Skip to main content

mabi_knx/
address.rs

1//! KNX address types.
2//!
3//! This module provides implementations for KNX individual addresses (physical addresses)
4//! and group addresses used in KNX communication.
5
6use std::fmt;
7use std::str::FromStr;
8
9use serde::{Deserialize, Serialize};
10
11use crate::error::KnxError;
12
13// ============================================================================
14// Individual Address (Physical Address)
15// ============================================================================
16
17/// KNX Individual Address (physical address).
18///
19/// Format: Area.Line.Device (e.g., "1.2.3")
20/// - Area: 0-15 (4 bits)
21/// - Line: 0-15 (4 bits)
22/// - Device: 0-255 (8 bits)
23///
24/// Total: 16 bits
25#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
26pub struct IndividualAddress {
27    /// Area (0-15).
28    area: u8,
29    /// Line (0-15).
30    line: u8,
31    /// Device (0-255).
32    device: u8,
33}
34
35impl IndividualAddress {
36    /// Maximum area value.
37    pub const MAX_AREA: u8 = 15;
38    /// Maximum line value.
39    pub const MAX_LINE: u8 = 15;
40    /// Maximum device value.
41    pub const MAX_DEVICE: u8 = 255;
42
43    /// Create a new individual address.
44    ///
45    /// # Panics
46    /// Panics if area > 15 or line > 15.
47    pub fn new(area: u8, line: u8, device: u8) -> Self {
48        assert!(area <= Self::MAX_AREA, "Area must be 0-15");
49        assert!(line <= Self::MAX_LINE, "Line must be 0-15");
50        Self { area, line, device }
51    }
52
53    /// Try to create a new individual address with validation.
54    pub fn try_new(area: u8, line: u8, device: u8) -> Result<Self, KnxError> {
55        if area > Self::MAX_AREA {
56            return Err(KnxError::AddressOutOfRange {
57                address: format!("area={}", area),
58                valid_range: "0-15".to_string(),
59            });
60        }
61        if line > Self::MAX_LINE {
62            return Err(KnxError::AddressOutOfRange {
63                address: format!("line={}", line),
64                valid_range: "0-15".to_string(),
65            });
66        }
67        Ok(Self { area, line, device })
68    }
69
70    /// Get the area component.
71    #[inline]
72    pub fn area(&self) -> u8 {
73        self.area
74    }
75
76    /// Get the line component.
77    #[inline]
78    pub fn line(&self) -> u8 {
79        self.line
80    }
81
82    /// Get the device component.
83    #[inline]
84    pub fn device(&self) -> u8 {
85        self.device
86    }
87
88    /// Encode to 16-bit value.
89    #[inline]
90    pub fn encode(&self) -> u16 {
91        ((self.area as u16) << 12) | ((self.line as u16) << 8) | (self.device as u16)
92    }
93
94    /// Decode from 16-bit value.
95    #[inline]
96    pub fn decode(value: u16) -> Self {
97        Self {
98            area: ((value >> 12) & 0x0F) as u8,
99            line: ((value >> 8) & 0x0F) as u8,
100            device: (value & 0xFF) as u8,
101        }
102    }
103
104    /// Encode to byte array (big-endian).
105    pub fn to_bytes(&self) -> [u8; 2] {
106        self.encode().to_be_bytes()
107    }
108
109    /// Decode from byte array (big-endian).
110    pub fn from_bytes(bytes: [u8; 2]) -> Self {
111        Self::decode(u16::from_be_bytes(bytes))
112    }
113
114    /// Check if this is a broadcast address (0.0.0).
115    #[inline]
116    pub fn is_broadcast(&self) -> bool {
117        self.area == 0 && self.line == 0 && self.device == 0
118    }
119
120    /// Check if this is a valid device address (not 0.0.0).
121    #[inline]
122    pub fn is_valid_device(&self) -> bool {
123        !self.is_broadcast()
124    }
125}
126
127impl Default for IndividualAddress {
128    fn default() -> Self {
129        Self::new(1, 1, 1)
130    }
131}
132
133impl fmt::Display for IndividualAddress {
134    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
135        write!(f, "{}.{}.{}", self.area, self.line, self.device)
136    }
137}
138
139impl FromStr for IndividualAddress {
140    type Err = KnxError;
141
142    fn from_str(s: &str) -> Result<Self, Self::Err> {
143        let parts: Vec<&str> = s.split('.').collect();
144        if parts.len() != 3 {
145            return Err(KnxError::InvalidIndividualAddress(format!(
146                "Expected format 'area.line.device', got '{}'",
147                s
148            )));
149        }
150
151        let area: u8 = parts[0]
152            .parse()
153            .map_err(|_| KnxError::InvalidIndividualAddress(format!("Invalid area: {}", parts[0])))?;
154        let line: u8 = parts[1]
155            .parse()
156            .map_err(|_| KnxError::InvalidIndividualAddress(format!("Invalid line: {}", parts[1])))?;
157        let device: u8 = parts[2].parse().map_err(|_| {
158            KnxError::InvalidIndividualAddress(format!("Invalid device: {}", parts[2]))
159        })?;
160
161        Self::try_new(area, line, device)
162            .map_err(|_| KnxError::InvalidIndividualAddress(s.to_string()))
163    }
164}
165
166impl From<u16> for IndividualAddress {
167    fn from(value: u16) -> Self {
168        Self::decode(value)
169    }
170}
171
172impl From<IndividualAddress> for u16 {
173    fn from(addr: IndividualAddress) -> Self {
174        addr.encode()
175    }
176}
177
178// ============================================================================
179// Group Address
180// ============================================================================
181
182/// KNX Group Address.
183///
184/// Supports multiple formats:
185/// - 3-level: Main/Middle/Sub (e.g., "1/2/3")
186/// - 2-level: Main/Sub (e.g., "1/2048")
187/// - Free: Raw 16-bit value (e.g., "12345")
188///
189/// 3-level format (most common):
190/// - Main: 0-31 (5 bits)
191/// - Middle: 0-7 (3 bits)
192/// - Sub: 0-255 (8 bits)
193#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
194pub struct GroupAddress {
195    raw: u16,
196}
197
198impl GroupAddress {
199    /// Maximum main group value (3-level).
200    pub const MAX_MAIN: u8 = 31;
201    /// Maximum middle group value (3-level).
202    pub const MAX_MIDDLE: u8 = 7;
203    /// Maximum sub group value (3-level).
204    pub const MAX_SUB: u8 = 255;
205
206    /// Create from 3-level format (main/middle/sub).
207    ///
208    /// # Panics
209    /// Panics if values are out of range.
210    pub fn three_level(main: u8, middle: u8, sub: u8) -> Self {
211        assert!(main <= Self::MAX_MAIN, "Main must be 0-31");
212        assert!(middle <= Self::MAX_MIDDLE, "Middle must be 0-7");
213
214        let raw =
215            ((main as u16 & 0x1F) << 11) | ((middle as u16 & 0x07) << 8) | (sub as u16 & 0xFF);
216        Self { raw }
217    }
218
219    /// Try to create from 3-level format with validation.
220    pub fn try_three_level(main: u8, middle: u8, sub: u8) -> Result<Self, KnxError> {
221        if main > Self::MAX_MAIN {
222            return Err(KnxError::AddressOutOfRange {
223                address: format!("main={}", main),
224                valid_range: "0-31".to_string(),
225            });
226        }
227        if middle > Self::MAX_MIDDLE {
228            return Err(KnxError::AddressOutOfRange {
229                address: format!("middle={}", middle),
230                valid_range: "0-7".to_string(),
231            });
232        }
233        Ok(Self::three_level(main, middle, sub))
234    }
235
236    /// Create from 2-level format (main/sub).
237    pub fn two_level(main: u8, sub: u16) -> Self {
238        assert!(main <= Self::MAX_MAIN, "Main must be 0-31");
239        assert!(sub <= 0x07FF, "Sub must be 0-2047");
240
241        let raw = ((main as u16 & 0x1F) << 11) | (sub & 0x07FF);
242        Self { raw }
243    }
244
245    /// Create from raw 16-bit value.
246    #[inline]
247    pub fn from_raw(raw: u16) -> Self {
248        Self { raw }
249    }
250
251    /// Get the raw 16-bit value.
252    #[inline]
253    pub fn raw(&self) -> u16 {
254        self.raw
255    }
256
257    /// Parse as 3-level format.
258    pub fn as_three_level(&self) -> (u8, u8, u8) {
259        let main = ((self.raw >> 11) & 0x1F) as u8;
260        let middle = ((self.raw >> 8) & 0x07) as u8;
261        let sub = (self.raw & 0xFF) as u8;
262        (main, middle, sub)
263    }
264
265    /// Parse as 2-level format.
266    pub fn as_two_level(&self) -> (u8, u16) {
267        let main = ((self.raw >> 11) & 0x1F) as u8;
268        let sub = self.raw & 0x07FF;
269        (main, sub)
270    }
271
272    /// Get main group (3-level).
273    #[inline]
274    pub fn main(&self) -> u8 {
275        ((self.raw >> 11) & 0x1F) as u8
276    }
277
278    /// Get middle group (3-level).
279    #[inline]
280    pub fn middle(&self) -> u8 {
281        ((self.raw >> 8) & 0x07) as u8
282    }
283
284    /// Get sub group (3-level).
285    #[inline]
286    pub fn sub(&self) -> u8 {
287        (self.raw & 0xFF) as u8
288    }
289
290    /// Encode to byte array (big-endian).
291    pub fn to_bytes(&self) -> [u8; 2] {
292        self.raw.to_be_bytes()
293    }
294
295    /// Decode from byte array (big-endian).
296    pub fn from_bytes(bytes: [u8; 2]) -> Self {
297        Self {
298            raw: u16::from_be_bytes(bytes),
299        }
300    }
301
302    /// Check if this is group address 0/0/0.
303    #[inline]
304    pub fn is_zero(&self) -> bool {
305        self.raw == 0
306    }
307
308    /// Format as 3-level string.
309    pub fn format_three_level(&self) -> String {
310        let (main, middle, sub) = self.as_three_level();
311        format!("{}/{}/{}", main, middle, sub)
312    }
313
314    /// Format as 2-level string.
315    pub fn format_two_level(&self) -> String {
316        let (main, sub) = self.as_two_level();
317        format!("{}/{}", main, sub)
318    }
319}
320
321impl Default for GroupAddress {
322    fn default() -> Self {
323        Self::three_level(0, 0, 1)
324    }
325}
326
327impl fmt::Display for GroupAddress {
328    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
329        let (main, middle, sub) = self.as_three_level();
330        write!(f, "{}/{}/{}", main, middle, sub)
331    }
332}
333
334impl FromStr for GroupAddress {
335    type Err = KnxError;
336
337    fn from_str(s: &str) -> Result<Self, Self::Err> {
338        let parts: Vec<&str> = s.split('/').collect();
339
340        match parts.len() {
341            // 3-level format: main/middle/sub
342            3 => {
343                let main: u8 = parts[0].parse().map_err(|_| {
344                    KnxError::InvalidGroupAddress(format!("Invalid main group: {}", parts[0]))
345                })?;
346                let middle: u8 = parts[1].parse().map_err(|_| {
347                    KnxError::InvalidGroupAddress(format!("Invalid middle group: {}", parts[1]))
348                })?;
349                let sub: u8 = parts[2].parse().map_err(|_| {
350                    KnxError::InvalidGroupAddress(format!("Invalid sub group: {}", parts[2]))
351                })?;
352                Self::try_three_level(main, middle, sub)
353            }
354            // 2-level format: main/sub
355            2 => {
356                let main: u8 = parts[0].parse().map_err(|_| {
357                    KnxError::InvalidGroupAddress(format!("Invalid main group: {}", parts[0]))
358                })?;
359                let sub: u16 = parts[1].parse().map_err(|_| {
360                    KnxError::InvalidGroupAddress(format!("Invalid sub group: {}", parts[1]))
361                })?;
362                if main > Self::MAX_MAIN {
363                    return Err(KnxError::AddressOutOfRange {
364                        address: format!("main={}", main),
365                        valid_range: "0-31".to_string(),
366                    });
367                }
368                if sub > 0x07FF {
369                    return Err(KnxError::AddressOutOfRange {
370                        address: format!("sub={}", sub),
371                        valid_range: "0-2047".to_string(),
372                    });
373                }
374                Ok(Self::two_level(main, sub))
375            }
376            // Free format: raw value
377            1 => {
378                let raw: u16 = parts[0]
379                    .parse()
380                    .map_err(|_| KnxError::InvalidGroupAddress(s.to_string()))?;
381                Ok(Self::from_raw(raw))
382            }
383            _ => Err(KnxError::InvalidGroupAddress(format!(
384                "Invalid format: expected 'main/middle/sub', 'main/sub', or raw value, got '{}'",
385                s
386            ))),
387        }
388    }
389}
390
391impl From<u16> for GroupAddress {
392    fn from(value: u16) -> Self {
393        Self::from_raw(value)
394    }
395}
396
397impl From<GroupAddress> for u16 {
398    fn from(addr: GroupAddress) -> Self {
399        addr.raw()
400    }
401}
402
403// ============================================================================
404// Address Type Enum
405// ============================================================================
406
407/// KNX address type.
408#[derive(Debug, Clone, Copy, PartialEq, Eq)]
409pub enum AddressType {
410    /// Individual (physical) address.
411    Individual,
412    /// Group address.
413    Group,
414}
415
416impl AddressType {
417    /// Create from the address type bit in control byte.
418    #[inline]
419    pub fn from_bit(bit: bool) -> Self {
420        if bit {
421            Self::Group
422        } else {
423            Self::Individual
424        }
425    }
426
427    /// Convert to address type bit.
428    #[inline]
429    pub fn to_bit(&self) -> bool {
430        matches!(self, Self::Group)
431    }
432}
433
434// ============================================================================
435// Address Range
436// ============================================================================
437
438/// A range of group addresses.
439#[derive(Debug, Clone, PartialEq, Eq)]
440pub struct GroupAddressRange {
441    start: GroupAddress,
442    end: GroupAddress,
443}
444
445impl GroupAddressRange {
446    /// Create a new address range.
447    pub fn new(start: GroupAddress, end: GroupAddress) -> Self {
448        Self { start, end }
449    }
450
451    /// Check if address is in range.
452    pub fn contains(&self, addr: &GroupAddress) -> bool {
453        addr.raw() >= self.start.raw() && addr.raw() <= self.end.raw()
454    }
455
456    /// Iterate over all addresses in range.
457    pub fn iter(&self) -> impl Iterator<Item = GroupAddress> {
458        (self.start.raw()..=self.end.raw()).map(GroupAddress::from_raw)
459    }
460
461    /// Get the number of addresses in range.
462    pub fn len(&self) -> usize {
463        (self.end.raw() - self.start.raw() + 1) as usize
464    }
465
466    /// Check if range is empty.
467    pub fn is_empty(&self) -> bool {
468        self.start.raw() > self.end.raw()
469    }
470}
471
472#[cfg(test)]
473mod tests {
474    use super::*;
475
476    // ========================================================================
477    // Individual Address Tests
478    // ========================================================================
479
480    #[test]
481    fn test_individual_address_new() {
482        let addr = IndividualAddress::new(1, 2, 3);
483        assert_eq!(addr.area(), 1);
484        assert_eq!(addr.line(), 2);
485        assert_eq!(addr.device(), 3);
486    }
487
488    #[test]
489    fn test_individual_address_encode_decode() {
490        let addr = IndividualAddress::new(15, 15, 255);
491        let encoded = addr.encode();
492        let decoded = IndividualAddress::decode(encoded);
493        assert_eq!(addr, decoded);
494    }
495
496    #[test]
497    fn test_individual_address_display() {
498        let addr = IndividualAddress::new(1, 2, 3);
499        assert_eq!(addr.to_string(), "1.2.3");
500    }
501
502    #[test]
503    fn test_individual_address_parse() {
504        let addr: IndividualAddress = "1.2.3".parse().unwrap();
505        assert_eq!(addr.area(), 1);
506        assert_eq!(addr.line(), 2);
507        assert_eq!(addr.device(), 3);
508    }
509
510    #[test]
511    fn test_individual_address_parse_invalid() {
512        assert!("1.2".parse::<IndividualAddress>().is_err());
513        assert!("1.2.3.4".parse::<IndividualAddress>().is_err());
514        assert!("a.b.c".parse::<IndividualAddress>().is_err());
515        assert!("16.0.0".parse::<IndividualAddress>().is_err()); // area > 15
516    }
517
518    #[test]
519    fn test_individual_address_bytes() {
520        let addr = IndividualAddress::new(1, 2, 3);
521        let bytes = addr.to_bytes();
522        let decoded = IndividualAddress::from_bytes(bytes);
523        assert_eq!(addr, decoded);
524    }
525
526    // ========================================================================
527    // Group Address Tests
528    // ========================================================================
529
530    #[test]
531    fn test_group_address_three_level() {
532        let addr = GroupAddress::three_level(1, 2, 3);
533        let (main, middle, sub) = addr.as_three_level();
534        assert_eq!(main, 1);
535        assert_eq!(middle, 2);
536        assert_eq!(sub, 3);
537    }
538
539    #[test]
540    fn test_group_address_two_level() {
541        let addr = GroupAddress::two_level(1, 515);
542        let (main, sub) = addr.as_two_level();
543        assert_eq!(main, 1);
544        assert_eq!(sub, 515);
545    }
546
547    #[test]
548    fn test_group_address_display() {
549        let addr = GroupAddress::three_level(1, 2, 3);
550        assert_eq!(addr.to_string(), "1/2/3");
551    }
552
553    #[test]
554    fn test_group_address_parse_three_level() {
555        let addr: GroupAddress = "1/2/3".parse().unwrap();
556        assert_eq!(addr.main(), 1);
557        assert_eq!(addr.middle(), 2);
558        assert_eq!(addr.sub(), 3);
559    }
560
561    #[test]
562    fn test_group_address_parse_two_level() {
563        let addr: GroupAddress = "1/515".parse().unwrap();
564        let (main, sub) = addr.as_two_level();
565        assert_eq!(main, 1);
566        assert_eq!(sub, 515);
567    }
568
569    #[test]
570    fn test_group_address_parse_raw() {
571        let addr: GroupAddress = "12345".parse().unwrap();
572        assert_eq!(addr.raw(), 12345);
573    }
574
575    #[test]
576    fn test_group_address_bytes() {
577        let addr = GroupAddress::three_level(1, 2, 3);
578        let bytes = addr.to_bytes();
579        let decoded = GroupAddress::from_bytes(bytes);
580        assert_eq!(addr, decoded);
581    }
582
583    // ========================================================================
584    // Address Range Tests
585    // ========================================================================
586
587    #[test]
588    fn test_address_range_contains() {
589        let range = GroupAddressRange::new(
590            GroupAddress::three_level(1, 0, 0),
591            GroupAddress::three_level(1, 0, 255),
592        );
593
594        assert!(range.contains(&GroupAddress::three_level(1, 0, 100)));
595        assert!(!range.contains(&GroupAddress::three_level(2, 0, 0)));
596    }
597
598    #[test]
599    fn test_address_range_len() {
600        let range = GroupAddressRange::new(
601            GroupAddress::three_level(1, 0, 0),
602            GroupAddress::three_level(1, 0, 9),
603        );
604        assert_eq!(range.len(), 10);
605    }
606}