use crate::error::{Error, ErrorSource};
use arrayvec::ArrayString;
use core::{
cell::RefCell,
future::Future,
ops::DerefMut,
sync::atomic::{AtomicBool, AtomicPtr, Ordering},
task::Poll,
};
use critical_section::Mutex;
use futures::task::AtomicWaker;
static AT_PROGRESS: AtomicBool = AtomicBool::new(false);
static AT_PROGRESS_WAKER: AtomicWaker = AtomicWaker::new();
static AT_DATA: Mutex<RefCell<(AtomicPtr<u8>, usize)>> =
Mutex::new(RefCell::new((AtomicPtr::new(core::ptr::null_mut()), 0)));
static AT_DATA_WAKER: AtomicWaker = AtomicWaker::new();
unsafe extern "C" fn at_callback(resp: *const core::ffi::c_char) {
#[cfg(feature = "defmt")]
defmt::trace!(
"AT <- {}",
core::ffi::CStr::from_ptr(resp as _).to_str().unwrap()
);
critical_section::with(|cs| {
let mut data = AT_DATA.borrow_ref_mut(cs);
let (ptr, size) = data.deref_mut();
if ptr.get_mut().is_null() {
return;
}
let mut index = 0;
while index < *size && *resp.add(index) != 0 {
*ptr.get_mut().add(index) = *resp.add(index) as _;
index += 1;
}
*ptr = AtomicPtr::default();
*size = 0;
});
AT_DATA_WAKER.wake();
}
pub async fn send_at<const CAP: usize>(command: &str) -> Result<ArrayString<CAP>, Error> {
SendATFuture {
state: Default::default(),
command: command.as_bytes(),
response: [0; CAP],
}
.await
}
pub async fn send_at_bytes<const CAP: usize>(command: &[u8]) -> Result<ArrayString<CAP>, Error> {
SendATFuture {
state: Default::default(),
command,
response: [0; CAP],
}
.await
}
pub fn send_at_blocking<const CAP: usize>(command: &str) -> Result<ArrayString<CAP>, Error> {
#[cfg(feature = "defmt")]
defmt::trace!("AT -> {}", command);
let string = if CAP > 0 {
let mut buffer = [0; CAP];
unsafe {
nrfxlib_sys::nrf_modem_at_cmd(
buffer.as_mut_ptr() as _,
buffer.len(),
c"%.*s".as_ptr() as *const core::ffi::c_char,
command.len(),
command.as_ptr(),
)
.into_result()?;
}
let mut return_string = ArrayString::from_byte_string(&buffer).unwrap();
strip_null_bytes(&mut return_string);
return_string
} else {
unsafe {
nrfxlib_sys::nrf_modem_at_printf(
c"%.*s".as_ptr() as *const core::ffi::c_char,
command.len(),
command.as_ptr(),
)
.into_result()?;
}
ArrayString::new()
};
#[cfg(feature = "defmt")]
defmt::trace!("AT <- {}", string.as_str());
Ok(string)
}
struct SendATFuture<'c, const CAP: usize> {
state: SendATState,
command: &'c [u8],
response: [u8; CAP],
}
impl<const CAP: usize> Future for SendATFuture<'_, CAP> {
type Output = Result<ArrayString<CAP>, Error>;
fn poll(
mut self: core::pin::Pin<&mut Self>,
cx: &mut core::task::Context<'_>,
) -> core::task::Poll<Self::Output> {
match self.state {
SendATState::WaitingOnAccess => {
if AT_PROGRESS.compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst)
== Ok(false)
{
self.state = SendATState::AccessGranted;
cx.waker().wake_by_ref();
Poll::Pending
} else {
AT_PROGRESS_WAKER.register(cx.waker());
Poll::Pending
}
}
SendATState::AccessGranted => {
critical_section::with(|cs| {
*AT_DATA.borrow_ref_mut(cs) = (AtomicPtr::new(self.response.as_mut_ptr()), CAP)
});
AT_DATA_WAKER.register(cx.waker());
#[cfg(feature = "defmt")]
defmt::trace!(
"AT -> {}",
defmt::unwrap!(core::str::from_utf8(self.command).ok())
);
let result = unsafe {
nrfxlib_sys::nrf_modem_at_cmd_async(
Some(at_callback),
c"%.*s".as_ptr() as *const core::ffi::c_char,
self.command.len(),
self.command.as_ptr(),
)
.into_result()
};
match result {
Ok(_) => {
self.state = SendATState::WaitingOnData;
Poll::Pending
}
Err(e) => Poll::Ready(Err(e)),
}
}
SendATState::WaitingOnData => critical_section::with(|cs| {
let mut data = AT_DATA.borrow_ref_mut(cs);
if data.0.get_mut().is_null() {
if let Some(last) = self.response.last_mut() {
*last = 0
}
let mut return_string = ArrayString::from_byte_string(&self.response).unwrap();
strip_null_bytes(&mut return_string);
Poll::Ready(Ok(return_string))
} else {
AT_DATA_WAKER.register(cx.waker());
Poll::Pending
}
}),
}
}
}
impl<const CAP: usize> Drop for SendATFuture<'_, CAP> {
fn drop(&mut self) {
match self.state {
SendATState::WaitingOnAccess => {}
SendATState::AccessGranted | SendATState::WaitingOnData => {
critical_section::with(|cs| {
*AT_DATA.borrow_ref_mut(cs) = (AtomicPtr::default(), 0)
});
AT_PROGRESS.store(false, Ordering::SeqCst);
AT_PROGRESS_WAKER.wake();
}
}
}
}
#[derive(Default)]
enum SendATState {
#[default]
WaitingOnAccess,
AccessGranted,
WaitingOnData,
}
fn strip_null_bytes<const CAP: usize>(string: &mut ArrayString<CAP>) {
if let Some((reverse_index, _)) = string
.bytes()
.rev()
.enumerate()
.find(|(_, byte)| *byte != 0)
{
let index = string.len() - reverse_index;
string.truncate(index);
}
}