Przejdź do treści

Etap 5 — Kiosk hardware integration

Etap 5 took the Flutter kiosk from a MockLockerHardware proof-of-concept to a Modbus-driven production kiosk on LOCKER-SZB-SDI-001. It also closed several lifecycle bugs that only surfaced once a real lock + sensor was on the bus.

Production state

LOCKER-SZB-SDI-001 na TEMREXLOCKERBOX runs Modbus TCP locker driver since v0.78, hardened through v0.83. The mock driver is still in the tree for dev / CI but production never touches it.

Hardware

  • Locker rig: TEMREXLOCKERBOX with Modbus TCP latch controller on 172.31.0.201.
  • Driver: lib/hardware/modbus_locker_hardware.dart. Implements the same LockerHardware interface as the mock — pulses the lock coil for ~300 ms to release, then polls the door-contact register at ~5 Hz to emit LockerEvent.opened and LockerEvent.closed.
  • App wiring: app.dart constructs ModbusLockerHardware in production builds (config-driven; mock kept available for dev / CI).

Lifecycle bugs that landed in v0.78v0.83

Numbered by the version that fixed them:

  • v0.78 — first Modbus build wired into the production kiosk; replaces MockLockerHardware end-to-end.
  • v0.79"kliknąłem otwórz ponownie a była otwarta skrytka": LockerOpenScreen would deadlock if the user re-tapped "Otwórz ponownie" while the cell was already open. Fix: open() is now idempotent on an already-released latch (no-op + immediate opened event).
  • v0.80 — broken contact sensor flicker would emit a phantom closed event mid-pickup. Fix: debounce on the sensor poll — close needs two consecutive sampled-closed reads.
  • v0.81"chcę nadać przesyłkę, skrytka 10 wolna": an orphaned ASSIGNED shipment could grab a locker that had been physically reused. Fix: /deposit/begin-existing now refuses if the existing reservation points to a different machine, and the kiosk reconciles its cached_lockers view against availableFunctions from /machines/me.
  • v0.82 — UX hardening drop:
  • Bigger touch targets on Wyloguj / Anuluj / Cofnij (footer buttons).
  • PIN buffer is wiped on auth failure (was previously left intact, leading to silent re-attempts on top of stale digits).
  • Pickup is gated when the locker isn't reporting closed — refuses to send confirm-pickup until the latch sensor agrees.
  • Deposit is gated when the cell hasn't returned to closed since the previous transaction.
  • v0.83"poszło w otwartą skrzynkę nie wykrył otwartej skrytki i jest w pętli": a deposit could go into an already-open cell because the isClosed check was racing the latch open/close cycle. Fix: LockerOpenScreen blocks Nadaj if the cell is reporting open, and the deposit flow polls isClosed() before flipping to the success screen.

All of the above are persisted in Machine.currentVersion after the OTA agent installs the corresponding release.

Operational notes

  • The Modbus driver shares the kiosk's network with the backend — check the kiosk's externalIp in Machine and confirm it can reach the latch controller before reporting "kiosk hangs".
  • A latch that won't acknowledge will leave the locker in RESERVED until ShipmentWorker.shipment.auto-expire frees it on expiresAt. To free it sooner, use the panel's emergency-open (POST /machines/:id/lockers/:lockerId/emergency-open) — that spawns an OPEN_LOCKER task and the kiosk drives the cell open.
  • The MockLockerHardware is still in the tree for dev — toggle by env var or by editing app.dart. CI uses the mock; production never does.

What's next

Etap 5 closes out kiosk hardware. Etap 6 (ErgoFlow integration, see etap-6-ergoflow-integration.md) was the next chunk shipped. Etap 7 (multi-machine sync, machine-selection engine, document/key-rental flows) and etap 8 (real notifications, OIDC code-flow, Gitea Actions, monitoring) remain.