updated hydrobuddy web interface
landing page + 1st configuration + overview when configured (hoffentlich lol) ltack kram rausgehauen (Testbench code)
This commit is contained in:
parent
57ad33718c
commit
7a3e2830a0
36 changed files with 5075 additions and 8077 deletions
|
|
@ -1,692 +1,255 @@
|
|||
import { useCallback, useEffect } from 'preact/hooks';
|
||||
import { $isUpdaterModalOpen, $isSuccessPopupOpen, $isErrorPopupOpen, $websocket, $wsConnectionStatus, $updateErrorMessages, $p4FirmwareProgress, $p4WebinterfaceProgress, $stm32FirmwareProgress, $c6FirmwareProgress, $updateState, $updatedTargets, $availableUpdates, type UpdateInfo, UpdateStatus, TargetUpdateStatus, TotalUpdateStatus } from './AppProviderStore';
|
||||
import { stateSetters, request_available_updates } from './AppProviderStore';
|
||||
import UpdaterModal from './UpdaterModal';
|
||||
import { useEffect } from "preact/hooks";
|
||||
import { $websocket } from "./WebSocketAtom";
|
||||
import { stateSetters } from "./AppProviderStore";
|
||||
import {
|
||||
$scanned_wifi_networks,
|
||||
$current_network_configuration,
|
||||
$network_status_display,
|
||||
$network_config_error,
|
||||
request_network_config,
|
||||
type WiFiNetwork,
|
||||
authmodeToSecurity,
|
||||
ConnectionMode,
|
||||
} from "./NetworkManagerStore";
|
||||
|
||||
// Helper function to get target index for update types
|
||||
const getTargetIndex = (core: string) => {
|
||||
switch (core.toLowerCase()) {
|
||||
case 'p4 webinterface': return 1; // P4 webinterface (first)
|
||||
case 'p4 firmware': return 2; // P4 firmware (second)
|
||||
case 'stm32': return 0; // STM32 firmware
|
||||
case 'c6': return 3; // C6 firmware (last)
|
||||
default: return -1;
|
||||
}
|
||||
};
|
||||
export { $websocket } from "./WebSocketAtom";
|
||||
|
||||
// Heartbeat configuration
|
||||
const HEARTBEAT_INTERVAL = 1500; // Send ping every 1 second
|
||||
const HEARTBEAT_TIMEOUT = 700; // Wait 1 second for pong response
|
||||
const HEARTBEAT_INTERVAL = 1500; // Send ping every 1.5 seconds
|
||||
const HEARTBEAT_TIMEOUT = 1000; // Wait 1 second for pong response
|
||||
const MAX_MISSED_HEARTBEATS = 5; // Close connection after 5 missed pongs
|
||||
|
||||
// Heartbeat state
|
||||
let heartbeatInterval: ReturnType<typeof setInterval> | null = null;
|
||||
let heartbeatTimeout: ReturnType<typeof setTimeout> | null = null;
|
||||
let missedHeartbeats = 0;
|
||||
let _lastPingSentAt = 0;
|
||||
|
||||
// Reconnect state
|
||||
let isReconnecting = false;
|
||||
let reconnectTimeout: ReturnType<typeof setTimeout> | null = null;
|
||||
let reload_site_after_webinterface_update = false;
|
||||
|
||||
// Restart detection state
|
||||
let disconnectDetected = false;
|
||||
let heartbeatsAfterReconnect = 0;
|
||||
const HEARTBEATS_NEEDED_AFTER_RESTART = 3;
|
||||
|
||||
function clearHeartbeat() {
|
||||
if (heartbeatInterval) {
|
||||
clearInterval(heartbeatInterval);
|
||||
heartbeatInterval = null;
|
||||
}
|
||||
if (heartbeatTimeout) {
|
||||
clearTimeout(heartbeatTimeout);
|
||||
heartbeatTimeout = null;
|
||||
}
|
||||
missedHeartbeats = 0;
|
||||
if (heartbeatInterval) {
|
||||
clearInterval(heartbeatInterval);
|
||||
heartbeatInterval = null;
|
||||
}
|
||||
if (heartbeatTimeout) {
|
||||
clearTimeout(heartbeatTimeout);
|
||||
heartbeatTimeout = null;
|
||||
}
|
||||
missedHeartbeats = 0;
|
||||
}
|
||||
|
||||
function clearReconnect() {
|
||||
if (reconnectTimeout) {
|
||||
clearTimeout(reconnectTimeout);
|
||||
reconnectTimeout = null;
|
||||
}
|
||||
isReconnecting = false;
|
||||
if (reconnectTimeout) {
|
||||
clearTimeout(reconnectTimeout);
|
||||
reconnectTimeout = null;
|
||||
}
|
||||
isReconnecting = false;
|
||||
}
|
||||
|
||||
function scheduleReconnect() {
|
||||
if (isReconnecting) {
|
||||
////console.log("Reconnect already scheduled, skipping");
|
||||
return;
|
||||
}
|
||||
|
||||
isReconnecting = true;
|
||||
////console.log("Scheduling reconnect in 1 second");
|
||||
reconnectTimeout = setTimeout(() => {
|
||||
connectWebSocket();
|
||||
isReconnecting = false;
|
||||
}, 1000);
|
||||
if (isReconnecting) {
|
||||
return;
|
||||
}
|
||||
isReconnecting = true;
|
||||
reconnectTimeout = setTimeout(() => {
|
||||
connectWebSocket();
|
||||
isReconnecting = false;
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
function sendHeartbeat() {
|
||||
if ($websocket.get() && ($websocket.get() as WebSocket).readyState === ($websocket.get() as WebSocket).OPEN) {
|
||||
// Send ping message
|
||||
const pingMessage = JSON.stringify({ message_type: "ping" });
|
||||
try {
|
||||
($websocket.get() as WebSocket).send(pingMessage);
|
||||
} catch (error) {
|
||||
console.error("Failed to send heartbeat ping:", error);
|
||||
missedHeartbeats++;
|
||||
return;
|
||||
}
|
||||
|
||||
// Set timeout for pong response
|
||||
if (heartbeatTimeout) {
|
||||
clearTimeout(heartbeatTimeout);
|
||||
heartbeatTimeout = null;
|
||||
}
|
||||
heartbeatTimeout = setTimeout(() => {
|
||||
missedHeartbeats++;
|
||||
if (missedHeartbeats >= MAX_MISSED_HEARTBEATS) {
|
||||
console.error("Too many missed heartbeats, closing connection");
|
||||
|
||||
// Check if we're in restarting state - this indicates device restart
|
||||
const currentUpdateState = $updateState.get();
|
||||
if (currentUpdateState === 'restarting') {
|
||||
//console.log("🔄 Disconnect detected during restart - marking for restart detection");
|
||||
disconnectDetected = true;
|
||||
heartbeatsAfterReconnect = 0;
|
||||
}
|
||||
|
||||
// Wichtig: Erst Heartbeat stoppen
|
||||
clearHeartbeat();
|
||||
|
||||
// Dann WebSocket schließen und null setzen
|
||||
|
||||
if ($websocket.get()) {
|
||||
($websocket.get() as WebSocket).close();
|
||||
stateSetters.setWebSocketConnectionStatus("Disconnected");
|
||||
$websocket.set(null);
|
||||
}
|
||||
}
|
||||
}, HEARTBEAT_TIMEOUT);
|
||||
const ws = $websocket.get();
|
||||
if (ws && (ws as WebSocket).readyState === (ws as WebSocket).OPEN) {
|
||||
// Send ping message via WebSocket
|
||||
const pingMessage = JSON.stringify({ message_type: "ping" });
|
||||
try {
|
||||
(ws as WebSocket).send(pingMessage);
|
||||
_lastPingSentAt = performance.now();
|
||||
} catch (error) {
|
||||
missedHeartbeats++;
|
||||
return;
|
||||
}
|
||||
|
||||
// Set timeout for pong response
|
||||
if (heartbeatTimeout) {
|
||||
clearTimeout(heartbeatTimeout);
|
||||
heartbeatTimeout = null;
|
||||
}
|
||||
heartbeatTimeout = setTimeout(() => {
|
||||
missedHeartbeats++;
|
||||
if (missedHeartbeats >= MAX_MISSED_HEARTBEATS) {
|
||||
// First stop the heartbeat, then close + null the socket
|
||||
clearHeartbeat();
|
||||
const cur = $websocket.get();
|
||||
if (cur) {
|
||||
(cur as WebSocket).close();
|
||||
stateSetters.setWebSocketConnectionStatus("Disconnected");
|
||||
$websocket.set(null);
|
||||
}
|
||||
}
|
||||
}, HEARTBEAT_TIMEOUT);
|
||||
}
|
||||
}
|
||||
|
||||
function startHeartbeat() {
|
||||
clearHeartbeat(); // Clear any existing heartbeat
|
||||
|
||||
heartbeatInterval = setInterval(() => {
|
||||
sendHeartbeat();
|
||||
}, HEARTBEAT_INTERVAL);
|
||||
clearHeartbeat(); // Clear any existing heartbeat
|
||||
heartbeatInterval = setInterval(() => {
|
||||
sendHeartbeat();
|
||||
}, HEARTBEAT_INTERVAL);
|
||||
}
|
||||
|
||||
function onOpen() {
|
||||
stateSetters.setWebSocketConnectionStatus("Connected");
|
||||
//console.log("WebSocket erfolgreich verbunden");
|
||||
missedHeartbeats = 0;
|
||||
|
||||
// If we reconnected after detecting a restart, start counting heartbeats
|
||||
if (disconnectDetected) {
|
||||
//console.log("🔄 Reconnected after restart - starting heartbeat counter");
|
||||
heartbeatsAfterReconnect = 0;
|
||||
}
|
||||
|
||||
// Start heartbeat mechanism
|
||||
startHeartbeat();
|
||||
// Sende initiale Statusanfrage
|
||||
if ($websocket.get() !== null) {
|
||||
const initial_state_request = JSON.stringify({ message_type: "initial_state_request" });
|
||||
try {
|
||||
($websocket.get() as WebSocket).send(initial_state_request);
|
||||
} catch (error) {
|
||||
console.error("Failed to send initial state request:", error);
|
||||
}
|
||||
|
||||
// Request update status immediately to catch any missed completion messages
|
||||
setTimeout(() => {
|
||||
if ($websocket.get() && ($websocket.get() as WebSocket).readyState === ($websocket.get() as WebSocket).OPEN) {
|
||||
const update_status_request = JSON.stringify({ message_type: "request_update_status" });
|
||||
try {
|
||||
($websocket.get() as WebSocket).send(update_status_request);
|
||||
//console.log("Requested update status on reconnection");
|
||||
} catch (error) {
|
||||
console.error("Failed to send update status request:", error);
|
||||
}
|
||||
}
|
||||
}, 500); // Reduced from 1000ms to 500ms for faster response
|
||||
stateSetters.setWebSocketConnectionStatus("Connected");
|
||||
missedHeartbeats = 0;
|
||||
|
||||
// send request for available updates
|
||||
setTimeout(() => {
|
||||
if ($websocket.get() && ($websocket.get() as WebSocket).readyState === ($websocket.get() as WebSocket).OPEN) {
|
||||
request_available_updates();
|
||||
}
|
||||
}, 1500);
|
||||
}
|
||||
// Start heartbeat mechanism
|
||||
startHeartbeat();
|
||||
|
||||
// Ask the device for its current network config so the UI knows whether to show
|
||||
// the first-time wizard or the configured overview.
|
||||
setTimeout(() => {
|
||||
request_network_config();
|
||||
}, 100);
|
||||
}
|
||||
|
||||
function onMessage(message: MessageEvent) {
|
||||
////console.log("WebSocket message received:", message.data.toString());
|
||||
// Verarbeite JSON Nachrichten
|
||||
const dataFromServer = JSON.parse(message.data.toString());
|
||||
if (dataFromServer.message_type === "pong") {
|
||||
// reset missed heartbeats and clear pending timeout
|
||||
if (heartbeatTimeout) {
|
||||
clearTimeout(heartbeatTimeout);
|
||||
heartbeatTimeout = null;
|
||||
}
|
||||
missedHeartbeats = 0;
|
||||
//console.log("Heartbeat pong received");
|
||||
|
||||
// Count heartbeats after reconnect for restart detection
|
||||
if (disconnectDetected) {
|
||||
heartbeatsAfterReconnect++;
|
||||
//console.log(`🔄 Heartbeat ${heartbeatsAfterReconnect}/${HEARTBEATS_NEEDED_AFTER_RESTART} after restart`);
|
||||
|
||||
if (heartbeatsAfterReconnect >= HEARTBEATS_NEEDED_AFTER_RESTART) {
|
||||
//console.log("🎉 Device restart completed - closing success popup and resetting state");
|
||||
disconnectDetected = false;
|
||||
disconnectDetected = false;
|
||||
heartbeatsAfterReconnect = 0;
|
||||
|
||||
// Close success popup and reset state
|
||||
stateSetters.setSuccessPopupOpen(false);
|
||||
stateSetters.setUpdateState('initial');
|
||||
if (reload_site_after_webinterface_update) {
|
||||
// Hard reload ohne Cache
|
||||
//window.location.reload();
|
||||
}
|
||||
}
|
||||
}
|
||||
// Verarbeite JSON Nachrichten
|
||||
const dataFromServer = JSON.parse(message.data.toString());
|
||||
|
||||
if (dataFromServer.message_type === "pong") {
|
||||
// reset missed heartbeats and clear pending timeout
|
||||
if (heartbeatTimeout) {
|
||||
clearTimeout(heartbeatTimeout);
|
||||
heartbeatTimeout = null;
|
||||
}
|
||||
else if (dataFromServer.message_type === "state_update" || dataFromServer.message_type === "initial_state_response") {
|
||||
|
||||
// If this is the initial state response, also request update status
|
||||
if (dataFromServer.message_type === "initial_state_response") {
|
||||
//console.log("📊 Initial state received, requesting update status...");
|
||||
setTimeout(() => {
|
||||
if ($websocket.get() && ($websocket.get() as WebSocket).readyState === ($websocket.get() as WebSocket).OPEN) {
|
||||
const update_status_request = JSON.stringify({ message_type: "request_update_status" });
|
||||
try {
|
||||
($websocket.get() as WebSocket).send(update_status_request);
|
||||
//console.log("Requested update status after initial state");
|
||||
} catch (error) {
|
||||
console.error("Failed to send update status request:", error);
|
||||
}
|
||||
}
|
||||
}, 200); // Short delay to ensure connection is stable
|
||||
}
|
||||
|
||||
if (dataFromServer.free_internal_heap) {
|
||||
stateSetters.setFreeInternalHeap(dataFromServer.free_internal_heap);
|
||||
}
|
||||
// DCDC State aktualisieren
|
||||
if (dataFromServer.dcdcStateMachine) {
|
||||
stateSetters.setDCDCState(dataFromServer.dcdcStateMachine.dcdc_state);
|
||||
stateSetters.setPOWERGOOD(dataFromServer.dcdcStateMachine.POWERGOOD);
|
||||
stateSetters.setDcdcErrorStatus(dataFromServer.dcdcStateMachine.error_flag);
|
||||
stateSetters.setREF_value(dataFromServer.dcdcStateMachine.ref_value);
|
||||
}
|
||||
|
||||
// Laser State aktualisieren
|
||||
if (dataFromServer.laserStateMachine) {
|
||||
// Laser state update
|
||||
stateSetters.setLaserState(dataFromServer.laserStateMachine.laser_state);
|
||||
stateSetters.setModulationInputMethod(dataFromServer.laserStateMachine.laser_state);
|
||||
stateSetters.setREF_calibrated(dataFromServer.laserStateMachine.is_REF_calibrated);
|
||||
stateSetters.setHAS_DUTY_LOOKUP_TABLE(dataFromServer.laserStateMachine.has_duty_lookup_table);
|
||||
stateSetters.setLaserErrorStatus(dataFromServer.laserStateMachine.error_flag);
|
||||
stateSetters.setLASERGOOD(dataFromServer.laserStateMachine.LASERGOOD);
|
||||
stateSetters.setLaserSetTemp(dataFromServer.laserStateMachine.set_temp);
|
||||
stateSetters.setLaserMaxTemp(dataFromServer.laserStateMachine.max_temp);
|
||||
stateSetters.setInterlock(dataFromServer.laserStateMachine.interlock);
|
||||
stateSetters.setModulationInput(dataFromServer.laserStateMachine.modulationInput);
|
||||
stateSetters.setSetCurrent(dataFromServer.laserStateMachine.set_current);
|
||||
stateSetters.setMaxCurrent(dataFromServer.laserStateMachine.max_current);
|
||||
stateSetters.setThresholdCurrent(dataFromServer.laserStateMachine.threshold_current);
|
||||
stateSetters.setCurrentReading(dataFromServer.laserStateMachine.current_reading);
|
||||
stateSetters.setLaserTemp(dataFromServer.laserStateMachine.laser_temp);
|
||||
}
|
||||
|
||||
// Driver State aktualisieren
|
||||
if (dataFromServer.driverStateMachine) {
|
||||
stateSetters.setDriverTemp(dataFromServer.driverStateMachine.driver_temp);
|
||||
stateSetters.setDriverMaxTemp(dataFromServer.driverStateMachine.max_temp);
|
||||
stateSetters.setVccVoltage(dataFromServer.driverStateMachine.vcc_voltage);
|
||||
stateSetters.setPdVoltage(dataFromServer.driverStateMachine.pd_voltage);
|
||||
}
|
||||
} else if (dataFromServer.message_type === "calibration_table") {
|
||||
// Log raw data to see the actual format of values
|
||||
//console.log("Raw calibration message:", message.data.toString().substring(0, 500) + "...");
|
||||
// Parse values and format to always show with decimal places
|
||||
const parsedValues = dataFromServer.values ?
|
||||
dataFromServer.values.map((val: any) => {
|
||||
// Convert to number and keep as float
|
||||
const numVal = Number(val);
|
||||
|
||||
return numVal;
|
||||
}) : [];
|
||||
|
||||
// Erstelle einen neuen Array mit korrekten Indizes
|
||||
// Fülle Indizes 0-499 mit Nullen und füge dann die tatsächlichen Werte ein
|
||||
const minDuty = dataFromServer.min_duty || 500;
|
||||
const completedValues = [];
|
||||
|
||||
// Fülle Nullen für Indizes von 0 bis (minDuty-1)
|
||||
for (let i = 0; i < minDuty; i++) {
|
||||
completedValues.push(0);
|
||||
}
|
||||
for (let i = 0; i < parsedValues.length; i++) {
|
||||
completedValues.push(parsedValues[i]);
|
||||
}
|
||||
|
||||
// Store values in state - use the completed values with correct indices
|
||||
stateSetters.setCalibrationTableData(
|
||||
completedValues,
|
||||
minDuty,
|
||||
dataFromServer.max_duty || 3500, (dataFromServer.points_count || parsedValues.length) + minDuty
|
||||
);
|
||||
} else if (dataFromServer.message_type === "update_information") {
|
||||
//console.log("Update information received");
|
||||
//console.log("Raw update_information data:", dataFromServer);
|
||||
// Update Information
|
||||
if (dataFromServer.update_available) {
|
||||
const infos: UpdateInfo[] = [];
|
||||
if (dataFromServer.update_available.p4_firmware) {
|
||||
infos.push({
|
||||
core: 'p4 firmware',
|
||||
currentVersion: dataFromServer.update_available.p4_firmware.current_version,
|
||||
newVersion: dataFromServer.update_available.p4_firmware.new_version,
|
||||
icon: 'p4 firmware',
|
||||
hasProgressBar: true,
|
||||
updateAvailable: true,
|
||||
});
|
||||
//console.log("P4 Firmware update available");
|
||||
}
|
||||
if (dataFromServer.update_available.p4_webinterface) {
|
||||
infos.push({
|
||||
core: "p4 webinterface",
|
||||
currentVersion: dataFromServer.update_available.p4_webinterface.current_version,
|
||||
newVersion: dataFromServer.update_available.p4_webinterface.new_version,
|
||||
icon: "p4 webinterface",
|
||||
hasProgressBar: true,
|
||||
updateAvailable: true,
|
||||
});
|
||||
//console.log("P4 Webinterface update available");
|
||||
}
|
||||
if (dataFromServer.update_available.stm32_firmware) {
|
||||
infos.push({
|
||||
core: "STM32",
|
||||
currentVersion: dataFromServer.update_available.stm32_firmware.current_version,
|
||||
newVersion: dataFromServer.update_available.stm32_firmware.new_version,
|
||||
icon: "stm32",
|
||||
hasProgressBar: true,
|
||||
updateAvailable: true,
|
||||
});
|
||||
//console.log("STM32 Firmware update available");
|
||||
}
|
||||
if (dataFromServer.update_available.c6_firmware) {
|
||||
infos.push({
|
||||
core: "C6",
|
||||
currentVersion: dataFromServer.update_available.c6_firmware.current_version,
|
||||
newVersion: dataFromServer.update_available.c6_firmware.new_version,
|
||||
icon: "c6",
|
||||
hasProgressBar: true,
|
||||
updateAvailable: true,
|
||||
});
|
||||
//console.log("C6 Firmware update available");
|
||||
}
|
||||
stateSetters.setAvailableUpdates(infos);
|
||||
stateSetters.setUpdateChangelog(dataFromServer.changelog);
|
||||
}
|
||||
// Alte einzelne Progress-Message-Handler wurden durch complete_update_status ersetzt
|
||||
|
||||
} else if (dataFromServer.message_type === "complete_update_status") {
|
||||
//console.log("📊 Complete update status received:", dataFromServer);
|
||||
|
||||
// Check if we're currently in restarting state - if so, ignore status updates
|
||||
const currentUpdateState = $updateState.get() as any;
|
||||
if (currentUpdateState === 'restarting') {
|
||||
//console.log("🔄 Ignoring complete update status during RESTARTING state - countdown in progress");
|
||||
return;
|
||||
}
|
||||
|
||||
// Process the complete status information for all targets
|
||||
if (dataFromServer.targets) {
|
||||
const targets = dataFromServer.targets;
|
||||
|
||||
// STM32 Status
|
||||
if (targets.stm32) {
|
||||
const status = targets.stm32.status;
|
||||
const progress = targets.stm32.progress || 0;
|
||||
const updateAvailable = targets.stm32.update_available || false;
|
||||
|
||||
// Convert string status to TargetUpdateStatus enum
|
||||
let statusEnum = TargetUpdateStatus.NO_UPDATE;
|
||||
switch (status) {
|
||||
case 'waiting': statusEnum = TargetUpdateStatus.WAITING; break;
|
||||
case 'started': statusEnum = TargetUpdateStatus.IN_PROGRESS; break;
|
||||
case 'in_progress': statusEnum = TargetUpdateStatus.IN_PROGRESS; break;
|
||||
case 'completed': statusEnum = TargetUpdateStatus.COMPLETED; break;
|
||||
case 'error': statusEnum = TargetUpdateStatus.ERROR; break;
|
||||
case 'no_update': statusEnum = TargetUpdateStatus.NO_UPDATE; break;
|
||||
case 'restarting': statusEnum = TargetUpdateStatus.IN_PROGRESS; break;
|
||||
default: statusEnum = TargetUpdateStatus.NO_UPDATE; break;
|
||||
}
|
||||
|
||||
stateSetters.setStm32FirmwareProgress(progress, statusEnum);
|
||||
//console.log(`📊 STM32: status=${status}, progress=${progress}, available=${updateAvailable}`);
|
||||
}
|
||||
|
||||
// P4 Webinterface Status
|
||||
if (targets.p4_webinterface) {
|
||||
const status = targets.p4_webinterface.status;
|
||||
const progress = targets.p4_webinterface.progress || 0;
|
||||
const updateAvailable = targets.p4_webinterface.update_available || false;
|
||||
|
||||
let statusEnum = TargetUpdateStatus.NO_UPDATE;
|
||||
switch (status) {
|
||||
case 'waiting': statusEnum = TargetUpdateStatus.WAITING; break;
|
||||
case 'started': statusEnum = TargetUpdateStatus.IN_PROGRESS; break;
|
||||
case 'in_progress': statusEnum = TargetUpdateStatus.IN_PROGRESS; break;
|
||||
case 'completed': statusEnum = TargetUpdateStatus.COMPLETED; break;
|
||||
case 'error': statusEnum = TargetUpdateStatus.ERROR; break;
|
||||
case 'no_update': statusEnum = TargetUpdateStatus.NO_UPDATE; break;
|
||||
case 'restarting': statusEnum = TargetUpdateStatus.IN_PROGRESS; break;
|
||||
default: statusEnum = TargetUpdateStatus.NO_UPDATE; break;
|
||||
}
|
||||
|
||||
stateSetters.setP4WebinterfaceProgress(progress, statusEnum);
|
||||
//console.log(`📊 P4 Webinterface: status=${status}, progress=${progress}, available=${updateAvailable}`);
|
||||
}
|
||||
|
||||
// P4 Firmware Status
|
||||
if (targets.p4_firmware) {
|
||||
const status = targets.p4_firmware.status;
|
||||
const progress = targets.p4_firmware.progress || 0;
|
||||
const updateAvailable = targets.p4_firmware.update_available || false;
|
||||
|
||||
let statusEnum = TargetUpdateStatus.NO_UPDATE;
|
||||
switch (status) {
|
||||
case 'waiting': statusEnum = TargetUpdateStatus.WAITING; break;
|
||||
case 'started': statusEnum = TargetUpdateStatus.IN_PROGRESS; break;
|
||||
case 'in_progress': statusEnum = TargetUpdateStatus.IN_PROGRESS; break;
|
||||
case 'completed': statusEnum = TargetUpdateStatus.COMPLETED; break;
|
||||
case 'error': statusEnum = TargetUpdateStatus.ERROR; break;
|
||||
case 'no_update': statusEnum = TargetUpdateStatus.NO_UPDATE; break;
|
||||
case 'restarting': statusEnum = TargetUpdateStatus.IN_PROGRESS; break;
|
||||
default: statusEnum = TargetUpdateStatus.NO_UPDATE; break;
|
||||
}
|
||||
|
||||
stateSetters.setP4FirmwareProgress(progress, statusEnum);
|
||||
//console.log(`📊 P4 Firmware: status=${status}, progress=${progress}, available=${updateAvailable}`);
|
||||
}
|
||||
|
||||
// C6 Status
|
||||
if (targets.c6) {
|
||||
const status = targets.c6.status;
|
||||
const progress = targets.c6.progress || 0;
|
||||
const updateAvailable = targets.c6.update_available || false;
|
||||
|
||||
let statusEnum = TargetUpdateStatus.NO_UPDATE;
|
||||
switch (status) {
|
||||
case 'waiting': statusEnum = TargetUpdateStatus.WAITING; break;
|
||||
case 'started': statusEnum = TargetUpdateStatus.IN_PROGRESS; break;
|
||||
case 'in_progress': statusEnum = TargetUpdateStatus.IN_PROGRESS; break;
|
||||
case 'completed': statusEnum = TargetUpdateStatus.COMPLETED; break;
|
||||
case 'error': statusEnum = TargetUpdateStatus.ERROR; break;
|
||||
case 'no_update': statusEnum = TargetUpdateStatus.NO_UPDATE; break;
|
||||
case 'restarting': statusEnum = TargetUpdateStatus.IN_PROGRESS; break;
|
||||
default: statusEnum = TargetUpdateStatus.NO_UPDATE; break;
|
||||
}
|
||||
|
||||
stateSetters.setC6FirmwareProgress(progress, statusEnum);
|
||||
//console.log(`📊 C6: status=${status}, progress=${progress}, available=${updateAvailable}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Check if there's an ongoing update
|
||||
if (dataFromServer.update_in_progress) {
|
||||
//console.log("🔄 Update in progress detected, setting total_update_status to updating");
|
||||
stateSetters.setUpdateInProgress(true);
|
||||
stateSetters.setTotalUpdateStatus(TotalUpdateStatus.UPDATING);
|
||||
|
||||
// Use the total_update_status from backend if available
|
||||
if (dataFromServer.total_update_status !== undefined) {
|
||||
const totalStatus = dataFromServer.total_update_status;
|
||||
//console.log("📊 Backend total_update_status:", totalStatus);
|
||||
|
||||
switch (totalStatus) {
|
||||
case 0: // INITIAL
|
||||
stateSetters.setTotalUpdateStatus(TotalUpdateStatus.INITIAL);
|
||||
break;
|
||||
case 1: // STARTED
|
||||
stateSetters.setTotalUpdateStatus(TotalUpdateStatus.STARTED);
|
||||
break;
|
||||
case 2: // UPDATING
|
||||
stateSetters.setTotalUpdateStatus(TotalUpdateStatus.UPDATING);
|
||||
break;
|
||||
case 3: // SUCCESS
|
||||
stateSetters.setTotalUpdateStatus(TotalUpdateStatus.SUCCESS);
|
||||
break;
|
||||
case 4: // ERROR
|
||||
stateSetters.setTotalUpdateStatus(TotalUpdateStatus.ERROR);
|
||||
break;
|
||||
case 5: // RESTARTING
|
||||
stateSetters.setTotalUpdateStatus(TotalUpdateStatus.RESTARTING);
|
||||
break;
|
||||
default:
|
||||
console.warn("Unknown total_update_status:", totalStatus);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Open the updater modal, if it is not already open and no success/error popups are showing
|
||||
const isModalOpen = $isUpdaterModalOpen.get();
|
||||
const isSuccessPopupOpen = $isSuccessPopupOpen.get();
|
||||
const isErrorPopupOpen = $isErrorPopupOpen.get();
|
||||
/*console.log("🎯 Modal decision matrix:", {
|
||||
isModalOpen,
|
||||
totalUpdateStatus: currentUpdateState,
|
||||
isSuccessPopupOpen,
|
||||
isErrorPopupOpen,
|
||||
shouldOpen: !isModalOpen && !isSuccessPopupOpen && !isErrorPopupOpen && currentUpdateState !== 'success' && currentUpdateState !== 'restarting'
|
||||
});*/
|
||||
|
||||
// Don't open updater modal if success/error popups are open or we're in success/restarting state
|
||||
const shouldOpenModal = !isModalOpen && !isSuccessPopupOpen && !isErrorPopupOpen &&
|
||||
currentUpdateState !== 'success' && currentUpdateState !== 'restarting';
|
||||
if (shouldOpenModal) {
|
||||
//console.log("🚀 Opening updater modal - conditions met");
|
||||
stateSetters.setUpdaterModalOpen(true);
|
||||
} else {
|
||||
//console.log("🚫 Skipping updater modal - conditions not met");
|
||||
}
|
||||
} else {
|
||||
//console.log("✅ No update in progress, checking if all available updates are completed");
|
||||
|
||||
// Check if all targets with update_available: true are completed
|
||||
let allAvailableUpdatesCompleted = true;
|
||||
let hasAnyAvailableUpdates = false;
|
||||
|
||||
if (dataFromServer.targets.stm32 && dataFromServer.targets.stm32.update_available) {
|
||||
hasAnyAvailableUpdates = true;
|
||||
if (dataFromServer.targets.stm32.status !== 'completed') {
|
||||
allAvailableUpdatesCompleted = false;
|
||||
}
|
||||
}
|
||||
if (dataFromServer.targets.p4_webinterface && dataFromServer.targets.p4_webinterface.update_available) {
|
||||
hasAnyAvailableUpdates = true;
|
||||
if (dataFromServer.targets.p4_webinterface.status !== 'completed') {
|
||||
allAvailableUpdatesCompleted = false;
|
||||
}
|
||||
}
|
||||
if (dataFromServer.targets.p4_firmware && dataFromServer.targets.p4_firmware.update_available) {
|
||||
hasAnyAvailableUpdates = true;
|
||||
if (dataFromServer.targets.p4_firmware.status !== 'completed') {
|
||||
allAvailableUpdatesCompleted = false;
|
||||
}
|
||||
}
|
||||
if (dataFromServer.targets.c6 && dataFromServer.targets.c6.update_available) {
|
||||
hasAnyAvailableUpdates = true;
|
||||
if (dataFromServer.targets.c6.status !== 'completed') {
|
||||
allAvailableUpdatesCompleted = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasAnyAvailableUpdates && allAvailableUpdatesCompleted) {
|
||||
//console.log("🎉 All available updates completed successfully, setting total_update_status to success");
|
||||
stateSetters.setUpdateInProgress(false);
|
||||
stateSetters.setTotalUpdateStatus(TotalUpdateStatus.SUCCESS);
|
||||
} else if (!hasAnyAvailableUpdates) {
|
||||
//console.log("ℹ️ No updates available, resetting total_update_status to initial");
|
||||
stateSetters.setUpdateInProgress(false);
|
||||
if (currentUpdateState !== 'success' && currentUpdateState !== 'error' && currentUpdateState !== 'restarting') {
|
||||
stateSetters.setTotalUpdateStatus(TotalUpdateStatus.INITIAL);
|
||||
}
|
||||
} else {
|
||||
//console.log("⏳ Some available updates still in progress, keeping current total_update_status");
|
||||
}
|
||||
}
|
||||
|
||||
} else if (dataFromServer.message_type === "response_update_status") {
|
||||
console.log("📊 Update status response received:", dataFromServer);
|
||||
|
||||
// Check if we're currently in restarting state - if so, ignore status updates
|
||||
const currentUpdateState = $updateState.get() as any;
|
||||
if (currentUpdateState === 'restarting') {
|
||||
//console.log("🔄 Ignoring update status during RESTARTING state - countdown in progress");
|
||||
return;
|
||||
}
|
||||
|
||||
// Always process the complete status information, regardless of update_in_progress
|
||||
//console.log("🔄 Processing complete update status for all targets");
|
||||
|
||||
// Set progress and status for each update type if provided
|
||||
if (dataFromServer.p4_firmware_progress !== undefined) {
|
||||
const status = dataFromServer.p4_firmware_status || 0;
|
||||
stateSetters.setP4FirmwareProgress(dataFromServer.p4_firmware_progress, status);
|
||||
//console.log(`📊 P4 Firmware: progress=${dataFromServer.p4_firmware_progress}, status=${status}`);
|
||||
}
|
||||
if (dataFromServer.p4_webinterface_progress !== undefined) {
|
||||
const status = dataFromServer.p4_webinterface_status || 0;
|
||||
stateSetters.setP4WebinterfaceProgress(dataFromServer.p4_webinterface_progress, status);
|
||||
//console.log(`📊 P4 Webinterface: progress=${dataFromServer.p4_webinterface_progress}, status=${status}`);
|
||||
}
|
||||
if (dataFromServer.stm32_firmware_progress !== undefined) {
|
||||
const status = dataFromServer.stm32_firmware_status || 0;
|
||||
stateSetters.setStm32FirmwareProgress(dataFromServer.stm32_firmware_progress, status);
|
||||
//console.log(`📊 STM32 Firmware: progress=${dataFromServer.stm32_firmware_progress}, status=${status}`);
|
||||
}
|
||||
if (dataFromServer.c6_firmware_progress !== undefined) {
|
||||
const status = dataFromServer.c6_firmware_status || 0;
|
||||
stateSetters.setC6FirmwareProgress(dataFromServer.c6_firmware_progress, status);
|
||||
//console.log(`📊 C6 Firmware: progress=${dataFromServer.c6_firmware_progress}, status=${status}`);
|
||||
}
|
||||
|
||||
// Store updated_targets array from server for UpdaterModal
|
||||
if (dataFromServer.updated_targets && Array.isArray(dataFromServer.updated_targets)) {
|
||||
stateSetters.setUpdatedTargets(dataFromServer.updated_targets);
|
||||
//console.log("🎯 Updated targets:", dataFromServer.updated_targets);
|
||||
}
|
||||
|
||||
// Set current target if provided
|
||||
if (dataFromServer.current_target !== undefined) {
|
||||
stateSetters.setCurrentUpdateTargetIndex(dataFromServer.current_target);
|
||||
//console.log("🎯 Current target:", dataFromServer.current_target);
|
||||
}
|
||||
|
||||
// Check if there's an ongoing update
|
||||
if (dataFromServer.update_in_progress) {
|
||||
//console.log("🔄 Update in progress detected, setting state to updating");
|
||||
stateSetters.setUpdateInProgress(true);
|
||||
stateSetters.setUpdateState("updating");
|
||||
|
||||
// Open the updater modal, if it is not already open and no success/error popups are showing
|
||||
const isModalOpen = $isUpdaterModalOpen.get();
|
||||
const isSuccessPopupOpen = $isSuccessPopupOpen.get();
|
||||
const isErrorPopupOpen = $isErrorPopupOpen.get();
|
||||
/*console.log("🎯 Modal decision matrix:", {
|
||||
isModalOpen,
|
||||
updateState: currentUpdateState,
|
||||
isSuccessPopupOpen,
|
||||
isErrorPopupOpen,
|
||||
shouldOpen: !isModalOpen && !isSuccessPopupOpen && !isErrorPopupOpen && currentUpdateState !== 'success' && currentUpdateState !== 'restarting'
|
||||
});*/
|
||||
|
||||
// Don't open updater modal if success/error popups are open or we're in success/restarting state
|
||||
const shouldOpenModal = !isModalOpen && !isSuccessPopupOpen && !isErrorPopupOpen &&
|
||||
currentUpdateState !== 'success' && currentUpdateState !== 'restarting';
|
||||
if (shouldOpenModal) {
|
||||
//console.log("🚀 Opening updater modal - conditions met");
|
||||
stateSetters.setUpdaterModalOpen(true);
|
||||
} else {
|
||||
//console.log("🚫 Skipping updater modal - conditions not met");
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
// Unbekannter Nachrichtentyp – korrektes Feld protokollieren
|
||||
console.warn(`[${new Date().toISOString()}] Unbekannter Nachrichtentyp: ${dataFromServer.message_type ?? 'undefined'}`);
|
||||
console.debug('Payload:', dataFromServer);
|
||||
missedHeartbeats = 0;
|
||||
if ((window as any).__wsDebug && _lastPingSentAt > 0)
|
||||
console.log(`[WS] pong RTT=${(performance.now() - _lastPingSentAt).toFixed(1)}ms`);
|
||||
} else if (dataFromServer.message_type === "wifi_networks_response") {
|
||||
const networks: WiFiNetwork[] = (dataFromServer.networks ?? []).map((n: any) => ({
|
||||
SSID: n.ssid ?? "",
|
||||
RSSI: n.rssi ?? 0,
|
||||
Channel: n.channel ?? 0,
|
||||
SecurityType: authmodeToSecurity(n.authmode ?? 0),
|
||||
}));
|
||||
$scanned_wifi_networks.set(networks);
|
||||
} else if (dataFromServer.message_type === "network_status_update") {
|
||||
$network_status_display.set({
|
||||
Connection_Mode: dataFromServer.Connection_Mode ?? ConnectionMode.CONNECTION_MODE_UNKNOWN,
|
||||
AP_Channel: dataFromServer.AP_Channel ?? 0,
|
||||
IP_Address_Station: dataFromServer.IP_Address_Station ?? "",
|
||||
Gateway_IP_Address_Station: dataFromServer.Gateway_IP_Address_Station ?? "",
|
||||
Netmask_Station: dataFromServer.Netmask_Station ?? "",
|
||||
MDNS_Hostname: dataFromServer.MDNS_Hostname ?? "",
|
||||
MAC_Address_WiFi: dataFromServer.MAC_Address_WiFi ?? "",
|
||||
rssi: dataFromServer.rssi ?? null,
|
||||
});
|
||||
} else if (dataFromServer.message_type === "network_config_response") {
|
||||
console.log("[WS] network_config_response received:", dataFromServer);
|
||||
$current_network_configuration.set({
|
||||
Connection_Mode: dataFromServer.Connection_Mode ?? ConnectionMode.CONNECTION_MODE_UNKNOWN,
|
||||
DHCP_Mode_Station: dataFromServer.DHCP_Mode_Station ?? true,
|
||||
AP_Channel: dataFromServer.AP_Channel ?? 0,
|
||||
IP_Address_Station: dataFromServer.IP_Address_Station ?? "",
|
||||
Gateway_IP_Address_Station: dataFromServer.Gateway_IP_Address_Station ?? "",
|
||||
Netmask_Station: dataFromServer.Netmask_Station ?? "",
|
||||
Static_Cfg_IP_Address_Station: dataFromServer.Static_Cfg_IP_Address_Station ?? "",
|
||||
Static_Cfg_Gateway_IP_Address_Station: dataFromServer.Static_Cfg_Gateway_IP_Address_Station ?? "",
|
||||
Static_Cfg_Netmask_Station: dataFromServer.Static_Cfg_Netmask_Station ?? "",
|
||||
SSID_Station: dataFromServer.SSID_Station ?? "",
|
||||
Password_Station: dataFromServer.Password_Station ?? "",
|
||||
SSID_AP: dataFromServer.SSID_AP ?? "",
|
||||
Password_AP: dataFromServer.Password_AP ?? "",
|
||||
MDNS_Hostname: dataFromServer.MDNS_Hostname ?? "hydrobuddy",
|
||||
});
|
||||
// Update MAC in status display — config response is the first message that carries it
|
||||
if (dataFromServer.MAC_Address_WiFi) {
|
||||
$network_status_display.set({
|
||||
...$network_status_display.get(),
|
||||
MAC_Address_WiFi: dataFromServer.MAC_Address_WiFi || $network_status_display.get().MAC_Address_WiFi,
|
||||
});
|
||||
}
|
||||
console.log("[WS] $current_network_configuration updated:", $current_network_configuration.get());
|
||||
} else if (dataFromServer.message_type === "network_config_failed") {
|
||||
const failedSsid: string = dataFromServer.failed_ssid ?? "";
|
||||
const reason: string = dataFromServer.reason ?? "unknown";
|
||||
const connectionMode: ConnectionMode = dataFromServer.connection_mode ?? ConnectionMode.CONNECTION_MODE_UNKNOWN;
|
||||
console.warn(`[WS] network_config_failed: ssid='${failedSsid}' reason='${reason}'`);
|
||||
$network_config_error.set({ reason, failed_ssid: failedSsid, connection_mode: connectionMode });
|
||||
} else if (dataFromServer.message_type === "error_popup") {
|
||||
console.warn(`[error_popup] ${dataFromServer.error ?? dataFromServer.message ?? "(no detail)"}`);
|
||||
} else {
|
||||
// Unbekannter Nachrichtentyp - korrektes Feld protokollieren
|
||||
console.warn(`[${new Date().toISOString()}] Unbekannter Nachrichtentyp: ${dataFromServer.message_type ?? "undefined"}`);
|
||||
console.debug("Payload:", dataFromServer);
|
||||
}
|
||||
}
|
||||
|
||||
function onError(error: Event) {
|
||||
$wsConnectionStatus.set("Disconnected");
|
||||
//console.log("WebSocket error:", error);
|
||||
clearHeartbeat();
|
||||
scheduleReconnect();
|
||||
stateSetters.setWebSocketConnectionStatus("Disconnected");
|
||||
console.log("WebSocket error:", error);
|
||||
clearHeartbeat();
|
||||
scheduleReconnect();
|
||||
}
|
||||
|
||||
function onClose(event: CloseEvent) {
|
||||
$wsConnectionStatus.set("Disconnected");
|
||||
//console.log("WebSocket closed with code:", event.code);
|
||||
//console.log("Close reason:", event.reason);
|
||||
//console.log("Clean close:", event.wasClean);
|
||||
clearHeartbeat();
|
||||
if (event.code === 1000) {
|
||||
//console.log("Normal closure");
|
||||
}
|
||||
scheduleReconnect();
|
||||
stateSetters.setWebSocketConnectionStatus("Disconnected");
|
||||
console.log("WebSocket closed with code:", event.code);
|
||||
console.log("Close reason:", event.reason);
|
||||
console.log("Clean close:", event.wasClean);
|
||||
clearHeartbeat();
|
||||
if (event.code === 1000) {
|
||||
console.log("Normal closure");
|
||||
}
|
||||
scheduleReconnect();
|
||||
}
|
||||
|
||||
function getWsAddress(): string {
|
||||
// Derive WS address from the page's origin so it works with any hostname or IP.
|
||||
const h = window.location.hostname;
|
||||
if (h === "localhost" || h === "127.0.0.1") {
|
||||
return (import.meta.env.PUBLIC_DEV_WS_HOST as string | undefined) ?? "ws://hydrobuddy.local/ws";
|
||||
}
|
||||
return `ws://${h}/ws`;
|
||||
}
|
||||
|
||||
const connectWebSocket = () => {
|
||||
// Clear any existing reconnect attempts
|
||||
clearReconnect();
|
||||
clearHeartbeat();
|
||||
// Clear any existing reconnect attempts
|
||||
clearReconnect();
|
||||
clearHeartbeat();
|
||||
|
||||
const wsAddress = `ws://hydrobuddy.local/ws`;
|
||||
//console.log(`Verbinde mit: ${wsAddress}`);
|
||||
if ($websocket.get() !== null) {
|
||||
($websocket.get() as WebSocket).close(1000);
|
||||
$websocket.set(null);
|
||||
}
|
||||
$websocket.set(new WebSocket(wsAddress));
|
||||
if ($websocket.get() !== null) {
|
||||
($websocket.get() as WebSocket).onopen = onOpen;
|
||||
|
||||
($websocket.get() as WebSocket).onmessage = onMessage;
|
||||
|
||||
($websocket.get() as WebSocket).onerror = onError;
|
||||
|
||||
($websocket.get() as WebSocket).onclose = onClose;
|
||||
}
|
||||
}
|
||||
const wsAddress = getWsAddress();
|
||||
console.log(`Verbinde mit: ${wsAddress}`);
|
||||
if ($websocket.get() !== null) {
|
||||
($websocket.get() as WebSocket).close(1000);
|
||||
$websocket.set(null);
|
||||
}
|
||||
$websocket.set(new WebSocket(wsAddress));
|
||||
const ws = $websocket.get();
|
||||
if (ws !== null) {
|
||||
(ws as WebSocket).onopen = onOpen;
|
||||
(ws as WebSocket).onmessage = onMessage;
|
||||
(ws as WebSocket).onerror = onError;
|
||||
(ws as WebSocket).onclose = onClose;
|
||||
}
|
||||
};
|
||||
|
||||
export const WebSocketProvider = () => {
|
||||
|
||||
// Auto-connect WebSocket at first render
|
||||
useEffect(() => {
|
||||
//console.log("Component mounted, starting WebSocket connection...");
|
||||
connectWebSocket();
|
||||
// Cleanup beim Unmounting
|
||||
return () => {
|
||||
clearHeartbeat();
|
||||
clearReconnect();
|
||||
($websocket.get() as WebSocket).close();
|
||||
$websocket.set(null);
|
||||
$wsConnectionStatus.set("Disconnected");
|
||||
};
|
||||
}, []);
|
||||
// Auto-connect WebSocket at first render
|
||||
useEffect(() => {
|
||||
console.log("Component mounted, starting WebSocket connection...");
|
||||
connectWebSocket();
|
||||
// Cleanup beim Unmounting
|
||||
return () => {
|
||||
clearHeartbeat();
|
||||
clearReconnect();
|
||||
const cur = $websocket.get();
|
||||
if (cur) (cur as WebSocket).close();
|
||||
$websocket.set(null);
|
||||
stateSetters.setWebSocketConnectionStatus("Disconnected");
|
||||
};
|
||||
}, []);
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue