How to Use WendyOS as a BLE Gateway for Edge AI on Jetson and Raspberry Pi

You’ve built the BLE gateway the hard way. A bleak loop, a few retries that mostly work, an MQTT bridge in a Python venv, an ONNX inference container next to it, and four systemd units pretending to be a system. It runs fine on the bench.
Then you put 50 of them in a steel mill. The BlueZ daemon starts dropping characteristic subscriptions at 3am. The model container drifts a version ahead of the preprocessor. Correlating an anomaly score back to the raw vibration packet that caused it means SSH-ing in and grepping across five journals.
This guide shows how to replace that whole stack with WendyOS, where BLE ingest and edge AI are peer subsystems in one declarative pipeline, running the same config on a Jetson Orin Nano or a Raspberry Pi 5.
Why BLE Gateways Are Harder Than They Look
The standard pattern (BlueZ + Python + MQTT + a separate inference runtime) has three failure modes that only show up at scale.
First, BLE state is fragile and nobody owns it. Your bleak loop reconnects, but BlueZ’s internal cache disagrees about which characteristic handles are valid. Subscriptions go silent without errors.
Second, the model and the data preprocessor version independently. The Python service does FFT in NumPy. The ONNX model expects a specific bin layout. One gets updated, the other doesn’t, and your anomaly scores are quietly garbage for two weeks.
Third, updates are all-or-nothing image reflashes, or worse, scp and pray.
Container-based fleet OSes solve the deployment problem but treat BLE as just another container with USB passthrough, which inherits every BlueZ pain point. Heavier edge frameworks add their own abstraction layer on top of the OS, which is more software to babysit, not less.
| Concern | BlueZ + scripts | WendyOS |
|---|---|---|
| BLE ingest | bleak loop + retries | declarative source |
| Inference | separate container | inline pipeline stage |
| Updates | image reflash | atomic wendyctl deploy |
| Observability | grep across services | correlated logs |
What WendyOS Gives You Out of the Box
WendyOS ships a native BLE ingest layer that handles central role, multi-adapter scanning, GATT subscriptions, and exponential reconnect. The BLE state machine is owned by the runtime rather than a userspace script poking at bluetoothd.
On top of that sits the physical AI runtime: an inference engine that loads ONNX or TFLite models and auto-selects an execution provider based on the hardware it finds. CUDA on Jetson. NEON-optimized kernels on Pi. CPU fallback if neither is present.
The two subsystems talk through a declarative pipeline. A BLE source feeds a transform stage, the transform feeds inference, inference feeds a sink. You describe this in YAML. You don’t write the glue.
If you have assets where running a local gateway isn’t practical (mobile fleets, remote sensors, anything battery-powered at the edge of nowhere), Hubble can act as a managed global BLE backhaul into the same pipeline model, so the config shape stays the same whether the BLE traffic comes from a dongle on the gateway or from a satellite link.
Setting Up the Jetson BLE Gateway (or Raspberry Pi)
WendyOS supports Jetson Orin Nano and Orin NX, plus Raspberry Pi 4 and 5. The image and config are identical across them.
Flash the image:
# Jetson Orin Nano (via SDK Manager host)
wendyctl image flash --target jetson-orin-nano --device /dev/sdX
# Raspberry Pi 5
wendyctl image flash --target rpi5 --device /dev/sdXAfter first boot, verify that the BLE adapter and any accelerator showed up:
wendyctl hw inspectYou should see your HCI adapter listed: the onboard controller on Pi 5, or a USB dongle on Jetson (which doesn’t ship with onboard BLE). The accelerator line should read cuda on Jetson or neon on Pi.
The topology you’re building:
[BLE Sensors] [WendyOS Gateway] [Cloud / Hubble]
vibration )) +------------------+ +---------------+
temp ))) ---> | BLE ingest | | |
current )) | | | ---> | MQTT broker |
| v | | or Hubble |
| edge inference | | backhaul |
| | | | |
| v | +---------------+
| sink (mqtt/http) |
+------------------+One note for Jetson: the Orin Nano dev kit has no onboard BLE. Plan for a USB dongle (and a second one for redundancy if this is production). The Pi 5’s onboard controller is fine for low-density deployments but will saturate around 8 to 10 concurrently subscribed peripherals in practice.
Defining the BLE Ingest Layer
Here’s a wendyos.yaml BLE source block declaring three characteristics across a fleet of advertised bearing-monitor sensors. The syntax below is representative; check the current WendyOS schema docs before copying into production.
sources:
- id: bearing-fleet
type: ble
adapter: auto # or hci0, hci1 for explicit pinning
scan:
mode: active
filters:
- manufacturer_id: 0x05A7
- name_prefix: "BRG-"
subscriptions:
- characteristic: 0x2A56 # vibration RMS
as: vibration_rms
notify: true
- characteristic: 0x2A6E # temperature
as: temp_c
notify: true
- characteristic: 0x2A66 # current draw
as: current_a
notify: true
reconnect:
backoff: exponential
max_interval: 60s
dedup: true
timestamp: ingestThe runtime owns reconnect, dedup, and ingest-time timestamping. Each event that flows downstream carries the peripheral address, characteristic ID, timestamp, and raw payload. That’s roughly 300 lines of bleak plus retry plus dedup logic you don’t write or maintain.
Multiple adapters work the same way: declare adapter: hci0 and adapter: hci1 as separate sources and the runtime load-balances scan windows across them. That’s how you get adapter redundancy on a Pi without writing failover logic.
Wiring in Edge AI for Predictive Maintenance
Drop your ONNX model into /var/lib/wendyos/models/, then declare the pipeline. For bearing fault detection, a small autoencoder trained on healthy vibration spectra works well: input is a 64-bin FFT window, output is a reconstruction error you threshold for anomaly.
transforms:
- id: vibration-window
source: bearing-fleet
select: vibration_rms
window:
size: 2s
stride: 1s
features:
- fft:
bins: 64
sample_rate: 1000
inference:
- id: bearing-anomaly
model: /var/lib/wendyos/models/bearing-ae-v3.onnx
accelerator: auto # cuda on Jetson, neon on Pi
input:
spectrum: vibration-window.fft
output:
anomaly_score: reconstruction_error
sinks:
- id: mqtt-out
type: mqtt
broker: tls://mqtt.internal:8883
topic: "plant/{peripheral_address}/anomaly"
from: bearing-anomalyThe pipeline reads as one graph:
BLE source --> window/transform --> inference --> sink
(GATT subs) (RMS, FFT bins) (ONNX) (MQTT)
| | | |
+--- timestamps ---+--- features ------+--- score ----+
(correlated in structured logs)The transform stage lives inside the runtime, so the FFT layout and the model’s expected input layout ship together and can’t drift.
The same YAML runs on both targets. On Jetson Orin Nano, accelerator: auto resolves to the CUDA execution provider and a typical 64-bin window hits inference in under 15 ms. On a Pi 5, the NEON-optimized ONNX Runtime provider gets you to roughly 40 to 60 ms for the same model. End-to-end (BLE notify to MQTT publish) lands under 200 ms in both cases for normal window sizes.
Running, Observing, Updating
Standard systemd:
systemctl status wendyos
wendyctl pipeline tail --id bearing-anomalypipeline tail is the thing you’d otherwise spend a weekend building. It streams structured events that follow a single BLE notification through window, feature extraction, inference, and sink, with one correlation ID. When an anomaly score looks wrong, you can pull the exact 2-second FFT window and raw vibration samples that produced it without log archaeology.
For updates, ship the model and config as one transaction:
wendyctl deploy --config wendyos.yaml --models ./models/The deploy is atomic, and a new pipeline that fails its health checks after activation rolls back automatically. No half-updated fleet.
For fleets where putting a gateway in the field is impractical (mobile assets, remote sensors, anywhere you don’t want hardware to maintain), Hubble can backhaul BLE traffic to a regional WendyOS instance over satellite or terrestrial coverage. The pipeline config is identical; only the source’s transport changes.
Production Checklist
Before you call this done:
- Adapter redundancy. On Pi, add a USB dongle as
hci1alongside the onboard controller. On Jetson, run two dongles. Saturation on a single adapter is the most common silent failure. - Thermal headroom on Jetson. CUDA inference under sustained load will throttle the Orin Nano if it’s in a sealed enclosure without a fan. Budget for it.
- Shadow inference for model rollouts. Run the new model in parallel with the old one as a separate
inferenceblock, compare outputs in the sink, and promote when you trust it. - Layered config. Keep a base
wendyos.yamland per-site overrides (different MQTT brokers, different scan filters per facility).wendyctl deploymerges them. - Decide what stays on-gateway. Raw spectra to the cloud is bandwidth suicide. Anomaly scores plus the offending window on threshold cross is usually the right cut.
Scaling the Fleet or Skipping the Gateway
You’ve got one runtime, one config, and BLE ingest sitting next to edge AI as peers rather than as duct-taped processes. The same artifact runs on Jetson and Pi, updates atomically, and produces logs you can actually trace.
If you’re scaling the gateway fleet itself, the fleet-scale IoT topology guide covers config layering and rollout patterns. If your assets are mobile or remote enough that a local gateway doesn’t pencil out, look at the Hubble device integration guide for backhauling BLE telemetry into the same pipeline model without putting hardware in the field.
Hubble Network reaches BLE devices directly from satellite, so you can stream telemetry from assets where deploying gateways isn’t practical. See how it works →