Fleet monitor¶
This page is a working MQTT dashboard. It connects from your browser to a broker over WSS, subscribes to a DrowsyGuard alert topic, and shows one card per device with the driver's current risk, when it was last seen, and what it last alerted about - plus a live timeline, counters and filters.
It exists for two jobs: testing, because the alternative way to check that a board is publishing is to fall asleep in front of it, and demonstration, because a supervisor with a phone can watch the device work without installing anything.
The default broker is public, and so is everything on it
broker.emqx.io is the official EMQX public broker. It is preconfigured
here and on the device so that a demonstration works in a room with no broker in
it, and it is demonstration and testing only: there is no authentication and
no isolation, so anyone who subscribes to the topic - which they can guess - reads
every alert your device publishes, including the driver remark. Point both ends at
your own broker before any real vehicle. See
Security.
Press Configure MQTT, then Connect. Nothing is sent anywhere until you do.
Drivers and devices
No devices yet. Nothing has published to this topic since you connected.
Event timeline
No alerts yet.
Using it¶
- On the device, open
http://192.168.4.1/, press Configure MQTT, fill in the broker and switch publishing on. Copy the fleet subscription topic - the middle one of the three, with a+where the device id goes. - Here, press Configure MQTT, paste that topic, check the host and port, and press Connect.
- On the device, press Test publish. A card should appear within a second or two.
If the device is on its own access point with no route to the internet, it cannot reach a broker at all - give it station credentials in the same modal, or point both ends at a broker on the same network.
What each card shows¶
| Field | Meaning |
|---|---|
| the name | the device's remark - "Driver A", "Van 3" - or its device id when it has no remark |
| the dot | green online, red offline, grey no status message seen yet. Offline comes from the Last Will: the broker publishes it when the device stops answering, so it distinguishes "no alerts because the driver is fine" from "no alerts because the device fell off the network" |
| the number | the fused drowsiness risk from the device's last alert, 0.00-1.00 |
| last alert | the alert type and its severity |
| last seen | when this page last received something from that device |
| PERCLOS | share of the measurement window the eyes were closed for, at the moment of the alert |
| alerts | how many this page has seen, and the device's own running total since its last boot. The second number is larger because it counts alerts nobody was watching for |
| uptime | the device's uptime at the moment of the alert |
The event timeline is the same information in arrival order, with the device's own
timestamp when it had one. It usually does not: there is no real-time clock on the
board, so unless something has set the clock the device publishes an empty ts and
the page shows the uptime instead. This is deliberate - see the
payload schema.
What it does not do¶
- It never publishes. This page subscribes and acknowledges; it sends no commands to any device. A drowsiness alarm a dashboard can reconfigure - or mute - is a much larger safety argument than anything here needs.
- It stores nothing on a server. Everything is in the tab. Closing it loses the history; there is no backend to lose it from.
- It never stores a password. The host, port, path, topic and client id are remembered in your browser's local storage so you do not retype them at every demonstration. The password is held in a variable for the life of the tab, the field is emptied when you connect, and nothing writes it anywhere.
How it works, and why there is no MQTT library in it¶
A browser cannot open a raw MQTT socket, so this speaks MQTT over WebSocket - and it
speaks it itself, in about two hundred lines of docs/assets/js/fleet.js, rather than
loading a client library from a CDN. Three reasons:
- This page is published under the project's own domain and its only job is to display a driver's alertness state. A third-party script on that page means a third party can change what it does, for every reader, forever.
- It has to be testable.
tests/fleet_page_harness.mjsdrives the codec against byte sequences from the MQTT 3.1.1 and 5 specifications; a wrapped library would have moved that test to "we called the library correctly". - The subset needed is small and closed: CONNECT, SUBSCRIBE, receive PUBLISH, acknowledge it, PINGREQ, DISCONNECT. This page never publishes, which is most of what an MQTT client is for.
Everything from the broker is treated as hostile¶
On the default broker, anyone can publish anything to the topic you are watching. So:
- every string is length-capped and stripped of control characters, C1 codes, zero-width characters and the bidirectional overrides that would otherwise let a remark reverse the rendering of the rest of the page;
- every number is coerced and clamped - a
riskof1e308becomes1, aNaNbecomes0; - a payload that is not a JSON object, or whose
schemais notdrowsyguard.alert.*, is rejected and counted rather than rendered. A steady rejection rate is information: it means somebody else is publishing to that topic; - every value reaches the page through
textContent. There is noinnerHTMLinfleet.js, which is the only structural guarantee available against a page whose job is to render strings a stranger chose; - duplicates are dropped by
event_id. QoS 1 is at-least-once, and a reconnect redelivers whatever the broker still holds, so the same alert arrives more than once as a matter of course.
Troubleshooting¶
| Symptom | Cause |
|---|---|
| "The connection failed" immediately | wrong port or path, or ws:// from an HTTPS page. The browser deliberately does not say which - that would be a cross-origin information leak |
| connects, then nothing arrives | the topic does not match. Copy it from the device rather than retyping it; a + in the wrong level matches nothing |
| "The broker refused the subscription" | a shared broker restricting the topic. Try your own |
| "messages rejected" climbing | something else is publishing to that topic. On the public broker, that is somebody else's project |
| a device stuck at "no status message seen" | its Last Will is off, or its status topic is not the one this page subscribed to |
More in Troubleshooting.