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.
i2c =
<pamoja.gpio._I2c object>
pin =
<pamoja.gpio._Pin object>
spi =
<pamoja.gpio._Spi object>