Compare commits

..

16 Commits

Author SHA1 Message Date
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
24 changed files with 469 additions and 112 deletions

View File

@@ -1,6 +1,6 @@
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.15.0
rev: v0.14.13
hooks:
- id: ruff-check
args:

View File

@@ -8,5 +8,5 @@
"iot_class": "cloud_polling",
"loggers": ["aioamazondevices"],
"quality_scale": "platinum",
"requirements": ["aioamazondevices==11.1.3"]
"requirements": ["aioamazondevices==11.1.1"]
}

View File

@@ -47,13 +47,6 @@ SENSOR_TYPES: dict[str, dict[str, SensorSpecs]] = {
0,
SensorDeviceClass.TEMPERATURE,
),
"FlowTemperature": (
"Hc1FlowTemp",
UnitOfTemperature.CELSIUS,
None,
0,
SensorDeviceClass.TEMPERATURE,
),
"PumpStatus": ("Hc1PumpStatus", None, "mdi:toggle-switch", 2, None),
"HCSummerTemperatureLimit": (
"Hc1SummerTempLimit",
@@ -134,13 +127,6 @@ SENSOR_TYPES: dict[str, dict[str, SensorSpecs]] = {
0,
SensorDeviceClass.TEMPERATURE,
),
"OutsideTemperature": (
"DisplayedOutsideTemp",
UnitOfTemperature.CELSIUS,
None,
0,
SensorDeviceClass.TEMPERATURE,
),
"Zone1TimerMonday": ("z1Timer.Monday", None, "mdi:timer-outline", 1, None),
"Zone1TimerTuesday": ("z1Timer.Tuesday", None, "mdi:timer-outline", 1, None),
"Zone1TimerWednesday": (
@@ -176,13 +162,6 @@ SENSOR_TYPES: dict[str, dict[str, SensorSpecs]] = {
0,
SensorDeviceClass.ENERGY,
),
"TotalEnergyConsumption": (
"PrEnergySum",
UnitOfEnergy.KILO_WATT_HOUR,
"mdi:flash",
0,
SensorDeviceClass.ENERGY,
),
},
"ehp": {
"HWTemperature": (

View File

@@ -358,6 +358,39 @@ DISCOVERY_SCHEMAS = [
entity_class=MatterNumber,
required_attributes=(clusters.OccupancySensing.Attributes.HoldTime,),
),
MatterDiscoverySchema(
platform=Platform.NUMBER,
entity_description=MatterNumberEntityDescription(
key="OccupancySensingUnoccupiedToOccupiedDelay",
entity_category=EntityCategory.CONFIG,
translation_key="unoccupied_to_occupied_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="OccupancySensingUnoccupiedToOccupiedThreshold",
entity_category=EntityCategory.CONFIG,
translation_key="unoccupied_to_occupied_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

@@ -247,6 +247,12 @@
"temperature_setpoint": {
"name": "Temperature setpoint"
},
"unoccupied_to_occupied_delay": {
"name": "Unoccupied to occupied delay"
},
"unoccupied_to_occupied_threshold": {
"name": "Unoccupied to occupied threshold"
},
"user_code_temporary_disable_time": {
"name": "User code temporary disable time"
},

View File

@@ -5280,7 +5280,7 @@ async def async_get_broker_settings( # noqa: C901
)
schema = vol.Schema({cv.string: cv.template})
schema(validated_user_input[CONF_WS_HEADERS])
except (*JSON_DECODE_EXCEPTIONS, vol.MultipleInvalid): # fmt: off
except (*JSON_DECODE_EXCEPTIONS, vol.MultipleInvalid):
errors["base"] = "bad_ws_headers"
return False
return True

View File

@@ -436,6 +436,7 @@ ENERGY_INFO_DESCRIPTIONS: tuple[SensorEntityDescription, ...] = (
SensorEntityDescription(
key="vpp_backup_reserve_percent",
entity_category=EntityCategory.DIAGNOSTIC,
device_class=SensorDeviceClass.BATTERY,
native_unit_of_measurement=PERCENTAGE,
),
SensorEntityDescription(key="version"),

View File

@@ -48,7 +48,6 @@ TUYA_HVAC_TO_HA = {
"heat": HVACMode.HEAT,
"hot": HVACMode.HEAT,
"manual": HVACMode.HEAT_COOL,
"off": HVACMode.OFF,
"wet": HVACMode.DRY,
"wind": HVACMode.FAN_ONLY,
}
@@ -443,9 +442,7 @@ class TuyaClimateEntity(TuyaEntity, ClimateEntity):
if hvac_mode_wrapper:
self._attr_hvac_modes = [HVACMode.OFF]
for mode in hvac_mode_wrapper.options:
if mode != HVACMode.OFF:
# OFF is always added first
self._attr_hvac_modes.append(HVACMode(mode))
self._attr_hvac_modes.append(HVACMode(mode))
elif switch_wrapper:
self._attr_hvac_modes = [

View File

@@ -233,5 +233,5 @@ async def async_get_installed_packages() -> list[InstalledPackage]:
try:
return cast(list[InstalledPackage], json_loads_array(stdout.decode()))
except (*JSON_DECODE_EXCEPTIONS, ValueError): # fmt: off
except (*JSON_DECODE_EXCEPTIONS, ValueError):
return []

View File

@@ -675,7 +675,7 @@ exclude_lines = [
]
[tool.ruff]
required-version = ">=0.15.0"
required-version = ">=0.14.13"
[tool.ruff.lint]
select = [
@@ -784,7 +784,6 @@ select = [
ignore = [
"ASYNC109", # Async function definition with a `timeout` parameter Use `asyncio.timeout` instead
"ASYNC110", # Use `asyncio.Event` instead of awaiting `asyncio.sleep` in a `while` loop
"ASYNC240", # Use an async function for entering the file system
"D202", # No blank lines allowed after function docstring
"D203", # 1 blank line required before class docstring
"D213", # Multi-line docstring summary should start at the second line
@@ -799,7 +798,6 @@ ignore = [
"PLR0913", # Too many arguments to function call ({c_args} > {max_args})
"PLR0915", # Too many statements ({statements} > {max_statements})
"PLR2004", # Magic value used in comparison, consider replacing {value} with a constant variable
"PLW0108", # Unnecessary lambda wrapping a function call; can often be replaced by the function itself
"PLW1641", # __eq__ without __hash__
"PLW2901", # Outer {outer_kind} variable {name} overwritten by inner {inner_kind} target
"PT011", # pytest.raises({exception}) is too broad, set the `match` parameter or use a more specific exception

2
requirements_all.txt generated
View File

@@ -190,7 +190,7 @@ aioairzone-cloud==0.7.2
aioairzone==1.0.5
# homeassistant.components.alexa_devices
aioamazondevices==11.1.3
aioamazondevices==11.1.1
# homeassistant.components.ambient_network
# homeassistant.components.ambient_station

View File

@@ -181,7 +181,7 @@ aioairzone-cloud==0.7.2
aioairzone==1.0.5
# homeassistant.components.alexa_devices
aioamazondevices==11.1.3
aioamazondevices==11.1.1
# homeassistant.components.ambient_network
# homeassistant.components.ambient_station

View File

@@ -1,5 +1,5 @@
# Automatically generated from .pre-commit-config.yaml by gen_requirements_all.py, do not edit
codespell==2.4.1
ruff==0.15.0
ruff==0.14.13
yamllint==1.37.1

View File

@@ -26,7 +26,7 @@ RUN --mount=from=ghcr.io/astral-sh/uv:0.9.26,source=/uv,target=/bin/uv \
-r /usr/src/homeassistant/requirements.txt \
pipdeptree==2.26.1 \
tqdm==4.67.1 \
ruff==0.15.0
ruff==0.14.13
LABEL "name"="hassfest"
LABEL "maintainer"="Home Assistant <hello@home-assistant.io>"

View File

@@ -14,7 +14,6 @@ from homeassistant.util.yaml import load_yaml_dict
from .model import Config, Integration, ScaledQualityScaleTiers
from .quality_scale_validation import (
RuleValidationProtocol,
action_setup,
config_entry_unloading,
config_flow,
diagnostics,
@@ -42,7 +41,7 @@ class Rule:
ALL_RULES = [
# BRONZE
Rule("action-setup", ScaledQualityScaleTiers.BRONZE, action_setup),
Rule("action-setup", ScaledQualityScaleTiers.BRONZE),
Rule("appropriate-polling", ScaledQualityScaleTiers.BRONZE),
Rule("brands", ScaledQualityScaleTiers.BRONZE),
Rule("common-modules", ScaledQualityScaleTiers.BRONZE),

View File

@@ -1,73 +0,0 @@
"""Enforce that the integration service actions are registered in async_setup.
https://developers.home-assistant.io/docs/core/integration-quality-scale/rules/action-setup/
"""
import ast
from script.hassfest import ast_parse_module
from script.hassfest.manifest import Platform
from script.hassfest.model import Config, Integration
def _get_setup_entry_function(module: ast.Module) -> ast.AsyncFunctionDef | None:
"""Get async_setup_entry function."""
for item in module.body:
if isinstance(item, ast.AsyncFunctionDef) and item.name == "async_setup_entry":
return item
return None
def _calls_service_registration(
async_setup_entry_function: ast.AsyncFunctionDef,
) -> bool:
"""Check if there are calls to service registration."""
for node in ast.walk(async_setup_entry_function):
if not (isinstance(node, ast.Call) and isinstance(node.func, ast.Attribute)):
continue
if node.func.attr == "async_register_entity_service":
return True
if (
isinstance(node.func.value, ast.Attribute)
and isinstance(node.func.value.value, ast.Name)
and node.func.value.value.id == "hass"
and node.func.value.attr == "services"
and node.func.attr in {"async_register", "register"}
):
return True
return False
def validate(
config: Config, integration: Integration, *, rules_done: set[str]
) -> list[str] | None:
"""Validate that service actions are registered in async_setup."""
errors = []
module_file = integration.path / "__init__.py"
module = ast_parse_module(module_file)
if (
async_setup_entry := _get_setup_entry_function(module)
) and _calls_service_registration(async_setup_entry):
errors.append(
f"Integration registers services in {module_file} (async_setup_entry)"
)
for platform in Platform:
module_file = integration.path / f"{platform}.py"
if not module_file.exists():
continue
module = ast_parse_module(module_file)
if (
async_setup_entry := _get_setup_entry_function(module)
) and _calls_service_registration(async_setup_entry):
errors.append(
f"Integration registers services in {module_file} (async_setup_entry)"
)
return errors

View File

@@ -122,6 +122,7 @@ async def integration_fixture(
"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

@@ -797,6 +797,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

@@ -2887,6 +2887,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

@@ -2523,6 +2523,123 @@
'state': 'unavailable',
})
# ---
# name: test_numbers[mock_occupancy_sensor_pir][number.mock_pir_occupancy_sensor_unoccupied_to_occupied_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_unoccupied_to_occupied_delay',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'Unoccupied to occupied delay',
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Unoccupied to occupied delay',
'platform': 'matter',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'unoccupied_to_occupied_delay',
'unique_id': '00000000000004D2-0000000000000001-MatterNodeDevice-1-OccupancySensingUnoccupiedToOccupiedDelay-1030-17',
'unit_of_measurement': <UnitOfTime.SECONDS: 's'>,
})
# ---
# name: test_numbers[mock_occupancy_sensor_pir][number.mock_pir_occupancy_sensor_unoccupied_to_occupied_delay-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Mock PIR Occupancy Sensor Unoccupied to occupied 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_unoccupied_to_occupied_delay',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '10',
})
# ---
# name: test_numbers[mock_occupancy_sensor_pir][number.mock_pir_occupancy_sensor_unoccupied_to_occupied_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_unoccupied_to_occupied_threshold',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'Unoccupied to occupied threshold',
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Unoccupied to occupied threshold',
'platform': 'matter',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'unoccupied_to_occupied_threshold',
'unique_id': '00000000000004D2-0000000000000001-MatterNodeDevice-1-OccupancySensingUnoccupiedToOccupiedThreshold-1030-18',
'unit_of_measurement': None,
})
# ---
# name: test_numbers[mock_occupancy_sensor_pir][number.mock_pir_occupancy_sensor_unoccupied_to_occupied_threshold-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Mock PIR Occupancy Sensor Unoccupied to occupied threshold',
'max': 254,
'min': 1,
'mode': <NumberMode.BOX: 'box'>,
'step': 1.0,
}),
'context': <ANY>,
'entity_id': 'number.mock_pir_occupancy_sensor_unoccupied_to_occupied_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,94 @@ 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_thresholds(
hass: HomeAssistant,
matter_client: MagicMock,
matter_node: MatterNode,
) -> None:
"""Test PIR threshold entities for OccupancySensing cluster."""
# PIRUnoccupiedToOccupiedThreshold - check actual entity created
state = hass.states.get(
"number.mock_pir_occupancy_sensor_unoccupied_to_occupied_threshold"
)
assert state
assert state.state == "1"
assert state.attributes["min"] == 1
assert state.attributes["max"] == 254
set_node_attribute(matter_node, 1, 1030, 0x12, 5)
await trigger_subscription_callback(hass, matter_client)
state = hass.states.get(
"number.mock_pir_occupancy_sensor_unoccupied_to_occupied_threshold"
)
assert state
assert state.state == "5"
# Test set value
await hass.services.async_call(
"number",
"set_value",
{
"entity_id": "number.mock_pir_occupancy_sensor_unoccupied_to_occupied_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,
)
@pytest.mark.parametrize("node_fixture", ["mock_occupancy_sensor_pir"])
async def test_occupancy_sensing_pir_delays(
hass: HomeAssistant,
matter_client: MagicMock,
matter_node: MatterNode,
) -> None:
"""Test PIR delay entities for OccupancySensing cluster."""
# PIRUnoccupiedToOccupiedDelay - check actual entity created
state = hass.states.get(
"number.mock_pir_occupancy_sensor_unoccupied_to_occupied_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, 0x11, 20)
await trigger_subscription_callback(hass, matter_client)
state = hass.states.get(
"number.mock_pir_occupancy_sensor_unoccupied_to_occupied_delay"
)
assert state
assert state.state == "20"
# Test set value
await hass.services.async_call(
"number",
"set_value",
{
"entity_id": "number.mock_pir_occupancy_sensor_unoccupied_to_occupied_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,
)

View File

@@ -2448,7 +2448,7 @@
'object_id_base': 'VPP backup reserve',
'options': dict({
}),
'original_device_class': None,
'original_device_class': <SensorDeviceClass.BATTERY: 'battery'>,
'original_icon': None,
'original_name': 'VPP backup reserve',
'platform': 'tesla_fleet',
@@ -2463,6 +2463,7 @@
# name: test_sensors[sensor.energy_site_vpp_backup_reserve-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'battery',
'friendly_name': 'Energy Site VPP backup reserve',
'unit_of_measurement': '%',
}),
@@ -2477,6 +2478,7 @@
# name: test_sensors[sensor.energy_site_vpp_backup_reserve-statealt]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'battery',
'friendly_name': 'Energy Site VPP backup reserve',
'unit_of_measurement': '%',
}),

View File

@@ -90,6 +90,7 @@
'preset_modes': list([
'auto',
'manual',
'off',
]),
'target_temp_step': 1.0,
}),
@@ -136,6 +137,7 @@
'preset_modes': list([
'auto',
'manual',
'off',
]),
'supported_features': <ClimateEntityFeature: 17>,
'target_temp_step': 1.0,
@@ -458,6 +460,7 @@
'preset_modes': list([
'auto',
'manual',
'off',
]),
'target_temp_step': 1.0,
}),
@@ -506,6 +509,7 @@
'preset_modes': list([
'auto',
'manual',
'off',
]),
'supported_features': <ClimateEntityFeature: 17>,
'target_temp_step': 1.0,
@@ -534,6 +538,7 @@
'preset_modes': list([
'auto',
'manual',
'off',
]),
'target_temp_step': 1.0,
}),
@@ -582,6 +587,7 @@
'preset_modes': list([
'auto',
'manual',
'off',
]),
'supported_features': <ClimateEntityFeature: 401>,
'target_temp_step': 1.0,