Skip to main content

knx_core/
address.rs

1use core::fmt;
2use core::str::FromStr;
3
4use crate::{KnxError, Result};
5
6// Individual address bit layout: 4-bit area | 4-bit line | 8-bit device.
7const IA_AREA_SHIFT: u16 = 12;
8const IA_LINE_SHIFT: u16 = 8;
9const IA_FIELD_MAX: u8 = 0x0f;
10const IA_NIBBLE_MASK: u16 = 0x0f;
11const IA_DEVICE_MASK: u16 = 0xff;
12
13// Group address bit layout: 5-bit main | 3-bit middle | 8-bit sub (three-level),
14// or 5-bit main | 11-bit sub (two-level).
15const GA_MAIN_SHIFT: u16 = 11;
16const GA_MAIN_MAX: u8 = 0x1f;
17const GA_MAIN_MASK: u16 = 0x1f;
18const GA_MIDDLE_SHIFT: u16 = 8;
19const GA_MIDDLE_MAX: u8 = 0x07;
20const GA_MIDDLE_MASK: u16 = 0x07;
21const GA_SUB_MASK: u16 = 0xff;
22const GA_TWO_LEVEL_SUB_MAX: u16 = 0x07ff;
23const GA_TWO_LEVEL_SUB_MASK: u16 = 0x07ff;
24
25/// A device's own address: 4 bits of area, 4 bits of line, and 8 bits of
26/// device, held as the 2 octets a frame carries.
27///
28/// This is where a frame comes from, never where a group telegram is going -
29/// a telegram is addressed to a [`GroupAddress`]. Written and parsed as
30/// area.line.device.
31#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
32pub struct IndividualAddress(u16);
33
34impl IndividualAddress {
35    /// Builds an address from its 3 parts, refusing an area or a line past
36    /// 15: each has 4 bits. Every device value fits the 8 bits it has.
37    pub const fn new(area: u8, line: u8, device: u8) -> Result<Self> {
38        if area > IA_FIELD_MAX {
39            return Err(KnxError::InvalidAddress("individual area out of range"));
40        }
41        if line > IA_FIELD_MAX {
42            return Err(KnxError::InvalidAddress("individual line out of range"));
43        }
44
45        Ok(Self(
46            ((area as u16) << IA_AREA_SHIFT) | ((line as u16) << IA_LINE_SHIFT) | device as u16,
47        ))
48    }
49
50    /// The address the given 2 octets state, as a frame carries them.
51    ///
52    /// Infallible: every u16 maps to a structurally valid individual address
53    /// (4-bit area + 4-bit line + 8-bit device covers the full 16-bit space),
54    /// so there is no invalid input a checked constructor could reject.
55    pub const fn from_raw(raw: u16) -> Self {
56        Self(raw)
57    }
58
59    /// The address as the 16-bit value a frame carries, most significant
60    /// octet first on the wire.
61    pub const fn raw(self) -> u16 {
62        self.0
63    }
64
65    /// The area: the top 4 bits.
66    pub const fn area(self) -> u8 {
67        ((self.0 >> IA_AREA_SHIFT) & IA_NIBBLE_MASK) as u8
68    }
69
70    /// The line within the area: the next 4 bits.
71    pub const fn line(self) -> u8 {
72        ((self.0 >> IA_LINE_SHIFT) & IA_NIBBLE_MASK) as u8
73    }
74
75    /// The device on the line: the low 8 bits.
76    pub const fn device(self) -> u8 {
77        (self.0 & IA_DEVICE_MASK) as u8
78    }
79}
80
81impl fmt::Display for IndividualAddress {
82    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
83        write!(f, "{}.{}.{}", self.area(), self.line(), self.device())
84    }
85}
86
87impl FromStr for IndividualAddress {
88    type Err = KnxError;
89
90    fn from_str(value: &str) -> Result<Self> {
91        let mut parts = value.split('.');
92        let area = parse_part(parts.next(), "missing individual area")?;
93        let line = parse_part(parts.next(), "missing individual line")?;
94        let device = parse_part(parts.next(), "missing individual device")?;
95
96        if parts.next().is_some() {
97            return Err(KnxError::InvalidAddress("too many individual parts"));
98        }
99
100        Self::new(area, line, device)
101    }
102}
103
104/// Where a telegram is addressed: 16 bits naming a group, not a device.
105///
106/// How those 16 bits divide into levels is an installation's convention and
107/// not something the frame states, so the same address reads 2 ways. The
108/// 3-level split - 5 bits of main, 3 of middle, 8 of sub - is this type's
109/// [`fmt::Display`] and what [`FromStr`] parses. The 2-level split reads the
110/// low 11 bits as 1 sub group and has its own named surface:
111/// [`Self::new_two_level`], [`Self::parse_two_level`],
112/// [`Self::two_level_sub`], and [`Self::to_two_level_display`].
113#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
114pub struct GroupAddress(u16);
115
116impl GroupAddress {
117    /// Builds an address from 3 levels, refusing a main past 31 (it has 5
118    /// bits) or a middle past 7 (3 bits). Every sub value fits its 8 bits.
119    pub const fn new_three_level(main: u8, middle: u8, sub: u8) -> Result<Self> {
120        if main > GA_MAIN_MAX {
121            return Err(KnxError::InvalidAddress("group main out of range"));
122        }
123        if middle > GA_MIDDLE_MAX {
124            return Err(KnxError::InvalidAddress("group middle out of range"));
125        }
126
127        Ok(Self(
128            ((main as u16) << GA_MAIN_SHIFT) | ((middle as u16) << GA_MIDDLE_SHIFT) | sub as u16,
129        ))
130    }
131
132    /// Builds an address as an installation that addresses in 2 levels states
133    /// it: a main group and 1 sub group under it.
134    ///
135    /// The main is the same top 5 bits either convention uses, and the sub
136    /// takes the remaining 11 - which is why it is refused past 2047 rather
137    /// than 255. The result is an ordinary [`GroupAddress`] and prints in the
138    /// 3-level form unless asked for [`Self::to_two_level_display`]; the
139    /// bits, not the convention, are what a telegram carries.
140    pub const fn new_two_level(main: u8, sub: u16) -> Result<Self> {
141        if main > GA_MAIN_MAX {
142            return Err(KnxError::InvalidAddress("group main out of range"));
143        }
144        if sub > GA_TWO_LEVEL_SUB_MAX {
145            return Err(KnxError::InvalidAddress("group sub out of range"));
146        }
147
148        Ok(Self(((main as u16) << GA_MAIN_SHIFT) | sub))
149    }
150
151    /// The address the given 2 octets state, as a telegram carries them.
152    ///
153    /// Infallible: every u16 maps to a structurally valid group address
154    /// (5-bit main + 3-bit middle + 8-bit sub, or 5-bit main + 11-bit sub,
155    /// both covering the full 16-bit space), so a checked constructor would
156    /// have no invalid input to reject.
157    pub const fn from_raw(raw: u16) -> Self {
158        Self(raw)
159    }
160
161    /// The address as the 16-bit value a telegram carries, most significant
162    /// octet first on the wire.
163    pub const fn raw(self) -> u16 {
164        self.0
165    }
166
167    /// The main group: the top 5 bits, which both level conventions read the
168    /// same way.
169    pub const fn main(self) -> u8 {
170        ((self.0 >> GA_MAIN_SHIFT) & GA_MAIN_MASK) as u8
171    }
172
173    /// The middle group of the 3-level convention: the 3 bits under the main.
174    pub const fn middle(self) -> u8 {
175        ((self.0 >> GA_MIDDLE_SHIFT) & GA_MIDDLE_MASK) as u8
176    }
177
178    /// The sub group of the 3-level convention: the low 8 bits.
179    pub const fn sub(self) -> u8 {
180        (self.0 & GA_SUB_MASK) as u8
181    }
182
183    /// The sub group of the 2-level convention: the low 11 bits, which is the
184    /// 3-level middle and sub read as 1 number.
185    pub const fn two_level_sub(self) -> u16 {
186        self.0 & GA_TWO_LEVEL_SUB_MASK
187    }
188
189    /// Parses main/sub, the form an installation that addresses in 2 levels
190    /// writes.
191    ///
192    /// [`FromStr`] parses the 3-level form; this is a second named entry
193    /// point rather than a fallback, so a string with 3 parts is refused here
194    /// instead of being reinterpreted as one convention or the other.
195    pub fn parse_two_level(value: &str) -> Result<Self> {
196        let mut parts = value.split('/');
197        let main = parse_part(parts.next(), "missing group main")?;
198        let sub = parse_part(parts.next(), "missing group sub")?;
199
200        if parts.next().is_some() {
201            return Err(KnxError::InvalidAddress("too many group parts"));
202        }
203
204        Self::new_two_level(main, sub)
205    }
206
207    /// A wrapper that writes this address as main/sub.
208    ///
209    /// The 3-level form stays this type's own [`fmt::Display`], so the
210    /// 2-level form is asked for here rather than changing what every
211    /// existing caller prints.
212    pub fn to_two_level_display(self) -> TwoLevelGroupAddressDisplay {
213        TwoLevelGroupAddressDisplay(self)
214    }
215}
216
217impl fmt::Display for GroupAddress {
218    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
219        write!(f, "{}/{}/{}", self.main(), self.middle(), self.sub())
220    }
221}
222
223impl FromStr for GroupAddress {
224    type Err = KnxError;
225
226    fn from_str(value: &str) -> Result<Self> {
227        let mut parts = value.split('/');
228        let main = parse_part(parts.next(), "missing group main")?;
229        let middle = parse_part(parts.next(), "missing group middle")?;
230        let sub = parse_part(parts.next(), "missing group sub")?;
231
232        if parts.next().is_some() {
233            return Err(KnxError::InvalidAddress("too many group parts"));
234        }
235
236        Self::new_three_level(main, middle, sub)
237    }
238}
239
240/// A [`GroupAddress`] written as main/sub, for installations that address in
241/// 2 levels.
242///
243/// Built by [`GroupAddress::to_two_level_display`]. It exists so the 2-level
244/// form is something a caller asks for, rather than a mode that would change
245/// what [`GroupAddress`] itself prints.
246#[derive(Debug, Clone, Copy, PartialEq, Eq)]
247pub struct TwoLevelGroupAddressDisplay(GroupAddress);
248
249impl fmt::Display for TwoLevelGroupAddressDisplay {
250    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
251        write!(f, "{}/{}", self.0.main(), self.0.two_level_sub())
252    }
253}
254
255fn parse_part<T: core::str::FromStr>(value: Option<&str>, missing: &'static str) -> Result<T> {
256    let value = value.ok_or(KnxError::InvalidAddress(missing))?;
257    value.parse().map_err(|_| KnxError::InvalidAddress("invalid numeric address part"))
258}