Source code for drivers.gpio.gpio

"""GPIO utility functions for Raspberry Pi BCM pin control.

Wraps ``RPi.GPIO`` to provide a simplified interface for pin setup, digital
read/write, cleanup, and I2C multiplexer channel selection. Tracks pin modes
internally so that pins are auto-configured on first use.
"""

import time

import RPi.GPIO as GPIO

from utils.oizom_logger import OizomLogger

# -----------------------------------------------------------------------------
# Configure logging
# -----------------------------------------------------------------------------
basic_logger = OizomLogger(__name__).get()
context_logger = OizomLogger(__name__)

# Constants
IN: str = "in"
OUT: str = "out"
LOW: int = 0
HIGH: int = 1

# Set up default mode
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)

# Track mode per pin
_pin_modes = {}


[docs] def setup(pin: int, mode: str, pullup: bool | None = None, initial: bool = False) -> None: """Configure a GPIO pin as input or output. Args: pin: BCM GPIO pin number. mode: ``"in"`` for input or ``"out"`` for output. pullup: If not None, enable pull-up (True) or pull-down (False) for inputs. initial: Initial output level when mode is ``"out"`` (True = HIGH). """ try: if mode == IN: if pullup is not None: pud = GPIO.PUD_UP if pullup else GPIO.PUD_DOWN GPIO.setup(pin, GPIO.IN, pull_up_down=pud) else: GPIO.setup(pin, GPIO.IN) elif mode == OUT: GPIO.setup(pin, GPIO.OUT, initial=GPIO.HIGH if initial else GPIO.LOW) else: msg = f"Invalid mode: {mode}" raise ValueError(msg) _pin_modes[pin] = mode except Exception as e: context_logger.error_with_context("GPIO", f"Failed to setup pin {pin} as {mode}: {e}")
[docs] def set(pin: int, value: bool) -> None: """Set a GPIO pin HIGH or LOW, auto-configuring as output if needed. Args: pin: BCM GPIO pin number. value: True for HIGH, False for LOW. """ try: if _pin_modes.get(pin) != OUT: setup(pin, OUT) GPIO.output(pin, GPIO.HIGH if value else GPIO.LOW) except Exception as e: context_logger.error_with_context("GPIO", f"Failed to set pin {pin} to {value}: {e}")
[docs] def read(pin: int) -> bool: """Read the logic level of a GPIO pin, auto-configuring as input if needed. Args: pin: BCM GPIO pin number. Returns: bool: True if HIGH, False if LOW. """ try: if _pin_modes.get(pin) != IN: setup(pin, IN) return GPIO.input(pin) except Exception as e: context_logger.error_with_context("GPIO", f"Failed to read pin {pin}: {e}")
[docs] def input(pin: int) -> bool: """Alias for ``read()`` -- read the logic level of a GPIO pin. Args: pin: BCM GPIO pin number. Returns: bool: True if HIGH, False if LOW. """ return read(pin)
[docs] def output(pin: int, value: bool) -> None: """Alias for ``set()`` -- set a GPIO pin HIGH or LOW. Args: pin: BCM GPIO pin number. value: True for HIGH, False for LOW. """ return set(pin, value)
[docs] def mode(pin: int) -> str | None: """Return the current configured mode of a pin. Args: pin: BCM GPIO pin number. Returns: str | None: ``"in"``, ``"out"``, or None if the pin has not been set up. """ return _pin_modes.get(pin, None)
[docs] def cleanup(pin: int | None = None, assert_exists: bool = False) -> None: """Release GPIO resources for a specific pin or all pins. Args: pin: BCM GPIO pin number, or None to clean up all pins. assert_exists: Unused; kept for API compatibility. """ try: if pin is None: GPIO.cleanup() _pin_modes.clear() else: GPIO.cleanup(pin) _pin_modes.pop(pin, None) except Exception as e: context_logger.warning_with_context("GPIO", f"Failed to cleanup pin {pin}: {e}")
[docs] def setwarnings(value: bool) -> None: """Enable or disable RPi.GPIO runtime warnings. Args: value: True to enable warnings, False to suppress. """ GPIO.setwarnings(value)
[docs] def setmode(value: int) -> None: """Set the GPIO pin numbering scheme (BCM or BOARD). Args: value: ``GPIO.BCM`` or ``GPIO.BOARD``. """ GPIO.setmode(value)
[docs] def select_I2C(pn: int = 24, ch: int = -1) -> None: """Select an I2C multiplexer channel via GPIO pins 24 and 25. Maps a part-number or channel identifier to the two-bit address lines that control the Oizom I2C mux. A 100 ms settling delay is applied after switching. Args: pn: Part number or logical channel identifier. ch: Explicit channel override; if not -1, overrides ``pn``. """ try: context_logger.debug_with_context("GPIO", f"Selecting I2C {pn}") if ch != -1: pn = ch if pn in [0, 18, 23, 24, 29, 53, 54]: set(24, 0) set(25, 0) elif pn in [1, 32, 33, 34, 35, 36, 38, 39]: set(24, 1) set(25, 0) elif pn in [2, 25, 26, 27, 28]: set(24, 0) set(25, 1) elif pn in [3, 4]: set(24, 1) set(25, 1) time.sleep(0.1) except Exception as e: context_logger.error_with_context("GPIO", f"Failed to select I2C channel {pn}: {e}")