use serde::{Deserialize, Serialize};
use std::net::{IpAddr, Ipv4Addr};
use std::path::Path;
fn default_bind_addr() -> IpAddr {
IpAddr::V4(Ipv4Addr::LOCALHOST)
}
#[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,
#[serde(default)]
pub gimbal: GimbalConfig,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub struct GimbalConfig {
#[serde(default = "default_yaw_corrector_enabled")]
pub yaw_corrector_enabled: bool,
}
fn default_yaw_corrector_enabled() -> bool {
true
}
impl Default for GimbalConfig {
fn default() -> Self {
Self {
yaw_corrector_enabled: default_yaw_corrector_enabled(),
}
}
}
#[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(),
gimbal: GimbalConfig::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_ng::from_str(&contents)?;
Ok(config)
}
pub fn save_to_file<P: AsRef<Path>>(&self, path: P) -> crate::error::Result<()> {
let contents = serde_yaml_ng::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()
}
}
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
mod tests {
use super::*;
fn yaml_without_gimbal_section() -> &'static str {
r#"
device:
path: auto
ipc:
socket_path: /tmp/turret.sock
mavlink:
enabled: true
udp_port: 14550
sysid: 1
compid: 154
logging:
level: info
"#
}
#[test]
fn yaw_corrector_enabled_defaults_to_true_when_section_omitted() {
let cfg: DaemonConfig = serde_yaml_ng::from_str(yaml_without_gimbal_section()).unwrap();
assert!(cfg.gimbal.yaw_corrector_enabled);
}
#[test]
fn yaw_corrector_enabled_defaults_to_true_when_section_empty() {
let mut yaml = String::from(yaml_without_gimbal_section());
yaml.push_str("gimbal: {}\n");
let cfg: DaemonConfig = serde_yaml_ng::from_str(&yaml).unwrap();
assert!(cfg.gimbal.yaw_corrector_enabled);
}
#[test]
fn yaw_corrector_enabled_can_be_explicitly_disabled() {
let mut yaml = String::from(yaml_without_gimbal_section());
yaml.push_str("gimbal:\n yaw_corrector_enabled: false\n");
let cfg: DaemonConfig = serde_yaml_ng::from_str(&yaml).unwrap();
assert!(!cfg.gimbal.yaw_corrector_enabled);
}
#[test]
fn daemon_config_default_enables_corrector() {
assert!(DaemonConfig::default().gimbal.yaw_corrector_enabled);
}
}