1use core::fmt::Write;
8use core::net;
9use core::time::Duration;
10use embedded_timers::{clock::Clock, timer::Timer};
11use enumflags2::{bitflags, BitFlags};
12use heapless::String;
13
14use crate::atst_commands;
15use crate::error::Bg77Error;
16use crate::types::Protocol;
17use crate::util::{wait, wait_ms};
18use crate::Bg77Hal;
19
20macro_rules! hl_format {
24 ($dst:expr, $($arg : tt) *) => {{
25 let mut formatted = String::new();
26 match write!(formatted, $dst, $($arg)*) {
27 Ok(_) => Ok(formatted),
28 Err(_) => Err(Bg77Error::BufferOverflow),
29 }
30 }}
31}
32
33#[derive(Copy, Clone, Debug, PartialEq, Eq)]
36pub enum RadioConfig {
37 Both {
40 emtc_bands: BitFlags<EmtcBand>,
41 nbiot_bands: BitFlags<NbiotBand>,
42 },
43 OnlyEmtc { bands: BitFlags<EmtcBand> },
45 OnlyNbiot { bands: BitFlags<NbiotBand> },
47 Prioritized {
49 preference: RadioTechnology,
50 emtc_bands: BitFlags<EmtcBand>,
51 nbiot_bands: BitFlags<NbiotBand>,
52 },
53}
54
55#[derive(Copy, Clone, Debug, PartialEq, Eq)]
57#[allow(non_camel_case_types)]
58pub enum RadioTechnology {
59 NB_IoT,
60 eMTC,
61}
62
63#[bitflags]
70#[repr(u64)]
71#[derive(Copy, Clone, Debug, PartialEq, Eq)]
72pub enum EmtcBand {
73 B1 = 0x1,
74 B2 = 0x2,
75 B3 = 0x4,
76 B4 = 0x8,
77 B5 = 0x10,
78 B8 = 0x80,
79 B12 = 0x800,
80 B13 = 0x1000,
81 B18 = 0x20000,
82 B19 = 0x40000,
83 B20 = 0x80000,
84 B25 = 0x1000000,
85 B26 = 0x2000000, B27 = 0x4000000, B28 = 0x8000000,
88 B66 = 0x200000000, B85 = 0x10000000000000, }
91
92#[bitflags]
99#[repr(u64)]
100#[derive(Copy, Clone, Debug, PartialEq, Eq)]
101pub enum NbiotBand {
102 B1 = 0x1,
103 B2 = 0x2,
104 B3 = 0x4,
105 B4 = 0x8,
106 B5 = 0x10,
107 B8 = 0x80,
108 B12 = 0x800,
109 B13 = 0x1000,
110 B18 = 0x20000,
111 B19 = 0x40000,
112 B20 = 0x80000,
113 B25 = 0x1000000,
114 B28 = 0x8000000,
115 B66 = 0x200000000, B71 = 0x4000000000, B85 = 0x10000000000000, }
119
120#[bitflags]
128#[repr(u64)]
129#[derive(Copy, Clone, Debug, PartialEq)]
130enum Band {
131 B1 = 0x1,
132 B2 = 0x2,
133 B3 = 0x4,
134 B4 = 0x8,
135 B5 = 0x10,
136 B8 = 0x80,
137 B12 = 0x800,
138 B13 = 0x1000,
139 B14 = 0x2000, B18 = 0x20000,
141 B19 = 0x40000,
142 B20 = 0x80000,
143 B25 = 0x1000000,
144 B26 = 0x2000000, B27 = 0x4000000, B28 = 0x8000000,
147 B31 = 0x40000000, B66 = 0x200000000, B71 = 0x4000000000, B72 = 0x8000000000, B73 = 0x10000000000, B85 = 0x10000000000000, }
156
157fn from_emtc(band: BitFlags<EmtcBand>) -> BitFlags<Band> {
158 BitFlags::from_bits(band.bits())
159 .expect("EmtcBand should only contain a subset of a generic Band")
160}
161fn from_nbiot(band: BitFlags<NbiotBand>) -> BitFlags<Band> {
162 BitFlags::from_bits(band.bits())
163 .expect("NbiotBand should only contain a subset of a generic Band")
164}
165
166fn format_bands(bands: BitFlags<Band>) -> String<22> {
168 let upper_bands_shifted = (bands.bits() & 0xFFFFFFFF00000000) as u128;
169 let upper_bands = upper_bands_shifted << 32;
170 let lower_bands = (bands.bits() & 0xFFFFFFFF) as u128;
171 let all_bands = upper_bands | lower_bands;
172 hl_format!("{:X}", all_bands).expect("Could not format radio bands")
173}
174
175pub struct Bg77CmdFlows<'a, P0, P1, P2, TX, RX, UE, CLOCK>
180where
181 P0: embedded_hal::digital::OutputPin,
182 P1: embedded_hal::digital::OutputPin,
183 P2: embedded_hal::digital::OutputPin,
184 UE: core::fmt::Debug,
185 TX: embedded_hal_nb::serial::Write<u8, Error = UE>,
186 RX: embedded_hal_nb::serial::Read<u8, Error = UE>,
187 CLOCK: Clock,
188{
189 pin_enable: P0,
190 pin_reset_n: P1,
191 pin_pwrkey: P2,
192 timer: Timer<'a, CLOCK>,
193 at: atst_commands::AtHelper<'a, TX, RX, UE, CLOCK>,
194 radio_config: RadioConfig,
195 apn: Option<String<32>>,
196 operator: Option<String<8>>,
197 connection_timeout: Duration,
198 delay_before_attach: Duration,
199}
200
201impl<'a, P0, P1, P2, TX, RX, UE, CLOCK> Bg77CmdFlows<'a, P0, P1, P2, TX, RX, UE, CLOCK>
202where
203 P0: embedded_hal::digital::OutputPin,
204 P1: embedded_hal::digital::OutputPin,
205 P2: embedded_hal::digital::OutputPin,
206 UE: core::fmt::Debug,
207 TX: embedded_hal_nb::serial::Write<u8, Error = UE>,
208 RX: embedded_hal_nb::serial::Read<u8, Error = UE>,
209 CLOCK: Clock,
210{
211 pub fn new(
218 hal: Bg77Hal<'a, P0, P1, P2, TX, RX, CLOCK>,
219 radio_config: RadioConfig,
220 apn: Option<&str>,
221 operator: Option<&str>,
222 connection_timeout: Duration,
223 delay_before_attach: Duration,
224 tx_rx_cooldown: Duration,
225 ) -> Self {
226 Bg77CmdFlows {
227 pin_enable: hal.pin_enable,
228 pin_reset_n: hal.pin_reset_n,
229 pin_pwrkey: hal.pin_pwrkey,
230 timer: Timer::new(hal.clock),
231 at: atst_commands::AtHelper::new(hal.tx, hal.rx, hal.clock, tx_rx_cooldown),
232 radio_config,
233 apn: apn.map(|a| a.into()),
234 operator: operator.map(|o| o.into()),
235 connection_timeout,
236 delay_before_attach,
237 }
238 }
239
240 #[cfg(feature = "direct-serial-access")]
241 pub fn serial(&mut self) -> (&mut TX, &mut RX) {
242 self.at.serial()
243 }
244
245 pub fn power_on(&mut self) -> Result<(), Bg77Error> {
247 log::info!("Turn BG77 modem on");
248
249 #[cfg(feature = "bg77")]
253 {
254 self.pin_enable.set_low().map_err(|_| Bg77Error::Hardware)?;
264 wait_ms(&mut self.timer, 100);
265 self.pin_enable
266 .set_high()
267 .map_err(|_| Bg77Error::Hardware)?;
268 wait_ms(&mut self.timer, 100);
269 self.pin_reset_n
270 .set_low()
271 .map_err(|_| Bg77Error::Hardware)?;
272 self.pin_pwrkey.set_low().map_err(|_| Bg77Error::Hardware)?;
273 wait_ms(&mut self.timer, 550);
274 self.pin_reset_n
275 .set_high()
276 .map_err(|_| Bg77Error::Hardware)?;
277 self.pin_pwrkey
278 .set_high()
279 .map_err(|_| Bg77Error::Hardware)?;
280 }
281
282 #[cfg(feature = "bg770")]
283 {
284 self.pin_enable.set_low().map_err(|_| Bg77Error::Hardware)?;
287 wait_ms(&mut self.timer, 100);
288 self.pin_enable
289 .set_high()
290 .map_err(|_| Bg77Error::Hardware)?;
291 wait_ms(&mut self.timer, 500);
292
293 self.pin_reset_n.set_low().ok();
294 wait_ms(&mut self.timer, 100);
295 self.pin_reset_n.set_high().ok();
296 wait_ms(&mut self.timer, 100);
297
298 self.pin_pwrkey.set_low().ok();
299 wait_ms(&mut self.timer, 550);
300 self.pin_pwrkey.set_high().ok();
301 wait_ms(&mut self.timer, 100);
302 }
303
304 log::info!("BG77 modem turned on");
305 Ok(())
306 }
307
308 pub fn power_off(&mut self) {
309 let _ = self.pin_enable.set_low();
310 }
311
312 pub fn connect_network(&mut self) -> Result<(), Bg77Error> {
314 log::info!("Connect to network");
315 self.uart_ready_at_command(10)?;
316
317 log::debug!("DisableSleep");
318 self.at.simple_command(b"AT+QSCLK=0\r")?;
319
320 log::debug!("EnableEcho");
321 self.at.simple_command(b"AT+CMEE=1\r")?;
322
323 log::debug!("Check SIM & GetPinState");
324 let res = self
325 .at
326 .command_with_response(b"AT+CPIN?\r", &[b"+CPIN: ", b"\r\n\r\nOK\r\n"])?;
327 if res[0] != b"READY" {
328 return Err(Bg77Error::PinRequired);
329 }
330 drop(res);
331
332 log::debug!("Turn modem functionality on");
333 #[cfg(feature = "bg77")]
334 self.at.simple_command(b"AT+CFUN=1,0\r")?; #[cfg(feature = "bg770")]
336 self.at
337 .command_with_timeout(b"AT+CFUN=1,0\r", Duration::from_secs(5))?; log::debug!("SelectOperator (detach)");
341 self.at
342 .command_with_timeout(b"AT+COPS=2\r", Duration::from_secs(10))?;
343
344 let (iotopmode, scanseq, emtc_bands, nbiot_bands, cops_act);
345 match self.radio_config {
346 RadioConfig::Both {
347 emtc_bands: emtc_bnds,
348 nbiot_bands: nbiot_bnds,
349 } => {
350 iotopmode = 2;
351 scanseq = "00";
352 emtc_bands = format_bands(from_emtc(emtc_bnds));
353 nbiot_bands = format_bands(from_nbiot(nbiot_bnds));
354 cops_act = None;
355 }
356 RadioConfig::OnlyEmtc { bands } => {
357 iotopmode = 0;
358 #[cfg(feature = "bg77")]
360 {
361 scanseq = "02"; }
363 #[cfg(feature = "bg770")]
364 {
365 scanseq = "0203"; }
367 emtc_bands = format_bands(from_emtc(bands));
368 nbiot_bands = String::from("0");
369 cops_act = Some(8);
370 }
371 RadioConfig::OnlyNbiot { bands } => {
372 iotopmode = 1;
373 #[cfg(feature = "bg77")]
374 {
375 scanseq = "03"; }
377 #[cfg(feature = "bg770")]
378 {
379 scanseq = "0302"; }
381 emtc_bands = String::from("0");
382 nbiot_bands = format_bands(from_nbiot(bands));
383 cops_act = Some(9);
384 }
385 RadioConfig::Prioritized {
386 preference: RadioTechnology::eMTC,
387 emtc_bands: emtc_bnds,
388 nbiot_bands: nbiot_bnds,
389 } => {
390 iotopmode = 2;
391 scanseq = "0203";
392 emtc_bands = format_bands(from_emtc(emtc_bnds));
393 nbiot_bands = format_bands(from_nbiot(nbiot_bnds));
394 cops_act = None;
395 }
396 RadioConfig::Prioritized {
397 preference: RadioTechnology::NB_IoT,
398 emtc_bands: emtc_bnds,
399 nbiot_bands: nbiot_bnds,
400 } => {
401 iotopmode = 2;
402 scanseq = "0302";
403 emtc_bands = format_bands(from_emtc(emtc_bnds));
404 nbiot_bands = format_bands(from_nbiot(nbiot_bnds));
405 cops_act = None;
406 }
407 }
408
409 log::debug!("Configure servicedomain");
413 #[cfg(feature = "bg77")]
414 self.at.simple_command(b"AT+QCFG=\"servicedomain\",1,1\r")?; #[cfg(feature = "bg770")]
416 self.at.simple_command(b"AT+QCFG=\"servicedomain\",1\r")?; log::debug!("Configure iotopmode");
420 let cmd: String<64> = hl_format!("AT+QCFG=\"iotopmode\",{},1\r", iotopmode)?;
421 #[cfg(feature = "bg77")]
422 self.at.simple_command(cmd.as_ref())?; #[cfg(feature = "bg770")]
424 self.at
425 .command_with_timeout(cmd.as_ref(), Duration::from_secs(6))?; log::debug!("Configure scansequence");
430 let cmd: String<64> = hl_format!("AT+QCFG=\"nwscanseq\",{},1\r", scanseq)?;
431 self.at.simple_command(cmd.as_ref())?;
432
433 log::debug!("Configure band");
467 let cmd: String<65> = hl_format!("AT+QCFG=\"band\",0,{},{},1\r", emtc_bands, nbiot_bands)?;
468 #[cfg(feature = "bg77")]
469 self.at.simple_command(cmd.as_ref())?; #[cfg(feature = "bg770")]
471 self.at
472 .command_with_timeout(cmd.as_ref(), Duration::from_secs(4))?; log::debug!("Set pdp context");
479 let cmd: String<64> = match &self.apn {
480 Some(apn) => hl_format!("AT+CGDCONT=1,\"IP\",\"{}\"\r", apn)?,
481 None => String::from("AT+CGDCONT=1,\"IP\"\r"),
482 };
483 self.at.simple_command(cmd.as_ref())?;
484
485 if self.delay_before_attach != Duration::new(0, 0) {
486 log::debug!("Wait before attach");
487 wait(&mut self.timer, self.delay_before_attach);
488 }
489
490 log::debug!("Select operator");
496 let cmd: String<64> = match (&self.operator, cops_act) {
497 (Some(operator), Some(act)) => hl_format!("AT+COPS=1,2,\"{}\",{}\r", operator, act)?,
498 (Some(operator), None) => hl_format!("AT+COPS=1,2,\"{}\"\r", operator)?,
499 (None, _) => String::from("AT+COPS=0\r"),
500 };
501 self.at
502 .command_with_timeout(cmd.as_ref(), self.connection_timeout)?;
503
504 log::debug!("Check attachment state");
505 for _ in 0..=self.connection_timeout.as_secs() {
510 let res = self
511 .at
512 .command_with_response(b"AT+CGATT?\r", &[b"+CGATT: ", b"\r\n\r\nOK\r\n"])?;
513 if res[0] == b"1" {
514 drop(res);
515 log::debug!("Disable data echo");
516 self.at.simple_command(b"AT+QISDE=0\r")?;
517
518 return Ok(());
519 }
520 wait_ms(&mut self.timer, 1000);
521 }
522
523 Err(Bg77Error::CgattFailure)
524 }
525
526 fn uart_ready_at_command(&mut self, num_tries: usize) -> Result<(), Bg77Error> {
528 for _ in 0..num_tries {
529 log::debug!("Modem ready? Sending AT!");
530 if self.at.simple_command(b"AT\r").is_ok() {
531 return Ok(());
532 }
533 }
534 Err(Bg77Error::AtCmdTimeout)
535 }
536
537 pub fn disconnect_network(&mut self) -> Result<(), Bg77Error> {
539 log::info!("Disconnect from network / Power off");
540 log::debug!("SelectOperator (detach)");
541 self.at
542 .command_with_timeout(b"AT+COPS=2\r", Duration::from_secs(10))
543 }
544
545 pub fn open_socket(
546 &mut self,
547 socket_index: usize,
548 protocol: Protocol,
549 remote: net::SocketAddr,
550 ) -> Result<(), Bg77Error> {
551 let protocol = match protocol {
552 Protocol::Tcp => "TCP",
553 Protocol::Udp => "UDP",
554 };
555 let ip_address = match remote {
556 net::SocketAddr::V6(_) => {
557 log::warn!("IPv6 addresses are not supported (yet?)");
558 return Err(Bg77Error::IPv6Unsupported);
559 }
560 net::SocketAddr::V4(v4) => {
561 let ip_address: String<16> = hl_format!("{}", v4.ip())?;
562 ip_address
563 }
564 };
565 let local_port = 0; log::info!("Open {} socket", protocol);
567 let cmd: String<64> = hl_format!(
568 "AT+QIOPEN=1,{},\"{}\",\"{}\",{},{}\r",
569 socket_index,
570 protocol,
571 ip_address,
572 remote.port(),
573 local_port
574 )?;
575 if let Ok(res) = self.at.command(
580 cmd.as_ref(),
581 &[b"\r\nOK\r\n\r\n+QIOPEN: ", b",", b"\r\n"],
582 Duration::from_secs(10), ) {
584 if let Ok(Ok(socket_id)) = core::str::from_utf8(res[0]).map(str::parse::<usize>) {
585 if socket_id == socket_index && res[1] == b"0" {
586 return Ok(());
588 }
589 }
590 }
591 log::warn!("Could not open socket, calling 'close_socket' to clean up.");
592 let _ = self.close_socket(socket_index);
593 Err(Bg77Error::OpenSocketError)
594 }
595
596 pub fn close_socket(&mut self, socket_index: usize) -> Result<(), Bg77Error> {
597 log::info!("Closing socket");
598 #[cfg(feature = "bg77")]
599 let cmd: String<64> = hl_format!("AT+QICLOSE={},10\r", socket_index)?; #[cfg(feature = "bg770")]
601 let cmd: String<64> = hl_format!("AT+QICLOSE={}\r", socket_index)?; self.at
603 .command_with_timeout(cmd.as_ref(), Duration::from_secs(11))
604 }
605
606 pub fn send_data(
607 &mut self,
608 socket_index: usize,
609 protocol: Protocol,
610 tx_data: &[u8],
611 ) -> Result<usize, Bg77Error> {
612 log::info!("Send data");
613 log::debug!("Announce data transmission");
614 let txlen = tx_data.len();
619 let txlen = match (txlen > 1460, protocol) {
620 (true, Protocol::Udp) => return Err(Bg77Error::TxSize),
621 (true, Protocol::Tcp) => 1460,
622 (false, _) => txlen,
623 };
624 let cmd: String<64> = hl_format!("AT+QISEND={},{}\r", socket_index, txlen)?;
625 self.at.command_with_response(cmd.as_ref(), &[b"> "])?;
626 log::debug!("Transmit raw data");
627 self.at
628 .command_with_response(&tx_data[..txlen], &[b"\r\nSEND OK\r\n"])?;
629 Ok(txlen)
630 }
631
632 pub fn receive_data(
633 &mut self,
634 socket_index: usize,
635 buffer: &mut [u8],
636 ) -> nb::Result<usize, Bg77Error> {
637 log::info!("Read data");
638 log::debug!("Sending read data command.");
639 let max_data_length = core::cmp::min(1460, buffer.len());
640 let cmd: String<64> = hl_format!("AT+QIRD={},{}\r", socket_index, max_data_length)?;
641 let (res, parsed_len) = self.at.command_with_parsed_len(
642 cmd.as_ref(),
643 &[b"+QIRD: ", b"\r\n"],
644 Duration::from_secs(1),
645 )?;
646 let data_length_str = core::str::from_utf8(res[0]).map_err(|_| Bg77Error::ParseFailed)?;
647 let data_length: usize = data_length_str
648 .parse()
649 .map_err(|_| Bg77Error::ParseFailed)?;
650 drop(res);
651 if data_length > max_data_length {
652 log::error!("Modem responded with more data than we requested!");
653 return Err(nb::Error::Other(Bg77Error::RxSize));
654 }
655 if data_length == 0 {
656 Err(nb::Error::WouldBlock)
657 } else {
658 log::debug!("Read raw data");
659 self.at
660 .read_raw_data(&mut buffer[..data_length], 2500, parsed_len)?;
661 Ok(data_length)
662 }
663 }
664
665 pub fn dns_lookup(&mut self, hostname: &str) -> Result<net::IpAddr, Bg77Error> {
666 log::info!("DNS lookup");
667 let cmd: String<64> = hl_format!("AT+QIDNSGIP=1,\"{}\"\r", hostname)?;
668 let res = self.at.command(
669 cmd.as_ref(),
670 &[
672 b"\r\nOK\r\n\r\n+QIURC: \"dnsgip\",0,", b",", b"\r\n\r\n+QIURC: \"dnsgip\",\"", b"\"\r\n",
676 ],
677 Duration::from_secs(10),
678 )?;
679 let ip_str = core::str::from_utf8(res[2]).map_err(|_| Bg77Error::ParseIpAddressFailed)?;
680 let mut ip_addr_parts = [0; 4];
681 for (i, octet) in ip_str.split('.').enumerate() {
682 if i >= 4 {
683 return Err(Bg77Error::ParseIpAddressFailed);
684 }
685 ip_addr_parts[i] = octet.parse().map_err(|_| Bg77Error::ParseIpAddressFailed)?;
686 }
687 let ap = ip_addr_parts;
688 let ipv4 = net::Ipv4Addr::new(ap[0], ap[1], ap[2], ap[3]);
689 Ok(net::IpAddr::V4(ipv4))
690 }
691}
692
693#[cfg(test)]
694mod tests {
695 use super::*;
696 use enumflags2::BitFlag;
697 #[test]
698 fn band_formatting() {
699 assert_eq!(&format_bands(Band::B1.into()), "1");
700 assert_eq!(&format_bands(Band::B8.into()), "80");
701 assert_eq!(&format_bands(Band::B1 | Band::B8), "81");
702 assert_eq!(&format_bands(Band::B1 | Band::B2), "3");
703 assert_eq!(&format_bands(Band::B28 | Band::B2), "8000002");
704 assert_eq!(&format_bands(Band::B66.into()), "20000000000000000");
705 assert_eq!(&format_bands(Band::B66 | Band::B1), "20000000000000001");
706 assert_eq!(&format_bands(Band::B72.into()), "800000000000000000");
707 assert_eq!(&format_bands(Band::B73.into()), "1000000000000000000");
708 assert_eq!(&format_bands(Band::B85.into()), "1000000000000000000000");
709 assert_eq!(
710 &format_bands(Band::B66 | Band::B71 | Band::B72 | Band::B73 | Band::B85),
711 "1001C20000000000000000"
712 );
713 assert_eq!(&format_bands(Band::all()), "1001C2000000004F0E389F");
714 }
715}