diff --git a/backend/main.py b/backend/main.py index 3ce5b18..6b3d653 100644 --- a/backend/main.py +++ b/backend/main.py @@ -411,6 +411,22 @@ async def save_opc_config(config: OpcConfig): @app.get("/api/opc/status") def opc_status(): + # 获取当前正在追踪的4个钢卷 + current_tracking_coils = [] + try: + from sqlite_sync import sqlite_get_coil_track + coils = sqlite_get_coil_track() + current_tracking_coils = [ + { + "coilid": coil.get("coilid", ""), + "sequencenb": coil.get("sequencenb", 0), + "rollprogramnb": coil.get("rollprogramnb", 0) + } + for coil in coils[:4] + ] + except Exception as e: + logger.warning(f"Failed to get current tracking coils: {e}") + return { "running": opc_service.running, "last_counter": opc_service.last_counter, @@ -418,6 +434,13 @@ def opc_status(): "log": opc_service.event_log[-50:], "track_state": opc_service.track_state, "write_counter_last": opc_service.write_counter_last, + "tracking_info": { + "first_coilid": opc_service.first_coilid, # 这个保持不变 + "last_tracked_coilid": opc_service.last_tracked_coilid, + "end_coilid": opc_service.end_coilid, + "tracking_ended": opc_service.tracking_ended, + "current_tracking_coils": current_tracking_coils # 当前正在追踪的4个钢卷 + } } @@ -664,3 +687,104 @@ async def simulate_signal2(): """模拟信号2触发 - 更新追踪表""" await opc_service._handle_signal2() return {"message": "信号2已触发"} + + +# ───────────────────────────────────────────── +# Manual Starting Coil Configuration +# ───────────────────────────────────────────── + +@app.post("/api/track/set-start-coil") +async def set_start_coil(data: dict): + """设置手动起始钢卷ID用于追踪""" + coilid = data.get("coilid") + if not coilid: + raise HTTPException(status_code=400, detail="coilid不能为空") + + # 验证钢卷是否存在 + conn = get_connection() + cursor = conn.cursor() + try: + cursor.execute("SELECT COUNT(*) FROM PLTM.PDI_PLTM WHERE COILID = :coilid", {"coilid": coilid}) + if cursor.fetchone()[0] == 0: + raise HTTPException(status_code=404, detail=f"钢卷 {coilid} 不存在") + finally: + cursor.close() + conn.close() + + # 设置起始钢卷 + opc_service.set_manual_start_coil(coilid) + + # 自动读取起始钢卷及后续3个钢卷到暂存表 + try: + await opc_service._handle_signal1() + except Exception as e: + # 即使读取失败也不影响设置成功 + logger.warning(f"Failed to load initial coils after setting start coil: {e}") + + return {"message": f"已设置起始钢卷: {coilid}"} + + +@app.get("/api/track/available-coils") +def get_available_coils( + page: int = Query(1, ge=1), + page_size: int = Query(20, ge=1, le=200), + coilid: Optional[str] = None, + sequencenb_min: Optional[int] = None, + sequencenb_max: Optional[int] = None +): + """获取可用的钢卷列表用于选择起始点(支持搜索和分页)""" + conn = get_connection() + cursor = conn.cursor() + try: + # 构建查询条件 + conditions = [] + params = {} + + if coilid: + conditions.append("COILID LIKE :coilid") + params["coilid"] = f"%{coilid}%" + + if sequencenb_min is not None: + conditions.append("SEQUENCENB >= :sequencenb_min") + params["sequencenb_min"] = sequencenb_min + + if sequencenb_max is not None: + conditions.append("SEQUENCENB <= :sequencenb_max") + params["sequencenb_max"] = sequencenb_max + + where_clause = "" + if conditions: + where_clause = "WHERE " + " AND ".join(conditions) + + # 查询总数 + count_sql = f"SELECT COUNT(*) FROM PLTM.PDI_PLTM {where_clause}" + cursor.execute(count_sql, params) + total = cursor.fetchone()[0] + + # 分页查询 + offset = (page - 1) * page_size + data_sql = f""" + SELECT COILID, SEQUENCENB, ROLLPROGRAMNB, STEEL_GRADE, + ENTRY_COIL_THICKNESS, ENTRY_COIL_WIDTH, ENTRY_COIL_WEIGHT + FROM PLTM.PDI_PLTM + {where_clause} + ORDER BY COILID ASC + """ + cursor.execute(data_sql, params) + + # 手动分页(因为Oracle的ROWNUM处理比较复杂) + all_rows = cursor.fetchall() + paginated_rows = all_rows[offset:offset + page_size] + + columns = [col[0].lower() for col in cursor.description] + rows = [dict(zip(columns, row)) for row in paginated_rows] + + return { + "data": rows, + "total": total, + "page": page, + "page_size": page_size + } + finally: + cursor.close() + conn.close() diff --git a/backend/opc_service.py b/backend/opc_service.py index de79a02..fb7404d 100644 --- a/backend/opc_service.py +++ b/backend/opc_service.py @@ -92,6 +92,8 @@ class OpcService: self.event_log: List[str] = [] self._stop_event = asyncio.Event() self._task: Optional[asyncio.Task] = None + self._need_fetch_next_batch: bool = False + self._need_initial_load: bool = False # ------------------------------------------------------------------ def _load_trackmap_nodes(self) -> Dict[str, str]: @@ -569,6 +571,32 @@ class OpcService: return value + # ------------------------------------------------------------------ + def set_manual_start_coil(self, coilid: str): + """Set manual starting coil ID for tracking and load initial coils. + + Args: + coilid: The coil ID to start tracking from + """ + self.first_coilid = coilid + self.last_tracked_coilid = None + self.end_coilid = None + self.tracking_ended = False + self.signal1_coils = [] + self._log(f"Manual start coil set to: {coilid}") + + # Clear any existing temp track data + try: + from sqlite_sync import sqlite_clear_coil_track + sqlite_clear_coil_track() + self._log("Cleared temp track data for manual start") + except Exception as exc: + self._log(f"Failed to clear temp track data: {exc}") + + # 标记需要自动读取起始钢卷(由外部调用_handle_signal1) + self._need_initial_load = True + + # ------------------------------------------------------------------ async def _handle_signal1(self): """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 @@ -663,7 +691,12 @@ class OpcService: 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}") + # 详细日志:显示当前读取的4个钢卷信息 + coil_details = [] + for i, coil in enumerate(coils): + coil_details.append(f"pos{i+1}:{coil['coilid']}(seq:{coil['sequencenb']})") + + self._log(f"Signal1: Saved {len(coils)} coils [{', '.join(coil_details)}], next_start: {self.first_coilid}, last_tracked: {self.last_tracked_coilid}") loop = asyncio.get_event_loop() await loop.run_in_executor(None, _do_fetch) @@ -760,7 +793,7 @@ class OpcService: conn.commit() 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: @@ -779,15 +812,10 @@ class OpcService: 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" - ) + # 还有钢卷,标记需要自动读取下一批 + self._log(f"Signal2: Will auto-trigger Signal1 logic to fetch next batch of coils") + self._need_fetch_next_batch = True except Exception as exc: self._log(f"Signal2: Check next coils failed: {exc}") finally: @@ -800,6 +828,15 @@ class OpcService: loop = asyncio.get_event_loop() await loop.run_in_executor(None, _do_update) + + # 如果标记需要读取下一批,则调用Signal1的逻辑 + if hasattr(self, '_need_fetch_next_batch') and self._need_fetch_next_batch: + self._need_fetch_next_batch = False + try: + await self._handle_signal1() + self._log("Signal2: Successfully fetched next batch of coils") + except Exception as exc: + self._log(f"Signal2: Failed to fetch next batch: {exc}") # ------------------------------------------------------------------ async def _update_oracle(self, position: Any, data: Dict[str, Any]): diff --git a/frontend/src/views/TrackCoil.vue b/frontend/src/views/TrackCoil.vue index 98eaa42..27318cd 100644 --- a/frontend/src/views/TrackCoil.vue +++ b/frontend/src/views/TrackCoil.vue @@ -1,11 +1,30 @@