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
Support for advanced peripherals including NeoPixel LED strips, rotary encoders, keypads, and USB HID devices. Provides CircuitPython-compatible interfaces for complex input/output devices with platform-specific optimizations.
Precision-timed writing support for WS2812-style addressable RGB LED strips. Provides bit-banged timing control for accurate color data transmission.
def neopixel_write(gpio, buf: bytes) -> None:
"""
Write RGB color buffer to NeoPixel LED strip.
Args:
gpio: DigitalInOut pin object configured as output
buf: Color data buffer (3 bytes per LED: G, R, B order)
Note:
Each LED requires 3 bytes in GRB (Green, Red, Blue) order.
Buffer length must be multiple of 3.
Timing is critical - interrupts may cause color corruption.
"""Incremental encoder support for reading rotary position changes. Tracks quadrature encoder signals to provide accurate position feedback.
class IncrementalEncoder(ContextManaged):
def __init__(self, pin_a, pin_b):
"""
Initialize rotary encoder tracking.
Args:
pin_a: First encoder signal pin (A channel)
pin_b: Second encoder signal pin (B channel, 90° phase shifted)
Note:
Uses interrupt-based tracking for accurate position counting.
Encoder should have pull-up resistors on both signal lines.
"""
@property
def position(self) -> int:
"""
Current encoder position.
Returns:
int: Position count (positive for clockwise, negative for counter-clockwise)
"""
def deinit(self) -> None:
"""Release encoder resources and disable tracking"""Comprehensive key scanning support for individual keys, key matrices, and shift register-based keypads. Provides debounced input with event queuing.
class Event:
def __init__(self, key_number: int = 0, pressed: bool = True):
"""
Key transition event.
Args:
key_number: Identifier for the key (0-based index)
pressed: True for key press, False for key release
"""
@property
def key_number(self) -> int:
"""Key number that generated this event"""
@property
def pressed(self) -> bool:
"""True if key was pressed, False if released"""
@property
def released(self) -> bool:
"""True if key was released, False if pressed"""
class EventQueue:
def get(self) -> Event:
"""
Get next key event from queue.
Returns:
Event: Next key transition event, or None if queue empty
"""
def get_into(self, event: Event) -> bool:
"""
Store next event in provided Event object.
Args:
event: Event object to populate
Returns:
bool: True if event was available and stored
"""
def clear(self) -> None:
"""Clear all queued events"""
def __len__(self) -> int:
"""Number of events in queue"""
def __bool__(self) -> bool:
"""True if queue has events"""
@property
def overflowed(self) -> bool:
"""True if events were dropped due to full queue"""
class Keys(ContextManaged):
def __init__(
self,
pins,
*,
value_when_pressed: bool,
pull: bool = True,
interval: float = 0.02,
max_events: int = 64
):
"""
Individual key scanner.
Args:
pins: Sequence of pin objects for keys
value_when_pressed: True if pin reads high when pressed
pull: Enable internal pull resistors
interval: Scan interval in seconds (default 0.02 = 20ms)
max_events: Maximum events to queue
"""
@property
def events(self) -> EventQueue:
"""Event queue for key transitions (read-only)"""
@property
def key_count(self) -> int:
"""Number of keys being scanned (read-only)"""
def reset(self) -> None:
"""Reset scanner state - treats all keys as released"""
def deinit(self) -> None:
"""Stop scanning and release pins"""
class KeyMatrix(ContextManaged):
def __init__(
self,
row_pins,
column_pins,
columns_to_anodes: bool = True,
interval: float = 0.02,
max_events: int = 64
):
"""
Key matrix scanner.
Args:
row_pins: Sequence of row pin objects
column_pins: Sequence of column pin objects
columns_to_anodes: True if diode anodes connect to columns
interval: Scan interval in seconds
max_events: Maximum events to queue
Note:
Key number = row * len(column_pins) + column
"""
@property
def events(self) -> EventQueue:
"""Event queue for key transitions (read-only)"""
@property
def key_count(self) -> int:
"""Total number of keys in matrix (rows × columns)"""
def reset(self) -> None:
"""Reset scanner state"""
def deinit(self) -> None:
"""Stop scanning and release pins"""
class ShiftRegisterKeys(ContextManaged):
def __init__(
self,
*,
clock,
data,
latch,
value_to_latch: bool = True,
key_count: int,
value_when_pressed: bool,
interval: float = 0.02,
max_events: int = 64
):
"""
Shift register key scanner (74HC165, CD4021).
Args:
clock: Clock pin for shift register
data: Serial data input pin
latch: Latch pin for parallel data capture
value_to_latch: True if data latched on high, False on low
key_count: Number of keys to read
value_when_pressed: True if key reads high when pressed
interval: Scan interval in seconds
max_events: Maximum events to queue
"""
@property
def events(self) -> EventQueue:
"""Event queue for key transitions (read-only)"""
@property
def key_count(self) -> int:
"""Number of keys being scanned"""
def reset(self) -> None:
"""Reset scanner state"""
def deinit(self) -> None:
"""Stop scanning and release pins"""USB Human Interface Device emulation for creating keyboards, mice, and custom HID devices. Uses Linux USB gadget framework for device presentation.
class Device:
# Pre-defined device types
KEYBOARD: Device # Standard keyboard
MOUSE: Device # Standard mouse
CONSUMER_CONTROL: Device # Media control device
BOOT_KEYBOARD: Device # BIOS-compatible keyboard
BOOT_MOUSE: Device # BIOS-compatible mouse
def __init__(
self,
*,
descriptor: bytes,
usage_page: int,
usage: int,
report_ids: tuple[int, ...],
in_report_lengths: tuple[int, ...],
out_report_lengths: tuple[int, ...]
):
"""
Create custom HID device.
Args:
descriptor: HID report descriptor bytes
usage_page: HID usage page
usage: HID usage within page
report_ids: Tuple of report ID numbers
in_report_lengths: Input report sizes for each report ID
out_report_lengths: Output report sizes for each report ID
"""
def send_report(self, report: bytes, report_id: int = None) -> None:
"""
Send HID input report to host.
Args:
report: Report data to send
report_id: Report ID (optional if device has single report ID)
"""
def get_last_received_report(self, report_id: int = None) -> bytes:
"""
Get last received HID output report.
Args:
report_id: Report ID to check (optional)
Returns:
bytes: Last received report data, or None if none received
"""
def enable(devices: tuple[Device, ...], boot_device: int = 0) -> None:
"""
Enable USB HID with specified devices.
Args:
devices: Tuple of Device objects to enable
boot_device: Boot device type (0=none, 1=keyboard, 2=mouse)
Note:
Must be called before USB connection. Requires dwc2 and libcomposite
kernel modules. Creates /dev/hidg* device files for communication.
"""
def disable() -> None:
"""Disable all USB HID devices"""import board
import digitalio
import neopixel_write
import time
# Setup NeoPixel data pin
pixel_pin = digitalio.DigitalInOut(board.D18)
pixel_pin.direction = digitalio.Direction.OUTPUT
# Number of LEDs
num_pixels = 10
def set_pixel_color(index, red, green, blue, buf):
"""Set color for single pixel in buffer (GRB order)"""
offset = index * 3
buf[offset] = green # Green first
buf[offset + 1] = red # Red second
buf[offset + 2] = blue # Blue third
# Create color buffer (3 bytes per pixel: GRB)
pixel_buffer = bytearray(num_pixels * 3)
# Rainbow effect
for cycle in range(100):
for i in range(num_pixels):
# Calculate rainbow colors
hue = (i * 256 // num_pixels + cycle * 5) % 256
# Simple HSV to RGB conversion
if hue < 85:
red, green, blue = hue * 3, 255 - hue * 3, 0
elif hue < 170:
hue -= 85
red, green, blue = 255 - hue * 3, 0, hue * 3
else:
hue -= 170
red, green, blue = 0, hue * 3, 255 - hue * 3
set_pixel_color(i, red, green, blue, pixel_buffer)
# Write to NeoPixel strip
neopixel_write.neopixel_write(pixel_pin, pixel_buffer)
time.sleep(0.05)
# Turn off all pixels
pixel_buffer = bytearray(num_pixels * 3) # All zeros
neopixel_write.neopixel_write(pixel_pin, pixel_buffer)
pixel_pin.deinit()import board
import rotaryio
import time
# Initialize rotary encoder
encoder = rotaryio.IncrementalEncoder(board.D2, board.D3)
# Track position
last_position = 0
print("Rotate encoder... Press Ctrl+C to stop")
try:
while True:
position = encoder.position
if position != last_position:
direction = "clockwise" if position > last_position else "counter-clockwise"
print(f"Position: {position} ({direction})")
last_position = position
time.sleep(0.01)
except KeyboardInterrupt:
print("Stopping encoder reading")
encoder.deinit()import board
import keypad
import time
# Setup keys on pins D2, D3, D4
keys = keypad.Keys([board.D2, board.D3, board.D4],
value_when_pressed=False, # Grounded when pressed
pull=True) # Enable pull-ups
print("Press keys... Press Ctrl+C to stop")
try:
while True:
event = keys.events.get()
if event:
if event.pressed:
print(f"Key {event.key_number} pressed")
else:
print(f"Key {event.key_number} released")
time.sleep(0.01)
except KeyboardInterrupt:
print("Stopping key scanning")
keys.deinit()import board
import keypad
import time
# 3x3 key matrix
row_pins = [board.D18, board.D19, board.D20]
col_pins = [board.D21, board.D22, board.D23]
matrix = keypad.KeyMatrix(row_pins, col_pins)
print(f"Scanning {matrix.key_count} keys in 3x3 matrix")
print("Press keys... Press Ctrl+C to stop")
try:
while True:
event = matrix.events.get()
if event:
row = event.key_number // len(col_pins)
col = event.key_number % len(col_pins)
action = "pressed" if event.pressed else "released"
print(f"Key at row {row}, col {col} (#{event.key_number}) {action}")
time.sleep(0.01)
except KeyboardInterrupt:
print("Stopping matrix scanning")
matrix.deinit()import board
import keypad
import time
# 74HC165 shift register setup
shift_keys = keypad.ShiftRegisterKeys(
clock=board.D18, # Clock pin
data=board.D19, # Serial data in
latch=board.D20, # Parallel load
key_count=8, # 8 keys
value_when_pressed=False, # Active low
value_to_latch=False # Latch on low (74HC165 style)
)
print("Scanning 8 keys via shift register")
print("Press keys... Press Ctrl+C to stop")
try:
while True:
event = shift_keys.events.get()
if event:
action = "pressed" if event.pressed else "released"
print(f"Shift register key {event.key_number} {action}")
time.sleep(0.01)
except KeyboardInterrupt:
print("Stopping shift register scanning")
shift_keys.deinit()Linux-based USB Human Interface Device (HID) support for creating keyboard, mouse, and custom HID devices using the USB Gadget framework.
class Device:
"""USB HID device specification"""
def __init__(
self,
*,
descriptor: bytes,
usage_page: int,
usage: int,
report_ids: Sequence[int],
in_report_lengths: Sequence[int],
out_report_lengths: Sequence[int]
):
"""
Create a custom HID device.
Args:
descriptor: HID report descriptor bytes
usage_page: HID usage page (e.g., 0x01 for Generic Desktop)
usage: HID usage ID (e.g., 0x06 for Keyboard)
report_ids: List of report IDs this device uses
in_report_lengths: List of input report lengths (device to host)
out_report_lengths: List of output report lengths (host to device)
"""
def send_report(self, report: bytearray, report_id: int = None) -> None:
"""
Send HID report to host.
Args:
report: Report data to send
report_id: Report ID (optional if device has only one report ID)
"""
def get_last_received_report(self, report_id: int = None) -> bytes:
"""
Get last received HID OUT or feature report.
Args:
report_id: Report ID to get (optional if device has only one)
Returns:
Last received report data, or None if nothing received
"""
@property
def last_received_report(self) -> bytes:
"""
Last received HID OUT report (deprecated, use get_last_received_report()).
Returns:
Last received report data, or None if nothing received
"""
# Pre-defined device constants
Device.KEYBOARD: Device # Standard USB keyboard
Device.MOUSE: Device # Standard USB mouse
def enable(devices: Sequence[Device]) -> None:
"""
Enable USB HID with specified devices.
Args:
devices: Tuple of Device objects to enable
Raises:
Exception: If required kernel modules not loaded or setup fails
Note:
Must be called before USB enumeration.
Requires root privileges and dwc2/libcomposite kernel modules.
"""
def disable() -> None:
"""Disable USB HID gadget"""# Note: Requires Linux with dwc2 and libcomposite kernel modules
# Must be run as root and called before USB enumeration
import usb_hid
import time
# Enable USB HID keyboard
usb_hid.enable((usb_hid.Device.KEYBOARD,))
# Get keyboard device
keyboard = usb_hid.Device.KEYBOARD
# Keyboard usage codes (simplified)
KEY_A = 0x04
KEY_ENTER = 0x28
KEY_SPACE = 0x2C
def send_key(key_code, modifier=0):
"""Send a key press and release"""
# HID keyboard report: [modifier, reserved, key1, key2, key3, key4, key5, key6]
report = bytearray([modifier, 0, key_code, 0, 0, 0, 0, 0])
# Send key press
keyboard.send_report(report)
time.sleep(0.01)
# Send key release
report = bytearray([0, 0, 0, 0, 0, 0, 0, 0])
keyboard.send_report(report)
time.sleep(0.01)
# Type "Hello World"
print("Typing 'Hello World' via USB HID...")
# Type characters (this is simplified - real implementation needs full keycode mapping)
send_key(KEY_A) # This would need proper character-to-keycode mapping
time.sleep(0.5)
# Send Enter
send_key(KEY_ENTER)
print("USB HID keyboard demo complete")import usb_hid
import time
# Enable USB HID mouse
usb_hid.enable((usb_hid.Device.MOUSE,))
# Get mouse device
mouse = usb_hid.Device.MOUSE
def move_mouse(x, y, buttons=0, wheel=0):
"""Move mouse and/or click buttons"""
# HID mouse report: [buttons, x, y, wheel]
# Limit movement to signed 8-bit range (-127 to 127)
x = max(-127, min(127, x))
y = max(-127, min(127, y))
wheel = max(-127, min(127, wheel))
report = bytearray([buttons, x, y, wheel])
mouse.send_report(report)
# Mouse button constants
LEFT_BUTTON = 0x01
RIGHT_BUTTON = 0x02
MIDDLE_BUTTON = 0x04
print("USB HID mouse demo...")
# Move in square pattern
movements = [
(50, 0), # Right
(0, 50), # Down
(-50, 0), # Left
(0, -50), # Up
]
for dx, dy in movements:
move_mouse(dx, dy)
time.sleep(0.5)
# Click left button
move_mouse(0, 0, LEFT_BUTTON)
time.sleep(0.1)
move_mouse(0, 0, 0) # Release
print("USB HID mouse demo complete")Supported Platforms:
Timing Requirements:
Supported Platforms:
Implementation Notes:
Debouncing:
Threading:
Linux Requirements:
Device Limitations:
# NeoPixel error handling
try:
import neopixel_write
# Use NeoPixel functionality
except ImportError:
print("NeoPixel not supported on this platform")
# Rotary encoder error handling
try:
import rotaryio
encoder = rotaryio.IncrementalEncoder(board.D2, board.D3)
except RuntimeError as e:
print(f"Encoder not supported: {e}")
# USB HID error handling
try:
import usb_hid
usb_hid.enable((usb_hid.Device.KEYBOARD,))
except Exception as e:
if "dwc2" in str(e):
print("USB HID requires dwc2 kernel module")
elif "libcomposite" in str(e):
print("USB HID requires libcomposite kernel module")
else:
print(f"USB HID error: {e}")Install with Tessl CLI
npx tessl i tessl/pypi-adafruit-blinka