Summary
Disconnecting a machine through PUT /api/v1/devices/disconnect (app-initiated, clean) makes StatusPublisher emit an error-severity scaleDisconnected — "Scale disconnected unexpectedly." — for the machine's controller-owned internal scale (bengle-internal-<machineId>). The machine's own disconnect is correctly suppressed as expected; the internal scale's cascade is not.
Evidence (tablet logs, two independent occurrences)
Every deliberate API disconnect of a connected machine produced the error; a real USB detach produces both errors (correctly):
08:08:15.640 PUT /api/v1/devices/disconnect (machine usb-2e8a-a-<serial>, 200)
08:08:15.652 ScaleController - scale connection update: disconnected
08:08:15.655 WARNING StatusPublisher - emit error: kind=scaleDisconnected message=Scale disconnected unexpectedly. deviceId=bengle-internal-usb-2e8a-a-<serial>
(no machineDisconnected error — machine expectation consumed correctly)
08:11:57.292 PUT /api/v1/devices/disconnect (machine usb-2e8a-a-<serial>, 200)
08:11:57.304 ScaleController - scale connection update: disconnected
08:11:57.306 WARNING StatusPublisher - emit error: kind=scaleDisconnected message=Scale disconnected unexpectedly. deviceId=bengle-internal-usb-2e8a-a-<serial>
By contrast, a physical USB_DETACHED at 07:58:51 emitted both machineDisconnected and scaleDisconnected — the expected behavior for a genuine unexpected drop.
Root cause
BengleVirtualScale.connectionState is _machine.connectionState (lib/src/models/device/impl/bengle/bengle_virtual_scale.dart) — the internal scale mirrors the machine's connection-state stream, so a machine disconnect necessarily fires the scale's disconnect in the same emission.
- The API disconnect path (
lib/src/services/webserver/devices_handler.dart, _handleDisconnect) marks only the target device as an expected disconnect: _connectionManager.markExpectingDisconnect(device.deviceId).
DisconnectSupervisor._handleScaleDisconnect (lib/src/controllers/connection/disconnect_supervisor.dart) consumes expectations by scale id; none was registered for bengle-internal-<machineId> — so the "unexpectedly" error fires.
So a clean, app-initiated machine disconnect always reports its own internal scale as an unexpected drop. ConnectionManager.disconnectMachine() has the same gap (marks only de1.deviceId). The sleep flow at lib/src/controllers/de1_state_manager.dart:593 does mark the scale, which is why only some flows are affected.
Suggested fix directions
- When a machine is deliberately disconnected, also mark its controller-owned scale(s) as expected — e.g. in
disconnectMachine() and the devices-handler disconnect path, mark bengle-internal-<machineId> alongside the machine id.
- Or, in
DisconnectSupervisor, treat a scale disconnect as expected when it mirrors a machine whose disconnect was just accepted (scale id == bengle-internal-<lastKnownMachineId> and that machine's expectation was consumed).
Repro
- Connect a Bengle over USB (machine + internal
bengle-internal-* scale become connected).
PUT /api/v1/devices/disconnect with the machine id.
- Observe
StatusPublisher warning error scaleDisconnected "unexpectedly" for the internal scale, even though the disconnect was deliberate.
Environment: Android tablet, Decaid with a Bengle composite USB device (machine on interface 0, serial scrubbed). Severity: low — no functional loss (the scale does disconnect with the machine), but it surfaces a false error banner/status to skins and pollutes telemetry with spurious scaleDisconnected events.
Summary
Disconnecting a machine through
PUT /api/v1/devices/disconnect(app-initiated, clean) makesStatusPublisheremit an error-severityscaleDisconnected— "Scale disconnected unexpectedly." — for the machine's controller-owned internal scale (bengle-internal-<machineId>). The machine's own disconnect is correctly suppressed as expected; the internal scale's cascade is not.Evidence (tablet logs, two independent occurrences)
Every deliberate API disconnect of a connected machine produced the error; a real USB detach produces both errors (correctly):
By contrast, a physical
USB_DETACHEDat 07:58:51 emitted bothmachineDisconnectedandscaleDisconnected— the expected behavior for a genuine unexpected drop.Root cause
BengleVirtualScale.connectionStateis_machine.connectionState(lib/src/models/device/impl/bengle/bengle_virtual_scale.dart) — the internal scale mirrors the machine's connection-state stream, so a machine disconnect necessarily fires the scale's disconnect in the same emission.lib/src/services/webserver/devices_handler.dart,_handleDisconnect) marks only the target device as an expected disconnect:_connectionManager.markExpectingDisconnect(device.deviceId).DisconnectSupervisor._handleScaleDisconnect(lib/src/controllers/connection/disconnect_supervisor.dart) consumes expectations by scale id; none was registered forbengle-internal-<machineId>— so the "unexpectedly" error fires.So a clean, app-initiated machine disconnect always reports its own internal scale as an unexpected drop.
ConnectionManager.disconnectMachine()has the same gap (marks onlyde1.deviceId). The sleep flow atlib/src/controllers/de1_state_manager.dart:593does mark the scale, which is why only some flows are affected.Suggested fix directions
disconnectMachine()and the devices-handler disconnect path, markbengle-internal-<machineId>alongside the machine id.DisconnectSupervisor, treat a scale disconnect as expected when it mirrors a machine whose disconnect was just accepted (scale id ==bengle-internal-<lastKnownMachineId>and that machine's expectation was consumed).Repro
bengle-internal-*scale become connected).PUT /api/v1/devices/disconnectwith the machine id.StatusPublisherwarning errorscaleDisconnected"unexpectedly" for the internal scale, even though the disconnect was deliberate.Environment: Android tablet, Decaid with a Bengle composite USB device (machine on interface 0, serial scrubbed). Severity: low — no functional loss (the scale does disconnect with the machine), but it surfaces a false error banner/status to skins and pollutes telemetry with spurious
scaleDisconnectedevents.