1use core::cell::RefCell;
2
3use atat::{AtatCmd, UrcChannel, UrcSubscription, asynch::AtatClient};
4use embassy_sync::{
5 blocking_mutex::{
6 Mutex,
7 raw::{CriticalSectionRawMutex, NoopRawMutex},
8 },
9 signal::Signal,
10};
11use heapless::String;
12use static_cell::StaticCell;
13
14use crate::{
15 Bool,
16 command::{
17 self, Urc,
18 device::{GetOperatingMode, SetOperatingMode, types::RAT},
19 mobile_equipment::{SetFunctionality, types::FunctionalMode},
20 mqtt::{self, types::MQTTStatusCode},
21 network::{PLMNSelection, types::NetworkRegistrationState},
22 pdp::DefinePDPContext,
23 system_features::{ConfigureCEREGReports, ConfigureCMEErrorReports},
24 },
25 error::Error,
26 nvm, ssl_tls,
27};
28#[cfg(feature = "gm02sp")]
29use crate::{
30 Reserved,
31 command::{
32 device::GetClock,
33 gnss::{
34 GetGnssAssitance, ProgramGnss, SetGnssConfig, UpdateGnssAssitance,
35 types::FixSensitivity, urc::GnssFixReady,
36 },
37 },
38};
39use embassy_time::{Duration, Timer, with_timeout};
40
41struct ModemState {
46 reg_state: Mutex<CriticalSectionRawMutex, RefCell<NetworkRegistrationState>>,
47 mqtt_connected: Signal<NoopRawMutex, mqtt::urc::Connected>,
48
49 #[cfg(feature = "gm02sp")]
50 fix_subscriber: Signal<NoopRawMutex, GnssFixReady>,
51}
52
53impl ModemState {
54 const fn new() -> Self {
56 Self {
57 reg_state: Mutex::new(RefCell::new(NetworkRegistrationState::NotSearching)),
58 mqtt_connected: Signal::new(),
59 #[cfg(feature = "gm02sp")]
60 fix_subscriber: Signal::new(),
61 }
62 }
63}
64
65pub struct Modem<'a, AtCl, const N: usize, const L: usize> {
67 client: AtCl,
68 state: &'a ModemState,
69 urc_chan: &'a UrcChannel<Urc, N, L>,
70 initialized: bool,
71 #[cfg(feature = "gm02sp")]
72 update_almanac: bool,
73 #[cfg(feature = "gm02sp")]
74 update_ephemeris: bool,
75}
76
77pub struct UrcHandler<'a, const N: usize, const L: usize> {
83 urc_subscription: UrcSubscription<'a, Urc, N, L>,
84 state: &'a ModemState,
85}
86
87impl<'a, const N: usize, const L: usize> UrcHandler<'a, N, L> {
88 pub async fn run(&mut self) -> ! {
92 loop {
93 let msg = self.urc_subscription.next_message_pure().await;
94 match msg {
95 #[cfg(feature = "gm02sp")]
96 command::Urc::GnssFixReady(fix_ready) => {
97 debug!("GNSS fix ready: {:?}", fix_ready);
98 self.state.fix_subscriber.signal(fix_ready);
99 }
100 command::Urc::MqttConnected(connected) => {
101 debug!("MQTT connected: {:?}", connected);
102 self.state.mqtt_connected.signal(connected);
103 }
104 command::Urc::MqttDisconnected(disconnected) => {
105 debug!("MQTT disconnected: {:?}", disconnected);
106 }
108 command::Urc::MqttMessagePublished(published) => {
109 debug!("MQTT message published: {:?}", published);
110 }
111 command::Urc::MqttMessageReceived(received) => {
112 debug!("MQTT message received: {:?}", received);
113 }
114 command::Urc::MqttSubscribed(subscribed) => {
115 debug!("MQTT subscribed: {:?}", subscribed);
116 }
117 command::Urc::MqttPromptToPublish(prompt) => {
118 debug!("MQTT prompt to publish: {:?}", prompt);
119 }
120 command::Urc::Shutdown => {
121 debug!("Device shutdown");
122 }
123 command::Urc::Start => {
124 debug!("Device started");
125 }
126 command::Urc::CoapConnected(conn) => {
127 debug!("COAP connected: {:?}", conn);
128 }
129 command::Urc::NetworkRegistrationStatus(status) => {
130 debug!("Network registration status: {:?}", status);
131 self.state.reg_state.lock(|v| {
132 v.replace(status.stat);
133 });
134 }
135 };
136 }
137 }
138}
139
140impl<'a, AtCl, const N: usize, const L: usize> Modem<'a, AtCl, N, L>
141where
142 AtCl: AtatClient,
143{
144 pub fn new(client: AtCl, urc_chan: &'a UrcChannel<Urc, N, L>) -> Self {
153 static MODEM_STATE_CELL: StaticCell<ModemState> = StaticCell::new();
154 let modem_state: &'static ModemState = MODEM_STATE_CELL.init(ModemState::new());
155 Self {
156 client,
157 urc_chan,
158 state: modem_state,
159 initialized: false,
160 #[cfg(feature = "gm02sp")]
161 update_almanac: false,
162 #[cfg(feature = "gm02sp")]
163 update_ephemeris: false,
164 }
165 }
166
167 pub fn urc_handler(&self) -> UrcHandler<'a, N, L> {
176 UrcHandler {
177 urc_subscription: self.urc_chan.subscribe().unwrap(),
178 state: self.state,
179 }
180 }
181
182 pub async fn send<Cmd: AtatCmd>(&mut self, cmd: &Cmd) -> Result<Cmd::Response, Error> {
183 self.client.send(cmd).await.map_err(|e| e.into())
184 }
185
186 pub async fn begin(&mut self) -> Result<(), Error> {
194 if self.initialized {
195 return Ok(());
196 }
197
198 self.send(&ConfigureCMEErrorReports {
199 typ: crate::command::system_features::types::CMEErrorReports::Numeric,
200 })
201 .await?;
202
203 self.send(&ConfigureCEREGReports {
204 typ: crate::command::system_features::types::CEREGReports::Enabled,
205 })
206 .await?;
207
208 self.initialized = true;
209
210 Ok(())
211 }
212
213 pub async fn get_operation_mode(&mut self) -> Result<RAT, Error> {
214 let res = self.send(&GetOperatingMode).await?;
215 Ok(res.rat)
216 }
217
218 pub async fn set_opeartion_mode(&mut self, mode: RAT) -> Result<(), Error> {
219 self.send(&SetOperatingMode { mode }).await?;
220 Ok(())
221 }
222
223 pub async fn ping(&mut self) -> Result<(), Error> {
224 self.send(&command::AT).await?;
225 Ok(())
226 }
227
228 pub async fn define_pdp_context(&mut self) -> Result<(), Error> {
229 self.send(&DefinePDPContext {
230 cid: 1,
231 pdp_type: command::pdp::types::PDPType::IP,
232 apn: String::try_from("").unwrap(),
233 pdp_addr: String::try_from("").unwrap(),
234 d_comp: command::pdp::types::PDPDComp::default(),
235 h_comp: command::pdp::types::PDPHComp::default(),
236 ipv4_alloc: command::pdp::types::PDPIPv4Alloc::NAS,
237 request_type: command::pdp::types::PDPRequestType::NewOrHandover,
238 pdp_pcscf_discovery_method: command::pdp::types::PDPPCSCF::Auto,
239 for_imcn: Bool::False,
240 nslpi: Bool::False,
241 secure_pco: Bool::False,
242 ipv4_mtu_discovery: Bool::False,
243 local_addr_ind: Bool::False,
244 non_ip_mtu_discovery: Bool::False,
245 })
246 .await?;
247 Ok(())
248 }
249
250 pub async fn set_op_state(&mut self, mode: FunctionalMode) -> Result<(), Error> {
251 self.send(&SetFunctionality {
252 fun: mode,
253 rst: None,
254 })
255 .await?;
256 Ok(())
257 }
258
259 pub fn get_network_registration_state(&self) -> NetworkRegistrationState {
260 self.state.reg_state.lock(|v| v.borrow().clone())
261 }
262}
263
264impl<'sub, AtCl, const N: usize, const L: usize> Modem<'sub, AtCl, N, L>
265where
266 AtCl: AtatClient,
267{
268 pub async fn lte_connect(&mut self) -> Result<(), Error> {
273 self.set_op_state(FunctionalMode::Full).await?;
274
275 self.send(&PLMNSelection {
277 mode: command::network::types::NetworkSelectionMode::Automatic,
278 ..Default::default()
279 })
280 .await?;
281
282 loop {
283 match self.get_network_registration_state() {
284 NetworkRegistrationState::RegisteredHome => break,
285 NetworkRegistrationState::RegisteredRoaming => break,
286 _ => {
287 Timer::after(Duration::from_millis(1000)).await;
288 }
291 }
292 }
293
294 Ok(())
295 }
296
297 pub async fn lte_disconnect(&mut self) -> Result<(), Error> {
303 self.set_op_state(command::mobile_equipment::types::FunctionalMode::Minimum)
304 .await?;
305
306 while self.get_network_registration_state() != NetworkRegistrationState::NotSearching {
307 Timer::after(Duration::from_millis(100)).await;
308 }
309
310 Ok(())
311 }
312}
313
314#[cfg(feature = "gm02sp")]
315impl<'sub, AtCl, const N: usize, const L: usize> Modem<'sub, AtCl, N, L>
316where
317 AtCl: AtatClient,
318{
319 pub async fn set_gnss_config(&mut self, sensitivity: FixSensitivity) -> Result<(), Error> {
320 self.send(&SetGnssConfig {
321 location_mode: command::gnss::types::LocationMode::OnDeviceLocation,
322 fix_sensitivity: sensitivity,
323 urc_settings: command::gnss::types::UrcNotificationSetting::Full,
324 reserved: Reserved,
325 metrics: false.into(),
326 acquisition_mode: command::gnss::types::AcquisitionMode::ColdWarmStart,
327 early_abort: false.into(),
328 })
329 .await?;
330
331 Ok(())
332 }
333
334 async fn check_assistance_data(&mut self) -> Result<(), Error> {
340 use crate::gnss::responses::GnssAsssitance;
341
342 let data = self.send(&GetGnssAssitance).await?;
343
344 self.update_almanac = false;
345 self.update_ephemeris = false;
346
347 for GnssAsssitance {
348 typ,
349 available,
350 time_to_update,
351 ..
352 } in data
353 {
354 match typ {
355 crate::gnss::types::GnssAssitanceType::Almanac => match available {
356 Bool::True => {
357 debug!(
358 "almanace data is available and should be updated within {}",
359 time_to_update
360 );
361 self.update_almanac = time_to_update <= 0;
362 }
363 Bool::False => {
364 debug!("almanace data is not available",);
365 self.update_almanac = true;
366 }
367 },
368 crate::gnss::types::GnssAssitanceType::RealTimeEphemeris => match available {
369 Bool::True => {
370 debug!(
371 "real-time ephemeris data is available and should be updated within {}",
372 time_to_update
373 );
374 self.update_ephemeris = time_to_update <= 0;
375 }
376 Bool::False => {
377 debug!("real-time ephemerise data is not available",);
378 self.update_ephemeris = true;
379 }
380 },
381 crate::gnss::types::GnssAssitanceType::PredictedEphemeris => {}
382 }
383 }
384
385 Ok(())
386 }
387
388 pub async fn update_gnss_asistance(&mut self) -> Result<(), Error> {
394 self.lte_disconnect().await?;
395
396 let mut clock = self.send(&GetClock).await?;
398
399 if clock.time.0.timestamp().is_zero() {
400 debug!("Clock time out of sync, synchronizing");
401
402 self.lte_connect().await?;
404
405 for _ in 0..5 {
408 Timer::after(Duration::from_millis(500)).await;
409 clock = self.send(&GetClock).await?;
410 if !clock.time.0.timestamp().is_zero() {
411 break;
412 }
413 }
414
415 self.lte_disconnect().await?;
416
417 if clock.time.0.timestamp().is_zero() {
418 return Err(Error::ClockSynchronization);
419 }
420 };
421
422 self.check_assistance_data().await?;
424
425 if !self.update_almanac && !self.update_ephemeris {
426 return Ok(());
427 }
428
429 self.lte_connect().await?;
430
431 if self.update_almanac {
432 self.send(&UpdateGnssAssitance {
433 typ: command::gnss::types::GnssAssitanceType::Almanac,
434 })
435 .await?;
436 }
437
438 if self.update_ephemeris {
439 self.send(&UpdateGnssAssitance {
440 typ: command::gnss::types::GnssAssitanceType::RealTimeEphemeris,
441 })
442 .await?;
443 }
444
445 for _ in 0..10 {
446 Timer::after(Duration::from_secs(10)).await;
447 self.check_assistance_data().await?;
448 if !self.update_almanac && !self.update_ephemeris {
449 break;
450 }
451 }
452
453 self.lte_disconnect().await?;
454
455 Ok(())
456 }
457
458 pub async fn get_gnss_fix(&mut self) -> Result<GnssFixReady, Error> {
459 use embassy_time::TimeoutError;
460
461 self.state.fix_subscriber.reset();
462
463 self.send(&ProgramGnss {
464 action: command::gnss::types::ProgramGnssAction::Single,
465 })
466 .await?;
467
468 match with_timeout(Duration::from_secs(180), self.state.fix_subscriber.wait()).await {
469 Ok(fix) => {
470 debug!("GNSS fix received: {:?}", fix);
471 Ok(fix)
472 }
473 Err(TimeoutError) => {
474 debug!("GNSS fix timed out");
475
476 self.send(&ProgramGnss {
477 action: command::gnss::types::ProgramGnssAction::Stop,
478 })
479 .await?;
480
481 Err(TimeoutError.into())
482 }
483 }
484 }
485}
486
487#[derive(Clone, Debug, PartialEq)]
488pub struct UsernamePassword {
489 pub username: String<256>,
491
492 pub password: String<256>,
494}
495
496#[derive(Clone, Debug, PartialEq)]
497pub struct SecurityProfile {
498 pub id: u8,
500}
501
502#[derive(Clone, Debug, PartialEq)]
504#[allow(clippy::large_enum_variant)]
505pub enum MqttAuth {
506 UsernamePassword(UsernamePassword),
507 SecurityProfile(SecurityProfile),
508}
509
510impl<'sub, AtCl, const N: usize, const L: usize> Modem<'sub, AtCl, N, L>
511where
512 AtCl: AtatClient,
513{
514 pub async fn mqtt_configure(
515 &mut self,
516 client_id: &str,
517 auth: Option<MqttAuth>,
518 ) -> Result<(), Error> {
519 let msg = match auth {
520 Some(MqttAuth::UsernamePassword(UsernamePassword { username, password })) => {
521 &mqtt::Configure {
522 id: 0,
523 client_id,
524 username: Some(username),
525 password: Some(password),
526 sp_id: None,
527 }
528 }
529 Some(MqttAuth::SecurityProfile(SecurityProfile { id })) => &mqtt::Configure {
530 id: 0,
531 client_id,
532 username: None,
533 password: None,
534 sp_id: Some(id),
535 },
536 None => &mqtt::Configure {
537 id: 0,
538 client_id,
539 username: None,
540 password: None,
541 sp_id: None,
542 },
543 };
544
545 self.send(msg).await?;
546
547 Ok(())
548 }
549
550 pub async fn mqtt_connect(&mut self, host: &str) -> Result<(), Error> {
551 self.lte_connect().await?;
552
553 self.send(&mqtt::Connect {
554 id: 0,
555 host,
556 port: None,
557 keepalive: None,
558 })
559 .await?;
560
561 let connected =
562 with_timeout(Duration::from_secs(30), self.state.mqtt_connected.wait()).await?;
563
564 match connected.rc {
565 MQTTStatusCode::Success => Ok(()),
566 status => {
567 error!("MQTT connect error: {:?}", connected.rc);
568 Err(Error::MQTT(status))
569 }
570 }
571 }
572
573 pub async fn mqtt_send(
574 &mut self,
575 topic: &str,
576 qos: mqtt::types::Qos,
577 data: &[u8],
578 ) -> Result<(), Error> {
579 debug!("Sending MQTT message");
580
581 self.send(&mqtt::PreparePublish {
582 id: 0,
583 topic,
584 qos: Some(qos),
585 length: data.len(),
586 })
587 .await?;
588
589 debug!("MQTT publish prepared");
590
591 self.send(&mqtt::Publish {
592 payload: atat::serde_bytes::Bytes::new(data),
593 })
594 .await?;
595
596 debug!("MQTT publish Sent");
597
598 Ok(())
599 }
600
601 pub async fn mqtt_disconnect(&mut self) -> Result<(), Error> {
602 self.send(&mqtt::Disconnect { id: 0 }).await?;
603 self.lte_disconnect().await?;
604 Ok(())
605 }
606}
607
608impl<'sub, AtCl, const N: usize, const L: usize> Modem<'sub, AtCl, N, L>
609where
610 AtCl: AtatClient,
611{
612 pub async fn nvm_write(
613 &mut self,
614 data_type: nvm::types::DataType,
615 index: u8,
616 data: &[u8],
617 ) -> Result<(), Error> {
618 debug!("Writing to nvm");
619
620 assert!(
621 !(0..=4).contains(&index) && !(7..=10).contains(&index),
622 "Indexes O to 4 and 7 to 10 are reserved for Sequans's internal use."
623 );
624
625 self.send(&nvm::PrepareWrite {
626 data_type,
627 index,
628 size: data.len(),
629 })
630 .await?;
631
632 debug!("NVM write ready");
633
634 self.send(&nvm::Write {
635 data: atat::serde_bytes::Bytes::new(data),
636 })
637 .await?;
638
639 debug!("NVM written");
640
641 Ok(())
642 }
643}
644
645impl<'sub, AtCl, const N: usize, const L: usize> Modem<'sub, AtCl, N, L>
646where
647 AtCl: AtatClient,
648{
649 pub async fn configure_tls_profile(
653 &mut self,
654 sp_id: u8,
655 ca_cert_id: u8,
656 client_cert_id: u8,
657 client_private_key_id: u8,
658 ) -> Result<(), Error> {
659 self.send(&ssl_tls::Configure {
660 sp_id,
661 version: ssl_tls::types::SslTlsVersion::Tls12,
662 cipher_specs: String::new(),
663 cert_valid_level: 0,
664 ca_cert_id,
665 client_cert_id,
666 client_private_key_id,
667 psk: String::new(),
668 psk_identity: String::new(),
669 storage_id: ssl_tls::types::StorageId::NVM,
670 resume: ssl_tls::types::Resume::Disabled,
671 lifetime: 0,
672 })
673 .await?;
674
675 Ok(())
676 }
677}