1 Commits

Author SHA1 Message Date
e83f0d607c Switch to async patterns 2025-08-28 22:55:10 +12:00
25 changed files with 423 additions and 2633 deletions

1
.gitignore vendored
View File

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

View File

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

571
Pipfile.lock generated
View File

@@ -1,7 +1,7 @@
{ {
"_meta": { "_meta": {
"hash": { "hash": {
"sha256": "7b8033c15743e27f2589635c75bd0bb86ffc3a725b179d7db9ef200119aa9164" "sha256": "8b14bb293b7e7117ffc89c2bc92d7aa2290e8f68be7fc0f073f2b3f7f959ef71"
}, },
"pipfile-spec": 6, "pipfile-spec": 6,
"requires": { "requires": {
@@ -16,152 +16,152 @@
] ]
}, },
"default": { "default": {
"anyio": { "argcomplete": {
"hashes": [ "hashes": [
"sha256:3f3fae35c96039744587aa5b8371e7e8e603c0702999535961dd336026973ba6", "sha256:65b3133a29ad53fb42c48cf5114752c7ab66c1c38544fdf6460f450c09b42591",
"sha256:60e474ac86736bbfd6f210f7a61218939c318f43f9972497381f1c5e930ed3d1" "sha256:d0519b1bc867f5f4f4713c41ad0aba73a4a5f007449716b16f385f2166dc6adf"
], ],
"markers": "python_version >= '3.9'", "markers": "sys_platform != 'win32'",
"version": "==4.10.0" "version": "==3.6.2"
}, },
"bitarray": { "bitarray": {
"hashes": [ "hashes": [
"sha256:00628196dd3592972a5183194ab1475dadf9ef2a4cf3fd8c7c184a94934012e8", "sha256:01299fb36af3e7955967f3dbc4097a2d88845166837899350f411d95a857f8aa",
"sha256:01d6dc548e7fe5c66913c2274f44855b0f8474935acff7811e84fe1f4024c94f", "sha256:0580b905ad589e3be52d36fbc83d32f6e3f6a63751d6c0da0ca328c32d037790",
"sha256:056fe779f01a867d572e071c0944ac2f3bf58d8bced326040f0bd060af33a209", "sha256:0952d05e1d6b0a736d73d34128b652d7549ba7d00ccc1e7c00efbc6edd687ee3",
"sha256:080a7bf55c432abdae74f25dc3dbff407418346aeae1d43e31f65e8ef114f785", "sha256:0b7e1f4139d3f17feba72e386a8f1318fb35182ff65890281e727fd07fdfbd72",
"sha256:0956322bf4d5e2293e57600aa929c241edf1e209e94e12483bf58c5c691432db", "sha256:0ba347a4dcc990637aa700227675d8033f68b417dcd7ccf660bd2e87e10885ec",
"sha256:0a6f9e897907757e9c2d722ae6c203d48a04826a14e1495e33935c8583c163a9", "sha256:0d11e1a8914321fac34f50c48a9b1f92a1f51f45f9beb23e990806588137c4ca",
"sha256:0ac446f557eb28e3f7c65372608810ff073840627e9037e22ed10bd081793a34", "sha256:131ff1eed8902fb54ea64f8d0bf8fcbbda8ad6b9639d81cacc3a398c7488fecb",
"sha256:0b47843f2f288fa746dead4394591a3432a358aaad48240283fa230d6e74b0e7", "sha256:14f04e4eec65891523a8ca3bf9e1dcdefed52d695f40c4e50d5980471ffd22a4",
"sha256:11fc8bc65f964c7278deb1b7a69379dab3ecc90095f252deb17365637ebb274d", "sha256:176991b2769425341da4d52a684795498c0cd4136f4329ba9d524bcb96d26604",
"sha256:129165b68a3e0c2a633ed0d8557cf5ade24a0b37ca97d7805fa6fc5fb73c19d5", "sha256:1b7e89d4005eee831dc90d50c69af74ece6088f3c1b673d0089c8ef7d5346c37",
"sha256:139963494fc3dd5caee5e38c0a03783ef50be118565e94b1dbb0210770f0b32d", "sha256:1ce3e352f1b7f1201b04600f93035312b00c9f8f4d606048c39adac32b2fb738",
"sha256:157313a124287cbc8a11b55a75def0dd59e68badbc82c2dc2d204dc852742874", "sha256:24296caffe89af65fc8029a56274db6a268f6a297a5163e65df8177c2dd67b19",
"sha256:16d0edab54bb9d214319418f65bd15cfc4210ec41a16c3dd0b71e626c803212d", "sha256:2441da551787086c57fa8983d43e103fd2519389c8e03302908697138c287d6a",
"sha256:1971050b447023288a2b694a03b400bd5163829cd67b10f19e757fe87cd1161e", "sha256:26a26614bba95f3e4ea8c285206a4efe5ffb99e8539356d78a62491facc326cf",
"sha256:1c4e75bbf9ade3d2cdf1b607a8b353b17d9b3cf54e88b2a5a773f50ae6f1bfbc", "sha256:28d866fa462d77cafbf284aea14102a31dcfdebb9a5abbfb453f6eb6b2deb4fd",
"sha256:1c9f36055a89b9517db66eb8e80137126bf629c767ceeade4d004e40bc8bcd99", "sha256:2e92d2d7d405e004f2bdf9ff6d58faed6d04e0b74a9d96905ade61c293abe315",
"sha256:2020102a40edd094c0aa80e09203af71c533c41f76ce3237c99fd194a473ea33", "sha256:2fbd399cfdb7dee0bb4705bc8cd51163a9b2f25bb266807d57e5c693e0a14df2",
"sha256:20febc849a1f858e6a57a7d47b323fe9e727c579ddd526d317ad8831748a66a8", "sha256:307e4cd6b94de4b4b5b0f4599ffddabde4c33ac22a74998887048d24cb379ad3",
"sha256:220d4b8649ef54ac98e5e0e3dd92230247f67270d1524a8b31aa9859007affb0", "sha256:31f21c7df3b40db541182db500f96cf2b9688261baec7b03a6010fdfc5e31855",
"sha256:22188943a29072b684cd7c99e0b2cfc0af317cea3366c583d820507e6d1f2ed4", "sha256:36851e3244950adc75670354dcd9bcad65e1695933c18762bb6f7590734c14ef",
"sha256:222cb27ff05bc0aec72498d075dba1facec49a76a7da45740690cebbe3e81e43", "sha256:39fdd56fd9076a4a34c3cd21e1c84dc861dac5e92c1ed9daed6aed6b11719c8c",
"sha256:243825f56b58bef28bfc602992a8c6d09bbc625628c195498d6020120d632a09", "sha256:3afe39028afff6e94bb90eb0f8c5eb9357c0e37ce3c249f96dbcfc1a73938015",
"sha256:25060e7162e44242a449ed1a14a4e94b5aef340812754c443459f19c7954be91", "sha256:3fcdaf79970b41cfe21b6cf6a7bbe2d0f17e3371a4d839f1279283ac03dd2a47",
"sha256:26691454a6770628882b68fe74e9f84ca2a51512edd49cbb025b14df5a9dd85a", "sha256:421da43706c9a01d1b1454c34edbff372a7cfeff33879b6c048fc5f4481a9454",
"sha256:27d13c7b886afc5d2fc49d6e92f9c96b1f0a14dc7b5502520c29f3da7550d401", "sha256:42376c9e0a1357acc8830c4c0267e1c30ebd04b2d822af702044962a9f30b795",
"sha256:27eeee915258b105a21a4b0f8aebc5f77bb4dc4fb4063a09dd329fa1fdcbd234", "sha256:434180c1340268763439b80d21e074df24633c8748a867573bafecdbfaa68a76",
"sha256:29ed022189a7997de46cb9bd4e2e49d6163d4f8d78dea72ac5a0e0293b856810", "sha256:434e389958ab98415ed4d9d67dd94c0ac835036a16b488df6736222f4f55ff35",
"sha256:2a324e3007afb5c667026f5235b35efe3c4a95f1b83cd93aa9fce67b42f08e7c", "sha256:47abbec73f20176e119f5c4c68aaf243c46a5e072b9c182f2c110b5b227256a7",
"sha256:2c533c828d0007fac27cf45e5c1a711e5914dd469db5fe6be5f4e606bf2d7f63", "sha256:492524a28c3aab6a4ef0a741ee9f3578b6606bb52a7a94106c386bdebab1df44",
"sha256:30ba4fba3de1dca653de41c879349ec6ca521d85cff6a7ca5d2fdd8f76c93781", "sha256:4bb2fa914a7bbcd7c6a457d44461a8540b9450e9bb4163d734eb74bffba90e69",
"sha256:357e07c827bad01f98d0bd0dfdc722f483febeed39140fd75ffd016a451b60b9", "sha256:4bda4e4219c6271beec737a5361b009dcf9ff6d84a2df92bf3dd4f4e97bb87e5",
"sha256:3800f3c8c9780f281cf590543fd4b3278fea6988202273a260ecc58136895efb", "sha256:4c516daf790bd870d7575ac0e4136f1c3bc180b0de2a6bfa9fa112ea668131a0",
"sha256:3d6f3a94abd8b44b2bf346ca81ab2ff41ab9146c53905eedf5178b19d9fe53bf", "sha256:4ddef0b620db43dfde43fe17448ddc37289f67ad9a8ae39ffa64fa7bf529145f",
"sha256:3eb1390a8b062fe9125e5cc4c5eba990b5d383eec54f2b996e7ce73ac43150f9", "sha256:50da5ecd86ee25df9f658d8724efbe8060de97217fb12a1163bee61d42946d83",
"sha256:407920e9318d94cc6c9611aaa5b5e5963a09f1cbfa17b16b66edea453b3754f4", "sha256:50df8e1915a1acfd9cd0a4657d26cacd5aee4c3286ebb63e9dd75271ea6b54e0",
"sha256:42622c42c159ea4535bba7e1e3c97f1fec79505bc6873ae657dc0a8f861c60de", "sha256:518e04584654a155fca829a6fe847cd403a17007e5afdc2b05b4240b53cd0842",
"sha256:4695fcd37478988b1d0a16d5bc0df56dcb677fd5db37f1893d993fd3ebef914b", "sha256:51ce410a2d91da4b98d0f043df9e0938c33a2d9ad4a370fa8ec1ce7352fc20d9",
"sha256:4798f6744fa2633666e17b4ea8ff70250781b52a25afdbf5ffb5e176c58848f1", "sha256:52e8d36933bb3fb132c95c43171f47f07c22dd31536495be20f86ddbf383e3c6",
"sha256:4a5b0d277087a5bf261a607fc6ff4aaffcf80b300cd19b7a1e9754a4649f5fd4", "sha256:52edf707f2fddb6a60a20093c3051c1925830d8c4e7fb2692aac2ee970cee2b0",
"sha256:4e297fd2e58afe17e33dd80c231c3a9d850279a2a8625aed1d39f9be9534809e", "sha256:535cc398610ff22dc0341e8833c34be73634a9a0a5d04912b4044e91dfbbc413",
"sha256:507e567aee4806576e20752f22533e8b7ec61e7e75062a7ce9222a0675aa0da6", "sha256:54093229fec0f8c605b7873020c07681c1f1f96c433ae082d2da106ab11b206f",
"sha256:50d702149747852923be60cae125285eca8d189d4c7d8832c0c958d4071a0f78", "sha256:54ac6f8d2f696d83f9ccbb4cc4ce321dc80b9fa4613749a8ab23bda5674510ea",
"sha256:51947a00ae9924584fb14c0c1b1f4c1fd916d9abd6f47582f318ab9c9cb9f3d0", "sha256:5500052aaf761afede3763434097a59042e22fbde508c88238d34105c13564c0",
"sha256:52328192d454ca2ddad09fbc088872b014c74b22ecdd5164717dc7e6442014fa", "sha256:551844744d22fe2e37525bd7132d2e9dae5a9621e3d8a43f46bbe6edadb4c63b",
"sha256:531e6dfec8058fcf5d69e863b61e6b28e3749b615a4dcc0ab8ad36307c4017fc", "sha256:58365c6c3e4a5ebbc8f28bf7764f5b00be5c8b1ffbd70474e6f801383f3fe0a0",
"sha256:54bd71f14a5fa9bae73ef92f2e2be894dc36c7a6d1c4962e5969bd8a9aa39325", "sha256:5aacbf54ad69248e17aab92a9f2d8a0a7efaea9d5401207cb9dac41d46294d56",
"sha256:552a93be286ca485914777461b384761519db313e0a7f3012dca424c9610a4d5", "sha256:5d47d349468177afbe77e5306e70fd131d8da6946dd22ed93cbe70c5f2965307",
"sha256:583b46b3ba44121de5e87e95ae379932dc5fd2e37ebdf2c11a6d7975891425c1", "sha256:5dcb5aaaa2d91cc04fa9adfe31222ab150e72d99c779b1ddca10400a2fd319ec",
"sha256:5b58a672ec448fb36839a5fc7bf2b2f60df9a97b872d8bd6ca1a28da6126f5c7", "sha256:601fedd0e5227a5591e2eae2d35d45a07f030783fc41fd217cdf0c74db554cb9",
"sha256:5cfbdccddaa0ff07789e9e180db127906c676e479e05c04830cd458945de3511", "sha256:62c2278763edc823e79b8f0a0fdc7c8c9c45a3e982db9355042839c1f0c4ea92",
"sha256:5da4939e241301f5e1d18118695e8d2c300be90431b66bd43a00376acec45e1e", "sha256:638ad50ecbffd05efdfa9f77b24b497b8e523f078315846614c647ebc3389bb5",
"sha256:5dd9edcab8979a50c2c4dec6d5b66789fb6f630bb52ab90a4548111075a75e48", "sha256:64a5404a258ef903db67d7911147febf112858ba30c180dae0c23405412e0a2f",
"sha256:5e304f94c0353f6ae5711533b5793b3a45b17aa2c5b07e656649b0af4e0939b5", "sha256:64cef9f2d15261ea667838a4460f75acf4b03d64d53df664357541cc8d2c8183",
"sha256:60408ec9c0bd76f1fa00d28034429a0316246d31069b982a86aec8d5c99e910a", "sha256:653d56c58197940f0c1305cb474b75597421b424be99284915bb4f3529d51837",
"sha256:62f71b268f14ee6cc3045b95441bfe0518cef1d0b2ffbc6f3e9758f786ff5a03", "sha256:673a21ebb6c72904d7de58fe8c557bad614fce773f21ddc86bcf8dd09a387a32",
"sha256:664d462a4c0783fd755fe3440f07b7e46d149859c96caacadf3f28890f19a8de", "sha256:69679fcd5f2c4b7c8920d2824519e3bff81a18fac25acf33ded4524ea68d8a39",
"sha256:66d8b7a89fac6042f7df9ea97d97ed0f5e404281110a882e3babd909161f85b6", "sha256:71838052ad546da110b8a8aaa254bda2e162e65af563d92b15c8bc7ab1642909",
"sha256:6755cfcfa7d8966e704d580c831e39818f85e7b2b7852ad22708973176f0009e", "sha256:7445c34e5d55ec512447efa746f046ecf4627c08281fc6e9ef844423167237bc",
"sha256:679856547f0b27b98811b73756bdf53769c23b045a6f95177cae634daabf1ddf", "sha256:751a2cd05326e1552b56090595ba8d35fe6fef666d5ca9c0a26d329c65a9c4a0",
"sha256:6841c08b51417f8ffe398b2828fc0593440c99525c868f640e0302476745320b", "sha256:75eb4d353dcf571d98e2818119af303fb0181b54361ac9a3e418b31c08131e56",
"sha256:68f6e64d4867ee79e25c49d7f35b2b1f04a6d6f778176dcf5b759f3b17a02b2b", "sha256:76abaeac4f94eda1755eed633a720c1f5f90048cb7ea4ab217ea84c48414189a",
"sha256:69d2d507c1174330c71c834b5d65e66181ad7b42b0d88b5b31804ee9b4f5dae7", "sha256:78d069a00a8d06fb68248edd5bf2aa5e8009f4f5eae8dd5b5a529812132ad8a6",
"sha256:6f0be27d06732e2833b672a8fcc32fa195bdb22161eb88f8890de15e30264a01", "sha256:7964b17923c1bfa519afe273335023e0800c64bdca854008e75f2b148614d3f2",
"sha256:6f7d2dbe628f3db935622a5b80a5c4d95665cdefc4904372aa3c4d786289477f", "sha256:7c7913d3cf7017bd693177ca0a4262d51587378d9c4ae38d13be3655386f0c27",
"sha256:72760411d60d8d76979a20ed3f15586d824db04668b581b86e61158c2b616db0", "sha256:8076650a08cec080f6726860c769320c27eb4379cfd22e2f5732787dec119bfe",
"sha256:727f7a969416f02ef5c1256541e06f0836fb615022699fa8e2591e85296c5570", "sha256:811f559e0e5fca85d26b834e02f2a767aa7765e6b1529d4b2f9d4e9015885b4b",
"sha256:77d2368a06a86a18919c05a9b4b0ee9869f770e6a5f414b0fecc911870fe3974", "sha256:824bd92e53f8e32dfa4bf38643246d1a500b13461ade361d342a8fcc3ddb6905",
"sha256:79ab1c5f26f23e51d4a44c4397c8a3bf56c306c125dfab6b3eebdfa13d1dca6f", "sha256:833e06b01ff8f5a9f5b52156a23e9930402d964c96130f6d0bd5297e5dec95dc",
"sha256:79db23eda81627132327ed292bd813a9af64399b98aaac3d42ad8deeed24cd5e", "sha256:8360759897d50e4f7ec8be51f788119bd43a61b1fe9c68a508a7ba495144859a",
"sha256:7c20d6e6cafce5027e7092beb2ac6eec0d71045d6318b34f36e1387a8c8859a3", "sha256:8627fc0c9806d6dac2fb422d9cd650b0d225f498601381d334685b9f071b793c",
"sha256:7e0851a985a7b10f634188117c825ef99d63402555cca5bc32c7bfc5adaf0d6f", "sha256:86dd5b8031d690afc90430997187a4fc5871bc6b81d73055354b8eb48b3e6342",
"sha256:7e2e1ff784c2cdfd863bad31985851427f2d2796e445cec85080c7510cba4315", "sha256:8c84c3df9b921439189d0be6ad4f4212085155813475a58fbc5fb3f1d5e8a001",
"sha256:7f8b12424f8fdf29d1c0749c628bd1530cecfc77374935d096cccc0e4eada232", "sha256:8c89219a672d0a15ab70f8a6f41bc8355296ec26becef89a127c1a32bb2e6345",
"sha256:81e84054b22babcd6c5cc1eac0de2bfc1054ecdf742720cbfb36efbe89ec6c30", "sha256:8f267edd51db6903c67b2a2b0f780bb0e52d2e92ec569ddd241486eeff347283",
"sha256:84bb57010a1ab76cf880424a2e0bce8dd26989849d2122ff073aa11bfc271c27", "sha256:90178b8c6f75b43612dadf50ff0df08a560e220424ce33cf6d2514d7ab1803a7",
"sha256:870ed23361e2918ab1ffc23fe0ab293abf3c372a68ee7387456d13da3e213008", "sha256:90b35553c318b49d5ffdaf3d25b6f0117fd5bbfc3be5576fc41ca506ca0e9b8e",
"sha256:8cf44b012e7493127ce7ca6e469138ac96b3295a117877d5469aabe7c8728d87", "sha256:9101d48f9532ceb6b1d6a5f7d3a2dd5c853015850c65a47045c70f5f2f9ff88f",
"sha256:8d6c9bc14bacdfbfd51fed85f0576973eaaa7b30d81ef93264f8e22b86a9c9f7", "sha256:946e97712014784c3257e4ca45cf5071ffdbbebe83977d429e8f7329d0e2387f",
"sha256:8d759cecfa8aab4a1eb4e23b6420126b15c7743e85b33f389916bb98c4ecbb84", "sha256:9511420cf727eb6e603dc6f3c122da1a16af38abc92272a715ce68c47b19b140",
"sha256:8ef3f0977c21190f949d5cfd71ded09de87d330c6d98bd5ecb5bb1135d666d0d", "sha256:958b75f26f8abbcb9bc47a8a546a0449ba565d6aac819e5bb80417b93e5777fa",
"sha256:8f95daf0ce2b24815ddf62667229ba5dfc0cfee43eb43b2549766170d0f24ae9", "sha256:99ea63932e86b08e36d6246ff8f663728a5baefa7e9a0e2f682864fe13297514",
"sha256:911b4a16dce370657e5b8d8b6ba0fbb50dd5e2b24c4416f4b9e664503d3f0502", "sha256:9aa5cf7a6a8597968ff6f4e7488d5518bba911344b32b7948012a41ca3ae7e41",
"sha256:96117212229905da864794df9ea7bd54987c30a5dcbab3432edc3f344231adae", "sha256:9c8f580590822df5675b9bc04b9df534be23a4917f709f9483fa554fd2e0a4df",
"sha256:963cbcf296943f7017470d0b705e63e908f32b4f7dbe43f72c22f6fe1bd9ef66", "sha256:9ce64e247af33fa348694dbf7f4943a60040b5cc04df813649cc8b54c7f54061",
"sha256:975a118aa019d745f1398613b27fd8789f60a8cea057a00cdc1abedee123ffe6", "sha256:9d6fe373572b20adde2d6a58f8dc900b0cb4eec625b05ca1adbf053772723c78",
"sha256:9930853d451086c4c084d83a87294bdb0c5bc0fa4105a26c487ac09ea62e565b", "sha256:a0c87ffc5bf3669b0dfa91752653c41c9c38e1fd5b95aeb4c7ee40208c953fcd",
"sha256:99d16862e802e7c50c3b6cdd1bf041b6142335c9c2b426631f731257adfe5a15", "sha256:a1adc8cd484de52b6b11a0e59e087cd3ae593ce4c822c18d4095d16e06e49453",
"sha256:9ed4a2852b3de7a64884afcc6936db771707943249a060aec8e551c16361d478", "sha256:a29ad824bf4b735cb119e2c79a4b821ad462aeb4495e80ff186f1a8e48362082",
"sha256:9f7796959c9c036a115d34696563f75d4a2912d3b97c15c15f2a36bdd5496ce9", "sha256:ab52dd26d24061d67f485f3400cc7d3d5696f0246294a372ef09aa8ef31a44c4",
"sha256:a04b7a9017b8d0341ebbe77f61b74df1cf1b714f42b671a06f4912dc93d82597", "sha256:ac5d80cd43a9a995a501b4e3b38802628b35065e896f79d33430989e2e3f0870",
"sha256:a1b3c4ca3bec8e0ad9d32ce62444c5f3913588124a922629aa7d39357b2adf3f", "sha256:af6a09c296aa2d68b25eb154079abd5a58da883db179e9df0fc9215c405be6be",
"sha256:a290a417608f50137bec731d1f22ff3efebac72845530807a8433b2db9358c95", "sha256:b0bca424ee4d80a4880da332e56d2863e8d75305842c10aa6e94eb975bcad4fc",
"sha256:a33f7c5acf44961f29018b13f0b5f5e1589ac0cfdf75a97c9774cf7ec84d09e0", "sha256:b521c2d73f6fa1c461a68c5d220836d0fea9261d5f934833aaffde5114aecffb",
"sha256:a39be79a7c36e9a2e20376261c30deb3cdca86b50f7462ae9ff10a755c6720b9", "sha256:b77a03aba84bf2d2c8f2d5a81af5957da42324d9f701d584236dc735b6a191f8",
"sha256:a50a66fa34dd7f9dcdbc7602a1b7bf6f9ab030b4f43e892324193423d9ede180", "sha256:b81664adf97f54cb174472f5511075bfb5e8fb13151e9c1592a09b45d544dab0",
"sha256:a5ce1bdee102f7e60c075274df10b892d9ff5183ad6f5f515973eda8903dfe4c", "sha256:b9f2247b76e2e8c88f81fb850adb211d9b322f498ae7e5797f7629954f5b9767",
"sha256:a763dd33d6e27c9b4db3f8089a5fa39179a8a3cf48ce702b24a857d7c621333c", "sha256:bf7ead8b947a14c785d04943ff4743db90b0c40a4cb27e6bef4c3650800a927d",
"sha256:a773199dc42b5d02fcd46c8add552da2c4725ce2caa069527c7e27b5b6089e85", "sha256:c001b7ac2d9cf1a73899cf857d3d66919deca677df26df905852039c46aa30a6",
"sha256:aa3c925502bd0b957a96a5619134bcdc0382ef73cffd40bad218ced3586bcf8d", "sha256:c00b2ea9aab5b2c623b1901a4c04043fb847c8bd64a2f52518488434eb44c4e6",
"sha256:aeb6db2f4ab54ac21a3851d05130a2aa78a6f6a5f14003f9ae3114fb8b210850", "sha256:c17eae957d61fea05d3f2333a95dd79dc4733f3eadf44862cd6d586daae31ea3",
"sha256:af670708e145b048ead87375b899229443f2d0b4af2d1450d7701c74cd932b03", "sha256:c17fd3a63b31a21a979962bd3ab0f96d22dcdb79dc5149efc2cf66a16ae0bb59",
"sha256:afa24e5750c9b89ad5a7efef037efe49f4e339f20a94bf678c422c0c71e1207a", "sha256:cb388586c9b4d338f9585885a6f4bd2736d4a7a7eb4b63746587cb8d04f7d156",
"sha256:b02cc1cac9099c0ec72da09593e7fadb1b6cf88a101acc8153592c700d732d80", "sha256:cbf063667ef89b0d8b8bd1fcaaa4dcc8c65c17048eb14fb1fa9dbe9cb5197c81",
"sha256:b37c9ea942395de029be270f0eca8c141eb14e8455941495cd3b6f95bbe465f4", "sha256:d030b96f6ccfec0812e2fc1b02ab72d56a408ec215f496a7a25cde31160a88b4",
"sha256:b3b521e117ab991d6b3b830656f464b354a42dbea2ca16a0e7d93d573f7ab7ff", "sha256:d2c411b7d3784109dfc33f5f7cdf331d3373b8349a4ad608ee482f1a04c30efe",
"sha256:b5ad8261f47c2a72d0f676bc40f752db8cfdcab911e970753343836e41d5a9a7", "sha256:d2c8b7da269eb877cc2361d868fdcb63bfe7b5821c5b3ea2640be3f4b047b4bb",
"sha256:b9616ea14917d06736339cf36bb9eaf4eb52110a74136b0dc5eff94e92417d22", "sha256:d3f5cec4f8d27284f559a0d7c4a4bdfbae74d3b69d09c3f3b53989a730833ad8",
"sha256:b9a03767c937b621ee267507bc394df97fb2f8f61130f39f2033f3c6c191f124", "sha256:d40dbc3609f1471ca3c189815ab4596adae75d8ee0da01412b2e3d0f6e94ab46",
"sha256:b9ae0008cff25e154ef1e3975a1705d344e844ffdeb34c25b007fd48c876e95d", "sha256:d57b3b92bfa453cba737716680292afb313ec92ada6c139847e005f5ac1ad08c",
"sha256:bdd6412c1f38da7565126b174f4e644f362e317ef0560fac1fb9d0c70900ff4d", "sha256:da225a602cb4a97900e416059bc77d7b0bb8ac5cb6cb3cc734fd01c636387d2b",
"sha256:bfc417e58f277e949ed662d9cd050ddbb00c0dea8a828abaccc93dc357b7a6d1", "sha256:dc448e4871fc4df22dd04db4a7b34829e5c3404003b9b1709b6b496d340db9c7",
"sha256:c15b9e37bbca59657e4dcc63ad068c821a4676def15f04742c406748a0a11b9c", "sha256:dc6407e899fc3148d796fc4c3b0cec78153f034c5ff9baa6ae9c91d7ea05fb45",
"sha256:c677849947d523a082be6e0b5c9137f443a54e951a1711ef003ec52910c41ece", "sha256:dd0ba0cc46b9a7d5cee4c4a9733dce2f0aa21caf04fe18d18d2025a4211adc18",
"sha256:c9d247fcc33c90f2758f4162693250341e3f38cd094f64390076ef33ad0887f9", "sha256:dea204d3c6ec17fc3084c1db11bcad1347f707b7f5c08664e116a9c75ca134e9",
"sha256:ca643295bf5441dd38dadf7571ca4b63961820eedbffbe46ceba0893bf226203", "sha256:e362fc7a72fd00f641b3d6ed91076174cae36f49183afe8b4b4b77a2b5a116b0",
"sha256:ca87f639094c72268e17bc7f57c1225cc38f9e191a489a0134762e3fec402c1a", "sha256:e44be933a60b27ef0378a2fdc111ae4ac53a090169db9f97219910cac51ff885",
"sha256:cc060bc17b9de27874997d612e37d52f72092f9b59d1e04284a90ed8113cadca", "sha256:e61b7552c953e58cf2d82b95843ca410eef18af2a5380f3ff058d21eaf902eda",
"sha256:ccf4a73e07bfbd790443d6b3c1f1447ffda23cc9391e40c035d9b7d3514b57b8", "sha256:e75c4a1f00f46057f2fc98d717b2eabba09582422fe608158beed2ef0a5642da",
"sha256:cf36cadeb9c989f760a13058dbc455e5406ec3d2d247c705c8d4bc6dd1b0fcc6", "sha256:e95f13d615f91da5a5ee5a782d9041c58be051661843416f2df9453f57008d40",
"sha256:d47e2bdeba4fb1986af2ba395ce51223f4d460e6e77119439e78f2b592cafade", "sha256:e9b18889a809d8f190e09dd6ee513983e1cdc04c3f23395d237ccf699dce5eaf",
"sha256:db78cc5c03b446a43413165aa873e2f408e9fd5ddb45533e7bd3b638bace867c", "sha256:ea48f168274d60f900f847dd5fff9bd9d4e4f8af5a84149037c2b5fe1712fa0b",
"sha256:dbc5029c61f9ebb2d4c247f13584a0ef0e8e49abb13e56460310821aca3ffcaf", "sha256:ed6f9b158c11e7bcf9b0b6788003aed5046a0759e7b25e224d9551a01c779ee7",
"sha256:ddb319f869d497ef2d3d56319360b61284a9a1d8b3de3bc936748698acfda6be", "sha256:eeda85d034a2649b7e4dbd7067411e9c55c1fc65fafb9feb973d810b103e36a0",
"sha256:e0e4fdeae6c0a9d886749780ec5dcf469e98f27b312efa93008d03eaa2426fd5", "sha256:f1767c325ef4983f52a9d62590f09ea998c06d8d4aa9f13b9eeabaac3536381e",
"sha256:e4c5e7edf1e7bcbde3b52058f171a411e2a24a081b3e951d685dfea4c3c383d5", "sha256:f26f3197779fe5a90a54505334d34ceb948cec6378caea49cd9153b3bbe57566",
"sha256:e71c9dba78671d38a549e3b2d52514f50e199f9d7e18ed9b0180adeef0d04130", "sha256:f46e7fe734b57f3783a324bf3a7053df54299653e646d86558a4b2576cb47208",
"sha256:e997d22e0d1e08c8752f61675a75d93659f7aa4dbeaee54207f8d877817b4a0c", "sha256:f4e2fc0f6a573979462786edbf233fc9e1b644b4e790e8c29796f96bbe45353a",
"sha256:efa5834ba5e6c70b22afdca3894097e5a592d8d483c976359654ba990477799a", "sha256:f51322a55687f1ac075b897d409d0314a81f1ec55ebae96eeca40c9e8ad4a1c1",
"sha256:f2d951002b11962b26afb31f758c18ad39771f287b100fa5adb1d09a47eaaf5b", "sha256:f5f44d71486949237679a8052cda171244d0be9279776c1d3d276861950dd608",
"sha256:f3f96f57cea35ba19fd23a20b38fa0dfa3d87d582507129b8c8e314aa298f59b", "sha256:f62738cc16a387aa2f0dc6e93e0b0f48d5b084db249f632a0e3048d5ace783e6",
"sha256:f738051052abc95dc17f9a4c92044294a263fb7f762efdb13e528d419005c0e4", "sha256:f7cee295219988b50b543791570b013e3f3325867f9650f6233b48cb00b020c2",
"sha256:f76784355060999c36fa807b59faecb38f5769ae58283d00270835773f95e35b", "sha256:f7eb851d62a3166b8d1da5d5740509e215fa5b986467bf135a5a2d197bf16345",
"sha256:f92462ea3888c99439f58f7561ecd5dd4cf8b8b1b259ccf5376667b8c46ee747", "sha256:f937ef83e5666b6266236f59b1f38abe64851fb20e7d8d13033c5168d35ef39d",
"sha256:fefd18b29f3b84a0cdea1d86340219d9871c3b0673a38e722a73a2c39591eaa7" "sha256:fd3ed1f7d2d33856252863d5fa976c41013fac4eb0898bf7c3f5341f7ae73e06"
], ],
"version": "==3.6.0" "version": "==3.3.1"
}, },
"bitstring": { "bitstring": {
"hashes": [ "hashes": [
@@ -244,72 +244,61 @@
"markers": "platform_python_implementation != 'PyPy'", "markers": "platform_python_implementation != 'PyPy'",
"version": "==1.17.1" "version": "==1.17.1"
}, },
"click": {
"hashes": [
"sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202",
"sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b"
],
"markers": "python_version >= '3.10'",
"version": "==8.2.1"
},
"cryptography": { "cryptography": {
"hashes": [ "hashes": [
"sha256:00e8724bdad672d75e6f069b27970883179bd472cd24a63f6e620ca7e41cc0c5", "sha256:04abd71114848aa25edb28e225ab5f268096f44cf0127f3d36975bdf1bdf3390",
"sha256:048e7ad9e08cf4c0ab07ff7f36cc3115924e22e2266e034450a890d9e312dd74", "sha256:0529b1d5a0105dd3731fa65680b45ce49da4d8115ea76e9da77a875396727b41",
"sha256:0d9ef57b6768d9fa58e92f4947cea96ade1233c0e236db22ba44748ffedca394", "sha256:1bc312dfb7a6e5d66082c87c34c8a62176e684b6fe3d90fcfe1568de675e6688",
"sha256:18f878a34b90d688982e43f4b700408b478102dd58b3e39de21b5ebf6509c301", "sha256:268e4e9b177c76d569e8a145a6939eca9a5fec658c932348598818acf31ae9a5",
"sha256:1b7fa6a1c1188c7ee32e47590d16a5a0646270921f8020efc9a511648e1b2e08", "sha256:29ecec49f3ba3f3849362854b7253a9f59799e3763b0c9d0826259a88efa02f1",
"sha256:20ae4906a13716139d6d762ceb3e0e7e110f7955f3bc3876e3a07f5daadec5f3", "sha256:2bf7bf75f7df9715f810d1b038870309342bff3069c5bd8c6b96128cb158668d",
"sha256:20d15aed3ee522faac1a39fbfdfee25d17b1284bafd808e1640a74846d7c4d1b", "sha256:3b721b8b4d948b218c88cb8c45a01793483821e709afe5f622861fc6182b20a7",
"sha256:2384f2ab18d9be88a6e4f8972923405e2dbb8d3e16c6b43f15ca491d7831bd18", "sha256:3c00b6b757b32ce0f62c574b78b939afab9eecaf597c4d624caca4f9e71e7843",
"sha256:275ba5cc0d9e320cd70f8e7b96d9e59903c815ca579ab96c1e37278d231fc402", "sha256:3dc62975e31617badc19a906481deacdeb80b4bb454394b4098e3f2525a488c5",
"sha256:2dac5ec199038b8e131365e2324c03d20e97fe214af051d20c49db129844e8b3", "sha256:4973da6ca3db4405c54cd0b26d328be54c7747e89e284fcff166132eb7bccc9c",
"sha256:31a2b9a10530a1cb04ffd6aa1cd4d3be9ed49f7d77a4dafe198f3b382f41545c", "sha256:4e389622b6927d8133f314949a9812972711a111d577a5d1f4bee5e58736b80a",
"sha256:3436128a60a5e5490603ab2adbabc8763613f638513ffa7d311c900a8349a2a0", "sha256:51e4de3af4ec3899d6d178a8c005226491c27c4ba84101bfb59c901e10ca9f79",
"sha256:3b5bf5267e98661b9b888a9250d05b063220dfa917a8203744454573c7eb79db", "sha256:5f6f90b72d8ccadb9c6e311c775c8305381db88374c65fa1a68250aa8a9cb3a6",
"sha256:3de77e4df42ac8d4e4d6cdb342d989803ad37707cf8f3fbf7b088c9cbdd46427", "sha256:6210c05941994290f3f7f175a4a57dbbb2afd9273657614c506d5976db061181",
"sha256:44647c5d796f5fc042bbc6d61307d04bf29bccb74d188f18051b635f20a9c75f", "sha256:6f101b1f780f7fc613d040ca4bdf835c6ef3b00e9bd7125a4255ec574c7916e4",
"sha256:550ae02148206beb722cfe4ef0933f9352bab26b087af00e48fdfb9ade35c5b3", "sha256:7bdcd82189759aba3816d1f729ce42ffded1ac304c151d0a8e89b9996ab863d5",
"sha256:599c8d7df950aa68baa7e98f7b73f4f414c9f02d0e8104a30c0182a07732638b", "sha256:7ca25849404be2f8e4b3c59483d9d3c51298a22c1c61a0e84415104dacaf5562",
"sha256:5b64e668fc3528e77efa51ca70fadcd6610e8ab231e3e06ae2bab3b31c2b8ed9", "sha256:81276f0ea79a208d961c433a947029e1a15948966658cf6710bbabb60fcc2639",
"sha256:5bd6020c80c5b2b2242d6c48487d7b85700f5e0038e67b29d706f98440d66eb5", "sha256:8cadc6e3b5a1f144a039ea08a0bdb03a2a92e19c46be3285123d32029f40a922",
"sha256:5c966c732cf6e4a276ce83b6e4c729edda2df6929083a952cc7da973c539c719", "sha256:8e0ddd63e6bf1161800592c71ac794d3fb8001f2caebe0966e77c5234fa9efc3",
"sha256:629127cfdcdc6806dfe234734d7cb8ac54edaf572148274fa377a7d3405b0043", "sha256:909c97ab43a9c0c0b0ada7a1281430e4e5ec0458e6d9244c0e821bbf152f061d",
"sha256:705bb7c7ecc3d79a50f236adda12ca331c8e7ecfbea51edd931ce5a7a7c4f012", "sha256:96e7a5e9d6e71f9f4fca8eebfd603f8e86c5225bb18eb621b2c1e50b290a9471",
"sha256:780c40fb751c7d2b0c6786ceee6b6f871e86e8718a8ff4bc35073ac353c7cd02", "sha256:9a1e657c0f4ea2a23304ee3f964db058c9e9e635cc7019c4aa21c330755ef6fd",
"sha256:7a3085d1b319d35296176af31c90338eeb2ddac8104661df79f80e1d9787b8b2", "sha256:9eb9d22b0a5d8fd9925a7764a054dca914000607dff201a24c791ff5c799e1fa",
"sha256:826b46dae41a1155a0c0e66fafba43d0ede1dc16570b95e40c4d83bfcf0a451d", "sha256:af4ff3e388f2fa7bff9f7f2b31b87d5651c45731d3e8cfa0944be43dff5cfbdb",
"sha256:833dc32dfc1e39b7376a87b9a6a4288a10aae234631268486558920029b086ec", "sha256:b042d2a275c8cee83a4b7ae30c45a15e6a4baa65a179a0ec2d78ebb90e4f6699",
"sha256:cc4d66f5dc4dc37b89cfef1bd5044387f7a1f6f0abb490815628501909332d5d", "sha256:bc821e161ae88bfe8088d11bb39caf2916562e0a2dc7b6d56714a48b784ef0bb",
"sha256:d063341378d7ee9c91f9d23b431a3502fc8bfacd54ef0a27baa72a0843b29159", "sha256:c505d61b6176aaf982c5717ce04e87da5abc9a36a5b39ac03905c4aafe8de7aa",
"sha256:e2a21a8eda2d86bb604934b6b37691585bd095c1f788530c1fcefc53a82b3453", "sha256:c63454aa261a0cf0c5b4718349629793e9e634993538db841165b3df74f37ec0",
"sha256:e40b80ecf35ec265c452eea0ba94c9587ca763e739b8e559c128d23bff7ebbbf", "sha256:c7362add18b416b69d58c910caa217f980c5ef39b23a38a0880dfd87bdf8cd23",
"sha256:e5b3dda1b00fb41da3af4c5ef3f922a200e33ee5ba0f0bc9ecf0b0c173958385", "sha256:d03806036b4f89e3b13b6218fefea8d5312e450935b1a2d55f0524e2ed7c59d9",
"sha256:ea3c42f2016a5bbf71825537c2ad753f2870191134933196bee408aac397b3d9", "sha256:d1b3031093a366ac767b3feb8bcddb596671b3aaff82d4050f984da0c248b615",
"sha256:eccddbd986e43014263eda489abbddfbc287af5cddfd690477993dbb31e31016", "sha256:d1c3572526997b36f245a96a2b1713bf79ce99b271bbcf084beb6b9b075f29ea",
"sha256:ee411a1b977f40bd075392c80c10b58025ee5c6b47a822a33c1198598a7a5f05", "sha256:efcfe97d1b3c79e486554efddeb8f6f53a4cdd4cf6086642784fa31fc384e1d7",
"sha256:f4028f29a9f38a2025abedb2e409973709c660d44319c61762202206ed577c42", "sha256:f514ef4cd14bb6fb484b4a60203e912cfcb64f2ab139e88c2274511514bf7308"
"sha256:f68f833a9d445cc49f01097d95c83a850795921b3f7cc6488731e69bde3288da",
"sha256:fc022c1fa5acff6def2fc6d7819bbbd31ccddfe67d075331a65d9cfb28a20983"
], ],
"markers": "python_version >= '3.7' and python_full_version not in '3.9.0, 3.9.1'", "markers": "python_version >= '3.7' and python_full_version not in '3.9.0, 3.9.1'",
"version": "==45.0.6" "version": "==44.0.2"
},
"ecdsa": {
"hashes": [
"sha256:30638e27cf77b7e15c4c4cc1973720149e1033827cfd00661ca5c8cc0cdb24c3",
"sha256:478cba7b62555866fcb3bb3fe985e06decbdb68ef55713c4e5ab98c57d508e61"
],
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'",
"version": "==0.19.1"
}, },
"esptool": { "esptool": {
"hashes": [ "hashes": [
"sha256:05cc4732eb2a9a7766c9e3531f7943d76ff0ca06dc9cd308d1d3d0b72f74aac2" "sha256:dc4ef26b659e1a8dcb019147c0ea6d94980b34de99fbe09121c7941c8b254531"
], ],
"index": "pypi", "index": "pypi",
"markers": "python_version >= '3.10'", "version": "==4.8.1"
"version": "==5.0.2"
},
"idna": {
"hashes": [
"sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9",
"sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"
],
"markers": "python_version >= '3.6'",
"version": "==3.10"
}, },
"intelhex": { "intelhex": {
"hashes": [ "hashes": [
@@ -318,38 +307,13 @@
], ],
"version": "==2.3.0" "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": { "mpremote": {
"hashes": [ "hashes": [
"sha256:7f347318fb6d3bb8f89401d399a05efba39b51c74f747cebe92d3c6a9a4ee0b4", "sha256:1a3c16d255748cfe54d4a897908651fc8286233173f7c7b2a0e56ae4b9fa940e",
"sha256:daed9b795fdf98edb0c9c4f7f892bf66f075ec5e728bdcc4ab0915abf23d5d17" "sha256:d3ae3d0a0ae7713c537be2b6afadd11c7cde5f1750ea1260f6667bb80071b15b"
], ],
"index": "pypi", "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": { "pycparser": {
"hashes": [ "hashes": [
@@ -359,14 +323,6 @@
"markers": "python_version >= '3.8'", "markers": "python_version >= '3.8'",
"version": "==2.22" "version": "==2.22"
}, },
"pygments": {
"hashes": [
"sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887",
"sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"
],
"markers": "python_version >= '3.8'",
"version": "==2.19.2"
},
"pyserial": { "pyserial": {
"hashes": [ "hashes": [
"sha256:3c77e014170dfffbd816e6ffc205e9842efb10be9f58ec16d3e8675b4925cddb", "sha256:3c77e014170dfffbd816e6ffc205e9842efb10be9f58ec16d3e8675b4925cddb",
@@ -441,150 +397,13 @@
], ],
"version": "==1.7.0" "version": "==1.7.0"
}, },
"rich": { "six": {
"hashes": [ "hashes": [
"sha256:536f5f1785986d6dbdea3c75205c473f970777b4a0d6c6dd1b696aa05a3fa04f", "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274",
"sha256:e497a48b844b0320d45007cdebfeaeed8db2a4f4bcf49f15e455cfc4af11eaa8" "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"
], ],
"markers": "python_full_version >= '3.8.0'", "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==14.1.0" "version": "==1.17.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": {} "develop": {}

2
dev.py
View File

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

View File

@@ -15,20 +15,12 @@ async def main():
settings = Settings() settings = Settings()
patterns = Patterns(settings["led_pin"], settings["num_leds"], selected=settings["pattern"]) patterns = Patterns(settings["led_pin"], settings["num_leds"], selected=settings["pattern"])
if settings["color_order"] == "rbg": if settings["color_order"] == "rbg": color_order = (1, 5, 3)
color_order = (1, 5, 3)
print("RBG")
if settings["color_order"] == "grb":
color_order = (3, 1, 5)
else: color_order = (1, 3, 5) else: color_order = (1, 3, 5)
patterns.colors = [(8,0,0)] patterns.set_color(0,(tuple(int(settings["color1"][i:i+2], 16) for i in color_order)))
patterns.set_color(1,(tuple(int(settings["color2"][i:i+2], 16) for i in color_order)))
async def system(): patterns.set_brightness(int(settings["brightness"]))
while True: patterns.set_delay(int(settings["delay"]))
gc.collect()
for i in range(60):
wdt.feed()
await asyncio.sleep(1)
w = web(settings, patterns) w = web(settings, patterns)
print(settings) print(settings)
@@ -39,11 +31,14 @@ async def main():
wdt.feed() wdt.feed()
asyncio.create_task(p2p(settings, patterns)) asyncio.create_task(p2p(settings, patterns))
asyncio.create_task(system())
patterns.select(settings["pattern"])
await patterns.run()
while True:
#print(time.localtime())
gc.collect()
for i in range(20):
wdt.feed()
await asyncio.sleep_ms(1000)
# cleanup before ending the application # cleanup before ending the application
await server await server

View File

@@ -11,6 +11,10 @@ async def p2p(settings, patterns):
except: except:
print(f"Failed to load espnow data {msg}") print(f"Failed to load espnow data {msg}")
continue continue
print(data)
if "names" not in data or settings.get("name") in data.get("names", []): 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)) if "step" in settings and isinstance(settings["step"], int):
patterns.set_pattern_step(settings["step"])
else:
await settings.set_settings(data.get("settings", {}), patterns, data.get("save", False))
print("should not print")

View File

@@ -1,447 +1,222 @@
from machine import Pin import asyncio
from machine import Pin, WDT
from neopixel import NeoPixel from neopixel import NeoPixel
import utime import utime
import random import random
import _thread
import asyncio
from patterns_base import Patterns as PatternsBase
# Short-key parameter mapping for convenience setters class Patterns:
param_mapping = { def __init__(self, pin, num_leds, color1=(0,0,0), color2=(0,0,0), brightness=127, selected="rainbow_cycle", delay=100):
"pt": "selected", self.n = NeoPixel(Pin(pin, Pin.OUT), num_leds)
"pa": "selected", self.num_leds = num_leds
"cl": "colors", self.delay = delay
"br": "brightness", self.brightness = 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="off", delay=100):
super().__init__(pin, num_leds, color1, color2, brightness, selected, delay)
self.auto = True
self.step = 0
self.patterns = { self.patterns = {
"off": self.off, "off": self.off,
"on" : self.on, "on" : self.on,
"blink": self.blink, "blink": self.blink,
"rainbow": self.rainbow, "rainbow": self.rainbow,
"pulse": self.pulse, "theater chase": self.theater_chase,
"transition": self.transition, "flicker": self.flicker # Added flicker pattern
"chase": self.n_chase,
"n_chase": self.n_chase,
"circle": self.circle,
} }
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.task = None
self.pattern_step = 0
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 blink(self): def set_delay(self, delay):
self.stopped = False self.delay = delay
self.running = True
state = True # True = on, False = off
last_update = utime.ticks_ms()
# Only continue running this pattern while it is the selected one
while self.running and self.selected == "blink":
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_brightness(self, brightness):
self.brightness = brightness
def rainbow(self): def set_colors(self, colors):
self.stopped = False self.colors = colors
self.running = True
step = self.step % 256
step_amount = max(1, int(self.n1)) # n1 controls step increment
# If auto is False, run once and update step
if not self.auto:
for i in range(self.num_leds):
rc_index = (i * 256 // self.num_leds) + 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
# Auto is True: run continuously
last_update = utime.ticks_ms()
# Only continue running this pattern while it is the selected one
while self.running and self.selected == "rainbow":
current_time = utime.ticks_ms()
sleep_ms = max(1, int(self.delay)) # Access delay directly
if utime.ticks_diff(current_time, last_update) >= sleep_ms:
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()
step = (step + step_amount) % 256
self.step = step
last_update = current_time
self.running = False
self.stopped = True
def set_color(self, num, color):
def pulse(self): # Changed: More robust index check
self.stopped = False if 0 <= num < len(self.colors):
self.running = True self.colors[num] = color
self.off() return True
elif num == len(self.colors): # Allow setting a new color at the end
# Get timing parameters, ensure non-negative self.colors.append(color)
attack_ms = max(0, int(self.n1)) # Attack time in ms return True
hold_ms = max(0, int(self.n2)) # Hold time in ms return False
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
# Only continue running this pattern while it is the selected one
while self.running and self.selected == "pulse":
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()
delay_ms = int(self.delay) # Access delay directly
wait_until = utime.ticks_add(cycle_start, delay_ms)
while self.running and utime.ticks_diff(wait_until, utime.ticks_ms()) > 0:
pass
self.running = False
self.stopped = True
def transition(self): def del_color(self, num):
"""Transition between colors, taking delay ms between each color""" # Changed: More robust index check and using del for lists
self.stopped = False if 0 <= num < len(self.colors):
self.running = True del self.colors[num]
return True
if not self.colors: return False
# 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 and self.selected == "transition":
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_start = utime.ticks_ms()
last_update = transition_start
while self.running:
# Access delay and colors directly for live updates
transition_duration = max(10, int(self.delay)) # At least 10ms
update_interval = max(10, transition_duration // 50) # Update every ~2% of transition
color1 = self.colors[0] if len(self.colors) > 0 else (0, 0, 0)
color2 = self.colors[1] if len(self.colors) > 1 else color1
if utime.ticks_diff(utime.ticks_ms(), transition_start) >= transition_duration:
break
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
# Auto is True: cycle through all colors continuously
while self.running and self.selected == "transition":
# Access colors directly for live updates
if not self.colors:
break
# 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:
# Access delay directly for live updates
transition_duration = max(10, int(self.delay)) # At least 10ms
update_interval = max(10, transition_duration // 50) # Update every ~2% of transition
if utime.ticks_diff(utime.ticks_ms(), transition_start) >= transition_duration:
break
now = utime.ticks_ms()
if utime.ticks_diff(now, last_update) >= update_interval:
# Calculate interpolation factor (0.0 to 1.0)
elapsed = utime.ticks_diff(now, transition_start)
factor = min(1.0, elapsed / transition_duration)
# Interpolate between colors
interpolated = tuple(
int(current_color[i] + (next_color[i] - current_color[i]) * factor)
for i in range(3)
)
# Apply brightness and fill
self.fill(self.apply_brightness(interpolated))
last_update = now
# Move to next color
color_index = (color_index + 1) % len(self.colors)
self.running = False
self.stopped = True
def n_chase(self): def apply_brightness(self, color, brightness_override=None):
"""N-chase pattern: n1 LEDs of color0, n2 LEDs of color1, repeating. effective_brightness = brightness_override if brightness_override is not None else self.brightness
Moves by n3 on even steps, n4 on odd steps (n3/n4 can be positive or negative)""" return tuple(int(c * effective_brightness / 255) for c in color)
self.stopped = False
self.running = True async def select(self, pattern, reset = True):
if pattern not in self.patterns:
if len(self.colors) < 1: return False
# Need at least 1 color self.selected = pattern
self.running = False if self.task is not None:
self.stopped = True self.task.cancel()
return if reset: self.pattern_step = 0
print(pattern)
segment_length = 0 # Will be calculated in loop self.task = asyncio.create_task(self.patterns[pattern]())
position = 0 # Current position offset return True
step_count = 0 # Track which step we're on
def set(self, i, color):
last_update = utime.ticks_ms() self.n[i] = color
# Only continue running this pattern while it is the selected one def write(self):
# Note: this pattern can be selected as "n_chase" or "chase" self.n.write()
while self.running and self.selected in ("n_chase", "chase"):
# Access colors, delay, and n values directly for live updates def fill(self, color):
if not self.colors: self.n.fill(color)
break self.n.write()
# If only one color provided, use it for both colors
if len(self.colors) < 2: async def off(self):
color0 = self.colors[0] self.fill((0, 0, 0))
color1 = self.colors[0]
async def on(self):
self.fill(self.apply_brightness(self.colors[0]))
def sync(self):
self.pattern_step = 0
async def rainbow(self):
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: else:
color0 = self.colors[0] pos -= 170
color1 = self.colors[1] return (0, pos * 3, 255 - pos * 3)
last_update = utime.ticks_ms()
color0 = self.apply_brightness(color0) while True:
color1 = self.apply_brightness(color1) if utime.ticks_diff(utime.ticks_ms(), last_update) >= self.delay:
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
transition_duration = max(10, int(self.delay))
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): for i in range(self.num_leds):
# Calculate position in the repeating segment rc_index = (i * 256 // self.num_leds) + self.pattern_step
relative_pos = (i - position) % segment_length self.n[i] = self.apply_brightness(wheel(rc_index & 255))
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.n.write()
self.pattern_step = (self.pattern_step + 1) % 256
# Move position by n3 or n4 on alternate steps last_update += self.delay
if step_count % 2 == 0: await asyncio.sleep(0)
position = position + n3
async def theater_chase(self):
last_update = utime.ticks_ms()
while True:
if utime.ticks_diff(utime.ticks_ms(), 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.colors[0])
else:
self.n[i] = (0, 0, 0)
self.n.write()
self.pattern_step = (self.pattern_step + 1) % 3
last_update += self.delay
await asyncio.sleep(0)
async def blink(self):
last_update = utime.ticks_ms()
self.pattern_step = 0
while True:
if utime.ticks_diff(utime.ticks_ms(), last_update) >= self.delay:
if self.pattern_step:
self.off()
self.pattern_step = 0
else: else:
position = position + n4 self.on()
self.pattern_step = 1
# Wrap position to keep it reasonable last_update += self.delay
max_pos = self.num_leds + segment_length await asyncio.sleep(0)
position = position % max_pos
if position < 0:
position += max_pos
step_count += 1
last_update = current_time
self.running = False
self.stopped = True
def circle(self): async def flicker(self):
"""Circle loading pattern - grows to n2, then tail moves forward at n3 until min length n4""" last_update = utime.ticks_ms()
self.stopped = False while True:
self.running = True if utime.ticks_diff(utime.ticks_ms(), last_update) >= self.delay:
head = 0 # Calculate a single flicker amount for all LEDs
tail = 0 flicker_amount = random.randint(int(-self.brightness // 1.5), int(self.brightness // 1.5))
flicker_brightness = max(0, min(255, self.brightness + flicker_amount))
# Calculate timing self.fill(self.apply_brightness(self.colors[0], brightness_override=flicker_brightness))
head_rate = max(1, int(self.n1)) # n1 = head moves per second last_update += self.delay
tail_rate = max(1, int(self.n3)) # n3 = tail moves per second await asyncio.sleep(0)
max_length = max(1, int(self.n2)) # n2 = max length
min_length = max(0, int(self.n4)) # n4 = min length
head_delay = 1000 // head_rate # ms between head movements
tail_delay = 1000 // tail_rate # ms between tail movements
last_head_move = utime.ticks_ms()
last_tail_move = utime.ticks_ms()
phase = "growing" # "growing", "shrinking", or "off"
# Only continue running this pattern while it is the selected one
while self.running and self.selected == "circle":
current_time = utime.ticks_ms()
# Clear all LEDs
self.n.fill((0, 0, 0))
# Calculate segment length
segment_length = (head - tail) % self.num_leds
if segment_length == 0 and head != tail:
segment_length = self.num_leds
# Draw segment from tail to head
color = self.apply_brightness(self.colors[0])
for i in range(segment_length + 1):
led_pos = (tail + i) % self.num_leds
self.n[led_pos] = color
# Move head continuously at n1 LEDs per second
if utime.ticks_diff(current_time, last_head_move) >= head_delay:
head = (head + 1) % self.num_leds
last_head_move = current_time
# Tail behavior based on phase
if phase == "growing":
# Growing phase: tail stays at 0 until max length reached
if segment_length >= max_length:
phase = "shrinking"
elif phase == "shrinking":
# Shrinking phase: move tail forward at n3 LEDs per second
if utime.ticks_diff(current_time, last_tail_move) >= tail_delay:
tail = (tail + 1) % self.num_leds
last_tail_move = current_time
# Check if we've reached min length
current_length = (head - tail) % self.num_leds
if current_length == 0 and head != tail:
current_length = self.num_leds
# For min_length = 0, we need at least 1 LED (the head)
if min_length == 0 and current_length <= 1:
phase = "off" # All LEDs off for 1 step
elif min_length > 0 and current_length <= min_length:
phase = "growing" # Cycle repeats
else: # phase == "off"
# Off phase: all LEDs off for 1 step, then restart
tail = head # Reset tail to head position to start fresh
phase = "growing"
self.n.write()
self.running = False
self.stopped = True
async def color_transition(self):
if len(self.colors) < 2:
# If there's only one color or no colors, just display that color (or off)
self.fill(self.apply_brightness(self.colors[0]))
return
last_transition_start_time = utime.ticks_ms()
current_color_index = 0
transition_duration_ms = self.delay # Use self.delay as the transition time
while True:
color_from = self.colors[current_color_index]
color_to = self.colors[(current_color_index + 1) % len(self.colors)]
start_time = utime.ticks_ms()
elapsed_time = 0
while elapsed_time < transition_duration_ms:
# Calculate the interpolation factor (0.0 to 1.0)
# Maximize to avoid division by zero if delay is 0, though a meaningful delay is expected
t = min(1.0, elapsed_time / max(1, transition_duration_ms))
# Interpolate each color component
interpolated_color = (
int(color_from[0] + (color_to[0] - color_from[0]) * t),
int(color_from[1] + (color_to[1] - color_from[1]) * t),
int(color_from[2] + (color_to[2] - color_from[2]) * t)
)
self.fill(self.apply_brightness(interpolated_color))
await asyncio.sleep(0) # Update smoothly
elapsed_time = utime.ticks_diff(utime.ticks_ms(), start_time)
# Ensure the final color is set precisely after interpolation loop
self.fill(self.apply_brightness(color_to))
current_color_index = (current_color_index + 1) % len(self.colors)
await asyncio.sleep(0) # Yield control
async def main():
w = WDT(timeout = 10000)
p = Patterns(num_leds=10, pin=10, color1=(16,16,0))
# p.set_delay(100)
# await p.select("blink")
# await asyncio.sleep(2)
# p.set_delay(10)
# await p.select("rainbow")
# await asyncio.sleep(2)
# p.set_delay(100)
# await p.select("theater chase")
# await asyncio.sleep(2)
# p.set_colors([(255, 100, 0)]) # Set a base color for flicker (e.g., orange for a candle effect)
# p.set_brightness(200) # Set a brighter base for flicker to allow for dimming
# p.set_delay(100) # Faster updates for a more convincing flicker
# await p.select("flicker")
# await asyncio.sleep(2)
w.feed()
# Test the new color transition pattern
print("Starting color transition...")
p.set_colors([(255, 0, 0), (0, 255, 0), (0, 0, 255), (255, 255, 0)]) # Red, Green, Blue, Yellow
p.set_delay(1000) # 1 second transition between colors
p.set_brightness(150)
await p.select("color transition")
await asyncio.sleep(10) # Let it run for 10 seconds
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -1,167 +0,0 @@
from machine import Pin
from neopixel import NeoPixel
import utime
import random
import _thread
import asyncio
import json
# Short-key parameter mapping for convenience setters
param_mapping = {
"pt": "selected",
"pa": "selected",
"cl": "colors",
"br": "brightness",
"dl": "delay",
"nl": "num_leds",
"co": "color_order",
"lp": "led_pin",
"n1": "n1",
"n2": "n2",
"n3": "n3",
"n4": "n4",
"n5": "n5",
"n6": "n6",
"auto": "auto",
}
class Patterns:
def __init__(self, pin, num_leds, color1=(0,0,0), color2=(0,0,0), brightness=127, selected="off", 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.n1 = 0
self.n2 = 0
self.n3 = 0
self.n4 = 0
self.n5 = 0
self.n6 = 0
def select(self, pattern):
if pattern in self.patterns:
self.selected = pattern
return True
# If pattern doesn't exist, default to "off"
if "off" in self.patterns:
self.selected = "off"
return False
async def run(self):
await self.stop()
# Ensure we wait a bit more to let the thread fully terminate
# If selected pattern doesn't exist, default to "off"
if self.selected not in self.patterns:
print(f"Pattern {self.selected} not found, defaulting to 'off'")
if "off" in self.patterns:
self.selected = "off"
else:
print("No patterns available")
self.running = False
self.stopped = True
return
print(f"Starting pattern {self.selected}")
_thread.start_new_thread(self.patterns[self.selected], ())
async def stop(self):
if not self.running:
# Already stopped
self.stopped = True
return
self.running = False
start = utime.ticks_ms()
timeout = 2000 # Increased timeout to 2 seconds
while not self.stopped and utime.ticks_diff(utime.ticks_ms(), start) < timeout:
await asyncio.sleep_ms(10) # Check every 10ms instead of 0ms
if not self.stopped:
# Timeout reached, force stop
print("Warning: Pattern did not stop within timeout")
self.stopped = True
def set_param(self, key, value):
if key in param_mapping:
setattr(self, param_mapping[key], value)
return True
print(f"Invalid parameter: {key}")
return False
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,31 +0,0 @@
import json
import wifi
import ubinascii
import machine
class Presets(dict):
FILE = "/presets.json"
def __init__(self):
super().__init__()
self.load() # Load settings from file during initialization
def save(self):
try:
j = json.dumps(self)
with open(self.FILE, 'w') as file:
file.write(j)
print("Presets saved successfully.")
except Exception as e:
print(f"Error saving settings: {e}")
def load(self):
try:
with open(self.FILE, 'r') as file:
self.update(json.load(file))
print("Presets loaded successfully.")
except Exception as e:
print(f"Error loading presets")
self.save()

View File

@@ -47,38 +47,29 @@ class Settings(dict):
async def set_settings(self, data, patterns, save): async def set_settings(self, data, patterns, save):
try: try:
print(f"Setting settings: {data}") print(data)
for key, value in data.items(): for key, value in data.items():
print(key, value) print(key, value)
if key == "colors": if key == "colors":
buff = [] buff = []
for color in value: for color in value:
buff.append(tuple(int(color[i:i+2], 16) for i in self.color_order)) buff.append(tuple(int(color[i:i+2], 16) for i in self.color_order))
patterns.colors = buff patterns.set_colors(buff)
elif key == "color1":
patterns.set_color(0,(tuple(int(value[i:i+2], 16) for i in self.color_order))) # Convert hex to RGB
elif key == "color2":
patterns.set_color(1,(tuple(int(value[i:i+2], 16) for i in self.color_order))) # Convert hex to RGB
elif key == "num_leds": elif key == "num_leds":
patterns.update_num_leds(self["led_pin"], value) patterns.update_num_leds(self["led_pin"], value)
elif key == "pattern": elif key == "pattern":
if not patterns.select(value): if not await patterns.select(value):
return "Pattern doesn't exist", 400 return "Pattern doesn't exist", 400
await patterns.run()
elif key == "delay": elif key == "delay":
delay = int(data["delay"]) delay = int(data["delay"])
patterns.delay = delay patterns.set_delay(delay)
elif key == "brightness": elif key == "brightness":
brightness = int(data["brightness"]) brightness = int(data["brightness"])
patterns.brightness = brightness patterns.set_brightness(brightness)
elif key == "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": elif key == "name":
self[key] = value self[key] = value
self.save() self.save()
@@ -95,9 +86,9 @@ class Settings(dict):
return "Invalid key", 400 return "Invalid key", 400
self[key] = value self[key] = value
#print(self) #print(self)
patterns.sync()
if save: if save:
self.save() self.save()
print(self)
return "OK", 200 return "OK", 200
except (KeyError, ValueError): except (KeyError, ValueError):
return "Bad request", 400 return "Bad request", 400

View File

@@ -12,7 +12,7 @@ def web(settings, patterns):
@app.route('/') @app.route('/')
async def index_hnadler(request): async def index_hnadler(request):
mac = wifi.get_mac().hex() mac = wifi.get_mac().hex()
return Template('index.html').render(settings=settings, patterns=patterns.patterns.keys()) return Template('/index.html').render(settings=settings, patterns=patterns.patterns.keys(), mac=mac)
@app.route("/static/<path:path>") @app.route("/static/<path:path>")
def static_handler(request, path): def static_handler(request, path):

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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