refactor: project structure (#9)

Split up project into multiple crates and use a Cargo workspace.
This commit is contained in:
Markus Zehnder
2025-08-28 09:03:30 +02:00
committed by GitHub
parent f0128197d9
commit d98cd89c48
71 changed files with 672 additions and 401 deletions
-1
View File
@@ -1,4 +1,3 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectInspectionProfilesVisibleTreeState">
<entry key="Project Default">
+1 -1
View File
@@ -1,7 +1,7 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Run demo" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<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$" />
<envs />
<option name="emulateTerminal" value="true" />
+1 -1
View File
@@ -1,7 +1,7 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Run demo DEV" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<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$" />
<envs />
<option name="emulateTerminal" value="true" />
+1 -1
View File
@@ -1,7 +1,7 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Run sensor panel" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<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$" />
<envs />
<option name="emulateTerminal" value="true" />
+1 -1
View File
@@ -1,7 +1,7 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Run sensor panel DEV" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<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$" />
<envs />
<option name="emulateTerminal" value="true" />
+1 -1
View File
@@ -1,7 +1,7 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Run sysinfo" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<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$" />
<envs />
<option name="emulateTerminal" value="true" />
+1 -1
View File
@@ -1,7 +1,7 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Run sysinfo repeat" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<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$" />
<envs />
<option name="emulateTerminal" value="true" />
-19
View File
@@ -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>
Generated
+1 -1
View File
@@ -8,6 +8,6 @@
</profile>
</component>
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
<mapping directory="" vcs="Git" />
</component>
</project>
+7 -3
View File
@@ -1,12 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="RUST_MODULE" version="4">
<module version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<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$/crates/asterctl-lcd/target" />
<excludeFolder url="file://$MODULE_DIR$/crates/sysinfo/target" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
+4
View File
@@ -11,6 +11,10 @@ _Changes in the next release_
### Added
- 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
---
Generated
+68 -49
View File
@@ -98,31 +98,6 @@ version = "1.0.99"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "approx"
version = "0.5.1"
@@ -155,6 +130,37 @@ version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "autocfg"
version = "1.5.0"
@@ -198,9 +204,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.9.2"
version = "2.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a65b545ab31d687cff52899d4890855fec459eb6afe0da6417b8a18da87aa29"
checksum = "34efbcccd345379ca2868b2b2c9d3782e9cc58ba87bc7d79d5b53d9c9ae6f25d"
[[package]]
name = "bitstream-io"
@@ -240,9 +246,9 @@ checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
[[package]]
name = "cc"
version = "1.2.33"
version = "1.2.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ee0f8803222ba5a7e2777dd72ca451868909b1ac410621b676adf07280e9b5f"
checksum = "42bc4aea80032b7bf409b0bc7ccad88853858911b7713a8062fdc0623867bedc"
dependencies = [
"jobserver",
"libc",
@@ -651,9 +657,9 @@ checksum = "d0263a3d970d5c054ed9312c0057b4f3bde9c0b33836d3637361d4a9e6e7a408"
[[package]]
name = "indexmap"
version = "2.10.0"
version = "2.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661"
checksum = "f2481980430f9f78649238835720ddccc57e52df14ffce1c6f37391d61b563e9"
dependencies = [
"equivalent",
"hashbrown",
@@ -665,7 +671,7 @@ version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f37dccff2791ab604f9babef0ba14fbe0be30bd368dc541e2b08d07c8aa908f3"
dependencies = [
"bitflags 2.9.2",
"bitflags 2.9.3",
"inotify-sys",
"libc",
]
@@ -756,9 +762,9 @@ dependencies = [
[[package]]
name = "jobserver"
version = "0.1.33"
version = "0.1.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a"
checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33"
dependencies = [
"getrandom 0.3.3",
"libc",
@@ -986,7 +992,7 @@ version = "8.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d3d07927151ff8575b7087f245456e549fea62edf0ec4e565a5ee50c8402bc3"
dependencies = [
"bitflags 2.9.2",
"bitflags 2.9.3",
"fsevent-sys",
"inotify",
"kqueue",
@@ -1104,7 +1110,7 @@ version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166"
dependencies = [
"bitflags 2.9.2",
"bitflags 2.9.3",
]
[[package]]
@@ -1384,9 +1390,9 @@ dependencies = [
[[package]]
name = "regex"
version = "1.11.1"
version = "1.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912"
dependencies = [
"aho-corasick",
"memchr",
@@ -1396,9 +1402,9 @@ dependencies = [
[[package]]
name = "regex-automata"
version = "0.4.9"
version = "0.4.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
checksum = "6b9458fa0bfeeac22b5ca447c63aaf45f28439a709ccd244698632f9aa6394d6"
dependencies = [
"aho-corasick",
"memchr",
@@ -1407,9 +1413,9 @@ dependencies = [
[[package]]
name = "regex-syntax"
version = "0.8.5"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001"
[[package]]
name = "relative-path"
@@ -1467,7 +1473,7 @@ version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8"
dependencies = [
"bitflags 2.9.2",
"bitflags 2.9.3",
"errno",
"libc",
"linux-raw-sys",
@@ -1570,11 +1576,11 @@ dependencies = [
[[package]]
name = "serialport"
version = "4.7.2"
version = "4.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdb0bc984f6af6ef8bab54e6cf2071579ee75b9286aa9f2319a0d220c28b0a2b"
checksum = "2acaf3f973e8616d7ceac415f53fc60e190b2a686fbcf8d27d0256c741c5007b"
dependencies = [
"bitflags 2.9.2",
"bitflags 2.9.3",
"cfg-if",
"core-foundation",
"core-foundation-sys",
@@ -1650,6 +1656,19 @@ dependencies = [
"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]]
name = "sysinfo"
version = "0.37.0"
@@ -2221,9 +2240,9 @@ checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486"
[[package]]
name = "winnow"
version = "0.7.12"
version = "0.7.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95"
checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf"
dependencies = [
"memchr",
]
@@ -2234,7 +2253,7 @@ version = "0.39.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
dependencies = [
"bitflags 2.9.2",
"bitflags 2.9.3",
]
[[package]]
+7 -42
View File
@@ -1,45 +1,10 @@
[package]
name = "aoostar-rs"
version = "0.1.0"
edition = "2024"
[workspace]
members = ["crates/*"]
resolver = "3"
[workspace.package]
rust-version = "1.88"
edition = "2024"
authors = ["Markus Zehnder"]
license = "MIT or Apache-2.0"
[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"
repository = "https://github.com/zehnm/aoostar-rs"
+13 -11
View File
@@ -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)
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 ‼️**
@@ -10,6 +10,8 @@ This project should also support the GEM12+ PRO device.
> There is no official documentation available;
> 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 could crash the display firmware, requiring a power cycle.
- 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!
**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/)
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 engineered LCD commands: [doc/lcd_protocol.md](doc/lcd_protocol.md)
Reverse engineered LCD commands: [docs/lcd_protocol.md](docs/lcd_protocol.md)
### 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.
- 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.
- [ ] 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:**
@@ -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.
- Switch the display on or off.
- 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.
## Setup
@@ -87,21 +91,19 @@ cd aoostar-rs
### 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
cargo build --release --bins --all-features
cargo build --release
```
The `--bins` option builds the main `asterctl` app and all other tools.
### Install
See [Linux systemd Service](linux/) on how to automatically switch off the LCD at boot up.
## 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
+3 -3
View File
@@ -1,5 +1,5 @@
cpu_temperature: 65
cpu_percent: 98
cpu_percent: 47.7
memory_usage: 77
memory_Temperature: 48
net_ip_address: 146.56.182.244
@@ -17,9 +17,9 @@ storage_ssd[1]['used']: 18
storage_ssd[2]['temperature']: 33
storage_ssd[2]['used']: 19
storage_ssd[3]['temperature']: 34
storage_ssd[3]['used']: 20
storage_ssd[3]['used']: 35
storage_ssd[4]['temperature']: 35
storage_ssd[4]['used']: 21
storage_ssd[4]['used']: 80
storage_hdd[0]['temperature']: 36
storage_hdd[0]['used']: 22
storage_hdd[1]['temperature']: 37
+18
View File
@@ -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"
+1
View File
@@ -0,0 +1 @@
../../LICENSE-APACHE
+1
View File
@@ -0,0 +1 @@
../../LICENSE-MIT
+17
View File
@@ -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-FileCopyrightText: Copyright (c) 2025 Markus Zehnder
use crate::dummy_serialport::DummySerialPort;
use crate::img::ToRgb565;
use crate::FakeSerialPort;
use crate::ToRgb565;
use anyhow::{Context, anyhow};
use bytes::{BufMut, BytesMut};
use log::{debug, error, info, warn};
@@ -68,7 +69,7 @@ impl AooScreenBuilder {
/// Simulate the LCD device. No real device or serial port is required.
pub fn simulate(self) -> anyhow::Result<AooScreen> {
Ok(AooScreen {
port: Some(Box::new(DummySerialPort::new())),
port: Some(Box::new(FakeSerialPort::new())),
enable_cache: self.enable_cache.unwrap_or(true),
prev_frame: None,
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::time::Duration;
pub struct DummySerialPort {
pub struct FakeSerialPort {
baud_rate: u32,
data_bits: DataBits,
flow_control: FlowControl,
@@ -14,8 +14,14 @@ pub struct DummySerialPort {
timeout: Duration,
}
impl DummySerialPort {
pub fn new() -> DummySerialPort {
impl Default for FakeSerialPort {
fn default() -> Self {
Self::new()
}
}
impl FakeSerialPort {
pub fn new() -> FakeSerialPort {
Self {
baud_rate: 1_500_000,
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> {
buf[0] = b'A';
Ok(1)
}
}
impl std::io::Write for DummySerialPort {
impl std::io::Write for FakeSerialPort {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
// just some approximation, additional overhead like flushing etc is not considered
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> {
Some("Dummy Serial".into())
}
+53
View File
@@ -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
}
}
+30
View File
@@ -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"
+1
View File
@@ -0,0 +1 @@
../../LICENSE-APACHE
+1
View File
@@ -0,0 +1 @@
../../LICENSE-MIT
+3
View File
@@ -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

+262
View File
@@ -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)
}
+1 -1
View File
@@ -13,7 +13,7 @@ use std::path::PathBuf;
static DEFAULT_TTF_FONT: Lazy<FontArc> = Lazy::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"),
)
});
@@ -47,7 +47,7 @@ impl From<Option<i32>> for IntegerDigits {
/// # 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");
/// ```
pub fn format_value(
+1 -41
View File
@@ -3,9 +3,8 @@
//! Image helper functions.
use bytes::{BufMut, BytesMut};
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 log::{debug, warn};
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
pub struct ImageCache {
img_path: PathBuf,
+14
View File
@@ -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::*;
+9 -188
View File
@@ -1,31 +1,21 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
// SPDX-FileCopyrightText: Copyright (c) 2025 Markus Zehnder
mod cfg;
mod display;
mod dummy_serialport;
mod font;
mod format_value;
mod img;
mod render;
mod sensors;
#![forbid(non_ascii_idents)]
#![deny(unsafe_code)]
use asterctl::cfg::{MonitorConfig, Panel, load_custom_panel};
use asterctl::render::PanelRenderer;
use asterctl::sensors::start_file_slurper;
use asterctl::{cfg, img};
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 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::{debug, error, info};
use std::collections::HashMap;
use std::fs;
use std::io::Cursor;
use std::path::{Path, PathBuf};
use std::str::FromStr;
use std::sync::{Arc, RwLock};
@@ -83,10 +73,6 @@ struct Args {
#[arg(long)]
sensor_path: Option<PathBuf>,
/// Run a demo
#[arg(long)]
demo: bool,
/// Switch off display n seconds after loading image or running demo.
#[arg(short, long)]
off_after: Option<u32>,
@@ -134,9 +120,7 @@ fn main() -> anyhow::Result<()> {
// switch on screen for remaining commands
screen.init()?;
if !args.demo
&& let Some(config) = args.config
{
if let Some(config) = args.config {
info!("Starting sensor panel mode");
let img_save_path = if args.save {
let img_save_path = PathBuf::from("out");
@@ -167,17 +151,6 @@ fn main() -> anyhow::Result<()> {
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 {
info!("Switching off display in {off}s");
sleep(Duration::from_secs(off as u64));
@@ -298,155 +271,3 @@ fn update_panel(
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::font::FontHandler;
use crate::format_value::format_value;
use crate::format_value;
use crate::img::{ImageCache, Size, rotate_image};
use ab_glyph::Font;
use image::{ImageBuffer, Rgba, RgbaImage};
+19
View File
@@ -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"
+1
View File
@@ -0,0 +1 @@
../../LICENSE-APACHE
+1
View File
@@ -0,0 +1 @@
../../LICENSE-MIT
+47
View File
@@ -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-FileCopyrightText: Copyright (c) 2025 Markus Zehnder
#![forbid(non_ascii_idents)]
#![deny(unsafe_code)]
use clap::Parser;
use env_logger::Env;
use itertools::Itertools;
View File
+16 -21
View File
@@ -2,9 +2,6 @@
> 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
panels with dynamic sensor values.
@@ -51,9 +48,6 @@ Options:
--sensor-path <SENSOR_PATH>
Single sensor value input file or directory for multiple sensor input files. Default: `./cfg/sensors`
--demo
Run a demo
-o, --off-after <OFF_AFTER>
Switch off display n seconds after loading image or running demo
@@ -73,21 +67,6 @@ Options:
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
```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).
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
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.
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!
```
Single test run with printing all sensors on the console:
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`:
Normal mode providing sensor values for `asterctl` in `/tmp/sensors/sysinfo.txt` every 3 seconds:
```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!
+42
View File
@@ -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