atat icon indicating copy to clipboard operation
atat copied to clipboard

[RFC] Allow streaming/chunked type

Open MathiasKoch opened this issue 3 years ago • 1 comments

We were toying around with the idea of introducing a newtype wrapper that would allow for streaming/chunked payloads of AT commands.

Motivation

The main motivation would be to be able to reduce the stack usage of the library, by reducing the size of BufLen, as this currently needs to be able to hold the full WORST-CASE command length at all times. For all of our applications, that would be a socket ingress payload, usually in the magnitude of 512 bytes + overhead. Almost every other command we have in our applications of ATAT would be able to run with a much smaller BufLen of say 128 bytes. This could probably be even further reduced by fixing #82.

In our applications of ATAT, BufLen is 1024 bytes.

Command types to consider

  • Large byte payloads (eg. socket RX)
  • Vec types (eg. wifi ssid scan or cellular operator scan)

A typical command suitable for this would be AT+USORD:

Syntax:

AT+USORD=< socket >,< length >

Response:

+USORD: < socket >,< length >,< data in the ASCII [0x00,0xFF] range > OK

Example:

AT+USORD=3,16 +USORD: 3,16,"16 bytes of data" OK

Implementation suggestion


#[derive(Clone, AtatCmd)]
#[at_cmd("+USORD", SocketData)]
pub struct ReadSocketData {
    #[at_arg(position = 0)]
    pub socket: SocketHandle,
    #[at_arg(position = 1)]
    pub length: usize,
}

/// Example AT command with streaming payload
#[derive(Clone, AtatResp)]
pub struct SocketData<'a> {
    #[at_arg(position = 0)]
    pub socket: SocketHandle,
    #[at_arg(position = 1)]
    pub length: usize,
    #[at_arg(position = 2)]
    pub data: Streaming<'a, U128>,
}


pub fn example_using_stream() {
    match at_client.send(&ReadSocketData { socket: SocketHandle(0), length: 512 }) {
        Err(nb::Error::WouldBlock) => {
            // This would indicate that the first part of the response has yet to be received
        }
        Err(nb::Error::Other(_)) => {
            // This would indicate that an actual error occured on the AT line, 
            // or a timeout occured while waiting for the first part of the response.
        }
        Ok(response) => {
            // `response.length` can be used to verify that all chunks are received in this case
            println!("Socket: {:?}, length: {:?}", response.socket, response.length);

            // `at_client` is locked for the full lifetime of `response`, 
            // but the ingress part of `ATAT` should still be ingressing and pushing to the response queue.

            loop {
                match response.data.poll_next() {
                    Poll::Pending => {}
                    Poll::Ready(None) => break
                    Poll::Ready(chunk) => {
                        // Data can be enqueued to a socket buffer, one chunk at a time.
                        socket_layer.enqueue_slice(&chunk);
                    }
                }
            }

            // Above loop should be possible to rewrite using async, as:
            while let Some(chunk) in response.data.next().await {
                // Data can be enqueued to a socket buffer, one chunk at a time.
                socket_layer.enqueue_slice(&chunk);
            }
        }
    }
}


// Below here is given by `ATAT`
pub type ResItem<BufLen> = Result<AtItem<BufLen>, InternalError>;

pub enum AtItem<BufLen> {
    Response(Vec<u8, BufLen>),
    Chunk(Vec<u8, BufLen>)
}

pub struct Streaming<'a, BufLen> {
    consumer: &'a ResConsumer<BufLen>,
}

impl<'a, BufLen> Stream for Streaming<'a, BufLen> {
    type item = Vec<u8, BufLen>;

    fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
        match self.consumer.peek() {
            Some(Ok(AtItem::Chunk(bytes)) => {
                unsafe { self.consumer.dequeue_unchecked(); }
                Poll::Ready(Some(bytes))
            }
            // The next item in the queue is a new response
            Some(Ok(AtItem::Response(_)) => {
                Poll::Ready(None)
            }
            // FIXME: Is this correct?
            Some(Err(e)) => Poll::Ready(None),
            None => Poll::Pending
        }
    }
}

impl<'a, BufLen> Drop for Streaming<'a, BufLen> {
    fn drop(&mut self) {
        // Dequeue items until there are no more `AtItem::Chunk`
        while let Some(Ok(AtItem::Chunk(_)) = self.consumer.peek() {
            self.consumer.dequeue();
        }
    }
}

Apart from above implementation steps, the parse() function of commands needs to be altered to handle AtItem::Response & constructing Streaming, and the Digester needs to be able to handle digesting in chunks. Perhaps a new StreamingDigester would allow non-breaking changes?

Considerations

  • This should preferably not incour significant overhead if not using the Streaming wrapper
  • cmd.parse(..) would need to take a reference to self.res_c, which might be tricky to get right?
  • The lifetime of A::Response might be tricky to get right?
  • ~~Above implementation suggestion will not be able to handle Vec<T> response streaming.~~ See below comment on handling Vec<T>
  • Not sure if this should be implemented with futures::Stream directly, or a similar self-defined trait. Unknown part would be how to handle self: Pin<&mut Self>, cx: &mut Context<'_>
  • Can this be applied to URC's as well?

Limitations

  • The streaming parameter has to be the last parameter of the response! I think this will always be the case anyways, but am not sure? (This could be checked for any type deriving the AtatCmd at compile time)
  • The length / chunk-size of Streaming<'a, U128> (U128 here) has to be equal to BufLen, as the AtItem::Chunk is dequeued on success. Could probably be allowed to <= BufLen, if we kept a chunk index in Streaming, and only dequeued the chunk on the remainder part, but i am not sure we would win anything by it?

Further improvements

  • It would be awesome if we could fill out the size_hint() function somehow, on the responses where that would be known/can be calculated. For example for the above SocketData, the size_hint would be response.length / 128.

MathiasKoch avatar May 06 '21 10:05 MathiasKoch

Above implementation suggestion will not be able to handle Vec<T> response streaming.

An example of an AT command that would benefit alot from streaming Vec<T> like handling would be AT+UWSCAN.

Example of `AT+UWSCAN`

AT+UWSCAN
+UWSCAN:EA63DA77C485,1,"emendo-Blackbird",1,-54,18,8,8
+UWSCAN:E063DA74C38A,1,"Blackbird-device",1,-62,18,8,8
+UWSCAN:6009C32A1811,1,"Factbird-Duo-768e8aa",1,-40,0,0,0
+UWSCAN:E063DA74A587,1,"Blackbird-device",1,-59,18,8,8
+UWSCAN:E063DA77C485,1,"Blackbird-device",1,-54,18,8,8
+UWSCAN:E663DA74A587,1,"emendo-guest",1,-60,18,8,8 +UWSCAN:E063DA74C38A,1,"Blackbird-device",1,-62,18,8,8 +UWSCAN:EA63DA74A587,1,"emendo-Blackbird",1,-59,18,8,8 +UWSCAN:E663DA77C485,1,"emendo-guest",1,-54,18,8,8 +UWSCAN:E263DA74C38A,1,"emendo-guest",1,-62,18,8,8 +UWSCAN:EA63DA77C485,1,"emendo-Blackbird",1,-54,18,8,8 +UWSCAN:F263DA74C38A,1,"emendo-Blackbird",1,-62,18,8,8 +UWSCAN:6009C32A1811,1,"Factbird-Duo-768e8aa",1,-39,0,0,0 +UWSCAN:E063DA74C38A,1,"Blackbird-device",1,-62,18,8,8 +UWSCAN:E063DA77C485,1,"Blackbird-device",1,-53,18,8,8 +UWSCAN:B4FBE4C732FD,1,"Blackbird-device",1,-76,18,8,8 +UWSCAN:E663DA77C485,1,"emendo-guest",1,-54,18,8,8 +UWSCAN:EA63DA77C485,1,"emendo-Blackbird",1,-53,18,8,8 +UWSCAN:E063DA74C38A,1,"Blackbird-device",1,-62,18,8,8 +UWSCAN:E263DA74C38A,1,"emendo-guest",1,-62,18,8,8 +UWSCAN:F263DA74C38A,1,"emendo-Blackbird",1,-62,18,8,8 +UWSCAN:E063DA74A587,1,"Blackbird-device",1,-60,18,8,8 +UWSCAN:B4FBE4C7325C,1,"Blackbird-device",1,-74,18,8,8 +UWSCAN:EA63DA77C485,1,"emendo-Blackbird",1,-53,18,8,8 +UWSCAN:BAFBE4C7325C,1,"emendo-guest",1,-75,18,8,8 +UWSCAN:E663DA74A587,1,"emendo-guest",1,-60,18,8,8 +UWSCAN:BAFBE4C732FD,1,"emendo-guest",1,-76,18,8,8 +UWSCAN:EA63DA74A587,1,"emendo-Blackbird",1,-60,18,8,8 +UWSCAN:EA63DA74A587,1,"emendo-Blackbird",1,-59,18,8,8 +UWSCAN:EA63DA74A587,1,"emendo-Blackbird",1,-60,18,8,8 +UWSCAN:BEFBE4C732FD,1,"emendo-Blackbird",1,-77,18,8,8 +UWSCAN:BEFBE4C7325C,1,"emendo-Blackbird",1,-75,18,8,8 +UWSCAN:E663DA74A587,1,"emendo-guest",1,-59,18,8,8 +UWSCAN:B4FBE4C7325C,1,"Blackbird-device",1,-75,18,8,8 +UWSCAN:E263DA74C38A,1,"emendo-guest",1,-62,18,8,8 +UWSCAN:EA63DA74A664,1,"emendo-Blackbird",6,-56,18,8,8 +UWSCAN:E063DA74A664,1,"Blackbird-device",6,-56,18,8,8 +UWSCAN:B4FBE4C7331A,1,"Blackbird-device",6,-65,18,8,8 +UWSCAN:E663DA74A664,1,"emendo-guest",6,-56,18,8,8 +UWSCAN:BAFBE4C7331A,1,"emendo-guest",6,-65,18,8,8 +UWSCAN:EA63DA74A664,1,"emendo-Blackbird",6,-56,18,8,8 +UWSCAN:BAFBE4C7331A,1,"emendo-guest",6,-65,18,8,8 +UWSCAN:BEFBE4C7331A,1,"emendo-Blackbird",6,-64,18,8,8 +UWSCAN:EA63DA74C515,1,"emendo-Blackbird",6,-69,18,8,8 +UWSCAN:E063DA74A664,1,"Blackbird-device",6,-56,18,8,8 +UWSCAN:B4FBE4C731AA,1,"Blackbird-device",6,-70,18,8,8 +UWSCAN:E663DA74A664,1,"emendo-guest",6,-55,18,8,8 +UWSCAN:E063DA74C515,1,"Blackbird-device",6,-69,18,8,8 +UWSCAN:EA63DA74A664,1,"emendo-Blackbird",6,-57,18,8,8 +UWSCAN:B4FBE4C7331A,1,"Blackbird-device",6,-65,18,8,8 +UWSCAN:E663DA74C515,1,"emendo-guest",6,-69,18,8,8 +UWSCAN:BAFBE4C7331A,1,"emendo-guest",6,-64,18,8,8 +UWSCAN:B4FBE4C731AA,1,"Blackbird-device",6,-71,18,8,8 +UWSCAN:BEFBE4C731AA,1,"emendo-Blackbird",6,-71,18,8,8 +UWSCAN:BEFBE4C7331A,1,"emendo-Blackbird",6,-65,18,8,8 +UWSCAN:BEFBE4C7331A,1,"emendo-Blackbird",6,-65,18,8,8 +UWSCAN:E663DA74C62C,1,"emendo-guest",11,-60,18,8,8 +UWSCAN:001E42258D9C,1,"RUT_8D9C_2G",11,-62,18,12,4 +UWSCAN:E063DA74A530,1,"Blackbird-device",11,-64,18,8,8 +UWSCAN:B4FBE4C731B2,1,"Blackbird-device",11,-71,18,8,8 +UWSCAN:B4FBE4C731B2,1,"Blackbird-device",11,-71,18,8,8 +UWSCAN:E663DA74A530,1,"emendo-guest",11,-64,18,8,8 +UWSCAN:E063DA74C62C,1,"Blackbird-device",11,-60,18,8,8 +UWSCAN:BAFBE4C731B2,1,"emendo-guest",11,-72,18,8,8 +UWSCAN:BEFBE4C731B2,1,"emendo-Blackbird",11,-72,18,8,8 +UWSCAN:E663DA74C62C,1,"emendo-guest",11,-60,18,8,8 +UWSCAN:EA63DA74A530,1,"emendo-Blackbird",11,-64,18,8,8 +UWSCAN:EA63DA74C62C,1,"emendo-Blackbird",11,-59,18,8,8 +UWSCAN:EA63DA74C62C,1,"emendo-Blackbird",11,-59,18,8,8 +UWSCAN:E063DA74C62C,1,"Blackbird-device",11,-59,18,8,8 +UWSCAN:E663DA74C62C,1,"emendo-guest",11,-60,18,8,8 +UWSCAN:001E42258D9C,1,"RUT_8D9C_2G",11,-60,18,12,4 +UWSCAN:E063DA74A530,1,"Blackbird-device",11,-64,18,8,8 +UWSCAN:E663DA74A530,1,"emendo-guest",11,-64,18,8,8 +UWSCAN:E663DA74A530,1,"emendo-guest",11,-64,18,8,8 +UWSCAN:EA63DA74A530,1,"emendo-Blackbird",11,-64,18,8,8 +UWSCAN:EA63DA74C62C,1,"emendo-Blackbird",11,-59,18,8,8 +UWSCAN:B4FBE4C731B2,1,"Blackbird-device",11,-71,18,8,8 +UWSCAN:60634C263349,1,"DWR-921-3348",11,-89,26,12,4 +UWSCAN:EA63DA74A530,1,"emendo-Blackbird",11,-64,18,8,8 +UWSCAN:9CB6D03EB4A3,1,"TEST",36,-41,0,0,0 +UWSCAN:9CB6D03EB4A3,1,"TEST",36,-41,0,0,0 +UWSCAN:EA63DA75C62C,1,"emendo-Blackbird",36,-57,18,8,8 +UWSCAN:E063DA75C62C,1,"Blackbird-device",36,-58,18,8,8 +UWSCAN:E663DA75C62C,1,"emendo-guest",36,-58,18,8,8 +UWSCAN:E063DA75C62C,1,"Blackbird-device",36,-58,18,8,8 +UWSCAN:E663DA75C62C,1,"emendo-guest",36,-58,18,8,8 +UWSCAN:EA63DA75C62C,1,"emendo-Blackbird",36,-58,18,8,8 +UWSCAN:EA63DA75C62C,1,"emendo-Blackbird",36,-58,18,8,8 +UWSCAN:E063DA75C62C,1,"Blackbird-device",36,-58,18,8,8 +UWSCAN:D8616240D55F,1,"ClickShare-ThinkingStudio",40,-88,18,8,8 +UWSCAN:D8616240D55F,1,"ClickShare-ThinkingStudio",40,-88,18,8,8 +UWSCAN:D8616240D55F,1,"ClickShare-ThinkingStudio",40,-88,18,8,8 +UWSCAN:B4FBE4C832FD,1,"Blackbird-device",44,-86,18,8,8 +UWSCAN:B4FBE4C832FD,1,"Blackbird-device",44,-87,18,8,8 +UWSCAN:BAFBE4C832FD,1,"emendo-guest",44,-87,18,8,8 +UWSCAN:BAFBE4C832FD,1,"emendo-guest",44,-87,18,8,8 +UWSCAN:BEFBE4C832FD,1,"emendo-Blackbird",44,-87,18,8,8 +UWSCAN:BEFBE4C832FD,1,"emendo-Blackbird",44,-87,18,8,8 +UWSCAN:B4FBE4C832FD,1,"Blackbird-device",44,-88,18,8,8 +UWSCAN:B4FBE4C8331A,1,"Blackbird-device",48,-70,18,8,8 +UWSCAN:BAFBE4C8331A,1,"emendo-guest",48,-71,18,8,8 +UWSCAN:BEFBE4C8331A,1,"emendo-Blackbird",48,-71,18,8,8 +UWSCAN:B4FBE4C8331A,1,"Blackbird-device",48,-71,18,8,8 +UWSCAN:BAFBE4C8331A,1,"emendo-guest",48,-71,18,8,8 +UWSCAN:BEFBE4C8331A,1,"emendo-Blackbird",48,-71,18,8,8 +UWSCAN:BAFBE4C8331A,1,"emendo-guest",48,-71,18,8,8 +UWSCAN:BEFBE4C831B2,1,"emendo-Blackbird",52,-79,18,8,8 +UWSCAN:B4FBE4C831B2,1,"Blackbird-device",52,-80,18,8,8 +UWSCAN:BAFBE4C831B2,1,"emendo-guest",52,-79,18,8,8 +UWSCAN:BEFBE4C831B2,1,"emendo-Blackbird",52,-85,18,8,8 +UWSCAN:B4FBE4C831B2,1,"Blackbird-device",52,-79,18,8,8 +UWSCAN:BAFBE4C831B2,1,"emendo-guest",52,-79,18,8,8 +UWSCAN:BEFBE4C831B2,1,"emendo-Blackbird",52,-79,18,8,8 +UWSCAN:B4FBE4C831B2,1,"Blackbird-device",52,-79,18,8,8 +UWSCAN:BAFBE4C831B2,1,"emendo-guest",52,-79,18,8,8 +UWSCAN:BEFBE4C831B2,1,"emendo-Blackbird",52,-79,18,8,8 +UWSCAN:BEFBE4C831B2,1,"emendo-Blackbird",52,-79,18,8,8 +UWSCAN:EA63DA75C515,1,"emendo-Blackbird",56,-80,18,8,8 +UWSCAN:E063DA75C38A,1,"Blackbird-device",56,-70,18,8,8 +UWSCAN:E063DA75C515,1,"Blackbird-device",56,-79,18,8,8 +UWSCAN:E263DA75C38A,1,"emendo-guest",56,-69,18,8,8 +UWSCAN:E663DA75C515,1,"emendo-guest",56,-79,18,8,8 +UWSCAN:F263DA75C38A,1,"emendo-Blackbird",56,-69,18,8,8 +UWSCAN:EA63DA75C515,1,"emendo-Blackbird",56,-79,18,8,8 +UWSCAN:E063DA75C38A,1,"Blackbird-device",56,-69,18,8,8 +UWSCAN:E063DA75C515,1,"Blackbird-device",56,-79,18,8,8 +UWSCAN:E063DA75C515,1,"Blackbird-device",56,-79,18,8,8 +UWSCAN:F263DA75C38A,1,"emendo-Blackbird",56,-69,18,8,8 +UWSCAN:E063DA75C38A,1,"Blackbird-device",56,-69,18,8,8 +UWSCAN:E063DA75C38A,1,"Blackbird-device",56,-69,18,8,8 +UWSCAN:E063DA75C38A,1,"Blackbird-device",56,-69,18,8,8 +UWSCAN:E063DA75C38A,1,"Blackbird-device",56,-69,18,8,8 +UWSCAN:E663DA75C515,1,"emendo-guest",56,-79,18,8,8 +UWSCAN:E263DA75C38A,1,"emendo-guest",56,-69,18,8,8 +UWSCAN:E263DA75C38A,1,"emendo-guest",56,-69,18,8,8 +UWSCAN:EA63DA75C515,1,"emendo-Blackbird",56,-79,18,8,8 +UWSCAN:F263DA75C38A,1,"emendo-Blackbird",56,-70,18,8,8 +UWSCAN:E063DA75C38A,1,"Blackbird-device",56,-70,18,8,8 +UWSCAN:E063DA75C515,1,"Blackbird-device",56,-79,18,8,8 +UWSCAN:E263DA75C38A,1,"emendo-guest",56,-69,18,8,8 +UWSCAN:E263DA75C38A,1,"emendo-guest",56,-69,18,8,8 +UWSCAN:F263DA75C38A,1,"emendo-Blackbird",56,-69,18,8,8 +UWSCAN:E663DA75C515,1,"emendo-guest",56,-79,18,8,8 +UWSCAN:F263DA75C38A,1,"emendo-Blackbird",56,-69,18,8,8 +UWSCAN:EA63DA75C515,1,"emendo-Blackbird",56,-79,18,8,8 +UWSCAN:EA63DA75C515,1,"emendo-Blackbird",56,-79,18,8,8 +UWSCAN:E063DA75C38A,1,"Blackbird-device",56,-69,18,8,8 +UWSCAN:E063DA75C515,1,"Blackbird-device",56,-79,18,8,8 +UWSCAN:E063DA75C515,1,"Blackbird-device",56,-79,18,8,8 +UWSCAN:E663DA75C515,1,"emendo-guest",56,-79,18,8,8 +UWSCAN:E663DA75C515,1,"emendo-guest",56,-79,18,8,8 +UWSCAN:EA63DA75C515,1,"emendo-Blackbird",56,-79,18,8,8 +UWSCAN:EA63DA75C515,1,"emendo-Blackbird",56,-79,18,8,8 +UWSCAN:EA63DA75C515,1,"emendo-Blackbird",56,-80,18,8,8 +UWSCAN:E063DA75C515,1,"Blackbird-device",56,-80,18,8,8 +UWSCAN:BEFBE4C8325C,1,"emendo-Blackbird",60,-78,18,8,8 +UWSCAN:B4FBE4C8325C,1,"Blackbird-device",60,-79,18,8,8 +UWSCAN:BAFBE4C8325C,1,"emendo-guest",60,-79,18,8,8 +UWSCAN:BEFBE4C8325C,1,"emendo-Blackbird",60,-79,18,8,8 +UWSCAN:B4FBE4C8325C,1,"Blackbird-device",60,-79,18,8,8 +UWSCAN:BAFBE4C8325C,1,"emendo-guest",60,-79,18,8,8 +UWSCAN:BAFBE4C8325C,1,"emendo-guest",60,-80,18,8,8 +UWSCAN:BEFBE4C8325C,1,"emendo-Blackbird",60,-80,18,8,8 +UWSCAN:BEFBE4C8325C,1,"emendo-Blackbird",60,-79,18,8,8 +UWSCAN:E063DA75A664,1,"Blackbird-device",64,-57,18,8,8 +UWSCAN:E663DA75A664,1,"emendo-guest",64,-58,18,8,8 +UWSCAN:EA63DA75A664,1,"emendo-Blackbird",64,-58,18,8,8 +UWSCAN:E063DA75A664,1,"Blackbird-device",64,-58,18,8,8 +UWSCAN:E663DA75A664,1,"emendo-guest",64,-57,18,8,8 +UWSCAN:E063DA75A664,1,"Blackbird-device",64,-57,18,8,8 +UWSCAN:E663DA75A664,1,"emendo-guest",64,-57,18,8,8 +UWSCAN:EA63DA75A664,1,"emendo-Blackbird",64,-58,18,8,8 +UWSCAN:E063DA75A664,1,"Blackbird-device",64,-57,18,8,8 +UWSCAN:E663DA75A664,1,"emendo-guest",64,-57,18,8,8 +UWSCAN:EA63DA75A664,1,"emendo-Blackbird",64,-58,18,8,8 +UWSCAN:E063DA75A664,1,"Blackbird-device",64,-58,18,8,8 +UWSCAN:EA63DA78C485,1,"emendo-Blackbird",100,-63,18,8,8 +UWSCAN:E063DA78C485,1,"Blackbird-device",100,-63,18,8,8 +UWSCAN:E663DA78C485,1,"emendo-guest",100,-63,18,8,8 +UWSCAN:EA63DA78C485,1,"emendo-Blackbird",100,-64,18,8,8 +UWSCAN:E063DA78C485,1,"Blackbird-device",100,-63,18,8,8 +UWSCAN:E663DA78C485,1,"emendo-guest",100,-64,18,8,8 +UWSCAN:EA63DA78C485,1,"emendo-Blackbird",100,-64,18,8,8 +UWSCAN:E663DA78C485,1,"emendo-guest",100,-64,18,8,8 +UWSCAN:E063DA78C485,1,"Blackbird-device",100,-64,18,8,8 +UWSCAN:E663DA78C485,1,"emendo-guest",100,-64,18,8,8 +UWSCAN:EA63DA78C485,1,"emendo-Blackbird",100,-64,18,8,8 +UWSCAN:EA63DA78C485,1,"emendo-Blackbird",100,-64,18,8,8 OK

After the implementation of framed queues, this could be solved using an additional ResponseHeader variant as:

pub enum ResponseHeader {
    Complete,
    Line,
    Chunk,
}

As well as the introduction of another AT wrapper type for responses, next to Streaming:

pub struct Lines<'a, A: AtatResp, const N: usize> {
    consumer: &'a FrameConsumer<'a, N>,
}


impl<'a,  A: AtatResp, const N: usize> Stream for Lines<'a,  A, N> {
    type item = A;

    fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
        match self.consumer.peek() {
            Some(Ok(ResponseHeader::Line(bytes)) => {
                unsafe { self.consumer.dequeue_unchecked(); }
                let s: A = A::parse(bytes);
                Poll::Ready(Some(s))
            }
            // The next item in the queue is a new response
            Some(Ok(ResponseHeader::Response(_)) => {
                Poll::Ready(None)
            }
            // FIXME: Is this correct?
            Some(Err(e)) => Poll::Ready(None),
            None => Poll::Pending
        }
    }
}

impl<'a,  A: AtatResp, const N: usize> Drop for Lines<'a,  A, N> {
    fn drop(&mut self) {
        // Dequeue items until there are no more `ResponseHeader::Line`
        while let Some(Ok(AtItem::Line(_)) = self.consumer.peek() {
            self.consumer.dequeue();
        }
    }
}

such that

#[derive(Clone, AtatResp)]
pub struct Wifi {
    ssid: String<64>
}

#[derive(Clone, AtatCmd)]
#[at_cmd("+USORD", Lines<'_, Wifi>)]
pub struct ScanWifi;

pub fn example_using_lines() {
    match at_client.send(&ScanWifi) {
        Err(nb::Error::WouldBlock) => {
            // This would indicate that the first part of the response has yet to be received
        }
        Err(nb::Error::Other(_)) => {
            // This would indicate that an actual error occured on the AT line, 
            // or a timeout occured while waiting for the first part of the response.
        }
        Ok(response) => {
            // `at_client` is locked for the full lifetime of `response`, 
            // but the ingress part of `ATAT` should still be ingressing and pushing to the response queue.

            loop {
                match response.poll_next() {
                    Poll::Pending => {}
                    Poll::Ready(None) => break
                    Poll::Ready(w: Wifi) => {
                        // Found wifi accesspoints can be dealt with one at a time.
                        println!("Found wifi with ssid: {:?}", w.ssid);
                    }
                }
            }
        }
    }
}

MathiasKoch avatar Oct 04 '21 14:10 MathiasKoch