Compare commits
17 Commits
29d7a5bcfc
...
main
Author | SHA1 | Date | |
---|---|---|---|
d00d21e2b6 | |||
deca1b6c37 | |||
5c35e68ab2 | |||
8b6bbdeb56 | |||
09bc09cca3 | |||
e57feda131 | |||
3242aa464b | |||
72b7ba39ef | |||
c2a0cfaef4 | |||
4c3337a232 | |||
825ae1f637 | |||
14a70cb024 | |||
425511d41f | |||
3e5239f3c6 | |||
2fa02086c9 | |||
28fa71b8ad | |||
dc691b522b |
14
Pipfile
Normal file
14
Pipfile
Normal file
@@ -0,0 +1,14 @@
|
||||
[[source]]
|
||||
url = "https://pypi.org/simple"
|
||||
verify_ssl = true
|
||||
name = "pypi"
|
||||
|
||||
[packages]
|
||||
mpremote = "*"
|
||||
pyserial = "*"
|
||||
esptool = "*"
|
||||
|
||||
[dev-packages]
|
||||
|
||||
[requires]
|
||||
python_version = "3.12"
|
414
Pipfile.lock
generated
Normal file
414
Pipfile.lock
generated
Normal file
@@ -0,0 +1,414 @@
|
||||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "8b14bb293b7e7117ffc89c2bc92d7aa2290e8f68be7fc0f073f2b3f7f959ef71"
|
||||
},
|
||||
"pipfile-spec": 6,
|
||||
"requires": {
|
||||
"python_version": "3.12"
|
||||
},
|
||||
"sources": [
|
||||
{
|
||||
"name": "pypi",
|
||||
"url": "https://pypi.org/simple",
|
||||
"verify_ssl": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"default": {
|
||||
"argcomplete": {
|
||||
"hashes": [
|
||||
"sha256:65b3133a29ad53fb42c48cf5114752c7ab66c1c38544fdf6460f450c09b42591",
|
||||
"sha256:d0519b1bc867f5f4f4713c41ad0aba73a4a5f007449716b16f385f2166dc6adf"
|
||||
],
|
||||
"markers": "sys_platform != 'win32'",
|
||||
"version": "==3.6.2"
|
||||
},
|
||||
"bitarray": {
|
||||
"hashes": [
|
||||
"sha256:013ba795deb6c54fdb0e70103fc142f97746074d2f67b4b6a8f67a17f2d03f06",
|
||||
"sha256:01df531279959c95c0eb1eccd3e6121cb241ddcb821594f3eb07a94b086f71a0",
|
||||
"sha256:0330f470bdb76825d760215e01f8d60ce09d4ac84434b364e27236db5657d323",
|
||||
"sha256:05db62a7867702ddc7f4c58ed3804d5aa9cc0cf5ce652f98b30281a2d1174bda",
|
||||
"sha256:0626cfd86070cc71bf089e9c62c27c03ced24d3ebc44ff9b1c6a590991ace74f",
|
||||
"sha256:07d9fa226a06971ca35c720c99666cb8542f0e4d5cf234583e0822b45e68755b",
|
||||
"sha256:0a4bb5dd53250e3c70924fd473034cb2e741027938702d9cc319646e53091dc1",
|
||||
"sha256:0bedc6531388e719d8fa1eb80b1bcf97ccdccedf4a0daa02bc4f81d34a50d309",
|
||||
"sha256:0dd3b351628fe0edf812d8a7e29d2b44ca8a5599d871fadf5cfa5362dbd10689",
|
||||
"sha256:0e942e5ac197e31ce6108ab783cbb177f6372289a5a0c9a84dcd8e3ea1129748",
|
||||
"sha256:0efde6c15876a159733d6d57512fc565581e3bba877ad84508b224758c4bd50f",
|
||||
"sha256:1037ac94bf04f59e0085b24dc8252d663e6e5024af4de1372bc026efa8d4bd01",
|
||||
"sha256:10c1dc463de03521b7a350426449daa1606177e2e15f6874a27b1a7330f42a4f",
|
||||
"sha256:11fcc8e92699a2463055ceab63071ff2179a1f53d1284f4b7b9a405365065efa",
|
||||
"sha256:122d390230075671420845b6d59d01d9e3e5a16f9ad8791a78ef06396a6ca2d7",
|
||||
"sha256:12cc15dba08edb6e80c3a7f43cfaebba98dcbb89b120d534e32a42cd57c5f15f",
|
||||
"sha256:12fb9cdd96d1d1646d4fe67aa21e28d2fcedb431c703d1f37b2221fcb0cfe0c9",
|
||||
"sha256:15197c8a3ec258401f80bbcc64b942d82dfcf3d9549320147aef900c80bdf77b",
|
||||
"sha256:15714d2dc4fe6bb3c93ffa88cab026da993c6bc131c191fb3c59f697847a7621",
|
||||
"sha256:16651c323570bf9ddf43b155aa47d58c0046bd0f98cec3fdd8cfac9a9468da5c",
|
||||
"sha256:171f2fd9f0d3e3e9f08addcd8393677df7e5e55a294c13b5ac16a961870ee647",
|
||||
"sha256:1babc8dba17fad7409ca1cfe6ec4b89d175070f20d2c6f97f87d1c257be4aea9",
|
||||
"sha256:1ed6b53f947ae738258175ecf8f249172a416204f18ce67ba67a3f02dc910703",
|
||||
"sha256:1feb9bf948d075d7599632c14d3a499e31718502355f2ec96690d09ff7f71b8d",
|
||||
"sha256:1fec333d4c744b32396a46378ed42b05ffa90aa62ae99ed799c851e6a2134327",
|
||||
"sha256:20408d8d6eb3ff5ef3c62ece8a785b7cfb1a4be6979ee614eac63d578fe9b303",
|
||||
"sha256:2232724b1b0822ca56a6649769147104306849d2841bba5cdee746c4748ce34b",
|
||||
"sha256:268ec3d5744ced25edfcf65e01ce4b72592b0b587d8919bc409288e97e2831a1",
|
||||
"sha256:27bb390521ba1032b95e31683fa9aed042222fca653760d5101435c2dbf28ede",
|
||||
"sha256:2bcc21447e4a7f132485905b471164d51030da829563512789a7085817545f3b",
|
||||
"sha256:2d0b70cf75f82c919fe486af185895a77644ac3621ea8bd5b5a82fd21c03c843",
|
||||
"sha256:30f8925e9a101843a89e55f527f581b9da34bd97a697c063754be0b681c49694",
|
||||
"sha256:332a373fe20fdba78968bbeeb1aa01f2d861a30d938bb986e7101246cf371500",
|
||||
"sha256:336369abb755401278221577f83963d4522a0454de4bbf3cc913d24855d8a47e",
|
||||
"sha256:33eee090eade2c8303bfc01a9e104fea306d330035b18b5c50a04cb0cb76f08d",
|
||||
"sha256:340dd788dad07ad004b591925e4b906786aaefb6632ea9d9ac616913f3cafa4e",
|
||||
"sha256:341104cb87536114dc30728231427a335db4f90ea7e9ab94d8b1a94ff253624f",
|
||||
"sha256:35c4d724b79a3a0878999dff799d477c7eb771fd96695cf9bc5aec8aa4d956a2",
|
||||
"sha256:36efcce5a4b18c0920d623c99fa16d2fd1bd2315e666f829d50c1a6fa1d6891a",
|
||||
"sha256:3b6dede8d214301d2e0133c76cb59c713ca9dfbadd039165dfde181f4aa8f6b7",
|
||||
"sha256:3d089a0570e2acfabac9dd40ee7bfbc36ec48ff73c9312f3e61ebf31b315d05d",
|
||||
"sha256:3edbc614128b1f054584924e330f04f083f2e2bbb1bb6df1559a85cca490d408",
|
||||
"sha256:4292ef2a67ff6a3811e018c7e32c3ce4fb74c2f5c85257c06222895138df86f4",
|
||||
"sha256:437d983fb4b34874faa2c6a0247be770ec3935b4cedc16f65f8a4cbf8c970f03",
|
||||
"sha256:4616a3318ce687b0bda0b3f09f9b074f521ebf6f6fd527c1ff96619390a3f3e2",
|
||||
"sha256:49a41a724693b9f15ac965f548c2f68f6ff7b0ab36a29009d82e99f7d402888b",
|
||||
"sha256:4aa615307dbe11f8776af6a01a056ac6851ab200ef7a4ab49140bfac2fada5e4",
|
||||
"sha256:4db7cd3926ba52c5f4d4e76f9813ccc89b8c2cb077fccac0b86289f5db9b3710",
|
||||
"sha256:4fddc0366431da5b6c0f5527e8e7093680f76706420acdfb460be4a6cfb03197",
|
||||
"sha256:51126ccfb42d32da59f8eccaba2952e6091be534d45f594ea2b60a9c621734f5",
|
||||
"sha256:51e27ac27a6c85eb4f970e71134c0dc60b753ec9d18e2aeac5b3a5b31ea0847d",
|
||||
"sha256:5776328e0630af51e11c6dcf44490ef8c4b4f862e88ca48cb619ef65d20e6b67",
|
||||
"sha256:5811b9aeacc8c2b62ed0732649600405a7df5bf28eb7b7475f56822f702fc718",
|
||||
"sha256:5af9d61f383335a52c28cac82b2a06ecf7ad72bb6a7e90711cb7534ce8c5fa07",
|
||||
"sha256:5c62c2ae324c486f8e8f0482d5a8635e255da5302c44e7a5df83eee7d87e28ec",
|
||||
"sha256:5cfd6bbd0bdd47335828a8269ce66729dff6125b4f92642f465f9924f22f4fba",
|
||||
"sha256:5e5fac8a9d1140ae55858f13914574ca63b48f968e424a02d918e46602569c02",
|
||||
"sha256:64fb1eef44861fa301f393f8b4a6606c6b030db152b8aeaa6e5370c75887c1b6",
|
||||
"sha256:6799c9002876eae5e88a48220638144d80cc3e754dfae7333482b2b185f9bc31",
|
||||
"sha256:6d39ea865f22c14f7adf44e645ff71d459b3e9588c58c71ef7b8ce488b90b29c",
|
||||
"sha256:6d4564cc36b12e8ba5d40c8fde9978012dfe912d038343c12a01b88df8ca90a1",
|
||||
"sha256:6e7274cdfe405c4e70a585b997d3a8c001425c03fa37d09a8e5460828a3d8bd6",
|
||||
"sha256:6e812aabec2438a50c58c15755db8a9f7679b604a9d3992903f9a29a9da37356",
|
||||
"sha256:70a01907ceebadbd6082b971d57d80e5af97667a8a45938a46ae23df42589c9b",
|
||||
"sha256:7201d4574f70a0f563a27e12028e39231bde277f8a2768a2c530c4f82f95b3b8",
|
||||
"sha256:722c105dd4229b91d17804a0855e8f27519ceee99d8fd4db80bf09b507d7fb60",
|
||||
"sha256:75860edbfca1550b66105e59ec06a6437935283ee9849d0b2a302f73e1260dad",
|
||||
"sha256:75df7335ed7324a1ee9002d747c36a37de42b6469601ac39fef00c6bd80a4cb4",
|
||||
"sha256:77fd3e9c576f3952870b527c3a42795108946862ec11a3b17a723939dae76a12",
|
||||
"sha256:788947fd11fad912ad17b1dee810e142c63b509a4a7b0211a44549a94baba593",
|
||||
"sha256:78a8dd296c1ee30f9e3d0fe48d0168d89f99133d797f7fc2b1993bab1b23c21f",
|
||||
"sha256:7d22ed65d138ddaf63c743a68869e78eac6a7b7632ab97ccfbf0fd96ebc43001",
|
||||
"sha256:823decea26d8be2ec46000583114d050d02033f99e54e3285c0a80f31e3d7784",
|
||||
"sha256:83e008a8d7c115926717d826cbd3bc5e35816c63feff43da9456d09df9842743",
|
||||
"sha256:8733f59c5f07f043d7e9936201fd0674e591244520c8725f0061a8f7dcd71cc1",
|
||||
"sha256:87e7a438f6a806a3f508f7d9165e3b6162bfbe0351ce1b4a954f9fb76155ea9d",
|
||||
"sha256:8ab8dc6c2e55ad9bc918672f258ac3a1251fc6621dbe4e3edfc4617b91ad6eb9",
|
||||
"sha256:8f02850724d3e6c57265246329eeb71893a4a6884521b7f18fc5d9ea467300fe",
|
||||
"sha256:8f2c1c3d1d0109b993791755f18d4b495f02744118f8f683eed982b9c8ed8687",
|
||||
"sha256:8f9a4d5595c69b65d4198d952c820e48563d66d3e17aaa46645de1196f5ff12b",
|
||||
"sha256:9007e6b0a9580eaf2826b2019b7d799ea94249fd167ddd3fde1b6b39f5bff390",
|
||||
"sha256:93b88fa7bd0f958d7172862dcbebbe7c96eff90f989c6ddd28ec6e28bbe3f768",
|
||||
"sha256:957524b4f2185bde3e1e0408320ded62de8fb2c4646d3bc74b4c8365cbf9eb50",
|
||||
"sha256:95e3cb8e64672a977b3b0cc9c0f92b6d398b4aa89c96e84a92688efd312bef2e",
|
||||
"sha256:95efab4a2fc0f9329332e62e22c505ae8d355991c1c58d4d37d05ae4a6e65b01",
|
||||
"sha256:99b35f36d70d033d6548a3e90c57fccde197190606de9466869706454993bbc5",
|
||||
"sha256:9a57105bd2100c2a98cee8fca7cab26a1c0c1f0926b0ae78bc9cc9715c2d83e9",
|
||||
"sha256:9be0b089fd1cb7d5c88ff0277585df141471ff7ee8ed0bf9b386f23d5fde5573",
|
||||
"sha256:a2115dce1756537acd870ecdb9ec96ff3ace1f4de470ffc9112c0214fae6aef4",
|
||||
"sha256:a27456e66fae5726b2b1b9bc3ee0e2f1235bf8a353dc216d2651ad0652596657",
|
||||
"sha256:a2be3bf9a5230fe6c3c99eab4a7a015f137da4b795e8d7e9f641171ba65398fc",
|
||||
"sha256:a7eaed4731bd84504176ba5e0af3eba7a6e66afe208d5efb6a8779b66ecd51aa",
|
||||
"sha256:ae10e24915c7d84f5edb39d5385455b961c66e90a40b786cfcfba59f8399999e",
|
||||
"sha256:b0f896a433075ab422266ace595f12c49c417ec8bacfa85ae453b45a8694de14",
|
||||
"sha256:b1f0fa69cb879924391bd7751cc8135602d03b6cdac6dbc830ab60164d70e5a0",
|
||||
"sha256:b238e48844645ac397cfc67f5c8df86d640a9b33063c82ca2393a39e48b01c15",
|
||||
"sha256:b2c3541aab6ac40d9090cb52788541bcdc06524d2cdba18f9cc9cbd1edafd093",
|
||||
"sha256:b38516170daa962e342d54b1677d81c32826f9e94c21856e879b46b6e2008293",
|
||||
"sha256:bb04ec25c55614d60bbe1591a59ff29149030010ee3e1262c16a43460b193cfd",
|
||||
"sha256:bb1dcea97241397517cb52dc2646648d33a291534d8b1a8f7039d5583ef6e5f5",
|
||||
"sha256:bbe0786261a3d7c9214a8c348edc64a75c70ca4eab3164b1ec97aa10c0f0855d",
|
||||
"sha256:bcfd19c1b3c56dfde1b524e54b0f1d73a439c488dbeb40ff677b9e0a339e2356",
|
||||
"sha256:c217dc3559b21bcfc58ea37af11f7a25e8b1f71d855992bf3453b9c1ae6c02a0",
|
||||
"sha256:c233e308fd055fff62679e6a72744cf3b51621a6c18930bbcbe78649bc97f481",
|
||||
"sha256:c2984abfc4e6281e703675280edbcf7618fa6983367d1fb4822b41917e2c3490",
|
||||
"sha256:c7576226ee79957e8a4ff64addf2929cbbf2bf749ec622f325adc615d8470b14",
|
||||
"sha256:c85f235dc643857e2c8e0a93e91f1099dc56db2b4bebd160c08cae8d5ddaec21",
|
||||
"sha256:c991ba157475c196bdf9c2368d5159208dbdf23d715de8e8d94b8c1cb739bddb",
|
||||
"sha256:cf924be7b97cc5bec88bf63c09732aa5c90bd00f3152cffebed259a49df351b0",
|
||||
"sha256:d2c567ce44f9d821776682ece59237b5761443121137afa656b9f586157176af",
|
||||
"sha256:d3c0db664bffeb4bb80b228ed31773ccb701da11f266f9d8a56732e083e2cab0",
|
||||
"sha256:d4755823c68ff36b3defb2079258279fb0fb3a2d19dbd1401a28672a10f26ae0",
|
||||
"sha256:d47810266e7c5b74fe61e983cf7e6e3937120473cba6f6e1ecdc411e6f27b639",
|
||||
"sha256:d5349869fa33bd28f362f8702cc1bb1a4ec1c7cde7183cdc41933eb794c3d651",
|
||||
"sha256:d5c10255889045479b86405dd040c58e77ccf4f63a0e6e686d341b5fd8fa32c2",
|
||||
"sha256:d62db2fbf0a923ecbf5b71babc9deabd5ccea74d275bf74a5e37c050238d8f6a",
|
||||
"sha256:d6895389eeebf6836cfad1b301bae9e5386e3b94a21076aaf0c2dab0524af6d1",
|
||||
"sha256:d9ce8cb9161288288859032227039197f4ce6cbf1ff5f022b060216e49f8b591",
|
||||
"sha256:de59a5a4a54fbdb00c716a8a54935bbe19248c99647c64964b11f2f787a67cec",
|
||||
"sha256:e11ea9eabf6c886811bdc0bf4ea3350a07c7354c8abc6b6a8bd707eef687bcf5",
|
||||
"sha256:e1652bf956c8874c790fe78f0dcdc0de04d82ded81373759bfc05f427afd1ff3",
|
||||
"sha256:e264ad4a850bd1488a754b5e812d09d74fede57bc5ae679a4316ff08aa8edcaa",
|
||||
"sha256:e4e1437bda368e46871d7fbc8e1de4260c302ff22cfb734e1907f164933c7fdf",
|
||||
"sha256:e66f6fd558abf563eb68580a3961e47f7c843c2fd80be931027bc484f933b4b7",
|
||||
"sha256:e8fba33d97fd6b682c9b9107ea1f652485f3149af5aa3b6c8698034007f4adf5",
|
||||
"sha256:ef3f2dc1a95bec2af77c8685c847d41fc0c64d7329c994b6054c54462f835401",
|
||||
"sha256:f0e9aa0722b339f971e0f55b3c418d825d1ab7ecd71c2b10115897a3a39352d3",
|
||||
"sha256:f455c100df47295ca19eb36527462fecbb2710140d92a61228df4cfdd2d7dd81",
|
||||
"sha256:f4c445ed8e059a3cb6c72e4ff68b947d050791f4007a2b6e68afe12b1b2852ab",
|
||||
"sha256:f55b48bccd4f5c97401e18e9ad4483ccc4fea2d8feded13eee4a05d6850f90cf",
|
||||
"sha256:f661ed4bda9a82874cda44829dab0ef604090b8b2f8e9d1759766ffa51f1d6fe",
|
||||
"sha256:fbbc606b8bf3578356d93db02d071824f66bb7f18e5aa57aa4d74fcd6898d87c",
|
||||
"sha256:fbcf4b12fa21df99d9a5855aa52e1ec9ab0e42735d9d0f003d0b737c62522e69",
|
||||
"sha256:fc534fc485fae64ab6a5b21b26815a3dec4e57f0dc373dd3c44242e89437f3ed"
|
||||
],
|
||||
"version": "==3.4.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"
|
||||
},
|
||||
"cryptography": {
|
||||
"hashes": [
|
||||
"sha256:02f55fb4f8b79c1221b0961488eaae21015b69b210e18c386b69de182ebb1259",
|
||||
"sha256:157f1f3b8d941c2bd8f3ffee0af9b049c9665c39d3da9db2dc338feca5e98a43",
|
||||
"sha256:192ed30fac1728f7587c6f4613c29c584abdc565d7417c13904708db10206645",
|
||||
"sha256:21a83f6f35b9cc656d71b5de8d519f566df01e660ac2578805ab245ffd8523f8",
|
||||
"sha256:25cd194c39fa5a0aa4169125ee27d1172097857b27109a45fadc59653ec06f44",
|
||||
"sha256:3883076d5c4cc56dbef0b898a74eb6992fdac29a7b9013870b34efe4ddb39a0d",
|
||||
"sha256:3bb0847e6363c037df8f6ede57d88eaf3410ca2267fb12275370a76f85786a6f",
|
||||
"sha256:3be3f649d91cb182c3a6bd336de8b61a0a71965bd13d1a04a0e15b39c3d5809d",
|
||||
"sha256:3f07943aa4d7dad689e3bb1638ddc4944cc5e0921e3c227486daae0e31a05e54",
|
||||
"sha256:479d92908277bed6e1a1c69b277734a7771c2b78633c224445b5c60a9f4bc1d9",
|
||||
"sha256:4ffc61e8f3bf5b60346d89cd3d37231019c17a081208dfbbd6e1605ba03fa137",
|
||||
"sha256:5639c2b16764c6f76eedf722dbad9a0914960d3489c0cc38694ddf9464f1bb2f",
|
||||
"sha256:58968d331425a6f9eedcee087f77fd3c927c88f55368f43ff7e0a19891f2642c",
|
||||
"sha256:5d186f32e52e66994dce4f766884bcb9c68b8da62d61d9d215bfe5fb56d21334",
|
||||
"sha256:5d20cc348cca3a8aa7312f42ab953a56e15323800ca3ab0706b8cd452a3a056c",
|
||||
"sha256:6866df152b581f9429020320e5eb9794c8780e90f7ccb021940d7f50ee00ae0b",
|
||||
"sha256:7d5fe7195c27c32a64955740b949070f21cba664604291c298518d2e255931d2",
|
||||
"sha256:896530bc9107b226f265effa7ef3f21270f18a2026bc09fed1ebd7b66ddf6375",
|
||||
"sha256:962bc30480a08d133e631e8dfd4783ab71cc9e33d5d7c1e192f0b7c06397bb88",
|
||||
"sha256:978631ec51a6bbc0b7e58f23b68a8ce9e5f09721940933e9c217068388789fe5",
|
||||
"sha256:9b4d4a5dbee05a2c390bf212e78b99434efec37b17a4bff42f50285c5c8c9647",
|
||||
"sha256:ab0b005721cc0039e885ac3503825661bd9810b15d4f374e473f8c89b7d5460c",
|
||||
"sha256:af653022a0c25ef2e3ffb2c673a50e5a0d02fecc41608f4954176f1933b12359",
|
||||
"sha256:b0cc66c74c797e1db750aaa842ad5b8b78e14805a9b5d1348dc603612d3e3ff5",
|
||||
"sha256:b424563394c369a804ecbee9b06dfb34997f19d00b3518e39f83a5642618397d",
|
||||
"sha256:c138abae3a12a94c75c10499f1cbae81294a6f983b3af066390adee73f433028",
|
||||
"sha256:c6cd67722619e4d55fdb42ead64ed8843d64638e9c07f4011163e46bc512cf01",
|
||||
"sha256:c91fc8e8fd78af553f98bc7f2a1d8db977334e4eea302a4bfd75b9461c2d8904",
|
||||
"sha256:cad399780053fb383dc067475135e41c9fe7d901a97dd5d9c5dfb5611afc0d7d",
|
||||
"sha256:cb90f60e03d563ca2445099edf605c16ed1d5b15182d21831f58460c48bffb93",
|
||||
"sha256:dad80b45c22e05b259e33ddd458e9e2ba099c86ccf4e88db7bbab4b747b18d06",
|
||||
"sha256:dd3db61b8fe5be220eee484a17233287d0be6932d056cf5738225b9c05ef4fff",
|
||||
"sha256:e28d62e59a4dbd1d22e747f57d4f00c459af22181f0b2f787ea83f5a876d7c76",
|
||||
"sha256:e909df4053064a97f1e6565153ff8bb389af12c5c8d29c343308760890560aff",
|
||||
"sha256:f3ffef566ac88f75967d7abd852ed5f182da252d23fac11b4766da3957766759",
|
||||
"sha256:fc3c9babc1e1faefd62704bb46a69f359a9819eb0292e40df3fb6e3574715cd4",
|
||||
"sha256:fe19d8bc5536a91a24a8133328880a41831b6c5df54599a8417b62fe015d3053"
|
||||
],
|
||||
"markers": "python_version >= '3.7' and python_full_version not in '3.9.0, 3.9.1'",
|
||||
"version": "==44.0.3"
|
||||
},
|
||||
"ecdsa": {
|
||||
"hashes": [
|
||||
"sha256:30638e27cf77b7e15c4c4cc1973720149e1033827cfd00661ca5c8cc0cdb24c3",
|
||||
"sha256:478cba7b62555866fcb3bb3fe985e06decbdb68ef55713c4e5ab98c57d508e61"
|
||||
],
|
||||
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'",
|
||||
"version": "==0.19.1"
|
||||
},
|
||||
"esptool": {
|
||||
"hashes": [
|
||||
"sha256:dc4ef26b659e1a8dcb019147c0ea6d94980b34de99fbe09121c7941c8b254531"
|
||||
],
|
||||
"index": "pypi",
|
||||
"markers": "python_version >= '3.7'",
|
||||
"version": "==4.8.1"
|
||||
},
|
||||
"intelhex": {
|
||||
"hashes": [
|
||||
"sha256:87cc5225657524ec6361354be928adfd56bcf2a3dcc646c40f8f094c39c07db4",
|
||||
"sha256:892b7361a719f4945237da8ccf754e9513db32f5628852785aea108dcd250093"
|
||||
],
|
||||
"version": "==2.3.0"
|
||||
},
|
||||
"mpremote": {
|
||||
"hashes": [
|
||||
"sha256:42691ff8f7ea4b5f2fc1b51de99609995d383671a4b4d4daad8cbd486d26aa23",
|
||||
"sha256:d0dcd8ab364d87270e1766308882e536e541052efd64aadaac83bc7ebbea2815"
|
||||
],
|
||||
"index": "pypi",
|
||||
"markers": "python_version >= '3.4'",
|
||||
"version": "==1.25.0"
|
||||
},
|
||||
"pycparser": {
|
||||
"hashes": [
|
||||
"sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6",
|
||||
"sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"
|
||||
],
|
||||
"markers": "python_version >= '3.8'",
|
||||
"version": "==2.22"
|
||||
},
|
||||
"pyserial": {
|
||||
"hashes": [
|
||||
"sha256:3c77e014170dfffbd816e6ffc205e9842efb10be9f58ec16d3e8675b4925cddb",
|
||||
"sha256:c4451db6ba391ca6ca299fb3ec7bae67a5c55dde170964c7a14ceefec02f2cf0"
|
||||
],
|
||||
"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"
|
||||
},
|
||||
"six": {
|
||||
"hashes": [
|
||||
"sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274",
|
||||
"sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'",
|
||||
"version": "==1.17.0"
|
||||
}
|
||||
},
|
||||
"develop": {}
|
||||
}
|
33
dev.py
Executable file
33
dev.py
Executable file
@@ -0,0 +1,33 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import subprocess
|
||||
import serial
|
||||
import sys
|
||||
|
||||
print(sys.argv)
|
||||
|
||||
port = sys.argv[1]
|
||||
|
||||
cmd = sys.argv[1]
|
||||
|
||||
for cmd in sys.argv[1:]:
|
||||
print(cmd)
|
||||
match cmd:
|
||||
case "src":
|
||||
subprocess.call(["mpremote", "connect", port, "fs", "cp", "-r", ".", ":" ], cwd="src")
|
||||
case "lib":
|
||||
subprocess.call(["mpremote", "connect", port, "fs", "cp", "-r", "lib", ":" ])
|
||||
case "ls":
|
||||
subprocess.call(["mpremote", "connect", port, "fs", "ls", ":" ])
|
||||
case "reset":
|
||||
with serial.Serial(port, baudrate=115200) as ser:
|
||||
ser.write(b'\x03\x03\x04')
|
||||
case "follow":
|
||||
with serial.Serial(port, baudrate=115200) as ser:
|
||||
while True:
|
||||
if ser.in_waiting > 0: # Check if there is data in the buffer
|
||||
data = ser.readline().decode('utf-8').strip() # Read and decode the data
|
||||
print(data)
|
||||
|
||||
|
||||
|
2
lib/microdot/__init__.py
Normal file
2
lib/microdot/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
from microdot.microdot import Microdot, Request, Response, abort, redirect, \
|
||||
send_file # noqa: F401
|
8
lib/microdot/helpers.py
Normal file
8
lib/microdot/helpers.py
Normal file
@@ -0,0 +1,8 @@
|
||||
try:
|
||||
from functools import wraps
|
||||
except ImportError: # pragma: no cover
|
||||
# MicroPython does not currently implement functools.wraps
|
||||
def wraps(wrapped):
|
||||
def _(wrapper):
|
||||
return wrapper
|
||||
return _
|
1450
lib/microdot/microdot.py
Normal file
1450
lib/microdot/microdot.py
Normal file
File diff suppressed because it is too large
Load Diff
70
lib/microdot/utemplate.py
Normal file
70
lib/microdot/utemplate.py
Normal file
@@ -0,0 +1,70 @@
|
||||
from utemplate import recompile
|
||||
|
||||
_loader = None
|
||||
|
||||
|
||||
class Template:
|
||||
"""A template object.
|
||||
|
||||
:param template: The filename of the template to render, relative to the
|
||||
configured template directory.
|
||||
"""
|
||||
@classmethod
|
||||
def initialize(cls, template_dir='templates',
|
||||
loader_class=recompile.Loader):
|
||||
"""Initialize the templating subsystem.
|
||||
|
||||
:param template_dir: the directory where templates are stored. This
|
||||
argument is optional. The default is to load
|
||||
templates from a *templates* subdirectory.
|
||||
:param loader_class: the ``utemplate.Loader`` class to use when loading
|
||||
templates. This argument is optional. The default
|
||||
is the ``recompile.Loader`` class, which
|
||||
automatically recompiles templates when they
|
||||
change.
|
||||
"""
|
||||
global _loader
|
||||
_loader = loader_class(None, template_dir)
|
||||
|
||||
def __init__(self, template):
|
||||
if _loader is None: # pragma: no cover
|
||||
self.initialize()
|
||||
#: The name of the template
|
||||
self.name = template
|
||||
self.template = _loader.load(template)
|
||||
|
||||
def generate(self, *args, **kwargs):
|
||||
"""Return a generator that renders the template in chunks, with the
|
||||
given arguments."""
|
||||
return self.template(*args, **kwargs)
|
||||
|
||||
def render(self, *args, **kwargs):
|
||||
"""Render the template with the given arguments and return it as a
|
||||
string."""
|
||||
return ''.join(self.generate(*args, **kwargs))
|
||||
|
||||
def generate_async(self, *args, **kwargs):
|
||||
"""Return an asynchronous generator that renders the template in
|
||||
chunks, using the given arguments."""
|
||||
class sync_to_async_iter():
|
||||
def __init__(self, iter):
|
||||
self.iter = iter
|
||||
|
||||
def __aiter__(self):
|
||||
return self
|
||||
|
||||
async def __anext__(self):
|
||||
try:
|
||||
return next(self.iter)
|
||||
except StopIteration:
|
||||
raise StopAsyncIteration
|
||||
|
||||
return sync_to_async_iter(self.generate(*args, **kwargs))
|
||||
|
||||
async def render_async(self, *args, **kwargs):
|
||||
"""Render the template with the given arguments asynchronously and
|
||||
return it as a string."""
|
||||
response = ''
|
||||
async for chunk in self.generate_async(*args, **kwargs):
|
||||
response += chunk
|
||||
return response
|
231
lib/microdot/websocket.py
Normal file
231
lib/microdot/websocket.py
Normal file
@@ -0,0 +1,231 @@
|
||||
import binascii
|
||||
import hashlib
|
||||
from microdot import Request, Response
|
||||
from microdot.microdot import MUTED_SOCKET_ERRORS, print_exception
|
||||
from microdot.helpers import wraps
|
||||
|
||||
|
||||
class WebSocketError(Exception):
|
||||
"""Exception raised when an error occurs in a WebSocket connection."""
|
||||
pass
|
||||
|
||||
|
||||
class WebSocket:
|
||||
"""A WebSocket connection object.
|
||||
|
||||
An instance of this class is sent to handler functions to manage the
|
||||
WebSocket connection.
|
||||
"""
|
||||
CONT = 0
|
||||
TEXT = 1
|
||||
BINARY = 2
|
||||
CLOSE = 8
|
||||
PING = 9
|
||||
PONG = 10
|
||||
|
||||
#: Specify the maximum message size that can be received when calling the
|
||||
#: ``receive()`` method. Messages with payloads that are larger than this
|
||||
#: size will be rejected and the connection closed. Set to 0 to disable
|
||||
#: the size check (be aware of potential security issues if you do this),
|
||||
#: or to -1 to use the value set in
|
||||
#: ``Request.max_body_length``. The default is -1.
|
||||
#:
|
||||
#: Example::
|
||||
#:
|
||||
#: WebSocket.max_message_length = 4 * 1024 # up to 4KB messages
|
||||
max_message_length = -1
|
||||
|
||||
def __init__(self, request):
|
||||
self.request = request
|
||||
self.closed = False
|
||||
|
||||
async def handshake(self):
|
||||
response = self._handshake_response()
|
||||
await self.request.sock[1].awrite(
|
||||
b'HTTP/1.1 101 Switching Protocols\r\n')
|
||||
await self.request.sock[1].awrite(b'Upgrade: websocket\r\n')
|
||||
await self.request.sock[1].awrite(b'Connection: Upgrade\r\n')
|
||||
await self.request.sock[1].awrite(
|
||||
b'Sec-WebSocket-Accept: ' + response + b'\r\n\r\n')
|
||||
|
||||
async def receive(self):
|
||||
"""Receive a message from the client."""
|
||||
while True:
|
||||
opcode, payload = await self._read_frame()
|
||||
send_opcode, data = self._process_websocket_frame(opcode, payload)
|
||||
if send_opcode: # pragma: no cover
|
||||
await self.send(data, send_opcode)
|
||||
elif data: # pragma: no branch
|
||||
return data
|
||||
|
||||
async def send(self, data, opcode=None):
|
||||
"""Send a message to the client.
|
||||
|
||||
:param data: the data to send, given as a string or bytes.
|
||||
:param opcode: a custom frame opcode to use. If not given, the opcode
|
||||
is ``TEXT`` or ``BINARY`` depending on the type of the
|
||||
data.
|
||||
"""
|
||||
frame = self._encode_websocket_frame(
|
||||
opcode or (self.TEXT if isinstance(data, str) else self.BINARY),
|
||||
data)
|
||||
await self.request.sock[1].awrite(frame)
|
||||
|
||||
async def close(self):
|
||||
"""Close the websocket connection."""
|
||||
if not self.closed: # pragma: no cover
|
||||
self.closed = True
|
||||
await self.send(b'', self.CLOSE)
|
||||
|
||||
def _handshake_response(self):
|
||||
connection = False
|
||||
upgrade = False
|
||||
websocket_key = None
|
||||
for header, value in self.request.headers.items():
|
||||
h = header.lower()
|
||||
if h == 'connection':
|
||||
connection = True
|
||||
if 'upgrade' not in value.lower():
|
||||
return self.request.app.abort(400)
|
||||
elif h == 'upgrade':
|
||||
upgrade = True
|
||||
if not value.lower() == 'websocket':
|
||||
return self.request.app.abort(400)
|
||||
elif h == 'sec-websocket-key':
|
||||
websocket_key = value
|
||||
if not connection or not upgrade or not websocket_key:
|
||||
return self.request.app.abort(400)
|
||||
d = hashlib.sha1(websocket_key.encode())
|
||||
d.update(b'258EAFA5-E914-47DA-95CA-C5AB0DC85B11')
|
||||
return binascii.b2a_base64(d.digest())[:-1]
|
||||
|
||||
@classmethod
|
||||
def _parse_frame_header(cls, header):
|
||||
fin = header[0] & 0x80
|
||||
opcode = header[0] & 0x0f
|
||||
if fin == 0 or opcode == cls.CONT: # pragma: no cover
|
||||
raise WebSocketError('Continuation frames not supported')
|
||||
has_mask = header[1] & 0x80
|
||||
length = header[1] & 0x7f
|
||||
if length == 126:
|
||||
length = -2
|
||||
elif length == 127:
|
||||
length = -8
|
||||
return fin, opcode, has_mask, length
|
||||
|
||||
def _process_websocket_frame(self, opcode, payload):
|
||||
if opcode == self.TEXT:
|
||||
payload = payload.decode()
|
||||
elif opcode == self.BINARY:
|
||||
pass
|
||||
elif opcode == self.CLOSE:
|
||||
raise WebSocketError('Websocket connection closed')
|
||||
elif opcode == self.PING:
|
||||
return self.PONG, payload
|
||||
elif opcode == self.PONG: # pragma: no branch
|
||||
return None, None
|
||||
return None, payload
|
||||
|
||||
@classmethod
|
||||
def _encode_websocket_frame(cls, opcode, payload):
|
||||
frame = bytearray()
|
||||
frame.append(0x80 | opcode)
|
||||
if opcode == cls.TEXT:
|
||||
payload = payload.encode()
|
||||
if len(payload) < 126:
|
||||
frame.append(len(payload))
|
||||
elif len(payload) < (1 << 16):
|
||||
frame.append(126)
|
||||
frame.extend(len(payload).to_bytes(2, 'big'))
|
||||
else:
|
||||
frame.append(127)
|
||||
frame.extend(len(payload).to_bytes(8, 'big'))
|
||||
frame.extend(payload)
|
||||
return frame
|
||||
|
||||
async def _read_frame(self):
|
||||
header = await self.request.sock[0].read(2)
|
||||
if len(header) != 2: # pragma: no cover
|
||||
raise WebSocketError('Websocket connection closed')
|
||||
fin, opcode, has_mask, length = self._parse_frame_header(header)
|
||||
if length == -2:
|
||||
length = await self.request.sock[0].read(2)
|
||||
length = int.from_bytes(length, 'big')
|
||||
elif length == -8:
|
||||
length = await self.request.sock[0].read(8)
|
||||
length = int.from_bytes(length, 'big')
|
||||
max_allowed_length = Request.max_body_length \
|
||||
if self.max_message_length == -1 else self.max_message_length
|
||||
if length > max_allowed_length:
|
||||
raise WebSocketError('Message too large')
|
||||
if has_mask: # pragma: no cover
|
||||
mask = await self.request.sock[0].read(4)
|
||||
payload = await self.request.sock[0].read(length)
|
||||
if has_mask: # pragma: no cover
|
||||
payload = bytes(x ^ mask[i % 4] for i, x in enumerate(payload))
|
||||
return opcode, payload
|
||||
|
||||
|
||||
async def websocket_upgrade(request):
|
||||
"""Upgrade a request handler to a websocket connection.
|
||||
|
||||
This function can be called directly inside a route function to process a
|
||||
WebSocket upgrade handshake, for example after the user's credentials are
|
||||
verified. The function returns the websocket object::
|
||||
|
||||
@app.route('/echo')
|
||||
async def echo(request):
|
||||
if not authenticate_user(request):
|
||||
abort(401)
|
||||
ws = await websocket_upgrade(request)
|
||||
while True:
|
||||
message = await ws.receive()
|
||||
await ws.send(message)
|
||||
"""
|
||||
ws = WebSocket(request)
|
||||
await ws.handshake()
|
||||
|
||||
@request.after_request
|
||||
async def after_request(request, response):
|
||||
return Response.already_handled
|
||||
|
||||
return ws
|
||||
|
||||
|
||||
def websocket_wrapper(f, upgrade_function):
|
||||
@wraps(f)
|
||||
async def wrapper(request, *args, **kwargs):
|
||||
ws = await upgrade_function(request)
|
||||
try:
|
||||
await f(request, ws, *args, **kwargs)
|
||||
except OSError as exc:
|
||||
if exc.errno not in MUTED_SOCKET_ERRORS: # pragma: no cover
|
||||
raise
|
||||
except WebSocketError:
|
||||
pass
|
||||
except Exception as exc:
|
||||
print_exception(exc)
|
||||
finally: # pragma: no cover
|
||||
try:
|
||||
await ws.close()
|
||||
except Exception:
|
||||
pass
|
||||
return Response.already_handled
|
||||
return wrapper
|
||||
|
||||
|
||||
def with_websocket(f):
|
||||
"""Decorator to make a route a WebSocket endpoint.
|
||||
|
||||
This decorator is used to define a route that accepts websocket
|
||||
connections. The route then receives a websocket object as a second
|
||||
argument that it can use to send and receive messages::
|
||||
|
||||
@app.route('/echo')
|
||||
@with_websocket
|
||||
async def echo(request, ws):
|
||||
while True:
|
||||
message = await ws.receive()
|
||||
await ws.send(message)
|
||||
"""
|
||||
return websocket_wrapper(f, websocket_upgrade)
|
0
lib/utemplate/__init__.py
Normal file
0
lib/utemplate/__init__.py
Normal file
14
lib/utemplate/compiled.py
Normal file
14
lib/utemplate/compiled.py
Normal file
@@ -0,0 +1,14 @@
|
||||
class Loader:
|
||||
|
||||
def __init__(self, pkg, dir):
|
||||
if dir == ".":
|
||||
dir = ""
|
||||
else:
|
||||
dir = dir.replace("/", ".") + "."
|
||||
if pkg and pkg != "__main__":
|
||||
dir = pkg + "." + dir
|
||||
self.p = dir
|
||||
|
||||
def load(self, name):
|
||||
name = name.replace(".", "_")
|
||||
return __import__(self.p + name, None, None, (name,)).render
|
21
lib/utemplate/recompile.py
Normal file
21
lib/utemplate/recompile.py
Normal file
@@ -0,0 +1,21 @@
|
||||
# (c) 2014-2020 Paul Sokolovsky. MIT license.
|
||||
try:
|
||||
from uos import stat, remove
|
||||
except:
|
||||
from os import stat, remove
|
||||
from . import source
|
||||
|
||||
|
||||
class Loader(source.Loader):
|
||||
|
||||
def load(self, name):
|
||||
o_path = self.pkg_path + self.compiled_path(name)
|
||||
i_path = self.pkg_path + self.dir + "/" + name
|
||||
try:
|
||||
o_stat = stat(o_path)
|
||||
i_stat = stat(i_path)
|
||||
if i_stat[8] > o_stat[8]:
|
||||
# input file is newer, remove output to force recompile
|
||||
remove(o_path)
|
||||
finally:
|
||||
return super().load(name)
|
188
lib/utemplate/source.py
Normal file
188
lib/utemplate/source.py
Normal file
@@ -0,0 +1,188 @@
|
||||
# (c) 2014-2019 Paul Sokolovsky. MIT license.
|
||||
from . import compiled
|
||||
|
||||
|
||||
class Compiler:
|
||||
|
||||
START_CHAR = "{"
|
||||
STMNT = "%"
|
||||
STMNT_END = "%}"
|
||||
EXPR = "{"
|
||||
EXPR_END = "}}"
|
||||
|
||||
def __init__(self, file_in, file_out, indent=0, seq=0, loader=None):
|
||||
self.file_in = file_in
|
||||
self.file_out = file_out
|
||||
self.loader = loader
|
||||
self.seq = seq
|
||||
self._indent = indent
|
||||
self.stack = []
|
||||
self.in_literal = False
|
||||
self.flushed_header = False
|
||||
self.args = "*a, **d"
|
||||
|
||||
def indent(self, adjust=0):
|
||||
if not self.flushed_header:
|
||||
self.flushed_header = True
|
||||
self.indent()
|
||||
self.file_out.write("def render%s(%s):\n" % (str(self.seq) if self.seq else "", self.args))
|
||||
self.stack.append("def")
|
||||
self.file_out.write(" " * (len(self.stack) + self._indent + adjust))
|
||||
|
||||
def literal(self, s):
|
||||
if not s:
|
||||
return
|
||||
if not self.in_literal:
|
||||
self.indent()
|
||||
self.file_out.write('yield """')
|
||||
self.in_literal = True
|
||||
self.file_out.write(s.replace('"', '\\"'))
|
||||
|
||||
def close_literal(self):
|
||||
if self.in_literal:
|
||||
self.file_out.write('"""\n')
|
||||
self.in_literal = False
|
||||
|
||||
def render_expr(self, e):
|
||||
self.indent()
|
||||
self.file_out.write('yield str(' + e + ')\n')
|
||||
|
||||
def parse_statement(self, stmt):
|
||||
tokens = stmt.split(None, 1)
|
||||
if tokens[0] == "args":
|
||||
if len(tokens) > 1:
|
||||
self.args = tokens[1]
|
||||
else:
|
||||
self.args = ""
|
||||
elif tokens[0] == "set":
|
||||
self.indent()
|
||||
self.file_out.write(stmt[3:].strip() + "\n")
|
||||
elif tokens[0] == "include":
|
||||
if not self.flushed_header:
|
||||
# If there was no other output, we still need a header now
|
||||
self.indent()
|
||||
tokens = tokens[1].split(None, 1)
|
||||
args = ""
|
||||
if len(tokens) > 1:
|
||||
args = tokens[1]
|
||||
if tokens[0][0] == "{":
|
||||
self.indent()
|
||||
# "1" as fromlist param is uPy hack
|
||||
self.file_out.write('_ = __import__(%s.replace(".", "_"), None, None, 1)\n' % tokens[0][2:-2])
|
||||
self.indent()
|
||||
self.file_out.write("yield from _.render(%s)\n" % args)
|
||||
return
|
||||
|
||||
with self.loader.input_open(tokens[0][1:-1]) as inc:
|
||||
self.seq += 1
|
||||
c = Compiler(inc, self.file_out, len(self.stack) + self._indent, self.seq)
|
||||
inc_id = self.seq
|
||||
self.seq = c.compile()
|
||||
self.indent()
|
||||
self.file_out.write("yield from render%d(%s)\n" % (inc_id, args))
|
||||
elif len(tokens) > 1:
|
||||
if tokens[0] == "elif":
|
||||
assert self.stack[-1] == "if"
|
||||
self.indent(-1)
|
||||
self.file_out.write(stmt + ":\n")
|
||||
else:
|
||||
self.indent()
|
||||
self.file_out.write(stmt + ":\n")
|
||||
self.stack.append(tokens[0])
|
||||
else:
|
||||
if stmt.startswith("end"):
|
||||
assert self.stack[-1] == stmt[3:]
|
||||
self.stack.pop(-1)
|
||||
elif stmt == "else":
|
||||
assert self.stack[-1] == "if"
|
||||
self.indent(-1)
|
||||
self.file_out.write("else:\n")
|
||||
else:
|
||||
assert False
|
||||
|
||||
def parse_line(self, l):
|
||||
while l:
|
||||
start = l.find(self.START_CHAR)
|
||||
if start == -1:
|
||||
self.literal(l)
|
||||
return
|
||||
self.literal(l[:start])
|
||||
self.close_literal()
|
||||
sel = l[start + 1]
|
||||
#print("*%s=%s=" % (sel, EXPR))
|
||||
if sel == self.STMNT:
|
||||
end = l.find(self.STMNT_END)
|
||||
assert end > 0
|
||||
stmt = l[start + len(self.START_CHAR + self.STMNT):end].strip()
|
||||
self.parse_statement(stmt)
|
||||
end += len(self.STMNT_END)
|
||||
l = l[end:]
|
||||
if not self.in_literal and l == "\n":
|
||||
break
|
||||
elif sel == self.EXPR:
|
||||
# print("EXPR")
|
||||
end = l.find(self.EXPR_END)
|
||||
assert end > 0
|
||||
expr = l[start + len(self.START_CHAR + self.EXPR):end].strip()
|
||||
self.render_expr(expr)
|
||||
end += len(self.EXPR_END)
|
||||
l = l[end:]
|
||||
else:
|
||||
self.literal(l[start])
|
||||
l = l[start + 1:]
|
||||
|
||||
def header(self):
|
||||
self.file_out.write("# Autogenerated file\n")
|
||||
|
||||
def compile(self):
|
||||
self.header()
|
||||
for l in self.file_in:
|
||||
self.parse_line(l)
|
||||
self.close_literal()
|
||||
return self.seq
|
||||
|
||||
|
||||
class Loader(compiled.Loader):
|
||||
|
||||
def __init__(self, pkg, dir):
|
||||
super().__init__(pkg, dir)
|
||||
self.dir = dir
|
||||
if pkg == "__main__":
|
||||
# if pkg isn't really a package, don't bother to use it
|
||||
# it means we're running from "filesystem directory", not
|
||||
# from a package.
|
||||
pkg = None
|
||||
|
||||
self.pkg_path = ""
|
||||
if pkg:
|
||||
p = __import__(pkg)
|
||||
if isinstance(p.__path__, str):
|
||||
# uPy
|
||||
self.pkg_path = p.__path__
|
||||
else:
|
||||
# CPy
|
||||
self.pkg_path = p.__path__[0]
|
||||
self.pkg_path += "/"
|
||||
|
||||
def input_open(self, template):
|
||||
path = self.pkg_path + self.dir + "/" + template
|
||||
return open(path)
|
||||
|
||||
def compiled_path(self, template):
|
||||
return self.dir + "/" + template.replace(".", "_") + ".py"
|
||||
|
||||
def load(self, name):
|
||||
try:
|
||||
return super().load(name)
|
||||
except (OSError, ImportError):
|
||||
pass
|
||||
|
||||
compiled_path = self.pkg_path + self.compiled_path(name)
|
||||
|
||||
f_in = self.input_open(name)
|
||||
f_out = open(compiled_path, "w")
|
||||
c = Compiler(f_in, f_out, loader=self)
|
||||
c.compile()
|
||||
f_in.close()
|
||||
f_out.close()
|
||||
return super().load(name)
|
8
src/boot.py
Normal file
8
src/boot.py
Normal file
@@ -0,0 +1,8 @@
|
||||
import settings
|
||||
import wifi
|
||||
from settings import Settings
|
||||
|
||||
s = Settings()
|
||||
|
||||
name = s.get('name', 'led')
|
||||
wifi.ap(name, '')
|
23
src/main.py
Normal file
23
src/main.py
Normal file
@@ -0,0 +1,23 @@
|
||||
import asyncio
|
||||
from settings import Settings
|
||||
from web import web
|
||||
import gc
|
||||
import machine
|
||||
|
||||
async def main():
|
||||
settings = Settings()
|
||||
print("Starting")
|
||||
w = web(settings)
|
||||
server = asyncio.create_task(w.start_server(host="0.0.0.0", port=80))
|
||||
|
||||
wdt = machine.WDT(timeout=10000)
|
||||
wdt.feed()
|
||||
|
||||
while True:
|
||||
gc.collect()
|
||||
for i in range(60):
|
||||
wdt.feed()
|
||||
await asyncio.sleep_ms(500)
|
||||
# cleanup before ending the application
|
||||
await server
|
||||
asyncio.run(main())
|
291
src/patterns.py
Normal file
291
src/patterns.py
Normal file
@@ -0,0 +1,291 @@
|
||||
from machine import Pin
|
||||
from neopixel import NeoPixel
|
||||
import utime
|
||||
import random
|
||||
|
||||
class Patterns:
|
||||
def __init__(self, pin, num_leds, color1=(0,0,0), color2=(0,0,0), brightness=127, selected="rainbow_cycle", delay=100):
|
||||
self.n = NeoPixel(Pin(pin, Pin.OUT), num_leds)
|
||||
self.num_leds = num_leds
|
||||
self.pattern_step = 0
|
||||
self.last_update = utime.ticks_ms()
|
||||
self.delay = delay
|
||||
self.brightness = brightness
|
||||
self.patterns = {
|
||||
"off": self.off,
|
||||
"on" : self.on,
|
||||
"color_wipe": self.color_wipe_step,
|
||||
"rainbow_cycle": self.rainbow_cycle_step,
|
||||
"theater_chase": self.theater_chase_step,
|
||||
"blink": self.blink_step,
|
||||
"random_color_wipe": self.random_color_wipe_step,
|
||||
"random_rainbow_cycle": self.random_rainbow_cycle_step,
|
||||
"random_theater_chase": self.random_theater_chase_step,
|
||||
"random_blink": self.random_blink_step,
|
||||
"color_transition": self.color_transition_step,
|
||||
"external": None
|
||||
}
|
||||
self.selected = selected
|
||||
self.color1 = color1
|
||||
self.color2 = color2
|
||||
self.transition_duration = 50 # Duration of color transition in milliseconds
|
||||
self.transition_step = 0
|
||||
|
||||
def sync(self):
|
||||
self.pattern_step=0
|
||||
self.last_update = utime.ticks_ms()
|
||||
|
||||
def tick(self):
|
||||
if self.patterns[self.selected]:
|
||||
self.patterns[self.selected]()
|
||||
|
||||
def update_num_leds(self, pin, num_leds):
|
||||
self.n = NeoPixel(Pin(pin, Pin.OUT), num_leds)
|
||||
self.num_leds = num_leds
|
||||
self.pattern_step = 0
|
||||
|
||||
def set_delay(self, delay):
|
||||
self.delay = delay
|
||||
|
||||
def set_brightness(self, brightness):
|
||||
self.brightness = brightness
|
||||
|
||||
def set_color1(self, color):
|
||||
print(color)
|
||||
self.color1 = self.apply_brightness(color)
|
||||
|
||||
def set_color2(self, color):
|
||||
self.color2 = self.apply_brightness(color)
|
||||
|
||||
def apply_brightness(self, color):
|
||||
return tuple(int(c * self.brightness / 255) for c in color)
|
||||
|
||||
def select(self, pattern):
|
||||
if pattern in self.patterns:
|
||||
self.selected = pattern
|
||||
return True
|
||||
return False
|
||||
|
||||
def set(self, i, color):
|
||||
self.n[i] = color
|
||||
|
||||
def write(self):
|
||||
self.n.write()
|
||||
|
||||
def fill(self):
|
||||
for i in range(self.num_leds):
|
||||
self.n[i] = self.color1
|
||||
self.n.write()
|
||||
|
||||
def off(self):
|
||||
color = self.color1
|
||||
self.color1 = (0,0,0)
|
||||
self.fill()
|
||||
self.color1 = color
|
||||
|
||||
def on(self):
|
||||
color = self.color1
|
||||
self.color1 = self.apply_brightness(self.color1)
|
||||
self.fill()
|
||||
self.color1 = color
|
||||
|
||||
|
||||
def color_wipe_step(self):
|
||||
color = self.apply_brightness(self.color1)
|
||||
current_time = utime.ticks_ms()
|
||||
if utime.ticks_diff(current_time, self.last_update) >= self.delay:
|
||||
if self.pattern_step < self.num_leds:
|
||||
for i in range(self.num_leds):
|
||||
self.n[i] = (0, 0, 0)
|
||||
self.n[self.pattern_step] = self.apply_brightness(color)
|
||||
self.n.write()
|
||||
self.pattern_step += 1
|
||||
else:
|
||||
self.pattern_step = 0
|
||||
self.last_update = current_time
|
||||
|
||||
def rainbow_cycle_step(self):
|
||||
current_time = utime.ticks_ms()
|
||||
if utime.ticks_diff(current_time, self.last_update) >= self.delay/5:
|
||||
def wheel(pos):
|
||||
if pos < 85:
|
||||
return (pos * 3, 255 - pos * 3, 0)
|
||||
elif pos < 170:
|
||||
pos -= 85
|
||||
return (255 - pos * 3, 0, pos * 3)
|
||||
else:
|
||||
pos -= 170
|
||||
return (0, pos * 3, 255 - pos * 3)
|
||||
|
||||
for i in range(self.num_leds):
|
||||
rc_index = (i * 256 // self.num_leds) + self.pattern_step
|
||||
self.n[i] = self.apply_brightness(wheel(rc_index & 255))
|
||||
self.n.write()
|
||||
self.pattern_step = (self.pattern_step + 1) % 256
|
||||
self.last_update = current_time
|
||||
|
||||
def theater_chase_step(self):
|
||||
current_time = utime.ticks_ms()
|
||||
if utime.ticks_diff(current_time, self.last_update) >= self.delay:
|
||||
for i in range(self.num_leds):
|
||||
if (i + self.pattern_step) % 3 == 0:
|
||||
self.n[i] = self.apply_brightness(self.color1)
|
||||
else:
|
||||
self.n[i] = (0, 0, 0)
|
||||
self.n.write()
|
||||
self.pattern_step = (self.pattern_step + 1) % 3
|
||||
self.last_update = current_time
|
||||
|
||||
def blink_step(self):
|
||||
current_time = utime.ticks_ms()
|
||||
if utime.ticks_diff(current_time, self.last_update) >= self.delay:
|
||||
if self.pattern_step % 2 == 0:
|
||||
for i in range(self.num_leds):
|
||||
self.n[i] = self.apply_brightness(self.color1)
|
||||
else:
|
||||
for i in range(self.num_leds):
|
||||
self.n[i] = (0, 0, 0)
|
||||
self.n.write()
|
||||
self.pattern_step = (self.pattern_step + 1) % 2
|
||||
self.last_update = current_time
|
||||
|
||||
def random_color_wipe_step(self):
|
||||
current_time = utime.ticks_ms()
|
||||
if utime.ticks_diff(current_time, self.last_update) >= self.delay:
|
||||
color = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))
|
||||
if self.pattern_step < self.num_leds:
|
||||
for i in range(self.num_leds):
|
||||
self.n[i] = (0, 0, 0)
|
||||
self.n[self.pattern_step] = self.apply_brightness(color)
|
||||
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.pattern_step = (self.pattern_step + 1) % 2
|
||||
self.last_update = current_time
|
||||
|
||||
def color_transition_step(self):
|
||||
current_time = utime.ticks_ms()
|
||||
if utime.ticks_diff(current_time, self.last_update) >= self.delay:
|
||||
# Calculate transition factor based on elapsed time
|
||||
transition_factor = (self.pattern_step * 100) / self.transition_duration
|
||||
if transition_factor > 100:
|
||||
transition_factor = 100
|
||||
color = self.interpolate_color(self.color1, self.color2, transition_factor / 100)
|
||||
|
||||
# Apply the interpolated color to all LEDs
|
||||
for i in range(self.num_leds):
|
||||
self.n[i] = self.apply_brightness(color)
|
||||
self.n.write()
|
||||
|
||||
self.pattern_step += self.delay
|
||||
if self.pattern_step > self.transition_duration:
|
||||
self.pattern_step = 0
|
||||
|
||||
self.last_update = current_time
|
||||
|
||||
def interpolate_color(self, color1, color2, factor):
|
||||
return (
|
||||
int(color1[0] + (color2[0] - color1[0]) * factor),
|
||||
int(color1[1] + (color2[1] - color1[1]) * factor),
|
||||
int(color1[2] + (color2[2] - color1[2]) * factor)
|
||||
)
|
||||
|
||||
def two_steps_forward_one_step_back_step(self):
|
||||
current_time = utime.ticks_ms()
|
||||
if utime.ticks_diff(current_time, self.last_update) >= self.delay:
|
||||
# Move forward 2 steps and backward 1 step
|
||||
if self.direction == 1: # Moving forward
|
||||
if self.scanner_position < self.num_leds - 2:
|
||||
self.scanner_position += 2 # Move forward 2 steps
|
||||
else:
|
||||
self.direction = -1 # Change direction to backward
|
||||
else: # Moving backward
|
||||
if self.scanner_position > 0:
|
||||
self.scanner_position -= 1 # Move backward 1 step
|
||||
else:
|
||||
self.direction = 1 # Change direction to forward
|
||||
|
||||
# Set all LEDs to off
|
||||
for i in range(self.num_leds):
|
||||
self.n[i] = (0, 0, 0)
|
||||
|
||||
# Set the current position to the color
|
||||
self.n[self.scanner_position] = self.apply_brightness(self.color1)
|
||||
|
||||
# Apply the color transition
|
||||
transition_factor = (self.pattern_step * 100) / self.transition_duration
|
||||
if transition_factor > 100:
|
||||
transition_factor = 100
|
||||
color = self.interpolate_color(self.color1, self.color2, transition_factor / 100)
|
||||
self.n[self.scanner_position] = self.apply_brightness(color)
|
||||
|
||||
self.n.write()
|
||||
self.pattern_step += self.delay
|
||||
if self.pattern_step > self.transition_duration:
|
||||
self.pattern_step = 0
|
||||
|
||||
self.last_update = current_time
|
||||
|
||||
if __name__ == "__main__":
|
||||
p = Patterns(4, 180)
|
||||
p.set_color1((255,0,0))
|
||||
p.set_color2((0,255,0))
|
||||
#p.set_delay(10)
|
||||
try:
|
||||
while True:
|
||||
for key in p.patterns:
|
||||
print(key)
|
||||
p.select(key)
|
||||
for _ in range(2000):
|
||||
p.tick()
|
||||
utime.sleep_ms(1)
|
||||
except KeyboardInterrupt:
|
||||
p.fill((0, 0, 0))
|
34
src/settings.py
Normal file
34
src/settings.py
Normal file
@@ -0,0 +1,34 @@
|
||||
import json
|
||||
import wifi
|
||||
import ubinascii
|
||||
import machine
|
||||
|
||||
class Settings(dict):
|
||||
SETTINGS_FILE = "/settings.json"
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.load() # Load settings from file during initialization
|
||||
|
||||
def set_defaults(self):
|
||||
self = {}
|
||||
|
||||
def save(self):
|
||||
try:
|
||||
j = json.dumps(self)
|
||||
with open(self.SETTINGS_FILE, 'w') as file:
|
||||
file.write(j)
|
||||
print("Settings saved successfully.")
|
||||
except Exception as e:
|
||||
print(f"Error saving settings: {e}")
|
||||
|
||||
def load(self):
|
||||
try:
|
||||
with open(self.SETTINGS_FILE, 'r') as file:
|
||||
loaded_settings = json.load(file)
|
||||
self.update(loaded_settings)
|
||||
print("Settings loaded successfully.")
|
||||
except Exception as e:
|
||||
print(f"Error loading settings")
|
||||
self.set_defaults()
|
||||
self.save()
|
143
src/static/light-component.js
Normal file
143
src/static/light-component.js
Normal file
@@ -0,0 +1,143 @@
|
||||
import { getWebSocket } from "./websocket.js";
|
||||
|
||||
export class LightComponent extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
// Create a shadow DOM for encapsulation
|
||||
const shadow = this.attachShadow({ mode: "open" });
|
||||
|
||||
// Create the content for the component
|
||||
const style = document.createElement("style");
|
||||
style.textContent = `
|
||||
:host {
|
||||
display: block;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
background-color: #4caf50;
|
||||
color: white;
|
||||
text-align: center;
|
||||
line-height: 100px;
|
||||
cursor: grab;
|
||||
position: absolute;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
:host:active {
|
||||
cursor: grabbing;
|
||||
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.color-picker {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
cursor: pointer;
|
||||
}
|
||||
`;
|
||||
|
||||
// Create the main content (draggable area)
|
||||
const content = document.createElement("div");
|
||||
content.textContent = this.textContent || "Light Me Up!";
|
||||
content.style.position = "absolute";
|
||||
content.style.top = "0";
|
||||
content.style.left = "0";
|
||||
content.style.width = "100%";
|
||||
content.style.height = "100%";
|
||||
content.style.display = "flex";
|
||||
content.style.justifyContent = "center";
|
||||
content.style.alignItems = "center";
|
||||
|
||||
// Create the color picker
|
||||
const colorPicker = document.createElement("input");
|
||||
colorPicker.type = "color";
|
||||
colorPicker.classList.add("color-picker");
|
||||
colorPicker.value = "#4caf50"; // Default color
|
||||
colorPicker.addEventListener("input", () => {
|
||||
this.style.backgroundColor = colorPicker.value;
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("color-change", {
|
||||
detail: { lightId: this.lightId, color: colorPicker.value },
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
// Append the style, content, and color picker to the shadow DOM
|
||||
shadow.appendChild(style);
|
||||
shadow.appendChild(content);
|
||||
shadow.appendChild(colorPicker);
|
||||
|
||||
// Add event listeners for drag-and-drop
|
||||
content.addEventListener("mousedown", this.handleMouseDown.bind(this));
|
||||
document.addEventListener("mousemove", this.handleMouseMove.bind(this));
|
||||
document.addEventListener("mouseup", this.handleMouseUp.bind(this));
|
||||
}
|
||||
|
||||
// Track the initial mouse position and component position
|
||||
handleMouseDown(event) {
|
||||
event.preventDefault();
|
||||
|
||||
// Get the initial mouse position relative to the component
|
||||
this.initialMouseX = event.clientX;
|
||||
this.initialMouseY = event.clientY;
|
||||
|
||||
// Get the initial position of the component
|
||||
const rect = this.getBoundingClientRect();
|
||||
this.initialComponentX = rect.left;
|
||||
this.initialComponentY = rect.top;
|
||||
|
||||
// Add a class to indicate dragging
|
||||
this.classList.add("dragging");
|
||||
}
|
||||
|
||||
// Update the component's position as the mouse moves
|
||||
handleMouseMove(event) {
|
||||
if (!this.classList.contains("dragging")) return;
|
||||
|
||||
// Calculate the new position of the component
|
||||
const newX = this.initialComponentX + (event.clientX - this.initialMouseX);
|
||||
const newY = this.initialComponentY + (event.clientY - this.initialMouseY);
|
||||
|
||||
// Update the component's position
|
||||
this.style.left = `${newX}px`;
|
||||
this.style.top = `${newY}px`;
|
||||
}
|
||||
|
||||
// Stop dragging when the mouse is released
|
||||
handleMouseUp() {
|
||||
// Check if the component is being dragged
|
||||
if (!this.classList.contains("dragging")) {
|
||||
return; // Do nothing if not dragging
|
||||
}
|
||||
|
||||
// Remove the dragging class
|
||||
this.classList.remove("dragging");
|
||||
|
||||
// Get the current position of the component
|
||||
const rect = this.getBoundingClientRect();
|
||||
const newX = rect.left;
|
||||
const newY = rect.top;
|
||||
|
||||
// Dispatch an event to notify the parent about the updated position
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("position-change", {
|
||||
detail: { lightId: this.lightId, x: newX, y: newY },
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
// Add a property to hold the lightId
|
||||
set lightId(id) {
|
||||
this._lightId = id;
|
||||
}
|
||||
|
||||
get lightId() {
|
||||
return this._lightId;
|
||||
}
|
||||
}
|
||||
|
||||
// Define the custom element
|
||||
customElements.define("light-component", LightComponent);
|
90
src/static/light-components.js
Normal file
90
src/static/light-components.js
Normal file
@@ -0,0 +1,90 @@
|
||||
// light-components.js
|
||||
import { LightComponent } from "./light-component.js";
|
||||
import { getWebSocket } from "./websocket.js";
|
||||
|
||||
// Map to store backend IDs and their corresponding components
|
||||
const componentMap = new Map();
|
||||
|
||||
// Function to create and configure a light component
|
||||
function createLightComponent(data, key, appContainer) {
|
||||
const lightComponent = document.createElement("light-component");
|
||||
lightComponent.style.left = `${data.x}px`; // Set the x position
|
||||
lightComponent.style.top = `${data.y}px`; // Set the y position
|
||||
lightComponent.style.backgroundColor = data.settings?.color || "#4caf50"; // Set the background color
|
||||
lightComponent.textContent = data.name || "Light Me Up!"; // Set the text content
|
||||
|
||||
// Set the lightId property
|
||||
lightComponent.lightId = key; // Use the backend ID as the lightId
|
||||
|
||||
// Store the component in the map
|
||||
componentMap.set(key, lightComponent);
|
||||
|
||||
// Append the light component to the container
|
||||
appContainer.appendChild(lightComponent);
|
||||
|
||||
// Handle position change
|
||||
lightComponent.addEventListener("position-change", (event) => {
|
||||
const { lightId, x, y } = event.detail;
|
||||
updatePositionOnServer(lightId, x, y);
|
||||
});
|
||||
|
||||
// Handle color change
|
||||
lightComponent.addEventListener("color-change", (event) => {
|
||||
const { lightId, color } = event.detail;
|
||||
sendColorToServer(lightId, color);
|
||||
});
|
||||
|
||||
// Example: Add a click event listener to the light-component
|
||||
lightComponent.addEventListener("click", () => {
|
||||
console.log(`Light component clicked! ID: ${lightComponent.lightId}`);
|
||||
});
|
||||
}
|
||||
|
||||
// Function to create light components from the fetched data
|
||||
export function createLightComponents(appContainer, lightData) {
|
||||
for (const key in lightData) {
|
||||
if (lightData.hasOwnProperty(key)) {
|
||||
const light = lightData[key];
|
||||
createLightComponent(light, key, appContainer); // Pass the backend ID
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Function to send the updated position to the server via a PATCH request
|
||||
async function updatePositionOnServer(componentId, x, y) {
|
||||
try {
|
||||
const response = await fetch(`/light/${componentId}`, {
|
||||
method: "PATCH",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({ x, y }),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! Status: ${response.status}`);
|
||||
}
|
||||
|
||||
console.log(
|
||||
`Updated position for component ${componentId}: x=${x}, y=${y}`,
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("Error updating position on server:", error);
|
||||
}
|
||||
}
|
||||
|
||||
// Function to send the selected color to the server via WebSocket
|
||||
function sendColorToServer(componentId, color) {
|
||||
const websocket = getWebSocket();
|
||||
const message = JSON.stringify({
|
||||
componentId,
|
||||
color,
|
||||
});
|
||||
|
||||
if (websocket.readyState === WebSocket.OPEN) {
|
||||
websocket.send(message);
|
||||
console.log("Sent color to server:", message);
|
||||
} else {
|
||||
console.warn("WebSocket is not open. Unable to send color.");
|
||||
}
|
||||
}
|
32
src/static/main.js
Normal file
32
src/static/main.js
Normal file
@@ -0,0 +1,32 @@
|
||||
// main.js
|
||||
import { createLightComponents } from "./light-components.js";
|
||||
import { getWebSocket } from "./websocket.js";
|
||||
|
||||
// Wait for the DOM to be fully loaded
|
||||
document.addEventListener("DOMContentLoaded", async () => {
|
||||
// Select the container where the light-components will be added
|
||||
const appContainer = document.getElementById("app");
|
||||
|
||||
// Fetch the JSON data from the /light endpoint
|
||||
try {
|
||||
const response = await fetch("/light");
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! Status: ${response.status}`);
|
||||
}
|
||||
const lightData = await response.json();
|
||||
|
||||
// Create and configure light components
|
||||
createLightComponents(appContainer, lightData);
|
||||
|
||||
// Initialize WebSocket connection
|
||||
const websocket = getWebSocket();
|
||||
websocket.addEventListener("open", () => {
|
||||
console.log("WebSocket connection established.");
|
||||
});
|
||||
websocket.addEventListener("message", (event) => {
|
||||
console.log("Message from server:", event.data);
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error fetching light data:", error);
|
||||
}
|
||||
});
|
195
src/static/rgb-slider.js
Normal file
195
src/static/rgb-slider.js
Normal file
@@ -0,0 +1,195 @@
|
||||
// rgb-slider.js
|
||||
|
||||
export class RGBSlider extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
const shadow = this.attachShadow({ mode: "open" });
|
||||
|
||||
shadow.innerHTML = `
|
||||
<style>
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 1em;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 8px;
|
||||
width: 50%;
|
||||
|
||||
font-family: sans-serif;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.preview {
|
||||
width: 50%;
|
||||
height: 60px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid #000;
|
||||
background-color: rgb(0, 0, 0);
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.sliders {
|
||||
display: flex;
|
||||
gap: 50px;
|
||||
justify-content: center;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.slider-group {
|
||||
display: flex;
|
||||
flex-direction: column-reverse;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.slider-group input[type="range"] {
|
||||
writing-mode: vertical-lr;
|
||||
direction: rtl;
|
||||
|
||||
width: 10px;
|
||||
height: 120px;
|
||||
}
|
||||
|
||||
.slider-group label {
|
||||
margin-top: 8px;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
.rgb-inputs {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.rgb-inputs input {
|
||||
width: 6ch;
|
||||
padding: 2px;
|
||||
font-family: monospace;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.rgb-inputs label {
|
||||
font-size: 0.8em;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.rgb-input-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* Mobile styles */
|
||||
@media (max-width: 600px) {
|
||||
.preview {
|
||||
height: 80px;
|
||||
}
|
||||
|
||||
.slider-group input[type="range"] {
|
||||
height: 180px;
|
||||
width: 14px;
|
||||
}
|
||||
|
||||
.rgb-inputs input {
|
||||
font-size: 1em;
|
||||
padding: 4px;
|
||||
width: 7ch;
|
||||
}
|
||||
|
||||
.slider-group label,
|
||||
.rgb-inputs label {
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
.container {
|
||||
padding: 1.5em;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
<div class="container">
|
||||
<div class="preview" id="preview"></div>
|
||||
|
||||
<div class="sliders">
|
||||
<div class="slider-group">
|
||||
<input type="range" min="0" max="255" value="0" id="r">
|
||||
<label>R</label>
|
||||
</div>
|
||||
<div class="slider-group">
|
||||
<input type="range" min="0" max="255" value="0" id="g">
|
||||
<label>G</label>
|
||||
</div>
|
||||
<div class="slider-group">
|
||||
<input type="range" min="0" max="255" value="0" id="b">
|
||||
<label>B</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rgb-inputs">
|
||||
<div class="rgb-input-group">
|
||||
<label for="rInput">R</label>
|
||||
<input type="number" min="0" max="255" id="rInput" value="0">
|
||||
</div>
|
||||
<div class="rgb-input-group">
|
||||
<label for="gInput">G</label>
|
||||
<input type="number" min="0" max="255" id="gInput" value="0">
|
||||
</div>
|
||||
<div class="rgb-input-group">
|
||||
<label for="bInput">B</label>
|
||||
<input type="number" min="0" max="255" id="bInput" value="0">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
const get = (id) => shadow.querySelector(id);
|
||||
this.r = get("#r");
|
||||
this.g = get("#g");
|
||||
this.b = get("#b");
|
||||
this.rInput = get("#rInput");
|
||||
this.gInput = get("#gInput");
|
||||
this.bInput = get("#bInput");
|
||||
this.preview = get("#preview");
|
||||
|
||||
const updateColor = (r, g, b) => {
|
||||
this.preview.style.backgroundColor = `rgb(${r}, ${g}, ${b})`;
|
||||
this.rInput.value = r;
|
||||
this.gInput.value = g;
|
||||
this.bInput.value = b;
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("color-change", {
|
||||
detail: { r, g, b },
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const syncFromSliders = () => {
|
||||
const r = +this.r.value;
|
||||
const g = +this.g.value;
|
||||
const b = +this.b.value;
|
||||
updateColor(r, g, b);
|
||||
};
|
||||
|
||||
const syncFromInputs = () => {
|
||||
const r = Math.min(255, Math.max(0, +this.rInput.value));
|
||||
const g = Math.min(255, Math.max(0, +this.gInput.value));
|
||||
const b = Math.min(255, Math.max(0, +this.bInput.value));
|
||||
this.r.value = r;
|
||||
this.g.value = g;
|
||||
this.b.value = b;
|
||||
updateColor(r, g, b);
|
||||
};
|
||||
|
||||
this.r.addEventListener("input", syncFromSliders);
|
||||
this.g.addEventListener("input", syncFromSliders);
|
||||
this.b.addEventListener("input", syncFromSliders);
|
||||
|
||||
this.rInput.addEventListener("change", syncFromInputs);
|
||||
this.gInput.addEventListener("change", syncFromInputs);
|
||||
this.bInput.addEventListener("change", syncFromInputs);
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("rgb-slider", RGBSlider);
|
20
src/static/styles.css
Normal file
20
src/static/styles.css
Normal file
@@ -0,0 +1,20 @@
|
||||
/* Default styles for the light component */
|
||||
light-component {
|
||||
display: block;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
background-color: #4caf50;
|
||||
color: white;
|
||||
text-align: center;
|
||||
line-height: 100px;
|
||||
cursor: grab;
|
||||
position: absolute;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
/* Styles when the component is being dragged */
|
||||
light-component:active {
|
||||
cursor: grabbing;
|
||||
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.3);
|
||||
}
|
26
src/static/websocket.js
Normal file
26
src/static/websocket.js
Normal file
@@ -0,0 +1,26 @@
|
||||
// websocket.js
|
||||
let websocket = null;
|
||||
|
||||
export function getWebSocket() {
|
||||
if (!websocket) {
|
||||
// Replace 'ws://your-server-url' with your WebSocket server URL
|
||||
websocket = new WebSocket(`ws://${window.location.host}/ws`);
|
||||
|
||||
// Handle WebSocket connection open
|
||||
websocket.onopen = () => {
|
||||
console.log("WebSocket connection established");
|
||||
};
|
||||
|
||||
// Handle WebSocket connection close
|
||||
websocket.onclose = () => {
|
||||
console.log("WebSocket connection closed");
|
||||
};
|
||||
|
||||
// Handle WebSocket errors
|
||||
websocket.onerror = (error) => {
|
||||
console.error("WebSocket error:", error);
|
||||
};
|
||||
}
|
||||
|
||||
return websocket;
|
||||
}
|
16
src/templates/index.html
Normal file
16
src/templates/index.html
Normal file
@@ -0,0 +1,16 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Light Component</title>
|
||||
<!-- Link to the external CSS file -->
|
||||
<link rel="stylesheet" href="static/styles.css" />
|
||||
</head>
|
||||
<body>
|
||||
<!-- The light-component will be added dynamically by main.js -->
|
||||
<div id="app"></div>
|
||||
<!-- Import the JavaScript files -->
|
||||
<script type="module" src="static/main.js"></script>
|
||||
</body>
|
||||
</html>
|
113
src/web.py
Normal file
113
src/web.py
Normal file
@@ -0,0 +1,113 @@
|
||||
from microdot import Microdot, send_file, Response
|
||||
from microdot.utemplate import Template
|
||||
from microdot.websocket import with_websocket
|
||||
import json
|
||||
|
||||
def web(settings):
|
||||
app = Microdot()
|
||||
Response.default_content_type = 'text/html'
|
||||
|
||||
@app.route('/')
|
||||
async def index_handler(request):
|
||||
return Template('/index.html').render(settings=settings)
|
||||
|
||||
@app.route("/static/<path:path>")
|
||||
def static_handler(request, path):
|
||||
if '..' in path:
|
||||
# Directory traversal is not allowed
|
||||
return 'Not found', 404
|
||||
return send_file('static/' + path)
|
||||
|
||||
@app.route("/ws")
|
||||
@with_websocket
|
||||
async def ws(request, ws):
|
||||
# Register the client's WebSocket connection
|
||||
print("WebSocket connection established")
|
||||
|
||||
while True:
|
||||
data = await ws.receive()
|
||||
if data:
|
||||
try:
|
||||
# Parse the JSON message from the client
|
||||
message = json.loads(data)
|
||||
light = message.get("light")
|
||||
if message["light"] in settings.get("lights"):
|
||||
settings["lights"][light].update(message["settings"])
|
||||
if message["save"]:
|
||||
settings.save()
|
||||
except json.JSONDecodeError:
|
||||
print("Invalid JSON received")
|
||||
|
||||
else:
|
||||
break
|
||||
|
||||
print("WebSocket connection closed")
|
||||
|
||||
@app.get("/light")
|
||||
async def get_lights(request):
|
||||
return json.dumps(settings)
|
||||
|
||||
@app.get("/light/<light>")
|
||||
async def get_light(request, light):
|
||||
light_data = settings.get(light, None)
|
||||
if light_data:
|
||||
return json.dumps(light_data), 200
|
||||
else:
|
||||
return json.dumps({"error": "Light not found"}), 404
|
||||
|
||||
@app.post("/light/<light>")
|
||||
async def add_light(request, light):
|
||||
try:
|
||||
# Parse the JSON request body
|
||||
data = request.json
|
||||
# Check if the light already exists
|
||||
if light in settings:
|
||||
return json.dumps({"error": "Light already exists"}), 409
|
||||
|
||||
# Add the new light to the settings
|
||||
settings[light] = data
|
||||
print(settings)
|
||||
settings.save()
|
||||
return json.dumps(
|
||||
{"message": "Light added successfully",
|
||||
"light": light}
|
||||
), 200
|
||||
except Exception as e:
|
||||
print(f"Exception: {e}")
|
||||
return json.dumps({"error": "Invalid JSON request"}), 400
|
||||
|
||||
@app.patch("/light/<light>")
|
||||
async def update_light(request, light):
|
||||
try:
|
||||
# Parse the JSON request body
|
||||
data = request.json
|
||||
print(light, data)
|
||||
# Check if the light exists
|
||||
if light not in settings:
|
||||
return json.dumps({"error": "Light not found"}), 404
|
||||
|
||||
# Update the existing light with the provided data
|
||||
settings[light].update(data)
|
||||
settings.save() # Uncomment if using persistent storage
|
||||
return json.dumps(
|
||||
{"message": "Light updated successfully", "light": settings[light]}
|
||||
), 200
|
||||
except json.JSONDecodeError:
|
||||
return json.dumps({"error": "Invalid JSON request"}), 400
|
||||
|
||||
@app.delete("/light/<light>")
|
||||
async def del_light(request, light):
|
||||
if light in settings:
|
||||
# Remove the light from the settings
|
||||
del settings[light]
|
||||
settings.save()
|
||||
return json.dumps({"message": "Light deleted successfully"})
|
||||
else:
|
||||
return json.dumps({"error": "Light not found"}), 404
|
||||
|
||||
return app
|
||||
|
||||
# Example usage
|
||||
if __name__ == "__main__":
|
||||
app = web(settings)
|
||||
app.run()
|
38
src/wifi.py
Normal file
38
src/wifi.py
Normal file
@@ -0,0 +1,38 @@
|
||||
import network
|
||||
from time import sleep
|
||||
|
||||
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():
|
||||
print('connecting to network...')
|
||||
sta_if.active(True)
|
||||
sta_if.connect(ssid, password)
|
||||
sleep(0.1)
|
||||
if sta_if.isconnected():
|
||||
return sta_if.ifconfig()
|
||||
return None
|
||||
return sta_if.ifconfig()
|
||||
except Exception as e:
|
||||
print(f"Failed to connect to wifi {e}")
|
||||
return None
|
||||
|
||||
|
||||
def ap(ssid, password):
|
||||
ap_if = network.WLAN(network.AP_IF)
|
||||
ap_mac = ap_if.config('mac')
|
||||
print(ssid)
|
||||
ap_if.active(True)
|
||||
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