Discovery
BleDiscovery connects a node to the nodes in range. Every node advertises
(when its host listens on /ble) and scans (when discovery starts).
final discovery = BleDiscovery(host: host, transport: ble);await discovery.start();Who dials
Section titled “Who dials”When a node sees another node’s token, it:
- Reads the other node’s peer ID from its identity characteristic.
- Adds
/ble/<token>to the peerstore for that peer. - Connects with
host.connect. Noise then proves the peer ID.
If both nodes dialed at the same time, a pair would get two links. To stop
that, the node with the lower token dials. The node with the higher token
waits, and dials only if no connection came after yieldFor (8 seconds), in
case the other node cannot hear it.
A node keeps at most maxLinks BLE connections (4), in both directions.
Phones can hold only a few BLE links at a time, and each link shares the
same radio time. In a room with more nodes, each node connects to some of
them, and the protocols on top relay across the partial mesh. When other
nodes dial in past the budget, the node closes the newest inbound
connections. A node that connected to a token does not dial it again for
retryAfter, so a full node is not dialed again at once.
Signal strength
Section titled “Signal strength”With minRssi, discovery does not dial a device that it hears weaker than
that (in dBm). A weak link is slow and drops often.
With swapMargin, a full node that hears a device at least that many dB
stronger than its weakest link closes that link and dials the stronger
device. Only links open for minLinkAge or longer are swapped out, and
only links whose device discovery heard (it knows their signal strength).
Swaps are off by default: the weak link can be the only bridge to another
part of the mesh, and a swap then cuts the mesh in two.
final discovery = BleDiscovery( host: host, transport: ble, minRssi: -90, swapMargin: 15,);A mesh
Section titled “A mesh”Gossipsub works on the links as they are. Start a PubSub on each host and
subscribe to a topic: a message reaches every node that is connected
through other nodes, also nodes out of range of the sender.
for (final (_, pubsub, _) in nodes) { pubsub.subscribe(_topic);}final atC = c.subscribe(_topic).stream.cast<PubSubMessage>().map((m) => utf8.decode(m.data)).first;for (final (_, _, discovery) in nodes) { await discovery.start();}
// Gossipsub builds its mesh on heartbeats, so the first messages can go// nowhere: send until C has one.final sender = Timer.periodic(const Duration(milliseconds: 500), (_) { a.publish(_topic, Uint8List.fromList(utf8.encode('hello from A')));});print('C got: ${await atC.timeout(const Duration(seconds: 30))}');sender.cancel();This example runs on in-memory radios: Testing without Bluetooth shows the rest of it.
Settings
Section titled “Settings”| Parameter | Default | What it does |
|---|---|---|
maxLinks |
4 | Most BLE connections this node keeps, inbound ones included. |
yieldFor |
8 s | How long the higher token waits for the lower one to dial. |
retryAfter |
30 s | How long to wait before trying a token again after a dial fails, or after a connection to it. |
minRssi |
none | Devices heard weaker than this (dBm) are not dialed. |
swapMargin |
none (off) | When full, a device this many dB stronger than the weakest link replaces it. |
minLinkAge |
1 min | Links younger than this are not swapped out. |
addrTtl |
10 min | How long a learned /ble address stays in the peerstore. |
recheckEvery |
3 s | How often the tokens seen so far are looked at again. |
recheckEvery exists because iOS reports a device only once per scan. A
node that waits (yieldFor) or backs off (retryAfter) cannot count on a
new sighting to try again, so it looks at the tokens it knows on a timer.
What discovery tells you
Section titled “What discovery tells you”peerFound: each peer that this node connected to, once.knownTokens: the peer ID of each token seen so far.bleConnections: the number of open BLE connections.
Connections that another node dialed do not show in peerFound. To see all
peers, watch the host’s network.
Limits
Section titled “Limits”- A closed inbound link can stay open on the other side for a while. iOS cannot disconnect a central: the peripheral stops sending, and the central’s side ends when its streams time out.
- Tokens change on restart. A token means something only to devices nearby, and only while the node runs. To find a known peer again, use its peer ID, not its token.
- Discovery is open. Any device in range can read your peer ID and connect. Noise proves who the peer is, but not that you met them: to know that, compare something in person (a QR code, or a short code on both screens).