From 760183415d77725f11c4ba116071d65ddada1f77 Mon Sep 17 00:00:00 2001 From: Tamas Domok Date: Fri, 9 Jan 2026 17:59:07 +0100 Subject: [PATCH] project: probe-rs and some initial tests on hardware. - removed usb_logger and env_logger - added some URC parsing (call indication, sms indication) - added some SMS commands --- README.md | 16 +- pico/Cargo.lock | 220 ++----------------------- pico/README.md | 42 +++++ pico/app/.cargo/config.toml | 4 +- pico/app/Cargo.toml | 5 +- pico/app/src/main.rs | 163 +++++++++++++------ pico/pico-lib/Cargo.toml | 6 +- pico/pico-lib/src/at.rs | 20 +-- pico/pico-lib/src/battery.rs | 8 +- pico/pico-lib/src/call.rs | 108 ++++++++++++- pico/pico-lib/src/gps.rs | 293 ++++++++++++++++++++++++---------- pico/pico-lib/src/gsm.rs | 79 ++++----- pico/pico-lib/src/location.rs | 3 +- pico/pico-lib/src/network.rs | 81 +++++----- pico/pico-lib/src/poro.rs | 22 +-- pico/pico-lib/src/sms.rs | 215 +++++++++++++++++++++++-- pico/pico-lib/src/urc.rs | 37 ++++- pico/pico-lib/src/utils.rs | 20 ++- 18 files changed, 853 insertions(+), 489 deletions(-) diff --git a/README.md b/README.md index 25c26f4..61bc2f6 100644 --- a/README.md +++ b/README.md @@ -47,18 +47,30 @@ Building the uf2 file: ```shell cd pico/app + +# default run is with probe-rs, uf2 can also be generated, see .cargo/config.yaml cargo run + +# attach to the app +probe-rs attach --chip RP2040 ../target/thumbv6m-none-eabi/debug/tATA-pico ``` Running the tests, etc: ```shell cd pico/pico-lib + +# run the tests cargo test + +# analyze cargo check + +# format cargo fmt -# run a specific test with env logger -RUST_LOG=debug cargo test test_call_number -- --nocapture + +# fun a specific test case +RUST_BACKTRACE=1 cargo test test_call_number -- --nocapture ``` Reading the logs: diff --git a/pico/Cargo.lock b/pico/Cargo.lock index b554734..f8b65c8 100644 --- a/pico/Cargo.lock +++ b/pico/Cargo.lock @@ -2,18 +2,6 @@ # It is not intended for manual editing. version = 4 -[[package]] -name = "ahash" -version = "0.8.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" -dependencies = [ - "cfg-if", - "once_cell", - "version_check", - "zerocopy", -] - [[package]] name = "aho-corasick" version = "1.1.4" @@ -45,6 +33,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f32b6cfb3965b910406a5ee0dab42c604da8b52964baf85d297b3cefc35dac7e" dependencies = [ "atat_derive", + "defmt 0.3.100", "embassy-futures", "embassy-sync 0.6.2", "embassy-time 0.4.0", @@ -53,7 +42,6 @@ dependencies = [ "futures", "heapless 0.8.0", "heapless-bytes 0.4.0", - "log", "nom", "serde_at", "serde_bytes", @@ -80,17 +68,6 @@ dependencies = [ "critical-section", ] -[[package]] -name = "atty" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" -dependencies = [ - "hermit-abi", - "libc", - "winapi", -] - [[package]] name = "autocfg" version = "1.5.0" @@ -139,12 +116,6 @@ version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46afbd2983a5d5a7bd740ccb198caf5b82f45c40c09c0eed36052d91cb92e719" -[[package]] -name = "bitfield" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d7e60934ceec538daadb9d8432424ed043a904d8e0243f3c6446bce549a46ac" - [[package]] name = "bitflags" version = "1.3.2" @@ -213,7 +184,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ec610d8f49840a5b376c69663b6369e71f4b34484b9b2eb29fb918d92516cb9" dependencies = [ "bare-metal", - "bitfield 0.13.2", + "bitfield", "embedded-hal 0.2.7", "volatile-register", ] @@ -462,23 +433,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "embassy-net-driver" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524eb3c489760508f71360112bca70f6e53173e6fe48fc5f0efd0f5ab217751d" - -[[package]] -name = "embassy-net-driver-channel" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7b2739fbcf6cd206ae08779c7d709087b16577d255f2ea4a45bc4bbbf305b3f" -dependencies = [ - "embassy-futures", - "embassy-net-driver", - "embassy-sync 0.7.2", -] - [[package]] name = "embassy-rp" version = "0.9.0" @@ -599,23 +553,6 @@ dependencies = [ "heapless 0.8.0", ] -[[package]] -name = "embassy-usb" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc4462e48b19a4f401a11901bdd981aab80c6a826608016a0bdc73cbbab31954" -dependencies = [ - "defmt 1.0.1", - "embassy-futures", - "embassy-net-driver-channel", - "embassy-sync 0.7.2", - "embassy-usb-driver", - "embedded-io-async", - "heapless 0.8.0", - "ssmarshal", - "usbd-hid", -] - [[package]] name = "embassy-usb-driver" version = "0.2.0" @@ -626,18 +563,6 @@ dependencies = [ "embedded-io-async", ] -[[package]] -name = "embassy-usb-logger" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "deed6d36715838d6adbbff13b215b03a9deeaa66a64d5fccd6353708ccfb8b8f" -dependencies = [ - "embassy-futures", - "embassy-sync 0.7.2", - "embassy-usb", - "log", -] - [[package]] name = "embedded-alloc" version = "0.6.0" @@ -690,6 +615,9 @@ name = "embedded-io" version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" +dependencies = [ + "defmt 0.3.100", +] [[package]] name = "embedded-io-async" @@ -697,6 +625,7 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ff09972d4073aa8c299395be75161d582e7629cd663171d62af73c8d50dba3f" dependencies = [ + "defmt 0.3.100", "embedded-io", ] @@ -724,25 +653,6 @@ dependencies = [ "log", ] -[[package]] -name = "encode_unicode" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" - -[[package]] -name = "env_logger" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a12e6657c4c97ebab115a42dcee77225f7f482cdd841cf7088c657a42e9e00e7" -dependencies = [ - "atty", - "humantime", - "log", - "regex", - "termcolor", -] - [[package]] name = "equivalent" version = "1.0.2" @@ -879,15 +789,6 @@ dependencies = [ "byteorder", ] -[[package]] -name = "hashbrown" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" -dependencies = [ - "ahash", -] - [[package]] name = "hashbrown" version = "0.16.1" @@ -913,6 +814,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" dependencies = [ + "defmt 0.3.100", "hash32 0.3.1", "serde", "stable_deref_trait", @@ -939,21 +841,6 @@ dependencies = [ "serde", ] -[[package]] -name = "hermit-abi" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" -dependencies = [ - "libc", -] - -[[package]] -name = "humantime" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424" - [[package]] name = "ident_case" version = "1.0.1" @@ -967,7 +854,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" dependencies = [ "equivalent", - "hashbrown 0.16.1", + "hashbrown", ] [[package]] @@ -1152,12 +1039,6 @@ dependencies = [ "syn 2.0.111", ] -[[package]] -name = "once_cell" -version = "1.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" - [[package]] name = "panic-probe" version = "1.0.0" @@ -1226,11 +1107,10 @@ name = "pico-lib" version = "0.1.0" dependencies = [ "atat", + "defmt 1.0.1", "embedded-alloc", - "env_logger", "fasttime", "libm", - "log", "machine-derive", "tokio", ] @@ -1622,16 +1502,6 @@ dependencies = [ "lock_api", ] -[[package]] -name = "ssmarshal" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3e6ad23b128192ed337dfa4f1b8099ced0c2bf30d61e551b65fda5916dbb850" -dependencies = [ - "encode_unicode", - "serde", -] - [[package]] name = "stable_deref_trait" version = "1.2.1" @@ -1715,10 +1585,7 @@ dependencies = [ "embassy-rp", "embassy-sync 0.6.2", "embassy-time 0.5.0", - "embassy-usb", - "embassy-usb-logger", "embedded-alloc", - "log", "panic-probe", "pico-lib", "portable-atomic", @@ -1815,53 +1682,6 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" -[[package]] -name = "usb-device" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98816b1accafbb09085168b90f27e93d790b4bfa19d883466b5e53315b5f06a6" -dependencies = [ - "heapless 0.8.0", - "portable-atomic", -] - -[[package]] -name = "usbd-hid" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6f291ab53d428685cc780f08a2eb9d5d6ff58622db2b36e239a4f715f1e184c" -dependencies = [ - "serde", - "ssmarshal", - "usb-device", - "usbd-hid-macros", -] - -[[package]] -name = "usbd-hid-descriptors" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee54712c5d778d2fb2da43b1ce5a7b5060886ef7b09891baeb4bf36910a3ed" -dependencies = [ - "bitfield 0.14.0", -] - -[[package]] -name = "usbd-hid-macros" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb573c76e7884035ac5e1ab4a81234c187a82b6100140af0ab45757650ccda38" -dependencies = [ - "byteorder", - "hashbrown 0.13.2", - "log", - "proc-macro2", - "quote", - "serde", - "syn 1.0.109", - "usbd-hid-descriptors", -] - [[package]] name = "vcell" version = "0.1.3" @@ -1905,22 +1725,6 @@ version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - [[package]] name = "winapi-util" version = "0.1.11" @@ -1930,12 +1734,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - [[package]] name = "windows-link" version = "0.2.1" diff --git a/pico/README.md b/pico/README.md index 101376c..1edb64c 100644 --- a/pico/README.md +++ b/pico/README.md @@ -9,3 +9,45 @@ This project uses [atat](https://github.com/FactbirdHQ/atat/) a no_std crate for - [SIM800_Series_GSM_Location_Application_Note_V1.03.pdf](https://www.waveshare.com/wiki/File:SIM800_Series_GSM_Location_Application_Note_V1.03.pdf) - [SIM868_Series_Hardware_Design_V1.07.pdf](https://www.waveshare.com/wiki/File:SIM868_Series_Hardware_Design_V1.07.pdf) - [Sim868](https://www.simcom.com/product/SIM868.html) + +### Terminology + +1.3 Conventions and abbreviations + +GSM engines: + +- ME (Mobile Equipment) +- MS (Mobile Station) +- TA (Terminal Adapter) +- DCE (Data Communication Equipment) + +Controlling device: + +- TE (Terminal Equipment) +- DTE (Data Terminal Equipment) + +### AT commands syntax + +Command: `AT` +Response: `` + +1.14.1 Basic syntax + +`AT` or `AT&` where `` is the command `` is the parameter. + +1.14.2 S parameter syntax + +`ATS=` where `` is the index of S register set, `` is the value. + +1.14.3 Extended syntax + +- Test: `AT+=?` +- Read: `AT+?` +- Write: `AT+=<...>` +- Execute: `AT+` + +1.7.1 Parameter Saving mod + +- NO_SAVE: parameter is lost on reboot or no parameter. +- AUTO_SAVE: parameter is kept in NVRAM, won't be lost on reboot. +- AT&W_SAVE: parameter is kept in NVRAM, won't be lost on reboot. Use `AT&W` command. diff --git a/pico/app/.cargo/config.toml b/pico/app/.cargo/config.toml index 4a8fa92..13fa342 100644 --- a/pico/app/.cargo/config.toml +++ b/pico/app/.cargo/config.toml @@ -1,6 +1,6 @@ [target.'cfg(all(target_arch = "arm", target_os = "none"))'] -#runner = "probe-rs run --chip RP2040" -runner = "elf2uf2-rs" +runner = "probe-rs run --chip RP2040" +#runner = "elf2uf2-rs" [build] target = "thumbv6m-none-eabi" diff --git a/pico/app/Cargo.toml b/pico/app/Cargo.toml index 6362051..5b8775d 100644 --- a/pico/app/Cargo.toml +++ b/pico/app/Cargo.toml @@ -24,9 +24,7 @@ embassy-rp = { version = "0.9.0", features = [ "critical-section-impl", "rp2040", ] } -embassy-usb = { version = "0.5.1", features = ["defmt"] } embassy-futures = { version = "0.1.2" } -embassy-usb-logger = { version = "0.5.1" } embedded-alloc = "0.6.0" @@ -36,9 +34,8 @@ critical-section = "1.1" panic-probe = "1.0.0" defmt = "1.0.1" defmt-rtt = "1.0.0" -log = "0.4" -atat = { version = "0.24.1", features = ["log", "heapless"] } +atat = { version = "0.24.1", features = ["defmt", "heapless"] } static_cell = { version = "2" } portable-atomic = { version = "1.6.0", features = ["critical-section"] } diff --git a/pico/app/src/main.rs b/pico/app/src/main.rs index 83edb17..d82247e 100644 --- a/pico/app/src/main.rs +++ b/pico/app/src/main.rs @@ -5,13 +5,13 @@ use alloc::string::ToString; use atat::asynch::Client; use atat::{AtatIngress, DefaultDigester, Ingress, ResponseSlot, UrcChannel}; use core::ptr::addr_of_mut; +use defmt::*; use embassy_executor::Spawner; use embassy_rp::adc::{Adc, Channel, Config, InterruptHandler as AdcInterruptHandler}; use embassy_rp::bind_interrupts; use embassy_rp::gpio::{Level, Output, Pull}; -use embassy_rp::peripherals::{UART0, USB}; +use embassy_rp::peripherals::UART0; use embassy_rp::uart::{self, BufferedInterruptHandler, BufferedUart, BufferedUartRx}; -use embassy_rp::usb::{Driver, InterruptHandler as UsbInterruptHandler}; use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; use embassy_sync::pubsub; use embassy_sync::pubsub::Subscriber; @@ -37,26 +37,28 @@ const URC_SUBSCRIBERS: usize = 3; bind_interrupts!(struct Irqs { UART0_IRQ => BufferedInterruptHandler; - USBCTRL_IRQ => UsbInterruptHandler; ADC_IRQ_FIFO => AdcInterruptHandler; }); #[embassy_executor::main] async fn main(spawner: Spawner) { + info!("STARTING"); { use core::mem::MaybeUninit; - const HEAP_SIZE: usize = 1280; + const HEAP_SIZE: usize = 4096; static mut HEAP_MEM: [MaybeUninit; HEAP_SIZE] = [MaybeUninit::uninit(); HEAP_SIZE]; unsafe { HEAP.init(addr_of_mut!(HEAP_MEM) as usize, HEAP_SIZE) } } - let p = embassy_rp::init(Default::default()); - let driver = Driver::new(p.USB, Irqs); - spawner.spawn(logger_task(driver)).unwrap(); + Timer::after(Duration::from_secs(2)).await; + info!("STARTED"); - Timer::after(Duration::from_millis(500)).await; - log::info!("USB Logger Task spawned"); + let mut pico = Pico { + led: Output::new(p.PIN_25, Level::Low), + power: Output::new(p.PIN_14, Level::Low), + }; + // This is just a Test will be removed later. let pm = poro::ProtectorMachine {}; let dumped = pm.dump(&poro::Protector { car_location: Some(poro::CarLocation { @@ -78,7 +80,7 @@ async fn main(spawner: Spawner) { status: Some(poro::Status::CarTheftDetected), service: Some(poro::Service { value: true }), }); - log::info!("dumped {}", dumped); + info!("PORO TEST: {}", dumped.as_str()); let mut adc = Adc::new(p.ADC, Irqs, Config::default()); let mut p26 = Channel::new_pin(p.PIN_26, Pull::None); @@ -87,15 +89,15 @@ async fn main(spawner: Spawner) { let (tx_pin, rx_pin, uart) = (p.PIN_0, p.PIN_1, p.UART0); static INGRESS_BUF: StaticCell<[u8; INGRESS_BUF_SIZE]> = StaticCell::new(); - static TX_BUF: StaticCell<[u8; 16]> = StaticCell::new(); - static RX_BUF: StaticCell<[u8; 16]> = StaticCell::new(); + static TX_BUF: StaticCell<[u8; 256]> = StaticCell::new(); + static RX_BUF: StaticCell<[u8; 256]> = StaticCell::new(); let uart = BufferedUart::new( uart, tx_pin, rx_pin, Irqs, - TX_BUF.init([0; 16]), - RX_BUF.init([0; 16]), + TX_BUF.init([0; 256]), + RX_BUF.init([0; 256]), uart::Config::default(), ); let (writer, reader) = uart.split(); @@ -117,24 +119,25 @@ async fn main(spawner: Spawner) { ); Timer::after(Duration::from_millis(500)).await; - log::info!("Before spawning reader Task"); + info!("Before spawning reader Task"); spawner.spawn(ingress_task(ingress, reader)).unwrap(); Timer::after(Duration::from_millis(500)).await; - log::info!("After spawning reader Task"); + info!("After spawning reader Task"); let sub = URC_CHANNEL.subscribe().unwrap(); spawner.spawn(urc_handler_task(sub)).unwrap(); - log::info!("After spawning Urc Task"); + info!("After spawning Urc Task"); Timer::after(Duration::from_secs(2)).await; - let mut pico = Pico { - led: Output::new(p.PIN_25, Level::Low), - power: Output::new(p.PIN_14, Level::Low), - }; + pico.restart_module().await; + info!("Network init"); + Timer::after(Duration::from_secs(2)).await; network::init_network(&mut client, &mut pico).await; + sms::init(&mut client, &mut pico).await; + call::init(&mut client, &mut pico).await; for _ in 0..30 { pico.set_led_high(); @@ -150,37 +153,41 @@ async fn main(spawner: Spawner) { ) .await { - Ok(v) => log::info!(" {:?}", v), + Ok(v) => info!(" {:?}", v), Err(_) => (), } - match gps::get_gps_location(&mut client, &mut pico, 30).await { - Some(v) => log::info!("GPS location: {:?}", v), + match gps::get_gps_location(&mut client, &mut pico, 10).await { + Some(v) => info!("GPS location: {:?}", v), None => (), } - match gsm::get_gsm_location(&mut client, &mut pico, 30, "online").await { - Some(v) => log::info!("GSM location: {:?}", v), + match gsm::get_gsm_location(&mut client, &mut pico, 10, "online").await { + Some(v) => info!("GSM location: {:?}", v), None => (), } + const PHONE_NUMBER: &'static str = "+36301234567"; + call::call_number( &mut client, &mut pico, - "+36301234567", - Duration::from_secs(6).as_millis(), + PHONE_NUMBER, + Duration::from_secs(10).as_millis(), ) .await; sms::send_sms( &mut client, &mut pico, - "+36301234567", + PHONE_NUMBER, "this is a text message", ) .await; - let mut counter = 0u8; + sms::receive_sms(&mut client, &mut pico).await; + + let mut counter = 0u64; loop { pico.set_led_high(); Timer::after(Duration::from_millis(500)).await; @@ -189,11 +196,9 @@ async fn main(spawner: Spawner) { let level = adc.read(&mut p26).await.unwrap(); let temp = convert_to_celsius(adc.read(&mut ts).await.unwrap()); - log::info!( + info!( "Tick counter: {} Pin 26 ADC: {} Temp: {}", - counter, - level, - temp + counter, level, temp ); pico.set_led_low(); @@ -213,7 +218,7 @@ async fn ingress_task( >, mut reader: BufferedUartRx, ) -> ! { - log::info!("ingress task spawned..."); + info!("INGRESS TASK SPAWNED"); ingress.read_from(&mut reader).await } @@ -228,33 +233,70 @@ async fn urc_handler_task( 1, >, ) -> ! { - log::info!("Ucr Handler task spawned..."); + info!("URC TASK SPAWNED"); loop { let m = sub.next_message().await; match m { pubsub::WaitResult::Message(u) => match u { urc::Urc::CallReady => { - log::info!("URC CallReady"); + info!("URC CallReady"); } urc::Urc::SMSReady => { - log::info!("URC SMSReady"); + info!("URC SMSReady"); + } + urc::Urc::SetBearer(_v) => { + info!("URC SetBearer"); + } + urc::Urc::GprsDisconnected(_v) => { + info!("URC GprsDisconnected"); + } + urc::Urc::Ring => { + info!("URC Ring"); + } + urc::Urc::NormalPowerDown => { + info!("URC NormalPowerDown"); + } + urc::Urc::UnderVoltagePowerDown => { + info!("URC UnderVoltagePowerDown"); + } + urc::Urc::UnderVoltageWarning => { + info!("URC UnderVoltageWarning"); + } + urc::Urc::OverVoltagePowerDown => { + info!("URC OverVoltagePowerDown"); } - urc::Urc::SetBearer(v) => { - log::info!("SetBearer {:?}", v); + urc::Urc::OverVoltageWarning => { + info!("URC OverVoltageWarning"); + } + urc::Urc::ChargeOnlyMode => { + info!("URC ChargeOnlyMode"); + } + urc::Urc::Ready => { + info!("URC Ready"); + } + urc::Urc::ConnectOK1 => { + info!("URC ConnectOK1"); + } + urc::Urc::ConnectOK => { + info!("URC ConnectOK"); + } + urc::Urc::ClipUrc(v) => { + info!("URC ClipUrc number={}, type={}", v.number, v.type_); + } + urc::Urc::NewMessageIndicationUrc(v) => { + info!( + "URC NewMessageIndicationUrc index={} mem={}", + v.index, v.mem + ); } }, pubsub::WaitResult::Lagged(b) => { - log::info!("Urc Lagged messages: {}", b); + info!("Urc Lagged messages: {}", b); } } } } -#[embassy_executor::task] -async fn logger_task(driver: Driver<'static, USB>) { - embassy_usb_logger::run!(1024, log::LevelFilter::Trace, driver); -} - fn convert_to_celsius(raw_temp: u16) -> f32 { // According to chapter 4.9.5. Temperature Sensor in RP2040 datasheet let temp = 27.0 - (raw_temp as f32 * 3.3 / 4096.0 - 0.706) / 0.001721; @@ -281,13 +323,36 @@ impl at::PicoHW for Pico<'_> { self.led.set_low(); } - async fn power_on_off(&mut self) { + async fn restart_module(&mut self) { + info!("Sim868 restart procedure"); + self.led.set_high(); - log::info!("power on"); + info!("Sim868 power off"); self.power.set_high(); + // Customer can power off GSM by pulling down the PWRKEY pin for at least 1.5 second and release. Timer::after_secs(2).await; self.power.set_low(); - log::info!("power off"); self.led.set_low(); + + Timer::after_secs(1).await; + self.led.set_high(); + info!("Sim868 power on"); + self.power.set_high(); + // Customer can power on GSM by pulling down the PWRKEY pin for at least 1 second and then release. + Timer::after_secs(1).await; + self.power.set_low(); + self.led.set_low(); + + Timer::after_secs(2).await; + info!("Sim868 should be Ready"); + + // Power off GSM by AT command “AT+CPOWD=1”. + + // The GSM will restart after pulling the PWRKEY over 33 seconds. + + // Customer can use AT command “AT+IPR=x” to set a fixed baud rate and save the configuration to + // non-volatile flash memory. After the configuration is saved as fixed baud rate, the Code “RDY” should be + // received from the serial port every time when SIM868 is powered on. For details, please refer to the chapter + // “AT+IPR” in document [1] } } diff --git a/pico/pico-lib/Cargo.toml b/pico/pico-lib/Cargo.toml index 1f1909b..b50f38e 100644 --- a/pico/pico-lib/Cargo.toml +++ b/pico/pico-lib/Cargo.toml @@ -8,10 +8,8 @@ machine-derive = { version = "0.1.0", path = "../machine-derive" } embedded-alloc = "0.6.0" libm = "0.2.11" fasttime = { version = "0.1", default-features = false } -atat = { version = "0.24.1", features = ["log", "heapless"] } - -log = "0.4" +atat = { version = "0.24.1", features = ["defmt", "heapless"] } +defmt = "1.0.1" [dev-dependencies] tokio = { version = "1", features = ["full"] } -env_logger = { version = "0.9.1" } diff --git a/pico/pico-lib/src/at.rs b/pico/pico-lib/src/at.rs index 4baa8f1..d57eba0 100644 --- a/pico/pico-lib/src/at.rs +++ b/pico/pico-lib/src/at.rs @@ -1,13 +1,14 @@ use atat::atat_derive::AtatResp; +use defmt::Format; -#[derive(Debug, Clone, AtatResp)] +#[derive(Debug, Format, Clone, AtatResp)] pub struct NoResponse; pub trait PicoHW { fn sleep(&mut self, millis: u64) -> impl core::future::Future + Send; fn set_led_high(&mut self); fn set_led_low(&mut self); - fn power_on_off(&mut self) -> impl core::future::Future + Send; + fn restart_module(&mut self) -> impl core::future::Future + Send; } #[cfg(test)] @@ -17,7 +18,6 @@ extern crate std; pub mod tests { use alloc::string::String as AString; use alloc::{collections::vec_deque::VecDeque, vec::Vec as AVec}; - use atat::heapless::String; use atat::{AtatCmd, heapless::Vec}; use crate::at::PicoHW; @@ -40,7 +40,7 @@ pub mod tests { let mut buffer = crate::at::tests::zeros(); assert_eq!(text.len(), cmd.write(&mut buffer)); assert_eq!( - String::from_utf8(buffer) + atat::heapless::String::from_utf8(buffer) .unwrap() .trim_matches(char::from(0)), text @@ -50,10 +50,6 @@ pub mod tests { } } - pub fn init_env_logger() { - let _ = env_logger::builder().is_test(true).try_init(); - } - #[derive(Default)] pub struct ClientMock<'a> { pub sent_commands: VecDeque, @@ -64,7 +60,7 @@ pub mod tests { async fn send(&mut self, cmd: &Cmd) -> Result { let mut buffer = crate::at::tests::zeros(); cmd.write(&mut buffer); - let tmp = String::from_utf8(buffer).unwrap(); + let tmp = atat::heapless::String::from_utf8(buffer).unwrap(); let trimmed = tmp.trim_matches(char::from(0)); self.sent_commands.push_back(AString::from(trimmed)); cmd.parse(self.results.pop_front().expect("missing result")) @@ -76,7 +72,7 @@ pub mod tests { pub sleep_calls: AVec, pub set_led_high_calls: u32, pub set_led_low_calls: u32, - pub set_power_on_off_calls: u32, + pub restart_module_calls: u32, } impl PicoHW for PicoMock { @@ -92,8 +88,8 @@ pub mod tests { self.set_led_low_calls += 1; } - async fn power_on_off(&mut self) { - self.set_power_on_off_calls += 1; + async fn restart_module(&mut self) { + self.restart_module_calls += 1; } } } diff --git a/pico/pico-lib/src/battery.rs b/pico/pico-lib/src/battery.rs index abb01cf..0f8d2df 100644 --- a/pico/pico-lib/src/battery.rs +++ b/pico/pico-lib/src/battery.rs @@ -1,15 +1,16 @@ use atat::atat_derive::AtatCmd; use atat::atat_derive::AtatEnum; use atat::atat_derive::AtatResp; +use defmt::Format; // 3.2.52 AT+CBC Battery Charge // AT+CBC -#[derive(Clone, Debug, AtatCmd)] +#[derive(Clone, Debug, Format, AtatCmd)] #[at_cmd("+CBC", BatteryChargeResponse)] pub struct AtBatteryChargeExecute; // +CBC: ,, -#[derive(Debug, Clone, AtatResp, PartialEq)] +#[derive(Debug, Format, Clone, AtatResp, PartialEq)] pub struct BatteryChargeResponse { #[at_arg(position = 0)] pub bcs: BatteryStatus, @@ -19,7 +20,7 @@ pub struct BatteryChargeResponse { pub voltage: u32, } -#[derive(Debug, Clone, PartialEq, AtatEnum)] +#[derive(Debug, Format, Clone, PartialEq, AtatEnum)] pub enum BatteryStatus { NotCharging = 0, Charging = 1, @@ -32,7 +33,6 @@ mod tests { use super::*; use atat::AtatCmd; - use atat::heapless::String; cmd_serialization_tests! { test_at_battery_charge_execute: ( diff --git a/pico/pico-lib/src/call.rs b/pico/pico-lib/src/call.rs index 4a6d7cb..4fcb577 100644 --- a/pico/pico-lib/src/call.rs +++ b/pico/pico-lib/src/call.rs @@ -3,20 +3,22 @@ use alloc::string::ToString; use atat::AtatCmd; use atat::atat_derive::AtatCmd; use atat::atat_derive::AtatEnum; +use atat::atat_derive::AtatResp; use atat::heapless::String; +use defmt::Format; use crate::at::NoResponse; use crate::utils::LogBE; use crate::utils::send_command_logged; // 6.2.19 AT+CHFA Swap the Audio Channels -#[derive(Clone, Debug, AtatCmd)] +#[derive(Clone, Debug, Format, AtatCmd)] #[at_cmd("+CHFA", NoResponse)] pub struct AtSwapAudioChannelsWrite { pub n: AudioChannels, } -#[derive(Debug, Clone, PartialEq, AtatEnum)] +#[derive(Debug, Format, Clone, PartialEq, AtatEnum)] pub enum AudioChannels { Main = 1, Aux = 2, @@ -52,10 +54,67 @@ impl<'a> AtatCmd for AtDialNumber { } // AT+CHUP; hang up the call -#[derive(Clone, Debug, AtatCmd)] +#[derive(Clone, Debug, Format, AtatCmd)] #[at_cmd("+CHUP;", NoResponse)] pub struct AtHangup; +// 3.2.18 AT+CLIP Calling Line Identification Presentation +// AT+CLIP= +#[derive(Clone, Debug, Format, AtatCmd)] +#[at_cmd("+CLIP", NoResponse, timeout_ms = 15000)] +pub struct AtCallingLineIdentificationPresentationWrite { + pub n: ClipMode, +} + +#[derive(Debug, Format, Clone, PartialEq, AtatEnum)] +pub enum ClipMode { + DisableClipNotification = 0, + EnableClipNotification = 1, // +CLIP URC +} + +// ,[,,,,] +#[derive(Debug, Clone, AtatResp, PartialEq, Default)] +pub struct ClipUrc { + pub number: String<30>, + pub type_: ClipType, + pub sub_addr: Option>, + pub sa_type: Option, + pub alpha_id: Option>, + pub cli_validity: Option, +} + +#[derive(Debug, Default, Format, Clone, PartialEq, AtatEnum)] +pub enum ClipType { + #[default] + Unknown = 129, + National = 161, + International = 145, + NetworkSpecific = 177, +} + +#[derive(Debug, Default, Format, Clone, PartialEq, AtatEnum)] +pub enum ClipValidity { + #[default] + Valid = 0, + Withheld = 1, + NotAvailable = 2, +} + +pub async fn init( + client: &mut T, + _pico: &mut U, +) { + send_command_logged( + client, + &AtCallingLineIdentificationPresentationWrite { + n: ClipMode::EnableClipNotification, + }, + "AtCallingLineIdentificationPresentationWrite".to_string(), + ) + .await + .ok(); +} + pub async fn call_number( client: &mut T, pico: &mut U, @@ -94,7 +153,7 @@ pub async fn call_number( #[cfg(test)] mod tests { - use crate::{at, cmd_serialization_tests}; + use crate::cmd_serialization_tests; use super::*; use atat::AtatCmd; @@ -116,12 +175,49 @@ mod tests { AtHangup, "AT+CHUP;\r", ), + test_at_calling_line_identification_presentation_write: ( + AtCallingLineIdentificationPresentationWrite { + n: ClipMode::EnableClipNotification, + }, + "AT+CLIP=1\r", + ), + } + + #[test] + fn test_clip_response() { + #[derive(Clone, Debug, Format, AtatCmd)] + #[at_cmd("+CLIP", ClipUrc, timeout_ms = 15000)] + struct AtUrcHack; + + let cmd = AtUrcHack; + assert_eq!( + ClipUrc { + number: String::try_from("+36301234567").unwrap(), + type_: ClipType::International, + sub_addr: Some(String::new()), + sa_type: Some(0), + alpha_id: Some(String::new()), + cli_validity: Some(ClipValidity::Valid), + }, + cmd.parse(Ok(b"+CLIP: \"+36301234567\",145,\"\",0,\"\",0\r\n")) + .unwrap() + ); } #[tokio::test] - async fn test_call_number() { - at::tests::init_env_logger(); + async fn test_sms_init() { + let mut client = crate::at::tests::ClientMock::default(); + client.results.push_back(Ok("".as_bytes())); + client.results.push_back(Ok("".as_bytes())); + + let mut pico = crate::at::tests::PicoMock::default(); + init(&mut client, &mut pico).await; + assert_eq!(1, client.sent_commands.len()); + assert_eq!("AT+CLIP=1\r", client.sent_commands.get(0).unwrap()); + } + #[tokio::test] + async fn test_call_number() { let mut client = crate::at::tests::ClientMock::default(); client.results.push_back(Ok("".as_bytes())); client.results.push_back(Ok("".as_bytes())); diff --git a/pico/pico-lib/src/gps.rs b/pico/pico-lib/src/gps.rs index 7a9f721..f215ad5 100644 --- a/pico/pico-lib/src/gps.rs +++ b/pico/pico-lib/src/gps.rs @@ -1,3 +1,7 @@ +use atat::serde_at; +use defmt::Format; +use defmt::debug; + use core::num::ParseFloatError; use core::num::ParseIntError; use core::str::Utf8Error; @@ -7,7 +11,7 @@ use alloc::string::ToString; use atat::atat_derive::AtatCmd; use atat::atat_derive::AtatEnum; use atat::atat_derive::AtatResp; -use atat::heapless::String; +use atat::heapless_bytes::Bytes; use fasttime::Date; use fasttime::DateTime; @@ -15,19 +19,20 @@ use crate::at::NoResponse; use crate::location; use crate::utils; use crate::utils::as_tokens; +use crate::utils::bytes_to_string; use crate::utils::send_command_logged; // SIM868_Series_GNSS_Application_Note_V1.02.pdf // 2.1 AT+CGNSPWR GNSS Power Control // AT+CMGF=[] -#[derive(Clone, Debug, AtatCmd)] +#[derive(Clone, Debug, Format, AtatCmd)] #[at_cmd("+CGNSPWR", NoResponse)] pub struct AtGnssPowerControlWrite { pub mode: PowerMode, } -#[derive(Debug, Clone, PartialEq, AtatEnum)] +#[derive(Debug, Format, Clone, PartialEq, AtatEnum)] pub enum PowerMode { TurnOff = 0, TurnOn = 1, @@ -35,8 +40,8 @@ pub enum PowerMode { // 2.3 AT+CGNSINF GNSS navigation information parsed from NMEA sentences // AT+CGNSINF=[] -#[derive(Clone, Debug, AtatCmd)] -#[at_cmd("+CGNSINF", GnssNavigationInformationResponse, parse = parse_gnss_navigation_information)] +#[derive(Clone, Debug, Format, AtatCmd)] +#[at_cmd("+CGNSINF", GnssNavigationInformationResponse, parse = parse_gnss_navigation_information)] // TODO: this should not need custom parsing pub struct AtGnssNavigationInformationExecute; // +CGNSINF: ,,,,,,,,,,,,,,,,,,,, @@ -44,47 +49,47 @@ pub struct AtGnssNavigationInformationExecute; #[rustfmt::skip] pub struct GnssNavigationInformationResponse { // Length Format #[at_arg(position = 0)] - pub gnss_run_status: GNSSRunStatus, // 1 + pub gnss_run_status: Option, // 1 #[at_arg(position = 1)] - pub fix_status: FixStatus, // 1 + pub fix_status: Option, // 1 #[at_arg(position = 2)] - pub utc_date_time: String<18>, // 18 yyyyMMddhhmmss.sss [1980-2039][1-12][1-31][0-23][0-59][0.000-60.999] + pub utc_date_time: Option>, // 18 yyyyMMddhhmmss.sss [1980-2039][1-12][1-31][0-23][0-59][0.000-60.999] #[at_arg(position = 3)] - pub latitude: f64, // 10 [-90.000000,90.000000] + pub latitude: Option, // 10 [-90.000000,90.000000] #[at_arg(position = 4)] - pub longitude: f64, // 11 [-180.000000,180.000000] + pub longitude: Option, // 11 [-180.000000,180.000000] #[at_arg(position = 5)] - pub msl_altitude: f64, // 8 [-180.000000,180.000000] meters + pub msl_altitude: Option, // 8 [-180.000000,180.000000] meters #[at_arg(position = 6)] - pub speed_over_ground: f64, // 6 [0,999.999] km/h + pub speed_over_ground: Option, // 6 [0,999.999] km/h #[at_arg(position = 7)] - pub course_over_ground: f64, // 6 [0,360.00] degrees + pub course_over_ground: Option, // 6 [0,360.00] degrees #[at_arg(position = 8)] - pub fix_mode: u8, // 1 [0,1,2(reserved)] + pub fix_mode: Option, // 1 [0,1,2(reserved)] #[at_arg(position = 9)] - pub reserved1: Option, // 0 + pub reserved1: Option, // 0 #[at_arg(position = 10)] - pub hdop: f64, // 4 [0,99.9] (Horizontal Dilution of Precision) + pub hdop: Option, // 4 [0,99.9] (Horizontal Dilution of Precision) #[at_arg(position = 11)] - pub pdop: f64, // 4 [0,99.9] (Position Dilution of Precision) + pub pdop: Option, // 4 [0,99.9] (Position Dilution of Precision) #[at_arg(position = 12)] - pub vdop: f64, // 4 [0,99.9] (Vertical Dilution of Precision) + pub vdop: Option, // 4 [0,99.9] (Vertical Dilution of Precision) #[at_arg(position = 13)] - pub reserved2: Option, // 0 + pub reserved2: Option, // 0 #[at_arg(position = 14)] - pub gps_satellites_in_view: Option, // 2 [0,99] + pub gps_satellites_in_view: Option, // 2 [0,99] #[at_arg(position = 15)] - pub gnss_satellites_used: Option, // 2 [0,99] + pub gnss_satellites_used: Option, // 2 [0,99] #[at_arg(position = 16)] - pub glonass_satellites_in_view: Option, // 2 [0,99] + pub glonass_satellites_in_view: Option, // 2 [0,99] #[at_arg(position = 17)] - pub reserved3: Option, // 0 + pub reserved3: Option, // 0 #[at_arg(position = 18)] - pub c_n0_max: u8, // 2 [0,55] dBHz + pub c_n0_max: Option, // 2 [0,55] dBHz #[at_arg(position = 19)] - pub hpa: Option, // 6 [0,9999.9] meters (Horizontal Positional Accuracy) reversed + pub hpa: Option, // 6 [0,9999.9] meters (Horizontal Positional Accuracy) reversed #[at_arg(position = 20)] - pub vpa: Option, // 6 [0,9999.9] meters (Vertical Positional Accuracy) reversed + pub vpa: Option, // 6 [0,9999.9] meters (Vertical Positional Accuracy) reversed } // 94 extern crate atat; @@ -122,13 +127,18 @@ impl From for AtatError { } } +impl From for AtatError { + fn from(_: atat::serde_at::de::Error) -> Self { + AtatError(atat::Error::Parse) + } +} + fn parse_gnss_navigation_information( response: &[u8], ) -> Result { - log::debug!(" parse_gnss_navigation_information input: {:?}", response); + debug!(" parse_gnss_navigation_information input: {:?}", response); let text = core::str::from_utf8(&response[10..])?; // removes "AT+CGNSINF+", ends with \r\n let mut tokens = as_tokens(text.trim_end().to_string(), ","); - log::debug!(" input: {:?}, len={}", tokens, tokens.len()); if tokens.len() != 21 { return Err(atat::Error::Parse.into()); } @@ -136,32 +146,109 @@ fn parse_gnss_navigation_information( let mut resp = GnssNavigationInformationResponse::default(); match tokens.pop_front().unwrap().as_str() { - "0" => resp.gnss_run_status = GNSSRunStatus::Off, - "1" => resp.gnss_run_status = GNSSRunStatus::On, - _ => { - return Err(atat::Error::Parse.into()); - } + "0" => resp.gnss_run_status = Some(GNSSRunStatus::Off), + "1" => resp.gnss_run_status = Some(GNSSRunStatus::On), + _ => (), }; match tokens.pop_front().unwrap().as_str() { - "0" => resp.fix_status = FixStatus::NotFixedPosition, - "1" => resp.fix_status = FixStatus::FixedPosition, - _ => { - return Err(atat::Error::Parse.into()); - } + "0" => resp.fix_status = Some(FixStatus::NotFixedPosition), + "1" => resp.fix_status = Some(FixStatus::FixedPosition), + _ => (), }; - resp.utc_date_time = String::try_from(tokens.pop_front().unwrap().as_str())?; - resp.latitude = tokens.pop_front().unwrap().parse()?; - resp.longitude = tokens.pop_front().unwrap().parse()?; - resp.msl_altitude = tokens.pop_front().unwrap().parse()?; - resp.speed_over_ground = tokens.pop_front().unwrap().parse()?; - resp.course_over_ground = tokens.pop_front().unwrap().parse()?; - resp.fix_mode = tokens.pop_front().unwrap().parse()?; + match tokens.pop_front().unwrap().as_str() { + "" => Ok::<(), AtatError>(()), + text => { + let u: Bytes<18> = serde_at::from_str(text)?; + resp.utc_date_time = Some(u); + Ok(()) + } + }?; + + match tokens.pop_front().unwrap().as_str() { + "" => Ok::<(), AtatError>(()), + text => { + let u: f64 = text.parse()?; + resp.latitude = Some(u); + Ok(()) + } + }?; + + match tokens.pop_front().unwrap().as_str() { + "" => Ok::<(), AtatError>(()), + text => { + let u: f64 = text.parse()?; + resp.longitude = Some(u); + Ok(()) + } + }?; + + match tokens.pop_front().unwrap().as_str() { + "" => Ok::<(), AtatError>(()), + text => { + let u: f64 = text.parse()?; + resp.msl_altitude = Some(u); + Ok(()) + } + }?; + + match tokens.pop_front().unwrap().as_str() { + "" => Ok::<(), AtatError>(()), + text => { + let u: f64 = text.parse()?; + resp.speed_over_ground = Some(u); + Ok(()) + } + }?; + + match tokens.pop_front().unwrap().as_str() { + "" => Ok::<(), AtatError>(()), + text => { + let u: f64 = text.parse()?; + resp.course_over_ground = Some(u); + Ok(()) + } + }?; + + match tokens.pop_front().unwrap().as_str() { + "" => Ok::<(), AtatError>(()), + text => { + let u: u8 = text.parse()?; + resp.fix_mode = Some(u); + Ok(()) + } + }?; + tokens.pop_front(); // reserved1 - resp.hdop = tokens.pop_front().unwrap().parse()?; - resp.pdop = tokens.pop_front().unwrap().parse()?; - resp.vdop = tokens.pop_front().unwrap().parse()?; + + match tokens.pop_front().unwrap().as_str() { + "" => Ok::<(), AtatError>(()), + text => { + let u: f64 = text.parse()?; + resp.hdop = Some(u); + Ok(()) + } + }?; + + match tokens.pop_front().unwrap().as_str() { + "" => Ok::<(), AtatError>(()), + text => { + let u: f64 = text.parse()?; + resp.pdop = Some(u); + Ok(()) + } + }?; + + match tokens.pop_front().unwrap().as_str() { + "" => Ok::<(), AtatError>(()), + text => { + let u: f64 = text.parse()?; + resp.vdop = Some(u); + Ok(()) + } + }?; + tokens.pop_front(); // reserved2 match tokens.pop_front().unwrap().as_str() { @@ -193,7 +280,14 @@ fn parse_gnss_navigation_information( tokens.pop_front(); // reserved3 - resp.c_n0_max = tokens.pop_front().unwrap().parse()?; + match tokens.pop_front().unwrap().as_str() { + "" => Ok::<(), AtatError>(()), + text => { + let u: u8 = text.parse()?; + resp.c_n0_max = Some(u); + Ok(()) + } + }?; match tokens.pop_front().unwrap().as_str() { "" => Ok::<(), AtatError>(()), @@ -216,14 +310,14 @@ fn parse_gnss_navigation_information( return Ok(resp); } -#[derive(Debug, Clone, PartialEq, AtatEnum, Default)] +#[derive(Debug, Format, Clone, PartialEq, AtatEnum, Default)] pub enum GNSSRunStatus { #[default] Off = 0, On = 1, } -#[derive(Debug, Clone, PartialEq, AtatEnum, Default)] +#[derive(Debug, Format, Clone, PartialEq, AtatEnum, Default)] pub enum FixStatus { #[default] NotFixedPosition = 0, @@ -247,8 +341,8 @@ pub async fn get_gps_location // TODO defer { AtGnssPowerControlWrite::TurnOff }; would be better - let mut location: Option = None; for i in 0..max_retries { + pico.sleep(1000).await; match send_command_logged( client, &AtGnssNavigationInformationExecute, @@ -257,12 +351,15 @@ pub async fn get_gps_location .await { Ok(resp) => { - log::info!(" OK {:?}", resp); - if resp.utc_date_time.len() != 18 { + if resp.utc_date_time.is_none() + || resp.latitude.is_none() + || resp.longitude.is_none() + { continue; } - let (year, rest) = resp.utc_date_time.as_str().split_at(4); + let datetime = bytes_to_string(&resp.utc_date_time.unwrap()); + let (year, rest) = datetime.as_str().split_at(4); let (month, rest) = rest.split_at(2); let (day, rest) = rest.split_at(2); let (hour, rest) = rest.split_at(2); @@ -284,17 +381,26 @@ pub async fn get_gps_location }, }; - location = Some(location::Location { - latitude: resp.latitude, - longitude: resp.longitude, - accuracy: utils::estimate_gps_accuracy(resp.pdop), + send_command_logged( + client, + &AtGnssPowerControlWrite { + mode: PowerMode::TurnOff, + }, + "AtGnssPowerControlWrite OFF".to_string(), + ) + .await + .ok(); + + let pdop = resp.pdop.unwrap_or(10.0); + return Some(location::Location { + latitude: resp.latitude.unwrap(), + longitude: resp.longitude.unwrap(), + accuracy: utils::estimate_gps_accuracy(pdop), timestamp: (datetime.unix_timestamp_nanos() / 1_000_000) as i64, }); - break; } Err(_) => (), } - pico.sleep(1000).await; } send_command_logged( @@ -307,7 +413,7 @@ pub async fn get_gps_location .await .ok(); - return location; + return None; } #[cfg(test)] @@ -315,7 +421,7 @@ extern crate std; #[cfg(test)] mod tests { - use crate::{at, cmd_serialization_tests}; + use crate::cmd_serialization_tests; use super::*; use atat::AtatCmd; @@ -342,40 +448,62 @@ mod tests { #[test] fn test_at_gnss_navigation_information_responses() { - at::tests::init_env_logger(); let cmd = AtGnssNavigationInformationExecute; assert_eq!( atat::Error::Parse, - cmd.parse(Ok(b"+CGNSINF: ,,,,\r\n")).err().unwrap(), + cmd.parse(Ok(b"+CGNSINF: 1,1,20221212120221.123,46.7624859,18.6304591,329.218,2.20,285.8,1,,2.1,2.3,0.9,,7,f,,,51,,\r\n")).err().unwrap(), ); assert_eq!( - atat::Error::Parse, - cmd.parse(Ok(b"+CGNSINF: 1,1,20221212120221.123,46.7624859,18.6304591,329.218,2.20,285.8,1,,2.1,2.3,0.9,,7,f,,,51,,\r\n")).err().unwrap(), + GnssNavigationInformationResponse { + gnss_run_status: Some(GNSSRunStatus::Off), + fix_status: None, + utc_date_time: None, + latitude: None, + longitude: None, + msl_altitude: None, + speed_over_ground: None, + course_over_ground: None, + fix_mode: None, + reserved1: None, + hdop: None, + pdop: None, + vdop: None, + reserved2: None, + gps_satellites_in_view: None, + gnss_satellites_used: None, + glonass_satellites_in_view: None, + reserved3: None, + c_n0_max: None, + hpa: None, + vpa: None, + }, + cmd.parse(Ok(b"+CGNSINF: 0,,,,,,,,,,,,,,,,,,,,\r\n")) + .unwrap(), ); assert_eq!( GnssNavigationInformationResponse { - gnss_run_status: GNSSRunStatus::On, - fix_status: FixStatus::FixedPosition, - utc_date_time: serde_at::from_slice(b"20221212120221.123").unwrap(), - latitude: 46.7624859, - longitude: 18.6304591, - msl_altitude: 329.218, - speed_over_ground: 2.20, - course_over_ground: 285.8, - fix_mode: 1, + gnss_run_status: Some(GNSSRunStatus::On), + fix_status: Some(FixStatus::FixedPosition), + utc_date_time: Some(serde_at::from_slice(b"20221212120221.123").unwrap()), + latitude: Some(46.7624859), + longitude: Some(18.6304591), + msl_altitude: Some(329.218), + speed_over_ground: Some(2.20), + course_over_ground: Some(285.8), + fix_mode: Some(1), reserved1: None, - hdop: 2.1, - pdop: 2.3, - vdop: 0.9, + hdop: Some(2.1), + pdop: Some(2.3), + vdop: Some(0.9), reserved2: None, gps_satellites_in_view: Some(7), gnss_satellites_used: Some(6), glonass_satellites_in_view: None, reserved3: None, - c_n0_max: 51, + c_n0_max: Some(51), hpa: None, vpa: None, }, @@ -385,8 +513,6 @@ mod tests { #[tokio::test] async fn test_get_gps_location() { - at::tests::init_env_logger(); - let mut client = crate::at::tests::ClientMock::default(); client.results.push_back(Ok("".as_bytes())); // Turn On client.results.push_back(Ok("+CGNSINF: ,,,,".as_bytes())); // error @@ -411,9 +537,10 @@ mod tests { }, loc1.unwrap() ); - assert_eq!(2, pico.sleep_calls.len()); + assert_eq!(3, pico.sleep_calls.len()); assert_eq!(1000, *pico.sleep_calls.get(0).unwrap()); assert_eq!(1000, *pico.sleep_calls.get(1).unwrap()); + assert_eq!(1000, *pico.sleep_calls.get(2).unwrap()); } // TODO test error handling diff --git a/pico/pico-lib/src/gsm.rs b/pico/pico-lib/src/gsm.rs index d16473d..f7355a5 100644 --- a/pico/pico-lib/src/gsm.rs +++ b/pico/pico-lib/src/gsm.rs @@ -1,3 +1,5 @@ +use defmt::Format; + use alloc::format; use alloc::string::ToString; use atat::atat_derive::AtatCmd; @@ -5,22 +7,24 @@ use atat::atat_derive::AtatEnum; use atat::atat_derive::AtatResp; use atat::heapless::String; use atat::heapless_bytes::Bytes; +use defmt::info; use fasttime::Date; use fasttime::DateTime; use crate::at::NoResponse; use crate::location; +use crate::utils::bytes_to_string; use crate::utils::send_command_logged; // 7.2.1 AT+CGATT Attach or Detach from GPRS Service // AT+CGATT=[] -#[derive(Clone, Debug, AtatCmd)] +#[derive(Clone, Debug, Format, AtatCmd)] #[at_cmd("+CGATT", NoResponse, timeout_ms = 7500)] pub struct AtAttachGPRS { pub state: AttachState, } -#[derive(Debug, Clone, PartialEq, AtatEnum)] +#[derive(Debug, Format, Clone, PartialEq, AtatEnum)] pub enum AttachState { Detach = 0, Attach = 1, @@ -28,7 +32,7 @@ pub enum AttachState { // 8.2.9 AT+CSTT Start Task and Set APN, USER NAME, PASSWORD // AT+CSTT=[,[,]] -#[derive(Clone, Debug, AtatCmd)] +#[derive(Clone, Debug, Format, AtatCmd)] #[at_cmd("+CSTT", NoResponse)] pub struct AtSetApnWrite { pub apn: String<50>, @@ -38,17 +42,18 @@ pub struct AtSetApnWrite { // 8.2.10 AT+CIICR Bring up Wireless Connection with GPRS or CSD // AT+CIICR -#[derive(Clone, Debug, AtatCmd)] +#[derive(Clone, Debug, Format, AtatCmd)] #[at_cmd("+CIICR", NoResponse, timeout_ms = 85000)] pub struct AtBringUpWirelessConnectionExecute; // 8.2.11 AT+CIFSR Get Local IP Address // AT+CIFSR -#[derive(Clone, Debug, AtatCmd)] +#[derive(Clone, Debug, Format, AtatCmd)] #[at_cmd("+CIFSR", GetLocalIPAddressResponse)] pub struct AtGetLocalIPAddressExecute; -#[derive(Debug, Clone, AtatResp, PartialEq, Default)] +// TODO: parsing issue +#[derive(Debug, Format, Clone, AtatResp, PartialEq, Default)] pub struct GetLocalIPAddressResponse { pub address: String<50>, } @@ -60,7 +65,7 @@ pub struct GetLocalIPAddressResponse { // if == 4 (GetBearerParameters) // +SAPBR: , // Only type 1 (OpenBearer), type 0 (CloseBearer) and type 3 (SetBearerParameter) is used, so ok with NoResponse -#[derive(Clone, Debug, AtatCmd)] +#[derive(Clone, Debug, Format, AtatCmd)] #[at_cmd("+SAPBR", NoResponse, timeout_ms = 85000)] // 85 sec for 1 (OpenBearer), 65 sec for 0 (CloseBearer) #[rustfmt::skip] pub struct AtSetBearerWrite { @@ -70,7 +75,7 @@ pub struct AtSetBearerWrite { pub con_param_value: Option>, // CSD/GPRS len(64) len(32) len(32) for CSD for CSD (2400, 4800, 9600, 14400) } -#[derive(Debug, Clone, PartialEq, AtatEnum)] +#[derive(Debug, Format, Clone, PartialEq, AtatEnum)] pub enum CmdType { CloseBearer = 0, OpenBearer = 1, @@ -84,7 +89,7 @@ pub enum CmdType { // 2.2 AT+CLBSCFG Base station Location Configuration // AT+CLBSCFG=,[,] // NoResponse for Operate 1 (Set) -#[derive(Clone, Debug, AtatCmd)] +#[derive(Clone, Debug, Format, AtatCmd)] #[at_cmd("+CLBSCFG", NoResponse)] pub struct AtBaseStationLocationConfWrite { pub operate: Operate, @@ -92,13 +97,13 @@ pub struct AtBaseStationLocationConfWrite { pub value: Option>, } -#[derive(Debug, Clone, PartialEq, AtatEnum)] +#[derive(Debug, Format, Clone, PartialEq, AtatEnum)] pub enum Operate { Read = 0, Set = 1, } -#[derive(Debug, Clone, PartialEq, AtatEnum, Default)] +#[derive(Debug, Format, Clone, PartialEq, AtatEnum, Default)] pub enum Para { #[default] CustomerID = 0, @@ -108,7 +113,7 @@ pub enum Para { // 2.1 AT+CLBS Base station Location // AT+CLBS=,,[[,],[]] -#[derive(Clone, Debug, AtatCmd)] +#[derive(Clone, Debug, Format, AtatCmd)] #[at_cmd("+CLBS", BaseStationLocationResponseType4)] // NOTE: only type 4 response is implemented pub struct AtBaseStationLocationWrite { pub type_: LocationType, @@ -118,7 +123,7 @@ pub struct AtBaseStationLocationWrite { pub lon_type: Option, } -#[derive(Debug, Clone, PartialEq, AtatEnum, Default)] +#[derive(Debug, Format, Clone, PartialEq, AtatEnum, Default)] pub enum LocationType { #[default] Use3Cell = 1, @@ -145,7 +150,7 @@ pub struct BaseStationLocationResponseType4 { pub time: Bytes<8>, // HH:MM:SS } -#[derive(Debug, Clone, PartialEq, AtatEnum, Default)] +#[derive(Debug, Format, Clone, PartialEq, AtatEnum, Default)] pub enum LocationCode { #[default] Success = 0, @@ -161,7 +166,7 @@ pub enum LocationCode { ReportLbsToServerFAiled = 82, } -#[derive(Debug, Clone, PartialEq, AtatEnum, Default)] +#[derive(Debug, Format, Clone, PartialEq, AtatEnum, Default)] pub enum LonType { #[default] WGS84 = 0, @@ -237,7 +242,7 @@ pub async fn get_gsm_location ) .await { - Ok(v) => log::info!(" OK {:?}", v), + Ok(v) => info!(" OK {:?}", v), Err(_) => { detach(client).await; return loc; @@ -249,10 +254,10 @@ pub async fn get_gsm_location &AtSetBearerWrite { cmd_type: CmdType::SetBearerParameters, cid: 1, - con_param_tag: Some(String::<50>::try_from("APN").unwrap()), - con_param_value: Some(String::<64>::try_from(apn).unwrap()), + con_param_tag: Some(String::<50>::try_from("Contype").unwrap()), + con_param_value: Some(String::<64>::try_from("GPRS").unwrap()), }, - "AtSetBearerWrite APN".to_string(), + "AtSetBearerWrite GPRS".to_string(), ) .await .is_err() @@ -266,10 +271,10 @@ pub async fn get_gsm_location &AtSetBearerWrite { cmd_type: CmdType::SetBearerParameters, cid: 1, - con_param_tag: Some(String::<50>::try_from("Contype").unwrap()), - con_param_value: Some(String::<64>::try_from("GPRS").unwrap()), + con_param_tag: Some(String::<50>::try_from("APN").unwrap()), + con_param_value: Some(String::<64>::try_from(apn).unwrap()), }, - "AtSetBearerWrite GPRS".to_string(), + "AtSetBearerWrite APN".to_string(), ) .await .is_err() @@ -328,6 +333,7 @@ pub async fn get_gsm_location } for i in 0..max_retries { + pico.sleep(1000).await; match send_command_logged( client, &AtBaseStationLocationWrite { @@ -342,17 +348,11 @@ pub async fn get_gsm_location .await { Ok(resp) => { - log::info!(" OK {:?}", resp); if resp.location_code != LocationCode::Success { continue; } - // TODO heapless_bytes::Bytes<8> -> heapless::String<8> conversion, how? - let mut date_vec = atat::heapless::Vec::::new(); - for v in resp.date.into_iter() { - let _ = date_vec.push(v); - } - let date = String::<8>::from_utf8(date_vec).unwrap(); + let date = bytes_to_string(&resp.date); let (day, rest) = date.as_str().split_at(2); let (_, rest) = rest.split_at(1); let (month, rest) = rest.split_at(2); @@ -360,12 +360,7 @@ pub async fn get_gsm_location let (yy, _) = rest.split_at(2); let year = format!("20{}", yy); - // TODO heapless_bytes::Bytes<8> -> heapless::String<8> conversion, how? - let mut time_vec = atat::heapless::Vec::::new(); - for v in resp.time.into_iter() { - let _ = time_vec.push(v); - } - let time = String::<8>::from_utf8(time_vec).unwrap(); + let time = bytes_to_string(&resp.time); let (hour, rest) = time.as_str().split_at(2); let (_, rest) = rest.split_at(1); let (minute, rest) = rest.split_at(2); @@ -396,7 +391,6 @@ pub async fn get_gsm_location } Err(_) => (), } - pico.sleep(1000).await; } deactivate(client).await; @@ -409,7 +403,7 @@ extern crate std; #[cfg(test)] mod tests { - use crate::{at, cmd_serialization_tests}; + use crate::cmd_serialization_tests; use super::*; use atat::AtatCmd; @@ -502,7 +496,6 @@ mod tests { #[test] fn test_at_get_local_ip_address_response() { - at::tests::init_env_logger(); let cmd = AtGetLocalIPAddressExecute; assert_eq!( @@ -515,7 +508,6 @@ mod tests { #[test] fn test_at_a() { - at::tests::init_env_logger(); let cmd = AtBaseStationLocationWrite { type_: LocationType::GetLongLatDateTime, cid: 1, @@ -544,8 +536,6 @@ mod tests { #[tokio::test] async fn test_get_gsm_location() { - at::tests::init_env_logger(); - let mut client = crate::at::tests::ClientMock::default(); client.results.push_back(Ok("".as_bytes())); // GPRS On client.results.push_back(Ok("".as_bytes())); // Set APN @@ -571,11 +561,11 @@ mod tests { assert_eq!("AT+CIICR\r", client.sent_commands.get(2).unwrap()); assert_eq!("AT+CIFSR\r", client.sent_commands.get(3).unwrap()); assert_eq!( - "AT+SAPBR=3,1,\"APN\",\"online\"\r", + "AT+SAPBR=3,1,\"Contype\",\"GPRS\"\r", client.sent_commands.get(4).unwrap() ); assert_eq!( - "AT+SAPBR=3,1,\"Contype\",\"GPRS\"\r", + "AT+SAPBR=3,1,\"APN\",\"online\"\r", client.sent_commands.get(5).unwrap() ); assert_eq!("AT+SAPBR=1,1\r", client.sent_commands.get(6).unwrap()); @@ -597,9 +587,10 @@ mod tests { }, loc1.unwrap() ); - assert_eq!(2, pico.sleep_calls.len()); + assert_eq!(3, pico.sleep_calls.len()); assert_eq!(1000, *pico.sleep_calls.get(0).unwrap()); assert_eq!(1000, *pico.sleep_calls.get(1).unwrap()); + assert_eq!(1000, *pico.sleep_calls.get(2).unwrap()); } // TODO test error handling diff --git a/pico/pico-lib/src/location.rs b/pico/pico-lib/src/location.rs index d62803f..39bc974 100644 --- a/pico/pico-lib/src/location.rs +++ b/pico/pico-lib/src/location.rs @@ -1,6 +1,7 @@ use crate::{gps::get_gps_location, gsm::get_gsm_location}; +use defmt::Format; -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug, Format, PartialEq)] pub struct Location { pub latitude: f64, pub longitude: f64, diff --git a/pico/pico-lib/src/network.rs b/pico/pico-lib/src/network.rs index 07526dd..4e9ebee 100644 --- a/pico/pico-lib/src/network.rs +++ b/pico/pico-lib/src/network.rs @@ -1,3 +1,6 @@ +use defmt::Format; +use defmt::info; + use alloc::string::ToString; use atat::atat_derive::AtatCmd; use atat::atat_derive::AtatEnum; @@ -7,24 +10,25 @@ use atat::heapless::String; use crate::at::NoResponse; use crate::utils::send_command_logged; -#[derive(Clone, Debug, AtatCmd)] +#[derive(Clone, Debug, Format, AtatCmd)] #[at_cmd("AT", NoResponse, cmd_prefix = "", timeout_ms = 5000)] pub struct AtInit; // 2.2.7 ATE Set Command Echo Mode // ATE1 - Echo On // ATE0 - Echo Off -#[derive(Clone, Debug, AtatCmd)] +#[derive(Clone, Debug, Format, AtatCmd)] #[at_cmd("ATE0", NoResponse, cmd_prefix = "")] pub struct AtSetCommandEchoOff; // 3.2.32 AT+CGREG Network Registration -#[derive(Clone, Debug, AtatCmd)] +#[derive(Clone, Debug, Format, AtatCmd)] #[at_cmd("+CGREG?", NetworkRegistrationReadResponse)] pub struct AtNetworkRegistrationRead; -#[derive(Debug, Clone, PartialEq, AtatEnum)] +#[derive(Debug, Format, Clone, PartialEq, AtatEnum)] pub enum NetworkRegistrationStatus { + NotRegistered = 0, Registered = 1, Searching = 2, Denied = 3, @@ -33,7 +37,7 @@ pub enum NetworkRegistrationStatus { } // +CGREG: ,[,[lac],[ci]] -#[derive(Debug, Clone, AtatResp, PartialEq)] +#[derive(Debug, Format, Clone, AtatResp, PartialEq)] pub struct NetworkRegistrationReadResponse { #[at_arg(position = 0)] pub n: u8, @@ -45,7 +49,7 @@ pub struct NetworkRegistrationReadResponse { } // 3.2.28 AT+CPIN Enter Pin -#[derive(Clone, Debug, AtatCmd)] +#[derive(Clone, Debug, Format, AtatCmd)] #[at_cmd("+CPIN?", EnterPinReadResponse, timeout_ms = 5000)] pub struct AtEnterPinRead; @@ -53,18 +57,18 @@ pub struct AtEnterPinRead; // READY, SIM PIN, SIM PUK, PH_SIM PIN, PH_SIM PUK, SIM PIN2, SIM PUK2 // +CPIN: NOT READY // +CPIN: NOT INSERTED -#[derive(Debug, Clone, AtatResp, PartialEq)] +#[derive(Debug, Format, Clone, AtatResp, PartialEq)] pub struct EnterPinReadResponse { #[at_arg(position = 0)] pub code: String<16>, } // 3.2.53 AT+CSQ Signal Quality Report -#[derive(Clone, Debug, AtatCmd)] +#[derive(Clone, Debug, Format, AtatCmd)] #[at_cmd("+CSQ", SignalQualityReportResponse)] pub struct AtSignalQualityReportExecute; -#[derive(Debug, Clone, AtatResp, PartialEq)] +#[derive(Debug, Format, Clone, AtatResp, PartialEq)] pub struct SignalQualityReportResponse { #[at_arg(position = 0)] pub rssi: u8, // 0: -115 dBm or less, 1: -111 dBm, 2..30: -110...-54 dBm, 31: -52 dBm or greater, 99: not known @@ -73,12 +77,12 @@ pub struct SignalQualityReportResponse { } // 3.2.22 AT+COPS Operator Selection -#[derive(Clone, Debug, AtatCmd)] +#[derive(Clone, Debug, Format, AtatCmd)] #[at_cmd("+COPS?", OperatorSelectionReadResponse)] pub struct AtOperatorSelectionRead; // +COPS: [,,] -#[derive(Debug, Clone, AtatResp, PartialEq)] +#[derive(Debug, Format, Clone, AtatResp, PartialEq)] pub struct OperatorSelectionReadResponse { #[at_arg(position = 0)] pub mode: u8, // 0 Automatic, 1 Manual @@ -94,18 +98,22 @@ pub async fn init_network( client: &mut T, pico: &mut U, ) { - send_command_logged( - client, - &AtSetCommandEchoOff, - "AtSetCommandEchoOff".to_string(), - ) - .await - .ok(); - loop { + send_command_logged( + client, + &AtSetCommandEchoOff, + "AtSetCommandEchoOff".to_string(), + ) + .await + .ok(); + match send_command_logged(client, &AtInit, "AtInit".to_string()).await { - Ok(_) => break, - Err(_) => pico.power_on_off().await, + Ok(_) => { + break; + } + Err(_) => { + pico.restart_module().await; + } } } @@ -118,7 +126,7 @@ pub async fn init_network( .await { Ok(v) => { - log::info!(" {:?}", v); + info!(" {:?}", v); if v.stat == NetworkRegistrationStatus::Registered || v.stat == NetworkRegistrationStatus::RegisteredRoaming { @@ -128,14 +136,15 @@ pub async fn init_network( Err(_) => (), } pico.sleep(1000).await; + // TODO if not registered for a while restart? } match send_command_logged(client, &AtEnterPinRead, "AtEnterPinRead".to_string()).await { Ok(v) => { - log::info!(" {:?}", v); + info!(" {:?}", v); if v.code != "READY" { pico.set_led_high(); - log::info!(" !!!DISABLE PIN ON SIM CARD!!!"); + info!(" !!!DISABLE PIN ON SIM CARD!!!"); pico.sleep(60 * 1000).await; } } @@ -149,7 +158,7 @@ pub async fn init_network( ) .await { - Ok(v) => log::info!(" {:?}", v), + Ok(v) => info!(" {:?}", v), Err(_) => (), } @@ -160,14 +169,14 @@ pub async fn init_network( ) .await { - Ok(v) => log::info!(" {:?}", v), + Ok(v) => info!(" {:?}", v), Err(_) => (), } } #[cfg(test)] mod tests { - use crate::{at, cmd_serialization_tests}; + use crate::cmd_serialization_tests; use super::*; use atat::AtatCmd; @@ -296,11 +305,10 @@ mod tests { #[tokio::test] async fn test_init_network() { - at::tests::init_env_logger(); - let mut client = crate::at::tests::ClientMock::default(); client.results.push_back(Ok("".as_bytes())); // ATE client.results.push_back(Err(atat::InternalError::Error)); // AT + client.results.push_back(Ok("".as_bytes())); // ATE retried client.results.push_back(Ok("".as_bytes())); // AT retried client.results.push_back(Ok("0,2".as_bytes())); // AT+CGREG Searching client.results.push_back(Ok("0,1".as_bytes())); // AT+CGREG Ready @@ -312,20 +320,21 @@ mod tests { let mut pico = crate::at::tests::PicoMock::default(); init_network(&mut client, &mut pico).await; - assert_eq!(8, client.sent_commands.len()); + assert_eq!(9, client.sent_commands.len()); assert_eq!("ATE0\r", client.sent_commands.get(0).unwrap()); assert_eq!("AT\r", client.sent_commands.get(1).unwrap()); - assert_eq!("AT\r", client.sent_commands.get(2).unwrap()); - assert_eq!("AT+CGREG?\r", client.sent_commands.get(3).unwrap()); + assert_eq!("ATE0\r", client.sent_commands.get(2).unwrap()); + assert_eq!("AT\r", client.sent_commands.get(3).unwrap()); assert_eq!("AT+CGREG?\r", client.sent_commands.get(4).unwrap()); - assert_eq!("AT+CPIN?\r", client.sent_commands.get(5).unwrap()); - assert_eq!("AT+CSQ\r", client.sent_commands.get(6).unwrap()); - assert_eq!("AT+COPS?\r", client.sent_commands.get(7).unwrap()); + assert_eq!("AT+CGREG?\r", client.sent_commands.get(5).unwrap()); + assert_eq!("AT+CPIN?\r", client.sent_commands.get(6).unwrap()); + assert_eq!("AT+CSQ\r", client.sent_commands.get(7).unwrap()); + assert_eq!("AT+COPS?\r", client.sent_commands.get(8).unwrap()); assert_eq!(1, pico.sleep_calls.len()); assert_eq!(1000u64, *pico.sleep_calls.get(0).unwrap()); assert_eq!(0, pico.set_led_high_calls); assert_eq!(0, pico.set_led_low_calls); - assert_eq!(1, pico.set_power_on_off_calls); + assert_eq!(1, pico.restart_module_calls); } } diff --git a/pico/pico-lib/src/poro.rs b/pico/pico-lib/src/poro.rs index 57de363..674d56d 100644 --- a/pico/pico-lib/src/poro.rs +++ b/pico/pico-lib/src/poro.rs @@ -1,3 +1,5 @@ +use defmt::Format; + use alloc::collections::vec_deque::VecDeque; use alloc::format; use alloc::vec::Vec; @@ -15,20 +17,20 @@ use fasttime::DateTime; #[cfg(test)] extern crate std; -#[derive(Debug, PartialEq, Default, MachineParser, MachineDumper)] +#[derive(Debug, Format, PartialEq, Default, MachineParser, MachineDumper)] pub struct Service { pub value: bool, } // Protector -#[derive(Debug, PartialEq, Default, MachineParser, MachineDumper)] +#[derive(Debug, Format, PartialEq, Default, MachineParser, MachineDumper)] pub struct Position { pub latitude: f64, pub longitude: f64, } -#[derive(Debug, PartialEq, Default, MachineParser, MachineDumper)] +#[derive(Debug, Format, PartialEq, Default, MachineParser, MachineDumper)] pub struct CarLocation { pub position: Position, pub accuracy: f32, @@ -36,13 +38,13 @@ pub struct CarLocation { pub timestamp: i64, } -#[derive(Debug, PartialEq, Default, MachineParser, MachineDumper)] +#[derive(Debug, Format, PartialEq, Default, MachineParser, MachineDumper)] pub struct ParkLocation { pub position: Position, pub accuracy: f32, } -#[derive(Debug, Default, PartialEq)] +#[derive(Debug, Format, Default, PartialEq)] pub enum Status { #[default] ParkingDetected, @@ -50,7 +52,7 @@ pub enum Status { CarTheftDetected, } -#[derive(Debug, PartialEq, Default, MachineParser, MachineDumper)] +#[derive(Debug, Format, PartialEq, Default, MachineParser, MachineDumper)] pub struct Protector { pub car_location: Option, pub park_location: Option, @@ -60,22 +62,22 @@ pub struct Protector { // Watcher -#[derive(Debug, PartialEq, Default, MachineParser, MachineDumper)] +#[derive(Debug, Format, PartialEq, Default, MachineParser, MachineDumper)] pub struct Call { pub value: bool, } -#[derive(Debug, PartialEq, Default, MachineParser, MachineDumper)] +#[derive(Debug, Format, PartialEq, Default, MachineParser, MachineDumper)] pub struct Refresh { pub value: bool, } -#[derive(Debug, PartialEq, Default, MachineParser, MachineDumper)] +#[derive(Debug, Format, PartialEq, Default, MachineParser, MachineDumper)] pub struct Park { pub value: bool, } -#[derive(Debug, Default, PartialEq)] +#[derive(Debug, Format, Default, PartialEq)] pub enum Source { #[default] Gcm, diff --git a/pico/pico-lib/src/sms.rs b/pico/pico-lib/src/sms.rs index be6c143..6e5ac2a 100644 --- a/pico/pico-lib/src/sms.rs +++ b/pico/pico-lib/src/sms.rs @@ -1,22 +1,26 @@ +use defmt::Format; + use alloc::format; use alloc::string::ToString; use atat::AtatCmd; use atat::atat_derive::AtatCmd; use atat::atat_derive::AtatEnum; +use atat::atat_derive::AtatResp; use atat::heapless::String; +use defmt::info; use crate::at::NoResponse; use crate::utils::send_command_logged; // 4.2.2 AT+CMGF Select SMS Message Format // AT+CMGF=[] -#[derive(Clone, Debug, AtatCmd)] +#[derive(Clone, Debug, Format, AtatCmd)] #[at_cmd("+CMGF", NoResponse)] pub struct AtSelectSMSMessageFormatWrite { pub mode: MessageMode, } -#[derive(Debug, Clone, PartialEq, AtatEnum)] +#[derive(Debug, Format, Clone, PartialEq, AtatEnum)] pub enum MessageMode { PDU = 0, Text = 1, @@ -35,6 +39,7 @@ impl<'a> AtatCmd for AtSendSMSWrite { const MAX_LEN: usize = 16; + // TODO this is not working! fn write(&self, buf: &mut [u8]) -> usize { let formatted = format!("AT+CMGS={}\r{}\x1a", self.number, self.message); let cmd = formatted.as_bytes(); @@ -48,11 +53,91 @@ impl<'a> AtatCmd for AtSendSMSWrite { } } -pub async fn send_sms( +// 4.2.3 AT+CMGL List SMS Messages from Preferred Store +// AT+CMGL=[,] +// Not Implemented, Read SMS one by one will be enough for this project + +// 4.2.4 AT+CMGR Read SMS Message +// AT+CMGR=[,] +#[derive(Clone, Debug, Format, AtatCmd)] +#[at_cmd("+CMGR", SMSMessageResponse, timeout_ms = 5000)] +pub struct AtReadSMSMessagesWrite { + pub index: u32, + pub mode: Option, +} + +#[derive(Debug, Default, Format, Clone, PartialEq, AtatEnum)] +pub enum ReadSMSMode { + #[default] + Normal = 0, + NotChangeStatusOfSMSRecord = 1, +} + +// for CBM storage: +// +CMGR: ,,,,, +#[derive(Debug, Clone, AtatResp, PartialEq, Default)] +pub struct SMSMessageResponse { + stat: String<30>, + sn: String<30>, + mid: Option>, + date_time: String<30>, + message: String<256>, +} + +// 4.2.8 AT+CNMI New SMS Message Indications +// AT+CNMI=[,[,[,[,]]]] +#[derive(Clone, Debug, Format, AtatCmd)] +#[at_cmd("+CNMI", NoResponse)] +pub struct AtNewSMSMessageIndicationsWrite { + pub mode: SMSIndicationsMode, + pub mt: Option, + pub bm: Option, + pub ds: Option, + pub bfr: Option, +} + +#[derive(Debug, Format, Clone, PartialEq, AtatEnum)] +pub enum SMSIndicationsMode { + BufferUCRinTA = 0, + DiscardIndicationRejectNewUCR = 1, + BufferUCRInTA = 2, + ForwardUCRDirectlyToTE = 3, +} + +#[derive(Debug, Format, Clone, PartialEq, AtatEnum)] +pub enum MtMode { + NoSMSDeliverIndicationsToTE = 0, + SMSStoredInMETAToTE = 1, // +CMTI: , + SMSDirectlyToTE = 2, +} + +#[derive(Debug, Format, Clone, PartialEq, AtatEnum)] +pub enum CBMMode { + NoCBMToTE = 0, + CBMDirectlyToTE = 2, +} + +#[derive(Debug, Format, Clone, PartialEq, AtatEnum)] +pub enum DSMode { + NoDSToTE = 0, + DSirectlyToTE = 1, +} + +#[derive(Debug, Format, Clone, PartialEq, AtatEnum)] +pub enum BFRMode { + TAFlushedToTE = 0, + TACleared = 1, +} +// +CMTI: , +#[derive(Debug, Clone, AtatResp, PartialEq, Default)] +pub struct NewMessageIndicationUrc { + pub mem: String<30>, + pub index: i32, +} + +pub async fn init( client: &mut T, - _pico: &mut U, // TODO: - number: &'static str, // Bytes<16> ? (same for Call) - message: &'static str, // Bytes<160> ? + _pico: &mut U, ) { send_command_logged( client, @@ -64,6 +149,28 @@ pub async fn send_sms( .await .ok(); + send_command_logged( + client, + &AtNewSMSMessageIndicationsWrite { + mode: SMSIndicationsMode::BufferUCRInTA, + mt: Some(MtMode::SMSStoredInMETAToTE), + bm: Some(CBMMode::NoCBMToTE), + ds: Some(DSMode::NoDSToTE), + bfr: Some(BFRMode::TAFlushedToTE), + }, + "AtNewSMSMessageIndicationsWrite".to_string(), + ) + .await + .ok(); +} + +// TODO this is not working yet, need to debug +pub async fn send_sms( + client: &mut T, + _pico: &mut U, // TODO: + number: &'static str, // Bytes<16> ? (same for Call) + message: &'static str, // Bytes<160> ? +) { send_command_logged( client, &AtSendSMSWrite { @@ -76,9 +183,36 @@ pub async fn send_sms( .ok(); } +// todo, temporary helper +pub async fn receive_sms( + client: &mut T, + _pico: &mut U, +) { + for i in 1..1000 { + match send_command_logged( + client, + &AtReadSMSMessagesWrite { + index: i, + mode: None, + }, + "AtReadSMSMessagesWrite".to_string(), + ) + .await + { + Ok(v) => { + info!( + "SMS RESP state={} date={} sender={} message={}", + v.stat, v.date_time, v.sn, v.message + ); + } + Err(_) => break, + } + } +} + #[cfg(test)] mod tests { - use crate::{at, cmd_serialization_tests}; + use crate::cmd_serialization_tests; use super::*; use atat::AtatCmd; @@ -97,14 +231,57 @@ mod tests { }, "AT+CMGS=+361234567\rthis is the message content\u{1a}", ), + test_at_new_sms_message_indications_write: ( + AtNewSMSMessageIndicationsWrite { + mode: SMSIndicationsMode::BufferUCRInTA, + mt: Some(MtMode::SMSStoredInMETAToTE), + bm: Some(CBMMode::NoCBMToTE), + ds: Some(DSMode::NoDSToTE), + bfr: Some(BFRMode::TAFlushedToTE), + }, + "AT+CNMI=2,1,0,0,0\r", + ), + test_at_read_sms_messages_write: ( + AtReadSMSMessagesWrite { + index: 42, + mode: None, + }, + "AT+CMGR=42\r", + ), } - #[tokio::test] - async fn test_send_sms() { - at::tests::init_env_logger(); + #[test] + fn test_clip_response() { + #[derive(Clone, Debug, Format, AtatCmd)] + #[at_cmd("+CMTI", NewMessageIndicationUrc, timeout_ms = 15000)] + struct AtUrcHack; + let cmd = AtUrcHack; + assert_eq!( + NewMessageIndicationUrc { + mem: String::try_from("SM").unwrap(), + index: 1 + }, + cmd.parse(Ok(b"+CMTI: \"SM\",1\r\n")).unwrap() + ); + } + + #[tokio::test] + async fn test_sms_init() { let mut client = crate::at::tests::ClientMock::default(); client.results.push_back(Ok("".as_bytes())); + client.results.push_back(Ok("".as_bytes())); + + let mut pico = crate::at::tests::PicoMock::default(); + init(&mut client, &mut pico).await; + assert_eq!(2, client.sent_commands.len()); + assert_eq!("AT+CMGF=1\r", client.sent_commands.get(0).unwrap()); + assert_eq!("AT+CNMI=2,1,0,0,0\r", client.sent_commands.get(1).unwrap()); + } + + #[tokio::test] + async fn test_send_sms() { + let mut client = crate::at::tests::ClientMock::default(); client.results.push_back(Ok(">".as_bytes())); let mut pico = crate::at::tests::PicoMock::default(); @@ -115,11 +292,23 @@ mod tests { "this is the text message", ) .await; - assert_eq!(2, client.sent_commands.len()); - assert_eq!("AT+CMGF=1\r", client.sent_commands.get(0).unwrap()); + assert_eq!(1, client.sent_commands.len()); assert_eq!( "AT+CMGS=+36301234567\rthis is the text message\u{1a}", - client.sent_commands.get(1).unwrap() + client.sent_commands.get(0).unwrap() ); } + + #[tokio::test] + async fn test_receive_sms() { + let mut client = crate::at::tests::ClientMock::default(); + client.results.push_back(Ok("+CMGR: \"REC READ\",\"+36301234567\",\"\",\"26/01/10,17:25:32+04\"\r\n$tATA/location/12345".as_bytes())); + client.results.push_back(Err(atat::InternalError::Timeout)); + + let mut pico = crate::at::tests::PicoMock::default(); + receive_sms(&mut client, &mut pico).await; + assert_eq!(2, client.sent_commands.len()); + assert_eq!("AT+CMGR=1\r", client.sent_commands.get(0).unwrap()); + assert_eq!("AT+CMGR=2\r", client.sent_commands.get(1).unwrap()); + } } diff --git a/pico/pico-lib/src/urc.rs b/pico/pico-lib/src/urc.rs index 48aa30d..fbb325a 100644 --- a/pico/pico-lib/src/urc.rs +++ b/pico/pico-lib/src/urc.rs @@ -2,26 +2,55 @@ use atat::atat_derive::AtatResp; use atat::atat_derive::AtatUrc; use atat::heapless_bytes::Bytes; +use crate::call::ClipUrc; +use crate::sms::NewMessageIndicationUrc; + // 18.1 CME ERROR // +CME ERROR: // 18.2 CMS ERROR // +CMS ERROR: // These are handled by the atat library -// +SAPBR : DEACT +// DEACT #[derive(Debug, Clone, AtatResp, PartialEq, Default)] -pub struct BearerSettingsDeact { - pub deact: Bytes<16>, +pub struct DeactResponse { + pub deact: Bytes<5>, } // 18.3 Summary of Unsolicited Result Codes // All URCs must be defined (https://github.com/FactbirdHQ/atat/issues/149#issuecomment-1538193692) #[derive(Clone, AtatUrc)] pub enum Urc { + #[at_urc("RING")] + Ring, + #[at_urc("NORMAL POWER DOWN")] + NormalPowerDown, + #[at_urc("UNDER-VOLTAGE POWER DOWN")] + UnderVoltagePowerDown, + #[at_urc("UNDER-VOLTAGE WARNING")] + UnderVoltageWarning, + #[at_urc("OVER-VOLTAGE POWER DOWN")] + OverVoltagePowerDown, + #[at_urc("OVER-VOLTAGE WARNING")] + OverVoltageWarning, + #[at_urc("CHARGE-ONLY MODE")] + ChargeOnlyMode, + #[at_urc("RDY")] + Ready, #[at_urc("Call Ready")] CallReady, #[at_urc("SMS Ready")] SMSReady, + #[at_urc("1 CONNECT OK")] + ConnectOK1, + #[at_urc("CONNECT OK")] + ConnectOK, #[at_urc("+SAPBR 1")] // 1 is the connection id +SAPBR : DEACT - SetBearer(BearerSettingsDeact), + SetBearer(DeactResponse), + #[at_urc("+PDP")] + GprsDisconnected(DeactResponse), + #[at_urc("+CLIP")] + ClipUrc(ClipUrc), + #[at_urc("+CMTI")] + NewMessageIndicationUrc(NewMessageIndicationUrc), } diff --git a/pico/pico-lib/src/utils.rs b/pico/pico-lib/src/utils.rs index f3ee70a..5477d74 100644 --- a/pico/pico-lib/src/utils.rs +++ b/pico/pico-lib/src/utils.rs @@ -1,3 +1,5 @@ +use defmt::info; + use alloc::collections::vec_deque::VecDeque; use alloc::string::String; use libm::{asin, cos, pow, sin, sqrt}; @@ -29,20 +31,30 @@ pub fn as_tokens(input: String, delimiter: &'static str) -> VecDeque { return tokens; } +pub fn bytes_to_string( + bytes: &atat::heapless_bytes::Bytes, +) -> atat::heapless::String { + let mut data = atat::heapless::Vec::::new(); + for c in bytes.into_iter() { + let _ = data.push(*c); + } + return atat::heapless::String::::from_utf8(data).unwrap(); +} + pub struct LogBE { context: String, } impl LogBE { pub fn new(context: String) -> Self { - log::info!("BEGIN {}", context); + info!("BEGIN {}", context.as_str()); LogBE { context: context } } } impl Drop for LogBE { fn drop(&mut self) { - log::info!("END {}", self.context); + info!("END {}", self.context.as_str()); } } @@ -54,8 +66,8 @@ pub async fn send_command_logged( let _l = LogBE::new(context); let r = client.send(command).await; match r.as_ref() { - Ok(_) => log::info!(" OK"), // TODO: {:?}, v ? - Err(e) => log::info!(" ERROR: {:?}", e), + Ok(_) => info!(" OK"), // TODO: {:?}, v ? + Err(e) => info!(" ERROR: {:?}", e), } return r; }