From f25bd813bc694e996b4d2738aa81af3ebecfe897 Mon Sep 17 00:00:00 2001 From: Etienne Chassaing <60154720+cetiennec@users.noreply.github.com> Date: Tue, 8 Jul 2025 13:54:10 +0200 Subject: [PATCH] Adds mock CAN class for test and new layout, and Ploop setpoitn setting --- MockCAN.py | 35 +++++++ classCAN.py | 8 +- main.py | 42 +++------ static/index.html | 234 ++++++++++++++++++++++++++-------------------- 4 files changed, 186 insertions(+), 133 deletions(-) create mode 100644 MockCAN.py diff --git a/MockCAN.py b/MockCAN.py new file mode 100644 index 0000000..fdc6a71 --- /dev/null +++ b/MockCAN.py @@ -0,0 +1,35 @@ +import numpy as np + +PUs_states = [{"PU_MODE": "OFF","ploop_setpoint":0.0},{"PU_MODE": "OFF","ploop_setpoint":0.0},{"PU_MODE": "OFF","ploop_setpoint":0.0}] # data[i] is PU_i dictionnary + +# Placeholder for CAN backend +class CANBackend: + def __init__(self): + self.connected = False + + def connect(self, node_id: int, eds_path: str) -> bool: + # Placeholder for connection logic + self.connected = True + return True + + def shutdown(self): + self.connected = False + + + def read_current_state(self,pu_number: int): + # Placeholder for reading mode command + return PUs_states[pu_number-1] + + def send_thermal_loop_cleaning(self, mode: str): + # Placeholder for thermal loop cleaning + pass + + def send_state_command(self, state: str, pu_number : int, ploop_setpoint : float): + # Placeholder for sending mode command + PUs_states[pu_number-1] = {"PU_MODE": state, "ploop_setpoint":ploop_setpoint} + + + def get_latest_data(self): + # Placeholder for getting latest TPDO data + return {"FM2" : round(1000*np.random.random(),1),"PS1" : round(10*np.random.random(),2)} + diff --git a/classCAN.py b/classCAN.py index eda934c..6e32df0 100644 --- a/classCAN.py +++ b/classCAN.py @@ -56,8 +56,14 @@ class CANBackend: def get_latest_data(self): 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): + def send_state_command(self, state: str, pu_number: int, ploop_setpoint:float): + #TODO : link ploop_setpoint if not self.connected: raise RuntimeError("CAN not connected") diff --git a/main.py b/main.py index 0c0f916..9d2d5dc 100644 --- a/main.py +++ b/main.py @@ -1,8 +1,12 @@ -from fastapi import FastAPI, HTTPException +from fastapi import FastAPI, HTTPException, Query from fastapi.staticfiles import StaticFiles from fastapi.responses import HTMLResponse import logging -from classCAN import CANBackend # Your real backend +import platform +if platform.system() in ['Darwin']: # macOS or Windows + from MockCAN import CANBackend +else : + from classCAN import CANBackend # Your real backend app = FastAPI() logging.basicConfig(level=logging.INFO) @@ -29,50 +33,26 @@ def connect_toggle(): return {"connected": True} @app.post("/command/{state}/pu/{pu_number}") -def send_command(state: str, pu_number: int): +def send_command(state: str, pu_number: int, ploop_setpoint: float = Query(...)): """Send a state command to a specific PU.""" - logging.info(f"Sending state '{state}' to PU {pu_number}") + logging.info(f"Sending state '{state}' to PU {pu_number} with ploop_setpoint {ploop_setpoint}") try: - can_backend.send_state_command(state.upper(), pu_number) - return {"status": "success", "command": state.upper(), "pu": pu_number} + can_backend.send_state_command(state.upper(), pu_number, ploop_setpoint) + return {"status": "success", "command": state.upper(), "pu": pu_number, "ploop_setpoint": ploop_setpoint} except Exception as e: raise HTTPException(status_code=500, detail=str(e)) - - @app.get("/monitor") def get_monitor_data(): data = can_backend.get_latest_data() print(f"[MONITOR] Latest SDO: {data}") return { - "Qperm": [0.0, data.get("FM2", 0.0), 0.0], + "Qperm": [data.get("FM2", 0.0), data.get("FM2", 0.0), 0.0], "Pdilute": [data.get("PS1", 0.0), 0.0, 0.0], "Conductivity": [0.0, 0.0, 0.0], "Pro": [0.0, 0.0, 0.0], } - -# -# @app.get("/monitor") -# def get_monitor_values(): -# """ -# Returns current machine monitoring data. -# Expected structure: -# Q_perm: [Q1, Q2, Q3] -# Pdilute: [P1, P2, P3] -# Conductivity: [C1, C2, C3] -# Pro: [PR1, PR2, PR3] -# """ -# try: -# return { -# "Q_perm": can_backend.get_q_perm(), -# "Pdilute": can_backend.get_pdilute(), -# "Conductivity": can_backend.get_conductivity(), -# "Pro": can_backend.get_pro(), -# } -# except Exception as e: -# raise HTTPException(status_code=500, detail=str(e)) - @app.get("/can_status") def can_status(): """Return current CAN connection status.""" diff --git a/static/index.html b/static/index.html index 14da391..dcc0190 100644 --- a/static/index.html +++ b/static/index.html @@ -13,12 +13,15 @@ display: flex; flex-direction: column; height: 100vh; - background-color: #f4f4f9; + background-color: #121212; + color: white; } .header { - background-color: #333; - padding: 10px; - text-align: center; + background-color: #1e1e1e; + padding: 10px 20px; + display: flex; + justify-content: space-between; + align-items: center; } .connect-button { background-color: #ff4444; @@ -28,7 +31,9 @@ font-size: 16px; cursor: pointer; border-radius: 5px; - transition: background-color 0.3s; + display: flex; + align-items: center; + gap: 10px; } .connected { background-color: #00C851; @@ -43,132 +48,169 @@ overflow-y: auto; } .left-panel { + background-color: #1e1e1e; display: flex; flex-direction: column; - gap: 15px; - background-color: #e9ecef; + gap: 10px; } .mode-block { - border: 1px solid #ccc; + background-color: #333; padding: 15px; border-radius: 5px; - background-color: #fff; - box-shadow: 0 2px 4px rgba(0,0,0,0.1); - transition: background-color 0.3s; + display: flex; + flex-direction: column; + gap: 10px; } - .mode-block.active { - background-color: #a5d6a7; /* Light green background for active mode */ - } - .mode-block h2 { - margin-top: 0; - color: #333; + .pu-buttons { + display: flex; + gap: 10px; } .mode-block button { background-color: #4285F4; color: white; border: none; padding: 10px; - margin: 5px 0; cursor: pointer; border-radius: 5px; - width: 100%; font-size: 14px; transition: background-color 0.3s; + flex: 1; } .mode-block button:hover { background-color: #3367d6; } - .mode-block button i { - margin-right: 8px; + .mode-block button.active { + background-color: #00C851; + } + .pu-status { + margin-top: 20px; + } + .pu-item { + background-color: #333; + padding: 10px; + border-radius: 5px; + margin-bottom: 10px; + display: flex; + justify-content: space-between; + align-items: center; } .monitor-block { - border: 1px solid #ccc; + background-color: #333; padding: 15px; border-radius: 5px; - background-color: #fff; margin-bottom: 15px; - box-shadow: 0 2px 4px rgba(0,0,0,0.1); } .monitor-block h2 { margin-top: 0; - color: #333; + display: flex; + align-items: center; + gap: 10px; } .monitor-values { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; + margin-top: 10px; } .monitor-value { - border: 1px solid #ddd; + background-color: #444; padding: 10px; text-align: center; border-radius: 5px; - background-color: #f8f9fa; + } + .slider-container { + margin-top: 10px; + } + .slider-container label { + display: block; + margin-bottom: 5px; + } + .slider-container input { + width: 100%; }
+

Hydraulic Machine Control

-
-

IDLE Mode

- - - -
-
-

PRODUCTION Mode

- - - -
-
-

First Start

- - - +
+
+ + + +
-

Thermal Loop Cleaning

- +
+ + + +
+
+
+
+ + + +
+
+
+ + + 1.0 +
+
+ +
+
+
+ PU 1: Offline +
+
+ PU 2: Offline +
+
+ PU 3: Offline +
-

Q perm

+

Q Perm

-
Q1: 0.0
-
Q2: 0.0
-
Q3: 0.0
+
#1
0.0 L/h
+
#2
0.0 L/h
+
#3
0.0 L/h
-

Pdilute

+

P Dilute

-
P1: 0.0
-
P2: 0.0
-
P3: 0.0
+
#1
0.0 bar
+
#2
0.0 bar
+
#3
0.0 bar

Conductivity

-
C1: 0.0
-
C2: 0.0
-
C3: 0.0
+
#1
0.0 µS/cm
+
#2
0.0 µS/cm
+
#3
0.0 µS/cm
-

Pro

+

Pro

-
PR1: 0.0
-
PR2: 0.0
-
PR3: 0.0
+
#1
0.0 units
+
#2
0.0 units
+
#3
0.0 units
@@ -183,65 +225,55 @@ const connectButton = document.getElementById('connectButton'); if (data.connected) { connectButton.classList.add('connected'); - connectButton.innerHTML = ' Connected'; + connectButton.innerHTML = ' Disconnect'; } else { connectButton.classList.remove('connected'); - connectButton.innerHTML = ' Disconnected'; + connectButton.innerHTML = ' Connect'; } } - async function triggerPU(state, puNumber) { - const response = await fetch(`/command/${state}/pu/${puNumber}`, { - method: 'POST' - }); - const data = await response.json(); - console.log(data); + async function sendCommand(state, puNumber, buttonEl) { + const ploopSetpoint = document.getElementById('ploopSetpoint').value; + const response = await fetch(`/command/${state}/pu/${puNumber}?ploop_setpoint=${ploopSetpoint}`, { + method: 'POST' + }); - // Highlight the active mode block based on the state - document.querySelectorAll('.mode-block').forEach(block => { - block.classList.remove('active'); - }); + if (!response.ok) { + console.error('Failed to send command'); + return; + } - // Map state to lowercase div ID suffix - const stateToId = { - "IDLE": "idle", - "PRODUCTION": "prod", - "FIRST_START": "firstStart" - }; + const data = await response.json(); + console.log(data); - const blockId = stateToId[state]; - if (blockId) { - const block = document.getElementById(`${blockId}Mode`); - if (block) { - block.classList.add('active'); - } - } - } + // Reset all buttons + document.querySelectorAll('.mode-block button').forEach(button => { + button.classList.remove('active'); + }); + // Set the clicked button to active + buttonEl.classList.add('active'); + + // Update PU status + document.getElementById(`pu${puNumber}-status`).textContent = state; +} - async function thermalLoopCleaning(mode) { - const response = await fetch(`/mode/${mode}/thermal_loop`, { - method: 'POST' - }); - const data = await response.json(); - console.log(data); - } async function updateMonitorData() { const response = await fetch('/monitor'); const data = await response.json(); - updateMonitorValues('Qperm', data.Qperm); - updateMonitorValues('Pdilute', data.Pdilute); - updateMonitorValues('Conductivity', data.Conductivity); - updateMonitorValues('Pro', data.Pro); + updateMonitorValues('Qperm', data.Qperm, 'L/h'); + updateMonitorValues('Pdilute', data.Pdilute, 'bar'); + updateMonitorValues('Conductivity', data.Conductivity, 'µS/cm'); + updateMonitorValues('Pro', data.Pro, 'units'); } - function updateMonitorValues(id, values) { + function updateMonitorValues(id, values, unit) { const container = document.getElementById(id); const valueElements = container.querySelectorAll('.monitor-value'); valueElements.forEach((element, index) => { if (index < values.length) { - element.textContent = `${id.charAt(0)}${index + 1}: ${values[index]}`; + element.innerHTML = `#${index + 1}
${values[index]} ${unit}`; } }); }