diff --git a/Cargo.toml b/Cargo.toml index 16f7b05..48110ef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,11 +33,13 @@ zeroconf = ["os", "rs-matter/zeroconf"] os = ["backtrace", "rs-matter/os", "embassy-time/std", "embassy-time/generic-queue"] backtrace = ["std", "rs-matter/backtrace"] async-io-mini = ["std", "edge-nal-std/async-io-mini"] -std = ["rs-matter/std", "edge-nal-std/async-io"] +std = ["alloc", "rs-matter/std", "edge-nal-std/async-io"] +alloc = ["embedded-svc/alloc"] [dependencies] log = { version = "0.4", default-features = false } heapless = "0.8" +enumset = { version = "1", default-features = false } scopeguard = { version = "1", default-features = false } embassy-futures = "0.1" embassy-sync = "0.5" diff --git a/src/modem.rs b/src/modem.rs index 5437edc..72ce128 100644 --- a/src/modem.rs +++ b/src/modem.rs @@ -1,10 +1,15 @@ use edge_nal::UdpBind; -use embedded_svc::wifi::asynch::Wifi; +use embedded_svc::wifi::{asynch::Wifi, AccessPointInfo, Capability, Configuration}; + +use enumset::EnumSet; use rs_matter::transport::network::btp::GattPeripheral; -use crate::netif::Netif; +use crate::netif::{DummyNetif, Netif, NetifConf}; + +#[cfg(feature = "alloc")] +extern crate alloc; /// A trait representing the radio of the device, which can operate either in BLE mode, or in Wifi mode. pub trait Modem { @@ -43,6 +48,7 @@ where } } +// A trait represeting the Wifi device, which can be split into an L2 and L3 portion. pub trait WifiDevice { type L2<'a>: Wifi where @@ -66,3 +72,167 @@ where T::split(*self).await } } + +/// A dummy modem implementation that can be used for testing purposes. +/// +/// The "dummy" aspects of this implementation are related to the Wifi network: +/// - The L2 (Wifi) network is simulated by a dummy network interface that does not actually connect to Wifi networks. +/// - The L3 (IP) network is simulated by a `DummyNetif` instance that uses a hard-coded `NetifConf` and assumes the +/// netif is always up +/// +/// The BLE network is not simulated and is expected to be user-provided, because - without a functioning BLE stack - +/// the device cannot even be commissioned. +/// +/// On Linux, the BlueR-based BLE gatt peripheral can be used. +pub struct DummyModem { + netif: DummyNetif, + gf: F, +} + +impl DummyModem { + /// Create a new `DummyModem` with the given configuration, UDP `bind` stack and BLE peripheral factory + pub const fn new(conf: Option, bind: U, gf: F) -> Self { + Self { + netif: DummyNetif::new(conf, bind), + gf, + } + } +} + +/// An instantiation of `DummyModem` for Linux specifically, +/// that uses the `edge-nal-std` stack and the BlueR GATT peripheral from `rs-matter`. +#[cfg(all(feature = "std", target_os = "linux"))] +pub type DummyLinuxModem = DummyModem< + edge_nal_std::Stack, + fn() -> rs_matter::transport::network::btp::BuiltinGattPeripheral, +>; + +#[cfg(all(feature = "std", target_os = "linux"))] +impl Default for DummyLinuxModem { + fn default() -> Self { + Self::new( + Some(NetifConf::default()), + edge_nal_std::Stack::new(), + || rs_matter::transport::network::btp::BuiltinGattPeripheral::new(Some("BT")), + ) + } +} + +impl Modem for DummyModem +where + U: UdpBind, + F: FnMut() -> G, + G: GattPeripheral, +{ + type BleDevice<'t> = G where Self: 't; + + type WifiDevice<'t> = DummyWifiDevice<'t, U> where Self: 't; + + async fn ble(&mut self) -> Self::BleDevice<'_> { + (self.gf)() + } + + async fn wifi(&mut self) -> Self::WifiDevice<'_> { + DummyWifiDevice(&self.netif) + } +} + +/// A dummy Wifi device. +/// +/// The "dummy" aspects of this implementation are related to the Wifi network: +/// - The L2 (Wifi) network is simulated by a dummy network interface that does not actually connect to Wifi networks. +/// - The L3 (IP) network is simulated by a `DummyNetif` instance that uses a hard-coded `NetifConf` and assumes the +/// netif is always up +pub struct DummyWifiDevice<'a, U>(&'a DummyNetif); + +impl<'a, U> WifiDevice for DummyWifiDevice<'a, U> +where + U: UdpBind, +{ + type L2<'t> = DummyL2 where Self: 't; + + type L3<'t> = &'t DummyNetif where Self: 't; + + async fn split(&mut self) -> (Self::L2<'_>, Self::L3<'_>) { + (DummyL2::new(), &self.0) + } +} + +/// A dummy L2 Wifi device that does not actually connect to Wifi networks +/// - yet - happily returns `Ok(())` to everything. +pub struct DummyL2 { + conf: Configuration, + started: bool, + connected: bool, +} + +impl DummyL2 { + const fn new() -> Self { + Self { + conf: Configuration::None, + started: false, + connected: false, + } + } +} + +impl Wifi for DummyL2 { + type Error = core::convert::Infallible; + + async fn get_capabilities(&self) -> Result, Self::Error> { + Ok(EnumSet::empty()) + } + + async fn get_configuration(&self) -> Result { + Ok(self.conf.clone()) + } + + async fn set_configuration(&mut self, conf: &Configuration) -> Result<(), Self::Error> { + self.conf = conf.clone(); + + Ok(()) + } + + async fn start(&mut self) -> Result<(), Self::Error> { + self.started = true; + + Ok(()) + } + + async fn stop(&mut self) -> Result<(), Self::Error> { + self.started = false; + + Ok(()) + } + + async fn connect(&mut self) -> Result<(), Self::Error> { + self.connected = true; + + Ok(()) + } + + async fn disconnect(&mut self) -> Result<(), Self::Error> { + self.connected = false; + + Ok(()) + } + + async fn is_started(&self) -> Result { + Ok(self.started) + } + + async fn is_connected(&self) -> Result { + Ok(self.connected) + } + + async fn scan_n( + &mut self, + ) -> Result<(heapless::Vec, usize), Self::Error> { + Ok((heapless::Vec::new(), 0)) + } + + #[cfg(feature = "alloc")] + async fn scan(&mut self) -> Result, Self::Error> { + Ok(alloc::vec::Vec::new()) + } +} diff --git a/src/netif.rs b/src/netif.rs index 628cdbf..9362053 100644 --- a/src/netif.rs +++ b/src/netif.rs @@ -1,6 +1,7 @@ use core::fmt; use core::net::{Ipv4Addr, Ipv6Addr}; +use edge_nal::UdpBind; use rs_matter::error::Error; /// Async trait for accessing the network interface (netif) of a driver. @@ -55,6 +56,23 @@ pub struct NetifConf { pub mac: [u8; 6], } +impl NetifConf { + pub const fn new() -> Self { + Self { + ipv4: Ipv4Addr::UNSPECIFIED, + ipv6: Ipv6Addr::UNSPECIFIED, + interface: 0, + mac: [0; 6], + } + } +} + +impl Default for NetifConf { + fn default() -> Self { + Self::new() + } +} + impl fmt::Display for NetifConf { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( @@ -80,44 +98,31 @@ impl fmt::Display for NetifConf { /// (by default `Ipv4Addr::UNSPECIFIED` / `Ipv6Addr::UNSPECIFIED` and 0). /// /// Useful for demoing purposes -pub struct DummyNetif { - ipv4: Ipv4Addr, - ipv6: Ipv6Addr, - interface: u32, - mac: [u8; 6], +pub struct DummyNetif { + conf: Option, + bind: U, } -impl DummyNetif { +impl DummyNetif { /// Create a new `DummyNetif` with the given IP configuration and MAC address - pub const fn new(ipv4: Ipv4Addr, ipv6: Ipv6Addr, interface: u32, mac: [u8; 6]) -> Self { - Self { - ipv4, - ipv6, - interface, - mac, - } + pub const fn new(conf: Option, bind: U) -> Self { + Self { conf, bind } } } -impl Default for DummyNetif { +#[cfg(feature = "std")] +impl Default for DummyNetif { fn default() -> Self { Self { - ipv4: Ipv4Addr::UNSPECIFIED, - ipv6: Ipv6Addr::UNSPECIFIED, - interface: 0, - mac: [1, 2, 3, 4, 5, 6], + conf: Some(NetifConf::default()), + bind: edge_nal_std::Stack::new(), } } } -impl Netif for DummyNetif { +impl Netif for DummyNetif { async fn get_conf(&self) -> Result, Error> { - Ok(Some(NetifConf { - ipv4: self.ipv4, - ipv6: self.ipv6, - interface: self.interface, - mac: self.mac, - })) + Ok(self.conf.clone()) } async fn wait_conf_change(&self) -> Result<(), Error> { @@ -126,13 +131,15 @@ impl Netif for DummyNetif { } } -#[cfg(feature = "std")] -impl edge_nal::UdpBind for DummyNetif { - type Error = std::io::Error; +impl edge_nal::UdpBind for DummyNetif +where + U: UdpBind, +{ + type Error = U::Error; - type Socket<'a> = edge_nal_std::UdpSocket; + type Socket<'a> = U::Socket<'a> where Self: 'a; async fn bind(&self, addr: core::net::SocketAddr) -> Result, Self::Error> { - edge_nal_std::Stack::new().bind(addr).await + self.bind.bind(addr).await } }