Compare commits
58 Commits
822bbc40e6
...
patterns
Author | SHA1 | Date | |
---|---|---|---|
44cb35d1aa | |||
fc080f7796 | |||
70fe5a0cdc | |||
2a7b5527a5 | |||
50545e3170 | |||
d2826a0f63 | |||
87fc74bb51 | |||
03f3f02da8 | |||
524db5e979 | |||
279416cded | |||
fbd14f2e16 | |||
1989f6f5c9 | |||
a19b1e86f2 | |||
c63e907204 | |||
b7920e224f | |||
42e92dafc8 | |||
0b6eb9724f | |||
55ef5c1580 | |||
c15f9787a7 | |||
3d0078f118 | |||
9e72dba035 | |||
3d7dd754eb | |||
2dd20fa51b | |||
d33bd6b0e4 | |||
8902adf18c | |||
9abd425f46 | |||
ee28b5805d | |||
ec29dbdd01 | |||
3fa9377438 | |||
ec049b52c0 | |||
a009ea85bc | |||
bd2e6e56cf | |||
37c7280a15 | |||
2f10d4cabd | |||
385dcffe68 | |||
fa0578349b | |||
4a36ff0da0 | |||
bd4046572c | |||
fdd299b063 | |||
a44ef2d0ad | |||
2d1208e223 | |||
67279a8f46 | |||
a52ac3df99 | |||
c4356cf354 | |||
0c219e0697 | |||
cee8c20176 | |||
135f6b06f8 | |||
0aa3803f20 | |||
ed3351a20b | |||
c8bcb85062 | |||
17d33d98e2 | |||
afaf4c02e4 | |||
e1e472b2e4 | |||
2f6704a346 | |||
60dddafc04 | |||
db94530f29 | |||
31081d98ee | |||
b681ff02d8 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1 +1,2 @@
|
||||
settings.json
|
||||
settings.json
|
||||
.venv
|
||||
|
6
Pipfile
6
Pipfile
@@ -6,8 +6,14 @@ name = "pypi"
|
||||
[packages]
|
||||
mpremote = "*"
|
||||
pyserial = "*"
|
||||
esptool = "*"
|
||||
watchfiles = "*"
|
||||
|
||||
[dev-packages]
|
||||
|
||||
[requires]
|
||||
python_version = "3.12"
|
||||
|
||||
[scripts]
|
||||
|
||||
dev = 'watchfiles "./dev.py /dev/ttyACM0 src reset follow"'
|
561
Pipfile.lock
generated
561
Pipfile.lock
generated
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "314935c581ed025688a743136699c770653f47d605a9d0d23b95f5ed73c747b4"
|
||||
"sha256": "7b8033c15743e27f2589635c75bd0bb86ffc3a725b179d7db9ef200119aa9164"
|
||||
},
|
||||
"pipfile-spec": 6,
|
||||
"requires": {
|
||||
@@ -16,14 +16,356 @@
|
||||
]
|
||||
},
|
||||
"default": {
|
||||
"anyio": {
|
||||
"hashes": [
|
||||
"sha256:3f3fae35c96039744587aa5b8371e7e8e603c0702999535961dd336026973ba6",
|
||||
"sha256:60e474ac86736bbfd6f210f7a61218939c318f43f9972497381f1c5e930ed3d1"
|
||||
],
|
||||
"markers": "python_version >= '3.9'",
|
||||
"version": "==4.10.0"
|
||||
},
|
||||
"bitarray": {
|
||||
"hashes": [
|
||||
"sha256:00628196dd3592972a5183194ab1475dadf9ef2a4cf3fd8c7c184a94934012e8",
|
||||
"sha256:01d6dc548e7fe5c66913c2274f44855b0f8474935acff7811e84fe1f4024c94f",
|
||||
"sha256:056fe779f01a867d572e071c0944ac2f3bf58d8bced326040f0bd060af33a209",
|
||||
"sha256:080a7bf55c432abdae74f25dc3dbff407418346aeae1d43e31f65e8ef114f785",
|
||||
"sha256:0956322bf4d5e2293e57600aa929c241edf1e209e94e12483bf58c5c691432db",
|
||||
"sha256:0a6f9e897907757e9c2d722ae6c203d48a04826a14e1495e33935c8583c163a9",
|
||||
"sha256:0ac446f557eb28e3f7c65372608810ff073840627e9037e22ed10bd081793a34",
|
||||
"sha256:0b47843f2f288fa746dead4394591a3432a358aaad48240283fa230d6e74b0e7",
|
||||
"sha256:11fc8bc65f964c7278deb1b7a69379dab3ecc90095f252deb17365637ebb274d",
|
||||
"sha256:129165b68a3e0c2a633ed0d8557cf5ade24a0b37ca97d7805fa6fc5fb73c19d5",
|
||||
"sha256:139963494fc3dd5caee5e38c0a03783ef50be118565e94b1dbb0210770f0b32d",
|
||||
"sha256:157313a124287cbc8a11b55a75def0dd59e68badbc82c2dc2d204dc852742874",
|
||||
"sha256:16d0edab54bb9d214319418f65bd15cfc4210ec41a16c3dd0b71e626c803212d",
|
||||
"sha256:1971050b447023288a2b694a03b400bd5163829cd67b10f19e757fe87cd1161e",
|
||||
"sha256:1c4e75bbf9ade3d2cdf1b607a8b353b17d9b3cf54e88b2a5a773f50ae6f1bfbc",
|
||||
"sha256:1c9f36055a89b9517db66eb8e80137126bf629c767ceeade4d004e40bc8bcd99",
|
||||
"sha256:2020102a40edd094c0aa80e09203af71c533c41f76ce3237c99fd194a473ea33",
|
||||
"sha256:20febc849a1f858e6a57a7d47b323fe9e727c579ddd526d317ad8831748a66a8",
|
||||
"sha256:220d4b8649ef54ac98e5e0e3dd92230247f67270d1524a8b31aa9859007affb0",
|
||||
"sha256:22188943a29072b684cd7c99e0b2cfc0af317cea3366c583d820507e6d1f2ed4",
|
||||
"sha256:222cb27ff05bc0aec72498d075dba1facec49a76a7da45740690cebbe3e81e43",
|
||||
"sha256:243825f56b58bef28bfc602992a8c6d09bbc625628c195498d6020120d632a09",
|
||||
"sha256:25060e7162e44242a449ed1a14a4e94b5aef340812754c443459f19c7954be91",
|
||||
"sha256:26691454a6770628882b68fe74e9f84ca2a51512edd49cbb025b14df5a9dd85a",
|
||||
"sha256:27d13c7b886afc5d2fc49d6e92f9c96b1f0a14dc7b5502520c29f3da7550d401",
|
||||
"sha256:27eeee915258b105a21a4b0f8aebc5f77bb4dc4fb4063a09dd329fa1fdcbd234",
|
||||
"sha256:29ed022189a7997de46cb9bd4e2e49d6163d4f8d78dea72ac5a0e0293b856810",
|
||||
"sha256:2a324e3007afb5c667026f5235b35efe3c4a95f1b83cd93aa9fce67b42f08e7c",
|
||||
"sha256:2c533c828d0007fac27cf45e5c1a711e5914dd469db5fe6be5f4e606bf2d7f63",
|
||||
"sha256:30ba4fba3de1dca653de41c879349ec6ca521d85cff6a7ca5d2fdd8f76c93781",
|
||||
"sha256:357e07c827bad01f98d0bd0dfdc722f483febeed39140fd75ffd016a451b60b9",
|
||||
"sha256:3800f3c8c9780f281cf590543fd4b3278fea6988202273a260ecc58136895efb",
|
||||
"sha256:3d6f3a94abd8b44b2bf346ca81ab2ff41ab9146c53905eedf5178b19d9fe53bf",
|
||||
"sha256:3eb1390a8b062fe9125e5cc4c5eba990b5d383eec54f2b996e7ce73ac43150f9",
|
||||
"sha256:407920e9318d94cc6c9611aaa5b5e5963a09f1cbfa17b16b66edea453b3754f4",
|
||||
"sha256:42622c42c159ea4535bba7e1e3c97f1fec79505bc6873ae657dc0a8f861c60de",
|
||||
"sha256:4695fcd37478988b1d0a16d5bc0df56dcb677fd5db37f1893d993fd3ebef914b",
|
||||
"sha256:4798f6744fa2633666e17b4ea8ff70250781b52a25afdbf5ffb5e176c58848f1",
|
||||
"sha256:4a5b0d277087a5bf261a607fc6ff4aaffcf80b300cd19b7a1e9754a4649f5fd4",
|
||||
"sha256:4e297fd2e58afe17e33dd80c231c3a9d850279a2a8625aed1d39f9be9534809e",
|
||||
"sha256:507e567aee4806576e20752f22533e8b7ec61e7e75062a7ce9222a0675aa0da6",
|
||||
"sha256:50d702149747852923be60cae125285eca8d189d4c7d8832c0c958d4071a0f78",
|
||||
"sha256:51947a00ae9924584fb14c0c1b1f4c1fd916d9abd6f47582f318ab9c9cb9f3d0",
|
||||
"sha256:52328192d454ca2ddad09fbc088872b014c74b22ecdd5164717dc7e6442014fa",
|
||||
"sha256:531e6dfec8058fcf5d69e863b61e6b28e3749b615a4dcc0ab8ad36307c4017fc",
|
||||
"sha256:54bd71f14a5fa9bae73ef92f2e2be894dc36c7a6d1c4962e5969bd8a9aa39325",
|
||||
"sha256:552a93be286ca485914777461b384761519db313e0a7f3012dca424c9610a4d5",
|
||||
"sha256:583b46b3ba44121de5e87e95ae379932dc5fd2e37ebdf2c11a6d7975891425c1",
|
||||
"sha256:5b58a672ec448fb36839a5fc7bf2b2f60df9a97b872d8bd6ca1a28da6126f5c7",
|
||||
"sha256:5cfbdccddaa0ff07789e9e180db127906c676e479e05c04830cd458945de3511",
|
||||
"sha256:5da4939e241301f5e1d18118695e8d2c300be90431b66bd43a00376acec45e1e",
|
||||
"sha256:5dd9edcab8979a50c2c4dec6d5b66789fb6f630bb52ab90a4548111075a75e48",
|
||||
"sha256:5e304f94c0353f6ae5711533b5793b3a45b17aa2c5b07e656649b0af4e0939b5",
|
||||
"sha256:60408ec9c0bd76f1fa00d28034429a0316246d31069b982a86aec8d5c99e910a",
|
||||
"sha256:62f71b268f14ee6cc3045b95441bfe0518cef1d0b2ffbc6f3e9758f786ff5a03",
|
||||
"sha256:664d462a4c0783fd755fe3440f07b7e46d149859c96caacadf3f28890f19a8de",
|
||||
"sha256:66d8b7a89fac6042f7df9ea97d97ed0f5e404281110a882e3babd909161f85b6",
|
||||
"sha256:6755cfcfa7d8966e704d580c831e39818f85e7b2b7852ad22708973176f0009e",
|
||||
"sha256:679856547f0b27b98811b73756bdf53769c23b045a6f95177cae634daabf1ddf",
|
||||
"sha256:6841c08b51417f8ffe398b2828fc0593440c99525c868f640e0302476745320b",
|
||||
"sha256:68f6e64d4867ee79e25c49d7f35b2b1f04a6d6f778176dcf5b759f3b17a02b2b",
|
||||
"sha256:69d2d507c1174330c71c834b5d65e66181ad7b42b0d88b5b31804ee9b4f5dae7",
|
||||
"sha256:6f0be27d06732e2833b672a8fcc32fa195bdb22161eb88f8890de15e30264a01",
|
||||
"sha256:6f7d2dbe628f3db935622a5b80a5c4d95665cdefc4904372aa3c4d786289477f",
|
||||
"sha256:72760411d60d8d76979a20ed3f15586d824db04668b581b86e61158c2b616db0",
|
||||
"sha256:727f7a969416f02ef5c1256541e06f0836fb615022699fa8e2591e85296c5570",
|
||||
"sha256:77d2368a06a86a18919c05a9b4b0ee9869f770e6a5f414b0fecc911870fe3974",
|
||||
"sha256:79ab1c5f26f23e51d4a44c4397c8a3bf56c306c125dfab6b3eebdfa13d1dca6f",
|
||||
"sha256:79db23eda81627132327ed292bd813a9af64399b98aaac3d42ad8deeed24cd5e",
|
||||
"sha256:7c20d6e6cafce5027e7092beb2ac6eec0d71045d6318b34f36e1387a8c8859a3",
|
||||
"sha256:7e0851a985a7b10f634188117c825ef99d63402555cca5bc32c7bfc5adaf0d6f",
|
||||
"sha256:7e2e1ff784c2cdfd863bad31985851427f2d2796e445cec85080c7510cba4315",
|
||||
"sha256:7f8b12424f8fdf29d1c0749c628bd1530cecfc77374935d096cccc0e4eada232",
|
||||
"sha256:81e84054b22babcd6c5cc1eac0de2bfc1054ecdf742720cbfb36efbe89ec6c30",
|
||||
"sha256:84bb57010a1ab76cf880424a2e0bce8dd26989849d2122ff073aa11bfc271c27",
|
||||
"sha256:870ed23361e2918ab1ffc23fe0ab293abf3c372a68ee7387456d13da3e213008",
|
||||
"sha256:8cf44b012e7493127ce7ca6e469138ac96b3295a117877d5469aabe7c8728d87",
|
||||
"sha256:8d6c9bc14bacdfbfd51fed85f0576973eaaa7b30d81ef93264f8e22b86a9c9f7",
|
||||
"sha256:8d759cecfa8aab4a1eb4e23b6420126b15c7743e85b33f389916bb98c4ecbb84",
|
||||
"sha256:8ef3f0977c21190f949d5cfd71ded09de87d330c6d98bd5ecb5bb1135d666d0d",
|
||||
"sha256:8f95daf0ce2b24815ddf62667229ba5dfc0cfee43eb43b2549766170d0f24ae9",
|
||||
"sha256:911b4a16dce370657e5b8d8b6ba0fbb50dd5e2b24c4416f4b9e664503d3f0502",
|
||||
"sha256:96117212229905da864794df9ea7bd54987c30a5dcbab3432edc3f344231adae",
|
||||
"sha256:963cbcf296943f7017470d0b705e63e908f32b4f7dbe43f72c22f6fe1bd9ef66",
|
||||
"sha256:975a118aa019d745f1398613b27fd8789f60a8cea057a00cdc1abedee123ffe6",
|
||||
"sha256:9930853d451086c4c084d83a87294bdb0c5bc0fa4105a26c487ac09ea62e565b",
|
||||
"sha256:99d16862e802e7c50c3b6cdd1bf041b6142335c9c2b426631f731257adfe5a15",
|
||||
"sha256:9ed4a2852b3de7a64884afcc6936db771707943249a060aec8e551c16361d478",
|
||||
"sha256:9f7796959c9c036a115d34696563f75d4a2912d3b97c15c15f2a36bdd5496ce9",
|
||||
"sha256:a04b7a9017b8d0341ebbe77f61b74df1cf1b714f42b671a06f4912dc93d82597",
|
||||
"sha256:a1b3c4ca3bec8e0ad9d32ce62444c5f3913588124a922629aa7d39357b2adf3f",
|
||||
"sha256:a290a417608f50137bec731d1f22ff3efebac72845530807a8433b2db9358c95",
|
||||
"sha256:a33f7c5acf44961f29018b13f0b5f5e1589ac0cfdf75a97c9774cf7ec84d09e0",
|
||||
"sha256:a39be79a7c36e9a2e20376261c30deb3cdca86b50f7462ae9ff10a755c6720b9",
|
||||
"sha256:a50a66fa34dd7f9dcdbc7602a1b7bf6f9ab030b4f43e892324193423d9ede180",
|
||||
"sha256:a5ce1bdee102f7e60c075274df10b892d9ff5183ad6f5f515973eda8903dfe4c",
|
||||
"sha256:a763dd33d6e27c9b4db3f8089a5fa39179a8a3cf48ce702b24a857d7c621333c",
|
||||
"sha256:a773199dc42b5d02fcd46c8add552da2c4725ce2caa069527c7e27b5b6089e85",
|
||||
"sha256:aa3c925502bd0b957a96a5619134bcdc0382ef73cffd40bad218ced3586bcf8d",
|
||||
"sha256:aeb6db2f4ab54ac21a3851d05130a2aa78a6f6a5f14003f9ae3114fb8b210850",
|
||||
"sha256:af670708e145b048ead87375b899229443f2d0b4af2d1450d7701c74cd932b03",
|
||||
"sha256:afa24e5750c9b89ad5a7efef037efe49f4e339f20a94bf678c422c0c71e1207a",
|
||||
"sha256:b02cc1cac9099c0ec72da09593e7fadb1b6cf88a101acc8153592c700d732d80",
|
||||
"sha256:b37c9ea942395de029be270f0eca8c141eb14e8455941495cd3b6f95bbe465f4",
|
||||
"sha256:b3b521e117ab991d6b3b830656f464b354a42dbea2ca16a0e7d93d573f7ab7ff",
|
||||
"sha256:b5ad8261f47c2a72d0f676bc40f752db8cfdcab911e970753343836e41d5a9a7",
|
||||
"sha256:b9616ea14917d06736339cf36bb9eaf4eb52110a74136b0dc5eff94e92417d22",
|
||||
"sha256:b9a03767c937b621ee267507bc394df97fb2f8f61130f39f2033f3c6c191f124",
|
||||
"sha256:b9ae0008cff25e154ef1e3975a1705d344e844ffdeb34c25b007fd48c876e95d",
|
||||
"sha256:bdd6412c1f38da7565126b174f4e644f362e317ef0560fac1fb9d0c70900ff4d",
|
||||
"sha256:bfc417e58f277e949ed662d9cd050ddbb00c0dea8a828abaccc93dc357b7a6d1",
|
||||
"sha256:c15b9e37bbca59657e4dcc63ad068c821a4676def15f04742c406748a0a11b9c",
|
||||
"sha256:c677849947d523a082be6e0b5c9137f443a54e951a1711ef003ec52910c41ece",
|
||||
"sha256:c9d247fcc33c90f2758f4162693250341e3f38cd094f64390076ef33ad0887f9",
|
||||
"sha256:ca643295bf5441dd38dadf7571ca4b63961820eedbffbe46ceba0893bf226203",
|
||||
"sha256:ca87f639094c72268e17bc7f57c1225cc38f9e191a489a0134762e3fec402c1a",
|
||||
"sha256:cc060bc17b9de27874997d612e37d52f72092f9b59d1e04284a90ed8113cadca",
|
||||
"sha256:ccf4a73e07bfbd790443d6b3c1f1447ffda23cc9391e40c035d9b7d3514b57b8",
|
||||
"sha256:cf36cadeb9c989f760a13058dbc455e5406ec3d2d247c705c8d4bc6dd1b0fcc6",
|
||||
"sha256:d47e2bdeba4fb1986af2ba395ce51223f4d460e6e77119439e78f2b592cafade",
|
||||
"sha256:db78cc5c03b446a43413165aa873e2f408e9fd5ddb45533e7bd3b638bace867c",
|
||||
"sha256:dbc5029c61f9ebb2d4c247f13584a0ef0e8e49abb13e56460310821aca3ffcaf",
|
||||
"sha256:ddb319f869d497ef2d3d56319360b61284a9a1d8b3de3bc936748698acfda6be",
|
||||
"sha256:e0e4fdeae6c0a9d886749780ec5dcf469e98f27b312efa93008d03eaa2426fd5",
|
||||
"sha256:e4c5e7edf1e7bcbde3b52058f171a411e2a24a081b3e951d685dfea4c3c383d5",
|
||||
"sha256:e71c9dba78671d38a549e3b2d52514f50e199f9d7e18ed9b0180adeef0d04130",
|
||||
"sha256:e997d22e0d1e08c8752f61675a75d93659f7aa4dbeaee54207f8d877817b4a0c",
|
||||
"sha256:efa5834ba5e6c70b22afdca3894097e5a592d8d483c976359654ba990477799a",
|
||||
"sha256:f2d951002b11962b26afb31f758c18ad39771f287b100fa5adb1d09a47eaaf5b",
|
||||
"sha256:f3f96f57cea35ba19fd23a20b38fa0dfa3d87d582507129b8c8e314aa298f59b",
|
||||
"sha256:f738051052abc95dc17f9a4c92044294a263fb7f762efdb13e528d419005c0e4",
|
||||
"sha256:f76784355060999c36fa807b59faecb38f5769ae58283d00270835773f95e35b",
|
||||
"sha256:f92462ea3888c99439f58f7561ecd5dd4cf8b8b1b259ccf5376667b8c46ee747",
|
||||
"sha256:fefd18b29f3b84a0cdea1d86340219d9871c3b0673a38e722a73a2c39591eaa7"
|
||||
],
|
||||
"version": "==3.6.0"
|
||||
},
|
||||
"bitstring": {
|
||||
"hashes": [
|
||||
"sha256:69d1587f0ac18dc7d93fc7e80d5f447161a33e57027e726dc18a0a8bacf1711a",
|
||||
"sha256:a08bc09d3857216d4c0f412a1611056f1cc2b64fd254fb1e8a0afba7cfa1a95a"
|
||||
],
|
||||
"markers": "python_version >= '3.8'",
|
||||
"version": "==4.3.1"
|
||||
},
|
||||
"cffi": {
|
||||
"hashes": [
|
||||
"sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8",
|
||||
"sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2",
|
||||
"sha256:0e2b1fac190ae3ebfe37b979cc1ce69c81f4e4fe5746bb401dca63a9062cdaf1",
|
||||
"sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15",
|
||||
"sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36",
|
||||
"sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824",
|
||||
"sha256:1d599671f396c4723d016dbddb72fe8e0397082b0a77a4fab8028923bec050e8",
|
||||
"sha256:28b16024becceed8c6dfbc75629e27788d8a3f9030691a1dbf9821a128b22c36",
|
||||
"sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17",
|
||||
"sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf",
|
||||
"sha256:31000ec67d4221a71bd3f67df918b1f88f676f1c3b535a7eb473255fdc0b83fc",
|
||||
"sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3",
|
||||
"sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed",
|
||||
"sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702",
|
||||
"sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1",
|
||||
"sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8",
|
||||
"sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903",
|
||||
"sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6",
|
||||
"sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d",
|
||||
"sha256:636062ea65bd0195bc012fea9321aca499c0504409f413dc88af450b57ffd03b",
|
||||
"sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e",
|
||||
"sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be",
|
||||
"sha256:6f17be4345073b0a7b8ea599688f692ac3ef23ce28e5df79c04de519dbc4912c",
|
||||
"sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683",
|
||||
"sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9",
|
||||
"sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c",
|
||||
"sha256:7596d6620d3fa590f677e9ee430df2958d2d6d6de2feeae5b20e82c00b76fbf8",
|
||||
"sha256:78122be759c3f8a014ce010908ae03364d00a1f81ab5c7f4a7a5120607ea56e1",
|
||||
"sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4",
|
||||
"sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655",
|
||||
"sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67",
|
||||
"sha256:9755e4345d1ec879e3849e62222a18c7174d65a6a92d5b346b1863912168b595",
|
||||
"sha256:98e3969bcff97cae1b2def8ba499ea3d6f31ddfdb7635374834cf89a1a08ecf0",
|
||||
"sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65",
|
||||
"sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41",
|
||||
"sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6",
|
||||
"sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401",
|
||||
"sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6",
|
||||
"sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3",
|
||||
"sha256:b2ab587605f4ba0bf81dc0cb08a41bd1c0a5906bd59243d56bad7668a6fc6c16",
|
||||
"sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93",
|
||||
"sha256:c03e868a0b3bc35839ba98e74211ed2b05d2119be4e8a0f224fba9384f1fe02e",
|
||||
"sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4",
|
||||
"sha256:c7eac2ef9b63c79431bc4b25f1cd649d7f061a28808cbc6c47b534bd789ef964",
|
||||
"sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c",
|
||||
"sha256:ca74b8dbe6e8e8263c0ffd60277de77dcee6c837a3d0881d8c1ead7268c9e576",
|
||||
"sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0",
|
||||
"sha256:cdf5ce3acdfd1661132f2a9c19cac174758dc2352bfe37d98aa7512c6b7178b3",
|
||||
"sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662",
|
||||
"sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3",
|
||||
"sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff",
|
||||
"sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5",
|
||||
"sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd",
|
||||
"sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f",
|
||||
"sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5",
|
||||
"sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14",
|
||||
"sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d",
|
||||
"sha256:e221cf152cff04059d011ee126477f0d9588303eb57e88923578ace7baad17f9",
|
||||
"sha256:e31ae45bc2e29f6b2abd0de1cc3b9d5205aa847cafaecb8af1476a609a2f6eb7",
|
||||
"sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382",
|
||||
"sha256:f1e22e8c4419538cb197e4dd60acc919d7696e5ef98ee4da4e01d3f8cfa4cc5a",
|
||||
"sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e",
|
||||
"sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a",
|
||||
"sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4",
|
||||
"sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99",
|
||||
"sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87",
|
||||
"sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b"
|
||||
],
|
||||
"markers": "platform_python_implementation != 'PyPy'",
|
||||
"version": "==1.17.1"
|
||||
},
|
||||
"click": {
|
||||
"hashes": [
|
||||
"sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202",
|
||||
"sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b"
|
||||
],
|
||||
"markers": "python_version >= '3.10'",
|
||||
"version": "==8.2.1"
|
||||
},
|
||||
"cryptography": {
|
||||
"hashes": [
|
||||
"sha256:00e8724bdad672d75e6f069b27970883179bd472cd24a63f6e620ca7e41cc0c5",
|
||||
"sha256:048e7ad9e08cf4c0ab07ff7f36cc3115924e22e2266e034450a890d9e312dd74",
|
||||
"sha256:0d9ef57b6768d9fa58e92f4947cea96ade1233c0e236db22ba44748ffedca394",
|
||||
"sha256:18f878a34b90d688982e43f4b700408b478102dd58b3e39de21b5ebf6509c301",
|
||||
"sha256:1b7fa6a1c1188c7ee32e47590d16a5a0646270921f8020efc9a511648e1b2e08",
|
||||
"sha256:20ae4906a13716139d6d762ceb3e0e7e110f7955f3bc3876e3a07f5daadec5f3",
|
||||
"sha256:20d15aed3ee522faac1a39fbfdfee25d17b1284bafd808e1640a74846d7c4d1b",
|
||||
"sha256:2384f2ab18d9be88a6e4f8972923405e2dbb8d3e16c6b43f15ca491d7831bd18",
|
||||
"sha256:275ba5cc0d9e320cd70f8e7b96d9e59903c815ca579ab96c1e37278d231fc402",
|
||||
"sha256:2dac5ec199038b8e131365e2324c03d20e97fe214af051d20c49db129844e8b3",
|
||||
"sha256:31a2b9a10530a1cb04ffd6aa1cd4d3be9ed49f7d77a4dafe198f3b382f41545c",
|
||||
"sha256:3436128a60a5e5490603ab2adbabc8763613f638513ffa7d311c900a8349a2a0",
|
||||
"sha256:3b5bf5267e98661b9b888a9250d05b063220dfa917a8203744454573c7eb79db",
|
||||
"sha256:3de77e4df42ac8d4e4d6cdb342d989803ad37707cf8f3fbf7b088c9cbdd46427",
|
||||
"sha256:44647c5d796f5fc042bbc6d61307d04bf29bccb74d188f18051b635f20a9c75f",
|
||||
"sha256:550ae02148206beb722cfe4ef0933f9352bab26b087af00e48fdfb9ade35c5b3",
|
||||
"sha256:599c8d7df950aa68baa7e98f7b73f4f414c9f02d0e8104a30c0182a07732638b",
|
||||
"sha256:5b64e668fc3528e77efa51ca70fadcd6610e8ab231e3e06ae2bab3b31c2b8ed9",
|
||||
"sha256:5bd6020c80c5b2b2242d6c48487d7b85700f5e0038e67b29d706f98440d66eb5",
|
||||
"sha256:5c966c732cf6e4a276ce83b6e4c729edda2df6929083a952cc7da973c539c719",
|
||||
"sha256:629127cfdcdc6806dfe234734d7cb8ac54edaf572148274fa377a7d3405b0043",
|
||||
"sha256:705bb7c7ecc3d79a50f236adda12ca331c8e7ecfbea51edd931ce5a7a7c4f012",
|
||||
"sha256:780c40fb751c7d2b0c6786ceee6b6f871e86e8718a8ff4bc35073ac353c7cd02",
|
||||
"sha256:7a3085d1b319d35296176af31c90338eeb2ddac8104661df79f80e1d9787b8b2",
|
||||
"sha256:826b46dae41a1155a0c0e66fafba43d0ede1dc16570b95e40c4d83bfcf0a451d",
|
||||
"sha256:833dc32dfc1e39b7376a87b9a6a4288a10aae234631268486558920029b086ec",
|
||||
"sha256:cc4d66f5dc4dc37b89cfef1bd5044387f7a1f6f0abb490815628501909332d5d",
|
||||
"sha256:d063341378d7ee9c91f9d23b431a3502fc8bfacd54ef0a27baa72a0843b29159",
|
||||
"sha256:e2a21a8eda2d86bb604934b6b37691585bd095c1f788530c1fcefc53a82b3453",
|
||||
"sha256:e40b80ecf35ec265c452eea0ba94c9587ca763e739b8e559c128d23bff7ebbbf",
|
||||
"sha256:e5b3dda1b00fb41da3af4c5ef3f922a200e33ee5ba0f0bc9ecf0b0c173958385",
|
||||
"sha256:ea3c42f2016a5bbf71825537c2ad753f2870191134933196bee408aac397b3d9",
|
||||
"sha256:eccddbd986e43014263eda489abbddfbc287af5cddfd690477993dbb31e31016",
|
||||
"sha256:ee411a1b977f40bd075392c80c10b58025ee5c6b47a822a33c1198598a7a5f05",
|
||||
"sha256:f4028f29a9f38a2025abedb2e409973709c660d44319c61762202206ed577c42",
|
||||
"sha256:f68f833a9d445cc49f01097d95c83a850795921b3f7cc6488731e69bde3288da",
|
||||
"sha256:fc022c1fa5acff6def2fc6d7819bbbd31ccddfe67d075331a65d9cfb28a20983"
|
||||
],
|
||||
"markers": "python_version >= '3.7' and python_full_version not in '3.9.0, 3.9.1'",
|
||||
"version": "==45.0.6"
|
||||
},
|
||||
"esptool": {
|
||||
"hashes": [
|
||||
"sha256:05cc4732eb2a9a7766c9e3531f7943d76ff0ca06dc9cd308d1d3d0b72f74aac2"
|
||||
],
|
||||
"index": "pypi",
|
||||
"markers": "python_version >= '3.10'",
|
||||
"version": "==5.0.2"
|
||||
},
|
||||
"idna": {
|
||||
"hashes": [
|
||||
"sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9",
|
||||
"sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==3.10"
|
||||
},
|
||||
"intelhex": {
|
||||
"hashes": [
|
||||
"sha256:87cc5225657524ec6361354be928adfd56bcf2a3dcc646c40f8f094c39c07db4",
|
||||
"sha256:892b7361a719f4945237da8ccf754e9513db32f5628852785aea108dcd250093"
|
||||
],
|
||||
"version": "==2.3.0"
|
||||
},
|
||||
"markdown-it-py": {
|
||||
"hashes": [
|
||||
"sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1",
|
||||
"sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"
|
||||
],
|
||||
"markers": "python_version >= '3.8'",
|
||||
"version": "==3.0.0"
|
||||
},
|
||||
"mdurl": {
|
||||
"hashes": [
|
||||
"sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8",
|
||||
"sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"
|
||||
],
|
||||
"markers": "python_version >= '3.7'",
|
||||
"version": "==0.1.2"
|
||||
},
|
||||
"mpremote": {
|
||||
"hashes": [
|
||||
"sha256:1a3c16d255748cfe54d4a897908651fc8286233173f7c7b2a0e56ae4b9fa940e",
|
||||
"sha256:d3ae3d0a0ae7713c537be2b6afadd11c7cde5f1750ea1260f6667bb80071b15b"
|
||||
"sha256:7f347318fb6d3bb8f89401d399a05efba39b51c74f747cebe92d3c6a9a4ee0b4",
|
||||
"sha256:daed9b795fdf98edb0c9c4f7f892bf66f075ec5e728bdcc4ab0915abf23d5d17"
|
||||
],
|
||||
"index": "pypi",
|
||||
"markers": "python_version >= '3.4'",
|
||||
"version": "==1.24.1"
|
||||
"version": "==1.26.0"
|
||||
},
|
||||
"platformdirs": {
|
||||
"hashes": [
|
||||
"sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc",
|
||||
"sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4"
|
||||
],
|
||||
"markers": "python_version >= '3.9'",
|
||||
"version": "==4.3.8"
|
||||
},
|
||||
"pycparser": {
|
||||
"hashes": [
|
||||
"sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6",
|
||||
"sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"
|
||||
],
|
||||
"markers": "python_version >= '3.8'",
|
||||
"version": "==2.22"
|
||||
},
|
||||
"pygments": {
|
||||
"hashes": [
|
||||
"sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887",
|
||||
"sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"
|
||||
],
|
||||
"markers": "python_version >= '3.8'",
|
||||
"version": "==2.19.2"
|
||||
},
|
||||
"pyserial": {
|
||||
"hashes": [
|
||||
@@ -32,6 +374,217 @@
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==3.5"
|
||||
},
|
||||
"pyyaml": {
|
||||
"hashes": [
|
||||
"sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff",
|
||||
"sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48",
|
||||
"sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086",
|
||||
"sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e",
|
||||
"sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133",
|
||||
"sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5",
|
||||
"sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484",
|
||||
"sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee",
|
||||
"sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5",
|
||||
"sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68",
|
||||
"sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a",
|
||||
"sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf",
|
||||
"sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99",
|
||||
"sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8",
|
||||
"sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85",
|
||||
"sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19",
|
||||
"sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc",
|
||||
"sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a",
|
||||
"sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1",
|
||||
"sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317",
|
||||
"sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c",
|
||||
"sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631",
|
||||
"sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d",
|
||||
"sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652",
|
||||
"sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5",
|
||||
"sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e",
|
||||
"sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b",
|
||||
"sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8",
|
||||
"sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476",
|
||||
"sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706",
|
||||
"sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563",
|
||||
"sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237",
|
||||
"sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b",
|
||||
"sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083",
|
||||
"sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180",
|
||||
"sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425",
|
||||
"sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e",
|
||||
"sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f",
|
||||
"sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725",
|
||||
"sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183",
|
||||
"sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab",
|
||||
"sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774",
|
||||
"sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725",
|
||||
"sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e",
|
||||
"sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5",
|
||||
"sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d",
|
||||
"sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290",
|
||||
"sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44",
|
||||
"sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed",
|
||||
"sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4",
|
||||
"sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba",
|
||||
"sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12",
|
||||
"sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"
|
||||
],
|
||||
"markers": "python_version >= '3.8'",
|
||||
"version": "==6.0.2"
|
||||
},
|
||||
"reedsolo": {
|
||||
"hashes": [
|
||||
"sha256:2b6a3e402a1ee3e1eea3f932f81e6c0b7bbc615588074dca1dbbcdeb055002bd",
|
||||
"sha256:c1359f02742751afe0f1c0de9f0772cc113835aa2855d2db420ea24393c87732"
|
||||
],
|
||||
"version": "==1.7.0"
|
||||
},
|
||||
"rich": {
|
||||
"hashes": [
|
||||
"sha256:536f5f1785986d6dbdea3c75205c473f970777b4a0d6c6dd1b696aa05a3fa04f",
|
||||
"sha256:e497a48b844b0320d45007cdebfeaeed8db2a4f4bcf49f15e455cfc4af11eaa8"
|
||||
],
|
||||
"markers": "python_full_version >= '3.8.0'",
|
||||
"version": "==14.1.0"
|
||||
},
|
||||
"rich-click": {
|
||||
"hashes": [
|
||||
"sha256:c3fa81ed8a671a10de65a9e20abf642cfdac6fdb882db1ef465ee33919fbcfe2",
|
||||
"sha256:fd98c0ab9ddc1cf9c0b7463f68daf28b4d0033a74214ceb02f761b3ff2af3136"
|
||||
],
|
||||
"markers": "python_version >= '3.7'",
|
||||
"version": "==1.8.9"
|
||||
},
|
||||
"sniffio": {
|
||||
"hashes": [
|
||||
"sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2",
|
||||
"sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"
|
||||
],
|
||||
"markers": "python_version >= '3.7'",
|
||||
"version": "==1.3.1"
|
||||
},
|
||||
"typing-extensions": {
|
||||
"hashes": [
|
||||
"sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36",
|
||||
"sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76"
|
||||
],
|
||||
"markers": "python_version >= '3.9'",
|
||||
"version": "==4.14.1"
|
||||
},
|
||||
"watchfiles": {
|
||||
"hashes": [
|
||||
"sha256:00645eb79a3faa70d9cb15c8d4187bb72970b2470e938670240c7998dad9f13a",
|
||||
"sha256:04e4ed5d1cd3eae68c89bcc1a485a109f39f2fd8de05f705e98af6b5f1861f1f",
|
||||
"sha256:0a7d40b77f07be87c6faa93d0951a0fcd8cbca1ddff60a1b65d741bac6f3a9f6",
|
||||
"sha256:0ece16b563b17ab26eaa2d52230c9a7ae46cf01759621f4fbbca280e438267b3",
|
||||
"sha256:11ee4444250fcbeb47459a877e5e80ed994ce8e8d20283857fc128be1715dac7",
|
||||
"sha256:12b0a02a91762c08f7264e2e79542f76870c3040bbc847fb67410ab81474932a",
|
||||
"sha256:12fe8eaffaf0faa7906895b4f8bb88264035b3f0243275e0bf24af0436b27259",
|
||||
"sha256:130fc497b8ee68dce163e4254d9b0356411d1490e868bd8790028bc46c5cc297",
|
||||
"sha256:17ab167cca6339c2b830b744eaf10803d2a5b6683be4d79d8475d88b4a8a4be1",
|
||||
"sha256:199207b2d3eeaeb80ef4411875a6243d9ad8bc35b07fc42daa6b801cc39cc41c",
|
||||
"sha256:20ecc8abbd957046f1fe9562757903f5eaf57c3bce70929fda6c7711bb58074a",
|
||||
"sha256:239736577e848678e13b201bba14e89718f5c2133dfd6b1f7846fa1b58a8532b",
|
||||
"sha256:249590eb75ccc117f488e2fabd1bfa33c580e24b96f00658ad88e38844a040bb",
|
||||
"sha256:27f30e14aa1c1e91cb653f03a63445739919aef84c8d2517997a83155e7a2fcc",
|
||||
"sha256:29e7bc2eee15cbb339c68445959108803dc14ee0c7b4eea556400131a8de462b",
|
||||
"sha256:328dbc9bff7205c215a7807da7c18dce37da7da718e798356212d22696404339",
|
||||
"sha256:32d6d4e583593cb8576e129879ea0991660b935177c0f93c6681359b3654bfa9",
|
||||
"sha256:3366f56c272232860ab45c77c3ca7b74ee819c8e1f6f35a7125556b198bbc6df",
|
||||
"sha256:3434e401f3ce0ed6b42569128b3d1e3af773d7ec18751b918b89cd49c14eaafb",
|
||||
"sha256:37d3d3f7defb13f62ece99e9be912afe9dd8a0077b7c45ee5a57c74811d581a4",
|
||||
"sha256:3a6fd40bbb50d24976eb275ccb55cd1951dfb63dbc27cae3066a6ca5f4beabd5",
|
||||
"sha256:3aba215958d88182e8d2acba0fdaf687745180974946609119953c0e112397dc",
|
||||
"sha256:406520216186b99374cdb58bc48e34bb74535adec160c8459894884c983a149c",
|
||||
"sha256:4281cd9fce9fc0a9dbf0fc1217f39bf9cf2b4d315d9626ef1d4e87b84699e7e8",
|
||||
"sha256:42f92befc848bb7a19658f21f3e7bae80d7d005d13891c62c2cd4d4d0abb3433",
|
||||
"sha256:48aa25e5992b61debc908a61ab4d3f216b64f44fdaa71eb082d8b2de846b7d12",
|
||||
"sha256:5007f860c7f1f8df471e4e04aaa8c43673429047d63205d1630880f7637bca30",
|
||||
"sha256:50a51a90610d0845a5931a780d8e51d7bd7f309ebc25132ba975aca016b576a0",
|
||||
"sha256:51556d5004887045dba3acdd1fdf61dddea2be0a7e18048b5e853dcd37149b86",
|
||||
"sha256:51b81e55d40c4b4aa8658427a3ee7ea847c591ae9e8b81ef94a90b668999353c",
|
||||
"sha256:5366164391873ed76bfdf618818c82084c9db7fac82b64a20c44d335eec9ced5",
|
||||
"sha256:54062ef956807ba806559b3c3d52105ae1827a0d4ab47b621b31132b6b7e2866",
|
||||
"sha256:60022527e71d1d1fda67a33150ee42869042bce3d0fcc9cc49be009a9cded3fb",
|
||||
"sha256:622d6b2c06be19f6e89b1d951485a232e3b59618def88dbeda575ed8f0d8dbf2",
|
||||
"sha256:62cc7a30eeb0e20ecc5f4bd113cd69dcdb745a07c68c0370cea919f373f65d9e",
|
||||
"sha256:693ed7ec72cbfcee399e92c895362b6e66d63dac6b91e2c11ae03d10d503e575",
|
||||
"sha256:6d2404af8db1329f9a3c9b79ff63e0ae7131986446901582067d9304ae8aaf7f",
|
||||
"sha256:7049e52167fc75fc3cc418fc13d39a8e520cbb60ca08b47f6cedb85e181d2f2a",
|
||||
"sha256:7080c4bb3efd70a07b1cc2df99a7aa51d98685be56be6038c3169199d0a1c69f",
|
||||
"sha256:7738027989881e70e3723c75921f1efa45225084228788fc59ea8c6d732eb30d",
|
||||
"sha256:7a7bd57a1bb02f9d5c398c0c1675384e7ab1dd39da0ca50b7f09af45fa435277",
|
||||
"sha256:7b3443f4ec3ba5aa00b0e9fa90cf31d98321cbff8b925a7c7b84161619870bc9",
|
||||
"sha256:7c55b0f9f68590115c25272b06e63f0824f03d4fc7d6deed43d8ad5660cabdbf",
|
||||
"sha256:7fd1b3879a578a8ec2076c7961076df540b9af317123f84569f5a9ddee64ce92",
|
||||
"sha256:8076a5769d6bdf5f673a19d51da05fc79e2bbf25e9fe755c47595785c06a8c72",
|
||||
"sha256:80f811146831c8c86ab17b640801c25dc0a88c630e855e2bef3568f30434d52b",
|
||||
"sha256:8412eacef34cae2836d891836a7fff7b754d6bcac61f6c12ba5ca9bc7e427b68",
|
||||
"sha256:865c8e95713744cf5ae261f3067861e9da5f1370ba91fc536431e29b418676fa",
|
||||
"sha256:86b1e28d4c37e89220e924305cd9f82866bb0ace666943a6e4196c5df4d58dcc",
|
||||
"sha256:891c69e027748b4a73847335d208e374ce54ca3c335907d381fde4e41661b13b",
|
||||
"sha256:8ac164e20d17cc285f2b94dc31c384bc3aa3dd5e7490473b3db043dd70fbccfd",
|
||||
"sha256:8c5701dc474b041e2934a26d31d39f90fac8a3dee2322b39f7729867f932b1d4",
|
||||
"sha256:90ebb429e933645f3da534c89b29b665e285048973b4d2b6946526888c3eb2c7",
|
||||
"sha256:923fec6e5461c42bd7e3fd5ec37492c6f3468be0499bc0707b4bbbc16ac21792",
|
||||
"sha256:935f9edd022ec13e447e5723a7d14456c8af254544cefbc533f6dd276c9aa0d9",
|
||||
"sha256:95ab1594377effac17110e1352989bdd7bdfca9ff0e5eeccd8c69c5389b826d0",
|
||||
"sha256:9974d2f7dc561cce3bb88dfa8eb309dab64c729de85fba32e98d75cf24b66297",
|
||||
"sha256:9c733cda03b6d636b4219625a4acb5c6ffb10803338e437fb614fef9516825ef",
|
||||
"sha256:9dc001c3e10de4725c749d4c2f2bdc6ae24de5a88a339c4bce32300a31ede179",
|
||||
"sha256:9f811079d2f9795b5d48b55a37aa7773680a5659afe34b54cc1d86590a51507d",
|
||||
"sha256:a2726d7bfd9f76158c84c10a409b77a320426540df8c35be172444394b17f7ea",
|
||||
"sha256:a479466da6db5c1e8754caee6c262cd373e6e6c363172d74394f4bff3d84d7b5",
|
||||
"sha256:a543492513a93b001975ae283a51f4b67973662a375a403ae82f420d2c7205ee",
|
||||
"sha256:a89c75a5b9bc329131115a409d0acc16e8da8dfd5867ba59f1dd66ae7ea8fa82",
|
||||
"sha256:a8f6f72974a19efead54195bc9bed4d850fc047bb7aa971268fd9a8387c89011",
|
||||
"sha256:a9ccbf1f129480ed3044f540c0fdbc4ee556f7175e5ab40fe077ff6baf286d4e",
|
||||
"sha256:aa0cc8365ab29487eb4f9979fd41b22549853389e22d5de3f134a6796e1b05a4",
|
||||
"sha256:adb4167043d3a78280d5d05ce0ba22055c266cf8655ce942f2fb881262ff3cdf",
|
||||
"sha256:af06c863f152005c7592df1d6a7009c836a247c9d8adb78fef8575a5a98699db",
|
||||
"sha256:b067915e3c3936966a8607f6fe5487df0c9c4afb85226613b520890049deea20",
|
||||
"sha256:b7c5f6fe273291f4d414d55b2c80d33c457b8a42677ad14b4b47ff025d0893e4",
|
||||
"sha256:b915daeb2d8c1f5cee4b970f2e2c988ce6514aace3c9296e58dd64dc9aa5d575",
|
||||
"sha256:ba0e3255b0396cac3cc7bbace76404dd72b5438bf0d8e7cefa2f79a7f3649caa",
|
||||
"sha256:bda8136e6a80bdea23e5e74e09df0362744d24ffb8cd59c4a95a6ce3d142f79c",
|
||||
"sha256:bfe3c517c283e484843cb2e357dd57ba009cff351edf45fb455b5fbd1f45b15f",
|
||||
"sha256:c588c45da9b08ab3da81d08d7987dae6d2a3badd63acdb3e206a42dbfa7cb76f",
|
||||
"sha256:c600e85f2ffd9f1035222b1a312aff85fd11ea39baff1d705b9b047aad2ce267",
|
||||
"sha256:c68e9f1fcb4d43798ad8814c4c1b61547b014b667216cb754e606bfade587018",
|
||||
"sha256:c9649dfc57cc1f9835551deb17689e8d44666315f2e82d337b9f07bd76ae3aa2",
|
||||
"sha256:cb45350fd1dc75cd68d3d72c47f5b513cb0578da716df5fba02fff31c69d5f2d",
|
||||
"sha256:cbcf8630ef4afb05dc30107bfa17f16c0896bb30ee48fc24bf64c1f970f3b1fd",
|
||||
"sha256:cbd949bdd87567b0ad183d7676feb98136cde5bb9025403794a4c0db28ed3a47",
|
||||
"sha256:cc08ef8b90d78bfac66f0def80240b0197008e4852c9f285907377b2947ffdcb",
|
||||
"sha256:cd17a1e489f02ce9117b0de3c0b1fab1c3e2eedc82311b299ee6b6faf6c23a29",
|
||||
"sha256:d05686b5487cfa2e2c28ff1aa370ea3e6c5accfe6435944ddea1e10d93872147",
|
||||
"sha256:d0e10e6f8f6dc5762adee7dece33b722282e1f59aa6a55da5d493a97282fedd8",
|
||||
"sha256:d181ef50923c29cf0450c3cd47e2f0557b62218c50b2ab8ce2ecaa02bd97e670",
|
||||
"sha256:d1caf40c1c657b27858f9774d5c0e232089bca9cb8ee17ce7478c6e9264d2587",
|
||||
"sha256:d7642b9bc4827b5518ebdb3b82698ada8c14c7661ddec5fe719f3e56ccd13c97",
|
||||
"sha256:d9481174d3ed982e269c090f780122fb59cee6c3796f74efe74e70f7780ed94c",
|
||||
"sha256:d9ba68ec283153dead62cbe81872d28e053745f12335d037de9cbd14bd1877f5",
|
||||
"sha256:da71945c9ace018d8634822f16cbc2a78323ef6c876b1d34bbf5d5222fd6a72e",
|
||||
"sha256:dc44678a72ac0910bac46fa6a0de6af9ba1355669b3dfaf1ce5f05ca7a74364e",
|
||||
"sha256:df32d59cb9780f66d165a9a7a26f19df2c7d24e3bd58713108b41d0ff4f929c6",
|
||||
"sha256:df670918eb7dd719642e05979fc84704af913d563fd17ed636f7c4783003fdcc",
|
||||
"sha256:e78b6ed8165996013165eeabd875c5dfc19d41b54f94b40e9fff0eb3193e5e8e",
|
||||
"sha256:ed8fc66786de8d0376f9f913c09e963c66e90ced9aa11997f93bdb30f7c872a8",
|
||||
"sha256:eff4b8d89f444f7e49136dc695599a591ff769300734446c0a86cba2eb2f9895",
|
||||
"sha256:f21af781a4a6fbad54f03c598ab620e3a77032c5878f3d780448421a6e1818c7",
|
||||
"sha256:f2bcdc54ea267fe72bfc7d83c041e4eb58d7d8dc6f578dfddb52f037ce62f432",
|
||||
"sha256:f2f0498b7d2a3c072766dba3274fe22a183dbea1f99d188f1c6c72209a1063dc",
|
||||
"sha256:f7208ab6e009c627b7557ce55c465c98967e8caa8b11833531fdf95799372633",
|
||||
"sha256:f7590d5a455321e53857892ab8879dce62d1f4b04748769f5adf2e707afb9d4f",
|
||||
"sha256:fa257a4d0d21fcbca5b5fcba9dca5a78011cb93c0323fb8855c6d2dfbc76eb77",
|
||||
"sha256:fba9b62da882c1be1280a7584ec4515d0a6006a94d6e5819730ec2eab60ffe12",
|
||||
"sha256:fe4371595edf78c41ef8ac8df20df3943e13defd0efcb732b2e393b5a8a7a71f"
|
||||
],
|
||||
"index": "pypi",
|
||||
"markers": "python_version >= '3.9'",
|
||||
"version": "==1.1.0"
|
||||
}
|
||||
},
|
||||
"develop": {}
|
||||
|
23
README.md
23
README.md
@@ -8,14 +8,31 @@ https://pipenv.pypa.io/en/latest/installation.html
|
||||
|
||||
```pipenv sync ```
|
||||
|
||||
## Install Micropython
|
||||
|
||||
https://micropython.org/resources/firmware/ESP32_GENERIC_C3-20241129-v1.24.1.bin
|
||||
|
||||
```
|
||||
pipenv run esptool.py --port PORTNAME erase_flash
|
||||
pipenv run esptool.py --port PORTNAME --baud 460800 write_flash 0 ESP32_GENERIC_C3-20241129-v1.24.1.bin
|
||||
```
|
||||
|
||||
## Upload libraries and src
|
||||
|
||||
```pipenv run ./dev.py <PORT> lib src```
|
||||
```pipenv run ./dev.py PORTNAME lib src```
|
||||
|
||||
## Upload src, reset and follow
|
||||
|
||||
```pipenv run ./dev.py <PORT> src reset follow```
|
||||
|
||||
```pipenv run ./dev.py PORTNAME src reset follow```
|
||||
|
||||
|
||||
## Connect
|
||||
|
||||
Connect to wifi nextwork called led-xxxxxx...
|
||||
|
||||
http://192.168.4.1/
|
||||
|
||||
|
||||
|
||||

|
||||

|
BIN
screenshots/controls.png
Normal file
BIN
screenshots/controls.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 26 KiB |
BIN
screenshots/settings.png
Normal file
BIN
screenshots/settings.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 26 KiB |
20
src/boot.py
20
src/boot.py
@@ -1,19 +1,9 @@
|
||||
import settings
|
||||
import wifi
|
||||
import time
|
||||
from settings import Settings
|
||||
|
||||
print(wifi.ap('qwerty'))
|
||||
s = Settings()
|
||||
|
||||
|
||||
settings = Settings()
|
||||
ssid = settings.get('wifi', {}).get('ssid', None)
|
||||
password = settings.get('wifi', {}).get('password', None)
|
||||
ip = settings.get('wifi', {}).get('ip', None)
|
||||
gateway = settings.get('wifi', {}).get('gateway', None)
|
||||
|
||||
for i in range(10):
|
||||
config = wifi.connect(ssid, password, ip, gateway)
|
||||
if config:
|
||||
print(config)
|
||||
break
|
||||
time.sleep(0.1)
|
||||
name = s.get('name', 'led')
|
||||
password = s.get("ap_password", "")
|
||||
wifi.ap(name, password)
|
||||
|
45
src/main.py
45
src/main.py
@@ -1,27 +1,39 @@
|
||||
import asyncio
|
||||
import aioespnow
|
||||
from settings import Settings
|
||||
from web import web
|
||||
from patterns import Patterns
|
||||
import gc
|
||||
import utime
|
||||
import machine
|
||||
import ntptime
|
||||
import time
|
||||
import wifi
|
||||
|
||||
import json
|
||||
from p2p import p2p
|
||||
|
||||
async def main():
|
||||
|
||||
|
||||
|
||||
settings = Settings()
|
||||
|
||||
patterns = Patterns(4, settings["num_leds"], selected=settings["selected_pattern"])
|
||||
patterns.set_color1(tuple(int(settings["color1"][i:i+2], 16) for i in (1, 5, 3)))
|
||||
patterns.set_color2(tuple(int(settings["color2"][i:i+2], 16) for i in (1, 5, 3)))
|
||||
patterns = Patterns(settings["led_pin"], settings["num_leds"], selected=settings["pattern"])
|
||||
if settings["color_order"] == "rbg": color_order = (1, 5, 3)
|
||||
else: color_order = (1, 3, 5)
|
||||
patterns.set_color1(tuple(int(settings["color1"][i:i+2], 16) for i in color_order))
|
||||
patterns.set_color2(tuple(int(settings["color2"][i:i+2], 16) for i in color_order))
|
||||
patterns.set_brightness(int(settings["brightness"]))
|
||||
patterns.set_delay(int(settings["delay"]))
|
||||
|
||||
async def tick():
|
||||
while True:
|
||||
patterns.tick()
|
||||
await asyncio.sleep_ms(0)
|
||||
|
||||
async def system():
|
||||
while True:
|
||||
gc.collect()
|
||||
for i in range(60):
|
||||
wdt.feed()
|
||||
await asyncio.sleep(1)
|
||||
|
||||
w = web(settings, patterns)
|
||||
print(settings)
|
||||
# start the server in a bacakground task
|
||||
@@ -30,26 +42,13 @@ async def main():
|
||||
wdt = machine.WDT(timeout=10000)
|
||||
wdt.feed()
|
||||
|
||||
async def tick():
|
||||
while True:
|
||||
patterns.tick()
|
||||
await asyncio.sleep_ms(1)
|
||||
|
||||
asyncio.create_task(tick())
|
||||
asyncio.create_task(p2p(settings, patterns))
|
||||
asyncio.create_task(system())
|
||||
|
||||
first = True
|
||||
|
||||
while True:
|
||||
#print(time.localtime())
|
||||
|
||||
# gc.collect()
|
||||
for i in range(60):
|
||||
wdt.feed()
|
||||
await asyncio.sleep_ms(500)
|
||||
|
||||
# cleanup before ending the application
|
||||
await server
|
||||
|
||||
asyncio.run(main())
|
||||
|
||||
|
||||
|
20
src/p2p.py
Normal file
20
src/p2p.py
Normal file
@@ -0,0 +1,20 @@
|
||||
import asyncio
|
||||
import aioespnow
|
||||
import json
|
||||
|
||||
async def p2p(settings, patterns):
|
||||
e = aioespnow.AIOESPNow() # Returns AIOESPNow enhanced with async support
|
||||
e.active(True)
|
||||
async for mac, msg in e:
|
||||
try:
|
||||
data = json.loads(msg)
|
||||
except:
|
||||
print(f"Failed to load espnow data {msg}")
|
||||
continue
|
||||
print(data)
|
||||
if "names" not in data or settings.get("name") in data.get("names", []):
|
||||
if "step" in settings and isinstance(settings["step"], int):
|
||||
patterns.set_pattern_step(settings["step"])
|
||||
else:
|
||||
settings.set_settings(data.get("settings", {}), patterns, data.get("save", False))
|
||||
print("should not print")
|
468
src/patterns.py
468
src/patterns.py
@@ -18,27 +18,50 @@ class Patterns:
|
||||
"rainbow_cycle": self.rainbow_cycle_step,
|
||||
"theater_chase": self.theater_chase_step,
|
||||
"blink": self.blink_step,
|
||||
"random_color_wipe": self.random_color_wipe_step,
|
||||
"random_rainbow_cycle": self.random_rainbow_cycle_step,
|
||||
"random_theater_chase": self.random_theater_chase_step,
|
||||
"random_blink": self.random_blink_step,
|
||||
"color_transition": self.color_transition_step,
|
||||
"external": None
|
||||
"color_transition": self.color_transition_step, # Added new pattern
|
||||
"flicker": self.flicker_step,
|
||||
"scanner": self.scanner_step, # New: Single direction scanner
|
||||
"bidirectional_scanner": self.bidirectional_scanner_step, # New: Bidirectional scanner
|
||||
"external": None,
|
||||
"pulse": self.pulse
|
||||
}
|
||||
self.selected = selected
|
||||
self.color1 = color1
|
||||
self.color2 = color2
|
||||
self.transition_duration = 50 # Duration of color transition in milliseconds
|
||||
self.transition_step = 0
|
||||
|
||||
# Ensure colors list always starts with at least two for robust transition handling
|
||||
self.colors = [color1, color2] if color1 != color2 else [color1, (255, 255, 255)] # Fallback if initial colors are same
|
||||
if not self.colors: # Ensure at least one color exists
|
||||
self.colors = [(0, 0, 0)]
|
||||
|
||||
self.transition_duration = delay * 50 # Default transition duration
|
||||
self.hold_duration = delay * 10 # Default hold duration at each color
|
||||
self.transition_step = 0 # Current step in the transition
|
||||
self.current_color_idx = 0 # Index of the color currently being held/transitioned from
|
||||
self.current_color = self.colors[self.current_color_idx] # The actual blended color
|
||||
|
||||
self.hold_start_time = utime.ticks_ms() # Time when the current color hold started
|
||||
|
||||
# New attributes for scanner patterns
|
||||
self.scanner_direction = 1 # 1 for forward, -1 for backward
|
||||
self.scanner_tail_length = 3 # Number of trailing pixels
|
||||
|
||||
def sync(self):
|
||||
self.pattern_step=0
|
||||
self.last_update = utime.ticks_ms()
|
||||
self.last_update = utime.ticks_ms() - self.delay
|
||||
if self.selected == "color_transition":
|
||||
self.transition_step = 0
|
||||
self.current_color_idx = 0
|
||||
self.current_color = self.colors[self.current_color_idx]
|
||||
self.hold_start_time = utime.ticks_ms() # Reset hold time
|
||||
# Reset scanner specific variables
|
||||
self.scanner_direction = 1
|
||||
self.tick()
|
||||
|
||||
def set_pattern_step(self, step):
|
||||
self.pattern_step = step
|
||||
|
||||
def tick(self):
|
||||
if self.patterns[self.selected]:
|
||||
self.patterns[self.selected]()
|
||||
|
||||
|
||||
def update_num_leds(self, pin, num_leds):
|
||||
self.n = NeoPixel(Pin(pin, Pin.OUT), num_leds)
|
||||
self.num_leds = num_leds
|
||||
@@ -46,52 +69,145 @@ class Patterns:
|
||||
|
||||
def set_delay(self, delay):
|
||||
self.delay = delay
|
||||
# Update transition duration and hold duration when delay changes
|
||||
self.transition_duration = self.delay * 50
|
||||
self.hold_duration = self.delay * 10
|
||||
|
||||
|
||||
def set_brightness(self, brightness):
|
||||
self.brightness = brightness
|
||||
|
||||
|
||||
def set_color1(self, color):
|
||||
print(color)
|
||||
self.color1 = self.apply_brightness(color)
|
||||
|
||||
if len(self.colors) > 0:
|
||||
self.colors[0] = color
|
||||
if self.selected == "color_transition":
|
||||
# If the first color is changed, potentially reset transition
|
||||
# to start from this new color if we were about to transition from it
|
||||
if self.current_color_idx == 0:
|
||||
self.transition_step = 0
|
||||
self.current_color = self.colors[0]
|
||||
self.hold_start_time = utime.ticks_ms()
|
||||
else:
|
||||
self.colors.append(color)
|
||||
|
||||
|
||||
def set_color2(self, color):
|
||||
self.color2 = self.apply_brightness(color)
|
||||
|
||||
def apply_brightness(self, color):
|
||||
return tuple(int(c * self.brightness / 255) for c in color)
|
||||
|
||||
if len(self.colors) > 1:
|
||||
self.colors[1] = color
|
||||
elif len(self.colors) == 1:
|
||||
self.colors.append(color)
|
||||
else: # List is empty
|
||||
self.colors.append((0,0,0)) # Dummy color
|
||||
self.colors.append(color)
|
||||
|
||||
|
||||
def set_colors(self, colors):
|
||||
if colors and len(colors) >= 2:
|
||||
self.colors = colors
|
||||
if self.selected == "color_transition":
|
||||
self.sync() # Reset transition if new color list is provided
|
||||
elif colors and len(colors) == 1:
|
||||
self.colors = [colors[0], (255,255,255)] # Add a default second color
|
||||
if self.selected == "color_transition":
|
||||
print("Warning: 'color_transition' requires at least two colors. Adding a default second color.")
|
||||
self.sync()
|
||||
else:
|
||||
print("Error: set_colors requires a list of at least one color.")
|
||||
self.colors = [(0,0,0), (255,255,255)] # Fallback
|
||||
if self.selected == "color_transition":
|
||||
self.sync()
|
||||
|
||||
def set_color(self, num, color):
|
||||
# Changed: More robust index check
|
||||
if 0 <= num < len(self.colors):
|
||||
self.colors[num] = color
|
||||
# If the changed color is part of the current or next transition,
|
||||
# restart the transition for smoother updates
|
||||
if self.selected == "color_transition":
|
||||
current_from_idx = self.current_color_idx
|
||||
current_to_idx = (self.current_color_idx + 1) % len(self.colors)
|
||||
if num == current_from_idx or num == current_to_idx:
|
||||
# If we change a color involved in the current transition,
|
||||
# it's best to restart the transition state for smoothness.
|
||||
self.transition_step = 0
|
||||
self.current_color_idx = current_from_idx # Stay at the current starting color
|
||||
self.current_color = self.colors[self.current_color_idx]
|
||||
self.hold_start_time = utime.ticks_ms() # Reset hold
|
||||
return True
|
||||
elif num == len(self.colors): # Allow setting a new color at the end
|
||||
self.colors.append(color)
|
||||
return True
|
||||
return False
|
||||
|
||||
def add_color(self, color):
|
||||
self.colors.append(color)
|
||||
if self.selected == "color_transition" and len(self.colors) == 2:
|
||||
# If we just added the second color needed for transition
|
||||
self.sync()
|
||||
|
||||
|
||||
def del_color(self, num):
|
||||
# Changed: More robust index check and using del for lists
|
||||
if 0 <= num < len(self.colors):
|
||||
del self.colors[num]
|
||||
# If the color being deleted was part of the current transition,
|
||||
# re-evaluate the current_color_idx
|
||||
if self.selected == "color_transition":
|
||||
if len(self.colors) < 2: # Need at least two colors for transition
|
||||
print("Warning: Not enough colors for 'color_transition'. Switching to 'on'.")
|
||||
self.select("on") # Or some other default
|
||||
else:
|
||||
# Adjust index if it's out of bounds after deletion or was the one transitioning from
|
||||
self.current_color_idx %= len(self.colors)
|
||||
self.transition_step = 0
|
||||
self.current_color = self.colors[self.current_color_idx]
|
||||
self.hold_start_time = utime.ticks_ms()
|
||||
return True
|
||||
return False
|
||||
|
||||
def apply_brightness(self, color, brightness_override=None):
|
||||
effective_brightness = brightness_override if brightness_override is not None else self.brightness
|
||||
return tuple(int(c * effective_brightness / 255) for c in color)
|
||||
|
||||
def select(self, pattern):
|
||||
if pattern in self.patterns:
|
||||
self.selected = pattern
|
||||
self.sync() # Reset pattern state when selecting a new pattern
|
||||
if pattern == "color_transition":
|
||||
if len(self.colors) < 2:
|
||||
print("Warning: 'color_transition' requires at least two colors. Switching to 'on'.")
|
||||
self.selected = "on" # Fallback if not enough colors
|
||||
self.sync() # Re-sync for the new pattern
|
||||
else:
|
||||
self.transition_step = 0
|
||||
self.current_color_idx = 0 # Start from the first color in the list
|
||||
self.current_color = self.colors[self.current_color_idx]
|
||||
self.hold_start_time = utime.ticks_ms() # Reset hold timer
|
||||
self.transition_duration = self.delay * 50 # Initialize transition duration
|
||||
self.hold_duration = self.delay * 10 # Initialize hold duration
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def set(self, i, color):
|
||||
self.n[i] = color
|
||||
|
||||
def write(self):
|
||||
self.n.write()
|
||||
|
||||
def fill(self):
|
||||
|
||||
def fill(self, color=None):
|
||||
fill_color = color if color is not None else self.colors[0]
|
||||
for i in range(self.num_leds):
|
||||
self.n[i] = self.color1
|
||||
self.n[i] = fill_color
|
||||
self.n.write()
|
||||
|
||||
def off(self):
|
||||
color = self.color1
|
||||
self.color1 = (0,0,0)
|
||||
self.fill()
|
||||
self.color1 = color
|
||||
|
||||
self.fill((0, 0, 0))
|
||||
|
||||
def on(self):
|
||||
color = self.color1
|
||||
self.color1 = self.apply_brightness(self.color1)
|
||||
self.fill()
|
||||
self.color1 = color
|
||||
|
||||
|
||||
self.fill(self.apply_brightness(self.colors[0]))
|
||||
|
||||
def color_wipe_step(self):
|
||||
color = self.apply_brightness(self.color1)
|
||||
color = self.apply_brightness(self.colors[0])
|
||||
current_time = utime.ticks_ms()
|
||||
if utime.ticks_diff(current_time, self.last_update) >= self.delay:
|
||||
if self.pattern_step < self.num_leds:
|
||||
@@ -129,7 +245,7 @@ class Patterns:
|
||||
if utime.ticks_diff(current_time, self.last_update) >= self.delay:
|
||||
for i in range(self.num_leds):
|
||||
if (i + self.pattern_step) % 3 == 0:
|
||||
self.n[i] = self.apply_brightness(self.color1)
|
||||
self.n[i] = self.apply_brightness(self.colors[0])
|
||||
else:
|
||||
self.n[i] = (0, 0, 0)
|
||||
self.n.write()
|
||||
@@ -140,152 +256,162 @@ class Patterns:
|
||||
current_time = utime.ticks_ms()
|
||||
if utime.ticks_diff(current_time, self.last_update) >= self.delay:
|
||||
if self.pattern_step % 2 == 0:
|
||||
for i in range(self.num_leds):
|
||||
self.n[i] = self.apply_brightness(self.color1)
|
||||
self.fill(self.apply_brightness(self.colors[0]))
|
||||
else:
|
||||
for i in range(self.num_leds):
|
||||
self.n[i] = (0, 0, 0)
|
||||
self.n.write()
|
||||
self.pattern_step = (self.pattern_step + 1) % 2
|
||||
self.last_update = current_time
|
||||
|
||||
def random_color_wipe_step(self):
|
||||
current_time = utime.ticks_ms()
|
||||
if utime.ticks_diff(current_time, self.last_update) >= self.delay:
|
||||
color = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))
|
||||
if self.pattern_step < self.num_leds:
|
||||
for i in range(self.num_leds):
|
||||
self.n[i] = (0, 0, 0)
|
||||
self.n[self.pattern_step] = self.apply_brightness(color)
|
||||
self.n.write()
|
||||
self.pattern_step += 1
|
||||
else:
|
||||
self.pattern_step = 0
|
||||
self.last_update = current_time
|
||||
|
||||
def random_rainbow_cycle_step(self):
|
||||
current_time = utime.ticks_ms()
|
||||
if utime.ticks_diff(current_time, self.last_update) >= self.delay:
|
||||
def wheel(pos):
|
||||
if pos < 85:
|
||||
return (pos * 3, 255 - pos * 3, 0)
|
||||
elif pos < 170:
|
||||
pos -= 85
|
||||
return (255 - pos * 3, 0, pos * 3)
|
||||
else:
|
||||
pos -= 170
|
||||
return (0, pos * 3, 255 - pos * 3)
|
||||
|
||||
random_offset = random.randint(0, 255)
|
||||
for i in range(self.num_leds):
|
||||
rc_index = (i * 256 // self.num_leds) + self.pattern_step + random_offset
|
||||
self.n[i] = self.apply_brightness(wheel(rc_index & 255))
|
||||
self.n.write()
|
||||
self.pattern_step = (self.pattern_step + 1) % 256
|
||||
self.last_update = current_time
|
||||
|
||||
def random_theater_chase_step(self):
|
||||
current_time = utime.ticks_ms()
|
||||
if utime.ticks_diff(current_time, self.last_update) >= self.delay:
|
||||
color = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))
|
||||
for i in range(self.num_leds):
|
||||
if (i + self.pattern_step) % 3 == 0:
|
||||
self.n[i] = self.apply_brightness(color)
|
||||
else:
|
||||
self.n[i] = (0, 0, 0)
|
||||
self.n.write()
|
||||
self.pattern_step = (self.pattern_step + 1) % 3
|
||||
self.last_update = current_time
|
||||
|
||||
def random_blink_step(self):
|
||||
current_time = utime.ticks_ms()
|
||||
if utime.ticks_diff(current_time, self.last_update) >= self.delay:
|
||||
color = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))
|
||||
if self.pattern_step % 2 == 0:
|
||||
for i in range(self.num_leds):
|
||||
self.n[i] = self.apply_brightness(color)
|
||||
else:
|
||||
for i in range(self.num_leds):
|
||||
self.n[i] = (0, 0, 0)
|
||||
self.n.write()
|
||||
self.fill((0, 0, 0))
|
||||
self.pattern_step = (self.pattern_step + 1) % 2
|
||||
self.last_update = current_time
|
||||
|
||||
def color_transition_step(self):
|
||||
current_time = utime.ticks_ms()
|
||||
if utime.ticks_diff(current_time, self.last_update) >= self.delay:
|
||||
# Calculate transition factor based on elapsed time
|
||||
transition_factor = (self.pattern_step * 100) / self.transition_duration
|
||||
if transition_factor > 100:
|
||||
transition_factor = 100
|
||||
color = self.interpolate_color(self.color1, self.color2, transition_factor / 100)
|
||||
|
||||
# Apply the interpolated color to all LEDs
|
||||
for i in range(self.num_leds):
|
||||
self.n[i] = self.apply_brightness(color)
|
||||
self.n.write()
|
||||
|
||||
self.pattern_step += self.delay
|
||||
if self.pattern_step > self.transition_duration:
|
||||
self.pattern_step = 0
|
||||
# Check for hold duration first
|
||||
if utime.ticks_diff(current_time, self.hold_start_time) < self.hold_duration:
|
||||
# Still in hold phase, just display the current solid color
|
||||
self.fill(self.apply_brightness(self.current_color))
|
||||
self.last_update = current_time # Keep updating last_update to avoid skipping frames
|
||||
return
|
||||
|
||||
# If hold duration is over, proceed with transition
|
||||
if utime.ticks_diff(current_time, self.last_update) >= self.delay:
|
||||
num_colors = len(self.colors)
|
||||
if num_colors < 2:
|
||||
# Should not happen if select handles it, but as a safeguard
|
||||
self.select("on")
|
||||
return
|
||||
|
||||
from_color = self.colors[self.current_color_idx]
|
||||
to_color_idx = (self.current_color_idx + 1) % num_colors
|
||||
to_color = self.colors[to_color_idx]
|
||||
|
||||
# Calculate interpolation factor (0.0 to 1.0)
|
||||
# transition_step goes from 0 to transition_duration - 1
|
||||
if self.transition_duration > 0:
|
||||
interp_factor = self.transition_step / self.transition_duration
|
||||
else:
|
||||
interp_factor = 1.0 # Immediately transition if duration is zero
|
||||
|
||||
# Interpolate each color component
|
||||
r = int(from_color[0] + (to_color[0] - from_color[0]) * interp_factor)
|
||||
g = int(from_color[1] + (to_color[1] - from_color[1]) * interp_factor)
|
||||
b = int(from_color[2] + (to_color[2] - from_color[2]) * interp_factor)
|
||||
|
||||
self.current_color = (r, g, b)
|
||||
self.fill(self.apply_brightness(self.current_color))
|
||||
|
||||
self.transition_step += self.delay # Advance the transition step by the delay
|
||||
|
||||
if self.transition_step >= self.transition_duration:
|
||||
# Transition complete, move to the next color and reset for hold phase
|
||||
self.current_color_idx = to_color_idx
|
||||
self.current_color = self.colors[self.current_color_idx] # Ensure current_color is the exact target color
|
||||
self.transition_step = 0 # Reset transition progress
|
||||
self.hold_start_time = current_time # Start hold phase for the new color
|
||||
|
||||
self.last_update = current_time
|
||||
|
||||
def interpolate_color(self, color1, color2, factor):
|
||||
return (
|
||||
int(color1[0] + (color2[0] - color1[0]) * factor),
|
||||
int(color1[1] + (color2[1] - color1[1]) * factor),
|
||||
int(color1[2] + (color2[2] - color1[2]) * factor)
|
||||
)
|
||||
|
||||
def two_steps_forward_one_step_back_step(self):
|
||||
def flicker_step(self):
|
||||
current_time = utime.ticks_ms()
|
||||
if utime.ticks_diff(current_time, self.last_update) >= self.delay/5:
|
||||
base_color = self.colors[0]
|
||||
# Increase the range for flicker_brightness_offset
|
||||
# Changed from self.brightness // 4 to self.brightness // 2 (or even self.brightness for max intensity)
|
||||
flicker_brightness_offset = random.randint(-int(self.brightness // 1.5), int(self.brightness // 1.5))
|
||||
flicker_brightness = max(0, min(255, self.brightness + flicker_brightness_offset))
|
||||
|
||||
flicker_color = self.apply_brightness(base_color, brightness_override=flicker_brightness)
|
||||
self.fill(flicker_color)
|
||||
self.last_update = current_time
|
||||
|
||||
def scanner_step(self):
|
||||
"""
|
||||
Mimics a 'Knight Rider' style scanner, moving in one direction.
|
||||
"""
|
||||
current_time = utime.ticks_ms()
|
||||
if utime.ticks_diff(current_time, self.last_update) >= self.delay:
|
||||
# Move forward 2 steps and backward 1 step
|
||||
if self.direction == 1: # Moving forward
|
||||
if self.scanner_position < self.num_leds - 2:
|
||||
self.scanner_position += 2 # Move forward 2 steps
|
||||
else:
|
||||
self.direction = -1 # Change direction to backward
|
||||
else: # Moving backward
|
||||
if self.scanner_position > 0:
|
||||
self.scanner_position -= 1 # Move backward 1 step
|
||||
else:
|
||||
self.direction = 1 # Change direction to forward
|
||||
|
||||
# Set all LEDs to off
|
||||
for i in range(self.num_leds):
|
||||
self.n[i] = (0, 0, 0)
|
||||
|
||||
# Set the current position to the color
|
||||
self.n[self.scanner_position] = self.apply_brightness(self.color1)
|
||||
|
||||
# Apply the color transition
|
||||
transition_factor = (self.pattern_step * 100) / self.transition_duration
|
||||
if transition_factor > 100:
|
||||
transition_factor = 100
|
||||
color = self.interpolate_color(self.color1, self.color2, transition_factor / 100)
|
||||
self.n[self.scanner_position] = self.apply_brightness(color)
|
||||
|
||||
self.fill((0, 0, 0)) # Clear all LEDs
|
||||
|
||||
# Calculate the head and tail position
|
||||
head_pos = self.pattern_step
|
||||
color = self.apply_brightness(self.colors[0])
|
||||
|
||||
# Draw the head
|
||||
if 0 <= head_pos < self.num_leds:
|
||||
self.n[head_pos] = color
|
||||
|
||||
# Draw the trailing pixels with decreasing brightness
|
||||
for i in range(1, self.scanner_tail_length + 1):
|
||||
tail_pos = head_pos - i
|
||||
if 0 <= tail_pos < self.num_leds:
|
||||
# Calculate fading color for tail
|
||||
# Example: linear fade from full brightness to off
|
||||
fade_factor = 1.0 - (i / (self.scanner_tail_length + 1))
|
||||
faded_color = tuple(int(c * fade_factor) for c in color)
|
||||
self.n[tail_pos] = faded_color
|
||||
|
||||
self.n.write()
|
||||
self.pattern_step += self.delay
|
||||
if self.pattern_step > self.transition_duration:
|
||||
self.pattern_step = 0
|
||||
|
||||
self.pattern_step += 1
|
||||
if self.pattern_step >= self.num_leds + self.scanner_tail_length:
|
||||
self.pattern_step = 0 # Reset to start
|
||||
|
||||
self.last_update = current_time
|
||||
|
||||
def bidirectional_scanner_step(self):
|
||||
"""
|
||||
Mimics a 'Knight Rider' style scanner, moving back and forth.
|
||||
"""
|
||||
current_time = utime.ticks_ms()
|
||||
if utime.ticks_diff(current_time, self.last_update) >= self.delay/100:
|
||||
self.fill((0, 0, 0)) # Clear all LEDs
|
||||
|
||||
color = self.apply_brightness(self.colors[0])
|
||||
|
||||
# Calculate the head position based on direction
|
||||
head_pos = self.pattern_step
|
||||
|
||||
# Draw the head
|
||||
if 0 <= head_pos < self.num_leds:
|
||||
self.n[head_pos] = color
|
||||
|
||||
# Draw the trailing pixels with decreasing brightness
|
||||
for i in range(1, self.scanner_tail_length + 1):
|
||||
tail_pos = head_pos - (i * self.scanner_direction)
|
||||
if 0 <= tail_pos < self.num_leds:
|
||||
fade_factor = 1.0 - (i / (self.scanner_tail_length + 1))
|
||||
faded_color = tuple(int(c * fade_factor) for c in color)
|
||||
self.n[tail_pos] = faded_color
|
||||
|
||||
self.n.write()
|
||||
|
||||
self.pattern_step += self.scanner_direction
|
||||
|
||||
# Change direction if boundaries are reached
|
||||
if self.scanner_direction == 1 and self.pattern_step >= self.num_leds:
|
||||
self.scanner_direction = -1
|
||||
self.pattern_step = self.num_leds - 1 # Start moving back from the last LED
|
||||
elif self.scanner_direction == -1 and self.pattern_step < 0:
|
||||
self.scanner_direction = 1
|
||||
self.pattern_step = 0 # Start moving forward from the first LED
|
||||
|
||||
self.last_update = current_time
|
||||
|
||||
def pulse(self):
|
||||
if self.pattern_step == 0:
|
||||
self.fill(self.apply_brightness(self.colors[0]))
|
||||
self.pattern_step = 1
|
||||
self.last_update = utime.ticks_ms()
|
||||
if utime.ticks_diff(utime.ticks_ms(), self.last_update) > self.delay:
|
||||
self.fill((0, 0, 0))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
p = Patterns(4, 180)
|
||||
p.set_color1((255,0,0))
|
||||
p.set_color2((0,255,0))
|
||||
#p.set_delay(10)
|
||||
try:
|
||||
while True:
|
||||
for key in p.patterns:
|
||||
print(key)
|
||||
p.select(key)
|
||||
for _ in range(2000):
|
||||
p.tick()
|
||||
utime.sleep_ms(1)
|
||||
except KeyboardInterrupt:
|
||||
p.fill((0, 0, 0))
|
||||
import time
|
||||
from machine import WDT
|
||||
wdt = WDT(timeout=2000) # Enable watchdog with a 2 second timeout
|
||||
p = Patterns(pin=10, num_leds=200, color1=(255,0,0), color2=(0,0,255), brightness=127, selected="bidirectional_scanner", delay=50)
|
||||
p.select("pulse")
|
||||
for i in range(1000):
|
||||
p.tick()
|
||||
wdt.feed()
|
||||
time.sleep_ms(1)
|
194
src/patterns_base.py
Normal file
194
src/patterns_base.py
Normal file
@@ -0,0 +1,194 @@
|
||||
from machine import Pin
|
||||
from neopixel import NeoPixel
|
||||
import utime
|
||||
|
||||
|
||||
class PatternBase:
|
||||
def __init__(self, pin, num_leds, color1=(0,0,0), color2=(0,0,0), brightness=127, selected="rainbow_cycle", delay=100):
|
||||
self.n = NeoPixel(Pin(pin, Pin.OUT), num_leds)
|
||||
self.num_leds = num_leds
|
||||
self.pattern_step = 0
|
||||
self.last_update = utime.ticks_ms()
|
||||
self.delay = delay
|
||||
self.brightness = brightness
|
||||
self.patterns = {}
|
||||
self.selected = selected
|
||||
# Ensure colors list always starts with at least two for robust transition handling
|
||||
self.colors = [color1, color2] if color1 != color2 else [color1, (255, 255, 255)] # Fallback if initial colors are same
|
||||
if not self.colors: # Ensure at least one color exists
|
||||
self.colors = [(0, 0, 0)]
|
||||
|
||||
self.transition_duration = delay * 50 # Default transition duration
|
||||
self.hold_duration = delay * 10 # Default hold duration at each color
|
||||
self.transition_step = 0 # Current step in the transition
|
||||
self.current_color_idx = 0 # Index of the color currently being held/transitioned from
|
||||
self.current_color = self.colors[self.current_color_idx] # The actual blended color
|
||||
|
||||
self.hold_start_time = utime.ticks_ms() # Time when the current color hold started
|
||||
|
||||
# New attributes for scanner patterns
|
||||
self.scanner_direction = 1 # 1 for forward, -1 for backward
|
||||
self.scanner_tail_length = 3 # Number of trailing pixels
|
||||
|
||||
def sync(self):
|
||||
self.pattern_step=0
|
||||
self.last_update = utime.ticks_ms() - self.delay
|
||||
if self.selected == "color_transition":
|
||||
self.transition_step = 0
|
||||
self.current_color_idx = 0
|
||||
self.current_color = self.colors[self.current_color_idx]
|
||||
self.hold_start_time = utime.ticks_ms() # Reset hold time
|
||||
# Reset scanner specific variables
|
||||
self.scanner_direction = 1
|
||||
self.tick()
|
||||
|
||||
def set_pattern_step(self, step):
|
||||
self.pattern_step = step
|
||||
|
||||
def tick(self):
|
||||
if self.patterns[self.selected]:
|
||||
self.patterns[self.selected]()
|
||||
|
||||
def update_num_leds(self, pin, num_leds):
|
||||
self.n = NeoPixel(Pin(pin, Pin.OUT), num_leds)
|
||||
self.num_leds = num_leds
|
||||
self.pattern_step = 0
|
||||
|
||||
def set_delay(self, delay):
|
||||
self.delay = delay
|
||||
# Update transition duration and hold duration when delay changes
|
||||
self.transition_duration = self.delay * 50
|
||||
self.hold_duration = self.delay * 10
|
||||
|
||||
|
||||
def set_brightness(self, brightness):
|
||||
self.brightness = brightness
|
||||
|
||||
def set_color1(self, color):
|
||||
if len(self.colors) > 0:
|
||||
self.colors[0] = color
|
||||
if self.selected == "color_transition":
|
||||
# If the first color is changed, potentially reset transition
|
||||
# to start from this new color if we were about to transition from it
|
||||
if self.current_color_idx == 0:
|
||||
self.transition_step = 0
|
||||
self.current_color = self.colors[0]
|
||||
self.hold_start_time = utime.ticks_ms()
|
||||
else:
|
||||
self.colors.append(color)
|
||||
|
||||
|
||||
def set_color2(self, color):
|
||||
if len(self.colors) > 1:
|
||||
self.colors[1] = color
|
||||
elif len(self.colors) == 1:
|
||||
self.colors.append(color)
|
||||
else: # List is empty
|
||||
self.colors.append((0,0,0)) # Dummy color
|
||||
self.colors.append(color)
|
||||
|
||||
|
||||
def set_colors(self, colors):
|
||||
if colors and len(colors) >= 2:
|
||||
self.colors = colors
|
||||
if self.selected == "color_transition":
|
||||
self.sync() # Reset transition if new color list is provided
|
||||
elif colors and len(colors) == 1:
|
||||
self.colors = [colors[0], (255,255,255)] # Add a default second color
|
||||
if self.selected == "color_transition":
|
||||
print("Warning: 'color_transition' requires at least two colors. Adding a default second color.")
|
||||
self.sync()
|
||||
else:
|
||||
print("Error: set_colors requires a list of at least one color.")
|
||||
self.colors = [(0,0,0), (255,255,255)] # Fallback
|
||||
if self.selected == "color_transition":
|
||||
self.sync()
|
||||
|
||||
def set_color(self, num, color):
|
||||
# Changed: More robust index check
|
||||
if 0 <= num < len(self.colors):
|
||||
self.colors[num] = color
|
||||
# If the changed color is part of the current or next transition,
|
||||
# restart the transition for smoother updates
|
||||
if self.selected == "color_transition":
|
||||
current_from_idx = self.current_color_idx
|
||||
current_to_idx = (self.current_color_idx + 1) % len(self.colors)
|
||||
if num == current_from_idx or num == current_to_idx:
|
||||
# If we change a color involved in the current transition,
|
||||
# it's best to restart the transition state for smoothness.
|
||||
self.transition_step = 0
|
||||
self.current_color_idx = current_from_idx # Stay at the current starting color
|
||||
self.current_color = self.colors[self.current_color_idx]
|
||||
self.hold_start_time = utime.ticks_ms() # Reset hold
|
||||
return True
|
||||
elif num == len(self.colors): # Allow setting a new color at the end
|
||||
self.colors.append(color)
|
||||
return True
|
||||
return False
|
||||
|
||||
def add_color(self, color):
|
||||
self.colors.append(color)
|
||||
if self.selected == "color_transition" and len(self.colors) == 2:
|
||||
# If we just added the second color needed for transition
|
||||
self.sync()
|
||||
|
||||
|
||||
def del_color(self, num):
|
||||
# Changed: More robust index check and using del for lists
|
||||
if 0 <= num < len(self.colors):
|
||||
del self.colors[num]
|
||||
# If the color being deleted was part of the current transition,
|
||||
# re-evaluate the current_color_idx
|
||||
if self.selected == "color_transition":
|
||||
if len(self.colors) < 2: # Need at least two colors for transition
|
||||
print("Warning: Not enough colors for 'color_transition'. Switching to 'on'.")
|
||||
self.select("on") # Or some other default
|
||||
else:
|
||||
# Adjust index if it's out of bounds after deletion or was the one transitioning from
|
||||
self.current_color_idx %= len(self.colors)
|
||||
self.transition_step = 0
|
||||
self.current_color = self.colors[self.current_color_idx]
|
||||
self.hold_start_time = utime.ticks_ms()
|
||||
return True
|
||||
return False
|
||||
|
||||
def apply_brightness(self, color, brightness_override=None):
|
||||
effective_brightness = brightness_override if brightness_override is not None else self.brightness
|
||||
return tuple(int(c * effective_brightness / 255) for c in color)
|
||||
|
||||
def select(self, pattern):
|
||||
if pattern in self.patterns:
|
||||
self.selected = pattern
|
||||
self.sync() # Reset pattern state when selecting a new pattern
|
||||
if pattern == "color_transition":
|
||||
if len(self.colors) < 2:
|
||||
print("Warning: 'color_transition' requires at least two colors. Switching to 'on'.")
|
||||
self.selected = "on" # Fallback if not enough colors
|
||||
self.sync() # Re-sync for the new pattern
|
||||
else:
|
||||
self.transition_step = 0
|
||||
self.current_color_idx = 0 # Start from the first color in the list
|
||||
self.current_color = self.colors[self.current_color_idx]
|
||||
self.hold_start_time = utime.ticks_ms() # Reset hold timer
|
||||
self.transition_duration = self.delay * 50 # Initialize transition duration
|
||||
self.hold_duration = self.delay * 10 # Initialize hold duration
|
||||
return True
|
||||
return False
|
||||
|
||||
def set(self, i, color):
|
||||
self.n[i] = color
|
||||
|
||||
def write(self):
|
||||
self.n.write()
|
||||
|
||||
def fill(self, color=None):
|
||||
fill_color = color if color is not None else self.colors[0]
|
||||
for i in range(self.num_leds):
|
||||
self.n[i] = fill_color
|
||||
self.n.write()
|
||||
|
||||
def off(self):
|
||||
self.fill((0, 0, 0))
|
||||
|
||||
def on(self):
|
||||
self.fill(self.apply_brightness(self.colors[0]))
|
@@ -1,4 +1,7 @@
|
||||
import json
|
||||
import wifi
|
||||
import ubinascii
|
||||
import machine
|
||||
|
||||
class Settings(dict):
|
||||
SETTINGS_FILE = "/settings.json"
|
||||
@@ -6,15 +9,21 @@ class Settings(dict):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.load() # Load settings from file during initialization
|
||||
if self["color_order"] == "rbg": self.color_order = (1, 5, 3)
|
||||
else: self.color_order = (1, 3, 5)
|
||||
|
||||
def set_defaults(self):
|
||||
self["led_pin"] = 10
|
||||
self["num_leds"] = 50
|
||||
self["selected_pattern"] = "blink"
|
||||
self["color1"] = "#000f00"
|
||||
self["color2"] = "#0f0000"
|
||||
self["pattern"] = "on"
|
||||
self["color1"] = "#00ff00"
|
||||
self["color2"] = "#ff0000"
|
||||
self["delay"] = 100
|
||||
self["brightness"] = 100
|
||||
self["wifi"] = {"ssid": "", "password": ""}
|
||||
self["brightness"] = 10
|
||||
self["color_order"] = "rgb"
|
||||
self["name"] = f"led-{ubinascii.hexlify(wifi.get_mac()).decode()}"
|
||||
self["ap_password"] = ""
|
||||
self["id"] = 0
|
||||
|
||||
def save(self):
|
||||
try:
|
||||
@@ -34,6 +43,55 @@ class Settings(dict):
|
||||
except Exception as e:
|
||||
print(f"Error loading settings")
|
||||
self.set_defaults()
|
||||
self.save()
|
||||
|
||||
def set_settings(self, data, patterns, save):
|
||||
try:
|
||||
print(data)
|
||||
for key, value in data.items():
|
||||
print(key, value)
|
||||
if key == "colors":
|
||||
buff = []
|
||||
for color in value:
|
||||
buff.append(tuple(int(color[i:i+2], 16) for i in self.color_order))
|
||||
patterns.set_colors(buff)
|
||||
elif key == "color1":
|
||||
patterns.set_color1(tuple(int(value[i:i+2], 16) for i in self.color_order)) # Convert hex to RGB
|
||||
elif key == "color2":
|
||||
patterns.set_color2(tuple(int(value[i:i+2], 16) for i in self.color_order)) # Convert hex to RGB
|
||||
elif key == "num_leds":
|
||||
patterns.update_num_leds(self["led_pin"], value)
|
||||
elif key == "pattern":
|
||||
if not patterns.select(value):
|
||||
return "Pattern doesn't exist", 400
|
||||
elif key == "delay":
|
||||
delay = int(data["delay"])
|
||||
patterns.set_delay(delay)
|
||||
elif key == "brightness":
|
||||
brightness = int(data["brightness"])
|
||||
patterns.set_brightness(brightness)
|
||||
elif key == "name":
|
||||
self[key] = value
|
||||
self.save()
|
||||
machine.reset()
|
||||
elif key == "color_order":
|
||||
if value == "rbg": self.color_order = (1, 5, 3)
|
||||
else: self.color_order = (1, 3, 5)
|
||||
pass
|
||||
elif key == "id":
|
||||
pass
|
||||
elif key == "led_pin":
|
||||
patterns.update_num_leds(value, self["num_leds"])
|
||||
else:
|
||||
return "Invalid key", 400
|
||||
self[key] = value
|
||||
#print(self)
|
||||
patterns.sync()
|
||||
if save:
|
||||
self.save()
|
||||
return "OK", 200
|
||||
except (KeyError, ValueError):
|
||||
return "Bad request", 400
|
||||
|
||||
# Example usage
|
||||
def main():
|
||||
@@ -42,12 +100,14 @@ def main():
|
||||
settings['num_leds'] = 100
|
||||
print(f"Updated number of LEDs: {settings['num_leds']}")
|
||||
settings.save()
|
||||
|
||||
|
||||
# Create a new Settings object to test loading
|
||||
new_settings = Settings()
|
||||
print(f"Loaded number of LEDs: {new_settings['num_leds']}")
|
||||
print(settings)
|
||||
|
||||
|
||||
|
||||
# Run the example
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
main()
|
||||
|
@@ -1,75 +1,109 @@
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
h1 {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
form {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
input[type="text"], input[type="submit"], input[type="range"], input[type="color"] {
|
||||
width: 100%;
|
||||
|
||||
margin-bottom: 10px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
input[type="range"] {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
height: 25px;
|
||||
background: #d3d3d3;
|
||||
outline: none;
|
||||
opacity: 0.7;
|
||||
transition: opacity .2s;
|
||||
}
|
||||
input[type="range"]:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
input[type="range"]::-webkit-slider-thumb {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
background: #4CAF50;
|
||||
cursor: pointer;
|
||||
border-radius: 50%;
|
||||
}
|
||||
input[type="range"]::-moz-range-thumb {
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
background: #4CAF50;
|
||||
cursor: pointer;
|
||||
border-radius: 50%;
|
||||
}
|
||||
#pattern_buttons {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
#pattern_buttons button {
|
||||
flex: 1 0 calc(33.333% - 10px);
|
||||
padding: 10px;
|
||||
background-color: #4CAF50;
|
||||
color: white;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
#pattern_buttons button:hover {
|
||||
background-color: #45a049;
|
||||
}
|
||||
@media (max-width: 480px) {
|
||||
#pattern_buttons button {
|
||||
flex: 1 0 calc(50% - 10px);
|
||||
}
|
||||
}
|
||||
font-family: Arial, sans-serif;
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
h1 {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
form {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
input[type="text"],
|
||||
input[type="submit"],
|
||||
input[type="range"],
|
||||
input[type="color"] {
|
||||
width: 100%;
|
||||
|
||||
margin-bottom: 10px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
input[type="range"] {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
height: 25px;
|
||||
background: #d3d3d3;
|
||||
outline: none;
|
||||
opacity: 0.7;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
input[type="range"]:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
input[type="range"]::-webkit-slider-thumb {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
background: #4caf50;
|
||||
cursor: pointer;
|
||||
border-radius: 50%;
|
||||
}
|
||||
input[type="range"]::-moz-range-thumb {
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
background: #4caf50;
|
||||
cursor: pointer;
|
||||
border-radius: 50%;
|
||||
}
|
||||
#pattern_buttons {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
#pattern_buttons button {
|
||||
flex: 1 0 calc(33.333% - 10px);
|
||||
padding: 10px;
|
||||
background-color: #4caf50;
|
||||
color: white;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
#pattern_buttons button:hover {
|
||||
background-color: #45a049;
|
||||
}
|
||||
@media (max-width: 480px) {
|
||||
#pattern_buttons button {
|
||||
flex: 1 0 calc(50% - 10px);
|
||||
}
|
||||
}
|
||||
#connection-status {
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
border-radius: 50%;
|
||||
display: inline-block; /* Or block, depending on where you put it */
|
||||
margin-left: 10px; /* Adjust spacing as needed */
|
||||
vertical-align: middle; /* Align with nearby text */
|
||||
background-color: grey; /* Default: Unknown */
|
||||
}
|
||||
|
||||
#connection-status.connecting {
|
||||
background-color: yellow;
|
||||
}
|
||||
|
||||
#connection-status.open {
|
||||
background-color: green;
|
||||
}
|
||||
|
||||
#connection-status.closing,
|
||||
#connection-status.closed {
|
||||
background-color: red;
|
||||
}
|
||||
|
||||
#color_order_form label,
|
||||
#color_order_form input[type="radio"] {
|
||||
/* Ensures they behave as inline elements */
|
||||
display: inline-block;
|
||||
/* Adds some space between them for readability */
|
||||
margin-right: 10px;
|
||||
vertical-align: middle; /* Aligns them nicely if heights vary */
|
||||
}
|
||||
|
@@ -2,146 +2,243 @@ let delayTimeout;
|
||||
let brightnessTimeout;
|
||||
let colorTimeout;
|
||||
let color2Timeout;
|
||||
let ws; // Variable to hold the WebSocket connection
|
||||
let connectionStatusElement; // Variable to hold the connection status element
|
||||
|
||||
async function post(path, data) {
|
||||
console.log(`POST to ${path}`, data);
|
||||
try {
|
||||
const response = await fetch(path, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(data) // Convert data to JSON string
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! Status: ${response.status}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error during POST request:', error);
|
||||
// Function to update the connection status indicator
|
||||
function updateConnectionStatus(status) {
|
||||
if (!connectionStatusElement) {
|
||||
connectionStatusElement = document.getElementById("connection-status");
|
||||
}
|
||||
if (connectionStatusElement) {
|
||||
connectionStatusElement.className = ""; // Clear existing classes
|
||||
connectionStatusElement.classList.add(status);
|
||||
// Optionally, you could also update text content based on status
|
||||
// connectionStatusElement.textContent = status.charAt(0).toUpperCase() + status.slice(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Function to establish WebSocket connection
|
||||
function connectWebSocket() {
|
||||
// Determine the WebSocket URL based on the current location
|
||||
const wsUrl = `ws://${window.location.host}/ws`;
|
||||
ws = new WebSocket(wsUrl);
|
||||
|
||||
updateConnectionStatus("connecting"); // Indicate connecting state
|
||||
|
||||
ws.onopen = function (event) {
|
||||
console.log("WebSocket connection opened:", event);
|
||||
updateConnectionStatus("open"); // Indicate open state
|
||||
// Optionally, you could send an initial message here
|
||||
};
|
||||
|
||||
ws.onmessage = function (event) {
|
||||
console.log("WebSocket message received:", event.data);
|
||||
};
|
||||
|
||||
ws.onerror = function (event) {
|
||||
console.error("WebSocket error:", event);
|
||||
updateConnectionStatus("closed"); // Indicate error state (treat as closed)
|
||||
};
|
||||
|
||||
ws.onclose = function (event) {
|
||||
if (event.wasClean) {
|
||||
console.log(
|
||||
`WebSocket connection closed cleanly, code=${event.code}, reason=${event.reason}`,
|
||||
);
|
||||
updateConnectionStatus("closed"); // Indicate closed state
|
||||
} else {
|
||||
console.error("WebSocket connection died");
|
||||
updateConnectionStatus("closed"); // Indicate closed state
|
||||
}
|
||||
// Attempt to reconnect after a delay
|
||||
setTimeout(connectWebSocket, 1000);
|
||||
};
|
||||
}
|
||||
|
||||
// Function to send data over WebSocket
|
||||
function sendWebSocketData(data) {
|
||||
if (ws && ws.readyState === WebSocket.OPEN) {
|
||||
console.log("Sending data over WebSocket:", data);
|
||||
ws.send(JSON.stringify(data));
|
||||
} else {
|
||||
console.error("WebSocket is not connected. Cannot send data:", data);
|
||||
// You might want to queue messages or handle this in a different way
|
||||
}
|
||||
}
|
||||
|
||||
// Keep the post and get functions for now, they might still be useful
|
||||
async function post(path, data) {
|
||||
console.log(`POST to ${path}`, data);
|
||||
try {
|
||||
const response = await fetch(path, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! Status: ${response.status}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error during POST request:", error);
|
||||
}
|
||||
}
|
||||
|
||||
async function get(path) {
|
||||
try {
|
||||
const response = await fetch(path);
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! Status: ${response.status}`);
|
||||
}
|
||||
return await response.json(); // Assuming you are expecting JSON response
|
||||
} catch (error) {
|
||||
console.error('Error during GET request:', error);
|
||||
try {
|
||||
const response = await fetch(path);
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! Status: ${response.status}`);
|
||||
}
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
console.error("Error during GET request:", error);
|
||||
}
|
||||
}
|
||||
|
||||
async function updateColor(event) {
|
||||
event.preventDefault();
|
||||
clearTimeout(colorTimeout);
|
||||
colorTimeout = setTimeout(async function() {
|
||||
const color = document.getElementById('color').value;
|
||||
await post("/color", { color }); // Send as JSON
|
||||
}, 500);
|
||||
event.preventDefault();
|
||||
clearTimeout(colorTimeout);
|
||||
colorTimeout = setTimeout(function () {
|
||||
const color = document.getElementById("color").value;
|
||||
sendWebSocketData({ color1: color });
|
||||
}, 500);
|
||||
}
|
||||
|
||||
async function updateColor2(event) {
|
||||
event.preventDefault();
|
||||
clearTimeout(color2Timeout);
|
||||
color2Timeout = setTimeout(async function() {
|
||||
const color = document.getElementById('color2').value;
|
||||
await post("/color2", { color }); // Send as JSON
|
||||
}, 500);
|
||||
event.preventDefault();
|
||||
clearTimeout(color2Timeout);
|
||||
color2Timeout = setTimeout(function () {
|
||||
const color = document.getElementById("color2").value;
|
||||
sendWebSocketData({ color2: color });
|
||||
}, 500);
|
||||
}
|
||||
|
||||
async function updatePattern(pattern) {
|
||||
event.preventDefault();
|
||||
await post("/pattern", { pattern }); // Send as JSON
|
||||
sendWebSocketData({ pattern: pattern });
|
||||
}
|
||||
|
||||
async function updateBrightness(event) {
|
||||
event.preventDefault();
|
||||
clearTimeout(brightnessTimeout);
|
||||
brightnessTimeout = setTimeout(async function() {
|
||||
const brightness = document.getElementById('brightness').value;
|
||||
await post('/brightness', { brightness }); // Send as JSON
|
||||
}, 500);
|
||||
event.preventDefault();
|
||||
clearTimeout(brightnessTimeout);
|
||||
brightnessTimeout = setTimeout(function () {
|
||||
const brightness = document.getElementById("brightness").value;
|
||||
sendWebSocketData({ brightness: brightness });
|
||||
}, 500);
|
||||
}
|
||||
|
||||
async function updateDelay(event) {
|
||||
event.preventDefault();
|
||||
clearTimeout(delayTimeout);
|
||||
delayTimeout = setTimeout(async function() {
|
||||
const delay = document.getElementById('delay').value;
|
||||
await post('/delay', { delay }); // Send as JSON
|
||||
}, 500);
|
||||
event.preventDefault();
|
||||
clearTimeout(delayTimeout);
|
||||
delayTimeout = setTimeout(function () {
|
||||
const delay = document.getElementById("delay").value;
|
||||
sendWebSocketData({ delay: delay });
|
||||
}, 500);
|
||||
}
|
||||
|
||||
async function updateNumLeds(event) {
|
||||
event.preventDefault();
|
||||
const numLeds = document.getElementById('num_leds').value;
|
||||
await post('/num_leds', { num_leds: numLeds }); // Send as JSON
|
||||
event.preventDefault();
|
||||
const numLeds = document.getElementById("num_leds").value;
|
||||
sendWebSocketData({ num_leds: parseInt(numLeds) });
|
||||
}
|
||||
|
||||
async function updateWifi(event) {
|
||||
event.preventDefault();
|
||||
const ssid = document.getElementById('ssid').value;
|
||||
const password = document.getElementById('password').value;
|
||||
const ip = document.getElementById('ip').value;
|
||||
const gateway = document.getElementById('gateway').value;
|
||||
async function updateName(event) {
|
||||
event.preventDefault();
|
||||
const name = document.getElementById("name").value;
|
||||
sendWebSocketData({ name: name });
|
||||
}
|
||||
|
||||
const wifiSettings = { ssid, password, ip, gateway }; // Create JSON object
|
||||
console.log(wifiSettings);
|
||||
const response = await post('/wifi_settings', wifiSettings); // Send as JSON
|
||||
if (response === 500) {
|
||||
alert("Failed to connect to Wi-Fi");
|
||||
}
|
||||
async function updateID(event) {
|
||||
event.preventDefault();
|
||||
const id = document.getElementById("id").value;
|
||||
sendWebSocketData({ id: parseInt(id) });
|
||||
}
|
||||
|
||||
async function updateLedPin(event) {
|
||||
event.preventDefault();
|
||||
const ledpin = document.getElementById("led_pin").value;
|
||||
sendWebSocketData({ led_pin: parseInt(ledpin) });
|
||||
}
|
||||
|
||||
function handleRadioChange(event) {
|
||||
event.preventDefault();
|
||||
console.log("Selected color order:", event.target.value);
|
||||
// Add your specific logic here
|
||||
if (event.target.value === "rgb") {
|
||||
console.log("RGB order selected!");
|
||||
} else if (event.target.value === "rbg") {
|
||||
console.log("RBG order selected!");
|
||||
}
|
||||
sendWebSocketData({ color_order: event.target.value });
|
||||
}
|
||||
|
||||
function createPatternButtons(patterns) {
|
||||
const container = document.getElementById('pattern_buttons');
|
||||
container.innerHTML = ''; // Clear previous buttons
|
||||
const container = document.getElementById("pattern_buttons");
|
||||
container.innerHTML = ""; // Clear previous buttons
|
||||
|
||||
patterns.forEach(pattern => {
|
||||
const button = document.createElement('button');
|
||||
button.type = 'button'; // Use 'button' instead of 'submit'
|
||||
button.textContent = pattern;
|
||||
button.value = pattern;
|
||||
button.addEventListener('click', async function(event) {
|
||||
event.preventDefault();
|
||||
await updatePattern(pattern);
|
||||
});
|
||||
container.appendChild(button);
|
||||
patterns.forEach((pattern) => {
|
||||
const button = document.createElement("button");
|
||||
button.type = "button";
|
||||
button.textContent = pattern;
|
||||
button.value = pattern;
|
||||
button.addEventListener("click", async function (event) {
|
||||
event.preventDefault();
|
||||
await updatePattern(pattern);
|
||||
});
|
||||
container.appendChild(button);
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', async function() {
|
||||
document.getElementById('color').addEventListener('input', updateColor);
|
||||
document.getElementById('color2').addEventListener('input', updateColor2);
|
||||
document.getElementById('delay').addEventListener('input', updateDelay);
|
||||
document.getElementById('brightness').addEventListener('input', updateBrightness);
|
||||
document.getElementById('num_leds_form').addEventListener('submit', updateNumLeds);
|
||||
document.getElementById('wifi_form').addEventListener('submit', updateWifi);
|
||||
document.getElementById('delay').addEventListener('touchend', updateDelay);
|
||||
document.getElementById('brightness').addEventListener('touchend', updateBrightness);
|
||||
document.addEventListener("DOMContentLoaded", async function () {
|
||||
// Get the connection status element once the DOM is ready
|
||||
connectionStatusElement = document.getElementById("connection-status");
|
||||
|
||||
document.querySelectorAll(".pattern_button").forEach(button => {
|
||||
console.log(button.value);
|
||||
button.addEventListener('click', async event => {
|
||||
event.preventDefault();
|
||||
await updatePattern(button.value);
|
||||
});
|
||||
});
|
||||
// Establish WebSocket connection on page load
|
||||
connectWebSocket();
|
||||
|
||||
document.getElementById("color").addEventListener("input", updateColor);
|
||||
document.getElementById("color2").addEventListener("input", updateColor2);
|
||||
document.getElementById("delay").addEventListener("input", updateDelay);
|
||||
document
|
||||
.getElementById("brightness")
|
||||
.addEventListener("input", updateBrightness);
|
||||
document
|
||||
.getElementById("num_leds_form")
|
||||
.addEventListener("submit", updateNumLeds);
|
||||
document.getElementById("name_form").addEventListener("submit", updateName);
|
||||
document.getElementById("id_form").addEventListener("submit", updateID);
|
||||
document
|
||||
.getElementById("led_pin_form")
|
||||
.addEventListener("submit", updateLedPin);
|
||||
document.getElementById("delay").addEventListener("touchend", updateDelay);
|
||||
document
|
||||
.getElementById("brightness")
|
||||
.addEventListener("touchend", updateBrightness);
|
||||
|
||||
document.getElementById("rgb").addEventListener("change", handleRadioChange);
|
||||
document.getElementById("rbg").addEventListener("change", handleRadioChange);
|
||||
document.querySelectorAll(".pattern_button").forEach((button) => {
|
||||
console.log(button.value);
|
||||
button.addEventListener("click", async (event) => {
|
||||
event.preventDefault();
|
||||
await updatePattern(button.value);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Function to toggle the display of the settings menu
|
||||
function selectSettings() {
|
||||
const settingsMenu = document.getElementById('settings_menu');
|
||||
controls = document.getElementById('controls');
|
||||
settingsMenu.style.display = 'block';
|
||||
controls.style.display = 'none';
|
||||
const settingsMenu = document.getElementById("settings_menu");
|
||||
controls = document.getElementById("controls");
|
||||
settingsMenu.style.display = "block";
|
||||
controls.style.display = "none";
|
||||
}
|
||||
|
||||
function selectControls() {
|
||||
const settingsMenu = document.getElementById('settings_menu');
|
||||
controls = document.getElementById('controls');
|
||||
settingsMenu.style.display = 'none';
|
||||
controls.style.display = 'block';
|
||||
const settingsMenu = document.getElementById("settings_menu");
|
||||
controls = document.getElementById("controls");
|
||||
settingsMenu.style.display = "none";
|
||||
controls.style.display = "block";
|
||||
}
|
||||
|
@@ -1,71 +1,124 @@
|
||||
{% args settings, patterns %}
|
||||
<!DOCTYPE html>
|
||||
{% args settings, patterns, mac %}
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>LED Control</title>
|
||||
<script src="static/main.js"></script>
|
||||
<link rel="stylesheet" href="static/main.css">
|
||||
</head>
|
||||
<body>
|
||||
<h1>Control LEDs</h1>
|
||||
<button onclick="selectControls()">Controls</button>
|
||||
<button onclick="selectSettings()">Settings</button>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>{{settings['name']}}</title>
|
||||
<script src="static/main.js"></script>
|
||||
<link rel="stylesheet" href="static/main.css" />
|
||||
</head>
|
||||
<body>
|
||||
<h1>{{settings['name']}}</h1>
|
||||
<button onclick="selectControls()">Controls</button>
|
||||
<button onclick="selectSettings()">Settings</button>
|
||||
|
||||
<!-- Main LED Controls -->
|
||||
<div id="controls">
|
||||
<div id="pattern_buttons">
|
||||
{% for p in patterns %}
|
||||
<button class="pattern_button" value="{{p}}">{{p}}</button>
|
||||
{% endfor %}
|
||||
|
||||
<!-- Pattern buttons will be inserted here -->
|
||||
<!-- Main LED Controls -->
|
||||
<div id="controls">
|
||||
<div id="pattern_buttons">
|
||||
{% for p in patterns %}
|
||||
<button class="pattern_button" value="{{p}}">{{p}}</button>
|
||||
{% endfor %}
|
||||
|
||||
<!-- Pattern buttons will be inserted here -->
|
||||
</div>
|
||||
<form id="delay_form" method="post" action="/delay">
|
||||
<label for="delay">Delay:</label>
|
||||
<input
|
||||
type="range"
|
||||
id="delay"
|
||||
name="delay"
|
||||
min="1"
|
||||
max="1000"
|
||||
value="{{settings['delay']}}"
|
||||
step="10"
|
||||
/>
|
||||
</form>
|
||||
<form id="brightness_form" method="post" action="/brightness">
|
||||
<label for="brightness">Brightness:</label>
|
||||
<input
|
||||
type="range"
|
||||
id="brightness"
|
||||
name="brightness"
|
||||
min="0"
|
||||
max="100"
|
||||
value="{{settings['brightness']}}"
|
||||
step="1"
|
||||
/>
|
||||
</form>
|
||||
<form id="color_form" method="post" action="/color">
|
||||
<input
|
||||
type="color"
|
||||
id="color"
|
||||
name="color"
|
||||
value="{{settings['color1']}}"
|
||||
/>
|
||||
</form>
|
||||
<form id="color2_form" method="post" action="/color2">
|
||||
<input
|
||||
type="color"
|
||||
id="color2"
|
||||
name="color2"
|
||||
value="{{settings['color2']}}"
|
||||
/>
|
||||
</form>
|
||||
</div>
|
||||
<form id="delay_form" method="post" action="/delay">
|
||||
<label for="delay">Delay:</label>
|
||||
<input type="range" id="delay" name="delay" min="1" max="1000" value="{{settings['delay']}}" step="10">
|
||||
</form>
|
||||
<form id="brightness_form" method="post" action="/brightness">
|
||||
<label for="brightness">Brightness:</label>
|
||||
<input type="range" id="brightness" name="brightness" min="0" max="100" value="{{settings['brightness']}}" step="1">
|
||||
</form>
|
||||
<form id="color_form" method="post" action="/color">
|
||||
<input type="color" id="color" name="color" value="{{settings['color1']}}">
|
||||
</form>
|
||||
<form id="color2_form" method="post" action="/color2">
|
||||
<input type="color" id="color2" name="color2" value="{{settings['color2']}}">
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Settings Menu for num_leds, Wi-Fi SSID, and Password -->
|
||||
|
||||
<div id="settings_menu" style="display: none;">
|
||||
<h2>Settings</h2>
|
||||
|
||||
<!-- Separate form for submitting num_leds -->
|
||||
<form id="num_leds_form" method="post" action="/num_leds">
|
||||
<label for="num_leds">Number of LEDs:</label>
|
||||
<input type="text" id="num_leds" name="num_leds" value="{{settings['num_leds']}}">
|
||||
<input type="submit" value="Update Number of LEDs">
|
||||
</form>
|
||||
<!-- Settings Menu for num_leds, Wi-Fi SSID, and Password -->
|
||||
|
||||
<!-- Form for Wi-Fi SSID and password -->
|
||||
<form id="wifi_form" method="post" action="/wifi_settings">
|
||||
<label for="ssid">Wi-Fi SSID:</label>
|
||||
<input type="text" id="ssid" name="ssid" value="{{settings['wifi']['ssid']}}">
|
||||
<br>
|
||||
<label for="password">Wi-Fi Password:</label>
|
||||
<input type="password" id="password" name="password">
|
||||
<br>
|
||||
<label for="ip">Wi-Fi IP:</label>
|
||||
<input type="ip" id="ip" name="ip" value="{{settings.get('wifi', {}).get('ip', '')}}">
|
||||
<br>
|
||||
<label for="gateway">Wi-Fi Gateway:</label>
|
||||
<input type="gateway" id="gateway" name="gateway" value="{{settings.get('wifi', {}).get('gateway', '')}}">
|
||||
<br>
|
||||
<input type="submit" value="Save Wi-Fi Settings">
|
||||
</form>
|
||||
</div>
|
||||
</body>
|
||||
<div id="settings_menu" style="display: none">
|
||||
<h2>Settings</h2>
|
||||
|
||||
<form id="name_form" method="post" action="/name">
|
||||
<label for="name">Name:</label>
|
||||
<input
|
||||
type="text"
|
||||
id="name"
|
||||
name="num_leds"
|
||||
value="{{settings['name']}}"
|
||||
/>
|
||||
<input type="submit" value="Update Name" />
|
||||
</form>
|
||||
<form id="id_form" method="post" action="/id">
|
||||
<label for="id">ID:</label>
|
||||
<input
|
||||
type="text"
|
||||
id="id"
|
||||
name="id"
|
||||
value="{{settings['id']}}"
|
||||
/>
|
||||
<input type="submit" value="Update ID" />
|
||||
</form>
|
||||
<!-- Separate form for submitting num_leds -->
|
||||
<form id="num_leds_form" method="post" action="/num_leds">
|
||||
<label for="num_leds">Number of LEDs:</label>
|
||||
<input
|
||||
type="text"
|
||||
id="num_leds"
|
||||
name="num_leds"
|
||||
value="{{settings['num_leds']}}"
|
||||
/>
|
||||
<input type="submit" value="Update Number of LEDs" />
|
||||
</form>
|
||||
<form id="led_pin_form" method="post" action="/led_pin">
|
||||
<label for="num_leds">Led pin:</label>
|
||||
<input
|
||||
type="text"
|
||||
id="led_pin"
|
||||
name="led_pin"
|
||||
value="{{settings['led_pin']}}"
|
||||
/>
|
||||
<input type="submit" value="Update Led Pin" />
|
||||
</form>
|
||||
<form id="color_order_form">
|
||||
<label for="rgb">RGB:</label>
|
||||
<input type="radio" id="rgb" name="color_order" value="rgb" {{'checked' if settings["color_order"]=="rgb" else ''}} />
|
||||
<label for="rbg">RBG</label>
|
||||
<input type="radio" id="rbg" name="color_order" value="rbg" {{'checked' if settings["color_order"]=="rbg" else ''}}/>
|
||||
</form>
|
||||
|
||||
<p>Mac address: {{mac}}</p>
|
||||
</div>
|
||||
<div id="connection-status"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
@@ -1,94 +0,0 @@
|
||||
# Autogenerated file
|
||||
def render(settings, patterns):
|
||||
yield """<!DOCTYPE html>
|
||||
<html lang=\"en\">
|
||||
<head>
|
||||
<meta charset=\"UTF-8\">
|
||||
<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">
|
||||
<title>LED Control</title>
|
||||
<script src=\"static/main.js\"></script>
|
||||
<link rel=\"stylesheet\" href=\"static/main.css\">
|
||||
</head>
|
||||
<body>
|
||||
<h1>Control LEDs</h1>
|
||||
<button onclick=\"selectControls()\">Controls</button>
|
||||
<button onclick=\"selectSettings()\">Settings</button>
|
||||
|
||||
<!-- Main LED Controls -->
|
||||
<div id=\"controls\">
|
||||
<div id=\"pattern_buttons\">
|
||||
"""
|
||||
for p in patterns:
|
||||
yield """ <button class=\"pattern_button\" value=\""""
|
||||
yield str(p)
|
||||
yield """\">"""
|
||||
yield str(p)
|
||||
yield """</button>
|
||||
"""
|
||||
yield """
|
||||
<!-- Pattern buttons will be inserted here -->
|
||||
</div>
|
||||
<form id=\"delay_form\" method=\"post\" action=\"/delay\">
|
||||
<label for=\"delay\">Delay:</label>
|
||||
<input type=\"range\" id=\"delay\" name=\"delay\" min=\"1\" max=\"1000\" value=\""""
|
||||
yield str(settings['delay'])
|
||||
yield """\" step=\"10\">
|
||||
</form>
|
||||
<form id=\"brightness_form\" method=\"post\" action=\"/brightness\">
|
||||
<label for=\"brightness\">Brightness:</label>
|
||||
<input type=\"range\" id=\"brightness\" name=\"brightness\" min=\"0\" max=\"100\" value=\""""
|
||||
yield str(settings['brightness'])
|
||||
yield """\" step=\"1\">
|
||||
</form>
|
||||
<form id=\"color_form\" method=\"post\" action=\"/color\">
|
||||
<input type=\"color\" id=\"color\" name=\"color\" value=\""""
|
||||
yield str(settings['color1'])
|
||||
yield """\">
|
||||
</form>
|
||||
<form id=\"color2_form\" method=\"post\" action=\"/color2\">
|
||||
<input type=\"color\" id=\"color2\" name=\"color2\" value=\""""
|
||||
yield str(settings['color2'])
|
||||
yield """\">
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Settings Menu for num_leds, Wi-Fi SSID, and Password -->
|
||||
|
||||
<div id=\"settings_menu\" style=\"display: none;\">
|
||||
<h2>Settings</h2>
|
||||
|
||||
<!-- Separate form for submitting num_leds -->
|
||||
<form id=\"num_leds_form\" method=\"post\" action=\"/num_leds\">
|
||||
<label for=\"num_leds\">Number of LEDs:</label>
|
||||
<input type=\"text\" id=\"num_leds\" name=\"num_leds\" value=\""""
|
||||
yield str(settings['num_leds'])
|
||||
yield """\">
|
||||
<input type=\"submit\" value=\"Update Number of LEDs\">
|
||||
</form>
|
||||
|
||||
<!-- Form for Wi-Fi SSID and password -->
|
||||
<form id=\"wifi_form\" method=\"post\" action=\"/wifi_settings\">
|
||||
<label for=\"ssid\">Wi-Fi SSID:</label>
|
||||
<input type=\"text\" id=\"ssid\" name=\"ssid\" value=\""""
|
||||
yield str(settings['wifi']['ssid'])
|
||||
yield """\">
|
||||
<br>
|
||||
<label for=\"password\">Wi-Fi Password:</label>
|
||||
<input type=\"password\" id=\"password\" name=\"password\">
|
||||
<br>
|
||||
<label for=\"ip\">Wi-Fi IP:</label>
|
||||
<input type=\"ip\" id=\"ip\" name=\"ip\" value=\""""
|
||||
yield str(settings.get('wifi', {}).get('ip', ''))
|
||||
yield """\">
|
||||
<br>
|
||||
<label for=\"gateway\">Wi-Fi Gateway:</label>
|
||||
<input type=\"gateway\" id=\"gateway\" name=\"gateway\" value=\""""
|
||||
yield str(settings.get('wifi', {}).get('gateway', ''))
|
||||
yield """\">
|
||||
<br>
|
||||
<input type=\"submit\" value=\"Save Wi-Fi Settings\">
|
||||
</form>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
130
src/web.py
130
src/web.py
@@ -1,135 +1,43 @@
|
||||
from microdot import Microdot, send_file, Response
|
||||
from microdot.utemplate import Template
|
||||
from microdot.websocket import with_websocket
|
||||
|
||||
import json
|
||||
import machine
|
||||
import wifi
|
||||
import json
|
||||
|
||||
def web(settings, patterns):
|
||||
app = Microdot()
|
||||
Response.default_content_type = 'text/html'
|
||||
|
||||
@app.route('/')
|
||||
async def index(request):
|
||||
return Template('/index.html').render(settings=settings, patterns=patterns.patterns.keys())
|
||||
async def index_hnadler(request):
|
||||
mac = wifi.get_mac().hex()
|
||||
return Template('/index.html').render(settings=settings, patterns=patterns.patterns.keys(), mac=mac)
|
||||
|
||||
@app.route("/static/<path:path>")
|
||||
def static(request, path):
|
||||
def static_handler(request, path):
|
||||
if '..' in path:
|
||||
# Directory traversal is not allowed
|
||||
return 'Not found', 404
|
||||
return send_file('static/' + path)
|
||||
|
||||
@app.post("/num_leds")
|
||||
def num_leds(request):
|
||||
try:
|
||||
data = json.loads(request.body.decode('utf-8'))
|
||||
num_leds = int(data["num_leds"])
|
||||
patterns.update_num_leds(4, num_leds)
|
||||
settings["num_leds"] = num_leds
|
||||
settings.save()
|
||||
return "OK", 200
|
||||
except (ValueError, KeyError, json.JSONDecodeError):
|
||||
return "Bad request", 400
|
||||
@app.post("/settings")
|
||||
def settings_handler(request):
|
||||
# Keep the POST handler for compatibility or alternative usage if needed
|
||||
# For WebSocket updates, the /ws handler is now primary
|
||||
return settings.set_settings(request.body.decode('utf-8'), patterns)
|
||||
|
||||
@app.post("/pattern")
|
||||
def pattern(request):
|
||||
try:
|
||||
data = json.loads(request.body.decode('utf-8'))
|
||||
pattern = data["pattern"]
|
||||
if patterns.select(pattern):
|
||||
settings["selected_pattern"] = pattern
|
||||
settings.save()
|
||||
return "OK", 200
|
||||
else:
|
||||
return "Bad request", 400
|
||||
except (KeyError, json.JSONDecodeError):
|
||||
return "Bad request", 400
|
||||
|
||||
@app.post("/delay")
|
||||
def delay(request):
|
||||
try:
|
||||
data = json.loads(request.body.decode('utf-8'))
|
||||
delay = int(data["delay"])
|
||||
patterns.set_delay(delay)
|
||||
settings["delay"] = delay
|
||||
settings.save()
|
||||
return "OK", 200
|
||||
except (ValueError, KeyError, json.JSONDecodeError):
|
||||
return "Bad request", 400
|
||||
|
||||
@app.post("/brightness")
|
||||
def brightness(request):
|
||||
try:
|
||||
data = json.loads(request.body.decode('utf-8'))
|
||||
brightness = int(data["brightness"])
|
||||
patterns.set_brightness(brightness)
|
||||
settings["brightness"] = brightness
|
||||
settings.save()
|
||||
return "OK", 200
|
||||
except (ValueError, KeyError, json.JSONDecodeError):
|
||||
return "Bad request", 400
|
||||
|
||||
@app.post("/color")
|
||||
def color(request):
|
||||
try:
|
||||
data = json.loads(request.body.decode('utf-8'))
|
||||
color = data["color"]
|
||||
patterns.set_color1(tuple(int(color[i:i+2], 16) for i in (1, 3, 5))) # Convert hex to RGB
|
||||
settings["color1"] = color
|
||||
settings.save()
|
||||
return "OK", 200
|
||||
except (KeyError, json.JSONDecodeError, ValueError):
|
||||
return "Bad request", 400
|
||||
|
||||
@app.post("/color2")
|
||||
def color2(request):
|
||||
try:
|
||||
data = json.loads(request.body.decode('utf-8'))
|
||||
color = data["color2"]
|
||||
patterns.set_color2(tuple(int(color[i:i+2], 16) for i in (1, 3, 5))) # Convert hex to RGB
|
||||
settings["color2"] = color
|
||||
settings.save()
|
||||
return "OK", 200
|
||||
except (KeyError, json.JSONDecodeError, ValueError):
|
||||
return "Bad request", 400
|
||||
|
||||
|
||||
@app.post("/wifi_settings")
|
||||
def wifi_settings(request):
|
||||
try:
|
||||
data = json.loads(request.body.decode('utf-8'))
|
||||
print(data)
|
||||
ssid = settings['wifi']['ssid'] = data['ssid']
|
||||
password = settings['wifi']['password'] = data.get('password', settings['wifi']['password'])
|
||||
ip = settings['wifi']['ip'] = data.get('ip', None)
|
||||
gateway = settings['wifi']['gateway'] = data.get('gateway', None)
|
||||
print(settings)
|
||||
|
||||
if config := wifi.connect(ssid, password, ip, gateway):
|
||||
print(config)
|
||||
settings.save()
|
||||
|
||||
return "OK", 200
|
||||
except Exception as e:
|
||||
print(f"Wifi {e}")
|
||||
return "Bad request", 400
|
||||
|
||||
|
||||
@app.post("/sync")
|
||||
def sync(request):
|
||||
patterns.sync()
|
||||
return "OK", 200
|
||||
|
||||
@app.route("/external")
|
||||
@app.route("/ws")
|
||||
@with_websocket
|
||||
async def ws(request, ws):
|
||||
patterns.select("external")
|
||||
while True:
|
||||
data = await ws.receive()
|
||||
print(data)
|
||||
for i in range(min(patterns.num_leds, int(len(data)/3))):
|
||||
patterns.set(i, (data[i*3], data[i*3+1], data[i*3+2]))
|
||||
patterns.write()
|
||||
if data:
|
||||
|
||||
# Process the received data
|
||||
_, status_code = settings.set_settings(json.loads(data), patterns, True)
|
||||
#await ws.send(status_code)
|
||||
else:
|
||||
break
|
||||
|
||||
return app
|
||||
|
29
src/wifi.py
29
src/wifi.py
@@ -1,18 +1,16 @@
|
||||
import network
|
||||
from machine import Pin
|
||||
from time import sleep
|
||||
import ubinascii
|
||||
from settings import Settings
|
||||
|
||||
def connect(ssid, password, ip, gateway):
|
||||
if ssid is None or password is None:
|
||||
print("Missing ssid or password")
|
||||
return None
|
||||
|
||||
try:
|
||||
sta_if = network.WLAN(network.STA_IF)
|
||||
if ip is not None and gateway is not None:
|
||||
sta_if.ifconfig((ip, '255.255.255.0', gateway, '1.1.1.1'))
|
||||
if not sta_if.isconnected():
|
||||
if ssid == "" or password == "":
|
||||
print("Missing ssid or password")
|
||||
return None
|
||||
if ip != "" and gateway != "":
|
||||
sta_if.ifconfig((ip, '255.255.255.0', gateway, '1.1.1.1'))
|
||||
print('connecting to network...')
|
||||
sta_if.active(True)
|
||||
sta_if.connect(ssid, password)
|
||||
@@ -26,21 +24,16 @@ def connect(ssid, password, ip, gateway):
|
||||
return None
|
||||
|
||||
|
||||
def ap(password):
|
||||
def ap(ssid, password):
|
||||
ap_if = network.WLAN(network.AP_IF)
|
||||
ap_mac = ap_if.config('mac')
|
||||
ssid = f"led-{ubinascii.hexlify(ap_mac).decode()}"
|
||||
print(ssid)
|
||||
ap_if.active(True)
|
||||
ap_if.config(essid=ssid, password="qwerty1234")
|
||||
ap_if.config(essid=ssid, password=password)
|
||||
ap_if.active(False)
|
||||
ap_if.active(True)
|
||||
print(ap_if.ifconfig())
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def get_mac():
|
||||
ap_if = network.WLAN(network.AP_IF)
|
||||
return ap_if.config('mac')
|
||||
|
Reference in New Issue
Block a user