From 801168f9d723d67cbb2fe3e3af985c3bc0c1d7e3 Mon Sep 17 00:00:00 2001 From: Glenn Waters Date: Thu, 15 Oct 2020 18:05:07 -0400 Subject: [PATCH] Add ElkM1 time and counter services and keypress event (#41867) --- homeassistant/components/elkm1/__init__.py | 53 ++++++++++++++++---- homeassistant/components/elkm1/const.py | 6 +++ homeassistant/components/elkm1/manifest.json | 2 +- homeassistant/components/elkm1/sensor.py | 31 +++++++++++- homeassistant/components/elkm1/services.yaml | 24 +++++++++ requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 7 files changed, 105 insertions(+), 15 deletions(-) diff --git a/homeassistant/components/elkm1/__init__.py b/homeassistant/components/elkm1/__init__.py index e8e226f7882..146a9a21fb8 100644 --- a/homeassistant/components/elkm1/__init__.py +++ b/homeassistant/components/elkm1/__init__.py @@ -19,12 +19,16 @@ from homeassistant.const import ( TEMP_FAHRENHEIT, ) from homeassistant.core import HomeAssistant, callback -from homeassistant.exceptions import ConfigEntryNotReady +from homeassistant.exceptions import ConfigEntryNotReady, HomeAssistantError from homeassistant.helpers import config_validation as cv from homeassistant.helpers.entity import Entity from homeassistant.helpers.typing import ConfigType +import homeassistant.util.dt as dt_util from .const import ( + ATTR_KEY, + ATTR_KEY_NAME, + ATTR_KEYPAD_ID, BARE_TEMP_CELSIUS, BARE_TEMP_FAHRENHEIT, CONF_AREA, @@ -41,6 +45,7 @@ from .const import ( CONF_ZONE, DOMAIN, ELK_ELEMENTS, + EVENT_ELKM1_KEYPAD_KEY_PRESSED, ) SYNC_TIMEOUT = 120 @@ -63,6 +68,12 @@ SPEAK_SERVICE_SCHEMA = vol.Schema( } ) +SET_TIME_SERVICE_SCHEMA = vol.Schema( + { + vol.Optional("prefix", default=""): cv.string, + } +) + def _host_validator(config): """Validate that a host is properly configured.""" @@ -221,6 +232,23 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): ) elk.connect() + def _element_changed(element, changeset): + keypress = changeset.get("last_keypress") + if keypress is None: + return + + hass.bus.async_fire( + EVENT_ELKM1_KEYPAD_KEY_PRESSED, + { + ATTR_KEYPAD_ID: element.index + 1, + ATTR_KEY_NAME: keypress[0], + ATTR_KEY: keypress[1], + }, + ) + + for keypad in elk.keypads: # pylint: disable=no-member + keypad.add_callback(_element_changed) + if not await async_wait_for_elk_to_sync(elk, SYNC_TIMEOUT): _LOGGER.error( "Timed out after %d seconds while trying to sync with ElkM1 at %s", @@ -297,21 +325,21 @@ async def async_wait_for_elk_to_sync(elk, timeout): def _create_elk_services(hass): - def _speak_word_service(service): + def _getelk(service): prefix = service.data["prefix"] elk = _find_elk_by_prefix(hass, prefix) if elk is None: - _LOGGER.error("No elk m1 with prefix for speak_word: '%s'", prefix) - return - elk.panel.speak_word(service.data["number"]) + raise HomeAssistantError(f"No ElkM1 with prefix '{prefix}' found") + return elk + + def _speak_word_service(service): + _getelk(service).panel.speak_word(service.data["number"]) def _speak_phrase_service(service): - prefix = service.data["prefix"] - elk = _find_elk_by_prefix(hass, prefix) - if elk is None: - _LOGGER.error("No elk m1 with prefix for speak_phrase: '%s'", prefix) - return - elk.panel.speak_phrase(service.data["number"]) + _getelk(service).panel.speak_phrase(service.data["number"]) + + def _set_time_service(service): + _getelk(service).panel.set_time(dt_util.now()) hass.services.async_register( DOMAIN, "speak_word", _speak_word_service, SPEAK_SERVICE_SCHEMA @@ -319,6 +347,9 @@ def _create_elk_services(hass): hass.services.async_register( DOMAIN, "speak_phrase", _speak_phrase_service, SPEAK_SERVICE_SCHEMA ) + hass.services.async_register( + DOMAIN, "set_time", _set_time_service, SET_TIME_SERVICE_SCHEMA + ) def create_elk_entities(elk_data, elk_elements, element_type, class_, entities): diff --git a/homeassistant/components/elkm1/const.py b/homeassistant/components/elkm1/const.py index 91f51eefeef..71646582c99 100644 --- a/homeassistant/components/elkm1/const.py +++ b/homeassistant/components/elkm1/const.py @@ -36,10 +36,16 @@ ELK_ELEMENTS = { CONF_ZONE: Max.ZONES.value, } +EVENT_ELKM1_KEYPAD_KEY_PRESSED = "elkm1.keypad_key_pressed" + +ATTR_KEYPAD_ID = "keypad_id" +ATTR_KEY = "key" +ATTR_KEY_NAME = "key_name" ATTR_CHANGED_BY_KEYPAD = "changed_by_keypad" ATTR_CHANGED_BY_ID = "changed_by_id" ATTR_CHANGED_BY_TIME = "changed_by_time" +ATTR_VALUE = "value" ELK_USER_CODE_SERVICE_SCHEMA = { vol.Required(ATTR_CODE): vol.All(vol.Coerce(int), vol.Range(0, 999999)) diff --git a/homeassistant/components/elkm1/manifest.json b/homeassistant/components/elkm1/manifest.json index c7d4ca4ca2c..18e58101cbe 100644 --- a/homeassistant/components/elkm1/manifest.json +++ b/homeassistant/components/elkm1/manifest.json @@ -2,7 +2,7 @@ "domain": "elkm1", "name": "Elk-M1 Control", "documentation": "https://www.home-assistant.io/integrations/elkm1", - "requirements": ["elkm1-lib==0.8.2"], + "requirements": ["elkm1-lib==0.8.3"], "codeowners": ["@gwww", "@bdraco"], "config_flow": true } diff --git a/homeassistant/components/elkm1/sensor.py b/homeassistant/components/elkm1/sensor.py index c628105dc60..c6442af2e44 100644 --- a/homeassistant/components/elkm1/sensor.py +++ b/homeassistant/components/elkm1/sensor.py @@ -6,18 +6,25 @@ from elkm1_lib.const import ( ZoneType, ) from elkm1_lib.util import pretty_const, username +import voluptuous as vol from homeassistant.const import VOLT from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import entity_platform from . import ElkAttachedEntity, create_elk_entities -from .const import DOMAIN, ELK_USER_CODE_SERVICE_SCHEMA +from .const import ATTR_VALUE, DOMAIN, ELK_USER_CODE_SERVICE_SCHEMA +SERVICE_SENSOR_COUNTER_REFRESH = "sensor_counter_refresh" +SERVICE_SENSOR_COUNTER_SET = "sensor_counter_set" SERVICE_SENSOR_ZONE_BYPASS = "sensor_zone_bypass" SERVICE_SENSOR_ZONE_TRIGGER = "sensor_zone_trigger" UNDEFINED_TEMPATURE = -40 +ELK_SET_COUNTER_SERVICE_SCHEMA = { + vol.Required(ATTR_VALUE): vol.All(vol.Coerce(int), vol.Range(0, 65535)) +} + async def async_setup_entry(hass, config_entry, async_add_entities): """Create the Elk-M1 sensor platform.""" @@ -33,6 +40,16 @@ async def async_setup_entry(hass, config_entry, async_add_entities): platform = entity_platform.current_platform.get() + platform.async_register_entity_service( + SERVICE_SENSOR_COUNTER_REFRESH, + {}, + "async_counter_refresh", + ) + platform.async_register_entity_service( + SERVICE_SENSOR_COUNTER_SET, + ELK_SET_COUNTER_SERVICE_SCHEMA, + "async_counter_set", + ) platform.async_register_entity_service( SERVICE_SENSOR_ZONE_BYPASS, ELK_USER_CODE_SERVICE_SCHEMA, @@ -63,6 +80,18 @@ class ElkSensor(ElkAttachedEntity): """Return the state of the sensor.""" return self._state + async def async_counter_refresh(self): + """Refresh the value of a counter from the panel.""" + if not isinstance(self, ElkCounter): + raise HomeAssistantError("supported only on ElkM1 Counter sensors") + self._element.get() + + async def async_counter_set(self, value=None): + """Set the value of a counter on the panel.""" + if not isinstance(self, ElkCounter): + raise HomeAssistantError("supported only on ElkM1 Counter sensors") + self._element.set(value) + async def async_zone_bypass(self, code=None): """Bypass zone.""" if not isinstance(self, ElkZone): diff --git a/homeassistant/components/elkm1/services.yaml b/homeassistant/components/elkm1/services.yaml index 6d30fa5ae92..9f9fb2c39e5 100644 --- a/homeassistant/components/elkm1/services.yaml +++ b/homeassistant/components/elkm1/services.yaml @@ -70,6 +70,13 @@ alarm_display_message: description: Up to 16 characters of text (truncated if too long). Default blank. example: the universe, and everything. +set_time: + description: Set the time for the panel. + fields: + prefix: + description: Prefix for the panel. + example: gatehouse + speak_phrase: description: Speak a phrase. See list of phrases in ElkM1 ASCII Protocol documentation. fields: @@ -84,6 +91,23 @@ speak_word: description: Word number to speak. example: 142 +sensor_counter_refresh: + description: Refresh the value of a counter from the panel. + fields: + entity_id: + description: Name of counter to refresh. + example: "sensor.counting_sheep" + +sensor_counter_set: + description: Set the value of a counter on the panel. + fields: + entity_id: + description: Name of counter to set. + example: "sensor.test42" + value: + description: Value to set the counter to. + example: 4242 + sensor_zone_bypass: description: Bypass zone. fields: diff --git a/requirements_all.txt b/requirements_all.txt index a2398dbb345..cb5f6b40711 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -541,7 +541,7 @@ elgato==0.2.0 eliqonline==1.2.2 # homeassistant.components.elkm1 -elkm1-lib==0.8.2 +elkm1-lib==0.8.3 # homeassistant.components.mobile_app emoji==0.5.4 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 272dd722d9f..8671b0da992 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -281,7 +281,7 @@ eebrightbox==0.0.4 elgato==0.2.0 # homeassistant.components.elkm1 -elkm1-lib==0.8.2 +elkm1-lib==0.8.3 # homeassistant.components.mobile_app emoji==0.5.4