Generated
+1
-1
@@ -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 --bin asterctl -- --demo -c monitor.json" />
|
<option name="command" value="run --bin 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" />
|
||||||
|
|||||||
Generated
+1
-1
@@ -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" />
|
||||||
q <option name="command" value="run --bin sysinfo -- --console --out ./cfg/sensors/sysinfo.txt" />
|
<option name="command" value="run --bin aster-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
-1
@@ -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 --bin sysinfo -- --console --out ./cfg/sensors/sysinfo.txt --refresh 3" />
|
<option name="command" value="run --bin aster-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" />
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
# Mapping sensor labels from sensor value providers to panel definition labels.
|
||||||
|
#
|
||||||
|
# Mapping definition for: aster-sysinfo
|
||||||
|
#
|
||||||
|
# Key = label identifier used in panel definition
|
||||||
|
# Value = label identifier used in sensor providers
|
||||||
|
|
||||||
|
cpu_temperature: temperature_cpu
|
||||||
|
memory_usage: mem_usage_percent
|
||||||
|
memory_Temperature: temperature_memory
|
||||||
|
gpu_temperature: temperature_gpu
|
||||||
|
|
||||||
|
# replace network_###_ with the desired interface: enp100s0f0np0 / enp100s0f1np1 / enp102s0 / enp103s0 etc
|
||||||
|
net_upload_speed: network_enp100s0f1np1_upload_speed
|
||||||
|
net_download_speed: network_enp100s0f1np1_download_speed
|
||||||
|
net_ip_address: network_enp100s0f1np1_address0
|
||||||
|
|
||||||
|
# Individual storage_ssd / hdd[x] disk info doesn't seem very useful for a NAS.
|
||||||
|
# Usually there are different arrays / fs mounts. But that's easy to map now!
|
||||||
|
storage_ssd[0]['temperature']: temperature_nvme_Composite_KINGSTON_OM8PGP41024Q-A0
|
||||||
|
storage_ssd[0]['used']: storage_ssd[0]_usage_percent
|
||||||
|
storage_ssd[1]['temperature']: temperature_nvme_Composite_Samsung_SSD_990_EVO_Plus_2TB
|
||||||
|
storage_ssd[1]['used']: storage_ssd[1]_usage_percent
|
||||||
|
# storage_ssd[2]['temperature']:
|
||||||
|
storage_ssd[2]['used']: storage_ssd[2]_usage_percent
|
||||||
|
# storage_ssd[3]['temperature']:
|
||||||
|
storage_ssd[3]['used']: storage_ssd[3]_usage_percent
|
||||||
|
# storage_ssd[4]['temperature']:
|
||||||
|
storage_ssd[4]['used']: storage_ssd[4]_usage_percent
|
||||||
|
# storage_hdd[0]['temperature']:
|
||||||
|
storage_hdd[0]['used']: storage_hdd[0]_usage_percent
|
||||||
|
# storage_hdd[1]['temperature']:
|
||||||
|
storage_hdd[1]['used']: storage_hdd[1]_usage_percent
|
||||||
|
# storage_hdd[2]['temperature']:
|
||||||
|
storage_hdd[2]['used']: storage_hdd[2]_usage_percent
|
||||||
|
# storage_hdd[3]['temperature']:
|
||||||
|
storage_hdd[3]['used']: storage_hdd[3]_usage_percent
|
||||||
|
# storage_hdd[4]['temperature']:
|
||||||
|
storage_hdd[4]['used']: storage_hdd[4]_usage_percent
|
||||||
|
# storage_hdd[5]['temperature']:
|
||||||
|
storage_hdd[5]['used']: storage_hdd[5]_usage_percent
|
||||||
|
|
||||||
|
# TODO not (yet) available in aster-sysinfo
|
||||||
|
# cpu_percent: cpu_usage_percent
|
||||||
|
# gpu_core:
|
||||||
|
# motherboard_temperature:
|
||||||
|
|
||||||
|
# Not yet supported
|
||||||
|
# DATE_m_d_h_m_2:
|
||||||
@@ -13,6 +13,7 @@ use log::{info, warn};
|
|||||||
use serde::de::Visitor;
|
use serde::de::Visitor;
|
||||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||||
use serde_repr::{Deserialize_repr, Serialize_repr};
|
use serde_repr::{Deserialize_repr, Serialize_repr};
|
||||||
|
use std::collections::HashMap;
|
||||||
use std::io::BufReader;
|
use std::io::BufReader;
|
||||||
use std::num::ParseIntError;
|
use std::num::ParseIntError;
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
@@ -118,6 +119,9 @@ pub struct MonitorConfig {
|
|||||||
/// Internal index of the currently active panel. 1-based!
|
/// Internal index of the currently active panel. 1-based!
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
active_panel_idx: Option<usize>,
|
active_panel_idx: Option<usize>,
|
||||||
|
/// Internal sensor label mapping
|
||||||
|
#[serde(skip)]
|
||||||
|
sensor_mapping: Option<HashMap<String, String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MonitorConfig {
|
impl MonitorConfig {
|
||||||
@@ -146,10 +150,33 @@ impl MonitorConfig {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn include_custom_panel(&mut self, panel: Panel) {
|
/// Adds a custom panel to the application and maps sensor labels if applicable.
|
||||||
|
///
|
||||||
|
/// The panel is marked active and will be returned with [get_next_active_panel] when it is its turn.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `panel` - the Panel to include in the active panels.
|
||||||
|
pub fn include_custom_panel(&mut self, mut panel: Panel) {
|
||||||
|
if let Some(mapping) = &self.sensor_mapping {
|
||||||
|
panel.map_sensor_labels(mapping);
|
||||||
|
}
|
||||||
self.panels.push(panel);
|
self.panels.push(panel);
|
||||||
self.active_panels.push(self.panels.len() as u32);
|
self.active_panels.push(self.panels.len() as u32);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Apply a sensor label mapping on the included panels.
|
||||||
|
///
|
||||||
|
/// The mapping will also be applied on any custom panel added in the future with [include_custom_panel].
|
||||||
|
///
|
||||||
|
/// **Attention**: this method may only be called once at startup.
|
||||||
|
/// Dynamically changing mappings are not supported, and the original sensor labels are not preserved.
|
||||||
|
pub fn set_sensor_mapping(&mut self, mapping: HashMap<String, String>) {
|
||||||
|
for panel in self.panels.iter_mut() {
|
||||||
|
panel.map_sensor_labels(&mapping);
|
||||||
|
}
|
||||||
|
self.sensor_mapping = Some(mapping);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Web-app user login
|
/// Web-app user login
|
||||||
@@ -278,6 +305,14 @@ impl Panel {
|
|||||||
})
|
})
|
||||||
.unwrap_or_else(|| "panel".into())
|
.unwrap_or_else(|| "panel".into())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn map_sensor_labels(&mut self, mapping: &HashMap<String, String>) {
|
||||||
|
for sensor in self.sensor.iter_mut() {
|
||||||
|
if let Some(new_label) = mapping.get(&sensor.label) {
|
||||||
|
sensor.label = new_label.clone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// One Data Display Unit
|
/// One Data Display Unit
|
||||||
|
|||||||
+39
-15
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
use asterctl::cfg::{MonitorConfig, Panel, load_custom_panel};
|
use asterctl::cfg::{MonitorConfig, Panel, load_custom_panel};
|
||||||
use asterctl::render::PanelRenderer;
|
use asterctl::render::PanelRenderer;
|
||||||
use asterctl::sensors::start_file_slurper;
|
use asterctl::sensors::{read_key_value_file, start_file_slurper};
|
||||||
use asterctl::{cfg, img};
|
use asterctl::{cfg, img};
|
||||||
use asterctl_lcd::{AooScreen, AooScreenBuilder, DISPLAY_SIZE};
|
use asterctl_lcd::{AooScreen, AooScreenBuilder, DISPLAY_SIZE};
|
||||||
|
|
||||||
@@ -26,7 +26,7 @@ use std::time::{Duration, Instant};
|
|||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
#[command(version, about, long_about = None)]
|
#[command(version, about, long_about = None)]
|
||||||
struct Args {
|
struct Args {
|
||||||
/// Serial device, for example "/dev/cu.usbserial-AB0KOHLS". Takes priority over --usb option.
|
/// Serial device, for example, "/dev/cu.usbserial-AB0KOHLS". Takes priority over --usb option.
|
||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
device: Option<String>,
|
device: Option<String>,
|
||||||
|
|
||||||
@@ -60,18 +60,24 @@ struct Args {
|
|||||||
panels: Option<Vec<PathBuf>>,
|
panels: Option<Vec<PathBuf>>,
|
||||||
|
|
||||||
/// Configuration directory containing configuration files and background images
|
/// Configuration directory containing configuration files and background images
|
||||||
/// specified in the `config` file. Default: `./cfg`
|
/// specified in the `config` file.
|
||||||
#[arg(long)]
|
#[arg(long, default_value_t = String::from("cfg"))]
|
||||||
config_dir: Option<PathBuf>,
|
config_dir: String, // default_value_t requires Display trait which PathBuf does not implement
|
||||||
|
|
||||||
/// Font directory for fonts specified in the `config` file. Default: `./fonts`
|
/// Font directory for fonts specified in the `config` file.
|
||||||
#[arg(long)]
|
#[arg(long, default_value_t = String::from("fonts"))]
|
||||||
font_dir: Option<PathBuf>,
|
font_dir: String,
|
||||||
|
|
||||||
/// Single sensor value input file or directory for multiple sensor input files.
|
/// Single sensor value input file or directory for multiple sensor input files.
|
||||||
/// Default: `./cfg/sensors`
|
#[arg(long, default_value_t = String::from("cfg/sensors"))]
|
||||||
#[arg(long)]
|
sensor_path: String,
|
||||||
sensor_path: Option<PathBuf>,
|
|
||||||
|
/// Sensor identifier mapping file. Ignored if the file does not exist.
|
||||||
|
///
|
||||||
|
/// The configuration file will be loaded from the `config_dir` directory if no full path is
|
||||||
|
/// specified.
|
||||||
|
#[arg(long, default_value_t = String::from("sensor-mapping.cfg"))]
|
||||||
|
sensor_mapping: String,
|
||||||
|
|
||||||
/// 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)]
|
||||||
@@ -130,14 +136,17 @@ fn main() -> anyhow::Result<()> {
|
|||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
let cfg_dir = args.config_dir.unwrap_or_else(|| "cfg".into());
|
let cfg_dir = PathBuf::from(args.config_dir);
|
||||||
let cfg = load_configuration(&config, &cfg_dir, args.panels)?;
|
let font_dir = PathBuf::from(args.font_dir);
|
||||||
|
let sensor_path = PathBuf::from(args.sensor_path);
|
||||||
|
let mapping_cfg = PathBuf::from(args.sensor_mapping);
|
||||||
|
let cfg = load_configuration(&config, &cfg_dir, args.panels, &mapping_cfg)?;
|
||||||
run_sensor_panel(
|
run_sensor_panel(
|
||||||
&mut screen,
|
&mut screen,
|
||||||
cfg,
|
cfg,
|
||||||
cfg_dir,
|
cfg_dir,
|
||||||
args.font_dir.unwrap_or_else(|| "fonts".into()),
|
font_dir,
|
||||||
args.sensor_path.unwrap_or_else(|| "cfg/sensors".into()),
|
sensor_path,
|
||||||
img_save_path,
|
img_save_path,
|
||||||
)?;
|
)?;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
@@ -166,6 +175,7 @@ fn load_configuration<P: AsRef<Path>>(
|
|||||||
config: P,
|
config: P,
|
||||||
config_dir: P,
|
config_dir: P,
|
||||||
panels: Option<Vec<PathBuf>>,
|
panels: Option<Vec<PathBuf>>,
|
||||||
|
sensor_mapping: P,
|
||||||
) -> anyhow::Result<MonitorConfig> {
|
) -> anyhow::Result<MonitorConfig> {
|
||||||
let config = config.as_ref();
|
let config = config.as_ref();
|
||||||
let config_dir = config_dir.as_ref();
|
let config_dir = config_dir.as_ref();
|
||||||
@@ -182,6 +192,20 @@ fn load_configuration<P: AsRef<Path>>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let sensor_mapping = sensor_mapping.as_ref();
|
||||||
|
let mapping_cfg = if sensor_mapping.is_absolute() {
|
||||||
|
sensor_mapping.to_path_buf()
|
||||||
|
} else {
|
||||||
|
config_dir.join(sensor_mapping)
|
||||||
|
};
|
||||||
|
if mapping_cfg.is_file() {
|
||||||
|
let mut mapping = HashMap::new();
|
||||||
|
read_key_value_file(&mapping_cfg, &mut mapping)?;
|
||||||
|
cfg.set_sensor_mapping(mapping);
|
||||||
|
} else {
|
||||||
|
info!("Sensor mapping file {mapping_cfg:?} not found");
|
||||||
|
}
|
||||||
|
|
||||||
Ok(cfg)
|
Ok(cfg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ pub fn start_file_slurper<P: Into<PathBuf>>(
|
|||||||
debug!("Modified sensor file ({kind:?}): {path:?}");
|
debug!("Modified sensor file ({kind:?}): {path:?}");
|
||||||
let mut val = file_values.write().expect("Poisoned sensor RwLock");
|
let mut val = file_values.write().expect("Poisoned sensor RwLock");
|
||||||
|
|
||||||
if let Err(e) = read_from_file(path, val.deref_mut()) {
|
if let Err(e) = read_key_value_file(path, val.deref_mut()) {
|
||||||
warn!("Failed to read sensor file {path:?}: {e}");
|
warn!("Failed to read sensor file {path:?}: {e}");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -113,7 +113,7 @@ fn read_path<P: AsRef<Path>>(path: P, values: &mut HashMap<String, String>) -> a
|
|||||||
}
|
}
|
||||||
|
|
||||||
if path.is_file() {
|
if path.is_file() {
|
||||||
return read_from_file(path, values);
|
return read_key_value_file(path, values);
|
||||||
}
|
}
|
||||||
|
|
||||||
for entry in fs::read_dir(path)? {
|
for entry in fs::read_dir(path)? {
|
||||||
@@ -121,7 +121,7 @@ fn read_path<P: AsRef<Path>>(path: P, values: &mut HashMap<String, String>) -> a
|
|||||||
|
|
||||||
if path.is_file()
|
if path.is_file()
|
||||||
&& path.extension().unwrap_or_default() == "txt"
|
&& path.extension().unwrap_or_default() == "txt"
|
||||||
&& let Err(e) = read_from_file(&path, values)
|
&& let Err(e) = read_key_value_file(&path, values)
|
||||||
{
|
{
|
||||||
warn!("Failed to read sensor file {path:?}: {e}");
|
warn!("Failed to read sensor file {path:?}: {e}");
|
||||||
}
|
}
|
||||||
@@ -139,11 +139,11 @@ fn read_path<P: AsRef<Path>>(path: P, values: &mut HashMap<String, String>) -> a
|
|||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
///
|
///
|
||||||
/// * `path`: file path to read
|
/// * `path`: file path to read.
|
||||||
/// * `values`: HashMap to store read key-value pairs.
|
/// * `values`: HashMap to insert key-value pairs from the file.
|
||||||
///
|
///
|
||||||
/// returns: Result<(), Error>
|
/// returns: Result<(), Error>
|
||||||
fn read_from_file<P: AsRef<Path>>(
|
pub fn read_key_value_file<P: AsRef<Path>>(
|
||||||
path: P,
|
path: P,
|
||||||
values: &mut HashMap<String, String>,
|
values: &mut HashMap<String, String>,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
|
|||||||
@@ -53,6 +53,13 @@ Options:
|
|||||||
Single sensor value input file or directory for multiple sensor input files.
|
Single sensor value input file or directory for multiple sensor input files.
|
||||||
Default: `./cfg/sensors`
|
Default: `./cfg/sensors`
|
||||||
|
|
||||||
|
--sensor-mapping <SENSOR_MAPPING>
|
||||||
|
Sensor identifier mapping file. Ignored if the file does not exist.
|
||||||
|
|
||||||
|
The configuration file will be loaded from the `config_dir` directory if no full path is specified.
|
||||||
|
|
||||||
|
[default: sensor-mapping.cfg]
|
||||||
|
|
||||||
-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
|
||||||
|
|
||||||
|
|||||||
@@ -26,3 +26,28 @@ Only the file data source is supported at the moment, other sources like pipes,
|
|||||||
|
|
||||||
- Proof of concept [Linux shell scripts](provider/shell_scripts.md)
|
- Proof of concept [Linux shell scripts](provider/shell_scripts.md)
|
||||||
- [aster-sysinfo tool](provider/sysinfo.md)
|
- [aster-sysinfo tool](provider/sysinfo.md)
|
||||||
|
|
||||||
|
### Sensor Identifier Mapping
|
||||||
|
|
||||||
|
The original AOOSTAR-X software uses very weird label identifiers (actually sometimes even a composite key depending on
|
||||||
|
the data source), which are likely based on an internal JSON structure.
|
||||||
|
|
||||||
|
To easily use original custom sensor panels with various sensor data sources, a sensor identifier mapping file can be used.
|
||||||
|
|
||||||
|
The mapping file is a simple text file with one identifier mapping per line:
|
||||||
|
- Key = label identifier used in panel definition
|
||||||
|
- Value = label identifier used in sensor providers
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```
|
||||||
|
cpu_temperature: temperature_cpu
|
||||||
|
```
|
||||||
|
|
||||||
|
This maps the `temperature_cpu` sensor from the `aster-sysinfo` tool to the `cpu_temperature` sensor used in the
|
||||||
|
AOOSTAR-X panel definitions.
|
||||||
|
|
||||||
|
Usage example:
|
||||||
|
```shell
|
||||||
|
asterctl --config monitor.json --sensor-mapping sensor-mapping/sysinfo-to-aoostar.cfg
|
||||||
|
```
|
||||||
|
|||||||
Reference in New Issue
Block a user