diff --git a/README.md b/README.md
index 9237149..1118366 100644
--- a/README.md
+++ b/README.md
@@ -26,8 +26,6 @@ What it can do:
- **Even more**
There are some more goodies in to docs below. Enjoy!
-_Note: currently this is limited to Blue switches using ZHA, but ideally Z2M and other Inovelli switches will be added in the future._
-
**Configure notifications** for multiple switches easily:
@@ -273,6 +271,30 @@ Restore state functionality is provided via a subset of entities:
If you disable these entities, it is possible that various other entities may not be restored after restarting Home Assistant.
+##### Z-Wave
+
+Only the Red series switches are supported by this integration as the Black series switches do not have a concept of notifications.
+
+Some of the older Red series switches only have a single LED or have a subset of available effects. Lampie will do the following for these switches:
+
+- If an unsupported effect is used, it will choose something similar
+- If [individual LEDs](#full-led-configuration) are used on a switch with just one LED, the first LED settings will be used
+
+Unlike the Blue series switches under ZHA, there is no way to receive events for when a notification expires (it only supports, for instance, when the config button is dobule pressed `property_key_name="003"` and `value="KeyPressed2x"`). This may be supported in the firmware and not yet available for end user consumption.
+
+This integration therefore handles notification expiration itself for switches configured with Z-Wave. This may change unexpectedly in the future—if and when it is possible, Lampie will change to sending durations to the firmware.
+
+##### Matter
+
+White series switches only have a single LED and do not support effects. Lampie will do the following for these switches:
+
+- All will simply indicate to enable the notification (besides `CLEAR`)
+- If [individual LEDs](#full-led-configuration) are configured, the first LED settings will be used
+
+Unlike the Blue series switches under ZHA, there is no way to receive events for when a notification expires (it only supports, for instance, when the config button is dobule pressed via a state change on `event._config` with `event_type="multi_press_2"`). This may be supported in the firmware and not yet available for end user consumption.
+
+This integration therefore handles notification expiration itself for switches configured with Matter. This may change unexpectedly in the future—if and when it is possible, Lampie will change to sending durations to the firmware.
+
## More Screenshots
Once configured, the integration links the various entities to logical devices:
diff --git a/custom_components/lampie/__init__.py b/custom_components/lampie/__init__.py
index 2ade10e..5dd05ce 100644
--- a/custom_components/lampie/__init__.py
+++ b/custom_components/lampie/__init__.py
@@ -48,11 +48,14 @@ async def async_setup_entry(hass: HomeAssistant, entry: LampieConfigEntry) -> bo
_LOGGER.debug("setup %s with config:%s", entry.title, entry.data)
if DOMAIN not in hass.data:
- hass.data[DOMAIN] = LampieOrchestrator(hass)
+ orchestrator = LampieOrchestrator(hass)
+ hass.data[DOMAIN] = orchestrator
+
+ await orchestrator.setup()
coordinator = LampieUpdateCoordinator(hass, entry)
- orchestrator: LampieOrchestrator = hass.data[DOMAIN]
- orchestrator.add_coordinator(coordinator)
+ orchestrator = hass.data[DOMAIN]
+ await orchestrator.add_coordinator(coordinator)
entry.runtime_data = LampieConfigEntryRuntimeData(
orchestrator=orchestrator,
coordinator=coordinator,
@@ -123,7 +126,7 @@ async def async_unload_entry(hass: HomeAssistant, entry: LampieConfigEntry) -> b
unload_ok: bool = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
if orchestrator := entry.runtime_data.orchestrator:
- orchestrator.remove_coordinator(coordinator)
+ await orchestrator.remove_coordinator(coordinator)
if orchestrator.teardown() and orchestrator == hass.data.get(DOMAIN):
hass.data.pop(DOMAIN)
diff --git a/custom_components/lampie/config_flow.py b/custom_components/lampie/config_flow.py
index caf1fba..f7f2a00 100644
--- a/custom_components/lampie/config_flow.py
+++ b/custom_components/lampie/config_flow.py
@@ -620,19 +620,19 @@ def _is_inovelli_switch(
entity_registry: er.EntityRegistry,
entity_id: str,
) -> bool:
- """Check if entity_id should be included for dependent entities.
+ """Check if entity_id should be included for the list of switches.
- Determine if an entity_id represents an entity with a `state_class` of
- `total_increasing` and a `unit_of_measurement` of `km`.
+ Determine if an entity_id represents an Inovelli switch.
Returns:
A flag indicating if the entity should be included.
"""
+
return bool(
(entity := entity_registry.async_get(entity_id))
and (device := device_registry.async_get(entity.device_id))
- and (model := device.model)
- and (model in INOVELLI_MODELS)
+ and ((model := device.model), (model_id := device.model_id))
+ and ((model in INOVELLI_MODELS) or (model_id in INOVELLI_MODELS))
)
diff --git a/custom_components/lampie/const.py b/custom_components/lampie/const.py
index 8bcc560..53e3f06 100644
--- a/custom_components/lampie/const.py
+++ b/custom_components/lampie/const.py
@@ -32,10 +32,94 @@
CONF_SWITCH_ENTITIES: Final = "switches"
INOVELLI_MODELS = {
- "VZM30-SN", # switch
- "VZM31-SN", # two in one switch/dimmer
- "VZM35-SN", # fan switch
- "VZM36", # canopy module
+ "LZW30-SN", # red on/off switch
+ "LZW31-SN", # red dimmer
+ "LZW36", # red fan/light combo
+ "VTM31-SN", # white dimmer (2-in-1 switch/dimmer)
+ "VTM35-SN", # white fan
+ "VZM30-SN", # blue switch
+ "VZM31-SN", # blue 2-in-1 switch/dimmer
+ "VZM35-SN", # blue fan switch
+ "VZM36", # blue canopy module
+ "VZW31-SN", # red 2-in-1 dimmer
}
+
+ZWAVE_EFFECT_PARAMETERS = {
+ "LZW31-SN": 16, # red dimmer
+ "LZW30-SN": 8, # red on/off switch
+ "LZW36_light": 24, # red fan/light combo
+ "LZW36_fan": 25, # red fan/light combo
+ "VZW31-SN": 99, # red 2-in-1 dimmer
+ "VZW31-SN_individual_1": 64,
+ "VZW31-SN_individual_2": 69,
+ "VZW31-SN_individual_3": 74,
+ "VZW31-SN_individual_4": 79,
+ "VZW31-SN_individual_5": 84,
+ "VZW31-SN_individual_6": 89,
+ "VZW31-SN_individual_7": 94,
+}
+
+ZWAVE_EFFECT_MAPPING = {
+ "LZW30-SN": { # red on/off switch
+ "CLEAR": 0,
+ "AURORA": 4,
+ "MEDIUM_BLINK": 3,
+ "CHASE": 2,
+ "FAST_CHASE": 2,
+ "SLOW_CHASE": 3,
+ "FAST_FALLING": 2,
+ "MEDIUM_FALLING": 3,
+ "SLOW_FALLING": 3,
+ "OPEN_CLOSE": 4,
+ "FAST_RISING": 2,
+ "MEDIUM_RISING": 3,
+ "SLOW_RISING": 3,
+ "FAST_SIREN": 4,
+ "SLOW_SIREN": 4,
+ "SMALL_TO_BIG": 4,
+ },
+ "LZW31-SN": { # red dimmer
+ "AURORA": 4,
+ "FAST_BLINK": 3,
+ "MEDIUM_BLINK": 4,
+ "SLOW_BLINK": 4,
+ "CHASE": 2,
+ "FAST_CHASE": 2,
+ "SLOW_CHASE": 2,
+ "FAST_FALLING": 2,
+ "MEDIUM_FALLING": 2,
+ "SLOW_FALLING": 2,
+ "OPEN_CLOSE": 2,
+ "PULSE": 5,
+ "FAST_RISING": 2,
+ "MEDIUM_RISING": 2,
+ "SLOW_RISING": 2,
+ "SLOW_SIREN": 2,
+ "FAST_SIREN": 2,
+ "SMALL_TO_BIG": 2,
+ },
+ "LZW36": { # red fan/light combo
+ "AURORA": 4,
+ "FAST_BLINK": 3,
+ "MEDIUM_BLINK": 4,
+ "SLOW_BLINK": 4,
+ "CHASE": 2,
+ "FAST_CHASE": 2,
+ "SLOW_CHASE": 2,
+ "FAST_FALLING": 2,
+ "MEDIUM_FALLING": 2,
+ "SLOW_FALLING": 2,
+ "OPEN_CLOSE": 2,
+ "PULSE": 5,
+ "FAST_RISING": 2,
+ "MEDIUM_RISING": 2,
+ "SLOW_RISING": 2,
+ "SLOW_SIREN": 2,
+ "FAST_SIREN": 2,
+ "SMALL_TO_BIG": 2,
+ },
+}
+
+
TRACE: Final = 5
diff --git a/custom_components/lampie/manifest.json b/custom_components/lampie/manifest.json
index 4c1e4f8..4ec521a 100644
--- a/custom_components/lampie/manifest.json
+++ b/custom_components/lampie/manifest.json
@@ -5,7 +5,7 @@
"@wbyoung"
],
"config_flow": true,
- "dependencies": [],
+ "dependencies": ["mqtt"],
"documentation": "https://github.com/wbyoung/lampie",
"homekit": {},
"iot_class": "calculated",
diff --git a/custom_components/lampie/orchestrator.py b/custom_components/lampie/orchestrator.py
index 37e0847..9337648 100644
--- a/custom_components/lampie/orchestrator.py
+++ b/custom_components/lampie/orchestrator.py
@@ -6,33 +6,46 @@
from contextlib import suppress
from dataclasses import replace
import datetime as dt
-from enum import IntEnum
+from enum import Enum, IntEnum, auto
from functools import partial
import logging
import re
from typing import TYPE_CHECKING, Any, Final, NamedTuple, Protocol, Unpack
+from homeassistant.components import mqtt
+from homeassistant.components.light import DOMAIN as LIGHT_DOMAIN
+from homeassistant.components.mqtt import DOMAIN as MQTT_DOMAIN
+from homeassistant.components.mqtt.models import MqttData
from homeassistant.components.script import DOMAIN as SCRIPT_DOMAIN
+from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN
from homeassistant.core import CALLBACK_TYPE, Event, HomeAssistant, callback
-from homeassistant.helpers import entity_registry as er, event as evt
-from homeassistant.helpers.device import (
- async_device_info_to_link_from_entity,
- async_entity_id_to_device_id,
+from homeassistant.helpers import (
+ device_registry as dr,
+ entity_registry as er,
+ event as evt,
)
+from homeassistant.helpers.device import async_entity_id_to_device_id
+from homeassistant.helpers.json import json_dumps
from homeassistant.util import dt as dt_util
+from homeassistant.util.hass_dict import HassKey
+from homeassistant.util.json import json_loads_object
from .const import (
CONF_END_ACTION,
CONF_PRIORITY,
CONF_START_ACTION,
CONF_SWITCH_ENTITIES,
+ INOVELLI_MODELS,
TRACE,
+ ZWAVE_EFFECT_MAPPING as _ZWAVE_EFFECT_MAPPING,
+ ZWAVE_EFFECT_PARAMETERS,
)
from .types import (
Color,
DeviceId,
Effect,
ExpirationInfo,
+ Integration,
LampieNotificationInfo,
LampieNotificationOptionsDict,
LampieSwitchInfo,
@@ -40,20 +53,42 @@
LEDConfig,
LEDConfigSource,
LEDConfigSourceType,
+ MatterSwitchInfo,
Slug,
SwitchId,
+ Z2MSwitchInfo,
+ ZHASwitchInfo,
+ ZWaveSwitchInfo,
)
if TYPE_CHECKING:
from .coordinator import LampieUpdateCoordinator
type ZHAEventData = dict[str, Any]
+type ZWaveEventData = dict[str, Any]
+type MatterEventData = dict[str, Any]
+type MQTTDeviceName = str
_LOGGER = logging.getLogger(__name__)
ZHA_DOMAIN: Final = "zha"
+DATA_MQTT: HassKey[MqttData] = HassKey("mqtt")
+ZWAVE_DOMAIN: Final = "zwave_js"
+ZWAVE_EFFECT_MAPPING: Final = {
+ model: {Effect[effect_key].value: value for effect_key, value in mapping.items()}
+ for model, mapping in _ZWAVE_EFFECT_MAPPING.items()
+}
+MATTER_DOMAIN: Final = "matter"
+
ALREADY_EXPIRED: Final = 0
+SWITCH_INTEGRATIONS = {
+ ZHA_DOMAIN: Integration.ZHA,
+ MQTT_DOMAIN: Integration.Z2M,
+ ZWAVE_DOMAIN: Integration.ZWAVE,
+ MATTER_DOMAIN: Integration.MATTER,
+}
+
FIRMWARE_SECONDS_MAX = dt.timedelta(seconds=60).total_seconds()
FIRMWARE_MINUTES_MAX = dt.timedelta(minutes=60).total_seconds()
FIRMWARE_HOURS_MAX = dt.timedelta(hours=134).total_seconds()
@@ -63,6 +98,9 @@
effect=Effect.CLEAR,
)
+# the ZHA commands are used as the canonical command representation for this
+# integration. other integrations map their command representation to these
+# before using methods such as `_handle_generic_event`.
DISMISSAL_COMMANDS = {
"button_3_double",
"button_6_double",
@@ -86,6 +124,16 @@
"button_6_double",
}
+Z2M_ACTION_TO_COMMAND_MAP = {
+ "config_double": "button_3_double",
+}
+
+ZWAVE_COMMAND_MAP = {
+ ("003", "KeyPressed2x"): "button_3_double",
+}
+
+MATTER_COMMAND_MAP = {("config", "multi_press_2"): "button_3_double"}
+
class _LEDMode(IntEnum):
ALL = 1
@@ -96,6 +144,16 @@ class _DismissalBlocked(Exception):
pass
+class _SwitchKeyType(Enum):
+ DEVICE_ID = auto()
+ MQTT_NAME = auto() # name from MQTT's discovery `state_topic`
+
+
+class _SwitchKey[T](NamedTuple):
+ type: _SwitchKeyType
+ identifier: T
+
+
class _StartScriptResult(NamedTuple):
led_config: tuple[LEDConfig, ...] | None
block_activation: bool
@@ -106,6 +164,10 @@ class _EndScriptResult(NamedTuple):
block_next: bool
+class _UnknownIntegrationError(Exception):
+ pass
+
+
class _LampieUnmanagedSwitchCoordinator:
def async_update_listeners(self) -> None:
pass
@@ -134,24 +196,42 @@ def __init__(self, hass: HomeAssistant) -> None:
self._coordinators: dict[Slug, LampieUpdateCoordinator] = {}
self._notifications: dict[Slug, LampieNotificationInfo] = {}
self._switches: dict[SwitchId, LampieSwitchInfo] = {}
- self._device_switches: dict[DeviceId, SwitchId] = {}
- self._cancel_zha_listener: CALLBACK_TYPE = hass.bus.async_listen(
- "zha_event",
- self._handle_zha_event,
- self._filter_zha_events,
- )
+ self._switch_ids: dict[_SwitchKey[DeviceId | MQTTDeviceName], SwitchId] = {}
+ self._mqtt_subscriptions: set[str] = set()
+ self._teardown_callbacks: list[CALLBACK_TYPE] = [
+ hass.bus.async_listen(
+ "zha_event",
+ self._handle_zha_event,
+ self._filter_zha_events,
+ ),
+ hass.bus.async_listen(
+ "zwave_js_value_notification",
+ self._handle_zwave_event,
+ self._filter_zwave_events,
+ ),
+ hass.bus.async_listen( # matter listener is for any state change event
+ "state_changed",
+ self._handle_matter_event,
+ self._filter_matter_events,
+ ),
+ ]
- def add_coordinator(self, coordinator: LampieUpdateCoordinator) -> None:
+ async def add_coordinator(self, coordinator: LampieUpdateCoordinator) -> None:
self._coordinators[coordinator.slug] = coordinator
- self._update_references()
+ await self._update_references()
- def remove_coordinator(self, coordinator: LampieUpdateCoordinator) -> None:
+ async def remove_coordinator(self, coordinator: LampieUpdateCoordinator) -> None:
self._coordinators.pop(coordinator.slug)
- self._update_references()
+ await self._update_references()
+
+ async def setup(self) -> None:
+ pass
def teardown(self) -> bool:
if len(self._coordinators) == 0:
- self._cancel_zha_listener()
+ for callback in self._teardown_callbacks:
+ callback()
+
for key, expiration in (
*((key, info.expiration) for key, info in self._notifications.items()),
*((key, info.expiration) for key, info in self._switches.items()),
@@ -161,35 +241,6 @@ def teardown(self) -> bool:
return False
def switch_info(self, switch_id: SwitchId) -> LampieSwitchInfo:
- if switch_id not in self._switches:
- entity_registry = er.async_get(self._hass)
- device_id = async_entity_id_to_device_id(self._hass, switch_id)
- entity_entries = er.async_entries_for_device(entity_registry, device_id)
- local_protection_id = None
- disable_clear_notification_id = None
-
- _LOGGER.debug(
- "searching %s related entries: %s",
- switch_id,
- [(entry.unique_id, entry.translation_key) for entry in entity_entries],
- )
-
- for entity_entry in entity_entries:
- if entity_entry.translation_key == "local_protection":
- local_protection_id = entity_entry.entity_id
- if (
- entity_entry.translation_key
- == "disable_clear_notifications_double_tap"
- ):
- disable_clear_notification_id = entity_entry.entity_id
-
- self._device_switches[device_id] = switch_id
- self._switches[switch_id] = LampieSwitchInfo(
- led_config=(),
- led_config_source=LEDConfigSource(None),
- local_protection_id=local_protection_id,
- disable_clear_notification_id=disable_clear_notification_id,
- )
return self._switches[switch_id]
def store_switch_info(
@@ -221,6 +272,187 @@ def store_switch_info(
"off" if _all_clear(config) else "on",
)
+ async def _ensure_switch_setup_completed(self, switch_id: SwitchId) -> None:
+ if switch_id in self._switches:
+ return
+
+ entity_registry = er.async_get(self._hass)
+ device_registry = dr.async_get(self._hass)
+ device_id = async_entity_id_to_device_id(self._hass, switch_id)
+ device = device_registry.async_get(device_id)
+ integration = self._switch_integration(device) if device else None
+ entity_entries = er.async_entries_for_device(entity_registry, device_id)
+
+ if not integration:
+ raise _UnknownIntegrationError
+
+ _LOGGER.debug(
+ "searching %s related entries: %s",
+ switch_id,
+ [
+ (
+ entry.domain,
+ entry.platform,
+ entry.unique_id,
+ entry.translation_key,
+ )
+ for entry in entity_entries
+ ],
+ )
+
+ integration_info = await {
+ Integration.ZHA: self._zha_switch_setup,
+ Integration.Z2M: self._z2m_switch_setup,
+ Integration.ZWAVE: self._zwave_switch_setup,
+ Integration.MATTER: self._matter_switch_setup,
+ }[integration](switch_id, device, entity_entries)
+
+ self._switch_ids[_SwitchKey(_SwitchKeyType.DEVICE_ID, device_id)] = switch_id
+ self._switches[switch_id] = LampieSwitchInfo(
+ integration=integration,
+ integration_info=integration_info,
+ led_config=(),
+ led_config_source=LEDConfigSource(None),
+ )
+
+ async def _zha_switch_setup( # noqa: PLR6301
+ self,
+ switch_id: SwitchId, # noqa: ARG002
+ device: dr.DeviceEntry, # noqa: ARG002
+ entity_entries: list[er.RegistryEntry],
+ ) -> ZHASwitchInfo:
+ local_protection_id = None
+ disable_clear_notification_id = None
+
+ def is_zha_platform(entry: er.RegistryEntry) -> bool:
+ return bool(entry.platform == ZHA_DOMAIN)
+
+ for entity_entry in filter(is_zha_platform, entity_entries):
+ if (
+ entity_entry.domain == SWITCH_DOMAIN
+ and entity_entry.translation_key == "local_protection"
+ ):
+ local_protection_id = entity_entry.entity_id
+ _LOGGER.debug(
+ "found ZHA local protection entity: %s",
+ local_protection_id,
+ )
+ if entity_entry.domain == SWITCH_DOMAIN and (
+ entity_entry.translation_key == "disable_clear_notifications_double_tap"
+ ):
+ disable_clear_notification_id = entity_entry.entity_id
+ _LOGGER.debug(
+ "found ZHA disable clear notification entity: %s",
+ disable_clear_notification_id,
+ )
+
+ return ZHASwitchInfo(
+ local_protection_id=local_protection_id,
+ disable_clear_notification_id=disable_clear_notification_id,
+ )
+
+ async def _z2m_switch_setup(
+ self,
+ switch_id: SwitchId,
+ device: dr.DeviceEntry,
+ entity_entries: list[er.RegistryEntry], # noqa: ARG002
+ ) -> Z2MSwitchInfo | None:
+ if not await mqtt.async_wait_for_mqtt_client(self._hass):
+ _LOGGER.error("MQTT integration is not available")
+ return None
+
+ try:
+ full_topic = self._hass.data[DATA_MQTT].debug_info_entities[switch_id][
+ "discovery_data"
+ ]["discovery_payload"]["state_topic"]
+
+ _LOGGER.debug(
+ "found Z2M full topic for switch %s: %s", switch_id, full_topic
+ )
+ except (KeyError, AttributeError):
+ full_topic = f"zigbee2mqtt/{device.name}"
+
+ _LOGGER.warning(
+ "failed to determine MQTT topic from internal HASS state for %s. "
+ "using default of %s from device name",
+ switch_id,
+ full_topic,
+ )
+
+ # pull out the base topic and the device name from the full topic.
+ base_topic, mqtt_device_name = full_topic.rsplit("/", 1)
+
+ self._switch_ids[_SwitchKey(_SwitchKeyType.MQTT_NAME, mqtt_device_name)] = (
+ switch_id
+ )
+
+ # subscribe to the base topic for state updates of `localProtection` and
+ # `doubleTapClearNotifications` as well as the `action` updates for
+ # interaction on switches.
+ if base_topic not in self._mqtt_subscriptions:
+ _LOGGER.debug("subscribing to %s for %s", base_topic, switch_id)
+ self._mqtt_subscriptions.add(base_topic)
+ self._teardown_callbacks.append(
+ await mqtt.async_subscribe(
+ self._hass,
+ f"{base_topic}/+",
+ self._handle_z2m_message,
+ )
+ )
+
+ # request an update to attributes we need to know the value of later.
+ # these will be processed in the event handler.
+ await self._hass.services.async_call(
+ MQTT_DOMAIN,
+ "publish",
+ {
+ "topic": f"{full_topic}/get",
+ "payload": json_dumps(
+ {
+ "localProtection": "",
+ "doubleTapClearNotifications": "",
+ }
+ ),
+ },
+ blocking=True,
+ )
+
+ return Z2MSwitchInfo(full_topic=full_topic)
+
+ async def _zwave_switch_setup( # noqa: PLR6301
+ self,
+ switch_id: SwitchId, # noqa: ARG002
+ device: dr.DeviceEntry, # noqa: ARG002
+ entity_entries: list[er.RegistryEntry], # noqa: ARG002
+ ) -> ZWaveSwitchInfo:
+ return ZWaveSwitchInfo()
+
+ async def _matter_switch_setup( # noqa: PLR6301
+ self,
+ switch_id: SwitchId, # noqa: ARG002
+ device: dr.DeviceEntry, # noqa: ARG002
+ entity_entries: list[er.RegistryEntry],
+ ) -> MatterSwitchInfo:
+ effect_id = None
+
+ def is_matter_platform(entry: er.RegistryEntry) -> bool:
+ return bool(entry.platform == MATTER_DOMAIN)
+
+ for entity_entry in filter(is_matter_platform, entity_entries):
+ if (
+ entity_entry.domain == LIGHT_DOMAIN
+ and entity_entry.translation_key == "effect"
+ ):
+ effect_id = entity_entry.entity_id
+ _LOGGER.debug(
+ "found Matter effect entity: %s",
+ effect_id,
+ )
+
+ return MatterSwitchInfo(
+ effect_id=effect_id,
+ )
+
def has_notification(self, slug: Slug) -> bool:
return slug in self._coordinators
@@ -291,6 +523,7 @@ async def activate_notification(
or start_action_response.led_config
or coordinator.led_config,
partial(self._async_handle_notification_expired, slug),
+ has_expiration_messaging=self._any_has_expiration_messaging(switches),
log_context=slug,
),
)
@@ -376,6 +609,11 @@ async def override_switch(
The switch state will be stored and the physical switch will be updated
to show the specified configuration.
"""
+
+ # since this may be a switch that's not part of any of the config
+ # entries, we need to start by ensuring it's set up.
+ await self._ensure_switch_setup_completed(switch_id)
+
from_state = self.switch_info(switch_id)
is_reset = led_config is None
@@ -385,6 +623,9 @@ async def override_switch(
from_state.expiration,
led_config or (),
partial(self._async_handle_switch_override_expired, switch_id),
+ has_expiration_messaging=self._any_has_expiration_messaging(
+ [switch_id]
+ ),
log_context=f"{led_config_source}",
),
)
@@ -666,11 +907,14 @@ async def _issue_switch_commands(
TRACE, "issue_switch_commands: %s; %s, %s", switch_id, led_mode, led_config
)
- from_params = [self._switch_command_led_params(led) for led in from_config]
- device_info = async_device_info_to_link_from_entity(self._hass, switch_id)
- id_tuple = next(iter(device_info["identifiers"]))
+ from_params = [
+ self._switch_command_led_params(led, switch_id) for led in from_config
+ ]
+ device_registry = dr.async_get(self._hass)
+ device_id = async_entity_id_to_device_id(self._hass, switch_id)
+ device = device_registry.async_get(device_id=device_id)
+
updated_leds = []
- ieee = id_tuple[1]
# actually apply the desired changes either for the full LED bar or for
# individual lights within the bar. only apply changes when needed to
@@ -678,7 +922,7 @@ async def _issue_switch_commands(
# no changes need to be applied if the changes were via the switch
# firmware since the switch will already have cleared the notification.
for idx, led in enumerate(led_config):
- params = self._switch_command_led_params(led)
+ params = self._switch_command_led_params(led, switch_id)
with suppress(IndexError):
if params == from_params[idx]:
@@ -690,20 +934,11 @@ async def _issue_switch_commands(
updated_leds.append(str(idx))
_LOGGER.log(TRACE, "update LED %s command: %r", idx, params)
- await self._hass.services.async_call(
- ZHA_DOMAIN,
- "issue_zigbee_cluster_command",
- {
- "ieee": ieee,
- "endpoint_id": 1,
- "cluster_id": 64561,
- "cluster_type": "in",
- "command": int(led_mode),
- "command_type": "server",
- "params": params,
- "manufacturer": 4655,
- },
- blocking=True,
+ await self._dispatch_service_command(
+ switch_id=switch_id,
+ device=device,
+ led_mode=led_mode,
+ params=params,
)
_LOGGER.debug(
@@ -712,9 +947,224 @@ async def _issue_switch_commands(
"all" if led_mode == _LEDMode.ALL else ", ".join(updated_leds),
)
- @classmethod
- def _switch_command_led_params(cls, led: LEDConfig) -> dict[str, Any]:
- firmware_duration = cls._firmware_duration(led.duration)
+ async def _dispatch_service_command(
+ self,
+ *,
+ switch_id: SwitchId,
+ device: dr.DeviceEntry,
+ led_mode: _LEDMode,
+ params: dict[str, Any],
+ ) -> None:
+ switch_info = self.switch_info(switch_id)
+ service_command = {
+ Integration.ZHA: self._zha_service_command,
+ Integration.Z2M: self._z2m_service_command,
+ Integration.ZWAVE: self._zwave_service_command,
+ Integration.MATTER: self._matter_service_command,
+ }[switch_info.integration]
+
+ await service_command(
+ switch_id=switch_id,
+ device=device,
+ led_mode=led_mode,
+ params=params,
+ )
+
+ async def _zha_service_command(
+ self,
+ *,
+ switch_id: SwitchId,
+ device: dr.DeviceEntry,
+ led_mode: _LEDMode,
+ params: dict[str, Any],
+ ) -> None:
+ id_tuple = next(iter(device.identifiers))
+ ieee = id_tuple[1]
+
+ _LOGGER.log(
+ TRACE, "zha.issue_zigbee_cluster_command: %s, mode=%s", switch_id, led_mode
+ )
+ await self._hass.services.async_call(
+ ZHA_DOMAIN,
+ "issue_zigbee_cluster_command",
+ {
+ "ieee": ieee,
+ "endpoint_id": 1,
+ "cluster_id": 64561,
+ "cluster_type": "in",
+ "command": int(led_mode),
+ "command_type": "server",
+ "params": params,
+ "manufacturer": 4655,
+ },
+ blocking=True,
+ )
+
+ async def _z2m_service_command(
+ self,
+ *,
+ switch_id: SwitchId,
+ device: dr.DeviceEntry, # noqa: ARG002
+ led_mode: _LEDMode,
+ params: dict[str, Any],
+ ) -> None:
+ command = (
+ "individual_led_effect" if led_mode == _LEDMode.INDIVIDUAL else "led_effect"
+ )
+ switch_info = self.switch_info(switch_id)
+ zha_info = switch_info.integration_info
+ topic = f"{zha_info.full_topic}/set"
+
+ _LOGGER.log(TRACE, "mqtt.publish: %s for %s", topic, switch_id)
+ await self._hass.services.async_call(
+ MQTT_DOMAIN,
+ "publish",
+ {
+ "topic": topic,
+ "payload": json_dumps({command: params}),
+ },
+ blocking=True,
+ )
+
+ async def _zwave_service_command(
+ self,
+ *,
+ switch_id: SwitchId,
+ device: dr.DeviceEntry,
+ led_mode: _LEDMode,
+ params: dict[str, Any],
+ ) -> None:
+ model = next(
+ iter({model for model in INOVELLI_MODELS if model in device.model}), None
+ )
+
+ assert model is not None, (
+ f"failed to lookup model switch {switch_id}: {device.model}"
+ )
+ effect_mapping = ZWAVE_EFFECT_MAPPING.get(model, {})
+ led_effect = params["led_effect"]
+ led_effect = effect_mapping.get(led_effect, led_effect)
+
+ # different models target different parameters & targeting individual
+ # LEDs is also through different parameters. pull out the one for the
+ # model & setting all LEDs, then update to the individual parameter
+ # if the switch supports it (or just set the first LED).
+ if "LZW36" in model:
+ combo_button = switch_id.split(".", 1)[0]
+ parameter_key = f"{model}_{combo_button}"
+ else:
+ parameter_key = model
+
+ parameter = ZWAVE_EFFECT_PARAMETERS.get(parameter_key)
+
+ if led_mode == _LEDMode.INDIVIDUAL:
+ led_number = params["led_number"]
+ individual_parameter = ZWAVE_EFFECT_PARAMETERS.get(
+ f"{parameter_key}_individual_{led_number + 1}"
+ )
+
+ if individual_parameter is not None:
+ parameter = individual_parameter
+ elif led_number != 0:
+ _LOGGER.warning(
+ "skipping setting LED_%s (idx %s) on for model %s: "
+ "individual LEDs unsupported",
+ led_number + 1,
+ led_number,
+ model,
+ )
+ return
+
+ assert parameter is not None, (
+ f"failed to lookup notification parameter for {model}"
+ )
+
+ _LOGGER.log(
+ TRACE,
+ "zwave_js.bulk_set_partial_config_parameters: parameter=%s for model %s",
+ parameter,
+ model,
+ )
+
+ if model in { # older models have a different value calculation
+ "LZW30-SN", # red on/off switch
+ "LZW31-SN", # red dimmer
+ "LZW36", # red fan/light combo
+ }:
+ value = (
+ params["led_color"]
+ + (int(params["led_level"] / 10) << 8)
+ + (params["led_duration"] << 16)
+ + (led_effect << 24)
+ )
+ else:
+ value = (
+ params["led_duration"]
+ + (params["led_level"] << 8)
+ + (params["led_color"] << 16)
+ + (led_effect << 24)
+ )
+
+ await self._hass.services.async_call(
+ ZWAVE_DOMAIN,
+ "bulk_set_partial_config_parameters",
+ {
+ "device_id": device.id,
+ "parameter": parameter,
+ "value": value,
+ },
+ )
+
+ async def _matter_service_command(
+ self,
+ *,
+ switch_id: SwitchId,
+ device: dr.DeviceEntry,
+ led_mode: _LEDMode,
+ params: dict[str, Any],
+ ) -> None:
+ if (
+ led_mode == _LEDMode.INDIVIDUAL
+ and (led_number := params["led_number"]) != 0
+ ):
+ _LOGGER.warning(
+ "skipping setting LED_%s (idx %s) on for model %s: "
+ "individual LEDs unsupported",
+ led_number + 1,
+ led_number,
+ device.model,
+ )
+ return
+
+ switch_info = self.switch_info(switch_id)
+
+ service_call_action = "turn_on"
+ service_call_data = {
+ "brightness_pct": params["led_level"],
+ "hs_color": [round(((params["led_color"] / 255) * 360), 1), 100],
+ }
+
+ if params["led_effect"] == Effect.CLEAR.value:
+ service_call_action = "turn_off"
+ service_call_data = {}
+
+ await self._hass.services.async_call(
+ LIGHT_DOMAIN,
+ service_call_action,
+ {
+ "entity_id": switch_info.integration_info.effect_id,
+ **service_call_data,
+ },
+ )
+
+ def _switch_command_led_params(
+ self, led: LEDConfig, switch_id: SwitchId
+ ) -> dict[str, Any]:
+ firmware_duration = (
+ self._firmware_duration(led.duration)
+ if self._any_has_expiration_messaging([switch_id])
+ else None
+ )
return {
"led_color": int(led.color),
@@ -727,8 +1177,97 @@ def _switch_command_led_params(cls, led: LEDConfig) -> dict[str, Any]:
else firmware_duration,
}
- @classmethod
- def _firmware_duration(cls, seconds: int | None) -> int | None:
+ def _local_protection_enabled(self, switch_id: SwitchId) -> bool:
+ switch_info = self.switch_info(switch_id)
+ integration = switch_info.integration
+ integration_info = switch_info.integration_info
+
+ assert integration_info is not None, (
+ f"missing integration info for switch {switch_id}"
+ )
+
+ def _zha() -> bool:
+ zha_info: ZHASwitchInfo = integration_info
+
+ return bool(
+ local_protection_id := zha_info.local_protection_id
+ ) and self._hass.states.is_state(local_protection_id, "on")
+
+ def _z2m() -> bool:
+ z2m_info: Z2MSwitchInfo = integration_info
+ return bool(z2m_info.local_protection_enabled)
+
+ def _zwave() -> bool:
+ return False # unsupported for now
+
+ def _matter() -> bool:
+ return False # unsupported for now
+
+ return {
+ Integration.ZHA: _zha,
+ Integration.Z2M: _z2m,
+ Integration.ZWAVE: _zwave,
+ Integration.MATTER: _matter,
+ }[integration]()
+
+ def _double_tap_clear_notifications_disabled(self, switch_id: SwitchId) -> bool:
+ switch_info = self.switch_info(switch_id)
+ integration = switch_info.integration
+ integration_info = switch_info.integration_info
+
+ assert integration_info is not None, (
+ f"missing integration info for switch {switch_id}"
+ )
+
+ def _zha() -> bool:
+ zha_info: ZHASwitchInfo = integration_info
+
+ return bool(
+ disable_clear_notification_id := zha_info.disable_clear_notification_id
+ ) and self._hass.states.is_state(disable_clear_notification_id, "on")
+
+ def _z2m() -> bool:
+ z2m_info: Z2MSwitchInfo = integration_info
+ return bool(z2m_info.double_tap_clear_notifications_disabled)
+
+ def _zwave() -> bool:
+ return False # unsupported for now
+
+ def _matter() -> bool:
+ return False # unsupported for now
+
+ return {
+ Integration.ZHA: _zha,
+ Integration.Z2M: _z2m,
+ Integration.ZWAVE: _zwave,
+ Integration.MATTER: _matter,
+ }[integration]()
+
+ def _any_has_expiration_messaging(self, switches: Sequence[SwitchId]) -> bool:
+ """Check if any switch via an integration that sends expiration events.
+
+ All Inovelli Blue series switches send Zigbee messages for notifications
+ expiring (when local protection is not enabled). However, integrations
+ currently handle them differently:
+ ZHA: Processes these and turns them into
+ `led_effect_complete_{ALL|LED_1-7}` commands.
+ Z2M: Processes these and turns them into
+ `{"notificationComplete": "{ALL|LED_1-7}"}` messages.
+ ZWAVE: May or may not create these messages.
+ MATTER: May or may not create these messages.
+
+ Returns:
+ A boolean indicating if any of the switches are part of such an
+ integration.
+ """
+ return any(
+ self.switch_info(switch_id).integration
+ in {Integration.ZHA, Integration.Z2M}
+ for switch_id in switches
+ )
+
+ @staticmethod
+ def _firmware_duration(seconds: int | None) -> int | None:
"""Convert a timeframe to a duration supported by the switch firmware.
Args:
@@ -753,17 +1292,174 @@ def _filter_zha_events(
self,
event_data: ZHAEventData,
) -> bool:
+ switch_key = _SwitchKey(_SwitchKeyType.DEVICE_ID, event_data["device_id"])
return (
- event_data["device_id"] in self._device_switches
+ switch_key in self._switch_ids
and event_data["command"] in DISMISSAL_COMMANDS
)
@callback
async def _handle_zha_event(self, event: Event[ZHAEventData]) -> None:
- hass = self._hass
- command = event.data["command"]
device_id = event.data["device_id"]
- switch_id = self._device_switches[device_id]
+ switch_key = _SwitchKey(_SwitchKeyType.DEVICE_ID, device_id)
+ switch_id = self._switch_ids[switch_key]
+ await self._handle_generic_event(
+ command=event.data["command"],
+ switch_id=switch_id,
+ integration=Integration.ZHA,
+ )
+
+ @callback
+ async def _handle_z2m_message(self, message: mqtt.ReceiveMessage) -> None:
+ _LOGGER.log(TRACE, "handling mqtt message: %s", message)
+
+ _, mqtt_device_name = message.topic.rsplit("/", 1)
+ switch_key = _SwitchKey(_SwitchKeyType.MQTT_NAME, mqtt_device_name)
+ switch_id = self._switch_ids.get(switch_key)
+
+ # ignore the message if it's not related to a switch in the system
+ if switch_id is None:
+ return
+
+ switch_info = self.switch_info(switch_id)
+ zha_info = switch_info.integration_info
+ zha_info_changed = False
+
+ # ignore the message if for some reason there's a topic mismatch, i.e.
+ # two devices named with different base topics.
+ if message.topic != zha_info.full_topic:
+ return
+
+ # wait to parse the payload until the above checks are performed to avoid
+ # the overhead of parsing messages we won't handle.
+ payload = json_loads_object(message.payload)
+
+ # if this is for a valid action, dispatch it to the generic event handler
+ if (action := payload.get("action")) and (
+ command := Z2M_ACTION_TO_COMMAND_MAP.get(action)
+ ):
+ await self._handle_generic_event(
+ command=command,
+ switch_id=switch_id,
+ integration=Integration.Z2M,
+ )
+
+ if (
+ (notification_complete := payload.get("notificationComplete"))
+ and (command := f"led_effect_complete_{notification_complete}")
+ and command in DISMISSAL_COMMANDS
+ ):
+ await self._handle_generic_event(
+ command=command,
+ switch_id=switch_id,
+ integration=Integration.Z2M,
+ )
+
+ if "localProtection" in payload:
+ zha_info_changed = True
+ zha_info = replace(
+ zha_info,
+ local_protection_enabled=payload["localProtection"].lower()
+ in {"enabled", "enabled (default)"},
+ )
+
+ if "doubleTapClearNotifications" in payload:
+ zha_info_changed = True
+ zha_info = replace(
+ zha_info,
+ double_tap_clear_notifications_disabled=payload[
+ "doubleTapClearNotifications"
+ ].lower()
+ == "disabled",
+ )
+
+ if zha_info_changed:
+ self.store_switch_info(
+ switch_id,
+ integration_info=zha_info,
+ )
+
+ @callback
+ def _filter_zwave_events(
+ self,
+ event_data: ZWaveEventData,
+ ) -> bool:
+ switch_key = _SwitchKey(_SwitchKeyType.DEVICE_ID, event_data["device_id"])
+ command = ZWAVE_COMMAND_MAP.get(
+ (
+ event_data["property_key_name"],
+ event_data["value"],
+ )
+ )
+ return switch_key in self._switch_ids and command in DISMISSAL_COMMANDS
+
+ @callback
+ async def _handle_zwave_event(self, event: Event[ZWaveEventData]) -> None:
+ device_id = event.data["device_id"]
+ switch_key = _SwitchKey(_SwitchKeyType.DEVICE_ID, device_id)
+ switch_id = self._switch_ids[switch_key]
+ command = ZWAVE_COMMAND_MAP[
+ event.data["property_key_name"],
+ event.data["value"],
+ ]
+ await self._handle_generic_event(
+ command=command,
+ switch_id=switch_id,
+ integration=Integration.ZWAVE,
+ )
+
+ @callback
+ def _filter_matter_events(
+ self,
+ event_data: MatterEventData,
+ ) -> bool:
+ entity_id = event_data["entity_id"]
+
+ if (
+ not entity_id.startswith("event.")
+ or not (entity_registry := er.async_get(self._hass))
+ or not (event_entity := entity_registry.async_get(entity_id))
+ or not (
+ switch_key := _SwitchKey(
+ _SwitchKeyType.DEVICE_ID, event_entity.device_id
+ )
+ )
+ ):
+ return False
+
+ state = event_data["new_state"]
+ attributes = state["attributes"]
+ command = MATTER_COMMAND_MAP.get(
+ (event_entity.translation_key, attributes.get("event_type"))
+ )
+ return switch_key in self._switch_ids and command in DISMISSAL_COMMANDS
+
+ @callback
+ async def _handle_matter_event(self, event: Event[MatterEventData]) -> None:
+ entity_id = event.data["entity_id"]
+ entity_registry = er.async_get(self._hass)
+ event_entity = entity_registry.async_get(entity_id)
+ device_id = event_entity.device_id
+ switch_key = _SwitchKey(_SwitchKeyType.DEVICE_ID, device_id)
+ switch_id = self._switch_ids[switch_key]
+ state = event.data["new_state"]
+ attributes = state["attributes"]
+ command = MATTER_COMMAND_MAP[
+ event_entity.translation_key, attributes["event_type"]
+ ]
+ await self._handle_generic_event(
+ command=command,
+ switch_id=switch_id,
+ integration=Integration.MATTER,
+ )
+
+ async def _handle_generic_event(
+ self,
+ *,
+ command: str,
+ switch_id: SwitchId,
+ integration: Integration,
+ ) -> None:
from_state = self.switch_info(switch_id)
led_config_source = from_state.led_config_source
led_config = [*from_state.led_config]
@@ -775,14 +1471,17 @@ async def _handle_zha_event(self, event: Event[ZHAEventData]) -> None:
if not led_config or not led_config_source:
_LOGGER.warning(
"missing LED config and/or source for dismissal on switch %s; "
- "skipping processing ZHA event %s",
+ "skipping processing %s event %s",
switch_id,
+ str(integration).upper(),
command,
stack_info=True,
)
return
- _LOGGER.debug("processing ZHA event %s on %s", command, switch_id)
+ _LOGGER.debug(
+ "processing %s event %s on %s", str(integration).upper(), command, switch_id
+ )
if match := re.search(r"_LED_(\d+)$", command):
index = int(match[1]) - 1
@@ -806,15 +1505,9 @@ async def _handle_zha_event(self, event: Event[ZHAEventData]) -> None:
all_clear = True
if all_clear and command in CONFIGURABLE_COMMANDS:
- switch_info = self.switch_info(switch_id)
- local_protection = switch_info.local_protection_id and hass.states.is_state(
- switch_info and switch_info.local_protection_id, "on"
- )
- disable_clear_notification = (
- switch_info.disable_clear_notification_id
- and hass.states.is_state(
- switch_info.disable_clear_notification_id, "on"
- )
+ local_protection = self._local_protection_enabled(switch_id)
+ disable_clear_notification = self._double_tap_clear_notifications_disabled(
+ switch_id
)
is_valid_dismissal = not disable_clear_notification
@@ -1018,6 +1711,9 @@ async def _async_handle_expiration(
led_config,
handle_expired,
is_starting=False,
+ has_expiration_messaging=self._any_has_expiration_messaging(
+ switches
+ ),
log_context=f"partial-expiry {log_context}",
),
)
@@ -1044,6 +1740,7 @@ def _schedule_fallback_expiration(
callback: _ExpirationHandler,
*,
is_starting: bool = True,
+ has_expiration_messaging: bool,
log_context: str,
) -> ExpirationInfo:
started_at = expiration.started_at
@@ -1060,7 +1757,10 @@ def _schedule_fallback_expiration(
for item in led_config
if item.duration is not None
and item.duration != ALREADY_EXPIRED
- and self._firmware_duration(item.duration) is None
+ and (
+ not has_expiration_messaging
+ or self._firmware_duration(item.duration) is None
+ )
]
duration: int | None = min(durations) if durations else None
@@ -1105,7 +1805,7 @@ def _cancel_expiration(
return expiration
- def _update_references(self) -> None:
+ async def _update_references(self) -> None:
processed_slugs: list[Slug] = []
processed_switches: set[SwitchId] = set()
@@ -1118,8 +1818,18 @@ def _update_references(self) -> None:
)
for switch_id in switch_ids:
+ try:
+ await self._ensure_switch_setup_completed(switch_id)
+ except _UnknownIntegrationError:
+ _LOGGER.exception(
+ "ignoring switch %s: could not to a valid integration",
+ switch_id,
+ )
+ continue
+
+ switch_info = self.switch_info(switch_id)
priorities = switch_priorities.get(switch_id) or [slug]
- expected = [*self.switch_info(switch_id).priorities]
+ expected = [*switch_info.priorities]
if switch_id in processed_switches and expected != priorities:
_LOGGER.warning(
@@ -1137,13 +1847,19 @@ def _update_references(self) -> None:
continue
self._switches[switch_id] = replace(
- self.switch_info(switch_id),
+ switch_info,
priorities=tuple(priorities),
)
processed_switches.add(switch_id)
processed_slugs.append(slug)
+ @classmethod
+ def _switch_integration(cls, device: dr.DeviceEntry) -> Integration | None:
+ id_tuple = next(iter(device.identifiers))
+ domain = id_tuple[0]
+ return SWITCH_INTEGRATIONS.get(domain)
+
def _all_clear(led_config: Sequence[LEDConfig]) -> bool:
return all(item.effect == Effect.CLEAR for item in led_config)
diff --git a/custom_components/lampie/services.yaml b/custom_components/lampie/services.yaml
index 6a40526..9e05dc6 100644
--- a/custom_components/lampie/services.yaml
+++ b/custom_components/lampie/services.yaml
@@ -16,10 +16,14 @@ override:
entity:
multiple: true
filter:
- integration: zha
- domain:
- - light
- - fan
+ - integration: zha
+ domain:
+ - light
+ - fan
+ - integration: mqtt
+ domain:
+ - light
+ - fan
leds:
required: true
selector:
diff --git a/custom_components/lampie/types.py b/custom_components/lampie/types.py
index 1448c87..484e05f 100644
--- a/custom_components/lampie/types.py
+++ b/custom_components/lampie/types.py
@@ -245,6 +245,15 @@ class InvalidColor(Exception):
index: int | None = None
+class Integration(StrEnum):
+ """Switch integration type."""
+
+ ZHA = auto()
+ Z2M = auto()
+ ZWAVE = auto()
+ MATTER = auto()
+
+
@dataclass(frozen=True)
class ExpirationInfo:
"""Storage of expiration info."""
@@ -271,14 +280,43 @@ class LampieNotificationOptionsDict(TypedDict):
expiration: NotRequired[ExpirationInfo]
+@dataclass(frozen=True)
+class ZHASwitchInfo:
+ """ZHA switch info data class."""
+
+ local_protection_id: EntityId | None = None
+ disable_clear_notification_id: EntityId | None = None
+
+
+@dataclass(frozen=True)
+class Z2MSwitchInfo:
+ """Z2M switch info data class."""
+
+ full_topic: str
+ local_protection_enabled: bool | None = None
+ double_tap_clear_notifications_disabled: bool | None = None
+
+
+@dataclass(frozen=True)
+class ZWaveSwitchInfo:
+ """ZWave switch info data class."""
+
+
+@dataclass(frozen=True)
+class MatterSwitchInfo:
+ """Matter switch info data class."""
+
+ effect_id: EntityId | None = None
+
+
@dataclass(frozen=True)
class LampieSwitchInfo:
"""Lampie switch data class."""
led_config: tuple[LEDConfig, ...]
led_config_source: LEDConfigSource
- local_protection_id: EntityId | None = None
- disable_clear_notification_id: EntityId | None = None
+ integration_info: Any
+ integration: Integration = Integration.ZHA
priorities: tuple[Slug, ...] = field(default_factory=tuple)
expiration: ExpirationInfo = field(default_factory=ExpirationInfo)
@@ -288,8 +326,7 @@ class LampieSwitchOptionsDict(TypedDict):
led_config: NotRequired[tuple[LEDConfig, ...]]
led_config_source: NotRequired[LEDConfigSource]
- local_protection_id: NotRequired[EntityId | None]
- disable_clear_notification_id: NotRequired[EntityId | None]
+ integration_info: NotRequired[Any | None]
priorities: NotRequired[tuple[Slug, ...]]
expiration: NotRequired[ExpirationInfo]
diff --git a/tests/__init__.py b/tests/__init__.py
index abc745c..b7ff5ab 100644
--- a/tests/__init__.py
+++ b/tests/__init__.py
@@ -2,6 +2,7 @@
from __future__ import annotations
+from dataclasses import dataclass
import datetime as dt
from enum import Flag
from typing import Any
@@ -14,8 +15,12 @@
async_fire_time_changed,
)
+from custom_components.lampie.orchestrator import DATA_MQTT
+from custom_components.lampie.types import Integration
+
ZHA_DOMAIN = "zha"
MOCK_UTC_NOW = dt.datetime(2025, 5, 20, 10, 51, 32, 3245, tzinfo=dt.UTC)
+MOCK_Z2M_DEVICE_ID = "mock-z2m-device-name" # note: in a real system, this is in the format 0x0000000000000000
class _ANY:
@@ -37,6 +42,17 @@ def _tick(self, seconds) -> None:
async_fire_time_changed(self.hass)
+@dataclass
+class IntegrationConfig:
+ integration: Integration
+ model: str | None = None
+
+ def __str__(self):
+ if self.model:
+ return f"{self.integration}-{self.model.lower()}"
+ return self.integration
+
+
async def setup_integration(hass: HomeAssistant, config_entry: MockConfigEntry) -> None:
"""Set up the component."""
config_entry.add_to_hass(hass)
@@ -53,7 +69,11 @@ async def setup_added_integration(
def add_mock_switch(
- hass, entity_id, device_attrs: dict[str, Any] | None = None
+ hass,
+ entity_id,
+ device_attrs: dict[str, Any] | None = None,
+ *,
+ integration: Integration = Integration.ZHA,
) -> er.RegistryEntry:
"""Add a switch device and (some) related entities.
@@ -61,41 +81,123 @@ def add_mock_switch(
The created switch entity.
"""
domain, object_id = entity_id.split(".")
+
+ integration_domain = {
+ Integration.ZHA: "zha",
+ Integration.Z2M: "mqtt",
+ Integration.ZWAVE: "zwave_js",
+ Integration.MATTER: "matter",
+ }[integration]
+
+ identifiers = {
+ Integration.ZHA: ("zha", f"mock-ieee:{object_id}"),
+ Integration.Z2M: ("mqtt", f"{MOCK_Z2M_DEVICE_ID}_{object_id}"),
+ Integration.ZWAVE: (
+ "zwave_js",
+ f"mock-zwave-driver-controller-id_mock-node-{object_id}",
+ ),
+ Integration.MATTER: ("matter", f"mock:deviceid_{object_id}-nodeid"),
+ }[integration]
+
+ model_key = "model_id" if integration == Integration.Z2M else "model"
+ model = {
+ Integration.ZHA: "VZM31-SN",
+ Integration.Z2M: "VZM31-SN",
+ Integration.ZWAVE: "VZW31-SN",
+ Integration.MATTER: "VTM31-SN",
+ }[integration]
+
device_registry = dr.async_get(hass)
entity_registry = er.async_get(hass)
mock_config_entry = MockConfigEntry(
- title=" ".join(object_id.capitalize().split("_")), domain=ZHA_DOMAIN, data={}
+ title=" ".join(object_id.capitalize().split("_")),
+ domain=integration_domain,
+ data={},
)
mock_config_entry.add_to_hass(hass)
device_entry = device_registry.async_get_or_create(
name=mock_config_entry.title,
config_entry_id=mock_config_entry.entry_id,
- identifiers={(ZHA_DOMAIN, f"mock-ieee:{object_id}")},
- **(device_attrs or {}),
+ identifiers={identifiers},
+ **{
+ model_key: model,
+ **(device_attrs or {}),
+ },
)
switch = entity_registry.async_get_or_create(
domain,
- ZHA_DOMAIN,
+ integration_domain,
object_id,
suggested_object_id=object_id,
device_id=device_entry.id,
)
- entity_registry.async_get_or_create(
- "switch",
- ZHA_DOMAIN,
- f"{object_id}-local_protection",
- suggested_object_id=f"{object_id}_local_protection",
- translation_key="local_protection",
- device_id=device_entry.id,
- )
- entity_registry.async_get_or_create(
- "switch",
- ZHA_DOMAIN,
- f"{object_id}-disable_clear_notifications_double_tap",
- suggested_object_id=f"{object_id}_disable_config_2x_tap_to_clear_notifications",
- translation_key="disable_clear_notifications_double_tap",
- device_id=device_entry.id,
- )
+
+ if integration == Integration.ZHA:
+ entity_registry.async_get_or_create(
+ "switch",
+ integration_domain,
+ f"{object_id}-local_protection",
+ suggested_object_id=f"{object_id}_local_protection",
+ translation_key="local_protection",
+ device_id=device_entry.id,
+ )
+ entity_registry.async_get_or_create(
+ "switch",
+ integration_domain,
+ f"{object_id}-disable_clear_notifications_double_tap",
+ suggested_object_id=f"{object_id}_disable_config_2x_tap_to_clear_notifications",
+ translation_key="disable_clear_notifications_double_tap",
+ device_id=device_entry.id,
+ )
+
+ if integration == Integration.Z2M:
+ entity_registry.async_get_or_create(
+ "select",
+ integration_domain,
+ f"{MOCK_Z2M_DEVICE_ID}_localProtection_zigbee2mqtt",
+ suggested_object_id=f"{object_id}_localProtection", # note: not part of real Z2M setup
+ original_name="LocalProtection",
+ capabilities={
+ "options": ["Disabled", "Enabled"],
+ },
+ device_id=device_entry.id,
+ )
+ entity_registry.async_get_or_create(
+ "select",
+ integration_domain,
+ f"{MOCK_Z2M_DEVICE_ID}_doubleTapClearNotifications_zigbee2mqtt",
+ suggested_object_id=f"{object_id}_doubleTapClearNotifications", # note: not part of real Z2M setup
+ original_name="DoubleTapClearNotifications",
+ capabilities={
+ "options": ["Enabled (Default)", "Disabled"],
+ },
+ device_id=device_entry.id,
+ )
+
+ # set a custom base topic for this. the default is `zigbee2mqtt` and
+ # here it's changed to `home/z2m`.
+ hass.data[DATA_MQTT].debug_info_entities[entity_id]["discovery_data"][
+ "discovery_payload"
+ ]["state_topic"] = f"home/z2m/{mock_config_entry.title}"
+
+ if integration == Integration.MATTER:
+ entity_registry.async_get_or_create(
+ "event",
+ integration_domain,
+ f"{object_id}-config",
+ suggested_object_id=f"{object_id}_config",
+ translation_key="config",
+ device_id=device_entry.id,
+ )
+ entity_registry.async_get_or_create(
+ "light",
+ integration_domain,
+ f"{object_id}-effect",
+ suggested_object_id=f"{object_id}_effect",
+ translation_key="effect",
+ device_id=device_entry.id,
+ )
+
return switch
diff --git a/tests/conftest.py b/tests/conftest.py
index 24e9c1f..4ac6121 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -1,6 +1,9 @@
"""Fixtures for testing."""
+from collections import defaultdict
+from collections.abc import Generator
import logging
+from unittest.mock import AsyncMock, Mock, patch
from freezegun.api import FrozenDateTimeFactory
from homeassistant.core import HomeAssistant
@@ -16,8 +19,16 @@
DOMAIN,
TRACE,
)
-
-from . import MOCK_UTC_NOW, MockNow, add_mock_switch, setup_integration
+from custom_components.lampie.orchestrator import DATA_MQTT
+from custom_components.lampie.types import Integration
+
+from . import (
+ MOCK_UTC_NOW,
+ IntegrationConfig,
+ MockNow,
+ add_mock_switch,
+ setup_integration,
+)
from .syrupy import LampieSnapshotExtension
_LOGGER = logging.getLogger(__name__)
@@ -52,6 +63,59 @@ def auto_enable_custom_integrations(enable_custom_integrations):
return
+@pytest.fixture(name="mqtt", autouse=True)
+def auto_patch_mqtt(
+ hass: HomeAssistant,
+) -> Generator[dict[str, Mock | AsyncMock]]:
+ """Patch mqtt helpers and integration setup/teardown."""
+ mock_unsubscribe = Mock()
+
+ with (
+ patch(
+ "homeassistant.components.mqtt.async_wait_for_mqtt_client",
+ return_value=True,
+ ) as mock_wait_for_mqtt_client,
+ patch(
+ "homeassistant.components.mqtt.async_subscribe",
+ return_value=mock_unsubscribe,
+ ) as mock_subscribe,
+ patch(
+ "homeassistant.components.mqtt.async_setup", return_value=True
+ ) as mock_setup,
+ patch(
+ "homeassistant.components.mqtt.async_setup_entry", return_value=True
+ ) as mock_setup_entry,
+ patch(
+ "homeassistant.components.mqtt.async_unload_entry", return_value=True
+ ) as mock_unload_entry,
+ ):
+
+ def infinite_defaultdict():
+ return defaultdict(infinite_defaultdict)
+
+ mock = Mock()
+ mock.debug_info_entities = infinite_defaultdict()
+ hass.data[DATA_MQTT] = mock
+
+ yield {
+ "subscribe": mock_subscribe,
+ "unsubscribe": mock_unsubscribe,
+ "wait_for_mqtt_client": mock_wait_for_mqtt_client,
+ "setup": mock_setup,
+ "setup_entry": mock_setup_entry,
+ "unload_entry": mock_unload_entry,
+ }
+
+ hass.data.pop(DATA_MQTT)
+
+
+@pytest.fixture(name="mqtt_subscribe")
+def patch_mqtt_async_subscribe( # noqa: FURB118
+ mqtt: dict[str, Mock | AsyncMock],
+) -> Generator[dict[str, Mock | AsyncMock]]:
+ return mqtt["subscribe"]
+
+
@pytest.fixture
def snapshot(snapshot: SnapshotAssertion) -> SnapshotAssertion:
"""Return snapshot assertion fixture with the Home Assistant extension."""
@@ -84,11 +148,29 @@ def mock_config_entry() -> MockConfigEntry:
)
+@pytest.fixture(name="integration_config")
+def mock_integration_config() -> IntegrationConfig:
+ """Return the default mocked integration config."""
+ return IntegrationConfig(Integration.ZHA)
+
+
@pytest.fixture(name="switch")
-def mock_switch(hass: HomeAssistant) -> er.RegistryEntry:
+def mock_switch(
+ hass: HomeAssistant,
+ integration_config: IntegrationConfig,
+) -> er.RegistryEntry:
"""Return the default mocked config entry."""
+
+ switch_attrs = {"manufacturer": "Inovelli"}
+
+ if integration_config.model:
+ switch_attrs["model"] = integration_config.model
+
return add_mock_switch(
- hass, "light.kitchen", {"manufacturer": "Inovelli", "model": "VZM31-SN"}
+ hass,
+ "light.kitchen",
+ switch_attrs,
+ integration=integration_config.integration,
)
diff --git a/tests/snapshots/test_diagnostics.ambr b/tests/snapshots/test_diagnostics.ambr
index ca6df0c..4cc34e4 100644
--- a/tests/snapshots/test_diagnostics.ambr
+++ b/tests/snapshots/test_diagnostics.ambr
@@ -86,11 +86,15 @@
}),
'switches': dict({
'light.kitchen': dict({
- 'disable_clear_notification_id': 'switch.kitchen_disable_config_2x_tap_to_clear_notifications',
'expiration': dict({
'cancel_listener': None,
'started_at': None,
}),
+ 'integration': 'zha',
+ 'integration_info': dict({
+ 'disable_clear_notification_id': 'switch.kitchen_disable_config_2x_tap_to_clear_notifications',
+ 'local_protection_id': 'switch.kitchen_local_protection',
+ }),
'led_config': list([
dict({
'brightness': 100.0,
@@ -106,7 +110,6 @@
'type': 'notification',
'value': 'doors_open',
}),
- 'local_protection_id': 'switch.kitchen_local_protection',
'priorities': list([
'doors_open',
]),
diff --git a/tests/snapshots/test_scenarios.ambr b/tests/snapshots/test_scenarios.ambr
index eaa255d..5e26c0c 100644
--- a/tests/snapshots/test_scenarios.ambr
+++ b/tests/snapshots/test_scenarios.ambr
@@ -136,12 +136,16 @@
# ---
# name: test_dismissal_from_switch[aux_double_press_dismiss_higher_priority_updates_to_display_original][switch_info]
dict({
- 'disable_clear_notification_id': 'switch.kitchen_disable_config_2x_tap_to_clear_notifications',
'expiration': ExpirationInfoSnapshot({
'cancel_listener': None,
'expires_at': None,
'started_at': None,
}),
+ 'integration': ,
+ 'integration_info': dict({
+ 'disable_clear_notification_id': 'switch.kitchen_disable_config_2x_tap_to_clear_notifications',
+ 'local_protection_id': 'switch.kitchen_local_protection',
+ }),
'led_config': tuple(
dict({
'brightness': 100.0,
@@ -154,7 +158,6 @@
'type': ,
'value': 'doors_open',
}),
- 'local_protection_id': 'switch.kitchen_local_protection',
'priorities': tuple(
'medicine',
'doors_open',
@@ -490,12 +493,16 @@
# ---
# name: test_dismissal_from_switch[config_double_press_dismiss_higher_priority_updates_to_display_original][switch_info]
dict({
- 'disable_clear_notification_id': 'switch.kitchen_disable_config_2x_tap_to_clear_notifications',
'expiration': ExpirationInfoSnapshot({
'cancel_listener': None,
'expires_at': None,
'started_at': None,
}),
+ 'integration': ,
+ 'integration_info': dict({
+ 'disable_clear_notification_id': 'switch.kitchen_disable_config_2x_tap_to_clear_notifications',
+ 'local_protection_id': 'switch.kitchen_local_protection',
+ }),
'led_config': tuple(
dict({
'brightness': 100.0,
@@ -508,7 +515,6 @@
'type': ,
'value': 'doors_open',
}),
- 'local_protection_id': 'switch.kitchen_local_protection',
'priorities': tuple(
'medicine',
'doors_open',
@@ -741,7 +747,7 @@
list([
])
# ---
-# name: test_enable_notification_with_switch_override[doors_open_on,kitchen_override_leds_5m1s_duration_expired][events]
+# name: test_enable_notification_with_switch_override[integration_config0-doors_open_on,kitchen_override_leds_5m1s_duration_expired][events]
list([
EventSnapshot({
'context': ,
@@ -756,7 +762,7 @@
}),
])
# ---
-# name: test_enable_notification_with_switch_override[doors_open_on,kitchen_override_leds_5m1s_duration_expired][notification_info]
+# name: test_enable_notification_with_switch_override[integration_config0-doors_open_on,kitchen_override_leds_5m1s_duration_expired][notification_info]
dict({
'expiration': ExpirationInfoSnapshot({
'cancel_listener': None,
@@ -767,7 +773,7 @@
'notification_on': True,
})
# ---
-# name: test_enable_notification_with_switch_override[doors_open_on,kitchen_override_leds_5m1s_duration_expired][sensor.kitchen_effect_brightness]
+# name: test_enable_notification_with_switch_override[integration_config0-doors_open_on,kitchen_override_leds_5m1s_duration_expired][sensor.kitchen_effect_brightness]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Kitchen Effect brightness',
@@ -783,7 +789,7 @@
'state': '100.0',
})
# ---
-# name: test_enable_notification_with_switch_override[doors_open_on,kitchen_override_leds_5m1s_duration_expired][sensor.kitchen_effect_color]
+# name: test_enable_notification_with_switch_override[integration_config0-doors_open_on,kitchen_override_leds_5m1s_duration_expired][sensor.kitchen_effect_color]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Kitchen Effect color',
@@ -797,7 +803,7 @@
'state': 'red',
})
# ---
-# name: test_enable_notification_with_switch_override[doors_open_on,kitchen_override_leds_5m1s_duration_expired][sensor.kitchen_effect_duration]
+# name: test_enable_notification_with_switch_override[integration_config0-doors_open_on,kitchen_override_leds_5m1s_duration_expired][sensor.kitchen_effect_duration]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Kitchen Effect duration',
@@ -813,7 +819,7 @@
'state': 'unknown',
})
# ---
-# name: test_enable_notification_with_switch_override[doors_open_on,kitchen_override_leds_5m1s_duration_expired][sensor.kitchen_effect_type]
+# name: test_enable_notification_with_switch_override[integration_config0-doors_open_on,kitchen_override_leds_5m1s_duration_expired][sensor.kitchen_effect_type]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Kitchen Effect type',
@@ -827,7 +833,7 @@
'state': 'open_close',
})
# ---
-# name: test_enable_notification_with_switch_override[doors_open_on,kitchen_override_leds_5m1s_duration_expired][sensor.kitchen_notification]
+# name: test_enable_notification_with_switch_override[integration_config0-doors_open_on,kitchen_override_leds_5m1s_duration_expired][sensor.kitchen_notification]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Kitchen Notification',
@@ -841,11 +847,11 @@
'state': 'doors_open',
})
# ---
-# name: test_enable_notification_with_switch_override[doors_open_on,kitchen_override_leds_5m1s_duration_expired][service_calls]
+# name: test_enable_notification_with_switch_override[integration_config0-doors_open_on,kitchen_override_leds_5m1s_duration_expired][service_calls]
list([
])
# ---
-# name: test_enable_notification_with_switch_override[doors_open_on,kitchen_override_leds_5m1s_duration_expired][switch.doors_open_notification]
+# name: test_enable_notification_with_switch_override[integration_config0-doors_open_on,kitchen_override_leds_5m1s_duration_expired][switch.doors_open_notification]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Doors Open Notification',
@@ -859,14 +865,18 @@
'state': 'on',
})
# ---
-# name: test_enable_notification_with_switch_override[doors_open_on,kitchen_override_leds_5m1s_duration_expired][switch_info]
+# name: test_enable_notification_with_switch_override[integration_config0-doors_open_on,kitchen_override_leds_5m1s_duration_expired][switch_info]
dict({
- 'disable_clear_notification_id': 'switch.kitchen_disable_config_2x_tap_to_clear_notifications',
'expiration': ExpirationInfoSnapshot({
'cancel_listener': None,
'expires_at': None,
'started_at': None,
}),
+ 'integration': ,
+ 'integration_info': dict({
+ 'disable_clear_notification_id': 'switch.kitchen_disable_config_2x_tap_to_clear_notifications',
+ 'local_protection_id': 'switch.kitchen_local_protection',
+ }),
'led_config': tuple(
dict({
'brightness': 100.0,
@@ -879,13 +889,12 @@
'type': ,
'value': 'doors_open',
}),
- 'local_protection_id': 'switch.kitchen_local_protection',
'priorities': tuple(
'doors_open',
),
})
# ---
-# name: test_enable_notification_with_switch_override[doors_open_on,kitchen_override_leds_5m1s_duration_expired][zha_cluster_commands]
+# name: test_enable_notification_with_switch_override[integration_config0-doors_open_on,kitchen_override_leds_5m1s_duration_expired][zha_cluster_commands]
list([
ServiceCallSnapshot({
'context': ,
@@ -955,7 +964,7 @@
}),
])
# ---
-# name: test_enable_notification_with_switch_override[doors_open_on,kitchen_override_leds_dismissed][events]
+# name: test_enable_notification_with_switch_override[integration_config0-doors_open_on,kitchen_override_leds_dismissed][events]
list([
EventSnapshot({
'context': ,
@@ -970,7 +979,7 @@
}),
])
# ---
-# name: test_enable_notification_with_switch_override[doors_open_on,kitchen_override_leds_dismissed][notification_info]
+# name: test_enable_notification_with_switch_override[integration_config0-doors_open_on,kitchen_override_leds_dismissed][notification_info]
dict({
'expiration': ExpirationInfoSnapshot({
'cancel_listener': None,
@@ -981,7 +990,7 @@
'notification_on': True,
})
# ---
-# name: test_enable_notification_with_switch_override[doors_open_on,kitchen_override_leds_dismissed][sensor.kitchen_effect_brightness]
+# name: test_enable_notification_with_switch_override[integration_config0-doors_open_on,kitchen_override_leds_dismissed][sensor.kitchen_effect_brightness]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Kitchen Effect brightness',
@@ -997,7 +1006,7 @@
'state': '100.0',
})
# ---
-# name: test_enable_notification_with_switch_override[doors_open_on,kitchen_override_leds_dismissed][sensor.kitchen_effect_color]
+# name: test_enable_notification_with_switch_override[integration_config0-doors_open_on,kitchen_override_leds_dismissed][sensor.kitchen_effect_color]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Kitchen Effect color',
@@ -1011,7 +1020,7 @@
'state': 'red',
})
# ---
-# name: test_enable_notification_with_switch_override[doors_open_on,kitchen_override_leds_dismissed][sensor.kitchen_effect_duration]
+# name: test_enable_notification_with_switch_override[integration_config0-doors_open_on,kitchen_override_leds_dismissed][sensor.kitchen_effect_duration]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Kitchen Effect duration',
@@ -1027,7 +1036,7 @@
'state': 'unknown',
})
# ---
-# name: test_enable_notification_with_switch_override[doors_open_on,kitchen_override_leds_dismissed][sensor.kitchen_effect_type]
+# name: test_enable_notification_with_switch_override[integration_config0-doors_open_on,kitchen_override_leds_dismissed][sensor.kitchen_effect_type]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Kitchen Effect type',
@@ -1041,7 +1050,7 @@
'state': 'open_close',
})
# ---
-# name: test_enable_notification_with_switch_override[doors_open_on,kitchen_override_leds_dismissed][sensor.kitchen_notification]
+# name: test_enable_notification_with_switch_override[integration_config0-doors_open_on,kitchen_override_leds_dismissed][sensor.kitchen_notification]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Kitchen Notification',
@@ -1055,11 +1064,11 @@
'state': 'doors_open',
})
# ---
-# name: test_enable_notification_with_switch_override[doors_open_on,kitchen_override_leds_dismissed][service_calls]
+# name: test_enable_notification_with_switch_override[integration_config0-doors_open_on,kitchen_override_leds_dismissed][service_calls]
list([
])
# ---
-# name: test_enable_notification_with_switch_override[doors_open_on,kitchen_override_leds_dismissed][switch.doors_open_notification]
+# name: test_enable_notification_with_switch_override[integration_config0-doors_open_on,kitchen_override_leds_dismissed][switch.doors_open_notification]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Doors Open Notification',
@@ -1073,14 +1082,18 @@
'state': 'on',
})
# ---
-# name: test_enable_notification_with_switch_override[doors_open_on,kitchen_override_leds_dismissed][switch_info]
+# name: test_enable_notification_with_switch_override[integration_config0-doors_open_on,kitchen_override_leds_dismissed][switch_info]
dict({
- 'disable_clear_notification_id': 'switch.kitchen_disable_config_2x_tap_to_clear_notifications',
'expiration': ExpirationInfoSnapshot({
'cancel_listener': None,
'expires_at': None,
'started_at': None,
}),
+ 'integration': ,
+ 'integration_info': dict({
+ 'disable_clear_notification_id': 'switch.kitchen_disable_config_2x_tap_to_clear_notifications',
+ 'local_protection_id': 'switch.kitchen_local_protection',
+ }),
'led_config': tuple(
dict({
'brightness': 100.0,
@@ -1093,13 +1106,12 @@
'type': ,
'value': 'doors_open',
}),
- 'local_protection_id': 'switch.kitchen_local_protection',
'priorities': tuple(
'doors_open',
),
})
# ---
-# name: test_enable_notification_with_switch_override[doors_open_on,kitchen_override_leds_dismissed][zha_cluster_commands]
+# name: test_enable_notification_with_switch_override[integration_config0-doors_open_on,kitchen_override_leds_dismissed][zha_cluster_commands]
list([
ServiceCallSnapshot({
'context': ,
@@ -1169,7 +1181,7 @@
}),
])
# ---
-# name: test_enable_notification_with_switch_override[doors_open_on,kitchen_override_leds_reset][notification_info]
+# name: test_enable_notification_with_switch_override[integration_config0-doors_open_on,kitchen_override_leds_reset][notification_info]
dict({
'expiration': ExpirationInfoSnapshot({
'cancel_listener': None,
@@ -1180,7 +1192,7 @@
'notification_on': True,
})
# ---
-# name: test_enable_notification_with_switch_override[doors_open_on,kitchen_override_leds_reset][sensor.kitchen_effect_brightness]
+# name: test_enable_notification_with_switch_override[integration_config0-doors_open_on,kitchen_override_leds_reset][sensor.kitchen_effect_brightness]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Kitchen Effect brightness',
@@ -1196,7 +1208,7 @@
'state': '100.0',
})
# ---
-# name: test_enable_notification_with_switch_override[doors_open_on,kitchen_override_leds_reset][sensor.kitchen_effect_color]
+# name: test_enable_notification_with_switch_override[integration_config0-doors_open_on,kitchen_override_leds_reset][sensor.kitchen_effect_color]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Kitchen Effect color',
@@ -1210,7 +1222,7 @@
'state': 'red',
})
# ---
-# name: test_enable_notification_with_switch_override[doors_open_on,kitchen_override_leds_reset][sensor.kitchen_effect_duration]
+# name: test_enable_notification_with_switch_override[integration_config0-doors_open_on,kitchen_override_leds_reset][sensor.kitchen_effect_duration]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Kitchen Effect duration',
@@ -1226,7 +1238,7 @@
'state': 'unknown',
})
# ---
-# name: test_enable_notification_with_switch_override[doors_open_on,kitchen_override_leds_reset][sensor.kitchen_effect_type]
+# name: test_enable_notification_with_switch_override[integration_config0-doors_open_on,kitchen_override_leds_reset][sensor.kitchen_effect_type]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Kitchen Effect type',
@@ -1240,7 +1252,7 @@
'state': 'open_close',
})
# ---
-# name: test_enable_notification_with_switch_override[doors_open_on,kitchen_override_leds_reset][sensor.kitchen_notification]
+# name: test_enable_notification_with_switch_override[integration_config0-doors_open_on,kitchen_override_leds_reset][sensor.kitchen_notification]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Kitchen Notification',
@@ -1254,11 +1266,11 @@
'state': 'doors_open',
})
# ---
-# name: test_enable_notification_with_switch_override[doors_open_on,kitchen_override_leds_reset][service_calls]
+# name: test_enable_notification_with_switch_override[integration_config0-doors_open_on,kitchen_override_leds_reset][service_calls]
list([
])
# ---
-# name: test_enable_notification_with_switch_override[doors_open_on,kitchen_override_leds_reset][switch.doors_open_notification]
+# name: test_enable_notification_with_switch_override[integration_config0-doors_open_on,kitchen_override_leds_reset][switch.doors_open_notification]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Doors Open Notification',
@@ -1272,14 +1284,18 @@
'state': 'on',
})
# ---
-# name: test_enable_notification_with_switch_override[doors_open_on,kitchen_override_leds_reset][switch_info]
+# name: test_enable_notification_with_switch_override[integration_config0-doors_open_on,kitchen_override_leds_reset][switch_info]
dict({
- 'disable_clear_notification_id': 'switch.kitchen_disable_config_2x_tap_to_clear_notifications',
'expiration': ExpirationInfoSnapshot({
'cancel_listener': None,
'expires_at': None,
'started_at': HAFakeDatetime(2025, 5, 20, 3, 51, 32, 3245, tzinfo=zoneinfo.ZoneInfo(key='US/Pacific')),
}),
+ 'integration': ,
+ 'integration_info': dict({
+ 'disable_clear_notification_id': 'switch.kitchen_disable_config_2x_tap_to_clear_notifications',
+ 'local_protection_id': 'switch.kitchen_local_protection',
+ }),
'led_config': tuple(
dict({
'brightness': 100.0,
@@ -1292,13 +1308,12 @@
'type': ,
'value': 'doors_open',
}),
- 'local_protection_id': 'switch.kitchen_local_protection',
'priorities': tuple(
'doors_open',
),
})
# ---
-# name: test_enable_notification_with_switch_override[doors_open_on,kitchen_override_leds_reset][zha_cluster_commands]
+# name: test_enable_notification_with_switch_override[integration_config0-doors_open_on,kitchen_override_leds_reset][zha_cluster_commands]
list([
ServiceCallSnapshot({
'context': ,
@@ -1368,7 +1383,7 @@
}),
])
# ---
-# name: test_enable_notification_with_switch_override[kitchen_override,doors_open_on][notification_info]
+# name: test_enable_notification_with_switch_override[integration_config0-kitchen_override,doors_open_on][notification_info]
dict({
'expiration': ExpirationInfoSnapshot({
'cancel_listener': None,
@@ -1379,7 +1394,7 @@
'notification_on': True,
})
# ---
-# name: test_enable_notification_with_switch_override[kitchen_override,doors_open_on][sensor.kitchen_effect_brightness]
+# name: test_enable_notification_with_switch_override[integration_config0-kitchen_override,doors_open_on][sensor.kitchen_effect_brightness]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Kitchen Effect brightness',
@@ -1395,7 +1410,7 @@
'state': '100.0',
})
# ---
-# name: test_enable_notification_with_switch_override[kitchen_override,doors_open_on][sensor.kitchen_effect_color]
+# name: test_enable_notification_with_switch_override[integration_config0-kitchen_override,doors_open_on][sensor.kitchen_effect_color]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Kitchen Effect color',
@@ -1409,7 +1424,7 @@
'state': 'green',
})
# ---
-# name: test_enable_notification_with_switch_override[kitchen_override,doors_open_on][sensor.kitchen_effect_duration]
+# name: test_enable_notification_with_switch_override[integration_config0-kitchen_override,doors_open_on][sensor.kitchen_effect_duration]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Kitchen Effect duration',
@@ -1425,7 +1440,7 @@
'state': 'unknown',
})
# ---
-# name: test_enable_notification_with_switch_override[kitchen_override,doors_open_on][sensor.kitchen_effect_type]
+# name: test_enable_notification_with_switch_override[integration_config0-kitchen_override,doors_open_on][sensor.kitchen_effect_type]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Kitchen Effect type',
@@ -1439,7 +1454,7 @@
'state': 'solid',
})
# ---
-# name: test_enable_notification_with_switch_override[kitchen_override,doors_open_on][sensor.kitchen_notification]
+# name: test_enable_notification_with_switch_override[integration_config0-kitchen_override,doors_open_on][sensor.kitchen_notification]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Kitchen Notification',
@@ -1453,11 +1468,11 @@
'state': 'lampie.override',
})
# ---
-# name: test_enable_notification_with_switch_override[kitchen_override,doors_open_on][service_calls]
+# name: test_enable_notification_with_switch_override[integration_config0-kitchen_override,doors_open_on][service_calls]
list([
])
# ---
-# name: test_enable_notification_with_switch_override[kitchen_override,doors_open_on][switch.doors_open_notification]
+# name: test_enable_notification_with_switch_override[integration_config0-kitchen_override,doors_open_on][switch.doors_open_notification]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Doors Open Notification',
@@ -1471,14 +1486,18 @@
'state': 'on',
})
# ---
-# name: test_enable_notification_with_switch_override[kitchen_override,doors_open_on][switch_info]
+# name: test_enable_notification_with_switch_override[integration_config0-kitchen_override,doors_open_on][switch_info]
dict({
- 'disable_clear_notification_id': 'switch.kitchen_disable_config_2x_tap_to_clear_notifications',
'expiration': ExpirationInfoSnapshot({
'cancel_listener': None,
'expires_at': None,
'started_at': HAFakeDatetime(2025, 5, 20, 3, 51, 32, 3245, tzinfo=zoneinfo.ZoneInfo(key='US/Pacific')),
}),
+ 'integration': ,
+ 'integration_info': dict({
+ 'disable_clear_notification_id': 'switch.kitchen_disable_config_2x_tap_to_clear_notifications',
+ 'local_protection_id': 'switch.kitchen_local_protection',
+ }),
'led_config': tuple(
dict({
'brightness': 100.0,
@@ -1491,13 +1510,12 @@
'type': ,
'value': 'lampie.override',
}),
- 'local_protection_id': 'switch.kitchen_local_protection',
'priorities': tuple(
'doors_open',
),
})
# ---
-# name: test_enable_notification_with_switch_override[kitchen_override,doors_open_on][zha_cluster_commands]
+# name: test_enable_notification_with_switch_override[integration_config0-kitchen_override,doors_open_on][zha_cluster_commands]
list([
ServiceCallSnapshot({
'context': ,
@@ -1523,12 +1541,13 @@
}),
])
# ---
-# name: test_single_config_entry[doors_open_10s_duration_expired][events]
+# name: test_enable_notification_with_switch_override[integration_config1-doors_open_on,kitchen_override_leds_5m1s_duration_expired][events]
list([
EventSnapshot({
'context': ,
'data': ReadOnlyDict({
- 'notification': 'doors_open',
+ 'entity_id': 'light.kitchen',
+ 'override': 'lampie.override',
}),
'event_type': 'lampie.expired',
'id': ,
@@ -1537,18 +1556,18 @@
}),
])
# ---
-# name: test_single_config_entry[doors_open_10s_duration_expired][notification_info]
+# name: test_enable_notification_with_switch_override[integration_config1-doors_open_on,kitchen_override_leds_5m1s_duration_expired][notification_info]
dict({
'expiration': ExpirationInfoSnapshot({
'cancel_listener': None,
'expires_at': None,
- 'started_at': None,
+ 'started_at': HAFakeDatetime(2025, 5, 20, 3, 51, 32, 3245, tzinfo=zoneinfo.ZoneInfo(key='US/Pacific')),
}),
'led_config_override': None,
- 'notification_on': False,
+ 'notification_on': True,
})
# ---
-# name: test_single_config_entry[doors_open_10s_duration_expired][sensor.kitchen_effect_brightness]
+# name: test_enable_notification_with_switch_override[integration_config1-doors_open_on,kitchen_override_leds_5m1s_duration_expired][sensor.kitchen_effect_brightness]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Kitchen Effect brightness',
@@ -1561,10 +1580,10 @@
'last_changed': ,
'last_reported': ,
'last_updated': ,
- 'state': 'unknown',
+ 'state': '100.0',
})
# ---
-# name: test_single_config_entry[doors_open_10s_duration_expired][sensor.kitchen_effect_color]
+# name: test_enable_notification_with_switch_override[integration_config1-doors_open_on,kitchen_override_leds_5m1s_duration_expired][sensor.kitchen_effect_color]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Kitchen Effect color',
@@ -1575,10 +1594,10 @@
'last_changed': ,
'last_reported': ,
'last_updated': ,
- 'state': 'unknown',
+ 'state': 'red',
})
# ---
-# name: test_single_config_entry[doors_open_10s_duration_expired][sensor.kitchen_effect_duration]
+# name: test_enable_notification_with_switch_override[integration_config1-doors_open_on,kitchen_override_leds_5m1s_duration_expired][sensor.kitchen_effect_duration]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Kitchen Effect duration',
@@ -1594,7 +1613,7 @@
'state': 'unknown',
})
# ---
-# name: test_single_config_entry[doors_open_10s_duration_expired][sensor.kitchen_effect_type]
+# name: test_enable_notification_with_switch_override[integration_config1-doors_open_on,kitchen_override_leds_5m1s_duration_expired][sensor.kitchen_effect_type]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Kitchen Effect type',
@@ -1605,10 +1624,10 @@
'last_changed': ,
'last_reported': ,
'last_updated': ,
- 'state': 'unknown',
+ 'state': 'open_close',
})
# ---
-# name: test_single_config_entry[doors_open_10s_duration_expired][sensor.kitchen_notification]
+# name: test_enable_notification_with_switch_override[integration_config1-doors_open_on,kitchen_override_leds_5m1s_duration_expired][sensor.kitchen_notification]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Kitchen Notification',
@@ -1619,14 +1638,14 @@
'last_changed': ,
'last_reported': ,
'last_updated': ,
- 'state': 'unknown',
+ 'state': 'doors_open',
})
# ---
-# name: test_single_config_entry[doors_open_10s_duration_expired][service_calls]
+# name: test_enable_notification_with_switch_override[integration_config1-doors_open_on,kitchen_override_leds_5m1s_duration_expired][service_calls]
list([
])
# ---
-# name: test_single_config_entry[doors_open_10s_duration_expired][switch.doors_open_notification]
+# name: test_enable_notification_with_switch_override[integration_config1-doors_open_on,kitchen_override_leds_5m1s_duration_expired][switch.doors_open_notification]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Doors Open Notification',
@@ -1637,61 +1656,94 @@
'last_changed': ,
'last_reported': ,
'last_updated': ,
- 'state': 'off',
+ 'state': 'on',
})
# ---
-# name: test_single_config_entry[doors_open_10s_duration_expired][switch_info]
+# name: test_enable_notification_with_switch_override[integration_config1-doors_open_on,kitchen_override_leds_5m1s_duration_expired][switch_info]
dict({
- 'disable_clear_notification_id': 'switch.kitchen_disable_config_2x_tap_to_clear_notifications',
'expiration': ExpirationInfoSnapshot({
'cancel_listener': None,
'expires_at': None,
'started_at': None,
}),
+ 'integration': ,
+ 'integration_info': dict({
+ 'double_tap_clear_notifications_disabled': None,
+ 'full_topic': 'home/z2m/Kitchen',
+ 'local_protection_enabled': None,
+ }),
'led_config': tuple(
+ dict({
+ 'brightness': 100.0,
+ 'color': ,
+ 'duration': None,
+ 'effect': ,
+ }),
),
'led_config_source': dict({
'type': ,
- 'value': None,
+ 'value': 'doors_open',
}),
- 'local_protection_id': 'switch.kitchen_local_protection',
'priorities': tuple(
'doors_open',
),
})
# ---
-# name: test_single_config_entry[doors_open_10s_duration_expired][zha_cluster_commands]
+# name: test_enable_notification_with_switch_override[integration_config1-doors_open_on,kitchen_override_leds_5m1s_duration_expired][z2m_cluster_commands]
list([
ServiceCallSnapshot({
'context': ,
'data': ReadOnlyDict({
- 'cluster_id': 64561,
- 'cluster_type': 'in',
- 'command': 1,
- 'command_type': 'server',
- 'endpoint_id': 1,
- 'ieee': 'mock-ieee:kitchen',
- 'manufacturer': 4655,
- 'params': dict({
- 'led_color': 0,
- 'led_duration': 10,
- 'led_effect': 6,
- 'led_level': 100,
- }),
+ 'payload': '{"localProtection":"","doubleTapClearNotifications":""}',
+ 'topic': 'home/z2m/Kitchen/get',
}),
- 'domain': 'zha',
+ 'domain': 'mqtt',
'hass': ,
'return_response': False,
- 'service': 'issue_zigbee_cluster_command',
+ 'service': 'publish',
+ }),
+ ServiceCallSnapshot({
+ 'context': ,
+ 'data': ReadOnlyDict({
+ 'payload': '{"led_effect":{"led_color":0,"led_effect":6,"led_level":100,"led_duration":255}}',
+ 'topic': 'home/z2m/Kitchen/set',
+ }),
+ 'domain': 'mqtt',
+ 'hass': ,
+ 'return_response': False,
+ 'service': 'publish',
+ }),
+ ServiceCallSnapshot({
+ 'context': ,
+ 'data': ReadOnlyDict({
+ 'payload': '{"led_effect":{"led_color":90,"led_effect":1,"led_level":100,"led_duration":255}}',
+ 'topic': 'home/z2m/Kitchen/set',
+ }),
+ 'domain': 'mqtt',
+ 'hass': ,
+ 'return_response': False,
+ 'service': 'publish',
+ }),
+ ServiceCallSnapshot({
+ 'context': ,
+ 'data': ReadOnlyDict({
+ 'payload': '{"led_effect":{"led_color":0,"led_effect":6,"led_level":100,"led_duration":255}}',
+ 'topic': 'home/z2m/Kitchen/set',
+ }),
+ 'domain': 'mqtt',
+ 'hass': ,
+ 'return_response': False,
+ 'service': 'publish',
}),
])
# ---
-# name: test_single_config_entry[doors_open_5m1s_dismissed][events]
+# name: test_enable_notification_with_switch_override[integration_config1-doors_open_on,kitchen_override_leds_dismissed][events]
list([
EventSnapshot({
'context': ,
'data': ReadOnlyDict({
- 'notification': 'doors_open',
+ 'entity_id': 'light.kitchen',
+ 'override': 'lampie.override',
}),
'event_type': 'lampie.dismissed',
'id': ,
@@ -1700,18 +1752,18 @@
}),
])
# ---
-# name: test_single_config_entry[doors_open_5m1s_dismissed][notification_info]
+# name: test_enable_notification_with_switch_override[integration_config1-doors_open_on,kitchen_override_leds_dismissed][notification_info]
dict({
'expiration': ExpirationInfoSnapshot({
'cancel_listener': None,
'expires_at': None,
- 'started_at': None,
+ 'started_at': HAFakeDatetime(2025, 5, 20, 3, 51, 32, 3245, tzinfo=zoneinfo.ZoneInfo(key='US/Pacific')),
}),
'led_config_override': None,
- 'notification_on': False,
+ 'notification_on': True,
})
# ---
-# name: test_single_config_entry[doors_open_5m1s_dismissed][sensor.kitchen_effect_brightness]
+# name: test_enable_notification_with_switch_override[integration_config1-doors_open_on,kitchen_override_leds_dismissed][sensor.kitchen_effect_brightness]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Kitchen Effect brightness',
@@ -1724,10 +1776,10 @@
'last_changed': ,
'last_reported': ,
'last_updated': ,
- 'state': 'unknown',
+ 'state': '100.0',
})
# ---
-# name: test_single_config_entry[doors_open_5m1s_dismissed][sensor.kitchen_effect_color]
+# name: test_enable_notification_with_switch_override[integration_config1-doors_open_on,kitchen_override_leds_dismissed][sensor.kitchen_effect_color]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Kitchen Effect color',
@@ -1738,10 +1790,10 @@
'last_changed': ,
'last_reported': ,
'last_updated': ,
- 'state': 'unknown',
+ 'state': 'red',
})
# ---
-# name: test_single_config_entry[doors_open_5m1s_dismissed][sensor.kitchen_effect_duration]
+# name: test_enable_notification_with_switch_override[integration_config1-doors_open_on,kitchen_override_leds_dismissed][sensor.kitchen_effect_duration]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Kitchen Effect duration',
@@ -1757,7 +1809,7 @@
'state': 'unknown',
})
# ---
-# name: test_single_config_entry[doors_open_5m1s_dismissed][sensor.kitchen_effect_type]
+# name: test_enable_notification_with_switch_override[integration_config1-doors_open_on,kitchen_override_leds_dismissed][sensor.kitchen_effect_type]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Kitchen Effect type',
@@ -1768,10 +1820,10 @@
'last_changed': ,
'last_reported': ,
'last_updated': ,
- 'state': 'unknown',
+ 'state': 'open_close',
})
# ---
-# name: test_single_config_entry[doors_open_5m1s_dismissed][sensor.kitchen_notification]
+# name: test_enable_notification_with_switch_override[integration_config1-doors_open_on,kitchen_override_leds_dismissed][sensor.kitchen_notification]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Kitchen Notification',
@@ -1782,14 +1834,14 @@
'last_changed': ,
'last_reported': ,
'last_updated': ,
- 'state': 'unknown',
+ 'state': 'doors_open',
})
# ---
-# name: test_single_config_entry[doors_open_5m1s_dismissed][service_calls]
+# name: test_enable_notification_with_switch_override[integration_config1-doors_open_on,kitchen_override_leds_dismissed][service_calls]
list([
])
# ---
-# name: test_single_config_entry[doors_open_5m1s_dismissed][switch.doors_open_notification]
+# name: test_enable_notification_with_switch_override[integration_config1-doors_open_on,kitchen_override_leds_dismissed][switch.doors_open_notification]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Doors Open Notification',
@@ -1800,81 +1852,99 @@
'last_changed': ,
'last_reported': ,
'last_updated': ,
- 'state': 'off',
+ 'state': 'on',
})
# ---
-# name: test_single_config_entry[doors_open_5m1s_dismissed][switch_info]
+# name: test_enable_notification_with_switch_override[integration_config1-doors_open_on,kitchen_override_leds_dismissed][switch_info]
dict({
- 'disable_clear_notification_id': 'switch.kitchen_disable_config_2x_tap_to_clear_notifications',
'expiration': ExpirationInfoSnapshot({
'cancel_listener': None,
'expires_at': None,
'started_at': None,
}),
+ 'integration': ,
+ 'integration_info': dict({
+ 'double_tap_clear_notifications_disabled': None,
+ 'full_topic': 'home/z2m/Kitchen',
+ 'local_protection_enabled': None,
+ }),
'led_config': tuple(
+ dict({
+ 'brightness': 100.0,
+ 'color': ,
+ 'duration': None,
+ 'effect': ,
+ }),
),
'led_config_source': dict({
'type': ,
- 'value': None,
+ 'value': 'doors_open',
}),
- 'local_protection_id': 'switch.kitchen_local_protection',
'priorities': tuple(
'doors_open',
),
})
# ---
-# name: test_single_config_entry[doors_open_5m1s_dismissed][zha_cluster_commands]
+# name: test_enable_notification_with_switch_override[integration_config1-doors_open_on,kitchen_override_leds_dismissed][z2m_cluster_commands]
list([
ServiceCallSnapshot({
'context': ,
'data': ReadOnlyDict({
- 'cluster_id': 64561,
- 'cluster_type': 'in',
- 'command': 1,
- 'command_type': 'server',
- 'endpoint_id': 1,
- 'ieee': 'mock-ieee:kitchen',
- 'manufacturer': 4655,
- 'params': dict({
- 'led_color': 0,
- 'led_duration': 255,
- 'led_effect': 6,
- 'led_level': 100,
- }),
+ 'payload': '{"localProtection":"","doubleTapClearNotifications":""}',
+ 'topic': 'home/z2m/Kitchen/get',
}),
- 'domain': 'zha',
+ 'domain': 'mqtt',
'hass': ,
'return_response': False,
- 'service': 'issue_zigbee_cluster_command',
+ 'service': 'publish',
}),
- ])
-# ---
-# name: test_single_config_entry[doors_open_5m1s_duration_expired][events]
- list([
- EventSnapshot({
+ ServiceCallSnapshot({
'context': ,
'data': ReadOnlyDict({
- 'notification': 'doors_open',
+ 'payload': '{"led_effect":{"led_color":0,"led_effect":6,"led_level":100,"led_duration":255}}',
+ 'topic': 'home/z2m/Kitchen/set',
}),
- 'event_type': 'lampie.expired',
- 'id': ,
- 'origin': 'LOCAL',
- 'time_fired': ,
+ 'domain': 'mqtt',
+ 'hass': ,
+ 'return_response': False,
+ 'service': 'publish',
+ }),
+ ServiceCallSnapshot({
+ 'context': ,
+ 'data': ReadOnlyDict({
+ 'payload': '{"led_effect":{"led_color":90,"led_effect":1,"led_level":100,"led_duration":255}}',
+ 'topic': 'home/z2m/Kitchen/set',
+ }),
+ 'domain': 'mqtt',
+ 'hass': ,
+ 'return_response': False,
+ 'service': 'publish',
+ }),
+ ServiceCallSnapshot({
+ 'context': ,
+ 'data': ReadOnlyDict({
+ 'payload': '{"led_effect":{"led_color":0,"led_effect":6,"led_level":100,"led_duration":255}}',
+ 'topic': 'home/z2m/Kitchen/set',
+ }),
+ 'domain': 'mqtt',
+ 'hass': ,
+ 'return_response': False,
+ 'service': 'publish',
}),
])
# ---
-# name: test_single_config_entry[doors_open_5m1s_duration_expired][notification_info]
+# name: test_enable_notification_with_switch_override[integration_config1-doors_open_on,kitchen_override_leds_reset][notification_info]
dict({
'expiration': ExpirationInfoSnapshot({
'cancel_listener': None,
'expires_at': None,
- 'started_at': None,
+ 'started_at': HAFakeDatetime(2025, 5, 20, 3, 51, 32, 3245, tzinfo=zoneinfo.ZoneInfo(key='US/Pacific')),
}),
'led_config_override': None,
- 'notification_on': False,
+ 'notification_on': True,
})
# ---
-# name: test_single_config_entry[doors_open_5m1s_duration_expired][sensor.kitchen_effect_brightness]
+# name: test_enable_notification_with_switch_override[integration_config1-doors_open_on,kitchen_override_leds_reset][sensor.kitchen_effect_brightness]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Kitchen Effect brightness',
@@ -1887,10 +1957,10 @@
'last_changed': ,
'last_reported': ,
'last_updated': ,
- 'state': 'unknown',
+ 'state': '100.0',
})
# ---
-# name: test_single_config_entry[doors_open_5m1s_duration_expired][sensor.kitchen_effect_color]
+# name: test_enable_notification_with_switch_override[integration_config1-doors_open_on,kitchen_override_leds_reset][sensor.kitchen_effect_color]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Kitchen Effect color',
@@ -1901,10 +1971,10 @@
'last_changed': ,
'last_reported': ,
'last_updated': ,
- 'state': 'unknown',
+ 'state': 'red',
})
# ---
-# name: test_single_config_entry[doors_open_5m1s_duration_expired][sensor.kitchen_effect_duration]
+# name: test_enable_notification_with_switch_override[integration_config1-doors_open_on,kitchen_override_leds_reset][sensor.kitchen_effect_duration]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Kitchen Effect duration',
@@ -1920,7 +1990,7 @@
'state': 'unknown',
})
# ---
-# name: test_single_config_entry[doors_open_5m1s_duration_expired][sensor.kitchen_effect_type]
+# name: test_enable_notification_with_switch_override[integration_config1-doors_open_on,kitchen_override_leds_reset][sensor.kitchen_effect_type]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Kitchen Effect type',
@@ -1931,10 +2001,10 @@
'last_changed': ,
'last_reported': ,
'last_updated': ,
- 'state': 'unknown',
+ 'state': 'open_close',
})
# ---
-# name: test_single_config_entry[doors_open_5m1s_duration_expired][sensor.kitchen_notification]
+# name: test_enable_notification_with_switch_override[integration_config1-doors_open_on,kitchen_override_leds_reset][sensor.kitchen_notification]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Kitchen Notification',
@@ -1945,14 +2015,14 @@
'last_changed': ,
'last_reported': ,
'last_updated': ,
- 'state': 'unknown',
+ 'state': 'doors_open',
})
# ---
-# name: test_single_config_entry[doors_open_5m1s_duration_expired][service_calls]
+# name: test_enable_notification_with_switch_override[integration_config1-doors_open_on,kitchen_override_leds_reset][service_calls]
list([
])
# ---
-# name: test_single_config_entry[doors_open_5m1s_duration_expired][switch.doors_open_notification]
+# name: test_enable_notification_with_switch_override[integration_config1-doors_open_on,kitchen_override_leds_reset][switch.doors_open_notification]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Doors Open Notification',
@@ -1963,103 +2033,99 @@
'last_changed': ,
'last_reported': ,
'last_updated': ,
- 'state': 'off',
+ 'state': 'on',
})
# ---
-# name: test_single_config_entry[doors_open_5m1s_duration_expired][switch_info]
+# name: test_enable_notification_with_switch_override[integration_config1-doors_open_on,kitchen_override_leds_reset][switch_info]
dict({
- 'disable_clear_notification_id': 'switch.kitchen_disable_config_2x_tap_to_clear_notifications',
'expiration': ExpirationInfoSnapshot({
'cancel_listener': None,
'expires_at': None,
- 'started_at': None,
+ 'started_at': HAFakeDatetime(2025, 5, 20, 3, 51, 32, 3245, tzinfo=zoneinfo.ZoneInfo(key='US/Pacific')),
+ }),
+ 'integration': ,
+ 'integration_info': dict({
+ 'double_tap_clear_notifications_disabled': None,
+ 'full_topic': 'home/z2m/Kitchen',
+ 'local_protection_enabled': None,
}),
'led_config': tuple(
+ dict({
+ 'brightness': 100.0,
+ 'color': ,
+ 'duration': None,
+ 'effect': ,
+ }),
),
'led_config_source': dict({
'type': ,
- 'value': None,
+ 'value': 'doors_open',
}),
- 'local_protection_id': 'switch.kitchen_local_protection',
'priorities': tuple(
'doors_open',
),
})
# ---
-# name: test_single_config_entry[doors_open_5m1s_duration_expired][zha_cluster_commands]
+# name: test_enable_notification_with_switch_override[integration_config1-doors_open_on,kitchen_override_leds_reset][z2m_cluster_commands]
list([
ServiceCallSnapshot({
'context': ,
'data': ReadOnlyDict({
- 'cluster_id': 64561,
- 'cluster_type': 'in',
- 'command': 1,
- 'command_type': 'server',
- 'endpoint_id': 1,
- 'ieee': 'mock-ieee:kitchen',
- 'manufacturer': 4655,
- 'params': dict({
- 'led_color': 0,
- 'led_duration': 255,
- 'led_effect': 6,
- 'led_level': 100,
- }),
+ 'payload': '{"localProtection":"","doubleTapClearNotifications":""}',
+ 'topic': 'home/z2m/Kitchen/get',
}),
- 'domain': 'zha',
+ 'domain': 'mqtt',
'hass': ,
'return_response': False,
- 'service': 'issue_zigbee_cluster_command',
+ 'service': 'publish',
}),
ServiceCallSnapshot({
'context': ,
'data': ReadOnlyDict({
- 'cluster_id': 64561,
- 'cluster_type': 'in',
- 'command': 1,
- 'command_type': 'server',
- 'endpoint_id': 1,
- 'ieee': 'mock-ieee:kitchen',
- 'manufacturer': 4655,
- 'params': dict({
- 'led_color': 170,
- 'led_duration': 255,
- 'led_effect': 255,
- 'led_level': 100,
- }),
+ 'payload': '{"led_effect":{"led_color":0,"led_effect":6,"led_level":100,"led_duration":255}}',
+ 'topic': 'home/z2m/Kitchen/set',
}),
- 'domain': 'zha',
+ 'domain': 'mqtt',
'hass': ,
'return_response': False,
- 'service': 'issue_zigbee_cluster_command',
+ 'service': 'publish',
}),
- ])
-# ---
-# name: test_single_config_entry[doors_open_5m1s_duration_expired_not_blocked_via_end_action][events]
- list([
- EventSnapshot({
+ ServiceCallSnapshot({
'context': ,
'data': ReadOnlyDict({
- 'notification': 'doors_open',
+ 'payload': '{"led_effect":{"led_color":90,"led_effect":1,"led_level":100,"led_duration":255}}',
+ 'topic': 'home/z2m/Kitchen/set',
}),
- 'event_type': 'lampie.expired',
- 'id': ,
- 'origin': 'LOCAL',
- 'time_fired': ,
+ 'domain': 'mqtt',
+ 'hass': ,
+ 'return_response': False,
+ 'service': 'publish',
+ }),
+ ServiceCallSnapshot({
+ 'context': ,
+ 'data': ReadOnlyDict({
+ 'payload': '{"led_effect":{"led_color":0,"led_effect":6,"led_level":100,"led_duration":255}}',
+ 'topic': 'home/z2m/Kitchen/set',
+ }),
+ 'domain': 'mqtt',
+ 'hass': ,
+ 'return_response': False,
+ 'service': 'publish',
}),
])
# ---
-# name: test_single_config_entry[doors_open_5m1s_duration_expired_not_blocked_via_end_action][notification_info]
+# name: test_enable_notification_with_switch_override[integration_config1-kitchen_override,doors_open_on][notification_info]
dict({
'expiration': ExpirationInfoSnapshot({
'cancel_listener': None,
'expires_at': None,
- 'started_at': None,
+ 'started_at': HAFakeDatetime(2025, 5, 20, 3, 51, 32, 3245, tzinfo=zoneinfo.ZoneInfo(key='US/Pacific')),
}),
'led_config_override': None,
- 'notification_on': False,
+ 'notification_on': True,
})
# ---
-# name: test_single_config_entry[doors_open_5m1s_duration_expired_not_blocked_via_end_action][sensor.kitchen_effect_brightness]
+# name: test_enable_notification_with_switch_override[integration_config1-kitchen_override,doors_open_on][sensor.kitchen_effect_brightness]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Kitchen Effect brightness',
@@ -2072,10 +2138,10 @@
'last_changed': ,
'last_reported': ,
'last_updated': ,
- 'state': 'unknown',
+ 'state': '100.0',
})
# ---
-# name: test_single_config_entry[doors_open_5m1s_duration_expired_not_blocked_via_end_action][sensor.kitchen_effect_color]
+# name: test_enable_notification_with_switch_override[integration_config1-kitchen_override,doors_open_on][sensor.kitchen_effect_color]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Kitchen Effect color',
@@ -2086,10 +2152,10 @@
'last_changed': ,
'last_reported': ,
'last_updated': ,
- 'state': 'unknown',
+ 'state': 'green',
})
# ---
-# name: test_single_config_entry[doors_open_5m1s_duration_expired_not_blocked_via_end_action][sensor.kitchen_effect_duration]
+# name: test_enable_notification_with_switch_override[integration_config1-kitchen_override,doors_open_on][sensor.kitchen_effect_duration]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Kitchen Effect duration',
@@ -2105,7 +2171,7 @@
'state': 'unknown',
})
# ---
-# name: test_single_config_entry[doors_open_5m1s_duration_expired_not_blocked_via_end_action][sensor.kitchen_effect_type]
+# name: test_enable_notification_with_switch_override[integration_config1-kitchen_override,doors_open_on][sensor.kitchen_effect_type]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Kitchen Effect type',
@@ -2116,10 +2182,10 @@
'last_changed': ,
'last_reported': ,
'last_updated': ,
- 'state': 'unknown',
+ 'state': 'solid',
})
# ---
-# name: test_single_config_entry[doors_open_5m1s_duration_expired_not_blocked_via_end_action][sensor.kitchen_notification]
+# name: test_enable_notification_with_switch_override[integration_config1-kitchen_override,doors_open_on][sensor.kitchen_notification]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Kitchen Notification',
@@ -2130,24 +2196,14 @@
'last_changed': ,
'last_reported': ,
'last_updated': ,
- 'state': 'unknown',
+ 'state': 'lampie.override',
})
# ---
-# name: test_single_config_entry[doors_open_5m1s_duration_expired_not_blocked_via_end_action][service_calls]
+# name: test_enable_notification_with_switch_override[integration_config1-kitchen_override,doors_open_on][service_calls]
list([
- tuple(
- 'script',
- 'block_dismissal',
- dict({
- 'device_id': None,
- 'dismissed': False,
- 'notification': 'doors_open',
- 'switch_id': None,
- }),
- ),
])
# ---
-# name: test_single_config_entry[doors_open_5m1s_duration_expired_not_blocked_via_end_action][switch.doors_open_notification]
+# name: test_enable_notification_with_switch_override[integration_config1-kitchen_override,doors_open_on][switch.doors_open_notification]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Doors Open Notification',
@@ -2158,89 +2214,92 @@
'last_changed': ,
'last_reported': ,
'last_updated': ,
- 'state': 'off',
+ 'state': 'on',
})
# ---
-# name: test_single_config_entry[doors_open_5m1s_duration_expired_not_blocked_via_end_action][switch_info]
+# name: test_enable_notification_with_switch_override[integration_config1-kitchen_override,doors_open_on][switch_info]
dict({
- 'disable_clear_notification_id': 'switch.kitchen_disable_config_2x_tap_to_clear_notifications',
'expiration': ExpirationInfoSnapshot({
'cancel_listener': None,
'expires_at': None,
- 'started_at': None,
+ 'started_at': HAFakeDatetime(2025, 5, 20, 3, 51, 32, 3245, tzinfo=zoneinfo.ZoneInfo(key='US/Pacific')),
+ }),
+ 'integration': ,
+ 'integration_info': dict({
+ 'double_tap_clear_notifications_disabled': None,
+ 'full_topic': 'home/z2m/Kitchen',
+ 'local_protection_enabled': None,
}),
'led_config': tuple(
+ dict({
+ 'brightness': 100.0,
+ 'color': ,
+ 'duration': None,
+ 'effect': ,
+ }),
),
'led_config_source': dict({
- 'type': ,
- 'value': None,
+ 'type': ,
+ 'value': 'lampie.override',
}),
- 'local_protection_id': 'switch.kitchen_local_protection',
'priorities': tuple(
'doors_open',
),
})
# ---
-# name: test_single_config_entry[doors_open_5m1s_duration_expired_not_blocked_via_end_action][zha_cluster_commands]
+# name: test_enable_notification_with_switch_override[integration_config1-kitchen_override,doors_open_on][z2m_cluster_commands]
list([
ServiceCallSnapshot({
'context': ,
'data': ReadOnlyDict({
- 'cluster_id': 64561,
- 'cluster_type': 'in',
- 'command': 1,
- 'command_type': 'server',
- 'endpoint_id': 1,
- 'ieee': 'mock-ieee:kitchen',
- 'manufacturer': 4655,
- 'params': dict({
- 'led_color': 0,
- 'led_duration': 255,
- 'led_effect': 6,
- 'led_level': 100,
- }),
+ 'payload': '{"localProtection":"","doubleTapClearNotifications":""}',
+ 'topic': 'home/z2m/Kitchen/get',
}),
- 'domain': 'zha',
+ 'domain': 'mqtt',
'hass': ,
'return_response': False,
- 'service': 'issue_zigbee_cluster_command',
+ 'service': 'publish',
}),
ServiceCallSnapshot({
'context': ,
'data': ReadOnlyDict({
- 'cluster_id': 64561,
- 'cluster_type': 'in',
- 'command': 1,
- 'command_type': 'server',
- 'endpoint_id': 1,
- 'ieee': 'mock-ieee:kitchen',
- 'manufacturer': 4655,
- 'params': dict({
- 'led_color': 170,
- 'led_duration': 255,
- 'led_effect': 255,
- 'led_level': 100,
- }),
+ 'payload': '{"led_effect":{"led_color":90,"led_effect":1,"led_level":100,"led_duration":255}}',
+ 'topic': 'home/z2m/Kitchen/set',
}),
- 'domain': 'zha',
+ 'domain': 'mqtt',
'hass': ,
'return_response': False,
- 'service': 'issue_zigbee_cluster_command',
+ 'service': 'publish',
+ }),
+ ])
+# ---
+# name: test_enable_notification_with_switch_override[integration_config2-doors_open_on,kitchen_override_leds_5m1s_duration_expired][events]
+ list([
+ EventSnapshot({
+ 'context': ,
+ 'data': ReadOnlyDict({
+ 'entity_id': 'light.kitchen',
+ 'override': 'lampie.override',
+ }),
+ 'event_type': 'lampie.expired',
+ 'id': ,
+ 'origin': 'LOCAL',
+ 'time_fired': ,
}),
])
# ---
-# name: test_single_config_entry[doors_open_5m1s_duration_unexpired][notification_info]
+# name: test_enable_notification_with_switch_override[integration_config2-doors_open_on,kitchen_override_leds_5m1s_duration_expired][notification_info]
dict({
'expiration': ExpirationInfoSnapshot({
- 'cancel_listener': ,
- 'expires_at': HAFakeDatetime(2025, 5, 20, 3, 56, 33, 3245, tzinfo=zoneinfo.ZoneInfo(key='US/Pacific')),
+ 'cancel_listener': None,
+ 'expires_at': None,
'started_at': HAFakeDatetime(2025, 5, 20, 3, 51, 32, 3245, tzinfo=zoneinfo.ZoneInfo(key='US/Pacific')),
}),
'led_config_override': None,
'notification_on': True,
})
# ---
-# name: test_single_config_entry[doors_open_5m1s_duration_unexpired][sensor.kitchen_effect_brightness]
+# name: test_enable_notification_with_switch_override[integration_config2-doors_open_on,kitchen_override_leds_5m1s_duration_expired][sensor.kitchen_effect_brightness]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Kitchen Effect brightness',
@@ -2256,7 +2315,7 @@
'state': '100.0',
})
# ---
-# name: test_single_config_entry[doors_open_5m1s_duration_unexpired][sensor.kitchen_effect_color]
+# name: test_enable_notification_with_switch_override[integration_config2-doors_open_on,kitchen_override_leds_5m1s_duration_expired][sensor.kitchen_effect_color]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Kitchen Effect color',
@@ -2270,7 +2329,7 @@
'state': 'red',
})
# ---
-# name: test_single_config_entry[doors_open_5m1s_duration_unexpired][sensor.kitchen_effect_duration]
+# name: test_enable_notification_with_switch_override[integration_config2-doors_open_on,kitchen_override_leds_5m1s_duration_expired][sensor.kitchen_effect_duration]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Kitchen Effect duration',
@@ -2283,10 +2342,10 @@
'last_changed': ,
'last_reported': ,
'last_updated': ,
- 'state': '301',
+ 'state': 'unknown',
})
# ---
-# name: test_single_config_entry[doors_open_5m1s_duration_unexpired][sensor.kitchen_effect_type]
+# name: test_enable_notification_with_switch_override[integration_config2-doors_open_on,kitchen_override_leds_5m1s_duration_expired][sensor.kitchen_effect_type]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Kitchen Effect type',
@@ -2300,7 +2359,7 @@
'state': 'open_close',
})
# ---
-# name: test_single_config_entry[doors_open_5m1s_duration_unexpired][sensor.kitchen_notification]
+# name: test_enable_notification_with_switch_override[integration_config2-doors_open_on,kitchen_override_leds_5m1s_duration_expired][sensor.kitchen_notification]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Kitchen Notification',
@@ -2314,17 +2373,15 @@
'state': 'doors_open',
})
# ---
-# name: test_single_config_entry[doors_open_5m1s_duration_unexpired][service_calls]
+# name: test_enable_notification_with_switch_override[integration_config2-doors_open_on,kitchen_override_leds_5m1s_duration_expired][service_calls]
list([
])
# ---
-# name: test_single_config_entry[doors_open_5m1s_duration_unexpired][switch.doors_open_notification]
+# name: test_enable_notification_with_switch_override[integration_config2-doors_open_on,kitchen_override_leds_5m1s_duration_expired][switch.doors_open_notification]
StateSnapshot({
'attributes': ReadOnlyDict({
- 'expires_at': '2025-05-20T03:56:33.003245-07:00',
'friendly_name': 'Doors Open Notification',
'icon': 'mdi:circle-box',
- 'started_at': '2025-05-20T03:51:32.003245-07:00',
}),
'context': ,
'entity_id': 'switch.doors_open_notification',
@@ -2334,19 +2391,21 @@
'state': 'on',
})
# ---
-# name: test_single_config_entry[doors_open_5m1s_duration_unexpired][switch_info]
+# name: test_enable_notification_with_switch_override[integration_config2-doors_open_on,kitchen_override_leds_5m1s_duration_expired][switch_info]
dict({
- 'disable_clear_notification_id': 'switch.kitchen_disable_config_2x_tap_to_clear_notifications',
'expiration': ExpirationInfoSnapshot({
'cancel_listener': None,
'expires_at': None,
'started_at': None,
}),
+ 'integration': ,
+ 'integration_info': dict({
+ }),
'led_config': tuple(
dict({
'brightness': 100.0,
'color': ,
- 'duration': 301,
+ 'duration': None,
'effect': ,
}),
),
@@ -2354,50 +2413,78 @@
'type': ,
'value': 'doors_open',
}),
- 'local_protection_id': 'switch.kitchen_local_protection',
'priorities': tuple(
'doors_open',
),
})
# ---
-# name: test_single_config_entry[doors_open_5m1s_duration_unexpired][zha_cluster_commands]
+# name: test_enable_notification_with_switch_override[integration_config2-doors_open_on,kitchen_override_leds_5m1s_duration_expired][zwave_cluster_commands]
list([
ServiceCallSnapshot({
'context': ,
'data': ReadOnlyDict({
- 'cluster_id': 64561,
- 'cluster_type': 'in',
- 'command': 1,
- 'command_type': 'server',
- 'endpoint_id': 1,
- 'ieee': 'mock-ieee:kitchen',
- 'manufacturer': 4655,
- 'params': dict({
- 'led_color': 0,
- 'led_duration': 255,
- 'led_effect': 6,
- 'led_level': 100,
- }),
+ 'device_id': ,
+ 'parameter': 99,
+ 'value': 100689151,
}),
- 'domain': 'zha',
+ 'domain': 'zwave_js',
'hass': ,
'return_response': False,
- 'service': 'issue_zigbee_cluster_command',
+ 'service': 'bulk_set_partial_config_parameters',
+ }),
+ ServiceCallSnapshot({
+ 'context': ,
+ 'data': ReadOnlyDict({
+ 'device_id': ,
+ 'parameter': 99,
+ 'value': 22701311,
+ }),
+ 'domain': 'zwave_js',
+ 'hass': ,
+ 'return_response': False,
+ 'service': 'bulk_set_partial_config_parameters',
+ }),
+ ServiceCallSnapshot({
+ 'context': ,
+ 'data': ReadOnlyDict({
+ 'device_id': ,
+ 'parameter': 99,
+ 'value': 100689151,
+ }),
+ 'domain': 'zwave_js',
+ 'hass': ,
+ 'return_response': False,
+ 'service': 'bulk_set_partial_config_parameters',
+ }),
+ ])
+# ---
+# name: test_enable_notification_with_switch_override[integration_config2-doors_open_on,kitchen_override_leds_dismissed][events]
+ list([
+ EventSnapshot({
+ 'context': ,
+ 'data': ReadOnlyDict({
+ 'entity_id': 'light.kitchen',
+ 'override': 'lampie.override',
+ }),
+ 'event_type': 'lampie.dismissed',
+ 'id': ,
+ 'origin': 'LOCAL',
+ 'time_fired': ,
}),
])
# ---
-# name: test_single_config_entry[doors_open_5m1s_reactivated_and_unexpired][notification_info]
+# name: test_enable_notification_with_switch_override[integration_config2-doors_open_on,kitchen_override_leds_dismissed][notification_info]
dict({
'expiration': ExpirationInfoSnapshot({
- 'cancel_listener': ,
- 'expires_at': HAFakeDatetime(2025, 5, 20, 3, 56, 33, 3245, tzinfo=zoneinfo.ZoneInfo(key='US/Pacific')),
+ 'cancel_listener': None,
+ 'expires_at': None,
'started_at': HAFakeDatetime(2025, 5, 20, 3, 51, 32, 3245, tzinfo=zoneinfo.ZoneInfo(key='US/Pacific')),
}),
'led_config_override': None,
'notification_on': True,
})
# ---
-# name: test_single_config_entry[doors_open_5m1s_reactivated_and_unexpired][sensor.kitchen_effect_brightness]
+# name: test_enable_notification_with_switch_override[integration_config2-doors_open_on,kitchen_override_leds_dismissed][sensor.kitchen_effect_brightness]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Kitchen Effect brightness',
@@ -2413,7 +2500,7 @@
'state': '100.0',
})
# ---
-# name: test_single_config_entry[doors_open_5m1s_reactivated_and_unexpired][sensor.kitchen_effect_color]
+# name: test_enable_notification_with_switch_override[integration_config2-doors_open_on,kitchen_override_leds_dismissed][sensor.kitchen_effect_color]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Kitchen Effect color',
@@ -2427,7 +2514,7 @@
'state': 'red',
})
# ---
-# name: test_single_config_entry[doors_open_5m1s_reactivated_and_unexpired][sensor.kitchen_effect_duration]
+# name: test_enable_notification_with_switch_override[integration_config2-doors_open_on,kitchen_override_leds_dismissed][sensor.kitchen_effect_duration]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Kitchen Effect duration',
@@ -2440,10 +2527,10 @@
'last_changed': ,
'last_reported': ,
'last_updated': ,
- 'state': '301',
+ 'state': 'unknown',
})
# ---
-# name: test_single_config_entry[doors_open_5m1s_reactivated_and_unexpired][sensor.kitchen_effect_type]
+# name: test_enable_notification_with_switch_override[integration_config2-doors_open_on,kitchen_override_leds_dismissed][sensor.kitchen_effect_type]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Kitchen Effect type',
@@ -2457,7 +2544,7 @@
'state': 'open_close',
})
# ---
-# name: test_single_config_entry[doors_open_5m1s_reactivated_and_unexpired][sensor.kitchen_notification]
+# name: test_enable_notification_with_switch_override[integration_config2-doors_open_on,kitchen_override_leds_dismissed][sensor.kitchen_notification]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Kitchen Notification',
@@ -2471,17 +2558,15 @@
'state': 'doors_open',
})
# ---
-# name: test_single_config_entry[doors_open_5m1s_reactivated_and_unexpired][service_calls]
+# name: test_enable_notification_with_switch_override[integration_config2-doors_open_on,kitchen_override_leds_dismissed][service_calls]
list([
])
# ---
-# name: test_single_config_entry[doors_open_5m1s_reactivated_and_unexpired][switch.doors_open_notification]
+# name: test_enable_notification_with_switch_override[integration_config2-doors_open_on,kitchen_override_leds_dismissed][switch.doors_open_notification]
StateSnapshot({
'attributes': ReadOnlyDict({
- 'expires_at': '2025-05-20T03:56:33.003245-07:00',
'friendly_name': 'Doors Open Notification',
'icon': 'mdi:circle-box',
- 'started_at': '2025-05-20T03:51:32.003245-07:00',
}),
'context': ,
'entity_id': 'switch.doors_open_notification',
@@ -2491,19 +2576,21 @@
'state': 'on',
})
# ---
-# name: test_single_config_entry[doors_open_5m1s_reactivated_and_unexpired][switch_info]
+# name: test_enable_notification_with_switch_override[integration_config2-doors_open_on,kitchen_override_leds_dismissed][switch_info]
dict({
- 'disable_clear_notification_id': 'switch.kitchen_disable_config_2x_tap_to_clear_notifications',
'expiration': ExpirationInfoSnapshot({
'cancel_listener': None,
'expires_at': None,
'started_at': None,
}),
+ 'integration': ,
+ 'integration_info': dict({
+ }),
'led_config': tuple(
dict({
'brightness': 100.0,
'color': ,
- 'duration': 301,
+ 'duration': None,
'effect': ,
}),
),
@@ -2511,50 +2598,63 @@
'type': ,
'value': 'doors_open',
}),
- 'local_protection_id': 'switch.kitchen_local_protection',
'priorities': tuple(
'doors_open',
),
})
# ---
-# name: test_single_config_entry[doors_open_5m1s_reactivated_and_unexpired][zha_cluster_commands]
+# name: test_enable_notification_with_switch_override[integration_config2-doors_open_on,kitchen_override_leds_dismissed][zwave_cluster_commands]
list([
ServiceCallSnapshot({
'context': ,
'data': ReadOnlyDict({
- 'cluster_id': 64561,
- 'cluster_type': 'in',
- 'command': 1,
- 'command_type': 'server',
- 'endpoint_id': 1,
- 'ieee': 'mock-ieee:kitchen',
- 'manufacturer': 4655,
- 'params': dict({
- 'led_color': 0,
- 'led_duration': 255,
- 'led_effect': 6,
- 'led_level': 100,
- }),
+ 'device_id': ,
+ 'parameter': 99,
+ 'value': 100689151,
}),
- 'domain': 'zha',
+ 'domain': 'zwave_js',
'hass': ,
'return_response': False,
- 'service': 'issue_zigbee_cluster_command',
+ 'service': 'bulk_set_partial_config_parameters',
+ }),
+ ServiceCallSnapshot({
+ 'context': ,
+ 'data': ReadOnlyDict({
+ 'device_id': ,
+ 'parameter': 99,
+ 'value': 22701311,
+ }),
+ 'domain': 'zwave_js',
+ 'hass': ,
+ 'return_response': False,
+ 'service': 'bulk_set_partial_config_parameters',
+ }),
+ ServiceCallSnapshot({
+ 'context': ,
+ 'data': ReadOnlyDict({
+ 'device_id': ,
+ 'parameter': 99,
+ 'value': 100689151,
+ }),
+ 'domain': 'zwave_js',
+ 'hass': ,
+ 'return_response': False,
+ 'service': 'bulk_set_partial_config_parameters',
}),
])
# ---
-# name: test_single_config_entry[doors_open_5m1s_turned_off][notification_info]
+# name: test_enable_notification_with_switch_override[integration_config2-doors_open_on,kitchen_override_leds_reset][notification_info]
dict({
'expiration': ExpirationInfoSnapshot({
'cancel_listener': None,
'expires_at': None,
- 'started_at': None,
+ 'started_at': HAFakeDatetime(2025, 5, 20, 3, 51, 32, 3245, tzinfo=zoneinfo.ZoneInfo(key='US/Pacific')),
}),
'led_config_override': None,
- 'notification_on': False,
+ 'notification_on': True,
})
# ---
-# name: test_single_config_entry[doors_open_5m1s_turned_off][sensor.kitchen_effect_brightness]
+# name: test_enable_notification_with_switch_override[integration_config2-doors_open_on,kitchen_override_leds_reset][sensor.kitchen_effect_brightness]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Kitchen Effect brightness',
@@ -2567,10 +2667,10 @@
'last_changed': ,
'last_reported': ,
'last_updated': ,
- 'state': 'unknown',
+ 'state': '100.0',
})
# ---
-# name: test_single_config_entry[doors_open_5m1s_turned_off][sensor.kitchen_effect_color]
+# name: test_enable_notification_with_switch_override[integration_config2-doors_open_on,kitchen_override_leds_reset][sensor.kitchen_effect_color]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Kitchen Effect color',
@@ -2581,10 +2681,10 @@
'last_changed': ,
'last_reported': ,
'last_updated': ,
- 'state': 'unknown',
+ 'state': 'red',
})
# ---
-# name: test_single_config_entry[doors_open_5m1s_turned_off][sensor.kitchen_effect_duration]
+# name: test_enable_notification_with_switch_override[integration_config2-doors_open_on,kitchen_override_leds_reset][sensor.kitchen_effect_duration]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Kitchen Effect duration',
@@ -2600,7 +2700,7 @@
'state': 'unknown',
})
# ---
-# name: test_single_config_entry[doors_open_5m1s_turned_off][sensor.kitchen_effect_type]
+# name: test_enable_notification_with_switch_override[integration_config2-doors_open_on,kitchen_override_leds_reset][sensor.kitchen_effect_type]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Kitchen Effect type',
@@ -2611,10 +2711,10 @@
'last_changed': ,
'last_reported': ,
'last_updated': ,
- 'state': 'unknown',
+ 'state': 'open_close',
})
# ---
-# name: test_single_config_entry[doors_open_5m1s_turned_off][sensor.kitchen_notification]
+# name: test_enable_notification_with_switch_override[integration_config2-doors_open_on,kitchen_override_leds_reset][sensor.kitchen_notification]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Kitchen Notification',
@@ -2625,14 +2725,14 @@
'last_changed': ,
'last_reported': ,
'last_updated': ,
- 'state': 'unknown',
+ 'state': 'doors_open',
})
# ---
-# name: test_single_config_entry[doors_open_5m1s_turned_off][service_calls]
+# name: test_enable_notification_with_switch_override[integration_config2-doors_open_on,kitchen_override_leds_reset][service_calls]
list([
])
# ---
-# name: test_single_config_entry[doors_open_5m1s_turned_off][switch.doors_open_notification]
+# name: test_enable_notification_with_switch_override[integration_config2-doors_open_on,kitchen_override_leds_reset][switch.doors_open_notification]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Doors Open Notification',
@@ -2643,103 +2743,88 @@
'last_changed': ,
'last_reported': ,
'last_updated': ,
- 'state': 'off',
+ 'state': 'on',
})
# ---
-# name: test_single_config_entry[doors_open_5m1s_turned_off][switch_info]
+# name: test_enable_notification_with_switch_override[integration_config2-doors_open_on,kitchen_override_leds_reset][switch_info]
dict({
- 'disable_clear_notification_id': 'switch.kitchen_disable_config_2x_tap_to_clear_notifications',
'expiration': ExpirationInfoSnapshot({
'cancel_listener': None,
'expires_at': None,
- 'started_at': None,
+ 'started_at': HAFakeDatetime(2025, 5, 20, 3, 51, 32, 3245, tzinfo=zoneinfo.ZoneInfo(key='US/Pacific')),
+ }),
+ 'integration': ,
+ 'integration_info': dict({
}),
'led_config': tuple(
+ dict({
+ 'brightness': 100.0,
+ 'color': ,
+ 'duration': None,
+ 'effect': ,
+ }),
),
'led_config_source': dict({
'type': ,
- 'value': None,
+ 'value': 'doors_open',
}),
- 'local_protection_id': 'switch.kitchen_local_protection',
'priorities': tuple(
'doors_open',
),
})
# ---
-# name: test_single_config_entry[doors_open_5m1s_turned_off][zha_cluster_commands]
+# name: test_enable_notification_with_switch_override[integration_config2-doors_open_on,kitchen_override_leds_reset][zwave_cluster_commands]
list([
ServiceCallSnapshot({
'context': ,
'data': ReadOnlyDict({
- 'cluster_id': 64561,
- 'cluster_type': 'in',
- 'command': 1,
- 'command_type': 'server',
- 'endpoint_id': 1,
- 'ieee': 'mock-ieee:kitchen',
- 'manufacturer': 4655,
- 'params': dict({
- 'led_color': 0,
- 'led_duration': 255,
- 'led_effect': 6,
- 'led_level': 100,
- }),
+ 'device_id': ,
+ 'parameter': 99,
+ 'value': 100689151,
}),
- 'domain': 'zha',
+ 'domain': 'zwave_js',
'hass': ,
'return_response': False,
- 'service': 'issue_zigbee_cluster_command',
+ 'service': 'bulk_set_partial_config_parameters',
}),
ServiceCallSnapshot({
'context': ,
'data': ReadOnlyDict({
- 'cluster_id': 64561,
- 'cluster_type': 'in',
- 'command': 1,
- 'command_type': 'server',
- 'endpoint_id': 1,
- 'ieee': 'mock-ieee:kitchen',
- 'manufacturer': 4655,
- 'params': dict({
- 'led_color': 170,
- 'led_duration': 255,
- 'led_effect': 255,
- 'led_level': 100,
- }),
+ 'device_id': ,
+ 'parameter': 99,
+ 'value': 22701311,
}),
- 'domain': 'zha',
+ 'domain': 'zwave_js',
'hass': ,
'return_response': False,
- 'service': 'issue_zigbee_cluster_command',
+ 'service': 'bulk_set_partial_config_parameters',
}),
- ])
-# ---
-# name: test_single_config_entry[doors_open_mixed_specific_durations_expired][events]
- list([
- EventSnapshot({
+ ServiceCallSnapshot({
'context': ,
'data': ReadOnlyDict({
- 'notification': 'doors_open',
+ 'device_id': ,
+ 'parameter': 99,
+ 'value': 100689151,
}),
- 'event_type': 'lampie.expired',
- 'id': ,
- 'origin': 'LOCAL',
- 'time_fired': ,
+ 'domain': 'zwave_js',
+ 'hass': ,
+ 'return_response': False,
+ 'service': 'bulk_set_partial_config_parameters',
}),
])
# ---
-# name: test_single_config_entry[doors_open_mixed_specific_durations_expired][notification_info]
+# name: test_enable_notification_with_switch_override[integration_config2-kitchen_override,doors_open_on][notification_info]
dict({
'expiration': ExpirationInfoSnapshot({
'cancel_listener': None,
'expires_at': None,
- 'started_at': None,
+ 'started_at': HAFakeDatetime(2025, 5, 20, 3, 51, 32, 3245, tzinfo=zoneinfo.ZoneInfo(key='US/Pacific')),
}),
'led_config_override': None,
- 'notification_on': False,
+ 'notification_on': True,
})
# ---
-# name: test_single_config_entry[doors_open_mixed_specific_durations_expired][sensor.kitchen_effect_brightness]
+# name: test_enable_notification_with_switch_override[integration_config2-kitchen_override,doors_open_on][sensor.kitchen_effect_brightness]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Kitchen Effect brightness',
@@ -2752,10 +2837,10 @@
'last_changed': ,
'last_reported': ,
'last_updated': ,
- 'state': 'unknown',
+ 'state': '100.0',
})
# ---
-# name: test_single_config_entry[doors_open_mixed_specific_durations_expired][sensor.kitchen_effect_color]
+# name: test_enable_notification_with_switch_override[integration_config2-kitchen_override,doors_open_on][sensor.kitchen_effect_color]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Kitchen Effect color',
@@ -2766,10 +2851,10 @@
'last_changed': ,
'last_reported': ,
'last_updated': ,
- 'state': 'unknown',
+ 'state': 'green',
})
# ---
-# name: test_single_config_entry[doors_open_mixed_specific_durations_expired][sensor.kitchen_effect_duration]
+# name: test_enable_notification_with_switch_override[integration_config2-kitchen_override,doors_open_on][sensor.kitchen_effect_duration]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Kitchen Effect duration',
@@ -2785,7 +2870,7 @@
'state': 'unknown',
})
# ---
-# name: test_single_config_entry[doors_open_mixed_specific_durations_expired][sensor.kitchen_effect_type]
+# name: test_enable_notification_with_switch_override[integration_config2-kitchen_override,doors_open_on][sensor.kitchen_effect_type]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Kitchen Effect type',
@@ -2796,10 +2881,10 @@
'last_changed': ,
'last_reported': ,
'last_updated': ,
- 'state': 'unknown',
+ 'state': 'solid',
})
# ---
-# name: test_single_config_entry[doors_open_mixed_specific_durations_expired][sensor.kitchen_notification]
+# name: test_enable_notification_with_switch_override[integration_config2-kitchen_override,doors_open_on][sensor.kitchen_notification]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Kitchen Notification',
@@ -2810,14 +2895,14 @@
'last_changed': ,
'last_reported': ,
'last_updated': ,
- 'state': 'unknown',
+ 'state': 'lampie.override',
})
# ---
-# name: test_single_config_entry[doors_open_mixed_specific_durations_expired][service_calls]
+# name: test_enable_notification_with_switch_override[integration_config2-kitchen_override,doors_open_on][service_calls]
list([
])
# ---
-# name: test_single_config_entry[doors_open_mixed_specific_durations_expired][switch.doors_open_notification]
+# name: test_enable_notification_with_switch_override[integration_config2-kitchen_override,doors_open_on][switch.doors_open_notification]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Doors Open Notification',
@@ -2828,380 +2913,268 @@
'last_changed': ,
'last_reported': ,
'last_updated': ,
- 'state': 'off',
+ 'state': 'on',
})
# ---
-# name: test_single_config_entry[doors_open_mixed_specific_durations_expired][switch_info]
+# name: test_enable_notification_with_switch_override[integration_config2-kitchen_override,doors_open_on][switch_info]
dict({
- 'disable_clear_notification_id': 'switch.kitchen_disable_config_2x_tap_to_clear_notifications',
'expiration': ExpirationInfoSnapshot({
'cancel_listener': None,
'expires_at': None,
- 'started_at': None,
+ 'started_at': HAFakeDatetime(2025, 5, 20, 3, 51, 32, 3245, tzinfo=zoneinfo.ZoneInfo(key='US/Pacific')),
+ }),
+ 'integration': ,
+ 'integration_info': dict({
}),
'led_config': tuple(
+ dict({
+ 'brightness': 100.0,
+ 'color': ,
+ 'duration': None,
+ 'effect': ,
+ }),
),
'led_config_source': dict({
- 'type': ,
- 'value': None,
+ 'type': ,
+ 'value': 'lampie.override',
}),
- 'local_protection_id': 'switch.kitchen_local_protection',
'priorities': tuple(
'doors_open',
),
})
# ---
-# name: test_single_config_entry[doors_open_mixed_specific_durations_expired][zha_cluster_commands]
+# name: test_enable_notification_with_switch_override[integration_config2-kitchen_override,doors_open_on][zwave_cluster_commands]
list([
ServiceCallSnapshot({
'context': ,
'data': ReadOnlyDict({
- 'cluster_id': 64561,
- 'cluster_type': 'in',
- 'command': 3,
- 'command_type': 'server',
- 'endpoint_id': 1,
- 'ieee': 'mock-ieee:kitchen',
- 'manufacturer': 4655,
- 'params': dict({
- 'led_color': 0,
- 'led_duration': 255,
- 'led_effect': 1,
- 'led_level': 100,
- 'led_number': 0,
- }),
+ 'device_id': ,
+ 'parameter': 99,
+ 'value': 22701311,
}),
- 'domain': 'zha',
+ 'domain': 'zwave_js',
'hass': ,
'return_response': False,
- 'service': 'issue_zigbee_cluster_command',
+ 'service': 'bulk_set_partial_config_parameters',
}),
- ServiceCallSnapshot({
+ ])
+# ---
+# name: test_enable_notification_with_switch_override[integration_config3-doors_open_on,kitchen_override_leds_5m1s_duration_expired][events]
+ list([
+ EventSnapshot({
'context': ,
'data': ReadOnlyDict({
- 'cluster_id': 64561,
- 'cluster_type': 'in',
- 'command': 3,
- 'command_type': 'server',
- 'endpoint_id': 1,
- 'ieee': 'mock-ieee:kitchen',
- 'manufacturer': 4655,
- 'params': dict({
- 'led_color': 25,
- 'led_duration': 255,
- 'led_effect': 1,
- 'led_level': 100,
- 'led_number': 1,
- }),
+ 'entity_id': 'light.kitchen',
+ 'override': 'lampie.override',
}),
- 'domain': 'zha',
- 'hass': ,
- 'return_response': False,
- 'service': 'issue_zigbee_cluster_command',
+ 'event_type': 'lampie.expired',
+ 'id': ,
+ 'origin': 'LOCAL',
+ 'time_fired': ,
}),
- ServiceCallSnapshot({
- 'context': ,
- 'data': ReadOnlyDict({
- 'cluster_id': 64561,
- 'cluster_type': 'in',
- 'command': 3,
- 'command_type': 'server',
- 'endpoint_id': 1,
- 'ieee': 'mock-ieee:kitchen',
- 'manufacturer': 4655,
- 'params': dict({
- 'led_color': 255,
- 'led_duration': 255,
- 'led_effect': 1,
- 'led_level': 100,
- 'led_number': 2,
- }),
- }),
- 'domain': 'zha',
- 'hass': ,
- 'return_response': False,
- 'service': 'issue_zigbee_cluster_command',
+ ])
+# ---
+# name: test_enable_notification_with_switch_override[integration_config3-doors_open_on,kitchen_override_leds_5m1s_duration_expired][notification_info]
+ dict({
+ 'expiration': ExpirationInfoSnapshot({
+ 'cancel_listener': None,
+ 'expires_at': None,
+ 'started_at': HAFakeDatetime(2025, 5, 20, 3, 51, 32, 3245, tzinfo=zoneinfo.ZoneInfo(key='US/Pacific')),
}),
- ServiceCallSnapshot({
- 'context': ,
- 'data': ReadOnlyDict({
- 'cluster_id': 64561,
- 'cluster_type': 'in',
- 'command': 3,
- 'command_type': 'server',
- 'endpoint_id': 1,
- 'ieee': 'mock-ieee:kitchen',
- 'manufacturer': 4655,
- 'params': dict({
- 'led_color': 0,
- 'led_duration': 255,
- 'led_effect': 1,
- 'led_level': 100,
- 'led_number': 3,
- }),
+ 'led_config_override': None,
+ 'notification_on': True,
+ })
+# ---
+# name: test_enable_notification_with_switch_override[integration_config3-doors_open_on,kitchen_override_leds_5m1s_duration_expired][sensor.kitchen_effect_brightness]
+ StateSnapshot({
+ 'attributes': ReadOnlyDict({
+ 'friendly_name': 'Kitchen Effect brightness',
+ 'icon': 'mdi:brightness-percent',
+ 'state_class': ,
+ 'unit_of_measurement': '%',
+ }),
+ 'context': ,
+ 'entity_id': 'sensor.kitchen_effect_brightness',
+ 'last_changed': ,
+ 'last_reported': ,
+ 'last_updated': ,
+ 'state': '100.0',
+ })
+# ---
+# name: test_enable_notification_with_switch_override[integration_config3-doors_open_on,kitchen_override_leds_5m1s_duration_expired][sensor.kitchen_effect_color]
+ StateSnapshot({
+ 'attributes': ReadOnlyDict({
+ 'friendly_name': 'Kitchen Effect color',
+ 'icon': 'mdi:palette',
+ }),
+ 'context': ,
+ 'entity_id': 'sensor.kitchen_effect_color',
+ 'last_changed': ,
+ 'last_reported': ,
+ 'last_updated': ,
+ 'state': 'red',
+ })
+# ---
+# name: test_enable_notification_with_switch_override[integration_config3-doors_open_on,kitchen_override_leds_5m1s_duration_expired][sensor.kitchen_effect_duration]
+ StateSnapshot({
+ 'attributes': ReadOnlyDict({
+ 'friendly_name': 'Kitchen Effect duration',
+ 'icon': 'mdi:timer',
+ 'state_class': ,
+ 'unit_of_measurement': ,
+ }),
+ 'context': ,
+ 'entity_id': 'sensor.kitchen_effect_duration',
+ 'last_changed': ,
+ 'last_reported': ,
+ 'last_updated': ,
+ 'state': 'unknown',
+ })
+# ---
+# name: test_enable_notification_with_switch_override[integration_config3-doors_open_on,kitchen_override_leds_5m1s_duration_expired][sensor.kitchen_effect_type]
+ StateSnapshot({
+ 'attributes': ReadOnlyDict({
+ 'friendly_name': 'Kitchen Effect type',
+ 'icon': 'mdi:star-four-points-box',
+ }),
+ 'context': ,
+ 'entity_id': 'sensor.kitchen_effect_type',
+ 'last_changed': ,
+ 'last_reported': ,
+ 'last_updated': ,
+ 'state': 'open_close',
+ })
+# ---
+# name: test_enable_notification_with_switch_override[integration_config3-doors_open_on,kitchen_override_leds_5m1s_duration_expired][sensor.kitchen_notification]
+ StateSnapshot({
+ 'attributes': ReadOnlyDict({
+ 'friendly_name': 'Kitchen Notification',
+ 'icon': 'mdi:circle-box',
+ }),
+ 'context': ,
+ 'entity_id': 'sensor.kitchen_notification',
+ 'last_changed':