From 12579af9d40ad4023acc6a63f212a6fae22c6010 Mon Sep 17 00:00:00 2001 From: Tamas Domok Date: Sun, 1 Feb 2026 16:05:18 +0100 Subject: [PATCH 1/2] project: rtc, watchdog, answer/hangup and some fixes. - fix crash (LogBE::Drop -> defmt BorrowMutError) - added watchdog - added rtc mechanism - urc handling moved to main task - microphone setup - answer / hang up call - restart when network is not registered - fix gps parsing crash --- pico/Cargo.lock | 5 +- pico/app/Cargo.toml | 14 +- pico/app/src/main.rs | 303 ++++++++++++++++++++++------------- pico/pico-lib/src/call.rs | 183 ++++++++++++++++++--- pico/pico-lib/src/gps.rs | 8 +- pico/pico-lib/src/gsm.rs | 11 +- pico/pico-lib/src/network.rs | 117 +++++++------- pico/pico-lib/src/sms.rs | 1 - pico/pico-lib/src/utils.rs | 19 +-- 9 files changed, 448 insertions(+), 213 deletions(-) diff --git a/pico/Cargo.lock b/pico/Cargo.lock index f8b65c8..f1b3568 100644 --- a/pico/Cargo.lock +++ b/pico/Cargo.lock @@ -1046,6 +1046,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd402d00b0fb94c5aee000029204a46884b1262e0c443f166d86d2c0747e1a1a" dependencies = [ "cortex-m", + "defmt 1.0.1", ] [[package]] @@ -1177,9 +1178,9 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "1.13.0" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f89776e4d69bb58bc6993e99ffa1d11f228b839984854c7daeb5d37f87cbe950" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" dependencies = [ "critical-section", ] diff --git a/pico/app/Cargo.toml b/pico/app/Cargo.toml index 5b8775d..132c5d9 100644 --- a/pico/app/Cargo.toml +++ b/pico/app/Cargo.toml @@ -7,7 +7,7 @@ edition = "2024" pico-lib = { path = "../pico-lib" } embassy-sync = { version = "0.6.2", features = ["defmt"] } -embassy-executor = { version = "0.9.0", features = [ +embassy-executor = { version = "0.9.1", features = [ "arch-cortex-m", "executor-thread", "executor-interrupt", @@ -28,16 +28,16 @@ embassy-futures = { version = "0.1.2" } embedded-alloc = "0.6.0" -cortex-m = { version = "0.7.6", features = ["inline-asm"] } -cortex-m-rt = "0.7.0" -critical-section = "1.1" -panic-probe = "1.0.0" +cortex-m = { version = "0.7.7", features = ["inline-asm"] } +cortex-m-rt = "0.7.5" +critical-section = "1.2.0" +panic-probe = { version = "1.0.0", features = ["print-defmt"] } defmt = "1.0.1" -defmt-rtt = "1.0.0" +defmt-rtt = "1.1.0" atat = { version = "0.24.1", features = ["defmt", "heapless"] } static_cell = { version = "2" } -portable-atomic = { version = "1.6.0", features = ["critical-section"] } +portable-atomic = { version = "1.13.1", features = ["critical-section"] } [[bin]] name = "tATA-pico" diff --git a/pico/app/src/main.rs b/pico/app/src/main.rs index c847b9e..e4419e0 100644 --- a/pico/app/src/main.rs +++ b/pico/app/src/main.rs @@ -8,23 +8,24 @@ use atat::{AtatIngress, DefaultDigester, Ingress, ResponseSlot, UrcChannel}; use core::ptr::addr_of_mut; use defmt::*; use embassy_executor::Spawner; +use embassy_futures::select::{Either3, select3}; 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; +use embassy_rp::rtc::{DateTime, DateTimeFilter, DayOfWeek, Rtc}; use embassy_rp::uart::{self, BufferedInterruptHandler, BufferedUart, BufferedUartRx}; -use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; use embassy_sync::pubsub; -use embassy_sync::pubsub::Subscriber; use embassy_time::{Duration, Timer}; use embedded_alloc::LlffHeap as Heap; use static_cell::StaticCell; use {defmt_rtt as _, panic_probe as _}; +use embassy_rp::watchdog::Watchdog; use pico_lib::at::PicoHW; use pico_lib::poro; use pico_lib::urc; -use pico_lib::utils::{astring_to_string, send_command_logged}; +use pico_lib::utils::send_command_logged; use pico_lib::{at, battery, call, gps, gsm, network, sms}; extern crate alloc; @@ -39,6 +40,7 @@ const URC_SUBSCRIBERS: usize = 3; bind_interrupts!(struct Irqs { UART0_IRQ => BufferedInterruptHandler; ADC_IRQ_FIFO => AdcInterruptHandler; + RTC_IRQ => embassy_rp::rtc::InterruptHandler; }); #[embassy_executor::main] @@ -51,9 +53,29 @@ async fn main(spawner: Spawner) { unsafe { HEAP.init(addr_of_mut!(HEAP_MEM) as usize, HEAP_SIZE) } } let p = embassy_rp::init(Default::default()); + let mut rtc = Rtc::new(p.RTC, Irqs); + + if !rtc.is_running() { + let now = DateTime { + year: 2000, + month: 1, + day: 1, + day_of_week: DayOfWeek::Saturday, + hour: 0, + minute: 0, + second: 0, + }; + rtc.set_datetime(now).unwrap(); + // The rp2040 chip will always add a Feb 29th on every year that is divisible by 4, + // but this may be incorrect (e.g. on century years) + rtc.set_leap_year_check(false); + } Timer::after(Duration::from_secs(2)).await; info!("STARTED"); + let watchdog = Watchdog::new(p.WATCHDOG); + spawner.spawn(watchdog_task(watchdog)).unwrap(); + let mut pico = Pico { led: Output::new(p.PIN_25, Level::Low), power: Output::new(p.PIN_14, Level::Low), @@ -127,10 +149,7 @@ async fn main(spawner: Spawner) { Timer::after(Duration::from_millis(500)).await; info!("After spawning reader Task"); - let sub = URC_CHANNEL.subscribe().unwrap(); - spawner.spawn(urc_handler_task(sub)).unwrap(); - info!("After spawning Urc Task"); - Timer::after(Duration::from_secs(2)).await; + let mut sub = URC_CHANNEL.subscribe().unwrap(); info!("Network init"); Timer::after(Duration::from_secs(2)).await; @@ -146,23 +165,12 @@ async fn main(spawner: Spawner) { Timer::after(Duration::from_millis(100)).await; } - match send_command_logged( - &mut client, - &battery::AtBatteryChargeExecute, - "AtBatteryChargeExecute".to_string(), - ) - .await - { - Ok(v) => info!(" {:?}", v), - Err(_) => (), - } - - match gps::get_gps_location(&mut client, &mut pico, 10).await { + match gps::get_gps_location(&mut client, &mut pico, 5).await { Some(v) => info!("GPS location: {:?}", v), None => (), } - match gsm::get_gsm_location(&mut client, &mut pico, 10, "online").await { + match gsm::get_gsm_location(&mut client, &mut pico, 5, "online").await { Some(v) => info!("GSM location: {:?}", v), None => (), } @@ -191,21 +199,139 @@ async fn main(spawner: Spawner) { sms::receive_sms(&mut client, &mut pico).await; let mut counter = 0u64; - loop { - pico.set_led_high(); - Timer::after(Duration::from_millis(500)).await; + rtc.schedule_alarm(DateTimeFilter::default().second(30)); - counter += 1; - - let level = adc.read(&mut p26).await.unwrap(); - let temp = convert_to_celsius(adc.read(&mut ts).await.unwrap()); - info!( - "Tick counter: {} Pin 26 ADC: {} Temp: {}", - counter, level, temp - ); + loop { + // Wait for 5 seconds or until the alarm is triggered + match select3( + Timer::after_secs(4), + rtc.wait_for_alarm(), + sub.next_message(), + ) + .await + { + // Timer expired + Either3::First(_) => { + pico.set_led_high(); + Timer::after(Duration::from_millis(500)).await; + let dt = rtc.now().unwrap(); + info!( + "Now: {}-{:02}-{:02} {}:{:02}:{:02}", + dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second, + ); + + counter += 1; + let level = adc.read(&mut p26).await.unwrap(); + let temp = convert_to_celsius(adc.read(&mut ts).await.unwrap()); + info!( + "Tick counter: {} Pin 26 ADC: {} Temp: {}", + counter, level, temp + ); + + pico.set_led_low(); + Timer::after(Duration::from_millis(500)).await; + } + // Alarm triggered + Either3::Second(_) => { + let dt = rtc.now().unwrap(); + info!( + "ALARM TRIGGERED! Now: {}-{:02}-{:02} {}:{:02}:{:02}", + dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second, + ); + rtc.schedule_alarm(DateTimeFilter::default().second(30)); + + match gps::get_gps_location(&mut client, &mut pico, 5).await { + Some(v) => info!("GPS location: {:?}", v), + None => (), + } - pico.set_led_low(); - Timer::after(Duration::from_millis(500)).await; + match send_command_logged( + &mut client, + &battery::AtBatteryChargeExecute, + "AtBatteryChargeExecute".to_string(), + ) + .await + { + Ok(v) => info!(" {:?}", v), + Err(_) => (), + } + } + Either3::Third(m) => match &m { + pubsub::WaitResult::Message(u) => match u { + urc::Urc::CallReady => { + info!("URC CallReady"); + } + urc::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::OverVoltageWarning => { + info!("URC OverVoltageWarning"); + } + urc::Urc::ChargeOnlyMode => { + info!("URC ChargeOnlyMode"); + call::call_number( + &mut client, + &mut pico, + &phone_number, + Duration::from_secs(10).as_millis(), + ) + .await; + } + 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.as_str(), v.type_); + if v.number == phone_number { + Timer::after_millis(2000).await; + call::answer_incoming_call(&mut client, &mut pico).await; + } else { + call::hangup_incoming_call(&mut client, &mut pico).await; + } + } + urc::Urc::NewMessageIndicationUrc(v) => { + info!( + "URC NewMessageIndicationUrc index={} mem={}", + v.index, + v.mem.as_str() + ); + } + urc::Urc::EnterPinReadResponse(v) => { + info!("URC EnterPinReadResponse code={}", v.code); + } + }, + pubsub::WaitResult::Lagged(b) => { + info!("Urc Lagged messages: {}", b); + } + }, + } } } @@ -226,80 +352,13 @@ async fn ingress_task( } #[embassy_executor::task] -async fn urc_handler_task( - mut sub: Subscriber< - 'static, - CriticalSectionRawMutex, - urc::Urc, - URC_CAPACITY, - URC_SUBSCRIBERS, - 1, - >, -) -> ! { - info!("URC TASK SPAWNED"); +async fn watchdog_task(mut watchdog: Watchdog) -> ! { + info!("WATCHDOG TASK SPAWNED"); + watchdog.start(Duration::from_millis(6000)); loop { - let m = sub.next_message().await; - match m { - pubsub::WaitResult::Message(u) => match u { - urc::Urc::CallReady => { - info!("URC CallReady"); - } - urc::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::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 - ); - } - urc::Urc::EnterPinReadResponse(v) => { - info!("URC EnterPinReadResponse code={}", v.code); - } - }, - pubsub::WaitResult::Lagged(b) => { - info!("Urc Lagged messages: {}", b); - } - } + Timer::after_millis(2000).await; + debug!(" WATCHDOG FEED"); + watchdog.feed(); } } @@ -330,24 +389,50 @@ impl at::PicoHW for Pico<'_> { } async fn restart_module(&mut self) { - info!("Sim868 restart procedure"); + // 5.3.2.3. Restart GSM by PWRKEY + // 1. power off the GSM (between 1.5s and 2s) + // 2. wait 800ms + // 3. power on the GSM (> 800ms) + + info!("Sim868 restart procedure power"); + info!( + " power: is_high? {} is low? {}", + self.power.is_set_high(), + self.power.is_set_low() + ); + + /* + This power off procedure is not working as expected. TODO: investigate more. self.led.set_high(); info!("Sim868 power off"); self.power.set_high(); + info!(" power: is_high? {} is low? {}", self.power.is_set_high(), self.power.is_set_low()); // Customer can power off GSM by pulling down the PWRKEY pin for at least 1.5 second and release. - Timer::after_secs(2).await; + Timer::after_millis(1600).await; self.power.set_low(); self.led.set_low(); + info!(" power: is_high? {} is low? {}", self.power.is_set_high(), self.power.is_set_low()); + */ Timer::after_secs(1).await; self.led.set_high(); info!("Sim868 power on"); self.power.set_high(); + info!( + " power: is_high? {} is low? {}", + self.power.is_set_high(), + self.power.is_set_low() + ); // Customer can power on GSM by pulling down the PWRKEY pin for at least 1 second and then release. - Timer::after_secs(1).await; + Timer::after_millis(900).await; self.power.set_low(); self.led.set_low(); + info!( + " power: is_high? {} is low? {}", + self.power.is_set_high(), + self.power.is_set_low() + ); Timer::after_secs(2).await; info!("Sim868 should be Ready"); diff --git a/pico/pico-lib/src/call.rs b/pico/pico-lib/src/call.rs index abc0c33..7c1a9b7 100644 --- a/pico/pico-lib/src/call.rs +++ b/pico/pico-lib/src/call.rs @@ -6,9 +6,9 @@ use atat::atat_derive::AtatEnum; use atat::atat_derive::AtatResp; use atat::heapless::String; use defmt::Format; +use defmt::info; use crate::at::NoResponse; -use crate::utils::LogBE; use crate::utils::send_command_logged; // 6.2.19 AT+CHFA Swap the Audio Channels @@ -58,6 +58,16 @@ impl<'a> AtatCmd for AtDialNumber { #[at_cmd("+CHUP;", NoResponse)] pub struct AtHangup; +// 2.2.8 ATH Disconnect Existing Connection +#[derive(Clone, Debug, Format, AtatCmd)] +#[at_cmd("ATH", NoResponse, cmd_prefix = "", timeout_ms = 20000)] +pub struct AtHangupIncomingCall; + +// 2.2.2 ATA Answer an Incoming Call +#[derive(Clone, Debug, Format, AtatCmd)] +#[at_cmd("ATA", NoResponse, cmd_prefix = "", timeout_ms = 20000)] +pub struct AtAnswerIncomingCall; // may be aborted by receiving a character during execution + // 3.2.18 AT+CLIP Calling Line Identification Presentation // AT+CLIP= #[derive(Clone, Debug, Format, AtatCmd)] @@ -72,7 +82,7 @@ pub enum ClipMode { EnableClipNotification = 1, // +CLIP URC } -// ,[,,,,] +// +CLIP: ,[,,,,] #[derive(Debug, Clone, AtatResp, PartialEq, Default)] pub struct ClipUrc { pub number: String<30>, @@ -100,6 +110,51 @@ pub enum ClipValidity { NotAvailable = 2, } +// 6.2.4 AT+CMIC Change the Microphone Gain Level +// AT+CMIC=, +#[derive(Clone, Debug, Format, AtatCmd)] +#[at_cmd("+CMIC", NoResponse)] +pub struct AtChangeMicrophoneGainLevelWrite { + pub channel: MicAudioChannels, + pub gain_level: u8, // 0-15, 0dB - 1.5dB step - +22.5dB, default 10 or 6 on Main channel +} + +#[derive(Debug, Format, Clone, PartialEq, AtatEnum)] +pub enum MicAudioChannels { + Main = 1, + Aux = 2, + MainHandFree = 3, + AuxHandFree = 4, +} + +// 6.2.50 AT+CEXTERNTONE Close or Open Microphone +#[derive(Clone, Debug, Format, AtatCmd)] +#[at_cmd("+CEXTERNTONE", NoResponse)] +pub struct AtCloseOrOpenMicrophoneWrite { + pub mode: MicrophoneMode, +} + +#[derive(Debug, Default, Format, Clone, PartialEq, AtatEnum)] +pub enum MicrophoneMode { + #[default] + ReOpen = 0, + Close = 1, +} + +// 6.2.56 AT+CMICBIAS Close or Open the MICBIAS +#[derive(Clone, Debug, Format, AtatCmd)] +#[at_cmd("+CMICBIAS", NoResponse)] +pub struct AtCloseOrOpenMicBiasWrite { + pub mode: MicrophoneBias, +} + +#[derive(Debug, Default, Format, Clone, PartialEq, AtatEnum)] +pub enum MicrophoneBias { + Off = 0, + #[default] + On = 1, +} + pub async fn init( client: &mut T, _pico: &mut U, @@ -116,14 +171,7 @@ pub async fn init( ) .await .ok(); -} -pub async fn call_number( - client: &mut T, - pico: &mut U, - number: &String<30>, - duration_millis: u64, -) { send_command_logged( client, &AtSwapAudioChannelsWrite { @@ -134,18 +182,56 @@ pub async fn call_number( .await .ok(); + send_command_logged( + client, + &AtChangeMicrophoneGainLevelWrite { + channel: MicAudioChannels::Main, + gain_level: 12, + }, + "AtChangeMicrophoneGainLevelWrite".to_string(), + ) + .await + .ok(); + + send_command_logged( + client, + &AtCloseOrOpenMicrophoneWrite { + mode: MicrophoneMode::ReOpen, + }, + "AtCloseOrOpenMicrophoneWrite".to_string(), + ) + .await + .ok(); + + send_command_logged( + client, + &AtCloseOrOpenMicBiasWrite { + mode: MicrophoneBias::On, + }, + "AtCloseOrOpenMicBiasWrite".to_string(), + ) + .await + .ok(); +} + +pub async fn call_number( + client: &mut T, + pico: &mut U, + number: &String<30>, + duration_millis: u64, +) { send_command_logged( client, &AtDialNumber { number: number.clone(), }, - "AtSwapAudioChannelsAtDialNumberWrite".to_string(), + "AtDialNumber".to_string(), ) .await .ok(); { - let _l = LogBE::new("Sleeping".to_string()); + info!("Sleeping {} ms", duration_millis); pico.sleep(duration_millis).await; } @@ -154,6 +240,32 @@ pub async fn call_number( .ok(); } +pub async fn answer_incoming_call( + client: &mut T, + _pico: &mut U, +) { + send_command_logged( + client, + &AtAnswerIncomingCall, + "AtAnswerIncomingCall".to_string(), + ) + .await + .ok(); +} + +pub async fn hangup_incoming_call( + client: &mut T, + _pico: &mut U, +) { + send_command_logged( + client, + &AtHangupIncomingCall, + "AtHangupIncomingCall".to_string(), + ) + .await + .ok(); +} + #[cfg(test)] mod tests { use crate::cmd_serialization_tests; @@ -184,6 +296,33 @@ mod tests { }, "AT+CLIP=1\r", ), + test_at_hangup_incoming_call: ( + AtHangupIncomingCall, + "ATH\r", + ), + test_at_answer_incoming_call: ( + AtAnswerIncomingCall, + "ATA\r", + ), + test_at_change_microphone_gain_level: ( + AtChangeMicrophoneGainLevelWrite { + channel: MicAudioChannels::Main, + gain_level: 12, + }, + "AT+CMIC=1,12\r", + ), + test_at_close_or_open_microphone: ( + AtCloseOrOpenMicrophoneWrite { + mode: MicrophoneMode::ReOpen, + }, + "AT+CEXTERNTONE=0\r", + ), + test_at_close_or_open_microphone_bias: ( + AtCloseOrOpenMicBiasWrite { + mode: MicrophoneBias::On, + }, + "AT+CMICBIAS=1\r", + ), } #[test] @@ -205,18 +344,28 @@ mod tests { cmd.parse(Ok(b"+CLIP: \"+36301234567\",145,\"\",0,\"\",0\r\n")) .unwrap() ); + + // TODO: + // +CLIP: \"+36301234567\",1,0,\"\",0 } #[tokio::test] - async fn test_sms_init() { + async fn test_call_init() { let mut client = crate::at::tests::ClientMock::default(); client.results.push_back(Ok("".as_bytes())); client.results.push_back(Ok("".as_bytes())); + client.results.push_back(Ok("".as_bytes())); + 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!(5, client.sent_commands.len()); assert_eq!("AT+CLIP=1\r", client.sent_commands.get(0).unwrap()); + assert_eq!("AT+CHFA=1\r", client.sent_commands.get(1).unwrap()); + assert_eq!("AT+CMIC=1,12\r", client.sent_commands.get(2).unwrap()); + assert_eq!("AT+CEXTERNTONE=0\r", client.sent_commands.get(3).unwrap()); + assert_eq!("AT+CMICBIAS=1\r", client.sent_commands.get(4).unwrap()); } #[tokio::test] @@ -224,7 +373,6 @@ mod tests { let mut client = crate::at::tests::ClientMock::default(); client.results.push_back(Ok("".as_bytes())); client.results.push_back(Ok("".as_bytes())); - client.results.push_back(Ok("".as_bytes())); let mut pico = crate::at::tests::PicoMock::default(); call_number( @@ -234,10 +382,9 @@ mod tests { 100, ) .await; - assert_eq!(3, client.sent_commands.len()); - assert_eq!("AT+CHFA=1\r", client.sent_commands.get(0).unwrap()); - assert_eq!("ATD+36301234567,i;\r", client.sent_commands.get(1).unwrap()); - assert_eq!("AT+CHUP;\r", client.sent_commands.get(2).unwrap()); + assert_eq!(2, client.sent_commands.len()); + assert_eq!("ATD+36301234567,i;\r", client.sent_commands.get(0).unwrap()); + assert_eq!("AT+CHUP;\r", client.sent_commands.get(1).unwrap()); assert_eq!(1, pico.sleep_calls.len()); assert_eq!(100u64, *pico.sleep_calls.get(0).unwrap()); diff --git a/pico/pico-lib/src/gps.rs b/pico/pico-lib/src/gps.rs index c26000f..fafd03e 100644 --- a/pico/pico-lib/src/gps.rs +++ b/pico/pico-lib/src/gps.rs @@ -93,7 +93,11 @@ fn parse_gnss_navigation_information( response: &[u8], ) -> Result { debug!(" parse_gnss_navigation_information input: {:?}", response); - let text = core::str::from_utf8(&response[10..])?; // removes "AT+CGNSINF+", ends with \r\n + const LEN: usize = "CGNSINF+: ".len(); + if response.len() < LEN { + return Err(atat::Error::Parse.into()); + } + let text = core::str::from_utf8(&response[LEN..])?; // removes "AT+CGNSINF+", ends with \r\n let mut tokens = as_tokens(text.trim_end().to_string(), ","); if tokens.len() != 21 { return Err(atat::Error::Parse.into()); @@ -411,6 +415,8 @@ mod tests { 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"+CGNSI")).err().unwrap(),); + assert_eq!( GnssNavigationInformationResponse { gnss_run_status: Some(GNSSRunStatus::Off), diff --git a/pico/pico-lib/src/gsm.rs b/pico/pico-lib/src/gsm.rs index f7355a5..9251618 100644 --- a/pico/pico-lib/src/gsm.rs +++ b/pico/pico-lib/src/gsm.rs @@ -235,6 +235,11 @@ pub async fn get_gsm_location return loc; } + // Only after PDP context is activated, local IP address can be obtained by AT+CIFSR, + // otherwise it will respond ERROR. To see the status use AT+CIPSTATUS command. + // TODO: debug. I get timeout error. + pico.sleep(5000).await; + match send_command_logged( client, &AtGetLocalIPAddressExecute, @@ -587,10 +592,12 @@ mod tests { }, loc1.unwrap() ); - assert_eq!(3, pico.sleep_calls.len()); - assert_eq!(1000, *pico.sleep_calls.get(0).unwrap()); + + assert_eq!(4, pico.sleep_calls.len()); + assert_eq!(5000, *pico.sleep_calls.get(0).unwrap()); assert_eq!(1000, *pico.sleep_calls.get(1).unwrap()); assert_eq!(1000, *pico.sleep_calls.get(2).unwrap()); + assert_eq!(1000, *pico.sleep_calls.get(3).unwrap()); } // TODO test error handling diff --git a/pico/pico-lib/src/network.rs b/pico/pico-lib/src/network.rs index 026a722..d652343 100644 --- a/pico/pico-lib/src/network.rs +++ b/pico/pico-lib/src/network.rs @@ -135,7 +135,7 @@ pub struct SlowClockResponse { pub enum SlowClockMode { #[default] DisableSlowClock = 0, - EnableSlowClockByDTR = 1, + EnableSlowClockByDTR = 1, // GP17 pin // Two caveats: // 1. you should input some characters (at least one) to wake the module // 2. 100ms or more is needed between the waking characters and following AT commands @@ -149,76 +149,83 @@ pub async fn init_network( client: &mut T, pico: &mut U, ) { - loop { - send_command_logged( - client, - &AtSetCommandEchoOff, - "AtSetCommandEchoOff".to_string(), - ) - .await - .ok(); - - match send_command_logged(client, &AtInit, "AtInit".to_string()).await { - Ok(_) => { - match send_command_logged( - client, - &AtSetPhoneFunctionalityRead, - "AtSetPhoneFunctionalityRead".to_string(), - ) - .await - { - Ok(v) => { - info!(" {:?}", v); - if v.fun == Functionality::Full { - break; - } else { + let mut registered = false; + while !registered { + loop { + send_command_logged( + client, + &AtSetCommandEchoOff, + "AtSetCommandEchoOff".to_string(), + ) + .await + .ok(); + + match send_command_logged(client, &AtInit, "AtInit".to_string()).await { + Ok(_) => { + match send_command_logged( + client, + &AtSetPhoneFunctionalityRead, + "AtSetPhoneFunctionalityRead".to_string(), + ) + .await + { + Ok(v) => { + info!(" {:?}", v); + if v.fun == Functionality::Full { + break; + } else { + pico.restart_module().await; + } + } + Err(_) => { pico.restart_module().await; } } - Err(_) => { - pico.restart_module().await; - } + } + Err(_) => { + pico.restart_module().await; } } - Err(_) => { - pico.restart_module().await; - } - } - } - - match send_command_logged( - client, - &AtConfigureSlowClockRead, - "AtConfigureSlowClockRead".to_string(), - ) - .await - { - Ok(v) => { - info!(" {:?}", v); } - Err(_) => (), - } - loop { match send_command_logged( client, - &AtNetworkRegistrationRead, - "AtNetworkRegistrationRead".to_string(), + &AtConfigureSlowClockRead, + "AtConfigureSlowClockRead".to_string(), ) .await { Ok(v) => { info!(" {:?}", v); - if v.stat == NetworkRegistrationStatus::Registered - || v.stat == NetworkRegistrationStatus::RegisteredRoaming - { - break; - } } Err(_) => (), } - pico.sleep(1000).await; - // TODO if not registered for a while restart? + + for _ in 0..30 { + match send_command_logged( + client, + &AtNetworkRegistrationRead, + "AtNetworkRegistrationRead".to_string(), + ) + .await + { + Ok(v) => { + info!(" {:?}", v); + if v.stat == NetworkRegistrationStatus::Registered + || v.stat == NetworkRegistrationStatus::RegisteredRoaming + { + registered = true; + break; + } + } + Err(_) => (), + } + pico.sleep(2000).await; + } + if !registered { + info!("Could not register, restarting module!"); + pico.restart_module().await; + } } match send_command_logged(client, &AtEnterPinRead, "AtEnterPinRead".to_string()).await { @@ -472,7 +479,7 @@ mod tests { assert_eq!("AT+COPS?\r", client.sent_commands.get(10).unwrap()); assert_eq!(1, pico.sleep_calls.len()); - assert_eq!(1000u64, *pico.sleep_calls.get(0).unwrap()); + assert_eq!(2000u64, *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.restart_module_calls); diff --git a/pico/pico-lib/src/sms.rs b/pico/pico-lib/src/sms.rs index 787c427..b900be0 100644 --- a/pico/pico-lib/src/sms.rs +++ b/pico/pico-lib/src/sms.rs @@ -190,7 +190,6 @@ pub async fn init( .await .ok(); - // TODO: SMSSending is not adjusted yet. Either use plain text for send and UCS2 only for receive or try it with UCS2HexString<> parameters. send_command_logged( client, &AtSelectTECharsetWrite { diff --git a/pico/pico-lib/src/utils.rs b/pico/pico-lib/src/utils.rs index c9a4dbf..74db9aa 100644 --- a/pico/pico-lib/src/utils.rs +++ b/pico/pico-lib/src/utils.rs @@ -90,29 +90,12 @@ impl From for AtatError { } } -pub struct LogBE { - context: String, -} - -impl LogBE { - pub fn new(context: String) -> Self { - info!("BEGIN {}", context.as_str()); - LogBE { context: context } - } -} - -impl Drop for LogBE { - fn drop(&mut self) { - info!("END {}", self.context.as_str()); - } -} - pub async fn send_command_logged( client: &mut T, command: &U, context: String, ) -> Result<::Response, atat::Error> { - let _l = LogBE::new(context); + info!("SENDING COMMAND: {}", context.as_str()); let r = client.send(command).await; match r.as_ref() { Ok(_) => info!(" OK"), // TODO: {:?}, v ? From 868715a5ba5f2c28d76e9af4d00a67d58fd4954f Mon Sep 17 00:00:00 2001 From: Tamas Domok Date: Sun, 15 Feb 2026 11:11:40 +0100 Subject: [PATCH 2/2] fixups --- pico/app/src/main.rs | 2 +- pico/pico-lib/src/call.rs | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/pico/app/src/main.rs b/pico/app/src/main.rs index e4419e0..42fa34e 100644 --- a/pico/app/src/main.rs +++ b/pico/app/src/main.rs @@ -25,7 +25,7 @@ use embassy_rp::watchdog::Watchdog; use pico_lib::at::PicoHW; use pico_lib::poro; use pico_lib::urc; -use pico_lib::utils::send_command_logged; +use pico_lib::utils::{astring_to_string, send_command_logged}; use pico_lib::{at, battery, call, gps, gsm, network, sms}; extern crate alloc; diff --git a/pico/pico-lib/src/call.rs b/pico/pico-lib/src/call.rs index 7c1a9b7..c2a4d20 100644 --- a/pico/pico-lib/src/call.rs +++ b/pico/pico-lib/src/call.rs @@ -389,4 +389,26 @@ mod tests { assert_eq!(1, pico.sleep_calls.len()); assert_eq!(100u64, *pico.sleep_calls.get(0).unwrap()); } + + #[tokio::test] + async fn test_answer_incoming_call() { + let mut client = crate::at::tests::ClientMock::default(); + client.results.push_back(Ok("".as_bytes())); + + let mut pico = crate::at::tests::PicoMock::default(); + answer_incoming_call(&mut client, &mut pico).await; + assert_eq!(1, client.sent_commands.len()); + assert_eq!("ATA\r", client.sent_commands.get(0).unwrap()); + } + + #[tokio::test] + async fn test_hangup_incoming_call() { + let mut client = crate::at::tests::ClientMock::default(); + client.results.push_back(Ok("".as_bytes())); + + let mut pico = crate::at::tests::PicoMock::default(); + hangup_incoming_call(&mut client, &mut pico).await; + assert_eq!(1, client.sent_commands.len()); + assert_eq!("ATH\r", client.sent_commands.get(0).unwrap()); + } }