Removed feedvalve logic (obsolete) and documented the backend class
This commit is contained in:
parent
3db1f96489
commit
feb48c6a19
81
classCAN.py
81
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")
|
||||
|
||||
|
|
|
|||
19
main.py
19
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():
|
||||
|
|
|
|||
|
|
@ -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 @@
|
|||
<div class="pu-item"><span>PU 2: </span><span id="pu2-status">Offline</span></div>
|
||||
<div class="pu-item"><span>PU 3: </span><span id="pu3-status">Offline</span></div>
|
||||
</div>
|
||||
<div class="feed-valve-buttons">
|
||||
<button onclick="setFeedValve(0, this)">Feed Valve 0%</button>
|
||||
<button onclick="setFeedValve(50, this)">Feed Valve 50%</button>
|
||||
<button onclick="setFeedValve(100, this)">Feed Valve 100%</button>
|
||||
</div>
|
||||
<div class="button-group">
|
||||
<button onclick="runAutoTest(1)">Automatic Test PU1</button>
|
||||
<button onclick="runAutoTest(2)">Automatic Test PU2</button>
|
||||
|
|
@ -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();
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user