feature/zephyr #1

Merged
tristan merged 41 commits from feature/zephyr into main 2026-05-22 07:04:39 +00:00
Owner

Zephyr Branch in main-Branch überführen. Es wird keine andere Plattform/Entwicklung mehr verfolgt.

Zephyr Branch in main-Branch überführen. Es wird keine andere Plattform/Entwicklung mehr verfolgt.
Im Zuge der Migration von PlatformIO auf Zephyr/CMake werden die zuvor
über lib_deps verwalteten Bibliotheken nun manuell als Submodules unter
lib/ gepflegt:

- lib/knx           (thelsing/knx)
- lib/acetime       (bxparks/AceTime)
- lib/acecommon     (bxparks/AceCommon, transitive Abhaengigkeit)
- lib/acesorting    (bxparks/AceSorting, transitive Abhaengigkeit)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Ersetzt die PlatformIO-Build-Konfiguration durch ein Zephyr-Projekt:

- CMakeLists.txt:  Zephyr-Entry-Point, target_sources(app), Platzhalter
                   fuer add_subdirectory der lib/-Submodules.
- prj.conf:        C++20, SERIAL/SPI/GPIO/FLASH/NVS, mbedTLS-Defaults.
- Kconfig:         Anwendungsspezifischer Stub.
- boards/custom/zehnder_knx_gw/: Custom Board (Hardware-Model v2) fuer den
                   STM32L471RE inkl. DTS-Skelett (USART1 Konsole, USART2 KNX,
                   SPI1 SPIRIT1, Flash-Partitionierung).
- src/main.cpp:    Minimales Zephyr-Skelett (printk/LOG_INF) ersetzt das
                   bisherige Arduino-setup()/loop()-Schema.

Konkrete Pin-Zuordnungen im DTS sind als TODO markiert und werden noch
gegen den Schaltplan abgeglichen.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Icon-Assets, die der KNX-Productfile-Generator zusammen mit knxprod.h
erzeugt hat.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Drei Bausteine, die die Arduino-API-Abhaengigkeiten aufloesen:

1. src/Thread.{h,cpp}
   Abstrakte Thread-Basisklasse, die die Zephyr-Thread-API kapselt.
   Stack wird per K_THREAD_STACK_DEFINE in der TU der abgeleiteten
   Klasse deklariert und als Pointer+Size uebergeben (Alignment-
   Probleme mit Class-Member-Stacks vermieden). KNX- und SPIRIT1-
   Threads werden in spaeteren Schritten davon abgeleitet.

2. lib/arduino-shim/
   Minimaler Arduino-API-Shim auf Zephyr-APIs (millis, micros, delay,
   delayMicroseconds, Print-Basisklasse, GPIO-Stubs). Wird primaer
   fuer AceTime gebraucht.

3. lib/knx-zephyr-platform/
   ZephyrPlatform erbt direkt von Platform aus dem knx-Submodule
   (NICHT von ArduinoPlatform). Liegt ausserhalb des Submoduls,
   damit Upstream sauber bleibt. UART-Pfad ueber Ringbuffer +
   IRQ-Callback bereits implementiert; Persistenz-Methoden noch als
   Stubs (TODO).

CMakeLists.txt: bindet beide Adapter-Libraries jetzt aktiv ein.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Build-Integration fuer die in lib/ liegenden Drittanbieter-Sourcen:

- lib/spirit1/CMakeLists.txt
  Neue Zephyr-Library, kompiliert alle .c-Quellen unter lib/spirit1/Src/.
  Die in MCU_Interface.h deklarierten SPI-Funktionen werden spaeter durch
  die RFTransceiver-Klasse bereitgestellt.

- lib/knx-zephyr-platform/CMakeLists.txt
  Erweitert, sodass jetzt auch die knx-Stack-Quellen aus dem Submodule
  (lib/knx/src/knx/*.cpp + knx_facade.cpp) mitkompiliert werden. Das
  Submodule selbst bleibt unveraendert. Plattform-spezifische Files
  des Stacks (arduino_platform.cpp, stm32_platform.cpp, ...) werden
  bewusst NICHT eingebunden — diese Funktion uebernimmt unsere
  ZephyrPlatform-Klasse. Compile-Definitionen aus der frueheren
  PlatformIO-Konfiguration uebernommen (NCN5120, KNX_WAIT_FOR_ADDR,
  KNX_NO_PRINT, KNX_FLASH_SIZE=2048).

- lib/crypto-algorithms wird bewusst NICHT gebaut: laut CLAUDE.md kommt
  das Zephyr-mbedTLS-Modul zum Einsatz. Das Submodule bleibt im Tree
  fuer ggf. Algorithmen, die mbedTLS nicht abdeckt.

- ZephyrPlatform::uniqueSerialNumber() nutzt jetzt hwinfo_get_device_id()
  statt einer hartcodierten STM32-UID-Adresse — portabel auf RP2040 und
  andere Zephyr-Targets. prj.conf zieht CONFIG_HWINFO=y nach.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Kryptografie laeuft kuenftig vollstaendig ueber das Zephyr-mbedTLS-Modul
(siehe prj.conf, CONFIG_MBEDTLS=y). mbedTLS deckt die in diesem Projekt
benoetigten Algorithmen (AES-128-ECB, SHA1/256, HMAC) ab; eine zweite
Crypto-Library bringt keinen Mehrwert.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Vorher stand die Groesse der KNX-Persistenz an drei Stellen:
  - DTS-Partition (boards/.../zehnder_knx_gw.dts)
  - Compile-Define in lib/knx-zephyr-platform/CMakeLists.txt
  - Hardcoded Returnwert in ZephyrPlatform::getNonVolatileMemorySize()

Jetzt ist die DT-Partition 'knx_partition' die einzige Quelle:

  - CMakeLists.txt liest die Groesse zur Configure-Zeit via
    dt_nodelabel() + dt_reg_size() und setzt KNX_FLASH_SIZE entsprechend
    fuer den knx-Stack.
  - ZephyrPlatform::getNonVolatileMemorySize() liefert direkt
    DT_REG_SIZE(DT_NODELABEL(knx_partition)).
  - Ein static_assert garantiert, dass beide Pfade dieselbe Zahl liefern.

Wer die Persistenz spaeter vergroessern oder verschieben moechte,
aendert ausschliesslich den reg-Eintrag der DT-Partition.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Die Klasse kapselt den ST-SPIRIT1-Sub-GHz-Transceiver:

- Zephyr-Treiberzugriffe via DT (DEVICE_DT_GET / SPI_DT_SPEC / GPIO_DT_SPEC).
- Eigener Thread (erbt von Thread), der GPIO-IRQs aus dem ISR-Trampolin
  ueber ein Semaphor abholt und einen vom Anwender registrierten Callback
  aus Thread-Kontext heraus aufruft.
- Hardware-Singleton (genau ein SPIRIT1 im Geraet) — noetig, weil die in
  MCU_Interface.h deklarierten Vendor-C-Callbacks keinen this-Pointer
  transportieren.
- extern "C" Adapter implementieren das SPIRIT1-SPI-Protokoll
  (Write/Read/Command-Strobe/FIFO via 2-Byte-Header + Daten) und leiten
  die Status-Bytes aus der MISO-Header-Phase an die Vendor-Library zurueck.
- SPI-Zugriffe sind ueber k_mutex serialisiert.

Devicetree:
- dts/bindings/st,spirit1.yaml: Minimal-Binding (basiert auf spi-device.yaml,
  ergaenzt sdn-gpios und irq-gpios).
- boards/custom/zehnder_knx_gw/zehnder_knx_gw.dts: spirit1-Node aktiviert,
  IRQ-Polaritaet auf GPIO_ACTIVE_LOW (entspricht SPIRIT1-GPIOx-Default).

Konkrete SDN-/IRQ-Pins sind weiterhin Platzhalter und werden gegen den
Schaltplan ausgetauscht. Die Vendor-Library (lib/spirit1/) kompiliert nun
end-to-end gegen die hier definierten MCU_Interface-Adapter.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Der von der ST-SPIRIT1-Workbench generierte Init-Code wird ueber die
neue Methode RFTransceiver::configureRadio() in die Aufrufkette
aufgenommen:

  RFTransceiver::instance().init();           // Zephyr-Treibersetup
  RFTransceiver::instance().configureRadio(); // SRES + Workbench-
                                              // Register + VCO-Calib
  RFTransceiver::instance().start();          // IRQ-Thread

Aenderungen im Detail:

- src/generated/spirit1/Register_Setting.c
  '#include "MCU_Interface.h"' ergaenzt, damit die im Workbench-Output
  verwendeten SpiritSpi*-Aufrufe ueber die Makros auf unsere RadioSpi*-
  Adapter umgelenkt werden (ohne diesen Include wuerde der Linker auf
  undefinierte Symbole laufen). Hinweis im File: bei erneutem Export
  aus der Workbench wieder ergaenzen.

- src/RFTransceiver.{h,cpp}
  Neue Methode configureRadio() ruft SpiritBaseConfiguration() (SRES +
  Workbench-Register-Init) gefolgt von SpiritVcoCalibration() (TX/RX-
  VCO-Werte). Vorwaerts-Deklarationen der beiden Workbench-Funktionen
  im extern "C"-Block, da Register_Setting.c keinen eigenen Header
  besitzt.

- CMakeLists.txt
  Register_Setting.c jetzt teil des app-Targets. Generierter Code wird
  mit -w gebaut, damit projektweite Warn-Levels nicht durch Vendor-
  Code-Stil verletzt werden.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Die zuvor als LOG_WRN-Stubs hinterlassenen Persistenzfunktionen sind jetzt
echte Implementierungen gegen Zephyrs Flash-Map-API. Statt alle hoeheren
NV-Memory-Methoden zu ueberschreiben, nutzen wir die Buffer-Verwaltung der
KNX-Stack-Base-Class und liefern nur die Low-Level-Primitiva:

  - userFlashStart()           Memory-mapped Pointer auf Partitionsanfang
                               (Adresse zur Compile-Zeit aus DT-Flash-Node-
                               Basis + Partitions-Offset abgeleitet).
  - userFlashSizeEraseBlocks() Partitionsgroesse / (Page * Eraseblock).
  - flashPageSize()            erase-block-size-Property des Flash-Controllers
                               (STM32L4 = 2048, RP2040 = 4096) — vorher
                               2048 hartcodiert.
  - flashEraseBlockSize()      1 Page pro Eraseblock.
  - flashErase(idx)            flash_area_erase().
  - flashWritePage(idx, data)  flash_area_write().

Aus zephyr_platform.h entfernt: die Override-Deklarationen von
getNonVolatileMemoryStart/Size, commitNonVolatileMemory sowie beide
writeNonVolatileMemory- und die readNonVolatileMemory-Methode. Die
Base-Class-Default-Implementierungen erledigen das fuer Flash-Mode korrekt.

ZephyrPlatform haelt jetzt einen flash_area-Handle, den der Konstruktor
ueber flash_area_open(FIXED_PARTITION_ID(knx_partition), ...) initialisiert.
Der Handle ist fuer die gesamte Anwendungslaufzeit gueltig — bei
fixed-partitions ist kein Close noetig.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Stellt die Primitiva bereit, die das Zehnder-RF-Protokoll benoetigt:

- encryptBlock / decryptBlock                AES-128-ECB, ein 16-Byte-Block
- encryptEcb / decryptEcb                    AES-128-ECB, mehrere Bloecke
- crc8(data, length, seed)                   CRC-8 Polynom 0x07, beliebiger
                                             Seed (RF-Protokoll: 0x0B)
- hmacSha256(key, data, out[32])             HMAC-SHA-256 (fuer Pairing)

AES-Kontexte werden pro Aufruf lokal angelegt und mit mbedtls_aes_free()
sicher freigegeben — kein langlebiges Schluesselmaterial in
Klassenmembern, dadurch implizit thread-safe ohne Mutex.

Diffie-Hellman fuer Pairing ist noch NICHT abgedeckt — wird ergaenzt,
sobald die Implementation_guide den DH-Aufbau finalisiert hat.

prj.conf um CONFIG_MBEDTLS_CIPHER=y und CONFIG_MBEDTLS_MD=y erweitert,
damit der generische Cipher-Layer und der Message-Digest-Layer (Basis
fuer HMAC) gebaut werden.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Die Heater-Klasse vermittelt zwischen Application (KNX) und RFProtocol:

  KNX-Application  <-->  Heater  <-->  RFProtocol  <-->  RFTransceiver

Sie kapselt das Heizgeraet auf Anwendungsebene und erlaubt der Application
einen typsicheren Zugriff (Mode-Enum, Temperatur in °C, Boost-Dauer in
Minuten) — die Protokoll-Codierungen (tempX2, Modus-Bytes 0x01..0x08,
1/100 °C-Codierung der Raumtemperatur) bleiben hier intern.

Zwei Daten-Pfade:

  1. Befehle (Application -> Heater -> RFProtocol):
     setMode, setComfortSetpoint, setEcoSetpoint, setBoost,
     setWindowOpenDetection, setSensorMode, sendRoomTemperature,
     syncRtc, setWeeklySchedule, startPairing.
     Eingangswerte werden validiert/geclampt (Setpoint 7..28 °C bzw.
     7..19 °C ECO, Boost 15..120 min).

  2. Zustands-Updates (RFProtocol -> Heater -> Application):
     updateMode/-ComfortSetpoint/-EcoSetpoint/-HeatingStatus/
     -SensorMode/-BoostActive/-Reachable.
     Heater haelt den zuletzt bestaetigten Geraete-Zustand und feuert
     bei Aenderung den jeweiligen Observer-Callback. Damit reagiert
     die Application auf eingehende Burst-RX-Werte, ohne RFProtocol
     direkt zu kennen.

Quelle der Mode-Codes und Sollwert-Kodierung: doc/Implementation_guide.md
Abschnitt 7 und 8.

Da RFProtocol noch nicht existiert (vorhandener Header ist nur ein
Entwurf), ist die Klasse per Forward-Declaration eingebunden und die
Setter loggen aktuell nur LOG_INF. Die Update-Pfade sind dagegen
vollstaendig implementiert — sobald RFProtocol live ist, kann es die
update*()-Methoden direkt befuellen.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
CONFIG_PSA_CRYPTO=y schaltet das PSA-Framework ein, wodurch die bereits
gesetzten PSA_WANT_*-Optionen erst wirksam werden und die ueber
crypto_adjust_config_enable_builtins.h abgeleiteten MBEDTLS_*_C-Defines
gesetzt werden -- ohne das brach tf-psa-crypto/extras/md.c mit "unused
parameter" ab. Ergaenzend wird im Board-DTS MSI@48 MHz und der RNG-Node
freigeschaltet (STM32L471 hat kein HSI48), sodass CSPRNG_NEEDED durch
echte Hardware-Entropie abgedeckt ist und der Fallback auf
TIMER_RANDOM_GENERATOR/TEST_CSPRNG entfaellt.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Das Projekt wird ausschliesslich ueber west/CMake gebaut. Die Datei
boards/genericSTM32L471RE.json war ein Ueberbleibsel der frueheren
PlatformIO-Toolchain (vgl. Commits 119f349 und ae2254f) und hat in
einem Zephyr-Projekt keine Funktion mehr.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Die bits.h des knx-Submoduls definiert ntohs/htons/ntohl/htonl nur fuer
__linux__, ARDUINO_ARCH_* und LIBRETINY. Unter Zephyr fielen alle knx-
Core-Quellen damit auf den generischen #else-Zweig und brachen mit
"'ntohs' was not declared in this scope" ab (group_object.cpp,
association_table_object.cpp, interface_object.cpp u.a.).

Statt das knx-Submodule zu patchen (wuerde den Submodule-Pointer dirty
machen und Updates erschweren), liefert nun lib/knx-zephyr-platform/
knx_compat.h die Makros auf Basis von <zephyr/sys/byteorder.h>
(sys_be16_to_cpu etc.) und wird per `-include` vor jede knx-Quelle
gezogen.

TARGET_DIRECTORY ist dabei zwingend: ab CMake 3.18 (Policy CMP0118)
sind Source-File-Properties scope-lokal, das `app`-Target lebt aber
in der Root-CMakeLists.txt.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Ab mbedTLS 4 liegen die klassischen Header (mbedtls/aes.h, mbedtls/md.h)
nicht mehr im oeffentlichen Include-Pfad — sie sind unter mbedtls/private/
und auf dem Pfad zur Entfernung. Stattdessen ist die PSA-Crypto-API der
unterstuetzte Weg, und Zephyr exponiert sie ueber CONFIG_PSA_CRYPTO=y.

Diese Migration ersetzt die mbedtls_aes_*- und mbedtls_md_*-Aufrufe durch
psa_cipher_encrypt/decrypt (AES-128-ECB) und psa_mac_compute (HMAC-
SHA-256). Die public API der Crypto-Klasse bleibt unveraendert, sodass
keine Aufrufer angepasst werden muessen.

Details:
  * psa_crypto_init() wird ueber einen C++20-Magic-Static genau einmal
    thread-safe ausgefuehrt — kein eigenes Locking noetig.
  * Schluessel werden pro Aufruf als volatile PSA-Keys importiert und
    am Ende mit psa_destroy_key() zerstoert; damit bleibt der bisherige
    Pure-Function-Stil erhalten und es leakt kein Klartext-Schluessel.
  * Die ECB-Pfade (encryptBlock/decryptBlock und encryptEcb/decryptEcb)
    teilen sich einen internen doEcb-Helper. Da PSA fuer ECB_NO_PADDING
    keinen IV einfuegt und Multi-Block intern zerlegt, entfaellt die
    bisherige Block-Schleife.
  * Header-Kommentar in Crypto.h auf PSA-Konfiguration aktualisiert.

Die noetigen Kconfig-Symbole (CONFIG_PSA_CRYPTO=y plus PSA_WANT_KEY_TYPE_
AES/ALG_ECB_NO_PADDING/KEY_TYPE_HMAC/ALG_HMAC/ALG_SHA_256) waren in
prj.conf bereits gesetzt.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
DT_MTD_FROM_FIXED_PARTITION liefert den Flash-CONTROLLER (flash-controller@
40022000) — auf dem es weder erase-block-size noch write-block-size gibt.
DT_PROP(..., erase_block_size) brach daher beim Compile mit "not declared
in this scope" ab.

Korrekt ist DT_MEM_FROM_FIXED_PARTITION: liefert den Flash-Chip-Memory-Node
(flash@8000000, compatible "soc-nv-flash"), wo erase-block-size und
write-block-size deklariert sind. Auch DT_REG_ADDR gibt dort die korrekte
Memory-Base zurueck (STM32L4: 0x08000000); das ist semantisch sauberer als
am Controller-reg=0x40022000 zu drehen, das nur den Peripherie-Registersatz
adressiert.

Zusaetzlich:
  * static_assert auf DT_NODE_EXISTS(KNX_FLASH_DEV_NODE) — wenn die
    Partition mal nicht unter einem soc-nv-flash haengt, liefert das
    DT_MEM_FROM_FIXED_PARTITION-Macro DT_INVALID_NODE und der Fehler
    schlaegt fruehzeitig mit klarer Meldung zu.
  * Erklaerungsblock vor der Define-Kaskade ergaenzt, der die Falle
    Controller-vs-Memory-Node dokumentiert.

Damit baut der Zehnder-KNX-Gateway erstmals vollstaendig durch (zephyr.elf,
~34 KB FLASH).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eigenstaendige Zephyr-App unter tests/ (native_sim/WSL), GoogleTest v1.14
via FetchContent. Deckt CRC-8, AES-128-ECB (Single- und Multi-Block),
HMAC-SHA256 und Session-Key-Unwrap ab — alle Vektoren stammen aus dem
realen Capture "Mit_Heizkoerper.csv" und gewaehrleisten Bit-fuer-Bit-
Kompatibilitaet mit dem Heizer. Drei DISABLED_-Placeholders sind fuer
die spaetere Crypto::dhModPow-Implementierung vorbereitet.
Schrittweise Anleitung fuer Ubuntu/WSL2 (apt-Pakete, Python-venv, west,
Zephyr-SDK 1.0.1 minimal, bashrc-Setup). Board-Variante auf
native_sim/native/64 korrigiert (32-bit native_sim kollidiert mit dem
64-bit-GoogleTest beim Linken). Build-Verzeichnis liegt jetzt in
~/build-knx-tests (WSL-ext4) wegen 9P-Schreibperformance. GoogleTest-Filter
ueber native_sim's -testargs dokumentiert; eigener Abschnitt zur
VS-Code-Remote-WSL-Integration ergaenzt.
native_sim reicht die argv der Hostbinary nicht an Zephyrs main() durch;
Args fuer die App muessen ueber -testargs uebergeben und via
native_get_test_cmd_line_args() abgeholt werden. Da die API argv[0] nicht
liefert, GoogleTest aber ab Index 1 parst, einen Dummy-Programmnamen
prependen — damit funktionieren --gtest_filter und --gtest_list_tests
korrekt. Loest ausserdem GoogleTests 1.14-Warnung "did NOT call
testing::InitGoogleTest()" beim no-args-Aufruf.

Zusaetzlich am Ende _Exit(rc) statt return rc: native_sim laesst sonst
den Kernel-Loop nach main()-Return weiterlaufen, was CI-Runs blockiert.
Tagesgeschaeft-Befehle (zephyr-venv, west build, zephyr.exe) plus die
WSL-spezifischen Stolpersteine (native_sim/native/64 statt 32-bit-native_sim,
Build-Verzeichnis ausserhalb /mnt/c, -testargs fuer GoogleTest-Filter).
Verweis auf tests/README.md fuer das vollstaendige One-Time-Setup.
Implementiert die reinen Daten-Operationen des Zehnder-RF-Protokolls:
19-Byte-Normalpakete (READ/WRITE 1-/2-Byte), 35-Byte-Multi-Block-Pakete
und das B4-Session-Confirm-Sonderpaket - jeweils mit Ciphertext-Feedback-
Nonce und passender CRC-Behandlung. Discovery-Ping als constexpr Konstante.

17 GoogleTests gegen die Reverse-Engineering-Vektoren aus
crypto_vectors.h verifizieren Bit-fuer-Bit-Kompatibilitaet zum realen
Heizer (TX-Kette mit prev_ct-Feedback, B4-Capture, RX-Parser, Multi-Block-
Roundtrip, Limits, Korruptionsfaelle).

Beobachtung dabei: Die "Unified-CRC-Formel" aus Implementation_guide.md
Abschnitt 4.3 gilt nur fuer Single-Block-Pakete. Multi-Block-Pakete haben
LEN=0x22 statt 0x12 im Wire-Header, wodurch sich der Seed nicht ueber
CRC8(header, 0xA4) ableiten laesst - der Heizer verwendet hier trotzdem
direkt den klassischen RX-Seed 0x0B (vgl. C4-Vektor). Im Code dokumentiert
und durch einen Regressionstest abgesichert.
Erweitert RFTransceiver um den vollstaendigen Wire-Layer ueber den
SPIRIT1, ohne die State-Machine selbst (die kommt im naechsten Schritt):

- sendPacket(): blockierender TX mit SMPS-Workaround via Library-Helpern
  SpiritCmdStrobeTx / SpiritManagementWaCmdStrobeRx. Variable-Length-
  Mode: data[0] traegt das LEN-Feld, SPIRIT1 transportiert es auf Luft.
  Wartet ueber Semaphore auf TX_DATA_SENT (oder Fehler/Timeout).
- startReceive(): SABORT, FIFO-Flush, COMMAND_RX (via SpiritCmdStrobeRx,
  damit SMPS auch nach RX-Wechsel sauber steht).
- abort(), enterSleep(), wakeFromSleep(): Power-Management mit
  MC_STATE-Polling.
- run() loest den generischen irqCb_-Hook ab durch konkretes Dispatching:
  TX_DATA_SENT/TX_FIFO_ERROR/MAX_BO_CCA, RX_DATA_READY (mit FIFO-Read und
  RSSI ueber SpiritQiGetRssidBm), CRC_ERROR, RX_FIFO_ERROR, RX_TIMEOUT.
  Zwei neue Callbacks (RxCallback / RxErrorCallback) ersetzen den
  generischen Hook.

applyRegisterPatches() schiebt die Werte nach, die das Workbench-Skript
(Register_Setting.c) auslaesst — GPIO3=nIRQ, GPIO0..2 disabled, MOD0=0x5B,
PCKTCTRL2=0x27, PCKT_FLT_GOALS[0]=0xFE, PCKT_FLT_OPTIONS=0x01 (kein
Auto-ACK), IRQ_MASK ueber SpiritIrqInit/SpiritIrqs. Damit ist der Chip
nach configureRadio() protokollgerecht aufgesetzt.

Firmware-Build (zehnder_knx_gw/stm32l471xx) sauber, Flash 6.63 %, RAM 18.52 %.
Erweitert RFProtocol um die laufende Session-Logik und verdrahtet alle
Bausteine zu einer voll konstruierten Firmware:

- RFProtocol erbt jetzt von Thread und führt die Phasen aus
  Implementation_guide.md §6 sequenziell aus: Discovery → SessionAuth
  (Replay) → B4-Confirm → 2-Block-RX → SyncBurst → Sleeping. Bei Fehlern
  exponentielles Backoff (5 s, 10 s, … max. 4 h).
- SyncBurst pollt die 7 Standard-READs (q/v/t.5/t.4/t.1/n/m) und dispatcht
  die Antworten an Heater::updateXxx(). Anschließend werden gesetzte
  Dirty-Flags als WRITEs gesendet (Mode, Comfort-/Eco-Sollwert, Window-
  Open, Sensor-Mode, Room-Temperature). Boost/RTC/Weekly-Schedule sind
  als TODO im Code markiert.
- Setter (setMode, setComfortSetpoint, …) schreiben Pending-Wert plus
  Dirty-Flag unter stateMutex_ und triggern einen sofortigen Burst,
  wenn die Session gerade in Sleeping wartet. Burst-Intervall (Default
  30 Min., max. 60 wegen Keep-Alive-Pflicht) wird über k_timer in
  Sleeping rearmt.
- Threading: RFTransceiver-IRQ-Thread (Prio 6) füttert über
  setRxCallback eine k_msgq, RFProtocol-Thread (Prio 7) holt RX-Frames
  blockierend ab. Setter-Aufrufer (Heater) berühren nur die Dirty-
  Flag-Sektion — keine TX-Aktivität im Aufrufer-Kontext.

Heater.cpp: TODOs in allen Setter-Methoden durch rfProtocol_->setXxx()
ersetzt. Update-Pfade waren bereits vollständig.

main.cpp ist jetzt das echte Bootskript: Crypto → RFTransceiver
(init/configureRadio/start) → RFProtocol (init mit SessionData/start) →
Heater.init → RFProtocol.startSession.

src/SessionData.h kapselt das Session-Material (AES-Key, 57-Byte-Auth-
Paket, 15-Byte-B4-Plaintext) als Compile-Time-Konstante aus dem RE-
Capture. NVS-Persistenz und das tatsächliche Wire-Format des
Auth-Pakets (LEN-Byte-Frage) sind als TODOs dokumentiert.

Firmware: FLASH 11.02 % (vorher 6.63 %), RAM 34.94 % (vorher 18.52 %).
Anstieg im wesentlichen durch RFProtocol-Thread-Stack (4 KB),
Auth-Paket im Flash und die Pakete-Bauer im Burst-Code. Tests (68/68)
bleiben grün.
Application-Klasse zwischen thelsing/knx-Stack und Heater. Eingangs-KOs
(Mode, Sollwerte, Boost, Lock, Zeit/Datum, externe Temperatur) werden
DPT-konform in Heater-Setter uebersetzt; Heater-Observer publizieren
Status-KOs gesammelt aus dem KNX-Loop-Thread.

ZephyrPlatform bekommt einen Default-Ctor (loest knx-uart ueber
DT-Alias), damit `KnxFacade<ZephyrPlatform, Bau07B0>` als globale
Variable konstruierbar ist (Voraussetzung fuer die KoAPP_/ParamAPP_-
Makros aus knxprod.h).

HVAC-Mode- und Lock-Mode-Mapping liegen als inline-statische Helfer in
Application.h und werden von test_application.cpp ohne KNX-Stack auf
Roundtrip-Konsistenz geprueft.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Drei neue Sequenz-Helfer, die aus doSyncBurst() heraus laufen:

- doBoostSequence (Guide §8.3): 4-WRITE-Kette (v sub=3/6/2/1) fuer
  Boost EIN bzw. (t sub=3, v sub=6/2/1) fuer Boost AUS. Setter snapshotted
  beim ON den aktuellen Modus, damit AUS ihn wiederherstellen kann.
  dispatchToHeater merkt sich das rohe 'v' sub=1 Byte (Persistenz-Bits)
  als lastVSub1_; AUS-Sequenz schreibt das Byte mit Bit 0 gesetzt/
  geloescht zurueck.

- doScheduleSequence (Guide §9): 7x Multi-Block-WRITE auf 'u' sub=3..9
  (Mo..So) mit payload=[sub, 20 schedule-bytes], danach 'u' sub=2 mit
  P1/P2-Flagbits und 't' sub=3=0x08 zur Aktivierung. sendMultiBlock()
  als TX-only Variante von sendAndParseRx (Heizer-Antwort ist Normal-
  Frame, nicht Multi-Block).

- doRtcSyncSequence (Guide §10): 9-WRITE-Folge mit 15-s-Vorlauf, damit
  der Commit auf einem Sekundenrand committed. addSecondsSameDay() als
  pure Helper: liefert nullopt, wenn der Vorlauf den Tag wechseln wuerde
  — Aufrufer committed dann sofort (kein Datums-Rollover noetig).

Tests: addSecondsSameDay (Trivial, Rollover, Day-Crossing, negative,
zero) und ein Roundtrip-Test fuer die Multi-Block-Wochenprogramm-Payload
(buildMultiBlockPacket -> parseMultiBlockRx).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Refactor + Test-Infrastruktur:

- IRFTransceiver-Interface in RFTransceiver.h: TxResult/RxError und
  Callbacks leben jetzt im abstrakten Eltern; RFTransceiver erbt und
  override't. RFProtocol haengt nicht mehr direkt am SPIRIT1-Backend,
  sondern an dieser Schnittstelle. Aufrufstellen aendern sich nicht
  (Name-Lookup loest RFTransceiver::TxResult auf IRFTransceiver::TxResult).
- RFProtocol::init nimmt IRFTransceiver& statt RFTransceiver&; Member
  ist IRFTransceiver*.
- Neuer friend class RFProtocolTestAccess gibt Tests Zugriff auf die
  privaten Phasen-Methoden (doDiscovery, doSessionAuth, doB4Special,
  doTwoBlockRx, doSyncBurst) und auf interne Felder (allocIdx_, prevCt_,
  Dirty-Flags) — Production-API bleibt sauber privat.

Tests (tests/src/test_rfprotocol_statemachine.cpp, 13 Tests):

- Discovery: Erfolg im ersten Versuch, Retry nach falscher Header,
  Retry nach RxError-Event, Aufgabe nach 4 falschen Antworten.
- SessionAuth: 57-Byte-Replay korrekt versendet, 39-Byte-Antwort
  abgelehnt.
- B4-Sonderpaket: Frame mit Header 06 00 01 versendet, prevCt_ und
  allocIdx_ werden danach auf 0 zurueckgesetzt.
- TwoBlockRx: nimmt jedes Multi-Block-Frame entgegen.
- SyncBurst: 7 Read-Polls ohne Dirty-Flags, +2 fuer Mode/Comfort,
  +4 fuer Boost EIN, +4 fuer Boost AUS. dispatchToHeater-Pfad fuer
  einen gueltig verschluesselten 'q'-Read verifiziert.

MockTransceiver (tests/src/mock_transceiver.h): erbt IRFTransceiver,
zeichnet jeden sendPacket()-Aufruf in `sent` auf, konsumiert eine
Antwort aus der Queue und speist sie synchron in den RxCallback —
RFProtocol::waitForRx kehrt ohne Echtzeit-Verzoegerung zurueck.

Begleitfix in test_rfprotocol.cpp: ScheduleMonday-Roundtrip-Test
versuchte buildMultiBlockPacket -> parseMultiBlockRx, was an der
CRC-Seed-Asymmetrie (TX 0x90 vs. RX 0x0B) scheitert. Neu: direkter
Decrypt + Plaintext-Layout-Vergleich.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Heater bekommt eine kleine Forwarder-Methode, die den Wert direkt an
RFProtocol::setBurstIntervalMinutes durchreicht. Damit kann
Application::applyParameters() den KNX-Parameter syncInterval (6 Bit,
1..60 Minuten) wirken lassen, ohne RFProtocol direkt zu kennen
(Architekturregel aus CLAUDE.md).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
tristan/zehnder_knx_gateway!1
No description provided.