30 Commits

Author SHA1 Message Date
07cd592566 Mix forward-only and bidirectional n_chase patterns 2025-10-26 22:25:38 +13:00
2228b23a53 Remove blink from random test 2025-10-26 22:12:49 +13:00
1f33fff665 Change random test duration to 1-5 mins 2025-10-26 22:09:38 +13:00
e91b291dc6 Remove off from random test 2025-10-26 22:05:52 +13:00
2db9b3464a Add random test suite 2025-10-26 21:33:31 +13:00
8345b31caf Refactor n_chase pattern for bidirectional movement
- Updated n_chase to support bidirectional movement with n3 and n4 parameters
- n3 controls forward steps per direction change
- n4 controls backward steps per direction change
- Pattern alternates between moving forward n3 steps and backward n4 steps
- Each direction repeats for the specified number of steps before switching
- Added test/9.py with n1=20, n2=20, n3=20, n4=-5 parameters
- Updated to run as a thread-based pattern similar to other patterns
2025-10-26 20:39:28 +13:00
dce2954114 Refactor rainbow pattern to travel along the length of LED strip
- Modified rainbow() to distribute colors along the physical length
- Each LED gets a different hue based on its position
- n1 parameter controls how many times the rainbow cycles (nodes)
- Split rainbow tests into test/rainbow/ directory with 9 numbered test files
- Each test runs forever until interrupted
- Added main.py to run all tests in sequence repeatedly
2025-10-26 20:19:21 +13:00
15626431ce Add settings editor utility 2025-10-26 19:03:00 +13:00
b13ec01561 Add flicker and n_chase patterns, increase test brightness to 255, add test suites 2025-10-26 18:49:45 +13:00
83cb34d6a8 Add sine brightness pattern 2025-10-26 00:15:53 +13:00
c9449a6d86 Add circle loading pattern 2025-10-25 23:28:01 +13:00
059bd41a59 convert blink to use a thread 2025-10-25 19:53:16 +13:00
9fad8b1ae5 Refactor param mapping to dict 2025-10-25 19:01:57 +13:00
ae407ab3aa receiver: no change to protocol; revert temporary color-index handling; radiate uses ticks_us timing; development: ensure dev.py upload workflow retained 2025-10-04 01:10:46 +13:00
e516b49eb8 Add segmented_movement pattern with alternating forward/backward movement
- Add n4 parameter support to main.py ESP NOW receiver
- Implement segmented_movement pattern with configurable parameters:
  * n1: segment length (number of LEDs per segment)
  * n2: spacing between segments
  * n3: forward movement speed (positions per beat)
  * n4: backward movement speed (positions per beat)
- Pattern alternates between forward and backward movement each beat
- If only n3 or n4 is set, moves in that direction every beat
- Draws repeating segments with spacing across entire LED strip
- Add Pipfile script to run dev.py directly with arguments
2025-10-03 19:56:24 +13:00
355d113e32 Fix rainbow pattern synchronization in LED bar
- Use controller's step for synchronization instead of internal step counter
- Rainbow pattern now syncs with controller timing like n_chase pattern
- Prevents rainbow from running independently and out of sync
- Uses beat_index % 256 for full color wheel cycling
2025-09-19 01:29:48 +12:00
d715af4344 Fix n_chase pattern to properly chase through all LED positions
- Replace oscillating behavior with proper chasing movement
- Use pattern_step for internal tracking instead of controller's step
- Calculate position relative to chase head: (i - pattern_step) % num_leds
- Chase head moves through all LED positions with n3 step multiplier
- n1 controls width of lit chase segment
2025-09-19 00:22:25 +12:00
67c4a1a6f6 Update LED bar to handle message type field
- Process 't' field to distinguish between beat ('b') and update ('u') messages
- Beat messages: execute pattern immediately using current parameters
- Update messages: only update parameters, don't execute pattern
- Maintains backward compatibility with default to beat if 't' not specified
- Enables proper synchronization between controller and bars
2025-09-18 22:10:23 +12:00
748ad4b507 Add n3 step rate functionality to patterns 2025-09-18 20:35:21 +12:00
1275d60aaa Make alternating pattern timing independent of n1
- Changed alternating pattern to return delay/2 instead of delay
- Each phase now lasts delay/2, making full cycle equal to delay
- n1 now only controls ON/OFF segment width, not timing
2025-09-18 19:11:35 +12:00
d8e853183b main: enforce event-driven behavior; run selected pattern once per message; clarify comments; fix pattern lookup 2025-09-17 20:20:41 +12:00
8cfb3e156b patterns: add rainbow, specto, and radiate (out then dark-out)
radiate: origins every n1, step by delay, stop when full, dark wave outward, ensure strip off at end, run once

alternating: use n1 as ON width and n2 as OFF width; phase via self.step

pulse: attack (n1), hold (delay), decay (n2); stop at end

tests: add specto sweep (n1_sequence) and radiate demo; include n index per message; use nested {name:{...}} schema; support iterations/repeat-delay
2025-09-16 22:28:51 +12:00
d599af271b patterns: alternating uses n1 (on) and n2 (off); ensure visible ON color; return delay; phase via self.step
test: WS client sends nested {name:{...}}; add iterations and repeat-delay; include n per message; use n1/n2 for alternating
2025-09-16 21:22:47 +12:00
93560a253e patterns: fix blink timing; slow alternating; unify self-test with absolute tick scheduling 2025-09-15 14:12:43 +12:00
d68817ea18 Pipfile.lock: update lockfile 2025-09-15 12:58:51 +12:00
a7a2274a59 Pipfile: sync dependencies 2025-09-15 12:58:45 +12:00
df838dc4d6 settings: adjust defaults and color order handling 2025-09-15 12:58:39 +12:00
4ec48b9f8f main: update loop/test harness configuration 2025-09-15 12:58:30 +12:00
1456ed8a6e boot: minor adjustments 2025-09-15 12:58:20 +12:00
80d5a66fab patterns: centralize timing in tick(); remove selected-delay coupling; update self-test to use per-config durations 2025-09-15 12:56:57 +12:00
69 changed files with 4403 additions and 2386 deletions

1
.gitignore vendored
View File

@@ -1,3 +1,2 @@
settings.json settings.json
.venv .venv
__pycache__

71
8_BAR_SETUP.md Normal file
View File

@@ -0,0 +1,71 @@
# 8-LED Bar System Setup
This system supports 8 LED bars working together, each with unique names "100" through "107".
## Quick Setup
### 1. Configure Each LED Bar
Each LED bar needs a unique name. Run the configuration script on each bar:
```bash
python configure_bar.py
```
Then enter the bar name (100, 101, 102, etc.) when prompted.
### 2. Update Bar Names (Optional)
To change the bar names, edit `/home/jimmy/projects/lighting-controller/src/bar_config.py`:
```python
LED_BAR_NAMES = [
"100", # Bar 1
"101", # Bar 2
"102", # Bar 3
"103", # Bar 4
"104", # Bar 5
"105", # Bar 6
"106", # Bar 7
"107", # Bar 8
]
```
### 3. Default Settings
All bars use the same default settings defined in `bar_config.py`:
```python
DEFAULT_BAR_SETTINGS = {
"pattern": "pulse",
"delay": 100,
"colors": [(0, 255, 0)], # Default green
"brightness": 100,
"num_leds": 200,
"n1": 10,
"n2": 10,
"n3": 1,
"n": 0,
}
```
## How It Works
1. **Lighting Controller** sends ESP-NOW messages to all bars simultaneously
2. **Each LED Bar** listens for messages addressed to its unique name
3. **All bars** receive the same pattern/color/brightness settings
4. **Synchronized effects** across all 8 bars
## Current Features
- ✅ All bars show the same pattern simultaneously
- ✅ Individual bar addressing (100-107)
- ✅ Optimized JSON payloads with defaults deduplication
- ✅ Easy configuration via `bar_config.py`
- ✅ MIDI control for all bars
- ✅ n3 step rate functionality
## Future Enhancements
- Sequential patterns (bar 1 → bar 2 → bar 3...)
- Wave effects across bars
- Individual bar control
- Master/slave synchronization
- Physical arrangement awareness

View File

@@ -8,6 +8,7 @@ mpremote = "*"
pyserial = "*" pyserial = "*"
esptool = "*" esptool = "*"
watchfiles = "*" watchfiles = "*"
uvicorn = "*"
[dev-packages] [dev-packages]
@@ -15,5 +16,4 @@ watchfiles = "*"
python_version = "3.12" python_version = "3.12"
[scripts] [scripts]
dev = "./dev.py"
dev = 'watchfiles "./dev.py /dev/ttyACM0 src reset follow"'

546
Pipfile.lock generated
View File

@@ -1,7 +1,7 @@
{ {
"_meta": { "_meta": {
"hash": { "hash": {
"sha256": "7b8033c15743e27f2589635c75bd0bb86ffc3a725b179d7db9ef200119aa9164" "sha256": "53809b70ded7a2b3e577a8a4263fbadbb722d1e8d92eb016e134b0776fd40f6b"
}, },
"pipfile-spec": 6, "pipfile-spec": 6,
"requires": { "requires": {
@@ -26,142 +26,142 @@
}, },
"bitarray": { "bitarray": {
"hashes": [ "hashes": [
"sha256:00628196dd3592972a5183194ab1475dadf9ef2a4cf3fd8c7c184a94934012e8", "sha256:002b73bf4a9f7b3ecb02260bd4dd332a6ee4d7f74ee9779a1ef342a36244d0cf",
"sha256:01d6dc548e7fe5c66913c2274f44855b0f8474935acff7811e84fe1f4024c94f", "sha256:01e3ba46c2dee6d47a4ab22561a01d8ee6772f681defc9fcb357097a055e48cf",
"sha256:056fe779f01a867d572e071c0944ac2f3bf58d8bced326040f0bd060af33a209", "sha256:03dc877ec286b7f2813185ea6bc5f1f5527fd859e61038d38768883b134e06b3",
"sha256:080a7bf55c432abdae74f25dc3dbff407418346aeae1d43e31f65e8ef114f785", "sha256:03eeab48f376c3cd988add2b75c20d2d084b6fcc9a164adb0dc390ef152255b4",
"sha256:0956322bf4d5e2293e57600aa929c241edf1e209e94e12483bf58c5c691432db", "sha256:05ee46a734b5110c5ac483815da4379f7622f4316362872ec7c0ed16db4b0148",
"sha256:0a6f9e897907757e9c2d722ae6c203d48a04826a14e1495e33935c8583c163a9", "sha256:0751596f60f33df66245b2dafa3f7fbe13cb7ac91dd14ead87d8c2eec57cb3ed",
"sha256:0ac446f557eb28e3f7c65372608810ff073840627e9037e22ed10bd081793a34", "sha256:08c114cf02a63e13ce6d70bc5b9e7bdcfa8d5db17cece207cfa085c4bc4a7a0c",
"sha256:0b47843f2f288fa746dead4394591a3432a358aaad48240283fa230d6e74b0e7", "sha256:0ed4a87eda16e2f95d536152c5acccae07841fbdda3b9a752f3dbf43e39f4d6b",
"sha256:11fc8bc65f964c7278deb1b7a69379dab3ecc90095f252deb17365637ebb274d", "sha256:101230b8074919970433ef79866570989157ade3421246d4c3afb7a994fdc614",
"sha256:129165b68a3e0c2a633ed0d8557cf5ade24a0b37ca97d7805fa6fc5fb73c19d5", "sha256:11fcfdf272549a3d876f10d8422bcd5f675750aa746ce04ff04937ec3bb2329e",
"sha256:139963494fc3dd5caee5e38c0a03783ef50be118565e94b1dbb0210770f0b32d", "sha256:160f449bb91686f8fc9984200e78b8d793b79e382decf7eb1dc9948d7c21b36f",
"sha256:157313a124287cbc8a11b55a75def0dd59e68badbc82c2dc2d204dc852742874", "sha256:16426a843b1bc9c552a7c97d6d7555e69730c2de1e2f560503d3fc0e7f6d8005",
"sha256:16d0edab54bb9d214319418f65bd15cfc4210ec41a16c3dd0b71e626c803212d", "sha256:1f1575cc0f66aa70a0bb5cb57c8d9d1b7d541d920455169c6266919bf804dc20",
"sha256:1971050b447023288a2b694a03b400bd5163829cd67b10f19e757fe87cd1161e", "sha256:1f7a8fc5085450635a539c47c9fce6d441b4a973686f88fc220aa20e3921fe55",
"sha256:1c4e75bbf9ade3d2cdf1b607a8b353b17d9b3cf54e88b2a5a773f50ae6f1bfbc", "sha256:1fb0a46ae4b8d244a3fb80c3055717baa3dec6be17938e6871042a8d5b4ce670",
"sha256:1c9f36055a89b9517db66eb8e80137126bf629c767ceeade4d004e40bc8bcd99", "sha256:2965fd8ba31b04c42e4b696fad509dc5ab50663efca6eb06bb3b6d08587f3a09",
"sha256:2020102a40edd094c0aa80e09203af71c533c41f76ce3237c99fd194a473ea33", "sha256:2b524306104c1296f1e91d74ee4ccbeeea621f6a13e44addf0bb630a1839fd72",
"sha256:20febc849a1f858e6a57a7d47b323fe9e727c579ddd526d317ad8831748a66a8", "sha256:2db04b165a57499fbcfe0eaa2f7752f118552bbcfab2163a43fef8d95f4ae745",
"sha256:220d4b8649ef54ac98e5e0e3dd92230247f67270d1524a8b31aa9859007affb0", "sha256:3092f6bbf4a75b1e6f14a5b1030e27c435f341afeb23987115e45a25cc68ba91",
"sha256:22188943a29072b684cd7c99e0b2cfc0af317cea3366c583d820507e6d1f2ed4", "sha256:30a2fc37698820cbf9b51d5f801219ef4bed828a04f3307072b8f983dc422a0e",
"sha256:222cb27ff05bc0aec72498d075dba1facec49a76a7da45740690cebbe3e81e43", "sha256:3110b98c5dfb31dc1cf82d8b0c32e3fa6d6d0b268ff9f2a1599165770c1af80f",
"sha256:243825f56b58bef28bfc602992a8c6d09bbc625628c195498d6020120d632a09", "sha256:33f604bffd06b170637f8a48ddcf42074ed1e1980366ac46058e065ce04bfe2a",
"sha256:25060e7162e44242a449ed1a14a4e94b5aef340812754c443459f19c7954be91", "sha256:340c524c7c934b61d1985d805bffe7609180fb5d16ece6ce89b51aa535b936f2",
"sha256:26691454a6770628882b68fe74e9f84ca2a51512edd49cbb025b14df5a9dd85a", "sha256:37a6a8382864a1defb5b370b66a635e04358c7334054457bbbb8645610cd95b2",
"sha256:27d13c7b886afc5d2fc49d6e92f9c96b1f0a14dc7b5502520c29f3da7550d401", "sha256:3875578748b484638f6ea776f534e9088cfb15eee131aac051036cba40fd5d05",
"sha256:27eeee915258b105a21a4b0f8aebc5f77bb4dc4fb4063a09dd329fa1fdcbd234", "sha256:38b0261483c59bb39ae9300ad46bf0bbf431ab604266382d986a349c96171b36",
"sha256:29ed022189a7997de46cb9bd4e2e49d6163d4f8d78dea72ac5a0e0293b856810", "sha256:3b9a2eb7d2e0e9c2f25256d2663c0a2a4798fe3110e3ddbbb1a7b71740b4de08",
"sha256:2a324e3007afb5c667026f5235b35efe3c4a95f1b83cd93aa9fce67b42f08e7c", "sha256:3bb3cf22c3c03ae698647e6766314149c9cf04aa2018d9f48d5efddc3ced2764",
"sha256:2c533c828d0007fac27cf45e5c1a711e5914dd469db5fe6be5f4e606bf2d7f63", "sha256:3db0648536f3e08afa7ceb928153c39913f98fd50a5c3adf92a4d0d4268f213e",
"sha256:30ba4fba3de1dca653de41c879349ec6ca521d85cff6a7ca5d2fdd8f76c93781", "sha256:3dc654da62b3a3027b7c922f7e9f4b27feaabd5d38b2a98ea98de5e8107c72f2",
"sha256:357e07c827bad01f98d0bd0dfdc722f483febeed39140fd75ffd016a451b60b9", "sha256:4079857566077f290d35e23ff0e8ba593069c139ae85b0d152b9fa476494f50a",
"sha256:3800f3c8c9780f281cf590543fd4b3278fea6988202273a260ecc58136895efb", "sha256:44f468fb4857fff86c65bec5e2fb67067789e40dad69258e9bb78fc6a6df49e7",
"sha256:3d6f3a94abd8b44b2bf346ca81ab2ff41ab9146c53905eedf5178b19d9fe53bf", "sha256:45660e2fabcdc1bab9699a468b312f47956300d41d6a2ea91c8f067572aaf38a",
"sha256:3eb1390a8b062fe9125e5cc4c5eba990b5d383eec54f2b996e7ce73ac43150f9", "sha256:477b9456eb7d70f385dc8f097a1d66ee40771b62e47b3b3e33406dcfbc1c6a3b",
"sha256:407920e9318d94cc6c9611aaa5b5e5963a09f1cbfa17b16b66edea453b3754f4", "sha256:481239cd0966f965c2b8fa78b88614be5f12a64e7773bb5feecc567d39bb2dd5",
"sha256:42622c42c159ea4535bba7e1e3c97f1fec79505bc6873ae657dc0a8f861c60de", "sha256:4a83d247420b147d4b3cba0335e484365e117dc1cfe5ab35acd6a0817ad9244f",
"sha256:4695fcd37478988b1d0a16d5bc0df56dcb677fd5db37f1893d993fd3ebef914b", "sha256:53d2abeabb91a822e9d76420c9b44980edd2d6b21767c7bb9cb2b1b4cf091049",
"sha256:4798f6744fa2633666e17b4ea8ff70250781b52a25afdbf5ffb5e176c58848f1", "sha256:55c31bc3d2c9e48741c812ee5ce4607c6f33e33f339831c214d923ffc7777d21",
"sha256:4a5b0d277087a5bf261a607fc6ff4aaffcf80b300cd19b7a1e9754a4649f5fd4", "sha256:567d6891cb1ddbfd0051fcff3cb1bb86efc82ec818d9c5f98c37d59c1d23cc96",
"sha256:4e297fd2e58afe17e33dd80c231c3a9d850279a2a8625aed1d39f9be9534809e", "sha256:57b9df5d38ab49c13eaa9e0152fdfa8501fc23987f6dcf421b73484bfe573918",
"sha256:507e567aee4806576e20752f22533e8b7ec61e7e75062a7ce9222a0675aa0da6", "sha256:59ddb8a9f47ec807009c69e582d0de1c86c005f9f614557f4cebc7b8ac9b7d28",
"sha256:50d702149747852923be60cae125285eca8d189d4c7d8832c0c958d4071a0f78", "sha256:61b9f3cf3a55322baed8f0532b73bce77d688a01446c179392c4056ab74eb551",
"sha256:51947a00ae9924584fb14c0c1b1f4c1fd916d9abd6f47582f318ab9c9cb9f3d0", "sha256:639389b023315596e0293f85999645f47ec3dc28c892e51242dde6176c91486b",
"sha256:52328192d454ca2ddad09fbc088872b014c74b22ecdd5164717dc7e6442014fa", "sha256:64d1143e90299ba8c967324840912a63a903494b1870a52f6675bda53dc332f7",
"sha256:531e6dfec8058fcf5d69e863b61e6b28e3749b615a4dcc0ab8ad36307c4017fc", "sha256:6542e1cfe060badd160cd383ad93a84871595c14bb05fb8129f963248affd946",
"sha256:54bd71f14a5fa9bae73ef92f2e2be894dc36c7a6d1c4962e5969bd8a9aa39325", "sha256:69687ef16d501c9217675af36fa3c68c009c03e184b07d22ba245e5c01d47e6b",
"sha256:552a93be286ca485914777461b384761519db313e0a7f3012dca424c9610a4d5", "sha256:6f7e1cdf0abb11718e655bb258920453b1e89c2315e9019f60f0775704b12a8c",
"sha256:583b46b3ba44121de5e87e95ae379932dc5fd2e37ebdf2c11a6d7975891425c1", "sha256:7378055c9f456c5bb034ac313d9a9028fc6597619a0b16584099adba5a589fdb",
"sha256:5b58a672ec448fb36839a5fc7bf2b2f60df9a97b872d8bd6ca1a28da6126f5c7", "sha256:78103afbd0a94ac4c1f0b4014545fd149b968d5ea423aaa3b1f6e2c3fc19423e",
"sha256:5cfbdccddaa0ff07789e9e180db127906c676e479e05c04830cd458945de3511", "sha256:79038bf1a7b13d243e51f4b6909c6997c2ba2bffc45bcae264704308a2d17198",
"sha256:5da4939e241301f5e1d18118695e8d2c300be90431b66bd43a00376acec45e1e", "sha256:795b1760418ab750826420ae24f06f392c08e21dc234f0a369a69cc00444f8ec",
"sha256:5dd9edcab8979a50c2c4dec6d5b66789fb6f630bb52ab90a4548111075a75e48", "sha256:7998dfb1e9e0255fb8553abb019c3e7f558925de4edc8604243775ff9dd3898d",
"sha256:5e304f94c0353f6ae5711533b5793b3a45b17aa2c5b07e656649b0af4e0939b5", "sha256:7afc740ad45ee0e0cef055765faf64789c2c183eb4aa3ecb8cecdb4b607396b3",
"sha256:60408ec9c0bd76f1fa00d28034429a0316246d31069b982a86aec8d5c99e910a", "sha256:7b4a41dc183d7d16750634f65566205990f94144755a39f33da44c0350c3e1a8",
"sha256:62f71b268f14ee6cc3045b95441bfe0518cef1d0b2ffbc6f3e9758f786ff5a03", "sha256:7f825ebedcad87a2825ddb6cf62f6d7d5b7a56ddaf7c93eef4b974e7ddc16408",
"sha256:664d462a4c0783fd755fe3440f07b7e46d149859c96caacadf3f28890f19a8de", "sha256:7f9f9bb2c5cc1f679605ebbeb72f46fc395d850b93fa7de7addd502a1dc66e99",
"sha256:66d8b7a89fac6042f7df9ea97d97ed0f5e404281110a882e3babd909161f85b6", "sha256:7fdf059d4e3acec44f512ebe247718ae511fde632e2b06992022df8e637385a6",
"sha256:6755cfcfa7d8966e704d580c831e39818f85e7b2b7852ad22708973176f0009e", "sha256:81e4648c09103bc18f488957c1e0863d2397bab6625c0e6771891f151ee0bd96",
"sha256:679856547f0b27b98811b73756bdf53769c23b045a6f95177cae634daabf1ddf", "sha256:8489bff00a1f81ac0754355772e76775878c32a42f16f01d427c3645546761c4",
"sha256:6841c08b51417f8ffe398b2828fc0593440c99525c868f640e0302476745320b", "sha256:851398428f5604c53371b72c5e0a28163274264ada4a08cd1eafe65fde1f68d0",
"sha256:68f6e64d4867ee79e25c49d7f35b2b1f04a6d6f778176dcf5b759f3b17a02b2b", "sha256:87a29b8a4cc72af6118954592dcd4e49223420470ccc3f8091c255f6c7330bb1",
"sha256:69d2d507c1174330c71c834b5d65e66181ad7b42b0d88b5b31804ee9b4f5dae7", "sha256:8b8e07374d60040b24d1a158895d9758424db13be63d4b2fe1870e37f9dec009",
"sha256:6f0be27d06732e2833b672a8fcc32fa195bdb22161eb88f8890de15e30264a01", "sha256:8d4aa56782368269eb9402caf7378b2a5ada6f05eb9c7edc2362be258973fd7e",
"sha256:6f7d2dbe628f3db935622a5b80a5c4d95665cdefc4904372aa3c4d786289477f", "sha256:97c448a20aded59727261468873d9b11dfdcce5a6338a359135667d5e3f1d070",
"sha256:72760411d60d8d76979a20ed3f15586d824db04668b581b86e61158c2b616db0", "sha256:98373c273e01a5a7c17103ecb617de7c9980b7608351d58c72198e3525f0002e",
"sha256:727f7a969416f02ef5c1256541e06f0836fb615022699fa8e2591e85296c5570", "sha256:98e4a17f55f3cbf6fe06cc79234269572f234467c8355b6758eb252073f78e6b",
"sha256:77d2368a06a86a18919c05a9b4b0ee9869f770e6a5f414b0fecc911870fe3974", "sha256:99124e39658b2f72d296819ec03418609dd4f1b275b00289c2f278a19da6f9c0",
"sha256:79ab1c5f26f23e51d4a44c4397c8a3bf56c306c125dfab6b3eebdfa13d1dca6f", "sha256:9ad0df7886cb9d6d2ff75e87d323108a0e32bdca5c9918071681864129ce8ea8",
"sha256:79db23eda81627132327ed292bd813a9af64399b98aaac3d42ad8deeed24cd5e", "sha256:9bfdfe2e2af434d3f4e47250f693657334e34a7ec557cd703b129a814422b4b8",
"sha256:7c20d6e6cafce5027e7092beb2ac6eec0d71045d6318b34f36e1387a8c8859a3", "sha256:9faa4c6fcb19a31240ad389426699a99df481b6576f7286471e24efbf1b44dfc",
"sha256:7e0851a985a7b10f634188117c825ef99d63402555cca5bc32c7bfc5adaf0d6f", "sha256:a048e41e1cb0c1a37021269d02698e30d2a7cc9a0205dd3390e0807745b76dae",
"sha256:7e2e1ff784c2cdfd863bad31985851427f2d2796e445cec85080c7510cba4315", "sha256:a05982bb49c73463cb0f0f4bed2d8da82631708a2c2d1926107ba99651b419ec",
"sha256:7f8b12424f8fdf29d1c0749c628bd1530cecfc77374935d096cccc0e4eada232", "sha256:a23b5f13f9b292004e94b0b13fead4dae79c7512db04dc817ff2c2478298e04a",
"sha256:81e84054b22babcd6c5cc1eac0de2bfc1054ecdf742720cbfb36efbe89ec6c30", "sha256:a393b0f881eff94440f72846a6f0f95b983594a0a50af81c41ed18107420d6a7",
"sha256:84bb57010a1ab76cf880424a2e0bce8dd26989849d2122ff073aa11bfc271c27", "sha256:a3b6bd81c77d9925809b714980cd30b1831a86bd090316d37cab124d92af1daf",
"sha256:870ed23361e2918ab1ffc23fe0ab293abf3c372a68ee7387456d13da3e213008", "sha256:a43f4631ecb87bedc510568fef67db53f2a20c4a5953a9d1e07457e7b1d14911",
"sha256:8cf44b012e7493127ce7ca6e469138ac96b3295a117877d5469aabe7c8728d87", "sha256:a569c993942ac26c6c590639ed6712c6c9c3f0c8d287a067bf2a60eb615f3c6b",
"sha256:8d6c9bc14bacdfbfd51fed85f0576973eaaa7b30d81ef93264f8e22b86a9c9f7", "sha256:a5b89349f05431270d1ccc7321aaab91c42ff33f463868779e502438b7f0e668",
"sha256:8d759cecfa8aab4a1eb4e23b6420126b15c7743e85b33f389916bb98c4ecbb84", "sha256:ac39319e6322c2c093a660c02cea6bb3b1ae53d049b573d4781df8896e443e04",
"sha256:8ef3f0977c21190f949d5cfd71ded09de87d330c6d98bd5ecb5bb1135d666d0d", "sha256:acc56700963f63307ac096689d4547e8061028a66bb78b90e42c5da2898898fb",
"sha256:8f95daf0ce2b24815ddf62667229ba5dfc0cfee43eb43b2549766170d0f24ae9", "sha256:b723f9d10f7d8259f010b87fa66e924bb4d67927d9dcff4526a755e9ee84fef4",
"sha256:911b4a16dce370657e5b8d8b6ba0fbb50dd5e2b24c4416f4b9e664503d3f0502", "sha256:b99a0347bc6131046c19e056a113daa34d7df99f1f45510161bc78bc8461a470",
"sha256:96117212229905da864794df9ea7bd54987c30a5dcbab3432edc3f344231adae", "sha256:bc0880011b86f81c5353ce4abaeb2472d942ba2320985166a2a3dd4f783563a9",
"sha256:963cbcf296943f7017470d0b705e63e908f32b4f7dbe43f72c22f6fe1bd9ef66", "sha256:be2f40045432e8aa33d9fd5cb43c91b0c61d77d3d8810f88e84e2e46411c27a7",
"sha256:975a118aa019d745f1398613b27fd8789f60a8cea057a00cdc1abedee123ffe6", "sha256:bebb17125373c499beea009cc5bced757bde52bcb3fa1d6335650e6c2d8111d7",
"sha256:9930853d451086c4c084d83a87294bdb0c5bc0fa4105a26c487ac09ea62e565b", "sha256:befac6644c6f304a1b6a7948a04095682849c426cebcc44cb2459aa92d3e1735",
"sha256:99d16862e802e7c50c3b6cdd1bf041b6142335c9c2b426631f731257adfe5a15", "sha256:c1f4880bcb6fb7a8e2ab89128032b3dcf59e1e877ff4493b11c8bf7c3a5b3df2",
"sha256:9ed4a2852b3de7a64884afcc6936db771707943249a060aec8e551c16361d478", "sha256:c3e014f7295b9327fa6f0b3e55a3fd485abac98be145b9597e0cdbb05c44ad07",
"sha256:9f7796959c9c036a115d34696563f75d4a2912d3b97c15c15f2a36bdd5496ce9", "sha256:c427dfcce13a8c814556dfe7c110b8ef61b8fab5fca0d856d4890856807321dc",
"sha256:a04b7a9017b8d0341ebbe77f61b74df1cf1b714f42b671a06f4912dc93d82597", "sha256:c44cf0059633470c6bb415091def546adbeb5dcfa91cc3fcb1ac16593f14e52a",
"sha256:a1b3c4ca3bec8e0ad9d32ce62444c5f3913588124a922629aa7d39357b2adf3f", "sha256:c4e04c12f507942f1ddf215cb3a08c244d24051cdd2ba571060166ce8a92be16",
"sha256:a290a417608f50137bec731d1f22ff3efebac72845530807a8433b2db9358c95", "sha256:c65257899bb8faf6a111297b4ff0066324a6b901318582c0453a01422c3bcd5a",
"sha256:a33f7c5acf44961f29018b13f0b5f5e1589ac0cfdf75a97c9774cf7ec84d09e0", "sha256:c6c48cf5a92244ef3df4161c8625ee1890bb3d931db9a9f3b699e61a037cd58a",
"sha256:a39be79a7c36e9a2e20376261c30deb3cdca86b50f7462ae9ff10a755c6720b9", "sha256:c9bf2bf29854f165a47917b8782b6cf3a7d602971bf454806208d0cbb96f797a",
"sha256:a50a66fa34dd7f9dcdbc7602a1b7bf6f9ab030b4f43e892324193423d9ede180", "sha256:ca4b6298c89b92d6b0a67dfc5f98d68ae92b08101d227263ef2033b9c9a03a72",
"sha256:a5ce1bdee102f7e60c075274df10b892d9ff5183ad6f5f515973eda8903dfe4c", "sha256:cc76ad7453816318d794248fba4032967eaffd992d76e5d1af10ef9d46589770",
"sha256:a763dd33d6e27c9b4db3f8089a5fa39179a8a3cf48ce702b24a857d7c621333c", "sha256:cd7f6bfa2a36fb91b7dec9ddf905716f2ed0c3675d2b63c69b7530c9d211e715",
"sha256:a773199dc42b5d02fcd46c8add552da2c4725ce2caa069527c7e27b5b6089e85", "sha256:d12c45da97b2f31d0233e15f8d68731cfa86264c9f04b2669b9fdf46aaf68e1f",
"sha256:aa3c925502bd0b957a96a5619134bcdc0382ef73cffd40bad218ced3586bcf8d", "sha256:d160173efdad8a57c22e422a034196df3d84753672c497aee2f94bd5b128f8dd",
"sha256:aeb6db2f4ab54ac21a3851d05130a2aa78a6f6a5f14003f9ae3114fb8b210850", "sha256:d2b1ed363a4ef5622dccbf7822f01b51195062c4f382b28c9bd125d046d0324c",
"sha256:af670708e145b048ead87375b899229443f2d0b4af2d1450d7701c74cd932b03", "sha256:d30e7daaf228e3d69cdd8b02c0dd4199cec034c4b93c80109f56f4675a6db957",
"sha256:afa24e5750c9b89ad5a7efef037efe49f4e339f20a94bf678c422c0c71e1207a", "sha256:d3f38373d9b2629dedc559e647010541cc4ec4ad9bea560e2eb1017e6a00d9ef",
"sha256:b02cc1cac9099c0ec72da09593e7fadb1b6cf88a101acc8153592c700d732d80", "sha256:d7e274ac1975e55ebfb8166cce27e13dc99120c1d6ce9e490d7a716b9be9abb5",
"sha256:b37c9ea942395de029be270f0eca8c141eb14e8455941495cd3b6f95bbe465f4", "sha256:d877759842ff9eb16d9c2b8b497953a7d994d4b231c171515f0bf3a2ae185c0c",
"sha256:b3b521e117ab991d6b3b830656f464b354a42dbea2ca16a0e7d93d573f7ab7ff", "sha256:da3dfd2776226e15d3288a3a24c7975f9ee160ba198f2efa66bc28c5ba76d792",
"sha256:b5ad8261f47c2a72d0f676bc40f752db8cfdcab911e970753343836e41d5a9a7", "sha256:db0441e80773d747a1ed9edfb9f75e7acb68ce8627583bbb6f770b7ec49f0064",
"sha256:b9616ea14917d06736339cf36bb9eaf4eb52110a74136b0dc5eff94e92417d22", "sha256:dbbaa147cf28b3e87738c624d390a3a9e2a5dfef4316f4c38b4ecaf3155a3eab",
"sha256:b9a03767c937b621ee267507bc394df97fb2f8f61130f39f2033f3c6c191f124", "sha256:ddc646cec4899a137c134b13818469e4178a251d77f9f4b23229267e3da78cfb",
"sha256:b9ae0008cff25e154ef1e3975a1705d344e844ffdeb34c25b007fd48c876e95d", "sha256:df7cc9584614f495f474a5ded365cf72decbcee4efcdc888d2943f8a794c789e",
"sha256:bdd6412c1f38da7565126b174f4e644f362e317ef0560fac1fb9d0c70900ff4d", "sha256:dfde50ae55e075dcd5801e2c3ea0e749c849ed2cbbee991af0f97f1bdbadb2a6",
"sha256:bfc417e58f277e949ed662d9cd050ddbb00c0dea8a828abaccc93dc357b7a6d1", "sha256:e15e70a3cf5bb519e2448524d689c02ff6bcd4750587a517e2bffee06065bf27",
"sha256:c15b9e37bbca59657e4dcc63ad068c821a4676def15f04742c406748a0a11b9c", "sha256:e3572889fcb87e5ca94add412d8b365dbb7b59773a4362e52caa556e5fd98643",
"sha256:c677849947d523a082be6e0b5c9137f443a54e951a1711ef003ec52910c41ece", "sha256:e39f5e85e1e3d7d84ac2217cd095b3678306c979e991532df47012880e02215d",
"sha256:c9d247fcc33c90f2758f4162693250341e3f38cd094f64390076ef33ad0887f9", "sha256:e501bd27c795105aaba02b5212ecd1bb552ca2ee2ede53e5a8cb74deee0e2052",
"sha256:ca643295bf5441dd38dadf7571ca4b63961820eedbffbe46ceba0893bf226203", "sha256:e62892645f6a214eefb58a42c3ed2501af2e40a797844e0e09ec1e400ce75f3d",
"sha256:ca87f639094c72268e17bc7f57c1225cc38f9e191a489a0134762e3fec402c1a", "sha256:e75eb1734046291c554d9addecca9a8785bdf5d53a64f525569f8549da863dde",
"sha256:cc060bc17b9de27874997d612e37d52f72092f9b59d1e04284a90ed8113cadca", "sha256:e84cff8e8fe71903a6cf873fb3c8731df8bd7c1dac878e7a0fe19d8e2ef39aa9",
"sha256:ccf4a73e07bfbd790443d6b3c1f1447ffda23cc9391e40c035d9b7d3514b57b8", "sha256:ea60cf85b4e5a78b5a41eed3a65abc3839a50d915c6e0f6966cbcf81b85991bd",
"sha256:cf36cadeb9c989f760a13058dbc455e5406ec3d2d247c705c8d4bc6dd1b0fcc6", "sha256:ec3fd30622180cbe2326d48c14a4ab7f98a504b104bdca7dda88b134adad6e31",
"sha256:d47e2bdeba4fb1986af2ba395ce51223f4d460e6e77119439e78f2b592cafade", "sha256:eccc6829035c8b7b391a0aa124fade54932bb937dd1079f2740b9f1bde829226",
"sha256:db78cc5c03b446a43413165aa873e2f408e9fd5ddb45533e7bd3b638bace867c", "sha256:eda67136343db96752e58ef36ac37116f36cba40961e79fd0e9bd858f5a09b38",
"sha256:dbc5029c61f9ebb2d4c247f13584a0ef0e8e49abb13e56460310821aca3ffcaf", "sha256:ef5a99a8d1a5c47b4cf85925d1420fc4ee584c98be8efc548651447b3047242f",
"sha256:ddb319f869d497ef2d3d56319360b61284a9a1d8b3de3bc936748698acfda6be", "sha256:f0795e2be2aa8afd013635f30ffe599cc00f1bbaca2d1d19b6187b4d1c58fb44",
"sha256:e0e4fdeae6c0a9d886749780ec5dcf469e98f27b312efa93008d03eaa2426fd5", "sha256:f31d8c2168bf2a52e4539232392352832c2296e07e0e14b6e06a44da574099ba",
"sha256:e4c5e7edf1e7bcbde3b52058f171a411e2a24a081b3e951d685dfea4c3c383d5", "sha256:f41a4b57cbc128a699e9d716a56c90c7fc76554e680fe2962f49cc4d8688b051",
"sha256:e71c9dba78671d38a549e3b2d52514f50e199f9d7e18ed9b0180adeef0d04130", "sha256:f583a1fb180a123c00064fab1a3bfb9d43e574b6474be1be3f6469e0331e3e2e",
"sha256:e997d22e0d1e08c8752f61675a75d93659f7aa4dbeaee54207f8d877817b4a0c", "sha256:f7c531722e8c3901f6bb303db464cac98ab44ed422c0fd0c762baa4a8d49ffa1",
"sha256:efa5834ba5e6c70b22afdca3894097e5a592d8d483c976359654ba990477799a", "sha256:f8ab90410b2ba5b8276657c66941bcaae556a38be8dd81630a7647e8735f0a20",
"sha256:f2d951002b11962b26afb31f758c18ad39771f287b100fa5adb1d09a47eaaf5b", "sha256:fa05460dc4f57358680b977b4a254d331b24c8beb501319b998625fd6a22654b",
"sha256:f3f96f57cea35ba19fd23a20b38fa0dfa3d87d582507129b8c8e314aa298f59b", "sha256:fbe1ef622748d2edb3dd4fef933b934e90e479f9831dfe31bda3fdc16bf5287f",
"sha256:f738051052abc95dc17f9a4c92044294a263fb7f762efdb13e528d419005c0e4", "sha256:fdb7af369df317527d697c5bb37ab944bb9a17ea1a5e82e47d5c7c638f3ccdd6",
"sha256:f76784355060999c36fa807b59faecb38f5769ae58283d00270835773f95e35b", "sha256:fe1f1f4010244cb07f6a079854a12e1627e4fb9ea99d672f2ceccaf6653ca514",
"sha256:f92462ea3888c99439f58f7561ecd5dd4cf8b8b1b259ccf5376667b8c46ee747", "sha256:fe2493d3f49e314e573022ead4d8c845c9748979b7eb95e815429fe947c4bde2",
"sha256:fefd18b29f3b84a0cdea1d86340219d9871c3b0673a38e722a73a2c39591eaa7" "sha256:ffd112646486a31ea5a45aa1eca0e2cd90b6a12f67e848e50349e324c24cc2e7"
], ],
"version": "==3.6.0" "version": "==3.7.1"
}, },
"bitstring": { "bitstring": {
"hashes": [ "hashes": [
@@ -173,76 +173,93 @@
}, },
"cffi": { "cffi": {
"hashes": [ "hashes": [
"sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8", "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb",
"sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2", "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b",
"sha256:0e2b1fac190ae3ebfe37b979cc1ce69c81f4e4fe5746bb401dca63a9062cdaf1", "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f",
"sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15", "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9",
"sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36", "sha256:0cf2d91ecc3fcc0625c2c530fe004f82c110405f101548512cce44322fa8ac44",
"sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", "sha256:0f6084a0ea23d05d20c3edcda20c3d006f9b6f3fefeac38f59262e10cef47ee2",
"sha256:1d599671f396c4723d016dbddb72fe8e0397082b0a77a4fab8028923bec050e8", "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c",
"sha256:28b16024becceed8c6dfbc75629e27788d8a3f9030691a1dbf9821a128b22c36", "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75",
"sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17", "sha256:1cd13c99ce269b3ed80b417dcd591415d3372bcac067009b6e0f59c7d4015e65",
"sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf", "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e",
"sha256:31000ec67d4221a71bd3f67df918b1f88f676f1c3b535a7eb473255fdc0b83fc", "sha256:1f72fb8906754ac8a2cc3f9f5aaa298070652a0ffae577e0ea9bd480dc3c931a",
"sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3", "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e",
"sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed", "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25",
"sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702", "sha256:2081580ebb843f759b9f617314a24ed5738c51d2aee65d31e02f6f7a2b97707a",
"sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1", "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe",
"sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8", "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b",
"sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903", "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91",
"sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6", "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592",
"sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d", "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187",
"sha256:636062ea65bd0195bc012fea9321aca499c0504409f413dc88af450b57ffd03b", "sha256:2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c",
"sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e", "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1",
"sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be", "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94",
"sha256:6f17be4345073b0a7b8ea599688f692ac3ef23ce28e5df79c04de519dbc4912c", "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba",
"sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683", "sha256:3e837e369566884707ddaf85fc1744b47575005c0a229de3327f8f9a20f4efeb",
"sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9", "sha256:3f4d46d8b35698056ec29bca21546e1551a205058ae1a181d871e278b0b28165",
"sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c", "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529",
"sha256:7596d6620d3fa590f677e9ee430df2958d2d6d6de2feeae5b20e82c00b76fbf8", "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca",
"sha256:78122be759c3f8a014ce010908ae03364d00a1f81ab5c7f4a7a5120607ea56e1", "sha256:4647afc2f90d1ddd33441e5b0e85b16b12ddec4fca55f0d9671fef036ecca27c",
"sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4", "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6",
"sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655", "sha256:53f77cbe57044e88bbd5ed26ac1d0514d2acf0591dd6bb02a3ae37f76811b80c",
"sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67", "sha256:5eda85d6d1879e692d546a078b44251cdd08dd1cfb98dfb77b670c97cee49ea0",
"sha256:9755e4345d1ec879e3849e62222a18c7174d65a6a92d5b346b1863912168b595", "sha256:5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743",
"sha256:98e3969bcff97cae1b2def8ba499ea3d6f31ddfdb7635374834cf89a1a08ecf0", "sha256:61d028e90346df14fedc3d1e5441df818d095f3b87d286825dfcbd6459b7ef63",
"sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65", "sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5",
"sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41", "sha256:6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5",
"sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6", "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4",
"sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401", "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d",
"sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6", "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b",
"sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3", "sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93",
"sha256:b2ab587605f4ba0bf81dc0cb08a41bd1c0a5906bd59243d56bad7668a6fc6c16", "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205",
"sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93", "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27",
"sha256:c03e868a0b3bc35839ba98e74211ed2b05d2119be4e8a0f224fba9384f1fe02e", "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512",
"sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4", "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d",
"sha256:c7eac2ef9b63c79431bc4b25f1cd649d7f061a28808cbc6c47b534bd789ef964", "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c",
"sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c", "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037",
"sha256:ca74b8dbe6e8e8263c0ffd60277de77dcee6c837a3d0881d8c1ead7268c9e576", "sha256:8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26",
"sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0", "sha256:89472c9762729b5ae1ad974b777416bfda4ac5642423fa93bd57a09204712322",
"sha256:cdf5ce3acdfd1661132f2a9c19cac174758dc2352bfe37d98aa7512c6b7178b3", "sha256:8ea985900c5c95ce9db1745f7933eeef5d314f0565b27625d9a10ec9881e1bfb",
"sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662", "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c",
"sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3", "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8",
"sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff", "sha256:9332088d75dc3241c702d852d4671613136d90fa6881da7d770a483fd05248b4",
"sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5", "sha256:94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414",
"sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd", "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9",
"sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f", "sha256:9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664",
"sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5", "sha256:a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9",
"sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14", "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775",
"sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d", "sha256:b18a3ed7d5b3bd8d9ef7a8cb226502c6bf8308df1525e1cc676c3680e7176739",
"sha256:e221cf152cff04059d011ee126477f0d9588303eb57e88923578ace7baad17f9", "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc",
"sha256:e31ae45bc2e29f6b2abd0de1cc3b9d5205aa847cafaecb8af1476a609a2f6eb7", "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062",
"sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382", "sha256:b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe",
"sha256:f1e22e8c4419538cb197e4dd60acc919d7696e5ef98ee4da4e01d3f8cfa4cc5a", "sha256:b882b3df248017dba09d6b16defe9b5c407fe32fc7c65a9c69798e6175601be9",
"sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e", "sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92",
"sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a", "sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5",
"sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4", "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13",
"sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99", "sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d",
"sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87", "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26",
"sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b" "sha256:cb527a79772e5ef98fb1d700678fe031e353e765d1ca2d409c92263c6d43e09f",
"sha256:cf364028c016c03078a23b503f02058f1814320a56ad535686f90565636a9495",
"sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b",
"sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6",
"sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c",
"sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef",
"sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5",
"sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18",
"sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad",
"sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3",
"sha256:de8dad4425a6ca6e4e5e297b27b5c824ecc7581910bf9aee86cb6835e6812aa7",
"sha256:e11e82b744887154b182fd3e7e8512418446501191994dbf9c9fc1f32cc8efd5",
"sha256:e6e73b9e02893c764e7e8d5bb5ce277f1a009cd5243f8228f75f842bf937c534",
"sha256:f73b96c41e3b2adedc34a7356e64c8eb96e03a3782b535e043a986276ce12a49",
"sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2",
"sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5",
"sha256:fc7de24befaeae77ba923797c7c87834c73648a05a4bde34b3b7e5588973a453",
"sha256:fe562eb1a64e67dd297ccc4f5addea2501664954f2692b69a76449ec7913ecbf"
], ],
"markers": "platform_python_implementation != 'PyPy'", "markers": "platform_python_implementation != 'PyPy'",
"version": "==1.17.1" "version": "==2.0.0"
}, },
"click": { "click": {
"hashes": [ "hashes": [
@@ -254,46 +271,46 @@
}, },
"cryptography": { "cryptography": {
"hashes": [ "hashes": [
"sha256:00e8724bdad672d75e6f069b27970883179bd472cd24a63f6e620ca7e41cc0c5", "sha256:06ce84dc14df0bf6ea84666f958e6080cdb6fe1231be2a51f3fc1267d9f3fb34",
"sha256:048e7ad9e08cf4c0ab07ff7f36cc3115924e22e2266e034450a890d9e312dd74", "sha256:16ede8a4f7929b4b7ff3642eba2bf79aa1d71f24ab6ee443935c0d269b6bc513",
"sha256:0d9ef57b6768d9fa58e92f4947cea96ade1233c0e236db22ba44748ffedca394", "sha256:18fcf70f243fe07252dcb1b268a687f2358025ce32f9f88028ca5c364b123ef5",
"sha256:18f878a34b90d688982e43f4b700408b478102dd58b3e39de21b5ebf6509c301", "sha256:1993a1bb7e4eccfb922b6cd414f072e08ff5816702a0bdb8941c247a6b1b287c",
"sha256:1b7fa6a1c1188c7ee32e47590d16a5a0646270921f8020efc9a511648e1b2e08", "sha256:1f3d56f73595376f4244646dd5c5870c14c196949807be39e79e7bd9bac3da63",
"sha256:20ae4906a13716139d6d762ceb3e0e7e110f7955f3bc3876e3a07f5daadec5f3", "sha256:258e0dff86d1d891169b5af222d362468a9570e2532923088658aa866eb11130",
"sha256:20d15aed3ee522faac1a39fbfdfee25d17b1284bafd808e1640a74846d7c4d1b", "sha256:2f641b64acc00811da98df63df7d59fd4706c0df449da71cb7ac39a0732b40ae",
"sha256:2384f2ab18d9be88a6e4f8972923405e2dbb8d3e16c6b43f15ca491d7831bd18", "sha256:3808e6b2e5f0b46d981c24d79648e5c25c35e59902ea4391a0dcb3e667bf7443",
"sha256:275ba5cc0d9e320cd70f8e7b96d9e59903c815ca579ab96c1e37278d231fc402", "sha256:3994c809c17fc570c2af12c9b840d7cea85a9fd3e5c0e0491f4fa3c029216d59",
"sha256:2dac5ec199038b8e131365e2324c03d20e97fe214af051d20c49db129844e8b3", "sha256:3be4f21c6245930688bd9e162829480de027f8bf962ede33d4f8ba7d67a00cee",
"sha256:31a2b9a10530a1cb04ffd6aa1cd4d3be9ed49f7d77a4dafe198f3b382f41545c", "sha256:465ccac9d70115cd4de7186e60cfe989de73f7bb23e8a7aa45af18f7412e75bf",
"sha256:3436128a60a5e5490603ab2adbabc8763613f638513ffa7d311c900a8349a2a0", "sha256:48c41a44ef8b8c2e80ca4527ee81daa4c527df3ecbc9423c41a420a9559d0e27",
"sha256:3b5bf5267e98661b9b888a9250d05b063220dfa917a8203744454573c7eb79db", "sha256:4a862753b36620af6fc54209264f92c716367f2f0ff4624952276a6bbd18cbde",
"sha256:3de77e4df42ac8d4e4d6cdb342d989803ad37707cf8f3fbf7b088c9cbdd46427", "sha256:4b1654dfc64ea479c242508eb8c724044f1e964a47d1d1cacc5132292d851971",
"sha256:44647c5d796f5fc042bbc6d61307d04bf29bccb74d188f18051b635f20a9c75f", "sha256:4bd3e5c4b9682bc112d634f2c6ccc6736ed3635fc3319ac2bb11d768cc5a00d8",
"sha256:550ae02148206beb722cfe4ef0933f9352bab26b087af00e48fdfb9ade35c5b3", "sha256:577470e39e60a6cd7780793202e63536026d9b8641de011ed9d8174da9ca5339",
"sha256:599c8d7df950aa68baa7e98f7b73f4f414c9f02d0e8104a30c0182a07732638b", "sha256:67285f8a611b0ebc0857ced2081e30302909f571a46bfa7a3cc0ad303fe015c6",
"sha256:5b64e668fc3528e77efa51ca70fadcd6610e8ab231e3e06ae2bab3b31c2b8ed9", "sha256:7285a89df4900ed3bfaad5679b1e668cb4b38a8de1ccbfc84b05f34512da0a90",
"sha256:5bd6020c80c5b2b2242d6c48487d7b85700f5e0038e67b29d706f98440d66eb5", "sha256:81823935e2f8d476707e85a78a405953a03ef7b7b4f55f93f7c2d9680e5e0691",
"sha256:5c966c732cf6e4a276ce83b6e4c729edda2df6929083a952cc7da973c539c719", "sha256:8978132287a9d3ad6b54fcd1e08548033cc09dc6aacacb6c004c73c3eb5d3ac3",
"sha256:629127cfdcdc6806dfe234734d7cb8ac54edaf572148274fa377a7d3405b0043", "sha256:a20e442e917889d1a6b3c570c9e3fa2fdc398c20868abcea268ea33c024c4083",
"sha256:705bb7c7ecc3d79a50f236adda12ca331c8e7ecfbea51edd931ce5a7a7c4f012", "sha256:a24ee598d10befaec178efdff6054bc4d7e883f615bfbcd08126a0f4931c83a6",
"sha256:780c40fb751c7d2b0c6786ceee6b6f871e86e8718a8ff4bc35073ac353c7cd02", "sha256:b04f85ac3a90c227b6e5890acb0edbaf3140938dbecf07bff618bf3638578cf1",
"sha256:7a3085d1b319d35296176af31c90338eeb2ddac8104661df79f80e1d9787b8b2", "sha256:b6a0e535baec27b528cb07a119f321ac024592388c5681a5ced167ae98e9fff3",
"sha256:826b46dae41a1155a0c0e66fafba43d0ede1dc16570b95e40c4d83bfcf0a451d", "sha256:bef32a5e327bd8e5af915d3416ffefdbe65ed975b646b3805be81b23580b57b8",
"sha256:833dc32dfc1e39b7376a87b9a6a4288a10aae234631268486558920029b086ec", "sha256:bfb4c801f65dd61cedfc61a83732327fafbac55a47282e6f26f073ca7a41c3b2",
"sha256:cc4d66f5dc4dc37b89cfef1bd5044387f7a1f6f0abb490815628501909332d5d", "sha256:c13b1e3afd29a5b3b2656257f14669ca8fa8d7956d509926f0b130b600b50ab7",
"sha256:d063341378d7ee9c91f9d23b431a3502fc8bfacd54ef0a27baa72a0843b29159", "sha256:c987dad82e8c65ebc985f5dae5e74a3beda9d0a2a4daf8a1115f3772b59e5141",
"sha256:e2a21a8eda2d86bb604934b6b37691585bd095c1f788530c1fcefc53a82b3453", "sha256:ce7a453385e4c4693985b4a4a3533e041558851eae061a58a5405363b098fcd3",
"sha256:e40b80ecf35ec265c452eea0ba94c9587ca763e739b8e559c128d23bff7ebbbf", "sha256:d0c5c6bac22b177bf8da7435d9d27a6834ee130309749d162b26c3105c0795a9",
"sha256:e5b3dda1b00fb41da3af4c5ef3f922a200e33ee5ba0f0bc9ecf0b0c173958385", "sha256:d97cf502abe2ab9eff8bd5e4aca274da8d06dd3ef08b759a8d6143f4ad65d4b4",
"sha256:ea3c42f2016a5bbf71825537c2ad753f2870191134933196bee408aac397b3d9", "sha256:dad43797959a74103cb59c5dac71409f9c27d34c8a05921341fb64ea8ccb1dd4",
"sha256:eccddbd986e43014263eda489abbddfbc287af5cddfd690477993dbb31e31016", "sha256:dd342f085542f6eb894ca00ef70236ea46070c8a13824c6bde0dfdcd36065b9b",
"sha256:ee411a1b977f40bd075392c80c10b58025ee5c6b47a822a33c1198598a7a5f05", "sha256:de58755d723e86175756f463f2f0bddd45cc36fbd62601228a3f8761c9f58252",
"sha256:f4028f29a9f38a2025abedb2e409973709c660d44319c61762202206ed577c42", "sha256:f3df7b3d0f91b88b2106031fd995802a2e9ae13e02c36c1fc075b43f420f3a17",
"sha256:f68f833a9d445cc49f01097d95c83a850795921b3f7cc6488731e69bde3288da", "sha256:f5414a788ecc6ee6bc58560e85ca624258a55ca434884445440a810796ea0e0b",
"sha256:fc022c1fa5acff6def2fc6d7819bbbd31ccddfe67d075331a65d9cfb28a20983" "sha256:fa26fa54c0a9384c27fcdc905a2fb7d60ac6e47d14bc2692145f2b3b1e2cfdbd"
], ],
"markers": "python_version >= '3.7' and python_full_version not in '3.9.0, 3.9.1'", "markers": "python_version >= '3.7' and python_full_version not in '3.9.0, 3.9.1'",
"version": "==45.0.6" "version": "==45.0.7"
}, },
"esptool": { "esptool": {
"hashes": [ "hashes": [
@@ -303,6 +320,14 @@
"markers": "python_version >= '3.10'", "markers": "python_version >= '3.10'",
"version": "==5.0.2" "version": "==5.0.2"
}, },
"h11": {
"hashes": [
"sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1",
"sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86"
],
"markers": "python_version >= '3.8'",
"version": "==0.16.0"
},
"idna": { "idna": {
"hashes": [ "hashes": [
"sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9",
@@ -320,11 +345,11 @@
}, },
"markdown-it-py": { "markdown-it-py": {
"hashes": [ "hashes": [
"sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147",
"sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb" "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3"
], ],
"markers": "python_version >= '3.8'", "markers": "python_version >= '3.10'",
"version": "==3.0.0" "version": "==4.0.0"
}, },
"mdurl": { "mdurl": {
"hashes": [ "hashes": [
@@ -345,19 +370,19 @@
}, },
"platformdirs": { "platformdirs": {
"hashes": [ "hashes": [
"sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc", "sha256:abd01743f24e5287cd7a5db3752faf1a2d65353f38ec26d98e25a6db65958c85",
"sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4" "sha256:ca753cf4d81dc309bc67b0ea38fd15dc97bc30ce419a7f58d13eb3bf14c4febf"
], ],
"markers": "python_version >= '3.9'", "markers": "python_version >= '3.9'",
"version": "==4.3.8" "version": "==4.4.0"
}, },
"pycparser": { "pycparser": {
"hashes": [ "hashes": [
"sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6", "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2",
"sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc" "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934"
], ],
"markers": "python_version >= '3.8'", "markers": "implementation_name != 'PyPy'",
"version": "==2.22" "version": "==2.23"
}, },
"pygments": { "pygments": {
"hashes": [ "hashes": [
@@ -467,11 +492,20 @@
}, },
"typing-extensions": { "typing-extensions": {
"hashes": [ "hashes": [
"sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36", "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466",
"sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76" "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"
], ],
"markers": "python_version < '3.13'",
"version": "==4.15.0"
},
"uvicorn": {
"hashes": [
"sha256:197535216b25ff9b785e29a0b79199f55222193d47f820816e7da751e9bc8d4a",
"sha256:bc662f087f7cf2ce11a1d7fd70b90c9f98ef2e2831556dd078d131b96cc94a01"
],
"index": "pypi",
"markers": "python_version >= '3.9'", "markers": "python_version >= '3.9'",
"version": "==4.14.1" "version": "==0.35.0"
}, },
"watchfiles": { "watchfiles": {
"hashes": [ "hashes": [

58
configure_bar.py Normal file
View File

@@ -0,0 +1,58 @@
#!/usr/bin/env python3
"""
LED Bar Configuration Script
Updates the settings.json file for each LED bar with its unique name
"""
import json
import os
# LED Bar names/IDs
LED_BAR_NAMES = ["100", "101", "102", "103", "104", "105", "106", "107"]
def update_bar_settings(bar_name, settings_file="settings.json"):
"""Update the settings.json file with the bar name"""
if not os.path.exists(settings_file):
print(f"Error: {settings_file} not found")
return False
# Read current settings
with open(settings_file, 'r') as f:
settings = json.load(f)
# Update the name
settings["name"] = bar_name
# Write back to file
with open(settings_file, 'w') as f:
json.dump(settings, f, indent=4)
print(f"Updated {settings_file} with name: {bar_name}")
return True
def main():
print("LED Bar Configuration Script")
print("=" * 40)
print("Available bar names:", LED_BAR_NAMES)
print()
while True:
print("Enter bar name to configure (or 'quit' to exit):")
bar_name = input("> ").strip()
if bar_name.lower() == 'quit':
break
if bar_name not in LED_BAR_NAMES:
print(f"Invalid bar name. Must be one of: {LED_BAR_NAMES}")
continue
if update_bar_settings(bar_name):
print(f"Successfully configured LED bar as '{bar_name}'")
else:
print("Failed to update settings")
print()
if __name__ == "__main__":
main()

2
dev.py
View File

@@ -28,8 +28,6 @@ for cmd in sys.argv[1:]:
if ser.in_waiting > 0: # Check if there is data in the buffer if ser.in_waiting > 0: # Check if there is data in the buffer
data = ser.readline().decode('utf-8').strip() # Read and decode the data data = ser.readline().decode('utf-8').strip() # Read and decode the data
print(data) print(data)
case "clean":
subprocess.call(["mpremote", "connect", port, "fs", "rm", ":/settings.json"])

165
edit_settings.py Normal file
View File

@@ -0,0 +1,165 @@
#!/usr/bin/env python3
"""
Settings editor - copy from device, edit locally, upload back
Run with: python3 edit_settings.py
"""
import json
import subprocess
import os
import tempfile
def copy_settings_from_device():
"""Copy settings.json from the MicroPython device"""
print("Copying settings from device...")
try:
# Use mpremote to copy settings.json to local temp file
result = subprocess.run(['mpremote', 'cp', ':/settings.json', 'settings.json'],
capture_output=True, text=True)
if result.returncode == 0:
print("Settings copied successfully")
return True
else:
print(f"Failed to copy settings: {result.stderr}")
return False
except Exception as e:
print(f"Error copying settings: {e}")
return False
def load_local_settings():
"""Load settings from local file"""
try:
with open('settings.json', 'r') as f:
return json.load(f)
except FileNotFoundError:
print("No local settings file found")
return {}
except Exception as e:
print(f"Error loading settings: {e}")
return {}
def save_local_settings(settings):
"""Save settings to local file"""
try:
with open('settings.json', 'w') as f:
json.dump(settings, f, indent=2)
print("Settings saved locally")
return True
except Exception as e:
print(f"Error saving settings: {e}")
return False
def upload_settings_to_device():
"""Upload settings.json to the MicroPython device"""
print("Uploading settings to device...")
try:
result = subprocess.run(['mpremote', 'cp', 'settings.json', ':/settings.json'],
capture_output=True, text=True)
if result.returncode == 0:
print("Settings uploaded successfully")
return True
else:
print(f"Failed to upload settings: {result.stderr}")
return False
except Exception as e:
print(f"Error uploading settings: {e}")
return False
def edit_settings_interactive(settings):
"""Interactive settings editor"""
print("\nLED Bar Settings Editor")
print("=======================")
# Display current settings
current_pin = settings.get("led_pin", 10)
current_leds = settings.get("num_leds", 119)
current_name = settings.get("name", "104")
current_color_order = settings.get("color_order", "rgb")
print(f"\nCurrent settings:")
print(f" 1. LED Pin: {current_pin}")
print(f" 2. Number of LEDs: {current_leds}")
print(f" 3. Device Name: {current_name}")
print(f" 4. Color Order: {current_color_order}")
print(f"\nEnter new values (press Enter to keep current):")
# LED Pin
new_pin = input(f"LED Pin [{current_pin}]: ").strip()
if new_pin:
try:
settings["led_pin"] = int(new_pin)
except ValueError:
print("Invalid pin number, keeping current value.")
# Number of LEDs
new_leds = input(f"Number of LEDs [{current_leds}]: ").strip()
if new_leds:
try:
settings["num_leds"] = int(new_leds)
except ValueError:
print("Invalid LED count, keeping current value.")
# Device Name
new_name = input(f"Device Name [{current_name}]: ").strip()
if new_name:
settings["name"] = new_name
# Color Order
print(f"Color Order options: rgb, rbg")
new_color_order = input(f"Color Order [{current_color_order}]: ").strip().lower()
if new_color_order in ['rgb', 'rbg']:
settings["color_order"] = new_color_order
elif new_color_order:
print("Invalid color order, keeping current value.")
return settings
def main():
print("LED Bar Settings Editor")
print("=======================")
# Step 1: Copy settings from device
if not copy_settings_from_device():
print("Creating default settings...")
default_settings = {
"led_pin": 10,
"num_leds": 119,
"color_order": "rgb",
"name": "104"
}
save_local_settings(default_settings)
# Step 2: Load and edit settings
settings = load_local_settings()
if not settings:
print("No settings to edit")
return
# Step 3: Interactive editing
edited_settings = edit_settings_interactive(settings.copy())
# Step 4: Save locally
if edited_settings != settings:
save_local_settings(edited_settings)
# Step 5: Upload to device
upload_choice = input("\nUpload settings to device? (y/n): ").strip().lower()
if upload_choice in ['y', 'yes']:
if upload_settings_to_device():
print("\nSettings updated successfully!")
print("Restart the device to apply changes.")
else:
print("\nFailed to upload settings.")
else:
print("\nSettings saved locally but not uploaded.")
else:
print("\nNo changes made.")
if __name__ == "__main__":
main()

431
patterns.py Normal file
View File

@@ -0,0 +1,431 @@
import utime
import random
from patterns_base import PatternBase # Import PatternBase
class Patterns(PatternBase): # Inherit from PatternBase
def __init__(self, pin, num_leds, color1=(0,0,0), color2=(0,0,0), brightness=127, selected="rainbow_cycle", delay=100):
super().__init__(pin, num_leds, color1, color2, brightness, selected, delay) # Call parent constructor
# Pattern-specific initializations
self.on_width = 1 # Default on width
self.off_width = 2 # Default off width (so total segment is 3, matching original behavior)
self.n1 = 0 # Default start of fill range
self.n2 = self.num_leds - 1 # Default end of fill range
self.oneshot = False # New: One-shot flag for patterns like fill_range
self.patterns = {
"off": self.off,
"on" : self.on,
"color_wipe": self.color_wipe,
"rainbow_cycle": self.rainbow_cycle,
"theater_chase": self.theater_chase,
"blink": self.blink,
"color_transition": self.color_transition, # Added new pattern
"flicker": self.flicker,
"scanner": self.scanner, # New: Single direction scanner
"bidirectional_scanner": self.bidirectional_scanner, # New: Bidirectional scanner
"fill_range": self.fill_range, # New: Fill from n1 to n2
"n_chase": self.n_chase, # New: N1 on, N2 off repeating chase
"alternating": self.alternating, # New: N1 on/off, N2 off/on alternating chase
"external": None,
"pulse": self.pulse
}
# Beat-related functionality removed
# self.selected is already initialized in PatternBase, but we need to ensure it uses our patterns dict
# self.selected = selected # Handled by PatternBase
# Ensure colors list always starts with at least two for robust transition handling
# self.colors handled by PatternBase
# Transition attributes handled by PatternBase
# Scanner attributes handled by PatternBase
# self.run handled by PatternBase
def set_on_width(self, on_width):
self.on_width = on_width
def set_off_width(self, off_width):
self.off_width = off_width
def set_on_off_width(self, on_width, off_width):
self.on_width = on_width
self.off_width = off_width
self.sync()
def set_fill_range(self, n1, n2):
self.n1 = n1
self.n2 = n2
self.sync()
def set_oneshot(self, oneshot_value):
self.oneshot = oneshot_value
if self.oneshot: # Reset pattern step if enabling one-shot
self.pattern_step = 0
self.sync()
def select(self, pattern):
if pattern in self.patterns:
super().select(pattern) # Use parent select to set self.selected and self.transition_step
self.run = True # Set run flag
if pattern == "color_transition":
if len(self.colors) < 2:
print("Warning: 'color_transition' requires at least two colors. Switching to 'on'.")
self.selected = "on" # Fallback if not enough colors
self.sync() # Re-sync for the new pattern
else:
self.transition_step = 0
self.current_color_idx = 0 # Start from the first color in the list
self.current_color = self.colors[self.current_color_idx]
self.hold_start_time = utime.ticks_ms() # Reset hold timer
self.transition_duration = self.delay * 50 # Initialize transition duration
self.hold_duration = self.delay * 10 # Initialize hold duration
return True
return False
def off(self):
self.fill((0, 0, 0))
return self.delay
def on(self):
self.fill(self.apply_brightness(self.colors[0]))
return self.delay
def color_wipe(self):
color = self.apply_brightness(self.colors[0])
current_time = utime.ticks_ms()
if self.pattern_step < self.num_leds:
for i in range(self.num_leds):
self.n[i] = (0, 0, 0)
self.n[self.pattern_step] = self.apply_brightness(color)
self.n.write()
self.pattern_step += 1
else:
self.pattern_step = 0
self.last_update = current_time
return self.delay
def rainbow_cycle(self):
current_time = utime.ticks_ms()
def wheel(pos):
if pos < 85:
return (pos * 3, 255 - pos * 3, 0)
elif pos < 170:
pos -= 85
return (255 - pos * 3, 0, pos * 3)
else:
pos -= 170
return (0, pos * 3, 255 - pos * 3)
# Rainbow travels along the length - each LED position gets a different color
for i in range(self.num_leds):
# Map LED position along strip to rainbow color
# Full 256 color range distributed along the strip length
rc_index = ((i * 256 // self.num_leds) + self.pattern_step) % 256
self.n[i] = self.apply_brightness(wheel(rc_index & 255))
self.n.write()
self.pattern_step = (self.pattern_step + 1) % 256
self.last_update = current_time
return max(1, int(self.delay // 5))
def theater_chase(self):
current_time = utime.ticks_ms()
segment_length = self.on_width + self.off_width
for i in range(self.num_leds):
if (i + self.pattern_step) % segment_length < self.on_width:
self.n[i] = self.apply_brightness(self.colors[0])
else:
self.n[i] = (0, 0, 0)
self.n.write()
self.pattern_step = (self.pattern_step + 1) % segment_length
self.last_update = current_time
return self.delay
def blink(self):
current_time = utime.ticks_ms()
if self.pattern_step % 2 == 0:
self.fill(self.apply_brightness(self.colors[0]))
else:
self.fill((0, 0, 0))
self.pattern_step = (self.pattern_step + 1) % 2
self.last_update = current_time
return self.delay
def color_transition(self):
current_time = utime.ticks_ms()
# Check for hold duration first
if utime.ticks_diff(current_time, self.hold_start_time) < self.hold_duration:
# Still in hold phase, just display the current solid color
self.fill(self.apply_brightness(self.current_color))
self.last_update = current_time # Keep updating last_update to avoid skipping frames
return self.delay
# If hold duration is over, proceed with transition
if utime.ticks_diff(current_time, self.last_update) >= self.delay:
num_colors = len(self.colors)
if num_colors < 2:
# Should not happen if select handles it, but as a safeguard
self.select("on")
return self.delay
from_color = self.colors[self.current_color_idx]
to_color_idx = (self.current_color_idx + 1) % num_colors
to_color = self.colors[to_color_idx]
# Calculate interpolation factor (0.0 to 1.0)
# transition_step goes from 0 to transition_duration - 1
if self.transition_duration > 0:
interp_factor = self.transition_step / self.transition_duration
else:
interp_factor = 1.0 # Immediately transition if duration is zero
# Interpolate each color component
r = int(from_color[0] + (to_color[0] - from_color[0]) * interp_factor)
g = int(from_color[1] + (to_color[1] - from_color[1]) * interp_factor)
b = int(from_color[2] + (to_color[2] - from_color[2]) * interp_factor)
self.current_color = (r, g, b)
self.fill(self.apply_brightness(self.current_color))
self.transition_step += self.delay # Advance the transition step by the delay
if self.transition_step >= self.transition_duration:
# Transition complete, move to the next color and reset for hold phase
self.current_color_idx = to_color_idx
self.current_color = self.colors[self.current_color_idx] # Ensure current_color is the exact target color
self.transition_step = 0 # Reset transition progress
self.hold_start_time = current_time # Start hold phase for the new color
self.last_update = current_time
return self.delay
def flicker(self):
current_time = utime.ticks_ms()
base_color = self.colors[0]
# Increase the range for flicker_brightness_offset
# Changed from self.brightness // 4 to self.brightness // 2 (or even self.brightness for max intensity)
flicker_brightness_offset = random.randint(-int(self.brightness // 1.5), int(self.brightness // 1.5))
flicker_brightness = max(0, min(255, self.brightness + flicker_brightness_offset))
flicker_color = self.apply_brightness(base_color, brightness_override=flicker_brightness)
self.fill(flicker_color)
self.last_update = current_time
return max(1, int(self.delay // 5))
def scanner(self):
"""
Mimics a 'Knight Rider' style scanner, moving in one direction.
"""
current_time = utime.ticks_ms()
self.fill((0, 0, 0)) # Clear all LEDs
# Calculate the head and tail position
head_pos = self.pattern_step
color = self.apply_brightness(self.colors[0])
# Draw the head
if 0 <= head_pos < self.num_leds:
self.n[head_pos] = color
# Draw the trailing pixels with decreasing brightness
for i in range(1, self.scanner_tail_length + 1):
tail_pos = head_pos - i
if 0 <= tail_pos < self.num_leds:
# Calculate fading color for tail
# Example: linear fade from full brightness to off
fade_factor = 1.0 - (i / (self.scanner_tail_length + 1))
faded_color = tuple(int(c * fade_factor) for c in color)
self.n[tail_pos] = faded_color
self.n.write()
self.pattern_step += 1
if self.pattern_step >= self.num_leds + self.scanner_tail_length:
self.pattern_step = 0 # Reset to start
self.last_update = current_time
return self.delay
def bidirectional_scanner(self):
"""
Mimics a 'Knight Rider' style scanner, moving back and forth.
"""
current_time = utime.ticks_ms()
self.fill((0, 0, 0)) # Clear all LEDs
color = self.apply_brightness(self.colors[0])
# Calculate the head position based on direction
head_pos = self.pattern_step
# Draw the head
if 0 <= head_pos < self.num_leds:
self.n[head_pos] = color
# Draw the trailing pixels with decreasing brightness
for i in range(1, self.scanner_tail_length + 1):
tail_pos = head_pos - (i * self.scanner_direction)
if 0 <= tail_pos < self.num_leds:
fade_factor = 1.0 - (i / (self.scanner_tail_length + 1))
faded_color = tuple(int(c * fade_factor) for c in color)
self.n[tail_pos] = faded_color
self.n.write()
self.pattern_step += self.scanner_direction
# Change direction if boundaries are reached
if self.scanner_direction == 1 and self.pattern_step >= self.num_leds:
self.scanner_direction = -1
self.pattern_step = self.num_leds - 1 # Start moving back from the last LED
elif self.scanner_direction == -1 and self.pattern_step < 0:
self.scanner_direction = 1
self.pattern_step = 0 # Start moving forward from the first LED
self.last_update = current_time
return self.delay
def fill_range(self):
"""
Fills a range of LEDs from n1 to n2 with a solid color.
If self.oneshot is True, it fills once and then turns off the LEDs.
"""
current_time = utime.ticks_ms()
if self.oneshot and self.pattern_step >= 1:
self.fill((0, 0, 0)) # Turn off LEDs if one-shot already happened
else:
color = self.apply_brightness(self.colors[0])
for i in range(self.n1, self.n2 + 1):
self.n[i] = color
self.n.write()
self.last_update = current_time
return self.delay
self.last_update = current_time
return self.delay
def n_chase(self):
"""
A theater chase pattern using n1 for on-width and n2 for off-width.
"""
current_time = utime.ticks_ms()
segment_length = self.n1 + self.n2
if segment_length == 0: # Avoid division by zero
self.fill((0,0,0))
self.n.write()
self.last_update = current_time
return self.delay
for i in range(self.num_leds):
if (i + self.pattern_step) % segment_length < self.n1:
self.n[i] = self.apply_brightness(self.colors[0])
else:
self.n[i] = (0, 0, 0)
self.n.write()
self.pattern_step = (self.pattern_step + 1) % segment_length
self.last_update = current_time
return self.delay
def alternating(self):
"""
An alternating pattern where n1 LEDs are ON/OFF and n2 LEDs are OFF/ON globally, without moving.
"""
current_time = utime.ticks_ms()
total_segment_length = self.n1 + self.n2
if total_segment_length == 0:
self.fill((0,0,0))
self.n.write()
self.last_update = current_time
return self.delay
# current_phase will alternate between 0 and 1
current_phase = self.pattern_step % 2
for i in range(self.num_leds):
# Position within a single repeating segment (n1 + n2)
pos_in_segment = i % total_segment_length
if current_phase == 0: # State 0: n1 ON, n2 OFF
if pos_in_segment < self.n1:
self.n[i] = self.apply_brightness(self.colors[0]) # n1 is ON
else:
self.n[i] = (0, 0, 0) # n2 is OFF
else: # State 1: n1 OFF, n2 ON
if pos_in_segment < self.n1:
self.n[i] = (0, 0, 0) # n1 is OFF
else:
self.n[i] = self.apply_brightness(self.colors[0]) # n2 is ON
self.n.write()
self.pattern_step = (self.pattern_step + 1) % 2 # Toggle between 0 and 1
self.last_update = current_time
return self.delay * 2
def pulse(self):
if self.pattern_step == 0:
self.fill(self.apply_brightness(self.colors[0]))
self.pattern_step = 1
self.last_update = utime.ticks_ms()
if utime.ticks_diff(utime.ticks_ms(), self.last_update) > self.delay:
self.fill((0, 0, 0))
print(utime.ticks_diff(utime.ticks_ms(), self.last_update))
self.run = False
return self.delay
if __name__ == "__main__":
import time
from machine import WDT
wdt = WDT(timeout=2000) # Enable watchdog with a 2 second timeout
p = Patterns(pin=4, num_leds=60, color1=(255,0,0), color2=(0,0,255), brightness=127, selected="off", delay=100)
print(p.colors, p.brightness)
tests = [
("off", {"duration_ms": 500}),
("on", {"duration_ms": 500}),
("color_wipe", {"delay": 200, "duration_ms": 1000}),
("rainbow_cycle", {"delay": 100, "duration_ms": 2500}),
("theater_chase", {"on_width": 3, "off_width": 3, "delay": 1000, "duration_ms": 2500}),
("blink", {"delay": 500, "duration_ms": 2000}),
("color_transition", {"delay": 150, "colors": [(255,0,0),(0,255,0),(0,0,255)], "duration_ms": 5000}),
("flicker", {"delay": 100, "duration_ms": 2000}),
("scanner", {"delay": 150, "duration_ms": 2500}),
("bidirectional_scanner", {"delay": 50, "duration_ms": 2500}),
("fill_range", {"n1": 10, "n2": 20, "delay": 500, "duration_ms": 2000}),
("n_chase", {"n1": 5, "n2": 5, "delay": 2000, "duration_ms": 2500}),
("alternating", {"n1": 5, "n2": 5, "delay": 500, "duration_ms": 2500}),
("pulse", {"delay": 100, "duration_ms": 700}),
]
print("\n--- Running pattern self-test ---")
for name, cfg in tests:
print(f"\nPattern: {name}")
# apply simple config helpers
if "delay" in cfg:
p.set_delay(cfg["delay"])
if "on_width" in cfg:
p.set_on_width(cfg["on_width"])
if "off_width" in cfg:
p.set_off_width(cfg["off_width"])
if "n1" in cfg and "n2" in cfg:
p.set_fill_range(cfg["n1"], cfg["n2"])
if "colors" in cfg:
p.set_colors(cfg["colors"])
p.select(name)
# run per configured duration using absolute-scheduled tick(next_due_ms)
start = utime.ticks_ms()
duration_ms = cfg["duration_ms"]
delay = cfg.get("delay", 0)
next_due = utime.ticks_ms() - 1 # force immediate first call
while utime.ticks_diff(utime.ticks_ms(), start) < duration_ms:
delay = p.tick(delay)
wdt.feed()
print("\n--- Test routine finished ---")

View File

@@ -6,4 +6,4 @@ s = Settings()
name = s.get('name', 'led') name = s.get('name', 'led')
password = s.get("ap_password", "") password = s.get("ap_password", "")
wifi.ap(name, password) # wifi.ap(name, password)

View File

@@ -1,5 +1,5 @@
import asyncio
import aioespnow import patterns
from settings import Settings from settings import Settings
from web import web from web import web
from patterns import Patterns from patterns import Patterns
@@ -10,42 +10,76 @@ import time
import wifi import wifi
import json import json
from p2p import p2p from p2p import p2p
import espnow
import network
async def main(): def main():
settings = Settings() settings = Settings()
patterns = Patterns(settings["led_pin"], settings["num_leds"], selected=settings["pattern"])
if settings["color_order"] == "rbg":
color_order = (1, 5, 3)
print("RBG")
if settings["color_order"] == "grb":
color_order = (3, 1, 5)
else: color_order = (1, 3, 5)
patterns.colors = [(8,0,0)]
async def system():
while True:
gc.collect()
for i in range(60):
wdt.feed()
await asyncio.sleep(1)
w = web(settings, patterns)
print(settings) print(settings)
# start the server in a bacakground task
print("Starting") patterns = Patterns(settings["led_pin"], settings["num_leds"], selected="off")
server = asyncio.create_task(w.start_server(host="0.0.0.0", port=80))
sta_if = network.WLAN(network.STA_IF)
sta_if.active(True)
e = espnow.ESPNow()
e.config(rxbuf=1024)
e.active(True)
# Increase buffer size for 8-bar payloads (default 526 bytes might be too small) # Set to 1KB to handle larger multi-bar payloads
wdt = machine.WDT(timeout=10000) wdt = machine.WDT(timeout=10000)
wdt.feed() wdt.feed()
while True:
# advance pattern based on its own returned schedule
# due = patterns.tick(due)
wdt.feed()
asyncio.create_task(p2p(settings, patterns)) # Drain all pending packets and only process the latest
asyncio.create_task(system()) last_msg = None
patterns.select(settings["pattern"]) while True:
await patterns.run() host, msg = e.recv(0)
if not msg:
break
last_msg = msg
if last_msg:
try:
data = json.loads(last_msg)
print(data)
defaults = data.get("d", {})
bar = data.get(settings.get("name"), {})
# Check message type
message_type = defaults.get("t", "b") # Default to beat if not specified
# Always update parameters from message
param_mapping = {
"br": "brightness",
"dl": "delay",
"cl": "colors",
"n1": "n1",
"n2": "n2",
"n3": "n3",
"n4": "n4",
"s": "step"
}
# Iterate through values in bar and update parameters
print("Bar values:")
for key, value in bar.items():
print(f" {key}: {value}")
# Update parameter if it exists in param_mapping
if key in param_mapping:
attr_name = param_mapping[key]
current_value = getattr(patterns, attr_name)
new_value = bar.get(key, defaults.get(key, current_value))
setattr(patterns, attr_name, new_value)
# Print received parameters
except Exception as ex:
print(f"Failed to load espnow data {last_msg}: {ex}")
continue
main()
# cleanup before ending the application
await server
asyncio.run(main())

View File

@@ -1,16 +0,0 @@
import asyncio
import aioespnow
import json
async def p2p(settings, patterns):
e = aioespnow.AIOESPNow() # Returns AIOESPNow enhanced with async support
e.active(True)
async for mac, msg in e:
try:
data = json.loads(msg)
except:
print(f"Failed to load espnow data {msg}")
continue
if "names" not in data or settings.get("name") in data.get("names", []):
await settings.set_settings(data.get("settings", {}), patterns, data.get("save", False))

File diff suppressed because it is too large Load Diff

View File

@@ -1,41 +1,19 @@
from machine import Pin from machine import Pin
from neopixel import NeoPixel from neopixel import NeoPixel
import utime import utime
import random
import _thread
import asyncio
import json
# Short-key parameter mapping for convenience setters
param_mapping = {
"pt": "selected",
"pa": "selected",
"cl": "colors",
"br": "brightness",
"dl": "delay",
"nl": "num_leds",
"co": "color_order",
"lp": "led_pin",
"n1": "n1",
"n2": "n2",
"n3": "n3",
"n4": "n4",
"n5": "n5",
"n6": "n6",
"auto": "auto",
}
class Patterns: class PatternBase:
def __init__(self, pin, num_leds, color1=(0,0,0), color2=(0,0,0), brightness=127, selected="off", delay=100): def __init__(self, pin, num_leds, color1=(0,0,0), color2=(0,0,0), brightness=127, selected="rainbow_cycle", delay=100):
self.n = NeoPixel(Pin(pin, Pin.OUT), num_leds) self.n = NeoPixel(Pin(pin, Pin.OUT), num_leds)
self.num_leds = num_leds self.num_leds = num_leds
self.pattern_step = 0 self.pattern_step = 0
self.last_update = utime.ticks_ms() self.last_update = utime.ticks_ms()
self.delay = delay self.delay = delay
self.brightness = brightness self.brightness = brightness
self.auto = False
self.patterns = {} self.patterns = {}
self.selected = selected self.selected = selected
self.run = True
# Ensure colors list always starts with at least two for robust transition handling # Ensure colors list always starts with at least two for robust transition handling
self.colors = [color1, color2] if color1 != color2 else [color1, (255, 255, 255)] # Fallback if initial colors are same self.colors = [color1, color2] if color1 != color2 else [color1, (255, 255, 255)] # Fallback if initial colors are same
if not self.colors: # Ensure at least one color exists if not self.colors: # Ensure at least one color exists
@@ -49,87 +27,25 @@ class Patterns:
self.hold_start_time = utime.ticks_ms() # Time when the current color hold started self.hold_start_time = utime.ticks_ms() # Time when the current color hold started
# New attributes for scanner patterns # New attributes for scanner patterns (moved from Patterns to PatternBase as they are generic enough)
self.scanner_direction = 1 # 1 for forward, -1 for backward self.scanner_direction = 1 # 1 for forward, -1 for backward
self.scanner_tail_length = 3 # Number of trailing pixels self.scanner_tail_length = 3 # Number of trailing pixels
self.running = False
self.stopped = True
self.n1 = 0
self.n2 = 0
self.n3 = 0
self.n4 = 0
self.n5 = 0
self.n6 = 0
def select(self, pattern):
if pattern in self.patterns:
self.selected = pattern
return True
# If pattern doesn't exist, default to "off"
if "off" in self.patterns:
self.selected = "off"
return False
async def run(self):
await self.stop()
# Ensure we wait a bit more to let the thread fully terminate
# If selected pattern doesn't exist, default to "off"
if self.selected not in self.patterns:
print(f"Pattern {self.selected} not found, defaulting to 'off'")
if "off" in self.patterns:
self.selected = "off"
else:
print("No patterns available")
self.running = False
self.stopped = True
return
print(f"Starting pattern {self.selected}")
_thread.start_new_thread(self.patterns[self.selected], ())
async def stop(self):
if not self.running:
# Already stopped
self.stopped = True
return
self.running = False
start = utime.ticks_ms()
timeout = 2000 # Increased timeout to 2 seconds
while not self.stopped and utime.ticks_diff(utime.ticks_ms(), start) < timeout:
await asyncio.sleep_ms(10) # Check every 10ms instead of 0ms
if not self.stopped:
# Timeout reached, force stop
print("Warning: Pattern did not stop within timeout")
self.stopped = True
def set_param(self, key, value):
if key in param_mapping:
setattr(self, param_mapping[key], value)
return True
print(f"Invalid parameter: {key}")
return False
# Store last pattern-returned delay to use for subsequent gating
self._last_returned_delay = None
def update_num_leds(self, pin, num_leds): def update_num_leds(self, pin, num_leds):
self.n = NeoPixel(Pin(pin, Pin.OUT), num_leds) self.n = NeoPixel(Pin(pin, Pin.OUT), num_leds)
self.num_leds = num_leds self.num_leds = num_leds
self.pattern_step = 0
def set_color(self, num, color): def set_color(self, num, color):
# Changed: More robust index check
if 0 <= num < len(self.colors): if 0 <= num < len(self.colors):
self.colors[num] = color self.colors[num] = color
# If the changed color is part of the current or next transition,
# restart the transition for smoother updates
return True
elif num == len(self.colors): # Allow setting a new color at the end elif num == len(self.colors): # Allow setting a new color at the end
self.colors.append(color) self.colors.append(color)
return True return True
return False return False
def del_color(self, num): def del_color(self, num):
# Changed: More robust index check and using del for lists
if 0 <= num < len(self.colors): if 0 <= num < len(self.colors):
del self.colors[num] del self.colors[num]
return True return True
@@ -139,10 +55,12 @@ class Patterns:
effective_brightness = brightness_override if brightness_override is not None else self.brightness effective_brightness = brightness_override if brightness_override is not None else self.brightness
return tuple(int(c * effective_brightness / 255) for c in color) return tuple(int(c * effective_brightness / 255) for c in color)
def write(self):
self.n.write()
def fill(self, color=None): def fill(self, color=None):
fill_color = color if color is not None else self.colors[0] fill_color = color if color is not None else self.colors[0]
for i in range(self.num_leds): self.n.fill(fill_color)
self.n[i] = fill_color
self.n.write() self.n.write()
def off(self): def off(self):
@@ -150,18 +68,3 @@ class Patterns:
def on(self): def on(self):
self.fill(self.apply_brightness(self.colors[0])) self.fill(self.apply_brightness(self.colors[0]))
def wheel(self, pos):
if pos < 85:
return (pos * 3, 255 - pos * 3, 0)
elif pos < 170:
pos -= 85
return (255 - pos * 3, 0, pos * 3)
else:
pos -= 170
return (0, pos * 3, 255 - pos * 3)

View File

@@ -1,31 +0,0 @@
import json
import wifi
import ubinascii
import machine
class Presets(dict):
FILE = "/presets.json"
def __init__(self):
super().__init__()
self.load() # Load settings from file during initialization
def save(self):
try:
j = json.dumps(self)
with open(self.FILE, 'w') as file:
file.write(j)
print("Presets saved successfully.")
except Exception as e:
print(f"Error saving settings: {e}")
def load(self):
try:
with open(self.FILE, 'r') as file:
self.update(json.load(file))
print("Presets loaded successfully.")
except Exception as e:
print(f"Error loading presets")
self.save()

View File

@@ -9,21 +9,14 @@ class Settings(dict):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
self.load() # Load settings from file during initialization self.load() # Load settings from file during initialization
if self["color_order"] == "rbg": self.color_order = (1, 5, 3) if self.get("color_order", "rgb") == "rbg": self.color_order = (1, 5, 3)
else: self.color_order = (1, 3, 5) else: self.color_order = (1, 3, 5)
def set_defaults(self): def set_defaults(self):
self["led_pin"] = 10 self["led_pin"] = 10
self["num_leds"] = 50 self["num_leds"] = 119
self["pattern"] = "on"
self["color1"] = "#00ff00"
self["color2"] = "#ff0000"
self["delay"] = 100
self["brightness"] = 10
self["color_order"] = "rgb" self["color_order"] = "rgb"
self["name"] = f"led-{ubinascii.hexlify(wifi.get_mac()).decode()}" self["name"] = f"104"
self["ap_password"] = ""
self["id"] = 0
def save(self): def save(self):
try: try:
@@ -45,40 +38,48 @@ class Settings(dict):
self.set_defaults() self.set_defaults()
self.save() self.save()
async def set_settings(self, data, patterns, save): def set_settings(self, data, patterns, save):
try: try:
print(f"Setting settings: {data}")
for key, value in data.items(): for key, value in data.items():
print(key, value) print(key, value)
if key == "colors": if key == "colors":
buff = [] buff = []
for color in value: for color in value:
buff.append(tuple(int(color[i:i+2], 16) for i in self.color_order)) buff.append(tuple(int(color[i:i+2], 16) for i in self.color_order))
patterns.colors = buff patterns.set_colors(buff)
elif key == "color1":
patterns.set_color1(tuple(int(value[i:i+2], 16) for i in self.color_order)) # Convert hex to RGB
elif key == "color2":
patterns.set_color2(tuple(int(value[i:i+2], 16) for i in self.color_order)) # Convert hex to RGB
elif key == "num_leds": elif key == "num_leds":
patterns.update_num_leds(self["led_pin"], value) patterns.update_num_leds(self["led_pin"], value)
elif key == "pattern": elif key == "pattern":
if not patterns.select(value): if not patterns.select(value):
return "Pattern doesn't exist", 400 return "Pattern doesn't exist", 400
await patterns.run()
elif key == "delay": elif key == "delay":
delay = int(data["delay"]) delay = int(data["delay"])
patterns.delay = delay patterns.set_delay(delay)
elif key == "brightness": elif key == "brightness":
brightness = int(data["brightness"]) brightness = int(data["brightness"])
patterns.brightness = brightness patterns.set_brightness(brightness)
elif key == "on_width":
on_width = int(data["on_width"])
patterns.set_on_width(on_width)
elif key == "off_width":
off_width = int(data["off_width"])
on_width = int(data.get("on_width", self["on_width"]))
patterns.set_on_off_width(on_width, off_width)
elif key == "n1": elif key == "n1":
patterns.n1 = value n1 = int(data["n1"])
n2 = int(data.get("n2", patterns.n2))
patterns.set_fill_range(n1, n2)
elif key == "n2": elif key == "n2":
patterns.n2 = value n2 = int(data["n2"])
elif key == "n3": n1 = int(data.get("n1", patterns.n1))
patterns.n3 = value patterns.set_fill_range(n1, n2)
elif key == "n4": elif key == "oneshot":
patterns.n4 = value oneshot_value = bool(data["oneshot"])
elif key == "n5": patterns.set_oneshot(oneshot_value)
patterns.n5 = value
elif key == "n6":
patterns.n6 = value
elif key == "name": elif key == "name":
self[key] = value self[key] = value
self.save() self.save()
@@ -95,11 +96,12 @@ class Settings(dict):
return "Invalid key", 400 return "Invalid key", 400
self[key] = value self[key] = value
#print(self) #print(self)
patterns.sync()
if save: if save:
self.save() self.save()
print(self)
return "OK", 200 return "OK", 200
except (KeyError, ValueError): except Exception as e:
print(f"An unexpected error occurred in set_settings: {e}")
return "Bad request", 400 return "Bad request", 400
# Example usage # Example usage

View File

@@ -12,7 +12,7 @@ def web(settings, patterns):
@app.route('/') @app.route('/')
async def index_hnadler(request): async def index_hnadler(request):
mac = wifi.get_mac().hex() mac = wifi.get_mac().hex()
return Template('index.html').render(settings=settings, patterns=patterns.patterns.keys()) return Template('/index.html').render(settings=settings, patterns=patterns.patterns.keys(), mac=mac)
@app.route("/static/<path:path>") @app.route("/static/<path:path>")
def static_handler(request, path): def static_handler(request, path):
@@ -35,7 +35,7 @@ def web(settings, patterns):
if data: if data:
# Process the received data # Process the received data
_, status_code = await settings.set_settings(json.loads(data), patterns, True) _, status_code = settings.set_settings(json.loads(data), patterns, True)
#await ws.send(status_code) #await ws.send(status_code)
else: else:
break break

58
test/blink.py Normal file
View File

@@ -0,0 +1,58 @@
#!/usr/bin/env python3
"""
Test script for blink pattern
Run with: mpremote run test/blink.py
"""
import patterns
import utime
from settings import Settings
from machine import WDT
def test_blink():
print("Testing blink pattern...")
# Initialize watchdog timer
wdt = WDT(timeout=10000) # 10 second timeout
wdt.feed()
# Load settings
settings = Settings()
# Initialize patterns using settings
p = patterns.Patterns(
pin=settings["led_pin"],
num_leds=settings["num_leds"], # Set to 200 LEDs for test
brightness=255, # Full brightness
delay=500
)
# Set a bright red color
p.colors = [(255, 0, 0)] # Red
print(f"LED Pin: {settings['led_pin']}")
print(f"LEDs: {p.num_leds} (test override: 200)")
print(f"Brightness: {p.brightness}")
print(f"Delay: {p.delay}ms")
print(f"Color: {p.colors[0]}")
# Test blink pattern
print("Starting blink pattern for 5 seconds...")
p.select("bl")
# Let it run for 5 seconds with WDT feeding
start_time = utime.ticks_ms()
while utime.ticks_diff(utime.ticks_ms(), start_time) < 5000:
wdt.feed()
utime.sleep_ms(100)
# Stop the pattern
p.run = False
print("Blink test completed")
# Turn off LEDs
p.off()
print("LEDs turned off")
if __name__ == "__main__":
test_blink()

View File

@@ -1,17 +1,16 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
""" """
Circle test: n1=50, n2=100, n3=200, n4=0 (Red) Circle loading test 1: n1=50, n2=100, n3=200, n4=0 (Red)
Runs forever Runs forever
Run with: mpremote run test/circle.py Run with: mpremote run test/circle/1.py
""" """
import patterns import patterns
import utime import utime
import _thread
from settings import Settings from settings import Settings
from machine import WDT from machine import WDT
print("Starting Circle Test: n1=50, n2=100, n3=200, n4=0 (Red)") print("Starting Circle Loading Test 1: n1=50, n2=100, n3=200, n4=0 (Red)")
print("Press Ctrl+C to stop") print("Press Ctrl+C to stop")
# Load settings # Load settings
@@ -43,12 +42,8 @@ wdt = WDT(timeout=10000)
wdt.feed() wdt.feed()
# Start pattern # Start pattern
p.select("circle") p.select("cl")
if p.selected in p.patterns:
_thread.start_new_thread(p.patterns[p.selected], ())
print("Pattern started. Running forever...") print("Pattern started. Running forever...")
else:
print(f"Pattern {p.selected} not found")
# Run forever # Run forever
try: try:
@@ -57,7 +52,7 @@ try:
utime.sleep_ms(100) utime.sleep_ms(100)
except KeyboardInterrupt: except KeyboardInterrupt:
print("\nStopping...") print("\nStopping...")
p.running = False p.run = False
p.off() p.off()
print("LEDs turned off") print("LEDs turned off")

58
test/circle/2.py Normal file
View File

@@ -0,0 +1,58 @@
#!/usr/bin/env python3
"""
Circle loading test 2: n1=10, n2=30, n3=20, n4=5 (Green)
Runs forever
Run with: mpremote run test/circle/2.py
"""
import patterns
import utime
from settings import Settings
from machine import WDT
print("Starting Circle Loading Test 2: n1=10, n2=30, n3=20, n4=5 (Green)")
print("Press Ctrl+C to stop")
# Load settings
settings = Settings()
# Initialize patterns using settings
p = patterns.Patterns(
pin=settings["led_pin"],
num_leds=settings["num_leds"],
brightness=255,
delay=2000
)
# Configure test parameters
p.n1 = 10 # Head moves 10 LEDs/second
p.n2 = 30 # Max length 30 LEDs
p.n3 = 20 # Tail moves 20 LEDs/second
p.n4 = 5 # Min length 5 LEDs
p.colors = [(0, 255, 0)] # Green
print(f"LED Pin: {settings['led_pin']}")
print(f"LEDs: {settings['num_leds']}")
print(f"Brightness: {p.brightness}")
print(f"Parameters: n1={p.n1}, n2={p.n2}, n3={p.n3}, n4={p.n4}")
print(f"Color: {p.colors[0]}")
# Initialize watchdog timer
wdt = WDT(timeout=10000)
wdt.feed()
# Start pattern
p.select("cl")
print("Pattern started. Running forever...")
# Run forever
try:
while True:
wdt.feed()
utime.sleep_ms(100)
except KeyboardInterrupt:
print("\nStopping...")
p.run = False
p.off()
print("LEDs turned off")

58
test/circle/3.py Normal file
View File

@@ -0,0 +1,58 @@
#!/usr/bin/env python3
"""
Circle loading test 3: n1=15, n2=25, n3=15, n4=3 (Blue)
Runs forever
Run with: mpremote run test/circle/3.py
"""
import patterns
import utime
from settings import Settings
from machine import WDT
print("Starting Circle Loading Test 3: n1=15, n2=25, n3=15, n4=3 (Blue)")
print("Press Ctrl+C to stop")
# Load settings
settings = Settings()
# Initialize patterns using settings
p = patterns.Patterns(
pin=settings["led_pin"],
num_leds=settings["num_leds"],
brightness=255,
delay=2000
)
# Configure test parameters
p.n1 = 15 # Head moves 15 LEDs/second
p.n2 = 25 # Max length 25 LEDs
p.n3 = 15 # Tail moves 15 LEDs/second
p.n4 = 3 # Min length 3 LEDs
p.colors = [(0, 0, 255)] # Blue
print(f"LED Pin: {settings['led_pin']}")
print(f"LEDs: {settings['num_leds']}")
print(f"Brightness: {p.brightness}")
print(f"Parameters: n1={p.n1}, n2={p.n2}, n3={p.n3}, n4={p.n4}")
print(f"Color: {p.colors[0]}")
# Initialize watchdog timer
wdt = WDT(timeout=10000)
wdt.feed()
# Start pattern
p.select("cl")
print("Pattern started. Running forever...")
# Run forever
try:
while True:
wdt.feed()
utime.sleep_ms(100)
except KeyboardInterrupt:
print("\nStopping...")
p.run = False
p.off()
print("LEDs turned off")

58
test/circle/4.py Normal file
View File

@@ -0,0 +1,58 @@
#!/usr/bin/env python3
"""
Circle loading test 4: n1=30, n2=40, n3=5, n4=8 (Yellow)
Runs forever
Run with: mpremote run test/circle/4.py
"""
import patterns
import utime
from settings import Settings
from machine import WDT
print("Starting Circle Loading Test 4: n1=30, n2=40, n3=5, n4=8 (Yellow)")
print("Press Ctrl+C to stop")
# Load settings
settings = Settings()
# Initialize patterns using settings
p = patterns.Patterns(
pin=settings["led_pin"],
num_leds=settings["num_leds"],
brightness=255,
delay=2000
)
# Configure test parameters
p.n1 = 30 # Head moves 30 LEDs/second
p.n2 = 40 # Max length 40 LEDs
p.n3 = 5 # Tail moves 5 LEDs/second
p.n4 = 8 # Min length 8 LEDs
p.colors = [(255, 255, 0)] # Yellow
print(f"LED Pin: {settings['led_pin']}")
print(f"LEDs: {settings['num_leds']}")
print(f"Brightness: {p.brightness}")
print(f"Parameters: n1={p.n1}, n2={p.n2}, n3={p.n3}, n4={p.n4}")
print(f"Color: {p.colors[0]}")
# Initialize watchdog timer
wdt = WDT(timeout=10000)
wdt.feed()
# Start pattern
p.select("cl")
print("Pattern started. Running forever...")
# Run forever
try:
while True:
wdt.feed()
utime.sleep_ms(100)
except KeyboardInterrupt:
print("\nStopping...")
p.run = False
p.off()
print("LEDs turned off")

58
test/circle/5.py Normal file
View File

@@ -0,0 +1,58 @@
#!/usr/bin/env python3
"""
Circle loading test 5: n1=25, n2=60, n3=30, n4=2 (Magenta)
Runs forever
Run with: mpremote run test/circle/5.py
"""
import patterns
import utime
from settings import Settings
from machine import WDT
print("Starting Circle Loading Test 5: n1=25, n2=60, n3=30, n4=2 (Magenta)")
print("Press Ctrl+C to stop")
# Load settings
settings = Settings()
# Initialize patterns using settings
p = patterns.Patterns(
pin=settings["led_pin"],
num_leds=settings["num_leds"],
brightness=255,
delay=2000
)
# Configure test parameters
p.n1 = 25 # Head moves 25 LEDs/second
p.n2 = 60 # Max length 60 LEDs
p.n3 = 30 # Tail moves 30 LEDs/second
p.n4 = 2 # Min length 2 LEDs
p.colors = [(255, 0, 255)] # Magenta
print(f"LED Pin: {settings['led_pin']}")
print(f"LEDs: {settings['num_leds']}")
print(f"Brightness: {p.brightness}")
print(f"Parameters: n1={p.n1}, n2={p.n2}, n3={p.n3}, n4={p.n4}")
print(f"Color: {p.colors[0]}")
# Initialize watchdog timer
wdt = WDT(timeout=10000)
wdt.feed()
# Start pattern
p.select("cl")
print("Pattern started. Running forever...")
# Run forever
try:
while True:
wdt.feed()
utime.sleep_ms(100)
except KeyboardInterrupt:
print("\nStopping...")
p.run = False
p.off()
print("LEDs turned off")

58
test/circle/6.py Normal file
View File

@@ -0,0 +1,58 @@
#!/usr/bin/env python3
"""
Circle loading test 6: n1=5, n2=20, n3=40, n4=1 (Cyan)
Runs forever
Run with: mpremote run test/circle/6.py
"""
import patterns
import utime
from settings import Settings
from machine import WDT
print("Starting Circle Loading Test 6: n1=5, n2=20, n3=40, n4=1 (Cyan)")
print("Press Ctrl+C to stop")
# Load settings
settings = Settings()
# Initialize patterns using settings
p = patterns.Patterns(
pin=settings["led_pin"],
num_leds=settings["num_leds"],
brightness=255,
delay=2000
)
# Configure test parameters
p.n1 = 5 # Head moves 5 LEDs/second
p.n2 = 20 # Max length 20 LEDs
p.n3 = 40 # Tail moves 40 LEDs/second
p.n4 = 1 # Min length 1 LED
p.colors = [(0, 255, 255)] # Cyan
print(f"LED Pin: {settings['led_pin']}")
print(f"LEDs: {settings['num_leds']}")
print(f"Brightness: {p.brightness}")
print(f"Parameters: n1={p.n1}, n2={p.n2}, n3={p.n3}, n4={p.n4}")
print(f"Color: {p.colors[0]}")
# Initialize watchdog timer
wdt = WDT(timeout=10000)
wdt.feed()
# Start pattern
p.select("cl")
print("Pattern started. Running forever...")
# Run forever
try:
while True:
wdt.feed()
utime.sleep_ms(100)
except KeyboardInterrupt:
print("\nStopping...")
p.run = False
p.off()
print("LEDs turned off")

58
test/circle/7.py Normal file
View File

@@ -0,0 +1,58 @@
#!/usr/bin/env python3
"""
Circle loading test 7: n1=12, n2=15, n3=18, n4=4 (Orange)
Runs forever
Run with: mpremote run test/circle/7.py
"""
import patterns
import utime
from settings import Settings
from machine import WDT
print("Starting Circle Loading Test 7: n1=12, n2=15, n3=18, n4=4 (Orange)")
print("Press Ctrl+C to stop")
# Load settings
settings = Settings()
# Initialize patterns using settings
p = patterns.Patterns(
pin=settings["led_pin"],
num_leds=settings["num_leds"],
brightness=255,
delay=2000
)
# Configure test parameters
p.n1 = 12 # Head moves 12 LEDs/second
p.n2 = 15 # Max length 15 LEDs
p.n3 = 18 # Tail moves 18 LEDs/second
p.n4 = 4 # Min length 4 LEDs
p.colors = [(255, 128, 0)] # Orange
print(f"LED Pin: {settings['led_pin']}")
print(f"LEDs: {settings['num_leds']}")
print(f"Brightness: {p.brightness}")
print(f"Parameters: n1={p.n1}, n2={p.n2}, n3={p.n3}, n4={p.n4}")
print(f"Color: {p.colors[0]}")
# Initialize watchdog timer
wdt = WDT(timeout=10000)
wdt.feed()
# Start pattern
p.select("cl")
print("Pattern started. Running forever...")
# Run forever
try:
while True:
wdt.feed()
utime.sleep_ms(100)
except KeyboardInterrupt:
print("\nStopping...")
p.run = False
p.off()
print("LEDs turned off")

58
test/circle/8.py Normal file
View File

@@ -0,0 +1,58 @@
#!/usr/bin/env python3
"""
Circle loading test 8: n1=100, n2=50, n3=150, n4=10 (Purple)
Runs forever
Run with: mpremote run test/circle/8.py
"""
import patterns
import utime
from settings import Settings
from machine import WDT
print("Starting Circle Loading Test 8: n1=100, n2=50, n3=150, n4=10 (Purple)")
print("Press Ctrl+C to stop")
# Load settings
settings = Settings()
# Initialize patterns using settings
p = patterns.Patterns(
pin=settings["led_pin"],
num_leds=settings["num_leds"],
brightness=255,
delay=2000
)
# Configure test parameters
p.n1 = 100 # Head moves 100 LEDs/second
p.n2 = 50 # Max length 50 LEDs
p.n3 = 150 # Tail moves 150 LEDs/second
p.n4 = 10 # Min length 10 LEDs
p.colors = [(128, 0, 255)] # Purple
print(f"LED Pin: {settings['led_pin']}")
print(f"LEDs: {settings['num_leds']}")
print(f"Brightness: {p.brightness}")
print(f"Parameters: n1={p.n1}, n2={p.n2}, n3={p.n3}, n4={p.n4}")
print(f"Color: {p.colors[0]}")
# Initialize watchdog timer
wdt = WDT(timeout=10000)
wdt.feed()
# Start pattern
p.select("cl")
print("Pattern started. Running forever...")
# Run forever
try:
while True:
wdt.feed()
utime.sleep_ms(100)
except KeyboardInterrupt:
print("\nStopping...")
p.run = False
p.off()
print("LEDs turned off")

128
test/circle_loading.py Normal file
View File

@@ -0,0 +1,128 @@
#!/usr/bin/env python3
"""
Test script for circle loading pattern with multiple test cases
Run with: mpremote run test/circle_loading.py
"""
import patterns
import utime
from settings import Settings
from machine import WDT
def run_test_case(p, test_name, duration_ms=5000):
"""Run a test case for specified duration"""
print(f"\n--- {test_name} ---")
print(f"Parameters: n1={p.n1} head/sec, n2={p.n2} max length, n3={p.n3} tail/sec, n4={p.n4} min length")
print(f"Color: {p.colors[0]}")
print(f"Running for {duration_ms//1000} seconds...")
# Initialize watchdog timer
wdt = WDT(timeout=10000)
wdt.feed()
# Start pattern
p.select("cl")
# Run for specified duration
start_time = utime.ticks_ms()
while utime.ticks_diff(utime.ticks_ms(), start_time) < duration_ms:
wdt.feed()
utime.sleep_ms(100)
# Stop pattern
p.run = False
print(f"{test_name} completed")
# Brief pause between tests
utime.sleep_ms(500)
def test_circle_loading():
print("Testing circle loading pattern with multiple configurations...")
# Load settings
settings = Settings()
# Initialize patterns using settings
p = patterns.Patterns(
pin=settings["led_pin"],
num_leds=settings["num_leds"],
brightness=255,
delay=2000 # Base cycle time
)
print(f"LED Pin: {settings['led_pin']}")
print(f"LEDs: {settings['num_leds']}")
print(f"Brightness: {p.brightness}")
# Test Case 1: Your specified parameters
p.n1 = 50
p.n2 = 100
p.n3 = 200
p.n4 = 0
p.colors = [(255, 0, 0)] # Red
run_test_case(p, "Test 1: n1=50, n2=100, n3=200, n4=0", 15000)
# Test Case 2: Slow head, fast tail
p.n1 = 10 # Head moves 10 LEDs/second
p.n2 = 30 # Max length 30 LEDs
p.n3 = 20 # Tail moves 20 LEDs/second
p.n4 = 5 # Min length 5 LEDs
p.colors = [(0, 255, 0)] # Green
run_test_case(p, "Test 2: n1=10, n2=30, n3=20, n4=5", 10000)
# Test Case 3: Equal head and tail speed
p.n1 = 15 # Head moves 15 LEDs/second
p.n2 = 25 # Max length 25 LEDs
p.n3 = 15 # Tail moves 15 LEDs/second
p.n4 = 3 # Min length 3 LEDs
p.colors = [(0, 0, 255)] # Blue
run_test_case(p, "Test 3: n1=15, n2=25, n3=15, n4=3", 8000)
# Test Case 4: Very fast head, slow tail
p.n1 = 30 # Head moves 30 LEDs/second
p.n2 = 40 # Max length 40 LEDs
p.n3 = 5 # Tail moves 5 LEDs/second
p.n4 = 8 # Min length 8 LEDs
p.colors = [(255, 255, 0)] # Yellow
run_test_case(p, "Test 4: n1=30, n2=40, n3=5, n4=8", 12000)
# Test Case 5: Long segment, fast cycle
p.n1 = 25 # Head moves 25 LEDs/second
p.n2 = 60 # Max length 60 LEDs
p.n3 = 30 # Tail moves 30 LEDs/second
p.n4 = 2 # Min length 2 LEDs
p.colors = [(255, 0, 255)] # Magenta
run_test_case(p, "Test 5: n1=25, n2=60, n3=30, n4=2", 10000)
# Test Case 6: Very slow head, very fast tail
p.n1 = 5 # Head moves 5 LEDs/second
p.n2 = 20 # Max length 20 LEDs
p.n3 = 40 # Tail moves 40 LEDs/second
p.n4 = 1 # Min length 1 LED
p.colors = [(0, 255, 255)] # Cyan
run_test_case(p, "Test 6: n1=5, n2=20, n3=40, n4=1", 8000)
# Test Case 7: Medium speeds, short segment
p.n1 = 12 # Head moves 12 LEDs/second
p.n2 = 15 # Max length 15 LEDs
p.n3 = 18 # Tail moves 18 LEDs/second
p.n4 = 4 # Min length 4 LEDs
p.colors = [(255, 128, 0)] # Orange
run_test_case(p, "Test 7: n1=12, n2=15, n3=18, n4=4", 6000)
# Test Case 8: Ultra fast everything
p.n1 = 100 # Head moves 100 LEDs/second
p.n2 = 50 # Max length 50 LEDs
p.n3 = 150 # Tail moves 150 LEDs/second
p.n4 = 10 # Min length 10 LEDs
p.colors = [(128, 0, 255)] # Purple
run_test_case(p, "Test 8: n1=100, n2=50, n3=150, n4=10", 5000)
print("\n=== All tests completed ===")
# Turn off LEDs
p.off()
print("LEDs turned off")
if __name__ == "__main__":
test_circle_loading()

56
test/flicker/1.py Normal file
View File

@@ -0,0 +1,56 @@
#!/usr/bin/env python3
"""
Flicker test 1: Red, delay=50ms, min brightness=50
Runs forever
Run with: mpremote run test/flicker/1.py
"""
import patterns
import utime
from settings import Settings
from machine import WDT
print("Starting Flicker Test 1: Red, delay=50ms, min brightness=50")
print("Press Ctrl+C to stop")
# Load settings
settings = Settings()
# Initialize patterns using settings
p = patterns.Patterns(
pin=settings["led_pin"],
num_leds=settings["num_leds"],
brightness=255,
delay=50
)
# Configure test parameters
p.n1 = 50 # Min brightness 50
p.colors = [(255, 0, 0)] # Red
print(f"LED Pin: {settings['led_pin']}")
print(f"LEDs: {settings['num_leds']}")
print(f"Brightness: {p.brightness}")
print(f"Delay: {p.delay}ms")
print(f"Min brightness: {p.n1}")
print(f"Color: {p.colors[0]}")
# Initialize watchdog timer
wdt = WDT(timeout=10000)
wdt.feed()
# Start pattern
p.select("fl")
print("Pattern started. Running forever...")
# Run forever
try:
while True:
wdt.feed()
utime.sleep_ms(100)
except KeyboardInterrupt:
print("\nStopping...")
p.run = False
p.off()
print("LEDs turned off")

56
test/flicker/2.py Normal file
View File

@@ -0,0 +1,56 @@
#!/usr/bin/env python3
"""
Flicker test 2: Green, delay=100ms, min brightness=100
Runs forever
Run with: mpremote run test/flicker/2.py
"""
import patterns
import utime
from settings import Settings
from machine import WDT
print("Starting Flicker Test 2: Green, delay=100ms, min brightness=100")
print("Press Ctrl+C to stop")
# Load settings
settings = Settings()
# Initialize patterns using settings
p = patterns.Patterns(
pin=settings["led_pin"],
num_leds=settings["num_leds"],
brightness=255,
delay=100
)
# Configure test parameters
p.n1 = 100 # Min brightness 100
p.colors = [(0, 255, 0)] # Green
print(f"LED Pin: {settings['led_pin']}")
print(f"LEDs: {settings['num_leds']}")
print(f"Brightness: {p.brightness}")
print(f"Delay: {p.delay}ms")
print(f"Min brightness: {p.n1}")
print(f"Color: {p.colors[0]}")
# Initialize watchdog timer
wdt = WDT(timeout=10000)
wdt.feed()
# Start pattern
p.select("fl")
print("Pattern started. Running forever...")
# Run forever
try:
while True:
wdt.feed()
utime.sleep_ms(100)
except KeyboardInterrupt:
print("\nStopping...")
p.run = False
p.off()
print("LEDs turned off")

56
test/flicker/3.py Normal file
View File

@@ -0,0 +1,56 @@
#!/usr/bin/env python3
"""
Flicker test 3: Blue, delay=200ms, min brightness=20
Runs forever
Run with: mpremote run test/flicker/3.py
"""
import patterns
import utime
from settings import Settings
from machine import WDT
print("Starting Flicker Test 3: Blue, delay=200ms, min brightness=20")
print("Press Ctrl+C to stop")
# Load settings
settings = Settings()
# Initialize patterns using settings
p = patterns.Patterns(
pin=settings["led_pin"],
num_leds=settings["num_leds"],
brightness=255,
delay=200
)
# Configure test parameters
p.n1 = 20 # Min brightness 20
p.colors = [(0, 0, 255)] # Blue
print(f"LED Pin: {settings['led_pin']}")
print(f"LEDs: {settings['num_leds']}")
print(f"Brightness: {p.brightness}")
print(f"Delay: {p.delay}ms")
print(f"Min brightness: {p.n1}")
print(f"Color: {p.colors[0]}")
# Initialize watchdog timer
wdt = WDT(timeout=10000)
wdt.feed()
# Start pattern
p.select("fl")
print("Pattern started. Running forever...")
# Run forever
try:
while True:
wdt.feed()
utime.sleep_ms(100)
except KeyboardInterrupt:
print("\nStopping...")
p.run = False
p.off()
print("LEDs turned off")

56
test/flicker/4.py Normal file
View File

@@ -0,0 +1,56 @@
#!/usr/bin/env python3
"""
Flicker test 4: White, delay=80ms, min brightness=150
Runs forever
Run with: mpremote run test/flicker/4.py
"""
import patterns
import utime
from settings import Settings
from machine import WDT
print("Starting Flicker Test 4: White, delay=80ms, min brightness=150")
print("Press Ctrl+C to stop")
# Load settings
settings = Settings()
# Initialize patterns using settings
p = patterns.Patterns(
pin=settings["led_pin"],
num_leds=settings["num_leds"],
brightness=255,
delay=80
)
# Configure test parameters
p.n1 = 150 # Min brightness 150
p.colors = [(255, 255, 255)] # White
print(f"LED Pin: {settings['led_pin']}")
print(f"LEDs: {settings['num_leds']}")
print(f"Brightness: {p.brightness}")
print(f"Delay: {p.delay}ms")
print(f"Min brightness: {p.n1}")
print(f"Color: {p.colors[0]}")
# Initialize watchdog timer
wdt = WDT(timeout=10000)
wdt.feed()
# Start pattern
p.select("fl")
print("Pattern started. Running forever...")
# Run forever
try:
while True:
wdt.feed()
utime.sleep_ms(100)
except KeyboardInterrupt:
print("\nStopping...")
p.run = False
p.off()
print("LEDs turned off")

56
test/flicker/5.py Normal file
View File

@@ -0,0 +1,56 @@
#!/usr/bin/env python3
"""
Flicker test 5: Yellow, delay=150ms, min brightness=30
Runs forever
Run with: mpremote run test/flicker/5.py
"""
import patterns
import utime
from settings import Settings
from machine import WDT
print("Starting Flicker Test 5: Yellow, delay=150ms, min brightness=30")
print("Press Ctrl+C to stop")
# Load settings
settings = Settings()
# Initialize patterns using settings
p = patterns.Patterns(
pin=settings["led_pin"],
num_leds=settings["num_leds"],
brightness=255,
delay=150
)
# Configure test parameters
p.n1 = 30 # Min brightness 30
p.colors = [(255, 255, 0)] # Yellow
print(f"LED Pin: {settings['led_pin']}")
print(f"LEDs: {settings['num_leds']}")
print(f"Brightness: {p.brightness}")
print(f"Delay: {p.delay}ms")
print(f"Min brightness: {p.n1}")
print(f"Color: {p.colors[0]}")
# Initialize watchdog timer
wdt = WDT(timeout=10000)
wdt.feed()
# Start pattern
p.select("fl")
print("Pattern started. Running forever...")
# Run forever
try:
while True:
wdt.feed()
utime.sleep_ms(100)
except KeyboardInterrupt:
print("\nStopping...")
p.run = False
p.off()
print("LEDs turned off")

56
test/flicker/6.py Normal file
View File

@@ -0,0 +1,56 @@
#!/usr/bin/env python3
"""
Flicker test 6: Magenta, delay=60ms, min brightness=80
Runs forever
Run with: mpremote run test/flicker/6.py
"""
import patterns
import utime
from settings import Settings
from machine import WDT
print("Starting Flicker Test 6: Magenta, delay=60ms, min brightness=80")
print("Press Ctrl+C to stop")
# Load settings
settings = Settings()
# Initialize patterns using settings
p = patterns.Patterns(
pin=settings["led_pin"],
num_leds=settings["num_leds"],
brightness=255,
delay=60
)
# Configure test parameters
p.n1 = 80 # Min brightness 80
p.colors = [(255, 0, 255)] # Magenta
print(f"LED Pin: {settings['led_pin']}")
print(f"LEDs: {settings['num_leds']}")
print(f"Brightness: {p.brightness}")
print(f"Delay: {p.delay}ms")
print(f"Min brightness: {p.n1}")
print(f"Color: {p.colors[0]}")
# Initialize watchdog timer
wdt = WDT(timeout=10000)
wdt.feed()
# Start pattern
p.select("fl")
print("Pattern started. Running forever...")
# Run forever
try:
while True:
wdt.feed()
utime.sleep_ms(100)
except KeyboardInterrupt:
print("\nStopping...")
p.run = False
p.off()
print("LEDs turned off")

56
test/flicker/7.py Normal file
View File

@@ -0,0 +1,56 @@
#!/usr/bin/env python3
"""
Flicker test 7: Cyan, delay=120ms, min brightness=5
Runs forever
Run with: mpremote run test/flicker/7.py
"""
import patterns
import utime
from settings import Settings
from machine import WDT
print("Starting Flicker Test 7: Cyan, delay=120ms, min brightness=5")
print("Press Ctrl+C to stop")
# Load settings
settings = Settings()
# Initialize patterns using settings
p = patterns.Patterns(
pin=settings["led_pin"],
num_leds=settings["num_leds"],
brightness=255,
delay=120
)
# Configure test parameters
p.n1 = 5 # Min brightness 5
p.colors = [(0, 255, 255)] # Cyan
print(f"LED Pin: {settings['led_pin']}")
print(f"LEDs: {settings['num_leds']}")
print(f"Brightness: {p.brightness}")
print(f"Delay: {p.delay}ms")
print(f"Min brightness: {p.n1}")
print(f"Color: {p.colors[0]}")
# Initialize watchdog timer
wdt = WDT(timeout=10000)
wdt.feed()
# Start pattern
p.select("fl")
print("Pattern started. Running forever...")
# Run forever
try:
while True:
wdt.feed()
utime.sleep_ms(100)
except KeyboardInterrupt:
print("\nStopping...")
p.run = False
p.off()
print("LEDs turned off")

56
test/flicker/8.py Normal file
View File

@@ -0,0 +1,56 @@
#!/usr/bin/env python3
"""
Flicker test 8: Orange, delay=40ms, min brightness=180
Runs forever
Run with: mpremote run test/flicker/8.py
"""
import patterns
import utime
from settings import Settings
from machine import WDT
print("Starting Flicker Test 8: Orange, delay=40ms, min brightness=180")
print("Press Ctrl+C to stop")
# Load settings
settings = Settings()
# Initialize patterns using settings
p = patterns.Patterns(
pin=settings["led_pin"],
num_leds=settings["num_leds"],
brightness=255,
delay=40
)
# Configure test parameters
p.n1 = 180 # Min brightness 180
p.colors = [(255, 128, 0)] # Orange
print(f"LED Pin: {settings['led_pin']}")
print(f"LEDs: {settings['num_leds']}")
print(f"Brightness: {p.brightness}")
print(f"Delay: {p.delay}ms")
print(f"Min brightness: {p.n1}")
print(f"Color: {p.colors[0]}")
# Initialize watchdog timer
wdt = WDT(timeout=10000)
wdt.feed()
# Start pattern
p.select("fl")
print("Pattern started. Running forever...")
# Run forever
try:
while True:
wdt.feed()
utime.sleep_ms(100)
except KeyboardInterrupt:
print("\nStopping...")
p.run = False
p.off()
print("LEDs turned off")

158
test/main.py Normal file
View File

@@ -0,0 +1,158 @@
import asyncio
import json
import argparse
import signal
try:
import websockets # type: ignore
except Exception as e:
print("Please install websockets: pip install websockets")
raise
WS_URI = "ws://192.168.4.1/ws"
# Default pattern suite aligned with current firmware patterns
PATTERN_SUITE = [
{"pattern": "flicker", "delay": 80, "iterations": 30, "repeat_delay": 80, "colors": ["#ffaa00"]},
{"pattern": "fill_range", "n1": 10, "n2": 20, "delay": 400, "iterations": 1, "repeat_delay": 500, "colors": ["#888888"]},
{"pattern": "n_chase", "n1": 5, "n2": 5, "delay": 250, "iterations": 40, "repeat_delay": 120, "colors": ["#00ff88"]},
{"pattern": "alternating", "n1": 6, "n2": 6, "delay": 300, "iterations": 20, "repeat_delay": 300, "colors": ["#ff8800"]},
{"pattern": "pulse", "delay": 200, "iterations": 6, "repeat_delay": 300, "colors": ["#ffffff"]},
]
def build_message(
pattern: str,
n: int | None = None,
delay: int | None = None,
colors: list[str] | None = None,
brightness: int | None = None,
num_leds: int | None = None,
n1: int | None = None,
n2: int | None = None,
name: str = "0",
pattern_step: int | None = None,
):
settings: dict[str, object] = {
"pattern": pattern,
}
if n is not None:
settings["n"] = n
if delay is not None:
settings["delay"] = delay
if colors is not None:
settings["colors"] = colors
if brightness is not None:
settings["brightness"] = brightness
if num_leds is not None:
settings["num_leds"] = num_leds
if n1 is not None:
settings["n1"] = n1
if n2 is not None:
settings["n2"] = n2
if pattern_step is not None:
settings["pattern_step"] = pattern_step
# ESP-NOW-style nested payload keyed by name (e.g., "0")
return {name: settings}
async def send_once(uri: str, payload: dict, hold_ms: int | None = None):
async with websockets.connect(uri) as ws:
await ws.send(json.dumps(payload))
if hold_ms and hold_ms > 0:
await asyncio.sleep(hold_ms / 1000)
async def run_suite(uri: str):
async with websockets.connect(uri) as ws:
for cfg in PATTERN_SUITE:
iterations = int(cfg.get("iterations", 10))
interval_ms = int(cfg.get("interval_ms", cfg.get("delay", 100) or 100))
repeat_ms = int(cfg.get("repeat_delay", interval_ms))
for i in range(iterations):
msg = build_message(
cfg.get("pattern", "off"),
i,
delay=cfg.get("delay"),
colors=cfg.get("colors"),
brightness=cfg.get("brightness", 255),
num_leds=cfg.get("num_leds"),
n1=cfg.get("n1"),
n2=cfg.get("n2"),
name=cfg.get("name", "0"),
pattern_step=cfg.get("pattern_step"),
)
print(msg)
await ws.send(json.dumps(msg))
await asyncio.sleep(repeat_ms / 1000)
def _parse_args():
p = argparse.ArgumentParser(description="WebSocket LED pattern tester")
p.add_argument("--uri", default=WS_URI, help="WebSocket URI, default ws://192.168.4.1/ws")
p.add_argument("--pattern", help="Single pattern to send (overrides suite)")
p.add_argument("--delay", type=int, help="Delay ms")
p.add_argument("--brightness", type=int, help="Brightness 0-255")
p.add_argument("--num-leds", type=int, help="Number of LEDs")
p.add_argument("--colors", nargs="*", help="Hex colors like #ff0000 #00ff00")
p.add_argument("--on-width", type=int)
p.add_argument("--off-width", type=int)
p.add_argument("--n1", type=int)
p.add_argument("--n2", type=int)
p.add_argument("--name", default="0", help="Target name key for nested payload (default: 0)")
p.add_argument("--iterations", type=int, help="How many cycles/messages to send")
p.add_argument("--interval", type=int, help="Interval between messages in ms (default: delay or 100)")
p.add_argument("--repeat-delay", dest="repeat_delay", type=int, help="Delay between repeats in ms (overrides --interval if set)")
p.add_argument("--hold", type=int, default=1500, help="Hold ms for single send")
return p.parse_args()
def _setup_sigint(loop: asyncio.AbstractEventLoop):
for sig in (signal.SIGINT, signal.SIGTERM):
try:
loop.add_signal_handler(sig, loop.stop)
except NotImplementedError:
pass
async def main_async():
args = _parse_args()
if args.pattern:
iterations = int(args.iterations or 1)
interval_ms = int(args.interval or (args.delay if args.delay is not None else 100))
repeat_ms = int(args.repeat_delay or interval_ms)
async with websockets.connect(args.uri) as ws:
for i in range(iterations):
msg = build_message(
pattern=args.pattern,
n=i,
delay=args.delay,
colors=args.colors,
brightness=args.brightness,
num_leds=args.num_leds,
n1=args.n1,
n2=args.n2,
name=args.name,
)
print(msg)
await ws.send(json.dumps(msg))
await asyncio.sleep(repeat_ms / 1000)
else:
await run_suite(args.uri)
def main():
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
_setup_sigint(loop)
try:
loop.run_until_complete(main_async())
finally:
try:
loop.run_until_complete(asyncio.sleep(0))
except Exception:
pass
loop.close()
if __name__ == "__main__":
main()

58
test/n_chase/1.py Normal file
View File

@@ -0,0 +1,58 @@
#!/usr/bin/env python3
"""
N-chase test 1: Red, on=5, off=5, delay=100ms
Runs forever
Run with: mpremote run test/n_chase/1.py
"""
import patterns
import utime
from settings import Settings
from machine import WDT
print("Starting N-Chase Test 1: Red, on=5, off=5, delay=100ms")
print("Press Ctrl+C to stop")
# Load settings
settings = Settings()
# Initialize patterns using settings
p = patterns.Patterns(
pin=settings["led_pin"],
num_leds=settings["num_leds"],
brightness=255,
delay=100
)
# Configure test parameters
p.n1 = 5 # On width 5
p.n2 = 5 # Off width 5
p.colors = [(255, 0, 0)] # Red
print(f"LED Pin: {settings['led_pin']}")
print(f"LEDs: {settings['num_leds']}")
print(f"Brightness: {p.brightness}")
print(f"Delay: {p.delay}ms")
print(f"On width: {p.n1}")
print(f"Off width: {p.n2}")
print(f"Color: {p.colors[0]}")
# Initialize watchdog timer
wdt = WDT(timeout=10000)
wdt.feed()
# Start pattern
p.select("nc")
print("Pattern started. Running forever...")
# Run forever
try:
while True:
wdt.feed()
utime.sleep_ms(100)
except KeyboardInterrupt:
print("\nStopping...")
p.run = False
p.off()
print("LEDs turned off")

58
test/n_chase/2.py Normal file
View File

@@ -0,0 +1,58 @@
#!/usr/bin/env python3
"""
N-chase test 2: Green, on=10, off=5, delay=150ms
Runs forever
Run with: mpremote run test/n_chase/2.py
"""
import patterns
import utime
from settings import Settings
from machine import WDT
print("Starting N-Chase Test 2: Green, on=10, off=5, delay=150ms")
print("Press Ctrl+C to stop")
# Load settings
settings = Settings()
# Initialize patterns using settings
p = patterns.Patterns(
pin=settings["led_pin"],
num_leds=settings["num_leds"],
brightness=255,
delay=150
)
# Configure test parameters
p.n1 = 10 # On width 10
p.n2 = 5 # Off width 5
p.colors = [(0, 255, 0)] # Green
print(f"LED Pin: {settings['led_pin']}")
print(f"LEDs: {settings['num_leds']}")
print(f"Brightness: {p.brightness}")
print(f"Delay: {p.delay}ms")
print(f"On width: {p.n1}")
print(f"Off width: {p.n2}")
print(f"Color: {p.colors[0]}")
# Initialize watchdog timer
wdt = WDT(timeout=10000)
wdt.feed()
# Start pattern
p.select("nc")
print("Pattern started. Running forever...")
# Run forever
try:
while True:
wdt.feed()
utime.sleep_ms(100)
except KeyboardInterrupt:
print("\nStopping...")
p.run = False
p.off()
print("LEDs turned off")

58
test/n_chase/3.py Normal file
View File

@@ -0,0 +1,58 @@
#!/usr/bin/env python3
"""
N-chase test 3: Blue, on=3, off=10, delay=80ms
Runs forever
Run with: mpremote run test/n_chase/3.py
"""
import patterns
import utime
from settings import Settings
from machine import WDT
print("Starting N-Chase Test 3: Blue, on=3, off=10, delay=80ms")
print("Press Ctrl+C to stop")
# Load settings
settings = Settings()
# Initialize patterns using settings
p = patterns.Patterns(
pin=settings["led_pin"],
num_leds=settings["num_leds"],
brightness=255,
delay=80
)
# Configure test parameters
p.n1 = 3 # On width 3
p.n2 = 10 # Off width 10
p.colors = [(0, 0, 255)] # Blue
print(f"LED Pin: {settings['led_pin']}")
print(f"LEDs: {settings['num_leds']}")
print(f"Brightness: {p.brightness}")
print(f"Delay: {p.delay}ms")
print(f"On width: {p.n1}")
print(f"Off width: {p.n2}")
print(f"Color: {p.colors[0]}")
# Initialize watchdog timer
wdt = WDT(timeout=10000)
wdt.feed()
# Start pattern
p.select("nc")
print("Pattern started. Running forever...")
# Run forever
try:
while True:
wdt.feed()
utime.sleep_ms(100)
except KeyboardInterrupt:
print("\nStopping...")
p.run = False
p.off()
print("LEDs turned off")

58
test/n_chase/4.py Normal file
View File

@@ -0,0 +1,58 @@
#!/usr/bin/env python3
"""
N-chase test 4: Yellow, on=7, off=7, delay=120ms
Runs forever
Run with: mpremote run test/n_chase/4.py
"""
import patterns
import utime
from settings import Settings
from machine import WDT
print("Starting N-Chase Test 4: Yellow, on=7, off=7, delay=120ms")
print("Press Ctrl+C to stop")
# Load settings
settings = Settings()
# Initialize patterns using settings
p = patterns.Patterns(
pin=settings["led_pin"],
num_leds=settings["num_leds"],
brightness=255,
delay=120
)
# Configure test parameters
p.n1 = 7 # On width 7
p.n2 = 7 # Off width 7
p.colors = [(255, 255, 0)] # Yellow
print(f"LED Pin: {settings['led_pin']}")
print(f"LEDs: {settings['num_leds']}")
print(f"Brightness: {p.brightness}")
print(f"Delay: {p.delay}ms")
print(f"On width: {p.n1}")
print(f"Off width: {p.n2}")
print(f"Color: {p.colors[0]}")
# Initialize watchdog timer
wdt = WDT(timeout=10000)
wdt.feed()
# Start pattern
p.select("nc")
print("Pattern started. Running forever...")
# Run forever
try:
while True:
wdt.feed()
utime.sleep_ms(100)
except KeyboardInterrupt:
print("\nStopping...")
p.run = False
p.off()
print("LEDs turned off")

58
test/n_chase/5.py Normal file
View File

@@ -0,0 +1,58 @@
#!/usr/bin/env python3
"""
N-chase test 5: White, on=2, off=8, delay=200ms
Runs forever
Run with: mpremote run test/n_chase/5.py
"""
import patterns
import utime
from settings import Settings
from machine import WDT
print("Starting N-Chase Test 5: White, on=2, off=8, delay=200ms")
print("Press Ctrl+C to stop")
# Load settings
settings = Settings()
# Initialize patterns using settings
p = patterns.Patterns(
pin=settings["led_pin"],
num_leds=settings["num_leds"],
brightness=255,
delay=200
)
# Configure test parameters
p.n1 = 2 # On width 2
p.n2 = 8 # Off width 8
p.colors = [(255, 255, 255)] # White
print(f"LED Pin: {settings['led_pin']}")
print(f"LEDs: {settings['num_leds']}")
print(f"Brightness: {p.brightness}")
print(f"Delay: {p.delay}ms")
print(f"On width: {p.n1}")
print(f"Off width: {p.n2}")
print(f"Color: {p.colors[0]}")
# Initialize watchdog timer
wdt = WDT(timeout=10000)
wdt.feed()
# Start pattern
p.select("nc")
print("Pattern started. Running forever...")
# Run forever
try:
while True:
wdt.feed()
utime.sleep_ms(100)
except KeyboardInterrupt:
print("\nStopping...")
p.run = False
p.off()
print("LEDs turned off")

58
test/n_chase/6.py Normal file
View File

@@ -0,0 +1,58 @@
#!/usr/bin/env python3
"""
N-chase test 6: Magenta, on=15, off=3, delay=60ms
Runs forever
Run with: mpremote run test/n_chase/6.py
"""
import patterns
import utime
from settings import Settings
from machine import WDT
print("Starting N-Chase Test 6: Magenta, on=15, off=3, delay=60ms")
print("Press Ctrl+C to stop")
# Load settings
settings = Settings()
# Initialize patterns using settings
p = patterns.Patterns(
pin=settings["led_pin"],
num_leds=settings["num_leds"],
brightness=255,
delay=60
)
# Configure test parameters
p.n1 = 15 # On width 15
p.n2 = 3 # Off width 3
p.colors = [(255, 0, 255)] # Magenta
print(f"LED Pin: {settings['led_pin']}")
print(f"LEDs: {settings['num_leds']}")
print(f"Brightness: {p.brightness}")
print(f"Delay: {p.delay}ms")
print(f"On width: {p.n1}")
print(f"Off width: {p.n2}")
print(f"Color: {p.colors[0]}")
# Initialize watchdog timer
wdt = WDT(timeout=10000)
wdt.feed()
# Start pattern
p.select("nc")
print("Pattern started. Running forever...")
# Run forever
try:
while True:
wdt.feed()
utime.sleep_ms(100)
except KeyboardInterrupt:
print("\nStopping...")
p.run = False
p.off()
print("LEDs turned off")

58
test/n_chase/7.py Normal file
View File

@@ -0,0 +1,58 @@
#!/usr/bin/env python3
"""
N-chase test 7: Cyan, on=4, off=12, delay=180ms
Runs forever
Run with: mpremote run test/n_chase/7.py
"""
import patterns
import utime
from settings import Settings
from machine import WDT
print("Starting N-Chase Test 7: Cyan, on=4, off=12, delay=180ms")
print("Press Ctrl+C to stop")
# Load settings
settings = Settings()
# Initialize patterns using settings
p = patterns.Patterns(
pin=settings["led_pin"],
num_leds=settings["num_leds"],
brightness=255,
delay=180
)
# Configure test parameters
p.n1 = 4 # On width 4
p.n2 = 12 # Off width 12
p.colors = [(0, 255, 255)] # Cyan
print(f"LED Pin: {settings['led_pin']}")
print(f"LEDs: {settings['num_leds']}")
print(f"Brightness: {p.brightness}")
print(f"Delay: {p.delay}ms")
print(f"On width: {p.n1}")
print(f"Off width: {p.n2}")
print(f"Color: {p.colors[0]}")
# Initialize watchdog timer
wdt = WDT(timeout=10000)
wdt.feed()
# Start pattern
p.select("nc")
print("Pattern started. Running forever...")
# Run forever
try:
while True:
wdt.feed()
utime.sleep_ms(100)
except KeyboardInterrupt:
print("\nStopping...")
p.run = False
p.off()
print("LEDs turned off")

58
test/n_chase/8.py Normal file
View File

@@ -0,0 +1,58 @@
#!/usr/bin/env python3
"""
N-chase test 8: Orange, on=1, off=20, delay=250ms
Runs forever
Run with: mpremote run test/n_chase/8.py
"""
import patterns
import utime
from settings import Settings
from machine import WDT
print("Starting N-Chase Test 8: Orange, on=1, off=20, delay=250ms")
print("Press Ctrl+C to stop")
# Load settings
settings = Settings()
# Initialize patterns using settings
p = patterns.Patterns(
pin=settings["led_pin"],
num_leds=settings["num_leds"],
brightness=255,
delay=250
)
# Configure test parameters
p.n1 = 1 # On width 1
p.n2 = 20 # Off width 20
p.colors = [(255, 128, 0)] # Orange
print(f"LED Pin: {settings['led_pin']}")
print(f"LEDs: {settings['num_leds']}")
print(f"Brightness: {p.brightness}")
print(f"Delay: {p.delay}ms")
print(f"On width: {p.n1}")
print(f"Off width: {p.n2}")
print(f"Color: {p.colors[0]}")
# Initialize watchdog timer
wdt = WDT(timeout=10000)
wdt.feed()
# Start pattern
p.select("nc")
print("Pattern started. Running forever...")
# Run forever
try:
while True:
wdt.feed()
utime.sleep_ms(100)
except KeyboardInterrupt:
print("\nStopping...")
p.run = False
p.off()
print("LEDs turned off")

62
test/n_chase/9.py Normal file
View File

@@ -0,0 +1,62 @@
#!/usr/bin/env python3
"""
N-chase test 9: Cyan, on=20, off=20, delay=200ms
Runs forever
Run with: mpremote run test/n_chase/9.py
"""
import patterns
import utime
from settings import Settings
from machine import WDT
print("Starting N-Chase Test 9: Cyan, on=20, off=20, delay=200ms")
print("Press Ctrl+C to stop")
# Load settings
settings = Settings()
# Initialize patterns using settings
p = patterns.Patterns(
pin=settings["led_pin"],
num_leds=settings["num_leds"],
brightness=255,
delay=200
)
# Configure test parameters
p.n1 = 20 # On width 20
p.n2 = 20 # Off width 20
p.n3 = 20 # Reserved for future use
p.n4 = -5 # Reserved for future use
p.colors = [(0, 255, 255)] # Cyan
print(f"LED Pin: {settings['led_pin']}")
print(f"LEDs: {settings['num_leds']}")
print(f"Brightness: {p.brightness}")
print(f"Delay: {p.delay}ms")
print(f"On width: {p.n1}")
print(f"Off width: {p.n2}")
print(f"n3: {p.n3}")
print(f"n4: {p.n4}")
print(f"Color: {p.colors[0]}")
# Initialize watchdog timer
wdt = WDT(timeout=10000)
wdt.feed()
# Start pattern
p.select("nc")
print("Pattern started. Running forever...")
# Run forever
try:
while True:
wdt.feed()
utime.sleep_ms(100)
except KeyboardInterrupt:
print("\nStopping...")
p.run = False
p.off()
print("LEDs turned off")

View File

@@ -1,55 +0,0 @@
#!/usr/bin/env python3
import uasyncio as asyncio
from machine import WDT
from settings import Settings
from patterns import Patterns
async def main():
s = Settings()
pin = s.get("led_pin", 10)
num = s.get("num_leds", 30)
p = Patterns(pin=pin, num_leds=num)
p.load()
print(p)
p.save()
# print(p)
# wdt = WDT(timeout=10000)
# # Baseline params
# p.set_param("br", 64)
# p.set_param("dl", 500)
# p.set_param("cl", [(255, 0, 0), (0, 0, 255)])
# p.set_param("n1", 200)
# p.set_param("n2", 200)
# p.set_param("n3", 1)
# p.set_param("n4", 1)
# for name, fn in p.patterns.items():
# if fn is None:
# continue
# print(name)
# p.set_param("pt", name)
# task = asyncio.create_task(p.run())
# end = asyncio.get_event_loop().time() + 2.0
# while asyncio.get_event_loop().time() < end:
# wdt.feed()
# await asyncio.sleep_ms(10)
# p.stopped = True
# await task
# p.stopped = False
# p.set_param("pt", "off")
# task = asyncio.create_task(p.run())
# await asyncio.sleep_ms(200)
# p.stopped = True
# await task
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -1,32 +0,0 @@
#!/usr/bin/env python3
import uasyncio as asyncio
import utime
from machine import WDT
from settings import Settings
from patterns import Patterns
async def main():
s = Settings()
pin = s.get("led_pin", 10)
num = s.get("num_leds", 30)
p = Patterns(pin=pin, num_leds=num)
wdt = WDT(timeout=10000)
p.set_param("br", 64)
p.set_param("dl", 200)
p.set_param("cl", [(255, 0, 0), (0, 0, 255)])
p.select("blink")
task = asyncio.create_task(p.run())
start = utime.ticks_ms()
while utime.ticks_diff(utime.ticks_ms(), start) < 1500:
wdt.feed()
await asyncio.sleep_ms(10)
await p.stop()
await task
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -1,132 +0,0 @@
#!/usr/bin/env python3
import uasyncio as asyncio
import utime
from machine import WDT
from settings import Settings
from patterns import Patterns
async def main():
s = Settings()
pin = s.get("led_pin", 10)
num = s.get("num_leds", 30)
p = Patterns(pin=pin, num_leds=num)
wdt = WDT(timeout=10000)
# Test 1: Basic circle (n1=50, n2=100, n3=200, n4=0)
print("Test 1: Basic circle (n1=50, n2=100, n3=200, n4=0)")
p.set_param("br", 255)
p.set_param("n1", 50) # Head moves 50 LEDs/second
p.set_param("n2", 100) # Max length 100 LEDs
p.set_param("n3", 200) # Tail moves 200 LEDs/second
p.set_param("n4", 0) # Min length 0 LEDs
p.set_param("cl", [(255, 0, 0)]) # Red
p.select("circle")
task = asyncio.create_task(p.run())
start = utime.ticks_ms()
# Run for 5 seconds to see full cycle
while utime.ticks_diff(utime.ticks_ms(), start) < 5000:
wdt.feed()
await asyncio.sleep_ms(10)
await p.stop()
await task
# Test 2: Slow growth, fast shrink (n1=20, n2=50, n3=100, n4=0)
print("Test 2: Slow growth, fast shrink (n1=20, n2=50, n3=100, n4=0)")
p.stopped = False
p.set_param("n1", 20) # Head moves 20 LEDs/second (slow)
p.set_param("n2", 50) # Max length 50 LEDs
p.set_param("n3", 100) # Tail moves 100 LEDs/second (fast)
p.set_param("n4", 0) # Min length 0 LEDs
p.set_param("cl", [(0, 255, 0)]) # Green
p.select("circle")
task = asyncio.create_task(p.run())
start = utime.ticks_ms()
while utime.ticks_diff(utime.ticks_ms(), start) < 5000:
wdt.feed()
await asyncio.sleep_ms(10)
await p.stop()
await task
# Test 3: Fast growth, slow shrink (n1=100, n2=30, n3=20, n4=0)
print("Test 3: Fast growth, slow shrink (n1=100, n2=30, n3=20, n4=0)")
p.stopped = False
p.set_param("n1", 100) # Head moves 100 LEDs/second (fast)
p.set_param("n2", 30) # Max length 30 LEDs
p.set_param("n3", 20) # Tail moves 20 LEDs/second (slow)
p.set_param("n4", 0) # Min length 0 LEDs
p.set_param("cl", [(0, 0, 255)]) # Blue
p.select("circle")
task = asyncio.create_task(p.run())
start = utime.ticks_ms()
while utime.ticks_diff(utime.ticks_ms(), start) < 5000:
wdt.feed()
await asyncio.sleep_ms(10)
await p.stop()
await task
# Test 4: With minimum length (n1=50, n2=40, n3=100, n4=10)
print("Test 4: With minimum length (n1=50, n2=40, n3=100, n4=10)")
p.stopped = False
p.set_param("n1", 50) # Head moves 50 LEDs/second
p.set_param("n2", 40) # Max length 40 LEDs
p.set_param("n3", 100) # Tail moves 100 LEDs/second
p.set_param("n4", 10) # Min length 10 LEDs (never fully disappears)
p.set_param("cl", [(255, 255, 0)]) # Yellow
p.select("circle")
task = asyncio.create_task(p.run())
start = utime.ticks_ms()
while utime.ticks_diff(utime.ticks_ms(), start) < 5000:
wdt.feed()
await asyncio.sleep_ms(10)
await p.stop()
await task
# Test 5: Very fast (n1=200, n2=20, n3=200, n4=0)
print("Test 5: Very fast (n1=200, n2=20, n3=200, n4=0)")
p.stopped = False
p.set_param("n1", 200) # Head moves 200 LEDs/second (very fast)
p.set_param("n2", 20) # Max length 20 LEDs
p.set_param("n3", 200) # Tail moves 200 LEDs/second (very fast)
p.set_param("n4", 0) # Min length 0 LEDs
p.set_param("cl", [(255, 0, 255)]) # Magenta
p.select("circle")
task = asyncio.create_task(p.run())
start = utime.ticks_ms()
while utime.ticks_diff(utime.ticks_ms(), start) < 3000:
wdt.feed()
await asyncio.sleep_ms(10)
await p.stop()
await task
# Test 6: Very slow (n1=10, n2=25, n3=10, n4=0)
print("Test 6: Very slow (n1=10, n2=25, n3=10, n4=0)")
p.stopped = False
p.set_param("n1", 10) # Head moves 10 LEDs/second (very slow)
p.set_param("n2", 25) # Max length 25 LEDs
p.set_param("n3", 10) # Tail moves 10 LEDs/second (very slow)
p.set_param("n4", 0) # Min length 0 LEDs
p.set_param("cl", [(0, 255, 255)]) # Cyan
p.select("circle")
task = asyncio.create_task(p.run())
start = utime.ticks_ms()
while utime.ticks_diff(utime.ticks_ms(), start) < 5000:
wdt.feed()
await asyncio.sleep_ms(10)
await p.stop()
await task
# Cleanup
print("Test complete, turning off")
p.stopped = False
p.select("off")
task = asyncio.create_task(p.run())
await asyncio.sleep_ms(100)
await p.stop()
await task
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -1,144 +0,0 @@
#!/usr/bin/env python3
import uasyncio as asyncio
import utime
from machine import WDT
from settings import Settings
from patterns import Patterns
async def main():
s = Settings()
pin = s.get("led_pin", 10)
num = s.get("num_leds", 30)
p = Patterns(pin=pin, num_leds=num)
wdt = WDT(timeout=10000)
# Test 1: Basic n_chase (n1=5, n2=5, n3=1, n4=1)
print("Test 1: Basic n_chase (n1=5, n2=5, n3=1, n4=1)")
p.set_param("br", 255)
p.set_param("dl", 200)
p.set_param("n1", 5) # 5 LEDs color0
p.set_param("n2", 5) # 5 LEDs color1
p.set_param("n3", 1) # Move 1 forward on even steps
p.set_param("n4", 1) # Move 1 forward on odd steps
p.set_param("cl", [(255, 0, 0), (0, 255, 0)]) # Red and Green
p.select("n_chase")
task = asyncio.create_task(p.run())
start = utime.ticks_ms()
while utime.ticks_diff(utime.ticks_ms(), start) < 3000:
wdt.feed()
await asyncio.sleep_ms(10)
await p.stop()
await task
# Test 2: Forward and backward (n3=2, n4=-1)
print("Test 2: Forward and backward (n3=2, n4=-1)")
p.stopped = False
p.set_param("n1", 3)
p.set_param("n2", 3)
p.set_param("n3", 2) # Move 2 forward on even steps
p.set_param("n4", -1) # Move 1 backward on odd steps
p.set_param("dl", 150)
p.set_param("cl", [(0, 0, 255), (255, 255, 0)]) # Blue and Yellow
p.select("n_chase")
task = asyncio.create_task(p.run())
start = utime.ticks_ms()
while utime.ticks_diff(utime.ticks_ms(), start) < 3000:
wdt.feed()
await asyncio.sleep_ms(10)
await p.stop()
await task
# Test 3: Large segments (n1=10, n2=5)
print("Test 3: Large segments (n1=10, n2=5, n3=3, n4=3)")
p.stopped = False
p.set_param("n1", 10) # 10 LEDs color0
p.set_param("n2", 5) # 5 LEDs color1
p.set_param("n3", 3) # Move 3 forward
p.set_param("n4", 3) # Move 3 forward
p.set_param("dl", 200)
p.set_param("cl", [(255, 128, 0), (128, 0, 255)]) # Orange and Purple
p.select("n_chase")
task = asyncio.create_task(p.run())
start = utime.ticks_ms()
while utime.ticks_diff(utime.ticks_ms(), start) < 3000:
wdt.feed()
await asyncio.sleep_ms(10)
await p.stop()
await task
# Test 4: Fast movement (n3=5, n4=5)
print("Test 4: Fast movement (n3=5, n4=5)")
p.stopped = False
p.set_param("n1", 4)
p.set_param("n2", 4)
p.set_param("n3", 5) # Move 5 forward
p.set_param("n4", 5) # Move 5 forward
p.set_param("dl", 100)
p.set_param("cl", [(255, 0, 255), (0, 255, 255)]) # Magenta and Cyan
p.select("n_chase")
task = asyncio.create_task(p.run())
start = utime.ticks_ms()
while utime.ticks_diff(utime.ticks_ms(), start) < 2000:
wdt.feed()
await asyncio.sleep_ms(10)
await p.stop()
await task
# Test 5: Backward movement (n3=-2, n4=-2)
print("Test 5: Backward movement (n3=-2, n4=-2)")
p.stopped = False
p.set_param("n1", 6)
p.set_param("n2", 4)
p.set_param("n3", -2) # Move 2 backward
p.set_param("n4", -2) # Move 2 backward
p.set_param("dl", 200)
p.set_param("cl", [(255, 255, 255), (0, 0, 0)]) # White and Black
p.select("n_chase")
task = asyncio.create_task(p.run())
start = utime.ticks_ms()
while utime.ticks_diff(utime.ticks_ms(), start) < 3000:
wdt.feed()
await asyncio.sleep_ms(10)
await p.stop()
await task
# Test 6: Alternating forward/backward (n3=3, n4=-2)
print("Test 6: Alternating forward/backward (n3=3, n4=-2)")
p.stopped = False
p.set_param("n1", 5)
p.set_param("n2", 5)
p.set_param("n3", 3) # Move 3 forward on even steps
p.set_param("n4", -2) # Move 2 backward on odd steps
p.set_param("dl", 250)
p.set_param("cl", [(255, 0, 0), (0, 255, 0)]) # Red and Green
p.select("n_chase")
task = asyncio.create_task(p.run())
start = utime.ticks_ms()
while utime.ticks_diff(utime.ticks_ms(), start) < 4000:
wdt.feed()
await asyncio.sleep_ms(10)
await p.stop()
await task
# Cleanup
print("Test complete, turning off")
p.stopped = False
p.select("off")
task = asyncio.create_task(p.run())
await asyncio.sleep_ms(100)
await p.stop()
await task
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -1,26 +0,0 @@
#!/usr/bin/env python3
import uasyncio as asyncio
from machine import WDT
from settings import Settings
from patterns import Patterns
async def main():
s = Settings()
pin = s.get("led_pin", 10)
num = s.get("num_leds", 30)
p = Patterns(pin=pin, num_leds=num)
wdt = WDT(timeout=10000)
p.select("off")
task = asyncio.create_task(p.run())
wdt.feed()
await asyncio.sleep_ms(200)
p.stopped = True
await task
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -1,34 +0,0 @@
#!/usr/bin/env python3
import uasyncio as asyncio
from machine import WDT
from settings import Settings
from patterns import Patterns
async def main():
s = Settings()
pin = s.get("led_pin", 10)
num = s.get("num_leds", 30)
p = Patterns(pin=pin, num_leds=num)
wdt = WDT(timeout=10000)
p.set_param("br", 64)
p.set_param("dl", 120)
p.set_param("cl", [(255, 0, 0), (0, 0, 255)])
p.select("on")
task = asyncio.create_task(p.run())
await asyncio.sleep_ms(800)
p.stopped = True
await task
p.stopped = False
p.select("off")
task = asyncio.create_task(p.run())
await asyncio.sleep_ms(100)
p.stopped = True
await task
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -1,160 +0,0 @@
#!/usr/bin/env python3
import uasyncio as asyncio
import utime
from machine import WDT
from settings import Settings
from patterns import Patterns
async def main():
s = Settings()
pin = s.get("led_pin", 10)
num = s.get("num_leds", 30)
p = Patterns(pin=pin, num_leds=num)
wdt = WDT(timeout=10000)
# Test 1: Basic pulse with attack, hold, and decay
print("Test 1: Basic pulse pattern")
p.set_param("br", 255)
p.set_param("dl", 1000) # 1 second delay between pulses
p.set_param("auto", True) # Run continuously
p.set_param("cl", [(255, 255, 255), (255, 255, 255)])
p.set_param("n1", 200) # Attack: 200ms
p.set_param("n2", 200) # Hold: 200ms
p.set_param("n3", 200) # Decay: 200ms
p.select("pulse")
task = asyncio.create_task(p.run())
start = utime.ticks_ms()
# Run for 3 seconds to see multiple pulse cycles
while utime.ticks_diff(utime.ticks_ms(), start) < 3000:
wdt.feed()
await asyncio.sleep_ms(10)
p.stopped = True
await task
# Test 2: Fast pulse with shorter delay
print("Test 2: Fast pulse pattern")
p.stopped = False
p.set_param("dl", 500) # 500ms delay between pulses
p.set_param("auto", True) # Run continuously
p.set_param("n1", 100) # Attack: 100ms
p.set_param("n2", 100) # Hold: 100ms
p.set_param("n3", 100) # Decay: 100ms
p.select("pulse")
task = asyncio.create_task(p.run())
start = utime.ticks_ms()
# Run for 2 seconds
while utime.ticks_diff(utime.ticks_ms(), start) < 2000:
wdt.feed()
await asyncio.sleep_ms(10)
p.stopped = True
await task
# Test 3: Colored pulse
print("Test 3: Colored pulse pattern")
p.stopped = False
p.set_param("dl", 800)
p.set_param("auto", True) # Run continuously
p.set_param("cl", [(255, 0, 0), (0, 0, 255)]) # Red pulse
p.set_param("n1", 150)
p.set_param("n2", 150)
p.set_param("n3", 150)
p.select("pulse")
task = asyncio.create_task(p.run())
start = utime.ticks_ms()
# Run for 2 seconds
while utime.ticks_diff(utime.ticks_ms(), start) < 2000:
wdt.feed()
await asyncio.sleep_ms(10)
p.stopped = True
await task
# Test 4: Verify delay restart timing
print("Test 4: Testing delay restart timing")
p.stopped = False
p.set_param("dl", 500) # 500ms delay
p.set_param("auto", True) # Run continuously
p.set_param("n1", 100) # Total attack+hold+decay = 300ms, should wait 200ms more
p.set_param("n2", 100)
p.set_param("n3", 100)
p.select("pulse")
task = asyncio.create_task(p.run())
# Monitor pulse cycles
cycle_count = 0
last_cycle_time = utime.ticks_ms()
start = utime.ticks_ms()
while utime.ticks_diff(utime.ticks_ms(), start) < 3000:
wdt.feed()
await asyncio.sleep_ms(10)
# Check if we're near the start of a new cycle (LEDs off)
# This is a simplified check - in practice you'd monitor LED state
p.stopped = True
await task
# Test 5: Single-shot pulse (auto=False)
print("Test 5: Single-shot pulse (auto=False)")
p.stopped = False
p.set_param("dl", 500) # Delay between pulses
p.set_param("auto", False) # Run only once
p.set_param("cl", [(0, 255, 0), (0, 255, 0)]) # Green pulse
p.set_param("n1", 150) # Attack: 150ms
p.set_param("n2", 150) # Hold: 150ms
p.set_param("n3", 150) # Decay: 150ms
p.select("pulse")
task = asyncio.create_task(p.run())
# The pulse should complete once and then stop
# Total time should be ~450ms (attack + hold + decay)
# Wait a bit longer to verify it doesn't repeat
start = utime.ticks_ms()
while utime.ticks_diff(utime.ticks_ms(), start) < 1000:
wdt.feed()
await asyncio.sleep_ms(10)
# Task should have completed on its own (not stopped manually)
# Verify it's stopped
if not p.stopped:
print("Warning: Pulse should have stopped automatically with auto=False")
p.stopped = True
await task
# Test 6: Pulse cycles through colors
print("Test 6: Pulse cycles through colors")
p.stopped = False
p.set_param("dl", 300) # cycle interval
p.set_param("auto", True) # Run continuously
p.set_param("cl", [
(255, 0, 0), # red
(0, 255, 0), # green
(0, 0, 255), # blue
(255, 255, 0), # yellow
])
p.set_param("n1", 50)
p.set_param("n2", 0)
p.set_param("n3", 50)
p.select("pulse")
task = asyncio.create_task(p.run())
start = utime.ticks_ms()
# Run long enough to observe multiple color cycles
while utime.ticks_diff(utime.ticks_ms(), start) < 10000:
wdt.feed()
await asyncio.sleep_ms(10)
p.stopped = True
await task
# Cleanup
print("Test complete, turning off")
p.stopped = False
p.select("off")
task = asyncio.create_task(p.run())
await asyncio.sleep_ms(100)
p.stopped = True
await task
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -1,167 +0,0 @@
#!/usr/bin/env python3
import uasyncio as asyncio
import utime
from machine import WDT
from settings import Settings
from patterns import Patterns
async def main():
s = Settings()
pin = s.get("led_pin", 10)
num = s.get("num_leds", 30)
p = Patterns(pin=pin, num_leds=num)
wdt = WDT(timeout=10000)
# Test 1: Basic rainbow with auto=True (continuous)
print("Test 1: Basic rainbow (auto=True, n1=1)")
p.set_param("br", 255)
p.set_param("dl", 100) # Delay affects animation speed
p.set_param("n1", 1) # Step increment of 1
p.set_param("auto", True) # Run continuously
p.select("rainbow")
task = asyncio.create_task(p.run())
start = utime.ticks_ms()
# Run for 3 seconds to see rainbow animation
while utime.ticks_diff(utime.ticks_ms(), start) < 3000:
wdt.feed()
await asyncio.sleep_ms(10)
await p.stop()
await task
# Test 2: Fast rainbow
print("Test 2: Fast rainbow (low delay, n1=1)")
p.stopped = False
p.set_param("dl", 50) # Faster animation
p.set_param("n1", 1)
p.set_param("auto", True)
p.select("rainbow")
task = asyncio.create_task(p.run())
start = utime.ticks_ms()
while utime.ticks_diff(utime.ticks_ms(), start) < 2000:
wdt.feed()
await asyncio.sleep_ms(10)
await p.stop()
await task
# Test 3: Slow rainbow
print("Test 3: Slow rainbow (high delay, n1=1)")
p.stopped = False
p.set_param("dl", 500) # Slower animation
p.set_param("n1", 1)
p.set_param("auto", True)
p.select("rainbow")
task = asyncio.create_task(p.run())
start = utime.ticks_ms()
while utime.ticks_diff(utime.ticks_ms(), start) < 3000:
wdt.feed()
await asyncio.sleep_ms(10)
await p.stop()
await task
# Test 4: Low brightness rainbow
print("Test 4: Low brightness rainbow (n1=1)")
p.stopped = False
p.set_param("br", 64) # Low brightness
p.set_param("dl", 100)
p.set_param("n1", 1)
p.set_param("auto", True)
p.select("rainbow")
task = asyncio.create_task(p.run())
start = utime.ticks_ms()
while utime.ticks_diff(utime.ticks_ms(), start) < 2000:
wdt.feed()
await asyncio.sleep_ms(10)
await p.stop()
await task
# Test 5: Single-step rainbow (auto=False)
print("Test 5: Single-step rainbow (auto=False, n1=1)")
p.stopped = False
p.set_param("br", 255)
p.set_param("dl", 100)
p.set_param("n1", 1)
p.set_param("auto", False) # Run once per call
p.set_param("step", 0) # Reset step
p.select("rainbow")
# Call rainbow multiple times to see step progression
for i in range(10):
task = asyncio.create_task(p.run())
await task
await asyncio.sleep_ms(100) # Small delay between steps
wdt.feed()
# Test 6: Verify step updates correctly
print("Test 6: Verify step updates (auto=False, n1=1)")
p.stopped = False
p.set_param("n1", 1)
initial_step = p.step
p.select("rainbow")
task = asyncio.create_task(p.run())
await task
final_step = p.step
print(f"Step updated from {initial_step} to {final_step} (expected increment: 1)")
# Test 7: Fast step increment (n1=5)
print("Test 7: Fast rainbow (n1=5, auto=True)")
p.stopped = False
p.set_param("br", 255)
p.set_param("dl", 100)
p.set_param("n1", 5) # Step increment of 5 (5x faster)
p.set_param("auto", True)
p.select("rainbow")
task = asyncio.create_task(p.run())
start = utime.ticks_ms()
while utime.ticks_diff(utime.ticks_ms(), start) < 2000:
wdt.feed()
await asyncio.sleep_ms(10)
await p.stop()
await task
# Test 8: Very fast step increment (n1=10)
print("Test 8: Very fast rainbow (n1=10, auto=True)")
p.stopped = False
p.set_param("n1", 10) # Step increment of 10 (10x faster)
p.set_param("auto", True)
p.select("rainbow")
task = asyncio.create_task(p.run())
start = utime.ticks_ms()
while utime.ticks_diff(utime.ticks_ms(), start) < 2000:
wdt.feed()
await asyncio.sleep_ms(10)
await p.stop()
await task
# Test 9: Verify n1 controls step increment (auto=False)
print("Test 9: Verify n1 step increment (auto=False, n1=5)")
p.stopped = False
p.set_param("n1", 5) # Step increment of 5
p.set_param("auto", False)
p.set_param("step", 0) # Reset step
initial_step = p.step
p.select("rainbow")
task = asyncio.create_task(p.run())
await task
final_step = p.step
expected_step = (initial_step + 5) % 256
print(f"Step updated from {initial_step} to {final_step} (expected: {expected_step})")
if final_step == expected_step:
print("✓ n1 step increment working correctly")
else:
print(f"✗ Step increment mismatch! Expected {expected_step}, got {final_step}")
# Cleanup
print("Test complete, turning off")
p.stopped = False
p.select("off")
task = asyncio.create_task(p.run())
await asyncio.sleep_ms(100)
await p.stop()
await task
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -1,165 +0,0 @@
#!/usr/bin/env python3
import uasyncio as asyncio
import utime
from machine import WDT
from settings import Settings
from patterns import Patterns
async def main():
s = Settings()
pin = s.get("led_pin", 10)
num = s.get("num_leds", 30)
p = Patterns(pin=pin, num_leds=num)
wdt = WDT(timeout=10000)
# Test 1: Basic transition with 2 colors (auto=True, cycles continuously)
print("Test 1: Basic transition (2 colors, 1000ms delay, auto=True)")
p.set_param("br", 255)
p.set_param("dl", 1000) # 1 second transition time
p.set_param("auto", True) # Cycle continuously
p.set_param("cl", [(255, 0, 0), (0, 255, 0)]) # Red to Green
p.select("transition")
task = asyncio.create_task(p.run())
start = utime.ticks_ms()
# Run for 5 seconds to see multiple transitions
while utime.ticks_diff(utime.ticks_ms(), start) < 5000:
wdt.feed()
await asyncio.sleep_ms(10)
p.stopped = True
await task
# Test 2: Fast transition (auto=True, cycles continuously)
print("Test 2: Fast transition (500ms delay, auto=True)")
p.stopped = False
p.set_param("dl", 500) # 500ms transition time
p.set_param("auto", True) # Cycle continuously
p.set_param("cl", [(0, 0, 255), (255, 255, 0)]) # Blue to Yellow
p.select("transition")
task = asyncio.create_task(p.run())
start = utime.ticks_ms()
# Run for 3 seconds
while utime.ticks_diff(utime.ticks_ms(), start) < 3000:
wdt.feed()
await asyncio.sleep_ms(10)
p.stopped = True
await task
# Test 3: Multiple colors transition (auto=True, cycles continuously)
print("Test 3: Multiple colors transition (3 colors, auto=True)")
p.stopped = False
p.set_param("dl", 800)
p.set_param("auto", True) # Cycle continuously
p.set_param("cl", [
(255, 0, 0), # Red
(0, 255, 0), # Green
(0, 0, 255), # Blue
])
p.select("transition")
task = asyncio.create_task(p.run())
start = utime.ticks_ms()
# Run for 8 seconds to see full cycles
while utime.ticks_diff(utime.ticks_ms(), start) < 8000:
wdt.feed()
await asyncio.sleep_ms(10)
p.stopped = True
await task
# Test 4: Single color (should just stay that color)
print("Test 4: Single color (should stay that color)")
p.stopped = False
p.set_param("dl", 1000)
p.set_param("cl", [(255, 128, 0)]) # Orange
p.select("transition")
task = asyncio.create_task(p.run())
start = utime.ticks_ms()
# Run for 3 seconds
while utime.ticks_diff(utime.ticks_ms(), start) < 3000:
wdt.feed()
await asyncio.sleep_ms(10)
p.stopped = True
await task
# Test 5: Many colors transition (auto=True, cycles continuously)
print("Test 5: Many colors transition (5 colors, auto=True)")
p.stopped = False
p.set_param("dl", 600)
p.set_param("auto", True) # Cycle continuously
p.set_param("cl", [
(255, 0, 0), # Red
(255, 128, 0), # Orange
(255, 255, 0), # Yellow
(0, 255, 0), # Green
(0, 0, 255), # Blue
])
p.select("transition")
task = asyncio.create_task(p.run())
start = utime.ticks_ms()
# Run for 10 seconds to see multiple cycles
while utime.ticks_diff(utime.ticks_ms(), start) < 10000:
wdt.feed()
await asyncio.sleep_ms(10)
p.stopped = True
await task
# Test 6: Low brightness transition (auto=True, cycles continuously)
print("Test 6: Low brightness transition (auto=True)")
p.stopped = False
p.set_param("br", 64) # Low brightness
p.set_param("dl", 1000)
p.set_param("auto", True) # Cycle continuously
p.set_param("cl", [(255, 0, 0), (0, 255, 0)])
p.select("transition")
task = asyncio.create_task(p.run())
start = utime.ticks_ms()
# Run for 3 seconds
while utime.ticks_diff(utime.ticks_ms(), start) < 3000:
wdt.feed()
await asyncio.sleep_ms(10)
p.stopped = True
await task
# Test 7: Single-shot transition (auto=False, only color1 to color2)
print("Test 7: Single-shot transition (auto=False, color1 to color2 only)")
p.stopped = False
p.set_param("br", 255)
p.set_param("dl", 1000) # 1 second transition
p.set_param("auto", False) # Run only once
p.set_param("cl", [
(255, 0, 0), # Red (color1)
(0, 255, 0), # Green (color2)
(0, 0, 255), # Blue (should be ignored)
(255, 255, 0), # Yellow (should be ignored)
])
p.select("transition")
task = asyncio.create_task(p.run())
# The transition should complete once (color1 to color2) and then stop
# Total time should be ~1000ms
# Wait a bit longer to verify it doesn't continue
start = utime.ticks_ms()
while utime.ticks_diff(utime.ticks_ms(), start) < 2000:
wdt.feed()
await asyncio.sleep_ms(10)
# Task should have completed on its own (not stopped manually)
# Verify it's stopped
if not p.stopped:
print("Warning: Transition should have stopped automatically with auto=False")
p.stopped = True
await task
# Cleanup
print("Test complete, turning off")
p.stopped = False
p.select("off")
task = asyncio.create_task(p.run())
await asyncio.sleep_ms(100)
p.stopped = True
await task
if __name__ == "__main__":
asyncio.run(main())

56
test/rainbow/1.py Normal file
View File

@@ -0,0 +1,56 @@
#!/usr/bin/env python3
"""
Rainbow test 1: Single node (full spectrum), 3 second cycle, full brightness
Runs forever
Run with: mpremote run test/rainbow/1.py
"""
import patterns
import utime
from settings import Settings
from machine import WDT
print("Starting Rainbow Test 1: Single node (full spectrum), 3 second cycle")
print("Press Ctrl+C to stop")
# Load settings
settings = Settings()
# Initialize patterns using settings
p = patterns.Patterns(
pin=settings["led_pin"],
num_leds=settings["num_leds"],
brightness=255,
delay=3000 # 3 second cycle
)
# Configure test parameters
p.n1 = 1 # 1 node (full spectrum)
p.delay = 3000 # 3 second cycle
p.brightness = 255 # Full brightness
print(f"LED Pin: {settings['led_pin']}")
print(f"LEDs: {settings['num_leds']}")
print(f"Brightness: {p.brightness}")
print(f"Delay: {p.delay}ms")
print(f"Nodes: {p.n1}")
# Initialize watchdog timer
wdt = WDT(timeout=10000)
wdt.feed()
# Start pattern
p.select("rb")
print("Pattern started. Running forever...")
# Run forever
try:
while True:
wdt.feed()
utime.sleep_ms(100)
except KeyboardInterrupt:
print("\nStopping...")
p.run = False
p.off()
print("LEDs turned off")

56
test/rainbow/2.py Normal file
View File

@@ -0,0 +1,56 @@
#!/usr/bin/env python3
"""
Rainbow test 2: Two nodes, 2 second cycle, full brightness
Runs forever
Run with: mpremote run test/rainbow/2.py
"""
import patterns
import utime
from settings import Settings
from machine import WDT
print("Starting Rainbow Test 2: Two nodes, 2 second cycle")
print("Press Ctrl+C to stop")
# Load settings
settings = Settings()
# Initialize patterns using settings
p = patterns.Patterns(
pin=settings["led_pin"],
num_leds=settings["num_leds"],
brightness=255,
delay=2000 # 2 second cycle
)
# Configure test parameters
p.n1 = 2 # 2 nodes
p.delay = 2000 # 2 second cycle
p.brightness = 255 # Full brightness
print(f"LED Pin: {settings['led_pin']}")
print(f"LEDs: {settings['num_leds']}")
print(f"Brightness: {p.brightness}")
print(f"Delay: {p.delay}ms")
print(f"Nodes: {p.n1}")
# Initialize watchdog timer
wdt = WDT(timeout=10000)
wdt.feed()
# Start pattern
p.select("rb")
print("Pattern started. Running forever...")
# Run forever
try:
while True:
wdt.feed()
utime.sleep_ms(100)
except KeyboardInterrupt:
print("\nStopping...")
p.run = False
p.off()
print("LEDs turned off")

56
test/rainbow/3.py Normal file
View File

@@ -0,0 +1,56 @@
#!/usr/bin/env python3
"""
Rainbow test 3: Three nodes (RGB), 1.5 second cycle, full brightness
Runs forever
Run with: mpremote run test/rainbow/3.py
"""
import patterns
import utime
from settings import Settings
from machine import WDT
print("Starting Rainbow Test 3: Three nodes (RGB), 1.5 second cycle")
print("Press Ctrl+C to stop")
# Load settings
settings = Settings()
# Initialize patterns using settings
p = patterns.Patterns(
pin=settings["led_pin"],
num_leds=settings["num_leds"],
brightness=255,
delay=1500 # 1.5 second cycle
)
# Configure test parameters
p.n1 = 3 # 3 nodes
p.delay = 1500 # 1.5 second cycle
p.brightness = 255 # Full brightness
print(f"LED Pin: {settings['led_pin']}")
print(f"LEDs: {settings['num_leds']}")
print(f"Brightness: {p.brightness}")
print(f"Delay: {p.delay}ms")
print(f"Nodes: {p.n1}")
# Initialize watchdog timer
wdt = WDT(timeout=10000)
wdt.feed()
# Start pattern
p.select("rb")
print("Pattern started. Running forever...")
# Run forever
try:
while True:
wdt.feed()
utime.sleep_ms(100)
except KeyboardInterrupt:
print("\nStopping...")
p.run = False
p.off()
print("LEDs turned off")

56
test/rainbow/4.py Normal file
View File

@@ -0,0 +1,56 @@
#!/usr/bin/env python3
"""
Rainbow test 4: Four nodes (RYGB), 1 second cycle, full brightness
Runs forever
Run with: mpremote run test/rainbow/4.py
"""
import patterns
import utime
from settings import Settings
from machine import WDT
print("Starting Rainbow Test 4: Four nodes (RYGB), 1 second cycle")
print("Press Ctrl+C to stop")
# Load settings
settings = Settings()
# Initialize patterns using settings
p = patterns.Patterns(
pin=settings["led_pin"],
num_leds=settings["num_leds"],
brightness=255,
delay=1000 # 1 second cycle
)
# Configure test parameters
p.n1 = 4 # 4 nodes
p.delay = 1000 # 1 second cycle
p.brightness = 255 # Full brightness
print(f"LED Pin: {settings['led_pin']}")
print(f"LEDs: {settings['num_leds']}")
print(f"Brightness: {p.brightness}")
print(f"Delay: {p.delay}ms")
print(f"Nodes: {p.n1}")
# Initialize watchdog timer
wdt = WDT(timeout=10000)
wdt.feed()
# Start pattern
p.select("rb")
print("Pattern started. Running forever...")
# Run forever
try:
while True:
wdt.feed()
utime.sleep_ms(100)
except KeyboardInterrupt:
print("\nStopping...")
p.run = False
p.off()
print("LEDs turned off")

56
test/rainbow/5.py Normal file
View File

@@ -0,0 +1,56 @@
#!/usr/bin/env python3
"""
Rainbow test 5: Six nodes (ROYGBP), 0.8 second cycle, full brightness
Runs forever
Run with: mpremote run test/rainbow/5.py
"""
import patterns
import utime
from settings import Settings
from machine import WDT
print("Starting Rainbow Test 5: Six nodes (ROYGBP), 0.8 second cycle")
print("Press Ctrl+C to stop")
# Load settings
settings = Settings()
# Initialize patterns using settings
p = patterns.Patterns(
pin=settings["led_pin"],
num_leds=settings["num_leds"],
brightness=255,
delay=800 # 0.8 second cycle
)
# Configure test parameters
p.n1 = 6 # 6 nodes
p.delay = 800 # 0.8 second cycle
p.brightness = 255 # Full brightness
print(f"LED Pin: {settings['led_pin']}")
print(f"LEDs: {settings['num_leds']}")
print(f"Brightness: {p.brightness}")
print(f"Delay: {p.delay}ms")
print(f"Nodes: {p.n1}")
# Initialize watchdog timer
wdt = WDT(timeout=10000)
wdt.feed()
# Start pattern
p.select("rb")
print("Pattern started. Running forever...")
# Run forever
try:
while True:
wdt.feed()
utime.sleep_ms(100)
except KeyboardInterrupt:
print("\nStopping...")
p.run = False
p.off()
print("LEDs turned off")

56
test/rainbow/6.py Normal file
View File

@@ -0,0 +1,56 @@
#!/usr/bin/env python3
"""
Rainbow test 6: Eight nodes, 0.6 second cycle, full brightness
Runs forever
Run with: mpremote run test/rainbow/6.py
"""
import patterns
import utime
from settings import Settings
from machine import WDT
print("Starting Rainbow Test 6: Eight nodes, 0.6 second cycle")
print("Press Ctrl+C to stop")
# Load settings
settings = Settings()
# Initialize patterns using settings
p = patterns.Patterns(
pin=settings["led_pin"],
num_leds=settings["num_leds"],
brightness=255,
delay=600 # 0.6 second cycle
)
# Configure test parameters
p.n1 = 8 # 8 nodes
p.delay = 600 # 0.6 second cycle
p.brightness = 255 # Full brightness
print(f"LED Pin: {settings['led_pin']}")
print(f"LEDs: {settings['num_leds']}")
print(f"Brightness: {p.brightness}")
print(f"Delay: {p.delay}ms")
print(f"Nodes: {p.n1}")
# Initialize watchdog timer
wdt = WDT(timeout=10000)
wdt.feed()
# Start pattern
p.select("rb")
print("Pattern started. Running forever...")
# Run forever
try:
while True:
wdt.feed()
utime.sleep_ms(100)
except KeyboardInterrupt:
print("\nStopping...")
p.run = False
p.off()
print("LEDs turned off")

56
test/rainbow/7.py Normal file
View File

@@ -0,0 +1,56 @@
#!/usr/bin/env python3
"""
Rainbow test 7: Twelve nodes (fine gradation), 0.4 second cycle, full brightness
Runs forever
Run with: mpremote run test/rainbow/7.py
"""
import patterns
import utime
from settings import Settings
from machine import WDT
print("Starting Rainbow Test 7: Twelve nodes (fine gradation), 0.4 second cycle")
print("Press Ctrl+C to stop")
# Load settings
settings = Settings()
# Initialize patterns using settings
p = patterns.Patterns(
pin=settings["led_pin"],
num_leds=settings["num_leds"],
brightness=255,
delay=400 # 0.4 second cycle
)
# Configure test parameters
p.n1 = 12 # 12 nodes
p.delay = 400 # 0.4 second cycle
p.brightness = 255 # Full brightness
print(f"LED Pin: {settings['led_pin']}")
print(f"LEDs: {settings['num_leds']}")
print(f"Brightness: {p.brightness}")
print(f"Delay: {p.delay}ms")
print(f"Nodes: {p.n1}")
# Initialize watchdog timer
wdt = WDT(timeout=10000)
wdt.feed()
# Start pattern
p.select("rb")
print("Pattern started. Running forever...")
# Run forever
try:
while True:
wdt.feed()
utime.sleep_ms(100)
except KeyboardInterrupt:
print("\nStopping...")
p.run = False
p.off()
print("LEDs turned off")

56
test/rainbow/8.py Normal file
View File

@@ -0,0 +1,56 @@
#!/usr/bin/env python3
"""
Rainbow test 8: Many nodes (20), slow cycle (2 seconds), full brightness
Runs forever
Run with: mpremote run test/rainbow/8.py
"""
import patterns
import utime
from settings import Settings
from machine import WDT
print("Starting Rainbow Test 8: Many nodes (20), slow cycle (2 seconds)")
print("Press Ctrl+C to stop")
# Load settings
settings = Settings()
# Initialize patterns using settings
p = patterns.Patterns(
pin=settings["led_pin"],
num_leds=settings["num_leds"],
brightness=255,
delay=2000 # 2 second cycle
)
# Configure test parameters
p.n1 = 20 # 20 nodes
p.delay = 2000 # 2 second cycle
p.brightness = 255 # Full brightness
print(f"LED Pin: {settings['led_pin']}")
print(f"LEDs: {settings['num_leds']}")
print(f"Brightness: {p.brightness}")
print(f"Delay: {p.delay}ms")
print(f"Nodes: {p.n1}")
# Initialize watchdog timer
wdt = WDT(timeout=10000)
wdt.feed()
# Start pattern
p.select("rb")
print("Pattern started. Running forever...")
# Run forever
try:
while True:
wdt.feed()
utime.sleep_ms(100)
except KeyboardInterrupt:
print("\nStopping...")
p.run = False
p.off()
print("LEDs turned off")

56
test/rainbow/9.py Normal file
View File

@@ -0,0 +1,56 @@
#!/usr/bin/env python3
"""
Rainbow test 9: Five nodes, 1 second cycle, full brightness
Runs forever
Run with: mpremote run test/rainbow/9.py
"""
import patterns
import utime
from settings import Settings
from machine import WDT
print("Starting Rainbow Test 9: Five nodes, 1 second cycle")
print("Press Ctrl+C to stop")
# Load settings
settings = Settings()
# Initialize patterns using settings
p = patterns.Patterns(
pin=settings["led_pin"],
num_leds=settings["num_leds"],
brightness=255,
delay=1000 # 1 second cycle
)
# Configure test parameters
p.n1 = 5 # 5 nodes
p.delay = 1000 # 1 second cycle
p.brightness = 255 # Full brightness
print(f"LED Pin: {settings['led_pin']}")
print(f"LEDs: {settings['num_leds']}")
print(f"Brightness: {p.brightness}")
print(f"Delay: {p.delay}ms")
print(f"Nodes: {p.n1}")
# Initialize watchdog timer
wdt = WDT(timeout=10000)
wdt.feed()
# Start pattern
p.select("rb")
print("Pattern started. Running forever...")
# Run forever
try:
while True:
wdt.feed()
utime.sleep_ms(100)
except KeyboardInterrupt:
print("\nStopping...")
p.run = False
p.off()
print("LEDs turned off")

100
test/rainbow/main.py Normal file
View File

@@ -0,0 +1,100 @@
#!/usr/bin/env python3
"""
Rainbow test suite - runs all tests in sequence forever
Run with: mpremote run test/rainbow/main.py
"""
import patterns
import utime
from settings import Settings
from machine import WDT
def run_test_case(p, test_name, n1, delay, brightness, duration_ms=6000):
"""Run a test case for specified duration"""
print(f"\n--- {test_name} ---")
print(f"Parameters: n1={n1} nodes, delay={delay}ms cycle time, brightness={brightness}")
# Configure parameters
p.n1 = n1
p.delay = delay
p.brightness = brightness
# Restart the pattern with new parameters
p.run = False
while p.running:
utime.sleep_ms(1)
p.select("rb")
# Run for specified duration
start_time = utime.ticks_ms()
while utime.ticks_diff(utime.ticks_ms(), start_time) < duration_ms:
wdt.feed()
utime.sleep_ms(100)
# Stop pattern
p.run = False
print(f"{test_name} completed")
# Brief pause between tests
utime.sleep_ms(500)
def test_rainbow_suite():
"""Run all rainbow tests continuously"""
print("Starting rainbow test suite...")
print("Runs all tests in sequence, then repeats")
print("Press Ctrl+C to stop")
# Load settings
settings = Settings()
# Initialize patterns using settings
p = patterns.Patterns(
pin=settings["led_pin"],
num_leds=settings["num_leds"],
brightness=255,
delay=1000
)
print(f"LED Pin: {settings['led_pin']}")
print(f"LEDs: {settings['num_leds']}")
print(f"Base Brightness: {p.brightness}")
# Initialize watchdog timer
global wdt
wdt = WDT(timeout=10000)
wdt.feed()
# Test configurations
tests = [
("Test 1: Single node (full spectrum)", 1, 3000, 255, 12000),
("Test 2: Two nodes", 2, 2000, 255, 10000),
("Test 3: Three nodes (RGB)", 3, 1500, 255, 9000),
("Test 4: Four nodes (RYGB)", 4, 1000, 255, 8000),
("Test 5: Six nodes (ROYGBP)", 6, 800, 255, 8000),
("Test 6: Eight nodes", 8, 600, 255, 6000),
("Test 7: Twelve nodes (fine gradation)", 12, 400, 255, 6000),
("Test 8: Many nodes (20), slow cycle", 20, 2000, 255, 10000),
("Test 9: Five nodes", 5, 1000, 255, 6000),
]
cycle = 0
try:
while True:
cycle += 1
print(f"\n\n========== Starting Cycle {cycle} ==========")
for test_name, n1, delay, brightness, duration in tests:
run_test_case(p, test_name, n1, delay, brightness, duration)
print("\n========== Cycle completed ==========\n")
except KeyboardInterrupt:
print("\n\nStopping test suite...")
p.run = False
p.off()
print("LEDs turned off")
if __name__ == "__main__":
test_rainbow_suite()

160
test/random_test.py Normal file
View File

@@ -0,0 +1,160 @@
#!/usr/bin/env python3
"""
Random test - randomly selects and runs test patterns for 2-10 minutes each
Run with: mpremote run test/random_test.py
"""
import patterns
import utime
import random
import math
from settings import Settings
from machine import WDT
def run_pattern(p, pattern_name, config, duration_ms):
"""Run a pattern for specified duration"""
print(f"\n{'='*60}")
print(f"Starting: {pattern_name}")
print(f"Duration: {duration_ms//1000} seconds")
# Configure parameters
if pattern_name == "blink":
p.delay = config.get("delay", 500)
elif pattern_name == "circle_loading":
p.n1 = config.get("n1", 50)
p.n2 = config.get("n2", 100)
p.n3 = config.get("n3", 200)
p.n4 = config.get("n4", 0)
p.delay = config.get("delay", 2000)
elif pattern_name == "sine_brightness":
p.n1 = config.get("n1", 50)
p.brightness = config.get("brightness", 255)
p.delay = config.get("delay", 1000)
elif pattern_name == "rainbow":
p.n1 = config.get("n1", 1)
p.delay = config.get("delay", 1000)
elif pattern_name == "flicker":
p.n1 = config.get("n1", 50)
p.delay = config.get("delay", 50)
elif pattern_name == "n_chase":
p.n1 = config.get("n1", 5)
p.n2 = config.get("n2", 5)
p.n3 = config.get("n3", 1)
p.n4 = config.get("n4", 1)
p.delay = config.get("delay", 100)
# Set color if provided
if "color" in config:
p.colors = [config["color"]]
# Start pattern
p.select(config.get("short_name", "o"))
# Run for duration
start_time = utime.ticks_ms()
while utime.ticks_diff(utime.ticks_ms(), start_time) < duration_ms:
wdt.feed()
utime.sleep_ms(100)
# Stop pattern
p.run = False
while p.running:
utime.sleep_ms(1)
p.off()
print(f"Completed: {pattern_name}")
utime.sleep_ms(500)
def random_test():
"""Run random test patterns continuously"""
print("Starting Random Test Suite")
print("Patterns will change every 1-5 minutes")
print("Press Ctrl+C to stop")
# Load settings
settings = Settings()
# Initialize patterns
p = patterns.Patterns(
pin=settings["led_pin"],
num_leds=settings["num_leds"],
brightness=255
)
print(f"LED Pin: {settings['led_pin']}")
print(f"LEDs: {settings['num_leds']}")
# Initialize watchdog
global wdt
wdt = WDT(timeout=10000)
wdt.feed()
# Define all test configurations
test_configs = [
# Circle loading patterns
{"pattern": "circle_loading", "short_name": "cl", "n1": 50, "n2": 100, "n3": 200, "n4": 0, "delay": 2000, "color": (255, 0, 0)},
{"pattern": "circle_loading", "short_name": "cl", "n1": 30, "n2": 60, "n3": 100, "n4": 0, "delay": 1500, "color": (0, 255, 0)},
{"pattern": "circle_loading", "short_name": "cl", "n1": 75, "n2": 150, "n3": 300, "n4": 10, "delay": 3000, "color": (0, 0, 255)},
# Sine brightness patterns
{"pattern": "sine_brightness", "short_name": "sb", "n1": 50, "brightness": 255, "delay": 1000, "color": (255, 0, 0)},
{"pattern": "sine_brightness", "short_name": "sb", "n1": 100, "brightness": 255, "delay": 800, "color": (0, 255, 0)},
{"pattern": "sine_brightness", "short_name": "sb", "n1": 0, "brightness": 255, "delay": 1500, "color": (0, 0, 255)},
# Rainbow patterns
{"pattern": "rainbow", "short_name": "rb", "n1": 1, "delay": 3000, "color": None},
{"pattern": "rainbow", "short_name": "rb", "n1": 2, "delay": 2000, "color": None},
{"pattern": "rainbow", "short_name": "rb", "n1": 3, "delay": 1500, "color": None},
{"pattern": "rainbow", "short_name": "rb", "n1": 6, "delay": 1000, "color": None},
{"pattern": "rainbow", "short_name": "rb", "n1": 12, "delay": 800, "color": None},
# Flicker patterns
{"pattern": "flicker", "short_name": "fl", "n1": 50, "delay": 50, "color": (255, 0, 0)},
{"pattern": "flicker", "short_name": "fl", "n1": 30, "delay": 100, "color": (0, 255, 0)},
{"pattern": "flicker", "short_name": "fl", "n1": 100, "delay": 30, "color": (0, 0, 255)},
# N-chase patterns (mix of forward-only and bidirectional)
# Forward-only patterns
{"pattern": "n_chase", "short_name": "nc", "n1": 5, "n2": 5, "n3": 5, "n4": 5, "delay": 100, "color": (255, 0, 0)},
{"pattern": "n_chase", "short_name": "nc", "n1": 10, "n2": 5, "n3": 10, "n4": 10, "delay": 150, "color": (0, 255, 0)},
{"pattern": "n_chase", "short_name": "nc", "n1": 20, "n2": 20, "n3": 20, "n4": 20, "delay": 200, "color": (0, 255, 255)},
# Bidirectional patterns
{"pattern": "n_chase", "short_name": "nc", "n1": 15, "n2": 10, "n3": 10, "n4": 5, "delay": 180, "color": (255, 255, 0)},
{"pattern": "n_chase", "short_name": "nc", "n1": 20, "n2": 20, "n3": 20, "n4": 5, "delay": 200, "color": (255, 0, 255)},
{"pattern": "n_chase", "short_name": "nc", "n1": 12, "n2": 8, "n3": 15, "n4": 8, "delay": 160, "color": (255, 128, 128)},
# N-chase patterns (forward only, large segments)
{"pattern": "n_chase", "short_name": "nc", "n1": 30, "n2": 10, "n3": 5, "n4": 5, "delay": 150, "color": (255, 128, 0)},
{"pattern": "n_chase", "short_name": "nc", "n1": 40, "n2": 20, "n3": 10, "n4": 10, "delay": 180, "color": (128, 255, 0)},
{"pattern": "n_chase", "short_name": "nc", "n1": 50, "n2": 25, "n3": 20, "n4": 20, "delay": 200, "color": (0, 128, 255)},
{"pattern": "n_chase", "short_name": "nc", "n1": 60, "n2": 30, "n3": 30, "n4": 30, "delay": 220, "color": (255, 0, 128)},
{"pattern": "n_chase", "short_name": "nc", "n1": 80, "n2": 40, "n3": 50, "n4": 50, "delay": 250, "color": (128, 0, 255)},
{"pattern": "n_chase", "short_name": "nc", "n1": 100, "n2": 50, "n3": 100, "n4": 100, "delay": 300, "color": (255, 255, 255)},
]
try:
cycle = 0
while True:
cycle += 1
print(f"\n\n{'='*60}")
print(f"Random Test Cycle {cycle}")
print(f"{'='*60}")
# Randomly select a test configuration
config = random.choice(test_configs)
# Random duration between 1-5 minutes (60,000 - 300,000 ms)
duration_ms = random.randint(60000, 300000)
# Run the selected pattern
run_pattern(p, config["pattern"], config, duration_ms)
except KeyboardInterrupt:
print("\n\nStopping random test suite...")
p.run = False
p.off()
print("LEDs turned off")
if __name__ == "__main__":
random_test()

120
test/sine_brightness.py Normal file
View File

@@ -0,0 +1,120 @@
#!/usr/bin/env python3
"""
Test script for sine brightness pattern
Run with: mpremote run test/sine_brightness.py
"""
import patterns
import utime
from settings import Settings
from machine import WDT
def run_test_case(p, test_name, duration_ms=5000):
"""Run a test case for specified duration"""
print(f"\n--- {test_name} ---")
print(f"Parameters: n1={p.n1} min brightness, brightness={p.brightness} max brightness, delay={p.delay}ms wavelength")
print(f"Color: {p.colors[0]}")
print(f"Running for {duration_ms//1000} seconds...")
# Initialize watchdog timer
wdt = WDT(timeout=10000)
wdt.feed()
# Start pattern
p.select("sb")
# Run for specified duration
start_time = utime.ticks_ms()
while utime.ticks_diff(utime.ticks_ms(), start_time) < duration_ms:
wdt.feed()
utime.sleep_ms(100)
# Stop pattern
p.run = False
print(f"{test_name} completed")
# Brief pause between tests
utime.sleep_ms(500)
def test_sine_brightness():
print("Testing sine brightness pattern with multiple configurations...")
# Load settings
settings = Settings()
# Initialize patterns using settings
p = patterns.Patterns(
pin=settings["led_pin"],
num_leds=settings["num_leds"],
brightness=255, # Full brightness
delay=1000 # Base wavelength
)
print(f"LED Pin: {settings['led_pin']}")
print(f"LEDs: {settings['num_leds']}")
print(f"Base Brightness: {p.brightness}")
# Test Case 1: Slow breathing
p.n1 = 50 # Min brightness 50
p.brightness = 255 # Max brightness 255
p.delay = 3000 # 3 second cycle
p.colors = [(255, 0, 0)] # Red
run_test_case(p, "Test 1: Slow breathing (50-255 brightness)", 12000)
# Test Case 2: Fast pulsing
p.n1 = 100 # Min brightness 100
p.brightness = 255 # Max brightness 255
p.delay = 500 # 0.5 second cycle
p.colors = [(0, 255, 0)] # Green
run_test_case(p, "Test 2: Fast pulsing (100-255 brightness)", 8000)
# Test Case 3: Medium breathing
p.n1 = 80 # Min brightness 80
p.brightness = 255 # Max brightness 255
p.delay = 1500 # 1.5 second cycle
p.colors = [(0, 0, 255)] # Blue
run_test_case(p, "Test 3: Medium breathing (80-255 brightness)", 10000)
# Test Case 4: Very slow breathing
p.n1 = 150 # Min brightness 150
p.brightness = 255 # Max brightness 255
p.delay = 5000 # 5 second cycle
p.colors = [(255, 255, 0)] # Yellow
run_test_case(p, "Test 4: Very slow breathing (150-255 brightness)", 15000)
# Test Case 5: Very fast pulsing
p.n1 = 50 # Min brightness 50
p.brightness = 255 # Max brightness 255
p.delay = 200 # 0.2 second cycle
p.colors = [(255, 0, 255)] # Magenta
run_test_case(p, "Test 5: Very fast pulsing (50-255 brightness)", 6000)
# Test Case 6: Low range, slow cycle
p.n1 = 200 # Min brightness 200
p.brightness = 255 # Max brightness 255
p.delay = 4000 # 4 second cycle
p.colors = [(0, 255, 255)] # Cyan
run_test_case(p, "Test 6: Low range, slow cycle (200-255 brightness)", 12000)
# Test Case 7: High range, medium cycle
p.n1 = 100 # Min brightness 100
p.brightness = 255 # Max brightness 255
p.delay = 1000 # 1 second cycle
p.colors = [(255, 128, 0)] # Orange
run_test_case(p, "Test 7: High range, medium cycle (100-255 brightness)", 8000)
# Test Case 8: Ultra fast strobe
p.n1 = 100 # Min brightness 100
p.brightness = 255 # Max brightness 255
p.delay = 100 # 0.1 second cycle
p.colors = [(128, 0, 255)] # Purple
run_test_case(p, "Test 8: Ultra fast strobe (100-255 brightness)", 4000)
print("\n=== All tests completed ===")
# Turn off LEDs
p.off()
print("LEDs turned off")
if __name__ == "__main__":
test_sine_brightness()

View File

@@ -1,111 +0,0 @@
#!/usr/bin/env python3
"""
Test for saving and loading patterns
Run with: mpremote run test/test_patterns_save_load.py
"""
import json
import uasyncio as asyncio
from settings import Settings
from patterns import Patterns
async def test_patterns_save_load():
"""Test saving and loading patterns"""
print("Testing patterns save and load functionality...")
# Test 1: Initialize patterns and check initial state
print("\nTest 1: Initialize patterns")
s = Settings()
pin = s.get("led_pin", 10)
num_leds = s.get("num_leds", 30)
p1 = Patterns(pin=pin, num_leds=num_leds)
print(f"Initial patterns count: {len(p1.patterns)}")
print(f"Available patterns: {list(p1.patterns.keys())}")
print(f"Selected pattern: {p1.selected}")
# Test 2: Try to save patterns (will fail because patterns contain functions)
print("\nTest 2: Attempt to save patterns")
try:
result = p1.save()
if result:
print("✓ Patterns saved successfully")
else:
print("✗ Patterns save failed (expected - patterns contain functions)")
except Exception as e:
print(f"✗ Exception during save: {e}")
# Test 3: Try to load patterns
print("\nTest 3: Attempt to load patterns")
try:
result = p1.load()
if result:
print("✓ Patterns loaded successfully")
print(f"Patterns after load: {list(p1.patterns.keys())}")
else:
print("✗ Patterns load failed")
except Exception as e:
print(f"✗ Exception during load: {e}")
# Test 4: Test with empty patterns dict (simulating custom patterns)
print("\nTest 4: Test save/load with empty patterns dict")
p2 = Patterns(pin=pin, num_leds=num_leds)
# Store original patterns
original_patterns = p2.patterns.copy()
# Clear patterns to test save/load with empty dict
p2.patterns = {}
try:
result = p2.save()
if result:
print("✓ Empty patterns dict saved successfully")
else:
print("✗ Failed to save empty patterns dict")
except Exception as e:
print(f"✗ Exception saving empty patterns: {e}")
# Try to load
p3 = Patterns(pin=pin, num_leds=num_leds)
p3.patterns = {} # Start with empty
try:
result = p3.load()
if result:
print("✓ Patterns loaded successfully")
print(f"Patterns count after load: {len(p3.patterns)}")
else:
print("✗ Failed to load patterns")
except Exception as e:
print(f"✗ Exception loading patterns: {e}")
# Restore original patterns
p2.patterns = original_patterns
p3.patterns = original_patterns
# Test 5: Verify patterns object state
print("\nTest 5: Verify patterns object state")
print(f"Patterns object type: {type(p1)}")
print(f"Has save method: {hasattr(p1, 'save')}")
print(f"Has load method: {hasattr(p1, 'load')}")
print(f"PATTERNS_FILE: {p1.PATTERNS_FILE}")
# Test 6: Test pattern selection persists
print("\nTest 6: Test pattern selection")
test_pattern = "rainbow"
if test_pattern in p1.patterns:
p1.select(test_pattern)
print(f"Selected pattern: {p1.selected}")
if p1.selected == test_pattern:
print("✓ Pattern selection works")
else:
print(f"✗ Pattern selection failed. Expected '{test_pattern}', got '{p1.selected}'")
else:
print(f"Pattern '{test_pattern}' not available")
print("\n=== Patterns Save/Load test complete ===")
if __name__ == "__main__":
asyncio.run(test_patterns_save_load())

View File

@@ -1,112 +0,0 @@
#!/usr/bin/env python3
"""
Test for saving and loading settings
Run with: mpremote run test/test_save_load.py
"""
import json
import os
from settings import Settings
from patterns import Patterns
def test_save_load():
"""Test saving and loading settings"""
print("Testing save and load functionality...")
# Test 1: Save settings
print("\nTest 1: Save settings")
settings1 = Settings()
# Modify some settings
original_num_leds = settings1.get("num_leds", 50)
original_pattern = settings1.get("pattern", "off")
original_brightness = settings1.get("brightness", 127)
settings1["num_leds"] = 100
settings1["pattern"] = "rainbow"
settings1["brightness"] = 200
settings1["delay"] = 150
settings1["color1"] = "#ff0000"
settings1["color2"] = "#00ff00"
print(f"Original num_leds: {original_num_leds}")
print(f"Setting num_leds to: {settings1['num_leds']}")
print(f"Setting pattern to: {settings1['pattern']}")
print(f"Setting brightness to: {settings1['brightness']}")
# Save settings
settings1.save()
print("Settings saved")
# Test 2: Load settings
print("\nTest 2: Load settings")
settings2 = Settings()
# Verify loaded values
print(f"Loaded num_leds: {settings2['num_leds']}")
print(f"Loaded pattern: {settings2['pattern']}")
print(f"Loaded brightness: {settings2['brightness']}")
print(f"Loaded delay: {settings2.get('delay', 'not set')}")
print(f"Loaded color1: {settings2.get('color1', 'not set')}")
print(f"Loaded color2: {settings2.get('color2', 'not set')}")
# Verify values match
if settings2["num_leds"] == 100:
print("✓ num_leds saved and loaded correctly")
else:
print(f"✗ num_leds mismatch! Expected 100, got {settings2['num_leds']}")
if settings2["pattern"] == "rainbow":
print("✓ pattern saved and loaded correctly")
else:
print(f"✗ pattern mismatch! Expected 'rainbow', got '{settings2['pattern']}'")
if settings2["brightness"] == 200:
print("✓ brightness saved and loaded correctly")
else:
print(f"✗ brightness mismatch! Expected 200, got {settings2['brightness']}")
# Test 3: Test with patterns
print("\nTest 3: Test pattern persistence")
pin = settings2.get("led_pin", 10)
num_leds = settings2["num_leds"]
patterns = Patterns(pin=pin, num_leds=num_leds, selected=settings2["pattern"])
patterns.set_brightness(settings2["brightness"])
patterns.set_delay(settings2["delay"])
print(f"Pattern selected: {patterns.selected}")
print(f"Pattern brightness: {patterns.brightness}")
print(f"Pattern delay: {patterns.delay}")
if patterns.selected == settings2["pattern"]:
print("✓ Pattern selection persisted")
else:
print(f"✗ Pattern mismatch! Expected '{settings2['pattern']}', got '{patterns.selected}'")
# Test 4: Restore original settings
print("\nTest 4: Restore original settings")
settings3 = Settings()
settings3["num_leds"] = original_num_leds
settings3["pattern"] = original_pattern
settings3["brightness"] = original_brightness
settings3.save()
print(f"Restored num_leds to: {original_num_leds}")
print(f"Restored pattern to: {original_pattern}")
print(f"Restored brightness to: {original_brightness}")
# Verify restoration
settings4 = Settings()
if settings4["num_leds"] == original_num_leds:
print("✓ Settings restored correctly")
else:
print(f"✗ Restoration failed! Expected {original_num_leds}, got {settings4['num_leds']}")
print("\n=== Save/Load test complete ===")
if __name__ == "__main__":
test_save_load()

View File

@@ -1,58 +0,0 @@
# LED Bar Configuration Tool
A tkinter GUI tool for configuring LED bar settings via mpremote.
## Features
- Download `settings.json` from MicroPython device using mpremote
- Edit LED configuration settings
- Upload modified `settings.json` back to device
- Load/save settings from/to local files
## Requirements
- Python 3.x with tkinter (usually included)
- mpremote: `pip install mpremote`
## Usage
```bash
python3 tool/led_config.py
```
Or make it executable:
```bash
chmod +x tool/led_config.py
./tool/led_config.py
```
## Configuration Fields
- **LED Pin**: GPIO pin number for LED strip
- **Number of LEDs**: Total number of LEDs in the strip
- **Color Order**: RGB or RBG color order
- **Device Name**: Name identifier for the device
- **Pattern**: Current LED pattern
- **Color 1/Color 2**: Primary colors (hex format, e.g., #ff0000)
- **Delay**: Pattern delay in milliseconds
- **Brightness**: LED brightness level
- **N1-N6**: Pattern-specific parameters
- **AP Password**: WiFi access point password
- **ID**: Device ID
## Device Connection
Default device is `/dev/ttyUSB0`. Change it in the "Device" field if your device is on a different port (e.g., `/dev/ttyACM0`, `COM3` on Windows).
## Workflow
1. Enter your device path (e.g., `/dev/ttyUSB0`)
2. Click "Download Settings" to fetch current settings from device
3. Edit any settings as needed
4. Click "Upload Settings" to save changes back to device
You can also:
- Load settings from a local JSON file
- Save current settings to a local JSON file

View File

@@ -1,329 +0,0 @@
#!/usr/bin/env python3
"""
LED Bar Configuration Tool
A tkinter GUI for downloading, editing, and uploading settings.json to/from MicroPython devices via mpremote.
"""
import tkinter as tk
from tkinter import ttk, messagebox, filedialog
import json
import subprocess
import os
import tempfile
import serial
from pathlib import Path
class LEDConfigTool:
def __init__(self, root):
self.root = root
self.root.title("LED Bar Configuration Tool")
self.root.geometry("600x700")
self.settings = {}
self.temp_file = None
# Create main frame
main_frame = ttk.Frame(root, padding="10")
main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
# Title
title_label = ttk.Label(main_frame, text="LED Bar Configuration", font=("Arial", 16, "bold"))
title_label.grid(row=0, column=0, columnspan=2, pady=(0, 20))
# Device connection section
device_frame = ttk.LabelFrame(main_frame, text="Device Connection", padding="10")
device_frame.grid(row=1, column=0, columnspan=2, sticky=(tk.W, tk.E), pady=(0, 10))
ttk.Label(device_frame, text="Device:").grid(row=0, column=0, sticky=tk.W, padx=(0, 5))
self.device_entry = ttk.Entry(device_frame, width=30)
self.device_entry.insert(0, "/dev/ttyACM0") # Default device
self.device_entry.grid(row=0, column=1, sticky=(tk.W, tk.E), padx=(0, 10))
ttk.Button(device_frame, text="Download Settings", command=self.download_settings).grid(row=0, column=2)
# Settings section
settings_frame = ttk.LabelFrame(main_frame, text="Settings", padding="10")
settings_frame.grid(row=2, column=0, columnspan=2, sticky=(tk.W, tk.E, tk.N, tk.S), pady=(0, 10))
# Create scrollable frame for settings
canvas = tk.Canvas(settings_frame, height=400)
scrollbar = ttk.Scrollbar(settings_frame, orient="vertical", command=canvas.yview)
scrollable_frame = ttk.Frame(canvas)
scrollable_frame.bind(
"<Configure>",
lambda e: canvas.configure(scrollregion=canvas.bbox("all"))
)
canvas.create_window((0, 0), window=scrollable_frame, anchor="nw")
canvas.configure(yscrollcommand=scrollbar.set)
# Settings fields
self.setting_widgets = {}
settings_config = [
("led_pin", "LED Pin", "number"),
("num_leds", "Number of LEDs", "number"),
("color_order", "Color Order", "choice", ["rgb", "rbg"]),
("name", "Device Name", "text"),
("pattern", "Pattern", "text"),
("color1", "Color 1", "color"),
("color2", "Color 2", "color"),
("delay", "Delay (ms)", "number"),
("brightness", "Brightness", "number"),
("n1", "N1", "number"),
("n2", "N2", "number"),
("n3", "N3", "number"),
("n4", "N4", "number"),
("n5", "N5", "number"),
("n6", "N6", "number"),
("ap_password", "AP Password", "text"),
("id", "ID", "number"),
]
for idx, config in enumerate(settings_config):
key = config[0]
label_text = config[1]
field_type = config[2]
ttk.Label(scrollable_frame, text=f"{label_text}:").grid(row=idx, column=0, sticky=tk.W, padx=(0, 10), pady=5)
if field_type == "number":
widget = ttk.Entry(scrollable_frame, width=20)
elif field_type == "choice":
widget = ttk.Combobox(scrollable_frame, width=17, values=config[3], state="readonly")
elif field_type == "color":
widget = ttk.Entry(scrollable_frame, width=20)
else: # text
widget = ttk.Entry(scrollable_frame, width=20)
widget.grid(row=idx, column=1, sticky=(tk.W, tk.E), pady=5)
self.setting_widgets[key] = widget
canvas.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
scrollbar.grid(row=0, column=1, sticky=(tk.N, tk.S))
settings_frame.grid_rowconfigure(0, weight=1)
settings_frame.grid_columnconfigure(0, weight=1)
# Buttons section
button_frame = ttk.Frame(main_frame)
button_frame.grid(row=3, column=0, columnspan=2, pady=(10, 0))
ttk.Button(button_frame, text="Load from File", command=self.load_from_file).grid(row=0, column=0, padx=5)
ttk.Button(button_frame, text="Save to File", command=self.save_to_file).grid(row=0, column=1, padx=5)
ttk.Button(button_frame, text="Upload Settings", command=self.upload_settings).grid(row=0, column=2, padx=5)
# Status bar
self.status_label = ttk.Label(main_frame, text="Ready", relief=tk.SUNKEN, anchor=tk.W)
self.status_label.grid(row=4, column=0, columnspan=2, sticky=(tk.W, tk.E), pady=(10, 0))
# Configure grid weights
root.columnconfigure(0, weight=1)
root.rowconfigure(0, weight=1)
main_frame.columnconfigure(0, weight=1)
main_frame.rowconfigure(2, weight=1)
device_frame.columnconfigure(1, weight=1)
def update_status(self, message):
"""Update the status bar message."""
self.status_label.config(text=message)
self.root.update_idletasks()
def download_settings(self):
"""Download settings.json from the device using mpremote."""
device = self.device_entry.get().strip()
if not device:
messagebox.showerror("Error", "Please specify a device")
return
self.update_status("Downloading settings...")
try:
# Create temporary file
self.temp_file = tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False)
temp_path = self.temp_file.name
self.temp_file.close()
# Download file using mpremote
cmd = ["mpremote", "connect", device, "cp", ":/settings.json", temp_path]
result = subprocess.run(cmd, capture_output=True, text=True, timeout=10)
if result.returncode != 0:
raise Exception(f"mpremote error: {result.stderr}")
# Load the downloaded file
with open(temp_path, 'r') as f:
self.settings = json.load(f)
# Update UI with loaded settings
self.update_ui_from_settings()
self.update_status(f"Settings downloaded successfully from {device}")
messagebox.showinfo("Success", "Settings downloaded successfully!")
except subprocess.TimeoutExpired:
self.update_status("Error: Connection timeout")
messagebox.showerror("Error", "Connection timeout. Check device connection.")
except FileNotFoundError:
self.update_status("Error: mpremote not found")
messagebox.showerror("Error", "mpremote not found. Please install it:\npip install mpremote")
except Exception as e:
self.update_status(f"Error: {str(e)}")
messagebox.showerror("Error", f"Failed to download settings:\n{str(e)}")
finally:
# Clean up temp file
if self.temp_file and os.path.exists(temp_path):
try:
os.unlink(temp_path)
except:
pass
def upload_settings(self):
"""Upload settings.json to the device using mpremote."""
device = self.device_entry.get().strip()
if not device:
messagebox.showerror("Error", "Please specify a device")
return
if not self.settings:
messagebox.showerror("Error", "No settings to upload. Please download or load settings first.")
return
self.update_status("Uploading settings...")
try:
# Get current settings from UI
self.update_settings_from_ui()
# Create temporary file with current settings
temp_file = tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False)
temp_path = temp_file.name
json.dump(self.settings, temp_file, indent=2)
temp_file.close()
# Upload file using mpremote
cmd = ["mpremote", "connect", device, "cp", temp_path, ":/settings.json"]
result = subprocess.run(cmd, capture_output=True, text=True, timeout=10)
if result.returncode != 0:
raise Exception(f"mpremote error: {result.stderr}")
# Reset the device
self.update_status("Resetting device...")
try:
with serial.Serial(device, baudrate=115200) as ser:
ser.write(b'\x03\x03\x04')
except Exception as e:
# If serial reset fails, try mpremote method as fallback
reset_cmd = ["mpremote", "connect", device, "exec", "import machine; machine.reset()"]
subprocess.run(reset_cmd, capture_output=True, text=True, timeout=5)
self.update_status(f"Settings uploaded and device reset on {device}")
messagebox.showinfo("Success", "Settings uploaded successfully and device reset!")
except subprocess.TimeoutExpired:
self.update_status("Error: Connection timeout")
messagebox.showerror("Error", "Connection timeout. Check device connection.")
except FileNotFoundError:
self.update_status("Error: mpremote not found")
messagebox.showerror("Error", "mpremote not found. Please install it:\npip install mpremote")
except Exception as e:
self.update_status(f"Error: {str(e)}")
messagebox.showerror("Error", f"Failed to upload settings:\n{str(e)}")
finally:
# Clean up temp file
if os.path.exists(temp_path):
try:
os.unlink(temp_path)
except:
pass
def load_from_file(self):
"""Load settings from a local JSON file."""
file_path = filedialog.askopenfilename(
title="Load Settings",
filetypes=[("JSON files", "*.json"), ("All files", "*.*")]
)
if not file_path:
return
try:
with open(file_path, 'r') as f:
self.settings = json.load(f)
self.update_ui_from_settings()
self.update_status(f"Settings loaded from {os.path.basename(file_path)}")
messagebox.showinfo("Success", "Settings loaded successfully!")
except Exception as e:
self.update_status(f"Error: {str(e)}")
messagebox.showerror("Error", f"Failed to load settings:\n{str(e)}")
def save_to_file(self):
"""Save current settings to a local JSON file."""
if not self.settings:
messagebox.showerror("Error", "No settings to save. Please download or load settings first.")
return
file_path = filedialog.asksaveasfilename(
title="Save Settings",
defaultextension=".json",
filetypes=[("JSON files", "*.json"), ("All files", "*.*")]
)
if not file_path:
return
try:
# Get current settings from UI
self.update_settings_from_ui()
with open(file_path, 'w') as f:
json.dump(self.settings, f, indent=2)
self.update_status(f"Settings saved to {os.path.basename(file_path)}")
messagebox.showinfo("Success", "Settings saved successfully!")
except Exception as e:
self.update_status(f"Error: {str(e)}")
messagebox.showerror("Error", f"Failed to save settings:\n{str(e)}")
def update_ui_from_settings(self):
"""Update UI widgets with current settings values."""
for key, widget in self.setting_widgets.items():
if key in self.settings:
value = self.settings[key]
if isinstance(widget, ttk.Combobox):
widget.set(str(value))
else:
widget.delete(0, tk.END)
widget.insert(0, str(value))
def update_settings_from_ui(self):
"""Update settings dictionary from UI widget values."""
for key, widget in self.setting_widgets.items():
value = widget.get().strip()
if value:
# Try to convert to appropriate type
if key in ["led_pin", "num_leds", "delay", "brightness", "id", "n1", "n2", "n3", "n4", "n5", "n6"]:
try:
self.settings[key] = int(value)
except ValueError:
pass # Keep as string if conversion fails
else:
self.settings[key] = value
elif key in self.settings:
# Keep existing value if widget is empty
pass
def main():
root = tk.Tk()
app = LEDConfigTool(root)
root.mainloop()
if __name__ == "__main__":
main()