115 lines
3.2 KiB
Rust
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(())
|
|
});
|
|
}
|