use crate::{error::Error, send_at, LteLink};
use arrayvec::{ArrayString, ArrayVec};
use core::{fmt::Write, write};
const ASCII_TO_7BIT_TABLE: [u8; 256] = [
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x0A, 0x20, 0x20, 0x0D, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x21, 0x22, 0x23, 0x02, 0x25, 0x26,
0x27,
0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F,
0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F,
0x00, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,
0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F,
0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57,
0x58, 0x59, 0x5A, 0xBC, 0xAF, 0xBE, 0x94, 0x11,
0x27, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67,
0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F,
0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77,
0x78, 0x79, 0x7A, 0xA8, 0xC0, 0xA9, 0xBD, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x40, 0x63, 0x01, 0xE5, 0x03, 0x53,
0x5F,
0x73, 0x63, 0x20, 0x20, 0x20, 0x2D, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x5A, 0x75, 0x0A, 0x20,
0x7A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x59, 0x60,
0x41, 0x41, 0x41, 0x41, 0x5B, 0x0E, 0x1C, 0x09,
0x45, 0x1F, 0x45, 0x45, 0x49, 0x49, 0x49, 0x49,
0x44, 0x5D, 0x4F, 0x4F, 0x4F, 0x4F, 0x5C, 0x2A,
0x0B, 0x55, 0x55, 0x55, 0x5E, 0x59, 0x20, 0x1E,
0x7F, 0x61, 0x61, 0x61, 0x7B, 0x0F, 0x1D, 0x63,
0x04, 0x05, 0x65, 0x65, 0x07, 0x69, 0x69, 0x69,
0x20, 0x7D, 0x08, 0x6F, 0x6F, 0x6F, 0x7C, 0x2F,
0x0C, 0x06, 0x75, 0x75, 0x7E, 0x79, 0x20, 0x79,
];
const STR_7BIT_ESCAPE_IND: u8 = 0x80;
const STR_7BIT_CODE_MASK: u8 = 0x7F;
const STR_7BIT_ESCAPE_CODE: u8 = 0x1B;
pub struct Sms<'a> {
number: &'a str,
message: &'a str,
}
impl<'a> Sms<'a> {
pub fn new(number: &'a str, message: &'a str) -> Self {
Self { number, message }
}
fn encode_number(number: &str) -> Result<ArrayString<15>, Error> {
let mut number: ArrayString<15> = ArrayString::from(number.trim_start_matches('+'))
.map_err(|_| Error::BufferTooSmall(None))?;
if number.len() % 2 != 0 {
number
.try_push('F')
.map_err(|_| Error::BufferTooSmall(None))?;
}
if number.is_ascii() {
let mut swapped_number = ArrayString::from_byte_string(
&number
.as_bytes()
.chunks(2)
.flat_map(|c| [c[1], c[0]])
.chain((0..15 - number.len()).map(|_| 0))
.collect::<ArrayVec<u8, 15>>()
.into_inner()
.unwrap(),
)
.unwrap();
swapped_number.truncate(number.len());
Ok(swapped_number)
} else {
Err(Error::SmsNumberNotAscii)
}
}
fn ascii_to_gsm7bit<const N: usize>(text: &str) -> Result<ArrayString<N>, Error> {
let mut encoded_message = ArrayString::new();
for c in text.chars() {
if c.is_ascii() {
let char_7bit = ASCII_TO_7BIT_TABLE[c as usize];
if char_7bit & STR_7BIT_ESCAPE_IND == 0 {
encoded_message
.try_push(char_7bit as char)
.map_err(|_| Error::BufferTooSmall(None))?;
} else {
encoded_message
.try_push(STR_7BIT_ESCAPE_CODE as char)
.map_err(|_| Error::BufferTooSmall(None))?;
encoded_message
.try_push((char_7bit & STR_7BIT_CODE_MASK) as char)
.map_err(|_| Error::BufferTooSmall(None))?;
}
}
}
Ok(encoded_message)
}
fn pack_gsm7bit<const N: usize>(text: ArrayString<N>) -> ArrayVec<u8, N> {
let mut src: usize = 0;
let mut dst: usize = 0;
let mut shift: usize = 0;
let len = text.len();
let mut bytes: ArrayVec<u8, N> = ArrayVec::new();
bytes.try_extend_from_slice(text.as_bytes()).unwrap();
while src < len {
bytes[dst] = bytes[src] >> shift;
src += 1;
if src < len {
bytes[dst] |= bytes[src] << (7 - shift);
shift += 1;
if shift == 7 {
shift = 0;
src += 1;
}
}
dst += 1;
}
bytes.truncate(dst);
bytes
}
pub async fn send<const N: usize>(self) -> Result<(), Error> {
let encoded_number = Self::encode_number(self.number).unwrap();
#[cfg(feature = "defmt")]
defmt::trace!("encoded_number: {}", encoded_number.as_str());
let encoded_message = Self::pack_gsm7bit(Self::ascii_to_gsm7bit::<N>(self.message)?);
let size = 2 +
1 +
1 +
encoded_number.len()/2 +
2 +
1 +
encoded_message.len();
let mut at_cmgs: ArrayString<N> = ArrayString::new();
let mut encoded_number_len = encoded_number.len();
if self.number.trim_start_matches('+').len() % 2 != 0 {
encoded_number_len -= 1;
}
write!(
&mut at_cmgs,
"AT+CMGS={}\r{:04X}{:04X}91{}",
size, 0x01, encoded_number_len, encoded_number
)
.map_err(|_| Error::BufferTooSmall(None))?;
write!(&mut at_cmgs, "00{:04X}", self.message.len())
.map_err(|_| Error::BufferTooSmall(None))?;
for c in &encoded_message {
write!(&mut at_cmgs, "{c:02X}").map_err(|_| Error::BufferTooSmall(None))?;
}
write!(&mut at_cmgs, "\x1A").map_err(|_| Error::BufferTooSmall(None))?;
#[cfg(feature = "defmt")]
defmt::trace!("at_cmgs: {:?}", at_cmgs.as_str());
let lte_link = LteLink::new().await?;
lte_link.wait_for_link().await?;
#[cfg(feature = "defmt")]
defmt::trace!("link found");
if send_at::<6>("AT+CNMI=3,2,0,1").await?.as_str() != "OK\r\n" {
return Err(Error::UnexpectedAtResponse);
}
let result = send_at::<18>(&at_cmgs).await?;
#[cfg(feature = "defmt")]
defmt::trace!("result: {}", result.as_str());
lte_link.deactivate().await?;
if result.ends_with("OK\r\n") {
Ok(())
} else {
Err(Error::UnexpectedAtResponse)
}
}
}