Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for Thread #484

Merged
merged 60 commits into from
Oct 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
86c6ecc
Support for Thread
ivmarkov Sep 17, 2024
1ff57f2
Fix docu
ivmarkov Sep 17, 2024
dc01b6c
Support for ESP IDF 4
ivmarkov Sep 17, 2024
a0c4c03
Support for ESP IDF 4
ivmarkov Sep 17, 2024
abbb993
Small fix to the docu
ivmarkov Sep 17, 2024
7965189
Clippy
ivmarkov Sep 17, 2024
964fab8
Fix the ESP-IDF 4.4 build
ivmarkov Sep 17, 2024
c6b0570
Ipv4 conf is now optional
ivmarkov Sep 17, 2024
d907575
Leave out a set of TODOs as to what is pending
ivmarkov Sep 17, 2024
eef9d8c
Leave out a set of TODOs as to what is pending
ivmarkov Sep 17, 2024
4253fab
Leave out a set of TODOs as to what is pending
ivmarkov Sep 17, 2024
32bfa02
Correct part name for port config; eliminate copy-paste
ivmarkov Sep 17, 2024
ef054f0
Events
ivmarkov Sep 17, 2024
2bef075
Fix CI
ivmarkov Sep 17, 2024
b6d99ac
Fix wrong cfg
ivmarkov Sep 17, 2024
b16384b
fix events build for earlier ESP IDFs
ivmarkov Sep 18, 2024
4433e0e
TOD initialization
ivmarkov Sep 19, 2024
2de8284
Expose the additional APIs on EspThread too
ivmarkov Sep 19, 2024
8d7c9a3
Fix small typo in docs
ivmarkov Sep 19, 2024
91374b5
Re-use the UART config struct of the UART driver
ivmarkov Sep 19, 2024
d0c49cb
Re-use the SPI config struct of the SPI driver
ivmarkov Sep 19, 2024
15907e9
Clippy
ivmarkov Sep 19, 2024
ca6dd21
Options to scan for Thread networks
ivmarkov Sep 19, 2024
8f41544
Expose the scan APIs on EspThread as well
ivmarkov Sep 19, 2024
0ed0365
Small rename to match the other methods
ivmarkov Sep 19, 2024
9c24f3e
Clippy
ivmarkov Sep 19, 2024
3a78011
Border router
ivmarkov Sep 19, 2024
d86e676
Current device role
ivmarkov Sep 19, 2024
be5413f
Border router works OK since 5.2
ivmarkov Sep 19, 2024
40176f5
Use strong typing for roles in the events
ivmarkov Sep 19, 2024
a4eb6f3
Border router works OK since 5.2
ivmarkov Sep 19, 2024
03dd062
Border router works OK since 5.2
ivmarkov Sep 19, 2024
1f914a9
Border router works OK since... 5.3
ivmarkov Sep 19, 2024
7f64159
Border router works OK since... 5.3
ivmarkov Sep 19, 2024
f3e6532
Border router works OK since... 5.3
ivmarkov Sep 19, 2024
b7a85e5
Problem is the MCU, not the ESP IDF version
ivmarkov Sep 19, 2024
035acc3
Re-enable 4.4.7 to catch BR errors
ivmarkov Sep 19, 2024
b14d783
Fix wrong method name
ivmarkov Sep 19, 2024
8679ded
Initial examples
ivmarkov Sep 19, 2024
e3d4b30
Examples; Node and BorderRouter seem to work (do something)
ivmarkov Sep 19, 2024
bc23144
Restore default build conf
ivmarkov Sep 19, 2024
b7b4600
Restore default build conf
ivmarkov Sep 19, 2024
924df0e
Reduce the event_fd handles to 4 to match the other Thread example
ivmarkov Sep 20, 2024
89bdbcb
ESP-IDF 4.4 comnpat
ivmarkov Sep 20, 2024
b7e855e
The driver is actually Send and Sync
ivmarkov Sep 20, 2024
d1c9eb9
Protect code when netif is not enabled
ivmarkov Sep 20, 2024
99d22d8
Typo
ivmarkov Sep 20, 2024
6e9f93e
Raw IPv6 packets RX/TX
ivmarkov Sep 20, 2024
26335c4
Set log level; option to initialize OT CLI
ivmarkov Sep 21, 2024
6d7adc8
Wifi Coexist APIs
ivmarkov Sep 21, 2024
b26da68
Small enhancements to the examples
ivmarkov Sep 21, 2024
6645bd6
Refine the flags for coex modes; fix docu
ivmarkov Sep 21, 2024
c7706c3
Refine the flags for coex modes; fix docu
ivmarkov Sep 21, 2024
7a04d9a
RCP example; bugfixes related to UART
ivmarkov Sep 23, 2024
76b6095
fmt
ivmarkov Sep 23, 2024
5143075
Thread BR example with UART connection
ivmarkov Sep 23, 2024
6c9aa4f
Restore the BR example in commented out state
ivmarkov Sep 23, 2024
54b86e8
Restore the BR example in commented out state
ivmarkov Sep 23, 2024
b22aaa4
Remove resolved TODOs
ivmarkov Sep 23, 2024
30bfc73
fmt
ivmarkov Sep 23, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading