2015-02-26 07:27:17 +00:00
|
|
|
"""
|
2016-03-07 17:49:31 +00:00
|
|
|
Support for Z-Wave.
|
2015-10-21 19:26:16 +00:00
|
|
|
|
2015-11-09 12:12:18 +00:00
|
|
|
For more details about this component, please refer to the documentation at
|
|
|
|
https://home-assistant.io/components/zwave/
|
2015-02-26 07:27:17 +00:00
|
|
|
"""
|
2016-03-27 18:29:39 +00:00
|
|
|
import logging
|
2015-11-20 04:35:18 +00:00
|
|
|
import os.path
|
2016-03-27 18:29:39 +00:00
|
|
|
import time
|
2015-02-26 07:27:17 +00:00
|
|
|
from pprint import pprint
|
2016-10-05 12:40:08 +00:00
|
|
|
|
2016-08-21 21:36:44 +00:00
|
|
|
import voluptuous as vol
|
2016-02-19 05:27:50 +00:00
|
|
|
|
2017-02-13 07:55:27 +00:00
|
|
|
from homeassistant.helpers import discovery
|
2015-02-23 01:36:28 +00:00
|
|
|
from homeassistant.const import (
|
2017-02-10 16:54:48 +00:00
|
|
|
ATTR_BATTERY_LEVEL, ATTR_LOCATION, ATTR_ENTITY_ID, ATTR_WAKEUP,
|
2017-02-13 07:55:27 +00:00
|
|
|
EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP)
|
2017-01-27 06:21:33 +00:00
|
|
|
from homeassistant.helpers.entity import Entity
|
2017-02-16 23:19:22 +00:00
|
|
|
from homeassistant.helpers.entity_values import EntityValues
|
2016-06-07 16:29:15 +00:00
|
|
|
from homeassistant.helpers.event import track_time_change
|
2016-02-19 05:27:50 +00:00
|
|
|
from homeassistant.util import convert, slugify
|
2016-08-21 21:36:44 +00:00
|
|
|
import homeassistant.config as conf_util
|
|
|
|
import homeassistant.helpers.config_validation as cv
|
2017-02-16 23:19:22 +00:00
|
|
|
|
2016-09-30 15:43:18 +00:00
|
|
|
from . import const
|
2017-02-08 04:37:11 +00:00
|
|
|
from . import workaround
|
2015-02-23 01:36:28 +00:00
|
|
|
|
2015-08-30 01:39:50 +00:00
|
|
|
REQUIREMENTS = ['pydispatcher==2.0.5']
|
2015-02-23 01:36:28 +00:00
|
|
|
|
2016-10-05 12:40:08 +00:00
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
|
2017-02-09 12:40:35 +00:00
|
|
|
CLASS_ID = 'class_id'
|
2016-10-05 12:40:08 +00:00
|
|
|
CONF_AUTOHEAL = 'autoheal'
|
|
|
|
CONF_DEBUG = 'debug'
|
|
|
|
CONF_POLLING_INTENSITY = 'polling_intensity'
|
|
|
|
CONF_POLLING_INTERVAL = 'polling_interval'
|
|
|
|
CONF_USB_STICK_PATH = 'usb_path'
|
|
|
|
CONF_CONFIG_PATH = 'config_path'
|
2016-10-14 15:36:55 +00:00
|
|
|
CONF_IGNORED = 'ignored'
|
2016-11-18 20:59:01 +00:00
|
|
|
CONF_REFRESH_VALUE = 'refresh_value'
|
|
|
|
CONF_REFRESH_DELAY = 'delay'
|
2017-02-13 07:55:27 +00:00
|
|
|
CONF_DEVICE_CONFIG = 'device_config'
|
2017-02-16 23:19:22 +00:00
|
|
|
CONF_DEVICE_CONFIG_GLOB = 'device_config_glob'
|
|
|
|
CONF_DEVICE_CONFIG_DOMAIN = 'device_config_domain'
|
2016-10-05 12:40:08 +00:00
|
|
|
|
2016-06-07 16:29:15 +00:00
|
|
|
DEFAULT_CONF_AUTOHEAL = True
|
2016-10-05 12:40:08 +00:00
|
|
|
DEFAULT_CONF_USB_STICK_PATH = '/zwaveusbstick'
|
|
|
|
DEFAULT_POLLING_INTERVAL = 60000
|
2017-01-02 17:45:44 +00:00
|
|
|
DEFAULT_DEBUG = False
|
2016-10-14 15:36:55 +00:00
|
|
|
DEFAULT_CONF_IGNORED = False
|
2016-11-18 20:59:01 +00:00
|
|
|
DEFAULT_CONF_REFRESH_VALUE = False
|
|
|
|
DEFAULT_CONF_REFRESH_DELAY = 2
|
2016-10-05 12:40:08 +00:00
|
|
|
DOMAIN = 'zwave'
|
|
|
|
|
2016-09-30 15:43:18 +00:00
|
|
|
NETWORK = None
|
2017-02-13 07:55:27 +00:00
|
|
|
DATA_DEVICE_CONFIG = 'zwave_device_config'
|
2016-02-20 20:24:45 +00:00
|
|
|
|
2016-03-07 17:49:31 +00:00
|
|
|
# List of tuple (DOMAIN, discovered service, supported command classes,
|
2016-06-20 05:30:57 +00:00
|
|
|
# value type, genre type, specific device class).
|
2015-02-26 07:27:17 +00:00
|
|
|
DISCOVERY_COMPONENTS = [
|
2015-11-07 14:53:29 +00:00
|
|
|
('sensor',
|
2016-09-30 15:43:18 +00:00
|
|
|
[const.GENERIC_TYPE_WHATEVER],
|
|
|
|
[const.SPECIFIC_TYPE_WHATEVER],
|
|
|
|
[const.COMMAND_CLASS_SENSOR_MULTILEVEL,
|
|
|
|
const.COMMAND_CLASS_METER,
|
|
|
|
const.COMMAND_CLASS_ALARM,
|
|
|
|
const.COMMAND_CLASS_SENSOR_ALARM],
|
|
|
|
const.TYPE_WHATEVER,
|
|
|
|
const.GENRE_USER),
|
2015-11-07 14:56:28 +00:00
|
|
|
('light',
|
2016-09-30 15:43:18 +00:00
|
|
|
[const.GENERIC_TYPE_SWITCH_MULTILEVEL,
|
|
|
|
const.GENERIC_TYPE_SWITCH_REMOTE],
|
|
|
|
[const.SPECIFIC_TYPE_POWER_SWITCH_MULTILEVEL,
|
|
|
|
const.SPECIFIC_TYPE_SCENE_SWITCH_MULTILEVEL,
|
|
|
|
const.SPECIFIC_TYPE_NOT_USED],
|
|
|
|
[const.COMMAND_CLASS_SWITCH_MULTILEVEL],
|
|
|
|
const.TYPE_BYTE,
|
|
|
|
const.GENRE_USER),
|
2015-11-07 14:52:36 +00:00
|
|
|
('switch',
|
2016-09-30 15:43:18 +00:00
|
|
|
[const.GENERIC_TYPE_SENSOR_ALARM,
|
|
|
|
const.GENERIC_TYPE_SENSOR_BINARY,
|
|
|
|
const.GENERIC_TYPE_SWITCH_BINARY,
|
|
|
|
const.GENERIC_TYPE_ENTRY_CONTROL,
|
|
|
|
const.GENERIC_TYPE_SENSOR_MULTILEVEL,
|
|
|
|
const.GENERIC_TYPE_SWITCH_MULTILEVEL,
|
|
|
|
const.GENERIC_TYPE_SENSOR_NOTIFICATION,
|
|
|
|
const.GENERIC_TYPE_GENERIC_CONTROLLER,
|
|
|
|
const.GENERIC_TYPE_SWITCH_REMOTE,
|
|
|
|
const.GENERIC_TYPE_REPEATER_SLAVE,
|
|
|
|
const.GENERIC_TYPE_THERMOSTAT,
|
|
|
|
const.GENERIC_TYPE_WALL_CONTROLLER],
|
|
|
|
[const.SPECIFIC_TYPE_WHATEVER],
|
|
|
|
[const.COMMAND_CLASS_SWITCH_BINARY],
|
|
|
|
const.TYPE_BOOL,
|
|
|
|
const.GENRE_USER),
|
2016-02-20 20:24:45 +00:00
|
|
|
('binary_sensor',
|
2016-09-30 15:43:18 +00:00
|
|
|
[const.GENERIC_TYPE_SENSOR_ALARM,
|
|
|
|
const.GENERIC_TYPE_SENSOR_BINARY,
|
|
|
|
const.GENERIC_TYPE_SWITCH_BINARY,
|
|
|
|
const.GENERIC_TYPE_METER,
|
|
|
|
const.GENERIC_TYPE_SENSOR_MULTILEVEL,
|
|
|
|
const.GENERIC_TYPE_SWITCH_MULTILEVEL,
|
|
|
|
const.GENERIC_TYPE_SENSOR_NOTIFICATION,
|
|
|
|
const.GENERIC_TYPE_THERMOSTAT],
|
|
|
|
[const.SPECIFIC_TYPE_WHATEVER],
|
|
|
|
[const.COMMAND_CLASS_SENSOR_BINARY],
|
|
|
|
const.TYPE_BOOL,
|
|
|
|
const.GENRE_USER),
|
2016-05-03 02:38:48 +00:00
|
|
|
('lock',
|
2016-09-30 15:43:18 +00:00
|
|
|
[const.GENERIC_TYPE_ENTRY_CONTROL],
|
|
|
|
[const.SPECIFIC_TYPE_ADVANCED_DOOR_LOCK,
|
|
|
|
const.SPECIFIC_TYPE_SECURE_KEYPAD_DOOR_LOCK],
|
|
|
|
[const.COMMAND_CLASS_DOOR_LOCK],
|
|
|
|
const.TYPE_BOOL,
|
|
|
|
const.GENRE_USER),
|
2016-08-24 01:23:18 +00:00
|
|
|
('cover',
|
2016-09-30 15:43:18 +00:00
|
|
|
[const.GENERIC_TYPE_SWITCH_MULTILEVEL,
|
|
|
|
const.GENERIC_TYPE_ENTRY_CONTROL],
|
|
|
|
[const.SPECIFIC_TYPE_CLASS_A_MOTOR_CONTROL,
|
|
|
|
const.SPECIFIC_TYPE_CLASS_B_MOTOR_CONTROL,
|
|
|
|
const.SPECIFIC_TYPE_CLASS_C_MOTOR_CONTROL,
|
|
|
|
const.SPECIFIC_TYPE_MOTOR_MULTIPOSITION,
|
|
|
|
const.SPECIFIC_TYPE_SECURE_BARRIER_ADDON,
|
|
|
|
const.SPECIFIC_TYPE_SECURE_DOOR],
|
|
|
|
[const.COMMAND_CLASS_SWITCH_BINARY,
|
|
|
|
const.COMMAND_CLASS_BARRIER_OPERATOR,
|
|
|
|
const.COMMAND_CLASS_SWITCH_MULTILEVEL],
|
|
|
|
const.TYPE_WHATEVER,
|
|
|
|
const.GENRE_USER),
|
2016-08-19 07:17:28 +00:00
|
|
|
('climate',
|
2016-09-30 15:43:18 +00:00
|
|
|
[const.GENERIC_TYPE_THERMOSTAT],
|
|
|
|
[const.SPECIFIC_TYPE_WHATEVER],
|
|
|
|
[const.COMMAND_CLASS_THERMOSTAT_SETPOINT],
|
|
|
|
const.TYPE_WHATEVER,
|
|
|
|
const.GENRE_WHATEVER),
|
2015-02-26 07:27:17 +00:00
|
|
|
]
|
|
|
|
|
2016-08-21 21:36:44 +00:00
|
|
|
RENAME_NODE_SCHEMA = vol.Schema({
|
|
|
|
vol.Required(ATTR_ENTITY_ID): cv.entity_id,
|
2016-09-30 15:43:18 +00:00
|
|
|
vol.Required(const.ATTR_NAME): cv.string,
|
2016-08-21 21:36:44 +00:00
|
|
|
})
|
2016-10-05 05:04:19 +00:00
|
|
|
SET_CONFIG_PARAMETER_SCHEMA = vol.Schema({
|
2016-10-14 06:45:00 +00:00
|
|
|
vol.Required(const.ATTR_NODE_ID): vol.Coerce(int),
|
2016-10-05 05:04:19 +00:00
|
|
|
vol.Required(const.ATTR_CONFIG_PARAMETER): vol.Coerce(int),
|
|
|
|
vol.Required(const.ATTR_CONFIG_VALUE): vol.Coerce(int),
|
|
|
|
vol.Optional(const.ATTR_CONFIG_SIZE): vol.Coerce(int)
|
|
|
|
})
|
2017-01-01 20:10:45 +00:00
|
|
|
PRINT_CONFIG_PARAMETER_SCHEMA = vol.Schema({
|
|
|
|
vol.Required(const.ATTR_NODE_ID): vol.Coerce(int),
|
|
|
|
vol.Required(const.ATTR_CONFIG_PARAMETER): vol.Coerce(int),
|
|
|
|
})
|
|
|
|
|
2017-02-17 20:03:55 +00:00
|
|
|
PRINT_NODE_SCHEMA = vol.Schema({
|
|
|
|
vol.Required(const.ATTR_NODE_ID): vol.Coerce(int),
|
|
|
|
})
|
|
|
|
|
2016-10-16 18:36:06 +00:00
|
|
|
CHANGE_ASSOCIATION_SCHEMA = vol.Schema({
|
|
|
|
vol.Required(const.ATTR_ASSOCIATION): cv.string,
|
|
|
|
vol.Required(const.ATTR_NODE_ID): vol.Coerce(int),
|
|
|
|
vol.Required(const.ATTR_TARGET_NODE_ID): vol.Coerce(int),
|
|
|
|
vol.Required(const.ATTR_GROUP): vol.Coerce(int),
|
|
|
|
vol.Optional(const.ATTR_INSTANCE, default=0x00): vol.Coerce(int)
|
|
|
|
})
|
2016-08-21 21:36:44 +00:00
|
|
|
|
2017-02-10 16:54:48 +00:00
|
|
|
SET_WAKEUP_SCHEMA = vol.Schema({
|
|
|
|
vol.Required(const.ATTR_NODE_ID): vol.Coerce(int),
|
|
|
|
vol.Required(const.ATTR_CONFIG_VALUE):
|
|
|
|
vol.All(vol.Coerce(int), cv.positive_int),
|
|
|
|
})
|
|
|
|
|
2017-02-14 05:34:36 +00:00
|
|
|
DEVICE_CONFIG_SCHEMA_ENTRY = vol.Schema({
|
2017-01-28 20:29:51 +00:00
|
|
|
vol.Optional(CONF_POLLING_INTENSITY): cv.positive_int,
|
2016-10-14 15:36:55 +00:00
|
|
|
vol.Optional(CONF_IGNORED, default=DEFAULT_CONF_IGNORED): cv.boolean,
|
2016-11-18 20:59:01 +00:00
|
|
|
vol.Optional(CONF_REFRESH_VALUE, default=DEFAULT_CONF_REFRESH_VALUE):
|
|
|
|
cv.boolean,
|
|
|
|
vol.Optional(CONF_REFRESH_DELAY, default=DEFAULT_CONF_REFRESH_DELAY):
|
|
|
|
cv.positive_int
|
2016-10-05 12:40:08 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
CONFIG_SCHEMA = vol.Schema({
|
|
|
|
DOMAIN: vol.Schema({
|
|
|
|
vol.Optional(CONF_AUTOHEAL, default=DEFAULT_CONF_AUTOHEAL): cv.boolean,
|
|
|
|
vol.Optional(CONF_CONFIG_PATH): cv.string,
|
2017-02-13 07:55:27 +00:00
|
|
|
vol.Optional(CONF_DEVICE_CONFIG, default={}):
|
2017-02-14 05:34:36 +00:00
|
|
|
vol.Schema({cv.entity_id: DEVICE_CONFIG_SCHEMA_ENTRY}),
|
2017-02-16 23:19:22 +00:00
|
|
|
vol.Optional(CONF_DEVICE_CONFIG_GLOB, default={}):
|
|
|
|
vol.Schema({cv.string: DEVICE_CONFIG_SCHEMA_ENTRY}),
|
|
|
|
vol.Optional(CONF_DEVICE_CONFIG_DOMAIN, default={}):
|
|
|
|
vol.Schema({cv.string: DEVICE_CONFIG_SCHEMA_ENTRY}),
|
2017-01-02 17:45:44 +00:00
|
|
|
vol.Optional(CONF_DEBUG, default=DEFAULT_DEBUG): cv.boolean,
|
2016-10-05 12:40:08 +00:00
|
|
|
vol.Optional(CONF_POLLING_INTERVAL, default=DEFAULT_POLLING_INTERVAL):
|
|
|
|
cv.positive_int,
|
|
|
|
vol.Optional(CONF_USB_STICK_PATH, default=DEFAULT_CONF_USB_STICK_PATH):
|
|
|
|
cv.string,
|
|
|
|
}),
|
|
|
|
}, extra=vol.ALLOW_EXTRA)
|
2016-03-27 18:29:39 +00:00
|
|
|
|
2015-02-23 01:36:28 +00:00
|
|
|
|
2015-02-26 07:27:17 +00:00
|
|
|
def _obj_to_dict(obj):
|
2016-03-08 16:55:57 +00:00
|
|
|
"""Convert an object into a hash for debug."""
|
2015-02-26 07:27:17 +00:00
|
|
|
return {key: getattr(obj, key) for key
|
|
|
|
in dir(obj)
|
|
|
|
if key[0] != '_' and not hasattr(getattr(obj, key), '__call__')}
|
2015-02-23 01:36:28 +00:00
|
|
|
|
|
|
|
|
2016-02-03 11:52:35 +00:00
|
|
|
def _node_name(node):
|
2016-03-08 16:55:57 +00:00
|
|
|
"""Return the name of the node."""
|
2016-10-05 12:40:08 +00:00
|
|
|
return node.name or '{} {}'.format(
|
2016-02-03 11:52:35 +00:00
|
|
|
node.manufacturer_name, node.product_name)
|
|
|
|
|
|
|
|
|
|
|
|
def _value_name(value):
|
2016-03-08 16:55:57 +00:00
|
|
|
"""Return the name of the value."""
|
2016-10-05 12:40:08 +00:00
|
|
|
return '{} {}'.format(_node_name(value.node), value.label)
|
2016-02-03 11:52:35 +00:00
|
|
|
|
|
|
|
|
2016-07-29 19:56:03 +00:00
|
|
|
def _node_object_id(node):
|
|
|
|
"""Return the object_id of the node."""
|
2016-10-05 12:40:08 +00:00
|
|
|
node_object_id = '{}_{}'.format(slugify(_node_name(node)), node.node_id)
|
2016-07-29 19:56:03 +00:00
|
|
|
return node_object_id
|
|
|
|
|
|
|
|
|
2016-11-20 19:49:54 +00:00
|
|
|
def object_id(value):
|
2016-03-08 16:55:57 +00:00
|
|
|
"""Return the object_id of the device value.
|
|
|
|
|
2016-02-03 11:52:35 +00:00
|
|
|
The object_id contains node_id and value instance id
|
2016-03-07 17:49:31 +00:00
|
|
|
to not collide with other entity_ids.
|
|
|
|
"""
|
2016-11-20 19:49:54 +00:00
|
|
|
_object_id = "{}_{}_{}".format(slugify(_value_name(value)),
|
|
|
|
value.node.node_id, value.index)
|
2016-02-03 11:52:35 +00:00
|
|
|
|
|
|
|
# Add the instance id if there is more than one instance for the value
|
|
|
|
if value.instance > 1:
|
2016-11-22 03:35:36 +00:00
|
|
|
return '{}_{}'.format(_object_id, value.instance)
|
2016-11-20 19:49:54 +00:00
|
|
|
return _object_id
|
2016-02-03 11:52:35 +00:00
|
|
|
|
|
|
|
|
2015-02-23 08:01:04 +00:00
|
|
|
def nice_print_node(node):
|
2016-03-08 16:55:57 +00:00
|
|
|
"""Print a nice formatted node to the output (debug method)."""
|
2015-02-26 07:27:17 +00:00
|
|
|
node_dict = _obj_to_dict(node)
|
|
|
|
node_dict['values'] = {value_id: _obj_to_dict(value)
|
|
|
|
for value_id, value in node.values.items()}
|
2015-02-23 08:01:04 +00:00
|
|
|
|
2015-02-26 07:27:17 +00:00
|
|
|
print("\n\n\n")
|
2015-02-23 08:01:04 +00:00
|
|
|
print("FOUND NODE", node.product_name)
|
2015-02-26 07:27:17 +00:00
|
|
|
pprint(node_dict)
|
|
|
|
print("\n\n\n")
|
2015-02-23 08:01:04 +00:00
|
|
|
|
|
|
|
|
2017-02-12 23:42:09 +00:00
|
|
|
def get_config_value(node, value_index, tries=5):
|
2016-03-08 16:55:57 +00:00
|
|
|
"""Return the current configuration value for a specific index."""
|
2015-10-31 22:34:19 +00:00
|
|
|
try:
|
|
|
|
for value in node.values.values():
|
|
|
|
# 112 == config command class
|
|
|
|
if value.command_class == 112 and value.index == value_index:
|
|
|
|
return value.data
|
|
|
|
except RuntimeError:
|
|
|
|
# If we get an runtime error the dict has changed while
|
|
|
|
# we was looking for a value, just do it again
|
2017-02-12 23:42:09 +00:00
|
|
|
return None if tries <= 0 else get_config_value(
|
|
|
|
node, value_index, tries=tries - 1)
|
|
|
|
return None
|
2015-10-31 20:23:33 +00:00
|
|
|
|
|
|
|
|
2017-02-12 23:42:09 +00:00
|
|
|
def _get_wakeup(node, tries=5):
|
2017-02-10 16:54:48 +00:00
|
|
|
"""Return wakeup interval of the node or None if node is not wakable."""
|
2017-02-12 23:42:09 +00:00
|
|
|
try:
|
|
|
|
if node.can_wake_up():
|
|
|
|
for value_id in node.get_values(
|
|
|
|
class_id=const.COMMAND_CLASS_WAKE_UP):
|
|
|
|
return node.values[value_id].data
|
|
|
|
except RuntimeError:
|
|
|
|
# If we get an runtime error the dict has changed while
|
|
|
|
# we was looking for a value, just do it again
|
|
|
|
return None if tries <= 0 else _get_wakeup(
|
|
|
|
node, tries=tries - 1)
|
|
|
|
|
2017-02-10 16:54:48 +00:00
|
|
|
return None
|
|
|
|
|
|
|
|
|
2016-03-13 17:51:09 +00:00
|
|
|
# pylint: disable=R0914
|
2015-02-23 01:36:28 +00:00
|
|
|
def setup(hass, config):
|
2016-03-08 16:55:57 +00:00
|
|
|
"""Setup Z-Wave.
|
|
|
|
|
2015-02-23 01:36:28 +00:00
|
|
|
Will automatically load components to support devices found on the network.
|
|
|
|
"""
|
2015-02-26 07:27:17 +00:00
|
|
|
# pylint: disable=global-statement, import-error
|
2015-02-23 01:36:28 +00:00
|
|
|
global NETWORK
|
|
|
|
|
2016-08-21 21:36:44 +00:00
|
|
|
descriptions = conf_util.load_yaml_config_file(
|
2016-10-05 12:40:08 +00:00
|
|
|
os.path.join(os.path.dirname(__file__), 'services.yaml'))
|
2016-08-21 21:36:44 +00:00
|
|
|
|
2016-04-26 09:41:20 +00:00
|
|
|
try:
|
|
|
|
import libopenzwave
|
|
|
|
except ImportError:
|
|
|
|
_LOGGER.error("You are missing required dependency Python Open "
|
|
|
|
"Z-Wave. Please follow instructions at: "
|
|
|
|
"https://home-assistant.io/components/zwave/")
|
|
|
|
return False
|
2015-03-01 06:49:27 +00:00
|
|
|
from pydispatch import dispatcher
|
2015-02-23 01:36:28 +00:00
|
|
|
from openzwave.option import ZWaveOption
|
|
|
|
from openzwave.network import ZWaveNetwork
|
2016-10-16 18:36:06 +00:00
|
|
|
from openzwave.group import ZWaveGroup
|
2015-02-23 01:36:28 +00:00
|
|
|
|
2016-04-26 09:41:20 +00:00
|
|
|
default_zwave_config_path = os.path.join(os.path.dirname(
|
|
|
|
libopenzwave.__file__), 'config')
|
|
|
|
|
2016-02-03 11:52:35 +00:00
|
|
|
# Load configuration
|
2016-10-05 12:40:08 +00:00
|
|
|
use_debug = config[DOMAIN].get(CONF_DEBUG)
|
|
|
|
autoheal = config[DOMAIN].get(CONF_AUTOHEAL)
|
2017-02-16 23:19:22 +00:00
|
|
|
hass.data[DATA_DEVICE_CONFIG] = EntityValues(
|
|
|
|
config[DOMAIN][CONF_DEVICE_CONFIG],
|
|
|
|
config[DOMAIN][CONF_DEVICE_CONFIG_DOMAIN],
|
|
|
|
config[DOMAIN][CONF_DEVICE_CONFIG_GLOB])
|
2015-02-23 08:01:04 +00:00
|
|
|
|
2015-02-23 01:36:28 +00:00
|
|
|
# Setup options
|
|
|
|
options = ZWaveOption(
|
2016-10-05 12:40:08 +00:00
|
|
|
config[DOMAIN].get(CONF_USB_STICK_PATH),
|
2015-11-20 04:35:18 +00:00
|
|
|
user_path=hass.config.config_dir,
|
2016-10-05 12:40:08 +00:00
|
|
|
config_path=config[DOMAIN].get(
|
|
|
|
CONF_CONFIG_PATH, default_zwave_config_path))
|
2015-02-23 01:36:28 +00:00
|
|
|
|
2015-02-23 08:01:04 +00:00
|
|
|
options.set_console_output(use_debug)
|
2015-02-23 01:36:28 +00:00
|
|
|
options.lock()
|
|
|
|
|
|
|
|
NETWORK = ZWaveNetwork(options, autostart=False)
|
|
|
|
|
2015-02-23 08:01:04 +00:00
|
|
|
if use_debug:
|
2015-02-26 07:27:17 +00:00
|
|
|
def log_all(signal, value=None):
|
2016-03-08 16:55:57 +00:00
|
|
|
"""Log all the signals."""
|
2015-02-23 08:01:04 +00:00
|
|
|
print("")
|
2015-03-19 02:15:48 +00:00
|
|
|
print("SIGNAL *****", signal)
|
2015-02-26 07:27:17 +00:00
|
|
|
if value and signal in (ZWaveNetwork.SIGNAL_VALUE_CHANGED,
|
2016-07-29 19:56:03 +00:00
|
|
|
ZWaveNetwork.SIGNAL_VALUE_ADDED,
|
|
|
|
ZWaveNetwork.SIGNAL_SCENE_EVENT,
|
2016-08-08 14:52:28 +00:00
|
|
|
ZWaveNetwork.SIGNAL_NODE_EVENT,
|
|
|
|
ZWaveNetwork.SIGNAL_AWAKE_NODES_QUERIED,
|
|
|
|
ZWaveNetwork.SIGNAL_ALL_NODES_QUERIED):
|
2015-02-26 07:27:17 +00:00
|
|
|
pprint(_obj_to_dict(value))
|
2016-02-03 11:52:35 +00:00
|
|
|
|
2015-02-23 08:01:04 +00:00
|
|
|
print("")
|
2015-02-23 01:36:28 +00:00
|
|
|
|
2015-03-01 06:49:27 +00:00
|
|
|
dispatcher.connect(log_all, weak=False)
|
2015-02-23 01:36:28 +00:00
|
|
|
|
2015-02-26 07:27:17 +00:00
|
|
|
def value_added(node, value):
|
2016-03-07 17:49:31 +00:00
|
|
|
"""Called when a value is added to a node on the network."""
|
2015-11-07 14:53:29 +00:00
|
|
|
for (component,
|
2016-06-24 15:44:24 +00:00
|
|
|
generic_device_class,
|
|
|
|
specific_device_class,
|
|
|
|
command_class,
|
2015-11-07 14:53:29 +00:00
|
|
|
value_type,
|
2016-06-24 15:44:24 +00:00
|
|
|
value_genre) in DISCOVERY_COMPONENTS:
|
|
|
|
|
|
|
|
_LOGGER.debug("Component=%s Node_id=%s query start",
|
|
|
|
component, node.node_id)
|
|
|
|
if node.generic not in generic_device_class and \
|
|
|
|
None not in generic_device_class:
|
2016-08-08 18:05:45 +00:00
|
|
|
_LOGGER.debug("node.generic %s not None and in "
|
|
|
|
"generic_device_class %s",
|
2016-06-24 15:44:24 +00:00
|
|
|
node.generic, generic_device_class)
|
|
|
|
continue
|
|
|
|
if node.specific not in specific_device_class and \
|
|
|
|
None not in specific_device_class:
|
2016-08-08 18:05:45 +00:00
|
|
|
_LOGGER.debug("node.specific %s is not None and in "
|
|
|
|
"specific_device_class %s", node.specific,
|
2016-06-24 15:44:24 +00:00
|
|
|
specific_device_class)
|
2015-11-07 14:53:29 +00:00
|
|
|
continue
|
2016-06-24 15:44:24 +00:00
|
|
|
if value.command_class not in command_class and \
|
|
|
|
None not in command_class:
|
2016-08-08 18:05:45 +00:00
|
|
|
_LOGGER.debug("value.command_class %s is not None "
|
|
|
|
"and in command_class %s",
|
2016-06-24 15:44:24 +00:00
|
|
|
value.command_class, command_class)
|
2015-11-07 14:53:29 +00:00
|
|
|
continue
|
2016-06-24 15:44:24 +00:00
|
|
|
if value_type != value.type and value_type is not None:
|
|
|
|
_LOGGER.debug("value.type %s != value_type %s",
|
|
|
|
value.type, value_type)
|
2015-11-07 14:53:29 +00:00
|
|
|
continue
|
2016-06-24 15:44:24 +00:00
|
|
|
if value_genre != value.genre and value_genre is not None:
|
|
|
|
_LOGGER.debug("value.genre %s != value_genre %s",
|
|
|
|
value.genre, value_genre)
|
2016-06-20 05:30:57 +00:00
|
|
|
continue
|
2015-11-07 14:53:29 +00:00
|
|
|
|
2016-02-03 11:52:35 +00:00
|
|
|
# Configure node
|
2016-08-08 18:05:45 +00:00
|
|
|
_LOGGER.debug("Adding Node_id=%s Generic_command_class=%s, "
|
|
|
|
"Specific_command_class=%s, "
|
|
|
|
"Command_class=%s, Value type=%s, "
|
2017-02-08 04:37:11 +00:00
|
|
|
"Genre=%s as %s", node.node_id,
|
2016-06-24 15:44:24 +00:00
|
|
|
node.generic, node.specific,
|
|
|
|
value.command_class, value.type,
|
2017-02-08 04:37:11 +00:00
|
|
|
value.genre, component)
|
|
|
|
workaround_component = workaround.get_device_component_mapping(
|
|
|
|
value)
|
2017-02-08 19:11:36 +00:00
|
|
|
if workaround_component and workaround_component != component:
|
2017-02-13 18:59:42 +00:00
|
|
|
if workaround_component == workaround.WORKAROUND_IGNORE:
|
2017-02-17 15:19:21 +00:00
|
|
|
_LOGGER.info("Ignoring device %s due to workaround.",
|
2017-02-13 18:59:42 +00:00
|
|
|
"{}.{}".format(component, object_id(value)))
|
|
|
|
continue
|
2017-02-08 04:37:11 +00:00
|
|
|
_LOGGER.debug("Using %s instead of %s",
|
|
|
|
workaround_component, component)
|
|
|
|
component = workaround_component
|
2016-02-03 11:52:35 +00:00
|
|
|
|
2017-02-08 04:37:11 +00:00
|
|
|
name = "{}.{}".format(component, object_id(value))
|
2017-02-16 23:19:22 +00:00
|
|
|
node_config = hass.data[DATA_DEVICE_CONFIG].get(name)
|
2016-10-14 15:36:55 +00:00
|
|
|
|
|
|
|
if node_config.get(CONF_IGNORED):
|
2017-02-17 15:19:21 +00:00
|
|
|
_LOGGER.info(
|
|
|
|
"Ignoring device %s due to device settings.", name)
|
2016-10-14 15:36:55 +00:00
|
|
|
return
|
|
|
|
|
2016-02-03 11:52:35 +00:00
|
|
|
polling_intensity = convert(
|
|
|
|
node_config.get(CONF_POLLING_INTENSITY), int)
|
2016-03-27 18:29:39 +00:00
|
|
|
if polling_intensity:
|
2016-02-03 11:52:35 +00:00
|
|
|
value.enable_poll(polling_intensity)
|
2016-03-27 18:29:39 +00:00
|
|
|
else:
|
|
|
|
value.disable_poll()
|
2016-02-03 11:52:35 +00:00
|
|
|
|
2016-06-12 00:43:13 +00:00
|
|
|
discovery.load_platform(hass, component, DOMAIN, {
|
2016-09-30 15:43:18 +00:00
|
|
|
const.ATTR_NODE_ID: node.node_id,
|
|
|
|
const.ATTR_VALUE_ID: value.value_id,
|
2016-06-12 00:43:13 +00:00
|
|
|
}, config)
|
2015-02-23 01:36:28 +00:00
|
|
|
|
2016-02-03 11:52:35 +00:00
|
|
|
def scene_activated(node, scene_id):
|
2016-03-07 17:49:31 +00:00
|
|
|
"""Called when a scene is activated on any node in the network."""
|
2016-09-30 15:43:18 +00:00
|
|
|
hass.bus.fire(const.EVENT_SCENE_ACTIVATED, {
|
2016-07-29 19:56:03 +00:00
|
|
|
ATTR_ENTITY_ID: _node_object_id(node),
|
2016-09-30 15:43:18 +00:00
|
|
|
const.ATTR_OBJECT_ID: _node_object_id(node),
|
|
|
|
const.ATTR_SCENE_ID: scene_id
|
2016-02-03 11:52:35 +00:00
|
|
|
})
|
|
|
|
|
2016-07-29 19:56:03 +00:00
|
|
|
def node_event_activated(node, value):
|
|
|
|
"""Called when a nodeevent is activated on any node in the network."""
|
2016-09-30 15:43:18 +00:00
|
|
|
hass.bus.fire(const.EVENT_NODE_EVENT, {
|
|
|
|
const.ATTR_OBJECT_ID: _node_object_id(node),
|
|
|
|
const.ATTR_BASIC_LEVEL: value
|
2016-07-29 19:56:03 +00:00
|
|
|
})
|
|
|
|
|
2016-08-08 14:52:28 +00:00
|
|
|
def network_ready():
|
|
|
|
"""Called when all awake nodes have been queried."""
|
|
|
|
_LOGGER.info("Zwave network is ready for use. All awake nodes"
|
|
|
|
" have been queried. Sleeping nodes will be"
|
|
|
|
" queried when they awake.")
|
2016-09-30 15:43:18 +00:00
|
|
|
hass.bus.fire(const.EVENT_NETWORK_READY)
|
2016-08-08 14:52:28 +00:00
|
|
|
|
|
|
|
def network_complete():
|
|
|
|
"""Called when all nodes on network have been queried."""
|
|
|
|
_LOGGER.info("Zwave network is complete. All nodes on the network"
|
|
|
|
" have been queried")
|
2016-09-30 15:43:18 +00:00
|
|
|
hass.bus.fire(const.EVENT_NETWORK_COMPLETE)
|
2016-08-08 14:52:28 +00:00
|
|
|
|
2015-03-01 06:49:27 +00:00
|
|
|
dispatcher.connect(
|
2015-02-26 07:27:17 +00:00
|
|
|
value_added, ZWaveNetwork.SIGNAL_VALUE_ADDED, weak=False)
|
2016-02-03 11:52:35 +00:00
|
|
|
dispatcher.connect(
|
|
|
|
scene_activated, ZWaveNetwork.SIGNAL_SCENE_EVENT, weak=False)
|
2016-07-29 19:56:03 +00:00
|
|
|
dispatcher.connect(
|
|
|
|
node_event_activated, ZWaveNetwork.SIGNAL_NODE_EVENT, weak=False)
|
2016-08-08 14:52:28 +00:00
|
|
|
dispatcher.connect(
|
|
|
|
network_ready, ZWaveNetwork.SIGNAL_AWAKE_NODES_QUERIED, weak=False)
|
|
|
|
dispatcher.connect(
|
|
|
|
network_complete, ZWaveNetwork.SIGNAL_ALL_NODES_QUERIED, weak=False)
|
2015-02-23 01:36:28 +00:00
|
|
|
|
2016-06-11 05:53:31 +00:00
|
|
|
def add_node(service):
|
2016-03-07 17:49:31 +00:00
|
|
|
"""Switch into inclusion mode."""
|
2016-08-08 14:52:28 +00:00
|
|
|
_LOGGER.info("Zwave add_node have been initialized.")
|
2016-07-13 17:56:14 +00:00
|
|
|
NETWORK.controller.add_node()
|
2016-01-10 13:44:46 +00:00
|
|
|
|
2016-08-02 18:17:10 +00:00
|
|
|
def add_node_secure(service):
|
|
|
|
"""Switch into secure inclusion mode."""
|
2016-08-08 14:52:28 +00:00
|
|
|
_LOGGER.info("Zwave add_node_secure have been initialized.")
|
2016-08-02 18:17:10 +00:00
|
|
|
NETWORK.controller.add_node(True)
|
|
|
|
|
2016-06-11 05:53:31 +00:00
|
|
|
def remove_node(service):
|
2016-03-07 17:49:31 +00:00
|
|
|
"""Switch into exclusion mode."""
|
2016-08-08 14:52:28 +00:00
|
|
|
_LOGGER.info("Zwave remove_node have been initialized.")
|
2016-07-13 17:56:14 +00:00
|
|
|
NETWORK.controller.remove_node()
|
2016-01-10 13:44:46 +00:00
|
|
|
|
2016-08-08 14:52:28 +00:00
|
|
|
def cancel_command(service):
|
|
|
|
"""Cancel a running controller command."""
|
|
|
|
_LOGGER.info("Cancel running ZWave command.")
|
|
|
|
NETWORK.controller.cancel_command()
|
|
|
|
|
2016-06-11 05:53:31 +00:00
|
|
|
def heal_network(service):
|
2016-03-13 17:51:09 +00:00
|
|
|
"""Heal the network."""
|
2016-06-07 16:29:15 +00:00
|
|
|
_LOGGER.info("ZWave heal running.")
|
2016-03-13 17:51:09 +00:00
|
|
|
NETWORK.heal()
|
|
|
|
|
2016-06-11 05:53:31 +00:00
|
|
|
def soft_reset(service):
|
2016-03-13 17:51:09 +00:00
|
|
|
"""Soft reset the controller."""
|
2016-08-08 14:52:28 +00:00
|
|
|
_LOGGER.info("Zwave soft_reset have been initialized.")
|
2016-03-13 17:51:09 +00:00
|
|
|
NETWORK.controller.soft_reset()
|
|
|
|
|
2016-06-11 05:53:31 +00:00
|
|
|
def test_network(service):
|
2016-03-30 08:29:11 +00:00
|
|
|
"""Test the network by sending commands to all the nodes."""
|
2016-08-08 14:52:28 +00:00
|
|
|
_LOGGER.info("Zwave test_network have been initialized.")
|
2016-03-30 08:29:11 +00:00
|
|
|
NETWORK.test()
|
|
|
|
|
2016-08-02 17:08:04 +00:00
|
|
|
def stop_zwave(_service_or_event):
|
2016-08-08 14:52:28 +00:00
|
|
|
"""Stop Z-Wave network."""
|
|
|
|
_LOGGER.info("Stopping ZWave network.")
|
2015-02-23 01:36:28 +00:00
|
|
|
NETWORK.stop()
|
2016-10-22 12:08:24 +00:00
|
|
|
if hass.state == 'RUNNING':
|
|
|
|
hass.bus.fire(const.EVENT_NETWORK_STOP)
|
2015-02-23 01:36:28 +00:00
|
|
|
|
2016-08-21 21:36:44 +00:00
|
|
|
def rename_node(service):
|
|
|
|
"""Rename a node."""
|
|
|
|
state = hass.states.get(service.data.get(ATTR_ENTITY_ID))
|
2016-09-30 15:43:18 +00:00
|
|
|
node_id = state.attributes.get(const.ATTR_NODE_ID)
|
2016-08-21 21:36:44 +00:00
|
|
|
node = NETWORK.nodes[node_id]
|
2016-09-30 15:43:18 +00:00
|
|
|
name = service.data.get(const.ATTR_NAME)
|
2016-08-21 21:36:44 +00:00
|
|
|
node.name = name
|
|
|
|
_LOGGER.info(
|
|
|
|
"Renamed ZWave node %d to %s", node_id, name)
|
|
|
|
|
2016-10-05 05:04:19 +00:00
|
|
|
def set_config_parameter(service):
|
|
|
|
"""Set a config parameter to a node."""
|
2016-10-14 06:45:00 +00:00
|
|
|
node_id = service.data.get(const.ATTR_NODE_ID)
|
2016-10-05 05:04:19 +00:00
|
|
|
node = NETWORK.nodes[node_id]
|
|
|
|
param = service.data.get(const.ATTR_CONFIG_PARAMETER)
|
2017-01-02 17:53:46 +00:00
|
|
|
selection = service.data.get(const.ATTR_CONFIG_VALUE)
|
2016-10-05 05:04:19 +00:00
|
|
|
size = service.data.get(const.ATTR_CONFIG_SIZE, 2)
|
2017-01-02 17:53:46 +00:00
|
|
|
i = 0
|
|
|
|
for value in (
|
|
|
|
node.get_values(class_id=const.COMMAND_CLASS_CONFIGURATION)
|
|
|
|
.values()):
|
|
|
|
if value.index == param and value.type == const.TYPE_LIST:
|
|
|
|
_LOGGER.debug('Values for parameter %s: %s', param,
|
|
|
|
value.data_items)
|
|
|
|
i = len(value.data_items) - 1
|
|
|
|
if i == 0:
|
|
|
|
node.set_config_param(param, selection, size)
|
|
|
|
else:
|
|
|
|
if selection > i:
|
|
|
|
_LOGGER.info('Config parameter selection does not exist!'
|
|
|
|
' Please check zwcfg_[home_id].xml in'
|
|
|
|
' your homeassistant config directory. '
|
|
|
|
' Available selections are 0 to %s', i)
|
|
|
|
return
|
|
|
|
node.set_config_param(param, selection, size)
|
|
|
|
_LOGGER.info('Setting config parameter %s on Node %s '
|
|
|
|
'with selection %s and size=%s', param, node_id,
|
|
|
|
selection, size)
|
2016-10-05 05:04:19 +00:00
|
|
|
|
2017-01-01 20:10:45 +00:00
|
|
|
def print_config_parameter(service):
|
|
|
|
"""Print a config parameter from a node."""
|
|
|
|
node_id = service.data.get(const.ATTR_NODE_ID)
|
|
|
|
node = NETWORK.nodes[node_id]
|
|
|
|
param = service.data.get(const.ATTR_CONFIG_PARAMETER)
|
|
|
|
_LOGGER.info("Config parameter %s on Node %s : %s",
|
|
|
|
param, node_id, get_config_value(node, param))
|
|
|
|
|
2017-02-17 20:03:55 +00:00
|
|
|
def print_node(service):
|
|
|
|
"""Print all information about z-wave node."""
|
|
|
|
node_id = service.data.get(const.ATTR_NODE_ID)
|
|
|
|
node = NETWORK.nodes[node_id]
|
|
|
|
nice_print_node(node)
|
|
|
|
|
2017-02-10 16:54:48 +00:00
|
|
|
def set_wakeup(service):
|
|
|
|
"""Set wake-up interval of a node."""
|
|
|
|
node_id = service.data.get(const.ATTR_NODE_ID)
|
|
|
|
node = NETWORK.nodes[node_id]
|
|
|
|
value = service.data.get(const.ATTR_CONFIG_VALUE)
|
|
|
|
if node.can_wake_up():
|
|
|
|
for value_id in node.get_values(
|
|
|
|
class_id=const.COMMAND_CLASS_WAKE_UP):
|
|
|
|
node.values[value_id].data = value
|
|
|
|
_LOGGER.info("Node %s wake-up set to %d", node_id, value)
|
|
|
|
else:
|
|
|
|
_LOGGER.info("Node %s is not wakeable", node_id)
|
|
|
|
|
2016-10-16 18:36:06 +00:00
|
|
|
def change_association(service):
|
|
|
|
"""Change an association in the zwave network."""
|
|
|
|
association_type = service.data.get(const.ATTR_ASSOCIATION)
|
|
|
|
node_id = service.data.get(const.ATTR_NODE_ID)
|
|
|
|
target_node_id = service.data.get(const.ATTR_TARGET_NODE_ID)
|
|
|
|
group = service.data.get(const.ATTR_GROUP)
|
|
|
|
instance = service.data.get(const.ATTR_INSTANCE)
|
|
|
|
|
|
|
|
node = ZWaveGroup(group, NETWORK, node_id)
|
|
|
|
if association_type == 'add':
|
|
|
|
node.add_association(target_node_id, instance)
|
|
|
|
_LOGGER.info("Adding association for node:%s in group:%s "
|
|
|
|
"target node:%s, instance=%s", node_id, group,
|
|
|
|
target_node_id, instance)
|
|
|
|
if association_type == 'remove':
|
|
|
|
node.remove_association(target_node_id, instance)
|
|
|
|
_LOGGER.info("Removing association for node:%s in group:%s "
|
|
|
|
"target node:%s, instance=%s", node_id, group,
|
|
|
|
target_node_id, instance)
|
|
|
|
|
2016-08-02 17:08:04 +00:00
|
|
|
def start_zwave(_service_or_event):
|
2016-08-08 14:52:28 +00:00
|
|
|
"""Startup Z-Wave network."""
|
|
|
|
_LOGGER.info("Starting ZWave network.")
|
2015-02-23 01:36:28 +00:00
|
|
|
NETWORK.start()
|
2016-09-30 15:43:18 +00:00
|
|
|
hass.bus.fire(const.EVENT_NETWORK_START)
|
2015-02-23 01:36:28 +00:00
|
|
|
|
2016-03-27 18:29:39 +00:00
|
|
|
# Need to be in STATE_AWAKED before talking to nodes.
|
|
|
|
# Wait up to NETWORK_READY_WAIT_SECS seconds for the zwave network
|
|
|
|
# to be ready.
|
2016-09-30 15:43:18 +00:00
|
|
|
for i in range(const.NETWORK_READY_WAIT_SECS):
|
2016-06-11 05:53:31 +00:00
|
|
|
_LOGGER.debug(
|
2016-03-27 18:29:39 +00:00
|
|
|
"network state: %d %s", NETWORK.state, NETWORK.state_str)
|
|
|
|
if NETWORK.state >= NETWORK.STATE_AWAKED:
|
|
|
|
_LOGGER.info("zwave ready after %d seconds", i)
|
|
|
|
break
|
|
|
|
time.sleep(1)
|
|
|
|
else:
|
|
|
|
_LOGGER.warning(
|
|
|
|
"zwave not ready after %d seconds, continuing anyway",
|
2016-09-30 15:43:18 +00:00
|
|
|
const.NETWORK_READY_WAIT_SECS)
|
2016-03-27 18:29:39 +00:00
|
|
|
_LOGGER.info(
|
|
|
|
"final network state: %d %s", NETWORK.state, NETWORK.state_str)
|
|
|
|
|
2016-02-03 11:52:35 +00:00
|
|
|
polling_interval = convert(
|
|
|
|
config[DOMAIN].get(CONF_POLLING_INTERVAL), int)
|
2015-11-15 16:45:20 +00:00
|
|
|
if polling_interval is not None:
|
2016-02-03 11:52:35 +00:00
|
|
|
NETWORK.set_poll_interval(polling_interval, False)
|
2015-11-15 16:45:20 +00:00
|
|
|
|
2016-03-27 18:29:39 +00:00
|
|
|
poll_interval = NETWORK.get_poll_interval()
|
|
|
|
_LOGGER.info("zwave polling interval set to %d ms", poll_interval)
|
|
|
|
|
2015-02-23 01:36:28 +00:00
|
|
|
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_zwave)
|
|
|
|
|
2016-08-02 18:17:10 +00:00
|
|
|
# Register node services for Z-Wave network
|
2016-09-30 15:43:18 +00:00
|
|
|
hass.services.register(DOMAIN, const.SERVICE_ADD_NODE, add_node,
|
|
|
|
descriptions[const.SERVICE_ADD_NODE])
|
|
|
|
hass.services.register(DOMAIN, const.SERVICE_ADD_NODE_SECURE,
|
|
|
|
add_node_secure,
|
|
|
|
descriptions[const.SERVICE_ADD_NODE_SECURE])
|
|
|
|
hass.services.register(DOMAIN, const.SERVICE_REMOVE_NODE, remove_node,
|
|
|
|
descriptions[const.SERVICE_REMOVE_NODE])
|
|
|
|
hass.services.register(DOMAIN, const.SERVICE_CANCEL_COMMAND,
|
|
|
|
cancel_command,
|
|
|
|
descriptions[const.SERVICE_CANCEL_COMMAND])
|
|
|
|
hass.services.register(DOMAIN, const.SERVICE_HEAL_NETWORK,
|
|
|
|
heal_network,
|
|
|
|
descriptions[const.SERVICE_HEAL_NETWORK])
|
|
|
|
hass.services.register(DOMAIN, const.SERVICE_SOFT_RESET, soft_reset,
|
|
|
|
descriptions[const.SERVICE_SOFT_RESET])
|
|
|
|
hass.services.register(DOMAIN, const.SERVICE_TEST_NETWORK,
|
|
|
|
test_network,
|
|
|
|
descriptions[const.SERVICE_TEST_NETWORK])
|
|
|
|
hass.services.register(DOMAIN, const.SERVICE_STOP_NETWORK, stop_zwave,
|
|
|
|
descriptions[const.SERVICE_STOP_NETWORK])
|
|
|
|
hass.services.register(DOMAIN, const.SERVICE_START_NETWORK,
|
|
|
|
start_zwave,
|
|
|
|
descriptions[const.SERVICE_START_NETWORK])
|
|
|
|
hass.services.register(DOMAIN, const.SERVICE_RENAME_NODE, rename_node,
|
|
|
|
descriptions[const.SERVICE_RENAME_NODE],
|
2016-08-21 21:36:44 +00:00
|
|
|
schema=RENAME_NODE_SCHEMA)
|
2016-10-05 05:04:19 +00:00
|
|
|
hass.services.register(DOMAIN, const.SERVICE_SET_CONFIG_PARAMETER,
|
|
|
|
set_config_parameter,
|
|
|
|
descriptions[
|
|
|
|
const.SERVICE_SET_CONFIG_PARAMETER],
|
|
|
|
schema=SET_CONFIG_PARAMETER_SCHEMA)
|
2017-01-01 20:10:45 +00:00
|
|
|
hass.services.register(DOMAIN, const.SERVICE_PRINT_CONFIG_PARAMETER,
|
|
|
|
print_config_parameter,
|
|
|
|
descriptions[
|
|
|
|
const.SERVICE_PRINT_CONFIG_PARAMETER],
|
|
|
|
schema=PRINT_CONFIG_PARAMETER_SCHEMA)
|
2016-10-16 18:36:06 +00:00
|
|
|
hass.services.register(DOMAIN, const.SERVICE_CHANGE_ASSOCIATION,
|
|
|
|
change_association,
|
|
|
|
descriptions[
|
|
|
|
const.SERVICE_CHANGE_ASSOCIATION],
|
|
|
|
schema=CHANGE_ASSOCIATION_SCHEMA)
|
2017-02-10 16:54:48 +00:00
|
|
|
hass.services.register(DOMAIN, const.SERVICE_SET_WAKEUP,
|
|
|
|
set_wakeup,
|
|
|
|
descriptions[
|
|
|
|
const.SERVICE_SET_WAKEUP],
|
|
|
|
schema=SET_WAKEUP_SCHEMA)
|
2017-02-17 20:03:55 +00:00
|
|
|
hass.services.register(DOMAIN, const.SERVICE_PRINT_NODE,
|
|
|
|
print_node,
|
|
|
|
descriptions[
|
|
|
|
const.SERVICE_PRINT_NODE],
|
|
|
|
schema=PRINT_NODE_SCHEMA)
|
2016-01-10 13:44:46 +00:00
|
|
|
|
2016-06-11 05:53:31 +00:00
|
|
|
# Setup autoheal
|
|
|
|
if autoheal:
|
|
|
|
_LOGGER.info("ZWave network autoheal is enabled.")
|
|
|
|
track_time_change(hass, heal_network, hour=0, minute=0, second=0)
|
|
|
|
|
2015-02-23 01:36:28 +00:00
|
|
|
hass.bus.listen_once(EVENT_HOMEASSISTANT_START, start_zwave)
|
2015-03-03 16:49:31 +00:00
|
|
|
|
|
|
|
return True
|
2016-01-26 21:11:27 +00:00
|
|
|
|
|
|
|
|
2017-01-27 06:21:33 +00:00
|
|
|
class ZWaveDeviceEntity(Entity):
|
2016-03-08 16:55:57 +00:00
|
|
|
"""Representation of a Z-Wave node entity."""
|
|
|
|
|
2016-01-26 21:11:27 +00:00
|
|
|
def __init__(self, value, domain):
|
2016-03-08 16:55:57 +00:00
|
|
|
"""Initialize the z-Wave device."""
|
2017-01-27 06:21:33 +00:00
|
|
|
# pylint: disable=import-error
|
|
|
|
from openzwave.network import ZWaveNetwork
|
|
|
|
from pydispatch import dispatcher
|
2016-01-26 21:11:27 +00:00
|
|
|
self._value = value
|
|
|
|
self.entity_id = "{}.{}".format(domain, self._object_id())
|
|
|
|
|
2017-01-27 06:21:33 +00:00
|
|
|
dispatcher.connect(
|
|
|
|
self.network_value_changed, ZWaveNetwork.SIGNAL_VALUE_CHANGED)
|
|
|
|
|
|
|
|
def network_value_changed(self, value):
|
|
|
|
"""Called when a value has changed on the network."""
|
|
|
|
if self._value.value_id == value.value_id or \
|
|
|
|
self._value.node == value.node:
|
|
|
|
_LOGGER.debug('Value changed for label %s', self._value.label)
|
|
|
|
self.value_changed(value)
|
|
|
|
|
|
|
|
def value_changed(self, value):
|
|
|
|
"""Called when a value for this entity's node has changed."""
|
|
|
|
self.update_properties()
|
|
|
|
self.schedule_update_ha_state()
|
|
|
|
|
2017-02-09 12:40:35 +00:00
|
|
|
def _value_handler(self, method=None, class_id=None, index=None,
|
|
|
|
label=None, data=None, member=None, **kwargs):
|
|
|
|
"""Get the values for a given command_class with arguments."""
|
|
|
|
if class_id is not None:
|
|
|
|
kwargs[CLASS_ID] = class_id
|
|
|
|
_LOGGER.debug('method=%s, class_id=%s, index=%s, label=%s, data=%s,'
|
|
|
|
' member=%s, kwargs=%s',
|
|
|
|
method, class_id, index, label, data, member, kwargs)
|
|
|
|
values = self._value.node.get_values(**kwargs).values()
|
|
|
|
_LOGGER.debug('values=%s', values)
|
2017-02-10 10:30:44 +00:00
|
|
|
results = None
|
2017-02-09 12:40:35 +00:00
|
|
|
if not values:
|
|
|
|
return None
|
|
|
|
for value in values:
|
|
|
|
if index is not None and value.index != index:
|
|
|
|
continue
|
|
|
|
if label is not None:
|
2017-02-12 23:42:09 +00:00
|
|
|
label_found = False
|
2017-02-09 12:40:35 +00:00
|
|
|
for entry in label:
|
2017-02-12 23:42:09 +00:00
|
|
|
if value.label == entry:
|
|
|
|
label_found = True
|
|
|
|
break
|
|
|
|
if not label_found:
|
|
|
|
continue
|
2017-02-09 12:40:35 +00:00
|
|
|
if method == 'set':
|
|
|
|
value.data = data
|
|
|
|
return
|
|
|
|
if data is not None and value.data != data:
|
|
|
|
continue
|
|
|
|
if member is not None:
|
2017-02-12 23:42:09 +00:00
|
|
|
results = getattr(value, member)
|
2017-02-09 12:40:35 +00:00
|
|
|
else:
|
|
|
|
results = value
|
|
|
|
break
|
|
|
|
_LOGGER.debug('final result=%s', results)
|
|
|
|
return results
|
|
|
|
|
|
|
|
def get_value(self, **kwargs):
|
|
|
|
"""Simplifyer to get values."""
|
|
|
|
return self._value_handler(method='get', **kwargs)
|
|
|
|
|
|
|
|
def set_value(self, **kwargs):
|
|
|
|
"""Simplifyer to set a value."""
|
|
|
|
return self._value_handler(method='set', **kwargs)
|
|
|
|
|
2017-01-27 06:21:33 +00:00
|
|
|
def update_properties(self):
|
|
|
|
"""Callback on data changes for node values."""
|
|
|
|
pass
|
|
|
|
|
2016-01-26 21:11:27 +00:00
|
|
|
@property
|
|
|
|
def should_poll(self):
|
2016-03-07 17:49:31 +00:00
|
|
|
"""No polling needed."""
|
2016-01-26 21:11:27 +00:00
|
|
|
return False
|
|
|
|
|
|
|
|
@property
|
|
|
|
def unique_id(self):
|
2016-03-08 16:55:57 +00:00
|
|
|
"""Return an unique ID."""
|
2016-01-26 21:11:27 +00:00
|
|
|
return "ZWAVE-{}-{}".format(self._value.node.node_id,
|
|
|
|
self._value.object_id)
|
|
|
|
|
|
|
|
@property
|
|
|
|
def name(self):
|
2016-03-08 16:55:57 +00:00
|
|
|
"""Return the name of the device."""
|
2016-02-03 11:52:35 +00:00
|
|
|
return _value_name(self._value)
|
2016-01-26 21:11:27 +00:00
|
|
|
|
|
|
|
def _object_id(self):
|
2016-03-08 16:55:57 +00:00
|
|
|
"""Return the object_id of the device value.
|
|
|
|
|
|
|
|
The object_id contains node_id and value instance id to not collide
|
|
|
|
with other entity_ids.
|
2016-03-07 17:49:31 +00:00
|
|
|
"""
|
2016-11-20 19:49:54 +00:00
|
|
|
return object_id(self._value)
|
2016-01-26 21:11:27 +00:00
|
|
|
|
|
|
|
@property
|
2016-02-04 01:12:33 +00:00
|
|
|
def device_state_attributes(self):
|
2016-03-08 16:55:57 +00:00
|
|
|
"""Return the device specific state attributes."""
|
2016-01-26 21:11:27 +00:00
|
|
|
attrs = {
|
2016-09-30 15:43:18 +00:00
|
|
|
const.ATTR_NODE_ID: self._value.node.node_id,
|
2016-01-26 21:11:27 +00:00
|
|
|
}
|
|
|
|
|
2017-01-02 17:45:10 +00:00
|
|
|
try:
|
|
|
|
battery_level = self._value.node.get_battery_level()
|
|
|
|
except RuntimeError:
|
|
|
|
# If we get an runtime error the dict has changed while
|
|
|
|
# we was looking for a value, just do it again
|
|
|
|
battery_level = self._value.node.get_battery_level()
|
2016-01-26 21:11:27 +00:00
|
|
|
|
|
|
|
if battery_level is not None:
|
|
|
|
attrs[ATTR_BATTERY_LEVEL] = battery_level
|
|
|
|
|
|
|
|
location = self._value.node.location
|
|
|
|
|
|
|
|
if location:
|
|
|
|
attrs[ATTR_LOCATION] = location
|
|
|
|
|
2017-02-10 16:54:48 +00:00
|
|
|
wakeup = _get_wakeup(self._value.node)
|
|
|
|
if wakeup:
|
|
|
|
attrs[ATTR_WAKEUP] = wakeup
|
|
|
|
|
2016-01-26 21:11:27 +00:00
|
|
|
return attrs
|