UniteLabs
Guides

Serial Troubleshooting

Serial devices that violate their own rules and how to master them.
Experimental API: This API is still under development and therefore subject to change without notice.

In serial communication, the message sender and the receiver structure their messages to each other with defined envelopes. Whenever either communication partner sends a message, the other partner listens until it hears the defined terminating sequence which tells the listener that the message is over, think "over" when using walkie-talkies.

Because all messages sent must include the message terminator and all listening actions will continue until a message terminator is encountered, we can encapsulate this behavior into a reusable Command that ensures these rules are consistently followed.

Take a look at SerialCommand to see what customizing this behavior looks like in action. SerialCommand allows the configuration of a _read_terminator, which defines the message envelope expected from the device, a _write_terminator, which defines the message envelope expected by the device, and an encoding which is used to convert between bytes and strings.

Problem

In an ideal world the SerialCommand works for all interactions with a serial device. The messages returned by the device, however were encoded by humans and it is very possible that in communicating with the device, messages are prematurely terminated by our Protocol because the full message from the device is malformatted and contains multiple terminators.

Solution

To handle this behavior, the Command can be modified with the multiline decorator on the _validate_response method.

from unitelabs.bus import CommandBuilder
builder = CommandBuilder(SerialCommand).with_multiline(0.001)
cmd = builder.build("request")

with_multiline takes as an optional argument a callable function to replace the default super() call within _validate_response.

from unitelabs.bus import SerialCommand
class MultilineSerialCommand(SerialCommand):
    @multiline(0.001)
    def _validate_response(self, data: bytes) -> bool:
        return super(SerialCommand, self)._validate_response(data)
cmd = MultilineSerialCommand("request")

multiline takes a timeout argument which sets the amount of time in seconds to wait for more data before calling the _validate_response method. The timeout should be as short a time as possible and is specific to the device's data transfer speeds.

Calls to super from within the multiline wrapped method must use the old style super which specifies the base class, i.e. super(SelfClass, self)

Copyright © 2025