nrf_modem/
at.rs

1//! Implementation of AT functionality
2
3use crate::error::{Error, ErrorSource};
4use arrayvec::ArrayString;
5use core::{
6    cell::RefCell,
7    future::Future,
8    ops::DerefMut,
9    sync::atomic::{AtomicBool, AtomicPtr, Ordering},
10    task::Poll,
11};
12use critical_section::Mutex;
13use futures::task::AtomicWaker;
14
15// AT commands usually get a quick response, so there's only one active waiter at a time.
16// If two futures wait, then they will get bumped out and reregister.
17// This fighting only happens for a bit
18
19/// Set to false if there's no at command in progress.
20/// There can only be one in progress at a time.
21static AT_PROGRESS: AtomicBool = AtomicBool::new(false);
22/// This waker gets called when the [AT_PROGRESS] is set to false.
23/// This can be used by a future to await being able to send a command.
24static AT_PROGRESS_WAKER: AtomicWaker = AtomicWaker::new();
25
26/// A pointer to where the data should be written to
27static AT_DATA: Mutex<RefCell<(AtomicPtr<u8>, usize)>> =
28    Mutex::new(RefCell::new((AtomicPtr::new(core::ptr::null_mut()), 0)));
29/// When the [AT_DATA] is updated, this waker is called so a future can be woken up.
30static AT_DATA_WAKER: AtomicWaker = AtomicWaker::new();
31
32/// The callback that will be called by nrfxlib when the at command has a response.
33/// The `resp` is a null-terminated string.
34unsafe extern "C" fn at_callback(resp: *const core::ffi::c_char) {
35    #[cfg(feature = "defmt")]
36    defmt::trace!(
37        "AT <- {}",
38        core::ffi::CStr::from_ptr(resp as _).to_str().unwrap()
39    );
40
41    // Store the data and wake the future that waits for it
42    critical_section::with(|cs| {
43        let mut data = AT_DATA.borrow_ref_mut(cs);
44        let (ptr, size) = data.deref_mut();
45
46        if ptr.get_mut().is_null() {
47            return;
48        }
49
50        // Copy the contents
51        let mut index = 0;
52        while index < *size && *resp.add(index) != 0 {
53            *ptr.get_mut().add(index) = *resp.add(index) as _;
54            index += 1;
55        }
56
57        // Reset the data so that the future knows that the callback was called
58        *ptr = AtomicPtr::default();
59        *size = 0;
60    });
61    AT_DATA_WAKER.wake();
62}
63
64/// Send an AT command to the modem.
65///
66/// The const `CAP` parameter is the size of the returned response string.
67/// It is ok to set this to 0 you don't need the response.
68///
69/// If the `CAP` is too small to contain the entire response, then the string is simply tuncated.
70pub async fn send_at<const CAP: usize>(command: &str) -> Result<ArrayString<CAP>, Error> {
71    SendATFuture {
72        state: Default::default(),
73        command: command.as_bytes(),
74        response: [0; CAP],
75    }
76    .await
77}
78
79/// Same as [send_at], but send a byte array (that must contain ascii chars) instead
80pub async fn send_at_bytes<const CAP: usize>(command: &[u8]) -> Result<ArrayString<CAP>, Error> {
81    SendATFuture {
82        state: Default::default(),
83        command,
84        response: [0; CAP],
85    }
86    .await
87}
88
89/// Sends a blocking AT command. The non-blocking variants should be preferred, but sometimes it's necessary to
90/// call this in e.g. a drop function.
91///
92/// If a capacity of 0 is given, then the command is given in a way where no textual response is gotten.
93/// A capacity of >0 will require you to have a capacity that is big enough to contain the full message.
94/// This is different from the async functions where the message is simply truncated.
95pub fn send_at_blocking<const CAP: usize>(command: &str) -> Result<ArrayString<CAP>, Error> {
96    #[cfg(feature = "defmt")]
97    defmt::trace!("AT -> {}", command);
98
99    let string = if CAP > 0 {
100        let mut buffer = [0; CAP];
101        unsafe {
102            nrfxlib_sys::nrf_modem_at_cmd(
103                buffer.as_mut_ptr() as _,
104                buffer.len(),
105                c"%.*s".as_ptr() as *const core::ffi::c_char,
106                command.len(),
107                command.as_ptr(),
108            )
109            .into_result()?;
110        }
111
112        let mut return_string = ArrayString::from_byte_string(&buffer).unwrap();
113        strip_null_bytes(&mut return_string);
114        return_string
115    } else {
116        unsafe {
117            nrfxlib_sys::nrf_modem_at_printf(
118                c"%.*s".as_ptr() as *const core::ffi::c_char,
119                command.len(),
120                command.as_ptr(),
121            )
122            .into_result()?;
123        }
124
125        ArrayString::new()
126    };
127
128    #[cfg(feature = "defmt")]
129    defmt::trace!("AT <- {}", string.as_str());
130
131    Ok(string)
132}
133
134struct SendATFuture<'c, const CAP: usize> {
135    state: SendATState,
136    command: &'c [u8],
137    response: [u8; CAP],
138}
139
140impl<const CAP: usize> Future for SendATFuture<'_, CAP> {
141    type Output = Result<ArrayString<CAP>, Error>;
142
143    fn poll(
144        mut self: core::pin::Pin<&mut Self>,
145        cx: &mut core::task::Context<'_>,
146    ) -> core::task::Poll<Self::Output> {
147        match self.state {
148            SendATState::WaitingOnAccess => {
149                if AT_PROGRESS.compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst)
150                    == Ok(false)
151                {
152                    self.state = SendATState::AccessGranted;
153                    cx.waker().wake_by_ref();
154                    Poll::Pending
155                } else {
156                    AT_PROGRESS_WAKER.register(cx.waker());
157                    Poll::Pending
158                }
159            }
160            SendATState::AccessGranted => {
161                // Set the data pointer. This can be done because we are pinned
162                critical_section::with(|cs| {
163                    *AT_DATA.borrow_ref_mut(cs) = (AtomicPtr::new(self.response.as_mut_ptr()), CAP)
164                });
165                AT_DATA_WAKER.register(cx.waker());
166
167                #[cfg(feature = "defmt")]
168                defmt::trace!(
169                    "AT -> {}",
170                    defmt::unwrap!(core::str::from_utf8(self.command).ok())
171                );
172
173                let result = unsafe {
174                    nrfxlib_sys::nrf_modem_at_cmd_async(
175                        Some(at_callback),
176                        c"%.*s".as_ptr() as *const core::ffi::c_char,
177                        self.command.len(),
178                        self.command.as_ptr(),
179                    )
180                    .into_result()
181                };
182
183                match result {
184                    Ok(_) => {
185                        self.state = SendATState::WaitingOnData;
186                        Poll::Pending
187                    }
188                    Err(e) => Poll::Ready(Err(e)),
189                }
190            }
191            SendATState::WaitingOnData => critical_section::with(|cs| {
192                let mut data = AT_DATA.borrow_ref_mut(cs);
193
194                if data.0.get_mut().is_null() {
195                    // The callback was called and we have the response
196
197                    // Because we handle with c strings, let's at least make the last byte in the buffer a null character
198                    if let Some(last) = self.response.last_mut() {
199                        *last = 0
200                    }
201
202                    let mut return_string = ArrayString::from_byte_string(&self.response).unwrap();
203                    strip_null_bytes(&mut return_string);
204
205                    Poll::Ready(Ok(return_string))
206                } else {
207                    AT_DATA_WAKER.register(cx.waker());
208                    Poll::Pending
209                }
210            }),
211        }
212    }
213}
214
215impl<const CAP: usize> Drop for SendATFuture<'_, CAP> {
216    fn drop(&mut self) {
217        match self.state {
218            SendATState::WaitingOnAccess => {}
219            SendATState::AccessGranted | SendATState::WaitingOnData => {
220                // Reset the data. We don't have to worry that somebody else accessed this
221                // because they're only allowed to after we've set `AT_PROGRESS` back to false which we're
222                // gonna do after this.
223                critical_section::with(|cs| {
224                    *AT_DATA.borrow_ref_mut(cs) = (AtomicPtr::default(), 0)
225                });
226                AT_PROGRESS.store(false, Ordering::SeqCst);
227                AT_PROGRESS_WAKER.wake();
228            }
229        }
230    }
231}
232
233#[derive(Default)]
234enum SendATState {
235    #[default]
236    WaitingOnAccess,
237    AccessGranted,
238    WaitingOnData,
239}
240
241fn strip_null_bytes<const CAP: usize>(string: &mut ArrayString<CAP>) {
242    if let Some((reverse_index, _)) = string
243        .bytes()
244        .rev()
245        .enumerate()
246        .find(|(_, byte)| *byte != 0)
247    {
248        let index = string.len() - reverse_index;
249        string.truncate(index);
250    }
251}