Three independent reviewers (two external councils + a field integrator) asked for the same thing: the enriched model mixes ETS facts, DPT structure and name heuristics, and downstream tools treat it almost as fact. explain_ga makes the reasoning auditable per GA — evidence per decision with a confidence tier (authoritative ETS Function > structural DPT > heuristic name), the status-pairing method, and CONFLICTS (name 'AC' vs DPT 'lighting' -> contested), the silent- misclassification hotspot. Additive, read-only, no core-model change. Full suite green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
29 KiB
Changelog
All notable changes to this project are documented here. The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
Unreleased
Added
-
Provenance / confidence (
explain.py, new MCP toolexplain_ga). The enriched model mixes ETS facts, DPT-derived structure and name heuristics; downstream tools then treat the result almost like a fact.explain_ga(address)makes the reasoning auditable for one GA: per decision (category / kind / status pairing) it reports the signals that fired with a confidence tier — authoritative (an ETS Function role) > structural (the KNX DPT) > heuristic (a name keyword) — and flags conflicts (e.g. a GA the DPT callslightingwhile the name says "AC" →contested), the hotspot for silent misclassification. Additive and read-only (no change to the core model). Asked for by three independent reviewers (two external councils + a field integrator).tests/test_explain.py. Tool count 27 → 28. -
Role-aware feedback completeness (
detect_role_completenessinanalyze.py, surfaced throughcheck_missing_status). "Does the function have a status?" was not enough — a dimmer with an on/off status but no brightness status silently passed and inflated coverage/Matter scores. The new check flags a brightness/position command (DPT 5.001) whose device has no matching value status (missing_value_status), using a device-identity match so it does not borrow a sibling's status. Closes the demo's own documented limitation — recall on the demo house is now 5/5. Raised by an external expert review.tests/test_role_completeness.py. -
Project Policy Profile (
policy.py, new MCP toolcheck_policy). Validate a project against its own agreed rules (main-group taxonomy, naming regex, command/status exemptions) instead of one universal "professional standard" — a direct answer to integrator feedback that conventions differ per school. With a YAML profile the declared taxonomy is authoritative; with no profile the taxonomy is inferred from the project itself and GAs that deviate from their main group's own majority domain are flagged (policy_taxonomy_outlier) — never against an alien standard. Ships an example profile (examples/policy-profile.example.yaml) andtests/test_policy.py. Tool count 26 → 27. -
Cross-device parameter QA (
param_check.py, new MCP toolcheck_device_parameters). Reads per-deviceParameterInstanceRefvalues straight from the.knxprojproject part (data xknxproject does not expose), groups identical devices by application program, and flags the odd one out:clear_outliers(a strong majority with a small minority — e.g. one thermostat with a different setpoint/hysteresis, one presence detector with a different detection time) andsplit_configs(balanced 2+ variants — review, often two zones). Parameter names resolved from the app-program. Findings are ranked by significance: afocuslist of config-value outliers (setpoint/hysteresis/time/threshold — where an odd device is usually a real mistake), separated from mode/type flags and per-room text labels — on a real 275-device project this turns 422 raw outliers into a 9-item focus (incl. the thermostats whose init-setpoint differs from their siblings). Read-only; no ETS/bus; encrypted projects are skipped honestly. Validated on real 42–275-device projects; synthetictests/test_param_check.py. Community-driven (asked for in Discussions). Tool count 25 → 26. -
skills/ha-git-backup— an ops-companion skill for the engineer package: a two-circuit Home Assistant backup system (real git in/configwith a deploy key and a pre-commit secret scanner + age-encrypted full backups in GitHub Releases), with install/sync/scan/offsite/restore scripts, HA automations, a restore runbook and a mandatory monthly drill. Field-tested; the secret scanner ships canary-tested (BusyBoxgrep -elesson recorded). -
Manufacturer names from the archive itself (
appprog_parser.py):parse_devices_from_projectnow reads vendor names from theknx_master.xmlshipped inside every.knxproj/.knxprod(e.g. M-0073 → HDL, M-00B6 → Ekinex S.p.A.) instead of relying on a hardcoded id map that can never be complete; the static map remains as fallback.
Fixed
-
Noise & unsafe repairs surfaced by an external expert review on the demo house. (1) Scene-control GAs (DPT 17/18) are no longer flagged as
missing_status— they recall a preset and have no single state to read back (nowscene_no_status, INFO), andsuggest_repairsno longer synthesises a bogus boolean status GA for them. (2) Generic group commands (All blinds down,All shutters up) are now recognised as central macros (INFO) likeAll lights off, not missing-status warnings. (3) Synthesised repair names match the GA's language — an English project no longer gets a Russian(статус)/Значение яркостиsuffix. On the demo house this cutmissing_status_addresswarnings from ~7 (mostly scenes) to the one genuine case.tests/test_council_fixes.py. -
skills/ha-git-backup:install.shnow pinscore.sshCommand+ a repo-localknown_hostsso the nightly sync pushes from HA's Core container (was failingHost key verification failed).
0.8.0 — 2026-07-07
Lessons from a second field project (a different integrator naming school): positional command/status pairing, ref-level application programs, and two detector blind spots. All four changes were validated against a real 859-GA as-built project.
Added
- Positional command/status pairing (#4,
pairing.py/analyze.py). A widespread naming school keeps feedback in a parallel middle group (command1/0/s↔ status1/1/s) with the GA name duplicated 1:1 and no status keyword at all — invisible to lexical pairing.detect_missing_statusnow also pairs positionally (same main, same sub, different middle, identical name, DPT-compatible) and recognises self-reporting commands (the actuator's R+T communication object linked to the command GA itself). On the field project this cut missing-status warnings from 339 to 89 — the ~90 that are actually real — and reports astatus_pairing_summaryinfo with both counters. - Application-program parser v2 (#6,
appprog_parser.py). Vendors like HDL, Ekinex and Creatrol publish object names/DPTs/flags on<ComObjectRef>while the base<ComObject>is a near-empty stub. The parser now merges ref-level data over the base (conservatively: refs only fill empty fields; a DPT is taken only when every declaring ref agrees, disagreeing variants are recorded asdpt_variants, never guessed). Unverified DPTs on the field project's 15 device models dropped 4341 → 1167 (−73%). The app-program version is now picked by the highest ApplicationVersion instead of list position — a stale v16 was being parsed where v18 existed.
Fixed
main_group_unnamedread the wrong names (#3,analyze.py): main-group names are now taken from the authoritativegroup_ranges(a GA record'smain_namecan carry a middle range's name), eliminating both false "unnamed" findings and masked truly-unnamed mains.- Completeness grader missed pattern-named ranges (#5,
advanced.py): a middle range literally called «Сцены» full of plain-named scene GAs now counts as scene evidence — the grader reads main/middle range names in addition to GA names (and the scene token matches Russian plural forms). - Pinned
mcp>=1.10,<2— the MCP Python SDK v2 (in alpha) renamesmcp.server.fastmcp.FastMCPtomcp.server.MCPServer; without the upper bound a futurepip installwould pull v2 and break the server import. Verified against the SDK docs.
0.7.0 — 2026-07-02
Added
- Exact device decomposition from a local catalog (
device_library.py). When theNICKOL_KNX_CATALOGenv var points at a device-library YAML file or directory (schema:library-schema.md),decompose_devicenow returns the exact vendor object model (source: catalog-exact) — real per-channel blocks, object counts, app-program version and first-instance objects with their true DPTs — instead of the generic recipe. Falls back to the built-in recipes (source: recipe-approximate) for any device not in the catalog, so behaviour is unchanged when the env is unset. The catalog itself is vendor-catalog data kept local and is not shipped with the package. Objects the vendor app-program leaves without a DatapointType staydpt: null— never guessed. parse_devices_from_project(new MCP tool +appprog_parser.py) — deterministic parser that extracts the exact vendor comm-object model from theM-*application programs embedded in a.knxproj/.knxprod: per device it reports order number, app-program version, object counts and detected per-channel blocks (unit · objects-per-instance · stride), convertingDPST-x-y→x.00y. Read-only and PII-safe (reads only vendor catalog data, never the clientP-*/0.xml). Withoutput_pathit writes a device-library YAML into the workspace — feeding the local catalog above, soparse → catalog → decompose_device (catalog-exact)is a closed loop. Now 25 MCP tools.
0.6.0 — 2026-07-01
Completes the roadmap — the tool now validates, repairs, generates (HA / ETS / handover / IoT), diffs, grades and drafts acceptance protocols, all design-time & read-only.
Added — B-tier
- B2 — climate-correctness review (
generate_ha.py). Climate generation now emits an explicit review note: controller/operation modes made explicit, setpoint-shift command and state paired, and a flag raised for any mode object that lacks a corresponding state address. - B3 — semantic project diff (
diffproj.py, new module; new MCP toolsdiff_projects/diff_loaded). Compares two.knxprojversions and reports added / removed / DPT-changed / renamed / security-changed group addresses — a reviewable delta between as-designed revisions. - B4 — acceptance test protocol (
advanced.py; new MCP toolgenerate_test_protocol). Produces a per-function acceptance checklist in Markdown for commissioning sign-off. - B5 — Matter readiness (
advanced.py; new MCP toolcheck_matter). Reports which functions round-trip cleanly to a Matter cluster and which do not. - B6 — energy scaffold (
advanced.py; new MCP toolcheck_energy). Checks metering / energy DPT coverage and scaffolds PV / battery / EVSE structure.
Added — C-tier
- C1 — KNX IoT semantic export (
iot.py, new module; new MCP toolgenerate_knx_iot). Emits a KNX IoT Turtle / RDF skeleton for the project. - C2 — naming suggestions (
advanced.py; new MCP toolsuggest_names). Naming-hygiene proposals for group addresses that drift from the zone + function convention. - C3 — as-built completeness grader (
advanced.py; new MCP toolgrade_completeness). Grades a project from bare functional skeleton to as-built, by presence of professional patterns (central macros, motion tuning, astro/meteo, monitoring, deep metering, scenes, reserves, debug).
Added — A-tier quick wins
- A5 — Areas / voice note (
generate_ha.py). Documents that HA Areas and voice assignment are UI-only concerns, not derivable from the.knxproj. - A6 — time/date expose (
generate_ha.py). Emits a KNXexposeblock for DPT-19.001 clock broadcast so HA can serve time/date to the bus.
0.5.0 — 2026-07-01
From validator to repairer. Previous releases flagged problems in a .knxproj; this one
starts proposing the fix. The headline is a repair-suggestion engine that turns each finding
into a concrete, reviewable change, alongside two new detectors for gaps that silently break the
Home Assistant layer: relative-only dimmers and actuator-dependent cover behaviour.
Added
- B1 — repair-suggestion engine (
repair.py, new module; new MCP toolsuggest_repairs). For each finding it proposes a concrete fix rather than only naming the problem: infer a DPT for a group address that has none, correct a suspect sub-DPT, synthesise a status/feedback GA in a free address slot, or add an absolute-brightness GA for a relative-only dimmer. Suggestions only — a human reviews them, and accepted new GAs feedgenerate_ets_group_addresses; the server never writes to ETS or the bus. On a real 3646-GA project it produced 145 proposals: 32 set-DPT, 1 change-DPT, and 112 synthesised status GAs. - A2 — relative-only-dimming detector (
analyze.py,relative_only_dimmingfinding). A3.007relative dimmer with no5.001absolute-brightness GA in its zone is flagged, because Home Assistant cannot set a brightness level from relative dimming alone. - A3 — cover invert / travel-time surfacing (
generate_ha.py). The cover review reason is nowverify_cover_invertand carries a note listing the actuator-dependent flags that are not in the.knxproj(invert_position/invert_updown/invert_angle,travelling_time_up/travelling_time_down), plus a warning when a cover's position lacks a state address.
0.4.0 — 2026-07-01
Two new lint dimensions: does the DPT sub-type match what the name promises, and is the project's KNX Secure posture consistent. This release adds a conservative sub-DPT sanity linter and a report-only KNX Data Secure posture summary (no key material ever touched), plus the itemised QA findings in the handover pack.
Added
- A1 — sub-DPT sanity linter (
analyze.py, surfaced bycheck_dptandanalyze_allas thesubdpt_suspectfinding). When a group-address name implies a specific DPT sub-type (temperature →9.001, power →14.056, brightness/position →5.001, and similar), the linter flags a wrong sub-type or a wrong main type. Multilingual keyword matching, deliberately conservative — it only fires when the name is unambiguous, so it does not second-guess correct or generic DPTs. - A4 — KNX Data Secure posture (
analyze.pysecure_posture(), new MCP toolcheck_secure). A report-only summary of the project's security posture: secured vs plaintext GA counts, middle groups that mix secure and plaintext objects, and a keyring (.knxkeys) handover checklist. It reads only the per-GASecurityflag — no key material is read, derived, or emitted. - KNX Secure posture section in the handover pack — section 5 of
handover.mdis rewritten from a flat secure-GA count into the full posture section (counts, mixed-group flag, keyring handover checklist), so the as-built deliverable states the security posture explicitly. - Itemised QA findings in the handover pack — section 6 of
handover.mdnow lists the actual 🔴 errors and 🟡 warnings (address + name, grouped by check), not just totals, so the handover doubles as a review checklist. Info-level items (intentional reserves / logic / macros) stay collapsed.
0.3.0 — 2026-07-01
Spec→structure: a device library and a design methodology, plus the as-built handover pack.
This release turns the tool from a .knxproj validator into a design aid: from a project
spec you can now expand each device into its group-address recipe and reason about the whole
structure. Shipped alongside the Track B project handover pack and a set of noise-reduction
refinements, the latter two driven by a second real signed Zennio project (a 3646-GA
multi-vendor villa, 5× larger, no ETS Functions).
Added
- Device library +
decompose_device— one device is not one GA (device_library.py, new MCP toolsdecompose_device+list_device_recipes). Each actuator channel expands into a set of communication objects — command, status, dimming (3.007), absolute value/position (5.001), HVAC mode (20.102/20.105), colour (232.600/251.600) — each with its own DPT. Given a manufacturer order number, device type or alias (e.g.ZDIDBDX4,dimmer,JRA/S,presence detector) and a channel count, the tool returns the objects a professional typically wires per channel and the total GA count, so a spec/ТЗ device list can be turned into a group-address structure. Recipes cover switch, dimmer, RGBW LED, shutter/blind, floor-heating, AC gateway, DALI, presence, metering, leak and touch-panel families across Zennio + ABB. Recipes are generic vendor facts compiled from KNX manufacturer ETS product databases and public documentation — the typical-wired subset, not the full selectable master menu. New regression tests cover the dimmer/shutter/panel/unknown paths. docs/spec-to-structure.md— the spec→structure methodology. Documents the reverse of validation (design a group-address structure from a spec), thedecompose_devicepipeline, and an honest, measured account of what is reproducible from a spec (~90 %: taxonomy, domains, logic structure, command/status pairing, DPT discipline) versus what is not (the exact per-device object count — an integrator parameterisation choice that varies 2–9× between projects; predict a range, never a false-precise number).generate_handover_pack— the as-built commissioning deliverable (Track B). From a read-only.knxprojit assembleshandover.md(equipment inventory by manufacturer, group-address map by domain, command/status feedback coverage %, KNX Secure scope, QA state), a standalonetopology.svgarea/line/device diagram, the fullgroup-addresses.csvand theha-package.yaml. On the villa: 275 devices across 8 manufacturers, 14 domains, 93 % feedback coverage — one command produces the whole handover bundle.- Divider/separator scratch detection — commissioning placeholder names made only of
punctuation (
---,=====) or a marker wrapped in it (-----addition------) now classify asscratchintent, so a missing DPT on them is an INFO note, not a 🔴 error. - Logic-function object awareness — a typed GA (e.g. 9.x temperature) wired into a Zennio
[LF] … Data Entrycontainer (intentionally type-agnostic, raw 2-byte) surfaces as INFOdpt_on_logic_objectinstead of a falsedpt_mismatch_cowarning. - Central-macro status tolerance — all-groups broadcast commands (
Общее …,Все группы,Все шторы - Стоп) surface as INFOcentral_macro_no_statusinstead of amissing_status_addresswarning, since a fan-out broadcast has no single state to read back.
Fixed
- Handover domain names — the group-address map now reads main/middle range names straight
from the project's
GroupRangesinstead ofGARecord.main_name, which the parser could mislabel (a middle range's name leaking into the main). Domains now render correctly (e.g.[1] Освещение 1 этаж,[5] Климат) instead of a middle-group name.
0.2.0 — 2026-06-30
Colour/climate entity assembly and GA-intent noise reduction. Two feature tracks
shipped together, both driven by real ETS projects: full colour-light (RGBW/RGB/CCT) and
KNX climate entity generation with a status-pairing (B1) correctness fix, and a GA-intent
classifier that stops intentional non-functional addresses from raising false errors. On a
real 685-GA Zennio project the intent work cut false errors 29 → 6 and missing-status
noise 79 → 45 while preserving all 12 genuine dpt_mismatch_co catches.
Added
- Colour lights, climate entities and a status-pairing fix (Track A), driven by the real
signed demo and a 685-GA Zennio project.
- Colour control is assembled into the
lightentity: RGB (DPT 232.600 →color_address), RGBW (251.600 →rgbw_address), xyY (242.600 →xyy_address) and absolute colour-temperature (7.600 →color_temperature_address+color_temperature_mode), each with its status, matched to the zone's light by identity. A colour GA with no on/off in its zone is routed to review instead of dropped. - Climate entities are now generated: anchored on an HVAC mode (DPT 20.102/20.105), the
zone's current temperature (9.001), target-temperature status, operation/controller modes
and valve
command_value_stateare gathered by location and emitted only when the HA- required minimum (temperature_address+target_temperature_state_address) is present — otherwise the zone goes to review, so an invalid climate entity is never written. Keys verified against the Home Assistant KNX docs. (Real files: demo 6, Zennio 7 climate zones.) - B1 fix — no borrowed status. A command now only takes a status whose identity nests with its own (a shared zone token like "kitchen" is not enough), and a status maps to exactly one entity. This stopped "worktop LED" inheriting "island pendants" brightness and removed all shared-status addresses on the real files.
- New DPTs: 232.600 / 251.600 / 242.600 / 7.600 (colour), 20.105 (controller mode), 1.100 (heat/cool). New regression tests cover colour assembly, the B1 borrow guard and climate (valid zone emitted, mode-only zone reviewed).
- Colour control is assembled into the
- GA-intent classification — noise reduction on real projects (
intent.py). Every group address is now classified asfunctional/reserve/logic/scratch. Intentional non-functional GAs no longer "cry wolf": reserve spares with no DPT become an INFO note (reserve_without_dpt) instead of a 🔴 error; reserve names repeated across different DPTs are not flaggedduplicate_name/inconsistent_dpt; internal logic / virtual signals and scratch leftovers are excluded from missing-status warnings. Thedpt_mismatch_cocheck and every real functional finding are untouched. Driven by a real 685-GA Zennio project where this cut false errors 29 → 6 and missing-status noise 79 → 45 while preserving all 12 realdpt_mismatch_cocatches.list_group_addresses, the report inventory and theanalyze_allsummary now expose the intent breakdown. New regression test covers the reserve / logic / scratch patterns (and proves real DPT + status problems still surface). docs/— a self-contained GitHub Pages landing site (project overview, two-layer architecture, demo-house stats, an interactive 5-tab dashboard preview, the HA "brain", and a call for testers).examples/demo-home/— a complete synthetic worked example: a 239-GA / 47-Function demo.knxproj, the tool's generated report + Home Assistant entities + ETS export, and aha-brain/Home Assistant smart layer (circadian lighting, multi-factor climate, presence/ season/time logic, statistics). Includes 5 deliberate mistakes to show the checks (and an honest note on the one the missing-status check doesn't yet catch).
0.1.2 — 2026-06-28
Home Assistant mapping quality — a second hardening pass driven by running the tool
against more public ETS 4.2 / 5.0 / 5.5 / 6 projects (yene/knxproj DemoCase,
tuxedo0801/KnxProjParser, dataheld/knxray, whaeuser/open-knxviewer). Three new
regression tests; zero crashes across the whole corpus.
Added
- Dimmable lights are assembled completely. A light now collects BOTH its on/off
status (1.x) and brightness status (5.x) via identity-based pairing, folds the on/off
command into the same
lightentity (no more duplicateswitch), and pairs correctly even when the device identity is a single name token (e.g.HaloSpotLeft.A.VALUE↔HaloSpotLeft.A.STATE%). Switches gained the same identity-based status pairing. - Wider shutter detection. A cover's up/down is recognised on canonical DPT 1.008 or
any 1.x command named up/down (
UP/DOWN,auf/ab); stop is recognised on DPT 1.007 / 1.010 / 1.017 or a "stop"/"stopp"/"стоп" name. Position and stop siblings attach only to the same shutter (zone-identity guard), so multiple blinds in one main group no longer cross-wire. Real ETS4/5 projects that use 1.001+1.017 now map to full covers. - Date / time / text DPTs recognised (10.001 time, 11.001 date, 19.001 datetime,
16.000/16.001 string): routed to
reviewasmanual_datetime/manual_text(HA KNX has dedicated date/time/text platforms) instead of an opaqueunmapped_dpt.
0.1.1 — 2026-06-28
Hardening release. Every change below was surfaced by running the tool end-to-end
against real ETS5/ETS6 project files (the XKNX/xknxproject test fixtures), not the
synthetic smoke test — which alone never exercised the real parser or the MCP server path.
Five new regression tests now guard these cases.
Fixed
- Critical: the
load_projectMCP tool recursed infinitely (RecursionError) on every real.knxproj. The server tool functionload_projectshadowed the imported project loader of the same name, so it called itself instead of parsing. It now delegates toload_project_file. This made the tool's primary entry point unusable over MCP; the synthetic smoke test missed it because it calls the parser directly, bypassing the server.
Added
- ETS Function role pairing — the headline feature — is now actually implemented.
command↔status pairs are taken from ETS Function roles (e.g.
SwitchOnOff↔InfoOnOff) viapairing.function_status_pairs(), consumed by bothcheck_missing_statusand the HA generator. Previously Functions were ignored entirely despite the README claiming they were the primary signal. Consequences fixed: a feedback GA named only "Status" now pairs correctly (name tokens no longer required), HA entities get the rightstate_address, and function-paired commands are no longer false-flagged as missing a status. - No silent drops in HA generation ("no silent caps"). Every group address is now either
emitted as an entity or listed in the
reviewoutput; the YAML header reports how many need manual review. Previously, GAs the generator could not classify simply vanished. - Smarter shutter classification. German/directional names are recognised (
Behang,Lamelle,auf/ab,Raffstore,Markise, plus RU equivalents). A shutter-looking command whose DPT lacks a sub-type now gets an actionableshutter_incomplete_dptreview hint (set 1.008 / 1.010 / 5.001 in ETS) instead of being dropped as "unknown". - Venetian slats handled as tilt. A slat GA (Lamelle / slat / ламель / tilt) is attached
to its parent blind cover as
move_short_addressinstead of becoming a standalone cover; an unmatched slat is flaggedshutter_slat_unattachedfor manual attachment. - Diagnostics alarms are inputs. A 1-bit diagnostics GA (wind/frost/rain/smoke/leak
alarm, fault) is now a read-only
binary_sensorinstead of a phantom command switch.
0.1.0 — 2026-06-28
Initial public beta.
Added
- Design-time MCP server with 12 tools:
load_project,list_group_addresses,get_devices,get_topology,check_naming,check_missing_status,check_dpt,analyze_all,generate_ha_package,generate_ets_group_addresses,project_report,workspace_info. - Read-only
.knxprojparsing viaxknxproject(ETS5/ETS6, password-protected supported). - GA classification (category + kind) from DPT and multilingual (EN/DE/RU) name keywords.
- Naming, missing-status, and DPT validation checks.
- Home Assistant KNX YAML generation with a conservative
reviewlist for ambiguous items. - ETS-importable group-address export in XML (
knx.org/xml/ga-export/01) and CSV. - Markdown project report.
- Confined-workspace write guarantee (
NICKOL_KNX_WORKSPACE); no bus access by design. CLAUDE.mddesign playbook, end-to-end smoke test (synthetic 16-GA project), example Claude Desktop config.
Known limitations
- Tested end-to-end on a synthetic project only; real-world
.knxprojtesting is ongoing (see the call for testers in the README).