118 lines
3.9 KiB
Rust
118 lines
3.9 KiB
Rust
use std::collections::{HashMap, HashSet};
|
||
use std::path::{Path, PathBuf};
|
||
use std::process::exit;
|
||
use std::sync::Arc;
|
||
|
||
use anyhow::{anyhow, Result};
|
||
|
||
use crate::modules::{InitializationContext, ModuleContext, ModuleContextMqtt};
|
||
use crate::mqtt::OwnedTopicsService;
|
||
use crate::util::generate_alphanumeric_id;
|
||
|
||
mod config;
|
||
mod modules;
|
||
mod mqtt;
|
||
mod util;
|
||
|
||
struct Paths {
|
||
data_directory: Box<Path>,
|
||
config: Box<Path>,
|
||
}
|
||
|
||
async fn get_paths_and_create_directories() -> Result<Paths> {
|
||
let project_dirs = directories::ProjectDirs::from("", "", "Hassliebe");
|
||
|
||
let paths = Paths {
|
||
config: std::env::var_os("HASS_CONFIG")
|
||
.map(PathBuf::from)
|
||
// I don’t like this clone
|
||
.or(project_dirs.clone().map(|d| d.config_dir().to_path_buf().join("config.toml")))
|
||
.ok_or_else(|| anyhow!("Please specify a config file via HASS_CONFIG"))?
|
||
.into_boxed_path(),
|
||
data_directory: std::env::var_os("HASS_DATA_DIR")
|
||
.map(PathBuf::from)
|
||
.or(project_dirs.map(|d| d.data_dir().to_path_buf()))
|
||
.ok_or_else(|| anyhow!("Please specify a data directory via HASS_DATA_DIR"))?
|
||
.into_boxed_path(),
|
||
};
|
||
|
||
if let Some(p) = paths.config.parent() {
|
||
tokio::fs::create_dir_all(p).await?;
|
||
}
|
||
|
||
tokio::fs::create_dir_all(&paths.data_directory).await?;
|
||
|
||
Ok(paths)
|
||
}
|
||
|
||
fn initialize_logger() {
|
||
let mut builder = env_logger::builder();
|
||
builder.parse_filters("warn,hassliebe=info");
|
||
builder.parse_default_env();
|
||
builder.init();
|
||
}
|
||
|
||
async fn load_machine_id(paths: &Paths) -> Result<String> {
|
||
let overwrite_path = paths.data_directory.join("machine_id");
|
||
let mut value = tokio::fs::read_to_string(&overwrite_path).await.unwrap_or("".to_owned()).trim().to_owned();
|
||
|
||
if value.is_empty() {
|
||
log::debug!("{} does not exist or is empty", overwrite_path.to_string_lossy());
|
||
|
||
match tokio::fs::read_to_string("/etc/machine-id").await {
|
||
Ok(id) => {
|
||
value = id.trim().to_owned();
|
||
log::debug!("Found machine ID in /etc/machine-id: {}", value);
|
||
}
|
||
Err(_) => {
|
||
log::debug!("Could not read /etc/machine-id");
|
||
|
||
value = generate_alphanumeric_id(32);
|
||
log::info!("Generated new machine ID: {}", value);
|
||
tokio::fs::write(overwrite_path, &value).await?;
|
||
}
|
||
}
|
||
} else {
|
||
log::debug!("Found machine ID in {}: {}", overwrite_path.to_string_lossy(), value);
|
||
}
|
||
|
||
Ok(value)
|
||
}
|
||
|
||
#[tokio::main]
|
||
async fn main() -> Result<()> {
|
||
initialize_logger();
|
||
|
||
let paths = get_paths_and_create_directories().await?;
|
||
let machine_id = load_machine_id(&paths).await?;
|
||
|
||
let Some(config) = config::load(&paths.config)? else {
|
||
exit(exitcode::CONFIG);
|
||
};
|
||
|
||
let availability_topic = config.friendly_id.to_owned() + "/availability";
|
||
let (mqtt_client, event_loop) = mqtt::create_client(&config, &machine_id, &availability_topic).await?;
|
||
let discovery_device_object = mqtt::create_discovery_device_object(&config, &machine_id);
|
||
|
||
let owned_topics_service = OwnedTopicsService::new(&paths.data_directory).await?;
|
||
|
||
let mut initialization_context = InitializationContext {
|
||
owned_mqtt_topics: HashSet::new(),
|
||
message_handler_by_mqtt_topic: HashMap::new(),
|
||
|
||
full: Arc::new(ModuleContext {
|
||
config,
|
||
machine_id,
|
||
mqtt: ModuleContextMqtt {
|
||
client: mqtt_client,
|
||
availability_topic,
|
||
discovery_device_object,
|
||
},
|
||
// dbus_session_connection: zbus::Connection::session().await.context("while connecting to the D-Bus session bus")?
|
||
}),
|
||
};
|
||
|
||
modules::init_all(&mut initialization_context).await?;
|
||
|
||
mqtt::start_communication(&initialization_context, event_loop, owned_topics_service).await
|
||
}
|