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’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Scaffold device automation #26871

Merged
merged 1 commit into from Sep 27, 2019
Merged
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
4 changes: 2 additions & 2 deletions homeassistant/components/zha/device_trigger.py
Expand Up @@ -36,13 +36,13 @@ async def async_attach_trigger(hass, config, action, automation_info):

trigger = zha_device.device_automation_triggers[trigger]

state_config = {
event_config = {
event.CONF_EVENT_TYPE: ZHA_EVENT,
event.CONF_EVENT_DATA: {DEVICE_IEEE: str(zha_device.ieee), **trigger},
}

return await event.async_attach_trigger(
hass, state_config, action, automation_info, platform_type="device"
hass, event_config, action, automation_info, platform_type="device"
)


Expand Down
4 changes: 2 additions & 2 deletions script/scaffold/__main__.py
Expand Up @@ -65,10 +65,10 @@ def main():
print()

print("Running tests")
print(f"$ pytest -v tests/components/{info.domain}")
print(f"$ pytest -vvv tests/components/{info.domain}")
if (
subprocess.run(
f"pytest -v tests/components/{info.domain}", shell=True
f"pytest -vvv tests/components/{info.domain}", shell=True
).returncode
!= 0
):
Expand Down
27 changes: 27 additions & 0 deletions script/scaffold/docs.py
Expand Up @@ -29,5 +29,32 @@ def print_relevant_docs(template: str, info: Info) -> None:
- {info.tests_dir / "test_reproduce_state.py"}

Please update the relevant items marked as TODO before submitting a pull request.
"""
)

elif template == "device_trigger":
print(
f"""
Device trigger base has been added to the {info.domain} integration:
- {info.integration_dir / "device_trigger.py"}
- {info.tests_dir / "test_device_trigger.py"}
"""
)

elif template == "device_condition":
print(
f"""
Device condition base has been added to the {info.domain} integration:
- {info.integration_dir / "device_condition.py"}
- {info.tests_dir / "test_device_condition.py"}
"""
)

elif template == "device_action":
print(
f"""
Device action base has been added to the {info.domain} integration:
- {info.integration_dir / "device_action.py"}
- {info.tests_dir / "test_device_action.py"}
"""
)
@@ -0,0 +1,84 @@
"""Provides device automations for NEW_NAME."""
from typing import Optional, List
import voluptuous as vol

from homeassistant.const import (
ATTR_ENTITY_ID,
CONF_DOMAIN,
CONF_TYPE,
CONF_DEVICE_ID,
CONF_ENTITY_ID,
SERVICE_TURN_ON,
SERVICE_TURN_OFF,
)
from homeassistant.core import HomeAssistant, Context
from homeassistant.helpers import entity_registry
import homeassistant.helpers.config_validation as cv
from . import DOMAIN

# TODO specify your supported action types.
ACTION_TYPES = {"turn_on", "turn_off"}

ACTION_SCHEMA = cv.DEVICE_ACTION_BASE_SCHEMA.extend(
{
vol.Required(CONF_TYPE): vol.In(ACTION_TYPES),
vol.Required(CONF_ENTITY_ID): cv.entity_domain(DOMAIN),
}
)


async def async_get_actions(hass: HomeAssistant, device_id: str) -> List[dict]:
"""List device actions for NEW_NAME devices."""
registry = await entity_registry.async_get_registry(hass)
actions = []

# TODO Read this comment and remove it.
# This example shows how to iterate over the entities of this device
# that match this integration. If your actions instead rely on
# calling services, do something like:
# zha_device = await _async_get_zha_device(hass, device_id)
# return zha_device.device_actions

# 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 actions for each entity that belongs to this integration
# TODO add your own actions.
actions.append(
{
CONF_DEVICE_ID: device_id,
CONF_DOMAIN: DOMAIN,
CONF_ENTITY_ID: entry.entity_id,
CONF_TYPE: "turn_on",
}
)
actions.append(
{
CONF_DEVICE_ID: device_id,
CONF_DOMAIN: DOMAIN,
CONF_ENTITY_ID: entry.entity_id,
CONF_TYPE: "turn_off",
}
)

return actions


async def async_call_action_from_config(
hass: HomeAssistant, config: dict, variables: dict, context: Optional[Context]
) -> None:
"""Execute a device action."""
config = ACTION_SCHEMA(config)

service_data = {ATTR_ENTITY_ID: config[CONF_ENTITY_ID]}

if config[CONF_TYPE] == "turn_on":
service = SERVICE_TURN_ON
elif config[CONF_TYPE] == "turn_off":
service = SERVICE_TURN_OFF

await hass.services.async_call(
DOMAIN, service, service_data, blocking=True, context=context
)
103 changes: 103 additions & 0 deletions script/scaffold/templates/device_action/tests/test_device_action.py
@@ -0,0 +1,103 @@
"""The tests for NEW_NAME device actions."""
import pytest

from homeassistant.components.NEW_DOMAIN import DOMAIN
from homeassistant.setup import async_setup_component
import homeassistant.components.automation as automation
from homeassistant.helpers import device_registry

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


@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)


async def test_get_actions(hass, device_reg, entity_reg):
"""Test we get the expected actions from a switch."""
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_actions = [
{
"domain": DOMAIN,
"type": "turn_on",
"device_id": device_entry.id,
"entity_id": "NEW_DOMAIN.test_5678",
},
{
"domain": DOMAIN,
"type": "turn_off",
"device_id": device_entry.id,
"entity_id": "NEW_DOMAIN.test_5678",
},
]
actions = await async_get_device_automations(hass, "action", device_entry.id)
assert actions == expected_actions


async def test_action(hass):
"""Test for turn_on and turn_off actions."""
assert await async_setup_component(
hass,
automation.DOMAIN,
{
automation.DOMAIN: [
{
"trigger": {
"platform": "event",
"event_type": "test_event_turn_off",
},
"action": {
"domain": DOMAIN,
"device_id": "abcdefgh",
"entity_id": "NEW_DOMAIN.entity",
"type": "turn_off",
},
},
{
"trigger": {
"platform": "event",
"event_type": "test_event_turn_on",
},
"action": {
"domain": DOMAIN,
"device_id": "abcdefgh",
"entity_id": "NEW_DOMAIN.entity",
"type": "turn_on",
},
},
]
},
)

turn_off_calls = async_mock_service(hass, "NEW_DOMAIN", "turn_off")
turn_on_calls = async_mock_service(hass, "NEW_DOMAIN", "turn_on")

hass.bus.async_fire("test_event_turn_off")
await hass.async_block_till_done()
assert len(turn_off_calls) == 1
assert len(turn_on_calls) == 0

hass.bus.async_fire("test_event_turn_on")
await hass.async_block_till_done()
assert len(turn_off_calls) == 1
assert len(turn_on_calls) == 1
@@ -0,0 +1,64 @@
"""Provides device automations for NEW_NAME."""
from typing import List
import voluptuous as vol

from homeassistant.const import (
ATTR_ENTITY_ID,
CONF_DOMAIN,
CONF_TYPE,
CONF_PLATFORM,
CONF_DEVICE_ID,
CONF_ENTITY_ID,
STATE_ON,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers import condition, entity_registry
from homeassistant.helpers.typing import ConfigType, TemplateVarsType
from homeassistant.helpers.config_validation import DEVICE_CONDITION_BASE_SCHEMA
from . import DOMAIN

# TODO specify your supported condition types.
CONDITION_TYPES = {"is_on"}

CONDITION_SCHEMA = DEVICE_CONDITION_BASE_SCHEMA.extend(
{vol.Required(CONF_TYPE): vol.In(CONDITION_TYPES)}
)


async def async_get_conditions(hass: HomeAssistant, device_id: str) -> List[str]:
"""List device conditions for NEW_NAME devices."""
registry = await entity_registry.async_get_registry(hass)
conditions = []

# 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 conditions for each entity that belongs to this integration
# TODO add your own conditions.
conditions.append(
{
CONF_PLATFORM: "device",
CONF_DEVICE_ID: device_id,
CONF_DOMAIN: DOMAIN,
CONF_ENTITY_ID: entry.entity_id,
CONF_TYPE: "is_on",
}
)

return conditions


def async_condition_from_config(
config: ConfigType, config_validation: bool
) -> condition.ConditionCheckerType:
"""Create a function to test a device condition."""
if config_validation:
config = CONDITION_SCHEMA(config)

def test_is_on(hass: HomeAssistant, variables: TemplateVarsType) -> bool:
"""Test if an entity is on."""
return condition.state(hass, config[ATTR_ENTITY_ID], STATE_ON)

return test_is_on