knx_pico/addressing/
individual.rs

1//! KNX Individual Address implementation.
2//!
3//! Individual addresses identify physical devices on the KNX bus.
4//! Format: Area.Line.Device (e.g., 1.1.5)
5//! - Area: 0-15 (4 bits)
6//! - Line: 0-15 (4 bits)
7//! - Device: 0-255 (8 bits)
8
9use crate::error::{KnxError, Result};
10use core::fmt;
11
12/// KNX Individual Address (Area.Line.Device)
13///
14/// Used to identify physical devices on the KNX bus.
15///
16/// # Examples
17///
18/// ```
19/// use knx_pico::IndividualAddress;
20///
21/// // Create from components
22/// let addr = IndividualAddress::new(1, 1, 5).unwrap();
23/// assert_eq!(addr.to_string(), "1.1.5");
24///
25/// // Create from raw u16
26/// let addr = IndividualAddress::from(0x1105u16);
27/// assert_eq!(addr.area(), 1);
28/// assert_eq!(addr.line(), 1);
29/// assert_eq!(addr.device(), 5);
30///
31/// // Parse from string
32/// let addr: IndividualAddress = "1.1.5".parse().unwrap();
33/// assert_eq!(u16::from(addr), 0x1105);
34/// ```
35#[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    /// Maximum area value (4 bits)
43    pub const MAX_AREA: u8 = 15;
44    /// Maximum line value (4 bits)
45    pub const MAX_LINE: u8 = 15;
46    /// Maximum device value (8 bits)
47    pub const MAX_DEVICE: u8 = 255;
48
49    /// Create a new Individual Address from components.
50    ///
51    /// # Arguments
52    ///
53    /// * `area` - Area (0-15)
54    /// * `line` - Line (0-15)
55    /// * `device` - Device (0-255)
56    ///
57    /// # Errors
58    ///
59    /// Returns `KnxError::AddressOutOfRange` if any component is out of range.
60    ///
61    /// # Examples
62    ///
63    /// ```
64    /// use knx_pico::IndividualAddress;
65    ///
66    /// let addr = IndividualAddress::new(1, 1, 5)?;
67    /// assert_eq!(addr.to_string(), "1.1.5");
68    /// # Ok::<(), knx_pico::KnxError>(())
69    /// ```
70    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        // device is u8, so it's always in range
78
79        let raw = (u16::from(area) << 12) | (u16::from(line) << 8) | u16::from(device);
80        Ok(Self { raw })
81    }
82
83    /// Create from a 3-element array `[area, line, device]`.
84    ///
85    /// Convenient for creating addresses from array literals.
86    ///
87    /// # Examples
88    ///
89    /// ```
90    /// use knx_pico::IndividualAddress;
91    ///
92    /// let addr = IndividualAddress::from_array([1, 1, 5])?;
93    /// assert_eq!(addr.to_string(), "1.1.5");
94    /// # Ok::<(), knx_pico::KnxError>(())
95    /// ```
96    pub fn from_array(parts: [u8; 3]) -> Result<Self> {
97        Self::new(parts[0], parts[1], parts[2])
98    }
99
100    /// Get the raw u16 representation of the address.
101    #[inline(always)]
102    pub const fn raw(self) -> u16 {
103        self.raw
104    }
105
106    /// Get the area component (0-15).
107    #[inline(always)]
108    pub const fn area(self) -> u8 {
109        ((self.raw >> 12) & 0x0F) as u8
110    }
111
112    /// Get the line component (0-15).
113    #[inline(always)]
114    pub const fn line(self) -> u8 {
115        ((self.raw >> 8) & 0x0F) as u8
116    }
117
118    /// Get the device component (0-255).
119    #[inline(always)]
120    pub const fn device(self) -> u8 {
121        (self.raw & 0xFF) as u8
122    }
123
124    /// Encode the address into a byte buffer (big-endian).
125    ///
126    /// # Arguments
127    ///
128    /// * `buf` - Buffer to write to (must be at least 2 bytes)
129    ///
130    /// # Errors
131    ///
132    /// Returns `KnxError::BufferTooSmall` if buffer is too small.
133    #[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    /// Decode an address from a byte buffer (big-endian).
143    ///
144    /// # Arguments
145    ///
146    /// * `buf` - Buffer to read from (must be at least 2 bytes)
147    ///
148    /// # Errors
149    ///
150    /// Returns `KnxError::BufferTooSmall` if buffer is too small.
151    #[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        // Zero-allocation parsing using iterators
186        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        // Ensure no extra parts
204        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        // Too few parts
276        let result = "1.2".parse::<IndividualAddress>();
277        assert!(result.is_err());
278
279        // Out of range
280        let result = "16.0.0".parse::<IndividualAddress>();
281        assert!(result.is_err());
282
283        // Too many parts
284        let result = "1.2.3.4".parse::<IndividualAddress>();
285        assert!(result.is_err());
286
287        // Non-numeric
288        let result = "a.b.c".parse::<IndividualAddress>();
289        assert!(result.is_err());
290
291        // Empty
292        let result = "".parse::<IndividualAddress>();
293        assert!(result.is_err());
294    }
295}