Skip to content

Commit

Permalink
Unified add/clear code services (#33)
Browse files Browse the repository at this point in the history
* Add support for zwave lock codes

* Removed unused import
* Add support for `zwave` lock codes

* Ignore *s

* Ignore "*" is code slots
* Increased code refresh time to 5 seconds from 10 seconds

* Unified add/clear code services

* Add `add_code` service
* Add `clear_code` service
* Tweak the code slot sensor to continue rather than stop on codes with `*`s
* Add debugging messages to sensor.py

* Convert int to str to check for stars
  • Loading branch information
firstof9 authored Oct 25, 2020
1 parent babe5bc commit 233f317
Show file tree
Hide file tree
Showing 4 changed files with 204 additions and 6 deletions.
135 changes: 134 additions & 1 deletion custom_components/lock-manager/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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):
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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(
Expand Down
10 changes: 9 additions & 1 deletion custom_components/lock-manager/const.py
Original file line number Diff line number Diff line change
@@ -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"
Expand Down
42 changes: 38 additions & 4 deletions custom_components/lock-manager/sensor.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -12,6 +12,7 @@
MANAGER = "manager"
ATTR_VALUES = "values"
ATTR_NODE_ID = "node_id"
COMMAND_CLASS_USER_CODE = 99

_LOGGER = logging.getLogger(__name__)

Expand Down Expand Up @@ -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"""
Expand All @@ -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]
Expand All @@ -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)
Expand Down
23 changes: 23 additions & 0 deletions custom_components/lock-manager/services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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

0 comments on commit 233f317

Please sign in to comment.