BERGSONNE

Thread

On the wireless Core, core_thread puts several boards on one Thread mesh: they find each other, route for each other, and exchange UDP messages, with no phone, hub or gateway in between.

Overview

Core.ST.W5 only
Thread runs on the wireless Core (STM32WBA55) and shares its 2.4 GHz radio with Bluetooth: one build can run both, so a board can join the mesh and stream to a phone at the same time. It needs clock medium or higher. The C API is Tier 1; Studio has no Thread blocks yet, so a Studio project uses it in C mode.

Each board is a full Thread device: it attaches as a child, becomes a router when the mesh needs one, and takes over as leader if the leader disappears. With Thread enabled in config.json it starts on boot, and core_delay_ms() and core_watchdog_feed() keep it running, so an ordinary loop needs no Thread calls at all. A loop that neither waits nor feeds the watchdog calls core_thread_process() itself.

#include "core.h"
#include "core_thread.h"

int main(void) {
    core_init();                         // Thread starts here
    while (1) {
        if (core_thread_role() >= CORE_THREAD_CHILD) {
            // attached: the mesh is reachable
        }
        core_delay_ms(10);               // keeps Thread running
    }
}

The network in config.json

The network is fixed at build time. Every board built with the same thread block joins the same mesh:

"thread": {
  "enabled": true,
  "channel": 15,
  "panid": "0x1234",
  "network_name": "greenhouse",
  "network_key": "<32 hex digits>",
  "ext_panid": "<16 hex digits>"
}

Every key but enabled is optional. The channel is 11 to 26, the PAN ID 0x0000 to 0xFFFE, the name up to 16 characters; role can only be "ftd" today, and mesh_local_prefix takes an IPv6 /64. Absent keys take the SDK’s development defaults, including a network key everyone knows, so set a key of your own. Studio does that for you: turning Thread on in a Core.ST.W5’s settings generates a key and extended PAN ID for the project, and Copy network / Paste network there put boards from different projects on one mesh.

Sending and receiving

Open a UDP port with a receive callback, then send to one address or to every node. The callback gets one packet: its data and length, and the sender’s address and port. It runs from the SDK’s Thread servicing, never from an interrupt, and the packet is valid only during the call, so copy what you keep:

static void on_rx(const core_thread_udp_packet_t *pkt, void *ctx) {
    (void)ctx;
    core_led_toggle();                                 // pkt->data, pkt->len: the payload
    core_thread_udp_send(pkt->src, pkt->src_port, "ack", 3);   // reply to the sender
}

core_thread_udp_open(1234, on_rx, NULL);

// in the loop, once attached:
core_thread_udp_broadcast(1234, "hello", 5);         // every node (ff03::1)

For anything the wrapper leaves out (CoAP, SRP, the full diagnostics), core_thread_instance() returns the OpenThread instance for the OpenThread C API.

Limits

This is the first milestone: a development network. There is no commissioning, so anyone holding the config (or its bytes) can join the mesh and read its traffic. Every board is router-capable with its receiver on: no sleepy battery end devices. There is no border router, so the mesh does not reach the internet.

Cross-architecture support

Thread is hardware-verified on the wireless Core; the other ST Cores have no 802.15.4 radio.

·L0M0+n/a·L4M4n/a●W5M336/7·H5M33n/aWCH (RISC-V) · Nordic (nRF54) — in development

See the implementation status for the full matrix.

Known gaps

What the SDK itself lists as missing here, straight from the header:

highC APIBLE provisioning is open (development only)

With "ble_provisioning" any phone that connects can move the node to another Thread network: there is no pairing or authorization yet. The network key cannot be read back. core_thread_prov_allow_write() is the hook for a gate; pairing-protected writes are planned.

mediumDSLNo DSL blocks for Thread

Thread is C-only (Tier 1) for now, like BLE: no palette blocks, no twin. The role and UDP callbacks need the host-call ABI to grow first.

highC APINo commissioning (fixed development network)

Every node built from the same config.json joins the same network with the same network key, unless it is moved at run time (core_thread_set_dataset(), or the open BLE provisioning service); there is no joiner / commissioner yet. Development only.

lowC APIBLE + Thread builds use a trimmed OpenThread

With BLE in the same image, OpenThread is rebuilt without the commissioner, joiner, CoAP secure (DTLS), SRP and DNS clients, TCP, ping, mesh and link-metrics diagnostics, with 64 message buffers instead of 128, so both stacks fit in RAM with room for an application. A full router (FTD) all the same; Thread-only builds keep ST's complete library.

mediumC APIFTD only: no sleepy end devices (MTD / SED)

The device is always a full Thread device with its receiver on; battery end devices that sleep between polls are deferred.

lowC APINo ECC (PKA): ECJPAKE, ECDSA, DTLS unavailable

The prebuilt mbedTLS calls the ST HAL for the PKA; the SDK answers those calls with an error, so commissioning, SRP client keys and CoAP secure fail. AES (MAC and MLE security) runs on the AES peripheral.

lowC APINo CoAP / SRP / DNS / border routing wrappers

Present in the OpenThread library and callable through core_thread_instance() and the OpenThread headers, but not wrapped.

From the @studio unsupported notes in core_thread.h — tiles@77fd3f3.

API reference

Lower-level · Tier 1
int core_thread_start(void);
Start Thread: bring up the radio and OpenThread, then join (or form) the network from config.json.
void core_thread_process(void);
Run Thread: radio events, OpenThread timers and tasks, received datagrams.
core_thread_role_t core_thread_role(void);
The device's current Thread role.
void core_thread_on_role_change(core_thread_role_cb cb, void * ctx);
Register a callback for role changes (detached, child, router, leader).
int core_thread_udp_open(uint16_t port, core_thread_udp_rx_cb rx_cb, void * ctx);
Open a UDP socket bound to a port and receive datagrams sent to it.
int core_thread_udp_send(const uint8_t addr6[16], uint16_t port, const void * buf, uint16_t len);
Send a UDP datagram to an IPv6 address on the Thread network.
int core_thread_udp_broadcast(uint16_t port, const void * buf, uint16_t len);
Send a UDP datagram to every node on the Thread network (ff03::1, realm-local all-nodes).
uint16_t core_thread_rloc16(void);
The device's RLOC16 (its short address in the mesh; changes with the role).
void core_thread_ext_addr(uint8_t out[8]);
Copy the device's IEEE 802.15.4 extended address (8 bytes).
uint32_t core_thread_partition_id(void);
The ID of the Thread partition the device belongs to.
uint8_t core_thread_neighbor_count(void);
Number of neighbours in the device's neighbour table (routers and children it hears).
int8_t core_thread_parent_rssi(void);
Average RSSI of frames from the parent, when the device is a child.
int core_thread_last_error(void);
The last error OpenThread reported to this API (an otError value; 0 = none).
void * core_thread_instance(void);
The OpenThread instance, for C code that calls the OpenThread API directly.
int core_thread_get_network(core_thread_dataset_t * out);
Read the network the device is set to (its active dataset), without the network key.
int core_thread_set_enabled(int on);
Stop or restart Thread: leave the network (detach, radio idle for Thread) or attach again.
int core_thread_set_dataset(const core_thread_dataset_t * ds);
Move the device to another Thread network and store it, so it wins over config.json from now on.
int core_thread_factory_reset(void);
Forget the stored (provisioned) network and return to config.json's, then attach again.
int core_thread_is_provisioned(void);
Is the current network a stored (provisioned) one rather than config.json's factory default?
int core_thread_prov_allow_write(void);
Hook: may a BLE client change the network now? Override to gate the provisioning service.

Generated from core_thread.h — tiles@77fd3f3.