diff --git a/custom_components/lock-manager/__init__.py b/custom_components/lock-manager/__init__.py index 601b550..227982a 100644 --- a/custom_components/lock-manager/__init__.py +++ b/custom_components/lock-manager/__init__.py @@ -6,6 +6,11 @@ import logging import os from .const import ( + ATTR_NAME, + ATTR_CODE_SLOT, + ATTR_ENTITY_ID, + ATTR_NODE_ID, + ATTR_USER_CODE, CONF_ALARM_LEVEL, CONF_ALARM_TYPE, CONF_ENTITY_ID, @@ -25,8 +30,15 @@ _LOGGER = logging.getLogger(__name__) -ATTR_NAME = "lockname" SERVICE_GENERATE_PACKAGE = "generate_package" +SERVICE_ADD_CODE = "add_code" +SERVICE_CLEAR_CODE = "clear_code" + +OZW_DOMAIN = "ozw" +ZWAVE_DOMAIN = "lock" + +SET_USERCODE = "set_usercode" +CLEAR_USERCODE = "clear_usercode" async def async_setup(hass, config_entry): @@ -56,6 +68,100 @@ async def async_setup_entry(hass, config_entry): config_entry.options = config_entry.data config_entry.add_update_listener(update_listener) + async def _add_code(service): + """Generate the package files""" + _LOGGER.debug("Add Code service: %s", service) + entity_id = service.data[ATTR_ENTITY_ID] + code_slot = service.data[ATTR_CODE_SLOT] + usercode = service.data[ATTR_USER_CODE] + using_ozw = config_entry.options[CONF_OZW] + data = None + + # Pull the node_id from the entity + test = hass.states.get(entity_id) + if test is not None: + data = test.attributes[ATTR_NODE_ID] + + # Bail out if no node_id could be extracted + if data is None: + _LOGGER.error("Problem pulling node_id from entity.") + return + + servicedata = { + ATTR_ENTITY_ID: entity_id, + ATTR_CODE_SLOT: code_slot, + ATTR_USER_CODE: usercode, + } + + _LOGGER.debug("Attempting to call set_usercode...") + + if using_ozw: + try: + await hass.services.async_call(OZW_DOMAIN, SET_USERCODE, servicedata) + except Exception as err: + _LOGGER.error( + "Error calling ozw.set_usercode service call: %s", str(err) + ) + pass + + else: + try: + await hass.services.async_call(ZWAVE_DOMAIN, SET_USERCODE, servicedata) + except Exception as err: + _LOGGER.error( + "Error calling lock.set_usercode service call: %s", str(err) + ) + pass + + _LOGGER.debug("Add code call completed.") + + async def _clear_code(service): + """Generate the package files""" + _LOGGER.debug("Clear Code service: %s", service) + entity_id = service.data[ATTR_ENTITY_ID] + code_slot = service.data[ATTR_CODE_SLOT] + using_ozw = config_entry.options[CONF_OZW] + data = None + + # Pull the node_id from the entity + test = hass.states.get(entity_id) + if test is not None: + data = test.attributes[ATTR_NODE_ID] + + # Bail out if no node_id could be extracted + if data is None: + _LOGGER.error("Problem pulling node_id from entity.") + return + + servicedata = { + ATTR_ENTITY_ID: entity_id, + ATTR_CODE_SLOT: code_slot, + } + + _LOGGER.debug("Attempting to call clear_usercode...") + + if using_ozw: + try: + await hass.services.async_call(OZW_DOMAIN, CLEAR_USERCODE, servicedata) + except Exception as err: + _LOGGER.error( + "Error calling ozw.clear_usercode service call: %s", str(err) + ) + pass + + else: + try: + await hass.services.async_call( + ZWAVE_DOMAIN, CLEAR_USERCODE, servicedata + ) + except Exception as err: + _LOGGER.error( + "Error calling lock.clear_usercode service call: %s", str(err) + ) + pass + + _LOGGER.debug("Clear code call completed.") + async def _generate_package(service): """Generate the package files""" _LOGGER.debug("DEBUG: %s", service) @@ -195,6 +301,33 @@ async def _generate_package(service): schema=vol.Schema({vol.Optional(ATTR_NAME): vol.Coerce(str)}), ) + # Add code + hass.services.async_register( + DOMAIN, + SERVICE_ADD_CODE, + _add_code, + schema=vol.Schema( + { + vol.Required(ATTR_ENTITY_ID): vol.Coerce(str), + vol.Required(ATTR_CODE_SLOT): vol.Coerce(int), + vol.Required(ATTR_USER_CODE): vol.Coerce(str), + } + ), + ) + + # Clear code + hass.services.async_register( + DOMAIN, + SERVICE_CLEAR_CODE, + _clear_code, + schema=vol.Schema( + { + vol.Required(ATTR_NODE_ID): vol.Coerce(str), + vol.Required(ATTR_CODE_SLOT): vol.Coerce(int), + } + ), + ) + # Load the code slot sensors if OZW is enabled if config_entry.options[CONF_OZW]: hass.async_create_task( diff --git a/custom_components/lock-manager/const.py b/custom_components/lock-manager/const.py index 73a13f5..35dd0c7 100644 --- a/custom_components/lock-manager/const.py +++ b/custom_components/lock-manager/const.py @@ -1,7 +1,15 @@ DOMAIN = "lock-manager" -VERSION = "0.0.31" +VERSION = "0.0.32" ISSUE_URL = "https://github.com/FutureTense/lock-manager" PLATFORM = "sensor" +ZWAVE_NETWORK = "zwave_network" + +# Attributes +ATTR_NAME = "lockname" +ATTR_CODE_SLOT = "code_slot" +ATTR_USER_CODE = "usercode" +ATTR_ENTITY_ID = "entity_id" +ATTR_NODE_ID = "node_id" # Configuration Properties CONF_ALARM_LEVEL = "alarm_level" diff --git a/custom_components/lock-manager/sensor.py b/custom_components/lock-manager/sensor.py index 9223c19..5cfa4e8 100644 --- a/custom_components/lock-manager/sensor.py +++ b/custom_components/lock-manager/sensor.py @@ -1,9 +1,9 @@ """ Sensor for lock-manager """ -from .const import CONF_ENTITY_ID, CONF_SLOTS, CONF_LOCK_NAME +from .const import CONF_ENTITY_ID, CONF_SLOTS, CONF_LOCK_NAME, ZWAVE_NETWORK from datetime import timedelta from homeassistant.components.ozw import DOMAIN as OZW_DOMAIN -from openzwavemqtt.const import CommandClass, ValueIndex +from openzwavemqtt.const import CommandClass from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle import logging @@ -12,6 +12,7 @@ MANAGER = "manager" ATTR_VALUES = "values" ATTR_NODE_ID = "node_id" +COMMAND_CLASS_USER_CODE = 99 _LOGGER = logging.getLogger(__name__) @@ -45,7 +46,7 @@ def __init__(self, hass, config): self._entity_id = config.get(CONF_ENTITY_ID) self._data = None - self.update = Throttle(timedelta(seconds=10))(self.update) + self.update = Throttle(timedelta(seconds=5))(self.update) def update(self): """Get the latest data""" @@ -56,7 +57,7 @@ def update(self): # data["node_id"] = _get_node_id(self._hass, self._entity_id) data[ATTR_NODE_ID] = self._get_node_id() - # only pull the codes for ozw + # pull the codes for ozw if OZW_DOMAIN in self._hass.data: if data[ATTR_NODE_ID] is not None: manager = self._hass.data[OZW_DOMAIN][MANAGER] @@ -67,11 +68,44 @@ def update(self): ) for value in lock_values: if value.command_class == CommandClass.USER_CODE: + _LOGGER.debug( + "DEBUG: code_slot_%s value: %s", + str(value.index), + str(value.value), + ) + # do not update if the code contains *s + if "*" in str(value.value): + _LOGGER.debug("DEBUG: Ignoring code slot with * in value.") + continue sensor_name = f"code_slot_{value.index}" data[sensor_name] = value.value self._data = data + # pull codes for zwave + elif ZWAVE_NETWORK in self._hass.data: + if data[ATTR_NODE_ID] is not None: + network = self._hass.data[ZWAVE_NETWORK] + lock_values = ( + network.nodes[data[ATTR_NODE_ID]] + .get_values(class_id=COMMAND_CLASS_USER_CODE) + .values() + ) + for value in lock_values: + _LOGGER.debug( + "DEBUG: code_slot_%s value: %s", + str(value.index), + str(value.value), + ) + # do not update if the code contains *s + if "*" in str(value.value): + _LOGGER.debug("DEBUG: Ignoring code slot with * in value.") + continue + sensor_name = f"code_slot_{value.index}" + data[sensor_name] = value.value + + self._data = data + def _get_node_id(self): data = None test = self._hass.states.get(self._entity_id) diff --git a/custom_components/lock-manager/services.yaml b/custom_components/lock-manager/services.yaml index 05d49db..c1f6c53 100644 --- a/custom_components/lock-manager/services.yaml +++ b/custom_components/lock-manager/services.yaml @@ -4,3 +4,26 @@ generate_package: lockname: description: Lock name to generate files for. example: frontdoor + +add_code: + description: Add code to lock. Supports ozw and zwave locks. + fields: + entity_id: + description: The entity_id of the lock you are attempting to modify the code of + example: lock.frontdoor_locked + code_slot: + description: The code slot you are attempting to add/replace a code into + example: 2 + usercode: + description: The code you are attempting to set + example: 1234 + +clear_code: + description: Clear code from lock. Supports ozw and zwave locks. + fields: + entity_id: + description: The entity_id of the lock you are attempting to modify the code of + example: lock.frontdoor_locked + code_slot: + description: The code slot you are attempting to clear + example: 2