Skip to content

Commit

Permalink
dummy modem; allow DummyNetif to be used in no_std configs
Browse files Browse the repository at this point in the history
  • Loading branch information
ivmarkov committed May 28, 2024
1 parent fb93843 commit 176ac3d
Show file tree
Hide file tree
Showing 3 changed files with 212 additions and 33 deletions.
4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
174 changes: 172 additions & 2 deletions src/modem.rs
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -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
Expand All @@ -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<U, F> {
netif: DummyNetif<U>,
gf: F,
}

impl<U, F> DummyModem<U, F> {
/// Create a new `DummyModem` with the given configuration, UDP `bind` stack and BLE peripheral factory
pub const fn new(conf: Option<NetifConf>, 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<U, F, G> Modem for DummyModem<U, F>
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<U>);

impl<'a, U> WifiDevice for DummyWifiDevice<'a, U>
where
U: UdpBind,
{
type L2<'t> = DummyL2 where Self: 't;

type L3<'t> = &'t DummyNetif<U> 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<EnumSet<Capability>, Self::Error> {
Ok(EnumSet::empty())
}

async fn get_configuration(&self) -> Result<Configuration, Self::Error> {
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<bool, Self::Error> {
Ok(self.started)
}

async fn is_connected(&self) -> Result<bool, Self::Error> {
Ok(self.connected)
}

async fn scan_n<const N: usize>(
&mut self,
) -> Result<(heapless::Vec<AccessPointInfo, N>, usize), Self::Error> {
Ok((heapless::Vec::new(), 0))
}

#[cfg(feature = "alloc")]
async fn scan(&mut self) -> Result<alloc::vec::Vec<AccessPointInfo>, Self::Error> {
Ok(alloc::vec::Vec::new())
}
}
67 changes: 37 additions & 30 deletions src/netif.rs
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -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!(
Expand All @@ -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<U> {
conf: Option<NetifConf>,
bind: U,
}

impl DummyNetif {
impl<U> DummyNetif<U> {
/// 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<NetifConf>, bind: U) -> Self {
Self { conf, bind }
}
}

impl Default for DummyNetif {
#[cfg(feature = "std")]
impl Default for DummyNetif<edge_nal_std::Stack> {
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<U> Netif for DummyNetif<U> {
async fn get_conf(&self) -> Result<Option<NetifConf>, 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> {
Expand All @@ -126,13 +131,15 @@ impl Netif for DummyNetif {
}
}

#[cfg(feature = "std")]
impl edge_nal::UdpBind for DummyNetif {
type Error = std::io::Error;
impl<U> edge_nal::UdpBind for DummyNetif<U>
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::Socket<'_>, Self::Error> {
edge_nal_std::Stack::new().bind(addr).await
self.bind.bind(addr).await
}
}

0 comments on commit 176ac3d

Please sign in to comment.