The controller ladder
A SimBLE host stack produces and consumes HCI — the Host Controller Interface. Everything above HCI (GAP, GATT, ATT, L2CAP, SMP) is SimBLE. Everything below it — the Link Layer, the physical channel, timing, encryption, the radio — is the job of a controller. SimBLE ships just one controller of its own, and it is deliberately tiny: it runs in-process — natively, and inside the browser tab showing this page — and needs no install, no daemon, and no radio. Every heavier rung is a controller you plug in underneath — from a software controller to a real radio — forming a ladder that trades weight for fidelity. Pick the lowest rung that still answers your question.
At a glance
SimBLE compiles both natively (the Rust crate — tests, CLIs, real hardware) and to
WebAssembly (these demo pages). The built-in controller runs
in both worlds — natively under cargo test and the
simble CLI, and in-process in a browser tab. The heavier rungs are
external processes a browser reaches over WebSocket (rootcanal-ws,
netsim) — and a real radio, whether a USB dongle or a phone running
SimBLE Android, joins the same way through SimBLE's own USB
bridge, simble --usb. The Native and Browser columns
below capture this per rung.
| Tier | Fidelity | Native | Browser | External process | Choose when |
|---|---|---|---|---|---|
In-processsim::Link |
Minimal — no real PHY, timing, encryption, ISO, or ranging | Yes | Yes (wasm) | None | Tests, CI, quick single-page or multi-tab browser scenes |
| rootcanal-ws | High — full Link Layer/LMP, BR/EDR + LE + ISO, ext/periodic adv, encryption | Yes | Yes (via WebSocket) | Binary (rootcanal-ws) | Full controller behavior; no spatial/ranging modeling |
| netsim | Highest simulated — Rootcanal + 3D position/ranging + multi-radio (BLE/Wi-Fi/UWB) | Yes | Yes (via WebSocket) | Daemon + emulator | Movement, distance/ranging (Channel Sounding), or a real Android app in the emulator |
Real radioUsbTransport |
Real — an actual Bluetooth controller and radio; no model at all | Yes | Via bridge — simble --usb |
Hardware — a dongle, or a phone running SimBLE Android | Final interop over the air; phone-throughput and phone-to-phone runs |
1 · In-process controller micro
simble::controller::sim::Link
A minimal, pure-Rust LE controller plus a shared medium built directly into SimBLE. It routes advertising packets to scanners, completes connections, and shuttles ACL data between devices. There is no external process, no netsim, and no radio — it is just library code that stands in for the Link Layer and PHY.
Because it is ordinary Rust, it runs identically native and in the browser
(compiled to WebAssembly). Natively, it is what cargo test, the
examples, and the simble CLI drive — no setup, no daemon. In the
browser, one web page hosts many devices talking to each other on a single
Link; the Shared demo puts that
Link in a SharedWorker, so every same-origin
tab joins the same scene.
- Writing unit and integration tests for the host stack.
- Running CI with no daemon, no emulator, and no setup.
- Building quick single-page or multi-tab browser scenes.
- Models no real PHY — no channel hopping, no timing, no encryption, no ISO, no Channel Sounding.
- Confined to one machine — and, in the browser, one origin. A
SharedWorkeralready shares aLinkacross same-origin tabs, but nothing reaches another machine or a separate app; for that, climb to a WebSocket rung.
It is just library code — nothing to launch. See the in_process_scene example:
cargo run --example in_process_scene
2 · rootcanal-ws high fidelity
Rootcanal (rootcanal-rs) · netsim-style WebSocket
Rootcanal — AOSP's virtual Bluetooth controller — via the
rootcanal-rs
Rust crate, wrapped in a binary that speaks SimBLE's
netsim-style WebSocket protocol. It implements the Link Layer and LMP for BR/EDR, LE, and ISO,
including extended and periodic advertising and encryption, without netsim's spatial or
ranging modeling.
- You need spec-accurate controller behavior (Link Layer, LMP, encryption, ISO).
- You are exercising extended or periodic advertising, or BR/EDR alongside LE.
- You do not need device positions, movement, or distance/ranging modeling.
From the rootcanal-rs repo, launch the binary and point SimBLE at it:
bazel run //:rootcanal-ws -- --ws-port 7681
# then point SimBLE at ws://localhost:7681
The rootcanal-ws binary lives in the
rootcanal-rs companion
repository, not in the SimBLE crate itself.
3 · netsim full radio + spatial
Rootcanal + 3D position/ranging + multi-radio + Android emulator
netsim is Rootcanal plus a 3D position and ranging model, multiple radios (BLE / Wi-Fi / UWB), and interop with the Android emulator. On top of everything rootcanal-ws gives you, it models where devices sit in space and how far apart they are, and it lets a real Android app running in the emulator share the scene with SimBLE devices.
Choose it when- You need device movement or spatial layout in the scene.
- You need distance / ranging behavior, including Channel Sounding.
- You want to test a real Android app in the emulator against SimBLE devices.
Needs the canary-channel Android emulator. Start netsimd, then point SimBLE at it:
~/Library/Android/sdk/emulator/netsimd --logtostderr --no-shutdown --ws-port 7681
# then point SimBLE at ws://localhost:7681
See the README's netsim quick-start for emulator setup details.
4 · Real radio real hardware
simble::transport::UsbTransport · a physical dongle, or a phone running SimBLE Android
Not a simulation at all: UsbTransport drives a physical USB
Bluetooth controller over the Bluetooth USB transport (control/interrupt/bulk
endpoints) using the pure-Rust nusb crate — no libusb, no C. SimBLE's
host stack now runs over a real controller and radio, so a SimBLE peripheral
is discoverable and connectable by real phones and laptops over the air — the
top of the ladder, with nothing modeled.
A dongle is not the only real radio, which is why this rung is
Real radio and not "USB dongle": a phone running the SimBLE
Android app is one too, reached through the same
simble --usb bridge. A phone can play the
peripheral — it advertises the bulk service and counts the bytes that
land, reporting them back; that is the phone-throughput measurement. It can now also
play the central: with the app's "source" role, one phone drives a bulk
transfer into another phone over its own radio — a phone-to-phone run
with no dongle in the path at all.
The bridge finds phones over adb: it lists them and checks whether the
SimBLE app is running with adb shell pidof, which works even when the
phone's screen is off. This page reads a phone's counters back through the bridge's
/sink/<ip> proxy.
- You are doing final interop against real phones, laptops, or accessories.
- You need real RF behavior — timing, encryption, coexistence — not a model of it.
- You want to demonstrate a scripted SimBLE device to a physical scanner across the room.
- You want a phone's real throughput — a phone advertising the bulk service and counting what lands.
- You want a phone-to-phone run: one phone as source driving another over the air, no dongle in the path.
- The dongle path needs a physical USB Bluetooth dongle whose interface SimBLE can claim.
- A browser can't open the dongle directly (WebUSB can't claim it) — run
simble --usbto bridge it over WebSocket, the same pattern asrootcanal-ws. - A browser can't start a phone's central role directly — it's an Android intent over adb — so the bridge orchestrates a phone-to-phone run on the page's behalf.
- Real RF is non-deterministic: great for interop, wrong for CI — use
sim::Linkthere.
Natively, open the dongle with UsbTransport in place of a WebSocket
transport — the rest of SimBLE sees the same HCI packet stream. To reach it from a browser page, run the
bridge and point the page's WebSocket backend at it:
simble --usb --ws 32323 # every dongle, one port
curl 127.0.0.1:32323/devices # what is plugged in
# ws://127.0.0.1:32323/?device=02.3.2 # open that one
One bridge serves every dongle, the way netsim serves every device: one port, one
connection each, named in the URL. Two dongles of the same model share a
vid:pid and publish no serial number, so that form is refused as
ambiguous — 02.3.2 names the physical socket and is the only
selector that survives a re-plug. cargo run --example usb_list
prints every name each dongle answers to.
Phones ride the same bridge. On the Testing → Data page you select a phone for each end — a sink phone alone for phone-throughput, or a source phone and a sink phone for a phone-to-phone run.
Which dongle you own decides what you can testA Bluetooth 4.0 dongle gives you real RF and real timing, but cannot do extended
advertising, periodic advertising, BIG, or PHY switching — so those parts of the stack
stay unverifiable on it. A 5.x controller (an nRF52840 flashed with Zephyr's
hci_usb) adds all four, and is currently the only way to check
SimBLE's broadcast code against an independent implementation at all. Channel Sounding
needs 6.0 silicon, which today is not USB.
docs/usb-controllers.md
has the inventory, the flashing procedure, and what is worth buying.