Compare commits

...

23 Commits

Author SHA1 Message Date
Ludovic BOUÉ
706127c9ea Add mock_occupancy_sensor_pir to common.py 2026-02-12 11:13:19 +01:00
Ludovic BOUÉ
b163829970 Merge branch 'dev' into PIRUnoccupiedToOccupiedDelay 2026-02-12 10:49:35 +01:00
Ludovic BOUÉ
7a93eb779c Merge branch 'dev' into PIRUnoccupiedToOccupiedDelay 2026-02-11 16:30:17 +01:00
Ludovic BOUÉ
7d673cd9c4 Update occupancy sensing PIR attributes for detection delay and threshold 2026-02-07 09:49:56 +00:00
Ludovic BOUÉ
44bc11580d Rename occupancy sensor attributes for clarity and update tests 2026-02-07 09:49:14 +00:00
Ludovic BOUÉ
c23795fe14 Rename occupancy sensing keys to include PIR prefix for clarity 2026-02-07 09:45:46 +00:00
Ludovic BOUÉ
bf6f9a011b Rename occupancy sensing translation keys and add new entries for detection delay and threshold 2026-02-07 09:44:30 +00:00
Ludovic BOUÉ
1cdbe596fe Update snapshots 2026-02-06 17:29:30 +00:00
Ludovic BOUÉ
a9d52bfbe7 Remove feature map attribute from occupancy sensing discovery schema 2026-02-06 17:24:51 +00:00
Ludovic BOUÉ
6eed1f9961 Update snapshots 2026-02-06 17:06:27 +00:00
Ludovic BOUÉ
149607ab17 Refactor strings.json: Remove duplicate unoccupied to occupied delay entries and standardize casing for threshold name 2026-02-06 17:04:42 +00:00
Ludovic BOUÉ
279b5be357 Add assertions for min, max, and unit_of_measurement in occupancy sensor tests 2026-02-06 17:02:19 +00:00
Ludovic BOUÉ
82b93e788b Update snapshots 2026-02-06 16:58:16 +00:00
Ludovic BOUÉ
555813f84f Move PIRUnoccupiedToOccupiedDelay before 2026-02-06 16:58:16 +00:00
Ludovic BOUÉ
ecf1b4e591 Fix occupancy sensor threshold test assertion to match updated mock data 2026-02-06 16:58:15 +00:00
Ludovic BOUÉ
e17a9f12a1 Rename occupancy sensor state and entity IDs for clarity in PIR tests 2026-02-06 16:58:15 +00:00
Ludovic BOUÉ
e8f05f5291 Update snapshots 2026-02-06 16:58:15 +00:00
Ludovic BOUÉ
a5a76e9268 Add mock occupancy sensor JSON fixture 2026-02-06 16:58:15 +00:00
Ludovic BOUÉ
edc3fb47b2 Réorganiser les chaînes pour le délai et le seuil de passage de l'état inoccupé à occupé 2026-02-06 16:58:15 +00:00
Ludovic BOUÉ
f1e514a70a Update homeassistant/components/matter/strings.json
Co-authored-by: Norbert Rittel <norbert@rittel.de>
2026-02-06 17:52:35 +01:00
Ludovic BOUÉ
5632baca5b Merge branch 'dev' into PIRUnoccupiedToOccupiedDelay 2026-02-06 17:08:24 +01:00
Ludovic BOUÉ
78f9bad706 PIRUnoccupiedToOccupiedDelay attribute 2026-02-06 16:00:18 +00:00
Ludovic BOUÉ
3fdaaecd0f PIRUnoccupiedToOccupied attributes 2026-02-04 13:01:13 +00:00
8 changed files with 433 additions and 0 deletions

View File

@@ -379,6 +379,39 @@ DISCOVERY_SCHEMAS = [
entity_class=MatterNumber,
required_attributes=(clusters.OccupancySensing.Attributes.HoldTime,),
),
MatterDiscoverySchema(
platform=Platform.NUMBER,
entity_description=MatterNumberEntityDescription(
key="OccupancySensingPIRUnoccupiedToOccupiedDelay",
entity_category=EntityCategory.CONFIG,
translation_key="detection_delay",
native_max_value=65534,
native_min_value=0,
native_unit_of_measurement=UnitOfTime.SECONDS,
mode=NumberMode.BOX,
),
entity_class=MatterNumber,
required_attributes=(
clusters.OccupancySensing.Attributes.PIRUnoccupiedToOccupiedDelay,
),
featuremap_contains=clusters.OccupancySensing.Bitmaps.Feature.kPassiveInfrared,
),
MatterDiscoverySchema(
platform=Platform.NUMBER,
entity_description=MatterNumberEntityDescription(
key="OccupancySensingPIRUnoccupiedToOccupiedThreshold",
entity_category=EntityCategory.CONFIG,
translation_key="detection_threshold",
native_max_value=254,
native_min_value=1,
mode=NumberMode.BOX,
),
entity_class=MatterNumber,
required_attributes=(
clusters.OccupancySensing.Attributes.PIRUnoccupiedToOccupiedThreshold,
),
featuremap_contains=clusters.OccupancySensing.Bitmaps.Feature.kPassiveInfrared,
),
MatterDiscoverySchema(
platform=Platform.NUMBER,
entity_description=MatterNumberEntityDescription(

View File

@@ -211,6 +211,12 @@
"cook_time": {
"name": "Cooking time"
},
"detection_delay": {
"name": "Detection delay"
},
"detection_threshold": {
"name": "Detection threshold"
},
"hold_time": {
"name": "Hold time"
},

View File

@@ -68,6 +68,7 @@ FIXTURES = [
"mock_microwave_oven",
"mock_mounted_dimmable_load_control_fixture",
"mock_occupancy_sensor",
"mock_occupancy_sensor_pir",
"mock_on_off_plugin_unit",
"mock_onoff_light",
"mock_onoff_light_alt_name",

View File

@@ -0,0 +1,100 @@
{
"node_id": 1,
"date_commissioned": "2022-11-29T21:23:48.485051",
"last_interview": "2022-11-29T21:23:48.485057",
"interview_version": 2,
"attributes": {
"0/29/0": [
{
"0": 22,
"1": 1
}
],
"0/29/1": [
4, 29, 31, 40, 42, 43, 44, 48, 49, 50, 51, 52, 53, 54, 55, 59, 60, 62, 63,
64, 65
],
"0/29/2": [41],
"0/29/3": [1],
"0/29/65532": 0,
"0/29/65533": 1,
"0/29/65528": [],
"0/29/65529": [],
"0/29/65531": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533],
"0/40/0": 1,
"0/40/1": "Nabu Casa",
"0/40/2": 65521,
"0/40/3": "Mock PIR Occupancy Sensor",
"0/40/4": 32768,
"0/40/5": "Mock PIR Occupancy Sensor",
"0/40/6": "XX",
"0/40/7": 0,
"0/40/8": "v1.0",
"0/40/9": 1,
"0/40/10": "v1.0",
"0/40/11": "20260206",
"0/40/12": "",
"0/40/13": "",
"0/40/14": "",
"0/40/15": "TEST_SN",
"0/40/16": false,
"0/40/17": true,
"0/40/18": "mock-pir-occupancy-sensor",
"0/40/19": {
"0": 3,
"1": 3
},
"0/40/65532": 0,
"0/40/65533": 1,
"0/40/65528": [],
"0/40/65529": [],
"0/40/65531": [
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
65528, 65529, 65531, 65532, 65533
],
"1/3/0": 0,
"1/3/1": 2,
"1/3/65532": 0,
"1/3/65533": 4,
"1/3/65528": [],
"1/3/65529": [0, 64],
"1/3/65531": [0, 1, 65528, 65529, 65531, 65532, 65533],
"1/29/0": [
{
"0": 263,
"1": 1
}
],
"1/29/1": [
3, 4, 5, 6, 7, 8, 15, 29, 30, 37, 47, 59, 64, 65, 69, 80, 257, 258, 259,
512, 513, 514, 516, 768, 1024, 1026, 1027, 1028, 1029, 1030, 1283, 1284,
1285, 1286, 1287, 1288, 1289, 1290, 1291, 1292, 1293, 1294, 2820,
4294048773
],
"1/29/2": [],
"1/29/3": [],
"1/29/65532": 0,
"1/29/65533": 1,
"1/29/65528": [],
"1/29/65529": [],
"1/29/65531": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533],
"1/30/0": [],
"1/30/65532": 0,
"1/30/65533": 1,
"1/30/65528": [],
"1/30/65529": [],
"1/30/65531": [0, 65528, 65529, 65531, 65532, 65533],
"1/1030/0": 1,
"1/1030/1": 0,
"1/1030/2": 1,
"1/1030/17": 10,
"1/1030/18": 1,
"1/1030/65532": 2,
"1/1030/65533": 3,
"1/1030/65528": [],
"1/1030/65529": [],
"1/1030/65531": [0, 1, 2, 17, 18, 65528, 65529, 65531, 65532, 65533]
},
"available": true,
"attribute_subscriptions": []
}

View File

@@ -1395,6 +1395,56 @@
'state': 'on',
})
# ---
# name: test_binary_sensors[mock_occupancy_sensor_pir][binary_sensor.mock_pir_occupancy_sensor_occupancy-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'binary_sensor',
'entity_category': None,
'entity_id': 'binary_sensor.mock_pir_occupancy_sensor_occupancy',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'Occupancy',
'options': dict({
}),
'original_device_class': <BinarySensorDeviceClass.OCCUPANCY: 'occupancy'>,
'original_icon': None,
'original_name': 'Occupancy',
'platform': 'matter',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': None,
'unique_id': '00000000000004D2-0000000000000001-MatterNodeDevice-1-OccupancySensor-1030-0',
'unit_of_measurement': None,
})
# ---
# name: test_binary_sensors[mock_occupancy_sensor_pir][binary_sensor.mock_pir_occupancy_sensor_occupancy-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'occupancy',
'friendly_name': 'Mock PIR Occupancy Sensor Occupancy',
}),
'context': <ANY>,
'entity_id': 'binary_sensor.mock_pir_occupancy_sensor_occupancy',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'on',
})
# ---
# name: test_binary_sensors[mock_onoff_light_alt_name][binary_sensor.mock_onoff_light_occupancy-entry]
EntityRegistryEntrySnapshot({
'aliases': set({

View File

@@ -3085,6 +3085,56 @@
'state': 'unknown',
})
# ---
# name: test_buttons[mock_occupancy_sensor_pir][button.mock_pir_occupancy_sensor_identify-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'button',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'button.mock_pir_occupancy_sensor_identify',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'Identify',
'options': dict({
}),
'original_device_class': <ButtonDeviceClass.IDENTIFY: 'identify'>,
'original_icon': None,
'original_name': 'Identify',
'platform': 'matter',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': None,
'unique_id': '00000000000004D2-0000000000000001-MatterNodeDevice-1-IdentifyButton-3-1',
'unit_of_measurement': None,
})
# ---
# name: test_buttons[mock_occupancy_sensor_pir][button.mock_pir_occupancy_sensor_identify-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'identify',
'friendly_name': 'Mock PIR Occupancy Sensor Identify',
}),
'context': <ANY>,
'entity_id': 'button.mock_pir_occupancy_sensor_identify',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
})
# ---
# name: test_buttons[mock_on_off_plugin_unit][button.mock_onoffpluginunit_identify-entry]
EntityRegistryEntrySnapshot({
'aliases': set({

View File

@@ -2583,6 +2583,123 @@
'state': 'unavailable',
})
# ---
# name: test_numbers[mock_occupancy_sensor_pir][number.mock_pir_occupancy_sensor_detection_delay-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'max': 65534,
'min': 0,
'mode': <NumberMode.BOX: 'box'>,
'step': 1.0,
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'number',
'entity_category': <EntityCategory.CONFIG: 'config'>,
'entity_id': 'number.mock_pir_occupancy_sensor_detection_delay',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'Detection delay',
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Detection delay',
'platform': 'matter',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'detection_delay',
'unique_id': '00000000000004D2-0000000000000001-MatterNodeDevice-1-OccupancySensingPIRUnoccupiedToOccupiedDelay-1030-17',
'unit_of_measurement': <UnitOfTime.SECONDS: 's'>,
})
# ---
# name: test_numbers[mock_occupancy_sensor_pir][number.mock_pir_occupancy_sensor_detection_delay-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Mock PIR Occupancy Sensor Detection delay',
'max': 65534,
'min': 0,
'mode': <NumberMode.BOX: 'box'>,
'step': 1.0,
'unit_of_measurement': <UnitOfTime.SECONDS: 's'>,
}),
'context': <ANY>,
'entity_id': 'number.mock_pir_occupancy_sensor_detection_delay',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '10',
})
# ---
# name: test_numbers[mock_occupancy_sensor_pir][number.mock_pir_occupancy_sensor_detection_threshold-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'max': 254,
'min': 1,
'mode': <NumberMode.BOX: 'box'>,
'step': 1.0,
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'number',
'entity_category': <EntityCategory.CONFIG: 'config'>,
'entity_id': 'number.mock_pir_occupancy_sensor_detection_threshold',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'Detection threshold',
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Detection threshold',
'platform': 'matter',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'detection_threshold',
'unique_id': '00000000000004D2-0000000000000001-MatterNodeDevice-1-OccupancySensingPIRUnoccupiedToOccupiedThreshold-1030-18',
'unit_of_measurement': None,
})
# ---
# name: test_numbers[mock_occupancy_sensor_pir][number.mock_pir_occupancy_sensor_detection_threshold-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Mock PIR Occupancy Sensor Detection threshold',
'max': 254,
'min': 1,
'mode': <NumberMode.BOX: 'box'>,
'step': 1.0,
}),
'context': <ANY>,
'entity_id': 'number.mock_pir_occupancy_sensor_detection_threshold',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '1',
})
# ---
# name: test_numbers[mock_on_off_plugin_unit][number.mock_onoffpluginunit_off_transition_time-entry]
EntityRegistryEntrySnapshot({
'aliases': set({

View File

@@ -333,3 +333,79 @@ async def test_matter_exception_on_door_lock_write_attribute(
)
assert str(exc_info.value) == "Boom!"
@pytest.mark.parametrize("node_fixture", ["mock_occupancy_sensor_pir"])
async def test_occupancy_sensing_pir_attributes(
hass: HomeAssistant,
matter_client: MagicMock,
matter_node: MatterNode,
) -> None:
"""Test PIR occupancy sensor attributes."""
# PIRUnoccupiedToOccupiedDelay
state = hass.states.get("number.mock_pir_occupancy_sensor_detection_delay")
assert state
assert state.state == "10"
assert state.attributes["min"] == 0
assert state.attributes["max"] == 65534
assert state.attributes["unit_of_measurement"] == "s"
set_node_attribute(matter_node, 1, 1030, 17, 20)
await trigger_subscription_callback(hass, matter_client)
state = hass.states.get("number.mock_pir_occupancy_sensor_detection_delay")
assert state
assert state.state == "20"
# PIRUnoccupiedToOccupiedThreshold
state = hass.states.get("number.mock_pir_occupancy_sensor_detection_threshold")
assert state
assert state.state == "1"
assert state.attributes["min"] == 1
assert state.attributes["max"] == 254
set_node_attribute(matter_node, 1, 1030, 18, 5)
await trigger_subscription_callback(hass, matter_client)
state = hass.states.get("number.mock_pir_occupancy_sensor_detection_threshold")
assert state
assert state.state == "5"
# Test set value for delay
await hass.services.async_call(
"number",
"set_value",
{
"entity_id": "number.mock_pir_occupancy_sensor_detection_delay",
"value": 15,
},
blocking=True,
)
assert matter_client.write_attribute.call_count == 1
assert matter_client.write_attribute.call_args_list[0] == call(
node_id=matter_node.node_id,
attribute_path=create_attribute_path_from_attribute(
endpoint_id=1,
attribute=clusters.OccupancySensing.Attributes.PIRUnoccupiedToOccupiedDelay,
),
value=15,
)
# Test set value for threshold
matter_client.write_attribute.reset_mock()
await hass.services.async_call(
"number",
"set_value",
{
"entity_id": "number.mock_pir_occupancy_sensor_detection_threshold",
"value": 3,
},
blocking=True,
)
assert matter_client.write_attribute.call_count == 1
assert matter_client.write_attribute.call_args_list[0] == call(
node_id=matter_node.node_id,
attribute_path=create_attribute_path_from_attribute(
endpoint_id=1,
attribute=clusters.OccupancySensing.Attributes.PIRUnoccupiedToOccupiedThreshold,
),
value=3,
)