Skip to content

Commit

Permalink
communication: minimize time spent waiting for serial bytes
Browse files Browse the repository at this point in the history
The previous implementation would always block until an unrealistic 1000 bytes had
been read from the serial port, causing the read() call to end with the configured
timeout (100ms) for every message. This put a limit to the throughput at 10 messages
per second, and imposed a latency of about 100ms since even after receiving a complete
SLIP packet the read() call would block waiting to reach 1000 bytes.

By waiting for a single byte, we make sure to unblock the read as soon as some data is
available, while avoiding a CPU-heavy busy loop (if the firmware is completely silent,
the read() call will block for 100ms each time, like it did before). To avoid extra read
passes, we try to pull as many bytes as are available in the serial buffer once we have
received at least one byte.

Also, in the case where we have received multiple messages in a single read, we avoid
blocking on a wait for more data by only going to the serial port once we have consumed
all already-read messages.
  • Loading branch information
jonathanperret authored and dl1com committed Jul 25, 2024
1 parent f3361f3 commit b175f52
Showing 1 changed file with 26 additions and 6 deletions.
32 changes: 26 additions & 6 deletions src/main/python/main/ayab/engine/communication.py
Original file line number Diff line number Diff line change
Expand Up @@ -209,14 +209,34 @@ def parse_API6(self, msg: Optional[bytes]) -> tuple[bytes | None, Token, int]:
return msg, Token.unknown, 0

def read_API6(self) -> Optional[bytes]:
"""Read data from serial as SLIP packet."""
if self.__ser:
data = self.__ser.read(1000)
"""Read data from serial, return the next SLIP packet"""

if self.__ser is None:
return None

# If we already have messages pending from previous serial reads,
# do not bother waiting on the serial port. The data will be safely
# buffered by the OS until we have consumed the messages already
# received.
if len(self.rx_msg_list) == 0:
# This will block until the timeout configured in the Serial constructor
# if no data is available (to avoid busy-waiting), but return as soon as
# a single byte is available to read.
data = self.__ser.read(1)

# More bytes may have become available simultaneously: grab them now.
if self.__ser.in_waiting > 0:
data = data + self.__ser.read(self.__ser.in_waiting)

# Send everything we received to the SLIP decoder and enqueue any messages
# it extracted from the data so far.
if len(data) > 0:
self.rx_msg_list.extend(self.__driver.receive(data))
if len(self.rx_msg_list) > 0:
return self.rx_msg_list.pop(0) # FIFO
# else

# Now, return the oldest message we have in the queue.
if len(self.rx_msg_list) > 0:
return self.rx_msg_list.pop(0) # FIFO

return None

def write_API6(self, cmd: bytearray) -> None:
Expand Down

0 comments on commit b175f52

Please sign in to comment.