kenwood_thd75/aprs/
mcp_bridge.rs

1//! Bridge between MCP (radio memory) and runtime APRS types.
2//!
3//! The TH-D75 stores `SmartBeaconing` parameters in its MCP memory using
4//! mph-based units (see [`crate::types::aprs::McpSmartBeaconingConfig`]).
5//! The runtime [`aprs::SmartBeaconingConfig`] uses km/h instead so the
6//! algorithm arithmetic matches the `HamHUD` reference. This module
7//! provides the `From` conversion so callers can do:
8//!
9//! ```no_run
10//! use kenwood_thd75::types::aprs::McpSmartBeaconingConfig;
11//! use aprs::SmartBeaconingConfig;
12//!
13//! let mcp = McpSmartBeaconingConfig::default();
14//! let runtime: SmartBeaconingConfig = mcp.into();
15//! ```
16
17use aprs::SmartBeaconingConfig;
18
19use crate::types::aprs::McpSmartBeaconingConfig;
20
21/// Converts a radio-memory `SmartBeaconing` config (mph/seconds) to the
22/// runtime form (km/h / seconds / `f64`).
23///
24/// Field mapping (all rates are in seconds on both sides):
25///
26/// | MCP field    | Runtime field       | Conversion                          |
27/// |--------------|---------------------|-------------------------------------|
28/// | `low_speed`  | `low_speed_kmh`     | mph → km/h (× `1.609_344`)          |
29/// | `high_speed` | `high_speed_kmh`    | mph → km/h (× `1.609_344`)          |
30/// | `slow_rate`  | `slow_rate_secs`    | seconds (widened `u16` → `u32`)     |
31/// | `fast_rate`  | `fast_rate_secs`    | seconds (widened `u8` → `u32`)      |
32/// | `turn_slope` | `turn_slope`        | widened `u8` → `u16`                |
33/// | `turn_angle` | `turn_min_deg`      | widened `u8` → `f64`                |
34/// | `turn_time`  | `turn_time_secs`    | widened `u8` → `u32`                |
35impl From<McpSmartBeaconingConfig> for SmartBeaconingConfig {
36    fn from(mcp: McpSmartBeaconingConfig) -> Self {
37        const MPH_TO_KMH: f64 = 1.609_344;
38        Self {
39            low_speed_kmh: f64::from(mcp.low_speed) * MPH_TO_KMH,
40            high_speed_kmh: f64::from(mcp.high_speed) * MPH_TO_KMH,
41            slow_rate_secs: u32::from(mcp.slow_rate),
42            fast_rate_secs: u32::from(mcp.fast_rate),
43            turn_slope: u16::from(mcp.turn_slope),
44            turn_min_deg: f64::from(mcp.turn_angle),
45            turn_time_secs: u32::from(mcp.turn_time),
46        }
47    }
48}
49
50#[cfg(test)]
51mod tests {
52    use super::*;
53
54    #[test]
55    fn default_mcp_converts_to_runtime() {
56        let mcp = McpSmartBeaconingConfig::default();
57        let runtime: SmartBeaconingConfig = mcp.into();
58        // Default MCP: low_speed = 5 mph → 5 × 1.609344 ≈ 8.046 km/h
59        assert!((runtime.low_speed_kmh - 8.046_72).abs() < 1e-4);
60        // Default MCP: high_speed = 60 mph → 60 × 1.609344 ≈ 96.56 km/h
61        assert!((runtime.high_speed_kmh - 96.560_64).abs() < 1e-4);
62        assert_eq!(runtime.slow_rate_secs, 1800);
63        assert_eq!(runtime.fast_rate_secs, 60);
64        assert_eq!(runtime.turn_slope, 26);
65        assert!((runtime.turn_min_deg - 28.0).abs() < f64::EPSILON);
66        assert_eq!(runtime.turn_time_secs, 30);
67    }
68
69    #[test]
70    fn zero_mph_converts_to_zero_kmh() {
71        let mcp = McpSmartBeaconingConfig {
72            low_speed: 0,
73            high_speed: 0,
74            slow_rate: 0,
75            fast_rate: 0,
76            turn_angle: 0,
77            turn_slope: 0,
78            turn_time: 0,
79        };
80        let runtime: SmartBeaconingConfig = mcp.into();
81        assert!(runtime.low_speed_kmh.abs() < f64::EPSILON);
82        assert!(runtime.high_speed_kmh.abs() < f64::EPSILON);
83        assert_eq!(runtime.slow_rate_secs, 0);
84        assert_eq!(runtime.fast_rate_secs, 0);
85        assert_eq!(runtime.turn_slope, 0);
86        assert!(runtime.turn_min_deg.abs() < f64::EPSILON);
87        assert_eq!(runtime.turn_time_secs, 0);
88    }
89}