refactor: project structure (#9)
Split up project into multiple crates and use a Cargo workspace.
@@ -1,4 +1,3 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="ProjectInspectionProfilesVisibleTreeState">
|
<component name="ProjectInspectionProfilesVisibleTreeState">
|
||||||
<entry key="Project Default">
|
<entry key="Project Default">
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<component name="ProjectRunConfigurationManager">
|
<component name="ProjectRunConfigurationManager">
|
||||||
<configuration default="false" name="Run demo" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
|
<configuration default="false" name="Run demo" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
|
||||||
<option name="buildProfileId" value="dev" />
|
<option name="buildProfileId" value="dev" />
|
||||||
<option name="command" value="run --package aoostar-rs --bin asterctl -- --demo -c monitor.json" />
|
<option name="command" value="run --bin asterctl -- --demo -c monitor.json" />
|
||||||
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
|
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
|
||||||
<envs />
|
<envs />
|
||||||
<option name="emulateTerminal" value="true" />
|
<option name="emulateTerminal" value="true" />
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<component name="ProjectRunConfigurationManager">
|
<component name="ProjectRunConfigurationManager">
|
||||||
<configuration default="false" name="Run demo DEV" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
|
<configuration default="false" name="Run demo DEV" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
|
||||||
<option name="buildProfileId" value="release" />
|
<option name="buildProfileId" value="release" />
|
||||||
<option name="command" value="run --package aoostar-rs --bin asterctl -- --demo -c monitor.json --save --simulate" />
|
<option name="command" value="run --bin demo -- -c monitor.json --save --simulate" />
|
||||||
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
|
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
|
||||||
<envs />
|
<envs />
|
||||||
<option name="emulateTerminal" value="true" />
|
<option name="emulateTerminal" value="true" />
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<component name="ProjectRunConfigurationManager">
|
<component name="ProjectRunConfigurationManager">
|
||||||
<configuration default="false" name="Run sensor panel" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
|
<configuration default="false" name="Run sensor panel" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
|
||||||
<option name="buildProfileId" value="dev" />
|
<option name="buildProfileId" value="dev" />
|
||||||
<option name="command" value="run --package aoostar-rs --bin asterctl -- -c monitor.json" />
|
<option name="command" value="run --bin asterctl -- -c monitor.json" />
|
||||||
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
|
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
|
||||||
<envs />
|
<envs />
|
||||||
<option name="emulateTerminal" value="true" />
|
<option name="emulateTerminal" value="true" />
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<component name="ProjectRunConfigurationManager">
|
<component name="ProjectRunConfigurationManager">
|
||||||
<configuration default="false" name="Run sensor panel DEV" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
|
<configuration default="false" name="Run sensor panel DEV" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
|
||||||
<option name="buildProfileId" value="dev" />
|
<option name="buildProfileId" value="dev" />
|
||||||
<option name="command" value="run --package aoostar-rs --bin asterctl -- -c monitor.json --save --simulate" />
|
<option name="command" value="run --bin asterctl -- -c monitor.json --save --simulate" />
|
||||||
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
|
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
|
||||||
<envs />
|
<envs />
|
||||||
<option name="emulateTerminal" value="true" />
|
<option name="emulateTerminal" value="true" />
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<component name="ProjectRunConfigurationManager">
|
<component name="ProjectRunConfigurationManager">
|
||||||
<configuration default="false" name="Run sysinfo" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
|
<configuration default="false" name="Run sysinfo" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
|
||||||
<option name="buildProfileId" value="dev" />
|
<option name="buildProfileId" value="dev" />
|
||||||
<option name="command" value="run --package aoostar-rs --bin sysinfo -- --console --out ./cfg/sensors/sysinfo.txt" />
|
q <option name="command" value="run --bin sysinfo -- --console --out ./cfg/sensors/sysinfo.txt" />
|
||||||
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
|
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
|
||||||
<envs />
|
<envs />
|
||||||
<option name="emulateTerminal" value="true" />
|
<option name="emulateTerminal" value="true" />
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<component name="ProjectRunConfigurationManager">
|
<component name="ProjectRunConfigurationManager">
|
||||||
<configuration default="false" name="Run sysinfo repeat" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
|
<configuration default="false" name="Run sysinfo repeat" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
|
||||||
<option name="buildProfileId" value="dev" />
|
<option name="buildProfileId" value="dev" />
|
||||||
<option name="command" value="run --package aoostar-rs --bin sysinfo -- --console --out ./cfg/sensors/sysinfo.txt --refresh 3" />
|
<option name="command" value="run --bin sysinfo -- --console --out ./cfg/sensors/sysinfo.txt --refresh 3" />
|
||||||
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
|
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
|
||||||
<envs />
|
<envs />
|
||||||
<option name="emulateTerminal" value="true" />
|
<option name="emulateTerminal" value="true" />
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
<component name="ProjectRunConfigurationManager">
|
|
||||||
<configuration default="false" name="clippy sysinfo" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
|
|
||||||
<option name="command" value="clippy --bin sysinfo --features=sysinfo" />
|
|
||||||
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
|
|
||||||
<envs />
|
|
||||||
<option name="emulateTerminal" value="true" />
|
|
||||||
<option name="channel" value="DEFAULT" />
|
|
||||||
<option name="requiredFeatures" value="true" />
|
|
||||||
<option name="allFeatures" value="false" />
|
|
||||||
<option name="withSudo" value="false" />
|
|
||||||
<option name="buildTarget" value="REMOTE" />
|
|
||||||
<option name="backtrace" value="SHORT" />
|
|
||||||
<option name="isRedirectInput" value="false" />
|
|
||||||
<option name="redirectInputPath" value="" />
|
|
||||||
<method v="2">
|
|
||||||
<option name="CARGO.BUILD_TASK_PROVIDER" enabled="true" />
|
|
||||||
</method>
|
|
||||||
</configuration>
|
|
||||||
</component>
|
|
||||||
@@ -8,6 +8,6 @@
|
|||||||
</profile>
|
</profile>
|
||||||
</component>
|
</component>
|
||||||
<component name="VcsDirectoryMappings">
|
<component name="VcsDirectoryMappings">
|
||||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
<mapping directory="" vcs="Git" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
@@ -1,12 +1,16 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<module type="RUST_MODULE" version="4">
|
<module version="4">
|
||||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||||
<exclude-output />
|
<exclude-output />
|
||||||
<content url="file://$MODULE_DIR$">
|
<content url="file://$MODULE_DIR$">
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
<sourceFolder url="file://$MODULE_DIR$/crates/asterctl-lcd/src" isTestSource="false" />
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/crates/asterctl/examples" isTestSource="false" />
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/crates/asterctl/src" isTestSource="false" />
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/crates/sysinfo/src" isTestSource="false" />
|
||||||
<excludeFolder url="file://$MODULE_DIR$/target" />
|
<excludeFolder url="file://$MODULE_DIR$/target" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/crates/asterctl-lcd/target" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/crates/sysinfo/target" />
|
||||||
</content>
|
</content>
|
||||||
<orderEntry type="inheritedJdk" />
|
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
</component>
|
</component>
|
||||||
</module>
|
</module>
|
||||||
@@ -11,6 +11,10 @@ _Changes in the next release_
|
|||||||
|
|
||||||
### Added
|
### Added
|
||||||
- Simple sensor panel with a file-based data source (#6)
|
- Simple sensor panel with a file-based data source (#6)
|
||||||
|
- Initial support for fan-, progress-, & pointer-sensors (#8)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Project structure using a Cargo workspace
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -98,31 +98,6 @@ version = "1.0.99"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100"
|
checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "aoostar-rs"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"ab_glyph",
|
|
||||||
"anyhow",
|
|
||||||
"bytes",
|
|
||||||
"clap",
|
|
||||||
"env_logger",
|
|
||||||
"image",
|
|
||||||
"imageproc",
|
|
||||||
"itertools 0.14.0",
|
|
||||||
"log",
|
|
||||||
"notify",
|
|
||||||
"once_cell",
|
|
||||||
"regex",
|
|
||||||
"rstest",
|
|
||||||
"serde",
|
|
||||||
"serde_json",
|
|
||||||
"serde_repr",
|
|
||||||
"serialport",
|
|
||||||
"sysinfo",
|
|
||||||
"tempfile",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "approx"
|
name = "approx"
|
||||||
version = "0.5.1"
|
version = "0.5.1"
|
||||||
@@ -155,6 +130,37 @@ version = "0.7.6"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
|
checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "asterctl"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"ab_glyph",
|
||||||
|
"anyhow",
|
||||||
|
"asterctl-lcd",
|
||||||
|
"clap",
|
||||||
|
"env_logger",
|
||||||
|
"image",
|
||||||
|
"imageproc",
|
||||||
|
"log",
|
||||||
|
"notify",
|
||||||
|
"once_cell",
|
||||||
|
"rstest",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"serde_repr",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "asterctl-lcd"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"bytes",
|
||||||
|
"image",
|
||||||
|
"log",
|
||||||
|
"serialport",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "autocfg"
|
name = "autocfg"
|
||||||
version = "1.5.0"
|
version = "1.5.0"
|
||||||
@@ -198,9 +204,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "2.9.2"
|
version = "2.9.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6a65b545ab31d687cff52899d4890855fec459eb6afe0da6417b8a18da87aa29"
|
checksum = "34efbcccd345379ca2868b2b2c9d3782e9cc58ba87bc7d79d5b53d9c9ae6f25d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitstream-io"
|
name = "bitstream-io"
|
||||||
@@ -240,9 +246,9 @@ checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.2.33"
|
version = "1.2.34"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3ee0f8803222ba5a7e2777dd72ca451868909b1ac410621b676adf07280e9b5f"
|
checksum = "42bc4aea80032b7bf409b0bc7ccad88853858911b7713a8062fdc0623867bedc"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"jobserver",
|
"jobserver",
|
||||||
"libc",
|
"libc",
|
||||||
@@ -651,9 +657,9 @@ checksum = "d0263a3d970d5c054ed9312c0057b4f3bde9c0b33836d3637361d4a9e6e7a408"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "indexmap"
|
name = "indexmap"
|
||||||
version = "2.10.0"
|
version = "2.11.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661"
|
checksum = "f2481980430f9f78649238835720ddccc57e52df14ffce1c6f37391d61b563e9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"equivalent",
|
"equivalent",
|
||||||
"hashbrown",
|
"hashbrown",
|
||||||
@@ -665,7 +671,7 @@ version = "0.11.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f37dccff2791ab604f9babef0ba14fbe0be30bd368dc541e2b08d07c8aa908f3"
|
checksum = "f37dccff2791ab604f9babef0ba14fbe0be30bd368dc541e2b08d07c8aa908f3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.9.2",
|
"bitflags 2.9.3",
|
||||||
"inotify-sys",
|
"inotify-sys",
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
@@ -756,9 +762,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "jobserver"
|
name = "jobserver"
|
||||||
version = "0.1.33"
|
version = "0.1.34"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a"
|
checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"getrandom 0.3.3",
|
"getrandom 0.3.3",
|
||||||
"libc",
|
"libc",
|
||||||
@@ -986,7 +992,7 @@ version = "8.2.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4d3d07927151ff8575b7087f245456e549fea62edf0ec4e565a5ee50c8402bc3"
|
checksum = "4d3d07927151ff8575b7087f245456e549fea62edf0ec4e565a5ee50c8402bc3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.9.2",
|
"bitflags 2.9.3",
|
||||||
"fsevent-sys",
|
"fsevent-sys",
|
||||||
"inotify",
|
"inotify",
|
||||||
"kqueue",
|
"kqueue",
|
||||||
@@ -1104,7 +1110,7 @@ version = "0.3.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166"
|
checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.9.2",
|
"bitflags 2.9.3",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1384,9 +1390,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex"
|
name = "regex"
|
||||||
version = "1.11.1"
|
version = "1.11.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
|
checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aho-corasick",
|
"aho-corasick",
|
||||||
"memchr",
|
"memchr",
|
||||||
@@ -1396,9 +1402,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex-automata"
|
name = "regex-automata"
|
||||||
version = "0.4.9"
|
version = "0.4.10"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
|
checksum = "6b9458fa0bfeeac22b5ca447c63aaf45f28439a709ccd244698632f9aa6394d6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aho-corasick",
|
"aho-corasick",
|
||||||
"memchr",
|
"memchr",
|
||||||
@@ -1407,9 +1413,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex-syntax"
|
name = "regex-syntax"
|
||||||
version = "0.8.5"
|
version = "0.8.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
|
checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "relative-path"
|
name = "relative-path"
|
||||||
@@ -1467,7 +1473,7 @@ version = "1.0.8"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8"
|
checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.9.2",
|
"bitflags 2.9.3",
|
||||||
"errno",
|
"errno",
|
||||||
"libc",
|
"libc",
|
||||||
"linux-raw-sys",
|
"linux-raw-sys",
|
||||||
@@ -1570,11 +1576,11 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serialport"
|
name = "serialport"
|
||||||
version = "4.7.2"
|
version = "4.7.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cdb0bc984f6af6ef8bab54e6cf2071579ee75b9286aa9f2319a0d220c28b0a2b"
|
checksum = "2acaf3f973e8616d7ceac415f53fc60e190b2a686fbcf8d27d0256c741c5007b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.9.2",
|
"bitflags 2.9.3",
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"core-foundation",
|
"core-foundation",
|
||||||
"core-foundation-sys",
|
"core-foundation-sys",
|
||||||
@@ -1650,6 +1656,19 @@ dependencies = [
|
|||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sysinfo"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"clap",
|
||||||
|
"env_logger",
|
||||||
|
"itertools 0.14.0",
|
||||||
|
"log",
|
||||||
|
"regex",
|
||||||
|
"sysinfo 0.37.0",
|
||||||
|
"tempfile",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sysinfo"
|
name = "sysinfo"
|
||||||
version = "0.37.0"
|
version = "0.37.0"
|
||||||
@@ -2221,9 +2240,9 @@ checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winnow"
|
name = "winnow"
|
||||||
version = "0.7.12"
|
version = "0.7.13"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95"
|
checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
@@ -2234,7 +2253,7 @@ version = "0.39.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
|
checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.9.2",
|
"bitflags 2.9.3",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|||||||
@@ -1,45 +1,10 @@
|
|||||||
[package]
|
[workspace]
|
||||||
name = "aoostar-rs"
|
members = ["crates/*"]
|
||||||
version = "0.1.0"
|
resolver = "3"
|
||||||
edition = "2024"
|
|
||||||
|
[workspace.package]
|
||||||
rust-version = "1.88"
|
rust-version = "1.88"
|
||||||
|
edition = "2024"
|
||||||
authors = ["Markus Zehnder"]
|
authors = ["Markus Zehnder"]
|
||||||
license = "MIT or Apache-2.0"
|
license = "MIT or Apache-2.0"
|
||||||
|
repository = "https://github.com/zehnm/aoostar-rs"
|
||||||
[profile.release]
|
|
||||||
strip = true # Automatically strip symbols from the binary.
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = "asterctl"
|
|
||||||
path = "src/main.rs"
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = "sysinfo"
|
|
||||||
path = "src/bin/sysinfo.rs"
|
|
||||||
required-features = ["sysinfo"]
|
|
||||||
|
|
||||||
[features]
|
|
||||||
sysinfo = ["dep:sysinfo"]
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
anyhow = "1.0.98"
|
|
||||||
bytes = "1.10.1"
|
|
||||||
clap = { version = "4.5.42", features = ["derive"] }
|
|
||||||
serialport = "4.7.2"
|
|
||||||
image = "0.25.6"
|
|
||||||
imageproc = { version = "0.25.0", default-features = false }
|
|
||||||
ab_glyph = { version = "0.2.31", default-features = false, features = ["std"] }
|
|
||||||
log = "0.4.27"
|
|
||||||
env_logger = "0.11.8"
|
|
||||||
notify = "8.2.0"
|
|
||||||
regex = "1.11"
|
|
||||||
serde = { version = "1.0.219", features = ["derive"] }
|
|
||||||
serde_json = "1.0.142"
|
|
||||||
serde_repr = "0.1.20"
|
|
||||||
once_cell = "1.21.3"
|
|
||||||
sysinfo = { version = "0.37.0", optional = true }
|
|
||||||
itertools = "0.14"
|
|
||||||
tempfile = "3"
|
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
rstest = "0.26"
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
# AOOSTAR WTR MAX Screen Control
|
# AOOSTAR WTR MAX / GEM12+ PRO Screen Control
|
||||||
|
|
||||||
Reverse engineering the [AOOSTAR WTR MAX](https://aoostar.com/products/aoostar-wtr-max-amd-r7-pro-8845hs-11-bays-mini-pc)
|
Reverse engineering the [AOOSTAR WTR MAX](https://aoostar.com/products/aoostar-wtr-max-amd-r7-pro-8845hs-11-bays-mini-pc)
|
||||||
display protocol, with a proof-of-concept application written in Rust.
|
display protocol, with a proof-of-concept application written in Rust.
|
||||||
This project should also support the GEM12+ PRO device.
|
It has only been tested on the WTR MAX, but should also support the GEM12+ PRO device.
|
||||||
|
|
||||||
**Disclaimer: ‼️ EXPERIMENTAL — use at your own risk ‼️**
|
**Disclaimer: ‼️ EXPERIMENTAL — use at your own risk ‼️**
|
||||||
|
|
||||||
@@ -10,6 +10,8 @@ This project should also support the GEM12+ PRO device.
|
|||||||
> There is no official documentation available;
|
> There is no official documentation available;
|
||||||
> all display control commands have been reverse engineered from the original AOOSTAR-X software.
|
> all display control commands have been reverse engineered from the original AOOSTAR-X software.
|
||||||
|
|
||||||
|
Even though this software works fine **for me**, I cannot guarantee that it is risk-free:
|
||||||
|
|
||||||
- It may or may not work.
|
- It may or may not work.
|
||||||
- It could crash the display firmware, requiring a power cycle.
|
- It could crash the display firmware, requiring a power cycle.
|
||||||
- It could even brick the display firmware.
|
- It could even brick the display firmware.
|
||||||
@@ -20,14 +22,14 @@ Note: Multiple attempts to contact the manufacturer for documentation have recei
|
|||||||
|
|
||||||
With that out of the way, on to the fun stuff!
|
With that out of the way, on to the fun stuff!
|
||||||
|
|
||||||
**See [Linux shell commands](doc/shell_commands.md) on how to switch off the display with standard Linux commands!**
|
**See [Linux shell commands](docs/shell_commands.md) on how to switch off the display with standard Linux commands!**
|
||||||
|
|
||||||
See [releases](https://github.com/zehnm/aoostar-rs/releases) for binary Linux x64 releases and [Linux systemd Service](linux/)
|
See [releases](https://github.com/zehnm/aoostar-rs/releases) for binary Linux x64 releases and [Linux systemd Service](linux/)
|
||||||
on how to automatically switch off the LCD at boot up. A Debian package for easy installation is planned for the future!
|
on how to automatically switch off the LCD at boot up. A Debian package for easy installation is planned for the future!
|
||||||
|
|
||||||
## Reverse Engineering
|
## Reverse Engineering
|
||||||
|
|
||||||
Reverse engineered LCD commands: [doc/lcd_protocol.md](doc/lcd_protocol.md)
|
Reverse engineered LCD commands: [docs/lcd_protocol.md](docs/lcd_protocol.md)
|
||||||
|
|
||||||
### Motivation
|
### Motivation
|
||||||
|
|
||||||
@@ -50,7 +52,7 @@ The display remains on continuously (24×7) if the official software is not runn
|
|||||||
- [x] Reverse engineer the LCD serial protocol to provide open screen software.
|
- [x] Reverse engineer the LCD serial protocol to provide open screen software.
|
||||||
- Utilize the official AOOSTAR-X display software by sniffing USB communication, using `strace`, and decompiling the Python app.
|
- Utilize the official AOOSTAR-X display software by sniffing USB communication, using `strace`, and decompiling the Python app.
|
||||||
- [x] Document known commands so clients in other programming languages can be written.
|
- [x] Document known commands so clients in other programming languages can be written.
|
||||||
- [ ] Eventually, create a Rust crate for easy integration into other Rust applications.
|
- [ ] Eventually, publish a Rust crate for easy integration into other Rust applications.
|
||||||
|
|
||||||
**Out of scope:**
|
**Out of scope:**
|
||||||
|
|
||||||
@@ -63,7 +65,9 @@ The display remains on continuously (24×7) if the official software is not runn
|
|||||||
- Control the AOOSTAR WTR MAX and GEM12+ PRO second screen from Linux.
|
- Control the AOOSTAR WTR MAX and GEM12+ PRO second screen from Linux.
|
||||||
- Switch the display on or off.
|
- Switch the display on or off.
|
||||||
- Display images (with automatic scaling and partial update support).
|
- Display images (with automatic scaling and partial update support).
|
||||||
- Proof-of-concept demo for drawing shapes and text.
|
- Render dynamic sensor panels defined from the AOOSTAR-X software.
|
||||||
|
- Update sensor values from simple text files.
|
||||||
|
- Rotate through multiple panels in a defined interval.
|
||||||
- USB device/serial port selection.
|
- USB device/serial port selection.
|
||||||
|
|
||||||
## Setup
|
## Setup
|
||||||
@@ -87,21 +91,19 @@ cd aoostar-rs
|
|||||||
|
|
||||||
### Build
|
### Build
|
||||||
|
|
||||||
A release build is highly recommended, as it significantly improves graphics performance:
|
A release build is highly recommended, as it significantly improves graphic rendering performance:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
cargo build --release --bins --all-features
|
cargo build --release
|
||||||
```
|
```
|
||||||
|
|
||||||
The `--bins` option builds the main `asterctl` app and all other tools.
|
|
||||||
|
|
||||||
### Install
|
### Install
|
||||||
|
|
||||||
See [Linux systemd Service](linux/) on how to automatically switch off the LCD at boot up.
|
See [Linux systemd Service](linux/) on how to automatically switch off the LCD at boot up.
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
See [asterctl documentation](doc/README.md) for more information or run `asterctl --help` for available command line options.
|
See [asterctl documentation](docs/README.md) for more information or run `asterctl --help` for available command line options.
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
cpu_temperature: 65
|
cpu_temperature: 65
|
||||||
cpu_percent: 98
|
cpu_percent: 47.7
|
||||||
memory_usage: 77
|
memory_usage: 77
|
||||||
memory_Temperature: 48
|
memory_Temperature: 48
|
||||||
net_ip_address: 146.56.182.244
|
net_ip_address: 146.56.182.244
|
||||||
@@ -17,9 +17,9 @@ storage_ssd[1]['used']: 18
|
|||||||
storage_ssd[2]['temperature']: 33
|
storage_ssd[2]['temperature']: 33
|
||||||
storage_ssd[2]['used']: 19
|
storage_ssd[2]['used']: 19
|
||||||
storage_ssd[3]['temperature']: 34
|
storage_ssd[3]['temperature']: 34
|
||||||
storage_ssd[3]['used']: 20
|
storage_ssd[3]['used']: 35
|
||||||
storage_ssd[4]['temperature']: 35
|
storage_ssd[4]['temperature']: 35
|
||||||
storage_ssd[4]['used']: 21
|
storage_ssd[4]['used']: 80
|
||||||
storage_hdd[0]['temperature']: 36
|
storage_hdd[0]['temperature']: 36
|
||||||
storage_hdd[0]['used']: 22
|
storage_hdd[0]['used']: 22
|
||||||
storage_hdd[1]['temperature']: 37
|
storage_hdd[1]['temperature']: 37
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
[package]
|
||||||
|
name = "asterctl-lcd"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "AOOSTAR WTR MAX / GEM12+ PRO screen protocol"
|
||||||
|
|
||||||
|
rust-version.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
authors.workspace = true
|
||||||
|
license.workspace = true
|
||||||
|
repository.workspace = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
anyhow = "1.0.98"
|
||||||
|
bytes = "1.10.1"
|
||||||
|
# TODO make image an optional feature
|
||||||
|
image = "0.25.6"
|
||||||
|
log = "0.4.27"
|
||||||
|
serialport = "4.7.3"
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
../../LICENSE-APACHE
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
../../LICENSE-MIT
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
# AOOSTAR WTR MAX / GEM12+ PRO UART Screen Protocol
|
||||||
|
|
||||||
|
Reverse engineered [AOOSTAR WTR MAX](https://aoostar.com/products/aoostar-wtr-max-amd-r7-pro-8845hs-11-bays-mini-pc)
|
||||||
|
UART display protocol, written in Rust.
|
||||||
|
This project should also support the GEM12+ PRO device.
|
||||||
|
|
||||||
|
- [LCD Protocol](../../docs/lcd_protocol.md)
|
||||||
|
- See [README](../../README.md) for more information about the `asterctl` screen control tool.
|
||||||
|
|
||||||
|
## Display Information
|
||||||
|
|
||||||
|
- **Resolution:** 960 × 376
|
||||||
|
- **Manufacturer:** Synwit
|
||||||
|
- **Connected over USB UART** with a proprietary serial communication protocol:
|
||||||
|
- **USB device ID:** `416:90A1` (as shown by `lsusb`)
|
||||||
|
- **Linux device (example on Debian):** `/dev/ttyACM0`
|
||||||
|
- **1,500,000 baud**, 8N1 (likely ignored; actual USB transfer speed is much higher)
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||||
// SPDX-FileCopyrightText: Copyright (c) 2025 Markus Zehnder
|
// SPDX-FileCopyrightText: Copyright (c) 2025 Markus Zehnder
|
||||||
|
|
||||||
use crate::dummy_serialport::DummySerialPort;
|
use crate::FakeSerialPort;
|
||||||
use crate::img::ToRgb565;
|
use crate::ToRgb565;
|
||||||
|
|
||||||
use anyhow::{Context, anyhow};
|
use anyhow::{Context, anyhow};
|
||||||
use bytes::{BufMut, BytesMut};
|
use bytes::{BufMut, BytesMut};
|
||||||
use log::{debug, error, info, warn};
|
use log::{debug, error, info, warn};
|
||||||
@@ -68,7 +69,7 @@ impl AooScreenBuilder {
|
|||||||
/// Simulate the LCD device. No real device or serial port is required.
|
/// Simulate the LCD device. No real device or serial port is required.
|
||||||
pub fn simulate(self) -> anyhow::Result<AooScreen> {
|
pub fn simulate(self) -> anyhow::Result<AooScreen> {
|
||||||
Ok(AooScreen {
|
Ok(AooScreen {
|
||||||
port: Some(Box::new(DummySerialPort::new())),
|
port: Some(Box::new(FakeSerialPort::new())),
|
||||||
enable_cache: self.enable_cache.unwrap_or(true),
|
enable_cache: self.enable_cache.unwrap_or(true),
|
||||||
prev_frame: None,
|
prev_frame: None,
|
||||||
no_init_check: self.no_init_check.unwrap_or(false),
|
no_init_check: self.no_init_check.unwrap_or(false),
|
||||||
@@ -5,7 +5,7 @@ use serialport::{ClearBuffer, DataBits, FlowControl, Parity, SerialPort, StopBit
|
|||||||
use std::thread::sleep;
|
use std::thread::sleep;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
pub struct DummySerialPort {
|
pub struct FakeSerialPort {
|
||||||
baud_rate: u32,
|
baud_rate: u32,
|
||||||
data_bits: DataBits,
|
data_bits: DataBits,
|
||||||
flow_control: FlowControl,
|
flow_control: FlowControl,
|
||||||
@@ -14,8 +14,14 @@ pub struct DummySerialPort {
|
|||||||
timeout: Duration,
|
timeout: Duration,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DummySerialPort {
|
impl Default for FakeSerialPort {
|
||||||
pub fn new() -> DummySerialPort {
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FakeSerialPort {
|
||||||
|
pub fn new() -> FakeSerialPort {
|
||||||
Self {
|
Self {
|
||||||
baud_rate: 1_500_000,
|
baud_rate: 1_500_000,
|
||||||
data_bits: DataBits::Eight,
|
data_bits: DataBits::Eight,
|
||||||
@@ -27,14 +33,14 @@ impl DummySerialPort {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::io::Read for DummySerialPort {
|
impl std::io::Read for FakeSerialPort {
|
||||||
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
|
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
|
||||||
buf[0] = b'A';
|
buf[0] = b'A';
|
||||||
Ok(1)
|
Ok(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::io::Write for DummySerialPort {
|
impl std::io::Write for FakeSerialPort {
|
||||||
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
|
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
|
||||||
// just some approximation, additional overhead like flushing etc is not considered
|
// just some approximation, additional overhead like flushing etc is not considered
|
||||||
let byte_rate =
|
let byte_rate =
|
||||||
@@ -49,7 +55,7 @@ impl std::io::Write for DummySerialPort {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SerialPort for DummySerialPort {
|
impl SerialPort for FakeSerialPort {
|
||||||
fn name(&self) -> Option<String> {
|
fn name(&self) -> Option<String> {
|
||||||
Some("Dummy Serial".into())
|
Some("Dummy Serial".into())
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||||
|
// SPDX-FileCopyrightText: Copyright (c) 2025 Markus Zehnder
|
||||||
|
|
||||||
|
#![forbid(non_ascii_idents)]
|
||||||
|
#![deny(unsafe_code)]
|
||||||
|
|
||||||
|
use bytes::{BufMut, BytesMut};
|
||||||
|
use image::{RgbImage, RgbaImage};
|
||||||
|
|
||||||
|
mod aoo_screen;
|
||||||
|
mod fake_serialport;
|
||||||
|
|
||||||
|
pub use aoo_screen::{AooScreen, AooScreenBuilder, DISPLAY_SIZE};
|
||||||
|
pub use fake_serialport::FakeSerialPort;
|
||||||
|
|
||||||
|
/// Trait definition to get a RGB 565 representation from a source image.
|
||||||
|
pub trait ToRgb565 {
|
||||||
|
/// Get an RGB 565 representation of the image in little endian format.
|
||||||
|
fn to_rgb565_le(&self) -> BytesMut;
|
||||||
|
|
||||||
|
/// Convert a single RGB 888 pixel to 16 bit RGB 565 format.
|
||||||
|
fn convert_rgb(&self, r: u8, g: u8, b: u8) -> u16 {
|
||||||
|
((r & 248) as u16) << 8 | ((g & 252) as u16) << 3 | ((b as u16) >> 3)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO quick & dirty approach for converting RgbImage & RgbaImage to RGB 565.
|
||||||
|
// There should be a more generic way, maybe with PixelEnumerator...
|
||||||
|
impl ToRgb565 for &RgbImage {
|
||||||
|
fn to_rgb565_le(&self) -> BytesMut {
|
||||||
|
let mut img_rgb565 =
|
||||||
|
BytesMut::with_capacity(self.width() as usize * self.height() as usize * 2);
|
||||||
|
|
||||||
|
for (_x, _y, pixel) in self.enumerate_pixels() {
|
||||||
|
img_rgb565.put_u16_le(self.convert_rgb(pixel.0[0], pixel.0[1], pixel.0[2]));
|
||||||
|
}
|
||||||
|
|
||||||
|
img_rgb565
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToRgb565 for &RgbaImage {
|
||||||
|
fn to_rgb565_le(&self) -> BytesMut {
|
||||||
|
let mut img_rgb565 =
|
||||||
|
BytesMut::with_capacity(self.width() as usize * self.height() as usize * 2);
|
||||||
|
|
||||||
|
for (_x, _y, pixel) in self.enumerate_pixels() {
|
||||||
|
img_rgb565.put_u16_le(self.convert_rgb(pixel.0[0], pixel.0[1], pixel.0[2]));
|
||||||
|
}
|
||||||
|
|
||||||
|
img_rgb565
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
[package]
|
||||||
|
name = "asterctl"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "AOOSTAR WTR MAX Screen Control tool"
|
||||||
|
readme = "../../README.md"
|
||||||
|
|
||||||
|
rust-version.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
authors.workspace = true
|
||||||
|
license.workspace = true
|
||||||
|
repository.workspace = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
asterctl-lcd = { path = "../asterctl-lcd", version = "0.1.0" }
|
||||||
|
|
||||||
|
anyhow = "1.0.98"
|
||||||
|
clap = { version = "4.5.42", features = ["derive"] }
|
||||||
|
image = "0.25.6"
|
||||||
|
imageproc = { version = "0.25.0", default-features = false }
|
||||||
|
ab_glyph = { version = "0.2.31", default-features = false, features = ["std"] }
|
||||||
|
log = "0.4.27"
|
||||||
|
env_logger = "0.11.8"
|
||||||
|
notify = "8.2.0"
|
||||||
|
serde = { version = "1.0.219", features = ["derive"] }
|
||||||
|
serde_json = "1.0.142"
|
||||||
|
serde_repr = "0.1.20"
|
||||||
|
once_cell = "1.21.3"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
rstest = "0.26"
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
../../LICENSE-APACHE
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
../../LICENSE-MIT
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
# Screen control tool for AOOSTAR WTR MAX / GEM12+ PRO
|
||||||
|
|
||||||
|
See [README](../../README.md) in root directory for more information.
|
||||||
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
@@ -0,0 +1,262 @@
|
|||||||
|
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||||
|
// SPDX-FileCopyrightText: Copyright (c) 2025 Markus Zehnder
|
||||||
|
|
||||||
|
use asterctl::cfg;
|
||||||
|
use asterctl::font::FontHandler;
|
||||||
|
use asterctl::render::PanelRenderer;
|
||||||
|
use asterctl_lcd::{AooScreen, AooScreenBuilder, DISPLAY_SIZE};
|
||||||
|
|
||||||
|
use ab_glyph::PxScale;
|
||||||
|
use clap::Parser;
|
||||||
|
use env_logger::Env;
|
||||||
|
use image::imageops::FilterType;
|
||||||
|
use image::{ImageReader, Rgb, RgbImage};
|
||||||
|
use imageproc::drawing::{draw_line_segment_mut, draw_text_mut};
|
||||||
|
use log::{error, info};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::fs;
|
||||||
|
use std::io::Cursor;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::thread::sleep;
|
||||||
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
|
/// AOOSTAR WTR MAX and GEM12+ PRO screen control demo.
|
||||||
|
#[derive(Parser, Debug)]
|
||||||
|
#[command(version, about, long_about = None)]
|
||||||
|
struct Args {
|
||||||
|
/// Serial device, for example "/dev/cu.usbserial-AB0KOHLS". Takes priority over --usb option.
|
||||||
|
#[arg(short, long)]
|
||||||
|
device: Option<String>,
|
||||||
|
|
||||||
|
/// USB serial UART "vid:pid" in hex notation (lsusb output). Default: 416:90A1
|
||||||
|
#[arg(short, long)]
|
||||||
|
usb: Option<String>,
|
||||||
|
|
||||||
|
/// AOOSTAR-X json configuration file to parse.
|
||||||
|
///
|
||||||
|
/// The configuration file will be loaded from the `config_dir` directory if no full path is
|
||||||
|
/// specified.
|
||||||
|
#[arg(short, long)]
|
||||||
|
config: Option<PathBuf>,
|
||||||
|
|
||||||
|
/// Configuration directory containing configuration files and background images
|
||||||
|
/// specified in the `config` file. Default: `./cfg`
|
||||||
|
#[arg(long)]
|
||||||
|
config_dir: Option<PathBuf>,
|
||||||
|
|
||||||
|
/// Font directory for fonts specified in the `config` file. Default: `./fonts`
|
||||||
|
#[arg(long)]
|
||||||
|
font_dir: Option<PathBuf>,
|
||||||
|
|
||||||
|
/// Switch off display n seconds after loading image.
|
||||||
|
#[arg(short, long)]
|
||||||
|
off_after: Option<u32>,
|
||||||
|
|
||||||
|
/// Test mode: only write to the display without checking response.
|
||||||
|
#[arg(short, long)]
|
||||||
|
write_only: bool,
|
||||||
|
|
||||||
|
/// Test mode: save changed images in ./out folder.
|
||||||
|
#[arg(short, long)]
|
||||||
|
save: bool,
|
||||||
|
|
||||||
|
/// Simulate serial port for testing and development, `--device` and `--usb` options are ignored.
|
||||||
|
#[arg(long)]
|
||||||
|
simulate: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() -> anyhow::Result<()> {
|
||||||
|
env_logger::Builder::from_env(Env::default().default_filter_or("info")).init();
|
||||||
|
|
||||||
|
let args = Args::parse();
|
||||||
|
|
||||||
|
// initialize display with given UART port parameter
|
||||||
|
let mut builder = AooScreenBuilder::new();
|
||||||
|
builder.no_init_check(args.write_only);
|
||||||
|
let mut screen = if args.simulate {
|
||||||
|
builder.simulate()?
|
||||||
|
} else if let Some(device) = args.device {
|
||||||
|
builder.open_device(&device)?
|
||||||
|
} else if let Some(usb) = args.usb {
|
||||||
|
builder.open_usb_id(&usb)?
|
||||||
|
} else {
|
||||||
|
builder.open_default()?
|
||||||
|
};
|
||||||
|
|
||||||
|
info!("Loading and displaying demo...");
|
||||||
|
run_demo(
|
||||||
|
&mut screen,
|
||||||
|
args.config.as_deref(),
|
||||||
|
args.config_dir.unwrap_or_else(|| "cfg".into()),
|
||||||
|
args.font_dir.unwrap_or_else(|| "fonts".into()),
|
||||||
|
args.save,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
if let Some(off) = args.off_after {
|
||||||
|
info!("Switching off display in {off}s");
|
||||||
|
sleep(Duration::from_secs(off as u64));
|
||||||
|
screen.off()?;
|
||||||
|
}
|
||||||
|
|
||||||
|
info!("Bye bye!");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_demo(
|
||||||
|
screen: &mut AooScreen,
|
||||||
|
config: Option<&Path>,
|
||||||
|
config_dir: PathBuf,
|
||||||
|
font_dir: PathBuf,
|
||||||
|
save_images: bool,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
let rgb_img = demo_image()?;
|
||||||
|
|
||||||
|
// fill left and right side of the loaded image with neighboring pixel color
|
||||||
|
const WIDTH: u32 = 108;
|
||||||
|
let rgb_img = demo_blinds(screen, &rgb_img, WIDTH, save_images)?;
|
||||||
|
|
||||||
|
// print demo text over background image
|
||||||
|
demo_text(screen, &rgb_img, save_images)?;
|
||||||
|
|
||||||
|
if let Some(config) = config {
|
||||||
|
let mut cfg = if config.is_absolute() {
|
||||||
|
cfg::load_cfg(config)?
|
||||||
|
} else {
|
||||||
|
cfg::load_cfg(config_dir.join(config))?
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(panel) = cfg.get_next_active_panel() {
|
||||||
|
info!("Displaying demo panel...");
|
||||||
|
|
||||||
|
// get sensor values from panel configuration
|
||||||
|
let mut demo_values = HashMap::new();
|
||||||
|
for sensor in &panel.sensor {
|
||||||
|
demo_values.insert(
|
||||||
|
sensor.label.clone(),
|
||||||
|
sensor.value.clone().unwrap_or_default(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut renderer = PanelRenderer::new(DISPLAY_SIZE, &font_dir, &config_dir);
|
||||||
|
renderer.set_save_render_img(save_images);
|
||||||
|
renderer.set_save_processed_pic(save_images);
|
||||||
|
renderer.set_save_progress_layer(save_images);
|
||||||
|
|
||||||
|
match renderer.render(panel, &demo_values) {
|
||||||
|
Ok(image) => screen.send_image(&image)?,
|
||||||
|
Err(e) => error!("Error rendering panel '{}': {e:?}", panel.friendly_name()),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
error!("No active panel found");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn demo_image() -> anyhow::Result<RgbImage> {
|
||||||
|
let reader = ImageReader::new(Cursor::new(include_bytes!("aybabtu.png")))
|
||||||
|
.with_guessed_format()
|
||||||
|
.expect("Cursor io never fails");
|
||||||
|
|
||||||
|
Ok(reader
|
||||||
|
.decode()?
|
||||||
|
.resize_exact(DISPLAY_SIZE.0, DISPLAY_SIZE.1, FilterType::Lanczos3)
|
||||||
|
.to_rgb8())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn demo_text(
|
||||||
|
screen: &mut AooScreen,
|
||||||
|
background: &RgbImage,
|
||||||
|
save_images: bool,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
let text = "ALL YOUR BASE ARE BELONG TO US.";
|
||||||
|
let text_upd_delay = Duration::from_millis(0);
|
||||||
|
let font = FontHandler::default_font();
|
||||||
|
let height = 36.0;
|
||||||
|
let scale = PxScale {
|
||||||
|
x: height,
|
||||||
|
y: height,
|
||||||
|
};
|
||||||
|
|
||||||
|
if save_images {
|
||||||
|
fs::create_dir_all("out")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
for text_idx in 0..text.len() {
|
||||||
|
info!("Printing: {}", &text[0..text_idx + 1]);
|
||||||
|
let text_upd = Instant::now();
|
||||||
|
let mut rgb_img = background.clone();
|
||||||
|
draw_text_mut(
|
||||||
|
&mut rgb_img,
|
||||||
|
Rgb([118u8, 118u8, 97u8]),
|
||||||
|
4 * 47,
|
||||||
|
300,
|
||||||
|
scale,
|
||||||
|
&font,
|
||||||
|
&text[0..text_idx + 1],
|
||||||
|
);
|
||||||
|
|
||||||
|
if save_images {
|
||||||
|
rgb_img.save_with_format(
|
||||||
|
format!("out/demo_text-{text_idx}.png"),
|
||||||
|
image::ImageFormat::Png,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
screen.send_image(&rgb_img)?;
|
||||||
|
|
||||||
|
let elapsed = text_upd.elapsed();
|
||||||
|
if elapsed < text_upd_delay {
|
||||||
|
sleep(text_upd_delay - elapsed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// CPU intensive! Release build is ~ 5x faster on M1 Max
|
||||||
|
fn demo_blinds(
|
||||||
|
screen: &mut AooScreen,
|
||||||
|
background: &RgbImage,
|
||||||
|
width: u32,
|
||||||
|
save_images: bool,
|
||||||
|
) -> anyhow::Result<RgbImage> {
|
||||||
|
let mut rgb_img = background.clone();
|
||||||
|
|
||||||
|
info!("Masking {width} pixels of left & right image...");
|
||||||
|
|
||||||
|
if save_images {
|
||||||
|
fs::create_dir_all("out")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
for y in 0..DISPLAY_SIZE.1 {
|
||||||
|
let color = *rgb_img.get_pixel(width + 1, y);
|
||||||
|
draw_line_segment_mut(
|
||||||
|
&mut rgb_img,
|
||||||
|
(0.0, y as f32),
|
||||||
|
(width as f32, y as f32),
|
||||||
|
color,
|
||||||
|
);
|
||||||
|
let color = *rgb_img.get_pixel(DISPLAY_SIZE.0 - width - 1, y);
|
||||||
|
draw_line_segment_mut(
|
||||||
|
&mut rgb_img,
|
||||||
|
((DISPLAY_SIZE.0 - width) as f32, y as f32),
|
||||||
|
(DISPLAY_SIZE.0 as f32, y as f32),
|
||||||
|
color,
|
||||||
|
);
|
||||||
|
|
||||||
|
if y % 5 == 0 {
|
||||||
|
screen.send_image(&rgb_img)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if save_images {
|
||||||
|
rgb_img
|
||||||
|
.save_with_format(format!("out/demo_blinds-{y}.png"), image::ImageFormat::Png)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
screen.send_image(&rgb_img)?;
|
||||||
|
|
||||||
|
Ok(rgb_img)
|
||||||
|
}
|
||||||
@@ -13,7 +13,7 @@ use std::path::PathBuf;
|
|||||||
|
|
||||||
static DEFAULT_TTF_FONT: Lazy<FontArc> = Lazy::new(|| {
|
static DEFAULT_TTF_FONT: Lazy<FontArc> = Lazy::new(|| {
|
||||||
FontArc::new(
|
FontArc::new(
|
||||||
FontRef::try_from_slice(include_bytes!("../fonts/DejaVuSans.ttf"))
|
FontRef::try_from_slice(include_bytes!("../../../fonts/DejaVuSans.ttf"))
|
||||||
.expect("Failed to load default font"),
|
.expect("Failed to load default font"),
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
@@ -47,7 +47,7 @@ impl From<Option<i32>> for IntegerDigits {
|
|||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// let value = format_value("123.456", IntegerDigits::Auto, 0, "foobar");
|
/// let value = asterctl::format_value("123.456", asterctl::IntegerDigits::Auto, 0, "foobar");
|
||||||
/// assert_eq!(value, "123foobar");
|
/// assert_eq!(value, "123foobar");
|
||||||
/// ```
|
/// ```
|
||||||
pub fn format_value(
|
pub fn format_value(
|
||||||
@@ -3,9 +3,8 @@
|
|||||||
|
|
||||||
//! Image helper functions.
|
//! Image helper functions.
|
||||||
|
|
||||||
use bytes::{BufMut, BytesMut};
|
|
||||||
use image::imageops::FilterType;
|
use image::imageops::FilterType;
|
||||||
use image::{DynamicImage, GenericImageView, ImageBuffer, ImageReader, RgbImage, Rgba, RgbaImage};
|
use image::{DynamicImage, GenericImageView, ImageBuffer, ImageReader, Rgba, RgbaImage};
|
||||||
use imageproc::geometric_transformations::{Interpolation, rotate};
|
use imageproc::geometric_transformations::{Interpolation, rotate};
|
||||||
use log::{debug, warn};
|
use log::{debug, warn};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
@@ -40,45 +39,6 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Trait definition to get a RGB 565 representation from a source image.
|
|
||||||
pub trait ToRgb565 {
|
|
||||||
/// Get an RGB 565 representation of the image in little endian format.
|
|
||||||
fn to_rgb565_le(&self) -> BytesMut;
|
|
||||||
|
|
||||||
/// Convert a single RGB 888 pixel to 16 bit RGB 565 format.
|
|
||||||
fn convert_rgb(&self, r: u8, g: u8, b: u8) -> u16 {
|
|
||||||
((r & 248) as u16) << 8 | ((g & 252) as u16) << 3 | ((b as u16) >> 3)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO quick & dirty approach for converting RgbImage & RgbaImage to RGB 565.
|
|
||||||
// There should be a more generic way, maybe with PixelEnumerator...
|
|
||||||
impl ToRgb565 for &RgbImage {
|
|
||||||
fn to_rgb565_le(&self) -> BytesMut {
|
|
||||||
let mut img_rgb565 =
|
|
||||||
BytesMut::with_capacity(self.width() as usize * self.height() as usize * 2);
|
|
||||||
|
|
||||||
for (_x, _y, pixel) in self.enumerate_pixels() {
|
|
||||||
img_rgb565.put_u16_le(self.convert_rgb(pixel.0[0], pixel.0[1], pixel.0[2]));
|
|
||||||
}
|
|
||||||
|
|
||||||
img_rgb565
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ToRgb565 for &RgbaImage {
|
|
||||||
fn to_rgb565_le(&self) -> BytesMut {
|
|
||||||
let mut img_rgb565 =
|
|
||||||
BytesMut::with_capacity(self.width() as usize * self.height() as usize * 2);
|
|
||||||
|
|
||||||
for (_x, _y, pixel) in self.enumerate_pixels() {
|
|
||||||
img_rgb565.put_u16_le(self.convert_rgb(pixel.0[0], pixel.0[1], pixel.0[2]));
|
|
||||||
}
|
|
||||||
|
|
||||||
img_rgb565
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Cache for loaded images to avoid repeated file I/O
|
/// Cache for loaded images to avoid repeated file I/O
|
||||||
pub struct ImageCache {
|
pub struct ImageCache {
|
||||||
img_path: PathBuf,
|
img_path: PathBuf,
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||||
|
// SPDX-FileCopyrightText: Copyright (c) 2025 Markus Zehnder
|
||||||
|
|
||||||
|
#![forbid(non_ascii_idents)]
|
||||||
|
#![deny(unsafe_code)]
|
||||||
|
|
||||||
|
pub mod cfg;
|
||||||
|
pub mod font;
|
||||||
|
mod format_value;
|
||||||
|
pub mod img;
|
||||||
|
pub mod render;
|
||||||
|
pub mod sensors;
|
||||||
|
|
||||||
|
pub use format_value::*;
|
||||||
@@ -1,31 +1,21 @@
|
|||||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||||
// SPDX-FileCopyrightText: Copyright (c) 2025 Markus Zehnder
|
// SPDX-FileCopyrightText: Copyright (c) 2025 Markus Zehnder
|
||||||
|
|
||||||
mod cfg;
|
#![forbid(non_ascii_idents)]
|
||||||
mod display;
|
#![deny(unsafe_code)]
|
||||||
mod dummy_serialport;
|
|
||||||
mod font;
|
use asterctl::cfg::{MonitorConfig, Panel, load_custom_panel};
|
||||||
mod format_value;
|
use asterctl::render::PanelRenderer;
|
||||||
mod img;
|
use asterctl::sensors::start_file_slurper;
|
||||||
mod render;
|
use asterctl::{cfg, img};
|
||||||
mod sensors;
|
use asterctl_lcd::{AooScreen, AooScreenBuilder, DISPLAY_SIZE};
|
||||||
|
|
||||||
use crate::cfg::{MonitorConfig, Panel, load_custom_panel};
|
|
||||||
use crate::display::{AooScreen, AooScreenBuilder, DISPLAY_SIZE};
|
|
||||||
use crate::font::FontHandler;
|
|
||||||
use crate::render::PanelRenderer;
|
|
||||||
use crate::sensors::start_file_slurper;
|
|
||||||
use ab_glyph::PxScale;
|
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use env_logger::Env;
|
use env_logger::Env;
|
||||||
use image::imageops::FilterType;
|
|
||||||
use image::{ImageReader, Rgb, RgbImage};
|
|
||||||
use imageproc::drawing::{draw_line_segment_mut, draw_text_mut};
|
|
||||||
use log::{debug, error, info};
|
use log::{debug, error, info};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::io::Cursor;
|
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::sync::{Arc, RwLock};
|
use std::sync::{Arc, RwLock};
|
||||||
@@ -83,10 +73,6 @@ struct Args {
|
|||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
sensor_path: Option<PathBuf>,
|
sensor_path: Option<PathBuf>,
|
||||||
|
|
||||||
/// Run a demo
|
|
||||||
#[arg(long)]
|
|
||||||
demo: bool,
|
|
||||||
|
|
||||||
/// Switch off display n seconds after loading image or running demo.
|
/// Switch off display n seconds after loading image or running demo.
|
||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
off_after: Option<u32>,
|
off_after: Option<u32>,
|
||||||
@@ -134,9 +120,7 @@ fn main() -> anyhow::Result<()> {
|
|||||||
// switch on screen for remaining commands
|
// switch on screen for remaining commands
|
||||||
screen.init()?;
|
screen.init()?;
|
||||||
|
|
||||||
if !args.demo
|
if let Some(config) = args.config {
|
||||||
&& let Some(config) = args.config
|
|
||||||
{
|
|
||||||
info!("Starting sensor panel mode");
|
info!("Starting sensor panel mode");
|
||||||
let img_save_path = if args.save {
|
let img_save_path = if args.save {
|
||||||
let img_save_path = PathBuf::from("out");
|
let img_save_path = PathBuf::from("out");
|
||||||
@@ -167,17 +151,6 @@ fn main() -> anyhow::Result<()> {
|
|||||||
debug!("Image sent in {}ms", timestamp.elapsed().as_millis());
|
debug!("Image sent in {}ms", timestamp.elapsed().as_millis());
|
||||||
}
|
}
|
||||||
|
|
||||||
if args.demo {
|
|
||||||
info!("Loading and displaying demo...");
|
|
||||||
run_demo(
|
|
||||||
&mut screen,
|
|
||||||
args.config.as_deref(),
|
|
||||||
args.config_dir.unwrap_or_else(|| "cfg".into()),
|
|
||||||
args.font_dir.unwrap_or_else(|| "fonts".into()),
|
|
||||||
args.save,
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(off) = args.off_after {
|
if let Some(off) = args.off_after {
|
||||||
info!("Switching off display in {off}s");
|
info!("Switching off display in {off}s");
|
||||||
sleep(Duration::from_secs(off as u64));
|
sleep(Duration::from_secs(off as u64));
|
||||||
@@ -298,155 +271,3 @@ fn update_panel(
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_demo(
|
|
||||||
screen: &mut AooScreen,
|
|
||||||
config: Option<&Path>,
|
|
||||||
config_dir: PathBuf,
|
|
||||||
font_dir: PathBuf,
|
|
||||||
save_images: bool,
|
|
||||||
) -> anyhow::Result<()> {
|
|
||||||
let rgb_img = demo_image()?;
|
|
||||||
|
|
||||||
// fill left and right side of the loaded image with neighboring pixel color
|
|
||||||
const WIDTH: u32 = 108;
|
|
||||||
let rgb_img = demo_blinds(screen, &rgb_img, WIDTH, save_images)?;
|
|
||||||
|
|
||||||
// print demo text over background image
|
|
||||||
demo_text(screen, &rgb_img, save_images)?;
|
|
||||||
|
|
||||||
if let Some(config) = config {
|
|
||||||
let mut cfg = load_configuration(config, &config_dir, None)?;
|
|
||||||
|
|
||||||
if let Some(panel) = cfg.get_next_active_panel() {
|
|
||||||
info!("Displaying demo panel...");
|
|
||||||
|
|
||||||
// get sensor values from panel configuration
|
|
||||||
let mut demo_values = HashMap::new();
|
|
||||||
for sensor in &panel.sensor {
|
|
||||||
demo_values.insert(
|
|
||||||
sensor.label.clone(),
|
|
||||||
sensor.value.clone().unwrap_or_default(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut renderer = PanelRenderer::new(DISPLAY_SIZE, &font_dir, &config_dir);
|
|
||||||
renderer.set_save_render_img(save_images);
|
|
||||||
renderer.set_save_processed_pic(save_images);
|
|
||||||
renderer.set_save_progress_layer(save_images);
|
|
||||||
|
|
||||||
update_panel(screen, &mut renderer, panel, &demo_values)?;
|
|
||||||
} else {
|
|
||||||
error!("No active panel found");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn demo_image() -> anyhow::Result<RgbImage> {
|
|
||||||
let reader = ImageReader::new(Cursor::new(include_bytes!("../img/aybabtu.png")))
|
|
||||||
.with_guessed_format()
|
|
||||||
.expect("Cursor io never fails");
|
|
||||||
|
|
||||||
Ok(reader
|
|
||||||
.decode()?
|
|
||||||
.resize_exact(DISPLAY_SIZE.0, DISPLAY_SIZE.1, FilterType::Lanczos3)
|
|
||||||
.to_rgb8())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn demo_text(
|
|
||||||
screen: &mut AooScreen,
|
|
||||||
background: &RgbImage,
|
|
||||||
save_images: bool,
|
|
||||||
) -> anyhow::Result<()> {
|
|
||||||
let text = "ALL YOUR BASE ARE BELONG TO US.";
|
|
||||||
let text_upd_delay = Duration::from_millis(0);
|
|
||||||
let font = FontHandler::default_font();
|
|
||||||
let height = 36.0;
|
|
||||||
let scale = PxScale {
|
|
||||||
x: height,
|
|
||||||
y: height,
|
|
||||||
};
|
|
||||||
|
|
||||||
if save_images {
|
|
||||||
fs::create_dir_all("out")?;
|
|
||||||
}
|
|
||||||
|
|
||||||
for text_idx in 0..text.len() {
|
|
||||||
info!("Printing: {}", &text[0..text_idx + 1]);
|
|
||||||
let text_upd = Instant::now();
|
|
||||||
let mut rgb_img = background.clone();
|
|
||||||
draw_text_mut(
|
|
||||||
&mut rgb_img,
|
|
||||||
Rgb([118u8, 118u8, 97u8]),
|
|
||||||
4 * 47,
|
|
||||||
300,
|
|
||||||
scale,
|
|
||||||
&font,
|
|
||||||
&text[0..text_idx + 1],
|
|
||||||
);
|
|
||||||
|
|
||||||
if save_images {
|
|
||||||
rgb_img.save_with_format(
|
|
||||||
format!("out/demo_text-{text_idx}.png"),
|
|
||||||
image::ImageFormat::Png,
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
screen.send_image(&rgb_img)?;
|
|
||||||
|
|
||||||
let elapsed = text_upd.elapsed();
|
|
||||||
if elapsed < text_upd_delay {
|
|
||||||
sleep(text_upd_delay - elapsed);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
// CPU intensive! Release build is ~ 5x faster on M1 Max
|
|
||||||
fn demo_blinds(
|
|
||||||
screen: &mut AooScreen,
|
|
||||||
background: &RgbImage,
|
|
||||||
width: u32,
|
|
||||||
save_images: bool,
|
|
||||||
) -> anyhow::Result<RgbImage> {
|
|
||||||
let mut rgb_img = background.clone();
|
|
||||||
|
|
||||||
info!("Masking {width} pixels of left & right image...");
|
|
||||||
|
|
||||||
if save_images {
|
|
||||||
fs::create_dir_all("out")?;
|
|
||||||
}
|
|
||||||
|
|
||||||
for y in 0..DISPLAY_SIZE.1 {
|
|
||||||
let color = *rgb_img.get_pixel(width + 1, y);
|
|
||||||
draw_line_segment_mut(
|
|
||||||
&mut rgb_img,
|
|
||||||
(0.0, y as f32),
|
|
||||||
(width as f32, y as f32),
|
|
||||||
color,
|
|
||||||
);
|
|
||||||
let color = *rgb_img.get_pixel(DISPLAY_SIZE.0 - width - 1, y);
|
|
||||||
draw_line_segment_mut(
|
|
||||||
&mut rgb_img,
|
|
||||||
((DISPLAY_SIZE.0 - width) as f32, y as f32),
|
|
||||||
(DISPLAY_SIZE.0 as f32, y as f32),
|
|
||||||
color,
|
|
||||||
);
|
|
||||||
|
|
||||||
if y % 5 == 0 {
|
|
||||||
screen.send_image(&rgb_img)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
if save_images {
|
|
||||||
rgb_img
|
|
||||||
.save_with_format(format!("out/demo_blinds-{y}.png"), image::ImageFormat::Png)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
screen.send_image(&rgb_img)?;
|
|
||||||
|
|
||||||
Ok(rgb_img)
|
|
||||||
}
|
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
use crate::cfg::{Panel, Sensor, SensorDirection, SensorMode, TextAlign};
|
use crate::cfg::{Panel, Sensor, SensorDirection, SensorMode, TextAlign};
|
||||||
use crate::font::FontHandler;
|
use crate::font::FontHandler;
|
||||||
use crate::format_value::format_value;
|
use crate::format_value;
|
||||||
use crate::img::{ImageCache, Size, rotate_image};
|
use crate::img::{ImageCache, Size, rotate_image};
|
||||||
use ab_glyph::Font;
|
use ab_glyph::Font;
|
||||||
use image::{ImageBuffer, Rgba, RgbaImage};
|
use image::{ImageBuffer, Rgba, RgbaImage};
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
[package]
|
||||||
|
name = "sysinfo"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "System sensor provider for asterctl"
|
||||||
|
|
||||||
|
rust-version.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
authors.workspace = true
|
||||||
|
license.workspace = true
|
||||||
|
repository.workspace = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
clap = { version = "4.5.42", features = ["derive"] }
|
||||||
|
sysinfo = "0.37.0"
|
||||||
|
itertools = "0.14"
|
||||||
|
tempfile = "3"
|
||||||
|
log = "0.4.27"
|
||||||
|
env_logger = "0.11.8"
|
||||||
|
regex = "1.11"
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
../../LICENSE-APACHE
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
../../LICENSE-MIT
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
# System Sensor Provider for asterctl
|
||||||
|
|
||||||
|
This tool gathers system sensor values with the help of the [sysinfo](https://github.com/GuillaumeGomez/sysinfo) crate
|
||||||
|
and writes them into a text file.
|
||||||
|
|
||||||
|
See [README](../../README.md) in root directory for more information.
|
||||||
|
|
||||||
|
```
|
||||||
|
Proof of concept sensor value collection for the asterctl screen control tool
|
||||||
|
|
||||||
|
Usage: sysinfo [OPTIONS]
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-o, --out <OUT>
|
||||||
|
Output sensor file
|
||||||
|
|
||||||
|
-t, --temp-dir <TEMP_DIR>
|
||||||
|
Temporary directory for preparing the output sensor file.
|
||||||
|
|
||||||
|
The system temp directory is used if not specified.
|
||||||
|
The temp directory must be on the same file system for atomic rename operation!
|
||||||
|
|
||||||
|
--console
|
||||||
|
Print values in console
|
||||||
|
|
||||||
|
-r, --refresh <REFRESH>
|
||||||
|
System sensor refresh interval in seconds
|
||||||
|
|
||||||
|
--disk-refresh <DISK_REFRESH>
|
||||||
|
Enable individual disk refresh logic as used in AOOSTAR-X. Refresh interval in seconds
|
||||||
|
|
||||||
|
--smartctl
|
||||||
|
Retrieve drive temperature if `disk-update` option is enabled.
|
||||||
|
|
||||||
|
Requires smartctl and password-less sudo!
|
||||||
|
```
|
||||||
|
|
||||||
|
Single test run with printing all sensors in the console:
|
||||||
|
```shell
|
||||||
|
sysinfo --console
|
||||||
|
```
|
||||||
|
|
||||||
|
Normal mode providing sensor values for `asterctl` in `/tmp/sensors/sysinfo.txt` every 3 seconds:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
sysinfo --refresh 3 --out /tmp/sensors/sysinfo.txt
|
||||||
|
```
|
||||||
@@ -1,6 +1,9 @@
|
|||||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||||
// SPDX-FileCopyrightText: Copyright (c) 2025 Markus Zehnder
|
// SPDX-FileCopyrightText: Copyright (c) 2025 Markus Zehnder
|
||||||
|
|
||||||
|
#![forbid(non_ascii_idents)]
|
||||||
|
#![deny(unsafe_code)]
|
||||||
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use env_logger::Env;
|
use env_logger::Env;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
@@ -2,9 +2,6 @@
|
|||||||
|
|
||||||
> Aster: Greek for star and similar to AOOSTAR.
|
> Aster: Greek for star and similar to AOOSTAR.
|
||||||
|
|
||||||
Currently, the project includes a proof-of-concept demo application that loads an image, draws rectangles, and writes
|
|
||||||
text over the image.
|
|
||||||
|
|
||||||
A work-in-progress "panel-mode" mimics the AOOSTAR-X software and uses the same configuration files for rendering sensor
|
A work-in-progress "panel-mode" mimics the AOOSTAR-X software and uses the same configuration files for rendering sensor
|
||||||
panels with dynamic sensor values.
|
panels with dynamic sensor values.
|
||||||
|
|
||||||
@@ -51,9 +48,6 @@ Options:
|
|||||||
--sensor-path <SENSOR_PATH>
|
--sensor-path <SENSOR_PATH>
|
||||||
Single sensor value input file or directory for multiple sensor input files. Default: `./cfg/sensors`
|
Single sensor value input file or directory for multiple sensor input files. Default: `./cfg/sensors`
|
||||||
|
|
||||||
--demo
|
|
||||||
Run a demo
|
|
||||||
|
|
||||||
-o, --off-after <OFF_AFTER>
|
-o, --off-after <OFF_AFTER>
|
||||||
Switch off display n seconds after loading image or running demo
|
Switch off display n seconds after loading image or running demo
|
||||||
|
|
||||||
@@ -73,21 +67,6 @@ Options:
|
|||||||
Print version
|
Print version
|
||||||
```
|
```
|
||||||
|
|
||||||
## Demo Mode
|
|
||||||
|
|
||||||
```shell
|
|
||||||
cargo run --release -- --demo --config monitor.json
|
|
||||||
```
|
|
||||||
|
|
||||||
The `--config` parameter is optional. It loads the official configuration file and displays the defined sensors in the
|
|
||||||
first panel.
|
|
||||||
|
|
||||||
### Parameters
|
|
||||||
|
|
||||||
- `--device /dev/ttyACM0` — Specify the serial device.
|
|
||||||
- `--usb 0403:6001` — Specify the USB UART device by USB **VID:PID** (hexadecimal, as shown by `lsusb`).
|
|
||||||
- `--help` — Show all options.
|
|
||||||
|
|
||||||
## Sensor Panel Mode
|
## Sensor Panel Mode
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
@@ -122,3 +101,19 @@ asterctl --image img/aybabtu.png
|
|||||||
|
|
||||||
This expects a 960 × 376 image (other sizes are automatically scaled and the aspect ratio is ignored).
|
This expects a 960 × 376 image (other sizes are automatically scaled and the aspect ratio is ignored).
|
||||||
See Rust image crate for [supported image formats](https://github.com/image-rs/image?tab=readme-ov-file#supported-image-formats).
|
See Rust image crate for [supported image formats](https://github.com/image-rs/image?tab=readme-ov-file#supported-image-formats).
|
||||||
|
|
||||||
|
## Demo app
|
||||||
|
|
||||||
|
```shell
|
||||||
|
cargo run --release --bin demo -- --config monitor.json
|
||||||
|
```
|
||||||
|
|
||||||
|
The `--config` parameter is optional. It loads the official configuration file and displays the defined sensors in the
|
||||||
|
first panel.
|
||||||
|
|
||||||
|
### Parameters
|
||||||
|
|
||||||
|
- `--device /dev/ttyACM0` — Specify the serial device.
|
||||||
|
- `--usb 0403:6001` — Specify the USB UART device by USB **VID:PID** (hexadecimal, as shown by `lsusb`).
|
||||||
|
- `--help` — Show all options.
|
||||||
|
|
||||||
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 849 B After Width: | Height: | Size: 849 B |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 206 KiB After Width: | Height: | Size: 206 KiB |
|
Before Width: | Height: | Size: 81 KiB After Width: | Height: | Size: 81 KiB |
|
Before Width: | Height: | Size: 77 KiB After Width: | Height: | Size: 77 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 57 KiB |
|
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 42 KiB |
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 37 KiB |
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 219 KiB After Width: | Height: | Size: 219 KiB |
|
Before Width: | Height: | Size: 259 KiB After Width: | Height: | Size: 259 KiB |
@@ -1,6 +1,6 @@
|
|||||||
# sysinfo Tool
|
# sysinfo Tool
|
||||||
|
|
||||||
The Rust based [/src/bin/sysinfo.rs](../src/bin/sysinfo.rs) tool gathers many more system sensor values with the help of
|
The Rust based [sysinfo](../crates/sysinfo/src/main.rs) tool gathers many more system sensor values with the help of
|
||||||
the [sysinfo](https://github.com/GuillaumeGomez/sysinfo) crate.
|
the [sysinfo](https://github.com/GuillaumeGomez/sysinfo) crate.
|
||||||
|
|
||||||
It supports FreeBSD, Linux, macOS, Windows and other OSes, but it has only been tested on Linux so far.
|
It supports FreeBSD, Linux, macOS, Windows and other OSes, but it has only been tested on Linux so far.
|
||||||
@@ -35,15 +35,15 @@ Options:
|
|||||||
Requires smartctl and password-less sudo!
|
Requires smartctl and password-less sudo!
|
||||||
```
|
```
|
||||||
|
|
||||||
Single test run with printing all sensors on the console:
|
Single test run with printing all sensors in the console:
|
||||||
```shell
|
```shell
|
||||||
sysinfo --console
|
sysinfo --console
|
||||||
```
|
```
|
||||||
|
|
||||||
Normal mode providing sensor values for `asterctl` in `/tmp/sensors/sysinfo.txt`:
|
Normal mode providing sensor values for `asterctl` in `/tmp/sensors/sysinfo.txt` every 3 seconds:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
sysinfo --refresh 3 --out /tmp/sensor/sysinfo.txt
|
sysinfo --refresh 3 --out /tmp/sensors/sysinfo.txt
|
||||||
```
|
```
|
||||||
|
|
||||||
Note: the lower the refresh rate, the more resources are used!
|
Note: the lower the refresh rate, the more resources are used!
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Switch on embedded LCD
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=oneshot
|
||||||
|
RemainAfterExit=no
|
||||||
|
DynamicUser=true
|
||||||
|
# tailored to Debian: adapt for other Linux flavours! RW access to /dev/ttyACM0 is required
|
||||||
|
Group=dialout
|
||||||
|
|
||||||
|
ExecStart=/usr/bin/asterctl --on
|
||||||
|
|
||||||
|
# lock down service
|
||||||
|
CapabilityBoundingSet=
|
||||||
|
LockPersonality=true
|
||||||
|
RestrictNamespaces=true
|
||||||
|
ProtectHome=true
|
||||||
|
ProtectSystem=strict
|
||||||
|
NoNewPrivileges=true
|
||||||
|
ProtectKernelTunables=true
|
||||||
|
ProtectKernelModules=true
|
||||||
|
ProtectKernelLogs=true
|
||||||
|
ProtectControlGroups=true
|
||||||
|
MemoryDenyWriteExecute=true
|
||||||
|
RestrictSUIDSGID=true
|
||||||
|
KeyringMode=private
|
||||||
|
ProtectClock=true
|
||||||
|
ProtectProc=invisible
|
||||||
|
ProcSubset=pid
|
||||||
|
RestrictRealtime=true
|
||||||
|
PrivateNetwork=true
|
||||||
|
PrivateTmp=true
|
||||||
|
PrivateUsers=true
|
||||||
|
ProtectHostname=true
|
||||||
|
RestrictAddressFamilies=none
|
||||||
|
SystemCallFilter=@system-service
|
||||||
|
SystemCallFilter=~@privileged @resources
|
||||||
|
SystemCallErrorNumber=EPERM
|
||||||
|
UMask=0177
|
||||||
|
|
||||||
|
# that's all we need access to
|
||||||
|
DeviceAllow=/dev/ttyACM0 rw
|
||||||