diff --git a/adafruit_bluefruit_connect/_xyz_packet.py b/adafruit_bluefruit_connect/_xyz_packet.py index 37cfa58..5b3267b 100644 --- a/adafruit_bluefruit_connect/_xyz_packet.py +++ b/adafruit_bluefruit_connect/_xyz_packet.py @@ -12,6 +12,8 @@ """ +from __future__ import annotations + import struct from .packet import Packet @@ -20,20 +22,20 @@ class _XYZPacket(Packet): """A packet of x, y, z float values. Used for several different Bluefruit controller packets.""" - _FMT_PARSE = " None: # Construct an _XYZPacket subclass object # from the given x, y, and z float values, and type character. self._x = x self._y = y self._z = z - def to_bytes(self): + def to_bytes(self) -> bytes: """Return the bytes needed to send this packet.""" partial_packet = struct.pack( self._FMT_CONSTRUCT, self._TYPE_HEADER, self._x, self._y, self._z @@ -41,16 +43,16 @@ def to_bytes(self): return self.add_checksum(partial_packet) @property - def x(self): + def x(self) -> float: """The x value.""" return self._x @property - def y(self): + def y(self) -> float: """The y value.""" return self._y @property - def z(self): + def z(self) -> float: """The z value.""" return self._z diff --git a/adafruit_bluefruit_connect/accelerometer_packet.py b/adafruit_bluefruit_connect/accelerometer_packet.py index fc64554..a8ef855 100644 --- a/adafruit_bluefruit_connect/accelerometer_packet.py +++ b/adafruit_bluefruit_connect/accelerometer_packet.py @@ -12,6 +12,8 @@ """ +from __future__ import annotations + from ._xyz_packet import _XYZPacket @@ -19,7 +21,7 @@ class AccelerometerPacket(_XYZPacket): """A packet of x, y, z float values from an accelerometer.""" # Everything else is handled by _XYZPacket. - _TYPE_HEADER = b"!A" + _TYPE_HEADER: bytes = b"!A" # Register this class with the superclass. This allows the user to import only what is needed. diff --git a/adafruit_bluefruit_connect/button_packet.py b/adafruit_bluefruit_connect/button_packet.py index 36542a0..5f5391d 100644 --- a/adafruit_bluefruit_connect/button_packet.py +++ b/adafruit_bluefruit_connect/button_packet.py @@ -13,39 +13,46 @@ """ +from __future__ import annotations + import struct from .packet import Packet +try: + from typing import Optional # adjust these as needed +except ImportError: + pass + class ButtonPacket(Packet): """A packet containing a button name and its state.""" - BUTTON_1 = "1" + BUTTON_1: str = "1" """Code for Button 1 on the Bluefruit LE Connect app Control Pad screen.""" - BUTTON_2 = "2" + BUTTON_2: str = "2" """Button 2.""" - BUTTON_3 = "3" + BUTTON_3: str = "3" """Button 3.""" - BUTTON_4 = "4" + BUTTON_4: str = "4" """Button 4.""" # pylint: disable= invalid-name - UP = "5" + UP: str = "5" """Up Button.""" - DOWN = "6" + DOWN: str = "6" """Down Button.""" - LEFT = "7" + LEFT: str = "7" """Left Button.""" - RIGHT = "8" + RIGHT: str = "8" """Right Button.""" - _FMT_PARSE = " None: """Construct a ButtonPacket from a button name and the button's state. :param str button: a single character denoting the button @@ -59,11 +66,11 @@ def __init__(self, button, pressed): except Exception as err: raise ValueError("Button must be a single char.") from err - self._button = button - self._pressed = pressed + self._button: str = button + self._pressed: bool = pressed @classmethod - def parse_private(cls, packet): + def parse_private(cls, packet: bytes) -> Optional[Packet]: """Construct a ButtonPacket from an incoming packet. Do not call this directly; call Packet.from_bytes() instead. pylint makes it difficult to call this method _parse(), hence the name. @@ -73,9 +80,9 @@ def parse_private(cls, packet): raise ValueError("Bad button press/release value") return cls(chr(button[0]), pressed == b"1") - def to_bytes(self): + def to_bytes(self) -> bytes: """Return the bytes needed to send this packet.""" - partial_packet = struct.pack( + partial_packet: bytes = struct.pack( self._FMT_CONSTRUCT, self._TYPE_HEADER, bytes(self._button, "utf-8"), @@ -84,13 +91,13 @@ def to_bytes(self): return self.add_checksum(partial_packet) @property - def button(self): + def button(self) -> str: """A single character string (not bytes) specifying the button that the user pressed or released.""" return self._button @property - def pressed(self): + def pressed(self) -> bool: """``True`` if button is pressed, or ``False`` if it is released.""" return self._pressed diff --git a/adafruit_bluefruit_connect/color_packet.py b/adafruit_bluefruit_connect/color_packet.py index 9524813..717de71 100644 --- a/adafruit_bluefruit_connect/color_packet.py +++ b/adafruit_bluefruit_connect/color_packet.py @@ -12,21 +12,28 @@ """ +from __future__ import annotations + import struct from .packet import Packet +try: + from typing import Optional, Tuple # adjust these as needed +except ImportError: + pass + class ColorPacket(Packet): """A packet containing an RGB color value.""" - _FMT_PARSE = " None: """Construct a ColorPacket from a 3-element :class:`tuple` of RGB values, or from an int color value 0xRRGGBB. @@ -34,21 +41,21 @@ def __init__(self, color): or an int color value ``0xRRGGBB`` """ if isinstance(color, int): - self._color = tuple(color.to_bytes(3, "big")) + self._color: Tuple = tuple(color.to_bytes(3, "big")) elif len(color) == 3 and all(0 <= c <= 255 for c in color): self._color = color else: raise ValueError("Color must be an integer 0xRRGGBB or a tuple(r,g,b)") @classmethod - def parse_private(cls, packet): + def parse_private(cls, packet: bytes) -> Optional[Packet]: """Construct a ColorPacket from an incoming packet. Do not call this directly; call Packet.from_bytes() instead. pylint makes it difficult to call this method _parse(), hence the name. """ return cls(struct.unpack(cls._FMT_PARSE, packet)) - def to_bytes(self): + def to_bytes(self) -> bytes: """Return the bytes needed to send this packet.""" partial_packet = struct.pack( self._FMT_CONSTRUCT, self._TYPE_HEADER, *self._color @@ -56,7 +63,7 @@ def to_bytes(self): return self.add_checksum(partial_packet) @property - def color(self): + def color(self) -> tuple: """A :class:`tuple` ``(red, green blue)`` representing the color the user chose in the BlueFruit Connect app.""" return self._color diff --git a/adafruit_bluefruit_connect/gyro_packet.py b/adafruit_bluefruit_connect/gyro_packet.py index 7362119..0f17fa3 100644 --- a/adafruit_bluefruit_connect/gyro_packet.py +++ b/adafruit_bluefruit_connect/gyro_packet.py @@ -12,6 +12,8 @@ """ +from __future__ import annotations + from ._xyz_packet import _XYZPacket @@ -19,7 +21,7 @@ class GyroPacket(_XYZPacket): """A packet of x, y, z float values from a gyroscope.""" # Everything else is handled by _XYZPacket. - _TYPE_HEADER = b"!G" + _TYPE_HEADER: bytes = b"!G" # Register this class with the superclass. This allows the user to import only what is needed. diff --git a/adafruit_bluefruit_connect/location_packet.py b/adafruit_bluefruit_connect/location_packet.py index c7bae5d..e27dbc6 100644 --- a/adafruit_bluefruit_connect/location_packet.py +++ b/adafruit_bluefruit_connect/location_packet.py @@ -12,6 +12,8 @@ """ +from __future__ import annotations + import struct from .packet import Packet @@ -20,19 +22,19 @@ class LocationPacket(Packet): """A packet of latitude, longitude, and altitude values.""" - _FMT_PARSE = " None: """Construct a LocationPacket from the given values.""" self._latitude = latitude self._longitude = longitude self._altitude = altitude - def to_bytes(self): + def to_bytes(self) -> bytes: """Return the bytes needed to send this packet.""" partial_packet = struct.pack( self._FMT_CONSTRUCT, @@ -44,17 +46,17 @@ def to_bytes(self): return self.add_checksum(partial_packet) @property - def latitude(self): + def latitude(self) -> float: """The latitude value.""" return self._latitude @property - def longitude(self): + def longitude(self) -> float: """The longitude value.""" return self._longitude @property - def altitude(self): + def altitude(self) -> float: """The altitude value.""" return self._altitude diff --git a/adafruit_bluefruit_connect/magnetometer_packet.py b/adafruit_bluefruit_connect/magnetometer_packet.py index ef4cff3..5dbaa31 100644 --- a/adafruit_bluefruit_connect/magnetometer_packet.py +++ b/adafruit_bluefruit_connect/magnetometer_packet.py @@ -12,6 +12,8 @@ """ +from __future__ import annotations + from ._xyz_packet import _XYZPacket @@ -19,7 +21,7 @@ class MagnetometerPacket(_XYZPacket): """A packet of x, y, z float values from a magnetometer.""" # Everything else is handled by _XYZPacket. - _TYPE_HEADER = b"!M" + _TYPE_HEADER: bytes = b"!M" # Register this class with the superclass. This allows the user to import only what is needed. diff --git a/adafruit_bluefruit_connect/packet.py b/adafruit_bluefruit_connect/packet.py index 3808b34..40c2b8e 100644 --- a/adafruit_bluefruit_connect/packet.py +++ b/adafruit_bluefruit_connect/packet.py @@ -12,9 +12,18 @@ """ +from __future__ import annotations + import struct +try: + from typing import Optional, Any # adjust these as needed + from io import RawIOBase +except ImportError: + pass + + class Packet: """ A Bluefruit app controller packet. A packet consists of these bytes, in order: @@ -31,18 +40,18 @@ class Packet: # All concrete subclasses should define these class attributes. They're listed here # as a reminder and to make pylint happy. # _FMT_PARSE is the whole packet. - _FMT_PARSE = None + _FMT_PARSE: str # In each class, set PACKET_LENGTH = struct.calcsize(_FMT_PARSE). - PACKET_LENGTH = None + PACKET_LENGTH: int # _FMT_CONSTRUCT does not include the trailing byte, which is the checksum. - _FMT_CONSTRUCT = None + _FMT_CONSTRUCT: Optional[str] = None # The first byte of the prefix is always b'!'. The second byte is the type code. - _TYPE_HEADER = None + _TYPE_HEADER: Optional[bytes] = None - _type_to_class = {} + _type_to_class: dict = {} @classmethod - def register_packet_type(cls): + def register_packet_type(cls: Any) -> None: """Register a new packet type, using this class and its ``cls._TYPE_HEADER``. The ``from_bytes()`` and ``from_stream()`` methods will then be able to recognize this type of packet. @@ -51,7 +60,7 @@ def register_packet_type(cls): Packet._type_to_class[cls._TYPE_HEADER] = cls @classmethod - def from_bytes(cls, packet): + def from_bytes(cls, packet: bytes) -> Packet: """Create an appropriate object of the correct class for the given packet bytes. Validate packet type, length, and checksum. """ @@ -76,7 +85,7 @@ def from_bytes(cls, packet): return packet_class.parse_private(packet) @classmethod - def from_stream(cls, stream): + def from_stream(cls, stream: RawIOBase) -> Optional[Packet]: """Read the next packet from the incoming stream. Wait as long as the timeout set on stream, using its own preset timeout. Return None if there was no input, otherwise return an instance @@ -120,11 +129,13 @@ def from_stream(cls, stream): packet_class = cls._type_to_class.get(header, None) if not packet_class: raise ValueError("Unregistered packet type {}".format(header)) - packet = header + stream.read(packet_class.PACKET_LENGTH - 2) + rest = stream.read(packet_class.PACKET_LENGTH - 2) + assert rest is not None + packet = header + rest return cls.from_bytes(packet) @classmethod - def parse_private(cls, packet): + def parse_private(cls, packet: bytes) -> Optional[Packet]: """Default implementation for subclasses. Assumes arguments to ``__init__()`` are exactly the values parsed using ``cls._FMT_PARSE``. Subclasses may need to reimplement if that assumption @@ -136,11 +147,11 @@ def parse_private(cls, packet): return cls(*struct.unpack(cls._FMT_PARSE, packet)) @staticmethod - def checksum(partial_packet): + def checksum(partial_packet: bytes) -> int: """Compute checksum for bytes, not including the checksum byte itself.""" return ~sum(partial_packet) & 0xFF - def add_checksum(self, partial_packet): + def add_checksum(self, partial_packet: bytes) -> bytes: """Compute the checksum of partial_packet and return a new bytes with the checksum appended. """ diff --git a/adafruit_bluefruit_connect/quaternion_packet.py b/adafruit_bluefruit_connect/quaternion_packet.py index 359b1bf..f1b912e 100644 --- a/adafruit_bluefruit_connect/quaternion_packet.py +++ b/adafruit_bluefruit_connect/quaternion_packet.py @@ -12,6 +12,8 @@ """ +from __future__ import annotations + import struct from ._xyz_packet import _XYZPacket @@ -23,26 +25,26 @@ class QuaternionPacket(_XYZPacket): # Use _XYZPacket to handle x, y, z, and add w. - _FMT_PARSE = " None: """Construct a QuaternionPacket from the given x, y, z, and w float values.""" super().__init__(x, y, z) self._w = w - def to_bytes(self): + def to_bytes(self) -> bytes: """Return the bytes needed to send this packet.""" partial_packet = struct.pack( self._FMT_CONSTRUCT, self._TYPE_HEADER, self._x, self._y, self._z, self._w ) - return partial_packet + self.checksum(partial_packet) + return partial_packet + bytes((self.checksum(partial_packet),)) @property - def w(self): + def w(self) -> float: """The w value.""" return self._w diff --git a/adafruit_bluefruit_connect/raw_text_packet.py b/adafruit_bluefruit_connect/raw_text_packet.py index 729eea0..0c82b16 100644 --- a/adafruit_bluefruit_connect/raw_text_packet.py +++ b/adafruit_bluefruit_connect/raw_text_packet.py @@ -22,23 +22,25 @@ """ +from __future__ import annotations + from .packet import Packet class RawTextPacket(Packet): """A packet containing a text string.""" - _TYPE_HEADER = b"RT" + _TYPE_HEADER: bytes = b"RT" - def __init__(self, text): + def __init__(self, text: str) -> None: """Construct a RawTextPacket from a binary string.""" if isinstance(text, bytes): - self._text = text.strip() + self._text: str = text.strip() else: raise ValueError("Text must be a bytes string") @property - def text(self): + def text(self) -> str: """Return the text associated with the object.""" return self._text