Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add water_heater device trigger support #79499

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
82 changes: 82 additions & 0 deletions homeassistant/components/water_heater/device_trigger.py
@@ -0,0 +1,82 @@
"""Provides device triggers for Water Heater."""
from __future__ import annotations

from typing import Any

import voluptuous as vol

from homeassistant.components.automation import TriggerActionType, TriggerInfo
from homeassistant.components.device_automation import DEVICE_TRIGGER_BASE_SCHEMA
from homeassistant.components.homeassistant.triggers import state as state_trigger
from homeassistant.const import (
CONF_DEVICE_ID,
CONF_DOMAIN,
CONF_ENTITY_ID,
CONF_PLATFORM,
CONF_TYPE,
STATE_OFF,
STATE_ON,
)
from homeassistant.core import CALLBACK_TYPE, HomeAssistant
from homeassistant.helpers import config_validation as cv, entity_registry
from homeassistant.helpers.typing import ConfigType

from . import DOMAIN

TRIGGER_TYPES = {"turned_on", "turned_off"}

TRIGGER_SCHEMA = DEVICE_TRIGGER_BASE_SCHEMA.extend(
{
vol.Required(CONF_ENTITY_ID): cv.entity_id,
vol.Required(CONF_TYPE): vol.In(TRIGGER_TYPES),
}
)


async def async_get_triggers(
hass: HomeAssistant, device_id: str
) -> list[dict[str, Any]]:
"""List device triggers for Water Heater devices."""
registry = entity_registry.async_get(hass)
triggers = []

# Get all the integrations entities for this device
for entry in entity_registry.async_entries_for_device(registry, device_id):
if entry.domain != DOMAIN:
continue

# Add triggers for each entity that belongs to this integration
base_trigger = {
CONF_PLATFORM: "device",
CONF_DEVICE_ID: device_id,
CONF_DOMAIN: DOMAIN,
CONF_ENTITY_ID: entry.entity_id,
}
triggers.append({**base_trigger, CONF_TYPE: "turned_on"})
triggers.append({**base_trigger, CONF_TYPE: "turned_off"})

return triggers


async def async_attach_trigger(
hass: HomeAssistant,
config: ConfigType,
action: TriggerActionType,
automation_info: TriggerInfo,
) -> CALLBACK_TYPE:
"""Attach a trigger."""

if config[CONF_TYPE] == "turned_on":
to_state = STATE_ON
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I dont think water heater generally have am on state. Its all weird heatpump, eco, ... states.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was going based off the action_types in strings.json:

{
  "device_automation": {
    "action_type": {
      "turn_on": "Turn on {entity_name}",
      "turn_off": "Turn off {entity_name}"
    },
    "trigger_type": {
      "turned_on": "{entity_name} turned on",
      "turned_off": "{entity_name} turned off"
    }
  }

But I can update this with the states in water_heater/__init__.py:

STATE_ECO = "eco"
STATE_ELECTRIC = "electric"
STATE_PERFORMANCE = "performance"
STATE_HIGH_DEMAND = "high_demand"
STATE_HEAT_PUMP = "heat_pump"
STATE_GAS = "gas"

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you have to. Or trigger on OFF and NOT OFF

else:
to_state = STATE_OFF

state_config = {
state_trigger.CONF_PLATFORM: "state",
CONF_ENTITY_ID: config[CONF_ENTITY_ID],
state_trigger.CONF_TO: to_state,
}
state_config = await state_trigger.async_validate_trigger_config(hass, state_config)
return await state_trigger.async_attach_trigger(
hass, state_config, action, automation_info, platform_type="device"
)
4 changes: 4 additions & 0 deletions homeassistant/components/water_heater/strings.json
Expand Up @@ -3,6 +3,10 @@
"action_type": {
"turn_on": "Turn on {entity_name}",
"turn_off": "Turn off {entity_name}"
},
"trigger_type": {
"turned_on": "{entity_name} turned on",
"turned_off": "{entity_name} turned off"
}
},
"state": {
Expand Down
4 changes: 4 additions & 0 deletions homeassistant/components/water_heater/translations/en.json
Expand Up @@ -3,6 +3,10 @@
"action_type": {
"turn_off": "Turn off {entity_name}",
"turn_on": "Turn on {entity_name}"
},
"trigger_type": {
"turned_off": "{entity_name} turned off",
"turned_on": "{entity_name} turned on"
}
},
"state": {
Expand Down
132 changes: 132 additions & 0 deletions tests/components/water_heater/test_device_trigger.py
@@ -0,0 +1,132 @@
"""The tests for Water Heater device triggers."""
import pytest

from homeassistant.components import automation
from homeassistant.components.device_automation import DeviceAutomationType
from homeassistant.components.water_heater import DOMAIN
from homeassistant.const import STATE_OFF, STATE_ON
from homeassistant.helpers import device_registry
from homeassistant.setup import async_setup_component

from tests.common import (
MockConfigEntry,
assert_lists_same,
async_get_device_automations,
async_mock_service,
mock_device_registry,
mock_registry,
)


@pytest.fixture
def device_reg(hass):
"""Return an empty, loaded, registry."""
return mock_device_registry(hass)


@pytest.fixture
def entity_reg(hass):
"""Return an empty, loaded, registry."""
return mock_registry(hass)


@pytest.fixture
def calls(hass):
"""Track calls to a mock service."""
return async_mock_service(hass, "test", "automation")


async def test_get_triggers(hass, device_reg, entity_reg):
"""Test we get the expected triggers from a water_heater."""
config_entry = MockConfigEntry(domain="test", data={})
config_entry.add_to_hass(hass)
device_entry = device_reg.async_get_or_create(
config_entry_id=config_entry.entry_id,
connections={(device_registry.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")},
)
entity_reg.async_get_or_create(DOMAIN, "test", "5678", device_id=device_entry.id)
expected_triggers = [
{
"platform": "device",
"domain": DOMAIN,
"type": trigger,
"device_id": device_entry.id,
"entity_id": f"{DOMAIN}.test_5678",
"metadata": {"secondary": False},
}
for trigger in ["turned_on", "turned_off"]
]
triggers = await async_get_device_automations(
hass, DeviceAutomationType.TRIGGER, device_entry.id
)
assert_lists_same(triggers, expected_triggers)


async def test_if_fires_on_state_change(hass, calls):
"""Test for turn_on and turn_off triggers firing."""
hass.states.async_set("water_heater.entity", STATE_OFF)

assert await async_setup_component(
hass,
automation.DOMAIN,
{
automation.DOMAIN: [
{
"trigger": {
"platform": "device",
"domain": DOMAIN,
"device_id": "",
"entity_id": "water_heater.entity",
"type": "turned_on",
},
"action": {
"service": "test.automation",
"data_template": {
"some": (
"turn_on - {{ trigger.platform}} - "
"{{ trigger.entity_id}} - {{ trigger.from_state.state}} - "
"{{ trigger.to_state.state}} - {{ trigger.for }} - "
"{{ trigger.id}}"
)
},
},
},
{
"trigger": {
"platform": "device",
"domain": DOMAIN,
"device_id": "",
"entity_id": "water_heater.entity",
"type": "turned_off",
},
"action": {
"service": "test.automation",
"data_template": {
"some": (
"turn_off - {{ trigger.platform}} - "
"{{ trigger.entity_id}} - {{ trigger.from_state.state}} - "
"{{ trigger.to_state.state}} - {{ trigger.for }} - "
"{{ trigger.id}}"
)
},
},
},
]
},
)

# Fake that the entity is turning on.
hass.states.async_set("water_heater.entity", STATE_ON)
await hass.async_block_till_done()
assert len(calls) == 1
assert calls[0].data[
"some"
] == "turn_on - device - {} - off - on - None - 0".format("water_heater.entity")

# Fake that the entity is turning off.
hass.states.async_set("water_heater.entity", STATE_OFF)
await hass.async_block_till_done()
assert len(calls) == 2
assert calls[1].data[
"some"
] == "turn_off - device - {} - on - off - None - 0".format("water_heater.entity")