Compare commits

..

4 Commits

Author SHA1 Message Date
6b5ae78591 Revert to basic led driver 2026-02-19 14:23:54 +13:00
bfa85c5688 Add led test 2026-02-19 14:23:12 +13:00
a3ab572851 Add RP2040 WS2812 PIO+DMA driver
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-19 11:20:28 +13:00
a3510ab164 Fix for RP2350 2026-02-14 23:38:20 +13:00
17 changed files with 685 additions and 1656 deletions

13
Pipfile
View File

@@ -1,13 +0,0 @@
[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"
[packages]
esptool = "*"
mpremote = "*"
[dev-packages]
[requires]
python_version = "3.12"

470
Pipfile.lock generated
View File

@@ -1,470 +0,0 @@
{
"_meta": {
"hash": {
"sha256": "4cd5ca9be4f69340ea0ed740b1fd86d155de8e4b40efa538af17ae4e1b34334d"
},
"pipfile-spec": 6,
"requires": {
"python_version": "3.12"
},
"sources": [
{
"name": "pypi",
"url": "https://pypi.org/simple",
"verify_ssl": true
}
]
},
"default": {
"bitarray": {
"hashes": [
"sha256:002b73bf4a9f7b3ecb02260bd4dd332a6ee4d7f74ee9779a1ef342a36244d0cf",
"sha256:01e3ba46c2dee6d47a4ab22561a01d8ee6772f681defc9fcb357097a055e48cf",
"sha256:03dc877ec286b7f2813185ea6bc5f1f5527fd859e61038d38768883b134e06b3",
"sha256:03eeab48f376c3cd988add2b75c20d2d084b6fcc9a164adb0dc390ef152255b4",
"sha256:05ee46a734b5110c5ac483815da4379f7622f4316362872ec7c0ed16db4b0148",
"sha256:0751596f60f33df66245b2dafa3f7fbe13cb7ac91dd14ead87d8c2eec57cb3ed",
"sha256:08c114cf02a63e13ce6d70bc5b9e7bdcfa8d5db17cece207cfa085c4bc4a7a0c",
"sha256:0ed4a87eda16e2f95d536152c5acccae07841fbdda3b9a752f3dbf43e39f4d6b",
"sha256:101230b8074919970433ef79866570989157ade3421246d4c3afb7a994fdc614",
"sha256:11fcfdf272549a3d876f10d8422bcd5f675750aa746ce04ff04937ec3bb2329e",
"sha256:160f449bb91686f8fc9984200e78b8d793b79e382decf7eb1dc9948d7c21b36f",
"sha256:16426a843b1bc9c552a7c97d6d7555e69730c2de1e2f560503d3fc0e7f6d8005",
"sha256:1f1575cc0f66aa70a0bb5cb57c8d9d1b7d541d920455169c6266919bf804dc20",
"sha256:1f7a8fc5085450635a539c47c9fce6d441b4a973686f88fc220aa20e3921fe55",
"sha256:1fb0a46ae4b8d244a3fb80c3055717baa3dec6be17938e6871042a8d5b4ce670",
"sha256:2965fd8ba31b04c42e4b696fad509dc5ab50663efca6eb06bb3b6d08587f3a09",
"sha256:2b524306104c1296f1e91d74ee4ccbeeea621f6a13e44addf0bb630a1839fd72",
"sha256:2db04b165a57499fbcfe0eaa2f7752f118552bbcfab2163a43fef8d95f4ae745",
"sha256:3092f6bbf4a75b1e6f14a5b1030e27c435f341afeb23987115e45a25cc68ba91",
"sha256:30a2fc37698820cbf9b51d5f801219ef4bed828a04f3307072b8f983dc422a0e",
"sha256:3110b98c5dfb31dc1cf82d8b0c32e3fa6d6d0b268ff9f2a1599165770c1af80f",
"sha256:33f604bffd06b170637f8a48ddcf42074ed1e1980366ac46058e065ce04bfe2a",
"sha256:340c524c7c934b61d1985d805bffe7609180fb5d16ece6ce89b51aa535b936f2",
"sha256:37a6a8382864a1defb5b370b66a635e04358c7334054457bbbb8645610cd95b2",
"sha256:3875578748b484638f6ea776f534e9088cfb15eee131aac051036cba40fd5d05",
"sha256:38b0261483c59bb39ae9300ad46bf0bbf431ab604266382d986a349c96171b36",
"sha256:3b9a2eb7d2e0e9c2f25256d2663c0a2a4798fe3110e3ddbbb1a7b71740b4de08",
"sha256:3bb3cf22c3c03ae698647e6766314149c9cf04aa2018d9f48d5efddc3ced2764",
"sha256:3db0648536f3e08afa7ceb928153c39913f98fd50a5c3adf92a4d0d4268f213e",
"sha256:3dc654da62b3a3027b7c922f7e9f4b27feaabd5d38b2a98ea98de5e8107c72f2",
"sha256:4079857566077f290d35e23ff0e8ba593069c139ae85b0d152b9fa476494f50a",
"sha256:44f468fb4857fff86c65bec5e2fb67067789e40dad69258e9bb78fc6a6df49e7",
"sha256:45660e2fabcdc1bab9699a468b312f47956300d41d6a2ea91c8f067572aaf38a",
"sha256:477b9456eb7d70f385dc8f097a1d66ee40771b62e47b3b3e33406dcfbc1c6a3b",
"sha256:481239cd0966f965c2b8fa78b88614be5f12a64e7773bb5feecc567d39bb2dd5",
"sha256:4a83d247420b147d4b3cba0335e484365e117dc1cfe5ab35acd6a0817ad9244f",
"sha256:53d2abeabb91a822e9d76420c9b44980edd2d6b21767c7bb9cb2b1b4cf091049",
"sha256:55c31bc3d2c9e48741c812ee5ce4607c6f33e33f339831c214d923ffc7777d21",
"sha256:567d6891cb1ddbfd0051fcff3cb1bb86efc82ec818d9c5f98c37d59c1d23cc96",
"sha256:57b9df5d38ab49c13eaa9e0152fdfa8501fc23987f6dcf421b73484bfe573918",
"sha256:59ddb8a9f47ec807009c69e582d0de1c86c005f9f614557f4cebc7b8ac9b7d28",
"sha256:61b9f3cf3a55322baed8f0532b73bce77d688a01446c179392c4056ab74eb551",
"sha256:639389b023315596e0293f85999645f47ec3dc28c892e51242dde6176c91486b",
"sha256:64d1143e90299ba8c967324840912a63a903494b1870a52f6675bda53dc332f7",
"sha256:6542e1cfe060badd160cd383ad93a84871595c14bb05fb8129f963248affd946",
"sha256:69687ef16d501c9217675af36fa3c68c009c03e184b07d22ba245e5c01d47e6b",
"sha256:6f7e1cdf0abb11718e655bb258920453b1e89c2315e9019f60f0775704b12a8c",
"sha256:7378055c9f456c5bb034ac313d9a9028fc6597619a0b16584099adba5a589fdb",
"sha256:78103afbd0a94ac4c1f0b4014545fd149b968d5ea423aaa3b1f6e2c3fc19423e",
"sha256:79038bf1a7b13d243e51f4b6909c6997c2ba2bffc45bcae264704308a2d17198",
"sha256:795b1760418ab750826420ae24f06f392c08e21dc234f0a369a69cc00444f8ec",
"sha256:7998dfb1e9e0255fb8553abb019c3e7f558925de4edc8604243775ff9dd3898d",
"sha256:7afc740ad45ee0e0cef055765faf64789c2c183eb4aa3ecb8cecdb4b607396b3",
"sha256:7b4a41dc183d7d16750634f65566205990f94144755a39f33da44c0350c3e1a8",
"sha256:7f825ebedcad87a2825ddb6cf62f6d7d5b7a56ddaf7c93eef4b974e7ddc16408",
"sha256:7f9f9bb2c5cc1f679605ebbeb72f46fc395d850b93fa7de7addd502a1dc66e99",
"sha256:7fdf059d4e3acec44f512ebe247718ae511fde632e2b06992022df8e637385a6",
"sha256:81e4648c09103bc18f488957c1e0863d2397bab6625c0e6771891f151ee0bd96",
"sha256:8489bff00a1f81ac0754355772e76775878c32a42f16f01d427c3645546761c4",
"sha256:851398428f5604c53371b72c5e0a28163274264ada4a08cd1eafe65fde1f68d0",
"sha256:87a29b8a4cc72af6118954592dcd4e49223420470ccc3f8091c255f6c7330bb1",
"sha256:8b8e07374d60040b24d1a158895d9758424db13be63d4b2fe1870e37f9dec009",
"sha256:8d4aa56782368269eb9402caf7378b2a5ada6f05eb9c7edc2362be258973fd7e",
"sha256:97c448a20aded59727261468873d9b11dfdcce5a6338a359135667d5e3f1d070",
"sha256:98373c273e01a5a7c17103ecb617de7c9980b7608351d58c72198e3525f0002e",
"sha256:98e4a17f55f3cbf6fe06cc79234269572f234467c8355b6758eb252073f78e6b",
"sha256:99124e39658b2f72d296819ec03418609dd4f1b275b00289c2f278a19da6f9c0",
"sha256:9ad0df7886cb9d6d2ff75e87d323108a0e32bdca5c9918071681864129ce8ea8",
"sha256:9bfdfe2e2af434d3f4e47250f693657334e34a7ec557cd703b129a814422b4b8",
"sha256:9faa4c6fcb19a31240ad389426699a99df481b6576f7286471e24efbf1b44dfc",
"sha256:a048e41e1cb0c1a37021269d02698e30d2a7cc9a0205dd3390e0807745b76dae",
"sha256:a05982bb49c73463cb0f0f4bed2d8da82631708a2c2d1926107ba99651b419ec",
"sha256:a23b5f13f9b292004e94b0b13fead4dae79c7512db04dc817ff2c2478298e04a",
"sha256:a393b0f881eff94440f72846a6f0f95b983594a0a50af81c41ed18107420d6a7",
"sha256:a3b6bd81c77d9925809b714980cd30b1831a86bd090316d37cab124d92af1daf",
"sha256:a43f4631ecb87bedc510568fef67db53f2a20c4a5953a9d1e07457e7b1d14911",
"sha256:a569c993942ac26c6c590639ed6712c6c9c3f0c8d287a067bf2a60eb615f3c6b",
"sha256:a5b89349f05431270d1ccc7321aaab91c42ff33f463868779e502438b7f0e668",
"sha256:ac39319e6322c2c093a660c02cea6bb3b1ae53d049b573d4781df8896e443e04",
"sha256:acc56700963f63307ac096689d4547e8061028a66bb78b90e42c5da2898898fb",
"sha256:b723f9d10f7d8259f010b87fa66e924bb4d67927d9dcff4526a755e9ee84fef4",
"sha256:b99a0347bc6131046c19e056a113daa34d7df99f1f45510161bc78bc8461a470",
"sha256:bc0880011b86f81c5353ce4abaeb2472d942ba2320985166a2a3dd4f783563a9",
"sha256:be2f40045432e8aa33d9fd5cb43c91b0c61d77d3d8810f88e84e2e46411c27a7",
"sha256:bebb17125373c499beea009cc5bced757bde52bcb3fa1d6335650e6c2d8111d7",
"sha256:befac6644c6f304a1b6a7948a04095682849c426cebcc44cb2459aa92d3e1735",
"sha256:c1f4880bcb6fb7a8e2ab89128032b3dcf59e1e877ff4493b11c8bf7c3a5b3df2",
"sha256:c3e014f7295b9327fa6f0b3e55a3fd485abac98be145b9597e0cdbb05c44ad07",
"sha256:c427dfcce13a8c814556dfe7c110b8ef61b8fab5fca0d856d4890856807321dc",
"sha256:c44cf0059633470c6bb415091def546adbeb5dcfa91cc3fcb1ac16593f14e52a",
"sha256:c4e04c12f507942f1ddf215cb3a08c244d24051cdd2ba571060166ce8a92be16",
"sha256:c65257899bb8faf6a111297b4ff0066324a6b901318582c0453a01422c3bcd5a",
"sha256:c6c48cf5a92244ef3df4161c8625ee1890bb3d931db9a9f3b699e61a037cd58a",
"sha256:c9bf2bf29854f165a47917b8782b6cf3a7d602971bf454806208d0cbb96f797a",
"sha256:ca4b6298c89b92d6b0a67dfc5f98d68ae92b08101d227263ef2033b9c9a03a72",
"sha256:cc76ad7453816318d794248fba4032967eaffd992d76e5d1af10ef9d46589770",
"sha256:cd7f6bfa2a36fb91b7dec9ddf905716f2ed0c3675d2b63c69b7530c9d211e715",
"sha256:d12c45da97b2f31d0233e15f8d68731cfa86264c9f04b2669b9fdf46aaf68e1f",
"sha256:d160173efdad8a57c22e422a034196df3d84753672c497aee2f94bd5b128f8dd",
"sha256:d2b1ed363a4ef5622dccbf7822f01b51195062c4f382b28c9bd125d046d0324c",
"sha256:d30e7daaf228e3d69cdd8b02c0dd4199cec034c4b93c80109f56f4675a6db957",
"sha256:d3f38373d9b2629dedc559e647010541cc4ec4ad9bea560e2eb1017e6a00d9ef",
"sha256:d7e274ac1975e55ebfb8166cce27e13dc99120c1d6ce9e490d7a716b9be9abb5",
"sha256:d877759842ff9eb16d9c2b8b497953a7d994d4b231c171515f0bf3a2ae185c0c",
"sha256:da3dfd2776226e15d3288a3a24c7975f9ee160ba198f2efa66bc28c5ba76d792",
"sha256:db0441e80773d747a1ed9edfb9f75e7acb68ce8627583bbb6f770b7ec49f0064",
"sha256:dbbaa147cf28b3e87738c624d390a3a9e2a5dfef4316f4c38b4ecaf3155a3eab",
"sha256:ddc646cec4899a137c134b13818469e4178a251d77f9f4b23229267e3da78cfb",
"sha256:df7cc9584614f495f474a5ded365cf72decbcee4efcdc888d2943f8a794c789e",
"sha256:dfde50ae55e075dcd5801e2c3ea0e749c849ed2cbbee991af0f97f1bdbadb2a6",
"sha256:e15e70a3cf5bb519e2448524d689c02ff6bcd4750587a517e2bffee06065bf27",
"sha256:e3572889fcb87e5ca94add412d8b365dbb7b59773a4362e52caa556e5fd98643",
"sha256:e39f5e85e1e3d7d84ac2217cd095b3678306c979e991532df47012880e02215d",
"sha256:e501bd27c795105aaba02b5212ecd1bb552ca2ee2ede53e5a8cb74deee0e2052",
"sha256:e62892645f6a214eefb58a42c3ed2501af2e40a797844e0e09ec1e400ce75f3d",
"sha256:e75eb1734046291c554d9addecca9a8785bdf5d53a64f525569f8549da863dde",
"sha256:e84cff8e8fe71903a6cf873fb3c8731df8bd7c1dac878e7a0fe19d8e2ef39aa9",
"sha256:ea60cf85b4e5a78b5a41eed3a65abc3839a50d915c6e0f6966cbcf81b85991bd",
"sha256:ec3fd30622180cbe2326d48c14a4ab7f98a504b104bdca7dda88b134adad6e31",
"sha256:eccc6829035c8b7b391a0aa124fade54932bb937dd1079f2740b9f1bde829226",
"sha256:eda67136343db96752e58ef36ac37116f36cba40961e79fd0e9bd858f5a09b38",
"sha256:ef5a99a8d1a5c47b4cf85925d1420fc4ee584c98be8efc548651447b3047242f",
"sha256:f0795e2be2aa8afd013635f30ffe599cc00f1bbaca2d1d19b6187b4d1c58fb44",
"sha256:f31d8c2168bf2a52e4539232392352832c2296e07e0e14b6e06a44da574099ba",
"sha256:f41a4b57cbc128a699e9d716a56c90c7fc76554e680fe2962f49cc4d8688b051",
"sha256:f583a1fb180a123c00064fab1a3bfb9d43e574b6474be1be3f6469e0331e3e2e",
"sha256:f7c531722e8c3901f6bb303db464cac98ab44ed422c0fd0c762baa4a8d49ffa1",
"sha256:f8ab90410b2ba5b8276657c66941bcaae556a38be8dd81630a7647e8735f0a20",
"sha256:fa05460dc4f57358680b977b4a254d331b24c8beb501319b998625fd6a22654b",
"sha256:fbe1ef622748d2edb3dd4fef933b934e90e479f9831dfe31bda3fdc16bf5287f",
"sha256:fdb7af369df317527d697c5bb37ab944bb9a17ea1a5e82e47d5c7c638f3ccdd6",
"sha256:fe1f1f4010244cb07f6a079854a12e1627e4fb9ea99d672f2ceccaf6653ca514",
"sha256:fe2493d3f49e314e573022ead4d8c845c9748979b7eb95e815429fe947c4bde2",
"sha256:ffd112646486a31ea5a45aa1eca0e2cd90b6a12f67e848e50349e324c24cc2e7"
],
"version": "==3.7.1"
},
"bitstring": {
"hashes": [
"sha256:69d1587f0ac18dc7d93fc7e80d5f447161a33e57027e726dc18a0a8bacf1711a",
"sha256:a08bc09d3857216d4c0f412a1611056f1cc2b64fd254fb1e8a0afba7cfa1a95a"
],
"markers": "python_version >= '3.8'",
"version": "==4.3.1"
},
"cffi": {
"hashes": [
"sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb",
"sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b",
"sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f",
"sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9",
"sha256:0cf2d91ecc3fcc0625c2c530fe004f82c110405f101548512cce44322fa8ac44",
"sha256:0f6084a0ea23d05d20c3edcda20c3d006f9b6f3fefeac38f59262e10cef47ee2",
"sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c",
"sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75",
"sha256:1cd13c99ce269b3ed80b417dcd591415d3372bcac067009b6e0f59c7d4015e65",
"sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e",
"sha256:1f72fb8906754ac8a2cc3f9f5aaa298070652a0ffae577e0ea9bd480dc3c931a",
"sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e",
"sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25",
"sha256:2081580ebb843f759b9f617314a24ed5738c51d2aee65d31e02f6f7a2b97707a",
"sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe",
"sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b",
"sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91",
"sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592",
"sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187",
"sha256:2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c",
"sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1",
"sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94",
"sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba",
"sha256:3e837e369566884707ddaf85fc1744b47575005c0a229de3327f8f9a20f4efeb",
"sha256:3f4d46d8b35698056ec29bca21546e1551a205058ae1a181d871e278b0b28165",
"sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529",
"sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca",
"sha256:4647afc2f90d1ddd33441e5b0e85b16b12ddec4fca55f0d9671fef036ecca27c",
"sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6",
"sha256:53f77cbe57044e88bbd5ed26ac1d0514d2acf0591dd6bb02a3ae37f76811b80c",
"sha256:5eda85d6d1879e692d546a078b44251cdd08dd1cfb98dfb77b670c97cee49ea0",
"sha256:5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743",
"sha256:61d028e90346df14fedc3d1e5441df818d095f3b87d286825dfcbd6459b7ef63",
"sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5",
"sha256:6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5",
"sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4",
"sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d",
"sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b",
"sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93",
"sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205",
"sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27",
"sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512",
"sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d",
"sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c",
"sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037",
"sha256:8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26",
"sha256:89472c9762729b5ae1ad974b777416bfda4ac5642423fa93bd57a09204712322",
"sha256:8ea985900c5c95ce9db1745f7933eeef5d314f0565b27625d9a10ec9881e1bfb",
"sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c",
"sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8",
"sha256:9332088d75dc3241c702d852d4671613136d90fa6881da7d770a483fd05248b4",
"sha256:94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414",
"sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9",
"sha256:9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664",
"sha256:a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9",
"sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775",
"sha256:b18a3ed7d5b3bd8d9ef7a8cb226502c6bf8308df1525e1cc676c3680e7176739",
"sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc",
"sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062",
"sha256:b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe",
"sha256:b882b3df248017dba09d6b16defe9b5c407fe32fc7c65a9c69798e6175601be9",
"sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92",
"sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5",
"sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13",
"sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d",
"sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26",
"sha256:cb527a79772e5ef98fb1d700678fe031e353e765d1ca2d409c92263c6d43e09f",
"sha256:cf364028c016c03078a23b503f02058f1814320a56ad535686f90565636a9495",
"sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b",
"sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6",
"sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c",
"sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef",
"sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5",
"sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18",
"sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad",
"sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3",
"sha256:de8dad4425a6ca6e4e5e297b27b5c824ecc7581910bf9aee86cb6835e6812aa7",
"sha256:e11e82b744887154b182fd3e7e8512418446501191994dbf9c9fc1f32cc8efd5",
"sha256:e6e73b9e02893c764e7e8d5bb5ce277f1a009cd5243f8228f75f842bf937c534",
"sha256:f73b96c41e3b2adedc34a7356e64c8eb96e03a3782b535e043a986276ce12a49",
"sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2",
"sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5",
"sha256:fc7de24befaeae77ba923797c7c87834c73648a05a4bde34b3b7e5588973a453",
"sha256:fe562eb1a64e67dd297ccc4f5addea2501664954f2692b69a76449ec7913ecbf"
],
"markers": "platform_python_implementation != 'PyPy'",
"version": "==2.0.0"
},
"click": {
"hashes": [
"sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202",
"sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b"
],
"markers": "python_version >= '3.10'",
"version": "==8.2.1"
},
"cryptography": {
"hashes": [
"sha256:06ce84dc14df0bf6ea84666f958e6080cdb6fe1231be2a51f3fc1267d9f3fb34",
"sha256:16ede8a4f7929b4b7ff3642eba2bf79aa1d71f24ab6ee443935c0d269b6bc513",
"sha256:18fcf70f243fe07252dcb1b268a687f2358025ce32f9f88028ca5c364b123ef5",
"sha256:1993a1bb7e4eccfb922b6cd414f072e08ff5816702a0bdb8941c247a6b1b287c",
"sha256:1f3d56f73595376f4244646dd5c5870c14c196949807be39e79e7bd9bac3da63",
"sha256:258e0dff86d1d891169b5af222d362468a9570e2532923088658aa866eb11130",
"sha256:2f641b64acc00811da98df63df7d59fd4706c0df449da71cb7ac39a0732b40ae",
"sha256:3808e6b2e5f0b46d981c24d79648e5c25c35e59902ea4391a0dcb3e667bf7443",
"sha256:3994c809c17fc570c2af12c9b840d7cea85a9fd3e5c0e0491f4fa3c029216d59",
"sha256:3be4f21c6245930688bd9e162829480de027f8bf962ede33d4f8ba7d67a00cee",
"sha256:465ccac9d70115cd4de7186e60cfe989de73f7bb23e8a7aa45af18f7412e75bf",
"sha256:48c41a44ef8b8c2e80ca4527ee81daa4c527df3ecbc9423c41a420a9559d0e27",
"sha256:4a862753b36620af6fc54209264f92c716367f2f0ff4624952276a6bbd18cbde",
"sha256:4b1654dfc64ea479c242508eb8c724044f1e964a47d1d1cacc5132292d851971",
"sha256:4bd3e5c4b9682bc112d634f2c6ccc6736ed3635fc3319ac2bb11d768cc5a00d8",
"sha256:577470e39e60a6cd7780793202e63536026d9b8641de011ed9d8174da9ca5339",
"sha256:67285f8a611b0ebc0857ced2081e30302909f571a46bfa7a3cc0ad303fe015c6",
"sha256:7285a89df4900ed3bfaad5679b1e668cb4b38a8de1ccbfc84b05f34512da0a90",
"sha256:81823935e2f8d476707e85a78a405953a03ef7b7b4f55f93f7c2d9680e5e0691",
"sha256:8978132287a9d3ad6b54fcd1e08548033cc09dc6aacacb6c004c73c3eb5d3ac3",
"sha256:a20e442e917889d1a6b3c570c9e3fa2fdc398c20868abcea268ea33c024c4083",
"sha256:a24ee598d10befaec178efdff6054bc4d7e883f615bfbcd08126a0f4931c83a6",
"sha256:b04f85ac3a90c227b6e5890acb0edbaf3140938dbecf07bff618bf3638578cf1",
"sha256:b6a0e535baec27b528cb07a119f321ac024592388c5681a5ced167ae98e9fff3",
"sha256:bef32a5e327bd8e5af915d3416ffefdbe65ed975b646b3805be81b23580b57b8",
"sha256:bfb4c801f65dd61cedfc61a83732327fafbac55a47282e6f26f073ca7a41c3b2",
"sha256:c13b1e3afd29a5b3b2656257f14669ca8fa8d7956d509926f0b130b600b50ab7",
"sha256:c987dad82e8c65ebc985f5dae5e74a3beda9d0a2a4daf8a1115f3772b59e5141",
"sha256:ce7a453385e4c4693985b4a4a3533e041558851eae061a58a5405363b098fcd3",
"sha256:d0c5c6bac22b177bf8da7435d9d27a6834ee130309749d162b26c3105c0795a9",
"sha256:d97cf502abe2ab9eff8bd5e4aca274da8d06dd3ef08b759a8d6143f4ad65d4b4",
"sha256:dad43797959a74103cb59c5dac71409f9c27d34c8a05921341fb64ea8ccb1dd4",
"sha256:dd342f085542f6eb894ca00ef70236ea46070c8a13824c6bde0dfdcd36065b9b",
"sha256:de58755d723e86175756f463f2f0bddd45cc36fbd62601228a3f8761c9f58252",
"sha256:f3df7b3d0f91b88b2106031fd995802a2e9ae13e02c36c1fc075b43f420f3a17",
"sha256:f5414a788ecc6ee6bc58560e85ca624258a55ca434884445440a810796ea0e0b",
"sha256:fa26fa54c0a9384c27fcdc905a2fb7d60ac6e47d14bc2692145f2b3b1e2cfdbd"
],
"markers": "python_version >= '3.7' and python_full_version not in '3.9.0, 3.9.1'",
"version": "==45.0.7"
},
"esptool": {
"hashes": [
"sha256:05cc4732eb2a9a7766c9e3531f7943d76ff0ca06dc9cd308d1d3d0b72f74aac2"
],
"index": "pypi",
"markers": "python_version >= '3.10'",
"version": "==5.0.2"
},
"intelhex": {
"hashes": [
"sha256:87cc5225657524ec6361354be928adfd56bcf2a3dcc646c40f8f094c39c07db4",
"sha256:892b7361a719f4945237da8ccf754e9513db32f5628852785aea108dcd250093"
],
"version": "==2.3.0"
},
"markdown-it-py": {
"hashes": [
"sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147",
"sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3"
],
"markers": "python_version >= '3.10'",
"version": "==4.0.0"
},
"mdurl": {
"hashes": [
"sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8",
"sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"
],
"markers": "python_version >= '3.7'",
"version": "==0.1.2"
},
"mpremote": {
"hashes": [
"sha256:7f347318fb6d3bb8f89401d399a05efba39b51c74f747cebe92d3c6a9a4ee0b4",
"sha256:daed9b795fdf98edb0c9c4f7f892bf66f075ec5e728bdcc4ab0915abf23d5d17"
],
"index": "pypi",
"markers": "python_version >= '3.4'",
"version": "==1.26.0"
},
"platformdirs": {
"hashes": [
"sha256:abd01743f24e5287cd7a5db3752faf1a2d65353f38ec26d98e25a6db65958c85",
"sha256:ca753cf4d81dc309bc67b0ea38fd15dc97bc30ce419a7f58d13eb3bf14c4febf"
],
"markers": "python_version >= '3.9'",
"version": "==4.4.0"
},
"pycparser": {
"hashes": [
"sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2",
"sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934"
],
"markers": "implementation_name != 'PyPy'",
"version": "==2.23"
},
"pygments": {
"hashes": [
"sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887",
"sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"
],
"markers": "python_version >= '3.8'",
"version": "==2.19.2"
},
"pyserial": {
"hashes": [
"sha256:3c77e014170dfffbd816e6ffc205e9842efb10be9f58ec16d3e8675b4925cddb",
"sha256:c4451db6ba391ca6ca299fb3ec7bae67a5c55dde170964c7a14ceefec02f2cf0"
],
"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"
},
"typing-extensions": {
"hashes": [
"sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466",
"sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"
],
"markers": "python_version >= '3.9'",
"version": "==4.15.0"
}
},
"develop": {}
}

117
pico/lib/dma.py Normal file
View File

@@ -0,0 +1,117 @@
from machine import Pin
from rp2 import PIO, StateMachine, asm_pio
from time import sleep
import array
import uctypes
from uctypes import BF_POS, BF_LEN, UINT32, BFUINT32, struct
PIO0_BASE = 0x50200000
PIO1_BASE = 0x50300000
DMA_BASE = 0x50000000
DMA_CHAN_WIDTH = 0x40
DMA_CHAN_COUNT = 12
DMA_SIZE_BYTE = 0x0
DMA_SIZE_HALFWORD = 0x1
DMA_SIZE_WORD = 0x2
# DMA: RP2040 datasheet 2.5.7
DMA_CTRL_TRIG_FIELDS = {
"AHB_ERROR": 31<<BF_POS | 1<<BF_LEN | BFUINT32,
"READ_ERROR": 30<<BF_POS | 1<<BF_LEN | BFUINT32,
"WRITE_ERROR": 29<<BF_POS | 1<<BF_LEN | BFUINT32,
"BUSY": 24<<BF_POS | 1<<BF_LEN | BFUINT32,
"SNIFF_EN": 23<<BF_POS | 1<<BF_LEN | BFUINT32,
"BSWAP": 22<<BF_POS | 1<<BF_LEN | BFUINT32,
"IRQ_QUIET": 21<<BF_POS | 1<<BF_LEN | BFUINT32,
"TREQ_SEL": 15<<BF_POS | 6<<BF_LEN | BFUINT32,
"CHAIN_TO": 11<<BF_POS | 4<<BF_LEN | BFUINT32,
"RING_SEL": 10<<BF_POS | 1<<BF_LEN | BFUINT32,
"RING_SIZE": 6<<BF_POS | 4<<BF_LEN | BFUINT32,
"INCR_WRITE": 5<<BF_POS | 1<<BF_LEN | BFUINT32,
"INCR_READ": 4<<BF_POS | 1<<BF_LEN | BFUINT32,
"DATA_SIZE": 2<<BF_POS | 2<<BF_LEN | BFUINT32,
"HIGH_PRIORITY":1<<BF_POS | 1<<BF_LEN | BFUINT32,
"EN": 0<<BF_POS | 1<<BF_LEN | BFUINT32
}
# Channel-specific DMA registers
DMA_CHAN_REGS = {
"READ_ADDR_REG": 0x00|UINT32,
"WRITE_ADDR_REG": 0x04|UINT32,
"TRANS_COUNT_REG": 0x08|UINT32,
"CTRL_TRIG_REG": 0x0c|UINT32,
"CTRL_TRIG": (0x0c,DMA_CTRL_TRIG_FIELDS)
}
# General DMA registers
DMA_REGS = {
"INTR": 0x400|UINT32,
"INTE0": 0x404|UINT32,
"INTF0": 0x408|UINT32,
"INTS0": 0x40c|UINT32,
"INTE1": 0x414|UINT32,
"INTF1": 0x418|UINT32,
"INTS1": 0x41c|UINT32,
"TIMER0": 0x420|UINT32,
"TIMER1": 0x424|UINT32,
"TIMER2": 0x428|UINT32,
"TIMER3": 0x42c|UINT32,
"MULTI_CHAN_TRIGGER": 0x430|UINT32,
"SNIFF_CTRL": 0x434|UINT32,
"SNIFF_DATA": 0x438|UINT32,
"FIFO_LEVELS": 0x440|UINT32,
"CHAN_ABORT": 0x444|UINT32
}
DMA_CHANS = [struct(DMA_BASE + n*DMA_CHAN_WIDTH, DMA_CHAN_REGS) for n in range(0,DMA_CHAN_COUNT)]
DMA_DEVICE = struct(DMA_BASE, DMA_REGS)
DMA_CH0_AL3_TRANS_COUNT = DMA_BASE + 0x38
class PIO_DMA_Transfer():
def __init__(self, dma_channel, sm_num, block_size, transfer_count):
self.dma_chan = DMA_CHANS[dma_channel]
self.channel_number = dma_channel
if (sm_num >= 0 and sm_num < 4):
self.dma_chan.WRITE_ADDR_REG = PIO0_BASE + 0x10 + sm_num *4
self.dma_chan.CTRL_TRIG.TREQ_SEL = sm_num
elif (sm_num < 8):
self.dma_chan.WRITE_ADDR_REG = PIO1_BASE + 0x10 + (sm_num-4) *4
self.dma_chan.CTRL_TRIG.TREQ_SEL = sm_num + 4
if (block_size == 8):
self.dma_chan.CTRL_TRIG.DATA_SIZE = DMA_SIZE_BYTE
if (block_size == 16):
self.dma_chan.CTRL_TRIG.DATA_SIZE = DMA_SIZE_HALFWORD
if (block_size == 32):
self.dma_chan.CTRL_TRIG.DATA_SIZE = DMA_SIZE_WORD
self.dma_chan.TRANS_COUNT_REG = transfer_count
#Do I just always want these?
self.dma_chan.CTRL_TRIG.INCR_WRITE = 0
self.dma_chan.CTRL_TRIG.INCR_READ = 1
def start_transfer(self, buffer):
self.dma_chan.READ_ADDR_REG = uctypes.addressof(buffer)
self.dma_chan.CTRL_TRIG.EN = 1
def transfer_count(self):
return self.dma_chan.TRANS_COUNT_REG
def busy(self):
if self.dma_chan.CTRL_TRIG.DATA_SIZE == 1:
return True
else:
return False

68
pico/lib/ws2812.py Normal file
View File

@@ -0,0 +1,68 @@
import array, time
from machine import Pin
import rp2
from time import sleep
import dma
@rp2.asm_pio(sideset_init=rp2.PIO.OUT_LOW, out_shiftdir=rp2.PIO.SHIFT_LEFT, autopull=True, pull_thresh=8)
def ws2812():
T1 = 2
T2 = 5
T3 = 3
wrap_target()
label("bitloop")
out(x, 1) .side(0) [T3 - 1]
jmp(not_x, "do_zero") .side(1) [T1 - 1]
jmp("bitloop") .side(1) [T2 - 1]
label("do_zero")
nop() .side(0) [T2 - 1]
wrap()
class WS2812B:
def __init__(self, num_leds, pin, state_machine, brightness=0.1, invert=False):
self.sm = rp2.StateMachine(state_machine, ws2812, freq=8_000_000, sideset_base=Pin(pin))
self.sm.active(1)
self.ar = bytearray(num_leds*3)
self.num_leds = num_leds
self.brightness = brightness
self.invert = invert
self.pio_dma = dma.PIO_DMA_Transfer(state_machine+4, state_machine, 8, num_leds*3)
def show(self):
self.pio_dma.start_transfer(self.ar)
def set(self, i, color):
self.ar[i*3] = int(color[1]*self.brightness)
self.ar[i*3+1] = int(color[0]*self.brightness)
self.ar[i*3+2] = int(color[2]*self.brightness)
def fill(self, color):
for i in range(self.num_leds):
self.set(i, color)
def busy(self):
return self.pio_dma.busy()
BLACK = (0, 0, 0)
RED = (255, 0, 0)
YELLOW = (255, 150, 0)
GREEN = (0, 255, 0)
CYAN = (0, 255, 255)
BLUE = (0, 0, 255)
PURPLE = (180, 0, 255)
WHITE = (255, 255, 255)
COLORS = (BLACK, RED, YELLOW, GREEN, CYAN, BLUE, PURPLE, WHITE)
if __name__ == "__main__":
num_leds, pin, sm, brightness = 293, 2, 0, 0.1
ws0 = WS2812B(num_leds, pin, sm, brightness)
while True:
for color in ws0.COLORS:
ws0.fill(color)
ws0.show()
time.sleep(1)

32
pico/test/leds.py Normal file
View File

@@ -0,0 +1,32 @@
from ws2812 import WS2812B
import time
# --- Rainbow pattern (outside ws2812): pregen double buffer, show via head offset ---
# --- Strips + rainbow buffers per strip ---
strips = []
pins = ((2, 291),
(3, 290),
(4, 283),
(7, 278),
(0, 275),
(28, 278),
(29, 283),
(6, 290))
sm = 0
for pin, num_leds in pins:
print(pin, num_leds)
ws = WS2812B(num_leds, pin, sm, brightness=1.0) # 1.0 so fill() is visible
strips.append(ws)
sm += 1
ws.fill((255,0,0))
ws.show()
time.sleep(1)

View File

@@ -1,9 +1,19 @@
import settings
import wifi import wifi
import time
from settings import Settings from settings import Settings
s = Settings() print(wifi.ap('qwerty'))
name = s.get('name', 'led')
password = s.get("ap_password", "") settings = Settings()
wifi.ap(name, password) 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)

View File

@@ -1,12 +1,13 @@
# DMA driver for Raspberry Pi Pico 2 (RP2350) only.
from machine import Pin from machine import Pin
from rp2 import PIO, StateMachine, asm_pio from rp2 import PIO, StateMachine, asm_pio
from time import sleep
import array import array
import uctypes import uctypes
from uctypes import BF_POS, BF_LEN, UINT32, BFUINT32, struct from uctypes import BF_POS, BF_LEN, UINT32, BFUINT32, struct
PIO0_BASE = 0x50200000 PIO0_BASE = 0x50200000
PIO1_BASE = 0x50300000 PIO1_BASE = 0x50300000
PIO2_BASE = 0x50400000
DMA_BASE = 0x50000000 DMA_BASE = 0x50000000
DMA_CHAN_WIDTH = 0x40 DMA_CHAN_WIDTH = 0x40
DMA_CHAN_COUNT = 12 DMA_CHAN_COUNT = 12
@@ -15,35 +16,34 @@ DMA_SIZE_BYTE = 0x0
DMA_SIZE_HALFWORD = 0x1 DMA_SIZE_HALFWORD = 0x1
DMA_SIZE_WORD = 0x2 DMA_SIZE_WORD = 0x2
# DMA: RP2040 datasheet 2.5.7 # RP2350 DMA CTRL_TRIG bit positions
DMA_CTRL_TRIG_FIELDS = { DMA_CTRL_TRIG_FIELDS = {
"AHB_ERROR": 31<<BF_POS | 1<<BF_LEN | BFUINT32, "AHB_ERROR": 31<<BF_POS | 1<<BF_LEN | BFUINT32,
"READ_ERROR": 30<<BF_POS | 1<<BF_LEN | BFUINT32, "READ_ERROR": 30<<BF_POS | 1<<BF_LEN | BFUINT32,
"WRITE_ERROR": 29<<BF_POS | 1<<BF_LEN | BFUINT32, "WRITE_ERROR": 29<<BF_POS | 1<<BF_LEN | BFUINT32,
"BUSY": 24<<BF_POS | 1<<BF_LEN | BFUINT32, "BUSY": 26<<BF_POS | 1<<BF_LEN | BFUINT32,
"SNIFF_EN": 23<<BF_POS | 1<<BF_LEN | BFUINT32, "SNIFF_EN": 25<<BF_POS | 1<<BF_LEN | BFUINT32,
"BSWAP": 22<<BF_POS | 1<<BF_LEN | BFUINT32, "BSWAP": 24<<BF_POS | 1<<BF_LEN | BFUINT32,
"IRQ_QUIET": 21<<BF_POS | 1<<BF_LEN | BFUINT32, "IRQ_QUIET": 23<<BF_POS | 1<<BF_LEN | BFUINT32,
"TREQ_SEL": 15<<BF_POS | 6<<BF_LEN | BFUINT32, "TREQ_SEL": 17<<BF_POS | 6<<BF_LEN | BFUINT32,
"CHAIN_TO": 11<<BF_POS | 4<<BF_LEN | BFUINT32, "CHAIN_TO": 13<<BF_POS | 4<<BF_LEN | BFUINT32,
"RING_SEL": 10<<BF_POS | 1<<BF_LEN | BFUINT32, "RING_SEL": 12<<BF_POS | 1<<BF_LEN | BFUINT32,
"RING_SIZE": 6<<BF_POS | 4<<BF_LEN | BFUINT32, "RING_SIZE": 8<<BF_POS | 4<<BF_LEN | BFUINT32,
"INCR_WRITE": 5<<BF_POS | 1<<BF_LEN | BFUINT32, "INCR_WRITE": 6<<BF_POS | 1<<BF_LEN | BFUINT32,
"INCR_READ": 4<<BF_POS | 1<<BF_LEN | BFUINT32, "INCR_READ": 4<<BF_POS | 1<<BF_LEN | BFUINT32,
"DATA_SIZE": 2<<BF_POS | 2<<BF_LEN | BFUINT32, "DATA_SIZE": 2<<BF_POS | 2<<BF_LEN | BFUINT32,
"HIGH_PRIORITY":1<<BF_POS | 1<<BF_LEN | BFUINT32, "HIGH_PRIORITY":1<<BF_POS | 1<<BF_LEN | BFUINT32,
"EN": 0<<BF_POS | 1<<BF_LEN | BFUINT32 "EN": 0<<BF_POS | 1<<BF_LEN | BFUINT32
} }
# Channel-specific DMA registers
DMA_CHAN_REGS = { DMA_CHAN_REGS = {
"READ_ADDR_REG": 0x00|UINT32, "READ_ADDR_REG": 0x00|UINT32,
"WRITE_ADDR_REG": 0x04|UINT32, "WRITE_ADDR_REG": 0x04|UINT32,
"TRANS_COUNT_REG": 0x08|UINT32, "TRANS_COUNT_REG": 0x08|UINT32,
"CTRL_TRIG_REG": 0x0c|UINT32, "CTRL_TRIG_REG": 0x0c|UINT32,
"CTRL_TRIG": (0x0c,DMA_CTRL_TRIG_FIELDS) "CTRL_TRIG": (0x0c, DMA_CTRL_TRIG_FIELDS)
} }
# General DMA registers
DMA_REGS = { DMA_REGS = {
"INTR": 0x400|UINT32, "INTR": 0x400|UINT32,
"INTE0": 0x404|UINT32, "INTE0": 0x404|UINT32,
@@ -63,55 +63,51 @@ DMA_REGS = {
"CHAN_ABORT": 0x444|UINT32 "CHAN_ABORT": 0x444|UINT32
} }
DMA_CHANS = [struct(DMA_BASE + n*DMA_CHAN_WIDTH, DMA_CHAN_REGS) for n in range(0,DMA_CHAN_COUNT)] DMA_CHANS = [struct(DMA_BASE + n*DMA_CHAN_WIDTH, DMA_CHAN_REGS) for n in range(DMA_CHAN_COUNT)]
DMA_DEVICE = struct(DMA_BASE, DMA_REGS) DMA_DEVICE = struct(DMA_BASE, DMA_REGS)
DMA_CH0_AL3_TRANS_COUNT = DMA_BASE + 0x38 PIO_TX_FIFO_OFFSET = 0x10
# RP2350 DREQ: PIO0_TX=0-3, PIO0_RX=4-7, PIO1_TX=8-11, PIO1_RX=12-15, PIO2_TX=16-19
def _pio_base_and_treq(sm_num):
"""Return (PIO_BASE, TREQ_SEL) for state machine 0..11."""
if sm_num < 4:
return (PIO0_BASE, sm_num)
if sm_num < 8:
return (PIO1_BASE, sm_num + 4)
if sm_num < 12:
return (PIO2_BASE, sm_num + 8)
raise ValueError("state machine index out of range")
class PIO_DMA_Transfer(): class PIO_DMA_Transfer():
def __init__(self, dma_channel, sm_num, block_size, transfer_count): def __init__(self, dma_channel, sm_num, block_size, transfer_count):
self.dma_chan = DMA_CHANS[dma_channel] self.dma_chan = DMA_CHANS[dma_channel]
self.channel_number = dma_channel self.channel_number = dma_channel
if (sm_num >= 0 and sm_num < 4): pio_base, treq_sel = _pio_base_and_treq(sm_num)
self.dma_chan.WRITE_ADDR_REG = PIO0_BASE + 0x10 + sm_num *4 sm_offset = (sm_num % 4) * 4
self.dma_chan.CTRL_TRIG.TREQ_SEL = sm_num self.dma_chan.WRITE_ADDR_REG = pio_base + PIO_TX_FIFO_OFFSET + sm_offset
elif (sm_num < 8): self.dma_chan.CTRL_TRIG.TREQ_SEL = treq_sel
self.dma_chan.WRITE_ADDR_REG = PIO1_BASE + 0x10 + (sm_num-4) *4
self.dma_chan.CTRL_TRIG.TREQ_SEL = sm_num + 4 if block_size == 8:
if (block_size == 8):
self.dma_chan.CTRL_TRIG.DATA_SIZE = DMA_SIZE_BYTE self.dma_chan.CTRL_TRIG.DATA_SIZE = DMA_SIZE_BYTE
if (block_size == 16): elif block_size == 16:
self.dma_chan.CTRL_TRIG.DATA_SIZE = DMA_SIZE_HALFWORD self.dma_chan.CTRL_TRIG.DATA_SIZE = DMA_SIZE_HALFWORD
if (block_size == 32): elif block_size == 32:
self.dma_chan.CTRL_TRIG.DATA_SIZE = DMA_SIZE_WORD self.dma_chan.CTRL_TRIG.DATA_SIZE = DMA_SIZE_WORD
self.dma_chan.TRANS_COUNT_REG = transfer_count self.dma_chan.TRANS_COUNT_REG = transfer_count
#Do I just always want these?
self.dma_chan.CTRL_TRIG.INCR_WRITE = 0 self.dma_chan.CTRL_TRIG.INCR_WRITE = 0
self.dma_chan.CTRL_TRIG.INCR_READ = 1 self.dma_chan.CTRL_TRIG.INCR_READ = 1
self.dma_chan.CTRL_TRIG.CHAIN_TO = dma_channel
def start_transfer(self, buffer): def start_transfer(self, buffer):
self.dma_chan.READ_ADDR_REG = uctypes.addressof(buffer) self.dma_chan.READ_ADDR_REG = uctypes.addressof(buffer)
self.dma_chan.CTRL_TRIG.EN = 1 self.dma_chan.CTRL_TRIG.EN = 1
def transfer_count(self): def transfer_count(self):
return self.dma_chan.TRANS_COUNT_REG return self.dma_chan.TRANS_COUNT_REG
def busy(self): def busy(self):
if self.dma_chan.CTRL_TRIG.DATA_SIZE == 1: return bool(self.dma_chan.CTRL_TRIG_REG & (1 << 26))
return True
else:
return False

View File

@@ -1,54 +1,34 @@
import asyncio
import aioespnow from time import sleep
from settings import Settings from neopixel import NeoPixel
from web import web from machine import UART, Pin, PWM, ADC
import _thread
import network
import espnow
from patterns import Patterns from patterns import Patterns
import gc
import utime
import machine
import time
import wifi
import json
from p2p import p2p
async def main(): adc = ADC(2, atten=ADC.ATTN_11DB)
settings = Settings() sta = network.WLAN(network.WLAN.IF_STA) # Or network.WLAN.IF_AP
sta.active(True)
patterns = Patterns(selected=settings["pattern"]) e = espnow.ESPNow()
if settings["color_order"] == "rbg": color_order = (1, 5, 3) e.active(True)
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(): #e.add_peer(broadcast)
while True:
patterns.tick()
await asyncio.sleep_ms(0)
async def system(): p = Patterns()
while True:
gc.collect()
for i in range(60):
wdt.feed()
await asyncio.sleep(1)
w = web(settings, patterns) _thread.start_new_thread(p.scan_single_led, ((255,0,0),0))
print(settings)
# start the server in a bacakground task
print("Starting")
server = asyncio.create_task(w.start_server(host="0.0.0.0", port=80))
wdt = machine.WDT(timeout=10000)
wdt.feed()
asyncio.create_task(tick()) while True:
asyncio.create_task(p2p(settings, patterns)) value = adc.read_uv()*2
asyncio.create_task(system()) if value < 3_500_000:
p.run = False
p.off()
print(f"Voltage {value}")
sleep(1)
# cleanup before ending the application i = 0
await server
asyncio.run(main())

View File

@@ -1,20 +0,0 @@
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")

View File

@@ -3,512 +3,93 @@ from neopixel import NeoPixel
import utime import utime
import random import random
# 8 strips of 270 leds
strips = [(1, 270), (2, 277),
(3, 280), (4, 270),
(5, 270), (6, 270),
(7, 270), (10,270)]
class Patterns: class Patterns:
def __init__(self,color1=(0,0,0), color2=(0,0,0), brightness=127, selected="rainbow_cycle", delay=100): def __init__(self):
self.strips = [] self.pin_data = (21, 277) # Example: Pin 21, 277 LEDs
# Initialize all 8 strips self.strip = NeoPixel(Pin(self.pin_data[0]), self.pin_data[1])
for pin, num_leds in strips: self.run = False
strip = NeoPixel(Pin(pin, Pin.OUT), num_leds)
self.strips.append(strip)
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,
"rainbow_cycle": self.rainbow_cycle,
"rainbow_spiral": self.rainbow_spiral, # Rainbow cycle per strip
"rainbow_strips": self.rainbow_strips, # New: Single color per strip, rainbow between strips
"theater_chase": self.theater_chase,
"blink": self.blink,
"color_transition": self.color_transition, # Added new pattern
"flicker": self.flicker,
"scanner": self.scanner, # New: Single direction scanner
"bidirectional_scanner": self.bidirectional_scanner, # New: Bidirectional scanner
"strip_cycle": self.strip_cycle, # New: Cycle through strips
"external": None
}
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.strip.fill((0,0,0))
self.hold_duration = delay * 10 # Default hold duration at each color self.strip.write()
self.transition_step = 0 # Current step in the transition print(f"Initialized single strip on Pin {self.pin_data[0]} with {self.pin_data[1]} LEDs.")
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):
# Find and update the specific strip
for i, (strip_pin, _) in enumerate(strips):
if strip_pin == pin:
self.strips[i] = NeoPixel(Pin(pin, Pin.OUT), num_leds)
self.pattern_step = 0
break
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): def scan_single_led(self, color=(255, 255, 255), delay_ms=0):
self.brightness = brightness """
Scans a single LED along the length of the strip, turning it on and then off
as it moves. Optimized for speed by batching writes.
def set_color1(self, color): Args:
if len(self.colors) > 0: color (tuple): The (R, G, B) color of the scanning LED.
self.colors[0] = color delay_ms (int): Optional extra delay in milliseconds between each LED position.
if self.selected == "color_transition": Set to 0 for fastest possible without *extra* delay.
# If the first color is changed, potentially reset transition """
# to start from this new color if we were about to transition from it self.run = True
if self.current_color_idx == 0: num_pixels = len(self.strip)
self.transition_step = 0 last_pixel_index = num_pixels - 1
self.current_color = self.colors[0]
self.hold_start_time = utime.ticks_ms() # Turn off all pixels initially for a clean start if not already off
else: self.strip.fill((0, 0, 0))
self.colors.append(color) # No write here yet, as the first pixel will be set immediately
while self.run:
# --- Scan Forward ---
for i in range(num_pixels):
if not self.run:
break
# Turn on the current pixel
self.strip[i] = color
# Turn off the previous pixel if not the first one
if i > 0:
self.strip[i - 1] = (0, 0, 0)
# If it's the first pixel, ensure the last one from previous cycle is off (if applicable)
elif i == 0 and num_pixels > 1: # Only relevant if scanning backwards too
self.strip[last_pixel_index] = (0,0,0)
def set_color2(self, color): self.strip.write() # Write changes to the strip
if len(self.colors) > 1: if delay_ms > 0:
self.colors[1] = color utime.sleep_ms(delay_ms)
elif len(self.colors) == 1:
self.colors.append(color) # Ensure the last pixel of the forward scan is turned off
else: # List is empty if self.run and num_pixels > 0:
self.colors.append((0,0,0)) # Dummy color self.strip[last_pixel_index] = (0, 0, 0)
self.colors.append(color) self.strip.write() # Write this final change
def set_colors(self, colors): # --- Scan Backward (optional, remove this loop if you only want forward) ---
if colors and len(colors) >= 2: for i in range(num_pixels - 1, -1, -1): # From last_pixel_index down to 0
self.colors = colors if not self.run:
if self.selected == "color_transition": break
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): # Turn on the current pixel
# Changed: More robust index check self.strip[i] = color
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): # Turn off the next pixel (which was the previous one in reverse scan)
self.colors.append(color) if i < last_pixel_index:
if self.selected == "color_transition" and len(self.colors) == 2: self.strip[i + 1] = (0, 0, 0)
# If we just added the second color needed for transition # If it's the last pixel of the reverse scan, ensure the first one from previous cycle is off (if applicable)
self.sync() elif i == last_pixel_index and num_pixels > 1: # Only relevant if scanning forward too
self.strip[0] = (0,0,0)
self.strip.write() # Write changes to the strip
if delay_ms > 0:
utime.sleep_ms(delay_ms)
def del_color(self, num): # Ensure the first pixel of the backward scan is turned off
# Changed: More robust index check and using del for lists if self.run and num_pixels > 0:
if 0 <= num < len(self.colors): self.strip[0] = (0, 0, 0)
del self.colors[num] self.strip.write() # Write this final change
# 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):
# Find which strip contains LED i
current_pos = 0
for strip in self.strips:
if i < current_pos + len(strip):
strip[i - current_pos] = color
return
current_pos += len(strip)
def write(self):
for strip in self.strips:
strip.write()
def fill(self, color=None):
fill_color = color if color is not None else self.colors[0]
for strip in self.strips:
for i in range(len(strip)):
strip[i] = fill_color
self.write()
def off(self): def off(self):
self.fill((0, 0, 0)) print("Turning off LEDs.")
self.run = False
self.strip.fill((0,0,0))
self.strip.write()
utime.sleep_ms(50)
def on(self): # Example Usage (for MicroPython on actual hardware):
self.fill(self.apply_brightness(self.colors[0])) # (Same as before, just removed from the main block for brevity)
def color_wipe(self):
color = self.apply_brightness(self.colors[0])
current_time = utime.ticks_ms()
if utime.ticks_diff(current_time, self.last_update) >= self.delay:
# Calculate total LEDs dynamically
total_leds = sum(len(strip) for strip in self.strips)
if self.pattern_step < total_leds:
# Clear all LEDs
self.fill((0, 0, 0))
# Set the current LED
self.set(self.pattern_step, color)
self.write()
self.pattern_step += 1
else:
self.pattern_step = 0
self.last_update = current_time
def rainbow_cycle(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)
total_leds = sum(len(strip) for strip in self.strips)
for i in range(total_leds):
rc_index = (i * 256 // total_leds) + self.pattern_step
self.set(i, self.apply_brightness(wheel(rc_index & 255)))
self.write()
self.pattern_step = (self.pattern_step + 1) % 256
self.last_update = current_time
def theater_chase(self):
current_time = utime.ticks_ms()
if utime.ticks_diff(current_time, self.last_update) >= self.delay:
total_leds = sum(len(strip) for strip in self.strips)
for i in range(total_leds):
if (i + self.pattern_step) % 3 == 0:
self.set(i, self.apply_brightness(self.colors[0]))
else:
self.set(i, (0, 0, 0))
self.write()
self.pattern_step = (self.pattern_step + 1) % 3
self.last_update = current_time
def blink(self):
current_time = utime.ticks_ms()
if utime.ticks_diff(current_time, self.last_update) >= self.delay:
if self.pattern_step % 2 == 0:
self.fill(self.apply_brightness(self.colors[0]))
else:
self.fill((0, 0, 0))
self.pattern_step = (self.pattern_step + 1) % 2
self.last_update = current_time
def color_transition(self):
current_time = utime.ticks_ms()
# 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 flicker(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(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:
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])
total_leds = sum(len(strip) for strip in self.strips)
# Draw the head
if 0 <= head_pos < total_leds:
self.set(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 < total_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.set(tail_pos, faded_color)
self.write()
self.pattern_step += 1
if self.pattern_step >= total_leds + self.scanner_tail_length:
self.pattern_step = 0 # Reset to start
self.last_update = current_time
def bidirectional_scanner(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])
total_leds = sum(len(strip) for strip in self.strips)
# Calculate the head position based on direction
head_pos = self.pattern_step
# Draw the head
if 0 <= head_pos < total_leds:
self.set(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 < total_leds:
fade_factor = 1.0 - (i / (self.scanner_tail_length + 1))
faded_color = tuple(int(c * fade_factor) for c in color)
self.set(tail_pos, faded_color)
self.write()
self.pattern_step += self.scanner_direction
# Change direction if boundaries are reached
if self.scanner_direction == 1 and self.pattern_step >= total_leds:
self.scanner_direction = -1
self.pattern_step = total_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 strip_cycle(self):
"""
Cycles through each strip, turning them on and off one by one.
"""
current_time = utime.ticks_ms()
if utime.ticks_diff(current_time, self.last_update) >= self.delay:
# Turn off the previous strip
prev_strip = (self.pattern_step - 1) % len(self.strips)
for i in range(len(self.strips[prev_strip])):
self.strips[prev_strip][i] = (0, 0, 0)
# Turn on the current strip
current_strip = self.pattern_step % len(self.strips)
color = self.apply_brightness(self.colors[0])
for i in range(len(self.strips[current_strip])):
self.strips[current_strip][i] = color
self.write()
# Move to next strip
self.pattern_step += 1
self.last_update = current_time
def rainbow_spiral(self):
"""
Creates a rainbow effect that cycles through each strip individually.
Each strip shows its own rainbow pattern.
"""
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)
# Apply rainbow to each strip individually
for strip_idx, strip in enumerate(self.strips):
strip_length = len(strip)
for i in range(strip_length):
# Each strip gets its own rainbow cycle with offset based on strip index
rc_index = (i * 256 // strip_length) + self.pattern_step + (strip_idx * 32)
strip[i] = self.apply_brightness(wheel(rc_index & 255))
self.write()
self.pattern_step = (self.pattern_step + 1) % 256
self.last_update = current_time
def rainbow_strips(self):
"""
Each strip is a single color, rainbow effect is between strips.
Creates a rainbow pattern across the 8 strips.
"""
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)
# Each strip gets a single color from the rainbow
for strip_idx, strip in enumerate(self.strips):
# Calculate rainbow position for this strip
rainbow_pos = (strip_idx * 32 + self.pattern_step) % 256
color = self.apply_brightness(wheel(rainbow_pos))
# Fill entire strip with this single color
for i in range(len(strip)):
strip[i] = color
self.write()
self.pattern_step = (self.pattern_step + 1) % 256
self.last_update = current_time

View File

@@ -1,7 +1,4 @@
import json import json
import wifi
import ubinascii
import machine
class Settings(dict): class Settings(dict):
SETTINGS_FILE = "/settings.json" SETTINGS_FILE = "/settings.json"
@@ -9,21 +6,15 @@ class Settings(dict):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
self.load() # Load settings from file during initialization 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): def set_defaults(self):
self["led_pin"] = 10
self["num_leds"] = 50 self["num_leds"] = 50
self["pattern"] = "on" self["selected_pattern"] = "blink"
self["color1"] = "#00ff00" self["color1"] = "#000f00"
self["color2"] = "#ff0000" self["color2"] = "#0f0000"
self["delay"] = 100 self["delay"] = 100
self["brightness"] = 10 self["brightness"] = 100
self["color_order"] = "rgb" self["wifi"] = {"ssid": "", "password": ""}
self["name"] = f"led-hoop"
self["ap_password"] = ""
self["id"] = 0
def save(self): def save(self):
try: try:
@@ -43,55 +34,6 @@ class Settings(dict):
except Exception as e: except Exception as e:
print(f"Error loading settings") print(f"Error loading settings")
self.set_defaults() 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 # Example usage
def main(): def main():
@@ -100,14 +42,12 @@ def main():
settings['num_leds'] = 100 settings['num_leds'] = 100
print(f"Updated number of LEDs: {settings['num_leds']}") print(f"Updated number of LEDs: {settings['num_leds']}")
settings.save() settings.save()
# Create a new Settings object to test loading # Create a new Settings object to test loading
new_settings = Settings() new_settings = Settings()
print(f"Loaded number of LEDs: {new_settings['num_leds']}") print(f"Loaded number of LEDs: {new_settings['num_leds']}")
print(settings) print(settings)
# Run the example # Run the example
if __name__ == "__main__": if __name__ == "__main__":
main() main()

View File

@@ -1,109 +1,75 @@
body { body {
font-family: Arial, sans-serif; font-family: Arial, sans-serif;
max-width: 600px; max-width: 600px;
margin: 0 auto; margin: 0 auto;
padding: 20px; padding: 20px;
line-height: 1.6; line-height: 1.6;
} }
h1 { h1 {
text-align: center; text-align: center;
} }
form { form {
margin-bottom: 20px; margin-bottom: 20px;
} }
label { label {
display: block; display: block;
margin-bottom: 5px; margin-bottom: 5px;
} }
input[type="text"], input[type="text"], input[type="submit"], input[type="range"], input[type="color"] {
input[type="submit"], width: 100%;
input[type="range"],
input[type="color"] { margin-bottom: 10px;
width: 100%; box-sizing: border-box;
}
margin-bottom: 10px; input[type="range"] {
box-sizing: border-box; -webkit-appearance: none;
} appearance: none;
input[type="range"] { height: 25px;
-webkit-appearance: none; background: #d3d3d3;
appearance: none; outline: none;
height: 25px; opacity: 0.7;
background: #d3d3d3; transition: opacity .2s;
outline: none; }
opacity: 0.7; input[type="range"]:hover {
transition: opacity 0.2s; opacity: 1;
} }
input[type="range"]:hover { input[type="range"]::-webkit-slider-thumb {
opacity: 1; -webkit-appearance: none;
} appearance: none;
input[type="range"]::-webkit-slider-thumb { width: 25px;
-webkit-appearance: none; height: 25px;
appearance: none; background: #4CAF50;
width: 25px; cursor: pointer;
height: 25px; border-radius: 50%;
background: #4caf50; }
cursor: pointer; input[type="range"]::-moz-range-thumb {
border-radius: 50%; width: 25px;
} height: 25px;
input[type="range"]::-moz-range-thumb { background: #4CAF50;
width: 25px; cursor: pointer;
height: 25px; border-radius: 50%;
background: #4caf50; }
cursor: pointer; #pattern_buttons {
border-radius: 50%; display: flex;
} flex-wrap: wrap;
#pattern_buttons { gap: 10px;
display: flex; margin-bottom: 20px;
flex-wrap: wrap; }
gap: 10px; #pattern_buttons button {
margin-bottom: 20px; flex: 1 0 calc(33.333% - 10px);
} padding: 10px;
#pattern_buttons button { background-color: #4CAF50;
flex: 1 0 calc(33.333% - 10px); color: white;
padding: 10px; border: none;
background-color: #4caf50; cursor: pointer;
color: white; transition: background-color 0.3s;
border: none; }
cursor: pointer; #pattern_buttons button:hover {
transition: background-color 0.3s; background-color: #45a049;
} }
#pattern_buttons button:hover { @media (max-width: 480px) {
background-color: #45a049; #pattern_buttons button {
} flex: 1 0 calc(50% - 10px);
@media (max-width: 480px) { }
#pattern_buttons button { }
flex: 1 0 calc(50% - 10px);
}
}
#connection-status {
width: 15px;
height: 15px;
border-radius: 50%;
display: inline-block; /* Or block, depending on where you put it */
margin-left: 10px; /* Adjust spacing as needed */
vertical-align: middle; /* Align with nearby text */
background-color: grey; /* Default: Unknown */
}
#connection-status.connecting {
background-color: yellow;
}
#connection-status.open {
background-color: green;
}
#connection-status.closing,
#connection-status.closed {
background-color: red;
}
#color_order_form label,
#color_order_form input[type="radio"] {
/* Ensures they behave as inline elements */
display: inline-block;
/* Adds some space between them for readability */
margin-right: 10px;
vertical-align: middle; /* Aligns them nicely if heights vary */
}

View File

@@ -2,243 +2,146 @@ let delayTimeout;
let brightnessTimeout; let brightnessTimeout;
let colorTimeout; let colorTimeout;
let color2Timeout; let color2Timeout;
let ws; // Variable to hold the WebSocket connection
let connectionStatusElement; // Variable to hold the connection status element
// 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) { async function post(path, data) {
console.log(`POST to ${path}`, data); console.log(`POST to ${path}`, data);
try { try {
const response = await fetch(path, { const response = await fetch(path, {
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "application/json", 'Content-Type': 'application/json'
}, },
body: JSON.stringify(data), body: JSON.stringify(data) // Convert data to JSON string
}); });
if (!response.ok) { if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`); throw new Error(`HTTP error! Status: ${response.status}`);
}
} catch (error) {
console.error('Error during POST request:', error);
} }
} catch (error) {
console.error("Error during POST request:", error);
}
} }
async function get(path) { async function get(path) {
try { try {
const response = await fetch(path); const response = await fetch(path);
if (!response.ok) { if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`); 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);
} }
return await response.json();
} catch (error) {
console.error("Error during GET request:", error);
}
} }
async function updateColor(event) { async function updateColor(event) {
event.preventDefault(); event.preventDefault();
clearTimeout(colorTimeout); clearTimeout(colorTimeout);
colorTimeout = setTimeout(function () { colorTimeout = setTimeout(async function() {
const color = document.getElementById("color").value; const color = document.getElementById('color').value;
sendWebSocketData({ color1: color }); await post("/color", { color }); // Send as JSON
}, 500); }, 500);
} }
async function updateColor2(event) { async function updateColor2(event) {
event.preventDefault(); event.preventDefault();
clearTimeout(color2Timeout); clearTimeout(color2Timeout);
color2Timeout = setTimeout(function () { color2Timeout = setTimeout(async function() {
const color = document.getElementById("color2").value; const color = document.getElementById('color2').value;
sendWebSocketData({ color2: color }); await post("/color2", { color }); // Send as JSON
}, 500); }, 500);
} }
async function updatePattern(pattern) { async function updatePattern(pattern) {
sendWebSocketData({ pattern: pattern }); event.preventDefault();
await post("/pattern", { pattern }); // Send as JSON
} }
async function updateBrightness(event) { async function updateBrightness(event) {
event.preventDefault(); event.preventDefault();
clearTimeout(brightnessTimeout); clearTimeout(brightnessTimeout);
brightnessTimeout = setTimeout(function () { brightnessTimeout = setTimeout(async function() {
const brightness = document.getElementById("brightness").value; const brightness = document.getElementById('brightness').value;
sendWebSocketData({ brightness: brightness }); await post('/brightness', { brightness }); // Send as JSON
}, 500); }, 500);
} }
async function updateDelay(event) { async function updateDelay(event) {
event.preventDefault(); event.preventDefault();
clearTimeout(delayTimeout); clearTimeout(delayTimeout);
delayTimeout = setTimeout(function () { delayTimeout = setTimeout(async function() {
const delay = document.getElementById("delay").value; const delay = document.getElementById('delay').value;
sendWebSocketData({ delay: delay }); await post('/delay', { delay }); // Send as JSON
}, 500); }, 500);
} }
async function updateNumLeds(event) { async function updateNumLeds(event) {
event.preventDefault(); event.preventDefault();
const numLeds = document.getElementById("num_leds").value; const numLeds = document.getElementById('num_leds').value;
sendWebSocketData({ num_leds: parseInt(numLeds) }); await post('/num_leds', { num_leds: numLeds }); // Send as JSON
} }
async function updateName(event) { async function updateWifi(event) {
event.preventDefault(); event.preventDefault();
const name = document.getElementById("name").value; const ssid = document.getElementById('ssid').value;
sendWebSocketData({ name: name }); const password = document.getElementById('password').value;
} const ip = document.getElementById('ip').value;
const gateway = document.getElementById('gateway').value;
async function updateID(event) { const wifiSettings = { ssid, password, ip, gateway }; // Create JSON object
event.preventDefault(); console.log(wifiSettings);
const id = document.getElementById("id").value; const response = await post('/wifi_settings', wifiSettings); // Send as JSON
sendWebSocketData({ id: parseInt(id) }); if (response === 500) {
} alert("Failed to connect to Wi-Fi");
}
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) { function createPatternButtons(patterns) {
const container = document.getElementById("pattern_buttons"); const container = document.getElementById('pattern_buttons');
container.innerHTML = ""; // Clear previous buttons container.innerHTML = ''; // Clear previous buttons
patterns.forEach((pattern) => { patterns.forEach(pattern => {
const button = document.createElement("button"); const button = document.createElement('button');
button.type = "button"; button.type = 'button'; // Use 'button' instead of 'submit'
button.textContent = pattern; button.textContent = pattern;
button.value = pattern; button.value = pattern;
button.addEventListener("click", async function (event) { button.addEventListener('click', async function(event) {
event.preventDefault(); event.preventDefault();
await updatePattern(pattern); await updatePattern(pattern);
});
container.appendChild(button);
}); });
container.appendChild(button);
});
} }
document.addEventListener("DOMContentLoaded", async function () { document.addEventListener('DOMContentLoaded', async function() {
// Get the connection status element once the DOM is ready document.getElementById('color').addEventListener('input', updateColor);
connectionStatusElement = document.getElementById("connection-status"); 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);
// Establish WebSocket connection on page load document.querySelectorAll(".pattern_button").forEach(button => {
connectWebSocket(); console.log(button.value);
button.addEventListener('click', async event => {
document.getElementById("color").addEventListener("input", updateColor); event.preventDefault();
document.getElementById("color2").addEventListener("input", updateColor2); await updatePattern(button.value);
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 to toggle the display of the settings menu
function selectSettings() { function selectSettings() {
const settingsMenu = document.getElementById("settings_menu"); const settingsMenu = document.getElementById('settings_menu');
controls = document.getElementById("controls"); controls = document.getElementById('controls');
settingsMenu.style.display = "block"; settingsMenu.style.display = 'block';
controls.style.display = "none"; controls.style.display = 'none';
} }
function selectControls() { function selectControls() {
const settingsMenu = document.getElementById("settings_menu"); const settingsMenu = document.getElementById('settings_menu');
controls = document.getElementById("controls"); controls = document.getElementById('controls');
settingsMenu.style.display = "none"; settingsMenu.style.display = 'none';
controls.style.display = "block"; controls.style.display = 'block';
} }

View File

@@ -1,124 +1,41 @@
{% args settings, patterns, mac %} {% args settings, patterns %}
<!doctype html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{settings['name']}}</title> <title>LED Control</title>
<script src="static/main.js"></script> <script src="static/main.js"></script>
<link rel="stylesheet" href="static/main.css" /> <link rel="stylesheet" href="static/main.css">
</head> </head>
<body> <body>
<h1>{{settings['name']}}</h1> <h1>Control LEDs</h1>
<button onclick="selectControls()">Controls</button> <button onclick="selectControls()">Controls</button>
<button onclick="selectSettings()">Settings</button> <button onclick="selectSettings()">Settings</button>
<!-- Main LED Controls --> <!-- Main LED Controls -->
<div id="controls"> <div id="controls">
<div id="pattern_buttons"> <div id="pattern_buttons">
{% for p in patterns %} {% for p in patterns %}
<button class="pattern_button" value="{{p}}">{{p}}</button> <button class="pattern_button" value="{{p}}">{{p}}</button>
{% endfor %} {% endfor %}
<!-- Pattern buttons will be inserted here --> <!-- 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> </div>
<form id="delay_form" method="post" action="/delay">
<!-- Settings Menu for num_leds, Wi-Fi SSID, and Password --> <label for="delay">Delay:</label>
<input type="range" id="delay" name="delay" min="1" max="1000" value="{{settings['delay']}}" step="10">
<div id="settings_menu" style="display: none"> </form>
<h2>Settings</h2> <form id="brightness_form" method="post" action="/brightness">
<label for="brightness">Brightness:</label>
<form id="name_form" method="post" action="/name"> <input type="range" id="brightness" name="brightness" min="0" max="100" value="{{settings['brightness']}}" step="1">
<label for="name">Name:</label> </form>
<input <form id="color_form" method="post" action="/color">
type="text" <input type="color" id="color" name="color" value="{{settings['color1']}}">
id="name" </form>
name="num_leds" <form id="color2_form" method="post" action="/color2">
value="{{settings['name']}}" <input type="color" id="color2" name="color2" value="{{settings['color2']}}">
/> </form>
<input type="submit" value="Update Name" /> </div>
</form> </body>
<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> </html>

View File

@@ -1,43 +0,0 @@
from microdot import Microdot, send_file, Response
from microdot.utemplate import Template
from microdot.websocket import with_websocket
import machine
import wifi
import json
def web(settings, patterns):
app = Microdot()
Response.default_content_type = 'text/html'
@app.route('/')
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_handler(request, path):
if '..' in path:
# Directory traversal is not allowed
return 'Not found', 404
return send_file('static/' + path)
@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.route("/ws")
@with_websocket
async def ws(request, ws):
while True:
data = await ws.receive()
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

View File

@@ -1,43 +1,101 @@
from microdot import Microdot, send_file, Response from microdot import Microdot, send_file, Response
from microdot.utemplate import Template from microdot.utemplate import Template
from microdot.websocket import with_websocket from microdot.websocket import with_websocket
import machine
import wifi
import json
def web(settings, patterns): import json
import wifi
def web(settings, patterns, patterns2):
app = Microdot() app = Microdot()
Response.default_content_type = 'text/html' Response.default_content_type = 'text/html'
@app.route('/') @app.route('/')
async def index_hnadler(request): async def index(request):
mac = wifi.get_mac().hex() return Template('/index.html').render(settings=settings, patterns=patterns.patterns.keys())
return Template('/index.html').render(settings=settings, patterns=patterns.patterns.keys(), mac=mac)
@app.route("/static/<path:path>") @app.route("/static/<path:path>")
def static_handler(request, path): def static(request, path):
if '..' in path: if '..' in path:
# Directory traversal is not allowed # Directory traversal is not allowed
return 'Not found', 404 return 'Not found', 404
return send_file('static/' + path) return send_file('static/' + path)
@app.post("/settings") @app.post("/pattern")
def settings_handler(request): def pattern(request):
# Keep the POST handler for compatibility or alternative usage if needed try:
# For WebSocket updates, the /ws handler is now primary data = json.loads(request.body.decode('utf-8'))
return settings.set_settings(request.body.decode('utf-8'), patterns) pattern = data["pattern"]
if patterns.select(pattern):
patterns2.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.route("/ws") @app.post("/delay")
def delay(request):
try:
data = json.loads(request.body.decode('utf-8'))
delay = int(data["delay"])
patterns.set_delay(delay)
patterns2.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)
patterns2.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
patterns2.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
patterns2.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.route("/external")
@with_websocket @with_websocket
async def ws(request, ws): async def ws(request, ws):
patterns.select("external")
while True: while True:
data = await ws.receive() data = await ws.receive()
if data: print(data)
for i in range(min(patterns.num_leds, int(len(data)/3))):
# Process the received data patterns.set(i, (data[i*3], data[i*3+1], data[i*3+2]))
_, status_code = settings.set_settings(json.loads(data), patterns, True) patterns.write()
#await ws.send(status_code)
else:
break
return app return app

View File

@@ -1,16 +1,18 @@
import network import network
from machine import Pin
from time import sleep from time import sleep
import ubinascii
from settings import Settings
def connect(ssid, password, ip, gateway): def connect(ssid, password, ip, gateway):
if ssid is None or password is None:
print("Missing ssid or password")
return None
try: try:
sta_if = network.WLAN(network.STA_IF) 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 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...') print('connecting to network...')
sta_if.active(True) sta_if.active(True)
sta_if.connect(ssid, password) sta_if.connect(ssid, password)
@@ -24,16 +26,21 @@ def connect(ssid, password, ip, gateway):
return None return None
def ap(ssid, password): def ap(password):
ap_if = network.WLAN(network.AP_IF) ap_if = network.WLAN(network.AP_IF)
ap_mac = ap_if.config('mac') ap_mac = ap_if.config('mac')
ssid = f"led-{ubinascii.hexlify(ap_mac).decode()}"
print(ssid) print(ssid)
ap_if.active(True) ap_if.active(True)
ap_if.config(essid=ssid, password=password) ap_if.config(essid=ssid, password="qwerty1234")
ap_if.active(False) ap_if.active(False)
ap_if.active(True) ap_if.active(True)
print(ap_if.ifconfig()) print(ap_if.ifconfig())
def get_mac():
ap_if = network.WLAN(network.AP_IF)
return ap_if.config('mac')