use serde::{Deserialize, Serialize};
use std::net::{IpAddr, Ipv4Addr};
use std::path::Path;
fn default_bind_addr() -> IpAddr {
IpAddr::V4(Ipv4Addr::UNSPECIFIED)
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DaemonConfig {
pub device: DeviceConfig,
pub ipc: IpcConfig,
pub mavlink: MavlinkConfig,
pub logging: LoggingConfig,
#[serde(default)]
pub arbitrator: ArbitratorConfig,
}
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
pub struct ArbitratorConfig {
#[serde(default)]
pub min_command_interval_ms: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DeviceConfig {
pub path: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct IpcConfig {
pub socket_path: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum Transport {
#[default]
Udp,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MavlinkConfig {
pub enabled: bool,
#[serde(default)]
pub transport: Transport,
#[serde(default = "default_bind_addr")]
pub bind_addr: IpAddr,
pub udp_port: u16,
pub sysid: u8,
pub compid: u8,
#[serde(default)]
pub limits: GimbalLimits,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub struct GimbalLimits {
pub pitch_min_deg: f32,
pub pitch_max_deg: f32,
pub roll_min_deg: f32,
pub roll_max_deg: f32,
pub yaw_min_deg: f32,
pub yaw_max_deg: f32,
}
impl Default for GimbalLimits {
fn default() -> Self {
Self {
pitch_min_deg: -90.0,
pitch_max_deg: 90.0,
roll_min_deg: -180.0,
roll_max_deg: 180.0,
yaw_min_deg: -180.0,
yaw_max_deg: 180.0,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LoggingConfig {
pub level: String,
}
impl Default for DaemonConfig {
fn default() -> Self {
Self {
device: DeviceConfig {
path: "auto".to_string(),
},
ipc: IpcConfig {
socket_path: "/tmp/turret.sock".to_string(),
},
mavlink: MavlinkConfig {
enabled: true,
transport: Transport::Udp,
bind_addr: default_bind_addr(),
udp_port: 14550,
sysid: 1,
compid: 154, limits: GimbalLimits::default(),
},
logging: LoggingConfig {
level: "info".to_string(),
},
arbitrator: ArbitratorConfig::default(),
}
}
}
impl DaemonConfig {
pub fn load_from_file<P: AsRef<Path>>(path: P) -> crate::error::Result<Self> {
let contents = std::fs::read_to_string(path)?;
let config: DaemonConfig = serde_yaml::from_str(&contents)?;
Ok(config)
}
pub fn save_to_file<P: AsRef<Path>>(&self, path: P) -> crate::error::Result<()> {
let contents = serde_yaml::to_string(self)?;
std::fs::write(path, contents)?;
Ok(())
}
pub fn load_or_default<P: AsRef<Path>>(path: P) -> Self {
match Self::load_from_file(path) {
Ok(config) => config,
Err(e) => {
tracing::warn!("Failed to load config file: {}. Using defaults.", e);
Self::default()
}
}
}
}