matter-python-client 1.5.0a0.dev20261009__tar.gz → 1.5.0a0.dev20261014__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.
Files changed (205) hide show
  1. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/PKG-INFO +32 -1
  2. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/README.md +31 -0
  3. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/matter_python_client.egg-info/PKG-INFO +32 -1
  4. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/matter_python_client.egg-info/SOURCES.txt +1 -0
  5. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/matter_server/client/client.py +211 -5
  6. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/matter_server/client/models/node.py +61 -41
  7. matter_python_client-1.5.0a0.dev20261014/matter_server/common/errors.py +289 -0
  8. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/matter_server/common/models.py +377 -2
  9. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/pyproject.toml +1 -1
  10. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/tests/test_bridge_node_fixture.py +39 -6
  11. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/tests/test_bridge_topology.py +151 -15
  12. matter_python_client-1.5.0a0.dev20261014/tests/test_camera.py +549 -0
  13. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/tests/test_event_dispatch.py +55 -1
  14. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/tests/test_integration.py +2 -2
  15. matter_python_client-1.5.0a0.dev20261014/tests/test_webrtc_protocol.py +127 -0
  16. matter_python_client-1.5.0a0.dev20261009/matter_server/common/errors.py +0 -139
  17. matter_python_client-1.5.0a0.dev20261009/tests/test_webrtc_protocol.py +0 -36
  18. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/ChipUtility.py +0 -0
  19. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/__init__.py +0 -0
  20. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/ClusterObjects.py +0 -0
  21. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/Objects.py +0 -0
  22. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/Types.py +0 -0
  23. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/__init__.py +0 -0
  24. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/AccessControl.py +0 -0
  25. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/AccountLogin.py +0 -0
  26. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/Actions.py +0 -0
  27. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/ActivatedCarbonFilterMonitoring.py +0 -0
  28. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/AdministratorCommissioning.py +0 -0
  29. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/AirQuality.py +0 -0
  30. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/AmbientContextSensing.py +0 -0
  31. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/ApplicationBasic.py +0 -0
  32. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/ApplicationLauncher.py +0 -0
  33. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/AqaraAmbientSensingConfigurationCluster.py +0 -0
  34. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/AqaraOccupantLocationCluster.py +0 -0
  35. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/AqaraRadarSensingUnionCluster.py +0 -0
  36. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/AudioOutput.py +0 -0
  37. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/BasicInformation.py +0 -0
  38. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/Binding.py +0 -0
  39. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/BooleanState.py +0 -0
  40. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/BooleanStateConfiguration.py +0 -0
  41. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/BridgedDeviceBasicInformation.py +0 -0
  42. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/CameraAvSettingsUserLevelManagement.py +0 -0
  43. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/CameraAvStreamManagement.py +0 -0
  44. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/CarbonDioxideConcentrationMeasurement.py +0 -0
  45. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/CarbonMonoxideConcentrationMeasurement.py +0 -0
  46. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/Channel.py +0 -0
  47. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/Chime.py +0 -0
  48. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/ClosureControl.py +0 -0
  49. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/ClosureDimension.py +0 -0
  50. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/ColorControl.py +0 -0
  51. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/CommissionerControl.py +0 -0
  52. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/CommodityMetering.py +0 -0
  53. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/CommodityPrice.py +0 -0
  54. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/CommodityTariff.py +0 -0
  55. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/ContentAppObserver.py +0 -0
  56. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/ContentControl.py +0 -0
  57. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/ContentLauncher.py +0 -0
  58. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/Descriptor.py +0 -0
  59. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/DeviceEnergyManagement.py +0 -0
  60. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/DeviceEnergyManagementMode.py +0 -0
  61. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/DiagnosticLogs.py +0 -0
  62. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/DishwasherAlarm.py +0 -0
  63. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/DishwasherMode.py +0 -0
  64. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/DoorLock.py +0 -0
  65. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/DraftElectricalMeasurementCluster.py +0 -0
  66. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/EcosystemInformation.py +0 -0
  67. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/ElectricalEnergyMeasurement.py +0 -0
  68. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/ElectricalGridConditions.py +0 -0
  69. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/ElectricalPowerMeasurement.py +0 -0
  70. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/EnergyEvse.py +0 -0
  71. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/EnergyEvseMode.py +0 -0
  72. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/EnergyPreference.py +0 -0
  73. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/EthernetNetworkDiagnostics.py +0 -0
  74. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/EveCluster.py +0 -0
  75. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/FanControl.py +0 -0
  76. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/FixedLabel.py +0 -0
  77. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/FlowMeasurement.py +0 -0
  78. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/FormaldehydeConcentrationMeasurement.py +0 -0
  79. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/GeneralCommissioning.py +0 -0
  80. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/GeneralDiagnostics.py +0 -0
  81. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/Globals.py +0 -0
  82. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/GroupKeyManagement.py +0 -0
  83. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/Groupcast.py +0 -0
  84. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/Groups.py +0 -0
  85. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/HeimanCluster.py +0 -0
  86. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/HepaFilterMonitoring.py +0 -0
  87. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/IcdManagement.py +0 -0
  88. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/Identify.py +0 -0
  89. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/IkeaValveCalibrationCluster.py +0 -0
  90. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/IlluminanceMeasurement.py +0 -0
  91. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/InovelliCluster.py +0 -0
  92. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/JointFabricAdministrator.py +0 -0
  93. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/JointFabricDatastore.py +0 -0
  94. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/KeypadInput.py +0 -0
  95. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/LaundryDryerControls.py +0 -0
  96. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/LaundryWasherControls.py +0 -0
  97. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/LaundryWasherMode.py +0 -0
  98. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/LevelControl.py +0 -0
  99. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/LocalizationConfiguration.py +0 -0
  100. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/LowPower.py +0 -0
  101. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/MediaInput.py +0 -0
  102. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/MediaPlayback.py +0 -0
  103. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/Messages.py +0 -0
  104. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/MeterIdentification.py +0 -0
  105. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/MicrowaveOvenControl.py +0 -0
  106. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/MicrowaveOvenMode.py +0 -0
  107. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/ModeSelect.py +0 -0
  108. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/NeoCluster.py +0 -0
  109. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/NetworkCommissioning.py +0 -0
  110. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/NitrogenDioxideConcentrationMeasurement.py +0 -0
  111. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/OccupancySensing.py +0 -0
  112. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/OnOff.py +0 -0
  113. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/OperationalCredentials.py +0 -0
  114. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/OperationalState.py +0 -0
  115. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/OtaSoftwareUpdateProvider.py +0 -0
  116. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/OtaSoftwareUpdateRequestor.py +0 -0
  117. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/OvenCavityOperationalState.py +0 -0
  118. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/OvenMode.py +0 -0
  119. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/OzoneConcentrationMeasurement.py +0 -0
  120. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/Pm10ConcentrationMeasurement.py +0 -0
  121. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/Pm1ConcentrationMeasurement.py +0 -0
  122. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/Pm25ConcentrationMeasurement.py +0 -0
  123. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/PowerSource.py +0 -0
  124. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/PowerSourceConfiguration.py +0 -0
  125. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/PowerTopology.py +0 -0
  126. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/PressureMeasurement.py +0 -0
  127. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/PumpConfigurationAndControl.py +0 -0
  128. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/PushAvStreamTransport.py +0 -0
  129. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/RadonConcentrationMeasurement.py +0 -0
  130. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/RefrigeratorAlarm.py +0 -0
  131. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/RefrigeratorAndTemperatureControlledCabinetMode.py +0 -0
  132. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/RelativeHumidityMeasurement.py +0 -0
  133. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/RvcCleanMode.py +0 -0
  134. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/RvcOperationalState.py +0 -0
  135. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/RvcRunMode.py +0 -0
  136. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/ScenesManagement.py +0 -0
  137. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/ServiceArea.py +0 -0
  138. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/SmokeCoAlarm.py +0 -0
  139. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/SoftwareDiagnostics.py +0 -0
  140. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/SoilMeasurement.py +0 -0
  141. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/Switch.py +0 -0
  142. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/TargetNavigator.py +0 -0
  143. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/TclDehumidifierCluster.py +0 -0
  144. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/TemperatureAlarm.py +0 -0
  145. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/TemperatureControl.py +0 -0
  146. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/TemperatureMeasurement.py +0 -0
  147. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/Thermostat.py +0 -0
  148. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/ThermostatUserInterfaceConfiguration.py +0 -0
  149. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/ThirdRealityMeteringCluster.py +0 -0
  150. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/ThreadBorderRouterManagement.py +0 -0
  151. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/ThreadNetworkDiagnostics.py +0 -0
  152. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/ThreadNetworkDirectory.py +0 -0
  153. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/TimeFormatLocalization.py +0 -0
  154. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/TimeSynchronization.py +0 -0
  155. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/TlsCertificateManagement.py +0 -0
  156. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/TlsClientManagement.py +0 -0
  157. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/TotalVolatileOrganicCompoundsConcentrationMeasurement.py +0 -0
  158. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/UnitLocalization.py +0 -0
  159. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/UserLabel.py +0 -0
  160. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/ValveConfigurationAndControl.py +0 -0
  161. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/WagoCluster.py +0 -0
  162. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/WakeOnLan.py +0 -0
  163. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/WaterHeaterManagement.py +0 -0
  164. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/WaterHeaterMode.py +0 -0
  165. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/WaterTankLevelMonitoring.py +0 -0
  166. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/WebRtcTransportDefinitions.py +0 -0
  167. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/WebRtcTransportProvider.py +0 -0
  168. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/WebRtcTransportRequestor.py +0 -0
  169. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/WiFiNetworkDiagnostics.py +0 -0
  170. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/WiFiNetworkManagement.py +0 -0
  171. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/WindowCovering.py +0 -0
  172. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/ZoneManagement.py +0 -0
  173. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/cluster_defs/__init__.py +0 -0
  174. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/clusters/enum.py +0 -0
  175. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/tlv/__init__.py +0 -0
  176. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/chip/tlv/tlvlist.py +0 -0
  177. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/matter_python_client.egg-info/dependency_links.txt +0 -0
  178. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/matter_python_client.egg-info/not-zip-safe +0 -0
  179. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/matter_python_client.egg-info/requires.txt +0 -0
  180. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/matter_python_client.egg-info/top_level.txt +0 -0
  181. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/matter_server/__init__.py +0 -0
  182. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/matter_server/client/__init__.py +0 -0
  183. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/matter_server/client/connection.py +0 -0
  184. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/matter_server/client/exceptions.py +0 -0
  185. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/matter_server/client/models/__init__.py +0 -0
  186. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/matter_server/client/models/device_types.py +0 -0
  187. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/matter_server/common/__init__.py +0 -0
  188. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/matter_server/common/const.py +0 -0
  189. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/matter_server/common/custom_clusters.py +0 -0
  190. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/matter_server/common/helpers/__init__.py +0 -0
  191. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/matter_server/common/helpers/api.py +0 -0
  192. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/matter_server/common/helpers/json.py +0 -0
  193. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/matter_server/common/helpers/util.py +0 -0
  194. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/matter_server/py.typed +0 -0
  195. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/setup.cfg +0 -0
  196. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/tests/test_bigint_wire.py +0 -0
  197. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/tests/test_client.py +0 -0
  198. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/tests/test_client_integration.py +0 -0
  199. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/tests/test_credentials.py +0 -0
  200. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/tests/test_icd.py +0 -0
  201. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/tests/test_imports.py +0 -0
  202. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/tests/test_network_topology.py +0 -0
  203. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/tests/test_ota_upload.py +0 -0
  204. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/tests/test_unix_socket.py +0 -0
  205. {matter_python_client-1.5.0a0.dev20261009 → matter_python_client-1.5.0a0.dev20261014}/tests/test_util.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: matter-python-client
3
- Version: 1.5.0a0.dev20261009
3
+ Version: 1.5.0a0.dev20261014
4
4
  Summary: Python Client for the OHF Matter Server
5
5
  Author-email: Open Home Foundation <hello@openhomefoundation.io>
6
6
  License-Expression: Apache-2.0
@@ -110,6 +110,37 @@ Beyond the `python-matter-server` API, the client exposes commands the Matter.js
110
110
  then streams the bytes to `POST /ota-upload/<upload_id>`. Raises `OtaUploadError` (code 101) for a
111
111
  corrupt image, an expired/spent id, or disabled OTA support.
112
112
  - `get_icd_state`, `register_icd`, `unregister_icd`, `resync_icd` — ICD Check-In registration
113
+ - `camera_get_capabilities`, `camera_start_stream`, `camera_provide_answer`,
114
+ `camera_provide_ice_candidates`, `camera_stop_stream`, `camera_snapshot`, `camera_release_stream`
115
+ — the camera API (schema 14+). `camera_get_capabilities`, `camera_start_stream`, `camera_stop_stream`
116
+ and `camera_snapshot` return dataclasses from `matter_server.common.models`; the other three return
117
+ `None`.
118
+ `camera_start_stream`'s `video` / `audio` take `None` (the server decides), a `CameraVideoHints` /
119
+ `CameraAudioHints` dict, or `False` (no such track). `talkback=True` lets the offer ask the camera to
120
+ play your audio; without it the server makes the offer's audio sections not send, so a browser-style
121
+ `sendrecv` audio offer goes to the camera as `recvonly`. `camera_session_ended` and
122
+ `camera_stream_evicted` events arrive as `CameraSessionEndedData` / `CameraStreamEvictedData`;
123
+ `webrtc_callback` stays a dict. Errors 102 to 106 (`CameraStreamIncompatible`, ...) expose their
124
+ details as attributes, e.g. `err.reason` and `err.bound` on `CameraStreamIncompatible`
125
+
126
+ The minimal `camera_start_stream` call is `node_id`, `endpoint_id`, `stream_usage` and your SDP
127
+ offer: the server reuses or allocates the best stream the camera can serve that still fits what
128
+ the offer can decode, and adds audio when it can. Pass `video` / `audio` hints only to set hard bounds. A
129
+ session opened this way is driven with `camera_provide_answer`, `camera_provide_ice_candidates`
130
+ and `camera_stop_stream` only, not with `send_webrtc_provider_command` or a `send_device_command`
131
+ `EndSession`: the server tracks the session and its streams, and only the `camera_*` methods keep
132
+ that record correct. The exception is a re-offer (ICE restart), which goes through
133
+ `send_webrtc_provider_command`. See `docs/websockets_api.md` for the flow and every option.
134
+
135
+ ```python
136
+ try:
137
+ result = await client.camera_start_stream(node_id, 1, "LiveView", sdp=offer)
138
+ except CameraStreamIncompatible as err:
139
+ print(err.reason, err.bound)
140
+ else:
141
+ # The camera's answer arrives as a webrtc_callback "answer" event for this session.
142
+ print(result.webrtc_session_id, result.video.codec if result.video else None)
143
+ ```
113
144
 
114
145
  ```python
115
146
  version = await client.upload_ota_file("/path/to/firmware.ota")
@@ -78,6 +78,37 @@ Beyond the `python-matter-server` API, the client exposes commands the Matter.js
78
78
  then streams the bytes to `POST /ota-upload/<upload_id>`. Raises `OtaUploadError` (code 101) for a
79
79
  corrupt image, an expired/spent id, or disabled OTA support.
80
80
  - `get_icd_state`, `register_icd`, `unregister_icd`, `resync_icd` — ICD Check-In registration
81
+ - `camera_get_capabilities`, `camera_start_stream`, `camera_provide_answer`,
82
+ `camera_provide_ice_candidates`, `camera_stop_stream`, `camera_snapshot`, `camera_release_stream`
83
+ — the camera API (schema 14+). `camera_get_capabilities`, `camera_start_stream`, `camera_stop_stream`
84
+ and `camera_snapshot` return dataclasses from `matter_server.common.models`; the other three return
85
+ `None`.
86
+ `camera_start_stream`'s `video` / `audio` take `None` (the server decides), a `CameraVideoHints` /
87
+ `CameraAudioHints` dict, or `False` (no such track). `talkback=True` lets the offer ask the camera to
88
+ play your audio; without it the server makes the offer's audio sections not send, so a browser-style
89
+ `sendrecv` audio offer goes to the camera as `recvonly`. `camera_session_ended` and
90
+ `camera_stream_evicted` events arrive as `CameraSessionEndedData` / `CameraStreamEvictedData`;
91
+ `webrtc_callback` stays a dict. Errors 102 to 106 (`CameraStreamIncompatible`, ...) expose their
92
+ details as attributes, e.g. `err.reason` and `err.bound` on `CameraStreamIncompatible`
93
+
94
+ The minimal `camera_start_stream` call is `node_id`, `endpoint_id`, `stream_usage` and your SDP
95
+ offer: the server reuses or allocates the best stream the camera can serve that still fits what
96
+ the offer can decode, and adds audio when it can. Pass `video` / `audio` hints only to set hard bounds. A
97
+ session opened this way is driven with `camera_provide_answer`, `camera_provide_ice_candidates`
98
+ and `camera_stop_stream` only, not with `send_webrtc_provider_command` or a `send_device_command`
99
+ `EndSession`: the server tracks the session and its streams, and only the `camera_*` methods keep
100
+ that record correct. The exception is a re-offer (ICE restart), which goes through
101
+ `send_webrtc_provider_command`. See `docs/websockets_api.md` for the flow and every option.
102
+
103
+ ```python
104
+ try:
105
+ result = await client.camera_start_stream(node_id, 1, "LiveView", sdp=offer)
106
+ except CameraStreamIncompatible as err:
107
+ print(err.reason, err.bound)
108
+ else:
109
+ # The camera's answer arrives as a webrtc_callback "answer" event for this session.
110
+ print(result.webrtc_session_id, result.video.codec if result.video else None)
111
+ ```
81
112
 
82
113
  ```python
83
114
  version = await client.upload_ota_file("/path/to/firmware.ota")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: matter-python-client
3
- Version: 1.5.0a0.dev20261009
3
+ Version: 1.5.0a0.dev20261014
4
4
  Summary: Python Client for the OHF Matter Server
5
5
  Author-email: Open Home Foundation <hello@openhomefoundation.io>
6
6
  License-Expression: Apache-2.0
@@ -110,6 +110,37 @@ Beyond the `python-matter-server` API, the client exposes commands the Matter.js
110
110
  then streams the bytes to `POST /ota-upload/<upload_id>`. Raises `OtaUploadError` (code 101) for a
111
111
  corrupt image, an expired/spent id, or disabled OTA support.
112
112
  - `get_icd_state`, `register_icd`, `unregister_icd`, `resync_icd` — ICD Check-In registration
113
+ - `camera_get_capabilities`, `camera_start_stream`, `camera_provide_answer`,
114
+ `camera_provide_ice_candidates`, `camera_stop_stream`, `camera_snapshot`, `camera_release_stream`
115
+ — the camera API (schema 14+). `camera_get_capabilities`, `camera_start_stream`, `camera_stop_stream`
116
+ and `camera_snapshot` return dataclasses from `matter_server.common.models`; the other three return
117
+ `None`.
118
+ `camera_start_stream`'s `video` / `audio` take `None` (the server decides), a `CameraVideoHints` /
119
+ `CameraAudioHints` dict, or `False` (no such track). `talkback=True` lets the offer ask the camera to
120
+ play your audio; without it the server makes the offer's audio sections not send, so a browser-style
121
+ `sendrecv` audio offer goes to the camera as `recvonly`. `camera_session_ended` and
122
+ `camera_stream_evicted` events arrive as `CameraSessionEndedData` / `CameraStreamEvictedData`;
123
+ `webrtc_callback` stays a dict. Errors 102 to 106 (`CameraStreamIncompatible`, ...) expose their
124
+ details as attributes, e.g. `err.reason` and `err.bound` on `CameraStreamIncompatible`
125
+
126
+ The minimal `camera_start_stream` call is `node_id`, `endpoint_id`, `stream_usage` and your SDP
127
+ offer: the server reuses or allocates the best stream the camera can serve that still fits what
128
+ the offer can decode, and adds audio when it can. Pass `video` / `audio` hints only to set hard bounds. A
129
+ session opened this way is driven with `camera_provide_answer`, `camera_provide_ice_candidates`
130
+ and `camera_stop_stream` only, not with `send_webrtc_provider_command` or a `send_device_command`
131
+ `EndSession`: the server tracks the session and its streams, and only the `camera_*` methods keep
132
+ that record correct. The exception is a re-offer (ICE restart), which goes through
133
+ `send_webrtc_provider_command`. See `docs/websockets_api.md` for the flow and every option.
134
+
135
+ ```python
136
+ try:
137
+ result = await client.camera_start_stream(node_id, 1, "LiveView", sdp=offer)
138
+ except CameraStreamIncompatible as err:
139
+ print(err.reason, err.bound)
140
+ else:
141
+ # The camera's answer arrives as a webrtc_callback "answer" event for this session.
142
+ print(result.webrtc_session_id, result.video.codec if result.video else None)
143
+ ```
113
144
 
114
145
  ```python
115
146
  version = await client.upload_ota_file("/path/to/firmware.ota")
@@ -186,6 +186,7 @@ matter_server/common/helpers/util.py
186
186
  tests/test_bigint_wire.py
187
187
  tests/test_bridge_node_fixture.py
188
188
  tests/test_bridge_topology.py
189
+ tests/test_camera.py
189
190
  tests/test_client.py
190
191
  tests/test_client_integration.py
191
192
  tests/test_credentials.py
@@ -3,6 +3,7 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import asyncio
6
+ from dataclasses import asdict
6
7
  import logging
7
8
  from pathlib import Path
8
9
  from typing import TYPE_CHECKING, Any, Final, Literal, Self, cast
@@ -25,6 +26,16 @@ from matter_server.common.helpers.util import (
25
26
  )
26
27
  from matter_server.common.models import (
27
28
  APICommand,
29
+ CameraAudioHints,
30
+ CameraCapabilities,
31
+ CameraIceServer,
32
+ CameraResolutionHint,
33
+ CameraSessionEndedData,
34
+ CameraSnapshotResult,
35
+ CameraStartStreamResult,
36
+ CameraStopStreamResult,
37
+ CameraStreamEvictedData,
38
+ CameraVideoHints,
28
39
  CommandMessage,
29
40
  CommissionableNodeData,
30
41
  CommissioningParameters,
@@ -43,6 +54,7 @@ from matter_server.common.models import (
43
54
  ServerDiagnostics,
44
55
  ServerInfoMessage,
45
56
  SuccessResultMessage,
57
+ WebRTCIceCandidate,
46
58
  )
47
59
 
48
60
  from .connection import MatterClientConnection
@@ -61,7 +73,7 @@ from .models.node import (
61
73
  )
62
74
 
63
75
  if TYPE_CHECKING:
64
- from collections.abc import Callable
76
+ from collections.abc import Callable, Sequence
65
77
  from os import PathLike
66
78
  from types import TracebackType
67
79
 
@@ -71,6 +83,25 @@ if TYPE_CHECKING:
71
83
 
72
84
  SUB_WILDCARD: Final = "*"
73
85
 
86
+ WebRtcProviderCommandName = Literal[
87
+ "ProvideOffer",
88
+ "SolicitOffer",
89
+ "ProvideAnswer",
90
+ "ProvideIceCandidates",
91
+ ]
92
+
93
+ # Relayed from schema 14; the offer commands need only schema 12.
94
+ SIGNALLING_PROVIDER_COMMANDS: Final = frozenset({"ProvideAnswer", "ProvideIceCandidates"})
95
+
96
+ # Payload carries node_id, so node_filter subscribers receive these events.
97
+ NODE_SCOPED_CAMERA_EVENTS: Final = frozenset(
98
+ {
99
+ EventType.WEBRTC_CALLBACK,
100
+ EventType.CAMERA_SESSION_ENDED,
101
+ EventType.CAMERA_STREAM_EVICTED,
102
+ }
103
+ )
104
+
74
105
  # pylint: disable=too-many-public-methods,too-many-locals,too-many-branches
75
106
 
76
107
 
@@ -718,23 +749,190 @@ class MatterClient:
718
749
  self,
719
750
  node_id: int,
720
751
  endpoint_id: int,
721
- command_name: Literal["ProvideOffer", "SolicitOffer"],
752
+ command_name: WebRtcProviderCommandName,
722
753
  payload: dict,
723
- ) -> dict[str, Any]:
754
+ ) -> dict[str, Any] | None:
724
755
  """Invoke a WebRTCTransportProvider command on a commissioned camera.
725
756
 
726
757
  The server hard-codes the cluster id (0x0553) and injects
727
758
  originatingEndpointId — payload should omit both.
759
+
760
+ ``ProvideOffer`` and ``SolicitOffer`` start a session and return the camera's response.
761
+ ``ProvideAnswer`` and ``ProvideIceCandidates`` act on an existing session, return ``None``
762
+ and need schema 14. For ``EndSession``, use ``camera_stop_stream``.
728
763
  """
729
764
  response = await self.send_command(
730
765
  APICommand.SEND_WEBRTC_PROVIDER_COMMAND,
731
- require_schema=12,
766
+ require_schema=14 if command_name in SIGNALLING_PROVIDER_COMMANDS else 12,
732
767
  node_id=node_id,
733
768
  endpoint_id=endpoint_id,
734
769
  command_name=command_name,
735
770
  payload=payload,
736
771
  )
737
- return cast(dict[str, Any], response)
772
+ return cast("dict[str, Any] | None", response)
773
+
774
+ async def camera_get_capabilities(self, node_id: int, endpoint_id: int) -> CameraCapabilities:
775
+ """Return what the camera states and what is allocated on it. Allocates nothing.
776
+
777
+ Requires schema 14.
778
+ """
779
+ data = await self.send_command(
780
+ APICommand.CAMERA_GET_CAPABILITIES, require_schema=14, node_id=node_id, endpoint_id=endpoint_id
781
+ )
782
+ return dataclass_from_dict(CameraCapabilities, data)
783
+
784
+ async def camera_start_stream( # noqa: PLR0913
785
+ self,
786
+ node_id: int,
787
+ endpoint_id: int,
788
+ stream_usage: str,
789
+ sdp: str | None = None,
790
+ video: CameraVideoHints | Literal[False] | None = None,
791
+ audio: CameraAudioHints | Literal[False] | None = None,
792
+ ice_servers: list[CameraIceServer] | None = None,
793
+ ice_transport_policy: str | None = None,
794
+ metadata_enabled: bool | None = None,
795
+ allow_eviction: bool | None = None,
796
+ talkback: bool | None = None,
797
+ ) -> CameraStartStreamResult:
798
+ """Open a WebRTC session, allocating or reusing the streams it needs.
799
+
800
+ Sends ``ProvideOffer`` when ``sdp`` is given, ``SolicitOffer`` otherwise; then the camera's
801
+ offer arrives as a ``webrtc_callback`` ``offer`` event. For ``video`` and ``audio``, ``None``
802
+ lets the server decide, a hints dict sets hard requirements, and ``False`` leaves the track
803
+ out. ``talkback=True`` (requires ``sdp``) forwards the offer unchanged so the camera can play
804
+ the caller's audio; otherwise the server makes the offer's audio sections not send. That only
805
+ asks the camera: it may still answer ``sendrecv``, so a caller that must not send audio keeps
806
+ the track off its audio sender. Arguments left ``None`` are not sent.
807
+
808
+ Requires schema 14.
809
+ """
810
+ optional = {
811
+ "sdp": sdp,
812
+ "video": video,
813
+ "audio": audio,
814
+ "ice_servers": ice_servers,
815
+ "ice_transport_policy": ice_transport_policy,
816
+ "metadata_enabled": metadata_enabled,
817
+ "allow_eviction": allow_eviction,
818
+ "talkback": talkback,
819
+ }
820
+ data = await self.send_command(
821
+ APICommand.CAMERA_START_STREAM,
822
+ require_schema=14,
823
+ node_id=node_id,
824
+ endpoint_id=endpoint_id,
825
+ stream_usage=stream_usage,
826
+ **{key: value for key, value in optional.items() if value is not None},
827
+ )
828
+ return dataclass_from_dict(CameraStartStreamResult, data)
829
+
830
+ async def camera_provide_answer(self, node_id: int, endpoint_id: int, webrtc_session_id: int, sdp: str) -> None:
831
+ """Send the SDP answer for a session started without ``sdp``.
832
+
833
+ Requires schema 14.
834
+ """
835
+ await self.send_command(
836
+ APICommand.CAMERA_PROVIDE_ANSWER,
837
+ require_schema=14,
838
+ node_id=node_id,
839
+ endpoint_id=endpoint_id,
840
+ webrtc_session_id=webrtc_session_id,
841
+ sdp=sdp,
842
+ )
843
+
844
+ async def camera_provide_ice_candidates(
845
+ self,
846
+ node_id: int,
847
+ endpoint_id: int,
848
+ webrtc_session_id: int,
849
+ ice_candidates: Sequence[WebRTCIceCandidate | dict[str, Any]],
850
+ ) -> None:
851
+ """Send one or more ICE candidates to an existing session.
852
+
853
+ A candidate from a ``webrtc_callback`` ``ice_candidates`` event can be passed unchanged.
854
+
855
+ Requires schema 14.
856
+ """
857
+ await self.send_command(
858
+ APICommand.CAMERA_PROVIDE_ICE_CANDIDATES,
859
+ require_schema=14,
860
+ node_id=node_id,
861
+ endpoint_id=endpoint_id,
862
+ webrtc_session_id=webrtc_session_id,
863
+ ice_candidates=[
864
+ asdict(candidate) if isinstance(candidate, WebRTCIceCandidate) else candidate
865
+ for candidate in ice_candidates
866
+ ],
867
+ )
868
+
869
+ async def camera_stop_stream(
870
+ self, node_id: int, endpoint_id: int, webrtc_session_id: int
871
+ ) -> CameraStopStreamResult:
872
+ """End a WebRTC session; the stream allocation is kept.
873
+
874
+ Requires schema 14.
875
+ """
876
+ data = await self.send_command(
877
+ APICommand.CAMERA_STOP_STREAM,
878
+ require_schema=14,
879
+ node_id=node_id,
880
+ endpoint_id=endpoint_id,
881
+ webrtc_session_id=webrtc_session_id,
882
+ )
883
+ return dataclass_from_dict(CameraStopStreamResult, data)
884
+
885
+ async def camera_snapshot(
886
+ self,
887
+ node_id: int,
888
+ endpoint_id: int,
889
+ max_resolution: CameraResolutionHint | None = None,
890
+ codec: str | None = None,
891
+ watermark_enabled: bool | None = None,
892
+ osd_enabled: bool | None = None,
893
+ ) -> CameraSnapshotResult:
894
+ """Capture one image. An argument left ``None`` is not sent.
895
+
896
+ The snapshot stream stays allocated; free it with ``camera_release_stream``.
897
+
898
+ Requires schema 14.
899
+ """
900
+ optional = {
901
+ "max_resolution": max_resolution,
902
+ "codec": codec,
903
+ "watermark_enabled": watermark_enabled,
904
+ "osd_enabled": osd_enabled,
905
+ }
906
+ data = await self.send_command(
907
+ APICommand.CAMERA_SNAPSHOT,
908
+ require_schema=14,
909
+ node_id=node_id,
910
+ endpoint_id=endpoint_id,
911
+ **{key: value for key, value in optional.items() if value is not None},
912
+ )
913
+ return dataclass_from_dict(CameraSnapshotResult, data)
914
+
915
+ async def camera_release_stream(
916
+ self,
917
+ node_id: int,
918
+ endpoint_id: int,
919
+ kind: Literal["video", "audio", "snapshot"],
920
+ stream_id: int,
921
+ ) -> None:
922
+ """Deallocate a stream, whoever allocated it.
923
+
924
+ Raises ``CameraStreamInUse`` while the camera still references the stream.
925
+
926
+ Requires schema 14.
927
+ """
928
+ await self.send_command(
929
+ APICommand.CAMERA_RELEASE_STREAM,
930
+ require_schema=14,
931
+ node_id=node_id,
932
+ endpoint_id=endpoint_id,
933
+ kind=kind,
934
+ stream_id=stream_id,
935
+ )
738
936
 
739
937
  def _prepare_message(
740
938
  self,
@@ -957,6 +1155,14 @@ class MatterClient:
957
1155
  topology = dataclass_from_dict(NetworkTopology, msg.data)
958
1156
  self._signal_event(EventType.NETWORK_TOPOLOGY_UPDATED, data=topology)
959
1157
  return
1158
+ if msg.event in NODE_SCOPED_CAMERA_EVENTS:
1159
+ payload: Any = msg.data
1160
+ if msg.event == EventType.CAMERA_SESSION_ENDED:
1161
+ payload = dataclass_from_dict(CameraSessionEndedData, msg.data)
1162
+ elif msg.event == EventType.CAMERA_STREAM_EVICTED:
1163
+ payload = dataclass_from_dict(CameraStreamEvictedData, msg.data)
1164
+ self._signal_event(msg.event, data=payload, node_id=msg.data["node_id"])
1165
+ return
960
1166
  # An event type unknown to this (older) client is passed through by parse_value as a raw
961
1167
  # string; forwarding it would crash on `event.value` in _signal_event. Drop it instead so a
962
1168
  # newer server emitting a new event type can never disconnect the client.
@@ -434,22 +434,70 @@ class MatterNode:
434
434
  def _map_endpoint_parents(self) -> None:
435
435
  """Resolve each endpoint to its closest parent endpoint, split by parent kind.
436
436
 
437
+ The parents come from the server's `endpoint_parents` when it sends them, else they are
438
+ derived from the partsLists (see `_derive_endpoint_parents`). Only the endpoints of the
439
+ last snapshot take part.
440
+
441
+ A snapshot replaces the relation of an endpoint where it names a parent for it that decides
442
+ the relation: one that bridges or composes it, or with server parents any parent that
443
+ reports device types. Derived parents are a guess, so one that makes the endpoint neither
444
+ bridged nor a part only replaces the relation when it is the parent the endpoint already
445
+ had. Where a snapshot decides nothing - the endpoint unlisted or unreported, or its parent
446
+ without a Descriptor or device types - the endpoint keeps its relation until its own
447
+ removal or that of the parent is announced, so a consumer handling the announcement still
448
+ finds the device the endpoint is a part of. A kept relation that would close a cycle with a
449
+ named one is dropped.
450
+ """
451
+ snapshot_ids = self._snapshot_endpoint_ids & self.endpoints.keys()
452
+ server_parents = self.node_data.endpoint_parents
453
+ if server_parents is None:
454
+ parents = self._derive_endpoint_parents(snapshot_ids)
455
+ else:
456
+ parents = {
457
+ child_id: parent_id
458
+ for child_id, parent_id in server_parents.items()
459
+ if child_id in snapshot_ids and (parent_id == ROOT_ENDPOINT_ID or parent_id in snapshot_ids)
460
+ }
461
+
462
+ previous_composed = self._composed_endpoints
463
+ previous_bridge = self._bridge_parents
464
+ self._composed_endpoints = {}
465
+ self._bridge_parents = {}
466
+ decided = {
467
+ child_id for child_id, parent_id in parents.items() if self._map_endpoint_parent(child_id, parent_id)
468
+ }
469
+ related = self._composed_endpoints | self._bridge_parents
470
+ for relations, previous in (
471
+ (self._composed_endpoints, previous_composed),
472
+ (self._bridge_parents, previous_bridge),
473
+ ):
474
+ for child_id, parent_id in sorted(previous.items()):
475
+ if child_id in related or (
476
+ child_id in decided and (server_parents is not None or parents[child_id] == parent_id)
477
+ ):
478
+ continue
479
+ if _reaches(parent_id, child_id, related):
480
+ LOGGER.warning(
481
+ "Dropping parent relation that a snapshot made cyclic: Node %s, endpoint %s below %s",
482
+ self.node_id,
483
+ child_id,
484
+ parent_id,
485
+ )
486
+ continue
487
+ relations[child_id] = parent_id
488
+ related[child_id] = parent_id
489
+
490
+ self._report_bridged_devices_without_info()
491
+
492
+ def _derive_endpoint_parents(self, snapshot_ids: set[int]) -> dict[int, int]:
493
+ """Derive the closest parent of each endpoint from the partsLists of the snapshot.
494
+
437
495
  An endpoint's partsList may enumerate its whole family - every descendant, not just
438
496
  the direct children - which is the pattern a bridge uses. Of the endpoints listing a
439
497
  given endpoint, the closest one is therefore the endpoint that lists none of the
440
498
  others - on contradictory lists the smaller family wins, then the lower endpoint
441
499
  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
500
  """
452
- snapshot_ids = self._snapshot_endpoint_ids & self.endpoints.keys()
453
501
  families: dict[int, set[int]] = {}
454
502
  for endpoint in self.endpoints.values():
455
503
  if endpoint.endpoint_id not in snapshot_ids:
@@ -494,34 +542,7 @@ class MatterNode:
494
542
  continue
495
543
  parents[child_id] = parent_id
496
544
  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()
545
+ return parents
525
546
 
526
547
  def _report_bridged_devices_without_info(self) -> None:
527
548
  """Warn about bridged devices the bridge does not describe, once per endpoint.
@@ -550,14 +571,13 @@ class MatterNode:
550
571
  bridged device; any other child stands alone, whether or not the Aggregator is itself a
551
572
  Bridged Node (so ids derived from the compose parent match older clients). Below any other endpoint, a child
552
573
  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.
574
+ so only a parent without a Descriptor or device types decides nothing.
554
575
  """
555
576
  if parent_id == ROOT_ENDPOINT_ID:
556
577
  return True
557
578
  parent = self.endpoints[parent_id]
558
579
  descriptor = parent.get_cluster(Clusters.Descriptor)
559
- assert descriptor is not None # only endpoints with a Descriptor are parents
560
- if not descriptor.deviceTypeList:
580
+ if descriptor is None or not descriptor.deviceTypeList:
561
581
  return False
562
582
  if RootNode in parent.device_types:
563
583
  return True