kenwood_thd75/types/echolink.rs
1//! `EchoLink` memory types (Menu No. 164).
2//!
3//! `EchoLink` is a `VoIP` system that links amateur radio stations over the
4//! internet. The TH-D75 supports 10 `EchoLink` memory slots for storing
5//! frequently used node numbers and their associated station names for
6//! quick access via DTMF dialing.
7//!
8//! Per User Manual Chapter 11:
9//!
10//! - `EchoLink` memory channels are separate from normal DTMF memory.
11//! - They do NOT store operating frequencies, tones, or power information.
12//! - Each slot stores a callsign/name (up to 8 characters) and a node
13//! number or DTMF code (up to 8 digits).
14//! - The radio supports `EchoLink` "Connect by Call" (prefix `C`) and
15//! "Query by Call" (prefix `07`) functions with automatic callsign-to-DTMF
16//! conversion.
17//! - When only a name is stored (no code), the "Connect Call" function
18//! automatically converts the callsign to DTMF with `C` prefix and `#` suffix.
19//!
20//! These types model `EchoLink` settings from the TH-D75's menu system.
21//! Derived from the capability gap analysis feature 138.
22
23// ---------------------------------------------------------------------------
24// EchoLink memory slot
25// ---------------------------------------------------------------------------
26
27/// An `EchoLink` memory slot.
28///
29/// The TH-D75 provides 10 `EchoLink` memory slots (0-9), each storing
30/// a station name and node number. Node numbers are dialed via DTMF
31/// to connect to the remote `EchoLink` station through a repeater's
32/// `EchoLink` interface.
33#[derive(Debug, Clone, PartialEq, Eq, Hash)]
34pub struct EchoLinkMemory {
35 /// Slot index (0-9).
36 pub slot: EchoLinkSlot,
37 /// Station name or callsign (up to 8 characters).
38 pub name: EchoLinkName,
39 /// `EchoLink` node number (up to 6 digits).
40 pub node_number: EchoLinkNode,
41}
42
43/// `EchoLink` memory slot index (0-9).
44#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
45pub struct EchoLinkSlot(u8);
46
47impl EchoLinkSlot {
48 /// Maximum slot index.
49 pub const MAX: u8 = 9;
50
51 /// Total number of `EchoLink` memory slots.
52 pub const COUNT: usize = 10;
53
54 /// Creates a new `EchoLink` memory slot index.
55 ///
56 /// # Errors
57 ///
58 /// Returns `None` if the index exceeds 9.
59 #[must_use]
60 pub const fn new(index: u8) -> Option<Self> {
61 if index <= Self::MAX {
62 Some(Self(index))
63 } else {
64 None
65 }
66 }
67
68 /// Returns the slot index.
69 #[must_use]
70 pub const fn index(self) -> u8 {
71 self.0
72 }
73}
74
75/// `EchoLink` station name (up to 8 characters).
76#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
77pub struct EchoLinkName(String);
78
79impl EchoLinkName {
80 /// Maximum length of an `EchoLink` station name.
81 pub const MAX_LEN: usize = 8;
82
83 /// Creates a new `EchoLink` station name.
84 ///
85 /// # Errors
86 ///
87 /// Returns `None` if the text exceeds 8 characters.
88 #[must_use]
89 pub fn new(text: &str) -> Option<Self> {
90 if text.len() <= Self::MAX_LEN {
91 Some(Self(text.to_owned()))
92 } else {
93 None
94 }
95 }
96
97 /// Returns the name as a string slice.
98 #[must_use]
99 pub fn as_str(&self) -> &str {
100 &self.0
101 }
102}
103
104/// `EchoLink` node number (up to 6 digits).
105///
106/// `EchoLink` node numbers are numeric identifiers assigned to each
107/// registered station. They are transmitted via DTMF tones through
108/// a repeater to initiate an `EchoLink` connection.
109#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
110pub struct EchoLinkNode(String);
111
112impl EchoLinkNode {
113 /// Maximum length of an `EchoLink` node number.
114 pub const MAX_LEN: usize = 6;
115
116 /// Creates a new `EchoLink` node number.
117 ///
118 /// # Errors
119 ///
120 /// Returns `None` if the string exceeds 6 characters or contains
121 /// non-digit characters.
122 #[must_use]
123 pub fn new(number: &str) -> Option<Self> {
124 if number.len() <= Self::MAX_LEN && number.chars().all(|c| c.is_ascii_digit()) {
125 Some(Self(number.to_owned()))
126 } else {
127 None
128 }
129 }
130
131 /// Returns the node number as a string slice.
132 #[must_use]
133 pub fn as_str(&self) -> &str {
134 &self.0
135 }
136
137 /// Returns `true` if the node number is empty.
138 #[must_use]
139 pub const fn is_empty(&self) -> bool {
140 self.0.is_empty()
141 }
142}
143
144// ---------------------------------------------------------------------------
145// Tests
146// ---------------------------------------------------------------------------
147
148#[cfg(test)]
149mod tests {
150 use super::*;
151
152 #[test]
153 fn echolink_slot_valid_range() {
154 for i in 0u8..=9 {
155 assert!(EchoLinkSlot::new(i).is_some());
156 }
157 }
158
159 #[test]
160 fn echolink_slot_invalid() {
161 assert!(EchoLinkSlot::new(10).is_none());
162 }
163
164 #[test]
165 fn echolink_slot_index() {
166 let slot = EchoLinkSlot::new(5).unwrap();
167 assert_eq!(slot.index(), 5);
168 }
169
170 #[test]
171 fn echolink_name_valid() {
172 let name = EchoLinkName::new("W1AW").unwrap();
173 assert_eq!(name.as_str(), "W1AW");
174 }
175
176 #[test]
177 fn echolink_name_max_length() {
178 let name = EchoLinkName::new("12345678").unwrap();
179 assert_eq!(name.as_str().len(), 8);
180 }
181
182 #[test]
183 fn echolink_name_too_long() {
184 assert!(EchoLinkName::new("123456789").is_none());
185 }
186
187 #[test]
188 fn echolink_node_valid() {
189 let node = EchoLinkNode::new("123456").unwrap();
190 assert_eq!(node.as_str(), "123456");
191 assert!(!node.is_empty());
192 }
193
194 #[test]
195 fn echolink_node_short() {
196 let node = EchoLinkNode::new("1").unwrap();
197 assert_eq!(node.as_str(), "1");
198 }
199
200 #[test]
201 fn echolink_node_empty() {
202 let node = EchoLinkNode::new("").unwrap();
203 assert!(node.is_empty());
204 }
205
206 #[test]
207 fn echolink_node_too_long() {
208 assert!(EchoLinkNode::new("1234567").is_none());
209 }
210
211 #[test]
212 fn echolink_node_non_digit() {
213 assert!(EchoLinkNode::new("12A456").is_none());
214 }
215
216 #[test]
217 fn echolink_node_special_chars() {
218 assert!(EchoLinkNode::new("12*456").is_none());
219 }
220}