Hassliebe/src/modules/command_buttons.rs

118 lines
3.2 KiB
Rust

use anyhow::Result;
use serde::{Deserialize, Serialize};
use tokio::process::Command;
use validator::Validate;
use crate::config::validate_unique_id;
use super::ModuleContext;
const MODULE_ID: &str = "power";
const BUTTON_TRIGGER_TEXT: &str = "press";
#[derive(Serialize, Deserialize, Validate, Clone)]
pub(crate) struct ButtonConfig {
#[validate(custom = "validate_unique_id")]
pub id: String,
#[validate(length(min = 1))]
pub name: String,
#[validate(length(min = 1))]
pub command: String,
#[serde(default)]
pub run_in_shell: bool,
}
#[derive(Serialize, Deserialize, Validate)]
pub(crate) struct Config {
#[serde(default)]
pub enabled: bool,
#[serde(default)]
pub buttons: Vec<ButtonConfig>,
}
pub(crate) async fn init(context: &mut ModuleContext<'_>) -> Result<()> {
let config = match &context.config.command_buttons {
Some(c) if c.enabled => c,
_ => return Ok(())
};
log::info!("Initializing…");
for button in config.buttons.iter() {
init_command_button(context, button.clone()).await?;
}
Ok(())
}
async fn init_command_button(context: &mut ModuleContext<'_>, config: ButtonConfig) -> Result<()> {
let entity_id = context.get_entity_id(MODULE_ID, &config.id);
let command_topic = context.mqtt.get_topic("button", &entity_id, "trigger");
context
.mqtt
.send_retained_message(
context.mqtt.get_topic("button", &entity_id, "config"),
json::stringify(json::object! {
"availability_topic": context.mqtt.availability_topic,
"command_topic": command_topic.as_str(),
"device": context.mqtt.discovery_device_object.clone(),
"icon": "mdi:power",
"name": config.name,
"payload_press": BUTTON_TRIGGER_TEXT,
"object_id": entity_id.as_str(),
"unique_id": entity_id.as_str()
}),
)
.await?;
context.mqtt.subscribe(command_topic, move |text| {
if text == BUTTON_TRIGGER_TEXT {
run_command(config.command.clone(), config.run_in_shell);
} else {
log::warn!("Received invalid trigger text for button {}", config.id)
}
Ok(())
});
Ok(())
}
fn run_command(command: String, in_shell: bool) {
tokio::spawn(async move {
let is_dry_run = cfg!(feature = "dry_run");
let status = {
log::info!("Executing command{}: {}", if is_dry_run { " (dry run)" } else { "" }, command);
let mut command_parts = command.split(' ').collect::<Vec<_>>();
let mut actual_command = if in_shell {
let mut c = Command::new("/bin/sh");
c.arg("-lc");
c
} else {
let c = Command::new(command_parts[0]);
command_parts.remove(0);
c
};
actual_command.args(command_parts);
if is_dry_run {
None
} else {
Some(actual_command.status())
}
};
if let Some(f) = status {
f.await?;
}
anyhow::Ok(())
});
}