From 765f737f2821803ba3b297ea5750f05f7ea09dff Mon Sep 17 00:00:00 2001 From: "AzureAD\\AniketSaha" Date: Thu, 10 Jul 2025 14:19:05 +0200 Subject: [PATCH] Added changes for mode reply from PU --- classCAN.py | 134 ++++++++++++++++++++++++++++++++++++---------------- main.py | 25 ++++------ 2 files changed, 102 insertions(+), 57 deletions(-) diff --git a/classCAN.py b/classCAN.py index 29d2b87..6720077 100644 --- a/classCAN.py +++ b/classCAN.py @@ -1,41 +1,55 @@ import threading import canopen import time +import os + class CANBackend: def __init__(self): self.network = None - self.node = None + self.nodes = {} # {1: RemoteNode(0x02), 2: RemoteNode(0x03), ...} self.connected = False - self.latest_data = {} self.lock = threading.Lock() - - # Initialize thread-related attributes self.polling_thread = None self.polling_active = False + self.latest_data = {} + self.eds_path = os.path.join(os.path.dirname(__file__), "eds_file", "processBoard_0.eds") - def connect(self, node_id, eds_path): + def connect(self): try: self.network = canopen.Network() - self.network.connect(bustype='socketcan', channel='can0', bitrate=250000) - self.node = canopen.RemoteNode(node_id, eds_path) - self.network.add_node(self.node) - self.node.nmt.state = 'PRE-OPERATIONAL' + self.network.connect(channel='can0', bustype='socketcan') + + # PU mapping: PU1->0x02, PU2->0x03, PU3->0x04 + node_map = { + 1: 0x02, + 2: 0x03, + 3: 0x04 + } + + for pu_number, node_id in node_map.items(): + node = canopen.RemoteNode(node_id, self.eds_path) + self.network.add_node(node) + self.nodes[pu_number] = node self.connected = True - self.latest_data = {} - - # Start polling thread self._start_sdo_polling() return True + except Exception as e: - print(f"[CAN ERROR] {e}") - self.connected = False + print(f"[CONNECT ERROR] {e}") return False + def shutdown(self): + self.polling_active = False + if self.network: + self.network.disconnect() + self.nodes.clear() + self.connected = False + def _start_sdo_polling(self): if self.polling_thread and self.polling_thread.is_alive(): - return # Already running + return self.polling_active = True self.polling_thread = threading.Thread(target=self._sdo_polling_loop, daemon=True) self.polling_thread.start() @@ -44,11 +58,14 @@ class CANBackend: while self.polling_active: with self.lock: try: - fm2 = self.node.sdo[0x2004][2].raw - ps1 = self.node.sdo[0x2005][1].raw - self.latest_data["FM2"] = fm2/100 - self.latest_data["PS1"] = ps1/1000 - print(f"[SDO POLL] FM2: {fm2}, PS1: {ps1}") + # Poll only PU1 (node ID 0x02) for live monitor values + node = self.nodes.get(1) + if node: + fm2 = node.sdo[0x2004][2].raw + ps1 = node.sdo[0x2005][1].raw + self.latest_data["FM2"] = fm2 / 100.0 + self.latest_data["PS1"] = ps1 / 1000.0 + print(f"[SDO POLL] FM2: {fm2}, PS1: {ps1}") except Exception as e: print(f"[SDO POLL ERROR] {e}") time.sleep(1.0) @@ -57,12 +74,39 @@ class CANBackend: with self.lock: return self.latest_data.copy() - def read_current_state(self,pu_number: int): - # Placeholder for reading mode command - #TODO : CODE IT - pass - - def send_state_command(self, state: str, pu_number: int, ploop_setpoint:float): + def read_current_state(self, pu_number: int): + try: + node = self.nodes.get(pu_number) + if node is None: + return "Offline" + state_raw = node.sdo[0x2000].raw + return self.decode_state(state_raw) + except Exception as e: + print(f"[PU{pu_number} READ ERROR] {e}") + return "Offline" + + def decode_state(self, state_val: int) -> str: + state_map = { + 0: "SYSTEM_MODE_INIT", + 1: "SYSTEM_MODE_OFF", + 2: "SYSTEM_MODE_READY", + 3: "SYSTEM_MODE_PRODUCTION", + 4: "SYSTEM_MODE_LOW_LOOP_PRESSURE", + 5: "SYSTEM_MODE_LOOP_CLEANING", + 6: "SYSTEM_MODE_HEATING_RO", + 7: "SYSTEM_MODE_RINSING_RO", + 8: "SYSTEM_MODE_HEATING_EDI", + 9: "SYSTEM_MODE_COOLING_EDI", + 10: "SYSTEM_MODE_RO_FLUSH", + 11: "SYSTEM_MODE_RO_RINSE", + 12: "SYSTEM_MODE_EDI_RINSE", + 15: "SYSTEM_MODE_FAIL_SAFE", + 16: "SYSTEM_MODE_FIRST_FLUSH", + 255: "SYSTEM_MODE_DEFAULT" + } + return state_map.get(state_val, f"UNKNOWN({state_val})") + + def send_state_command(self, state: str, pu_number: int, ploop_setpoint: float): if not self.connected: raise RuntimeError("CAN not connected") @@ -78,21 +122,23 @@ class CANBackend: raise ValueError(f"Invalid state: {state}") try: - print(f"[DEBUG] Writing state {state_map[state]} to index 0x2024, subindex {pu_number}") - self.node.sdo[0x2024][pu_number].raw = state_map[state] + node = self.nodes.get(pu_number) + if node is None: + raise ValueError(f"PU{pu_number} not connected") + + print(f"[DEBUG] Writing state {state_map[state]} to 0x2024:{pu_number}") + node.sdo[0x2024][pu_number].raw = state_map[state] + + print(f"[DEBUG] Writing ploop_setpoint {ploop_setpoint} to 0x2007") + node.sdo[0x2007].raw = int(ploop_setpoint * 100) - # Write the ploop setpoint to its own index/subindex - print(f"[DEBUG] Writing ploop_setpoint {ploop_setpoint} to index 0x2007") - self.node.sdo[0x2007].raw = int(ploop_setpoint * 100) - except Exception as e: - print(f"[SDO WRITE ERROR] Failed to write to 0x2007:{e}") + print(f"[SDO WRITE ERROR] PU{pu_number}: {e}") raise - - def send_thermal_loop_cleaning(self, mode: str): + def send_thermal_loop_cleaning(self, mode: str, pu_number: int): if not self.connected: - raise RuntimeError("Not connected to CAN") + raise RuntimeError("CAN not connected") mode_map = { "IDLE": 0, @@ -102,10 +148,14 @@ class CANBackend: if mode not in mode_map: raise ValueError(f"Invalid thermal loop mode: {mode}") - self.node.sdo[0x2024][0x01].raw = mode_map[mode] + try: + node = self.nodes.get(pu_number) + if node is None: + raise ValueError(f"PU{pu_number} not connected") - def shutdown(self): - self.polling_active = False - if self.network: - self.network.disconnect() - self.connected = False + print(f"[DEBUG] Sending thermal loop mode {mode} to 0x2024:{pu_number}") + node.sdo[0x2024][pu_number].raw = mode_map[mode] + + except Exception as e: + print(f"[THERMAL LOOP ERROR] PU{pu_number}: {e}") + raise diff --git a/main.py b/main.py index 304c37e..d5641d1 100644 --- a/main.py +++ b/main.py @@ -17,6 +17,7 @@ if platform.system() in ['Darwin']: # macOS or Windows from MockCAN import CANBackend else : from classCAN import CANBackend # Your real backend + app = FastAPI() app.add_middleware(SessionMiddleware, secret_key="your_super_secret_key") router = APIRouter() @@ -94,21 +95,17 @@ def monitor_page(request: Request): @app.post("/connect_toggle") def connect_toggle(): - """Toggle CAN connection.""" logging.info("Toggling CAN connection...") if can_backend.connected: can_backend.shutdown() return {"connected": False} else: - success = can_backend.connect( - node_id=0x02, - eds_path = os.path.join(os.path.dirname(__file__), "eds_file", "processBoard_0.eds") - ) - + success = can_backend.connect() if not success: raise HTTPException(status_code=500, detail="Connection failed.") return {"connected": True} + @app.post("/command/{state}/pu/{pu_number}") def send_command(state: str, pu_number: int, ploop_setpoint: float = Query(...)): """Send a state command to a specific PU.""" @@ -122,15 +119,13 @@ def send_command(state: str, pu_number: int, ploop_setpoint: float = Query(...)) @app.get("/api/pu_status") def get_pu_status(): - states = [can_backend.read_current_state(1),can_backend.read_current_state(2),can_backend.read_current_state(3)] - logging.info(f"Reading state '{states}' from PUs") - - # Replace this with real machine status - return JSONResponse(content={ - "PU1": states[0], - "PU2": states[1], - "PU3": states[2] - }) + states = { + "PU1": can_backend.read_current_state(1), + "PU2": can_backend.read_current_state(2), + "PU3": can_backend.read_current_state(3), + } + logging.info(f"[PU STATUS] {states}") + return JSONResponse(content=states) @app.get("/monitor")