feat: enhance WebSocket error handling and cleanup logic in simulation and ESP32 libraries
parent
f5323aa557
commit
fdbc37b69b
|
|
@ -40,8 +40,14 @@ async def simulation_websocket(websocket: WebSocket, client_id: str):
|
||||||
logger.info('[%s] system event: %s', client_id, data.get('event'))
|
logger.info('[%s] system event: %s', client_id, data.get('event'))
|
||||||
elif event_type == 'error':
|
elif event_type == 'error':
|
||||||
logger.error('[%s] error: %s', client_id, data.get('message'))
|
logger.error('[%s] error: %s', client_id, data.get('message'))
|
||||||
|
elif event_type == 'serial_output':
|
||||||
|
text = data.get('data', '')
|
||||||
|
logger.info('[%s] serial_output uart=%s len=%d: %r', client_id, data.get('uart', 0), len(text), text[:80])
|
||||||
payload = json.dumps({'type': event_type, 'data': data})
|
payload = json.dumps({'type': event_type, 'data': data})
|
||||||
|
try:
|
||||||
await manager.send(client_id, payload)
|
await manager.send(client_id, payload)
|
||||||
|
except Exception as _send_exc:
|
||||||
|
logger.debug('[%s] qemu_callback send failed (%s): %s', client_id, event_type, _send_exc)
|
||||||
|
|
||||||
def _use_lib() -> bool:
|
def _use_lib() -> bool:
|
||||||
return esp_lib_manager.is_available()
|
return esp_lib_manager.is_available()
|
||||||
|
|
@ -177,12 +183,18 @@ async def simulation_websocket(websocket: WebSocket, client_id: str):
|
||||||
)
|
)
|
||||||
|
|
||||||
except WebSocketDisconnect:
|
except WebSocketDisconnect:
|
||||||
|
# Guard: only clean up if this coroutine still owns the connection for client_id.
|
||||||
|
# A newer simulation_websocket may have already connected and replaced us.
|
||||||
|
if manager.active_connections.get(client_id) is websocket:
|
||||||
manager.disconnect(client_id)
|
manager.disconnect(client_id)
|
||||||
qemu_manager.stop_instance(client_id)
|
qemu_manager.stop_instance(client_id)
|
||||||
await esp_lib_manager.stop_instance(client_id)
|
await esp_lib_manager.stop_instance(client_id)
|
||||||
esp_qemu_manager.stop_instance(client_id)
|
esp_qemu_manager.stop_instance(client_id)
|
||||||
|
else:
|
||||||
|
logger.info('[%s] old WS session ended; newer session is active — skipping cleanup', client_id)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.error('WebSocket error for %s: %s', client_id, exc)
|
logger.error('WebSocket error for %s: %s', client_id, exc)
|
||||||
|
if manager.active_connections.get(client_id) is websocket:
|
||||||
manager.disconnect(client_id)
|
manager.disconnect(client_id)
|
||||||
qemu_manager.stop_instance(client_id)
|
qemu_manager.stop_instance(client_id)
|
||||||
await esp_lib_manager.stop_instance(client_id)
|
await esp_lib_manager.stop_instance(client_id)
|
||||||
|
|
|
||||||
|
|
@ -160,7 +160,10 @@ class EspLibManager:
|
||||||
|
|
||||||
logger.info('Launching esp32_worker for %s (machine=%s, script=%s, python=%s)',
|
logger.info('Launching esp32_worker for %s (machine=%s, script=%s, python=%s)',
|
||||||
client_id, machine, _WORKER_SCRIPT, sys.executable)
|
client_id, machine, _WORKER_SCRIPT, sys.executable)
|
||||||
|
try:
|
||||||
await callback('system', {'event': 'booting'})
|
await callback('system', {'event': 'booting'})
|
||||||
|
except Exception as exc:
|
||||||
|
logger.warning('start_instance %s: booting event delivery failed: %s', client_id, exc)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
proc = subprocess.Popen(
|
proc = subprocess.Popen(
|
||||||
|
|
@ -362,6 +365,17 @@ class EspLibManager:
|
||||||
raw = raw.strip()
|
raw = raw.strip()
|
||||||
if not raw:
|
if not raw:
|
||||||
continue
|
continue
|
||||||
|
# With -nographic, serial0 is connected to the stdio mux so
|
||||||
|
# qemu_chr_fe_write() writes the raw UART byte to fd 1 just
|
||||||
|
# before picsimlab_uart_tx_event emits the JSON line. Strip
|
||||||
|
# any prefix bytes before the JSON object marker.
|
||||||
|
idx = raw.find(b'{"type":')
|
||||||
|
if idx > 0:
|
||||||
|
raw = raw[idx:]
|
||||||
|
elif idx < 0:
|
||||||
|
logger.debug('[%s] ignoring non-JSON worker line: %s',
|
||||||
|
client_id, raw[:200])
|
||||||
|
continue
|
||||||
try:
|
try:
|
||||||
event = json.loads(raw)
|
event = json.loads(raw)
|
||||||
except Exception:
|
except Exception:
|
||||||
|
|
|
||||||
|
|
@ -307,6 +307,13 @@ export const useSimulatorStore = create<SimulatorState>((set, get) => {
|
||||||
bridge.onCrash = () => {
|
bridge.onCrash = () => {
|
||||||
set({ esp32CrashBoardId: id });
|
set({ esp32CrashBoardId: id });
|
||||||
};
|
};
|
||||||
|
bridge.onDisconnected = () => {
|
||||||
|
set((s) => {
|
||||||
|
const boards = s.boards.map((b) => b.id === id ? { ...b, running: false } : b);
|
||||||
|
const isActive = s.activeBoardId === id;
|
||||||
|
return { boards, ...(isActive ? { running: false } : {}) };
|
||||||
|
});
|
||||||
|
};
|
||||||
bridge.onLedcUpdate = (update) => {
|
bridge.onLedcUpdate = (update) => {
|
||||||
// Route LEDC duty cycles to PinManager as PWM (0.0–1.0).
|
// Route LEDC duty cycles to PinManager as PWM (0.0–1.0).
|
||||||
// If gpio is known (from GPIO out_sel sync), use the actual GPIO pin;
|
// If gpio is known (from GPIO out_sel sync), use the actual GPIO pin;
|
||||||
|
|
@ -619,6 +626,13 @@ export const useSimulatorStore = create<SimulatorState>((set, get) => {
|
||||||
if (boardPm) boardPm.triggerPinChange(gpioPin, state);
|
if (boardPm) boardPm.triggerPinChange(gpioPin, state);
|
||||||
};
|
};
|
||||||
bridge.onCrash = () => { set({ esp32CrashBoardId: boardId }); };
|
bridge.onCrash = () => { set({ esp32CrashBoardId: boardId }); };
|
||||||
|
bridge.onDisconnected = () => {
|
||||||
|
set((s) => {
|
||||||
|
const boards = s.boards.map((b) => b.id === boardId ? { ...b, running: false } : b);
|
||||||
|
const isActive = s.activeBoardId === boardId;
|
||||||
|
return { boards, ...(isActive ? { running: false } : {}) };
|
||||||
|
});
|
||||||
|
};
|
||||||
bridge.onLedcUpdate = (update) => {
|
bridge.onLedcUpdate = (update) => {
|
||||||
const boardPm = pinManagerMap.get(boardId);
|
const boardPm = pinManagerMap.get(boardId);
|
||||||
if (boardPm && typeof boardPm.updatePwm === 'function') {
|
if (boardPm && typeof boardPm.updatePwm === 'function') {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue