Architecture
Open Locker is an IoT system made of four parts: a mobile app, a central Laravel backend, an MQTT broker (Mosquitto), and a locker client on a Raspberry Pi at the cabinet. The backend is the source of truth; the hardware is driven exclusively by the locker client.
The path of a command
Section titled “The path of a command”When a user opens a compartment in the app:
App → Backend (HTTPS/REST) → MQTT broker → Locker client → Modbus → LockStatus updates (e.g. “door opened”) travel the same path back and are pushed to the app in real time:
Lock → Locker client → MQTT broker → Backend → Push (WebSocket) → AppBackend
Section titled “Backend”- Laravel 12 with a Filament admin panel (the only server-rendered UI; everything else is API-first)
- Event sourcing: state changes flow as events through aggregates, projectors (read models), and reactors (side effects such as MQTT publishing)
- REST API with a live-generated OpenAPI specification (Scramble) at
/docs/api.json - PostgreSQL as the database, Redis for queues
Realtime communication with the app
Section titled “Realtime communication with the app”- Commands go through REST (
POST /api/compartments/{id}/open→command_id) - The current app applies door-state and content-note WebSocket pushes
(Laravel Reverb + Echo) from the private channel
users.{userId}.compartment-status - When realtime is unavailable or the app returns to the foreground, it refetches the accessible-compartments list
- The backend also exposes open-progress events and
GET /api/compartments/open-requests/{commandId}, but the current mobile app does not consume them
Details and payloads: App Communication Guide.
MQTT layer
Section titled “MQTT layer”- Mosquitto mediates between the backend and cabinet locations
- Clients authenticate against the backend (
mosquitto-go-authcalls/api/mosq/*) — there are no static broker passwords - The backend publishes via typed publisher services; topic structure and message contracts are captured in ADRs (see Reference)
- The backend’s
mqtt-listenerprocess reports liveness via a heartbeat and is restarted automatically on failure (see Operations)
Locker client (IoT)
Section titled “Locker client (IoT)”- TypeScript/Node service, runs as a Docker container on a Raspberry Pi at the cabinet
- Subscribes to commands via MQTT and translates them into serialized Modbus RTU signals for Waveshare relay boards
- Modbus operations are serialized and tolerate unreachable boards — only the client talks to the hardware, never the backend directly
Mobile app
Section titled “Mobile app”- React Native (Expo, TypeScript)
- The API client is generated from the backend’s OpenAPI specification via codegen (RTK Query) — API changes lead to regenerated, type-safe clients