electrical_protocol - Electrical-software communication standard

The electrical_protocol module is meant to serve as a bridge between a software library and a physical electrical device. The library is not dependent on a particular communication standard (UART, CAN, etc.) being used. Rather, the library just provides a packet structure that can be used by any project, and a list of packets using this structure.

The library also provides a simple driver for UART/serial communication with a physical electrical device – ROSSerialDevice. If a CAN standard is desired, the mil_usb_to_can.sub9.CANDeviceHandle class can be used, which supports packets built through this library.

Packet Format

In order to reliably communicate with an electrical board, a consistent packet format must be defined.

Below is the electrical protocol packet format. This packet format wraps every message being sent over the serial connection to the USB to CAN board from ROS.

Packet Format

Name

Length

Description

Sync character 1 (0x37)

1 byte

First sync character indicating the start of packets.

Sync character 2 (0x01)

1 byte

Second sync character indicating the start of packets.

Class ID

1 byte

Message class. Determines the family of messages the packet belongs to.

Subclass ID

1 byte

Message subclass. In combination with class, determines specific qualities of message.

Payload Length

2 bytes

Length of payload.

Payload

0-65535 bytes

Payload. Meaning of payload is determined by specific packet class/subclass.

Checksum A

1 byte

First byte of Fletcher’s checksum.

Checksum B

1 byte

Second byte of Fletcher’s checksum.

Checksums

All messages contain a checksum to help verify data integrity. However, receiving packets also have a special byte containing a slightly modified checksum formula.

The checksum in all packets is found by adding up all bytes in the byte string, including the start/end flags, and then using modulo 16 on this result. The library checksum is implemented like so:

    @classmethod
    def _calculate_checksum(cls, data: bytes) -> tuple[int, int]:
        """
        Used to calculate the Fletcher's checksum for a series of bytes. When
        calculating the checksum for a new packet, the start bytes/sync characters
        should not be included.
        """
        sum1, sum2 = 0, 0
        for byte in data:
            sum1 = (sum1 + byte) % 255
            sum2 = (sum2 + sum1) % 255
        return sum1, sum2

Packet Listing

Below is a listing of all available packets. The payload format is the format used by the struct module. For more information, see the Python documentation on the list of format characters, and their corresponding byte length.

Message ID

Subclass ID

Payload Format

Class

0x00 (Meta)

0x00

Empty

electrical_protocol.NackPacket

0x01

Empty

electrical_protocol.AckPacket

0x01 (Sub8 Thrust/ Kill)

0x00

Empty

sub8_thrust_and_kill_board.HeartbeatPacket

0x01

Bf

sub8_thrust_and_kill_board.ThrustSetPacket

0x02

B

sub8_thrust_and_kill_board.KillSetPacket

0x03

B

sub8_thrust_and_kill_board.KillReceivePacket

0x02 (Sub9 Thrust/ Kill)

0x00

Empty

sub9_thrust_and_kill_board.HeartbeatSetPacket

0x01

Empty

sub9_thrust_and_kill_board.HeartbeatReceivePacket

0x02

Bf

sub9_thrust_and_kill_board.ThrustSetPacket

0x03

B

sub9_thrust_and_kill_board.KillSetPacket

0x04

B

sub9_thrust_and_kill_board.KillReceivePacket

0x03 (Battery Monitor)

0x00

Empty

sub8_battery_monitor_board.BatteryPollRequestPacket

0x01

ffff

sub8_battery_monitor_board.BatteryPollResponsePacket

0x04 (Actuator Board)

0x00

BB

sub_actuator_board.ActuatorSetPacket

0x01

Empty

sub_actuator_board.ActuatorPollRequestPacket

0x02

B

sub_actuator_board.ActuatorPollResponsePacket

0x05 (System Status)

0x00

Empty

sub9_system_status_board.SetLedRequestPacket

0x10 (NaviGator Temporary Pico Kill Board)

0x00

?B

navigator_kill_light_board.KillSetPacket

0x01

?B

navigator_kill_light_board.KillReceivePacket

0x02

navigator_kill_light_board.SetMovementModePacket

0x11 (NaviGator Ball Launcher Board)

0x00

Empty

navigator_ball_launcher.ReleaseBallPacket

0x01

B

navigator_ball_launcher.SetSpinPacket

0x20 (Drone)

0x00

Empty

navigator_drone_comm.HeartbeatReceivePacket

0x01

Empty

navigator_drone_comm.HeartbeatSetPacket

0x02

fff

navigator_drone_comm.GPSDronePacket

0x03

Empty

navigator_drone_comm.EStopPacket

0x04

20s

navigator_drone_comm.StartPacket

0x05

Empty

navigator_drone_comm.StopPacket

0x06

ffc

navigator_drone_comm.TargetPacket

Exceptions

Exception Hierarchy

Exception List

class electrical_protocol.ChecksumException(packet: type[Packet], received: tuple[int, int], expected: tuple[int, int])[source]

An invalid checksum appeared.

packet

The packet with the invalid checksum.

Type

Type[Packet]

received

The received Fletcher’s checksum.

Type

Tuple[int, int]

expected

The expected Fletcher’s checksum.

Type

Tuple[int, int]

ROSSerialDevice

class electrical_protocol.ROSSerialDevice(port: str | None, baudrate: int | None)[source]

Represents a generic serial device, which is expected to be the main component of an individual ROS node.

port

The port used for the serial connection, if provided.

Type

Optional[str]

baudrate

The baudrate to use with the device, if provided.

Type

Optional[int]

device

The serial class used to communicate with the device.

Type

Optional[serial.Serial]

rate

The reading rate of the device, in Hertz. Set to 20 by default.

Type

float

Parameters
  • port (Optional[str]) – The serial port to connect to. If None, connection will not be established on initialization; rather, the user can use connect() to connect later.

  • baudrate (Optional[int]) – The baudrate to connect with. If None and a port is specified, then 115200 is assumed.

adjust_read_rate(rate: float) None[source]

Sets the reading rate to a specified amount.

Parameters

rate (float) – The reading speed to use, in hz.

close() None[source]

Closes the serial device.

connect(port: str, baudrate: int) None[source]

Connects to the port with the given baudrate. If the device is already connected, the input and output buffers will be flushed.

Parameters
  • port (str) – The serial port to connect to.

  • baudrate (int) – The baudrate to connect with.

abstract on_packet_received(packet: RecvPackets) None[source]

Abstract method to be implemented by subclasses for handling packets sent by the physical electrical board.

Parameters

packet (Packet) – The packet that is received.

scale_read_rate(scale: float) None[source]

Scales the reading rate of the device handle by some factor.

Parameters

scale (float) – The amount to scale the reading rate by.

send_packet(packet: SendPackets) None[source]

Sends a given packet to the device.

Parameters

packet (Packet) – The packet to send.

write(data: bytes) None[source]

Writes a series of raw bytes to the device. This method should rarely be used; using send_packet() is preferred because of the guarantees it provides through the packet class.

Parameters

data (bytes) – The data to send.

Packet

Methods
class electrical_protocol.Packet[source]

Represents one packet that can be sent or received by a serial device. This class is able to handle packaging unique data values into a bytes object for sending over a data stream.

This class should be overridden to implement unique packet payloads. Note that this class supports three subclass arguments to assign unique message IDs, subclass IDs, and payload formats. Note that all subclasses must be decorated with dataclasses.dataclass().

If any class members are annotated with a subclass of enum.Enum, the class will always make an attempt to convert the raw data value to an instance of the enum before constructing the rest of the values in the class.

from dataclasses import dataclass

@dataclass
class ExamplePacket(Packet, class_id = 0x02, subclass_id = 0x01, payload_format = "BHHf"):
    example_char: int
    example_short: int
    example_short_two: int
    example_float: float
bytes(x)

Returns a bytes object representing the data of the packet in the specified packet format.

Parameters
  • class_id (int) – The message ID. Can be between 0 and 255.

  • subclass_id (int) – The message subclass ID. Can be between 0 and 255.

  • payload_format (str) – The format for the payload. This determines how the individual payload is assembled. Each character in the format string represents the position of one class variable. The class variables are assembled in the order they are defined in.

classmethod from_bytes(packed: bytes, trim: bool = True) PacketSelf[source]

Constructs a packet from a packed packet in a bytes object. If a packet is found with the corresponding message and subclass ID, then an instance (or subclass) of that packet class will be returned.

Parameters
  • packed (bytes) – The packed packet to unpack.

  • trim (bool) – If True, only the required number of bytes will be used to construct the packet. Otherwise, the entire packet will be used. Default: True.

Raises
Returns

An instance of the appropriate packet subclass.

NackPacket

class electrical_protocol.NackPacket[source]

Common not-acknowledged packet.

AckPacket

class electrical_protocol.AckPacket[source]

Common acknowledgment packet.