Compare commits

..

1 Commits

Author SHA1 Message Date
abmantis
8bd5eed295 Cache flattened service descriptions in websocket api 2025-11-29 00:10:38 +00:00
115 changed files with 1150 additions and 3338 deletions

View File

@@ -231,7 +231,7 @@ jobs:
- name: Detect duplicates using AI
id: ai_detection
if: steps.extract.outputs.should_continue == 'true' && steps.fetch_similar.outputs.has_similar == 'true'
uses: actions/ai-inference@334892bb203895caaed82ec52d23c1ed9385151e # v2.0.4
uses: actions/ai-inference@02c6cc30ae592ce65ee356387748dfc2fd5f7993 # v2.0.3
with:
model: openai/gpt-4o
system-prompt: |

View File

@@ -57,7 +57,7 @@ jobs:
- name: Detect language using AI
id: ai_language_detection
if: steps.detect_language.outputs.should_continue == 'true'
uses: actions/ai-inference@334892bb203895caaed82ec52d23c1ed9385151e # v2.0.4
uses: actions/ai-inference@02c6cc30ae592ce65ee356387748dfc2fd5f7993 # v2.0.3
with:
model: openai/gpt-4o-mini
system-prompt: |

View File

@@ -160,6 +160,7 @@
"triggers": {
"armed": {
"description": "Triggers when an alarm is armed.",
"description_configured": "[%key:component::alarm_control_panel::triggers::armed::description%]",
"fields": {
"behavior": {
"description": "[%key:component::alarm_control_panel::common::trigger_behavior_description%]",
@@ -170,6 +171,7 @@
},
"armed_away": {
"description": "Triggers when an alarm is armed away.",
"description_configured": "[%key:component::alarm_control_panel::triggers::armed_away::description%]",
"fields": {
"behavior": {
"description": "[%key:component::alarm_control_panel::common::trigger_behavior_description%]",
@@ -180,6 +182,7 @@
},
"armed_home": {
"description": "Triggers when an alarm is armed home.",
"description_configured": "[%key:component::alarm_control_panel::triggers::armed_home::description%]",
"fields": {
"behavior": {
"description": "[%key:component::alarm_control_panel::common::trigger_behavior_description%]",
@@ -190,6 +193,7 @@
},
"armed_night": {
"description": "Triggers when an alarm is armed night.",
"description_configured": "[%key:component::alarm_control_panel::triggers::armed_night::description%]",
"fields": {
"behavior": {
"description": "[%key:component::alarm_control_panel::common::trigger_behavior_description%]",
@@ -200,6 +204,7 @@
},
"armed_vacation": {
"description": "Triggers when an alarm is armed vacation.",
"description_configured": "[%key:component::alarm_control_panel::triggers::armed_vacation::description%]",
"fields": {
"behavior": {
"description": "[%key:component::alarm_control_panel::common::trigger_behavior_description%]",
@@ -210,6 +215,7 @@
},
"disarmed": {
"description": "Triggers when an alarm is disarmed.",
"description_configured": "[%key:component::alarm_control_panel::triggers::disarmed::description%]",
"fields": {
"behavior": {
"description": "[%key:component::alarm_control_panel::common::trigger_behavior_description%]",
@@ -220,6 +226,7 @@
},
"triggered": {
"description": "Triggers when an alarm is triggered.",
"description_configured": "[%key:component::alarm_control_panel::triggers::triggered::description%]",
"fields": {
"behavior": {
"description": "[%key:component::alarm_control_panel::common::trigger_behavior_description%]",

View File

@@ -2,7 +2,6 @@
from __future__ import annotations
from aiohttp import CookieJar
from pyanglianwater import AnglianWater
from pyanglianwater.auth import MSOB2CAuth
from pyanglianwater.exceptions import (
@@ -19,7 +18,7 @@ from homeassistant.const import (
)
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryError
from homeassistant.helpers.aiohttp_client import async_create_clientsession
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from .const import CONF_ACCOUNT_NUMBER, DOMAIN
from .coordinator import AnglianWaterConfigEntry, AnglianWaterUpdateCoordinator
@@ -34,10 +33,7 @@ async def async_setup_entry(
auth = MSOB2CAuth(
username=entry.data[CONF_USERNAME],
password=entry.data[CONF_PASSWORD],
session=async_create_clientsession(
hass,
cookie_jar=CookieJar(quote_cookie=False),
),
session=async_get_clientsession(hass),
refresh_token=entry.data[CONF_ACCESS_TOKEN],
account_number=entry.data[CONF_ACCOUNT_NUMBER],
)

View File

@@ -19,7 +19,7 @@
"data_description": {
"account_number": "Your account number found on your latest bill.",
"password": "Your password",
"username": "Username or email used to log in to the Anglian Water website."
"username": "Username or email used to login to the Anglian Water website."
},
"description": "Enter your Anglian Water account credentials to connect to Home Assistant."
}

View File

@@ -421,8 +421,6 @@ class ConversationSubentryFlowHandler(ConfigSubentryFlow):
)
if short_form.search(model_alias):
model_alias += "-0"
if model_alias.endswith(("haiku", "opus", "sonnet")):
model_alias += "-latest"
model_options.append(
SelectOptionDict(
label=model_info.display_name,

View File

@@ -8,5 +8,5 @@
"documentation": "https://www.home-assistant.io/integrations/anthropic",
"integration_type": "service",
"iot_class": "cloud_polling",
"requirements": ["anthropic==0.75.0"]
"requirements": ["anthropic==0.73.0"]
}

View File

@@ -113,6 +113,7 @@
"triggers": {
"idle": {
"description": "Triggers when an Assist satellite becomes idle.",
"description_configured": "[%key:component::assist_satellite::triggers::idle::description%]",
"fields": {
"behavior": {
"description": "[%key:component::assist_satellite::common::trigger_behavior_description%]",
@@ -123,6 +124,7 @@
},
"listening": {
"description": "Triggers when an Assist satellite starts listening.",
"description_configured": "[%key:component::assist_satellite::triggers::listening::description%]",
"fields": {
"behavior": {
"description": "[%key:component::assist_satellite::common::trigger_behavior_description%]",
@@ -133,6 +135,7 @@
},
"processing": {
"description": "Triggers when an Assist satellite is processing.",
"description_configured": "[%key:component::assist_satellite::triggers::processing::description%]",
"fields": {
"behavior": {
"description": "[%key:component::assist_satellite::common::trigger_behavior_description%]",
@@ -143,6 +146,7 @@
},
"responding": {
"description": "Triggers when an Assist satellite is responding.",
"description_configured": "[%key:component::assist_satellite::triggers::responding::description%]",
"fields": {
"behavior": {
"description": "[%key:component::assist_satellite::common::trigger_behavior_description%]",

View File

@@ -68,9 +68,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: BoschAlarmConfigEntry) -
config_entry_id=entry.entry_id,
connections={(CONNECTION_NETWORK_MAC, mac)} if mac else set(),
identifiers={(DOMAIN, entry.unique_id or entry.entry_id)},
name=f"Bosch {panel.model.name}",
name=f"Bosch {panel.model}",
manufacturer="Bosch Security Systems",
model=panel.model.name,
model=panel.model,
sw_version=panel.firmware_version,
)
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)

View File

@@ -83,7 +83,7 @@ async def try_connect(
finally:
await panel.disconnect()
return (panel.model.name, panel.serial_number)
return (panel.model, panel.serial_number)
class BoschAlarmConfigFlow(ConfigFlow, domain=DOMAIN):

View File

@@ -20,8 +20,7 @@ async def async_get_config_entry_diagnostics(
return {
"entry_data": async_redact_data(entry.data, TO_REDACT),
"data": {
"model": entry.runtime_data.model.name,
"family": entry.runtime_data.model.family.name,
"model": entry.runtime_data.model,
"serial_number": entry.runtime_data.serial_number,
"protocol_version": entry.runtime_data.protocol_version,
"firmware_version": entry.runtime_data.firmware_version,

View File

@@ -26,7 +26,7 @@ class BoschAlarmEntity(Entity):
self._attr_should_poll = False
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, unique_id)},
name=f"Bosch {panel.model.name}",
name=f"Bosch {panel.model}",
manufacturer="Bosch Security Systems",
)

View File

@@ -12,5 +12,5 @@
"integration_type": "device",
"iot_class": "local_push",
"quality_scale": "platinum",
"requirements": ["bosch-alarm-mode2==0.4.10"]
"requirements": ["bosch-alarm-mode2==0.4.6"]
}

View File

@@ -300,6 +300,7 @@
"triggers": {
"started_cooling": {
"description": "Triggers when a climate started cooling.",
"description_configured": "[%key:component::climate::triggers::started_cooling::description%]",
"fields": {
"behavior": {
"description": "[%key:component::climate::common::trigger_behavior_description%]",
@@ -310,6 +311,7 @@
},
"started_drying": {
"description": "Triggers when a climate started drying.",
"description_configured": "[%key:component::climate::triggers::started_drying::description%]",
"fields": {
"behavior": {
"description": "[%key:component::climate::common::trigger_behavior_description%]",
@@ -320,6 +322,7 @@
},
"started_heating": {
"description": "Triggers when a climate starts to heat.",
"description_configured": "[%key:component::climate::triggers::started_heating::description%]",
"fields": {
"behavior": {
"description": "[%key:component::climate::common::trigger_behavior_description%]",
@@ -330,6 +333,7 @@
},
"turned_off": {
"description": "Triggers when a climate is turned off.",
"description_configured": "[%key:component::climate::triggers::turned_off::description%]",
"fields": {
"behavior": {
"description": "[%key:component::climate::common::trigger_behavior_description%]",
@@ -340,6 +344,7 @@
},
"turned_on": {
"description": "Triggers when a climate is turned on.",
"description_configured": "[%key:component::climate::triggers::turned_on::description%]",
"fields": {
"behavior": {
"description": "[%key:component::climate::common::trigger_behavior_description%]",

View File

@@ -108,5 +108,34 @@
"toggle_cover_tilt": {
"service": "mdi:arrow-top-right-bottom-left"
}
},
"triggers": {
"awning_opened": {
"trigger": "mdi:awning-outline"
},
"blind_opened": {
"trigger": "mdi:blinds-horizontal"
},
"curtain_opened": {
"trigger": "mdi:curtains"
},
"door_opened": {
"trigger": "mdi:door-open"
},
"garage_opened": {
"trigger": "mdi:garage-open"
},
"gate_opened": {
"trigger": "mdi:gate-open"
},
"shade_opened": {
"trigger": "mdi:roller-shade"
},
"shutter_opened": {
"trigger": "mdi:window-shutter-open"
},
"window_opened": {
"trigger": "mdi:window-open"
}
}
}

View File

@@ -1,4 +1,16 @@
{
"common": {
"trigger_behavior_description_awning": "The behavior of the targeted awnings to trigger on.",
"trigger_behavior_description_blind": "The behavior of the targeted blinds to trigger on.",
"trigger_behavior_description_curtain": "The behavior of the targeted curtains to trigger on.",
"trigger_behavior_description_door": "The behavior of the targeted doors to trigger on.",
"trigger_behavior_description_garage": "The behavior of the targeted garage doors to trigger on.",
"trigger_behavior_description_gate": "The behavior of the targeted gates to trigger on.",
"trigger_behavior_description_shade": "The behavior of the targeted shades to trigger on.",
"trigger_behavior_description_shutter": "The behavior of the targeted shutters to trigger on.",
"trigger_behavior_description_window": "The behavior of the targeted windows to trigger on.",
"trigger_behavior_name": "Behavior"
},
"device_automation": {
"action_type": {
"close": "Close {entity_name}",
@@ -82,6 +94,15 @@
"name": "Window"
}
},
"selector": {
"trigger_behavior": {
"options": {
"any": "Any",
"first": "First",
"last": "Last"
}
}
},
"services": {
"close_cover": {
"description": "Closes a cover.",
@@ -136,5 +157,142 @@
"name": "Toggle tilt"
}
},
"title": "Cover"
"title": "Cover",
"triggers": {
"awning_opened": {
"description": "Triggers when an awning opens.",
"description_configured": "[%key:component::cover::triggers::awning_opened::description%]",
"fields": {
"behavior": {
"description": "[%key:component::cover::common::trigger_behavior_description_awning%]",
"name": "[%key:component::cover::common::trigger_behavior_name%]"
},
"fully_opened": {
"description": "Require the awnings to be fully opened before triggering.",
"name": "Fully opened"
}
},
"name": "When an awning opens"
},
"blind_opened": {
"description": "Triggers when a blind opens.",
"description_configured": "[%key:component::cover::triggers::blind_opened::description%]",
"fields": {
"behavior": {
"description": "[%key:component::cover::common::trigger_behavior_description_blind%]",
"name": "[%key:component::cover::common::trigger_behavior_name%]"
},
"fully_opened": {
"description": "Require the blinds to be fully opened before triggering.",
"name": "Fully opened"
}
},
"name": "When a blind opens"
},
"curtain_opened": {
"description": "Triggers when a curtain opens.",
"description_configured": "[%key:component::cover::triggers::curtain_opened::description%]",
"fields": {
"behavior": {
"description": "[%key:component::cover::common::trigger_behavior_description_curtain%]",
"name": "[%key:component::cover::common::trigger_behavior_name%]"
},
"fully_opened": {
"description": "Require the curtains to be fully opened before triggering.",
"name": "Fully opened"
}
},
"name": "When a curtain opens"
},
"door_opened": {
"description": "Triggers when a door opens.",
"description_configured": "[%key:component::cover::triggers::door_opened::description%]",
"fields": {
"behavior": {
"description": "[%key:component::cover::common::trigger_behavior_description_door%]",
"name": "[%key:component::cover::common::trigger_behavior_name%]"
},
"fully_opened": {
"description": "Require the doors to be fully opened before triggering.",
"name": "Fully opened"
}
},
"name": "When a door opens"
},
"garage_opened": {
"description": "Triggers when a garage door opens.",
"description_configured": "[%key:component::cover::triggers::garage_opened::description%]",
"fields": {
"behavior": {
"description": "[%key:component::cover::common::trigger_behavior_description_garage%]",
"name": "[%key:component::cover::common::trigger_behavior_name%]"
},
"fully_opened": {
"description": "Require the garage doors to be fully opened before triggering.",
"name": "Fully opened"
}
},
"name": "When a garage door opens"
},
"gate_opened": {
"description": "Triggers when a gate opens.",
"description_configured": "[%key:component::cover::triggers::gate_opened::description%]",
"fields": {
"behavior": {
"description": "[%key:component::cover::common::trigger_behavior_description_gate%]",
"name": "[%key:component::cover::common::trigger_behavior_name%]"
},
"fully_opened": {
"description": "Require the gates to be fully opened before triggering.",
"name": "Fully opened"
}
},
"name": "When a gate opens"
},
"shade_opened": {
"description": "Triggers when a shade opens.",
"description_configured": "[%key:component::cover::triggers::shade_opened::description%]",
"fields": {
"behavior": {
"description": "[%key:component::cover::common::trigger_behavior_description_shade%]",
"name": "[%key:component::cover::common::trigger_behavior_name%]"
},
"fully_opened": {
"description": "Require the shades to be fully opened before triggering.",
"name": "Fully opened"
}
},
"name": "When a shade opens"
},
"shutter_opened": {
"description": "Triggers when a shutter opens.",
"description_configured": "[%key:component::cover::triggers::shutter_opened::description%]",
"fields": {
"behavior": {
"description": "[%key:component::cover::common::trigger_behavior_description_shutter%]",
"name": "[%key:component::cover::common::trigger_behavior_name%]"
},
"fully_opened": {
"description": "Require the shutters to be fully opened before triggering.",
"name": "Fully opened"
}
},
"name": "When a shutter opens"
},
"window_opened": {
"description": "Triggers when a window opens.",
"description_configured": "[%key:component::cover::triggers::window_opened::description%]",
"fields": {
"behavior": {
"description": "[%key:component::cover::common::trigger_behavior_description_window%]",
"name": "[%key:component::cover::common::trigger_behavior_name%]"
},
"fully_opened": {
"description": "Require the windows to be fully opened before triggering.",
"name": "Fully opened"
}
},
"name": "When a window opens"
}
}
}

View File

@@ -0,0 +1,116 @@
"""Provides triggers for covers."""
from typing import Final
import voluptuous as vol
from homeassistant.const import CONF_OPTIONS
from homeassistant.core import HomeAssistant, State
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity import get_device_class
from homeassistant.helpers.trigger import (
ENTITY_STATE_TRIGGER_SCHEMA_FIRST_LAST,
EntityTriggerBase,
Trigger,
TriggerConfig,
)
from homeassistant.helpers.typing import UNDEFINED, UndefinedType
from . import ATTR_CURRENT_POSITION, CoverDeviceClass, CoverState
from .const import DOMAIN
ATTR_FULLY_OPENED: Final = "fully_opened"
COVER_OPENED_TRIGGER_SCHEMA = ENTITY_STATE_TRIGGER_SCHEMA_FIRST_LAST.extend(
{
vol.Required(CONF_OPTIONS): {
vol.Required(ATTR_FULLY_OPENED, default=False): bool,
},
}
)
def get_device_class_or_undefined(
hass: HomeAssistant, entity_id: str
) -> str | None | UndefinedType:
"""Get the device class of an entity or UNDEFINED if not found."""
try:
return get_device_class(hass, entity_id)
except HomeAssistantError:
return UNDEFINED
class CoverOpenedClosedTrigger(EntityTriggerBase):
"""Class for cover opened and closed triggers."""
_attribute: str = ATTR_CURRENT_POSITION
_attribute_value: int | None = None
_device_class: CoverDeviceClass | None
_domain: str = DOMAIN
_to_states: set[str]
def is_to_state(self, state: State) -> bool:
"""Check if the state matches the target state."""
if state.state not in self._to_states:
return False
if (
self._attribute_value is not None
and (value := state.attributes.get(self._attribute)) is not None
and value != self._attribute_value
):
return False
return True
def entity_filter(self, entities: set[str]) -> set[str]:
"""Filter entities of this domain."""
entities = super().entity_filter(entities)
return {
entity_id
for entity_id in entities
if get_device_class_or_undefined(self._hass, entity_id)
== self._device_class
}
class CoverOpenedTrigger(CoverOpenedClosedTrigger):
"""Class for cover opened triggers."""
_schema = COVER_OPENED_TRIGGER_SCHEMA
_to_states = {CoverState.OPEN, CoverState.OPENING}
def __init__(self, hass: HomeAssistant, config: TriggerConfig) -> None:
"""Initialize the state trigger."""
super().__init__(hass, config)
if self._options.get(ATTR_FULLY_OPENED):
self._attribute_value = 100
def make_cover_opened_trigger(
device_class: CoverDeviceClass | None,
) -> type[CoverOpenedTrigger]:
"""Create an entity state attribute trigger class."""
class CustomTrigger(CoverOpenedTrigger):
"""Trigger for entity state changes."""
_device_class = device_class
return CustomTrigger
TRIGGERS: dict[str, type[Trigger]] = {
"awning_opened": make_cover_opened_trigger(CoverDeviceClass.AWNING),
"blind_opened": make_cover_opened_trigger(CoverDeviceClass.BLIND),
"curtain_opened": make_cover_opened_trigger(CoverDeviceClass.CURTAIN),
"door_opened": make_cover_opened_trigger(CoverDeviceClass.DOOR),
"garage_opened": make_cover_opened_trigger(CoverDeviceClass.GARAGE),
"gate_opened": make_cover_opened_trigger(CoverDeviceClass.GATE),
"shade_opened": make_cover_opened_trigger(CoverDeviceClass.SHADE),
"shutter_opened": make_cover_opened_trigger(CoverDeviceClass.SHUTTER),
"window_opened": make_cover_opened_trigger(CoverDeviceClass.WINDOW),
}
async def async_get_triggers(hass: HomeAssistant) -> dict[str, type[Trigger]]:
"""Return the triggers for covers."""
return TRIGGERS

View File

@@ -0,0 +1,79 @@
.trigger_common_fields: &trigger_common_fields
behavior:
required: true
default: any
selector:
select:
translation_key: trigger_behavior
options:
- first
- last
- any
fully_opened:
required: true
default: false
selector:
boolean:
awning_opened:
fields: *trigger_common_fields
target:
entity:
domain: cover
device_class: awning
blind_opened:
fields: *trigger_common_fields
target:
entity:
domain: cover
device_class: blind
curtain_opened:
fields: *trigger_common_fields
target:
entity:
domain: cover
device_class: curtain
door_opened:
fields: *trigger_common_fields
target:
entity:
domain: cover
device_class: door
garage_opened:
fields: *trigger_common_fields
target:
entity:
domain: cover
device_class: garage
gate_opened:
fields: *trigger_common_fields
target:
entity:
domain: cover
device_class: gate
shade_opened:
fields: *trigger_common_fields
target:
entity:
domain: cover
device_class: shade
shutter_opened:
fields: *trigger_common_fields
target:
entity:
domain: cover
device_class: shutter
window_opened:
fields: *trigger_common_fields
target:
entity:
domain: cover
device_class: window

View File

@@ -17,7 +17,7 @@ DEFAULT_NEW_CONFIG_ALLOW_ALLOW_SERVICE_CALLS = False
DEFAULT_PORT: Final = 6053
STABLE_BLE_VERSION_STR = "2025.11.0"
STABLE_BLE_VERSION_STR = "2025.8.0"
STABLE_BLE_VERSION = AwesomeVersion(STABLE_BLE_VERSION_STR)
PROJECT_URLS = {
"esphome.bluetooth-proxy": "https://esphome.github.io/bluetooth-proxies/",

View File

@@ -17,7 +17,7 @@
"mqtt": ["esphome/discover/#"],
"quality_scale": "platinum",
"requirements": [
"aioesphomeapi==42.9.0",
"aioesphomeapi==42.8.0",
"esphome-dashboard-api==1.3.0",
"bleak-esphome==3.4.0"
],

View File

@@ -157,7 +157,7 @@
"title": "[%key:component::assist_pipeline::issues::assist_in_progress_deprecated::title%]"
},
"ble_firmware_outdated": {
"description": "ESPHome {version} introduces ultra-low latency event processing, reducing BLE event delays from 0-16 milliseconds to approximately 12 microseconds. This resolves stability issues when pairing, connecting, or handshaking with devices that require low latency, and makes Bluetooth proxy operations rival or exceed local adapters. We highly recommend updating {name} to take advantage of these improvements.",
"description": "To improve Bluetooth reliability and performance, we highly recommend updating {name} with ESPHome {version} or later. When updating the device from ESPHome earlier than 2022.12.0, it is recommended to use a serial cable instead of an over-the-air update to take advantage of the new partition scheme.",
"title": "Update {name} with ESPHome {version} or later"
},
"device_conflict": {

View File

@@ -167,6 +167,7 @@
"triggers": {
"turned_off": {
"description": "Triggers when a fan is turned off.",
"description_configured": "[%key:component::fan::triggers::turned_off::description%]",
"fields": {
"behavior": {
"description": "[%key:component::fan::common::trigger_behavior_description%]",
@@ -177,6 +178,7 @@
},
"turned_on": {
"description": "Triggers when a fan is turned on.",
"description_configured": "[%key:component::fan::triggers::turned_on::description%]",
"fields": {
"behavior": {
"description": "[%key:component::fan::common::trigger_behavior_description%]",

View File

@@ -8,5 +8,5 @@
"iot_class": "cloud_polling",
"loggers": ["google_air_quality_api"],
"quality_scale": "bronze",
"requirements": ["google_air_quality_api==1.1.3"]
"requirements": ["google_air_quality_api==1.1.2"]
}

View File

@@ -2,7 +2,7 @@
"config": {
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
"not_implemented": "This integration can only be set up via discovery."
"not_implemented": "This integration can only be setup via discovery."
},
"error": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",

View File

@@ -42,6 +42,7 @@
"triggers": {
"docked": {
"description": "Triggers when a lawn mower has docked.",
"description_configured": "[%key:component::lawn_mower::triggers::docked::description%]",
"fields": {
"behavior": {
"description": "[%key:component::lawn_mower::common::trigger_behavior_description%]",
@@ -52,6 +53,7 @@
},
"errored": {
"description": "Triggers when a lawn mower has errored.",
"description_configured": "[%key:component::lawn_mower::triggers::errored::description%]",
"fields": {
"behavior": {
"description": "[%key:component::lawn_mower::common::trigger_behavior_description%]",
@@ -62,6 +64,7 @@
},
"paused_mowing": {
"description": "Triggers when a lawn mower has paused mowing.",
"description_configured": "[%key:component::lawn_mower::triggers::paused_mowing::description%]",
"fields": {
"behavior": {
"description": "[%key:component::lawn_mower::common::trigger_behavior_description%]",
@@ -72,6 +75,7 @@
},
"started_mowing": {
"description": "Triggers when a lawn mower has started mowing.",
"description_configured": "[%key:component::lawn_mower::triggers::started_mowing::description%]",
"fields": {
"behavior": {
"description": "[%key:component::lawn_mower::common::trigger_behavior_description%]",

View File

@@ -7,5 +7,5 @@
"documentation": "https://www.home-assistant.io/integrations/lg_thinq",
"iot_class": "cloud_push",
"loggers": ["thinqconnect"],
"requirements": ["thinqconnect==1.0.9"]
"requirements": ["thinqconnect==1.0.8"]
}

View File

@@ -43,6 +43,7 @@
"conditions": {
"is_off": {
"description": "Test if a light is off.",
"description_configured": "[%key:component::light::conditions::is_off::description%]",
"fields": {
"behavior": {
"description": "[%key:component::light::common::condition_behavior_description%]",
@@ -53,6 +54,7 @@
},
"is_on": {
"description": "Test if a light is on.",
"description_configured": "[%key:component::light::conditions::is_on::description%]",
"fields": {
"behavior": {
"description": "[%key:component::light::common::condition_behavior_description%]",
@@ -511,6 +513,7 @@
"triggers": {
"turned_off": {
"description": "Triggers when a light is turned off.",
"description_configured": "[%key:component::light::triggers::turned_off::description%]",
"fields": {
"behavior": {
"description": "[%key:component::light::common::trigger_behavior_description%]",
@@ -521,6 +524,7 @@
},
"turned_on": {
"description": "Triggers when a light is turned on.",
"description_configured": "[%key:component::light::triggers::turned_on::description%]",
"fields": {
"behavior": {
"description": "[%key:component::light::common::trigger_behavior_description%]",

View File

@@ -499,53 +499,4 @@ DISCOVERY_SCHEMAS = [
entity_class=MatterBinarySensor,
required_attributes=(clusters.WindowCovering.Attributes.ConfigStatus,),
),
MatterDiscoverySchema(
platform=Platform.BINARY_SENSOR,
entity_description=MatterBinarySensorEntityDescription(
key="ThermostatRemoteSensing_LocalTemperature",
translation_key="thermostat_remote_sensing_local_temperature",
entity_category=EntityCategory.DIAGNOSTIC,
# LocalTemperature bit from RemoteSensing attribute
device_to_ha=lambda x: bool(
x
& clusters.Thermostat.Bitmaps.RemoteSensingBitmap.kLocalTemperature # Calculated Local Temperature is derived from a remote node
),
),
entity_class=MatterBinarySensor,
required_attributes=(clusters.Thermostat.Attributes.RemoteSensing,),
allow_multi=True,
),
MatterDiscoverySchema(
platform=Platform.BINARY_SENSOR,
entity_description=MatterBinarySensorEntityDescription(
key="ThermostatRemoteSensing_OutdoorTemperature",
translation_key="thermostat_remote_sensing_outdoor_temperature",
entity_category=EntityCategory.DIAGNOSTIC,
# OutdoorTemperature bit from RemoteSensing attribute
device_to_ha=lambda x: bool(
x
& clusters.Thermostat.Bitmaps.RemoteSensingBitmap.kOutdoorTemperature # OutdoorTemperature is derived from a remote node
),
),
entity_class=MatterBinarySensor,
required_attributes=(clusters.Thermostat.Attributes.RemoteSensing,),
allow_multi=True,
),
MatterDiscoverySchema(
platform=Platform.BINARY_SENSOR,
entity_description=MatterBinarySensorEntityDescription(
key="ThermostatRemoteSensing_Occupancy",
translation_key="thermostat_remote_sensing_occupancy",
entity_category=EntityCategory.DIAGNOSTIC,
# Occupancy bit from RemoteSensing attribute
device_to_ha=lambda x: bool(
x
& clusters.Thermostat.Bitmaps.RemoteSensingBitmap.kOccupancy # Occupancy is derived from a remote node
),
),
entity_class=MatterBinarySensor,
required_attributes=(clusters.Thermostat.Attributes.RemoteSensing,),
featuremap_contains=clusters.Thermostat.Bitmaps.Feature.kOccupancy,
allow_multi=True,
),
]

View File

@@ -89,15 +89,6 @@
"test_in_progress": {
"name": "Test in progress"
},
"thermostat_remote_sensing_local_temperature": {
"name": "Local temperature remote sensing"
},
"thermostat_remote_sensing_occupancy": {
"name": "Occupancy remote sensing"
},
"thermostat_remote_sensing_outdoor_temperature": {
"name": "Outdoor temperature remote sensing"
},
"valve_fault_blocked": {
"name": "Valve blocked"
},

View File

@@ -382,6 +382,7 @@
"triggers": {
"stopped_playing": {
"description": "Triggers when a media player stops playing.",
"description_configured": "[%key:component::media_player::triggers::stopped_playing::description%]",
"fields": {
"behavior": {
"description": "[%key:component::media_player::common::trigger_behavior_description%]",

View File

@@ -1486,7 +1486,6 @@ class MqttEntity(
entity_registry.async_update_entity(
self.entity_id, new_entity_id=self._update_registry_entity_id
)
self._update_registry_entity_id = None
await super().async_added_to_hass()
self._subscriptions = {}

View File

@@ -729,8 +729,8 @@
"data_description": {
"payload_reset_percentage": "A special payload that resets the fan speed percentage state attribute to unknown when received at the percentage state topic.",
"percentage_command_template": "A [template]({command_templating_url}) to compose the payload to be published at the percentage command topic.",
"percentage_command_topic": "The MQTT topic to publish commands to change the fan speed state based on a percentage setting. The value shall be in the range from \"speed range min\" to \"speed range max\". [Learn more.]({url}#percentage_command_topic)",
"percentage_state_topic": "The MQTT topic subscribed to receive fan speed state. This is a value in the range from \"speed range min\" to \"speed range max\". [Learn more.]({url}#percentage_state_topic)",
"percentage_command_topic": "The MQTT topic to publish commands to change the fan speed state based on a percentage. [Learn more.]({url}#percentage_command_topic)",
"percentage_state_topic": "The MQTT topic subscribed to receive fan speed based on percentage. [Learn more.]({url}#percentage_state_topic)",
"percentage_value_template": "Defines a [template]({value_templating_url}) to extract the speed percentage value.",
"speed_range_max": "The maximum of numeric output range (representing 100 %). The percentage step is 100 / number of speeds within the \"speed range\".",
"speed_range_min": "The minimum of numeric output range (off not included, so speed_range_min - 1 represents 0 %). The percentage step is 100 / the number of speeds within the \"speed range\"."
@@ -1563,6 +1563,7 @@
"triggers": {
"_": {
"description": "When a specific message is received on a given MQTT topic.",
"description_configured": "When an MQTT message has been received",
"fields": {
"payload": {
"description": "The payload to trigger on.",

View File

@@ -19,5 +19,5 @@
"documentation": "https://www.home-assistant.io/integrations/nest",
"iot_class": "cloud_push",
"loggers": ["google_nest_sdm"],
"requirements": ["google-nest-sdm==9.1.1"]
"requirements": ["google-nest-sdm==9.1.0"]
}

View File

@@ -2,20 +2,17 @@
from __future__ import annotations
from homeassistant.const import UnitOfTemperature, UnitOfVolume, UnitOfVolumeFlowRate
from homeassistant.const import UnitOfTemperature, UnitOfVolumeFlowRate
DOMAIN = "pooldose"
MANUFACTURER = "SEKO"
# Mapping of device units (upper case) to Home Assistant units
# Mapping of device units to Home Assistant units
UNIT_MAPPING: dict[str, str] = {
# Temperature units
"°C": UnitOfTemperature.CELSIUS,
"°F": UnitOfTemperature.FAHRENHEIT,
# Volume flow rate units
"M3/H": UnitOfVolumeFlowRate.CUBIC_METERS_PER_HOUR,
"L/S": UnitOfVolumeFlowRate.LITERS_PER_SECOND,
# Volume units
"L": UnitOfVolume.LITERS,
"M3": UnitOfVolume.CUBIC_METERS,
"m3/h": UnitOfVolumeFlowRate.CUBIC_METERS_PER_HOUR,
"L/s": UnitOfVolumeFlowRate.LITERS_PER_SECOND,
}

View File

@@ -119,9 +119,6 @@
},
"ph_type_dosing": {
"default": "mdi:beaker"
},
"water_meter_total_permanent": {
"default": "mdi:counter"
}
},
"switch": {

View File

@@ -11,5 +11,5 @@
"documentation": "https://www.home-assistant.io/integrations/pooldose",
"iot_class": "local_polling",
"quality_scale": "bronze",
"requirements": ["python-pooldose==0.8.1"]
"requirements": ["python-pooldose==0.8.0"]
}

View File

@@ -10,7 +10,6 @@ from homeassistant.components.sensor import (
SensorDeviceClass,
SensorEntity,
SensorEntityDescription,
SensorStateClass,
)
from homeassistant.const import (
CONCENTRATION_PARTS_PER_MILLION,
@@ -59,13 +58,6 @@ SENSOR_DESCRIPTIONS: tuple[PooldoseSensorEntityDescription, ...] = (
device_class=SensorDeviceClass.VOLUME_FLOW_RATE,
use_dynamic_unit=True,
),
PooldoseSensorEntityDescription(
key="water_meter_total_permanent",
translation_key="water_meter_total_permanent",
device_class=SensorDeviceClass.VOLUME,
state_class=SensorStateClass.TOTAL_INCREASING,
use_dynamic_unit=True,
),
PooldoseSensorEntityDescription(
key="ph_type_dosing",
translation_key="ph_type_dosing",
@@ -231,8 +223,8 @@ class PooldoseSensor(PooldoseEntity, SensorEntity):
and (data := self.get_data()) is not None
and (device_unit := data.get("unit"))
):
# Map device unit (upper case) to Home Assistant unit, return None if unknown
return UNIT_MAPPING.get(device_unit.upper())
# Map device unit to Home Assistant unit, return None if unknown
return UNIT_MAPPING.get(device_unit)
# Fall back to static unit from entity description
return super().native_unit_of_measurement

View File

@@ -160,9 +160,6 @@
"acid": "pH-",
"alcalyne": "pH+"
}
},
"water_meter_total_permanent": {
"name": "Totalizer"
}
},
"switch": {

View File

@@ -19,7 +19,7 @@
"loggers": ["roborock"],
"quality_scale": "silver",
"requirements": [
"python-roborock==3.8.4",
"python-roborock==3.8.1",
"vacuum-map-parser-roborock==0.1.4"
]
}

View File

@@ -30,7 +30,7 @@ from .entity import (
ShellyRpcAttributeEntity,
ShellySleepingBlockAttributeEntity,
ShellySleepingRpcAttributeEntity,
async_setup_entry_block,
async_setup_entry_attribute_entities,
async_setup_entry_rest,
async_setup_entry_rpc,
)
@@ -127,7 +127,7 @@ class RpcBluTrvBinarySensor(RpcBinarySensor):
)
BLOCK_SENSORS: dict[tuple[str, str], BlockBinarySensorDescription] = {
SENSORS: dict[tuple[str, str], BlockBinarySensorDescription] = {
("device", "overtemp"): BlockBinarySensorDescription(
key="device|overtemp",
translation_key="overheating",
@@ -372,19 +372,19 @@ def _async_setup_block_entry(
) -> None:
"""Set up entities for BLOCK device."""
if config_entry.data[CONF_SLEEP_PERIOD]:
async_setup_entry_block(
async_setup_entry_attribute_entities(
hass,
config_entry,
async_add_entities,
BLOCK_SENSORS,
SENSORS,
BlockSleepingBinarySensor,
)
else:
async_setup_entry_block(
async_setup_entry_attribute_entities(
hass,
config_entry,
async_add_entities,
BLOCK_SENSORS,
SENSORS,
BlockBinarySensor,
)
async_setup_entry_rest(

View File

@@ -341,5 +341,3 @@ MODEL_TOP_EV_CHARGER_EVE01 = "EVE01"
MODEL_FRANKEVER_IRRIGATION_CONTROLLER = "Irrigation"
ROLE_GENERIC = "generic"
TRV_CHANNEL = 0

View File

@@ -27,7 +27,7 @@ from .entity import (
RpcEntityDescription,
ShellyBlockAttributeEntity,
ShellyRpcAttributeEntity,
async_setup_entry_block,
async_setup_entry_attribute_entities,
async_setup_entry_rpc,
rpc_call,
)
@@ -81,7 +81,7 @@ def _async_setup_block_entry(
coordinator = config_entry.runtime_data.block
assert coordinator
async_setup_entry_block(
async_setup_entry_attribute_entities(
hass, config_entry, async_add_entities, BLOCK_COVERS, BlockShellyCover
)

View File

@@ -34,14 +34,14 @@ from .utils import (
@callback
def async_setup_entry_block(
def async_setup_entry_attribute_entities(
hass: HomeAssistant,
config_entry: ShellyConfigEntry,
async_add_entities: AddEntitiesCallback,
sensors: Mapping[tuple[str, str], BlockEntityDescription],
sensor_class: Callable,
) -> None:
"""Set up block entities."""
"""Set up entities for attributes."""
coordinator = config_entry.runtime_data.block
assert coordinator
if coordinator.device.initialized:
@@ -150,7 +150,7 @@ def async_setup_entry_rpc(
sensors: Mapping[str, RpcEntityDescription],
sensor_class: Callable,
) -> None:
"""Set up RPC entities."""
"""Set up entities for RPC sensors."""
coordinator = config_entry.runtime_data.rpc
assert coordinator

View File

@@ -18,6 +18,7 @@ from homeassistant.components.event import (
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import (
BASIC_INPUTS_EVENTS_TYPES,
@@ -25,7 +26,7 @@ from .const import (
SHIX3_1_INPUTS_EVENTS_TYPES,
)
from .coordinator import ShellyBlockCoordinator, ShellyConfigEntry, ShellyRpcCoordinator
from .entity import ShellyBlockEntity, ShellyRpcEntity
from .entity import ShellyBlockEntity, get_entity_rpc_device_info
from .utils import (
async_remove_orphaned_entities,
async_remove_shelly_entity,
@@ -135,7 +136,7 @@ def _async_setup_rpc_entry(
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up entities for RPC device."""
entities: list[ShellyRpcEvent | ShellyRpcScriptEvent] = []
entities: list[ShellyRpcEvent] = []
coordinator = config_entry.runtime_data.rpc
if TYPE_CHECKING:
@@ -161,9 +162,7 @@ def _async_setup_rpc_entry(
continue
if script_events and (event_types := script_events[get_rpc_key_id(script)]):
entities.append(
ShellyRpcScriptEvent(coordinator, script, SCRIPT_EVENT, event_types)
)
entities.append(ShellyRpcScriptEvent(coordinator, script, event_types))
# If a script is removed, from the device configuration, we need to remove orphaned entities
async_remove_orphaned_entities(
@@ -228,7 +227,7 @@ class ShellyBlockEvent(ShellyBlockEntity, EventEntity):
self.async_write_ha_state()
class ShellyRpcEvent(ShellyRpcEntity, EventEntity):
class ShellyRpcEvent(CoordinatorEntity[ShellyRpcCoordinator], EventEntity):
"""Represent RPC event entity."""
_attr_has_entity_name = True
@@ -241,19 +240,25 @@ class ShellyRpcEvent(ShellyRpcEntity, EventEntity):
description: ShellyRpcEventDescription,
) -> None:
"""Initialize Shelly entity."""
super().__init__(coordinator, key)
super().__init__(coordinator)
self._attr_device_info = get_entity_rpc_device_info(coordinator, key)
self._attr_unique_id = f"{coordinator.mac}-{key}"
self.entity_description = description
_, component, component_id = get_rpc_key(key)
if custom_name := get_rpc_custom_name(coordinator.device, key):
self._attr_name = custom_name
else:
self._attr_translation_placeholders = {
"input_number": component_id
if get_rpc_number_of_channels(coordinator.device, component) > 1
else ""
}
self.event_id = int(component_id)
if description.key == "input":
_, component, component_id = get_rpc_key(key)
if custom_name := get_rpc_custom_name(coordinator.device, key):
self._attr_name = custom_name
else:
self._attr_translation_placeholders = {
"input_number": component_id
if get_rpc_number_of_channels(coordinator.device, component) > 1
else ""
}
self.event_id = int(component_id)
elif description.key == "script":
self._attr_name = get_rpc_custom_name(coordinator.device, key)
self.event_id = get_rpc_key_id(key)
async def async_added_to_hass(self) -> None:
"""When entity is added to hass."""
@@ -265,36 +270,30 @@ class ShellyRpcEvent(ShellyRpcEntity, EventEntity):
@callback
def _async_handle_event(self, event: dict[str, Any]) -> None:
"""Handle the event."""
"""Handle the demo button event."""
if event["id"] == self.event_id:
self._trigger_event(event["event"])
self.async_write_ha_state()
class ShellyRpcScriptEvent(ShellyRpcEntity, EventEntity):
class ShellyRpcScriptEvent(ShellyRpcEvent):
"""Represent RPC script event entity."""
_attr_has_entity_name = True
entity_description: ShellyRpcEventDescription
def __init__(
self,
coordinator: ShellyRpcCoordinator,
key: str,
description: ShellyRpcEventDescription,
event_types: list[str],
) -> None:
"""Initialize Shelly script event entity."""
super().__init__(coordinator, key)
self.entity_description = description
self._attr_event_types = event_types
super().__init__(coordinator, key, SCRIPT_EVENT)
self._attr_name = get_rpc_custom_name(coordinator.device, key)
self.event_id = get_rpc_key_id(key)
self.component = key
self._attr_event_types = event_types
async def async_added_to_hass(self) -> None:
"""When entity is added to hass."""
await super().async_added_to_hass()
await super(CoordinatorEntity, self).async_added_to_hass()
self.async_on_remove(
self.coordinator.async_subscribe_events(self._async_handle_event)
@@ -303,7 +302,7 @@ class ShellyRpcScriptEvent(ShellyRpcEntity, EventEntity):
@callback
def _async_handle_event(self, event: dict[str, Any]) -> None:
"""Handle script event."""
if event.get("component") == self.key:
if event.get("component") == self.component:
event_type = event.get("event")
if event_type not in self.event_types:
# This can happen if we didn't find this event type in the script

View File

@@ -44,7 +44,7 @@ from .entity import (
RpcEntityDescription,
ShellyBlockAttributeEntity,
ShellyRpcAttributeEntity,
async_setup_entry_block,
async_setup_entry_attribute_entities,
async_setup_entry_rpc,
)
from .utils import (
@@ -101,7 +101,7 @@ def _async_setup_block_entry(
coordinator = config_entry.runtime_data.block
assert coordinator
async_setup_entry_block(
async_setup_entry_attribute_entities(
hass, config_entry, async_add_entities, BLOCK_LIGHTS, BlockShellyLight
)

View File

@@ -17,7 +17,7 @@
"iot_class": "local_push",
"loggers": ["aioshelly"],
"quality_scale": "platinum",
"requirements": ["aioshelly==13.22.0"],
"requirements": ["aioshelly==13.21.0"],
"zeroconf": [
{
"name": "shelly*",

View File

@@ -4,7 +4,7 @@ from __future__ import annotations
from collections.abc import Callable
from dataclasses import dataclass
from typing import TYPE_CHECKING, Final, cast
from typing import TYPE_CHECKING, Any, Final, cast
from aioshelly.block_device import Block
from aioshelly.const import RPC_GENERATIONS
@@ -34,7 +34,6 @@ from .const import (
MODEL_LINKEDGO_ST1820_THERMOSTAT,
MODEL_TOP_EV_CHARGER_EVE01,
ROLE_GENERIC,
TRV_CHANNEL,
VIRTUAL_NUMBER_MODE_MAP,
)
from .coordinator import ShellyBlockCoordinator, ShellyConfigEntry, ShellyRpcCoordinator
@@ -43,7 +42,7 @@ from .entity import (
RpcEntityDescription,
ShellyRpcAttributeEntity,
ShellySleepingBlockAttributeEntity,
async_setup_entry_block,
async_setup_entry_attribute_entities,
async_setup_entry_rpc,
rpc_call,
)
@@ -63,6 +62,9 @@ PARALLEL_UPDATES = 0
class BlockNumberDescription(BlockEntityDescription, NumberEntityDescription):
"""Class to describe a BLOCK sensor."""
rest_path: str = ""
rest_arg: str = ""
@dataclass(frozen=True, kw_only=True)
class RpcNumberDescription(RpcEntityDescription, NumberEntityDescription):
@@ -176,7 +178,7 @@ class RpcBluTrvExtTempNumber(RpcBluTrvNumber):
self.async_write_ha_state()
BLOCK_NUMBERS: dict[tuple[str, str], BlockNumberDescription] = {
NUMBERS: dict[tuple[str, str], BlockNumberDescription] = {
("device", "valvePos"): BlockNumberDescription(
key="device|valvepos",
translation_key="valve_position",
@@ -187,6 +189,8 @@ BLOCK_NUMBERS: dict[tuple[str, str], BlockNumberDescription] = {
native_max_value=100,
native_step=1,
mode=NumberMode.SLIDER,
rest_path="thermostat/0",
rest_arg="pos",
),
}
@@ -349,11 +353,11 @@ def _async_setup_block_entry(
) -> None:
"""Set up entities for BLOCK device."""
if config_entry.data[CONF_SLEEP_PERIOD]:
async_setup_entry_block(
async_setup_entry_attribute_entities(
hass,
config_entry,
async_add_entities,
BLOCK_NUMBERS,
NUMBERS,
BlockSleepingNumber,
)
@@ -422,11 +426,18 @@ class BlockSleepingNumber(ShellySleepingBlockAttributeEntity, RestoreNumber):
async def async_set_native_value(self, value: float) -> None:
"""Set value."""
LOGGER.debug(
"Setting thermostat position for entity %s to %s", self.name, value
# Example for Shelly Valve: http://192.168.188.187/thermostat/0?pos=13.0
await self._set_state_full_path(
self.entity_description.rest_path,
{self.entity_description.rest_arg: value},
)
self.async_write_ha_state()
async def _set_state_full_path(self, path: str, params: Any) -> Any:
"""Set block state (HTTP request)."""
LOGGER.debug("Setting state for entity %s, state: %s", self.name, params)
try:
await self.coordinator.device.set_thermostat_state(TRV_CHANNEL, pos=value)
return await self.coordinator.device.http_request("get", path, params)
except DeviceConnectionError as err:
self.coordinator.last_update_success = False
raise HomeAssistantError(
@@ -439,4 +450,3 @@ class BlockSleepingNumber(ShellySleepingBlockAttributeEntity, RestoreNumber):
) from err
except InvalidAuthError:
await self.coordinator.async_shutdown_device_and_start_reauth()
self.async_write_ha_state()

View File

@@ -53,7 +53,7 @@ from .entity import (
ShellyRpcAttributeEntity,
ShellySleepingBlockAttributeEntity,
ShellySleepingRpcAttributeEntity,
async_setup_entry_block,
async_setup_entry_attribute_entities,
async_setup_entry_rest,
async_setup_entry_rpc,
get_entity_rpc_device_info,
@@ -198,7 +198,7 @@ class RpcBluTrvSensor(RpcSensor):
)
BLOCK_SENSORS: dict[tuple[str, str], BlockSensorDescription] = {
SENSORS: dict[tuple[str, str], BlockSensorDescription] = {
("device", "battery"): BlockSensorDescription(
key="device|battery",
native_unit_of_measurement=PERCENTAGE,
@@ -525,6 +525,7 @@ RPC_SENSORS: Final = {
"power_rgbcct": RpcSensorDescription(
key="rgbcct",
sub_key="apower",
name="Power",
native_unit_of_measurement=UnitOfPower.WATT,
device_class=SensorDeviceClass.POWER,
state_class=SensorStateClass.MEASUREMENT,
@@ -962,6 +963,7 @@ RPC_SENSORS: Final = {
"energy_rgbcct": RpcSensorDescription(
key="rgbcct",
sub_key="aenergy",
name="Energy",
native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,
suggested_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
value=lambda status, _: status["total"],
@@ -1734,19 +1736,19 @@ def _async_setup_block_entry(
) -> None:
"""Set up entities for BLOCK device."""
if config_entry.data[CONF_SLEEP_PERIOD]:
async_setup_entry_block(
async_setup_entry_attribute_entities(
hass,
config_entry,
async_add_entities,
BLOCK_SENSORS,
SENSORS,
BlockSleepingSensor,
)
else:
async_setup_entry_block(
async_setup_entry_attribute_entities(
hass,
config_entry,
async_add_entities,
BLOCK_SENSORS,
SENSORS,
BlockSensor,
)
async_setup_entry_rest(

View File

@@ -36,7 +36,7 @@ from .entity import (
ShellyBlockAttributeEntity,
ShellyRpcAttributeEntity,
ShellySleepingBlockAttributeEntity,
async_setup_entry_block,
async_setup_entry_attribute_entities,
async_setup_entry_rpc,
rpc_call,
)
@@ -337,11 +337,11 @@ def _async_setup_block_entry(
coordinator = config_entry.runtime_data.block
assert coordinator
async_setup_entry_block(
async_setup_entry_attribute_entities(
hass, config_entry, async_add_entities, BLOCK_RELAY_SWITCHES, BlockRelaySwitch
)
async_setup_entry_block(
async_setup_entry_attribute_entities(
hass,
config_entry,
async_add_entities,

View File

@@ -33,6 +33,9 @@
},
"solar_elevation": {
"default": "mdi:theme-light-dark"
},
"solar_rising": {
"default": "mdi:sun-clock"
}
}
}

View File

@@ -18,6 +18,11 @@ from homeassistant.core import HomeAssistant
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.issue_registry import (
IssueSeverity,
async_create_issue,
async_delete_issue,
)
from homeassistant.helpers.typing import StateType
from .const import DOMAIN, SIGNAL_EVENTS_CHANGED, SIGNAL_POSITION_CHANGED
@@ -95,6 +100,13 @@ SENSOR_TYPES: tuple[SunSensorEntityDescription, ...] = (
native_unit_of_measurement=DEGREE,
signal=SIGNAL_POSITION_CHANGED,
),
SunSensorEntityDescription(
key="solar_rising",
translation_key="solar_rising",
value_fn=lambda data: data.rising,
entity_registry_enabled_default=False,
signal=SIGNAL_EVENTS_CHANGED,
),
)
@@ -143,6 +155,20 @@ class SunSensor(SensorEntity):
"""Register signal listener when added to hass."""
await super().async_added_to_hass()
if self.entity_description.key == "solar_rising":
async_create_issue(
self.hass,
DOMAIN,
"deprecated_sun_solar_rising",
breaks_in_ha_version="2026.1.0",
is_fixable=False,
severity=IssueSeverity.WARNING,
translation_key="deprecated_sun_solar_rising",
translation_placeholders={
"entity": self.entity_id,
},
)
self.async_on_remove(
async_dispatcher_connect(
self.hass,
@@ -150,3 +176,9 @@ class SunSensor(SensorEntity):
self.async_write_ha_state,
)
)
async def async_will_remove_from_hass(self) -> None:
"""Call when entity will be removed from hass."""
await super().async_will_remove_from_hass()
if self.entity_description.key == "solar_rising":
async_delete_issue(self.hass, DOMAIN, "deprecated_sun_solar_rising")

View File

@@ -24,7 +24,8 @@
"next_rising": { "name": "Next rising" },
"next_setting": { "name": "Next setting" },
"solar_azimuth": { "name": "Solar azimuth" },
"solar_elevation": { "name": "Solar elevation" }
"solar_elevation": { "name": "Solar elevation" },
"solar_rising": { "name": "Solar rising" }
}
},
"entity_component": {
@@ -36,5 +37,11 @@
}
}
},
"issues": {
"deprecated_sun_solar_rising": {
"description": "The 'Solar rising' sensor of the Sun integration is being deprecated; an equivalent 'Solar rising' binary sensor has been made available as a replacement. To resolve this issue, disable {entity}.",
"title": "Deprecated 'Solar rising' sensor"
}
},
"title": "Sun"
}

View File

@@ -46,6 +46,7 @@
"triggers": {
"changed": {
"description": "Triggers when the text changes.",
"description_configured": "[%key:component::text::triggers::changed::description%]",
"name": "When the text changes"
}
}

View File

@@ -15,7 +15,7 @@ from uiprotect.exceptions import BadRequest, ClientError, NotAuthorized
# diagnostics module will not be imported in the executor.
from uiprotect.test_util.anonymize import anonymize_data # noqa: F401
from homeassistant.config_entries import ConfigEntryState
from homeassistant.config_entries import ConfigEntry, ConfigEntryState
from homeassistant.const import CONF_API_KEY, EVENT_HOMEASSISTANT_STOP
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import (
@@ -208,7 +208,7 @@ async def async_remove_config_entry_device(
return True
async def async_migrate_entry(hass: HomeAssistant, entry: UFPConfigEntry) -> bool:
async def async_migrate_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Migrate entry."""
_LOGGER.debug("Migrating configuration from version %s", entry.version)

View File

@@ -39,7 +39,6 @@ from .entity import (
)
_KEY_DOOR = "door"
PARALLEL_UPDATES = 0
@dataclasses.dataclass(frozen=True, kw_only=True)

View File

@@ -33,7 +33,6 @@ from .entity import (
)
_LOGGER = logging.getLogger(__name__)
PARALLEL_UPDATES = 0
@dataclass(frozen=True, kw_only=True)

View File

@@ -32,7 +32,6 @@ from .entity import ProtectDeviceEntity
from .utils import get_camera_base_name
_LOGGER = logging.getLogger(__name__)
PARALLEL_UPDATES = 0
@callback
@@ -92,11 +91,7 @@ def _get_camera_channels(
# no RTSP enabled use first channel with no stream
if is_default and not camera.is_third_party_camera:
# Only create repair issue if RTSP is not disabled globally
if not data.disable_stream:
_create_rtsp_repair(hass, entry, data, camera)
else:
ir.async_delete_issue(hass, DOMAIN, f"rtsp_disabled_{camera.id}")
_create_rtsp_repair(hass, entry, data, camera)
yield camera, camera.channels[0], True
else:
ir.async_delete_issue(hass, DOMAIN, f"rtsp_disabled_{camera.id}")

View File

@@ -16,6 +16,7 @@ import voluptuous as vol
from homeassistant.config_entries import (
SOURCE_IGNORE,
ConfigEntry,
ConfigEntryState,
ConfigFlow,
ConfigFlowResult,
@@ -54,7 +55,7 @@ from .const import (
MIN_REQUIRED_PROTECT_V,
OUTDATED_LOG_MESSAGE,
)
from .data import UFPConfigEntry, async_last_update_was_successful
from .data import async_last_update_was_successful
from .discovery import async_start_discovery
from .utils import _async_resolve, _async_short_mac, _async_unifi_mac_from_hass
@@ -79,7 +80,7 @@ def _host_is_direct_connect(host: str) -> bool:
async def _async_console_is_offline(
hass: HomeAssistant,
entry: UFPConfigEntry,
entry: ConfigEntry,
) -> bool:
"""Check if a console is offline.
@@ -223,7 +224,7 @@ class ProtectFlowHandler(ConfigFlow, domain=DOMAIN):
@staticmethod
@callback
def async_get_options_flow(
config_entry: UFPConfigEntry,
config_entry: ConfigEntry,
) -> OptionsFlowHandler:
"""Get the options flow for this handler."""
return OptionsFlowHandler()

View File

@@ -40,8 +40,6 @@ from .data import (
)
from .entity import EventEntityMixin, ProtectDeviceEntity, ProtectEventMixin
PARALLEL_UPDATES = 0
# Select best thumbnail
# Prefer thumbnails with LPR data, sorted by confidence

View File

@@ -6,9 +6,8 @@ import logging
from typing import Any
from uiprotect.data import Light, ModelType, ProtectAdoptableDeviceModel
from uiprotect.data.devices import LightDeviceSettings
from homeassistant.components.light import ATTR_BRIGHTNESS, ColorMode, LightEntity
from homeassistant.components.light import ColorMode, LightEntity
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
@@ -16,7 +15,6 @@ from .data import ProtectDeviceType, UFPConfigEntry
from .entity import ProtectDeviceEntity
_LOGGER = logging.getLogger(__name__)
PARALLEL_UPDATES = 0
async def async_setup_entry(
@@ -73,36 +71,10 @@ class ProtectLight(ProtectDeviceEntity, LightEntity):
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn the light on."""
brightness = kwargs.get(ATTR_BRIGHTNESS)
led_level: int | None = None
if brightness is not None:
led_level = hass_to_unifi_brightness(brightness)
_LOGGER.debug(
"Turning on light with brightness %s (led_level=%s)",
brightness,
led_level,
)
else:
_LOGGER.debug("Turning on light")
await self.device.api.update_light_public(
self.device.id,
is_light_force_enabled=True,
light_device_settings=(
LightDeviceSettings(
is_indicator_enabled=self.device.light_device_settings.is_indicator_enabled,
led_level=led_level,
pir_duration=self.device.light_device_settings.pir_duration,
pir_sensitivity=self.device.light_device_settings.pir_sensitivity,
)
if led_level is not None
else None
),
)
_LOGGER.debug("Turning on light")
await self.device.api.set_light_is_led_force_on(self.device.id, True)
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn the light off."""
_LOGGER.debug("Turning off light")
await self.device.api.update_light_public(
self.device.id, is_light_force_enabled=False
)
await self.device.api.set_light_is_led_force_on(self.device.id, False)

View File

@@ -20,7 +20,6 @@ from .data import ProtectDeviceType, UFPConfigEntry
from .entity import ProtectDeviceEntity
_LOGGER = logging.getLogger(__name__)
PARALLEL_UPDATES = 0
async def async_setup_entry(

View File

@@ -40,7 +40,7 @@
"integration_type": "hub",
"iot_class": "local_push",
"loggers": ["uiprotect", "unifi_discovery"],
"requirements": ["uiprotect==7.31.0", "unifi-discovery==1.2.0"],
"requirements": ["uiprotect==7.29.0", "unifi-discovery==1.2.0"],
"ssdp": [
{
"manufacturer": "Ubiquiti Networks",

View File

@@ -23,12 +23,10 @@ from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .const import DOMAIN
from .data import ProtectDeviceType, UFPConfigEntry
from .entity import ProtectDeviceEntity
_LOGGER = logging.getLogger(__name__)
PARALLEL_UPDATES = 0
_SPEAKER_DESCRIPTION = MediaPlayerEntityDescription(
key="speaker",
@@ -79,13 +77,7 @@ class ProtectMediaPlayer(ProtectDeviceEntity, MediaPlayerEntity):
def _async_update_device_from_protect(self, device: ProtectDeviceType) -> None:
super()._async_update_device_from_protect(device)
updated_device = self.device
speaker_settings = updated_device.speaker_settings
volume = (
speaker_settings.speaker_volume
if speaker_settings.speaker_volume is not None
else speaker_settings.volume
)
self._attr_volume_level = float(volume / 100)
self._attr_volume_level = float(updated_device.speaker_settings.volume / 100)
if (
updated_device.talkback_stream is not None
@@ -130,10 +122,7 @@ class ProtectMediaPlayer(ProtectDeviceEntity, MediaPlayerEntity):
media_id = async_process_play_media_url(self.hass, play_item.url)
if media_type != MediaType.MUSIC:
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="only_music_supported",
)
raise HomeAssistantError("Only music media type is supported")
_LOGGER.debug(
"Playing Media %s for %s Speaker", media_id, self.device.display_name
@@ -142,11 +131,7 @@ class ProtectMediaPlayer(ProtectDeviceEntity, MediaPlayerEntity):
try:
await self.device.play_audio(media_id, blocking=False)
except StreamError as err:
_LOGGER.debug("Error playing audio: %s", err)
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="stream_error",
) from err
raise HomeAssistantError(err) from err
# update state after starting player
self._async_updated_event(self.device)

View File

@@ -29,8 +29,6 @@ from .entity import (
async_all_device_entities,
)
PARALLEL_UPDATES = 0
@dataclass(frozen=True, kw_only=True)
class ProtectNumberEntityDescription(
@@ -92,36 +90,6 @@ CAMERA_NUMBERS: tuple[ProtectNumberEntityDescription, ...] = (
ufp_set_method="set_mic_volume",
ufp_perm=PermRequired.WRITE,
),
ProtectNumberEntityDescription(
key="system_sounds_volume",
translation_key="system_sounds_volume",
icon="mdi:volume-high",
entity_category=EntityCategory.CONFIG,
native_unit_of_measurement=PERCENTAGE,
ufp_min=0,
ufp_max=100,
ufp_step=1,
ufp_required_field="feature_flags.has_speaker",
ufp_value="speaker_settings.volume",
ufp_enabled="feature_flags.has_speaker",
ufp_set_method="set_volume",
ufp_perm=PermRequired.WRITE,
),
ProtectNumberEntityDescription(
key="doorbell_ring_volume",
translation_key="doorbell_ring_volume",
icon="mdi:bell-ring",
entity_category=EntityCategory.CONFIG,
native_unit_of_measurement=PERCENTAGE,
ufp_min=0,
ufp_max=100,
ufp_step=1,
ufp_required_field="feature_flags.is_doorbell",
ufp_value="speaker_settings.ring_volume",
ufp_enabled="feature_flags.is_doorbell",
ufp_set_method="set_ring_volume",
ufp_perm=PermRequired.WRITE,
),
ProtectNumberEntityDescription(
key="zoom_position",
translation_key="zoom_level",

View File

@@ -10,6 +10,7 @@ import voluptuous as vol
from homeassistant import data_entry_flow
from homeassistant.components.repairs import ConfirmRepairFlow, RepairsFlow
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import issue_registry as ir
@@ -164,7 +165,7 @@ class RTSPRepair(ProtectRepair):
@callback
def _async_get_or_create_api_client(
hass: HomeAssistant, entry: UFPConfigEntry
hass: HomeAssistant, entry: ConfigEntry
) -> ProtectApiClient:
"""Get or create an API client."""
if data := async_get_data_for_entry_id(hass, entry.entry_id):

View File

@@ -45,7 +45,6 @@ from .utils import async_get_light_motion_current
_LOGGER = logging.getLogger(__name__)
_KEY_LIGHT_MOTION = "light_motion"
PARALLEL_UPDATES = 0
HDR_MODES = [
{"id": "always", "name": "Always On"},

View File

@@ -55,7 +55,6 @@ from .utils import async_get_light_motion_current
_LOGGER = logging.getLogger(__name__)
OBJECT_TYPE_NONE = "none"
PARALLEL_UPDATES = 0
@dataclass(frozen=True, kw_only=True)

View File

@@ -3,7 +3,6 @@
from __future__ import annotations
import asyncio
import logging
from typing import Any, cast
from pydantic import ValidationError
@@ -46,8 +45,6 @@ from .const import (
)
from .data import async_ufp_instance_for_config_entry_ids
_LOGGER = logging.getLogger(__name__)
SERVICE_ADD_DOORBELL_TEXT = "add_doorbell_text"
SERVICE_REMOVE_DOORBELL_TEXT = "remove_doorbell_text"
SERVICE_SET_PRIVACY_ZONE = "set_privacy_zone"
@@ -95,11 +92,7 @@ GET_USER_KEYRING_INFO_SCHEMA = vol.Schema(
def _async_get_ufp_instance(hass: HomeAssistant, device_id: str) -> ProtectApiClient:
device_registry = dr.async_get(hass)
if not (device_entry := device_registry.async_get(device_id)):
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="device_not_found",
translation_placeholders={"device_id": device_id},
)
raise HomeAssistantError(f"No device found for device id: {device_id}")
if device_entry.via_device_id is not None:
return _async_get_ufp_instance(hass, device_entry.via_device_id)
@@ -108,11 +101,7 @@ def _async_get_ufp_instance(hass: HomeAssistant, device_id: str) -> ProtectApiCl
if ufp_instance := async_ufp_instance_for_config_entry_ids(hass, config_entry_ids):
return ufp_instance
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="device_not_found",
translation_placeholders={"device_id": device_id},
)
raise HomeAssistantError(f"No device found for device id: {device_id}")
@callback
@@ -152,11 +141,7 @@ async def _async_service_call_nvr(
*(getattr(i.bootstrap.nvr, method)(*args, **kwargs) for i in instances)
)
except (ClientError, ValidationError) as err:
_LOGGER.debug("Error calling UniFi Protect service: %s", err)
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="service_error",
) from err
raise HomeAssistantError(str(err)) from err
async def add_doorbell_text(call: ServiceCall) -> None:
@@ -185,12 +170,7 @@ async def remove_privacy_zone(call: ServiceCall) -> None:
if remove_index is None:
raise ServiceValidationError(
translation_domain=DOMAIN,
translation_key="privacy_zone_not_found",
translation_placeholders={
"zone_name": name,
"camera_name": camera.display_name,
},
f"Could not find privacy zone with name {name} on camera {camera.display_name}."
)
def remove_zone() -> None:
@@ -250,10 +230,7 @@ async def get_user_keyring_info(call: ServiceCall) -> ServiceResponse:
camera = _async_get_ufp_camera(call)
ulp_users = camera.api.bootstrap.ulp_users.as_list()
if not ulp_users:
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="no_users_found",
)
raise HomeAssistantError("No users found, please check Protect permissions.")
user_keyrings: list[JsonValueType] = [
{

View File

@@ -20,9 +20,7 @@
"username": "[%key:common::config_flow::data::username%]"
},
"data_description": {
"api_key": "[%key:component::unifiprotect::config::step::user::data_description::api_key%]",
"password": "[%key:component::unifiprotect::config::step::user::data_description::password%]",
"username": "[%key:component::unifiprotect::config::step::user::data_description::username%]"
"api_key": "API key for your local user account."
},
"description": "Do you want to set up {name} ({ip_address})? You will need a local user created in your UniFi OS Console to log in with. Ubiquiti Cloud users will not work. For more information: {local_user_documentation_url}",
"title": "UniFi Protect discovered"
@@ -36,11 +34,8 @@
"username": "[%key:common::config_flow::data::username%]"
},
"data_description": {
"api_key": "[%key:component::unifiprotect::config::step::user::data_description::api_key%]",
"host": "[%key:component::unifiprotect::config::step::user::data_description::host%]",
"password": "[%key:component::unifiprotect::config::step::user::data_description::password%]",
"port": "[%key:component::unifiprotect::config::step::user::data_description::port%]",
"username": "[%key:component::unifiprotect::config::step::user::data_description::username%]"
"api_key": "API key for your local user account.",
"username": "Username for your local (not cloud) user account."
},
"description": "Your credentials or API key seem to be missing or invalid. For instructions on how to create a local user or generate a new API key, please refer to the documentation: {local_user_documentation_url}",
"title": "UniFi Protect reauth"
@@ -56,11 +51,7 @@
},
"data_description": {
"api_key": "API key for your local user account.",
"host": "Hostname or IP address of your UniFi Protect device.",
"password": "Password for your local user account.",
"port": "Port of your UniFi Protect device.",
"username": "Username for your local (not cloud) user account.",
"verify_ssl": "Verify SSL certificate of the UniFi Protect device."
"host": "Hostname or IP address of your UniFi Protect device."
},
"description": "You will need a local user created in your UniFi OS Console to log in with. Ubiquiti Cloud users will not work. For more information: {local_user_documentation_url}",
"title": "UniFi Protect setup"
@@ -289,9 +280,6 @@
"chime_duration": {
"name": "Chime duration"
},
"doorbell_ring_volume": {
"name": "Doorbell ring volume"
},
"infrared_custom_lux_trigger": {
"name": "Infrared custom lux trigger"
},
@@ -301,9 +289,6 @@
"motion_sensitivity": {
"name": "Motion sensitivity"
},
"system_sounds_volume": {
"name": "System sounds volume"
},
"volume": {
"name": "[%key:component::sensor::entity_component::volume::name%]"
},
@@ -582,26 +567,8 @@
"api_key_required": {
"message": "API key is required. Please reauthenticate this integration to provide an API key."
},
"device_not_found": {
"message": "No device found for device id: {device_id}"
},
"no_users_found": {
"message": "No users found, please check Protect permissions"
},
"only_music_supported": {
"message": "Only music media type is supported"
},
"privacy_zone_not_found": {
"message": "Could not find privacy zone with name {zone_name} on camera {camera_name}"
},
"protect_version": {
"message": "Your UniFi Protect version ({current_version}) is too old. Minimum required: {min_version}"
},
"service_error": {
"message": "Error calling UniFi Protect service, check the logs for more details"
},
"stream_error": {
"message": "Error playing audio, check the logs for more details"
"message": "Your UniFi Protect version ({current_version}) is too old. Minimum required: {min_version}."
}
},
"issues": {
@@ -660,12 +627,6 @@
"max_media": "Max number of event to load for Media Browser (increases RAM usage)",
"override_connection_host": "Override connection host"
},
"data_description": {
"all_updates": "Enable realtime metrics updates. Only use if you have enabled diagnostic sensors and want them updated in realtime.",
"disable_rtsp": "Disable the RTSP stream for all cameras. Use this if you don't need live video feeds.",
"max_media": "Maximum number of events to load in the Media Browser. Higher values use more RAM.",
"override_connection_host": "Override the connection host for the UniFi Protect device."
},
"description": "Realtime metrics option should only be enabled if you have enabled the diagnostics sensors and want them updated in realtime. If not enabled, they will only update once every 15 minutes.",
"title": "UniFi Protect options"
}

View File

@@ -36,7 +36,6 @@ from .entity import (
ATTR_PREV_MIC = "prev_mic_level"
ATTR_PREV_RECORD = "prev_record_mode"
PARALLEL_UPDATES = 0
@dataclass(frozen=True, kw_only=True)

View File

@@ -27,8 +27,6 @@ from .entity import (
async_all_device_entities,
)
PARALLEL_UPDATES = 0
@dataclass(frozen=True, kw_only=True)
class ProtectTextEntityDescription(ProtectSetableKeysMixin[T], TextEntityDescription):

View File

@@ -114,6 +114,7 @@
"triggers": {
"docked": {
"description": "Triggers when a vacuum cleaner has docked.",
"description_configured": "[%key:component::vacuum::triggers::docked::description%]",
"fields": {
"behavior": {
"description": "[%key:component::vacuum::common::trigger_behavior_description%]",
@@ -124,6 +125,7 @@
},
"errored": {
"description": "Triggers when a vacuum cleaner has errored.",
"description_configured": "[%key:component::vacuum::triggers::errored::description%]",
"fields": {
"behavior": {
"description": "[%key:component::vacuum::common::trigger_behavior_description%]",
@@ -134,6 +136,7 @@
},
"paused_cleaning": {
"description": "Triggers when a vacuum cleaner has paused cleaning.",
"description_configured": "[%key:component::vacuum::triggers::paused_cleaning::description%]",
"fields": {
"behavior": {
"description": "[%key:component::vacuum::common::trigger_behavior_description%]",
@@ -144,6 +147,7 @@
},
"started_cleaning": {
"description": "Triggers when a vacuum cleaner has started cleaning.",
"description_configured": "[%key:component::vacuum::triggers::started_cleaning::description%]",
"fields": {
"behavior": {
"description": "[%key:component::vacuum::common::trigger_behavior_description%]",

View File

@@ -13,5 +13,5 @@
"documentation": "https://www.home-assistant.io/integrations/vesync",
"iot_class": "cloud_polling",
"loggers": ["pyvesync"],
"requirements": ["pyvesync==3.3.2"]
"requirements": ["pyvesync==3.2.2"]
}

View File

@@ -14,7 +14,6 @@ from homeassistant.core import (
ServiceResponse,
SupportsResponse,
)
from homeassistant.helpers.httpx_client import get_async_client
from homeassistant.helpers.location import find_coordinates
from homeassistant.helpers.selector import (
BooleanSelector,
@@ -47,6 +46,7 @@ from .const import (
VEHICLE_TYPES,
)
from .coordinator import WazeTravelTimeCoordinator, async_get_travel_times
from .httpx_client import create_httpx_client
PLATFORMS = [Platform.SENSOR]
@@ -106,7 +106,8 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b
if SEMAPHORE not in hass.data.setdefault(DOMAIN, {}):
hass.data.setdefault(DOMAIN, {})[SEMAPHORE] = asyncio.Semaphore(1)
httpx_client = get_async_client(hass)
httpx_client = await create_httpx_client(hass)
client = WazeRouteCalculator(
region=config_entry.data[CONF_REGION].upper(), client=httpx_client
)
@@ -119,7 +120,7 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b
await hass.config_entries.async_forward_entry_setups(config_entry, PLATFORMS)
async def async_get_travel_times_service(service: ServiceCall) -> ServiceResponse:
httpx_client = get_async_client(hass)
httpx_client = await create_httpx_client(hass)
client = WazeRouteCalculator(
region=service.data[CONF_REGION].upper(), client=httpx_client
)

View File

@@ -0,0 +1,26 @@
"""Special httpx client for Waze Travel Time integration."""
import httpx
from homeassistant.core import HomeAssistant
from homeassistant.helpers.httpx_client import create_async_httpx_client
from homeassistant.util.hass_dict import HassKey
from .const import DOMAIN
DATA_HTTPX_ASYNC_CLIENT: HassKey[httpx.AsyncClient] = HassKey("httpx_async_client")
def create_transport() -> httpx.AsyncHTTPTransport:
"""Create a httpx transport which enforces the use of IPv4."""
return httpx.AsyncHTTPTransport(local_address="0.0.0.0")
async def create_httpx_client(hass: HomeAssistant) -> httpx.AsyncClient:
"""Create a httpx client which enforces the use of IPv4."""
if (client := hass.data[DOMAIN].get(DATA_HTTPX_ASYNC_CLIENT)) is None:
transport = await hass.async_add_executor_job(create_transport)
client = hass.data[DOMAIN][DATA_HTTPX_ASYNC_CLIENT] = create_async_httpx_client(
hass, transport=transport
)
return client

View File

@@ -24,9 +24,14 @@ from homeassistant.helpers.trigger import (
async_get_all_descriptions as async_get_all_trigger_descriptions,
)
from homeassistant.helpers.typing import ConfigType
from homeassistant.util.hass_dict import HassKey
_LOGGER = logging.getLogger(__name__)
FLAT_SERVICE_DESCRIPTIONS_CACHE: HassKey[
tuple[dict[str, dict[str, Any]], dict[str, dict[str, Any] | None]]
] = HassKey("websocket_automation_flat_service_description_cache")
@dataclass(slots=True, kw_only=True)
class _EntityFilter:
@@ -217,12 +222,29 @@ async def async_get_services_for_target(
) -> set[str]:
"""Get services for a target."""
descriptions = await async_get_all_service_descriptions(hass)
# Flatten dicts to be keyed by domain.name to match trigger/condition format
descriptions_flatten = {
f"{domain}.{service_name}": desc
for domain, services in descriptions.items()
for service_name, desc in services.items()
}
def get_flattened_service_descriptions() -> dict[str, dict[str, Any] | None]:
"""Get flattened service descriptions, with caching."""
if FLAT_SERVICE_DESCRIPTIONS_CACHE in hass.data:
cached_descriptions, cached_flat_descriptions = hass.data[
FLAT_SERVICE_DESCRIPTIONS_CACHE
]
# If the descriptions are the same, return the cached flattened version
if cached_descriptions is descriptions:
return cached_flat_descriptions
# Flatten dicts to be keyed by domain.name to match trigger/condition format
flat_descriptions = {
f"{domain}.{service_name}": desc
for domain, services in descriptions.items()
for service_name, desc in services.items()
}
hass.data[FLAT_SERVICE_DESCRIPTIONS_CACHE] = (
descriptions,
flat_descriptions,
)
return flat_descriptions
return _async_get_automation_components_for_target(
hass, target_selector, expand_group, descriptions_flatten
hass, target_selector, expand_group, get_flattened_service_descriptions()
)

View File

@@ -1553,9 +1553,7 @@ class Entity(
# Clear the remove future to handle entity added again after entity id change
self.__remove_future = None
self._platform_state = EntityPlatformState.NOT_ADDED
await self.platform.async_add_entities(
[self], config_subentry_id=registry_entry.config_subentry_id
)
await self.platform.async_add_entities([self])
@callback
def _async_unsubscribe_device_updates(self) -> None:

22
requirements_all.txt generated
View File

@@ -252,7 +252,7 @@ aioelectricitymaps==1.1.1
aioemonitor==1.0.5
# homeassistant.components.esphome
aioesphomeapi==42.9.0
aioesphomeapi==42.8.0
# homeassistant.components.matrix
# homeassistant.components.slack
@@ -393,7 +393,7 @@ aioruuvigateway==0.1.0
aiosenz==1.0.0
# homeassistant.components.shelly
aioshelly==13.22.0
aioshelly==13.21.0
# homeassistant.components.skybell
aioskybell==22.7.0
@@ -504,7 +504,7 @@ anova-wifi==0.17.0
anthemav==1.4.1
# homeassistant.components.anthropic
anthropic==0.75.0
anthropic==0.73.0
# homeassistant.components.mcp_server
anyio==4.10.0
@@ -675,7 +675,7 @@ bluetooth-data-tools==1.28.4
bond-async==0.2.1
# homeassistant.components.bosch_alarm
bosch-alarm-mode2==0.4.10
bosch-alarm-mode2==0.4.6
# homeassistant.components.bosch_shc
boschshcpy==0.2.107
@@ -1087,13 +1087,13 @@ google-genai==1.38.0
google-maps-routing==0.6.15
# homeassistant.components.nest
google-nest-sdm==9.1.1
google-nest-sdm==9.1.0
# homeassistant.components.google_photos
google-photos-library-api==0.12.1
# homeassistant.components.google_air_quality
google_air_quality_api==1.1.3
google_air_quality_api==1.1.2
# homeassistant.components.slide
# homeassistant.components.slide_local
@@ -2551,7 +2551,7 @@ python-overseerr==0.7.1
python-picnic-api2==1.3.1
# homeassistant.components.pooldose
python-pooldose==0.8.1
python-pooldose==0.8.0
# homeassistant.components.rabbitair
python-rabbitair==0.0.8
@@ -2560,7 +2560,7 @@ python-rabbitair==0.0.8
python-ripple-api==0.0.3
# homeassistant.components.roborock
python-roborock==3.8.4
python-roborock==3.8.1
# homeassistant.components.smarttub
python-smarttub==0.0.45
@@ -2630,7 +2630,7 @@ pyvera==0.3.16
pyversasense==0.0.6
# homeassistant.components.vesync
pyvesync==3.3.2
pyvesync==3.2.2
# homeassistant.components.vizio
pyvizio==0.1.61
@@ -2987,7 +2987,7 @@ thermopro-ble==1.1.2
thingspeak==1.0.0
# homeassistant.components.lg_thinq
thinqconnect==1.0.9
thinqconnect==1.0.8
# homeassistant.components.tikteck
tikteck==0.4
@@ -3053,7 +3053,7 @@ typedmonarchmoney==0.4.4
uasiren==0.0.1
# homeassistant.components.unifiprotect
uiprotect==7.31.0
uiprotect==7.29.0
# homeassistant.components.landisgyr_heat_meter
ultraheat-api==0.5.7

View File

@@ -243,7 +243,7 @@ aioelectricitymaps==1.1.1
aioemonitor==1.0.5
# homeassistant.components.esphome
aioesphomeapi==42.9.0
aioesphomeapi==42.8.0
# homeassistant.components.matrix
# homeassistant.components.slack
@@ -378,7 +378,7 @@ aioruuvigateway==0.1.0
aiosenz==1.0.0
# homeassistant.components.shelly
aioshelly==13.22.0
aioshelly==13.21.0
# homeassistant.components.skybell
aioskybell==22.7.0
@@ -480,7 +480,7 @@ anova-wifi==0.17.0
anthemav==1.4.1
# homeassistant.components.anthropic
anthropic==0.75.0
anthropic==0.73.0
# homeassistant.components.mcp_server
anyio==4.10.0
@@ -609,7 +609,7 @@ bluetooth-data-tools==1.28.4
bond-async==0.2.1
# homeassistant.components.bosch_alarm
bosch-alarm-mode2==0.4.10
bosch-alarm-mode2==0.4.6
# homeassistant.components.bosch_shc
boschshcpy==0.2.107
@@ -963,13 +963,13 @@ google-genai==1.38.0
google-maps-routing==0.6.15
# homeassistant.components.nest
google-nest-sdm==9.1.1
google-nest-sdm==9.1.0
# homeassistant.components.google_photos
google-photos-library-api==0.12.1
# homeassistant.components.google_air_quality
google_air_quality_api==1.1.3
google_air_quality_api==1.1.2
# homeassistant.components.slide
# homeassistant.components.slide_local
@@ -2132,13 +2132,13 @@ python-overseerr==0.7.1
python-picnic-api2==1.3.1
# homeassistant.components.pooldose
python-pooldose==0.8.1
python-pooldose==0.8.0
# homeassistant.components.rabbitair
python-rabbitair==0.0.8
# homeassistant.components.roborock
python-roborock==3.8.4
python-roborock==3.8.1
# homeassistant.components.smarttub
python-smarttub==0.0.45
@@ -2196,7 +2196,7 @@ pyuptimerobot==22.2.0
pyvera==0.3.16
# homeassistant.components.vesync
pyvesync==3.3.2
pyvesync==3.2.2
# homeassistant.components.vizio
pyvizio==0.1.61
@@ -2481,7 +2481,7 @@ thermobeacon-ble==0.10.0
thermopro-ble==1.1.2
# homeassistant.components.lg_thinq
thinqconnect==1.0.9
thinqconnect==1.0.8
# homeassistant.components.tilt_ble
tilt-ble==1.0.1
@@ -2538,7 +2538,7 @@ typedmonarchmoney==0.4.4
uasiren==0.0.1
# homeassistant.components.unifiprotect
uiprotect==7.31.0
uiprotect==7.29.0
# homeassistant.components.landisgyr_heat_meter
ultraheat-api==0.5.7

View File

@@ -475,6 +475,7 @@ def gen_strings_schema(config: Config, integration: Integration) -> vol.Schema:
{
vol.Required("name"): translation_value_validator,
vol.Required("description"): translation_value_validator,
vol.Required("description_configured"): translation_value_validator,
vol.Optional("fields"): cv.schema_with_slug_keys(
{
vol.Required("name"): str,
@@ -490,6 +491,7 @@ def gen_strings_schema(config: Config, integration: Integration) -> vol.Schema:
{
vol.Required("name"): translation_value_validator,
vol.Required("description"): translation_value_validator,
vol.Required("description_configured"): translation_value_validator,
vol.Optional("fields"): cv.schema_with_slug_keys(
{
vol.Required("name"): str,

View File

@@ -128,12 +128,6 @@ async def mock_init_component(
"""Initialize integration."""
model_list = AsyncPage(
data=[
ModelInfo(
id="claude-opus-4-5-20251101",
created_at=datetime.datetime(2025, 11, 1, 0, 0, tzinfo=datetime.UTC),
display_name="Claude Opus 4.5",
type="model",
),
ModelInfo(
id="claude-haiku-4-5-20251001",
created_at=datetime.datetime(2025, 10, 15, 0, 0, tzinfo=datetime.UTC),

View File

@@ -357,10 +357,6 @@ async def test_model_list(
assert options["type"] == FlowResultType.FORM
assert options["step_id"] == "advanced"
assert options["data_schema"].schema["chat_model"].config["options"] == [
{
"label": "Claude Opus 4.5",
"value": "claude-opus-4-5",
},
{
"label": "Claude Haiku 4.5",
"value": "claude-haiku-4-5",
@@ -383,11 +379,11 @@ async def test_model_list(
},
{
"label": "Claude Sonnet 3.7",
"value": "claude-3-7-sonnet-latest",
"value": "claude-3-7-sonnet",
},
{
"label": "Claude Haiku 3.5",
"value": "claude-3-5-haiku-latest",
"value": "claude-3-5-haiku",
},
{
"label": "Claude Haiku 3",

View File

@@ -4,7 +4,6 @@ from collections.abc import Generator
from typing import Any
from unittest.mock import AsyncMock, patch
from bosch_alarm_mode2.const import PANEL_FAMILY, PanelModel
from bosch_alarm_mode2.panel import Area, Door, Output, Point
from bosch_alarm_mode2.utils import Observable
import pytest
@@ -40,10 +39,10 @@ def model(request: pytest.FixtureRequest) -> Generator[str]:
@pytest.fixture
def extra_config_entry_data(
model: str, panel_model: PanelModel, config_flow_data: dict[str, Any]
model: str, model_name: str, config_flow_data: dict[str, Any]
) -> dict[str, Any]:
"""Return extra config entry data."""
return {CONF_MODEL: panel_model.name} | config_flow_data
return {CONF_MODEL: model_name} | config_flow_data
@pytest.fixture(params=[None])
@@ -65,12 +64,12 @@ def config_flow_data(model: str) -> dict[str, Any]:
@pytest.fixture
def panel_model(model: str) -> PanelModel | None:
def model_name(model: str) -> str | None:
"""Return extra config entry data."""
return {
"solution_3000": PanelModel("Solution 3000", PANEL_FAMILY.SOLUTION),
"amax_3000": PanelModel("AMAX 3000", PANEL_FAMILY.AMAX),
"b5512": PanelModel("B5512 (US1B)", PANEL_FAMILY.BG_SERIES),
"solution_3000": "Solution 3000",
"amax_3000": "AMAX 3000",
"b5512": "B5512 (US1B)",
}.get(model)
@@ -167,7 +166,7 @@ def mock_panel(
door: AsyncMock,
output: AsyncMock,
points: dict[int, AsyncMock],
panel_model: str,
model_name: str,
serial_number: str | None,
) -> Generator[AsyncMock]:
"""Define a fixture to set up Bosch Alarm."""
@@ -182,7 +181,7 @@ def mock_panel(
client.doors = {1: door}
client.outputs = {1: output}
client.points = points
client.model = panel_model
client.model = model_name
client.faults = []
client.events = []
client.panel_faults_ids = []

View File

@@ -28,7 +28,6 @@
'open': False,
}),
]),
'family': 'AMAX',
'firmware_version': '1.0.0',
'history_events': list([
]),
@@ -125,7 +124,6 @@
'open': False,
}),
]),
'family': 'BG_SERIES',
'firmware_version': '1.0.0',
'history_events': list([
]),
@@ -221,7 +219,6 @@
'open': False,
}),
]),
'family': 'SOLUTION',
'firmware_version': '1.0.0',
'history_events': list([
]),

View File

@@ -4,7 +4,6 @@ import asyncio
from typing import Any
from unittest.mock import AsyncMock
from bosch_alarm_mode2.const import PANEL_FAMILY, PanelModel
import pytest
from homeassistant.components.bosch_alarm.const import DOMAIN
@@ -23,7 +22,7 @@ async def test_form_user(
hass: HomeAssistant,
mock_setup_entry: AsyncMock,
mock_panel: AsyncMock,
panel_model: PanelModel,
model_name: str,
serial_number: str,
config_flow_data: dict[str, Any],
) -> None:
@@ -46,13 +45,13 @@ async def test_form_user(
config_flow_data,
)
assert result["type"] is FlowResultType.CREATE_ENTRY
assert result["title"] == f"Bosch {panel_model.name}"
assert result["title"] == f"Bosch {model_name}"
assert (
result["data"]
== {
CONF_HOST: "1.1.1.1",
CONF_PORT: 7700,
CONF_MODEL: panel_model.name,
CONF_MODEL: model_name,
}
| config_flow_data
)
@@ -212,7 +211,7 @@ async def test_dhcp_can_finish(
hass: HomeAssistant,
mock_setup_entry: AsyncMock,
mock_panel: AsyncMock,
panel_model: PanelModel,
model_name: str,
serial_number: str,
config_flow_data: dict[str, Any],
) -> None:
@@ -238,12 +237,12 @@ async def test_dhcp_can_finish(
await hass.async_block_till_done()
assert result["type"] is FlowResultType.CREATE_ENTRY
assert result["title"] == f"Bosch {panel_model.name}"
assert result["title"] == f"Bosch {model_name}"
assert result["data"] == {
CONF_HOST: "1.1.1.1",
CONF_MAC: "34:ea:34:b4:3b:5a",
CONF_PORT: 7700,
CONF_MODEL: panel_model.name,
CONF_MODEL: model_name,
**config_flow_data,
}
@@ -259,7 +258,7 @@ async def test_dhcp_exceptions(
hass: HomeAssistant,
mock_setup_entry: AsyncMock,
mock_panel: AsyncMock,
panel_model: PanelModel,
model_name: str,
serial_number: str,
config_flow_data: dict[str, Any],
exception: Exception,
@@ -317,7 +316,7 @@ async def test_dhcp_discovery_if_panel_setup_config_flow(
mock_config_entry: MockConfigEntry,
mock_panel: AsyncMock,
serial_number: str,
panel_model: PanelModel,
model_name: str,
config_flow_data: dict[str, Any],
) -> None:
"""Test DHCP discovery doesn't fail if a different panel was set up via config flow."""
@@ -347,12 +346,12 @@ async def test_dhcp_discovery_if_panel_setup_config_flow(
await hass.async_block_till_done()
assert result["type"] is FlowResultType.CREATE_ENTRY
assert result["title"] == f"Bosch {panel_model.name}"
assert result["title"] == f"Bosch {model_name}"
assert result["data"] == {
CONF_HOST: "4.5.6.7",
CONF_MAC: "34:ea:34:b4:3b:5a",
CONF_PORT: 7700,
CONF_MODEL: panel_model.name,
CONF_MODEL: model_name,
**config_flow_data,
}
assert mock_config_entry.unique_id == serial_number
@@ -396,7 +395,7 @@ async def test_dhcp_updates_mac(
mock_setup_entry: AsyncMock,
mock_config_entry: MockConfigEntry,
mock_panel: AsyncMock,
panel_model: PanelModel,
model_name: str,
serial_number: str,
config_flow_data: dict[str, Any],
) -> None:
@@ -425,7 +424,7 @@ async def test_reauth_flow_success(
mock_setup_entry: AsyncMock,
mock_config_entry: MockConfigEntry,
mock_panel: AsyncMock,
panel_model: PanelModel,
model_name: str,
serial_number: str,
config_flow_data: dict[str, Any],
) -> None:
@@ -460,7 +459,7 @@ async def test_reauth_flow_error(
mock_setup_entry: AsyncMock,
mock_config_entry: MockConfigEntry,
mock_panel: AsyncMock,
panel_model: PanelModel,
model_name: str,
serial_number: str,
config_flow_data: dict[str, Any],
exception: Exception,
@@ -495,7 +494,7 @@ async def test_reconfig_flow(
mock_setup_entry: AsyncMock,
mock_config_entry: MockConfigEntry,
mock_panel: AsyncMock,
panel_model: PanelModel,
model_name: str,
serial_number: str,
config_flow_data: dict[str, Any],
) -> None:
@@ -530,7 +529,7 @@ async def test_reconfig_flow(
assert mock_config_entry.data == {
CONF_HOST: "1.1.1.1",
CONF_PORT: 7700,
CONF_MODEL: panel_model.name,
CONF_MODEL: model_name,
**config_flow_data,
}
@@ -541,7 +540,7 @@ async def test_reconfig_flow_incorrect_model(
mock_setup_entry: AsyncMock,
mock_config_entry: MockConfigEntry,
mock_panel: AsyncMock,
panel_model: PanelModel,
model_name: str,
serial_number: str,
config_flow_data: dict[str, Any],
) -> None:
@@ -557,7 +556,7 @@ async def test_reconfig_flow_incorrect_model(
},
)
mock_panel.model = PanelModel("Solution 3000", family=PANEL_FAMILY.SOLUTION)
mock_panel.model = "Solution 3000"
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "user"

View File

@@ -3,7 +3,6 @@
from typing import Any
from unittest.mock import AsyncMock
from bosch_alarm_mode2.const import PanelModel
from syrupy.assertion import SnapshotAssertion
from homeassistant.core import HomeAssistant
@@ -20,7 +19,7 @@ async def test_diagnostics(
hass_client: ClientSessionGenerator,
mock_panel: AsyncMock,
area: AsyncMock,
panel_model: PanelModel,
model_name: str,
serial_number: str,
snapshot: SnapshotAssertion,
mock_config_entry: MockConfigEntry,

View File

@@ -0,0 +1,297 @@
"""Test cover trigger."""
from collections.abc import Generator
from unittest.mock import patch
import pytest
from homeassistant.components.cover import ATTR_CURRENT_POSITION, CoverState
from homeassistant.const import ATTR_DEVICE_CLASS, ATTR_LABEL_ID, CONF_ENTITY_ID
from homeassistant.core import HomeAssistant, ServiceCall
from homeassistant.setup import async_setup_component
from tests.components import (
StateDescription,
arm_trigger,
parametrize_target_entities,
parametrize_trigger_states,
set_or_remove_state,
target_entities,
)
@pytest.fixture(autouse=True, name="stub_blueprint_populate")
def stub_blueprint_populate_autouse(stub_blueprint_populate: None) -> None:
"""Stub copying the blueprints to the config folder."""
@pytest.fixture(name="enable_experimental_triggers_conditions")
def enable_experimental_triggers_conditions() -> Generator[None]:
"""Enable experimental triggers and conditions."""
with patch(
"homeassistant.components.labs.async_is_preview_feature_enabled",
return_value=True,
):
yield
@pytest.fixture
async def target_covers(hass: HomeAssistant) -> list[str]:
"""Create multiple cover entities associated with different targets."""
return await target_entities(hass, "cover")
def parametrize_opened_trigger_states(
trigger: str, device_class: str
) -> list[tuple[str, dict, str, list[StateDescription]]]:
"""Parametrize states and expected service call counts.
Returns a list of tuples with (trigger, trigger_options,
list of StateDescription).
"""
additional_attributes = {ATTR_DEVICE_CLASS: device_class}
return [
# Test fully_opened = True
*(
(s[0], {"fully_opened": True}, *s[1:])
for s in parametrize_trigger_states(
trigger=trigger,
target_states=[
(CoverState.OPEN, {}),
(CoverState.OPENING, {}),
(CoverState.OPEN, {ATTR_CURRENT_POSITION: 100}),
(CoverState.OPENING, {ATTR_CURRENT_POSITION: 100}),
],
other_states=[
(CoverState.CLOSED, {}),
(CoverState.OPEN, {ATTR_CURRENT_POSITION: 0}),
],
additional_attributes=additional_attributes,
trigger_from_none=False,
)
),
# Test fully_opened = False
*(
(s[0], {}, *s[1:])
for s in parametrize_trigger_states(
trigger=trigger,
target_states=[
(CoverState.OPEN, {}),
(CoverState.OPENING, {}),
(CoverState.OPEN, {ATTR_CURRENT_POSITION: 1}),
(CoverState.OPENING, {ATTR_CURRENT_POSITION: 1}),
],
other_states=[
(CoverState.CLOSED, {}),
(CoverState.CLOSED, {ATTR_CURRENT_POSITION: 0}),
],
additional_attributes=additional_attributes,
trigger_from_none=False,
)
),
]
@pytest.mark.parametrize(
"trigger_key",
[
"cover.awning_opened",
"cover.blind_opened",
"cover.curtain_opened",
"cover.door_opened",
"cover.garage_opened",
"cover.gate_opened",
"cover.shade_opened",
"cover.shutter_opened",
"cover.window_opened",
],
)
async def test_cover_triggers_gated_by_labs_flag(
hass: HomeAssistant, caplog: pytest.LogCaptureFixture, trigger_key: str
) -> None:
"""Test the cover triggers are gated by the labs flag."""
await arm_trigger(hass, trigger_key, None, {ATTR_LABEL_ID: "test_label"})
assert (
"Unnamed automation failed to setup triggers and has been disabled: Trigger "
f"'{trigger_key}' requires the experimental 'New triggers and conditions' "
"feature to be enabled in Home Assistant Labs settings (feature flag: "
"'new_triggers_conditions')"
) in caplog.text
@pytest.mark.usefixtures("enable_experimental_triggers_conditions")
@pytest.mark.parametrize(
("trigger_target_config", "entity_id", "entities_in_target"),
parametrize_target_entities("cover"),
)
@pytest.mark.parametrize(
("trigger", "trigger_options", "states"),
[
*parametrize_opened_trigger_states("cover.awning_opened", "awning"),
*parametrize_opened_trigger_states("cover.blind_opened", "blind"),
*parametrize_opened_trigger_states("cover.curtain_opened", "curtain"),
*parametrize_opened_trigger_states("cover.door_opened", "door"),
*parametrize_opened_trigger_states("cover.garage_opened", "garage"),
*parametrize_opened_trigger_states("cover.gate_opened", "gate"),
*parametrize_opened_trigger_states("cover.shade_opened", "shade"),
*parametrize_opened_trigger_states("cover.shutter_opened", "shutter"),
*parametrize_opened_trigger_states("cover.window_opened", "window"),
],
)
async def test_cover_state_attribute_trigger_behavior_any(
hass: HomeAssistant,
service_calls: list[ServiceCall],
target_covers: list[str],
trigger_target_config: dict,
entity_id: str,
entities_in_target: int,
trigger: str,
trigger_options: dict,
states: list[StateDescription],
) -> None:
"""Test that the cover state trigger fires when any cover state changes to a specific state."""
await async_setup_component(hass, "cover", {})
other_entity_ids = set(target_covers) - {entity_id}
# Set all covers, including the tested cover, to the initial state
for eid in target_covers:
set_or_remove_state(hass, eid, states[0])
await hass.async_block_till_done()
await arm_trigger(hass, trigger, trigger_options, trigger_target_config)
for state in states[1:]:
set_or_remove_state(hass, entity_id, state)
await hass.async_block_till_done()
assert len(service_calls) == state["count"]
for service_call in service_calls:
assert service_call.data[CONF_ENTITY_ID] == entity_id
service_calls.clear()
# Check if changing other covers also triggers
for other_entity_id in other_entity_ids:
set_or_remove_state(hass, other_entity_id, state)
await hass.async_block_till_done()
assert len(service_calls) == (entities_in_target - 1) * state["count"]
service_calls.clear()
@pytest.mark.usefixtures("enable_experimental_triggers_conditions")
@pytest.mark.parametrize(
("trigger_target_config", "entity_id", "entities_in_target"),
parametrize_target_entities("cover"),
)
@pytest.mark.parametrize(
("trigger", "trigger_options", "states"),
[
*parametrize_opened_trigger_states("cover.awning_opened", "awning"),
*parametrize_opened_trigger_states("cover.blind_opened", "blind"),
*parametrize_opened_trigger_states("cover.curtain_opened", "curtain"),
*parametrize_opened_trigger_states("cover.door_opened", "door"),
*parametrize_opened_trigger_states("cover.garage_opened", "garage"),
*parametrize_opened_trigger_states("cover.gate_opened", "gate"),
*parametrize_opened_trigger_states("cover.shade_opened", "shade"),
*parametrize_opened_trigger_states("cover.shutter_opened", "shutter"),
*parametrize_opened_trigger_states("cover.window_opened", "window"),
],
)
async def test_cover_state_attribute_trigger_behavior_first(
hass: HomeAssistant,
service_calls: list[ServiceCall],
target_covers: list[str],
trigger_target_config: dict,
entity_id: str,
entities_in_target: int,
trigger: str,
trigger_options: dict,
states: list[StateDescription],
) -> None:
"""Test that the cover state trigger fires when the first cover state changes to a specific state."""
await async_setup_component(hass, "cover", {})
other_entity_ids = set(target_covers) - {entity_id}
# Set all covers, including the tested cover, to the initial state
for eid in target_covers:
set_or_remove_state(hass, eid, states[0])
await hass.async_block_till_done()
await arm_trigger(
hass,
trigger,
{"behavior": "first"} | trigger_options,
trigger_target_config,
)
for state in states[1:]:
set_or_remove_state(hass, entity_id, state)
await hass.async_block_till_done()
assert len(service_calls) == state["count"]
for service_call in service_calls:
assert service_call.data[CONF_ENTITY_ID] == entity_id
service_calls.clear()
# Triggering other covers should not cause the trigger to fire again
for other_entity_id in other_entity_ids:
set_or_remove_state(hass, other_entity_id, state)
await hass.async_block_till_done()
assert len(service_calls) == 0
@pytest.mark.usefixtures("enable_experimental_triggers_conditions")
@pytest.mark.parametrize(
("trigger_target_config", "entity_id", "entities_in_target"),
parametrize_target_entities("cover"),
)
@pytest.mark.parametrize(
("trigger", "trigger_options", "states"),
[
*parametrize_opened_trigger_states("cover.awning_opened", "awning"),
*parametrize_opened_trigger_states("cover.blind_opened", "blind"),
*parametrize_opened_trigger_states("cover.curtain_opened", "curtain"),
*parametrize_opened_trigger_states("cover.door_opened", "door"),
*parametrize_opened_trigger_states("cover.garage_opened", "garage"),
*parametrize_opened_trigger_states("cover.gate_opened", "gate"),
*parametrize_opened_trigger_states("cover.shade_opened", "shade"),
*parametrize_opened_trigger_states("cover.shutter_opened", "shutter"),
*parametrize_opened_trigger_states("cover.window_opened", "window"),
],
)
async def test_cover_state_attribute_trigger_behavior_last(
hass: HomeAssistant,
service_calls: list[ServiceCall],
target_covers: list[str],
trigger_target_config: dict,
entity_id: str,
entities_in_target: int,
trigger: str,
trigger_options: dict,
states: list[StateDescription],
) -> None:
"""Test that the cover state trigger fires when the last cover state changes to a specific state."""
await async_setup_component(hass, "cover", {})
other_entity_ids = set(target_covers) - {entity_id}
# Set all covers, including the tested cover, to the initial state
for eid in target_covers:
set_or_remove_state(hass, eid, states[0])
await hass.async_block_till_done()
await arm_trigger(
hass, trigger, {"behavior": "last"} | trigger_options, trigger_target_config
)
for state in states[1:]:
for other_entity_id in other_entity_ids:
set_or_remove_state(hass, other_entity_id, state)
await hass.async_block_till_done()
assert len(service_calls) == 0
set_or_remove_state(hass, entity_id, state)
await hass.async_block_till_done()
assert len(service_calls) == state["count"]
for service_call in service_calls:
assert service_call.data[CONF_ENTITY_ID] == entity_id
service_calls.clear()

View File

@@ -91,7 +91,6 @@ async def integration_fixture(
"door_lock_with_unbolt",
"eberle_ute3000",
"ecovacs_deebot",
"eufy_vacuum_omni_e28",
"eve_contact_sensor",
"eve_energy_20ecn4101",
"eve_energy_plug",
@@ -114,7 +113,6 @@ async def integration_fixture(
"light_sensor",
"microwave_oven",
"mock_lock",
"mock_thermostat",
"mounted_dimmable_load_control_fixture",
"multi_endpoint_light",
"occupancy_sensor",

View File

@@ -1,652 +0,0 @@
{
"node_id": 40,
"date_commissioned": "2025-11-27T01:47:55.787124",
"last_interview": "2025-11-27T01:49:41.087618",
"interview_version": 6,
"available": true,
"is_bridge": false,
"attributes": {
"0/3/0": 0,
"0/3/1": 2,
"0/3/65532": 0,
"0/3/65533": 5,
"0/3/65528": [],
"0/3/65529": [0],
"0/3/65531": [0, 1, 65528, 65529, 65531, 65532, 65533],
"0/29/0": [
{
"0": 22,
"1": 1
}
],
"0/29/1": [3, 29, 31, 40, 46, 48, 51, 60, 62, 63],
"0/29/2": [],
"0/29/3": [1],
"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": [
{
"254": 5
},
{
"254": 5
},
{
"254": 6
},
{
"1": 5,
"2": 2,
"3": [112233],
"4": null,
"254": 7
}
],
"0/31/2": 4,
"0/31/3": 3,
"0/31/4": 4,
"0/31/65532": 0,
"0/31/65533": 2,
"0/31/65528": [],
"0/31/65529": [],
"0/31/65531": [0, 2, 3, 4, 65528, 65529, 65531, 65532, 65533],
"0/40/0": 18,
"0/40/1": "Anker Innovations Technology",
"0/40/2": 5427,
"0/40/3": "eufy Robot Vacuum Omni E28",
"0/40/4": 10,
"0/40/5": "",
"0/40/6": "**REDACTED**",
"0/40/7": 7,
"0/40/8": "V7",
"0/40/9": 3,
"0/40/10": "3.0",
"0/40/11": "20250107",
"0/40/13": "https://www.eufy.com/collections/robot-vacuum-with-docking-station?ref=navimenu_1_2_2_all_copy",
"0/40/14": "2BAVS-AB6031X-44PE",
"0/40/15": "*********************",
"0/40/16": false,
"0/40/18": "*********************",
"0/40/19": {
"0": 3,
"1": 65535
},
"0/40/21": 17039360,
"0/40/22": 1,
"0/40/65532": 0,
"0/40/65533": 4,
"0/40/65528": [],
"0/40/65529": [],
"0/40/65531": [
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 18, 19, 21, 22,
65528, 65529, 65531, 65532, 65533
],
"0/46/0": [1],
"0/46/65532": 0,
"0/46/65533": 1,
"0/46/65528": [],
"0/46/65529": [],
"0/46/65531": [0, 65528, 65529, 65531, 65532, 65533],
"0/48/0": 0,
"0/48/1": {
"0": 60,
"1": 900
},
"0/48/2": 0,
"0/48/3": 2,
"0/48/4": true,
"0/48/65532": 0,
"0/48/65533": 2,
"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/51/0": [
{
"0": "p2p0",
"1": false,
"2": null,
"3": null,
"4": "Mr4pWXYJ",
"5": ["wKivAQ=="],
"6": [],
"7": 1
},
{
"0": "wlan0",
"1": true,
"2": null,
"3": null,
"4": "ML4pWXYJ",
"5": ["wKgUKQ=="],
"6": ["/oAAAAAAAADnm2WwWr92vA=="],
"7": 1
},
{
"0": "sit0",
"1": false,
"2": null,
"3": null,
"4": "AAAAAAAA",
"5": [],
"6": [],
"7": 0
},
{
"0": "lo",
"1": true,
"2": null,
"3": null,
"4": "AAAAAAAA",
"5": ["fwAAAQ=="],
"6": ["AAAAAAAAAAAAAAAAAAAAAQ=="],
"7": 0
}
],
"0/51/1": 41,
"0/51/2": 159947,
"0/51/3": 3133,
"0/51/4": 1,
"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, 3, 4, 8, 65528, 65529, 65531, 65532, 65533],
"0/60/0": 0,
"0/60/1": null,
"0/60/2": null,
"0/60/65532": 0,
"0/60/65533": 1,
"0/60/65528": [],
"0/60/65529": [0, 2],
"0/60/65531": [0, 1, 2, 65528, 65529, 65531, 65532, 65533],
"0/62/0": [
{
"254": 5
},
{
"254": 6
},
{
"1": "FTABAQEkAgE3AyQTAhgmBIAigScmBYAlTTo3BiQVAiQRKBgkBwEkCAEwCUEEzo04tfIUWFsjmoJzC0tgzeNlmT2cF6jmbDUuiF5q9X4b2kR9IFHTZnvd/gxTq538148FD9yBam9FTVotQe8wVDcKNQEoARgkAgE2AwQCBAEYMAQUKoF/1myXOXBAGIBagkz+UR460bQwBRRwoL/FqInZqSh+z7QAK0r1ew8axhgwC0DlvLjZ5VajnaTLnKa2JsKRrjratV6Kr0Ip3vkNck4zW9vpKVoHj3LKptNBQ4ROHaZX6eZNOZXXn3ikvKKo/pzGGA==",
"2": "FTABAQEkAgE3AyQUARgmBIAigScmBYAlTTo3BiQTAhgkBwEkCAEwCUEE2El8wzEcEHk4un8ZJSR8RWH86lcOvI5Q5bfSmDEaoEOyvYLA4Td/T0i7ZFpv9X0+CFDOgK9vV4sZ7sN8MSxMsTcKNQEpARgkAmAwBBRwoL/FqInZqSh+z7QAK0r1ew8axjAFFHGq+OWnV3HomxvqSHDxukU8THLXGDALQBMwck3ryDmue0Fx8kGCNAA8SjFWZF1kh+JWaesBrNwcO3oV7dEIvAblOuMmnolx5Wf/5Hbp8YRjEzKxnFyddR0Y",
"254": 7
}
],
"0/62/1": [
{
"1": "BFhpm8fVgw4hzcuwFGwSe59XhvdUHtMntaUUbgCX0jqoaA1fjjcRYrZCA0PDImdLtZSkrUdug3S/euAVf4gvaKo=",
"2": 4937,
"3": 3672061662,
"4": 4176574991,
"5": "JVChewy",
"254": 5
},
{
"1": "BP+6ik8SJgoRwv2p99/fGYj5G4xjjiyCNSGM83kGa6FxiLEceE4RbQar+xbksBwnzBqmqYA7PwwIZ8jdL5zKSLs=",
"2": 4996,
"3": 2560414293,
"4": 3062529239,
"5": "",
"254": 6
},
{
"1": "BC59F5uD+hRhy6qCXVp+pMpuBx68wgc6eTF57OLWHJ3+Wq1/bs3OLFr2vh4vCBzvG7C0Ijo6yKK4JERyWEtFS1E=",
"2": 4939,
"3": 2,
"4": 40,
"5": "JV Home",
"254": 7
}
],
"0/62/2": 16,
"0/62/3": 3,
"0/62/4": [
"FTABAQAkAgE3AyYUWi+aACYV3jbf2hgmBORpujAkBQA3BiYUWi+aACYV3jbf2hgkBwEkCAEwCUEEWGmbx9WDDiHNy7AUbBJ7n1eG91Qe0ye1pRRuAJfSOqhoDV+ONxFitkIDQ8MiZ0u1lKStR26DdL964BV/iC9oqjcKNQEpARgkAmAwBBSE8GakJIDzzseC1x/uiMKWPWSNSjAFFITwZqQkgPPOx4LXH+6IwpY9ZI1KGDALQB2kDCqAOX3YSsiOMWzA3Zq43VKqux6XQb3UtoELJok73SJIjo9YJ5UQ9bMIQIDZkW2ckuD2t4oGKSwY8tnsTwAY",
"FTABAQAkAgE3AycU7bEVXp1kzNEmFVXSnJgYJgR5MYMuJAUANwYnFO2xFV6dZMzRJhVV0pyYGCQHASQIATAJQQT/uopPEiYKEcL9qfff3xmI+RuMY44sgjUhjPN5BmuhcYixHHhOEW0Gq/sW5LAcJ8wapqmAOz8MCGfI3S+cyki7Nwo1ASkBGCQCYDAEFE18X69qbhhbH5f5+ExXr4ahWpvBMAUUTXxfr2puGFsfl/n4TFevhqFam8EYMAtAOhpZE0/FxSRhumv4BbBDb2ogpiwzr7lrC8FBk5BZxn2L0yNLqxTDNiOPuqv3eYgsISs3KyMDhvI05izsgLaHqhg=",
"FTABAQEkAgE3AyQUARgmBIAigScmBYAlTTo3BiQUARgkBwEkCAEwCUEELn0Xm4P6FGHLqoJdWn6kym4HHrzCBzp5MXns4tYcnf5arX9uzc4sWva+Hi8IHO8bsLQiOjrIorgkRHJYS0VLUTcKNQEpARgkAmAwBBRxqvjlp1dx6Jsb6khw8bpFPExy1zAFFHGq+OWnV3HomxvqSHDxukU8THLXGDALQEk9bbDTdpQroquObcShGBnZjzzXHVDRWxMp7o3i977ujuEdGiVOZVKQxNDO2wh6S7mEglvWP4qw4oE1+AEEPCgY"
],
"0/62/5": 7,
"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],
"1/3/0": 0,
"1/3/1": 2,
"1/3/65532": 0,
"1/3/65533": 5,
"1/3/65528": [],
"1/3/65529": [0],
"1/3/65531": [0, 1, 65528, 65529, 65531, 65532, 65533],
"1/29/0": [
{
"0": 17,
"1": 1
},
{
"0": 116,
"1": 1
}
],
"1/29/1": [3, 29, 47, 84, 85, 97, 336],
"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/47/0": 1,
"1/47/1": 0,
"1/47/2": "Battery",
"1/47/12": 200,
"1/47/14": 0,
"1/47/15": false,
"1/47/16": 0,
"1/47/26": 2,
"1/47/28": false,
"1/47/31": [],
"1/47/65532": 6,
"1/47/65533": 3,
"1/47/65528": [],
"1/47/65529": [],
"1/47/65531": [
0, 1, 2, 12, 14, 15, 16, 26, 28, 31, 65528, 65529, 65531, 65532, 65533
],
"1/84/0": [
{
"0": "Idle",
"1": 0,
"2": [
{
"1": 16384
}
]
},
{
"0": "Cleaning",
"1": 1,
"2": [
{
"1": 16385
}
]
},
{
"0": "Mapping",
"1": 2,
"2": [
{
"1": 16386
}
]
}
],
"1/84/1": 0,
"1/84/65532": 0,
"1/84/65533": 3,
"1/84/65528": [1],
"1/84/65529": [0],
"1/84/65531": [0, 1, 65528, 65529, 65531, 65532, 65533],
"1/85/0": [
{
"0": "Auto Mop & Vacuum",
"1": 0,
"2": [
{
"1": 0
},
{
"1": 16386
},
{
"1": 16385
}
]
},
{
"0": "Auto Vacuum",
"1": 1,
"2": [
{
"1": 0
},
{
"1": 16385
}
]
},
{
"0": "Auto Deep Mop & Vacuum",
"1": 2,
"2": [
{
"1": 0
},
{
"1": 16384
},
{
"1": 16386
},
{
"1": 16385
}
]
},
{
"0": "Quick Mop & Vacuum",
"1": 3,
"2": [
{
"1": 1
},
{
"1": 16386
},
{
"1": 16385
}
]
},
{
"0": "Quick Vacuum",
"1": 4,
"2": [
{
"1": 1
},
{
"1": 16385
}
]
},
{
"0": "Low Noise Mop & Vacuum",
"1": 5,
"2": [
{
"1": 3
},
{
"1": 16386
},
{
"1": 16385
}
]
},
{
"0": "Low Noise Vacuum",
"1": 6,
"2": [
{
"1": 3
},
{
"1": 16385
}
]
},
{
"0": "Eufy Customize Mode",
"1": 7,
"2": [
{
"1": 0
}
]
}
],
"1/85/1": 0,
"1/85/65532": 65536,
"1/85/65533": 3,
"1/85/65528": [1],
"1/85/65529": [0],
"1/85/65531": [0, 1, 65528, 65529, 65531, 65532, 65533],
"1/97/0": null,
"1/97/1": null,
"1/97/3": [
{
"0": 0
},
{
"0": 1
},
{
"0": 2
},
{
"0": 3
},
{
"0": 64
},
{
"0": 65
},
{
"0": 66
}
],
"1/97/4": 0,
"1/97/5": {
"0": 0
},
"1/97/65532": 0,
"1/97/65533": 2,
"1/97/65528": [4],
"1/97/65529": [0, 3, 128],
"1/97/65531": [0, 1, 3, 4, 5, 65528, 65529, 65531, 65532, 65533],
"1/336/0": [
{
"0": 40001,
"1": 4,
"2": {
"0": {
"0": "Dining Room",
"1": null,
"2": null
},
"1": null
}
},
{
"0": 40002,
"1": 4,
"2": {
"0": {
"0": "Spare Bedroom",
"1": null,
"2": null
},
"1": null
}
},
{
"0": 40003,
"1": 4,
"2": {
"0": {
"0": "Living Room",
"1": null,
"2": null
},
"1": null
}
},
{
"0": 40004,
"1": 4,
"2": {
"0": {
"0": "Kitchen",
"1": null,
"2": null
},
"1": null
}
},
{
"0": 40005,
"1": 4,
"2": {
"0": {
"0": "Master bedroom",
"1": null,
"2": null
},
"1": null
}
},
{
"0": 40006,
"1": 4,
"2": {
"0": {
"0": "Laundry Room",
"1": null,
"2": null
},
"1": null
}
},
{
"0": 40007,
"1": 4,
"2": {
"0": {
"0": "Master Closet",
"1": null,
"2": null
},
"1": null
}
},
{
"0": 40008,
"1": 4,
"2": {
"0": {
"0": "Back Hallway",
"1": null,
"2": null
},
"1": null
}
},
{
"0": 40009,
"1": 4,
"2": {
"0": {
"0": "Home Office",
"1": null,
"2": null
},
"1": null
}
},
{
"0": 40010,
"1": 4,
"2": {
"0": {
"0": "room_10",
"1": null,
"2": null
},
"1": null
}
},
{
"0": 40011,
"1": 4,
"2": {
"0": {
"0": "Gym",
"1": null,
"2": null
},
"1": null
}
},
{
"0": 40012,
"1": 4,
"2": {
"0": {
"0": "room_12",
"1": null,
"2": null
},
"1": null
}
},
{
"0": 40013,
"1": 4,
"2": {
"0": {
"0": "Master Bathroom",
"1": null,
"2": null
},
"1": null
}
},
{
"0": 40014,
"1": 4,
"2": {
"0": {
"0": "Front Hallway",
"1": null,
"2": null
},
"1": null
}
},
{
"0": 40015,
"1": 4,
"2": {
"0": {
"0": "Guest Bathroom",
"1": null,
"2": null
},
"1": null
}
}
],
"1/336/1": [
{
"0": 4,
"1": "map_4"
}
],
"1/336/2": [],
"1/336/3": null,
"1/336/5": [],
"1/336/65532": 6,
"1/336/65533": 1,
"1/336/65528": [1],
"1/336/65529": [0],
"1/336/65531": [0, 1, 2, 3, 5, 65528, 65529, 65531, 65532, 65533]
},
"attribute_subscriptions": []
}

View File

@@ -1,526 +0,0 @@
{
"node_id": 150,
"date_commissioned": "2025-11-18T06:53:08.679289",
"last_interview": "2025-11-18T06:53:08.679325",
"interview_version": 6,
"available": true,
"is_bridge": false,
"attributes": {
"0/49/0": 1,
"0/49/1": [
{
"0": "ZW5zMzM=",
"1": true
}
],
"0/49/4": true,
"0/49/5": 0,
"0/49/6": "ZW5zMzM=",
"0/49/7": null,
"0/49/65532": 4,
"0/49/65533": 2,
"0/49/65528": [],
"0/49/65529": [],
"0/49/65531": [0, 1, 4, 5, 6, 7, 65532, 65533, 65528, 65529, 65531],
"0/65/0": [],
"0/65/65532": 0,
"0/65/65533": 1,
"0/65/65528": [],
"0/65/65529": [],
"0/65/65531": [0, 65532, 65533, 65528, 65529, 65531],
"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": [5, 2],
"0/63/65529": [0, 1, 3, 4],
"0/63/65531": [0, 1, 2, 3, 65532, 65533, 65528, 65529, 65531],
"0/62/0": [
{
"1": "FTABAQEkAgE3AyQTAhgmBIAigScmBYAlTTo3BiQVAiQRlhgkBwEkCAEwCUEE2p7AKvoklmZUFHB0JFUiCsv5FCm0dmeH35yXz4UUH4HAWUwpbeU+R7hMGbAITM3T1R/mVWYthssdVcPNsfIVcjcKNQEoARgkAgE2AwQCBAEYMAQUQbZ3toX8hpE/FmJz7M6xHTbh6RMwBRS5+zzv8ZPGnI9mC3wH9vq10JnwlhgwC0DughBITJJHW/pS7o0J6o6FYTe1ufe0vCpaCj3qYeWb/QxLUydUaJQbce5Z3lUcFeHybUa/M9HID+0PRp2Ker3/GA==",
"2": "FTABAQEkAgE3AyQUARgmBIAigScmBYAlTTo3BiQTAhgkBwEkCAEwCUEE/DujEcdTsX19xbxX+KuKKWiMaA5D9u99P/pVxIOmscd2BA2PadEMNnjvtPOpf+WE2Zxar4rby1IfAClGUUuQrTcKNQEpARgkAmAwBBS5+zzv8ZPGnI9mC3wH9vq10JnwljAFFPT6p93JKGcb7g+rTWnA6evF2EdGGDALQGkPpvsbkAFEbfPN6H3Kf23R0zzmW/gpAA3kgaL6wKB2Ofm+Tmylw22qM536Kj8mOMwaV0EL1dCCGcuxF98aL6gY",
"254": 1
}
],
"0/62/1": [
{
"1": "BBmX+KwLR5HGlVNbvlC+dO8Jv9fPthHiTfGpUzi2JJADX5az6GxBAFn02QKHwLcZHyh+lh9faf6rf38/nPYF7/M=",
"2": 4939,
"3": 2,
"4": 150,
"5": "ha",
"254": 1
}
],
"0/62/2": 16,
"0/62/3": 1,
"0/62/4": [
"FTABAQEkAgE3AyQUARgmBIAigScmBYAlTTo3BiQUARgkBwEkCAEwCUEEGZf4rAtHkcaVU1u+UL507wm/18+2EeJN8alTOLYkkANflrPobEEAWfTZAofAtxkfKH6WH19p/qt/fz+c9gXv8zcKNQEpARgkAmAwBBT0+qfdyShnG+4Pq01pwOnrxdhHRjAFFPT6p93JKGcb7g+rTWnA6evF2EdGGDALQPVrsFnfFplsQGV5m5EUua+rmo9hAr+OP1bvaifdLqiEIn3uXLTLoKmVUkPImRL2Fb+xcMEAqR2p7RM6ZlFCR20Y"
],
"0/62/5": 1,
"0/62/65532": 0,
"0/62/65533": 2,
"0/62/65528": [1, 3, 5, 8, 14],
"0/62/65529": [0, 2, 4, 6, 7, 9, 10, 11, 12, 13],
"0/62/65531": [0, 1, 2, 3, 4, 5, 65532, 65533, 65528, 65529, 65531],
"0/60/0": 0,
"0/60/1": null,
"0/60/2": null,
"0/60/65532": 0,
"0/60/65533": 1,
"0/60/65528": [],
"0/60/65529": [0, 2],
"0/60/65531": [0, 1, 2, 65532, 65533, 65528, 65529, 65531],
"0/55/2": 425,
"0/55/3": 61,
"0/55/4": 0,
"0/55/5": 0,
"0/55/6": 0,
"0/55/7": null,
"0/55/1": true,
"0/55/0": 2,
"0/55/8": 16,
"0/55/65532": 3,
"0/55/65533": 1,
"0/55/65528": [],
"0/55/65529": [0],
"0/55/65531": [
2, 3, 4, 5, 6, 7, 1, 0, 8, 65532, 65533, 65528, 65529, 65531
],
"0/54/0": null,
"0/54/1": null,
"0/54/2": 3,
"0/54/3": null,
"0/54/4": null,
"0/54/5": null,
"0/54/12": null,
"0/54/6": null,
"0/54/7": null,
"0/54/8": null,
"0/54/9": null,
"0/54/10": null,
"0/54/11": null,
"0/54/65532": 3,
"0/54/65533": 1,
"0/54/65528": [],
"0/54/65529": [0],
"0/54/65531": [
0, 1, 2, 3, 4, 5, 12, 6, 7, 8, 9, 10, 11, 65532, 65533, 65528, 65529,
65531
],
"0/52/0": [
{
"0": 6163,
"1": "6163"
},
{
"0": 6162,
"1": "6162"
},
{
"0": 6161,
"1": "6161"
},
{
"0": 6160,
"1": "6160"
},
{
"0": 6159,
"1": "6159"
}
],
"0/52/1": 545392,
"0/52/2": 650640,
"0/52/3": 650640,
"0/52/65532": 1,
"0/52/65533": 1,
"0/52/65528": [],
"0/52/65529": [0],
"0/52/65531": [0, 1, 2, 3, 65532, 65533, 65528, 65529, 65531],
"0/51/0": [
{
"0": "docker0",
"1": false,
"2": null,
"3": null,
"4": "8mJ0KirG",
"5": ["rBEAAQ=="],
"6": [],
"7": 0
},
{
"0": "ens33",
"1": true,
"2": null,
"3": null,
"4": "AAwpaqXN",
"5": ["wKgBxA=="],
"6": [
"KgEOCgKzOZAcmuLd4EsaUA==",
"KgEOCgKzOZA2wMm9YG06Ag==",
"/oAAAAAAAACluAo+qvkuxw=="
],
"7": 2
},
{
"0": "lo",
"1": true,
"2": null,
"3": null,
"4": "AAAAAAAA",
"5": ["fwAAAQ=="],
"6": ["AAAAAAAAAAAAAAAAAAAAAQ=="],
"7": 0
}
],
"0/51/1": 1,
"0/51/8": false,
"0/51/3": 0,
"0/51/4": 0,
"0/51/2": 16,
"0/51/65532": 0,
"0/51/65533": 2,
"0/51/65528": [2],
"0/51/65529": [0, 1],
"0/51/65531": [0, 1, 8, 3, 4, 2, 65532, 65533, 65528, 65529, 65531],
"0/50/65532": 0,
"0/50/65533": 1,
"0/50/65528": [1],
"0/50/65529": [0],
"0/50/65531": [65532, 65533, 65528, 65529, 65531],
"0/48/0": 0,
"0/48/1": {
"0": 60,
"1": 900
},
"0/48/2": 0,
"0/48/3": 2,
"0/48/4": true,
"0/48/65532": 0,
"0/48/65533": 2,
"0/48/65528": [1, 3, 5],
"0/48/65529": [0, 2, 4],
"0/48/65531": [0, 1, 2, 3, 4, 65532, 65533, 65528, 65529, 65531],
"0/43/0": "en-US",
"0/43/1": ["en-US"],
"0/43/65532": 0,
"0/43/65533": 1,
"0/43/65528": [],
"0/43/65529": [],
"0/43/65531": [0, 1, 65532, 65533, 65528, 65529, 65531],
"0/40/0": 19,
"0/40/1": "TEST_VENDOR",
"0/40/2": 65521,
"0/40/3": "Mock Thermostat",
"0/40/4": 32769,
"0/40/5": "",
"0/40/6": "**REDACTED**",
"0/40/7": 0,
"0/40/8": "TEST_VERSION",
"0/40/9": 1,
"0/40/10": "1.0",
"0/40/19": {
"0": 3,
"1": 65535
},
"0/40/21": 17104896,
"0/40/22": 1,
"0/40/24": 1,
"0/40/11": "20200101",
"0/40/12": "",
"0/40/13": "",
"0/40/14": "",
"0/40/15": "TEST_SN",
"0/40/16": false,
"0/40/18": "29DB8B9DB518F05F",
"0/40/65532": 0,
"0/40/65533": 5,
"0/40/65528": [],
"0/40/65529": [],
"0/40/65531": [
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 19, 21, 22, 24, 11, 12, 13, 14, 15, 16,
18, 65532, 65533, 65528, 65529, 65531
],
"0/31/0": [
{
"1": 5,
"2": 2,
"3": [112233],
"4": null,
"254": 1
}
],
"0/31/2": 4,
"0/31/3": 3,
"0/31/4": 4,
"0/31/65532": 0,
"0/31/65533": 3,
"0/31/65528": [],
"0/31/65529": [],
"0/31/65531": [0, 2, 3, 4, 65532, 65533, 65528, 65529, 65531],
"0/30/0": [],
"0/30/65532": 0,
"0/30/65533": 1,
"0/30/65528": [],
"0/30/65529": [],
"0/30/65531": [0, 65532, 65533, 65528, 65529, 65531],
"0/29/0": [
{
"0": 18,
"1": 1
},
{
"0": 22,
"1": 3
}
],
"0/29/1": [
49, 65, 63, 62, 60, 55, 54, 52, 51, 50, 48, 43, 40, 31, 30, 29, 3, 42, 45,
53
],
"0/29/2": [41],
"0/29/3": [1],
"0/29/65532": 0,
"0/29/65533": 3,
"0/29/65528": [],
"0/29/65529": [],
"0/29/65531": [0, 1, 2, 3, 65532, 65533, 65528, 65529, 65531],
"0/3/0": 0,
"0/3/1": 2,
"0/3/65532": 0,
"0/3/65533": 6,
"0/3/65528": [],
"0/3/65529": [0, 64],
"0/3/65531": [0, 1, 65532, 65533, 65528, 65529, 65531],
"0/42/0": [],
"0/42/1": true,
"0/42/2": 0,
"0/42/3": 0,
"0/42/65532": 0,
"0/42/65533": 1,
"0/42/65528": [],
"0/42/65529": [0],
"0/42/65531": [0, 1, 2, 3, 65532, 65533, 65528, 65529, 65531],
"0/45/0": 1,
"0/45/65532": 1,
"0/45/65533": 2,
"0/45/65528": [],
"0/45/65529": [],
"0/45/65531": [0, 65532, 65533, 65528, 65529, 65531],
"0/53/0": null,
"0/53/1": null,
"0/53/2": null,
"0/53/3": null,
"0/53/4": null,
"0/53/5": null,
"0/53/6": 0,
"0/53/7": [],
"0/53/8": [],
"0/53/9": null,
"0/53/10": null,
"0/53/11": null,
"0/53/12": null,
"0/53/13": null,
"0/53/14": 0,
"0/53/15": 0,
"0/53/16": 0,
"0/53/17": 0,
"0/53/18": 0,
"0/53/19": 0,
"0/53/20": 0,
"0/53/21": 0,
"0/53/22": 0,
"0/53/23": 0,
"0/53/24": 0,
"0/53/25": 0,
"0/53/26": 0,
"0/53/27": 0,
"0/53/28": 0,
"0/53/29": 0,
"0/53/30": 0,
"0/53/31": 0,
"0/53/32": 0,
"0/53/33": 0,
"0/53/34": 0,
"0/53/35": 0,
"0/53/36": 0,
"0/53/37": 0,
"0/53/38": 0,
"0/53/39": 0,
"0/53/40": 0,
"0/53/41": 0,
"0/53/42": 0,
"0/53/43": 0,
"0/53/44": 0,
"0/53/45": 0,
"0/53/46": 0,
"0/53/47": 0,
"0/53/48": 0,
"0/53/49": 0,
"0/53/50": 0,
"0/53/51": 0,
"0/53/52": 0,
"0/53/53": 0,
"0/53/54": 0,
"0/53/55": 0,
"0/53/56": null,
"0/53/57": null,
"0/53/58": null,
"0/53/59": null,
"0/53/60": null,
"0/53/61": null,
"0/53/62": [],
"0/53/65532": 15,
"0/53/65533": 3,
"0/53/65528": [],
"0/53/65529": [0],
"0/53/65531": [
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38,
39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56,
57, 58, 59, 60, 61, 62, 65532, 65533, 65528, 65529, 65531
],
"1/29/0": [
{
"0": 769,
"1": 4
}
],
"1/29/1": [29, 3, 4, 513, 516],
"1/29/2": [3],
"1/29/3": [],
"1/29/65532": 0,
"1/29/65533": 3,
"1/29/65528": [],
"1/29/65529": [],
"1/29/65531": [0, 1, 2, 3, 65532, 65533, 65528, 65529, 65531],
"1/3/0": 0,
"1/3/1": 2,
"1/3/65532": 0,
"1/3/65533": 6,
"1/3/65528": [],
"1/3/65529": [0, 64],
"1/3/65531": [0, 1, 65532, 65533, 65528, 65529, 65531],
"1/4/0": 128,
"1/4/65532": 1,
"1/4/65533": 4,
"1/4/65528": [0, 1, 2, 3],
"1/4/65529": [0, 1, 2, 3, 4, 5],
"1/4/65531": [0, 65532, 65533, 65528, 65529, 65531],
"1/513/0": 1800,
"1/513/1": 500,
"1/513/3": 700,
"1/513/4": 3000,
"1/513/5": 1600,
"1/513/6": 3200,
"1/513/7": 0,
"1/513/8": 25,
"1/513/16": 0,
"1/513/17": 2600,
"1/513/18": 2000,
"1/513/21": 700,
"1/513/22": 3000,
"1/513/23": 1600,
"1/513/24": 3200,
"1/513/25": 25,
"1/513/26": 0,
"1/513/27": 4,
"1/513/28": 1,
"1/513/30": 4,
"1/513/35": 0,
"1/513/36": 0,
"1/513/37": 0,
"1/513/41": 1,
"1/513/48": 0,
"1/513/49": 150,
"1/513/50": 789004800,
"1/513/72": [
{
"0": 1,
"1": 1,
"2": 1
},
{
"0": 2,
"1": 1,
"2": 1
},
{
"0": 3,
"1": 1,
"2": 2
},
{
"0": 4,
"1": 1,
"2": 2
},
{
"0": 5,
"1": 1,
"2": 2
},
{
"0": 254,
"1": 1,
"2": 2
}
],
"1/513/73": [
{
"0": 4,
"1": 1,
"2": 2
},
{
"0": 3,
"1": 1,
"2": 2
}
],
"1/513/74": 5,
"1/513/78": null,
"1/513/80": [
{
"0": "AQ==",
"1": 1,
"3": 2500,
"4": 2100,
"5": true
},
{
"0": "Ag==",
"1": 2,
"3": 2600,
"4": 2000,
"5": true
}
],
"1/513/82": 0,
"1/513/83": 5,
"1/513/84": [],
"1/513/85": null,
"1/513/86": null,
"1/513/65532": 423,
"1/513/65533": 9,
"1/513/65528": [2, 253],
"1/513/65529": [0, 6, 7, 8, 254],
"1/513/65531": [
0, 1, 3, 4, 5, 6, 7, 8, 16, 17, 18, 21, 22, 23, 24, 25, 26, 27, 28, 30,
35, 36, 37, 41, 48, 49, 50, 72, 73, 74, 78, 80, 82, 83, 84, 85, 86, 65532,
65533, 65528, 65529, 65531
],
"1/516/0": 0,
"1/516/1": 0,
"1/516/65532": 0,
"1/516/65533": 2,
"1/516/65528": [],
"1/516/65529": [],
"1/516/65531": [0, 1, 65532, 65533, 65528, 65529, 65531]
},
"attribute_subscriptions": []
}

View File

@@ -391,102 +391,6 @@
'state': 'off',
})
# ---
# name: test_binary_sensors[eve_thermo][binary_sensor.eve_thermo_local_temperature_remote_sensing-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': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'binary_sensor.eve_thermo_local_temperature_remote_sensing',
'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': 'Local temperature remote sensing',
'platform': 'matter',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'thermostat_remote_sensing_local_temperature',
'unique_id': '00000000000004D2-0000000000000021-MatterNodeDevice-1-ThermostatRemoteSensing_LocalTemperature-513-26',
'unit_of_measurement': None,
})
# ---
# name: test_binary_sensors[eve_thermo][binary_sensor.eve_thermo_local_temperature_remote_sensing-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Eve Thermo Local temperature remote sensing',
}),
'context': <ANY>,
'entity_id': 'binary_sensor.eve_thermo_local_temperature_remote_sensing',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
# name: test_binary_sensors[eve_thermo][binary_sensor.eve_thermo_outdoor_temperature_remote_sensing-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': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'binary_sensor.eve_thermo_outdoor_temperature_remote_sensing',
'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': 'Outdoor temperature remote sensing',
'platform': 'matter',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'thermostat_remote_sensing_outdoor_temperature',
'unique_id': '00000000000004D2-0000000000000021-MatterNodeDevice-1-ThermostatRemoteSensing_OutdoorTemperature-513-26',
'unit_of_measurement': None,
})
# ---
# name: test_binary_sensors[eve_thermo][binary_sensor.eve_thermo_outdoor_temperature_remote_sensing-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Eve Thermo Outdoor temperature remote sensing',
}),
'context': <ANY>,
'entity_id': 'binary_sensor.eve_thermo_outdoor_temperature_remote_sensing',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
# name: test_binary_sensors[heiman_motion_sensor_m1][binary_sensor.smart_motion_sensor_occupancy-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
@@ -732,150 +636,6 @@
'state': 'off',
})
# ---
# name: test_binary_sensors[mock_thermostat][binary_sensor.mock_thermostat_local_temperature_remote_sensing-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': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'binary_sensor.mock_thermostat_local_temperature_remote_sensing',
'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': 'Local temperature remote sensing',
'platform': 'matter',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'thermostat_remote_sensing_local_temperature',
'unique_id': '00000000000004D2-0000000000000096-MatterNodeDevice-1-ThermostatRemoteSensing_LocalTemperature-513-26',
'unit_of_measurement': None,
})
# ---
# name: test_binary_sensors[mock_thermostat][binary_sensor.mock_thermostat_local_temperature_remote_sensing-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Mock Thermostat Local temperature remote sensing',
}),
'context': <ANY>,
'entity_id': 'binary_sensor.mock_thermostat_local_temperature_remote_sensing',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
# name: test_binary_sensors[mock_thermostat][binary_sensor.mock_thermostat_occupancy_remote_sensing-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': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'binary_sensor.mock_thermostat_occupancy_remote_sensing',
'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': 'Occupancy remote sensing',
'platform': 'matter',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'thermostat_remote_sensing_occupancy',
'unique_id': '00000000000004D2-0000000000000096-MatterNodeDevice-1-ThermostatRemoteSensing_Occupancy-513-26',
'unit_of_measurement': None,
})
# ---
# name: test_binary_sensors[mock_thermostat][binary_sensor.mock_thermostat_occupancy_remote_sensing-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Mock Thermostat Occupancy remote sensing',
}),
'context': <ANY>,
'entity_id': 'binary_sensor.mock_thermostat_occupancy_remote_sensing',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
# name: test_binary_sensors[mock_thermostat][binary_sensor.mock_thermostat_outdoor_temperature_remote_sensing-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': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'binary_sensor.mock_thermostat_outdoor_temperature_remote_sensing',
'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': 'Outdoor temperature remote sensing',
'platform': 'matter',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'thermostat_remote_sensing_outdoor_temperature',
'unique_id': '00000000000004D2-0000000000000096-MatterNodeDevice-1-ThermostatRemoteSensing_OutdoorTemperature-513-26',
'unit_of_measurement': None,
})
# ---
# name: test_binary_sensors[mock_thermostat][binary_sensor.mock_thermostat_outdoor_temperature_remote_sensing-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Mock Thermostat Outdoor temperature remote sensing',
}),
'context': <ANY>,
'entity_id': 'binary_sensor.mock_thermostat_outdoor_temperature_remote_sensing',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
# name: test_binary_sensors[occupancy_sensor][binary_sensor.mock_occupancy_sensor_occupancy-entry]
EntityRegistryEntrySnapshot({
'aliases': set({

View File

@@ -1026,104 +1026,6 @@
'state': 'unknown',
})
# ---
# name: test_buttons[eufy_vacuum_omni_e28][button.2bavs_ab6031x_44pe_identify_0-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.2bavs_ab6031x_44pe_identify_0',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <ButtonDeviceClass.IDENTIFY: 'identify'>,
'original_icon': None,
'original_name': 'Identify (0)',
'platform': 'matter',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': None,
'unique_id': '00000000000004D2-0000000000000028-MatterNodeDevice-0-IdentifyButton-3-1',
'unit_of_measurement': None,
})
# ---
# name: test_buttons[eufy_vacuum_omni_e28][button.2bavs_ab6031x_44pe_identify_0-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'identify',
'friendly_name': '2BAVS-AB6031X-44PE Identify (0)',
}),
'context': <ANY>,
'entity_id': 'button.2bavs_ab6031x_44pe_identify_0',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
})
# ---
# name: test_buttons[eufy_vacuum_omni_e28][button.2bavs_ab6031x_44pe_identify_1-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.2bavs_ab6031x_44pe_identify_1',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <ButtonDeviceClass.IDENTIFY: 'identify'>,
'original_icon': None,
'original_name': 'Identify (1)',
'platform': 'matter',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': None,
'unique_id': '00000000000004D2-0000000000000028-MatterNodeDevice-1-IdentifyButton-3-1',
'unit_of_measurement': None,
})
# ---
# name: test_buttons[eufy_vacuum_omni_e28][button.2bavs_ab6031x_44pe_identify_1-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'identify',
'friendly_name': '2BAVS-AB6031X-44PE Identify (1)',
}),
'context': <ANY>,
'entity_id': 'button.2bavs_ab6031x_44pe_identify_1',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
})
# ---
# name: test_buttons[eve_contact_sensor][button.eve_door_identify-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
@@ -2388,104 +2290,6 @@
'state': 'unknown',
})
# ---
# name: test_buttons[mock_thermostat][button.mock_thermostat_identify_0-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_thermostat_identify_0',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <ButtonDeviceClass.IDENTIFY: 'identify'>,
'original_icon': None,
'original_name': 'Identify (0)',
'platform': 'matter',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': None,
'unique_id': '00000000000004D2-0000000000000096-MatterNodeDevice-0-IdentifyButton-3-1',
'unit_of_measurement': None,
})
# ---
# name: test_buttons[mock_thermostat][button.mock_thermostat_identify_0-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'identify',
'friendly_name': 'Mock Thermostat Identify (0)',
}),
'context': <ANY>,
'entity_id': 'button.mock_thermostat_identify_0',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
})
# ---
# name: test_buttons[mock_thermostat][button.mock_thermostat_identify_1-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_thermostat_identify_1',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <ButtonDeviceClass.IDENTIFY: 'identify'>,
'original_icon': None,
'original_name': 'Identify (1)',
'platform': 'matter',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': None,
'unique_id': '00000000000004D2-0000000000000096-MatterNodeDevice-1-IdentifyButton-3-1',
'unit_of_measurement': None,
})
# ---
# name: test_buttons[mock_thermostat][button.mock_thermostat_identify_1-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'identify',
'friendly_name': 'Mock Thermostat Identify (1)',
}),
'context': <ANY>,
'entity_id': 'button.mock_thermostat_identify_1',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
})
# ---
# name: test_buttons[multi_endpoint_light][button.inovelli_identify_1-entry]
EntityRegistryEntrySnapshot({
'aliases': set({

View File

@@ -325,77 +325,6 @@
'state': 'cool',
})
# ---
# name: test_climates[mock_thermostat][climate.mock_thermostat-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'hvac_modes': list([
<HVACMode.OFF: 'off'>,
<HVACMode.HEAT: 'heat'>,
<HVACMode.COOL: 'cool'>,
<HVACMode.HEAT_COOL: 'heat_cool'>,
]),
'max_temp': 32.0,
'min_temp': 7.0,
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'climate',
'entity_category': None,
'entity_id': 'climate.mock_thermostat',
'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': None,
'platform': 'matter',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': <ClimateEntityFeature: 387>,
'translation_key': None,
'unique_id': '00000000000004D2-0000000000000096-MatterNodeDevice-1-MatterThermostat-513-0',
'unit_of_measurement': None,
})
# ---
# name: test_climates[mock_thermostat][climate.mock_thermostat-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'current_temperature': 18.0,
'friendly_name': 'Mock Thermostat',
'hvac_action': <HVACAction.HEATING: 'heating'>,
'hvac_modes': list([
<HVACMode.OFF: 'off'>,
<HVACMode.HEAT: 'heat'>,
<HVACMode.COOL: 'cool'>,
<HVACMode.HEAT_COOL: 'heat_cool'>,
]),
'max_temp': 32.0,
'min_temp': 7.0,
'supported_features': <ClimateEntityFeature: 387>,
'target_temp_high': 26.0,
'target_temp_low': 20.0,
'temperature': None,
}),
'context': <ANY>,
'entity_id': 'climate.mock_thermostat',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'heat_cool',
})
# ---
# name: test_climates[room_airconditioner][climate.room_airconditioner-entry]
EntityRegistryEntrySnapshot({
'aliases': set({

View File

@@ -1105,75 +1105,6 @@
'state': 'Vacuum & Mop',
})
# ---
# name: test_selects[eufy_vacuum_omni_e28][select.2bavs_ab6031x_44pe_clean_mode-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'options': list([
'Auto Mop & Vacuum',
'Auto Vacuum',
'Auto Deep Mop & Vacuum',
'Quick Mop & Vacuum',
'Quick Vacuum',
'Low Noise Mop & Vacuum',
'Low Noise Vacuum',
'Eufy Customize Mode',
]),
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'select',
'entity_category': None,
'entity_id': 'select.2bavs_ab6031x_44pe_clean_mode',
'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': 'Clean mode',
'platform': 'matter',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'clean_mode',
'unique_id': '00000000000004D2-0000000000000028-MatterNodeDevice-1-MatterRvcCleanMode-85-1',
'unit_of_measurement': None,
})
# ---
# name: test_selects[eufy_vacuum_omni_e28][select.2bavs_ab6031x_44pe_clean_mode-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': '2BAVS-AB6031X-44PE Clean mode',
'options': list([
'Auto Mop & Vacuum',
'Auto Vacuum',
'Auto Deep Mop & Vacuum',
'Quick Mop & Vacuum',
'Quick Vacuum',
'Low Noise Mop & Vacuum',
'Low Noise Vacuum',
'Eufy Customize Mode',
]),
}),
'context': <ANY>,
'entity_id': 'select.2bavs_ab6031x_44pe_clean_mode',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'Auto Mop & Vacuum',
})
# ---
# name: test_selects[eve_energy_20ecn4101][select.eve_energy_20ecn4101_power_on_behavior_on_startup_bottom-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
@@ -2639,63 +2570,6 @@
'state': 'silent',
})
# ---
# name: test_selects[mock_thermostat][select.mock_thermostat_temperature_display_mode-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'options': list([
'Celsius',
'Fahrenheit',
]),
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'select',
'entity_category': <EntityCategory.CONFIG: 'config'>,
'entity_id': 'select.mock_thermostat_temperature_display_mode',
'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': 'Temperature display mode',
'platform': 'matter',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'temperature_display_mode',
'unique_id': '00000000000004D2-0000000000000096-MatterNodeDevice-1-TrvTemperatureDisplayMode-516-0',
'unit_of_measurement': None,
})
# ---
# name: test_selects[mock_thermostat][select.mock_thermostat_temperature_display_mode-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Mock Thermostat Temperature display mode',
'options': list([
'Celsius',
'Fahrenheit',
]),
}),
'context': <ANY>,
'entity_id': 'select.mock_thermostat_temperature_display_mode',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'Celsius',
})
# ---
# name: test_selects[mounted_dimmable_load_control_fixture][select.mock_mounted_dimmable_load_control_power_on_behavior_on_startup-entry]
EntityRegistryEntrySnapshot({
'aliases': set({

View File

@@ -3786,279 +3786,6 @@
'state': '0.0',
})
# ---
# name: test_sensors[eufy_vacuum_omni_e28][sensor.2bavs_ab6031x_44pe_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.2bavs_ab6031x_44pe_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-0000000000000028-MatterNodeDevice-1-PowerSource-47-12',
'unit_of_measurement': '%',
})
# ---
# name: test_sensors[eufy_vacuum_omni_e28][sensor.2bavs_ab6031x_44pe_battery-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'battery',
'friendly_name': '2BAVS-AB6031X-44PE Battery',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': '%',
}),
'context': <ANY>,
'entity_id': 'sensor.2bavs_ab6031x_44pe_battery',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '100',
})
# ---
# name: test_sensors[eufy_vacuum_omni_e28][sensor.2bavs_ab6031x_44pe_battery_charge_state-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'options': list([
'not_charging',
'charging',
'full_charge',
]),
}),
'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.2bavs_ab6031x_44pe_battery_charge_state',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <SensorDeviceClass.ENUM: 'enum'>,
'original_icon': None,
'original_name': 'Battery charge state',
'platform': 'matter',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'battery_charge_state',
'unique_id': '00000000000004D2-0000000000000028-MatterNodeDevice-1-PowerSourceBatChargeState-47-26',
'unit_of_measurement': None,
})
# ---
# name: test_sensors[eufy_vacuum_omni_e28][sensor.2bavs_ab6031x_44pe_battery_charge_state-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'enum',
'friendly_name': '2BAVS-AB6031X-44PE Battery charge state',
'options': list([
'not_charging',
'charging',
'full_charge',
]),
}),
'context': <ANY>,
'entity_id': 'sensor.2bavs_ab6031x_44pe_battery_charge_state',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'full_charge',
})
# ---
# name: test_sensors[eufy_vacuum_omni_e28][sensor.2bavs_ab6031x_44pe_operational_error-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'options': list([
'no_error',
'unable_to_start_or_resume',
'unable_to_complete_operation',
'command_invalid_in_state',
'failed_to_find_charging_dock',
'stuck',
'dust_bin_missing',
'dust_bin_full',
'water_tank_empty',
'water_tank_missing',
'water_tank_lid_open',
'mop_cleaning_pad_missing',
'low_battery',
'cannot_reach_target_area',
'dirty_water_tank_full',
'dirty_water_tank_missing',
'wheels_jammed',
'brush_jammed',
'navigation_sensor_obscured',
]),
}),
'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.2bavs_ab6031x_44pe_operational_error',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <SensorDeviceClass.ENUM: 'enum'>,
'original_icon': None,
'original_name': 'Operational error',
'platform': 'matter',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'operational_error',
'unique_id': '00000000000004D2-0000000000000028-MatterNodeDevice-1-RvcOperationalStateOperationalError-97-5',
'unit_of_measurement': None,
})
# ---
# name: test_sensors[eufy_vacuum_omni_e28][sensor.2bavs_ab6031x_44pe_operational_error-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'enum',
'friendly_name': '2BAVS-AB6031X-44PE Operational error',
'options': list([
'no_error',
'unable_to_start_or_resume',
'unable_to_complete_operation',
'command_invalid_in_state',
'failed_to_find_charging_dock',
'stuck',
'dust_bin_missing',
'dust_bin_full',
'water_tank_empty',
'water_tank_missing',
'water_tank_lid_open',
'mop_cleaning_pad_missing',
'low_battery',
'cannot_reach_target_area',
'dirty_water_tank_full',
'dirty_water_tank_missing',
'wheels_jammed',
'brush_jammed',
'navigation_sensor_obscured',
]),
}),
'context': <ANY>,
'entity_id': 'sensor.2bavs_ab6031x_44pe_operational_error',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'no_error',
})
# ---
# name: test_sensors[eufy_vacuum_omni_e28][sensor.2bavs_ab6031x_44pe_operational_state-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'options': list([
'stopped',
'running',
'paused',
'error',
'seeking_charger',
'charging',
'docked',
]),
}),
'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.2bavs_ab6031x_44pe_operational_state',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <SensorDeviceClass.ENUM: 'enum'>,
'original_icon': None,
'original_name': 'Operational state',
'platform': 'matter',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'operational_state',
'unique_id': '00000000000004D2-0000000000000028-MatterNodeDevice-1-RvcOperationalState-97-4',
'unit_of_measurement': None,
})
# ---
# name: test_sensors[eufy_vacuum_omni_e28][sensor.2bavs_ab6031x_44pe_operational_state-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'enum',
'friendly_name': '2BAVS-AB6031X-44PE Operational state',
'options': list([
'stopped',
'running',
'paused',
'error',
'seeking_charger',
'charging',
'docked',
]),
}),
'context': <ANY>,
'entity_id': 'sensor.2bavs_ab6031x_44pe_operational_state',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'stopped',
})
# ---
# name: test_sensors[eve_contact_sensor][sensor.eve_door_battery-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
@@ -7532,167 +7259,6 @@
'state': 'stopped',
})
# ---
# name: test_sensors[mock_thermostat][sensor.mock_thermostat_heating_demand-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.mock_thermostat_heating_demand',
'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': 'Heating demand',
'platform': 'matter',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'pi_heating_demand',
'unique_id': '00000000000004D2-0000000000000096-MatterNodeDevice-1-ThermostatPIHeatingDemand-513-8',
'unit_of_measurement': '%',
})
# ---
# name: test_sensors[mock_thermostat][sensor.mock_thermostat_heating_demand-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Mock Thermostat Heating demand',
'unit_of_measurement': '%',
}),
'context': <ANY>,
'entity_id': 'sensor.mock_thermostat_heating_demand',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '25',
})
# ---
# name: test_sensors[mock_thermostat][sensor.mock_thermostat_outdoor_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.mock_thermostat_outdoor_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': 'Outdoor temperature',
'platform': 'matter',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'outdoor_temperature',
'unique_id': '00000000000004D2-0000000000000096-MatterNodeDevice-1-ThermostatOutdoorTemperature-513-1',
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
})
# ---
# name: test_sensors[mock_thermostat][sensor.mock_thermostat_outdoor_temperature-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'temperature',
'friendly_name': 'Mock Thermostat Outdoor temperature',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
}),
'context': <ANY>,
'entity_id': 'sensor.mock_thermostat_outdoor_temperature',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '5.0',
})
# ---
# name: test_sensors[mock_thermostat][sensor.mock_thermostat_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.mock_thermostat_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-0000000000000096-MatterNodeDevice-1-ThermostatLocalTemperature-513-0',
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
})
# ---
# name: test_sensors[mock_thermostat][sensor.mock_thermostat_temperature-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'temperature',
'friendly_name': 'Mock Thermostat Temperature',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
}),
'context': <ANY>,
'entity_id': 'sensor.mock_thermostat_temperature',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '18.0',
})
# ---
# name: test_sensors[multi_endpoint_light][sensor.inovelli_current_switch_position_config-entry]
EntityRegistryEntrySnapshot({
'aliases': set({

View File

@@ -48,55 +48,6 @@
'state': 'docked',
})
# ---
# name: test_vacuum[eufy_vacuum_omni_e28][vacuum.2bavs_ab6031x_44pe-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': 'vacuum',
'entity_category': None,
'entity_id': 'vacuum.2bavs_ab6031x_44pe',
'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': None,
'platform': 'matter',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': <VacuumEntityFeature: 12828>,
'translation_key': None,
'unique_id': '00000000000004D2-0000000000000028-MatterNodeDevice-1-MatterVacuumCleaner-84-1',
'unit_of_measurement': None,
})
# ---
# name: test_vacuum[eufy_vacuum_omni_e28][vacuum.2bavs_ab6031x_44pe-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': '2BAVS-AB6031X-44PE',
'supported_features': <VacuumEntityFeature: 12828>,
}),
'context': <ANY>,
'entity_id': 'vacuum.2bavs_ab6031x_44pe',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'idle',
})
# ---
# name: test_vacuum[switchbot_k11_plus][vacuum.k11-entry]
EntityRegistryEntrySnapshot({
'aliases': set({

View File

@@ -435,160 +435,3 @@ async def test_shutter_problem(
state = hass.states.get("binary_sensor.eve_shutter_switch_20eci1701_problem")
assert state
assert state.state == "on"
@pytest.mark.parametrize("node_fixture", ["mock_thermostat"])
async def test_thermostat_remote_sensing(
hass: HomeAssistant,
matter_client: MagicMock,
matter_node: MatterNode,
) -> None:
"""Test thermostat remote sensing binary sensors."""
remote_sensing_attribute = clusters.Thermostat.Attributes.RemoteSensing
# Test initial state (RemoteSensing = 0, all bits off)
state = hass.states.get(
"binary_sensor.mock_thermostat_local_temperature_remote_sensing"
)
assert state
assert state.state == "off"
state = hass.states.get(
"binary_sensor.mock_thermostat_outdoor_temperature_remote_sensing"
)
assert state
assert state.state == "off"
state = hass.states.get("binary_sensor.mock_thermostat_occupancy_remote_sensing")
assert state
assert state.state == "off"
# Set LocalTemperature bit (bit 0)
set_node_attribute(
matter_node,
1,
remote_sensing_attribute.cluster_id,
remote_sensing_attribute.attribute_id,
1,
)
await trigger_subscription_callback(hass, matter_client)
state = hass.states.get(
"binary_sensor.mock_thermostat_local_temperature_remote_sensing"
)
assert state
assert state.state == "on"
state = hass.states.get(
"binary_sensor.mock_thermostat_outdoor_temperature_remote_sensing"
)
assert state
assert state.state == "off"
state = hass.states.get("binary_sensor.mock_thermostat_occupancy_remote_sensing")
assert state
assert state.state == "off"
# Set OutdoorTemperature bit (bit 1)
set_node_attribute(
matter_node,
1,
remote_sensing_attribute.cluster_id,
remote_sensing_attribute.attribute_id,
2,
)
await trigger_subscription_callback(hass, matter_client)
state = hass.states.get(
"binary_sensor.mock_thermostat_local_temperature_remote_sensing"
)
assert state
assert state.state == "off"
state = hass.states.get(
"binary_sensor.mock_thermostat_outdoor_temperature_remote_sensing"
)
assert state
assert state.state == "on"
state = hass.states.get("binary_sensor.mock_thermostat_occupancy_remote_sensing")
assert state
assert state.state == "off"
# Set Occupancy bit (bit 2)
set_node_attribute(
matter_node,
1,
remote_sensing_attribute.cluster_id,
remote_sensing_attribute.attribute_id,
4,
)
await trigger_subscription_callback(hass, matter_client)
state = hass.states.get(
"binary_sensor.mock_thermostat_local_temperature_remote_sensing"
)
assert state
assert state.state == "off"
state = hass.states.get(
"binary_sensor.mock_thermostat_outdoor_temperature_remote_sensing"
)
assert state
assert state.state == "off"
state = hass.states.get("binary_sensor.mock_thermostat_occupancy_remote_sensing")
assert state
assert state.state == "on"
# Set multiple bits (bits 0 and 2 = value 5)
set_node_attribute(
matter_node,
1,
remote_sensing_attribute.cluster_id,
remote_sensing_attribute.attribute_id,
5,
)
await trigger_subscription_callback(hass, matter_client)
state = hass.states.get(
"binary_sensor.mock_thermostat_local_temperature_remote_sensing"
)
assert state
assert state.state == "on"
state = hass.states.get(
"binary_sensor.mock_thermostat_outdoor_temperature_remote_sensing"
)
assert state
assert state.state == "off"
state = hass.states.get("binary_sensor.mock_thermostat_occupancy_remote_sensing")
assert state
assert state.state == "on"
# Set all bits (value 7)
set_node_attribute(
matter_node,
1,
remote_sensing_attribute.cluster_id,
remote_sensing_attribute.attribute_id,
7,
)
await trigger_subscription_callback(hass, matter_client)
state = hass.states.get(
"binary_sensor.mock_thermostat_local_temperature_remote_sensing"
)
assert state
assert state.state == "on"
state = hass.states.get(
"binary_sensor.mock_thermostat_outdoor_temperature_remote_sensing"
)
assert state
assert state.state == "on"
state = hass.states.get("binary_sensor.mock_thermostat_occupancy_remote_sensing")
assert state
assert state.state == "on"

View File

@@ -1584,7 +1584,6 @@ async def test_discovery_with_object_id(
async def test_discovery_with_default_entity_id_for_previous_deleted_entity(
hass: HomeAssistant,
mqtt_mock_entry: MqttMockHAClientGenerator,
entity_registry: er.EntityRegistry,
) -> None:
"""Test discovering an MQTT entity with default_entity_id and unique_id."""
@@ -1599,7 +1598,6 @@ async def test_discovery_with_default_entity_id_for_previous_deleted_entity(
)
initial_entity_id = "sensor.hello_id"
new_entity_id = "sensor.updated_hello_id"
later_entity_id = "sensor.later_hello_id"
name = "Hello World 11"
domain = "sensor"
@@ -1628,14 +1626,6 @@ async def test_discovery_with_default_entity_id_for_previous_deleted_entity(
assert state.name == name
assert (domain, "object bla") in hass.data["mqtt"].discovery_already_discovered
# Assert the entity ID can be changed later
entity_registry.async_update_entity(new_entity_id, new_entity_id=later_entity_id)
await hass.async_block_till_done()
state = hass.states.get(later_entity_id)
assert state is not None
assert state.name == name
async def test_discovery_incl_nodeid(
hass: HomeAssistant, mqtt_mock_entry: MqttMockHAClientGenerator

View File

@@ -18,7 +18,7 @@
},
"flow_rate": {
"value": 150,
"unit": "l/s"
"unit": "L/s"
},
"ph_type_dosing": {
"value": "alcalyne",
@@ -75,14 +75,6 @@
"orp_calibration_slope": {
"value": 0.96,
"unit": "mV"
},
"water_meter_total_permanent": {
"value": 12345.67,
"unit": "m3"
},
"water_meter_total_resettable": {
"value": 123.45,
"unit": "m3"
}
},
"binary_sensor": {

View File

@@ -1054,59 +1054,3 @@
'state': '25',
})
# ---
# name: test_all_sensors[sensor.pool_device_totalizer-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
}),
'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.pool_device_totalizer',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
'sensor': dict({
'suggested_display_precision': 2,
}),
}),
'original_device_class': <SensorDeviceClass.VOLUME: 'volume'>,
'original_icon': None,
'original_name': 'Totalizer',
'platform': 'pooldose',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'water_meter_total_permanent',
'unique_id': 'TEST123456789_water_meter_total_permanent',
'unit_of_measurement': <UnitOfVolume.CUBIC_METERS: 'm³'>,
})
# ---
# name: test_all_sensors[sensor.pool_device_totalizer-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'volume',
'friendly_name': 'Pool Device Totalizer',
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
'unit_of_measurement': <UnitOfVolume.CUBIC_METERS: 'm³'>,
}),
'context': <ANY>,
'entity_id': 'sensor.pool_device_totalizer',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '12345.67',
})
# ---

Some files were not shown because too many files have changed in this diff Show More