Improve CHIP SDK compatibility in Python cluster generator#457
Improve CHIP SDK compatibility in Python cluster generator#457Apollon77 merged 59 commits intogenerated-python-clientfrom
Conversation
Systematically align generated output with CHIP SDK's chip.clusters.Objects: - Remove eventList global attribute (not in CHIP SDK) - Use uint for bitmap-typed fields (CHIP SDK convention) - Fix list element types to not wrap in Optional/Nullable - Fix kUnknownEnumValue to use first unused value from 0 - Use model.members for command/event fields (includes inherited) - Deduplicate and sort attributes by tag ID - Fix multi-bit bitmap masks for range constraints - Add CLASS_NAME_OVERRIDES + toChipClassName() for naming edge cases - Add K_VALUE_OVERRIDES for bitmap member name mismatches - Remove incorrect GroupId field-name override Also adds comparison utility scripts (compare_clusters.py, compare_summary.py) for verifying CHIP SDK compatibility.
Adds compare_clusters.py and compare_summary.py for verifying CHIP SDK compatibility
Output of running: npx tsx python_client/scripts/generate-python-clusters.ts
The generated files use `from __future__ import annotations` which already makes all annotations strings, so explicit quoting is redundant. Matches CHIP SDK style: `value: uint = 0` instead of `value: 'uint' = 0`.
Output of running: npx tsx python_client/scripts/generate-python-clusters.ts
eventList is part of the Matter spec and newer CHIP SDK versions include it. kUnknownEnumValue as first-unused-after-max avoids assigning semantically meaningful low values like 0.
Output of running: npx tsx python_client/scripts/generate-python-clusters.ts
Using the actual bitmap type (e.g., OptionsBitmap) is more type-safe than plain uint, even though the older CHIP SDK uses uint.
Output of running: npx tsx python_client/scripts/generate-python-clusters.ts
The CHIP SDK omits the "Enum" suffix for some enum class names (e.g., WindowCovering.Enums.Type not TypeEnum). Add CLASS_NAME_OVERRIDES for Type, EndProductType, ModeTag, StatusCode, SelectAreasStatus, and SkipAreaStatus. Fixes HA crash: AttributeError: type object 'Enums' has no attribute 'Type'
Output of running: npx tsx python_client/scripts/generate-python-clusters.ts
Make toChipClassName() accept optional cluster context to support cluster-qualified overrides (e.g., "AdministratorCommissioning.StatusCodeEnum" → "StatusCode" while ValveConfigurationAndControl keeps "StatusCodeEnum"). Also strip Bitmap suffix for WindowCovering bitmaps (Mode, ConfigStatus, OperationalStatus, SafetyStatus) to match CHIP SDK. Improve compare_summary.py to detect Enum/Bitmap suffix mismatches.
Output of running: npx tsx python_client/scripts/generate-python-clusters.ts
Matter.js uses TiltBlindLift but the CHIP SDK (and HA) expects kTiltBlindLiftAndTilt for WindowCovering.Enums.Type value 8.
Output of running: npx tsx python_client/scripts/generate-python-clusters.ts
The CHIP SDK uses legacy "Dl" prefixed names for three DoorLock enums that differ from the Matter.js model names (LockStateEnum, LockTypeEnum, StatusCodeEnum). HA references these legacy names.
Output of running: npx tsx python_client/scripts/generate-python-clusters.ts
The Matter.js model only has 2 members (Duplicate, Occupied) while the CHIP SDK's DlStatus has 7 (including kSuccess, kFailure, etc.). Renaming would expose an incomplete enum that crashes HA.
Output of running: npx tsx python_client/scripts/generate-python-clusters.ts
The Matter.js model only defines 2 members for DoorLock.StatusCodeEnum (Duplicate, Occupied) but the CHIP SDK's DlStatus has 7 including kSuccess, kFailure, kInvalidField, kResourceExhausted, and kNotFound. Add EXTRA_ENUM_MEMBERS mechanism to inject additional enum members that are present in the CHIP SDK but missing from the Matter.js model.
Output of running: npx tsx python_client/scripts/generate-python-clusters.ts
Custom cluster uses "Ac" not "AC" in attribute names (e.g., AcPowerDivisor not ACPowerDivisor). Add CLASS_NAME_OVERRIDES and FIELD_NAME_OVERRIDES.
Output of running: npx tsx python_client/scripts/generate-python-clusters.ts
Custom cluster uses "Rms" not "RMS" (e.g., RmsVoltage not RMSVoltage).
Output of running: npx tsx python_client/scripts/generate-python-clusters.ts
The DraftElectricalMeasurementCluster custom cluster uses "Rms"/"rms" while standard clusters like ElectricalPowerMeasurement use "RMS". Use cluster-qualified keys in both FIELD_NAME_OVERRIDES and CLASS_NAME_OVERRIDES, and add clusterName parameter to toCamelCase().
Output of running: npx tsx python_client/scripts/generate-python-clusters.ts
|
HA uses @dataclass
class DoorLock(Cluster):
# [...]
class Enums:
# [...]
class DlStatus(MatterIntEnum):
kSuccess = 0x00
kFailure = 0x01
kDuplicate = 0x02
kOccupied = 0x03
kInvalidField = 0x85
kResourceExhausted = 0x89
kNotFound = 0x8B
# All received enum values that are not listed above will be mapped
# to kUnknownEnumValue. This is a helper enum value that should only
# be used by code to process how it handles receiving an unknown
# enum value. This specific value should never be transmitted.
kUnknownEnumValue = 4We may need to use the generic status codes in HA(?) matterjs-server/packages/dashboard/src/util/matter-status.ts Lines 11 to 47 in 018f8a5 I'll add them as hardcoded values to the generator for now, just so I can further test what else breaks HA. |
EDIT: The |
The original hand-maintained device_types.py included HeatingCoolingUnit (0x0300) which HA references, but it's not in the Matter.js model. Add it as an extra device type in the generator.
Replace wildcard import with explicit per-cluster imports in Objects.py. This eliminates all indirection — Objects.py is the single source of truth for all exports (clusters, base classes, primitives). The _cluster_defs/__init__.py is now empty (only needed for relative imports in per-cluster files). No wildcard imports means better static analysis.
This reverts commit aaf807e.
Drop the underscore prefix — the package is imported directly by custom_clusters.py and tests, so it's not truly private.
|
Other differences to look at later: Critical (would cause wrong values or crashes in HA):
Field name mismatches (could cause attribute lookup failures):
Structural issues:
|
The generator mapped all integer types to uint, but semantic type aliases like temperature (int16), power-mW (int64), energy-mWh (int64), etc. are signed. Add these aliases to resolvePrimitiveByName and delegate the metatype "integer" case to it so both code paths resolve correctly. Fixes Thermostat setpoints, TemperatureMeasurement, DeviceEnergyManagement power values, and all other signed integer attributes.
Output of running: npx tsx python_client/scripts/generate-python-clusters.ts
Override keys must use the raw Matter.js model name, not the post-toChipName version. Fixes: - RequirePinForRemoteOperation (was RequirePINForRemoteOperation) - IPv4Addresses (was Ipv4Addresses) - OffPremiseServicesReachableIPv4/IPv6 (was ...IPV4/IPV6) - ExtendedPanIdPresent/PanIdPresent (was ...PanIDPresent)
Output of running: npx tsx python_client/scripts/generate-python-clusters.ts
- Add SignedTemperature and TemperatureDifference to signed int aliases (fixes localTemperatureCalibration, minSetpointDeadBand, setpointChangeAmount) - Add FIELD_NAME_OVERRIDE Lqi → lqi (CHIP SDK uses lowercase) - Add K_VALUE_OVERRIDE LedFeedback → LedFeedback (prevent LED expansion)
Output of running: npx tsx python_client/scripts/generate-python-clusters.ts
Build SIGNED_INT_TYPES set by walking the Matter.js datatype hierarchy (global + cluster-level) instead of hardcoding type aliases. Any type whose base resolves to int8/int16/.../int64 is automatically recognized.
d57f971 to
546f895
Compare
- Pass clusterName to toCamelCase in generateStruct, generateCommand, generateEvent (fixes cluster-qualified FIELD_NAME_OVERRIDES not matching) - Pass clusterName to toChipClassName in generateStruct, generateCommand, generateEvent, generateBitmap (fixes cluster-qualified CLASS_NAME_OVERRIDES) - Pass "Globals" to generateEnum in generateGlobalsFile for consistency - Remove identical if/else branches in resolvePythonType and attribute field generation
The compare_clusters.py and compare_summary.py scripts were developer tools used during this work. Removed from git but kept locally.
mypy cannot resolve names re-exported via wildcard imports. Add a complete __all__ listing all cluster names plus base classes and primitive types so mypy sees them as explicit exports. Fixes 11 mypy errors (name-defined, attr-defined) in node.py, test_imports.py, and test_integration.py.
deviceTypeList is typed as List[DeviceTypeStruct] (not Optional), so elements are never None. mypy correctly flags the check as unreachable.
No longer needed — removed the hardcoded HeatingCoolingUnit (0x0300) device type and the extra device types injection mechanism.
The generated GeneralDiagnostics cluster uses IPv4Addresses/IPv6Addresses (matching CHIP SDK), not iPv4Addresses/iPv6Addresses.
The generated NetworkCommissioning cluster uses LastNetworkID (uppercase ID from acronym expansion), not LastNetworkId.
- client.py: simplify `f.label if f.label else None` → `f.label or None` - node.py: add maxsplit=1 to split() when only first element is used
21495b7 to
7ad0ed6
Compare
…ses (#303) * Replace home-assistant-chip-clusters with generated code from Matter.js model Generate Python cluster definitions directly from MatterModel.standard, eliminating the dependency on home-assistant-chip-clusters and aenum. Changes: - Add python_client/chip/ package with hand-written infrastructure: - ChipUtility.py: classproperty descriptor - tlv/: uint, float32, TLVWriter, TLVReader - clusters/ClusterObjects.py: base classes + ALL_* registration dicts - clusters/Types.py: Nullable, NullValue - clusters/enum.py: MatterIntEnum (stdlib IntEnum, no aenum) - Add generator script python_client/scripts/generate-python-clusters.ts - Reads MatterModel.standard and generates 121 cluster files - One file per cluster in chip/clusters/objects/ - Objects.py re-exports all clusters for API compatibility - Also generates device_types.py from Matter.deviceTypes - Remove home-assistant-chip-clusters and aenum from pyproject.toml - Add dacite as explicit dependency (was transitive via chip-clusters) - Include chip* in setuptools package discovery - Add python:generate npm script All 12 Python unit tests pass. All 228 JS tests pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix generator compatibility: derived clusters and acronym casing - Resolve cluster inheritance: derived clusters (ModeBase, OperationalState, ConcentrationMeasurement, ResourceMonitoring, AlarmBase, Label) now inherit all base cluster members (datatypes, commands, attributes, events) - Handle command direction override: when derived cluster overrides a base command without specifying direction, use the base command's metadata - Preserve chip-clusters acronym casing: OTA, DST, UTC, NTP, CO, EV, CEC, URL, PIN, ACL, ICAC, CSR, NOC, TC, VID, LED, RFID are kept uppercase (e.g., AddNOC not AddNoc, SetDSTOffset not SetDstOffset) - Register inherited datatypes in the datatype registry for cross-references Result: 0 differences across all 121 common clusters when compared with the original home-assistant-chip-clusters package. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix missing Globals import in generated cluster files Clusters referencing global types (e.g., Globals.Enums.enum8) failed at runtime with NameError because the generator excluded 'Globals' from cross-cluster imports. Remove the Globals exclusion so it is imported like any other cross-cluster reference. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Mark generated Python files in .gitattributes Mark auto-generated cluster files, Objects.py, and device_types.py as linguist-generated=true to collapse them in GitHub PR diffs for easier review. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix generator trailing newlines in Python files Each generated Python file now ends with exactly one newline (PEP 8). Previously, loops added blank lines after every element including the last one, resulting in two trailing newlines. Changed all element loops to only add blank lines between elements, not after the last one. Affected: cluster files (Enums, Bitmaps, Structs, Commands, Attributes, Events sections) and device_types.py. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Add TypeScript config for Python scripts directory Created tsconfig.json for python_client/scripts/ with proper module resolution (node16) and type definitions. This enables IDE support and type checking for the code generator script. Also cleaned up unused imports and parameters in the generator to pass strict TypeScript checks (noUnusedLocals, noUnusedParameters). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Remove unnecessary type assertion in generator Removed '!' assertion on dt.id since TypeScript already knows it's defined after filtering for !== undefined. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix acronym casing in field names - add ID to acronym list Added 'ID' to the acronym list to ensure field names like vendorId, fabricId, nodeId get properly transformed to vendorID, fabricID, nodeID (matching chip-clusters behavior). Also updated toCamelCase() to apply acronym preservation before lowercasing the first character, ensuring camelCase field names like vendorID (not vendorId) match the original chip-clusters API. Reordered ACRONYMS list to process longer acronyms first to avoid over-matching (e.g., VID before ID). All 75 Python tests now pass including full integration tests against real Matter.js server. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Add Python linting and type checking tools - Add ruff, mypy, pylint configuration matching python-matter-server standards - Configure comprehensive ruff rule set with appropriate ignores for generated code - Add mypy strict type checking with overrides for generated files and tests - Fix field name casing bugs: iPv4Addresses/iPv6Addresses, LastNetworkId - Fix exception handling in connection.py to avoid type pollution - Add npm scripts for python:lint, python:lint-fix, python:typecheck - Update generator to add __all__ export list for type checkers - Update generator to fix docstring formatting (PEP 257) - Update generator to add type hint for __init_subclass__ - Generated code properly excluded from all linting/type checks - All 75 Python tests passing - Ruff: 0 errors (218 auto-fixes applied) - Mypy: 5 errors (all in tests, matching python-matter-server pattern) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Add Python linting and type checking to CI - Add new 'lint' job that runs before unit and integration tests - Run ruff linter on python_client (excluding generated chip/ directory) - Run mypy type checker on python_client - Both unit-tests and integration-tests now depend on lint job passing Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix mypy CI configuration path issue Run ruff and mypy from within python_client directory so they pick up pyproject.toml configuration correctly. This ensures generated code in chip/ is properly excluded. Fixes: Found 3701 errors → 5 errors (only in tests) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix remaining mypy errors - Add None check for process.stdout in test helper - Add None check for dev_info before accessing deviceType - Add assertion for client.server_info before accessing schema_version - Add return type annotation for _onoff_command helper - Add TYPE_CHECKING import for Clusters type hint - Add type ignore for aiohttp ClientWSTimeout deprecation Result: All mypy checks passing (0 errors in 157 files) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Address PR #303 code quality review comments 1. Add missing comparison methods to Nullable class (__le__, __gt__, __ge__) to complete ordering protocol. Maintains consistent behavior where Nullable compares as less-than-or-equal to everything. 2. Remove unnecessary pass statements from generated command classes with no fields. The descriptor method is already present, so pass is redundant. All tests passing. Addresses github-code-quality bot feedback. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Review feedback and updates * Improve Python cluster drop-in compatibility with chip-clusters package - Revert classproperty caching (PR reviewer request) - Add uint, NullValue, Nullable, float32 re-exports to chip.clusters.Objects - Expand ACRONYMS list in generator (~30 new entries, correct ordering) - Fix toKName() to apply acronym expansion for compound k-values (kProgrammingPIN etc.) - Add K_VALUE_OVERRIDES map for k-values chip-clusters keeps in TitleCase (kPin, kRfid, etc.) - Add EventList (0xFFFA) as a generated global attribute on all clusters - Remove spurious pass statements from generateStruct and generateEvent - Strip Enum suffix from enum class names only in registry lookup (not class names) - Add meaningful type assertions in test_imports.py for re-exported primitives - Reduce naming mismatches vs home-assistant-chip-clusters from 581 to ~349 (remaining are Matter spec version differences between 1.4 and 1.2/1.3) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: correct misleading comments in generate-python-clusters.ts - ACRONYMS comment now accurately states that ordering IS required (SNTP before NTP, UTC before TC, PIN before PI) because shorter forms are suffixes of longer ones - Add omission comment for ACL alongside ARL explanation - stripEnumSuffix comment now clarifies it is for registry aliases only; old chip-clusters (≤2024.11.4) KEEPS the Enum suffix in class names Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: kRollerShade casing in WindowCovering.TypeEnum matter.js models these as one-word "rollershade*" but chip SDK treats it as two words "RollerShade*". Add 4 K_VALUE_OVERRIDES entries to produce the correct kRollerShade, kRollerShade2Motor, kRollerShadeExterior, kRollerShadeExterior2Motor. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: correct field-name casing to match chip-clusters 2024.11.4 - toCamelCase now preserves leading acronyms (ESAType, ACCapacity, UTCTime, etc.) - Add FIELD_NAME_OVERRIDES for chip SDK inconsistencies (panId vs panID, etc.) - Add PAKE, IPK, LQI, URI to ACRONYMS list - BX/BY/GX/GY/RX/RY omitted from ACRONYMS (too broad — "ByNumber" regression) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: add ColorPoint coordinate FIELD_NAME_OVERRIDES (colorPointBX etc.) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: clarify FIELD_NAME_OVERRIDES comments and verify ACCapacityformat casing Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: emit must_use_timed_invoke for timed commands Commands with access.timed=true in the Matter.js model now emit the @ChipUtility.classproperty must_use_timed_invoke → True property, matching the old chip-clusters SDK convention. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: use model.members in generateStruct to include inherited fields ModeOptionStruct in mode clusters (DishwasherMode, OvenMode, RvcCleanMode, etc.) defines zero direct children but inherits label/mode/modeTags from the global base struct. Switching from model.children to model.members picks up these inherited fields and generates correct Python dataclasses. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test: add _require_state guard to skip dependent integration tests When an early integration test fails (e.g. commissioning), subsequent tests that depend on node_id/test_node_id would fail with confusing errors instead of cleanly skipping. This helper surfaces the real failure without masking it. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: avoid mypy attr-defined error in test_mode_option_struct_has_fields The loop over (DishwasherMode, OvenMode) caused mypy to infer cls as type[Cluster], which has no Structs attribute. Replace with direct calls to a helper function, keeping type information concrete. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: type _assert_mode_option_struct_fields param as Any for mypy struct: type caused attr-defined on __dataclass_fields__ since mypy doesn't know plain type objects carry dataclass metadata. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Improve CHIP SDK compatibility in Python cluster generator (#457) * fix: improve CHIP SDK compatibility in Python cluster generator Systematically align generated output with CHIP SDK's chip.clusters.Objects: - Remove eventList global attribute (not in CHIP SDK) - Use uint for bitmap-typed fields (CHIP SDK convention) - Fix list element types to not wrap in Optional/Nullable - Fix kUnknownEnumValue to use first unused value from 0 - Use model.members for command/event fields (includes inherited) - Deduplicate and sort attributes by tag ID - Fix multi-bit bitmap masks for range constraints - Add CLASS_NAME_OVERRIDES + toChipClassName() for naming edge cases - Add K_VALUE_OVERRIDES for bitmap member name mismatches - Remove incorrect GroupId field-name override Also adds comparison utility scripts (compare_clusters.py, compare_summary.py) for verifying CHIP SDK compatibility. * feat: Add comparison utility scripts Adds compare_clusters.py and compare_summary.py for verifying CHIP SDK compatibility * chore: regenerate Python cluster definitions Output of running: npx tsx python_client/scripts/generate-python-clusters.ts * fix: remove redundant quotes from type annotations The generated files use `from __future__ import annotations` which already makes all annotations strings, so explicit quoting is redundant. Matches CHIP SDK style: `value: uint = 0` instead of `value: 'uint' = 0`. * chore: regenerate Python cluster definitions Output of running: npx tsx python_client/scripts/generate-python-clusters.ts * revert: keep eventList attribute and kUnknownEnumValue after-max logic eventList is part of the Matter spec and newer CHIP SDK versions include it. kUnknownEnumValue as first-unused-after-max avoids assigning semantically meaningful low values like 0. * chore: regenerate Python cluster definitions Output of running: npx tsx python_client/scripts/generate-python-clusters.ts * revert: keep bitmap types instead of replacing with uint Using the actual bitmap type (e.g., OptionsBitmap) is more type-safe than plain uint, even though the older CHIP SDK uses uint. * chore: regenerate Python cluster definitions Output of running: npx tsx python_client/scripts/generate-python-clusters.ts * fix: strip Enum suffix from class names where CHIP SDK omits it The CHIP SDK omits the "Enum" suffix for some enum class names (e.g., WindowCovering.Enums.Type not TypeEnum). Add CLASS_NAME_OVERRIDES for Type, EndProductType, ModeTag, StatusCode, SelectAreasStatus, and SkipAreaStatus. Fixes HA crash: AttributeError: type object 'Enums' has no attribute 'Type' * chore: regenerate Python cluster definitions Output of running: npx tsx python_client/scripts/generate-python-clusters.ts * fix: cluster-aware class name overrides for Enum/Bitmap suffix stripping Make toChipClassName() accept optional cluster context to support cluster-qualified overrides (e.g., "AdministratorCommissioning.StatusCodeEnum" → "StatusCode" while ValveConfigurationAndControl keeps "StatusCodeEnum"). Also strip Bitmap suffix for WindowCovering bitmaps (Mode, ConfigStatus, OperationalStatus, SafetyStatus) to match CHIP SDK. Improve compare_summary.py to detect Enum/Bitmap suffix mismatches. * chore: regenerate Python cluster definitions Output of running: npx tsx python_client/scripts/generate-python-clusters.ts * fix: rename TiltBlindLift to TiltBlindLiftAndTilt for CHIP SDK compat Matter.js uses TiltBlindLift but the CHIP SDK (and HA) expects kTiltBlindLiftAndTilt for WindowCovering.Enums.Type value 8. * chore: regenerate Python cluster definitions Output of running: npx tsx python_client/scripts/generate-python-clusters.ts * fix: use legacy DoorLock enum names (DlLockState, DlLockType, DlStatus) The CHIP SDK uses legacy "Dl" prefixed names for three DoorLock enums that differ from the Matter.js model names (LockStateEnum, LockTypeEnum, StatusCodeEnum). HA references these legacy names. * chore: regenerate Python cluster definitions Output of running: npx tsx python_client/scripts/generate-python-clusters.ts * fix: don't rename DoorLock.StatusCodeEnum to DlStatus The Matter.js model only has 2 members (Duplicate, Occupied) while the CHIP SDK's DlStatus has 7 (including kSuccess, kFailure, etc.). Renaming would expose an incomplete enum that crashes HA. * chore: regenerate Python cluster definitions Output of running: npx tsx python_client/scripts/generate-python-clusters.ts * fix: add missing DlStatus enum members via EXTRA_ENUM_MEMBERS The Matter.js model only defines 2 members for DoorLock.StatusCodeEnum (Duplicate, Occupied) but the CHIP SDK's DlStatus has 7 including kSuccess, kFailure, kInvalidField, kResourceExhausted, and kNotFound. Add EXTRA_ENUM_MEMBERS mechanism to inject additional enum members that are present in the CHIP SDK but missing from the Matter.js model. * chore: regenerate Python cluster definitions Output of running: npx tsx python_client/scripts/generate-python-clusters.ts * fix: prevent AC acronym expansion for DraftElectricalMeasurementCluster Custom cluster uses "Ac" not "AC" in attribute names (e.g., AcPowerDivisor not ACPowerDivisor). Add CLASS_NAME_OVERRIDES and FIELD_NAME_OVERRIDES. * chore: regenerate Python cluster definitions Output of running: npx tsx python_client/scripts/generate-python-clusters.ts * fix: prevent RMS acronym expansion for DraftElectricalMeasurementCluster Custom cluster uses "Rms" not "RMS" (e.g., RmsVoltage not RMSVoltage). * chore: regenerate Python cluster definitions Output of running: npx tsx python_client/scripts/generate-python-clusters.ts * fix: make Rms field/class overrides cluster-specific The DraftElectricalMeasurementCluster custom cluster uses "Rms"/"rms" while standard clusters like ElectricalPowerMeasurement use "RMS". Use cluster-qualified keys in both FIELD_NAME_OVERRIDES and CLASS_NAME_OVERRIDES, and add clusterName parameter to toCamelCase(). * chore: regenerate Python cluster definitions Output of running: npx tsx python_client/scripts/generate-python-clusters.ts * fix: add HeatingCoolingUnit device type missing from Matter.js model The original hand-maintained device_types.py included HeatingCoolingUnit (0x0300) which HA references, but it's not in the Matter.js model. Add it as an extra device type in the generator. * fix: rename kUnbolting to kUnbolt for DoorLock Feature bitmap Matter.js uses "Unbolting" but the CHIP SDK (and HA) expects "kUnbolt". * chore: regenerate Python cluster definitions Output of running: npx tsx python_client/scripts/generate-python-clusters.ts * fix: use base cluster attribute type when local override is an empty stub Derived clusters (e.g., DishwasherMode from ModeBase) can have local attribute stubs with no type or children. These stubs overrode the base cluster's full definition, causing supportedModes to resolve as List[uint] instead of List[ModeOptionStruct]. Now falls back to the base attribute when the local one has no type. * chore: regenerate Python cluster definitions Output of running: npx tsx python_client/scripts/generate-python-clusters.ts * fix: add ColorCapabilitiesBitmap missing from Matter.js model The CHIP SDK has ColorControl.Bitmaps.ColorCapabilitiesBitmap but it's not in the Matter.js model. Add EXTRA_BITMAPS mechanism to inject bitmap definitions and use it for ColorCapabilitiesBitmap. * chore: regenerate Python cluster definitions Output of running: npx tsx python_client/scripts/generate-python-clusters.ts * fix: use metatype as fallback when attribute type is undefined Attributes with no explicit type (type=undefined) but a valid metatype (e.g., float, boolean, string) were all resolving to uint. Now checks metatype before falling back to uint. Fixes EveCluster pressure/voltage/watt/altitude typed as uint instead of float32, and BridgedDeviceBasicInformation fields typed as uint instead of str/bool. * chore: regenerate Python cluster definitions Output of running: npx tsx python_client/scripts/generate-python-clusters.ts * fix: use explicit assignments in Objects.py for pylint compatibility Pylint cannot resolve names re-exported via 'from ... import' and reports E0611 (no-name-in-module) for ClusterCommand, NullValue, uint, etc. Use explicit assignments (e.g., Cluster = _co.Cluster) so pylint sees them as concrete module-level names. * fix: move base class exports into objects/__init__.py for pylint compat On macOS (case-insensitive FS), chip/clusters/Objects and chip/clusters/objects resolve to the same path, so pylint/astroid reads objects/__init__.py instead of Objects.py. Move base class and primitive type assignments (Cluster, ClusterCommand, NullValue, uint, etc.) into objects/__init__.py so they're visible to pylint on all platforms. Objects.py becomes a thin re-export for case-sensitive filesystems. * fix: rename objects/ to _cluster_defs/ to avoid macOS case collision On macOS (case-insensitive FS), chip/clusters/objects/ and chip/clusters/Objects.py resolve to the same path, causing pylint to read __init__.py instead of Objects.py. Renaming the directory to _cluster_defs/ eliminates the collision entirely. Objects.py is now a thin re-export from chip.clusters._cluster_defs. _cluster_defs/__init__.py contains base classes, primitives, and all cluster imports. HA imports are unaffected (they use chip.clusters.Objects). * refactor: simplify Objects.py and _cluster_defs/__init__.py Now that the objects/ → _cluster_defs/ rename eliminates the macOS case collision, the explicit assignment workaround in __init__.py is unnecessary. Restore simple structure: - _cluster_defs/__init__.py: just cluster imports - Objects.py: re-exports clusters + base classes + primitives * refactor: use explicit imports in Objects.py, empty __init__.py Replace wildcard import with explicit per-cluster imports in Objects.py. This eliminates all indirection — Objects.py is the single source of truth for all exports (clusters, base classes, primitives). The _cluster_defs/__init__.py is now empty (only needed for relative imports in per-cluster files). No wildcard imports means better static analysis. * Revert "refactor: use explicit imports in Objects.py, empty __init__.py" This reverts commit aaf807e. * refactor: rename _cluster_defs to cluster_defs Drop the underscore prefix — the package is imported directly by custom_clusters.py and tests, so it's not truly private. * fix: use signed int for temperature, power, energy, voltage types The generator mapped all integer types to uint, but semantic type aliases like temperature (int16), power-mW (int64), energy-mWh (int64), etc. are signed. Add these aliases to resolvePrimitiveByName and delegate the metatype "integer" case to it so both code paths resolve correctly. Fixes Thermostat setpoints, TemperatureMeasurement, DeviceEnergyManagement power values, and all other signed integer attributes. * chore: regenerate Python cluster definitions Output of running: npx tsx python_client/scripts/generate-python-clusters.ts * fix: correct FIELD_NAME_OVERRIDES keys to match Matter.js model names Override keys must use the raw Matter.js model name, not the post-toChipName version. Fixes: - RequirePinForRemoteOperation (was RequirePINForRemoteOperation) - IPv4Addresses (was Ipv4Addresses) - OffPremiseServicesReachableIPv4/IPv6 (was ...IPV4/IPV6) - ExtendedPanIdPresent/PanIdPresent (was ...PanIDPresent) * chore: regenerate Python cluster definitions Output of running: npx tsx python_client/scripts/generate-python-clusters.ts * fix: Thermostat signed types, lqi field name, kLedFeedback casing - Add SignedTemperature and TemperatureDifference to signed int aliases (fixes localTemperatureCalibration, minSetpointDeadBand, setpointChangeAmount) - Add FIELD_NAME_OVERRIDE Lqi → lqi (CHIP SDK uses lowercase) - Add K_VALUE_OVERRIDE LedFeedback → LedFeedback (prevent LED expansion) * chore: regenerate Python cluster definitions Output of running: npx tsx python_client/scripts/generate-python-clusters.ts * refactor: resolve signed int types from Matter.js model, not hardcoded Build SIGNED_INT_TYPES set by walking the Matter.js datatype hierarchy (global + cluster-level) instead of hardcoding type aliases. Any type whose base resolves to int8/int16/.../int64 is automatically recognized. * fix: pass clusterName to all generators, remove dead code - Pass clusterName to toCamelCase in generateStruct, generateCommand, generateEvent (fixes cluster-qualified FIELD_NAME_OVERRIDES not matching) - Pass clusterName to toChipClassName in generateStruct, generateCommand, generateEvent, generateBitmap (fixes cluster-qualified CLASS_NAME_OVERRIDES) - Pass "Globals" to generateEnum in generateGlobalsFile for consistency - Remove identical if/else branches in resolvePythonType and attribute field generation * chore: remove comparison scripts from tracked files The compare_clusters.py and compare_summary.py scripts were developer tools used during this work. Removed from git but kept locally. * fix: add __all__ to Objects.py for mypy export resolution mypy cannot resolve names re-exported via wildcard imports. Add a complete __all__ listing all cluster names plus base classes and primitive types so mypy sees them as explicit exports. Fixes 11 mypy errors (name-defined, attr-defined) in node.py, test_imports.py, and test_integration.py. * fix: remove unreachable None check for deviceTypeList elements deviceTypeList is typed as List[DeviceTypeStruct] (not Optional), so elements are never None. mypy correctly flags the check as unreachable. * chore: remove HeatingCoolingUnit extra device type No longer needed — removed the hardcoded HeatingCoolingUnit (0x0300) device type and the extra device types injection mechanism. * fix: use correct field names IPv4Addresses/IPv6Addresses in client The generated GeneralDiagnostics cluster uses IPv4Addresses/IPv6Addresses (matching CHIP SDK), not iPv4Addresses/iPv6Addresses. * fix: use correct attribute name LastNetworkID in client The generated NetworkCommissioning cluster uses LastNetworkID (uppercase ID from acronym expansion), not LastNetworkId. * style: inline error messages in process_helpers.py * style: apply ruff auto-fixes (FURB110, PLC0207) - client.py: simplify `f.label if f.label else None` → `f.label or None` - node.py: add maxsplit=1 to split() when only first element is used * fix: generate commands for custom clusters with undefined direction The @command decorator doesn't set direction on custom cluster commands, causing them to be silently dropped by the generator's request/response filter. Treat undefined direction as "request" and use effectiveAccess for timed invoke check. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * chore: add comment for `except NotImplementedError` * fix: add `is_client` property to base class * Update generated cluster_defs gitattributes * Capitalize Heiman MutingSensor command * Normalize generated Python command class names * Restore camelCase Heiman command name * Normalize generated Python event class names --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: TheJulianJES <TheJulianJES@users.noreply.github.com>
Note: Target branch
This is a PR targeting the branch of the original PR:
This PR is to ease review of the differences compared to that PR and can later be merged into the
generated-python-clientbranch when it's ready.TODOs
The comparison scripts included in this PR should be dropped before it's merged!Lint checks are expected to fail because of thiseventListattributeTest with Home Assistant CoreAI summary
Improves the Python cluster generator (
generate-python-clusters.ts) to produce definitions that are much more closely aligned with the CHIP SDK'schip.clusters.Objects. A comparison script was used to systematically identify and fix differences across all 121 generated clusters. Fixes were verified by starting Home Assistant with the Matter integration.Key fixes in the generator
List[Type], notList[Type | None]. List entry types are now resolved viaresolveScalarType()directly, skipping optional/nullable wrappers (~183 annotations fixed).model.membersfor command/event fields: Commands and events now usemodel.members(which includes inherited fields) instead ofmodel.children. This fixes emptyWithOnOffcommands in LevelControl and ~51 other missing command fields.supportedModesresolving asList[uint]instead ofList[ModeOptionStruct]across all mode clusters.minLevelin LevelControl from the Matter.js model) and sorted by tag to match CHIP SDK ordering.{"value": 4}→1 << 4). Multi-bit fields use range constraints ({"min": 0, "max": 1}→ bits 0-1) which fell back to0x1instead of the correct mask. Example fromThermostat.HVACSystemTypeBitmap:CoolingStage(bits 0-1): was0x1, now0x3HeatingStage(bits 2-3): was0x1, now0xCfloat,boolean,string) were all resolving touint. Now checks metatype first. Fixes EveClusterpressure/voltage/watt/altitudetyped asuintinstead offloat32, and BridgedDeviceBasicInformationvendorName/reachableetc. typed asuintinstead ofstr/bool.intfor temperature, power, energy, voltage types: Builds aSIGNED_INT_TYPESset by walking the Matter.js datatype hierarchy (global + cluster-level) — any type whose base resolves toint8/int16/.../int64is automatically recognized as signed. No hardcoded type aliases needed. Fixes Thermostat setpoints and calibration values, TemperatureMeasurement, DeviceEnergyManagement power values, and all other signed integer attributes that could have negative values.from __future__ import annotations, so explicit quoting (value: 'uint' = 0) is redundant. Now matches CHIP SDK style:value: uint = 0.GroupIdfield-name override: CHIP SDK usesgroupID(uppercase ID) in most command fields; the previousGroupId: "groupId"override was wrong for the majority of uses.toChipNameversion. FixesrequirePINforRemoteOperation(DoorLock),IPv4Addresses/offPremiseServicesReachableIPv4(GeneralDiagnostics),extendedPanIdPresent/panIdPresent(ThreadNetworkDiagnostics).objects/tocluster_defs/: The per-cluster files directory was renamed fromobjectstocluster_defsto avoid a macOS case-insensitivity collision (objects/andObjects.pyresolved to the same path, causing pylint to read__init__.pyinstead ofObjects.py).clusterNameto all generators:toCamelCaseandtoChipClassNamewere only receivingclusterNamein some generators (cluster-level attributes, enums). Now all generators (structs, commands, events, bitmaps) pass it through, so cluster-qualified overrides inFIELD_NAME_OVERRIDESandCLASS_NAME_OVERRIDESwork in all contexts.CHIP SDK naming compatibility
Added several override mechanisms to match CHIP SDK naming where it diverges from the Matter.js model:
CLASS_NAME_OVERRIDES+toChipClassName(): New override map (supports both global and cluster-qualified"Cluster.Name"keys) and helper function for class names wheretoChipName()doesn't match the CHIP SDK. Applied to all class-name generation sites. Examples:CommissioningARL,AdminVendorId,ICACCSRRequest,ACCapacityformatWindowCovering.TypeEnum→Type,WindowCovering.EndProductTypeEnum→EndProductType,ModeTagEnum→ModeTagWindowCovering.ModeBitmap→Mode,ConfigStatusBitmap→ConfigStatus, etc.LockStateEnum→DlLockState,LockTypeEnum→DlLockType,StatusCodeEnum→DlStatusStatusCodein AdministratorCommissioning/RvcCleanMode/RvcRunMode/TimeSynchronization, kept asStatusCodeEnumelsewhereDraftElectricalMeasurementClusterusesAc/Rms(notAC/RMS)K_VALUE_OVERRIDES: Enum/bitmap member name overrides:HueSaturation→HueAndSaturation,CredentialOverTheAirAccess→CredentialsOverTheAirAccessTiltBlindLift→TiltBlindLiftAndTilt,Unbolting→UnboltLedFeedback→LedFeedback(prevent LED acronym expansion in WindowCovering)FIELD_NAME_OVERRIDES: Additional field name fixes:Lqi→lqi(CHIP SDK uses lowercase in NetworkCommissioning/ThreadNetworkDiagnostics struct fields)EXTRA_ENUM_MEMBERS: Inject enum members present in the CHIP SDK but missing from the Matter.js model. Used forDoorLock.DlStatuswhich needskSuccess,kFailure,kInvalidField,kResourceExhausted,kNotFound.EXTRA_BITMAPS: Inject entire bitmap definitions missing from the Matter.js model. Used forColorControl.Bitmaps.ColorCapabilitiesBitmap(kHueSaturation,kEnhancedHue,kColorLoop,kXy,kColorTemperature).FIELD_NAME_OVERRIDES:toCamelCase()now accepts optional cluster context for cluster-specific field name overrides (e.g.,rmsVoltageonly in DraftElectricalMeasurementCluster).Comparison scripts
Added two utility scripts under
python_client/scripts/for verifying compatibility. These are rough developer tools used during this work and can be dropped later:compare_clusters.py: Detailed per-cluster structural comparison between generated and CHIP SDK clusterscompare_summary.py: Categorized summary of systematic differences (detects case, Enum suffix, and Bitmap suffix mismatches)Intentionally not changed
These differences from the older CHIP SDK were identified but deliberately kept as-is, since the generated output is more correct or forward-compatible:
eventListglobal attribute: The CHIP SDK version doesn't includeeventList(tag 0xFFFA), but it is part of the Matter spec and newer CHIP SDK versions include it. Kept.kUnknownEnumValuecalculation: The older CHIP SDK sometimes uses 0, but the current approach (first unused value after the max defined value) avoids assigning semantically meaningful low values. Kept.uintfor bitmap-typed fields, but using the actual bitmap type (e.g.,OptionsBitmap) is more type-safe. Kept.Issues to fix upstream in Matter.js
These are data gaps or bugs in the Matter.js model that required workarounds in the generator. Fixing them upstream would allow removing the corresponding overrides:
MinLevelattribute in LevelControl: The model definesMinLevel(id 2) twice — once for the Lighting conformance variant and once for non-Lighting. The generator deduplicates by ID, but the model should only expose one.supportedModesto lose itsList[ModeOptionStruct]type. The generator now falls back to the base, but ideally the model would inherit the full definition.ColorCapabilitiesBitmapin ColorControl: The CHIP SDK defines this bitmap but it's absent from the Matter.js model. Currently injected viaEXTRA_BITMAPS.DlStatusenum members in DoorLock: TheStatusCodeEnumonly hasDuplicateandOccupied, but the CHIP SDK'sDlStatusalso includeskSuccess,kFailure,kInvalidField,kResourceExhausted,kNotFound. Currently injected viaEXTRA_ENUM_MEMBERS.TiltBlindLiftnaming in WindowCovering.TypeEnum: Matter.js usesTiltBlindLift(value 8) but the CHIP SDK calls itTiltBlindLiftAndTilt. Currently handled viaK_VALUE_OVERRIDES.Unboltingnaming in DoorLock Feature bitmap: Matter.js usesUnboltingbut the CHIP SDK usesUnbolt. Currently handled viaK_VALUE_OVERRIDES.typebut validmetatype: Custom cluster attributes (e.g., EveClusterpressure,voltage,altitude) and some inherited attributes (BridgedDeviceBasicInformationvendorName,reachable) havetype: undefinedin the model. The generator now falls back tometatype(float → float32, string → str, boolean → bool), but ideally the model would set thetypefield explicitly.ContentLauncher.AdditionalInfoStructbut CHIP SDK usesChannel.ProgramCastStruct. Cross-cluster reference issue in the model.Globals.Structs.semtag: CHIP SDK usesDescriptor.Structs.SemanticTagStruct. The struct is defined globally in Matter.js but locally in the CHIP SDK.uint: Should beLightSensorTypeEnumbut the enum reference isn't resolved. Model-level type resolution issue.data/encodingHintoptionality swapped:datais optional in CHIP SDK but mandatory in generated;encodingHintis the reverse. Conformance mismatch in the model.Issues to address in HA
E0611(no-name-in-module) forchip.clusters.Objects: Now resolved by renaming the per-cluster directory fromobjects/tocluster_defs/, which eliminates the macOS case-insensitivity collision that caused pylint to readobjects/__init__.pyinstead ofObjects.py.Remaining known differences (not fixable at generator level)
chip.clusters.cluster_defs.X.Xvschip.clusters.Objects.X)aenumvsenum(~189): Inherent difference inMatterIntEnumbase class between CHIP SDK and our implementationstatusText, MediaPlaybacktrackAttributesnullability.Channel.ProgramStruct.externalIDListreferencesContentLauncher.AdditionalInfoStructinstead ofChannel.ProgramCastStruct;Descriptor.tagListusesGlobals.Structs.semtaginstead ofDescriptor.Structs.SemanticTagStruct)GroupKeyManagement.groupIdvsgroupID,ThreadNetworkDirectory.extendedPanIdvsextendedPanID— trade-offs where the override benefits the majority of usesTest plan
npx tsx python_client/scripts/generate-python-clusters.ts)supportedModescorrectly typed asList[ModeOptionStruct](DishwasherMode, RvcRunMode, etc.)pressure,voltage, etc.) correctly typed asfloat32int(Thermostat, TemperatureMeasurement, DeviceEnergyManagement)objects/→cluster_defs/renamerequirePINforRemoteOperationfield name correct (lowercasefor)IPv4Addresses/offPremiseServicesReachableIPv4field names correctint(signed)lqifield name correct (lowercase)kLedFeedbackbitmap member correct (notkLEDFeedback)