Hassliebe/src/modules/buttons.rs

115 lines
3.2 KiB
Rust

use anyhow::Result;
use serde::Deserialize;
use tokio::process::Command;
use validator::Validate;
use crate::modules::InitializationContext;
const MODULE_ID: &str = "buttons";
const BUTTON_TRIGGER_TEXT: &str = "press";
#[derive(Deserialize, Validate, Clone)]
pub struct ButtonConfig {
#[validate(custom(function = "crate::util::validate_hass_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(Deserialize, Validate)]
pub struct Config {
#[serde(default)]
pub enabled: bool,
#[serde(default)]
pub buttons: Vec<ButtonConfig>,
}
pub async fn init(context: &mut InitializationContext) -> Result<()> {
let full_context = context.get_full();
let config = match &full_context.config.modules.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 InitializationContext, config: ButtonConfig) -> Result<()> {
let entity_id = context.full.get_entity_id(MODULE_ID, &config.id);
let command_topic = context.full.mqtt.get_homeassistant_topic("button", &entity_id, "trigger");
context
.publish_to_owned_mqtt_topic(
&context.full.mqtt.get_homeassistant_topic("button", &entity_id, "config"),
json::stringify(json::object! {
"availability_topic": context.full.mqtt.availability_topic.clone(),
"command_topic": command_topic.as_str(),
"device": context.full.mqtt.discovery_device_object.clone(),
"name": config.name,
"payload_press": BUTTON_TRIGGER_TEXT,
"object_id": entity_id.as_str(),
"unique_id": entity_id.as_str()
}),
)
.await?;
context.subscribe_mqtt_topic(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(())
});
}