pamoja.hal
Idiomatic bus facade.
A driver is a conversation with a part, and a bus is what carries it. An
I2cBus is one bus that the program and every driver on it share, with one of
three things on the other end: the kernel's adapter on a Linux board, simulated parts
that answer from their registers, or a script of the transfers a driver is expected to
make. A driver runs the same way over all three, so a program is written and tested
with nothing plugged in and then pointed at /dev/i2c-1. A SerialPort is the
same idea for a UART, and DelayLog and SleepDelay pace a driver that
waits between pin changes.
1"""Idiomatic bus facade. 2 3A driver is a conversation with a part, and a bus is what carries it. An 4:class:`I2cBus` is one bus that the program and every driver on it share, with one of 5three things on the other end: the kernel's adapter on a Linux board, simulated parts 6that answer from their registers, or a script of the transfers a driver is expected to 7make. A driver runs the same way over all three, so a program is written and tested 8with nothing plugged in and then pointed at ``/dev/i2c-1``. A :class:`SerialPort` is the 9same idea for a UART, and :class:`DelayLog` and :class:`SleepDelay` pace a driver that 10waits between pin changes. 11""" 12 13from __future__ import annotations 14 15import enum 16import time 17from dataclasses import dataclass 18from typing import Iterable, List, Optional, Protocol, Tuple, Union 19 20from pamoja._native import CommandPart as _NativeCommandPart 21from pamoja._native import I2cBus as _NativeBus 22from pamoja._native import I2cPart as _NativePart 23from pamoja._native import I2cStep as _NativeStep 24from pamoja._native import SerialPort as _NativeSerialPort 25from pamoja._native import SerialStep as _NativeSerialStep 26from pamoja._native import WordPart as _NativeWordPart 27from pamoja._native import serial_bits_per_character as _serial_bits_per_character 28from pamoja._native import serial_character_nanos as _serial_character_nanos 29from pamoja._native import serial_transfer_micros as _serial_transfer_micros 30 31__all__ = [ 32 "CommandPart", 33 "Delay", 34 "DelayLog", 35 "I2cBus", 36 "I2cBusKind", 37 "I2cFault", 38 "I2cPart", 39 "I2cStep", 40 "Parity", 41 "SerialPort", 42 "SerialPortKind", 43 "SerialSettings", 44 "SerialStep", 45 "SimulatedPart", 46 "SleepDelay", 47 "WordPart", 48] 49 50 51class I2cBusKind(str, enum.Enum): 52 """What answers on a bus.""" 53 54 #: The kernel's adapter, with real parts on real wires. 55 ADAPTER = "Adapter" 56 #: Simulated parts, answering from their registers. 57 SIMULATED = "Simulated" 58 #: A script of the transfers a driver is expected to make. 59 SCRIPTED = "Scripted" 60 61 62class I2cFault(str, enum.Enum): 63 """How a scripted step fails the transfer that reaches it.""" 64 65 #: Nothing acknowledged the address. 66 NO_ACKNOWLEDGE_ADDRESS = "NoAcknowledgeAddress" 67 #: The part did not acknowledge a data byte. 68 NO_ACKNOWLEDGE_DATA = "NoAcknowledgeData" 69 #: A missing acknowledge, with no telling whether of the address or the data. 70 NO_ACKNOWLEDGE = "NoAcknowledge" 71 #: A bus error, such as a misplaced start or stop condition. 72 BUS = "Bus" 73 #: Another controller won the bus. 74 ARBITRATION_LOSS = "ArbitrationLoss" 75 #: Data arrived faster than it was taken. 76 OVERRUN = "Overrun" 77 #: A failure of no more particular kind. 78 OTHER = "Other" 79 80 81class I2cPart: 82 """A part that is not there, answering from 256 registers. 83 84 A write names a register and fills it and the ones after it; a read takes them 85 back from wherever the last write left off. What a driver writes stays written, so 86 :meth:`register` reads a part's configuration back once a driver is done with it. 87 88 >>> BME280, CHIP_ID_REGISTER, BME280_CHIP_ID = 0x76, 0xD0, 0x60 89 >>> part = I2cPart(BME280).holding(CHIP_ID_REGISTER, bytes([BME280_CHIP_ID])) 90 >>> part.register(CHIP_ID_REGISTER) == BME280_CHIP_ID 91 True 92 """ 93 94 __slots__ = ("_native",) 95 96 def __init__(self, address: int) -> None: 97 """Make a part answering at one address, with every register reading zero. 98 99 :param address: The 7-bit address it answers to. 100 """ 101 self._native = _NativePart(address) 102 103 @classmethod 104 def _wrap(cls, native: _NativePart) -> I2cPart: 105 part = cls.__new__(cls) 106 part._native = native 107 return part 108 109 def holding(self, first: int, data: bytes) -> I2cPart: 110 """Put bytes in the part from a register on, and return the part. 111 112 :param first: The register the bytes start at. 113 :param data: What to put there. Past the last register it wraps to the first. 114 :returns: This part, so calls chain. 115 """ 116 self._native.load(first, bytes(data)) 117 return self 118 119 def register(self, register: int) -> int: 120 """Read what one register holds now. 121 122 :param register: Which register. 123 :returns: Its value, which is what a driver wrote if it wrote one. 124 """ 125 return self._native.register(register) 126 127 def read(self, first: int, length: int) -> bytes: 128 """Read consecutive registers from one register on. 129 130 :param first: The first register. 131 :param length: How many registers. 132 :returns: One byte per register. 133 """ 134 return bytes(self._native.read(first, length)) 135 136 @property 137 def address(self) -> int: 138 """The address the part answers to.""" 139 return self._native.address 140 141 @property 142 def transfers(self) -> int: 143 """How many transfers the part has served.""" 144 return self._native.transfers 145 146 147class WordPart: 148 """A part that is not there, answering from 256 registers sixteen bits wide. 149 150 This is how Texas Instruments lays out parts such as the TMP117, the INA219 and 151 INA226, the OPT3001, the ADS1115, and the HDC1080. A pointer byte names a register and 152 a register travels most significant byte first. Bits the part sets for itself, such as 153 a conversion-ready flag, are marked with :meth:`read_only` and keep the part's value 154 whatever a driver writes. 155 156 >>> TMP117, DEVICE_ID_REGISTER, TMP117_DEVICE_ID = 0x48, 0x0F, 0x0117 157 >>> part = WordPart(TMP117).holding(DEVICE_ID_REGISTER, TMP117_DEVICE_ID) 158 >>> part.word(DEVICE_ID_REGISTER) == TMP117_DEVICE_ID 159 True 160 """ 161 162 __slots__ = ("_native",) 163 164 def __init__(self, address: int) -> None: 165 """Make a part answering at one address, with every register reading zero. 166 167 :param address: The 7-bit address it answers to. 168 """ 169 self._native = _NativeWordPart(address) 170 171 @classmethod 172 def _wrap(cls, native: _NativeWordPart) -> WordPart: 173 part = cls.__new__(cls) 174 part._native = native 175 return part 176 177 def holding(self, register: int, value: int) -> WordPart: 178 """Put a value in one register, and return the part. 179 180 :param register: The register. 181 :param value: What it holds, read-only bits included. 182 :returns: This part, so calls chain. 183 """ 184 self._native.set(register, value) 185 return self 186 187 def read_only(self, register: int, mask: int) -> WordPart: 188 """Mark bits of one register as the part's to set, and return the part. 189 190 :param register: The register. 191 :param mask: The bits a driver's write leaves as the part holds them. 192 :returns: This part, so calls chain. 193 """ 194 self._native.read_only(register, mask) 195 return self 196 197 def set(self, register: int, value: int) -> None: 198 """Put a value in one register, read-only bits included, as the part itself would. 199 200 :param register: The register. 201 :param value: What it holds. 202 """ 203 self._native.set(register, value) 204 205 def word(self, register: int) -> int: 206 """Read what one register holds now. 207 208 :param register: Which register. 209 :returns: Its value, which is what a driver wrote apart from the read-only bits. 210 """ 211 return self._native.word(register) 212 213 @property 214 def address(self) -> int: 215 """The address the part answers to.""" 216 return self._native.address 217 218 @property 219 def transfers(self) -> int: 220 """How many transfers the part has served.""" 221 return self._native.transfers 222 223 224class CommandPart: 225 """A part that is not there, answering commands with the replies it was given. 226 227 This is how Sensirion lays out parts such as the SHT3x and the SCD4x. A write sends a 228 command and any arguments after it; a read takes the reply that command left, once, 229 padded with ``0xFF`` the way an idle bus reads. A command given no reply leaves none, 230 and a read then is not acknowledged, which is what a real part does when asked for data 231 it does not have. 232 233 >>> SHT3X, READ_STATUS = 0x44, (0xF32D).to_bytes(2, "big") 234 >>> STATUS_AFTER_RESET = bytes([0x80, 0x10, 0xE1]) # the word 0x8010, then its CRC 235 >>> part = CommandPart(SHT3X).answering(READ_STATUS, STATUS_AFTER_RESET) 236 >>> part.address == SHT3X 237 True 238 """ 239 240 __slots__ = ("_native",) 241 242 def __init__(self, address: int, width: int = 2) -> None: 243 """Make a part answering at one address that has been given no replies yet. 244 245 :param address: The 7-bit address it answers to. 246 :param width: How many bytes a command takes: two for Sensirion's commands. 247 """ 248 self._native = _NativeCommandPart(address, width) 249 250 @classmethod 251 def _wrap(cls, native: _NativeCommandPart) -> CommandPart: 252 part = cls.__new__(cls) 253 part._native = native 254 return part 255 256 def answering(self, command: bytes, reply: bytes) -> CommandPart: 257 """Answer one command with a reply from now on, and return the part. 258 259 :param command: The command's bytes. 260 :param reply: What a read after it returns, in place of any reply given before. 261 :returns: This part, so calls chain. 262 """ 263 self.answer(command, reply) 264 return self 265 266 def answer(self, command: bytes, reply: bytes) -> None: 267 """Answer one command with a reply from now on. 268 269 :param command: The command's bytes. 270 :param reply: What a read after it returns, in place of any reply given before. 271 """ 272 self._native.answer(bytes(command), bytes(reply)) 273 274 @property 275 def received(self) -> List[bytes]: 276 """Every write the part has received, oldest first: a command and any arguments.""" 277 return [bytes(write) for write in self._native.received] 278 279 @property 280 def address(self) -> int: 281 """The address the part answers to.""" 282 return self._native.address 283 284 @property 285 def transfers(self) -> int: 286 """How many transfers the part has served.""" 287 return self._native.transfers 288 289 290#: Any simulated part: a bus takes each kind and gives each back as its own class. 291SimulatedPart = Union[I2cPart, WordPart, CommandPart] 292 293 294def _part_of(native: object) -> SimulatedPart: 295 """Wrap a native part as the class of part it is.""" 296 if isinstance(native, _NativeWordPart): 297 return WordPart._wrap(native) 298 if isinstance(native, _NativeCommandPart): 299 return CommandPart._wrap(native) 300 return I2cPart._wrap(native) 301 302 303class I2cStep: 304 """One transfer a script expects, and what the part answers.""" 305 306 __slots__ = ("_native",) 307 308 def __init__(self, native: _NativeStep) -> None: 309 """Wrap a native step. Use :meth:`write`, :meth:`read`, :meth:`write_read`, or 310 :meth:`fault`.""" 311 self._native = native 312 313 @classmethod 314 def write(cls, address: int, data: bytes) -> I2cStep: 315 """The driver writes exactly ``data`` to the address. 316 317 :param address: The 7-bit address the write must go to. 318 :param data: The bytes the driver must send. 319 :returns: The step. 320 """ 321 return cls(_NativeStep.write(address, bytes(data))) 322 323 @classmethod 324 def read(cls, address: int, reply: bytes) -> I2cStep: 325 """The driver reads from the address and receives ``reply``. 326 327 :param address: The 7-bit address the read must come from. 328 :param reply: The bytes the part answers with; the driver must ask for exactly 329 this many. 330 :returns: The step. 331 """ 332 return cls(_NativeStep.read(address, bytes(reply))) 333 334 @classmethod 335 def write_read(cls, address: int, data: bytes, reply: bytes) -> I2cStep: 336 """The driver writes ``data`` and then reads ``reply`` in one transaction, the 337 shape of a register read. 338 339 :param address: The 7-bit address of the part. 340 :param data: The bytes the driver must send first, usually a register address. 341 :param reply: The bytes the part answers with. 342 :returns: The step. 343 """ 344 return cls(_NativeStep.write_read(address, bytes(data), bytes(reply))) 345 346 @classmethod 347 def fault(cls, address: int, fault: I2cFault) -> I2cStep: 348 """The next transfer to the address fails, the way a missing or busy part does. 349 350 :param address: The 7-bit address the failing transfer must go to. 351 :param fault: The failure the driver sees. 352 :returns: The step. 353 """ 354 return cls(_NativeStep.fault(address, I2cFault(fault).value)) 355 356 357class I2cBus: 358 """One I2C bus, shared by the program and every driver built on it. 359 360 :meth:`open` opens the kernel's adapter on a Linux board; :meth:`simulated` puts 361 simulated parts of any kind on a bus, each answering at its own address; 362 :meth:`scripted` plays :class:`I2cStep` s in order and refuses any other transfer. A 363 failed transfer raises ``PamojaError`` with the reason: nothing answered at the address, 364 the script expected something else, or the kernel's own words. 365 366 >>> BME280, CHIP_ID_REGISTER, BME280_CHIP_ID = 0x76, 0xD0, 0x60 367 >>> bus = I2cBus.simulated([I2cPart(BME280).holding(CHIP_ID_REGISTER, bytes([BME280_CHIP_ID]))]) 368 >>> bus.write_read(BME280, bytes([CHIP_ID_REGISTER]), 1) == bytes([BME280_CHIP_ID]) 369 True 370 >>> bus.transfers 371 1 372 """ 373 374 __slots__ = ("_native",) 375 376 def __init__(self, native: _NativeBus) -> None: 377 """Wrap a native bus. Use :meth:`open`, :meth:`simulated`, or :meth:`scripted`.""" 378 self._native = native 379 380 @classmethod 381 def open(cls, path: str) -> I2cBus: 382 """Open the kernel's I2C adapter, such as ``/dev/i2c-1`` on a Raspberry Pi. 383 384 :param path: The adapter's device file. 385 :returns: The bus, with the real parts wired to it on the other end. 386 :raises PamojaError: Anywhere but Linux, and when the file cannot be opened as 387 an adapter: the interface is not turned on, or the process may not use it. 388 """ 389 return cls(_NativeBus.open(path)) 390 391 @classmethod 392 def simulated(cls, parts: Iterable[SimulatedPart] = ()) -> I2cBus: 393 """Make a bus of simulated parts, each answering at its own address. 394 395 :param parts: The parts on the bus, of any kind. A later part at an address an 396 earlier one holds takes its place. 397 :returns: The bus. A transfer to an address no part holds raises 398 ``PamojaError``, as nothing acknowledges it. 399 """ 400 return cls(_NativeBus.simulated([part._native for part in parts])) 401 402 @classmethod 403 def scripted(cls, steps: Iterable[I2cStep]) -> I2cBus: 404 """Make a bus that plays the steps in order and refuses any other transfer. 405 406 :param steps: The transfers a driver is expected to make, and the replies. 407 :returns: The bus. 408 """ 409 return cls(_NativeBus.scripted([step._native for step in steps])) 410 411 def attach(self, part: SimulatedPart) -> None: 412 """Put a copy of a part on a simulated bus, in place of any part at its address. 413 414 A driver keeps working across the change, which is how a test moves a reading on. 415 416 :param part: The part, of any kind. 417 :raises PamojaError: If the bus is not simulated. 418 """ 419 self._native.attach(part._native) 420 421 @property 422 def kind(self) -> I2cBusKind: 423 """What answers on the bus.""" 424 return I2cBusKind(self._native.kind) 425 426 def write(self, address: int, data: bytes) -> None: 427 """Write bytes to a part in one transaction: usually a register and its value. 428 429 :param address: The part's 7-bit address. 430 :param data: The bytes to write. 431 :raises PamojaError: If the transfer fails. 432 """ 433 self._native.write(address, bytes(data)) 434 435 def read(self, address: int, length: int) -> bytes: 436 """Read bytes from a part in one transaction. 437 438 :param address: The part's 7-bit address. 439 :param length: How many bytes to read. 440 :returns: The bytes. 441 :raises PamojaError: If the transfer fails. 442 """ 443 return bytes(self._native.read(address, length)) 444 445 def write_read(self, address: int, data: bytes, length: int) -> bytes: 446 """Write bytes and then read the reply in one transaction, with a repeated start 447 between them, which is how a register is read. 448 449 :param address: The part's 7-bit address. 450 :param data: What to write first, usually the register address. 451 :param length: How many bytes to read. 452 :returns: The reply. 453 :raises PamojaError: If the transfer fails. 454 """ 455 return bytes(self._native.write_read(address, bytes(data), length)) 456 457 def part(self, address: int) -> Optional[SimulatedPart]: 458 """Copy what a simulated part holds now, with whatever drivers wrote to it. 459 460 :param address: The part's address. 461 :returns: The copy, as the class of part it is, or ``None`` when the bus is not 462 simulated or no part holds the address. 463 """ 464 native = self._native.part(address) 465 return None if native is None else _part_of(native) 466 467 @property 468 def transfers(self) -> int: 469 """How many transfers have been made on the bus, by the program and every driver 470 on it, including any that failed.""" 471 return self._native.transfers 472 473 @property 474 def remaining(self) -> Optional[int]: 475 """How many steps a script has left, or ``None`` when the bus is not scripted.""" 476 return self._native.remaining 477 478 @property 479 def waited_micros(self) -> int: 480 """How long the drivers on the bus have asked to wait, in microseconds, whether 481 or not the process slept through it.""" 482 return self._native.waited_micros 483 484 485class Parity(str, enum.Enum): 486 """The parity bit each character on a serial line carries.""" 487 488 #: No parity bit. 489 NONE = "None" 490 #: A bit that makes the count of ones even, what Modbus RTU asks for by default. 491 EVEN = "Even" 492 #: A bit that makes the count of ones odd. 493 ODD = "Odd" 494 495 496class SerialPortKind(str, enum.Enum): 497 """What is on the other end of a serial port.""" 498 499 #: The kernel's serial device, with a real line on the other end. 500 DEVICE = "Device" 501 #: The port's own output, looped back to its input. 502 LOOPED = "Looped" 503 #: The other end of a null-modem pair. 504 PAIRED = "Paired" 505 #: A simulated device that answers each write. 506 SIMULATED = "Simulated" 507 #: A script of the writes a driver is expected to make. 508 SCRIPTED = "Scripted" 509 510 511@dataclass(frozen=True) 512class SerialSettings: 513 """A port's speed and character format: eight data bits, with the parity and stop bits 514 given. 515 516 >>> modbus = SerialSettings(9_600, Parity.EVEN) 517 >>> str(modbus), modbus.bits_per_character, modbus.character_nanos 518 ('9600 8E1', 11, 1145834) 519 """ 520 521 #: The speed, in bits a second. 522 baud: int 523 #: The parity bit each character carries. 524 parity: Parity = Parity.NONE 525 #: 1 or 2 stop bits. 526 stop_bits: int = 1 527 528 def _values(self) -> tuple: 529 return (self.baud, Parity(self.parity).value, self.stop_bits) 530 531 @property 532 def bits_per_character(self) -> int: 533 """The bits one character takes on the wire: a start bit, eight data bits, the 534 parity bit if there is one, and the stop bits.""" 535 return _serial_bits_per_character(*self._values()) 536 537 @property 538 def character_nanos(self) -> int: 539 """How long one character takes on the wire, in nanoseconds, rounded up.""" 540 return _serial_character_nanos(*self._values()) 541 542 def transfer_micros(self, count: int) -> int: 543 """How long ``count`` bytes sent back to back take on the wire. 544 545 :param count: How many bytes. 546 :returns: The time in microseconds, rounded up. 547 """ 548 return _serial_transfer_micros(*self._values(), count) 549 550 def __str__(self) -> str: 551 letter = {Parity.NONE: "N", Parity.EVEN: "E", Parity.ODD: "O"}[Parity(self.parity)] 552 return f"{self.baud} 8{letter}{self.stop_bits}" 553 554 555class SerialStep: 556 """One step of a scripted port: :meth:`write` for a write the program is expected to 557 make, and :meth:`read` for bytes the far end sends.""" 558 559 __slots__ = ("_native",) 560 561 def __init__(self, native: _NativeSerialStep) -> None: 562 """Wrap a native step; use :meth:`write` or :meth:`read` instead.""" 563 self._native = native 564 565 @classmethod 566 def write(cls, data: bytes) -> SerialStep: 567 """A write the program is expected to make, in one call. 568 569 :param data: The bytes of the write. 570 :returns: The step. 571 """ 572 return cls(_NativeSerialStep.write(bytes(data))) 573 574 @classmethod 575 def read(cls, data: bytes) -> SerialStep: 576 """Bytes the far end sends, readable once every step before them has happened. 577 578 :param data: What arrives. 579 :returns: The step. 580 """ 581 return cls(_NativeSerialStep.read(bytes(data))) 582 583 584class SerialPort: 585 """One serial port, shared by the program and every driver built on it. 586 587 :meth:`open` opens the kernel's serial device raw on a Linux board, :meth:`looped` is a 588 line with TX wired to RX, :meth:`pair` the two ends of a null-modem cable, and 589 :meth:`scripted` a port that checks each write against a script. A write returns once 590 the bytes have left the UART, and a read once bytes have arrived or its timeout has 591 passed. On anything but the kernel's device a read never waits: it returns at once, and 592 the time it would have waited is added to :attr:`waited_micros`. Every call releases the 593 interpreter while the line is busy, and a failure raises ``PamojaError``. 594 595 >>> gateway, node = SerialPort.pair(SerialSettings(115_200)) 596 >>> node.write(b"t=21.5") 597 >>> gateway.read(16, timeout=0.1) 598 b't=21.5' 599 """ 600 601 __slots__ = ("_native",) 602 603 def __init__(self, native: _NativeSerialPort) -> None: 604 """Wrap a native port; use one of the class methods instead.""" 605 self._native = native 606 607 @classmethod 608 def open(cls, path: str, settings: SerialSettings) -> SerialPort: 609 """Open the kernel's serial device raw. 610 611 :param path: ``/dev/serial0`` for a Raspberry Pi's own UART, ``/dev/ttyUSB0`` or 612 ``/dev/ttyACM0`` for a USB adapter. 613 :param settings: The speed, a standard rate from 1200 to 921600, and the format. 614 :returns: The port. 615 :raises PamojaError: Anywhere but Linux, or when the device cannot be opened. 616 """ 617 return cls(_NativeSerialPort.open(path, *settings._values())) 618 619 @classmethod 620 def looped(cls, settings: SerialSettings) -> SerialPort: 621 """A line looped back on itself: every byte written is waiting to be read. 622 623 :param settings: The speed and format the line runs at. 624 :returns: The port. 625 """ 626 return cls(_NativeSerialPort.looped(*settings._values())) 627 628 @classmethod 629 def pair(cls, settings: SerialSettings) -> Tuple[SerialPort, SerialPort]: 630 """The two ends of a null-modem pair: what one end writes, the other reads. 631 632 :param settings: The speed and format both ends run at. 633 :returns: The two ends. 634 """ 635 one, other = _NativeSerialPort.pair(*settings._values()) 636 return cls(one), cls(other) 637 638 @classmethod 639 def scripted(cls, settings: SerialSettings, steps: Iterable[SerialStep]) -> SerialPort: 640 """A port that checks each write against the next step of a script. 641 642 :param settings: The speed and format the line runs at. 643 :param steps: The writes and reads, in order; reads at the start are there at once. 644 :returns: The port. 645 """ 646 natives = [step._native for step in steps] 647 return cls(_NativeSerialPort.scripted(*settings._values(), natives)) 648 649 @property 650 def kind(self) -> SerialPortKind: 651 """What is on the other end of the port.""" 652 return SerialPortKind(self._native.kind) 653 654 @property 655 def settings(self) -> SerialSettings: 656 """The speed and character format the port runs at.""" 657 baud, parity, stop_bits = self._native.settings 658 return SerialSettings(baud, Parity(parity), stop_bits) 659 660 def write(self, data: bytes) -> None: 661 """Write bytes, returning once they have left the UART. 662 663 :param data: The bytes, in order. 664 :raises PamojaError: When a script expected another write, or the device fails. 665 """ 666 self._native.write(bytes(data)) 667 668 def read(self, size: int, timeout: float) -> bytes: 669 """Read up to ``size`` bytes, waiting up to ``timeout`` for the first one when 670 nothing has arrived. 671 672 :param size: The most bytes to read. 673 :param timeout: How long to wait for the first byte, in seconds. 674 :returns: What arrived, empty when the timeout passed with nothing. 675 :raises PamojaError: When the device fails. 676 """ 677 return self._native.read(size, _micros(timeout)) 678 679 def discard_input(self) -> None: 680 """Drop whatever has arrived and not been read, as a client does before a request 681 so a stale reply cannot be taken for the new one. 682 683 :raises PamojaError: When the device fails. 684 """ 685 self._native.discard_input() 686 687 def wait(self, seconds: float) -> None: 688 """Wait, as a protocol does to leave the line silent between frames: really on the 689 kernel's device, and anywhere else only counted. 690 691 :param seconds: How long. 692 """ 693 self._native.wait(_micros(seconds)) 694 695 @property 696 def written(self) -> int: 697 """How many bytes have been written through the port.""" 698 return self._native.written 699 700 @property 701 def received(self) -> int: 702 """How many bytes have been read through the port.""" 703 return self._native.received 704 705 @property 706 def waited_micros(self) -> int: 707 """How long reads have waited without an answer, and waits have waited, in 708 microseconds, whether or not the process slept through it.""" 709 return self._native.waited_micros 710 711 @property 712 def remaining(self) -> Optional[int]: 713 """How many steps a script has left, or ``None`` when the port is not scripted.""" 714 return self._native.remaining 715 716 717def _micros(seconds: float) -> int: 718 if seconds < 0: 719 raise ValueError("a time must be zero or more") 720 return round(seconds * 1_000_000) 721 722 723class Delay(Protocol): 724 """What paces a driver that has to wait between pin changes, such as a stepper 725 between steps. :class:`SleepDelay` really waits; :class:`DelayLog` counts every wait 726 and waits for none, for a program run with nothing plugged in.""" 727 728 def delay_micros(self, micros: int) -> None: 729 """Wait, or count the wait. 730 731 :param micros: How long, in microseconds. 732 """ 733 ... 734 735 736class DelayLog: 737 """A delay that records every wait it is asked for and sleeps through none of them, 738 as ``pamoja_hal::script::DelayLog`` does in Rust. 739 740 >>> delay = DelayLog() 741 >>> delay.delay_micros(480) 742 >>> delay.delay_micros(10_000) 743 >>> delay.total_micros, delay.total_millis 744 (10480, 10) 745 """ 746 747 __slots__ = ("_total", "_waits") 748 749 def __init__(self) -> None: 750 """Create a log with nothing waited yet.""" 751 self._waits: List[int] = [] 752 self._total = 0 753 754 @property 755 def waits_micros(self) -> List[int]: 756 """Every wait asked for, in microseconds, oldest first.""" 757 return list(self._waits) 758 759 @property 760 def total_micros(self) -> int: 761 """The waits added up, in microseconds.""" 762 return self._total 763 764 @property 765 def total_millis(self) -> int: 766 """The waits added up, in whole milliseconds, rounded down.""" 767 return self._total // 1_000 768 769 def delay_micros(self, micros: int) -> None: 770 """Record a wait. 771 772 :param micros: How long, in microseconds. 773 """ 774 self._waits.append(micros) 775 self._total += micros 776 777 def clear(self) -> None: 778 """Forget every recorded wait.""" 779 self._waits.clear() 780 self._total = 0 781 782 783class SleepDelay: 784 """A delay that really waits: :func:`time.sleep` for a millisecond or more, and a 785 spin on :func:`time.perf_counter_ns` for a shorter wait, which the scheduler cannot 786 keep. A sleep lasts at least what was asked and may run over by the scheduler's own 787 latency.""" 788 789 __slots__ = () 790 791 def delay_micros(self, micros: int) -> None: 792 """Wait. 793 794 :param micros: How long, in microseconds. 795 """ 796 if micros >= 1_000: 797 time.sleep(micros / 1_000_000) 798 return 799 until = time.perf_counter_ns() + micros * 1_000 800 while time.perf_counter_ns() < until: 801 pass
225class CommandPart: 226 """A part that is not there, answering commands with the replies it was given. 227 228 This is how Sensirion lays out parts such as the SHT3x and the SCD4x. A write sends a 229 command and any arguments after it; a read takes the reply that command left, once, 230 padded with ``0xFF`` the way an idle bus reads. A command given no reply leaves none, 231 and a read then is not acknowledged, which is what a real part does when asked for data 232 it does not have. 233 234 >>> SHT3X, READ_STATUS = 0x44, (0xF32D).to_bytes(2, "big") 235 >>> STATUS_AFTER_RESET = bytes([0x80, 0x10, 0xE1]) # the word 0x8010, then its CRC 236 >>> part = CommandPart(SHT3X).answering(READ_STATUS, STATUS_AFTER_RESET) 237 >>> part.address == SHT3X 238 True 239 """ 240 241 __slots__ = ("_native",) 242 243 def __init__(self, address: int, width: int = 2) -> None: 244 """Make a part answering at one address that has been given no replies yet. 245 246 :param address: The 7-bit address it answers to. 247 :param width: How many bytes a command takes: two for Sensirion's commands. 248 """ 249 self._native = _NativeCommandPart(address, width) 250 251 @classmethod 252 def _wrap(cls, native: _NativeCommandPart) -> CommandPart: 253 part = cls.__new__(cls) 254 part._native = native 255 return part 256 257 def answering(self, command: bytes, reply: bytes) -> CommandPart: 258 """Answer one command with a reply from now on, and return the part. 259 260 :param command: The command's bytes. 261 :param reply: What a read after it returns, in place of any reply given before. 262 :returns: This part, so calls chain. 263 """ 264 self.answer(command, reply) 265 return self 266 267 def answer(self, command: bytes, reply: bytes) -> None: 268 """Answer one command with a reply from now on. 269 270 :param command: The command's bytes. 271 :param reply: What a read after it returns, in place of any reply given before. 272 """ 273 self._native.answer(bytes(command), bytes(reply)) 274 275 @property 276 def received(self) -> List[bytes]: 277 """Every write the part has received, oldest first: a command and any arguments.""" 278 return [bytes(write) for write in self._native.received] 279 280 @property 281 def address(self) -> int: 282 """The address the part answers to.""" 283 return self._native.address 284 285 @property 286 def transfers(self) -> int: 287 """How many transfers the part has served.""" 288 return self._native.transfers
A part that is not there, answering commands with the replies it was given.
This is how Sensirion lays out parts such as the SHT3x and the SCD4x. A write sends a
command and any arguments after it; a read takes the reply that command left, once,
padded with 0xFF the way an idle bus reads. A command given no reply leaves none,
and a read then is not acknowledged, which is what a real part does when asked for data
it does not have.
>>> SHT3X, READ_STATUS = 0x44, (0xF32D).to_bytes(2, "big")
>>> STATUS_AFTER_RESET = bytes([0x80, 0x10, 0xE1]) # the word 0x8010, then its CRC
>>> part = CommandPart(SHT3X).answering(READ_STATUS, STATUS_AFTER_RESET)
>>> part.address == SHT3X
True
243 def __init__(self, address: int, width: int = 2) -> None: 244 """Make a part answering at one address that has been given no replies yet. 245 246 :param address: The 7-bit address it answers to. 247 :param width: How many bytes a command takes: two for Sensirion's commands. 248 """ 249 self._native = _NativeCommandPart(address, width)
Make a part answering at one address that has been given no replies yet.
Parameters
- address: The 7-bit address it answers to.
- width: How many bytes a command takes: two for Sensirion's commands.
257 def answering(self, command: bytes, reply: bytes) -> CommandPart: 258 """Answer one command with a reply from now on, and return the part. 259 260 :param command: The command's bytes. 261 :param reply: What a read after it returns, in place of any reply given before. 262 :returns: This part, so calls chain. 263 """ 264 self.answer(command, reply) 265 return self
Answer one command with a reply from now on, and return the part.
Parameters
- command: The command's bytes.
- reply: What a read after it returns, in place of any reply given before. :returns: This part, so calls chain.
267 def answer(self, command: bytes, reply: bytes) -> None: 268 """Answer one command with a reply from now on. 269 270 :param command: The command's bytes. 271 :param reply: What a read after it returns, in place of any reply given before. 272 """ 273 self._native.answer(bytes(command), bytes(reply))
Answer one command with a reply from now on.
Parameters
- command: The command's bytes.
- reply: What a read after it returns, in place of any reply given before.
275 @property 276 def received(self) -> List[bytes]: 277 """Every write the part has received, oldest first: a command and any arguments.""" 278 return [bytes(write) for write in self._native.received]
Every write the part has received, oldest first: a command and any arguments.
724class Delay(Protocol): 725 """What paces a driver that has to wait between pin changes, such as a stepper 726 between steps. :class:`SleepDelay` really waits; :class:`DelayLog` counts every wait 727 and waits for none, for a program run with nothing plugged in.""" 728 729 def delay_micros(self, micros: int) -> None: 730 """Wait, or count the wait. 731 732 :param micros: How long, in microseconds. 733 """ 734 ...
What paces a driver that has to wait between pin changes, such as a stepper
between steps. SleepDelay really waits; DelayLog counts every wait
and waits for none, for a program run with nothing plugged in.
1968def _no_init_or_replace_init(self, *args, **kwargs): 1969 cls = type(self) 1970 1971 if cls._is_protocol: 1972 raise TypeError('Protocols cannot be instantiated') 1973 1974 # Already using a custom `__init__`. No need to calculate correct 1975 # `__init__` to call. This can lead to RecursionError. See bpo-45121. 1976 if cls.__init__ is not _no_init_or_replace_init: 1977 return 1978 1979 # Initially, `__init__` of a protocol subclass is set to `_no_init_or_replace_init`. 1980 # The first instantiation of the subclass will call `_no_init_or_replace_init` which 1981 # searches for a proper new `__init__` in the MRO. The new `__init__` 1982 # replaces the subclass' old `__init__` (ie `_no_init_or_replace_init`). Subsequent 1983 # instantiation of the protocol subclass will thus use the new 1984 # `__init__` and no longer call `_no_init_or_replace_init`. 1985 for base in cls.__mro__: 1986 init = base.__dict__.get('__init__', _no_init_or_replace_init) 1987 if init is not _no_init_or_replace_init: 1988 cls.__init__ = init 1989 break 1990 else: 1991 # should not happen 1992 cls.__init__ = object.__init__ 1993 1994 cls.__init__(self, *args, **kwargs)
737class DelayLog: 738 """A delay that records every wait it is asked for and sleeps through none of them, 739 as ``pamoja_hal::script::DelayLog`` does in Rust. 740 741 >>> delay = DelayLog() 742 >>> delay.delay_micros(480) 743 >>> delay.delay_micros(10_000) 744 >>> delay.total_micros, delay.total_millis 745 (10480, 10) 746 """ 747 748 __slots__ = ("_total", "_waits") 749 750 def __init__(self) -> None: 751 """Create a log with nothing waited yet.""" 752 self._waits: List[int] = [] 753 self._total = 0 754 755 @property 756 def waits_micros(self) -> List[int]: 757 """Every wait asked for, in microseconds, oldest first.""" 758 return list(self._waits) 759 760 @property 761 def total_micros(self) -> int: 762 """The waits added up, in microseconds.""" 763 return self._total 764 765 @property 766 def total_millis(self) -> int: 767 """The waits added up, in whole milliseconds, rounded down.""" 768 return self._total // 1_000 769 770 def delay_micros(self, micros: int) -> None: 771 """Record a wait. 772 773 :param micros: How long, in microseconds. 774 """ 775 self._waits.append(micros) 776 self._total += micros 777 778 def clear(self) -> None: 779 """Forget every recorded wait.""" 780 self._waits.clear() 781 self._total = 0
A delay that records every wait it is asked for and sleeps through none of them,
as pamoja_hal::script::DelayLog does in Rust.
>>> delay = DelayLog()
>>> delay.delay_micros(480)
>>> delay.delay_micros(10_000)
>>> delay.total_micros, delay.total_millis
(10480, 10)
750 def __init__(self) -> None: 751 """Create a log with nothing waited yet.""" 752 self._waits: List[int] = [] 753 self._total = 0
Create a log with nothing waited yet.
755 @property 756 def waits_micros(self) -> List[int]: 757 """Every wait asked for, in microseconds, oldest first.""" 758 return list(self._waits)
Every wait asked for, in microseconds, oldest first.
760 @property 761 def total_micros(self) -> int: 762 """The waits added up, in microseconds.""" 763 return self._total
The waits added up, in microseconds.
765 @property 766 def total_millis(self) -> int: 767 """The waits added up, in whole milliseconds, rounded down.""" 768 return self._total // 1_000
The waits added up, in whole milliseconds, rounded down.
358class I2cBus: 359 """One I2C bus, shared by the program and every driver built on it. 360 361 :meth:`open` opens the kernel's adapter on a Linux board; :meth:`simulated` puts 362 simulated parts of any kind on a bus, each answering at its own address; 363 :meth:`scripted` plays :class:`I2cStep` s in order and refuses any other transfer. A 364 failed transfer raises ``PamojaError`` with the reason: nothing answered at the address, 365 the script expected something else, or the kernel's own words. 366 367 >>> BME280, CHIP_ID_REGISTER, BME280_CHIP_ID = 0x76, 0xD0, 0x60 368 >>> bus = I2cBus.simulated([I2cPart(BME280).holding(CHIP_ID_REGISTER, bytes([BME280_CHIP_ID]))]) 369 >>> bus.write_read(BME280, bytes([CHIP_ID_REGISTER]), 1) == bytes([BME280_CHIP_ID]) 370 True 371 >>> bus.transfers 372 1 373 """ 374 375 __slots__ = ("_native",) 376 377 def __init__(self, native: _NativeBus) -> None: 378 """Wrap a native bus. Use :meth:`open`, :meth:`simulated`, or :meth:`scripted`.""" 379 self._native = native 380 381 @classmethod 382 def open(cls, path: str) -> I2cBus: 383 """Open the kernel's I2C adapter, such as ``/dev/i2c-1`` on a Raspberry Pi. 384 385 :param path: The adapter's device file. 386 :returns: The bus, with the real parts wired to it on the other end. 387 :raises PamojaError: Anywhere but Linux, and when the file cannot be opened as 388 an adapter: the interface is not turned on, or the process may not use it. 389 """ 390 return cls(_NativeBus.open(path)) 391 392 @classmethod 393 def simulated(cls, parts: Iterable[SimulatedPart] = ()) -> I2cBus: 394 """Make a bus of simulated parts, each answering at its own address. 395 396 :param parts: The parts on the bus, of any kind. A later part at an address an 397 earlier one holds takes its place. 398 :returns: The bus. A transfer to an address no part holds raises 399 ``PamojaError``, as nothing acknowledges it. 400 """ 401 return cls(_NativeBus.simulated([part._native for part in parts])) 402 403 @classmethod 404 def scripted(cls, steps: Iterable[I2cStep]) -> I2cBus: 405 """Make a bus that plays the steps in order and refuses any other transfer. 406 407 :param steps: The transfers a driver is expected to make, and the replies. 408 :returns: The bus. 409 """ 410 return cls(_NativeBus.scripted([step._native for step in steps])) 411 412 def attach(self, part: SimulatedPart) -> None: 413 """Put a copy of a part on a simulated bus, in place of any part at its address. 414 415 A driver keeps working across the change, which is how a test moves a reading on. 416 417 :param part: The part, of any kind. 418 :raises PamojaError: If the bus is not simulated. 419 """ 420 self._native.attach(part._native) 421 422 @property 423 def kind(self) -> I2cBusKind: 424 """What answers on the bus.""" 425 return I2cBusKind(self._native.kind) 426 427 def write(self, address: int, data: bytes) -> None: 428 """Write bytes to a part in one transaction: usually a register and its value. 429 430 :param address: The part's 7-bit address. 431 :param data: The bytes to write. 432 :raises PamojaError: If the transfer fails. 433 """ 434 self._native.write(address, bytes(data)) 435 436 def read(self, address: int, length: int) -> bytes: 437 """Read bytes from a part in one transaction. 438 439 :param address: The part's 7-bit address. 440 :param length: How many bytes to read. 441 :returns: The bytes. 442 :raises PamojaError: If the transfer fails. 443 """ 444 return bytes(self._native.read(address, length)) 445 446 def write_read(self, address: int, data: bytes, length: int) -> bytes: 447 """Write bytes and then read the reply in one transaction, with a repeated start 448 between them, which is how a register is read. 449 450 :param address: The part's 7-bit address. 451 :param data: What to write first, usually the register address. 452 :param length: How many bytes to read. 453 :returns: The reply. 454 :raises PamojaError: If the transfer fails. 455 """ 456 return bytes(self._native.write_read(address, bytes(data), length)) 457 458 def part(self, address: int) -> Optional[SimulatedPart]: 459 """Copy what a simulated part holds now, with whatever drivers wrote to it. 460 461 :param address: The part's address. 462 :returns: The copy, as the class of part it is, or ``None`` when the bus is not 463 simulated or no part holds the address. 464 """ 465 native = self._native.part(address) 466 return None if native is None else _part_of(native) 467 468 @property 469 def transfers(self) -> int: 470 """How many transfers have been made on the bus, by the program and every driver 471 on it, including any that failed.""" 472 return self._native.transfers 473 474 @property 475 def remaining(self) -> Optional[int]: 476 """How many steps a script has left, or ``None`` when the bus is not scripted.""" 477 return self._native.remaining 478 479 @property 480 def waited_micros(self) -> int: 481 """How long the drivers on the bus have asked to wait, in microseconds, whether 482 or not the process slept through it.""" 483 return self._native.waited_micros
One I2C bus, shared by the program and every driver built on it.
open() opens the kernel's adapter on a Linux board; simulated() puts
simulated parts of any kind on a bus, each answering at its own address;
scripted() plays I2cStep s in order and refuses any other transfer. A
failed transfer raises PamojaError with the reason: nothing answered at the address,
the script expected something else, or the kernel's own words.
>>> BME280, CHIP_ID_REGISTER, BME280_CHIP_ID = 0x76, 0xD0, 0x60
>>> bus = I2cBus.simulated([I2cPart(BME280).holding(CHIP_ID_REGISTER, bytes([BME280_CHIP_ID]))])
>>> bus.write_read(BME280, bytes([CHIP_ID_REGISTER]), 1) == bytes([BME280_CHIP_ID])
True
>>> bus.transfers
1
377 def __init__(self, native: _NativeBus) -> None: 378 """Wrap a native bus. Use :meth:`open`, :meth:`simulated`, or :meth:`scripted`.""" 379 self._native = native
Wrap a native bus. Use open(), simulated(), or scripted().
381 @classmethod 382 def open(cls, path: str) -> I2cBus: 383 """Open the kernel's I2C adapter, such as ``/dev/i2c-1`` on a Raspberry Pi. 384 385 :param path: The adapter's device file. 386 :returns: The bus, with the real parts wired to it on the other end. 387 :raises PamojaError: Anywhere but Linux, and when the file cannot be opened as 388 an adapter: the interface is not turned on, or the process may not use it. 389 """ 390 return cls(_NativeBus.open(path))
Open the kernel's I2C adapter, such as /dev/i2c-1 on a Raspberry Pi.
Parameters
- path: The adapter's device file. :returns: The bus, with the real parts wired to it on the other end.
Raises
- PamojaError: Anywhere but Linux, and when the file cannot be opened as an adapter: the interface is not turned on, or the process may not use it.
392 @classmethod 393 def simulated(cls, parts: Iterable[SimulatedPart] = ()) -> I2cBus: 394 """Make a bus of simulated parts, each answering at its own address. 395 396 :param parts: The parts on the bus, of any kind. A later part at an address an 397 earlier one holds takes its place. 398 :returns: The bus. A transfer to an address no part holds raises 399 ``PamojaError``, as nothing acknowledges it. 400 """ 401 return cls(_NativeBus.simulated([part._native for part in parts]))
Make a bus of simulated parts, each answering at its own address.
Parameters
- parts: The parts on the bus, of any kind. A later part at an address an
earlier one holds takes its place.
:returns: The bus. A transfer to an address no part holds raises
PamojaError, as nothing acknowledges it.
403 @classmethod 404 def scripted(cls, steps: Iterable[I2cStep]) -> I2cBus: 405 """Make a bus that plays the steps in order and refuses any other transfer. 406 407 :param steps: The transfers a driver is expected to make, and the replies. 408 :returns: The bus. 409 """ 410 return cls(_NativeBus.scripted([step._native for step in steps]))
Make a bus that plays the steps in order and refuses any other transfer.
Parameters
- steps: The transfers a driver is expected to make, and the replies. :returns: The bus.
412 def attach(self, part: SimulatedPart) -> None: 413 """Put a copy of a part on a simulated bus, in place of any part at its address. 414 415 A driver keeps working across the change, which is how a test moves a reading on. 416 417 :param part: The part, of any kind. 418 :raises PamojaError: If the bus is not simulated. 419 """ 420 self._native.attach(part._native)
Put a copy of a part on a simulated bus, in place of any part at its address.
A driver keeps working across the change, which is how a test moves a reading on.
Parameters
- part: The part, of any kind.
Raises
- PamojaError: If the bus is not simulated.
422 @property 423 def kind(self) -> I2cBusKind: 424 """What answers on the bus.""" 425 return I2cBusKind(self._native.kind)
What answers on the bus.
427 def write(self, address: int, data: bytes) -> None: 428 """Write bytes to a part in one transaction: usually a register and its value. 429 430 :param address: The part's 7-bit address. 431 :param data: The bytes to write. 432 :raises PamojaError: If the transfer fails. 433 """ 434 self._native.write(address, bytes(data))
Write bytes to a part in one transaction: usually a register and its value.
Parameters
- address: The part's 7-bit address.
- data: The bytes to write.
Raises
- PamojaError: If the transfer fails.
436 def read(self, address: int, length: int) -> bytes: 437 """Read bytes from a part in one transaction. 438 439 :param address: The part's 7-bit address. 440 :param length: How many bytes to read. 441 :returns: The bytes. 442 :raises PamojaError: If the transfer fails. 443 """ 444 return bytes(self._native.read(address, length))
Read bytes from a part in one transaction.
Parameters
- address: The part's 7-bit address.
- length: How many bytes to read. :returns: The bytes.
Raises
- PamojaError: If the transfer fails.
446 def write_read(self, address: int, data: bytes, length: int) -> bytes: 447 """Write bytes and then read the reply in one transaction, with a repeated start 448 between them, which is how a register is read. 449 450 :param address: The part's 7-bit address. 451 :param data: What to write first, usually the register address. 452 :param length: How many bytes to read. 453 :returns: The reply. 454 :raises PamojaError: If the transfer fails. 455 """ 456 return bytes(self._native.write_read(address, bytes(data), length))
Write bytes and then read the reply in one transaction, with a repeated start between them, which is how a register is read.
Parameters
- address: The part's 7-bit address.
- data: What to write first, usually the register address.
- length: How many bytes to read. :returns: The reply.
Raises
- PamojaError: If the transfer fails.
458 def part(self, address: int) -> Optional[SimulatedPart]: 459 """Copy what a simulated part holds now, with whatever drivers wrote to it. 460 461 :param address: The part's address. 462 :returns: The copy, as the class of part it is, or ``None`` when the bus is not 463 simulated or no part holds the address. 464 """ 465 native = self._native.part(address) 466 return None if native is None else _part_of(native)
Copy what a simulated part holds now, with whatever drivers wrote to it.
Parameters
- address: The part's address.
:returns: The copy, as the class of part it is, or
Nonewhen the bus is not simulated or no part holds the address.
468 @property 469 def transfers(self) -> int: 470 """How many transfers have been made on the bus, by the program and every driver 471 on it, including any that failed.""" 472 return self._native.transfers
How many transfers have been made on the bus, by the program and every driver on it, including any that failed.
474 @property 475 def remaining(self) -> Optional[int]: 476 """How many steps a script has left, or ``None`` when the bus is not scripted.""" 477 return self._native.remaining
How many steps a script has left, or None when the bus is not scripted.
479 @property 480 def waited_micros(self) -> int: 481 """How long the drivers on the bus have asked to wait, in microseconds, whether 482 or not the process slept through it.""" 483 return self._native.waited_micros
How long the drivers on the bus have asked to wait, in microseconds, whether or not the process slept through it.
52class I2cBusKind(str, enum.Enum): 53 """What answers on a bus.""" 54 55 #: The kernel's adapter, with real parts on real wires. 56 ADAPTER = "Adapter" 57 #: Simulated parts, answering from their registers. 58 SIMULATED = "Simulated" 59 #: A script of the transfers a driver is expected to make. 60 SCRIPTED = "Scripted"
What answers on a bus.
63class I2cFault(str, enum.Enum): 64 """How a scripted step fails the transfer that reaches it.""" 65 66 #: Nothing acknowledged the address. 67 NO_ACKNOWLEDGE_ADDRESS = "NoAcknowledgeAddress" 68 #: The part did not acknowledge a data byte. 69 NO_ACKNOWLEDGE_DATA = "NoAcknowledgeData" 70 #: A missing acknowledge, with no telling whether of the address or the data. 71 NO_ACKNOWLEDGE = "NoAcknowledge" 72 #: A bus error, such as a misplaced start or stop condition. 73 BUS = "Bus" 74 #: Another controller won the bus. 75 ARBITRATION_LOSS = "ArbitrationLoss" 76 #: Data arrived faster than it was taken. 77 OVERRUN = "Overrun" 78 #: A failure of no more particular kind. 79 OTHER = "Other"
How a scripted step fails the transfer that reaches it.
82class I2cPart: 83 """A part that is not there, answering from 256 registers. 84 85 A write names a register and fills it and the ones after it; a read takes them 86 back from wherever the last write left off. What a driver writes stays written, so 87 :meth:`register` reads a part's configuration back once a driver is done with it. 88 89 >>> BME280, CHIP_ID_REGISTER, BME280_CHIP_ID = 0x76, 0xD0, 0x60 90 >>> part = I2cPart(BME280).holding(CHIP_ID_REGISTER, bytes([BME280_CHIP_ID])) 91 >>> part.register(CHIP_ID_REGISTER) == BME280_CHIP_ID 92 True 93 """ 94 95 __slots__ = ("_native",) 96 97 def __init__(self, address: int) -> None: 98 """Make a part answering at one address, with every register reading zero. 99 100 :param address: The 7-bit address it answers to. 101 """ 102 self._native = _NativePart(address) 103 104 @classmethod 105 def _wrap(cls, native: _NativePart) -> I2cPart: 106 part = cls.__new__(cls) 107 part._native = native 108 return part 109 110 def holding(self, first: int, data: bytes) -> I2cPart: 111 """Put bytes in the part from a register on, and return the part. 112 113 :param first: The register the bytes start at. 114 :param data: What to put there. Past the last register it wraps to the first. 115 :returns: This part, so calls chain. 116 """ 117 self._native.load(first, bytes(data)) 118 return self 119 120 def register(self, register: int) -> int: 121 """Read what one register holds now. 122 123 :param register: Which register. 124 :returns: Its value, which is what a driver wrote if it wrote one. 125 """ 126 return self._native.register(register) 127 128 def read(self, first: int, length: int) -> bytes: 129 """Read consecutive registers from one register on. 130 131 :param first: The first register. 132 :param length: How many registers. 133 :returns: One byte per register. 134 """ 135 return bytes(self._native.read(first, length)) 136 137 @property 138 def address(self) -> int: 139 """The address the part answers to.""" 140 return self._native.address 141 142 @property 143 def transfers(self) -> int: 144 """How many transfers the part has served.""" 145 return self._native.transfers
A part that is not there, answering from 256 registers.
A write names a register and fills it and the ones after it; a read takes them
back from wherever the last write left off. What a driver writes stays written, so
register() reads a part's configuration back once a driver is done with it.
>>> BME280, CHIP_ID_REGISTER, BME280_CHIP_ID = 0x76, 0xD0, 0x60
>>> part = I2cPart(BME280).holding(CHIP_ID_REGISTER, bytes([BME280_CHIP_ID]))
>>> part.register(CHIP_ID_REGISTER) == BME280_CHIP_ID
True
97 def __init__(self, address: int) -> None: 98 """Make a part answering at one address, with every register reading zero. 99 100 :param address: The 7-bit address it answers to. 101 """ 102 self._native = _NativePart(address)
Make a part answering at one address, with every register reading zero.
Parameters
- address: The 7-bit address it answers to.
110 def holding(self, first: int, data: bytes) -> I2cPart: 111 """Put bytes in the part from a register on, and return the part. 112 113 :param first: The register the bytes start at. 114 :param data: What to put there. Past the last register it wraps to the first. 115 :returns: This part, so calls chain. 116 """ 117 self._native.load(first, bytes(data)) 118 return self
Put bytes in the part from a register on, and return the part.
Parameters
- first: The register the bytes start at.
- data: What to put there. Past the last register it wraps to the first. :returns: This part, so calls chain.
120 def register(self, register: int) -> int: 121 """Read what one register holds now. 122 123 :param register: Which register. 124 :returns: Its value, which is what a driver wrote if it wrote one. 125 """ 126 return self._native.register(register)
Read what one register holds now.
Parameters
- register: Which register. :returns: Its value, which is what a driver wrote if it wrote one.
128 def read(self, first: int, length: int) -> bytes: 129 """Read consecutive registers from one register on. 130 131 :param first: The first register. 132 :param length: How many registers. 133 :returns: One byte per register. 134 """ 135 return bytes(self._native.read(first, length))
Read consecutive registers from one register on.
Parameters
- first: The first register.
- length: How many registers. :returns: One byte per register.
304class I2cStep: 305 """One transfer a script expects, and what the part answers.""" 306 307 __slots__ = ("_native",) 308 309 def __init__(self, native: _NativeStep) -> None: 310 """Wrap a native step. Use :meth:`write`, :meth:`read`, :meth:`write_read`, or 311 :meth:`fault`.""" 312 self._native = native 313 314 @classmethod 315 def write(cls, address: int, data: bytes) -> I2cStep: 316 """The driver writes exactly ``data`` to the address. 317 318 :param address: The 7-bit address the write must go to. 319 :param data: The bytes the driver must send. 320 :returns: The step. 321 """ 322 return cls(_NativeStep.write(address, bytes(data))) 323 324 @classmethod 325 def read(cls, address: int, reply: bytes) -> I2cStep: 326 """The driver reads from the address and receives ``reply``. 327 328 :param address: The 7-bit address the read must come from. 329 :param reply: The bytes the part answers with; the driver must ask for exactly 330 this many. 331 :returns: The step. 332 """ 333 return cls(_NativeStep.read(address, bytes(reply))) 334 335 @classmethod 336 def write_read(cls, address: int, data: bytes, reply: bytes) -> I2cStep: 337 """The driver writes ``data`` and then reads ``reply`` in one transaction, the 338 shape of a register read. 339 340 :param address: The 7-bit address of the part. 341 :param data: The bytes the driver must send first, usually a register address. 342 :param reply: The bytes the part answers with. 343 :returns: The step. 344 """ 345 return cls(_NativeStep.write_read(address, bytes(data), bytes(reply))) 346 347 @classmethod 348 def fault(cls, address: int, fault: I2cFault) -> I2cStep: 349 """The next transfer to the address fails, the way a missing or busy part does. 350 351 :param address: The 7-bit address the failing transfer must go to. 352 :param fault: The failure the driver sees. 353 :returns: The step. 354 """ 355 return cls(_NativeStep.fault(address, I2cFault(fault).value))
One transfer a script expects, and what the part answers.
309 def __init__(self, native: _NativeStep) -> None: 310 """Wrap a native step. Use :meth:`write`, :meth:`read`, :meth:`write_read`, or 311 :meth:`fault`.""" 312 self._native = native
Wrap a native step. Use write(), read(), write_read(), or
fault().
314 @classmethod 315 def write(cls, address: int, data: bytes) -> I2cStep: 316 """The driver writes exactly ``data`` to the address. 317 318 :param address: The 7-bit address the write must go to. 319 :param data: The bytes the driver must send. 320 :returns: The step. 321 """ 322 return cls(_NativeStep.write(address, bytes(data)))
The driver writes exactly data to the address.
Parameters
- address: The 7-bit address the write must go to.
- data: The bytes the driver must send. :returns: The step.
324 @classmethod 325 def read(cls, address: int, reply: bytes) -> I2cStep: 326 """The driver reads from the address and receives ``reply``. 327 328 :param address: The 7-bit address the read must come from. 329 :param reply: The bytes the part answers with; the driver must ask for exactly 330 this many. 331 :returns: The step. 332 """ 333 return cls(_NativeStep.read(address, bytes(reply)))
The driver reads from the address and receives reply.
Parameters
- address: The 7-bit address the read must come from.
- reply: The bytes the part answers with; the driver must ask for exactly this many. :returns: The step.
335 @classmethod 336 def write_read(cls, address: int, data: bytes, reply: bytes) -> I2cStep: 337 """The driver writes ``data`` and then reads ``reply`` in one transaction, the 338 shape of a register read. 339 340 :param address: The 7-bit address of the part. 341 :param data: The bytes the driver must send first, usually a register address. 342 :param reply: The bytes the part answers with. 343 :returns: The step. 344 """ 345 return cls(_NativeStep.write_read(address, bytes(data), bytes(reply)))
The driver writes data and then reads reply in one transaction, the
shape of a register read.
Parameters
- address: The 7-bit address of the part.
- data: The bytes the driver must send first, usually a register address.
- reply: The bytes the part answers with. :returns: The step.
347 @classmethod 348 def fault(cls, address: int, fault: I2cFault) -> I2cStep: 349 """The next transfer to the address fails, the way a missing or busy part does. 350 351 :param address: The 7-bit address the failing transfer must go to. 352 :param fault: The failure the driver sees. 353 :returns: The step. 354 """ 355 return cls(_NativeStep.fault(address, I2cFault(fault).value))
The next transfer to the address fails, the way a missing or busy part does.
Parameters
- address: The 7-bit address the failing transfer must go to.
- fault: The failure the driver sees. :returns: The step.
486class Parity(str, enum.Enum): 487 """The parity bit each character on a serial line carries.""" 488 489 #: No parity bit. 490 NONE = "None" 491 #: A bit that makes the count of ones even, what Modbus RTU asks for by default. 492 EVEN = "Even" 493 #: A bit that makes the count of ones odd. 494 ODD = "Odd"
The parity bit each character on a serial line carries.
585class SerialPort: 586 """One serial port, shared by the program and every driver built on it. 587 588 :meth:`open` opens the kernel's serial device raw on a Linux board, :meth:`looped` is a 589 line with TX wired to RX, :meth:`pair` the two ends of a null-modem cable, and 590 :meth:`scripted` a port that checks each write against a script. A write returns once 591 the bytes have left the UART, and a read once bytes have arrived or its timeout has 592 passed. On anything but the kernel's device a read never waits: it returns at once, and 593 the time it would have waited is added to :attr:`waited_micros`. Every call releases the 594 interpreter while the line is busy, and a failure raises ``PamojaError``. 595 596 >>> gateway, node = SerialPort.pair(SerialSettings(115_200)) 597 >>> node.write(b"t=21.5") 598 >>> gateway.read(16, timeout=0.1) 599 b't=21.5' 600 """ 601 602 __slots__ = ("_native",) 603 604 def __init__(self, native: _NativeSerialPort) -> None: 605 """Wrap a native port; use one of the class methods instead.""" 606 self._native = native 607 608 @classmethod 609 def open(cls, path: str, settings: SerialSettings) -> SerialPort: 610 """Open the kernel's serial device raw. 611 612 :param path: ``/dev/serial0`` for a Raspberry Pi's own UART, ``/dev/ttyUSB0`` or 613 ``/dev/ttyACM0`` for a USB adapter. 614 :param settings: The speed, a standard rate from 1200 to 921600, and the format. 615 :returns: The port. 616 :raises PamojaError: Anywhere but Linux, or when the device cannot be opened. 617 """ 618 return cls(_NativeSerialPort.open(path, *settings._values())) 619 620 @classmethod 621 def looped(cls, settings: SerialSettings) -> SerialPort: 622 """A line looped back on itself: every byte written is waiting to be read. 623 624 :param settings: The speed and format the line runs at. 625 :returns: The port. 626 """ 627 return cls(_NativeSerialPort.looped(*settings._values())) 628 629 @classmethod 630 def pair(cls, settings: SerialSettings) -> Tuple[SerialPort, SerialPort]: 631 """The two ends of a null-modem pair: what one end writes, the other reads. 632 633 :param settings: The speed and format both ends run at. 634 :returns: The two ends. 635 """ 636 one, other = _NativeSerialPort.pair(*settings._values()) 637 return cls(one), cls(other) 638 639 @classmethod 640 def scripted(cls, settings: SerialSettings, steps: Iterable[SerialStep]) -> SerialPort: 641 """A port that checks each write against the next step of a script. 642 643 :param settings: The speed and format the line runs at. 644 :param steps: The writes and reads, in order; reads at the start are there at once. 645 :returns: The port. 646 """ 647 natives = [step._native for step in steps] 648 return cls(_NativeSerialPort.scripted(*settings._values(), natives)) 649 650 @property 651 def kind(self) -> SerialPortKind: 652 """What is on the other end of the port.""" 653 return SerialPortKind(self._native.kind) 654 655 @property 656 def settings(self) -> SerialSettings: 657 """The speed and character format the port runs at.""" 658 baud, parity, stop_bits = self._native.settings 659 return SerialSettings(baud, Parity(parity), stop_bits) 660 661 def write(self, data: bytes) -> None: 662 """Write bytes, returning once they have left the UART. 663 664 :param data: The bytes, in order. 665 :raises PamojaError: When a script expected another write, or the device fails. 666 """ 667 self._native.write(bytes(data)) 668 669 def read(self, size: int, timeout: float) -> bytes: 670 """Read up to ``size`` bytes, waiting up to ``timeout`` for the first one when 671 nothing has arrived. 672 673 :param size: The most bytes to read. 674 :param timeout: How long to wait for the first byte, in seconds. 675 :returns: What arrived, empty when the timeout passed with nothing. 676 :raises PamojaError: When the device fails. 677 """ 678 return self._native.read(size, _micros(timeout)) 679 680 def discard_input(self) -> None: 681 """Drop whatever has arrived and not been read, as a client does before a request 682 so a stale reply cannot be taken for the new one. 683 684 :raises PamojaError: When the device fails. 685 """ 686 self._native.discard_input() 687 688 def wait(self, seconds: float) -> None: 689 """Wait, as a protocol does to leave the line silent between frames: really on the 690 kernel's device, and anywhere else only counted. 691 692 :param seconds: How long. 693 """ 694 self._native.wait(_micros(seconds)) 695 696 @property 697 def written(self) -> int: 698 """How many bytes have been written through the port.""" 699 return self._native.written 700 701 @property 702 def received(self) -> int: 703 """How many bytes have been read through the port.""" 704 return self._native.received 705 706 @property 707 def waited_micros(self) -> int: 708 """How long reads have waited without an answer, and waits have waited, in 709 microseconds, whether or not the process slept through it.""" 710 return self._native.waited_micros 711 712 @property 713 def remaining(self) -> Optional[int]: 714 """How many steps a script has left, or ``None`` when the port is not scripted.""" 715 return self._native.remaining
One serial port, shared by the program and every driver built on it.
open() opens the kernel's serial device raw on a Linux board, looped() is a
line with TX wired to RX, pair() the two ends of a null-modem cable, and
scripted() a port that checks each write against a script. A write returns once
the bytes have left the UART, and a read once bytes have arrived or its timeout has
passed. On anything but the kernel's device a read never waits: it returns at once, and
the time it would have waited is added to waited_micros. Every call releases the
interpreter while the line is busy, and a failure raises PamojaError.
>>> gateway, node = SerialPort.pair(SerialSettings(115_200))
>>> node.write(b"t=21.5")
>>> gateway.read(16, timeout=0.1)
b't=21.5'
604 def __init__(self, native: _NativeSerialPort) -> None: 605 """Wrap a native port; use one of the class methods instead.""" 606 self._native = native
Wrap a native port; use one of the class methods instead.
608 @classmethod 609 def open(cls, path: str, settings: SerialSettings) -> SerialPort: 610 """Open the kernel's serial device raw. 611 612 :param path: ``/dev/serial0`` for a Raspberry Pi's own UART, ``/dev/ttyUSB0`` or 613 ``/dev/ttyACM0`` for a USB adapter. 614 :param settings: The speed, a standard rate from 1200 to 921600, and the format. 615 :returns: The port. 616 :raises PamojaError: Anywhere but Linux, or when the device cannot be opened. 617 """ 618 return cls(_NativeSerialPort.open(path, *settings._values()))
Open the kernel's serial device raw.
Parameters
- path:
/dev/serial0for a Raspberry Pi's own UART,/dev/ttyUSB0or/dev/ttyACM0for a USB adapter. - settings: The speed, a standard rate from 1200 to 921600, and the format. :returns: The port.
Raises
- PamojaError: Anywhere but Linux, or when the device cannot be opened.
620 @classmethod 621 def looped(cls, settings: SerialSettings) -> SerialPort: 622 """A line looped back on itself: every byte written is waiting to be read. 623 624 :param settings: The speed and format the line runs at. 625 :returns: The port. 626 """ 627 return cls(_NativeSerialPort.looped(*settings._values()))
A line looped back on itself: every byte written is waiting to be read.
Parameters
- settings: The speed and format the line runs at. :returns: The port.
629 @classmethod 630 def pair(cls, settings: SerialSettings) -> Tuple[SerialPort, SerialPort]: 631 """The two ends of a null-modem pair: what one end writes, the other reads. 632 633 :param settings: The speed and format both ends run at. 634 :returns: The two ends. 635 """ 636 one, other = _NativeSerialPort.pair(*settings._values()) 637 return cls(one), cls(other)
The two ends of a null-modem pair: what one end writes, the other reads.
Parameters
- settings: The speed and format both ends run at. :returns: The two ends.
639 @classmethod 640 def scripted(cls, settings: SerialSettings, steps: Iterable[SerialStep]) -> SerialPort: 641 """A port that checks each write against the next step of a script. 642 643 :param settings: The speed and format the line runs at. 644 :param steps: The writes and reads, in order; reads at the start are there at once. 645 :returns: The port. 646 """ 647 natives = [step._native for step in steps] 648 return cls(_NativeSerialPort.scripted(*settings._values(), natives))
A port that checks each write against the next step of a script.
Parameters
- settings: The speed and format the line runs at.
- steps: The writes and reads, in order; reads at the start are there at once. :returns: The port.
650 @property 651 def kind(self) -> SerialPortKind: 652 """What is on the other end of the port.""" 653 return SerialPortKind(self._native.kind)
What is on the other end of the port.
655 @property 656 def settings(self) -> SerialSettings: 657 """The speed and character format the port runs at.""" 658 baud, parity, stop_bits = self._native.settings 659 return SerialSettings(baud, Parity(parity), stop_bits)
The speed and character format the port runs at.
661 def write(self, data: bytes) -> None: 662 """Write bytes, returning once they have left the UART. 663 664 :param data: The bytes, in order. 665 :raises PamojaError: When a script expected another write, or the device fails. 666 """ 667 self._native.write(bytes(data))
Write bytes, returning once they have left the UART.
Parameters
- data: The bytes, in order.
Raises
- PamojaError: When a script expected another write, or the device fails.
669 def read(self, size: int, timeout: float) -> bytes: 670 """Read up to ``size`` bytes, waiting up to ``timeout`` for the first one when 671 nothing has arrived. 672 673 :param size: The most bytes to read. 674 :param timeout: How long to wait for the first byte, in seconds. 675 :returns: What arrived, empty when the timeout passed with nothing. 676 :raises PamojaError: When the device fails. 677 """ 678 return self._native.read(size, _micros(timeout))
Read up to size bytes, waiting up to timeout for the first one when
nothing has arrived.
Parameters
- size: The most bytes to read.
- timeout: How long to wait for the first byte, in seconds. :returns: What arrived, empty when the timeout passed with nothing.
Raises
- PamojaError: When the device fails.
680 def discard_input(self) -> None: 681 """Drop whatever has arrived and not been read, as a client does before a request 682 so a stale reply cannot be taken for the new one. 683 684 :raises PamojaError: When the device fails. 685 """ 686 self._native.discard_input()
Drop whatever has arrived and not been read, as a client does before a request so a stale reply cannot be taken for the new one.
Raises
- PamojaError: When the device fails.
688 def wait(self, seconds: float) -> None: 689 """Wait, as a protocol does to leave the line silent between frames: really on the 690 kernel's device, and anywhere else only counted. 691 692 :param seconds: How long. 693 """ 694 self._native.wait(_micros(seconds))
Wait, as a protocol does to leave the line silent between frames: really on the kernel's device, and anywhere else only counted.
Parameters
- seconds: How long.
696 @property 697 def written(self) -> int: 698 """How many bytes have been written through the port.""" 699 return self._native.written
How many bytes have been written through the port.
701 @property 702 def received(self) -> int: 703 """How many bytes have been read through the port.""" 704 return self._native.received
How many bytes have been read through the port.
706 @property 707 def waited_micros(self) -> int: 708 """How long reads have waited without an answer, and waits have waited, in 709 microseconds, whether or not the process slept through it.""" 710 return self._native.waited_micros
How long reads have waited without an answer, and waits have waited, in microseconds, whether or not the process slept through it.
497class SerialPortKind(str, enum.Enum): 498 """What is on the other end of a serial port.""" 499 500 #: The kernel's serial device, with a real line on the other end. 501 DEVICE = "Device" 502 #: The port's own output, looped back to its input. 503 LOOPED = "Looped" 504 #: The other end of a null-modem pair. 505 PAIRED = "Paired" 506 #: A simulated device that answers each write. 507 SIMULATED = "Simulated" 508 #: A script of the writes a driver is expected to make. 509 SCRIPTED = "Scripted"
What is on the other end of a serial port.
512@dataclass(frozen=True) 513class SerialSettings: 514 """A port's speed and character format: eight data bits, with the parity and stop bits 515 given. 516 517 >>> modbus = SerialSettings(9_600, Parity.EVEN) 518 >>> str(modbus), modbus.bits_per_character, modbus.character_nanos 519 ('9600 8E1', 11, 1145834) 520 """ 521 522 #: The speed, in bits a second. 523 baud: int 524 #: The parity bit each character carries. 525 parity: Parity = Parity.NONE 526 #: 1 or 2 stop bits. 527 stop_bits: int = 1 528 529 def _values(self) -> tuple: 530 return (self.baud, Parity(self.parity).value, self.stop_bits) 531 532 @property 533 def bits_per_character(self) -> int: 534 """The bits one character takes on the wire: a start bit, eight data bits, the 535 parity bit if there is one, and the stop bits.""" 536 return _serial_bits_per_character(*self._values()) 537 538 @property 539 def character_nanos(self) -> int: 540 """How long one character takes on the wire, in nanoseconds, rounded up.""" 541 return _serial_character_nanos(*self._values()) 542 543 def transfer_micros(self, count: int) -> int: 544 """How long ``count`` bytes sent back to back take on the wire. 545 546 :param count: How many bytes. 547 :returns: The time in microseconds, rounded up. 548 """ 549 return _serial_transfer_micros(*self._values(), count) 550 551 def __str__(self) -> str: 552 letter = {Parity.NONE: "N", Parity.EVEN: "E", Parity.ODD: "O"}[Parity(self.parity)] 553 return f"{self.baud} 8{letter}{self.stop_bits}"
A port's speed and character format: eight data bits, with the parity and stop bits given.
>>> modbus = SerialSettings(9_600, Parity.EVEN)
>>> str(modbus), modbus.bits_per_character, modbus.character_nanos
('9600 8E1', 11, 1145834)
532 @property 533 def bits_per_character(self) -> int: 534 """The bits one character takes on the wire: a start bit, eight data bits, the 535 parity bit if there is one, and the stop bits.""" 536 return _serial_bits_per_character(*self._values())
The bits one character takes on the wire: a start bit, eight data bits, the parity bit if there is one, and the stop bits.
538 @property 539 def character_nanos(self) -> int: 540 """How long one character takes on the wire, in nanoseconds, rounded up.""" 541 return _serial_character_nanos(*self._values())
How long one character takes on the wire, in nanoseconds, rounded up.
543 def transfer_micros(self, count: int) -> int: 544 """How long ``count`` bytes sent back to back take on the wire. 545 546 :param count: How many bytes. 547 :returns: The time in microseconds, rounded up. 548 """ 549 return _serial_transfer_micros(*self._values(), count)
How long count bytes sent back to back take on the wire.
Parameters
- count: How many bytes. :returns: The time in microseconds, rounded up.
556class SerialStep: 557 """One step of a scripted port: :meth:`write` for a write the program is expected to 558 make, and :meth:`read` for bytes the far end sends.""" 559 560 __slots__ = ("_native",) 561 562 def __init__(self, native: _NativeSerialStep) -> None: 563 """Wrap a native step; use :meth:`write` or :meth:`read` instead.""" 564 self._native = native 565 566 @classmethod 567 def write(cls, data: bytes) -> SerialStep: 568 """A write the program is expected to make, in one call. 569 570 :param data: The bytes of the write. 571 :returns: The step. 572 """ 573 return cls(_NativeSerialStep.write(bytes(data))) 574 575 @classmethod 576 def read(cls, data: bytes) -> SerialStep: 577 """Bytes the far end sends, readable once every step before them has happened. 578 579 :param data: What arrives. 580 :returns: The step. 581 """ 582 return cls(_NativeSerialStep.read(bytes(data)))
One step of a scripted port: write() for a write the program is expected to
make, and read() for bytes the far end sends.
566 @classmethod 567 def write(cls, data: bytes) -> SerialStep: 568 """A write the program is expected to make, in one call. 569 570 :param data: The bytes of the write. 571 :returns: The step. 572 """ 573 return cls(_NativeSerialStep.write(bytes(data)))
A write the program is expected to make, in one call.
Parameters
- data: The bytes of the write. :returns: The step.
575 @classmethod 576 def read(cls, data: bytes) -> SerialStep: 577 """Bytes the far end sends, readable once every step before them has happened. 578 579 :param data: What arrives. 580 :returns: The step. 581 """ 582 return cls(_NativeSerialStep.read(bytes(data)))
Bytes the far end sends, readable once every step before them has happened.
Parameters
- data: What arrives. :returns: The step.
784class SleepDelay: 785 """A delay that really waits: :func:`time.sleep` for a millisecond or more, and a 786 spin on :func:`time.perf_counter_ns` for a shorter wait, which the scheduler cannot 787 keep. A sleep lasts at least what was asked and may run over by the scheduler's own 788 latency.""" 789 790 __slots__ = () 791 792 def delay_micros(self, micros: int) -> None: 793 """Wait. 794 795 :param micros: How long, in microseconds. 796 """ 797 if micros >= 1_000: 798 time.sleep(micros / 1_000_000) 799 return 800 until = time.perf_counter_ns() + micros * 1_000 801 while time.perf_counter_ns() < until: 802 pass
A delay that really waits: time.sleep() for a millisecond or more, and a
spin on time.perf_counter_ns() for a shorter wait, which the scheduler cannot
keep. A sleep lasts at least what was asked and may run over by the scheduler's own
latency.
792 def delay_micros(self, micros: int) -> None: 793 """Wait. 794 795 :param micros: How long, in microseconds. 796 """ 797 if micros >= 1_000: 798 time.sleep(micros / 1_000_000) 799 return 800 until = time.perf_counter_ns() + micros * 1_000 801 while time.perf_counter_ns() < until: 802 pass
Wait.
Parameters
- micros: How long, in microseconds.
148class WordPart: 149 """A part that is not there, answering from 256 registers sixteen bits wide. 150 151 This is how Texas Instruments lays out parts such as the TMP117, the INA219 and 152 INA226, the OPT3001, the ADS1115, and the HDC1080. A pointer byte names a register and 153 a register travels most significant byte first. Bits the part sets for itself, such as 154 a conversion-ready flag, are marked with :meth:`read_only` and keep the part's value 155 whatever a driver writes. 156 157 >>> TMP117, DEVICE_ID_REGISTER, TMP117_DEVICE_ID = 0x48, 0x0F, 0x0117 158 >>> part = WordPart(TMP117).holding(DEVICE_ID_REGISTER, TMP117_DEVICE_ID) 159 >>> part.word(DEVICE_ID_REGISTER) == TMP117_DEVICE_ID 160 True 161 """ 162 163 __slots__ = ("_native",) 164 165 def __init__(self, address: int) -> None: 166 """Make a part answering at one address, with every register reading zero. 167 168 :param address: The 7-bit address it answers to. 169 """ 170 self._native = _NativeWordPart(address) 171 172 @classmethod 173 def _wrap(cls, native: _NativeWordPart) -> WordPart: 174 part = cls.__new__(cls) 175 part._native = native 176 return part 177 178 def holding(self, register: int, value: int) -> WordPart: 179 """Put a value in one register, and return the part. 180 181 :param register: The register. 182 :param value: What it holds, read-only bits included. 183 :returns: This part, so calls chain. 184 """ 185 self._native.set(register, value) 186 return self 187 188 def read_only(self, register: int, mask: int) -> WordPart: 189 """Mark bits of one register as the part's to set, and return the part. 190 191 :param register: The register. 192 :param mask: The bits a driver's write leaves as the part holds them. 193 :returns: This part, so calls chain. 194 """ 195 self._native.read_only(register, mask) 196 return self 197 198 def set(self, register: int, value: int) -> None: 199 """Put a value in one register, read-only bits included, as the part itself would. 200 201 :param register: The register. 202 :param value: What it holds. 203 """ 204 self._native.set(register, value) 205 206 def word(self, register: int) -> int: 207 """Read what one register holds now. 208 209 :param register: Which register. 210 :returns: Its value, which is what a driver wrote apart from the read-only bits. 211 """ 212 return self._native.word(register) 213 214 @property 215 def address(self) -> int: 216 """The address the part answers to.""" 217 return self._native.address 218 219 @property 220 def transfers(self) -> int: 221 """How many transfers the part has served.""" 222 return self._native.transfers
A part that is not there, answering from 256 registers sixteen bits wide.
This is how Texas Instruments lays out parts such as the TMP117, the INA219 and
INA226, the OPT3001, the ADS1115, and the HDC1080. A pointer byte names a register and
a register travels most significant byte first. Bits the part sets for itself, such as
a conversion-ready flag, are marked with read_only() and keep the part's value
whatever a driver writes.
>>> TMP117, DEVICE_ID_REGISTER, TMP117_DEVICE_ID = 0x48, 0x0F, 0x0117
>>> part = WordPart(TMP117).holding(DEVICE_ID_REGISTER, TMP117_DEVICE_ID)
>>> part.word(DEVICE_ID_REGISTER) == TMP117_DEVICE_ID
True
165 def __init__(self, address: int) -> None: 166 """Make a part answering at one address, with every register reading zero. 167 168 :param address: The 7-bit address it answers to. 169 """ 170 self._native = _NativeWordPart(address)
Make a part answering at one address, with every register reading zero.
Parameters
- address: The 7-bit address it answers to.
178 def holding(self, register: int, value: int) -> WordPart: 179 """Put a value in one register, and return the part. 180 181 :param register: The register. 182 :param value: What it holds, read-only bits included. 183 :returns: This part, so calls chain. 184 """ 185 self._native.set(register, value) 186 return self
Put a value in one register, and return the part.
Parameters
- register: The register.
- value: What it holds, read-only bits included. :returns: This part, so calls chain.
188 def read_only(self, register: int, mask: int) -> WordPart: 189 """Mark bits of one register as the part's to set, and return the part. 190 191 :param register: The register. 192 :param mask: The bits a driver's write leaves as the part holds them. 193 :returns: This part, so calls chain. 194 """ 195 self._native.read_only(register, mask) 196 return self
Mark bits of one register as the part's to set, and return the part.
Parameters
- register: The register.
- mask: The bits a driver's write leaves as the part holds them. :returns: This part, so calls chain.
198 def set(self, register: int, value: int) -> None: 199 """Put a value in one register, read-only bits included, as the part itself would. 200 201 :param register: The register. 202 :param value: What it holds. 203 """ 204 self._native.set(register, value)
Put a value in one register, read-only bits included, as the part itself would.
Parameters
- register: The register.
- value: What it holds.
206 def word(self, register: int) -> int: 207 """Read what one register holds now. 208 209 :param register: Which register. 210 :returns: Its value, which is what a driver wrote apart from the read-only bits. 211 """ 212 return self._native.word(register)
Read what one register holds now.
Parameters
- register: Which register. :returns: Its value, which is what a driver wrote apart from the read-only bits.