From feb48c6a1915c5dbbecf5bad0247e6913f620b20 Mon Sep 17 00:00:00 2001 From: "AzureAD\\AniketSaha" Date: Fri, 25 Jul 2025 11:59:57 +0200 Subject: [PATCH] Removed feedvalve logic (obsolete) and documented the backend class --- classCAN.py | 81 +++++++++++++++++++++++++++++++++--------- main.py | 19 ---------- templates/control.html | 31 ---------------- 3 files changed, 65 insertions(+), 66 deletions(-) diff --git a/classCAN.py b/classCAN.py index 2736c65..4feabf3 100644 --- a/classCAN.py +++ b/classCAN.py @@ -5,24 +5,38 @@ import time import os class CANBackend: + """ + CANBackend handles CANopen communication with two Process Units (PU1 and PU2). + It listens for TPDOs, tracks real-time data, and sends SDO control commands + such as setting system modes and setpoints. + """ + def __init__(self, eds_file=None): + """ + Initialize the CAN backend. + + :param eds_file: Optional path to the EDS file to use for the master node. + """ self.network = None self.master_node = None - self.master_node_id = 0x16 + self.master_node_id = 0x16 # Docking board node ID self.nodes = {} self.connected = False self.lock = threading.Lock() self.latest_data = { - 1: {}, # PU1 - 2: {}, # PU2 + 1: {}, # PU1 data + 2: {}, # PU2 data } - if eds_file is None: - self.eds_path = os.path.join(os.path.dirname(__file__), "eds_file", "dockingBoard_0.eds") - else: - self.eds_path = eds_file + # Default EDS file path + self.eds_path = eds_file if eds_file else os.path.join(os.path.dirname(__file__), "eds_file", "dockingBoard_0.eds") def connect(self): + """ + Connects to the CAN network and sets up the master node. + + :return: True if successful, False otherwise. + """ try: self.network = canopen.Network() self.network.connect(channel='can0', bustype='socketcan') @@ -32,7 +46,7 @@ class CANBackend: self.master_node.nmt.state = 'OPERATIONAL' self.nodes[0] = self.master_node - # Start TPDO listener thread + # Start background listener for TPDOs self.listener_active = True self.bus = can.interface.Bus(channel='can0', bustype='socketcan') self.listener_thread = threading.Thread(target=self._can_listener_loop, daemon=True) @@ -46,6 +60,9 @@ class CANBackend: return False def shutdown(self): + """ + Cleanly shuts down the CAN backend and listener. + """ self.listener_active = False if self.network: self.network.disconnect() @@ -55,6 +72,10 @@ class CANBackend: self.connected = False def _can_listener_loop(self): + """ + Background thread to listen for CAN TPDO messages. + Updates the internal state for PU1 and PU2 based on COB-ID. + """ while self.listener_active: msg = self.bus.recv(1.0) if msg is None: @@ -64,8 +85,8 @@ class CANBackend: cob_id = msg.arbitration_id data = msg.data - with self.lock: # Ensure thread safety - + with self.lock: + # ========== PU1 COB-IDs ========== if cob_id == 0x2A6 and len(data) >= 8: self.latest_data[1].update({ "FM1": int.from_bytes(data[0:2], 'little') / 100.0 * 60.0, @@ -81,7 +102,6 @@ class CANBackend: "PS3": int.from_bytes(data[4:6], 'little') / 1000.0, }) - elif cob_id == 0x2A8 and len(data) >= 8: self.latest_data[1].update({ "MV02_sp": int.from_bytes(data[0:2], 'little') / 100.0, @@ -107,13 +127,13 @@ class CANBackend: "MV06": data[4], "MV07": data[5], "MV08": data[6], - "MV09": data[7], # if used + "MV09": data[7], }) elif cob_id == 0x2AB and len(data) >= 1: self.latest_data[1]["PU1_STATE"] = data[0] - # ======== PU2 COB IDs =============== + # ========== PU2 COB-IDs ========== elif cob_id == 0x2AD and len(data) >= 8: self.latest_data[2].update({ "FM1": int.from_bytes(data[0:2], 'little') / 100.0 * 60.0, @@ -129,7 +149,6 @@ class CANBackend: "PS3": int.from_bytes(data[4:6], 'little') / 1000.0, }) - elif cob_id == 0x2AF and len(data) >= 8: self.latest_data[2].update({ "MV02_sp": int.from_bytes(data[0:2], 'little') / 100.0, @@ -155,7 +174,7 @@ class CANBackend: "MV06": data[4], "MV07": data[5], "MV08": data[6], - "MV09": data[7], # if used + "MV09": data[7], }) elif cob_id == 0x2B2 and len(data) >= 1: @@ -164,16 +183,33 @@ class CANBackend: except Exception as e: print(f"[TPDO PARSE ERROR] {e}") - def get_latest_data(self, pu_number: int): + """ + Retrieve the latest real-time data for the given PU. + + :param pu_number: 1 or 2 + :return: Dictionary of flow, pressure, valve data + """ with self.lock: return self.latest_data.get(pu_number, {}).copy() def read_current_state(self, pu_number: int): + """ + Get the system mode (decoded string) of the given PU. + + :param pu_number: 1 or 2 + :return: State name or "Offline" + """ state_val = self.latest_data.get(pu_number, {}).get(f"PU{pu_number}_STATE") return self.decode_state(state_val) if state_val is not None else "Offline" def decode_state(self, state_val: int) -> str: + """ + Convert system state integer to human-readable label. + + :param state_val: Integer value from TPDO + :return: String state name + """ state_map = { 0: "SYSTEM_MODE_INIT", 1: "SYSTEM_MODE_OFF", @@ -195,6 +231,13 @@ class CANBackend: return state_map.get(state_val, f"UNKNOWN({state_val})") def send_state_command(self, state: str, pu_number: int, ploop_setpoint: float): + """ + Send the PU state and pressure loop setpoint to the master node. + + :param state: State string (e.g., "PRODUCTION") + :param pu_number: PU1 or PU2 + :param ploop_setpoint: Float setpoint in bar (will be scaled) + """ if not self.connected: raise RuntimeError("CAN not connected") @@ -229,6 +272,12 @@ class CANBackend: raise def send_thermal_loop_cleaning(self, mode: str, pu_number: int): + """ + Activate or deactivate thermal loop cleaning mode. + + :param mode: "IDLE" or "ACTIVE" + :param pu_number: PU1 or PU2 + """ if not self.connected: raise RuntimeError("CAN not connected") diff --git a/main.py b/main.py index fb32254..8ce8360 100644 --- a/main.py +++ b/main.py @@ -17,7 +17,6 @@ from typing import Optional, Dict, Any from fastapi import Query import asyncio import datetime -from valveBackend import ValveBackend import csv from collections import deque import numpy as np @@ -39,9 +38,6 @@ app.add_middleware(SessionMiddleware, secret_key="your_super_secret_key") router = APIRouter() templates = Jinja2Templates(directory="templates") can_backend = CANBackend() -valve_backend = ValveBackend( - eds_file="/home/hmi/Desktop/HMI/eds_file/inletvalveboard.eds" -) # Serve static files (HTML, JS, CSS) app.mount("/static", StaticFiles(directory="static"), name="static") @@ -175,10 +171,6 @@ def connect_toggle(): return {"connected": False} else: success = can_backend.connect() - try: - valve_backend.connect() - except Exception as e: - print(f"Connection error : {e}") if not success: raise HTTPException(status_code=500, detail="Connection failed.") @@ -267,17 +259,6 @@ def can_status(): return {"connected": can_backend.connected} -@app.post("/command/feed_valve") -def feedvalve_control(MV01_opening: int = Query(...)): - """Control MV01 feed valve""" - global DEFAULT_FEED_VALVE - DEFAULT_FEED_VALVE = MV01_opening - valve_backend.send_command(MV01_opening) - logging.info(f"Feed valve opening to {MV01_opening}") - return {"status": "ok"} - - - # LOCAL RECORDER @app.post("/start_recording") async def start_recording(): diff --git a/templates/control.html b/templates/control.html index 5c745db..5b5ea39 100644 --- a/templates/control.html +++ b/templates/control.html @@ -192,23 +192,6 @@ .monitor-link:hover { background-color: #0056b3; } - .feed-valve-buttons { - display: flex; - gap: 10px; - margin-top: 10px; - } - .feed-valve-buttons button { - flex: 1; - padding: 10px; - border: none; - border-radius: 5px; - cursor: pointer; - background-color: #444; - color: white; - } - .feed-valve-buttons button.active { - background-color: #00C851; - } .monitor-pu-buttons { display: flex; gap: 10px; @@ -313,11 +296,6 @@
PU 2: Offline
PU 3: Offline
-
- - - -
@@ -475,15 +453,6 @@ // }); } - - async function setFeedValve(opening, buttonEl) { - await fetch(`/command/feed_valve?MV01_opening=${opening}`, {method: 'POST'}); - document.querySelectorAll('.feed-valve-buttons button').forEach(btn => { - btn.classList.remove('active'); - }); - buttonEl.classList.add('active'); - } - async function fetchPUStatus() { const response = await fetch("/api/pu_status"); const data = await response.json();