ParkAlert BLE parking beacon hardware
CASE STUDY / MOBILE & BACKEND REBUILD

ParkAlert — two legacy apps, rebuilt as one.

A shipping BLE parking product was running on two ageing native apps — Android in Java, iOS in Swift — against third-party backends nobody controlled. I reverse-engineered the beacon protocol from the legacy source, recovered its encryption key, and rebuilt the product as one React Native codebase on a new TypeScript API.

CLIENT
ParkAlert
ROLE
Mobile + backend rebuild
TIMELINE
2026 · shipped
STATUS
● Live

The beacon hardware was shipping and selling. The software around it was not keeping up: an Android app in Java and an iOS app in Swift, maintained in parallel, drifting apart feature by feature, and depending on third-party parking APIs that could be withdrawn without notice. Every change cost twice, and the two apps no longer behaved the same way at the moment that matters — the moment you walk away from the car.

The harder problem was underneath. The BLE protocol the beacon speaks existed only inside the legacy apps and the hardware team's heads. Rebuilding meant recovering it exactly — including the encryption — because a beacon already mounted in a customer's car cannot be reflashed to suit a new app.

Legacy Android (Java)Legacy iOS (Swift)Undocumented BLEThird-party parking APIsShipping hardware

Key decision — Recover the protocol before writing the app

Wrote a full BLE protocol specification from the legacy source before a line of the new app existed. Service and characteristic UUIDs, the challenge-response handshake, the status frames, and the AES-256-CBC encryption whose key had to be recovered rather than requested. This turned an open dependency on the hardware team into a written document, and it is why the new app talks to beacons that were manufactured and sold long before it.

Key decision — One codebase, and our own backend

Replaced two native apps with a single React Native and Expo codebase, and the third-party dependencies with an Express and PostgreSQL API we own. Parking-zone lookups, provider integrations and push all moved behind one contract, with the zone service caching on a grid and failing soft to unknown rather than guessing. Two apps became one; an unowned dependency became an owned one.

Trade-offs accepted

React Native means the parking loop runs through a bridge rather than in native code — accepted, because the loop is a 30-second debounce and a location fix, not a hot path. Keeping exact behavioural parity with the legacy apps mattered more than improving on it: the beep on return and the button that silences it were both missing from the first rebuild and had to be put back, because that beep is the product. Session state and provider credentials stay server-side, which adds a round trip and removes a class of client tampering.

HARDWARE · v2 productionHARDWARE · v2 production
FIELD · deployment
APP · real-time

The rebuild ships as one codebase to both stores, validated against real beacons on real iOS and Android hardware — scan, handshake, beep. The API runs in production with continuous deployment. Three parking providers sit behind one integration layer, and the road network that answers "is this a paid zone" is self-hosted PostGIS built from OpenStreetMap, with a live national traffic feed alongside it, so the product no longer depends on an API somebody else can switch off.

2→1
Native apps unified
Java + Swift → one codebase
AES-256
BLE protocol recovered
specified from legacy source
3
Parking providers
behind one integration
41
Automated tests
coverage gated in CI
React NativeExpoTypeScriptExpressPostgreSQLPostGISBLEDockerGitLab CIEAS
01 · PROTOCOL
Specify what the beacon speaks

BLE service and characteristic UUIDs, the challenge-response handshake and the AES-256-CBC encryption, all recovered from the legacy source and written down — removing the dependency on the hardware team before the rebuild started.

02 · API
An owned backend

Express and TypeScript on PostgreSQL: auth with refresh-token rotation, device pairing, parking sessions, excluded locations, preferences and push. Parking-zone lookups cached on a grid and failing soft rather than guessing.

03 · APP
Two codebases into one

React Native and Expo, fourteen screens, rebuilt to the legacy design system down to the typeface and palette so existing customers were not handed a different product. Dutch, English and French throughout.

04 · THE LOOP
Leave the car, come back to it

BLE disconnect, thirty-second debounce, location fix, zone check, notification. On return the beacon beeps and the button silences it. Kept alive in the background within each platform\'s rules, and validated on real beacons on both platforms.

Next case studies

Keep reading.

Build something real

Have a system that needs architecting?

No sales pitch, no agency layers. I'll read your situation and reply within 24 hours with whether I'm the right fit.

Start a project See all work