fix(esp32): force clean reconnect in Esp32Bridge.connect() — the real run-after-agent fix
The stop-first guard in the Run button only fires when board.running is true, but the Run button is DISABLED while a board runs — so by the time the user can actually click Run, the board has already disconnected (running=false) and the guard is a no-op. The failure lives one level down: Esp32Bridge.connect() early-returned whenever a socket lingered in ANY non-CLOSED state (CONNECTING/OPEN/CLOSING). The agent's run_simulation leaves such a socket; when its backend QEMU session ends but the frontend socket is still zombie, the user's Run → startBoard → connect() did nothing. A page reload "fixed" it only by constructing a fresh bridge. connect() now tears down any lingering socket (detaching handlers + close) and opens a new one to the same session key — exactly what the reload does, which is why the reload always worked. The backend already handles a new WS replacing an existing session, so no reload is needed. Test: connect() on an OPEN socket closes the old one and boots a fresh start_esp32 (esp32-dht22-flow). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
9c26174c93
commit
fce1cdafdc
|
|
@ -337,6 +337,24 @@ describe('Esp32Bridge — sensor WebSocket protocol', () => {
|
|||
]);
|
||||
});
|
||||
|
||||
it('connect() on a lingering (non-CLOSED) socket force-reconnects instead of no-op', () => {
|
||||
// Regression: the agent leaves the board running (socket OPEN); the user's
|
||||
// Run → startBoard → connect() must boot a FRESH session, not silently
|
||||
// return because a socket already exists. Reproduces "run after the agent
|
||||
// did nothing until I reloaded".
|
||||
const firstWs = (bridge as any).socket as MockWebSocket;
|
||||
expect(firstWs.readyState).toBe(MockWebSocket.OPEN);
|
||||
|
||||
bridge.connect(); // second connect while the first socket is still OPEN
|
||||
const secondWs = (bridge as any).socket as MockWebSocket;
|
||||
|
||||
expect(firstWs.readyState).toBe(MockWebSocket.CLOSED); // old zombie torn down
|
||||
expect(secondWs).not.toBe(firstWs); // a brand-new socket
|
||||
secondWs.open();
|
||||
const startMsg = secondWs.messages.find((m) => m.type === 'start_esp32');
|
||||
expect(startMsg).toBeDefined(); // fresh boot actually happened
|
||||
});
|
||||
|
||||
it('sendSensorUpdate sends esp32_sensor_update message', () => {
|
||||
bridge.sendSensorUpdate(4, { temperature: 35, humidity: 70 });
|
||||
expect(ws.messages).toEqual([
|
||||
|
|
|
|||
|
|
@ -292,7 +292,31 @@ export class Esp32Bridge {
|
|||
}
|
||||
|
||||
connect(): void {
|
||||
if (this.socket && this.socket.readyState !== WebSocket.CLOSED) return;
|
||||
// Force a clean reconnect. The old guard here was
|
||||
// if (this.socket && readyState !== CLOSED) return;
|
||||
// which made connect() a SILENT NO-OP whenever a socket lingered in any
|
||||
// non-CLOSED state (CONNECTING / OPEN / CLOSING). That's exactly the
|
||||
// "el agente terminó, di Run y no funcionó; recargué y sí" bug: the
|
||||
// agent's run_simulation left a live/half-dead socket, the backend QEMU
|
||||
// session had ended, and the user's Run → startBoard → connect() returned
|
||||
// without doing anything. A page reload worked only because it built a
|
||||
// fresh bridge. Tearing the zombie socket down and opening a new one to
|
||||
// the same session key is exactly what that reload does — the backend
|
||||
// already handles a new WS replacing an existing session (that's why
|
||||
// reload works), so it's safe to do it without the reload.
|
||||
if (this.socket) {
|
||||
try {
|
||||
this.socket.onopen = null;
|
||||
this.socket.onmessage = null;
|
||||
this.socket.onclose = null;
|
||||
this.socket.onerror = null;
|
||||
this.socket.close();
|
||||
} catch {
|
||||
/* already closing/closed */
|
||||
}
|
||||
this.socket = null;
|
||||
this._connected = false;
|
||||
}
|
||||
|
||||
const base = API_BASE();
|
||||
const wsProtocol = base.startsWith('https') ? 'wss:' : 'ws:';
|
||||
|
|
|
|||
Loading…
Reference in New Issue