System 7 download specification (mask 0705 / 0701)¶
Implementation-grade spec for two independent implementers building in parallel from this document alone: (1) the bussard System 7 download path (
bussard-downloadlowering +bussard-mgmtprimitives), and (2) the knx-sim System 7 device model. Both must converge on the same wire bytes. Grounded insystem7-research.md(standards/clean-room research) andsystem7-corpus.md+system7-corpus.json(49-app corpus archaeology). Issue #49.
0. Confidence discipline (read first)¶
Every byte-level claim below is tagged CONFIRMED (with source), INFERRED (reasoned from an allowed source, residual risk noted), or UNKNOWN (needs a live capture). The two research inputs disagree on exactly one load-bearing point (the LSM realisation, section 5); this spec resolves it by requiring a seam and a default, not by picking a winner on paper.
Rule for implementers: treat every UNKNOWN as "build the seam, pick the
best-evidence default, and leave a calibration TODO tagged with the greppable
marker string S7-CAL:". Do not hardcode an UNKNOWN as if it were CONFIRMED.
At M2 (section 9) a single ETS capture against a real Jung 0705 device settles
the UNKNOWNs; every S7-CAL: marker names precisely which constant that
capture must confirm.
Source shorthand: [XKNX PR#NNNN], [Resources 03.05.01 §X], [AL 03.03.07 §X],
[MP 03.05.02 §X], [corpus: N/49], [Selfbus], [endrekatona],
[Kögler SDK], [issue #49] (bussard first-party evidence). Full URLs in
system7-research.md.
1. Scope¶
In scope: masks 0x0705 and 0x0701 on TP1 (MaskFamily::System7 in
crates/bussard-mgmt/src/profile.rs). The download path: link tables (address,
association, group-object) and the application/parameter image, driven by the
.knxprod LoadProcedure. A_Authorize, absolute A_Memory_Write/_Read in
standard frames, the three parallel load-state machines, verify by read-back.
Out of scope (refuse cleanly, name the reason):
- BCU1-era System 1 masks 0x0012 / 0x002x and System 2 0x03xx [issue #49
non-goal].
- RF and KNX-IP media. High-nibble 0x2… / 0x5… System 7 masks are not
expected in the corpus; classify but do not drive [research 5.3].
- KNX Secure / Data Secure [issue #49 non-goal].
- Extended-frame long downloads: System 7 has no extended-frame guarantee;
standard frame only (section 6) [research 5.3, CONFIRMED substance].
0701 and 0705 share the resource model and op set, but the LSM realisation is vendor / mask-family dependent — they are NOT identical at the wire level. Two real-ETS download captures settle it (superseding the earlier "0701==0705" claim):
- Jung
0705(M2 issue #70, plus the binaereingang / automitschalter / schaltaktor 0705 analysis captures) drives load control property-based overPID_LOAD_STATE_CONTROL(PID 5) — see §5. - Theben
0701(Meteodata 1409207, IA 1.1.202) drives load control memory-mapped: 11-octet records written byA_Memory_Writeto0x0104, status read at0xB6EA + (lsm - 1), with ZERO PID-5 traffic — see §5.
So the download-path realisation branches on the mask family (0705 → property,
0701 → memory-mapped) via Sys7Profile::corpus_default_for_mask. Any further
divergence found in capture gets an S7-CAL: marker.
2. Resource model¶
System 7 is memory-mapped and absolute-addressed: the whole download is a
sequence of absolute segment writes at fixed 16-bit addresses. [corpus: 49/49]
There is no RelSegment, WriteRelMem, WriteMem, or WriteProp anywhere
in the System 7 corpus.
Amendment (2026-09-17, Jung 3361-1M): the freshly fetched Jung
M-0004_A-A011(0705, the reference installation's presence detector) DOES useLdCtrlLoadImagePropwithprop_id = 27(PID_MCB_TABLE) on objects 1-3. MCB verification on System 7 is therefore app-dependent, not absent: the 49 MDT/Theben/Zennio corpus apps omit it, Jung requires it. Both sides must support it: bussard executesLoadImagePropon System 7 exactly as the procedure demands (per-object MCB, as on System B); the sim's System 7 device servesPID_MCB_TABLEreads. Read-back compare remains the baseline verify for procedures without it. Supersedes the two "no MCB on System 7" claims below (section 6).
2.1 Interface objects¶
The standard object-type numbers still exist as identifiers even though the
System 7 profile does not serve tables through property arrays [research 1.3,
XKNX profile/const.py, CONFIRMED]:
| Object | Type |
|---|---|
| Device | 0x0000 |
| AddressTable | 0x0001 |
| AssociationTable | 0x0002 |
| ApplicationProgram | 0x0003 |
| GroupObjectTable | 0x0009 |
Standard PIDs referenced: PID_TABLE_REFERENCE = 7, PID_DEVICE_DESCRIPTOR =
83, and PID_HARDWARE_TYPE = 78 (the object-0 preflight target, section 4.6).
2.2 PID_TABLE_REFERENCE usage¶
On System B the association table is located via object-2 PID_TABLE_REFERENCE
(PID 7). Research states the same may hold on System 7 for the association
table [research 1.1, INFERRED-strong from issue #49]. The corpus contradicts
this for the download path: there is zero PID_TABLE_REFERENCE usage and zero
property-array table access in any of the 49 System 7 apps [corpus: 49/49,
§5]. Tables are addressed only by absolute memory (0x4000 / 0x4201).
Resolution: the System 7 download path does not resolve any table base via
PID_TABLE_REFERENCE. Addresses come from the segment Address attribute
directly. The sim need not implement a functional PID_TABLE_REFERENCE for the
download to work; it MAY expose PID 7 as a best-effort probe returning the
segment base, but bussard must not depend on it. S7-CAL: confirm no live 0705
download issues an A_PropertyValue_Read(obj2, PID7).
2.3 Absolute code segments¶
Every corpus segment is an AbsoluteSegment (SegmentKind::Absolute) with a
fixed Address; the parser already carries AbsSegment{lsm_idx, address, size}
plus the decoded <Data> and <Mask> payloads (see
crates/bussard-ets/src/application.rs, CodeSegment / LoadOp::AbsSegment).
The segment <Data> IS the payload to stream to Address; there is no
separate write op (section 4.2). Address families [corpus §3]:
| Address | Role | Data |
|---|---|---|
| 0x4000 (16384) | LSM 1 table region (address / com-object descriptors). The only segment carrying a per-byte <Mask>. ~511–513 B |
present + mask |
| 0x4201 (16897) | LSM 2 table region (association / group-object). ~511 B | present |
| 0x4400 (17408) | LSM 3 parameter image start | present |
| 0x0700 (1792), 0x0730 | LSM 3 low-RAM working region | allocate only, NO <Data> |
| 0x1C00, 0x4800, 0x4C00, 0x8710, 0xB800 | vendor code/param regions (Theben/Zennio) | present |
A segment with <Data> == None (e.g. 0x0700) is an allocation-only record:
the LSM reserves/zeroes device-initialised RAM; no memory write is streamed.
2.4 HawkConfigurationData — the load-bearing architecture decision¶
Do not hardcode per-mask addresses in Rust. Every .knxprod's
knx_master.xml carries a HawkConfigurationData block: the per-mask
programming config (resource/LSM realisation, table locations, memory map,
authorize levels). Parse it at import time (bussard-ets) into a data-driven
mask profile, resolved per product [research 1.3, CONFIRMED as the right source;
issue #49 architecture conclusion].
Extract, per mask version:
- LSM realisation (property-based vs memory-mapped; see section 5) and, if
memory-mapped, the LSM control address and status-poll address.
- Table locations / RealisationType for address, association, group-object
segments (or confirmation they come straight from segment Address).
- Authorize levels required for memory access.
- Memory map anchors used for verification.
Fallback when HawkConfigurationData is absent or unparsable: fall back to
the corpus-derived defaults hardcoded as a named default profile (not scattered
constants): LSM realisation = property (section 5 default, M2 CONFIRMED),
addresses from segment Address, authorize with the free-access key. The download
must still be attemptable on a device whose product data lacks the block, because
the corpus shape is uniform enough to drive blind.
M2 caveat on the Hawk block itself. The Jung MV-0705 HawkConfigurationData
carries a LoadControl_M112 @ StandardMemory 0x0104 block, which pre-M2 was read
as "this device's LSM is a 12-octet memory record". The M2 capture disproved that
reading (control is property; see section 5). So bussard's normal flash path does
not feed a parsed Hawk config to the planner — it plans with the property
corpus default. sys7_profile_from_hawk stays for the memory-mapped conformance
harness only.
For M1 the sim and bussard MAY share a single hand-written default profile
matching M-0083_A-000E; HawkConfigurationData parsing is the M1.5 hardening
step. Ship the seam first.
3. The three parallel load-state machines¶
System 7 runs three LSMs in parallel (System B uses one) [corpus: 49/49]:
- LSM 1 — table region at 0x4000 (address table + com-object descriptors).
- LSM 2 — table region at 0x4201 (association + group-object).
- LSM 3 — parameters (0x0700 RAM alloc + 0x4400 param image).
- LSM 5 — a fourth machine some vendors open after restart (Theben, §4.7).
Each LSM is torn down (Unload) up front, then loaded in order. The LSM index on
Load/LoadCompleted/AbsSegment names the machine, not an object index. The
mapping LSM-index → memory region is per-mask (from HawkConfigurationData;
default per the table above).
Canonical op sequence (MDT M-0083_A-000E, smallest 0705, first target)¶
connect
compare_prop obj=0 pid=78 data=00000000031200000000 # preflight (§4.6)
unload lsm=1 ; unload lsm=2 ; unload lsm=3 # tear down all three
load lsm=1
abs_segment lsm=1 addr=0x4000 size=513 # table region + <Mask>
task_segment lsm=1 addr=0x4000 # finalize LSM 1
load_completed lsm=1
load lsm=2
abs_segment lsm=2 addr=0x4201 size=511
task_segment lsm=2 addr=0x4201
load_completed lsm=2
load lsm=3
abs_segment lsm=3 addr=0x0700 size=48 # alloc only, no data
abs_segment lsm=3 addr=0x0730 size=1 # alloc only
abs_segment lsm=3 addr=0x4400 size=92 # param image
abs_segment lsm=3 addr=0x445C size=88 # param image
task_segment lsm=3 addr=0x4400
load_completed lsm=3
restart
disconnect
The A_Authorize handshake and the absolute A_Memory_Write streaming are not
named by any op token; they are implied by the connect gate and each
abs_segment's <Data> (sections 4, 6).
4. Load-procedure op semantics¶
The wire realisation of Unload / Load / LoadCompleted (the LSM events) is section 5. This section is the per-op content semantics.
4.1 Ordering / state rules¶
Abstract LSM states (1 octet, read back to poll) [XKNX, Resources 03.05.01
§4.23.2, CONFIRMED]:
| State | Value |
|---|---|
| Unloaded | 0 |
| Loaded | 1 |
| Loading | 2 |
| Error | 3 |
| Unloading | 4 (optional) |
| LoadCompleting | 5 (optional) |
Abstract load events (10-octet record, first octet = event, rest zero-padded
unless the sub-command below fills them) [XKNXLOAD_EVENT_SIZE=10]:
| Event | Opcode |
|---|---|
| NoOperation | 0x00 |
| StartLoading | 0x01 |
| LoadCompleted | 0x02 |
| AdditionalLoadControls | 0x03 |
| Unload | 0x04 |
State transitions the sim MUST enforce, and bussard MUST drive in order:
Unloaded --StartLoading--> Loading --(AdditionalLoadControls*)--> Loading
--LoadCompleted--> Loaded; Unload from any state → Unloaded; any illegal
event or a failed segment → Error (state 3), which must fail the flash.
The load event record is the 10-octet form ([Resources 03.05.01 §4.23.2]).
AdditionalLoadControls (0x03) uses octet 1 as the sub-command selector, big-
endian, total 10 octets [XKNX, CONFIRMED]:
| Sub-command | Code | Layout (after [0x03][subtype]) |
|---|---|---|
| Alloc absolute Data segment | 0x00 | [start:2][length:2][access:1][mem_type:1][mem_attr:1] |
| Alloc absolute Stack segment | 0x01 | same as 0x00 |
| Alloc absolute Task segment | 0x02 | same as 0x00 |
| Task pointer | 0x03 | (not in corpus) |
| Task control 1 | 0x04 | [address:2][count:…] (§4.4) |
| Task control 2 | 0x05 | (not in corpus) |
access: bits 0–3 write level, bits 4–7 read level. mem_type bits 0–2: 1 =
zero-page RAM, 2 = RAM, 3 = EEPROM. mem_attr bit 7 = checksum-control enable.
4.2 AbsSegment — allocation record + payload¶
AbsSegment{lsm_idx, address, size} lowers to two device actions:
- Allocate: an
AdditionalLoadControls"Alloc absolute Data segment" (subtype 0x00) record for the LSM, filled[event=3][subtype=0][start:2 BE][length:2 BE][seg_flags][mem_type][checksum_ctrl][reserved]. M2 CONFIRMED (cross-checked against the Jung app M-0004_A-A011's declared AbsSegment addresses/sizes): the opcode/subtype, the big-endianstartandlength(= the declared segment size in octets), andmem_typeat octet 7 —2(RAM) for the0x0700low-RAM region,3(EEPROM) for the0x4xxxtable/param regions. Every captured record's length equals the product's declared size (e.g.0x43FFsize 811 =0x032B;0x4743size 260 =0x0104). Two attribute octets are shown by the capture but not yet derivable from product data, so bussard emits0:seg_flags(octet 6, observed0xF2/0xF3) andchecksum_ctrl(octet 8, observed0x80/0x00). A 0705 device keys the allocation on subtype + start + length, so a zero attribute tail still drives it toLoaded.S7-CAL: derive the alloc-record seg_flags (0xF2/0xF3) and checksum_ctrl (0x80/0x00) octets from product data. - Stream payload: if the segment carries
<Data>, write those bytes toaddressvia absoluteA_Memory_Writein 12-octet chunks (section 6). The segment<Data>IS the payload — there is no separateWriteMemop[corpus §2]. A segment with no<Data>is allocate-only; skip the write.
The 0x4000 segment carries a per-byte <Mask>: 0xFF = this byte belongs to the
image, other = device-owned, leave untouched. Under the mask, bussard streams
only owned bytes (still in 12-octet chunks by address run). The sim, on receiving
a write into a masked region, must accept the owned bytes and preserve the rest.
4.3 TaskSegment — per-LSM finalize¶
TaskSegment{lsm_idx, address} [corpus: 47/49] writes a task/segment
descriptor pointing at the segment base, issued once per LSM immediately before
LoadCompleted. Realisation: an AdditionalLoadControls record carrying the
address. Default: encode as AllocAbsTaskSegment (subtype 0x02) with
start=address, length = the LSM's total loaded span. M2 CONFIRMED the
[03][02][address:2 BE] prefix (LSM1 0x4000, LSM2 0x41FF, LSM3 0x4722); in
the capture the length field was 0x0000 and the four trailing octets a fixed
04 a0 11 13 marker. bussard's length=span form still drives the sim to Loaded
(it keys the finalize on subtype + address). S7-CAL: confirm the TaskSegment
length field (0x0000 vs span) and the trailing 04 a0 11 13 marker.
The sim must accept a TaskSegment in Loading state and treat it as "segment
descriptor committed"; it is a precondition for the following LoadCompleted.
4.4 TaskCtrl1¶
TaskCtrl1{lsm_idx, address, count} [corpus: 3/49 — Theben, Steinel, Elsner;
plus Jung M-0004_A-A011] is AdditionalLoadControls subtype 0x04,
[address:2][count]. It writes a task-control table entry count times.
Implemented (2026-09-18): the Jung M-0004_A-A011 (0705) download issues a
TaskCtrl1 on LSM 3, so it is on the conformance hot path, not a second-phase op.
bussard plans and executes it; the sim decodes subtype 0x04 into TaskCtrl1
{address, count} and accepts it while Loading with no memory side effect in
the M1 model (like TaskSegment). M2 CONFIRMED the 03 04 opcode is on the
Jung download hot path (a single TaskCtrl1 on LSM 3), but the captured record was
03 04 then all-zero (address 0, count 0), so the product's declared
address/count are not carried on the wire the way this encoder lays them out; the
sim's no-side-effect accept still reaches Loaded. S7-CAL: reconcile the TaskCtrl1
address/count fields with the captured all-zero record.
4.5 CompareMem¶
Raw LdCtrlCompareMem{Address, InlineData, Size} [corpus: 1/49 — Zennio
LUMENTO]: an absolute A_Memory_Read(Address, Size) + byte-compare against
InlineData; mismatch fails the flash. No LSM interaction. Second-phase op.
4.6 CompareProp obj0 / PID78 — the MDT preflight¶
CompareProp{obj_idx=0, prop_id=78, inline_data} [corpus: 44/49 — all MDT]
runs before any Unload: read object-0 property 78 (PID_HARDWARE_TYPE in
apci.rs) and byte-compare against the 10-octet InlineData, e.g.
00000000 03 12 00000000. Mismatch fails the flash (it guards against flashing
the wrong app onto a device). Absent on Theben/Zennio/Steinel/Elsner.
The 6th octet (0x12 above) does not cleanly equal the application number
across the corpus (app 14 → 0x12, app 8 → 0x11, app 9 → 0x09, app 10 → 0x0A):
it is a hardware-type / app-family marker, not the app id. Treat the whole 10
octets as opaque expected bytes from InlineData; do not synthesize it. The sim
must serve object-0 PID78 as a readable 10-octet value that a correctly-targeted
flash matches. S7-CAL: PID78 value semantics and how the sim seeds it.
There is no WriteProp of the app id anywhere in System 7 — the device
derives run-state from the loaded tables, ETS only checks [corpus §5].
5. LSM realisation — RESOLVED: vendor / mask-family dependent¶
This was the single most important open question. Two real-ETS download captures settle it — and the answer is that the realisation depends on the mask family, NOT a single global default.
Verdict (both captures CONFIRMED):
- Jung
0705— property-based. The M2 live capture (issue #70, a real ETS 6 download to a Jung 3361-1M) and the 0705 analysis captures (binaereingang, automitschalter, schaltaktor) drive their LSMs property-based: every Unload / StartLoading / AbsSegment / TaskSegment / LoadCompleted is anA_PropertyValue_Write(objN, PID 5, 10-octet load event, one element at index 1), state read back viaA_PropertyValue_Read(objN, PID 5). There is noA_Memory_Writeto0x0104anywhere; the only0xB6EA+touch is a single readable-statusA_Memory_Read. So [LsmAccess::Property] is the0705default. - Theben
0701— memory-mapped. The Theben Meteodata 1409207 (IA 1.1.202) capture drives its LSMs memory-mapped: 11-octet records written byA_Memory_Writeto0x0104, status read at0xB6EA + (lsm - 1)(returning02Loading …01Loaded), with zero PID-5 traffic. So [LsmAccess::MemoryMapped] is the0701default.
The download path selects the realisation by mask family
(Sys7Profile::corpus_default_for_mask: 0701 → memory-mapped, everything else
→ property), overridable by HawkConfigurationData when a product carries a
usable block. This corrects the earlier claim that property was the default for
ALL System 7 — that regressed the memory-mapped Theben 0701.
The two research inputs that disagreed before M2:
- Standards/clean-room research (
system7-research.md2.1): System 7 is a BCU2 descendant; the KNX LSM is property-based — load events written toPID_LOAD_STATE_CONTROL(PID 5) viaA_PropertyValue_Write, state read back viaA_PropertyValue_Read. M2 confirmed this is correct for 0705. - First-party evidence (
[issue #49], from.knxprodHawkConfigurationData - ETS analysis): the
.knxprodHawkConfigurationDatacarries aLoadControl_M112 @ StandardMemory 0x0104block with status at0xB6EA+, read as evidence the LSM is a 12-octet memory record. M2 disproved this reading: theLoadControl_M112 @ 0x0104Hawk block did NOT predict the wire — control is property;0xB6EA+is a readable status region (the single read at0xB6EC), not a control-write target.sys7_profile_from_hawkno longer feeds the normal CLI flash path (it plans with the property corpus default); the helper stays only for the memory-mapped conformance harness.
Both realisations stay behind an LsmAccess seam, selected per mask family:
trait LsmAccess {
fn send_event(lsm, event_record: [u8; 10]) -> Result<()>; // 10-octet abstract event
fn read_state(lsm) -> Result<u8>; // 0..5 per §4.1
}
LsmAccess::Property(the0705default) —A_PropertyValue_Write(obj, PID 5, 10-octet event)/A_PropertyValue_Read(obj, PID 5) -> 1 octet. Confirmed by the M2 Jung 0705 capture.LsmAccess::MemoryMapped(the0701default) — write the 11-octet record to the LSM control address (default 0x0104), poll status at0xB6EA + (lsm - 1), both viaA_Memory_Write/_Read. Confirmed by the Theben 0701 Meteodata capture.
The memory-mapped variant's 11-octet record (Theben 0701 CONFIRMED) folds the
LSM index into the high nibble of the event opcode byte and widens the address to
3 octets — there is NO [lsm][00] prefix:
[0] (lsm << 4) | event_opcode [1] subtype [2] 0x00 (addr high octet)
[3..5] start:2 BE [5..7] length:2 BE [7..11] tail (alloc attrs or task marker)
13 00 00 40 00 00 1d f2 03 80 00 (LSM1 alloc 0x4000 len 0x1D EEPROM),
13 02 00 40 00 00 00 48 14 0c 14 (LSM1 task, marker 48 14 0c 14),
33 04 00 46 eb 01 00 00 00 00 00 (LSM3 taskctrl1 0x46EB count 1). Built by
bussard_mgmt::wrap_memory_lsm_record, decoded by the sim's
decode_memory_lsm_record.
Default: mask-family dependent (Sys7Profile::corpus_default_for_mask):
0701 → MemoryMapped { control 0x0104, status 0xB6EA }; every other System 7
mask → Property. A HawkConfigurationData block may still select MemoryMapped
for a product that carries a StandardMemory LoadControl (the 0701 Hawk block
does; the 0705 Jung block also resolves there but is a pre-M2 false positive, so
the normal Jung flash plans with the property default and does not feed a Hawk
config). BUSSARD_FLASH_SYS7_LSM=memory|property overrides the realisation for
conformance testing.
The sim implements the device side of BOTH variants (PID-5 property
writes/reads, plus 11-octet memory writes to 0x0104 with 0xB6EA + (lsm-1)
status) and selects by a construction-time flag, so bussard can be
conformance-tested against either realisation without a second sim.
Calibration constants now CONFIRMED (were S7-CAL:): LSM realisation is
mask-family dependent (0705 property PID 5 / 0701 memory-mapped 11-octet @0x0104);
free-access authorize with 0xFFFFFFFF → level 0; max-APDU absent → 15-octet
floor → 12-octet chunks; MCB via A_PropertyValue_Read(PID 27); bare-0x380
fire-and-forget restart. Remaining S7-CAL: on the memory-mapped path: the
TaskSegment marker lead/version octets and the residual alloc seg_flags 0xF3 /
last-EEPROM checksum_ctrl 0x00.
6. Wire constraints¶
- 12-octet memory chunks on standard frames. System 7 has no extended-frame
guarantee; treat max APDU as 15 → 12 data octets per
A_Memory_Write/_Read(3-octet header: APCI+count, addr-hi, addr-lo)[research 5.1/5.3, XKNX PR#1938; M2 Jung 0705 capture CONFIRMED]. The M2 capture is byte-exact on this: every segmentA_Memory_Writecarried exactly 12 data octets on a standard frame (only the trailing partial chunks were shorter), across the whole 0x4000–0x4916 span, with no extended frames. This is exactlyCONSERVATIVE_MEMORY_CHUNK = 12inapci.rs. Do not use the 63-octetMAX_MEMORY_*_LENceiling (that is the System B extended-frame path). Fallback max-APDU when unreadable = 15[XKNX PR#1834; M2 CONFIRMED]. In the M2 capture,PropRead(obj0, PID 56)returned count 0 (47 d6 00 38 00 01) — max-APDU absent — and ETS fell back to the 15-octet standard-frame floor exactly as bussard does. - APDU byte layouts
[research 5.2, CONFIRMED]: A_Memory_ReadAPCI 0x0200:[TPCI|APCI-hi][APCI-lo|count&0x3F][addr-hi][addr-lo].A_Memory_ResponseAPCI 0x0240: same header +countdata octets.A_Memory_WriteAPCI 0x0280: same header +countdata octets. No application-layer ack — verify by read-back.A_Authorizebefore memory access. Sequence:T_Connect → A_Authorize_Request → memory/load writes → A_Restart[corpus §5, research 5.4]. APCIs (CONFIRMED, matchapci.rs): Authorize_Request 0x3D1, Authorize_Response 0x3D2, Key_Write 0x3D3, Key_Response 0x3D4. Key-then-level model: device compares the 4-octet key against its per-level table and returns the granted level (0 = highest privilege … 15 = failed). Unkeyed device → free-access key0xFFFFFFFF(FREE_ACCESS_KEY). Field layout (INFERRED — verify against raw XKNX before wire use,S7-CAL:):M2 CONFIRMED: the Jung 0705 download authorized with the free-access key —Authorize_Request: [APCI][reserved=0x00][key:4 BE] Authorize_Response: [APCI][level:1] Key_Write: [APCI][level:1][key:4 BE] Key_Response: [APCI][level:1]Authorize_Request [d1 00 ff ff ff ff]→Authorize_Response [d2 00](level 0) — exactly the[APCI][reserved=0x00][key:4 BE]/[APCI][level:1]layout above, so0xFFFFFFFFsuffices on an unkeyed device. The sim must accept the free-access key and grant a usable level, and (optionally) reject memory writes issued before a successful authorize so bussard's ordering is tested.- Verification = read-back compare.
A_Memory_Writeis unconfirmed; verify byA_Memory_Read+ compare of echoed address, length, and bytes; a short response is an error[research 3.1, AL 03.03.07 §3.5, CONFIRMED]. MCB is app-dependent on System 7: absent from the MDT-era corpus but demanded by JungA-A011viaLoadImagePropPID 27 — see the section 2 amendment. M2 CONFIRMED: ETS verified the Jung MCB by readingPID 27 (0x1B)per object (PropRead [d5 03 1b 10 01]on objects 1/2/3, object 3 across start indices 1..6), never writing it — exactlyA_PropertyValue_Read(PID_MCB_TABLE). Segment checksums (last byte of a checksum-enabled segment) exist on the BCU2 lineage but are not required for M1. - Restart semantics
[research 6.3; M2 Jung 0705 capture CONFIRMED]. Basic Restart APCI 0x380, no payload, fire-and-forget, breaks the management connection — bussard reconnects after the device reboots. The M2 capture shows exactly this: bare[4f 80]/[6f 80]restarts with no payload and noRestartResponse, no master-reset (0x381/0x3A1) variant anywhere in a normal download. Every load procedure ends with a restart[corpus: 47/49]. Master-reset request/response caveats are errata (section 8), unaffected by M2.
7. Table formats (byte-exact)¶
These are the on-memory forms bussard writes into the 0x4000 / 0x4201 segments
and the sim must accept. ETS synthesizes the CONFIG/TYPE bytes itself (not from
.knxprod); only the data pointers come from product data [Selfbus, CONFIRMED].
7.1 Address table (GrAT) at 0x4000 [endrekatona, knx-stack, CONFIRMED]¶
[CNT:1][own-IA:2 BE][GA1:2 BE][GA2:2 BE]...
CNT = number of 2-byte entries including the own-IA slot (= 1 + number of
group addresses).
- Entry 0 = the device's own individual address, big-endian (TSAP 0 = own IA).
- Each GA = 2 bytes big-endian (15 significant bits, D15 reserved). TSAP 1..N map
to GAs in order.
7.2 Association table (GrOAT) [endrekatona, knx-stack, CONFIRMED]¶
[CNT:1][TSAP0:1][ASAP0:1][TSAP1:1][ASAP1:1]...
CNT = number of (TSAP, ASAP) pairs.
- TSAP = 1-byte index into the address table; ASAP = 1-byte group-object
number (lowest = 0). Ordered as links were created in ETS. m:n allowed.
- Index width: 1 byte confirmed on small devices; 0705 allows ~254 GAs, which
still fits u8. S7-CAL: confirm no 2-byte TSAP/ASAP variant in the corpus.
7.3 Group-object table + descriptors [Selfbus BIM112 dump, CONFIRMED]¶
[CNT:1][RAM-flags ptr:2 BE] then per object a 4-byte descriptor:
[data-ptr:2 BE][CONFIG:1][TYPE:1]
5C (92 objects), 07 00 (RAM-flags table at 0x0700), then
descriptors like 07 5C DF 03.
- data-ptr = 2 bytes BE into user RAM (live value location).
- CONFIG (worked example 0xDF):
This on-device order differs from the ETS UI "C R W T U I"; U/I are not in this byte. Synthesize CONFIG from com-object flags + DPT (bussard already parses
bit 7 reserved, must be 1 bit 6 Transmit enable (T) bit 5 Segment selector type (0 = value in user RAM segment) bit 4 Write enable (W) bit 3 Read enable (R) bit 2 Communication enable (C) bits 1-0 transmission priority (11 = low CONFIRMED; other codes INFERRED)flags/dpt).S7-CAL: exact CONFIG synthesis rule + priority codes for bits 1-0 other than 11=low. - TYPE = object size code. CONFIRMED anchors:
0x00= 1 bit,0x03= 4 bit. INFERRED proposed full mapping (standard KNX length code, verify vs 03.05.01 §4.11):0x00..0x05= 1..6 bit,0x06= 7 bit,0x07= 1 byte,0x08= 2 byte,0x09= 3 byte,0x0A= 4 byte, … up to 14 byte.S7-CAL: confirm TYPE-byte length table beyond 0x00/0x03. - RAM-flags table: 1 byte per com-object (live status). Confirmed 2-bit
codes:
0x00Idle/OK,0x01Idle/Error,0x02Transmitting,0x03Transmit-request. Remaining bits not fully sourced.
Both implementers compute these from the same YAML model (GAs, links, com-object flags/DPT) so a golden-byte test (section 9) can lock them without a device.
8. Errata found during research (re-verify before changing)¶
Two constants in the existing bussard tree disagree with XKNX. Both are for services not on the System 7 download hot path (System 7 uses only basic Restart 0x380), so neither blocks M1 — but they are latent bugs to fix. Re-verify against the existing DA.tp captures before changing them, because bussard's master-reset encoding was validated against a real capture, not GPL source, and the DA.tp captures are the tie-breaker.
-
A_RESTART_RESPONSE.crates/bussard-mgmt/src/apci.rs:106definesA_RESTART_RESPONSE = 0x381(shared with the master-reset requestA_RESTART_MASTER_RESET = 0x381). XKNX gives the master-reset response APCI as 0x3A1[research 6.3]. If XKNX is right, bussard cannot currently distinguish a master-reset request from its response by APCI (it relies on frame flow direction).S7-CAL: A_Restart master-reset response APCI 0x381 vs 0x3A1 against DA.tp. -
master_reset_error_reason.crates/bussard-mgmt/src/load.rs:496maps2= access denied,3= unsupported erase code,4= invalid channel. XKNX's table is0x01= access denied,0x02= unsupported erase code,0x03= invalid channel (0x00= success)[research 6.3]— bussard is off by one. Theapci.rs:104doc comment carries the same off-by-one.S7-CAL: master-reset error-code mapping against DA.tp.
Not errata but adjacent unknowns to resolve at capture: process-time unit (seconds, GPL-corroborated only) and erase-code names 0x02–0x08.
9. Test plan¶
Corpus targets, in order:
1. MDT M-0083_A-000E (AKK-01UP.03, Switching 1-fold) — smallest canonical
0705, 6 segments, ~740 B, no TaskCtrl1. Brings up AbsSegment + TaskSegment +
multi-LSM + the object-0/PID78 preflight. First conformance target.
2. MDT M-0083_A-0008 (Switching 2-fold) — same shape, sanity-check the
engine generalizes across sizes.
3. Theben M-0048_A-4947 (FIX2 DM 4 T, 0701) — exercises TaskCtrl1 and the
post-restart LSM-5 dance (§4.7). Also surfaces the orthogonal wide-integer
parameter-image bug in bussard-prod (472-bit field) — track separately.
4. Jung M-0004_A-A011 (Präsenzmelder Mini Universal, 3361-1MWW) — 11 of the
user's 17 real System 7 devices; not yet in the corpus, fetch this
.knxprod first. Expected to match the MDT canonical LSM 1/2/3 shape.
Conformance loop (sim as oracle): the knx-sim System 7 device model is the executable spec. bussard flashes the sim; the sim asserts the exact wire byte sequence (authorize, 12-octet chunks, LSM records, segment payloads, read-back) against a recorded expectation. Run the sim in both LsmAccess variants (section 5) so bussard's realisation switch is exercised against each. Because both implementers build from this doc, a sim-vs-bussard mismatch is the primary signal that one diverged.
Golden-byte table tests: for M-0083_A-000E, lock the 0x4000 address table,
0x4201 association table, and group-object descriptors as byte-exact fixtures
computed from the YAML model (section 7). These need no device and catch
CONFIG/TYPE-synthesis and endianness regressions.
M2 live-capture milestone. Capture an ETS re-download of a real Jung 0705
device (the .knxprod from target 4). The plaintext memory-write frames resolve,
in priority order, every S7-CAL: marker: (1) LSM realisation — memory vs
property, the 0x0104 / 0xB6EA+ addresses, the 12-octet record layout and status
protocol; (2) whether A_Authorize is mandatory on unkeyed 0705 and the request
field order; (3) the AbsSegment alloc-record access/mem_type/mem_attr octets and
the TaskSegment sub-command; (4) address-table base 0x4000 by read-back; (5) the
CONFIG/TYPE synthesis rule and TYPE length table; (6) the two errata APCIs. Until
M2, ship the seams with the best-evidence defaults above and keep every marker
greppable.
10. Milestones¶
- M1 — MDT canonical shape against the sim: AbsSegment + TaskSegment +
3 parallel LSMs + object-0/PID78 preflight +
A_Authorize+ 12-octet absolute memory streaming + read-back verify. DefaultLsmAccess::MemoryMapped, hand- written default profile. Covers all 43 MDT apps and (by structure) the Jung sensors. Refuse TaskCtrl1 / CompareMem cleanly with a named message. - M1.5 — parse
HawkConfigurationDatainto the data-driven mask profile; drop the hand-written default to a fallback. - M2 — live Jung capture resolves the
S7-CAL:markers; fix any default that was wrong; addTaskCtrl1+ post-restart LSM-5 (Theben) and ZennioCompareMem; fix the two errata against DA.tp. ```