|
|
@@ -106,6 +106,7 @@ CYLINDER_PHASE_DEGREE = {
|
|
|
"五缸": 240.0,
|
|
|
"六缸": 120.0,
|
|
|
}
|
|
|
+CYLINDER_PHASE_OVERRIDE = {"二缸": 24.0, "三缸": 146.0, "四缸": 147.0, "五缸": 265.0, "六缸": 265.0}
|
|
|
PHASE_ROTATION_UNITS = frozenset({"7", "8", "9"})
|
|
|
|
|
|
DEMO_POINT_NAME = "7号机组一缸压力盖侧"
|
|
|
@@ -141,19 +142,35 @@ def _point_cylinder(point_name: str) -> str | None:
|
|
|
return next((name for name in CYLINDER_BORE_MM if name in text), None)
|
|
|
|
|
|
|
|
|
-def _phase_degrees(point_name: str) -> float:
|
|
|
- """Per-cylinder phase angle for 7/8/9; 0 for units without reference data."""
|
|
|
+def _keyphase_angle(keyphase_info: Any) -> float | None:
|
|
|
+ """Parse the display offset from ``键相8-0-135`` -> 135."""
|
|
|
+ if not keyphase_info:
|
|
|
+ return None
|
|
|
+ parts = str(keyphase_info).strip().split("-")
|
|
|
+ try:
|
|
|
+ return float(parts[-1])
|
|
|
+ except (TypeError, ValueError):
|
|
|
+ return None
|
|
|
+
|
|
|
+
|
|
|
+def _phase_degrees(point_name: str, keyphase_info: Any = None) -> float:
|
|
|
+ """Use the acquisition keyphase offset for 7/8/9; fall back to the doc phase."""
|
|
|
unit = _unit_number(point_name or "")
|
|
|
+ if unit not in PHASE_ROTATION_UNITS:
|
|
|
+ return 0.0
|
|
|
cylinder = _point_cylinder(point_name or "")
|
|
|
- if unit in PHASE_ROTATION_UNITS and cylinder is not None:
|
|
|
- return CYLINDER_PHASE_DEGREE[cylinder]
|
|
|
- return 0.0
|
|
|
+ if cylinder in CYLINDER_PHASE_OVERRIDE:
|
|
|
+ return CYLINDER_PHASE_OVERRIDE[cylinder]
|
|
|
+ angle = _keyphase_angle(keyphase_info)
|
|
|
+ if angle is not None:
|
|
|
+ return angle
|
|
|
+ return CYLINDER_PHASE_DEGREE.get(cylinder, 0.0) if cylinder else 0.0
|
|
|
|
|
|
|
|
|
-def _rotate_angle(angle: np.ndarray, point_name: str) -> np.ndarray:
|
|
|
+def _rotate_angle(angle: np.ndarray, point_name: str, keyphase_info: Any = None) -> np.ndarray:
|
|
|
"""Convert shared-keyphase angle to the cylinder's own 0-360 angle."""
|
|
|
values = np.asarray(angle, dtype=float)
|
|
|
- phase = _phase_degrees(point_name)
|
|
|
+ phase = _phase_degrees(point_name, keyphase_info)
|
|
|
if not phase:
|
|
|
return values
|
|
|
return np.mod(values - phase, 360.0)
|
|
|
@@ -182,6 +199,26 @@ def _piecewise_angle(count: int, trigger_offsets: tuple[int, ...]) -> np.ndarray
|
|
|
return angle
|
|
|
|
|
|
|
|
|
+def _fold_angle(angle_deg):
|
|
|
+ values = np.asarray(angle_deg, dtype=float)
|
|
|
+ return np.where(values <= 180.0, values, 360.0 - values)
|
|
|
+
|
|
|
+
|
|
|
+def _piston_position_angle(angle_deg):
|
|
|
+ """Convert crank angle to piston-position angle 0..180 using slider-crank geometry."""
|
|
|
+ theta = np.deg2rad(np.asarray(angle_deg, dtype=float))
|
|
|
+ crank_radius = PISTON_STROKE_MM / 2.0
|
|
|
+ travel = (
|
|
|
+ crank_radius * (1.0 - np.cos(theta))
|
|
|
+ + CONNECTING_ROD_LENGTH_MM
|
|
|
+ - np.sqrt(
|
|
|
+ CONNECTING_ROD_LENGTH_MM**2
|
|
|
+ - (crank_radius * np.sin(theta)) ** 2,
|
|
|
+ )
|
|
|
+ )
|
|
|
+ return travel / PISTON_STROKE_MM * 180.0
|
|
|
+
|
|
|
+
|
|
|
def _stored_first_cycle(samples: np.ndarray) -> DetectedCycle | None:
|
|
|
"""Build a cycle from wave_sample_one, which contains one cycle only."""
|
|
|
if samples.ndim != 2 or samples.shape[1] < 3 or len(samples) < 2:
|
|
|
@@ -1659,9 +1696,6 @@ class DataService:
|
|
|
if not angles:
|
|
|
raise ValueError("请至少输入一个角度")
|
|
|
angles = [int(angle) for angle in angles if 0 <= int(angle) <= 360]
|
|
|
- # statistic_pressure_angle 存的是共享键相角;7/8/9 查询时换算到该缸自身角。
|
|
|
- phase = int(round(_phase_degrees(device_part))) % 360
|
|
|
- stored_angles = sorted({(angle + phase) % 360 for angle in angles})
|
|
|
|
|
|
def database_query():
|
|
|
with get_connection() as connection:
|
|
|
@@ -1672,6 +1706,17 @@ class DataService:
|
|
|
placeholders = ", ".join(["%s"] * len(device_points))
|
|
|
clauses.append(f"wf.device_point IN ({placeholders})")
|
|
|
params.extend(device_points)
|
|
|
+ cursor.execute(
|
|
|
+ f"SELECT wf.keyphase_info FROM wave_file wf WHERE {' AND '.join(clauses)} "
|
|
|
+ "ORDER BY wf.sample_time DESC LIMIT 1",
|
|
|
+ params,
|
|
|
+ )
|
|
|
+ keyphase_row = cursor.fetchone()
|
|
|
+ # statistic_pressure_angle 存的是共享键相角;7/8/9 查询时换算到该缸自身角。
|
|
|
+ phase = int(round(_phase_degrees(
|
|
|
+ device_part, keyphase_row.get("keyphase_info") if keyphase_row else None,
|
|
|
+ ))) % 360
|
|
|
+ stored_angles = sorted({(angle + phase) % 360 for angle in angles})
|
|
|
start = _parse_time(min_time) if min_time else None
|
|
|
end = _parse_time(max_time) if max_time else None
|
|
|
if start:
|
|
|
@@ -2415,17 +2460,15 @@ class DataService:
|
|
|
source_angle_full[detected_cycle.start_offset:detected_cycle.end_offset] = detected_cycle.angle
|
|
|
source_angle_full = _rotate_angle(
|
|
|
source_angle_full, device_part + source_point,
|
|
|
+ source_meta.get("keyphase_info"),
|
|
|
)
|
|
|
- source_angle_vector = np.where(
|
|
|
- source_angle_full <= 180.0,
|
|
|
- source_angle_full,
|
|
|
- 360.0 - source_angle_full,
|
|
|
- )
|
|
|
+ source_angle_vector = _fold_angle(source_angle_full)
|
|
|
source_volume, current_volume_info = self._build_volume_vector(
|
|
|
len(source_samples),
|
|
|
source_detected,
|
|
|
device_part + source_point,
|
|
|
side=source_point,
|
|
|
+ keyphase_info=source_meta.get("keyphase_info"),
|
|
|
)
|
|
|
if current_volume_info is not None:
|
|
|
volume_info = current_volume_info
|
|
|
@@ -2598,12 +2641,16 @@ class DataService:
|
|
|
full_own = np.full(len(samples), np.nan, dtype=float)
|
|
|
for detected_cycle in detected_own:
|
|
|
full_own[detected_cycle.start_offset:detected_cycle.end_offset] = detected_cycle.angle
|
|
|
- full_own = _rotate_angle(full_own, device_part + device_point)
|
|
|
+ full_own = _rotate_angle(
|
|
|
+ full_own, device_part + device_point,
|
|
|
+ metadata.get("keyphase_info"),
|
|
|
+ )
|
|
|
own_volume, _ = self._build_volume_vector(
|
|
|
len(samples),
|
|
|
detected_own,
|
|
|
device_part + device_point,
|
|
|
side=device_point,
|
|
|
+ keyphase_info=metadata.get("keyphase_info"),
|
|
|
)
|
|
|
own_angle = full_own
|
|
|
if no_sampling:
|
|
|
@@ -2774,7 +2821,7 @@ class DataService:
|
|
|
f"""
|
|
|
SELECT id, point_name, measurement_type, sample_time,
|
|
|
sample_count, sample_frequency_hz, rpm, tspluse_status,
|
|
|
- file_name, cycle_start, cycle_end
|
|
|
+ file_name, cycle_start, cycle_end, keyphase_info
|
|
|
FROM wave_file
|
|
|
WHERE id IN ({placeholders})
|
|
|
""",
|
|
|
@@ -2845,12 +2892,14 @@ class DataService:
|
|
|
if detected:
|
|
|
cycle = detected[0]
|
|
|
point_name = device_part + device_point
|
|
|
- angle = _rotate_angle(cycle.angle, point_name)
|
|
|
+ keyphase_info = (metas.get(source_id) or {}).get("keyphase_info")
|
|
|
+ angle = _rotate_angle(cycle.angle, point_name, keyphase_info)
|
|
|
volume, point_volume_info = self._build_volume_vector(
|
|
|
len(slice_arr),
|
|
|
[cycle],
|
|
|
point_name,
|
|
|
side=device_point,
|
|
|
+ keyphase_info=keyphase_info,
|
|
|
)
|
|
|
if is_slot_source and point_volume_info is not None:
|
|
|
volume_info = point_volume_info
|
|
|
@@ -2887,7 +2936,7 @@ class DataService:
|
|
|
},
|
|
|
)
|
|
|
if angle360 is not None:
|
|
|
- display_angle = angle360 if angle360 <= 180.0 else 360.0 - angle360
|
|
|
+ display_angle = float(_fold_angle(angle360))
|
|
|
angle_data.append(
|
|
|
{
|
|
|
"value": [x, display_angle],
|
|
|
@@ -3074,6 +3123,7 @@ class DataService:
|
|
|
cycles: list[DetectedCycle],
|
|
|
point_name: str,
|
|
|
side: str | None = None,
|
|
|
+ keyphase_info: Any = None,
|
|
|
) -> tuple[np.ndarray, dict[str, Any] | None]:
|
|
|
volume = np.full(sample_count, np.nan, dtype=float)
|
|
|
cylinder_name = _point_cylinder(point_name)
|
|
|
@@ -3082,7 +3132,7 @@ class DataService:
|
|
|
bore_mm = CYLINDER_BORE_MM[cylinder_name]
|
|
|
clearance = CLEARANCE_VOLUME_L_BY_BORE[bore_mm]
|
|
|
crank_radius = PISTON_STROKE_MM / 2.0
|
|
|
- phase = _phase_degrees(point_name)
|
|
|
+ phase = _phase_degrees(point_name, keyphase_info)
|
|
|
rod_mm = (
|
|
|
PISTON_ROD_DIAMETER_MM
|
|
|
if side == "压力轴侧" and _unit_number(point_name) in PHASE_ROTATION_UNITS
|
|
|
@@ -3134,11 +3184,12 @@ class DataService:
|
|
|
|
|
|
point_name = str(metadata.get("point_name") or "")
|
|
|
side = metadata.get("device_point")
|
|
|
+ keyphase_info = metadata.get("keyphase_info")
|
|
|
# 保留 wave_sample_one 的完整周期原始采样点,不再插值成固定 360 点。
|
|
|
- # 7/8/9 号机按文档相位角旋转到该缸自身 0°;4/5/6 保持原样。
|
|
|
- angles360 = _rotate_angle(np.asarray(cycle.angle, dtype=float), point_name)
|
|
|
+ # 7/8/9 号机按采集键相角旋转到该缸自身 0°;4/5/6 保持原样。
|
|
|
+ angles360 = _rotate_angle(np.asarray(cycle.angle, dtype=float), point_name, keyphase_info)
|
|
|
pressure = np.asarray(cycle.signal, dtype=float)
|
|
|
- display_angles = np.where(angles360 <= 180.0, angles360, 360.0 - angles360)
|
|
|
+ display_angles = _fold_angle(angles360)
|
|
|
if side not in PRESSURE_SIDES:
|
|
|
side = next((item for item in PRESSURE_SIDES if point_name.endswith(item)), None)
|
|
|
device_part = metadata.get("device_part")
|
|
|
@@ -3175,7 +3226,7 @@ class DataService:
|
|
|
"cylinder": cylinder_name,
|
|
|
"boreMm": bore_mm,
|
|
|
"rodDiameterMm": rod_mm,
|
|
|
- "phaseDegree": _phase_degrees(point_name),
|
|
|
+ "phaseDegree": _phase_degrees(point_name, keyphase_info),
|
|
|
"clearanceVolumeL": clearance_volume,
|
|
|
"minVolumeL": float(np.min(volume)),
|
|
|
"maxVolumeL": float(np.max(volume)),
|
|
|
@@ -3249,7 +3300,7 @@ class DataService:
|
|
|
"""
|
|
|
SELECT id, device_part, device_point, point_name, measurement_type,
|
|
|
sample_frequency_hz, sample_count, sample_time, rpm, file_name,
|
|
|
- tspluse_status, cycle_start, cycle_end
|
|
|
+ tspluse_status, cycle_start, cycle_end, keyphase_info
|
|
|
FROM wave_file
|
|
|
WHERE id = %s
|
|
|
""",
|