Merge branch 'dev' into ma_identify

This commit is contained in:
Ludovic BOUÉ
2025-11-10 22:15:06 +01:00
committed by GitHub
16 changed files with 1205 additions and 102 deletions

View File

@@ -4,6 +4,7 @@ import logging
from typing import TYPE_CHECKING
from aiopvapi.resources.model import PowerviewData
from aiopvapi.resources.shade_data import PowerviewShadeData
from aiopvapi.rooms import Rooms
from aiopvapi.scenes import Scenes
from aiopvapi.shades import Shades
@@ -16,7 +17,6 @@ from homeassistant.helpers import device_registry as dr, entity_registry as er
from .const import DOMAIN, HUB_EXCEPTIONS, MANUFACTURER
from .coordinator import PowerviewShadeUpdateCoordinator
from .model import PowerviewConfigEntry, PowerviewEntryData
from .shade_data import PowerviewShadeData
from .util import async_connect_hub
PARALLEL_UPDATES = 1

View File

@@ -8,6 +8,7 @@ import logging
from aiopvapi.helpers.aiorequest import PvApiMaintenance
from aiopvapi.hub import Hub
from aiopvapi.resources.shade_data import PowerviewShadeData
from aiopvapi.shades import Shades
from homeassistant.config_entries import ConfigEntry
@@ -15,7 +16,6 @@ from homeassistant.core import HomeAssistant
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from .const import HUB_EXCEPTIONS
from .shade_data import PowerviewShadeData
_LOGGER = logging.getLogger(__name__)

View File

@@ -208,13 +208,13 @@ class PowerViewShadeBase(ShadeEntity, CoverEntity):
async def _async_execute_move(self, move: ShadePosition) -> None:
"""Execute a move that can affect multiple positions."""
_LOGGER.debug("Move request %s: %s", self.name, move)
# Store the requested positions so subsequent move
# requests contain the secondary shade positions
self.data.update_shade_position(self._shade.id, move)
async with self.coordinator.radio_operation_lock:
response = await self._shade.move(move)
_LOGGER.debug("Move response %s: %s", self.name, response)
# Process the response from the hub (including new positions)
self.data.update_shade_position(self._shade.id, response)
async def _async_set_cover_position(self, target_hass_position: int) -> None:
"""Move the shade to a position."""
target_hass_position = self._clamp_cover_limit(target_hass_position)

View File

@@ -3,6 +3,7 @@
import logging
from aiopvapi.resources.shade import BaseShade, ShadePosition
from aiopvapi.resources.shade_data import PowerviewShadeData
from homeassistant.helpers import device_registry as dr
from homeassistant.helpers.device_registry import DeviceInfo
@@ -11,7 +12,6 @@ from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import DOMAIN, MANUFACTURER
from .coordinator import PowerviewShadeUpdateCoordinator
from .model import PowerviewDeviceInfo
from .shade_data import PowerviewShadeData
_LOGGER = logging.getLogger(__name__)

View File

@@ -1,80 +0,0 @@
"""Shade data for the Hunter Douglas PowerView integration."""
from __future__ import annotations
from dataclasses import fields
from typing import Any
from aiopvapi.resources.model import PowerviewData
from aiopvapi.resources.shade import BaseShade, ShadePosition
from .util import async_map_data_by_id
POSITION_FIELDS = [field for field in fields(ShadePosition) if field.name != "velocity"]
def copy_position_data(source: ShadePosition, target: ShadePosition) -> ShadePosition:
"""Copy position data from source to target for None values only."""
for field in POSITION_FIELDS:
if (value := getattr(source, field.name)) is not None:
setattr(target, field.name, value)
class PowerviewShadeData:
"""Coordinate shade data between multiple api calls."""
def __init__(self) -> None:
"""Init the shade data."""
self._raw_data_by_id: dict[int, dict[str | int, Any]] = {}
self._shade_group_data_by_id: dict[int, BaseShade] = {}
self.positions: dict[int, ShadePosition] = {}
def get_raw_data(self, shade_id: int) -> dict[str | int, Any]:
"""Get data for the shade."""
return self._raw_data_by_id[shade_id]
def get_all_raw_data(self) -> dict[int, dict[str | int, Any]]:
"""Get data for all shades."""
return self._raw_data_by_id
def get_shade(self, shade_id: int) -> BaseShade:
"""Get specific shade from the coordinator."""
return self._shade_group_data_by_id[shade_id]
def get_shade_position(self, shade_id: int) -> ShadePosition:
"""Get positions for a shade."""
if shade_id not in self.positions:
shade_position = ShadePosition()
# If we have the group data, use it to populate the initial position
if shade := self._shade_group_data_by_id.get(shade_id):
copy_position_data(shade.current_position, shade_position)
self.positions[shade_id] = shade_position
return self.positions[shade_id]
def update_from_group_data(self, shade_id: int) -> None:
"""Process an update from the group data."""
data = self._shade_group_data_by_id[shade_id]
copy_position_data(data.current_position, self.get_shade_position(data.id))
def store_group_data(self, shade_data: PowerviewData) -> None:
"""Store data from the all shades endpoint.
This does not update the shades or positions (self.positions)
as the data may be stale. update_from_group_data
with a shade_id will update a specific shade
from the group data.
"""
self._shade_group_data_by_id = shade_data.processed
self._raw_data_by_id = async_map_data_by_id(shade_data.raw)
def update_shade_position(self, shade_id: int, new_position: ShadePosition) -> None:
"""Update a single shades position."""
copy_position_data(new_position, self.get_shade_position(shade_id))
def update_shade_velocity(self, shade_id: int, shade_data: ShadePosition) -> None:
"""Update a single shades velocity."""
# the hub will always return a velocity of 0 on initial connect,
# separate definition to store consistent value in HA
# this value is purely driven from HA
if shade_data.velocity is not None:
self.get_shade_position(shade_id).velocity = shade_data.velocity

View File

@@ -2,25 +2,15 @@
from __future__ import annotations
from collections.abc import Iterable
from typing import Any
from aiopvapi.helpers.aiorequest import AioRequest
from aiopvapi.helpers.constants import ATTR_ID
from aiopvapi.hub import Hub
from homeassistant.core import HomeAssistant, callback
from homeassistant.core import HomeAssistant
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from .model import PowerviewAPI, PowerviewDeviceInfo
@callback
def async_map_data_by_id(data: Iterable[dict[str | int, Any]]):
"""Return a dict with the key being the id for a list of entries."""
return {entry[ATTR_ID]: entry for entry in data}
async def async_connect_hub(
hass: HomeAssistant, address: str, api_version: int | None = None
) -> PowerviewAPI:

View File

@@ -1009,7 +1009,7 @@
"cleaning_care_program": "Cleaning/care program",
"maintenance_program": "Maintenance program",
"normal_operation_mode": "Normal operation mode",
"own_program": "Own program"
"own_program": "Program"
}
},
"remaining_time": {

View File

@@ -14,7 +14,7 @@
"velbus-protocol"
],
"quality_scale": "bronze",
"requirements": ["velbus-aio==2025.8.0"],
"requirements": ["velbus-aio==2025.11.0"],
"usb": [
{
"pid": "0B1B",

View File

@@ -1304,7 +1304,11 @@ def issues(hass: HomeAssistant) -> dict[tuple[str, str], dict[str, Any]]:
"""Return all open issues."""
current_issues = ir.async_get(hass).issues
# Use JSON for safe representation
return {k: v.to_json() for (k, v) in current_issues.items()}
return {
key: issue_entry.to_json()
for (key, issue_entry) in current_issues.items()
if issue_entry.active
}
def issue(hass: HomeAssistant, domain: str, issue_id: str) -> dict[str, Any] | None:

2
requirements_all.txt generated
View File

@@ -3076,7 +3076,7 @@ vegehub==0.1.26
vehicle==2.2.2
# homeassistant.components.velbus
velbus-aio==2025.8.0
velbus-aio==2025.11.0
# homeassistant.components.venstar
venstarcolortouch==0.21

View File

@@ -2543,7 +2543,7 @@ vegehub==0.1.26
vehicle==2.2.2
# homeassistant.components.velbus
velbus-aio==2025.8.0
velbus-aio==2025.11.0
# homeassistant.components.venstar
venstarcolortouch==0.21

View File

@@ -79,6 +79,7 @@ async def integration_fixture(
"aqara_door_window_p2",
"aqara_motion_p2",
"aqara_presence_fp300",
"aqara_sensor_w100",
"aqara_thermostat_w500",
"aqara_u200",
"battery_storage",

View File

@@ -0,0 +1,528 @@
{
"node_id": 75,
"date_commissioned": "2025-06-07T15:30:15.263101",
"last_interview": "2025-06-07T15:30:15.263113",
"interview_version": 6,
"available": true,
"is_bridge": false,
"attributes": {
"0/29/0": [
{
"0": 18,
"1": 1
},
{
"0": 22,
"1": 3
}
],
"0/29/1": [29, 31, 40, 42, 48, 49, 51, 52, 53, 60, 62, 63, 70],
"0/29/2": [41],
"0/29/3": [1, 2, 3, 4, 5, 6],
"0/29/65532": 0,
"0/29/65533": 2,
"0/29/65528": [],
"0/29/65529": [],
"0/29/65531": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533],
"0/31/0": [
{
"1": 5,
"2": 2,
"3": [112233],
"4": null,
"254": 4
}
],
"0/31/1": [],
"0/31/2": 4,
"0/31/3": 3,
"0/31/4": 4,
"0/31/65532": 0,
"0/31/65533": 1,
"0/31/65528": [],
"0/31/65529": [],
"0/31/65531": [0, 1, 2, 3, 4, 65528, 65529, 65531, 65532, 65533],
"0/40/0": 17,
"0/40/1": "Aqara",
"0/40/2": 4447,
"0/40/3": "Aqara Climate Sensor W100",
"0/40/4": 8196,
"0/40/5": "Climate Sensor W100",
"0/40/6": "**REDACTED**",
"0/40/7": 12,
"0/40/8": "0.0.1.2",
"0/40/9": 1010,
"0/40/10": "1.0.1.0",
"0/40/11": "20250108",
"0/40/12": "AA016",
"0/40/13": "https://www.aqara.com/en/products.html",
"0/40/14": "Aqara Climate Sensor W100",
"0/40/15": "***************",
"0/40/16": false,
"0/40/18": "***************",
"0/40/19": {
"0": 3,
"1": 3
},
"0/40/21": 16973824,
"0/40/22": 1,
"0/40/65532": 0,
"0/40/65533": 3,
"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, 18, 19, 21, 22,
65528, 65529, 65531, 65532, 65533
],
"0/42/0": [],
"0/42/1": true,
"0/42/2": 1,
"0/42/3": null,
"0/42/65532": 0,
"0/42/65533": 1,
"0/42/65528": [],
"0/42/65529": [0],
"0/42/65531": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533],
"0/48/0": 0,
"0/48/1": {
"0": 60,
"1": 900
},
"0/48/2": 0,
"0/48/3": 0,
"0/48/4": true,
"0/48/65532": 0,
"0/48/65533": 1,
"0/48/65528": [1, 3, 5],
"0/48/65529": [0, 2, 4],
"0/48/65531": [0, 1, 2, 3, 4, 65528, 65529, 65531, 65532, 65533],
"0/49/0": 1,
"0/49/1": [
{
"0": "aFq/aOcqMFo=",
"1": true
}
],
"0/49/2": 10,
"0/49/3": 20,
"0/49/4": true,
"0/49/5": 0,
"0/49/6": "aFq/aOcqMFo=",
"0/49/7": null,
"0/49/9": 4,
"0/49/10": 4,
"0/49/65532": 2,
"0/49/65533": 2,
"0/49/65528": [1, 5, 7],
"0/49/65529": [0, 3, 4, 6, 8],
"0/49/65531": [
0, 1, 2, 3, 4, 5, 6, 7, 9, 10, 65528, 65529, 65531, 65532, 65533
],
"0/51/0": [
{
"0": "AqaraHome-0123",
"1": true,
"2": null,
"3": null,
"4": "piylcw37nWM=",
"5": [],
"6": [
"/RXRKakLAAFKcohVnCFKow==",
"/Z4/qUibGFsAAAD//gAcAg==",
"/Z4/qUibGFsYCaOd1Hp6Vg==",
"/oAAAAAAAACkLKVzDfudYw=="
],
"7": 4
}
],
"0/51/1": 1,
"0/51/2": 299,
"0/51/4": 6,
"0/51/5": [],
"0/51/8": false,
"0/51/65532": 0,
"0/51/65533": 2,
"0/51/65528": [2],
"0/51/65529": [0, 1],
"0/51/65531": [0, 1, 2, 4, 5, 8, 65528, 65529, 65531, 65532, 65533],
"0/52/0": [
{
"0": 2,
"1": "sys_evt",
"3": 1952
},
{
"0": 11,
"1": "Bluetoot",
"3": 1438
},
{
"0": 3,
"1": "THREAD",
"3": 1651
},
{
"0": 1,
"1": "Bluetoot",
"3": 306
},
{
"0": 10,
"1": "Bluetoot",
"3": 107
},
{
"0": 7,
"1": "Tmr Svc",
"3": 943
},
{
"0": 8,
"1": "app",
"3": 748
},
{
"0": 6,
"1": "IDLE",
"3": 231
},
{
"0": 4,
"1": "CHIP",
"3": 305
}
],
"0/52/1": 46224,
"0/52/2": 35696,
"0/52/3": 56048,
"0/52/65532": 1,
"0/52/65533": 1,
"0/52/65528": [],
"0/52/65529": [0],
"0/52/65531": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533],
"0/53/0": 11,
"0/53/1": 2,
"0/53/2": "AqaraHome-0123",
"0/53/3": 23343,
"0/53/4": 7519532985124270170,
"0/53/5": "QP2eP6lImxhb",
"0/53/6": 0,
"0/53/7": [
{
"0": 17151429082474872369,
"1": 284,
"2": 7168,
"3": 295817,
"4": 111774,
"5": 3,
"6": -74,
"7": -74,
"8": 37,
"9": 0,
"10": true,
"11": true,
"12": true,
"13": false
}
],
"0/53/8": [
{
"0": 17151429082474872369,
"1": 7168,
"2": 7,
"3": 0,
"4": 0,
"5": 3,
"6": 3,
"7": 28,
"8": true,
"9": true
}
],
"0/53/9": 405350277,
"0/53/22": 2799,
"0/53/23": 2797,
"0/53/24": 2,
"0/53/39": 503,
"0/53/40": 503,
"0/53/41": 0,
"0/53/65532": 15,
"0/53/65533": 2,
"0/53/65528": [],
"0/53/65529": [0],
"0/53/65531": [
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 22, 23, 24, 39, 40, 41, 65528, 65529, 65531,
65532, 65533
],
"0/60/0": 0,
"0/60/1": null,
"0/60/2": null,
"0/60/65532": 1,
"0/60/65533": 1,
"0/60/65528": [],
"0/60/65529": [0, 1, 2],
"0/60/65531": [0, 1, 2, 65528, 65529, 65531, 65532, 65533],
"0/62/0": [
{
"1": "FTABAQEkAgE3AyQTAhgmBIAigScmBYAlTTo3BiQVAiQRSxgkBwEkCAEwCUEEL5gmAVxeNTcndwbt1d1SNaICqrmw8Mk3fQ7CkQlM0XhpLv0XzjnnmI+jorFA31RvWDYa0URByx588JSq6G/d7DcKNQEoARgkAgE2AwQCBAEYMAQUPES5ZFkTssoDCAkEz+kBgkL3jMcwBRRT9HTfU5Nds+HA8j+/MRP+0pVyIxgwC0B5OoI+cs5wwGlxvfMdinguUmA+VEWBZjQP6rEvd929qf4zpgpkfyjX7LFYCvoqqKJCOW052dLhgfYGUOqCfo7AGA==",
"2": "FTABAQEkAgE3AyQUARgmBIAigScmBYAlTTo3BiQTAhgkBwEkCAEwCUEEyT62Yt4qMI+MorlmQ/Hxh2CpLetznVknlAbhvYAwTexpSxp9GnhR09SrcUhz3mOb0eZa2TylqcnPBhHJ2Ih2RTcKNQEpARgkAmAwBBRT9HTfU5Nds+HA8j+/MRP+0pVyIzAFFOMCO8Jk7ZCknJquFGPtPzJiNqsDGDALQI/Kc38hQyK7AkT7/pN4hiYW3LoWKT3NA43+ssMJoVpDcaZ989GXBQKIbHKbBEXzUQ1J8wfL7l2pL0Z8Lso9JwgY",
"254": 4
}
],
"0/62/1": [
{
"1": "BIrruNo7r0gX6j6lq1dDi5zeK3jxcTavjt2o4adCCSCYtbxOakfb7C3GXqgV4LzulFSinbewmYkdqFBHqm5pxvU=",
"2": 4939,
"3": 2,
"4": 75,
"5": "",
"254": 4
}
],
"0/62/2": 5,
"0/62/3": 4,
"0/62/4": [
"FTABAQAkAgE3AyYUyakYCSYVj6gLsxgmBGoW1y8kBQA3BiYUyakYCSYVj6gLsxgkBwEkCAEwCUEEgYwxrTB+tyiEGfrRwjlXTG34MiQtJXbg5Qqd0ohdRW7MfwYY7vZiX/0h9hI8MqUralFaVPcnghAP0MSJm1YrqTcKNQEpARgkAmAwBBS3BS9aJzt+p6i28Nj+trB2Uu+vdzAFFLcFL1onO36nqLbw2P62sHZS7693GDALQMvassZTgvO/snCPohEojdKdGb2IpuRpSsu4HkM1JJQ9yFwhkyl0OOS2kvOVUNlfb2YnoJaH4L2jz0G9GVclBIgY",
"FTABAQAkAgE3AycUQhmZbaIbYjokFQIYJgRWZLcqJAUANwYnFEIZmW2iG2I6JBUCGCQHASQIATAJQQT2AlKGW/kOMjqayzeO0md523/fuhrhGEUU91uQpTiKo0I7wcPpKnmrwfQNPX6g0kEQl+VGaXa3e22lzfu5Tzp0Nwo1ASkBGCQCYDAEFOOMk13ScMKuT2hlaydi1yEJnhTqMAUU44yTXdJwwq5PaGVrJ2LXIQmeFOoYMAtAv2jJd1qd5miXbYesH1XrJ+vgyY0hzGuZ78N6Jw4Cb1oN1sLSpA+PNM0u7+hsEqcSvvn2eSV8EaRR+hg5YQjHDxg=",
"FTABD38O1NiPyscyxScZaN7uECQCATcDJhSoQfl2GCYEIqqfLyYFImy36zcGJhSoQfl2GCQHASQIATAJQQT5WrI2v6EgLRXdxlmZLlXX3rxeBe1C3NN/x9QV0tMVF+gH/FPSyq69dZKuoyskx0UOHcN20wdPffFuqgy/4uiaNwo1ASkBGCQCYDAEFM8XoLF/WKnSeqflSO5TQBQz4ObIMAUUzxegsX9YqdJ6p+VI7lNAFDPg5sgYMAtAHTWpsQPPwqR9gCqBGcDbPu2gusKeVuytcD5v7qK1/UjVr2/WGjMw3SYM10HWKdPTQZa2f3JI3uxv1nFnlcQpDBg=",
"FTABAQEkAgE3AyQUARgmBIAigScmBYAlTTo3BiQUARgkBwEkCAEwCUEEiuu42juvSBfqPqWrV0OLnN4rePFxNq+O3ajhp0IJIJi1vE5qR9vsLcZeqBXgvO6UVKKdt7CZiR2oUEeqbmnG9TcKNQEpARgkAmAwBBTjAjvCZO2QpJyarhRj7T8yYjarAzAFFOMCO8Jk7ZCknJquFGPtPzJiNqsDGDALQE7hTxTRg92QOxwA1hK3xv8DaxvxL71r6ZHcNRzug9wNnonJ+NC84SFKvKDxwcBxHYqFdIyDiDgwJNTQIBgasmIY"
],
"0/62/5": 4,
"0/62/65532": 0,
"0/62/65533": 1,
"0/62/65528": [1, 3, 5, 8],
"0/62/65529": [0, 2, 4, 6, 7, 9, 10, 11],
"0/62/65531": [0, 1, 2, 3, 4, 5, 65528, 65529, 65531, 65532, 65533],
"0/63/0": [],
"0/63/1": [],
"0/63/2": 4,
"0/63/3": 3,
"0/63/65532": 0,
"0/63/65533": 2,
"0/63/65528": [2, 5],
"0/63/65529": [0, 1, 3, 4],
"0/63/65531": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533],
"0/70/0": 300,
"0/70/1": 0,
"0/70/2": 1000,
"0/70/65532": 0,
"0/70/65533": 2,
"0/70/65528": [],
"0/70/65529": [],
"0/70/65531": [0, 1, 2, 65528, 65529, 65531, 65532, 65533],
"1/3/0": 0,
"1/3/1": 4,
"1/3/65532": 0,
"1/3/65533": 4,
"1/3/65528": [],
"1/3/65529": [0],
"1/3/65531": [0, 1, 65528, 65529, 65531, 65532, 65533],
"1/29/0": [
{
"0": 770,
"1": 1
}
],
"1/29/1": [3, 29, 1026],
"1/29/2": [],
"1/29/3": [],
"1/29/65532": 0,
"1/29/65533": 2,
"1/29/65528": [],
"1/29/65529": [],
"1/29/65531": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533],
"1/1026/0": 2773,
"1/1026/1": -4000,
"1/1026/2": 12500,
"1/1026/65532": 0,
"1/1026/65533": 4,
"1/1026/65528": [],
"1/1026/65529": [],
"1/1026/65531": [0, 1, 2, 65528, 65529, 65531, 65532, 65533],
"2/3/0": 0,
"2/3/1": 4,
"2/3/65532": 0,
"2/3/65533": 4,
"2/3/65528": [],
"2/3/65529": [0],
"2/3/65531": [0, 1, 65528, 65529, 65531, 65532, 65533],
"2/29/0": [
{
"0": 775,
"1": 1
}
],
"2/29/1": [3, 29, 1029],
"2/29/2": [],
"2/29/3": [],
"2/29/65532": 0,
"2/29/65533": 2,
"2/29/65528": [],
"2/29/65529": [],
"2/29/65531": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533],
"2/1029/0": 4472,
"2/1029/1": 0,
"2/1029/2": 10000,
"2/1029/65532": 0,
"2/1029/65533": 3,
"2/1029/65528": [],
"2/1029/65529": [],
"2/1029/65531": [0, 1, 2, 65528, 65529, 65531, 65532, 65533],
"3/3/0": 0,
"3/3/1": 4,
"3/3/65532": 0,
"3/3/65533": 4,
"3/3/65528": [],
"3/3/65529": [0],
"3/3/65531": [0, 1, 65528, 65529, 65531, 65532, 65533],
"3/29/0": [
{
"0": 15,
"1": 3
}
],
"3/29/1": [3, 29, 59],
"3/29/2": [],
"3/29/3": [],
"3/29/4": [
{
"0": null,
"1": 7,
"2": 1
},
{
"0": null,
"1": 8,
"2": 2
}
],
"3/29/65532": 1,
"3/29/65533": 2,
"3/29/65528": [],
"3/29/65529": [],
"3/29/65531": [0, 1, 2, 3, 4, 65528, 65529, 65531, 65532, 65533],
"3/59/0": 2,
"3/59/1": 0,
"3/59/2": 2,
"3/59/65532": 30,
"3/59/65533": 1,
"3/59/65528": [],
"3/59/65529": [],
"3/59/65531": [0, 1, 2, 65528, 65529, 65531, 65532, 65533],
"4/3/0": 0,
"4/3/1": 4,
"4/3/65532": 0,
"4/3/65533": 4,
"4/3/65528": [],
"4/3/65529": [0],
"4/3/65531": [0, 1, 65528, 65529, 65531, 65532, 65533],
"4/29/0": [
{
"0": 15,
"1": 3
}
],
"4/29/1": [3, 29, 59],
"4/29/2": [],
"4/29/3": [],
"4/29/4": [
{
"0": null,
"1": 7,
"2": 2
},
{
"0": null,
"1": 8,
"2": 4
}
],
"4/29/65532": 1,
"4/29/65533": 2,
"4/29/65528": [],
"4/29/65529": [],
"4/29/65531": [0, 1, 2, 3, 4, 65528, 65529, 65531, 65532, 65533],
"4/59/0": 2,
"4/59/1": 0,
"4/59/2": 2,
"4/59/65532": 30,
"4/59/65533": 1,
"4/59/65528": [],
"4/59/65529": [],
"4/59/65531": [0, 1, 2, 65528, 65529, 65531, 65532, 65533],
"5/3/0": 0,
"5/3/1": 4,
"5/3/65532": 0,
"5/3/65533": 4,
"5/3/65528": [],
"5/3/65529": [0],
"5/3/65531": [0, 1, 65528, 65529, 65531, 65532, 65533],
"5/29/0": [
{
"0": 15,
"1": 3
}
],
"5/29/1": [3, 29, 59],
"5/29/2": [],
"5/29/3": [],
"5/29/4": [
{
"0": null,
"1": 7,
"2": 3
},
{
"0": null,
"1": 8,
"2": 3
}
],
"5/29/65532": 1,
"5/29/65533": 2,
"5/29/65528": [],
"5/29/65529": [],
"5/29/65531": [0, 1, 2, 3, 4, 65528, 65529, 65531, 65532, 65533],
"5/59/0": 2,
"5/59/1": 0,
"5/59/2": 2,
"5/59/65532": 30,
"5/59/65533": 1,
"5/59/65528": [],
"5/59/65529": [],
"5/59/65531": [0, 1, 2, 65528, 65529, 65531, 65532, 65533],
"6/29/0": [
{
"0": 17,
"1": 1
}
],
"6/29/1": [29, 47],
"6/29/2": [],
"6/29/3": [],
"6/29/65532": 0,
"6/29/65533": 2,
"6/29/65528": [],
"6/29/65529": [],
"6/29/65531": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533],
"6/47/0": 1,
"6/47/1": 0,
"6/47/2": "Battery",
"6/47/11": 3120,
"6/47/12": 200,
"6/47/14": 0,
"6/47/15": false,
"6/47/16": 2,
"6/47/19": "CR2450",
"6/47/25": 2,
"6/47/31": [],
"6/47/65532": 10,
"6/47/65533": 2,
"6/47/65528": [],
"6/47/65529": [],
"6/47/65531": [
0, 1, 2, 11, 12, 14, 15, 16, 19, 25, 31, 65528, 65529, 65531, 65532, 65533
]
},
"attribute_subscriptions": []
}

View File

@@ -1,4 +1,193 @@
# serializer version: 1
# name: test_events[aqara_sensor_w100][event.climate_sensor_w100_button_3-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'event_types': list([
'multi_press_1',
'multi_press_2',
'long_press',
'long_release',
]),
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'event',
'entity_category': None,
'entity_id': 'event.climate_sensor_w100_button_3',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <EventDeviceClass.BUTTON: 'button'>,
'original_icon': None,
'original_name': 'Button (3)',
'platform': 'matter',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'button',
'unique_id': '00000000000004D2-000000000000004B-MatterNodeDevice-3-GenericSwitch-59-1',
'unit_of_measurement': None,
})
# ---
# name: test_events[aqara_sensor_w100][event.climate_sensor_w100_button_3-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'button',
'event_type': None,
'event_types': list([
'multi_press_1',
'multi_press_2',
'long_press',
'long_release',
]),
'friendly_name': 'Climate Sensor W100 Button (3)',
}),
'context': <ANY>,
'entity_id': 'event.climate_sensor_w100_button_3',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
})
# ---
# name: test_events[aqara_sensor_w100][event.climate_sensor_w100_button_4-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'event_types': list([
'multi_press_1',
'multi_press_2',
'long_press',
'long_release',
]),
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'event',
'entity_category': None,
'entity_id': 'event.climate_sensor_w100_button_4',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <EventDeviceClass.BUTTON: 'button'>,
'original_icon': None,
'original_name': 'Button (4)',
'platform': 'matter',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'button',
'unique_id': '00000000000004D2-000000000000004B-MatterNodeDevice-4-GenericSwitch-59-1',
'unit_of_measurement': None,
})
# ---
# name: test_events[aqara_sensor_w100][event.climate_sensor_w100_button_4-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'button',
'event_type': None,
'event_types': list([
'multi_press_1',
'multi_press_2',
'long_press',
'long_release',
]),
'friendly_name': 'Climate Sensor W100 Button (4)',
}),
'context': <ANY>,
'entity_id': 'event.climate_sensor_w100_button_4',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
})
# ---
# name: test_events[aqara_sensor_w100][event.climate_sensor_w100_button_5-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'event_types': list([
'multi_press_1',
'multi_press_2',
'long_press',
'long_release',
]),
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'event',
'entity_category': None,
'entity_id': 'event.climate_sensor_w100_button_5',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <EventDeviceClass.BUTTON: 'button'>,
'original_icon': None,
'original_name': 'Button (5)',
'platform': 'matter',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'button',
'unique_id': '00000000000004D2-000000000000004B-MatterNodeDevice-5-GenericSwitch-59-1',
'unit_of_measurement': None,
})
# ---
# name: test_events[aqara_sensor_w100][event.climate_sensor_w100_button_5-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'button',
'event_type': None,
'event_types': list([
'multi_press_1',
'multi_press_2',
'long_press',
'long_release',
]),
'friendly_name': 'Climate Sensor W100 Button (5)',
}),
'context': <ANY>,
'entity_id': 'event.climate_sensor_w100_button_5',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
})
# ---
# name: test_events[generic_switch][event.mock_generic_switch_button-entry]
EntityRegistryEntrySnapshot({
'aliases': set({

View File

@@ -1944,6 +1944,428 @@
'state': '27.94',
})
# ---
# name: test_sensors[aqara_sensor_w100][sensor.climate_sensor_w100_battery-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'sensor.climate_sensor_w100_battery',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <SensorDeviceClass.BATTERY: 'battery'>,
'original_icon': None,
'original_name': 'Battery',
'platform': 'matter',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': None,
'unique_id': '00000000000004D2-000000000000004B-MatterNodeDevice-6-PowerSource-47-12',
'unit_of_measurement': '%',
})
# ---
# name: test_sensors[aqara_sensor_w100][sensor.climate_sensor_w100_battery-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'battery',
'friendly_name': 'Climate Sensor W100 Battery',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': '%',
}),
'context': <ANY>,
'entity_id': 'sensor.climate_sensor_w100_battery',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '100',
})
# ---
# name: test_sensors[aqara_sensor_w100][sensor.climate_sensor_w100_battery_type-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': 'sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'sensor.climate_sensor_w100_battery_type',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Battery type',
'platform': 'matter',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'battery_replacement_description',
'unique_id': '00000000000004D2-000000000000004B-MatterNodeDevice-6-PowerSourceBatReplacementDescription-47-19',
'unit_of_measurement': None,
})
# ---
# name: test_sensors[aqara_sensor_w100][sensor.climate_sensor_w100_battery_type-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Climate Sensor W100 Battery type',
}),
'context': <ANY>,
'entity_id': 'sensor.climate_sensor_w100_battery_type',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'CR2450',
})
# ---
# name: test_sensors[aqara_sensor_w100][sensor.climate_sensor_w100_battery_voltage-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'sensor.climate_sensor_w100_battery_voltage',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
'sensor': dict({
'suggested_display_precision': 0,
}),
'sensor.private': dict({
'suggested_unit_of_measurement': <UnitOfElectricPotential.VOLT: 'V'>,
}),
}),
'original_device_class': <SensorDeviceClass.VOLTAGE: 'voltage'>,
'original_icon': None,
'original_name': 'Battery voltage',
'platform': 'matter',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'battery_voltage',
'unique_id': '00000000000004D2-000000000000004B-MatterNodeDevice-6-PowerSourceBatVoltage-47-11',
'unit_of_measurement': <UnitOfElectricPotential.VOLT: 'V'>,
})
# ---
# name: test_sensors[aqara_sensor_w100][sensor.climate_sensor_w100_battery_voltage-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'voltage',
'friendly_name': 'Climate Sensor W100 Battery voltage',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfElectricPotential.VOLT: 'V'>,
}),
'context': <ANY>,
'entity_id': 'sensor.climate_sensor_w100_battery_voltage',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '3.12',
})
# ---
# name: test_sensors[aqara_sensor_w100][sensor.climate_sensor_w100_current_switch_position_3-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'sensor.climate_sensor_w100_current_switch_position_3',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Current switch position (3)',
'platform': 'matter',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'switch_current_position',
'unique_id': '00000000000004D2-000000000000004B-MatterNodeDevice-3-SwitchCurrentPosition-59-1',
'unit_of_measurement': None,
})
# ---
# name: test_sensors[aqara_sensor_w100][sensor.climate_sensor_w100_current_switch_position_3-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Climate Sensor W100 Current switch position (3)',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
}),
'context': <ANY>,
'entity_id': 'sensor.climate_sensor_w100_current_switch_position_3',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '0',
})
# ---
# name: test_sensors[aqara_sensor_w100][sensor.climate_sensor_w100_current_switch_position_4-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'sensor.climate_sensor_w100_current_switch_position_4',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Current switch position (4)',
'platform': 'matter',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'switch_current_position',
'unique_id': '00000000000004D2-000000000000004B-MatterNodeDevice-4-SwitchCurrentPosition-59-1',
'unit_of_measurement': None,
})
# ---
# name: test_sensors[aqara_sensor_w100][sensor.climate_sensor_w100_current_switch_position_4-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Climate Sensor W100 Current switch position (4)',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
}),
'context': <ANY>,
'entity_id': 'sensor.climate_sensor_w100_current_switch_position_4',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '0',
})
# ---
# name: test_sensors[aqara_sensor_w100][sensor.climate_sensor_w100_current_switch_position_5-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'sensor.climate_sensor_w100_current_switch_position_5',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Current switch position (5)',
'platform': 'matter',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'switch_current_position',
'unique_id': '00000000000004D2-000000000000004B-MatterNodeDevice-5-SwitchCurrentPosition-59-1',
'unit_of_measurement': None,
})
# ---
# name: test_sensors[aqara_sensor_w100][sensor.climate_sensor_w100_current_switch_position_5-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Climate Sensor W100 Current switch position (5)',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
}),
'context': <ANY>,
'entity_id': 'sensor.climate_sensor_w100_current_switch_position_5',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '0',
})
# ---
# name: test_sensors[aqara_sensor_w100][sensor.climate_sensor_w100_humidity-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': None,
'entity_id': 'sensor.climate_sensor_w100_humidity',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <SensorDeviceClass.HUMIDITY: 'humidity'>,
'original_icon': None,
'original_name': 'Humidity',
'platform': 'matter',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': None,
'unique_id': '00000000000004D2-000000000000004B-MatterNodeDevice-2-HumiditySensor-1029-0',
'unit_of_measurement': '%',
})
# ---
# name: test_sensors[aqara_sensor_w100][sensor.climate_sensor_w100_humidity-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'humidity',
'friendly_name': 'Climate Sensor W100 Humidity',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': '%',
}),
'context': <ANY>,
'entity_id': 'sensor.climate_sensor_w100_humidity',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '44.72',
})
# ---
# name: test_sensors[aqara_sensor_w100][sensor.climate_sensor_w100_temperature-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': None,
'entity_id': 'sensor.climate_sensor_w100_temperature',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
'sensor': dict({
'suggested_display_precision': 1,
}),
}),
'original_device_class': <SensorDeviceClass.TEMPERATURE: 'temperature'>,
'original_icon': None,
'original_name': 'Temperature',
'platform': 'matter',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': None,
'unique_id': '00000000000004D2-000000000000004B-MatterNodeDevice-1-TemperatureSensor-1026-0',
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
})
# ---
# name: test_sensors[aqara_sensor_w100][sensor.climate_sensor_w100_temperature-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'temperature',
'friendly_name': 'Climate Sensor W100 Temperature',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
}),
'context': <ANY>,
'entity_id': 'sensor.climate_sensor_w100_temperature',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '27.73',
})
# ---
# name: test_sensors[aqara_thermostat_w500][sensor.floor_heating_thermostat_energy-entry]
EntityRegistryEntrySnapshot({
'aliases': set({

View File

@@ -2878,6 +2878,55 @@ async def test_issues(hass: HomeAssistant, issue_registry: ir.IssueRegistry) ->
assert_result_info(info, {})
assert info.rate_limit is None
issue = ir.IssueEntry(
active=False,
breaks_in_ha_version="2025.12",
created=dt_util.utcnow(),
data=None,
dismissed_version=None,
domain="test",
is_fixable=False,
is_persistent=False,
issue_domain="test",
issue_id="issue 2",
learn_more_url=None,
severity="warning",
translation_key="abc_1234",
translation_placeholders={"abc": "123"},
)
# Add non active issue
issue_registry.issues[("test", "issue 2")] = issue
# Test non active issue is omitted
issue_entry = issue_registry.async_get_issue("test", "issue 2")
assert issue_entry
issue_2_created = issue_entry.created
assert issue_entry and not issue_entry.active
info = render_to_info(hass, "{{ issues() }}")
assert_result_info(info, {})
assert info.rate_limit is None
# Load and activate the issue
ir.async_create_issue(
hass=hass,
breaks_in_ha_version="2025.12",
data=None,
domain="test",
is_fixable=False,
is_persistent=False,
issue_domain="test",
issue_id="issue 2",
learn_more_url=None,
severity="warning",
translation_key="abc_1234",
translation_placeholders={"abc": "123"},
)
activated_issue_entry = issue_registry.async_get_issue("test", "issue 2")
assert activated_issue_entry and activated_issue_entry.active
assert issue_2_created == activated_issue_entry.created
info = render_to_info(hass, "{{ issues()['test', 'issue 2'] }}")
assert_result_info(info, activated_issue_entry.to_json())
assert info.rate_limit is None
async def test_issue(hass: HomeAssistant, issue_registry: ir.IssueRegistry) -> None:
"""Test issue function."""