Compare commits

..

3 Commits

Author SHA1 Message Date
061abd74b9 feat: bachelor test impl 2024-11-25 21:53:08 +01:00
d0b0de550b feat: light-control and microwave ui using slint and lvgl 2024-11-19 23:00:08 +01:00
0bfcfd494a fix(timer): fix + and - buttons 2024-11-19 22:59:52 +01:00
34 changed files with 5722 additions and 205 deletions

View File

@ -5,8 +5,12 @@
<sourceFolder url="file://$MODULE_DIR$/app/src" isTestSource="false" /> <sourceFolder url="file://$MODULE_DIR$/app/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/embedded-graphics-profiler-display/src" isTestSource="false" /> <sourceFolder url="file://$MODULE_DIR$/embedded-graphics-profiler-display/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" /> <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/lvgl-based/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/slint-based/src" isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/app/target" /> <excludeFolder url="file://$MODULE_DIR$/app/target" />
<excludeFolder url="file://$MODULE_DIR$/target" /> <excludeFolder url="file://$MODULE_DIR$/target" />
<excludeFolder url="file://$MODULE_DIR$/lvgl-based/target" />
<excludeFolder url="file://$MODULE_DIR$/slint-based/target" />
</content> </content>
<orderEntry type="inheritedJdk" /> <orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" /> <orderEntry type="sourceFolder" forTests="false" />

228
Cargo.lock generated
View File

@ -4,9 +4,9 @@ version = 3
[[package]] [[package]]
name = "anstream" name = "anstream"
version = "0.6.15" version = "0.6.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b"
dependencies = [ dependencies = [
"anstyle", "anstyle",
"anstyle-parse", "anstyle-parse",
@ -19,43 +19,43 @@ dependencies = [
[[package]] [[package]]
name = "anstyle" name = "anstyle"
version = "1.0.8" version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9"
[[package]] [[package]]
name = "anstyle-parse" name = "anstyle-parse"
version = "0.2.5" version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9"
dependencies = [ dependencies = [
"utf8parse", "utf8parse",
] ]
[[package]] [[package]]
name = "anstyle-query" name = "anstyle-query"
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 = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c"
dependencies = [ dependencies = [
"windows-sys 0.52.0", "windows-sys",
] ]
[[package]] [[package]]
name = "anstyle-wincon" name = "anstyle-wincon"
version = "3.0.4" version = "3.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125"
dependencies = [ dependencies = [
"anstyle", "anstyle",
"windows-sys 0.52.0", "windows-sys",
] ]
[[package]] [[package]]
name = "anyhow" name = "anyhow"
version = "1.0.88" version = "1.0.93"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e1496f8fb1fbf272686b8d37f523dab3e4a7443300055e74cdaa449f3114356" checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775"
[[package]] [[package]]
name = "atomic" name = "atomic"
@ -77,9 +77,9 @@ dependencies = [
[[package]] [[package]]
name = "autocfg" name = "autocfg"
version = "1.3.0" version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
[[package]] [[package]]
name = "az" name = "az"
@ -128,9 +128,9 @@ checksum = "c3ac9f8b63eca6fd385229b3675f6cc0dc5c8a5c8a54a59d4f52ffd670d87b0c"
[[package]] [[package]]
name = "bytemuck" name = "bytemuck"
version = "1.18.0" version = "1.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94bbb0ad554ad961ddc5da507a12a29b14e4ae5bda06b19f575a3e6079d2e2ae" checksum = "8b37c88a63ffd85d15b406896cc343916d7cf57838a847b3a6f2ca5d39a5695a"
[[package]] [[package]]
name = "byteorder" name = "byteorder"
@ -146,9 +146,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]] [[package]]
name = "clap" name = "clap"
version = "4.5.17" version = "4.5.21"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e5a21b8495e732f1b3c364c9949b201ca7bae518c502c80256c96ad79eaf6ac" checksum = "fb3b4b9e5a7c7514dfa52869339ee98b3156b0bfb4e8a77c4ff4babb64b1604f"
dependencies = [ dependencies = [
"clap_builder", "clap_builder",
"clap_derive", "clap_derive",
@ -156,9 +156,9 @@ dependencies = [
[[package]] [[package]]
name = "clap_builder" name = "clap_builder"
version = "4.5.17" version = "4.5.21"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8cf2dd12af7a047ad9d6da2b6b249759a22a7abc0f474c1dae1777afa4b21a73" checksum = "b17a95aa67cc7b5ebd32aa5370189aa0d79069ef1c64ce893bd30fb24bff20ec"
dependencies = [ dependencies = [
"anstream", "anstream",
"anstyle", "anstyle",
@ -168,33 +168,33 @@ dependencies = [
[[package]] [[package]]
name = "clap_derive" name = "clap_derive"
version = "4.5.13" version = "4.5.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0" checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab"
dependencies = [ dependencies = [
"heck", "heck",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.77", "syn 2.0.89",
] ]
[[package]] [[package]]
name = "clap_lex" name = "clap_lex"
version = "0.7.2" version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" checksum = "afb84c814227b90d6895e01398aee0d8033c00e7466aca416fb6a8e0eb19d8a7"
[[package]] [[package]]
name = "colorchoice" name = "colorchoice"
version = "1.0.2" version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
[[package]] [[package]]
name = "critical-section" name = "critical-section"
version = "1.1.3" version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f64009896348fc5af4222e9cf7d7d82a95a256c634ebcf61c53e4ea461422242" checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b"
[[package]] [[package]]
name = "darling" name = "darling"
@ -217,7 +217,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"strsim", "strsim",
"syn 2.0.77", "syn 2.0.89",
] ]
[[package]] [[package]]
@ -228,7 +228,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806"
dependencies = [ dependencies = [
"darling_core", "darling_core",
"quote", "quote",
"syn 2.0.77", "syn 2.0.89",
] ]
[[package]] [[package]]
@ -239,7 +239,7 @@ checksum = "4e018fccbeeb50ff26562ece792ed06659b9c2dae79ece77c4456bb10d9bf79b"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.77", "syn 2.0.89",
] ]
[[package]] [[package]]
@ -288,9 +288,9 @@ dependencies = [
[[package]] [[package]]
name = "embassy-executor" name = "embassy-executor"
version = "0.6.0" version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ed0e24bdd4a5f4ff1b72ee4f264b1d23e179ea71a77d984b5fd24877a2bbe1" checksum = "f64f84599b0f4296b92a4b6ac2109bc02340094bda47b9766c5f9ec6a318ebf8"
dependencies = [ dependencies = [
"critical-section", "critical-section",
"document-features", "document-features",
@ -300,14 +300,14 @@ dependencies = [
[[package]] [[package]]
name = "embassy-executor-macros" name = "embassy-executor-macros"
version = "0.5.0" version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4d4c0c34b32c2c653c9eecce1cefaf8539dd9a54e61deb5499254f01e2fcac2" checksum = "3577b1e9446f61381179a330fc5324b01d511624c55f25e3c66c9e3c626dbecf"
dependencies = [ dependencies = [
"darling", "darling",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.77", "syn 2.0.89",
] ]
[[package]] [[package]]
@ -497,9 +497,9 @@ dependencies = [
[[package]] [[package]]
name = "embedded-sdmmc" name = "embedded-sdmmc"
version = "0.8.0" 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 = "150f320125310e179b9e73b081173b349e63c5c7d4ca44db4e5b9121b10387ec" checksum = "eb637331040ec9b35f6a8151904d1aca914cc349d14c91b9a3e92ba789b22f3f"
dependencies = [ dependencies = [
"byteorder", "byteorder",
"embedded-hal 1.0.0", "embedded-hal 1.0.0",
@ -531,7 +531,7 @@ dependencies = [
"heck", "heck",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.77", "syn 2.0.89",
] ]
[[package]] [[package]]
@ -552,7 +552,7 @@ dependencies = [
"darling", "darling",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.77", "syn 2.0.89",
] ]
[[package]] [[package]]
@ -579,7 +579,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b94a4b8d74e7cc7baabcca5b2277b41877e039ad9cd49959d48ef94dac7eab4b" checksum = "b94a4b8d74e7cc7baabcca5b2277b41877e039ad9cd49959d48ef94dac7eab4b"
dependencies = [ dependencies = [
"quote", "quote",
"syn 2.0.77", "syn 2.0.89",
"termcolor", "termcolor",
] ]
@ -659,7 +659,7 @@ dependencies = [
"proc-macro-error", "proc-macro-error",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.77", "syn 2.0.89",
] ]
[[package]] [[package]]
@ -690,9 +690,9 @@ dependencies = [
[[package]] [[package]]
name = "esp-riscv-rt" name = "esp-riscv-rt"
version = "0.9.0" version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfc32298ed7c263b06c8b031704d8517cc62c819f2a9d5c261d0cb119634d6e9" checksum = "94aca65db6157aa5f42d9df6595b21462f28207ca4230b799aa3620352ef6a72"
dependencies = [ dependencies = [
"document-features", "document-features",
"riscv", "riscv",
@ -725,6 +725,12 @@ version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "foldhash"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f81ec6369c545a7d40e4589b5597581fa1c441fe1cce96dd1de43159910a36a2"
[[package]] [[package]]
name = "format_no_std" name = "format_no_std"
version = "1.2.0" version = "1.2.0"
@ -742,21 +748,21 @@ dependencies = [
[[package]] [[package]]
name = "futures-core" name = "futures-core"
version = "0.3.30" version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
[[package]] [[package]]
name = "futures-task" name = "futures-task"
version = "0.3.30" version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988"
[[package]] [[package]]
name = "futures-util" name = "futures-util"
version = "0.3.30" version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
dependencies = [ dependencies = [
"futures-core", "futures-core",
"futures-task", "futures-task",
@ -790,9 +796,9 @@ dependencies = [
[[package]] [[package]]
name = "hashbrown" name = "hashbrown"
version = "0.14.5" version = "0.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3"
[[package]] [[package]]
name = "heapless" name = "heapless"
@ -834,9 +840,9 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]] [[package]]
name = "indexmap" name = "indexmap"
version = "2.5.0" version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da"
dependencies = [ dependencies = [
"equivalent", "equivalent",
"hashbrown", "hashbrown",
@ -880,10 +886,11 @@ dependencies = [
[[package]] [[package]]
name = "kolibri-embedded-gui" name = "kolibri-embedded-gui"
version = "0.0.0-alpha.1" version = "0.0.0-alpha.1"
source = "git+https://github.com/Yandrik/kolibri.git?branch=optimizations#f3e92212ac29431561748e6c37427651b3151bc4" source = "git+https://github.com/Yandrik/kolibri.git?branch=optimizations#ab04bf432d09eace365029e6420bf6e59ac8fc2f"
dependencies = [ dependencies = [
"embedded-graphics", "embedded-graphics",
"embedded-iconoir", "embedded-iconoir",
"foldhash",
"heapless 0.7.17", "heapless 0.7.17",
] ]
@ -932,9 +939,9 @@ checksum = "c3c8dda44ff03a2f238717214da50f65d5a53b45cd213a7370424ffdb6fae815"
[[package]] [[package]]
name = "minijinja" name = "minijinja"
version = "2.2.0" version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d7d3e3a3eece1fa4618237ad41e1de855ced47eab705cec1c9a920e1d1c5aad" checksum = "2c37e1b517d1dcd0e51dc36c4567b9d5a29262b3ec8da6cb5d35e27a8fb529b5"
dependencies = [ dependencies = [
"serde", "serde",
] ]
@ -990,9 +997,9 @@ checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
[[package]] [[package]]
name = "pin-project-lite" name = "pin-project-lite"
version = "0.2.14" version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff"
[[package]] [[package]]
name = "pin-utils" name = "pin-utils"
@ -1002,9 +1009,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]] [[package]]
name = "portable-atomic" name = "portable-atomic"
version = "1.7.0" version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da544ee218f0d287a911e9c99a39a8c9bc8fcad3cb8db5959940044ecfc67265" checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2"
[[package]] [[package]]
name = "proc-macro-crate" name = "proc-macro-crate"
@ -1041,9 +1048,9 @@ dependencies = [
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.86" version = "1.0.92"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0"
dependencies = [ dependencies = [
"unicode-ident", "unicode-ident",
] ]
@ -1080,23 +1087,43 @@ checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
[[package]] [[package]]
name = "riscv" name = "riscv"
version = "0.11.1" version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f5c1b8bf41ea746266cdee443d1d1e9125c86ce1447e1a2615abd34330d33a9" checksum = "5ea8ff73d3720bdd0a97925f0bf79ad2744b6da8ff36be3840c48ac81191d7a7"
dependencies = [ dependencies = [
"critical-section", "critical-section",
"embedded-hal 1.0.0", "embedded-hal 1.0.0",
"paste",
"riscv-macros",
"riscv-pac",
] ]
[[package]] [[package]]
name = "riscv-rt-macros" name = "riscv-macros"
version = "0.2.1" version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8d100d466dbb76681ef6a9386f3da9abc570d57394e86da0ba5af8c4408486d" checksum = "f265be5d634272320a7de94cea15c22a3bfdd4eb42eb43edc528415f066a1f25"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 1.0.109", "syn 2.0.89",
]
[[package]]
name = "riscv-pac"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8188909339ccc0c68cfb5a04648313f09621e8b87dc03095454f1a11f6c5d436"
[[package]]
name = "riscv-rt-macros"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30f19a85fe107b65031e0ba8ec60c34c2494069fe910d6c297f5e7cb5a6f76d0"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.89",
] ]
[[package]] [[package]]
@ -1110,9 +1137,9 @@ dependencies = [
[[package]] [[package]]
name = "rustversion" name = "rustversion"
version = "1.0.17" version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248"
[[package]] [[package]]
name = "scopeguard" name = "scopeguard"
@ -1122,9 +1149,9 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]] [[package]]
name = "semihosting" name = "semihosting"
version = "0.1.14" version = "0.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e11f0f8bcc4088e72905fcd826b820e72de6113218c22f1734cd94a3a5cf9172" checksum = "a5c5996e5d1dec34b0dff3285e27124e70964504e3fd361bce330dc476cebafd"
[[package]] [[package]]
name = "semver" name = "semver"
@ -1134,29 +1161,29 @@ checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b"
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.210" version = "1.0.215"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f"
dependencies = [ dependencies = [
"serde_derive", "serde_derive",
] ]
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.210" version = "1.0.215"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.77", "syn 2.0.89",
] ]
[[package]] [[package]]
name = "serde_spanned" name = "serde_spanned"
version = "0.6.7" version = "0.6.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d" checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1"
dependencies = [ dependencies = [
"serde", "serde",
] ]
@ -1216,7 +1243,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"rustversion", "rustversion",
"syn 2.0.77", "syn 2.0.89",
] ]
[[package]] [[package]]
@ -1232,9 +1259,9 @@ dependencies = [
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.77" version = "2.0.89"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" checksum = "44d46482f1c1c87acd84dea20c1bf5ebff4c757009ed6bf19cfd36fb10e92c4e"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -1273,9 +1300,9 @@ dependencies = [
[[package]] [[package]]
name = "toml_edit" name = "toml_edit"
version = "0.22.20" version = "0.22.22"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d" checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5"
dependencies = [ dependencies = [
"indexmap", "indexmap",
"serde", "serde",
@ -1313,9 +1340,9 @@ checksum = "e87a2ed6b42ec5e28cc3b94c09982969e9227600b2e3dcbc1db927a84c06bd69"
[[package]] [[package]]
name = "unicode-ident" name = "unicode-ident"
version = "1.0.13" version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83"
[[package]] [[package]]
name = "utf8parse" name = "utf8parse"
@ -1347,16 +1374,7 @@ version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
dependencies = [ dependencies = [
"windows-sys 0.59.0", "windows-sys",
]
[[package]]
name = "windows-sys"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets",
] ]
[[package]] [[package]]
@ -1434,9 +1452,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]] [[package]]
name = "winnow" name = "winnow"
version = "0.6.18" version = "0.6.20"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f" checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b"
dependencies = [ dependencies = [
"memchr", "memchr",
] ]
@ -1464,9 +1482,9 @@ dependencies = [
[[package]] [[package]]
name = "xtensa-lx-rt" name = "xtensa-lx-rt"
version = "0.17.1" version = "0.17.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2ceb69c1487b78d83531c5d94fb81d0dceef1ccb0affba29f29420b1f72d3ddb" checksum = "5c0307d03dadbf95633942e13901984f2059df4c963367348168cbd21c962669"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bare-metal", "bare-metal",
@ -1490,5 +1508,5 @@ dependencies = [
"darling", "darling",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.77", "syn 2.0.89",
] ]

View File

@ -19,6 +19,9 @@ path = "src/bin/xpt.rs"
name = "calibrate" name = "calibrate"
path = "src/bin/calibrate.rs" path = "src/bin/calibrate.rs"
[[bin]]
name = "light-control"
[lib] [lib]
[dependencies] [dependencies]

19
app/rustfmt.toml Normal file
View File

@ -0,0 +1,19 @@
# Edition
edition = "2021"
# Comments
format_code_in_doc_comments = true
normalize_comments = true
wrap_comments = true
# Imports
group_imports = "StdExternalCrate"
imports_granularity = "Crate"
imports_layout = "HorizontalVertical"
# Miscellaneous
enum_discrim_align_threshold = 25
hex_literal_case = "Upper"
# Length
max_width=60

View File

@ -1,6 +1,8 @@
#![no_std] #![no_std]
#![no_main] #![no_main]
extern crate alloc;
use core::{cell::RefCell, cmp::min, fmt}; use core::{cell::RefCell, cmp::min, fmt};
use display_interface_spi::SPIInterface; use display_interface_spi::SPIInterface;
@ -42,6 +44,7 @@ use kolibri_embedded_gui::{
style::medsize_rgb565_style, style::medsize_rgb565_style,
ui::{Interaction, Ui}, ui::{Interaction, Ui},
}; };
use lvgl::{self, Display, DrawBuffer};
use mipidsi::{ use mipidsi::{
models::ILI9486Rgb565, models::ILI9486Rgb565,
options::{ColorInversion, ColorOrder, Orientation, Rotation}, options::{ColorInversion, ColorOrder, Orientation, Rotation},
@ -50,6 +53,19 @@ use mipidsi::{
use static_cell::StaticCell; use static_cell::StaticCell;
use xpt2046::Xpt2046; use xpt2046::Xpt2046;
// fn init_heap() {
// const HEAP_SIZE: usize = 32 * 1024;
// static mut HEAP: MaybeUninit<[u8; HEAP_SIZE]> = MaybeUninit::uninit();
//
// unsafe {
// esp_alloc::HEAP.add_region(esp_alloc::HeapRegion::new(
// HEAP.as_mut_ptr() as *mut u8,
// HEAP_SIZE,
// esp_alloc::MemoryCapability::Internal.into(),
// ));
// }
// }
#[embassy_executor::task] #[embassy_executor::task]
async fn touch_task( async fn touch_task(
touch_irq: GpioPin<36>, touch_irq: GpioPin<36>,
@ -235,10 +251,10 @@ async fn main(spawner: Spawner) {
let mut display = ProfilerDisplay::new(display); let mut display = ProfilerDisplay::new(display);
{ const HOR_RES: u32 = 320;
let mut ui = Ui::new_fullscreen(&mut display, medsize_rgb565_style()); const VER_RES: u32 = 240;
ui.clear_background().ok(); lvgl::init();
} {}
backlight.set_high(); backlight.set_high();
// init touchscreen pins // init touchscreen pins
@ -287,6 +303,7 @@ async fn main(spawner: Spawner) {
// Periodically feed the RWDT watchdog timer when our tasks are not running: // Periodically feed the RWDT watchdog timer when our tasks are not running:
let mut sm = SmartstateProvider::<20>::new(); let mut sm = SmartstateProvider::<20>::new();
loop { loop {
// SMART REDRAWING ENABLE / DISABLE // SMART REDRAWING ENABLE / DISABLE
// sm.force_redraw_all(); // sm.force_redraw_all();

View File

@ -0,0 +1,516 @@
#![no_std]
#![no_main]
extern crate alloc;
use core::{cell::RefCell, cmp::min, fmt};
use display_interface_spi::SPIInterface;
use embassy_embedded_hal::shared_bus::blocking::spi::SpiDevice;
use embassy_executor::Spawner;
use embassy_sync::{
blocking_mutex::{raw::NoopRawMutex, NoopMutex},
signal::Signal,
};
use embassy_time::{Duration, Instant, Timer};
use embedded_graphics::{
mono_font::ascii,
pixelcolor::Rgb565,
prelude::{DrawTarget, Point, RgbColor, Size, WebColors},
};
use embedded_graphics_profiler_display::ProfilerDisplay;
use esp_backtrace as _;
use esp_hal::{
clock::ClockControl,
gpio::{GpioPin, Input, Io, Level, Output, Pull, NO_PIN},
peripherals::{Peripherals, SPI2, SPI3},
prelude::*,
rtc_cntl::Rtc,
spi::{master::Spi, FullDuplexMode, SpiMode},
system::SystemControl,
timer::timg::TimerGroup,
};
use esp_println::println;
use kolibri_cyd_tester_app_embassy::Debouncer;
use kolibri_embedded_gui::{
button::Button,
checkbox::Checkbox,
icon::IconWidget,
iconbutton::IconButton,
icons::{size12px, size24px, size32px, size48px, size96px},
label::Label,
smartstate::SmartstateProvider,
spacer::Spacer,
style::medsize_rgb565_style,
ui::{Interaction, Ui},
};
use mipidsi::{
models::ILI9486Rgb565,
options::{ColorInversion, ColorOrder, Orientation, Rotation},
Builder,
};
use slint::platform::software_renderer::MinimalSoftwareWindow;
use static_cell::StaticCell;
use xpt2046::Xpt2046;
slint::include_modules!();
use esp_alloc as _;
fn init_heap() {
const HEAP_SIZE: usize = 32 * 1024;
static mut HEAP: MaybeUninit<[u8; HEAP_SIZE]> = MaybeUninit::uninit();
unsafe {
esp_alloc::HEAP.add_region(esp_alloc::HeapRegion::new(
HEAP.as_mut_ptr() as *mut u8,
HEAP_SIZE,
esp_alloc::MemoryCapability::Internal.into(),
));
}
}
#[embassy_executor::task]
async fn touch_task(
touch_irq: GpioPin<36>,
spi: &'static mut NoopMutex<RefCell<Spi<'static, SPI3, FullDuplexMode>>>,
touch_cs: GpioPin<33>,
touch_signal: &'static Signal<NoopRawMutex, Option<Point>>,
) -> ! {
let mut touch_driver = Xpt2046::new(
SpiDevice::new(spi, Output::new(touch_cs, Level::Low)),
Input::new(touch_irq, Pull::Up),
xpt2046::Orientation::LandscapeFlipped,
);
touch_driver.set_num_samples(16);
touch_driver.init(&mut embassy_time::Delay).unwrap();
println!("touch task");
loop {
touch_driver.run().expect("Running Touch driver failed");
if touch_driver.is_touched() {
let point = touch_driver.get_touch_point();
touch_signal.signal(Some(Point::new(point.x + 25, 240 - point.y)));
} else {
touch_signal.signal(None);
}
Timer::after(Duration::from_millis(1)).await; // 100 a second
// Your touch handling logic here
}
}
struct AppData {
timer_start: Instant,
timer_set_duration: Duration,
timer_remaining_duration: Duration,
timer_running: bool,
timer_paused: bool,
}
impl AppData {
fn new() -> Self {
Self {
timer_start: Instant::now(),
timer_set_duration: Duration::from_secs(10),
timer_remaining_duration: Duration::from_secs(10),
timer_running: false,
timer_paused: false,
}
}
fn set_timer_duration(&mut self, duration: Duration) {
self.timer_set_duration = duration;
}
fn add_secs(&mut self, secs: u64) {
self.set_timer_duration(
self.timer_set_duration
.checked_add(Duration::from_secs(secs))
.unwrap_or(Duration::from_secs(5999))
.min(Duration::from_secs(5999)),
);
}
fn sub_secs(&mut self, secs: u64) {
self.set_timer_duration(
self.timer_set_duration
.checked_sub(Duration::from_secs(secs))
.unwrap_or(Duration::from_secs(10))
.max(Duration::from_secs(10)),
);
}
fn start_timer(&mut self) {
if !self.timer_paused {
self.timer_remaining_duration = self.timer_set_duration;
}
self.timer_start = Instant::now();
self.timer_running = true;
self.timer_paused = false;
}
fn pause_timer(&mut self) {
self.timer_paused = true;
self.timer_remaining_duration = self
.timer_remaining_duration
.checked_sub(self.timer_start.elapsed())
.unwrap_or(Duration::from_secs(0));
}
fn reset_timer(&mut self) {
self.timer_start = Instant::now();
self.timer_paused = false;
self.timer_running = false;
self.timer_remaining_duration = self.timer_set_duration;
}
fn remaining(&self) -> Duration {
if self.timer_stopped() {
self.timer_set_duration
} else if self.timer_paused() {
self.timer_remaining_duration
} else {
self.timer_remaining_duration
.checked_sub(self.timer_start.elapsed())
.unwrap_or(Duration::from_secs(0))
}
}
fn timer_stopped(&self) -> bool {
!self.timer_running
}
fn timer_paused(&self) -> bool {
self.timer_running && self.timer_paused
}
fn timer_running(&self) -> bool {
self.timer_running && !self.timer_paused
}
fn timer_finished(&self) -> bool {
self.timer_running && self.remaining() == Duration::from_secs(0)
}
}
struct MyPlatform {
window: Rc<MinimalSoftwareWindow>,
}
impl Platform for MyPlatform {
fn create_window_adapter(&self) -> Result<Rc<dyn slint::platform::WindowAdapter>, slint::PlatformError> {
// Since on MCUs, there can be only one window, just return a clone of self.window.
// We'll also use the same window in the event loop.
Ok(self.window.clone())
}
fn duration_since_start(&self) -> core::time::Duration {
core::time::Duration::from_micros(self.timer.get_time())
}
// optional: You can put the event loop there, or in the main function, see later
fn run_event_loop(&self) -> Result<(), slint::PlatformError> {
todo!();
}
}
#[main]
async fn main(spawner: Spawner) {
init_heap();
let peripherals = Peripherals::take();
let system = SystemControl::new(peripherals.SYSTEM);
let mut clocks = ClockControl::boot_defaults(system.clock_control).freeze();
// Enable the RWDT watchdog timer:
let mut rtc = Rtc::new(peripherals.LPWR);
rtc.rwdt.set_timeout(2.secs());
rtc.rwdt.enable();
println!("RWDT watchdog enabled!");
// Initialize the SYSTIMER peripheral, and then Embassy:
let timg0 = TimerGroup::new(peripherals.TIMG0, &clocks);
esp_hal_embassy::init(&clocks, timg0.timer0);
println!("Embassy initialized!");
let io = Io::new(peripherals.GPIO, peripherals.IO_MUX);
// let mut led = Output::new(io.pins.gpio5, Level::High);
// Initialize SPI
let sclk = io.pins.gpio14;
let miso = io.pins.gpio12;
let mosi = io.pins.gpio13;
let cs = io.pins.gpio15;
let dc = io.pins.gpio2;
let mut backlight = Output::new(io.pins.gpio21, Level::Low);
// Note: RST is not initialized as it's set to -1 in the instructions
// up to 80MHz is possible (even tho the display driver isn't supposed to be
// that fast) Dataseheet sais 10MHz, so we're gonna go with that
let mut spi = Spi::new(peripherals.SPI2, 10.MHz(), SpiMode::Mode0, &mut clocks).with_pins(
Some(sclk),
Some(mosi),
Some(miso),
NO_PIN,
);
static DISP_SPI_BUS: StaticCell<NoopMutex<RefCell<Spi<SPI2, FullDuplexMode>>>> =
StaticCell::new();
let spi_bus = NoopMutex::new(RefCell::new(spi));
let spi_bus = DISP_SPI_BUS.init(spi_bus);
let di = SPIInterface::new(
SpiDevice::new(spi_bus, Output::new(cs, Level::Low)),
Output::new(dc, Level::Low),
);
let display = Builder::new(ILI9486Rgb565, di)
.orientation(Orientation {
rotation: Rotation::Deg90,
mirrored: true,
})
.color_order(ColorOrder::Bgr)
.invert_colors(ColorInversion::Inverted)
.init(&mut embassy_time::Delay)
.unwrap();
let mut display = ProfilerDisplay::new(display);
let window = MinimalSoftwareWindow::new(Default::default());
slint::platform::set_platform(Box::new(MyPlatform {
window: window.clone(),
//...
}))
.unwrap();
let ui =
backlight.set_high();
// init touchscreen pins
let touch_irq = io.pins.gpio36;
let touch_mosi = io.pins.gpio32;
let touch_miso = io.pins.gpio39;
let touch_clk = io.pins.gpio25;
let touch_cs = io.pins.gpio33;
// 2MHz is the MAX! DO NOT DECREASE! This is really important.
let mut touch_spi = Spi::new(peripherals.SPI3, 2.MHz(), SpiMode::Mode0, &mut clocks).with_pins(
Some(touch_clk),
Some(touch_mosi),
Some(touch_miso),
NO_PIN,
);
static TOUCH_SPI_BUS: StaticCell<NoopMutex<RefCell<Spi<SPI3, FullDuplexMode>>>> =
StaticCell::new();
let touch_spi_bus = NoopMutex::new(RefCell::new(touch_spi));
let touch_spi_bus = TOUCH_SPI_BUS.init(touch_spi_bus);
let touch_signal = Signal::new();
static TOUCH_SIGNAL: StaticCell<Signal<NoopRawMutex, Option<Point>>> = StaticCell::new();
let touch_signal = &*TOUCH_SIGNAL.init(touch_signal);
spawner
.spawn(touch_task(touch_irq, touch_spi_bus, touch_cs, touch_signal))
.unwrap();
// TODO: Spawn some tasks
let _ = spawner;
// variables
let mut appdata = AppData::new();
let (mut prev_mins, mut prev_secs, mut prev_millis) = (0, 0, 0);
let mut finished = false;
// touchpoints
let mut last_touch = None;
static BUF_CELL: StaticCell<[Rgb565; 100 * 100]> = StaticCell::new();
let buf = BUF_CELL.init([Rgb565::BLACK; 100 * 100]);
let mut textbuf = [0u8; 64];
// Periodically feed the RWDT watchdog timer when our tasks are not running:
let mut sm = SmartstateProvider::<20>::new();
loop {
// SMART REDRAWING ENABLE / DISABLE
// sm.force_redraw_all();
let start_time = embassy_time::Instant::now();
sm.restart_counter();
let mut ui = Ui::new_fullscreen(&mut display, medsize_rgb565_style());
if let Some(touch) = touch_signal.try_take() {
let interact = match (touch, last_touch) {
(Some(point), Some(_)) => Interaction::Drag(point),
(Some(point), None) => Interaction::Click(point),
(None, Some(point)) => Interaction::Release(point),
(None, None) => Interaction::None,
};
ui.interact(interact);
// println!("{:?}, {:?}, {:?}", last_touch, touch, interact);
last_touch = touch;
}
// BUFFER ENABLE/DISABLE
ui.set_buffer(buf);
let start_draw_time = embassy_time::Instant::now();
ui.sub_ui(|ui| {
ui.style_mut().default_font = ascii::FONT_9X18_BOLD;
ui.add(Label::new("Kolibri Timer App").smartstate(sm.next()));
Ok(())
})
.ok();
let remaining = appdata.remaining();
ui.add(Spacer::new(Size::new(0, 60)));
ui.add_horizontal(Spacer::new(Size::new(80, 0)));
ui.sub_ui(|ui| {
ui.style_mut().default_font = ascii::FONT_10X20;
if remaining.as_secs() / 60 != prev_mins {
sm.peek().force_redraw();
prev_mins = remaining.as_secs() / 60;
}
ui.add_horizontal(
Label::new(
&format_no_std::show(
&mut textbuf,
format_args!("{:02}", remaining.as_secs() / 60),
)
.unwrap(),
)
.smartstate(sm.next()),
);
ui.add_horizontal(Label::new(":").smartstate(sm.next()));
if remaining.as_secs() % 60 != prev_secs {
sm.peek().force_redraw();
prev_secs = remaining.as_secs() % 60;
}
ui.add_horizontal(
Label::new(
&format_no_std::show(
&mut textbuf,
format_args!("{:02}", remaining.as_secs() % 60),
)
.unwrap(),
)
.smartstate(sm.next()),
);
ui.add_horizontal(Label::new(":").smartstate(sm.next()));
if remaining.as_millis() % 1000 != prev_millis {
sm.peek().force_redraw();
prev_millis = remaining.as_millis() % 1000;
}
ui.add(
Label::new(
&format_no_std::show(
&mut textbuf,
format_args!("{:03}", remaining.as_millis() % 1000),
)
.unwrap(),
)
.smartstate(sm.next()),
);
Ok(())
})
.ok();
ui.add_horizontal(Spacer::new(Size::new(65, 0)));
ui.sub_ui(|ui| {
if appdata.timer_running() {
ui.style_mut().icon_color = Rgb565::CSS_LIGHT_GRAY;
}
if ui
.add_horizontal(IconButton::new(size32px::actions::AddCircle).smartstate(sm.next()))
.clicked()
{
if !appdata.timer_running() {
appdata.add_secs(10);
}
}
ui.add_horizontal(Label::new("+/- 10s").smartstate(sm.next()));
if ui
.add(IconButton::new(size32px::actions::MinusCircle).smartstate(sm.next()))
.clicked()
{
if !appdata.timer_running() {
appdata.sub_secs(10);
}
}
Ok(())
})
.ok();
ui.add_horizontal(Spacer::new(Size::new(80, 0)));
if ui
.add_horizontal(IconButton::new(size48px::actions::Undo).smartstate(sm.next()))
.clicked()
{
appdata.reset_timer();
sm.force_redraw_all();
}
if !finished && appdata.timer_finished() {
sm.peek().force_redraw();
}
if appdata.timer_finished() {
if ui
.add_horizontal(
IconButton::new(size48px::actions::RemoveSquare).smartstate(sm.next()),
)
.clicked()
{
appdata.reset_timer();
sm.force_redraw_all();
}
} else if appdata.timer_running() {
if ui
.add_horizontal(IconButton::new(size48px::music::Pause).smartstate(sm.next()))
.clicked()
{
appdata.pause_timer();
sm.force_redraw_all();
}
} else {
if ui
.add_horizontal(IconButton::new(size48px::music::Play).smartstate(sm.next()))
.clicked()
{
appdata.start_timer();
sm.force_redraw_all();
}
}
finished = appdata.timer_finished();
let end_time = embassy_time::Instant::now();
let draw_time = display.get_time();
let prep_time = start_draw_time - start_time;
let proc_time = end_time - start_draw_time;
let proc_time = proc_time - min(draw_time, proc_time);
rtc.rwdt.feed();
if draw_time.as_micros() > 0 {
println!(
"draw time: {}.{:03}ms | prep time: {}.{:03}ms | proc time: {}.{:03}ms | total time: {}.{:03}ms",
draw_time.as_millis(),
draw_time.as_micros() % 100,
prep_time.as_millis(),
prep_time.as_micros() % 100,
proc_time.as_millis(),
proc_time.as_micros() % 100,
(draw_time + prep_time + proc_time).as_millis(),
(draw_time + prep_time + proc_time).as_micros() % 100, );
}
display.reset_time();
Timer::after(Duration::from_millis(17)).await; // 60 a second
}
}

View File

@ -0,0 +1,453 @@
#![no_std]
#![no_main]
use core::{cell::RefCell, cmp::min, str::FromStr};
use display_interface_spi::SPIInterface;
use embassy_embedded_hal::shared_bus::blocking::spi::SpiDevice;
use embassy_executor::Spawner;
use embassy_sync::{
blocking_mutex::{raw::NoopRawMutex, NoopMutex},
signal::Signal,
};
use embassy_time::{Duration, Timer};
use embedded_graphics::{
mono_font::ascii,
pixelcolor::Rgb565,
prelude::{Point, RgbColor, Size, WebColors},
};
use embedded_graphics_profiler_display::ProfilerDisplay;
use esp_backtrace as _;
use esp_hal::{
clock::ClockControl,
gpio::{
GpioPin,
Input,
Io,
Level,
Output,
Pull,
NO_PIN,
},
peripherals::{Peripherals, SPI2, SPI3},
prelude::*,
rtc_cntl::Rtc,
spi::{master::Spi, FullDuplexMode, SpiMode},
system::SystemControl,
timer::timg::TimerGroup,
};
use esp_println::println;
use kolibri_embedded_gui::{
iconbutton::IconButton,
icons::size32px,
label::{HashLabel, Hasher, Label},
slider::Slider,
smartstate::SmartstateProvider,
spacer::Spacer,
style::medsize_rgb565_style,
toggle_switch::ToggleSwitch,
ui::{Interaction, Ui},
};
use mipidsi::{
models::ILI9341Rgb565,
options::{ColorOrder, Orientation, Rotation},
Builder,
};
use static_cell::StaticCell;
use xpt2046::Xpt2046;
#[embassy_executor::task]
async fn touch_task(
touch_irq: GpioPin<36>,
spi: &'static mut NoopMutex<
RefCell<Spi<'static, SPI3, FullDuplexMode>>,
>,
touch_cs: GpioPin<33>,
touch_signal: &'static Signal<
NoopRawMutex,
Option<Point>,
>,
) -> ! {
let mut touch_driver = Xpt2046::new(
SpiDevice::new(
spi,
Output::new(touch_cs, Level::Low),
),
Input::new(touch_irq, Pull::Up),
xpt2046::Orientation::LandscapeFlipped,
);
touch_driver.set_num_samples(16);
touch_driver.init(&mut embassy_time::Delay).unwrap();
println!("touch task");
loop {
touch_driver
.run()
.expect("Running Touch driver failed");
if touch_driver.is_touched() {
let point = touch_driver.get_touch_point();
touch_signal.signal(Some(Point::new(
point.x + 25,
240 - point.y,
)));
} else {
touch_signal.signal(None);
}
Timer::after(Duration::from_millis(1)).await; // 100
// a second
// Your touch handling logic here
}
}
fn lerp_fixed(start: u8, end: u8, t: u8, max_t: u8) -> u8 {
let (start, end, t, max_t) =
(start as u16, end as u16, t as u16, max_t as u16);
let t = t.min(max_t);
let result = start
+ ((end - start.min(end)) * t + (max_t / 2))
/ max_t;
result as u8
}
#[derive(Debug, Clone)]
struct Lamp {
pub name: heapless::String<64>,
pub on: bool,
pub brightness: i16,
}
impl Lamp {
pub fn new(name: &str) -> Self {
Self {
name: heapless::String::from_str(name).unwrap(),
on: false,
brightness: 255,
}
}
}
enum Page {
Home,
LampCtrl(usize),
}
struct AppData {
lamps: heapless::Vec<Lamp, 8>,
}
impl AppData {
fn new() -> Self {
Self {
lamps: heapless::Vec::new(),
}
}
fn add_lamp(&mut self, name: &str) {
self.lamps.push(Lamp::new(name)).unwrap();
}
}
#[main]
async fn main(spawner: Spawner) {
let peripherals = Peripherals::take();
let system = SystemControl::new(peripherals.SYSTEM);
let mut clocks =
ClockControl::boot_defaults(system.clock_control)
.freeze();
// Enable the RWDT watchdog timer:
let mut rtc = Rtc::new(peripherals.LPWR);
rtc.rwdt.set_timeout(2.secs());
rtc.rwdt.enable();
println!("RWDT watchdog enabled!");
// Initialize the SYSTIMER peripheral, and then Embassy:
let timg0 = TimerGroup::new(peripherals.TIMG0, &clocks);
esp_hal_embassy::init(&clocks, timg0.timer0);
println!("Embassy initialized!");
let io = Io::new(peripherals.GPIO, peripherals.IO_MUX);
// let mut led = Output::new(io.pins.gpio5,
// Level::High); Initialize SPI
let sclk = io.pins.gpio14;
let miso = io.pins.gpio12;
let mosi = io.pins.gpio13;
let cs = io.pins.gpio15;
let dc = io.pins.gpio2;
let mut backlight =
Output::new(io.pins.gpio21, Level::Low);
// Note: RST is not initialized as it's set to -1 in the
// instructions
// up to 80MHz is possible (even tho the display driver
// isn't supposed to be that fast) Dataseheet sais
// 10MHz, so we're gonna go with that
let mut spi = Spi::new(
peripherals.SPI2,
10.MHz(),
SpiMode::Mode0,
&mut clocks,
)
.with_pins(
Some(sclk),
Some(mosi),
Some(miso),
NO_PIN,
);
static DISP_SPI_BUS: StaticCell<
NoopMutex<RefCell<Spi<SPI2, FullDuplexMode>>>,
> = StaticCell::new();
let spi_bus = NoopMutex::new(RefCell::new(spi));
let spi_bus = DISP_SPI_BUS.init(spi_bus);
let di = SPIInterface::new(
SpiDevice::new(
spi_bus,
Output::new(cs, Level::Low),
),
Output::new(dc, Level::Low),
);
let display = Builder::new(ILI9341Rgb565, di)
.orientation(Orientation {
rotation: Rotation::Deg90,
mirrored: true,
})
.color_order(ColorOrder::Bgr)
// .invert_colors(ColorInversion::Inverted)
.init(&mut embassy_time::Delay)
.unwrap();
let mut display = ProfilerDisplay::new(display);
let style = medsize_rgb565_style();
{
let mut ui =
Ui::new_fullscreen(&mut display, style);
ui.clear_background().ok();
}
backlight.set_high();
// init touchscreen pins
let touch_irq = io.pins.gpio36;
let touch_mosi = io.pins.gpio32;
let touch_miso = io.pins.gpio39;
let touch_clk = io.pins.gpio25;
let touch_cs = io.pins.gpio33;
// 2MHz is the MAX! DO NOT DECREASE! This is really
// important.
let mut touch_spi = Spi::new(
peripherals.SPI3,
2.MHz(),
SpiMode::Mode0,
&mut clocks,
)
.with_pins(
Some(touch_clk),
Some(touch_mosi),
Some(touch_miso),
NO_PIN,
);
static TOUCH_SPI_BUS: StaticCell<
NoopMutex<RefCell<Spi<SPI3, FullDuplexMode>>>,
> = StaticCell::new();
let touch_spi_bus =
NoopMutex::new(RefCell::new(touch_spi));
let touch_spi_bus = TOUCH_SPI_BUS.init(touch_spi_bus);
let touch_signal = Signal::new();
static TOUCH_SIGNAL: StaticCell<
Signal<NoopRawMutex, Option<Point>>,
> = StaticCell::new();
let touch_signal = &*TOUCH_SIGNAL.init(touch_signal);
spawner
.spawn(touch_task(
touch_irq,
touch_spi_bus,
touch_cs,
touch_signal,
))
.unwrap();
// TODO: Spawn some tasks
let _ = spawner;
// variables
let mut appdata = AppData::new();
appdata.add_lamp("Front Door");
appdata.add_lamp("Living Room");
// appdata.lamps[1].on = true;
appdata.add_lamp("Bedroom");
// appdata.lamps[2].on = true;
appdata.add_lamp("Bathroom");
appdata.add_lamp("Porch");
let mut cur_page = Page::Home;
// touchpoints
let mut last_touch = None;
static BUF_CELL: StaticCell<[Rgb565; 200 * 100]> =
StaticCell::new();
let buf = BUF_CELL.init([Rgb565::BLACK; 200 * 100]);
let mut textbuf = [0u8; 64];
let hasher = Hasher::new();
// Periodically feed the RWDT watchdog timer when our
// tasks are not running:
let mut sm = SmartstateProvider::<20>::new();
loop {
// SMART REDRAWING ENABLE / DISABLE
// sm.force_redraw_all();
let start_time = embassy_time::Instant::now();
sm.restart_counter();
let mut ui =
Ui::new_fullscreen(&mut display, style);
if let Some(touch) = touch_signal.try_take() {
let interact = match (touch, last_touch) {
(Some(point), Some(_)) => {
Interaction::Drag(point)
}
(Some(point), None) => {
Interaction::Click(point)
}
(None, Some(point)) => {
Interaction::Release(point)
}
(None, None) => Interaction::None,
};
ui.interact(interact);
// println!("{:?}, {:?}, {:?}", last_touch,
// touch, interact);
last_touch = touch;
}
// BUFFER ENABLE/DISABLE
ui.set_buffer(buf);
let start_draw_time = embassy_time::Instant::now();
if let Page::Home = cur_page {
ui.add_centered(
HashLabel::new(
"Light Control App (Kolibri)",
sm.next(),
&hasher,
)
// .smartstate(sm.next())
.with_font(ascii::FONT_9X18_BOLD),
);
}
match cur_page {
Page::Home => {
for (i, lamp) in
appdata.lamps.iter().enumerate()
{
let mut break_loop = false;
ui.sub_ui(|ui| {
ui.style_mut().icon_color = if lamp.on {
Rgb565::CSS_GOLD
} else {
Rgb565::WHITE
};
if ui
.add_horizontal(
IconButton::new(size32px::home::LightBulb)
.label(lamp.name.as_str())
.smartstate(sm.next()),
)
.clicked()
{
cur_page = Page::LampCtrl(i);
ui.clear_background().ok();
sm.force_redraw_all();
break_loop = true;
}
Ok(())
})
.ok();
if break_loop {
break;
}
if i % 3 == 2 {
ui.new_row();
}
}
}
Page::LampCtrl(lamp) => {
let lamp = &mut appdata.lamps[lamp];
if ui
.add_horizontal(
IconButton::new(size32px::navigation::NavArrowLeft).smartstate(sm.next()),
)
.clicked()
{
cur_page = Page::Home;
ui.clear_background().ok();
sm.force_redraw_all();
continue;
}
ui.add_horizontal(Spacer::new(Size::new(
30, 0,
)));
ui.add(
Label::new(lamp.name.as_str())
.smartstate(sm.next())
.with_font(ascii::FONT_9X18_BOLD),
);
ui.add(Spacer::new(Size::new(0, 20)));
ui.add_centered(
Slider::new(
&mut lamp.brightness,
0..=255,
)
.width(300)
.label("Brightness")
.smartstate(sm.next()),
);
ui.add(Spacer::new(Size::new(0, 10)));
ui.add_centered(
ToggleSwitch::new(&mut lamp.on)
.smartstate(sm.next()),
);
ui.add_centered(
Label::new("Turn on/off")
.smartstate(sm.next()),
);
}
}
let end_time = embassy_time::Instant::now();
let draw_time = display.get_time();
let prep_time = start_draw_time - start_time;
let proc_time = end_time - start_draw_time;
let proc_time =
proc_time - min(draw_time, proc_time);
rtc.rwdt.feed();
if draw_time.as_micros() > 0 {
println!(
"draw time: {}.{:03}ms | prep time: {}.{:03}ms | proc time: {}.{:03}ms | total time: {}.{:03}ms",
draw_time.as_millis(),
draw_time.as_micros() % 100,
prep_time.as_millis(),
prep_time.as_micros() % 100,
proc_time.as_millis(),
proc_time.as_micros() % 100,
(draw_time + prep_time + proc_time).as_millis(),
(draw_time + prep_time + proc_time).as_micros() % 100, );
}
display.reset_time();
Timer::after(Duration::from_millis(17)).await; // 60
// a second
}
}

669
app/src/bin/microwave-ui.rs Normal file
View File

@ -0,0 +1,669 @@
#![no_std]
#![no_main]
use core::{cell::RefCell, cmp::min};
use display_interface_spi::SPIInterface;
use embassy_embedded_hal::shared_bus::blocking::spi::SpiDevice;
use embassy_executor::Spawner;
use embassy_sync::{
blocking_mutex::{raw::NoopRawMutex, NoopMutex},
signal::Signal,
};
use embassy_time::{Duration, Instant, Timer};
use embedded_graphics::{
mono_font::ascii,
pixelcolor::Rgb565,
prelude::{Point, RgbColor, Size, WebColors},
};
use embedded_graphics_profiler_display::ProfilerDisplay;
use esp_hal::{
clock::ClockControl,
gpio::{
GpioPin,
Input,
Io,
Level,
Output,
Pull,
NO_PIN,
},
peripherals::{Peripherals, SPI2, SPI3},
prelude::*,
rtc_cntl::Rtc,
spi::{master::Spi, FullDuplexMode, SpiMode},
system::SystemControl,
timer::timg::TimerGroup,
};
use esp_println::println;
use kolibri_embedded_gui::{
iconbutton::IconButton,
icons::{size32px, size48px},
label::{HashLabel, Hasher, Label},
smartstate::SmartstateProvider,
spacer::Spacer,
style::medsize_rgb565_style,
ui::{Interaction, Ui},
};
use mipidsi::{
models::ILI9341Rgb565,
options::{ColorOrder, Orientation, Rotation},
Builder,
};
use static_cell::StaticCell;
use xpt2046::Xpt2046;
#[embassy_executor::task]
async fn touch_task(
touch_irq: GpioPin<36>,
spi: &'static mut NoopMutex<
RefCell<Spi<'static, SPI3, FullDuplexMode>>,
>,
touch_cs: GpioPin<33>,
touch_signal: &'static Signal<
NoopRawMutex,
Option<Point>,
>,
) -> ! {
let mut touch_driver = Xpt2046::new(
SpiDevice::new(
spi,
Output::new(touch_cs, Level::Low),
),
Input::new(touch_irq, Pull::Up),
xpt2046::Orientation::LandscapeFlipped,
);
touch_driver.set_num_samples(16);
touch_driver.init(&mut embassy_time::Delay).unwrap();
println!("touch task");
loop {
touch_driver
.run()
.expect("Running Touch driver failed");
if touch_driver.is_touched() {
let point = touch_driver.get_touch_point();
touch_signal.signal(Some(Point::new(
point.x + 25,
240 - point.y,
)));
} else {
touch_signal.signal(None);
}
Timer::after(Duration::from_millis(1)).await; // 100
// a second
// Your touch handling logic here
}
}
struct AppData {
timer_start: Instant,
timer_set_duration: Duration,
timer_remaining_duration: Duration,
timer_running: bool,
timer_paused: bool,
wattage_level: u8,
}
impl AppData {
fn new() -> Self {
Self {
timer_start: Instant::now(),
timer_set_duration: Duration::from_secs(10),
timer_remaining_duration: Duration::from_secs(
10,
),
timer_running: false,
timer_paused: false,
wattage_level: 5,
}
}
fn set_timer_duration(&mut self, duration: Duration) {
self.timer_set_duration = duration;
}
fn add_secs(&mut self, secs: u64) {
self.set_timer_duration(
self.timer_set_duration
.checked_add(Duration::from_secs(secs))
.unwrap_or(Duration::from_secs(5999))
.min(Duration::from_secs(5999)),
);
}
fn sub_secs(&mut self, secs: u64) {
self.set_timer_duration(
self.timer_set_duration
.checked_sub(Duration::from_secs(secs))
.unwrap_or(Duration::from_secs(10))
.max(Duration::from_secs(10)),
);
}
fn start_timer(&mut self) {
if !self.timer_paused {
self.timer_remaining_duration =
self.timer_set_duration;
}
self.timer_start = Instant::now();
self.timer_running = true;
self.timer_paused = false;
}
fn pause_timer(&mut self) {
self.timer_paused = true;
self.timer_remaining_duration = self
.timer_remaining_duration
.checked_sub(self.timer_start.elapsed())
.unwrap_or(Duration::from_secs(0));
}
fn reset_timer(&mut self) {
self.timer_start = Instant::now();
self.timer_paused = false;
self.timer_running = false;
self.timer_remaining_duration =
self.timer_set_duration;
}
fn remaining(&self) -> Duration {
if self.timer_stopped() {
self.timer_set_duration
} else if self.timer_paused() {
self.timer_remaining_duration
} else {
self.timer_remaining_duration
.checked_sub(self.timer_start.elapsed())
.unwrap_or(Duration::from_secs(0))
}
}
fn timer_stopped(&self) -> bool {
!self.timer_running
}
fn timer_paused(&self) -> bool {
self.timer_running && self.timer_paused
}
fn timer_running(&self) -> bool {
self.timer_running && !self.timer_paused
}
fn timer_finished(&self) -> bool {
self.timer_running
&& self.remaining() == Duration::from_secs(0)
}
fn set_wattage_level(&mut self, level: u8) {
assert!(
level < 6,
"wattage level cannot be over 6"
);
self.wattage_level = level;
}
fn get_wattage_level_str(&self) -> &'static str {
match self.wattage_level {
0 => "180W",
1 => "220W",
2 => "360W",
3 => "480W",
4 => "620W",
5 => "800W",
_ => unreachable!(),
}
}
}
#[main]
async fn main(spawner: Spawner) {
let peripherals = Peripherals::take();
let system = SystemControl::new(peripherals.SYSTEM);
let mut clocks =
ClockControl::boot_defaults(system.clock_control)
.freeze();
// Enable the RWDT watchdog timer:
let mut rtc = Rtc::new(peripherals.LPWR);
rtc.rwdt.set_timeout(2.secs());
rtc.rwdt.enable();
println!("RWDT watchdog enabled!");
// Initialize the SYSTIMER peripheral, and then Embassy:
let timg0 = TimerGroup::new(peripherals.TIMG0, &clocks);
esp_hal_embassy::init(&clocks, timg0.timer0);
println!("Embassy initialized!");
let io = Io::new(peripherals.GPIO, peripherals.IO_MUX);
// let mut led = Output::new(io.pins.gpio5,
// Level::High); Initialize SPI
let sclk = io.pins.gpio14;
let miso = io.pins.gpio12;
let mosi = io.pins.gpio13;
let cs = io.pins.gpio15;
let dc = io.pins.gpio2;
let mut backlight =
Output::new(io.pins.gpio21, Level::Low);
// Note: RST is not initialized as it's set to -1 in the
// instructions
// up to 80MHz is possible (even tho the display driver
// isn't supposed to be that fast) Dataseheet sais
// 10MHz, so we're gonna go with that
let mut spi = Spi::new(
peripherals.SPI2,
10.MHz(),
SpiMode::Mode0,
&mut clocks,
)
.with_pins(
Some(sclk),
Some(mosi),
Some(miso),
NO_PIN,
);
static DISP_SPI_BUS: StaticCell<
NoopMutex<RefCell<Spi<SPI2, FullDuplexMode>>>,
> = StaticCell::new();
let spi_bus = NoopMutex::new(RefCell::new(spi));
let spi_bus = DISP_SPI_BUS.init(spi_bus);
let di = SPIInterface::new(
SpiDevice::new(
spi_bus,
Output::new(cs, Level::Low),
),
Output::new(dc, Level::Low),
);
let display = Builder::new(ILI9341Rgb565, di)
.orientation(Orientation {
rotation: Rotation::Deg90,
mirrored: true,
})
.color_order(ColorOrder::Bgr)
// .invert_colors(ColorInversion::Inverted)
.init(&mut embassy_time::Delay)
.unwrap();
let mut display = ProfilerDisplay::new(display);
{
let mut ui = Ui::new_fullscreen(
&mut display,
medsize_rgb565_style(),
);
ui.clear_background().ok();
}
backlight.set_high();
// init touchscreen pins
let touch_irq = io.pins.gpio36;
let touch_mosi = io.pins.gpio32;
let touch_miso = io.pins.gpio39;
let touch_clk = io.pins.gpio25;
let touch_cs = io.pins.gpio33;
// 2MHz is the MAX! DO NOT DECREASE! This is really
// important.
let mut touch_spi = Spi::new(
peripherals.SPI3,
2.MHz(),
SpiMode::Mode0,
&mut clocks,
)
.with_pins(
Some(touch_clk),
Some(touch_mosi),
Some(touch_miso),
NO_PIN,
);
static TOUCH_SPI_BUS: StaticCell<
NoopMutex<RefCell<Spi<SPI3, FullDuplexMode>>>,
> = StaticCell::new();
let touch_spi_bus =
NoopMutex::new(RefCell::new(touch_spi));
let touch_spi_bus = TOUCH_SPI_BUS.init(touch_spi_bus);
let touch_signal = Signal::new();
static TOUCH_SIGNAL: StaticCell<
Signal<NoopRawMutex, Option<Point>>,
> = StaticCell::new();
let touch_signal = &*TOUCH_SIGNAL.init(touch_signal);
spawner
.spawn(touch_task(
touch_irq,
touch_spi_bus,
touch_cs,
touch_signal,
))
.unwrap();
// TODO: Spawn some tasks
let _ = spawner;
// variables
let mut appdata = AppData::new();
let (mut prev_mins, mut prev_secs, mut prev_millis) =
(0, 0, 0);
let mut finished = false;
// touchpoints
let mut last_touch = None;
static BUF_CELL: StaticCell<[Rgb565; 100 * 100]> =
StaticCell::new();
let buf = BUF_CELL.init([Rgb565::BLACK; 100 * 100]);
let mut textbuf = [0u8; 64];
// Periodically feed the RWDT watchdog timer when our
// tasks are not running:
let mut sm = SmartstateProvider::<20>::new();
let hasher = Hasher::new();
loop {
// SMART REDRAWING ENABLE / DISABLE
// sm.force_redraw_all();
let start_time = embassy_time::Instant::now();
sm.restart_counter();
let mut ui = Ui::new_fullscreen(
&mut display,
medsize_rgb565_style(),
);
if let Some(touch) = touch_signal.try_take() {
let interact = match (touch, last_touch) {
(Some(point), Some(_)) => {
Interaction::Drag(point)
}
(Some(point), None) => {
Interaction::Click(point)
}
(None, Some(point)) => {
Interaction::Release(point)
}
(None, None) => Interaction::None,
};
ui.interact(interact);
// println!("{:?}, {:?}, {:?}", last_touch,
// touch, interact);
last_touch = touch;
}
// BUFFER ENABLE/DISABLE
ui.set_buffer(buf);
let start_draw_time = embassy_time::Instant::now();
ui.sub_ui(|ui| {
ui.style_mut().default_font =
ascii::FONT_9X18_BOLD;
ui.add(
Label::new("Kolibri Microwave UI")
.smartstate(sm.next()),
);
Ok(())
})
.ok();
let remaining = appdata.remaining();
ui.right_panel_ui(200, false, |ui| {
ui.add(Spacer::new(Size::new(0, 30)));
ui.add_horizontal(Spacer::new(Size::new(
15, 0,
)));
ui.sub_ui(|ui| {
ui.style_mut().default_font =
ascii::FONT_10X20;
// if remaining.as_secs() / 60 != prev_mins
// { sm.peek().
// force_redraw();
// prev_mins = remaining.as_secs() / 60;
// }
ui.add_horizontal(HashLabel::new(
&format_no_std::show(
&mut textbuf,
format_args!(
"{:02}",
remaining.as_secs() / 60
),
)
.unwrap(),
sm.next(),
&hasher,
));
ui.add_horizontal(
Label::new(":").smartstate(sm.next()),
);
ui.add_horizontal(HashLabel::new(
&format_no_std::show(
&mut textbuf,
format_args!(
"{:02}",
remaining.as_secs() % 60
),
)
.unwrap(),
sm.next(),
&hasher,
));
ui.add_horizontal(
Label::new(":").smartstate(sm.next()),
);
ui.add(HashLabel::new(
&format_no_std::show(
&mut textbuf,
format_args!(
"{:03}",
remaining.as_millis() % 1000
),
)
.unwrap(),
sm.next(),
&hasher,
));
Ok(())
})
.ok();
// ui.add_horizontal(Spacer::new(Size::new(65,
// 0)));
ui.sub_ui(|ui| {
if appdata.timer_running() {
ui.style_mut().icon_color =
Rgb565::CSS_LIGHT_GRAY;
}
if ui
.add_horizontal(
IconButton::new(
size32px::actions::AddCircle,
)
.smartstate(sm.next()),
)
.clicked()
{
if !(appdata.timer_running()
|| appdata.timer_paused())
{
appdata.add_secs(10);
}
}
ui.add_horizontal(
Label::new("+/- 10s")
.smartstate(sm.next()),
);
if ui
.add(
IconButton::new(
size32px::actions::MinusCircle,
)
.smartstate(sm.next()),
)
.clicked()
{
if !(appdata.timer_running()
|| appdata.timer_paused())
{
appdata.sub_secs(10);
}
}
Ok(())
})
.ok();
ui.add_horizontal(Spacer::new(Size::new(
15, 0,
)));
if ui
.add_horizontal(
IconButton::new(
size48px::actions::Undo,
)
.smartstate(sm.next()),
)
.clicked()
{
appdata.reset_timer();
sm.force_redraw_all();
}
if !finished && appdata.timer_finished() {
sm.peek().force_redraw();
}
if appdata.timer_finished() {
if ui
.add_horizontal(
IconButton::new(
size48px::actions::RemoveSquare,
)
.smartstate(sm.next()),
)
.clicked()
{
appdata.reset_timer();
sm.force_redraw_all();
}
} else if appdata.timer_running() {
if ui
.add_horizontal(
IconButton::new(
size48px::music::Pause,
)
.smartstate(sm.next()),
)
.clicked()
{
appdata.pause_timer();
sm.force_redraw_all();
}
} else {
if ui
.add_horizontal(
IconButton::new(
size48px::music::Play,
)
.smartstate(sm.next()),
)
.clicked()
{
appdata.start_timer();
sm.force_redraw_all();
}
}
Ok(())
})
.ok();
ui.add(Spacer::new(Size::new(0, 20)));
ui.expand_row_height(65);
ui.right_panel_ui(80, false, |ui| {
if appdata.timer_running() {
ui.style_mut().icon_color =
Rgb565::CSS_LIGHT_GRAY;
}
if ui
.add(
IconButton::new(
size32px::actions::AddCircle,
)
.smartstate(sm.next()),
)
.clicked()
{
if !appdata.timer_running() {
appdata.set_wattage_level(
(appdata.wattage_level + 1).min(5),
);
}
}
ui.expand_row_height(40);
// ui.add_horizontal(Spacer::new(Size::new(0,
// 40)));
ui.add(
HashLabel::new(
appdata.get_wattage_level_str(),
sm.next(),
&hasher,
)
.with_font(ascii::FONT_10X20),
);
if ui
.add(
IconButton::new(
size32px::actions::MinusCircle,
)
.smartstate(sm.next()),
)
.clicked()
{
if !appdata.timer_running() {
appdata.set_wattage_level(
appdata
.wattage_level
.saturating_sub(1),
);
}
}
Ok(())
})
.ok();
finished = appdata.timer_finished();
let end_time = embassy_time::Instant::now();
let draw_time = display.get_time();
let prep_time = start_draw_time - start_time;
let proc_time = end_time - start_draw_time;
let proc_time =
proc_time - min(draw_time, proc_time);
rtc.rwdt.feed();
if draw_time.as_micros() > 0 {
println!(
"draw time: {}.{:03}ms | prep time: {}.{:03}ms | proc time: {}.{:03}ms | total time: {}.{:03}ms",
draw_time.as_millis(),
draw_time.as_micros() % 100,
prep_time.as_millis(),
prep_time.as_micros() % 100,
proc_time.as_millis(),
proc_time.as_micros() % 100,
(draw_time + prep_time + proc_time).as_millis(),
(draw_time + prep_time + proc_time).as_micros() % 100, );
}
display.reset_time();
Timer::after(Duration::from_millis(17)).await; // 60
// a second
}
}

586
app/src/bin/timer.rs Normal file
View File

@ -0,0 +1,586 @@
#![no_std]
#![no_main]
use core::{cell::RefCell, cmp::min};
use display_interface_spi::SPIInterface;
use embassy_embedded_hal::shared_bus::blocking::spi::SpiDevice;
use embassy_executor::Spawner;
use embassy_sync::{
blocking_mutex::{raw::NoopRawMutex, NoopMutex},
signal::Signal,
};
use embassy_time::{Duration, Instant, Timer};
use embedded_graphics::{
mono_font::ascii,
pixelcolor::Rgb565,
prelude::{Point, RgbColor, Size, WebColors},
};
use embedded_graphics_profiler_display::ProfilerDisplay;
use esp_backtrace as _;
use esp_hal::{
clock::ClockControl,
gpio::{
GpioPin,
Input,
Io,
Level,
Output,
Pull,
NO_PIN,
},
peripherals::{Peripherals, SPI2, SPI3},
prelude::*,
rtc_cntl::Rtc,
spi::{master::Spi, FullDuplexMode, SpiMode},
system::SystemControl,
timer::timg::TimerGroup,
};
use esp_println::println;
use kolibri_embedded_gui::{
iconbutton::IconButton,
icons::{size32px, size48px},
label::Label,
smartstate::SmartstateProvider,
spacer::Spacer,
style::medsize_rgb565_style,
ui::{Interaction, Ui},
};
use mipidsi::{
models::ILI9341Rgb565,
options::{ColorOrder, Orientation, Rotation},
Builder,
};
use static_cell::StaticCell;
use xpt2046::Xpt2046;
#[embassy_executor::task]
async fn touch_task(
touch_irq: GpioPin<36>,
spi: &'static mut NoopMutex<
RefCell<Spi<'static, SPI3, FullDuplexMode>>,
>,
touch_cs: GpioPin<33>,
touch_signal: &'static Signal<
NoopRawMutex,
Option<Point>,
>,
) -> ! {
let mut touch_driver = Xpt2046::new(
SpiDevice::new(
spi,
Output::new(touch_cs, Level::Low),
),
Input::new(touch_irq, Pull::Up),
xpt2046::Orientation::LandscapeFlipped,
);
touch_driver.set_num_samples(16);
touch_driver.init(&mut embassy_time::Delay).unwrap();
println!("touch task");
loop {
touch_driver
.run()
.expect("Running Touch driver failed");
if touch_driver.is_touched() {
let point = touch_driver.get_touch_point();
touch_signal.signal(Some(Point::new(
point.x + 25,
240 - point.y,
)));
} else {
touch_signal.signal(None);
}
Timer::after(Duration::from_millis(1)).await; // 100
// a second
// Your touch handling logic here
}
}
struct AppData {
timer_start: Instant,
timer_set_duration: Duration,
timer_remaining_duration: Duration,
timer_running: bool,
timer_paused: bool,
}
impl AppData {
fn new() -> Self {
Self {
timer_start: Instant::now(),
timer_set_duration: Duration::from_secs(10),
timer_remaining_duration: Duration::from_secs(
10,
),
timer_running: false,
timer_paused: false,
}
}
fn set_timer_duration(&mut self, duration: Duration) {
self.timer_set_duration = duration;
}
fn add_secs(&mut self, secs: u64) {
self.set_timer_duration(
self.timer_set_duration
.checked_add(Duration::from_secs(secs))
.unwrap_or(Duration::from_secs(5999))
.min(Duration::from_secs(5999)),
);
}
fn sub_secs(&mut self, secs: u64) {
self.set_timer_duration(
self.timer_set_duration
.checked_sub(Duration::from_secs(secs))
.unwrap_or(Duration::from_secs(10))
.max(Duration::from_secs(10)),
);
}
fn start_timer(&mut self) {
if !self.timer_paused {
self.timer_remaining_duration =
self.timer_set_duration;
}
self.timer_start = Instant::now();
self.timer_running = true;
self.timer_paused = false;
}
fn pause_timer(&mut self) {
self.timer_paused = true;
self.timer_remaining_duration = self
.timer_remaining_duration
.checked_sub(self.timer_start.elapsed())
.unwrap_or(Duration::from_secs(0));
}
fn reset_timer(&mut self) {
self.timer_start = Instant::now();
self.timer_paused = false;
self.timer_running = false;
self.timer_remaining_duration =
self.timer_set_duration;
}
fn remaining(&self) -> Duration {
if self.timer_stopped() {
self.timer_set_duration
} else if self.timer_paused() {
self.timer_remaining_duration
} else {
self.timer_remaining_duration
.checked_sub(self.timer_start.elapsed())
.unwrap_or(Duration::from_secs(0))
}
}
fn timer_stopped(&self) -> bool {
!self.timer_running
}
fn timer_paused(&self) -> bool {
self.timer_running && self.timer_paused
}
fn timer_running(&self) -> bool {
self.timer_running && !self.timer_paused
}
fn timer_finished(&self) -> bool {
self.timer_running
&& self.remaining() == Duration::from_secs(0)
}
}
#[main]
async fn main(spawner: Spawner) {
let peripherals = Peripherals::take();
let system = SystemControl::new(peripherals.SYSTEM);
let mut clocks =
ClockControl::boot_defaults(system.clock_control)
.freeze();
// Enable the RWDT watchdog timer:
let mut rtc = Rtc::new(peripherals.LPWR);
rtc.rwdt.set_timeout(2.secs());
rtc.rwdt.enable();
println!("RWDT watchdog enabled!");
// Initialize the SYSTIMER peripheral, and then Embassy:
let timg0 = TimerGroup::new(peripherals.TIMG0, &clocks);
esp_hal_embassy::init(&clocks, timg0.timer0);
println!("Embassy initialized!");
let io = Io::new(peripherals.GPIO, peripherals.IO_MUX);
// let mut led = Output::new(io.pins.gpio5,
// Level::High); Initialize SPI
let sclk = io.pins.gpio14;
let miso = io.pins.gpio12;
let mosi = io.pins.gpio13;
let cs = io.pins.gpio15;
let dc = io.pins.gpio2;
let mut backlight =
Output::new(io.pins.gpio21, Level::Low);
// Note: RST is not initialized as it's set to -1 in the
// instructions
// up to 80MHz is possible (even tho the display driver
// isn't supposed to be that fast) Dataseheet sais
// 10MHz, so we're gonna go with that
let mut spi = Spi::new(
peripherals.SPI2,
10.MHz(),
SpiMode::Mode0,
&mut clocks,
)
.with_pins(
Some(sclk),
Some(mosi),
Some(miso),
NO_PIN,
);
static DISP_SPI_BUS: StaticCell<
NoopMutex<RefCell<Spi<SPI2, FullDuplexMode>>>,
> = StaticCell::new();
let spi_bus = NoopMutex::new(RefCell::new(spi));
let spi_bus = DISP_SPI_BUS.init(spi_bus);
let di = SPIInterface::new(
SpiDevice::new(
spi_bus,
Output::new(cs, Level::Low),
),
Output::new(dc, Level::Low),
);
let display = Builder::new(ILI9341Rgb565, di)
.orientation(Orientation {
rotation: Rotation::Deg90,
mirrored: true,
})
.color_order(ColorOrder::Bgr)
// .invert_colors(ColorInversion::Inverted)
.init(&mut embassy_time::Delay)
.unwrap();
let mut display = ProfilerDisplay::new(display);
{
let mut ui = Ui::new_fullscreen(
&mut display,
medsize_rgb565_style(),
);
ui.clear_background().ok();
}
backlight.set_high();
// init touchscreen pins
let touch_irq = io.pins.gpio36;
let touch_mosi = io.pins.gpio32;
let touch_miso = io.pins.gpio39;
let touch_clk = io.pins.gpio25;
let touch_cs = io.pins.gpio33;
// 2MHz is the MAX! DO NOT DECREASE! This is really
// important.
let mut touch_spi = Spi::new(
peripherals.SPI3,
2.MHz(),
SpiMode::Mode0,
&mut clocks,
)
.with_pins(
Some(touch_clk),
Some(touch_mosi),
Some(touch_miso),
NO_PIN,
);
static TOUCH_SPI_BUS: StaticCell<
NoopMutex<RefCell<Spi<SPI3, FullDuplexMode>>>,
> = StaticCell::new();
let touch_spi_bus =
NoopMutex::new(RefCell::new(touch_spi));
let touch_spi_bus = TOUCH_SPI_BUS.init(touch_spi_bus);
let touch_signal = Signal::new();
static TOUCH_SIGNAL: StaticCell<
Signal<NoopRawMutex, Option<Point>>,
> = StaticCell::new();
let touch_signal = &*TOUCH_SIGNAL.init(touch_signal);
spawner
.spawn(touch_task(
touch_irq,
touch_spi_bus,
touch_cs,
touch_signal,
))
.unwrap();
// TODO: Spawn some tasks
let _ = spawner;
// variables
let mut appdata = AppData::new();
let (mut prev_mins, mut prev_secs, mut prev_millis) =
(0, 0, 0);
let mut finished = false;
// touchpoints
let mut last_touch = None;
static BUF_CELL: StaticCell<[Rgb565; 100 * 100]> =
StaticCell::new();
let buf = BUF_CELL.init([Rgb565::BLACK; 100 * 100]);
let mut textbuf = [0u8; 64];
// Periodically feed the RWDT watchdog timer when our
// tasks are not running:
let mut sm = SmartstateProvider::<20>::new();
loop {
// SMART REDRAWING ENABLE / DISABLE
// sm.force_redraw_all();
let start_time = embassy_time::Instant::now();
sm.restart_counter();
let mut ui = Ui::new_fullscreen(
&mut display,
medsize_rgb565_style(),
);
if let Some(touch) = touch_signal.try_take() {
let interact = match (touch, last_touch) {
(Some(point), Some(_)) => {
Interaction::Drag(point)
}
(Some(point), None) => {
Interaction::Click(point)
}
(None, Some(point)) => {
Interaction::Release(point)
}
(None, None) => Interaction::None,
};
ui.interact(interact);
// println!("{:?}, {:?}, {:?}", last_touch,
// touch, interact);
last_touch = touch;
}
// BUFFER ENABLE/DISABLE
ui.set_buffer(buf);
let start_draw_time = embassy_time::Instant::now();
ui.sub_ui(|ui| {
ui.style_mut().default_font =
ascii::FONT_9X18_BOLD;
ui.add(
Label::new("Kolibri Timer App")
.smartstate(sm.next()),
);
Ok(())
})
.ok();
let remaining = appdata.remaining();
ui.add(Spacer::new(Size::new(0, 60)));
ui.add_horizontal(Spacer::new(Size::new(80, 0)));
ui.sub_ui(|ui| {
ui.style_mut().default_font = ascii::FONT_10X20;
if remaining.as_secs() / 60 != prev_mins {
sm.peek().force_redraw();
prev_mins = remaining.as_secs() / 60;
}
ui.add_horizontal(
Label::new(
&format_no_std::show(
&mut textbuf,
format_args!(
"{:02}",
remaining.as_secs() / 60
),
)
.unwrap(),
)
.smartstate(sm.next()),
);
ui.add_horizontal(
Label::new(":").smartstate(sm.next()),
);
if remaining.as_secs() % 60 != prev_secs {
sm.peek().force_redraw();
prev_secs = remaining.as_secs() % 60;
}
ui.add_horizontal(
Label::new(
&format_no_std::show(
&mut textbuf,
format_args!(
"{:02}",
remaining.as_secs() % 60
),
)
.unwrap(),
)
.smartstate(sm.next()),
);
ui.add_horizontal(
Label::new(":").smartstate(sm.next()),
);
if remaining.as_millis() % 1000 != prev_millis {
sm.peek().force_redraw();
prev_millis = remaining.as_millis() % 1000;
}
ui.add(
Label::new(
&format_no_std::show(
&mut textbuf,
format_args!(
"{:03}",
remaining.as_millis() % 1000
),
)
.unwrap(),
)
.smartstate(sm.next()),
);
Ok(())
})
.ok();
ui.add_horizontal(Spacer::new(Size::new(65, 0)));
ui.sub_ui(|ui| {
if appdata.timer_running() {
ui.style_mut().icon_color =
Rgb565::CSS_LIGHT_GRAY;
}
if ui
.add_horizontal(
IconButton::new(
size32px::actions::AddCircle,
)
.smartstate(sm.next()),
)
.clicked()
{
if !(appdata.timer_running()
|| appdata.timer_paused())
{
appdata.add_secs(10);
}
}
ui.add_horizontal(
Label::new("+/- 10s").smartstate(sm.next()),
);
if ui
.add(
IconButton::new(
size32px::actions::MinusCircle,
)
.smartstate(sm.next()),
)
.clicked()
{
if !(appdata.timer_running()
|| appdata.timer_paused())
{
appdata.sub_secs(10);
}
}
Ok(())
})
.ok();
ui.add_horizontal(Spacer::new(Size::new(80, 0)));
if ui
.add_horizontal(
IconButton::new(size48px::actions::Undo)
.smartstate(sm.next()),
)
.clicked()
{
appdata.reset_timer();
sm.force_redraw_all();
}
if !finished && appdata.timer_finished() {
sm.peek().force_redraw();
}
if appdata.timer_finished() {
if ui
.add_horizontal(
IconButton::new(
size48px::actions::RemoveSquare,
)
.smartstate(sm.next()),
)
.clicked()
{
appdata.reset_timer();
sm.force_redraw_all();
}
} else if appdata.timer_running() {
if ui
.add_horizontal(
IconButton::new(size48px::music::Pause)
.smartstate(sm.next()),
)
.clicked()
{
appdata.pause_timer();
sm.force_redraw_all();
}
} else {
if ui
.add_horizontal(
IconButton::new(size48px::music::Play)
.smartstate(sm.next()),
)
.clicked()
{
appdata.start_timer();
sm.force_redraw_all();
}
}
finished = appdata.timer_finished();
let end_time = embassy_time::Instant::now();
let draw_time = display.get_time();
let prep_time = start_draw_time - start_time;
let proc_time = end_time - start_draw_time;
let proc_time =
proc_time - min(draw_time, proc_time);
rtc.rwdt.feed();
if draw_time.as_micros() > 0 {
println!(
"draw time: {}.{:03}ms | prep time: {}.{:03}ms | proc time: {}.{:03}ms | total time: {}.{:03}ms",
draw_time.as_millis(),
draw_time.as_micros() % 100,
prep_time.as_millis(),
prep_time.as_micros() % 100,
proc_time.as_millis(),
proc_time.as_micros() % 100,
(draw_time + prep_time + proc_time).as_millis(),
(draw_time + prep_time + proc_time).as_micros() % 100, );
}
display.reset_time();
Timer::after(Duration::from_millis(17)).await; // 60
// a second
}
}

View File

@ -4,15 +4,14 @@ target = "xtensa-esp32-espidf"
[target.xtensa-esp32-espidf] [target.xtensa-esp32-espidf]
linker = "ldproxy" linker = "ldproxy"
# runner = "espflash --monitor" # Select this runner for espflash v1.x.x # runner = "espflash --monitor" # Select this runner for espflash v1.x.x
runner = "espflash flash --monitor --baud 921600" # Select this runner for espflash v2.x.x runner = "espflash flash --monitor" # --baud 921600" # Select this runner for espflash v2.x.x
rustflags = [ rustflags = [
# Extending time_t for ESP IDF 5: https://github.com/esp-rs/rust/issues/110 # Extending time_t for ESP IDF 5: https://github.com/esp-rs/rust/issues/110
"--cfg", "--cfg",
"espidf_time64", "espidf_time64",
# Added the following 2 entries so lvgl will build without getting string.h file not found # Added the following 2 entries so lvgl will build without getting string.h file not found
"--sysroot", "--sysroot",
"/home/ed/.rustup/toolchains/esp/xtensa-esp32s3-elf/esp-13.2.0_20230928/xtensa-esp-elf/xtensa-esp-elf/include", "/home/yannik/.rustup/toolchains/esp/xtensa-esp32s3-elf/esp-13.2.0_20230928/xtensa-esp-elf/xtensa-esp-elf/include",
] ]
[unstable] [unstable]
@ -30,7 +29,7 @@ DEP_LV_CONFIG_PATH = { relative = true, value = "lvgl-configs" }
CROSS_COMPILE = "xtensa-esp32-elf" CROSS_COMPILE = "xtensa-esp32-elf"
# Directory for custom fonts (written in C) that Lvgl can use # Directory for custom fonts (written in C) that Lvgl can use
LVGL_FONTS_DIR = {relative = true, value = "custom-fonts"} LVGL_FONTS_DIR = { relative = true, value = "custom-fonts" }
PATH="/usr/lib64/ccache:/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin:/var/lib/snapd/snap/bin:/home/yannik/.rustup/toolchains/esp/xtensa-esp32s2-elf/esp-2021r2-patch5-8_4_0/xtensa-esp32s2-elf/bin:/home/yannik/.rustup/toolchains/esp/xtensa-esp32s3-elf/esp-2021r2-patch5-8_4_0/xtensa-esp32s3-elf/bin:/home/yannik/.rustup/toolchains/esp/xtensa-esp32-elf/esp-2021r2-patch5-8_4_0/xtensa-esp32-elf/bin:/home/yannik/.rustup/toolchains/esp/riscv32-esp-elf/esp-2021r2-patch5-8_4_0/riscv32-esp-elf/bin:" PATH = "/home/yannik/.rustup/toolchains/esp/xtensa-esp-elf/esp-14.2.0_20240906/xtensa-esp-elf/bin:/home/yannik/.local/share/pnpm:/home/yannik/.local/share/zinit/polaris/bin:/home/yannik/.rustup/toolchains/esp/xtensa-esp-elf/esp-14.2.0_20240906/xtensa-esp-elf/bin:/home/yannik/.surrealdb:/home/yannik/.pyenv/shims:/home/yannik/.pyenv/bin:/home/yannik/.cargo/bin:/home/yannik/.local/bin:/home/yannik/bin:/usr/local/bin:/usr/lib64/ccache:/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin:/var/lib/snapd/snap/bin:/home/yannik/development/flutter/bin"
LIBCLANG_PATH="/home/yannik/.rustup/toolchains/esp/xtensa-esp32-elf-clang/esp-15.0.0-20221201/esp-clang/lib" LIBCLANG_PATH = "/home/yannik/.rustup/toolchains/esp/xtensa-esp32-elf-clang/esp-15.0.0-20221201/esp-clang/lib"

View File

@ -21,6 +21,12 @@ name = "starter"
[[bin]] [[bin]]
name = "timer" name = "timer"
[[bin]]
name = "light-control"
[[bin]]
name = "microwave-ui"
[features] [features]
default = ["embassy", "esp-idf-svc/native", "std"] default = ["embassy", "esp-idf-svc/native", "std"]
@ -60,7 +66,7 @@ xpt2046 = { git = "https://github.com/Yandrik/xpt2046.git", version = "0.3.1" }
embedded-graphics-profiler-display = { version = "0.1.0", path = "../embedded-graphics-profiler-display", features = ["std"] } embedded-graphics-profiler-display = { version = "0.1.0", path = "../embedded-graphics-profiler-display", features = ["std"] }
heapless = "0.8.0"
[build-dependencies] [build-dependencies]

View File

@ -328,17 +328,17 @@
*https://fonts.google.com/specimen/Montserrat*/ *https://fonts.google.com/specimen/Montserrat*/
#define LV_FONT_MONTSERRAT_8 0 #define LV_FONT_MONTSERRAT_8 0
#define LV_FONT_MONTSERRAT_10 0 #define LV_FONT_MONTSERRAT_10 0
#define LV_FONT_MONTSERRAT_12 0 #define LV_FONT_MONTSERRAT_12 1
#define LV_FONT_MONTSERRAT_14 1 #define LV_FONT_MONTSERRAT_14 1
#define LV_FONT_MONTSERRAT_16 0 #define LV_FONT_MONTSERRAT_16 1
#define LV_FONT_MONTSERRAT_18 0 #define LV_FONT_MONTSERRAT_18 0
#define LV_FONT_MONTSERRAT_20 0 #define LV_FONT_MONTSERRAT_20 0
#define LV_FONT_MONTSERRAT_22 0 #define LV_FONT_MONTSERRAT_22 0
#define LV_FONT_MONTSERRAT_24 0 #define LV_FONT_MONTSERRAT_24 1
#define LV_FONT_MONTSERRAT_26 0 #define LV_FONT_MONTSERRAT_26 0
#define LV_FONT_MONTSERRAT_28 0 #define LV_FONT_MONTSERRAT_28 0
#define LV_FONT_MONTSERRAT_30 0 #define LV_FONT_MONTSERRAT_30 0
#define LV_FONT_MONTSERRAT_32 0 #define LV_FONT_MONTSERRAT_32 1
#define LV_FONT_MONTSERRAT_34 0 #define LV_FONT_MONTSERRAT_34 0
#define LV_FONT_MONTSERRAT_36 0 #define LV_FONT_MONTSERRAT_36 0
#define LV_FONT_MONTSERRAT_38 0 #define LV_FONT_MONTSERRAT_38 0

19
lvgl-based/rustfmt.toml Normal file
View File

@ -0,0 +1,19 @@
# Edition
edition = "2021"
# Comments
format_code_in_doc_comments = true
normalize_comments = true
wrap_comments = true
# Imports
group_imports = "StdExternalCrate"
imports_granularity = "Crate"
imports_layout = "HorizontalVertical"
# Miscellaneous
enum_discrim_align_threshold = 25
hex_literal_case = "Upper"
# Length
max_width=60

View File

@ -0,0 +1,568 @@
use std::{
cell::RefCell,
cmp::min,
str::FromStr,
sync::mpsc::channel,
thread,
time::{Duration, Instant},
};
use cstr_core::CString;
use display_interface_spi::SPIInterface;
use embedded_graphics_core::{
draw_target::DrawTarget,
prelude::Point,
};
use embedded_graphics_profiler_display::ProfilerDisplay;
use esp_idf_hal::spi::SpiSingleDeviceDriver;
use esp_idf_hal::{
delay::{self, Delay},
gpio::*,
peripherals::Peripherals,
spi::{
config::DriverConfig,
Dma,
SpiConfig,
SpiDeviceDriver,
},
units::FromValueType, // for converting 26MHz to value
};
use lvgl::{
font::Font,
input_device::{
pointer::{Pointer, PointerInputData},
InputDriver,
},
style::{FlexAlign, FlexFlow, Layout, Style},
widgets::{Btn, Label, Slider, Switch},
Align,
AnimationState,
Color,
Display,
DrawBuffer,
Event,
LvError,
NativeObject,
Obj,
Part,
Screen,
TextAlign,
Widget,
};
use mipidsi::{
models::ILI9341Rgb565,
options::{
ColorInversion,
ColorOrder,
Orientation,
Rotation,
},
Builder,
};
use xpt2046::Xpt2046;
fn lerp_fixed(start: u8, end: u8, t: u8, max_t: u8) -> u8 {
let (start, end, t, max_t) =
(start as u16, end as u16, t as u16, max_t as u16);
let t = t.min(max_t);
let result = start
+ ((end - start.min(end)) * t + (max_t / 2))
/ max_t;
result as u8
}
#[derive(Debug, Clone)]
struct Lamp {
pub name: heapless::String<64>,
pub on: bool,
pub brightness: u8,
}
impl Lamp {
pub fn new(name: &str) -> Self {
Self {
name: heapless::String::from(
heapless::String::from_str(name).unwrap(),
),
on: false,
brightness: 255,
}
}
}
enum Page<'a> {
Home,
LampCtrl(&'a Lamp),
}
struct AppData {
lamps: heapless::Vec<Lamp, 8>,
}
impl AppData {
fn new() -> Self {
Self {
lamps: heapless::Vec::new(),
}
}
fn add_lamp(&mut self, name: &str) {
self.lamps.push(Lamp::new(name)).unwrap();
}
}
fn main() -> Result<(), LvError> {
const HOR_RES: u32 = 320;
const VER_RES: u32 = 240;
const LINES: u32 = 20;
// It is necessary to call this function once. Otherwise
// some patches to the runtime implemented by esp-idf-sys might not link properly. See https://github.com/esp-rs/esp-idf-template/issues/71
esp_idf_svc::sys::link_patches();
// Initialize lvgl
lvgl::init();
// Bind the log crate to the ESP Logging facilities
esp_idf_svc::log::EspLogger::initialize_default();
log::info!("================= Starting App =======================");
let peripherals = Peripherals::take().unwrap();
// #[allow(unused)]
let pins = peripherals.pins;
let sclk = pins.gpio14;
let miso = pins.gpio12;
let mosi = pins.gpio13;
let cs = pins.gpio15;
let dc = pins.gpio2;
let spi = SpiDeviceDriver::new_single(
peripherals.spi2,
sclk, // sclk
mosi, // sdo
Some(miso), // sdi
Some(cs), // cs
&DriverConfig::new().dma(Dma::Channel1(4096)),
&SpiConfig::new()
.write_only(true)
.baudrate(10.MHz().into()),
)
.unwrap();
// let rst = PinDriver::output(pins.gpio33).unwrap();
let dc = PinDriver::output(dc).unwrap();
let di = SPIInterface::new(spi, dc);
// Turn backlight on
let mut bklt = PinDriver::output(pins.gpio21).unwrap();
bklt.set_high().unwrap();
// Configuration for M5Stack Core Development Kit V1.0
// Puts display in landscape mode with the three buttons
// at the bottom of screen let mut m5stack_display =
// Builder::ili9342c_rgb565(di)
// .with_display_size(320, 240)
// .with_color_order(ColorOrder::Bgr)
// .with_orientation(Orientation::Portrait(false))
// .with_invert_colors(mipidsi::ColorInversion::Inverted)
// .init(&mut delay::Ets, Some(rst))
// .unwrap();
// 0.6.0
// let mut raw_display = Builder::ili9342c_rgb565(di)
// .with_orientation(Orientation::Portrait(false))
// .with_color_order(ColorOrder::Bgr)
// .with_invert_colors(true)
// .init(&mut Delay::new_default(),
// None::<PinDriver<AnyOutputPin, Output>>)
// .unwrap();
let raw_display = Builder::new(ILI9341Rgb565, di)
.orientation(Orientation {
rotation: Rotation::Deg90,
mirrored: true,
})
.color_order(ColorOrder::Bgr)
// .invert_colors(ColorInversion::Inverted)
.init(&mut Delay::new_default())
.unwrap();
let mut raw_display = ProfilerDisplay::new(raw_display);
// Stack size value - 20,000 for 10 lines, 40,000 for
// 20 lines let (touch_send, touch_recv) =
// channel();
let touch_irq = pins.gpio36;
let touch_mosi = pins.gpio32;
let touch_miso = pins.gpio39;
let touch_clk = pins.gpio25;
let touch_cs = pins.gpio33;
let mut touch_driver = Xpt2046::new(
SpiDeviceDriver::new_single(
peripherals.spi3,
touch_clk,
touch_mosi,
Some(touch_miso),
Some(touch_cs),
&DriverConfig::new(),
&SpiConfig::new()
.write_only(true)
.baudrate(2.MHz().into()),
)
.unwrap(),
PinDriver::input(touch_irq).unwrap(),
xpt2046::Orientation::LandscapeFlipped,
);
touch_driver.set_num_samples(1);
touch_driver.init(&mut Delay::new_default()).unwrap();
let touch_driver = RefCell::new(touch_driver);
let _lvgl_thread = thread::Builder::new()
.stack_size(40_000)
.spawn(move || {
println!("thread started");
let mut appdata = AppData::new();
let buffer = DrawBuffer::<{ (HOR_RES * LINES) as usize }>::default();
let display = Display::register(buffer, HOR_RES, VER_RES, |refresh| {
raw_display.draw_iter(refresh.as_pixels()).unwrap();
})
.unwrap();
// Register a new input device that's capable of reading the current state of
// the input
// let mut last_touch = RefCell::new(None);
let _touch_screen = Pointer::register(
|| {
let mut td_ref = touch_driver.borrow_mut();
td_ref.run().expect("Running Touch driver failed");
if td_ref.is_touched() {
let point = td_ref.get_touch_point();
// println!("touched {:?}", point);
PointerInputData::Touch(Point::new(point.x + 20, 240 - point.y)).pressed().once()
} else {
// println!("untouched");
PointerInputData::Touch(Point::new(0, 0)).released().once()
}
},
&display,
);
// Create screen and widgets
let mut screen = display.get_scr_act().unwrap();
let mut screen_style = Style::default();
screen_style.set_bg_color(Color::from_rgb((0, 0, 0)));
screen_style.set_radius(0);
screen_style.set_layout(Layout::flex());
screen_style.set_flex_flow(FlexFlow::ROW_WRAP);
screen_style.set_flex_main_place(FlexAlign::CENTER);
screen_style.set_flex_cross_place(FlexAlign::CENTER);
screen.add_style(Part::Main, &mut screen_style);
appdata.add_lamp("Front Door");
appdata.add_lamp("Living Room");
appdata.add_lamp("Bedroom");
appdata.add_lamp("Bathroom");
appdata.add_lamp("Porch");
let mut light_page = Screen::blank().unwrap();
light_page.set_size(320, 240);
let mut light_page_style = Style::default();
light_page_style.set_bg_color(Color::from_rgb((0, 0, 0)));
light_page_style.set_radius(0);
light_page.add_style(Part::Main, &mut light_page_style);
let mut light_page_title_label = Label::create(&mut light_page).unwrap();
let mut light_page_title_style = Style::default();
light_page_title_style.set_text_color(Color::from_rgb((255, 255, 255)));
unsafe { light_page_title_style.set_text_font(Font::new_raw(lvgl_sys::lv_font_montserrat_24)) };
light_page_title_label.add_style(Part::Main, &mut light_page_title_style);
light_page_title_label.set_align(Align::TopMid, 0, 6);
let mut brightness_slider = Slider::create(&mut light_page).unwrap();
brightness_slider.set_size(250, 10);
// brightness_slider.range(0..255);
brightness_slider.set_value(0, AnimationState::OFF);
brightness_slider.set_align(Align::Center, 0, -20);
let mut brightness_slider_label = Label::create(&mut light_page).unwrap();
// no text for now
let mut white_text_style = Style::default();
white_text_style.set_text_color(Color::from_rgb((255, 255, 255)));
unsafe { white_text_style.set_text_font(Font::new_raw(lvgl_sys::lv_font_montserrat_14)) };
brightness_slider_label.add_style(Part::Main, &mut white_text_style);
brightness_slider_label.set_text(CString::new("Brightness").unwrap().as_c_str());
brightness_slider_label.set_align(Align::Center, 0, -50);
// Add switch
let mut light_switch = Switch::create(&mut light_page).unwrap();
light_switch.set_align(Align::Center, 0, 40);
let mut light_switch_label = Label::create(&mut light_page).unwrap();
// no text for now
let mut lslabel_style = Style::default();
lslabel_style.set_text_color(Color::from_rgb((255, 255, 255)));
unsafe { lslabel_style.set_text_font(Font::new_raw(lvgl_sys::lv_font_montserrat_14)) };
light_switch_label.add_style(Part::Main, &mut lslabel_style);
light_switch_label.set_text(CString::new("On/Off").unwrap().as_c_str());
light_switch_label.set_align(Align::Center, 0, 10);
// Add back button
let mut back_btn = Btn::create(&mut light_page).unwrap();
back_btn.set_size(40, 40);
back_btn.set_align(Align::TopLeft, 10, 10);
back_btn.on_event(|_btn, event| {
if let Event::Pressed = event {
display.set_scr_act(&mut screen);
}
});
let mut back_label = Label::create(&mut back_btn).unwrap();
back_label.set_text(CString::new(b"\xef\x81\x93").unwrap().as_c_str()); // Font Awesome left arrow
let mut back_style = Style::default();
unsafe { back_style.set_text_font(Font::new_raw(lvgl_sys::lv_font_montserrat_24)) };
back_label.add_style(Part::Main, &mut back_style);
let mut light_ctrls = heapless::Vec::<(Btn, Label, Label), 8>::new();
let mut page = Page::Home;
for lamp in appdata.lamps.iter_mut() {
let mut cont = Btn::new().unwrap();
cont.set_size(100, 110);
// cont.set_align(Align::TopLeft, 0, 0);
cont.on_event(|_btn, event| {
if let Event::Pressed = event {
// println!("lamp {:?}", lamp.name);
// page = Page::LampCtrl(lamp);
brightness_slider.set_value(lerp_fixed(0, 100, lamp.brightness, 255).into(), AnimationState::OFF);
unsafe {
if lamp.on {
lvgl_sys::lv_obj_add_state(light_switch.raw().as_ptr(), lvgl_sys::LV_STATE_CHECKED as u16);
} else {
lvgl_sys::lv_obj_clear_state(light_switch.raw().as_ptr(), lvgl_sys::LV_STATE_CHECKED as u16);
}
}
light_switch.on_event(|_ls, event| {
if let Event::ValueChanged | Event::Released = event {
let on = unsafe { lvgl_sys::lv_obj_has_state(_ls.raw().as_ptr(), lvgl_sys::LV_STATE_CHECKED as u16) };
lamp.on = on;
}
});
brightness_slider.on_event(|_sldr, event| {
// println!("event: {:?}", event);
if let Event::ValueChanged | Event::Released = event {
let brightness = _sldr.get_value();
println!("brightness: {}", brightness);
lamp.brightness = lerp_fixed(0, 255, brightness as u8, 100);
println!("brightness: {}", lamp.brightness);
}
});
light_page_title_label.set_text(CString::new(lamp.name.as_str()).unwrap().as_c_str());
display.set_scr_act(&mut light_page);
}
});
let mut style_name = Style::default();
style_name.set_text_align(TextAlign::Center);
style_name.set_text_color(Color::from_rgb((255, 255, 255)));
unsafe { style_name.set_text_font(Font::new_raw(lvgl_sys::lv_font_montserrat_14)) };
let mut style_icon = Style::default();
style_icon.set_text_color(Color::from_rgb((255, 255, 255)));
unsafe { style_icon.set_text_font(Font::new_raw(lvgl_sys::lv_font_montserrat_32)) };
style_icon.set_text_align(TextAlign::Center);
let mut icon = Label::create(&mut cont).unwrap();
icon.set_text(CString::new(b"\xef\x84\xa4").unwrap().as_c_str());
icon.add_style(Part::Main, Box::leak(Box::new(style_icon)));
icon.set_align(Align::Center, 0, 0);
let mut name = Label::create(&mut cont).unwrap();
name.set_text(CString::new(lamp.name.as_str()).unwrap().as_c_str());
// style_name.set_text_color(Color::from_rgb((255, 255, 255))); // white
name.add_style(Part::Main, Box::leak(Box::new(style_name)));
name.set_align(Align::BottomMid, 0, -4);
light_ctrls.push((cont, icon, name))
.unwrap();
}
// let mut time = Label::new().unwrap();
// time.set_text(CString::new("00:10:000").unwrap().as_c_str());
// let mut style_time = Style::default();
// style_time.set_text_color(Color::from_rgb((255, 255, 255))); // white
// style_time.set_text_align(TextAlign::Center);
// unsafe { style_time.set_text_font(Font::new_raw(lvgl_sys::lv_font_montserrat_24)) };
// time.add_style(Part::Main, &mut style_time);
// time.set_align(Align::Center, 0, 0);
/*
// let mut cont = Obj::new().unwrap();
// cont.set_size(320, 50);
// let mut style = Style::default();
// style.set_flex_flow(FlexFlow::ROW);
// style.set_flex_main_place(FlexAlign::SPACE_EVENLY);
// style.set_flex_cross_place(FlexAlign::CENTER);
// // style.set_bg_opa(0);
// style.set_border_width(0);
// cont.add_style(Part::Any, &mut style);
// cont.set_align(Align::Center, 0, 40);
let mut button_add = Btn::create(&mut screen).unwrap();
button_add.set_align(Align::Center, -50, 32);
// button_add.set_pos(130, 150);
button_add.set_size(30, 30);
// button_add.set_align(Align::Center, 0, 0);
let mut btn_lbl1 = Label::create(&mut button_add).unwrap();
btn_lbl1.set_text(CString::new(b"+").unwrap().as_c_str());
button_add.on_event(|_btn, event| {
if let Event::Pressed = event {
println!("pressed");
if appdata.timer_stopped() {
appdata.add_secs(10);
}
}
// println!("Button received event: {:?}", event);
});
let mut button_sub = Btn::create(&mut screen).unwrap();
// button_sub.set_pos(170, 150);
button_sub.set_size(30, 30);
button_sub.set_align(Align::Center, 50, 35);
// button_sub.set_align(Align::Center, 0, 0);
let mut btn_lbl2 = Label::create(&mut button_sub).unwrap();
btn_lbl2.set_text(CString::new(b"-").unwrap().as_c_str());
button_sub.on_event(|_btn, event| {
if let Event::Pressed = event {
if appdata.timer_stopped() {
appdata.sub_secs(10);
}
}
});
let mut center_label = Label::new().unwrap();
center_label.set_text(CString::new(b"+/- 10s").unwrap().as_c_str());
center_label.set_align(Align::Center, 0, 35);
let mut cl_style = Style::default();
cl_style.set_text_color(Color::from_rgb((255, 255, 255)));
center_label.add_style(Part::Main, &mut cl_style);
let mut button_reset = Btn::create(&mut screen).unwrap();
//button_reset.set_pos(123, 190);
button_reset.set_align(Align::Center, -22, 70);
button_reset.set_size(35, 35);
let mut btn_lbl3 = Label::create(&mut button_reset).unwrap();
btn_lbl3.set_text(CString::new(b"\xEF\x80\xA1").unwrap().as_c_str());
const PLAY: &'static [u8; 3] = b"\xEF\x81\x8B";
const PAUSE: &'static [u8; 3] = b"\xEF\x81\x8C";
const STOP: &'static [u8; 3] = b"\xEF\x81\x8D";
let mut button_start_stop = Btn::create(&mut screen).unwrap();
// button_start_stop.set_pos(173, 190);
button_start_stop.set_align(Align::Center, 22, 70);
button_start_stop.set_size(35, 35);
let mut btn_lbl4 = Label::create(&mut button_start_stop).unwrap();
btn_lbl4.set_text(CString::new(b"\xEF\x81\x8B").unwrap().as_c_str());
button_reset.on_event(|_btn, event| {
if let Event::Pressed = event {
appdata.reset_timer();
btn_lbl4.set_text(CString::new(PLAY).unwrap().as_c_str());
}
});
button_start_stop.on_event(|_btn, event| {
if let Event::Pressed = event {
if appdata.timer_finished() {
// println!("Resetting finished timer");
appdata.reset_timer();
btn_lbl4.set_text(CString::new(PLAY).unwrap().as_c_str());
} else if appdata.timer_running() {
// println!("Pausing timer");
appdata.pause_timer();
btn_lbl4.set_text(CString::new(PLAY).unwrap().as_c_str());
} else {
// println!("Starting timer");
appdata.start_timer();
if appdata.timer_finished() {
// println!("Timer already finished");
btn_lbl4.set_text(CString::new(STOP).unwrap().as_c_str());
} else {
// println!("Timer running");
btn_lbl4.set_text(CString::new(PAUSE).unwrap().as_c_str());
}
}
}
});
let mut was_finished = false;
let mut last_rem_time: Duration = appdata.remaining() + Duration::from_millis(10);
*/
let mut last_time = Instant::now();
loop {
let start_time = Instant::now();
let start_draw_time = Instant::now();
lvgl::task_handler();
// Simulate clock - so sleep for one second so time text is incremented in
// seconds
// delay::FreeRtos::delay_ms(1);
let now_time = Instant::now();
lvgl::tick_inc(now_time.duration_since(last_time));
last_time = now_time;
let end_time = Instant::now();
let draw_time = raw_display.get_time();
let prep_time = start_draw_time - start_time;
let proc_time = end_time - start_draw_time;
let proc_time = proc_time - min(draw_time, proc_time);
if draw_time.as_micros() > 0 {
println!(
"draw time: {}.{:03}ms | prep time: {}.{:03}ms | proc time: {}.{:03}ms | total time: {}.{:03}ms",
draw_time.as_millis(),
draw_time.as_micros() % 100,
prep_time.as_millis(),
prep_time.as_micros() % 100,
proc_time.as_millis(),
proc_time.as_micros() % 100,
(draw_time + prep_time + proc_time).as_millis(),
(draw_time + prep_time + proc_time).as_micros() % 100, );
}
raw_display.reset_time();
delay::FreeRtos::delay_ms(2);
}
})
.unwrap();
loop {
// Don't exit application
delay::FreeRtos::delay_ms(1_000_000);
}
}

View File

@ -0,0 +1,546 @@
use std::{
cell::RefCell,
cmp::min,
str::FromStr,
sync::mpsc::channel,
thread,
time::{Duration, Instant},
};
use cstr_core::CString;
use display_interface_spi::SPIInterface;
use embedded_graphics_core::{
draw_target::DrawTarget,
prelude::Point,
};
use embedded_graphics_profiler_display::ProfilerDisplay;
use esp_idf_hal::spi::SpiSingleDeviceDriver;
use esp_idf_hal::{
delay::{self, Delay},
gpio::*,
peripherals::Peripherals,
spi::{
config::DriverConfig,
Dma,
SpiConfig,
SpiDeviceDriver,
},
units::FromValueType, // for converting 26MHz to value
};
use lvgl::{
font::Font,
input_device::{
pointer::{Pointer, PointerInputData},
InputDriver,
},
style::{FlexAlign, FlexFlow, Layout, Style},
widgets::{Arc, Btn, Label, Slider, Switch},
Align,
AnimationState,
Color,
Display,
DrawBuffer,
Event,
LvError,
NativeObject,
Obj,
Part,
Screen,
TextAlign,
Widget,
};
use mipidsi::{
models::ILI9341Rgb565,
options::{
ColorInversion,
ColorOrder,
Orientation,
Rotation,
},
Builder,
};
use xpt2046::Xpt2046;
fn lerp_fixed(start: u8, end: u8, t: u8, max_t: u8) -> u8 {
let (start, end, t, max_t) =
(start as u16, end as u16, t as u16, max_t as u16);
let t = t.min(max_t);
let result = start
+ ((end - start.min(end)) * t + (max_t / 2))
/ max_t;
result as u8
}
struct AppData {
timer_start: Instant,
timer_set_duration: Duration,
timer_remaining_duration: Duration,
timer_running: bool,
timer_paused: bool,
wattage_level: u8,
}
impl AppData {
fn new() -> Self {
Self {
timer_start: Instant::now(),
timer_set_duration: Duration::from_secs(10),
timer_remaining_duration: Duration::from_secs(
10,
),
timer_running: false,
timer_paused: false,
wattage_level: 5,
}
}
fn set_timer_duration(&mut self, duration: Duration) {
self.timer_set_duration = duration;
}
fn add_secs(&mut self, secs: u64) {
self.set_timer_duration(
self.timer_set_duration
.checked_add(Duration::from_secs(secs))
.unwrap_or(Duration::from_secs(5999))
.min(Duration::from_secs(5999)),
);
}
fn sub_secs(&mut self, secs: u64) {
self.set_timer_duration(
self.timer_set_duration
.checked_sub(Duration::from_secs(secs))
.unwrap_or(Duration::from_secs(10))
.max(Duration::from_secs(10)),
);
}
fn start_timer(&mut self) {
if !self.timer_paused {
self.timer_remaining_duration =
self.timer_set_duration;
}
self.timer_start = Instant::now();
self.timer_running = true;
self.timer_paused = false;
}
fn pause_timer(&mut self) {
self.timer_paused = true;
self.timer_remaining_duration = self
.timer_remaining_duration
.checked_sub(self.timer_start.elapsed())
.unwrap_or(Duration::from_secs(0));
}
fn reset_timer(&mut self) {
self.timer_start = Instant::now();
self.timer_paused = false;
self.timer_running = false;
self.timer_remaining_duration =
self.timer_set_duration;
}
fn remaining(&self) -> Duration {
if self.timer_stopped() {
self.timer_set_duration
} else if self.timer_paused() {
self.timer_remaining_duration
} else {
self.timer_remaining_duration
.checked_sub(self.timer_start.elapsed())
.unwrap_or(Duration::from_secs(0))
}
}
fn timer_stopped(&self) -> bool {
!self.timer_running
}
fn timer_paused(&self) -> bool {
self.timer_running && self.timer_paused
}
fn timer_running(&self) -> bool {
self.timer_running && !self.timer_paused
}
fn timer_finished(&self) -> bool {
self.timer_running
&& self.remaining() == Duration::from_secs(0)
}
fn set_wattage_level(&mut self, level: u8) {
assert!(
level < 6,
"wattage level cannot be over 6"
);
self.wattage_level = level;
}
fn get_wattage_level_str(&self) -> &'static str {
match self.wattage_level {
0 => "180W",
1 => "220W",
2 => "360W",
3 => "480W",
4 => "620W",
5 => "800W",
_ => unreachable!(),
}
}
}
fn main() -> Result<(), LvError> {
const HOR_RES: u32 = 320;
const VER_RES: u32 = 240;
const LINES: u32 = 20;
// It is necessary to call this function once. Otherwise
// some patches to the runtime implemented by esp-idf-sys might not link properly. See https://github.com/esp-rs/esp-idf-template/issues/71
esp_idf_svc::sys::link_patches();
// Initialize lvgl
lvgl::init();
// Bind the log crate to the ESP Logging facilities
esp_idf_svc::log::EspLogger::initialize_default();
log::info!("================= Starting App =======================");
let peripherals = Peripherals::take().unwrap();
// #[allow(unused)]
let pins = peripherals.pins;
let sclk = pins.gpio14;
let miso = pins.gpio12;
let mosi = pins.gpio13;
let cs = pins.gpio15;
let dc = pins.gpio2;
let spi = SpiDeviceDriver::new_single(
peripherals.spi2,
sclk, // sclk
mosi, // sdo
Some(miso), // sdi
Some(cs), // cs
&DriverConfig::new().dma(Dma::Channel1(4096)),
&SpiConfig::new()
.write_only(true)
.baudrate(10.MHz().into()),
)
.unwrap();
// let rst = PinDriver::output(pins.gpio33).unwrap();
let dc = PinDriver::output(dc).unwrap();
let di = SPIInterface::new(spi, dc);
// Turn backlight on
let mut bklt = PinDriver::output(pins.gpio21).unwrap();
bklt.set_high().unwrap();
// Configuration for M5Stack Core Development Kit V1.0
// Puts display in landscape mode with the three buttons
// at the bottom of screen let mut m5stack_display =
// Builder::ili9342c_rgb565(di)
// .with_display_size(320, 240)
// .with_color_order(ColorOrder::Bgr)
// .with_orientation(Orientation::Portrait(false))
// .with_invert_colors(mipidsi::ColorInversion::Inverted)
// .init(&mut delay::Ets, Some(rst))
// .unwrap();
// 0.6.0
// let mut raw_display = Builder::ili9342c_rgb565(di)
// .with_orientation(Orientation::Portrait(false))
// .with_color_order(ColorOrder::Bgr)
// .with_invert_colors(true)
// .init(&mut Delay::new_default(),
// None::<PinDriver<AnyOutputPin, Output>>)
// .unwrap();
let raw_display = Builder::new(ILI9341Rgb565, di)
.orientation(Orientation {
rotation: Rotation::Deg90,
mirrored: true,
})
.color_order(ColorOrder::Bgr)
// .invert_colors(ColorInversion::Inverted)
.init(&mut Delay::new_default())
.unwrap();
let mut raw_display = ProfilerDisplay::new(raw_display);
// Stack size value - 20,000 for 10 lines, 40,000 for
// 20 lines let (touch_send, touch_recv) =
// channel();
let touch_irq = pins.gpio36;
let touch_mosi = pins.gpio32;
let touch_miso = pins.gpio39;
let touch_clk = pins.gpio25;
let touch_cs = pins.gpio33;
let mut touch_driver = Xpt2046::new(
SpiDeviceDriver::new_single(
peripherals.spi3,
touch_clk,
touch_mosi,
Some(touch_miso),
Some(touch_cs),
&DriverConfig::new(),
&SpiConfig::new()
.write_only(true)
.baudrate(2.MHz().into()),
)
.unwrap(),
PinDriver::input(touch_irq).unwrap(),
xpt2046::Orientation::LandscapeFlipped,
);
touch_driver.set_num_samples(1);
touch_driver.init(&mut Delay::new_default()).unwrap();
let touch_driver = RefCell::new(touch_driver);
let _lvgl_thread = thread::Builder::new()
.stack_size(40_000)
.spawn(move || {
println!("thread started");
let mut appdata = AppData::new();
let buffer = DrawBuffer::<{ (HOR_RES * LINES) as usize }>::default();
let display = Display::register(buffer, HOR_RES, VER_RES, |refresh| {
raw_display.draw_iter(refresh.as_pixels()).unwrap();
})
.unwrap();
// Register a new input device that's capable of reading the current state of
// the input
// let mut last_touch = RefCell::new(None);
let _touch_screen = Pointer::register(
|| {
let mut td_ref = touch_driver.borrow_mut();
td_ref.run().expect("Running Touch driver failed");
if td_ref.is_touched() {
let point = td_ref.get_touch_point();
// println!("touched {:?}", point);
PointerInputData::Touch(Point::new(point.x + 20, 240 - point.y)).pressed().once()
} else {
// println!("untouched");
PointerInputData::Touch(Point::new(0, 0)).released().once()
}
},
&display,
);
// Create screen and widgets
let mut screen = display.get_scr_act().unwrap();
let mut screen_style = Style::default();
screen_style.set_bg_color(Color::from_rgb((0, 0, 0)));
screen_style.set_radius(0);
// screen_style.set_layout(Layout::flex());
// screen_style.set_flex_flow(FlexFlow::ROW_WRAP);
// screen_style.set_flex_main_place(FlexAlign::CENTER);
// screen_style.set_flex_cross_place(FlexAlign::CENTER);
screen.add_style(Part::Main, &mut screen_style);
let mut wattage_label = Label::create(&mut screen).unwrap();
wattage_label.set_align(Align::Center, -70, 0);
let mut wattage_style = Style::default();
wattage_style.set_text_color(Color::from_rgb((255, 255, 255)));
unsafe { wattage_style.set_text_font(Font::new_raw(lvgl_sys::lv_font_montserrat_32)) };
wattage_label.add_style(Part::Main, &mut wattage_style);
wattage_label.set_text(CString::new(appdata.get_wattage_level_str()).unwrap().as_c_str());
let mut power_arc = Arc::create(&mut screen).unwrap();
power_arc.set_size(150, 150);
power_arc.set_align(Align::Center, -70, 0);
power_arc.set_angles(135, 45); // Creates a 270-degree arc
power_arc.set_rotation(0);
power_arc.set_bg_angles(135, 45);
unsafe {
lvgl_sys::lv_arc_set_range(power_arc.raw().as_mut(), 0, 5);
}
power_arc.on_event(|arc, event| {
if let Event::ValueChanged = event {
let mut value = unsafe {
lvgl_sys::lv_arc_get_value(arc.raw().as_mut())
};
appdata.set_wattage_level((value as u8).min(6));
wattage_label.set_text(CString::new(appdata.get_wattage_level_str()).unwrap().as_c_str());
}
});
// Add timer display on the right
let mut time_label = Label::new().unwrap();
time_label.set_text(CString::new("00:10:000").unwrap().as_c_str());
let mut style_time = Style::default();
style_time.set_text_color(Color::from_rgb((255, 255, 255)));
style_time.set_text_align(TextAlign::Center);
unsafe { style_time.set_text_font(Font::new_raw(lvgl_sys::lv_font_montserrat_24)) };
time_label.add_style(Part::Main, &mut style_time);
time_label.set_align(Align::Center, 80, 0);
// Add + and - buttons at bottom
let mut button_add = Btn::create(&mut screen).unwrap();
button_add.set_align(Align::Center, 10, 70);
button_add.set_size(30, 30);
let mut btn_lbl1 = Label::create(&mut button_add).unwrap();
btn_lbl1.set_text(CString::new(b"+").unwrap().as_c_str());
let mut button_sub = Btn::create(&mut screen).unwrap();
button_sub.set_size(30, 30);
button_sub.set_align(Align::Center, 130, 70);
let mut btn_lbl2 = Label::create(&mut button_sub).unwrap();
btn_lbl2.set_text(CString::new(b"-").unwrap().as_c_str());
// Add timer control logic from timer.rs
button_add.on_event(|_btn, event| {
if let Event::Pressed = event {
if appdata.timer_stopped() {
appdata.add_secs(10);
}
}
});
button_sub.on_event(|_btn, event| {
if let Event::Pressed = event {
if appdata.timer_stopped() {
appdata.sub_secs(10);
}
}
});
// Add reset button
let mut button_reset = Btn::create(&mut screen).unwrap();
button_reset.set_align(Align::Center, 50, 70);
button_reset.set_size(35, 35);
let mut btn_lbl3 = Label::create(&mut button_reset).unwrap();
btn_lbl3.set_text(CString::new(b"\xEF\x80\xA1").unwrap().as_c_str());
// Add start/stop/pause button
const PLAY: &'static [u8; 3] = b"\xEF\x81\x8B";
const PAUSE: &'static [u8; 3] = b"\xEF\x81\x8C";
const STOP: &'static [u8; 3] = b"\xEF\x81\x8D";
let mut button_start_stop = Btn::create(&mut screen).unwrap();
button_start_stop.set_align(Align::Center, 90, 70);
button_start_stop.set_size(35, 35);
let mut btn_lbl4 = Label::create(&mut button_start_stop).unwrap();
btn_lbl4.set_text(CString::new(PLAY).unwrap().as_c_str());
// Add event handlers
button_reset.on_event(|_btn, event| {
if let Event::Pressed = event {
appdata.reset_timer();
btn_lbl4.set_text(CString::new(PLAY).unwrap().as_c_str());
unsafe {
lvgl_sys::lv_obj_clear_state(button_add.raw().as_mut(), lvgl_sys::LV_STATE_DISABLED as u16);
lvgl_sys::lv_obj_clear_state(button_sub.raw().as_mut(), lvgl_sys::LV_STATE_DISABLED as u16);
lvgl_sys::lv_obj_clear_state(power_arc.raw().as_mut(), lvgl_sys::LV_STATE_DISABLED as u16);
}
}
});
button_start_stop.on_event(|_btn, event| {
if let Event::Pressed = event {
if appdata.timer_finished() {
appdata.reset_timer();
btn_lbl4.set_text(CString::new(PLAY).unwrap().as_c_str());
// Enable all buttons
unsafe {
lvgl_sys::lv_obj_clear_state(button_add.raw().as_mut(), lvgl_sys::LV_STATE_DISABLED as u16);
lvgl_sys::lv_obj_clear_state(button_sub.raw().as_mut(), lvgl_sys::LV_STATE_DISABLED as u16);
lvgl_sys::lv_obj_clear_state(power_arc.raw().as_mut(), lvgl_sys::LV_STATE_DISABLED as u16);
}
} else if appdata.timer_running() {
appdata.pause_timer();
btn_lbl4.set_text(CString::new(PLAY).unwrap().as_c_str());
// Enable all buttons
unsafe {
lvgl_sys::lv_obj_clear_state(button_add.raw().as_mut(), lvgl_sys::LV_STATE_DISABLED as u16);
lvgl_sys::lv_obj_clear_state(button_sub.raw().as_mut(), lvgl_sys::LV_STATE_DISABLED as u16);
lvgl_sys::lv_obj_clear_state(power_arc.raw().as_mut(), lvgl_sys::LV_STATE_DISABLED as u16);
}
} else {
appdata.start_timer();
if appdata.timer_finished() {
btn_lbl4.set_text(CString::new(STOP).unwrap().as_c_str());
} else {
btn_lbl4.set_text(CString::new(PAUSE).unwrap().as_c_str());
// Disable buttons while running
unsafe {
lvgl_sys::lv_obj_add_state(button_add.raw().as_mut(), lvgl_sys::LV_STATE_DISABLED as u16);
lvgl_sys::lv_obj_add_state(button_sub.raw().as_mut(), lvgl_sys::LV_STATE_DISABLED as u16);
lvgl_sys::lv_obj_add_state(power_arc.raw().as_mut(), lvgl_sys::LV_STATE_DISABLED as u16);
}
}
}
}
});
let mut was_finished = false;
let mut last_rem_time: Duration = appdata.remaining() + Duration::from_millis(10);
let mut last_time = Instant::now();
loop {
let start_time = Instant::now();
let dur = appdata.remaining();
if last_rem_time != dur {
let val = CString::new(format!("{:02}:{:02}:{:03}",
dur.as_secs() / 60,
dur.as_secs() % 60,
dur.as_millis() % 1000)).unwrap();
time_label.set_text(&val).unwrap();
last_rem_time = dur;
}
if !was_finished && appdata.timer_finished() {
btn_lbl4.set_text(CString::new(STOP).unwrap().as_c_str());
}
was_finished = appdata.timer_finished();
let start_draw_time = Instant::now();
lvgl::task_handler();
let now_time = Instant::now();
lvgl::tick_inc(now_time.duration_since(last_time));
last_time = now_time;
let end_time = Instant::now();
let draw_time = raw_display.get_time();
let prep_time = start_draw_time - start_time;
let proc_time = end_time - start_draw_time;
let proc_time = proc_time - min(draw_time, proc_time);
if draw_time.as_micros() > 0 {
println!(
"draw time: {}.{:03}ms | prep time: {}.{:03}ms | proc time: {}.{:03}ms | total time: {}.{:03}ms",
draw_time.as_millis(),
draw_time.as_micros() % 100,
prep_time.as_millis(),
prep_time.as_micros() % 100,
proc_time.as_millis(),
proc_time.as_micros() % 100,
(draw_time + prep_time + proc_time).as_millis(),
(draw_time + prep_time + proc_time).as_micros() % 100, );
}
raw_display.reset_time();
delay::FreeRtos::delay_ms(2);
}
})
.unwrap();
loop {
// Don't exit application
delay::FreeRtos::delay_ms(1_000_000);
}
}

View File

@ -8,14 +8,22 @@ use std::{
use cstr_core::CString; use cstr_core::CString;
use display_interface_spi::SPIInterface; use display_interface_spi::SPIInterface;
use embedded_graphics_core::{draw_target::DrawTarget, prelude::Point}; use embedded_graphics_core::{
draw_target::DrawTarget,
prelude::Point,
};
use embedded_graphics_profiler_display::ProfilerDisplay; use embedded_graphics_profiler_display::ProfilerDisplay;
use esp_idf_hal::spi::SpiSingleDeviceDriver; use esp_idf_hal::spi::SpiSingleDeviceDriver;
use esp_idf_hal::{ use esp_idf_hal::{
delay::{self, Delay}, delay::{self, Delay},
gpio::*, gpio::*,
peripherals::Peripherals, peripherals::Peripherals,
spi::{config::DriverConfig, Dma, SpiConfig, SpiDeviceDriver}, spi::{
config::DriverConfig,
Dma,
SpiConfig,
SpiDeviceDriver,
},
units::FromValueType, // for converting 26MHz to value units::FromValueType, // for converting 26MHz to value
}; };
use lvgl::{ use lvgl::{
@ -24,7 +32,7 @@ use lvgl::{
pointer::{Pointer, PointerInputData}, pointer::{Pointer, PointerInputData},
InputDriver, InputDriver,
}, },
style::Style, style::{FlexAlign, FlexFlow, Style},
widgets::{Btn, Label}, widgets::{Btn, Label},
Align, Align,
Color, Color,
@ -32,13 +40,19 @@ use lvgl::{
DrawBuffer, DrawBuffer,
Event, Event,
LvError, LvError,
Obj,
Part, Part,
TextAlign, TextAlign,
Widget, Widget,
}; };
use mipidsi::{ use mipidsi::{
models::ILI9486Rgb565, models::ILI9341Rgb565,
options::{ColorInversion, ColorOrder, Orientation, Rotation}, options::{
ColorInversion,
ColorOrder,
Orientation,
Rotation,
},
Builder, Builder,
}; };
use xpt2046::Xpt2046; use xpt2046::Xpt2046;
@ -56,7 +70,9 @@ impl AppData {
Self { Self {
timer_start: Instant::now(), timer_start: Instant::now(),
timer_set_duration: Duration::from_secs(10), timer_set_duration: Duration::from_secs(10),
timer_remaining_duration: Duration::from_secs(10), timer_remaining_duration: Duration::from_secs(
10,
),
timer_running: false, timer_running: false,
timer_paused: false, timer_paused: false,
} }
@ -86,7 +102,8 @@ impl AppData {
fn start_timer(&mut self) { fn start_timer(&mut self) {
if !self.timer_paused { if !self.timer_paused {
self.timer_remaining_duration = self.timer_set_duration; self.timer_remaining_duration =
self.timer_set_duration;
} }
self.timer_start = Instant::now(); self.timer_start = Instant::now();
@ -106,7 +123,8 @@ impl AppData {
self.timer_start = Instant::now(); self.timer_start = Instant::now();
self.timer_paused = false; self.timer_paused = false;
self.timer_running = false; self.timer_running = false;
self.timer_remaining_duration = self.timer_set_duration; self.timer_remaining_duration =
self.timer_set_duration;
} }
fn remaining(&self) -> Duration { fn remaining(&self) -> Duration {
@ -134,7 +152,8 @@ impl AppData {
} }
fn timer_finished(&self) -> bool { fn timer_finished(&self) -> bool {
self.timer_running && self.remaining() == Duration::from_secs(0) self.timer_running
&& self.remaining() == Duration::from_secs(0)
} }
} }
@ -143,8 +162,8 @@ fn main() -> Result<(), LvError> {
const VER_RES: u32 = 240; const VER_RES: u32 = 240;
const LINES: u32 = 20; const LINES: u32 = 20;
// It is necessary to call this function once. Otherwise some patches to the // It is necessary to call this function once. Otherwise
// runtime implemented by esp-idf-sys might not link properly. See https://github.com/esp-rs/esp-idf-template/issues/71 // some patches to the runtime implemented by esp-idf-sys might not link properly. See https://github.com/esp-rs/esp-idf-template/issues/71
esp_idf_svc::sys::link_patches(); esp_idf_svc::sys::link_patches();
// Initialize lvgl // Initialize lvgl
@ -173,7 +192,9 @@ fn main() -> Result<(), LvError> {
Some(miso), // sdi Some(miso), // sdi
Some(cs), // cs Some(cs), // cs
&DriverConfig::new().dma(Dma::Channel1(4096)), &DriverConfig::new().dma(Dma::Channel1(4096)),
&SpiConfig::new().write_only(true).baudrate(10.MHz().into()), &SpiConfig::new()
.write_only(true)
.baudrate(10.MHz().into()),
) )
.unwrap(); .unwrap();
@ -186,8 +207,9 @@ fn main() -> Result<(), LvError> {
bklt.set_high().unwrap(); bklt.set_high().unwrap();
// Configuration for M5Stack Core Development Kit V1.0 // Configuration for M5Stack Core Development Kit V1.0
// Puts display in landscape mode with the three buttons at the bottom of screen // Puts display in landscape mode with the three buttons
// let mut m5stack_display = Builder::ili9342c_rgb565(di) // at the bottom of screen let mut m5stack_display =
// Builder::ili9342c_rgb565(di)
// .with_display_size(320, 240) // .with_display_size(320, 240)
// .with_color_order(ColorOrder::Bgr) // .with_color_order(ColorOrder::Bgr)
// .with_orientation(Orientation::Portrait(false)) // .with_orientation(Orientation::Portrait(false))
@ -200,23 +222,25 @@ fn main() -> Result<(), LvError> {
// .with_orientation(Orientation::Portrait(false)) // .with_orientation(Orientation::Portrait(false))
// .with_color_order(ColorOrder::Bgr) // .with_color_order(ColorOrder::Bgr)
// .with_invert_colors(true) // .with_invert_colors(true)
// .init(&mut Delay::new_default(), None::<PinDriver<AnyOutputPin, Output>>) // .init(&mut Delay::new_default(),
// None::<PinDriver<AnyOutputPin, Output>>)
// .unwrap(); // .unwrap();
let raw_display = Builder::new(ILI9486Rgb565, di) let raw_display = Builder::new(ILI9341Rgb565, di)
.orientation(Orientation { .orientation(Orientation {
rotation: Rotation::Deg90, rotation: Rotation::Deg90,
mirrored: true, mirrored: true,
}) })
.color_order(ColorOrder::Bgr) .color_order(ColorOrder::Bgr)
.invert_colors(ColorInversion::Inverted) // .invert_colors(ColorInversion::Inverted)
.init(&mut Delay::new_default()) .init(&mut Delay::new_default())
.unwrap(); .unwrap();
let mut raw_display = ProfilerDisplay::new(raw_display); let mut raw_display = ProfilerDisplay::new(raw_display);
// Stack size value - 20,000 for 10 lines, 40,000 for 20 lines // Stack size value - 20,000 for 10 lines, 40,000 for
// let (touch_send, touch_recv) = channel(); // 20 lines let (touch_send, touch_recv) =
// channel();
let touch_irq = pins.gpio36; let touch_irq = pins.gpio36;
let touch_mosi = pins.gpio32; let touch_mosi = pins.gpio32;
let touch_miso = pins.gpio39; let touch_miso = pins.gpio39;
@ -231,7 +255,9 @@ fn main() -> Result<(), LvError> {
Some(touch_miso), Some(touch_miso),
Some(touch_cs), Some(touch_cs),
&DriverConfig::new(), &DriverConfig::new(),
&SpiConfig::new().write_only(true).baudrate(2.MHz().into()), &SpiConfig::new()
.write_only(true)
.baudrate(2.MHz().into()),
) )
.unwrap(), .unwrap(),
PinDriver::input(touch_irq).unwrap(), PinDriver::input(touch_irq).unwrap(),
@ -282,9 +308,11 @@ fn main() -> Result<(), LvError> {
screen.add_style(Part::Main, &mut screen_style); screen.add_style(Part::Main, &mut screen_style);
let mut time = Label::new().unwrap(); let mut time = Label::new().unwrap();
time.set_text(CString::new("00:10:000").unwrap().as_c_str());
let mut style_time = Style::default(); let mut style_time = Style::default();
style_time.set_text_color(Color::from_rgb((255, 255, 255))); // white style_time.set_text_color(Color::from_rgb((255, 255, 255))); // white
style_time.set_text_align(TextAlign::Center); style_time.set_text_align(TextAlign::Center);
unsafe { style_time.set_text_font(Font::new_raw(lvgl_sys::lv_font_montserrat_24)) };
// Custom font requires lvgl-sys in Cargo.toml and 'use lvgl_sys' in this file // Custom font requires lvgl-sys in Cargo.toml and 'use lvgl_sys' in this file
@ -293,10 +321,22 @@ fn main() -> Result<(), LvError> {
// Time text will be centered in screen // Time text will be centered in screen
time.set_align(Align::Center, 0, 0); time.set_align(Align::Center, 0, 0);
// let mut cont = Obj::new().unwrap();
// cont.set_size(320, 50);
// let mut style = Style::default();
// style.set_flex_flow(FlexFlow::ROW);
// style.set_flex_main_place(FlexAlign::SPACE_EVENLY);
// style.set_flex_cross_place(FlexAlign::CENTER);
// // style.set_bg_opa(0);
// style.set_border_width(0);
// cont.add_style(Part::Any, &mut style);
// cont.set_align(Align::Center, 0, 40);
let mut button_add = Btn::create(&mut screen).unwrap(); let mut button_add = Btn::create(&mut screen).unwrap();
// button_add.set_align(Align::Center, -50, -100); button_add.set_align(Align::Center, -50, 32);
button_add.set_pos(130, 150); // button_add.set_pos(130, 150);
button_add.set_size(30, 30); button_add.set_size(30, 30);
// button_add.set_align(Align::Center, 0, 0);
let mut btn_lbl1 = Label::create(&mut button_add).unwrap(); let mut btn_lbl1 = Label::create(&mut button_add).unwrap();
btn_lbl1.set_text(CString::new(b"+").unwrap().as_c_str()); btn_lbl1.set_text(CString::new(b"+").unwrap().as_c_str());
@ -311,8 +351,10 @@ fn main() -> Result<(), LvError> {
}); });
let mut button_sub = Btn::create(&mut screen).unwrap(); let mut button_sub = Btn::create(&mut screen).unwrap();
button_sub.set_pos(170, 150); // button_sub.set_pos(170, 150);
button_sub.set_size(30, 30); button_sub.set_size(30, 30);
button_sub.set_align(Align::Center, 50, 35);
// button_sub.set_align(Align::Center, 0, 0);
let mut btn_lbl2 = Label::create(&mut button_sub).unwrap(); let mut btn_lbl2 = Label::create(&mut button_sub).unwrap();
btn_lbl2.set_text(CString::new(b"-").unwrap().as_c_str()); btn_lbl2.set_text(CString::new(b"-").unwrap().as_c_str());
@ -324,8 +366,16 @@ fn main() -> Result<(), LvError> {
} }
}); });
let mut center_label = Label::new().unwrap();
center_label.set_text(CString::new(b"+/- 10s").unwrap().as_c_str());
center_label.set_align(Align::Center, 0, 35);
let mut cl_style = Style::default();
cl_style.set_text_color(Color::from_rgb((255, 255, 255)));
center_label.add_style(Part::Main, &mut cl_style);
let mut button_reset = Btn::create(&mut screen).unwrap(); let mut button_reset = Btn::create(&mut screen).unwrap();
button_reset.set_pos(123, 190); //button_reset.set_pos(123, 190);
button_reset.set_align(Align::Center, -22, 70);
button_reset.set_size(35, 35); button_reset.set_size(35, 35);
let mut btn_lbl3 = Label::create(&mut button_reset).unwrap(); let mut btn_lbl3 = Label::create(&mut button_reset).unwrap();
btn_lbl3.set_text(CString::new(b"\xEF\x80\xA1").unwrap().as_c_str()); btn_lbl3.set_text(CString::new(b"\xEF\x80\xA1").unwrap().as_c_str());
@ -336,7 +386,8 @@ fn main() -> Result<(), LvError> {
const STOP: &'static [u8; 3] = b"\xEF\x81\x8D"; const STOP: &'static [u8; 3] = b"\xEF\x81\x8D";
let mut button_start_stop = Btn::create(&mut screen).unwrap(); let mut button_start_stop = Btn::create(&mut screen).unwrap();
button_start_stop.set_pos(173, 190); // button_start_stop.set_pos(173, 190);
button_start_stop.set_align(Align::Center, 22, 70);
button_start_stop.set_size(35, 35); button_start_stop.set_size(35, 35);
let mut btn_lbl4 = Label::create(&mut button_start_stop).unwrap(); let mut btn_lbl4 = Label::create(&mut button_start_stop).unwrap();
btn_lbl4.set_text(CString::new(b"\xEF\x81\x8B").unwrap().as_c_str()); btn_lbl4.set_text(CString::new(b"\xEF\x81\x8B").unwrap().as_c_str());
@ -373,15 +424,22 @@ fn main() -> Result<(), LvError> {
}); });
let mut was_finished = false; let mut was_finished = false;
let mut last_rem_time: Duration = appdata.remaining() + Duration::from_millis(10);
let mut last_time = Instant::now();
loop { loop {
let start_time = Instant::now(); let start_time = Instant::now();
let rem_time = appdata.remaining(); let rem_time = appdata.remaining();
if rem_time != last_rem_time {
let val = CString::new(format!("{:02}:{:02}:{:03}", let val = CString::new(format!("{:02}:{:02}:{:03}",
rem_time.as_secs() / 60, rem_time.as_secs() / 60,
rem_time.as_secs() % 60, rem_time.as_secs() % 60,
rem_time.as_millis() % 1000)).unwrap(); rem_time.as_millis() % 1000)).unwrap();
time.set_text(&val).unwrap(); time.set_text(&val).unwrap();
}
last_rem_time = rem_time;
if !was_finished && appdata.timer_finished() { if !was_finished && appdata.timer_finished() {
was_finished = true; was_finished = true;
@ -396,7 +454,9 @@ fn main() -> Result<(), LvError> {
// seconds // seconds
// delay::FreeRtos::delay_ms(1); // delay::FreeRtos::delay_ms(1);
lvgl::tick_inc(Instant::now().duration_since(start_time)); let now_time = Instant::now();
lvgl::tick_inc(now_time.duration_since(last_time));
last_time = now_time;
let end_time = Instant::now(); let end_time = Instant::now();
@ -419,7 +479,7 @@ fn main() -> Result<(), LvError> {
} }
raw_display.reset_time(); raw_display.reset_time();
delay::FreeRtos::delay_ms(1); delay::FreeRtos::delay_ms(2);
} }
}) })

View File

@ -14,3 +14,6 @@ imports_layout = "HorizontalVertical"
# Miscellaneous # Miscellaneous
enum_discrim_align_threshold = 25 enum_discrim_align_threshold = 25
hex_literal_case = "Upper" hex_literal_case = "Upper"
# Length
max_width=60

View File

@ -9,9 +9,8 @@
# This file is MIT or Apache-2.0 as per the repository README.md file # This file is MIT or Apache-2.0 as per the repository README.md file
# #
[target.xtensa-esp32-none]
# runner = "espflash --monitor" # Select this runner for espflash v1.x.x # runner = "espflash --monitor" # Select this runner for espflash v1.x.x
runner = "espflash flash --monitor --baud 921600" # Select this runner for espflash v2.x.x # runner = "espflash flash --monitor --baud 921600" # Select this runner for espflash v2.x.x
# Target specific options # Target specific options
[target.thumbv6m-none-eabi] [target.thumbv6m-none-eabi]

View File

@ -2340,9 +2340,9 @@ dependencies = [
[[package]] [[package]]
name = "itoa" name = "itoa"
version = "1.0.11" version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" checksum = "540654e97a3f4470a492cd30ff187bc95d89557a903a2bbf112e2fae98104ef2"
[[package]] [[package]]
name = "jni" name = "jni"

View File

@ -12,9 +12,26 @@ build = "build.rs"
# [[bin]] # [[bin]]
# name = "main" # name = "main"
[features]
starter = []
timer = []
light-control = []
microwave-ui = []
simulator = ["slint/backend-winit"]
[[bin]] [[bin]]
name = "starter" name = "starter"
[[bin]]
name = "timer"
[[bin]]
name = "light-control"
[[bin]]
name = "microwave-ui"
[dependencies] [dependencies]
slint = { version = "1.8", default-features = false, features = ["compat-1-2", "renderer-software", "libm", "unsafe-single-threaded"] } slint = { version = "1.8", default-features = false, features = ["compat-1-2", "renderer-software", "libm", "unsafe-single-threaded"] }
@ -42,7 +59,7 @@ embedded-graphics-profiler-display = { version = "0.1.0", path = "../embedded-gr
xpt2046 = { git = "https://github.com/Yandrik/xpt2046.git", version = "0.3.1" } xpt2046 = { git = "https://github.com/Yandrik/xpt2046.git", version = "0.3.1" }
esp-backtrace = { version = "0.14.2", features = ["esp32", "println"] } esp-backtrace = { version = "0.14.2", features = ["esp32", "println", "panic-handler", "exception-handler"] }
static_cell = { version = "2.1.0", features = ["nightly"] } static_cell = { version = "2.1.0", features = ["nightly"] }
embedded-hal-bus = "0.2.0" embedded-hal-bus = "0.2.0"
@ -51,19 +68,3 @@ embedded-hal-bus = "0.2.0"
slint-build = { version = "1.8" } slint-build = { version = "1.8" }
[features]
simulator = ["slint/backend-winit"]
# pico = [
# "slint/unsafe-single-threaded",
# "slint/libm",
# "cortex-m",
# "cortex-m-rt",
# "embedded-alloc",
# "embedded-hal",
# "fugit",
# "display-interface-spi",
# "embedded-graphics-core",
# "shared-bus",
# "panic-halt"
# ]

View File

@ -1,8 +1,30 @@
fn main() { fn main() {
#[cfg(feature = "starter")]
slint_build::compile_with_config( slint_build::compile_with_config(
"ui/app-window.slint", "ui/app-window.slint",
slint_build::CompilerConfiguration::new() slint_build::CompilerConfiguration::new()
.embed_resources(slint_build::EmbedResourcesKind::EmbedForSoftwareRenderer), .embed_resources(slint_build::EmbedResourcesKind::EmbedForSoftwareRenderer),
) )
.unwrap(); .unwrap();
#[cfg(feature = "timer")]
slint_build::compile_with_config(
"ui/timer-app.slint",
slint_build::CompilerConfiguration::new()
.embed_resources(slint_build::EmbedResourcesKind::EmbedForSoftwareRenderer),
)
.unwrap();
#[cfg(feature = "light-control")]
slint_build::compile_with_config(
"ui/lights-app.slint",
slint_build::CompilerConfiguration::new()
.embed_resources(slint_build::EmbedResourcesKind::EmbedForSoftwareRenderer),
)
.unwrap();
#[cfg(feature = "microwave-ui")]
slint_build::compile_with_config(
"ui/microwave-ui.slint",
slint_build::CompilerConfiguration::new()
.embed_resources(slint_build::EmbedResourcesKind::EmbedForSoftwareRenderer),
)
.unwrap();
} }

BIN
slint-based/img/pause.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 373 B

BIN
slint-based/img/play.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 401 B

BIN
slint-based/img/restart.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 542 B

View File

@ -1 +1,2 @@
use_small_heuristics = "Max" use_small_heuristics = "Max"
max_width = 60

View File

@ -0,0 +1,432 @@
#![no_std]
#![no_main]
extern crate alloc;
use alloc::boxed::Box;
use alloc::rc::Rc;
use core::mem::MaybeUninit;
use core::{cell::RefCell, cmp::min, fmt};
use display_interface_spi::SPIInterface;
use embassy_embedded_hal::shared_bus::blocking::spi::SpiDevice;
use embassy_executor::Spawner;
use embassy_sync::{
blocking_mutex::{raw::NoopRawMutex, NoopMutex},
signal::Signal,
};
use embassy_time::Delay;
use embassy_time::{Duration, Instant, Timer};
use embedded_graphics_core::pixelcolor::Rgb565;
use embedded_graphics_core::prelude::*;
use embedded_graphics_core::primitives::Rectangle;
use embedded_graphics_profiler_display::ProfilerDisplay;
use embedded_hal::digital::OutputPin;
use embedded_hal_bus::spi::ExclusiveDevice;
use esp_backtrace as _;
use esp_hal::interrupt::Priority;
use esp_hal::{self, prelude::*};
use esp_hal::{
clock::ClockControl,
gpio::{
GpioPin, Input, Io, Level, Output, Pull, NO_PIN,
},
peripherals::{Peripherals, SPI2, SPI3},
prelude::*,
rtc_cntl::Rtc,
spi::{master::Spi, FullDuplexMode, SpiMode},
system::SystemControl,
timer::timg::TimerGroup,
};
use esp_hal_embassy::InterruptExecutor;
use esp_println::println;
use mipidsi::{
models::ILI9486Rgb565,
options::{
ColorInversion, ColorOrder, Orientation, Rotation,
},
Builder, Display,
};
use slint::platform::software_renderer::MinimalSoftwareWindow;
use slint::platform::{
Platform, PointerEventButton, WindowEvent,
};
use slint::private_unstable_api::re_exports::LogicalPoint;
use slint::LogicalPosition;
use static_cell::StaticCell;
use xpt2046::Xpt2046;
use esp_alloc as _;
use mipidsi::models::ILI9341Rgb565;
// slint::slint!{ export MyUI := Window {} }
/*
slint::include_modules!();
# */
slint::include_modules!();
fn init_heap() {
const HEAP_SIZE: usize = 64 * 1024;
static mut HEAP: MaybeUninit<[u8; HEAP_SIZE]> =
MaybeUninit::uninit();
unsafe {
esp_alloc::HEAP.add_region(
esp_alloc::HeapRegion::new(
HEAP.as_mut_ptr() as *mut u8,
HEAP_SIZE,
esp_alloc::MemoryCapability::Internal
.into(),
),
);
}
}
#[embassy_executor::task]
async fn touch_task(
touch_irq: GpioPin<36>,
spi: ExclusiveDevice<
Spi<'static, SPI3, FullDuplexMode>,
Output<'static, GpioPin<33>>,
&'static mut Delay,
>,
touch_signal: &'static Signal<
NoopRawMutex,
Option<Point>,
>,
) -> ! {
let mut touch_driver = Xpt2046::new(
spi,
Input::new(touch_irq, Pull::Up),
xpt2046::Orientation::LandscapeFlipped,
);
touch_driver.set_num_samples(16);
touch_driver.init(&mut embassy_time::Delay).unwrap();
esp_println::println!("touch task");
loop {
touch_driver
.run()
.expect("Running Touch driver failed");
if touch_driver.is_touched() {
let point = touch_driver.get_touch_point();
touch_signal.signal(Some(Point::new(
point.x + 25,
240 - point.y,
)));
} else {
touch_signal.signal(None);
}
Timer::after(Duration::from_millis(1)).await; // 100 a second
// Your touch handling logic here
}
}
fn point_to_logical_pos(point: Point) -> LogicalPosition {
LogicalPosition::new(point.x as f32, point.y as f32)
}
struct CYDPlatform {
window: Rc<slint::platform::software_renderer::MinimalSoftwareWindow>,
}
impl Platform for CYDPlatform {
fn create_window_adapter(
&self,
) -> Result<
Rc<dyn slint::platform::WindowAdapter>,
slint::PlatformError,
> {
Ok(self.window.clone())
}
fn duration_since_start(&self) -> core::time::Duration {
embassy_time::Instant::from_millis(0)
.elapsed()
.into()
}
//noinspection DuplicatedCode
fn run_event_loop(
&self,
) -> Result<(), slint::PlatformError> {
todo!();
}
}
#[main]
async fn main(spawner: Spawner) {
init_heap();
let peripherals = Peripherals::take();
let system = SystemControl::new(peripherals.SYSTEM);
let mut clocks =
ClockControl::boot_defaults(system.clock_control)
.freeze();
let mut rtc = Rtc::new(peripherals.LPWR);
rtc.rwdt.disable();
let mut timer_group0 =
TimerGroup::new(peripherals.TIMG0, &clocks);
timer_group0.wdt.disable();
let mut timer_group1 =
TimerGroup::new(peripherals.TIMG1, &clocks);
timer_group1.wdt.disable();
esp_hal_embassy::init(&clocks, timer_group0.timer0);
let io = Io::new(peripherals.GPIO, peripherals.IO_MUX);
let touch_irq = io.pins.gpio36;
let touch_mosi = io.pins.gpio32;
let touch_miso = io.pins.gpio39;
let touch_clk = io.pins.gpio25;
let touch_cs = io.pins.gpio33;
// 2MHz is the MAX! DO NOT DECREASE! This is really important.
let mut touch_spi = Spi::new(
peripherals.SPI3,
2.MHz(),
SpiMode::Mode0,
&mut clocks,
)
.with_pins(
Some(touch_clk),
Some(touch_mosi),
Some(touch_miso),
NO_PIN,
);
static TOUCH_DELAY_STATICCELL: StaticCell<Delay> =
StaticCell::new();
let mut delay = TOUCH_DELAY_STATICCELL.init(Delay);
let touch_spi = ExclusiveDevice::new(
touch_spi,
Output::new(touch_cs, Level::Low),
delay,
)
.unwrap();
let touch_signal = Signal::new();
static TOUCH_SIGNAL: StaticCell<
Signal<NoopRawMutex, Option<Point>>,
> = StaticCell::new();
let touch_signal = &*TOUCH_SIGNAL.init(touch_signal);
// let sw_int = system.software_interrupt_control.software_interrupt2;
// static EXECUTOR: StaticCell<InterruptExecutor<2>> = StaticCell::new();
// let executor = InterruptExecutor::<2>::new(sw_int);
// let executor = EXECUTOR.init(executor);
spawner
.spawn(touch_task(
touch_irq,
touch_spi,
touch_signal,
))
.unwrap();
// executor.start(Priority::Priority1);
// Display setup
let sclk = io.pins.gpio14;
let miso = io.pins.gpio12;
let mosi = io.pins.gpio13;
let cs = io.pins.gpio15;
let dc = io.pins.gpio2;
let mut backlight =
Output::new(io.pins.gpio21, Level::Low);
let mut spi = Spi::new(
peripherals.SPI2,
10u32.MHz(),
SpiMode::Mode0,
&clocks,
)
.with_pins(
Some(sclk),
Some(mosi),
Some(miso),
esp_hal::gpio::NO_PIN,
);
// static DISP_SPI_BUS: StaticCell<NoopMutex<RefCell<Spi<SPI2, FullDuplexMode>>>> = StaticCell::new();
// let spi_bus = NoopMutex::new(RefCell::new(spi));
// let spi_bus = DISP_SPI_BUS.init(spi_bus);
static spi_delay_staticcell: StaticCell<Delay> =
StaticCell::new();
let mut delay = spi_delay_staticcell.init(Delay);
let spi = ExclusiveDevice::new(
spi,
Output::new(cs, Level::Low),
delay,
)
.unwrap();
let di =
SPIInterface::new(spi, Output::new(dc, Level::Low));
let display = Builder::new(ILI9341Rgb565, di)
.orientation(Orientation {
rotation: Rotation::Deg90,
mirrored: true,
})
.color_order(ColorOrder::Bgr)
// .invert_colors(ColorInversion::Inverted)
.init(&mut embassy_time::Delay)
.unwrap();
let mut display = ProfilerDisplay::new(display);
backlight.set_high();
let size = display.bounding_box().size;
// let size = slint::PhysicalSize::new(size.width as u32, size.height as u32);
let window =
MinimalSoftwareWindow::new(Default::default());
slint::platform::set_platform(Box::new(CYDPlatform {
window: window.clone(),
}))
.unwrap();
let ui = create_slint_app();
window.set_size(slint::PhysicalSize::new(320, 240));
let mut buffer_provider = DrawBuffer {
display,
buffer: &mut [slint::platform::software_renderer::Rgb565Pixel(0); 320],
};
let mut last_touch = None;
loop {
let start_time = Instant::now();
if let Some(touch) = touch_signal.try_take() {
// println!("touch: {:?}, last_touch: {:?}", touch, last_touch);
let button = PointerEventButton::Left;
let interact = match (touch, last_touch) {
(Some(point), Some(_)) => {
Some(WindowEvent::PointerMoved {
position: point_to_logical_pos(
point,
),
})
}
(Some(point), None) => {
Some(WindowEvent::PointerPressed {
position: point_to_logical_pos(
point,
),
button,
})
}
(None, Some(point)) => {
Some(WindowEvent::PointerReleased {
position: point_to_logical_pos(
point,
),
button,
})
}
(None, None) => None,
};
if let Some(event) = interact {
// println!("event: {:?}", event);
window.dispatch_event(event);
}
last_touch = touch;
}
slint::platform::update_timers_and_animations();
// let window = window.clone();
let start_draw_time = Instant::now();
window.draw_if_needed(|renderer| {
renderer.render_by_line(&mut buffer_provider);
});
if window.has_active_animations() {
continue;
}
let display = &mut buffer_provider.display;
let end_time = embassy_time::Instant::now();
let draw_time = display.get_time();
let prep_time = start_draw_time - start_time;
let proc_time = end_time - start_draw_time;
let proc_time =
proc_time - min(draw_time, proc_time);
rtc.rwdt.feed();
if draw_time.as_micros() > 0 {
println!(
"draw time: {}.{:03}ms | prep time: {}.{:03}ms | proc time: {}.{:03}ms | total time: {}.{:03}ms",
draw_time.as_millis(),
draw_time.as_micros() % 100,
prep_time.as_millis(),
prep_time.as_micros() % 100,
proc_time.as_millis(),
proc_time.as_micros() % 100,
(draw_time + prep_time + proc_time).as_millis(),
(draw_time + prep_time + proc_time).as_micros() % 100, );
}
display.reset_time();
Timer::after(Duration::from_millis(1)).await; // 60 a second
}
}
fn create_slint_app() -> LightsApp {
let ui = LightsApp::new().unwrap();
ui
}
struct DrawBuffer<'a, DT> {
display: DT,
buffer: &'a mut [slint::platform::software_renderer::Rgb565Pixel],
}
impl<
// DI: display_interface_spi::WriteOnlyDataCommand,
E: core::fmt::Debug,
DT: DrawTarget<Color = Rgb565, Error = E>,
// RST: OutputPin<Error = core::convert::Infallible>,
> slint::platform::software_renderer::LineBufferProvider
for &mut DrawBuffer<'_, DT>
{
type TargetPixel =
slint::platform::software_renderer::Rgb565Pixel;
fn process_line(
&mut self,
line: usize,
range: core::ops::Range<usize>,
render_fn: impl FnOnce(&mut [slint::platform::software_renderer::Rgb565Pixel]),
) {
let buffer = &mut self.buffer[range.clone()];
render_fn(buffer);
// We send empty data just to get the device in the right window
self.display
.fill_contiguous(
&Rectangle::new(
Point::new(range.start as i32, line as i32),
Size::new((range.end - range.start) as u32, 1),
),
// range.start as u16,
// line as _,
// range.end as u16,
// line as u16,
buffer
.iter()
.map(|x| embedded_graphics_core::pixelcolor::raw::RawU16::new(x.0).into()),
)
.unwrap();
}
}

View File

@ -0,0 +1,583 @@
#![no_std]
#![no_main]
extern crate alloc;
use alloc::boxed::Box;
use alloc::rc::Rc;
use core::mem::MaybeUninit;
use core::{cell::RefCell, cmp::min};
use display_interface_spi::SPIInterface;
use embassy_executor::Spawner;
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
use embassy_sync::signal::Signal;
use embassy_time::Delay;
use embassy_time::{Duration, Instant, Timer};
use embedded_graphics_core::pixelcolor::Rgb565;
use embedded_graphics_core::prelude::*;
use embedded_graphics_core::primitives::Rectangle;
use embedded_graphics_profiler_display::ProfilerDisplay;
use embedded_hal::digital::OutputPin;
use embedded_hal_bus::spi::ExclusiveDevice;
use esp_hal::{self, prelude::*};
use esp_hal::{
clock::ClockControl,
gpio::{
GpioPin, Input, Io, Level, Output, Pull, NO_PIN,
},
peripherals::{Peripherals, SPI3},
prelude::*,
rtc_cntl::Rtc,
spi::{master::Spi, FullDuplexMode, SpiMode},
system::SystemControl,
timer::timg::TimerGroup,
};
use esp_println::println;
use mipidsi::{
options::{ColorOrder, Orientation, Rotation},
Builder,
};
use slint::platform::software_renderer::MinimalSoftwareWindow;
use slint::platform::{
Platform, PointerEventButton, WindowEvent,
};
use slint::{format, LogicalPosition};
use static_cell::StaticCell;
use xpt2046::Xpt2046;
use mipidsi::models::ILI9341Rgb565;
// slint::slint!{ export MyUI := Window {} }
/*
slint::include_modules!();
# */
slint::include_modules!();
fn init_heap() {
const HEAP_SIZE: usize = 64 * 1024;
static mut HEAP: MaybeUninit<[u8; HEAP_SIZE]> =
MaybeUninit::uninit();
unsafe {
esp_alloc::HEAP.add_region(
esp_alloc::HeapRegion::new(
HEAP.as_mut_ptr() as *mut u8,
HEAP_SIZE,
esp_alloc::MemoryCapability::Internal
.into(),
),
);
}
}
#[embassy_executor::task]
async fn touch_task(
touch_irq: GpioPin<36>,
spi: ExclusiveDevice<
Spi<'static, SPI3, FullDuplexMode>,
Output<'static, GpioPin<33>>,
&'static mut Delay,
>,
touch_signal: &'static Signal<
CriticalSectionRawMutex,
Option<Point>,
>,
) -> ! {
let mut touch_driver = Xpt2046::new(
spi,
Input::new(touch_irq, Pull::Up),
xpt2046::Orientation::LandscapeFlipped,
);
touch_driver.set_num_samples(1);
touch_driver.init(&mut embassy_time::Delay).unwrap();
esp_println::println!("touch task");
loop {
touch_driver
.run()
.expect("Running Touch driver failed");
if touch_driver.is_touched() {
let point = touch_driver.get_touch_point();
touch_signal.signal(Some(Point::new(
point.x + 25,
240 - point.y,
)));
} else {
touch_signal.signal(None);
}
Timer::after(Duration::from_millis(1)).await; // 100 a second
// Your touch handling logic here
}
}
fn point_to_logical_pos(point: Point) -> LogicalPosition {
LogicalPosition::new(point.x as f32, point.y as f32)
}
struct CYDPlatform {
window: Rc<slint::platform::software_renderer::MinimalSoftwareWindow>,
}
impl Platform for CYDPlatform {
fn create_window_adapter(
&self,
) -> Result<
Rc<dyn slint::platform::WindowAdapter>,
slint::PlatformError,
> {
Ok(self.window.clone())
}
fn duration_since_start(&self) -> core::time::Duration {
embassy_time::Instant::from_millis(0)
.elapsed()
.into()
}
//noinspection DuplicatedCode
fn run_event_loop(
&self,
) -> Result<(), slint::PlatformError> {
todo!();
}
}
struct AppData {
timer_start: Instant,
timer_set_duration: Duration,
timer_remaining_duration: Duration,
timer_running: bool,
timer_paused: bool,
}
impl AppData {
fn new() -> Self {
Self {
timer_start: Instant::now(),
timer_set_duration: Duration::from_secs(10),
timer_remaining_duration: Duration::from_secs(
10,
),
timer_running: false,
timer_paused: false,
}
}
fn set_timer_duration(&mut self, duration: Duration) {
self.timer_set_duration = duration;
}
fn add_secs(&mut self, secs: u64) {
self.set_timer_duration(
self.timer_set_duration
.checked_add(Duration::from_secs(secs))
.unwrap_or(Duration::from_secs(5999))
.min(Duration::from_secs(5999)),
);
}
fn sub_secs(&mut self, secs: u64) {
self.set_timer_duration(
self.timer_set_duration
.checked_sub(Duration::from_secs(secs))
.unwrap_or(Duration::from_secs(10))
.max(Duration::from_secs(10)),
);
}
fn start_timer(&mut self) {
if !self.timer_paused {
self.timer_remaining_duration =
self.timer_set_duration;
}
self.timer_start = Instant::now();
self.timer_running = true;
self.timer_paused = false;
}
fn pause_timer(&mut self) {
self.timer_paused = true;
self.timer_remaining_duration = self
.timer_remaining_duration
.checked_sub(self.timer_start.elapsed())
.unwrap_or(Duration::from_secs(0));
}
fn reset_timer(&mut self) {
self.timer_start = Instant::now();
self.timer_paused = false;
self.timer_running = false;
self.timer_remaining_duration =
self.timer_set_duration;
}
fn remaining(&self) -> Duration {
if self.timer_stopped() {
self.timer_set_duration
} else if self.timer_paused() {
self.timer_remaining_duration
} else {
self.timer_remaining_duration
.checked_sub(self.timer_start.elapsed())
.unwrap_or(Duration::from_secs(0))
}
}
fn timer_stopped(&self) -> bool {
!self.timer_running
}
fn timer_paused(&self) -> bool {
self.timer_running && self.timer_paused
}
fn timer_running(&self) -> bool {
self.timer_running && !self.timer_paused
}
fn timer_finished(&self) -> bool {
self.timer_running
&& self.remaining() == Duration::from_secs(0)
}
}
#[main]
async fn main(spawner: Spawner) {
init_heap();
let peripherals = Peripherals::take();
let system = SystemControl::new(peripherals.SYSTEM);
let mut clocks =
ClockControl::boot_defaults(system.clock_control)
.freeze();
let mut rtc = Rtc::new(peripherals.LPWR);
rtc.rwdt.disable();
let mut timer_group0 =
TimerGroup::new(peripherals.TIMG0, &clocks);
timer_group0.wdt.disable();
let mut timer_group1 =
TimerGroup::new(peripherals.TIMG1, &clocks);
timer_group1.wdt.disable();
esp_hal_embassy::init(&clocks, timer_group0.timer0);
let io = Io::new(peripherals.GPIO, peripherals.IO_MUX);
let touch_irq = io.pins.gpio36;
let touch_mosi = io.pins.gpio32;
let touch_miso = io.pins.gpio39;
let touch_clk = io.pins.gpio25;
let touch_cs = io.pins.gpio33;
// 2MHz is the MAX! DO NOT DECREASE! This is really important.
let mut touch_spi = Spi::new(
peripherals.SPI3,
2.MHz(),
SpiMode::Mode0,
&mut clocks,
)
.with_pins(
Some(touch_clk),
Some(touch_mosi),
Some(touch_miso),
NO_PIN,
);
static TOUCH_DELAY_STATICCELL: StaticCell<Delay> =
StaticCell::new();
let mut delay = TOUCH_DELAY_STATICCELL.init(Delay);
let touch_spi = ExclusiveDevice::new(
touch_spi,
Output::new(touch_cs, Level::Low),
delay,
)
.unwrap();
let touch_signal = Signal::new();
static TOUCH_SIGNAL: StaticCell<
Signal<CriticalSectionRawMutex, Option<Point>>,
> = StaticCell::new();
let touch_signal = &*TOUCH_SIGNAL.init(touch_signal);
// let sw_int = system.software_interrupt_control.software_interrupt2;
// static EXECUTOR: StaticCell<InterruptExecutor<2>> = StaticCell::new();
// let executor = InterruptExecutor::<2>::new(sw_int);
// let executor = EXECUTOR.init(executor);
// static EXECUTOR: StaticCell<InterruptExecutor<2>> = StaticCell::new();
// let executor =
// InterruptExecutor::<2>::new(system.software_interrupt_control.software_interrupt2);
// let mut executor = EXECUTOR.init(executor);
// executor
// .start(Priority::Priority2)
spawner
.spawn(touch_task(
touch_irq,
touch_spi,
touch_signal,
))
.unwrap();
// Display setup
let sclk = io.pins.gpio14;
let miso = io.pins.gpio12;
let mosi = io.pins.gpio13;
let cs = io.pins.gpio15;
let dc = io.pins.gpio2;
let mut backlight =
Output::new(io.pins.gpio21, Level::Low);
let mut spi = Spi::new(
peripherals.SPI2,
10u32.MHz(),
SpiMode::Mode0,
&clocks,
)
.with_pins(
Some(sclk),
Some(mosi),
Some(miso),
esp_hal::gpio::NO_PIN,
);
// static DISP_SPI_BUS: StaticCell<NoopMutex<RefCell<Spi<SPI2, FullDuplexMode>>>> = StaticCell::new();
// let spi_bus = NoopMutex::new(RefCell::new(spi));
// let spi_bus = DISP_SPI_BUS.init(spi_bus);
static SPI_DELAY_STATICCELL: StaticCell<Delay> =
StaticCell::new();
let mut delay = SPI_DELAY_STATICCELL.init(Delay);
let spi = ExclusiveDevice::new(
spi,
Output::new(cs, Level::Low),
delay,
)
.unwrap();
let di =
SPIInterface::new(spi, Output::new(dc, Level::Low));
let display = Builder::new(ILI9341Rgb565, di)
.orientation(Orientation {
rotation: Rotation::Deg90,
mirrored: true,
})
.color_order(ColorOrder::Bgr)
// .invert_colors(ColorInversion::Inverted)
.init(&mut embassy_time::Delay)
.unwrap();
let mut display = ProfilerDisplay::new(display);
backlight.set_high();
let size = display.bounding_box().size;
// let size = slint::PhysicalSize::new(size.width as u32, size.height as u32);
let window =
MinimalSoftwareWindow::new(Default::default());
slint::platform::set_platform(Box::new(CYDPlatform {
window: window.clone(),
}))
.unwrap();
let ui = create_slint_app();
window.set_size(slint::PhysicalSize::new(320, 240));
let mut buffer_provider = DrawBuffer {
display,
buffer: &mut [slint::platform::software_renderer::Rgb565Pixel(0); 320],
};
let mut last_touch = None;
let mut appdata = Rc::new(RefCell::new(AppData::new()));
let mut cl_appdata = appdata.clone();
ui.on_add_10s(move || {
cl_appdata.borrow_mut().add_secs(10)
});
let mut cl_appdata = appdata.clone();
ui.on_sub_10s(move || {
cl_appdata.borrow_mut().sub_secs(10)
});
let mut cl_appdata = appdata.clone();
ui.on_start_timer(move || {
cl_appdata.borrow_mut().start_timer()
});
let mut cl_appdata = appdata.clone();
ui.on_stop_timer(move || {
cl_appdata.borrow_mut().pause_timer()
});
let mut cl_appdata = appdata.clone();
ui.on_reset_timer(move || {
cl_appdata.borrow_mut().reset_timer()
});
loop {
let start_time = Instant::now();
if let Some(touch) = touch_signal.try_take() {
// println!("touch: {:?}, last_touch: {:?}", touch, last_touch);
let button = PointerEventButton::Left;
let interact = match (touch, last_touch) {
(Some(point), Some(_)) => {
Some(WindowEvent::PointerMoved {
position: point_to_logical_pos(
point,
),
})
}
(Some(point), None) => {
Some(WindowEvent::PointerPressed {
position: point_to_logical_pos(
point,
),
button,
})
}
(None, Some(point)) => {
Some(WindowEvent::PointerReleased {
position: point_to_logical_pos(
point,
),
button,
})
}
(None, None) => None,
};
if let Some(event) = interact {
// println!("event: {:?}", event);
window.dispatch_event(event);
}
last_touch = touch;
}
{
let appdata = appdata.borrow();
let remaining = appdata.remaining();
ui.set_show_reset_timer(
appdata.timer_finished(),
);
ui.set_show_start_timer(
!appdata.timer_running()
|| appdata.timer_paused()
&& !appdata.timer_finished(),
);
ui.set_show_stop_timer(
appdata.timer_running()
&& !appdata.timer_finished(),
);
ui.set_timer_text(format!(
"{:02}:{:02}:{:03}",
remaining.as_secs() / 60,
remaining.as_secs() % 60,
remaining.as_millis() % 1000
));
}
slint::platform::update_timers_and_animations();
// let window = window.clone();
let start_draw_time = Instant::now();
window.draw_if_needed(|renderer| {
// println!("dirty reg: {:?}", renderer.render_by_line(&mut buffer_provider));
renderer.render_by_line(&mut buffer_provider);
});
if window.has_active_animations() {
continue;
}
let display = &mut buffer_provider.display;
let end_time = embassy_time::Instant::now();
let draw_time = display.get_time();
let prep_time = start_draw_time - start_time;
let proc_time = end_time - start_draw_time;
let proc_time =
proc_time - min(draw_time, proc_time);
rtc.rwdt.feed();
if draw_time.as_micros() > 0 {
println!(
"draw time: {}.{:03}ms | prep time: {}.{:03}ms | proc time: {}.{:03}ms | total time: {}.{:03}ms",
draw_time.as_millis(),
draw_time.as_micros() % 100,
prep_time.as_millis(),
prep_time.as_micros() % 100,
proc_time.as_millis(),
proc_time.as_micros() % 100,
(draw_time + prep_time + proc_time).as_millis(),
(draw_time + prep_time + proc_time).as_micros() % 100, );
}
display.reset_time();
Timer::after(Duration::from_millis(1)).await; // 60 a second
}
}
fn create_slint_app() -> MicrowaveUI {
let ui = MicrowaveUI::new().unwrap();
/*
let ui_handle = ui.as_weak();
ui.on_request_increase_value(move || {
let ui = ui_handle.unwrap();
ui.set_counter(ui.get_counter() + 1);
});
*/
ui
}
struct DrawBuffer<'a, DT> {
display: DT,
buffer: &'a mut [slint::platform::software_renderer::Rgb565Pixel],
}
impl<
// DI: display_interface_spi::WriteOnlyDataCommand,
E: core::fmt::Debug,
DT: DrawTarget<Color = Rgb565, Error = E>,
// RST: OutputPin<Error = core::convert::Infallible>,
> slint::platform::software_renderer::LineBufferProvider
for &mut DrawBuffer<'_, DT>
{
type TargetPixel =
slint::platform::software_renderer::Rgb565Pixel;
fn process_line(
&mut self,
line: usize,
range: core::ops::Range<usize>,
render_fn: impl FnOnce(&mut [slint::platform::software_renderer::Rgb565Pixel]),
) {
let buffer = &mut self.buffer[range.clone()];
render_fn(buffer);
// We send empty data just to get the device in the right window
self.display
.fill_contiguous(
&Rectangle::new(
Point::new(range.start as i32, line as i32),
Size::new((range.end - range.start) as u32, 1),
),
// range.start as u16,
// line as _,
// range.end as u16,
// line as u16,
buffer
.iter()
.map(|x| embedded_graphics_core::pixelcolor::raw::RawU16::new(x.0).into()),
)
.unwrap();
}
}

View File

@ -0,0 +1,583 @@
#![no_std]
#![no_main]
extern crate alloc;
use alloc::boxed::Box;
use alloc::rc::Rc;
use core::mem::MaybeUninit;
use core::{cell::RefCell, cmp::min};
use display_interface_spi::SPIInterface;
use embassy_executor::Spawner;
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
use embassy_sync::signal::Signal;
use embassy_time::Delay;
use embassy_time::{Duration, Instant, Timer};
use embedded_graphics_core::pixelcolor::Rgb565;
use embedded_graphics_core::prelude::*;
use embedded_graphics_core::primitives::Rectangle;
use embedded_graphics_profiler_display::ProfilerDisplay;
use embedded_hal::digital::OutputPin;
use embedded_hal_bus::spi::ExclusiveDevice;
use esp_hal::{self, prelude::*};
use esp_hal::{
clock::ClockControl,
gpio::{
GpioPin, Input, Io, Level, Output, Pull, NO_PIN,
},
peripherals::{Peripherals, SPI3},
prelude::*,
rtc_cntl::Rtc,
spi::{master::Spi, FullDuplexMode, SpiMode},
system::SystemControl,
timer::timg::TimerGroup,
};
use esp_println::println;
use mipidsi::{
options::{ColorOrder, Orientation, Rotation},
Builder,
};
use slint::platform::software_renderer::MinimalSoftwareWindow;
use slint::platform::{
Platform, PointerEventButton, WindowEvent,
};
use slint::{format, LogicalPosition};
use static_cell::StaticCell;
use xpt2046::Xpt2046;
use mipidsi::models::ILI9341Rgb565;
// slint::slint!{ export MyUI := Window {} }
/*
slint::include_modules!();
# */
slint::include_modules!();
fn init_heap() {
const HEAP_SIZE: usize = 32 * 1024;
static mut HEAP: MaybeUninit<[u8; HEAP_SIZE]> =
MaybeUninit::uninit();
unsafe {
esp_alloc::HEAP.add_region(
esp_alloc::HeapRegion::new(
HEAP.as_mut_ptr() as *mut u8,
HEAP_SIZE,
esp_alloc::MemoryCapability::Internal
.into(),
),
);
}
}
#[embassy_executor::task]
async fn touch_task(
touch_irq: GpioPin<36>,
spi: ExclusiveDevice<
Spi<'static, SPI3, FullDuplexMode>,
Output<'static, GpioPin<33>>,
&'static mut Delay,
>,
touch_signal: &'static Signal<
CriticalSectionRawMutex,
Option<Point>,
>,
) -> ! {
let mut touch_driver = Xpt2046::new(
spi,
Input::new(touch_irq, Pull::Up),
xpt2046::Orientation::LandscapeFlipped,
);
touch_driver.set_num_samples(1);
touch_driver.init(&mut embassy_time::Delay).unwrap();
esp_println::println!("touch task");
loop {
touch_driver
.run()
.expect("Running Touch driver failed");
if touch_driver.is_touched() {
let point = touch_driver.get_touch_point();
touch_signal.signal(Some(Point::new(
point.x + 25,
240 - point.y,
)));
} else {
touch_signal.signal(None);
}
Timer::after(Duration::from_millis(1)).await; // 100 a second
// Your touch handling logic here
}
}
fn point_to_logical_pos(point: Point) -> LogicalPosition {
LogicalPosition::new(point.x as f32, point.y as f32)
}
struct CYDPlatform {
window: Rc<slint::platform::software_renderer::MinimalSoftwareWindow>,
}
impl Platform for CYDPlatform {
fn create_window_adapter(
&self,
) -> Result<
Rc<dyn slint::platform::WindowAdapter>,
slint::PlatformError,
> {
Ok(self.window.clone())
}
fn duration_since_start(&self) -> core::time::Duration {
embassy_time::Instant::from_millis(0)
.elapsed()
.into()
}
//noinspection DuplicatedCode
fn run_event_loop(
&self,
) -> Result<(), slint::PlatformError> {
todo!();
}
}
struct AppData {
timer_start: Instant,
timer_set_duration: Duration,
timer_remaining_duration: Duration,
timer_running: bool,
timer_paused: bool,
}
impl AppData {
fn new() -> Self {
Self {
timer_start: Instant::now(),
timer_set_duration: Duration::from_secs(10),
timer_remaining_duration: Duration::from_secs(
10,
),
timer_running: false,
timer_paused: false,
}
}
fn set_timer_duration(&mut self, duration: Duration) {
self.timer_set_duration = duration;
}
fn add_secs(&mut self, secs: u64) {
self.set_timer_duration(
self.timer_set_duration
.checked_add(Duration::from_secs(secs))
.unwrap_or(Duration::from_secs(5999))
.min(Duration::from_secs(5999)),
);
}
fn sub_secs(&mut self, secs: u64) {
self.set_timer_duration(
self.timer_set_duration
.checked_sub(Duration::from_secs(secs))
.unwrap_or(Duration::from_secs(10))
.max(Duration::from_secs(10)),
);
}
fn start_timer(&mut self) {
if !self.timer_paused {
self.timer_remaining_duration =
self.timer_set_duration;
}
self.timer_start = Instant::now();
self.timer_running = true;
self.timer_paused = false;
}
fn pause_timer(&mut self) {
self.timer_paused = true;
self.timer_remaining_duration = self
.timer_remaining_duration
.checked_sub(self.timer_start.elapsed())
.unwrap_or(Duration::from_secs(0));
}
fn reset_timer(&mut self) {
self.timer_start = Instant::now();
self.timer_paused = false;
self.timer_running = false;
self.timer_remaining_duration =
self.timer_set_duration;
}
fn remaining(&self) -> Duration {
if self.timer_stopped() {
self.timer_set_duration
} else if self.timer_paused() {
self.timer_remaining_duration
} else {
self.timer_remaining_duration
.checked_sub(self.timer_start.elapsed())
.unwrap_or(Duration::from_secs(0))
}
}
fn timer_stopped(&self) -> bool {
!self.timer_running
}
fn timer_paused(&self) -> bool {
self.timer_running && self.timer_paused
}
fn timer_running(&self) -> bool {
self.timer_running && !self.timer_paused
}
fn timer_finished(&self) -> bool {
self.timer_running
&& self.remaining() == Duration::from_secs(0)
}
}
#[main]
async fn main(spawner: Spawner) {
init_heap();
let peripherals = Peripherals::take();
let system = SystemControl::new(peripherals.SYSTEM);
let mut clocks =
ClockControl::boot_defaults(system.clock_control)
.freeze();
let mut rtc = Rtc::new(peripherals.LPWR);
rtc.rwdt.disable();
let mut timer_group0 =
TimerGroup::new(peripherals.TIMG0, &clocks);
timer_group0.wdt.disable();
let mut timer_group1 =
TimerGroup::new(peripherals.TIMG1, &clocks);
timer_group1.wdt.disable();
esp_hal_embassy::init(&clocks, timer_group0.timer0);
let io = Io::new(peripherals.GPIO, peripherals.IO_MUX);
let touch_irq = io.pins.gpio36;
let touch_mosi = io.pins.gpio32;
let touch_miso = io.pins.gpio39;
let touch_clk = io.pins.gpio25;
let touch_cs = io.pins.gpio33;
// 2MHz is the MAX! DO NOT DECREASE! This is really important.
let mut touch_spi = Spi::new(
peripherals.SPI3,
2.MHz(),
SpiMode::Mode0,
&mut clocks,
)
.with_pins(
Some(touch_clk),
Some(touch_mosi),
Some(touch_miso),
NO_PIN,
);
static TOUCH_DELAY_STATICCELL: StaticCell<Delay> =
StaticCell::new();
let mut delay = TOUCH_DELAY_STATICCELL.init(Delay);
let touch_spi = ExclusiveDevice::new(
touch_spi,
Output::new(touch_cs, Level::Low),
delay,
)
.unwrap();
let touch_signal = Signal::new();
static TOUCH_SIGNAL: StaticCell<
Signal<CriticalSectionRawMutex, Option<Point>>,
> = StaticCell::new();
let touch_signal = &*TOUCH_SIGNAL.init(touch_signal);
// let sw_int = system.software_interrupt_control.software_interrupt2;
// static EXECUTOR: StaticCell<InterruptExecutor<2>> = StaticCell::new();
// let executor = InterruptExecutor::<2>::new(sw_int);
// let executor = EXECUTOR.init(executor);
// static EXECUTOR: StaticCell<InterruptExecutor<2>> = StaticCell::new();
// let executor =
// InterruptExecutor::<2>::new(system.software_interrupt_control.software_interrupt2);
// let mut executor = EXECUTOR.init(executor);
// executor
// .start(Priority::Priority2)
spawner
.spawn(touch_task(
touch_irq,
touch_spi,
touch_signal,
))
.unwrap();
// Display setup
let sclk = io.pins.gpio14;
let miso = io.pins.gpio12;
let mosi = io.pins.gpio13;
let cs = io.pins.gpio15;
let dc = io.pins.gpio2;
let mut backlight =
Output::new(io.pins.gpio21, Level::Low);
let mut spi = Spi::new(
peripherals.SPI2,
10u32.MHz(),
SpiMode::Mode0,
&clocks,
)
.with_pins(
Some(sclk),
Some(mosi),
Some(miso),
esp_hal::gpio::NO_PIN,
);
// static DISP_SPI_BUS: StaticCell<NoopMutex<RefCell<Spi<SPI2, FullDuplexMode>>>> = StaticCell::new();
// let spi_bus = NoopMutex::new(RefCell::new(spi));
// let spi_bus = DISP_SPI_BUS.init(spi_bus);
static SPI_DELAY_STATICCELL: StaticCell<Delay> =
StaticCell::new();
let mut delay = SPI_DELAY_STATICCELL.init(Delay);
let spi = ExclusiveDevice::new(
spi,
Output::new(cs, Level::Low),
delay,
)
.unwrap();
let di =
SPIInterface::new(spi, Output::new(dc, Level::Low));
let display = Builder::new(ILI9341Rgb565, di)
.orientation(Orientation {
rotation: Rotation::Deg90,
mirrored: true,
})
.color_order(ColorOrder::Bgr)
// .invert_colors(ColorInversion::Inverted)
.init(&mut embassy_time::Delay)
.unwrap();
let mut display = ProfilerDisplay::new(display);
backlight.set_high();
let size = display.bounding_box().size;
// let size = slint::PhysicalSize::new(size.width as u32, size.height as u32);
let window =
MinimalSoftwareWindow::new(Default::default());
slint::platform::set_platform(Box::new(CYDPlatform {
window: window.clone(),
}))
.unwrap();
let ui = create_slint_app();
window.set_size(slint::PhysicalSize::new(320, 240));
let mut buffer_provider = DrawBuffer {
display,
buffer: &mut [slint::platform::software_renderer::Rgb565Pixel(0); 320],
};
let mut last_touch = None;
let mut appdata = Rc::new(RefCell::new(AppData::new()));
let mut cl_appdata = appdata.clone();
ui.on_add_10s(move || {
cl_appdata.borrow_mut().add_secs(10)
});
let mut cl_appdata = appdata.clone();
ui.on_sub_10s(move || {
cl_appdata.borrow_mut().sub_secs(10)
});
let mut cl_appdata = appdata.clone();
ui.on_start_timer(move || {
cl_appdata.borrow_mut().start_timer()
});
let mut cl_appdata = appdata.clone();
ui.on_stop_timer(move || {
cl_appdata.borrow_mut().pause_timer()
});
let mut cl_appdata = appdata.clone();
ui.on_reset_timer(move || {
cl_appdata.borrow_mut().reset_timer()
});
loop {
let start_time = Instant::now();
if let Some(touch) = touch_signal.try_take() {
// println!("touch: {:?}, last_touch: {:?}", touch, last_touch);
let button = PointerEventButton::Left;
let interact = match (touch, last_touch) {
(Some(point), Some(_)) => {
Some(WindowEvent::PointerMoved {
position: point_to_logical_pos(
point,
),
})
}
(Some(point), None) => {
Some(WindowEvent::PointerPressed {
position: point_to_logical_pos(
point,
),
button,
})
}
(None, Some(point)) => {
Some(WindowEvent::PointerReleased {
position: point_to_logical_pos(
point,
),
button,
})
}
(None, None) => None,
};
if let Some(event) = interact {
// println!("event: {:?}", event);
window.dispatch_event(event);
}
last_touch = touch;
}
{
let appdata = appdata.borrow();
let remaining = appdata.remaining();
ui.set_show_reset_timer(
appdata.timer_finished(),
);
ui.set_show_start_timer(
!appdata.timer_running()
|| appdata.timer_paused()
&& !appdata.timer_finished(),
);
ui.set_show_stop_timer(
appdata.timer_running()
&& !appdata.timer_finished(),
);
ui.set_timer_text(format!(
"{:02}:{:02}:{:03}",
remaining.as_secs() / 60,
remaining.as_secs() % 60,
remaining.as_millis() % 1000
));
}
slint::platform::update_timers_and_animations();
// let window = window.clone();
let start_draw_time = Instant::now();
window.draw_if_needed(|renderer| {
// println!("dirty reg: {:?}", renderer.render_by_line(&mut buffer_provider));
renderer.render_by_line(&mut buffer_provider);
});
if window.has_active_animations() {
continue;
}
let display = &mut buffer_provider.display;
let end_time = embassy_time::Instant::now();
let draw_time = display.get_time();
let prep_time = start_draw_time - start_time;
let proc_time = end_time - start_draw_time;
let proc_time =
proc_time - min(draw_time, proc_time);
rtc.rwdt.feed();
if draw_time.as_micros() > 0 {
println!(
"draw time: {}.{:03}ms | prep time: {}.{:03}ms | proc time: {}.{:03}ms | total time: {}.{:03}ms",
draw_time.as_millis(),
draw_time.as_micros() % 100,
prep_time.as_millis(),
prep_time.as_micros() % 100,
proc_time.as_millis(),
proc_time.as_micros() % 100,
(draw_time + prep_time + proc_time).as_millis(),
(draw_time + prep_time + proc_time).as_micros() % 100, );
}
display.reset_time();
Timer::after(Duration::from_millis(1)).await; // 60 a second
}
}
fn create_slint_app() -> TimerApp {
let ui = TimerApp::new().unwrap();
/*
let ui_handle = ui.as_weak();
ui.on_request_increase_value(move || {
let ui = ui_handle.unwrap();
ui.set_counter(ui.get_counter() + 1);
});
*/
ui
}
struct DrawBuffer<'a, DT> {
display: DT,
buffer: &'a mut [slint::platform::software_renderer::Rgb565Pixel],
}
impl<
// DI: display_interface_spi::WriteOnlyDataCommand,
E: core::fmt::Debug,
DT: DrawTarget<Color = Rgb565, Error = E>,
// RST: OutputPin<Error = core::convert::Infallible>,
> slint::platform::software_renderer::LineBufferProvider
for &mut DrawBuffer<'_, DT>
{
type TargetPixel =
slint::platform::software_renderer::Rgb565Pixel;
fn process_line(
&mut self,
line: usize,
range: core::ops::Range<usize>,
render_fn: impl FnOnce(&mut [slint::platform::software_renderer::Rgb565Pixel]),
) {
let buffer = &mut self.buffer[range.clone()];
render_fn(buffer);
// We send empty data just to get the device in the right window
self.display
.fill_contiguous(
&Rectangle::new(
Point::new(range.start as i32, line as i32),
Size::new((range.end - range.start) as u32, 1),
),
// range.start as u16,
// line as _,
// range.end as u16,
// line as u16,
buffer
.iter()
.map(|x| embedded_graphics_core::pixelcolor::raw::RawU16::new(x.0).into()),
)
.unwrap();
}
}

View File

@ -5,20 +5,6 @@ export component AppWindow inherits Window {
height: 240px; height: 240px;
in-out property <int> counter: 42; in-out property <int> counter: 42;
callback request-increase-value(); callback request-increase-value();
VerticalBox {
alignment: start;
Text {
text: "Hello World!";
font-size: 24px;
horizontal-alignment: center;
}
}
Text {
text: "Counter App (Slint)";
}
VerticalBox { VerticalBox {
Text { Text {
text: "Counter: \{root.counter}"; text: "Counter: \{root.counter}";
@ -31,6 +17,6 @@ export component AppWindow inherits Window {
} }
} }
// AboutSlint { } AboutSlint { }
} }
} }

View File

@ -0,0 +1,164 @@
import { Button, VerticalBox , AboutSlint, Slider, Switch } from "std-widgets.slint";
export struct Light {
name: string,
brightness: int,
on: bool,
}
export component LightsApp inherits Window {
width: 320px;
height: 240px;
in property <bool> show-start-timer: true;
in property <bool> show-stop-timer: false;
in property <bool> show-reset-timer: false;
property <[Light]> lights: [
{
name: "Bathroom",
brightness: 100,
on: true,
},
{
name: "Bedroom",
brightness: 200,
on: true,
},
{
name: "Living Room",
brightness: 50,
on: false,
},
{
name: "Front Door",
brightness: 250,
on: false,
},
{
name: "Porch",
brightness: 1,
on: true,
},
// {name: "Bathroom", brightness: 4, on: false, },
];
property <int> selected-light: 0;
property <bool> show-light-page: false;
in property <string> timer-text: "00:00:000";
property <image> start-timer-icon: @image-url("../img/play.png");
property <image> stop-timer-icon: @image-url("../img/pause.png");
property <image> reset-timer-icon: @image-url("../img/xmark-square.png");
property <image> light-icon: @image-url("../img/play.png");
VerticalBox {
alignment: start;
Text {
text: show-light-page ? lights[selected-light].name : "Light Control App (Slint)";
font-size: 14px;
horizontal-alignment: center;
}
}
if !show-light-page: VerticalLayout {
alignment: center;
spacing: 5px;
padding-top: 25px;
// height: 200px;
HorizontalLayout {
alignment: LayoutAlignment.center;
spacing: 6px;
for i in 3: HorizontalLayout {
alignment: center;
height: 100px;
if i < lights.length:
Button {
width: 96px;
height: 96px;
// icon: light-icon;
text: lights[i].name;
clicked => {
selected-light = i;
show-light-page = true;
}
}
}
}
HorizontalLayout {
alignment: LayoutAlignment.center;
spacing: 6px;
for i in 3: HorizontalLayout {
alignment: center;
height: 100px;
if i + 3 < lights.length:
Button {
width: 96px;
height: 96px;
// icon: light-icon;
text: lights[i].name;
clicked => {
selected-light = i + 3;
show-light-page = true;
}
}
}
}
}
if show-light-page: Rectangle {
VerticalLayout {
alignment: LayoutAlignment.center;
width: 320px;
spacing: 4px;
HorizontalLayout {
alignment: LayoutAlignment.center;
Slider {
width: 250px;
maximum: 255;
value: lights[selected-light].brightness;
changed(f) => {
lights[selected-light].brightness = f
}
}
}
Text {
text: "Brightness";
horizontal-alignment: center;
}
Rectangle {
height: 16px;
}
HorizontalLayout {
alignment: LayoutAlignment.center;
Switch {
checked: lights[selected-light].on;
toggled => {
lights[selected-light].on = !lights[selected-light].on
}
}
}
Text {
text: "On / Off";
horizontal-alignment: center;
}
}
Button {
x: 10px;
y: 10px;
text: "<";
clicked => {
show-light-page = false;
}
}
}
}

View File

@ -0,0 +1,146 @@
import { Button, VerticalBox, HorizontalBox, AboutSlint } from "std-widgets.slint";
export component MicrowaveUI inherits Window {
width: 320px;
height: 240px;
property <[string]> wattages: ["180W", "220W", "360W", "480W", "620W", "800W"];
property <int> selected-wattage: 0;
callback add-10s();
callback sub-10s();
callback start-timer();
callback stop-timer();
callback reset-timer();
in property <bool> show-start-timer: true;
in property <bool> show-stop-timer: false;
in property <bool> show-reset-timer: false;
in property <string> timer-text: "00:00:000";
property <image> start-timer-icon: @image-url("../img/play.png");
property <image> stop-timer-icon: @image-url("../img/pause.png");
property <image> reset-timer-icon: @image-url("../img/xmark-square.png");
function multifunction-button-pressed() {
if (show-start-timer) {
start-timer();
}
if (show-stop-timer) {
stop-timer();
}
if (show-reset-timer) {
reset-timer();
}
}
function multifunction-button-icon() -> image {
if (show-start-timer) {
return start-timer-icon;
}
if (show-stop-timer) {
return stop-timer-icon;
}
if (show-reset-timer) {
return reset-timer-icon;
}
return start-timer-icon;
}
VerticalBox {
alignment: start;
Text {
text: " Microwave UI (Slint)";
font-size: 14px;
horizontal-alignment: center;
}
}
HorizontalBox {
VerticalBox {
padding-top: 15px;
padding-bottom: 20px;
Button {
enabled: (selected-wattage < wattages.length - 1) && show-start-timer;
text: "+";
clicked => {
selected-wattage += 1
}
}
Text {
font-size: 24px;
text: wattages[selected-wattage];
horizontal-alignment: center;
vertical-alignment: center;
}
Button {
enabled: !(selected-wattage <= 0) && show-start-timer;
text: "-";
clicked => {
selected-wattage -= 1
}
}
}
VerticalBox {
alignment: center;
Text {
text: timer-text;
font-size: 24px;
horizontal-alignment: center;
}
Rectangle {
height: 4px;
}
HorizontalLayout {
alignment: center;
spacing: 8px;
Button {
text: "+10s";
enabled: show-start-timer;
clicked => {
add-10s();
}
}
Text {
text: "Set Timer";
vertical-alignment: center;
}
Button {
text: "-10s";
enabled: show-start-timer;
clicked => {
sub-10s();
}
}
}
HorizontalLayout {
alignment: center;
spacing: 8px;
Button {
icon: @image-url("../img/restart.png");
clicked => {
reset-timer();
}
}
Button {
icon: multifunction-button-icon();
// visible: root.show_start_timer;
clicked => {
multifunction-button-pressed();
}
}
}
}
}
}

View File

@ -0,0 +1,114 @@
import { Button, VerticalBox , AboutSlint } from "std-widgets.slint";
export component TimerApp inherits Window {
width: 320px;
height: 240px;
callback add-10s();
callback sub-10s();
callback start-timer();
callback stop-timer();
callback reset-timer();
in property <bool> show-start-timer: true;
in property <bool> show-stop-timer: false;
in property <bool> show-reset-timer: false;
in property <string> timer-text: "00:00:000";
property <image> start-timer-icon: @image-url("../img/play.png");
property <image> stop-timer-icon: @image-url("../img/pause.png");
property <image> reset-timer-icon: @image-url("../img/xmark-square.png");
function multifunction-button-pressed() {
if (show-start-timer) {
start-timer();
}
if (show-stop-timer) {
stop-timer();
}
if (show-reset-timer) {
reset-timer();
}
}
function multifunction-button-icon() -> image {
if (show-start-timer) {
return start-timer-icon;
}
if (show-stop-timer) {
return stop-timer-icon;
}
if (show-reset-timer) {
return reset-timer-icon;
}
return start-timer-icon;
}
VerticalBox {
alignment: start;
Text {
text: "Timer App (Slint)";
font-size: 14px;
horizontal-alignment: center;
}
}
VerticalBox {
alignment: center;
Text {
text: timer-text;
font-size: 24px;
horizontal-alignment: center;
}
Rectangle {
height: 4px;
}
HorizontalLayout {
alignment: center;
spacing: 8px;
Button {
text: "+10s";
enabled: show-start-timer;
clicked => {
add-10s();
}
}
Text {
text: "Set Timer";
vertical-alignment: center;
}
Button {
text: "-10s";
enabled: show-start-timer;
clicked => {
sub-10s();
}
}
}
HorizontalLayout {
alignment: center;
spacing: 8px;
Button {
icon: @image-url("../img/restart.png");
clicked => {
reset-timer();
}
}
Button {
icon: multifunction-button-icon();
// visible: root.show_start_timer;
clicked => {
multifunction-button-pressed();
}
}
}
}
}

0
slint/ui.slint Normal file
View File