Source code for octoprobe.util_baseclasses

from __future__ import annotations

import abc
import dataclasses
import enum
import pathlib  # pylint: disable=W0611:unused-import

from .util_tentacle_label.label_data import LabelData, LabelsData

TENTACLE_TYPE_MCU = "tentacle_mcu"


class OctoprobeTestException(Exception):
    """
    This exception terminates a test.
    """


class OctoprobeAppExitException(Exception):
    """
    This exception terminates the application

    When this exception is thrown, everything has been handled and logged.
    When this exception is caught, the only thing to do is the
    message "Terminating test due to OctoprobeTestException" and exit.
    """


class VersionMismatchException(OctoprobeTestException):
    def __init__(self, msg: str, version_installed: str, version_expected: str):
        assert isinstance(msg, str)
        assert isinstance(version_installed, str)
        assert isinstance(version_expected, str)
        self.msg = msg
        self.version_installed = version_installed
        self.version_expected = version_expected
        full_msg = f"{self.msg}: Version installed: '{self.version_installed}' but expected: '{self.version_expected}'!"
        super().__init__(full_msg)


def assert_micropython_repo(directory: pathlib.Path) -> None:
    if not (directory / "ports").is_dir():
        raise OctoprobeAppExitException(
            f"Directory does not point to the top of a micropython repo: {directory}"
        )


@dataclasses.dataclass
class UsbID:
    """
    The usb specification defines a vendor and a product id
    """

    vendor_id: int
    product_id: int

    def __repr__(self) -> str:
        return (
            f"{self.__class__.__name__}(0x{self.vendor_id:04X}:0x{self.product_id:04X})"
        )


@dataclasses.dataclass
class BootApplicationUsbID:
    """
    A mcu typically has a different vendor-id/product-id in boot mode and application mode.
    """

    boot: UsbID
    """
    usb id's in boot/programming mode
    """
    application: UsbID
    """
    usb id's in application mode
    """


@dataclasses.dataclass(frozen=True)
class PropertyString:
    """
    Example: programmer=picotool,xy=5
    """

    text: str

    def get_tag_mandatory(self, tag: str) -> str:
        v = self.get_tag(tag=tag, mandatory=True)
        assert v is not None
        return v

    def get_tag(self, tag: str, mandatory: bool = False) -> None | str:
        """
        Example: get_tag("xy") -> "5"
        """
        if len(self.text) > 0:
            for x in self.text.split(","):
                _tag, value = x.split("=")
                if _tag == tag:
                    return value
        if mandatory:
            raise ValueError(f"No '{tag}' specified in '{self.text}'!")

        return None


[docs] @dataclasses.dataclass(frozen=True, repr=True, eq=True) class TentacleSpecBase(abc.ABC): """ Specification for a Tentacle, for example: >>> TentacleSpec( tentacle_type=TentacleType.TENTACLE_MCU, futs=[EnumFut.FUT_I2C, EnumFut.FUT_UART], label="pico", tags="boards=RPI_PICO,mcu=rp2,programmer=picotool", This class is heavely used in testcode and therefore has to support typehints and code completion to ease the work of writing/maintaining testcode. Therefore this class uses `generics <https://docs.python.org/3/library/typing.html#generics>`_. which allows to define parts of this class on testbed level. """ tentacle_type: enum.StrEnum tentacle_tag: str futs: list[enum.StrEnum] doc: str tags: str relays_closed: dict[enum.StrEnum | None, list[int]] = dataclasses.field( default_factory=lambda: {None: []} ) """ If the key is None, the value defines the relays to be closed by default. """ mcu_usb_id: BootApplicationUsbID | None = None programmer_args: list[str] = dataclasses.field(default_factory=list) """ Special argumets... """ def __post_init__(self) -> None: assert isinstance(self.tentacle_type, enum.StrEnum) assert isinstance(self.tentacle_tag, str) assert isinstance(self.futs, list) assert isinstance(self.doc, str) assert isinstance(self.tags, str) assert isinstance(self.relays_closed, dict) assert isinstance(self.mcu_usb_id, BootApplicationUsbID | None) assert isinstance(self.programmer_args, list) def __hash__(self) -> int: return hash(f"{self.tentacle_type}-{self.tentacle_tag}")
[docs] def get_tag(self, tag: str) -> str | None: """ Find a tag in a string like ``boards=RPI_PICO,mcu=rp2,programmer=picotool``. :param tag: ``mcu`` in above string :type tag: str :return: In this example: ``rp2`` :rtype: str | None """ return PropertyString(self.tags).get_tag(tag)
def get_tag_mandatory(self, tag: str) -> str: return PropertyString(self.tags).get_tag_mandatory(tag) @property def is_mcu(self) -> bool: return self.tentacle_type.value == TENTACLE_TYPE_MCU @property @abc.abstractmethod def description(self) -> str: ...
@dataclasses.dataclass(frozen=True, repr=True, eq=True) class TentacleInstance: serial: str tentacle_spec: TentacleSpecBase hw_version: str testbed_name: str testbed_instance: str @property def label_data(self) -> LabelData: return LabelData( serial=self.serial[-4:], description=self.tentacle_spec.description, tentacle_tag=self.tentacle_spec.tentacle_tag, tentacle_type=self.tentacle_spec.tentacle_type, testbed_name=self.testbed_name, testbed_instance=self.testbed_instance, ) class TentaclesInventory(dict[str, TentacleInstance]): @property def labels_data(self) -> LabelsData: return LabelsData([instance.label_data for instance in self.values()]) class TentaclesCollector: """ Allows to create inventory lists in a compact form. """ def __init__(self, testbed_name: str) -> None: assert isinstance(testbed_name, str) self.testbed_name = testbed_name self.inventory = TentaclesInventory() def set_testbed_name(self, testbed_name: str) -> TentaclesCollector: assert isinstance(testbed_name, str) self.testbed_name = testbed_name return self def add_testbed_instance( self, testbed_instance: str, tentacles: list[tuple[str, str, TentacleSpecBase]] ) -> TentaclesCollector: assert isinstance(testbed_instance, str) assert isinstance(tentacles, list) for serial, hw_version, tentacle_spec in tentacles: assert isinstance(serial, str) assert isinstance(hw_version, str) assert isinstance(tentacle_spec, TentacleSpecBase) tentacle_instance = TentacleInstance( serial=serial, hw_version=hw_version, tentacle_spec=tentacle_spec, testbed_name=self.testbed_name, testbed_instance=testbed_instance, ) assert ( tentacle_instance.serial not in self.inventory ), f"Duplicated tentacle serial {tentacle_instance.serial}!" self.inventory[tentacle_instance.serial] = tentacle_instance return self