1 """Support for Lutron Homeworks Series 4 and 8 systems."""
3 from __future__
import annotations
6 from collections.abc
import Mapping
7 from dataclasses
import dataclass
11 from pyhomeworks
import exceptions
as hw_exceptions
12 from pyhomeworks.pyhomeworks
import (
18 import voluptuous
as vol
28 EVENT_HOMEASSISTANT_STOP,
39 from .const
import CONF_ADDR, CONF_CONTROLLER_ID, CONF_KEYPADS, DOMAIN
41 _LOGGER = logging.getLogger(__name__)
43 PLATFORMS: list[Platform] = [Platform.BINARY_SENSOR, Platform.BUTTON, Platform.LIGHT]
45 CONF_COMMAND =
"command"
47 EVENT_BUTTON_PRESS =
"homeworks_button_press"
48 EVENT_BUTTON_RELEASE =
"homeworks_button_release"
50 KEYPAD_LEDSTATE_POLL_COOLDOWN = 1.0
52 CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN)
54 SERVICE_SEND_COMMAND_SCHEMA = vol.Schema(
56 vol.Required(CONF_CONTROLLER_ID): str,
57 vol.Required(CONF_COMMAND): vol.All(cv.ensure_list, [str]),
64 """Container for config entry data."""
68 keypads: dict[str, HomeworksKeypad]
73 """Set up services for Lutron Homeworks Series 4 and 8 integration."""
75 async
def async_call_service(service_call: ServiceCall) ->
None:
76 """Call the service."""
79 hass.services.async_register(
83 schema=SERVICE_SEND_COMMAND_SCHEMA,
88 """Send command to a controller."""
90 def get_controller_ids() -> list[str]:
91 """Get homeworks data for the specified controller ID."""
92 return [data.controller_id
for data
in hass.data[DOMAIN].values()]
94 def get_homeworks_data(controller_id: str) -> HomeworksData |
None:
95 """Get homeworks data for the specified controller ID."""
97 for data
in hass.data[DOMAIN].values():
98 if data.controller_id == controller_id:
102 homeworks_data = get_homeworks_data(data[CONF_CONTROLLER_ID])
103 if not homeworks_data:
105 translation_domain=DOMAIN,
106 translation_key=
"invalid_controller_id",
107 translation_placeholders={
108 "controller_id": data[CONF_CONTROLLER_ID],
109 "controller_ids":
",".join(get_controller_ids()),
113 commands = data[CONF_COMMAND]
114 _LOGGER.debug(
"Send commands: %s", commands)
115 for command
in commands:
116 if command.lower().startswith(
"delay"):
117 delay =
int(command.partition(
" ")[2])
118 _LOGGER.debug(
"Sleeping for %s ms", delay)
119 await asyncio.sleep(delay / 1000)
121 _LOGGER.debug(
"Sending command '%s'", command)
122 await hass.async_add_executor_job(
123 homeworks_data.controller._send,
128 async
def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
129 """Start Homeworks controller."""
136 """Set up Homeworks from a config entry."""
138 hass.data.setdefault(DOMAIN, {})
139 controller_id = entry.options[CONF_CONTROLLER_ID]
141 def hw_callback(msg_type: Any, values: Any) ->
None:
142 """Dispatch state changes."""
143 _LOGGER.debug(
"callback: %s, %s", msg_type, values)
144 if msg_type == HW_LOGIN_INCORRECT:
145 _LOGGER.debug(
"login incorrect")
148 signal = f
"homeworks_entity_{controller_id}_{addr}"
151 config = entry.options
152 controller = Homeworks(
156 entry.data.get(CONF_USERNAME),
157 entry.data.get(CONF_PASSWORD),
160 await hass.async_add_executor_job(controller.connect)
161 except hw_exceptions.HomeworksException
as err:
162 _LOGGER.debug(
"Failed to connect: %s", err, exc_info=
True)
163 raise ConfigEntryNotReady
from err
166 def cleanup(event: Event) ->
None:
169 entry.async_on_unload(hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, cleanup))
171 keypads: dict[str, HomeworksKeypad] = {}
172 for key_config
in config.get(CONF_KEYPADS, []):
173 addr = key_config[CONF_ADDR]
174 name = key_config[CONF_NAME]
175 keypads[addr] =
HomeworksKeypad(hass, controller, controller_id, addr, name)
178 controller, controller_id, keypads
181 await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
182 entry.async_on_unload(entry.add_update_listener(update_listener))
188 """Unload a config entry."""
189 if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
190 data: HomeworksData = hass.data[DOMAIN].pop(entry.entry_id)
191 for keypad
in data.keypads.values():
194 await hass.async_add_executor_job(data.controller.stop)
200 """Handle options update."""
201 await hass.config_entries.async_reload(entry.entry_id)
205 """When you want signals instead of entities.
207 Stateless sensors such as keypads are expected to generate an event
208 instead of a sensor entity in hass.
214 controller: Homeworks,
219 """Register callback that will be used for signals."""
225 cooldown=KEYPAD_LEDSTATE_POLL_COOLDOWN,
232 signal = f
"homeworks_entity_{controller_id}_{self._addr}"
233 _LOGGER.debug(
"connecting %s", signal)
240 """Fire events if button is pressed or released."""
242 if msg_type == HW_BUTTON_PRESSED:
243 event = EVENT_BUTTON_PRESS
244 elif msg_type == HW_BUTTON_RELEASED:
245 event = EVENT_BUTTON_RELEASE
248 data = {CONF_ID: self.
_id_id, CONF_NAME: self.
_name_name,
"button": values[1]}
249 self.
_hass_hass.bus.async_fire(event, data)
252 """Query keypad led state."""
253 self.
_controller_controller._send(f
"RKLS, {self._addr}")
256 """Query keypad led state.
258 Debounced to not storm the controller during setup.
None request_keypad_led_states(self)
None _update_callback(self, str msg_type, list[Any] values)
None _request_keypad_led_states(self)
None __init__(self, HomeAssistant hass, Homeworks controller, str controller_id, str addr, str name)
bool async_setup(HomeAssistant hass, ConfigType config)
bool async_unload_entry(HomeAssistant hass, ConfigEntry entry)
None async_setup_services(HomeAssistant hass)
bool async_setup_entry(HomeAssistant hass, ConfigEntry entry)
None async_send_command(HomeAssistant hass, Mapping[str, Any] data)
None update_listener(HomeAssistant hass, ConfigEntry entry)
Callable[[], None] async_dispatcher_connect(HomeAssistant hass, str signal, Callable[..., Any] target)
None dispatcher_send(HomeAssistant hass, str signal, *Any args)
str slugify(str|None text, *str separator="_")