Architecting Smart Parking Systems: What the LetsParky Ecosystem Gets Right
BLE-first control paths with replay-proof signed commands, access modeled as revocable grants, the marketplace layer for renting spots, and the fleet operations nobody demos.
Parking is one of those problems that looks trivial until you build for it: a physical barrier, a mobile app, radio in between, money on top, and users who expect it all to work in an underground garage with one bar of signal. LetsParky is a great case study — a platform that pairs smart barrier hardware (Bouncer) and gate controllers (Terminal) with an app for access control, spot rental and payments, promising 15-minute plug-and-play setup and ~30-metre Bluetooth control. This article reverse-engineers the architecture patterns behind that class of product: what it takes to build a smart-parking (or any app-controlled hardware) system that survives contact with concrete.
Key takeaways
- App-controlled hardware needs a dual control path: BLE for proximity (works offline, low latency) and cloud for remote management and sharing.
- Access rights are the real product — model them as time-boxed, revocable grants, not as "who has the key".
- Monetizing spots turns an IoT app into a two-sided marketplace: bookings, availability calendars, payments, disputes.
- Design for the garage: offline-first commands, idempotent actuation, and telemetry you can debug from the office.
The control path: BLE first, cloud always
A 30–35 m Bluetooth range is a design statement: the primary open/close path should not depend on the internet, because garages eat LTE for breakfast. The robust pattern:
- BLE direct: app ↔ device with a challenge–response over the GATT connection. Fast, offline-capable, proximity-bound (a feature, not a bug — you shouldn't open your barrier from another city by accident).
- Cloud relay: for remote actions (guest arriving while you're away) and for fleet management, commands flow app → cloud → device over the device's own uplink or a shared gateway.
- Grant sync: the source of truth for who may open lives in the cloud; devices cache signed grants so a dead uplink doesn't strand authorized users.
// Flutter-side sketch: BLE open with a signed, short-lived grant
Future<void> openBarrier(Device device) async {
final grant = await grantCache.validFor(device.id) // cached, signed, TTL-bound
?? await api.fetchGrant(device.id); // refresh when online
final conn = await ble.connect(device.bleId, timeout: 5.s);
final nonce = await conn.read(characteristic: kNonceChar);
// Device verifies: signature, TTL, nonce freshness — replay-proof
await conn.write(kCommandChar, grant.signOpenCommand(nonce));
}
The nonce matters: BLE sniffing is trivial, so a static "open sesame" packet is a free parking pass for the neighbourhood. Sign per-command with device-issued freshness and replays die.
Modeling access as grants
"Control who has access to your parking spot and under which conditions" — that sentence hides the entire domain model. A schema that holds up:
CREATE TABLE grants (
id UUID PRIMARY KEY,
device_id UUID NOT NULL REFERENCES devices(id),
grantee_id UUID NOT NULL REFERENCES users(id),
role ENUM('owner','resident','guest','renter') NOT NULL,
valid_from TIMESTAMPTZ NOT NULL,
valid_until TIMESTAMPTZ, -- NULL = until revoked
schedule JSONB, -- e.g. weekdays 08:00–18:00
booking_id UUID, -- set when the grant came from a paid booking
revoked_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
Everything else falls out of this table: guest links are short-lived grants; rentals are grants created by a paid booking; revocation is an update plus a push to the device's grant cache. Audit trail comes free.
The marketplace layer
Letting owners rent out idle spots upgrades the product from gadget to income stream — and upgrades your backend from CRUD to marketplace:
- Availability: owner-defined calendars minus active bookings minus their own presence. Get timezone handling right on day one.
- Booking lifecycle: reserved → active (grant issued) → completed → settled. Overstays are a state, not an exception.
- Payments & payouts: card-in via PSP, payouts to owners, platform fee in the middle — a textbook connected-accounts setup (Stripe Connect and friends).
- Trust: the barrier's open/close telemetry is your dispute evidence ("the renter never arrived" vs. the log saying the barrier opened at 09:02).
Fleet operations: the unglamorous 50%
| Concern | Pattern |
|---|---|
| Firmware updates | Staged OTA with rollback; never brick a barrier that's holding someone's car |
| Battery / power telemetry | Report on every connection; alert before the device dies, not after |
| Command idempotency | Actuation commands carry ids; "open" twice must not cycle the barrier |
| EV charging integration | Same grant model gates the charger — see the OCPP guide for the charging side |
| Enterprise tenants | Org accounts, bulk grants, SSO, and an API — the B2B tier is where the revenue is |
FAQ
Why not just use a keypad code?
Codes leak, can't be revoked per person, leave no audit trail, and can't power a rental marketplace. Grants can.
BLE or NFC or UWB?
BLE wins on range (drive-up UX) and hardware cost; UWB adds precise ranging if you need "only opens when the car is actually in front". NFC's tap distance fights the drive-up use case.
What's the hardest part in practice?
The intersection of radio and money: a BLE open that succeeds while the booking-state sync fails. Event-sourced bookings and device-side logs reconcile it — design for reconciliation, not for optimism.
Next steps
Smart parking is a compact masterclass in IoT product architecture: hardware control, marketplace mechanics and mobile UX in one box. Building something app-controlled — barriers, chargers, lockers, anything with a relay? Tell me about it; this stack is a favourite of mine.