diff --git a/.gitignore b/.gitignore index a1c9e07..1d240a8 100644 --- a/.gitignore +++ b/.gitignore @@ -4,5 +4,4 @@ *.fifo target/ *.o -.vscode Cargo.lock \ No newline at end of file diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..78f5c6d --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,11 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations. + // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp + // List of extensions which should be recommended for users of this workspace. + "recommendations": [ + "rust-lang.rust-analyzer", + "tamasfe.even-better-toml", + ], + // List of extensions recommended by VS Code that should not be recommended for users of this workspace. + "unwantedRecommendations": [] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..67d222c --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,17 @@ +{ + "editor.formatOnSave": true, + "[toml]": { + "editor.formatOnSave": false + }, + "rust-analyzer.cargo.target": "thumbv6m-none-eabi", + "rust-analyzer.cargo.noDefaultFeatures": true, + "rust-analyzer.check.allTargets": false, + "rust-analyzer.check.noDefaultFeatures": true, + "rust-analyzer.linkedProjects": [ + "examples/rpi-pico/Cargo.toml", + ], + "rust-analyzer.server.extraEnv": { + "WIFI_NETWORK": "foo", + "WIFI_PASSWORD": "foo", + } +} \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index bf3e86f..4dbbaf2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,72 @@ +[package] +name = "ublox-short-range-rs" +version = "0.1.1" +authors = ["Mads Andresen "] +description = "Driver crate for u-blox short range devices, implementation follows 'UBX-14044127 - R40'" +readme = "../README.md" +keywords = ["ublox", "wifi", "shortrange", "bluetooth"] +categories = ["embedded", "no-std"] +license = "MIT OR Apache-2.0" +repository = "https://github.com/BlackbirdHQ/ublox-short-range-rs" +edition = "2021" + +[lib] +name = "ublox_short_range" +doctest = false + +[dependencies] +atat = { version = "0.18.0", features = ["derive", "defmt", "bytes"] } +# atat = { git = "https://github.com/BlackbirdHQ/atat", rev = "70283be", features = ["derive", "defmt", "bytes"] } +heapless = { version = "^0.7", features = ["serde", "defmt-impl"] } +no-std-net = { version = "0.6", features = ["serde"] } +serde = { version = "^1", default-features = false, features = ["derive"] } +ublox-sockets = { version = "0.5", features = ["defmt", "edm"], optional = true } +postcard = "1.0.4" +smoltcp = { version = "0.9.1", default-features = false, optional = true } +atomic-polyfill = "1.0.2" + +defmt = { version = "0.3" } +embedded-hal = "=1.0.0-alpha.10" +# embedded-nal = "0.6.0" +embassy-time = "0.1" +embassy-sync = "0.2" +embassy-futures = "0.1" +embassy-hal-common = "0.1" +embassy-net-driver = "0.1" + +embedded-nal-async = { version = "0.4", optional = true } +futures = { version = "0.3.17", default-features = false, features = [ "async-await" ] } + +embedded-io = "0.4" + +[features] +default = ["async", "odin_w2xx", "ublox-sockets", "socket-tcp"] + +async = ["dep:embedded-nal-async", "atat/async", "ublox-sockets?/async"] + +std = [] + +defmt = ["postcard/use-defmt"] + +odin_w2xx = [] +nina_w1xx = [] +nina_b1xx = [] +anna_b1xx = [] +nina_b2xx = [] +nina_b3xx = [] + +socket-tcp = ["ublox-sockets?/socket-tcp", "smoltcp?/socket-tcp"] +socket-udp = ["ublox-sockets?/socket-udp", "smoltcp?/socket-udp"] + [workspace] -resolver = "2" -members = [ "ublox-short-range" ] +members = [] +default-members = ["."] +exclude = ["examples"] [patch.crates-io] -atat = { git = "https://github.com/BlackbirdHQ/atat", rev = "70283be" } +atat = { path = "../atat/atat" } +ublox-sockets = { path = "../ublox-sockets" } +no-std-net = { path = "../no-std-net" } +embassy-net-driver = { path = "../embassy/embassy-net-driver" } +embassy-hal-common = { path = "../embassy/embassy-hal-common" } \ No newline at end of file diff --git a/examples/rpi-pico/.cargo/config.toml b/examples/rpi-pico/.cargo/config.toml new file mode 100644 index 0000000..3217579 --- /dev/null +++ b/examples/rpi-pico/.cargo/config.toml @@ -0,0 +1,9 @@ +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +# runner = "probe-rs-cli run --chip RP2040" +runner = "probe-run --chip RP2040" + +[build] +target = "thumbv6m-none-eabi" + +[env] +DEFMT_LOG = "debug,atat=warn" \ No newline at end of file diff --git a/examples/rpi-pico/Cargo.toml b/examples/rpi-pico/Cargo.toml new file mode 100644 index 0000000..78d4b1a --- /dev/null +++ b/examples/rpi-pico/Cargo.toml @@ -0,0 +1,77 @@ +[package] +name = "ublox-short-range-examples-rpi-pico" +version = "0.1.0" +edition = "2021" + + +[dependencies] +ublox-short-range-rs = { path = "../../", features = ["async", "defmt", "odin_w2xx", "ublox-sockets", "socket-tcp"] } +embassy-executor = { version = "0.1.0", features = ["defmt", "integrated-timers", "nightly"] } +embassy-time = { version = "0.1.0", features = ["defmt", "defmt-timestamp-uptime"] } +embassy-rp = { version = "0.1.0", features = ["defmt", "unstable-traits", "nightly", "unstable-pac", "time-driver"] } +embassy-futures = { version = "0.1.0" } +atomic-polyfill = "1.0.2" +static_cell = "1.1" +no-std-net = { version = "0.6", features = ["serde"] } + +defmt = "0.3.4" +defmt-rtt = "0.3" +panic-probe = { version = "0.3", features = ["print-defmt"] } + +cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } +cortex-m-rt = "0.7.0" +futures = { version = "0.3.17", default-features = false, features = ["async-await", "cfg-target-has-atomic", "unstable"] } + +embedded-io = { version = "0.4.0", features = ["async", "defmt"] } +heapless = "0.7.15" + + +[patch.crates-io] +# embassy-executor = { git = "https://github.com/embassy-rs/embassy", rev = "03d6363d5af5dcaf21b52734994a466ca593d2b6" } +# embassy-time = { git = "https://github.com/embassy-rs/embassy", rev = "03d6363d5af5dcaf21b52734994a466ca593d2b6" } +# embassy-futures = { git = "https://github.com/embassy-rs/embassy", rev = "03d6363d5af5dcaf21b52734994a466ca593d2b6" } +# embassy-sync = { git = "https://github.com/embassy-rs/embassy", rev = "03d6363d5af5dcaf21b52734994a466ca593d2b6" } +# embassy-rp = { git = "https://github.com/embassy-rs/embassy", rev = "03d6363d5af5dcaf21b52734994a466ca593d2b6" } +# embassy-net-driver-channel = { git = "https://github.com/embassy-rs/embassy", rev = "03d6363d5af5dcaf21b52734994a466ca593d2b6" } + + +embassy-executor = { path = "../../../embassy/embassy-executor" } +embassy-hal-common = { path = "../../../embassy/embassy-hal-common" } +embassy-time = { path = "../../../embassy/embassy-time" } +embassy-futures = { path = "../../../embassy/embassy-futures" } +embassy-sync = { path = "../../../embassy/embassy-sync" } +embassy-rp = { path = "../../../embassy/embassy-rp" } +embassy-net-driver = { path = "../../../embassy/embassy-net-driver" } +atat = { path = "../../../atat/atat" } +ublox-sockets = { path = "../../../ublox-sockets" } +no-std-net = { path = "../../../no-std-net" } + +[profile.dev] +debug = 2 +debug-assertions = true +opt-level = 1 +overflow-checks = true + +[profile.release] +codegen-units = 1 +debug = 2 +debug-assertions = false +incremental = false +lto = 'fat' +opt-level = 'z' +overflow-checks = false + +# do not optimize proc-macro crates = faster builds from scratch +[profile.dev.build-override] +codegen-units = 8 +debug = false +debug-assertions = false +opt-level = 0 +overflow-checks = false + +[profile.release.build-override] +codegen-units = 8 +debug = false +debug-assertions = false +opt-level = 0 +overflow-checks = false diff --git a/examples/rpi-pico/build.rs b/examples/rpi-pico/build.rs new file mode 100644 index 0000000..3f915f9 --- /dev/null +++ b/examples/rpi-pico/build.rs @@ -0,0 +1,36 @@ +//! This build script copies the `memory.x` file from the crate root into +//! a directory where the linker can always find it at build time. +//! For many projects this is optional, as the linker always searches the +//! project root directory -- wherever `Cargo.toml` is. However, if you +//! are using a workspace or have a more complicated build setup, this +//! build script becomes required. Additionally, by requesting that +//! Cargo re-run the build script whenever `memory.x` is changed, +//! updating `memory.x` ensures a rebuild of the application with the +//! new memory settings. + +use std::env; +use std::fs::File; +use std::io::Write; +use std::path::PathBuf; + +fn main() { + // Put `memory.x` in our output directory and ensure it's + // on the linker search path. + let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); + File::create(out.join("memory.x")) + .unwrap() + .write_all(include_bytes!("memory.x")) + .unwrap(); + println!("cargo:rustc-link-search={}", out.display()); + + // By default, Cargo will re-run a build script whenever + // any file in the project changes. By specifying `memory.x` + // here, we ensure the build script is only re-run when + // `memory.x` is changed. + println!("cargo:rerun-if-changed=memory.x"); + + println!("cargo:rustc-link-arg-bins=--nmagic"); + println!("cargo:rustc-link-arg-bins=-Tlink.x"); + println!("cargo:rustc-link-arg-bins=-Tlink-rp.x"); + println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); +} diff --git a/examples/rpi-pico/memory.x b/examples/rpi-pico/memory.x new file mode 100644 index 0000000..eb8c173 --- /dev/null +++ b/examples/rpi-pico/memory.x @@ -0,0 +1,5 @@ +MEMORY { + BOOT2 : ORIGIN = 0x10000000, LENGTH = 0x100 + FLASH : ORIGIN = 0x10000100, LENGTH = 1024K - 0x100 + RAM : ORIGIN = 0x20000000, LENGTH = 256K +} \ No newline at end of file diff --git a/examples/rpi-pico/src/bin/embassy-async.rs b/examples/rpi-pico/src/bin/embassy-async.rs new file mode 100644 index 0000000..30140a5 --- /dev/null +++ b/examples/rpi-pico/src/bin/embassy-async.rs @@ -0,0 +1,311 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] +#![feature(async_fn_in_trait)] +#![allow(incomplete_features)] + +use core::fmt::Write as _; +use embassy_executor::Spawner; +use embassy_futures::select::{select, Either}; +use embassy_rp::gpio::{Input, Level, Output, Pull}; +use embassy_rp::peripherals::{PIN_26, UART1}; +use embassy_rp::uart::BufferedInterruptHandler; +use embassy_rp::{bind_interrupts, uart}; +use embassy_time::{Duration, Timer}; +use embedded_io::asynch::Write; +use no_std_net::{Ipv4Addr, SocketAddr}; +use static_cell::StaticCell; +use ublox_short_range::asynch::runner::Runner; +use ublox_short_range::asynch::ublox_stack::dns::DnsSocket; +use ublox_short_range::asynch::ublox_stack::tcp::TcpSocket; +use ublox_short_range::asynch::ublox_stack::{StackResources, UbloxStack}; +use ublox_short_range::asynch::{new, State}; +use ublox_short_range::atat::{self, AtatIngress, AtatUrcChannel}; +use ublox_short_range::command::custom_digest::EdmDigester; +use ublox_short_range::command::edm::urc::EdmEvent; +use ublox_short_range::embedded_nal_async::AddrType; +use {defmt_rtt as _, panic_probe as _}; + +const RX_BUF_LEN: usize = 1024; +const URC_CAPACITY: usize = 3; + +macro_rules! singleton { + ($val:expr) => {{ + type T = impl Sized; + static STATIC_CELL: StaticCell = StaticCell::new(); + let (x,) = STATIC_CELL.init(($val,)); + x + }}; +} + +#[embassy_executor::task] +async fn wifi_task( + runner: Runner< + 'static, + ublox_short_range::atat::asynch::Client< + 'static, + uart::BufferedUartTx<'static, UART1>, + RX_BUF_LEN, + >, + Output<'static, PIN_26>, + 8, + >, +) -> ! { + runner.run().await +} + +#[embassy_executor::task] +async fn net_task(stack: &'static UbloxStack) -> ! { + stack.run().await +} + +#[embassy_executor::task(pool_size = 2)] +async fn echo_task( + stack: &'static UbloxStack, + hostname: &'static str, + port: u16, + write_interval: Duration, +) { + let mut rx_buffer = [0; 128]; + let mut tx_buffer = [0; 128]; + let mut buf = [0; 128]; + let mut cnt = 0u32; + let mut msg = heapless::String::<64>::new(); + Timer::after(Duration::from_secs(1)).await; + + let ip_addr = match DnsSocket::new(stack).query(hostname, AddrType::IPv4).await { + Ok(ip) => ip, + Err(_) => { + defmt::error!("[{}] Failed to resolve IP addr", hostname); + return; + } + }; + + let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); + + defmt::info!( + "[{}] Connecting... {}", + hostname, + defmt::Debug2Format(&ip_addr) + ); + if let Err(e) = socket.connect((ip_addr, port)).await { + defmt::warn!("[{}] connect error: {:?}", hostname, e); + return; + } + defmt::info!( + "[{}] Connected to {:?}", + hostname, + defmt::Debug2Format(&socket.remote_endpoint()) + ); + + loop { + match select(Timer::after(write_interval), socket.read(&mut buf)).await { + Either::First(_) => { + msg.clear(); + write!(msg, "Hello {}! {}\n", ip_addr, cnt).unwrap(); + cnt = cnt.wrapping_add(1); + if let Err(e) = socket.write_all(msg.as_bytes()).await { + defmt::warn!("[{}] write error: {:?}", hostname, e); + break; + } + defmt::info!("[{}] txd: {}", hostname, msg); + Timer::after(Duration::from_millis(400)).await; + } + Either::Second(res) => { + let n = match res { + Ok(0) => { + defmt::warn!("[{}] read EOF", hostname); + break; + } + Ok(n) => n, + Err(e) => { + defmt::warn!("[{}] {:?}", hostname, e); + break; + } + }; + defmt::info!( + "[{}] rxd {}", + hostname, + core::str::from_utf8(&buf[..n]).unwrap() + ); + } + } + } +} + +#[embassy_executor::task] +async fn ingress_task( + mut ingress: atat::Ingress<'static, EdmDigester, EdmEvent, RX_BUF_LEN, URC_CAPACITY, 1>, + mut rx: uart::BufferedUartRx<'static, UART1>, +) -> ! { + ingress.read_from(&mut rx).await +} + +bind_interrupts!(struct Irqs { + UART1_IRQ => BufferedInterruptHandler; +}); + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + defmt::info!("Hello World!"); + + let p = embassy_rp::init(Default::default()); + + let rst = Output::new(p.PIN_26, Level::High); + + let (tx_pin, rx_pin, rts_pin, cts_pin, uart) = + (p.PIN_24, p.PIN_25, p.PIN_23, p.PIN_22, p.UART1); + let mut btn = Input::new(p.PIN_27, Pull::Up); + + let tx_buf = &mut singleton!([0u8; 64])[..]; + let rx_buf = &mut singleton!([0u8; 64])[..]; + let uart = uart::BufferedUart::new_with_rtscts( + uart, + Irqs, + tx_pin, + rx_pin, + rts_pin, + cts_pin, + tx_buf, + rx_buf, + uart::Config::default(), + ); + let (rx, tx) = uart.split(); + + let buffers = &*singleton!(atat::Buffers::new()); + let urc_channel = buffers.urc_channel.subscribe().unwrap(); + let (ingress, client) = buffers.split(tx, EdmDigester::default(), atat::Config::new()); + + defmt::unwrap!(spawner.spawn(ingress_task(ingress, rx))); + + let state = singleton!(State::new(client)); + let (net_device, mut control, runner) = new(state, urc_channel, rst).await; + + defmt::unwrap!(spawner.spawn(wifi_task(runner))); + + control + .set_hostname("Factbird-duo-wifi-test") + .await + .unwrap(); + + // Init network stack + let stack = &*singleton!(UbloxStack::new( + net_device, + singleton!(StackResources::<4>::new()), + )); + + defmt::unwrap!(spawner.spawn(net_task(stack))); + + // And now we can use it! + defmt::info!("Device initialized!"); + + // spawner + // .spawn(echo_task( + // &stack, + // "tcpbin.com", + // 4242, + // Duration::from_millis(500), + // )) + // .unwrap(); + + let mut rx_buffer = [0; 256]; + let mut tx_buffer = [0; 256]; + let mut buf = [0; 256]; + let mut cnt = 0u32; + let mut msg = heapless::String::<64>::new(); + + loop { + loop { + match control.join_wpa2("test", "1234abcd").await { + Ok(_) => { + defmt::info!("Network connected!"); + spawner + .spawn(echo_task( + &stack, + "echo.u-blox.com", + 7, + Duration::from_secs(1), + )) + .unwrap(); + break; + } + Err(err) => { + defmt::info!("join failed with error={:?}. Retrying in 1 second", err); + Timer::after(Duration::from_secs(1)).await; + } + } + } + 'outer: loop { + Timer::after(Duration::from_secs(1)).await; + + let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); + // // socket.set_timeout(Some(Duration::from_secs(10))); + + let remote: SocketAddr = (Ipv4Addr::new(192, 168, 73, 183), 4444).into(); + defmt::info!("Connecting... {}", defmt::Debug2Format(&remote)); + if let Err(e) = socket.connect(remote).await { + defmt::warn!("connect error: {:?}", e); + continue; + } + defmt::info!( + "Connected to {:?}", + defmt::Debug2Format(&socket.remote_endpoint()) + ); + + 'inner: loop { + match select(Timer::after(Duration::from_secs(3)), socket.read(&mut buf)).await { + Either::First(_) => { + msg.clear(); + write!(msg, "Hello world! {}\n", cnt).unwrap(); + cnt = cnt.wrapping_add(1); + if let Err(e) = socket.write_all(msg.as_bytes()).await { + defmt::warn!("write error: {:?}", e); + break; + } + defmt::info!("txd: {}", msg); + Timer::after(Duration::from_millis(400)).await; + } + Either::Second(res) => { + let n = match res { + Ok(0) => { + defmt::warn!("read EOF"); + break; + } + Ok(n) => n, + Err(e) => { + defmt::warn!("{:?}", e); + break; + } + }; + defmt::info!("rxd [{}] {}", n, core::str::from_utf8(&buf[..n]).unwrap()); + + match &buf[..n] { + b"c\n" => { + socket.close(); + break 'inner; + } + b"a\n" => { + socket.abort(); + break 'inner; + } + b"d\n" => { + drop(socket); + break 'inner; + } + b"f\n" => { + control.disconnect().await.unwrap(); + break 'outer; + } + _ => {} + } + } + } + } + defmt::info!("Press USER button to reconnect socket!"); + btn.wait_for_any_edge().await; + continue; + } + defmt::info!("Press USER button to reconnect to WiFi!"); + btn.wait_for_any_edge().await; + } +} diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..4bf55ae --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,7 @@ +[toolchain] +channel = "nightly-2023-02-07" +components = [ "rust-src", "rustfmt", "llvm-tools-preview", "clippy" ] +targets = [ + "thumbv6m-none-eabi", + "thumbv7em-none-eabihf" +] diff --git a/src/asynch/channel.rs b/src/asynch/channel.rs new file mode 100644 index 0000000..2c582a1 --- /dev/null +++ b/src/asynch/channel.rs @@ -0,0 +1,579 @@ +#![allow(dead_code)] + +use core::cell::RefCell; +use core::mem::MaybeUninit; +use core::task::{Context, Poll}; + +use defmt::unwrap; +pub use embassy_net_driver as driver; +use embassy_net_driver::{Capabilities, LinkState, Medium}; +use embassy_sync::blocking_mutex::raw::NoopRawMutex; +use embassy_sync::blocking_mutex::Mutex; +use embassy_sync::waitqueue::WakerRegistration; + +pub struct State { + rx: [PacketBuf; N_RX], + tx: [PacketBuf; N_TX], + inner: MaybeUninit>, +} + +impl State { + const NEW_PACKET: PacketBuf = PacketBuf::new(); + + pub const fn new() -> Self { + Self { + rx: [Self::NEW_PACKET; N_RX], + tx: [Self::NEW_PACKET; N_TX], + inner: MaybeUninit::uninit(), + } + } +} + +struct StateInner<'d, const MTU: usize> { + rx: zerocopy_channel::Channel<'d, NoopRawMutex, PacketBuf>, + tx: zerocopy_channel::Channel<'d, NoopRawMutex, PacketBuf>, + shared: Mutex>, +} + +/// State of the LinkState +struct Shared { + link_state: LinkState, + waker: WakerRegistration, + ethernet_address: [u8; 6], +} + +pub struct Runner<'d, const MTU: usize> { + tx_chan: zerocopy_channel::Receiver<'d, NoopRawMutex, PacketBuf>, + rx_chan: zerocopy_channel::Sender<'d, NoopRawMutex, PacketBuf>, + shared: &'d Mutex>, +} + +#[derive(Clone, Copy)] +pub struct StateRunner<'d> { + shared: &'d Mutex>, +} + +pub struct RxRunner<'d, const MTU: usize> { + rx_chan: zerocopy_channel::Sender<'d, NoopRawMutex, PacketBuf>, +} + +pub struct TxRunner<'d, const MTU: usize> { + tx_chan: zerocopy_channel::Receiver<'d, NoopRawMutex, PacketBuf>, +} + +impl<'d, const MTU: usize> Runner<'d, MTU> { + pub fn split(self) -> (StateRunner<'d>, RxRunner<'d, MTU>, TxRunner<'d, MTU>) { + ( + StateRunner { + shared: self.shared, + }, + RxRunner { + rx_chan: self.rx_chan, + }, + TxRunner { + tx_chan: self.tx_chan, + }, + ) + } + + pub fn state_runner(&self) -> StateRunner<'d> { + StateRunner { + shared: self.shared, + } + } + + pub fn set_link_state(&mut self, state: LinkState) { + self.shared.lock(|s| { + let s = &mut *s.borrow_mut(); + s.link_state = state; + s.waker.wake(); + }); + } + + pub fn set_ethernet_address(&mut self, address: [u8; 6]) { + self.shared.lock(|s| { + let s = &mut *s.borrow_mut(); + s.ethernet_address = address; + s.waker.wake(); + }); + } + + pub async fn rx_buf(&mut self) -> &mut [u8] { + let p = self.rx_chan.send().await; + &mut p.buf + } + + pub fn try_rx_buf(&mut self) -> Option<&mut [u8]> { + let p = self.rx_chan.try_send()?; + Some(&mut p.buf) + } + + pub fn poll_rx_buf(&mut self, cx: &mut Context) -> Poll<&mut [u8]> { + match self.rx_chan.poll_send(cx) { + Poll::Ready(p) => Poll::Ready(&mut p.buf), + Poll::Pending => Poll::Pending, + } + } + + pub fn rx_done(&mut self, len: usize) { + let p = self.rx_chan.try_send().unwrap(); + p.len = len; + self.rx_chan.send_done(); + } + + pub async fn tx_buf(&mut self) -> &mut [u8] { + let p = self.tx_chan.recv().await; + &mut p.buf[..p.len] + } + + pub fn try_tx_buf(&mut self) -> Option<&mut [u8]> { + let p = self.tx_chan.try_recv()?; + Some(&mut p.buf[..p.len]) + } + + pub fn poll_tx_buf(&mut self, cx: &mut Context) -> Poll<&mut [u8]> { + match self.tx_chan.poll_recv(cx) { + Poll::Ready(p) => Poll::Ready(&mut p.buf[..p.len]), + Poll::Pending => Poll::Pending, + } + } + + pub fn tx_done(&mut self) { + self.tx_chan.recv_done(); + } +} + +impl<'d> StateRunner<'d> { + pub fn set_link_state(&self, state: LinkState) { + self.shared.lock(|s| { + let s = &mut *s.borrow_mut(); + s.link_state = state; + s.waker.wake(); + }); + } + + pub fn set_ethernet_address(&self, address: [u8; 6]) { + self.shared.lock(|s| { + let s = &mut *s.borrow_mut(); + s.ethernet_address = address; + s.waker.wake(); + }); + } + + pub fn link_state(&mut self, cx: &mut Context) -> LinkState { + self.shared.lock(|s| { + let s = &mut *s.borrow_mut(); + s.waker.register(cx.waker()); + s.link_state + }) + } +} + +impl<'d, const MTU: usize> RxRunner<'d, MTU> { + pub async fn rx_buf(&mut self) -> &mut [u8] { + let p = self.rx_chan.send().await; + &mut p.buf + } + + pub fn try_rx_buf(&mut self) -> Option<&mut [u8]> { + let p = self.rx_chan.try_send()?; + Some(&mut p.buf) + } + + pub fn poll_rx_buf(&mut self, cx: &mut Context) -> Poll<&mut [u8]> { + match self.rx_chan.poll_send(cx) { + Poll::Ready(p) => Poll::Ready(&mut p.buf), + Poll::Pending => Poll::Pending, + } + } + + pub fn rx_done(&mut self, len: usize) { + let p = self.rx_chan.try_send().unwrap(); + p.len = len; + self.rx_chan.send_done(); + } +} + +impl<'d, const MTU: usize> TxRunner<'d, MTU> { + pub async fn tx_buf(&mut self) -> &mut [u8] { + let p = self.tx_chan.recv().await; + &mut p.buf[..p.len] + } + + pub fn try_tx_buf(&mut self) -> Option<&mut [u8]> { + let p = self.tx_chan.try_recv()?; + Some(&mut p.buf[..p.len]) + } + + pub fn poll_tx_buf(&mut self, cx: &mut Context) -> Poll<&mut [u8]> { + match self.tx_chan.poll_recv(cx) { + Poll::Ready(p) => Poll::Ready(&mut p.buf[..p.len]), + Poll::Pending => Poll::Pending, + } + } + + pub fn tx_done(&mut self) { + self.tx_chan.recv_done(); + } +} + +pub fn new<'d, const MTU: usize, const N_RX: usize, const N_TX: usize>( + state: &'d mut State, + ethernet_address: [u8; 6], +) -> (Runner<'d, MTU>, Device<'d, MTU>) { + let mut caps = Capabilities::default(); + caps.max_transmission_unit = MTU; + caps.medium = Medium::Ethernet; + + // safety: this is a self-referential struct, however: + // - it can't move while the `'d` borrow is active. + // - when the borrow ends, the dangling references inside the MaybeUninit will never be used again. + let state_uninit: *mut MaybeUninit> = + (&mut state.inner as *mut MaybeUninit>).cast(); + let state = unsafe { &mut *state_uninit }.write(StateInner { + rx: zerocopy_channel::Channel::new(&mut state.rx[..]), + tx: zerocopy_channel::Channel::new(&mut state.tx[..]), + shared: Mutex::new(RefCell::new(Shared { + link_state: LinkState::Down, + ethernet_address, + waker: WakerRegistration::new(), + })), + }); + + let (rx_sender, rx_receiver) = state.rx.split(); + let (tx_sender, tx_receiver) = state.tx.split(); + + ( + Runner { + tx_chan: tx_receiver, + rx_chan: rx_sender, + shared: &state.shared, + }, + Device { + caps, + shared: &state.shared, + rx: rx_receiver, + tx: tx_sender, + }, + ) +} + +pub struct PacketBuf { + len: usize, + buf: [u8; MTU], +} + +impl PacketBuf { + pub const fn new() -> Self { + Self { + len: 0, + buf: [0; MTU], + } + } +} + +pub struct Device<'d, const MTU: usize> { + rx: zerocopy_channel::Receiver<'d, NoopRawMutex, PacketBuf>, + tx: zerocopy_channel::Sender<'d, NoopRawMutex, PacketBuf>, + shared: &'d Mutex>, + caps: Capabilities, +} + +impl<'d, const MTU: usize> embassy_net_driver::Driver for Device<'d, MTU> { + type RxToken<'a> = RxToken<'a, MTU> where Self: 'a ; + type TxToken<'a> = TxToken<'a, MTU> where Self: 'a ; + + fn receive(&mut self, cx: &mut Context) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> { + if self.rx.poll_recv(cx).is_ready() && self.tx.poll_send(cx).is_ready() { + Some(( + RxToken { + rx: self.rx.borrow(), + }, + TxToken { + tx: self.tx.borrow(), + }, + )) + } else { + None + } + } + + /// Construct a transmit token. + fn transmit(&mut self, cx: &mut Context) -> Option> { + if self.tx.poll_send(cx).is_ready() { + Some(TxToken { + tx: self.tx.borrow(), + }) + } else { + None + } + } + + /// Get a description of device capabilities. + fn capabilities(&self) -> Capabilities { + self.caps.clone() + } + + fn ethernet_address(&self) -> [u8; 6] { + self.shared.lock(|s| s.borrow().ethernet_address) + } + + fn link_state(&mut self, cx: &mut Context) -> LinkState { + self.shared.lock(|s| { + let s = &mut *s.borrow_mut(); + s.waker.register(cx.waker()); + s.link_state + }) + } +} + +pub struct RxToken<'a, const MTU: usize> { + rx: zerocopy_channel::Receiver<'a, NoopRawMutex, PacketBuf>, +} + +impl<'a, const MTU: usize> embassy_net_driver::RxToken for RxToken<'a, MTU> { + fn consume(mut self, f: F) -> R + where + F: FnOnce(&mut [u8]) -> R, + { + // NOTE(unwrap): we checked the queue wasn't full when creating the token. + let pkt = unwrap!(self.rx.try_recv()); + let r = f(&mut pkt.buf[..pkt.len]); + self.rx.recv_done(); + r + } +} + +pub struct TxToken<'a, const MTU: usize> { + tx: zerocopy_channel::Sender<'a, NoopRawMutex, PacketBuf>, +} + +impl<'a, const MTU: usize> embassy_net_driver::TxToken for TxToken<'a, MTU> { + fn consume(mut self, len: usize, f: F) -> R + where + F: FnOnce(&mut [u8]) -> R, + { + // NOTE(unwrap): we checked the queue wasn't full when creating the token. + let pkt = unwrap!(self.tx.try_send()); + let r = f(&mut pkt.buf[..len]); + pkt.len = len; + self.tx.send_done(); + r + } +} + +mod zerocopy_channel { + use core::cell::RefCell; + use core::future::poll_fn; + use core::marker::PhantomData; + use core::task::{Context, Poll}; + + use embassy_sync::blocking_mutex::raw::RawMutex; + use embassy_sync::blocking_mutex::Mutex; + use embassy_sync::waitqueue::WakerRegistration; + + pub struct Channel<'a, M: RawMutex, T> { + buf: *mut T, + phantom: PhantomData<&'a mut T>, + state: Mutex>, + } + + impl<'a, M: RawMutex, T> Channel<'a, M, T> { + pub fn new(buf: &'a mut [T]) -> Self { + let len = buf.len(); + assert!(len != 0); + + Self { + buf: buf.as_mut_ptr(), + phantom: PhantomData, + state: Mutex::new(RefCell::new(State { + len, + front: 0, + back: 0, + full: false, + send_waker: WakerRegistration::new(), + recv_waker: WakerRegistration::new(), + })), + } + } + + pub fn split(&mut self) -> (Sender<'_, M, T>, Receiver<'_, M, T>) { + (Sender { channel: self }, Receiver { channel: self }) + } + } + + pub struct Sender<'a, M: RawMutex, T> { + channel: &'a Channel<'a, M, T>, + } + + impl<'a, M: RawMutex, T> Sender<'a, M, T> { + pub fn borrow(&mut self) -> Sender<'_, M, T> { + Sender { + channel: self.channel, + } + } + + pub fn try_send(&mut self) -> Option<&mut T> { + self.channel.state.lock(|s| { + let s = &mut *s.borrow_mut(); + match s.push_index() { + Some(i) => Some(unsafe { &mut *self.channel.buf.add(i) }), + None => None, + } + }) + } + + pub fn poll_send(&mut self, cx: &mut Context) -> Poll<&mut T> { + self.channel.state.lock(|s| { + let s = &mut *s.borrow_mut(); + match s.push_index() { + Some(i) => Poll::Ready(unsafe { &mut *self.channel.buf.add(i) }), + None => { + s.recv_waker.register(cx.waker()); + Poll::Pending + } + } + }) + } + + pub async fn send(&mut self) -> &mut T { + let i = poll_fn(|cx| { + self.channel.state.lock(|s| { + let s = &mut *s.borrow_mut(); + match s.push_index() { + Some(i) => Poll::Ready(i), + None => { + s.recv_waker.register(cx.waker()); + Poll::Pending + } + } + }) + }) + .await; + unsafe { &mut *self.channel.buf.add(i) } + } + + pub fn send_done(&mut self) { + self.channel.state.lock(|s| s.borrow_mut().push_done()) + } + } + pub struct Receiver<'a, M: RawMutex, T> { + channel: &'a Channel<'a, M, T>, + } + + impl<'a, M: RawMutex, T> Receiver<'a, M, T> { + pub fn borrow(&mut self) -> Receiver<'_, M, T> { + Receiver { + channel: self.channel, + } + } + + pub fn try_recv(&mut self) -> Option<&mut T> { + self.channel.state.lock(|s| { + let s = &mut *s.borrow_mut(); + match s.pop_index() { + Some(i) => Some(unsafe { &mut *self.channel.buf.add(i) }), + None => None, + } + }) + } + + pub fn poll_recv(&mut self, cx: &mut Context) -> Poll<&mut T> { + self.channel.state.lock(|s| { + let s = &mut *s.borrow_mut(); + match s.pop_index() { + Some(i) => Poll::Ready(unsafe { &mut *self.channel.buf.add(i) }), + None => { + s.send_waker.register(cx.waker()); + Poll::Pending + } + } + }) + } + + pub async fn recv(&mut self) -> &mut T { + let i = poll_fn(|cx| { + self.channel.state.lock(|s| { + let s = &mut *s.borrow_mut(); + match s.pop_index() { + Some(i) => Poll::Ready(i), + None => { + s.send_waker.register(cx.waker()); + Poll::Pending + } + } + }) + }) + .await; + unsafe { &mut *self.channel.buf.add(i) } + } + + pub fn recv_done(&mut self) { + self.channel.state.lock(|s| s.borrow_mut().pop_done()) + } + } + + struct State { + len: usize, + + /// Front index. Always 0..=(N-1) + front: usize, + /// Back index. Always 0..=(N-1). + back: usize, + + /// Used to distinguish "empty" and "full" cases when `front == back`. + /// May only be `true` if `front == back`, always `false` otherwise. + full: bool, + + send_waker: WakerRegistration, + recv_waker: WakerRegistration, + } + + impl State { + fn increment(&self, i: usize) -> usize { + if i + 1 == self.len { + 0 + } else { + i + 1 + } + } + + fn is_full(&self) -> bool { + self.full + } + + fn is_empty(&self) -> bool { + self.front == self.back && !self.full + } + + fn push_index(&mut self) -> Option { + match self.is_full() { + true => None, + false => Some(self.back), + } + } + + fn push_done(&mut self) { + assert!(!self.is_full()); + self.back = self.increment(self.back); + if self.back == self.front { + self.full = true; + } + self.send_waker.wake(); + } + + fn pop_index(&mut self) -> Option { + match self.is_empty() { + true => None, + false => Some(self.front), + } + } + + fn pop_done(&mut self) { + assert!(!self.is_empty()); + self.front = self.increment(self.front); + self.full = false; + self.recv_waker.wake(); + } + } +} diff --git a/src/asynch/control.rs b/src/asynch/control.rs new file mode 100644 index 0000000..20f7b8c --- /dev/null +++ b/src/asynch/control.rs @@ -0,0 +1,272 @@ +use core::future::poll_fn; +use core::task::Poll; + +use atat::asynch::AtatClient; +use embassy_net_driver::LinkState; +use embassy_time::{with_timeout, Duration}; + +use crate::command::network::SetNetworkHostName; +use crate::command::wifi::types::{ + Authentication, StatusId, WifiStationAction, WifiStationConfig, WifiStatus, WifiStatusVal, +}; +use crate::command::wifi::{ + ExecWifiStationAction, GetWifiMac, GetWifiStatus, SetWifiStationConfig, +}; +use crate::command::OnOff; +use crate::error::Error; +use crate::{ + command::gpio::{ + types::{GPIOId, GPIOValue}, + WriteGPIO, + }, + hex, +}; + +use super::{channel, AtHandle}; + +const CONFIG_ID: u8 = 0; + +pub struct Control<'a, AT: AtatClient> { + state_ch: channel::StateRunner<'a>, + at: AtHandle<'a, AT>, +} + +impl<'a, AT: AtatClient> Control<'a, AT> { + pub(crate) fn new(state_ch: channel::StateRunner<'a>, at: AtHandle<'a, AT>) -> Self { + Self { state_ch, at } + } + + pub(crate) async fn init(&mut self) -> Result<(), Error> { + defmt::debug!("Initalizing ublox control"); + // read MAC addr. + let mut resp = self.at.send_edm(GetWifiMac).await?; + self.state_ch.set_ethernet_address( + hex::from_hex(resp.mac_addr.as_mut_slice()) + .unwrap() + .try_into() + .unwrap(), + ); + + // let country = countries::WORLD_WIDE_XX; + // let country_info = CountryInfo { + // country_abbrev: [country.code[0], country.code[1], 0, 0], + // country_code: [country.code[0], country.code[1], 0, 0], + // rev: if country.rev == 0 { + // -1 + // } else { + // country.rev as _ + // }, + // }; + // self.set_iovar("country", &country_info.to_bytes()).await; + + // // set country takes some time, next ioctls fail if we don't wait. + // Timer::after(Duration::from_millis(100)).await; + + // // Set antenna to chip antenna + // self.ioctl_set_u32(IOCTL_CMD_ANTDIV, 0, 0).await; + + // self.set_iovar_u32("bus:txglom", 0).await; + // Timer::after(Duration::from_millis(100)).await; + // //self.set_iovar_u32("apsta", 1).await; // this crashes, also we already did it before...?? + // //Timer::after(Duration::from_millis(100)).await; + // self.set_iovar_u32("ampdu_ba_wsize", 8).await; + // Timer::after(Duration::from_millis(100)).await; + // self.set_iovar_u32("ampdu_mpdu", 4).await; + // Timer::after(Duration::from_millis(100)).await; + // //self.set_iovar_u32("ampdu_rx_factor", 0).await; // this crashes + + // // set wifi up + // self.ioctl(ControlType::Set, IOCTL_CMD_UP, 0, &mut []).await; + + // Timer::after(Duration::from_millis(100)).await; + + // self.ioctl_set_u32(110, 0, 1).await; // SET_GMODE = auto + // self.ioctl_set_u32(142, 0, 0).await; // SET_BAND = any + + Ok(()) + } + + pub async fn set_hostname(&mut self, hostname: &str) -> Result<(), Error> { + self.at + .send_edm(SetNetworkHostName { + host_name: hostname, + }) + .await?; + Ok(()) + } + + async fn get_wifi_status(&mut self) -> Result { + match self + .at + .send_edm(GetWifiStatus { + status_id: StatusId::Status, + }) + .await? + .status_id + { + WifiStatus::Status(s) => Ok(s), + _ => Err(Error::AT(atat::Error::InvalidResponse)), + } + } + + async fn get_connected_ssid(&mut self) -> Result, Error> { + match self + .at + .send_edm(GetWifiStatus { + status_id: StatusId::SSID, + }) + .await? + .status_id + { + WifiStatus::SSID(s) => Ok(s), + _ => Err(Error::AT(atat::Error::InvalidResponse)), + } + } + + pub async fn join_open(&mut self, ssid: &str) -> Result<(), Error> { + if matches!(self.get_wifi_status().await?, WifiStatusVal::Connected) { + // Wifi already connected. Check if the SSID is the same + let current_ssid = self.get_connected_ssid().await?; + if current_ssid.as_str() == ssid { + return Ok(()); + } else { + self.disconnect().await?; + }; + } + + self.at + .send_edm(SetWifiStationConfig { + config_id: CONFIG_ID, + config_param: WifiStationConfig::ActiveOnStartup(OnOff::Off), + }) + .await?; + + self.at + .send_edm(SetWifiStationConfig { + config_id: CONFIG_ID, + config_param: WifiStationConfig::SSID(heapless::String::from(ssid)), + }) + .await?; + + self.at + .send_edm(SetWifiStationConfig { + config_id: CONFIG_ID, + config_param: WifiStationConfig::Authentication(Authentication::Open), + }) + .await?; + + self.at + .send_edm(ExecWifiStationAction { + config_id: CONFIG_ID, + action: WifiStationAction::Activate, + }) + .await?; + + with_timeout(Duration::from_secs(10), self.wait_for_join(ssid)) + .await + .map_err(|_| Error::Timeout)??; + + Ok(()) + } + + pub async fn join_wpa2(&mut self, ssid: &str, passphrase: &str) -> Result<(), Error> { + if matches!(self.get_wifi_status().await?, WifiStatusVal::Connected) { + // Wifi already connected. Check if the SSID is the same + let current_ssid = self.get_connected_ssid().await?; + if current_ssid.as_str() == ssid { + return Ok(()); + } else { + self.disconnect().await?; + }; + } + + self.at + .send_edm(SetWifiStationConfig { + config_id: CONFIG_ID, + config_param: WifiStationConfig::ActiveOnStartup(OnOff::Off), + }) + .await?; + + self.at + .send_edm(SetWifiStationConfig { + config_id: CONFIG_ID, + config_param: WifiStationConfig::SSID(heapless::String::from(ssid)), + }) + .await?; + + self.at + .send_edm(SetWifiStationConfig { + config_id: CONFIG_ID, + config_param: WifiStationConfig::Authentication(Authentication::WpaWpa2Psk), + }) + .await?; + + self.at + .send_edm(SetWifiStationConfig { + config_id: CONFIG_ID, + config_param: WifiStationConfig::WpaPskOrPassphrase(heapless::String::from( + passphrase, + )), + }) + .await?; + + self.at + .send_edm(ExecWifiStationAction { + config_id: CONFIG_ID, + action: WifiStationAction::Activate, + }) + .await?; + + with_timeout(Duration::from_secs(10), self.wait_for_join(ssid)) + .await + .map_err(|_| Error::Timeout)??; + + Ok(()) + } + + pub async fn disconnect(&mut self) -> Result<(), Error> { + match self.get_wifi_status().await? { + WifiStatusVal::Disabled => {} + WifiStatusVal::Disconnected | WifiStatusVal::Connected => { + self.at + .send_edm(ExecWifiStationAction { + config_id: CONFIG_ID, + action: WifiStationAction::Deactivate, + }) + .await?; + } + } + + let wait_for_disconnect = poll_fn(|cx| match self.state_ch.link_state(cx) { + LinkState::Up => Poll::Pending, + LinkState::Down => Poll::Ready(()), + }); + + with_timeout(Duration::from_secs(10), wait_for_disconnect) + .await + .map_err(|_| Error::Timeout)?; + + Ok(()) + } + + async fn wait_for_join(&mut self, ssid: &str) -> Result<(), Error> { + poll_fn(|cx| match self.state_ch.link_state(cx) { + LinkState::Down => Poll::Pending, + LinkState::Up => Poll::Ready(()), + }) + .await; + + // Check that SSID matches + let current_ssid = self.get_connected_ssid().await?; + if ssid != current_ssid.as_str() { + return Err(Error::Network); + } + + Ok(()) + } + + pub async fn gpio_set(&mut self, id: GPIOId, value: GPIOValue) -> Result<(), Error> { + self.at.send_edm(WriteGPIO { id, value }).await?; + Ok(()) + } +} diff --git a/src/asynch/mod.rs b/src/asynch/mod.rs new file mode 100644 index 0000000..48c239a --- /dev/null +++ b/src/asynch/mod.rs @@ -0,0 +1,79 @@ +pub mod control; +pub mod runner; +#[cfg(feature = "ublox-sockets")] +pub mod ublox_stack; + +pub(crate) mod channel; + +use crate::command::edm::{urc::EdmEvent, EdmAtCmdWrapper}; +use atat::{asynch::AtatClient, UrcSubscription}; +use channel::Device; +use embassy_sync::{blocking_mutex::raw::NoopRawMutex, mutex::Mutex}; +use embedded_hal::digital::OutputPin; +use runner::Runner; + +use self::control::Control; + +// NOTE: Must be pow(2) due to internal usage of `FnvIndexMap` +const MAX_CONNS: usize = 8; + +pub struct AtHandle<'d, AT: AtatClient>(&'d Mutex); + +impl<'d, AT: AtatClient> AtHandle<'d, AT> { + async fn send_edm, const LEN: usize>( + &mut self, + cmd: Cmd, + ) -> Result { + self.send(EdmAtCmdWrapper(cmd)).await + } + + async fn send, const LEN: usize>( + &mut self, + cmd: Cmd, + ) -> Result { + self.0.lock().await.send_retry::(&cmd).await + } +} + +pub struct State { + ch: channel::State, + at_handle: Mutex, +} + +impl State { + pub fn new(at_handle: AT) -> Self { + Self { + ch: channel::State::new(), + at_handle: Mutex::new(at_handle), + } + } +} + +pub const MTU: usize = 4096 + 4; // DATA_PACKAGE_SIZE + +pub async fn new<'a, AT: AtatClient, RST: OutputPin>( + state: &'a mut State, + urc_subscription: UrcSubscription<'a, EdmEvent>, + reset: RST, +) -> ( + Device<'a, MTU>, + Control<'a, AT>, + Runner<'a, AT, RST, MAX_CONNS>, +) { + let (ch_runner, net_device) = channel::new(&mut state.ch, [0; 6]); + let state_ch = ch_runner.state_runner(); + + let mut runner = Runner::new( + ch_runner, + AtHandle(&state.at_handle), + reset, + urc_subscription, + ); + + runner.init().await.unwrap(); + + let mut control = Control::new(state_ch, AtHandle(&state.at_handle)); + control.init().await.unwrap(); + + (net_device, control, runner) +} diff --git a/src/asynch/runner.rs b/src/asynch/runner.rs new file mode 100644 index 0000000..13620d7 --- /dev/null +++ b/src/asynch/runner.rs @@ -0,0 +1,410 @@ +use core::str::FromStr; + +use super::channel::{self, driver::LinkState}; +use crate::{ + asynch::ublox_stack::Disconnect, + command::{ + data_mode::{ + responses::ConnectPeerResponse, urc::PeerDisconnected, ClosePeerConnection, ConnectPeer, + }, + edm::{urc::EdmEvent, EdmDataCommand, SwitchToEdmCommand}, + network::{ + responses::NetworkStatusResponse, + types::{InterfaceType, NetworkStatus, NetworkStatusParameter}, + urc::{NetworkDown, NetworkUp}, + GetNetworkStatus, + }, + ping::Ping, + system::{ + types::{BaudRate, ChangeAfterConfirm, EchoOn, FlowControl, Parity, StopBits}, + RebootDCE, SetEcho, SetRS232Settings, StoreCurrentConfig, + }, + wifi::{ + types::DisconnectReason, + urc::{WifiLinkConnected, WifiLinkDisconnected}, + }, + Urc, + }, + connection::{WiFiState, WifiConnection}, + error::Error, + network::WifiNetwork, +}; +use atat::{asynch::AtatClient, UrcSubscription}; +use embassy_futures::select::{select, Either}; +use embassy_time::{with_timeout, Duration, Timer}; +use embedded_hal::digital::OutputPin; +use no_std_net::{Ipv4Addr, Ipv6Addr}; + +use super::{ + ublox_stack::{Connect, DataPacket, SocketRx, SocketTx}, + AtHandle, MTU, +}; + +/// Background runner for the Ublox Module. +/// +/// You must call `.run()` in a background task for the Ublox Module to operate. +pub struct Runner<'d, AT: AtatClient, RST: OutputPin, const MAX_CONNS: usize> { + ch: channel::Runner<'d, MTU>, + at: AtHandle<'d, AT>, + reset: RST, + wifi_connection: Option, + // connections: FnvIndexMap, + urc_subscription: UrcSubscription<'d, EdmEvent>, +} + +impl<'d, AT: AtatClient, RST: OutputPin, const MAX_CONNS: usize> Runner<'d, AT, RST, MAX_CONNS> { + pub(crate) fn new( + ch: channel::Runner<'d, MTU>, + at: AtHandle<'d, AT>, + reset: RST, + urc_subscription: UrcSubscription<'d, EdmEvent>, + ) -> Self { + Self { + ch, + at, + reset, + wifi_connection: None, + urc_subscription, + // connections: IndexMap::new(), + } + } + + pub(crate) async fn init(&mut self) -> Result<(), Error> { + // Initilize a new ublox device to a known state (set RS232 settings) + defmt::debug!("Initializing module"); + // Hard reset module + self.reset().await?; + + // TODO: handle EDM settings quirk see EDM datasheet: 2.2.5.1 AT Request Serial settings + self.at + .send_edm(SetRS232Settings { + baud_rate: BaudRate::B115200, + flow_control: FlowControl::On, + data_bits: 8, + stop_bits: StopBits::One, + parity: Parity::None, + change_after_confirm: ChangeAfterConfirm::ChangeAfterOK, + }) + .await?; + + // self.restart(true).await?; + + // Move to control + // if let Some(size) = self.config.tls_in_buffer_size { + // self.at + // .send_edm(SetPeerConfiguration { + // parameter: PeerConfigParameter::TlsInBuffer(size), + // }) + // .await?; + // } + + // if let Some(size) = self.config.tls_out_buffer_size { + // self.at + // .send_edm(SetPeerConfiguration { + // parameter: PeerConfigParameter::TlsOutBuffer(size), + // }) + // .await?; + // } + + Ok(()) + } + + async fn wait_startup(&mut self, timeout: Duration) -> Result<(), Error> { + let fut = async { + loop { + match self.urc_subscription.next_message_pure().await { + EdmEvent::ATEvent(Urc::StartUp) | EdmEvent::StartUp => return, + _ => {} + } + } + }; + + with_timeout(timeout, fut).await.map_err(|_| Error::Timeout) + } + + pub async fn reset(&mut self) -> Result<(), Error> { + defmt::warn!("Hard resetting Ublox Short Range"); + self.reset.set_low().ok(); + Timer::after(Duration::from_millis(100)).await; + self.reset.set_high().ok(); + + self.wait_startup(Duration::from_secs(4)).await?; + + self.enter_edm(Duration::from_secs(4)).await?; + + Ok(()) + } + + pub async fn restart(&mut self, store: bool) -> Result<(), Error> { + defmt::warn!("Soft resetting Ublox Short Range"); + if store { + self.at.send_edm(StoreCurrentConfig).await?; + } + + self.at.send_edm(RebootDCE).await?; + + Timer::after(Duration::from_millis(3500)).await; + + self.enter_edm(Duration::from_secs(4)).await?; + + Ok(()) + } + + pub async fn enter_edm(&mut self, timeout: Duration) -> Result<(), Error> { + // Switch to EDM on Init. If in EDM, fail and check with autosense + let fut = async { + loop { + // Ignore AT results until we are successful in EDM mode + self.at.send(SwitchToEdmCommand).await.ok(); + + match select( + self.urc_subscription.next_message_pure(), + Timer::after(Duration::from_millis(100)), + ) + .await + { + Either::First(EdmEvent::StartUp) => break, + _ => {} + }; + } + }; + + with_timeout(timeout, fut) + .await + .map_err(|_| Error::Timeout)?; + + self.at.send_edm(SetEcho { on: EchoOn::Off }).await?; + + Ok(()) + } + + pub async fn is_link_up(&mut self) -> Result { + // Determine link state + let link_state = match self.wifi_connection { + Some(ref conn) + if conn.network_up && matches!(conn.wifi_state, WiFiState::Connected) => + { + LinkState::Up + } + _ => LinkState::Down, + }; + + self.ch.set_link_state(link_state); + + Ok(link_state == LinkState::Up) + } + + pub async fn run(mut self) -> ! { + loop { + let tx = self.ch.tx_buf(); + let urc = self.urc_subscription.next_message_pure(); + + match select(tx, urc).await { + Either::First(p) => { + match postcard::from_bytes::(p) { + Ok(SocketTx::Data(packet)) => { + self.at + .send(EdmDataCommand { + channel: packet.edm_channel, + data: packet.payload, + }) + .await + .ok(); + } + Ok(SocketTx::Connect(Connect { url, socket_handle })) => { + if let Ok(ConnectPeerResponse { peer_handle }) = + self.at.send_edm(ConnectPeer { url }).await + { + self.rx(SocketRx::PeerHandle(socket_handle, peer_handle)) + .await; + } + } + Ok(SocketTx::Disconnect(peer_handle)) => { + self.at + .send_edm(ClosePeerConnection { peer_handle }) + .await + .ok(); + } + Ok(SocketTx::Dns(hostname)) => { + if self + .at + .send_edm(Ping { + retry_num: 1, + hostname, + }) + .await + .is_err() + { + self.rx(SocketRx::Ping(Err(()))).await; + } + } + Err(_) => {} + } + self.ch.tx_done(); + } + Either::Second(p) => match p { + EdmEvent::BluetoothConnectEvent(_) => {} + EdmEvent::ATEvent(urc) => self.handle_urc(urc).await.unwrap(), + EdmEvent::StartUp => { + defmt::error!("EDM startup event?! Device restarted unintentionally!"); + } + + // All below events needs to be conveyed to `self.ch.rx` + EdmEvent::IPv4ConnectEvent(ev) => self.rx(ev).await, + EdmEvent::IPv6ConnectEvent(ev) => self.rx(ev).await, + EdmEvent::DisconnectEvent(channel_id) => self.rx(channel_id).await, + EdmEvent::DataEvent(ev) => { + let packet = DataPacket { + edm_channel: ev.channel_id, + payload: ev.data.as_slice(), + }; + + self.rx(packet).await; + } + }, + } + } + } + + async fn rx<'a>(&mut self, packet: impl Into>) { + let pkg = packet.into(); + let buf = self.ch.rx_buf().await; + let used = defmt::unwrap!(postcard::to_slice(&pkg, buf)); + let len = used.len(); + self.ch.rx_done(len); + } + + async fn handle_urc(&mut self, urc: Urc) -> Result<(), Error> { + match urc { + Urc::StartUp => { + defmt::error!("AT startup event?! Device restarted unintentionally!"); + } + Urc::PeerConnected(pc) => { + defmt::info!("Peer connected! {}", pc); + // if self.connections.insert(handle, connection_type).is_err() { + // defmt::warn!("Out of connection entries"); + // } + } + Urc::PeerDisconnected(PeerDisconnected { handle }) => { + defmt::info!("Peer disconnected!"); + self.rx(SocketRx::Disconnect(Disconnect::Peer(handle))) + .await; + // self.connections.remove(&handle); + } + Urc::WifiLinkConnected(WifiLinkConnected { + connection_id: _, + bssid, + channel, + }) => { + if let Some(ref mut con) = self.wifi_connection { + con.wifi_state = WiFiState::Connected; + con.network.bssid = bssid; + con.network.channel = channel; + } else { + defmt::debug!("[URC] Active network config discovered"); + self.wifi_connection.replace( + WifiConnection::new( + WifiNetwork::new_station(bssid, channel), + WiFiState::Connected, + 255, + ) + .activate(), + ); + } + self.is_link_up().await?; + } + Urc::WifiLinkDisconnected(WifiLinkDisconnected { reason, .. }) => { + if let Some(ref mut con) = self.wifi_connection { + match reason { + DisconnectReason::NetworkDisabled => { + con.wifi_state = WiFiState::Inactive; + } + DisconnectReason::SecurityProblems => { + defmt::error!("Wifi Security Problems"); + } + _ => { + con.wifi_state = WiFiState::NotConnected; + } + } + } + + self.is_link_up().await?; + } + Urc::WifiAPUp(_) => todo!(), + Urc::WifiAPDown(_) => todo!(), + Urc::WifiAPStationConnected(_) => todo!(), + Urc::WifiAPStationDisconnected(_) => todo!(), + Urc::EthernetLinkUp(_) => todo!(), + Urc::EthernetLinkDown(_) => todo!(), + Urc::NetworkUp(NetworkUp { interface_id }) => { + self.network_status_callback(interface_id).await?; + } + Urc::NetworkDown(NetworkDown { interface_id }) => { + self.network_status_callback(interface_id).await?; + } + Urc::NetworkError(_) => todo!(), + Urc::PingResponse(resp) => self.rx(SocketRx::Ping(Ok(resp.ip))).await, + Urc::PingErrorResponse(_) => self.rx(SocketRx::Ping(Err(()))).await, + } + Ok(()) + } + + async fn network_status_callback(&mut self, interface_id: u8) -> Result<(), Error> { + let NetworkStatusResponse { + status: NetworkStatus::InterfaceType(InterfaceType::WifiStation), + .. + } = self + .at.send_edm(GetNetworkStatus { + interface_id, + status: NetworkStatusParameter::InterfaceType, + }) + .await? else { + return Err(Error::Network); + }; + + let NetworkStatusResponse { + status: NetworkStatus::Gateway(ipv4), + .. + } = self + .at.send_edm(GetNetworkStatus { + interface_id, + status: NetworkStatusParameter::Gateway, + }) + .await? else { + return Err(Error::Network); + }; + + let ipv4_up = core::str::from_utf8(ipv4.as_slice()) + .ok() + .and_then(|s| Ipv4Addr::from_str(s).ok()) + .map(|ip| !ip.is_unspecified()) + .unwrap_or_default(); + + let NetworkStatusResponse { + status: NetworkStatus::IPv6LinkLocalAddress(ipv6), + .. + } = self + .at.send_edm(GetNetworkStatus { + interface_id, + status: NetworkStatusParameter::IPv6LinkLocalAddress, + }) + .await? else { + return Err(Error::Network); + }; + + let ipv6_up = core::str::from_utf8(ipv6.as_slice()) + .ok() + .and_then(|s| Ipv6Addr::from_str(s).ok()) + .map(|ip| !ip.is_unspecified()) + .unwrap_or_default(); + + // Use `ipv4_up` & `ipv6_up` to determine link state + if let Some(ref mut con) = self.wifi_connection { + con.network_up = ipv4_up && ipv6_up; + } + + self.is_link_up().await?; + + Ok(()) + } +} diff --git a/src/asynch/ublox_stack/dns.rs b/src/asynch/ublox_stack/dns.rs new file mode 100644 index 0000000..e402dd3 --- /dev/null +++ b/src/asynch/ublox_stack/dns.rs @@ -0,0 +1,72 @@ +//! DNS client compatible with the `embedded-nal-async` traits. +//! +//! This exists only for compatibility with crates that use `embedded-nal-async`. +//! Prefer using [`Stack::dns_query`](crate::Stack::dns_query) directly if you're +//! not using `embedded-nal-async`. + +use no_std_net::IpAddr; + +use super::UbloxStack; + +/// Errors returned by DnsSocket. +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Error { + /// Invalid name + InvalidName, + /// Name too long + NameTooLong, + /// Name lookup failed + Failed, +} + +/// DNS client compatible with the `embedded-nal-async` traits. +/// +/// This exists only for compatibility with crates that use `embedded-nal-async`. +/// Prefer using [`Stack::dns_query`](crate::Stack::dns_query) directly if you're +/// not using `embedded-nal-async`. +pub struct DnsSocket<'a> { + stack: &'a UbloxStack, +} + +impl<'a> DnsSocket<'a> { + /// Create a new DNS socket using the provided stack. + /// + /// NOTE: If using DHCP, make sure it has reconfigured the stack to ensure the DNS servers are updated. + pub fn new(stack: &'a UbloxStack) -> Self { + Self { stack } + } + + /// Make a query for a given name and return the corresponding IP addresses. + pub async fn query( + &self, + name: &str, + addr_type: embedded_nal_async::AddrType, + ) -> Result { + let mut addrs = self.stack.dns_query(name, addr_type).await?; + if addrs.len() < 1 { + return Err(Error::Failed); + } + Ok(addrs.swap_remove(0)) + } +} + +// #[cfg(all(feature = "unstable-traits", feature = "nightly"))] +impl<'a> embedded_nal_async::Dns for DnsSocket<'a> { + type Error = Error; + + async fn get_host_by_name( + &self, + host: &str, + addr_type: embedded_nal_async::AddrType, + ) -> Result { + self.query(host, addr_type).await + } + + async fn get_host_by_address( + &self, + _addr: embedded_nal_async::IpAddr, + ) -> Result, Self::Error> { + todo!() + } +} diff --git a/src/asynch/ublox_stack/mod.rs b/src/asynch/ublox_stack/mod.rs new file mode 100644 index 0000000..4d05d4c --- /dev/null +++ b/src/asynch/ublox_stack/mod.rs @@ -0,0 +1,495 @@ +#[cfg(feature = "socket-tcp")] +pub mod tcp; +// #[cfg(feature = "socket-udp")] +// pub mod udp; + +pub mod dns; + +use core::cell::RefCell; +use core::future::poll_fn; +use core::task::{Context, Poll}; + +use crate::command::edm::types::{IPv4ConnectEvent, IPv6ConnectEvent, Protocol}; +use crate::peer_builder::PeerUrlBuilder; + +use super::{channel, MTU}; + +use super::channel::driver::{Driver, LinkState, RxToken, TxToken}; +use embassy_sync::waitqueue::WakerRegistration; +use embassy_time::Timer; +use embedded_nal_async::SocketAddr; +use futures::{pin_mut, Future}; +use heapless::Vec; +use no_std_net::IpAddr; +use serde::{Deserialize, Serialize}; +use ublox_sockets::{ + AnySocket, ChannelId, PeerHandle, Socket, SocketHandle, SocketSet, SocketStorage, TcpState, +}; + +pub struct StackResources { + sockets: [SocketStorage<'static>; SOCK], +} + +impl StackResources { + pub fn new() -> Self { + Self { + sockets: [SocketStorage::EMPTY; SOCK], + } + } +} + +pub struct UbloxStack { + pub(crate) socket: RefCell, + inner: RefCell, +} + +struct Inner { + device: channel::Device<'static, MTU>, + link_up: bool, + dns_result: Option>, + dns_waker: WakerRegistration, +} + +pub(crate) struct SocketStack { + pub(crate) sockets: SocketSet<'static>, + pub(crate) waker: WakerRegistration, + dropped_sockets: heapless::Vec, +} + +impl UbloxStack { + pub fn new( + device: channel::Device<'static, MTU>, + resources: &'static mut StackResources, + ) -> Self { + let sockets = SocketSet::new(&mut resources.sockets[..]); + + let socket = SocketStack { + sockets, + waker: WakerRegistration::new(), + dropped_sockets: heapless::Vec::new(), + }; + + let inner = Inner { + device, + link_up: false, + dns_result: None, + dns_waker: WakerRegistration::new(), + }; + + Self { + socket: RefCell::new(socket), + inner: RefCell::new(inner), + } + } + + #[allow(dead_code)] + fn with(&self, f: impl FnOnce(&SocketStack, &Inner) -> R) -> R { + f(&*self.socket.borrow(), &*self.inner.borrow()) + } + + fn with_mut(&self, f: impl FnOnce(&mut SocketStack, &mut Inner) -> R) -> R { + f( + &mut *self.socket.borrow_mut(), + &mut *self.inner.borrow_mut(), + ) + } + + pub async fn run(&self) -> ! { + poll_fn(|cx| { + self.with_mut(|s, i| i.poll(cx, s)); + Poll::<()>::Pending + }) + .await; + unreachable!() + } + + /// Make a query for a given name and return the corresponding IP addresses. + // #[cfg(feature = "dns")] + pub async fn dns_query( + &self, + name: &str, + addr_type: embedded_nal_async::AddrType, + ) -> Result, dns::Error> { + // For A and AAAA queries we try detect whether `name` is just an IP address + match addr_type { + embedded_nal_async::AddrType::IPv4 => { + if let Ok(ip) = name.parse().map(IpAddr::V4) { + return Ok([ip].into_iter().collect()); + } + } + embedded_nal_async::AddrType::IPv6 => { + if let Ok(ip) = name.parse().map(IpAddr::V6) { + return Ok([ip].into_iter().collect()); + } + } + _ => {} + } + + poll_fn(|cx| { + self.with_mut(|_s, i| { + i.dns_result = None; + Poll::Ready( + i.send_packet(cx, SocketTx::Dns(name)) + .map_err(|_| dns::Error::Failed), + ) + }) + }) + .await?; + + poll_fn(|cx| { + self.with_mut(|_s, i| match i.dns_result { + Some(Ok(ip)) => Poll::Ready(Ok([ip].into_iter().collect())), + Some(Err(_)) => Poll::Ready(Err(dns::Error::Failed)), + None => { + i.dns_waker.register(cx.waker()); + Poll::Pending + } + }) + }) + .await + } +} + +impl Inner { + fn poll(&mut self, cx: &mut Context<'_>, s: &mut SocketStack) { + s.waker.register(cx.waker()); + + self.socket_rx(cx, s); + self.socket_tx(cx, s); + + // Handle delayed close-by-drop here + while let Some(dropped_peer_handle) = s.dropped_sockets.pop() { + defmt::warn!("Handling dropped socket {}", dropped_peer_handle); + self.send_packet(cx, SocketTx::Disconnect(dropped_peer_handle)) + .ok(); + } + // Update link up + let old_link_up = self.link_up; + self.link_up = self.device.link_state(cx) == LinkState::Up; + + // Print when changed + if old_link_up != self.link_up { + defmt::info!("link_up = {:?}", self.link_up); + } + } + + fn socket_rx(&mut self, cx: &mut Context<'_>, s: &mut SocketStack) { + while let Some((rx_token, _)) = self.device.receive(cx) { + if let Err(e) = rx_token.consume(|a| { + match postcard::from_bytes::(a)? { + SocketRx::Data(packet) => { + for (_handle, socket) in s.sockets.iter_mut() { + match socket { + #[cfg(feature = "socket-udp")] + Socket::Udp(udp) + if udp.edm_channel == Some(packet.edm_channel) + && udp.may_recv() => + { + udp.rx_enqueue_slice(&packet.payload); + break; + } + #[cfg(feature = "socket-tcp")] + Socket::Tcp(tcp) + if tcp.edm_channel == Some(packet.edm_channel) + && tcp.may_recv() => + { + tcp.rx_enqueue_slice(&packet.payload); + break; + } + _ => {} + } + } + } + SocketRx::Ipv4Connect(ev) => { + let endpoint = SocketAddr::new(ev.remote_ip.into(), ev.remote_port); + Self::connect_event(ev.channel_id, ev.protocol, endpoint, s); + } + SocketRx::Ipv6Connect(ev) => { + let endpoint = SocketAddr::new(ev.remote_ip.into(), ev.remote_port); + Self::connect_event(ev.channel_id, ev.protocol, endpoint, s); + } + SocketRx::Disconnect(Disconnect::EdmChannel(channel_id)) => { + for (_handle, socket) in s.sockets.iter_mut() { + match socket { + #[cfg(feature = "socket-udp")] + Socket::Udp(udp) if udp.edm_channel == Some(channel_id) => { + udp.edm_channel = None; + break; + } + #[cfg(feature = "socket-tcp")] + Socket::Tcp(tcp) if tcp.edm_channel == Some(channel_id) => { + tcp.edm_channel = None; + break; + } + _ => {} + } + } + } + SocketRx::Disconnect(Disconnect::Peer(peer_handle)) => { + for (_handle, socket) in s.sockets.iter_mut() { + match socket { + #[cfg(feature = "socket-udp")] + Socket::Udp(udp) if udp.peer_handle == Some(peer_handle) => { + tcp.peer_handle = None; + udp.set_state(UdpState::TimeWait); + break; + } + #[cfg(feature = "socket-tcp")] + Socket::Tcp(tcp) if tcp.peer_handle == Some(peer_handle) => { + tcp.peer_handle = None; + tcp.set_state(TcpState::TimeWait); + break; + } + _ => {} + } + } + } + SocketRx::PeerHandle(socket_handle, peer_handle) => { + for (handle, socket) in s.sockets.iter_mut() { + if handle == socket_handle { + match socket { + #[cfg(feature = "socket-udp")] + Socket::Udp(udp) => { + udp.peer_handle = Some(peer_handle); + } + #[cfg(feature = "socket-tcp")] + Socket::Tcp(tcp) => { + tcp.peer_handle = Some(peer_handle); + } + _ => {} + } + break; + } + } + } + SocketRx::Ping(res) => { + self.dns_result = Some(res); + self.dns_waker.wake(); + } + } + Ok::<_, postcard::Error>(()) + }) { + defmt::error!("Socket RX failed {:?}", e) + }; + } + } + + fn socket_tx(&mut self, cx: &mut Context<'_>, s: &mut SocketStack) { + for (handle, socket) in s.sockets.iter_mut() { + // if !socket.egress_permitted(self.inner.now, |ip_addr| self.inner.has_neighbor(&ip_addr)) + // { + // continue; + // } + + let result = match socket { + #[cfg(feature = "socket-udp")] + Socket::Udp(udp) => todo!(), + #[cfg(feature = "socket-tcp")] + Socket::Tcp(tcp) => { + let res = match tcp.poll() { + Ok(TcpState::Closed) => { + if let Some(addr) = tcp.remote_endpoint() { + let url = PeerUrlBuilder::new() + .address(&addr) + .set_local_port(tcp.local_port) + .tcp::<128>() + .unwrap(); + + let pkt = SocketTx::Connect(Connect { + socket_handle: handle, + url: &url, + }); + + self.send_packet(cx, pkt).and_then(|r| { + tcp.set_state(TcpState::SynSent); + Ok(r) + }) + } else { + Ok(()) + } + } + // We transmit data in all states where we may have data in the buffer, + // or the transmit half of the connection is still open. + Ok(TcpState::Established) + | Ok(TcpState::CloseWait) + | Ok(TcpState::LastAck) => { + if let Some(edm_channel) = tcp.edm_channel { + tcp.tx_dequeue(|payload| { + if payload.len() > 0 { + let pkt = SocketTx::Data(DataPacket { + edm_channel, + payload, + }); + + (payload.len(), self.send_packet(cx, pkt)) + } else { + (0, Ok(())) + } + }) + } else { + Ok(()) + } + } + Ok(TcpState::FinWait1) => { + let pkt = SocketTx::Disconnect(tcp.peer_handle.unwrap()); + self.send_packet(cx, pkt) + } + Ok(TcpState::Listen) => todo!(), + Ok(TcpState::SynReceived) => todo!(), + Err(_) => Err(()), + _ => Ok(()), + }; + + if let Some(poll_at) = tcp.poll_at() { + let t = Timer::at(poll_at); + pin_mut!(t); + if t.poll(cx).is_ready() { + cx.waker().wake_by_ref(); + } + } + + res + } + _ => Ok(()), + }; + + match result { + Err(_) => { + break; + } // Device buffer full. + Ok(()) => {} + } + } + } + + pub(crate) fn send_packet(&mut self, cx: &mut Context, pkt: SocketTx) -> Result<(), ()> { + let Some(tx_token) = self.device.transmit(cx) else { + return Err(()); + }; + + let len = postcard::experimental::serialized_size(&pkt).map_err(drop)?; + tx_token.consume(len, |tx_buf| { + postcard::to_slice(&pkt, tx_buf).map(drop).map_err(drop) + })?; + + Ok(()) + } + + fn connect_event( + channel_id: ChannelId, + protocol: Protocol, + endpoint: SocketAddr, + s: &mut SocketStack, + ) { + for (_handle, socket) in s.sockets.iter_mut() { + match protocol { + #[cfg(feature = "socket-tcp")] + Protocol::TCP => match ublox_sockets::tcp::Socket::downcast_mut(socket) { + Some(tcp) if tcp.remote_endpoint == Some(endpoint) => { + tcp.edm_channel = Some(channel_id); + tcp.set_state(TcpState::Established); + break; + } + _ => {} + }, + #[cfg(feature = "socket-udp")] + Protocol::UDP => match ublox_sockets::udp::Socket::downcast_mut(socket) { + Some(udp) if udp.remote_endpoint == Some(endpoint) => { + udp.edm_channel = Some(channel_id); + udp.set_state(ublox_sockets::UdpState::Established); + break; + } + _ => {} + }, + _ => {} + } + } + } +} + +#[derive(Serialize, Deserialize)] +pub enum SocketTx<'a> { + #[serde(borrow)] + Data(DataPacket<'a>), + #[serde(borrow)] + Connect(Connect<'a>), + Disconnect(PeerHandle), + Dns(&'a str), +} + +impl<'a> defmt::Format for SocketTx<'a> { + fn format(&self, fmt: defmt::Formatter) { + match self { + SocketTx::Data(_) => defmt::write!(fmt, "SocketTx::Data"), + SocketTx::Connect(_) => defmt::write!(fmt, "SocketTx::Connect"), + SocketTx::Disconnect(_) => defmt::write!(fmt, "SocketTx::Disconnect"), + SocketTx::Dns(_) => defmt::write!(fmt, "SocketTx::Dns"), + } + } +} + +#[derive(Serialize, Deserialize)] +pub struct Connect<'a> { + pub url: &'a str, + pub socket_handle: SocketHandle, +} + +#[derive(Serialize, Deserialize)] +pub enum SocketRx<'a> { + #[serde(borrow)] + Data(DataPacket<'a>), + PeerHandle(SocketHandle, PeerHandle), + Ipv4Connect(IPv4ConnectEvent), + Ipv6Connect(IPv6ConnectEvent), + Disconnect(Disconnect), + Ping(Result), +} + +#[derive(Serialize, Deserialize)] +pub enum Disconnect { + EdmChannel(ChannelId), + Peer(PeerHandle), +} + +impl<'a> defmt::Format for SocketRx<'a> { + fn format(&self, fmt: defmt::Formatter) { + match self { + SocketRx::Data(_) => defmt::write!(fmt, "SocketRx::Data"), + SocketRx::PeerHandle(_, _) => defmt::write!(fmt, "SocketRx::PeerHandle"), + SocketRx::Ipv4Connect(_) => defmt::write!(fmt, "SocketRx::Ipv4Connect"), + SocketRx::Ipv6Connect(_) => defmt::write!(fmt, "SocketRx::Ipv6Connect"), + SocketRx::Disconnect(_) => defmt::write!(fmt, "SocketRx::Disconnect"), + SocketRx::Ping(_) => defmt::write!(fmt, "SocketRx::Ping"), + } + } +} + +impl From for SocketRx<'_> { + fn from(value: IPv4ConnectEvent) -> Self { + Self::Ipv4Connect(value) + } +} + +impl From for SocketRx<'_> { + fn from(value: IPv6ConnectEvent) -> Self { + Self::Ipv6Connect(value) + } +} + +impl From for SocketRx<'_> { + fn from(value: ChannelId) -> Self { + Self::Disconnect(Disconnect::EdmChannel(value)) + } +} + +impl<'a> From> for SocketRx<'a> { + fn from(value: DataPacket<'a>) -> Self { + Self::Data(value) + } +} + +#[derive(Serialize, Deserialize)] +pub struct DataPacket<'a> { + pub edm_channel: ChannelId, + pub payload: &'a [u8], +} diff --git a/src/asynch/ublox_stack/tcp.rs b/src/asynch/ublox_stack/tcp.rs new file mode 100644 index 0000000..db7d242 --- /dev/null +++ b/src/asynch/ublox_stack/tcp.rs @@ -0,0 +1,508 @@ +use core::cell::RefCell; +use core::future::poll_fn; +use core::mem; +use core::task::Poll; + +use embedded_nal_async::SocketAddr; +use ublox_sockets::{tcp, SocketHandle, TcpState}; + +use super::{SocketStack, UbloxStack}; + +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Error { + ConnectionReset, +} + +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum ConnectError { + /// The socket is already connected or listening. + InvalidState, + /// The remote host rejected the connection with a RST packet. + ConnectionReset, + /// Connect timed out. + TimedOut, + /// No route to host. + NoRoute, +} + +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum AcceptError { + /// The socket is already connected or listening. + InvalidState, + /// Invalid listen port + InvalidPort, + /// The remote host rejected the connection with a RST packet. + ConnectionReset, +} + +pub struct TcpSocket<'a> { + io: TcpIo<'a>, +} + +pub struct TcpReader<'a> { + io: TcpIo<'a>, +} + +pub struct TcpWriter<'a> { + io: TcpIo<'a>, +} + +impl<'a> TcpReader<'a> { + pub async fn read(&mut self, buf: &mut [u8]) -> Result { + self.io.read(buf).await + } +} + +impl<'a> TcpWriter<'a> { + pub async fn write(&mut self, buf: &[u8]) -> Result { + self.io.write(buf).await + } + + pub async fn flush(&mut self) -> Result<(), Error> { + self.io.flush().await + } +} + +impl<'a> TcpSocket<'a> { + pub fn new(stack: &'a UbloxStack, rx_buffer: &'a mut [u8], tx_buffer: &'a mut [u8]) -> Self { + let s = &mut *stack.socket.borrow_mut(); + let rx_buffer: &'static mut [u8] = unsafe { mem::transmute(rx_buffer) }; + let tx_buffer: &'static mut [u8] = unsafe { mem::transmute(tx_buffer) }; + let handle = s.sockets.add(tcp::Socket::new( + tcp::SocketBuffer::new(rx_buffer), + tcp::SocketBuffer::new(tx_buffer), + )); + + Self { + io: TcpIo { + stack: &stack.socket, + handle, + }, + } + } + + pub fn split(&mut self) -> (TcpReader<'_>, TcpWriter<'_>) { + (TcpReader { io: self.io }, TcpWriter { io: self.io }) + } + + pub async fn connect(&mut self, remote_endpoint: T) -> Result<(), ConnectError> + where + T: Into, + { + match { self.io.with_mut(|s| s.connect(remote_endpoint, None)) } { + Ok(()) => {} + Err(_) => return Err(ConnectError::InvalidState), + // Err(tcp::ConnectError::Unaddressable) => return Err(ConnectError::NoRoute), + } + + poll_fn(|cx| { + self.io.with_mut(|s| match s.state() { + tcp::State::TimeWait => Poll::Ready(Err(ConnectError::ConnectionReset)), + tcp::State::Listen => unreachable!(), + tcp::State::Closed | tcp::State::SynSent | tcp::State::SynReceived => { + s.register_send_waker(cx.waker()); + Poll::Pending + } + _ => Poll::Ready(Ok(())), + }) + }) + .await + } + + // FIXME: + // pub async fn accept(&mut self, local_endpoint: T) -> Result<(), AcceptError> + // where + // T: Into, + // { + // match self.io.with_mut(|s, _| s.listen(local_endpoint)) { + // Ok(()) => {} + // Err(tcp::ListenError::InvalidState) => return Err(AcceptError::InvalidState), + // Err(tcp::ListenError::Unaddressable) => return Err(AcceptError::InvalidPort), + // } + + // poll_fn(|cx| { + // self.io.with_mut(|s, _| match s.state() { + // tcp::State::Listen | tcp::State::SynSent | tcp::State::SynReceived => { + // s.register_send_waker(cx.waker()); + // Poll::Pending + // } + // _ => Poll::Ready(Ok(())), + // }) + // }) + // .await + // } + + pub async fn read(&mut self, buf: &mut [u8]) -> Result { + self.io.read(buf).await + } + + pub async fn write(&mut self, buf: &[u8]) -> Result { + self.io.write(buf).await + } + + pub async fn flush(&mut self) -> Result<(), Error> { + self.io.flush().await + } + + // pub fn set_timeout(&mut self, duration: Option) { + // self.io.with_mut(|s| s.set_timeout(duration)) + // } + + // pub fn set_keep_alive(&mut self, interval: Option) { + // self.io.with_mut(|s| s.set_keep_alive(interval)) + // } + + // pub fn local_endpoint(&self) -> Option { + // self.io.with(|s, _| s.local_endpoint()) + // } + + pub fn remote_endpoint(&self) -> Option { + self.io.with(|s| s.remote_endpoint()) + } + + pub fn state(&self) -> tcp::State { + self.io.with(|s| s.state()) + } + + pub fn close(&mut self) { + self.io.with_mut(|s| s.close()) + } + + pub fn abort(&mut self) { + self.io.with_mut(|s| s.abort()) + } + + pub fn may_send(&self) -> bool { + self.io.with(|s| s.may_send()) + } + + pub fn may_recv(&self) -> bool { + self.io.with(|s| s.may_recv()) + } +} + +impl<'a> Drop for TcpSocket<'a> { + fn drop(&mut self) { + if matches!(self.state(), TcpState::Listen | TcpState::Established) { + if let Some(peer_handle) = self.io.with(|s| s.peer_handle) { + self.io + .stack + .borrow_mut() + .dropped_sockets + .push(peer_handle) + .ok(); + } + } + self.io.stack.borrow_mut().sockets.remove(self.io.handle); + } +} + +// ======================= + +#[derive(Copy, Clone)] +struct TcpIo<'a> { + stack: &'a RefCell, + handle: SocketHandle, +} + +impl<'d> TcpIo<'d> { + fn with(&self, f: impl FnOnce(&tcp::Socket) -> R) -> R { + let s = &*self.stack.borrow(); + let socket = s.sockets.get::(self.handle); + f(socket) + } + + fn with_mut(&mut self, f: impl FnOnce(&mut tcp::Socket) -> R) -> R { + let s = &mut *self.stack.borrow_mut(); + let socket = s.sockets.get_mut::(self.handle); + let res = f(socket); + s.waker.wake(); + res + } + + async fn read(&mut self, buf: &mut [u8]) -> Result { + poll_fn(move |cx| { + // CAUTION: smoltcp semantics around EOF are different to what you'd expect + // from posix-like IO, so we have to tweak things here. + self.with_mut(|s| match s.recv_slice(buf) { + // No data ready + Ok(0) => { + s.register_recv_waker(cx.waker()); + Poll::Pending + } + // Data ready! + Ok(n) => Poll::Ready(Ok(n)), + // EOF + Err(_) => Poll::Ready(Ok(0)), + // FIXME: + // Err(tcp::RecvError::Finished) => Poll::Ready(Ok(0)), + // Connection reset. TODO: this can also be timeouts etc, investigate. + // Err(tcp::RecvError::InvalidState) => Poll::Ready(Err(Error::ConnectionReset)), + }) + }) + .await + } + + async fn write(&mut self, buf: &[u8]) -> Result { + poll_fn(move |cx| { + self.with_mut(|s| match s.send_slice(buf) { + // Not ready to send (no space in the tx buffer) + Ok(0) => { + s.register_send_waker(cx.waker()); + Poll::Pending + } + // Some data sent + Ok(n) => Poll::Ready(Ok(n)), + // Connection reset. TODO: this can also be timeouts etc, investigate. + Err(_) => Poll::Ready(Err(Error::ConnectionReset)), + // FIXME: + // Err(tcp::SendError::InvalidState) => Poll::Ready(Err(Error::ConnectionReset)), + }) + }) + .await + } + + async fn flush(&mut self) -> Result<(), Error> { + poll_fn(move |cx| { + self.with_mut(|s| { + // If there are outstanding send operations, register for wake up and wait + // smoltcp issues wake-ups when octets are dequeued from the send buffer + if s.send_queue() > 0 { + s.register_send_waker(cx.waker()); + Poll::Pending + // No outstanding sends, socket is flushed + } else { + Poll::Ready(Ok(())) + } + }) + }) + .await + } +} + +// #[cfg(feature = "nightly")] +mod embedded_io_impls { + use super::*; + + impl embedded_io::Error for ConnectError { + fn kind(&self) -> embedded_io::ErrorKind { + embedded_io::ErrorKind::Other + } + } + + impl embedded_io::Error for Error { + fn kind(&self) -> embedded_io::ErrorKind { + embedded_io::ErrorKind::Other + } + } + + impl<'d> embedded_io::Io for TcpSocket<'d> { + type Error = Error; + } + + impl<'d> embedded_io::asynch::Read for TcpSocket<'d> { + async fn read(&mut self, buf: &mut [u8]) -> Result { + self.io.read(buf).await + } + } + + impl<'d> embedded_io::asynch::Write for TcpSocket<'d> { + async fn write(&mut self, buf: &[u8]) -> Result { + self.io.write(buf).await + } + + async fn flush(&mut self) -> Result<(), Self::Error> { + self.io.flush().await + } + } + + impl<'d> embedded_io::Io for TcpReader<'d> { + type Error = Error; + } + + impl<'d> embedded_io::asynch::Read for TcpReader<'d> { + async fn read(&mut self, buf: &mut [u8]) -> Result { + self.io.read(buf).await + } + } + + impl<'d> embedded_io::Io for TcpWriter<'d> { + type Error = Error; + } + + impl<'d> embedded_io::asynch::Write for TcpWriter<'d> { + async fn write(&mut self, buf: &[u8]) -> Result { + self.io.write(buf).await + } + + async fn flush(&mut self) -> Result<(), Self::Error> { + self.io.flush().await + } + } +} + +// #[cfg(all(feature = "unstable-traits", feature = "nightly"))] +pub mod client { + use core::cell::UnsafeCell; + use core::mem::MaybeUninit; + use core::ptr::NonNull; + + use atomic_polyfill::{AtomicBool, Ordering}; + + use super::*; + + /// TCP client capable of creating up to N multiple connections with tx and rx buffers according to TX_SZ and RX_SZ. + pub struct TcpClient<'d, const N: usize, const TX_SZ: usize = 1024, const RX_SZ: usize = 1024> { + pub(crate) stack: &'d UbloxStack, + pub(crate) state: &'d TcpClientState, + } + + impl<'d, const N: usize, const TX_SZ: usize, const RX_SZ: usize> TcpClient<'d, N, TX_SZ, RX_SZ> { + /// Create a new TcpClient + pub fn new(stack: &'d UbloxStack, state: &'d TcpClientState) -> Self { + Self { stack, state } + } + } + + impl<'d, const N: usize, const TX_SZ: usize, const RX_SZ: usize> embedded_nal_async::TcpConnect + for TcpClient<'d, N, TX_SZ, RX_SZ> + { + type Error = Error; + type Connection<'m> = TcpConnection<'m, N, TX_SZ, RX_SZ> where Self: 'm; + + async fn connect<'a>( + &'a self, + remote: embedded_nal_async::SocketAddr, + ) -> Result, Self::Error> + where + Self: 'a, + { + let remote_endpoint = (remote.ip(), remote.port()); + let mut socket = TcpConnection::new(&self.stack, self.state)?; + socket + .socket + .connect(remote_endpoint) + .await + .map_err(|_| Error::ConnectionReset)?; + Ok(socket) + } + } + + pub struct TcpConnection<'d, const N: usize, const TX_SZ: usize, const RX_SZ: usize> { + socket: TcpSocket<'d>, + state: &'d TcpClientState, + bufs: NonNull<([u8; TX_SZ], [u8; RX_SZ])>, + } + + impl<'d, const N: usize, const TX_SZ: usize, const RX_SZ: usize> + TcpConnection<'d, N, TX_SZ, RX_SZ> + { + fn new( + stack: &'d UbloxStack, + state: &'d TcpClientState, + ) -> Result { + let mut bufs = state.pool.alloc().ok_or(Error::ConnectionReset)?; + Ok(Self { + socket: unsafe { + TcpSocket::new(stack, &mut bufs.as_mut().1, &mut bufs.as_mut().0) + }, + state, + bufs, + }) + } + } + + impl<'d, const N: usize, const TX_SZ: usize, const RX_SZ: usize> Drop + for TcpConnection<'d, N, TX_SZ, RX_SZ> + { + fn drop(&mut self) { + unsafe { + self.socket.close(); + self.state.pool.free(self.bufs); + } + } + } + + impl<'d, const N: usize, const TX_SZ: usize, const RX_SZ: usize> embedded_io::Io + for TcpConnection<'d, N, TX_SZ, RX_SZ> + { + type Error = Error; + } + + impl<'d, const N: usize, const TX_SZ: usize, const RX_SZ: usize> embedded_io::asynch::Read + for TcpConnection<'d, N, TX_SZ, RX_SZ> + { + async fn read(&mut self, buf: &mut [u8]) -> Result { + self.socket.read(buf).await + } + } + + impl<'d, const N: usize, const TX_SZ: usize, const RX_SZ: usize> embedded_io::asynch::Write + for TcpConnection<'d, N, TX_SZ, RX_SZ> + { + async fn write(&mut self, buf: &[u8]) -> Result { + self.socket.write(buf).await + } + + async fn flush(&mut self) -> Result<(), Self::Error> { + self.socket.flush().await + } + } + + /// State for TcpClient + pub struct TcpClientState { + pool: Pool<([u8; TX_SZ], [u8; RX_SZ]), N>, + } + + impl TcpClientState { + pub const fn new() -> Self { + Self { pool: Pool::new() } + } + } + + unsafe impl Sync + for TcpClientState + { + } + + struct Pool { + used: [AtomicBool; N], + data: [UnsafeCell>; N], + } + + impl Pool { + const VALUE: AtomicBool = AtomicBool::new(false); + const UNINIT: UnsafeCell> = UnsafeCell::new(MaybeUninit::uninit()); + + const fn new() -> Self { + Self { + used: [Self::VALUE; N], + data: [Self::UNINIT; N], + } + } + } + + impl Pool { + fn alloc(&self) -> Option> { + for n in 0..N { + if self.used[n].swap(true, Ordering::SeqCst) == false { + let p = self.data[n].get() as *mut T; + return Some(unsafe { NonNull::new_unchecked(p) }); + } + } + None + } + + /// safety: p must be a pointer obtained from self.alloc that hasn't been freed yet. + unsafe fn free(&self, p: NonNull) { + let origin = self.data.as_ptr() as *mut T; + let n = p.as_ptr().offset_from(origin); + assert!(n >= 0); + assert!((n as usize) < N); + self.used[n as usize].store(false, Ordering::SeqCst); + } + } +} diff --git a/ublox-short-range/src/client.rs b/src/blocking/client.rs similarity index 92% rename from ublox-short-range/src/client.rs rename to src/blocking/client.rs index a6f486b..f80d704 100644 --- a/ublox-short-range/src/client.rs +++ b/src/blocking/client.rs @@ -1,6 +1,7 @@ use core::str::FromStr; use crate::{ + blocking::timer::Timer, command::{ data_mode::{ types::{IPProtocol, PeerConfigParameter}, @@ -30,9 +31,9 @@ use crate::{ }, }; use defmt::{debug, error, trace}; +use embassy_time::Duration; use embedded_hal::digital::OutputPin; -use embedded_nal::{nb, IpAddr, Ipv4Addr, SocketAddr}; -use fugit::ExtU32; +use embedded_nal::{IpAddr, Ipv4Addr, SocketAddr}; use ublox_sockets::{ udp_listener::UdpListener, AnySocket, SocketHandle, SocketSet, SocketType, TcpSocket, TcpState, UdpSocket, UdpState, @@ -61,8 +62,8 @@ pub struct SecurityCredentials { /// Creates new socket numbers /// Properly not Async safe -pub fn new_socket_num<'a, const TIMER_HZ: u32, const N: usize, const L: usize>( - sockets: &'a SocketSet, +pub fn new_socket_num<'a, const N: usize, const L: usize>( + sockets: &'a SocketSet, ) -> Result { let mut num = 0; while sockets.socket_type(SocketHandle(num)).is_some() { @@ -74,10 +75,9 @@ pub fn new_socket_num<'a, const TIMER_HZ: u32, const N: usize, const L: usize>( Ok(num) } -pub struct UbloxClient +pub struct UbloxClient where C: atat::blocking::AtatClient, - CLK: fugit_timer::Timer, RST: OutputPin, { pub(crate) module_started: bool, @@ -87,23 +87,20 @@ where pub(crate) wifi_config_active_on_startup: Option, pub(crate) client: C, pub(crate) config: Config, - pub(crate) sockets: Option<&'static mut SocketSet>, + pub(crate) sockets: Option<&'static mut SocketSet>, pub(crate) dns_state: DNSState, pub(crate) urc_attempts: u8, pub(crate) security_credentials: SecurityCredentials, - pub(crate) timer: CLK, pub(crate) socket_map: SocketMap, pub(crate) udp_listener: UdpListener<4, N>, } -impl - UbloxClient +impl UbloxClient where C: atat::blocking::AtatClient, - CLK: fugit_timer::Timer, RST: OutputPin, { - pub fn new(client: C, timer: CLK, config: Config) -> Self { + pub fn new(client: C, config: Config) -> Self { UbloxClient { module_started: false, initialized: false, @@ -116,18 +113,17 @@ where dns_state: DNSState::NotResolving, urc_attempts: 0, security_credentials: SecurityCredentials::default(), - timer, socket_map: SocketMap::default(), udp_listener: UdpListener::new(), } } - pub fn set_socket_storage(&mut self, socket_set: &'static mut SocketSet) { + pub fn set_socket_storage(&mut self, socket_set: &'static mut SocketSet) { socket_set.prune(); self.sockets.replace(socket_set); } - pub fn take_socket_storage(&mut self) -> Option<&'static mut SocketSet> { + pub fn take_socket_storage(&mut self) -> Option<&'static mut SocketSet> { self.sockets.take() } @@ -150,8 +146,7 @@ where while self.serial_mode != SerialMode::ExtendedData { self.send_internal(&SwitchToEdmCommand, true).ok(); - self.timer.start(100.millis()).map_err(|_| Error::Timer)?; - nb::block!(self.timer.wait()).map_err(|_| Error::Timer)?; + Timer::after(Duration::from_millis(100)).wait(); while self.handle_urc()? {} } @@ -190,8 +185,7 @@ where while self.serial_mode != SerialMode::ExtendedData { self.send_internal(&SwitchToEdmCommand, true).ok(); - self.timer.start(100.millis()).map_err(|_| Error::Timer)?; - nb::block!(self.timer.wait()).map_err(|_| Error::Timer)?; + Timer::after(Duration::from_millis(100)).wait(); while self.handle_urc()? {} } @@ -264,24 +258,18 @@ where if let Some(ref mut pin) = self.config.rst_pin { defmt::warn!("Hard resetting Ublox Short Range"); pin.set_low().ok(); - self.timer.start(50.millis()).map_err(|_| Error::Timer)?; - nb::block!(self.timer.wait()).map_err(|_| Error::Timer)?; - + Timer::after(Duration::from_millis(50)).wait(); pin.set_high().ok(); - self.timer.start(4.secs()).map_err(|_| Error::Timer)?; - loop { - match self.timer.wait() { - Ok(()) => return Err(Error::_Unknown), - Err(nb::Error::WouldBlock) => { - self.handle_urc().ok(); - if self.module_started { - break; - } - } - Err(_) => return Err(Error::Timer), + Timer::with_timeout(Duration::from_secs(4), || { + self.handle_urc().ok(); + if self.module_started { + Some(Ok::<(), ()>(())) + } else { + None } - } + }) + .map_err(|_| Error::Timeout)?; } Ok(()) } @@ -302,19 +290,15 @@ where self.send_internal(&EdmAtCmdWrapper(RebootDCE), false)?; self.clear_buffers()?; - self.timer.start(4.secs()).map_err(|_| Error::Timer)?; - loop { - match self.timer.wait() { - Ok(()) => return Err(Error::_Unknown), - Err(nb::Error::WouldBlock) => { - self.handle_urc().ok(); - if self.module_started { - break; - } - } - Err(_) => return Err(Error::Timer), + Timer::with_timeout(Duration::from_secs(4), || { + self.handle_urc().ok(); + if self.module_started { + Some(Ok::<(), ()>(())) + } else { + None } - } + }) + .map_err(|_| Error::Timeout)?; Ok(()) } @@ -327,8 +311,7 @@ where } // Allow ATAT some time to clear the buffers - self.timer.start(300.millis()).map_err(|_| Error::Timer)?; - nb::block!(self.timer.wait()).map_err(|_| Error::Timer)?; + Timer::after(Duration::from_millis(300)).wait(); Ok(()) } @@ -377,7 +360,6 @@ where let socket_map = &mut self.socket_map; let udp_listener = &mut self.udp_listener; let wifi_connection = &mut self.wifi_connection; - let ts = self.timer.now(); let mut a = self.urc_attempts; let max = self.config.max_urc_attempts; @@ -422,7 +404,7 @@ where // Check if there are any sockets closed by remote, and close it // if it has exceeded its timeout, in order to recycle it. // TODO Is this correct? - if !sockets.recycle(self.timer.now()) { + if !sockets.recycle() { handled = false; } } @@ -474,7 +456,7 @@ where } IPProtocol::UDP => { // if let Ok(mut udp) = - // sockets.get::>(event.handle) + // sockets.get::>(event.handle) // { // debug!( // "Binding remote {=[u8]:a} to UDP socket {:?}", @@ -500,14 +482,14 @@ where match sockets.socket_type(*handle) { Some(SocketType::Tcp) => { if let Ok(mut tcp) = - sockets.get::>(*handle) + sockets.get::>(*handle) { - tcp.closed_by_remote(ts); + tcp.closed_by_remote(); } } Some(SocketType::Udp) => { if let Ok(mut udp) = - sockets.get::>(*handle) + sockets.get::>(*handle) { udp.close(); } @@ -758,7 +740,7 @@ where Some(SocketType::Tcp) => { // Handle tcp socket let mut tcp = sockets - .get::>(*socket_handle) + .get::>(*socket_handle) .unwrap(); if tcp.can_recv() { tcp.rx_enqueue_slice(&event.data); @@ -770,7 +752,7 @@ where Some(SocketType::Udp) => { // Handle udp socket let mut udp = sockets - .get::>(*socket_handle) + .get::>(*socket_handle) .unwrap(); if udp.can_recv() { diff --git a/ublox-short-range/src/wifi/dns.rs b/src/blocking/dns.rs similarity index 50% rename from ublox-short-range/src/wifi/dns.rs rename to src/blocking/dns.rs index b75efd8..2ee95d8 100644 --- a/ublox-short-range/src/wifi/dns.rs +++ b/src/blocking/dns.rs @@ -1,17 +1,16 @@ -use crate::client::DNSState; +use super::client::{DNSState, UbloxClient}; +use super::timer; +use embassy_time::Duration; use embedded_hal::digital::OutputPin; use embedded_nal::{nb, AddrType, Dns, IpAddr}; -use fugit::ExtU32; use heapless::String; - -use crate::{command::ping::*, UbloxClient}; use ublox_sockets::Error; -impl Dns - for UbloxClient +use crate::{blocking::timer::Timer, command::ping::*}; + +impl Dns for UbloxClient where C: atat::blocking::AtatClient, - CLK: fugit_timer::Timer, RST: OutputPin, { type Error = Error; @@ -31,21 +30,23 @@ where retry_num: 1, }) .map_err(|_| nb::Error::Other(Error::Unaddressable))?; - self.dns_state = DNSState::Resolving; - - let expiration = self.timer.now() + 8.secs(); - while self.dns_state == DNSState::Resolving { - self.spin().map_err(|_| nb::Error::Other(Error::Illegal))?; + self.dns_state = DNSState::Resolving; - if self.timer.now() >= expiration { - return Err(nb::Error::Other(Error::Timeout)); + match Timer::with_timeout(Duration::from_secs(8), || { + if self.spin().is_err() { + return Some(Err(Error::Illegal)); } - } - match self.dns_state { - DNSState::Resolved(ip) => Ok(ip), - _ => Err(nb::Error::Other(Error::Illegal)), + match self.dns_state { + DNSState::Resolving => None, + DNSState::Resolved(ip) => Some(Ok(ip)), + _ => Some(Err(Error::Illegal)), + } + }) { + Ok(ip) => Ok(ip), + Err(timer::Error::Timeout) => Err(nb::Error::Other(Error::Timeout)), + Err(timer::Error::Other(e)) => Err(nb::Error::Other(e)), } } } diff --git a/src/blocking/mod.rs b/src/blocking/mod.rs new file mode 100644 index 0000000..208e4a5 --- /dev/null +++ b/src/blocking/mod.rs @@ -0,0 +1,12 @@ +pub(crate) mod client; +mod dns; +pub mod timer; +pub mod tls; + +#[cfg(feature = "socket-udp")] +pub mod udp_stack; + +#[cfg(feature = "socket-tcp")] +pub mod tcp_stack; + +pub use client::UbloxClient; diff --git a/ublox-short-range/src/wifi/tcp_stack.rs b/src/blocking/tcp_stack.rs similarity index 83% rename from ublox-short-range/src/wifi/tcp_stack.rs rename to src/blocking/tcp_stack.rs index 1c8f7f1..6de3f59 100644 --- a/ublox-short-range/src/wifi/tcp_stack.rs +++ b/src/blocking/tcp_stack.rs @@ -1,9 +1,8 @@ +use super::{client::new_socket_num, UbloxClient}; use crate::{ - client::new_socket_num, command::data_mode::*, command::edm::{EdmAtCmdWrapper, EdmDataCommand}, wifi::peer_builder::PeerUrlBuilder, - UbloxClient, }; use embedded_hal::digital::OutputPin; /// Handles receiving data from sockets @@ -12,13 +11,11 @@ use embedded_nal::{nb, SocketAddr, TcpClientStack}; use ublox_sockets::{Error, SocketHandle, TcpSocket, TcpState}; -use super::EGRESS_CHUNK_SIZE; +use crate::wifi::EGRESS_CHUNK_SIZE; -impl TcpClientStack - for UbloxClient +impl TcpClientStack for UbloxClient where C: atat::blocking::AtatClient, - CLK: fugit_timer::Timer, RST: OutputPin, { type Error = Error; @@ -35,7 +32,7 @@ where if sockets.len() >= sockets.capacity() { // Check if there are any sockets closed by remote, and close it // if it has exceeded its timeout, in order to recycle it. - if sockets.recycle(self.timer.now()) { + if sockets.recycle() { return Err(Error::SocketSetFull); } } @@ -76,7 +73,7 @@ where .sockets .as_mut() .unwrap() - .get::>(*socket) + .get::>(*socket) .map_err(Self::Error::from)?; tcp.set_state(TcpState::WaitingForConnect(remote)); @@ -94,7 +91,7 @@ where .sockets .as_mut() .unwrap() - .get::>(*socket) + .get::>(*socket) .map_err(Self::Error::from)?; tcp.set_state(TcpState::Created); return Err(nb::Error::Other(e)); @@ -110,7 +107,7 @@ where self.sockets .as_mut() .unwrap() - .get::>(*socket) + .get::>(*socket) .map_err(Self::Error::from)? .state(), TcpState::WaitingForConnect(_) @@ -125,7 +122,7 @@ where fn is_connected(&mut self, socket: &Self::TcpSocket) -> Result { self.connected_to_network().map_err(|_| Error::Illegal)?; if let Some(ref mut sockets) = self.sockets { - let tcp = sockets.get::>(*socket)?; + let tcp = sockets.get::>(*socket)?; Ok(tcp.is_connected()) } else { Err(Error::Illegal) @@ -142,7 +139,7 @@ where self.connected_to_network().map_err(|_| Error::Illegal)?; if let Some(ref mut sockets) = self.sockets { let tcp = sockets - .get::>(*socket) + .get::>(*socket) .map_err(|e| nb::Error::Other(e.into()))?; if !tcp.is_connected() { @@ -179,10 +176,10 @@ where self.spin().map_err(|_| nb::Error::Other(Error::Illegal))?; if let Some(ref mut sockets) = self.sockets { // Enable detecting closed socket from receive function - sockets.recycle(self.timer.now()); + sockets.recycle(); let mut tcp = sockets - .get::>(*socket) + .get::>(*socket) .map_err(Self::Error::from)?; Ok(tcp.recv_slice(buffer).map_err(Self::Error::from)?) @@ -196,7 +193,7 @@ where if let Some(ref mut sockets) = self.sockets { defmt::debug!("[TCP] Closing socket: {:?}", socket); // If the socket is not found it is already removed - if let Ok(ref tcp) = sockets.get::>(socket) { + if let Ok(ref tcp) = sockets.get::>(socket) { // If socket is not closed that means a connection excists which has to be closed if !matches!( tcp.state(), @@ -228,26 +225,3 @@ where } } } - -// impl TcpFullStack for UbloxClient -// where -// C: atat::blocking::AtatClient, -// CLK: fugit_timer::Timer, -// RST: OutputPin, -// Generic: TryInto, -// { -// fn bind(&mut self, socket: &mut Self::TcpSocket, local_port: u16) -> Result<(), Self::Error> { -// todo!() -// } - -// fn listen(&mut self, socket: &mut Self::TcpSocket) -> Result<(), Self::Error> { -// todo!() -// } - -// fn accept( -// &mut self, -// socket: &mut Self::TcpSocket, -// ) -> nb::Result<(Self::TcpSocket, SocketAddr), Self::Error> { -// todo!() -// } -// } diff --git a/src/blocking/timer.rs b/src/blocking/timer.rs new file mode 100644 index 0000000..7ddae99 --- /dev/null +++ b/src/blocking/timer.rs @@ -0,0 +1,42 @@ +use embassy_time::{Duration, Instant}; + +pub struct Timer { + expires_at: Instant, +} + +pub enum Error { + Timeout, + Other(E), +} + +impl Timer { + pub fn after(duration: Duration) -> Self { + Self { + expires_at: Instant::now() + duration, + } + } + + pub fn with_timeout(timeout: Duration, mut e: F) -> Result> + where + F: FnMut() -> Option>, + { + let timer = Timer::after(timeout); + + loop { + if let Some(res) = e() { + return res.map_err(Error::Other); + } + if timer.expires_at <= Instant::now() { + return Err(Error::Timeout); + } + } + } + + pub fn wait(self) { + loop { + if self.expires_at <= Instant::now() { + break; + } + } + } +} diff --git a/ublox-short-range/src/wifi/tls.rs b/src/blocking/tls.rs similarity index 94% rename from ublox-short-range/src/wifi/tls.rs rename to src/blocking/tls.rs index 2d0b04d..ed5687f 100644 --- a/ublox-short-range/src/wifi/tls.rs +++ b/src/blocking/tls.rs @@ -1,8 +1,8 @@ +use super::UbloxClient; use crate::{ command::edm::BigEdmAtCmdWrapper, command::security::{types::*, *}, error::Error, - UbloxClient, }; use embedded_hal::digital::OutputPin; use heapless::String; @@ -18,11 +18,9 @@ pub trait TLS { ) -> Result<(), Error>; } -impl TLS - for UbloxClient +impl TLS for UbloxClient where C: atat::blocking::AtatClient, - CLK: fugit_timer::Timer, RST: OutputPin, { /// Importing credentials enabeles their use for all further TCP connections diff --git a/ublox-short-range/src/wifi/udp_stack.rs b/src/blocking/udp_stack.rs similarity index 92% rename from ublox-short-range/src/wifi/udp_stack.rs rename to src/blocking/udp_stack.rs index 1c1a6da..203bada 100644 --- a/ublox-short-range/src/wifi/udp_stack.rs +++ b/src/blocking/udp_stack.rs @@ -1,12 +1,12 @@ +use super::client::new_socket_num; +use super::UbloxClient; use crate::{ - client::new_socket_num, command::data_mode::*, command::{ data_mode::types::{IPVersion, ServerType, UDPBehaviour}, edm::{EdmAtCmdWrapper, EdmDataCommand}, }, wifi::peer_builder::PeerUrlBuilder, - UbloxClient, }; use embedded_hal::digital::OutputPin; use embedded_nal::{nb, SocketAddr, UdpFullStack}; @@ -14,13 +14,11 @@ use embedded_nal::{nb, SocketAddr, UdpFullStack}; use embedded_nal::UdpClientStack; use ublox_sockets::{Error, SocketHandle, UdpSocket, UdpState}; -use super::EGRESS_CHUNK_SIZE; +use crate::wifi::EGRESS_CHUNK_SIZE; -impl UdpClientStack - for UbloxClient +impl UdpClientStack for UbloxClient where C: atat::blocking::AtatClient, - CLK: fugit_timer::Timer, RST: OutputPin, { type Error = Error; @@ -36,7 +34,7 @@ where if sockets.len() >= sockets.capacity() { // Check if there are any sockets closed by remote, and close it // if it has exceeded its timeout, in order to recycle it. - if sockets.recycle(self.timer.now()) { + if sockets.recycle() { return Err(Error::SocketSetFull); } } @@ -77,7 +75,7 @@ where .sockets .as_mut() .unwrap() - .get::>(*socket)?; + .get::>(*socket)?; udp.bind(remote)?; // Then connect modem @@ -95,7 +93,7 @@ where .sockets .as_mut() .unwrap() - .get::>(*socket)?; + .get::>(*socket)?; udp.close(); return Err(e); } @@ -104,7 +102,7 @@ where .sockets .as_mut() .unwrap() - .get::>(*socket)? + .get::>(*socket)? .state() == UdpState::Closed { @@ -123,7 +121,7 @@ where } let udp = sockets - .get::>(*socket) + .get::>(*socket) .map_err(Self::Error::from)?; if !udp.is_open() { @@ -175,7 +173,7 @@ where if let Some(ref mut sockets) = self.sockets { let mut udp = sockets - .get::>(*connection_handle) + .get::>(*connection_handle) .map_err(|_| Self::Error::InvalidSocket)?; let bytes = udp.recv_slice(buffer).map_err(Self::Error::from)?; @@ -187,7 +185,7 @@ where // Handle reciving for udp normal sockets } else if let Some(ref mut sockets) = self.sockets { let mut udp = sockets - .get::>(*socket) + .get::>(*socket) .map_err(Self::Error::from)?; let bytes = udp.recv_slice(buffer).map_err(Self::Error::from)?; @@ -255,14 +253,14 @@ where } else if let Some(ref mut sockets) = self.sockets { defmt::debug!("[UDP] Closing socket: {:?}", socket); // If no sockets exists, nothing to close. - if let Ok(ref mut udp) = sockets.get::>(socket) { + if let Ok(ref mut udp) = sockets.get::>(socket) { defmt::trace!("[UDP] Closing socket state: {:?}", udp.state()); match udp.state() { UdpState::Closed => { sockets.remove(socket).ok(); } UdpState::Established => { - udp.close(); + // FIXME:udp.close(); if let Some(peer_handle) = self.socket_map.socket_to_peer(&udp.handle()) { let peer_handle = *peer_handle; self.send_at(ClosePeerConnection { peer_handle }) @@ -293,11 +291,9 @@ where /// - The driver has to call send_to after reciving data, to release the socket bound by remote host, /// even if just sending no bytes. Else these sockets will be held open until closure of server socket. /// -impl UdpFullStack - for UbloxClient +impl UdpFullStack for UbloxClient where C: atat::blocking::AtatClient, - CLK: fugit_timer::Timer, RST: OutputPin, { fn bind(&mut self, socket: &mut Self::UdpSocket, local_port: u16) -> Result<(), Self::Error> { @@ -352,7 +348,7 @@ where } let udp = sockets - .get::>(connection_socket) + .get::>(connection_socket) .map_err(Self::Error::from)?; if !udp.is_open() { diff --git a/ublox-short-range/src/command/custom_digest.rs b/src/command/custom_digest.rs similarity index 92% rename from ublox-short-range/src/command/custom_digest.rs rename to src/command/custom_digest.rs index 20c4dde..cf6a4c4 100644 --- a/ublox-short-range/src/command/custom_digest.rs +++ b/src/command/custom_digest.rs @@ -103,49 +103,35 @@ impl Digester for EdmDigester { // #[cfg(test)] // mod test { - // use super::*; -// use atat::bbqueue::framed::FrameConsumer; -// use atat::{frame::Frame, AtatIngress, Buffers, Ingress, Response}; +// use atat::Config; +// use atat::{AtatIngress, Buffers, Response, blocking::AtatClient}; // const TEST_RX_BUF_LEN: usize = 256; // const TEST_RES_CAPACITY: usize = 3 * TEST_RX_BUF_LEN; // const TEST_URC_CAPACITY: usize = 3 * TEST_RX_BUF_LEN; -// // macro_rules! setup_ingressmanager { -// // () => {{ -// // static mut RES_Q: BBBuffer = BBBuffer::new(); -// // let (res_p, res_c) = unsafe { RES_Q.try_split_framed().unwrap() }; - -// // static mut URC_Q: BBBuffer = BBBuffer::new(); -// // let (urc_p, urc_c) = unsafe { URC_Q.try_split_framed().unwrap() }; - -// // ( -// // IngressManager::<_, TEST_RX_BUF_LEN, TEST_RES_CAPACITY, TEST_URC_CAPACITY>::new( -// // res_p, -// // urc_p, -// // EdmDigester::default(), -// // ), -// // res_c, -// // urc_c, -// // ) -// // }}; -// // } +// struct MockWriter; + +// impl embedded_io::Io for MockWriter { +// type Error = (); +// } + +// impl embedded_io::blocking::Write for MockWriter { +// fn write(&mut self, buf: &[u8]) -> Result { +// Ok(buf.len()) +// } + +// fn flush(&mut self) -> Result<(), Self::Error> { +// Ok(()) +// } +// } // /// Removed functionality used to change OK responses to empty responses. // #[test] // fn ok_response<'a>() { -// let mut at_pars: Ingress< -// 'a, -// EdmDigester, -// TEST_RX_BUF_LEN, -// TEST_RES_CAPACITY, -// TEST_URC_CAPACITY, -// >; -// let mut res_c: FrameConsumer<'a, TEST_RES_CAPACITY>; -// let mut urc_c: FrameConsumer<'a, TEST_URC_CAPACITY>; // let buf = Buffers::::new(); -// (at_pars, res_c, urc_c) = buf.to_ingress(EdmDigester::default()); +// (at_pars, client) = buf.split_blocking(MockWriter, EdmDigester::default(), Config::new()); // // Payload: "OK\r\n" // let data = &[0xAA, 0x00, 0x06, 0x00, 0x45, 0x4f, 0x4b, 0x0D, 0x0a, 0x55]; @@ -154,7 +140,7 @@ impl Digester for EdmDigester { // let ingress_buf = at_pars.write_buf(); // let len = usize::min(data.len(), ingress_buf.len()); // ingress_buf[..len].copy_from_slice(&data[..len]); -// at_pars.try_advance(len); +// at_pars.try_advance(len).unwrap(); // let mut grant = res_c.read().unwrap(); // grant.auto_release(true); @@ -167,6 +153,7 @@ impl Digester for EdmDigester { // assert_eq!(res, Ok(&empty_ok_response[..])); // assert_eq!(urc_c.read(), None); // } +// } // #[test] // fn error_response() { diff --git a/ublox-short-range/src/command/data_mode/mod.rs b/src/command/data_mode/mod.rs similarity index 98% rename from ublox-short-range/src/command/data_mode/mod.rs rename to src/command/data_mode/mod.rs index 18062e9..5aa156d 100644 --- a/ublox-short-range/src/command/data_mode/mod.rs +++ b/src/command/data_mode/mod.rs @@ -7,8 +7,9 @@ use atat::atat_derive::AtatCmd; use heapless::String; use responses::*; use types::*; +use ublox_sockets::PeerHandle; -use super::{NoResponse, PeerHandle}; +use super::NoResponse; /// 5.1 Enter data mode O /// diff --git a/ublox-short-range/src/command/data_mode/responses.rs b/src/command/data_mode/responses.rs similarity index 96% rename from ublox-short-range/src/command/data_mode/responses.rs rename to src/command/data_mode/responses.rs index 3f64833..0c8832c 100644 --- a/ublox-short-range/src/command/data_mode/responses.rs +++ b/src/command/data_mode/responses.rs @@ -1,7 +1,7 @@ //! Responses for Data Mode -use super::PeerHandle; use atat::atat_derive::AtatResp; use heapless::String; +use ublox_sockets::PeerHandle; /// 5.2 Connect peer +UDCP #[derive(Clone, AtatResp)] diff --git a/ublox-short-range/src/command/data_mode/types.rs b/src/command/data_mode/types.rs similarity index 92% rename from ublox-short-range/src/command/data_mode/types.rs rename to src/command/data_mode/types.rs index 9bbc5f6..6db7c29 100644 --- a/ublox-short-range/src/command/data_mode/types.rs +++ b/src/command/data_mode/types.rs @@ -5,6 +5,7 @@ use heapless::String; use crate::command::OnOff; #[derive(Clone, PartialEq, AtatEnum)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[repr(u8)] pub enum Mode { /// Command mode @@ -19,6 +20,7 @@ pub enum Mode { } #[derive(Clone, PartialEq, AtatEnum)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[repr(u8)] pub enum ConnectScheme { /// Always connected - Keep the peer connected when not in command mode. @@ -38,11 +40,13 @@ pub enum ConnectScheme { Both = 0b110, } #[derive(Clone, PartialEq, AtatEnum)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum ServerConfig { Type(ServerType), Url(String<128>), } #[derive(Clone, PartialEq, AtatEnum)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum ServerType { #[at_arg(value = 0)] Disabled, @@ -63,6 +67,7 @@ pub enum ServerType { } #[derive(Clone, PartialEq, AtatEnum)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[repr(u8)] pub enum Interface { TCP = 1, @@ -75,6 +80,7 @@ pub enum Interface { } #[derive(Clone, PartialEq, AtatEnum)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[repr(u8)] pub enum UDPBehaviour { /// No connect. This will trigger an +UUDPC URC immediately (with @@ -90,6 +96,7 @@ pub enum UDPBehaviour { } #[derive(Clone, PartialEq, AtatEnum)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[repr(u8)] pub enum ImmediateFlush { Disable = 0, @@ -97,6 +104,7 @@ pub enum ImmediateFlush { } #[derive(Clone, PartialEq, AtatEnum)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[repr(u8)] pub enum IPVersion { /// Default @@ -105,6 +113,7 @@ pub enum IPVersion { } #[derive(Clone, PartialEq, AtatEnum)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[repr(u8)] pub enum RemoteConfiguration { Disable = 0, @@ -112,6 +121,7 @@ pub enum RemoteConfiguration { } #[derive(Clone, PartialEq, AtatEnum)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[repr(u8)] pub enum WatchdogSetting { /// SPP (and all SPP based protocols like DUN) write timeout: is the time in @@ -158,6 +168,7 @@ pub enum WatchdogSetting { } #[derive(Clone, PartialEq, AtatEnum)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum PeerConfigParameter { /// Keep remote peer in the command mode /// - Off: Disconnect peers when entering the command mode @@ -218,6 +229,7 @@ pub enum PeerConfigParameter { } #[derive(Debug, Clone, PartialEq, AtatEnum)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[repr(u8)] pub enum ConnectionType { Bluetooth = 1, @@ -236,6 +248,7 @@ pub enum ConnectionType { // } #[derive(Debug, Clone, PartialEq, AtatEnum)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[repr(u8)] pub enum IPProtocol { TCP = 0, diff --git a/ublox-short-range/src/command/data_mode/urc.rs b/src/command/data_mode/urc.rs similarity index 87% rename from ublox-short-range/src/command/data_mode/urc.rs rename to src/command/data_mode/urc.rs index cdda3af..35eb098 100644 --- a/ublox-short-range/src/command/data_mode/urc.rs +++ b/src/command/data_mode/urc.rs @@ -1,12 +1,12 @@ //! Unsolicited responses for Data mode Commands -use crate::command::PeerHandle; - use super::types::*; use atat::atat_derive::AtatResp; use atat::heapless_bytes::Bytes; +use ublox_sockets::PeerHandle; /// 5.10 Peer connected +UUDPC #[derive(Debug, PartialEq, Clone, AtatResp)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct PeerConnected { #[at_arg(position = 0)] pub handle: PeerHandle, @@ -17,12 +17,14 @@ pub struct PeerConnected { // #[at_arg(position = 3)] // pub local_address: IpAddr, #[at_arg(position = 3)] + #[defmt(Debug2Format)] pub local_address: Bytes<40>, #[at_arg(position = 4)] pub local_port: u16, // #[at_arg(position = 5)] // pub remote_address: IpAddr, #[at_arg(position = 5)] + #[defmt(Debug2Format)] pub remote_address: Bytes<40>, #[at_arg(position = 6)] pub remote_port: u16, diff --git a/ublox-short-range/src/command/edm/mod.rs b/src/command/edm/mod.rs similarity index 98% rename from ublox-short-range/src/command/edm/mod.rs rename to src/command/edm/mod.rs index 324b196..64021f1 100644 --- a/ublox-short-range/src/command/edm/mod.rs +++ b/src/command/edm/mod.rs @@ -6,11 +6,12 @@ use core::convert::TryInto; use crate::command::{data_mode, data_mode::ChangeMode}; use crate::command::{NoResponse, Urc}; -use crate::wifi::EGRESS_CHUNK_SIZE; +// use crate::wifi::EGRESS_CHUNK_SIZE; /// Containing EDM structs with custom serialaization and deserilaisation. use atat::AtatCmd; use heapless::Vec; use types::*; +use ublox_sockets::ChannelId; pub(crate) fn calc_payload_len(resp: &[u8]) -> usize { (u16::from_be_bytes(resp[1..3].try_into().unwrap()) & EDM_FULL_SIZE_FILTER) as usize @@ -159,12 +160,12 @@ pub struct EdmDataCommand<'a> { pub data: &'a [u8], } // wifi::socket::EGRESS_CHUNK_SIZE + PAYLOAD_OVERHEAD = 512 + 6 + 1 = 519 -impl<'a> atat::AtatCmd<{ EGRESS_CHUNK_SIZE + 7 }> for EdmDataCommand<'a> { +impl<'a> atat::AtatCmd<{ DATA_PACKAGE_SIZE + 7 }> for EdmDataCommand<'a> { type Response = NoResponse; const EXPECTS_RESPONSE_CODE: bool = false; - fn as_bytes(&self) -> Vec { + fn as_bytes(&self) -> Vec { let payload_len = (self.data.len() + 3) as u16; [ STARTBYTE, diff --git a/ublox-short-range/src/command/edm/types.rs b/src/command/edm/types.rs similarity index 90% rename from ublox-short-range/src/command/edm/types.rs rename to src/command/edm/types.rs index 08f444b..08690cf 100644 --- a/ublox-short-range/src/command/edm/types.rs +++ b/src/command/edm/types.rs @@ -1,28 +1,11 @@ -use atat::atat_derive::AtatLen; -use embedded_nal::{Ipv4Addr, Ipv6Addr}; use heapless::Vec; +use no_std_net::{Ipv4Addr, Ipv6Addr}; use serde::{Deserialize, Serialize}; +use ublox_sockets::ChannelId; /// Start byte, Length: u16, Id+Type: u16, Endbyte // type EdmAtCmdOverhead = (u8, u16, u16, u8); -#[derive( - Debug, - Clone, - Copy, - PartialEq, - Eq, - PartialOrd, - AtatLen, - Ord, - Default, - Serialize, - Deserialize, - defmt::Format, - hash32_derive::Hash32, -)] -pub struct ChannelId(pub u8); - pub const DATA_PACKAGE_SIZE: usize = 4096; pub const STARTBYTE: u8 = 0xAA; pub const ENDBYTE: u8 = 0x55; @@ -98,7 +81,7 @@ pub struct BluetoothConnectEvent { pub frame_size: u16, } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct IPv4ConnectEvent { pub channel_id: ChannelId, pub protocol: Protocol, @@ -107,7 +90,7 @@ pub struct IPv4ConnectEvent { pub local_ip: Ipv4Addr, pub local_port: u16, } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct IPv6ConnectEvent { pub channel_id: ChannelId, pub protocol: Protocol, @@ -117,7 +100,7 @@ pub struct IPv6ConnectEvent { pub local_port: u16, } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct DataEvent { pub channel_id: ChannelId, pub data: Vec, @@ -151,7 +134,7 @@ impl From for ConnectType { } } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[repr(u8)] pub enum Protocol { TCP = 0x00, diff --git a/ublox-short-range/src/command/edm/urc.rs b/src/command/edm/urc.rs similarity index 98% rename from ublox-short-range/src/command/edm/urc.rs rename to src/command/edm/urc.rs index f1d0ed9..bc011a3 100644 --- a/ublox-short-range/src/command/edm/urc.rs +++ b/src/command/edm/urc.rs @@ -3,8 +3,9 @@ use super::types::*; use super::Urc; use atat::helpers::LossyStr; use atat::AtatUrc; -use embedded_nal::{Ipv4Addr, Ipv6Addr}; use heapless::Vec; +use no_std_net::{Ipv4Addr, Ipv6Addr}; +use ublox_sockets::ChannelId; #[derive(Debug, Clone, PartialEq)] pub enum EdmEvent { @@ -166,10 +167,9 @@ impl AtatUrc for EdmEvent { #[cfg(test)] mod test { use super::*; - use crate::command::{ - data_mode::urc::PeerConnected, edm::types::DATA_PACKAGE_SIZE, PeerHandle, Urc, - }; + use crate::command::{data_mode::urc::PeerConnected, edm::types::DATA_PACKAGE_SIZE, Urc}; use atat::{heapless::Vec, heapless_bytes::Bytes, AtatUrc}; + use ublox_sockets::PeerHandle; #[test] fn parse_at_urc() { diff --git a/ublox-short-range/src/command/ethernet/mod.rs b/src/command/ethernet/mod.rs similarity index 100% rename from ublox-short-range/src/command/ethernet/mod.rs rename to src/command/ethernet/mod.rs diff --git a/ublox-short-range/src/command/ethernet/responses.rs b/src/command/ethernet/responses.rs similarity index 100% rename from ublox-short-range/src/command/ethernet/responses.rs rename to src/command/ethernet/responses.rs diff --git a/ublox-short-range/src/command/ethernet/types.rs b/src/command/ethernet/types.rs similarity index 99% rename from ublox-short-range/src/command/ethernet/types.rs rename to src/command/ethernet/types.rs index eca3ffe..8c15549 100644 --- a/ublox-short-range/src/command/ethernet/types.rs +++ b/src/command/ethernet/types.rs @@ -1,7 +1,7 @@ //! Argument and parameter types used by Ethernet Commands and Responses use atat::atat_derive::AtatEnum; -use embedded_nal::Ipv4Addr; +use no_std_net::Ipv4Addr; use crate::command::OnOff; diff --git a/ublox-short-range/src/command/ethernet/urc.rs b/src/command/ethernet/urc.rs similarity index 100% rename from ublox-short-range/src/command/ethernet/urc.rs rename to src/command/ethernet/urc.rs diff --git a/ublox-short-range/src/command/general/mod.rs b/src/command/general/mod.rs similarity index 100% rename from ublox-short-range/src/command/general/mod.rs rename to src/command/general/mod.rs diff --git a/ublox-short-range/src/command/general/responses.rs b/src/command/general/responses.rs similarity index 100% rename from ublox-short-range/src/command/general/responses.rs rename to src/command/general/responses.rs diff --git a/ublox-short-range/src/command/general/types.rs b/src/command/general/types.rs similarity index 100% rename from ublox-short-range/src/command/general/types.rs rename to src/command/general/types.rs diff --git a/ublox-short-range/src/command/gpio/mod.rs b/src/command/gpio/mod.rs similarity index 95% rename from ublox-short-range/src/command/gpio/mod.rs rename to src/command/gpio/mod.rs index a5d4ab5..b73c4f2 100644 --- a/ublox-short-range/src/command/gpio/mod.rs +++ b/src/command/gpio/mod.rs @@ -41,7 +41,7 @@ pub struct ReadGPIO { /// Writes the value of an enabled GPIO pin configured as output. /// Supported by ODIN-W2 from software version 3.0.0 onwards only. #[derive(Clone, AtatCmd)] -#[at_cmd("+UGPIOW", NoResponse, value_sep = false, timeout_ms = 1000)] +#[at_cmd("+UGPIOW", NoResponse, timeout_ms = 1000)] pub struct WriteGPIO { #[at_arg(position = 0)] pub id: GPIOId, diff --git a/ublox-short-range/src/command/gpio/responses.rs b/src/command/gpio/responses.rs similarity index 100% rename from ublox-short-range/src/command/gpio/responses.rs rename to src/command/gpio/responses.rs diff --git a/ublox-short-range/src/command/gpio/types.rs b/src/command/gpio/types.rs similarity index 100% rename from ublox-short-range/src/command/gpio/types.rs rename to src/command/gpio/types.rs diff --git a/ublox-short-range/src/command/mod.rs b/src/command/mod.rs similarity index 89% rename from ublox-short-range/src/command/mod.rs rename to src/command/mod.rs index 67470da..bb12501 100644 --- a/ublox-short-range/src/command/mod.rs +++ b/src/command/mod.rs @@ -13,8 +13,7 @@ pub mod security; pub mod system; pub mod wifi; -use atat::atat_derive::{AtatCmd, AtatEnum, AtatLen, AtatResp, AtatUrc}; -use serde::{Deserialize, Serialize}; +use atat::atat_derive::{AtatCmd, AtatEnum, AtatResp, AtatUrc}; #[derive(Debug, Clone, AtatResp, PartialEq)] pub struct NoResponse; @@ -23,23 +22,6 @@ pub struct NoResponse; #[at_cmd("", NoResponse, timeout_ms = 1000)] pub struct AT; -#[derive( - Debug, - Clone, - Copy, - PartialEq, - Eq, - PartialOrd, - AtatLen, - Ord, - Default, - Serialize, - Deserialize, - defmt::Format, - hash32_derive::Hash32, -)] -pub struct PeerHandle(pub u8); - #[derive(Debug, PartialEq, Clone, AtatUrc)] pub enum Urc { /// Startup Message @@ -91,6 +73,7 @@ pub enum Urc { } #[derive(Clone, PartialEq, AtatEnum)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[repr(u8)] pub enum OnOff { On = 1, diff --git a/ublox-short-range/src/command/network/mod.rs b/src/command/network/mod.rs similarity index 100% rename from ublox-short-range/src/command/network/mod.rs rename to src/command/network/mod.rs diff --git a/ublox-short-range/src/command/network/responses.rs b/src/command/network/responses.rs similarity index 100% rename from ublox-short-range/src/command/network/responses.rs rename to src/command/network/responses.rs diff --git a/ublox-short-range/src/command/network/types.rs b/src/command/network/types.rs similarity index 99% rename from ublox-short-range/src/command/network/types.rs rename to src/command/network/types.rs index d27d7cc..97c8a4a 100644 --- a/ublox-short-range/src/command/network/types.rs +++ b/src/command/network/types.rs @@ -31,7 +31,7 @@ pub enum NetworkStatus { /// 101: The is the currently used IPv4_Addr (omitted if no IP address has /// been acquired). #[at_arg(value = 101)] - IPv4Address(#[at_arg(len = 16)] Bytes<40>), + IPv4Address(#[at_arg(len = 16)] Bytes<16>), /// 102: The is the currently used subnet mask (omitted if no IP address /// has been acquired). #[at_arg(value = 102)] diff --git a/ublox-short-range/src/command/network/urc.rs b/src/command/network/urc.rs similarity index 100% rename from ublox-short-range/src/command/network/urc.rs rename to src/command/network/urc.rs diff --git a/ublox-short-range/src/command/ping/mod.rs b/src/command/ping/mod.rs similarity index 100% rename from ublox-short-range/src/command/ping/mod.rs rename to src/command/ping/mod.rs diff --git a/ublox-short-range/src/command/ping/types.rs b/src/command/ping/types.rs similarity index 100% rename from ublox-short-range/src/command/ping/types.rs rename to src/command/ping/types.rs diff --git a/ublox-short-range/src/command/ping/urc.rs b/src/command/ping/urc.rs similarity index 96% rename from ublox-short-range/src/command/ping/urc.rs rename to src/command/ping/urc.rs index 28231cf..856d63c 100644 --- a/ublox-short-range/src/command/ping/urc.rs +++ b/src/command/ping/urc.rs @@ -1,8 +1,8 @@ //! Responses for Ping Commands use super::types::*; use atat::atat_derive::AtatResp; -use embedded_nal::IpAddr; use heapless::String; +use no_std_net::IpAddr; /// 16.1 Ping command +UPING /// /// The ping command is the common method to know if a remote host is reachable on the Internet. @@ -24,7 +24,6 @@ use heapless::String; /// in another way. #[derive(Debug, PartialEq, Clone, AtatResp)] pub struct PingResponse { - /// Text string that identifies the serial number. #[at_arg(position = 0)] pub retrynum: u32, #[at_arg(position = 1)] diff --git a/ublox-short-range/src/command/security/mod.rs b/src/command/security/mod.rs similarity index 100% rename from ublox-short-range/src/command/security/mod.rs rename to src/command/security/mod.rs diff --git a/ublox-short-range/src/command/security/responses.rs b/src/command/security/responses.rs similarity index 100% rename from ublox-short-range/src/command/security/responses.rs rename to src/command/security/responses.rs diff --git a/ublox-short-range/src/command/security/types.rs b/src/command/security/types.rs similarity index 100% rename from ublox-short-range/src/command/security/types.rs rename to src/command/security/types.rs diff --git a/ublox-short-range/src/command/system/mod.rs b/src/command/system/mod.rs similarity index 100% rename from ublox-short-range/src/command/system/mod.rs rename to src/command/system/mod.rs diff --git a/ublox-short-range/src/command/system/responses.rs b/src/command/system/responses.rs similarity index 100% rename from ublox-short-range/src/command/system/responses.rs rename to src/command/system/responses.rs diff --git a/ublox-short-range/src/command/system/types.rs b/src/command/system/types.rs similarity index 100% rename from ublox-short-range/src/command/system/types.rs rename to src/command/system/types.rs diff --git a/ublox-short-range/src/command/wifi/mod.rs b/src/command/wifi/mod.rs similarity index 100% rename from ublox-short-range/src/command/wifi/mod.rs rename to src/command/wifi/mod.rs diff --git a/ublox-short-range/src/command/wifi/responses.rs b/src/command/wifi/responses.rs similarity index 100% rename from ublox-short-range/src/command/wifi/responses.rs rename to src/command/wifi/responses.rs diff --git a/ublox-short-range/src/command/wifi/types.rs b/src/command/wifi/types.rs similarity index 97% rename from ublox-short-range/src/command/wifi/types.rs rename to src/command/wifi/types.rs index 7e8e2af..5772825 100644 --- a/ublox-short-range/src/command/wifi/types.rs +++ b/src/command/wifi/types.rs @@ -3,8 +3,8 @@ use crate::command::OnOff; use atat::atat_derive::AtatEnum; use atat::heapless_bytes::Bytes; -use embedded_nal::{Ipv4Addr, Ipv6Addr}; use heapless::{String, Vec}; +use no_std_net::{Ipv4Addr, Ipv6Addr}; use serde::Deserialize; #[derive(Clone, PartialEq, AtatEnum)] @@ -444,7 +444,10 @@ pub enum StatusId { /// - 0: Disabled, /// - 1: Disconnected, /// - 2: Connected, - Status = 6, + Status = 3, + /// The is the RSSI value of the current connection; will + /// return-32768, if not connected. + Rssi = 6, /// The is the mobility domain of the last or current /// connection This tag is supported by ODIN-W2 from software version 6.0.0 /// onwards only. @@ -489,8 +492,12 @@ pub enum WifiStatus { /// - 0: Disabled, /// - 1: Disconnected, /// - 2: Connected, - #[at_arg(value = 6)] + #[at_arg(value = 3)] Status(WifiStatusVal), + /// The is the RSSI value of the current connection; will + /// return-32768, if not connected. + #[at_arg(value = 6)] + Rssi(u32), /// The is the mobility domain of the last or current /// connection This tag is supported by ODIN-W2 from software version 6.0.0 /// onwards only. @@ -587,17 +594,35 @@ pub enum WifiConfigParameter { /// onwards RemainOnChannel = 15, /// Station TX rates bit mask where bit masks are defined according to: - /// 0x00000001: Rate 1 Mbps 0x00000002: Rate 2 Mbps 0x00000004: Rate 5.5 - /// Mbps 0x00000008: Rate 11 Mbps 0x00000010: Rate 6 Mbps 0x00000020: Rate 9 - /// Mbps 0x00000040: Rate 12 Mbps 0x00000080: Rate 18 Mbps 0x00000100: Rate - /// 24 Mbps 0x00000200: Rate 36 Mbps 0x00000400: Rate 48 Mbps 0x00000800: - /// Rate 54 Mbps 0x00001000: Rate MCS 0 0x00002000: Rate MCS 1 0x00004000: - /// Rate MCS 2 0x00008000: Rate MCS 3 0x00010000: Rate MCS 4 0x00020000: - /// Rate MCS 5 0x00040000: Rate MCS 6 0x00080000: Rate MCS 7 0x00100000: - /// Rate MCS 8 0x00200000: Rate MCS 9 0x00400000: Rate MCS 10 0x00800000: - /// Rate MCS 11 0x01000000: Rate MCS 12 0x02000000: Rate MCS 13 0x04000000: - /// Rate MCS 14 0x08000000: Rate MCS 15 Default value is 0, which means that - /// all rates are enabled. Supported software versions 7.0.0 onwards + /// - 0x00000001: Rate 1 Mbps + /// - 0x00000002: Rate 2 Mbps + /// - 0x00000004: Rate 5.5 Mbps + /// - 0x00000008: Rate 11 Mbps + /// - 0x00000010: Rate 6 Mbps + /// - 0x00000020: Rate 9 Mbps + /// - 0x00000040: Rate 12 Mbps + /// - 0x00000080: Rate 18 Mbps + /// - 0x00000100: Rate 24 Mbps + /// - 0x00000200: Rate 36 Mbps + /// - 0x00000400: Rate 48 Mbps + /// - 0x00000800: Rate 54 Mbps + /// - 0x00001000: Rate MCS 0 + /// - 0x00002000: Rate MCS 1 + /// - 0x00004000: Rate MCS 2 + /// - 0x00008000: Rate MCS 3 + /// - 0x00010000: Rate MCS 4 + /// - 0x00020000: Rate MCS 5 + /// - 0x00040000: Rate MCS 6 + /// - 0x00080000: Rate MCS 7 + /// - 0x00100000: Rate MCS 8 + /// - 0x00200000: Rate MCS 9 + /// - 0x00400000: Rate MCS 10 + /// - 0x00800000: Rate MCS 11 + /// - 0x01000000: Rate MCS 12 + /// - 0x02000000: Rate MCS 13 + /// - 0x04000000: Rate MCS 14 + /// - 0x08000000: Rate MCS 15 Default value is 0, which means that all rates + /// are enabled. Supported software versions 7.0.0 onwards StationTxRates = 16, /// Station short packet retry limit. Default value is 0x00141414. The /// definition of retry limits are listed below: diff --git a/ublox-short-range/src/command/wifi/urc.rs b/src/command/wifi/urc.rs similarity index 100% rename from ublox-short-range/src/command/wifi/urc.rs rename to src/command/wifi/urc.rs diff --git a/ublox-short-range/src/wifi/connection.rs b/src/connection.rs similarity index 62% rename from ublox-short-range/src/wifi/connection.rs rename to src/connection.rs index b16eb23..e657393 100644 --- a/ublox-short-range/src/wifi/connection.rs +++ b/src/connection.rs @@ -1,4 +1,4 @@ -use crate::wifi::network::{WifiMode, WifiNetwork}; +use crate::network::{WifiMode, WifiNetwork}; #[derive(Debug, Clone, Copy, PartialEq, defmt::Format)] pub enum WiFiState { @@ -8,22 +8,13 @@ pub enum WiFiState { Connected, } -/// Describes whether device is connected to a network and has an IP or not. -/// It is possible to be attached to a network but have no Wifi connection. -#[derive(Debug, Clone, Copy, PartialEq, defmt::Format)] -pub enum NetworkState { - Attached, - AlmostAttached, - Unattached, -} - // Fold into wifi connectivity pub struct WifiConnection { /// Keeps track of connection state on module pub wifi_state: WiFiState, - pub network_state: NetworkState, + pub network_up: bool, pub network: WifiNetwork, - /// Numbre from 0-9. 255 used for unknown + /// Number from 0-9. 255 used for unknown pub config_id: u8, /// Keeps track of activation of the config by driver pub activated: bool, @@ -33,17 +24,13 @@ impl WifiConnection { pub(crate) fn new(network: WifiNetwork, wifi_state: WiFiState, config_id: u8) -> Self { WifiConnection { wifi_state, - network_state: NetworkState::Unattached, + network_up: false, network, config_id, activated: false, } } - pub(crate) fn is_connected(&self) -> bool { - self.network_state == NetworkState::Attached && self.wifi_state == WiFiState::Connected - } - pub fn is_station(&self) -> bool { self.network.mode == WifiMode::Station } diff --git a/ublox-short-range/src/error.rs b/src/error.rs similarity index 95% rename from ublox-short-range/src/error.rs rename to src/error.rs index 2f2aaa9..8fe05fb 100644 --- a/ublox-short-range/src/error.rs +++ b/src/error.rs @@ -13,9 +13,9 @@ pub enum Error { SocketNotFound, SocketNotConnected, MissingSocketSet, - NetworkState(crate::wifi::connection::NetworkState), + // NetworkState(crate::wifi::connection::NetworkState), NoWifiSetup, - WifiState(crate::wifi::connection::WiFiState), + // WifiState(crate::wifi::connection::WiFiState), Socket(ublox_sockets::Error), AT(atat::Error), Busy, @@ -27,8 +27,9 @@ pub enum Error { SocketMemory, SocketMapMemory, Supplicant, - Timer, + Timeout, ShadowStoreBug, + AlreadyConnected, _Unknown, } diff --git a/ublox-short-range/src/hex.rs b/src/hex.rs similarity index 100% rename from ublox-short-range/src/hex.rs rename to src/hex.rs diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..ccf77d6 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,24 @@ +#![cfg_attr(all(not(test), not(feature = "std")), no_std)] +#![cfg_attr(feature = "async", allow(incomplete_features))] +#![cfg_attr(feature = "async", feature(generic_const_exprs))] +#![cfg_attr(feature = "async", feature(async_fn_in_trait))] +#![cfg_attr(feature = "async", feature(impl_trait_projections))] + +#[cfg(feature = "async")] +pub mod asynch; + +#[cfg(feature = "async")] +pub use embedded_nal_async; + +mod connection; +mod network; +mod peer_builder; + +// mod blocking; +mod hex; + +pub use atat; + +pub mod command; +pub mod error; +// pub mod wifi; diff --git a/ublox-short-range/src/wifi/network.rs b/src/network.rs similarity index 76% rename from ublox-short-range/src/wifi/network.rs rename to src/network.rs index df8e532..e980dc9 100644 --- a/ublox-short-range/src/wifi/network.rs +++ b/src/network.rs @@ -25,6 +25,22 @@ pub struct WifiNetwork { pub mode: WifiMode, } +impl WifiNetwork { + pub fn new_station(bssid: Bytes<20>, channel: u8) -> Self { + Self { + bssid, + op_mode: OperationMode::Infrastructure, + ssid: String::new(), + channel, + rssi: 1, + authentication_suites: 0, + unicast_ciphers: 0, + group_ciphers: 0, + mode: WifiMode::Station, + } + } +} + impl TryFrom for WifiNetwork { type Error = WifiError; diff --git a/ublox-short-range/src/wifi/peer_builder.rs b/src/peer_builder.rs similarity index 58% rename from ublox-short-range/src/wifi/peer_builder.rs rename to src/peer_builder.rs index b4dfb62..8334b53 100644 --- a/ublox-short-range/src/wifi/peer_builder.rs +++ b/src/peer_builder.rs @@ -1,16 +1,14 @@ -use crate::{client::SecurityCredentials, error::Error}; +use crate::error::Error; use core::fmt::Write; -/// Handles receiving data from sockets -/// implements TCP and UDP for WiFi client -use embedded_nal::{IpAddr, SocketAddr}; use heapless::String; +use no_std_net::{IpAddr, SocketAddr}; #[derive(Default)] pub(crate) struct PeerUrlBuilder<'a> { hostname: Option<&'a str>, ip_addr: Option, port: Option, - creds: Option, + // creds: Option, local_port: Option, } @@ -20,7 +18,7 @@ impl<'a> PeerUrlBuilder<'a> { Self::default() } - pub fn write_domain(&self, s: &mut String<128>) -> Result<(), Error> { + fn write_domain(&self, s: &mut String) -> Result<(), Error> { let port = self.port.ok_or(Error::Network)?; let addr = self .ip_addr @@ -32,7 +30,7 @@ impl<'a> PeerUrlBuilder<'a> { addr.xor(host).ok_or(Error::Network) } - pub fn udp(&self) -> Result, Error> { + pub fn udp(&self) -> Result, Error> { let mut s = String::new(); write!(&mut s, "udp://").ok(); self.write_domain(&mut s)?; @@ -47,7 +45,7 @@ impl<'a> PeerUrlBuilder<'a> { Ok(s) } - pub fn tcp(&mut self) -> Result, Error> { + pub fn tcp(&mut self) -> Result, Error> { let mut s = String::new(); write!(&mut s, "tcp://").ok(); self.write_domain(&mut s)?; @@ -56,20 +54,20 @@ impl<'a> PeerUrlBuilder<'a> { write!(&mut s, "?").ok(); self.local_port .map(|v| write!(&mut s, "local_port={}&", v).ok()); - self.creds.as_ref().map(|creds| { - creds - .ca_cert_name - .as_ref() - .map(|v| write!(&mut s, "ca={}&", v).ok()); - creds - .c_cert_name - .as_ref() - .map(|v| write!(&mut s, "cert={}&", v).ok()); - creds - .c_key_name - .as_ref() - .map(|v| write!(&mut s, "privKey={}&", v).ok()); - }); + // self.creds.as_ref().map(|creds| { + // creds + // .ca_cert_name + // .as_ref() + // .map(|v| write!(&mut s, "ca={}&", v).ok()); + // creds + // .c_cert_name + // .as_ref() + // .map(|v| write!(&mut s, "cert={}&", v).ok()); + // creds + // .c_key_name + // .as_ref() + // .map(|v| write!(&mut s, "privKey={}&", v).ok()); + // }); // Remove trailing '&' or '?' if no query. s.pop(); @@ -85,27 +83,47 @@ impl<'a> PeerUrlBuilder<'a> { self } + pub fn set_hostname(&mut self, hostname: Option<&'a str>) -> &mut Self { + self.hostname = hostname; + self + } + /// maximum length 64 pub fn ip_addr(&mut self, ip_addr: IpAddr) -> &mut Self { self.ip_addr.replace(ip_addr); self } + pub fn set_ip_addr(&mut self, ip_addr: Option) -> &mut Self { + self.ip_addr = ip_addr; + self + } + /// port number pub fn port(&mut self, port: u16) -> &mut Self { self.port.replace(port); self } - pub fn creds(&mut self, creds: SecurityCredentials) -> &mut Self { - self.creds.replace(creds); + pub fn set_port(&mut self, port: Option) -> &mut Self { + self.port = port; self } + // pub fn creds(&mut self, creds: SecurityCredentials) -> &mut Self { + // self.creds.replace(creds); + // self + // } + pub fn local_port(&mut self, local_port: u16) -> &mut Self { self.local_port.replace(local_port); self } + + pub fn set_local_port(&mut self, local_port: Option) -> &mut Self { + self.local_port = local_port; + self + } } #[cfg(test)] @@ -139,21 +157,21 @@ mod test { assert_eq!(url, "udp://example.org:2000/?local_port=2001"); } - #[test] - fn tcp_certs() { - let url = PeerUrlBuilder::new() - .hostname("example.org") - .port(2000) - .creds(SecurityCredentials { - c_cert_name: Some(heapless::String::from("client.crt")), - ca_cert_name: Some(heapless::String::from("ca.crt")), - c_key_name: Some(heapless::String::from("client.key")), - }) - .tcp() - .unwrap(); - assert_eq!( - url, - "tcp://example.org:2000/?ca=ca.crt&cert=client.crt&privKey=client.key" - ); - } + // #[test] + // fn tcp_certs() { + // let url = PeerUrlBuilder::new() + // .hostname("example.org") + // .port(2000) + // .creds(SecurityCredentials { + // c_cert_name: Some(heapless::String::from("client.crt")), + // ca_cert_name: Some(heapless::String::from("ca.crt")), + // c_key_name: Some(heapless::String::from("client.key")), + // }) + // .tcp() + // .unwrap(); + // assert_eq!( + // url, + // "tcp://example.org:2000/?ca=ca.crt&cert=client.crt&privKey=client.key" + // ); + // } } diff --git a/ublox-short-range/src/wifi/ap.rs b/src/wifi/ap.rs similarity index 97% rename from ublox-short-range/src/wifi/ap.rs rename to src/wifi/ap.rs index 54b0184..5995140 100644 --- a/ublox-short-range/src/wifi/ap.rs +++ b/src/wifi/ap.rs @@ -1,5 +1,5 @@ use crate::{ - client::UbloxClient, + blocking::UbloxClient, command::{ edm::EdmAtCmdWrapper, wifi::{ @@ -23,11 +23,9 @@ use embedded_hal::digital::OutputPin; use super::connection::{WiFiState, WifiConnection}; -impl - UbloxClient +impl UbloxClient where C: AtatClient, - CLK: fugit_timer::Timer, RST: OutputPin, { /// Creates wireless hotspot service for host machine. diff --git a/ublox-short-range/src/wifi/mod.rs b/src/wifi/mod.rs similarity index 93% rename from ublox-short-range/src/wifi/mod.rs rename to src/wifi/mod.rs index 7407c7f..623f2ca 100644 --- a/ublox-short-range/src/wifi/mod.rs +++ b/src/wifi/mod.rs @@ -1,24 +1,15 @@ -use crate::command::PeerHandle; -pub use ublox_sockets::SocketHandle; +pub use ublox_sockets::{PeerHandle, SocketHandle}; use crate::command::edm::types::ChannelId; pub mod ap; pub mod connection; -pub mod dns; pub mod network; pub mod options; pub mod supplicant; -pub mod tls; pub mod peer_builder; -#[cfg(feature = "socket-udp")] -pub mod udp_stack; - -#[cfg(feature = "socket-tcp")] -pub mod tcp_stack; - pub(crate) const EGRESS_CHUNK_SIZE: usize = 512; /// The socket map, keeps mappings between `ublox::sockets`s `SocketHandle`, /// and the modems `PeerHandle` and `ChannelId`. The peer handle is used diff --git a/ublox-short-range/src/wifi/options.rs b/src/wifi/options.rs similarity index 100% rename from ublox-short-range/src/wifi/options.rs rename to src/wifi/options.rs diff --git a/ublox-short-range/src/wifi/supplicant.rs b/src/wifi/supplicant.rs similarity index 100% rename from ublox-short-range/src/wifi/supplicant.rs rename to src/wifi/supplicant.rs diff --git a/ublox-short-range/Cargo.toml b/ublox-short-range/Cargo.toml deleted file mode 100644 index 799beb4..0000000 --- a/ublox-short-range/Cargo.toml +++ /dev/null @@ -1,53 +0,0 @@ -[package] -name = "ublox-short-range-rs" -version = "0.1.1" -authors = ["Mads Andresen "] -description = "Driver crate for u-blox short range devices, implementation follows 'UBX-14044127 - R40'" -readme = "../README.md" -keywords = ["ublox", "wifi", "shortrange", "bluetooth"] -categories = ["embedded", "no-std"] -license = "MIT OR Apache-2.0" -repository = "https://github.com/BlackbirdHQ/ublox-short-range-rs" -edition = "2021" - -[lib] -name = "ublox_short_range" -doctest = false - -[dependencies] -# atat = { version = "0.18.0", features = ["derive", "defmt", "bytes"] } -atat = { git = "https://github.com/BlackbirdHQ/atat", rev = "70283be", features = ["derive", "defmt", "bytes"] } -heapless = { version = "^0.7", features = ["serde", "defmt-impl"] } -no-std-net = { version = "^0.5", features = ["serde"] } -serde = { version = "^1", default-features = false, features = ["derive"] } -ublox-sockets = { version = "0.5", features = ["defmt"] } -hash32 = "^0.2.1" -hash32-derive = "^0.1.0" - -defmt = { version = "0.3" } -embedded-hal = "=1.0.0-alpha.10" -embedded-nal = "0.6.0" -fugit = { version = "0.3", features = ["defmt"] } -fugit-timer = "0.1.2" - -[dev-dependencies] -embedded-io = "0.4" - -[features] -default = ["odin_w2xx", "wifi_ap", "wifi_sta", "socket-udp", "socket-tcp"] - -async = ["atat/async"] - -odin_w2xx = [] -nina_w1xx = [] -nina_b1xx = [] -anna_b1xx = [] -nina_b2xx = [] -nina_b3xx = [] - -socket-tcp = ["ublox-sockets/socket-tcp"] -socket-udp = ["ublox-sockets/socket-udp"] - -wifi_ap = [] -wifi_sta = [] -bluetooth = [] diff --git a/ublox-short-range/src/bluetooth/options.rs b/ublox-short-range/src/bluetooth/options.rs deleted file mode 100644 index e69de29..0000000 diff --git a/ublox-short-range/src/config.rs b/ublox-short-range/src/config.rs deleted file mode 100644 index ccba3a5..0000000 --- a/ublox-short-range/src/config.rs +++ /dev/null @@ -1,104 +0,0 @@ -use embedded_hal::digital::{ErrorType, OutputPin}; -use heapless::String; - -pub struct NoPin; - -impl ErrorType for NoPin { - type Error = core::convert::Infallible; -} - -impl OutputPin for NoPin { - fn set_low(&mut self) -> Result<(), Self::Error> { - Ok(()) - } - - fn set_high(&mut self) -> Result<(), Self::Error> { - Ok(()) - } -} - -#[derive(Debug)] -pub struct Config { - pub(crate) rst_pin: Option, - pub(crate) hostname: Option>, - pub(crate) tls_in_buffer_size: Option, - pub(crate) tls_out_buffer_size: Option, - pub(crate) max_urc_attempts: u8, - pub(crate) network_up_bug: bool, -} - -impl Default for Config { - fn default() -> Self { - Config { - rst_pin: None, - hostname: None, - tls_in_buffer_size: None, - tls_out_buffer_size: None, - max_urc_attempts: 5, - network_up_bug: true, - } - } -} - -impl Config -where - RST: OutputPin, -{ - pub fn new() -> Self { - Config { - rst_pin: None, - hostname: None, - tls_in_buffer_size: None, - tls_out_buffer_size: None, - max_urc_attempts: 5, - network_up_bug: true, - } - } - - pub fn with_rst(self, rst_pin: RST) -> Self { - Config { - rst_pin: Some(rst_pin), - ..self - } - } - - pub fn with_hostname(self, hostname: &str) -> Self { - Config { - hostname: Some(String::from(hostname)), - ..self - } - } - - pub fn max_urc_attempts(self, max_attempts: u8) -> Self { - Config { - max_urc_attempts: max_attempts, - ..self - } - } - - /// Experimental use of undocumented setting for TLS buffers - /// - /// For Odin: - /// Minimum is 512 and maximum is 16K (16384). - /// DEFAULT_TLS_IN_BUFFER_SIZE (7800) - pub fn tls_in_buffer_size(self, bytes: u16) -> Self { - assert!(bytes > 512); - Config { - tls_in_buffer_size: Some(bytes), - ..self - } - } - - /// Experimental use of undocumented setting for TLS buffers - /// - /// For Odin: - /// Minimum is 512 and maximum is 16K (16384). - /// DEFAULT_TLS_OUT_BUFFER_SIZE (3072) - pub fn tls_out_buffer_size(self, bytes: u16) -> Self { - assert!(bytes > 512); - Config { - tls_out_buffer_size: Some(bytes), - ..self - } - } -} diff --git a/ublox-short-range/src/lib.rs b/ublox-short-range/src/lib.rs deleted file mode 100644 index b8305db..0000000 --- a/ublox-short-range/src/lib.rs +++ /dev/null @@ -1,18 +0,0 @@ -#![cfg_attr(not(test), no_std)] - -mod client; -mod hex; - -pub use atat; -pub use client::UbloxClient; - -pub mod command; -pub mod config; -pub mod error; -pub mod wifi; - -#[cfg(test)] -mod test_helper; - -#[cfg(any(feature = "socket-udp", feature = "socket-tcp"))] -pub use wifi::tls::TLS; diff --git a/ublox-short-range/src/test_helper.rs b/ublox-short-range/src/test_helper.rs deleted file mode 100644 index f370d06..0000000 --- a/ublox-short-range/src/test_helper.rs +++ /dev/null @@ -1,22 +0,0 @@ -//! This module is required in order to satisfy the requirements of defmt, while running tests. -//! Note that this will cause all log `defmt::` log statements to be thrown away. - -#[defmt::global_logger] -struct Logger; - -unsafe impl defmt::Logger for Logger { - fn acquire() {} - - unsafe fn flush() {} - - unsafe fn release() {} - - unsafe fn write(_bytes: &[u8]) {} -} - -defmt::timestamp!(""); - -#[export_name = "_defmt_panic"] -fn panic() -> ! { - panic!() -}