Source code for j5.boards.base

"""The base classes for boards and group of boards."""

import atexit
from abc import ABCMeta, abstractmethod
from collections import OrderedDict
from typing import TYPE_CHECKING, Dict, Iterator, List, Optional, Type

from j5.backends import Backend

if TYPE_CHECKING:
    from j5.components import Component  # noqa


[docs]class Board(metaclass=ABCMeta): """A collection of hardware that has an implementation.""" # BOARDS is a list of currently instantiated boards. # This is useful to know so that we can make them safe in a crash. BOARDS: List['Board'] = [] def __str__(self) -> str: """A string representation of this board.""" return f"{self.name} - {self.serial}" def __new__(cls, *args, **kwargs): # type: ignore """Ensure any instantiated board is added to the boards list.""" instance = super().__new__(cls) Board.BOARDS.append(instance) return instance def __repr__(self) -> str: """A representation of this board.""" return f"<{self.__class__.__name__} serial={self.serial}>" @property @abstractmethod def name(self) -> str: """A human friendly name for this board.""" raise NotImplementedError # pragma: no cover @property @abstractmethod def serial(self) -> str: """The serial number of the board.""" raise NotImplementedError # pragma: no cover @property @abstractmethod def firmware_version(self) -> Optional[str]: """The firmware version of the board.""" raise NotImplementedError # pragma: no cover
[docs] @abstractmethod def make_safe(self) -> None: """Make all components on this board safe.""" raise NotImplementedError # pragma: no cover
[docs] @staticmethod @abstractmethod def supported_components() -> List[Type['Component']]: """The types of component supported by this board.""" raise NotImplementedError # pragma: no cover
[docs] @staticmethod @abstractmethod def discover(backend: Backend) -> List['Board']: """Detect and return a list of boards of this type.""" raise NotImplementedError # pragma: no cover
[docs] @staticmethod @atexit.register def make_all_safe() -> None: """Make all boards safe.""" for board in Board.BOARDS: board.make_safe()
[docs]class BoardGroup: """A group of boards that can be accessed.""" def __init__(self, board: Board, backend: Backend): self.board_class: Board = board self._backend: Backend = backend self.boards: Dict[str, Board] = OrderedDict() self.update_boards()
[docs] def update_boards(self) -> None: """Update the boards in this group to see if new boards have been added.""" self.boards: Dict[str, Board] = OrderedDict() discovered_boards = self.board_class.discover(self._backend) discovered_boards.sort(key=lambda board: board.serial) for board in discovered_boards: self.boards.update({board.serial: board})
[docs] def singular(self) -> Board: """If there is only a single board in the group, return that board.""" if len(self) == 1: return list(self.boards.values())[0] raise Exception("There is more than one or zero boards connected.")
[docs] def make_safe(self) -> None: """Make all of the boards safe.""" for board in self.boards.values(): board.make_safe()
def __len__(self) -> int: """Get the number of boards in this group.""" return len(self.boards) def __iter__(self) -> Iterator[Board]: """ Iterate over the boards in the group. The boards are ordered lexiographically by serial number. """ return iter(self.boards.values()) def __getitem__(self, serial: str) -> Board: """Get the board from serial.""" try: return self.boards[serial] except KeyError: if type(serial) != str: raise TypeError("Serial must be a string") raise KeyError(f"Could not find a board with the serial {serial}")