knx_pico/addressing/
individual.rs1use crate::error::{KnxError, Result};
10use core::fmt;
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
36#[cfg_attr(feature = "defmt", derive(defmt::Format))]
37pub struct IndividualAddress {
38 raw: u16,
39}
40
41impl IndividualAddress {
42 pub const MAX_AREA: u8 = 15;
44 pub const MAX_LINE: u8 = 15;
46 pub const MAX_DEVICE: u8 = 255;
48
49 pub fn new(area: u8, line: u8, device: u8) -> Result<Self> {
71 if area > Self::MAX_AREA {
72 return Err(KnxError::address_out_of_range());
73 }
74 if line > Self::MAX_LINE {
75 return Err(KnxError::address_out_of_range());
76 }
77 let raw = (u16::from(area) << 12) | (u16::from(line) << 8) | u16::from(device);
80 Ok(Self { raw })
81 }
82
83 pub fn from_array(parts: [u8; 3]) -> Result<Self> {
97 Self::new(parts[0], parts[1], parts[2])
98 }
99
100 #[inline(always)]
102 pub const fn raw(self) -> u16 {
103 self.raw
104 }
105
106 #[inline(always)]
108 pub const fn area(self) -> u8 {
109 ((self.raw >> 12) & 0x0F) as u8
110 }
111
112 #[inline(always)]
114 pub const fn line(self) -> u8 {
115 ((self.raw >> 8) & 0x0F) as u8
116 }
117
118 #[inline(always)]
120 pub const fn device(self) -> u8 {
121 (self.raw & 0xFF) as u8
122 }
123
124 #[inline]
134 pub fn encode(&self, buf: &mut [u8]) -> Result<usize> {
135 if buf.len() < 2 {
136 return Err(KnxError::buffer_too_small());
137 }
138 buf[0..2].copy_from_slice(&self.raw.to_be_bytes());
139 Ok(2)
140 }
141
142 #[inline]
152 pub fn decode(buf: &[u8]) -> Result<Self> {
153 if buf.len() < 2 {
154 return Err(KnxError::buffer_too_small());
155 }
156 let raw = u16::from_be_bytes([buf[0], buf[1]]);
157 Ok(Self { raw })
158 }
159}
160
161impl fmt::Display for IndividualAddress {
162 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
163 write!(f, "{}.{}.{}", self.area(), self.line(), self.device())
164 }
165}
166
167impl From<u16> for IndividualAddress {
168 #[inline(always)]
169 fn from(raw: u16) -> Self {
170 Self { raw }
171 }
172}
173
174impl From<IndividualAddress> for u16 {
175 #[inline(always)]
176 fn from(addr: IndividualAddress) -> u16 {
177 addr.raw
178 }
179}
180
181impl core::str::FromStr for IndividualAddress {
182 type Err = KnxError;
183
184 fn from_str(s: &str) -> Result<Self> {
185 let mut parts = s.split('.');
187
188 let area = parts
189 .next()
190 .and_then(|s| s.parse::<u8>().ok())
191 .ok_or_else(KnxError::invalid_individual_address)?;
192
193 let line = parts
194 .next()
195 .and_then(|s| s.parse::<u8>().ok())
196 .ok_or_else(KnxError::invalid_individual_address)?;
197
198 let device = parts
199 .next()
200 .and_then(|s| s.parse::<u8>().ok())
201 .ok_or_else(KnxError::invalid_individual_address)?;
202
203 if parts.next().is_some() {
205 return Err(KnxError::invalid_individual_address());
206 }
207
208 Self::new(area, line, device)
209 }
210}
211
212#[cfg(test)]
213mod tests {
214 use super::*;
215
216 #[test]
217 fn test_new_valid() {
218 let addr = IndividualAddress::new(1, 2, 3).unwrap();
219 assert_eq!(addr.area(), 1);
220 assert_eq!(addr.line(), 2);
221 assert_eq!(addr.device(), 3);
222 }
223
224 #[test]
225 fn test_new_invalid_area() {
226 let result = IndividualAddress::new(16, 0, 0);
227 assert!(result.is_err());
228 }
229
230 #[test]
231 fn test_new_invalid_line() {
232 let result = IndividualAddress::new(0, 16, 0);
233 assert!(result.is_err());
234 }
235
236 #[test]
237 fn test_from_raw() {
238 let addr = IndividualAddress::from(0x1203u16);
239 assert_eq!(addr.area(), 1);
240 assert_eq!(addr.line(), 2);
241 assert_eq!(addr.device(), 3);
242 }
243
244 #[test]
245 fn test_to_raw() {
246 let addr = IndividualAddress::new(1, 2, 3).unwrap();
247 assert_eq!(u16::from(addr), 0x1203);
248 }
249
250 #[test]
251 fn test_encode_decode() {
252 let addr = IndividualAddress::new(15, 15, 255).unwrap();
253 let mut buf = [0u8; 2];
254 addr.encode(&mut buf).unwrap();
255 let decoded = IndividualAddress::decode(&buf).unwrap();
256 assert_eq!(addr, decoded);
257 }
258
259 #[test]
260 fn test_display() {
261 let addr = IndividualAddress::new(1, 2, 3).unwrap();
262 assert_eq!(format!("{}", addr), "1.2.3");
263 }
264
265 #[test]
266 fn test_from_str() {
267 let addr: IndividualAddress = "1.2.3".parse().unwrap();
268 assert_eq!(addr.area(), 1);
269 assert_eq!(addr.line(), 2);
270 assert_eq!(addr.device(), 3);
271 }
272
273 #[test]
274 fn test_from_str_invalid() {
275 let result = "1.2".parse::<IndividualAddress>();
277 assert!(result.is_err());
278
279 let result = "16.0.0".parse::<IndividualAddress>();
281 assert!(result.is_err());
282
283 let result = "1.2.3.4".parse::<IndividualAddress>();
285 assert!(result.is_err());
286
287 let result = "a.b.c".parse::<IndividualAddress>();
289 assert!(result.is_err());
290
291 let result = "".parse::<IndividualAddress>();
293 assert!(result.is_err());
294 }
295}