Skip to content

Commit

Permalink
Support for Thread (#484)
Browse files Browse the repository at this point in the history
* Support for Thread

* Fix docu

* Support for ESP IDF 4

* Support for ESP IDF 4

* Small fix to the docu

* Clippy

* Fix the ESP-IDF 4.4 build

* Ipv4 conf is now optional

* Leave out a set of TODOs as to what is pending

* Leave out a set of TODOs as to what is pending

* Leave out a set of TODOs as to what is pending

* Correct part name for port config; eliminate copy-paste

* Events

* Fix CI

* Fix wrong cfg

* fix events build for earlier ESP IDFs

* TOD initialization

* Expose the additional APIs on EspThread too

* Fix small typo in docs

* Re-use the UART config struct of the UART driver

* Re-use the SPI config struct of the SPI driver

* Clippy

* Options to scan for Thread networks

* Expose the scan APIs on EspThread as well

* Small rename to match the other methods

* Clippy

* Border router

* Current device role

* Border router works OK since 5.2

* Use strong typing for roles in the events

* Border router works OK since 5.2

* Border router works OK since 5.2

* Border router works OK since... 5.3

* Border router works OK since... 5.3

* Border router works OK since... 5.3

* Problem is the MCU, not the ESP IDF version

* Re-enable 4.4.7 to catch BR errors

* Fix wrong method name

* Initial examples

* Examples; Node and BorderRouter seem to work (do something)

* Restore default build conf

* Restore default build conf

* Reduce the event_fd handles to 4 to match the other Thread example

* ESP-IDF 4.4 comnpat

* The driver is actually Send and Sync

* Protect code when netif is not enabled

* Typo

* Raw IPv6 packets RX/TX

* Set log level; option to initialize OT CLI

* Wifi Coexist APIs

* Small enhancements to the examples

* Refine the flags for coex modes; fix docu

* Refine the flags for coex modes; fix docu

* RCP example; bugfixes related to UART

* fmt

* Thread BR example with UART connection

* Restore the BR example in commented out state

* Restore the BR example in commented out state

* Remove resolved TODOs

* fmt
  • Loading branch information
ivmarkov authored Oct 1, 2024
1 parent e2b97ba commit fdd4606
Show file tree
Hide file tree
Showing 9 changed files with 2,283 additions and 13 deletions.
28 changes: 28 additions & 0 deletions .github/configs/sdkconfig.defaults
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,31 @@ CONFIG_BT_BLE_DYNAMIC_ENV_MEMORY=y

CONFIG_LWIP_PPP_SUPPORT=y
#CONFIG_LWIP_SLIP_SUPPORT=y

# Generic Thread functionality
CONFIG_OPENTHREAD_ENABLED=y

# Thread Border Router
#CONFIG_OPENTHREAD_BORDER_ROUTER=y

# These are also necessary for the Joiner feature
#CONFIG_MBEDTLS_CMAC_C=y
#CONFIG_MBEDTLS_SSL_PROTO_DTLS=y
#CONFIG_MBEDTLS_KEY_EXCHANGE_ECJPAKE=y
#CONFIG_MBEDTLS_ECJPAKE_C=y

# Border Router again, LWIP
#CONFIG_LWIP_IPV6_NUM_ADDRESSES=12
#CONFIG_LWIP_NETIF_STATUS_CALLBACK=y
#CONFIG_LWIP_IPV6_FORWARD=y
#CONFIG_LWIP_MULTICAST_PING=y
#CONFIG_LWIP_NETIF_STATUS_CALLBACK=y
#CONFIG_LWIP_HOOK_IP6_ROUTE_DEFAULT=y
#CONFIG_LWIP_HOOK_ND6_GET_GW_DEFAULT=y
#CONFIG_LWIP_HOOK_IP6_INPUT_CUSTOM=y
#CONFIG_LWIP_HOOK_IP6_SELECT_SRC_ADDR_CUSTOM=y
#CONFIG_LWIP_IPV6_AUTOCONFIG=y
#CONFIG_LWIP_TCPIP_TASK_STACK_SIZE=4096

# Border Router again, mDNS
#CONFIG_MDNS_MULTIPLE_INSTANCE=y
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@
/.embuild
/target
/Cargo.lock
*.lock
**/*.rs.bk
/.devcontainer
68 changes: 68 additions & 0 deletions examples/thread.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
//! Example of using Thread in "Node" mode.
//!
//! The example just starts Thread and logs the events, without doing anything else useful.
//! However, in 99% of the case this is exactly what you want to do.
//!
//! NOTE: This example only works on MCUs that has Thread capabilities, like the ESP32-C6 or ESP32-H2.
//!
//! It is however possible to run this example on other MCUs, using the UART or SPI protocols, but then
//! you anyway would need _another_, Thread-capable MCU that runs Thread in RCP mode (see the `thread_rcp`) example.

fn main() -> anyhow::Result<()> {
esp_idf_svc::sys::link_patches();
esp_idf_svc::log::EspLogger::initialize_default();

#[cfg(any(esp32h2, esp32c6))]
example::main()?;

#[cfg(not(any(esp32h2, esp32c6)))]
log::error!("This example only works on MCUs that have Thread capabilities, like the ESP32-C6 or ESP32-H2.");

Ok(())
}

#[cfg(any(esp32h2, esp32c6))]
mod example {
use std::sync::Arc;

use log::info;

use esp_idf_svc::eventloop::EspSystemSubscription;
use esp_idf_svc::hal::prelude::Peripherals;
use esp_idf_svc::io::vfs::MountedEventfs;
use esp_idf_svc::thread::{EspThread, ThreadEvent};
use esp_idf_svc::{eventloop::EspSystemEventLoop, nvs::EspDefaultNvsPartition};

pub fn main() -> anyhow::Result<()> {
let peripherals = Peripherals::take()?;
let sys_loop = EspSystemEventLoop::take()?;
let nvs = EspDefaultNvsPartition::take()?;

let mounted_event_fs = Arc::new(MountedEventfs::mount(4)?);

info!("Initializing Thread...");

let _subscription = log_thread_sysloop(sys_loop.clone())?;

let mut thread =
EspThread::new(peripherals.modem, sys_loop.clone(), nvs, mounted_event_fs)?;

thread.init()?;

info!("Thread initialized, now running...");

thread.run()?;

Ok(())
}

fn log_thread_sysloop(
sys_loop: EspSystemEventLoop,
) -> Result<EspSystemSubscription<'static>, anyhow::Error> {
let subscription = sys_loop.subscribe::<ThreadEvent, _>(|event| {
info!("Got: {:?}", event);
})?;

Ok(subscription)
}
}
191 changes: 191 additions & 0 deletions examples/thread_br.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
//! Example of a Thread Border Router.
//!
//! This example only works on MCUs that do have Wifi capabilities, as follows:
//! - On MCUs with both native `Thread` as well as `Wifi` capabilities, the example will run in co-exist mode, on a single MCU;
//! - On MCUs with only `Wifi` capabilities, the example will run in UART mode, so you need to flash the `thread_rcp` example
//! on a separate MCU which does have native `Thread` capabilities, and connect the two via UART.
//!
//! NOTE NOTE NOTE:
//! To build, you need to put the following in your `sdkconfig.defaults`:
//! ```text
//! CONFIG_OPENTHREAD_ENABLED=y
//!
//! # Thread Border Router
//! CONFIG_OPENTHREAD_BORDER_ROUTER=y
//!
//! # These are also necessary for the Joiner feature
//! CONFIG_MBEDTLS_CMAC_C=y
//! CONFIG_MBEDTLS_SSL_PROTO_DTLS=y
//! CONFIG_MBEDTLS_KEY_EXCHANGE_ECJPAKE=y
//! CONFIG_MBEDTLS_ECJPAKE_C=y
//!
//! # Border Router again, lwIP
//! CONFIG_LWIP_IPV6_NUM_ADDRESSES=12
//! CONFIG_LWIP_NETIF_STATUS_CALLBACK=y
//! CONFIG_LWIP_IPV6_FORWARD=y
//! CONFIG_LWIP_MULTICAST_PING=y
//! CONFIG_LWIP_NETIF_STATUS_CALLBACK=y
//! CONFIG_LWIP_HOOK_IP6_ROUTE_DEFAULT=y
//! CONFIG_LWIP_HOOK_ND6_GET_GW_DEFAULT=y
//! CONFIG_LWIP_HOOK_IP6_INPUT_CUSTOM=y
//! CONFIG_LWIP_HOOK_IP6_SELECT_SRC_ADDR_CUSTOM=y
//! CONFIG_LWIP_IPV6_AUTOCONFIG=y
//! CONFIG_LWIP_TCPIP_TASK_STACK_SIZE=4096
//!
//! # Border Router again, mDNS
//! CONFIG_MDNS_MULTIPLE_INSTANCE=y
//! ```
//!
//! And also the following in your `Cargo.toml`:
//! ```toml
//! [[package.metadata.esp-idf-sys.extra_components]]
//! remote_component = { name = "espressif/mdns", version = "1.2" }
//! ```

#![allow(unexpected_cfgs)]

fn main() -> anyhow::Result<()> {
esp_idf_svc::sys::link_patches();
esp_idf_svc::log::EspLogger::initialize_default();

#[cfg(any(esp32h2, esp32h4))]
{
log::error!("This example only works on MCUs which do have Wifi support.");
}

#[cfg(not(any(esp32h2, esp32h4)))]
{
#[cfg(i_have_done_all_configs_from_the_top_comment)]
// Remove this `cfg` when you have done all of the above for the example to compile
example::main()?;

// Remove this whole code block when you have done all of the above for the example to compile
#[cfg(not(i_have_done_all_configs_from_the_top_comment))]
{
log::error!("Please follow the instructions in the source code.");
}
}

Ok(())
}

#[cfg(i_have_done_all_configs_from_the_top_comment)] // Remove this `cfg` when you have done all of the above for the example to compile
#[cfg(not(any(esp32h2, esp32h4)))]
mod example {
use core::convert::TryInto;

use std::sync::Arc;

use log::info;

use embedded_svc::wifi::{AuthMethod, ClientConfiguration, Configuration};

use esp_idf_svc::eventloop::EspSystemSubscription;
use esp_idf_svc::hal::prelude::Peripherals;
use esp_idf_svc::io::vfs::MountedEventfs;
use esp_idf_svc::thread::{EspThread, ThreadEvent};
use esp_idf_svc::wifi::{BlockingWifi, EspWifi};
use esp_idf_svc::{eventloop::EspSystemEventLoop, nvs::EspDefaultNvsPartition};

const SSID: &str = env!("WIFI_SSID");
const PASSWORD: &str = env!("WIFI_PASS");

pub fn main() -> anyhow::Result<()> {
let peripherals = Peripherals::take()?;
let sys_loop = EspSystemEventLoop::take()?;
let nvs = EspDefaultNvsPartition::take()?;

#[cfg(esp32c6)]
let (wifi_modem, _thread_modem) = { peripherals.modem.split() };

#[cfg(not(esp32c6))]
let (wifi_modem, _thread_modem) = { (peripherals.modem, ()) };

let mounted_event_fs = Arc::new(MountedEventfs::mount(6)?);

let mut wifi = BlockingWifi::wrap(
EspWifi::new(wifi_modem, sys_loop.clone(), Some(nvs.clone()))?,
sys_loop.clone(),
)?;

connect_wifi(&mut wifi)?;

let ip_info = wifi.wifi().sta_netif().get_ip_info()?;

info!("Wifi DHCP info: {:?}", ip_info);

info!("Initializing Thread Border Router...");

let _subscription = log_thread_sysloop(sys_loop.clone())?;

// On the C6, run the Thread Border Router in co-exist mode
#[cfg(esp32c6)]
let mut thread = EspThread::new_br(
_thread_modem,
sys_loop,
nvs,
mounted_event_fs,
wifi.wifi().sta_netif(),
)?;

// On all other chips, run the Thread Border Router in UART mode
#[cfg(not(esp32c6))]
let mut thread = EspThread::new_br_uart(
peripherals.uart1,
peripherals.pins.gpio2,
peripherals.pins.gpio3,
&esp_idf_svc::thread::config::uart_default_cfg(),
sys_loop,
nvs,
mounted_event_fs,
wifi.wifi().sta_netif(),
)?;

thread.init()?;

#[cfg(esp32c6)]
thread.init_coex()?;

thread.set_tod_from_cfg()?;

info!("Thread Border Router initialized, now running...");

thread.run()?;

Ok(())
}

fn connect_wifi(wifi: &mut BlockingWifi<EspWifi<'static>>) -> anyhow::Result<()> {
let wifi_configuration: Configuration = Configuration::Client(ClientConfiguration {
ssid: SSID.try_into().unwrap(),
bssid: None,
auth_method: AuthMethod::WPA2Personal,
password: PASSWORD.try_into().unwrap(),
channel: None,
..Default::default()
});

wifi.set_configuration(&wifi_configuration)?;

wifi.start()?;
info!("Wifi started");

wifi.connect()?;
info!("Wifi connected");

wifi.wait_netif_up()?;
info!("Wifi netif up");

Ok(())
}

fn log_thread_sysloop(
sys_loop: EspSystemEventLoop,
) -> Result<EspSystemSubscription<'static>, anyhow::Error> {
let subscription = sys_loop.subscribe::<ThreadEvent, _>(|event| {
info!("Got: {:?}", event);
})?;

Ok(subscription)
}
}
78 changes: 78 additions & 0 deletions examples/thread_rcp.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
//! Example of using Thread in "RCP" mode.
//!
//! The `RCP` mode of `ThreadDriver` is a mode where the Thread stack is running on the MCU, however,
//! it communicates via UART or SPI to another - "master" MCU which - most often than not - does not
//! have a native Thread radio, but has other connectivity like Wifi. It is this other MCU which actually runs
//! Thread as a real "Node", from the POV of the user.
//!
//! NOTE: This example only works on MCUs that has Thread capabilities, like the ESP32-C6 or ESP32-H2.

fn main() -> anyhow::Result<()> {
esp_idf_svc::sys::link_patches();
esp_idf_svc::log::EspLogger::initialize_default();

#[cfg(all(any(esp32h2, esp32c6), esp_idf_openthread_radio))]
example::main()?;

#[cfg(not(any(esp32h2, esp32c6)))]
log::error!("This example only works on MCUs that have Thread capabilities, like the ESP32-C6 or ESP32-H2.");

#[cfg(not(esp_idf_openthread_radio))]
log::error!("Put `CONFIG_OPENTHREAD_RADIO=y` in your `sdkconfig.defaults`");

Ok(())
}

#[cfg(all(any(esp32h2, esp32c6), esp_idf_openthread_radio))]
mod example {
use std::sync::Arc;

use log::info;

use esp_idf_svc::eventloop::EspSystemSubscription;
use esp_idf_svc::hal::prelude::Peripherals;
use esp_idf_svc::io::vfs::MountedEventfs;
use esp_idf_svc::thread::{ThreadDriver, ThreadEvent};
use esp_idf_svc::{eventloop::EspSystemEventLoop, nvs::EspDefaultNvsPartition};

pub fn main() -> anyhow::Result<()> {
let peripherals = Peripherals::take()?;
let sys_loop = EspSystemEventLoop::take()?;
let nvs = EspDefaultNvsPartition::take()?;

let mounted_event_fs = Arc::new(MountedEventfs::mount(4)?);

info!("Initializing Thread RCP...");

let _subscription = log_thread_sysloop(sys_loop.clone())?;

let mut thread = ThreadDriver::new_rcp_uart(
peripherals.modem,
peripherals.uart1,
peripherals.pins.gpio10,
peripherals.pins.gpio11,
&esp_idf_svc::thread::config::uart_default_cfg(),
sys_loop.clone(),
nvs,
mounted_event_fs,
)?;

thread.init()?;

info!("Thread RCP initialized, now running...");

thread.run()?;

Ok(())
}

fn log_thread_sysloop(
sys_loop: EspSystemEventLoop,
) -> Result<EspSystemSubscription<'static>, anyhow::Error> {
let subscription = sys_loop.subscribe::<ThreadEvent, _>(|event| {
info!("Got: {:?}", event);
})?;

Ok(subscription)
}
}
4 changes: 2 additions & 2 deletions examples/wifi_static_ip.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ fn configure_wifi(wifi: WifiDriver) -> anyhow::Result<EspWifi> {
let mut wifi = EspWifi::wrap_all(
wifi,
EspNetif::new_with_conf(&NetifConfiguration {
ip_configuration: IpConfiguration::Client(IpClientConfiguration::Fixed(
ip_configuration: Some(IpConfiguration::Client(IpClientConfiguration::Fixed(
IpClientSettings {
ip: static_ip,
subnet: Subnet {
Expand All @@ -77,7 +77,7 @@ fn configure_wifi(wifi: WifiDriver) -> anyhow::Result<EspWifi> {
dns: None,
secondary_dns: None,
},
)),
))),
..NetifConfiguration::wifi_default_client()
})?,
#[cfg(esp_idf_esp_wifi_softap_support)]
Expand Down
Loading

0 comments on commit fdd4606

Please sign in to comment.