sampler start

This commit is contained in:
2025-11-30 16:16:16 +01:00
parent db7b6c5937
commit 3cb8a04a77
30 changed files with 1256 additions and 149 deletions

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "aht20-driver"]
path = aht20-driver
url = git@github.com:chalbin73/aht20-driver.git

57
Cargo.lock generated
View File

@ -5,8 +5,6 @@ version = 4
[[package]]
name = "aht20-driver"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c920c092aef3156141a32df93663d683f06c733187f924b09d917efa53f08fb"
dependencies = [
"crc-any",
"embedded-hal 1.0.0",
@ -74,6 +72,19 @@ version = "2.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
[[package]]
name = "buoyant"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbc36827a22794f33600a539fc2f16e6c535f9de5cf26509f8ce7ddb666cda1b"
dependencies = [
"embedded-graphics",
"embedded-graphics-core",
"heapless 0.8.0",
"paste",
"u8g2-fonts",
]
[[package]]
name = "bytemuck"
version = "1.24.0"
@ -97,6 +108,7 @@ name = "co2-meter"
version = "0.1.0"
dependencies = [
"aht20-driver",
"buoyant",
"critical-section",
"defmt 1.0.1",
"embedded-graphics",
@ -107,9 +119,11 @@ dependencies = [
"esp-bootloader-esp-idf",
"esp-hal",
"esp-println",
"lcd-async",
"heapless 0.9.2",
"libm",
"mipidsi",
"profont",
"tinybmp",
]
[[package]]
@ -950,24 +964,18 @@ dependencies = [
"syn 2.0.111",
]
[[package]]
name = "lcd-async"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "362f088c5c176ceedd4d0e817d17ae3b79480495deac605158a7cd86450450c5"
dependencies = [
"embedded-graphics",
"embedded-graphics-core",
"embedded-hal 1.0.0",
"embedded-hal-async",
]
[[package]]
name = "libc"
version = "0.2.177"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976"
[[package]]
name = "libm"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de"
[[package]]
name = "linked_list_allocator"
version = "0.10.5"
@ -1441,6 +1449,15 @@ dependencies = [
"syn 2.0.111",
]
[[package]]
name = "tinybmp"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df43af2cb7b369009aa14144959bb4f2720ab62034c9073242f2d3a186c2edb6"
dependencies = [
"embedded-graphics",
]
[[package]]
name = "toml_datetime"
version = "0.7.3"
@ -1477,6 +1494,16 @@ version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb"
[[package]]
name = "u8g2-fonts"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "825f57be1429fd60f335a4aade11e128a7ae4f89d75ca3a003cb8410a91093f7"
dependencies = [
"embedded-graphics",
"embedded-graphics-core",
]
[[package]]
name = "ufmt-write"
version = "0.1.0"

View File

@ -23,14 +23,19 @@ esp-backtrace = { version = "0.18.1", features = [
"panic-handler",
] }
esp-println = { version = "0.16.1", features = ["defmt-espflash", "esp32c3"] }
aht20-driver = { version = "2.0", default-features = false }
aht20-driver = { path = "aht20-driver" , default-features = false }
ens160 = { version = "0.6", default-features = false}
embedded-hal-bus = "0.3.0"
embedded-hal-bus = {version = "0.3.0", features = ["alloc"]}
mipidsi = "0.9.0"
embedded-graphics = "0.8.1"
lcd-async = "0.1.1"
profont = "0.7.0"
buoyant = "0.5.3"
heapless = "0.9.2"
tinybmp = "0.6.0"
libm = "0.2.15"
[profile.dev]

1
aht20-driver Submodule

Submodule aht20-driver added at 1bb4b02145

18
assets/Makefile Normal file
View File

@ -0,0 +1,18 @@
SVGS = $(wildcard ./*.svg)
TARGET_BMPS = $(SVGS:.svg=.bmp)
BACKCOLOR = 080808
.PHONY: all
all: $(TARGET_BMPS)
@echo $(SVGS)
clean:
rm $(TARGET_BMPS)
%.bmp: %.png
@convert $< -background "#$(BACKCOLOR)" -alpha remove -define bmp:subtype=RGB565 $@
%.png: %.svg
inkscape $< -o $@

BIN
assets/a.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

52
assets/a.svg Normal file
View File

@ -0,0 +1,52 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="45"
height="45"
viewBox="0 0 28.421052 28.421053"
stroke-width="1.6"
fill="none"
color="#bababa"
data-darkreader-inline-color=""
version="1.1"
id="svg1"
sodipodi:docname="humidity-icon.svg"
inkscape:version="1.4.2 (ebf0e940d0, 2025-05-08)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs1" />
<sodipodi:namedview
id="namedview1"
pagecolor="#000000"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:zoom="9.5273334"
inkscape:cx="19.942621"
inkscape:cy="22.829053"
inkscape:window-width="1916"
inkscape:window-height="1032"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="0"
inkscape:current-layer="svg1" />
<rect
style="fill:#242424;stroke-width:11.5955;paint-order:markers stroke fill;fill-opacity:1"
id="rect1"
width="28.421053"
height="28.421053"
x="-2.1636963e-07"
y="-2.1636963e-07" />
<path
d="m 23.127726,16.838624 c 0,-5.022692 -9.094383,-13.6415758 -9.094383,-13.6415758 0,0 -9.0943834,8.6188838 -9.0943834,13.6415758 0,5.022714 4.0716919,9.094383 9.0943834,9.094383 5.022714,0 9.094383,-4.071669 9.094383,-9.094383 z"
stroke="#bababa"
stroke-width="1.6"
data-darkreader-inline-stroke=""
style="stroke:#0137f1;stroke-width:4.04437;stroke-dasharray:none;stroke-opacity:1"
id="path1" />
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
assets/co2-icon.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

89
assets/co2-icon.svg Normal file
View File

@ -0,0 +1,89 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="45"
height="45"
viewBox="0 0 11.90625 11.90625"
version="1.1"
id="svg1"
inkscape:version="1.4.2 (ebf0e940d0, 2025-05-08)"
sodipodi:docname="co2-icon.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview1"
pagecolor="#000000"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="mm"
inkscape:zoom="7.6922777"
inkscape:cx="29.510115"
inkscape:cy="28.27511"
inkscape:window-width="1916"
inkscape:window-height="1032"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="0"
inkscape:current-layer="layer1" />
<defs
id="defs1" />
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0.08272917,0.15224179)">
<rect
style="fill:#242424;fill-opacity:1;stroke:none;stroke-width:1.6006;stroke-dasharray:6.40237, 3.20118, 1.6006, 3.20118;paint-order:markers stroke fill"
id="rect1"
width="11.90625"
height="11.90625"
x="-0.082729168"
y="-0.1522418" />
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:4.74984px;font-family:'ProFont IIx Nerd Font Propo';-inkscape-font-specification:'ProFont IIx Nerd Font Propo';text-align:start;writing-mode:lr-tb;direction:ltr;text-anchor:start;fill:#00ade7;fill-opacity:1;stroke-width:0.296867;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:0.2"
x="9.2658949"
y="8.814003"
id="text1-3"
transform="scale(0.98156475,1.0187815)"
inkscape:label="2"><tspan
sodipodi:role="line"
id="tspan1-6"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:4.74984px;font-family:'ProFont IIx Nerd Font Propo';-inkscape-font-specification:'ProFont IIx Nerd Font Propo';fill:#00ade7;fill-opacity:1;stroke-width:0.296867"
x="9.2658949"
y="8.814003">2</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.34054px;font-family:'ProFont IIx Nerd Font Propo';-inkscape-font-specification:'ProFont IIx Nerd Font Propo';text-align:start;writing-mode:lr-tb;direction:ltr;text-anchor:start;fill:#00ade7;fill-opacity:1;stroke-width:0.458785;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:0.2"
x="0.056607485"
y="9.7028713"
id="text1-3-3"
transform="scale(0.98156475,1.0187815)"
inkscape:label="2"><tspan
sodipodi:role="line"
id="tspan1-6-5"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.34054px;font-family:'ProFont IIx Nerd Font Propo';-inkscape-font-specification:'ProFont IIx Nerd Font Propo';fill:#00ade7;fill-opacity:1;stroke-width:0.458785"
x="0.056607485"
y="9.7028713">C</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.34054px;font-family:'ProFont IIx Nerd Font Propo';-inkscape-font-specification:'ProFont IIx Nerd Font Propo';text-align:start;writing-mode:lr-tb;direction:ltr;text-anchor:start;fill:#00ade7;fill-opacity:1;stroke-width:0.458785;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:0.2"
x="4.6601677"
y="7.3943286"
id="text1-3-3-7"
transform="scale(0.98156475,1.0187815)"
inkscape:label="2"><tspan
sodipodi:role="line"
id="tspan1-6-5-9"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.34054px;font-family:'ProFont IIx Nerd Font Propo';-inkscape-font-specification:'ProFont IIx Nerd Font Propo';fill:#00ade7;fill-opacity:1;stroke-width:0.458785"
x="4.6601677"
y="7.3943286">O</tspan></text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.3 KiB

BIN
assets/humidity-icon.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

60
assets/humidity-icon.svg Normal file
View File

@ -0,0 +1,60 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg:svg
width="45"
height="45"
viewBox="0 0 28.421053 28.421053"
stroke-width="1.6"
fill="none"
color="#bababa"
data-darkreader-inline-color=""
version="1.1"
id="svg1"
sodipodi:docname="humidity-icon.svg"
inkscape:version="1.4.2 (ebf0e940d0, 2025-05-08)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:svg="http://www.w3.org/2000/svg">
<svg:defs
id="defs1" />
<sodipodi:namedview
id="namedview1"
pagecolor="#000000"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:zoom="9.5273334"
inkscape:cx="19.83766"
inkscape:cy="22.724092"
inkscape:window-width="1916"
inkscape:window-height="1032"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="0"
inkscape:current-layer="svg1" />
<svg:rect
style="fill:#242424;stroke-width:6.22986;stroke-dasharray:none;paint-order:markers stroke fill"
id="rect1"
width="28.421053"
height="28.421053"
x="3.3630371e-08"
y="3.3630371e-08" />
<svg:path
d="m 23.392462,16.891615 c 0,-5.071045 -9.181935,-13.7729019 -9.181935,-13.7729019 0,0 -9.1819355,8.7018569 -9.1819355,13.7729019 0,5.071068 4.1108899,9.181935 9.1819355,9.181935 5.071068,0 9.181935,-4.110867 9.181935,-9.181935 z"
stroke="#bababa"
stroke-width="1.6"
data-darkreader-inline-stroke=""
style="stroke:#0137f1;stroke-width:4.0833;stroke-dasharray:none;stroke-opacity:1"
id="path1" />
<style
class="darkreader darkreader--fallback">html, body, body :not(iframe) {
background-color: var(--darkreader-background-ffffff, #181a1b) !important;
border-color: var(--darkreader-border-404040, #776e62) !important;
color: var(--darkreader-text-000000, #e8e6e3) !important;
}
div[style*=&quot;background-color: rgb(135, 135, 135)&quot;] {
background-color: #878787 !important;
}</style>
</svg:svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

BIN
assets/indic-falling.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 538 B

94
assets/indic-falling.svg Normal file
View File

@ -0,0 +1,94 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="10"
height="20"
viewBox="0 0 10 20"
version="1.1"
id="svg1"
inkscape:version="1.4.2 (ebf0e940d0, 2025-05-08)"
sodipodi:docname="indic-falling.svg"
inkscape:export-filename="indic-rising.svg"
inkscape:export-xdpi="102.28008"
inkscape:export-ydpi="102.28008"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview1"
pagecolor="#000000"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="mm"
inkscape:export-bgcolor="#080808ff"
inkscape:zoom="32"
inkscape:cx="3.640625"
inkscape:cy="8.328125"
inkscape:window-width="1916"
inkscape:window-height="1032"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="0"
inkscape:current-layer="layer1" />
<defs
id="defs1" />
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="matrix(0.10139507,0,0,0.10139507,4.4930246,0.14783758)">
<rect
style="fill:#080808;fill-opacity:1;stroke:none;stroke-width:95.2216;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:0.2;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
id="rect4"
width="98.624123"
height="197.24825"
x="-44.312061"
y="-1.4580333" />
<path
sodipodi:type="star"
style="fill:#1c1c1c;fill-opacity:1;stroke:none;stroke-width:6.94267343;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:0.2;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
id="path1"
inkscape:flatsided="true"
sodipodi:sides="3"
sodipodi:cx="25.63151"
sodipodi:cy="24.391275"
sodipodi:r1="55.859524"
sodipodi:r2="27.929762"
sodipodi:arg1="0.52359878"
sodipodi:arg2="1.5707963"
inkscape:rounded="0.1"
inkscape:randomized="0"
d="m 74.007276,52.321038 c -4.837576,8.378928 -91.913956,8.378928 -96.751533,-1e-6 -4.837577,-8.378929 38.700614,-83.789285 48.375767,-83.789285 9.675153,0 53.213343,75.410357 48.375766,83.789286 z"
inkscape:transform-center-y="-12.4031"
transform="matrix(0.82051985,0,0,0.88816373,-16.031162,29.570393)" />
<path
sodipodi:type="star"
style="fill:#db0000;fill-opacity:1;stroke:none;stroke-width:6.94267343;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:0.2;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
id="path1-1"
inkscape:flatsided="true"
sodipodi:sides="3"
sodipodi:cx="25.63151"
sodipodi:cy="24.391275"
sodipodi:r1="55.859524"
sodipodi:r2="27.929762"
sodipodi:arg1="0.52359878"
sodipodi:arg2="1.5707963"
inkscape:rounded="0.1"
inkscape:randomized="0"
d="m 74.007276,52.321038 c -4.837576,8.378928 -91.913956,8.378928 -96.751533,-1e-6 -4.837577,-8.378929 38.700614,-83.789285 48.375767,-83.789285 9.675153,0 53.213343,75.410357 48.375766,83.789286 z"
inkscape:transform-center-y="12.403106"
transform="matrix(-0.82051985,0,0,-0.88816373,26.031162,164.76177)" />
<circle
style="fill:#1c1c1c;fill-opacity:1;stroke:#080808;stroke-width:5.96404155;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:0.2;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
id="path2"
cx="5.0000005"
cy="97.166092"
r="43.119678" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.8 KiB

BIN
assets/indic-rising.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 538 B

94
assets/indic-rising.svg Normal file
View File

@ -0,0 +1,94 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="10"
height="20"
viewBox="0 0 10 20"
version="1.1"
id="svg1"
inkscape:version="1.4.2 (ebf0e940d0, 2025-05-08)"
sodipodi:docname="indic-rising.svg"
inkscape:export-filename="indic-rising.svg"
inkscape:export-xdpi="102.28008"
inkscape:export-ydpi="102.28008"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview1"
pagecolor="#000000"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="mm"
inkscape:export-bgcolor="#080808ff"
inkscape:zoom="32"
inkscape:cx="3.640625"
inkscape:cy="8.328125"
inkscape:window-width="1916"
inkscape:window-height="1032"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="0"
inkscape:current-layer="layer1" />
<defs
id="defs1" />
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="matrix(0.10139507,0,0,0.10139507,4.4930246,0.14783758)">
<rect
style="fill:#080808;fill-opacity:1;stroke:none;stroke-width:95.2216;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:0.2;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
id="rect4"
width="98.624123"
height="197.24825"
x="-44.312061"
y="-1.4580333" />
<path
sodipodi:type="star"
style="fill:#00e565;fill-opacity:1;stroke:none;stroke-width:6.94267343;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:0.2;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
id="path1"
inkscape:flatsided="true"
sodipodi:sides="3"
sodipodi:cx="25.63151"
sodipodi:cy="24.391275"
sodipodi:r1="55.859524"
sodipodi:r2="27.929762"
sodipodi:arg1="0.52359878"
sodipodi:arg2="1.5707963"
inkscape:rounded="0.1"
inkscape:randomized="0"
d="m 74.007276,52.321038 c -4.837576,8.378928 -91.913956,8.378928 -96.751533,-1e-6 -4.837577,-8.378929 38.700614,-83.789285 48.375767,-83.789285 9.675153,0 53.213343,75.410357 48.375766,83.789286 z"
inkscape:transform-center-y="-12.4031"
transform="matrix(0.82051985,0,0,0.88816373,-16.031162,29.570393)" />
<path
sodipodi:type="star"
style="fill:#1c1c1c;fill-opacity:1;stroke:none;stroke-width:6.94267343;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:0.2;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
id="path1-1"
inkscape:flatsided="true"
sodipodi:sides="3"
sodipodi:cx="25.63151"
sodipodi:cy="24.391275"
sodipodi:r1="55.859524"
sodipodi:r2="27.929762"
sodipodi:arg1="0.52359878"
sodipodi:arg2="1.5707963"
inkscape:rounded="0.1"
inkscape:randomized="0"
d="m 74.007276,52.321038 c -4.837576,8.378928 -91.913956,8.378928 -96.751533,-1e-6 -4.837577,-8.378929 38.700614,-83.789285 48.375767,-83.789285 9.675153,0 53.213343,75.410357 48.375766,83.789286 z"
inkscape:transform-center-y="12.403106"
transform="matrix(-0.82051985,0,0,-0.88816373,26.031162,164.76177)" />
<circle
style="fill:#1c1c1c;fill-opacity:1;stroke:#080808;stroke-width:5.97976;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:0.2;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
id="path2"
cx="5.0000005"
cy="97.166092"
r="43.119678" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.8 KiB

BIN
assets/indic-steady.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 538 B

94
assets/indic-steady.svg Normal file
View File

@ -0,0 +1,94 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="10"
height="20"
viewBox="0 0 10 20"
version="1.1"
id="svg1"
inkscape:version="1.4.2 (ebf0e940d0, 2025-05-08)"
sodipodi:docname="indic-steady.svg"
inkscape:export-filename="indic-rising.svg"
inkscape:export-xdpi="102.28008"
inkscape:export-ydpi="102.28008"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview1"
pagecolor="#000000"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="mm"
inkscape:export-bgcolor="#080808ff"
inkscape:zoom="32"
inkscape:cx="3.640625"
inkscape:cy="8.328125"
inkscape:window-width="1916"
inkscape:window-height="1032"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="0"
inkscape:current-layer="layer1" />
<defs
id="defs1" />
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="matrix(0.10139507,0,0,0.10139507,4.4930246,0.14783758)">
<rect
style="fill:#080808;fill-opacity:1;stroke:none;stroke-width:95.2216;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:0.2;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
id="rect4"
width="98.624123"
height="197.24825"
x="-44.312061"
y="-1.4580333" />
<path
sodipodi:type="star"
style="fill:#1c1c1c;fill-opacity:1;stroke:none;stroke-width:6.94267343;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:0.2;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
id="path1"
inkscape:flatsided="true"
sodipodi:sides="3"
sodipodi:cx="25.63151"
sodipodi:cy="24.391275"
sodipodi:r1="55.859524"
sodipodi:r2="27.929762"
sodipodi:arg1="0.52359878"
sodipodi:arg2="1.5707963"
inkscape:rounded="0.1"
inkscape:randomized="0"
d="m 74.007276,52.321038 c -4.837576,8.378928 -91.913956,8.378928 -96.751533,-1e-6 -4.837577,-8.378929 38.700614,-83.789285 48.375767,-83.789285 9.675153,0 53.213343,75.410357 48.375766,83.789286 z"
inkscape:transform-center-y="-12.4031"
transform="matrix(0.82051985,0,0,0.88816373,-16.031162,29.570393)" />
<path
sodipodi:type="star"
style="fill:#1c1c1c;fill-opacity:1;stroke:none;stroke-width:6.94267343;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:0.2;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
id="path1-1"
inkscape:flatsided="true"
sodipodi:sides="3"
sodipodi:cx="25.63151"
sodipodi:cy="24.391275"
sodipodi:r1="55.859524"
sodipodi:r2="27.929762"
sodipodi:arg1="0.52359878"
sodipodi:arg2="1.5707963"
inkscape:rounded="0.1"
inkscape:randomized="0"
d="m 74.007276,52.321038 c -4.837576,8.378928 -91.913956,8.378928 -96.751533,-1e-6 -4.837577,-8.378929 38.700614,-83.789285 48.375767,-83.789285 9.675153,0 53.213343,75.410357 48.375766,83.789286 z"
inkscape:transform-center-y="12.403106"
transform="matrix(-0.82051985,0,0,-0.88816373,26.031162,164.76177)" />
<circle
style="fill:#f6ab00;fill-opacity:1;stroke:#080808;stroke-width:5.97976;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:0.2;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
id="path2"
cx="5.0000005"
cy="97.166092"
r="43.119678" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.8 KiB

BIN
assets/temperature-icon.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

116
assets/temperature-icon.svg Normal file
View File

@ -0,0 +1,116 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg:svg
width="45"
height="45"
viewBox="0 0 45 45"
stroke-width="1.5"
fill="none"
color="#ff6600"
data-darkreader-inline-color=""
version="1.1"
id="svg7"
sodipodi:docname="temperature-icon.svg"
inkscape:version="1.4.2 (ebf0e940d0, 2025-05-08)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:svg="http://www.w3.org/2000/svg">
<svg:defs
id="defs7" />
<sodipodi:namedview
id="namedview7"
pagecolor="#000000"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:zoom="10.216728"
inkscape:cx="24.861188"
inkscape:cy="27.748609"
inkscape:window-width="1916"
inkscape:window-height="1032"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="0"
inkscape:current-layer="layer1" />
<style
class="darkreader darkreader--fallback">html, body, body :not(iframe) {
background-color: var(--darkreader-background-ffffff, #181a1b) !important;
border-color: var(--darkreader-border-404040, #776e62) !important;
color: var(--darkreader-text-000000, #e8e6e3) !important;
}
div[style*=&quot;background-color: rgb(135, 135, 135)&quot;] {
background-color: #878787 !important;
}</style>
<svg:g
inkscape:groupmode="layer"
id="layer1"
inkscape:label="Layer 1"
transform="matrix(2.0390253,0,0,2.0390253,-3.3768322,-2.3378069)">
<svg:rect
style="fill:#242424;fill-opacity:1;stroke-width:5.46432;paint-order:markers stroke fill"
id="rect1"
width="22.069368"
height="22.069368"
x="1.6561011"
y="1.1465315" />
<svg:path
d="m 6,11.9995 c -1.21445,0.9122 -2,2.3646 -2,4.0004 0,2.7614 2.23858,5 5,5 2.7614,0 5,-2.2386 5,-5 0,-1.6358 -0.7856,-3.0882 -2,-4.0004"
stroke="#ff6600"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
data-darkreader-inline-stroke=""
id="path1" />
<svg:path
d="M 6,12 V 3 h 6 v 9"
stroke="#ff6600"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
data-darkreader-inline-stroke=""
id="path2" />
<svg:path
d="m 12,3 h 2"
stroke="#ff6600"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
data-darkreader-inline-stroke=""
id="path3" />
<svg:path
d="m 12,6 h 2"
stroke="#ff6600"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
data-darkreader-inline-stroke=""
id="path4" />
<svg:path
d="m 12,9 h 2"
stroke="#ff6600"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
data-darkreader-inline-stroke=""
id="path5" />
<svg:path
d="m 19,7 c 1.1046,0 2,-0.89543 2,-2 0,-1.10457 -0.8954,-2 -2,-2 -1.1046,0 -2,0.89543 -2,2 0,1.10457 0.8954,2 2,2 z"
stroke="#ff6600"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
data-darkreader-inline-stroke=""
id="path6" />
<svg:path
d="m 9,14 c -1.10457,0 -2,0.8954 -2,2 0,1.1046 0.89543,2 2,2 1.1046,0 2,-0.8954 2,-2 0,-1.1046 -0.8954,-2 -2,-2 z m 0,0 v -3"
stroke="#ff6600"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
data-darkreader-inline-stroke=""
id="path7"
inkscape:label="path7" />
</svg:g>
</svg:svg>

After

Width:  |  Height:  |  Size: 3.6 KiB

BIN
assets/voc-icon.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

46
assets/voc-icon.svg Normal file
View File

@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
viewBox="0 0 45 45"
fill="currentColor"
version="1.1"
id="svg1"
sodipodi:docname="voc-icon.svg"
inkscape:version="1.4.2 (ebf0e940d0, 2025-05-08)"
width="45"
height="45"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs1" />
<sodipodi:namedview
id="namedview1"
pagecolor="#000000"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:zoom="12.529602"
inkscape:cx="23.624054"
inkscape:cy="30.687327"
inkscape:window-width="1916"
inkscape:window-height="1032"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="0"
inkscape:current-layer="svg1" />
<rect
style="fill:#242424;stroke-width:12.8688;stroke-dasharray:51.4753, 25.7377, 12.8688, 25.7377;paint-order:markers stroke fill;fill-opacity:1"
id="rect1"
width="45"
height="45"
x="0"
y="0" />
<path
d="M 6.3874472,5.380413 H 14.443723 V 9.4085512 H 6.3874472 Z M 30.556276,35.591449 h 8.056276 v 4.028138 H 30.556276 Z M 2.359309,15.450758 h 10.070345 v 4.028139 H 2.359309 Z m 14.098484,0 H 22.5 v 4.028139 h -6.042207 z m 10.070345,0 h 12.084414 v 4.028139 H 26.528138 Z M 6.3874472,25.521104 H 18.471862 v 4.028138 H 6.3874472 Z m 16.1125528,0 h 6.042207 v 4.028138 H 22.5 Z m 10.070345,0 h 10.070346 v 4.028138 H 32.570345 Z M 18.471862,5.380413 H 42.640691 V 9.4085512 H 18.471862 Z M 2.359309,35.591449 h 24.168829 v 4.028138 H 2.359309 Z"
id="path1"
style="fill:#cacaca;fill-opacity:1;stroke:none;stroke-width:2.01407;stroke-opacity:1" />
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

13
src/bin/colors.rs Normal file
View File

@ -0,0 +1,13 @@
use embedded_graphics::pixelcolor::Rgb565;
use embedded_graphics::prelude::RgbColor;
use embedded_graphics::prelude::WebColors;
pub const BACKGROUND_COLOR: Rgb565 = Rgb565::BLACK;
//pub const FRAME_BACKGROUD_COLOR: Rgb565 = Rgb565::new(1, 2, 1);
pub const FRAME_BACKGROUD_COLOR: Rgb565 = Rgb565::new(0, 0, 0);
pub const FRAME_STROKE_COLOR: Rgb565 = Rgb565::new(4, 9, 4);
pub const MAIN_TEXT_COLOR: Rgb565 = Rgb565::WHITE;
pub const SUB_TEXT_COLOR: Rgb565 = Rgb565::CSS_DARK_GRAY;
pub const FRAME_STROKE: u32 = 3;

135
src/bin/graph.rs Normal file
View File

@ -0,0 +1,135 @@
use core::iter::Iterator;
use embedded_graphics::Pixel;
use embedded_graphics::pixelcolor::Rgb565;
use embedded_graphics::prelude::DrawTarget;
use embedded_graphics::prelude::Drawable;
use embedded_graphics::prelude::Point;
use embedded_graphics::prelude::Primitive;
use embedded_graphics::prelude::RgbColor;
use embedded_graphics::prelude::Size;
use embedded_graphics::primitives::Line;
use embedded_graphics::primitives::PrimitiveStyle;
fn map_float(x: f32, x_min: f32, x_max: f32, y_min: f32, y_max: f32) -> f32 {
((x - x_min) / (x_max - x_min)) * (y_max - y_min) + y_min
}
const DEFAULT_LUT: [Rgb565; 5] = [
Rgb565::new(0, 16, 11),
Rgb565::new(11, 20, 17),
Rgb565::new(23, 20, 18),
Rgb565::new(31, 24, 12),
Rgb565::new(31, 41, 0),
];
fn rgb565_interpolate(a: Rgb565, b: Rgb565, x: f32) -> Rgb565 {
Rgb565::new(
(a.r() as f32 * (1. - x)) as u8 + (b.r() as f32 * x) as u8,
(a.g() as f32 * (1. - x)) as u8 + (b.g() as f32 * x) as u8,
(a.b() as f32 * (1. - x)) as u8 + (b.b() as f32 * x) as u8,
)
}
fn color_lut(mut x: f32, colors: &[Rgb565]) -> Rgb565 {
if x == 1. {
return colors[colors.len() - 1];
}
if x == 0. {
return colors[0];
}
x *= (colors.len() - 1) as f32;
let index = libm::floorf(x);
let interp = x - index;
rgb565_interpolate(colors[index as usize], colors[index as usize + 1], interp)
}
pub fn graph_data<T: DrawTarget<Color = Rgb565>>(data: &[f32], target: &mut T) {
let min = data.iter().copied().reduce(f32::min).unwrap_or(0.);
let max = data.iter().copied().reduce(f32::max).unwrap_or(0.);
data.iter().map(|x| x);
let size = Size::new(
target.bounding_box().size.width,
target.bounding_box().size.height,
);
let mut start = Point::new(
0,
map_float(data[0], min, max, size.height as f32, 0.) as i32,
);
let point_count = size.width / 2;
for y in 0..size.height {
let value = map_float(y as f32, size.height as f32, 0., min, max);
let lut_color = color_lut(
map_float(y as f32, size.height as f32, 0., 0., 1.),
&DEFAULT_LUT,
);
let color = rgb565_interpolate(Rgb565::BLACK, lut_color, 0.3);
for x in 0..size.width {
let pos = map_float(
x as f32,
0.,
size.width as f32 - 1.,
0.,
(data.len() - 1) as f32,
);
// Sample
let index = libm::floorf(pos).min((data.len() - 2) as f32);
let interpolation = pos - index;
let curve_value = data[index as usize] * (1. - interpolation)
+ data[index as usize + 1] * interpolation;
// if value <= curve_value && (x + y) % 2 == 0 {
// let _ = Pixel(Point::new(x as i32, y as i32), color).draw(target);
// }
if (x as i32 - y as i32) % 6 == 0 {
if value <= curve_value {
let mut pixel_color = Rgb565::new(1, 2, 1);
pixel_color = color;
let _ = Pixel(Point::new(x as i32 - 2, y as i32), pixel_color).draw(target);
}
}
}
}
for (i, x) in data.iter().skip(1).enumerate() {
let point = Point::new(
map_float(i as f32, 0., data.len() as f32 - 1., 0., size.width as f32) as i32,
map_float(*x, min, max, size.height as f32, 0.) as i32,
);
let factor = map_float(*x, min, max, 0., 1.);
let _ = Line::new(start, point)
.into_styled(PrimitiveStyle::with_stroke(
color_lut(factor, &DEFAULT_LUT),
2,
))
.draw(target);
start = point;
}
// for i in 0..point_count {
// // Sample data
// let index = map_float(
// i as f32,
// 0.,
// point_count as f32,
// 0 as f32,
// data.len() as f32,
// ) as usize;
// let value = data[index];
// let point = Point::new(
// map_float(i as f32, 0., point_count as f32, 0., size.width as f32) as i32,
// map_float(value, min, max, 0., size.height as f32) as i32,
// );
//
// let _ = Line::new(start, point)
// .into_styled(PrimitiveStyle::with_stroke(Rgb565::WHITE, 2))
// .draw(target);
// start = point;
// }
}

55
src/bin/images.rs Normal file
View File

@ -0,0 +1,55 @@
use core::cell::UnsafeCell;
use core::include_bytes;
use core::mem::MaybeUninit;
use critical_section::Mutex;
use embedded_graphics::pixelcolor::Rgb565;
use tinybmp::Bmp;
pub type StaticImage = Mutex<UnsafeCell<MaybeUninit<Bmp<'static, Rgb565>>>>;
#[rustfmt::skip]
pub static HUMIDITY_ICON: StaticImage = Mutex::new(UnsafeCell::new(MaybeUninit::zeroed()));
pub static TEMPERATURE_ICON: StaticImage = Mutex::new(UnsafeCell::new(MaybeUninit::zeroed()));
pub static VOC_ICON: StaticImage = Mutex::new(UnsafeCell::new(MaybeUninit::zeroed()));
pub static CO2_ICON: StaticImage = Mutex::new(UnsafeCell::new(MaybeUninit::zeroed()));
// Tendency indicators
pub static TENDENCY_RISING: StaticImage = Mutex::new(UnsafeCell::new(MaybeUninit::zeroed()));
pub static TENDENCY_STEADY: StaticImage = Mutex::new(UnsafeCell::new(MaybeUninit::zeroed()));
pub static TENDENCY_FALLING: StaticImage = Mutex::new(UnsafeCell::new(MaybeUninit::zeroed()));
macro_rules! load_image {
($stat:expr, $path:expr) => {
critical_section::with(|cs| {
*$stat.borrow(cs).get() =
MaybeUninit::new(Bmp::from_slice(include_bytes!($path)).unwrap());
})
};
}
#[macro_export]
macro_rules! get_image {
($image:expr) => {
unsafe {
use core::mem::MaybeUninit;
use tinybmp::Bmp;
&*core::mem::transmute::<*mut MaybeUninit<Bmp<Rgb565>>, *mut Bmp<Rgb565>>(
critical_section::with(|cs| $image.borrow(cs).get()),
)
}
};
}
pub fn prepare_images() {
unsafe {
load_image!(HUMIDITY_ICON, "../../assets/humidity-icon.bmp");
load_image!(TEMPERATURE_ICON, "../../assets/temperature-icon.bmp");
load_image!(VOC_ICON, "../../assets/voc-icon.bmp");
load_image!(CO2_ICON, "../../assets/co2-icon.bmp");
load_image!(TENDENCY_RISING, "../../assets/indic-rising.bmp");
load_image!(TENDENCY_STEADY, "../../assets/indic-steady.bmp");
load_image!(TENDENCY_FALLING, "../../assets/indic-falling.bmp");
}
}

View File

@ -7,30 +7,51 @@
)]
#![allow(unreachable_code)]
mod colors;
mod graph;
mod images;
mod sampler;
mod views;
use buoyant::primitives::Size;
use buoyant::view::AsDrawable;
use buoyant::view::Image;
use buoyant::view::ViewExt;
use core::default::Default;
use core::iter::Iterator;
use defmt::info;
use embedded_graphics::Drawable;
use embedded_graphics::framebuffer::{Framebuffer, buffer_size};
use embedded_graphics::image::{Image, ImageRaw};
use embedded_graphics::pixelcolor::Rgb565;
use embedded_graphics::pixelcolor::raw::{BigEndian, LittleEndian};
use embedded_graphics::prelude::{Angle, DrawTarget, Point, Primitive, RgbColor, WebColors};
use embedded_graphics::primitives::{
Circle, PrimitiveStyle, PrimitiveStyleBuilder, Sector, StyledDrawable, Triangle,
};
use embedded_graphics::text::renderer::TextRenderer;
use embedded_graphics::framebuffer::Framebuffer;
use embedded_graphics::framebuffer::buffer_size;
use embedded_graphics::image::ImageRaw;
use embedded_graphics::pixelcolor::raw::LittleEndian;
use embedded_graphics::prelude::Point;
use embedded_graphics::prelude::RgbColor;
use embedded_hal_bus::spi::ExclusiveDevice;
use esp_hal::clock::CpuClock;
use esp_hal::delay::Delay;
use esp_hal::gpio::{Level, Output, OutputConfig};
use esp_hal::main;
use esp_hal::riscv::asm::delay;
use esp_hal::rtc_cntl::Rtc;
use esp_hal::time::{Duration, Instant, Rate};
use esp_hal::timer::Timer;
use esp_hal::timer::timg::TimerGroup;
use mipidsi::interface::{Interface, SpiInterface};
use esp_hal::gpio::Level;
use esp_hal::gpio::Output;
use esp_hal::gpio::OutputConfig;
use esp_hal::time::Rate;
use heapless::format;
use mipidsi::interface::SpiInterface;
use mipidsi::models::ST7789;
use {esp_backtrace as _, esp_println as _};
use mipidsi::options::Orientation;
use mipidsi::options::Rotation;
use buoyant::view::HStack;
use buoyant::view::Spacer;
use buoyant::view::View;
use core::env;
use embedded_graphics::pixelcolor::Rgb565;
use esp_backtrace as _;
use esp_hal::main;
use esp_println as _;
use tinybmp::Bmp;
use crate::graph::graph_data;
use crate::images::StaticImage;
extern crate alloc;
@ -41,6 +62,7 @@ esp_bootloader_esp_idf::esp_app_desc!();
#[main]
fn main() -> ! {
// generator version: 1.0.1
images::prepare_images();
esp_alloc::heap_allocator!(size: 32 * 1024);
let config = esp_hal::Config::default().with_cpu_clock(CpuClock::max());
@ -81,125 +103,97 @@ fn main() -> ! {
.init(&mut timer)
.unwrap();
display
.set_tearing_effect(mipidsi::options::TearingEffect::Vertical)
.unwrap();
let mut fb =
Framebuffer::<Rgb565, _, LittleEndian, 240, 240, { buffer_size::<Rgb565>(240, 240) }>::new(
);
//fb.clear(Rgb565::BLACK).unwrap();
// Create styles used by the drawing operations.
let sector_style = PrimitiveStyleBuilder::new()
.stroke_color(Rgb565::BLACK)
.stroke_width(2)
.fill_color(Rgb565::YELLOW)
.build();
let eye_style = PrimitiveStyleBuilder::new()
.stroke_color(Rgb565::BLACK)
.stroke_width(1)
.fill_color(Rgb565::BLACK)
.build();
//let output_settings = ::new().scale(4).build();
//let mut window = Window::new("Pacman", &output_settings);
// The current progress of the animation
const STEPS: i32 = 10;
let mut progress: i32 = 0;
let delay = Delay::new();
loop {
let mut fb = Framebuffer::<
Rgb565,
_,
LittleEndian,
320,
240,
{ buffer_size::<Rgb565>(320, 240) },
>::new();
fb.clear(Rgb565::WHITE).unwrap();
let p = (progress - STEPS).abs();
// Draw a Sector as the main Pacman feature.
let _ = Sector::new(
Point::new(2, 2),
61,
Angle::from_degrees((p * 30 / STEPS) as f32),
Angle::from_degrees((360 - 2 * p * 30 / STEPS) as f32),
)
.draw_styled(&sector_style, &mut fb);
// Draw a Circle as the eye.
let _ = Circle::new(Point::new(36, 16), 5).draw_styled(&eye_style, &mut fb);
delay.delay_millis(50);
progress = (progress + 1) % (2 * STEPS + 1);
let img_raw = ImageRaw::<Rgb565, LittleEndian>::new(fb.data(), 320);
let image = Image::new(&img_raw, Point::zero());
image.draw(&mut display).unwrap();
}
// display
// .set_pixels(0, 0, 50, 50, core::iter::repeat(Rgb565::BLUE))
// views::menu::menu_view()
// .as_drawable(Size::new(240, 240), Rgb565::WHITE)
// .draw(&mut fb)
// .unwrap();
// //display.set_pixel(10, 10, Rgb565::WHITE).unwrap();
// let i2c = I2c::new(peripherals.I2C0, Config::default())
// .unwrap()
// .with_sda(peripherals.GPIO8)
// .with_scl(peripherals.GPIO9);
//
// esp_alloc::heap_allocator!(#[esp_hal::ram(reclaimed)] size: 66320);
//
// let rc = RefCell::new(i2c);
//
// let mut device = Ens160::new(embedded_hal_bus::i2c::RefCellDevice::new(&rc), 0x53);
// timer.delay_millis(500);
// device.reset().unwrap();
// info!("Reset device");
// timer.delay_millis(500);
// device.operational().unwrap();
// info!("Device operational");
//
// // Configure the AHT20 temperature and humidity sensor.
// let mut aht20_uninit = aht20_driver::AHT20::new(
// embedded_hal_bus::i2c::RefCellDevice::new(&rc),
// aht20_driver::SENSOR_ADDRESS,
// );
// let mut aht20 = aht20_uninit.init(&mut timer).unwrap();
//
// // Take the temperature and humidity measurement.
//
// loop {
// //timer.delay_millis(5000);
// if let Ok(status) = device.status() {
// if status.data_is_ready() {
// let aht20_measurement = aht20.measure(&mut timer).unwrap();
// //println!("temperature (aht20): {}C", aht20_measurement.temperature);
// //println!("humidity (aht20): {}%", aht20_measurement.humidity);
//
// let tvoc = device.tvoc().unwrap();
// let eco2 = device.eco2().unwrap();
// //info!("eco2: {}, tvoc: {}", *eco2, tvoc);
// print!(
// "{}, {}, {}, {}\n",
// *eco2, tvoc, aht20_measurement.temperature, aht20_measurement.humidity
// );
// // from eCO2
// // directly
// //let air_quality_index = device.air_quality_index().unwrap();
// }
// }
// }
//
//
let mut x = [0.; 100];
for (i, x) in x.iter_mut().enumerate() {
*x = libm::sinf(i as f32 / 10.);
}
graph_data(&x, &mut fb);
let img_raw = ImageRaw::<Rgb565, LittleEndian>::new(fb.data(), 240);
let image = embedded_graphics::image::Image::new(&img_raw, Point::zero());
image.draw(&mut display).unwrap();
// embedded_graphics::image::Image::new(get_image!(images::HUMIDITY_ICON), Point::zero())
// .draw(&mut display)
// .unwrap();
info!("Finished !");
loop {
info!("Hello world!");
let delay_start = Instant::now();
while delay_start.elapsed() < Duration::from_millis(500) {}
core::hint::spin_loop();
}
// for inspiration have a look at the examples at https://github.com/esp-rs/esp-hal/tree/esp-hal-v1.0.0/examples/src/bin
}
fn draw_interface<T: DrawTarget>(target: &mut T) {}
pub enum MenuIndicatorType {
Temperature(f32),
Humidity(f32),
Co2(u32),
Voc(u32),
}
impl MenuIndicatorType {
pub fn get_corresponding_icon(&self) -> &'static StaticImage {
match self {
MenuIndicatorType::Temperature(_) => &images::TEMPERATURE_ICON,
MenuIndicatorType::Humidity(_) => &images::HUMIDITY_ICON,
MenuIndicatorType::Co2(_) => &images::CO2_ICON,
MenuIndicatorType::Voc(_) => &images::VOC_ICON,
}
}
pub fn get_corresponding_unit_string(&self) -> &'static str {
match self {
MenuIndicatorType::Temperature(_) => "C",
MenuIndicatorType::Humidity(_) => "%",
MenuIndicatorType::Co2(_) => "ppm",
MenuIndicatorType::Voc(_) => "ppb",
}
}
pub fn get_value_str(&self) -> heapless::String<16> {
match self {
MenuIndicatorType::Temperature(temp) => format!(16; "{:.1}", temp).unwrap(),
MenuIndicatorType::Humidity(hum) => format!(16; "{:.1}", hum).unwrap(),
MenuIndicatorType::Co2(co2) => format!(16; "{}", co2).unwrap(),
MenuIndicatorType::Voc(voc) => format!(16; "{}", voc).unwrap(),
}
}
}
pub enum Tendency {
Rising,
Steady,
Falling,
}
impl Tendency {
pub fn get_corresponding_icon(&self) -> &'static StaticImage {
match self {
Self::Rising => &images::TENDENCY_RISING,
Self::Steady => &images::TENDENCY_STEADY,
Self::Falling => &images::TENDENCY_FALLING,
}
}
}
fn tendency_indicator(tendency: Tendency) -> impl View<Rgb565> {
HStack::new((
Image::new(get_image!(tendency.get_corresponding_icon()))
.flex_frame()
.with_min_size(10, 20)
.with_max_size(10, 20),
Spacer::default(),
))
.flex_frame()
.with_max_width(15)
}

53
src/bin/sampler.rs Normal file
View File

@ -0,0 +1,53 @@
use core::cell::RefCell;
use aht20_driver::AHT20;
use alloc::rc::Rc;
use alloc::vec::Vec;
use core::default::Default;
use embedded_hal_bus::i2c::RcDevice;
use ens160::Ens160;
use esp_hal::Blocking;
use esp_hal::DriverMode;
use esp_hal::delay::Delay;
use esp_hal::gpio::interconnect::PeripheralOutput;
use esp_hal::i2c;
use esp_hal::i2c::master::I2c;
use esp_hal::i2c::master::Instance;
use esp_hal::peripherals;
use esp_hal::peripherals::Peripherals;
pub struct Sampler<'a> {
ens160: Ens160<RcDevice<I2c<'a, Blocking>>>,
aht20: aht20_driver::AHT20Initialized<RcDevice<I2c<'a, Blocking>>>,
}
impl<'a> Sampler<'a> {
pub fn new(
i2c: impl Instance + 'a,
sda: impl PeripheralOutput<'a>,
scl: impl PeripheralOutput<'a>,
mut timer: Delay,
) -> Self {
let i2c = I2c::new(i2c, Default::default())
.unwrap()
.with_sda(sda)
.with_scl(scl);
let i2c = Rc::new(RefCell::new(i2c));
let mut ens160 = Ens160::new(embedded_hal_bus::i2c::RcDevice::new(i2c.clone()), 0x53);
timer.delay_millis(500);
ens160.reset().unwrap();
timer.delay_millis(500);
ens160.operational().unwrap();
let aht20_uninit = AHT20::new(
embedded_hal_bus::i2c::RcDevice::new(i2c.clone()),
aht20_driver::SENSOR_ADDRESS,
);
let aht20 = aht20_uninit.init(&mut timer).unwrap();
Sampler { ens160, aht20 }
}
}

3
src/bin/views.rs Normal file
View File

@ -0,0 +1,3 @@
pub mod detail;
pub mod icon;
pub mod menu;

52
src/bin/views/detail.rs Normal file
View File

@ -0,0 +1,52 @@
use buoyant::layout::HorizontalAlignment;
use buoyant::layout::VerticalAlignment;
use buoyant::view::HStack;
use buoyant::view::Spacer;
use buoyant::view::Text;
use buoyant::view::VStack;
use buoyant::view::View;
use buoyant::view::ViewExt;
use embedded_graphics::pixelcolor::Rgb565;
use profont::PROFONT_18_POINT;
use profont::PROFONT_24_POINT;
use crate::MenuIndicatorType;
use crate::Tendency;
use crate::colors::FRAME_STROKE_COLOR;
use crate::colors::MAIN_TEXT_COLOR;
use crate::colors::SUB_TEXT_COLOR;
use crate::tendency_indicator;
use crate::views::icon::icon_box_view;
pub fn detailed_view(indicator: MenuIndicatorType, tendency: Tendency) -> impl View<Rgb565> {
VStack::new((
// Header
HStack::new((
icon_box_view(FRAME_STROKE_COLOR, indicator.get_corresponding_icon()),
Spacer::default().flex_frame().with_max_width(10),
tendency_indicator(tendency),
Text::new(indicator.get_value_str(), &PROFONT_24_POINT)
.foreground_color(MAIN_TEXT_COLOR),
Text::new(indicator.get_corresponding_unit_string(), &PROFONT_18_POINT)
.foreground_color(SUB_TEXT_COLOR)
.flex_frame()
.with_infinite_max_height()
.with_vertical_alignment(VerticalAlignment::Bottom)
.with_max_height(25),
Spacer::default(),
Text::new("Temperature", &PROFONT_18_POINT)
.foreground_color(SUB_TEXT_COLOR)
.flex_frame()
.with_infinite_max_height()
.with_vertical_alignment(VerticalAlignment::Bottom)
.with_max_height(25),
Spacer::default().flex_frame().with_max_width(10),
)),
// Window
Spacer::default()
.flex_frame()
.with_infinite_max_width()
.with_infinite_max_height(),
))
.with_alignment(HorizontalAlignment::Leading)
}

18
src/bin/views/icon.rs Normal file
View File

@ -0,0 +1,18 @@
use buoyant::view::View;
use buoyant::view::ViewExt;
use buoyant::view::ZStack;
use buoyant::view::shape::Rectangle;
use embedded_graphics::pixelcolor::Rgb565;
use crate::get_image;
use crate::images::StaticImage;
pub fn icon_box_view(box_color: Rgb565, icon: &'static StaticImage) -> impl View<Rgb565> {
ZStack::new((
Rectangle.corner_radius(10).foreground_color(box_color),
buoyant::view::Image::new(get_image!(icon)),
))
.flex_frame()
.with_max_size(53, 53)
.with_min_size(53, 53)
}

85
src/bin/views/menu.rs Normal file
View File

@ -0,0 +1,85 @@
use buoyant::view::VStack;
use buoyant::view::View;
use buoyant::view::prelude::*;
use embedded_graphics::pixelcolor::Rgb565;
use embedded_graphics::prelude::*;
use profont::PROFONT_18_POINT;
use profont::PROFONT_24_POINT;
use crate::MenuIndicatorType;
use crate::Tendency;
use crate::colors::FRAME_BACKGROUD_COLOR;
use crate::colors::FRAME_STROKE;
use crate::colors::FRAME_STROKE_COLOR;
use crate::colors::MAIN_TEXT_COLOR;
use crate::colors::SUB_TEXT_COLOR;
use crate::get_image;
use crate::views::icon::icon_box_view;
pub fn menu_view() -> impl View<Rgb565> {
VStack::new((
HStack::new((
main_menu_indicator(MenuIndicatorType::Temperature(31.5), Tendency::Falling),
main_menu_indicator(MenuIndicatorType::Humidity(36.2), Tendency::Steady),
))
.with_spacing(5),
HStack::new((
main_menu_indicator(MenuIndicatorType::Co2(1329), Tendency::Rising),
main_menu_indicator(MenuIndicatorType::Voc(29), Tendency::Falling),
))
.with_spacing(5),
))
.with_spacing(5)
}
fn main_menu_indicator(indicator_type: MenuIndicatorType, tendency: Tendency) -> impl View<Rgb565> {
Rectangle
.corner_radius(10)
.stroked(FRAME_STROKE)
.foreground_color(FRAME_STROKE_COLOR)
.background(Alignment::Center, || {
ZStack::new((
Rectangle
.corner_radius(15)
.foreground_color(FRAME_BACKGROUD_COLOR),
VStack::new((
HStack::new((
Spacer::default(),
icon_box_view(FRAME_STROKE_COLOR, indicator_type.get_corresponding_icon()),
Spacer::default(),
)),
HStack::new((
Spacer::default(),
tendency_indicator(tendency),
Text::new(indicator_type.get_value_str(), &PROFONT_24_POINT)
.foreground_color(MAIN_TEXT_COLOR),
Text::new(
indicator_type.get_corresponding_unit_string(),
&PROFONT_18_POINT,
)
.foreground_color(SUB_TEXT_COLOR)
.flex_frame()
.with_infinite_max_height()
.with_vertical_alignment(VerticalAlignment::Bottom)
.with_max_height(25),
Spacer::default(),
)),
))
.with_alignment(HorizontalAlignment::Center)
.flex_frame(),
))
})
}
fn tendency_indicator(tendency: Tendency) -> impl View<Rgb565> {
HStack::new((
Image::new(get_image!(tendency.get_corresponding_icon()))
.flex_frame()
.with_min_size(10, 20)
.with_max_size(10, 20),
Spacer::default(),
))
.flex_frame()
.with_max_width(15)
}