50 Thread & Matter Integration
Sensor Squad: Building with OpenThread and Matter
Sammy the Sensor wanted to learn coding: “How do I tell Thread what kind of device I am?” Max the Microcontroller showed the code: “If you are plugged into the wall, we set ‘always listening’ to true – you become a mail carrier (Router). If you run on batteries like Bella, we set it to false so you can sleep and save energy!” Bella the Battery nodded: “If someone accidentally sets me to ‘always listening,’ I would run out of power in DAYS instead of YEARS!” Lila the LED was excited about Matter: “It is like a universal translator – before, Apple devices spoke Apple-ish and Google devices spoke Google-ese, but now with Matter, everyone speaks the same language and can be friends!”
50.1 Learning Objectives
By the end of this chapter, you will be able to:
- Diagnose Thread Networks via CLI: Execute OpenThread CLI commands to inspect device state, routing tables, and neighbor connectivity
- Implement Device Role Configurations: Write Router and Sleepy End Device link mode settings in OpenThread C code, selecting correct flags for each power profile
- Evaluate Matter Cluster Architecture: Analyze how Matter organizes device capabilities into standardized clusters running over the Thread network layer
- Troubleshoot Common Pitfalls: Identify and resolve NAT64 prefix conflicts, commissioning window timeouts, and incorrect link mode settings that destroy battery life
- Construct Thread Sensor Applications: Build complete SED sensor data transmission patterns using the OpenThread UDP API
50.2 Prerequisites
Before diving into this chapter, you should be familiar with:
- Thread Network Operations: Understanding network formation, self-healing, addressing, and power management is essential before writing Thread code
- Thread Fundamentals and Roles: Device types, network architecture, and Border Router concepts
Related Chapters
Deep Dives:
- Thread Operation and Implementation - Chapter index
- Thread Network Operations - Formation and power management
- Thread Deployment Guide - Border routers and troubleshooting
Integration:
- Matter Overview - Matter protocol fundamentals
- Thread Security and Matter - Security implementation
Development:
- Hands-On Labs Hub - Wokwi simulations
For Beginners: OpenThread and Matter
OpenThread is like the “operating system” for Thread networking—it’s free, open-source software that handles all the complex mesh networking, security, and device communication so you don’t have to write it from scratch.
Matter is the “universal language” for smart home devices. Just like how USB lets different devices plug into any computer, Matter lets smart home devices from different brands (Apple, Google, Amazon, Samsung) work together seamlessly.
How They Work Together:
- Your smart light bulb runs OpenThread to join the mesh network
- The bulb uses Matter to understand commands like “turn on” or “set brightness to 50%”
- Your phone app sends Matter commands over the Thread network
- The bulb receives the command via Thread, interprets it via Matter, and turns on
Why This Matters:
- Before Matter: You needed separate apps for each brand (Philips Hue app, LIFX app, etc.)
- After Matter: One app controls all Matter devices, regardless of manufacturer
- Thread’s Role: Provides the reliable, low-power wireless mesh network that carries Matter commands
50.3 OpenThread Development
OpenThread is the open-source reference implementation of Thread. Here’s how to work with it.
50.3.1 OpenThread CLI Commands
The OpenThread CLI provides powerful diagnostic and configuration capabilities:
# Check device state
> state
router
# View network info
> netdata show
Prefixes:
fd12:3456::/64 paros med 4000
# View neighbor table
> neighbor list
0x4c01 0x6801 0x8001
# View routing table
> route
fd12:3456::/64 s med 4000
# Get device RLOC16
> rloc16
0x4c01
# Get mesh-local EID
> eid
fd12:3456:0:0:5e51:1d18:7c31:b80f
# Commissioning commands
> commissioner start
> commissioner joiner add * PSK123456Common Diagnostic Commands:
| Command | Purpose | Example Output |
|---|---|---|
state |
Current device role | router, leader, child |
rloc16 |
16-bit routing locator | 0x4c01 |
panid |
Network PAN ID | 0x1a2b |
channel |
Current 802.15.4 channel | 15 |
networkname |
Thread network name | HomeThread |
leaderdata |
Leader information | Partition ID, weighting |
neighbor list |
Connected neighbors | RLOC16 addresses |
childtable |
Child devices (for routers) | Child info with timeout |
50.3.2 Device Configuration for Different Roles
Router (Mains-Powered Light/Switch):
// OpenThread configuration for always-on router
#include <openthread/thread.h>
void configure_as_router(otInstance *instance) {
// Enable router-eligible mode
otThreadSetRouterEligible(instance, true);
// Set link mode: Router, RxOnWhenIdle, FullThread
otLinkModeConfig mode = {
.mRxOnWhenIdle = true,
.mDeviceType = true, // Full Thread Device
.mNetworkData = true // Full Network Data
};
otThreadSetLinkMode(instance, mode);
// Start Thread protocol
otThreadSetEnabled(instance, true);
}
Putting Numbers to It
The power difference between Router and SED modes is dramatic. For a Thread device with RxOnWhenIdle enabled (Router mode), the radio draws approximately 15-20 mA continuously. For battery life calculation:
\[ \text{Battery Life (hours)} = \frac{\text{Battery Capacity (mAh)}}{\text{Average Current (mA)}} \]
Router example (CR2032 coin cell, 225 mAh): \[ \text{Life} = \frac{225 \text{ mAh}}{18 \text{ mA}} = 12.5 \text{ hours} \]
SED example (same battery, 60-second poll interval, 5 µA sleep, 15 mA active for 10ms per poll): \[ \text{Average} = 0.005 \text{ mA (sleep)} + \frac{(15 \text{ mA} \times 10 \text{ ms})}{60,000 \text{ ms}} = 0.0075 \text{ mA} \] \[ \text{Life} = \frac{225 \text{ mAh}}{0.0075 \text{ mA}} = 30,000 \text{ hours} \approx 3.4 \text{ years} \]
This 2,400× difference in battery life (12 hours vs 3.4 years) shows why mRxOnWhenIdle configuration is critical.
Key Router Configuration:
mRxOnWhenIdle = true: Radio always listening (required for routing)mDeviceType = true: Full Thread Device (can become router)mNetworkData = true: Receives complete network topology dataotThreadSetRouterEligible(true): Allows promotion to router when needed
Sleepy End Device (Battery Sensor):
// OpenThread configuration for battery-powered sensor
void configure_as_sed(otInstance *instance, uint32_t poll_ms) {
// Set link mode: Sleepy, minimal network data
otLinkModeConfig mode = {
.mRxOnWhenIdle = false, // Sleep between polls
.mDeviceType = false, // Minimal Thread Device
.mNetworkData = false // Stable Network Data only
};
otThreadSetLinkMode(instance, mode);
// Configure polling interval (e.g., 60000ms = 1 minute)
otLinkSetPollPeriod(instance, poll_ms);
// Start Thread protocol
otThreadSetEnabled(instance, true);
}Key SED Configuration:
mRxOnWhenIdle = false: Radio sleeps between polls (saves power)mDeviceType = false: Minimal Thread Device (cannot route)mNetworkData = false: Only receives stable network data (less overhead)otLinkSetPollPeriod(): Time between parent polls (trade-off: latency vs battery)
50.3.3 Sensor Data Transmission Pattern
// Complete SED sensor application pattern
#include <openthread/thread.h>
#include <openthread/udp.h>
typedef struct {
otInstance *instance;
otUdpSocket socket;
uint16_t poll_period_ms;
} ThreadSensor;
// Initialize sensor as SED
void sensor_init(ThreadSensor *sensor, otInstance *instance) {
sensor->instance = instance;
sensor->poll_period_ms = 60000; // 60 second poll
// Configure as SED
otLinkModeConfig mode = {
.mRxOnWhenIdle = false,
.mDeviceType = false,
.mNetworkData = false
};
otThreadSetLinkMode(instance, mode);
otLinkSetPollPeriod(instance, sensor->poll_period_ms);
// Open UDP socket for sensor data
otUdpOpen(instance, &sensor->socket, NULL, NULL);
}
// Send sensor reading (call this from sensor interrupt or timer)
void sensor_send_reading(ThreadSensor *sensor, int16_t temperature) {
// Prepare message
otMessage *message = otUdpNewMessage(sensor->instance, NULL);
if (message == NULL) return;
// Add temperature data (simple format)
uint8_t payload[4] = {
(temperature >> 8) & 0xFF, // High byte
temperature & 0xFF, // Low byte
0x00, 0x01 // Sensor ID
};
otMessageAppend(message, payload, sizeof(payload));
// Set destination (border router or cloud gateway)
otMessageInfo info;
memset(&info, 0, sizeof(info));
otIp6AddressFromString("fd12:3456::1", &info.mPeerAddr);
info.mPeerPort = 5683; // CoAP port
// Send message
otUdpSend(sensor->instance, &sensor->socket, message, &info);
}50.4 Thread + Matter Integration
Thread serves as the primary network layer for Matter smart home devices:
50.4.1 Matter Device Structure
// Simplified Matter + Thread device structure
typedef struct {
// Thread configuration
uint16_t rloc16;
uint8_t extended_pan_id[8];
char network_name[17];
// Matter configuration
uint16_t vendor_id;
uint16_t product_id;
uint32_t fabric_id;
// Device clusters
bool on_off_state;
uint8_t level_value;
int16_t temperature_value;
} MatterThreadDevice;
// Matter command handler
void handle_on_off_command(MatterThreadDevice *device, bool new_state) {
device->on_off_state = new_state;
// Update hardware
if (new_state) {
turn_on_light();
} else {
turn_off_light();
}
// Report attribute change to fabric
matter_report_attribute_change(
device->fabric_id,
CLUSTER_ON_OFF,
ATTRIBUTE_ON_OFF,
&device->on_off_state
);
}50.4.2 Matter Cluster Model
Matter organizes device functionality into clusters—standardized groups of commands and attributes:
| Cluster | Purpose | Example Attributes | Example Commands |
|---|---|---|---|
| OnOff | Binary control | OnOff (bool) |
On(), Off(), Toggle() |
| LevelControl | Brightness/position | CurrentLevel (0-254) |
MoveToLevel(level) |
| ColorControl | Color management | Hue, Saturation |
MoveToHue(hue) |
| TemperatureMeasurement | Sensor reading | MeasuredValue (°C×100) |
(read-only) |
| DoorLock | Lock control | LockState |
LockDoor(), UnlockDoor() |
Why This Matters:
- All Matter lights implement the same
OnOffcluster - Any Matter controller can send
OnOff.Toggle()to any Matter light - Brand interoperability is guaranteed by the standard
50.5 Common Pitfalls
Pitfall: Misconfiguring Border Router NAT64 Prefix
The Mistake: Developers manually configure the NAT64 prefix on the Border Router (e.g., setting 64:ff9b::/96) without verifying that the prefix doesn’t conflict with the Thread network’s mesh-local prefix or other network infrastructure, causing IPv6-to-IPv4 translation failures.
Why It Happens: NAT64 allows Thread devices to reach IPv4 cloud services, but the synthesized IPv6 addresses must use a dedicated prefix that doesn’t overlap with any existing IPv6 addressing in the network. Many developers copy example configurations without understanding that the 64:ff9b::/96 well-known prefix requires proper upstream routing, or they use custom prefixes without advertising them correctly via Thread Network Data.
The Fix: Use the Border Router’s automatic NAT64 prefix advertisement. In OpenThread Border Router (OTBR):
# Check current NAT64 prefix
> nat64 prefix
64:ff9b::/96 (active)
# Verify prefix is advertised in network data
> netdata show
Prefixes:
64:ff9b::/96 paros med 4000 # NAT64 prefix advertised
# For custom prefix, ensure it's unique and properly routed
> nat64 prefix 2001:db8:1:ffff::/96Always verify with netdata show that the prefix appears with the paros flag (Published, Active, Router, On-mesh, Stable) and test with ping 64:ff9b::8.8.8.8 from a Thread device.
Pitfall: Commissioning Window Timeout During Multi-Device Setup
The Mistake: When commissioning multiple Thread devices in sequence, developers open the commissioning window once and assume it stays open indefinitely. After 15 minutes (900 seconds default), the window closes automatically, causing subsequent devices to fail joining with “No network found” or “Authentication failed” errors.
Why It Happens: Thread’s commissioning window has a security timeout to prevent prolonged exposure to unauthorized joining attempts. The default OPENTHREAD_CONFIG_COMMISSIONER_JOINER_TIMEOUT is 120 seconds per joiner, and the overall window timeout is typically 900 seconds. Developers batch-commissioning many devices often exceed this limit without realizing the window closed silently.
The Fix: Implement commissioning workflow with explicit window management:
// Before each batch of devices, open/extend window
otCommissionerStart(instance);
otCommissionerAddJoiner(instance, NULL, "PSK123456", 120); // 120s per joiner
// Check window status periodically
if (otCommissionerGetState(instance) != OT_COMMISSIONER_STATE_ACTIVE) {
// Window closed - reopen for next batch
otCommissionerStart(instance);
}
// For large deployments, use longer per-joiner timeout
otCommissionerAddJoiner(instance, NULL, pskd, 300); // 5 minutes per deviceIn production, use Matter’s enhanced commissioning with explicit window control: OpenCommissioningWindow(timeout=1800) for 30-minute windows during bulk provisioning.
Pitfall: Incorrect Link Mode for Battery Devices
The Mistake: Setting mRxOnWhenIdle = true for battery-powered sensors, causing the radio to stay on continuously and draining the battery in days instead of years.
Why It Happens: Developers copy router configuration examples without understanding that mRxOnWhenIdle controls whether the radio sleeps. For routers, this must be true (always listening to route traffic). For battery sensors, this must be false (sleep between polls).
The Fix: Always verify link mode matches device power source:
// Battery device - MUST be false
otLinkModeConfig sed_mode = {
.mRxOnWhenIdle = false, // Critical for battery life
.mDeviceType = false,
.mNetworkData = false
};
// Mains-powered device - must be true
otLinkModeConfig router_mode = {
.mRxOnWhenIdle = true, // Required for routing
.mDeviceType = true,
.mNetworkData = true
};Verify with mode CLI command - output should show r (rxOnWhenIdle) only for mains-powered devices.
50.6 Worked Example: Thread Device Commissioning Sequence
Worked Example: Thread Device Commissioning Sequence
Scenario: You are commissioning a new Eve Door & Window sensor (battery-powered SED) onto an existing Thread network using the Apple Home app on your iPhone. The Thread network already has a HomePod Mini as Border Router and 6 smart bulbs as routers.
Given:
- Existing Thread network: PAN ID 0x1A2B, Channel 15
- Network name: “HomeThread”
- Eve sensor PSKd (from QR code): “A1B2C3D4E5F6”
- Commissioner: iPhone with Apple Home app
- HomePod Mini: Border Router + Leader
- Target parent router: Hallway smart bulb (strongest signal)
Steps:
- Discovery phase (0-5 seconds):
- Eve sensor powers on and enters commissioning mode
- Scans 802.15.4 channels 11-26 for Thread networks
- Receives Beacon from HomePod Mini on channel 15
- Network info: PAN ID 0x1A2B, Extended PAN ID, Network Name “HomeThread”
- Commissioner authentication (5-15 seconds):
- User scans Eve sensor QR code with iPhone camera
- iPhone extracts PSKd: “A1B2C3D4E5F6”
- iPhone (Commissioner) connects to HomePod Mini via Wi-Fi
- Initiates commissioning session with Leader
- DTLS session establishment (15-25 seconds):
- Commissioner sends Joiner PSKd to Leader
- Leader advertises commissioning availability
- Eve sensor initiates DTLS handshake with Leader using PSKd
- DTLS 1.2 session established (encrypted channel)
- Session key: ECDH-derived 128-bit AES key
- Credential transfer (25-30 seconds):
- Leader sends Network Master Key (128-bit) over DTLS
- Transfers: Channel 15, PAN ID 0x1A2B, Extended PAN ID
- Eve sensor stores credentials in secure flash
- DTLS session closes
- Mesh attachment (30-45 seconds):
- Eve sensor sends MLE Parent Request (broadcast)
- Hallway bulb responds as best parent (strongest RSSI)
- MLE Child ID Request → Leader assigns Child ID
- Eve sensor attaches to Hallway bulb as parent
- Assigned RLOC16 address (e.g., 0x7C01)
Result: Eve Door sensor successfully commissioned in 45 seconds. Device appears in Apple Home app as “Eve Door Sensor”. Parent router: Hallway bulb. RLOC16: 0x7C01. Poll interval: 30 seconds (SED mode). First status report sent to cloud via HomePod Mini Border Router.
Key Insight: Thread commissioning uses out-of-band authentication (QR code provides PSKd) combined with DTLS encryption for credential transfer. The PSKd is never transmitted over the air; it’s used only to derive session keys. This provides significantly stronger security than Wi-Fi WPA2-PSK because each device has unique credentials, and network keys cannot be captured by simply sniffing commissioning traffic.
50.7 Understanding Check: Thread + Matter Integration
Understanding Check: Thread + Matter Integration
Scenario: You’re building a smart home using Matter devices from different brands: Philips Hue lights, Eve door sensors, and Nanoleaf panels. Your Apple HomePod Mini is the Thread Border Router. You also have some Wi-Fi-based Matter devices (cameras, speakers).
Think about:
- How does Matter use Thread differently for battery sensors vs mains-powered lights?
- What happens when you control a Thread light from your iPhone while away from home?
- Why can devices from different brands work together seamlessly?
Key Insight:
- Matter uses Thread for mesh, Wi-Fi for bandwidth: Battery sensors use Thread (low power, mesh reliability). Cameras/speakers use Wi-Fi (high bandwidth for video/audio). Matter application layer works over both.
- Message path (remote control): iPhone (cellular) → Cloud → Wi-Fi router → HomePod Mini (Border Router) → Thread mesh → Light bulb. Border Router bridges Wi-Fi ↔︎ Thread.
- Cross-brand compatibility: Matter defines standard device types (light, sensor, lock) and control commands (on/off, brightness, lock/unlock). Thread provides the reliable network layer. Brands implement Matter spec, so all devices speak the same language.
- Thread’s role: Provides IPv6 networking and mesh routing. Matter sits on top, handling application-level device control and interoperability.
Real-world example: When you say “Hey Siri, turn off bedroom lights”: 1. Siri (on HomePod Mini) sends Matter command over Thread mesh to Philips Hue bulb 2. Bulb receives Matter OnOff command via Thread multi-hop routing 3. Bulb turns off and sends status update back via Thread mesh 4. No Philips Hue bridge needed - HomePod Mini Border Router handles Thread ↔︎ Wi-Fi
50.8 Common Mistake: Incorrect Link Mode Configuration Destroying Battery Life
Common Mistake: Setting mRxOnWhenIdle=true for Battery Devices
The Mistake: A developer building a battery-powered Thread door sensor copies router configuration code and sets mRxOnWhenIdle = true in the link mode. The sensor works correctly during testing but drains batteries in 3-7 days instead of the expected 2-3 years.
Why This Happens:
Many OpenThread examples show router configuration (for smart bulbs, plugs, always-powered devices). Developers copy this code without understanding that mRxOnWhenIdle controls whether the radio sleeps or stays on continuously.
What mRxOnWhenIdle Actually Does:
When mRxOnWhenIdle = true (ROUTER MODE):
- Radio stays on 100% of the time
- Device listens for incoming messages continuously
- Can receive messages instantly (no polling delay)
- Current draw: 15-25 mA continuously (nRF52840: 5.3 mA RX, ESP32-C6: 95 mA RX)
- Battery life calculation (CR2032 coin cell, 220 mAh):
- Continuous draw: 15 mA
- Battery life: 220 mAh / 15 mA = 14.7 hours ❌
When mRxOnWhenIdle = false (SED MODE):
- Radio sleeps between scheduled polls
- Device wakes periodically (e.g., every 60 seconds) to check for messages
- Parent router buffers messages while device sleeps
- Current draw: 5 µA sleep + periodic 20 ms wake-ups
- Battery life calculation (CR2032, 220 mAh):
- Sleep: 5 µA × 3600 seconds = 18 mAh per hour
- Wake: 15 mA × 20 ms × 60 times = 18 mAh per hour
- Average: ~0.02 mA
- Battery life: 220 mAh / 0.02 mA = 11,000 hours = 458 days ≈ 1.25 years ✅
Real-World Impact:
Eve Door & Window Sensor (Correct Implementation):
// Eve's configuration (battery-powered)
otLinkModeConfig mode = {
.mRxOnWhenIdle = false, // ← CRITICAL FOR BATTERY
.mDeviceType = false,
.mNetworkData = false
};
otThreadSetLinkMode(instance, mode);
otLinkSetPollPeriod(instance, 30000); // 30-second pollResult: 2-3 year battery life on CR2032 ✅
Bad Implementation (Copied from Router Example):
// WRONG - Copied from smart bulb example
otLinkModeConfig mode = {
.mRxOnWhenIdle = true, // ← KILLS BATTERY
.mDeviceType = false,
.mNetworkData = false
};
otThreadSetLinkMode(instance, mode);
// No poll period set (not needed for always-on devices)Result: Battery drains in 3-7 days ❌
How to Detect This Bug:
During development:
Check link mode via OpenThread CLI:
> mode rdn # r = rxOnWhenIdle (WRONG for battery device!)Should show:
> mode - # No flags: no 'r' (radio sleeps), no 'd' (MTD), no 'n' (stable data only)Measure current draw with multimeter:
- Expected SED: 10-50 µA average
- Bug (always-on): 10-25 mA average (1000× higher!)
Check parent router’s child table:
> childtable | ID | RLOC16 | Timeout | Mode | |----|--------|---------|------| | 5 | 0x4805 | 240s | rdn | ← BAD (r flag present)Should be:
| 5 | 0x4805 | 240s | dn | ← GOOD (no r flag)
Field Failure Indicators:
- Customer complaints: “Batteries die in days”
- Support tickets: “Sensor works but needs constant battery replacement”
- High return rate: “Defective product - won’t hold charge”
How to Fix in Production Firmware:
// Add compile-time assertion to catch this at build
#if defined(DEVICE_TYPE_BATTERY_SENSOR)
// Force correct link mode for battery devices
otLinkModeConfig battery_mode = {
.mRxOnWhenIdle = false, // Enforced for battery
.mDeviceType = false,
.mNetworkData = false
};
#if defined(DEBUG_MODE)
// Build fails if someone tries to override this
static_assert(battery_mode.mRxOnWhenIdle == false,
"Battery sensors MUST have mRxOnWhenIdle=false");
#endif
otThreadSetLinkMode(instance, battery_mode);
otLinkSetPollPeriod(instance, BATTERY_POLL_PERIOD_MS);
#else
// Mains-powered devices can use always-on mode
otLinkModeConfig router_mode = {
.mRxOnWhenIdle = true,
.mDeviceType = true,
.mNetworkData = true
};
otThreadSetRouterEligible(instance, true);
otThreadSetLinkMode(instance, router_mode);
#endifTrade-Off Analysis:
| Link Mode | Battery Life (CR2032) | Latency | Use Case |
|---|---|---|---|
| mRxOnWhenIdle=true | 12-24 hours | 0 ms (instant) | ❌ NEVER for battery |
| mRxOnWhenIdle=false, poll=5s | 3-6 months | 0-5 seconds | Motion sensors |
| mRxOnWhenIdle=false, poll=30s | 1-2 years | 0-30 seconds | Door sensors |
| mRxOnWhenIdle=false, poll=300s | 5-10 years | 0-300 seconds | Temperature sensors |
Key Insight: The difference between mRxOnWhenIdle=true and mRxOnWhenIdle=false is literally 1000× battery life difference. This single boolean flag determines whether your product lasts days or years. Always verify this setting during code review for battery-powered devices.
Prevention Checklist:
- ✅ Code review: Flag any
mRxOnWhenIdle=truein battery device code - ✅ Power testing: Measure average current during development (should be µA, not mA)
- ✅ CLI verification: Check
modeoutput before shipping - ✅ Automated testing: Add unit test verifying link mode matches device type
- ✅ Documentation: Clearly label which code examples are for routers vs battery devices
50.9 How It Works: Thread Secure Commissioning Process
How It Works: DTLS Commissioning with PSKd
Thread uses a secure commissioning process to add new devices without exposing long-term network credentials:
Step 1: Device Powers On (Joiner Role)
- New device enters “detached” state (not part of any Thread network)
- Device knows only its PSKd (Pre-Shared Key for Devices) - printed on QR code or label
- PSKd is device-specific, not the network key (single-use credential)
Step 2: User Initiates Commissioning (Commissioner Role)
- User scans device QR code in Matter/Thread app (e.g., Apple Home, Google Home)
- App sends PSKd to Commissioner (Border Router or dedicated commissioning device)
- Commissioner authorizes the PSKd for 120 seconds (time window for joining)
Step 3: Joiner Discovery
- Device broadcasts MLE Discovery Request on all 16 Thread channels (11-26)
- Commissioner (or Joiner Routers acting as proxies) responds with MLE Discovery Response
- Response includes: network name, PAN ID, channel, Commissioner UDP port
Step 4: DTLS Handshake (Encrypted Channel Establishment)
- Device initiates DTLS handshake to Commissioner using PSKd as shared secret
- DTLS provides end-to-end encryption for commissioning (even over multi-hop mesh)
- Mutual authentication: Device proves it has valid PSKd, Commissioner proves it’s authorized
Step 5: Credential Distribution
- Commissioner sends Network Master Key over DTLS-encrypted channel
- Additional parameters distributed: PAN ID, extended PAN ID, mesh-local prefix, network name
- Device stores credentials in non-volatile memory (survives power cycles)
Step 6: Network Attachment
- Device uses Network Master Key to authenticate to Thread network
- Follows standard MLE Parent Request/Response process
- Router assigns RLOC16, device configures IPv6 addresses
- Device enters “child” or “router” state (operational)
Step 7: PSKd Invalidation
- Commissioner invalidates PSKd after successful join (single-use credential)
- Device can no longer use PSKd (must be factory reset to rejoin)
- Prevents replay attacks (stolen QR code can’t join network)
Key Security Properties:
- PSKd never transmitted in plaintext: Used only as DTLS pre-shared key
- Network key never exposed: Distributed only over DTLS tunnel
- Time-limited authorization: 120-second window reduces attack surface
- Out-of-band authentication: Physical QR code scan proves device proximity
- Forward secrecy: Compromised PSKd after join doesn’t expose network key
50.10 Try It Yourself: Thread Development with OpenThread CLI
Try It Yourself: Build a Thread Network with CLI
Scenario: You’re developing a Thread network using OpenThread CLI to understand network formation, device roles, and diagnostics before writing production firmware.
Tasks:
- Form a Thread Network (Border Router):
# Initialize Thread stack
> dataset init new
Done
# Configure network parameters
> dataset networkname MyThreadNet
Done
> dataset channel 15
Done
> dataset panid 0xABCD
Done
> dataset commit active
Done
# Start Thread interface
> ifconfig up
Done
> thread start
Done
# Verify Border Router role
> state
leader
> rloc16
0x0000
# Check assigned addresses
> ipaddr
fd12:3456:789a:1::ff:fe00:0 (Mesh-Local EID)
fe80::a0b:c0d:e0f:1234 (Link-Local)- Add a Router Device:
# On second device
> dataset networkname MyThreadNet
Done
> dataset channel 15
Done
> dataset panid 0xABCD
Done
> dataset masterkey 00112233445566778899aabbccddeeff
Done
> dataset commit active
Done
> ifconfig up
> thread start
# Wait 15-30 seconds, then check role
> state
router
> rloc16
0x4800 # Router ID 18 (0x12 in hex)
# Check parent (should show Border Router)
> parent
Ext Addr: a0bc0de0f1234567
Rloc: 0x0000
Link Quality In: 3
Link Quality Out: 3- Add a Sleepy End Device:
# On third device (battery-powered sensor)
# Join network (same dataset as router)
> dataset networkname MyThreadNet
> dataset channel 15
> dataset panid 0xABCD
> dataset masterkey 00112233445566778899aabbccddeeff
> dataset commit active
# Configure as SED (critical! NO 'r' flag)
> mode - # No flags: RxOnWhenIdle OFF, MTD, stable data only
Done
# Start Thread
> ifconfig up
> thread start
# Verify SED role
> state
child
> childip max
1 # Maximum 1 IP address (minimal)
# Check poll interval
> pollperiod
3000 # 3000ms = 3 seconds (configure longer for battery)
# Set longer poll interval
> pollperiod 60000 # 60 seconds
Done- Network Diagnostics:
# On Border Router, check neighbor table
> neighbor table
| Role | RLOC16 | Age | Avg RSSI | Last RSSI |
|------|--------|-----|----------|-----------|
| R | 0x4800 | 23 | -45 | -42 |
| C | 0x4801 | 45 | -58 | -60 |
# Check child table
> child table
| ID | RLOC16 | Timeout | Age | LQ | RSSI |
|----|--------|---------|-----|----|----- |
| 1 | 0x0401 | 240 | 45 | 3 | -58 |
# Check router table
> router table
| ID | RLOC16 | Next Hop | Path Cost | LQ In | LQ Out |
|----|--------|----------|-----------|-------|--------|
| 0 | 0x0000 | - | 0 | 3 | 3 |
| 18 | 0x4800 | 0 | 1 | 3 | 3 |What to Observe:
- Border Router becomes Leader (state = leader, RLOC16 = 0x0000)
- Router joins and gets RLOC16 with non-zero Router ID
- SED joins as child with
mode -(norflag means RxOnWhenIdle OFF) - Poll period affects how often SED wakes (battery life trade-off)
- Neighbor/child/router tables show network topology
Common Pitfalls to Avoid:
- Including the
rflag for SEDs: Therflag enables RxOnWhenIdle (radio always on), wasting battery – usemode sormode -for SEDs - Mismatched masterkey: Devices won’t join if network key differs
- Wrong channel: Devices won’t see each other if on different channels
thread startbeforeifconfig up: Must enable interface first
50.11 Concept Check
50.12 Concept Relationships
| Concept | Relationship | Connected Concept |
|---|---|---|
| PSKd (Pre-Shared Key for Devices) | Secures | DTLS commissioning handshake (single-use credential) |
| mRxOnWhenIdle Flag | Determines | Router/FED (true, ~20 mA) vs SED (false, ~5 µA) power mode |
| OpenThread CLI | Provides | Network diagnostics (state, rloc16, neighbor table, child table) |
| Matter Clusters | Defines | Application-layer device capabilities (On/Off, LevelControl, etc.) |
| NAT64 Prefix | Enables | IPv4 internet connectivity for IPv6-only Thread devices |
50.13 See Also
- Thread Network Operations - Network formation and self-healing details
- Thread Deployment Guide - Production deployment and troubleshooting
- Thread Security and Matter - Cryptographic details and Matter commissioning
- Matter Overview - Matter application layer and device types
- OpenThread Documentation - Official OpenThread SDK reference
50.14 Summary
This chapter covered Thread development and Matter integration:
- OpenThread CLI: Diagnostic commands (
state,rloc16,neighbor list) provide visibility into network health and topology - Device Role Configuration: Routers require
mRxOnWhenIdle = truefor continuous listening; SEDs requiremRxOnWhenIdle = falsefor battery life - Matter Integration: Matter provides application-layer device interoperability (clusters, commands) while Thread provides the mesh network layer
- Common Pitfalls: NAT64 prefix conflicts cause IPv4 connectivity failures; commissioning timeouts cause batch provisioning failures; incorrect link mode destroys battery life
- Commissioning: Secure device onboarding uses QR codes (PSKd) for out-of-band authentication combined with DTLS encryption
50.15 Knowledge Check
::
::
Key Concepts
- OpenThread: An open-source implementation of the Thread networking protocol by Google/Nest, widely used in ESP32-H2, nRF52840, and other IoT chips.
- ot-cli: The OpenThread command-line interface for managing Thread network configuration, viewing network state, and performing commissioning operations.
- FreeRTOS + OpenThread: A common embedded integration pairing OpenThread’s networking stack with FreeRTOS’s real-time task scheduling for Thread-enabled IoT devices.
- Thread Demo App: A reference application demonstrating Thread network formation, CoAP communication, and device role management on target hardware.
- RCP (Radio Co-Processor): An architecture where the IEEE 802.15.4 radio runs OpenThread minimal host on a co-processor, with the full Thread stack running on a separate application processor.
50.16 What’s Next
| Topic | Chapter | What You Will Learn |
|---|---|---|
| Thread deployment | Thread Deployment Guide | Border Router configuration, multi-network design, and production troubleshooting |
| Thread security | Thread Security and Matter | Cryptographic foundations, key rotation, and Matter security architecture |
| Matter protocol | Matter Overview | Matter application layer, device types, and multi-admin fabric model |
| Thread fundamentals | Thread Network Architecture | Device roles, mesh topology, and addressing architecture |
| Thread operations | Thread Network Operations | Network formation, self-healing mechanisms, and power management |
| Hands-on labs | Hands-On Labs Hub | Interactive ESP32 simulations for Thread and mesh networking concepts |