Add deep sleep and single-button UX with heartbeat LED

Replace the dual-switch handler with one button: short press cycles
patterns, long press arms deep sleep with ext1 GPIO wake. Add a
heartbeat LED task and fix the IMU test I2C pins to GPIO21/GPIO6.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-03 23:16:07 +12:00
parent c07e3e7a07
commit 7e14841478
2 changed files with 81 additions and 20 deletions

View File

@@ -8,6 +8,8 @@ from patterns import Patterns
import gc import gc
import utime import utime
import machine import machine
import esp32
import network
import time import time
import wifi import wifi
import json import json
@@ -15,8 +17,21 @@ from p2p import p2p
from machine import ADC, Pin, I2C from machine import ADC, Pin, I2C
from lsm6ds3 import LSM6DS3, is_lsm6ds3 from lsm6ds3 import LSM6DS3, is_lsm6ds3
# XIAO ESP32-C3: deep-sleep GPIO wake only on GPIO 0-5 (header D0-D3).
SW_PIN = 3 # D1 — switch to GND (short press cycle, long press sleep/wake)
LED_PIN = 8 # D8 — onboard LED (active low)
SLEEP_HOLD_MS = 2000
SLEEP_RELEASE_WAIT_MS = 500 # wait after release before arming wake
async def main(): async def main():
sw = Pin(SW_PIN, Pin.IN, Pin.PULL_UP)
if machine.reset_cause() == machine.DEEPSLEEP_RESET:
print("woke from deep sleep, reason:", esp32.wake_reason())
while sw.value() == 0:
utime.sleep_ms(50)
settings = Settings() settings = Settings()
led = Pin(LED_PIN, Pin.OUT, value=1)
patterns = Patterns(settings["led_pin"], settings["num_leds"], selected=settings["pattern"]) patterns = Patterns(settings["led_pin"], settings["num_leds"], selected=settings["pattern"])
if settings["color_order"] == "rbg": color_order = (1, 5, 3) if settings["color_order"] == "rbg": color_order = (1, 5, 3)
@@ -33,6 +48,14 @@ async def main():
patterns.tick() patterns.tick()
await asyncio.sleep_ms(0) await asyncio.sleep_ms(0)
async def heartbeat():
while True:
wdt.feed()
led.value(0)
await asyncio.sleep_ms(80)
led.value(1)
await asyncio.sleep_ms(920)
async def system(): async def system():
adc = ADC(Pin(0), atten=ADC.ATTN_11DB) adc = ADC(Pin(0), atten=ADC.ATTN_11DB)
while True: while True:
@@ -45,30 +68,66 @@ async def main():
print("%.3f V" % voltage) print("%.3f V" % voltage)
await asyncio.sleep(1) await asyncio.sleep(1)
async def sw(): def enter_deep_sleep():
sw1 = machine.Pin(8, machine.Pin.IN, machine.Pin.PULL_UP) patterns.select("off")
sw2 = machine.Pin(9, machine.Pin.IN, machine.Pin.PULL_UP) patterns.sync()
utime.sleep_ms(100)
led.value(1)
for iface in (network.STA_IF, network.AP_IF):
wl = network.WLAN(iface)
if wl.active():
wl.active(False)
if sw.value() == 0:
print("button still pressed, not sleeping")
return
esp32.wake_on_ext1(pins=(sw,), level=esp32.WAKEUP_ANY_LOW)
print("deep sleep — press GPIO %d to wake" % SW_PIN)
machine.deepsleep()
async def prepare_deep_sleep():
print("release button to sleep")
while sw.value() == 0:
wdt.feed()
await asyncio.sleep_ms(50)
await asyncio.sleep_ms(SLEEP_RELEASE_WAIT_MS)
if sw.value() == 0:
print("button pressed again, sleep cancelled")
return
enter_deep_sleep()
async def switch_loop():
p = ["on", "rainbow_cycle", "theater_chase", p = ["on", "rainbow_cycle", "theater_chase",
"color_transition", "flicker", "color_transition", "flicker",
"bidirectional_scanner"] "bidirectional_scanner"]
pattern_index = 0 pattern_index = 0
press_start = None
sleep_pending = False
print(p) print(p)
pressed1 = 0
pressed2 = 0
while True: while True:
if sw1.value() == 0: if sleep_pending:
patterns.select(p[pattern_index]) await prepare_deep_sleep()
print(p[pattern_index]) sleep_pending = False
pattern_index = pattern_index + 1 press_start = None
if pattern_index > len(p)-1: continue
pattern_index = 0
print("pressed 1") if sw.value() == 0:
if sw2.value() == 0: if press_start is None:
patterns.select("off") press_start = utime.ticks_ms()
await asyncio.sleep_ms(200) elif utime.ticks_diff(utime.ticks_ms(), press_start) >= SLEEP_HOLD_MS:
sleep_pending = True
press_start = None
else:
if press_start is not None:
held = utime.ticks_diff(utime.ticks_ms(), press_start)
if 50 < held < SLEEP_HOLD_MS:
patterns.select(p[pattern_index])
print(p[pattern_index])
pattern_index = (pattern_index + 1) % len(p)
press_start = None
await asyncio.sleep_ms(50)
telemetry = { telemetry = {
"voltage": 0.0, "voltage": 0.0,
@@ -171,18 +230,20 @@ async def main():
print("IMU read error:", e) print("IMU read error:", e)
await asyncio.sleep_ms(200) await asyncio.sleep_ms(200)
wdt = machine.WDT(timeout=10000)
wdt.feed()
w = web(settings, patterns, telemetry) w = web(settings, patterns, telemetry)
print(settings) print(settings)
# start the server in a bacakground task # start the server in a bacakground task
print("Starting") print("Starting")
server = asyncio.create_task(w.start_server(host="0.0.0.0", port=80)) server = asyncio.create_task(w.start_server(host="0.0.0.0", port=80))
wdt = machine.WDT(timeout=10000)
wdt.feed()
asyncio.create_task(tick()) asyncio.create_task(tick())
asyncio.create_task(heartbeat())
asyncio.create_task(p2p(settings, patterns)) asyncio.create_task(p2p(settings, patterns))
asyncio.create_task(system()) asyncio.create_task(system())
asyncio.create_task(sw()) asyncio.create_task(switch_loop())
if imu_sensor is not None: if imu_sensor is not None:
asyncio.create_task(imu_loop()) asyncio.create_task(imu_loop())

View File

@@ -5,7 +5,7 @@ from lsm6ds3 import LSM6DS3, is_lsm6ds3
# XIAO ESP32C3 default I2C pins: SDA=GPIO6 (D4), SCL=GPIO7 (D5) # XIAO ESP32C3 default I2C pins: SDA=GPIO6 (D4), SCL=GPIO7 (D5)
i2c = I2C(0, scl=Pin(23), sda=Pin(22), freq=400000) i2c = I2C(0, scl=Pin(20), sda=Pin(21), freq=400000)
def scan_i2c(): def scan_i2c():