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-process
sim::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 radio
UsbTransport
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

What it is

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.

Choose it when
  • 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.
Limits
  • 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 SharedWorker already shares a Link across same-origin tabs, but nothing reaches another machine or a separate app; for that, climb to a WebSocket rung.
Build & run

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

What it is

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.

Choose it when
  • 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.
Build & run

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

What it is

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.
Build & run

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

What it is

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.

Choose it when
  • 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.
Limits
  • 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 --usb to bridge it over WebSocket, the same pattern as rootcanal-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::Link there.
Build & run

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 test

A 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.