Proper way to read framed messages from embedded_hal::serial::Read

Hi all,

I want to interface with a few sensors via UART. Examples of these sensors are SDS011 and MH-Z19. What's common of these devices is that they have "request-response" protocol model over serial port, such as:

send    0xff 0x01 0x86 0x00 0x00 0x00 0x00 0x00 0x79 (request measurement)
receive 0xff 0x86 0x02 0x60 0x47 0x00 0x00 0x00 0xd1 (response with measurement data)

Here, response starts with 0xff and ends with checksum. So, the response has its own framing, to be able to detect that we read it from the right starting byte.

embedded_hal::serial traints by itself allow to read and write only single character. Writing is straightforward: block!(rx.write(byte)) in loop. write_str is implemented this way.

But reading is more involved. Naïve way to read response is:

for i in 0..9 {
    buf[i] = block!(uart_rx.read())?;
}

This has a problem with desyncing of start of response. If something got wrong, it might be possible that reading loop will start in middle of response. Then it will block until the next response and will read first half of the next response. So, either program will stuck forever, or reading method will return erroneous messages until restart.

What is the best way to deal with this? Maybe someone had already solved this problem?

Is something like this ok?

let mut timeout = /* hal::timer::CountDown implementation */;
timeout.start();
'byte: for i in 0..9 {
    loop {
        if timeout.is_err() {
            // a) Timeout tripped
            return /*...*/;
        }
        match uart_rx.read() {
            // b) We have a byte ready to read
            Ok(c) => {
                buf[i] = c;
                continue 'byte;
            },
            // c) No bytes to read
            Err(WouldBlock) => {
                continue;
            },
            // d) Other error
            Err(Other(e)) => {
                return Err(e);
            }
        }
    }
}

This has a problem with desyncing of start of response. If something got wrong, it might be possible that reading loop will start in middle of response.

besides the timeout (which i think is a good idea), you could handle reading the first byte specially and use the knowledge that it has to be 0xff, if it's not, perform some kind of re-sync sequence
(easiest is to read until 0xff, but that still has a fair chance of false positives, another way might be to send a request with a known response then read until that comes in)

2 Likes