CircuitPython APIs for non-CircuitPython versions of Python such as CPython on Linux and MicroPython.
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Base classes and utilities that provide resource management, platform detection, and CircuitPython compatibility features. Forms the foundation for all hardware interface classes with automatic cleanup, resource locking, and cross-platform abstractions.
Fundamental base classes that provide automatic resource cleanup and exclusive resource access patterns used throughout the Blinka ecosystem.
class ContextManaged:
def __enter__(self):
"""Context manager entry - returns self"""
def __exit__(self, exc_type, exc_val, exc_tb):
"""Context manager exit - automatically calls deinit()"""
def deinit(self) -> None:
"""
Free any hardware resources used by the object.
Note:
Override this method in subclasses to implement
specific resource cleanup logic.
"""
class Lockable(ContextManaged):
def try_lock(self) -> bool:
"""
Attempt to acquire exclusive lock on resource.
Returns:
bool: True if lock acquired, False if already locked
Note:
Must call try_lock() before using shared resources like SPI/I2C.
Always pair with unlock() or use context manager for safety.
"""
def unlock(self) -> None:
"""
Release exclusive lock on resource.
Note:
Only call if try_lock() returned True.
Context manager automatically handles unlock.
"""Enum class that provides CircuitPython-style static symbols and introspection capabilities for constants and configuration values.
class Enum:
def __repr__(self) -> str:
"""
Return dot-subscripted path to enum instance.
Returns:
str: Module path to enum value (e.g., "digitalio.Direction.OUTPUT")
"""
@classmethod
def iteritems(cls):
"""
Iterate over class attributes that are instances of this enum.
Yields:
tuple: (key, value) pairs for enum constants
Example:
for name, value in Direction.iteritems():
print(f"{name}: {value}")
"""Utilities for loading configuration settings and applying platform-specific patches to ensure compatibility across diverse environments.
def load_settings_toml() -> dict:
"""
Load values from settings.toml into environment variables.
Returns:
dict: Parsed TOML settings
Raises:
FileNotFoundError: If settings.toml not found in current directory
TOMLDecodeError: If settings.toml has invalid syntax
ValueError: If settings contain unsupported data types
Note:
Only supports bool, int, float, and str values.
Settings are added to os.environ for later access via os.getenv().
Existing environment variables are not overwritten.
"""
def patch_system() -> None:
"""
Apply platform-specific patches to system modules.
Note:
Patches the time module to ensure consistent behavior
across CPython, MicroPython, and CircuitPython platforms.
Called automatically during Blinka initialization.
"""Pin class that provides a common interface for hardware pin references across all supported platforms and microcontrollers.
class Pin:
"""
Hardware pin reference object.
Note:
Pin objects are typically accessed through board module constants
(e.g., board.D18) rather than created directly.
Provides platform-agnostic pin identification.
"""
id: int # Platform-specific pin identifierRuntime platform detection system that identifies hardware capabilities and loads appropriate drivers automatically.
# Platform detection globals (read-only)
detector: object # Platform detection instance from adafruit-platformdetect
chip_id: str # Detected microcontroller/SoC identifier
board_id: str # Detected development board identifier
implementation: str # Python implementation name ("cpython", "micropython", etc.)
# Platform-appropriate sleep function
sleep: function # Time.sleep or utime.sleep depending on platformMicrosecond-precision delay function for applications requiring accurate timing control.
def delay_us(delay: int) -> None:
"""
Sleep for the specified number of microseconds.
Args:
delay: Delay duration in microseconds
Note:
Provides microsecond precision timing for bit-banging protocols
and other time-critical operations. Actual precision depends on
platform capabilities and system load.
"""import digitalio
import board
import time
# Using context manager for automatic cleanup
with digitalio.DigitalInOut(board.D18) as led:
led.direction = digitalio.Direction.OUTPUT
# LED will be automatically cleaned up when exiting context
for i in range(10):
led.value = True
time.sleep(0.5)
led.value = False
time.sleep(0.5)
# LED pin is automatically released here
print("LED cleanup completed automatically")import digitalio
import board
# Manual resource management
led = digitalio.DigitalInOut(board.D18)
led.direction = digitalio.Direction.OUTPUT
try:
# Use the LED
led.value = True
# ... do work ...
finally:
# Always clean up resources
led.deinit()import busio
import board
import time
# SPI bus requires locking for thread safety
spi = busio.SPI(board.SCK, board.MOSI, board.MISO)
# Method 1: Manual locking
if spi.try_lock():
try:
spi.configure(baudrate=1000000)
spi.write(b'\x01\x02\x03')
finally:
spi.unlock()
else:
print("Could not acquire SPI lock")
# Method 2: Context manager (automatic locking)
with spi:
spi.configure(baudrate=500000)
spi.write(b'\x04\x05\x06')
spi.deinit()from adafruit_blinka import Enum
class MyConstants(Enum):
"""Custom enum for application constants"""
pass
# Define enum values
MyConstants.OPTION_A = MyConstants()
MyConstants.OPTION_B = MyConstants()
MyConstants.OPTION_C = MyConstants()
# Use enum values
current_mode = MyConstants.OPTION_A
print(f"Current mode: {current_mode}") # Shows full path
# Iterate over enum values
for name, value in MyConstants.iteritems():
print(f"Available option: {name}")# settings.toml file:
# wifi_ssid = "MyNetwork"
# wifi_password = "secret123"
# debug_enabled = true
# max_retries = 5
# timeout = 30.5
from adafruit_blinka import load_settings_toml
import os
try:
settings = load_settings_toml()
print("Loaded settings:", settings)
# Access via environment variables
wifi_ssid = os.getenv("wifi_ssid", "default_network")
debug_mode = os.getenv("debug_enabled", "false").lower() == "true"
max_retries = int(os.getenv("max_retries", "3"))
print(f"WiFi SSID: {wifi_ssid}")
print(f"Debug mode: {debug_mode}")
print(f"Max retries: {max_retries}")
except FileNotFoundError:
print("No settings.toml found, using defaults")
except ValueError as e:
print(f"Invalid settings format: {e}")from adafruit_blinka.agnostic import detector, chip_id, board_id, implementation
# Check what platform we're running on
print(f"Board: {board_id}")
print(f"Chip: {chip_id}")
print(f"Python implementation: {implementation}")
# Platform-specific feature detection
if detector.board.any_raspberry_pi:
print("Running on Raspberry Pi")
if detector.board.any_raspberry_pi_5_board:
print("Pi 5 detected - using lgpio")
else:
print("Pi 4 or earlier - using RPi.GPIO")
elif detector.board.any_jetson_board:
print("Running on NVIDIA Jetson")
elif detector.board.ftdi_ft232h:
print("Using FTDI FT232H USB adapter")
# Check for specific chip families
if chip_id.startswith("BCM"):
print("Broadcom chip detected")
elif chip_id.startswith("RK"):
print("Rockchip SoC detected")
elif chip_id == "RP2040":
print("Raspberry Pi Pico RP2040 detected")
# Adapt behavior based on platform
if detector.board.any_embedded_linux:
print("Full Linux system - all features available")
else:
print("Microcontroller or adapter - limited features")import microcontroller
import board
import digitalio
# Bit-banging a custom protocol with precise timing
data_pin = digitalio.DigitalInOut(board.D18)
data_pin.direction = digitalio.Direction.OUTPUT
def send_bit(bit_value):
"""Send a single bit with precise timing"""
if bit_value:
# Send '1' bit: 800ns high, 450ns low
data_pin.value = True
microcontroller.delay_us(1) # ~800ns (rounded to 1μs)
data_pin.value = False
microcontroller.delay_us(1) # ~450ns (rounded to 1μs)
else:
# Send '0' bit: 400ns high, 850ns low
data_pin.value = True
microcontroller.delay_us(1) # ~400ns (rounded to 1μs)
data_pin.value = False
microcontroller.delay_us(1) # ~850ns (rounded to 1μs)
def send_byte(byte_value):
"""Send 8 bits with precise timing"""
for bit in range(8):
bit_value = (byte_value >> (7 - bit)) & 1
send_bit(bit_value)
# Send custom protocol data
try:
for data in [0xFF, 0x00, 0xAA, 0x55]:
send_byte(data)
microcontroller.delay_us(10) # Inter-byte delay
finally:
data_pin.deinit()from adafruit_blinka import ContextManaged
import board
import digitalio
import time
class CustomSensor(ContextManaged):
"""Example custom hardware class using Blinka patterns"""
def __init__(self, data_pin, clock_pin):
self._data = digitalio.DigitalInOut(data_pin)
self._data.direction = digitalio.Direction.INPUT
self._clock = digitalio.DigitalInOut(clock_pin)
self._clock.direction = digitalio.Direction.OUTPUT
self._clock.value = False
def read_value(self):
"""Read sensor value using custom protocol"""
# Generate clock pulses and read data
value = 0
for bit in range(8):
self._clock.value = True
time.sleep(0.001) # 1ms clock high
if self._data.value:
value |= (1 << (7 - bit))
self._clock.value = False
time.sleep(0.001) # 1ms clock low
return value
def deinit(self):
"""Clean up hardware resources"""
if hasattr(self, '_data'):
self._data.deinit()
if hasattr(self, '_clock'):
self._clock.deinit()
# Use custom sensor class
with CustomSensor(board.D2, board.D3) as sensor:
for _ in range(10):
value = sensor.read_value()
print(f"Sensor reading: {value}")
time.sleep(1)
# Automatic cleanup when exiting contextRequired For:
Not Required For:
Automatic Detection Works For:
Manual Configuration May Be Needed For:
from adafruit_blinka import ContextManaged, load_settings_toml
import board
# Handle platform compatibility issues
try:
import pwmio
pwm = pwmio.PWMOut(board.D18)
print("PWM available")
pwm.deinit()
except (RuntimeError, AttributeError) as e:
print(f"PWM not available: {e}")
# Handle configuration loading errors
try:
settings = load_settings_toml()
except FileNotFoundError:
print("Using default configuration")
settings = {}
except ValueError as e:
print(f"Configuration error: {e}")
settings = {}
# Handle resource contention
import busio
spi = busio.SPI(board.SCK, board.MOSI, board.MISO)
# Timeout pattern for resource acquisition
import time
timeout = 5.0 # seconds
start_time = time.time()
while not spi.try_lock():
if time.time() - start_time > timeout:
print("Could not acquire SPI lock within timeout")
break
time.sleep(0.01)
else:
try:
# Use SPI bus
spi.configure(baudrate=1000000)
spi.write(b'\x01\x02\x03')
finally:
spi.unlock()
spi.deinit()Install with Tessl CLI
npx tessl i tessl/pypi-adafruit-blinka