Compare commits

..

13 Commits

Author SHA1 Message Date
FliegendeWurst
f8de136019 Remove silly GPIO pins for buttons
I'm finally giving in and will now connect stuff via a breadboard...
Risky move though, I already fried one DHT22 by plugging the connector
in the wrong way around -.-
2023-10-14 11:37:00 +02:00
FliegendeWurst
0266f9c465 Fix sensor reading 2023-10-14 11:36:21 +02:00
FliegendeWurst
f881075c4a Clear background in draw 2023-10-14 11:36:08 +02:00
FliegendeWurst
f4d42943af Fix TOTP paging 2023-10-12 15:36:56 +02:00
FliegendeWurst
30683b42f0 Basic TOTP viewer 2023-10-12 15:35:00 +02:00
FliegendeWurst
6335eb818e Implement measurements view 2023-10-12 14:42:54 +02:00
FliegendeWurst
3e38684c17 Fix schedules 2023-10-05 13:23:01 +02:00
FliegendeWurst
13c5a472ce Expiration system 2023-10-05 12:56:25 +02:00
FliegendeWurst
ca73b6545a Fix main loop delay 2023-10-04 12:42:30 +02:00
FliegendeWurst
c5a930bed1 Update PC debug code 2023-10-04 10:00:50 +02:00
FliegendeWurst
4e737ca007 More scheduling 2023-10-04 09:54:47 +02:00
FliegendeWurst
d51b298128 Start scheduler, screensaver and action framework 2023-10-04 08:59:56 +02:00
FliegendeWurst
f87f7b5001 Show colon separator in clock 2023-10-03 21:08:09 +02:00
23 changed files with 1510 additions and 284 deletions

6
.gitignore vendored
View File

@ -3,3 +3,9 @@
events_weekly.json events_weekly.json
events.json events.json
sensors.db sensors.db
/result
/box.svg
/mockup.png
/*.aes
/*.py
/*.txt

302
Cargo.lock generated
View File

@ -37,13 +37,28 @@ dependencies = [
[[package]] [[package]]
name = "aho-corasick" name = "aho-corasick"
version = "1.1.1" version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea5d730647d4fadd988536d06fecce94b7b4f2a7efdae548f1cf4b63205518ab" checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0"
dependencies = [ dependencies = [
"memchr", "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]] [[package]]
name = "android-activity" name = "android-activity"
version = "0.4.3" version = "0.4.3"
@ -113,6 +128,12 @@ version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b7e4c2464d97fe331d41de9d5db0def0a96f4d823b8b32a2efd503578988973" checksum = "7b7e4c2464d97fe331d41de9d5db0def0a96f4d823b8b32a2efd503578988973"
[[package]]
name = "base32"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23ce669cd6c8588f79e15cf450314f9638f967fc5770ff1c7c1deb0925ea7cfa"
[[package]] [[package]]
name = "base64" name = "base64"
version = "0.21.4" version = "0.21.4"
@ -143,6 +164,15 @@ version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" 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]] [[package]]
name = "block-sys" name = "block-sys"
version = "0.1.0-beta.1" version = "0.1.0-beta.1"
@ -191,14 +221,14 @@ checksum = "965ab7eb5f8f97d2a083c799f3a1b994fc397b2fe2da5d1da1626ce15a39f2b1"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.37", "syn 2.0.38",
] ]
[[package]] [[package]]
name = "byteorder" name = "byteorder"
version = "1.4.3" version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]] [[package]]
name = "calloop" name = "calloop"
@ -292,6 +322,12 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
[[package]]
name = "constant_time_eq"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21a53c0a4d288377e7415b53dcfc3c04da5cdc2cc95c8d5ac178b58f0b861ad6"
[[package]] [[package]]
name = "core-foundation" name = "core-foundation"
version = "0.9.3" version = "0.9.3"
@ -345,6 +381,15 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "cpufeatures"
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "crc32fast" name = "crc32fast"
version = "1.3.2" version = "1.3.2"
@ -393,6 +438,16 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" 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]] [[package]]
name = "ctor" name = "ctor"
version = "0.2.5" version = "0.2.5"
@ -400,7 +455,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37e366bff8cd32dd8754b0991fb66b279dc48f598c3a18914852a6673deef583" checksum = "37e366bff8cd32dd8754b0991fb66b279dc48f598c3a18914852a6673deef583"
dependencies = [ dependencies = [
"quote", "quote",
"syn 2.0.37", "syn 2.0.38",
] ]
[[package]] [[package]]
@ -409,6 +464,17 @@ version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2696e8a945f658fd14dc3b87242e6b80cd0f36ff04ea560fa39082368847946" 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]] [[package]]
name = "dispatch" name = "dispatch"
version = "0.2.0" version = "0.2.0"
@ -493,9 +559,9 @@ checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07"
[[package]] [[package]]
name = "embedded-graphics" name = "embedded-graphics"
version = "0.7.1" version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "750082c65094fbcc4baf9ba31583ce9a8bb7f52cadfb96f6164b1bc7f922f32b" checksum = "0649998afacf6d575d126d83e68b78c0ab0e00ca2ac7e9b3db11b4cbe8274ef0"
dependencies = [ dependencies = [
"az", "az",
"byteorder", "byteorder",
@ -506,9 +572,9 @@ dependencies = [
[[package]] [[package]]
name = "embedded-graphics-core" name = "embedded-graphics-core"
version = "0.3.3" version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8b1239db5f3eeb7e33e35bd10bd014e7b2537b17e071f726a09351431337cfa" checksum = "ba9ecd261f991856250d2207f6d8376946cd9f412a2165d3b75bc87a0bc7a044"
dependencies = [ dependencies = [
"az", "az",
"byteorder", "byteorder",
@ -526,10 +592,17 @@ dependencies = [
[[package]] [[package]]
name = "embedded-hal" 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" 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 = [ dependencies = [
"embedded-hal 1.0.0-alpha.9",
"nb 1.1.0", "nb 1.1.0",
] ]
@ -594,9 +667,9 @@ dependencies = [
[[package]] [[package]]
name = "float-cmp" name = "float-cmp"
version = "0.8.0" version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1267f4ac4f343772758f7b1bdcbe767c218bbab93bb432acbf5162bbf85a6c4" checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4"
dependencies = [ dependencies = [
"num-traits", "num-traits",
] ]
@ -637,7 +710,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.37", "syn 2.0.38",
] ]
[[package]] [[package]]
@ -661,6 +734,16 @@ dependencies = [
"percent-encoding", "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]] [[package]]
name = "gethostname" name = "gethostname"
version = "0.3.0" version = "0.3.0"
@ -767,6 +850,15 @@ version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7"
[[package]]
name = "hmac"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
dependencies = [
"digest",
]
[[package]] [[package]]
name = "human-sort" name = "human-sort"
version = "0.2.2" version = "0.2.2"
@ -893,9 +985,9 @@ checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130"
[[package]] [[package]]
name = "jobserver" name = "jobserver"
version = "0.1.26" version = "0.1.27"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "936cfd212a0155903bcbc060e316fb6cc7cbf2e1907329391ebadc1fe0ce77c2" checksum = "8c37f63953c4c63420ed5fd3d6d398c719489b9f872b9fa683262f8edd363c7d"
dependencies = [ dependencies = [
"libc", "libc",
] ]
@ -932,9 +1024,9 @@ checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.148" version = "0.2.149"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b" checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b"
[[package]] [[package]]
name = "libloading" name = "libloading"
@ -1011,9 +1103,9 @@ dependencies = [
[[package]] [[package]]
name = "memchr" name = "memchr"
version = "2.6.3" version = "2.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c" checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167"
[[package]] [[package]]
name = "memmap2" name = "memmap2"
@ -1062,9 +1154,9 @@ dependencies = [
[[package]] [[package]]
name = "micromath" name = "micromath"
version = "1.1.1" version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc4010833aea396656c2f91ee704d51a6f1329ec2ab56ffd00bfd56f7481ea94" checksum = "39617bc909d64b068dcffd0e3e31679195b5576d0c83fadc52690268cc2b2b55"
[[package]] [[package]]
name = "minimal-lexical" name = "minimal-lexical"
@ -1228,9 +1320,9 @@ dependencies = [
[[package]] [[package]]
name = "num-traits" name = "num-traits"
version = "0.2.16" version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c"
dependencies = [ dependencies = [
"autocfg", "autocfg",
] ]
@ -1274,7 +1366,7 @@ dependencies = [
"proc-macro-crate", "proc-macro-crate",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.37", "syn 2.0.38",
] ]
[[package]] [[package]]
@ -1432,9 +1524,9 @@ dependencies = [
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.67" version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328" checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da"
dependencies = [ dependencies = [
"unicode-ident", "unicode-ident",
] ]
@ -1494,6 +1586,7 @@ dependencies = [
name = "raspi-oled" name = "raspi-oled"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"andotp-import",
"display-interface-spi", "display-interface-spi",
"embedded-graphics", "embedded-graphics",
"embedded-hal 0.2.7", "embedded-hal 0.2.7",
@ -1502,7 +1595,8 @@ dependencies = [
"libc", "libc",
"linux-embedded-hal", "linux-embedded-hal",
"rand_xoshiro", "rand_xoshiro",
"rppal 0.13.1", "rpassword",
"rppal",
"rusqlite", "rusqlite",
"serde", "serde",
"serde_derive", "serde_derive",
@ -1511,6 +1605,7 @@ dependencies = [
"ssd1351", "ssd1351",
"time", "time",
"time-tz", "time-tz",
"totp-rs",
"ureq", "ureq",
"winit", "winit",
] ]
@ -1552,9 +1647,9 @@ dependencies = [
[[package]] [[package]]
name = "regex" name = "regex"
version = "1.9.5" version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "697061221ea1b4a94a624f67d0ae2bfe4e22b8a17b6a192afb11046542cc8c47" checksum = "d119d7c7ca818f8a53c300863d4f87566aac09943aef5b355bb83969dae75d87"
dependencies = [ dependencies = [
"aho-corasick", "aho-corasick",
"memchr", "memchr",
@ -1564,9 +1659,9 @@ dependencies = [
[[package]] [[package]]
name = "regex-automata" name = "regex-automata"
version = "0.3.8" version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2f401f4955220693b56f8ec66ee9c78abffd8d1c4f23dc41a23839eb88f0795" checksum = "465c6fc0621e4abc4187a2bda0937bfd4f722c2730b29562e19689ea796c9a4b"
dependencies = [ dependencies = [
"aho-corasick", "aho-corasick",
"memchr", "memchr",
@ -1575,38 +1670,60 @@ dependencies = [
[[package]] [[package]]
name = "regex-syntax" name = "regex-syntax"
version = "0.7.5" version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" checksum = "56d84fdd47036b038fc80dd333d10b6aab10d5d31f4a366e20014def75328d33"
[[package]] [[package]]
name = "rppal" name = "ring"
version = "0.12.0" version = "0.17.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb26758c881b4837b2f4aef569e4251f75388e36b37204e1804ef429c220121c" checksum = "9babe80d5c16becf6594aa32ad2be8fe08498e7ae60b77de8df700e67f191d7e"
dependencies = [ dependencies = [
"embedded-hal 0.2.7", "cc",
"lazy_static", "getrandom",
"libc", "libc",
"nb 0.1.3", "spin",
"void", "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]] [[package]]
name = "rppal" name = "rppal"
version = "0.13.1" version = "0.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c88c9c6248de4d337747b619d8f671055ef48a87dc21b97998833f189a0bbd4f" checksum = "612e1a22e21f08a246657c6433fe52b773ae43d07c9ef88ccfc433cc8683caba"
dependencies = [ dependencies = [
"embedded-hal 0.2.7", "embedded-hal 0.2.7",
"embedded-hal 1.0.0-alpha.5", "embedded-hal 1.0.0-alpha.9",
"lazy_static", "embedded-hal-nb",
"libc", "libc",
"nb 0.1.3", "nb 0.1.3",
"spin_sleep", "spin_sleep",
"void", "void",
] ]
[[package]]
name = "rtoolbox"
version = "0.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "034e22c514f5c0cb8a10ff341b9b048b5ceb21591f31c8f44c43b960f9b3524a"
dependencies = [
"libc",
"winapi",
]
[[package]] [[package]]
name = "rusqlite" name = "rusqlite"
version = "0.27.0" version = "0.27.0"
@ -1682,7 +1799,7 @@ checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.37", "syn 2.0.38",
] ]
[[package]] [[package]]
@ -1717,6 +1834,28 @@ dependencies = [
"termios", "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]] [[package]]
name = "simd-adler32" name = "simd-adler32"
version = "0.3.7" version = "0.3.7"
@ -1838,7 +1977,7 @@ dependencies = [
[[package]] [[package]]
name = "ssd1351" name = "ssd1351"
version = "0.3.0" 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 = [ dependencies = [
"chrono", "chrono",
"display-interface", "display-interface",
@ -1846,7 +1985,7 @@ dependencies = [
"embedded-graphics", "embedded-graphics",
"embedded-hal 0.2.7", "embedded-hal 0.2.7",
"linux-embedded-hal", "linux-embedded-hal",
"rppal 0.12.0", "rppal",
"simple-signal", "simple-signal",
] ]
@ -1856,6 +1995,12 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731"
[[package]]
name = "subtle"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc"
[[package]] [[package]]
name = "syn" name = "syn"
version = "1.0.109" version = "1.0.109"
@ -1869,9 +2014,9 @@ dependencies = [
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.37" version = "2.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7303ef2c05cd654186cb250d29049a24840ca25d2747c25c0381c8d9e2f582e8" checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -1898,22 +2043,22 @@ dependencies = [
[[package]] [[package]]
name = "thiserror" name = "thiserror"
version = "1.0.48" version = "1.0.49"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d6d7a740b8a666a7e828dd00da9c0dc290dff53154ea77ac109281de90589b7" checksum = "1177e8c6d7ede7afde3585fd2513e611227efd6481bd78d2e82ba1ce16557ed4"
dependencies = [ dependencies = [
"thiserror-impl", "thiserror-impl",
] ]
[[package]] [[package]]
name = "thiserror-impl" name = "thiserror-impl"
version = "1.0.48" version = "1.0.49"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49922ecae66cc8a249b77e68d1d0623c1b2c514f0060c27cdc68bd62a1219d35" checksum = "10712f02019e9288794769fba95cd6847df9874d49d871d062172f9dd41bc4cc"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.37", "syn 2.0.38",
] ]
[[package]] [[package]]
@ -1934,6 +2079,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "426f806f4089c493dcac0d24c29c01e2c38baf8e30f1b716ee37e83d200b18fe" checksum = "426f806f4089c493dcac0d24c29c01e2c38baf8e30f1b716ee37e83d200b18fe"
dependencies = [ dependencies = [
"deranged", "deranged",
"js-sys",
"serde", "serde",
"time-core", "time-core",
"time-macros", "time-macros",
@ -1956,9 +2102,9 @@ dependencies = [
[[package]] [[package]]
name = "time-tz" name = "time-tz"
version = "1.0.3" version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a422f65dfdf08a81317d54fa00b45dc58cbccab69be78c1447391cc39ae8c9d4" checksum = "733bc522e97980eb421cbf381160ff225bd14262a48a739110f6653c6258d625"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"parse-zoneinfo", "parse-zoneinfo",
@ -1967,6 +2113,7 @@ dependencies = [
"serde", "serde",
"serde-xml-rs", "serde-xml-rs",
"time", "time",
"wasm-bindgen",
] ]
[[package]] [[package]]
@ -2038,6 +2185,19 @@ dependencies = [
"winnow", "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]] [[package]]
name = "tracing" name = "tracing"
version = "0.1.37" version = "0.1.37"
@ -2061,6 +2221,12 @@ version = "0.19.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49d64318d8311fc2668e48b63969f4343e0a85c4a109aa8460d6672e364b8bd1" checksum = "49d64318d8311fc2668e48b63969f4343e0a85c4a109aa8460d6672e364b8bd1"
[[package]]
name = "typenum"
version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
[[package]] [[package]]
name = "unicode-bidi" name = "unicode-bidi"
version = "0.3.13" version = "0.3.13"
@ -2083,10 +2249,16 @@ dependencies = [
] ]
[[package]] [[package]]
name = "ureq" name = "untrusted"
version = "2.7.1" version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index" 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 = [ dependencies = [
"base64", "base64",
"log", "log",
@ -2156,7 +2328,7 @@ dependencies = [
"once_cell", "once_cell",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.37", "syn 2.0.38",
"wasm-bindgen-shared", "wasm-bindgen-shared",
] ]
@ -2178,7 +2350,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.37", "syn 2.0.38",
"wasm-bindgen-backend", "wasm-bindgen-backend",
"wasm-bindgen-shared", "wasm-bindgen-shared",
] ]
@ -2537,9 +2709,9 @@ dependencies = [
[[package]] [[package]]
name = "winnow" name = "winnow"
version = "0.5.15" version = "0.5.16"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c2e3184b9c4e92ad5167ca73039d0c42476302ab603e2fec4487511f38ccefc" checksum = "037711d82167854aff2018dfd193aa0fef5370f456732f0d5a0c59b0f1b4b907"
dependencies = [ dependencies = [
"memchr", "memchr",
] ]

View File

@ -7,29 +7,32 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
embedded-graphics = "0.7.1" embedded-graphics = "0.8.1"
linux-embedded-hal = "0.3.0" linux-embedded-hal = "0.3.0"
embedded-hal = "0.2.5" embedded-hal = "0.2.5"
libc = "0.2.98" libc = "0.2.98"
rusqlite = "0.27.0" rusqlite = "0.27.0"
time = { version = "0.3.9", features = ["parsing"] } time = { version = "0.3.9", features = ["parsing"] }
time-tz = "1.0.1" time-tz = "2"
image = "0.24.1" image = { version = "0.24.1", optional = true }
serde_json = "1.0.79" serde_json = "1.0.79"
serde_derive = "1.0.136" serde_derive = "1.0.136"
serde = "1.0.136" serde = "1.0.136"
rppal = { version = "0.13.1", features = ["hal"] } rppal = { version = "0.14.1", features = ["hal"] }
ssd1351 = { git = "https://github.com/FliegendeWurst/ssd1351-rust" } ssd1351 = { git = "https://github.com/FliegendeWurst/ssd1351-rust", rev = "3de5be50bd9a59391c669aec8357923a56d121f6" }
display-interface-spi = "0.4.1" display-interface-spi = "0.4.1"
ureq = { version = "2.4.0", default-features = false } ureq = { version = "2.4.0", default-features = false }
winit = { version = "0.28.7", optional = true } winit = { version = "0.28.7", optional = true }
softbuffer = { version = "0.3.1", optional = true } softbuffer = { version = "0.3.1", optional = true }
rand_xoshiro = "0.6.0" rand_xoshiro = "0.6.0"
gpiocdev = "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" } #gpio-am2302-rs = { git = "https://github.com/FliegendeWurst/gpio-am2302-rs" }
[features] [features]
pc = ["winit", "softbuffer"] pc = ["winit", "softbuffer", "image"]
default = [ "pc" ] default = [ "pc" ]
[profile.release] [profile.release]

View File

@ -9,14 +9,14 @@ rustPlatform.buildRustPackage {
cargoLock = { cargoLock = {
lockFile = ./Cargo.lock; lockFile = ./Cargo.lock;
outputHashes = { 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="; # "gpio-am2302-rs-1.1.0" = "sha256-tyA/R80LtWIXoVEoxHhkmzy0IsMdMH1Oi3FTQ56XjyQ=";
}; };
}; };
nativeBuildInputs = [ pkg-config ]; nativeBuildInputs = [ pkg-config ];
cargoBuildFlags = [ "--no-default-features" ]; cargoBuildFlags = [ "--no-default-features" "--bin" "take_measurement" ];
buildInputs = [ sqlite ]; buildInputs = [ sqlite ];
@ -25,7 +25,7 @@ rustPlatform.buildRustPackage {
meta = with lib; { meta = with lib; {
description = "OLED display of clock/calendar/temperature"; description = "OLED display of clock/calendar/temperature";
homepage = "https://github.com/FliegendeWurst/raspi-oled"; homepage = "https://github.com/FliegendeWurst/raspi-oled";
license = licenses.mit; license = licenses.gpl3;
maintainers = with maintainers; [ fliegendewurst ]; maintainers = with maintainers; [ fliegendewurst ];
}; };
} }

4
src/bin/action/mod.rs Normal file
View File

@ -0,0 +1,4 @@
#[derive(Debug, Clone, Copy)]
pub enum Action {
Screensaver(&'static str),
}

View File

@ -4,25 +4,25 @@ use linux_embedded_hal::I2cdev;
const CCS811_ADDR: u8 = 0x5A; // or 0x5B const CCS811_ADDR: u8 = 0x5A; // or 0x5B
const CCS811_STATUS: u8 = 0x00; pub const CCS811_STATUS: u8 = 0x00;
const CCS811_MEAS_MODE: u8 = 0x01; pub const CCS811_MEAS_MODE: u8 = 0x01;
const CCS811_ALG_RESULT_DATA: u8 = 0x02; pub const CCS811_ALG_RESULT_DATA: u8 = 0x02;
const CCS811_RAW_DATA: u8 = 0x03; pub const CCS811_RAW_DATA: u8 = 0x03;
const CCS811_ENV_DATA: u8 = 0x05; pub const CCS811_ENV_DATA: u8 = 0x05;
const CCS811_NTC: u8 = 0x06; pub const CCS811_NTC: u8 = 0x06;
const CCS811_THRESHOLDS: u8 = 0x10; pub const CCS811_THRESHOLDS: u8 = 0x10;
const CCS811_BASELINE: u8 = 0x11; pub const CCS811_BASELINE: u8 = 0x11;
const CCS811_HW_ID: u8 = 0x20; pub const CCS811_HW_ID: u8 = 0x20;
const CCS811_HW_VERSION: u8 = 0x21; pub const CCS811_HW_VERSION: u8 = 0x21;
const CCS811_FW_BOOT_VERSION: u8 = 0x23; pub const CCS811_FW_BOOT_VERSION: u8 = 0x23;
const CCS811_FW_APP_VERSION: u8 = 0x24; pub const CCS811_FW_APP_VERSION: u8 = 0x24;
const CCS811_ERROR_ID: u8 = 0xE0; pub const CCS811_ERROR_ID: u8 = 0xE0;
const CCS811_APP_START: u8 = 0xF4; pub const CCS811_APP_START: u8 = 0xF4;
const CCS811_SW_RESET: u8 = 0xFF; pub const CCS811_SW_RESET: u8 = 0xFF;
struct CCS811 { pub struct CCS811 {
i2c: I2cdev, i2c: I2cdev,
addr: u8, pub addr: u8,
} }
impl CCS811 { impl CCS811 {
@ -79,7 +79,7 @@ impl CCS811 {
} }
} }
enum CCS811DriveMode { pub enum CCS811DriveMode {
Idle = 0, Idle = 0,
EverySecond = 1, EverySecond = 1,
Every10Seconds = 2, Every10Seconds = 2,

View File

@ -15,40 +15,16 @@ use embedded_graphics::{
text::{renderer::CharacterStyle, Text}, text::{renderer::CharacterStyle, Text},
Drawable, Drawable,
}; };
use raspi_oled::FrameOutput;
use raspi_oled::Events;
use rppal::{ use rppal::{
gpio::Gpio, gpio::Gpio,
spi::{Bus, Mode, SlaveSelect, Spi}, spi::{Bus, Mode, SlaveSelect, Spi},
}; };
use rusqlite::Connection; 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::{format_description, Date, OffsetDateTime, PrimitiveDateTime};
use time_tz::{timezones::db::europe::BERLIN, OffsetDateTimeExt, PrimitiveDateTimeExt}; 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)] #[derive(Clone, Copy, PartialEq, Eq)]
enum Status { enum Status {
Unknown, Unknown,
@ -187,7 +163,7 @@ where
.font(&FONT_6X9) .font(&FONT_6X9)
.text_color(Rgb565::new(0xff, 0xff, 0xff)) .text_color(Rgb565::new(0xff, 0xff, 0xff))
.build(); .build();
let mut text_style_4x6 = MonoTextStyleBuilder::new() let text_style_4x6 = MonoTextStyleBuilder::new()
.font(&FONT_4X6) .font(&FONT_4X6)
.text_color(Rgb565::new(0xff, 0xff, 0xff)) .text_color(Rgb565::new(0xff, 0xff, 0xff))
.build(); .build();

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

Binary file not shown.

View 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
View 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
View 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
}
}

View File

@ -1,32 +1,42 @@
#![feature(array_windows, round_char_boundary)]
use std::{ use std::{
thread::sleep_ms, any::Any,
cell::RefCell,
env,
rc::Rc,
thread,
time::{Duration, Instant}, time::{Duration, Instant},
}; };
use action::Action;
use display_interface_spi::SPIInterfaceNoCS; use display_interface_spi::SPIInterfaceNoCS;
use embedded_graphics::{ use draw::Totp;
mono_font::{ascii::FONT_10X20, MonoTextStyleBuilder}, use embedded_graphics::{pixelcolor::Rgb565, prelude::DrawTarget};
pixelcolor::Rgb565,
prelude::{DrawTarget, Point, Size},
text::Text,
Drawable,
};
use gpiocdev::line::{Bias, EdgeDetection, Value}; 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::{ use rppal::{
gpio::{Gpio, OutputPin}, gpio::{Gpio, OutputPin},
hal::Delay, hal::Delay,
spi::{Bus, Mode, SlaveSelect, Spi}, spi::{Bus, Mode, SlaveSelect, Spi},
}; };
use rusqlite::Connection;
use schedule::Schedule;
use screensaver::{Screensaver, TimeDisplay};
use ssd1351::display::display::Ssd1351; use ssd1351::display::display::Ssd1351;
use time::OffsetDateTime; use time::OffsetDateTime;
use time_tz::{timezones::db::europe::BERLIN, OffsetDateTimeExt}; use time_tz::{timezones::db::europe::BERLIN, OffsetDateTimeExt};
static STAR: &'static [u8] = include_bytes!("../star.raw"); mod action;
static RPI: &'static [u8] = include_bytes!("../rpi.raw"); 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 BLACK: Rgb565 = Rgb565::new(0, 0, 0);
static TIME_COLOR: Rgb565 = Rgb565::new(0b01_111, 0b011_111, 0b01_111);
fn main() { fn main() {
if rppal::system::DeviceInfo::new().is_ok() { 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"))] #[cfg(not(feature = "pc"))]
fn pc_main() {} fn pc_main() {}
@ -43,11 +147,6 @@ fn pc_main() {}
fn pc_main() { fn pc_main() {
use std::num::NonZeroU32; use std::num::NonZeroU32;
use embedded_graphics::primitives::{PrimitiveStyleBuilder, Rectangle, StyledDrawable};
use rand_xoshiro::{
rand_core::{RngCore, SeedableRng},
Xoroshiro128StarStar,
};
use winit::{ use winit::{
dpi::LogicalSize, dpi::LogicalSize,
event::{Event, WindowEvent}, event::{Event, WindowEvent},
@ -55,6 +154,18 @@ fn pc_main() {
window::WindowBuilder, 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 event_loop = EventLoop::new();
let window = WindowBuilder::new() let window = WindowBuilder::new()
.with_inner_size(LogicalSize::new(128, 128)) .with_inner_size(LogicalSize::new(128, 128))
@ -66,10 +177,17 @@ fn pc_main() {
let start = Instant::now(); let start = Instant::now();
let mut iters = 0; let mut iters = 0;
let mut disp = FrameOutput::new(128, 128); 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 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| { event_loop.run(move |event, _, control_flow| {
// ControlFlow::Poll continuously runs the event loop, even if the OS hasn't // ControlFlow::Poll continuously runs the event loop, even if the OS hasn't
// dispatched any events. This is ideal for games and similar applications. // dispatched any events. This is ideal for games and similar applications.
@ -108,78 +226,9 @@ fn pc_main() {
.unwrap(); .unwrap();
// redraw // 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; iters += 1;
//loop_iter(&mut disp).unwrap(); buffer_dirty = ctx.loop_iter(&mut disp, &mut rng);
/*
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;
} }
let mut buffer = surface.buffer_mut().unwrap(); let mut buffer = surface.buffer_mut().unwrap();
@ -196,6 +245,7 @@ fn pc_main() {
} }
buffer.present().unwrap(); buffer.present().unwrap();
buffer_dirty = false; 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() { 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 spi = Spi::new(Bus::Spi0, SlaveSelect::Ss0, 19660800, Mode::Mode0).unwrap();
let gpio = Gpio::new().unwrap(); let gpio = Gpio::new().unwrap();
let dc = gpio.get(25).unwrap().into_output(); let dc = gpio.get(25).unwrap().into_output();
@ -213,25 +277,55 @@ fn rpi_main() {
let spii = SPIInterfaceNoCS::new(spi, dc); let spii = SPIInterfaceNoCS::new(spi, dc);
let mut disp = Ssd1351::new(spii); let mut disp = Ssd1351::new(spii);
// Reset & init // Reset & init display
disp.reset(&mut rst, &mut Delay).unwrap(); disp.reset(&mut rst, &mut Delay).unwrap();
disp.turn_on().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));
} }
fn main_loop(mut disp: Ssd1351<SPIInterfaceNoCS<Spi, OutputPin>>) { main_loop(disp, ctx);
let _ = pwm.join();
}
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(); 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 last_button = Instant::now();
let mut menu = vec![]; 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() let lines = gpiocdev::Request::builder()
.on_chip("/dev/gpiochip0") .on_chip("/dev/gpiochip0")
.with_line(19) .with_line(19)
@ -255,66 +349,85 @@ fn main_loop(mut disp: Ssd1351<SPIInterfaceNoCS<Spi, OutputPin>>) {
let e = lines.read_edge_event().unwrap(); let e = lines.read_edge_event().unwrap();
last_button = Instant::now(); last_button = Instant::now();
match e.offset { match e.offset {
19 => { 5 => {
menu.push(1); menu.push(1);
}, },
6 => { 6 => {
menu.push(2); menu.push(2);
}, },
5 => { 19 => {
menu.push(3); menu.push(3);
}, },
_ => { _ => {
println!("unknown offset: {}", e.offset); 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 // clean up stale menu selection
if !menu.is_empty() && Instant::now().duration_since(last_button).as_secs() >= 10 { if !menu.is_empty() && Instant::now().duration_since(last_button).as_secs() >= 10 {
menu.clear(); menu.clear();
} }
let time = OffsetDateTime::now_utc().to_timezone(BERLIN); // run context loop
if time.minute() == last_min { let dirty = ctx.loop_iter(&mut disp, &mut rng);
sleep_ms(1000); if dirty {
continue;
}
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 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(())
} }

View File

@ -1,31 +1,24 @@
use std::{ use std::{
fs, thread, thread,
time::{Duration, Instant}, time::{Duration, Instant},
}; };
use display_interface_spi::SPIInterfaceNoCS; use display_interface_spi::SPIInterfaceNoCS;
use embedded_graphics::{ use embedded_graphics::{
draw_target::DrawTarget, draw_target::DrawTarget,
mono_font::{ mono_font::{ascii::FONT_10X20, MonoTextStyleBuilder},
ascii::{FONT_10X20, FONT_5X8, FONT_6X9, FONT_9X15}, pixelcolor::Rgb565,
MonoTextStyleBuilder,
},
pixelcolor::{BinaryColor, Rgb565},
prelude::{OriginDimensions, Point, Primitive, Size},
primitives::{PrimitiveStyleBuilder, Rectangle},
text::Text, text::Text,
Drawable, Drawable,
}; };
use embedded_hal::digital::v2::OutputPin;
use image::{ImageBuffer, Rgb};
use linux_embedded_hal::I2cdev;
use rppal::{ use rppal::{
gpio::Gpio, gpio::Gpio,
hal::Delay, hal::Delay,
spi::{Bus, Mode, SlaveSelect, Spi}, spi::{Bus, Mode, SlaveSelect, Spi},
}; };
//use ssd1351::{properties::DisplaySize, mode::{GraphicsMode, displaymode::DisplayModeTrait}}; //use ssd1351::{properties::DisplaySize, mode::{GraphicsMode, displaymode::DisplayModeTrait}};
use time::{format_description, OffsetDateTime, PrimitiveDateTime};
//use time_tz::{timezones::db::europe::BERLIN, OffsetDateTimeExt, PrimitiveDateTimeExt}; //use time_tz::{timezones::db::europe::BERLIN, OffsetDateTimeExt, PrimitiveDateTimeExt};
fn main() { fn main() {

56
src/bin/schedule/mod.rs Normal file
View 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)]
}

Binary file not shown.

194
src/bin/screensaver/mod.rs Normal file
View 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()),
]
}

Binary file not shown.

Binary file not shown.

79
src/bin/status_check.rs Normal file
View 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();
}

View File

@ -1,4 +1,5 @@
use std::{ use std::{
sync::atomic::AtomicBool,
thread::sleep, thread::sleep,
time::{self, Duration}, time::{self, Duration},
}; };
@ -8,13 +9,20 @@ use embedded_graphics::{
pixelcolor::Rgb565, pixelcolor::Rgb565,
prelude::{OriginDimensions, RgbColor, Size}, 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}; use image::{ImageBuffer, Rgb};
#[cfg(feature = "pc")]
pub struct FrameOutput { pub struct FrameOutput {
pub buffer: ImageBuffer<Rgb<u8>, Vec<u8>>, pub buffer: ImageBuffer<Rgb<u8>, Vec<u8>>,
} }
#[cfg(feature = "pc")]
impl FrameOutput { impl FrameOutput {
pub fn new(width: u32, height: u32) -> Self { pub fn new(width: u32, height: u32) -> Self {
FrameOutput { FrameOutput {
@ -23,6 +31,7 @@ impl FrameOutput {
} }
} }
#[cfg(feature = "pc")]
impl DrawTarget for FrameOutput { impl DrawTarget for FrameOutput {
type Color = Rgb565; type Color = Rgb565;
@ -49,32 +58,52 @@ impl DrawTarget for FrameOutput {
} }
} }
#[cfg(feature = "pc")]
impl OriginDimensions for FrameOutput { impl OriginDimensions for FrameOutput {
fn size(&self) -> Size { fn size(&self) -> Size {
Size::new(self.buffer.width(), self.buffer.height()) 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() let input = gpiocdev::Request::builder()
.on_chip("/dev/gpiochip0") .on_chip("/dev/gpiochip0")
.with_line(26) .with_line(26)
.as_input() .as_input()
.with_edge_detection(EdgeDetection::BothEdges) //.with_edge_detection(EdgeDetection::BothEdges)
.with_bias(Bias::PullDown) //.with_debounce_period(Duration::ZERO)
.with_kernel_event_buffer_size(1024)
.with_bias(Bias::PullUp)
.request()?; .request()?;
*/
let start = time::Instant::now(); let start = time::Instant::now();
let mut last_value = Value::Active;
let mut events = Vec::with_capacity(81); let mut events = Vec::with_capacity(81);
while start.elapsed() < timeout && events.len() < 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)? { if input.wait_edge_event(timeout)? {
let event = input.read_edge_event()?; let event = input.read_edge_event()?;
events.push((start.elapsed().as_micros() as u64, event.kind)); events.push((start.elapsed().as_micros() as u64, event.kind));
} }
*/
} }
if events.len() < 81 { if events.len() < 81 {
println!("error: only got {} events", events.len()); println!("error: only got {} events: {:?}", events.len(), events);
return Err(SensorError::Timeout); return Err(SensorError::Timeout);
} }
Ok(events) Ok(events)
@ -169,12 +198,10 @@ pub fn am2302_reading() -> Result<(u16, u16), SensorError> {
out.set_value(26, Value::Active)?; out.set_value(26, Value::Active)?;
sleep(Duration::from_millis(500)); sleep(Duration::from_millis(500));
set_max_priority(); set_max_priority();
// set low for 20 ms
out.set_value(26, Value::Inactive)?; out.set_value(26, Value::Inactive)?;
sleep(Duration::from_millis(3)); sleep(Duration::from_millis(2));
drop(out);
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())); println!("{:?} {:?}", events, events.as_ref().map(|x| x.len()));
set_normal_priority(); set_normal_priority();
let events = events?; 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); 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,
}