espzero 0.0.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
espzero-0.0.1/PKG-INFO ADDED
@@ -0,0 +1,409 @@
1
+ Metadata-Version: 2.4
2
+ Name: espzero
3
+ Version: 0.0.1
4
+ Summary: Educational tool for physical computing and ESP32/ESP8266 development.
5
+ Author-email: Neo Neopia-uz <neopia.uz@gmail.com>
6
+ Project-URL: Homepage, https://github.com/roboticsware/espzero
7
+ Project-URL: Bug Tracker, https://github.com/roboticsware/espzero/issues
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Operating System :: OS Independent
11
+ Requires-Python: >=3.7
12
+ Description-Content-Type: text/markdown
13
+
14
+ # espzero
15
+
16
+ > A single MicroPython library that ports [picozero](https://github.com/raspberrypilearning/picozero) to the ESP32 family (WROOM, S3, C3, and more).
17
+ > **"One codebase, many boards"** — hardware differences are absorbed by the Board Profile system.
18
+
19
+ ---
20
+
21
+ ## Installation
22
+
23
+ Copy the `espzero/` directory to the root of your ESP32 filesystem using Mu Editor, Thonny, or `mpremote`:
24
+
25
+ ```
26
+ mpremote cp -r espzero/ :espzero/
27
+ ```
28
+
29
+ ---
30
+
31
+ ## Quick Start
32
+
33
+ ```python
34
+ import espzero
35
+ espzero.begin() # initialise: auto-detect board
36
+
37
+ # Built-in LED — available after begin()
38
+ from espzero import esp_led
39
+ from time import sleep
40
+
41
+ while True:
42
+ esp_led.on()
43
+ sleep(1)
44
+ esp_led.off()
45
+ sleep(1)
46
+ ```
47
+
48
+ Specify a board explicitly instead of auto-detection:
49
+
50
+ ```python
51
+ import espzero
52
+ espzero.begin("esp32_38pin_nodemcu") # or "esp32_devkit_v1", "esp8266_lolin_v3", ...
53
+ ```
54
+
55
+ Use other components after `begin()`:
56
+
57
+ ```python
58
+ from espzero import LED, Button, Servo, WiFi
59
+
60
+ led = LED("internal") # built-in LED — profile maps alias to real GPIO
61
+ btn = Button(0) # GPIO 0 (BOOT button)
62
+ led.blink(on_time=0.5, n=5) # identical API to picozero
63
+
64
+ # WiFi (ESP32-specific)
65
+ wifi = WiFi()
66
+ ip = wifi.connect("MySSID", "password")
67
+ print("IP:", ip)
68
+
69
+ # Capacitive touch (WROOM/WROVER only)
70
+ from espzero import CapTouch
71
+ touch = CapTouch(pin=4) # GPIO 4 = T0
72
+ if touch.is_touched:
73
+ esp_led.on()
74
+
75
+ # Servo
76
+ servo = Servo(13)
77
+ servo.mid()
78
+ servo.value = 0.75 # 0–1 range, same as picozero
79
+ ```
80
+
81
+ ---
82
+
83
+ ## 1. File Structure
84
+
85
+ ```
86
+ espzero/
87
+ ├── __init__.py # Public API entry point + begin()
88
+ ├── _hal.py # Hardware Abstraction Layer (HAL)
89
+ ├── _core.py # Ported picozero core logic
90
+ ├── _wifi.py # ESP32-specific: WiFi class
91
+ ├── _touch.py # ESP32-specific: capacitive touch class
92
+ └── profiles/
93
+ ├── _base.py # Abstract BoardProfile base class
94
+ ├── auto.py # Runtime board auto-detection
95
+ └── esp32_boards.py # Profile definitions for all supported boards
96
+ ```
97
+
98
+ ---
99
+
100
+ ## 2. Board Profile System
101
+
102
+ ### 2-A. Base Class (`profiles/_base.py`)
103
+
104
+ ```python
105
+ class BoardProfile:
106
+ NAME = "unknown" # Human-readable board name
107
+ CHIP = "esp32" # esp32 / esp32s3 / esp32c3 / esp8266
108
+
109
+ # Pin aliases — lets users write LED("internal") instead of LED(2)
110
+ PIN_ALIASES = {}
111
+
112
+ # ADC settings
113
+ ADC_MAX_RAW = 4095 # 12-bit resolution (0–4095)
114
+ ADC_SCALE = 65535 # Scale target for picozero read_u16() compatibility
115
+ ADC_ATTEN = None # e.g. machine.ADC.ATTN_11DB; None = firmware default
116
+ ADC_VREF = 3.3 # Maximum input voltage (tied to attenuation)
117
+
118
+ # PWM settings
119
+ PWM_DEFAULT_FREQ = 1000 # Hz — Pico default was 100 Hz; 1 kHz recommended for ESP32
120
+ PWM_DUTY_MAX = 65535 # Internal scale is always 16-bit
121
+
122
+ # Servo
123
+ SERVO_FREQ = 50 # 50 Hz (20 ms frame) — same as picozero
124
+
125
+ # Built-in LED type
126
+ # "digital" — standard GPIO LED
127
+ # "neopixel" — WS2812 RGB (e.g. ESP32-S3 DevKit, M5Stack ATOM)
128
+ INTERNAL_LED_TYPE = "digital"
129
+ INTERNAL_LED_ACTIVE_HIGH = True
130
+
131
+ # Boot strapping pins — connecting a button here may cause boot failures
132
+ STRAPPING_PINS = [] # e.g. [0, 2, 5, 12, 15]
133
+
134
+ # ADC2 pins — cannot be used while WiFi is active
135
+ ADC2_PINS = [] # e.g. [0, 2, 4, 12, 13, 14, 15, 25, 26, 27]
136
+ ```
137
+
138
+ ### 2-B. Board Profile Examples (`profiles/esp32_boards.py`)
139
+
140
+ ```python
141
+ class ESP32DevKitV1(BoardProfile):
142
+ NAME = "esp32_devkit_v1"
143
+ CHIP = "esp32"
144
+ PIN_ALIASES = {"internal": 2, "led": 2}
145
+ ADC_ATTEN = ADC.ATTN_11DB # 0–3.6 V
146
+ ADC_VREF = 3.6
147
+ INTERNAL_LED_TYPE = "digital"
148
+ INTERNAL_LED_ACTIVE_HIGH = False # active-low
149
+ STRAPPING_PINS = [0, 2, 5, 12, 15]
150
+ ADC2_PINS = [0, 2, 4, 12, 13, 14, 15, 25, 26, 27]
151
+
152
+
153
+ class ESP32S3DevKit(BoardProfile):
154
+ NAME = "esp32_s3_devkit"
155
+ CHIP = "esp32s3"
156
+ PIN_ALIASES = {"internal": 48, "led": 48}
157
+ INTERNAL_LED_TYPE = "neopixel" # WS2812 RGB on GPIO 48
158
+ STRAPPING_PINS = [0, 3, 45, 46]
159
+ ADC2_PINS = [11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
160
+
161
+
162
+ class ESP32C3Mini(BoardProfile):
163
+ NAME = "esp32_c3_mini"
164
+ CHIP = "esp32c3"
165
+ PIN_ALIASES = {"internal": 8, "led": 8}
166
+ INTERNAL_LED_ACTIVE_HIGH = False
167
+ STRAPPING_PINS = [2, 8, 9]
168
+ ADC2_PINS = [] # C3 has no ADC2
169
+ ```
170
+
171
+ ### 2-C. Auto-detection (`profiles/auto.py`)
172
+
173
+ ```python
174
+ import sys
175
+
176
+ def detect() -> str:
177
+ """
178
+ Identify the chip from sys.implementation._machine.
179
+ Returns a board key that matches an entry in espzero._PROFILE_MAP.
180
+ """
181
+ try:
182
+ machine_str = sys.implementation._machine.lower()
183
+ except AttributeError:
184
+ return "esp32_devkit_v1" # fallback
185
+
186
+ if "esp32s3" in machine_str or "esp32-s3" in machine_str:
187
+ return "esp32_s3_devkit"
188
+ elif "esp32c3" in machine_str or "esp32-c3" in machine_str:
189
+ return "esp32_c3_mini"
190
+ else:
191
+ return "esp32_devkit_v1" # default: WROOM
192
+ ```
193
+
194
+ ---
195
+
196
+ ## 3. Hardware Abstraction Layer (`_hal.py`)
197
+
198
+ All `machine.Pin / machine.PWM / machine.ADC` calls are routed through this layer.
199
+ Swapping the board profile is enough to support a new board — `_core.py` requires no changes.
200
+
201
+ ```python
202
+ _profile = None # Set by begin() to a BoardProfile instance
203
+ _wifi_active = False # True while WiFi is connected
204
+
205
+ def make_digital_in(pin, pull_up=False):
206
+ """Create input Pin. Warns if GPIO is a strapping pin."""
207
+ gpio = resolve_pin(pin)
208
+ if gpio in get_profile().STRAPPING_PINS:
209
+ print("[espzero] WARNING: GPIO {} is a strapping pin. "
210
+ "Attaching a button may cause boot issues.".format(gpio))
211
+ ...
212
+
213
+ def set_duty_u16(pwm_obj, value):
214
+ """Set duty cycle. Falls back to duty(0–1023) on firmware < 1.19."""
215
+ try:
216
+ pwm_obj.duty_u16(value)
217
+ except AttributeError:
218
+ pwm_obj.duty(value >> 6) # 16-bit → 10-bit
219
+
220
+ def make_adc(pin):
221
+ """Create ADC. Warns if WiFi is active and pin is in ADC2 group."""
222
+ if _wifi_active and gpio in get_profile().ADC2_PINS:
223
+ print("[espzero] WARNING: WiFi is active. ADC2 (GPIO {}) "
224
+ "cannot be used. Switch to an ADC1 pin.".format(gpio))
225
+ ...
226
+ ```
227
+
228
+ ---
229
+
230
+ ## 4. picozero → espzero Change Table
231
+
232
+ | Topic | picozero (Pico) | espzero (ESP32) | How |
233
+ |-------|----------------|-----------------|-----|
234
+ | **Pin creation** | `Pin(num, ...)` directly | `_hal.make_digital_out(pin)` | HAL wrapper |
235
+ | **PWM channel conflict** | `PIN_TO_PWM_CHANNEL[]` table | **Removed** (ESP32 LEDC: any pin, any channel) | Deleted |
236
+ | **PWM duty write** | `duty_u16(val)` | Same, with fallback for firmware < 1.19 | `_hal.set_duty_u16()` |
237
+ | **ADC read** | `adc.read_u16()` | `adc_read_u16()` → scales `read()×16` internally | HAL |
238
+ | **ADC attenuation** | None | `ATTN_11DB` (set in profile) | Profile |
239
+ | **Built-in LED** | `LED("LED")` or `LED(25)` | `LED("internal")` → profile maps to real pin | Alias system |
240
+ | **Built-in temp** | `pico_temp_sensor` (ADC ch.4) | `esp_temp_sensor` (ESP32 `esp32` module) | Separate class |
241
+ | **PWM default freq** | 100 Hz | 1000 Hz (`PWM_DEFAULT_FREQ` in profile) | Profile value |
242
+ | **Servo freq** | 50 Hz | 50 Hz (unchanged) | — |
243
+ | **WiFi** | None | `WiFi` class | New |
244
+ | **Touch** | `TouchSensor` (external TTP223) | + `CapTouch` (ESP32 built-in touch peripheral) | New |
245
+
246
+ ---
247
+
248
+ ## 5. Key `_core.py` Changes
249
+
250
+ ### 5-A. `PWMOutputDevice` — Channel restriction removed
251
+
252
+ ```python
253
+ # picozero: PIN_TO_PWM_CHANNEL table + _check_pwm_channel() → removed
254
+ # espzero: ESP32 LEDC assigns each pin an independent channel — no conflicts
255
+
256
+ class PWMOutputDevice(OutputDevice, PinMixin):
257
+ def __init__(self, pin, freq=None, duty_factor=65535, ...):
258
+ self._pwm = _hal.make_pwm(pin, freq) # routed through HAL
259
+
260
+ def _write(self, value):
261
+ _hal.set_duty_u16(self._pwm, self._value_to_state(value)) # with fallback
262
+ ```
263
+
264
+ ### 5-B. `AnalogInputDevice` — ADC scale correction
265
+
266
+ ```python
267
+ class AnalogInputDevice(InputDevice, PinMixin):
268
+ def __init__(self, pin, ...):
269
+ self._adc = _hal.make_adc(pin) # attenuation applied in profile
270
+
271
+ def _read(self):
272
+ raw_u16 = _hal.adc_read_u16(self._adc) # scaled to 0–65535
273
+ return self._state_to_value(raw_u16) # upper logic unchanged
274
+
275
+ @property
276
+ def voltage(self):
277
+ return self.value * _hal.get_profile().ADC_VREF
278
+ ```
279
+
280
+ ### 5-C. Built-in objects renamed
281
+
282
+ ```python
283
+ # picozero: pico_led, pico_temp_sensor
284
+ # espzero:
285
+ esp_led = LED("internal") # profile resolves "internal" alias
286
+ esp_temp_sensor = ESPTemperatureSensor() # uses esp32.raw_temperature()
287
+ ```
288
+
289
+ ---
290
+
291
+ ## 6. ESP32-Specific Classes
292
+
293
+ ### 6-A. `WiFi` (`_wifi.py`)
294
+
295
+ ```python
296
+ from espzero import WiFi
297
+
298
+ wifi = WiFi()
299
+ ip = wifi.connect("MySSID", "password", timeout=10)
300
+ print("Connected:", ip) # blocks until connected or raises OSError
301
+
302
+ wifi.scan() # returns list of nearby APs
303
+ wifi.is_connected # True / False
304
+ wifi.ip # current IP string
305
+ wifi.disconnect()
306
+ ```
307
+
308
+ > **Note:** After `connect()`, the HAL sets `_wifi_active = True` automatically.
309
+ > Any attempt to use an ADC2 pin after this will print a warning.
310
+
311
+ ### 6-B. `CapTouch` — Capacitive touch (`_touch.py`)
312
+
313
+ ```python
314
+ from espzero import CapTouch
315
+
316
+ touch = CapTouch(pin=4, threshold=300) # GPIO 4 = T0 on WROOM
317
+ if touch.is_touched:
318
+ print("Touched! Raw:", touch.value)
319
+ ```
320
+
321
+ Unlike `TouchSensor` (which wraps an external TTP223 IC), `CapTouch` uses the ESP32's built-in capacitive touch peripheral directly. Available on pins T0–T9 of WROOM/WROVER modules.
322
+
323
+ ### 6-C. `NeoPixelLED` — Built-in RGB LED wrapper (`_core.py`)
324
+
325
+ ```python
326
+ class NeoPixelLED:
327
+ """
328
+ Treats a single WS2812 pixel as a simple on/off LED.
329
+ Automatically used as esp_led on boards where INTERNAL_LED_TYPE == 'neopixel'
330
+ (e.g. ESP32-S3 DevKit GPIO 48, M5Stack ATOM GPIO 27).
331
+ """
332
+ ```
333
+
334
+ ---
335
+
336
+ ## 7. Safety Nets for Beginners
337
+
338
+ espzero includes three runtime warnings designed to save beginners from common hardware pitfalls:
339
+
340
+ | # | Trigger | Warning |
341
+ |---|---------|---------|
342
+ | 1 | Using an **ADC2 pin while WiFi is active** | `[espzero] WARNING: WiFi is active. ADC2 (GPIO N) cannot be used...` |
343
+ | 2 | Attaching a **button to a strapping pin** | `[espzero] WARNING: GPIO N is a strapping pin. Boot issues may occur.` |
344
+ | 3 | Running on **firmware < 1.19** (no `duty_u16`) | Silently falls back to `duty()` — no crash |
345
+
346
+ ---
347
+
348
+ ## 8. Supported Boards
349
+
350
+ | Board | Built-in LED | ADC1 Pins | ADC2 Pins* | Touch Pins |
351
+ |-------|-------------|-----------|------------|------------|
352
+ | ESP32 DevKit V1 (WROOM) | GPIO 2 (active-low) | 32–39 | 0,2,4,12–15,25–27 | T0(4)–T9(32) |
353
+ | ESP32-S3 DevKit | GPIO 48 (RGB) | 1–10 | 11–20 | T1–T14 |
354
+ | ESP32-C3 Mini | GPIO 8 (active-low) | 0–4 | None | None |
355
+ | M5Stack ATOM Lite | GPIO 27 (RGB) | 33,35,36 | 0,2,4,12–15,25–27 | T0(4),T3(15) |
356
+ | Wemos D1 Mini32 | GPIO 2 (active-low) | 32–39 | 0,2,4,12–15,25–27 | T0(4)–T9(32) |
357
+ | NodeMCU V3 Lolin (ESP8266) | GPIO 2 (active-low) | A0 only (10-bit, 0–1.0 V) | None | None |
358
+
359
+ > \* ADC2 pins cannot be used while WiFi is active. For educational use, stick to ADC1 pins only.
360
+ >
361
+ > **ESP8266 ADC note:** A0 accepts 0–1.0 V only. Use a voltage divider (e.g. 220 kΩ + 100 kΩ) to measure 3.3 V signals safely.
362
+
363
+ ---
364
+
365
+ ## 9. Implementation Roadmap
366
+
367
+ ```
368
+ Phase 1 — Core port (complete)
369
+ [x] profiles/_base.py — BoardProfile abstract base class
370
+ [x] profiles/esp32_boards.py — WROOM, S3, C3, M5Stack, Wemos profiles
371
+ [x] profiles/auto.py — Runtime auto-detection
372
+ [x] _hal.py — HAL wrappers
373
+ [x] _core.py — Ported picozero core logic
374
+ · PWMOutputDevice: removed channel-collision check
375
+ · AnalogInputDevice: ADC read routed through HAL
376
+ · pico_led / pico_temp_sensor → esp_led / esp_temp_sensor
377
+ [x] __init__.py — begin() + public API
378
+
379
+ Phase 2 — ESP32-specific features (complete)
380
+ [x] _wifi.py — WiFi class
381
+ [x] _touch.py — CapTouch class
382
+ [x] Additional board profiles — S3, C3, M5Stack, Wemos
383
+
384
+ Phase 3 — Mu Editor integration (complete)
385
+ [x] mu/resources/esp32/ — Library bundled with Mu Editor
386
+ [x] mu/logic.py — esp32_lib auto-provisioned to mu_code/
387
+ [x] mu/interface/editor.py — Jedi search path includes esp32_lib
388
+ (dynamic autocomplete via source analysis)
389
+ ```
390
+
391
+ ---
392
+
393
+ ## 10. Design Decisions
394
+
395
+ | # | Topic | Decision |
396
+ |---|-------|----------|
397
+ | 1 | **ADC2 + WiFi warning** | `_hal.make_adc()` checks `_wifi_active` flag and prints a warning |
398
+ | 2 | **`duty_u16()` fallback** | `_hal.set_duty_u16()` wrapper silently falls back to `duty(val>>6)` on firmware < 1.19 |
399
+ | 3 | **Strapping pin warning** | `make_digital_in()` checks `STRAPPING_PINS` and prints a warning |
400
+ | 4 | **NeoPixel built-in LED** | `INTERNAL_LED_TYPE = "neopixel"` profile field selects `NeoPixelLED` wrapper automatically |
401
+ | 5 | **Lazy profile loading** | `_PROFILE_MAP` dict + `importlib` — only the selected board's module is imported |
402
+ | 6 | **Autocomplete** | Jedi analyses live source in `mu/resources/esp32/` — no separate static API file needed |
403
+
404
+ ---
405
+
406
+ ## License
407
+
408
+ MIT License — contributions welcome.
409
+ This library is part of the [Mu Editor](https://github.com/roboticsware/mu) project for educational IoT programming.