72 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
135f6b06f8 Ignore .venv 2025-05-07 19:46:28 +12:00
0aa3803f20 Add espnow 2025-04-21 21:52:20 +12:00
ed3351a20b Have single json endpoint 2025-04-21 21:51:55 +12:00
c8bcb85062 Add mac. Remove wifi settings 2025-04-21 21:51:15 +12:00
17d33d98e2 Send to a single json endpoint 2025-04-21 21:50:27 +12:00
afaf4c02e4 Change and and defaults
Add set_settings
2025-04-21 21:49:51 +12:00
e1e472b2e4 Remove wifi connect 2025-04-21 21:48:36 +12:00
2f6704a346 Add screenshots 2025-02-06 22:45:58 +13:00
60dddafc04 Add wifi ap connection info 2025-02-06 22:45:29 +13:00
db94530f29 Change to PORTNAME 2025-02-06 22:39:10 +13:00
28 changed files with 2429 additions and 973 deletions

4
.gitignore vendored
View File

@@ -1 +1,3 @@
settings.json
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

@@ -23,8 +23,16 @@ pipenv run esptool.py --port PORTNAME --baud 460800 write_flash 0 ESP32_GENERIC_
## Upload src, reset and follow
```pipenv run ./dev.py <PORT> src reset follow```
```pipenv run ./dev.py PORTNAME src reset follow```
## Connect
Connect to wifi nextwork called led-xxxxxx...
http://192.168.4.1/
![alt text](screenshots/controls.png)
![alt text](screenshots/settings.png)

BIN
screenshots/controls.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

BIN
screenshots/settings.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

View File

@@ -1,19 +1,9 @@
import settings
import wifi
import time
from settings import Settings
print(wifi.ap('qwerty'))
s = Settings()
settings = Settings()
ssid = settings.get('wifi', {}).get('ssid', None)
password = settings.get('wifi', {}).get('password', None)
ip = settings.get('wifi', {}).get('ip', None)
gateway = settings.get('wifi', {}).get('gateway', None)
for i in range(10):
config = wifi.connect(ssid, password, ip, gateway)
if config:
print(config)
break
time.sleep(0.1)
name = s.get('name', 'led')
password = s.get("ap_password", "")
wifi.ap(name, password)

View File

@@ -1,26 +1,30 @@
import asyncio
import aioespnow
from settings import Settings
from web import web
from patterns import Patterns
import gc
import utime
import machine
import ntptime
import time
import wifi
import json
from p2p import p2p
async def main():
settings = Settings()
patterns = Patterns(4, settings["num_leds"], selected=settings["selected_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)
@@ -30,26 +34,14 @@ async def main():
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()
asyncio.create_task(tick())
first = True
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
asyncio.run(main())

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 blink(self):
self.stopped = False
self.running = True
state = True # True = on, False = off
last_update = utime.ticks_ms()
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
while self.running:
current_time = utime.ticks_ms()
if utime.ticks_diff(current_time, last_update) >= self.delay:
if state:
self.fill(self.apply_brightness(self.colors[0]))
else:
self.fill((0, 0, 0))
state = not state
last_update = current_time
self.running = False
self.stopped = True
def set_delay(self, delay):
self.delay = delay
def set_brightness(self, brightness):
self.brightness = brightness
def rainbow(self):
self.stopped = False
self.running = True
step = self.step % 256
step_amount = max(1, int(self.n1)) # n1 controls step increment
def set_color1(self, color):
print(color)
self.color1 = self.apply_brightness(color)
# 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) + step
self.n[i] = self.apply_brightness(self.wheel(rc_index & 255))
self.n.write()
# Increment step by n1 for next call
self.step = (step + step_amount) % 256
self.running = False
self.stopped = True
return
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()
# Auto is True: run continuously
sleep_ms = max(1, int(self.delay))
last_update = utime.ticks_ms()
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)
current_time = utime.ticks_ms()
if utime.ticks_diff(current_time, self.last_update) >= self.delay:
if self.pattern_step < self.num_leds:
while self.running:
current_time = utime.ticks_ms()
if utime.ticks_diff(current_time, last_update) >= sleep_ms:
for i in range(self.num_leds):
self.n[i] = (0, 0, 0)
self.n[self.pattern_step] = self.apply_brightness(color)
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 += 1
else:
self.pattern_step = 0
self.last_update = current_time
step = (step + step_amount) % 256
self.step = step
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 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
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))
self.n.write()
self.pattern_step = (self.pattern_step + 1) % 256
self.last_update = current_time
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, last_update) >= 100:
self.fill(self.apply_brightness(self.colors[0]))
last_update = current_time
self.running = False
self.stopped = True
return
# 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
transition_duration = max(10, self.delay) # At least 10ms
update_interval = max(10, transition_duration // 50) # Update every ~2% of transition
# Transition from color1 to color2
color1 = self.colors[0]
color2 = self.colors[1]
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 color1 and color2
interpolated = tuple(
int(color1[i] + (color2[i] - color1[i]) * factor)
for i in range(3)
)
# 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 theater_chase_step(self):
current_time = utime.ticks_ms()
if utime.ticks_diff(current_time, self.last_update) >= self.delay:
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)
self.n.write()
self.pattern_step = (self.pattern_step + 1) % 3
self.last_update = current_time
def blink_step(self):
current_time = utime.ticks_ms()
if utime.ticks_diff(current_time, self.last_update) >= self.delay:
if self.pattern_step % 2 == 0:
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, last_update) >= transition_duration:
# Clear all LEDs
self.n.fill((0, 0, 0))
# Draw repeating pattern starting at position
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
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)
# Calculate position in the repeating segment
relative_pos = (i - position) % segment_length
if relative_pos < 0:
relative_pos = (relative_pos + segment_length) % segment_length
# 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 += 1
else:
self.pattern_step = 0
self.last_update = current_time
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)
# Move position by n3 or n4 on alternate steps
if step_count % 2 == 0:
position = position + n3
else:
pos -= 170
return (0, pos * 3, 255 - pos * 3)
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
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
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
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)
)
def two_steps_forward_one_step_back_step(self):
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
# Set all LEDs to off
for i in range(self.num_leds):
self.n[i] = (0, 0, 0)
# 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)
self.n.write()
self.pattern_step += self.delay
if self.pattern_step > self.transition_duration:
self.pattern_step = 0
self.last_update = current_time
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))
position = position + n4
# 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

@@ -1,4 +1,7 @@
import json
import wifi
import ubinascii
import machine
class Settings(dict):
SETTINGS_FILE = "/settings.json"
@@ -6,15 +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["selected_pattern"] = "blink"
self["color1"] = "#000f00"
self["color2"] = "#0f0000"
self["pattern"] = "on"
self["color1"] = "#00ff00"
self["color2"] = "#ff0000"
self["delay"] = 100
self["brightness"] = 100
self["wifi"] = {"ssid": "", "password": ""}
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:
@@ -34,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():
@@ -42,12 +109,14 @@ def main():
settings['num_leds'] = 100
print(f"Updated number of LEDs: {settings['num_leds']}")
settings.save()
# Create a new Settings object to test loading
new_settings = Settings()
print(f"Loaded number of LEDs: {new_settings['num_leds']}")
print(settings)
# Run the example
if __name__ == "__main__":
main()
main()

View File

@@ -1,75 +1,109 @@
body {
font-family: Arial, sans-serif;
max-width: 600px;
margin: 0 auto;
padding: 20px;
line-height: 1.6;
}
h1 {
text-align: center;
}
form {
margin-bottom: 20px;
}
label {
display: block;
margin-bottom: 5px;
}
input[type="text"], input[type="submit"], input[type="range"], input[type="color"] {
width: 100%;
margin-bottom: 10px;
box-sizing: border-box;
}
input[type="range"] {
-webkit-appearance: none;
appearance: none;
height: 25px;
background: #d3d3d3;
outline: none;
opacity: 0.7;
transition: opacity .2s;
}
input[type="range"]:hover {
opacity: 1;
}
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 25px;
height: 25px;
background: #4CAF50;
cursor: pointer;
border-radius: 50%;
}
input[type="range"]::-moz-range-thumb {
width: 25px;
height: 25px;
background: #4CAF50;
cursor: pointer;
border-radius: 50%;
}
#pattern_buttons {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-bottom: 20px;
}
#pattern_buttons button {
flex: 1 0 calc(33.333% - 10px);
padding: 10px;
background-color: #4CAF50;
color: white;
border: none;
cursor: pointer;
transition: background-color 0.3s;
}
#pattern_buttons button:hover {
background-color: #45a049;
}
@media (max-width: 480px) {
#pattern_buttons button {
flex: 1 0 calc(50% - 10px);
}
}
font-family: Arial, sans-serif;
max-width: 600px;
margin: 0 auto;
padding: 20px;
line-height: 1.6;
}
h1 {
text-align: center;
}
form {
margin-bottom: 20px;
}
label {
display: block;
margin-bottom: 5px;
}
input[type="text"],
input[type="submit"],
input[type="range"],
input[type="color"] {
width: 100%;
margin-bottom: 10px;
box-sizing: border-box;
}
input[type="range"] {
-webkit-appearance: none;
appearance: none;
height: 25px;
background: #d3d3d3;
outline: none;
opacity: 0.7;
transition: opacity 0.2s;
}
input[type="range"]:hover {
opacity: 1;
}
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 25px;
height: 25px;
background: #4caf50;
cursor: pointer;
border-radius: 50%;
}
input[type="range"]::-moz-range-thumb {
width: 25px;
height: 25px;
background: #4caf50;
cursor: pointer;
border-radius: 50%;
}
#pattern_buttons {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-bottom: 20px;
}
#pattern_buttons button {
flex: 1 0 calc(33.333% - 10px);
padding: 10px;
background-color: #4caf50;
color: white;
border: none;
cursor: pointer;
transition: background-color 0.3s;
}
#pattern_buttons button:hover {
background-color: #45a049;
}
@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,146 +2,243 @@ 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
async function post(path, data) {
console.log(`POST to ${path}`, data);
try {
const response = await fetch(path, {
method: "POST",
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data) // Convert data to JSON string
});
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
} catch (error) {
console.error('Error during POST request:', error);
// 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 {
const response = await fetch(path, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(data),
});
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
} catch (error) {
console.error("Error during POST request:", error);
}
}
async function get(path) {
try {
const response = await fetch(path);
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
return await response.json(); // Assuming you are expecting JSON response
} catch (error) {
console.error('Error during GET request:', error);
try {
const response = await fetch(path);
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
return await response.json();
} catch (error) {
console.error("Error during GET request:", error);
}
}
async function updateColor(event) {
event.preventDefault();
clearTimeout(colorTimeout);
colorTimeout = setTimeout(async function() {
const color = document.getElementById('color').value;
await post("/color", { color }); // Send as JSON
}, 500);
event.preventDefault();
clearTimeout(colorTimeout);
colorTimeout = setTimeout(function () {
const color = document.getElementById("color").value;
sendWebSocketData({ color1: color });
}, 500);
}
async function updateColor2(event) {
event.preventDefault();
clearTimeout(color2Timeout);
color2Timeout = setTimeout(async function() {
const color = document.getElementById('color2').value;
await post("/color2", { color }); // Send as JSON
}, 500);
event.preventDefault();
clearTimeout(color2Timeout);
color2Timeout = setTimeout(function () {
const color = document.getElementById("color2").value;
sendWebSocketData({ color2: color });
}, 500);
}
async function updatePattern(pattern) {
event.preventDefault();
await post("/pattern", { pattern }); // Send as JSON
sendWebSocketData({ pattern: pattern });
}
async function updateBrightness(event) {
event.preventDefault();
clearTimeout(brightnessTimeout);
brightnessTimeout = setTimeout(async function() {
const brightness = document.getElementById('brightness').value;
await post('/brightness', { brightness }); // Send as JSON
}, 500);
event.preventDefault();
clearTimeout(brightnessTimeout);
brightnessTimeout = setTimeout(function () {
const brightness = document.getElementById("brightness").value;
sendWebSocketData({ brightness: brightness });
}, 500);
}
async function updateDelay(event) {
event.preventDefault();
clearTimeout(delayTimeout);
delayTimeout = setTimeout(async function() {
const delay = document.getElementById('delay').value;
await post('/delay', { delay }); // Send as JSON
}, 500);
event.preventDefault();
clearTimeout(delayTimeout);
delayTimeout = setTimeout(function () {
const delay = document.getElementById("delay").value;
sendWebSocketData({ delay: delay });
}, 500);
}
async function updateNumLeds(event) {
event.preventDefault();
const numLeds = document.getElementById('num_leds').value;
await post('/num_leds', { num_leds: numLeds }); // Send as JSON
event.preventDefault();
const numLeds = document.getElementById("num_leds").value;
sendWebSocketData({ num_leds: parseInt(numLeds) });
}
async function updateWifi(event) {
event.preventDefault();
const ssid = document.getElementById('ssid').value;
const password = document.getElementById('password').value;
const ip = document.getElementById('ip').value;
const gateway = document.getElementById('gateway').value;
async function updateName(event) {
event.preventDefault();
const name = document.getElementById("name").value;
sendWebSocketData({ name: name });
}
const wifiSettings = { ssid, password, ip, gateway }; // Create JSON object
console.log(wifiSettings);
const response = await post('/wifi_settings', wifiSettings); // Send as JSON
if (response === 500) {
alert("Failed to connect to Wi-Fi");
}
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) {
const container = document.getElementById('pattern_buttons');
container.innerHTML = ''; // Clear previous buttons
const container = document.getElementById("pattern_buttons");
container.innerHTML = ""; // Clear previous buttons
patterns.forEach(pattern => {
const button = document.createElement('button');
button.type = 'button'; // Use 'button' instead of 'submit'
button.textContent = pattern;
button.value = pattern;
button.addEventListener('click', async function(event) {
event.preventDefault();
await updatePattern(pattern);
});
container.appendChild(button);
patterns.forEach((pattern) => {
const button = document.createElement("button");
button.type = "button";
button.textContent = pattern;
button.value = pattern;
button.addEventListener("click", async function (event) {
event.preventDefault();
await updatePattern(pattern);
});
container.appendChild(button);
});
}
document.addEventListener('DOMContentLoaded', async function() {
document.getElementById('color').addEventListener('input', updateColor);
document.getElementById('color2').addEventListener('input', updateColor2);
document.getElementById('delay').addEventListener('input', updateDelay);
document.getElementById('brightness').addEventListener('input', updateBrightness);
document.getElementById('num_leds_form').addEventListener('submit', updateNumLeds);
document.getElementById('wifi_form').addEventListener('submit', updateWifi);
document.getElementById('delay').addEventListener('touchend', updateDelay);
document.getElementById('brightness').addEventListener('touchend', updateBrightness);
document.addEventListener("DOMContentLoaded", async function () {
// Get the connection status element once the DOM is ready
connectionStatusElement = document.getElementById("connection-status");
document.querySelectorAll(".pattern_button").forEach(button => {
console.log(button.value);
button.addEventListener('click', async event => {
event.preventDefault();
await updatePattern(button.value);
});
});
// 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);
document
.getElementById("brightness")
.addEventListener("input", updateBrightness);
document
.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) => {
event.preventDefault();
await updatePattern(button.value);
});
});
});
// Function to toggle the display of the settings menu
function selectSettings() {
const settingsMenu = document.getElementById('settings_menu');
controls = document.getElementById('controls');
settingsMenu.style.display = 'block';
controls.style.display = 'none';
const settingsMenu = document.getElementById("settings_menu");
controls = document.getElementById("controls");
settingsMenu.style.display = "block";
controls.style.display = "none";
}
function selectControls() {
const settingsMenu = document.getElementById('settings_menu');
controls = document.getElementById('controls');
settingsMenu.style.display = 'none';
controls.style.display = 'block';
const settingsMenu = document.getElementById("settings_menu");
controls = document.getElementById("controls");
settingsMenu.style.display = "none";
controls.style.display = "block";
}

View File

@@ -1,71 +1,124 @@
{% args settings, patterns %}
<!DOCTYPE html>
{% args settings, patterns, mac %}
<!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>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>{{settings['name']}}</title>
<script src="static/main.js"></script>
<link rel="stylesheet" href="static/main.css" />
</head>
<body>
<h1>{{settings['name']}}</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 %}
<button class="pattern_button" value="{{p}}">{{p}}</button>
{% endfor %}
<!-- Pattern buttons will be inserted here -->
<!-- Main LED Controls -->
<div id="controls">
<div id="pattern_buttons">
{% for p in patterns %}
<button class="pattern_button" value="{{p}}">{{p}}</button>
{% endfor %}
<!-- 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="{{settings['delay']}}"
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="{{settings['brightness']}}"
step="1"
/>
</form>
<form id="color_form" method="post" action="/color">
<input
type="color"
id="color"
name="color"
value="{{settings['color1']}}"
/>
</form>
<form id="color2_form" method="post" action="/color2">
<input
type="color"
id="color2"
name="color2"
value="{{settings['color2']}}"
/>
</form>
</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="{{settings['delay']}}" 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="{{settings['brightness']}}" step="1">
</form>
<form id="color_form" method="post" action="/color">
<input type="color" id="color" name="color" value="{{settings['color1']}}">
</form>
<form id="color2_form" method="post" action="/color2">
<input type="color" id="color2" name="color2" value="{{settings['color2']}}">
</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="{{settings['num_leds']}}">
<input type="submit" value="Update Number of LEDs">
</form>
<!-- Settings Menu for num_leds, Wi-Fi SSID, and Password -->
<!-- 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="{{settings['wifi']['ssid']}}">
<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="{{settings.get('wifi', {}).get('ip', '')}}">
<br>
<label for="gateway">Wi-Fi Gateway:</label>
<input type="gateway" id="gateway" name="gateway" value="{{settings.get('wifi', {}).get('gateway', '')}}">
<br>
<input type="submit" value="Save Wi-Fi Settings">
</form>
</div>
</body>
<div id="settings_menu" style="display: none">
<h2>Settings</h2>
<form id="name_form" method="post" action="/name">
<label for="name">Name:</label>
<input
type="text"
id="name"
name="num_leds"
value="{{settings['name']}}"
/>
<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>
<input
type="text"
id="num_leds"
name="num_leds"
value="{{settings['num_leds']}}"
/>
<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

@@ -1,135 +1,43 @@
from microdot import Microdot, send_file, Response
from microdot.utemplate import Template
from microdot.websocket import with_websocket
import json
import machine
import wifi
import json
def web(settings, patterns):
app = Microdot()
Response.default_content_type = 'text/html'
@app.route('/')
async def index(request):
return Template('/index.html').render(settings=settings, patterns=patterns.patterns.keys())
async def index_hnadler(request):
mac = wifi.get_mac().hex()
return Template('index.html').render(settings=settings, patterns=patterns.patterns.keys())
@app.route("/static/<path:path>")
def static(request, path):
def static_handler(request, path):
if '..' in path:
# Directory traversal is not allowed
return 'Not found', 404
return send_file('static/' + path)
@app.post("/num_leds")
def num_leds(request):
try:
data = json.loads(request.body.decode('utf-8'))
num_leds = int(data["num_leds"])
patterns.update_num_leds(4, num_leds)
settings["num_leds"] = num_leds
settings.save()
return "OK", 200
except (ValueError, KeyError, json.JSONDecodeError):
return "Bad request", 400
@app.post("/settings")
def settings_handler(request):
# 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.post("/pattern")
def pattern(request):
try:
data = json.loads(request.body.decode('utf-8'))
pattern = data["pattern"]
if patterns.select(pattern):
settings["selected_pattern"] = pattern
settings.save()
return "OK", 200
else:
return "Bad request", 400
except (KeyError, json.JSONDecodeError):
return "Bad request", 400
@app.post("/delay")
def delay(request):
try:
data = json.loads(request.body.decode('utf-8'))
delay = int(data["delay"])
patterns.set_delay(delay)
settings["delay"] = delay
settings.save()
return "OK", 200
except (ValueError, KeyError, json.JSONDecodeError):
return "Bad request", 400
@app.post("/brightness")
def brightness(request):
try:
data = json.loads(request.body.decode('utf-8'))
brightness = int(data["brightness"])
patterns.set_brightness(brightness)
settings["brightness"] = brightness
settings.save()
return "OK", 200
except (ValueError, KeyError, json.JSONDecodeError):
return "Bad request", 400
@app.post("/color")
def color(request):
try:
data = json.loads(request.body.decode('utf-8'))
color = data["color"]
patterns.set_color1(tuple(int(color[i:i+2], 16) for i in (1, 3, 5))) # Convert hex to RGB
settings["color1"] = color
settings.save()
return "OK", 200
except (KeyError, json.JSONDecodeError, ValueError):
return "Bad request", 400
@app.post("/color2")
def color2(request):
try:
data = json.loads(request.body.decode('utf-8'))
color = data["color2"]
patterns.set_color2(tuple(int(color[i:i+2], 16) for i in (1, 3, 5))) # Convert hex to RGB
settings["color2"] = color
settings.save()
return "OK", 200
except (KeyError, json.JSONDecodeError, ValueError):
return "Bad request", 400
@app.post("/wifi_settings")
def wifi_settings(request):
try:
data = json.loads(request.body.decode('utf-8'))
print(data)
ssid = settings['wifi']['ssid'] = data['ssid']
password = settings['wifi']['password'] = data.get('password', settings['wifi']['password'])
ip = settings['wifi']['ip'] = data.get('ip', None)
gateway = settings['wifi']['gateway'] = data.get('gateway', None)
print(settings)
if config := wifi.connect(ssid, password, ip, gateway):
print(config)
settings.save()
return "OK", 200
except Exception as e:
print(f"Wifi {e}")
return "Bad request", 400
@app.post("/sync")
def sync(request):
patterns.sync()
return "OK", 200
@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()