pamoja.gpio

Idiomatic on-board bus facade.

Before a node reaches any network it talks to the chips wired to its own board. Three interfaces cover almost everything cheap hardware uses, and each carries one small piece of logic that is a classic field bug when it is wrong: the I2C address byte, the SPI clock mode, and whether a relay is active high or active low.

  1"""Idiomatic on-board bus facade.
  2
  3Before a node reaches any network it talks to the chips wired to its own board.
  4Three interfaces cover almost everything cheap hardware uses, and each carries one
  5small piece of logic that is a classic field bug when it is wrong: the I2C address
  6byte, the SPI clock mode, and whether a relay is active high or active low.
  7"""
  8
  9from __future__ import annotations
 10
 11import enum
 12
 13from pamoja._native import I2C_RESERVED_BELOW as _RESERVED_BELOW
 14from pamoja._native import I2C_RESERVED_FROM as _RESERVED_FROM
 15from pamoja._native import SpiClock
 16from pamoja._native import i2c_address_frame as _address_frame
 17from pamoja._native import i2c_address_frame_len as _address_frame_len
 18from pamoja._native import i2c_address_is_general_call as _is_general_call
 19from pamoja._native import i2c_address_is_reserved as _is_reserved
 20from pamoja._native import pin_edge_triggered_by as _edge_triggered_by
 21from pamoja._native import pin_level_from_bool as _level_from_bool
 22from pamoja._native import pin_level_inverted as _level_inverted
 23from pamoja._native import pin_polarity_is_asserted as _polarity_is_asserted
 24from pamoja._native import pin_polarity_level as _polarity_level
 25from pamoja._native import spi_mode_clock as _mode_clock
 26from pamoja._native import spi_mode_from_clock as _mode_from_clock
 27
 28__all__ = ["Edge", "Level", "Polarity", "SpiClock", "i2c", "pin", "spi"]
 29
 30
 31class Level(str, enum.Enum):
 32    """The physical voltage level on a pin."""
 33
 34    #: A low level, near ground.
 35    LOW = "Low"
 36    #: A high level, near the supply voltage.
 37    HIGH = "High"
 38
 39
 40class Edge(str, enum.Enum):
 41    """The signal transition that triggers a pin interrupt."""
 42
 43    #: A low-to-high transition.
 44    RISING = "Rising"
 45    #: A high-to-low transition.
 46    FALLING = "Falling"
 47    #: Either transition.
 48    BOTH = "Both"
 49
 50
 51class Polarity(str, enum.Enum):
 52    """Whether a signal is asserted by a high or a low physical level."""
 53
 54    #: A high level means asserted.
 55    ACTIVE_HIGH = "ActiveHigh"
 56    #: A low level means asserted, the wiring of most buttons and relay boards.
 57    ACTIVE_LOW = "ActiveLow"
 58
 59
 60class _I2c:
 61    """I2C addressing per the NXP I2C-bus specification (UM10204)."""
 62
 63    __slots__ = ()
 64
 65    #: The lowest 7-bit address the specification keeps for itself.
 66    RESERVED_FROM = _RESERVED_FROM
 67
 68    #: The first 7-bit address above the reserved block at the bottom of the range.
 69    RESERVED_BELOW = _RESERVED_BELOW
 70
 71    def address_frame(
 72        self, address: int, *, read: bool = False, ten_bit: bool = False
 73    ) -> bytes:
 74        """Return the address bytes a controller puts on the bus for a transfer.
 75
 76        A 7-bit address frames as the single byte ``(address << 1) | r/w``; a
 77        10-bit one frames as two, the reserved ``11110`` prefix carrying the top
 78        two bits and the read/write bit, then the low eight.
 79
 80        :param address: The device address.
 81        :param read: Whether the transfer reads rather than writes.
 82        :param ten_bit: Whether this is a 10-bit address.
 83        :returns: One byte for a 7-bit address, two for a 10-bit one.
 84        :raises PamojaError: If the address is outside its width's range.
 85        """
 86        return _address_frame(address, ten_bit, read)
 87
 88    def frame_len(self, address: int, ten_bit: bool = False) -> int:
 89        """Return how many bytes an address frame occupies.
 90
 91        :param address: The device address.
 92        :param ten_bit: Whether this is a 10-bit address.
 93        :returns: ``1`` for a 7-bit address, ``2`` for a 10-bit one.
 94        :raises PamojaError: If the address is outside its width's range.
 95        """
 96        return _address_frame_len(address, ten_bit)
 97
 98    def is_reserved(self, address: int, ten_bit: bool = False) -> bool:
 99        """Report whether an address falls in a range the specification reserves.
100
101        UM10204 reserves ``0x00..=0x07`` and ``0x78..=0x7F``, leaving
102        ``0x08..=0x77`` for ordinary devices.
103
104        :param address: The device address.
105        :param ten_bit: Whether this is a 10-bit address, never reserved in this
106            sense.
107        :returns: Whether the address is reserved.
108        :raises PamojaError: If the address is outside its width's range.
109        """
110        return _is_reserved(address, ten_bit)
111
112    def is_general_call(self, address: int, ten_bit: bool = False) -> bool:
113        """Report whether an address is the general call address ``0x00``.
114
115        :param address: The device address.
116        :param ten_bit: Whether this is a 10-bit address.
117        :returns: Whether this is the broadcast every device listens to.
118        :raises PamojaError: If the address is outside its width's range.
119        """
120        return _is_general_call(address, ten_bit)
121
122
123class _Spi:
124    """The four SPI clock modes, as the ``(CPOL, CPHA)`` pair datasheets quote."""
125
126    __slots__ = ()
127
128    def clock_for(self, mode: int) -> SpiClock:
129        """Return the clock polarity and phase a mode number names.
130
131        :param mode: The mode number, 0 to 3.
132        :returns: The pair.
133        :raises ValueError: If the mode number is above 3.
134        """
135        return _mode_clock(mode)
136
137    def mode_for(self, cpol: bool, cpha: bool) -> int:
138        """Return the mode number a clock polarity and phase name.
139
140        :param cpol: Whether the clock idles high.
141        :param cpha: Whether data is sampled on the trailing edge.
142        :returns: The mode number, 0 to 3. Every pair names a mode.
143        """
144        return _mode_from_clock(cpol, cpha)
145
146
147class _Pin:
148    """The GPIO pin model: levels, interrupt edges, and active polarity."""
149
150    __slots__ = ()
151
152    def level_from(self, high: bool) -> Level:
153        """Return the level a boolean names.
154
155        :param high: ``True`` for high, ``False`` for low.
156        :returns: The level.
157        """
158        return Level(_level_from_bool(high))
159
160    def invert(self, level: Level) -> Level:
161        """Return the opposite level.
162
163        :param level: The level to invert.
164        :returns: The other level.
165        """
166        return Level(_level_inverted(Level(level).value))
167
168    def triggers(self, edge: Edge, from_level: Level, to_level: Level) -> bool:
169        """Report whether a transition fires an interrupt trigger.
170
171        :param edge: The trigger configured on the pin.
172        :param from_level: The level before the change.
173        :param to_level: The level after it.
174        :returns: Whether the trigger fires.
175        """
176        return _edge_triggered_by(
177            Edge(edge).value, Level(from_level).value, Level(to_level).value
178        )
179
180    def level_for(self, polarity: Polarity, asserted: bool) -> Level:
181        """Return the physical level that represents a logical state.
182
183        :param polarity: How the signal is wired.
184        :param asserted: Whether the signal should be asserted.
185        :returns: The level to drive, inverted for active-low wiring.
186        """
187        return Level(_polarity_level(Polarity(polarity).value, asserted))
188
189    def is_asserted(self, polarity: Polarity, level: Level) -> bool:
190        """Report whether a physical level means the signal is asserted.
191
192        :param polarity: How the signal is wired.
193        :param level: The level read on the pin.
194        :returns: Whether the signal is asserted.
195        """
196        return _polarity_is_asserted(Polarity(polarity).value, Level(level).value)
197
198
199#: I2C addressing, validated before anything reaches the bus.
200i2c = _I2c()
201
202#: SPI clock modes, as a checked value rather than two transposable booleans.
203spi = _Spi()
204
205#: The GPIO pin model, so an active-low relay is handled by the type.
206pin = _Pin()
class Edge(builtins.str, enum.Enum):
41class Edge(str, enum.Enum):
42    """The signal transition that triggers a pin interrupt."""
43
44    #: A low-to-high transition.
45    RISING = "Rising"
46    #: A high-to-low transition.
47    FALLING = "Falling"
48    #: Either transition.
49    BOTH = "Both"

The signal transition that triggers a pin interrupt.

RISING = <Edge.RISING: 'Rising'>
FALLING = <Edge.FALLING: 'Falling'>
BOTH = <Edge.BOTH: 'Both'>
class Level(builtins.str, enum.Enum):
32class Level(str, enum.Enum):
33    """The physical voltage level on a pin."""
34
35    #: A low level, near ground.
36    LOW = "Low"
37    #: A high level, near the supply voltage.
38    HIGH = "High"

The physical voltage level on a pin.

LOW = <Level.LOW: 'Low'>
HIGH = <Level.HIGH: 'High'>
class Polarity(builtins.str, enum.Enum):
52class Polarity(str, enum.Enum):
53    """Whether a signal is asserted by a high or a low physical level."""
54
55    #: A high level means asserted.
56    ACTIVE_HIGH = "ActiveHigh"
57    #: A low level means asserted, the wiring of most buttons and relay boards.
58    ACTIVE_LOW = "ActiveLow"

Whether a signal is asserted by a high or a low physical level.

ACTIVE_HIGH = <Polarity.ACTIVE_HIGH: 'ActiveHigh'>
ACTIVE_LOW = <Polarity.ACTIVE_LOW: 'ActiveLow'>
class SpiClock:

The clock polarity and phase pair an SPI mode number names.

cpha

Whether data is sampled on the trailing edge (CPHA = 1), which is modes 1 and 3.

cpol

Whether the clock idles high (CPOL = 1), which is modes 2 and 3.

i2c = <pamoja.gpio._I2c object>
pin = <pamoja.gpio._Pin object>
spi = <pamoja.gpio._Spi object>