feat(追踪系统): 修改钢卷追踪逻辑为按钢卷号升序处理

- 前端调整位置数量从5个改为4个并更新提示信息
- 后端修改SQL查询按COILID升序获取钢卷
- 新增按钢卷号范围查询功能
- 实现追踪状态管理,支持追踪结束检测和恢复
- 优化信号处理逻辑,支持末卷重复信号处理
This commit is contained in:
2026-04-13 14:57:44 +08:00
parent c609934156
commit fc8b38d44d
3 changed files with 201 additions and 33 deletions

View File

@@ -64,7 +64,10 @@ class OpcService:
self.signal2_last: Optional[int] = None
self.signal2_rise_time: Optional[datetime] = None
self.signal1_coils: List[Dict[str, Any]] = []
self.current_seq_start: int = 1
self.first_coilid: str = ""
self.last_tracked_coilid: str = ""
self.end_coilid: str = ""
self.tracking_ended: bool = False
# 状态机: WAIT_S1=等待信号1, WAIT_S2=等待信号2
self.track_state: str = "WAIT_S1"
self.last_counter_at_state_change: Optional[Any] = None
@@ -274,58 +277,138 @@ class OpcService:
self.signal2_last = signal2_value
async def _handle_signal1(self):
"""Handle signal1: fetch next 5 coils from PDI and save to SQLite temp table."""
from sqlite_sync import (
sqlite_get_max_sequencenb,
sqlite_get_coils_by_sequencenb_range,
sqlite_save_coils_to_track
)
"""Handle signal1: fetch next 4 coils from Oracle PDI table and save to SQLite temp table."""
from sqlite_sync import sqlite_save_coils_to_track, sqlite_clear_coil_track
def _do_fetch():
max_seq = sqlite_get_max_sequencenb()
if max_seq is None or max_seq < 1:
self._log("Signal1: No PDI data available")
# 如果追踪已结束,检查是否有新钢卷需要恢复追踪
self._log(f"Signal1: Called, first_coilid={repr(self.first_coilid)}, tracking_ended={self.tracking_ended}")
if self.tracking_ended:
try:
from database import get_connection
conn = get_connection()
cursor = conn.cursor()
try:
cursor.execute("SELECT COILID FROM PLTM.PDI_PLTM ORDER BY COILID ASC")
all_coil_ids = [r[0] for r in cursor.fetchall()]
# 使用 end_coilid 判断是否有新钢卷
check_coilid = self.end_coilid or self.last_tracked_coilid
if all_coil_ids and check_coilid:
has_new = any(cid > check_coilid for cid in all_coil_ids)
if has_new:
self._log("Signal1: New coils detected, resuming tracking")
self.tracking_ended = False
# 从最后一个追踪的钢卷之后继续
self.first_coilid = check_coilid
else:
self._log("Signal1: Tracking ended, no new coils, skip")
return
elif all_coil_ids and not check_coilid:
# 没有结束点记录但有计划,按首卷重新启动追踪
self._log("Signal1: Tracking ended without checkpoint, restarting from first plan")
self.tracking_ended = False
self.first_coilid = ""
else:
# 没有计划,保持结束状态
self._log("Signal1: Tracking ended, PDI empty, skip")
return
finally:
cursor.close()
conn.close()
except Exception as exc:
self._log(f"Signal1: Check new coils failed: {exc}")
return
try:
from database import get_connection
conn = get_connection()
cursor = conn.cursor()
try:
if self.first_coilid:
cursor.execute("""
SELECT COILID, SEQUENCENB, ROLLPROGRAMNB
FROM PLTM.PDI_PLTM
WHERE COILID >= :start_coilid
ORDER BY COILID ASC
""", {"start_coilid": self.first_coilid})
else:
cursor.execute("""
SELECT COILID, SEQUENCENB, ROLLPROGRAMNB
FROM PLTM.PDI_PLTM
ORDER BY COILID ASC
""")
rows = cursor.fetchmany(4)
coils = [{"coilid": r[0], "sequencenb": r[1], "rollprogramnb": r[2]} for r in rows]
finally:
cursor.close()
conn.close()
except Exception as exc:
self._log(f"Signal1: Failed to fetch from Oracle: {exc}")
return
start_seq = self.current_seq_start
end_seq = min(start_seq + 4, max_seq)
if end_seq < start_seq:
self._log(f"Signal1: Insufficient PDI data (max_seq={max_seq}, start={start_seq})")
return
coils = sqlite_get_coils_by_sequencenb_range(start_seq, end_seq)
if len(coils) == 0:
self._log(f"Signal1: No coils found")
self._log("Signal1: No more coils in PDI, ending tracking")
sqlite_clear_coil_track()
self.tracking_ended = True
return
if len(coils) == 1 and coils[0]["coilid"] == self.first_coilid:
self._log("Signal1: Only the same coil exists, saving it and setting for next query")
self.signal1_coils = coils
sqlite_save_coils_to_track(coils)
# 保持 first_coilid 不变,下次查询会从同一个位置继续
# Signal2 触发后,检查是否还有更多钢卷
return
self.signal1_coils = coils
sqlite_save_coils_to_track(coils)
self._log(f"Signal1: Saved {len(coils)} coils (seq {start_seq}-{end_seq}) to temp table")
if len(coils) >= 2:
self.first_coilid = coils[1]["coilid"]
else:
self.first_coilid = coils[0]["coilid"]
self.current_seq_start = start_seq + 1
self.last_tracked_coilid = coils[0]["coilid"]
self._log(f"Signal1: Saved {len(coils)} coils, next_start: {self.first_coilid}, last_tracked: {self.last_tracked_coilid}")
loop = asyncio.get_event_loop()
await loop.run_in_executor(None, _do_fetch)
async def _handle_signal2(self):
"""Handle signal2: update CMPT_PL_TRACKMAP with temp table data."""
from sqlite_sync import sqlite_get_coil_track
from sqlite_sync import sqlite_clear_coil_track, sqlite_get_coil_track
from database import get_connection
def _do_update():
coils = sqlite_get_coil_track()
if not coils:
self._log("Signal2: No coils in temp track table")
return
try:
conn = get_connection()
cursor = conn.cursor()
try:
if not coils:
self._log("Signal2: No coils in temp track table, clearing all positions")
for pos in range(1, 11):
cursor.execute("SELECT COUNT(*) FROM PLTM.CMPT_PL_TRACKMAP WHERE POSITION = :pos", {"pos": pos})
if cursor.fetchone()[0] > 0:
cursor.execute("UPDATE PLTM.CMPT_PL_TRACKMAP SET COILID = NULL, TOM = SYSDATE WHERE POSITION = :pos", {"pos": pos})
conn.commit()
# 标记追踪结束
if self.signal1_coils:
self.end_coilid = self.signal1_coils[0]["coilid"]
self.tracking_ended = True
# 清空 first_coilid下次 Signal1 触发时从头查询
self.first_coilid = ""
self._log(f"Signal2: Tracking ended (no coils), end_coilid={self.end_coilid}")
return
cursor.execute("SELECT COILID FROM PLTM.CMPT_PL_TRACKMAP WHERE POSITION = 1")
prev_pos1_coil = cursor.fetchone()
prev_pos1_coilid = prev_pos1_coil[0] if prev_pos1_coil else None
coil_count = len(coils)
for i in range(5):
for i in range(4):
target_position = i + 1
coil_index = target_position - 1
if coil_index >= 0 and coil_index < coil_count:
@@ -357,8 +440,63 @@ class OpcService:
INSERT INTO PLTM.CMPT_PL_TRACKMAP (POSITION, COILID, TOM)
VALUES (:position, NULL, SYSDATE)
""", {"position": target_position})
for pos in range(5, 11):
cursor.execute("SELECT COUNT(*) FROM PLTM.CMPT_PL_TRACKMAP WHERE POSITION = :pos", {"pos": pos})
if cursor.fetchone()[0] > 0:
cursor.execute("UPDATE PLTM.CMPT_PL_TRACKMAP SET COILID = NULL, TOM = SYSDATE WHERE POSITION = :pos", {"pos": pos})
new_pos1_coil = coils[0]["coilid"] if coils else None
pos1_unchanged = prev_pos1_coilid and new_pos1_coil and prev_pos1_coilid == new_pos1_coil
if pos1_unchanged:
# 只在“末卷(单卷)重复信号”时清空,避免 6-7 阶段提前进入清空/重写循环
if coil_count == 1:
for pos in range(1, 11):
cursor.execute("SELECT COUNT(*) FROM PLTM.CMPT_PL_TRACKMAP WHERE POSITION = :pos", {"pos": pos})
if cursor.fetchone()[0] > 0:
cursor.execute("UPDATE PLTM.CMPT_PL_TRACKMAP SET COILID = NULL, TOM = SYSDATE WHERE POSITION = :pos", {"pos": pos})
sqlite_clear_coil_track()
self.end_coilid = coils[0]["coilid"]
self.last_tracked_coilid = self.end_coilid
self.tracking_ended = True
self.first_coilid = ""
self._log("Signal2: Final single coil repeated, cleared all positions and ended tracking")
else:
self._log("Signal2: Coil at position 1 unchanged but not final single-coil stage, keep tracking")
conn.commit()
self._log(f"Signal2: Updated 5 positions (coils: {coil_count})")
self._log(f"Signal2: Updated 4 positions (coils: {coil_count})")
# 检查是否还有更多钢卷可以追踪
self._log(f"Signal2: Checking remaining coils, first_coilid={repr(self.first_coilid)}")
if self.first_coilid:
try:
cursor.execute("""
SELECT COILID FROM PLTM.PDI_PLTM
WHERE COILID >= :start_coilid
ORDER BY COILID ASC
""", {"start_coilid": self.first_coilid})
all_remaining = cursor.fetchall()
remaining_count = len(all_remaining)
self._log(f"Signal2: Check remaining after {self.first_coilid}: count={remaining_count}, coils={[r[0] for r in all_remaining]}")
if remaining_count == 0:
# 没有更多钢卷了,标记追踪结束
self.end_coilid = self.first_coilid
self.last_tracked_coilid = self.first_coilid
self.tracking_ended = True
self._log(f"Signal2: No more coils, tracking ended, end_coilid={self.end_coilid}")
elif remaining_count >= 2:
# 有2个以上钢卷保持 first_coilid 不变,让 Signal1 处理
self._log(f"Signal2: >=2 coils remain, first_coilid unchanged, waiting for Signal1")
else:
# 只有一个钢卷时先展示为“7(1个)”,下一次重复信号再清空到 NULL
self._log(
f"Signal2: One coil remains ({all_remaining[0][0]}), "
"waiting one more cycle before final clear"
)
except Exception as exc:
self._log(f"Signal2: Check next coils failed: {exc}")
finally:
cursor.close()
conn.close()

View File

@@ -337,7 +337,7 @@ def sqlite_get_coils_by_sequencenb_range(start_seq: int, end_seq: int) -> List[D
SELECT COILID, SEQUENCENB, ROLLPROGRAMNB
FROM PDI_PLTM
WHERE SEQUENCENB >= ? AND SEQUENCENB <= ?
ORDER BY SEQUENCENB DESC
ORDER BY COILID ASC
""", (start_seq, end_seq))
rows = cursor.fetchall()
return [{"coilid": r[0], "sequencenb": r[1], "rollprogramnb": r[2]} for r in rows]
@@ -345,12 +345,42 @@ def sqlite_get_coils_by_sequencenb_range(start_seq: int, end_seq: int) -> List[D
sc.close()
def sqlite_get_coils_by_coilid(start_coilid: str, count: int = 4) -> List[Dict[str, Any]]:
sc = get_sqlite()
try:
cursor = sc.execute("""
SELECT COILID, SEQUENCENB, ROLLPROGRAMNB
FROM PDI_PLTM
WHERE COILID >= ?
ORDER BY COILID ASC
LIMIT ?
""", (start_coilid, count))
rows = cursor.fetchall()
return [{"coilid": r[0], "sequencenb": r[1], "rollprogramnb": r[2]} for r in rows]
finally:
sc.close()
def sqlite_get_first_coils(count: int = 4) -> List[Dict[str, Any]]:
sc = get_sqlite()
try:
cursor = sc.execute("""
SELECT COILID, SEQUENCENB, ROLLPROGRAMNB
FROM PDI_PLTM
ORDER BY COILID ASC
LIMIT ?
""", (count,))
rows = cursor.fetchall()
return [{"coilid": r[0], "sequencenb": r[1], "rollprogramnb": r[2]} for r in rows]
finally:
sc.close()
def sqlite_save_coils_to_track(coils: List[Dict[str, Any]]):
sc = get_sqlite()
try:
sc.execute("DELETE FROM COIL_TRACK_TEMP")
reversed_coils = list(reversed(coils))
for i, coil in enumerate(reversed_coils):
for i, coil in enumerate(coils):
sc.execute("""
INSERT INTO COIL_TRACK_TEMP (COILID, SEQUENCENB, ROLLPROGRAMNB, POSITION)
VALUES (?, ?, ?, ?)