RU

BLE Device Gateway

Focus
Backend / Edge / IoT
Stack
Go · gRPC · Protocol Buffers · BLE · BlueZ / D-Bus · Concurrency · Google Cloud Run · Docker · CI
GitHub repository

Engineering problem

Keep vendor-specific BLE details out of application services, while still supporting live telemetry and remote control when the vehicle sits behind mobile-network CGNAT and has no stable inbound address.

My role

I designed the service boundaries and protobuf contracts, implemented the BLE adapters and binary protocol handling, built the concurrency and reconnect model, and implemented the cloud / edge control path with deployment to Google Cloud Run.

Runtime model

Telemetry is event-driven from BLE notifications to gRPC streams. Remote commands travel the other way: HTTPS client → Cloud Run → bidirectional gRPC stream → RV edge agent → local gateway → BLE device.

Outcome

Hardware-tested JBD telemetry, test-covered relay control, Docker and CI, plus a verified remote end-to-end path through Cloud Run. The final physical JDY-33 smoke test is still pending.

BLE Device Gateway is the hardware-facing edge layer of RV Monitor. It runs on Linux or Raspberry Pi, hides device-specific BLE protocols behind a typed gRPC API, streams JBD battery telemetry, controls a JDY-33 relay and connects to a Google Cloud Run control service through an outbound long-lived gRPC/TLS session. The system combines real hardware, concurrent Go services, local gRPC APIs and remote control over ordinary mobile internet.

Gateway architecture

Hardware behind a typed API

Consumers work with operations such as GetSnapshot, StreamSnapshots and SetChannel instead of ReadCharacteristic or WriteCharacteristic. BLE UUIDs, vendor packet layouts and command encoding stay inside the gateway.

The internal battery model remains independent from generated protobuf types. That keeps transport concerns and hardware-specific code from leaking into the rest of the application.

From fragmented BLE frames to domain state

JBD responses can arrive split across multiple BLE notifications. A frame assembler buffers fragments until a complete response is available, after which the gateway validates framing and checksum data and parses voltage, signed current, state of charge, cell voltages, temperatures, protection flags and other battery state. The telemetry path has been tested against the physical BMS used in the vehicle.

JBD RS485 / RS232 / UART / Bluetooth communication protocol

Protocol reference used for the JBD integration

Streaming without blocking device I/O

The API supports both a unary current snapshot and server-streaming telemetry. Each subscriber gets a small buffered channel and publication is non-blocking: when a client falls behind, stale pending data is replaced by the newest snapshot because current device state matters more than replaying every intermediate value. Shared state is protected with synchronization, and BLE discovery is serialized because both devices use the same physical Bluetooth controller.

Failure recovery and testability

Long-running hardware clients use context-aware retry loops with exponential backoff, and a lost BMS connection marks telemetry unavailable instead of serving stale values as current. Shutdown propagates through context cancellation and bounded gRPC graceful stop. A hardware-free mock source, Docker / Compose, bufconn gRPC integration tests, race-detector checks and GitHub Actions make the same public API easy to run and verify without the vehicle.

Remote control over mobile networks

The RV cannot depend on inbound connections: its address can change and the cellular provider may place it behind CGNAT. A separate edge agent therefore opens an outbound long-lived bidirectional gRPC stream to a cloud control service. The cloud exposes an HTTPS API for remote clients and routes commands through the active device session. On Google Cloud Run, REST and gRPC share the same container port; gRPC requests are routed to the gRPC server based on HTTP/2 and content type, while regular HTTP requests go to the control API. TLS is terminated at the public Cloud Run endpoint, and the edge reconnects automatically when the stream is interrupted.

Verified end to end

The full remote command path was exercised through the public Cloud Run service: an authenticated HTTPS request reached the cloud control service, crossed the live gRPC/TLS stream to the local edge agent, became a RelayService.SetChannel call to BLE Device Gateway, and returned an applied result to the HTTP client. Device online state and heartbeat timestamps were also verified through the same deployment.

The prototype deliberately keeps the active device registry in memory and does not queue commands for an offline vehicle. Cloud Run is therefore limited to one instance for this version, and a command fails while the RV is disconnected instead of being replayed later. Those constraints are intentional: they keep the demo small while making the scaling and persistence boundaries explicit.