Source code for mil_usb_to_can.sub8.application_packet

#! /usr/bin/env python3
from __future__ import annotations

import struct
from typing import TypeVar

T = TypeVar("T", bound="ApplicationPacket")


[docs]class ApplicationPacketWrongIdentifierException(Exception): """ Exception thrown when the identifier for a MIL application level CAN packet had a different identifier from what was expected. Inherits from :class:`Exception`. """ def __init__(self, received: int, expected: int): """ Attributes: received (int): A value representing what received found. expected (int): What the found value should have been. """ super().__init__(f"Expected identified {expected}, got {received}")
[docs]class ApplicationPacket: """ Represents an application-specific packet structure used by a CAN device. This class does not implement the entire packet that will be sent over the CAN bus; the packet only includes the identifier and payload in the packet. This class should be used to generate packets to send to the board. This packet does not handle communication with the board; this is instead handled by other packet classes. This class can be inherited from to implement packet structures for specific applications. .. code-block:: python class SendALetterMessage(ApplicationPacket): IDENTIFIER = 0xAA STRUCT_FORMAT = "B" def __init__(self, letter: str): self.letter = letter super().__init__(self.IDENTIFIER, struct.pack(self.STRUCT_FORMAT, ord(letter))) @classmethod def from_bytes(cls, data: bytes) -> SendALetterMessage: return cls(*struct.unpack(self.STRUCT_FORMAT, data)) .. container:: operations .. describe:: bytes(x) Assembles the packet into a form suitable for sending through a data stream. Packs :attr:`~.identifier` and :attr:`~.payload` into a single :class:`bytes` object. Attributes: identifier (int): The identifier for the packet. Allowed range is between 0 and 255. payload (bytes): The payload of bytes to be sent in the packet. """ def __init__(self, identifier: int, payload: bytes): self.identifier = identifier self.payload = payload def __bytes__(self) -> bytes: """ Packs the packet into a series of bytes using :meth:`struct.Struct.pack`. The identifier is packed as an unsigned integer, while the payload of bytes is packed as a sequence of bytes equal to the length of the payload. Returns: bytes: The packed bytes. """ return struct.pack(f"B{len(self.payload)}s", self.identifier, self.payload)
[docs] @classmethod def from_bytes( cls: type[T], data: bytes, expected_identifier: int | None = None, ) -> T: """ Unpacks a series of packed bytes representing an application packet using :meth:`struct.Struct.unpack`, which produces the packet identifier and array of data. These values are then used to produce the new instance of the class. Args: data (bytes): The packed packet. expected_identifier (Optional[int]): The identifier that is expected to result from the packet. If ``None``, then the identifier is not verified. If this value is passed and the identifiers do not match, then the below error is thrown. Raises: ApplicationPacketWrongIdentifierException: If the ``expected_identifier`` does not match the identifier found in the packet, then this is raised. Returns: ApplicationPacket: The data represented as an application packet. """ payload_len = len(data) - 1 packet = cls(*struct.unpack(f"B{payload_len}s", data)) if expected_identifier is not None and expected_identifier != packet.identifier: raise ApplicationPacketWrongIdentifierException( packet.identifier, expected_identifier, ) return packet
def __repr__(self): return f"{self.__class__.__name__}(identifier={self.identifier}, payload={self.payload})" def __eq__(self, other): if not isinstance(other, ApplicationPacket): raise NotImplementedError return self.identifier == other.identifier and self.payload == other.payload