0
# Core Framework
1
2
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.
3
4
## Capabilities
5
6
### Context Management Base Classes
7
8
Fundamental base classes that provide automatic resource cleanup and exclusive resource access patterns used throughout the Blinka ecosystem.
9
10
```python { .api }
11
class ContextManaged:
12
def __enter__(self):
13
"""Context manager entry - returns self"""
14
15
def __exit__(self, exc_type, exc_val, exc_tb):
16
"""Context manager exit - automatically calls deinit()"""
17
18
def deinit(self) -> None:
19
"""
20
Free any hardware resources used by the object.
21
22
Note:
23
Override this method in subclasses to implement
24
specific resource cleanup logic.
25
"""
26
27
class Lockable(ContextManaged):
28
def try_lock(self) -> bool:
29
"""
30
Attempt to acquire exclusive lock on resource.
31
32
Returns:
33
bool: True if lock acquired, False if already locked
34
35
Note:
36
Must call try_lock() before using shared resources like SPI/I2C.
37
Always pair with unlock() or use context manager for safety.
38
"""
39
40
def unlock(self) -> None:
41
"""
42
Release exclusive lock on resource.
43
44
Note:
45
Only call if try_lock() returned True.
46
Context manager automatically handles unlock.
47
"""
48
```
49
50
### CircuitPython Compatibility
51
52
Enum class that provides CircuitPython-style static symbols and introspection capabilities for constants and configuration values.
53
54
```python { .api }
55
class Enum:
56
def __repr__(self) -> str:
57
"""
58
Return dot-subscripted path to enum instance.
59
60
Returns:
61
str: Module path to enum value (e.g., "digitalio.Direction.OUTPUT")
62
"""
63
64
@classmethod
65
def iteritems(cls):
66
"""
67
Iterate over class attributes that are instances of this enum.
68
69
Yields:
70
tuple: (key, value) pairs for enum constants
71
72
Example:
73
for name, value in Direction.iteritems():
74
print(f"{name}: {value}")
75
"""
76
```
77
78
### Configuration Management
79
80
Utilities for loading configuration settings and applying platform-specific patches to ensure compatibility across diverse environments.
81
82
```python { .api }
83
def load_settings_toml() -> dict:
84
"""
85
Load values from settings.toml into environment variables.
86
87
Returns:
88
dict: Parsed TOML settings
89
90
Raises:
91
FileNotFoundError: If settings.toml not found in current directory
92
TOMLDecodeError: If settings.toml has invalid syntax
93
ValueError: If settings contain unsupported data types
94
95
Note:
96
Only supports bool, int, float, and str values.
97
Settings are added to os.environ for later access via os.getenv().
98
Existing environment variables are not overwritten.
99
"""
100
101
def patch_system() -> None:
102
"""
103
Apply platform-specific patches to system modules.
104
105
Note:
106
Patches the time module to ensure consistent behavior
107
across CPython, MicroPython, and CircuitPython platforms.
108
Called automatically during Blinka initialization.
109
"""
110
```
111
112
### Hardware Pin Abstraction
113
114
Pin class that provides a common interface for hardware pin references across all supported platforms and microcontrollers.
115
116
```python { .api }
117
class Pin:
118
"""
119
Hardware pin reference object.
120
121
Note:
122
Pin objects are typically accessed through board module constants
123
(e.g., board.D18) rather than created directly.
124
Provides platform-agnostic pin identification.
125
"""
126
id: int # Platform-specific pin identifier
127
```
128
129
### Platform Detection
130
131
Runtime platform detection system that identifies hardware capabilities and loads appropriate drivers automatically.
132
133
```python { .api }
134
# Platform detection globals (read-only)
135
detector: object # Platform detection instance from adafruit-platformdetect
136
chip_id: str # Detected microcontroller/SoC identifier
137
board_id: str # Detected development board identifier
138
implementation: str # Python implementation name ("cpython", "micropython", etc.)
139
140
# Platform-appropriate sleep function
141
sleep: function # Time.sleep or utime.sleep depending on platform
142
```
143
144
### Precision Timing
145
146
Microsecond-precision delay function for applications requiring accurate timing control.
147
148
```python { .api }
149
def delay_us(delay: int) -> None:
150
"""
151
Sleep for the specified number of microseconds.
152
153
Args:
154
delay: Delay duration in microseconds
155
156
Note:
157
Provides microsecond precision timing for bit-banging protocols
158
and other time-critical operations. Actual precision depends on
159
platform capabilities and system load.
160
"""
161
```
162
163
## Usage Examples
164
165
### Context Manager Pattern
166
167
```python
168
import digitalio
169
import board
170
import time
171
172
# Using context manager for automatic cleanup
173
with digitalio.DigitalInOut(board.D18) as led:
174
led.direction = digitalio.Direction.OUTPUT
175
176
# LED will be automatically cleaned up when exiting context
177
for i in range(10):
178
led.value = True
179
time.sleep(0.5)
180
led.value = False
181
time.sleep(0.5)
182
183
# LED pin is automatically released here
184
print("LED cleanup completed automatically")
185
```
186
187
### Manual Resource Management
188
189
```python
190
import digitalio
191
import board
192
193
# Manual resource management
194
led = digitalio.DigitalInOut(board.D18)
195
led.direction = digitalio.Direction.OUTPUT
196
197
try:
198
# Use the LED
199
led.value = True
200
# ... do work ...
201
202
finally:
203
# Always clean up resources
204
led.deinit()
205
```
206
207
### Resource Locking for Shared Buses
208
209
```python
210
import busio
211
import board
212
import time
213
214
# SPI bus requires locking for thread safety
215
spi = busio.SPI(board.SCK, board.MOSI, board.MISO)
216
217
# Method 1: Manual locking
218
if spi.try_lock():
219
try:
220
spi.configure(baudrate=1000000)
221
spi.write(b'\x01\x02\x03')
222
finally:
223
spi.unlock()
224
else:
225
print("Could not acquire SPI lock")
226
227
# Method 2: Context manager (automatic locking)
228
with spi:
229
spi.configure(baudrate=500000)
230
spi.write(b'\x04\x05\x06')
231
232
spi.deinit()
233
```
234
235
### Custom Enum Definition
236
237
```python
238
from adafruit_blinka import Enum
239
240
class MyConstants(Enum):
241
"""Custom enum for application constants"""
242
pass
243
244
# Define enum values
245
MyConstants.OPTION_A = MyConstants()
246
MyConstants.OPTION_B = MyConstants()
247
MyConstants.OPTION_C = MyConstants()
248
249
# Use enum values
250
current_mode = MyConstants.OPTION_A
251
print(f"Current mode: {current_mode}") # Shows full path
252
253
# Iterate over enum values
254
for name, value in MyConstants.iteritems():
255
print(f"Available option: {name}")
256
```
257
258
### Settings Configuration
259
260
```python
261
# settings.toml file:
262
# wifi_ssid = "MyNetwork"
263
# wifi_password = "secret123"
264
# debug_enabled = true
265
# max_retries = 5
266
# timeout = 30.5
267
268
from adafruit_blinka import load_settings_toml
269
import os
270
271
try:
272
settings = load_settings_toml()
273
print("Loaded settings:", settings)
274
275
# Access via environment variables
276
wifi_ssid = os.getenv("wifi_ssid", "default_network")
277
debug_mode = os.getenv("debug_enabled", "false").lower() == "true"
278
max_retries = int(os.getenv("max_retries", "3"))
279
280
print(f"WiFi SSID: {wifi_ssid}")
281
print(f"Debug mode: {debug_mode}")
282
print(f"Max retries: {max_retries}")
283
284
except FileNotFoundError:
285
print("No settings.toml found, using defaults")
286
except ValueError as e:
287
print(f"Invalid settings format: {e}")
288
```
289
290
### Platform Detection
291
292
```python
293
from adafruit_blinka.agnostic import detector, chip_id, board_id, implementation
294
295
# Check what platform we're running on
296
print(f"Board: {board_id}")
297
print(f"Chip: {chip_id}")
298
print(f"Python implementation: {implementation}")
299
300
# Platform-specific feature detection
301
if detector.board.any_raspberry_pi:
302
print("Running on Raspberry Pi")
303
if detector.board.any_raspberry_pi_5_board:
304
print("Pi 5 detected - using lgpio")
305
else:
306
print("Pi 4 or earlier - using RPi.GPIO")
307
308
elif detector.board.any_jetson_board:
309
print("Running on NVIDIA Jetson")
310
311
elif detector.board.ftdi_ft232h:
312
print("Using FTDI FT232H USB adapter")
313
314
# Check for specific chip families
315
if chip_id.startswith("BCM"):
316
print("Broadcom chip detected")
317
elif chip_id.startswith("RK"):
318
print("Rockchip SoC detected")
319
elif chip_id == "RP2040":
320
print("Raspberry Pi Pico RP2040 detected")
321
322
# Adapt behavior based on platform
323
if detector.board.any_embedded_linux:
324
print("Full Linux system - all features available")
325
else:
326
print("Microcontroller or adapter - limited features")
327
```
328
329
### Precision Timing
330
331
```python
332
import microcontroller
333
import board
334
import digitalio
335
336
# Bit-banging a custom protocol with precise timing
337
data_pin = digitalio.DigitalInOut(board.D18)
338
data_pin.direction = digitalio.Direction.OUTPUT
339
340
def send_bit(bit_value):
341
"""Send a single bit with precise timing"""
342
if bit_value:
343
# Send '1' bit: 800ns high, 450ns low
344
data_pin.value = True
345
microcontroller.delay_us(1) # ~800ns (rounded to 1μs)
346
data_pin.value = False
347
microcontroller.delay_us(1) # ~450ns (rounded to 1μs)
348
else:
349
# Send '0' bit: 400ns high, 850ns low
350
data_pin.value = True
351
microcontroller.delay_us(1) # ~400ns (rounded to 1μs)
352
data_pin.value = False
353
microcontroller.delay_us(1) # ~850ns (rounded to 1μs)
354
355
def send_byte(byte_value):
356
"""Send 8 bits with precise timing"""
357
for bit in range(8):
358
bit_value = (byte_value >> (7 - bit)) & 1
359
send_bit(bit_value)
360
361
# Send custom protocol data
362
try:
363
for data in [0xFF, 0x00, 0xAA, 0x55]:
364
send_byte(data)
365
microcontroller.delay_us(10) # Inter-byte delay
366
367
finally:
368
data_pin.deinit()
369
```
370
371
### Custom Hardware Class
372
373
```python
374
from adafruit_blinka import ContextManaged
375
import board
376
import digitalio
377
import time
378
379
class CustomSensor(ContextManaged):
380
"""Example custom hardware class using Blinka patterns"""
381
382
def __init__(self, data_pin, clock_pin):
383
self._data = digitalio.DigitalInOut(data_pin)
384
self._data.direction = digitalio.Direction.INPUT
385
386
self._clock = digitalio.DigitalInOut(clock_pin)
387
self._clock.direction = digitalio.Direction.OUTPUT
388
self._clock.value = False
389
390
def read_value(self):
391
"""Read sensor value using custom protocol"""
392
# Generate clock pulses and read data
393
value = 0
394
for bit in range(8):
395
self._clock.value = True
396
time.sleep(0.001) # 1ms clock high
397
398
if self._data.value:
399
value |= (1 << (7 - bit))
400
401
self._clock.value = False
402
time.sleep(0.001) # 1ms clock low
403
404
return value
405
406
def deinit(self):
407
"""Clean up hardware resources"""
408
if hasattr(self, '_data'):
409
self._data.deinit()
410
if hasattr(self, '_clock'):
411
self._clock.deinit()
412
413
# Use custom sensor class
414
with CustomSensor(board.D2, board.D3) as sensor:
415
for _ in range(10):
416
value = sensor.read_value()
417
print(f"Sensor reading: {value}")
418
time.sleep(1)
419
420
# Automatic cleanup when exiting context
421
```
422
423
## Platform Considerations
424
425
### Context Manager Benefits
426
427
- **Automatic Cleanup**: Resources always released, even on exceptions
428
- **Thread Safety**: Proper cleanup in multi-threaded applications
429
- **Memory Management**: Prevents resource leaks in long-running programs
430
- **Best Practice**: Follows Python conventions and CircuitPython patterns
431
432
### Resource Locking
433
434
**Required For:**
435
- SPI buses (shared among multiple devices)
436
- I2C buses (shared among multiple devices)
437
- Any hardware resource accessed by multiple threads
438
439
**Not Required For:**
440
- GPIO pins (exclusive to single DigitalInOut instance)
441
- PWM outputs (exclusive to single PWMOut instance)
442
- UART ports (typically exclusive to single UART instance)
443
444
### Platform Detection Reliability
445
446
**Automatic Detection Works For:**
447
- Standard development boards with known signatures
448
- Well-established SoC families with device tree information
449
- USB adapters with unique vendor/product IDs
450
451
**Manual Configuration May Be Needed For:**
452
- Custom boards without standard signatures
453
- Generic x86 systems without GPIO hardware
454
- Embedded systems with non-standard configurations
455
456
### Error Handling
457
458
```python
459
from adafruit_blinka import ContextManaged, load_settings_toml
460
import board
461
462
# Handle platform compatibility issues
463
try:
464
import pwmio
465
pwm = pwmio.PWMOut(board.D18)
466
print("PWM available")
467
pwm.deinit()
468
except (RuntimeError, AttributeError) as e:
469
print(f"PWM not available: {e}")
470
471
# Handle configuration loading errors
472
try:
473
settings = load_settings_toml()
474
except FileNotFoundError:
475
print("Using default configuration")
476
settings = {}
477
except ValueError as e:
478
print(f"Configuration error: {e}")
479
settings = {}
480
481
# Handle resource contention
482
import busio
483
484
spi = busio.SPI(board.SCK, board.MOSI, board.MISO)
485
486
# Timeout pattern for resource acquisition
487
import time
488
timeout = 5.0 # seconds
489
start_time = time.time()
490
491
while not spi.try_lock():
492
if time.time() - start_time > timeout:
493
print("Could not acquire SPI lock within timeout")
494
break
495
time.sleep(0.01)
496
else:
497
try:
498
# Use SPI bus
499
spi.configure(baudrate=1000000)
500
spi.write(b'\x01\x02\x03')
501
finally:
502
spi.unlock()
503
504
spi.deinit()
505
```