Two BLE links in a trench coat.
NotANode is an experimental, transparent Bluetooth LE relay for stock Meshtastic nodes. It runs on a Nordic nRF52-DK and lets an official Meshtastic client talk to a real node through two independent BLE links.
No custom Meshtastic firmware. No custom phone app. No Wi-Fi, cloud, MQTT, or extra LoRa radio.
LoRa wants height. BLE wants proximity.
Putting a Meshtastic node on a balcony, roof, mast, or attic can dramatically improve its radio position while making its phone connection dramatically worse. NotANode sits somewhere in the middle: close enough to the outdoor node and close enough to the people inside.
HIGH / OUTSIDE
┌─────────────────────────┐
LoRa mesh ))) stock Meshtastic node │ balcony / roof / mast
└────────────┬────────────┘
│
BLE link A
│
┌─────────────────────▼─────────────────────┐
│ □ □ □ □ □ ║ balcony │
├─────────────────────────────────╫─────────┤
│ □ □ □ □ □ ║ balcony │
├─────────────────────────────────╫─────────┤
│ ┌─────────────────┐ ║ balcony │
│ │ NotANode │ ║ │
│ │ Nordic nRF52-DK │ ║ │
│ └────────┬────────┘ ║ │
├────────────────┼────────────────╫─────────┤
│ BLE link B ║ balcony │
├────────────────┼────────────────╫─────────┤
│ ┌────────▼────────┐ ║ │
│ sofa │ official app │ ║ inside │
│ └─────────────────┘ ║ │
└───────────────────────────────────────────┘
INSIDE / COUCH
NotANode is not a BLE RF repeater. It terminates two real connections and proxies Meshtastic's GATT transport between them:
official app ⇄ BLE ⇄ NotANode ⇄ BLE ⇄ stock node ⇄ LoRa mesh
The firmware understands BLE transport semantics, but it does not decode or recreate Meshtastic protobufs. The design goal is deliberately boring:
GATT in → bounded queues → GATT out
The first successful end-to-end hardware session used a stock Meshtastic node, an nRF52-DK, and an unmodified official client. The app paired with NotANode, completed the normal PhoneAPI configuration and NodeDB handshake, and continued into normal traffic.
The captured session ended with:
upstream: ready, MTU 247
downstream: connected, MTU 247
ToRadio forwarded: 7
FromRadio forwarded: 75
FromNum received: 5
forward errors: 0
queue overflows: 0
The important discovery was a compatibility edge case: some released clients
wait for a FromNum wake during configuration even though current clients
proactively drain FromRadio. NotANode emits the smallest possible local wake
when its downstream queue changes from empty to non-empty. Payloads remain
opaque and unchanged.
NotANode is v0.1-alpha. The core path is running on real hardware; broader device and range testing is still wanted.
| Capability | Status |
|---|---|
| Upstream scan, connect, PIN pairing, bonding, discovery | Hardware tested |
Upstream FromNum subscription and reconnect |
Hardware tested |
| Official-client discovery, pairing, MTU 247 | Hardware tested |
| PhoneAPI configuration and real NodeDB transfer | Hardware tested |
| Normal bidirectional client traffic through the stock node | Hardware tested |
| Long-duration balcony/roof range measurements | Pending |
| Multiple remembered clients, used one at a time | Implemented; broader A/B testing pending |
- Nordic nRF52-DK / PCA10040
- nRF52832: 512 KiB flash, 64 KiB RAM
- Zephyr board target:
nrf52dk/nrf52832 - Tested with nRF Connect SDK v3.3.3
- Nordic SoftDevice Controller in central + peripheral multirole mode
From an initialized nRF Connect SDK v3.3.3 workspace:
west build --pristine -b nrf52dk/nrf52832 /path/to/notanode
west flashThe default image remains the development and first-setup image. A separate headless profile keeps the same GATT proxy but trades discovery/reconnection latency for lower average current:
west build --pristine -b nrf52dk/nrf52832 /path/to/notanode \
-- -Dblema_EXTRA_CONF_FILE=battery.conf
west flashConfigure the upstream target and PIN with the normal image first. Flashing the battery image does not erase the NVS settings partition, so the target, PIN, and BLE bonds remain available. Do not perform a full chip erase between the two images.
Some drag-and-drop J-Link MSD firmware performs a full-chip erase despite the
HEX file not containing the NVS address range. For that development-only case,
create an ignored battery.local.conf containing a fallback identity:
CONFIG_MESHTASTIC_RELAY_FACTORY_TARGET_NAME="Meshtastic_abcd"
CONFIG_MESHTASTIC_RELAY_FACTORY_UPSTREAM_PIN=123456
Build with both private fragments:
west build --pristine -b nrf52dk/nrf52832 /path/to/notanode \
-- -Dblema_EXTRA_CONF_FILE="battery.conf;battery.local.conf"The public defaults contain no target or PIN. NVS always overrides these fallbacks when stored configuration exists.
The battery profile:
- removes UART logging and the interactive shell;
- pulses status LEDs for 20 ms instead of leaving LED1/LED2 lit;
- uses 100–150 ms preferred connection intervals and downstream latency 4;
- advertises every 400–500 ms;
- scans upstream in eight-second bursts at 20% RX duty cycle, then backs off from 5 to 60 seconds while the node remains absent;
- disables periodic RSSI polling.
The relay transport, buffers, security, bonds, and one-active-client policy are unchanged. A phone or upstream node may negotiate different legal connection parameters. After a long upstream outage, automatic reconnection can take up to about 68 seconds (the 60-second maximum sleep plus the next scan burst).
The current default/debug build reports:
| Region | Used | Total | Free | Used |
|---|---|---|---|---|
| Flash application partition | 297,616 B | 499,712 B | 202,096 B | 59.56% |
| RAM | 55,188 B | 65,536 B | 10,348 B | 84.21% |
The battery image reports 226,928 B flash (45.41%) and 47,508 B RAM (72.49%), leaving 272,784 B flash and 18,028 B RAM. These size differences do not directly predict battery life; the radio/LED duty-cycle changes above are the important runtime savings.
The last 24 KiB of flash is reserved for NVS settings and BLE bonds. Both the
DTS overlay and pm_static.yml pin this partition to 0x7a000, so default and
battery builds use the same persistent data. The image is intended for direct
flashing, not MCUboot.
Connect to the DK USB UART at 115200 baud. Configure either the upstream BLE identity (preferred) or its exact advertised name:
notanode target AA:BB:CC:DD:EE:FF random
notanode target-name Meshtastic_abcd
notanode upstream-pin 123456
notanode status
Use public instead of random where appropriate. For a node configured in
Meshtastic NO_PIN mode:
notanode upstream-pin clear
The upstream PIN is persisted but never printed. NotANode's downstream PIN
defaults to 123456; change
CONFIG_MESHTASTIC_RELAY_DOWNSTREAM_PIN in prj.conf before deployment if
needed. It is compile-time because an nRF52-DK has no trustworthy PIN display.
Then:
- Wait for LED1 to become solid.
- Open the official Meshtastic app.
- Select NotANode_52D0 and enter NotANode's downstream PIN.
- Use Meshtastic normally. The configuration, NodeDB, and mesh state belong to the real upstream node.
NotANode advertises its Meshtastic-compatible service only after the upstream node is secured, discovered, and subscribed. Losing upstream terminates the active downstream session; recovery and advertising resume automatically.
notanode status
notanode scan
notanode target <AA:BB:CC:DD:EE:FF> [random|public]
notanode target-name <name>
notanode upstream-pin <PIN|clear>
notanode reconnect
notanode clear-upstream-bond
notanode clear-downstream-bonds
notanode verbose on|off
notanode stats [reset]
notanode stacks
relay remains available as a legacy alias. Verbose mode hex-dumps PhoneAPI
frames for development; default logs never print pairing keys or the configured
PIN.
notanode stacks reports each thread's high-water mark and the lowest observed
headroom. An upstream ToRadio write or FromRadio read that does not complete
within 10 seconds is logged, counted as gatt_timeouts, and recovered by the
normal upstream reconnect path instead of leaving the proxy permanently busy.
| Control | Meaning |
|---|---|
| LED1 blinking | Scanning, connecting, securing, or recovering upstream |
| LED1 solid | Upstream Meshtastic service ready |
| LED2 solid | Downstream client connected |
| LED3 pulse | ToRadio forwarded upstream |
| LED4 pulse | FromRadio consumed downstream |
| BTN1 | Force upstream reconnect |
| BTN2 | Clear upstream target, PIN, and bond |
| BTN3 | Disconnect downstream and clear downstream bonds |
| BTN4 | Toggle verbose diagnostics |
In the battery image, LEDs are event pulses rather than persistent state: LED1 pulses on upstream search/ready transitions and LED2 on downstream connection. Traffic LEDs use 10 ms pulses. Buttons still perform their recovery actions, but the headless image has no UART feedback and BTN4 has no visible effect.
- Exactly two BLE connection slots: one central plus one peripheral.
- Up to eight stored bonds, but only one active downstream client.
- Static four-entry queues in each direction, each entry bounded to 512 bytes.
- Independent ATT MTUs and GATT long operations; no protobuf fragmentation.
- FIFO ordering, explicit backpressure, and no silent truncation.
- The upstream connection stays alive while personal downstream devices change.
- 1M PHY and moderate 30–50 ms connection intervals favor reliability.
- Only one downstream client can be active at a time. Multiple clients may be bonded and used sequentially.
- Long-duration range and failure-injection testing is not complete.
LogRadio, Battery Service, Device Information Service, OTA, and DFU are not proxied.- Three ATT prepare buffers support a 512-byte downstream write at common 185/247-byte phone MTUs. A peer stuck at the default MTU 23 cannot prepare a full 512-byte write with this RAM-bounded configuration.
- On controllers that reject standard HCI Read RSSI, the diagnostic output is
explicitly labeled
RSSI(last-scan)rather than pretending it is live. - Address targeting assumes a stable advertised identity. Exact-name targeting is available for nodes using a private address unknown before bonding.
- Protocol reconnaissance — official UUIDs, client discovery, security, PhoneAPI, queue, and MTU evidence
- Architecture — modules, state transitions, buffering, and recovery design
- Test checklist — compatibility, range, multi-client, and failure-injection acceptance tests
The project requirements repeat one warning more than any other: this relay is not another Meshtastic node. The name simply makes that promise impossible to miss.
The architecture still answers to its internal codename, Dumbwaiter: it carries packets upstairs without looking inside.
Licensed under the Apache License 2.0.
NotANode is an independent community project. It is not affiliated with or endorsed by Meshtastic LLC. Meshtastic is a registered trademark of Meshtastic LLC. The relay does not include Meshtastic firmware or protobuf implementation; it interoperates with the public BLE GATT interface.