at_commands/
builder.rs

1//! Implementation of the CommandBuilder
2
3/// # CommandBuilder
4/// A builder struct for AT Commands
5///
6/// ## Summary
7/// This can be used to build:
8/// * A test command in the form `AT{name}=?`
9/// * A query command in the form `AT{name}?`
10/// * A set command in the form `AT{name}={param},{param},{param}`
11/// * An execute command in the form `AT{name}`
12///
13/// ## Example
14/// ```rust
15/// use at_commands::builder::CommandBuilder;
16///
17/// let mut buffer = [0; 128];
18///
19/// // Make a query command
20/// let result = CommandBuilder::create_query(&mut buffer, true)
21///     .named("+MYQUERY")
22///     .finish()
23///     .unwrap();
24///
25/// // Buffer now contains "AT+MYQUERY?"
26/// // Copy or DMA the resulting slice to the device.
27///
28/// // Make a set command
29/// let result = CommandBuilder::create_set(&mut buffer, false)
30///     .named("+MYSET")
31///     .with_int_parameter(42)
32///     .finish()
33///     .unwrap();
34///
35/// // Buffer now contains "+MYSET=42"
36/// // Copy or DMA the resulting slice to the device.
37/// ```
38pub struct CommandBuilder<'a, STAGE> {
39    buffer: &'a mut [u8],
40    index: usize,
41    phantom: core::marker::PhantomData<STAGE>,
42}
43
44impl<'a> CommandBuilder<'a, Uninitialized> {
45    /// Creates a builder for a test command.
46    ///
47    /// The given buffer is used to build the command in and must be big enough to contain it.
48    pub fn create_test(
49        buffer: &'a mut [u8],
50        at_prefix: bool,
51    ) -> CommandBuilder<'a, Initialized<Test>> {
52        let mut builder = CommandBuilder::<'a, Initialized<Test>> {
53            buffer,
54            index: 0,
55            phantom: Default::default(),
56        };
57
58        if at_prefix {
59            builder.try_append_data(b"AT");
60        }
61
62        builder
63    }
64
65    /// Creates a builder for a query command.
66    ///
67    /// The given buffer is used to build the command in and must be big enough to contain it.
68    pub fn create_query(
69        buffer: &'a mut [u8],
70        at_prefix: bool,
71    ) -> CommandBuilder<'a, Initialized<Query>> {
72        let mut builder = CommandBuilder::<'a, Initialized<Query>> {
73            buffer,
74            index: 0,
75            phantom: Default::default(),
76        };
77
78        if at_prefix {
79            builder.try_append_data(b"AT");
80        }
81
82        builder
83    }
84
85    /// Creates a builder for a set command.
86    ///
87    /// The given buffer is used to build the command in and must be big enough to contain it.
88    pub fn create_set(
89        buffer: &'a mut [u8],
90        at_prefix: bool,
91    ) -> CommandBuilder<'a, Initialized<Set>> {
92        let mut builder = CommandBuilder::<'a, Initialized<Set>> {
93            buffer,
94            index: 0,
95            phantom: Default::default(),
96        };
97
98        if at_prefix {
99            builder.try_append_data(b"AT");
100        }
101
102        builder
103    }
104
105    /// Creates a builder for an test execute.
106    ///
107    /// The given buffer is used to build the command in and must be big enough to contain it.
108    pub fn create_execute(
109        buffer: &'a mut [u8],
110        at_prefix: bool,
111    ) -> CommandBuilder<'a, Initialized<Execute>> {
112        let mut builder = CommandBuilder::<'a, Initialized<Execute>> {
113            buffer,
114            index: 0,
115            phantom: Default::default(),
116        };
117
118        if at_prefix {
119            builder.try_append_data(b"AT");
120        }
121
122        builder
123    }
124}
125impl<ANY> CommandBuilder<'_, ANY> {
126    /// Tries to append data to the buffer.
127    ///
128    /// If it won't fit, it silently fails and won't copy the data.
129    /// The index field is incremented no matter what.
130    fn try_append_data(&mut self, data: &[u8]) {
131        let data_length = data.len();
132
133        // Why not just use copy_from_slice?
134        // That can give a panic and thus dumps a lot of fmt code in the binary.
135        // The compiler can check every aspect of this and so the code will never panic.
136
137        // Does the buffer have enough space left?
138        if let Some(buffer_slice) = self.buffer.get_mut(self.index..(self.index + data_length)) {
139            // Yes, zip the buffer with the data
140            for (buffer, data) in buffer_slice.iter_mut().zip(data) {
141                // Copy over the bytes.
142                *buffer = *data;
143            }
144        }
145
146        // Increment the index
147        self.index += data_length;
148    }
149}
150
151impl<'a, N: Nameable> CommandBuilder<'a, Initialized<N>> {
152    /// Set the name of the command.
153    pub fn named<T: AsRef<[u8]>>(mut self, name: T) -> CommandBuilder<'a, N> {
154        self.try_append_data(name.as_ref());
155        self.try_append_data(N::NAME_SUFFIX);
156
157        CommandBuilder::<'a, N> {
158            buffer: self.buffer,
159            index: self.index,
160            phantom: Default::default(),
161        }
162    }
163}
164
165impl CommandBuilder<'_, Set> {
166    /// Add an integer parameter.
167    pub fn with_int_parameter<INT: Into<i32>>(mut self, value: INT) -> Self {
168        let mut formatting_buffer = [0; crate::formatter::MAX_INT_DIGITS];
169        self.try_append_data(crate::formatter::write_int(
170            &mut formatting_buffer,
171            value.into(),
172        ));
173        self.try_append_data(b",");
174        self
175    }
176
177    /// Add a string parameter
178    pub fn with_string_parameter<T: AsRef<[u8]>>(mut self, value: T) -> Self {
179        self.try_append_data(b"\"");
180        self.try_append_data(value.as_ref());
181        self.try_append_data(b"\"");
182        self.try_append_data(b",");
183        self
184    }
185
186    /// Add an optional integer parameter.
187    pub fn with_optional_int_parameter<INT: Into<i32>>(self, value: Option<INT>) -> Self {
188        match value {
189            None => self.with_empty_parameter(),
190            Some(value) => self.with_int_parameter(value),
191        }
192    }
193
194    /// Add an optional string parameter.
195    pub fn with_optional_string_parameter<T: AsRef<[u8]>>(self, value: Option<T>) -> Self {
196        match value {
197            None => self.with_empty_parameter(),
198            Some(value) => self.with_string_parameter(value),
199        }
200    }
201
202    /// Add a comma, representing an unset optional parameter.
203    pub fn with_empty_parameter(mut self) -> Self {
204        self.try_append_data(b",");
205        self
206    }
207
208    /// Add an unformatted parameter
209    pub fn with_raw_parameter<T: AsRef<[u8]>>(mut self, value: T) -> Self {
210        self.try_append_data(value.as_ref());
211        self.try_append_data(b",");
212        self
213    }
214}
215
216impl<'a, F: Finishable> CommandBuilder<'a, F> {
217    /// Finishes the builder.
218    ///
219    /// When Ok, it returns a slice with the built command.
220    /// The slice points to the same memory as the buffer,
221    /// but is only as long as is required to contain the command.
222    ///
223    /// The command length is thus the length of the slice.
224    ///
225    /// If the buffer was not long enough,
226    /// then an Err is returned with the size that was required for it to succeed.
227    pub fn finish(self) -> Result<&'a [u8], usize> {
228        self.finish_with(b"\r\n")
229    }
230
231    /// Finishes the builder.
232    ///
233    /// With the terminator variable, you can decide how to end the command.
234    /// Normally this is `\r\n`.
235    ///
236    /// ```rust
237    /// use at_commands::builder::CommandBuilder;
238    ///
239    /// let mut buffer = [0; 128];
240    ///
241    /// // Make a query command
242    /// let result = CommandBuilder::create_query(&mut buffer, true)
243    ///     .named("+MYQUERY")
244    ///     .finish_with(b"\0")
245    ///     .unwrap();
246    /// ```
247    ///
248    /// When Ok, it returns a slice with the built command.
249    /// The slice points to the same memory as the buffer,
250    /// but is only as long as is required to contain the command.
251    ///
252    /// The command length is thus the length of the slice.
253    ///
254    /// If the buffer was not long enough,
255    /// then an Err is returned with the size that was required for it to succeed.
256    pub fn finish_with(mut self, terminator: &[u8]) -> Result<&'a [u8], usize> {
257        // if last byte is a comma, decrement index to drop it
258        if let Some(c) = self.buffer.get(self.index - 1) {
259            if *c == b',' {
260                self.index -= 1;
261            }
262        }
263        self.try_append_data(terminator);
264
265        if self.index > self.buffer.len() {
266            Err(self.index)
267        } else {
268            Ok(&self.buffer[0..self.index])
269        }
270    }
271}
272
273/// Marker struct for uninitialized builders.
274pub struct Uninitialized;
275/// Marker struct for initialized builders.
276/// The T type is the type the builder will be marked after it has been named.
277pub struct Initialized<T>(core::marker::PhantomData<T>);
278
279/// Marker struct for builders that produce a test command.
280pub struct Test;
281/// Marker struct for builders that produce a query command.
282pub struct Query;
283/// Marker struct for builders that produce a set command.
284pub struct Set;
285/// Marker struct for builders that produce a execute command.
286pub struct Execute;
287
288/// A trait that can be implemented for marker structs to indicate that the command is ready to be finished.
289pub trait Finishable {}
290impl Finishable for Test {}
291impl Finishable for Query {}
292impl Finishable for Set {}
293impl Finishable for Execute {}
294
295/// A trait that can be implemented for marker structs to indicate that the command is ready to be named.
296pub trait Nameable {
297    /// The data that must be put after a name to comply with the type of command that is named.
298    const NAME_SUFFIX: &'static [u8];
299}
300impl Nameable for Test {
301    const NAME_SUFFIX: &'static [u8] = b"=?";
302}
303impl Nameable for Query {
304    const NAME_SUFFIX: &'static [u8] = b"?";
305}
306impl Nameable for Set {
307    const NAME_SUFFIX: &'static [u8] = b"=";
308}
309impl Nameable for Execute {
310    const NAME_SUFFIX: &'static [u8] = b"";
311}
312
313#[cfg(test)]
314mod tests {
315    use super::*;
316
317    #[test]
318    fn test_command() {
319        let mut buffer = [0; 128];
320        let value = CommandBuilder::create_test(&mut buffer, true)
321            .named("+TEST")
322            .finish()
323            .unwrap();
324
325        assert_eq!(core::str::from_utf8(value).unwrap(), "AT+TEST=?\r\n");
326    }
327
328    #[test]
329    fn test_query() {
330        let mut buffer = [0; 128];
331        let value = CommandBuilder::create_query(&mut buffer, true)
332            .named("+QUERY")
333            .finish()
334            .unwrap();
335
336        assert_eq!(core::str::from_utf8(value).unwrap(), "AT+QUERY?\r\n");
337    }
338
339    #[test]
340    fn test_set() {
341        let mut buffer = [0; 128];
342        let value = CommandBuilder::create_set(&mut buffer, true)
343            .named("+SET")
344            .with_int_parameter(12345)
345            .with_string_parameter("my_string_param")
346            .with_int_parameter(67)
347            .with_int_parameter(89)
348            .finish()
349            .unwrap();
350
351        assert_eq!(
352            core::str::from_utf8(value).unwrap(),
353            "AT+SET=12345,\"my_string_param\",67,89\r\n"
354        );
355    }
356
357    #[test]
358    fn test_execute() {
359        let mut buffer = [0; 128];
360        let value = CommandBuilder::create_execute(&mut buffer, true)
361            .named("+EXECUTE")
362            .finish()
363            .unwrap();
364
365        assert_eq!(core::str::from_utf8(value).unwrap(), "AT+EXECUTE\r\n");
366    }
367
368    #[test]
369    fn test_buffer_too_short() {
370        let mut buffer = [0; 5];
371        assert!(CommandBuilder::create_execute(&mut buffer, true)
372            .named("+BUFFERLENGTH")
373            .finish()
374            .is_err());
375        assert!(CommandBuilder::create_execute(&mut buffer, true)
376            .named("+A")
377            .finish()
378            .is_err()); // too short by only one byte
379    }
380
381    #[test]
382    fn test_buffer_exact_size() {
383        let mut buffer = [0; 32];
384        let value = CommandBuilder::create_execute(&mut buffer[..8], true)
385            .named("+GMR")
386            .finish()
387            .unwrap();
388
389        assert_eq!(core::str::from_utf8(value).unwrap(), "AT+GMR\r\n");
390
391        let value = CommandBuilder::create_set(&mut buffer[..19], true)
392            .named("+CWRECONNCFG")
393            .with_int_parameter(15)
394            .finish()
395            .unwrap();
396
397        assert_eq!(
398            core::str::from_utf8(value).unwrap(),
399            "AT+CWRECONNCFG=15\r\n"
400        );
401
402        let value = CommandBuilder::create_query(&mut buffer[..14], true)
403            .named("+UART_CUR")
404            .finish()
405            .unwrap();
406
407        assert_eq!(core::str::from_utf8(value).unwrap(), "AT+UART_CUR?\r\n");
408    }
409
410    #[test]
411    fn test_terminator() {
412        let mut buffer = [0; 128];
413        let value = CommandBuilder::create_test(&mut buffer, true)
414            .named("+TEST")
415            .finish_with(b"\0")
416            .unwrap();
417
418        assert_eq!(core::str::from_utf8(value).unwrap(), "AT+TEST=?\0");
419    }
420
421    #[test]
422    fn test_optional() {
423        let mut buffer = [0; 128];
424        let value = CommandBuilder::create_set(&mut buffer, true)
425            .named("+CCUG")
426            .with_empty_parameter()
427            .with_optional_int_parameter(Some(9))
428            .finish_with(b"\r")
429            .unwrap();
430        // see https://www.multitech.com/documents/publications/manuals/s000453c.pdf
431        // pages 8 and 85 for command and 150 for CR ending
432        assert_eq!(core::str::from_utf8(value).unwrap(), "AT+CCUG=,9\r");
433
434        let value = CommandBuilder::create_set(&mut buffer, true)
435            .named("+BLEGATTSSETATTR")
436            .with_int_parameter(1)
437            .with_int_parameter(1)
438            .with_empty_parameter()
439            .with_int_parameter(4)
440            .finish()
441            .unwrap();
442        // https://docs.espressif.com/projects/esp-at/en/latest/AT_Command_Set/BLE_AT_Commands.html#cmd-GSSETA
443        assert_eq!(
444            core::str::from_utf8(value).unwrap(),
445            "AT+BLEGATTSSETATTR=1,1,,4\r\n"
446        );
447
448        let value = CommandBuilder::create_set(&mut buffer, true)
449            .named("+HTTPCLIENT")
450            .with_int_parameter(2)
451            .with_int_parameter(1)
452            .with_optional_string_parameter(Some("http://localpc/ip"))
453            .with_empty_parameter()
454            .with_empty_parameter()
455            .with_int_parameter(1)
456            .finish()
457            .unwrap();
458
459        assert_eq!(
460            core::str::from_utf8(value).unwrap(),
461            "AT+HTTPCLIENT=2,1,\"http://localpc/ip\",,,1\r\n"
462        );
463    }
464
465    #[test]
466    fn test_raw_parameter() {
467        let mut buffer = [0; 128];
468        let value = CommandBuilder::create_set(&mut buffer, true)
469            .named("+CPIN")
470            .with_raw_parameter(b"1234")
471            .with_optional_int_parameter(Some(9))
472            .finish_with(b"\r")
473            .unwrap();
474        assert_eq!(core::str::from_utf8(value).unwrap(), "AT+CPIN=1234,9\r");
475    }
476}