More Projects
59 in totalSafetySync — Police Vitals Monitoring
Contributor — WebSocket bridge & dashboard · 2023
On-duty monitoring for police personnel, carried from the Rajasthan Police Hackathon's screening round through to the winning final entry. Round 1 was seven static pages over the Google Maps JavaScript API — a hundred severity-tagged incident points across Jaipur, a click-to-draw geofence, alerts, force availability and four stress bands — pitched to a panel on placeholder data. The final build put sensors behind all of it: a helmet sketch reads heart rate from a MAX30105 and free-fall from an MPU6050 onto its own serial monitor and a short-range nRF24 link, while a separate board running a separate sketch sends the one thing that has to travel far — a one-bit panic flag — over LoRa at 433 MHz. A deliberately dumb Node bridge reads the receiver's serial port and rebroadcasts that alert over WebSockets to an Express/MongoDB dashboard, and a notebook k-means-clusters simulated personnel coordinates into five patrol zones.
Rajasthan Police Hackathon — Winner
Project Details
Contributor — WebSocket bridge & dashboard
2023
Rajasthan Police Hackathon — Winner
Hackathon · IoT
A constable on patrol is the least instrumented person in the chain of command. Control knows where the vehicle is and nothing about the human. SafetySync puts the sensors in the helmet, gets the readings off the officer over a radio link that does not need cell coverage, and puts them in front of a duty officer in a browser. It won the Rajasthan Police Hackathon, and it got there in two passes: a screening round argued entirely on placeholder data, then a build that put a real sensor behind the numbers on those screens.
Round 1: the concept dashboard
The screening round is a paper round with a demo attached. You are pitching an operations picture to serving officers, and the only way to make that concrete is to put real Jaipur coordinates on a real map and let them click it. The constraint was the audience, not the technology: a panel does not read code and will not wait for a backend to warm up, they ask what this would look like on a Tuesday morning in Malviya Nagar, and you have about ninety seconds to answer. Anything that could fail live had to go. So round 1 is seven pages of static HTML wired straight to the Google Maps JavaScript API, with no server, no build step, no framework and no package manager in the repository
- five sidebar pages, two orphan demos, two hand-written stylesheets, Font Awesome from a CDN, and everything else loaded from Google's own script tags.
The incident map carried the pitch. markers.json holds 100 coordinate pairs across Jaipur, each
tagged with a severity from 0 to 4, and poly.js fetches it on initMap and renders every point as
a SymbolPath.CIRCLE coloured from a severity palette over a map centred on 26.9124, 75.7873. The
file is a flat array of three-element arrays, latitude, longitude and an unnamed integer, and the
severity distribution is deliberately lopsided - 39 of the 100 points are severity 3 - so the map
reads as a city with hot pockets rather than uniform noise. Around it sat officer cards carrying
rank, station, current state, date last on field and cumulative office load in hours, each with a
health bar and a stress bar rendered as CSS progress fills. stress.js clamps a stress value into
0-100 and swaps the bar's class across four bands: low under 25, medium under 50, high under 75,
extreme above. That colour language is what the deployment argument rested on, and those four bands
are the display the real heart-rate stream was eventually fed into. Beside them sat an alerts feed
with three classes styled apart - emergency, event report and suspected threat - on red, orange and
yellow card grounds.
The geofence page is the one that had to work under a finger on a projector, and its last two lines
are the ones that came out of demoing it: the map is set non-draggable and restored 500 milliseconds
later, because otherwise the same drag meant to reshape the new polygon pans the whole city out from
under it. Assignment is where the demo stopped. assignPoliceman refuses if no polygon exists and
pops an alert naming who was assigned, with the comment marking where the real logic would go
checked in verbatim and exactly two constables in the dropdown.
That repository is sixteen files and two commits, the second of which claims to fix marker initialisation and in fact adds a single trailing space. No README, no problem statement, no deck and no notes anywhere in the history; the only thing tying the code to the force it was pitched at is a stylesheet comment next to a hover colour noting that the blue matches the Rajasthan Police theme. The reasoning lived in the pitch and not in the repo, which is its own lesson.
The constraint that picked the radio
The reason the finished build is not just a fitness band is coverage. A patrol in rural Rajasthan is routinely outside usable mobile data, and the one message that must never fail is the one sent from a place where nothing else works. That ruled out anything that assumes a cellular uplink for the alert path. LoRa at 433 MHz gets kilometres of range through buildings on a few tens of milliwatts, at the cost of a data rate that will not carry a telemetry stream. So the architecture splits by criticality: the alert goes over LoRa because it has to arrive, and everything richer goes over links that are allowed to fail.
What the system does
- Heart rate from a MAX30105, beat-detected and smoothed over a four-sample rolling mean, gated to a plausible 20-255 BPM band, with a presence check on the IR reading so an unworn helmet does not report a pulse - below 50,000 on the IR channel the sketch prints "No finger?" instead of a number.
- Fall detection from an MPU6050 computed as the magnitude of the acceleration vector rather than any single axis. The trigger is magnitude below threshold, which makes it a free-fall detector: at rest the magnitude sits near 9.81, so it fires only when the officer is actually falling.
- A gas channel on an analog MiCS-5524 and two temperatures, one from the pulse sensor's on-die probe and one ambient from the IMU, composed with the averaged BPM into a single formatted line every 100 ms.
- An emergency button on a digital pin, read every loop.
- A LoRa link at 433 MHz between helmet and base station on SX1278 modules, chosen so alerts survive where mobile data does not. The receiver re-initialises its radio every sixty seconds as a watchdog against a silently wedged module.
- A Node bridge opening the receiver's serial port at 9600 baud with a newline parser and rebroadcasting to every connected WebSocket client.
- A phone-side location feed. A small Flutter app subscribes to the platform position stream with a one-metre distance filter and writes latitude, longitude and a server timestamp into Firebase Realtime Database; the geotagging page reads the same node and plots it. That is the second radio: LoRa carries the panic bit, the handset carries position over whatever cellular it has.
- A dashboard on Express and MongoDB with five screens: vitals, alerts, available forces, geotagging with a live camera feed, and duty allocation.
- Session auth on the dashboard with an md5 password hash and a UUID session row that accepts credentials from either cookies or a request body, so the same endpoints serve the browser and a mobile client.
- A geofence tool carried over from round 1, where the operator drops at least three markers, draws a polyline, then converts it into an editable, draggable polygon assigned to a named officer.
- Patrol zoning by k-means. A notebook generates a hundred coordinates inside a 1 km radius of a Jaipur centre point, runs the elbow method across one to fifteen clusters, and settles on five with k-means++ and a fixed seed. The labelled output is written to JSON and copied into the dashboard as the map's marker file.
What I owned
The serial-to-WebSocket bridge and the dashboard. The bridge is small on purpose: read the line, check whether it is the alarm value, broadcast to every socket in an open state. That is the whole of it — it never parses a payload, never decides what an alert means and never keeps a record of one. Every page opens a socket back to the same host on a fixed port and reacts to an inbound frame with a toast and an audio alert, so an alarm reaches whichever screen the duty officer happens to be on.
Keeping the bridge dumb was the one architectural call I would defend without qualification. It means the radio side and the browser side can be replaced independently: swap LoRa for a cellular modem and the dashboard does not change; rewrite the dashboard and the bridge does not care. It also means the bridge has no state to lose when it restarts, which matters when the thing is running off a laptop on a folding table.
The fall detector, and what it should have been
Worth being precise, because it is the most instructive line in the repo. The sketch computes the Euclidean magnitude of the three acceleration axes and fires when that magnitude drops below 0.5. Because the accelerometer reads gravity, a stationary helmet sits at roughly 9.81 and a helmet in free fall approaches zero - so the test detects the falling, not the landing.
That is a real signal and a very early one, but it is also easy to trigger by dropping the helmet on a desk, and it says nothing about severity. The test you actually want is a two-part one: a free-fall window of roughly 300 ms or more, followed by an impact spike above two or three g, optionally confirmed by the orientation staying changed for a few seconds afterwards. Free fall alone is the first half of that state machine. Shipping the first half and calling it fall detection is the shortcut a 36-hour build makes.
Honest scope
The final entry is a 36-hour build and the three subsystems were developed in parallel without ever
fully meeting. The rich sensor line the helmet composes - acceleration, gas, averaged BPM, two
temperatures - exists on the helmet's own serial monitor and on the short-range nRF24 link, and even
there it is truncated, because the transmit call sends sizeof(float) bytes of a 32-byte string.
What crosses the LoRa hop is a single character, a one-bit panic flag from a separate sketch on a
separate board that reads one analog pin against a threshold of 500 and prints 1 or 0 once a second.
So the bridge forwards a bit - the one line of interpretation it does is comparing the received
value against a literal 1 - and the dashboard treats any frame as an emergency without reading the
payload. The helmet sketch
also blocks in setup() waiting for a role to be typed into the serial monitor, which means it does
not boot untethered.
The rest of the honest list: the vitals panels wire a Google Fit client for steps, SpO2, heart rate
and sleep rather than reading the helmet, the stress bar is Math.random(), and the forces roster is
sample data. The geofence tool serialises the drawn polygon by stringifying the Maps object and
downloads it as duties.json on the operator's own machine, so an assignment never reaches the
server and the coordinates do not survive the stringify. The Firebase location feed writes to a
single hardcoded path, so it tracks one officer. And the bridge opens a fixed COM port.
Round 1 was placeholders by construction, and the inventory matters because half of what makes it an
honest artefact is knowing which half was real. Its roster is three hand-written cards, two of them
byte-identical duplicates of the same officer, all three posted to a station in Kochi in a
submission about Rajasthan, every field a constant down to a blood pressure of 120/80. The health
and stress bars there are static CSS widths that nothing updates, while the randomised bar sits on a
different page beneath a caption stating a fixed 35 percent and disagreeing with it. All three alert
bodies are lorem text, and the map frame on the emergency card still points at the Space Needle in
Seattle, the unmodified example from Google's own documentation. The Google Fit dependency could not
have worked there either: gapi is initialised with an API key and no client id and no OAuth scope,
and Fit only returns data for an authorised user, so those four vitals divs stay empty by
construction. The Dashboard nav item renders an empty panel, two sidebar links point at files that
do not exist, and the browser API keys and the whole Firebase config sit in the HTML as literals.
The parts worth keeping are right: free-fall detection as a vector magnitude, LoRa rather than
cellular for the alert path, and separating the radio bridge from the dashboard so either can be
replaced without touching the other. Reusing an unordered cluster index as an ordered clearance tier
is the one decision I would undo - the map filters markers with label <= permission, which reads
k-means cluster 4 as more sensitive than cluster 1 when the numbering is arbitrary.
Project Details
Contributor — WebSocket bridge & dashboard
2023
Rajasthan Police Hackathon — Winner
Hackathon · IoT