Skip to content

feat: Fundament für die Sensorwert-Dekodierung (ADR-006, Phase 0+1) - #15

Open
sidey79 wants to merge 5 commits into
mainfrom
feat/sensor-decoding
Open

sidey79 wants to merge 5 commits into
mainfrom
feat/sensor-decoding

Conversation

@sidey79

@sidey79 sidey79 commented Sep 16, 2026

Copy link
Copy Markdown
Collaborator

Worum es geht

PySignalduino demoduliert empfangene Funknachrichten bis zum Hex-Payload und publiziert diesen über MQTT. Die zweite Stufe — aus dem Payload konkrete Messwerte machen (Temperatur, Luftfeuchte, Batterie, Sensor-ID, Kanal) — fehlt bisher vollständig; sie liegt in FHEM in den Client-Modulen wie 14_SD_WS.pm. Dadurch ist PySignalduino praktisch nur mit FHEM nutzbar.

Dieser PR entscheidet die Architektur dafür und legt das Fundament. Es wird noch nichts dekodiert — die eigentlichen Decoder folgen in den nächsten Schritten.

Enthalten

ADR-006: Sensorwert-Dekodierschicht

Nimmt „Option 4" aus proposals/fhem_mqtt_integration.adoc an und arbeitet sie aus:

  • eine eigene Schicht signalduino/decoders/ zwischen Parser und Ausgabe — nicht in sd_protocols/, weil das aus den Perl-Quellen generiert wird, und nicht im MQTT-Publisher, weil die Werte auch anderen Senken zur Verfügung stehen müssen
  • datengetriebene JSON-Spezifikationen statt eines Python-Moduls pro Protokoll, mit Python-Ausweg für die unregelmäßigen Fälle
  • drei Ausgabeadapter über ein internes Modell: FHEM-Legacy-String, rtl_433-JSON, Home-Assistant-Discovery
  • Topics, die Ereignis und Zustand trennen; state/messages bleibt unverändert

Die Nummer 006 füllt bewusst die bestehende Lücke, da 007 auf feat/extendRAWmqtt bereits vergeben ist.

Enabler für Stufe 2

  • Bitfolge wird durchgereicht: metadata["bits"] für MS-, MU- und MC-Nachrichten. Die Bitfolge wurde bereits berechnet, aber nur ihre Länge behalten — die Dekodierlogik, die diese Schicht nachbildet, arbeitet auf Bits, nicht auf Hex.
  • Datenmodell: neue Dataclass SensorEvent plus ein einzelnes optionales Feld sensor an DecodedMessage. Keine bestehende Signatur ändert sich; der MQTT-Serializer verwirft das Feld, sodass state/messages byte-identisch bleibt.
  • Testvektoren: tools/fhem_testdata_import.py übernimmt die FHEM-Vektoren nach tests/data/fhem/. Sie enthalten Rohtelegramm, erwarteten Stufe-1-String und erwartete Stufe-2-Readings und sind damit die Paritätsreferenz für beide Stufen. Vendoring hält die Testsuite unabhängig von einem RFFHEM-Checkout daneben.

Aufräumen

sd_protocols/sd_protocol_data.py entfernt: drei handgepflegte Protokolleinträge, die niemand gelesen hat, während die Laufzeit alle 160 Protokolle aus protocols.json lädt. README und User Guide nannten den Stub fälschlich als Datenquelle und zeigen jetzt auf protocols.json.

Absicherung

Der neue Baseline-Test schreibt Stufe 1 auf 86 von 99 reproduzierten FHEM-Vektoren fest und verlangt für die fünf Protokolle mit echter Hardware (27, 50, 85, 125, 126) vollständige Übereinstimmung. Damit wird sichtbar, falls eine spätere Stufe-2-Änderung Stufe 1 beschädigt.

Testlauf: 285 passed, 0 failed (Basis waren 275; die 10 neuen Tests sind dazugekommen, keiner ist weggefallen).

Als Nächstes

Decoder-Kern (crc, dsl, spec, registry, pipeline) und die erste Spezifikation für Protokoll 125 (Fine Offset WH31), anschließend Protokoll 126 und die drei Ausgabeadapter.

sidey79 added 3 commits September 16, 2026 19:11
PySignalduino stops at the demodulated hex payload today, so only FHEM can
turn a received frame into actual measurements. ADR-006 accepts option 4 of
the FHEM integration proposal and lays out the second stage:

* a dedicated signalduino/decoders/ layer between parser and output
* SensorEvent plus one optional field on DecodedMessage, keeping every
  existing signature intact
* data-driven JSON decoder specifications instead of one module per
  protocol, with a Python escape hatch for the irregular ones
* three output adapters over one internal model: FHEM legacy string,
  rtl_433 JSON and Home Assistant discovery
* topics that separate event from retained state, leaving the existing
  state/messages untouched

Numbering note: 007 is already taken on feat/extendRAWmqtt, so this fills
the open 006 slot.
sd_protocol_data.py held three hand-written protocol entries and was the
only thing importing them, while the runtime loads all 160 protocols from
protocols.json via loader.py. Neither `protocols` nor `VERSION` was read
anywhere in the repository.

Keeping it around was actively misleading: README and the user guide named
SDProtocolData as the protocol data source, so both now point at
protocols.json and the generator that produces it.
Groundwork for ADR-006. No decoding happens yet, but everything stage 2
needs is now in place:

* the demodulated bit string is carried in metadata["bits"] for MS, MU and
  MC messages. It was computed already but only its length was kept, and
  the FHEM decoding logic this layer mirrors works on bits, not on hex.
* SensorEvent holds the interpreted measurements, and DecodedMessage gains
  a single optional `sensor` field, so no existing signature changes. The
  MQTT serializer drops that field, keeping state/messages byte identical
  for current consumers.
* tools/fhem_testdata_import.py vendors the FHEM test vectors into
  tests/data/fhem/. They carry the raw telegram, the expected stage 1
  string and the expected stage 2 readings, which makes them the parity
  reference for both stages. Vendoring keeps the suite independent of a
  RFFHEM checkout sitting next to this one.

The new baseline test pins stage 1 at 86 of 99 reproduced vectors and
requires the five protocols with hardware behind them here (27, 50, 85,
125, 126) to match completely, so a stage 2 change cannot quietly damage
stage 1.
@codecov

codecov Bot commented Sep 16, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 94.16581% with 57 lines in your changes missing coverage. Please review.
✅ Project coverage is 83.65%. Comparing base (f7a43fa) to head (d782083).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
signalduino/decoders/registry.py 76.66% 18 Missing and 3 partials ⚠️
signalduino/decoders/pipeline.py 89.09% 9 Missing and 3 partials ⚠️
signalduino/decoders/dsl.py 88.88% 7 Missing and 4 partials ⚠️
tests/fhem_vectors.py 88.52% 4 Missing and 3 partials ⚠️
tests/test_stage1_baseline.py 92.85% 1 Missing and 2 partials ⚠️
signalduino/decoders/spec.py 96.87% 1 Missing and 1 partial ⚠️
tests/test_decoder_registry.py 99.15% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main      #15      +/-   ##
==========================================
+ Coverage   80.77%   83.65%   +2.87%     
==========================================
  Files          57       68      +11     
  Lines        5546     6520     +974     
  Branches      603      701      +98     
==========================================
+ Hits         4480     5454     +974     
+ Misses        853      843      -10     
- Partials      213      223      +10     
Flag Coverage Δ
unittests 83.65% <94.16%> (+2.87%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

There is deliberately no virtualenv in the repository root, which invites
building an ad hoc environment instead - that pulls its own package
versions, misses the companion services and produces numbers that are not
the project's. The devcontainer already defines both, so AGENTS.md now says
so and gives the plain Compose commands for use outside VS Code, including
the explicit pip3 install that the postCreateCommand would otherwise do.

Also records why a branch switch can fail with a permission error on
.devcontainer/fhem-data/: the FHEM image owns those files as 6061 unless
FHEM_UID and FHEM_GID point at the host user.
@sidey79
sidey79 force-pushed the feat/sensor-decoding branch from 08ebb4a to 79502f7 Compare September 16, 2026 18:44
Implements the machinery ADR-006 describes, without any protocol yet: the
first specification follows separately, so the core can be reviewed on its
own merits.

* crc.py holds the checksums once instead of once per client module, as
  pure functions over bytes. Verified against the published check values
  for CRC-8/NRSC-5, CRC-8/MAXIM, CRC-16/CCITT-FALSE and CRC-16/ARC.
* dsl.py evaluates a field rule - bit or hex range, BCD, sign handling,
  offset, scale, rounding, value maps, derivations. Indices are inclusive
  and zero based so they can be copied straight from the FHEM comments.
* spec.py and spec_schema.json define and validate the specification
  language. Validating at load time means a typo is reported once with its
  path, not silently dropped on every telegram.
* registry.py resolves a protocol id to a decoder, preferring a registered
  Python decoder over a specification so a protocol can move between the
  two. A broken specification is logged and skipped instead of taking the
  others down with it.
* pipeline.py runs the sequence from SD_WS_Parse: strip preamble, get bits,
  prematch, checksum, variant, fields, limits. SensorDecoder wraps it so
  that no failure can reach stage 1.

Documented in docs/02_developer_guide/decoder_specs.adoc, including how to
add a protocol and when to reach for a Python decoder instead.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant