Source code for j5.boards.board

"""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,
    Generic,
    Iterator,
    List,
    Optional,
    Set,
    Type,
    TypeVar,
    cast,
)

from j5.backends import Backend, CommunicationError

if TYPE_CHECKING:  # pragma: nocover
    from j5.components import Component  # noqa


[docs]class Board(metaclass=ABCMeta): """A collection of hardware that has an implementation.""" # BOARDS is a set of currently instantiated boards. # This is useful to know so that we can make them safe in a crash. BOARDS: Set['Board'] = set() 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.add(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() -> Set[Type['Component']]: """The types of component supported by this board.""" 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()
T = TypeVar('T', bound='Board')
[docs]class BoardGroup(Generic[T]): """A group of boards that can be accessed.""" def __init__(self, backend_class: Type[Backend]): self._backend_class = backend_class self._boards: Dict[str, T] = 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.clear() discovered_boards = self._backend_class.discover() for board in sorted(discovered_boards, key=lambda b: b.serial): self._boards.update({board.serial: cast(T, board)})
[docs] def singular(self) -> T: """If there is only a single board in the group, return that board.""" num = len(self) if num == 1: return list(self._boards.values())[0] else: name = self._backend_class.board.__name__ raise CommunicationError( f"expected exactly one {name} to be connected, but found {num}", )
[docs] def make_safe(self) -> None: """Make all of the boards safe.""" for board in self._boards.values(): board.make_safe()
def __str__(self) -> str: """A string representation of the board group.""" list_str = ', '.join(map(str, self._boards.values())) return f"Group of Boards - [{list_str}]" def __repr__(self) -> str: """A representation of this board.""" return f"BoardGroup(backend_class={self._backend_class.__name__})" def __len__(self) -> int: """Get the number of boards in this group.""" return len(self._boards) def __contains__(self, serial: str) -> bool: """Check if a board is in this group.""" return serial in self._boards def __iter__(self) -> Iterator[T]: """ 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) -> T: """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}") @property def backend_class(self) -> Type[Backend]: """The Backend that this group uses for Boards.""" return self._backend_class @property def boards(self) -> List[T]: """Get an unordered list of boards in this group.""" return list(self._boards.values())