knx_catalog/schema.rs
1//! Declarative row and descriptor types for the catalogue.
2//!
3//! These types hold the declarative record of each subtype - a
4//! restatement where the KNX source is plain, and the reading this
5//! catalogue settled on where the source is silent, ambiguous or
6//! prints figures that disagree - in a shape a consumer can read
7//! without heap allocation or runtime text parsing. The types
8//! deliberately distinguish structural dimensions the source
9//! distinguishes - fixed fields, fixed arrays, repeated fixed-width
10//! groups, genuinely variable length, reserved bits, named bits, special
11//! values with distinct kinds - rather than collapsing them into a single
12//! width number.
13
14use core::fmt;
15
16use crate::id::DptId;
17
18/// The transmitted width contract of a subtype.
19#[derive(Clone, Copy, PartialEq, Eq, Debug)]
20pub enum PayloadWidth {
21 /// A fixed width shorter than one octet, transmitted on the low bit
22 /// positions of a data octet under the carrier rule for short types.
23 Bits(u8),
24 /// A fixed width in octets.
25 Octets(u16),
26 /// A variable octet count, bounded by the transport rather than by the
27 /// type.
28 Variable,
29}
30
31/// An exact decimal number: `num * 10^exp`.
32///
33/// The source declares ranges and resolutions in decimal; a scaled integer
34/// keeps them exact where a binary float would approximate.
35#[derive(Clone, Copy, PartialEq, Eq, Debug)]
36pub struct Decimal {
37 /// The scaled integer value.
38 pub num: i64,
39 /// The power of ten the value is scaled by.
40 pub exp: i8,
41}
42
43impl Decimal {
44 /// The value scaled to the given exponent, when that scaling fits
45 /// an `i128`.
46 fn scaled_to(self, exp: i8) -> Option<i128> {
47 let shift = u32::try_from(i32::from(self.exp) - i32::from(exp)).ok()?;
48 10i128.checked_pow(shift).and_then(|scale| i128::from(self.num).checked_mul(scale))
49 }
50
51 /// Compares two decimals by numeric value, exactly, for any
52 /// exponent pair: a scaling that cannot be represented is decided
53 /// by sign, because the unscalable side's magnitude necessarily
54 /// exceeds the other's.
55 pub fn value_cmp(self, other: Decimal) -> core::cmp::Ordering {
56 use core::cmp::Ordering;
57 if self.num == 0 || other.num == 0 || (self.num < 0) != (other.num < 0) {
58 return self.num.cmp(&other.num);
59 }
60 // The side at the minimum exponent scales by 10^0 and always
61 // fits, so at most one scaling can overflow - and a nonzero
62 // value whose scaling overflows i128 necessarily exceeds the
63 // other side's magnitude.
64 let exp = self.exp.min(other.exp);
65 match (self.scaled_to(exp), other.scaled_to(exp)) {
66 (Some(left), Some(right)) => left.cmp(&right),
67 (None, _) => {
68 if self.num > 0 {
69 Ordering::Greater
70 } else {
71 Ordering::Less
72 }
73 }
74 (_, None) => {
75 if other.num > 0 {
76 Ordering::Less
77 } else {
78 Ordering::Greater
79 }
80 }
81 }
82 }
83
84 /// The exact quotient of this value by `divisor`, when the division
85 /// is exact, every intermediate fits, and the result fits an `i64`.
86 ///
87 /// `None` means the value is off the divisor's grid - whether
88 /// because the division leaves a remainder or because a scaling
89 /// cannot be represented; an unscalable magnitude is off every
90 /// field grid, so `None` is the honest answer either way.
91 pub fn exact_quotient(self, divisor: Decimal) -> Option<i64> {
92 if divisor.num == 0 {
93 return None;
94 }
95 if self.num == 0 {
96 // Zero is on every grid whatever the exponents; the
97 // scaling below could overflow on the divisor's side.
98 return Some(0);
99 }
100 let exp = self.exp.min(divisor.exp);
101 let left = self.scaled_to(exp)?;
102 let right = divisor.scaled_to(exp)?;
103 if left % right != 0 {
104 return None;
105 }
106 i64::try_from(left / right).ok()
107 }
108
109 /// A binary floating-point approximation; exact wherever the
110 /// scaling power of ten is exactly representable (every catalogued
111 /// exponent is).
112 ///
113 /// A representation conversion only - the exact decimal remains the
114 /// recorded fact; a caller doing floating arithmetic opts into the
115 /// one rounding step here.
116 pub fn to_f64(self) -> f64 {
117 let mut scale = 1.0f64;
118 for _ in 0..self.exp.unsigned_abs() {
119 scale *= 10.0;
120 }
121 let num = self.num as f64;
122 if self.exp >= 0 {
123 num * scale
124 } else {
125 num / scale
126 }
127 }
128}
129
130/// Canonical decimal rendering: the exact value, a `.` only where the
131/// exponent is negative, no exponent notation and no trimming.
132impl fmt::Display for Decimal {
133 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
134 if self.exp >= 0 {
135 write!(formatter, "{}", self.num)?;
136 if self.num != 0 {
137 for _ in 0..self.exp {
138 formatter.write_str("0")?;
139 }
140 }
141 return Ok(());
142 }
143 if self.num < 0 {
144 formatter.write_str("-")?;
145 }
146 let magnitude = self.num.unsigned_abs();
147 let places = usize::from(self.exp.unsigned_abs());
148 let mut digits = 1usize;
149 let mut probe = magnitude;
150 while probe >= 10 {
151 probe /= 10;
152 digits += 1;
153 }
154 if digits > places {
155 // `places < digits <= 20`, so the split power fits u64.
156 let split = 10u64.pow(places as u32);
157 write!(
158 formatter,
159 "{}.{:0places$}",
160 magnitude / split,
161 magnitude % split,
162 places = places
163 )
164 } else {
165 formatter.write_str("0.")?;
166 for _ in 0..(places - digits) {
167 formatter.write_str("0")?;
168 }
169 write!(formatter, "{magnitude}")
170 }
171 }
172}
173
174/// The declared numeric range of a subtype's value.
175#[derive(Clone, Copy, PartialEq, Eq, Debug)]
176pub enum DeclaredRange {
177 /// The numeric range the reviewed data records for this field.
178 /// Where the source prints one range plainly this restates it;
179 /// where the source is silent, ambiguous, or prints figures that
180 /// disagree, it is the reading this catalogue settled on, not a
181 /// further source statement.
182 ///
183 /// Whether either direction judges against it follows the field's
184 /// carrier, so recording a range is not by itself a bound. On the
185 /// unsigned, signed and float carriers it is one: interpretation
186 /// refuses a value outside it and so does composition - except
187 /// under a linear mapping, which runs the encodable span onto it,
188 /// so composition refuses a value outside it and interpretation
189 /// produces none to refuse. The binary32 carrier keeps 1
190 /// deliberate asymmetry: a NaN is never outside a range on the
191 /// interpret side, which reports what the octets carry, and is
192 /// always outside one on the compose side, which refuses to
193 /// transmit it. No catalogued row exercises that asymmetry - no
194 /// catalogued binary32 row records a range at all. On the boolean and
195 /// enumerated carriers neither direction reads the range: a
196 /// boolean's 2 states and a declared code table are those fields'
197 /// whole domains. Generation still reads it there, checking every
198 /// code the declared table defines against it; how many rows
199 /// record both is a measured count, not a figure repeated here.
200 Declared {
201 /// The declared minimum.
202 min: Decimal,
203 /// The declared maximum.
204 max: Decimal,
205 },
206 /// The reviewed data records no range for this field. A recorded
207 /// absence, not zero - and, like a recorded range, a fact about
208 /// the record rather than a further statement about what the
209 /// source prints.
210 NotDeclared,
211}
212
213/// A declared resolution.
214#[derive(Clone, Copy, PartialEq, Eq, Debug)]
215pub struct Resolution {
216 /// The step value.
217 pub value: Decimal,
218 /// Whether the recorded step is an approximation. Set where the
219 /// source prints the figure as approximate; clear where it prints
220 /// an exact one, and clear where the figure is the reading this
221 /// catalogue settled on rather than a printed figure.
222 pub approximate: bool,
223}
224
225/// The kind of special encoded value, as the source distinguishes them.
226#[derive(Clone, Copy, PartialEq, Eq, Debug)]
227pub enum SpecialKind {
228 /// The encoded value marks the carried data as invalid.
229 ValueState,
230 /// The encoded value directs the receiver's handling of the
231 /// message; the source-assigned disposition rides the kind, so a
232 /// receiver-disposition special without one cannot exist.
233 ReceiverDisposition(ReceiveDisposition),
234 /// The value's meaning depends on a sibling validity flag.
235 FlagConditioned,
236}
237
238/// A receiver disposition the source assigns to declared data.
239///
240/// The vocabulary carries exactly the dispositions catalogued rows
241/// declare and interpretation returns. Rejection needs no declared
242/// data: an undeclared code or pattern already fails closed. The
243/// source assigns further dispositions at other layers (refuse the
244/// read, truncate, neglect the frame); those are not catalogue data
245/// and no row can declare them. A population row declaring a
246/// disposition outside this vocabulary is a source question that
247/// extends it through review.
248#[derive(Clone, Copy, PartialEq, Eq, Debug)]
249pub enum ReceiveDisposition {
250 /// Ignore the whole message.
251 IgnoreMessage,
252 /// Accept the message without acting on it.
253 AcceptWithoutAction,
254}
255
256/// A special encoded value the reviewed data records beside the
257/// field's ordinary domain: singled out by the source where the source
258/// singles one out, and the reading this catalogue settled on where a
259/// printed reference supplies it instead.
260///
261/// The kind carries the whole behavioral contract; any human-readable
262/// designation is a projection concern derived from it.
263#[derive(Debug)]
264pub struct SpecialValue {
265 /// The encoded value, as transmitted.
266 pub raw: u32,
267 /// Which kind of special value this is.
268 pub kind: SpecialKind,
269}
270
271/// One defined code in an enumeration code table.
272#[derive(Debug)]
273pub struct CodeEntry {
274 /// The encoded code.
275 pub code: u16,
276 /// A short human-readable name for the code. It is not part of the
277 /// encoding: match on `code`, never on this text, because a label may
278 /// be reworded between releases without the code's meaning changing.
279 ///
280 /// Some enumerations define their extent by a range rather than by
281 /// naming every member - 10.001's `Day` is one - and the labels
282 /// between the named anchors are derived from that range.
283 pub label: &'static str,
284}
285
286/// A band of codes that share one collective meaning.
287#[derive(Debug)]
288pub struct CodeBand {
289 /// The first code of the band, inclusive.
290 pub start: u16,
291 /// The last code of the band, inclusive.
292 pub end: u16,
293 /// A short human-readable name for what the whole band means. It is
294 /// not part of the encoding: match on the range, never on this text.
295 pub label: &'static str,
296 /// The receiver disposition the source assigns to the band, where
297 /// it states one. What silence does depends on the carrier the band
298 /// sits beside: on an enumerated carrier an undeclared band's codes
299 /// fail closed, while on an unsigned carrier the band only labels
300 /// values the declared range already carries and nothing refuses
301 /// (3.007's Step band is the precedent for the second reading).
302 pub disposition: Option<ReceiveDisposition>,
303}
304
305/// An enumeration code table: a set of defined codes, possibly sparse,
306/// with source-stated bands over the remaining code space.
307///
308/// A code domain is a set, never a range with a default.
309#[derive(Debug)]
310pub struct CodeTable {
311 /// The defined codes.
312 pub entries: &'static [CodeEntry],
313 /// Source-stated bands over the remaining code space.
314 pub bands: &'static [CodeBand],
315}
316
317/// A named bit in a bit-set layout.
318#[derive(Debug)]
319pub struct NamedBit {
320 /// The bit position, 0 = least significant.
321 pub bit: u8,
322 /// A short name for the bit. It is not part of the encoding: match
323 /// on `bit`, never on this text.
324 pub name: &'static str,
325 /// The encoded value at which the named condition holds. The source
326 /// states inverted polarities explicitly, so this is per-declaration
327 /// data rather than a convention.
328 pub active_value: u8,
329 /// A declared reference supplying this bit's interpretation
330 /// contract, where the source states one. 2.xxx's v bit prints no
331 /// encoding of its own - only the delegation to its 1.xxx twin -
332 /// so the reference is the bit's whole stated contract, exactly
333 /// as a scalar's [`ScalarField::semantics`] is. A records
334 /// statement: the generated contract derives nothing from the
335 /// target at runtime.
336 pub semantics: Option<RefTarget>,
337}
338
339/// How a receiver treats declared reserved or unused bits.
340///
341/// Per-declaration data: the source obliges receivers to check some
342/// reserved fields to be zero and to ignore other unused fields.
343#[derive(Clone, Copy, PartialEq, Eq, Debug)]
344pub enum ReservedBitRule {
345 /// Receivers check the bits are zero.
346 CheckZero,
347 /// Receivers ignore the bits.
348 Ignore,
349}
350
351/// A bit-set layout with named positions and reserved bits.
352///
353/// Bit positions are anchored to the bit-group field node that
354/// references this descriptor, not to the whole payload: bit 0 is the
355/// group's last transmitted bit, so a group of N bits spans positions
356/// N-1..0 whether or not it ends on an octet boundary. The group's
357/// placement in the payload is the field tree's data; this descriptor
358/// never restates it.
359#[derive(Debug)]
360pub struct BitSet {
361 /// The named bits.
362 pub named: &'static [NamedBit],
363 /// Mask of bits the sender transmits as zero.
364 pub reserved_mask: u32,
365 /// The receiver rule for the reserved bits.
366 pub reserved_rule: ReservedBitRule,
367}
368
369/// The numeric carrier of a scalar field, as the notation symbol
370/// declares it.
371#[derive(Clone, Copy, PartialEq, Eq, Debug)]
372pub enum Carrier {
373 /// A single-bit boolean (`B1` as a lone value field).
374 Boolean,
375 /// An enumeration code (`N`); the field carries a code table.
376 Enumerated,
377 /// An unsigned integer (`U`).
378 Unsigned,
379 /// A two's complement signed integer (`V`).
380 Signed,
381 /// The KNX 2-octet float (`F16`).
382 Float16,
383 /// IEEE binary32 (`F32`).
384 Float32,
385}
386
387impl Carrier {
388 /// All `Carrier` variants in declaration order; the canonical
389 /// single source for a consumer that must answer for every one.
390 pub const ALL: [Carrier; 6] = [
391 Carrier::Boolean,
392 Carrier::Enumerated,
393 Carrier::Unsigned,
394 Carrier::Signed,
395 Carrier::Float16,
396 Carrier::Float32,
397 ];
398}
399
400/// A declared value-to-field mapping of an integer scalar field.
401///
402/// The DPT specification form's encoding conventions distinguish how a
403/// field value relates to the value it carries, and the relationship is
404/// a per-field fact: one subtype can hold fields of different mapping
405/// kinds. The reviewed data declares the kind explicitly and generation
406/// fails when a declared kind contradicts the field's printed
407/// resolution or range, so no consumer ever re-derives it.
408///
409/// The declared range's domain follows the kind: identity, offset,
410/// century and interval-exponential ranges bound the FIELD value
411/// (19.001's Year prints 0..255), by this catalogue's own convention
412/// for all 4; resolution and linear ranges bound the mapped VALUE
413/// (7.003's 0..655350 ms, 5.001's 0..100 percent).
414#[derive(Clone, Copy, PartialEq, Eq, Debug)]
415pub enum Mapping {
416 /// The field value is the value, at a resolution of 1 in the unit
417 /// the source declares (or dimensionless).
418 Identity,
419 /// The field's encodable span maps onto the declared range by an
420 /// exact ratio; the printed resolution is approximate (5.001:
421 /// 100 percent encodes as 255, so the ratio is 100/255).
422 Linear,
423 /// The value is the field times the declared exact resolution
424 /// factor, in the unit the source declares (7.003: field x 10 ms); the encode
425 /// direction divides, and only exact multiples of the factor are
426 /// encodable. The factor is the field's declared resolution -
427 /// guaranteed present and exact by generation, never restated here.
428 Resolution,
429 /// The value is the field plus a declared base (19.001: the year is
430 /// the field plus 1900).
431 Offset {
432 /// The base added to the field value.
433 base: i32,
434 },
435 /// The date century rule (11.001): field values 90 through 99 read
436 /// as the years 1990 through 1999, values 0 through 89 as 2000
437 /// through 2089.
438 Century,
439 /// The field's value is the step code itself; the code additionally
440 /// declares an interval count of 2 to the power of the code minus 1
441 /// (3.007: step codes 1 through 7 subdivide the controlled range
442 /// into 1 through 64 intervals). Access reads and writes the code;
443 /// the recorded exponential relation is data for a consumer that
444 /// needs interval counts, and none exists yet.
445 IntervalExponential,
446}
447
448/// A scalar field's declarative record.
449///
450/// Resolution, range, codes and specials attach here because they are
451/// per-field facts: multi-field rows (251.600's 4 colour components,
452/// 204.001's main value) are only expressible at this level, and a
453/// per-field absence is recorded rather than erased by a row-level
454/// hoist. Each is a restatement where the source is plain and the
455/// reading this catalogue settled on where it is not.
456#[derive(Debug)]
457pub struct ScalarField {
458 /// The carrier the notation symbol declares.
459 pub carrier: Carrier,
460 /// The field width in bits.
461 pub bits: u8,
462 /// A declared reference supplying this field's interpretation
463 /// contract, where the source states one.
464 pub semantics: Option<RefTarget>,
465 /// The resolution the reviewed data records for this field, where
466 /// it records one: a restatement where the source prints a figure,
467 /// and the reading this catalogue settled on where it does not.
468 pub resolution: Option<Resolution>,
469 /// The declared value-to-field mapping. Present on every unsigned
470 /// and signed integer carrier; absent on boolean, enumerated and
471 /// float carriers, whose relationship is owned by the code table or
472 /// the float format itself.
473 pub mapping: Option<Mapping>,
474 /// The declared numeric range.
475 pub range: DeclaredRange,
476 /// The enumeration code table, for fields that declare one.
477 pub codes: Option<&'static CodeTable>,
478 /// Special encoded values of this field.
479 pub specials: &'static [SpecialValue],
480 /// The Z8 main-value designation: true on the one top-level
481 /// scalar the Z8 control octet couples with, on a row that
482 /// declares MORE than one top-level scalar (the source designates
483 /// the field per row). A single-scalar
484 /// Z8 row's sole scalar is the main by construction and carries
485 /// no designation; every non-Z8 field is false.
486 pub main: bool,
487}
488
489/// The shape of one field in a subtype's declared encoding.
490///
491/// Recursion appears exactly where the source structure requires it: a
492/// repeated group and an embedded record contain fields; everything
493/// else is flat. Print-level octet groupings are not nesting and are
494/// flattened.
495#[derive(Debug)]
496pub enum FieldShape {
497 /// A value-carrying scalar.
498 Scalar(ScalarField),
499 /// Reserved bits: a declared `r` field, or unused carrier bits of
500 /// the declared width that the format string leaves uncovered - two
501 /// categories the source equates while assigning them different
502 /// receiver rules per declaration. Senders transmit zero either way.
503 Reserved {
504 /// The reserved width in bits.
505 bits: u8,
506 /// The receiver rule; check-zero where the source is silent.
507 rule: ReservedBitRule,
508 },
509 /// A named-bit group. Positions in the descriptor anchor to this
510 /// group.
511 BitGroup {
512 /// The group width in bits.
513 bits: u8,
514 /// The bit-set descriptor.
515 set: &'static BitSet,
516 },
517 /// A fixed-width character array with 8-bit elements.
518 CharArray {
519 /// The element count.
520 count: u16,
521 /// The declared fill value for unused trailing positions, where
522 /// the source declares one.
523 pad: Option<u8>,
524 /// The declared character contract, as a schema token.
525 repertoire: TextRepertoire,
526 },
527 /// A fixed-width container holding a terminator-delimited character
528 /// sequence: at most `octets - 1` content octets, then the mandatory
529 /// terminator, then zero fill.
530 CharTerminated {
531 /// The container width in octets, the terminator included.
532 octets: u16,
533 /// The declared terminator value.
534 terminator: u8,
535 /// The declared character contract, as a schema token.
536 repertoire: TextRepertoire,
537 },
538 /// A variable-length character sequence with 8-bit elements and a
539 /// terminator that is part of the format.
540 CharVariable {
541 /// The declared terminator value.
542 terminator: u8,
543 /// The declared character contract, as a schema token.
544 repertoire: TextRepertoire,
545 },
546 /// The service-selected Z8 status/command octet. A row containing
547 /// this field requires service-selected context; a standalone
548 /// status-only Z8 layout (21.001) is a bit group instead, which is
549 /// per-subtype data.
550 Z8Control,
551 /// An embedded record declared same-as another subtype. The fields
552 /// are this row's own declared instantiation; per-subtype
553 /// divergences in the embedded layout are data, never options.
554 Embed {
555 /// The subtype whose record is embedded.
556 of: DptId,
557 /// The embedded fields, in transmission order.
558 fields: &'static [FieldNode],
559 },
560 /// A field group repeated a declared number of times.
561 Repeat {
562 /// The declared repetition count.
563 count: u16,
564 /// The declared base contract of the group, where the source
565 /// states one.
566 base: Option<RefTarget>,
567 /// The group fields, in transmission order.
568 group: &'static [FieldNode],
569 },
570}
571
572/// One field of a subtype's declared encoding, in transmission order
573/// (most significant, first transmitted, first).
574#[derive(Debug)]
575pub struct FieldNode {
576 /// A short name for the field. It identifies the field within its
577 /// subtype and is not part of the encoding.
578 pub name: &'static str,
579 /// The field's shape and declared facts.
580 pub shape: FieldShape,
581}
582
583/// What the gating flag's active state declares about its target.
584///
585/// The source states both polarities: 251.600's mask bits and 235.001's
586/// validity bits declare the component valid, while 19.001's "No ..."
587/// flags declare the gated fields not valid. The sense is recorded per
588/// edge because it cannot be derived from a bit name.
589#[derive(Clone, Copy, PartialEq, Eq, Debug)]
590pub enum ValiditySense {
591 /// The target is valid exactly when the flag is in its active
592 /// state.
593 AssertsValid,
594 /// The target is not valid when the flag is in its active state.
595 AssertsInvalid,
596}
597
598/// A validity association the reviewed data records: a named flag bit
599/// gates a field (or another named bit) of the same row.
600///
601/// Source-declared where the defining clause declares one, and the
602/// reading this catalogue settled on where the clause reaches its flags
603/// through a reference instead. The reviewed data records no per-edge
604/// provenance column; the kind is assigned per ROW, in the 2 paragraphs
605/// below, and every per-row count is pinned by a test - so the totals
606/// live where they are measured, not here - a moving figure restated
607/// in prose goes stale the commit the set grows. Per row, the counts
608/// below are fixed facts of each clause.
609///
610/// The clause-declared rows: 19.001 carries 8, 251.600 carries 4,
611/// and 235.001 and 235.002 carry 2 each - each row's own field table
612/// names the gating flag, its polarity and what it gates. The
613/// structured compound rows whose own bit tables print a validity
614/// declaration are clause-declared the same way, 1 edge per printed
615/// declaration - a growing membership, so it is not enumerated here;
616/// the per-row counts are pinned by tests.
617///
618/// The remainder rest on a reference. 265.001 and 247.600 carry 8 each,
619/// under 2 same-shaped readings: 265.001 declares no validity association of
620/// its own at all: its clause reaches 19.001 through a same-as cell in
621/// the column that states how a field's value relates to its coded
622/// value, and whether such a cell carries the named row's whole
623/// definition or only its field encodings is a question the source
624/// leaves open. Answering it broadly - so that the edges come across
625/// with the layout - is a decision recorded for this catalogue rather
626/// than a printed fact. 247.600's clause hands its octets 12 to 5 -
627/// the whole date-and-time block, flags included - to 19.001 through
628/// one see-cell, and its edges come across under the same broad
629/// reading (its flag octets re-package 19.001's B16 as B8 plus B1r7,
630/// so the layout could not come across as an instantiation - the
631/// contract did). The DateTime-carrying register variants take the
632/// SAME same-as reading for their date-and-time portion: each
633/// carries 19.001's 8 flag edges through its embedded record,
634/// exactly as 265.001 does. And every register-family row (277.1200,
635/// its instantiation-count variants, and the DateTime variants
636/// alike) carries its `E` and `T` pair under a qualified delegation:
637/// the
638/// family clause prints the validity octet's bit geometry and no bit
639/// label, polarity or per-bit meaning at all, handing the repeated
640/// record to 235.001 under a qualifier whose extension is printed as
641/// prose with no encoding - so the pair is read off the delegation
642/// target, and whether the referenced meanings survive the qualifier
643/// stays an open reading. A DateTime register variant therefore
644/// carries both classes: the 8 date-and-time edges and the pair.
645///
646/// The flag's active state is its declared `active_value`; the sense
647/// says what that state means for the target. The value-level
648/// consequences of an invalid target are semantic-layer concerns.
649/// Within a repeated group, the association binds per instance - and
650/// that is why both ends live in one scope: an association binding per
651/// instance has no reading where one end sits outside the group, so an
652/// edge whose flag and target are declared on opposite sides of a
653/// repeated group is refused where the row is bound rather than
654/// resolved per payload.
655#[derive(Debug)]
656pub struct ValidityEdge {
657 /// The gating flag: a named bit in one of the row's bit groups.
658 pub flag: &'static str,
659 /// The gated target: a field name, or a named bit of the same row.
660 pub target: &'static str,
661 /// What the flag's active state declares about the target.
662 pub sense: ValiditySense,
663}
664
665/// A source-declared relation between fields of one row.
666///
667/// The vocabulary is closed over the relations the reviewed rows
668/// declare; a new source-declared relation form extends it through
669/// review, never through a generic escape hatch. A rule's operands
670/// all live in one scope - the row's top scope, or one repeated
671/// group, where the relation binds every instance independently
672/// (instance i's trigger obliges instance i's targets alone), the
673/// same per-instance reading a [`ValidityEdge`] takes.
674#[derive(Debug)]
675pub enum CrossFieldRule {
676 /// When the trigger field carries the stated value, every target
677 /// field shall be zero (19.001: hour 24 obliges zero minutes and
678 /// seconds). A violating message gets the stated receiver
679 /// disposition; an encoder rejects what it cannot ignore.
680 ZeroWhen {
681 /// The field whose value conditions the rule.
682 trigger: &'static str,
683 /// The trigger's raw field value that activates the rule.
684 value: u64,
685 /// The fields obliged to be zero while the rule is active.
686 targets: &'static [&'static str],
687 /// The receiver disposition the source assigns to a violation.
688 disposition: ReceiveDisposition,
689 },
690}
691
692/// The target of a source-declared reference.
693///
694/// The source references both specific subtypes and whole families; a
695/// family-level reference is not collapsed onto an arbitrary subtype.
696#[derive(Clone, Copy, PartialEq, Eq, Debug)]
697pub enum RefTarget {
698 /// A specific subtype.
699 Subtype(DptId),
700 /// A whole main-number family.
701 Family(u16),
702}
703
704/// Emits [`TextRepertoire`] and its `ALL` census from ONE variant
705/// list, so a variant cannot exist outside the census: the
706/// variant-live-census-short state is not expressible, on stable and
707/// on this workspace's MSRV alike. A `macro_rules!` list, following
708/// the tree's own precedent, not a derive.
709macro_rules! text_repertoires {
710 (
711 $(#[$enum_doc:meta])*
712 pub enum TextRepertoire { $( $(#[$member_doc:meta])* $member:ident, )+ }
713 ) => {
714 $(#[$enum_doc])*
715 #[derive(Clone, Copy, PartialEq, Eq, Debug)]
716 pub enum TextRepertoire { $( $(#[$member_doc])* $member, )+ }
717
718 impl TextRepertoire {
719 /// The census the vocabulary tests loop over, emitted from
720 /// the same variant list as the enum itself - a member
721 /// cannot exist outside it by construction. The guard test
722 /// beside `carrier_all` still pins the census's SIZE, so a
723 /// vocabulary change stays a loudly reviewed number.
724 pub const ALL: [TextRepertoire; text_repertoires!(@count $($member)+)] =
725 [ $(TextRepertoire::$member,)+ ];
726 }
727 };
728 (@count $head:ident $($rest:ident)*) => { 1usize + text_repertoires!(@count $($rest)*) };
729 (@count) => { 0usize };
730}
731
732text_repertoires! {
733 /// The declared character contract of a character field, carried as a
734 /// datum of the schema rather than decided by a reader.
735 ///
736 /// The reviewed data declares a repertoire by reference, or - on
737 /// the 3 defining rows 4.001, 4.002 and 28.001 - by the recorded
738 /// `self` token; the generator maps either declaration to this
739 /// token when it emits the field, so the vocabulary is closed
740 /// exactly once - here. A
741 /// reader matching on this enum answers for every member or does not
742 /// compile, which is what makes a half-added repertoire unbuildable
743 /// rather than silently withdrawn: while the reference was
744 /// re-interpreted in a hand-written library arm instead, a repertoire
745 /// the generator admitted and the arm did not know registered for
746 /// encode, published 0 composable items, and sealed a zeroed payload
747 /// with no refusal at any gate.
748 pub enum TextRepertoire {
749 /// The ASCII repertoire, referenced as DPT 4.001: 0x00..=0x7F,
750 /// the most significant bit always 0 per the defining clause.
751 Ascii,
752 /// The ISO-8859-1 octet repertoire, referenced as DPT 4.002.
753 Iso8859_1,
754 /// The UTF-8 repertoire, referenced as DPT 28.001.
755 Utf8,
756 }
757}
758
759/// Whether a Z8 command's main value carries data.
760///
761/// The command table's own column: under some commands the main value
762/// is valid, under others it is don't care (the source's term) - the
763/// receiver reads no data from it.
764#[derive(Clone, Copy, PartialEq, Eq, Debug)]
765pub enum Z8MainValue {
766 /// The main value field carries the datapoint value.
767 Valid,
768 /// The main value field carries no data under this command.
769 DontCare,
770}
771
772/// One command of the Z8 command enumeration.
773#[derive(Debug)]
774pub struct Z8Command {
775 /// The encoded command value.
776 pub code: u8,
777 /// A short name for the command. It is not part of the encoding:
778 /// match on `code`, never on this text.
779 pub label: &'static str,
780 /// Whether the main value carries data under this command.
781 pub main_value: Z8MainValue,
782}
783
784/// The standardised Z8 status/command contract, declared once.
785///
786/// A general clause defines the Z8 octet for every Z8-bearing subtype:
787/// read as STATUS it is the DPT_StatusGen bit set, read as COMMAND it
788/// is this command enumeration, and the reading is selected by the
789/// Application Layer service, never by payload content. Under a set
790/// Fault status bit the main value field carries failure information
791/// whose codes this descriptor also names.
792#[derive(Debug)]
793pub struct Z8Descriptor {
794 /// The status-reading bit set (the DPT_StatusGen layout).
795 pub status: &'static BitSet,
796 /// The command enumeration, sorted by code.
797 pub commands: &'static [Z8Command],
798 /// The failure-information codes the main value carries when the
799 /// status Fault bit is set.
800 pub fault_info: &'static CodeTable,
801}
802
803/// The operation context a subtype's interpretation requires.
804#[derive(Clone, Copy, PartialEq, Eq, Debug)]
805pub enum ContextRequirement {
806 /// No context beyond the payload.
807 None,
808 /// The Application Layer service selects the reading; there is no
809 /// payload-derivable answer.
810 ServiceSelected,
811}
812
813/// The transport restriction the source states for this type.
814///
815/// The variants rest on different source statements; they are not
816/// degrees of one rule, and a refusal names the rule the source
817/// actually states.
818#[derive(Clone, Copy, PartialEq, Eq, Debug)]
819pub enum GroupTransport {
820 /// No source-stated restriction.
821 Eligible,
822 /// The source declares the row not available for standard group
823 /// communication: for Z8-bearing rows by constraint, and for the
824 /// structured HVAC subtype range by the identifier-allocation
825 /// table's LTE-only cell.
826 Forbidden,
827 /// The source does not allow this DPT for runtime communication and
828 /// confines it to parameters and diagnostic data, or to what a
829 /// Functional Block specification designates as such.
830 ParametersAndDiagnosticsOnly,
831}
832
833/// One catalogued datapoint subtype: its declarative record - a
834/// restatement where the KNX source is plain, and the reading this
835/// catalogue settled on where the source is silent, ambiguous or prints
836/// figures that disagree.
837///
838/// Per-field facts - resolutions, ranges, code tables, named bits,
839/// specials, reserved declarations - live on the field nodes that own
840/// them.
841#[derive(Debug)]
842pub struct SubtypeRow {
843 /// The identifier.
844 pub id: DptId,
845 /// The official name.
846 pub name: &'static str,
847 /// The width contract, as the source declares it. The generator
848 /// checks it equals the width the field tree computes.
849 pub width: PayloadWidth,
850 /// The declared field decomposition, in transmission order.
851 pub fields: &'static [FieldNode],
852 /// The validity associations the reviewed data records between the
853 /// row's flags and its fields: source-declared where the clause
854 /// declares them, and read off a reference where the clause reaches
855 /// its flags through one instead - the 265.001 same-as class and
856 /// the register family's qualified delegation; every count is
857 /// pinned by a test.
858 pub validity: &'static [ValidityEdge],
859 /// Source-declared relations between the row's fields.
860 pub cross_field: &'static [CrossFieldRule],
861 /// The operation context interpretation requires.
862 pub context: ContextRequirement,
863 /// Group-transport eligibility as the source states it.
864 pub group_transport: GroupTransport,
865}