Source code for j5.components.gpio_pin

"""Classes for GPIO Pins."""

from abc import abstractmethod
from enum import IntEnum
from typing import List, Optional, Type

from j5.boards import Board
from j5.components import Component, Interface, NotSupportedByHardwareError


class BadGPIOPinModeError(Exception):
    """The pin is not in the correct mode."""

    pass


[docs]class GPIOPinMode(IntEnum): """Hardware modes that a GPIO pin can be set to.""" DIGITAL_INPUT = 0 #: The digital state of the pin can be read DIGITAL_INPUT_PULLUP = 1 #: Same as DIGITAL_INPUT but internal pull-up is enabled DIGITAL_INPUT_PULLDOWN = 2 #: Same as DIGITAL_INPUT but internal pull-down is enabled DIGITAL_OUTPUT = 3 #: The digital state of the pin can be set. ANALOGUE_INPUT = 4 #: The analogue voltage of the pin can be read. ANALOGUE_OUTPUT = 5 #: The analogue voltage of the pin can be set using a DAC. PWM_OUTPUT = 6 #: A PWM output signal can be created on the pin.
[docs]class GPIOPinInterface(Interface): """An interface containing the methods required for a GPIO Pin."""
[docs] @abstractmethod def set_gpio_pin_mode(self, board: Board, identifier: int, pin_mode: GPIOPinMode, ) -> None: """Set the hardware mode of a GPIO pin.""" raise NotImplementedError # pragma: nocover
[docs] @abstractmethod def get_gpio_pin_mode(self, board: Board, identifier: int) -> GPIOPinMode: """Get the hardware mode of a GPIO pin.""" raise NotImplementedError # pragma: nocover
[docs] @abstractmethod def write_gpio_pin_digital_state(self, board: Board, identifier: int, state: bool, ) -> None: """Write to the digital state of a GPIO pin.""" raise NotImplementedError # pragma: nocover
[docs] @abstractmethod def get_gpio_pin_digital_state(self, board: Board, identifier: int) -> bool: """Get the last written state of the GPIO pin.""" raise NotImplementedError # pragma: nocover
[docs] @abstractmethod def read_gpio_pin_digital_state(self, board: Board, identifier: int) -> bool: """Read the digital state of the GPIO pin.""" raise NotImplementedError # pragma: nocover
[docs] @abstractmethod def read_gpio_pin_analogue_value(self, board: Board, identifier: int) -> float: """Read the scaled analogue value of the GPIO pin.""" raise NotImplementedError # pragma: nocover
[docs] @abstractmethod def write_gpio_pin_dac_value( self, board: Board, identifier: int, scaled_value: float, ) -> None: """Write a scaled analogue value to the DAC on the GPIO pin.""" raise NotImplementedError # pragma: nocover
[docs] @abstractmethod def write_gpio_pin_pwm_value( self, board: Board, identifier: int, duty_cycle: float, ) -> None: """Write a scaled analogue value to the PWM on the GPIO pin.""" raise NotImplementedError # pragma: nocover
[docs]class GPIOPin(Component): """A GPIO Pin.""" def __init__( self, identifier: int, board: Board, backend: GPIOPinInterface, supported_modes: List[GPIOPinMode] = [GPIOPinMode.DIGITAL_OUTPUT], initial_mode: Optional[GPIOPinMode] = None, ) -> None: self._board = board self._backend = backend self._identifier = identifier self._supported_modes = supported_modes if len(supported_modes) < 1: raise ValueError("A GPIO pin must support at least one GPIOPinMode.") if initial_mode is None: # If no initial mode is set, choose the first supported mode. initial_mode = self._supported_modes[0] self.mode = initial_mode
[docs] @staticmethod def interface_class() -> Type[GPIOPinInterface]: """Get the interface class that is required to use this component.""" return GPIOPinInterface
def _require_pin_modes(self, pin_modes: List[GPIOPinMode]) -> None: """Ensure that this pin is in the specified hardware mode.""" if not any(self.mode == mode for mode in pin_modes) and not len(pin_modes) == 0: raise BadGPIOPinModeError( f"Pin {self._identifier} needs to be in one of {pin_modes}", ) @property def mode(self) -> GPIOPinMode: """Get the hardware mode of this pin.""" return self._backend.get_gpio_pin_mode(self._board, self._identifier) @mode.setter def mode(self, pin_mode: GPIOPinMode) -> None: """Set the hardware mode of this pin.""" if pin_mode not in self._supported_modes: raise NotSupportedByHardwareError( f"Pin {self._identifier} on {str(self._board)} \ does not support {str(pin_mode)}.", ) self._backend.set_gpio_pin_mode(self._board, self._identifier, pin_mode) @property def digital_state(self) -> bool: """Get the digital state of the pin.""" self._require_pin_modes([ GPIOPinMode.DIGITAL_OUTPUT, GPIOPinMode.DIGITAL_INPUT, GPIOPinMode.DIGITAL_INPUT_PULLUP, GPIOPinMode.DIGITAL_INPUT_PULLDOWN], ) # Behave differently depending on the hardware mode. if self.mode is GPIOPinMode.DIGITAL_OUTPUT: return self._backend.get_gpio_pin_digital_state(self._board, self._identifier) return self._backend.read_gpio_pin_digital_state(self._board, self._identifier) @digital_state.setter def digital_state(self, state: bool) -> None: """Set the digital state of the pin.""" self._require_pin_modes([GPIOPinMode.DIGITAL_OUTPUT]) self._backend.write_gpio_pin_digital_state(self._board, self._identifier, state) @property def analogue_value(self) -> float: """Get the scaled analogue reading of the pin.""" self._require_pin_modes([GPIOPinMode.ANALOGUE_INPUT]) return self._backend.read_gpio_pin_analogue_value(self._board, self._identifier) @analogue_value.setter def analogue_value(self, new_value: float) -> None: """Set the analogue value of the pin.""" self._require_pin_modes([ GPIOPinMode.ANALOGUE_OUTPUT, GPIOPinMode.PWM_OUTPUT, ]) if new_value < 0 or new_value > 1: raise ValueError("An analogue pin value must be between 0 and 1.") if self.mode is GPIOPinMode.ANALOGUE_OUTPUT: self._backend.write_gpio_pin_dac_value( self._board, self._identifier, new_value, ) else: # We must be a PWM_OUTPUT self._backend.write_gpio_pin_pwm_value( self._board, self._identifier, new_value, )