Compare commits
2 Commits
428ed8b884
...
4575ef16ad
| Author | SHA1 | Date | |
|---|---|---|---|
| 4575ef16ad | |||
| a342187635 |
1
presets.json
Normal file
1
presets.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"15": {"n5": 0, "n4": 0, "a": true, "n6": 0, "c": [[255, 0, 0], [0, 255, 0]], "b": 255, "n2": 0, "n1": 0, "p": "blink", "n3": 0, "d": 500}, "40": {"n5": 0, "n4": 0, "a": true, "n6": 0, "c": [[255, 255, 0]], "b": 255, "n2": 2600, "n1": 35, "p": "flame", "n3": 0, "d": 50}, "41": {"n5": 0, "n4": 5, "a": true, "n6": 0, "c": [[120, 200, 255], [80, 140, 255], [180, 120, 255], [100, 220, 232], [160, 200, 255]], "b": 255, "n2": 10, "n1": 72, "p": "twinkle", "n3": 5, "d": 500}, "42": {"n5": 0, "n4": 0, "a": true, "n6": 0, "c": [[166, 0, 255], [0, 10, 10]], "b": 255, "n2": 900, "n1": 30, "p": "radiate", "n3": 4000, "d": 5000}, "6": {"n5": 0, "n4": 0, "a": true, "n6": 0, "c": [[0, 255, 0]], "b": 255, "n2": 500, "n1": 1000, "p": "pulse", "n3": 1000, "d": 500}, "10": {"n5": 0, "n4": 0, "a": true, "n6": 0, "c": [[230, 242, 255]], "b": 200, "n2": 0, "n1": 0, "p": "on", "n3": 0, "d": 100}, "13": {"n5": 0, "n4": 0, "a": true, "n6": 0, "c": [[255, 255, 255]], "b": 255, "n2": 0, "n1": 1, "p": "rainbow", "n3": 0, "d": 150}, "3": {"n5": 0, "n4": 0, "a": true, "n6": 0, "c": [[255, 255, 255]], "b": 255, "n2": 0, "n1": 2, "p": "rainbow", "n3": 0, "d": 100}, "2": {"n5": 0, "n4": 0, "a": true, "n6": 0, "c": [[255, 255, 255]], "b": 0, "n2": 0, "n1": 0, "p": "off", "n3": 0, "d": 100}, "38": {"n5": 0, "n4": 0, "a": true, "n6": 0, "c": [[255, 0, 0], [0, 0, 255]], "b": 255, "n2": 0, "n1": 1, "p": "colour_cycle", "n3": 0, "d": 100}, "11": {"n5": 0, "n4": 0, "a": true, "n6": 0, "c": [[255, 0, 0]], "b": 255, "n2": 0, "n1": 0, "p": "on", "n3": 0, "d": 100}, "12": {"n5": 0, "n4": 0, "a": true, "n6": 0, "c": [[0, 0, 255]], "b": 255, "n2": 0, "n1": 0, "p": "on", "n3": 0, "d": 100}, "1": {"n5": 0, "n4": 0, "a": true, "n6": 0, "c": [[255, 255, 255]], "b": 255, "n2": 0, "n1": 0, "p": "on", "n3": 0, "d": 100}, "9": {"n5": 0, "n4": 0, "a": true, "n6": 0, "c": [[255, 245, 230]], "b": 200, "n2": 0, "n1": 0, "p": "on", "n3": 0, "d": 100}, "8": {"n5": 0, "n4": 0, "a": true, "n6": 0, "c": [[255, 0, 0], [0, 255, 0], [0, 0, 255], [255, 255, 0]], "b": 255, "n2": 0, "n1": 0, "p": "blink", "n3": 0, "d": 1000}, "39": {"n5": 0, "n4": 0, "a": true, "n6": 0, "c": [[255, 184, 77]], "b": 255, "n2": 0, "n1": 30, "p": "flicker", "n3": 0, "d": 80}, "14": {"n5": 0, "n4": 0, "a": true, "n6": 0, "c": [[255, 102, 0]], "b": 255, "n2": 1000, "n1": 2000, "p": "pulse", "n3": 2000, "d": 800}, "5": {"n5": 0, "n4": 1, "a": true, "n6": 0, "c": [[255, 0, 0], [0, 0, 255]], "b": 255, "n2": 5, "n1": 5, "p": "chase", "n3": 1, "d": 200}, "4": {"n5": 0, "n4": 0, "a": true, "n6": 0, "c": [[255, 0, 0], [0, 255, 0], [0, 0, 255], [255, 255, 255], [0, 0, 255], [255, 255, 0]], "b": 255, "n2": 0, "n1": 0, "p": "transition", "n3": 0, "d": 5000}, "7": {"n5": 0, "n4": 5, "a": true, "n6": 0, "c": [[255, 165, 0], [128, 0, 128]], "b": 255, "n2": 10, "n1": 2, "p": "circle", "n3": 2, "d": 200}}
|
||||||
227
src/patterns/twinkle.py
Normal file
227
src/patterns/twinkle.py
Normal file
@@ -0,0 +1,227 @@
|
|||||||
|
import random
|
||||||
|
import utime
|
||||||
|
|
||||||
|
# Default cool palette (icy blues, violet, mint) when preset has no colours.
|
||||||
|
# When `driver.debug` is True, print stats every N twinkle ticks (serial can be slow).
|
||||||
|
_TWINKLE_DBG_INTERVAL = 40
|
||||||
|
|
||||||
|
_DEFAULT_COOL = (
|
||||||
|
(120, 200, 255),
|
||||||
|
(80, 140, 255),
|
||||||
|
(180, 120, 255),
|
||||||
|
(100, 220, 240),
|
||||||
|
(160, 200, 255),
|
||||||
|
(90, 180, 220),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Twinkle:
|
||||||
|
def __init__(self, driver):
|
||||||
|
self.driver = driver
|
||||||
|
|
||||||
|
def _palette(self, preset):
|
||||||
|
colors = preset.c
|
||||||
|
if not colors:
|
||||||
|
return list(_DEFAULT_COOL)
|
||||||
|
out = []
|
||||||
|
for c in colors:
|
||||||
|
if isinstance(c, (list, tuple)) and len(c) == 3:
|
||||||
|
out.append(
|
||||||
|
(
|
||||||
|
max(0, min(255, int(c[0]))),
|
||||||
|
max(0, min(255, int(c[1]))),
|
||||||
|
max(0, min(255, int(c[2]))),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return out if out else list(_DEFAULT_COOL)
|
||||||
|
|
||||||
|
def run(self, preset):
|
||||||
|
"""Twinkle: n1 activity, n2 density; n3/n4 min/max length of adjacent on/off runs."""
|
||||||
|
palette = self._palette(preset)
|
||||||
|
num = self.driver.num_leds
|
||||||
|
if num <= 0:
|
||||||
|
while True:
|
||||||
|
yield
|
||||||
|
return
|
||||||
|
|
||||||
|
def activity_rate():
|
||||||
|
r = int(preset.n1)
|
||||||
|
if r <= 0:
|
||||||
|
r = 48
|
||||||
|
return max(1, min(255, r))
|
||||||
|
|
||||||
|
def density255():
|
||||||
|
"""Higher → more LEDs lit on average when a twinkle step fires (0 = default mid)."""
|
||||||
|
d = int(preset.n2)
|
||||||
|
if d <= 0:
|
||||||
|
d = 128
|
||||||
|
return max(0, min(255, d))
|
||||||
|
|
||||||
|
def cluster_len_bounds():
|
||||||
|
"""n3 = min adjacent LEDs per twinkle, n4 = max (both 0 → 1..4)."""
|
||||||
|
lo = int(preset.n3)
|
||||||
|
hi = int(preset.n4)
|
||||||
|
if lo <= 0 and hi <= 0:
|
||||||
|
lo, hi = 1, min(4, num)
|
||||||
|
else:
|
||||||
|
if lo <= 0:
|
||||||
|
lo = 1
|
||||||
|
if hi <= 0:
|
||||||
|
hi = lo
|
||||||
|
if hi < lo:
|
||||||
|
lo, hi = hi, lo
|
||||||
|
lo = max(1, min(lo, num))
|
||||||
|
hi = max(lo, min(hi, num))
|
||||||
|
return lo, hi
|
||||||
|
|
||||||
|
def random_cluster_len():
|
||||||
|
lo, hi = cluster_len_bounds()
|
||||||
|
# When min and max match, every lit/dim run is exactly that many LEDs (still capped by strip length).
|
||||||
|
if lo == hi:
|
||||||
|
return lo
|
||||||
|
return random.randint(lo, hi)
|
||||||
|
|
||||||
|
def cluster_base_index(start, k):
|
||||||
|
"""Shift run left so a length-k segment fits; keeps full k when num >= k."""
|
||||||
|
k = min(max(0, int(k)), num)
|
||||||
|
if k <= 0:
|
||||||
|
return 0
|
||||||
|
return max(0, min(int(start), num - k))
|
||||||
|
|
||||||
|
dens = density255()
|
||||||
|
on = [random.randint(0, 255) < dens for _ in range(num)]
|
||||||
|
colour_i = [random.randint(0, len(palette) - 1) for _ in range(num)]
|
||||||
|
last_update = utime.ticks_ms()
|
||||||
|
dbg_tick = 0
|
||||||
|
dbg_banner = False
|
||||||
|
|
||||||
|
def on_run_min_max(bits):
|
||||||
|
"""Smallest and largest contiguous run of True in bits (0,0 if all off)."""
|
||||||
|
best_min = num + 1
|
||||||
|
best_max = 0
|
||||||
|
cur = 0
|
||||||
|
for j in range(num):
|
||||||
|
if bits[j]:
|
||||||
|
cur += 1
|
||||||
|
else:
|
||||||
|
if cur:
|
||||||
|
if cur < best_min:
|
||||||
|
best_min = cur
|
||||||
|
if cur > best_max:
|
||||||
|
best_max = cur
|
||||||
|
cur = 0
|
||||||
|
if cur:
|
||||||
|
if cur < best_min:
|
||||||
|
best_min = cur
|
||||||
|
if cur > best_max:
|
||||||
|
best_max = cur
|
||||||
|
if best_min == num + 1:
|
||||||
|
return 0, 0
|
||||||
|
return best_min, best_max
|
||||||
|
|
||||||
|
if not preset.a:
|
||||||
|
for i in range(num):
|
||||||
|
if on[i]:
|
||||||
|
base = palette[colour_i[i] % len(palette)]
|
||||||
|
self.driver.n[i] = self.driver.apply_brightness(base, preset.b)
|
||||||
|
else:
|
||||||
|
self.driver.n[i] = (0, 0, 0)
|
||||||
|
self.driver.n.write()
|
||||||
|
yield
|
||||||
|
return
|
||||||
|
|
||||||
|
while True:
|
||||||
|
now = utime.ticks_ms()
|
||||||
|
delay_ms = max(1, int(preset.d))
|
||||||
|
if utime.ticks_diff(now, last_update) >= delay_ms:
|
||||||
|
rate = activity_rate()
|
||||||
|
dens = density255()
|
||||||
|
dbg = bool(getattr(self.driver, "debug", False))
|
||||||
|
dbg_tick += 1
|
||||||
|
# Snapshot for decisions; apply all darks then all lights so
|
||||||
|
# overlaps in the same tick favour lit runs (lights win).
|
||||||
|
prev_on = on[:]
|
||||||
|
prev_ci = colour_i[:]
|
||||||
|
next_on = list(prev_on)
|
||||||
|
next_ci = list(prev_ci)
|
||||||
|
dbg_ops = {"L": 0, "D": 0}
|
||||||
|
|
||||||
|
light_i = []
|
||||||
|
dark_i = []
|
||||||
|
for i in range(num):
|
||||||
|
if random.randint(0, 255) < rate:
|
||||||
|
r = random.randint(0, 255)
|
||||||
|
if not prev_on[i]:
|
||||||
|
if r < dens:
|
||||||
|
light_i.append(i)
|
||||||
|
else:
|
||||||
|
if r < (255 - dens):
|
||||||
|
dark_i.append(i)
|
||||||
|
|
||||||
|
def light_adjacent(start):
|
||||||
|
dbg_ops["L"] += 1
|
||||||
|
k = random_cluster_len()
|
||||||
|
b = cluster_base_index(start, k)
|
||||||
|
for dj in range(k):
|
||||||
|
idx = b + dj
|
||||||
|
next_on[idx] = True
|
||||||
|
next_ci[idx] = random.randint(0, len(palette) - 1)
|
||||||
|
|
||||||
|
def dark_adjacent(start):
|
||||||
|
dbg_ops["D"] += 1
|
||||||
|
k = random_cluster_len()
|
||||||
|
b = cluster_base_index(start, k)
|
||||||
|
for dj in range(k):
|
||||||
|
idx = b + dj
|
||||||
|
next_on[idx] = False
|
||||||
|
|
||||||
|
for i in dark_i:
|
||||||
|
dark_adjacent(i)
|
||||||
|
for i in light_i:
|
||||||
|
light_adjacent(i)
|
||||||
|
|
||||||
|
for i in range(num):
|
||||||
|
if next_on[i]:
|
||||||
|
base = palette[next_ci[i] % len(palette)]
|
||||||
|
self.driver.n[i] = self.driver.apply_brightness(base, preset.b)
|
||||||
|
else:
|
||||||
|
self.driver.n[i] = (0, 0, 0)
|
||||||
|
self.driver.n.write()
|
||||||
|
on = next_on
|
||||||
|
colour_i = next_ci
|
||||||
|
last_update = utime.ticks_add(last_update, delay_ms)
|
||||||
|
|
||||||
|
if dbg:
|
||||||
|
lo, hi = cluster_len_bounds()
|
||||||
|
if not dbg_banner:
|
||||||
|
dbg_banner = True
|
||||||
|
print(
|
||||||
|
"[twinkle] debug on: n1=%s n2=%s n3=%s n4=%s d=%s -> lo=%d hi=%d num=%d"
|
||||||
|
% (
|
||||||
|
preset.n1,
|
||||||
|
preset.n2,
|
||||||
|
preset.n3,
|
||||||
|
preset.n4,
|
||||||
|
preset.d,
|
||||||
|
lo,
|
||||||
|
hi,
|
||||||
|
num,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
rmin, rmax = on_run_min_max(on)
|
||||||
|
bad = lo > 0 and rmin > 0 and rmin < lo and num >= lo
|
||||||
|
if bad or (dbg_tick % _TWINKLE_DBG_INTERVAL == 0):
|
||||||
|
print(
|
||||||
|
"[twinkle] tick=%d rate=%d dens=%d L=%d D=%d on_runs min=%d max=%d%s"
|
||||||
|
% (
|
||||||
|
dbg_tick,
|
||||||
|
rate,
|
||||||
|
dens,
|
||||||
|
dbg_ops["L"],
|
||||||
|
dbg_ops["D"],
|
||||||
|
rmin,
|
||||||
|
rmax,
|
||||||
|
" **run<lo**" if bad else "",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
yield
|
||||||
25
tests/peers.py
Normal file
25
tests/peers.py
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
from espnow import ESPNow
|
||||||
|
import network
|
||||||
|
|
||||||
|
sta = network.WLAN(network.STA_IF)
|
||||||
|
sta.active(True)
|
||||||
|
|
||||||
|
espnow = ESPNow()
|
||||||
|
espnow.active(True)
|
||||||
|
|
||||||
|
# add_peer() expects a 6-byte MAC (bytes/bytearray), not integers.
|
||||||
|
# Unicast placeholders (not broadcast/multicast) so get_peers() lists them.
|
||||||
|
# PEERS = aa:aa:aa:aa:aa:START … aa:aa:aa:aa:aa:END (inclusive last octet).
|
||||||
|
_PREFIX = b"\xaa\xaa\xaa\xaa\xaa"
|
||||||
|
_START_LAST_OCTET = 1
|
||||||
|
_END_LAST_OCTET = 40
|
||||||
|
PEERS = tuple(_PREFIX + bytes((i,)) for i in range(_START_LAST_OCTET, _END_LAST_OCTET + 1))
|
||||||
|
for peer in PEERS:
|
||||||
|
espnow.add_peer(peer)
|
||||||
|
|
||||||
|
print("peers:", PEERS)
|
||||||
|
|
||||||
|
for peer in PEERS:
|
||||||
|
espnow.send(peer, b"Hello, world!")
|
||||||
|
|
||||||
|
print(espnow.get_peers())
|
||||||
41
tests/test_ap_pm0.py
Normal file
41
tests/test_ap_pm0.py
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""MicroPython AP example with power management disabled (pm=0).
|
||||||
|
|
||||||
|
Run on device:
|
||||||
|
mpremote connect /dev/ttyACM0 run tests/test_ap_pm0.py
|
||||||
|
"""
|
||||||
|
|
||||||
|
import network
|
||||||
|
import time
|
||||||
|
|
||||||
|
AP_SSID = "led-ap"
|
||||||
|
AP_PASSWORD = "ledpass123"
|
||||||
|
AP_CHANNEL = 6
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
ap = network.WLAN(network.AP_IF)
|
||||||
|
ap.active(True)
|
||||||
|
|
||||||
|
# Explicitly disable Wi-Fi power save for AP mode.
|
||||||
|
try:
|
||||||
|
ap.config(pm=0)
|
||||||
|
except (AttributeError, ValueError, TypeError):
|
||||||
|
try:
|
||||||
|
ap.config(pm=network.WLAN.PM_NONE)
|
||||||
|
except (AttributeError, ValueError, TypeError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
ap.config(essid=AP_SSID, password=AP_PASSWORD, channel=AP_CHANNEL, authmode=3)
|
||||||
|
|
||||||
|
print("[ap-pm0] AP active:", ap.active())
|
||||||
|
print("[ap-pm0] SSID:", AP_SSID)
|
||||||
|
print("[ap-pm0] IFCONFIG:", ap.ifconfig())
|
||||||
|
print("[ap-pm0] Waiting for clients. Ctrl+C to stop.")
|
||||||
|
|
||||||
|
while True:
|
||||||
|
time.sleep(2)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Reference in New Issue
Block a user