From b7d2f52fc3986cdc6b424efbe12d7adafdc81730 Mon Sep 17 00:00:00 2001 From: jimmy Date: Sun, 25 Jan 2026 23:23:14 +1300 Subject: [PATCH] Refactor patterns to use preset-based API and fix initialization order - Fix initialization order: initialize self.presets before calling self.select() - Separate add() and edit() methods: add() creates new presets, edit() updates existing ones - Update all test files to use add() instead of edit() for creating presets - Add comprehensive auto/manual mode test - Remove tool.py (moved to led-tool project) --- Pipfile | 1 + Pipfile.lock | 948 +++++++++++++++++++++++++++++++++++ dev.py | 165 ++---- install.sh | 4 + src/patterns.py | 126 ++--- src/patterns_base.py | 70 ++- test/patterns/auto_manual.py | 190 +++++++ test/patterns/blink.py | 13 +- test/patterns/chase.py | 107 ++-- test/patterns/circle.py | 95 ++-- test/patterns/off.py | 5 +- test/patterns/on.py | 16 +- test/patterns/pulse.py | 71 +-- test/patterns/rainbow.py | 104 ++-- test/patterns/transition.py | 49 +- tool.py | 734 --------------------------- 16 files changed, 1593 insertions(+), 1105 deletions(-) create mode 100644 Pipfile.lock create mode 100755 install.sh create mode 100644 test/patterns/auto_manual.py delete mode 100644 tool.py diff --git a/Pipfile b/Pipfile index ce0c926..c05731c 100644 --- a/Pipfile +++ b/Pipfile @@ -20,3 +20,4 @@ python_version = "3" [scripts] dev = 'watchfiles "./dev.py /dev/ttyACM0 src reset follow"' web = "uvicorn tool:app --host 0.0.0.0 --port 8080" +install = "pipenv install" diff --git a/Pipfile.lock b/Pipfile.lock new file mode 100644 index 0000000..59e4cb1 --- /dev/null +++ b/Pipfile.lock @@ -0,0 +1,948 @@ +{ + "_meta": { + "hash": { + "sha256": "1d0184b0df68796cc30d8a808f27b6a5d447b3e1f8af0633b2a543d14f0ab829" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "annotated-doc": { + "hashes": [ + "sha256:571ac1dc6991c450b25a9c2d84a3705e2ae7a53467b5d111c24fa8baabbed320", + "sha256:fbcda96e87e9c92ad167c2e53839e57503ecfda18804ea28102353485033faa4" + ], + "markers": "python_version >= '3.8'", + "version": "==0.0.4" + }, + "annotated-types": { + "hashes": [ + "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", + "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89" + ], + "markers": "python_version >= '3.8'", + "version": "==0.7.0" + }, + "anyio": { + "hashes": [ + "sha256:73c693b567b0c55130c104d0b43a9baf3aa6a31fc6110116509f27bf75e21ec0", + "sha256:dad2376a628f98eeca4881fc56cd06affd18f659b17a747d3ff0307ced94b1bb" + ], + "markers": "python_version >= '3.9'", + "version": "==4.12.0" + }, + "asgiref": { + "hashes": [ + "sha256:13acff32519542a1736223fb79a715acdebe24286d98e8b164a73085f40da2c4", + "sha256:1db9021efadb0d9512ce8ffaf72fcef601c7b73a8807a1bb2ef143dc6b14846d" + ], + "index": "pypi", + "markers": "python_version >= '3.9'", + "version": "==3.11.0" + }, + "bitarray": { + "hashes": [ + "sha256:004d518fa410e6da43386d20e07b576a41eb417ac67abf9f30fa75e125697199", + "sha256:014df8a9430276862392ac5d471697de042367996c49f32d0008585d2c60755a", + "sha256:01c5f0dc080b0ebb432f7a68ee1e88a76bd34f6d89c9568fcec65fb16ed71f0e", + "sha256:025d133bf4ca8cf75f904eeb8ea946228d7c043231866143f31946a6f4dd0bf3", + "sha256:0ca70ccf789446a6dfde40b482ec21d28067172cd1f8efd50d5548159fccad9e", + "sha256:0df69d26f21a9d2f1b20266f6737fa43f08aa5015c99900fb69f255fbe4dabb4", + "sha256:0f8069a807a3e6e3c361ce302ece4bf1c3b49962c1726d1d56587e8f48682861", + "sha256:15a2eff91f54d2b1f573cca8ca6fb58763ce8fea80e7899ab028f3987ef71cd5", + "sha256:15e8d0597cc6e8496de6f4dea2a6880c57e1251502a7072f5631108a1aa28521", + "sha256:165052a0e61c880f7093808a0c524ce1b3555bfa114c0dfb5c809cd07918a60d", + "sha256:178c5a4c7fdfb5cd79e372ae7f675390e670f3732e5bc68d327e01a5b3ff8d55", + "sha256:18214bac86341f1cc413772e66447d6cca10981e2880b70ecaf4e826c04f95e9", + "sha256:1a54d7e7999735faacdcbe8128e30207abc2caf9f9fd7102d180b32f1b78bfce", + "sha256:1a926fa554870642607fd10e66ee25b75fdd9a7ca4bbffa93d424e4ae2bf734a", + "sha256:1c8f2a5d8006db5a555e06f9437e76bf52537d3dfd130cb8ae2b30866aca32c9", + "sha256:21ca6a47bf20db9e7ad74ca04b3d479e4d76109b68333eb23535553d2705339e", + "sha256:22c540ed20167d3dbb1e2d868ca935180247d620c40eace90efa774504a40e3b", + "sha256:239578587b9c29469ab61149dda40a2fe714a6a4eca0f8ff9ea9439ec4b7bc30", + "sha256:25b9cff6c9856bc396232e2f609ea0c5ec1a8a24c500cee4cca96ba8a3cd50b6", + "sha256:26714898eb0d847aac8af94c4441c9cb50387847d0fe6b9fc4217c086cd68b80", + "sha256:28a85b056c0eb7f5d864c0ceef07034117e8ebfca756f50648c71950a568ba11", + "sha256:2a3d1b05ffdd3e95687942ae7b13c63689f85d3f15c39b33329e3cb9ce6c015f", + "sha256:2c3bb96b6026643ce24677650889b09073f60b9860a71765f843c99f9ab38b25", + "sha256:2fa23fdb3beab313950bbb49674e8a161e61449332d3997089fe3944953f1b77", + "sha256:2fe8c54b15a9cd4f93bc2aaceab354ec65af93370aa1496ba2f9c537a4855ee0", + "sha256:30989a2451b693c3f9359d91098a744992b5431a0be4858f1fdf0ec76b457125", + "sha256:31a4ad2b730128e273f1c22300da3e3631f125703e4fee0ac44d385abfb15671", + "sha256:337c8cd46a4c6568d367ed676cbf2d7de16f890bb31dbb54c44c1d6bb6d4a1de", + "sha256:33af25c4ff7723363cb8404dfc2eefeab4110b654f6c98d26aba8a08c745d860", + "sha256:3ea52df96566457735314794422274bd1962066bfb609e7eea9113d70cf04ffe", + "sha256:3eae38daffd77c9621ae80c16932eea3fb3a4af141fb7cc724d4ad93eff9210d", + "sha256:41b53711f89008ba2de62e4c2d2260a8b357072fd4f18e1351b28955db2719dc", + "sha256:451f9958850ea98440d542278368c8d1e1ea821e2494b204570ba34a340759df", + "sha256:46cf239856b87fe1c86dfbb3d459d840a8b1649e7922b1e0bfb6b6464692644a", + "sha256:4779f356083c62e29b4198d290b7b17a39a69702d150678b7efff0fdddf494a8", + "sha256:4902f4ecd5fcb6a5f482d7b0ae1c16c21f26fc5279b3b6127363d13ad8e7a9d9", + "sha256:4d73d4948dcc5591d880db8933004e01f1dd2296df9de815354d53469beb26fe", + "sha256:4d9984017314da772f5f7460add7a0301a4ffc06c72c2998bb16c300a6253607", + "sha256:4f298daaaea58d45e245a132d6d2bdfb6f856da50dc03d75ebb761439fb626cf", + "sha256:50ddbe3a7b4b6ab96812f5a4d570f401a2cdb95642fd04c062f98939610bbeee", + "sha256:5338a313f998e1be7267191b7caaae82563b4a2b42b393561055412a34042caa", + "sha256:5591daf81313096909d973fb2612fccd87528fdfdd39f6478bdce54543178954", + "sha256:56896ceeffe25946c4010320629e2d858ca763cd8ded273c81672a5edbcb1e0a", + "sha256:58a01ea34057463f7a98a4d6ff40160f65f945e924fec08a5b39e327e372875d", + "sha256:5bfac7f236ba1a4d402644bdce47fb9db02a7cf3214a1f637d3a88390f9e5428", + "sha256:5c5a8a83df95e51f7a7c2b083eaea134cbed39fc42c6aeb2e764ddb7ccccd43e", + "sha256:5f2fb10518f6b365f5b720e43a529c3b2324ca02932f609631a44edb347d8d54", + "sha256:64af877116edf051375b45f0bda648143176a017b13803ec7b3a3111dc05f4c5", + "sha256:6d70fa9c6d2e955bde8cd327ffc11f2cc34bc21944e5571a46ca501e7eadef24", + "sha256:6d79f659965290af60d6acc8e2716341865fe74609a7ede2a33c2f86ad893b8f", + "sha256:720963fee259291a88348ae9735d9deb5d334e84a016244f61c89f5a49aa400a", + "sha256:75a3b6e9c695a6570ea488db75b84bb592ff70a944957efa1c655867c575018b", + "sha256:792462abfeeca6cc8c6c1e6d27e14319682f0182f6b0ba37befe911af794db70", + "sha256:79ec4498a545733ecace48d780d22407411b07403a2e08b9a4d7596c0b97ebd7", + "sha256:7f14d6b303e55bd7d19b28309ef8014370e84a3806c5e452e078e7df7344d97a", + "sha256:7f65bd5d4cdb396295b6aa07f84ca659ac65c5c68b53956a6d95219e304b0ada", + "sha256:81c6b4a6c1af800d52a6fa32389ef8f4281583f4f99dc1a40f2bb47667281541", + "sha256:82a07de83dce09b4fa1bccbdc8bde8f188b131666af0dc9048ba0a0e448d8a3b", + "sha256:847c7f61964225fc489fe1d49eda7e0e0d253e98862c012cecf845f9ad45cdf4", + "sha256:84b52b2cf77bb7f703d16c4007b021078dbbe6cf8ffb57abe81a7bacfc175ef2", + "sha256:86685fa04067f7175f9718489ae755f6acde03593a1a9ca89305554af40e14fd", + "sha256:8a9c962c64a4c08def58b9799333e33af94ec53038cf151d36edacdb41f81646", + "sha256:8cbd4bfc933b33b85c43ef4c1f4d5e3e9d91975ea6368acf5fbac02bac06ea89", + "sha256:8ffe660e963ae711cb9e2b8d8461c9b1ad6167823837fc17d59d5e539fb898fa", + "sha256:94652da1a4ca7cfb69c15dd6986b205e0bd9c63a05029c3b48b4201085f527bd", + "sha256:969fd67de8c42affdb47b38b80f1eaa79ac0ef17d65407cdd931db1675315af1", + "sha256:9858dcbc23ba7eaadcd319786b982278a1a2b2020720b19db43e309579ff76fb", + "sha256:99d25aff3745c54e61ab340b98400c52ebec04290a62078155e0d7eb30380220", + "sha256:99f55e14e7c56f4fafe1343480c32b110ef03836c21ff7c48bae7add6818f77c", + "sha256:9d35d8f8a1c9ed4e2b08187b513f8a3c71958600129db3aa26d85ea3abfd1310", + "sha256:a2ba92f59e30ce915e9e79af37649432e3a212ddddf416d4d686b1b4825bcdb2", + "sha256:a2cb35a6efaa0e3623d8272471371a12c7e07b51a33e5efce9b58f655d864b4e", + "sha256:a358277122456666a8b2a0b9aa04f1b89d34e8aa41d08a6557d693e6abb6667c", + "sha256:a60da2f9efbed355edb35a1fb6829148676786c829fad708bb6bb47211b3593a", + "sha256:aa7dec53c25f1949513457ef8b0ea1fb40e76c672cc4d2daa8ad3c8d6b73491a", + "sha256:b1572ee0eb1967e71787af636bb7d1eb9c6735d5337762c450650e7f51844594", + "sha256:b4f10d3f304be7183fac79bf2cd997f82e16aa9a9f37343d76c026c6e435a8a8", + "sha256:bbbbfbb7d039b20d289ce56b1beb46138d65769d04af50c199c6ac4cb6054d52", + "sha256:c394a3f055b49f92626f83c1a0b6d6cd2c628f1ccd72481c3e3c6aa4695f3b20", + "sha256:c396358023b876cff547ce87f4e8ff8a2280598873a137e8cc69e115262260b8", + "sha256:c5ba07e58fd98c9782201e79eb8dd4225733d212a5a3700f9a84d329bd0463a6", + "sha256:c764fb167411d5afaef88138542a4bfa28bd5e5ded5e8e42df87cef965efd6e9", + "sha256:cbba763d99de0255a3e4938f25a8579930ac8aa089233cb2fb2ed7d04d4aff02", + "sha256:cbd1660fb48827381ce3a621a4fdc237959e1cd4e98b098952a8f624a0726425", + "sha256:cd761d158f67e288fd0ebe00c3b158095ce80a4bc7c32b60c7121224003ba70d", + "sha256:cdfbb27f2c46bb5bbdcee147530cbc5ca8ab858d7693924e88e30ada21b2c5e2", + "sha256:d2dbe8a3baf2d842e342e8acb06ae3844765d38df67687c144cdeb71f1bcb5d7", + "sha256:d5c931ec1c03111718cabf85f6012bb2815fa0ce578175567fa8d6f2cc15d3b4", + "sha256:df6d7bf3e15b7e6e202a16ff4948a51759354016026deb04ab9b5acbbe35e096", + "sha256:dfbe2aa45b273f49e715c5345d94874cb65a28482bf231af408891c260601b8d", + "sha256:e12769d3adcc419e65860de946df8d2ed274932177ac1cdb05186e498aaa9149", + "sha256:e5aed4754895942ae15ffa48c52d181e1c1463236fda68d2dba29c03aa61786b", + "sha256:e645b4c365d6f1f9e0799380ad6395268f3c3b898244a650aaeb8d9d27b74c35", + "sha256:ed3493a369fe849cce98542d7405c88030b355e4d2e113887cb7ecc86c205773", + "sha256:f08342dc8d19214faa7ef99574dea6c37a2790d6d04a9793ef8fa76c188dc08d", + "sha256:f0a55cf02d2cdd739b40ce10c09bbdd520e141217696add7a48b56e67bdfdfe6", + "sha256:f0ce9d9e07c75da8027c62b4c9f45771d1d8aae7dc9ad7fb606c6a5aedbe9741", + "sha256:f1f723e260c35e1c7c57a09d3a6ebe681bd56c83e1208ae3ce1869b7c0d10d4f", + "sha256:f2fcbe9b3a5996b417e030aa33a562e7e20dfc86271e53d7e841fc5df16268b8", + "sha256:f3fd8df63c41ff6a676d031956aebf68ebbc687b47c507da25501eb22eec341f", + "sha256:f8d3417db5e14a6789073b21ae44439a755289477901901bae378a57b905e148", + "sha256:fbf05678c2ae0064fb1b8de7e9e8f0fc30621b73c8477786dd0fb3868044a8c8", + "sha256:fc98ff43abad61f00515ad9a06213b7716699146e46eabd256cdfe7cb522bd97", + "sha256:ff1863f037dad765ef5963efc2e37d399ac023e192a6f2bb394e2377d023cefe" + ], + "version": "==3.8.0" + }, + "bitstring": { + "hashes": [ + "sha256:69d1587f0ac18dc7d93fc7e80d5f447161a33e57027e726dc18a0a8bacf1711a", + "sha256:a08bc09d3857216d4c0f412a1611056f1cc2b64fd254fb1e8a0afba7cfa1a95a" + ], + "markers": "python_version >= '3.8'", + "version": "==4.3.1" + }, + "blinker": { + "hashes": [ + "sha256:b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf", + "sha256:ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc" + ], + "markers": "python_version >= '3.9'", + "version": "==1.9.0" + }, + "cffi": { + "hashes": [ + "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", + "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", + "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", + "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", + "sha256:0cf2d91ecc3fcc0625c2c530fe004f82c110405f101548512cce44322fa8ac44", + "sha256:0f6084a0ea23d05d20c3edcda20c3d006f9b6f3fefeac38f59262e10cef47ee2", + "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", + "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", + "sha256:1cd13c99ce269b3ed80b417dcd591415d3372bcac067009b6e0f59c7d4015e65", + "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e", + "sha256:1f72fb8906754ac8a2cc3f9f5aaa298070652a0ffae577e0ea9bd480dc3c931a", + "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", + "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", + "sha256:2081580ebb843f759b9f617314a24ed5738c51d2aee65d31e02f6f7a2b97707a", + "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe", + "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", + "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", + "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", + "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187", + "sha256:2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c", + "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", + "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94", + "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba", + "sha256:3e837e369566884707ddaf85fc1744b47575005c0a229de3327f8f9a20f4efeb", + "sha256:3f4d46d8b35698056ec29bca21546e1551a205058ae1a181d871e278b0b28165", + "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", + "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", + "sha256:4647afc2f90d1ddd33441e5b0e85b16b12ddec4fca55f0d9671fef036ecca27c", + "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6", + "sha256:53f77cbe57044e88bbd5ed26ac1d0514d2acf0591dd6bb02a3ae37f76811b80c", + "sha256:5eda85d6d1879e692d546a078b44251cdd08dd1cfb98dfb77b670c97cee49ea0", + "sha256:5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743", + "sha256:61d028e90346df14fedc3d1e5441df818d095f3b87d286825dfcbd6459b7ef63", + "sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5", + "sha256:6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5", + "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", + "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d", + "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", + "sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93", + "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", + "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", + "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", + "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", + "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", + "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037", + "sha256:8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26", + "sha256:89472c9762729b5ae1ad974b777416bfda4ac5642423fa93bd57a09204712322", + "sha256:8ea985900c5c95ce9db1745f7933eeef5d314f0565b27625d9a10ec9881e1bfb", + "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c", + "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", + "sha256:9332088d75dc3241c702d852d4671613136d90fa6881da7d770a483fd05248b4", + "sha256:94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414", + "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", + "sha256:9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664", + "sha256:a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9", + "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", + "sha256:b18a3ed7d5b3bd8d9ef7a8cb226502c6bf8308df1525e1cc676c3680e7176739", + "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", + "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062", + "sha256:b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe", + "sha256:b882b3df248017dba09d6b16defe9b5c407fe32fc7c65a9c69798e6175601be9", + "sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92", + "sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5", + "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", + "sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d", + "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", + "sha256:cb527a79772e5ef98fb1d700678fe031e353e765d1ca2d409c92263c6d43e09f", + "sha256:cf364028c016c03078a23b503f02058f1814320a56ad535686f90565636a9495", + "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", + "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", + "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", + "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", + "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5", + "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18", + "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", + "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", + "sha256:de8dad4425a6ca6e4e5e297b27b5c824ecc7581910bf9aee86cb6835e6812aa7", + "sha256:e11e82b744887154b182fd3e7e8512418446501191994dbf9c9fc1f32cc8efd5", + "sha256:e6e73b9e02893c764e7e8d5bb5ce277f1a009cd5243f8228f75f842bf937c534", + "sha256:f73b96c41e3b2adedc34a7356e64c8eb96e03a3782b535e043a986276ce12a49", + "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", + "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", + "sha256:fc7de24befaeae77ba923797c7c87834c73648a05a4bde34b3b7e5588973a453", + "sha256:fe562eb1a64e67dd297ccc4f5addea2501664954f2692b69a76449ec7913ecbf" + ], + "markers": "python_full_version >= '3.9' and platform_python_implementation != 'PyPy'", + "version": "==2.0.0" + }, + "click": { + "hashes": [ + "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", + "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6" + ], + "markers": "python_version >= '3.10'", + "version": "==8.3.1" + }, + "cryptography": { + "hashes": [ + "sha256:00a5e7e87938e5ff9ff5447ab086a5706a957137e6e433841e9d24f38a065217", + "sha256:01ca9ff2885f3acc98c29f1860552e37f6d7c7d013d7334ff2a9de43a449315d", + "sha256:09859af8466b69bc3c27bdf4f5d84a665e0f7ab5088412e9e2ec49758eca5cbc", + "sha256:0abf1ffd6e57c67e92af68330d05760b7b7efb243aab8377e583284dbab72c71", + "sha256:1000713389b75c449a6e979ffc7dcc8ac90b437048766cef052d4d30b8220971", + "sha256:109d4ddfadf17e8e7779c39f9b18111a09efb969a301a31e987416a0191ed93a", + "sha256:10b01676fc208c3e6feeb25a8b83d81767e8059e1fe86e1dc62d10a3018fa926", + "sha256:10ca84c4668d066a9878890047f03546f3ae0a6b8b39b697457b7757aaf18dbc", + "sha256:15ab9b093e8f09daab0f2159bb7e47532596075139dd74365da52ecc9cb46c5d", + "sha256:191bb60a7be5e6f54e30ba16fdfae78ad3a342a0599eb4193ba88e3f3d6e185b", + "sha256:22d7e97932f511d6b0b04f2bfd818d73dcd5928db509460aaf48384778eb6d20", + "sha256:23b1a8f26e43f47ceb6d6a43115f33a5a37d57df4ea0ca295b780ae8546e8044", + "sha256:36e627112085bb3b81b19fed209c05ce2a52ee8b15d161b7c643a7d5a88491f3", + "sha256:39b6755623145ad5eff1dab323f4eae2a32a77a7abef2c5089a04a3d04366715", + "sha256:3b51b8ca4f1c6453d8829e1eb7299499ca7f313900dd4d89a24b8b87c0a780d4", + "sha256:402b58fc32614f00980b66d6e56a5b4118e6cb362ae8f3fda141ba4689bd4506", + "sha256:416260257577718c05135c55958b674000baef9a1c7d9e8f306ec60d71db850f", + "sha256:46acf53b40ea38f9c6c229599a4a13f0d46a6c3fa9ef19fc1a124d62e338dfa0", + "sha256:4b7387121ac7d15e550f5cb4a43aef2559ed759c35df7336c402bb8275ac9683", + "sha256:50fc3343ac490c6b08c0cf0d704e881d0d660be923fd3076db3e932007e726e3", + "sha256:516ea134e703e9fe26bcd1277a4b59ad30586ea90c365a87781d7887a646fe21", + "sha256:549e234ff32571b1f4076ac269fcce7a808d3bf98b76c8dd560e42dbc66d7d91", + "sha256:5d7f93296ee28f68447397bf5198428c9aeeab45705a55d53a6343455dcb2c3c", + "sha256:5ecfccd2329e37e9b7112a888e76d9feca2347f12f37918facbb893d7bb88ee8", + "sha256:6276eb85ef938dc035d59b87c8a7dc559a232f954962520137529d77b18ff1df", + "sha256:6b5063083824e5509fdba180721d55909ffacccc8adbec85268b48439423d78c", + "sha256:6eae65d4c3d33da080cff9c4ab1f711b15c1d9760809dad6ea763f3812d254cb", + "sha256:6f61efb26e76c45c4a227835ddeae96d83624fb0d29eb5df5b96e14ed1a0afb7", + "sha256:71e842ec9bc7abf543b47cf86b9a743baa95f4677d22baa4c7d5c69e49e9bc04", + "sha256:760f83faa07f8b64e9c33fc963d790a2edb24efb479e3520c14a45741cd9b2db", + "sha256:78a97cf6a8839a48c49271cdcbd5cf37ca2c1d6b7fdd86cc864f302b5e9bf459", + "sha256:7ce938a99998ed3c8aa7e7272dca1a610401ede816d36d0693907d863b10d9ea", + "sha256:8a6e050cb6164d3f830453754094c086ff2d0b2f3a897a1d9820f6139a1f0914", + "sha256:9394673a9f4de09e28b5356e7fff97d778f8abad85c9d5ac4a4b7e25a0de7717", + "sha256:94cd0549accc38d1494e1f8de71eca837d0509d0d44bf11d158524b0e12cebf9", + "sha256:a04bee9ab6a4da801eb9b51f1b708a1b5b5c9eb48c03f74198464c66f0d344ac", + "sha256:a23582810fedb8c0bc47524558fb6c56aac3fc252cb306072fd2815da2a47c32", + "sha256:a2c0cd47381a3229c403062f764160d57d4d175e022c1df84e168c6251a22eec", + "sha256:a8b17438104fed022ce745b362294d9ce35b4c2e45c1d958ad4a4b019285f4a1", + "sha256:a9a3008438615669153eb86b26b61e09993921ebdd75385ddd748702c5adfddb", + "sha256:b02cf04496f6576afffef5ddd04a0cb7d49cf6be16a9059d793a30b035f6b6ac", + "sha256:b419ae593c86b87014b9be7396b385491ad7f320bde96826d0dd174459e54665", + "sha256:c0a7bb1a68a5d3471880e264621346c48665b3bf1c3759d682fc0864c540bd9e", + "sha256:c70cc23f12726be8f8bc72e41d5065d77e4515efae3690326764ea1b07845cfb", + "sha256:c8daeb2d2174beb4575b77482320303f3d39b8e81153da4f0fb08eb5fe86a6c5", + "sha256:cb3d760a6117f621261d662bccc8ef5bc32ca673e037c83fbe565324f5c46936", + "sha256:d55f3dffadd674514ad19451161118fd010988540cee43d8bc20675e775925de", + "sha256:d89c3468de4cdc4f08a57e214384d0471911a3830fcdaf7a8cc587e42a866372", + "sha256:db391fa7c66df6762ee3f00c95a89e6d428f4d60e7abc8328f4fe155b5ac6e54", + "sha256:dfb781ff7eaa91a6f7fd41776ec37c5853c795d3b358d4896fdbb5df168af422", + "sha256:e5bf0ed4490068a2e72ac03d786693adeb909981cc596425d09032d372bcc849", + "sha256:e7aec276d68421f9574040c26e2a7c3771060bc0cff408bae1dcb19d3ab1e63c", + "sha256:ef639cb3372f69ec44915fafcd6698b6cc78fbe0c2ea41be867f6ed612811963", + "sha256:f260d0d41e9b4da1ed1e0f1ce571f97fe370b152ab18778e9e8f67d6af432018" + ], + "markers": "python_version >= '3.8' and python_full_version not in '3.9.0, 3.9.1'", + "version": "==46.0.3" + }, + "esptool": { + "hashes": [ + "sha256:2ea9bcd7eb263d380a4fe0170856a10e4c65e3f38c757ebdc73584c8dd8322da" + ], + "index": "pypi", + "markers": "python_version >= '3.10'", + "version": "==5.1.0" + }, + "fastapi": { + "hashes": [ + "sha256:0503b7b7bc71bc98f7c90c9117d21fdf6147c0d74703011b87936becc86985c1", + "sha256:624d384d7cda7c096449c889fc776a0571948ba14c3c929fa8e9a78cd0b0a6a8" + ], + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==0.123.10" + }, + "flask": { + "hashes": [ + "sha256:bf656c15c80190ed628ad08cdfd3aaa35beb087855e2f494910aa3774cc4fd87", + "sha256:ca1d8112ec8a6158cc29ea4858963350011b5c846a414cdb7a954aa9e967d03c" + ], + "index": "pypi", + "markers": "python_version >= '3.9'", + "version": "==3.1.2" + }, + "h11": { + "hashes": [ + "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", + "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86" + ], + "markers": "python_version >= '3.8'", + "version": "==0.16.0" + }, + "idna": { + "hashes": [ + "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", + "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902" + ], + "markers": "python_version >= '3.8'", + "version": "==3.11" + }, + "intelhex": { + "hashes": [ + "sha256:87cc5225657524ec6361354be928adfd56bcf2a3dcc646c40f8f094c39c07db4", + "sha256:892b7361a719f4945237da8ccf754e9513db32f5628852785aea108dcd250093" + ], + "version": "==2.3.0" + }, + "itsdangerous": { + "hashes": [ + "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef", + "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173" + ], + "markers": "python_version >= '3.8'", + "version": "==2.2.0" + }, + "jinja2": { + "hashes": [ + "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", + "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67" + ], + "markers": "python_version >= '3.7'", + "version": "==3.1.6" + }, + "markdown-it-py": { + "hashes": [ + "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", + "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3" + ], + "markers": "python_version >= '3.10'", + "version": "==4.0.0" + }, + "markupsafe": { + "hashes": [ + "sha256:0303439a41979d9e74d18ff5e2dd8c43ed6c6001fd40e5bf2e43f7bd9bbc523f", + "sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a", + "sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf", + "sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19", + "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", + "sha256:0f4b68347f8c5eab4a13419215bdfd7f8c9b19f2b25520968adfad23eb0ce60c", + "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", + "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", + "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", + "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", + "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", + "sha256:15d939a21d546304880945ca1ecb8a039db6b4dc49b2c5a400387cdae6a62e26", + "sha256:177b5253b2834fe3678cb4a5f0059808258584c559193998be2601324fdeafb1", + "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", + "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", + "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", + "sha256:1ba88449deb3de88bd40044603fafffb7bc2b055d626a330323a9ed736661695", + "sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad", + "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", + "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", + "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", + "sha256:2a15a08b17dd94c53a1da0438822d70ebcd13f8c3a95abe3a9ef9f11a94830aa", + "sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559", + "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", + "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", + "sha256:3537e01efc9d4dccdf77221fb1cb3b8e1a38d5428920e0657ce299b20324d758", + "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", + "sha256:38664109c14ffc9e7437e86b4dceb442b0096dfe3541d7864d9cbe1da4cf36c8", + "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", + "sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c", + "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", + "sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a", + "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", + "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", + "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", + "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", + "sha256:591ae9f2a647529ca990bc681daebdd52c8791ff06c2bfa05b65163e28102ef2", + "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", + "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", + "sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50", + "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", + "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", + "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", + "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", + "sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115", + "sha256:7c3fb7d25180895632e5d3148dbdc29ea38ccb7fd210aa27acbd1201a1902c6e", + "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", + "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", + "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", + "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", + "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", + "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", + "sha256:949b8d66bc381ee8b007cd945914c721d9aba8e27f71959d750a46f7c282b20b", + "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", + "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", + "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", + "sha256:a320721ab5a1aba0a233739394eb907f8c8da5c98c9181d1161e77a0c8e36f2d", + "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", + "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", + "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", + "sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f", + "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", + "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", + "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", + "sha256:c0c0b3ade1c0b13b936d7970b1d37a57acde9199dc2aecc4c336773e1d86049c", + "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", + "sha256:c4ffb7ebf07cfe8931028e3e4c85f0357459a3f9f9490886198848f4fa002ec8", + "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", + "sha256:d2ee202e79d8ed691ceebae8e0486bd9a2cd4794cec4824e1c99b6f5009502f6", + "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", + "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", + "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", + "sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01", + "sha256:df2449253ef108a379b8b5d6b43f4b1a8e81a061d6537becd5582fba5f9196d7", + "sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419", + "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", + "sha256:e2103a929dfa2fcaf9bb4e7c091983a49c9ac3b19c9061b6d5427dd7d14d81a1", + "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", + "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", + "sha256:e8fc20152abba6b83724d7ff268c249fa196d8259ff481f3b1476383f8f24e42", + "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", + "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", + "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", + "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", + "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", + "sha256:f42d0984e947b8adf7dd6dde396e720934d12c506ce84eea8476409563607591", + "sha256:f71a396b3bf33ecaa1626c255855702aca4d3d9fea5e051b41ac59a9c1c41edc", + "sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a", + "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50" + ], + "markers": "python_version >= '3.9'", + "version": "==3.0.3" + }, + "mdurl": { + "hashes": [ + "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", + "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba" + ], + "markers": "python_version >= '3.7'", + "version": "==0.1.2" + }, + "mpremote": { + "hashes": [ + "sha256:39251644305be718c52bc5965315adc4ae824901750abf6a3fb63683234df05c", + "sha256:61a39bf5af502e1ec56d1b28bf067766c3a0daea9d7487934cb472e378a12fe1" + ], + "index": "pypi", + "markers": "python_version >= '3.4'", + "version": "==1.26.1" + }, + "platformdirs": { + "hashes": [ + "sha256:61d5cdcc6065745cdd94f0f878977f8de9437be93de97c1c12f853c9c0cdcbda", + "sha256:d03afa3963c806a9bed9d5125c8f4cb2fdaf74a55ab60e5d59b3fde758104d31" + ], + "markers": "python_version >= '3.10'", + "version": "==4.5.1" + }, + "pycparser": { + "hashes": [ + "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2", + "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934" + ], + "markers": "implementation_name != 'PyPy'", + "version": "==2.23" + }, + "pydantic": { + "hashes": [ + "sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49", + "sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d" + ], + "markers": "python_version >= '3.9'", + "version": "==2.12.5" + }, + "pydantic-core": { + "hashes": [ + "sha256:0177272f88ab8312479336e1d777f6b124537d47f2123f89cb37e0accea97f90", + "sha256:01a3d0ab748ee531f4ea6c3e48ad9dac84ddba4b0d82291f87248f2f9de8d740", + "sha256:0384e2e1021894b1ff5a786dbf94771e2986ebe2869533874d7e43bc79c6f504", + "sha256:03b77d184b9eb40240ae9fd676ca364ce1085f203e1b1256f8ab9984dca80a84", + "sha256:03ca43e12fab6023fc79d28ca6b39b05f794ad08ec2feccc59a339b02f2b3d33", + "sha256:05a2c8852530ad2812cb7914dc61a1125dc4e06252ee98e5638a12da6cc6fb6c", + "sha256:070259a8818988b9a84a449a2a7337c7f430a22acc0859c6b110aa7212a6d9c0", + "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e", + "sha256:0cbaad15cb0c90aa221d43c00e77bb33c93e8d36e0bf74760cd00e732d10a6a0", + "sha256:100baa204bb412b74fe285fb0f3a385256dad1d1879f0a5cb1499ed2e83d132a", + "sha256:112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34", + "sha256:16f80f7abe3351f8ea6858914ddc8c77e02578544a0ebc15b4c2e1a0e813b0b2", + "sha256:1746d4a3d9a794cacae06a5eaaccb4b8643a131d45fbc9af23e353dc0a5ba5c3", + "sha256:1962293292865bca8e54702b08a4f26da73adc83dd1fcf26fbc875b35d81c815", + "sha256:1d1d9764366c73f996edd17abb6d9d7649a7eb690006ab6adbda117717099b14", + "sha256:1f8d33a7f4d5a7889e60dc39856d76d09333d8a6ed0f5f1190635cbec70ec4ba", + "sha256:22f0fb8c1c583a3b6f24df2470833b40207e907b90c928cc8d3594b76f874375", + "sha256:239edca560d05757817c13dc17c50766136d21f7cd0fac50295499ae24f90fdf", + "sha256:242a206cd0318f95cd21bdacff3fcc3aab23e79bba5cac3db5a841c9ef9c6963", + "sha256:25e1c2af0fce638d5f1988b686f3b3ea8cd7de5f244ca147c777769e798a9cd1", + "sha256:266fb4cbf5e3cbd0b53669a6d1b039c45e3ce651fd5442eff4d07c2cc8d66808", + "sha256:2782c870e99878c634505236d81e5443092fba820f0373997ff75f90f68cd553", + "sha256:287dad91cfb551c363dc62899a80e9e14da1f0e2b6ebde82c806612ca2a13ef1", + "sha256:29452c56df2ed968d18d7e21f4ab0ac55e71dc59524872f6fc57dcf4a3249ed2", + "sha256:299e0a22e7ae2b85c1a57f104538b2656e8ab1873511fd718a1c1c6f149b77b5", + "sha256:2a5e06546e19f24c6a96a129142a75cee553cc018ffee48a460059b1185f4470", + "sha256:2b761d210c9ea91feda40d25b4efe82a1707da2ef62901466a42492c028553a2", + "sha256:2c010c6ded393148374c0f6f0bf89d206bf3217f201faa0635dcd56bd1520f6b", + "sha256:2ff4321e56e879ee8d2a879501c8e469414d948f4aba74a2d4593184eb326660", + "sha256:3006c3dd9ba34b0c094c544c6006cc79e87d8612999f1a5d43b769b89181f23c", + "sha256:33cb885e759a705b426baada1fe68cbb0a2e68e34c5d0d0289a364cf01709093", + "sha256:346285d28e4c8017da95144c7f3acd42740d637ff41946af5ce6e5e420502dd5", + "sha256:34a64bc3441dc1213096a20fe27e8e128bd3ff89921706e83c0b1ac971276594", + "sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008", + "sha256:378bec5c66998815d224c9ca994f1e14c0c21cb95d2f52b6021cc0b2a58f2a5a", + "sha256:3f37a19d7ebcdd20b96485056ba9e8b304e27d9904d233d7b1015db320e51f0a", + "sha256:3f84d5c1b4ab906093bdc1ff10484838aca54ef08de4afa9de0f5f14d69639cd", + "sha256:4009935984bd36bd2c774e13f9a09563ce8de4abaa7226f5108262fa3e637284", + "sha256:406bf18d345822d6c21366031003612b9c77b3e29ffdb0f612367352aab7d586", + "sha256:4819fa52133c9aa3c387b3328f25c1facc356491e6135b459f1de698ff64d869", + "sha256:482c982f814460eabe1d3bb0adfdc583387bd4691ef00b90575ca0d2b6fe2294", + "sha256:4bc36bbc0b7584de96561184ad7f012478987882ebf9f9c389b23f432ea3d90f", + "sha256:506d766a8727beef16b7adaeb8ee6217c64fc813646b424d0804d67c16eddb66", + "sha256:56121965f7a4dc965bff783d70b907ddf3d57f6eba29b6d2e5dabfaf07799c51", + "sha256:58133647260ea01e4d0500089a8c4f07bd7aa6ce109682b1426394988d8aaacc", + "sha256:5921a4d3ca3aee735d9fd163808f5e8dd6c6972101e4adbda9a4667908849b97", + "sha256:5a4e67afbc95fa5c34cf27d9089bca7fcab4e51e57278d710320a70b956d1b9a", + "sha256:5cb1b2f9742240e4bb26b652a5aeb840aa4b417c7748b6f8387927bc6e45e40d", + "sha256:62de39db01b8d593e45871af2af9e497295db8d73b085f6bfd0b18c83c70a8f9", + "sha256:634e8609e89ceecea15e2d61bc9ac3718caaaa71963717bf3c8f38bfde64242c", + "sha256:63510af5e38f8955b8ee5687740d6ebf7c2a0886d15a6d65c32814613681bc07", + "sha256:650ae77860b45cfa6e2cdafc42618ceafab3a2d9a3811fcfbd3bbf8ac3c40d36", + "sha256:6561e94ba9dacc9c61bce40e2d6bdc3bfaa0259d3ff36ace3b1e6901936d2e3e", + "sha256:65840751b72fbfd82c3c640cff9284545342a4f1eb1586ad0636955b261b0b05", + "sha256:6cb58b9c66f7e4179a2d5e0f849c48eff5c1fca560994d6eb6543abf955a149e", + "sha256:6f52298fbd394f9ed112d56f3d11aabd0d5bd27beb3084cc3d8ad069483b8941", + "sha256:707625ef0983fcfb461acfaf14de2067c5942c6bb0f3b4c99158bed6fedd3cf3", + "sha256:72f6c8b11857a856bcfa48c86f5368439f74453563f951e473514579d44aa612", + "sha256:753e230374206729bf0a807954bcc6c150d3743928a73faffee51ac6557a03c3", + "sha256:76d0819de158cd855d1cbb8fcafdf6f5cf1eb8e470abe056d5d161106e38062b", + "sha256:76ee27c6e9c7f16f47db7a94157112a2f3a00e958bc626e2f4ee8bec5c328fbe", + "sha256:77b63866ca88d804225eaa4af3e664c5faf3568cea95360d21f4725ab6e07146", + "sha256:79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11", + "sha256:7b93a4d08587e2b7e7882de461e82b6ed76d9026ce91ca7915e740ecc7855f60", + "sha256:7da7087d756b19037bc2c06edc6c170eeef3c3bafcb8f532ff17d64dc427adfd", + "sha256:7f3bf998340c6d4b0c9a2f02d6a400e51f123b59565d74dc60d252ce888c260b", + "sha256:80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c", + "sha256:8566def80554c3faa0e65ac30ab0932b9e3a5cd7f8323764303d468e5c37595a", + "sha256:873e0d5b4fb9b89ef7c2d2a963ea7d02879d9da0da8d9d4933dee8ee86a8b460", + "sha256:88942d3a3dff3afc8288c21e565e476fc278902ae4d6d134f1eeda118cc830b1", + "sha256:8bfeaf8735be79f225f3fefab7f941c712aaca36f1128c9d7e2352ee1aa87bdf", + "sha256:8e7c86f27c585ef37c35e56a96363ab8de4e549a95512445b85c96d3e2f7c1bf", + "sha256:915c3d10f81bec3a74fbd4faebe8391013ba61e5a1a8d48c4455b923bdda7858", + "sha256:93e8740d7503eb008aa2df04d3b9735f845d43ae845e6dcd2be0b55a2da43cd2", + "sha256:941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9", + "sha256:97aeba56665b4c3235a0e52b2c2f5ae9cd071b8a8310ad27bddb3f7fb30e9aa2", + "sha256:a39455728aabd58ceabb03c90e12f71fd30fa69615760a075b9fec596456ccc3", + "sha256:a3a52f6156e73e7ccb0f8cced536adccb7042be67cb45f9562e12b319c119da6", + "sha256:a668ce24de96165bb239160b3d854943128f4334822900534f2fe947930e5770", + "sha256:a75dafbf87d6276ddc5b2bf6fae5254e3d0876b626eb24969a574fff9149ee5d", + "sha256:aabf5777b5c8ca26f7824cb4a120a740c9588ed58df9b2d196ce92fba42ff8dc", + "sha256:aec5cf2fd867b4ff45b9959f8b20ea3993fc93e63c7363fe6851424c8a7e7c23", + "sha256:b2379fa7ed44ddecb5bfe4e48577d752db9fc10be00a6b7446e9663ba143de26", + "sha256:b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa", + "sha256:b5819cd790dbf0c5eb9f82c73c16b39a65dd6dd4d1439dcdea7816ec9adddab8", + "sha256:b74557b16e390ec12dca509bce9264c3bbd128f8a2c376eaa68003d7f327276d", + "sha256:b80aa5095cd3109962a298ce14110ae16b8c1aece8b72f9dafe81cf597ad80b3", + "sha256:b93590ae81f7010dbe380cdeab6f515902ebcbefe0b9327cc4804d74e93ae69d", + "sha256:b96d5f26b05d03cc60f11a7761a5ded1741da411e7fe0909e27a5e6a0cb7b034", + "sha256:bd3d54f38609ff308209bd43acea66061494157703364ae40c951f83ba99a1a9", + "sha256:bfea2a5f0b4d8d43adf9d7b8bf019fb46fdd10a2e5cde477fbcb9d1fa08c68e1", + "sha256:c007fe8a43d43b3969e8469004e9845944f1a80e6acd47c150856bb87f230c56", + "sha256:c1df3d34aced70add6f867a8cf413e299177e0c22660cc767218373d0779487b", + "sha256:c23e27686783f60290e36827f9c626e63154b82b116d7fe9adba1fda36da706c", + "sha256:c8d8b4eb992936023be7dee581270af5c6e0697a8559895f527f5b7105ecd36a", + "sha256:c9e19dd6e28fdcaa5a1de679aec4141f691023916427ef9bae8584f9c2fb3b0e", + "sha256:d0d2568a8c11bf8225044aa94409e21da0cb09dcdafe9ecd10250b2baad531a9", + "sha256:d38548150c39b74aeeb0ce8ee1d8e82696f4a4e16ddc6de7b1d8823f7de4b9b5", + "sha256:d3a978c4f57a597908b7e697229d996d77a6d3c94901e9edee593adada95ce1a", + "sha256:d5160812ea7a8a2ffbe233d8da666880cad0cbaf5d4de74ae15c313213d62556", + "sha256:dc799088c08fa04e43144b164feb0c13f9a0bc40503f8df3e9fde58a3c0c101e", + "sha256:df3959765b553b9440adfd3c795617c352154e497a4eaf3752555cfb5da8fc49", + "sha256:dfa8a0c812ac681395907e71e1274819dec685fec28273a28905df579ef137e2", + "sha256:e25c479382d26a2a41b7ebea1043564a937db462816ea07afa8a44c0866d52f9", + "sha256:e4f4a984405e91527a0d62649ee21138f8e3d0ef103be488c1dc11a80d7f184b", + "sha256:e536c98a7626a98feb2d3eaf75944ef6f3dbee447e1f841eae16f2f0a72d8ddc", + "sha256:e56ba91f47764cc14f1daacd723e3e82d1a89d783f0f5afe9c364b8bb491ccdb", + "sha256:e672ba74fbc2dc8eea59fb6d4aed6845e6905fc2a8afe93175d94a83ba2a01a0", + "sha256:e7b576130c69225432866fe2f4a469a85a54ade141d96fd396dffcf607b558f8", + "sha256:e8465ab91a4bd96d36dde3263f06caa6a8a6019e4113f24dc753d79a8b3a3f82", + "sha256:e96cea19e34778f8d59fe40775a7a574d95816eb150850a85a7a4c8f4b94ac69", + "sha256:ece5c59f0ce7d001e017643d8d24da587ea1f74f6993467d85ae8a5ef9d4f42b", + "sha256:eceb81a8d74f9267ef4081e246ffd6d129da5d87e37a77c9bde550cb04870c1c", + "sha256:ed2e99c456e3fadd05c991f8f437ef902e00eedf34320ba2b0842bd1c3ca3a75", + "sha256:f0cd744688278965817fd0839c4a4116add48d23890d468bc436f78beb28abf5", + "sha256:f14f8f046c14563f8eb3f45f499cc658ab8d10072961e07225e507adb700e93f", + "sha256:f15489ba13d61f670dcc96772e733aad1a6f9c429cc27574c6cdaed82d0146ad", + "sha256:f31d95a179f8d64d90f6831d71fa93290893a33148d890ba15de25642c5d075b", + "sha256:f41a7489d32336dbf2199c8c0a215390a751c5b014c2c1c5366e817202e9cdf7", + "sha256:f41eb9797986d6ebac5e8edff36d5cef9de40def462311b3eb3eeded1431e425", + "sha256:f547144f2966e1e16ae626d8ce72b4cfa0caedc7fa28052001c94fb2fcaa1c52" + ], + "markers": "python_version >= '3.9'", + "version": "==2.41.5" + }, + "pygments": { + "hashes": [ + "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", + "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b" + ], + "markers": "python_version >= '3.8'", + "version": "==2.19.2" + }, + "pyserial": { + "hashes": [ + "sha256:3c77e014170dfffbd816e6ffc205e9842efb10be9f58ec16d3e8675b4925cddb", + "sha256:c4451db6ba391ca6ca299fb3ec7bae67a5c55dde170964c7a14ceefec02f2cf0" + ], + "index": "pypi", + "version": "==3.5" + }, + "pyyaml": { + "hashes": [ + "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", + "sha256:0150219816b6a1fa26fb4699fb7daa9caf09eb1999f3b70fb6e786805e80375a", + "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", + "sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956", + "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", + "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c", + "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", + "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a", + "sha256:1ebe39cb5fc479422b83de611d14e2c0d3bb2a18bbcb01f229ab3cfbd8fee7a0", + "sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b", + "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", + "sha256:22ba7cfcad58ef3ecddc7ed1db3409af68d023b7f940da23c6c2a1890976eda6", + "sha256:27c0abcb4a5dac13684a37f76e701e054692a9b2d3064b70f5e4eb54810553d7", + "sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e", + "sha256:2e71d11abed7344e42a8849600193d15b6def118602c4c176f748e4583246007", + "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", + "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4", + "sha256:3c5677e12444c15717b902a5798264fa7909e41153cdf9ef7ad571b704a63dd9", + "sha256:3ff07ec89bae51176c0549bc4c63aa6202991da2d9a6129d7aef7f1407d3f295", + "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", + "sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0", + "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e", + "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", + "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", + "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", + "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", + "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", + "sha256:5cf4e27da7e3fbed4d6c3d8e797387aaad68102272f8f9752883bc32d61cb87b", + "sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69", + "sha256:5ed875a24292240029e4483f9d4a4b8a1ae08843b9c54f43fcc11e404532a8a5", + "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", + "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", + "sha256:6344df0d5755a2c9a276d4473ae6b90647e216ab4757f8426893b5dd2ac3f369", + "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", + "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824", + "sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198", + "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", + "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", + "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", + "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", + "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", + "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b", + "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00", + "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", + "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", + "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", + "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", + "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", + "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", + "sha256:9c57bb8c96f6d1808c030b1687b9b5fb476abaa47f0db9c0101f5e9f394e97f4", + "sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b", + "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf", + "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", + "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", + "sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8", + "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", + "sha256:b865addae83924361678b652338317d1bd7e79b1f4596f96b96c77a5a34b34da", + "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d", + "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", + "sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c", + "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", + "sha256:c2514fceb77bc5e7a2f7adfaa1feb2fb311607c9cb518dbc378688ec73d8292f", + "sha256:c3355370a2c156cffb25e876646f149d5d68f5e0a3ce86a5084dd0b64a994917", + "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", + "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", + "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", + "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", + "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", + "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", + "sha256:efd7b85f94a6f21e4932043973a7ba2613b059c4a000551892ac9f1d11f5baf3", + "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", + "sha256:fa160448684b4e94d80416c0fa4aac48967a969efe22931448d853ada8baf926", + "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0" + ], + "markers": "python_version >= '3.8'", + "version": "==6.0.3" + }, + "reedsolo": { + "hashes": [ + "sha256:2b6a3e402a1ee3e1eea3f932f81e6c0b7bbc615588074dca1dbbcdeb055002bd", + "sha256:c1359f02742751afe0f1c0de9f0772cc113835aa2855d2db420ea24393c87732" + ], + "version": "==1.7.0" + }, + "rich": { + "hashes": [ + "sha256:73ff50c7c0c1c77c8243079283f4edb376f0f6442433aecb8ce7e6d0b92d1fe4", + "sha256:76bc51fe2e57d2b1be1f96c524b890b816e334ab4c1e45888799bfaab0021edd" + ], + "markers": "python_full_version >= '3.8.0'", + "version": "==14.2.0" + }, + "rich-click": { + "hashes": [ + "sha256:af73dc68e85f3bebb80ce302a642b9fe3b65f3df0ceb42eb9a27c467c1b678c8", + "sha256:d70f39938bcecaf5543e8750828cbea94ef51853f7d0e174cda1e10543767389" + ], + "markers": "python_version >= '3.8'", + "version": "==1.9.4" + }, + "starlette": { + "hashes": [ + "sha256:9e5391843ec9b6e472eed1365a78c8098cfceb7a74bfd4d6b1c0c0095efb3bca", + "sha256:a2a17b22203254bcbc2e1f926d2d55f3f9497f769416b3190768befe598fa3ca" + ], + "markers": "python_version >= '3.10'", + "version": "==0.50.0" + }, + "typing-extensions": { + "hashes": [ + "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", + "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548" + ], + "markers": "python_version >= '3.9'", + "version": "==4.15.0" + }, + "typing-inspection": { + "hashes": [ + "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", + "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464" + ], + "markers": "python_version >= '3.9'", + "version": "==0.4.2" + }, + "uvicorn": { + "hashes": [ + "sha256:48c0afd214ceb59340075b4a052ea1ee91c16fbc2a9b1469cca0e54566977b02", + "sha256:fd97093bdd120a2609fc0d3afe931d4d4ad688b6e75f0f929fde1bc36fe0e91d" + ], + "index": "pypi", + "markers": "python_version >= '3.9'", + "version": "==0.38.0" + }, + "watchfiles": { + "hashes": [ + "sha256:00485f441d183717038ed2e887a7c868154f216877653121068107b227a2f64c", + "sha256:03fa0f5237118a0c5e496185cafa92878568b652a2e9a9382a5151b1a0380a43", + "sha256:04e78dd0b6352db95507fd8cb46f39d185cf8c74e4cf1e4fbad1d3df96faf510", + "sha256:059098c3a429f62fc98e8ec62b982230ef2c8df68c79e826e37b895bc359a9c0", + "sha256:08af70fd77eee58549cd69c25055dc344f918d992ff626068242259f98d598a2", + "sha256:0b495de0bb386df6a12b18335a0285dda90260f51bdb505503c02bcd1ce27a8b", + "sha256:130e4876309e8686a5e37dba7d5e9bc77e6ed908266996ca26572437a5271e18", + "sha256:14e0b1fe858430fc0251737ef3824c54027bedb8c37c38114488b8e131cf8219", + "sha256:17ef139237dfced9da49fb7f2232c86ca9421f666d78c264c7ffca6601d154c3", + "sha256:1a0bb430adb19ef49389e1ad368450193a90038b5b752f4ac089ec6942c4dff4", + "sha256:1db5d7ae38ff20153d542460752ff397fcf5c96090c1230803713cf3147a6803", + "sha256:28475ddbde92df1874b6c5c8aaeb24ad5be47a11f87cde5a28ef3835932e3e94", + "sha256:2edc3553362b1c38d9f06242416a5d8e9fe235c204a4072e988ce2e5bb1f69f6", + "sha256:30f7da3fb3f2844259cba4720c3fc7138eb0f7b659c38f3bfa65084c7fc7abce", + "sha256:311ff15a0bae3714ffb603e6ba6dbfba4065ab60865d15a6ec544133bdb21099", + "sha256:319b27255aacd9923b8a276bb14d21a5f7ff82564c744235fc5eae58d95422ae", + "sha256:35c53bd62a0b885bf653ebf6b700d1bf05debb78ad9292cf2a942b23513dc4c4", + "sha256:36193ed342f5b9842edd3532729a2ad55c4160ffcfa3700e0d54be496b70dd43", + "sha256:39574d6370c4579d7f5d0ad940ce5b20db0e4117444e39b6d8f99db5676c52fd", + "sha256:399600947b170270e80134ac854e21b3ccdefa11a9529a3decc1327088180f10", + "sha256:3a476189be23c3686bc2f4321dd501cb329c0a0469e77b7b534ee10129ae6374", + "sha256:3ad9fe1dae4ab4212d8c91e80b832425e24f421703b5a42ef2e4a1e215aff051", + "sha256:3bc570d6c01c206c46deb6e935a260be44f186a2f05179f52f7fcd2be086a94d", + "sha256:3dbd8cbadd46984f802f6d479b7e3afa86c42d13e8f0f322d669d79722c8ec34", + "sha256:3e6f39af2eab0118338902798b5aa6664f46ff66bc0280de76fca67a7f262a49", + "sha256:3f53fa183d53a1d7a8852277c92b967ae99c2d4dcee2bfacff8868e6e30b15f7", + "sha256:3f6d37644155fb5beca5378feb8c1708d5783145f2a0f1c4d5a061a210254844", + "sha256:3f7eb7da0eb23aa2ba036d4f616d46906013a68caf61b7fdbe42fc8b25132e77", + "sha256:3fa0b59c92278b5a7800d3ee7733da9d096d4aabcfabb9a928918bd276ef9b9b", + "sha256:421e29339983e1bebc281fab40d812742268ad057db4aee8c4d2bce0af43b741", + "sha256:4b943d3668d61cfa528eb949577479d3b077fd25fb83c641235437bc0b5bc60e", + "sha256:526e86aced14a65a5b0ec50827c745597c782ff46b571dbfe46192ab9e0b3c33", + "sha256:52e06553899e11e8074503c8e716d574adeeb7e68913115c4b3653c53f9bae42", + "sha256:544364b2b51a9b0c7000a4b4b02f90e9423d97fbbf7e06689236443ebcad81ab", + "sha256:5524298e3827105b61951a29c3512deb9578586abf3a7c5da4a8069df247cccc", + "sha256:55c7475190662e202c08c6c0f4d9e345a29367438cf8e8037f3155e10a88d5a5", + "sha256:563b116874a9a7ce6f96f87cd0b94f7faf92d08d0021e837796f0a14318ef8da", + "sha256:57ca5281a8b5e27593cb7d82c2ac927ad88a96ed406aa446f6344e4328208e9e", + "sha256:5c85794a4cfa094714fb9c08d4a218375b2b95b8ed1666e8677c349906246c05", + "sha256:5f3bde70f157f84ece3765b42b4a52c6ac1a50334903c6eaf765362f6ccca88a", + "sha256:5f3f58818dc0b07f7d9aa7fe9eb1037aecb9700e63e1f6acfed13e9fef648f5d", + "sha256:5fac835b4ab3c6487b5dbad78c4b3724e26bcc468e886f8ba8cc4306f68f6701", + "sha256:620bae625f4cb18427b1bb1a2d9426dc0dd5a5ba74c7c2cdb9de405f7b129863", + "sha256:672b8adf25b1a0d35c96b5888b7b18699d27d4194bac8beeae75be4b7a3fc9b2", + "sha256:6aae418a8b323732fa89721d86f39ec8f092fc2af67f4217a2b07fd3e93c6101", + "sha256:6c3631058c37e4a0ec440bf583bc53cdbd13e5661bb6f465bc1d88ee9a0a4d02", + "sha256:6c9c9262f454d1c4d8aaa7050121eb4f3aea197360553699520767daebf2180b", + "sha256:6e43d39a741e972bab5d8100b5cdacf69db64e34eb19b6e9af162bccf63c5cc6", + "sha256:7365b92c2e69ee952902e8f70f3ba6360d0d596d9299d55d7d386df84b6941fb", + "sha256:743185e7372b7bc7c389e1badcc606931a827112fbbd37f14c537320fca08620", + "sha256:74472234c8370669850e1c312490f6026d132ca2d396abfad8830b4f1c096957", + "sha256:74d5012b7630714b66be7b7b7a78855ef7ad58e8650c73afc4c076a1f480a8d6", + "sha256:77a13aea58bc2b90173bc69f2a90de8e282648939a00a602e1dc4ee23e26b66d", + "sha256:79ff6c6eadf2e3fc0d7786331362e6ef1e51125892c75f1004bd6b52155fb956", + "sha256:831a62658609f0e5c64178211c942ace999517f5770fe9436be4c2faeba0c0ef", + "sha256:836398932192dae4146c8f6f737d74baeac8b70ce14831a239bdb1ca882fc261", + "sha256:842178b126593addc05acf6fce960d28bc5fae7afbaa2c6c1b3a7b9460e5be02", + "sha256:8526e8f916bb5b9a0a777c8317c23ce65de259422bba5b31325a6fa6029d33af", + "sha256:859e43a1951717cc8de7f4c77674a6d389b106361585951d9e69572823f311d9", + "sha256:88863fbbc1a7312972f1c511f202eb30866370ebb8493aef2812b9ff28156a21", + "sha256:89eef07eee5e9d1fda06e38822ad167a044153457e6fd997f8a858ab7564a336", + "sha256:8c89f9f2f740a6b7dcc753140dd5e1ab9215966f7a3530d0c0705c83b401bd7d", + "sha256:8c91ed27800188c2ae96d16e3149f199d62f86c7af5f5f4d2c61a3ed8cd3666c", + "sha256:8ca65483439f9c791897f7db49202301deb6e15fe9f8fe2fed555bf986d10c31", + "sha256:8fbe85cb3201c7d380d3d0b90e63d520f15d6afe217165d7f98c9c649654db81", + "sha256:91d4c9a823a8c987cce8fa2690923b069966dabb196dd8d137ea2cede885fde9", + "sha256:9bb9f66367023ae783551042d31b1d7fd422e8289eedd91f26754a66f44d5cff", + "sha256:a173cb5c16c4f40ab19cecf48a534c409f7ea983ab8fed0741304a1c0a31b3f2", + "sha256:a36d8efe0f290835fd0f33da35042a1bb5dc0e83cbc092dcf69bce442579e88e", + "sha256:a55f3e9e493158d7bfdb60a1165035f1cf7d320914e7b7ea83fe22c6023b58fc", + "sha256:a625815d4a2bdca61953dbba5a39d60164451ef34c88d751f6c368c3ea73d404", + "sha256:a916a2932da8f8ab582f242c065f5c81bed3462849ca79ee357dd9551b0e9b01", + "sha256:ac3cc5759570cd02662b15fbcd9d917f7ecd47efe0d6b40474eafd246f91ea18", + "sha256:acb08650863767cbc58bca4813b92df4d6c648459dcaa3d4155681962b2aa2d3", + "sha256:aebfd0861a83e6c3d1110b78ad54704486555246e542be3e2bb94195eabb2606", + "sha256:afaeff7696e0ad9f02cbb8f56365ff4686ab205fcf9c4c5b6fdfaaa16549dd04", + "sha256:b27cf2eb1dda37b2089e3907d8ea92922b673c0c427886d4edc6b94d8dfe5db3", + "sha256:b2cd9e04277e756a2e2d2543d65d1e2166d6fd4c9b183f8808634fda23f17b14", + "sha256:b9c4702f29ca48e023ffd9b7ff6b822acdf47cb1ff44cb490a3f1d5ec8987e9c", + "sha256:bbe1ef33d45bc71cf21364df962af171f96ecaeca06bd9e3d0b583efb12aec82", + "sha256:bd404be08018c37350f0d6e34676bd1e2889990117a2b90070b3007f172d0610", + "sha256:bf0a91bfb5574a2f7fc223cf95eeea79abfefa404bf1ea5e339c0c1560ae99a0", + "sha256:bfb5862016acc9b869bb57284e6cb35fdf8e22fe59f7548858e2f971d045f150", + "sha256:bfff9740c69c0e4ed32416f013f3c45e2ae42ccedd1167ef2d805c000b6c71a5", + "sha256:c1f5210f1b8fc91ead1283c6fd89f70e76fb07283ec738056cf34d51e9c1d62c", + "sha256:c2047d0b6cea13b3316bdbafbfa0c4228ae593d995030fda39089d36e64fc03a", + "sha256:c22c776292a23bfc7237a98f791b9ad3144b02116ff10d820829ce62dff46d0b", + "sha256:c755367e51db90e75b19454b680903631d41f9e3607fbd941d296a020c2d752d", + "sha256:c882d69f6903ef6092bedfb7be973d9319940d56b8427ab9187d1ecd73438a70", + "sha256:cb467c999c2eff23a6417e58d75e5828716f42ed8289fe6b77a7e5a91036ca70", + "sha256:cdab464fee731e0884c35ae3588514a9bcf718d0e2c82169c1c4a85cc19c3c7f", + "sha256:ce19e06cbda693e9e7686358af9cd6f5d61312ab8b00488bc36f5aabbaf77e24", + "sha256:ce70f96a46b894b36eba678f153f052967a0d06d5b5a19b336ab0dbbd029f73e", + "sha256:cf57a27fb986c6243d2ee78392c503826056ffe0287e8794503b10fb51b881be", + "sha256:d1715143123baeeaeadec0528bb7441103979a1d5f6fd0e1f915383fea7ea6d5", + "sha256:d6ff426a7cb54f310d51bfe83fe9f2bbe40d540c741dc974ebc30e6aa238f52e", + "sha256:d7e7067c98040d646982daa1f37a33d3544138ea155536c2e0e63e07ff8a7e0f", + "sha256:db476ab59b6765134de1d4fe96a1a9c96ddf091683599be0f26147ea1b2e4b88", + "sha256:dcc5c24523771db3a294c77d94771abcfcb82a0e0ee8efd910c37c59ec1b31bb", + "sha256:de6da501c883f58ad50db3a32ad397b09ad29865b5f26f64c24d3e3281685849", + "sha256:e84087b432b6ac94778de547e08611266f1f8ffad28c0ee4c82e028b0fc5966d", + "sha256:eef58232d32daf2ac67f42dea51a2c80f0d03379075d44a587051e63cc2e368c", + "sha256:f096076119da54a6080e8920cbdaac3dbee667eb91dcc5e5b78840b87415bd44", + "sha256:f0ab1c1af0cb38e3f598244c17919fb1a84d1629cc08355b0074b6d7f53138ac", + "sha256:f27db948078f3823a6bb3b465180db8ebecf26dd5dae6f6180bd87383b6b4428", + "sha256:f537afb3276d12814082a2e9b242bdcf416c2e8fd9f799a737990a1dbe906e5b", + "sha256:f57b396167a2565a4e8b5e56a5a1c537571733992b226f4f1197d79e94cf0ae5", + "sha256:f8979280bdafff686ba5e4d8f97840f929a87ed9cdf133cbbd42f7766774d2aa", + "sha256:f9a2ae5c91cecc9edd47e041a930490c31c3afb1f5e6d71de3dc671bfaca02bf" + ], + "index": "pypi", + "markers": "python_version >= '3.9'", + "version": "==1.1.1" + }, + "werkzeug": { + "hashes": [ + "sha256:2ad50fb9ed09cc3af22c54698351027ace879a0b60a3b5edf5730b2f7d876905", + "sha256:cd3cd98b1b92dc3b7b3995038826c68097dcb16f9baa63abe35f20eafeb9fe5e" + ], + "markers": "python_version >= '3.9'", + "version": "==3.1.4" + } + }, + "develop": {} +} diff --git a/dev.py b/dev.py index 920df5e..6ea8abc 100755 --- a/dev.py +++ b/dev.py @@ -3,130 +3,51 @@ import subprocess import serial import sys -import glob -def upload_src(port): - subprocess.call(["mpremote", "connect", port, "fs", "cp", "-r", ".", ":"], cwd="src") +print(sys.argv) -def upload_lib(port): - subprocess.call(["mpremote", "connect", port, "fs", "cp", "-r", "lib", ":"]) +# Extract port (first arg if it's not a command) +commands = ["src", "lib", "ls", "reset", "follow", "db"] +port = None +if len(sys.argv) > 1 and sys.argv[1] not in commands: + port = sys.argv[1] -def list_files(port): - subprocess.call(["mpremote", "connect", port, "fs", "ls", ":"]) -def reset_device(port): - with serial.Serial(port, baudrate=115200) as ser: - ser.write(b'\x03\x03\x04') - -def follow_serial(port): - with serial.Serial(port, baudrate=115200) as ser: - while True: - if ser.in_waiting > 0: - data = ser.readline().decode('utf-8').strip() - print(data) - -def clean_settings(port): - subprocess.call(["mpremote", "connect", port, "fs", "rm", ":/settings.json"]) - -def flash_firmware(port): - # Find MicroPython firmware binary - firmware_files = glob.glob("*.bin") - if not firmware_files: - print("Error: No .bin firmware file found in current directory") - print("Please download MicroPython firmware and place it in the project directory") - sys.exit(1) - - firmware = firmware_files[0] - if len(firmware_files) > 1: - print(f"Warning: Multiple .bin files found, using: {firmware}") - - print(f"Flashing MicroPython firmware: {firmware}") - print("Erasing flash...") - subprocess.call(["esptool.py", "--port", port, "erase_flash"]) - - print(f"Writing firmware to flash...") - subprocess.call([ - "esptool.py", - "--port", port, - "--baud", "460800", - "write_flash", "0", - firmware - ]) - print("Flash complete!") - -def main(): - port = "/dev/ttyACM0" - commands = [] - i = 1 - - # Parse arguments manually to preserve order - while i < len(sys.argv): - arg = sys.argv[i] - if arg in ["-p", "--port"]: - if i + 1 < len(sys.argv): - port = sys.argv[i + 1] - i += 2 +for cmd in sys.argv[1:]: + print(cmd) + match cmd: + case "src": + if port: + subprocess.call(["mpremote", "connect", port, "fs", "cp", "-r", ".", ":" ], cwd="src") else: - print(f"Error: {arg} requires a port argument") - sys.exit(1) - elif arg in ["-s", "--src"]: - commands.append(("src", upload_src)) - i += 1 - elif arg in ["-r", "--reset"]: - commands.append(("reset", reset_device)) - i += 1 - elif arg in ["-f", "--follow"]: - commands.append(("follow", follow_serial)) - i += 1 - elif arg == "--lib": - commands.append(("lib", upload_lib)) - i += 1 - elif arg == "--ls": - commands.append(("ls", list_files)) - i += 1 - elif arg == "--clean": - commands.append(("clean", clean_settings)) - i += 1 - elif arg == "--flash": - commands.append(("flash", flash_firmware)) - i += 1 - elif arg in ["-h", "--help"]: - print("LED Driver development tools") - print("\nUsage:") - print(" ./dev.py [-p PORT] [FLAGS...]") - print("\nFlags:") - print(" -p, --port PORT Serial port (default: /dev/ttyACM0)") - print(" -s, --src Upload src directory") - print(" -r, --reset Reset device") - print(" -f, --follow Follow serial output") - print(" --lib Upload lib directory") - print(" --ls List files on device") - print(" --clean Remove settings.json from device") - print(" --flash Flash MicroPython firmware") - print("\nExamples:") - print(" ./dev.py -p /dev/ttyACM0 -s -r -f") - print(" ./dev.py --flash -s -r") - sys.exit(0) - else: - print(f"Error: Unknown argument: {arg}") - print("Use -h or --help for usage information") - sys.exit(1) - - # Execute commands in the order they were given - if not commands: - print("No commands specified. Use -h or --help for usage information.") - sys.exit(1) - - for cmd_name, cmd_func in commands: - if cmd_name == "reset": - print("Resetting device...") - elif cmd_name == "follow": - print("Following serial output (Ctrl+C to exit)...") - elif cmd_name == "flash": - pass # flash_firmware prints its own messages - else: - print(f"{cmd_name.capitalize()}...") - cmd_func(port) - -if __name__ == "__main__": - main() + print("Error: Port required for 'src' command") + case "lib": + if port: + subprocess.call(["mpremote", "connect", port, "fs", "cp", "-r", "lib", ":" ]) + else: + print("Error: Port required for 'lib' command") + case "ls": + if port: + subprocess.call(["mpremote", "connect", port, "fs", "ls", ":" ]) + else: + print("Error: Port required for 'ls' command") + case "reset": + if port: + with serial.Serial(port, baudrate=115200) as ser: + ser.write(b'\x03\x03\x04') + else: + print("Error: Port required for 'reset' command") + case "follow": + if port: + with serial.Serial(port, baudrate=115200) as ser: + while True: + 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 + print(data) + else: + print("Error: Port required for 'follow' command") + case "db": + if port: + subprocess.call(["mpremote", "connect", port, "fs", "cp", "-r", "db", ":" ]) + else: + print("Error: Port required for 'db' command") diff --git a/install.sh b/install.sh new file mode 100755 index 0000000..ce3a525 --- /dev/null +++ b/install.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash +# Install script - runs pipenv install + +pipenv install "$@" diff --git a/src/patterns.py b/src/patterns.py index 0b5fbad..9ae3a9f 100644 --- a/src/patterns.py +++ b/src/patterns.py @@ -1,7 +1,7 @@ import utime -from patterns_base import atternsBase +from patterns_base import Patterns_Base -class Patterns(PatternsBase): +class Patterns(Patterns_Base): 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 @@ -18,15 +18,16 @@ class Patterns(PatternsBase): } - def blink(self): + def blink(self, preset): state = True # True = on, False = off last_update = utime.ticks_ms() while True: current_time = utime.ticks_ms() - if utime.ticks_diff(current_time, last_update) >= self.delay: + if utime.ticks_diff(current_time, last_update) >= preset.delay: if state: - self.fill(self.apply_brightness(self.colors[0])) + color = preset.colors[0] if preset.colors else (255, 255, 255) + self.fill(self.apply_brightness(color, preset.brightness)) else: self.fill((0, 0, 0)) state = not state @@ -35,15 +36,15 @@ class Patterns(PatternsBase): yield - def rainbow(self): + def rainbow(self, preset): step = self.step % 256 - step_amount = max(1, int(self.n1)) # n1 controls step increment + step_amount = max(1, int(preset.n1)) # n1 controls step increment # If auto is False, run a single step and then stop - if not self.auto: + if not preset.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[i] = self.apply_brightness(self.wheel(rc_index & 255), preset.brightness) self.n.write() # Increment step by n1 for next manual call self.step = (step + step_amount) % 256 @@ -55,11 +56,11 @@ class Patterns(PatternsBase): while True: current_time = utime.ticks_ms() - sleep_ms = max(1, int(self.delay)) # Access delay directly + sleep_ms = max(1, int(preset.delay)) # Get delay from preset 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[i] = self.apply_brightness(self.wheel(rc_index & 255), preset.brightness) self.n.write() step = (step + step_amount) % 256 self.step = step @@ -68,23 +69,24 @@ class Patterns(PatternsBase): yield - def pulse(self): + def pulse(self, preset): self.off() - # Ensure we have at least one color - if not self.colors: - self.colors = [(255, 255, 255)] + # Get colors from preset + colors = preset.colors + if not colors: + colors = [(255, 255, 255)] color_index = 0 cycle_start = utime.ticks_ms() # State machine based pulse using a single generator loop while True: - # Read current timing parameters each cycle so they can be changed live - attack_ms = max(0, int(self.n1)) # Attack time in ms - hold_ms = max(0, int(self.n2)) # Hold time in ms - decay_ms = max(0, int(self.n3)) # Decay time in ms - delay_ms = max(0, int(self.delay)) + # Read current timing parameters from preset + attack_ms = max(0, int(preset.n1)) # Attack time in ms + hold_ms = max(0, int(preset.n2)) # Hold time in ms + decay_ms = max(0, int(preset.n3)) # Decay time in ms + delay_ms = max(0, int(preset.delay)) total_ms = attack_ms + hold_ms + decay_ms + delay_ms if total_ms <= 0: @@ -93,22 +95,22 @@ class Patterns(PatternsBase): now = utime.ticks_ms() elapsed = utime.ticks_diff(now, cycle_start) - base_color = self.colors[color_index % len(self.colors)] + base_color = colors[color_index % len(colors)] if elapsed < attack_ms and attack_ms > 0: # Attack: fade 0 -> 1 factor = elapsed / attack_ms color = tuple(int(c * factor) for c in base_color) - self.fill(self.apply_brightness(color)) + self.fill(self.apply_brightness(color, preset.brightness)) elif elapsed < attack_ms + hold_ms: # Hold: full brightness - self.fill(self.apply_brightness(base_color)) + self.fill(self.apply_brightness(base_color, preset.brightness)) elif elapsed < attack_ms + hold_ms + decay_ms and decay_ms > 0: # Decay: fade 1 -> 0 dec_elapsed = elapsed - attack_ms - hold_ms factor = max(0.0, 1.0 - (dec_elapsed / decay_ms)) color = tuple(int(c * factor) for c in base_color) - self.fill(self.apply_brightness(color)) + self.fill(self.apply_brightness(color, preset.brightness)) elif elapsed < total_ms: # Delay phase: LEDs off between pulses self.fill((0, 0, 0)) @@ -116,7 +118,7 @@ class Patterns(PatternsBase): # End of cycle, move to next color and restart timing color_index += 1 cycle_start = now - if not self.auto: + if not preset.auto: break # Skip drawing this tick, start next cycle yield @@ -125,17 +127,18 @@ class Patterns(PatternsBase): # Yield once per tick yield - def transition(self): + def transition(self, preset): """Transition between colors, blending over `delay` ms.""" - if not self.colors: + colors = preset.colors + if not colors: self.off() yield return # Only one color: just keep it on - if len(self.colors) == 1: + if len(colors) == 1: while True: - self.fill(self.apply_brightness(self.colors[0])) + self.fill(self.apply_brightness(colors[0], preset.brightness)) yield return @@ -143,25 +146,25 @@ class Patterns(PatternsBase): start_time = utime.ticks_ms() while True: - if not self.colors: + if not colors: break # Get current and next color based on live list - c1 = self.colors[color_index % len(self.colors)] - c2 = self.colors[(color_index + 1) % len(self.colors)] + c1 = colors[color_index % len(colors)] + c2 = colors[(color_index + 1) % len(colors)] - duration = max(10, int(self.delay)) # At least 10ms + duration = max(10, int(preset.delay)) # At least 10ms now = utime.ticks_ms() elapsed = utime.ticks_diff(now, start_time) if elapsed >= duration: # End of this transition step - if not self.auto and color_index >= 0: + if not preset.auto and color_index >= 0: # One-shot: transition from first to second color only - self.fill(self.apply_brightness(c2)) + self.fill(self.apply_brightness(c2, preset.brightness)) break # Auto: move to next pair - color_index = (color_index + 1) % len(self.colors) + color_index = (color_index + 1) % len(colors) start_time = now yield continue @@ -171,14 +174,15 @@ class Patterns(PatternsBase): interpolated = tuple( int(c1[i] + (c2[i] - c1[i]) * factor) for i in range(3) ) - self.fill(self.apply_brightness(interpolated)) + self.fill(self.apply_brightness(interpolated, preset.brightness)) yield - def chase(self): + def chase(self, preset): """Chase pattern: n1 LEDs of color0, n2 LEDs of color1, repeating. Moves by n3 on even steps, n4 on odd steps (n3/n4 can be positive or negative)""" - if len(self.colors) < 1: + colors = preset.colors + if len(colors) < 1: # Need at least 1 color return @@ -189,27 +193,27 @@ class Patterns(PatternsBase): last_update = utime.ticks_ms() while True: - # Access colors, delay, and n values directly for live updates - if not self.colors: + # Access colors, delay, and n values from preset + if not colors: break # If only one color provided, use it for both colors - if len(self.colors) < 2: - color0 = self.colors[0] - color1 = self.colors[0] + if len(colors) < 2: + color0 = colors[0] + color1 = colors[0] else: - color0 = self.colors[0] - color1 = self.colors[1] + color0 = colors[0] + color1 = colors[1] - color0 = self.apply_brightness(color0) - color1 = self.apply_brightness(color1) + color0 = self.apply_brightness(color0, preset.brightness) + color1 = self.apply_brightness(color1, preset.brightness) - 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) + n1 = max(1, int(preset.n1)) # LEDs of color 0 + n2 = max(1, int(preset.n2)) # LEDs of color 1 + n3 = int(preset.n3) # Step movement on odd steps (can be negative) + n4 = int(preset.n4) # Step movement on even steps (can be negative) segment_length = n1 + n2 - transition_duration = max(10, int(self.delay)) + transition_duration = max(10, int(preset.delay)) current_time = utime.ticks_ms() if utime.ticks_diff(current_time, last_update) >= transition_duration: @@ -249,16 +253,16 @@ class Patterns(PatternsBase): # Yield once per tick so other logic can run yield - def circle(self): + def circle(self, preset): """Circle loading pattern - grows to n2, then tail moves forward at n3 until min length n4""" head = 0 tail = 0 - # Calculate timing - head_rate = max(1, int(self.n1)) # n1 = head moves per second - tail_rate = max(1, int(self.n3)) # n3 = tail moves per second - max_length = max(1, int(self.n2)) # n2 = max length - min_length = max(0, int(self.n4)) # n4 = min length + # Calculate timing from preset + head_rate = max(1, int(preset.n1)) # n1 = head moves per second + tail_rate = max(1, int(preset.n3)) # n3 = tail moves per second + max_length = max(1, int(preset.n2)) # n2 = max length + min_length = max(0, int(preset.n4)) # n4 = min length head_delay = 1000 // head_rate # ms between head movements tail_delay = 1000 // tail_rate # ms between tail movements @@ -268,6 +272,9 @@ class Patterns(PatternsBase): phase = "growing" # "growing", "shrinking", or "off" + colors = preset.colors + color = self.apply_brightness(colors[0] if colors else (255, 255, 255), preset.brightness) + while True: current_time = utime.ticks_ms() @@ -280,7 +287,6 @@ class Patterns(PatternsBase): 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 diff --git a/src/patterns_base.py b/src/patterns_base.py index 069ed93..0e70285 100644 --- a/src/patterns_base.py +++ b/src/patterns_base.py @@ -24,6 +24,31 @@ param_mapping = { "auto": "auto", } +class Preset: + def __init__(self, data): + # Set default values for all preset attributes + self.pattern = "off" + self.delay = 100 + self.brightness = 127 + self.colors = [(255, 255, 255)] + self.auto = True + self.n1 = 0 + self.n2 = 0 + self.n3 = 0 + self.n4 = 0 + self.n5 = 0 + self.n6 = 0 + + # Override defaults with provided data + self.edit(data) + + def edit(self, data=None): + if not data: + return False + for key, value in data.items(): + setattr(self, key, value) + return True + class Patterns_Base: 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) @@ -46,7 +71,26 @@ class Patterns_Base: self.n6 = 0 self.generator = None - self.select(self.selected) + self.presets = {} + self.select(self.selected) + + def edit(self, name, data): + if name in self.presets: + self.presets[name].edit(data) + return True + return False + + def add(self, name, data): + self.presets[name] = Preset(data) + return + + + + def delete(self, name): + if name in self.presets: + del self.presets[name] + return True + return False def tick(self): @@ -57,13 +101,14 @@ class Patterns_Base: except StopIteration: self.generator = None - def select(self, pattern): - if pattern in self.patterns: - self.selected = pattern - self.generator = self.patterns[pattern]() - print(f"Selected pattern: {pattern}") - return True - # If pattern doesn't exist, default to "off" + def select(self, preset_name): + if preset_name in self.presets: + preset = self.presets[preset_name] + if preset.pattern in self.patterns: + self.generator = self.patterns[preset.pattern](preset) + self.selected = preset_name # Store the preset name, not the object + return True + # If preset doesn't exist or pattern not found, default to "off" return False def set_param(self, key, value): @@ -108,11 +153,13 @@ class Patterns_Base: self.n[i] = fill_color self.n.write() - def off(self): + def off(self, preset=None): self.fill((0, 0, 0)) - def on(self): - self.fill(self.apply_brightness(self.colors[0])) + def on(self, preset): + colors = preset.colors + color = colors[0] if colors else (255, 255, 255) + self.fill(self.apply_brightness(color, preset.brightness)) @@ -127,4 +174,3 @@ class Patterns_Base: pos -= 170 return (0, pos * 3, 255 - pos * 3) - \ No newline at end of file diff --git a/test/patterns/auto_manual.py b/test/patterns/auto_manual.py new file mode 100644 index 0000000..aaed560 --- /dev/null +++ b/test/patterns/auto_manual.py @@ -0,0 +1,190 @@ +#!/usr/bin/env python3 +import utime +from machine import WDT +from settings import Settings +from patterns import Patterns + + +def run_for(p, wdt, duration_ms): + """Run pattern for specified duration.""" + start = utime.ticks_ms() + while utime.ticks_diff(utime.ticks_ms(), start) < duration_ms: + wdt.feed() + p.tick() + utime.sleep_ms(10) + + +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) + + print("=" * 50) + print("Testing Auto and Manual Modes") + print("=" * 50) + + # Test 1: Rainbow in AUTO mode (continuous) + print("\nTest 1: Rainbow pattern in AUTO mode (should run continuously)") + p.add("rainbow_auto", { + "pattern": "rainbow", + "brightness": 128, + "delay": 50, + "n1": 2, + "auto": True + }) + p.select("rainbow_auto") + print("Running rainbow_auto for 3 seconds...") + run_for(p, wdt, 3000) + print("✓ Auto mode: Pattern ran continuously") + + # Test 2: Rainbow in MANUAL mode (one step per tick) + print("\nTest 2: Rainbow pattern in MANUAL mode (one step per tick)") + p.add("rainbow_manual", { + "pattern": "rainbow", + "brightness": 128, + "delay": 50, + "n1": 2, + "auto": False + }) + p.select("rainbow_manual") + print("Calling tick() 5 times (should advance 5 steps)...") + for i in range(5): + p.tick() + utime.sleep_ms(100) # Small delay to see changes + print(f" Tick {i+1}: generator={'active' if p.generator is not None else 'stopped'}") + + # Check if generator stopped after one cycle + if p.generator is None: + print("✓ Manual mode: Generator stopped after one step (as expected)") + else: + print("⚠ Manual mode: Generator still active (may need multiple ticks)") + + # Test 3: Pulse in AUTO mode (continuous cycles) + print("\nTest 3: Pulse pattern in AUTO mode (should pulse continuously)") + p.add("pulse_auto", { + "pattern": "pulse", + "brightness": 128, + "delay": 100, + "n1": 500, # Attack + "n2": 200, # Hold + "n3": 500, # Decay + "colors": [(255, 0, 0)], + "auto": True + }) + p.select("pulse_auto") + print("Running pulse_auto for 3 seconds...") + run_for(p, wdt, 3000) + print("✓ Auto mode: Pulse ran continuously") + + # Test 4: Pulse in MANUAL mode (one cycle then stop) + print("\nTest 4: Pulse pattern in MANUAL mode (one cycle then stop)") + p.add("pulse_manual", { + "pattern": "pulse", + "brightness": 128, + "delay": 100, + "n1": 300, # Attack + "n2": 200, # Hold + "n3": 300, # Decay + "colors": [(0, 255, 0)], + "auto": False + }) + p.select("pulse_manual") + print("Running pulse_manual until generator stops...") + tick_count = 0 + max_ticks = 200 # Safety limit + while p.generator is not None and tick_count < max_ticks: + p.tick() + tick_count += 1 + utime.sleep_ms(10) + + if p.generator is None: + print(f"✓ Manual mode: Pulse completed one cycle after {tick_count} ticks") + else: + print(f"⚠ Manual mode: Pulse still running after {tick_count} ticks") + + # Test 5: Transition in AUTO mode (continuous transitions) + print("\nTest 5: Transition pattern in AUTO mode (continuous transitions)") + p.add("transition_auto", { + "pattern": "transition", + "brightness": 128, + "delay": 500, + "colors": [(255, 0, 0), (0, 255, 0), (0, 0, 255)], + "auto": True + }) + p.select("transition_auto") + print("Running transition_auto for 3 seconds...") + run_for(p, wdt, 3000) + print("✓ Auto mode: Transition ran continuously") + + # Test 6: Transition in MANUAL mode (one transition then stop) + print("\nTest 6: Transition pattern in MANUAL mode (one transition then stop)") + p.add("transition_manual", { + "pattern": "transition", + "brightness": 128, + "delay": 500, + "colors": [(255, 0, 0), (0, 255, 0)], + "auto": False + }) + p.select("transition_manual") + print("Running transition_manual until generator stops...") + tick_count = 0 + max_ticks = 200 + while p.generator is not None and tick_count < max_ticks: + p.tick() + tick_count += 1 + utime.sleep_ms(10) + + if p.generator is None: + print(f"✓ Manual mode: Transition completed after {tick_count} ticks") + else: + print(f"⚠ Manual mode: Transition still running after {tick_count} ticks") + + # Test 7: Switching between auto and manual modes + print("\nTest 7: Switching between auto and manual modes") + p.add("switch_test", { + "pattern": "rainbow", + "brightness": 128, + "delay": 50, + "n1": 2, + "auto": True + }) + p.select("switch_test") + print("Running in auto mode for 1 second...") + run_for(p, wdt, 1000) + + # Switch to manual mode by editing the preset + print("Switching to manual mode...") + p.edit("switch_test", {"auto": False}) + p.select("switch_test") # Re-select to apply changes + + print("Calling tick() 3 times in manual mode...") + for i in range(3): + p.tick() + utime.sleep_ms(100) + print(f" Tick {i+1}: generator={'active' if p.generator is not None else 'stopped'}") + + # Switch back to auto mode + print("Switching back to auto mode...") + p.edit("switch_test", {"auto": True}) + p.select("switch_test") + print("Running in auto mode for 1 second...") + run_for(p, wdt, 1000) + print("✓ Successfully switched between auto and manual modes") + + # Cleanup + print("\nCleaning up...") + p.add("cleanup_off", {"pattern": "off"}) + p.select("cleanup_off") + p.tick() + utime.sleep_ms(100) + + print("\n" + "=" * 50) + print("All tests completed!") + print("=" * 50) + + +if __name__ == "__main__": + main() diff --git a/test/patterns/blink.py b/test/patterns/blink.py index 105f940..0deb1ee 100644 --- a/test/patterns/blink.py +++ b/test/patterns/blink.py @@ -12,10 +12,15 @@ def main(): 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") + + # Create blink preset + p.add("test_blink", { + "pattern": "blink", + "brightness": 64, + "delay": 200, + "colors": [(255, 0, 0), (0, 0, 255)] + }) + p.select("test_blink") start = utime.ticks_ms() while utime.ticks_diff(utime.ticks_ms(), start) < 1500: diff --git a/test/patterns/chase.py b/test/patterns/chase.py index 5cbd44a..f21de1c 100644 --- a/test/patterns/chase.py +++ b/test/patterns/chase.py @@ -24,74 +24,93 @@ def main(): # Test 1: Basic chase (n1=5, n2=5, n3=1, n4=1) print("Test 1: Basic chase (n1=5, n2=5, n3=1, n4=1)") - p.set_param("br", 255) - p.set_param("dl", 200) - p.set_param("n1", 5) - p.set_param("n2", 5) - p.set_param("n3", 1) - p.set_param("n4", 1) - p.set_param("cl", [(255, 0, 0), (0, 255, 0)]) - p.select("chase") + p.add("chase1", { + "pattern": "chase", + "brightness": 255, + "delay": 200, + "n1": 5, + "n2": 5, + "n3": 1, + "n4": 1, + "colors": [(255, 0, 0), (0, 255, 0)] + }) + p.select("chase1") run_for(p, wdt, 3000) # Test 2: Forward and backward (n3=2, n4=-1) print("Test 2: Forward and backward (n3=2, n4=-1)") - p.set_param("n1", 3) - p.set_param("n2", 3) - p.set_param("n3", 2) - p.set_param("n4", -1) - p.set_param("dl", 150) - p.set_param("cl", [(0, 0, 255), (255, 255, 0)]) - p.select("chase") + p.add("chase2", { + "pattern": "chase", + "n1": 3, + "n2": 3, + "n3": 2, + "n4": -1, + "delay": 150, + "colors": [(0, 0, 255), (255, 255, 0)] + }) + p.select("chase2") run_for(p, wdt, 3000) # Test 3: Large segments (n1=10, n2=5) print("Test 3: Large segments (n1=10, n2=5, n3=3, n4=3)") - p.set_param("n1", 10) - p.set_param("n2", 5) - p.set_param("n3", 3) - p.set_param("n4", 3) - p.set_param("dl", 200) - p.set_param("cl", [(255, 128, 0), (128, 0, 255)]) - p.select("chase") + p.add("chase3", { + "pattern": "chase", + "n1": 10, + "n2": 5, + "n3": 3, + "n4": 3, + "delay": 200, + "colors": [(255, 128, 0), (128, 0, 255)] + }) + p.select("chase3") run_for(p, wdt, 3000) # Test 4: Fast movement (n3=5, n4=5) print("Test 4: Fast movement (n3=5, n4=5)") - p.set_param("n1", 4) - p.set_param("n2", 4) - p.set_param("n3", 5) - p.set_param("n4", 5) - p.set_param("dl", 100) - p.set_param("cl", [(255, 0, 255), (0, 255, 255)]) - p.select("chase") + p.add("chase4", { + "pattern": "chase", + "n1": 4, + "n2": 4, + "n3": 5, + "n4": 5, + "delay": 100, + "colors": [(255, 0, 255), (0, 255, 255)] + }) + p.select("chase4") run_for(p, wdt, 2000) # Test 5: Backward movement (n3=-2, n4=-2) print("Test 5: Backward movement (n3=-2, n4=-2)") - p.set_param("n1", 6) - p.set_param("n2", 4) - p.set_param("n3", -2) - p.set_param("n4", -2) - p.set_param("dl", 200) - p.set_param("cl", [(255, 255, 255), (0, 0, 0)]) - p.select("chase") + p.add("chase5", { + "pattern": "chase", + "n1": 6, + "n2": 4, + "n3": -2, + "n4": -2, + "delay": 200, + "colors": [(255, 255, 255), (0, 0, 0)] + }) + p.select("chase5") run_for(p, wdt, 3000) # Test 6: Alternating forward/backward (n3=3, n4=-2) print("Test 6: Alternating forward/backward (n3=3, n4=-2)") - p.set_param("n1", 5) - p.set_param("n2", 5) - p.set_param("n3", 3) - p.set_param("n4", -2) - p.set_param("dl", 250) - p.set_param("cl", [(255, 0, 0), (0, 255, 0)]) - p.select("chase") + p.add("chase6", { + "pattern": "chase", + "n1": 5, + "n2": 5, + "n3": 3, + "n4": -2, + "delay": 250, + "colors": [(255, 0, 0), (0, 255, 0)] + }) + p.select("chase6") run_for(p, wdt, 4000) # Cleanup print("Test complete, turning off") - p.select("off") + p.add("cleanup_off", {"pattern": "off"}) + p.select("cleanup_off") run_for(p, wdt, 100) diff --git a/test/patterns/circle.py b/test/patterns/circle.py index aed7bf4..bee4390 100644 --- a/test/patterns/circle.py +++ b/test/patterns/circle.py @@ -24,68 +24,87 @@ def main(): # 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") + p.add("circle1", { + "pattern": "circle", + "brightness": 255, + "n1": 50, # Head moves 50 LEDs/second + "n2": 100, # Max length 100 LEDs + "n3": 200, # Tail moves 200 LEDs/second + "n4": 0, # Min length 0 LEDs + "colors": [(255, 0, 0)] # Red + }) + p.select("circle1") run_for(p, wdt, 5000) # 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.set_param("n1", 20) - p.set_param("n2", 50) - p.set_param("n3", 100) - p.set_param("n4", 0) - p.set_param("cl", [(0, 255, 0)]) # Green - p.select("circle") + p.add("circle2", { + "pattern": "circle", + "n1": 20, + "n2": 50, + "n3": 100, + "n4": 0, + "colors": [(0, 255, 0)] # Green + }) + p.select("circle2") run_for(p, wdt, 5000) # 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.set_param("n1", 100) - p.set_param("n2", 30) - p.set_param("n3", 20) - p.set_param("n4", 0) - p.set_param("cl", [(0, 0, 255)]) # Blue - p.select("circle") + p.add("circle3", { + "pattern": "circle", + "n1": 100, + "n2": 30, + "n3": 20, + "n4": 0, + "colors": [(0, 0, 255)] # Blue + }) + p.select("circle3") run_for(p, wdt, 5000) # 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.set_param("n1", 50) - p.set_param("n2", 40) - p.set_param("n3", 100) - p.set_param("n4", 10) - p.set_param("cl", [(255, 255, 0)]) # Yellow - p.select("circle") + p.add("circle4", { + "pattern": "circle", + "n1": 50, + "n2": 40, + "n3": 100, + "n4": 10, + "colors": [(255, 255, 0)] # Yellow + }) + p.select("circle4") run_for(p, wdt, 5000) # 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.set_param("n1", 200) - p.set_param("n2", 20) - p.set_param("n3", 200) - p.set_param("n4", 0) - p.set_param("cl", [(255, 0, 255)]) # Magenta - p.select("circle") + p.add("circle5", { + "pattern": "circle", + "n1": 200, + "n2": 20, + "n3": 200, + "n4": 0, + "colors": [(255, 0, 255)] # Magenta + }) + p.select("circle5") run_for(p, wdt, 3000) # 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.set_param("n1", 10) - p.set_param("n2", 25) - p.set_param("n3", 10) - p.set_param("n4", 0) - p.set_param("cl", [(0, 255, 255)]) # Cyan - p.select("circle") + p.add("circle6", { + "pattern": "circle", + "n1": 10, + "n2": 25, + "n3": 10, + "n4": 0, + "colors": [(0, 255, 255)] # Cyan + }) + p.select("circle6") run_for(p, wdt, 5000) # Cleanup print("Test complete, turning off") - p.select("off") + p.add("cleanup_off", {"pattern": "off"}) + p.select("cleanup_off") run_for(p, wdt, 100) diff --git a/test/patterns/off.py b/test/patterns/off.py index c0df8f9..516b678 100644 --- a/test/patterns/off.py +++ b/test/patterns/off.py @@ -12,7 +12,10 @@ def main(): p = Patterns(pin=pin, num_leds=num) wdt = WDT(timeout=10000) - p.select("off") + + # Create an "off" preset + p.add("test_off", {"pattern": "off"}) + p.select("test_off") start = utime.ticks_ms() while utime.ticks_diff(utime.ticks_ms(), start) < 200: diff --git a/test/patterns/on.py b/test/patterns/on.py index 09d3379..219bedc 100644 --- a/test/patterns/on.py +++ b/test/patterns/on.py @@ -12,12 +12,18 @@ def main(): 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)]) + + # Create presets for on and off + p.add("test_on", { + "pattern": "on", + "brightness": 64, + "delay": 120, + "colors": [(255, 0, 0), (0, 0, 255)] + }) + p.add("test_off", {"pattern": "off"}) # ON phase - p.select("on") + p.select("test_on") start = utime.ticks_ms() while utime.ticks_diff(utime.ticks_ms(), start) < 800: wdt.feed() @@ -25,7 +31,7 @@ def main(): utime.sleep_ms(10) # OFF phase - p.select("off") + p.select("test_off") start = utime.ticks_ms() while utime.ticks_diff(utime.ticks_ms(), start) < 100: wdt.feed() diff --git a/test/patterns/pulse.py b/test/patterns/pulse.py index bf6912d..3b1f9e5 100644 --- a/test/patterns/pulse.py +++ b/test/patterns/pulse.py @@ -24,52 +24,65 @@ def main(): # Test 1: Simple single-color pulse print("Test 1: Single-color pulse (attack=500, hold=500, decay=500, delay=500)") - p.set_param("br", 255) - p.set_param("cl", [(255, 0, 0)]) # Red - p.set_param("n1", 500) # attack ms - p.set_param("n2", 500) # hold ms - p.set_param("n3", 500) # decay ms - p.set_param("dl", 500) # delay ms between pulses - p.set_param("auto", True) - p.select("pulse") + p.add("pulse1", { + "pattern": "pulse", + "brightness": 255, + "colors": [(255, 0, 0)], + "n1": 500, # attack ms + "n2": 500, # hold ms + "n3": 500, # decay ms + "delay": 500, # delay ms between pulses + "auto": True + }) + p.select("pulse1") run_for(p, wdt, 5000) # Test 2: Faster pulse print("Test 2: Fast pulse (attack=100, hold=100, decay=100, delay=100)") - p.set_param("n1", 100) - p.set_param("n2", 100) - p.set_param("n3", 100) - p.set_param("dl", 100) - p.set_param("cl", [(0, 255, 0)]) # Green - p.select("pulse") + p.add("pulse2", { + "pattern": "pulse", + "n1": 100, + "n2": 100, + "n3": 100, + "delay": 100, + "colors": [(0, 255, 0)] + }) + p.select("pulse2") run_for(p, wdt, 4000) # Test 3: Multi-color pulse cycle print("Test 3: Multi-color pulse (red -> green -> blue)") - p.set_param("n1", 300) - p.set_param("n2", 300) - p.set_param("n3", 300) - p.set_param("dl", 200) - p.set_param("cl", [(255, 0, 0), (0, 255, 0), (0, 0, 255)]) - p.set_param("auto", True) - p.select("pulse") + p.add("pulse3", { + "pattern": "pulse", + "n1": 300, + "n2": 300, + "n3": 300, + "delay": 200, + "colors": [(255, 0, 0), (0, 255, 0), (0, 0, 255)], + "auto": True + }) + p.select("pulse3") run_for(p, wdt, 6000) # Test 4: One-shot pulse (auto=False) print("Test 4: Single pulse, auto=False") - p.set_param("n1", 400) - p.set_param("n2", 0) - p.set_param("n3", 400) - p.set_param("dl", 0) - p.set_param("cl", [(255, 255, 255)]) - p.set_param("auto", False) - p.select("pulse") + p.add("pulse4", { + "pattern": "pulse", + "n1": 400, + "n2": 0, + "n3": 400, + "delay": 0, + "colors": [(255, 255, 255)], + "auto": False + }) + p.select("pulse4") # Run long enough to allow one full pulse cycle run_for(p, wdt, 1500) # Cleanup print("Test complete, turning off") - p.select("off") + p.add("cleanup_off", {"pattern": "off"}) + p.select("cleanup_off") run_for(p, wdt, 200) diff --git a/test/patterns/rainbow.py b/test/patterns/rainbow.py index 8e4791d..e4569ef 100644 --- a/test/patterns/rainbow.py +++ b/test/patterns/rainbow.py @@ -24,47 +24,62 @@ def main(): # 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) - p.select("rainbow") + p.add("rainbow1", { + "pattern": "rainbow", + "brightness": 255, + "delay": 100, + "n1": 1, + "auto": True + }) + p.select("rainbow1") run_for(p, wdt, 3000) # Test 2: Fast rainbow print("Test 2: Fast rainbow (low delay, n1=1)") - p.set_param("dl", 50) - p.set_param("n1", 1) - p.set_param("auto", True) - p.select("rainbow") + p.add("rainbow2", { + "pattern": "rainbow", + "delay": 50, + "n1": 1, + "auto": True + }) + p.select("rainbow2") run_for(p, wdt, 2000) # Test 3: Slow rainbow print("Test 3: Slow rainbow (high delay, n1=1)") - p.set_param("dl", 500) - p.set_param("n1", 1) - p.set_param("auto", True) - p.select("rainbow") + p.add("rainbow3", { + "pattern": "rainbow", + "delay": 500, + "n1": 1, + "auto": True + }) + p.select("rainbow3") run_for(p, wdt, 3000) # Test 4: Low brightness rainbow print("Test 4: Low brightness rainbow (n1=1)") - p.set_param("br", 64) - p.set_param("dl", 100) - p.set_param("n1", 1) - p.set_param("auto", True) - p.select("rainbow") + p.add("rainbow4", { + "pattern": "rainbow", + "brightness": 64, + "delay": 100, + "n1": 1, + "auto": True + }) + p.select("rainbow4") run_for(p, wdt, 2000) # Test 5: Single-step rainbow (auto=False) print("Test 5: Single-step rainbow (auto=False, n1=1)") - p.set_param("br", 255) - p.set_param("dl", 100) - p.set_param("n1", 1) - p.set_param("auto", False) + p.add("rainbow5", { + "pattern": "rainbow", + "brightness": 255, + "delay": 100, + "n1": 1, + "auto": False + }) p.step = 0 for i in range(10): - p.select("rainbow") + p.select("rainbow5") # One tick advances the generator one frame when auto=False p.tick() utime.sleep_ms(100) @@ -72,37 +87,49 @@ def main(): # Test 6: Verify step updates correctly print("Test 6: Verify step updates (auto=False, n1=1)") - p.set_param("n1", 1) - p.set_param("auto", False) + p.add("rainbow6", { + "pattern": "rainbow", + "n1": 1, + "auto": False + }) initial_step = p.step - p.select("rainbow") + p.select("rainbow6") p.tick() 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.set_param("br", 255) - p.set_param("dl", 100) - p.set_param("n1", 5) - p.set_param("auto", True) - p.select("rainbow") + p.add("rainbow7", { + "pattern": "rainbow", + "brightness": 255, + "delay": 100, + "n1": 5, + "auto": True + }) + p.select("rainbow7") run_for(p, wdt, 2000) # Test 8: Very fast step increment (n1=10) print("Test 8: Very fast rainbow (n1=10, auto=True)") - p.set_param("n1", 10) - p.set_param("auto", True) - p.select("rainbow") + p.add("rainbow8", { + "pattern": "rainbow", + "n1": 10, + "auto": True + }) + p.select("rainbow8") run_for(p, wdt, 2000) # Test 9: Verify n1 controls step increment (auto=False) print("Test 9: Verify n1 step increment (auto=False, n1=5)") - p.set_param("n1", 5) - p.set_param("auto", False) + p.add("rainbow9", { + "pattern": "rainbow", + "n1": 5, + "auto": False + }) p.step = 0 initial_step = p.step - p.select("rainbow") + p.select("rainbow9") p.tick() final_step = p.step expected_step = (initial_step + 5) % 256 @@ -114,7 +141,8 @@ def main(): # Cleanup print("Test complete, turning off") - p.select("off") + p.add("cleanup_off", {"pattern": "off"}) + p.select("cleanup_off") run_for(p, wdt, 100) diff --git a/test/patterns/transition.py b/test/patterns/transition.py index 7e2f95b..86690e3 100644 --- a/test/patterns/transition.py +++ b/test/patterns/transition.py @@ -24,41 +24,54 @@ def main(): # Test 1: Simple two-color transition print("Test 1: Two-color transition (red <-> blue, delay=1000)") - p.set_param("br", 255) - p.set_param("dl", 1000) # transition duration - p.set_param("cl", [(255, 0, 0), (0, 0, 255)]) - p.set_param("auto", True) - p.select("transition") + p.add("transition1", { + "pattern": "transition", + "brightness": 255, + "delay": 1000, # transition duration + "colors": [(255, 0, 0), (0, 0, 255)], + "auto": True + }) + p.select("transition1") run_for(p, wdt, 6000) # Test 2: Multi-color transition print("Test 2: Multi-color transition (red -> green -> blue -> white)") - p.set_param("dl", 800) - p.set_param("cl", [(255, 0, 0), (0, 255, 0), (0, 0, 255), (255, 255, 255)]) - p.set_param("auto", True) - p.select("transition") + p.add("transition2", { + "pattern": "transition", + "delay": 800, + "colors": [(255, 0, 0), (0, 255, 0), (0, 0, 255), (255, 255, 255)], + "auto": True + }) + p.select("transition2") run_for(p, wdt, 8000) # Test 3: One-shot transition (auto=False) print("Test 3: One-shot transition (auto=False)") - p.set_param("dl", 1000) - p.set_param("cl", [(255, 0, 0), (0, 255, 0)]) - p.set_param("auto", False) - p.select("transition") + p.add("transition3", { + "pattern": "transition", + "delay": 1000, + "colors": [(255, 0, 0), (0, 255, 0)], + "auto": False + }) + p.select("transition3") # Run long enough for a single transition step run_for(p, wdt, 2000) # Test 4: Single-color behavior (should just stay on) print("Test 4: Single-color transition (should hold color)") - p.set_param("cl", [(0, 0, 255)]) - p.set_param("dl", 500) - p.set_param("auto", True) - p.select("transition") + p.add("transition4", { + "pattern": "transition", + "colors": [(0, 0, 255)], + "delay": 500, + "auto": True + }) + p.select("transition4") run_for(p, wdt, 3000) # Cleanup print("Test complete, turning off") - p.select("off") + p.add("cleanup_off", {"pattern": "off"}) + p.select("cleanup_off") run_for(p, wdt, 200) diff --git a/tool.py b/tool.py deleted file mode 100644 index a4754cf..0000000 --- a/tool.py +++ /dev/null @@ -1,734 +0,0 @@ -#!/usr/bin/env python3 -""" -LED Bar Configuration Web App - -Flask-based web UI for downloading, editing, and uploading settings.json -to/from MicroPython devices via mpremote. -""" - -import json -import tempfile -import subprocess -import os -from pathlib import Path - -from flask import ( - Flask, - render_template_string, - request, - redirect, - url_for, - flash, -) - - -app = Flask(__name__) -app.secret_key = "change-me-in-production" - - -SETTINGS_CONFIG = [ - ("led_pin", "LED Pin", "number"), - ("num_leds", "Number of LEDs", "number"), - ("color_order", "Color Order", "choice", ["rgb", "rbg", "grb", "gbr", "brg", "bgr"]), - ("name", "Device Name", "text"), - ("pattern", "Pattern", "text"), - ("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"), - ("debug", "Debug Mode", "choice", ["True", "False"]), -] - - -def _run_mpremote_copy(from_device: bool, device: str, temp_path: str) -> None: - if from_device: - cmd = ["mpremote", "connect", device, "cp", ":/settings.json", temp_path] - else: - 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 RuntimeError(f"mpremote error: {result.stderr.strip() or result.stdout.strip()}") - - -def download_settings(device: str) -> dict: - """Download settings.json from the device using mpremote.""" - temp_file = tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) - temp_path = temp_file.name - temp_file.close() - - try: - _run_mpremote_copy(from_device=True, device=device, temp_path=temp_path) - with open(temp_path, "r", encoding="utf-8") as f: - return json.load(f) - finally: - if os.path.exists(temp_path): - try: - os.unlink(temp_path) - except OSError: - pass - - -def upload_settings(device: str, settings: dict) -> None: - """Upload settings.json to the device using mpremote and reset device.""" - temp_file = tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) - temp_path = temp_file.name - - try: - json.dump(settings, temp_file, indent=2) - temp_file.close() - - _run_mpremote_copy(from_device=False, device=device, temp_path=temp_path) - - # Reset device (best effort) - try: - import serial # type: ignore - - with serial.Serial(device, baudrate=115200) as ser: - ser.write(b"\x03\x03\x04") - except Exception: - reset_cmd = [ - "mpremote", - "connect", - device, - "exec", - "import machine; machine.reset()", - ] - try: - subprocess.run(reset_cmd, capture_output=True, text=True, timeout=5) - except subprocess.TimeoutExpired: - pass - finally: - if os.path.exists(temp_path): - try: - os.unlink(temp_path) - except OSError: - pass - - -def parse_settings_from_form(form) -> dict: - settings = {} - for cfg in SETTINGS_CONFIG: - key = cfg[0] - raw = (form.get(key) or "").strip() - if raw == "": - continue - - if key in ["led_pin", "num_leds", "delay", "brightness", "id", "n1", "n2", "n3", "n4", "n5", "n6"]: - try: - settings[key] = int(raw) - except ValueError: - settings[key] = raw - elif key == "debug": - settings[key] = raw == "True" - else: - settings[key] = raw - return settings - - -TEMPLATE = """ - - - - - LED Bar Configuration - - - - -
-
-
-

- LED Bar Configuration - Web Console -

-
- - - Raspberry Pi · MicroPython - - settings.json live editor -
-
-
- Device: {{ device or "/dev/ttyACM0" }} -
-
- -
-
-
-
-

Device Connection

- Connect to your MicroPython LED controller and sync configuration -
-
- -
- -
- - - -
- -
- - {{ status or "Ready" }} -
- -
- Tip: - Download from device → tweak parameters → Upload and reboot. -
- -
- -
-
-

LED Settings

- Edit all fields before uploading back to your controller -
-
- -
- {% for field in settings_config %} - {% set key, label, field_type = field[0], field[1], field[2] %} -
- - {% if field_type == 'choice' %} - {% set choices = field[3] %} - - {% else %} - - {% endif %} -
- {% endfor %} -
- -
- - -
-
-
- -
-
-
-

Raw JSON

- For advanced editing, paste or copy the full settings.json -
-
- -
- - - - -
- - -
-
-
-
-
- -
- {% with messages = get_flashed_messages(with_categories=true) %} - {% if messages %} - {% for category, message in messages %} -
- {{ message }} -
- {% endfor %} - {% endif %} - {% endwith %} -
- - -""" - - -@app.route("/", methods=["GET"]) -def index(): - return render_template_string( - TEMPLATE, - device="/dev/ttyACM0", - settings={}, - settings_config=SETTINGS_CONFIG, - status="Ready", - status_type="ok", - raw_json="{}", - ) - - -@app.route("/", methods=["POST"]) -def handle_action(): - action = request.form.get("action") or "" - device = (request.form.get("device") or "/dev/ttyACM0").strip() - raw_json = (request.form.get("raw_json") or "").strip() - - settings = {} - status = "Ready" - status_type = "ok" - - if action == "download": - if not device: - flash("Please specify a device.", "error") - status, status_type = "Missing device.", "error" - else: - try: - settings = download_settings(device) - raw_json = json.dumps(settings, indent=2) - flash(f"Settings downloaded from {device}.", "success") - status = f"Settings downloaded from {device}" - except subprocess.TimeoutExpired: - flash("Connection timeout. Check device connection.", "error") - status, status_type = "Connection timeout.", "error" - except FileNotFoundError: - flash("mpremote not found. Install with: pip install mpremote", "error") - status, status_type = "mpremote not found.", "error" - except Exception as exc: # pylint: disable=broad-except - flash(f"Failed to download settings: {exc}", "error") - status, status_type = "Download failed.", "error" - - elif action == "upload": - if not device: - flash("Please specify a device.", "error") - status, status_type = "Missing device.", "error" - else: - # Take current form fields as source of truth, falling back to JSON if present - if raw_json: - try: - settings = json.loads(raw_json) - except json.JSONDecodeError: - flash("Raw JSON is invalid; using form values instead.", "error") - settings = {} - form_settings = parse_settings_from_form(request.form) - settings.update(form_settings) - - if not settings: - flash("No settings to upload. Download or provide settings first.", "error") - status, status_type = "No settings to upload.", "error" - else: - try: - upload_settings(device, settings) - raw_json = json.dumps(settings, indent=2) - flash(f"Settings uploaded and device reset on {device}.", "success") - status = f"Settings uploaded and device reset on {device}" - except subprocess.TimeoutExpired: - flash("Connection timeout. Check device connection.", "error") - status, status_type = "Connection timeout.", "error" - except FileNotFoundError: - flash("mpremote not found. Install with: pip install mpremote", "error") - status, status_type = "mpremote not found.", "error" - except Exception as exc: # pylint: disable=broad-except - flash(f"Failed to upload settings: {exc}", "error") - status, status_type = "Upload failed.", "error" - - elif action == "from_json": - # No-op here, JSON is just edited in the side panel - form_settings = parse_settings_from_form(request.form) - settings.update(form_settings) - if raw_json: - try: - settings.update(json.loads(raw_json)) - flash("JSON merged into form values.", "success") - status = "JSON merged into form." - except json.JSONDecodeError: - flash("Invalid JSON; keeping previous form values.", "error") - status, status_type = "JSON parse error.", "error" - - elif action == "to_form": - if raw_json: - try: - settings = json.loads(raw_json) - flash("Form fields updated from JSON.", "success") - status = "Form fields updated from JSON." - except json.JSONDecodeError: - flash("Invalid JSON; could not update form fields.", "error") - status, status_type = "JSON parse error.", "error" - - elif action == "pretty": - if raw_json: - try: - parsed = json.loads(raw_json) - raw_json = json.dumps(parsed, indent=2) - settings = parsed if isinstance(parsed, dict) else {} - flash("JSON pretty-printed.", "success") - status = "JSON pretty-printed." - except json.JSONDecodeError: - flash("Invalid JSON; cannot pretty-print.", "error") - status, status_type = "JSON parse error.", "error" - - elif action == "clear": - settings = {} - raw_json = "{}" - flash("Form cleared.", "success") - status = "Form cleared." - - else: - # Unknown / initial action: just reflect form values back - settings = parse_settings_from_form(request.form) - if raw_json and not settings: - try: - settings = json.loads(raw_json) - except json.JSONDecodeError: - pass - - return render_template_string( - TEMPLATE, - device=device, - settings=settings, - settings_config=SETTINGS_CONFIG, - status=status, - status_type=status_type, - raw_json=raw_json or json.dumps(settings or {}, indent=2), - ) - - -def main() -> None: - # Bind to all interfaces so you can reach it from your LAN: - # python web_app.py - # Then open: http://:5000/ - app.run(host="0.0.0.0", port=5000, debug=False) - - -if __name__ == "__main__": - main() - -