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}