# NiusThread

**Thread (OpenThread) IPv6 mesh networking** for the
[ArduinoNRF](../ArduinoNRF) board package, running entirely on the nRF52840's
**own 2.4 GHz radio**. The full OpenThread FTD stack (plus mbedtls) is vendored
into the library and sits on a register-level IEEE 802.15.4 driver written for
this core — no SoftDevice, no nrfx, no external radio module.

Four calls take a bare board into a self-healing mesh; after that every node
has real IPv6 and UDP across the network, and the complete official OpenThread
C API stays one `Thread.instance()` away.

## Highlights

- **A mesh in four calls.** `begin()`, `setNetwork(...)`, `start()`, and
  `process()` in `loop()`. The first board promotes itself to Leader; every
  further board with the same credentials attaches and gets promoted to Router
  as the mesh grows.
- **CoAP built in.** Register text resources with `onCoapGet()/onCoapPost()`
  and reach any node (or the whole mesh at `ff03::1`) with
  `coapPost()/coapGet()` — the same protocol Matter devices and Linux
  `coap-client` speak.
- **Survives reboots.** The operational dataset, network key sequence and
  frame counters persist in two dedicated flash pages (OpenThread's two-swap
  settings store over raw NVMC); a rebooted node re-attaches by itself.
- **Two API levels.** The friendly `Thread` object for bring-up and role/state
  queries — and the **official OpenThread C API** (`otXxx()` from
  `<openthread/...>`) for everything else, via `Thread.instance()`. Nothing is
  hidden.
- **Own radio driver, hardware-verified.** The 802.15.4 PHY/MAC driver
  (`src/ot_radio_nrf52840.cpp`) programs the RADIO peripheral directly:
  IRQ-driven RX, software address filter, immediate-acks with source-match
  frame-pending, single-shot hardware CCA. The OpenThread sub-MAC does CSMA
  backoff, retransmits, ack timeout and TX security in software.
- **Real interop evidence.** Frames were cross-checked against a TI CC2530
  sniffer during bring-up, and a two-node mesh (Leader + Child → Router,
  bidirectional UDP multicast) runs on real boards.
- **Self-contained vendoring.** OpenThread `fa3213ec` + mbedtls 3.6.5 live in
  `src/`, pinned and patched in exactly three marked places
  (`ARDUINONRF-PATCH`) — see [docs/VENDORING.md](docs/VENDORING.md).

## Requirements

- An **ArduinoNRF** board (nRF52840) with the
  [ArduinoNRF board package](https://github.com/dunknowcoding/ArduinoNRF)
  installed — the library uses its `NrfRtc` / `NrfPeripherals` core drivers.
- Peripherals claimed: **RADIO** (exclusive with NimBLE / Zigbee / NrfRadio),
  **RTC2** (millisecond alarm), **TIMER3** (microsecond alarm + timestamps).
- Footprint with the UDP example: ~225 KB flash (28 %), ~35 KB RAM (14 %).

## Install

In the Arduino Library Manager it is published as **NiusThread**. The GitHub
repository keeps the `ArduinoNRF-Thread` name because it is developed alongside
the ArduinoNRF core.

Or compile an example straight from a checkout:

```sh
arduino-cli compile \
  --fqbn arduinonrf:nrf52:promicro_nrf52840 \
  --library <path-to>/ArduinoNRF-Thread \
  <path-to>/ArduinoNRF-Thread/examples/FormNetwork
```

> **Bootloader layout note:** on nice!nano-style bootloaders without a
> SoftDevice (`INFO_UF2.TXT` says `SoftDevice: not found`), build with the
> no-SoftDevice bootloader option (e.g. `:bootloader=promicroserialnosd`) so
> the sketch links at `0x1000` — otherwise it uploads fine but never runs.

Existing sketches that did `#include <Thread.h>` keep working: the ArduinoNRF
package ships a compatibility shim that forwards to this library.

## Quick start

```cpp
#include <NiusThread.h>

const uint8_t key[16] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
                         0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF};

void setup() {
  Thread.begin();
  Thread.setNetwork("MyMesh", 11, 0xBEEF, key);  // name, channel, PAN id, key
  Thread.start();
}

void loop() {
  Thread.process();              // keep the stack running
  if (Thread.isAttached()) {
    // IPv6/UDP across the mesh - see the UdpBroadcast example
  }
}
```

Flash the same sketch to several boards: the first becomes **leader**, the rest
join as **child** and get promoted to **router**.

## Examples

| Example | What it shows |
| ------- | ------------- |
| [`FormNetwork`](examples/FormNetwork) | The hello-world: form/join a mesh and watch the role change on the serial monitor |
| [`NetworkInfo`](examples/NetworkInfo) | A live mesh dashboard — role, addresses, partition, neighbor table with RSSI — using the official `otXxx()` API |
| [`UdpBroadcast`](examples/UdpBroadcast) | Real data across the mesh: every node multicasts a numbered hello over UDP and prints what it hears |
| [`CoapLamp`](examples/CoapLamp) | A CoAP server on every node (`GET`/`POST /led`) — the boards toggle each other's LEDs over the mesh |
| [`ThreadCli`](examples/ThreadCli) | The full official OpenThread CLI on the serial monitor — `state`, `dataset`, `ping`, `udp`, everything `ot-cli-ftd` can do |

Verified two-board output of `UdpBroadcast` (board 2's serial monitor):

```text
UdpBroadcast: attaching...
role -> detached
role -> child
TX  "hello #1 from 0xE401"
RX  from fd00:db8:0:0:535d:5645:501c:5ef2  "hello #31 from 0xE400"
TX  "hello #32 from 0xC000"          <- promoted from child to router
RX  from fd00:db8:0:0:535d:5645:501c:5ef2  "hello #62 from 0xE400"
```

## The API

```cpp
Thread.begin();                  // platform + OpenThread instance up
Thread.setNetwork(name, channel /*11..26*/, panId, key16 [, extPanId8]);
Thread.start();   Thread.stop();
Thread.process();                // call from loop() - alarms, radio, tasklets

Thread.setLinkMode(ThreadClass::LINK_SED, 1000); // sleepy end device,
                                 // 1 s poll; LINK_MED = always-on end device,
                                 // LINK_ROUTER = full device (default)

Thread.role();                   // ROLE_DISABLED/DETACHED/CHILD/ROUTER/LEADER
Thread.roleString();             // "leader", ...
Thread.isAttached();             // child, router or leader
Thread.rloc16();                 // mesh-internal short address
Thread.getEui64(buf8);           // factory-unique 64-bit id (FICR)

// CoAP, text payloads (full otCoap API available via instance()):
Thread.coapStart();                          // server on :5683
Thread.onCoapGet("led", []() { return "on"; });
Thread.onCoapPost("led", [](const char *s) { /* act on s */ });
Thread.coapPost("ff03::1", "led", "toggle"); // POST to one node or the mesh
Thread.coapGet(addr, "led", [](const char *reply) { /* NULL on timeout */ });

otInstance *ot = Thread.instance();   // the official OpenThread C API
```

`NiusThread` is an alias for `Thread` if another library in your sketch already
uses that name.

## How it works

```
sketch  ->  Thread (Thread.h)           friendly Arduino API
        ->  OpenThread core (vendored)  MLE, mesh forwarding, MeshCoP, IPv6, UDP
        ->  platform_impl.cpp           alarms (RTC2/TIMER3), entropy (TRNG),
                                        RAM settings, logging -> Serial
        ->  ot_radio_nrf52840.cpp       register-level 802.15.4 RADIO driver
```

Everything is **polled from `process()`** — interrupts only move bytes and set
flags, so the (non-ISR-safe) OpenThread core always runs in thread context.
The build is configured by `src/arduino-ot-config.h` (FTD role, software MAC,
no CSL/commissioner/joiner/border-router) since Arduino cannot pass per-library
compiler flags.

Persistent settings use OpenThread's two-swap flash store over raw NVMC
writes, in two 4 KB pages directly below the bootloader (default `0xF2000`,
override with `NIUSTHREAD_SETTINGS_FLASH_BASE`) — clear of the application,
the core's EEPROM region, and the bootloader itself.

Current limitations, by design of the bring-up scope:

- No CSL receiver (sleepy-child long polling), commissioner, joiner or border
  router roles yet.
- One outstanding `coapGet()` at a time; CoAP payloads are text up to ~120
  bytes (use the full `otCoap` API for more).
- Logging defaults to `OT_LOG_LEVEL_NOTE` on `Serial`.

## License

Apache-2.0 for the library code — see [LICENSE](LICENSE). Vendored components
keep their own licenses: [OpenThread](https://github.com/openthread/openthread)
(BSD-3-Clause) and [Mbed TLS](https://github.com/Mbed-TLS/mbedtls)
(Apache-2.0), unmodified except for the marked `ARDUINONRF-PATCH` config hooks.
