Add button to homeworks (#112269)

This commit is contained in:
Erik Montnemery 2024-03-05 19:10:13 +01:00 committed by GitHub
parent 8e2de51710
commit a515603aaf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 763 additions and 9 deletions

View file

@ -546,6 +546,7 @@ omit =
homeassistant/components/homematic/sensor.py
homeassistant/components/homematic/switch.py
homeassistant/components/homeworks/__init__.py
homeassistant/components/homeworks/button.py
homeassistant/components/homeworks/light.py
homeassistant/components/horizon/media_player.py
homeassistant/components/hp_ilo/sensor.py

View file

@ -36,7 +36,7 @@ from .const import (
_LOGGER = logging.getLogger(__name__)
PLATFORMS: list[Platform] = [Platform.LIGHT]
PLATFORMS: list[Platform] = [Platform.BUTTON, Platform.LIGHT]
EVENT_BUTTON_PRESS = "homeworks_button_press"
EVENT_BUTTON_RELEASE = "homeworks_button_release"
@ -186,6 +186,7 @@ class HomeworksEntity(Entity):
self._controller_id, self._addr, self._idx
)
self._controller = controller
self._attr_extra_state_attributes = {"homeworks_address": self._addr}
class HomeworksKeypad:

View file

@ -0,0 +1,78 @@
"""Support for Lutron Homeworks buttons."""
from __future__ import annotations
from time import sleep
from pyhomeworks.pyhomeworks import Homeworks
from homeassistant.components.button import ButtonEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_NAME
from homeassistant.core import HomeAssistant
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import HomeworksData, HomeworksEntity
from .const import (
CONF_ADDR,
CONF_BUTTONS,
CONF_CONTROLLER_ID,
CONF_KEYPADS,
CONF_NUMBER,
CONF_RELEASE_DELAY,
DOMAIN,
)
async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
"""Set up Homeworks buttons."""
data: HomeworksData = hass.data[DOMAIN][entry.entry_id]
controller = data.controller
controller_id = entry.options[CONF_CONTROLLER_ID]
devs = []
for keypad in entry.options.get(CONF_KEYPADS, []):
for button in keypad[CONF_BUTTONS]:
dev = HomeworksButton(
controller,
controller_id,
keypad[CONF_ADDR],
keypad[CONF_NAME],
button[CONF_NAME],
button[CONF_NUMBER],
button[CONF_RELEASE_DELAY],
)
devs.append(dev)
async_add_entities(devs, True)
class HomeworksButton(HomeworksEntity, ButtonEntity):
"""Homeworks Button."""
def __init__(
self,
controller: Homeworks,
controller_id: str,
addr: str,
keypad_name: str,
button_name: str,
button_number: int,
release_delay: float,
) -> None:
"""Create device with Addr, name, and rate."""
super().__init__(controller, controller_id, addr, button_number, button_name)
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, f"{controller_id}.{addr}")}, name=keypad_name
)
self._release_delay = release_delay
def press(self) -> None:
"""Press the button."""
# pylint: disable-next=protected-access
self._controller._send(f"KBP, {self._addr}, {self._idx}")
if not self._release_delay:
return
sleep(self._release_delay)
# pylint: disable-next=protected-access
self._controller._send(f"KBR, {self._addr}, {self._idx}")

View file

@ -8,6 +8,7 @@ from typing import Any
from pyhomeworks.pyhomeworks import Homeworks
import voluptuous as vol
from homeassistant.components.button import DOMAIN as BUTTON_DOMAIN
from homeassistant.components.light import DOMAIN as LIGHT_DOMAIN
from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult
from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PORT
@ -37,11 +38,15 @@ from homeassistant.util import slugify
from . import DEFAULT_FADE_RATE, calculate_unique_id
from .const import (
CONF_ADDR,
CONF_BUTTONS,
CONF_CONTROLLER_ID,
CONF_DIMMERS,
CONF_INDEX,
CONF_KEYPADS,
CONF_NUMBER,
CONF_RATE,
CONF_RELEASE_DELAY,
DEFAULT_BUTTON_NAME,
DEFAULT_KEYPAD_NAME,
DEFAULT_LIGHT_NAME,
DOMAIN,
@ -71,6 +76,18 @@ LIGHT_EDIT = {
),
}
BUTTON_EDIT = {
vol.Optional(CONF_RELEASE_DELAY, default=0): selector.NumberSelector(
selector.NumberSelectorConfig(
min=0,
max=5,
step=0.01,
mode=selector.NumberSelectorMode.BOX,
unit_of_measurement="s",
),
),
}
validate_addr = cv.matches_regex(r"\[\d\d:\d\d:\d\d:\d\d\]")
@ -160,6 +177,31 @@ def _validate_address(handler: SchemaCommonFlowHandler, addr: str) -> None:
raise SchemaFlowError("duplicated_addr")
def _validate_button_number(handler: SchemaCommonFlowHandler, number: int) -> None:
"""Validate button number."""
keypad = handler.flow_state["_idx"]
buttons: list[dict[str, Any]] = handler.options[CONF_KEYPADS][keypad][CONF_BUTTONS]
for button in buttons:
if button[CONF_NUMBER] == number:
raise SchemaFlowError("duplicated_number")
async def validate_add_button(
handler: SchemaCommonFlowHandler, user_input: dict[str, Any]
) -> dict[str, Any]:
"""Validate button input."""
user_input[CONF_NUMBER] = int(user_input[CONF_NUMBER])
_validate_button_number(handler, user_input[CONF_NUMBER])
# Standard behavior is to merge the result with the options.
# In this case, we want to add a sub-item so we update the options directly.
keypad = handler.flow_state["_idx"]
buttons: list[dict[str, Any]] = handler.options[CONF_KEYPADS][keypad][CONF_BUTTONS]
buttons.append(user_input)
return {}
async def validate_add_keypad(
handler: SchemaCommonFlowHandler, user_input: dict[str, Any]
) -> dict[str, Any]:
@ -169,7 +211,7 @@ async def validate_add_keypad(
# Standard behavior is to merge the result with the options.
# In this case, we want to add a sub-item so we update the options directly.
items = handler.options[CONF_KEYPADS]
items.append(user_input)
items.append(user_input | {CONF_BUTTONS: []})
return {}
@ -186,6 +228,37 @@ async def validate_add_light(
return {}
async def get_select_button_schema(handler: SchemaCommonFlowHandler) -> vol.Schema:
"""Return schema for selecting a button."""
keypad = handler.flow_state["_idx"]
buttons: list[dict[str, Any]] = handler.options[CONF_KEYPADS][keypad][CONF_BUTTONS]
return vol.Schema(
{
vol.Required(CONF_INDEX): vol.In(
{
str(index): f"{config[CONF_NAME]} ({config[CONF_NUMBER]})"
for index, config in enumerate(buttons)
},
)
}
)
async def get_select_keypad_schema(handler: SchemaCommonFlowHandler) -> vol.Schema:
"""Return schema for selecting a keypad."""
return vol.Schema(
{
vol.Required(CONF_INDEX): vol.In(
{
str(index): f"{config[CONF_NAME]} ({config[CONF_ADDR]})"
for index, config in enumerate(handler.options[CONF_KEYPADS])
},
)
}
)
async def get_select_light_schema(handler: SchemaCommonFlowHandler) -> vol.Schema:
"""Return schema for selecting a light."""
return vol.Schema(
@ -200,6 +273,14 @@ async def get_select_light_schema(handler: SchemaCommonFlowHandler) -> vol.Schem
)
async def validate_select_button(
handler: SchemaCommonFlowHandler, user_input: dict[str, Any]
) -> dict[str, Any]:
"""Store button index in flow state."""
handler.flow_state["_button_idx"] = int(user_input[CONF_INDEX])
return {}
async def validate_select_keypad_light(
handler: SchemaCommonFlowHandler, user_input: dict[str, Any]
) -> dict[str, Any]:
@ -208,6 +289,15 @@ async def validate_select_keypad_light(
return {}
async def get_edit_button_suggested_values(
handler: SchemaCommonFlowHandler,
) -> dict[str, Any]:
"""Return suggested values for button editing."""
keypad_idx: int = handler.flow_state["_idx"]
button_idx: int = handler.flow_state["_button_idx"]
return dict(handler.options[CONF_KEYPADS][keypad_idx][CONF_BUTTONS][button_idx])
async def get_edit_light_suggested_values(
handler: SchemaCommonFlowHandler,
) -> dict[str, Any]:
@ -216,6 +306,19 @@ async def get_edit_light_suggested_values(
return dict(handler.options[CONF_DIMMERS][idx])
async def validate_button_edit(
handler: SchemaCommonFlowHandler, user_input: dict[str, Any]
) -> dict[str, Any]:
"""Update edited keypad or light."""
# Standard behavior is to merge the result with the options.
# In this case, we want to add a sub-item so we update the options directly.
keypad_idx: int = handler.flow_state["_idx"]
button_idx: int = handler.flow_state["_button_idx"]
buttons: list[dict] = handler.options[CONF_KEYPADS][keypad_idx][CONF_BUTTONS]
buttons[button_idx].update(user_input)
return {}
async def validate_light_edit(
handler: SchemaCommonFlowHandler, user_input: dict[str, Any]
) -> dict[str, Any]:
@ -227,6 +330,22 @@ async def validate_light_edit(
return {}
async def get_remove_button_schema(handler: SchemaCommonFlowHandler) -> vol.Schema:
"""Return schema for button removal."""
keypad_idx: int = handler.flow_state["_idx"]
buttons: list[dict] = handler.options[CONF_KEYPADS][keypad_idx][CONF_BUTTONS]
return vol.Schema(
{
vol.Required(CONF_INDEX): cv.multi_select(
{
str(index): f"{config[CONF_NAME]} ({config[CONF_NUMBER]})"
for index, config in enumerate(buttons)
},
)
}
)
async def get_remove_keypad_light_schema(
handler: SchemaCommonFlowHandler, *, key: str
) -> vol.Schema:
@ -243,6 +362,37 @@ async def get_remove_keypad_light_schema(
)
async def validate_remove_button(
handler: SchemaCommonFlowHandler, user_input: dict[str, Any]
) -> dict[str, Any]:
"""Validate remove keypad or light."""
removed_indexes: set[str] = set(user_input[CONF_INDEX])
# Standard behavior is to merge the result with the options.
# In this case, we want to remove sub-items so we update the options directly.
entity_registry = er.async_get(handler.parent_handler.hass)
keypad_idx: int = handler.flow_state["_idx"]
keypad: dict = handler.options[CONF_KEYPADS][keypad_idx]
items: list[dict[str, Any]] = []
item: dict[str, Any]
for index, item in enumerate(keypad[CONF_BUTTONS]):
if str(index) not in removed_indexes:
items.append(item)
button_number = keypad[CONF_BUTTONS][index][CONF_NUMBER]
if entity_id := entity_registry.async_get_entity_id(
BUTTON_DOMAIN,
DOMAIN,
calculate_unique_id(
handler.options[CONF_CONTROLLER_ID],
keypad[CONF_ADDR],
button_number,
),
):
entity_registry.async_remove(entity_id)
keypad[CONF_BUTTONS] = items
return {}
async def validate_remove_keypad_light(
handler: SchemaCommonFlowHandler, user_input: dict[str, Any], *, key: str
) -> dict[str, Any]:
@ -292,12 +442,28 @@ DATA_SCHEMA_ADD_KEYPAD = vol.Schema(
vol.Required(CONF_ADDR): TextSelector(),
}
)
DATA_SCHEMA_ADD_BUTTON = vol.Schema(
{
vol.Optional(CONF_NAME, default=DEFAULT_BUTTON_NAME): TextSelector(),
vol.Required(CONF_NUMBER): selector.NumberSelector(
selector.NumberSelectorConfig(
min=1,
max=24,
step=1,
mode=selector.NumberSelectorMode.BOX,
),
),
**BUTTON_EDIT,
}
)
DATA_SCHEMA_EDIT_BUTTON = vol.Schema(BUTTON_EDIT)
DATA_SCHEMA_EDIT_LIGHT = vol.Schema(LIGHT_EDIT)
OPTIONS_FLOW = {
"init": SchemaFlowMenuStep(
[
"add_keypad",
"select_edit_keypad",
"remove_keypad",
"add_light",
"select_edit_light",
@ -309,6 +475,40 @@ OPTIONS_FLOW = {
suggested_values=None,
validate_user_input=validate_add_keypad,
),
"select_edit_keypad": SchemaFlowFormStep(
get_select_keypad_schema,
suggested_values=None,
validate_user_input=validate_select_keypad_light,
next_step="edit_keypad",
),
"edit_keypad": SchemaFlowMenuStep(
[
"add_button",
"select_edit_button",
"remove_button",
]
),
"add_button": SchemaFlowFormStep(
DATA_SCHEMA_ADD_BUTTON,
suggested_values=None,
validate_user_input=validate_add_button,
),
"select_edit_button": SchemaFlowFormStep(
get_select_button_schema,
suggested_values=None,
validate_user_input=validate_select_button,
next_step="edit_button",
),
"edit_button": SchemaFlowFormStep(
DATA_SCHEMA_EDIT_BUTTON,
suggested_values=get_edit_button_suggested_values,
validate_user_input=validate_button_edit,
),
"remove_button": SchemaFlowFormStep(
get_remove_button_schema,
suggested_values=None,
validate_user_input=validate_remove_button,
),
"remove_keypad": SchemaFlowFormStep(
partial(get_remove_keypad_light_schema, key=CONF_KEYPADS),
suggested_values=None,
@ -359,6 +559,14 @@ class HomeworksConfigFlowHandler(ConfigFlow, domain=DOMAIN):
CONF_KEYPADS: [
{
CONF_ADDR: keypad[CONF_ADDR],
CONF_BUTTONS: [
{
CONF_NAME: button[CONF_NAME],
CONF_NUMBER: button[CONF_NUMBER],
CONF_RELEASE_DELAY: button[CONF_RELEASE_DELAY],
}
for button in keypad[CONF_BUTTONS]
],
CONF_NAME: keypad[CONF_NAME],
}
for keypad in config[CONF_KEYPADS]

View file

@ -4,11 +4,14 @@ from __future__ import annotations
DOMAIN = "homeworks"
CONF_ADDR = "addr"
CONF_BUTTONS = "buttons"
CONF_CONTROLLER_ID = "controller_id"
CONF_DIMMERS = "dimmers"
CONF_INDEX = "index"
CONF_KEYPADS = "keypads"
CONF_NUMBER = "number"
CONF_RATE = "rate"
CONF_RELEASE_DELAY = "release_delay"
DEFAULT_BUTTON_NAME = "Homeworks button"
DEFAULT_KEYPAD_NAME = "Homeworks keypad"

View file

@ -97,11 +97,6 @@ class HomeworksLight(HomeworksEntity, LightEntity):
float((level * 100.0) / 255.0), self._rate, 0, self._addr
)
@property
def extra_state_attributes(self) -> dict[str, str]:
"""Supported attributes."""
return {"homeworks_address": self._addr}
@property
def is_on(self) -> bool:
"""Is the light on/off."""

View file

@ -31,5 +31,105 @@
"description": "Add a Lutron Homeworks controller"
}
}
},
"options": {
"error": {
"duplicated_addr": "The specified address is already in use",
"duplicated_number": "The specified number is already in use",
"invalid_addr": "Invalid address"
},
"step": {
"init": {
"menu_options": {
"add_keypad": "Add keypad",
"add_light": "Add light",
"remove_keypad": "Remove keypad",
"remove_light": "Remove light",
"select_edit_keypad": "Configure keypad",
"select_edit_light": "Configure light"
}
},
"add_button": {
"data": {
"name": "[%key:common::config_flow::data::name%]",
"number": "Number",
"release_delay": "Release delay"
},
"data_description": {
"number": "Button number in the range 1 to 24",
"release_delay": "Time between press and release, set to 0 to only press"
},
"title": "[%key:component::homeworks::options::step::init::menu_options::add_keypad%]"
},
"add_keypad": {
"data": {
"name": "[%key:common::config_flow::data::name%]",
"addr": "Address"
},
"data_description": {
"addr": "Keypad address, must be formatted as `[##:##:##:##]`"
},
"title": "[%key:component::homeworks::options::step::init::menu_options::add_keypad%]"
},
"add_light": {
"data": {
"name": "[%key:common::config_flow::data::name%]",
"addr": "[%key:component::homeworks::options::step::add_keypad::data::addr%]",
"rate": "Fade rate"
},
"data_description": {
"addr": "Keypad address, must be formatted as `[##:##:##:##]`",
"rate": "Time in seconds for the light to transition to a new brightness level"
},
"title": "[%key:component::homeworks::options::step::init::menu_options::add_light%]"
},
"edit_button": {
"data": {
"release_delay": "[%key:component::homeworks::options::step::add_button::data::release_delay%]"
},
"data_description": {
"release_delay": "[%key:component::homeworks::options::step::add_button::data_description::release_delay%]"
},
"title": "[%key:component::homeworks::options::step::edit_keypad::menu_options::select_edit_button%]"
},
"edit_keypad": {
"menu_options": {
"add_button": "Add button",
"remove_button": "Remove button",
"select_edit_button": "Configure button"
}
},
"edit_light": {
"data": {
"rate": "[%key:component::homeworks::options::step::add_light::data::rate%]"
},
"data_description": {
"rate": "[%key:component::homeworks::options::step::add_light::data_description::rate%]"
},
"description": "Select a light to configure",
"title": "[%key:component::homeworks::options::step::init::menu_options::select_edit_light%]"
},
"remove_button": {
"description": "Select buttons to remove",
"title": "[%key:component::homeworks::options::step::edit_keypad::menu_options::remove_button%]"
},
"remove_keypad": {
"description": "Select keypads to remove",
"title": "[%key:component::homeworks::options::step::init::menu_options::remove_keypad%]"
},
"remove_light": {
"description": "Select lights to remove",
"title": "[%key:component::homeworks::options::step::init::menu_options::remove_light%]"
},
"select_edit_button": {
"title": "[%key:component::homeworks::options::step::edit_keypad::menu_options::select_edit_button%]"
},
"select_edit_keypad": {
"title": "[%key:component::homeworks::options::step::init::menu_options::select_edit_keypad%]"
},
"select_edit_light": {
"title": "[%key:component::homeworks::options::step::init::menu_options::select_edit_light%]"
}
}
}
}

View file

@ -6,10 +6,13 @@ import pytest
from homeassistant.components.homeworks.const import (
CONF_ADDR,
CONF_BUTTONS,
CONF_CONTROLLER_ID,
CONF_DIMMERS,
CONF_KEYPADS,
CONF_NUMBER,
CONF_RATE,
CONF_RELEASE_DELAY,
DOMAIN,
)
from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PORT
@ -39,6 +42,23 @@ def mock_config_entry() -> MockConfigEntry:
{
CONF_ADDR: "[02:08:02:01]",
CONF_NAME: "Foyer Keypad",
CONF_BUTTONS: [
{
CONF_NAME: "Morning",
CONF_NUMBER: 1,
CONF_RELEASE_DELAY: None,
},
{
CONF_NAME: "Relax",
CONF_NUMBER: 2,
CONF_RELEASE_DELAY: None,
},
{
CONF_NAME: "Dim up",
CONF_NUMBER: 3,
CONF_RELEASE_DELAY: 0.2,
},
],
}
],
},

View file

@ -4,12 +4,16 @@ from unittest.mock import ANY, MagicMock
import pytest
from pytest_unordered import unordered
from homeassistant.components.button import DOMAIN as BUTTON_DOMAIN
from homeassistant.components.homeworks.const import (
CONF_ADDR,
CONF_BUTTONS,
CONF_DIMMERS,
CONF_INDEX,
CONF_KEYPADS,
CONF_NUMBER,
CONF_RATE,
CONF_RELEASE_DELAY,
DOMAIN,
)
from homeassistant.components.light import DOMAIN as LIGHT_DOMAIN
@ -154,6 +158,23 @@ async def test_import_flow(
{
CONF_ADDR: "[02:08:02:01]",
CONF_NAME: "Foyer Keypad",
CONF_BUTTONS: [
{
CONF_NAME: "Morning",
CONF_NUMBER: 1,
CONF_RELEASE_DELAY: None,
},
{
CONF_NAME: "Relax",
CONF_NUMBER: 2,
CONF_RELEASE_DELAY: None,
},
{
CONF_NAME: "Dim up",
CONF_NUMBER: 3,
CONF_RELEASE_DELAY: 0.2,
},
],
}
],
},
@ -180,6 +201,15 @@ async def test_import_flow(
"keypads": [
{
"addr": "[02:08:02:01]",
"buttons": [
{
"name": "Morning",
"number": 1,
"release_delay": None,
},
{"name": "Relax", "number": 2, "release_delay": None},
{"name": "Dim up", "number": 3, "release_delay": 0.2},
],
"name": "Foyer Keypad",
}
],
@ -320,6 +350,15 @@ async def test_options_add_remove_light_flow(
"keypads": [
{
"addr": "[02:08:02:01]",
"buttons": [
{
"name": "Morning",
"number": 1,
"release_delay": None,
},
{"name": "Relax", "number": 2, "release_delay": None},
{"name": "Dim up", "number": 3, "release_delay": 0.2},
],
"name": "Foyer Keypad",
}
],
@ -362,6 +401,15 @@ async def test_options_add_remove_light_flow(
"keypads": [
{
"addr": "[02:08:02:01]",
"buttons": [
{
"name": "Morning",
"number": 1,
"release_delay": None,
},
{"name": "Relax", "number": 2, "release_delay": None},
{"name": "Dim up", "number": 3, "release_delay": 0.2},
],
"name": "Foyer Keypad",
}
],
@ -412,9 +460,18 @@ async def test_options_add_remove_keypad_flow(
"keypads": [
{
"addr": "[02:08:02:01]",
"buttons": [
{
"name": "Morning",
"number": 1,
"release_delay": None,
},
{"name": "Relax", "number": 2, "release_delay": None},
{"name": "Dim up", "number": 3, "release_delay": 0.2},
],
"name": "Foyer Keypad",
},
{"addr": "[02:08:03:01]", "name": "Hall Keypad"},
{"addr": "[02:08:03:01]", "buttons": [], "name": "Hall Keypad"},
],
"port": 1234,
}
@ -447,7 +504,7 @@ async def test_options_add_remove_keypad_flow(
{"addr": "[02:08:01:01]", "name": "Foyer Sconces", "rate": 1.0},
],
"host": "192.168.0.1",
"keypads": [{"addr": "[02:08:03:01]", "name": "Hall Keypad"}],
"keypads": [{"addr": "[02:08:03:01]", "buttons": [], "name": "Hall Keypad"}],
"port": 1234,
}
await hass.async_block_till_done()
@ -551,6 +608,15 @@ async def test_options_edit_light_no_lights_flow(
"keypads": [
{
"addr": "[02:08:02:01]",
"buttons": [
{
"name": "Morning",
"number": 1,
"release_delay": None,
},
{"name": "Relax", "number": 2, "release_delay": None},
{"name": "Dim up", "number": 3, "release_delay": 0.2},
],
"name": "Foyer Keypad",
}
],
@ -586,3 +652,285 @@ async def test_options_edit_light_flow_empty(
assert result["type"] == FlowResultType.FORM
assert result["step_id"] == "select_edit_light"
assert result["data_schema"].schema["index"].container == {}
async def test_options_add_button_flow(
hass: HomeAssistant, mock_config_entry: MockConfigEntry, mock_homeworks: MagicMock
) -> None:
"""Test options flow to add a button."""
mock_config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
assert len(hass.states.async_entity_ids(BUTTON_DOMAIN)) == 3
result = await hass.config_entries.options.async_init(mock_config_entry.entry_id)
assert result["type"] == FlowResultType.MENU
assert result["step_id"] == "init"
result = await hass.config_entries.options.async_configure(
result["flow_id"], {"next_step_id": "select_edit_keypad"}
)
assert result["type"] == FlowResultType.FORM
assert result["step_id"] == "select_edit_keypad"
assert result["data_schema"].schema["index"].container == {
"0": "Foyer Keypad ([02:08:02:01])"
}
result = await hass.config_entries.options.async_configure(
result["flow_id"],
{"index": "0"},
)
assert result["type"] == FlowResultType.MENU
assert result["step_id"] == "edit_keypad"
result = await hass.config_entries.options.async_configure(
result["flow_id"], {"next_step_id": "add_button"}
)
assert result["type"] == FlowResultType.FORM
assert result["step_id"] == "add_button"
result = await hass.config_entries.options.async_configure(
result["flow_id"],
user_input={
CONF_NAME: "Dim down",
CONF_NUMBER: 4,
CONF_RELEASE_DELAY: 0.2,
},
)
await hass.async_block_till_done()
assert result["type"] == FlowResultType.CREATE_ENTRY
assert result["data"] == {
"controller_id": "main_controller",
"dimmers": [{"addr": "[02:08:01:01]", "name": "Foyer Sconces", "rate": 1.0}],
"host": "192.168.0.1",
"keypads": [
{
"addr": "[02:08:02:01]",
"buttons": [
{
"name": "Morning",
"number": 1,
"release_delay": None,
},
{"name": "Relax", "number": 2, "release_delay": None},
{"name": "Dim up", "number": 3, "release_delay": 0.2},
{
"name": "Dim down",
"number": 4,
"release_delay": 0.2,
},
],
"name": "Foyer Keypad",
}
],
"port": 1234,
}
await hass.async_block_till_done()
# Check the new entities were added
assert len(hass.states.async_entity_ids(BUTTON_DOMAIN)) == 4
async def test_options_add_button_flow_duplicate(
hass: HomeAssistant, mock_config_entry: MockConfigEntry, mock_homeworks: MagicMock
) -> None:
"""Test options flow to add a button."""
mock_config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
assert len(hass.states.async_entity_ids(BUTTON_DOMAIN)) == 3
result = await hass.config_entries.options.async_init(mock_config_entry.entry_id)
assert result["type"] == FlowResultType.MENU
assert result["step_id"] == "init"
result = await hass.config_entries.options.async_configure(
result["flow_id"], {"next_step_id": "select_edit_keypad"}
)
assert result["type"] == FlowResultType.FORM
assert result["step_id"] == "select_edit_keypad"
assert result["data_schema"].schema["index"].container == {
"0": "Foyer Keypad ([02:08:02:01])"
}
result = await hass.config_entries.options.async_configure(
result["flow_id"],
{"index": "0"},
)
assert result["type"] == FlowResultType.MENU
assert result["step_id"] == "edit_keypad"
result = await hass.config_entries.options.async_configure(
result["flow_id"], {"next_step_id": "add_button"}
)
assert result["type"] == FlowResultType.FORM
assert result["step_id"] == "add_button"
result = await hass.config_entries.options.async_configure(
result["flow_id"],
user_input={
CONF_NAME: "Dim down",
CONF_NUMBER: 1,
CONF_RELEASE_DELAY: 0.2,
},
)
assert result["type"] == FlowResultType.FORM
assert result["errors"] == {"base": "duplicated_number"}
async def test_options_edit_button_flow(
hass: HomeAssistant, mock_config_entry: MockConfigEntry, mock_homeworks: MagicMock
) -> None:
"""Test options flow to add a button."""
mock_config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
assert len(hass.states.async_entity_ids(BUTTON_DOMAIN)) == 3
result = await hass.config_entries.options.async_init(mock_config_entry.entry_id)
assert result["type"] == FlowResultType.MENU
assert result["step_id"] == "init"
result = await hass.config_entries.options.async_configure(
result["flow_id"], {"next_step_id": "select_edit_keypad"}
)
assert result["type"] == FlowResultType.FORM
assert result["step_id"] == "select_edit_keypad"
assert result["data_schema"].schema["index"].container == {
"0": "Foyer Keypad ([02:08:02:01])"
}
result = await hass.config_entries.options.async_configure(
result["flow_id"],
{"index": "0"},
)
assert result["type"] == FlowResultType.MENU
assert result["step_id"] == "edit_keypad"
result = await hass.config_entries.options.async_configure(
result["flow_id"], {"next_step_id": "select_edit_button"}
)
assert result["type"] == FlowResultType.FORM
assert result["step_id"] == "select_edit_button"
assert result["data_schema"].schema["index"].container == {
"0": "Morning (1)",
"1": "Relax (2)",
"2": "Dim up (3)",
}
result = await hass.config_entries.options.async_configure(
result["flow_id"],
{"index": "0"},
)
assert result["type"] == FlowResultType.FORM
assert result["step_id"] == "edit_button"
result = await hass.config_entries.options.async_configure(
result["flow_id"],
user_input={
CONF_RELEASE_DELAY: 0,
},
)
await hass.async_block_till_done()
assert result["type"] == FlowResultType.CREATE_ENTRY
assert result["data"] == {
"controller_id": "main_controller",
"dimmers": [{"addr": "[02:08:01:01]", "name": "Foyer Sconces", "rate": 1.0}],
"host": "192.168.0.1",
"keypads": [
{
"addr": "[02:08:02:01]",
"buttons": [
{
"name": "Morning",
"number": 1,
"release_delay": 0.0,
},
{"name": "Relax", "number": 2, "release_delay": None},
{"name": "Dim up", "number": 3, "release_delay": 0.2},
],
"name": "Foyer Keypad",
}
],
"port": 1234,
}
await hass.async_block_till_done()
# Check the new entities were added
assert len(hass.states.async_entity_ids(BUTTON_DOMAIN)) == 3
async def test_options_remove_button_flow(
hass: HomeAssistant, mock_config_entry: MockConfigEntry, mock_homeworks: MagicMock
) -> None:
"""Test options flow to remove a button."""
mock_config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
assert len(hass.states.async_entity_ids(BUTTON_DOMAIN)) == 3
result = await hass.config_entries.options.async_init(mock_config_entry.entry_id)
assert result["type"] == FlowResultType.MENU
assert result["step_id"] == "init"
result = await hass.config_entries.options.async_configure(
result["flow_id"], {"next_step_id": "select_edit_keypad"}
)
assert result["type"] == FlowResultType.FORM
assert result["step_id"] == "select_edit_keypad"
assert result["data_schema"].schema["index"].container == {
"0": "Foyer Keypad ([02:08:02:01])"
}
result = await hass.config_entries.options.async_configure(
result["flow_id"],
{"index": "0"},
)
assert result["type"] == FlowResultType.MENU
assert result["step_id"] == "edit_keypad"
result = await hass.config_entries.options.async_configure(
result["flow_id"], {"next_step_id": "remove_button"}
)
assert result["type"] == FlowResultType.FORM
assert result["step_id"] == "remove_button"
assert result["data_schema"].schema["index"].options == {
"0": "Morning (1)",
"1": "Relax (2)",
"2": "Dim up (3)",
}
result = await hass.config_entries.options.async_configure(
result["flow_id"], user_input={CONF_INDEX: ["0"]}
)
assert result["type"] == FlowResultType.CREATE_ENTRY
assert result["data"] == {
"controller_id": "main_controller",
"dimmers": [{"addr": "[02:08:01:01]", "name": "Foyer Sconces", "rate": 1.0}],
"host": "192.168.0.1",
"keypads": [
{
"addr": "[02:08:02:01]",
"buttons": [
{"name": "Relax", "number": 2, "release_delay": None},
{"name": "Dim up", "number": 3, "release_delay": 0.2},
],
"name": "Foyer Keypad",
}
],
"port": 1234,
}
await hass.async_block_till_done()
# Check the entities were removed
assert len(hass.states.async_entity_ids(BUTTON_DOMAIN)) == 2