A startup came to me with a smart parking concept: install BLE beacons in parking spots, detect whether each spot is occupied, and let drivers find available spaces through a mobile app. They had seed funding, a slide deck, and a pilot agreement with a commercial parking operator. What they did not have was anyone who could build it.
No hardware engineer. No firmware developer. No mobile team. No backend. They needed one person who could take the entire product from whiteboard sketch to something real, installed in a parking lot, and available in both app stores. This is the story of how that happened in under twelve months.
Choosing the Radio Technology
The first architecture decision was the radio. Every other decision would cascade from this one, so it had to be right. There were three realistic options: WiFi, cellular (LTE-M or NB-IoT), and Bluetooth Low Energy.
WiFi was eliminated quickly. A WiFi radio draws 150-300 mA during transmission. For a battery-powered device meant to survive years without maintenance, that is a non-starter. You would either need to wire power to every parking spot or replace batteries every few weeks. Neither is viable at scale.
Cellular (LTE-M / NB-IoT) looked promising on paper. Each beacon could communicate directly with the cloud, no intermediate infrastructure needed. But the costs added up: cellular modules are significantly more expensive per unit than BLE chips, each device needs a SIM card and a carrier contract, and the power consumption, while better than WiFi, still limits battery life to roughly 6-12 months under typical usage. For a fleet of hundreds of beacons, the ongoing carrier fees alone would erode the business model.
Bluetooth Low Energy won the evaluation. A BLE radio draws under 10 mA during a short advertising burst and can run on a CR2477 coin cell for two years or more. The silicon is cheap — a Nordic nRF52832 costs a few euros in volume. The range, 30 to 50 meters in open air, is more than adequate for a parking lot. The trade-off is real: BLE needs something nearby to receive the signal, either a dedicated gateway or a smartphone. For a parking application, where every user by definition has a phone in their pocket, this constraint was acceptable. The phone itself becomes the relay.
Hardware Design: From Breadboard to PCB
Prototyping started with a Nordic nRF52840 development kit — a roughly credit-card-sized board with a BLE radio, GPIO pins, and a USB connection for programming and debugging. Within a week, the dev kit was broadcasting BLE advertising packets with mock occupancy data. This proved the radio concept worked before any custom hardware investment.
The transition from dev kit to custom PCB introduced a different category of problems. The design had to account for several constraints simultaneously:
- Antenna placement. BLE range depends heavily on antenna design and its relationship to the ground plane. An inverted-F antenna (IFA) was selected for its compact footprint and reasonable omnidirectional pattern. The PCB layout required a keep-out zone around the antenna — no copper pour, no traces, no components within several millimeters. Getting this wrong costs you 30-50% of your range.
- Vehicle detection. An ST LIS2DH12 accelerometer was added to detect when a vehicle parks on or near the beacon. The sensor detects vibration from the engine and the tilt change from a car driving over the spot. This was cheaper and more reliable than alternatives like magnetometers (which require per-spot calibration) or ultrasonic sensors (which draw too much power).
- Weatherproofing. The enclosure needed IP67 rating — dust-tight and waterproof under temporary submersion. Parking lots flood. Snowplows hit things. The beacon had to survive being driven over. This meant potted electronics inside a high-impact ABS enclosure with a gasket-sealed battery compartment.
- Battery replacement. Whoever replaces batteries in the field is not an engineer. The battery holder had to be accessible without tools, yet sealed against water ingress. A quarter-turn bayonet cap solved this.
We went through three PCB revisions before settling on the production design. The first revision had an antenna impedance mismatch that reduced range to 15 meters. The second had a battery contact issue that caused intermittent power loss in cold temperatures. Each revision cost time and money.
One lesson I will pass on to anyone doing hardware for the first time: always order at least 20% more PCBs than you think you need. Between assembly defects, testing casualties, and the boards you will accidentally destroy during development, attrition is higher than you expect.
The jump from prototype to small-batch manufacturing (initial run of 200 units) required a bill of materials review, sourcing components from authorized distributors with confirmed lead times, and a relationship with a contract manufacturer who could handle the pick-and-place and reflow process. We used a European CM for the first batch to maintain tight feedback loops during production validation.
Firmware: The Code Nobody Sees
The firmware was written in C using the Nordic nRF5 SDK (this was before the full transition to nRF Connect SDK / Zephyr, though I would use Zephyr today). The firmware has one job: determine the occupancy state of the parking spot and broadcast it over BLE as efficiently as possible.
The BLE advertising packet carried a compact payload:
- Device ID (4 bytes) — unique identifier for this beacon
- Occupancy state (1 byte) — occupied, vacant, or unknown
- Battery level (1 byte) — percentage remaining
- Firmware version (2 bytes) — for fleet management
- Uptime counter (2 bytes) — helps detect resets
Power management was the core firmware challenge. The beacon spends almost all its time in deep sleep, drawing microamps. The accelerometer is configured to trigger a hardware interrupt when it detects motion above a threshold. On interrupt, the main MCU wakes, samples the accelerometer for a short window to confirm whether this is a real state change (car arriving or departing) or noise (a person walking past, wind shaking the enclosure). If the state changed, the beacon begins advertising the new state for a configurable window, then returns to sleep.
This interrupt-driven architecture is what delivers the two-year battery life. A beacon that polled the accelerometer every second would last months, not years. The difference between polling and interrupt-driven design is the difference between a viable product and a maintenance nightmare.
Over-the-air (OTA) firmware updates were a non-negotiable requirement from day one. Once beacons are epoxied into enclosures and bolted to a parking lot, you cannot reprogram them with a cable. The Nordic DFU (Device Firmware Update) bootloader was configured as a fail-safe: if a new firmware image fails validation, the bootloader reverts to the previous known-good image. Without this, a single bad firmware push could brick your entire fleet. I have seen this happen to other companies. It is not recoverable without physically retrieving every device.
Flash memory management also mattered. The nRF52832 has 512 KB of flash. The bootloader, SoftDevice (Nordic's BLE stack), and application firmware each occupy their own regions. Configuration data — advertising interval, accelerometer thresholds, device name — is stored in a dedicated flash page so it survives firmware updates.
Mobile Apps: React Native for Both Platforms
The mobile app needed to ship on both iOS and Android. With one engineer and a twelve-month deadline, building two native apps was not realistic. React Native was the pragmatic choice, and the BLE library ecosystem had matured enough to make it viable. We used react-native-ble-plx for Bluetooth communication.
BLE scanning on mobile is where theory meets harsh reality. iOS severely restricts BLE scanning in the background. When your app is not in the foreground, iOS limits scan frequency and delays delivery of scan results. You can work around this partially with Core Bluetooth's state restoration and by registering for specific BLE service UUIDs, but real-time background detection on iOS is fundamentally constrained by Apple's design decisions. Users had to have the app in the foreground for reliable, instant updates.
Android posed a different problem: fragmentation. BLE behavior varies significantly across manufacturers and Android versions. Samsung's BLE stack behaves differently from Xiaomi's, which behaves differently from Pixel's. Some devices aggressively kill background processes. Others throttle BLE scans after a few minutes. We tested on over 15 devices across 6 manufacturers to build a compatibility matrix and identify workarounds for the worst offenders.
The core UX was a map view of the parking facility with each spot color-coded: green for available, red for occupied, gray for offline or unknown. Users could tap a spot to see historical availability patterns ("this spot is usually free before 9 AM") and set a notification for when a tracked spot becomes available. The map data came from the backend via WebSocket for real-time updates, with a REST fallback for initial load.
One design decision that saved significant development time: the phone does double duty as both the user's interface and the data relay. When the app scans nearby beacons, it sends the received data to the backend. This eliminated the need for dedicated gateway hardware in the parking lot, reducing infrastructure cost to zero.
Cloud Backend
The data flow is straightforward: beacons broadcast state, phones receive it, the app sends it to the API, and the API persists it. The backend was built with Node.js, chosen for fast iteration and because the WebSocket support is native and well-tested.
PostgreSQL served as the primary database. The schema was simple: tables for parking facilities, spots, beacons, users, and occupancy events. Each state change from a beacon generates an event row with a timestamp. This event-sourced approach means you can reconstruct the entire history of any parking spot, which turned out to be valuable for the operator's analytics dashboard.
Scaling math was important for infrastructure planning. Each beacon generates roughly 20 state changes per day on average (vehicles coming and going), with a peak of maybe 50 on a busy day. For a facility with 500 spots, that is 10,000-25,000 events per day — trivial for PostgreSQL. Even at 10,000 spots across multiple facilities, the write load stays well within what a single properly-indexed PostgreSQL instance can handle. No need for Kafka, no need for a time-series database. Start simple.
Real-time updates for the app used WebSocket connections. When the backend receives a state change, it broadcasts to all connected clients viewing that facility. The dashboard for parking operators used the same WebSocket feed, showing a live overview of occupancy rates, historical trends, and beacon health.
Monitoring was built around one critical alert: if a beacon has not reported any data in 24 hours, something is wrong. Either the battery is dead, the hardware has failed, or the beacon has been physically removed. This alert goes to the operator so they can send someone to inspect. In practice, the most common cause was dead batteries, followed by construction crews accidentally paving over a beacon.
What I Learned
After shipping the product and deploying the initial fleet, a few lessons stand out that apply to any hardware-software product:
Hardware has no undo button. If you ship a firmware update that bricks a device's OTA mechanism, that device is now a paperweight. You cannot SSH into it. You cannot roll back remotely. Building a robust bootloader with automatic rollback is not optional — it is the single most important piece of firmware you will write. Test your OTA update path as aggressively as you test anything else.
Start with 10, not 1000. We deployed 10 beacons in a real parking lot for six weeks before ordering the production batch. Those 10 beacons revealed problems that bench testing never would: the accelerometer threshold needed tuning for different pavement types, rain caused false triggers until we adjusted the debounce timing, and one enclosure design cracked after three weeks of direct sun exposure. Each of these would have been an expensive mistake at scale.
Battery life specs are theoretical. Nordic's datasheet gives you numbers for specific, controlled conditions — a fixed temperature, a fixed TX power, a fixed advertising interval. Real-world battery life is typically 60-70% of the datasheet figure. Temperature extremes, higher-than-expected advertising frequency, and component aging all eat into the margin. Plan for the real number, not the spec sheet number.
Submit to app stores early. Apple's App Review process took five calendar days on our first submission, and the app was rejected for a minor metadata issue. The resubmission took another four days. If you are planning a launch date, submit your app at least three weeks before it. Google Play was faster but still not instant. Do not leave this for the last week.
The hardest integration is between hardware and software teams. In this case, there was no integration problem because one person did both. But for larger IoT projects, the gap between firmware engineers and app developers is where most delays happen. Define the BLE protocol — exact packet format, byte order, state machine — as a written specification before anyone writes a line of code. Make it a contract.
The product shipped on schedule. The initial deployment covered a 200-spot parking facility, and the system was processing real occupancy data within a day of installation. Twelve months from first whiteboard session to cars parking with live guidance on their phones.