mirror of
https://github.com/FliegendeWurst/raspi-oled.git
synced 2024-11-25 03:34:58 +00:00
Compare commits
13 Commits
3b97c44e10
...
f8de136019
Author | SHA1 | Date | |
---|---|---|---|
|
f8de136019 | ||
|
0266f9c465 | ||
|
f881075c4a | ||
|
f4d42943af | ||
|
30683b42f0 | ||
|
6335eb818e | ||
|
3e38684c17 | ||
|
13c5a472ce | ||
|
ca73b6545a | ||
|
c5a930bed1 | ||
|
4e737ca007 | ||
|
d51b298128 | ||
|
f87f7b5001 |
6
.gitignore
vendored
6
.gitignore
vendored
@ -3,3 +3,9 @@
|
||||
events_weekly.json
|
||||
events.json
|
||||
sensors.db
|
||||
/result
|
||||
/box.svg
|
||||
/mockup.png
|
||||
/*.aes
|
||||
/*.py
|
||||
/*.txt
|
302
Cargo.lock
generated
302
Cargo.lock
generated
@ -37,13 +37,28 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.1"
|
||||
version = "1.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ea5d730647d4fadd988536d06fecce94b7b4f2a7efdae548f1cf4b63205518ab"
|
||||
checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "andotp-import"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89e4375a7f794f05aaaf899b684307a837fd73c773888cbb01a0f622eed59e43"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"ring",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
"totp-rs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "android-activity"
|
||||
version = "0.4.3"
|
||||
@ -113,6 +128,12 @@ version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7b7e4c2464d97fe331d41de9d5db0def0a96f4d823b8b32a2efd503578988973"
|
||||
|
||||
[[package]]
|
||||
name = "base32"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23ce669cd6c8588f79e15cf450314f9638f967fc5770ff1c7c1deb0925ea7cfa"
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.21.4"
|
||||
@ -143,6 +164,15 @@ version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a"
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.10.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block-sys"
|
||||
version = "0.1.0-beta.1"
|
||||
@ -191,14 +221,14 @@ checksum = "965ab7eb5f8f97d2a083c799f3a1b994fc397b2fe2da5d1da1626ce15a39f2b1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.37",
|
||||
"syn 2.0.38",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.4.3"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
|
||||
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||
|
||||
[[package]]
|
||||
name = "calloop"
|
||||
@ -292,6 +322,12 @@ version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
|
||||
|
||||
[[package]]
|
||||
name = "constant_time_eq"
|
||||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "21a53c0a4d288377e7415b53dcfc3c04da5cdc2cc95c8d5ac178b58f0b861ad6"
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation"
|
||||
version = "0.9.3"
|
||||
@ -345,6 +381,15 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.3.2"
|
||||
@ -393,6 +438,16 @@ version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ctor"
|
||||
version = "0.2.5"
|
||||
@ -400,7 +455,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37e366bff8cd32dd8754b0991fb66b279dc48f598c3a18914852a6673deef583"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn 2.0.37",
|
||||
"syn 2.0.38",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -409,6 +464,17 @@ version = "0.3.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2696e8a945f658fd14dc3b87242e6b80cd0f36ff04ea560fa39082368847946"
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.10.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
|
||||
dependencies = [
|
||||
"block-buffer",
|
||||
"crypto-common",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dispatch"
|
||||
version = "0.2.0"
|
||||
@ -493,9 +559,9 @@ checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07"
|
||||
|
||||
[[package]]
|
||||
name = "embedded-graphics"
|
||||
version = "0.7.1"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "750082c65094fbcc4baf9ba31583ce9a8bb7f52cadfb96f6164b1bc7f922f32b"
|
||||
checksum = "0649998afacf6d575d126d83e68b78c0ab0e00ca2ac7e9b3db11b4cbe8274ef0"
|
||||
dependencies = [
|
||||
"az",
|
||||
"byteorder",
|
||||
@ -506,9 +572,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "embedded-graphics-core"
|
||||
version = "0.3.3"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8b1239db5f3eeb7e33e35bd10bd014e7b2537b17e071f726a09351431337cfa"
|
||||
checksum = "ba9ecd261f991856250d2207f6d8376946cd9f412a2165d3b75bc87a0bc7a044"
|
||||
dependencies = [
|
||||
"az",
|
||||
"byteorder",
|
||||
@ -526,10 +592,17 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "embedded-hal"
|
||||
version = "1.0.0-alpha.5"
|
||||
version = "1.0.0-alpha.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a554c04648665230499563ccdfd1fcd719ef2d5a0af54bdc81c5d877fb556db4"
|
||||
checksum = "129b101ddfee640565f7c07b301a31d95aa21e5acef21a491c307139f5fa4c91"
|
||||
|
||||
[[package]]
|
||||
name = "embedded-hal-nb"
|
||||
version = "1.0.0-alpha.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7e0760ec0a3bf76859d5e33f39542af103f157d5b2ecfb00ace56dd461472e3a"
|
||||
dependencies = [
|
||||
"embedded-hal 1.0.0-alpha.9",
|
||||
"nb 1.1.0",
|
||||
]
|
||||
|
||||
@ -594,9 +667,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "float-cmp"
|
||||
version = "0.8.0"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e1267f4ac4f343772758f7b1bdcbe767c218bbab93bb432acbf5162bbf85a6c4"
|
||||
checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
@ -637,7 +710,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.37",
|
||||
"syn 2.0.38",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -661,6 +734,16 @@ dependencies = [
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.14.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
|
||||
dependencies = [
|
||||
"typenum",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gethostname"
|
||||
version = "0.3.0"
|
||||
@ -767,6 +850,15 @@ version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7"
|
||||
|
||||
[[package]]
|
||||
name = "hmac"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
|
||||
dependencies = [
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "human-sort"
|
||||
version = "0.2.2"
|
||||
@ -893,9 +985,9 @@ checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130"
|
||||
|
||||
[[package]]
|
||||
name = "jobserver"
|
||||
version = "0.1.26"
|
||||
version = "0.1.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "936cfd212a0155903bcbc060e316fb6cc7cbf2e1907329391ebadc1fe0ce77c2"
|
||||
checksum = "8c37f63953c4c63420ed5fd3d6d398c719489b9f872b9fa683262f8edd363c7d"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
@ -932,9 +1024,9 @@ checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.148"
|
||||
version = "0.2.149"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b"
|
||||
checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b"
|
||||
|
||||
[[package]]
|
||||
name = "libloading"
|
||||
@ -1011,9 +1103,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.6.3"
|
||||
version = "2.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c"
|
||||
checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167"
|
||||
|
||||
[[package]]
|
||||
name = "memmap2"
|
||||
@ -1062,9 +1154,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "micromath"
|
||||
version = "1.1.1"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bc4010833aea396656c2f91ee704d51a6f1329ec2ab56ffd00bfd56f7481ea94"
|
||||
checksum = "39617bc909d64b068dcffd0e3e31679195b5576d0c83fadc52690268cc2b2b55"
|
||||
|
||||
[[package]]
|
||||
name = "minimal-lexical"
|
||||
@ -1228,9 +1320,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.16"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2"
|
||||
checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
@ -1274,7 +1366,7 @@ dependencies = [
|
||||
"proc-macro-crate",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.37",
|
||||
"syn 2.0.38",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1432,9 +1524,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.67"
|
||||
version = "1.0.69"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328"
|
||||
checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
@ -1494,6 +1586,7 @@ dependencies = [
|
||||
name = "raspi-oled"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"andotp-import",
|
||||
"display-interface-spi",
|
||||
"embedded-graphics",
|
||||
"embedded-hal 0.2.7",
|
||||
@ -1502,7 +1595,8 @@ dependencies = [
|
||||
"libc",
|
||||
"linux-embedded-hal",
|
||||
"rand_xoshiro",
|
||||
"rppal 0.13.1",
|
||||
"rpassword",
|
||||
"rppal",
|
||||
"rusqlite",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
@ -1511,6 +1605,7 @@ dependencies = [
|
||||
"ssd1351",
|
||||
"time",
|
||||
"time-tz",
|
||||
"totp-rs",
|
||||
"ureq",
|
||||
"winit",
|
||||
]
|
||||
@ -1552,9 +1647,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.9.5"
|
||||
version = "1.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "697061221ea1b4a94a624f67d0ae2bfe4e22b8a17b6a192afb11046542cc8c47"
|
||||
checksum = "d119d7c7ca818f8a53c300863d4f87566aac09943aef5b355bb83969dae75d87"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
@ -1564,9 +1659,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.3.8"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2f401f4955220693b56f8ec66ee9c78abffd8d1c4f23dc41a23839eb88f0795"
|
||||
checksum = "465c6fc0621e4abc4187a2bda0937bfd4f722c2730b29562e19689ea796c9a4b"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
@ -1575,38 +1670,60 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.7.5"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da"
|
||||
checksum = "56d84fdd47036b038fc80dd333d10b6aab10d5d31f4a366e20014def75328d33"
|
||||
|
||||
[[package]]
|
||||
name = "rppal"
|
||||
version = "0.12.0"
|
||||
name = "ring"
|
||||
version = "0.17.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eb26758c881b4837b2f4aef569e4251f75388e36b37204e1804ef429c220121c"
|
||||
checksum = "9babe80d5c16becf6594aa32ad2be8fe08498e7ae60b77de8df700e67f191d7e"
|
||||
dependencies = [
|
||||
"embedded-hal 0.2.7",
|
||||
"lazy_static",
|
||||
"cc",
|
||||
"getrandom",
|
||||
"libc",
|
||||
"nb 0.1.3",
|
||||
"void",
|
||||
"spin",
|
||||
"untrusted",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rpassword"
|
||||
version = "7.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6678cf63ab3491898c0d021b493c94c9b221d91295294a2a5746eacbe5928322"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"rtoolbox",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rppal"
|
||||
version = "0.13.1"
|
||||
version = "0.14.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c88c9c6248de4d337747b619d8f671055ef48a87dc21b97998833f189a0bbd4f"
|
||||
checksum = "612e1a22e21f08a246657c6433fe52b773ae43d07c9ef88ccfc433cc8683caba"
|
||||
dependencies = [
|
||||
"embedded-hal 0.2.7",
|
||||
"embedded-hal 1.0.0-alpha.5",
|
||||
"lazy_static",
|
||||
"embedded-hal 1.0.0-alpha.9",
|
||||
"embedded-hal-nb",
|
||||
"libc",
|
||||
"nb 0.1.3",
|
||||
"spin_sleep",
|
||||
"void",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rtoolbox"
|
||||
version = "0.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "034e22c514f5c0cb8a10ff341b9b048b5ceb21591f31c8f44c43b960f9b3524a"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rusqlite"
|
||||
version = "0.27.0"
|
||||
@ -1682,7 +1799,7 @@ checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.37",
|
||||
"syn 2.0.38",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1717,6 +1834,28 @@ dependencies = [
|
||||
"termios",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha1"
|
||||
version = "0.10.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha2"
|
||||
version = "0.10.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "simd-adler32"
|
||||
version = "0.3.7"
|
||||
@ -1838,7 +1977,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "ssd1351"
|
||||
version = "0.3.0"
|
||||
source = "git+https://github.com/FliegendeWurst/ssd1351-rust#9192fde637a2cc672e27ed1dc324f82e6bb65f72"
|
||||
source = "git+https://github.com/FliegendeWurst/ssd1351-rust?rev=3de5be50bd9a59391c669aec8357923a56d121f6#3de5be50bd9a59391c669aec8357923a56d121f6"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"display-interface",
|
||||
@ -1846,7 +1985,7 @@ dependencies = [
|
||||
"embedded-graphics",
|
||||
"embedded-hal 0.2.7",
|
||||
"linux-embedded-hal",
|
||||
"rppal 0.12.0",
|
||||
"rppal",
|
||||
"simple-signal",
|
||||
]
|
||||
|
||||
@ -1856,6 +1995,12 @@ version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731"
|
||||
|
||||
[[package]]
|
||||
name = "subtle"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.109"
|
||||
@ -1869,9 +2014,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.37"
|
||||
version = "2.0.38"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7303ef2c05cd654186cb250d29049a24840ca25d2747c25c0381c8d9e2f582e8"
|
||||
checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@ -1898,22 +2043,22 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.48"
|
||||
version = "1.0.49"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d6d7a740b8a666a7e828dd00da9c0dc290dff53154ea77ac109281de90589b7"
|
||||
checksum = "1177e8c6d7ede7afde3585fd2513e611227efd6481bd78d2e82ba1ce16557ed4"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.48"
|
||||
version = "1.0.49"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49922ecae66cc8a249b77e68d1d0623c1b2c514f0060c27cdc68bd62a1219d35"
|
||||
checksum = "10712f02019e9288794769fba95cd6847df9874d49d871d062172f9dd41bc4cc"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.37",
|
||||
"syn 2.0.38",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1934,6 +2079,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "426f806f4089c493dcac0d24c29c01e2c38baf8e30f1b716ee37e83d200b18fe"
|
||||
dependencies = [
|
||||
"deranged",
|
||||
"js-sys",
|
||||
"serde",
|
||||
"time-core",
|
||||
"time-macros",
|
||||
@ -1956,9 +2102,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "time-tz"
|
||||
version = "1.0.3"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a422f65dfdf08a81317d54fa00b45dc58cbccab69be78c1447391cc39ae8c9d4"
|
||||
checksum = "733bc522e97980eb421cbf381160ff225bd14262a48a739110f6653c6258d625"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"parse-zoneinfo",
|
||||
@ -1967,6 +2113,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde-xml-rs",
|
||||
"time",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2038,6 +2185,19 @@ dependencies = [
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "totp-rs"
|
||||
version = "5.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df3504f96adf86d28e7eb16fa236a7951ec72c15ee100d1b5318e225944bc8cb"
|
||||
dependencies = [
|
||||
"base32",
|
||||
"constant_time_eq",
|
||||
"hmac",
|
||||
"sha1",
|
||||
"sha2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing"
|
||||
version = "0.1.37"
|
||||
@ -2061,6 +2221,12 @@ version = "0.19.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49d64318d8311fc2668e48b63969f4343e0a85c4a109aa8460d6672e364b8bd1"
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-bidi"
|
||||
version = "0.3.13"
|
||||
@ -2083,10 +2249,16 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ureq"
|
||||
version = "2.7.1"
|
||||
name = "untrusted"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b11c96ac7ee530603dcdf68ed1557050f374ce55a5a07193ebf8cbc9f8927e9"
|
||||
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
|
||||
|
||||
[[package]]
|
||||
name = "ureq"
|
||||
version = "2.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f5ccd538d4a604753ebc2f17cd9946e89b77bf87f6a8e2309667c6f2e87855e3"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"log",
|
||||
@ -2156,7 +2328,7 @@ dependencies = [
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.37",
|
||||
"syn 2.0.38",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
@ -2178,7 +2350,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.37",
|
||||
"syn 2.0.38",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
@ -2537,9 +2709,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.5.15"
|
||||
version = "0.5.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7c2e3184b9c4e92ad5167ca73039d0c42476302ab603e2fec4487511f38ccefc"
|
||||
checksum = "037711d82167854aff2018dfd193aa0fef5370f456732f0d5a0c59b0f1b4b907"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
15
Cargo.toml
15
Cargo.toml
@ -7,29 +7,32 @@ edition = "2018"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
embedded-graphics = "0.7.1"
|
||||
embedded-graphics = "0.8.1"
|
||||
linux-embedded-hal = "0.3.0"
|
||||
embedded-hal = "0.2.5"
|
||||
libc = "0.2.98"
|
||||
rusqlite = "0.27.0"
|
||||
time = { version = "0.3.9", features = ["parsing"] }
|
||||
time-tz = "1.0.1"
|
||||
image = "0.24.1"
|
||||
time-tz = "2"
|
||||
image = { version = "0.24.1", optional = true }
|
||||
serde_json = "1.0.79"
|
||||
serde_derive = "1.0.136"
|
||||
serde = "1.0.136"
|
||||
rppal = { version = "0.13.1", features = ["hal"] }
|
||||
ssd1351 = { git = "https://github.com/FliegendeWurst/ssd1351-rust" }
|
||||
rppal = { version = "0.14.1", features = ["hal"] }
|
||||
ssd1351 = { git = "https://github.com/FliegendeWurst/ssd1351-rust", rev = "3de5be50bd9a59391c669aec8357923a56d121f6" }
|
||||
display-interface-spi = "0.4.1"
|
||||
ureq = { version = "2.4.0", default-features = false }
|
||||
winit = { version = "0.28.7", optional = true }
|
||||
softbuffer = { version = "0.3.1", optional = true }
|
||||
rand_xoshiro = "0.6.0"
|
||||
gpiocdev = "0.6.0"
|
||||
rpassword = "7.2.0"
|
||||
andotp-import = "0.1.0"
|
||||
totp-rs = "5.4.0"
|
||||
#gpio-am2302-rs = { git = "https://github.com/FliegendeWurst/gpio-am2302-rs" }
|
||||
|
||||
[features]
|
||||
pc = ["winit", "softbuffer"]
|
||||
pc = ["winit", "softbuffer", "image"]
|
||||
default = [ "pc" ]
|
||||
|
||||
[profile.release]
|
||||
|
@ -9,14 +9,14 @@ rustPlatform.buildRustPackage {
|
||||
cargoLock = {
|
||||
lockFile = ./Cargo.lock;
|
||||
outputHashes = {
|
||||
"ssd1351-0.3.0" = "sha256-K6QCU9qPEuU7Ur8W6fTdi4JWk8NsVz3mLfV0afpfdBA=";
|
||||
"ssd1351-0.3.0" = "sha256-DD7+NhYwUwD/xC+7ZUNKdhcfsSCOQ9NVEy9lcS47Q5E=";
|
||||
# "gpio-am2302-rs-1.1.0" = "sha256-tyA/R80LtWIXoVEoxHhkmzy0IsMdMH1Oi3FTQ56XjyQ=";
|
||||
};
|
||||
};
|
||||
|
||||
nativeBuildInputs = [ pkg-config ];
|
||||
|
||||
cargoBuildFlags = [ "--no-default-features" ];
|
||||
cargoBuildFlags = [ "--no-default-features" "--bin" "take_measurement" ];
|
||||
|
||||
buildInputs = [ sqlite ];
|
||||
|
||||
@ -25,7 +25,7 @@ rustPlatform.buildRustPackage {
|
||||
meta = with lib; {
|
||||
description = "OLED display of clock/calendar/temperature";
|
||||
homepage = "https://github.com/FliegendeWurst/raspi-oled";
|
||||
license = licenses.mit;
|
||||
license = licenses.gpl3;
|
||||
maintainers = with maintainers; [ fliegendewurst ];
|
||||
};
|
||||
}
|
||||
|
4
src/bin/action/mod.rs
Normal file
4
src/bin/action/mod.rs
Normal file
@ -0,0 +1,4 @@
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum Action {
|
||||
Screensaver(&'static str),
|
||||
}
|
@ -4,25 +4,25 @@ use linux_embedded_hal::I2cdev;
|
||||
|
||||
const CCS811_ADDR: u8 = 0x5A; // or 0x5B
|
||||
|
||||
const CCS811_STATUS: u8 = 0x00;
|
||||
const CCS811_MEAS_MODE: u8 = 0x01;
|
||||
const CCS811_ALG_RESULT_DATA: u8 = 0x02;
|
||||
const CCS811_RAW_DATA: u8 = 0x03;
|
||||
const CCS811_ENV_DATA: u8 = 0x05;
|
||||
const CCS811_NTC: u8 = 0x06;
|
||||
const CCS811_THRESHOLDS: u8 = 0x10;
|
||||
const CCS811_BASELINE: u8 = 0x11;
|
||||
const CCS811_HW_ID: u8 = 0x20;
|
||||
const CCS811_HW_VERSION: u8 = 0x21;
|
||||
const CCS811_FW_BOOT_VERSION: u8 = 0x23;
|
||||
const CCS811_FW_APP_VERSION: u8 = 0x24;
|
||||
const CCS811_ERROR_ID: u8 = 0xE0;
|
||||
const CCS811_APP_START: u8 = 0xF4;
|
||||
const CCS811_SW_RESET: u8 = 0xFF;
|
||||
pub const CCS811_STATUS: u8 = 0x00;
|
||||
pub const CCS811_MEAS_MODE: u8 = 0x01;
|
||||
pub const CCS811_ALG_RESULT_DATA: u8 = 0x02;
|
||||
pub const CCS811_RAW_DATA: u8 = 0x03;
|
||||
pub const CCS811_ENV_DATA: u8 = 0x05;
|
||||
pub const CCS811_NTC: u8 = 0x06;
|
||||
pub const CCS811_THRESHOLDS: u8 = 0x10;
|
||||
pub const CCS811_BASELINE: u8 = 0x11;
|
||||
pub const CCS811_HW_ID: u8 = 0x20;
|
||||
pub const CCS811_HW_VERSION: u8 = 0x21;
|
||||
pub const CCS811_FW_BOOT_VERSION: u8 = 0x23;
|
||||
pub const CCS811_FW_APP_VERSION: u8 = 0x24;
|
||||
pub const CCS811_ERROR_ID: u8 = 0xE0;
|
||||
pub const CCS811_APP_START: u8 = 0xF4;
|
||||
pub const CCS811_SW_RESET: u8 = 0xFF;
|
||||
|
||||
struct CCS811 {
|
||||
pub struct CCS811 {
|
||||
i2c: I2cdev,
|
||||
addr: u8,
|
||||
pub addr: u8,
|
||||
}
|
||||
|
||||
impl CCS811 {
|
||||
@ -79,7 +79,7 @@ impl CCS811 {
|
||||
}
|
||||
}
|
||||
|
||||
enum CCS811DriveMode {
|
||||
pub enum CCS811DriveMode {
|
||||
Idle = 0,
|
||||
EverySecond = 1,
|
||||
Every10Seconds = 2,
|
@ -15,40 +15,16 @@ use embedded_graphics::{
|
||||
text::{renderer::CharacterStyle, Text},
|
||||
Drawable,
|
||||
};
|
||||
use raspi_oled::FrameOutput;
|
||||
|
||||
use raspi_oled::Events;
|
||||
use rppal::{
|
||||
gpio::Gpio,
|
||||
spi::{Bus, Mode, SlaveSelect, Spi},
|
||||
};
|
||||
use rusqlite::Connection;
|
||||
use serde_derive::Deserialize;
|
||||
//use ssd1306::{I2CDisplayInterface, Ssd1306, size::DisplaySize128x64, rotation::DisplayRotation, mode::DisplayConfig};
|
||||
use time::{format_description, Date, OffsetDateTime, PrimitiveDateTime};
|
||||
use time_tz::{timezones::db::europe::BERLIN, OffsetDateTimeExt, PrimitiveDateTimeExt};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Events {
|
||||
events: Vec<Event>,
|
||||
weekly: Vec<Weekly>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct Event {
|
||||
name: String,
|
||||
start_time: String,
|
||||
end_time: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Weekly {
|
||||
name: String,
|
||||
day: i32,
|
||||
hour: i32,
|
||||
minute: i32,
|
||||
duration: i32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
enum Status {
|
||||
Unknown,
|
||||
@ -187,7 +163,7 @@ where
|
||||
.font(&FONT_6X9)
|
||||
.text_color(Rgb565::new(0xff, 0xff, 0xff))
|
||||
.build();
|
||||
let mut text_style_4x6 = MonoTextStyleBuilder::new()
|
||||
let text_style_4x6 = MonoTextStyleBuilder::new()
|
||||
.font(&FONT_4X6)
|
||||
.text_color(Rgb565::new(0xff, 0xff, 0xff))
|
||||
.build();
|
||||
|
BIN
src/bin/draw/font_15x30.png
Normal file
BIN
src/bin/draw/font_15x30.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 781 B |
BIN
src/bin/draw/font_15x30.raw
Normal file
BIN
src/bin/draw/font_15x30.raw
Normal file
Binary file not shown.
431
src/bin/draw/measurements.rs
Normal file
431
src/bin/draw/measurements.rs
Normal file
@ -0,0 +1,431 @@
|
||||
use std::{any::Any, fs, ops::Sub, sync::atomic::AtomicBool, time::Duration};
|
||||
|
||||
use embedded_graphics::{
|
||||
image::ImageRaw,
|
||||
mono_font::{
|
||||
ascii::{FONT_4X6, FONT_5X8, FONT_6X9, FONT_9X15},
|
||||
mapping::StrGlyphMapping,
|
||||
DecorationDimensions, MonoFont, MonoTextStyleBuilder,
|
||||
},
|
||||
pixelcolor::Rgb565,
|
||||
prelude::*,
|
||||
primitives::{Primitive, PrimitiveStyleBuilder, Rectangle},
|
||||
text::{renderer::CharacterStyle, Text},
|
||||
Drawable,
|
||||
};
|
||||
use raspi_oled::Events;
|
||||
use time::{format_description, Date, OffsetDateTime, PrimitiveDateTime};
|
||||
|
||||
use crate::{screensaver::Screensaver, Context, ContextDefault, Draw, BLACK};
|
||||
use time_tz::{timezones::db::europe::BERLIN, OffsetDateTimeExt, PrimitiveDateTimeExt};
|
||||
|
||||
static CLOCK_FONT: MonoFont = MonoFont {
|
||||
image: ImageRaw::new(include_bytes!("font_15x30.raw"), 165),
|
||||
glyph_mapping: &StrGlyphMapping::new("0123456789:", 0),
|
||||
character_size: Size::new(15, 30),
|
||||
character_spacing: 0,
|
||||
baseline: 22,
|
||||
underline: DecorationDimensions::default_underline(30),
|
||||
strikethrough: DecorationDimensions::default_strikethrough(30),
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Measurements {
|
||||
drawn: AtomicBool,
|
||||
mode: MeasurementsMode,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum MeasurementsMode {
|
||||
Default,
|
||||
Temps,
|
||||
Events,
|
||||
}
|
||||
|
||||
impl Default for Measurements {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
drawn: AtomicBool::new(false),
|
||||
mode: MeasurementsMode::Default,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Measurements {
|
||||
pub fn temps() -> Self {
|
||||
Self {
|
||||
drawn: AtomicBool::new(false),
|
||||
mode: MeasurementsMode::Temps,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn events() -> Self {
|
||||
Self {
|
||||
drawn: AtomicBool::new(false),
|
||||
mode: MeasurementsMode::Events,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<D: DrawTarget<Color = Rgb565>> Screensaver<D> for Measurements {
|
||||
fn id(&self) -> &'static str {
|
||||
match self.mode {
|
||||
MeasurementsMode::Default => "measurements",
|
||||
MeasurementsMode::Temps => "measurements_temps",
|
||||
MeasurementsMode::Events => "measurements_events",
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_draw(&self) -> Box<dyn Draw<D>> {
|
||||
Box::new(Measurements {
|
||||
drawn: AtomicBool::new(false),
|
||||
mode: self.mode,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<D: DrawTarget<Color = Rgb565>> Draw<D> for Measurements {
|
||||
fn draw_with_ctx(&self, ctx: &ContextDefault<D>, disp: &mut D, _rng: &mut crate::Rng) -> Result<bool, D::Error> {
|
||||
if self.drawn.load(std::sync::atomic::Ordering::Relaxed) {
|
||||
return Ok(false);
|
||||
}
|
||||
disp.clear(BLACK)?;
|
||||
let events = fs::read_to_string("events.json").expect("failed to read events.json");
|
||||
let events: Events = serde_json::from_str(&events).unwrap();
|
||||
let database = ctx.database();
|
||||
let database = database.borrow_mut();
|
||||
|
||||
let (rh, temp): (i64, i64) = database
|
||||
.query_row(
|
||||
"SELECT humidity, celsius FROM sensor_readings ORDER BY sensor_readings.time DESC LIMIT 1",
|
||||
[],
|
||||
|row| Ok((row.get(0).unwrap(), row.get(1).unwrap())),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let time = OffsetDateTime::now_utc().to_timezone(BERLIN);
|
||||
|
||||
let mut query = database
|
||||
.prepare("SELECT celsius FROM sensor_readings ORDER BY sensor_readings.time DESC LIMIT 288")
|
||||
.unwrap();
|
||||
let mut temps: Vec<i32> = query
|
||||
.query_map([], |r| Ok(r.get(0)))
|
||||
.unwrap()
|
||||
.map(Result::unwrap)
|
||||
.map(Result::unwrap)
|
||||
.collect();
|
||||
let mut global_min = 1000;
|
||||
let mut global_max = 0;
|
||||
let mut vals: Vec<(i32, i32)> = vec![];
|
||||
for hour in temps.chunks_mut(6) {
|
||||
hour.sort();
|
||||
let mut min = hour[1];
|
||||
let mut max = hour[hour.len() - 2];
|
||||
//println!("min {} max {}", min, max);
|
||||
// sanity check value
|
||||
if max > 400 {
|
||||
if vals.is_empty() {
|
||||
max = min;
|
||||
} else {
|
||||
max = vals.last().unwrap().1;
|
||||
}
|
||||
min = min.min(max);
|
||||
}
|
||||
|
||||
global_min = min.min(global_min);
|
||||
global_max = max.max(global_max);
|
||||
vals.push((min, max));
|
||||
}
|
||||
|
||||
let hour = time.hour();
|
||||
let minute = time.minute();
|
||||
|
||||
let text_style_clock = MonoTextStyleBuilder::new()
|
||||
.font(&CLOCK_FONT)
|
||||
.text_color(Rgb565::new(0xff, 0xff, 0xff))
|
||||
.build();
|
||||
let text_style2 = MonoTextStyleBuilder::new()
|
||||
.font(&FONT_9X15)
|
||||
.text_color(Rgb565::new(0xff, 0xff, 0xff))
|
||||
.build();
|
||||
let mut text_style_6x9 = MonoTextStyleBuilder::new()
|
||||
.font(&FONT_6X9)
|
||||
.text_color(Rgb565::new(0xff, 0xff, 0xff))
|
||||
.build();
|
||||
let text_style_4x6 = MonoTextStyleBuilder::new()
|
||||
.font(&FONT_4X6)
|
||||
.text_color(Rgb565::new(0xff, 0xff, 0xff))
|
||||
.build();
|
||||
let text_style4 = MonoTextStyleBuilder::new()
|
||||
.font(&FONT_5X8)
|
||||
.text_color(Rgb565::new(0xff, 0xff, 0xff))
|
||||
.build();
|
||||
let rect_style = PrimitiveStyleBuilder::new()
|
||||
.fill_color(Rgb565::new(0xff, 0xff, 0xff))
|
||||
.build();
|
||||
|
||||
//let text = format!("{}.{}% {}.{}°C", rh / 10, rh % 10, temp / 10, temp % 10);
|
||||
//Text::new(&text, Point::new(0, 10), text_style).draw(disp).unwrap();
|
||||
let hour = format!("{:02}", hour);
|
||||
Text::new(&hour, Point::new(64 - 2, 6 + 20), text_style_clock).draw(disp)?;
|
||||
let minute = format!("{:02}", minute);
|
||||
Text::new(&minute, Point::new(64 + 30 + 4, 6 + 20), text_style_clock).draw(disp)?;
|
||||
Rectangle::new((93, 14).into(), (4, 4).into())
|
||||
.into_styled(rect_style)
|
||||
.draw(disp)?;
|
||||
Rectangle::new((93, 22).into(), (4, 4).into())
|
||||
.into_styled(rect_style)
|
||||
.draw(disp)?;
|
||||
|
||||
let rh = format!("{:02}", rh / 10);
|
||||
Text::new(&rh, Point::new(64 + 3, 64 - 4), text_style2).draw(disp)?;
|
||||
Text::new("%", Point::new(64 + 3 + 18, 64 - 4), text_style_6x9).draw(disp)?;
|
||||
let temp_int = format!("{:02}", temp / 10);
|
||||
Text::new(&temp_int, Point::new(64 + 32 + 3, 64 - 4), text_style2).draw(disp)?;
|
||||
Rectangle::new((64 + 32 + 3 + 18, 64 - 4).into(), (1, 1).into())
|
||||
.into_styled(rect_style)
|
||||
.draw(disp)?;
|
||||
let temp_fract = format!("{}", temp % 10);
|
||||
Text::new(&temp_fract, Point::new(64 + 32 + 3 + 18 + 2, 64 - 4), text_style_6x9).draw(disp)?;
|
||||
|
||||
for (x, y) in [
|
||||
(118, 49),
|
||||
(119, 49),
|
||||
(117, 50),
|
||||
(117, 51),
|
||||
(120, 50),
|
||||
(120, 51),
|
||||
(118, 52),
|
||||
(119, 52),
|
||||
(122, 50),
|
||||
(122, 51),
|
||||
(122, 52),
|
||||
(123, 49),
|
||||
(124, 49),
|
||||
(123, 53),
|
||||
(124, 53),
|
||||
] {
|
||||
Rectangle::new((x, y).into(), (1, 1).into())
|
||||
.into_styled(rect_style)
|
||||
.draw(disp)?;
|
||||
}
|
||||
|
||||
let x = 0;
|
||||
let y = 0;
|
||||
Rectangle::new((x + 2, y + 8).into(), (1, 24).into())
|
||||
.into_styled(rect_style)
|
||||
.draw(disp)?;
|
||||
Rectangle::new((x + 1, y + 16).into(), (1, 1).into())
|
||||
.into_styled(rect_style)
|
||||
.draw(disp)?;
|
||||
Rectangle::new((x + 1, y + 28).into(), (1, 1).into())
|
||||
.into_styled(rect_style)
|
||||
.draw(disp)?;
|
||||
let mut day = time.weekday().number_days_from_monday() as usize;
|
||||
let days = [
|
||||
("M", "o"),
|
||||
("D", "i"),
|
||||
("M", "i"),
|
||||
("D", "o"),
|
||||
("F", "r"),
|
||||
("S", "a"),
|
||||
("S", "o"),
|
||||
];
|
||||
for i in 0..5 {
|
||||
Text::new(days[day].0, (x + 12 * i + 4, y + 6).into(), text_style_6x9).draw(disp)?;
|
||||
Text::new(days[day].1, (x + 12 * i + 10, y + 6).into(), text_style4).draw(disp)?;
|
||||
day += 1;
|
||||
day %= days.len();
|
||||
}
|
||||
let mut bits = vec![];
|
||||
// events
|
||||
let mut all_events = vec![];
|
||||
for event in events.weekly {
|
||||
let mut event_time = time.clone();
|
||||
while event_time.weekday().number_days_from_monday() as i32 != event.day {
|
||||
event_time += Duration::from_secs(24 * 60 * 60);
|
||||
}
|
||||
all_events.push((
|
||||
event.day,
|
||||
event.hour,
|
||||
event.minute,
|
||||
event.duration,
|
||||
event.name,
|
||||
event_time.to_julian_day(),
|
||||
));
|
||||
}
|
||||
let format = format_description::parse("[year]-[month]-[day]T[hour]:[minute]:[second]").unwrap();
|
||||
for event in events.events {
|
||||
let dt = PrimitiveDateTime::parse(&event.start_time, &format)
|
||||
.unwrap()
|
||||
.assume_timezone(BERLIN)
|
||||
.unwrap();
|
||||
let julian_day = dt.to_julian_day();
|
||||
if dt < time {
|
||||
continue;
|
||||
}
|
||||
let duration = if let Some(end_time) = event.end_time.as_ref() {
|
||||
let dt2 = PrimitiveDateTime::parse(end_time, &format)
|
||||
.unwrap()
|
||||
.assume_timezone(BERLIN)
|
||||
.unwrap();
|
||||
(dt2.sub(dt).as_seconds_f32() / 60.0) as i32
|
||||
} else {
|
||||
30
|
||||
};
|
||||
all_events.push((
|
||||
dt.weekday().number_days_from_monday() as _,
|
||||
dt.hour() as _,
|
||||
dt.minute() as _,
|
||||
duration,
|
||||
event.name,
|
||||
julian_day,
|
||||
));
|
||||
}
|
||||
let today = time.date().to_julian_day();
|
||||
let weekday = time.weekday().number_days_from_monday() as i32;
|
||||
all_events.sort_by_key(|x| (x.5, ((x.0 + 7) - weekday) % 7, x.1, x.2));
|
||||
//println!("{:?}", all_events);
|
||||
let mut time_until_first = None;
|
||||
let colors = vec![
|
||||
Rgb565::new(0xff >> 3, 0xff >> 2, 0x00 >> 3),
|
||||
Rgb565::new(0xff >> 3, 0x00 >> 2, 0xff >> 3),
|
||||
Rgb565::new(0x00 >> 3, 0xff >> 2, 0xff >> 3),
|
||||
Rgb565::new(0xff >> 3, 0x00 >> 2, 0x00 >> 3),
|
||||
Rgb565::new(0x00 >> 3, 0xff >> 2, 0x00 >> 3),
|
||||
Rgb565::new(0x00 >> 3, 0x00 >> 2, 0xff >> 3),
|
||||
Rgb565::new(0xff >> 3, 0xff >> 2, 0xff >> 3),
|
||||
];
|
||||
for i in 0..5 {
|
||||
let day = (weekday + i) % 7;
|
||||
for hour in 0..24 {
|
||||
for minute in 0..60 {
|
||||
if minute % 6 != 0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
if i == 0 && hour == time.hour() as i32 && minute == (time.minute() as i32 / 6) * 6 {
|
||||
bits.push((i, hour, minute / 6, Some(Rgb565::new(0xff, 0x00, 0xff))));
|
||||
}
|
||||
|
||||
for (event_idx, event) in all_events.iter().enumerate() {
|
||||
if event.0 != day || event.5 < today || event.5 - today > 4 {
|
||||
continue;
|
||||
}
|
||||
let event_start = event.1 * 60 + event.2;
|
||||
let event_end = event_start + event.3;
|
||||
let now = hour * 60 + minute;
|
||||
let now2 = hour * 60 + minute + 6;
|
||||
if now2 > event_start && now < event_end {
|
||||
bits.push((i, hour, minute / 6, colors.get(event_idx).copied()));
|
||||
}
|
||||
if time_until_first.is_none()
|
||||
&& (i > 0
|
||||
|| event.1 > time.hour() as i32 || (event.1 == time.hour() as i32
|
||||
&& event.2 >= time.minute() as i32))
|
||||
{
|
||||
time_until_first = Some(
|
||||
((i * 24 + event.1) * 60 + event.2) * 60
|
||||
- (time.hour() as i32 * 60 + time.minute() as i32) * 60,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (d, h, m, color) in bits {
|
||||
// calculate position
|
||||
let x = x + 4 + d * 12 + m;
|
||||
let y = y + 8 + h;
|
||||
disp.fill_solid(
|
||||
&Rectangle::new((x, y).into(), (1, 1).into()),
|
||||
color.unwrap_or(Rgb565::new(0xff, 0xff, 0x10)),
|
||||
)?;
|
||||
//Rectangle::new((x, y).into(), (1, 1).into()).into_styled(rect_style).draw(disp).unwrap();
|
||||
}
|
||||
if self.mode == MeasurementsMode::Events {
|
||||
for (i, event) in all_events.iter().take(7).enumerate() {
|
||||
let text = if event.4.len() > 19 {
|
||||
&event.4[0..event.4.floor_char_boundary(19)]
|
||||
} else {
|
||||
&event.4
|
||||
};
|
||||
let day = event.0 as usize;
|
||||
let y = y + 64 + 9 * i as i32 + 5;
|
||||
if event.5 > today && event.5 - today > 7 {
|
||||
let dt = Date::from_julian_day(event.5).unwrap();
|
||||
Text::new(
|
||||
&format!("{}.{}.", dt.day(), dt.month() as u8),
|
||||
(0, y).into(),
|
||||
text_style_4x6,
|
||||
)
|
||||
.draw(disp)?;
|
||||
} else {
|
||||
text_style_6x9.set_text_color(Some(Rgb565::new(0xff, 0xff, 0xff)));
|
||||
Text::new(days[day].0, (x, y).into(), text_style_6x9).draw(disp)?;
|
||||
Text::new(days[day].1, (x + 6, y).into(), text_style4).draw(disp)?;
|
||||
}
|
||||
text_style_6x9.set_text_color(Some(colors[i]));
|
||||
Text::new(text, (x + 14, y).into(), text_style_6x9).draw(disp)?;
|
||||
}
|
||||
} else if self.mode == MeasurementsMode::Temps {
|
||||
let diff = global_max - global_min;
|
||||
let x = 0;
|
||||
let y = 64;
|
||||
let scaley = 63;
|
||||
let scalex = 2;
|
||||
vals.reverse();
|
||||
for (i, (a, b)) in vals.into_iter().enumerate() {
|
||||
let x = x + i as i32 * scalex;
|
||||
let y1 = y + (global_max - b) * scaley / diff;
|
||||
let y2 = y + (global_max - a) * scaley / diff;
|
||||
let height = y2 - y1 + 1;
|
||||
let rect = Rectangle::new((x, y1).into(), (scalex as u32, height as u32).into());
|
||||
disp.fill_solid(&rect, Rgb565::new(0xff, 0xff, 0xff))?;
|
||||
}
|
||||
Text::new(
|
||||
&format!("{}", global_max as f32 / 10.0),
|
||||
(100, 64 + 10).into(),
|
||||
text_style_6x9,
|
||||
)
|
||||
.draw(disp)?;
|
||||
Text::new(
|
||||
&format!("{}", global_min as f32 / 10.0),
|
||||
(100, 64 + 50).into(),
|
||||
text_style_6x9,
|
||||
)
|
||||
.draw(disp)?;
|
||||
}
|
||||
if let Some(secs) = time_until_first {
|
||||
let days = secs / (24 * 60 * 60);
|
||||
let hours = secs / (60 * 60) % 24;
|
||||
let minutes = secs / 60 % 60;
|
||||
let text = if days > 0 {
|
||||
String::new()
|
||||
} else if hours > 0 {
|
||||
format!("{}h{}m", hours, minutes)
|
||||
} else if minutes > 0 {
|
||||
format!("{}m", minutes)
|
||||
} else {
|
||||
"?".into()
|
||||
};
|
||||
Text::new(&text, (x + 2, y + 60).into(), text_style2).draw(disp)?;
|
||||
}
|
||||
|
||||
self.drawn.store(true, std::sync::atomic::Ordering::Relaxed);
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
fn draw(&self, _disp: &mut D, _rng: &mut crate::Rng) -> Result<bool, <D as DrawTarget>::Error> {
|
||||
panic!("draw without ctx");
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn as_any_mut(&mut self) -> &mut dyn Any {
|
||||
self
|
||||
}
|
||||
}
|
4
src/bin/draw/mod.rs
Normal file
4
src/bin/draw/mod.rs
Normal file
@ -0,0 +1,4 @@
|
||||
mod measurements;
|
||||
pub use measurements::Measurements;
|
||||
mod totp;
|
||||
pub use totp::Totp;
|
114
src/bin/draw/totp.rs
Normal file
114
src/bin/draw/totp.rs
Normal file
@ -0,0 +1,114 @@
|
||||
use std::{any::Any, cell::RefCell};
|
||||
|
||||
use andotp_import::Account;
|
||||
use embedded_graphics::{
|
||||
mono_font::{
|
||||
ascii::{FONT_10X20, FONT_9X15},
|
||||
MonoTextStyleBuilder,
|
||||
},
|
||||
pixelcolor::Rgb565,
|
||||
prelude::*,
|
||||
text::Text,
|
||||
Drawable,
|
||||
};
|
||||
use totp_rs::TOTP;
|
||||
|
||||
use crate::{screensaver::Screensaver, Draw, BLACK};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Totp {
|
||||
codes: RefCell<Vec<String>>,
|
||||
secrets: Vec<(Account, TOTP)>,
|
||||
page: usize,
|
||||
}
|
||||
|
||||
impl Totp {
|
||||
pub fn new(secrets: Vec<(Account, TOTP)>) -> Self {
|
||||
Self {
|
||||
codes: RefCell::new(vec![]),
|
||||
secrets,
|
||||
page: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn next_page(&mut self) {
|
||||
self.page += 1;
|
||||
if self.secrets.len() < self.page * 6 {
|
||||
self.page = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<D: DrawTarget<Color = Rgb565>> Screensaver<D> for Totp {
|
||||
fn id(&self) -> &'static str {
|
||||
"totp"
|
||||
}
|
||||
|
||||
fn convert_draw(&self) -> Box<dyn Draw<D>> {
|
||||
Box::new(Totp {
|
||||
codes: RefCell::new(vec![]),
|
||||
secrets: self.secrets.clone(),
|
||||
page: 0,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<D: DrawTarget<Color = Rgb565>> Draw<D> for Totp {
|
||||
fn draw(&self, disp: &mut D, _rng: &mut crate::Rng) -> Result<bool, <D as DrawTarget>::Error> {
|
||||
let codes: Vec<_> = self
|
||||
.secrets
|
||||
.iter()
|
||||
.skip(self.page * 6)
|
||||
.take(6)
|
||||
.map(|x| (&x.0.issuer, &x.0.label, x.1.generate_current().unwrap()))
|
||||
.collect();
|
||||
if codes.len() == self.codes.borrow().len()
|
||||
&& codes.iter().zip(self.codes.borrow().iter()).all(|(x, y)| &x.2 == y)
|
||||
{
|
||||
return Ok(false);
|
||||
}
|
||||
*self.codes.borrow_mut() = codes.iter().map(|x| x.2.clone()).collect();
|
||||
disp.clear(BLACK)?;
|
||||
let mut y = 16;
|
||||
|
||||
let text_style_code = MonoTextStyleBuilder::new()
|
||||
.font(&FONT_10X20)
|
||||
.text_color(Rgb565::new(0xff, 0xff, 0xff))
|
||||
.build();
|
||||
let text_style_label = MonoTextStyleBuilder::new()
|
||||
.font(&FONT_9X15)
|
||||
.text_color(Rgb565::new(0xff, 0xff, 0xff))
|
||||
.build();
|
||||
|
||||
for (issuer, label, code) in codes {
|
||||
Text::new(&code, Point::new(0, y), text_style_code).draw(disp)?;
|
||||
let label_text = if issuer != "" {
|
||||
issuer
|
||||
} else if let Some((issuer, _label)) = label.split_once(" - ") {
|
||||
issuer
|
||||
} else {
|
||||
label
|
||||
};
|
||||
Text::new(
|
||||
if label_text.len() > 7 {
|
||||
&label_text[0..7]
|
||||
} else {
|
||||
label_text
|
||||
},
|
||||
Point::new(60, y),
|
||||
text_style_label,
|
||||
)
|
||||
.draw(disp)?;
|
||||
y += 20 + 1;
|
||||
}
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn as_any_mut(&mut self) -> &mut dyn Any {
|
||||
self
|
||||
}
|
||||
}
|
@ -1,32 +1,42 @@
|
||||
#![feature(array_windows, round_char_boundary)]
|
||||
|
||||
use std::{
|
||||
thread::sleep_ms,
|
||||
any::Any,
|
||||
cell::RefCell,
|
||||
env,
|
||||
rc::Rc,
|
||||
thread,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use action::Action;
|
||||
use display_interface_spi::SPIInterfaceNoCS;
|
||||
use embedded_graphics::{
|
||||
mono_font::{ascii::FONT_10X20, MonoTextStyleBuilder},
|
||||
pixelcolor::Rgb565,
|
||||
prelude::{DrawTarget, Point, Size},
|
||||
text::Text,
|
||||
Drawable,
|
||||
};
|
||||
use draw::Totp;
|
||||
use embedded_graphics::{pixelcolor::Rgb565, prelude::DrawTarget};
|
||||
use gpiocdev::line::{Bias, EdgeDetection, Value};
|
||||
use raspi_oled::FrameOutput;
|
||||
use rand_xoshiro::{rand_core::SeedableRng, Xoroshiro128StarStar};
|
||||
use raspi_oled::{disable_pwm, enable_pwm, PWM_ON};
|
||||
use rppal::{
|
||||
gpio::{Gpio, OutputPin},
|
||||
hal::Delay,
|
||||
spi::{Bus, Mode, SlaveSelect, Spi},
|
||||
};
|
||||
use rusqlite::Connection;
|
||||
use schedule::Schedule;
|
||||
use screensaver::{Screensaver, TimeDisplay};
|
||||
use ssd1351::display::display::Ssd1351;
|
||||
use time::OffsetDateTime;
|
||||
use time_tz::{timezones::db::europe::BERLIN, OffsetDateTimeExt};
|
||||
|
||||
static STAR: &'static [u8] = include_bytes!("../star.raw");
|
||||
static RPI: &'static [u8] = include_bytes!("../rpi.raw");
|
||||
mod action;
|
||||
mod draw;
|
||||
mod schedule;
|
||||
mod screensaver;
|
||||
|
||||
pub type Oled = Ssd1351<SPIInterfaceNoCS<Spi, OutputPin>>;
|
||||
pub type Rng = Xoroshiro128StarStar;
|
||||
|
||||
static BLACK: Rgb565 = Rgb565::new(0, 0, 0);
|
||||
static TIME_COLOR: Rgb565 = Rgb565::new(0b01_111, 0b011_111, 0b01_111);
|
||||
|
||||
fn main() {
|
||||
if rppal::system::DeviceInfo::new().is_ok() {
|
||||
@ -36,6 +46,100 @@ fn main() {
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Context {
|
||||
fn do_action(&self, action: Action);
|
||||
|
||||
fn active_count(&self) -> usize;
|
||||
|
||||
fn database(&self) -> Rc<RefCell<Connection>>;
|
||||
|
||||
fn enable_pwm(&self);
|
||||
}
|
||||
|
||||
pub struct ContextDefault<D: DrawTarget<Color = Rgb565>> {
|
||||
screensavers: Vec<Box<dyn Screensaver<D>>>,
|
||||
scheduled: Vec<Box<dyn Schedule>>,
|
||||
active: RefCell<Vec<Box<dyn Draw<D>>>>,
|
||||
database: Rc<RefCell<Connection>>,
|
||||
}
|
||||
|
||||
impl<D: DrawTarget<Color = Rgb565>> ContextDefault<D> {
|
||||
fn new() -> Self {
|
||||
let mut screensavers = screensaver::screensavers();
|
||||
screensavers.push(Box::new(draw::Measurements::default()));
|
||||
screensavers.push(Box::new(draw::Measurements::temps()));
|
||||
screensavers.push(Box::new(draw::Measurements::events()));
|
||||
let database = Connection::open("sensors.db").expect("failed to open database");
|
||||
ContextDefault {
|
||||
database: Rc::new(RefCell::new(database)),
|
||||
screensavers,
|
||||
scheduled: schedule::reminders(),
|
||||
active: RefCell::new(vec![Box::new(TimeDisplay::new())]),
|
||||
}
|
||||
}
|
||||
|
||||
fn add(&mut self, totp: Totp) {
|
||||
self.screensavers.push(Box::new(totp));
|
||||
}
|
||||
|
||||
fn loop_iter(&mut self, disp: &mut D, rng: &mut Rng) -> bool {
|
||||
let time = OffsetDateTime::now_utc().to_timezone(BERLIN);
|
||||
// check schedules
|
||||
for s in &self.scheduled {
|
||||
s.check_and_do(&*self, time);
|
||||
}
|
||||
let active = self.active.borrow();
|
||||
if active.is_empty() {
|
||||
return false;
|
||||
}
|
||||
let a = active.last().unwrap();
|
||||
if !a.expired() {
|
||||
return a.draw_with_ctx(self, disp, rng).unwrap_or(true);
|
||||
}
|
||||
drop(active);
|
||||
self.active.borrow_mut().pop();
|
||||
disable_pwm().unwrap();
|
||||
self.loop_iter(disp, rng)
|
||||
}
|
||||
|
||||
fn pop_action_and_clear(&mut self, disp: &mut D) -> Result<(), D::Error> {
|
||||
let active = self.active.get_mut();
|
||||
if active.len() > 1 {
|
||||
active.pop();
|
||||
disp.clear(BLACK)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<D: DrawTarget<Color = Rgb565>> Context for ContextDefault<D> {
|
||||
fn do_action(&self, action: Action) {
|
||||
match action {
|
||||
Action::Screensaver(id) => {
|
||||
for s in &self.screensavers {
|
||||
if s.id() == id {
|
||||
self.active.borrow_mut().push(s.convert_draw());
|
||||
return;
|
||||
}
|
||||
}
|
||||
println!("warning: screensaver not found");
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn active_count(&self) -> usize {
|
||||
self.active.borrow().len()
|
||||
}
|
||||
|
||||
fn database(&self) -> Rc<RefCell<Connection>> {
|
||||
self.database.clone()
|
||||
}
|
||||
|
||||
fn enable_pwm(&self) {
|
||||
enable_pwm().unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "pc"))]
|
||||
fn pc_main() {}
|
||||
|
||||
@ -43,11 +147,6 @@ fn pc_main() {}
|
||||
fn pc_main() {
|
||||
use std::num::NonZeroU32;
|
||||
|
||||
use embedded_graphics::primitives::{PrimitiveStyleBuilder, Rectangle, StyledDrawable};
|
||||
use rand_xoshiro::{
|
||||
rand_core::{RngCore, SeedableRng},
|
||||
Xoroshiro128StarStar,
|
||||
};
|
||||
use winit::{
|
||||
dpi::LogicalSize,
|
||||
event::{Event, WindowEvent},
|
||||
@ -55,6 +154,18 @@ fn pc_main() {
|
||||
window::WindowBuilder,
|
||||
};
|
||||
|
||||
use raspi_oled::FrameOutput;
|
||||
|
||||
let args: Vec<_> = env::args().map(|x| x.to_string()).collect();
|
||||
for [key, val] in args.array_windows() {
|
||||
match key.as_str() {
|
||||
"--speed" => {
|
||||
screensaver::SPEED.store(val.parse().unwrap(), std::sync::atomic::Ordering::Relaxed);
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
|
||||
let event_loop = EventLoop::new();
|
||||
let window = WindowBuilder::new()
|
||||
.with_inner_size(LogicalSize::new(128, 128))
|
||||
@ -66,10 +177,17 @@ fn pc_main() {
|
||||
let start = Instant::now();
|
||||
let mut iters = 0;
|
||||
let mut disp = FrameOutput::new(128, 128);
|
||||
//disp.buffer.save("/tmp/x.png").unwrap();
|
||||
let mut rng = Xoroshiro128StarStar::seed_from_u64(0);
|
||||
let mut buffer_dirty = true;
|
||||
|
||||
let mut ctx = ContextDefault::new();
|
||||
if args.iter().any(|x| x == "--totp") {
|
||||
let pw = rpassword::prompt_password("TOTP password: ").unwrap();
|
||||
let totps = andotp_import::read_from_file("./otp_accounts_2023-10-02_18-58-25.json.aes", &pw).unwrap();
|
||||
ctx.add(Totp::new(totps));
|
||||
ctx.do_action(Action::Screensaver("totp"));
|
||||
}
|
||||
let mut rng = Xoroshiro128StarStar::seed_from_u64(17381);
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
// ControlFlow::Poll continuously runs the event loop, even if the OS hasn't
|
||||
// dispatched any events. This is ideal for games and similar applications.
|
||||
@ -108,78 +226,9 @@ fn pc_main() {
|
||||
.unwrap();
|
||||
|
||||
// redraw
|
||||
if Instant::now().duration_since(start) > Duration::from_millis(iters * 50) {
|
||||
if Instant::now().duration_since(start) > Duration::from_millis(iters * 66) {
|
||||
iters += 1;
|
||||
//loop_iter(&mut disp).unwrap();
|
||||
/*
|
||||
let mut time = OffsetDateTime::now_utc().to_timezone(BERLIN);
|
||||
//time += Duration::new(iters * 60, 0);
|
||||
disp.clear(Rgb565::new(0, 0, 0)).unwrap();
|
||||
display_clock(&mut disp, &time).unwrap();
|
||||
*/
|
||||
/*
|
||||
let iters = iters % 300;
|
||||
let (s, c) = (iters as f32 * 0.1).sin_cos();
|
||||
let (mut x, mut y) = (s * iters as f32 * 0.005, c * iters as f32 * 0.005);
|
||||
x *= 64.;
|
||||
y *= 64.;
|
||||
x += 64.;
|
||||
y += 64.;
|
||||
let variation = iters as u32 / 16 + 1;
|
||||
for _ in 0..16 {
|
||||
let dx = (rng.next_u32() % variation) as i32 - variation as i32 / 2;
|
||||
let dy = (rng.next_u32() % variation) as i32 - variation as i32 / 2;
|
||||
let color = rng.next_u32();
|
||||
let p = Rectangle::new(Point::new(x as i32 + dx, y as i32 + dy), Size::new(1, 1));
|
||||
let s = PrimitiveStyleBuilder::new()
|
||||
.fill_color(Rgb565::new(
|
||||
color as u8 & 0b11111,
|
||||
((color >> 8) & 0b111111) as u8,
|
||||
((color >> 16) & 0b111111) as u8,
|
||||
))
|
||||
.build();
|
||||
p.draw_styled(&s, &mut disp).unwrap();
|
||||
}
|
||||
if iters % 300 == 0 {
|
||||
disp.clear(Rgb565::new(0, 0, 0)).unwrap();
|
||||
}
|
||||
*/
|
||||
for _ in 0..16 {
|
||||
let x = (rng.next_u32() % 128) as usize;
|
||||
let y = (rng.next_u32() % 128) as usize;
|
||||
let dx = (rng.next_u32() % 8) as i32 - 4;
|
||||
let dy = (rng.next_u32() % 8) as i32 - 4;
|
||||
let red = STAR[y * 128 * 3 + x * 3];
|
||||
let green = STAR[y * 128 * 3 + x * 3 + 1];
|
||||
if red == 0xff {
|
||||
let color = rng.next_u32();
|
||||
let r;
|
||||
let g;
|
||||
let b;
|
||||
// star
|
||||
r = (color as u8 & 0b11111).saturating_mul(2);
|
||||
g = (((color >> 8) & 0b111111) as u8).saturating_mul(2);
|
||||
b = ((color >> 16) & 0b111111) as u8 / 3;
|
||||
// rpi
|
||||
/*
|
||||
if red > green {
|
||||
r = (color as u8 & 0b11111).saturating_mul(2);
|
||||
g = ((color >> 8) & 0b111111) as u8 / 3;
|
||||
b = ((color >> 16) & 0b111111) as u8 / 3;
|
||||
} else {
|
||||
r = (color as u8 & 0b11111) / 2;
|
||||
g = (((color >> 8) & 0b111111) as u8).saturating_mul(2);
|
||||
b = ((color >> 16) & 0b111111) as u8 / 3;
|
||||
}
|
||||
*/
|
||||
let p = Rectangle::new(Point::new(x as i32 + dx, y as i32 + dy), Size::new(1, 1));
|
||||
let s = PrimitiveStyleBuilder::new()
|
||||
.fill_color(Rgb565::new(r as u8, g as u8, b as u8))
|
||||
.build();
|
||||
p.draw_styled(&s, &mut disp).unwrap();
|
||||
}
|
||||
}
|
||||
buffer_dirty = true;
|
||||
buffer_dirty = ctx.loop_iter(&mut disp, &mut rng);
|
||||
}
|
||||
|
||||
let mut buffer = surface.buffer_mut().unwrap();
|
||||
@ -196,6 +245,7 @@ fn pc_main() {
|
||||
}
|
||||
buffer.present().unwrap();
|
||||
buffer_dirty = false;
|
||||
let _ = disp.buffer.save(format!("/tmp/iter{}.png", iters));
|
||||
}
|
||||
},
|
||||
_ => (),
|
||||
@ -203,7 +253,21 @@ fn pc_main() {
|
||||
});
|
||||
}
|
||||
|
||||
pub trait Draw<D: DrawTarget<Color = Rgb565>> {
|
||||
fn draw_with_ctx(&self, _ctx: &ContextDefault<D>, disp: &mut D, rng: &mut Rng) -> Result<bool, D::Error> {
|
||||
self.draw(disp, rng)
|
||||
}
|
||||
fn draw(&self, disp: &mut D, rng: &mut Rng) -> Result<bool, D::Error>;
|
||||
fn expired(&self) -> bool {
|
||||
false
|
||||
}
|
||||
fn as_any(&self) -> &dyn Any;
|
||||
fn as_any_mut(&mut self) -> &mut dyn Any;
|
||||
}
|
||||
|
||||
fn rpi_main() {
|
||||
let args: Vec<_> = env::args().map(|x| x.to_string()).collect();
|
||||
|
||||
let spi = Spi::new(Bus::Spi0, SlaveSelect::Ss0, 19660800, Mode::Mode0).unwrap();
|
||||
let gpio = Gpio::new().unwrap();
|
||||
let dc = gpio.get(25).unwrap().into_output();
|
||||
@ -213,25 +277,55 @@ fn rpi_main() {
|
||||
let spii = SPIInterfaceNoCS::new(spi, dc);
|
||||
let mut disp = Ssd1351::new(spii);
|
||||
|
||||
// Reset & init
|
||||
// Reset & init display
|
||||
disp.reset(&mut rst, &mut Delay).unwrap();
|
||||
disp.turn_on().unwrap();
|
||||
|
||||
main_loop(disp);
|
||||
// Init PWM handling
|
||||
let pwm = thread::spawn(handle_pwm);
|
||||
|
||||
let mut ctx = ContextDefault::new();
|
||||
if args.iter().any(|x| x == "--totp") {
|
||||
let pw = rpassword::prompt_password("TOTP password: ").unwrap();
|
||||
let totps = andotp_import::read_from_file("./otp_accounts_2023-10-02_18-58-25.json.aes", &pw).unwrap();
|
||||
ctx.add(Totp::new(totps));
|
||||
}
|
||||
|
||||
main_loop(disp, ctx);
|
||||
|
||||
let _ = pwm.join();
|
||||
}
|
||||
|
||||
fn main_loop(mut disp: Ssd1351<SPIInterfaceNoCS<Spi, OutputPin>>) {
|
||||
fn handle_pwm() {
|
||||
let pwm = gpiocdev::Request::builder()
|
||||
.on_chip("/dev/gpiochip0")
|
||||
.with_line(12)
|
||||
.as_output(Value::Inactive)
|
||||
.request()
|
||||
.unwrap();
|
||||
loop {
|
||||
thread::sleep(Duration::from_millis(500));
|
||||
let on = PWM_ON.load(std::sync::atomic::Ordering::Relaxed);
|
||||
if !on {
|
||||
let _ = pwm.set_value(12, Value::Inactive);
|
||||
continue;
|
||||
}
|
||||
for _ in 0..100 {
|
||||
let _ = pwm.set_value(12, Value::Active);
|
||||
thread::sleep(Duration::from_millis(1));
|
||||
let _ = pwm.set_value(12, Value::Inactive);
|
||||
thread::sleep(Duration::from_millis(1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main_loop(mut disp: Oled, mut ctx: ContextDefault<Oled>) {
|
||||
disp.clear(BLACK).unwrap();
|
||||
let mut last_min = 0xff;
|
||||
|
||||
let mut rng = Xoroshiro128StarStar::seed_from_u64(17381);
|
||||
let mut last_button = Instant::now();
|
||||
|
||||
let mut menu = vec![];
|
||||
let _high_outputs = gpiocdev::Request::builder()
|
||||
.on_chip("/dev/gpiochip0")
|
||||
.with_lines(&[23, 24])
|
||||
.as_output(Value::Active)
|
||||
.request()
|
||||
.unwrap();
|
||||
let lines = gpiocdev::Request::builder()
|
||||
.on_chip("/dev/gpiochip0")
|
||||
.with_line(19)
|
||||
@ -255,66 +349,85 @@ fn main_loop(mut disp: Ssd1351<SPIInterfaceNoCS<Spi, OutputPin>>) {
|
||||
let e = lines.read_edge_event().unwrap();
|
||||
last_button = Instant::now();
|
||||
match e.offset {
|
||||
19 => {
|
||||
5 => {
|
||||
menu.push(1);
|
||||
},
|
||||
6 => {
|
||||
menu.push(2);
|
||||
},
|
||||
5 => {
|
||||
19 => {
|
||||
menu.push(3);
|
||||
},
|
||||
_ => {
|
||||
println!("unknown offset: {}", e.offset);
|
||||
},
|
||||
}
|
||||
println!("menu: {menu:?}");
|
||||
let mut pop_last = false;
|
||||
let mut clear = false;
|
||||
match &*menu {
|
||||
[1] => {
|
||||
ctx.do_action(Action::Screensaver("measurements"));
|
||||
},
|
||||
[1, 2] => {
|
||||
let _ = ctx.pop_action_and_clear(&mut disp);
|
||||
ctx.do_action(Action::Screensaver("measurements_temps"));
|
||||
pop_last = true;
|
||||
},
|
||||
[1, 3] => {
|
||||
let _ = ctx.pop_action_and_clear(&mut disp);
|
||||
ctx.do_action(Action::Screensaver("measurements_events"));
|
||||
pop_last = true;
|
||||
},
|
||||
[2] => {
|
||||
if ctx.active_count() > 1 {
|
||||
let _ = ctx.pop_action_and_clear(&mut disp);
|
||||
disable_pwm().unwrap();
|
||||
let _ = disp.flush();
|
||||
clear = true;
|
||||
}
|
||||
},
|
||||
[3] => {
|
||||
ctx.do_action(Action::Screensaver("totp"));
|
||||
},
|
||||
[3, 1] => {
|
||||
if let Some(x) = ctx.active.borrow_mut().last_mut() {
|
||||
let totp: Option<&mut Totp> = x.as_any_mut().downcast_mut();
|
||||
if let Some(x) = totp {
|
||||
x.next_page();
|
||||
}
|
||||
}
|
||||
pop_last = true;
|
||||
},
|
||||
[3, 2, 1] => {
|
||||
enable_pwm().unwrap();
|
||||
pop_last = true;
|
||||
},
|
||||
[3, 2, 3] => {
|
||||
disable_pwm().unwrap();
|
||||
pop_last = true;
|
||||
},
|
||||
[3, 3] => {
|
||||
ctx.do_action(Action::Screensaver("rpi"));
|
||||
clear = true;
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
if pop_last {
|
||||
menu.pop();
|
||||
}
|
||||
if clear {
|
||||
menu.clear();
|
||||
}
|
||||
}
|
||||
// clean up stale menu selection
|
||||
if !menu.is_empty() && Instant::now().duration_since(last_button).as_secs() >= 10 {
|
||||
menu.clear();
|
||||
}
|
||||
let time = OffsetDateTime::now_utc().to_timezone(BERLIN);
|
||||
if time.minute() == last_min {
|
||||
sleep_ms(1000);
|
||||
continue;
|
||||
// run context loop
|
||||
let dirty = ctx.loop_iter(&mut disp, &mut rng);
|
||||
if dirty {
|
||||
let _ = disp.flush(); // ignore bus write errors, they are harmless
|
||||
}
|
||||
last_min = time.minute();
|
||||
if let Err(e) = loop_iter(&mut disp) {
|
||||
println!("error: {:?}", e);
|
||||
}
|
||||
let _ = disp.flush(); // ignore bus write errors, they are harmless
|
||||
thread::sleep(Duration::from_millis(66));
|
||||
}
|
||||
}
|
||||
|
||||
fn loop_iter<D: DrawTarget<Color = Rgb565>>(disp: &mut D) -> Result<(), D::Error> {
|
||||
disp.clear(Rgb565::new(0, 0, 0))?;
|
||||
let time = OffsetDateTime::now_utc().to_timezone(BERLIN);
|
||||
display_clock(disp, &time)
|
||||
}
|
||||
|
||||
fn display_clock<D: DrawTarget<Color = Rgb565>>(disp: &mut D, time: &OffsetDateTime) -> Result<(), D::Error> {
|
||||
let text_style_clock = MonoTextStyleBuilder::new()
|
||||
.font(&FONT_10X20)
|
||||
.text_color(TIME_COLOR)
|
||||
.build();
|
||||
let hour = time.hour();
|
||||
let minute = time.minute();
|
||||
let unix_minutes = minute as i32 * 5 / 3; // (time.unix_timestamp() / 60) as i32;
|
||||
let dx = ((hour % 3) as i32 - 1) * 40 - 2;
|
||||
let hour = format!("{:02}", hour);
|
||||
Text::new(
|
||||
&hour,
|
||||
Point::new(64 - 20 + dx, 20 + unix_minutes % 100),
|
||||
text_style_clock,
|
||||
)
|
||||
.draw(disp)?;
|
||||
let minute = format!("{:02}", minute);
|
||||
Text::new(
|
||||
&minute,
|
||||
Point::new(64 + 4 + dx, 20 + unix_minutes % 100),
|
||||
text_style_clock,
|
||||
)
|
||||
.draw(disp)?;
|
||||
Ok(())
|
||||
}
|
||||
|
@ -1,31 +1,24 @@
|
||||
use std::{
|
||||
fs, thread,
|
||||
thread,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use display_interface_spi::SPIInterfaceNoCS;
|
||||
use embedded_graphics::{
|
||||
draw_target::DrawTarget,
|
||||
mono_font::{
|
||||
ascii::{FONT_10X20, FONT_5X8, FONT_6X9, FONT_9X15},
|
||||
MonoTextStyleBuilder,
|
||||
},
|
||||
pixelcolor::{BinaryColor, Rgb565},
|
||||
prelude::{OriginDimensions, Point, Primitive, Size},
|
||||
primitives::{PrimitiveStyleBuilder, Rectangle},
|
||||
mono_font::{ascii::FONT_10X20, MonoTextStyleBuilder},
|
||||
pixelcolor::Rgb565,
|
||||
text::Text,
|
||||
Drawable,
|
||||
};
|
||||
use embedded_hal::digital::v2::OutputPin;
|
||||
use image::{ImageBuffer, Rgb};
|
||||
use linux_embedded_hal::I2cdev;
|
||||
|
||||
use rppal::{
|
||||
gpio::Gpio,
|
||||
hal::Delay,
|
||||
spi::{Bus, Mode, SlaveSelect, Spi},
|
||||
};
|
||||
//use ssd1351::{properties::DisplaySize, mode::{GraphicsMode, displaymode::DisplayModeTrait}};
|
||||
use time::{format_description, OffsetDateTime, PrimitiveDateTime};
|
||||
|
||||
//use time_tz::{timezones::db::europe::BERLIN, OffsetDateTimeExt, PrimitiveDateTimeExt};
|
||||
|
||||
fn main() {
|
||||
|
56
src/bin/schedule/mod.rs
Normal file
56
src/bin/schedule/mod.rs
Normal file
@ -0,0 +1,56 @@
|
||||
use time::OffsetDateTime;
|
||||
|
||||
use crate::{action::Action, Context};
|
||||
|
||||
/// Task to be executed at certain times.
|
||||
/// Guaranteed to be checked at least once every minute.
|
||||
pub trait Schedule {
|
||||
fn check_and_do(&self, ctx: &dyn Context, time: OffsetDateTime) {
|
||||
if self.check(ctx, time) {
|
||||
self.execute(ctx, time);
|
||||
}
|
||||
}
|
||||
|
||||
fn check(&self, ctx: &dyn Context, time: OffsetDateTime) -> bool;
|
||||
fn execute(&self, ctx: &dyn Context, time: OffsetDateTime);
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Reminder {
|
||||
hour: u8,
|
||||
minute: u8,
|
||||
action: Action,
|
||||
should_beep: bool,
|
||||
}
|
||||
|
||||
impl Schedule for Reminder {
|
||||
fn check(&self, ctx: &dyn Context, time: OffsetDateTime) -> bool {
|
||||
time.hour() == self.hour && time.minute() == self.minute && ctx.active_count() == 1
|
||||
}
|
||||
|
||||
fn execute(&self, ctx: &dyn Context, _time: OffsetDateTime) {
|
||||
if self.should_beep {
|
||||
ctx.enable_pwm();
|
||||
}
|
||||
ctx.do_action(self.action);
|
||||
}
|
||||
}
|
||||
|
||||
impl Reminder {
|
||||
const fn new(hour: u8, minute: u8, action: Action, should_beep: bool) -> Self {
|
||||
Reminder {
|
||||
hour,
|
||||
minute,
|
||||
action,
|
||||
should_beep,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static DUOLINGO: Reminder = Reminder::new(11, 40, Action::Screensaver("duolingo"), false);
|
||||
static DUOLINGO_NIGHT: Reminder = Reminder::new(23, 40, Action::Screensaver("duolingo"), false);
|
||||
static FOOD: Reminder = Reminder::new(13, 15, Action::Screensaver("plate"), false);
|
||||
|
||||
pub fn reminders() -> Vec<Box<dyn Schedule>> {
|
||||
vec![Box::new(DUOLINGO), Box::new(DUOLINGO_NIGHT), Box::new(FOOD)]
|
||||
}
|
BIN
src/bin/screensaver/duolingo.raw
Normal file
BIN
src/bin/screensaver/duolingo.raw
Normal file
Binary file not shown.
194
src/bin/screensaver/mod.rs
Normal file
194
src/bin/screensaver/mod.rs
Normal file
@ -0,0 +1,194 @@
|
||||
use std::any::Any;
|
||||
use std::cell::RefCell;
|
||||
use std::sync::atomic::{AtomicU32, AtomicU64};
|
||||
|
||||
use embedded_graphics::mono_font::ascii::FONT_10X20;
|
||||
use embedded_graphics::{
|
||||
mono_font::MonoTextStyleBuilder,
|
||||
pixelcolor::Rgb565,
|
||||
prelude::{DrawTarget, Point, Size},
|
||||
primitives::{PrimitiveStyleBuilder, Rectangle, StyledDrawable},
|
||||
text::Text,
|
||||
Drawable,
|
||||
};
|
||||
use rand_xoshiro::rand_core::RngCore;
|
||||
use time::{Duration, OffsetDateTime};
|
||||
use time_tz::{timezones::db::europe::BERLIN, OffsetDateTimeExt};
|
||||
|
||||
use crate::{Draw, Rng};
|
||||
|
||||
pub static SPEED: AtomicU64 = AtomicU64::new(32);
|
||||
|
||||
pub trait Screensaver<D: DrawTarget<Color = Rgb565>>: Draw<D> {
|
||||
fn id(&self) -> &'static str;
|
||||
fn convert_draw(&self) -> Box<dyn Draw<D>>;
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SimpleScreensaver {
|
||||
id: &'static str,
|
||||
data: &'static [u8],
|
||||
iters: AtomicU32,
|
||||
}
|
||||
|
||||
impl Clone for SimpleScreensaver {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
id: self.id,
|
||||
data: self.data,
|
||||
iters: AtomicU32::new(self.iters.load(std::sync::atomic::Ordering::Relaxed)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<D: DrawTarget<Color = Rgb565>> Screensaver<D> for SimpleScreensaver {
|
||||
fn id(&self) -> &'static str {
|
||||
self.id
|
||||
}
|
||||
|
||||
fn convert_draw(&self) -> Box<dyn Draw<D>> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl<D: DrawTarget<Color = Rgb565>> Draw<D> for SimpleScreensaver {
|
||||
fn draw(&self, disp: &mut D, rng: &mut Rng) -> Result<bool, D::Error> {
|
||||
for _ in 0..SPEED.load(std::sync::atomic::Ordering::Relaxed) {
|
||||
let x = (rng.next_u32() % 128) as usize;
|
||||
let y = (rng.next_u32() % 128) as usize;
|
||||
let dx = (rng.next_u32() % 8) as i32 - 4;
|
||||
let dy = (rng.next_u32() % 8) as i32 - 4;
|
||||
let red = self.data[y * 128 * 3 + x * 3 + 0];
|
||||
let green = self.data[y * 128 * 3 + x * 3 + 1];
|
||||
let blue = self.data[y * 128 * 3 + x * 3 + 2];
|
||||
if red | green | blue != 0 {
|
||||
let color = rng.next_u32();
|
||||
let r;
|
||||
let g;
|
||||
let b;
|
||||
r = (red >> 3).overflowing_add(color as u8 & 0b11).0;
|
||||
g = (green >> 2).overflowing_add(((color >> 2) & 0b11) as u8).0;
|
||||
b = (blue >> 3).overflowing_add(((color >> 4) & 0b11) as u8).0;
|
||||
let p = Rectangle::new(Point::new(x as i32 + dx, y as i32 + dy), Size::new(1, 1));
|
||||
let s = PrimitiveStyleBuilder::new().fill_color(Rgb565::new(r, g, b)).build();
|
||||
p.draw_styled(&s, disp)?;
|
||||
}
|
||||
}
|
||||
self.iters.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
fn expired(&self) -> bool {
|
||||
self.iters.load(std::sync::atomic::Ordering::Relaxed) > 1000
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn as_any_mut(&mut self) -> &mut dyn Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl SimpleScreensaver {
|
||||
const fn new(id: &'static str, data: &'static [u8]) -> Self {
|
||||
if data.len() != 128 * 128 * 3 {
|
||||
panic!("invalid screensaver size");
|
||||
}
|
||||
SimpleScreensaver {
|
||||
id,
|
||||
data,
|
||||
iters: AtomicU32::new(0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static TIME_COLOR: Rgb565 = Rgb565::new(0b01_111, 0b011_111, 0b01_111);
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TimeDisplay {
|
||||
last_min: RefCell<OffsetDateTime>,
|
||||
}
|
||||
|
||||
impl TimeDisplay {
|
||||
pub fn new() -> Self {
|
||||
TimeDisplay {
|
||||
last_min: RefCell::new(
|
||||
OffsetDateTime::now_utc()
|
||||
.to_timezone(BERLIN)
|
||||
.checked_sub(Duration::minutes(2))
|
||||
.unwrap(),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<D: DrawTarget<Color = Rgb565>> Screensaver<D> for TimeDisplay {
|
||||
fn id(&self) -> &'static str {
|
||||
"time"
|
||||
}
|
||||
|
||||
fn convert_draw(&self) -> Box<dyn Draw<D>> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl<D: DrawTarget<Color = Rgb565>> Draw<D> for TimeDisplay {
|
||||
fn draw(&self, disp: &mut D, _rng: &mut Rng) -> Result<bool, D::Error> {
|
||||
let time = OffsetDateTime::now_utc().to_timezone(BERLIN);
|
||||
if time.minute() == self.last_min.borrow().minute() {
|
||||
return Ok(false);
|
||||
}
|
||||
*self.last_min.borrow_mut() = time;
|
||||
disp.clear(Rgb565::new(0, 0, 0))?;
|
||||
let text_style_clock = MonoTextStyleBuilder::new()
|
||||
.font(&FONT_10X20)
|
||||
.text_color(TIME_COLOR)
|
||||
.build();
|
||||
let hour = time.hour();
|
||||
let minute = time.minute();
|
||||
let unix_minutes = minute as i32 * 5 / 3; // (time.unix_timestamp() / 60) as i32;
|
||||
let dx = ((hour % 3) as i32 - 1) * 40 - 2;
|
||||
let hour = format!("{:02}", hour);
|
||||
Text::new(
|
||||
&hour,
|
||||
Point::new(64 - 20 + dx, 20 + unix_minutes % 100),
|
||||
text_style_clock,
|
||||
)
|
||||
.draw(disp)?;
|
||||
Text::new(&":", Point::new(64 - 3 + dx, 18 + unix_minutes % 100), text_style_clock).draw(disp)?;
|
||||
let minute = format!("{:02}", minute);
|
||||
Text::new(
|
||||
&minute,
|
||||
Point::new(64 + 5 + dx, 20 + unix_minutes % 100),
|
||||
text_style_clock,
|
||||
)
|
||||
.draw(disp)?;
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn as_any_mut(&mut self) -> &mut dyn Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub static STAR: SimpleScreensaver = SimpleScreensaver::new("star", include_bytes!("./star.raw"));
|
||||
pub static RPI: SimpleScreensaver = SimpleScreensaver::new("rpi", include_bytes!("./rpi.raw"));
|
||||
pub static DUOLINGO: SimpleScreensaver = SimpleScreensaver::new("duolingo", include_bytes!("./duolingo.raw"));
|
||||
pub static SPAGHETTI: SimpleScreensaver = SimpleScreensaver::new("spaghetti", include_bytes!("./spaghetti.raw"));
|
||||
pub static PLATE: SimpleScreensaver = SimpleScreensaver::new("plate", include_bytes!("./plate.raw"));
|
||||
|
||||
pub fn screensavers<D: DrawTarget<Color = Rgb565>>() -> Vec<Box<dyn Screensaver<D>>> {
|
||||
vec![
|
||||
Box::new(STAR.clone()),
|
||||
Box::new(RPI.clone()),
|
||||
Box::new(DUOLINGO.clone()),
|
||||
Box::new(SPAGHETTI.clone()),
|
||||
Box::new(PLATE.clone()),
|
||||
]
|
||||
}
|
BIN
src/bin/screensaver/plate.raw
Normal file
BIN
src/bin/screensaver/plate.raw
Normal file
Binary file not shown.
BIN
src/bin/screensaver/spaghetti.raw
Normal file
BIN
src/bin/screensaver/spaghetti.raw
Normal file
Binary file not shown.
79
src/bin/status_check.rs
Normal file
79
src/bin/status_check.rs
Normal file
@ -0,0 +1,79 @@
|
||||
use std::{
|
||||
process::{Command, Stdio},
|
||||
time::SystemTime,
|
||||
};
|
||||
|
||||
use rusqlite::Connection;
|
||||
|
||||
fn main() {
|
||||
let args = std::env::args().collect::<Vec<_>>();
|
||||
if args.len() < 2 {
|
||||
panic!("missing argument: database path");
|
||||
}
|
||||
let database = Connection::open(&args[1]).expect("failed to open database");
|
||||
|
||||
let timestamp: i64 = database
|
||||
.query_row(
|
||||
"SELECT time FROM sensor_readings ORDER BY time DESC LIMIT 1",
|
||||
[],
|
||||
|row| Ok(row.get(0).unwrap()),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let time = std::time::SystemTime::now()
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_secs() as i64;
|
||||
|
||||
let recent_reading = time - timestamp <= 12 * 60;
|
||||
let pi_up = Command::new("ping")
|
||||
.args(["-c1", "raspberrypi.fritz.box"])
|
||||
.spawn()
|
||||
.unwrap()
|
||||
.wait()
|
||||
.unwrap()
|
||||
.success();
|
||||
let traffic_good = if pi_up {
|
||||
let x = Command::new("ssh")
|
||||
.args(["pi@raspberrypi", "vnstat --json h 2"])
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()
|
||||
.unwrap()
|
||||
.wait_with_output()
|
||||
.unwrap();
|
||||
if x.status.success() {
|
||||
if let Ok(x) = serde_json::from_slice::<serde_json::Value>(&x.stdout) {
|
||||
let it = x.pointer("/interfaces/0/traffic/hour/0/tx");
|
||||
it.map(|x| x.as_u64()).flatten().unwrap_or(0) / (60 * 60 * 1000 * 1000 / 8) > 5
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
};
|
||||
let nixos_up = Command::new("ping")
|
||||
.args(["-c1", "nixos.fritz.box"])
|
||||
.spawn()
|
||||
.unwrap()
|
||||
.wait()
|
||||
.unwrap()
|
||||
.success();
|
||||
let sync_good = if nixos_up {
|
||||
if let Ok(x) = ureq::get("http://nixos.fritz.box:12783/").call() {
|
||||
x.status() < 400
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
};
|
||||
let status = format!(
|
||||
"{} {} {} {} {}",
|
||||
recent_reading, pi_up, traffic_good, nixos_up, sync_good
|
||||
);
|
||||
std::fs::write("/run/user/1000/status.json", status).unwrap();
|
||||
}
|
99
src/lib.rs
99
src/lib.rs
@ -1,4 +1,5 @@
|
||||
use std::{
|
||||
sync::atomic::AtomicBool,
|
||||
thread::sleep,
|
||||
time::{self, Duration},
|
||||
};
|
||||
@ -8,13 +9,20 @@ use embedded_graphics::{
|
||||
pixelcolor::Rgb565,
|
||||
prelude::{OriginDimensions, RgbColor, Size},
|
||||
};
|
||||
use gpiocdev::line::{Bias, EdgeDetection, EdgeKind, Value};
|
||||
use gpiocdev::{
|
||||
line::{Bias, EdgeKind, Value},
|
||||
request::Config,
|
||||
Request,
|
||||
};
|
||||
#[cfg(feature = "pc")]
|
||||
use image::{ImageBuffer, Rgb};
|
||||
|
||||
#[cfg(feature = "pc")]
|
||||
pub struct FrameOutput {
|
||||
pub buffer: ImageBuffer<Rgb<u8>, Vec<u8>>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "pc")]
|
||||
impl FrameOutput {
|
||||
pub fn new(width: u32, height: u32) -> Self {
|
||||
FrameOutput {
|
||||
@ -23,6 +31,7 @@ impl FrameOutput {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "pc")]
|
||||
impl DrawTarget for FrameOutput {
|
||||
type Color = Rgb565;
|
||||
|
||||
@ -49,32 +58,52 @@ impl DrawTarget for FrameOutput {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "pc")]
|
||||
impl OriginDimensions for FrameOutput {
|
||||
fn size(&self) -> Size {
|
||||
Size::new(self.buffer.width(), self.buffer.height())
|
||||
}
|
||||
}
|
||||
|
||||
fn read_events(timeout: std::time::Duration) -> Result<Vec<(u64, EdgeKind)>, SensorError> {
|
||||
fn read_events(timeout: std::time::Duration, input: Request) -> Result<Vec<(u64, EdgeKind)>, SensorError> {
|
||||
let mut c = Config::default();
|
||||
c.as_input();
|
||||
c.with_bias(Bias::PullUp);
|
||||
input.reconfigure(&c)?;
|
||||
/*
|
||||
let input = gpiocdev::Request::builder()
|
||||
.on_chip("/dev/gpiochip0")
|
||||
.with_line(26)
|
||||
.as_input()
|
||||
.with_edge_detection(EdgeDetection::BothEdges)
|
||||
.with_bias(Bias::PullDown)
|
||||
//.with_edge_detection(EdgeDetection::BothEdges)
|
||||
//.with_debounce_period(Duration::ZERO)
|
||||
.with_kernel_event_buffer_size(1024)
|
||||
.with_bias(Bias::PullUp)
|
||||
.request()?;
|
||||
*/
|
||||
|
||||
let start = time::Instant::now();
|
||||
let mut last_value = Value::Active;
|
||||
|
||||
let mut events = Vec::with_capacity(81);
|
||||
while start.elapsed() < timeout && events.len() < 81 {
|
||||
let new_value = input.value(26)?;
|
||||
if new_value != last_value {
|
||||
match new_value {
|
||||
Value::Inactive => events.push((start.elapsed().as_micros() as u64, EdgeKind::Falling)),
|
||||
Value::Active => events.push((start.elapsed().as_micros() as u64, EdgeKind::Rising)),
|
||||
}
|
||||
last_value = new_value;
|
||||
}
|
||||
/*
|
||||
if input.wait_edge_event(timeout)? {
|
||||
let event = input.read_edge_event()?;
|
||||
events.push((start.elapsed().as_micros() as u64, event.kind));
|
||||
}
|
||||
*/
|
||||
}
|
||||
if events.len() < 81 {
|
||||
println!("error: only got {} events", events.len());
|
||||
println!("error: only got {} events: {:?}", events.len(), events);
|
||||
return Err(SensorError::Timeout);
|
||||
}
|
||||
Ok(events)
|
||||
@ -169,12 +198,10 @@ pub fn am2302_reading() -> Result<(u16, u16), SensorError> {
|
||||
out.set_value(26, Value::Active)?;
|
||||
sleep(Duration::from_millis(500));
|
||||
set_max_priority();
|
||||
// set low for 20 ms
|
||||
out.set_value(26, Value::Inactive)?;
|
||||
sleep(Duration::from_millis(3));
|
||||
drop(out);
|
||||
sleep(Duration::from_millis(2));
|
||||
|
||||
let events = read_events(Duration::from_secs(1));
|
||||
let events = read_events(Duration::from_secs(1), out);
|
||||
println!("{:?} {:?}", events, events.as_ref().map(|x| x.len()));
|
||||
set_normal_priority();
|
||||
let events = events?;
|
||||
@ -196,3 +223,57 @@ fn set_normal_priority() {
|
||||
libc::sched_setscheduler(0, libc::SCHED_OTHER, (&sched_para) as *const libc::sched_param);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn disable_pwm() -> Result<(), rppal::pwm::Error> {
|
||||
/*
|
||||
let pwm = Pwm::new(rppal::pwm::Channel::Pwm0)?;
|
||||
if pwm.is_enabled()? {
|
||||
pwm.disable()?;
|
||||
}
|
||||
*/
|
||||
PWM_ON.store(false, std::sync::atomic::Ordering::Relaxed);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn enable_pwm() -> Result<(), rppal::pwm::Error> {
|
||||
PWM_ON.store(true, std::sync::atomic::Ordering::Relaxed);
|
||||
/*
|
||||
let mut pwm = Pwm::with_period(
|
||||
rppal::pwm::Channel::Pwm0,
|
||||
Duration::from_micros(500),
|
||||
Duration::from_micros(250),
|
||||
rppal::pwm::Polarity::Normal,
|
||||
true,
|
||||
)?;
|
||||
assert!(pwm.is_enabled()?);
|
||||
pwm.set_reset_on_drop(false);
|
||||
*/
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub static PWM_ON: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
use serde_derive::Deserialize;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct Events {
|
||||
pub events: Vec<Event>,
|
||||
pub weekly: Vec<Weekly>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Event {
|
||||
pub name: String,
|
||||
pub start_time: String,
|
||||
pub end_time: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct Weekly {
|
||||
pub name: String,
|
||||
pub day: i32,
|
||||
pub hour: i32,
|
||||
pub minute: i32,
|
||||
pub duration: i32,
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user