Skip to content

Commit

Permalink
Write first unit test
Browse files Browse the repository at this point in the history
  • Loading branch information
claui committed Jul 28, 2024
1 parent 3a145d9 commit 0986e07
Show file tree
Hide file tree
Showing 7 changed files with 193 additions and 82 deletions.
4 changes: 2 additions & 2 deletions itchcraft/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from contextlib import ExitStack

from .devices import find_devices
from . import devices
from .errors import CliError
from .logging import get_logger

Expand All @@ -28,7 +28,7 @@ def start(self) -> None: # pylint: disable=no-self-use
with ExitStack() as stack:
candidates = [
stack.enter_context(candidate)
for candidate in find_devices()
for candidate in devices.find_devices()
]
if not candidates:
raise CliError('No bite healer connected')
Expand Down
50 changes: 37 additions & 13 deletions itchcraft/backend.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"""USB backend management"""

from abc import ABC, abstractmethod
from collections.abc import Callable
import array
from typing import Optional, Union, cast

Expand All @@ -8,7 +10,35 @@
from .errors import BackendInitializationError, EndpointNotFound


class UsbBulkTransferDevice:
class BulkTransferDevice(ABC):
"""Abstract base class for USB devices with two bulk transfer
endpoints."""

@abstractmethod
def bulk_transfer(
self,
request: Union[list[int], bytes, bytearray],
) -> bytes:
"""Sends a payload via USB bulk transfer and waits for a response
from the device.
:param `request`: the request payload from the host.
:return: the response received from the device.
"""

@property
@abstractmethod
def product_name(self) -> Optional[str]:
"""Product name of the device that this backend represents."""

@property
@abstractmethod
def serial_number(self) -> Optional[str]:
"""Serial number of the device that this backend represents."""


class UsbBulkTransferDevice(BulkTransferDevice):
"""USB device with two bulk transfer endpoints."""

MAX_RESPONSE_LENGTH = 12
Expand Down Expand Up @@ -43,13 +73,6 @@ def bulk_transfer(
self,
request: Union[list[int], bytes, bytearray],
) -> bytes:
"""Sends a payload via USB bulk transfer and waits for a response
from the device.
:param `request`: the request payload from the host.
:return: the response received from the device.
"""
response = array.array('B', bytearray(self.MAX_RESPONSE_LENGTH))
assert self.device.write(self.endpoint_out, request) == len(
request
Expand All @@ -59,16 +82,17 @@ def bulk_transfer(

@property
def product_name(self) -> Optional[str]:
"""Product name of the device that this backend represents."""
return self.device.product
return cast(Optional[str], self.device.product)

@property
def serial_number(self) -> Optional[str]:
"""Serial number of the device that this backend represents."""
return self.device.serial_number
return cast(Optional[str], self.device.serial_number)


def _find_endpoint(interface, custom_match) -> usb.core.Endpoint:
def _find_endpoint(
interface: usb.core.Interface,
custom_match: Callable[[usb.core.Endpoint], bool],
) -> usb.core.Endpoint:
if (
endpoint := usb.util.find_descriptor(
interface,
Expand Down
15 changes: 5 additions & 10 deletions itchcraft/devices.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,17 @@
"""Device management and discovery"""

from collections.abc import Generator
from contextlib import (
AbstractContextManager,
contextmanager,
)
from collections.abc import Iterator, Generator
from contextlib import AbstractContextManager, contextmanager
from typing import Any, cast

import usb.core
import usb.core # type: ignore

from .backend import UsbBulkTransferDevice
from .heat_it import HeatItDevice
from .device import Device


def find_devices() -> (
Generator[AbstractContextManager[Device], None, None]
):
def find_devices() -> Iterator[AbstractContextManager[Device]]:
"""Finds the first available backend"""
devices = cast(
Generator[usb.core.Device, Any, None],
Expand All @@ -29,5 +24,5 @@ def find_devices() -> (
@contextmanager
def _heat_it_device(
usb_device: usb.core.Device,
) -> Generator[HeatItDevice, None, None]:
) -> Iterator[HeatItDevice]:
yield HeatItDevice(UsbBulkTransferDevice(usb_device))
6 changes: 3 additions & 3 deletions itchcraft/heat_it.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
)
import usb.core # type: ignore

from .backend import UsbBulkTransferDevice
from .backend import BulkTransferDevice
from .device import Device
from .logging import get_logger

Expand All @@ -23,9 +23,9 @@
class HeatItDevice(Device):
"""A heat-it bite healer, configured over USB."""

device: UsbBulkTransferDevice
device: BulkTransferDevice

def __init__(self, device: UsbBulkTransferDevice) -> None:
def __init__(self, device: BulkTransferDevice) -> None:
self.device = device

def test_bootloader(self) -> bytes:
Expand Down
Loading

0 comments on commit 0986e07

Please sign in to comment.