matter-python-client 1.4.1a0.dev20261008__tar.gz → 1.4.1a0.dev20261009__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/PKG-INFO +1 -1
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/matter_python_client.egg-info/PKG-INFO +1 -1
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/matter_python_client.egg-info/SOURCES.txt +2 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/matter_server/client/client.py +9 -1
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/matter_server/client/models/node.py +245 -33
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/pyproject.toml +1 -1
- matter_python_client-1.4.1a0.dev20261009/tests/test_bridge_node_fixture.py +258 -0
- matter_python_client-1.4.1a0.dev20261009/tests/test_bridge_topology.py +1307 -0
- matter_python_client-1.4.1a0.dev20261009/tests/test_event_dispatch.py +333 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/tests/test_integration.py +219 -0
- matter_python_client-1.4.1a0.dev20261008/tests/test_event_dispatch.py +0 -35
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/README.md +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/ChipUtility.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/__init__.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/ClusterObjects.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/Objects.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/Types.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/__init__.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/AccessControl.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/AccountLogin.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/Actions.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/ActivatedCarbonFilterMonitoring.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/AdministratorCommissioning.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/AirQuality.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/AmbientContextSensing.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/ApplicationBasic.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/ApplicationLauncher.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/AqaraAmbientSensingConfigurationCluster.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/AqaraOccupantLocationCluster.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/AqaraRadarSensingUnionCluster.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/AudioOutput.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/BasicInformation.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/Binding.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/BooleanState.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/BooleanStateConfiguration.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/BridgedDeviceBasicInformation.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/CameraAvSettingsUserLevelManagement.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/CameraAvStreamManagement.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/CarbonDioxideConcentrationMeasurement.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/CarbonMonoxideConcentrationMeasurement.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/Channel.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/Chime.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/ClosureControl.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/ClosureDimension.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/ColorControl.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/CommissionerControl.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/CommodityMetering.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/CommodityPrice.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/CommodityTariff.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/ContentAppObserver.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/ContentControl.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/ContentLauncher.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/Descriptor.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/DeviceEnergyManagement.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/DeviceEnergyManagementMode.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/DiagnosticLogs.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/DishwasherAlarm.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/DishwasherMode.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/DoorLock.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/DraftElectricalMeasurementCluster.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/EcosystemInformation.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/ElectricalEnergyMeasurement.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/ElectricalGridConditions.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/ElectricalPowerMeasurement.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/EnergyEvse.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/EnergyEvseMode.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/EnergyPreference.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/EthernetNetworkDiagnostics.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/EveCluster.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/FanControl.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/FixedLabel.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/FlowMeasurement.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/FormaldehydeConcentrationMeasurement.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/GeneralCommissioning.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/GeneralDiagnostics.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/Globals.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/GroupKeyManagement.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/Groupcast.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/Groups.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/HeimanCluster.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/HepaFilterMonitoring.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/IcdManagement.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/Identify.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/IkeaValveCalibrationCluster.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/IlluminanceMeasurement.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/InovelliCluster.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/JointFabricAdministrator.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/JointFabricDatastore.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/KeypadInput.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/LaundryDryerControls.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/LaundryWasherControls.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/LaundryWasherMode.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/LevelControl.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/LocalizationConfiguration.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/LowPower.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/MediaInput.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/MediaPlayback.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/Messages.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/MeterIdentification.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/MicrowaveOvenControl.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/MicrowaveOvenMode.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/ModeSelect.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/NeoCluster.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/NetworkCommissioning.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/NitrogenDioxideConcentrationMeasurement.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/OccupancySensing.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/OnOff.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/OperationalCredentials.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/OperationalState.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/OtaSoftwareUpdateProvider.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/OtaSoftwareUpdateRequestor.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/OvenCavityOperationalState.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/OvenMode.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/OzoneConcentrationMeasurement.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/Pm10ConcentrationMeasurement.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/Pm1ConcentrationMeasurement.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/Pm25ConcentrationMeasurement.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/PowerSource.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/PowerSourceConfiguration.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/PowerTopology.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/PressureMeasurement.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/PumpConfigurationAndControl.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/PushAvStreamTransport.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/RadonConcentrationMeasurement.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/RefrigeratorAlarm.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/RefrigeratorAndTemperatureControlledCabinetMode.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/RelativeHumidityMeasurement.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/RvcCleanMode.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/RvcOperationalState.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/RvcRunMode.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/ScenesManagement.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/ServiceArea.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/SmokeCoAlarm.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/SoftwareDiagnostics.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/SoilMeasurement.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/Switch.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/TargetNavigator.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/TclDehumidifierCluster.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/TemperatureAlarm.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/TemperatureControl.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/TemperatureMeasurement.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/Thermostat.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/ThermostatUserInterfaceConfiguration.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/ThirdRealityMeteringCluster.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/ThreadBorderRouterManagement.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/ThreadNetworkDiagnostics.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/ThreadNetworkDirectory.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/TimeFormatLocalization.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/TimeSynchronization.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/TlsCertificateManagement.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/TlsClientManagement.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/TotalVolatileOrganicCompoundsConcentrationMeasurement.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/UnitLocalization.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/UserLabel.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/ValveConfigurationAndControl.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/WagoCluster.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/WakeOnLan.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/WaterHeaterManagement.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/WaterHeaterMode.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/WaterTankLevelMonitoring.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/WebRtcTransportDefinitions.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/WebRtcTransportProvider.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/WebRtcTransportRequestor.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/WiFiNetworkDiagnostics.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/WiFiNetworkManagement.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/WindowCovering.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/ZoneManagement.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/cluster_defs/__init__.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/clusters/enum.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/tlv/__init__.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/chip/tlv/tlvlist.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/matter_python_client.egg-info/dependency_links.txt +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/matter_python_client.egg-info/not-zip-safe +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/matter_python_client.egg-info/requires.txt +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/matter_python_client.egg-info/top_level.txt +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/matter_server/__init__.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/matter_server/client/__init__.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/matter_server/client/connection.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/matter_server/client/exceptions.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/matter_server/client/models/__init__.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/matter_server/client/models/device_types.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/matter_server/common/__init__.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/matter_server/common/const.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/matter_server/common/custom_clusters.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/matter_server/common/errors.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/matter_server/common/helpers/__init__.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/matter_server/common/helpers/api.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/matter_server/common/helpers/json.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/matter_server/common/helpers/util.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/matter_server/common/models.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/matter_server/py.typed +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/setup.cfg +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/tests/test_bigint_wire.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/tests/test_client.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/tests/test_client_integration.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/tests/test_credentials.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/tests/test_icd.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/tests/test_imports.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/tests/test_network_topology.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/tests/test_ota_upload.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/tests/test_unix_socket.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/tests/test_util.py +0 -0
- {matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/tests/test_webrtc_protocol.py +0 -0
|
@@ -184,6 +184,8 @@ matter_server/common/helpers/api.py
|
|
|
184
184
|
matter_server/common/helpers/json.py
|
|
185
185
|
matter_server/common/helpers/util.py
|
|
186
186
|
tests/test_bigint_wire.py
|
|
187
|
+
tests/test_bridge_node_fixture.py
|
|
188
|
+
tests/test_bridge_topology.py
|
|
187
189
|
tests/test_client.py
|
|
188
190
|
tests/test_client_integration.py
|
|
189
191
|
tests/test_credentials.py
|
|
@@ -908,7 +908,7 @@ class MatterClient:
|
|
|
908
908
|
self._signal_event(EventType.ENDPOINT_REMOVED, data=msg.data, node_id=node_id)
|
|
909
909
|
# cleanup endpoint only after signalling subscribers
|
|
910
910
|
if node := self._nodes.get(node_id):
|
|
911
|
-
node.
|
|
911
|
+
node._remove_endpoint(endpoint_id)
|
|
912
912
|
return
|
|
913
913
|
if msg.event == EventType.ATTRIBUTE_UPDATED:
|
|
914
914
|
# data is tuple[node_id, attribute_path, new_value]
|
|
@@ -932,6 +932,14 @@ class MatterClient:
|
|
|
932
932
|
node_id = msg.data["node_id"]
|
|
933
933
|
endpoint_id = msg.data["endpoint_id"]
|
|
934
934
|
self.logger.debug("Endpoint added: %s/%s", node_id, endpoint_id)
|
|
935
|
+
# subscribers look the endpoint up; the node snapshot that carries it reaches them later
|
|
936
|
+
if (node := self._nodes.get(node_id)) is None or endpoint_id not in node.endpoints:
|
|
937
|
+
self.logger.debug(
|
|
938
|
+
"Not signalling endpoint %s/%s that the node model does not contain",
|
|
939
|
+
node_id,
|
|
940
|
+
endpoint_id,
|
|
941
|
+
)
|
|
942
|
+
return
|
|
935
943
|
if msg.event == EventType.NODE_EVENT:
|
|
936
944
|
if self.logger.isEnabledFor(logging.DEBUG):
|
|
937
945
|
self.logger.debug(
|
|
@@ -24,10 +24,15 @@ from .device_types import (
|
|
|
24
24
|
)
|
|
25
25
|
|
|
26
26
|
if TYPE_CHECKING:
|
|
27
|
+
from collections.abc import Mapping
|
|
28
|
+
|
|
27
29
|
from matter_server.common.models import MatterNodeData
|
|
28
30
|
|
|
29
31
|
LOGGER = logging.getLogger(__name__)
|
|
30
32
|
|
|
33
|
+
# the Matter specification places the Root Node on endpoint 0 of every node
|
|
34
|
+
ROOT_ENDPOINT_ID = 0
|
|
35
|
+
|
|
31
36
|
# pylint: disable=invalid-name
|
|
32
37
|
_CLUSTER_T = TypeVar("_CLUSTER_T", bound=Clusters.Cluster)
|
|
33
38
|
_ATTRIBUTE_T = TypeVar("_ATTRIBUTE_T", bound=Clusters.ClusterAttributeDescriptor)
|
|
@@ -44,6 +49,18 @@ def get_object_params(
|
|
|
44
49
|
raise KeyError(f"No descriptor found for object {object_id}")
|
|
45
50
|
|
|
46
51
|
|
|
52
|
+
def _reaches(start_id: int, target_id: int, parents: Mapping[int, int]) -> bool:
|
|
53
|
+
"""Return whether walking up the parent chain from an endpoint arrives at another."""
|
|
54
|
+
seen: set[int] = set()
|
|
55
|
+
current: int | None = start_id
|
|
56
|
+
while current is not None and current not in seen:
|
|
57
|
+
if current == target_id:
|
|
58
|
+
return True
|
|
59
|
+
seen.add(current)
|
|
60
|
+
current = parents.get(current)
|
|
61
|
+
return False
|
|
62
|
+
|
|
63
|
+
|
|
47
64
|
@dataclass
|
|
48
65
|
class MatterFabricData:
|
|
49
66
|
"""Data about a Matter fabric."""
|
|
@@ -84,18 +101,25 @@ class MatterEndpoint:
|
|
|
84
101
|
@property
|
|
85
102
|
def device_info(
|
|
86
103
|
self,
|
|
87
|
-
) -> Clusters.BasicInformation | Clusters.BridgedDeviceBasicInformation:
|
|
104
|
+
) -> Clusters.BasicInformation | Clusters.BridgedDeviceBasicInformation | None:
|
|
88
105
|
"""
|
|
89
106
|
Return device info.
|
|
90
107
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
108
|
+
Returns the BridgedDeviceBasicInformation of the Bridged Node this endpoint is or is a part
|
|
109
|
+
of, else the BasicInformation of the Node itself. It is None when that Bridged Node reports
|
|
110
|
+
no BridgedDeviceBasicInformation, because the info of the bridge or of the Aggregator above
|
|
111
|
+
it belongs to a different device.
|
|
94
112
|
"""
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
113
|
+
endpoint = self
|
|
114
|
+
seen: set[int] = set()
|
|
115
|
+
while endpoint.endpoint_id not in seen:
|
|
116
|
+
seen.add(endpoint.endpoint_id)
|
|
117
|
+
if endpoint.is_bridged_device:
|
|
118
|
+
return endpoint.get_cluster(Clusters.BridgedDeviceBasicInformation)
|
|
119
|
+
parent = endpoint.node.get_closest_compose_parent(endpoint.endpoint_id)
|
|
120
|
+
if parent is None:
|
|
121
|
+
break
|
|
122
|
+
endpoint = parent
|
|
99
123
|
return self.node.device_info
|
|
100
124
|
|
|
101
125
|
def has_cluster(self, cluster: type[_CLUSTER_T] | int) -> bool:
|
|
@@ -217,11 +241,14 @@ class MatterEndpoint:
|
|
|
217
241
|
|
|
218
242
|
def update(self, attributes_data: dict[str, Any]) -> None:
|
|
219
243
|
"""Update MatterEndpoint from (endpoint-specific) raw Attributes data."""
|
|
244
|
+
# a snapshot carries the complete state of the endpoint, never a delta
|
|
245
|
+
self.clusters = {}
|
|
220
246
|
# unwrap cluster and clusterattributes from raw node data attributes
|
|
221
247
|
for attribute_path, attribute_value in attributes_data.items():
|
|
222
248
|
self.set_attribute_value(attribute_path, attribute_value)
|
|
223
|
-
#
|
|
224
|
-
if cluster := self.get_cluster(Clusters.Descriptor):
|
|
249
|
+
# a snapshot without the DeviceTypeList says nothing about the device types, so the previous ones stay
|
|
250
|
+
if f"{self.endpoint_id}/29/0" in attributes_data and (cluster := self.get_cluster(Clusters.Descriptor)):
|
|
251
|
+
self.device_types = set()
|
|
225
252
|
for dev_info in cluster.deviceTypeList:
|
|
226
253
|
device_type = DEVICE_TYPES.get(dev_info.deviceType)
|
|
227
254
|
if device_type is None:
|
|
@@ -240,9 +267,10 @@ class MatterNode:
|
|
|
240
267
|
def __init__(self, node_data: MatterNodeData) -> None:
|
|
241
268
|
"""Initialize MatterNode from MatterNodeData."""
|
|
242
269
|
self.endpoints: dict[int, MatterEndpoint] = {}
|
|
243
|
-
# composed devices reference to other endpoints through the partsList attribute
|
|
244
|
-
# create a mapping table
|
|
245
270
|
self._composed_endpoints: dict[int, int] = {}
|
|
271
|
+
self._bridge_parents: dict[int, int] = {}
|
|
272
|
+
self._snapshot_endpoint_ids: set[int] = set()
|
|
273
|
+
self._reported_missing_bridged_info: set[int] = set()
|
|
246
274
|
self.update(node_data)
|
|
247
275
|
|
|
248
276
|
@property
|
|
@@ -263,11 +291,12 @@ class MatterNode:
|
|
|
263
291
|
return self.node_data.available
|
|
264
292
|
|
|
265
293
|
@property
|
|
266
|
-
def device_info(self) -> Clusters.BasicInformation:
|
|
294
|
+
def device_info(self) -> Clusters.BasicInformation | None:
|
|
267
295
|
"""
|
|
268
296
|
Return device info for this Node.
|
|
269
297
|
|
|
270
|
-
Returns BasicInformation from the Node itself (endpoint 0)
|
|
298
|
+
Returns BasicInformation from the Node itself (endpoint 0), or None while the node has not
|
|
299
|
+
reported it.
|
|
271
300
|
"""
|
|
272
301
|
return self.get_cluster(0, Clusters.BasicInformation)
|
|
273
302
|
|
|
@@ -304,17 +333,49 @@ class MatterNode:
|
|
|
304
333
|
|
|
305
334
|
Returns None is the Cluster is not present on the node.
|
|
306
335
|
"""
|
|
307
|
-
|
|
336
|
+
if (endpoint_obj := self.endpoints.get(endpoint)) is None:
|
|
337
|
+
return None
|
|
338
|
+
return endpoint_obj.get_cluster(cluster)
|
|
308
339
|
|
|
309
340
|
def get_compose_parent(self, endpoint_id: int) -> MatterEndpoint | None:
|
|
310
|
-
"""Return endpoint of
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
341
|
+
"""Return the endpoint of the composed device the endpoint is a part of, at any depth.
|
|
342
|
+
|
|
343
|
+
Consumers derive the device of a part from this endpoint, so a part nested below another
|
|
344
|
+
part resolves to the top of the composition, not to its closest parent.
|
|
345
|
+
"""
|
|
346
|
+
if (parent_id := self._composed_endpoints.get(endpoint_id)) is None:
|
|
347
|
+
return None
|
|
348
|
+
seen = {endpoint_id, parent_id}
|
|
349
|
+
while (next_id := self._composed_endpoints.get(parent_id)) is not None and next_id not in seen:
|
|
350
|
+
seen.add(next_id)
|
|
351
|
+
parent_id = next_id
|
|
352
|
+
return self.endpoints.get(parent_id)
|
|
353
|
+
|
|
354
|
+
def get_closest_compose_parent(self, endpoint_id: int) -> MatterEndpoint | None:
|
|
355
|
+
"""Return the closest endpoint the endpoint is a part of."""
|
|
356
|
+
if (parent_id := self._composed_endpoints.get(endpoint_id)) is None:
|
|
357
|
+
return None
|
|
358
|
+
return self.endpoints.get(parent_id)
|
|
359
|
+
|
|
360
|
+
def get_compose_child_ids(self, endpoint_id: int) -> tuple[int, ...]:
|
|
361
|
+
"""Return endpoint IDs of the parts, at any depth, of the composed device the endpoint is the top of."""
|
|
362
|
+
return tuple(
|
|
363
|
+
sorted(
|
|
364
|
+
child_id
|
|
365
|
+
for child_id in self._composed_endpoints
|
|
366
|
+
if (parent := self.get_compose_parent(child_id)) is not None and parent.endpoint_id == endpoint_id
|
|
367
|
+
)
|
|
368
|
+
)
|
|
314
369
|
|
|
315
|
-
def
|
|
316
|
-
"""Return
|
|
317
|
-
|
|
370
|
+
def get_bridge_parent(self, endpoint_id: int) -> MatterEndpoint | None:
|
|
371
|
+
"""Return the Aggregator endpoint that bridges the given endpoint, if any."""
|
|
372
|
+
if (parent_id := self._bridge_parents.get(endpoint_id)) is None:
|
|
373
|
+
return None
|
|
374
|
+
return self.endpoints.get(parent_id)
|
|
375
|
+
|
|
376
|
+
def get_bridge_child_ids(self, endpoint_id: int) -> tuple[int, ...]:
|
|
377
|
+
"""Return endpoint IDs of the devices bridged by the given Aggregator endpoint."""
|
|
378
|
+
return tuple(sorted(x for x, y in self._bridge_parents.items() if y == endpoint_id))
|
|
318
379
|
|
|
319
380
|
def update(self, node_data: MatterNodeData) -> None:
|
|
320
381
|
"""Update MatterNode from MatterNodeData."""
|
|
@@ -326,6 +387,7 @@ class MatterNode:
|
|
|
326
387
|
if endpoint_id not in endpoint_data:
|
|
327
388
|
endpoint_data[endpoint_id] = {}
|
|
328
389
|
endpoint_data[endpoint_id][attribute_path] = attribute_data
|
|
390
|
+
self._snapshot_endpoint_ids = set(endpoint_data)
|
|
329
391
|
for endpoint_id, attributes_data in endpoint_data.items():
|
|
330
392
|
if endpoint_id in self.endpoints:
|
|
331
393
|
self.endpoints[endpoint_id].update(attributes_data)
|
|
@@ -333,15 +395,64 @@ class MatterNode:
|
|
|
333
395
|
self.endpoints[endpoint_id] = MatterEndpoint(
|
|
334
396
|
endpoint_id=endpoint_id, attributes_data=attributes_data, node=self
|
|
335
397
|
)
|
|
336
|
-
|
|
337
|
-
|
|
398
|
+
self._map_endpoint_parents()
|
|
399
|
+
|
|
400
|
+
def _remove_endpoint(self, endpoint_id: int) -> None:
|
|
401
|
+
"""Remove an endpoint together with its parts, and every parent relation they are in.
|
|
402
|
+
|
|
403
|
+
This is the only way an endpoint leaves `endpoints`: a snapshot that no longer reports an
|
|
404
|
+
endpoint keeps its object until the server announces the removal, because consumers look
|
|
405
|
+
the endpoint up when handling that announcement. The parts of the endpoint, at any depth,
|
|
406
|
+
leave with it whether or not a snapshot still reports them: their device is gone, and left
|
|
407
|
+
behind they would resolve to no device when their own removal arrives. A device the
|
|
408
|
+
endpoint bridged stays, without its bridge parent, until its own removal arrives.
|
|
409
|
+
|
|
410
|
+
May only be called by logic that received data from the server.
|
|
411
|
+
"""
|
|
412
|
+
if endpoint_id not in self.endpoints:
|
|
413
|
+
return
|
|
414
|
+
removed = {endpoint_id}
|
|
415
|
+
pending = [endpoint_id]
|
|
416
|
+
while pending:
|
|
417
|
+
parent_id = pending.pop()
|
|
418
|
+
for child_id, child_parent_id in self._composed_endpoints.items():
|
|
419
|
+
if child_parent_id == parent_id and child_id not in removed:
|
|
420
|
+
removed.add(child_id)
|
|
421
|
+
pending.append(child_id)
|
|
422
|
+
for removed_id in removed:
|
|
423
|
+
del self.endpoints[removed_id]
|
|
424
|
+
self._reported_missing_bridged_info.discard(removed_id)
|
|
425
|
+
self._composed_endpoints = {
|
|
426
|
+
child_id: parent_id for child_id, parent_id in self._composed_endpoints.items() if child_id not in removed
|
|
427
|
+
}
|
|
428
|
+
self._bridge_parents = {
|
|
429
|
+
child_id: parent_id
|
|
430
|
+
for child_id, parent_id in self._bridge_parents.items()
|
|
431
|
+
if child_id not in removed and parent_id not in removed
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
def _map_endpoint_parents(self) -> None:
|
|
435
|
+
"""Resolve each endpoint to its closest parent endpoint, split by parent kind.
|
|
436
|
+
|
|
437
|
+
An endpoint's partsList may enumerate its whole family - every descendant, not just
|
|
438
|
+
the direct children - which is the pattern a bridge uses. Of the endpoints listing a
|
|
439
|
+
given endpoint, the closest one is therefore the endpoint that lists none of the
|
|
440
|
+
others - on contradictory lists the smaller family wins, then the lower endpoint
|
|
441
|
+
number - and a parent is only accepted while the relations stay a tree.
|
|
442
|
+
|
|
443
|
+
Only the endpoints of the last snapshot take part. A snapshot replaces the relation of an
|
|
444
|
+
endpoint only where it names a parent for it: one that bridges or composes it, or the
|
|
445
|
+
parent it already had, whose classification then wins. Where it names none - the endpoint
|
|
446
|
+
unlisted or unreported, its parent without a Descriptor or device types, or listed only by
|
|
447
|
+
endpoints that make it neither bridged nor a part - the endpoint keeps its relation until
|
|
448
|
+
its own removal or that of the parent is announced, so a consumer handling the
|
|
449
|
+
announcement still finds the device the endpoint is a part of. A kept relation that would
|
|
450
|
+
close a cycle with a named one is dropped.
|
|
451
|
+
"""
|
|
452
|
+
snapshot_ids = self._snapshot_endpoint_ids & self.endpoints.keys()
|
|
453
|
+
families: dict[int, set[int]] = {}
|
|
338
454
|
for endpoint in self.endpoints.values():
|
|
339
|
-
if
|
|
340
|
-
# ignore root endpoint
|
|
341
|
-
continue
|
|
342
|
-
if Aggregator in endpoint.device_types:
|
|
343
|
-
# ignore Bridge endpoint
|
|
344
|
-
# (as that will also use partsList to indicate its child's)
|
|
455
|
+
if endpoint.endpoint_id not in snapshot_ids:
|
|
345
456
|
continue
|
|
346
457
|
descriptor = endpoint.get_cluster(Clusters.Descriptor)
|
|
347
458
|
if descriptor is None:
|
|
@@ -351,9 +462,110 @@ class MatterNode:
|
|
|
351
462
|
endpoint.endpoint_id,
|
|
352
463
|
)
|
|
353
464
|
continue
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
465
|
+
families[endpoint.endpoint_id] = {
|
|
466
|
+
child_id
|
|
467
|
+
for child_id in descriptor.partsList or ()
|
|
468
|
+
if child_id in snapshot_ids and child_id != endpoint.endpoint_id
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
candidates: dict[int, set[int]] = {}
|
|
472
|
+
for parent_id, family in families.items():
|
|
473
|
+
for child_id in family:
|
|
474
|
+
candidates.setdefault(child_id, set()).add(parent_id)
|
|
475
|
+
|
|
476
|
+
parents: dict[int, int] = {}
|
|
477
|
+
for child_id in sorted(candidates):
|
|
478
|
+
parent_ids = candidates[child_id]
|
|
479
|
+
for parent_id in sorted(
|
|
480
|
+
parent_ids,
|
|
481
|
+
key=lambda pid: (
|
|
482
|
+
len((parent_ids - {pid}) & families[pid]),
|
|
483
|
+
len(families[pid]),
|
|
484
|
+
pid,
|
|
485
|
+
),
|
|
486
|
+
):
|
|
487
|
+
if _reaches(parent_id, child_id, parents):
|
|
488
|
+
LOGGER.warning(
|
|
489
|
+
"Ignoring cyclic partsList relation: Node %s, endpoint %s below %s",
|
|
490
|
+
self.node_id,
|
|
491
|
+
child_id,
|
|
492
|
+
parent_id,
|
|
493
|
+
)
|
|
494
|
+
continue
|
|
495
|
+
parents[child_id] = parent_id
|
|
496
|
+
break
|
|
497
|
+
|
|
498
|
+
previous_composed = self._composed_endpoints
|
|
499
|
+
previous_bridge = self._bridge_parents
|
|
500
|
+
self._composed_endpoints = {}
|
|
501
|
+
self._bridge_parents = {}
|
|
502
|
+
decided = {
|
|
503
|
+
child_id for child_id, parent_id in parents.items() if self._map_endpoint_parent(child_id, parent_id)
|
|
504
|
+
}
|
|
505
|
+
related = self._composed_endpoints | self._bridge_parents
|
|
506
|
+
for relations, previous in (
|
|
507
|
+
(self._composed_endpoints, previous_composed),
|
|
508
|
+
(self._bridge_parents, previous_bridge),
|
|
509
|
+
):
|
|
510
|
+
for child_id, parent_id in sorted(previous.items()):
|
|
511
|
+
if child_id in related or (child_id in decided and parents[child_id] == parent_id):
|
|
512
|
+
continue
|
|
513
|
+
if _reaches(parent_id, child_id, related):
|
|
514
|
+
LOGGER.warning(
|
|
515
|
+
"Dropping parent relation that a snapshot made cyclic: Node %s, endpoint %s below %s",
|
|
516
|
+
self.node_id,
|
|
517
|
+
child_id,
|
|
518
|
+
parent_id,
|
|
519
|
+
)
|
|
520
|
+
continue
|
|
521
|
+
relations[child_id] = parent_id
|
|
522
|
+
related[child_id] = parent_id
|
|
523
|
+
|
|
524
|
+
self._report_bridged_devices_without_info()
|
|
525
|
+
|
|
526
|
+
def _report_bridged_devices_without_info(self) -> None:
|
|
527
|
+
"""Warn about bridged devices the bridge does not describe, once per endpoint.
|
|
528
|
+
|
|
529
|
+
Such an endpoint has no device info of its own, and the info of the bridge describes a
|
|
530
|
+
different device, so it stays unnamed until the bridge reports it.
|
|
531
|
+
"""
|
|
532
|
+
for endpoint_id, endpoint in self.endpoints.items():
|
|
533
|
+
if endpoint_id in self._reported_missing_bridged_info:
|
|
534
|
+
continue
|
|
535
|
+
if not endpoint.is_bridged_device:
|
|
536
|
+
continue
|
|
537
|
+
if endpoint.get_cluster(Clusters.BridgedDeviceBasicInformation) is not None:
|
|
538
|
+
continue
|
|
539
|
+
self._reported_missing_bridged_info.add(endpoint_id)
|
|
540
|
+
LOGGER.warning(
|
|
541
|
+
"Bridged device without BridgedDeviceBasicInformation: Node %s, endpoint %s",
|
|
542
|
+
self.node_id,
|
|
543
|
+
endpoint_id,
|
|
544
|
+
)
|
|
545
|
+
|
|
546
|
+
def _map_endpoint_parent(self, child_id: int, parent_id: int) -> bool:
|
|
547
|
+
"""Record what the parent endpoint makes of the endpoint below it, return whether it decides.
|
|
548
|
+
|
|
549
|
+
A child of the root endpoint is top level. Below an Aggregator, a Bridged Node is a
|
|
550
|
+
bridged device; any other child stands alone, whether or not the Aggregator is itself a
|
|
551
|
+
Bridged Node (so ids derived from the compose parent match older clients). Below any other endpoint, a child
|
|
552
|
+
is a part of it - also when the device types of that endpoint are unknown to this client,
|
|
553
|
+
so only a parent that reports no device type at all decides nothing.
|
|
554
|
+
"""
|
|
555
|
+
if parent_id == ROOT_ENDPOINT_ID:
|
|
556
|
+
return True
|
|
557
|
+
parent = self.endpoints[parent_id]
|
|
558
|
+
descriptor = parent.get_cluster(Clusters.Descriptor)
|
|
559
|
+
assert descriptor is not None # only endpoints with a Descriptor are parents
|
|
560
|
+
if not descriptor.deviceTypeList:
|
|
561
|
+
return False
|
|
562
|
+
if RootNode in parent.device_types:
|
|
563
|
+
return True
|
|
564
|
+
if Aggregator not in parent.device_types:
|
|
565
|
+
self._composed_endpoints[child_id] = parent_id
|
|
566
|
+
elif BridgedNode in self.endpoints[child_id].device_types:
|
|
567
|
+
self._bridge_parents[child_id] = parent_id
|
|
568
|
+
return True
|
|
357
569
|
|
|
358
570
|
def update_attribute(self, attribute_path: str, new_value: Any) -> None:
|
|
359
571
|
"""Handle Attribute value update."""
|
{matter_python_client-1.4.1a0.dev20261008 → matter_python_client-1.4.1a0.dev20261009}/pyproject.toml
RENAMED
|
@@ -25,7 +25,7 @@ license = "Apache-2.0"
|
|
|
25
25
|
name = "matter-python-client"
|
|
26
26
|
readme = "README.md"
|
|
27
27
|
requires-python = ">=3.12"
|
|
28
|
-
version = "1.4.1a0.
|
|
28
|
+
version = "1.4.1a0.dev20261009"
|
|
29
29
|
|
|
30
30
|
[project.urls]
|
|
31
31
|
Homepage = "https://github.com/matter-js/matterjs-server"
|