document language: English | 简体ä¸æ–‡
Component | Build Status | NuGet Package |
---|---|---|
nanoFramework.Networking.Thread |
.NET nanoFramework library for working with OpenThread networking.
The OpenThread network is based on IPV6 and requires a firmware which has IPV6 and Thread enabled. Currently there 3 different firmwares available.
- ESP32_C6_Thread
- ESP32_H2_Thread
- ESP32_PSRAM_REV3_IPV6
The ESP32_PSRAM_REV3_IPV6 firmware requires a OpenThread radio Co-Processor using the RCP firmware. This firmware is available in the Espressif IDF SDK under examples/openthread/ot_rcp
This library is the initial version and is a work in progress. More work needs to be done to add direct support for CoAP. This version enables access to the OpenThread CLI which allows most options to be accessed if not available directly in C#.
For more information on OpenThread see the website https://openthread.io/
When the openThread stack is created the node device type needs to be specified.
This can be one of the types.
- Router
- End device
- Sleepy end device
The Router and End device are for a powered devices. The End device will stay in the child role and won't ever be promoted to a router. The Router type will start with the child role and be promoted to the router or leader roles as required.
The Sleepy end device are for battery operated devices and don't stay connected to the mesh network to save on battery. It will regularly poll its connected router for any messages. Routers connected to a sleepy end device will store and forward messages.
The devices can take on a number of roles as already mentioned. These are child, Router or Leader.
Creating openThread object for a device with a built-in 802.15.4 radio.
OpenThread ot = OpenThread.CreateThreadWithNativeRadio(ThreadDeviceType.Router);
Create the openThread object specifying the COM port connected to the co-processor. Before calling this, the pins used by COM port may need to be set up. This depends on the target device. (i.e. ESP32)
This code creates the stack using the network processor connected by UART on device COM1.
OpenThread ot = OpenThread.CreateThreadWithUartRadio(ThreadDeviceType.Router, 1);
Create the openThread object specifying the SPI port connected to the co-processor. Before calling this, the pins used by SPI port may need to be set up. This depends on the target device. (i.e. ESP32)
This code creates the stack using the network processor connected by SPI on device SPI1.
OpenThread ot = OpenThread.CreateThreadWithSpiRadio(ThreadDeviceType.Router, 1);
To connect to an existing Thread network you need to specify the minimum of the network name, network key and panId. Supplying other parameters like the channel number reduces the time it take to re-connect as it doesn't need to scan all channels for network.
// Create dataset for existing network
OpenThreadDataset data = new OpenThreadDataset()
{
NetworkName = "nanoFramework",
NetworkKey = new byte[16] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 },
Channel = 15
};
// Set the Active Dataset
ot.Dataset = data;
// Start Thread network
ot.Start();
If an existing network already exists it will attach to it. If not it will wait about 2 minutes and create a new network using credentials in dataset.
Monitor the state change events to know when the device has attached to the mesh network. See later section on events raised by the OpenThread class.
To attach a device to an existing network where the network credentials are unknown. A commissioner from another device on network(border router) or an external device will need to be used.
This is done using a pre-shared key between the device and the commissioner. Once the device is connected to network the network credentials can be saved so that next time it can attach to the mesh directly.
Start the joiner using the pre-shared key (PSKd) When JoinerStart() return true the commissioning will be complete.
Loop forever until device is commissioned.
while (ot.JoinerStart("J01NME") == false)
{
// Wait for Join to work
Thread.Sleep(2000);
}
// Start Thread network
ot.Start();
Once the device is in an attached state the current dataset can be read using the Dataset property of the openThread object and saved for next time.
The OpenThread object has 3 events that can be monitored.
ot.OnStatusChanged += Ot_OnStatusChanged;
ot.OnRoleChanged += Ot_OnRoleChanged;
ot.OnConsoleOutputAvailable += Ot_OnConsoleOutputAvailable;
This event is fired when the state of the Thread stack changes. The main states that need to be monitored are the Detached and Attached events. There will also be events for network interface up/down, an IPV6 address has been assigned and when the stack has been started or stopped.
Here is a example of handling the Attached event. It uses an event to signal main thread when connected to mesh network. The main thread can wait on the event after starting the openThread.
See samples for full code.
static AutoResetEvent WaitNetAttached = new AutoResetEvent(false);
private static void Ot_OnStatusChanged(OpenThread sender, OpenThreadStateChangeEventArgs args)
{
switch ((ThreadDeviceState)args.currentState)
{
case ThreadDeviceState.OPENTHREAD_EVENT_ATTACHED:
WaitNetAttached.Set();
break;
}
}
This event is fired when the role of the device changes.
The role can be one of the following:
Role | Description |
---|---|
Disabled | OpenThread stack not started. |
Detached | OpenThread stack started but not connected to mesh. |
Child | The device is currently a child, connected to a router. |
Router | The device is a router and has 0 or more child devices. |
Leader | The device is a router that has been promoted to leader. |
This event is fired when unsolicited messages are received on Command Line Interface (CLI). The event will supply an array of strings from the CLI to the application. See CLI section for more information.
With Thread the main means of communication is via UDP due to its low resource overhead. With UDP error/retries needs to be done at the application level. For that reason CoAP is the recommended way to transfer information between devices as it has mechanisms to guarantee message delivery.
In OpenThread, multi cast addresses are used to communicate with multiple devices simultaneously within the network. For example the IPV6 multi cast address FC03::1 can be used to communicate with all nodes except sleepy end devices in the mesh network. Sleepy end devices need to be addressed directly.
For more information on OpenThread IPV6 addressing see: https://openthread.io/guides/thread-primer/ipv6-addressing. These addresses are crucial for sending messages to multiple devices efficiently in an OpenThread network.
The openThread stack includes coAP support. Currently this is not been implemented from C# but it can be accessed using the CLI interface although this method is limited.
Another way is to use a third party library.
The CLI supports limited UDP communication but the best method now is to use the normal UDP socket communication built in to nanoFramework. The samples show how this can be done using sockets.
For examples on using sockets for communication see the Thread sockets samples
As the OpenThread is based on an IP networking it also supports communication via TCP/IP. All the normal methods of communicating with TCP/IP will work.
The OpenThread library has a large number of different APIs.
We can't implement all of these in this library but we have created an API to call the Command Line Interface(CLI) which
gives access to all the OpenThread APIs available in the build.
For details on available CLI commands see: https://openthread.io/reference/cli/commands
There are 2 different APIs to send commands to the CLI. One where the result of the command is returned as a return value of API and another API where all results are fired on OnConsoleOutputAvailable event.
If you have unsolicited messages being outputted on the CLI at the same time as a command is returning results then it will be better to just use the OnConsoleOutputAvailable event for all output.
This will send a command to the CLI and wait for all strings returned until a 'done' string is seen. These strings will be returned directly by the command excluding the 'done' message.
CLI command to return all the IPV6 addresses assigned to the interface.
string[] results = ot.CommandLineInputAndWaitResponse("ipaddr");
If you want to know the IPV6 address for local communication then just use the property MeshLocalAddress.
IPAddress meshLocal = ot.MeshLocalAddress;
The CommandLineInput method sends the command to the CLI and returns. All the results for the command are returned via the OnConsoleOutputAvailable event.
For documentation, providing feedback, issues and finding out how to contribute please refer to the Home repo.
Join our Discord community here.
The list of contributors to this project can be found at CONTRIBUTORS.
The nanoFramework Class Libraries are licensed under the MIT license.
This project has adopted the code of conduct defined by the Contributor Covenant to clarify expected behavior in our community. For more information see the .NET Foundation Code of Conduct.
This project is supported by the .NET Foundation.