mirror of
https://github.com/home-assistant/core.git
synced 2026-02-07 07:44:50 +01:00
Compare commits
16 Commits
dev
...
PIRUnoccup
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1cdbe596fe | ||
|
|
a9d52bfbe7 | ||
|
|
6eed1f9961 | ||
|
|
149607ab17 | ||
|
|
279b5be357 | ||
|
|
82b93e788b | ||
|
|
555813f84f | ||
|
|
ecf1b4e591 | ||
|
|
e17a9f12a1 | ||
|
|
e8f05f5291 | ||
|
|
a5a76e9268 | ||
|
|
edc3fb47b2 | ||
|
|
f1e514a70a | ||
|
|
5632baca5b | ||
|
|
78f9bad706 | ||
|
|
3fdaaecd0f |
@@ -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:
|
||||
|
||||
@@ -8,5 +8,5 @@
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["aioamazondevices"],
|
||||
"quality_scale": "platinum",
|
||||
"requirements": ["aioamazondevices==11.1.3"]
|
||||
"requirements": ["aioamazondevices==11.1.1"]
|
||||
}
|
||||
|
||||
@@ -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": (
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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 = [
|
||||
|
||||
@@ -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 []
|
||||
|
||||
@@ -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
2
requirements_all.txt
generated
@@ -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
|
||||
|
||||
2
requirements_test_all.txt
generated
2
requirements_test_all.txt
generated
@@ -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
|
||||
|
||||
2
requirements_test_pre_commit.txt
generated
2
requirements_test_pre_commit.txt
generated
@@ -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
|
||||
|
||||
2
script/hassfest/docker/Dockerfile
generated
2
script/hassfest/docker/Dockerfile
generated
@@ -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>"
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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
|
||||
@@ -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",
|
||||
|
||||
@@ -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": []
|
||||
}
|
||||
@@ -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({
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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': '%',
|
||||
}),
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user