mirror of
https://codeberg.org/icewind/taspromto.git
synced 2026-06-03 08:34:21 +02:00
allow using config file instead of env
This commit is contained in:
parent
8687197051
commit
7f7ad384a8
7 changed files with 357 additions and 33 deletions
117
src/config.rs
117
src/config.rs
|
|
@ -1,23 +1,86 @@
|
|||
use crate::device::{BDAddr, RfDeviceId};
|
||||
use color_eyre::{eyre::WrapErr, Report, Result};
|
||||
use rumqttc::MqttOptions;
|
||||
use serde::Deserialize;
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
use std::fs::read_to_string;
|
||||
use std::net::{IpAddr, Ipv4Addr};
|
||||
use std::path::Path;
|
||||
use std::str::FromStr;
|
||||
use std::time::Duration;
|
||||
|
||||
#[derive(Default)]
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Config {
|
||||
pub mqtt_host: String,
|
||||
pub mqtt_port: u16,
|
||||
pub host_port: u16,
|
||||
pub mi_temp_names: BTreeMap<BDAddr, String>,
|
||||
pub rf_temp_names: HashMap<RfDeviceId<'static>, String>,
|
||||
pub mqtt_credentials: Option<Credentials>,
|
||||
pub listen: ListenConfig,
|
||||
pub names: NamesConfig,
|
||||
pub mqtt: MqttConfig,
|
||||
}
|
||||
|
||||
pub struct Credentials {
|
||||
username: String,
|
||||
password: String,
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum ListenConfig {
|
||||
Ip {
|
||||
#[serde(default = "default_address")]
|
||||
address: IpAddr,
|
||||
port: u16,
|
||||
},
|
||||
Unix {
|
||||
path: String,
|
||||
},
|
||||
}
|
||||
|
||||
fn default_address() -> IpAddr {
|
||||
Ipv4Addr::UNSPECIFIED.into()
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct NamesConfig {
|
||||
#[serde(rename = "mitemp")]
|
||||
pub mi_temp: BTreeMap<BDAddr, String>,
|
||||
#[serde(rename = "rftemp")]
|
||||
pub rf_temp: HashMap<RfDeviceId<'static>, String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct MqttConfig {
|
||||
#[serde(rename = "hostname")]
|
||||
host: String,
|
||||
#[serde(default = "default_mqtt_port")]
|
||||
port: u16,
|
||||
#[serde(flatten)]
|
||||
credentials: Option<Credentials>,
|
||||
}
|
||||
|
||||
fn default_mqtt_port() -> u16 {
|
||||
1883
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum Credentials {
|
||||
Raw {
|
||||
username: String,
|
||||
password: String,
|
||||
},
|
||||
File {
|
||||
username: String,
|
||||
password_file: String,
|
||||
},
|
||||
}
|
||||
|
||||
impl Credentials {
|
||||
pub fn username(&self) -> String {
|
||||
match self {
|
||||
Credentials::Raw { username, .. } => username.clone(),
|
||||
Credentials::File { username, .. } => username.clone(),
|
||||
}
|
||||
}
|
||||
pub fn password(&self) -> String {
|
||||
match self {
|
||||
Credentials::Raw { password, .. } => password.clone(),
|
||||
Credentials::File { password_file, .. } => secretfile::load(password_file).unwrap(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Config {
|
||||
|
|
@ -68,32 +131,44 @@ impl Config {
|
|||
Ok(username) => {
|
||||
let password = dotenvy::var("MQTT_PASSWORD")
|
||||
.wrap_err("MQTT_USERNAME set, but MQTT_PASSWORD not set")?;
|
||||
Some(Credentials { username, password })
|
||||
Some(Credentials::Raw { username, password })
|
||||
}
|
||||
Err(_) => None,
|
||||
};
|
||||
|
||||
Ok(Config {
|
||||
mqtt_host,
|
||||
mqtt_port,
|
||||
host_port,
|
||||
mi_temp_names,
|
||||
rf_temp_names,
|
||||
mqtt_credentials,
|
||||
listen: ListenConfig::Ip {
|
||||
port: host_port,
|
||||
address: default_address(),
|
||||
},
|
||||
names: NamesConfig {
|
||||
mi_temp: mi_temp_names,
|
||||
rf_temp: rf_temp_names,
|
||||
},
|
||||
mqtt: MqttConfig {
|
||||
port: mqtt_port,
|
||||
host: mqtt_host,
|
||||
credentials: mqtt_credentials,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Config> {
|
||||
let raw = read_to_string(path)?;
|
||||
Ok(toml::from_str(&raw)?)
|
||||
}
|
||||
|
||||
pub fn mqtt(&self) -> Result<MqttOptions> {
|
||||
let hostname = hostname::get()?
|
||||
.into_string()
|
||||
.map_err(|_| Report::msg("invalid hostname"))?;
|
||||
let mut mqtt_options = MqttOptions::new(
|
||||
format!("taspromto-{}", hostname),
|
||||
&self.mqtt_host,
|
||||
self.mqtt_port,
|
||||
&self.mqtt.host,
|
||||
self.mqtt.port,
|
||||
);
|
||||
if let Some(credentials) = self.mqtt_credentials.as_ref() {
|
||||
mqtt_options.set_credentials(&credentials.username, &credentials.password);
|
||||
if let Some(credentials) = self.mqtt.credentials.as_ref() {
|
||||
mqtt_options.set_credentials(credentials.username(), credentials.password());
|
||||
}
|
||||
mqtt_options.set_keep_alive(Duration::from_secs(5));
|
||||
Ok(mqtt_options)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
use color_eyre::{eyre::WrapErr, Report, Result};
|
||||
use jzon::JsonValue;
|
||||
use rumqttc::{AsyncClient, QoS};
|
||||
use serde::de::Error;
|
||||
use serde::{Deserialize, Deserializer};
|
||||
use std::borrow::Cow;
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
use std::convert::TryFrom;
|
||||
|
|
@ -565,6 +567,16 @@ pub struct BDAddr {
|
|||
pub address: [u8; 6usize],
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for BDAddr {
|
||||
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let str = <Cow<'de, str>>::deserialize(deserializer)?;
|
||||
Self::from_mi_temp_mac_part(&str).map_err(D::Error::custom)
|
||||
}
|
||||
}
|
||||
|
||||
impl BDAddr {
|
||||
/// parse BDAddr from the last 6 characters of the mac address
|
||||
/// first 6 characters are always set to 582D34
|
||||
|
|
@ -806,6 +818,16 @@ impl RfDeviceId<'_> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for RfDeviceId<'static> {
|
||||
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let str = <Cow<'de, str>>::deserialize(deserializer)?;
|
||||
Self::from_str(&str).map_err(D::Error::custom)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for RfDeviceId<'static> {
|
||||
type Err = ParseIntError;
|
||||
|
||||
|
|
|
|||
35
src/main.rs
35
src/main.rs
|
|
@ -3,13 +3,14 @@ mod device;
|
|||
mod mqtt;
|
||||
mod topic;
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::config::{Config, ListenConfig};
|
||||
use crate::device::{
|
||||
format_device_state, format_dsmr_state, format_mi_temp_state, format_rf_temp_state, Device,
|
||||
DeviceStates,
|
||||
};
|
||||
use crate::mqtt::mqtt_stream;
|
||||
use crate::topic::Topic;
|
||||
use clap::Parser;
|
||||
use color_eyre::{eyre::WrapErr, Result};
|
||||
|
||||
use pin_utils::pin_mut;
|
||||
|
|
@ -18,14 +19,28 @@ use rumqttc::{AsyncClient, Publish, QoS};
|
|||
use std::pin::Pin;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::time::Instant;
|
||||
use tokio::net::UnixListener;
|
||||
use tokio::task::spawn;
|
||||
use tokio::time::{sleep, Duration};
|
||||
use tokio_stream::wrappers::UnixListenerStream;
|
||||
use tokio_stream::{Stream, StreamExt};
|
||||
use warp::Filter;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(author, version, about, long_about = None)]
|
||||
struct Args {
|
||||
/// Config file to use, if omitted the config will be loaded from environment variables
|
||||
config: Option<String>,
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
let config = Config::from_env()?;
|
||||
let args = Args::parse();
|
||||
|
||||
let config = match args.config {
|
||||
Some(path) => Config::from_file(path)?,
|
||||
_ => Config::from_env()?,
|
||||
};
|
||||
let mqtt_options = config.mqtt()?;
|
||||
|
||||
let device_states = <Arc<Mutex<DeviceStates>>>::default();
|
||||
|
|
@ -57,9 +72,8 @@ async fn main() -> Result<()> {
|
|||
}
|
||||
|
||||
async fn serve(device_states: Arc<Mutex<DeviceStates>>, config: Config) {
|
||||
let host_port = config.host_port;
|
||||
let mi_temp_names = config.mi_temp_names.clone();
|
||||
let rf_temp_names = config.rf_temp_names.clone();
|
||||
let mi_temp_names = config.names.mi_temp.clone();
|
||||
let rf_temp_names = config.names.rf_temp.clone();
|
||||
|
||||
let state = warp::any().map(move || device_states.clone());
|
||||
|
||||
|
|
@ -83,7 +97,16 @@ async fn serve(device_states: Arc<Mutex<DeviceStates>>, config: Config) {
|
|||
response
|
||||
});
|
||||
|
||||
warp::serve(metrics).run(([0, 0, 0, 0], host_port)).await;
|
||||
match config.listen {
|
||||
ListenConfig::Ip { address, port } => {
|
||||
warp::serve(metrics).run((address, port)).await;
|
||||
}
|
||||
ListenConfig::Unix { path } => {
|
||||
let listener = UnixListener::bind(path).unwrap();
|
||||
let incoming = UnixListenerStream::new(listener);
|
||||
warp::serve(metrics).run_incoming(incoming).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn command(client: &AsyncClient, device: &Device, command: &str, body: &str) -> Result<()> {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue