62 Commits

Author SHA1 Message Date
a17380c250 Add n values; remove color1, color2 2025-11-20 18:22:29 +13:00
fb51d65077 Add n_chase 2025-11-20 18:18:30 +13:00
ca80f6a3f5 Add n params 2025-11-20 18:17:42 +13:00
66bfc80771 Remove step 2025-11-20 18:15:19 +13:00
3855e76da1 Update to match the new patterns format 2025-11-20 18:14:30 +13:00
3c3a2a0fb7 Ignore __pycache__ 2025-11-20 18:13:00 +13:00
4dacd8ca38 Add pattern tests 2025-11-20 18:11:54 +13:00
3dae9363e7 Remove unused pattern tests 2025-11-12 19:20:50 +13:00
1962638b81 Add rainbow and transition tests 2025-11-12 19:20:48 +13:00
4f413ee4ff Implement websocket handler 2025-11-12 19:20:42 +13:00
2b0b83f981 Update rainbow: n1 controls step increment 2025-11-12 19:20:33 +13:00
1d82ea6a91 Add auto flag to patterns 2025-11-12 19:20:25 +13:00
f17dd302da refactor: simplify patterns_base class
Extract base Patterns class with async run/stop support
2025-11-06 19:25:07 +13:00
846d574ad6 feat: add pulse pattern
Configurable attack/hold/decay phases via n1/n2/n3. Single-shot when delay=0.
2025-11-06 19:16:51 +13:00
f8851d2e7c add short-key param mapping and set_param() 2025-10-30 21:44:45 +13:00
12e242724e Add pattern test 2025-10-30 21:42:45 +13:00
44cb35d1aa Split into pattern and low level methods 2025-09-05 23:29:18 +12:00
fc080f7796 Add watchfiles 2025-08-26 22:53:48 +12:00
70fe5a0cdc Add watchfiles 2025-08-11 22:15:21 +12:00
2a7b5527a5 Move gc and wdt to function 2025-08-03 19:39:25 +12:00
50545e3170 Remove random patterns 2025-08-03 19:29:10 +12:00
d2826a0f63 Swtich to names isntead of ids 2025-07-12 10:22:17 +12:00
87fc74bb51 Add flicker pattern 2025-07-12 10:21:43 +12:00
03f3f02da8 Remove wifi client 2025-06-19 19:13:13 +12:00
524db5e979 Move espnow to seperate file 2025-06-19 19:05:08 +12:00
279416cded Add set_pattern_step 2025-06-19 19:03:22 +12:00
fbd14f2e16 If no ids run set_settings 2025-06-12 21:32:24 +12:00
1989f6f5c9 Switch to list for colors 2025-06-12 21:29:31 +12:00
a19b1e86f2 Have to save when using espnow 2025-06-08 13:18:28 +12:00
c63e907204 espnow if id is 0 call set_settings 2025-06-04 21:07:15 +12:00
b7920e224f Add color order 2025-06-04 21:02:55 +12:00
42e92dafc8 Add led pin setting 2025-06-04 20:03:51 +12:00
0b6eb9724f Add device ID 2025-06-04 19:54:06 +12:00
55ef5c1580 Move json load out of set_settings 2025-06-04 19:22:09 +12:00
c15f9787a7 Sync after going through all the keys 2025-06-02 00:32:43 +12:00
3d0078f118 Update leds straight away after a sync 2025-06-02 00:18:25 +12:00
9e72dba035 Check all keys before returning 2025-06-02 00:16:58 +12:00
3d7dd754eb Wifi and ESPNOW don't work at the same time 2025-06-02 00:15:31 +12:00
2dd20fa51b Enable garabage collection 2025-05-28 21:19:56 +12:00
d33bd6b0e4 Enable watchdog timer 2025-05-28 21:17:23 +12:00
8902adf18c Fix color transition 2025-05-24 13:09:28 +12:00
9abd425f46 Add wifi 2025-05-22 22:02:49 +12:00
ee28b5805d Change title and h1 to "name" 2025-05-22 22:02:29 +12:00
ec29dbdd01 Add color order 2025-05-19 22:00:35 +12:00
3fa9377438 Add set_settings to Settings class 2025-05-19 21:59:43 +12:00
ec049b52c0 Only check wifi settings if not connected 2025-05-19 19:35:29 +12:00
a009ea85bc Add wifi settings 2025-05-19 19:32:53 +12:00
bd2e6e56cf Check if ssid, password, ip and gateway are "" 2025-05-19 19:31:30 +12:00
37c7280a15 Get ap password from settings 2025-05-19 19:28:11 +12:00
2f10d4cabd Fix rgb order 2025-05-19 19:24:17 +12:00
385dcffe68 Add led pin in settings 2025-05-19 19:22:38 +12:00
fa0578349b Delete index_html.py 2025-05-18 21:28:44 +12:00
4a36ff0da0 Update main.py 2025-05-18 21:28:41 +12:00
bd4046572c Update main.css 2025-05-18 21:28:38 +12:00
fdd299b063 Update main.js 2025-05-18 21:28:34 +12:00
a44ef2d0ad Update index.html 2025-05-18 21:28:29 +12:00
2d1208e223 Update web.py 2025-05-18 21:28:21 +12:00
67279a8f46 Update wifi.py 2025-05-18 21:28:14 +12:00
a52ac3df99 Update Pipfile.lock 2025-05-18 21:27:58 +12:00
c4356cf354 Switch to web socket 2025-05-18 21:26:33 +12:00
0c219e0697 Add websocket settings endpoint 2025-05-12 22:22:05 +12:00
cee8c20176 Always sync 2025-05-12 22:21:11 +12:00
25 changed files with 2226 additions and 741 deletions

1
.gitignore vendored
View File

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

View File

@@ -7,8 +7,13 @@ name = "pypi"
mpremote = "*"
pyserial = "*"
esptool = "*"
watchfiles = "*"
[dev-packages]
[requires]
python_version = "3.12"
[scripts]
dev = 'watchfiles "./dev.py /dev/ttyACM0 src reset follow"'

572
Pipfile.lock generated
View File

@@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
"sha256": "8b14bb293b7e7117ffc89c2bc92d7aa2290e8f68be7fc0f073f2b3f7f959ef71"
"sha256": "7b8033c15743e27f2589635c75bd0bb86ffc3a725b179d7db9ef200119aa9164"
},
"pipfile-spec": 6,
"requires": {
@@ -16,163 +16,160 @@
]
},
"default": {
"argcomplete": {
"anyio": {
"hashes": [
"sha256:2ab2c4a215c59fd6caaff41a869480a23e8f6a5f910b266c1808037f4e375b61",
"sha256:c12bf50eded8aebb298c7b7da7a5ff3ee24dffd9f5281867dfe1424b58c55392"
"sha256:3f3fae35c96039744587aa5b8371e7e8e603c0702999535961dd336026973ba6",
"sha256:60e474ac86736bbfd6f210f7a61218939c318f43f9972497381f1c5e930ed3d1"
],
"markers": "sys_platform != 'win32'",
"version": "==3.5.3"
"markers": "python_version >= '3.9'",
"version": "==4.10.0"
},
"bitarray": {
"hashes": [
"sha256:000df24c183011b5d27c23d79970f49b6762e5bb5aacd25da9c3e9695c693222",
"sha256:0027b8f3bb2bba914c79115e96a59b9924aafa1a578223a7c4f0a7242d349842",
"sha256:00f9a88c56e373009ac3c73c55205cfbd9683fbd247e2f9a64bae3da78795252",
"sha256:041c889e69c847b8a96346650e50f728b747ae176889199c49a3f31ae1de0e23",
"sha256:0879f839ec8f079fa60c3255966c2e1aa7196699a234d4e5b7898fbc321901b5",
"sha256:0b555006a7dea53f6bebc616a4d0249cecbf8f1fadf77860120a2e5dbdc2f167",
"sha256:0b655c3110e315219e266b2732609fddb0857bc69593de29f3c2ba74b7d3f51a",
"sha256:0cecaf2981c9cd2054547f651537b4f4939f9fe225d3fc2b77324b597c124e40",
"sha256:0e104f9399144fab6a892d379ba1bb4275e56272eb465059beef52a77b4e5ce6",
"sha256:0ef5c787c8263c082a73219a69eb60a500e157a4ac69d1b8515ad836b0e71fb4",
"sha256:12f19ede03e685c5c588ab5ed63167999295ffab5e1126c5fe97d12c0718c18f",
"sha256:1414a7102a3c4986f241480544f5c99f5d32258fb9b85c9c04e84e48c490ab35",
"sha256:147542299f458bdb177f798726e5f7d39ab8491de4182c3c6d9885ed275a3c2b",
"sha256:150b7b29c36d9f1a24779aea723fdfc73d1c1c161dc0ea14990da27d4e947092",
"sha256:153d7c416a70951dcfa73487af05d2f49c632e95602f1620cd9a651fa2033695",
"sha256:184972c96e1c7e691be60c3792ca1a51dd22b7f25d96ebea502fe3c9b554f25d",
"sha256:18abdce7ab5d2104437c39670821cba0b32fdb9b2da9e6d17a4ff295362bd9dc",
"sha256:2055206ed653bee0b56628f6a4d248d53e5660228d355bbec0014bdfa27050ae",
"sha256:20f30373f0af9cb583e4122348cefde93c82865dbcbccc4997108b3d575ece84",
"sha256:22b00f65193fafb13aa644e16012c8b49e7d5cbb6bb72825105ff89aadaa01e3",
"sha256:251cd5bd47f542893b2b61860eded54f34920ea47fd5bff038d85e7a2f7ae99b",
"sha256:2855cc01ee370f7e6e3ec97eebe44b1453c83fb35080313145e2c8c3c5243afb",
"sha256:2ac67b658fa5426503e9581a3fb44a26a3b346c1abd17105735f07db572195b3",
"sha256:2d9fe3ee51afeb909b68f97e14c6539ace3f4faa99b21012e610bbe7315c388d",
"sha256:2da91ab3633c66999c2a352f0ca9ae064f553e5fc0eca231d28e7e305b83e942",
"sha256:2dad7ba2af80f9ec1dd988c3aca7992408ec0d0b4c215b65d353d95ab0070b10",
"sha256:34fc13da3518f14825b239374734fce93c1a9299ed7b558c3ec1d659ec7e4c70",
"sha256:369b6d457af94af901d632c7e625ca6caf0a7484110fc91c6290ce26bc4f1478",
"sha256:37be5482b9df3105bad00fdf7dc65244e449b130867c3879c9db1db7d72e508b",
"sha256:3963b80a68aedcd722a9978d261ae53cb9bb6a8129cc29790f0f10ce5aca287a",
"sha256:39b38a3d45dac39d528c87b700b81dfd5e8dc8e9e1a102503336310ef837c3fd",
"sha256:3cd565253889940b4ec4768d24f101d9fe111cad4606fdb203ea16f9797cf9ed",
"sha256:3d47bc4ff9b0e1624d613563c6fa7b80aebe7863c56c3df5ab238bb7134e8755",
"sha256:3fa5d8e4b28388b337face6ce4029be73585651a44866901513df44be9a491ab",
"sha256:42bf1b222c698b467097f58b9f59dc850dfa694dde4e08237407a6a103757aa3",
"sha256:43b6c7c4f4a7b80e86e24a76f4c6b9b67d03229ea16d7d403520616535c32196",
"sha256:44c3e78b60070389b824d5a654afa1c893df723153c81904088d4922c3cfb6ac",
"sha256:4683bff52f5a0fd523fb5d3138161ef87611e63968e1fcb6cf4b0c6a86970fe0",
"sha256:47ccf9887bd595d4a0536f2310f0dcf89e17ab83b8befa7dc8727b8017120fda",
"sha256:4800c91a14656789d2e67d9513359e23e8a534c8ee1482bb9b517a4cfc845200",
"sha256:4817d73d995bd2b977d9cde6050be8d407791cf1f84c8047fa0bea88c1b815bc",
"sha256:4839d3b64af51e4b8bb4a602563b98b9faeb34fd6c00ed23d7834e40a9d080fc",
"sha256:4ac2027ca650a7302864ed2528220d6cc6921501b383e9917afc7a2424a1e36d",
"sha256:4cb5702dd667f4bb10fed056ffdc4ddaae8193a52cd74cb2cdb54e71f4ef2dd1",
"sha256:53e002ac1073ac70e323a7a4bfa9ab95e7e1a85c79160799e265563f342b1557",
"sha256:545d36332de81e4742a845a80df89530ff193213a50b4cbef937ed5a44c0e5e5",
"sha256:572a61fba7e3a710a8324771322fba8488d134034d349dcd036a7aef74723a80",
"sha256:57d5ef854f8ec434f2ffd9ddcefc25a10848393fe2976e2be2c8c773cf5fef42",
"sha256:5ddbf71a97ad1d6252e6e93d2d703b624d0a5b77c153b12f9ea87d83e1250e0c",
"sha256:5fa4b4d9fa90124b33b251ef74e44e737021f253dc7a9174e1b39f097451f7ca",
"sha256:628f93e9c2c23930bd1cfe21c634d6c84ec30f45f23e69aefe1fcd262186d7bb",
"sha256:648e7ce794928e8d11343b5da8ecc5b910af75a82ea1a4264d5d0a55c3785faa",
"sha256:656db7bdf1d81ec3b57b3cad7ec7276765964bcfd0eb81c5d1331f385298169c",
"sha256:666e44b0458bb2894b64264a29f2cc7b5b2cbcc4c5e9cedfe1fdbde37a8e329a",
"sha256:66a33a537e781eac3a352397ce6b07eedf3a8380ef4a804f8844f3f45e335544",
"sha256:66d6134b7bb737b88f1d16478ad0927c571387f6054f4afa5557825a4c1b78e2",
"sha256:67a0b56dd02f2713f6f52cacb3f251afd67c94c5f0748026d307d87a81a8e15c",
"sha256:6c33129b49196aa7965ac0f16fcde7b6ad8614b606caf01669a0277cef1afe1d",
"sha256:6d2a2ce73f9897268f58857ad6893a1a6680c5a6b28f79d21c7d33285a5ae646",
"sha256:71ad0139c95c9acf4fb62e203b428f9906157b15eecf3f30dc10b55919225896",
"sha256:7814c9924a0b30ecd401f02f082d8697fc5a5be3f8d407efa6e34531ff3c306a",
"sha256:787db8da5e9e29be712f7a6bce153c7bc8697ccc2c38633e347bb9c82475d5c9",
"sha256:7cb885c043000924554fe2124d13084c8fdae03aec52c4086915cd4cb87fe8be",
"sha256:7cd021ada988e73d649289cee00428b75564c46d55fbdcb0e3402e504b0ae5ea",
"sha256:7e51e7f8289bf6bb631e1ef2a8f5e9ca287985ff518fe666abbdfdb6a848cb26",
"sha256:7e9eee03f187cef1e54a4545124109ee0afc84398628b4b32ebb4852b4a66393",
"sha256:7edb83089acbf2c86c8002b96599071931dc4ea5e1513e08306f6f7df879a48b",
"sha256:7f1c24be7519f16a47b7e2ad1a1ef73023d34d8cbe1a3a59b185fc14baabb132",
"sha256:8330912be6cb8e2fbfe8eb69f82dee139d605730cadf8d50882103af9ac83bb4",
"sha256:8a9eb510cde3fa78c2e302bece510bf5ed494ec40e6b082dec753d6e22d5d1b1",
"sha256:8c9733d2ff9b7838ac04bf1048baea153174753e6a47312be14c83c6a395424b",
"sha256:904c1d5e3bd24f0c0d37a582d2461312033c91436a6a4f3bdeeceb4bea4a899d",
"sha256:928b8b6dfcd015e1a81334cfdac02815da2a2407854492a80cf8a3a922b04052",
"sha256:9502c2230d59a4ace2fddfd770dad8e8b414cbd99517e7e56c55c20997c28b8d",
"sha256:96cf0898f8060b2d3ae491762ae871b071212ded97ff9e1e3a5229e9fefe544c",
"sha256:98a4070ddafabddaee70b2aa7cc6286cf73c37984169ab03af1782da2351059a",
"sha256:9929051feeaf8d948cc0b1c9ce57748079a941a1a15c89f6014edf18adaade84",
"sha256:996d1b83eb904589f40974538223eaed1ab0f62be8a5105c280b9bd849e685c4",
"sha256:9c6e52005e91803eb4e08c0a08a481fb55ddce97f926bae1f6fa61b3396b5b61",
"sha256:9e3727ab63dfb6bde00b281934e2212bb7529ea3006c0031a556a84d2268bea5",
"sha256:a0255bd05ec7165e512c115423a5255a3f301417973d20a80fc5bfc3f3640bcb",
"sha256:a2083dc20f0d828a7cdf7a16b20dae56aab0f43dc4f347a3b3039f6577992b03",
"sha256:a3c36b2fcfebe15ad1c10a90c1d52a42bebe960adcbce340fef867203028fbe7",
"sha256:a4f49ac31734fe654a68e2515c0da7f5bbdf2d52755ba09a42ac406f1f08c9d0",
"sha256:a667ea05ba1ea81b722682276dbef1d36990f8908cf51e570099fd505a89f931",
"sha256:a754c1464e7b946b1cac7300c582c6fba7d66e535cd1dab76d998ad285ac5a37",
"sha256:a817ad70c1aff217530576b4f037dd9b539eb2926603354fcac605d824082ad1",
"sha256:aa54c7e1da8cf4be0aab941ea284ec64033ede5d6de3fd47d75e77cafe986e9d",
"sha256:ab37da66a8736ad5a75a58034180e92c41e864da0152b84e71fcc253a2f69cd4",
"sha256:ac06dd72ee1e1b6e312504d06f75220b5894af1fb58f0c20643698f5122aea76",
"sha256:aca0a9cd376beaccd9f504961de83e776dd209c2de5a4c78dc87a78edf61839b",
"sha256:acc07211a59e2f245e9a06f28fa374d094fb0e71cf5366eef52abbb826ddc81e",
"sha256:aef404d5400d95c6ec86664df9924bde667c8865f8e33c9b7bd79823d53b3e5d",
"sha256:b1047999f1797c3ea7b7c85261649249c243308dcf3632840d076d18fa72f142",
"sha256:b7d09ef06ba57bea646144c29764bf6b870fb3c5558ca098191e07b6a1d40bf7",
"sha256:bcf0150ae0bcc4aa97bdfcb231b37bad1a59083c1b5012643b266012bf420e68",
"sha256:bcf524a087b143ba736aebbb054bb399d49e77cf7c04ed24c728e411adc82bfa",
"sha256:beeb79e476d19b91fd6a3439853e4e5ba1b3b475920fa40d62bde719c8af786f",
"sha256:bf90aba4cff9e72e24ecdefe33bad608f147a23fa5c97790a5bab0e72fe62b6d",
"sha256:c23286abba0cb509733c6ce8f4013cd951672c332b2e184dbefbd7331cd234c8",
"sha256:c2945e0390d1329c585c584c6b6d78be017d9c6a1288f9c92006fe907f69cc28",
"sha256:c756a92cf1c1abf01e56a4cc40cb89f0ff9147f2a0be5b557ec436a23ff464d8",
"sha256:c9e9fef0754867d88e948ce8351c9fd7e507d8514e0f242fd67c907b9cdf98b3",
"sha256:ca79f02a98cbda1472449d440592a2fe2ad96fe55515a0447fa8864a38017cf8",
"sha256:cb7302dbcfcb676f0b66f15891f091d0233c4fc23e1d4b9dc9b9e958156e347f",
"sha256:cb98d5b6eac4b2cf2a5a69f60a9c499844b8bea207059e9fc45c752436e6bb49",
"sha256:cc83ea003dd75e9ade3291ef0585577dd5524aec0c8c99305c0aaa2a7570d6db",
"sha256:ce249ed981f428a8b61538ca82d3875847733d579dd40084ab8246549160f8a4",
"sha256:cf0cc2e91dd38122dec2e6541efa99aafb0a62e118179218181eff720b4b8153",
"sha256:d1a199e6d7c3bad5ba9d0e4dc00dde70ee7d111c9dfc521247fa646ef59fa57e",
"sha256:d1d5abf1d6d910599ac16afdd9a0ed3e24f3b46af57f3070cf2792f236f36e0b",
"sha256:d3f761184b93092077c7f6b7dad7bd4e671c1620404a76620da7872ceb576a94",
"sha256:d756bfeb62ca4fe65d2af7a39249d442c05070c047d03729ad6cd4c2e9b0f0bd",
"sha256:d8c36ddc1923bcc4c11b9994c54eaae25034812a42400b7b8a86fe6d242166a2",
"sha256:dbe1084935b942fab206e609fa1ed3f46ad1f2612fb4833e177e9b2a5e006c96",
"sha256:dc1937a0ff2671797d35243db4b596329842480d125a65e9fe964bcffaf16dfc",
"sha256:dfea514e665af278b2e1d4deb542de1cd4f77413bee83dd15ae16175976ea8d5",
"sha256:e008b7b4ce6c7f7a54b250c45c28d4243cc2a3bbfd5298fa7dac92afda229842",
"sha256:e0e7f24a0b01e6e6a0191c50b06ca8edfdec1988d9d2b264d669d2487f4f4680",
"sha256:e15c94d79810c5ab90ddf4d943f71f14332890417be896ca253f21fa3d78d2b1",
"sha256:e56ba8be5f17dee0ffa6d6ce85251e062ded2faa3cbd2558659c671e6c3bf96d",
"sha256:e89ea59a3ed86a6eb150d016ed28b1bedf892802d0ed32b5659d3199440f3ced",
"sha256:e91d46d12781a14ccb8b284566b14933de4e3b29f8bc5e1c17de7a2001ad3b5b",
"sha256:ea40e98d751ed4b255db4a88fe8fb743374183f78470b9e9305aab186bf28ede",
"sha256:eb27c01b747649afd7e1c342961680893df6d8d81f832a6f04d8c8e03a8a54cc",
"sha256:ec5b0f2d13da53e0975ac15ecbe8badb463bdb0bebaa09457f4df3320421915c",
"sha256:ee040ad3b7dfa05e459713099f16373c1f2a6f68b43cb0575a66718e7a5daef4",
"sha256:f12cc7c7638074918cdcc7491aff897df921b092ffd877227892d2686e98f876",
"sha256:f536fc4d1a683025f9caef0bebeafd60384054579ffe0825bb9bd8c59f8c55b8",
"sha256:f71f24b58e75a889b9915e3197865302467f13e7390efdea5b6afc7424b3a2ea",
"sha256:f75fc0198c955d840b836059bd43e0993edbf119923029ca60c4fc017cefa54a",
"sha256:f785af6b7cb07a9b1e5db0dea9ef9e3e8bb3d74874a0a61303eab9c16acc1999",
"sha256:fbb645477595ce2a0fbb678d1cfd08d3b896e5d56196d40fb9e114eeab9382b3",
"sha256:fcef31b062f756ba7eebcd7890c5d5de84b9d64ee877325257bcc9782288564a",
"sha256:fe606e728842389943a939258809dc5db2de831b1d2e0118515059e87f7bbc1a",
"sha256:fef4e3b3f2084b4dae3e5316b44cda72587dcc81f68b4eb2dbda1b8d15261b61",
"sha256:ffd94b4803811c738e504a4b499fb2f848b2f7412d71e6b517508217c1d7929d"
"sha256:00628196dd3592972a5183194ab1475dadf9ef2a4cf3fd8c7c184a94934012e8",
"sha256:01d6dc548e7fe5c66913c2274f44855b0f8474935acff7811e84fe1f4024c94f",
"sha256:056fe779f01a867d572e071c0944ac2f3bf58d8bced326040f0bd060af33a209",
"sha256:080a7bf55c432abdae74f25dc3dbff407418346aeae1d43e31f65e8ef114f785",
"sha256:0956322bf4d5e2293e57600aa929c241edf1e209e94e12483bf58c5c691432db",
"sha256:0a6f9e897907757e9c2d722ae6c203d48a04826a14e1495e33935c8583c163a9",
"sha256:0ac446f557eb28e3f7c65372608810ff073840627e9037e22ed10bd081793a34",
"sha256:0b47843f2f288fa746dead4394591a3432a358aaad48240283fa230d6e74b0e7",
"sha256:11fc8bc65f964c7278deb1b7a69379dab3ecc90095f252deb17365637ebb274d",
"sha256:129165b68a3e0c2a633ed0d8557cf5ade24a0b37ca97d7805fa6fc5fb73c19d5",
"sha256:139963494fc3dd5caee5e38c0a03783ef50be118565e94b1dbb0210770f0b32d",
"sha256:157313a124287cbc8a11b55a75def0dd59e68badbc82c2dc2d204dc852742874",
"sha256:16d0edab54bb9d214319418f65bd15cfc4210ec41a16c3dd0b71e626c803212d",
"sha256:1971050b447023288a2b694a03b400bd5163829cd67b10f19e757fe87cd1161e",
"sha256:1c4e75bbf9ade3d2cdf1b607a8b353b17d9b3cf54e88b2a5a773f50ae6f1bfbc",
"sha256:1c9f36055a89b9517db66eb8e80137126bf629c767ceeade4d004e40bc8bcd99",
"sha256:2020102a40edd094c0aa80e09203af71c533c41f76ce3237c99fd194a473ea33",
"sha256:20febc849a1f858e6a57a7d47b323fe9e727c579ddd526d317ad8831748a66a8",
"sha256:220d4b8649ef54ac98e5e0e3dd92230247f67270d1524a8b31aa9859007affb0",
"sha256:22188943a29072b684cd7c99e0b2cfc0af317cea3366c583d820507e6d1f2ed4",
"sha256:222cb27ff05bc0aec72498d075dba1facec49a76a7da45740690cebbe3e81e43",
"sha256:243825f56b58bef28bfc602992a8c6d09bbc625628c195498d6020120d632a09",
"sha256:25060e7162e44242a449ed1a14a4e94b5aef340812754c443459f19c7954be91",
"sha256:26691454a6770628882b68fe74e9f84ca2a51512edd49cbb025b14df5a9dd85a",
"sha256:27d13c7b886afc5d2fc49d6e92f9c96b1f0a14dc7b5502520c29f3da7550d401",
"sha256:27eeee915258b105a21a4b0f8aebc5f77bb4dc4fb4063a09dd329fa1fdcbd234",
"sha256:29ed022189a7997de46cb9bd4e2e49d6163d4f8d78dea72ac5a0e0293b856810",
"sha256:2a324e3007afb5c667026f5235b35efe3c4a95f1b83cd93aa9fce67b42f08e7c",
"sha256:2c533c828d0007fac27cf45e5c1a711e5914dd469db5fe6be5f4e606bf2d7f63",
"sha256:30ba4fba3de1dca653de41c879349ec6ca521d85cff6a7ca5d2fdd8f76c93781",
"sha256:357e07c827bad01f98d0bd0dfdc722f483febeed39140fd75ffd016a451b60b9",
"sha256:3800f3c8c9780f281cf590543fd4b3278fea6988202273a260ecc58136895efb",
"sha256:3d6f3a94abd8b44b2bf346ca81ab2ff41ab9146c53905eedf5178b19d9fe53bf",
"sha256:3eb1390a8b062fe9125e5cc4c5eba990b5d383eec54f2b996e7ce73ac43150f9",
"sha256:407920e9318d94cc6c9611aaa5b5e5963a09f1cbfa17b16b66edea453b3754f4",
"sha256:42622c42c159ea4535bba7e1e3c97f1fec79505bc6873ae657dc0a8f861c60de",
"sha256:4695fcd37478988b1d0a16d5bc0df56dcb677fd5db37f1893d993fd3ebef914b",
"sha256:4798f6744fa2633666e17b4ea8ff70250781b52a25afdbf5ffb5e176c58848f1",
"sha256:4a5b0d277087a5bf261a607fc6ff4aaffcf80b300cd19b7a1e9754a4649f5fd4",
"sha256:4e297fd2e58afe17e33dd80c231c3a9d850279a2a8625aed1d39f9be9534809e",
"sha256:507e567aee4806576e20752f22533e8b7ec61e7e75062a7ce9222a0675aa0da6",
"sha256:50d702149747852923be60cae125285eca8d189d4c7d8832c0c958d4071a0f78",
"sha256:51947a00ae9924584fb14c0c1b1f4c1fd916d9abd6f47582f318ab9c9cb9f3d0",
"sha256:52328192d454ca2ddad09fbc088872b014c74b22ecdd5164717dc7e6442014fa",
"sha256:531e6dfec8058fcf5d69e863b61e6b28e3749b615a4dcc0ab8ad36307c4017fc",
"sha256:54bd71f14a5fa9bae73ef92f2e2be894dc36c7a6d1c4962e5969bd8a9aa39325",
"sha256:552a93be286ca485914777461b384761519db313e0a7f3012dca424c9610a4d5",
"sha256:583b46b3ba44121de5e87e95ae379932dc5fd2e37ebdf2c11a6d7975891425c1",
"sha256:5b58a672ec448fb36839a5fc7bf2b2f60df9a97b872d8bd6ca1a28da6126f5c7",
"sha256:5cfbdccddaa0ff07789e9e180db127906c676e479e05c04830cd458945de3511",
"sha256:5da4939e241301f5e1d18118695e8d2c300be90431b66bd43a00376acec45e1e",
"sha256:5dd9edcab8979a50c2c4dec6d5b66789fb6f630bb52ab90a4548111075a75e48",
"sha256:5e304f94c0353f6ae5711533b5793b3a45b17aa2c5b07e656649b0af4e0939b5",
"sha256:60408ec9c0bd76f1fa00d28034429a0316246d31069b982a86aec8d5c99e910a",
"sha256:62f71b268f14ee6cc3045b95441bfe0518cef1d0b2ffbc6f3e9758f786ff5a03",
"sha256:664d462a4c0783fd755fe3440f07b7e46d149859c96caacadf3f28890f19a8de",
"sha256:66d8b7a89fac6042f7df9ea97d97ed0f5e404281110a882e3babd909161f85b6",
"sha256:6755cfcfa7d8966e704d580c831e39818f85e7b2b7852ad22708973176f0009e",
"sha256:679856547f0b27b98811b73756bdf53769c23b045a6f95177cae634daabf1ddf",
"sha256:6841c08b51417f8ffe398b2828fc0593440c99525c868f640e0302476745320b",
"sha256:68f6e64d4867ee79e25c49d7f35b2b1f04a6d6f778176dcf5b759f3b17a02b2b",
"sha256:69d2d507c1174330c71c834b5d65e66181ad7b42b0d88b5b31804ee9b4f5dae7",
"sha256:6f0be27d06732e2833b672a8fcc32fa195bdb22161eb88f8890de15e30264a01",
"sha256:6f7d2dbe628f3db935622a5b80a5c4d95665cdefc4904372aa3c4d786289477f",
"sha256:72760411d60d8d76979a20ed3f15586d824db04668b581b86e61158c2b616db0",
"sha256:727f7a969416f02ef5c1256541e06f0836fb615022699fa8e2591e85296c5570",
"sha256:77d2368a06a86a18919c05a9b4b0ee9869f770e6a5f414b0fecc911870fe3974",
"sha256:79ab1c5f26f23e51d4a44c4397c8a3bf56c306c125dfab6b3eebdfa13d1dca6f",
"sha256:79db23eda81627132327ed292bd813a9af64399b98aaac3d42ad8deeed24cd5e",
"sha256:7c20d6e6cafce5027e7092beb2ac6eec0d71045d6318b34f36e1387a8c8859a3",
"sha256:7e0851a985a7b10f634188117c825ef99d63402555cca5bc32c7bfc5adaf0d6f",
"sha256:7e2e1ff784c2cdfd863bad31985851427f2d2796e445cec85080c7510cba4315",
"sha256:7f8b12424f8fdf29d1c0749c628bd1530cecfc77374935d096cccc0e4eada232",
"sha256:81e84054b22babcd6c5cc1eac0de2bfc1054ecdf742720cbfb36efbe89ec6c30",
"sha256:84bb57010a1ab76cf880424a2e0bce8dd26989849d2122ff073aa11bfc271c27",
"sha256:870ed23361e2918ab1ffc23fe0ab293abf3c372a68ee7387456d13da3e213008",
"sha256:8cf44b012e7493127ce7ca6e469138ac96b3295a117877d5469aabe7c8728d87",
"sha256:8d6c9bc14bacdfbfd51fed85f0576973eaaa7b30d81ef93264f8e22b86a9c9f7",
"sha256:8d759cecfa8aab4a1eb4e23b6420126b15c7743e85b33f389916bb98c4ecbb84",
"sha256:8ef3f0977c21190f949d5cfd71ded09de87d330c6d98bd5ecb5bb1135d666d0d",
"sha256:8f95daf0ce2b24815ddf62667229ba5dfc0cfee43eb43b2549766170d0f24ae9",
"sha256:911b4a16dce370657e5b8d8b6ba0fbb50dd5e2b24c4416f4b9e664503d3f0502",
"sha256:96117212229905da864794df9ea7bd54987c30a5dcbab3432edc3f344231adae",
"sha256:963cbcf296943f7017470d0b705e63e908f32b4f7dbe43f72c22f6fe1bd9ef66",
"sha256:975a118aa019d745f1398613b27fd8789f60a8cea057a00cdc1abedee123ffe6",
"sha256:9930853d451086c4c084d83a87294bdb0c5bc0fa4105a26c487ac09ea62e565b",
"sha256:99d16862e802e7c50c3b6cdd1bf041b6142335c9c2b426631f731257adfe5a15",
"sha256:9ed4a2852b3de7a64884afcc6936db771707943249a060aec8e551c16361d478",
"sha256:9f7796959c9c036a115d34696563f75d4a2912d3b97c15c15f2a36bdd5496ce9",
"sha256:a04b7a9017b8d0341ebbe77f61b74df1cf1b714f42b671a06f4912dc93d82597",
"sha256:a1b3c4ca3bec8e0ad9d32ce62444c5f3913588124a922629aa7d39357b2adf3f",
"sha256:a290a417608f50137bec731d1f22ff3efebac72845530807a8433b2db9358c95",
"sha256:a33f7c5acf44961f29018b13f0b5f5e1589ac0cfdf75a97c9774cf7ec84d09e0",
"sha256:a39be79a7c36e9a2e20376261c30deb3cdca86b50f7462ae9ff10a755c6720b9",
"sha256:a50a66fa34dd7f9dcdbc7602a1b7bf6f9ab030b4f43e892324193423d9ede180",
"sha256:a5ce1bdee102f7e60c075274df10b892d9ff5183ad6f5f515973eda8903dfe4c",
"sha256:a763dd33d6e27c9b4db3f8089a5fa39179a8a3cf48ce702b24a857d7c621333c",
"sha256:a773199dc42b5d02fcd46c8add552da2c4725ce2caa069527c7e27b5b6089e85",
"sha256:aa3c925502bd0b957a96a5619134bcdc0382ef73cffd40bad218ced3586bcf8d",
"sha256:aeb6db2f4ab54ac21a3851d05130a2aa78a6f6a5f14003f9ae3114fb8b210850",
"sha256:af670708e145b048ead87375b899229443f2d0b4af2d1450d7701c74cd932b03",
"sha256:afa24e5750c9b89ad5a7efef037efe49f4e339f20a94bf678c422c0c71e1207a",
"sha256:b02cc1cac9099c0ec72da09593e7fadb1b6cf88a101acc8153592c700d732d80",
"sha256:b37c9ea942395de029be270f0eca8c141eb14e8455941495cd3b6f95bbe465f4",
"sha256:b3b521e117ab991d6b3b830656f464b354a42dbea2ca16a0e7d93d573f7ab7ff",
"sha256:b5ad8261f47c2a72d0f676bc40f752db8cfdcab911e970753343836e41d5a9a7",
"sha256:b9616ea14917d06736339cf36bb9eaf4eb52110a74136b0dc5eff94e92417d22",
"sha256:b9a03767c937b621ee267507bc394df97fb2f8f61130f39f2033f3c6c191f124",
"sha256:b9ae0008cff25e154ef1e3975a1705d344e844ffdeb34c25b007fd48c876e95d",
"sha256:bdd6412c1f38da7565126b174f4e644f362e317ef0560fac1fb9d0c70900ff4d",
"sha256:bfc417e58f277e949ed662d9cd050ddbb00c0dea8a828abaccc93dc357b7a6d1",
"sha256:c15b9e37bbca59657e4dcc63ad068c821a4676def15f04742c406748a0a11b9c",
"sha256:c677849947d523a082be6e0b5c9137f443a54e951a1711ef003ec52910c41ece",
"sha256:c9d247fcc33c90f2758f4162693250341e3f38cd094f64390076ef33ad0887f9",
"sha256:ca643295bf5441dd38dadf7571ca4b63961820eedbffbe46ceba0893bf226203",
"sha256:ca87f639094c72268e17bc7f57c1225cc38f9e191a489a0134762e3fec402c1a",
"sha256:cc060bc17b9de27874997d612e37d52f72092f9b59d1e04284a90ed8113cadca",
"sha256:ccf4a73e07bfbd790443d6b3c1f1447ffda23cc9391e40c035d9b7d3514b57b8",
"sha256:cf36cadeb9c989f760a13058dbc455e5406ec3d2d247c705c8d4bc6dd1b0fcc6",
"sha256:d47e2bdeba4fb1986af2ba395ce51223f4d460e6e77119439e78f2b592cafade",
"sha256:db78cc5c03b446a43413165aa873e2f408e9fd5ddb45533e7bd3b638bace867c",
"sha256:dbc5029c61f9ebb2d4c247f13584a0ef0e8e49abb13e56460310821aca3ffcaf",
"sha256:ddb319f869d497ef2d3d56319360b61284a9a1d8b3de3bc936748698acfda6be",
"sha256:e0e4fdeae6c0a9d886749780ec5dcf469e98f27b312efa93008d03eaa2426fd5",
"sha256:e4c5e7edf1e7bcbde3b52058f171a411e2a24a081b3e951d685dfea4c3c383d5",
"sha256:e71c9dba78671d38a549e3b2d52514f50e199f9d7e18ed9b0180adeef0d04130",
"sha256:e997d22e0d1e08c8752f61675a75d93659f7aa4dbeaee54207f8d877817b4a0c",
"sha256:efa5834ba5e6c70b22afdca3894097e5a592d8d483c976359654ba990477799a",
"sha256:f2d951002b11962b26afb31f758c18ad39771f287b100fa5adb1d09a47eaaf5b",
"sha256:f3f96f57cea35ba19fd23a20b38fa0dfa3d87d582507129b8c8e314aa298f59b",
"sha256:f738051052abc95dc17f9a4c92044294a263fb7f762efdb13e528d419005c0e4",
"sha256:f76784355060999c36fa807b59faecb38f5769ae58283d00270835773f95e35b",
"sha256:f92462ea3888c99439f58f7561ecd5dd4cf8b8b1b259ccf5376667b8c46ee747",
"sha256:fefd18b29f3b84a0cdea1d86340219d9871c3b0673a38e722a73a2c39591eaa7"
],
"version": "==3.0.0"
"version": "==3.6.0"
},
"bitstring": {
"hashes": [
"sha256:3282a896814813f8fe5fa09dbafac842c57aace1d3bfd94546c6f1ed9aafcbe1",
"sha256:81800bc4e00b6508716adbae648e741256355c8dfd19541f76482fb89bee0313"
"sha256:69d1587f0ac18dc7d93fc7e80d5f447161a33e57027e726dc18a0a8bacf1711a",
"sha256:a08bc09d3857216d4c0f412a1611056f1cc2b64fd254fb1e8a0afba7cfa1a95a"
],
"markers": "python_version >= '3.8'",
"version": "==4.3.0"
"version": "==4.3.1"
},
"cffi": {
"hashes": [
@@ -247,54 +244,72 @@
"markers": "platform_python_implementation != 'PyPy'",
"version": "==1.17.1"
},
"click": {
"hashes": [
"sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202",
"sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b"
],
"markers": "python_version >= '3.10'",
"version": "==8.2.1"
},
"cryptography": {
"hashes": [
"sha256:1923cb251c04be85eec9fda837661c67c1049063305d6be5721643c22dd4e2b7",
"sha256:37d76e6863da3774cd9db5b409a9ecfd2c71c981c38788d3fcfaf177f447b731",
"sha256:3c672a53c0fb4725a29c303be906d3c1fa99c32f58abe008a82705f9ee96f40b",
"sha256:404fdc66ee5f83a1388be54300ae978b2efd538018de18556dde92575e05defc",
"sha256:4ac4c9f37eba52cb6fbeaf5b59c152ea976726b865bd4cf87883a7e7006cc543",
"sha256:62901fb618f74d7d81bf408c8719e9ec14d863086efe4185afd07c352aee1d2c",
"sha256:660cb7312a08bc38be15b696462fa7cc7cd85c3ed9c576e81f4dc4d8b2b31591",
"sha256:708ee5f1bafe76d041b53a4f95eb28cdeb8d18da17e597d46d7833ee59b97ede",
"sha256:761817a3377ef15ac23cd7834715081791d4ec77f9297ee694ca1ee9c2c7e5eb",
"sha256:831c3c4d0774e488fdc83a1923b49b9957d33287de923d58ebd3cec47a0ae43f",
"sha256:84111ad4ff3f6253820e6d3e58be2cc2a00adb29335d4cacb5ab4d4d34f2a123",
"sha256:8b3e6eae66cf54701ee7d9c83c30ac0a1e3fa17be486033000f2a73a12ab507c",
"sha256:9e6fc8a08e116fb7c7dd1f040074c9d7b51d74a8ea40d4df2fc7aa08b76b9e6c",
"sha256:a01956ddfa0a6790d594f5b34fc1bfa6098aca434696a03cfdbe469b8ed79285",
"sha256:abc998e0c0eee3c8a1904221d3f67dcfa76422b23620173e28c11d3e626c21bd",
"sha256:b15492a11f9e1b62ba9d73c210e2416724633167de94607ec6069ef724fad092",
"sha256:be4ce505894d15d5c5037167ffb7f0ae90b7be6f2a98f9a5c3442395501c32fa",
"sha256:c5eb858beed7835e5ad1faba59e865109f3e52b3783b9ac21e7e47dc5554e289",
"sha256:cd4e834f340b4293430701e772ec543b0fbe6c2dea510a5286fe0acabe153a02",
"sha256:d2436114e46b36d00f8b72ff57e598978b37399d2786fd39793c36c6d5cb1c64",
"sha256:eb33480f1bad5b78233b0ad3e1b0be21e8ef1da745d8d2aecbb20671658b9053",
"sha256:eca27345e1214d1b9f9490d200f9db5a874479be914199194e746c893788d417",
"sha256:ed3534eb1090483c96178fcb0f8893719d96d5274dfde98aa6add34614e97c8e",
"sha256:f3f6fdfa89ee2d9d496e2c087cebef9d4fcbb0ad63c40e821b39f74bf48d9c5e",
"sha256:f53c2c87e0fb4b0c00fa9571082a057e37690a8f12233306161c8f4b819960b7",
"sha256:f5e7cb1e5e56ca0933b4873c0220a78b773b24d40d186b6738080b73d3d0a756",
"sha256:f677e1268c4e23420c3acade68fac427fffcb8d19d7df95ed7ad17cdef8404f4"
"sha256:00e8724bdad672d75e6f069b27970883179bd472cd24a63f6e620ca7e41cc0c5",
"sha256:048e7ad9e08cf4c0ab07ff7f36cc3115924e22e2266e034450a890d9e312dd74",
"sha256:0d9ef57b6768d9fa58e92f4947cea96ade1233c0e236db22ba44748ffedca394",
"sha256:18f878a34b90d688982e43f4b700408b478102dd58b3e39de21b5ebf6509c301",
"sha256:1b7fa6a1c1188c7ee32e47590d16a5a0646270921f8020efc9a511648e1b2e08",
"sha256:20ae4906a13716139d6d762ceb3e0e7e110f7955f3bc3876e3a07f5daadec5f3",
"sha256:20d15aed3ee522faac1a39fbfdfee25d17b1284bafd808e1640a74846d7c4d1b",
"sha256:2384f2ab18d9be88a6e4f8972923405e2dbb8d3e16c6b43f15ca491d7831bd18",
"sha256:275ba5cc0d9e320cd70f8e7b96d9e59903c815ca579ab96c1e37278d231fc402",
"sha256:2dac5ec199038b8e131365e2324c03d20e97fe214af051d20c49db129844e8b3",
"sha256:31a2b9a10530a1cb04ffd6aa1cd4d3be9ed49f7d77a4dafe198f3b382f41545c",
"sha256:3436128a60a5e5490603ab2adbabc8763613f638513ffa7d311c900a8349a2a0",
"sha256:3b5bf5267e98661b9b888a9250d05b063220dfa917a8203744454573c7eb79db",
"sha256:3de77e4df42ac8d4e4d6cdb342d989803ad37707cf8f3fbf7b088c9cbdd46427",
"sha256:44647c5d796f5fc042bbc6d61307d04bf29bccb74d188f18051b635f20a9c75f",
"sha256:550ae02148206beb722cfe4ef0933f9352bab26b087af00e48fdfb9ade35c5b3",
"sha256:599c8d7df950aa68baa7e98f7b73f4f414c9f02d0e8104a30c0182a07732638b",
"sha256:5b64e668fc3528e77efa51ca70fadcd6610e8ab231e3e06ae2bab3b31c2b8ed9",
"sha256:5bd6020c80c5b2b2242d6c48487d7b85700f5e0038e67b29d706f98440d66eb5",
"sha256:5c966c732cf6e4a276ce83b6e4c729edda2df6929083a952cc7da973c539c719",
"sha256:629127cfdcdc6806dfe234734d7cb8ac54edaf572148274fa377a7d3405b0043",
"sha256:705bb7c7ecc3d79a50f236adda12ca331c8e7ecfbea51edd931ce5a7a7c4f012",
"sha256:780c40fb751c7d2b0c6786ceee6b6f871e86e8718a8ff4bc35073ac353c7cd02",
"sha256:7a3085d1b319d35296176af31c90338eeb2ddac8104661df79f80e1d9787b8b2",
"sha256:826b46dae41a1155a0c0e66fafba43d0ede1dc16570b95e40c4d83bfcf0a451d",
"sha256:833dc32dfc1e39b7376a87b9a6a4288a10aae234631268486558920029b086ec",
"sha256:cc4d66f5dc4dc37b89cfef1bd5044387f7a1f6f0abb490815628501909332d5d",
"sha256:d063341378d7ee9c91f9d23b431a3502fc8bfacd54ef0a27baa72a0843b29159",
"sha256:e2a21a8eda2d86bb604934b6b37691585bd095c1f788530c1fcefc53a82b3453",
"sha256:e40b80ecf35ec265c452eea0ba94c9587ca763e739b8e559c128d23bff7ebbbf",
"sha256:e5b3dda1b00fb41da3af4c5ef3f922a200e33ee5ba0f0bc9ecf0b0c173958385",
"sha256:ea3c42f2016a5bbf71825537c2ad753f2870191134933196bee408aac397b3d9",
"sha256:eccddbd986e43014263eda489abbddfbc287af5cddfd690477993dbb31e31016",
"sha256:ee411a1b977f40bd075392c80c10b58025ee5c6b47a822a33c1198598a7a5f05",
"sha256:f4028f29a9f38a2025abedb2e409973709c660d44319c61762202206ed577c42",
"sha256:f68f833a9d445cc49f01097d95c83a850795921b3f7cc6488731e69bde3288da",
"sha256:fc022c1fa5acff6def2fc6d7819bbbd31ccddfe67d075331a65d9cfb28a20983"
],
"markers": "python_version >= '3.7' and python_full_version not in '3.9.0, 3.9.1'",
"version": "==44.0.0"
},
"ecdsa": {
"hashes": [
"sha256:2cea9b88407fdac7bbeca0833b189e4c9c53f2ef1e1eaa29f6224dbc809b707a",
"sha256:60eaad1199659900dd0af521ed462b793bbdf867432b3948e87416ae4caf6bf8"
],
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==0.19.0"
"version": "==45.0.6"
},
"esptool": {
"hashes": [
"sha256:dc4ef26b659e1a8dcb019147c0ea6d94980b34de99fbe09121c7941c8b254531"
"sha256:05cc4732eb2a9a7766c9e3531f7943d76ff0ca06dc9cd308d1d3d0b72f74aac2"
],
"index": "pypi",
"markers": "python_version >= '3.7'",
"version": "==4.8.1"
"markers": "python_version >= '3.10'",
"version": "==5.0.2"
},
"idna": {
"hashes": [
"sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9",
"sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"
],
"markers": "python_version >= '3.6'",
"version": "==3.10"
},
"intelhex": {
"hashes": [
@@ -303,14 +318,38 @@
],
"version": "==2.3.0"
},
"markdown-it-py": {
"hashes": [
"sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1",
"sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"
],
"markers": "python_version >= '3.8'",
"version": "==3.0.0"
},
"mdurl": {
"hashes": [
"sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8",
"sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"
],
"markers": "python_version >= '3.7'",
"version": "==0.1.2"
},
"mpremote": {
"hashes": [
"sha256:1a3c16d255748cfe54d4a897908651fc8286233173f7c7b2a0e56ae4b9fa940e",
"sha256:d3ae3d0a0ae7713c537be2b6afadd11c7cde5f1750ea1260f6667bb80071b15b"
"sha256:7f347318fb6d3bb8f89401d399a05efba39b51c74f747cebe92d3c6a9a4ee0b4",
"sha256:daed9b795fdf98edb0c9c4f7f892bf66f075ec5e728bdcc4ab0915abf23d5d17"
],
"index": "pypi",
"markers": "python_version >= '3.4'",
"version": "==1.24.1"
"version": "==1.26.0"
},
"platformdirs": {
"hashes": [
"sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc",
"sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4"
],
"markers": "python_version >= '3.9'",
"version": "==4.3.8"
},
"pycparser": {
"hashes": [
@@ -320,6 +359,14 @@
"markers": "python_version >= '3.8'",
"version": "==2.22"
},
"pygments": {
"hashes": [
"sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887",
"sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"
],
"markers": "python_version >= '3.8'",
"version": "==2.19.2"
},
"pyserial": {
"hashes": [
"sha256:3c77e014170dfffbd816e6ffc205e9842efb10be9f58ec16d3e8675b4925cddb",
@@ -394,13 +441,150 @@
],
"version": "==1.7.0"
},
"six": {
"rich": {
"hashes": [
"sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274",
"sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"
"sha256:536f5f1785986d6dbdea3c75205c473f970777b4a0d6c6dd1b696aa05a3fa04f",
"sha256:e497a48b844b0320d45007cdebfeaeed8db2a4f4bcf49f15e455cfc4af11eaa8"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==1.17.0"
"markers": "python_full_version >= '3.8.0'",
"version": "==14.1.0"
},
"rich-click": {
"hashes": [
"sha256:c3fa81ed8a671a10de65a9e20abf642cfdac6fdb882db1ef465ee33919fbcfe2",
"sha256:fd98c0ab9ddc1cf9c0b7463f68daf28b4d0033a74214ceb02f761b3ff2af3136"
],
"markers": "python_version >= '3.7'",
"version": "==1.8.9"
},
"sniffio": {
"hashes": [
"sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2",
"sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"
],
"markers": "python_version >= '3.7'",
"version": "==1.3.1"
},
"typing-extensions": {
"hashes": [
"sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36",
"sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76"
],
"markers": "python_version >= '3.9'",
"version": "==4.14.1"
},
"watchfiles": {
"hashes": [
"sha256:00645eb79a3faa70d9cb15c8d4187bb72970b2470e938670240c7998dad9f13a",
"sha256:04e4ed5d1cd3eae68c89bcc1a485a109f39f2fd8de05f705e98af6b5f1861f1f",
"sha256:0a7d40b77f07be87c6faa93d0951a0fcd8cbca1ddff60a1b65d741bac6f3a9f6",
"sha256:0ece16b563b17ab26eaa2d52230c9a7ae46cf01759621f4fbbca280e438267b3",
"sha256:11ee4444250fcbeb47459a877e5e80ed994ce8e8d20283857fc128be1715dac7",
"sha256:12b0a02a91762c08f7264e2e79542f76870c3040bbc847fb67410ab81474932a",
"sha256:12fe8eaffaf0faa7906895b4f8bb88264035b3f0243275e0bf24af0436b27259",
"sha256:130fc497b8ee68dce163e4254d9b0356411d1490e868bd8790028bc46c5cc297",
"sha256:17ab167cca6339c2b830b744eaf10803d2a5b6683be4d79d8475d88b4a8a4be1",
"sha256:199207b2d3eeaeb80ef4411875a6243d9ad8bc35b07fc42daa6b801cc39cc41c",
"sha256:20ecc8abbd957046f1fe9562757903f5eaf57c3bce70929fda6c7711bb58074a",
"sha256:239736577e848678e13b201bba14e89718f5c2133dfd6b1f7846fa1b58a8532b",
"sha256:249590eb75ccc117f488e2fabd1bfa33c580e24b96f00658ad88e38844a040bb",
"sha256:27f30e14aa1c1e91cb653f03a63445739919aef84c8d2517997a83155e7a2fcc",
"sha256:29e7bc2eee15cbb339c68445959108803dc14ee0c7b4eea556400131a8de462b",
"sha256:328dbc9bff7205c215a7807da7c18dce37da7da718e798356212d22696404339",
"sha256:32d6d4e583593cb8576e129879ea0991660b935177c0f93c6681359b3654bfa9",
"sha256:3366f56c272232860ab45c77c3ca7b74ee819c8e1f6f35a7125556b198bbc6df",
"sha256:3434e401f3ce0ed6b42569128b3d1e3af773d7ec18751b918b89cd49c14eaafb",
"sha256:37d3d3f7defb13f62ece99e9be912afe9dd8a0077b7c45ee5a57c74811d581a4",
"sha256:3a6fd40bbb50d24976eb275ccb55cd1951dfb63dbc27cae3066a6ca5f4beabd5",
"sha256:3aba215958d88182e8d2acba0fdaf687745180974946609119953c0e112397dc",
"sha256:406520216186b99374cdb58bc48e34bb74535adec160c8459894884c983a149c",
"sha256:4281cd9fce9fc0a9dbf0fc1217f39bf9cf2b4d315d9626ef1d4e87b84699e7e8",
"sha256:42f92befc848bb7a19658f21f3e7bae80d7d005d13891c62c2cd4d4d0abb3433",
"sha256:48aa25e5992b61debc908a61ab4d3f216b64f44fdaa71eb082d8b2de846b7d12",
"sha256:5007f860c7f1f8df471e4e04aaa8c43673429047d63205d1630880f7637bca30",
"sha256:50a51a90610d0845a5931a780d8e51d7bd7f309ebc25132ba975aca016b576a0",
"sha256:51556d5004887045dba3acdd1fdf61dddea2be0a7e18048b5e853dcd37149b86",
"sha256:51b81e55d40c4b4aa8658427a3ee7ea847c591ae9e8b81ef94a90b668999353c",
"sha256:5366164391873ed76bfdf618818c82084c9db7fac82b64a20c44d335eec9ced5",
"sha256:54062ef956807ba806559b3c3d52105ae1827a0d4ab47b621b31132b6b7e2866",
"sha256:60022527e71d1d1fda67a33150ee42869042bce3d0fcc9cc49be009a9cded3fb",
"sha256:622d6b2c06be19f6e89b1d951485a232e3b59618def88dbeda575ed8f0d8dbf2",
"sha256:62cc7a30eeb0e20ecc5f4bd113cd69dcdb745a07c68c0370cea919f373f65d9e",
"sha256:693ed7ec72cbfcee399e92c895362b6e66d63dac6b91e2c11ae03d10d503e575",
"sha256:6d2404af8db1329f9a3c9b79ff63e0ae7131986446901582067d9304ae8aaf7f",
"sha256:7049e52167fc75fc3cc418fc13d39a8e520cbb60ca08b47f6cedb85e181d2f2a",
"sha256:7080c4bb3efd70a07b1cc2df99a7aa51d98685be56be6038c3169199d0a1c69f",
"sha256:7738027989881e70e3723c75921f1efa45225084228788fc59ea8c6d732eb30d",
"sha256:7a7bd57a1bb02f9d5c398c0c1675384e7ab1dd39da0ca50b7f09af45fa435277",
"sha256:7b3443f4ec3ba5aa00b0e9fa90cf31d98321cbff8b925a7c7b84161619870bc9",
"sha256:7c55b0f9f68590115c25272b06e63f0824f03d4fc7d6deed43d8ad5660cabdbf",
"sha256:7fd1b3879a578a8ec2076c7961076df540b9af317123f84569f5a9ddee64ce92",
"sha256:8076a5769d6bdf5f673a19d51da05fc79e2bbf25e9fe755c47595785c06a8c72",
"sha256:80f811146831c8c86ab17b640801c25dc0a88c630e855e2bef3568f30434d52b",
"sha256:8412eacef34cae2836d891836a7fff7b754d6bcac61f6c12ba5ca9bc7e427b68",
"sha256:865c8e95713744cf5ae261f3067861e9da5f1370ba91fc536431e29b418676fa",
"sha256:86b1e28d4c37e89220e924305cd9f82866bb0ace666943a6e4196c5df4d58dcc",
"sha256:891c69e027748b4a73847335d208e374ce54ca3c335907d381fde4e41661b13b",
"sha256:8ac164e20d17cc285f2b94dc31c384bc3aa3dd5e7490473b3db043dd70fbccfd",
"sha256:8c5701dc474b041e2934a26d31d39f90fac8a3dee2322b39f7729867f932b1d4",
"sha256:90ebb429e933645f3da534c89b29b665e285048973b4d2b6946526888c3eb2c7",
"sha256:923fec6e5461c42bd7e3fd5ec37492c6f3468be0499bc0707b4bbbc16ac21792",
"sha256:935f9edd022ec13e447e5723a7d14456c8af254544cefbc533f6dd276c9aa0d9",
"sha256:95ab1594377effac17110e1352989bdd7bdfca9ff0e5eeccd8c69c5389b826d0",
"sha256:9974d2f7dc561cce3bb88dfa8eb309dab64c729de85fba32e98d75cf24b66297",
"sha256:9c733cda03b6d636b4219625a4acb5c6ffb10803338e437fb614fef9516825ef",
"sha256:9dc001c3e10de4725c749d4c2f2bdc6ae24de5a88a339c4bce32300a31ede179",
"sha256:9f811079d2f9795b5d48b55a37aa7773680a5659afe34b54cc1d86590a51507d",
"sha256:a2726d7bfd9f76158c84c10a409b77a320426540df8c35be172444394b17f7ea",
"sha256:a479466da6db5c1e8754caee6c262cd373e6e6c363172d74394f4bff3d84d7b5",
"sha256:a543492513a93b001975ae283a51f4b67973662a375a403ae82f420d2c7205ee",
"sha256:a89c75a5b9bc329131115a409d0acc16e8da8dfd5867ba59f1dd66ae7ea8fa82",
"sha256:a8f6f72974a19efead54195bc9bed4d850fc047bb7aa971268fd9a8387c89011",
"sha256:a9ccbf1f129480ed3044f540c0fdbc4ee556f7175e5ab40fe077ff6baf286d4e",
"sha256:aa0cc8365ab29487eb4f9979fd41b22549853389e22d5de3f134a6796e1b05a4",
"sha256:adb4167043d3a78280d5d05ce0ba22055c266cf8655ce942f2fb881262ff3cdf",
"sha256:af06c863f152005c7592df1d6a7009c836a247c9d8adb78fef8575a5a98699db",
"sha256:b067915e3c3936966a8607f6fe5487df0c9c4afb85226613b520890049deea20",
"sha256:b7c5f6fe273291f4d414d55b2c80d33c457b8a42677ad14b4b47ff025d0893e4",
"sha256:b915daeb2d8c1f5cee4b970f2e2c988ce6514aace3c9296e58dd64dc9aa5d575",
"sha256:ba0e3255b0396cac3cc7bbace76404dd72b5438bf0d8e7cefa2f79a7f3649caa",
"sha256:bda8136e6a80bdea23e5e74e09df0362744d24ffb8cd59c4a95a6ce3d142f79c",
"sha256:bfe3c517c283e484843cb2e357dd57ba009cff351edf45fb455b5fbd1f45b15f",
"sha256:c588c45da9b08ab3da81d08d7987dae6d2a3badd63acdb3e206a42dbfa7cb76f",
"sha256:c600e85f2ffd9f1035222b1a312aff85fd11ea39baff1d705b9b047aad2ce267",
"sha256:c68e9f1fcb4d43798ad8814c4c1b61547b014b667216cb754e606bfade587018",
"sha256:c9649dfc57cc1f9835551deb17689e8d44666315f2e82d337b9f07bd76ae3aa2",
"sha256:cb45350fd1dc75cd68d3d72c47f5b513cb0578da716df5fba02fff31c69d5f2d",
"sha256:cbcf8630ef4afb05dc30107bfa17f16c0896bb30ee48fc24bf64c1f970f3b1fd",
"sha256:cbd949bdd87567b0ad183d7676feb98136cde5bb9025403794a4c0db28ed3a47",
"sha256:cc08ef8b90d78bfac66f0def80240b0197008e4852c9f285907377b2947ffdcb",
"sha256:cd17a1e489f02ce9117b0de3c0b1fab1c3e2eedc82311b299ee6b6faf6c23a29",
"sha256:d05686b5487cfa2e2c28ff1aa370ea3e6c5accfe6435944ddea1e10d93872147",
"sha256:d0e10e6f8f6dc5762adee7dece33b722282e1f59aa6a55da5d493a97282fedd8",
"sha256:d181ef50923c29cf0450c3cd47e2f0557b62218c50b2ab8ce2ecaa02bd97e670",
"sha256:d1caf40c1c657b27858f9774d5c0e232089bca9cb8ee17ce7478c6e9264d2587",
"sha256:d7642b9bc4827b5518ebdb3b82698ada8c14c7661ddec5fe719f3e56ccd13c97",
"sha256:d9481174d3ed982e269c090f780122fb59cee6c3796f74efe74e70f7780ed94c",
"sha256:d9ba68ec283153dead62cbe81872d28e053745f12335d037de9cbd14bd1877f5",
"sha256:da71945c9ace018d8634822f16cbc2a78323ef6c876b1d34bbf5d5222fd6a72e",
"sha256:dc44678a72ac0910bac46fa6a0de6af9ba1355669b3dfaf1ce5f05ca7a74364e",
"sha256:df32d59cb9780f66d165a9a7a26f19df2c7d24e3bd58713108b41d0ff4f929c6",
"sha256:df670918eb7dd719642e05979fc84704af913d563fd17ed636f7c4783003fdcc",
"sha256:e78b6ed8165996013165eeabd875c5dfc19d41b54f94b40e9fff0eb3193e5e8e",
"sha256:ed8fc66786de8d0376f9f913c09e963c66e90ced9aa11997f93bdb30f7c872a8",
"sha256:eff4b8d89f444f7e49136dc695599a591ff769300734446c0a86cba2eb2f9895",
"sha256:f21af781a4a6fbad54f03c598ab620e3a77032c5878f3d780448421a6e1818c7",
"sha256:f2bcdc54ea267fe72bfc7d83c041e4eb58d7d8dc6f578dfddb52f037ce62f432",
"sha256:f2f0498b7d2a3c072766dba3274fe22a183dbea1f99d188f1c6c72209a1063dc",
"sha256:f7208ab6e009c627b7557ce55c465c98967e8caa8b11833531fdf95799372633",
"sha256:f7590d5a455321e53857892ab8879dce62d1f4b04748769f5adf2e707afb9d4f",
"sha256:fa257a4d0d21fcbca5b5fcba9dca5a78011cb93c0323fb8855c6d2dfbc76eb77",
"sha256:fba9b62da882c1be1280a7584ec4515d0a6006a94d6e5819730ec2eab60ffe12",
"sha256:fe4371595edf78c41ef8ac8df20df3943e13defd0efcb732b2e393b5a8a7a71f"
],
"index": "pypi",
"markers": "python_version >= '3.9'",
"version": "==1.1.0"
}
},
"develop": {}

View File

@@ -5,4 +5,5 @@ from settings import Settings
s = Settings()
name = s.get('name', 'led')
wifi.ap(name, '')
password = s.get("ap_password", "")
wifi.ap(name, password)

View File

@@ -1,6 +1,6 @@
import asyncio
import aioespnow
from settings import Settings, set_settings
from settings import Settings
from web import web
from patterns import Patterns
import gc
@@ -8,51 +8,38 @@ import utime
import machine
import time
import wifi
import json
from p2p import p2p
async def main():
settings = Settings()
patterns = Patterns(4, settings["num_leds"], selected=settings["pattern"])
patterns.set_color1(tuple(int(settings["color1"][i:i+2], 16) for i in (1, 5, 3)))
patterns.set_color2(tuple(int(settings["color2"][i:i+2], 16) for i in (1, 5, 3)))
patterns.set_brightness(int(settings["brightness"]))
patterns.set_delay(int(settings["delay"]))
patterns = Patterns(settings["led_pin"], settings["num_leds"], selected=settings["pattern"])
if settings["color_order"] == "rbg": color_order = (1, 5, 3)
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)
# start the server in a bacakground task
print("Starting")
server = asyncio.create_task(w.start_server(host="0.0.0.0", port=80))
#wdt = machine.WDT(timeout=10000)
#wdt.feed()
wdt = machine.WDT(timeout=10000)
wdt.feed()
async def tick():
while True:
patterns.tick()
await asyncio.sleep_ms(1)
asyncio.create_task(p2p(settings, patterns))
asyncio.create_task(system())
patterns.select(settings["pattern"])
await patterns.run()
async def espnow():
e = aioespnow.AIOESPNow() # Returns AIOESPNow enhanced with async support
e.active(True)
async for mac, msg in e:
print(msg)
set_settings(msg, settings, patterns)
asyncio.create_task(tick())
asyncio.create_task(espnow())
while True:
#print(time.localtime())
# gc.collect()
for i in range(60):
#wdt.feed()
await asyncio.sleep_ms(500)
# cleanup before ending the application
await server

16
src/p2p.py Normal file
View File

@@ -0,0 +1,16 @@
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))

View File

@@ -2,290 +2,339 @@ from machine import Pin
from neopixel import NeoPixel
import utime
import random
import _thread
import asyncio
from patterns_base import Patterns as PatternsBase
class Patterns:
# 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(PatternsBase):
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.num_leds = num_leds
self.pattern_step = 0
self.last_update = utime.ticks_ms()
self.delay = delay
self.brightness = brightness
super().__init__(pin, num_leds, color1, color2, brightness, selected, delay)
self.auto = True
self.step = 0
self.patterns = {
"off": self.off,
"on" : self.on,
"color_wipe": self.color_wipe_step,
"rainbow_cycle": self.rainbow_cycle_step,
"theater_chase": self.theater_chase_step,
"blink": self.blink_step,
"random_color_wipe": self.random_color_wipe_step,
"random_rainbow_cycle": self.random_rainbow_cycle_step,
"random_theater_chase": self.random_theater_chase_step,
"random_blink": self.random_blink_step,
"color_transition": self.color_transition_step,
"external": None
"blink": self.blink,
"rainbow": self.rainbow,
"pulse": self.pulse,
"transition": self.transition,
"n_chase": self.n_chase,
}
self.selected = selected
self.color1 = color1
self.color2 = color2
self.transition_duration = 50 # Duration of color transition in milliseconds
self.transition_step = 0
def sync(self):
self.pattern_step=0
self.last_update = utime.ticks_ms()
def tick(self):
if self.patterns[self.selected]:
self.patterns[self.selected]()
def update_num_leds(self, pin, num_leds):
self.n = NeoPixel(Pin(pin, Pin.OUT), num_leds)
self.num_leds = num_leds
self.pattern_step = 0
def set_delay(self, delay):
self.delay = delay
def set_brightness(self, brightness):
self.brightness = brightness
def set_color1(self, color):
print(color)
self.color1 = self.apply_brightness(color)
def set_color2(self, color):
self.color2 = self.apply_brightness(color)
def apply_brightness(self, color):
return tuple(int(c * self.brightness / 255) for c in color)
def select(self, pattern):
if pattern in self.patterns:
self.selected = pattern
return True
return False
def set(self, i, color):
self.n[i] = color
def write(self):
self.n.write()
def fill(self):
for i in range(self.num_leds):
self.n[i] = self.color1
self.n.write()
def off(self):
color = self.color1
self.color1 = (0,0,0)
self.fill()
self.color1 = color
def on(self):
color = self.color1
self.color1 = self.apply_brightness(self.color1)
self.fill()
self.color1 = color
def color_wipe_step(self):
color = self.apply_brightness(self.color1)
def blink(self):
self.stopped = False
self.running = True
state = True # True = on, False = off
last_update = utime.ticks_ms()
while self.running:
current_time = utime.ticks_ms()
if utime.ticks_diff(current_time, self.last_update) >= self.delay:
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
if utime.ticks_diff(current_time, last_update) >= self.delay:
if state:
self.fill(self.apply_brightness(self.colors[0]))
else:
self.pattern_step = 0
self.last_update = current_time
self.fill((0, 0, 0))
state = not state
last_update = current_time
self.running = False
self.stopped = True
def rainbow_cycle_step(self):
current_time = utime.ticks_ms()
if utime.ticks_diff(current_time, self.last_update) >= self.delay/5:
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)
def rainbow(self):
self.stopped = False
self.running = True
step = self.step % 256
step_amount = max(1, int(self.n1)) # n1 controls step increment
# If auto is False, run once and update step
if not self.auto:
for i in range(self.num_leds):
rc_index = (i * 256 // self.num_leds) + self.pattern_step
self.n[i] = self.apply_brightness(wheel(rc_index & 255))
rc_index = (i * 256 // self.num_leds) + step
self.n[i] = self.apply_brightness(self.wheel(rc_index & 255))
self.n.write()
self.pattern_step = (self.pattern_step + 1) % 256
self.last_update = current_time
# Increment step by n1 for next call
self.step = (step + step_amount) % 256
self.running = False
self.stopped = True
return
def theater_chase_step(self):
# Auto is True: run continuously
sleep_ms = max(1, int(self.delay))
last_update = utime.ticks_ms()
while self.running:
current_time = utime.ticks_ms()
if utime.ticks_diff(current_time, self.last_update) >= self.delay:
if utime.ticks_diff(current_time, last_update) >= sleep_ms:
for i in range(self.num_leds):
if (i + self.pattern_step) % 3 == 0:
self.n[i] = self.apply_brightness(self.color1)
else:
self.n[i] = (0, 0, 0)
rc_index = (i * 256 // self.num_leds) + step
self.n[i] = self.apply_brightness(self.wheel(rc_index & 255))
self.n.write()
self.pattern_step = (self.pattern_step + 1) % 3
self.last_update = current_time
step = (step + step_amount) % 256
self.step = step
last_update = current_time
self.running = False
self.stopped = True
def blink_step(self):
def pulse(self):
self.stopped = False
self.running = True
self.off()
# Get timing parameters, ensure non-negative
attack_ms = max(0, int(self.n1)) # Attack time in ms
hold_ms = max(0, int(self.n2)) # Hold time in ms
decay_ms = max(0, int(self.n3)) # Decay time in ms
# Ensure we have at least one color
if not self.colors:
self.colors = [(255, 255, 255)]
color_index = 0
# Calculate minimum update interval based on LED count
# NeoPixel timing: ~30µs per LED + reset time = ~6ms for 200 LEDs
# Use 10ms minimum to ensure writes complete + overhead
min_write_time_ms = (self.num_leds * 30) // 1000 + 1 # Convert µs to ms, add 1ms overhead
update_interval = max(10, min_write_time_ms + 4) # At least 10ms, add margin for safety
while self.running:
cycle_start = utime.ticks_ms()
# Get the current color from the cycle
base_color = self.colors[color_index % len(self.colors)]
# Attack phase: fade from 0 to full brightness
if attack_ms > 0:
attack_start = utime.ticks_ms()
last_update = attack_start
while self.running and utime.ticks_diff(utime.ticks_ms(), attack_start) < attack_ms:
now = utime.ticks_ms()
if utime.ticks_diff(now, last_update) >= update_interval:
elapsed = utime.ticks_diff(now, attack_start)
brightness_factor = min(1.0, elapsed / attack_ms)
color = tuple(int(c * brightness_factor) for c in base_color)
self.fill(self.apply_brightness(color))
last_update = now
# Hold phase: maintain full brightness
if hold_ms > 0 and self.running:
self.fill(self.apply_brightness(base_color))
hold_start = utime.ticks_ms()
while self.running and utime.ticks_diff(utime.ticks_ms(), hold_start) < hold_ms:
pass
# Decay phase: fade from full brightness to 0
if decay_ms > 0:
decay_start = utime.ticks_ms()
last_update = decay_start
while self.running and utime.ticks_diff(utime.ticks_ms(), decay_start) < decay_ms:
now = utime.ticks_ms()
if utime.ticks_diff(now, last_update) >= update_interval:
elapsed = utime.ticks_diff(now, decay_start)
brightness_factor = max(0.0, 1.0 - (elapsed / decay_ms))
color = tuple(int(c * brightness_factor) for c in base_color)
self.fill(self.apply_brightness(color))
last_update = now
# Move to next color in the cycle
color_index += 1
# If auto flag is False, run only once and exit
if not self.auto:
break
# Ensure the cycle takes exactly delay milliseconds before restarting
if self.running:
self.off()
wait_until = utime.ticks_add(cycle_start, self.delay)
while self.running and utime.ticks_diff(wait_until, utime.ticks_ms()) > 0:
pass
self.running = False
self.stopped = True
def transition(self):
"""Transition between colors, taking delay ms between each color"""
self.stopped = False
self.running = True
if not self.colors:
# No colors, turn off
self.off()
self.running = False
self.stopped = True
return
if len(self.colors) == 1:
# Only one color, just stay that color
last_update = utime.ticks_ms()
while self.running:
current_time = utime.ticks_ms()
if utime.ticks_diff(current_time, self.last_update) >= self.delay:
if self.pattern_step % 2 == 0:
for i in range(self.num_leds):
self.n[i] = self.apply_brightness(self.color1)
else:
for i in range(self.num_leds):
self.n[i] = (0, 0, 0)
self.n.write()
self.pattern_step = (self.pattern_step + 1) % 2
self.last_update = current_time
if utime.ticks_diff(current_time, last_update) >= 100:
self.fill(self.apply_brightness(self.colors[0]))
last_update = current_time
self.running = False
self.stopped = True
return
def random_color_wipe_step(self):
current_time = utime.ticks_ms()
if utime.ticks_diff(current_time, self.last_update) >= self.delay:
color = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))
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
# If auto is False, only transition between color1 and color2
if not self.auto:
if len(self.colors) < 2:
# Need at least 2 colors for transition
self.running = False
self.stopped = True
return
def random_rainbow_cycle_step(self):
current_time = utime.ticks_ms()
if utime.ticks_diff(current_time, self.last_update) >= self.delay:
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)
transition_duration = max(10, self.delay) # At least 10ms
update_interval = max(10, transition_duration // 50) # Update every ~2% of transition
random_offset = random.randint(0, 255)
for i in range(self.num_leds):
rc_index = (i * 256 // self.num_leds) + self.pattern_step + random_offset
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
# Transition from color1 to color2
color1 = self.colors[0]
color2 = self.colors[1]
def random_theater_chase_step(self):
current_time = utime.ticks_ms()
if utime.ticks_diff(current_time, self.last_update) >= self.delay:
color = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))
for i in range(self.num_leds):
if (i + self.pattern_step) % 3 == 0:
self.n[i] = self.apply_brightness(color)
else:
self.n[i] = (0, 0, 0)
self.n.write()
self.pattern_step = (self.pattern_step + 1) % 3
self.last_update = current_time
transition_start = utime.ticks_ms()
last_update = transition_start
def random_blink_step(self):
current_time = utime.ticks_ms()
if utime.ticks_diff(current_time, self.last_update) >= self.delay:
color = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))
if self.pattern_step % 2 == 0:
for i in range(self.num_leds):
self.n[i] = self.apply_brightness(color)
else:
for i in range(self.num_leds):
self.n[i] = (0, 0, 0)
self.n.write()
self.pattern_step = (self.pattern_step + 1) % 2
self.last_update = current_time
while self.running and utime.ticks_diff(utime.ticks_ms(), transition_start) < transition_duration:
now = utime.ticks_ms()
if utime.ticks_diff(now, last_update) >= update_interval:
# Calculate interpolation factor (0.0 to 1.0)
elapsed = utime.ticks_diff(now, transition_start)
factor = min(1.0, elapsed / transition_duration)
def color_transition_step(self):
current_time = utime.ticks_ms()
if utime.ticks_diff(current_time, self.last_update) >= self.delay:
# Calculate transition factor based on elapsed time
transition_factor = (self.pattern_step * 100) / self.transition_duration
if transition_factor > 100:
transition_factor = 100
color = self.interpolate_color(self.color1, self.color2, transition_factor / 100)
# Apply the interpolated color to all LEDs
for i in range(self.num_leds):
self.n[i] = self.apply_brightness(color)
self.n.write()
self.pattern_step += self.delay
if self.pattern_step > self.transition_duration:
self.pattern_step = 0
self.last_update = current_time
def interpolate_color(self, color1, color2, factor):
return (
int(color1[0] + (color2[0] - color1[0]) * factor),
int(color1[1] + (color2[1] - color1[1]) * factor),
int(color1[2] + (color2[2] - color1[2]) * factor)
# Interpolate between color1 and color2
interpolated = tuple(
int(color1[i] + (color2[i] - color1[i]) * factor)
for i in range(3)
)
def two_steps_forward_one_step_back_step(self):
# Apply brightness and fill
self.fill(self.apply_brightness(interpolated))
last_update = now
self.running = False
self.stopped = True
return
# Auto is True: cycle through all colors continuously
color_index = 0
transition_duration = max(10, self.delay) # At least 10ms
update_interval = max(10, transition_duration // 50) # Update every ~2% of transition
while self.running:
# Get current and next color
current_color = self.colors[color_index % len(self.colors)]
next_color = self.colors[(color_index + 1) % len(self.colors)]
# Transition from current to next color
transition_start = utime.ticks_ms()
last_update = transition_start
while self.running and utime.ticks_diff(utime.ticks_ms(), transition_start) < transition_duration:
now = utime.ticks_ms()
if utime.ticks_diff(now, last_update) >= update_interval:
# Calculate interpolation factor (0.0 to 1.0)
elapsed = utime.ticks_diff(now, transition_start)
factor = min(1.0, elapsed / transition_duration)
# Interpolate between colors
interpolated = tuple(
int(current_color[i] + (next_color[i] - current_color[i]) * factor)
for i in range(3)
)
# Apply brightness and fill
self.fill(self.apply_brightness(interpolated))
last_update = now
# Move to next color
color_index = (color_index + 1) % len(self.colors)
self.running = False
self.stopped = True
def n_chase(self):
"""N-chase pattern: n1 LEDs of color0, n2 LEDs of color1, repeating.
Moves by n3 on even steps, n4 on odd steps (n3/n4 can be positive or negative)"""
self.stopped = False
self.running = True
if len(self.colors) < 2:
# Need at least 2 colors
self.running = False
self.stopped = True
return
n1 = max(1, int(self.n1)) # LEDs of color 0
n2 = max(1, int(self.n2)) # LEDs of color 1
n3 = int(self.n3) # Step movement on odd steps (can be negative)
n4 = int(self.n4) # Step movement on even steps (can be negative)
segment_length = n1 + n2
position = 0 # Current position offset
step_count = 0 # Track which step we're on
color0 = self.apply_brightness(self.colors[0])
color1 = self.apply_brightness(self.colors[1])
transition_duration = max(10, self.delay)
last_update = utime.ticks_ms()
while self.running:
current_time = utime.ticks_ms()
if utime.ticks_diff(current_time, self.last_update) >= self.delay:
# Move forward 2 steps and backward 1 step
if self.direction == 1: # Moving forward
if self.scanner_position < self.num_leds - 2:
self.scanner_position += 2 # Move forward 2 steps
else:
self.direction = -1 # Change direction to backward
else: # Moving backward
if self.scanner_position > 0:
self.scanner_position -= 1 # Move backward 1 step
else:
self.direction = 1 # Change direction to forward
if utime.ticks_diff(current_time, last_update) >= transition_duration:
# Clear all LEDs
self.n.fill((0, 0, 0))
# Set all LEDs to off
# Draw repeating pattern starting at position
for i in range(self.num_leds):
self.n[i] = (0, 0, 0)
# Calculate position in the repeating segment
relative_pos = (i - position) % segment_length
if relative_pos < 0:
relative_pos = (relative_pos + segment_length) % segment_length
# Set the current position to the color
self.n[self.scanner_position] = self.apply_brightness(self.color1)
# Apply the color transition
transition_factor = (self.pattern_step * 100) / self.transition_duration
if transition_factor > 100:
transition_factor = 100
color = self.interpolate_color(self.color1, self.color2, transition_factor / 100)
self.n[self.scanner_position] = self.apply_brightness(color)
# Determine which color based on position in segment
if relative_pos < n1:
self.n[i] = color0
else:
self.n[i] = color1
self.n.write()
self.pattern_step += self.delay
if self.pattern_step > self.transition_duration:
self.pattern_step = 0
self.last_update = current_time
# Move position by n3 or n4 on alternate steps
if step_count % 2 == 0:
position = position + n3
else:
position = position + n4
if __name__ == "__main__":
p = Patterns(4, 180)
p.set_color1((255,0,0))
p.set_color2((0,255,0))
#p.set_delay(10)
try:
while True:
for key in p.patterns:
print(key)
p.select(key)
for _ in range(2000):
p.tick()
utime.sleep_ms(1)
except KeyboardInterrupt:
p.fill((0, 0, 0))
# Wrap position to keep it reasonable
max_pos = self.num_leds + segment_length
position = position % max_pos
if position < 0:
position += max_pos
step_count += 1
last_update = current_time
self.running = False
self.stopped = True

152
src/patterns_base.py Normal file
View File

@@ -0,0 +1,152 @@
from machine import Pin
from neopixel import NeoPixel
import utime
import random
import _thread
import asyncio
import json
from presets import Presets
# 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:
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.num_leds = num_leds
self.pattern_step = 0
self.last_update = utime.ticks_ms()
self.delay = delay
self.brightness = brightness
self.auto = False
self.patterns = {}
self.selected = selected
# 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
if not self.colors: # Ensure at least one color exists
self.colors = [(0, 0, 0)]
self.transition_duration = delay * 50 # Default transition duration
self.hold_duration = delay * 10 # Default hold duration at each color
self.transition_step = 0 # Current step in the transition
self.current_color_idx = 0 # Index of the color currently being held/transitioned from
self.current_color = self.colors[self.current_color_idx] # The actual blended color
self.hold_start_time = utime.ticks_ms() # Time when the current color hold started
# New attributes for scanner patterns
self.scanner_direction = 1 # 1 for forward, -1 for backward
self.scanner_tail_length = 3 # Number of trailing pixels
self.running = False
self.stopped = True
self.presets = Presets()
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
return False
async def run(self):
print(f"Stopping pattern")
await self.stop()
self.running = True
print(f"Starting pattern {self.selected}")
if self.selected in self.patterns:
_thread.start_new_thread(self.patterns[self.selected], ())
else:
print(f"Pattern {self.selected} not found")
async def stop(self):
self.running = False
start = utime.ticks_ms()
while not self.stopped and utime.ticks_diff(utime.ticks_ms(), start) < 1000:
await asyncio.sleep_ms(0)
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
def update_num_leds(self, pin, num_leds):
self.n = NeoPixel(Pin(pin, Pin.OUT), num_leds)
self.num_leds = num_leds
self.pattern_step = 0
def set_color(self, num, color):
# Changed: More robust index check
if 0 <= num < len(self.colors):
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
self.colors.append(color)
return True
return False
def del_color(self, num):
# Changed: More robust index check and using del for lists
if 0 <= num < len(self.colors):
del self.colors[num]
return True
return False
def apply_brightness(self, color, brightness_override=None):
effective_brightness = brightness_override if brightness_override is not None else self.brightness
return tuple(int(c * effective_brightness / 255) for c in color)
def fill(self, color=None):
fill_color = color if color is not None else self.colors[0]
for i in range(self.num_leds):
self.n[i] = fill_color
self.n.write()
def off(self):
self.fill((0, 0, 0))
def on(self):
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

@@ -9,16 +9,21 @@ class Settings(dict):
def __init__(self):
super().__init__()
self.load() # Load settings from file during initialization
if self["color_order"] == "rbg": self.color_order = (1, 5, 3)
else: self.color_order = (1, 3, 5)
def set_defaults(self):
self["led_pin"] = 10
self["num_leds"] = 50
self["pattern"] = "on"
self["color1"] = "#00ff00"
self["color2"] = "#ff0000"
self["delay"] = 100
self["brightness"] = 10
self["color_order"] = "rgb"
self["name"] = f"led-{ubinascii.hexlify(wifi.get_mac()).decode()}"
self["ap_password"] = ""
self["id"] = 0
def save(self):
try:
@@ -38,6 +43,64 @@ class Settings(dict):
except Exception as e:
print(f"Error loading settings")
self.set_defaults()
self.save()
async def set_settings(self, data, patterns, save):
try:
print(f"Setting settings: {data}")
for key, value in data.items():
print(key, value)
if key == "colors":
buff = []
for color in value:
buff.append(tuple(int(color[i:i+2], 16) for i in self.color_order))
patterns.colors = buff
elif key == "num_leds":
patterns.update_num_leds(self["led_pin"], value)
elif key == "pattern":
if not patterns.select(value):
return "Pattern doesn't exist", 400
await patterns.run()
elif key == "delay":
delay = int(data["delay"])
patterns.delay = delay
elif key == "brightness":
brightness = int(data["brightness"])
patterns.brightness = brightness
elif key == "n1":
patterns.n1 = value
elif key == "n2":
patterns.n2 = value
elif key == "n3":
patterns.n3 = value
elif key == "n4":
patterns.n4 = value
elif key == "n5":
patterns.n5 = value
elif key == "n6":
patterns.n6 = value
elif key == "name":
self[key] = value
self.save()
machine.reset()
elif key == "color_order":
if value == "rbg": self.color_order = (1, 5, 3)
else: self.color_order = (1, 3, 5)
pass
elif key == "id":
pass
elif key == "led_pin":
patterns.update_num_leds(value, self["num_leds"])
else:
return "Invalid key", 400
self[key] = value
#print(self)
if save:
self.save()
print(self)
return "OK", 200
except (KeyError, ValueError):
return "Bad request", 400
# Example usage
def main():
@@ -52,42 +115,7 @@ def main():
print(f"Loaded number of LEDs: {new_settings['num_leds']}")
print(settings)
def set_settings(raw_json, settings, patterns):
try:
data = json.loads(raw_json)
print(data)
for key, value in data.items():
print(key, value)
if key == "color1":
patterns.set_color1(tuple(int(value[i:i+2], 16) for i in (1, 3, 5))) # Convert hex to RGB
elif key == "color2":
patterns.set_color2(tuple(int(value[i:i+2], 16) for i in (1, 3, 5))) # Convert hex to RGB
elif key == "num_leds":
patterns.update_num_leds(4, value)
elif key == "pattern":
if not patterns.select(value):
return "Pattern doesn't exist", 400
elif key == "delay":
delay = int(data["delay"])
patterns.set_delay(delay)
elif key == "brightness":
brightness = int(data["brightness"])
patterns.set_brightness(brightness)
elif key == "name":
settings[key] = value
settings.save()
machine.reset()
elif key == "sync":
patterns.sync()
return "OK", 200
else:
return "Invalid key", 400
settings[key] = value
settings.save()
return "OK", 200
except (KeyError, ValueError):
return "Bad request", 400
# Run the example
if __name__ == "__main__":

View File

@@ -4,72 +4,106 @@ body {
margin: 0 auto;
padding: 20px;
line-height: 1.6;
}
h1 {
}
h1 {
text-align: center;
}
}
form {
form {
margin-bottom: 20px;
}
label {
}
label {
display: block;
margin-bottom: 5px;
}
input[type="text"], input[type="submit"], input[type="range"], input[type="color"] {
}
input[type="text"],
input[type="submit"],
input[type="range"],
input[type="color"] {
width: 100%;
margin-bottom: 10px;
box-sizing: border-box;
}
input[type="range"] {
}
input[type="range"] {
-webkit-appearance: none;
appearance: none;
height: 25px;
background: #d3d3d3;
outline: none;
opacity: 0.7;
transition: opacity .2s;
}
input[type="range"]:hover {
transition: opacity 0.2s;
}
input[type="range"]:hover {
opacity: 1;
}
input[type="range"]::-webkit-slider-thumb {
}
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 25px;
height: 25px;
background: #4CAF50;
background: #4caf50;
cursor: pointer;
border-radius: 50%;
}
input[type="range"]::-moz-range-thumb {
}
input[type="range"]::-moz-range-thumb {
width: 25px;
height: 25px;
background: #4CAF50;
background: #4caf50;
cursor: pointer;
border-radius: 50%;
}
#pattern_buttons {
}
#pattern_buttons {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-bottom: 20px;
}
#pattern_buttons button {
}
#pattern_buttons button {
flex: 1 0 calc(33.333% - 10px);
padding: 10px;
background-color: #4CAF50;
background-color: #4caf50;
color: white;
border: none;
cursor: pointer;
transition: background-color 0.3s;
}
#pattern_buttons button:hover {
}
#pattern_buttons button:hover {
background-color: #45a049;
}
@media (max-width: 480px) {
}
@media (max-width: 480px) {
#pattern_buttons button {
flex: 1 0 calc(50% - 10px);
}
}
}
#connection-status {
width: 15px;
height: 15px;
border-radius: 50%;
display: inline-block; /* Or block, depending on where you put it */
margin-left: 10px; /* Adjust spacing as needed */
vertical-align: middle; /* Align with nearby text */
background-color: grey; /* Default: Unknown */
}
#connection-status.connecting {
background-color: yellow;
}
#connection-status.open {
background-color: green;
}
#connection-status.closing,
#connection-status.closed {
background-color: red;
}
#color_order_form label,
#color_order_form input[type="radio"] {
/* Ensures they behave as inline elements */
display: inline-block;
/* Adds some space between them for readability */
margin-right: 10px;
vertical-align: middle; /* Aligns them nicely if heights vary */
}

View File

@@ -2,7 +2,72 @@ let delayTimeout;
let brightnessTimeout;
let colorTimeout;
let color2Timeout;
let ws; // Variable to hold the WebSocket connection
let connectionStatusElement; // Variable to hold the connection status element
// Function to update the connection status indicator
function updateConnectionStatus(status) {
if (!connectionStatusElement) {
connectionStatusElement = document.getElementById("connection-status");
}
if (connectionStatusElement) {
connectionStatusElement.className = ""; // Clear existing classes
connectionStatusElement.classList.add(status);
// Optionally, you could also update text content based on status
// connectionStatusElement.textContent = status.charAt(0).toUpperCase() + status.slice(1);
}
}
// Function to establish WebSocket connection
function connectWebSocket() {
// Determine the WebSocket URL based on the current location
const wsUrl = `ws://${window.location.host}/ws`;
ws = new WebSocket(wsUrl);
updateConnectionStatus("connecting"); // Indicate connecting state
ws.onopen = function (event) {
console.log("WebSocket connection opened:", event);
updateConnectionStatus("open"); // Indicate open state
// Optionally, you could send an initial message here
};
ws.onmessage = function (event) {
console.log("WebSocket message received:", event.data);
};
ws.onerror = function (event) {
console.error("WebSocket error:", event);
updateConnectionStatus("closed"); // Indicate error state (treat as closed)
};
ws.onclose = function (event) {
if (event.wasClean) {
console.log(
`WebSocket connection closed cleanly, code=${event.code}, reason=${event.reason}`,
);
updateConnectionStatus("closed"); // Indicate closed state
} else {
console.error("WebSocket connection died");
updateConnectionStatus("closed"); // Indicate closed state
}
// Attempt to reconnect after a delay
setTimeout(connectWebSocket, 1000);
};
}
// Function to send data over WebSocket
function sendWebSocketData(data) {
if (ws && ws.readyState === WebSocket.OPEN) {
console.log("Sending data over WebSocket:", data);
ws.send(JSON.stringify(data));
} else {
console.error("WebSocket is not connected. Cannot send data:", data);
// You might want to queue messages or handle this in a different way
}
}
// Keep the post and get functions for now, they might still be useful
async function post(path, data) {
console.log(`POST to ${path}`, data);
try {
@@ -11,7 +76,7 @@ async function post(path, data) {
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(data), // Convert data to JSON string
body: JSON.stringify(data),
});
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
@@ -27,7 +92,7 @@ async function get(path) {
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
return await response.json(); // Assuming you are expecting JSON response
return await response.json();
} catch (error) {
console.error("Error during GET request:", error);
}
@@ -36,53 +101,77 @@ async function get(path) {
async function updateColor(event) {
event.preventDefault();
clearTimeout(colorTimeout);
colorTimeout = setTimeout(async function () {
colorTimeout = setTimeout(function () {
const color = document.getElementById("color").value;
await post("settings", { color1: color }); // Send as JSON
sendWebSocketData({ color1: color });
}, 500);
}
async function updateColor2(event) {
event.preventDefault();
clearTimeout(color2Timeout);
color2Timeout = setTimeout(async function () {
color2Timeout = setTimeout(function () {
const color = document.getElementById("color2").value;
await post("/settings", { color2: color }); // Send as JSON
sendWebSocketData({ color2: color });
}, 500);
}
async function updatePattern(pattern) {
await post("/settings", { pattern: pattern }); // Send as JSON
sendWebSocketData({ pattern: pattern });
}
async function updateBrightness(event) {
event.preventDefault();
clearTimeout(brightnessTimeout);
brightnessTimeout = setTimeout(async function () {
brightnessTimeout = setTimeout(function () {
const brightness = document.getElementById("brightness").value;
await post("/settings", { brightness: brightness }); // Send as JSON
sendWebSocketData({ brightness: brightness });
}, 500);
}
async function updateDelay(event) {
event.preventDefault();
clearTimeout(delayTimeout);
delayTimeout = setTimeout(async function () {
delayTimeout = setTimeout(function () {
const delay = document.getElementById("delay").value;
await post("/settings", { delay: delay }); // Send as JSON
sendWebSocketData({ delay: delay });
}, 500);
}
async function updateNumLeds(event) {
event.preventDefault();
const numLeds = document.getElementById("num_leds").value;
await post("/settings", { num_leds: parseInt(numLeds) }); // Send as JSON
sendWebSocketData({ num_leds: parseInt(numLeds) });
}
async function updateName(event) {
event.preventDefault();
const name = document.getElementById("name").value;
await post("/settings", { name: name }); // Send as JSON
sendWebSocketData({ name: name });
}
async function updateID(event) {
event.preventDefault();
const id = document.getElementById("id").value;
sendWebSocketData({ id: parseInt(id) });
}
async function updateLedPin(event) {
event.preventDefault();
const ledpin = document.getElementById("led_pin").value;
sendWebSocketData({ led_pin: parseInt(ledpin) });
}
function handleRadioChange(event) {
event.preventDefault();
console.log("Selected color order:", event.target.value);
// Add your specific logic here
if (event.target.value === "rgb") {
console.log("RGB order selected!");
} else if (event.target.value === "rbg") {
console.log("RBG order selected!");
}
sendWebSocketData({ color_order: event.target.value });
}
function createPatternButtons(patterns) {
@@ -91,7 +180,7 @@ function createPatternButtons(patterns) {
patterns.forEach((pattern) => {
const button = document.createElement("button");
button.type = "button"; // Use 'button' instead of 'submit'
button.type = "button";
button.textContent = pattern;
button.value = pattern;
button.addEventListener("click", async function (event) {
@@ -103,6 +192,12 @@ function createPatternButtons(patterns) {
}
document.addEventListener("DOMContentLoaded", async function () {
// Get the connection status element once the DOM is ready
connectionStatusElement = document.getElementById("connection-status");
// Establish WebSocket connection on page load
connectWebSocket();
document.getElementById("color").addEventListener("input", updateColor);
document.getElementById("color2").addEventListener("input", updateColor2);
document.getElementById("delay").addEventListener("input", updateDelay);
@@ -113,11 +208,17 @@ document.addEventListener("DOMContentLoaded", async function () {
.getElementById("num_leds_form")
.addEventListener("submit", updateNumLeds);
document.getElementById("name_form").addEventListener("submit", updateName);
document.getElementById("id_form").addEventListener("submit", updateID);
document
.getElementById("led_pin_form")
.addEventListener("submit", updateLedPin);
document.getElementById("delay").addEventListener("touchend", updateDelay);
document
.getElementById("brightness")
.addEventListener("touchend", updateBrightness);
document.getElementById("rgb").addEventListener("change", handleRadioChange);
document.getElementById("rbg").addEventListener("change", handleRadioChange);
document.querySelectorAll(".pattern_button").forEach((button) => {
console.log(button.value);
button.addEventListener("click", async (event) => {

View File

@@ -4,12 +4,12 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>LED Control</title>
<title>{{settings['name']}}</title>
<script src="static/main.js"></script>
<link rel="stylesheet" href="static/main.css" />
</head>
<body>
<h1>Control LEDs</h1>
<h1>{{settings['name']}}</h1>
<button onclick="selectControls()">Controls</button>
<button onclick="selectSettings()">Settings</button>
@@ -79,6 +79,16 @@
/>
<input type="submit" value="Update Name" />
</form>
<form id="id_form" method="post" action="/id">
<label for="id">ID:</label>
<input
type="text"
id="id"
name="id"
value="{{settings['id']}}"
/>
<input type="submit" value="Update ID" />
</form>
<!-- Separate form for submitting num_leds -->
<form id="num_leds_form" method="post" action="/num_leds">
<label for="num_leds">Number of LEDs:</label>
@@ -90,7 +100,25 @@
/>
<input type="submit" value="Update Number of LEDs" />
</form>
<form id="led_pin_form" method="post" action="/led_pin">
<label for="num_leds">Led pin:</label>
<input
type="text"
id="led_pin"
name="led_pin"
value="{{settings['led_pin']}}"
/>
<input type="submit" value="Update Led Pin" />
</form>
<form id="color_order_form">
<label for="rgb">RGB:</label>
<input type="radio" id="rgb" name="color_order" value="rgb" {{'checked' if settings["color_order"]=="rgb" else ''}} />
<label for="rbg">RBG</label>
<input type="radio" id="rbg" name="color_order" value="rbg" {{'checked' if settings["color_order"]=="rbg" else ''}}/>
</form>
<p>Mac address: {{mac}}</p>
</div>
<div id="connection-status"></div>
</body>
</html>

View File

@@ -1,94 +0,0 @@
# Autogenerated file
def render(settings, patterns):
yield """<!DOCTYPE html>
<html lang=\"en\">
<head>
<meta charset=\"UTF-8\">
<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">
<title>LED Control</title>
<script src=\"static/main.js\"></script>
<link rel=\"stylesheet\" href=\"static/main.css\">
</head>
<body>
<h1>Control LEDs</h1>
<button onclick=\"selectControls()\">Controls</button>
<button onclick=\"selectSettings()\">Settings</button>
<!-- Main LED Controls -->
<div id=\"controls\">
<div id=\"pattern_buttons\">
"""
for p in patterns:
yield """ <button class=\"pattern_button\" value=\""""
yield str(p)
yield """\">"""
yield str(p)
yield """</button>
"""
yield """
<!-- Pattern buttons will be inserted here -->
</div>
<form id=\"delay_form\" method=\"post\" action=\"/delay\">
<label for=\"delay\">Delay:</label>
<input type=\"range\" id=\"delay\" name=\"delay\" min=\"1\" max=\"1000\" value=\""""
yield str(settings['delay'])
yield """\" step=\"10\">
</form>
<form id=\"brightness_form\" method=\"post\" action=\"/brightness\">
<label for=\"brightness\">Brightness:</label>
<input type=\"range\" id=\"brightness\" name=\"brightness\" min=\"0\" max=\"100\" value=\""""
yield str(settings['brightness'])
yield """\" step=\"1\">
</form>
<form id=\"color_form\" method=\"post\" action=\"/color\">
<input type=\"color\" id=\"color\" name=\"color\" value=\""""
yield str(settings['color1'])
yield """\">
</form>
<form id=\"color2_form\" method=\"post\" action=\"/color2\">
<input type=\"color\" id=\"color2\" name=\"color2\" value=\""""
yield str(settings['color2'])
yield """\">
</form>
</div>
<!-- Settings Menu for num_leds, Wi-Fi SSID, and Password -->
<div id=\"settings_menu\" style=\"display: none;\">
<h2>Settings</h2>
<!-- Separate form for submitting num_leds -->
<form id=\"num_leds_form\" method=\"post\" action=\"/num_leds\">
<label for=\"num_leds\">Number of LEDs:</label>
<input type=\"text\" id=\"num_leds\" name=\"num_leds\" value=\""""
yield str(settings['num_leds'])
yield """\">
<input type=\"submit\" value=\"Update Number of LEDs\">
</form>
<!-- Form for Wi-Fi SSID and password -->
<form id=\"wifi_form\" method=\"post\" action=\"/wifi_settings\">
<label for=\"ssid\">Wi-Fi SSID:</label>
<input type=\"text\" id=\"ssid\" name=\"ssid\" value=\""""
yield str(settings['wifi']['ssid'])
yield """\">
<br>
<label for=\"password\">Wi-Fi Password:</label>
<input type=\"password\" id=\"password\" name=\"password\">
<br>
<label for=\"ip\">Wi-Fi IP:</label>
<input type=\"ip\" id=\"ip\" name=\"ip\" value=\""""
yield str(settings.get('wifi', {}).get('ip', ''))
yield """\">
<br>
<label for=\"gateway\">Wi-Fi Gateway:</label>
<input type=\"gateway\" id=\"gateway\" name=\"gateway\" value=\""""
yield str(settings.get('wifi', {}).get('gateway', ''))
yield """\">
<br>
<input type=\"submit\" value=\"Save Wi-Fi Settings\">
</form>
</div>
</body>
</html>
"""

View File

@@ -2,7 +2,6 @@ from microdot import Microdot, send_file, Response
from microdot.utemplate import Template
from microdot.websocket import with_websocket
import machine
from settings import set_settings
import wifi
import json
@@ -13,7 +12,7 @@ def web(settings, patterns):
@app.route('/')
async def index_hnadler(request):
mac = wifi.get_mac().hex()
return Template('/index.html').render(settings=settings, patterns=patterns.patterns.keys(), mac=mac)
return Template('index.html').render(settings=settings, patterns=patterns.patterns.keys())
@app.route("/static/<path:path>")
def static_handler(request, path):
@@ -24,18 +23,21 @@ def web(settings, patterns):
@app.post("/settings")
def settings_handler(request):
return set_settings(request.body.decode('utf-8'), settings, patterns)
# Keep the POST handler for compatibility or alternative usage if needed
# For WebSocket updates, the /ws handler is now primary
return settings.set_settings(request.body.decode('utf-8'), patterns)
@app.route("/external")
@app.route("/ws")
@with_websocket
async def ws(request, ws):
patterns.select("external")
while True:
data = await ws.receive()
print(data)
for i in range(min(patterns.num_leds, int(len(data)/3))):
patterns.set(i, (data[i*3], data[i*3+1], data[i*3+2]))
patterns.write()
if data:
# Process the received data
_, status_code = await settings.set_settings(json.loads(data), patterns, True)
#await ws.send(status_code)
else:
break
return app

View File

@@ -1,18 +1,16 @@
import network
from machine import Pin
from time import sleep
import ubinascii
from settings import Settings
def connect(ssid, password, ip, gateway):
if ssid is None or password is None:
print("Missing ssid or password")
return None
try:
sta_if = network.WLAN(network.STA_IF)
if ip is not None and gateway is not None:
sta_if.ifconfig((ip, '255.255.255.0', gateway, '1.1.1.1'))
if not sta_if.isconnected():
if ssid == "" or password == "":
print("Missing ssid or password")
return None
if ip != "" and gateway != "":
sta_if.ifconfig((ip, '255.255.255.0', gateway, '1.1.1.1'))
print('connecting to network...')
sta_if.active(True)
sta_if.connect(ssid, password)
@@ -26,21 +24,16 @@ def connect(ssid, password, ip, gateway):
return None
def ap(password):
def ap(ssid, password):
ap_if = network.WLAN(network.AP_IF)
ap_mac = ap_if.config('mac')
ssid = f"led-{ubinascii.hexlify(ap_mac).decode()}"
print(ssid)
ap_if.active(True)
ap_if.config(essid=ssid, password="qwerty1234")
ap_if.config(essid=ssid, password=password)
ap_if.active(False)
ap_if.active(True)
print(ap_if.ifconfig())
def get_mac():
ap_if = network.WLAN(network.AP_IF)
return ap_if.config('mac')

55
test/patterns.py Normal file
View File

@@ -0,0 +1,55 @@
#!/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())

32
test/patterns/blink.py Normal file
View File

@@ -0,0 +1,32 @@
#!/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())

138
test/patterns/n_chase.py Normal file
View File

@@ -0,0 +1,138 @@
#!/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())

26
test/patterns/off.py Normal file
View File

@@ -0,0 +1,26 @@
#!/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())

34
test/patterns/on.py Normal file
View File

@@ -0,0 +1,34 @@
#!/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())

160
test/patterns/pulse.py Normal file
View File

@@ -0,0 +1,160 @@
#!/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())

167
test/patterns/rainbow.py Normal file
View File

@@ -0,0 +1,167 @@
#!/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())

165
test/patterns/transition.py Normal file
View File

@@ -0,0 +1,165 @@
#!/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())

View File

@@ -0,0 +1,110 @@
#!/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())

111
test/test_save_load.py Normal file
View File

@@ -0,0 +1,111 @@
#!/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()