From 28a98e02e675f91545cc8ba37b8f196d17e65b92 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Mon, 27 Apr 2020 03:28:08 +0200 Subject: [PATCH 01/51] Copy from custom component --- CODEOWNERS | 1 + .../components/zwave_mqtt/__init__.py | 298 +++++++++++++++ .../components/zwave_mqtt/binary_sensor.py | 178 +++++++++ .../components/zwave_mqtt/config_flow.py | 21 + homeassistant/components/zwave_mqtt/const.py | 198 ++++++++++ homeassistant/components/zwave_mqtt/cover.py | 142 +++++++ .../components/zwave_mqtt/discovery.py | 359 ++++++++++++++++++ homeassistant/components/zwave_mqtt/entity.py | 253 ++++++++++++ homeassistant/components/zwave_mqtt/fan.py | 82 ++++ homeassistant/components/zwave_mqtt/light.py | 138 +++++++ .../components/zwave_mqtt/manifest.json | 16 + homeassistant/components/zwave_mqtt/sensor.py | 130 +++++++ .../components/zwave_mqtt/services.py | 171 +++++++++ .../components/zwave_mqtt/services.yaml | 208 ++++++++++ .../components/zwave_mqtt/strings.json | 21 + homeassistant/components/zwave_mqtt/switch.py | 49 +++ .../zwave_mqtt/translations/en.json | 21 + homeassistant/generated/config_flows.py | 3 +- requirements_all.txt | 3 + 19 files changed, 2291 insertions(+), 1 deletion(-) create mode 100644 homeassistant/components/zwave_mqtt/__init__.py create mode 100644 homeassistant/components/zwave_mqtt/binary_sensor.py create mode 100644 homeassistant/components/zwave_mqtt/config_flow.py create mode 100644 homeassistant/components/zwave_mqtt/const.py create mode 100644 homeassistant/components/zwave_mqtt/cover.py create mode 100644 homeassistant/components/zwave_mqtt/discovery.py create mode 100644 homeassistant/components/zwave_mqtt/entity.py create mode 100644 homeassistant/components/zwave_mqtt/fan.py create mode 100644 homeassistant/components/zwave_mqtt/light.py create mode 100644 homeassistant/components/zwave_mqtt/manifest.json create mode 100644 homeassistant/components/zwave_mqtt/sensor.py create mode 100644 homeassistant/components/zwave_mqtt/services.py create mode 100755 homeassistant/components/zwave_mqtt/services.yaml create mode 100644 homeassistant/components/zwave_mqtt/strings.json create mode 100644 homeassistant/components/zwave_mqtt/switch.py create mode 100644 homeassistant/components/zwave_mqtt/translations/en.json diff --git a/CODEOWNERS b/CODEOWNERS index 43959f67c30be..69a6ccd518a95 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -461,6 +461,7 @@ homeassistant/components/zha/* @dmulcahey @adminiuga homeassistant/components/zone/* @home-assistant/core homeassistant/components/zoneminder/* @rohankapoorcom homeassistant/components/zwave/* @home-assistant/z-wave +homeassistant/components/zwave_mqtt/* @cgarwood @MartinHjelmare # Individual files homeassistant/components/demo/weather @fabaff diff --git a/homeassistant/components/zwave_mqtt/__init__.py b/homeassistant/components/zwave_mqtt/__init__.py new file mode 100644 index 0000000000000..e2d0502bdb678 --- /dev/null +++ b/homeassistant/components/zwave_mqtt/__init__.py @@ -0,0 +1,298 @@ +"""The zwave_mqtt integration.""" +import asyncio +import json +import logging + +from openzwavemqtt import OZWManager, OZWOptions +from openzwavemqtt.const import ( + EVENT_INSTANCE_EVENT, + EVENT_NODE_ADDED, + EVENT_NODE_CHANGED, + EVENT_NODE_REMOVED, + EVENT_VALUE_ADDED, + EVENT_VALUE_CHANGED, + EVENT_VALUE_REMOVED, + CommandClass, + ValueType, +) +from openzwavemqtt.models.node import OZWNode +from openzwavemqtt.models.value import OZWValue +import voluptuous as vol + +from homeassistant.components import mqtt +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers.device_registry import async_get_registry as get_dev_reg +from homeassistant.helpers.dispatcher import async_dispatcher_send + +from . import const +from .const import DATA_UNSUBSCRIBE, DOMAIN, PLATFORMS, TOPIC_OPENZWAVE +from .discovery import DISCOVERY_SCHEMAS, check_node_schema, check_value_schema +from .entity import ZWaveDeviceEntityValues, create_device_id +from .services import ZWaveServices + +_LOGGER = logging.getLogger(__name__) + +CONFIG_SCHEMA = vol.Schema({DOMAIN: vol.Schema({})}, extra=vol.ALLOW_EXTRA) +DATA_DEVICES = "zwave-mqtt-devices" + + +async def async_setup(hass: HomeAssistant, config: dict): + """Initialize basic config of zwave_mqtt component.""" + hass.data[DOMAIN] = {} + return True + + +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): + """Set up zwave_mqtt from a config entry.""" + + @callback + def async_receive_message(msg): + manager.receive_message(msg.topic, msg.payload) + + platforms_loaded = [] + + async def mark_platform_loaded(platform): + platforms_loaded.append(platform) + + if len(platforms_loaded) != len(PLATFORMS): + return + + hass.data[DOMAIN][entry.entry_id][DATA_UNSUBSCRIBE].append( + await mqtt.async_subscribe( + hass, f"{TOPIC_OPENZWAVE}/#", async_receive_message + ) + ) + + hass.data[DOMAIN][entry.entry_id] = { + "mark_platform_loaded": mark_platform_loaded, + DATA_UNSUBSCRIBE: [], + } + + data_nodes = {} + data_values = {} + removed_nodes = [] + + @callback + def send_message(topic, payload): + mqtt.async_publish(hass, topic, json.dumps(payload)) + + options = OZWOptions(send_message=send_message, topic_prefix=f"{TOPIC_OPENZWAVE}/") + manager = OZWManager(options) + + for component in PLATFORMS: + hass.async_create_task( + hass.config_entries.async_forward_entry_setup(entry, component) + ) + + @callback + def async_node_added(node): + # Caution: This is also called on (re)start. + _LOGGER.debug("[NODE ADDED] node_id: %s", node.id) + data_nodes[node.id] = node + if node.id not in data_values: + data_values[node.id] = [] + + @callback + def async_node_changed(node): + _LOGGER.debug("[NODE CHANGED] node_id: %s", node.id) + data_nodes[node.id] = node + + @callback + def async_node_removed(node): + _LOGGER.debug("[NODE REMOVED] node_id: %s", node.id) + data_nodes.pop(node.id) + # node added/removed events also happen on (re)starts of hass/mqtt/ozw + # cleanup device/entity registry if we know this node is permanently deleted + # entities itself are removed by the values logic + if node.id in removed_nodes: + hass.async_create_task(handle_remove_node(hass, node)) + removed_nodes.remove(node.id) + + @callback + def async_instance_event(message): + event = message["event"] + event_data = message["data"] + _LOGGER.debug("[INSTANCE EVENT]: %s - data: %s", event, event_data) + # The actual removal action of a Z-Wave node is reported as instance event + # Only when this event is detected we cleanup the device and entities from hass + if event == "removenode" and "Node" in event_data: + removed_nodes.append(event_data["Node"]) + + @callback + def async_value_added(value): + node = value.node + node_id = value.node.node_id + + # Filter out CommandClasses we're definitely not interested in. + if value.command_class in [ + CommandClass.CONFIGURATION, + CommandClass.VERSION, + CommandClass.MANUFACTURER_SPECIFIC, + ]: + return + + _LOGGER.debug( + "[VALUE ADDED] node_id: %s - label: %s - value: %s - value_id: %s - CC: %s", + value.node.id, + value.label, + value.value, + value.value_id_key, + value.command_class, + ) + + node_data_values = data_values[node_id] + + # Check if this value should be tracked by an existing entity + value_unique_id = f"{value.node.id}-{value.value_id_key}" + for values in node_data_values: + values.check_value(value) + if values.values_id == value_unique_id: + return # this value already has an entity + + # Run discovery on it and see if any entities need created + for schema in DISCOVERY_SCHEMAS: + if not check_node_schema(node, schema): + continue + if not check_value_schema( + value, schema[const.DISC_VALUES][const.DISC_PRIMARY] + ): + continue + + values = ZWaveDeviceEntityValues(hass, options, schema, value) + values.setup() + + # We create a new list and update the reference here so that + # the list can be safely iterated over in the main thread + data_values[node_id] = node_data_values + [values] + + @callback + def async_value_changed(value): + # if an entity belonging to this value needs updating, + # it's handled within the entity logic + _LOGGER.debug( + "[VALUE CHANGED] node_id: %s - label: %s - value: %s - value_id: %s - CC: %s", + value.node.id, + value.label, + value.value, + value.value_id_key, + value.command_class, + ) + # Handle a scene activation message + if value.command_class in [ + CommandClass.SCENE_ACTIVATION, + CommandClass.CENTRAL_SCENE, + ]: + handle_scene_activated(hass, value) + return + + @callback + def async_value_removed(value): + _LOGGER.debug( + "[VALUE REMOVED] node_id: %s - label: %s - value: %s - value_id: %s - CC: %s", + value.node.id, + value.label, + value.value, + value.value_id_key, + value.command_class, + ) + # signal all entities using this value for removal + value_unique_id = f"{value.node.id}-{value.value_id_key}" + async_dispatcher_send(hass, const.SIGNAL_DELETE_ENTITY, value_unique_id) + # remove value from our local list + node_data_values = data_values[value.node.id] + node_data_values[:] = [ + item for item in node_data_values if item.values_id != value_unique_id + ] + + # Listen to events for node and value changes + options.listen(EVENT_NODE_ADDED, async_node_added) + options.listen(EVENT_VALUE_ADDED, async_value_added) + options.listen(EVENT_NODE_CHANGED, async_node_changed) + options.listen(EVENT_NODE_REMOVED, async_node_removed) + options.listen(EVENT_VALUE_CHANGED, async_value_changed) + options.listen(EVENT_VALUE_REMOVED, async_value_removed) + options.listen(EVENT_INSTANCE_EVENT, async_instance_event) + + # Register Services + services = ZWaveServices(hass, manager, data_nodes) + services.register() + + return True + + +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): + """Unload a config entry.""" + # cleanup platforms + unload_ok = all( + await asyncio.gather( + *[ + hass.config_entries.async_forward_entry_unload(entry, component) + for component in PLATFORMS + ] + ) + ) + if not unload_ok: + return False + + # unsubscribe all listeners + for unsubscribe_listener in hass.data[DOMAIN][entry.entry_id][DATA_UNSUBSCRIBE]: + unsubscribe_listener() + hass.data[DOMAIN][entry.entry_id][DATA_UNSUBSCRIBE].clear() + hass.data[DOMAIN].pop(entry.entry_id) + + return True + + +async def handle_remove_node(hass: HomeAssistant, node: OZWNode): + """Handle the removal of a Z-Wave node, removing all traces in device/entity registry.""" + dev_registry = await get_dev_reg(hass) + # grab device in device registry attached to this node + dev_id = create_device_id(node) + device = dev_registry.async_get_device({(DOMAIN, dev_id)}, set()) + if device: + devices_to_remove = [device.id] + # also grab slave devices (node instances) + for item in dev_registry.devices.values(): + if item.via_device_id == device.id: + devices_to_remove.append(item.id) + # remove all devices in registry related to this node + # note: removal of entity registry is handled by core + for dev_id in devices_to_remove: + dev_registry.async_remove_device(dev_id) + + +@callback +def handle_scene_activated(hass: HomeAssistant, scene_value: OZWValue): + """Handle a (central) scene activation message.""" + node_id = scene_value.node.id + scene_id = scene_value.index + scene_label = scene_value.label + if scene_value.command_class == CommandClass.SCENE_ACTIVATION: + # legacy/network scene + scene_value_id = scene_value.value + scene_value_label = scene_value.label + else: + # central scene command + if scene_value.type != ValueType.LIST: + return + scene_value_label = scene_value.value["Selected"] + scene_value_id = scene_value.value["Selected_id"] + + _LOGGER.debug( + "[SCENE_ACTIVATED] node_id: %s - scene_id: %s - scene_value_id: %s", + node_id, + scene_id, + scene_value_id, + ) + # Simply forward it to the hass event bus + hass.bus.async_fire( + const.EVENT_SCENE_ACTIVATED, + { + const.ATTR_NODE_ID: node_id, + const.ATTR_SCENE_ID: scene_id, + const.ATTR_SCENE_LABEL: scene_label, + const.ATTR_SCENE_VALUE_ID: scene_value_id, + const.ATTR_SCENE_VALUE_LABEL: scene_value_label, + }, + ) diff --git a/homeassistant/components/zwave_mqtt/binary_sensor.py b/homeassistant/components/zwave_mqtt/binary_sensor.py new file mode 100644 index 0000000000000..8ba4b0a0ab077 --- /dev/null +++ b/homeassistant/components/zwave_mqtt/binary_sensor.py @@ -0,0 +1,178 @@ +"""Representation of Z-Wave binary_sensors.""" + +import logging + +from openzwavemqtt.const import ValueType + +from homeassistant.components.binary_sensor import ( + DEVICE_CLASS_DOOR, + DEVICE_CLASS_GAS, + DEVICE_CLASS_HEAT, + DEVICE_CLASS_MOISTURE, + DEVICE_CLASS_MOTION, + DEVICE_CLASS_POWER, + DEVICE_CLASS_PROBLEM, + DEVICE_CLASS_SAFETY, + DEVICE_CLASS_SMOKE, + DEVICE_CLASS_SOUND, + BinarySensorDevice, +) +from homeassistant.core import callback +from homeassistant.helpers.dispatcher import async_dispatcher_connect + +from .const import DATA_UNSUBSCRIBE, DOMAIN +from .entity import ZWaveDeviceEntity, create_device_name + +_LOGGER = logging.getLogger(__name__) + +DEVICE_CLASS_MAPPING = { + # Mapping from Value Index in Notification CC to device class + 1: DEVICE_CLASS_SMOKE, + 2: DEVICE_CLASS_GAS, + 3: DEVICE_CLASS_GAS, + 4: DEVICE_CLASS_HEAT, + 5: DEVICE_CLASS_MOISTURE, + 6: DEVICE_CLASS_SAFETY, + 7: DEVICE_CLASS_SAFETY, + 8: DEVICE_CLASS_POWER, + 9: DEVICE_CLASS_PROBLEM, + 10: DEVICE_CLASS_PROBLEM, + 14: DEVICE_CLASS_SOUND, + 15: DEVICE_CLASS_MOISTURE, + 18: DEVICE_CLASS_GAS, +} + + +async def async_setup_entry(hass, config_entry, async_add_entities): + """Set up Z-Wave binary_sensor from config entry.""" + + @callback + def async_add_binary_sensor(values): + """Add Z-Wave Binary Sensor.""" + sensors_to_add = [] + + if values.primary.type == ValueType.LIST: + + # Handle special cases + # we convert some of the Notification values into it's own binary sensor + # https://github.com/OpenZWave/open-zwave/blob/master/config/NotificationCCTypes.xml + # TODO: Use constants/Enums from lib (when added) + for item in values.primary.value["List"]: + if values.primary.index == 6 and item["Value"] == 22: + # Door/Window Open + sensors_to_add.append( + ZWaveListValueSensor(values, item["Value"], DEVICE_CLASS_DOOR) + ) + if values.primary.index == 7 and item["Value"] in [7, 8]: + # Motion detected + sensors_to_add.append( + ZWaveListValueSensor(values, item["Value"], DEVICE_CLASS_MOTION) + ) + + # Fallback to a generic binary sensor for the notification topic + if not sensors_to_add: + sensors_to_add.append(ZWaveListSensor(values)) + + elif values.primary.type == ValueType.BOOL: + # classic/legacy binary sensor + sensors_to_add.append(ZWaveBinarySensor(values)) + else: + # should not happen but just in case log it while we're in beta + _LOGGER.warning("Sensor not implemented for value %s", values.primary.label) + return + + async_add_entities(sensors_to_add) + + hass.data[DOMAIN][config_entry.entry_id][DATA_UNSUBSCRIBE].append( + async_dispatcher_connect( + hass, "zwave_new_binary_sensor", async_add_binary_sensor + ) + ) + + await hass.data[DOMAIN][config_entry.entry_id]["mark_platform_loaded"]( + "binary_sensor" + ) + + +class ZWaveBinarySensor(ZWaveDeviceEntity, BinarySensorDevice): + """Representation of a Z-Wave binary_sensor.""" + + @property + def is_on(self): + """Return if the sensor is on or off.""" + return self.values.primary.value + + @property + def entity_registry_enabled_default(self) -> bool: + """Return if the entity should be enabled when first added to the entity registry.""" + # Legacy binary sensors are phased out (replaced by notification sensors) + # Disable by default to not confuse users + return False + + +class ZWaveListSensor(ZWaveDeviceEntity, BinarySensorDevice): + """Representation of a ZWaveListSensor translated to binary_sensor.""" + + @property + def is_on(self): + """Return if the sensor is on or off.""" + return self.values.primary.value["Selected"] != "Clear" + + @property + def device_state_attributes(self): + """Return the device specific state attributes.""" + attributes = super().device_state_attributes + attributes["event"] = self.values.primary.value["Selected"] + return attributes + + @property + def device_class(self): + """Return the class of this device, from component DEVICE_CLASSES.""" + return DEVICE_CLASS_MAPPING.get(self.values.primary.index) + + @property + def entity_registry_enabled_default(self) -> bool: + """Return if the entity should be enabled when first added to the entity registry.""" + # We hide some of the more advanced sensors by default to not overwhelm users + if self.values.primary.index in [8, 9]: + return False + return True + + +class ZWaveListValueSensor(ZWaveDeviceEntity, BinarySensorDevice): + """Representation of a ZWaveListValueSensor binary_sensor.""" + + def __init__(self, values, list_value, device_class=None): + """Initialize a ZWaveListValueSensor entity.""" + self._list_value = list_value + self._device_class = device_class + super().__init__(values) + + @property + def name(self): + """Return the name of the entity.""" + node = self.values.primary.node + value_label = "" + for item in self.values.primary.value["List"]: + if item["Value"] == self._list_value: + value_label = item["Label"] + break + value_label = value_label.split(" on ")[0] # strip "on location" from name + value_label = value_label.split(" at ")[0] # strip "at location" from name + return f"{create_device_name(node)}: {value_label}" + + @property + def unique_id(self): + """Return the unique_id of the entity.""" + unique_id = super().unique_id + return f"{unique_id}.{self._list_value}" + + @property + def is_on(self): + """Return if the sensor is on or off.""" + return self.values.primary.value["Selected_id"] == self._list_value + + @property + def device_class(self): + """Return the class of this device, from component DEVICE_CLASSES.""" + return self._device_class diff --git a/homeassistant/components/zwave_mqtt/config_flow.py b/homeassistant/components/zwave_mqtt/config_flow.py new file mode 100644 index 0000000000000..0f698225efd1a --- /dev/null +++ b/homeassistant/components/zwave_mqtt/config_flow.py @@ -0,0 +1,21 @@ +"""Config flow for zwave_mqtt integration.""" +import logging + +from homeassistant import config_entries + +from .const import DOMAIN # pylint:disable=unused-import + +_LOGGER = logging.getLogger(__name__) + +TITLE = "Z-Wave MQTT" + + +class DomainConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): + """Handle a config flow for zwave_mqtt.""" + + VERSION = 1 + CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_PUSH + + async def async_step_user(self, user_input=None): + """Handle the initial step.""" + return self.async_create_entry(title=TITLE, data={}) diff --git a/homeassistant/components/zwave_mqtt/const.py b/homeassistant/components/zwave_mqtt/const.py new file mode 100644 index 0000000000000..f0ac0bbca5510 --- /dev/null +++ b/homeassistant/components/zwave_mqtt/const.py @@ -0,0 +1,198 @@ +"""Constants for the zwave_mqtt integration.""" + +DOMAIN = "zwave_mqtt" +DATA_UNSUBSCRIBE = "unsubscribe" +PLATFORMS = ["binary_sensor", "cover", "fan", "sensor", "switch", "light"] + +# MQTT Topics +TOPIC_OPENZWAVE = "OpenZWave" + +# Common Attributes +ATTR_INSTANCE_ID = "instance_id" +ATTR_SECURE = "secure" +ATTR_CONFIG_PARAMETER = "parameter" +ATTR_CONFIG_VALUE = "value" +ATTR_CONFIG_SIZE = "size" +ATTR_NODE_ID = "node_id" +ATTR_SCENE_ID = "scene_id" +ATTR_SCENE_LABEL = "scene_label" +ATTR_SCENE_VALUE_ID = "scene_value_id" +ATTR_SCENE_VALUE_LABEL = "scene_value_label" + +# Service specific +SERVICE_ADD_NODE = "add_node" +SERVICE_REMOVE_NODE = "remove_node" +SERVICE_REMOVE_FAILED_NODE = "remove_failed_node" +SERVICE_REPLACE_FAILED_NODE = "replace_failed_node" +SERVICE_CANCEL_COMMAND = "cancel_command" +SERVICE_SET_CONFIG_PARAMETER = "set_config_parameter" + +# Home Assistant Events +EVENT_SCENE_ACTIVATED = f"{DOMAIN}.scene_activated" + +# Signals +SIGNAL_DELETE_ENTITY = f"{DOMAIN}_delete_entity" + +# Discovery Information +GENERIC_TYPE_WHATEVER = None # Match ALL +SPECIFIC_TYPE_WHATEVER = None # Match ALL +SPECIFIC_TYPE_NOT_USED = 0 # Available in all Generic types + +GENERIC_TYPE_AV_CONTROL_POINT = 3 +SPECIFIC_TYPE_DOORBELL = 18 +SPECIFIC_TYPE_SATELLITE_RECEIVER = 4 +SPECIFIC_TYPE_SATELLITE_RECEIVER_V2 = 17 + +GENERIC_TYPE_DISPLAY = 4 +SPECIFIC_TYPE_SIMPLE_DISPLAY = 1 + +GENERIC_TYPE_ENTRY_CONTROL = 64 +SPECIFIC_TYPE_DOOR_LOCK = 1 +SPECIFIC_TYPE_ADVANCED_DOOR_LOCK = 2 +SPECIFIC_TYPE_SECURE_KEYPAD_DOOR_LOCK = 3 +SPECIFIC_TYPE_SECURE_KEYPAD_DOOR_LOCK_DEADBOLT = 4 +SPECIFIC_TYPE_SECURE_DOOR = 5 +SPECIFIC_TYPE_SECURE_GATE = 6 +SPECIFIC_TYPE_SECURE_BARRIER_ADDON = 7 +SPECIFIC_TYPE_SECURE_BARRIER_OPEN_ONLY = 8 +SPECIFIC_TYPE_SECURE_BARRIER_CLOSE_ONLY = 9 +SPECIFIC_TYPE_SECURE_LOCKBOX = 10 +SPECIFIC_TYPE_SECURE_KEYPAD = 11 + +GENERIC_TYPE_GENERIC_CONTROLLER = 1 +SPECIFIC_TYPE_PORTABLE_CONTROLLER = 1 +SPECIFIC_TYPE_PORTABLE_SCENE_CONTROLLER = 2 +SPECIFIC_TYPE_PORTABLE_INSTALLER_TOOL = 3 +SPECIFIC_TYPE_REMOTE_CONTROL_AV = 4 +SPECIFIC_TYPE_REMOTE_CONTROL_SIMPLE = 6 + +GENERIC_TYPE_METER = 49 +SPECIFIC_TYPE_SIMPLE_METER = 1 +SPECIFIC_TYPE_ADV_ENERGY_CONTROL = 2 +SPECIFIC_TYPE_WHOLE_HOME_METER_SIMPLE = 3 + +GENERIC_TYPE_METER_PULSE = 48 + +GENERIC_TYPE_NON_INTEROPERABLE = 255 + +GENERIC_TYPE_REPEATER_SLAVE = 15 +SPECIFIC_TYPE_REPEATER_SLAVE = 1 +SPECIFIC_TYPE_VIRTUAL_NODE = 2 + +GENERIC_TYPE_SECURITY_PANEL = 23 +SPECIFIC_TYPE_ZONED_SECURITY_PANEL = 1 + +GENERIC_TYPE_SEMI_INTEROPERABLE = 80 +SPECIFIC_TYPE_ENERGY_PRODUCTION = 1 + +GENERIC_TYPE_SENSOR_ALARM = 161 +SPECIFIC_TYPE_ADV_ZENSOR_NET_ALARM_SENSOR = 5 +SPECIFIC_TYPE_ADV_ZENSOR_NET_SMOKE_SENSOR = 10 +SPECIFIC_TYPE_BASIC_ROUTING_ALARM_SENSOR = 1 +SPECIFIC_TYPE_BASIC_ROUTING_SMOKE_SENSOR = 6 +SPECIFIC_TYPE_BASIC_ZENSOR_NET_ALARM_SENSOR = 3 +SPECIFIC_TYPE_BASIC_ZENSOR_NET_SMOKE_SENSOR = 8 +SPECIFIC_TYPE_ROUTING_ALARM_SENSOR = 2 +SPECIFIC_TYPE_ROUTING_SMOKE_SENSOR = 7 +SPECIFIC_TYPE_ZENSOR_NET_ALARM_SENSOR = 4 +SPECIFIC_TYPE_ZENSOR_NET_SMOKE_SENSOR = 9 +SPECIFIC_TYPE_ALARM_SENSOR = 11 + +GENERIC_TYPE_SENSOR_BINARY = 32 +SPECIFIC_TYPE_ROUTING_SENSOR_BINARY = 1 + +GENERIC_TYPE_SENSOR_MULTILEVEL = 33 +SPECIFIC_TYPE_ROUTING_SENSOR_MULTILEVEL = 1 +SPECIFIC_TYPE_CHIMNEY_FAN = 2 + +GENERIC_TYPE_STATIC_CONTROLLER = 2 +SPECIFIC_TYPE_PC_CONTROLLER = 1 +SPECIFIC_TYPE_SCENE_CONTROLLER = 2 +SPECIFIC_TYPE_STATIC_INSTALLER_TOOL = 3 +SPECIFIC_TYPE_SET_TOP_BOX = 4 +SPECIFIC_TYPE_SUB_SYSTEM_CONTROLLER = 5 +SPECIFIC_TYPE_TV = 6 +SPECIFIC_TYPE_GATEWAY = 7 + +GENERIC_TYPE_SWITCH_BINARY = 16 +SPECIFIC_TYPE_POWER_SWITCH_BINARY = 1 +SPECIFIC_TYPE_SCENE_SWITCH_BINARY = 3 +SPECIFIC_TYPE_POWER_STRIP = 4 +SPECIFIC_TYPE_SIREN = 5 +SPECIFIC_TYPE_VALVE_OPEN_CLOSE = 6 +SPECIFIC_TYPE_COLOR_TUNABLE_BINARY = 2 +SPECIFIC_TYPE_IRRIGATION_CONTROLLER = 7 + +GENERIC_TYPE_SWITCH_MULTILEVEL = 17 +SPECIFIC_TYPE_CLASS_A_MOTOR_CONTROL = 5 +SPECIFIC_TYPE_CLASS_B_MOTOR_CONTROL = 6 +SPECIFIC_TYPE_CLASS_C_MOTOR_CONTROL = 7 +SPECIFIC_TYPE_MOTOR_MULTIPOSITION = 3 +SPECIFIC_TYPE_POWER_SWITCH_MULTILEVEL = 1 +SPECIFIC_TYPE_SCENE_SWITCH_MULTILEVEL = 4 +SPECIFIC_TYPE_FAN_SWITCH = 8 +SPECIFIC_TYPE_COLOR_TUNABLE_MULTILEVEL = 2 + +GENERIC_TYPE_SWITCH_REMOTE = 18 +SPECIFIC_TYPE_REMOTE_BINARY = 1 +SPECIFIC_TYPE_REMOTE_MULTILEVEL = 2 +SPECIFIC_TYPE_REMOTE_TOGGLE_BINARY = 3 +SPECIFIC_TYPE_REMOTE_TOGGLE_MULTILEVEL = 4 + +GENERIC_TYPE_SWITCH_TOGGLE = 19 +SPECIFIC_TYPE_SWITCH_TOGGLE_BINARY = 1 +SPECIFIC_TYPE_SWITCH_TOGGLE_MULTILEVEL = 2 + +GENERIC_TYPE_THERMOSTAT = 8 +SPECIFIC_TYPE_SETBACK_SCHEDULE_THERMOSTAT = 3 +SPECIFIC_TYPE_SETBACK_THERMOSTAT = 5 +SPECIFIC_TYPE_SETPOINT_THERMOSTAT = 4 +SPECIFIC_TYPE_THERMOSTAT_GENERAL = 2 +SPECIFIC_TYPE_THERMOSTAT_GENERAL_V2 = 6 +SPECIFIC_TYPE_THERMOSTAT_HEATING = 1 + +GENERIC_TYPE_VENTILATION = 22 +SPECIFIC_TYPE_RESIDENTIAL_HRV = 1 + +GENERIC_TYPE_WINDOWS_COVERING = 9 +SPECIFIC_TYPE_SIMPLE_WINDOW_COVERING = 1 + +GENERIC_TYPE_ZIP_NODE = 21 +SPECIFIC_TYPE_ZIP_ADV_NODE = 2 +SPECIFIC_TYPE_ZIP_TUN_NODE = 1 + +GENERIC_TYPE_WALL_CONTROLLER = 24 +SPECIFIC_TYPE_BASIC_WALL_CONTROLLER = 1 + +GENERIC_TYPE_NETWORK_EXTENDER = 5 +SPECIFIC_TYPE_SECURE_EXTENDER = 1 + +GENERIC_TYPE_APPLIANCE = 6 +SPECIFIC_TYPE_GENERAL_APPLIANCE = 1 +SPECIFIC_TYPE_KITCHEN_APPLIANCE = 2 +SPECIFIC_TYPE_LAUNDRY_APPLIANCE = 3 + +GENERIC_TYPE_SENSOR_NOTIFICATION = 7 +SPECIFIC_TYPE_NOTIFICATION_SENSOR = 1 + + +DISC_COMMAND_CLASS = "command_class" +DISC_COMPONENT = "component" +DISC_GENERIC_DEVICE_CLASS = "generic_device_class" +DISC_GENRE = "genre" +DISC_INDEX = "index" +DISC_INSTANCE = "instance" +DISC_NODE_ID = "node_id" +DISC_OPTIONAL = "optional" +DISC_PRIMARY = "primary" +DISC_SCHEMAS = "schemas" +DISC_SPECIFIC_DEVICE_CLASS = "specific_device_class" +DISC_TYPE = "type" +DISC_VALUES = "values" + + +# Manufacturer IDs +MANUFACTURER_ID_FIBARO = "0x010f" + +# Product Types +PRODUCT_TYPE_FIBARO_FGRM222 = "0x0302" diff --git a/homeassistant/components/zwave_mqtt/cover.py b/homeassistant/components/zwave_mqtt/cover.py new file mode 100644 index 0000000000000..09c74a8463dce --- /dev/null +++ b/homeassistant/components/zwave_mqtt/cover.py @@ -0,0 +1,142 @@ +"""Support for Z-Wave cover.""" +import logging + +from openzwavemqtt.const import CommandClass + +from homeassistant.components.cover import ( + ATTR_POSITION, + ATTR_TILT_POSITION, + SUPPORT_CLOSE, + SUPPORT_CLOSE_TILT, + SUPPORT_OPEN, + SUPPORT_OPEN_TILT, + SUPPORT_SET_POSITION, + SUPPORT_SET_TILT_POSITION, + CoverDevice, +) +from homeassistant.core import callback +from homeassistant.helpers.dispatcher import async_dispatcher_connect + +from .const import ( + DATA_UNSUBSCRIBE, + DOMAIN, + MANUFACTURER_ID_FIBARO, + PRODUCT_TYPE_FIBARO_FGRM222, +) +from .entity import ZWaveDeviceEntity + +_LOGGER = logging.getLogger(__name__) + +SUPPORTED_FEATURES_POSITION = SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_SET_POSITION +SUPPORTED_FEATURES_TILT = ( + SUPPORT_OPEN_TILT | SUPPORT_CLOSE_TILT | SUPPORT_SET_TILT_POSITION +) + + +async def async_setup_entry(hass, config_entry, async_add_entities): + """Set up Z-Wave Cover from Config Entry.""" + + @callback + def async_add_cover(values): + """Add Z-Wave Cover.""" + # Specific Cover Types + if values.primary.command_class != CommandClass.SWITCH_MULTILEVEL: + _LOGGER.warning("Cover not implemented for values %s", values.primary) + return + + if ( + values.primary.node.node_manufacturer_id == MANUFACTURER_ID_FIBARO + and values.primary.node.node_product_type == PRODUCT_TYPE_FIBARO_FGRM222 + ): + cover = FibaroFGRM222Cover(values) + else: + cover = ZWaveCover(values) + + async_add_entities([cover]) + + hass.data[DOMAIN][config_entry.entry_id][DATA_UNSUBSCRIBE].append( + async_dispatcher_connect(hass, "zwave_new_cover", async_add_cover) + ) + + await hass.data[DOMAIN][config_entry.entry_id]["mark_platform_loaded"]("cover") + + +class ZWaveCover(ZWaveDeviceEntity, CoverDevice): + """Representation of a Z-Wave cover.""" + + @property + def supported_features(self): + """Flag supported features.""" + return SUPPORTED_FEATURES_POSITION + + @property + def is_closed(self): + """Return true if cover is closed.""" + return self.values.primary.value < 5 + + @property + def current_cover_position(self): + """Return the current position of cover where 0 means closed and 100 is fully open.""" + return self.values.primary.value + + async def async_set_cover_position(self, **kwargs): + """Move the cover to a specific position.""" + self.values.primary.send_value(kwargs[ATTR_POSITION]) + + async def async_open_cover(self, **kwargs): + """Open the cover.""" + self.values.primary.send_value(99) + + async def async_close_cover(self, **kwargs): + """Close cover.""" + self.values.primary.send_value(0) + + +class FibaroFGRM222Cover(ZWaveDeviceEntity, CoverDevice): + """Representation of a Fibaro FGRM-222 cover.""" + + @property + def supported_features(self): + """Flag supported features.""" + return SUPPORTED_FEATURES_POSITION | SUPPORTED_FEATURES_TILT + + @property + def is_closed(self): + """Return true if cover is closed.""" + return self.values.fgrm222_slat_position.value < 5 + + @property + def current_cover_position(self): + """Return the current position of cover where 0 means closed and 100 is fully open.""" + return self.values.fgrm222_slat_position.value + + @property + def current_cover_tilt_position(self): + """Return the current tilt position of the cover where 0 means closed/no tilt and 100 means open/maximum tilt.""" + return self.values.fgrm222_tilt_position.value + + async def async_open_cover(self, **kwargs): + """Open the cover.""" + self.values.fgrm222_slat_position.send_value(99) + self.values.fgrm222_tilt_position.send_value(99) + + async def async_close_cover(self, **kwargs): + """Close cover.""" + self.values.fgrm222_slat_position.send_value(0) + self.values.fgrm222_tilt_position.send_value(0) + + async def async_set_cover_position(self, **kwargs): + """Move the cover to a specific position.""" + self.values.fgrm222_slat_position.send_value(kwargs[ATTR_POSITION]) + + async def async_set_cover_tilt_position(self, **kwargs): + """Move the cover tilt to a specific position.""" + self.values.fgrm222_tilt_position.send_value(kwargs[ATTR_TILT_POSITION]) + + async def async_open_cover_tilt(self, **kwargs): + """Open the cover tilt.""" + self.values.fgrm222_tilt_position.send_value(99) + + async def async_close_cover_tilt(self, **kwargs): + """Close the cover tilt.""" + self.values.fgrm222_tilt_position.send_value(0) diff --git a/homeassistant/components/zwave_mqtt/discovery.py b/homeassistant/components/zwave_mqtt/discovery.py new file mode 100644 index 0000000000000..3607abdc15939 --- /dev/null +++ b/homeassistant/components/zwave_mqtt/discovery.py @@ -0,0 +1,359 @@ +"""Map Z-Wave nodes and values to Home Assistant entities.""" + +import logging + +from openzwavemqtt.const import CommandClass, ValueGenre, ValueIndex, ValueType + +from . import const + +_LOGGER = logging.getLogger(__name__) + +DISCOVERY_SCHEMAS = [ + { # Binary sensors + const.DISC_COMPONENT: "binary_sensor", + const.DISC_GENERIC_DEVICE_CLASS: [ + const.GENERIC_TYPE_ENTRY_CONTROL, + 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_THERMOSTAT, + const.GENERIC_TYPE_SENSOR_NOTIFICATION, + ], + const.DISC_VALUES: { + const.DISC_PRIMARY: { + const.DISC_COMMAND_CLASS: [CommandClass.SENSOR_BINARY], + const.DISC_TYPE: ValueType.BOOL, + const.DISC_GENRE: ValueGenre.USER, + }, + "off_delay": { + const.DISC_COMMAND_CLASS: [CommandClass.CONFIGURATION], + const.DISC_INDEX: [9], + const.DISC_OPTIONAL: True, + }, + }, + }, + { # Notification CommandClass translates to binary_sensor + const.DISC_COMPONENT: "binary_sensor", + const.DISC_VALUES: { + const.DISC_PRIMARY: { + const.DISC_COMMAND_CLASS: [CommandClass.NOTIFICATION], + const.DISC_GENRE: ValueGenre.USER, + const.DISC_TYPE: [ValueType.BOOL, ValueType.LIST], + } + }, + }, + { # Thermostat translates to climate + const.DISC_COMPONENT: "climate", + const.DISC_GENERIC_DEVICE_CLASS: [ + const.GENERIC_TYPE_THERMOSTAT, + const.GENERIC_TYPE_SENSOR_MULTILEVEL, + ], + const.DISC_VALUES: { + const.DISC_PRIMARY: { + const.DISC_COMMAND_CLASS: [CommandClass.THERMOSTAT_SETPOINT] + }, + "temperature": { + const.DISC_COMMAND_CLASS: [CommandClass.SENSOR_MULTILEVEL], + const.DISC_INDEX: [ValueIndex.SENSOR_MULTILEVEL_TEMPERATURE], + const.DISC_OPTIONAL: True, + }, + "mode": { + const.DISC_COMMAND_CLASS: [CommandClass.THERMOSTAT_MODE], + const.DISC_OPTIONAL: True, + }, + "fan_mode": { + const.DISC_COMMAND_CLASS: [CommandClass.THERMOSTAT_FAN_MODE], + const.DISC_OPTIONAL: True, + }, + "operating_state": { + const.DISC_COMMAND_CLASS: [CommandClass.THERMOSTAT_OPERATING_STATE], + const.DISC_OPTIONAL: True, + }, + "fan_action": { + const.DISC_COMMAND_CLASS: [CommandClass.THERMOSTAT_FAN_STATE], + const.DISC_OPTIONAL: True, + }, + "zxt_120_swing_mode": { + const.DISC_COMMAND_CLASS: [CommandClass.CONFIGURATION], + const.DISC_INDEX: [33], + const.DISC_OPTIONAL: True, + }, + }, + }, + { # Rollershutter + const.DISC_COMPONENT: "cover", + const.DISC_GENERIC_DEVICE_CLASS: [ + const.GENERIC_TYPE_SWITCH_MULTILEVEL, + const.GENERIC_TYPE_ENTRY_CONTROL, + ], + const.DISC_SPECIFIC_DEVICE_CLASS: [ + 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.DISC_VALUES: { + const.DISC_PRIMARY: { + const.DISC_COMMAND_CLASS: [CommandClass.SWITCH_MULTILEVEL], + const.DISC_INDEX: [ValueIndex.SWITCH_MULTILEVEL_LEVEL], + const.DISC_GENRE: ValueGenre.USER, + }, + "open": { + const.DISC_COMMAND_CLASS: [CommandClass.SWITCH_MULTILEVEL], + const.DISC_INDEX: [ValueIndex.SWITCH_MULTILEVEL_BRIGHT], + const.DISC_OPTIONAL: True, + }, + "close": { + const.DISC_COMMAND_CLASS: [CommandClass.SWITCH_MULTILEVEL], + const.DISC_INDEX: [ValueIndex.SWITCH_MULTILEVEL_DIM], + const.DISC_OPTIONAL: True, + }, + "fgrm222_slat_position": { + const.DISC_COMMAND_CLASS: [CommandClass.MANUFACTURER_PROPRIETARY], + const.DISC_INDEX: [0], + const.DISC_OPTIONAL: True, + }, + "fgrm222_tilt_position": { + const.DISC_COMMAND_CLASS: [CommandClass.MANUFACTURER_PROPRIETARY], + const.DISC_INDEX: [1], + const.DISC_OPTIONAL: True, + }, + }, + }, + { # Garage Door Switch + const.DISC_COMPONENT: "cover", + const.DISC_GENERIC_DEVICE_CLASS: [ + const.GENERIC_TYPE_SWITCH_MULTILEVEL, + const.GENERIC_TYPE_ENTRY_CONTROL, + ], + const.DISC_SPECIFIC_DEVICE_CLASS: [ + 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.DISC_VALUES: { + const.DISC_PRIMARY: { + const.DISC_COMMAND_CLASS: [CommandClass.SWITCH_BINARY], + const.DISC_GENRE: ValueGenre.USER, + } + }, + }, + { # Garage Door Barrier + const.DISC_COMPONENT: "cover", + const.DISC_GENERIC_DEVICE_CLASS: [ + const.GENERIC_TYPE_SWITCH_MULTILEVEL, + const.GENERIC_TYPE_ENTRY_CONTROL, + ], + const.DISC_SPECIFIC_DEVICE_CLASS: [ + 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.DISC_VALUES: { + const.DISC_PRIMARY: { + const.DISC_COMMAND_CLASS: [CommandClass.BARRIER_OPERATOR], + const.DISC_INDEX: [ValueIndex.BARRIER_OPERATOR_LABEL], + } + }, + }, + { # Fan + const.DISC_COMPONENT: "fan", + const.DISC_GENERIC_DEVICE_CLASS: [const.GENERIC_TYPE_SWITCH_MULTILEVEL], + const.DISC_SPECIFIC_DEVICE_CLASS: [const.SPECIFIC_TYPE_FAN_SWITCH], + const.DISC_VALUES: { + const.DISC_PRIMARY: { + const.DISC_COMMAND_CLASS: [CommandClass.SWITCH_MULTILEVEL], + const.DISC_INDEX: [ValueIndex.SWITCH_MULTILEVEL_LEVEL], + const.DISC_TYPE: ValueType.BYTE, + } + }, + }, + { # Light + const.DISC_COMPONENT: "light", + const.DISC_GENERIC_DEVICE_CLASS: [ + const.GENERIC_TYPE_SWITCH_MULTILEVEL, + const.GENERIC_TYPE_SWITCH_REMOTE, + ], + const.DISC_SPECIFIC_DEVICE_CLASS: [ + const.SPECIFIC_TYPE_POWER_SWITCH_MULTILEVEL, + const.SPECIFIC_TYPE_SCENE_SWITCH_MULTILEVEL, + const.SPECIFIC_TYPE_NOT_USED, + ], + const.DISC_VALUES: { + const.DISC_PRIMARY: { + const.DISC_COMMAND_CLASS: [CommandClass.SWITCH_MULTILEVEL], + const.DISC_INDEX: [ValueIndex.SWITCH_MULTILEVEL_LEVEL], + const.DISC_TYPE: ValueType.BYTE, + }, + "dimming_duration": { + const.DISC_COMMAND_CLASS: [CommandClass.SWITCH_MULTILEVEL], + const.DISC_INDEX: [ValueIndex.SWITCH_MULTILEVEL_DURATION], + const.DISC_OPTIONAL: True, + }, + "color": { + const.DISC_COMMAND_CLASS: [CommandClass.SWITCH_COLOR], + const.DISC_INDEX: [ValueIndex.SWITCH_COLOR_COLOR], + const.DISC_OPTIONAL: True, + }, + "color_channels": { + const.DISC_COMMAND_CLASS: [CommandClass.SWITCH_COLOR], + const.DISC_INDEX: [ValueIndex.SWITCH_COLOR_CHANNELS], + const.DISC_OPTIONAL: True, + }, + }, + }, + { # Lock + const.DISC_COMPONENT: "lock", + const.DISC_GENERIC_DEVICE_CLASS: [const.GENERIC_TYPE_ENTRY_CONTROL], + const.DISC_SPECIFIC_DEVICE_CLASS: [ + const.SPECIFIC_TYPE_DOOR_LOCK, + const.SPECIFIC_TYPE_ADVANCED_DOOR_LOCK, + const.SPECIFIC_TYPE_SECURE_KEYPAD_DOOR_LOCK, + const.SPECIFIC_TYPE_SECURE_LOCKBOX, + ], + const.DISC_VALUES: { + const.DISC_PRIMARY: { + const.DISC_COMMAND_CLASS: [CommandClass.DOOR_LOCK], + const.DISC_INDEX: [ValueIndex.DOOR_LOCK_LOCK], + }, + "access_control": { + const.DISC_COMMAND_CLASS: [CommandClass.ALARM], + const.DISC_INDEX: [ValueIndex.ALARM_ACCESS_CONTROL], + const.DISC_OPTIONAL: True, + }, + "alarm_type": { + const.DISC_COMMAND_CLASS: [CommandClass.ALARM], + const.DISC_INDEX: [ValueIndex.ALARM_TYPE], + const.DISC_OPTIONAL: True, + }, + "alarm_level": { + const.DISC_COMMAND_CLASS: [CommandClass.ALARM], + const.DISC_INDEX: [ValueIndex.ALARM_LEVEL], + const.DISC_OPTIONAL: True, + }, + "v2btze_advanced": { + const.DISC_COMMAND_CLASS: [CommandClass.CONFIGURATION], + const.DISC_INDEX: [12], + const.DISC_OPTIONAL: True, + }, + }, + }, + { # All other text/numeric sensors + const.DISC_COMPONENT: "sensor", + const.DISC_VALUES: { + const.DISC_PRIMARY: { + const.DISC_COMMAND_CLASS: [ + CommandClass.SENSOR_MULTILEVEL, + CommandClass.METER, + CommandClass.ALARM, + CommandClass.SENSOR_ALARM, + CommandClass.INDICATOR, + CommandClass.BATTERY, + CommandClass.NOTIFICATION, + CommandClass.BASIC, + ], + const.DISC_TYPE: [ + ValueType.DECIMAL, + ValueType.INT, + ValueType.STRING, + ValueType.BYTE, + ValueType.LIST, + ], + } + }, + }, + { # Switch platform + const.DISC_COMPONENT: "switch", + const.DISC_GENERIC_DEVICE_CLASS: [ + const.GENERIC_TYPE_METER, + 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_GENERIC_CONTROLLER, + const.GENERIC_TYPE_SWITCH_REMOTE, + const.GENERIC_TYPE_REPEATER_SLAVE, + const.GENERIC_TYPE_THERMOSTAT, + const.GENERIC_TYPE_WALL_CONTROLLER, + ], + const.DISC_VALUES: { + const.DISC_PRIMARY: { + const.DISC_COMMAND_CLASS: [CommandClass.SWITCH_BINARY], + const.DISC_TYPE: ValueType.BOOL, + const.DISC_GENRE: ValueGenre.USER, + } + }, + }, +] + + +def check_node_schema(node, schema): + """Check if node matches the passed node schema.""" + if const.DISC_NODE_ID in schema and node.node_id not in schema[const.DISC_NODE_ID]: + return False + if ( + const.DISC_GENERIC_DEVICE_CLASS in schema + and node.node_generic + not in ensure_list(schema[const.DISC_GENERIC_DEVICE_CLASS]) + ): + return False + if ( + const.DISC_SPECIFIC_DEVICE_CLASS in schema + and node.node_specific + not in ensure_list(schema[const.DISC_SPECIFIC_DEVICE_CLASS]) + ): + return False + return True + + +def check_value_schema(value, schema): + """Check if the value matches the passed value schema.""" + if ( + const.DISC_COMMAND_CLASS in schema + and value.parent.command_class_id not in schema[const.DISC_COMMAND_CLASS] + ): + return False + if const.DISC_TYPE in schema and value.type not in ensure_list( + schema[const.DISC_TYPE] + ): + return False + if const.DISC_GENRE in schema and value.genre not in ensure_list( + schema[const.DISC_GENRE] + ): + return False + if const.DISC_INDEX in schema and value.index not in ensure_list( + schema[const.DISC_INDEX] + ): + return False + if const.DISC_INSTANCE in schema and value.instance not in ensure_list( + schema[const.DISC_INSTANCE] + ): + return False + if const.DISC_SCHEMAS in schema: + found = False + for schema_item in schema[const.DISC_SCHEMAS]: + found = found or check_value_schema(value, schema_item) + if not found: + return False + + return True + + +def ensure_list(value): + """Convert a value to a list if needed.""" + if isinstance(value, list): + return value + return [value] diff --git a/homeassistant/components/zwave_mqtt/entity.py b/homeassistant/components/zwave_mqtt/entity.py new file mode 100644 index 0000000000000..eaa89019439d6 --- /dev/null +++ b/homeassistant/components/zwave_mqtt/entity.py @@ -0,0 +1,253 @@ +"""Generic Z-Wave Entity Classes.""" + +import copy +import logging + +from openzwavemqtt.const import EVENT_INSTANCE_STATUS_CHANGED, EVENT_VALUE_CHANGED +from openzwavemqtt.models.node import OZWNode + +from homeassistant.core import callback +from homeassistant.helpers.dispatcher import ( + async_dispatcher_connect, + async_dispatcher_send, +) +from homeassistant.helpers.entity import Entity + +from . import const +from .const import DOMAIN, PLATFORMS +from .discovery import check_node_schema, check_value_schema + +_LOGGER = logging.getLogger(__name__) + + +class ZWaveDeviceEntityValues: + """Manages entity access to the underlying Z-Wave value objects.""" + + def __init__(self, hass, options, schema, primary_value): + """Initialize the values object with the passed entity schema.""" + self._hass = hass + self._entity_created = False + self._schema = copy.deepcopy(schema) + self._values = {} + self.options = options + + # Go through values listed in the discovery schema, initialize them, + # and add a check to the schema to make sure the Instance matches. + for name, disc_settings in self._schema[const.DISC_VALUES].items(): + self._values[name] = None + disc_settings[const.DISC_INSTANCE] = [primary_value.instance] + + self._values[const.DISC_PRIMARY] = primary_value + self._node = primary_value.node + self._schema[const.DISC_NODE_ID] = [self._node.node_id] + + def setup(self): + """Set up values instance.""" + # Check values that have already been discovered for node + # and see if they match the schema and need added to the entity. + for value in self._node.values(): + self.check_value(value) + + # Check if all the _required_ values in the schema are present and + # create the entity. + self._check_entity_ready() + + def __getattr__(self, name): + """Get the specified value for this entity.""" + return self._values.get(name, None) + + def __iter__(self): + """Allow iteration over all values.""" + return iter(self._values.values()) + + def __contains__(self, name): + """Check if the specified name/key exists in the values.""" + return name in self._values + + @callback + def check_value(self, value): + """Check if the new value matches a missing value for this entity. + + If a match is found, it is added to the values mapping. + """ + # Make sure the node matches the schema for this entity. + if not check_node_schema(value.node, self._schema): + return + + # Go through the possible values for this entity defined by the schema. + for name in self._values: + # Skip if it's already been added. + if self._values[name] is not None: + continue + # Skip if the value doesn't match the schema. + if not check_value_schema(value, self._schema[const.DISC_VALUES][name]): + continue + + # Add value to mapping. + self._values[name] = value + + # If the entity has already been created, notify it of the new value. + if self._entity_created: + async_dispatcher_send(self._hass, f"{self.values_id}_value_added") + + # Check if entity has all required values and create the entity if needed. + self._check_entity_ready() + + @callback + def _check_entity_ready(self): + """Check if all required values are discovered and create entity.""" + # Abort if the entity has already been created + if self._entity_created: + return + + # Go through values defined in the schema and abort if a required value is missing. + for name, disc_settings in self._schema[const.DISC_VALUES].items(): + if self._values[name] is None and not disc_settings.get( + const.DISC_OPTIONAL + ): + return + + # We have all the required values, so create the entity. + component = self._schema[const.DISC_COMPONENT] + + _LOGGER.debug( + "Adding Node_id=%s Generic_command_class=%s, " + "Specific_command_class=%s, " + "Command_class=%s, Index=%s, Value type=%s, " + "Genre=%s as %s", + self._node.node_id, + self._node.node_generic, + self._node.node_specific, + self.primary.command_class, + self.primary.index, + self.primary.type, + self.primary.genre, + component, + ) + self._entity_created = True + + if component in PLATFORMS: + async_dispatcher_send(self._hass, f"zwave_new_{component}", self) + + @property + def values_id(self): + """Identification for this values collection.""" + return f"{self.primary.node.id}-{self.primary.value_id_key}" + + +class ZWaveDeviceEntity(Entity): + """Generic Entity Class for a Z-Wave Device.""" + + def __init__(self, values): + """Initialize a generic Z-Wave device entity.""" + self.values = values + self.options = values.options + + @callback + def value_changed(self, value): + """Call when the value is changed.""" + if value.value_id_key in (v.value_id_key for v in self.values if v): + self.async_write_ha_state() + + @callback + def value_added(self): + """Handle a new value for this entity.""" + + @callback + def instance_updated(self, new_status): + """Call when the instance status changes.""" + self.async_write_ha_state() + + async def async_added_to_hass(self): + """Call when entity is added.""" + # add dispatcher and OZW listeners callbacks, + self.options.listen(EVENT_VALUE_CHANGED, self.value_changed) + self.options.listen(EVENT_INSTANCE_STATUS_CHANGED, self.instance_updated) + # add to on_remove so they will be cleaned up on entity removal + self.async_on_remove( + async_dispatcher_connect( + self.hass, const.SIGNAL_DELETE_ENTITY, self._delete_callback + ) + ) + self.async_on_remove( + async_dispatcher_connect( + self.hass, f"{self.values.values_id}_value_added", self.value_added + ) + ) + + @property + def device_info(self): + """Return device information for the device registry.""" + node = self.values.primary.node + node_instance = self.values.primary.instance + dev_id = create_device_id(node, self.values.primary.instance) + device_info = { + "identifiers": {(DOMAIN, dev_id)}, + "name": create_device_name(node), + "manufacturer": node.node_manufacturer_name, + "model": node.node_product_name, + } + # device with multiple instances is split up into virtual devices for each instance + if node_instance > 1: + parent_dev_id = create_device_id(node) + device_info["name"] += f" - Instance {node_instance}" + device_info["via_device"] = (DOMAIN, parent_dev_id) + return device_info + + @property + def device_state_attributes(self): + """Return the device specific state attributes.""" + return {const.ATTR_NODE_ID: self.values.primary.node.node_id} + + @property + def name(self): + """Return the name of the entity.""" + node = self.values.primary.node + return f"{create_device_name(node)}: {self.values.primary.label}" + + @property + def unique_id(self): + """Return the unique_id of the entity.""" + return self.values.values_id + + @property + def available(self) -> bool: + """Return entity availability.""" + # Use OZW Daemon status for availability. + instance_status = self.values.primary.ozw_instance.get_status() + return instance_status and instance_status.status in [ + "driverAllNodesQueriedSomeDead", + "driverAllNodesQueried", + "driverAwakeNodesQueried", + ] + + async def _delete_callback(self, values_id): + """Remove this entity.""" + if not self.values: + return # race condition: delete already requested + if values_id == self.values.values_id: + await self.async_remove() + + async def async_will_remove_from_hass(self) -> None: + """Call when entity will be removed from hass.""" + # cleanup OZW listeners + self.options.listeners[EVENT_VALUE_CHANGED].remove(self.value_changed) + self.options.listeners[EVENT_INSTANCE_STATUS_CHANGED].remove( + self.instance_updated + ) + + +def create_device_name(node: OZWNode): + """Generate sensible (short) default device name from a OZWNode.""" + if node.meta_data["Name"]: + dev_name = f'{node.meta_data["Name"]}' + else: + dev_name = f"{node.node_product_name}" + return dev_name + + +def create_device_id(node: OZWNode, node_instance: int = 1): + """Generate unique device_id from a OZWNode.""" + ozw_instance = node.parent.id + dev_id = f"{ozw_instance}.{node.node_id}.{node_instance}" + return dev_id diff --git a/homeassistant/components/zwave_mqtt/fan.py b/homeassistant/components/zwave_mqtt/fan.py new file mode 100644 index 0000000000000..5976d326f040a --- /dev/null +++ b/homeassistant/components/zwave_mqtt/fan.py @@ -0,0 +1,82 @@ +"""Support for Z-Wave fans.""" +import math + +from homeassistant.components.fan import ( + SPEED_HIGH, + SPEED_LOW, + SPEED_MEDIUM, + SPEED_OFF, + SUPPORT_SET_SPEED, + FanEntity, +) +from homeassistant.core import callback +from homeassistant.helpers.dispatcher import async_dispatcher_connect + +from .const import DATA_UNSUBSCRIBE, DOMAIN +from .entity import ZWaveDeviceEntity + +SPEED_LIST = [SPEED_OFF, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH] + +SUPPORTED_FEATURES = SUPPORT_SET_SPEED + +# Value will first be divided to an integer +VALUE_TO_SPEED = {0: SPEED_OFF, 1: SPEED_LOW, 2: SPEED_MEDIUM, 3: SPEED_HIGH} + +SPEED_TO_VALUE = {SPEED_OFF: 0, SPEED_LOW: 1, SPEED_MEDIUM: 50, SPEED_HIGH: 99} + + +async def async_setup_entry(hass, config_entry, async_add_entities): + """Set up Z-Wave Fan from Config Entry.""" + + @callback + def async_add_fan(values): + """Add Z-Wave Fan.""" + fan = ZwaveFan(values) + async_add_entities([fan]) + + hass.data[DOMAIN][config_entry.entry_id][DATA_UNSUBSCRIBE].append( + async_dispatcher_connect(hass, "zwave_new_fan", async_add_fan) + ) + + await hass.data[DOMAIN][config_entry.entry_id]["mark_platform_loaded"]("fan") + + +class ZwaveFan(ZWaveDeviceEntity, FanEntity): + """Representation of a Z-Wave fan.""" + + async def async_set_speed(self, speed): + """Set the speed of the fan.""" + self.values.primary.send_value(SPEED_TO_VALUE[speed]) + + async def async_turn_on(self, speed=None, **kwargs): + """Turn the device on.""" + if speed is None: + # Value 255 tells device to return to previous value + self.values.primary.send_value(255) + else: + await self.async_set_speed(speed) + + async def async_turn_off(self, **kwargs): + """Turn the device off.""" + self.values.primary.send_value(0) + + @property + def is_on(self): + """Return true if device is on (speed above 0).""" + return self.values.primary.value > 0 + + @property + def speed(self): + """Return the current speed.""" + value = math.ceil(self.values.primary.value * 3 / 100) + return VALUE_TO_SPEED[value] + + @property + def speed_list(self): + """Get the list of available speeds.""" + return SPEED_LIST + + @property + def supported_features(self): + """Flag supported features.""" + return SUPPORTED_FEATURES diff --git a/homeassistant/components/zwave_mqtt/light.py b/homeassistant/components/zwave_mqtt/light.py new file mode 100644 index 0000000000000..0558bbf0c800c --- /dev/null +++ b/homeassistant/components/zwave_mqtt/light.py @@ -0,0 +1,138 @@ +"""Support for Z-Wave lights.""" +import logging + +from homeassistant.components.light import ( + ATTR_BRIGHTNESS, + ATTR_TRANSITION, + SUPPORT_BRIGHTNESS, + SUPPORT_TRANSITION, + Light, +) +from homeassistant.core import callback +from homeassistant.helpers.dispatcher import async_dispatcher_connect + +from .const import DATA_UNSUBSCRIBE, DOMAIN +from .entity import ZWaveDeviceEntity + +_LOGGER = logging.getLogger(__name__) + + +async def async_setup_entry(hass, config_entry, async_add_entities): + """Set up Z-Wave Light from Config Entry.""" + + @callback + def async_add_light(values): + """Add Z-Wave Light.""" + light = ZwaveDimmer(values) + async_add_entities([light]) + + hass.data[DOMAIN][config_entry.entry_id][DATA_UNSUBSCRIBE].append( + async_dispatcher_connect(hass, "zwave_new_light", async_add_light) + ) + + await hass.data[DOMAIN][config_entry.entry_id]["mark_platform_loaded"]("light") + + +def byte_to_zwave_brightness(value): + """Convert brightness in 0-255 scale to 0-99 scale. + + `value` -- (int) Brightness byte value from 0-255. + """ + if value > 0: + return max(1, int((value / 255) * 99)) + return 0 + + +class ZwaveDimmer(ZWaveDeviceEntity, Light): + """Representation of a Z-Wave dimmer.""" + + def __init__(self, values): + """Initialize the light.""" + ZWaveDeviceEntity.__init__(self, values) + self._supported_features = None + self.value_added() + + @callback + def value_added(self): + """Call when a new value is added to this entity.""" + self._supported_features = SUPPORT_BRIGHTNESS + if self.values.dimming_duration is not None: + self._supported_features |= SUPPORT_TRANSITION + + @property + def brightness(self): + """Return the brightness of this light between 0..255.""" + if "target" in self.values: + return round((self.values.target.value / 99) * 255) + return round((self.values.primary.value / 99) * 255) + + @property + def is_on(self): + """Return true if device is on (brightness above 0).""" + if "target" in self.values: + return self.values.target.value > 0 + return self.values.primary.value > 0 + + @property + def supported_features(self): + """Flag supported features.""" + return self._supported_features + + async def async_set_duration(self, **kwargs): + """Set the transition time for the brightness value. + + Zwave Dimming Duration values: + 0 = instant + 0-127 = 1 second to 127 seconds + 128-254 = 1 minute to 127 minutes + 255 = factory default + """ + if self.values.dimming_duration is None: + if ATTR_TRANSITION in kwargs: + _LOGGER.debug("Dimming not supported by %s.", self.entity_id) + return + + if ATTR_TRANSITION not in kwargs: + # no transition specified by user, use defaults + new_value = 255 + else: + # transition specified by user, convert to zwave value + transition = kwargs[ATTR_TRANSITION] + if transition <= 127: + new_value = int(transition) + elif transition > 7620: + new_value = 254 + _LOGGER.warning( + "Transition clipped to 127 minutes for %s.", self.entity_id + ) + else: + minutes = int(transition / 60) + _LOGGER.debug( + "Transition rounded to %d minutes for %s.", minutes, self.entity_id + ) + new_value = minutes + 128 + + # only send value if it differs from current + # this prevents a command for nothing + if self.values.dimming_duration.value != new_value: + self.values.dimming_duration.send_value(new_value) + + async def async_turn_on(self, **kwargs): + """Turn the device on.""" + await self.async_set_duration(**kwargs) + + # Zwave multilevel switches use a range of [0, 99] to control + # brightness. Level 255 means to set it to previous value. + if ATTR_BRIGHTNESS in kwargs: + brightness = kwargs[ATTR_BRIGHTNESS] + brightness = byte_to_zwave_brightness(brightness) + else: + brightness = 255 + + self.values.primary.send_value(brightness) + + async def async_turn_off(self, **kwargs): + """Turn the device off.""" + await self.async_set_duration(**kwargs) + + self.values.primary.send_value(0) diff --git a/homeassistant/components/zwave_mqtt/manifest.json b/homeassistant/components/zwave_mqtt/manifest.json new file mode 100644 index 0000000000000..08af09223cd34 --- /dev/null +++ b/homeassistant/components/zwave_mqtt/manifest.json @@ -0,0 +1,16 @@ +{ + "domain": "zwave_mqtt", + "name": "Z-Wave over MQTT", + "config_flow": true, + "documentation": "https://www.home-assistant.io/integrations/zwave_mqtt", + "requirements": [ + "python-openzwave-mqtt==0.0.8" + ], + "dependencies": [ + "mqtt" + ], + "codeowners": [ + "@cgarwood", + "@MartinHjelmare" + ] +} diff --git a/homeassistant/components/zwave_mqtt/sensor.py b/homeassistant/components/zwave_mqtt/sensor.py new file mode 100644 index 0000000000000..906615192fbe2 --- /dev/null +++ b/homeassistant/components/zwave_mqtt/sensor.py @@ -0,0 +1,130 @@ +"""Representation of Z-Wave sensors.""" + +import logging + +from openzwavemqtt.const import CommandClass + +from homeassistant.components.sensor import ( + DEVICE_CLASS_BATTERY, + DEVICE_CLASS_HUMIDITY, + DEVICE_CLASS_ILLUMINANCE, + DEVICE_CLASS_POWER, + DEVICE_CLASS_PRESSURE, + DEVICE_CLASS_TEMPERATURE, +) +from homeassistant.const import TEMP_CELSIUS, TEMP_FAHRENHEIT +from homeassistant.core import callback +from homeassistant.helpers.dispatcher import async_dispatcher_connect + +from .const import DATA_UNSUBSCRIBE, DOMAIN +from .entity import ZWaveDeviceEntity + +_LOGGER = logging.getLogger(__name__) + + +async def async_setup_entry(hass, config_entry, async_add_entities): + """Set up Z-Wave sensor from config entry.""" + + @callback + def async_add_sensor(value): + """Add Z-Wave Sensor.""" + # Basic Sensor types + if isinstance(value.primary.value, (float, int)): + sensor = ZWaveNumericSensor(value) + + elif isinstance(value.primary.value, dict): + sensor = ZWaveListSensor(value) + + else: + _LOGGER.warning("Sensor not implemented for value %s", value.primary.label) + return + + async_add_entities([sensor]) + + hass.data[DOMAIN][config_entry.entry_id][DATA_UNSUBSCRIBE].append( + async_dispatcher_connect(hass, "zwave_new_sensor", async_add_sensor) + ) + + await hass.data[DOMAIN][config_entry.entry_id]["mark_platform_loaded"]("sensor") + + +class ZwaveSensorBase(ZWaveDeviceEntity): + """Basic Representation of a Z-Wave sensor.""" + + @property + def device_class(self): + """Return the device class of the sensor.""" + if self.values.primary.command_class == CommandClass.BATTERY: + return DEVICE_CLASS_BATTERY + if self.values.primary.command_class == CommandClass.METER: + return DEVICE_CLASS_POWER + if "Temperature" in self.values.primary.label: + return DEVICE_CLASS_TEMPERATURE + if "Illuminance" in self.values.primary.label: + return DEVICE_CLASS_ILLUMINANCE + if "Humidity" in self.values.primary.label: + return DEVICE_CLASS_HUMIDITY + if "Power" in self.values.primary.label: + return DEVICE_CLASS_POWER + if "Energy" in self.values.primary.label: + return DEVICE_CLASS_POWER + if "Electric" in self.values.primary.label: + return DEVICE_CLASS_POWER + if "Pressure" in self.values.primary.label: + return DEVICE_CLASS_PRESSURE + return None + + @property + def entity_registry_enabled_default(self) -> bool: + """Return if the entity should be enabled when first added to the entity registry.""" + # We hide some of the more advanced sensors by default to not overwhelm users + if self.values.primary.command_class in [ + CommandClass.BASIC, + CommandClass.INDICATOR, + CommandClass.NOTIFICATION, + ]: + return False + return True + + +class ZWaveNumericSensor(ZwaveSensorBase): + """Representation of a Z-Wave sensor.""" + + @property + def state(self): + """Return state of the sensor.""" + return round(self.values.primary.value, 2) + + @property + def unit_of_measurement(self): + """Return unit of measurement the value is expressed in.""" + if self.values.primary.units == "C": + return TEMP_CELSIUS + if self.values.primary.units == "F": + return TEMP_FAHRENHEIT + + return self.values.primary.units + + +class ZWaveListSensor(ZwaveSensorBase): + """Representation of a Z-Wave list sensor.""" + + @property + def state(self): + """Return the state of the sensor.""" + # We use the id as value for backwards compatibility + return self.values.primary.value["Selected_id"] + + @property + def device_state_attributes(self): + """Return the device specific state attributes.""" + attributes = super().device_state_attributes + # add the value's label as property + attributes["label"] = self.values.primary.value["Selected"] + return attributes + + @property + def entity_registry_enabled_default(self) -> bool: + """Return if the entity should be enabled when first added to the entity registry.""" + # these sensors are only here for backwards compatibility, disable them by default + return False diff --git a/homeassistant/components/zwave_mqtt/services.py b/homeassistant/components/zwave_mqtt/services.py new file mode 100644 index 0000000000000..c257188dc4c4e --- /dev/null +++ b/homeassistant/components/zwave_mqtt/services.py @@ -0,0 +1,171 @@ +"""Methods and classes related to executing Z-Wave commands and publishing these to hass.""" +import logging + +from openzwavemqtt.const import CommandClass, ValueType +import voluptuous as vol + +from homeassistant.core import callback +import homeassistant.helpers.config_validation as cv + +from . import const + +_LOGGER = logging.getLogger(__name__) + + +class ZWaveServices: + """Class that holds our services ( Zwave Commands) that should be published to hass.""" + + def __init__(self, hass, manager, data_nodes): + """Initialize with both hass and ozwmanager objects.""" + self._hass = hass + self._manager = manager + self._data_nodes = data_nodes + + @callback + def register(self): + """Register all our services.""" + self._hass.services.async_register( + const.DOMAIN, + const.SERVICE_ADD_NODE, + self.add_node, + schema=vol.Schema( + { + vol.Optional(const.ATTR_INSTANCE_ID, default=1): vol.Coerce(int), + vol.Optional(const.ATTR_SECURE, default=False): vol.Coerce(bool), + } + ), + ) + self._hass.services.async_register( + const.DOMAIN, + const.SERVICE_REMOVE_NODE, + self.remove_node, + schema=vol.Schema( + {vol.Optional(const.ATTR_INSTANCE_ID, default=1): vol.Coerce(int)} + ), + ) + self._hass.services.async_register( + const.DOMAIN, + const.SERVICE_REMOVE_FAILED_NODE, + self.remove_failed_node, + schema=vol.Schema( + { + vol.Required(const.ATTR_NODE_ID): vol.Coerce(int), + vol.Optional(const.ATTR_INSTANCE_ID, default=1): vol.Coerce(int), + } + ), + ) + self._hass.services.async_register( + const.DOMAIN, + const.SERVICE_REPLACE_FAILED_NODE, + self.replace_failed_node, + schema=vol.Schema( + { + vol.Required(const.ATTR_NODE_ID): vol.Coerce(int), + vol.Optional(const.ATTR_INSTANCE_ID, default=1): vol.Coerce(int), + } + ), + ) + self._hass.services.async_register( + const.DOMAIN, + const.SERVICE_CANCEL_COMMAND, + self.cancel_command, + schema=vol.Schema( + {vol.Optional(const.ATTR_INSTANCE_ID, default=1): vol.Coerce(int)} + ), + ) + self._hass.services.async_register( + const.DOMAIN, + const.SERVICE_SET_CONFIG_PARAMETER, + self.set_config_parameter, + schema=vol.Schema( + { + vol.Required(const.ATTR_NODE_ID): vol.Coerce(int), + vol.Required(const.ATTR_CONFIG_PARAMETER): vol.Coerce(int), + vol.Required(const.ATTR_CONFIG_VALUE): vol.Any( + vol.Coerce(int), cv.string + ), + vol.Optional(const.ATTR_CONFIG_SIZE, default=2): vol.Coerce(int), + vol.Optional(const.ATTR_INSTANCE_ID, default=1): vol.Coerce(int), + } + ), + ) + + @callback + def add_node(self, service): + """Enter inclusion mode on the controller.""" + instance_id = service.data[const.ATTR_INSTANCE_ID] + secure = service.data[const.ATTR_SECURE] + instance = self._manager.get_instance(instance_id) + instance.add_node(secure) + + @callback + def remove_node(self, service): + """Enter exclusion mode on the controller.""" + instance_id = service.data[const.ATTR_INSTANCE_ID] + instance = self._manager.get_instance(instance_id) + instance.remove_node() + + @callback + def remove_failed_node(self, service): + """Remove a failed node from the controller.""" + instance_id = service.data[const.ATTR_INSTANCE_ID] + node_id = service.data[const.ATTR_NODE_ID] + instance = self._manager.get_instance(instance_id) + instance.remove_failed_node(node_id) + + @callback + def replace_failed_node(self, service): + """Replace a failed node from the controller with a new device.""" + instance_id = service.data[const.ATTR_INSTANCE_ID] + node_id = service.data[const.ATTR_NODE_ID] + instance = self._manager.get_instance(instance_id) + instance.replace_failed_node(node_id) + + @callback + def cancel_command(self, service): + """Cancel in Controller Commands that are in progress.""" + instance_id = service.data[const.ATTR_INSTANCE_ID] + instance = self._manager.get_instance(instance_id) + instance.cancel_controller_command() + + @callback + def set_config_parameter(self, service): + """Set a config parameter to a node.""" + node_id = service.data[const.ATTR_NODE_ID] + node = self._data_nodes[node_id] + param = service.data.get(const.ATTR_CONFIG_PARAMETER) + selection = service.data.get(const.ATTR_CONFIG_VALUE) + # enumerate values until we find the param within configuration items + for value in node.values(): + if value.index != param: + continue + if value.command_class != CommandClass.CONFIGURATION: + continue + _LOGGER.info( + "Setting config parameter %s on Node %s with selection %s", + param, + node_id, + selection, + ) + # Bool value + if value.type == ValueType.BOOL: + return value.send_value(int(selection == "True")) + # List value + if value.type == ValueType.LIST: + return value.send_value(str(selection)) + # Button + if value.type == ValueType.BUTTON: + value.send_value(True) + value.send_value(False) + return + # Byte value + value.send_value(int(selection)) + return + + # Parameter-index not found! + _LOGGER.warning( + "Unknown config parameter %s on Node %s with selection %s", + param, + node_id, + selection, + ) diff --git a/homeassistant/components/zwave_mqtt/services.yaml b/homeassistant/components/zwave_mqtt/services.yaml new file mode 100755 index 0000000000000..0ec754974eded --- /dev/null +++ b/homeassistant/components/zwave_mqtt/services.yaml @@ -0,0 +1,208 @@ +# Describes the format for available Z-Wave services + +change_association: + description: Change an association in the Z-Wave network. + fields: + association: + description: Specify add or remove association + example: add + node_id: + description: Node id of the node to set association for. + example: 10 + target_node_id: + description: Node id of the node to associate to. + example: 42 + group: + description: Group number to set association for. + instance: + description: (Optional) Instance of multichannel association. Defaults to 0. + +add_node: + description: Add a new node to the Z-Wave network. + fields: + secure: + description: Add the new node with secure communications. Secure network key must be set, this process will fallback to add_node (unsecure) for unsupported devices. Note that unsecure devices can't directly talk to secure devices. + instance_id: + description: (Optional) The OZW Instance/Controller to use, defaults to 1. + +cancel_command: + description: Cancel a running Z-Wave controller command. Use this to exit add_node, if you weren't going to use it but activated it. + fields: + instance_id: + description: (Optional) The OZW Instance/Controller to use, defaults to 1. + +heal_network: + description: Start a Z-Wave network heal. This might take a while and will slow down the Z-Wave network greatly while it is being processed. Refer to OZW_Log.txt for progress. + fields: + return_routes: + description: Whether or not to update the return routes from the nodes to the controller. Defaults to False. + example: true + instance_id: + description: (Optional) The OZW Instance/Controller to use, defaults to 1. + +heal_node: + description: Start a Z-Wave node heal. Refer to OZW_Log.txt for progress. + fields: + return_routes: + description: Whether or not to update the return routes from the node to the controller. Defaults to False. + example: true + +remove_node: + description: Remove a node from the Z-Wave network. Will set the controller into exclusion mode. + fields: + instance_id: + description: (Optional) The OZW Instance/Controller to use, defaults to 1. + +remove_failed_node: + description: This command will remove a failed node from the network. The node should be on the controller's failed nodes list, otherwise this command will fail. Refer to OZW_Log.txt for progress. + fields: + node_id: + description: Node id of the device to remove (integer). + example: 10 + instance_id: + description: (Optional) The OZW Instance/Controller to use, defaults to 1. + +replace_failed_node: + description: Replace a failed node with another. If the node is not in the controller's failed nodes list, or the node responds, this command will fail. Refer to OZW_Log.txt for progress. + fields: + node_id: + description: Node id of the device to replace (integer). + example: 10 + instance_id: + description: (Optional) The OZW Instance/Controller to use, defaults to 1. + +set_config_parameter: + description: Set a config parameter to a node on the Z-Wave network. + fields: + node_id: + description: Node id of the device to set config parameter to (integer). + parameter: + description: Parameter index to set (integer). + value: + description: Value to set for parameter. (String value for list and bool parameters, integer for others). + +set_node_value: + description: Set the value for a given value_id on a Z-Wave device. + fields: + node_id: + description: Node id of the device to set the value on (integer). + value_id: + description: Value id of the value to set (integer). + value: + description: Value to set (integer). + +refresh_node_value: + description: Refresh the value for a given value_id on a Z-Wave device. + fields: + node_id: + description: Node id of the device to refresh value from (integer). + value_id: + description: Value id of the value to refresh. + +set_poll_intensity: + description: Set the polling interval to a nodes value + fields: + node_id: + description: ID of the node to set polling to. + example: 10 + value_id: + description: ID of the value to set polling to. + example: 72037594255792737 + poll_intensity: + description: The intensity to poll, 0 = disabled, 1 = Every time through list, 2 = Every second time through list... + example: 2 + +print_config_parameter: + description: Prints a Z-Wave node config parameter value to log. + fields: + node_id: + description: Node id of the device to print the parameter from (integer). + parameter: + description: Parameter number to print (integer). + +print_node: + description: Print all information about z-wave node. + fields: + node_id: + description: Node id of the device to print. + +refresh_entity: + description: Refresh zwave entity. + fields: + entity_id: + description: Name of the entity to refresh. + example: "light.leviton_vrmx11lz_multilevel_scene_switch_level_40" + +refresh_node: + description: Refresh zwave node. + fields: + node_id: + description: ID of the node to refresh. + example: 10 + +set_wakeup: + description: Sets wake-up interval of a node. + fields: + node_id: + description: Node id of the device to set the wake-up interval for. (integer) + value: + description: Value of the interval to set. (integer) + +start_network: + description: Start the Z-Wave network. This might take a while, depending on how big your Z-Wave network is. + +stop_network: + description: Stop the Z-Wave network, all updates into Home Assistant will stop. + +soft_reset: + description: This will reset the controller without removing its data. Use carefully because not all controllers support this. Refer to your controller's manual. + +test_network: + description: This will send test to nodes in the Z-Wave network. This will greatly slow down the Z-Wave network while it is being processed. Refer to OZW_Log.txt for progress. + +test_node: + description: This will send test messages to a node in the Z-Wave network. This could bring back dead nodes. + fields: + node_id: + description: ID of the node to send test messages to. + example: 10 + messages: + description: Optional. Amount of test messages to send. + example: 3 + +rename_node: + description: Set the name of a node. This will also affect the IDs of all entities in the node. + fields: + node_id: + description: ID of the node to rename. + example: 10 + update_ids: + description: (optional) Rename the entity IDs for entities of this node. + example: true + name: + description: New Name + example: "kitchen" + +rename_value: + description: Set the name of a node value. This will affect the ID of the value entity. Value IDs can be queried from /api/zwave/values/{node_id} + fields: + node_id: + description: ID of the node to rename. + example: 10 + value_id: + description: ID of the value to rename. + example: 72037594255792737 + update_ids: + description: (optional) Update the entity ID for this value's entity. + example: true + name: + description: New Name + example: "Luminosity" + +reset_node_meters: + description: Resets the meter counters of a node. + fields: + node_id: + description: Node id of the device to reset meters for. (integer) + instance: + description: (Optional) Instance of association. Defaults to instance 1. diff --git a/homeassistant/components/zwave_mqtt/strings.json b/homeassistant/components/zwave_mqtt/strings.json new file mode 100644 index 0000000000000..af42389617f55 --- /dev/null +++ b/homeassistant/components/zwave_mqtt/strings.json @@ -0,0 +1,21 @@ +{ + "title": "Z-Wave over MQTT", + "config": { + "step": { + "user": { + "title": "Connect to the device", + "data": { + "host": "Host" + } + } + }, + "error": { + "cannot_connect": "Failed to connect, please try again", + "invalid_auth": "Invalid authentication", + "unknown": "Unexpected error" + }, + "abort": { + "already_configured": "Device is already configured" + } + } +} diff --git a/homeassistant/components/zwave_mqtt/switch.py b/homeassistant/components/zwave_mqtt/switch.py new file mode 100644 index 0000000000000..30cd5828bd65f --- /dev/null +++ b/homeassistant/components/zwave_mqtt/switch.py @@ -0,0 +1,49 @@ +"""Representation of Z-Wave switches.""" + +import logging + +from homeassistant.components.switch import SwitchDevice +from homeassistant.const import STATE_OFF, STATE_ON +from homeassistant.core import callback +from homeassistant.helpers.dispatcher import async_dispatcher_connect + +from .const import DATA_UNSUBSCRIBE, DOMAIN +from .entity import ZWaveDeviceEntity + +_LOGGER = logging.getLogger(__name__) + + +async def async_setup_entry(hass, config_entry, async_add_entities): + """Set up Z-Wave switch from config entry.""" + + @callback + def async_add_switch(value): + """Add Z-Wave Switch.""" + switch = ZWaveSwitch(value) + + async_add_entities([switch]) + + hass.data[DOMAIN][config_entry.entry_id][DATA_UNSUBSCRIBE].append( + async_dispatcher_connect(hass, "zwave_new_switch", async_add_switch) + ) + + await hass.data[DOMAIN][config_entry.entry_id]["mark_platform_loaded"]("switch") + + +class ZWaveSwitch(ZWaveDeviceEntity, SwitchDevice): + """Representation of a Z-Wave switch.""" + + @property + def state(self): + """Return the state of the switch.""" + if self.values.primary.value: + return STATE_ON + return STATE_OFF + + async def async_turn_on(self, **kwargs): + """Turn the switch on.""" + self.values.primary.send_value(True) + + async def async_turn_off(self, **kwargs): + """Turn the switch off.""" + self.values.primary.send_value(False) diff --git a/homeassistant/components/zwave_mqtt/translations/en.json b/homeassistant/components/zwave_mqtt/translations/en.json new file mode 100644 index 0000000000000..a8f215a75b732 --- /dev/null +++ b/homeassistant/components/zwave_mqtt/translations/en.json @@ -0,0 +1,21 @@ +{ + "config": { + "title": "ZWave over MQTT", + "step": { + "user": { + "title": "Connect to the device", + "data": { + "host": "Host" + } + } + }, + "error": { + "cannot_connect": "Failed to connect, please try again", + "invalid_auth": "Invalid authentication", + "unknown": "Unexpected error" + }, + "abort": { + "already_configured": "Device is already configured" + } + } +} diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index 6e259c2bf84af..3d2ae9883c742 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -144,5 +144,6 @@ "wwlln", "xiaomi_miio", "zha", - "zwave" + "zwave", + "zwave_mqtt" ] diff --git a/requirements_all.txt b/requirements_all.txt index d166d68ae7f3c..ac4db55c6932e 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1673,6 +1673,9 @@ python-nest==4.1.0 # homeassistant.components.nmap_tracker python-nmap==0.6.1 +# homeassistant.components.zwave_mqtt +python-openzwave-mqtt==0.0.8 + # homeassistant.components.qbittorrent python-qbittorrent==0.4.1 From 08348af0e54560db705d84458ed15a4195a078f7 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Mon, 27 Apr 2020 03:33:56 +0200 Subject: [PATCH 02/51] Delint --- homeassistant/components/zwave_mqtt/binary_sensor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/zwave_mqtt/binary_sensor.py b/homeassistant/components/zwave_mqtt/binary_sensor.py index 8ba4b0a0ab077..0e796a456afef 100644 --- a/homeassistant/components/zwave_mqtt/binary_sensor.py +++ b/homeassistant/components/zwave_mqtt/binary_sensor.py @@ -56,7 +56,7 @@ def async_add_binary_sensor(values): # Handle special cases # we convert some of the Notification values into it's own binary sensor # https://github.com/OpenZWave/open-zwave/blob/master/config/NotificationCCTypes.xml - # TODO: Use constants/Enums from lib (when added) + # Use constants/Enums from lib (when added) for item in values.primary.value["List"]: if values.primary.index == 6 and item["Value"] == 22: # Door/Window Open From afc69ecd3ba99b470f59349d1362c5f6e1193d55 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Mon, 27 Apr 2020 04:05:33 +0200 Subject: [PATCH 03/51] Exclude csv files from pre-commit codespell --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 05e062e43b9c0..8d7efbf00f163 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -18,9 +18,9 @@ repos: - id: codespell args: - --ignore-words-list=hass,alot,datas,dof,dur,farenheit,hist,iff,ines,ist,lightsensor,mut,nd,pres,referer,ser,serie,te,technik,ue,uint,visability,wan,wanna,withing - - --skip="./.*,*.json" + - --skip="./.*,*.csv,*.json" - --quiet-level=2 - exclude_types: [json] + exclude_types: [csv, json] - repo: https://gitlab.com/pycqa/flake8 rev: 3.7.9 hooks: From 61e022d5285b69d357a72d40a4a3c606f9229cb2 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Mon, 27 Apr 2020 04:05:46 +0200 Subject: [PATCH 04/51] Copy tests --- requirements_test_all.txt | 3 + tests/components/zwave_mqtt/__init__.py | 1 + tests/components/zwave_mqtt/common.py | 58 ++++ tests/components/zwave_mqtt/conftest.py | 19 ++ .../fixtures/generic_network_dump.csv | 276 ++++++++++++++++++ tests/components/zwave_mqtt/test_init.py | 23 ++ tests/components/zwave_mqtt/test_light.py | 45 +++ tests/components/zwave_mqtt/test_scenes.py | 100 +++++++ tests/components/zwave_mqtt/test_sensor.py | 18 ++ tests/components/zwave_mqtt/test_switch.py | 30 ++ 10 files changed, 573 insertions(+) create mode 100644 tests/components/zwave_mqtt/__init__.py create mode 100644 tests/components/zwave_mqtt/common.py create mode 100644 tests/components/zwave_mqtt/conftest.py create mode 100644 tests/components/zwave_mqtt/fixtures/generic_network_dump.csv create mode 100644 tests/components/zwave_mqtt/test_init.py create mode 100644 tests/components/zwave_mqtt/test_light.py create mode 100644 tests/components/zwave_mqtt/test_scenes.py create mode 100644 tests/components/zwave_mqtt/test_sensor.py create mode 100644 tests/components/zwave_mqtt/test_switch.py diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 607acf46478d4..2417e339397a5 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -655,6 +655,9 @@ python-miio==0.5.0.1 # homeassistant.components.nest python-nest==4.1.0 +# homeassistant.components.zwave_mqtt +python-openzwave-mqtt==0.0.8 + # homeassistant.components.synology_dsm python-synology==0.7.3 diff --git a/tests/components/zwave_mqtt/__init__.py b/tests/components/zwave_mqtt/__init__.py new file mode 100644 index 0000000000000..95d36355b29d9 --- /dev/null +++ b/tests/components/zwave_mqtt/__init__.py @@ -0,0 +1 @@ +"""Tests for the Z-Wave MQTT integration.""" diff --git a/tests/components/zwave_mqtt/common.py b/tests/components/zwave_mqtt/common.py new file mode 100644 index 0000000000000..f1158830bef32 --- /dev/null +++ b/tests/components/zwave_mqtt/common.py @@ -0,0 +1,58 @@ +"""Helpers for tests.""" +import logging +from pathlib import Path + +from asynctest import Mock, patch + +from homeassistant import config_entries, core as ha +from homeassistant.components.zwave_mqtt.const import DOMAIN + +from tests.common import MockConfigEntry + +_LOGGER = logging.getLogger(__name__) + + +async def setup_zwave(hass, fixture=None): + """Set up Z-Wave and load a dump.""" + hass.config.components.add("mqtt") + + entry = MockConfigEntry( + domain=DOMAIN, + title="Z-Wave", + connection_class=config_entries.CONN_CLASS_LOCAL_PUSH, + ) + + entry.add_to_hass(hass) + + with patch("homeassistant.components.mqtt.async_subscribe") as mock_subscribe: + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + assert "zwave_mqtt" in hass.config.components + assert len(mock_subscribe.mock_calls) == 1 + receive_message = mock_subscribe.mock_calls[0][1][2] + + if fixture is not None: + data = Path(__file__).parent / "fixtures" / fixture + + with data.open("rt") as fp: + for line in fp: + topic, payload = line.strip().split(",", 1) + receive_message(Mock(topic=topic, payload=payload)) + + await hass.async_block_till_done() + + return receive_message + + +def async_capture_events(hass, event_name): + """Create a helper that captures events.""" + events = [] + + @ha.callback + def capture_events(event): + events.append(event) + + hass.bus.async_listen(event_name, capture_events) + + return events diff --git a/tests/components/zwave_mqtt/conftest.py b/tests/components/zwave_mqtt/conftest.py new file mode 100644 index 0000000000000..d67010f04a953 --- /dev/null +++ b/tests/components/zwave_mqtt/conftest.py @@ -0,0 +1,19 @@ +"""Helpers for tests.""" +import json + +from asynctest import patch +import pytest + + +@pytest.fixture(name="sent_messages") +def sent_messages_fixture(): + """Fixture to capture sent messages.""" + sent_messages = [] + + with patch( + "homeassistant.components.mqtt.async_publish", + side_effect=lambda hass, topic, payload: sent_messages.append( + {"topic": topic, "payload": json.loads(payload)} + ), + ): + yield sent_messages diff --git a/tests/components/zwave_mqtt/fixtures/generic_network_dump.csv b/tests/components/zwave_mqtt/fixtures/generic_network_dump.csv new file mode 100644 index 0000000000000..fb2aa1dcfe1b2 --- /dev/null +++ b/tests/components/zwave_mqtt/fixtures/generic_network_dump.csv @@ -0,0 +1,276 @@ +OpenZWave/1/status/,{ "OpenZWave_Version": "1.6.1008", "OZWDeamon_Version": "0.1", "QTOpenZWave_Version": "1.0.0", "QT_Version": "5.12.5", "Status": "driverAllNodesQueried", "TimeStamp": 1579566933, "ManufacturerSpecificDBReady": true, "homeID": 3245146787, "getControllerNodeId": 1, "getSUCNodeId": 1, "isPrimaryController": true, "isBridgeController": false, "hasExtendedTXStatistics": true, "getControllerLibraryVersion": "Z-Wave 3.95", "getControllerLibraryType": "Static Controller", "getControllerPath": "/dev/zwave"} +OpenZWave/1/node/1/,{ "NodeID": 1, "NodeQueryStage": "Complete", "isListening": true, "isFlirs": false, "isBeaming": true, "isRouting": false, "isSecurityv1": false, "isZWavePlus": false, "isNIFRecieved": true, "isAwake": true, "isFailed": false, "MetaData": { "OZWInfoURL": "http://www.openzwave.com/device-database/0086:005A:0101", "ZWAProductURL": "", "ProductPic": "images/aeotec/zw090.png", "Description": "Aeotec Z-Stick Gen5 is a USB controller. When connected to a host controller via USB, it enables the host controller to take part in the Z-Wave network. Products that are Z-Wave certified can be used and communicate with other Z-Wave certified devices.", "ProductManualURL": "https://Products.Z-WaveAlliance.org/ProductManual/File?folder=&filename=Manuals/1355/Z Stick Gen5 manual 1.pdf", "ProductPageURL": "", "InclusionHelp": "Plug the Z-Stick into USB port of your host Controller and then click the “Inclusion” button on your PC/host Controller application.", "ExclusionHelp": "Plug the Z-Stick into USB port of your host Controller and then click the “Exclusion” button on your PC/host Controller application.", "ResetHelp": "Use this procedure only in the event that the primary controller is missing or otherwise inoperable. Press and hold the Action Button on Z-Stick for 20 seconds and then release.", "WakeupHelp": "N/A", "ProductSupportURL": "", "Frequency": "", "Name": "Z-Stick Gen5", "ProductPicBase64": "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" }, "Event": "nodeQueriesComplete", "TimeStamp": 1579566911, "NodeManufacturerName": "AEON Labs", "NodeProductName": "ZW090 Z-Stick Gen5 US", "NodeBasicString": "Static Controller", "NodeBasic": 2, "NodeGenericString": "Static Controller", "NodeGeneric": 2, "NodeSpecificString": "Static PC Controller", "NodeSpecific": 1, "NodeManufacturerID": "0x0086", "NodeProductType": "0x0101", "NodeProductID": "0x005a", "NodeBaudRate": 100000, "NodeVersion": 4, "NodeGroups": 0, "NodeName": "", "NodeLocation": "", "NodeDeviceTypeString": "Unknown Type (0x0000)", "NodeDeviceType": 0, "NodeRole": 0, "NodeRoleString": "Central Controller", "NodePlusType": 0, "NodePlusTypeString": "Z-Wave+ node", "Neighbors": [ 31, 32, 33, 36, 37, 39 ]} +OpenZWave/1/node/1/instance/1/,{ "Instance": 1, "TimeStamp": 1579566891} +OpenZWave/1/node/1/instance/1/commandclass/32/,{ "Instance": 1, "CommandClassId": 32, "CommandClass": "COMMAND_CLASS_BASIC", "TimeStamp": 1579566891} +OpenZWave/1/node/1/instance/1/commandclass/32/value/17301521/,{ "Label": "Basic", "Value": 0, "Units": "", "Min": 0, "Max": 255, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_BASIC", "Index": 0, "Node": 1, "Genre": "Basic", "Help": "Basic status of the node", "ValueIDKey": 17301521, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/1/instance/1/commandclass/112/,{ "Instance": 1, "CommandClassId": 112, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "TimeStamp": 1579566891} +OpenZWave/1/node/1/instance/1/commandclass/112/value/22799473140563988/,{ "Label": "LED indicator configuration", "Value": { "List": [ { "Value": 0, "Label": "Disable" }, { "Value": 1, "Label": "Enable" } ], "Selected": "Enable" }, "Units": "", "Min": 0, "Max": 1, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 81, "Node": 1, "Genre": "Config", "Help": "Enable/Disable LED indicator when plugged in", "ValueIDKey": 22799473140563988, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/1/instance/1/commandclass/112/value/61924494903345172/,{ "Label": "Configuration of the RF power level", "Value": { "List": [ { "Value": 1, "Label": "1" }, { "Value": 2, "Label": "2" }, { "Value": 3, "Label": "3" }, { "Value": 4, "Label": "4" }, { "Value": 5, "Label": "5" }, { "Value": 6, "Label": "6" }, { "Value": 7, "Label": "7" }, { "Value": 8, "Label": "8" }, { "Value": 9, "Label": "9" }, { "Value": 10, "Label": "10" } ], "Selected": "10" }, "Units": "", "Min": 1, "Max": 10, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 220, "Node": 1, "Genre": "Config", "Help": "1~10, other= ignore. A total of 10 levels, level 1 as the weak output power, and so on, 10 for most output power level", "ValueIDKey": 61924494903345172, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/1/instance/1/commandclass/112/value/68116944390979604/,{ "Label": "Security network enabled", "Value": { "List": [ { "Value": 0, "Label": "Disable" }, { "Value": 1, "Label": "Enable" } ], "Selected": "Disable" }, "Units": "", "Min": 0, "Max": 1, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 242, "Node": 1, "Genre": "Config", "Help": "", "ValueIDKey": 68116944390979604, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/1/instance/1/commandclass/112/value/68398419367690259/,{ "Label": "Security network key", "Value": 0, "Units": "", "Min": -2147483648, "Max": 2147483647, "Type": "Int", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 243, "Node": 1, "Genre": "Config", "Help": "", "ValueIDKey": 68398419367690259, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/1/instance/1/commandclass/112/value/70931694158086164/,{ "Label": "Lock/Unlock Configuration", "Value": { "List": [ { "Value": 0, "Label": "Unlock" }, { "Value": 1, "Label": "Lock" } ], "Selected": "Unlock" }, "Units": "", "Min": 0, "Max": 1, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 252, "Node": 1, "Genre": "Config", "Help": "Lock/ unlock all configuration parameters", "ValueIDKey": 70931694158086164, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/1/instance/1/commandclass/112/value/71776119088218131/,{ "Label": "Reset default configuration", "Value": 0, "Units": "", "Min": -2147483648, "Max": 2147483647, "Type": "Int", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 255, "Node": 1, "Genre": "Config", "Help": "Reset to the default configuration", "ValueIDKey": 71776119088218131, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/1/instance/1/commandclass/114/,{ "Instance": 1, "CommandClassId": 114, "CommandClass": "COMMAND_CLASS_MANUFACTURER_SPECIFIC", "TimeStamp": 1579566891} +OpenZWave/1/node/1/instance/1/commandclass/114/value/31227923/,{ "Label": "Loaded Config Revision", "Value": 6, "Units": "", "Min": -2147483648, "Max": 2147483647, "Type": "Int", "Instance": 1, "CommandClass": "COMMAND_CLASS_MANUFACTURER_SPECIFIC", "Index": 0, "Node": 1, "Genre": "System", "Help": "Revision of the Config file currently loaded", "ValueIDKey": 31227923, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/1/instance/1/commandclass/114/value/281475007938579/,{ "Label": "Config File Revision", "Value": 6, "Units": "", "Min": -2147483648, "Max": 2147483647, "Type": "Int", "Instance": 1, "CommandClass": "COMMAND_CLASS_MANUFACTURER_SPECIFIC", "Index": 1, "Node": 1, "Genre": "System", "Help": "Revision of the Config file on the File System", "ValueIDKey": 281475007938579, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/1/instance/1/commandclass/114/value/562949984649235/,{ "Label": "Latest Available Config File Revision", "Value": 6, "Units": "", "Min": -2147483648, "Max": 2147483647, "Type": "Int", "Instance": 1, "CommandClass": "COMMAND_CLASS_MANUFACTURER_SPECIFIC", "Index": 2, "Node": 1, "Genre": "System", "Help": "Latest Revision of the Config file available for download", "ValueIDKey": 562949984649235, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/1/instance/1/commandclass/114/value/844424961359895/,{ "Label": "Device ID", "Value": "", "Units": "", "Min": 0, "Max": 0, "Type": "String", "Instance": 1, "CommandClass": "COMMAND_CLASS_MANUFACTURER_SPECIFIC", "Index": 3, "Node": 1, "Genre": "System", "Help": "Manufacturer Specific Device ID/Model", "ValueIDKey": 844424961359895, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/1/instance/1/commandclass/114/value/1125899938070551/,{ "Label": "Serial Number", "Value": "", "Units": "", "Min": 0, "Max": 0, "Type": "String", "Instance": 1, "CommandClass": "COMMAND_CLASS_MANUFACTURER_SPECIFIC", "Index": 4, "Node": 1, "Genre": "System", "Help": "Device Serial Number", "ValueIDKey": 1125899938070551, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/32/,{ "NodeID": 32, "NodeQueryStage": "Complete", "isListening": true, "isFlirs": false, "isBeaming": true, "isRouting": true, "isSecurityv1": false, "isZWavePlus": true, "isNIFRecieved": true, "isAwake": true, "isFailed": false, "MetaData": { "OZWInfoURL": "http://www.openzwave.com/device-database/0208:0005:0101", "ZWAProductURL": "", "ProductPic": "images/hank/hkzw-so01-smartplug.png", "Description": "Smart Plug is a Z-Wave Switch plugin module specifically used to enable Z-Wave command and control (on/off) of any plug-in tool. It can report wattage consumption or kWh energy usage.Smart Plug is also a security Z-Wave device and supports the Over The Air (OTA) feature for the product’s firmware upgrade.", "ProductManualURL": "https://Products.Z-WaveAlliance.org/ProductManual/File?folder=&filename=Manuals/1789/HKZW-SO01_manual.pdf", "ProductPageURL": "", "InclusionHelp": "Set the Z-Wave network main controller into learning mode. Short press the Z-button, the LED will keep turning on or off, which indicates the inclusion is successful.", "ExclusionHelp": "Set the Z-Wave network main controller into remove mode. Triple click the Z-button, the LED will blink slowly, which indicates the inclusion is successful.", "ResetHelp": "Press and hold the Z-button for more than 20 seconds, the LED will blink faster and faster when the button is pressed. If holding time more than 20seconds, the LED indicator will be on for 3 seconds then it blinking slowly, which indicates the reseting is successful. Use this procedure only in the event that the network primary controller is missing or otherwise inoperable.", "WakeupHelp": "", "ProductSupportURL": "", "Frequency": "", "Name": "Smart Plug", "ProductPicBase64": "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" }, "Event": "nodeQueriesComplete", "TimeStamp": 1579566933, "NodeManufacturerName": "HANK Electronics Ltd", "NodeProductName": "HKZW-SO01 Smart Plug", "NodeBasicString": "Routing Slave", "NodeBasic": 4, "NodeGenericString": "Binary Switch", "NodeGeneric": 16, "NodeSpecificString": "Binary Power Switch", "NodeSpecific": 1, "NodeManufacturerID": "0x0208", "NodeProductType": "0x0101", "NodeProductID": "0x0005", "NodeBaudRate": 100000, "NodeVersion": 4, "NodeGroups": 1, "NodeName": "", "NodeLocation": "", "NodeDeviceTypeString": "On/Off Power Switch", "NodeDeviceType": 1792, "NodeRole": 5, "NodeRoleString": "Always On Slave", "NodePlusType": 0, "NodePlusTypeString": "Z-Wave+ node", "Neighbors": [ 1, 33, 36, 37, 39 ]} +OpenZWave/1/node/32/instance/1/,{ "Instance": 1, "TimeStamp": 1579566891} +OpenZWave/1/node/32/instance/1/commandclass/37/,{ "Instance": 1, "CommandClassId": 37, "CommandClass": "COMMAND_CLASS_SWITCH_BINARY", "TimeStamp": 1579566891} +OpenZWave/1/node/32/instance/1/commandclass/37/value/541671440/,{ "Label": "Switch", "Value": false, "Units": "", "Min": 0, "Max": 0, "Type": "Bool", "Instance": 1, "CommandClass": "COMMAND_CLASS_SWITCH_BINARY", "Index": 0, "Node": 32, "Genre": "User", "Help": "Turn On/Off Device", "ValueIDKey": 541671440, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/32/instance/1/commandclass/39/,{ "Instance": 1, "CommandClassId": 39, "CommandClass": "COMMAND_CLASS_SWITCH_ALL", "TimeStamp": 1579566891} +OpenZWave/1/node/32/instance/1/commandclass/39/value/550092820/,{ "Label": "Switch All", "Value": { "List": [ { "Value": 0, "Label": "Disabled" }, { "Value": 1, "Label": "Off Enabled" }, { "Value": 2, "Label": "On Enabled" }, { "Value": 255, "Label": "On and Off Enabled" } ], "Selected": "On and Off Enabled" }, "Units": "", "Min": 0, "Max": 0, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_SWITCH_ALL", "Index": 0, "Node": 32, "Genre": "System", "Help": "Switch All Devices On/Off", "ValueIDKey": 550092820, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/32/instance/1/commandclass/43/,{ "Instance": 1, "CommandClassId": 43, "CommandClass": "COMMAND_CLASS_SCENE_ACTIVATION", "TimeStamp": 1579566891} +OpenZWave/1/node/32/instance/1/commandclass/43/value/541769747/,{ "Label": "Scene", "Value": 0, "Units": "", "Min": -2147483648, "Max": 2147483647, "Type": "Int", "Instance": 1, "CommandClass": "COMMAND_CLASS_SCENE_ACTIVATION", "Index": 0, "Node": 32, "Genre": "User", "Help": "", "ValueIDKey": 541769747, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/32/instance/1/commandclass/43/value/281475518480403/,{ "Label": "Duration", "Value": 0, "Units": "", "Min": -2147483648, "Max": 2147483647, "Type": "Int", "Instance": 1, "CommandClass": "COMMAND_CLASS_SCENE_ACTIVATION", "Index": 1, "Node": 32, "Genre": "User", "Help": "", "ValueIDKey": 281475518480403, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/32/instance/1/commandclass/50/,{ "Instance": 1, "CommandClassId": 50, "CommandClass": "COMMAND_CLASS_METER", "TimeStamp": 1579566891} +OpenZWave/1/node/32/instance/1/commandclass/50/value/541884434/,{ "Label": "Electric - kWh", "Value": 0.06199999898672104, "Units": "kWh", "Min": 0, "Max": 0, "Type": "Decimal", "Instance": 1, "CommandClass": "COMMAND_CLASS_METER", "Index": 0, "Node": 32, "Genre": "User", "Help": "", "ValueIDKey": 541884434, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueChanged", "TimeStamp": 1579566931} +OpenZWave/1/node/32/instance/1/commandclass/50/value/562950495305746/,{ "Label": "Electric - W", "Value": 0.0, "Units": "W", "Min": 0, "Max": 0, "Type": "Decimal", "Instance": 1, "CommandClass": "COMMAND_CLASS_METER", "Index": 2, "Node": 32, "Genre": "User", "Help": "", "ValueIDKey": 562950495305746, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/32/instance/1/commandclass/50/value/1125900448727058/,{ "Label": "Electric - V", "Value": 123.90499877929688, "Units": "V", "Min": 0, "Max": 0, "Type": "Decimal", "Instance": 1, "CommandClass": "COMMAND_CLASS_METER", "Index": 4, "Node": 32, "Genre": "User", "Help": "", "ValueIDKey": 1125900448727058, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueChanged", "TimeStamp": 1579566933} +OpenZWave/1/node/32/instance/1/commandclass/50/value/1407375425437714/,{ "Label": "Electric - A", "Value": 0.0, "Units": "A", "Min": 0, "Max": 0, "Type": "Decimal", "Instance": 1, "CommandClass": "COMMAND_CLASS_METER", "Index": 5, "Node": 32, "Genre": "User", "Help": "", "ValueIDKey": 1407375425437714, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/32/instance/1/commandclass/50/value/72057594579812368/,{ "Label": "Exporting", "Value": false, "Units": "", "Min": 0, "Max": 0, "Type": "Bool", "Instance": 1, "CommandClass": "COMMAND_CLASS_METER", "Index": 256, "Node": 32, "Genre": "User", "Help": "", "ValueIDKey": 72057594579812368, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/32/instance/1/commandclass/50/value/72339069564911640/,{ "Label": "Reset", "Value": false, "Units": "", "Min": 0, "Max": 0, "Type": "Button", "Instance": 1, "CommandClass": "COMMAND_CLASS_METER", "Index": 257, "Node": 32, "Genre": "System", "Help": "", "ValueIDKey": 72339069564911640, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/32/instance/1/commandclass/94/,{ "Instance": 1, "CommandClassId": 94, "CommandClass": "COMMAND_CLASS_ZWAVEPLUS_INFO", "TimeStamp": 1579566891} +OpenZWave/1/node/32/instance/1/commandclass/94/value/550993937/,{ "Label": "ZWave+ Version", "Value": 1, "Units": "", "Min": 0, "Max": 255, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_ZWAVEPLUS_INFO", "Index": 0, "Node": 32, "Genre": "System", "Help": "ZWave+ Version Supported on the Device", "ValueIDKey": 550993937, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/32/instance/1/commandclass/94/value/281475527704598/,{ "Label": "InstallerIcon", "Value": 1792, "Units": "", "Min": -32768, "Max": 32767, "Type": "Short", "Instance": 1, "CommandClass": "COMMAND_CLASS_ZWAVEPLUS_INFO", "Index": 1, "Node": 32, "Genre": "System", "Help": "Icon File to use for the Installer Application", "ValueIDKey": 281475527704598, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/32/instance/1/commandclass/94/value/562950504415254/,{ "Label": "UserIcon", "Value": 1792, "Units": "", "Min": -32768, "Max": 32767, "Type": "Short", "Instance": 1, "CommandClass": "COMMAND_CLASS_ZWAVEPLUS_INFO", "Index": 2, "Node": 32, "Genre": "System", "Help": "Icon File to use for the User Application", "ValueIDKey": 562950504415254, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/32/instance/1/commandclass/112/,{ "Instance": 1, "CommandClassId": 112, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "TimeStamp": 1579566891} +OpenZWave/1/node/32/instance/1/commandclass/112/value/5629500081307668/,{ "Label": "Overload Protection", "Value": { "List": [ { "Value": 0, "Label": "Disabled" }, { "Value": 1, "Label": "Enabled" } ], "Selected": "Enabled" }, "Units": "", "Min": 0, "Max": 1, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 20, "Node": 32, "Genre": "Config", "Help": "Smart Plug keep detecting the load power, once the current exceeds 16.5a for more than 5s, smart plug's relay will turn off", "ValueIDKey": 5629500081307668, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/32/instance/1/commandclass/112/value/5910975058018324/,{ "Label": "Device status after power failure", "Value": { "List": [ { "Value": 0, "Label": "Memorize" }, { "Value": 1, "Label": "On" }, { "Value": 2, "Label": "Off" } ], "Selected": "Memorize" }, "Units": "", "Min": 0, "Max": 2, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 21, "Node": 32, "Genre": "Config", "Help": "Define how the plug reacts after the power supply is back on. 0 - Smart Plug memorizes its state after a power failure. 1 - Smart Plug does not memorize its state after a power failure. Connected device will be on after the power supply is reconnected. 2 - Smart Plug does not memorize its state after a power failure. Connected device will be off after the power supply is reconnected.", "ValueIDKey": 5910975058018324, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/32/instance/1/commandclass/112/value/6755399988150292/,{ "Label": "Notification when load status change", "Value": { "List": [ { "Value": 0, "Label": "Disabled" }, { "Value": 1, "Label": "Basic" }, { "Value": 2, "Label": "Basic without Z-WAVE Command" } ], "Selected": "Basic" }, "Units": "", "Min": 0, "Max": 2, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 24, "Node": 32, "Genre": "Config", "Help": "Smart Plug can send notifications to association device(Group Lifeline) when state of smart plug's load change 0 - The function is disabled 1 - Send Basic report. 2 - Send Basic report only when Load condition is not changed by Z-WAVE Command", "ValueIDKey": 6755399988150292, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/32/instance/1/commandclass/112/value/7599824918282260/,{ "Label": "Indicator Modes", "Value": { "List": [ { "Value": 0, "Label": "Enabled" }, { "Value": 1, "Label": "Disabled" } ], "Selected": "Enabled" }, "Units": "", "Min": 0, "Max": 1, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 27, "Node": 32, "Genre": "Config", "Help": "After smart plug being included into a Z-Wave network, the LED in the device will indicator the state of load. 0 - The LED will follow the status(on/off) of its load 1 - When the state of Switch's load changed, THe LED will follow the status(on/off) of its load, but the red LED will turn off after 5 seconds if there is no any switch action.", "ValueIDKey": 7599824918282260, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/32/instance/1/commandclass/112/value/42502722030403606/,{ "Label": "Threshold of power report", "Value": 50, "Units": "W", "Min": 0, "Max": 65535, "Type": "Short", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 151, "Node": 32, "Genre": "Config", "Help": "Power threshold to be interpereted, when the change value of load power exceeds the setting threshold, the smart plug will send meter report to association device(Group Lifeline)", "ValueIDKey": 42502722030403606, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/32/instance/1/commandclass/112/value/42784197007114257/,{ "Label": "Percentage threshold of power report", "Value": 10, "Units": "%", "Min": 0, "Max": 255, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 152, "Node": 32, "Genre": "Config", "Help": "Power percentage threshold to be interpreted, when change value of the load power exceeds the setting threshold, the smart plug will send meter report to association device(Group Lifeline).", "ValueIDKey": 42784197007114257, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/32/instance/1/commandclass/112/value/48132221564616723/,{ "Label": "Power report frequency", "Value": 30, "Units": "seconds", "Min": 5, "Max": 2678400, "Type": "Int", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 171, "Node": 32, "Genre": "Config", "Help": "The interval of sending power report to association device(Group Lifeline). 0 - The function is disabled.", "ValueIDKey": 48132221564616723, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/32/instance/1/commandclass/112/value/48413696541327379/,{ "Label": "Energy report frequency", "Value": 300, "Units": "seconds", "Min": 5, "Max": 2678400, "Type": "Int", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 172, "Node": 32, "Genre": "Config", "Help": "The interval of sending power report to association device(Group Lifeline). 0 - The function is disabled.", "ValueIDKey": 48413696541327379, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/32/instance/1/commandclass/112/value/48695171518038035/,{ "Label": "Voltage report frequency", "Value": 0, "Units": "seconds", "Min": 5, "Max": 2678400, "Type": "Int", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 173, "Node": 32, "Genre": "Config", "Help": "The interval of sending voltage report to association device(Group Lifeline). 0 - The function is disabled.", "ValueIDKey": 48695171518038035, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/32/instance/1/commandclass/112/value/48976646494748691/,{ "Label": "Electricity report frequency", "Value": 0, "Units": "seconds", "Min": 5, "Max": 2678400, "Type": "Int", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 174, "Node": 32, "Genre": "Config", "Help": "The interval of sending electricity report to association device(Group Lifeline). 0 - The function is disabled.", "ValueIDKey": 48976646494748691, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/32/instance/1/commandclass/114/,{ "Instance": 1, "CommandClassId": 114, "CommandClass": "COMMAND_CLASS_MANUFACTURER_SPECIFIC", "TimeStamp": 1579566891} +OpenZWave/1/node/32/instance/1/commandclass/114/value/551321619/,{ "Label": "Loaded Config Revision", "Value": 2, "Units": "", "Min": -2147483648, "Max": 2147483647, "Type": "Int", "Instance": 1, "CommandClass": "COMMAND_CLASS_MANUFACTURER_SPECIFIC", "Index": 0, "Node": 32, "Genre": "System", "Help": "Revision of the Config file currently loaded", "ValueIDKey": 551321619, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/32/instance/1/commandclass/114/value/281475528032275/,{ "Label": "Config File Revision", "Value": 2, "Units": "", "Min": -2147483648, "Max": 2147483647, "Type": "Int", "Instance": 1, "CommandClass": "COMMAND_CLASS_MANUFACTURER_SPECIFIC", "Index": 1, "Node": 32, "Genre": "System", "Help": "Revision of the Config file on the File System", "ValueIDKey": 281475528032275, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/32/instance/1/commandclass/114/value/562950504742931/,{ "Label": "Latest Available Config File Revision", "Value": 2, "Units": "", "Min": -2147483648, "Max": 2147483647, "Type": "Int", "Instance": 1, "CommandClass": "COMMAND_CLASS_MANUFACTURER_SPECIFIC", "Index": 2, "Node": 32, "Genre": "System", "Help": "Latest Revision of the Config file available for download", "ValueIDKey": 562950504742931, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/32/instance/1/commandclass/114/value/844425481453591/,{ "Label": "Device ID", "Value": "", "Units": "", "Min": 0, "Max": 0, "Type": "String", "Instance": 1, "CommandClass": "COMMAND_CLASS_MANUFACTURER_SPECIFIC", "Index": 3, "Node": 32, "Genre": "System", "Help": "Manufacturer Specific Device ID/Model", "ValueIDKey": 844425481453591, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/32/instance/1/commandclass/114/value/1125900458164247/,{ "Label": "Serial Number", "Value": "0107020900000000000607034800010001000000", "Units": "", "Min": 0, "Max": 0, "Type": "String", "Instance": 1, "CommandClass": "COMMAND_CLASS_MANUFACTURER_SPECIFIC", "Index": 4, "Node": 32, "Genre": "System", "Help": "Device Serial Number", "ValueIDKey": 1125900458164247, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/32/instance/1/commandclass/115/,{ "Instance": 1, "CommandClassId": 115, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "TimeStamp": 1579566891} +OpenZWave/1/node/32/instance/1/commandclass/115/value/551338004/,{ "Label": "Powerlevel", "Value": { "List": [ { "Value": 0, "Label": "Normal" }, { "Value": 1, "Label": "-1dB" }, { "Value": 2, "Label": "-2dB" }, { "Value": 3, "Label": "-3dB" }, { "Value": 4, "Label": "-4dB" }, { "Value": 5, "Label": "-5dB" }, { "Value": 6, "Label": "-6dB" }, { "Value": 7, "Label": "-7dB" }, { "Value": 8, "Label": "-8dB" }, { "Value": 9, "Label": "-9dB" } ], "Selected": "Normal" }, "Units": "dB", "Min": 0, "Max": 0, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 0, "Node": 32, "Genre": "System", "Help": "Output RF PowerLevel", "ValueIDKey": 551338004, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/32/instance/1/commandclass/115/value/281475528048657/,{ "Label": "Timeout", "Value": 0, "Units": "seconds", "Min": 0, "Max": 255, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 1, "Node": 32, "Genre": "System", "Help": "Timeout till the PowerLevel is reset to Normal", "ValueIDKey": 281475528048657, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/32/instance/1/commandclass/115/value/562950504759320/,{ "Label": "Set Powerlevel", "Value": false, "Units": "", "Min": 0, "Max": 0, "Type": "Button", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 2, "Node": 32, "Genre": "System", "Help": "Apply the Output PowerLevel and Timeout Values", "ValueIDKey": 562950504759320, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/32/instance/1/commandclass/115/value/844425481469969/,{ "Label": "Test Node", "Value": 0, "Units": "", "Min": 0, "Max": 255, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 3, "Node": 32, "Genre": "System", "Help": "Node to Perform a test against", "ValueIDKey": 844425481469969, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/32/instance/1/commandclass/115/value/1125900458180628/,{ "Label": "Test Powerlevel", "Value": { "List": [ { "Value": 0, "Label": "Normal" }, { "Value": 1, "Label": "-1dB" }, { "Value": 2, "Label": "-2dB" }, { "Value": 3, "Label": "-3dB" }, { "Value": 4, "Label": "-4dB" }, { "Value": 5, "Label": "-5dB" }, { "Value": 6, "Label": "-6dB" }, { "Value": 7, "Label": "-7dB" }, { "Value": 8, "Label": "-8dB" }, { "Value": 9, "Label": "-9dB" } ], "Selected": "Normal" }, "Units": "dB", "Min": 0, "Max": 0, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 4, "Node": 32, "Genre": "System", "Help": "PowerLevel to use for the Test", "ValueIDKey": 1125900458180628, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/32/instance/1/commandclass/115/value/1407375434891286/,{ "Label": "Frame Count", "Value": 0, "Units": "", "Min": -32768, "Max": 32767, "Type": "Short", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 5, "Node": 32, "Genre": "System", "Help": "How Many Messages to send to the Note for the Test", "ValueIDKey": 1407375434891286, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/32/instance/1/commandclass/115/value/1688850411601944/,{ "Label": "Test", "Value": false, "Units": "", "Min": 0, "Max": 0, "Type": "Button", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 6, "Node": 32, "Genre": "System", "Help": "Perform a PowerLevel Test against the a Node", "ValueIDKey": 1688850411601944, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/32/instance/1/commandclass/115/value/1970325388312600/,{ "Label": "Report", "Value": false, "Units": "", "Min": 0, "Max": 0, "Type": "Button", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 7, "Node": 32, "Genre": "System", "Help": "Get the results of the latest PowerLevel Test against a Node", "ValueIDKey": 1970325388312600, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/32/instance/1/commandclass/115/value/2251800365023252/,{ "Label": "Test Status", "Value": { "List": [ { "Value": 0, "Label": "Failed" }, { "Value": 1, "Label": "Success" }, { "Value": 2, "Label": "In Progress" } ], "Selected": "Failed" }, "Units": "", "Min": 0, "Max": 0, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 8, "Node": 32, "Genre": "System", "Help": "The Current Status of the last PowerNode Test Executed", "ValueIDKey": 2251800365023252, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/32/instance/1/commandclass/115/value/2533275341733910/,{ "Label": "Acked Frames", "Value": 0, "Units": "", "Min": -32768, "Max": 32767, "Type": "Short", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 9, "Node": 32, "Genre": "System", "Help": "Number of Messages successfully Acked by the Target Node", "ValueIDKey": 2533275341733910, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/32/instance/1/commandclass/134/,{ "Instance": 1, "CommandClassId": 134, "CommandClass": "COMMAND_CLASS_VERSION", "TimeStamp": 1579566891} +OpenZWave/1/node/32/instance/1/commandclass/134/value/551649303/,{ "Label": "Library Version", "Value": "3", "Units": "", "Min": 0, "Max": 0, "Type": "String", "Instance": 1, "CommandClass": "COMMAND_CLASS_VERSION", "Index": 0, "Node": 32, "Genre": "System", "Help": "Z-Wave Library Version", "ValueIDKey": 551649303, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/32/instance/1/commandclass/134/value/281475528359959/,{ "Label": "Protocol Version", "Value": "4.24", "Units": "", "Min": 0, "Max": 0, "Type": "String", "Instance": 1, "CommandClass": "COMMAND_CLASS_VERSION", "Index": 1, "Node": 32, "Genre": "System", "Help": "Z-Wave Protocol Version", "ValueIDKey": 281475528359959, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/32/instance/1/commandclass/134/value/562950505070615/,{ "Label": "Application Version", "Value": "1.05", "Units": "", "Min": 0, "Max": 0, "Type": "String", "Instance": 1, "CommandClass": "COMMAND_CLASS_VERSION", "Index": 2, "Node": 32, "Genre": "System", "Help": "Application Version", "ValueIDKey": 562950505070615, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/32/association/1/,{ "Name": "Lifeline", "Help": "", "MaxAssociations": 5, "Members": [ "1.0", "255.0" ], "TimeStamp": 1579566915} +OpenZWave/1/node/36/,{ "NodeID": 36, "NodeQueryStage": "CacheLoad", "isListening": false, "isFlirs": false, "isBeaming": true, "isRouting": true, "isSecurityv1": false, "isZWavePlus": false, "isNIFRecieved": true, "isAwake": true, "isFailed": false, "MetaData": { "OZWInfoURL": "http://www.openzwave.com/device-database/0086:007A:0102", "ZWAProductURL": "", "ProductPic": "images/aeotec/zw122.png", "Description": "Aeotec by Aeon Labs Water Sensor 6 brings intelligence to a new level, one that is suited to both safety and convenience. It contains 4 sensing points, which would be more accurately to detect the presence and absence of water or detect whether there is water leak in some places of your home. The Water Sensor 6 has an inbuilt buzzer that can play alarm sounds to let you know when the water is detected. The Water Sensor 6 is also a security Z-Wave device that supports Over The Air (OTA) for firmware updates.", "ProductManualURL": "https://Products.Z-WaveAlliance.org/ProductManual/File?folder=&filename=Manuals/2437/Aeon Labs Water Sensor 6 manual.pdf", "ProductPageURL": "", "InclusionHelp": "Turn the primary controller of Z-Wave network into inclusion mode, short press the product’s Action Button that you can find on the product.", "ExclusionHelp": "Turn the primary controller of Z-Wave network into exclusion mode, short press the product’s Action Button that you can find on the product.", "ResetHelp": "Press and hold the Action Button that you can find on the product for 20 seconds and then release. This procedure should only be used when the primary controller is inoperable.", "WakeupHelp": "Pressing the Action Button once will trigger sending the Wake up notification command. If press and hold the Z-Wave button for 3 seconds, the Water Sensor will wake up for 10 minutes.", "ProductSupportURL": "", "Frequency": "", "Name": "Water Sensor 6", "ProductPicBase64": "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" }, "Event": "nodeNaming", "TimeStamp": 1579566891, "NodeManufacturerName": "AEON Labs", "NodeProductName": "ZW122 Water Sensor 6", "NodeBasicString": "Routing Slave", "NodeBasic": 4, "NodeGenericString": "Notification Sensor", "NodeGeneric": 7, "NodeSpecificString": "Notification Sensor", "NodeSpecific": 1, "NodeManufacturerID": "0x0086", "NodeProductType": "0x0102", "NodeProductID": "0x007a", "NodeBaudRate": 100000, "NodeVersion": 4, "NodeGroups": 4} +OpenZWave/1/node/36/instance/1/,{ "Instance": 1, "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/32/,{ "Instance": 1, "CommandClassId": 32, "CommandClass": "COMMAND_CLASS_BASIC", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/32/value/604504081/,{ "Label": "Basic", "Value": 0, "Units": "", "Min": 0, "Max": 255, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_BASIC", "Index": 0, "Node": 36, "Genre": "Basic", "Help": "Basic status of the node", "ValueIDKey": 604504081, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/49/,{ "Instance": 1, "CommandClassId": 49, "CommandClass": "COMMAND_CLASS_SENSOR_MULTILEVEL", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/49/value/281475585687570/,{ "Label": "Air Temperature", "Value": 17.100000381469728, "Units": "C", "Min": 0, "Max": 0, "Type": "Decimal", "Instance": 1, "CommandClass": "COMMAND_CLASS_SENSOR_MULTILEVEL", "Index": 1, "Node": 36, "Genre": "User", "Help": "Air Temperature Sensor Value", "ValueIDKey": 281475585687570, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/49/value/72057594655293460/,{ "Label": "Air Temperature Units", "Value": { "List": [ { "Value": 0, "Label": "Celsius" }, { "Value": 1, "Label": "Fahrenheit" } ], "Selected": "Celsius" }, "Units": "", "Min": 0, "Max": 0, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_SENSOR_MULTILEVEL", "Index": 256, "Node": 36, "Genre": "System", "Help": "Air Temperature Sensor Available Units", "ValueIDKey": 72057594655293460, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/94/,{ "Instance": 1, "CommandClassId": 94, "CommandClass": "COMMAND_CLASS_ZWAVEPLUS_INFO", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/94/value/618102801/,{ "Label": "Instance 1: ZWave+ Version", "Value": 1, "Units": "", "Min": 0, "Max": 255, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_ZWAVEPLUS_INFO", "Index": 0, "Node": 36, "Genre": "System", "Help": "ZWave+ Version Supported on the Device", "ValueIDKey": 618102801, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/94/value/281475594813462/,{ "Label": "Instance 1: InstallerIcon", "Value": 3079, "Units": "", "Min": -32768, "Max": 32767, "Type": "Short", "Instance": 1, "CommandClass": "COMMAND_CLASS_ZWAVEPLUS_INFO", "Index": 1, "Node": 36, "Genre": "System", "Help": "Icon File to use for the Installer Application", "ValueIDKey": 281475594813462, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/94/value/562950571524118/,{ "Label": "Instance 1: UserIcon", "Value": 3079, "Units": "", "Min": -32768, "Max": 32767, "Type": "Short", "Instance": 1, "CommandClass": "COMMAND_CLASS_ZWAVEPLUS_INFO", "Index": 2, "Node": 36, "Genre": "System", "Help": "Icon File to use for the User Application", "ValueIDKey": 562950571524118, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/112/,{ "Instance": 1, "CommandClassId": 112, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/112/value/562950567624724/,{ "Label": "Waking up for 10 minutes when re-power on", "Value": { "List": [ { "Value": 0, "Label": "Disabled" }, { "Value": 1, "Label": "Enabled" } ], "Selected": "Disabled" }, "Units": "", "Min": 0, "Max": 1, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 2, "Node": 36, "Genre": "Config", "Help": "Enable/Disable waking up for 10 minutes when re-power on (battery mode) the Water Sensor.", "ValueIDKey": 562950567624724, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/112/value/2251800427888657/,{ "Label": "Timeout of awake after the Wake Up CC is sent out", "Value": 30, "Units": "seconds", "Min": 8, "Max": 127, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 8, "Node": 36, "Genre": "Config", "Help": "Set the timeout of awake after the Wake Up CC is sent out. Available rang is 8 to 127 seconds.", "ValueIDKey": 2251800427888657, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/112/value/2533275404599316/,{ "Label": "Current power mode", "Value": { "List": [ { "Value": 0, "Label": "USB power, sleeping mode after re-power on" }, { "Value": 1, "Label": "USB power, keep awake for 10 minutes after re-power on" }, { "Value": 2, "Label": "USB power, always awake state" }, { "Value": 256, "Label": "Battery power, sleeping mode after re-power on" }, { "Value": 257, "Label": "Battery power, keep awake for 10 minutes after re-power on" }, { "Value": 258, "Label": "Battery power, always awake state" } ], "Selected": "USB power, sleeping mode after re-power on" }, "Units": "", "Min": 0, "Max": 258, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 9, "Node": 36, "Genre": "Config", "Help": "Report the current power mode and the product state for battery power mode", "ValueIDKey": 2533275404599316, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/112/value/2814750381309971/,{ "Label": "Alarm time for the Buzzer", "Value": 1968650, "Units": "", "Min": 655360, "Max": 2147483647, "Type": "Int", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 10, "Node": 36, "Genre": "Config", "Help": "Set the alarm time for the Buzzer when the sensor is triggered. 1 to 255 Repeated cycle of Buzzer alarm. 256 to 65535 the time of Buzzer keeping ON state (MSB). 65536 to 2147483647 The time of Buzzer keeping OFF state.", "ValueIDKey": 2814750381309971, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/112/value/10977524705918993/,{ "Label": "Set the low battery value", "Value": 20, "Units": "%", "Min": 10, "Max": 50, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 39, "Node": 36, "Genre": "Config", "Help": "10% to 50%", "ValueIDKey": 10977524705918993, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/112/value/13510799496314897/,{ "Label": "Sensor report", "Value": 55, "Units": "", "Min": 0, "Max": 55, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 48, "Node": 36, "Genre": "Config", "Help": "Enable/disable the sensor report: Bit 7 - Bit 6 - Bit 5 Notification Report for Overheat alarm. Bit 4 Notification Report for Under heat alarm. Bit 3 - Bit 2 Configuration Report for Tilt sensor. Bit 1 Notification Report for Vibration event. Bit 0 Notification Report for Water Leak event. Note: if the value = 1+2+4+16+32=55, which means if any sensor will report alarm.", "ValueIDKey": 13510799496314897, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/112/value/13792274473025555/,{ "Label": "Upper limit value", "Value": 26214400, "Units": "", "Min": 65536, "Max": 2147483647, "Type": "Int", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 49, "Node": 36, "Genre": "Config", "Help": "Set the upper limit value (overheat). 0 Celsius unit 1 Fahrenheit unit 65536 to 2147483647 Temperature value. Default: 0x01900000 => 40.0C", "ValueIDKey": 13792274473025555, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/112/value/14073749449736211/,{ "Label": "Lower limit value", "Value": 0, "Units": "", "Min": 65536, "Max": 2147483647, "Type": "Int", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 50, "Node": 36, "Genre": "Config", "Help": "Set the lower limit value (under heat). 0 Celsius unit 1 Fahrenheit unit 65536 to 2147483647 Temperature value", "ValueIDKey": 14073749449736211, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/112/value/16044074286710806/,{ "Label": "Recover limit value of temperature sensor", "Value": 5120, "Units": "", "Min": 100, "Max": 4080, "Type": "Short", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 57, "Node": 36, "Genre": "Config", "Help": "Set the recover limit value of temperature sensor. Note: 1. When the current measurement less than or equal (Upper limit - Recover limit), the upper limit report is enabled and then it would send out a sensor report when the next measurement is more than the upper limit. After that the upper limit report would be disabled again until the measurement less than or equal (Upper limit - Recover limit). 2. When the current measurement greater than or equal (Lower limit + Recover limit), the lower limit report is enabled and then it would send out a sensor report when the next measurement is less than the lower limit. After that the lower limit report would be disabled again until the measurement >= (Lower limit + Recover limit). 3. High byte is the recover limit value. Low byte is the unit (0x00=Celsius, 0x01=Fahrenheit). 4. Recover limit range: 1.0 to 25.5 C/F (0x0100 to 0xFF00 or 0x0101 to 0xFF01). E.g. The default recover limit value is 2.0 C/F (0x1400/0x1401), when the measurement is less than (Upper limit - 2), the upper limit report would be enabled one time or when the measurement is more than (Lower limit + 2), the lower limit report would be enabled one time.", "ValueIDKey": 16044074286710806, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/112/value/18014399123685396/,{ "Label": "Unit of the automatic temperature report", "Value": { "List": [ { "Value": 0, "Label": "Celsius" }, { "Value": 1, "Label": "Fahrenheit" } ], "Selected": "Celsius" }, "Units": "", "Min": 0, "Max": 1, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 64, "Node": 36, "Genre": "Config", "Help": "Set the default unit of the automatic temperature report in parameter 101-103", "ValueIDKey": 18014399123685396, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/112/value/23643898657898516/,{ "Label": "Get the state of tilt sensor", "Value": { "List": [ { "Value": 0, "Label": "Horizontal" }, { "Value": 1, "Label": "Vertical" } ], "Selected": "Horizontal" }, "Units": "", "Min": 0, "Max": 1, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 84, "Node": 36, "Genre": "Config", "Help": "Get the state of tilt sensor", "ValueIDKey": 23643898657898516, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/112/value/24206848611319828/,{ "Label": "Buzzer", "Value": { "List": [ { "Value": 0, "Label": "Disabled" }, { "Value": 1, "Label": "Enabled" } ], "Selected": "Enabled" }, "Units": "", "Min": 0, "Max": 1, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 86, "Node": 36, "Genre": "Config", "Help": "Enable/ disable the buzzer.", "ValueIDKey": 24206848611319828, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/112/value/24488323588030490/,{ "Label": "Sensor is triggered the buzzer will alarm", "Value": [ { "Label": "Vibration", "Help": "If the vibration is triggered, the buzzer will alarm.", "Value": 1, "Position": 1 }, { "Label": "Tilt Sensor", "Help": "If the Tilt Sensor is triggered, the buzzer will alarm.", "Value": 1, "Position": 2 }, { "Label": "UnderHeat", "Help": "If the Under Heat Temperature is triggered, the buzzer will alarm.", "Value": 1, "Position": 4 }, { "Label": "OverHeat", "Help": "If the Over Heat Temperature is triggered, the buzzer will alarm.", "Value": 1, "Position": 5 } ], "Units": "", "Min": 0, "Max": 55, "Type": "BitSet", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 87, "Node": 36, "Genre": "Config", "Help": "What Sensors Trigger the Buzzer", "ValueIDKey": 24488323588030490, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/112/value/24769798564741140/,{ "Label": "Probe 1 Basic Set on grp 3", "Value": { "List": [ { "Value": 0, "Label": "Send nothing" }, { "Value": 1, "Label": "Presence/absence of water 0xFF/0x00" }, { "Value": 2, "Label": "Presence/absence of water 0x00/0xFF" } ], "Selected": "Send nothing" }, "Units": "", "Min": 0, "Max": 2, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 88, "Node": 36, "Genre": "Config", "Help": "To set which value of the Basic Set will be sent to the associated nodes in association Group 3 when the Sensor probe 1 is triggered.", "ValueIDKey": 24769798564741140, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/112/value/25051273541451796/,{ "Label": "Probe 2 Basic Set on grp 4", "Value": { "List": [ { "Value": 0, "Label": "Send nothing" }, { "Value": 1, "Label": "Presence/absence of water 0xFF/0x00" }, { "Value": 2, "Label": "Presence/absence of water 0x00/0xFF" } ], "Selected": "Send nothing" }, "Units": "", "Min": 0, "Max": 2, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 89, "Node": 36, "Genre": "Config", "Help": "To set which value of the Basic Set will be sent to the associated nodes in association Group 4 when the Sensor probe 2 is triggered.", "ValueIDKey": 25051273541451796, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/112/value/26458648425005076/,{ "Label": "Battery report selection", "Value": { "List": [ { "Value": 0, "Label": "USB power level" }, { "Value": 1, "Label": "CR123A battery level" } ], "Selected": "USB power level" }, "Units": "", "Min": 0, "Max": 1, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 94, "Node": 36, "Genre": "Config", "Help": "To set which power source level is reported via the Battery CC.", "ValueIDKey": 26458648425005076, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/112/value/28428973261979668/,{ "Label": "Unsolicited report", "Value": { "List": [ { "Value": 0, "Label": "Send Nothing" }, { "Value": 1, "Label": "Battery Report" }, { "Value": 2, "Label": "Multilevel sensor report for temperature" }, { "Value": 3, "Label": "Battery Report and Multilevel sensor report for temperature" } ], "Selected": "Battery Report and Multilevel sensor report for temperature" }, "Units": "", "Min": 0, "Max": 3, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 101, "Node": 36, "Genre": "Config", "Help": "To set what unsolicited report would be sent to the Lifeline group.", "ValueIDKey": 28428973261979668, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/112/value/31243723029086227/,{ "Label": "Unsolicited report interval time", "Value": 3600, "Units": "seconds", "Min": 5, "Max": 2678400, "Type": "Int", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 111, "Node": 36, "Genre": "Config", "Help": "To set the interval time of sending reports in Report group 1", "ValueIDKey": 31243723029086227, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/112/value/37999122470141972/,{ "Label": "Water leak event report selection", "Value": { "List": [ { "Value": 0, "Label": "Send nothing" }, { "Value": 1, "Label": "Send notification report to association group 1" }, { "Value": 2, "Label": "Send configuration 0x88 report to association group 2" }, { "Value": 3, "Label": "Send notification report to association group 1 and Send configuration 0x88 report to association group 2" } ], "Selected": "Send notification report to association group 1" }, "Units": "", "Min": 0, "Max": 3, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 135, "Node": 36, "Genre": "Config", "Help": "To set which sensor report can be sent when the water leak event is triggered and if the receiving device is a non-multichannel device.", "ValueIDKey": 37999122470141972, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/112/value/38280597446852628/,{ "Label": "Report Type to Send", "Value": { "List": [ { "Value": 0, "Label": "Absence of water is triggered by probe 1 and 2" }, { "Value": 1, "Label": "Presence of water is triggered by probe 1" }, { "Value": 2, "Label": "Presence of water is triggered by probe 2" }, { "Value": 3, "Label": "Presence of water is triggered by probe 1 and 2" } ], "Selected": "Absence of water is triggered by probe 1 and 2" }, "Units": "", "Min": 0, "Max": 2, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 136, "Node": 36, "Genre": "Config", "Help": "When the parameter 0x87 is set to 2 or 3, it can get the sensor probes status through this configuration value.", "ValueIDKey": 38280597446852628, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/112/value/56576470933045270/,{ "Label": "Temperature sensor calibration", "Value": 0, "Units": "", "Min": -32768, "Max": 32767, "Type": "Short", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 201, "Node": 36, "Genre": "Config", "Help": "Temperature calibration (the available value range is [-128, 127] or [-12.8C, 12.7C]). Note: 1. High byte is the calibration value. Low byte is the unit (0x00=Celsius, 0x01=Fahrenheit). 2. The calibration value (high byte) contains one decimal point. E.g. if the value is set to 20 (0x1400), the calibration value is 2.0 C (EU/AU version) or if the value is set to 20 (0x1401), the calibration value is 2.0 F(US version). 3. The calibration value (high byte) = standard value - measure value. E.g. If measure value =25.3C and the standard value = 23.2C, so the calibration value= 23.2C - 25.3C= -2.1C (0xEB). If the measure value =30.1C and the standard value = 33.2C, so the calibration value= 33.2C - 30.1C=3.1C (0x1F).", "ValueIDKey": 56576470933045270, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/112/value/70931694745288724/,{ "Label": "Lock/Unlock Configuration", "Value": { "List": [ { "Value": 0, "Label": "Unlock" }, { "Value": 1, "Label": "Lock" } ], "Selected": "Unlock" }, "Units": "", "Min": 0, "Max": 1, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 252, "Node": 36, "Genre": "Config", "Help": "Lock/ unlock all configuration parameters", "ValueIDKey": 70931694745288724, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/112/value/71776119675420692/,{ "Label": "Reset To Factory Defaults", "Value": { "List": [ { "Value": 0, "Label": "Normal" }, { "Value": 1, "Label": "Reset to factory default setting" }, { "Value": 1431655765, "Label": "Reset to factory default setting and removed from the z-wave network" } ], "Selected": "Reset to factory default setting" }, "Units": "", "Min": 0, "Max": 0, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 255, "Node": 36, "Genre": "Config", "Help": "Reset to factory defaults", "ValueIDKey": 71776119675420692, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/113/,{ "Instance": 1, "CommandClassId": 113, "CommandClass": "COMMAND_CLASS_NOTIFICATION", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/113/value/1407375493578772/,{ "Label": "Instance 1: Water", "Value": { "List": [ { "Value": 0, "Label": "Clear" }, { "Value": 2, "Label": "Water Leak at Unknown Location" } ], "Selected": "Clear" }, "Units": "", "Min": 0, "Max": 0, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_NOTIFICATION", "Index": 5, "Node": 36, "Genre": "User", "Help": "Water Alerts", "ValueIDKey": 1407375493578772, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/113/value/72057594647953425/,{ "Label": "Instance 1: Previous Event Cleared", "Value": 0, "Units": "", "Min": 0, "Max": 255, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_NOTIFICATION", "Index": 256, "Node": 36, "Genre": "User", "Help": "Previous Event that was sent", "ValueIDKey": 72057594647953425, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/114/,{ "Instance": 1, "CommandClassId": 114, "CommandClass": "COMMAND_CLASS_MANUFACTURER_SPECIFIC", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/114/value/618430483/,{ "Label": "Loaded Config Revision", "Value": 10, "Units": "", "Min": -2147483648, "Max": 2147483647, "Type": "Int", "Instance": 1, "CommandClass": "COMMAND_CLASS_MANUFACTURER_SPECIFIC", "Index": 0, "Node": 36, "Genre": "System", "Help": "Revision of the Config file currently loaded", "ValueIDKey": 618430483, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/114/value/281475595141139/,{ "Label": "Config File Revision", "Value": 10, "Units": "", "Min": -2147483648, "Max": 2147483647, "Type": "Int", "Instance": 1, "CommandClass": "COMMAND_CLASS_MANUFACTURER_SPECIFIC", "Index": 1, "Node": 36, "Genre": "System", "Help": "Revision of the Config file on the File System", "ValueIDKey": 281475595141139, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/114/value/562950571851795/,{ "Label": "Latest Available Config File Revision", "Value": 10, "Units": "", "Min": -2147483648, "Max": 2147483647, "Type": "Int", "Instance": 1, "CommandClass": "COMMAND_CLASS_MANUFACTURER_SPECIFIC", "Index": 2, "Node": 36, "Genre": "System", "Help": "Latest Revision of the Config file available for download", "ValueIDKey": 562950571851795, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/114/value/844425548562455/,{ "Label": "Device ID", "Value": "", "Units": "", "Min": 0, "Max": 0, "Type": "String", "Instance": 1, "CommandClass": "COMMAND_CLASS_MANUFACTURER_SPECIFIC", "Index": 3, "Node": 36, "Genre": "System", "Help": "Manufacturer Specific Device ID/Model", "ValueIDKey": 844425548562455, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/114/value/1125900525273111/,{ "Label": "Serial Number", "Value": "0d000100010108010100000004030800000000", "Units": "", "Min": 0, "Max": 0, "Type": "String", "Instance": 1, "CommandClass": "COMMAND_CLASS_MANUFACTURER_SPECIFIC", "Index": 4, "Node": 36, "Genre": "System", "Help": "Device Serial Number", "ValueIDKey": 1125900525273111, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/115/,{ "Instance": 1, "CommandClassId": 115, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/115/value/618446868/,{ "Label": "Powerlevel", "Value": { "List": [ { "Value": 0, "Label": "Normal" }, { "Value": 1, "Label": "-1dB" }, { "Value": 2, "Label": "-2dB" }, { "Value": 3, "Label": "-3dB" }, { "Value": 4, "Label": "-4dB" }, { "Value": 5, "Label": "-5dB" }, { "Value": 6, "Label": "-6dB" }, { "Value": 7, "Label": "-7dB" }, { "Value": 8, "Label": "-8dB" }, { "Value": 9, "Label": "-9dB" } ], "Selected": "Normal" }, "Units": "dB", "Min": 0, "Max": 0, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 0, "Node": 36, "Genre": "System", "Help": "Output RF PowerLevel", "ValueIDKey": 618446868, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/115/value/281475595157521/,{ "Label": "Timeout", "Value": 0, "Units": "seconds", "Min": 0, "Max": 255, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 1, "Node": 36, "Genre": "System", "Help": "Timeout till the PowerLevel is reset to Normal", "ValueIDKey": 281475595157521, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/115/value/562950571868184/,{ "Label": "Set Powerlevel", "Value": false, "Units": "", "Min": 0, "Max": 0, "Type": "Button", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 2, "Node": 36, "Genre": "System", "Help": "Apply the Output PowerLevel and Timeout Values", "ValueIDKey": 562950571868184, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/115/value/844425548578833/,{ "Label": "Test Node", "Value": 0, "Units": "", "Min": 0, "Max": 255, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 3, "Node": 36, "Genre": "System", "Help": "Node to Perform a test against", "ValueIDKey": 844425548578833, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/115/value/1125900525289492/,{ "Label": "Test Powerlevel", "Value": { "List": [ { "Value": 0, "Label": "Normal" }, { "Value": 1, "Label": "-1dB" }, { "Value": 2, "Label": "-2dB" }, { "Value": 3, "Label": "-3dB" }, { "Value": 4, "Label": "-4dB" }, { "Value": 5, "Label": "-5dB" }, { "Value": 6, "Label": "-6dB" }, { "Value": 7, "Label": "-7dB" }, { "Value": 8, "Label": "-8dB" }, { "Value": 9, "Label": "-9dB" } ], "Selected": "Normal" }, "Units": "dB", "Min": 0, "Max": 0, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 4, "Node": 36, "Genre": "System", "Help": "PowerLevel to use for the Test", "ValueIDKey": 1125900525289492, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/115/value/1407375502000150/,{ "Label": "Frame Count", "Value": 0, "Units": "", "Min": -32768, "Max": 32767, "Type": "Short", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 5, "Node": 36, "Genre": "System", "Help": "How Many Messages to send to the Note for the Test", "ValueIDKey": 1407375502000150, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/115/value/1688850478710808/,{ "Label": "Test", "Value": false, "Units": "", "Min": 0, "Max": 0, "Type": "Button", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 6, "Node": 36, "Genre": "System", "Help": "Perform a PowerLevel Test against the a Node", "ValueIDKey": 1688850478710808, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/115/value/1970325455421464/,{ "Label": "Report", "Value": false, "Units": "", "Min": 0, "Max": 0, "Type": "Button", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 7, "Node": 36, "Genre": "System", "Help": "Get the results of the latest PowerLevel Test against a Node", "ValueIDKey": 1970325455421464, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/115/value/2251800432132116/,{ "Label": "Test Status", "Value": { "List": [ { "Value": 0, "Label": "Failed" }, { "Value": 1, "Label": "Success" }, { "Value": 2, "Label": "In Progress" } ], "Selected": "Failed" }, "Units": "", "Min": 0, "Max": 0, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 8, "Node": 36, "Genre": "System", "Help": "The Current Status of the last PowerNode Test Executed", "ValueIDKey": 2251800432132116, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/115/value/2533275408842774/,{ "Label": "Acked Frames", "Value": 0, "Units": "", "Min": -32768, "Max": 32767, "Type": "Short", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 9, "Node": 36, "Genre": "System", "Help": "Number of Messages successfully Acked by the Target Node", "ValueIDKey": 2533275408842774, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/128/,{ "Instance": 1, "CommandClassId": 128, "CommandClass": "COMMAND_CLASS_BATTERY", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/128/value/610271249/,{ "Label": "Battery Level", "Value": 100, "Units": "%", "Min": 0, "Max": 255, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_BATTERY", "Index": 0, "Node": 36, "Genre": "User", "Help": "Current Battery Level", "ValueIDKey": 610271249, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/132/,{ "Instance": 1, "CommandClassId": 132, "CommandClass": "COMMAND_CLASS_WAKE_UP", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/132/value/281475595436051/,{ "Label": "Minimum Wake-up Interval", "Value": 240, "Units": "Seconds", "Min": -2147483648, "Max": 2147483647, "Type": "Int", "Instance": 1, "CommandClass": "COMMAND_CLASS_WAKE_UP", "Index": 1, "Node": 36, "Genre": "System", "Help": "Minimum Time in seconds the device will wake up", "ValueIDKey": 281475595436051, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/132/value/562950572146707/,{ "Label": "Maximum Wake-up Interval", "Value": 16777200, "Units": "Seconds", "Min": -2147483648, "Max": 2147483647, "Type": "Int", "Instance": 1, "CommandClass": "COMMAND_CLASS_WAKE_UP", "Index": 2, "Node": 36, "Genre": "System", "Help": "Maximum Time in seconds the device will wake up", "ValueIDKey": 562950572146707, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/132/value/844425548857363/,{ "Label": "Default Wake-up Interval", "Value": 3600, "Units": "Seconds", "Min": -2147483648, "Max": 2147483647, "Type": "Int", "Instance": 1, "CommandClass": "COMMAND_CLASS_WAKE_UP", "Index": 3, "Node": 36, "Genre": "System", "Help": "The Default Wake-Up Interval the device will wake up", "ValueIDKey": 844425548857363, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/132/value/1125900525568019/,{ "Label": "Wake-up Interval Step", "Value": 240, "Units": "Seconds", "Min": -2147483648, "Max": 2147483647, "Type": "Int", "Instance": 1, "CommandClass": "COMMAND_CLASS_WAKE_UP", "Index": 4, "Node": 36, "Genre": "System", "Help": "Step Size on Wake-up interval", "ValueIDKey": 1125900525568019, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/132/value/618725395/,{ "Label": "Wake-up Interval", "Value": 3600, "Units": "Seconds", "Min": -2147483648, "Max": 2147483647, "Type": "Int", "Instance": 1, "CommandClass": "COMMAND_CLASS_WAKE_UP", "Index": 0, "Node": 36, "Genre": "System", "Help": "How often the Device will Wake up to check for pending commands", "ValueIDKey": 618725395, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/134/,{ "Instance": 1, "CommandClassId": 134, "CommandClass": "COMMAND_CLASS_VERSION", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/134/value/618758167/,{ "Label": "Library Version", "Value": "3", "Units": "", "Min": 0, "Max": 0, "Type": "String", "Instance": 1, "CommandClass": "COMMAND_CLASS_VERSION", "Index": 0, "Node": 36, "Genre": "System", "Help": "Z-Wave Library Version", "ValueIDKey": 618758167, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/134/value/281475595468823/,{ "Label": "Protocol Version", "Value": "4.54", "Units": "", "Min": 0, "Max": 0, "Type": "String", "Instance": 1, "CommandClass": "COMMAND_CLASS_VERSION", "Index": 1, "Node": 36, "Genre": "System", "Help": "Z-Wave Protocol Version", "ValueIDKey": 281475595468823, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/134/value/562950572179479/,{ "Label": "Application Version", "Value": "1.05", "Units": "", "Min": 0, "Max": 0, "Type": "String", "Instance": 1, "CommandClass": "COMMAND_CLASS_VERSION", "Index": 2, "Node": 36, "Genre": "System", "Help": "Application Version", "ValueIDKey": 562950572179479, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/2/,{ "Instance": 2, "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/2/commandclass/94/,{ "Instance": 2, "CommandClassId": 94, "CommandClass": "COMMAND_CLASS_ZWAVEPLUS_INFO", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/2/commandclass/94/value/618102817/,{ "Label": "Instance 2: ZWave+ Version", "Value": 1, "Units": "", "Min": 0, "Max": 255, "Type": "Byte", "Instance": 2, "CommandClass": "COMMAND_CLASS_ZWAVEPLUS_INFO", "Index": 0, "Node": 36, "Genre": "System", "Help": "ZWave+ Version Supported on the Device", "ValueIDKey": 618102817, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/2/commandclass/94/value/281475594813478/,{ "Label": "Instance 2: InstallerIcon", "Value": 3079, "Units": "", "Min": -32768, "Max": 32767, "Type": "Short", "Instance": 2, "CommandClass": "COMMAND_CLASS_ZWAVEPLUS_INFO", "Index": 1, "Node": 36, "Genre": "System", "Help": "Icon File to use for the Installer Application", "ValueIDKey": 281475594813478, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/2/commandclass/94/value/562950571524134/,{ "Label": "Instance 2: UserIcon", "Value": 3079, "Units": "", "Min": -32768, "Max": 32767, "Type": "Short", "Instance": 2, "CommandClass": "COMMAND_CLASS_ZWAVEPLUS_INFO", "Index": 2, "Node": 36, "Genre": "System", "Help": "Icon File to use for the User Application", "ValueIDKey": 562950571524134, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/2/commandclass/113/,{ "Instance": 2, "CommandClassId": 113, "CommandClass": "COMMAND_CLASS_NOTIFICATION", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/2/commandclass/113/value/1407375493578788/,{ "Label": "Instance 2: Water", "Value": { "List": [ { "Value": 0, "Label": "Clear" }, { "Value": 2, "Label": "Water Leak at Unknown Location" } ], "Selected": "Clear" }, "Units": "", "Min": 0, "Max": 0, "Type": "List", "Instance": 2, "CommandClass": "COMMAND_CLASS_NOTIFICATION", "Index": 5, "Node": 36, "Genre": "User", "Help": "Water Alerts", "ValueIDKey": 1407375493578788, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/2/commandclass/113/value/72057594647953441/,{ "Label": "Instance 2: Previous Event Cleared", "Value": 0, "Units": "", "Min": 0, "Max": 255, "Type": "Byte", "Instance": 2, "CommandClass": "COMMAND_CLASS_NOTIFICATION", "Index": 256, "Node": 36, "Genre": "User", "Help": "Previous Event that was sent", "ValueIDKey": 72057594647953441, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/association/1/,{ "Name": "Lifeline", "Help": "", "MaxAssociations": 5, "Members": [ "1.1" ], "TimeStamp": 1579566891} +OpenZWave/1/node/36/association/2/,{ "Name": "Send the configuration parameter 0x88", "Help": "", "MaxAssociations": 5, "Members": [], "TimeStamp": 1579566891} +OpenZWave/1/node/36/association/3/,{ "Name": "Send Basic Set when the Sensor probe 1 is triggered", "Help": "", "MaxAssociations": 5, "Members": [], "TimeStamp": 1579566891} +OpenZWave/1/node/36/association/4/,{ "Name": "Send Basic Set when the Sensor probe 2 is triggered", "Help": "", "MaxAssociations": 5, "Members": [], "TimeStamp": 1579566891} +OpenZWave/1/node/37/,{ "NodeID": 37, "NodeQueryStage": "CacheLoad", "isListening": false, "isFlirs": false, "isBeaming": true, "isRouting": true, "isSecurityv1": false, "isZWavePlus": false, "isNIFRecieved": true, "isAwake": true, "isFailed": false, "MetaData": { "OZWInfoURL": "http://www.openzwave.com/device-database/0371:0005:0002", "ZWAProductURL": "", "ProductPic": "images/aeotec/zwa005.png", "Description": "Aeotec TriSensor is a universal Z-Wave Plus compatible product, consists of temperature, lighting and motion sensors, powered by a CR123A battery. It can be included and operated in any Z-Wave network with other Z-Wave certified devices from other manufacturers and/or other applications. By the built-in motion sensor, an alam will be sent to the gateway when the motion sensor is triggered.", "ProductManualURL": "https://Products.Z-WaveAlliance.org/ProductManual/File?folder=&filename=Manuals/2919/TriSensor user manual 20180416.pdf", "ProductPageURL": "", "InclusionHelp": "Press once TriSensor’s Action Button. If it is the first installation, the yellow LED will keep solid until whole network processing is complete. If successful, the LED will flash white -> green -> white -> green, after 2 seconds finished. If failed, the yellow LED lasts for 30 seconds, then the green LED flashes once. If it is the S2 encryption network, please enter the first 5 digits of DSK.", "ExclusionHelp": "Press once TriSensor’s Action Button, the Purple LED will keep solid until whole network processing is complete. If the exclusion is successful, the LED will flash white -> green - >white -> green and then LED will pulse a blue. If failed, the yellow LED lasts for 30 seconds, then the green LED flashes once.", "ResetHelp": "1. Power up the device. 2. Press and hold the button for 15s until Red Led is blinking,then release the button. Note: Please use this procedure only when the network primary controller is missing or otherwise inoperable.", "WakeupHelp": "Press and hold the button at least 2s until Red Led is on and then release the button,device will send wakeup notification to controller if device is in a Z-Wave network.", "ProductSupportURL": "", "Frequency": "", "Name": "TriSensor", "ProductPicBase64": "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" }, "Event": "nodeNaming", "TimeStamp": 1579566891, "NodeManufacturerName": "Aeotec Limited", "NodeProductName": "ZWA005 TriSensor", "NodeBasicString": "Routing Slave", "NodeBasic": 4, "NodeGenericString": "Notification Sensor", "NodeGeneric": 7, "NodeSpecificString": "Notification Sensor", "NodeSpecific": 1, "NodeManufacturerID": "0x0371", "NodeProductType": "0x0102", "NodeProductID": "0x0005", "NodeBaudRate": 100000, "NodeVersion": 4, "NodeGroups": 3} +OpenZWave/1/node/37/instance/1/,{ "Instance": 1, "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/32/,{ "Instance": 1, "CommandClassId": 32, "CommandClass": "COMMAND_CLASS_BASIC", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/32/value/621281297/,{ "Label": "Basic", "Value": 0, "Units": "", "Min": 0, "Max": 255, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_BASIC", "Index": 0, "Node": 37, "Genre": "Basic", "Help": "Basic status of the node", "ValueIDKey": 621281297, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/48/,{ "Instance": 1, "CommandClassId": 48, "CommandClass": "COMMAND_CLASS_SENSOR_BINARY", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/48/value/625737744/,{ "Label": "Sensor", "Value": false, "Units": "", "Min": 0, "Max": 0, "Type": "Bool", "Instance": 1, "CommandClass": "COMMAND_CLASS_SENSOR_BINARY", "Index": 0, "Node": 37, "Genre": "User", "Help": "Binary Sensor State", "ValueIDKey": 625737744, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/49/,{ "Instance": 1, "CommandClassId": 49, "CommandClass": "COMMAND_CLASS_SENSOR_MULTILEVEL", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/49/value/281475602464786/,{ "Label": "Air Temperature", "Value": 20.700000762939454, "Units": "C", "Min": 0, "Max": 0, "Type": "Decimal", "Instance": 1, "CommandClass": "COMMAND_CLASS_SENSOR_MULTILEVEL", "Index": 1, "Node": 37, "Genre": "User", "Help": "Air Temperature Sensor Value", "ValueIDKey": 281475602464786, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/49/value/844425555886098/,{ "Label": "Illuminance", "Value": 0.0, "Units": "Lux", "Min": 0, "Max": 0, "Type": "Decimal", "Instance": 1, "CommandClass": "COMMAND_CLASS_SENSOR_MULTILEVEL", "Index": 3, "Node": 37, "Genre": "User", "Help": "Luminance Sensor Value", "ValueIDKey": 844425555886098, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/49/value/72057594672070676/,{ "Label": "Air Temperature Units", "Value": { "List": [ { "Value": 0, "Label": "Celsius" }, { "Value": 1, "Label": "Fahrenheit" } ], "Selected": "Celsius" }, "Units": "", "Min": 0, "Max": 0, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_SENSOR_MULTILEVEL", "Index": 256, "Node": 37, "Genre": "System", "Help": "Air Temperature Sensor Available Units", "ValueIDKey": 72057594672070676, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/49/value/72620544625491988/,{ "Label": "Illuminance Units", "Value": { "List": [ { "Value": 1, "Label": "Lux" } ], "Selected": "Lux" }, "Units": "", "Min": 0, "Max": 0, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_SENSOR_MULTILEVEL", "Index": 258, "Node": 37, "Genre": "System", "Help": "Luminance Sensor Available Units", "ValueIDKey": 72620544625491988, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/94/,{ "Instance": 1, "CommandClassId": 94, "CommandClass": "COMMAND_CLASS_ZWAVEPLUS_INFO", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/94/value/634880017/,{ "Label": "ZWave+ Version", "Value": 1, "Units": "", "Min": 0, "Max": 255, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_ZWAVEPLUS_INFO", "Index": 0, "Node": 37, "Genre": "System", "Help": "ZWave+ Version Supported on the Device", "ValueIDKey": 634880017, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/94/value/281475611590678/,{ "Label": "InstallerIcon", "Value": 3079, "Units": "", "Min": -32768, "Max": 32767, "Type": "Short", "Instance": 1, "CommandClass": "COMMAND_CLASS_ZWAVEPLUS_INFO", "Index": 1, "Node": 37, "Genre": "System", "Help": "Icon File to use for the Installer Application", "ValueIDKey": 281475611590678, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/94/value/562950588301334/,{ "Label": "UserIcon", "Value": 3079, "Units": "", "Min": -32768, "Max": 32767, "Type": "Short", "Instance": 1, "CommandClass": "COMMAND_CLASS_ZWAVEPLUS_INFO", "Index": 2, "Node": 37, "Genre": "System", "Help": "Icon File to use for the User Application", "ValueIDKey": 562950588301334, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/112/,{ "Instance": 1, "CommandClassId": 112, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/112/value/281475607691286/,{ "Label": "Motion Re-trigger Time", "Value": 30, "Units": "Second", "Min": 0, "Max": 32767, "Type": "Short", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 1, "Node": 37, "Genre": "Config", "Help": "This parameter is configured the delay time before PIR sensor can be triggered again to reset motion timeout counter. Value = 0 will disable PIR sensor from triggering until motion timeout has finished.", "ValueIDKey": 281475607691286, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/112/value/562950584401942/,{ "Label": "Motion clear time", "Value": 240, "Units": "Second", "Min": 1, "Max": 32767, "Type": "Short", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 2, "Node": 37, "Genre": "Config", "Help": "This configures the clear time when your motion sensor times out and sends a no motion status.", "ValueIDKey": 562950584401942, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/112/value/844425561112596/,{ "Label": "Motion Sensitivity", "Value": { "List": [ { "Value": 0, "Label": "Disabled" }, { "Value": 1, "Label": "1" }, { "Value": 2, "Label": "2" }, { "Value": 3, "Label": "3" }, { "Value": 4, "Label": "4" }, { "Value": 5, "Label": "5" }, { "Value": 6, "Label": "6" }, { "Value": 7, "Label": "7" }, { "Value": 8, "Label": "8" }, { "Value": 9, "Label": "9" }, { "Value": 10, "Label": "10" }, { "Value": 11, "Label": "11" } ], "Selected": "8" }, "Units": "", "Min": 0, "Max": 11, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 3, "Node": 37, "Genre": "Config", "Help": "This parameter is configured the sensitivity that motion detect. 0 - PIR sensor disabled. 1 - Lowest sensitivity. 11 - Highest sensitivity.", "ValueIDKey": 844425561112596, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/112/value/1125900537823252/,{ "Label": "Binary Sensor Report", "Value": { "List": [ { "Value": 0, "Label": "Disable" }, { "Value": 1, "Label": "Enable" } ], "Selected": "Disable" }, "Units": "", "Min": 0, "Max": 1, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 4, "Node": 37, "Genre": "Config", "Help": "Enable/disable sensor binary report when motion event is detected or cleared", "ValueIDKey": 1125900537823252, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/112/value/1407375514533908/,{ "Label": "Disable BASIC_SET to Associated nodes", "Value": { "List": [ { "Value": 0, "Label": "Disabled All Group Basic Set Command" }, { "Value": 1, "Label": "Enabled Group 2" }, { "Value": 2, "Label": "Enabled Group 3 " }, { "Value": 3, "Label": "Enabled Group 2 and Group 3" } ], "Selected": "Enabled Group 2 and Group 3" }, "Units": "", "Min": 0, "Max": 3, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 5, "Node": 37, "Genre": "Config", "Help": "This parameter is configured the enabled or disabled send BASIC_SET command to nodes that associated in group 2 and group 3.", "ValueIDKey": 1407375514533908, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/112/value/1688850491244564/,{ "Label": "Basic Set Value Settings for Group 2", "Value": { "List": [ { "Value": 0, "Label": "0xFF when motion is triggered and 0x00 when motion is cleared" }, { "Value": 1, "Label": "0x00 when motion is triggered and 0xFF when motion is cleared" }, { "Value": 2, "Label": "0xFF when motion is triggered" }, { "Value": 3, "Label": "0x00 when motion is triggered" }, { "Value": 4, "Label": "0x00 when motion event is cleared" }, { "Value": 5, "Label": "0xFF when motion event is cleared" } ], "Selected": "0xFF when motion is triggered and 0x00 when motion is cleared" }, "Units": "", "Min": 0, "Max": 5, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 6, "Node": 37, "Genre": "Config", "Help": "Define Basic Set Value when motion event is triggered and / or cleared", "ValueIDKey": 1688850491244564, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/112/value/1970325467955222/,{ "Label": "Temperature Alarm Value", "Value": 239, "Units": "0.1", "Min": -32768, "Max": 32767, "Type": "Short", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 7, "Node": 37, "Genre": "Config", "Help": "This parameter is configured the threshold value that alarm level for temperature. When the current ambient temperature value is larger than this configuration value, device will send a BASIC_SET = 0xFF to nodes associated in group 3. If current temperature value is less than this value, device will send a BASIC_SET = 0x00 to nodes associated in group 3. Value = [Value] x 0.1(Celsius / Fahrenheit) Available Settings: -400 to 850 (40.0 to 85.0 Celsius) or -400 to 1185 (-40.0 to 118.5 Fahrenheit). Default value: 239 (23.9 Celsius) or 750 (75.0 Fahrenheit)", "ValueIDKey": 1970325467955222, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/112/value/2814750398087188/,{ "Label": "LED over TriSensor", "Value": { "List": [ { "Value": 0, "Label": "Disable" }, { "Value": 1, "Label": "Enable" } ], "Selected": "Enable" }, "Units": "", "Min": 0, "Max": 1, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 10, "Node": 37, "Genre": "Config", "Help": "Enable or Disable LED over TriSensor This completely disables all LED reaction regardless of Parameter 9 - 13 settings", "ValueIDKey": 2814750398087188, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/112/value/3096225374797844/,{ "Label": "Motion report LED", "Value": { "List": [ { "Value": 0, "Label": "Disabled" }, { "Value": 1, "Label": "Red" }, { "Value": 2, "Label": "Green" }, { "Value": 3, "Label": "Blue" }, { "Value": 4, "Label": "Yellow" }, { "Value": 5, "Label": "Pink" }, { "Value": 6, "Label": "Cyan" }, { "Value": 7, "Label": "Purple" }, { "Value": 8, "Label": "Orange" } ], "Selected": "Green" }, "Units": "", "Min": 0, "Max": 8, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 11, "Node": 37, "Genre": "Config", "Help": "This setting changes the color of the LED when your TriSensor sends a motion report.", "ValueIDKey": 3096225374797844, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/112/value/3377700351508500/,{ "Label": "Temperature report LED", "Value": { "List": [ { "Value": 0, "Label": "Disabled" }, { "Value": 1, "Label": "Red" }, { "Value": 2, "Label": "Green" }, { "Value": 3, "Label": "Blue" }, { "Value": 4, "Label": "Yellow" }, { "Value": 5, "Label": "Pink" }, { "Value": 6, "Label": "Cyan" }, { "Value": 7, "Label": "Purple" }, { "Value": 8, "Label": "Orange" } ], "Selected": "Disabled" }, "Units": "", "Min": 0, "Max": 1, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 12, "Node": 37, "Genre": "Config", "Help": "This setting changes the color of the LED when your TriSensor sends a temperature report.", "ValueIDKey": 3377700351508500, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/112/value/3659175328219156/,{ "Label": "Light report LED", "Value": { "List": [ { "Value": 0, "Label": "Disabled" }, { "Value": 1, "Label": "Red" }, { "Value": 2, "Label": "Green" }, { "Value": 3, "Label": "Blue" }, { "Value": 4, "Label": "Yellow" }, { "Value": 5, "Label": "Pink" }, { "Value": 6, "Label": "Cyan" }, { "Value": 7, "Label": "Purple" }, { "Value": 8, "Label": "Orange" } ], "Selected": "Disabled" }, "Units": "", "Min": 0, "Max": 8, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 13, "Node": 37, "Genre": "Config", "Help": "This setting changes the color of the LED when your TriSensor sends a light report.", "ValueIDKey": 3659175328219156, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/112/value/3940650304929812/,{ "Label": "Battery report LED", "Value": { "List": [ { "Value": 0, "Label": "Disabled" }, { "Value": 1, "Label": "Red" }, { "Value": 2, "Label": "Green" }, { "Value": 3, "Label": "Blue" }, { "Value": 4, "Label": "Yellow" }, { "Value": 5, "Label": "Pink" }, { "Value": 6, "Label": "Cyan" }, { "Value": 7, "Label": "Purple" }, { "Value": 8, "Label": "Orange" } ], "Selected": "Disabled" }, "Units": "", "Min": 0, "Max": 8, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 14, "Node": 37, "Genre": "Config", "Help": "It is possible to change the color of what the LED blinks when your TriSensor sends a battery report.", "ValueIDKey": 3940650304929812, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/112/value/4222125281640468/,{ "Label": "Wakeup report LED", "Value": { "List": [ { "Value": 0, "Label": "Disabled" }, { "Value": 1, "Label": "Red" }, { "Value": 2, "Label": "Green" }, { "Value": 3, "Label": "Blue" }, { "Value": 4, "Label": "Yellow" }, { "Value": 5, "Label": "Pink" }, { "Value": 6, "Label": "Cyan" }, { "Value": 7, "Label": "Purple" }, { "Value": 8, "Label": "Orange" } ], "Selected": "Disabled" }, "Units": "", "Min": 0, "Max": 8, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 15, "Node": 37, "Genre": "Config", "Help": "This setting changes the color of the LED when your TriSensor sends a wakeup report.", "ValueIDKey": 4222125281640468, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/112/value/5629500165193748/,{ "Label": "Temperature Scale Setting", "Value": { "List": [ { "Value": 0, "Label": "Celsius" }, { "Value": 1, "Label": "Fahrenheit" } ], "Selected": "Celsius" }, "Units": "", "Min": 0, "Max": 1, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 20, "Node": 37, "Genre": "Config", "Help": "Configure temperature sensor scale type, Temperature to report in Celsius or Fahrenheit", "ValueIDKey": 5629500165193748, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/112/value/5910975141904406/,{ "Label": "Temperature Threshold reporting", "Value": 20, "Units": "0.1", "Min": 0, "Max": 250, "Type": "Short", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 21, "Node": 37, "Genre": "Config", "Help": "Change threshold value for change in temperature to induce an automatic report for temperature sensor. Scale is identical setting in Parameter No.20. 0-> Disable Threshold Report for Temperature Sensor. Setting of value 20 can be a change of -2.0 or +2.0 (C or F depending on Parameter No.20) to induce automatic report or setting a value of 2 will be a change of 0.2(C or F). Available Settings: 0 to 250.", "ValueIDKey": 5910975141904406, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/112/value/6192450118615062/,{ "Label": "Light intensity Threshold Value to Report", "Value": 100, "Units": "Lux", "Min": 0, "Max": 10000, "Type": "Short", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 22, "Node": 37, "Genre": "Config", "Help": "Change threshold value for change in lux to induce an automatic report for light sensor.", "ValueIDKey": 6192450118615062, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/112/value/6473925095325718/,{ "Label": "Temperature Sensor Report Interval", "Value": 3600, "Units": "Second", "Min": 0, "Max": 32767, "Type": "Short", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 23, "Node": 37, "Genre": "Config", "Help": "This parameter is configured the time interval for temperature sensor report. This value is larger, the battery life is longer. And the temperature value changed is not obvious.", "ValueIDKey": 6473925095325718, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/112/value/6755400072036374/,{ "Label": "Light Sensor Report Interval", "Value": 3600, "Units": "Second", "Min": 0, "Max": 32767, "Type": "Short", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 24, "Node": 37, "Genre": "Config", "Help": "This parameter is configured the time interval for light sensor report. This value is larger, the battery life is longer. And the light intensity changed is not obvious.", "ValueIDKey": 6755400072036374, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/112/value/8444249932300310/,{ "Label": "Temperature Offset Value", "Value": 0, "Units": "0.1", "Min": -32768, "Max": 32767, "Type": "Short", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 30, "Node": 37, "Genre": "Config", "Help": "The current measuring temperature value can be add and minus a value by this setting. The scale can be decided by Parameter Number 20. Temperature Offset Value = [Value] * 0.1(Celsius / Fahrenheit) Available Settings: -200 to 200.", "ValueIDKey": 8444249932300310, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/112/value/8725724909010966/,{ "Label": "Light Intensity Offset Value", "Value": 0, "Units": "Lux", "Min": -32768, "Max": 32767, "Type": "Short", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 31, "Node": 37, "Genre": "Config", "Help": "The current measuring light intensity value can be add and minus a value by this setting. Available Settings: -1000 to 1000.", "ValueIDKey": 8725724909010966, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/112/value/28147498302046230/,{ "Label": "Light Sensor Calibrated Coefficient", "Value": 1024, "Units": "", "Min": 0, "Max": 32767, "Type": "Short", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 100, "Node": 37, "Genre": "Config", "Help": "This configuration defines the calibrated scale for ambient light intensity. Because the method and position that the sensor mounted and the cover of sensor will bring measurement error, user can get more real light intensity by this parameter setting. User should run the steps as blows for calibrating 1) Set this parameter value to default (Assumes the sensor has been added in a Z- Wave Network). 2) Place a digital light meter close to sensor and keep the same direction, monitor the light intensity value (Vm) which report to controller and record it. The same time user should record the value (Vs) of light meter. 3) The scale calibration formula: k = Vm / Vs. 4) The value of k is then multiplied by 1024 and rounded to the nearest whole number. 5) Set the value getting in 5) to this parameter, calibrate finished. For example, Vm = 300, Vs = 2600, then k = 2600 / 300 = 8.6667 k = 8.6667 * 1024 = 8874.7 => 8875. The parameter should be set to 8875.", "ValueIDKey": 28147498302046230, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/113/,{ "Instance": 1, "CommandClassId": 113, "CommandClass": "COMMAND_CLASS_NOTIFICATION", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/113/value/1970325463777300/,{ "Label": "Home Security", "Value": { "List": [ { "Value": 0, "Label": "Clear" }, { "Value": 8, "Label": "Motion Detected at Unknown Location" } ], "Selected": "Clear", "Selected_id": 0 }, "Units": "", "Min": 0, "Max": 0, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_NOTIFICATION", "Index": 7, "Node": 37, "Genre": "User", "Help": "Home Security Alerts", "ValueIDKey": 1970325463777300, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/113/value/72057594664730641/,{ "Label": "Previous Event Cleared", "Value": 0, "Units": "", "Min": 0, "Max": 255, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_NOTIFICATION", "Index": 256, "Node": 37, "Genre": "User", "Help": "Previous Event that was sent", "ValueIDKey": 72057594664730641, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/114/,{ "Instance": 1, "CommandClassId": 114, "CommandClass": "COMMAND_CLASS_MANUFACTURER_SPECIFIC", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/114/value/635207699/,{ "Label": "Loaded Config Revision", "Value": 6, "Units": "", "Min": -2147483648, "Max": 2147483647, "Type": "Int", "Instance": 1, "CommandClass": "COMMAND_CLASS_MANUFACTURER_SPECIFIC", "Index": 0, "Node": 37, "Genre": "System", "Help": "Revision of the Config file currently loaded", "ValueIDKey": 635207699, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/114/value/281475611918355/,{ "Label": "Config File Revision", "Value": 6, "Units": "", "Min": -2147483648, "Max": 2147483647, "Type": "Int", "Instance": 1, "CommandClass": "COMMAND_CLASS_MANUFACTURER_SPECIFIC", "Index": 1, "Node": 37, "Genre": "System", "Help": "Revision of the Config file on the File System", "ValueIDKey": 281475611918355, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/114/value/562950588629011/,{ "Label": "Latest Available Config File Revision", "Value": 6, "Units": "", "Min": -2147483648, "Max": 2147483647, "Type": "Int", "Instance": 1, "CommandClass": "COMMAND_CLASS_MANUFACTURER_SPECIFIC", "Index": 2, "Node": 37, "Genre": "System", "Help": "Latest Revision of the Config file available for download", "ValueIDKey": 562950588629011, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/114/value/844425565339671/,{ "Label": "Device ID", "Value": "", "Units": "", "Min": 0, "Max": 0, "Type": "String", "Instance": 1, "CommandClass": "COMMAND_CLASS_MANUFACTURER_SPECIFIC", "Index": 3, "Node": 37, "Genre": "System", "Help": "Manufacturer Specific Device ID/Model", "ValueIDKey": 844425565339671, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/114/value/1125900542050327/,{ "Label": "Serial Number", "Value": "", "Units": "", "Min": 0, "Max": 0, "Type": "String", "Instance": 1, "CommandClass": "COMMAND_CLASS_MANUFACTURER_SPECIFIC", "Index": 4, "Node": 37, "Genre": "System", "Help": "Device Serial Number", "ValueIDKey": 1125900542050327, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/115/,{ "Instance": 1, "CommandClassId": 115, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/115/value/635224084/,{ "Label": "Powerlevel", "Value": { "List": [ { "Value": 0, "Label": "Normal" }, { "Value": 1, "Label": "-1dB" }, { "Value": 2, "Label": "-2dB" }, { "Value": 3, "Label": "-3dB" }, { "Value": 4, "Label": "-4dB" }, { "Value": 5, "Label": "-5dB" }, { "Value": 6, "Label": "-6dB" }, { "Value": 7, "Label": "-7dB" }, { "Value": 8, "Label": "-8dB" }, { "Value": 9, "Label": "-9dB" } ], "Selected": "Normal" }, "Units": "dB", "Min": 0, "Max": 0, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 0, "Node": 37, "Genre": "System", "Help": "Output RF PowerLevel", "ValueIDKey": 635224084, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/115/value/281475611934737/,{ "Label": "Timeout", "Value": 0, "Units": "seconds", "Min": 0, "Max": 255, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 1, "Node": 37, "Genre": "System", "Help": "Timeout till the PowerLevel is reset to Normal", "ValueIDKey": 281475611934737, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/115/value/562950588645400/,{ "Label": "Set Powerlevel", "Value": false, "Units": "", "Min": 0, "Max": 0, "Type": "Button", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 2, "Node": 37, "Genre": "System", "Help": "Apply the Output PowerLevel and Timeout Values", "ValueIDKey": 562950588645400, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/115/value/844425565356049/,{ "Label": "Test Node", "Value": 0, "Units": "", "Min": 0, "Max": 255, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 3, "Node": 37, "Genre": "System", "Help": "Node to Perform a test against", "ValueIDKey": 844425565356049, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/115/value/1125900542066708/,{ "Label": "Test Powerlevel", "Value": { "List": [ { "Value": 0, "Label": "Normal" }, { "Value": 1, "Label": "-1dB" }, { "Value": 2, "Label": "-2dB" }, { "Value": 3, "Label": "-3dB" }, { "Value": 4, "Label": "-4dB" }, { "Value": 5, "Label": "-5dB" }, { "Value": 6, "Label": "-6dB" }, { "Value": 7, "Label": "-7dB" }, { "Value": 8, "Label": "-8dB" }, { "Value": 9, "Label": "-9dB" } ], "Selected": "Normal" }, "Units": "dB", "Min": 0, "Max": 0, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 4, "Node": 37, "Genre": "System", "Help": "PowerLevel to use for the Test", "ValueIDKey": 1125900542066708, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/115/value/1407375518777366/,{ "Label": "Frame Count", "Value": 0, "Units": "", "Min": -32768, "Max": 32767, "Type": "Short", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 5, "Node": 37, "Genre": "System", "Help": "How Many Messages to send to the Note for the Test", "ValueIDKey": 1407375518777366, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/115/value/1688850495488024/,{ "Label": "Test", "Value": false, "Units": "", "Min": 0, "Max": 0, "Type": "Button", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 6, "Node": 37, "Genre": "System", "Help": "Perform a PowerLevel Test against the a Node", "ValueIDKey": 1688850495488024, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/115/value/1970325472198680/,{ "Label": "Report", "Value": false, "Units": "", "Min": 0, "Max": 0, "Type": "Button", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 7, "Node": 37, "Genre": "System", "Help": "Get the results of the latest PowerLevel Test against a Node", "ValueIDKey": 1970325472198680, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/115/value/2251800448909332/,{ "Label": "Test Status", "Value": { "List": [ { "Value": 0, "Label": "Failed" }, { "Value": 1, "Label": "Success" }, { "Value": 2, "Label": "In Progress" } ], "Selected": "Failed" }, "Units": "", "Min": 0, "Max": 0, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 8, "Node": 37, "Genre": "System", "Help": "The Current Status of the last PowerNode Test Executed", "ValueIDKey": 2251800448909332, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/115/value/2533275425619990/,{ "Label": "Acked Frames", "Value": 0, "Units": "", "Min": -32768, "Max": 32767, "Type": "Short", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 9, "Node": 37, "Genre": "System", "Help": "Number of Messages successfully Acked by the Target Node", "ValueIDKey": 2533275425619990, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/128/,{ "Instance": 1, "CommandClassId": 128, "CommandClass": "COMMAND_CLASS_BATTERY", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/128/value/627048465/,{ "Label": "Battery Level", "Value": 90, "Units": "%", "Min": 0, "Max": 255, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_BATTERY", "Index": 0, "Node": 37, "Genre": "User", "Help": "Current Battery Level", "ValueIDKey": 627048465, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/132/,{ "Instance": 1, "CommandClassId": 132, "CommandClass": "COMMAND_CLASS_WAKE_UP", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/132/value/281475612213267/,{ "Label": "Minimum Wake-up Interval", "Value": 1800, "Units": "Seconds", "Min": -2147483648, "Max": 2147483647, "Type": "Int", "Instance": 1, "CommandClass": "COMMAND_CLASS_WAKE_UP", "Index": 1, "Node": 37, "Genre": "System", "Help": "Minimum Time in seconds the device will wake up", "ValueIDKey": 281475612213267, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/132/value/562950588923923/,{ "Label": "Maximum Wake-up Interval", "Value": 64800, "Units": "Seconds", "Min": -2147483648, "Max": 2147483647, "Type": "Int", "Instance": 1, "CommandClass": "COMMAND_CLASS_WAKE_UP", "Index": 2, "Node": 37, "Genre": "System", "Help": "Maximum Time in seconds the device will wake up", "ValueIDKey": 562950588923923, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/132/value/844425565634579/,{ "Label": "Default Wake-up Interval", "Value": 28800, "Units": "Seconds", "Min": -2147483648, "Max": 2147483647, "Type": "Int", "Instance": 1, "CommandClass": "COMMAND_CLASS_WAKE_UP", "Index": 3, "Node": 37, "Genre": "System", "Help": "The Default Wake-Up Interval the device will wake up", "ValueIDKey": 844425565634579, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/132/value/1125900542345235/,{ "Label": "Wake-up Interval Step", "Value": 60, "Units": "Seconds", "Min": -2147483648, "Max": 2147483647, "Type": "Int", "Instance": 1, "CommandClass": "COMMAND_CLASS_WAKE_UP", "Index": 4, "Node": 37, "Genre": "System", "Help": "Step Size on Wake-up interval", "ValueIDKey": 1125900542345235, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/132/value/635502611/,{ "Label": "Wake-up Interval", "Value": 28800, "Units": "Seconds", "Min": -2147483648, "Max": 2147483647, "Type": "Int", "Instance": 1, "CommandClass": "COMMAND_CLASS_WAKE_UP", "Index": 0, "Node": 37, "Genre": "System", "Help": "How often the Device will Wake up to check for pending commands", "ValueIDKey": 635502611, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/134/,{ "Instance": 1, "CommandClassId": 134, "CommandClass": "COMMAND_CLASS_VERSION", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/134/value/635535383/,{ "Label": "Library Version", "Value": "3", "Units": "", "Min": 0, "Max": 0, "Type": "String", "Instance": 1, "CommandClass": "COMMAND_CLASS_VERSION", "Index": 0, "Node": 37, "Genre": "System", "Help": "Z-Wave Library Version", "ValueIDKey": 635535383, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/134/value/281475612246039/,{ "Label": "Protocol Version", "Value": "4.61", "Units": "", "Min": 0, "Max": 0, "Type": "String", "Instance": 1, "CommandClass": "COMMAND_CLASS_VERSION", "Index": 1, "Node": 37, "Genre": "System", "Help": "Z-Wave Protocol Version", "ValueIDKey": 281475612246039, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/134/value/562950588956695/,{ "Label": "Application Version", "Value": "2.15", "Units": "", "Min": 0, "Max": 0, "Type": "String", "Instance": 1, "CommandClass": "COMMAND_CLASS_VERSION", "Index": 2, "Node": 37, "Genre": "System", "Help": "Application Version", "ValueIDKey": 562950588956695, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/association/1/,{ "Name": "Lifeline", "Help": "", "MaxAssociations": 1, "Members": [ "1.0", "1.1" ], "TimeStamp": 1579566891} +OpenZWave/1/node/37/association/2/,{ "Name": "BasicSet report", "Help": "", "MaxAssociations": 5, "Members": [], "TimeStamp": 1579566891} +OpenZWave/1/node/37/association/3/,{ "Name": "Temperature Alarm report", "Help": "", "MaxAssociations": 5, "Members": [], "TimeStamp": 1579566891} +OpenZWave/1/node/39/,{ "NodeID": 39, "NodeQueryStage": "CacheLoad", "isListening": true, "isFlirs": false, "isBeaming": true, "isRouting": true, "isSecurityv1": false, "isZWavePlus": false, "isNIFRecieved": true, "isAwake": true, "isFailed": false, "MetaData": { "OZWInfoURL": "http://www.openzwave.com/device-database/0371:0002:0103", "ZWAProductURL": "", "ProductPic": "images/aeotec/zwa002.png", "Description": "✓ Standard form factor and appearance of the light bulb with 800 lm output ✓ RGBW: dimmable from 5% to 100%, tunable from 1800K to 6500K, and 16 million colors ✓ Possible to be included in groups, scenes, or schedules ✓ Suitable for indoor lighting: Corridors, Bedroom, Living Room, etc.", "ProductManualURL": "https://Products.Z-WaveAlliance.org/ProductManual/File?folder=&filename=Manuals/2881/AA LED Bulb 6 说明书(RGBW-AL001)_转曲-2dd.pdf", "ProductPageURL": "", "InclusionHelp": "Add for inclusion 1. Ensure the led bulb has been excluded outside the network. 2. Triggered by OFF ->ON (between 0.5-2 seconds each time) 3. LED solid yellow Color (0xFFFF00) during the pairing(Timeout is 10 seconds).  Failure: Blinks between 100% White and Red 0x0000FF color for 3 seconds (at a rate of 200ms per flash), Once 3 seconds have passed, the LED should return to a Warm White LED at 100%  Success: Blinks between 100% White and Green 0x00FF00 color for 3 seconds (at a rate of 200ms per flash). Once 3 seconds have passed, the LED should return to a Warm White LED at 100%.", "ExclusionHelp": "Remove for exclusion 1. Assuming led bulb was added to controller. 2. Triggered by OFF -> ON -> OFF -> ON -> OFF -> ON (between 0.5-2 seconds each time). 3. LED Solid Purple/Violet Color (0xEE82EE) during the unpairing process. (Timeout is 10 seconds).  Failure: Blinks between 100% White and Red 0x0000FF color for 3 seconds (at a rate of 200ms per flash), Once 3 seconds have passed, the LED should return to the last color ( memory status(color cc set)) of LED Bulb.  Success: Blinks between 100% White and Blue 0x0000FF color for 3 seconds (at a rate of 200ms per flash). Once 3 seconds have passed, the LED should return to a Warm White LED at 100%.", "ResetHelp": "Reset the Device. 1. Assuming led bulb was added to controller and was power on. 2. RGBW bulb re-power 6 times (between 0.5-2 seconds each time). Note: ON -> OFF -> ON -> OFF -> ON -> OFF -> ON -> OFF -> ON -> OFF -> ON -> OFF -> ON 3. If the 6th power on, the led bulb change to Yellow color(into pairing process ), which means that the reset factory settings are successf. Using this action in case of the primary controller is missing or inoperable.", "WakeupHelp": "", "ProductSupportURL": "", "Frequency": "", "Name": "LED Bulb 6:Multi-Colour", "ProductPicBase64": "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" }, "Event": "nodeNaming", "TimeStamp": 1579566891, "NodeManufacturerName": "Aeotec Limited", "NodeProductName": "ZWA002 LED Bulb 6 Multi-Color", "NodeBasicString": "Routing Slave", "NodeBasic": 4, "NodeGenericString": "Multilevel Switch", "NodeGeneric": 17, "NodeSpecificString": "Multilevel Power Switch", "NodeSpecific": 1, "NodeManufacturerID": "0x0371", "NodeProductType": "0x0103", "NodeProductID": "0x0002", "NodeBaudRate": 100000, "NodeVersion": 4, "NodeGroups": 1} +OpenZWave/1/node/39/instance/1/,{ "Instance": 1, "TimeStamp": 1579566891} +OpenZWave/1/node/39/instance/1/commandclass/38/,{ "Instance": 1, "CommandClassId": 38, "CommandClass": "COMMAND_CLASS_SWITCH_MULTILEVEL", "TimeStamp": 1579566891} +OpenZWave/1/node/39/instance/1/commandclass/38/value/1407375551070225/,{ "Label": "Dimming Duration", "Value": 255, "Units": "", "Min": 0, "Max": 255, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_SWITCH_MULTILEVEL", "Index": 5, "Node": 39, "Genre": "System", "Help": "Duration taken when changing the Level of a Device", "ValueIDKey": 1407375551070225, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/39/instance/1/commandclass/38/value/659128337/,{ "Label": "Level", "Value": 0, "Units": "", "Min": 0, "Max": 255, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_SWITCH_MULTILEVEL", "Index": 0, "Node": 39, "Genre": "User", "Help": "The Current Level of the Device", "ValueIDKey": 659128337, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/39/instance/1/commandclass/38/value/281475635839000/,{ "Label": "Bright", "Value": false, "Units": "", "Min": 0, "Max": 0, "Type": "Button", "Instance": 1, "CommandClass": "COMMAND_CLASS_SWITCH_MULTILEVEL", "Index": 1, "Node": 39, "Genre": "User", "Help": "Increase the Brightness of the Device", "ValueIDKey": 281475635839000, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/39/instance/1/commandclass/38/value/562950612549656/,{ "Label": "Dim", "Value": false, "Units": "", "Min": 0, "Max": 0, "Type": "Button", "Instance": 1, "CommandClass": "COMMAND_CLASS_SWITCH_MULTILEVEL", "Index": 2, "Node": 39, "Genre": "User", "Help": "Decrease the Brightness of the Device", "ValueIDKey": 562950612549656, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/39/instance/1/commandclass/38/value/844425597648912/,{ "Label": "Ignore Start Level", "Value": true, "Units": "", "Min": 0, "Max": 0, "Type": "Bool", "Instance": 1, "CommandClass": "COMMAND_CLASS_SWITCH_MULTILEVEL", "Index": 3, "Node": 39, "Genre": "System", "Help": "Ignore the Start Level of the Device when increasing/decreasing brightness", "ValueIDKey": 844425597648912, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/39/instance/1/commandclass/38/value/1125900574359569/,{ "Label": "Start Level", "Value": 0, "Units": "", "Min": 0, "Max": 255, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_SWITCH_MULTILEVEL", "Index": 4, "Node": 39, "Genre": "System", "Help": "Start Level when Changing the Brightness of a Device", "ValueIDKey": 1125900574359569, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/39/instance/1/commandclass/39/,{ "Instance": 1, "CommandClassId": 39, "CommandClass": "COMMAND_CLASS_SWITCH_ALL", "TimeStamp": 1579566891} +OpenZWave/1/node/39/instance/1/commandclass/39/value/667533332/,{ "Label": "Switch All", "Value": { "List": [ { "Value": 0, "Label": "Disabled" }, { "Value": 1, "Label": "Off Enabled" }, { "Value": 2, "Label": "On Enabled" }, { "Value": 255, "Label": "On and Off Enabled" } ], "Selected": "On and Off Enabled" }, "Units": "", "Min": 0, "Max": 0, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_SWITCH_ALL", "Index": 0, "Node": 39, "Genre": "System", "Help": "Switch All Devices On/Off", "ValueIDKey": 667533332, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/39/instance/1/commandclass/51/,{ "Instance": 1, "CommandClassId": 51, "CommandClass": "COMMAND_CLASS_COLOR", "TimeStamp": 1579566891} +OpenZWave/1/node/39/instance/1/commandclass/51/value/562950621151251/,{ "Label": "Color Channels", "Value": 31, "Units": "", "Min": -2147483648, "Max": 2147483647, "Type": "Int", "Instance": 1, "CommandClass": "COMMAND_CLASS_COLOR", "Index": 2, "Node": 39, "Genre": "System", "Help": "Color Capabilities of the device", "ValueIDKey": 562950621151251, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/39/instance/1/commandclass/51/value/659341335/,{ "Label": "Color", "Value": "#000000FF00", "Units": "#RRGGBBWWCW", "Min": 0, "Max": 0, "Type": "String", "Instance": 1, "CommandClass": "COMMAND_CLASS_COLOR", "Index": 0, "Node": 39, "Genre": "User", "Help": "Color (in RGB format)", "ValueIDKey": 659341335, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/39/instance/1/commandclass/51/value/281475636051988/,{ "Label": "Color Index", "Value": { "List": [ { "Value": 0, "Label": "Off" }, { "Value": 1, "Label": "Cool White" }, { "Value": 2, "Label": "Warm White" }, { "Value": 3, "Label": "Red" }, { "Value": 4, "Label": "Lime" }, { "Value": 5, "Label": "Blue" }, { "Value": 6, "Label": "Yellow" }, { "Value": 7, "Label": "Cyan" }, { "Value": 8, "Label": "Magenta" }, { "Value": 9, "Label": "Silver" }, { "Value": 10, "Label": "Gray" }, { "Value": 11, "Label": "Maroon" }, { "Value": 12, "Label": "Olive" }, { "Value": 13, "Label": "Green" }, { "Value": 14, "Label": "Purple" }, { "Value": 15, "Label": "Teal" }, { "Value": 16, "Label": "Navy" }, { "Value": 17, "Label": "Custom" } ], "Selected": "Warm White" }, "Units": "", "Min": 0, "Max": 0, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_COLOR", "Index": 1, "Node": 39, "Genre": "User", "Help": "Preset Color", "ValueIDKey": 281475636051988, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/39/instance/1/commandclass/94/,{ "Instance": 1, "CommandClassId": 94, "CommandClass": "COMMAND_CLASS_ZWAVEPLUS_INFO", "TimeStamp": 1579566891} +OpenZWave/1/node/39/instance/1/commandclass/94/value/668434449/,{ "Label": "ZWave+ Version", "Value": 1, "Units": "", "Min": 0, "Max": 255, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_ZWAVEPLUS_INFO", "Index": 0, "Node": 39, "Genre": "System", "Help": "ZWave+ Version Supported on the Device", "ValueIDKey": 668434449, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/39/instance/1/commandclass/94/value/281475645145110/,{ "Label": "InstallerIcon", "Value": 1536, "Units": "", "Min": -32768, "Max": 32767, "Type": "Short", "Instance": 1, "CommandClass": "COMMAND_CLASS_ZWAVEPLUS_INFO", "Index": 1, "Node": 39, "Genre": "System", "Help": "Icon File to use for the Installer Application", "ValueIDKey": 281475645145110, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/39/instance/1/commandclass/94/value/562950621855766/,{ "Label": "UserIcon", "Value": 1536, "Units": "", "Min": -32768, "Max": 32767, "Type": "Short", "Instance": 1, "CommandClass": "COMMAND_CLASS_ZWAVEPLUS_INFO", "Index": 2, "Node": 39, "Genre": "System", "Help": "Icon File to use for the User Application", "ValueIDKey": 562950621855766, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/39/instance/1/commandclass/112/,{ "Instance": 1, "CommandClassId": 112, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "TimeStamp": 1579566891} +OpenZWave/1/node/39/instance/1/commandclass/112/value/281475641245716/,{ "Label": "User custom mode LED animations", "Value": { "List": [ { "Value": 0, "Label": "Disable" }, { "Value": 1, "Label": "Blink Colors in order mode" }, { "Value": 2, "Label": "Randomized blink color mode" } ], "Selected": "Disable" }, "Units": "", "Min": 0, "Max": 2, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 1, "Node": 39, "Genre": "Config", "Help": "User custom mode for LED animations", "ValueIDKey": 281475641245716, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/39/instance/1/commandclass/112/value/562950617956372/,{ "Label": "Strobe over Custom Color", "Value": { "List": [ { "Value": 0, "Label": "Disable" }, { "Value": 1, "Label": "Enable" } ], "Selected": "Disable" }, "Units": "", "Min": 0, "Max": 1, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 2, "Node": 39, "Genre": "Config", "Help": "Enable/Disable Strobe over Custom Color.", "ValueIDKey": 562950617956372, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/39/instance/1/commandclass/112/value/844425594667027/,{ "Label": "Set the rate of change to next color in Custom Mode", "Value": 50, "Units": "ms", "Min": 5, "Max": 8640000, "Type": "Int", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 3, "Node": 39, "Genre": "Config", "Help": "Set the rate of change to next color in Custom Mode.", "ValueIDKey": 844425594667027, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/39/instance/1/commandclass/112/value/1125900571377681/,{ "Label": "Set color that LED Bulb blinks", "Value": 1, "Units": "", "Min": 1, "Max": 255, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 4, "Node": 39, "Genre": "Config", "Help": "Set color that LED Bulb blinks in Blink Mode.", "ValueIDKey": 1125900571377681, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/39/instance/1/commandclass/112/value/4503600291905553/,{ "Label": "Ramp rate when dimming using Multilevel Switch", "Value": 20, "Units": "100ms", "Min": 0, "Max": 100, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 16, "Node": 39, "Genre": "Config", "Help": "Specifying the ramp rate when dimming using Multilevel Switch V1 CC in 100ms.", "ValueIDKey": 4503600291905553, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/39/instance/1/commandclass/112/value/22517998801387540/,{ "Label": "Notification", "Value": { "List": [ { "Value": 0, "Label": "Nothing" }, { "Value": 1, "Label": "Basic CC report" } ], "Selected": "Basic CC report" }, "Units": "", "Min": 0, "Max": 1, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 80, "Node": 39, "Genre": "Config", "Help": "Enable to send notifications to associated devices (Group 1) when the state of LED Bulb is changed.", "ValueIDKey": 22517998801387540, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/39/instance/1/commandclass/112/value/22799473778098198/,{ "Label": "Warm White temperature", "Value": 2700, "Units": "k", "Min": 2700, "Max": 4999, "Type": "Short", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 81, "Node": 39, "Genre": "Config", "Help": "Adjusting the color temperature in warm white color component. available value: 2700k to 4999k", "ValueIDKey": 22799473778098198, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/39/instance/1/commandclass/112/value/23080948754808854/,{ "Label": "cold white temperature", "Value": 6500, "Units": "k", "Min": 5000, "Max": 6500, "Type": "Short", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 82, "Node": 39, "Genre": "Config", "Help": "Adjusting the color temperature in cold white color component. available value:5000k to 6500k", "ValueIDKey": 23080948754808854, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/39/instance/1/commandclass/114/,{ "Instance": 1, "CommandClassId": 114, "CommandClass": "COMMAND_CLASS_MANUFACTURER_SPECIFIC", "TimeStamp": 1579566891} +OpenZWave/1/node/39/instance/1/commandclass/114/value/668762131/,{ "Label": "Loaded Config Revision", "Value": 3, "Units": "", "Min": -2147483648, "Max": 2147483647, "Type": "Int", "Instance": 1, "CommandClass": "COMMAND_CLASS_MANUFACTURER_SPECIFIC", "Index": 0, "Node": 39, "Genre": "System", "Help": "Revision of the Config file currently loaded", "ValueIDKey": 668762131, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/39/instance/1/commandclass/114/value/281475645472787/,{ "Label": "Config File Revision", "Value": 3, "Units": "", "Min": -2147483648, "Max": 2147483647, "Type": "Int", "Instance": 1, "CommandClass": "COMMAND_CLASS_MANUFACTURER_SPECIFIC", "Index": 1, "Node": 39, "Genre": "System", "Help": "Revision of the Config file on the File System", "ValueIDKey": 281475645472787, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/39/instance/1/commandclass/114/value/562950622183443/,{ "Label": "Latest Available Config File Revision", "Value": 3, "Units": "", "Min": -2147483648, "Max": 2147483647, "Type": "Int", "Instance": 1, "CommandClass": "COMMAND_CLASS_MANUFACTURER_SPECIFIC", "Index": 2, "Node": 39, "Genre": "System", "Help": "Latest Revision of the Config file available for download", "ValueIDKey": 562950622183443, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/39/instance/1/commandclass/114/value/844425598894103/,{ "Label": "Device ID", "Value": "", "Units": "", "Min": 0, "Max": 0, "Type": "String", "Instance": 1, "CommandClass": "COMMAND_CLASS_MANUFACTURER_SPECIFIC", "Index": 3, "Node": 39, "Genre": "System", "Help": "Manufacturer Specific Device ID/Model", "ValueIDKey": 844425598894103, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/39/instance/1/commandclass/114/value/1125900575604759/,{ "Label": "Serial Number", "Value": "00001cd6bda18c83", "Units": "", "Min": 0, "Max": 0, "Type": "String", "Instance": 1, "CommandClass": "COMMAND_CLASS_MANUFACTURER_SPECIFIC", "Index": 4, "Node": 39, "Genre": "System", "Help": "Device Serial Number", "ValueIDKey": 1125900575604759, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/39/instance/1/commandclass/115/,{ "Instance": 1, "CommandClassId": 115, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "TimeStamp": 1579566891} +OpenZWave/1/node/39/instance/1/commandclass/115/value/668778516/,{ "Label": "Powerlevel", "Value": { "List": [ { "Value": 0, "Label": "Normal" }, { "Value": 1, "Label": "-1dB" }, { "Value": 2, "Label": "-2dB" }, { "Value": 3, "Label": "-3dB" }, { "Value": 4, "Label": "-4dB" }, { "Value": 5, "Label": "-5dB" }, { "Value": 6, "Label": "-6dB" }, { "Value": 7, "Label": "-7dB" }, { "Value": 8, "Label": "-8dB" }, { "Value": 9, "Label": "-9dB" } ], "Selected": "Normal" }, "Units": "dB", "Min": 0, "Max": 0, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 0, "Node": 39, "Genre": "System", "Help": "Output RF PowerLevel", "ValueIDKey": 668778516, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/39/instance/1/commandclass/115/value/281475645489169/,{ "Label": "Timeout", "Value": 0, "Units": "seconds", "Min": 0, "Max": 255, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 1, "Node": 39, "Genre": "System", "Help": "Timeout till the PowerLevel is reset to Normal", "ValueIDKey": 281475645489169, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/39/instance/1/commandclass/115/value/562950622199832/,{ "Label": "Set Powerlevel", "Value": false, "Units": "", "Min": 0, "Max": 0, "Type": "Button", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 2, "Node": 39, "Genre": "System", "Help": "Apply the Output PowerLevel and Timeout Values", "ValueIDKey": 562950622199832, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/39/instance/1/commandclass/115/value/844425598910481/,{ "Label": "Test Node", "Value": 0, "Units": "", "Min": 0, "Max": 255, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 3, "Node": 39, "Genre": "System", "Help": "Node to Perform a test against", "ValueIDKey": 844425598910481, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/39/instance/1/commandclass/115/value/1125900575621140/,{ "Label": "Test Powerlevel", "Value": { "List": [ { "Value": 0, "Label": "Normal" }, { "Value": 1, "Label": "-1dB" }, { "Value": 2, "Label": "-2dB" }, { "Value": 3, "Label": "-3dB" }, { "Value": 4, "Label": "-4dB" }, { "Value": 5, "Label": "-5dB" }, { "Value": 6, "Label": "-6dB" }, { "Value": 7, "Label": "-7dB" }, { "Value": 8, "Label": "-8dB" }, { "Value": 9, "Label": "-9dB" } ], "Selected": "Normal" }, "Units": "dB", "Min": 0, "Max": 0, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 4, "Node": 39, "Genre": "System", "Help": "PowerLevel to use for the Test", "ValueIDKey": 1125900575621140, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/39/instance/1/commandclass/115/value/1407375552331798/,{ "Label": "Frame Count", "Value": 0, "Units": "", "Min": -32768, "Max": 32767, "Type": "Short", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 5, "Node": 39, "Genre": "System", "Help": "How Many Messages to send to the Note for the Test", "ValueIDKey": 1407375552331798, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/39/instance/1/commandclass/115/value/1688850529042456/,{ "Label": "Test", "Value": false, "Units": "", "Min": 0, "Max": 0, "Type": "Button", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 6, "Node": 39, "Genre": "System", "Help": "Perform a PowerLevel Test against the a Node", "ValueIDKey": 1688850529042456, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/39/instance/1/commandclass/115/value/1970325505753112/,{ "Label": "Report", "Value": false, "Units": "", "Min": 0, "Max": 0, "Type": "Button", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 7, "Node": 39, "Genre": "System", "Help": "Get the results of the latest PowerLevel Test against a Node", "ValueIDKey": 1970325505753112, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/39/instance/1/commandclass/115/value/2251800482463764/,{ "Label": "Test Status", "Value": { "List": [ { "Value": 0, "Label": "Failed" }, { "Value": 1, "Label": "Success" }, { "Value": 2, "Label": "In Progress" } ], "Selected": "Failed" }, "Units": "", "Min": 0, "Max": 0, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 8, "Node": 39, "Genre": "System", "Help": "The Current Status of the last PowerNode Test Executed", "ValueIDKey": 2251800482463764, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/39/instance/1/commandclass/115/value/2533275459174422/,{ "Label": "Acked Frames", "Value": 0, "Units": "", "Min": -32768, "Max": 32767, "Type": "Short", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 9, "Node": 39, "Genre": "System", "Help": "Number of Messages successfully Acked by the Target Node", "ValueIDKey": 2533275459174422, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/39/instance/1/commandclass/134/,{ "Instance": 1, "CommandClassId": 134, "CommandClass": "COMMAND_CLASS_VERSION", "TimeStamp": 1579566891} +OpenZWave/1/node/39/instance/1/commandclass/134/value/669089815/,{ "Label": "Library Version", "Value": "3", "Units": "", "Min": 0, "Max": 0, "Type": "String", "Instance": 1, "CommandClass": "COMMAND_CLASS_VERSION", "Index": 0, "Node": 39, "Genre": "System", "Help": "Z-Wave Library Version", "ValueIDKey": 669089815, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/39/instance/1/commandclass/134/value/281475645800471/,{ "Label": "Protocol Version", "Value": "4.38", "Units": "", "Min": 0, "Max": 0, "Type": "String", "Instance": 1, "CommandClass": "COMMAND_CLASS_VERSION", "Index": 1, "Node": 39, "Genre": "System", "Help": "Z-Wave Protocol Version", "ValueIDKey": 281475645800471, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/39/instance/1/commandclass/134/value/562950622511127/,{ "Label": "Application Version", "Value": "2.00", "Units": "", "Min": 0, "Max": 0, "Type": "String", "Instance": 1, "CommandClass": "COMMAND_CLASS_VERSION", "Index": 2, "Node": 39, "Genre": "System", "Help": "Application Version", "ValueIDKey": 562950622511127, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/39/association/1/,{ "Name": "Lifeline", "Help": "", "MaxAssociations": 1, "Members": [ "1.0" ], "TimeStamp": 1579566891} +OpenZWave/1/node/39/instance/1/commandclass/43/,{ "Instance": 1, "CommandClassId": 43, "CommandClass": "COMMAND_CLASS_SCENE_ACTIVATION", "TimeStamp": 1579566891} +OpenZWave/1/node/39/instance/1/commandclass/43/value/562950622511127/,{ "Label": "Scene", "Value": 0, "Units": "", "Min": -2147483648, "Max": 2147483647, "Type": "Int", "Instance": 1, "CommandClass": "COMMAND_CLASS_SCENE_ACTIVATION", "Index": 0, "Node": 7, "Genre": "User", "Help": "", "ValueIDKey": 122339347, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueChanged", "TimeStamp": 1579630367} +OpenZWave/1/node/39/instance/1/commandclass/91/,{ "Instance": 1, "CommandClassId": 91, "CommandClass": "COMMAND_CLASS_CENTRAL_SCENE", "TimeStamp": 1579630630} +OpenZWave/1/node/39/instance/1/commandclass/91/value/281476005806100/,{ "Label": "Scene 1", "Value": { "List": [ { "Value": 0, "Label": "Inactive" }, { "Value": 1, "Label": "Pressed 1 Time" }, { "Value": 2, "Label": "Key Released" }, { "Value": 3, "Label": "Key Held down" } ], "Selected": "Inactive", "Selected_id": 0 }, "Units": "", "Min": 0, "Max": 0, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CENTRAL_SCENE", "Index": 1, "Node": 61, "Genre": "User", "Help": "", "ValueIDKey": 281476005806100, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueChanged", "TimeStamp": 1579640710} \ No newline at end of file diff --git a/tests/components/zwave_mqtt/test_init.py b/tests/components/zwave_mqtt/test_init.py new file mode 100644 index 0000000000000..1982a6236c1eb --- /dev/null +++ b/tests/components/zwave_mqtt/test_init.py @@ -0,0 +1,23 @@ +"""Test integration initialization.""" +from homeassistant.components.zwave_mqtt import DOMAIN, PLATFORMS, const + +from .common import setup_zwave + + +async def test_init_entry(hass): + """Test setting up config entry.""" + await setup_zwave(hass, "generic_network_dump.csv") + + # Verify integration + platform loaded. + assert "zwave_mqtt" in hass.config.components + for platform in PLATFORMS: + assert platform in hass.config.components, platform + assert f"{platform}.{DOMAIN}" in hass.config.components, f"{platform}.{DOMAIN}" + + # Verify services registered + assert hass.services.has_service(DOMAIN, const.SERVICE_ADD_NODE) + assert hass.services.has_service(DOMAIN, const.SERVICE_REMOVE_NODE) + assert hass.services.has_service(DOMAIN, const.SERVICE_REMOVE_FAILED_NODE) + assert hass.services.has_service(DOMAIN, const.SERVICE_REPLACE_FAILED_NODE) + assert hass.services.has_service(DOMAIN, const.SERVICE_CANCEL_COMMAND) + assert hass.services.has_service(DOMAIN, const.SERVICE_SET_CONFIG_PARAMETER) diff --git a/tests/components/zwave_mqtt/test_light.py b/tests/components/zwave_mqtt/test_light.py new file mode 100644 index 0000000000000..71a77918b82d3 --- /dev/null +++ b/tests/components/zwave_mqtt/test_light.py @@ -0,0 +1,45 @@ +"""Test Z-Wave Lights.""" +from homeassistant.components.zwave_mqtt.light import byte_to_zwave_brightness + +from .common import setup_zwave + + +async def test_light(hass, sent_messages): + """Test setting up config entry.""" + await setup_zwave(hass, "generic_network_dump.csv") + + # Test loaded + state = hass.states.get("light.led_bulb_6_multi_colour_level") + assert state is not None + assert state.state == "off" + + # Test turning on + new_brightness = 45 + await hass.services.async_call( + "light", + "turn_on", + { + "entity_id": "light.led_bulb_6_multi_colour_level", + "brightness": new_brightness, + }, + blocking=True, + ) + assert len(sent_messages) == 1 + msg = sent_messages[0] + assert msg["topic"] == "OpenZWave/1/command/setvalue/" + assert msg["payload"] == { + "Value": byte_to_zwave_brightness(new_brightness), + "ValueIDKey": 659128337, + } + + # Test turning off + await hass.services.async_call( + "light", + "turn_off", + {"entity_id": "light.led_bulb_6_multi_colour_level"}, + blocking=True, + ) + assert len(sent_messages) == 2 + msg = sent_messages[1] + assert msg["topic"] == "OpenZWave/1/command/setvalue/" + assert msg["payload"] == {"Value": 0, "ValueIDKey": 659128337} diff --git a/tests/components/zwave_mqtt/test_scenes.py b/tests/components/zwave_mqtt/test_scenes.py new file mode 100644 index 0000000000000..156b8fb973fd3 --- /dev/null +++ b/tests/components/zwave_mqtt/test_scenes.py @@ -0,0 +1,100 @@ +"""Test Z-Wave (central) Scenes.""" +import asyncio +import json +from unittest.mock import Mock + +from .common import async_capture_events, setup_zwave + + +async def test_scenes(hass, sent_messages): + """Test setting up config entry.""" + + receive_message = await setup_zwave(hass, "generic_network_dump.csv") + events = async_capture_events(hass, "zwave_mqtt.scene_activated") + + # Publish fake scene event on mqtt + receive_message( + Mock( + topic="OpenZWave/1/node/39/instance/1/commandclass/43/value/562950622511127/", + payload=json.dumps( + { + "Label": "Scene", + "Value": 16, + "Units": "", + "Min": -2147483648, + "Max": 2147483647, + "Type": "Int", + "Instance": 1, + "CommandClass": "COMMAND_CLASS_SCENE_ACTIVATION", + "Index": 0, + "Node": 7, + "Genre": "User", + "Help": "", + "ValueIDKey": 122339347, + "ReadOnly": False, + "WriteOnly": False, + "ValueSet": False, + "ValuePolled": False, + "ChangeVerified": False, + "Event": "valueChanged", + "TimeStamp": 1579630367, + } + ), + ) + ) + # wait for the event + count = 0 + while count < 5 and not events: + await asyncio.sleep(0.5) + count += 1 + assert len(events) == 1 + assert events[0].data["scene_value_id"] == 16 + + # Publish fake central scene event on mqtt + receive_message( + Mock( + topic="OpenZWave/1/node/39/instance/1/commandclass/91/value/281476005806100/", + payload=json.dumps( + { + "Label": "Scene 1", + "Value": { + "List": [ + {"Value": 0, "Label": "Inactive"}, + {"Value": 1, "Label": "Pressed 1 Time"}, + {"Value": 2, "Label": "Key Released"}, + {"Value": 3, "Label": "Key Held down"}, + ], + "Selected": "Pressed 1 Time", + "Selected_id": 1, + }, + "Units": "", + "Min": 0, + "Max": 0, + "Type": "List", + "Instance": 1, + "CommandClass": "COMMAND_CLASS_CENTRAL_SCENE", + "Index": 1, + "Node": 61, + "Genre": "User", + "Help": "", + "ValueIDKey": 281476005806100, + "ReadOnly": False, + "WriteOnly": False, + "ValueSet": False, + "ValuePolled": False, + "ChangeVerified": False, + "Event": "valueChanged", + "TimeStamp": 1579640710, + } + ), + ) + ) + # wait for the event + count = 0 + while count < 5 and len(events) != 2: + await asyncio.sleep(0.5) + count += 1 + assert len(events) == 2 + assert events[1].data["scene_id"] == 1 + assert events[1].data["scene_label"] == "Scene 1" + assert events[1].data["scene_value_label"] == "Pressed 1 Time" diff --git a/tests/components/zwave_mqtt/test_sensor.py b/tests/components/zwave_mqtt/test_sensor.py new file mode 100644 index 0000000000000..64630723f3bb9 --- /dev/null +++ b/tests/components/zwave_mqtt/test_sensor.py @@ -0,0 +1,18 @@ +"""Test Z-Wave Sensors.""" +from .common import setup_zwave + + +async def test_sensor(hass, sent_messages): + """Test setting up config entry.""" + await setup_zwave(hass, "generic_network_dump.csv") + + # Test standard sensor + state = hass.states.get("sensor.smart_plug_electric_v") + assert state is not None + assert state.state == "123.9" + assert state.attributes["unit_of_measurement"] == "V" + + # Test list sensor converted to binary sensor + state = hass.states.get("binary_sensor.trisensor_motion_detected") + assert state is not None + assert state.state == "off" diff --git a/tests/components/zwave_mqtt/test_switch.py b/tests/components/zwave_mqtt/test_switch.py new file mode 100644 index 0000000000000..01231a2fd9d88 --- /dev/null +++ b/tests/components/zwave_mqtt/test_switch.py @@ -0,0 +1,30 @@ +"""Test Z-Wave Switches.""" +from .common import setup_zwave + + +async def test_switch(hass, sent_messages): + """Test setting up config entry.""" + await setup_zwave(hass, "generic_network_dump.csv") + + # Test loaded + state = hass.states.get("switch.smart_plug_switch") + assert state is not None + assert state.state == "off" + + # Test turning on + await hass.services.async_call( + "switch", "turn_on", {"entity_id": "switch.smart_plug_switch"}, blocking=True + ) + assert len(sent_messages) == 1 + msg = sent_messages[0] + assert msg["topic"] == "OpenZWave/1/command/setvalue/" + assert msg["payload"] == {"Value": True, "ValueIDKey": 541671440} + + # Test turning off + await hass.services.async_call( + "switch", "turn_off", {"entity_id": "switch.smart_plug_switch"}, blocking=True + ) + assert len(sent_messages) == 2 + msg = sent_messages[1] + assert msg["topic"] == "OpenZWave/1/command/setvalue/" + assert msg["payload"] == {"Value": False, "ValueIDKey": 541671440} From 7d94a3c31dd4abbfe17deafb7821a3b6b58c8fab Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Mon, 27 Apr 2020 04:12:21 +0200 Subject: [PATCH 05/51] Sleep 0 seconds in tests --- tests/components/zwave_mqtt/test_scenes.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/components/zwave_mqtt/test_scenes.py b/tests/components/zwave_mqtt/test_scenes.py index 156b8fb973fd3..3f86008e089ff 100644 --- a/tests/components/zwave_mqtt/test_scenes.py +++ b/tests/components/zwave_mqtt/test_scenes.py @@ -45,7 +45,7 @@ async def test_scenes(hass, sent_messages): # wait for the event count = 0 while count < 5 and not events: - await asyncio.sleep(0.5) + await asyncio.sleep(0.0) count += 1 assert len(events) == 1 assert events[0].data["scene_value_id"] == 16 @@ -92,7 +92,7 @@ async def test_scenes(hass, sent_messages): # wait for the event count = 0 while count < 5 and len(events) != 2: - await asyncio.sleep(0.5) + await asyncio.sleep(0.0) count += 1 assert len(events) == 2 assert events[1].data["scene_id"] == 1 From d286ff7a4e988b0367f4d5740e3253a7fef5ab00 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Tue, 28 Apr 2020 16:41:54 +0200 Subject: [PATCH 06/51] Fix set config parameter return value --- homeassistant/components/zwave_mqtt/services.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/zwave_mqtt/services.py b/homeassistant/components/zwave_mqtt/services.py index c257188dc4c4e..54b823847b87d 100644 --- a/homeassistant/components/zwave_mqtt/services.py +++ b/homeassistant/components/zwave_mqtt/services.py @@ -149,10 +149,12 @@ def set_config_parameter(self, service): ) # Bool value if value.type == ValueType.BOOL: - return value.send_value(int(selection == "True")) + value.send_value(int(selection == "True")) + return # List value if value.type == ValueType.LIST: - return value.send_value(str(selection)) + value.send_value(str(selection)) + return # Button if value.type == ValueType.BUTTON: value.send_value(True) From 8a0e3834d265784bd48c7780ded951309ee1a655 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Tue, 28 Apr 2020 16:45:48 +0200 Subject: [PATCH 07/51] Remove not used service descriptions --- .../components/zwave_mqtt/services.yaml | 160 ------------------ 1 file changed, 160 deletions(-) diff --git a/homeassistant/components/zwave_mqtt/services.yaml b/homeassistant/components/zwave_mqtt/services.yaml index 0ec754974eded..35b59d879fc45 100755 --- a/homeassistant/components/zwave_mqtt/services.yaml +++ b/homeassistant/components/zwave_mqtt/services.yaml @@ -1,22 +1,4 @@ # Describes the format for available Z-Wave services - -change_association: - description: Change an association in the Z-Wave network. - fields: - association: - description: Specify add or remove association - example: add - node_id: - description: Node id of the node to set association for. - example: 10 - target_node_id: - description: Node id of the node to associate to. - example: 42 - group: - description: Group number to set association for. - instance: - description: (Optional) Instance of multichannel association. Defaults to 0. - add_node: description: Add a new node to the Z-Wave network. fields: @@ -31,22 +13,6 @@ cancel_command: instance_id: description: (Optional) The OZW Instance/Controller to use, defaults to 1. -heal_network: - description: Start a Z-Wave network heal. This might take a while and will slow down the Z-Wave network greatly while it is being processed. Refer to OZW_Log.txt for progress. - fields: - return_routes: - description: Whether or not to update the return routes from the nodes to the controller. Defaults to False. - example: true - instance_id: - description: (Optional) The OZW Instance/Controller to use, defaults to 1. - -heal_node: - description: Start a Z-Wave node heal. Refer to OZW_Log.txt for progress. - fields: - return_routes: - description: Whether or not to update the return routes from the node to the controller. Defaults to False. - example: true - remove_node: description: Remove a node from the Z-Wave network. Will set the controller into exclusion mode. fields: @@ -80,129 +46,3 @@ set_config_parameter: description: Parameter index to set (integer). value: description: Value to set for parameter. (String value for list and bool parameters, integer for others). - -set_node_value: - description: Set the value for a given value_id on a Z-Wave device. - fields: - node_id: - description: Node id of the device to set the value on (integer). - value_id: - description: Value id of the value to set (integer). - value: - description: Value to set (integer). - -refresh_node_value: - description: Refresh the value for a given value_id on a Z-Wave device. - fields: - node_id: - description: Node id of the device to refresh value from (integer). - value_id: - description: Value id of the value to refresh. - -set_poll_intensity: - description: Set the polling interval to a nodes value - fields: - node_id: - description: ID of the node to set polling to. - example: 10 - value_id: - description: ID of the value to set polling to. - example: 72037594255792737 - poll_intensity: - description: The intensity to poll, 0 = disabled, 1 = Every time through list, 2 = Every second time through list... - example: 2 - -print_config_parameter: - description: Prints a Z-Wave node config parameter value to log. - fields: - node_id: - description: Node id of the device to print the parameter from (integer). - parameter: - description: Parameter number to print (integer). - -print_node: - description: Print all information about z-wave node. - fields: - node_id: - description: Node id of the device to print. - -refresh_entity: - description: Refresh zwave entity. - fields: - entity_id: - description: Name of the entity to refresh. - example: "light.leviton_vrmx11lz_multilevel_scene_switch_level_40" - -refresh_node: - description: Refresh zwave node. - fields: - node_id: - description: ID of the node to refresh. - example: 10 - -set_wakeup: - description: Sets wake-up interval of a node. - fields: - node_id: - description: Node id of the device to set the wake-up interval for. (integer) - value: - description: Value of the interval to set. (integer) - -start_network: - description: Start the Z-Wave network. This might take a while, depending on how big your Z-Wave network is. - -stop_network: - description: Stop the Z-Wave network, all updates into Home Assistant will stop. - -soft_reset: - description: This will reset the controller without removing its data. Use carefully because not all controllers support this. Refer to your controller's manual. - -test_network: - description: This will send test to nodes in the Z-Wave network. This will greatly slow down the Z-Wave network while it is being processed. Refer to OZW_Log.txt for progress. - -test_node: - description: This will send test messages to a node in the Z-Wave network. This could bring back dead nodes. - fields: - node_id: - description: ID of the node to send test messages to. - example: 10 - messages: - description: Optional. Amount of test messages to send. - example: 3 - -rename_node: - description: Set the name of a node. This will also affect the IDs of all entities in the node. - fields: - node_id: - description: ID of the node to rename. - example: 10 - update_ids: - description: (optional) Rename the entity IDs for entities of this node. - example: true - name: - description: New Name - example: "kitchen" - -rename_value: - description: Set the name of a node value. This will affect the ID of the value entity. Value IDs can be queried from /api/zwave/values/{node_id} - fields: - node_id: - description: ID of the node to rename. - example: 10 - value_id: - description: ID of the value to rename. - example: 72037594255792737 - update_ids: - description: (optional) Update the entity ID for this value's entity. - example: true - name: - description: New Name - example: "Luminosity" - -reset_node_meters: - description: Resets the meter counters of a node. - fields: - node_id: - description: Node id of the device to reset meters for. (integer) - instance: - description: (Optional) Instance of association. Defaults to instance 1. From 1884d33fc13921c0ca03dc5f7ba6c194816a99ad Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Tue, 28 Apr 2020 17:25:56 +0200 Subject: [PATCH 08/51] Improve config flow and add test --- .../components/zwave_mqtt/config_flow.py | 9 +++--- .../components/zwave_mqtt/manifest.json | 28 ++++++++--------- .../components/zwave_mqtt/strings.json | 13 +------- .../zwave_mqtt/translations/en.json | 29 ++++++----------- .../components/zwave_mqtt/test_config_flow.py | 31 +++++++++++++++++++ 5 files changed, 59 insertions(+), 51 deletions(-) create mode 100644 tests/components/zwave_mqtt/test_config_flow.py diff --git a/homeassistant/components/zwave_mqtt/config_flow.py b/homeassistant/components/zwave_mqtt/config_flow.py index 0f698225efd1a..8297a9925dfcc 100644 --- a/homeassistant/components/zwave_mqtt/config_flow.py +++ b/homeassistant/components/zwave_mqtt/config_flow.py @@ -1,12 +1,8 @@ """Config flow for zwave_mqtt integration.""" -import logging - from homeassistant import config_entries from .const import DOMAIN # pylint:disable=unused-import -_LOGGER = logging.getLogger(__name__) - TITLE = "Z-Wave MQTT" @@ -18,4 +14,7 @@ class DomainConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user(self, user_input=None): """Handle the initial step.""" - return self.async_create_entry(title=TITLE, data={}) + if user_input is not None: + return self.async_create_entry(title=TITLE, data={}) + + return self.async_show_form(step_id="user") diff --git a/homeassistant/components/zwave_mqtt/manifest.json b/homeassistant/components/zwave_mqtt/manifest.json index 08af09223cd34..4c6fe47571b6a 100644 --- a/homeassistant/components/zwave_mqtt/manifest.json +++ b/homeassistant/components/zwave_mqtt/manifest.json @@ -1,16 +1,16 @@ { - "domain": "zwave_mqtt", - "name": "Z-Wave over MQTT", - "config_flow": true, - "documentation": "https://www.home-assistant.io/integrations/zwave_mqtt", - "requirements": [ - "python-openzwave-mqtt==0.0.8" - ], - "dependencies": [ - "mqtt" - ], - "codeowners": [ - "@cgarwood", - "@MartinHjelmare" - ] + "domain": "zwave_mqtt", + "name": "Z-Wave over MQTT", + "config_flow": true, + "documentation": "https://www.home-assistant.io/integrations/zwave_mqtt", + "requirements": [ + "python-openzwave-mqtt==0.0.8" + ], + "dependencies": [ + "mqtt" + ], + "codeowners": [ + "@cgarwood", + "@MartinHjelmare" + ] } diff --git a/homeassistant/components/zwave_mqtt/strings.json b/homeassistant/components/zwave_mqtt/strings.json index af42389617f55..ed8961f608f89 100644 --- a/homeassistant/components/zwave_mqtt/strings.json +++ b/homeassistant/components/zwave_mqtt/strings.json @@ -3,19 +3,8 @@ "config": { "step": { "user": { - "title": "Connect to the device", - "data": { - "host": "Host" - } + "title": "Confirm set up" } - }, - "error": { - "cannot_connect": "Failed to connect, please try again", - "invalid_auth": "Invalid authentication", - "unknown": "Unexpected error" - }, - "abort": { - "already_configured": "Device is already configured" } } } diff --git a/homeassistant/components/zwave_mqtt/translations/en.json b/homeassistant/components/zwave_mqtt/translations/en.json index a8f215a75b732..14ef45e42e51c 100644 --- a/homeassistant/components/zwave_mqtt/translations/en.json +++ b/homeassistant/components/zwave_mqtt/translations/en.json @@ -1,21 +1,10 @@ { - "config": { - "title": "ZWave over MQTT", - "step": { - "user": { - "title": "Connect to the device", - "data": { - "host": "Host" - } - } - }, - "error": { - "cannot_connect": "Failed to connect, please try again", - "invalid_auth": "Invalid authentication", - "unknown": "Unexpected error" - }, - "abort": { - "already_configured": "Device is already configured" - } - } -} + "config": { + "step": { + "user": { + "title": "Confirm set up" + } + } + }, + "title": "Z-Wave over MQTT" +} \ No newline at end of file diff --git a/tests/components/zwave_mqtt/test_config_flow.py b/tests/components/zwave_mqtt/test_config_flow.py new file mode 100644 index 0000000000000..89e9adb3c172c --- /dev/null +++ b/tests/components/zwave_mqtt/test_config_flow.py @@ -0,0 +1,31 @@ +"""Test the Z-Wave over MQTT config flow.""" +from asynctest import patch + +from homeassistant import config_entries, setup +from homeassistant.components.zwave_mqtt.config_flow import TITLE +from homeassistant.components.zwave_mqtt.const import DOMAIN + + +async def test_form(hass): + """Test we get the form.""" + hass.config.components.add("mqtt") + await setup.async_setup_component(hass, "persistent_notification", {}) + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result["type"] == "form" + assert result["errors"] is None + + with patch( + "homeassistant.components.zwave_mqtt.async_setup", return_value=True + ) as mock_setup, patch( + "homeassistant.components.zwave_mqtt.async_setup_entry", return_value=True, + ) as mock_setup_entry: + result2 = await hass.config_entries.flow.async_configure(result["flow_id"], {}) + + assert result2["type"] == "create_entry" + assert result2["title"] == TITLE + assert result2["data"] == {} + await hass.async_block_till_done() + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 From 83082be90fcecc3466cf9477c1dea0e2c33f4d7b Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Tue, 28 Apr 2020 17:29:29 +0200 Subject: [PATCH 09/51] Remove platforms for now except switch --- .../components/zwave_mqtt/binary_sensor.py | 178 ------------------ homeassistant/components/zwave_mqtt/cover.py | 142 -------------- homeassistant/components/zwave_mqtt/fan.py | 82 -------- homeassistant/components/zwave_mqtt/light.py | 138 -------------- homeassistant/components/zwave_mqtt/sensor.py | 130 ------------- tests/components/zwave_mqtt/test_light.py | 45 ----- tests/components/zwave_mqtt/test_sensor.py | 18 -- 7 files changed, 733 deletions(-) delete mode 100644 homeassistant/components/zwave_mqtt/binary_sensor.py delete mode 100644 homeassistant/components/zwave_mqtt/cover.py delete mode 100644 homeassistant/components/zwave_mqtt/fan.py delete mode 100644 homeassistant/components/zwave_mqtt/light.py delete mode 100644 homeassistant/components/zwave_mqtt/sensor.py delete mode 100644 tests/components/zwave_mqtt/test_light.py delete mode 100644 tests/components/zwave_mqtt/test_sensor.py diff --git a/homeassistant/components/zwave_mqtt/binary_sensor.py b/homeassistant/components/zwave_mqtt/binary_sensor.py deleted file mode 100644 index 0e796a456afef..0000000000000 --- a/homeassistant/components/zwave_mqtt/binary_sensor.py +++ /dev/null @@ -1,178 +0,0 @@ -"""Representation of Z-Wave binary_sensors.""" - -import logging - -from openzwavemqtt.const import ValueType - -from homeassistant.components.binary_sensor import ( - DEVICE_CLASS_DOOR, - DEVICE_CLASS_GAS, - DEVICE_CLASS_HEAT, - DEVICE_CLASS_MOISTURE, - DEVICE_CLASS_MOTION, - DEVICE_CLASS_POWER, - DEVICE_CLASS_PROBLEM, - DEVICE_CLASS_SAFETY, - DEVICE_CLASS_SMOKE, - DEVICE_CLASS_SOUND, - BinarySensorDevice, -) -from homeassistant.core import callback -from homeassistant.helpers.dispatcher import async_dispatcher_connect - -from .const import DATA_UNSUBSCRIBE, DOMAIN -from .entity import ZWaveDeviceEntity, create_device_name - -_LOGGER = logging.getLogger(__name__) - -DEVICE_CLASS_MAPPING = { - # Mapping from Value Index in Notification CC to device class - 1: DEVICE_CLASS_SMOKE, - 2: DEVICE_CLASS_GAS, - 3: DEVICE_CLASS_GAS, - 4: DEVICE_CLASS_HEAT, - 5: DEVICE_CLASS_MOISTURE, - 6: DEVICE_CLASS_SAFETY, - 7: DEVICE_CLASS_SAFETY, - 8: DEVICE_CLASS_POWER, - 9: DEVICE_CLASS_PROBLEM, - 10: DEVICE_CLASS_PROBLEM, - 14: DEVICE_CLASS_SOUND, - 15: DEVICE_CLASS_MOISTURE, - 18: DEVICE_CLASS_GAS, -} - - -async def async_setup_entry(hass, config_entry, async_add_entities): - """Set up Z-Wave binary_sensor from config entry.""" - - @callback - def async_add_binary_sensor(values): - """Add Z-Wave Binary Sensor.""" - sensors_to_add = [] - - if values.primary.type == ValueType.LIST: - - # Handle special cases - # we convert some of the Notification values into it's own binary sensor - # https://github.com/OpenZWave/open-zwave/blob/master/config/NotificationCCTypes.xml - # Use constants/Enums from lib (when added) - for item in values.primary.value["List"]: - if values.primary.index == 6 and item["Value"] == 22: - # Door/Window Open - sensors_to_add.append( - ZWaveListValueSensor(values, item["Value"], DEVICE_CLASS_DOOR) - ) - if values.primary.index == 7 and item["Value"] in [7, 8]: - # Motion detected - sensors_to_add.append( - ZWaveListValueSensor(values, item["Value"], DEVICE_CLASS_MOTION) - ) - - # Fallback to a generic binary sensor for the notification topic - if not sensors_to_add: - sensors_to_add.append(ZWaveListSensor(values)) - - elif values.primary.type == ValueType.BOOL: - # classic/legacy binary sensor - sensors_to_add.append(ZWaveBinarySensor(values)) - else: - # should not happen but just in case log it while we're in beta - _LOGGER.warning("Sensor not implemented for value %s", values.primary.label) - return - - async_add_entities(sensors_to_add) - - hass.data[DOMAIN][config_entry.entry_id][DATA_UNSUBSCRIBE].append( - async_dispatcher_connect( - hass, "zwave_new_binary_sensor", async_add_binary_sensor - ) - ) - - await hass.data[DOMAIN][config_entry.entry_id]["mark_platform_loaded"]( - "binary_sensor" - ) - - -class ZWaveBinarySensor(ZWaveDeviceEntity, BinarySensorDevice): - """Representation of a Z-Wave binary_sensor.""" - - @property - def is_on(self): - """Return if the sensor is on or off.""" - return self.values.primary.value - - @property - def entity_registry_enabled_default(self) -> bool: - """Return if the entity should be enabled when first added to the entity registry.""" - # Legacy binary sensors are phased out (replaced by notification sensors) - # Disable by default to not confuse users - return False - - -class ZWaveListSensor(ZWaveDeviceEntity, BinarySensorDevice): - """Representation of a ZWaveListSensor translated to binary_sensor.""" - - @property - def is_on(self): - """Return if the sensor is on or off.""" - return self.values.primary.value["Selected"] != "Clear" - - @property - def device_state_attributes(self): - """Return the device specific state attributes.""" - attributes = super().device_state_attributes - attributes["event"] = self.values.primary.value["Selected"] - return attributes - - @property - def device_class(self): - """Return the class of this device, from component DEVICE_CLASSES.""" - return DEVICE_CLASS_MAPPING.get(self.values.primary.index) - - @property - def entity_registry_enabled_default(self) -> bool: - """Return if the entity should be enabled when first added to the entity registry.""" - # We hide some of the more advanced sensors by default to not overwhelm users - if self.values.primary.index in [8, 9]: - return False - return True - - -class ZWaveListValueSensor(ZWaveDeviceEntity, BinarySensorDevice): - """Representation of a ZWaveListValueSensor binary_sensor.""" - - def __init__(self, values, list_value, device_class=None): - """Initialize a ZWaveListValueSensor entity.""" - self._list_value = list_value - self._device_class = device_class - super().__init__(values) - - @property - def name(self): - """Return the name of the entity.""" - node = self.values.primary.node - value_label = "" - for item in self.values.primary.value["List"]: - if item["Value"] == self._list_value: - value_label = item["Label"] - break - value_label = value_label.split(" on ")[0] # strip "on location" from name - value_label = value_label.split(" at ")[0] # strip "at location" from name - return f"{create_device_name(node)}: {value_label}" - - @property - def unique_id(self): - """Return the unique_id of the entity.""" - unique_id = super().unique_id - return f"{unique_id}.{self._list_value}" - - @property - def is_on(self): - """Return if the sensor is on or off.""" - return self.values.primary.value["Selected_id"] == self._list_value - - @property - def device_class(self): - """Return the class of this device, from component DEVICE_CLASSES.""" - return self._device_class diff --git a/homeassistant/components/zwave_mqtt/cover.py b/homeassistant/components/zwave_mqtt/cover.py deleted file mode 100644 index 09c74a8463dce..0000000000000 --- a/homeassistant/components/zwave_mqtt/cover.py +++ /dev/null @@ -1,142 +0,0 @@ -"""Support for Z-Wave cover.""" -import logging - -from openzwavemqtt.const import CommandClass - -from homeassistant.components.cover import ( - ATTR_POSITION, - ATTR_TILT_POSITION, - SUPPORT_CLOSE, - SUPPORT_CLOSE_TILT, - SUPPORT_OPEN, - SUPPORT_OPEN_TILT, - SUPPORT_SET_POSITION, - SUPPORT_SET_TILT_POSITION, - CoverDevice, -) -from homeassistant.core import callback -from homeassistant.helpers.dispatcher import async_dispatcher_connect - -from .const import ( - DATA_UNSUBSCRIBE, - DOMAIN, - MANUFACTURER_ID_FIBARO, - PRODUCT_TYPE_FIBARO_FGRM222, -) -from .entity import ZWaveDeviceEntity - -_LOGGER = logging.getLogger(__name__) - -SUPPORTED_FEATURES_POSITION = SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_SET_POSITION -SUPPORTED_FEATURES_TILT = ( - SUPPORT_OPEN_TILT | SUPPORT_CLOSE_TILT | SUPPORT_SET_TILT_POSITION -) - - -async def async_setup_entry(hass, config_entry, async_add_entities): - """Set up Z-Wave Cover from Config Entry.""" - - @callback - def async_add_cover(values): - """Add Z-Wave Cover.""" - # Specific Cover Types - if values.primary.command_class != CommandClass.SWITCH_MULTILEVEL: - _LOGGER.warning("Cover not implemented for values %s", values.primary) - return - - if ( - values.primary.node.node_manufacturer_id == MANUFACTURER_ID_FIBARO - and values.primary.node.node_product_type == PRODUCT_TYPE_FIBARO_FGRM222 - ): - cover = FibaroFGRM222Cover(values) - else: - cover = ZWaveCover(values) - - async_add_entities([cover]) - - hass.data[DOMAIN][config_entry.entry_id][DATA_UNSUBSCRIBE].append( - async_dispatcher_connect(hass, "zwave_new_cover", async_add_cover) - ) - - await hass.data[DOMAIN][config_entry.entry_id]["mark_platform_loaded"]("cover") - - -class ZWaveCover(ZWaveDeviceEntity, CoverDevice): - """Representation of a Z-Wave cover.""" - - @property - def supported_features(self): - """Flag supported features.""" - return SUPPORTED_FEATURES_POSITION - - @property - def is_closed(self): - """Return true if cover is closed.""" - return self.values.primary.value < 5 - - @property - def current_cover_position(self): - """Return the current position of cover where 0 means closed and 100 is fully open.""" - return self.values.primary.value - - async def async_set_cover_position(self, **kwargs): - """Move the cover to a specific position.""" - self.values.primary.send_value(kwargs[ATTR_POSITION]) - - async def async_open_cover(self, **kwargs): - """Open the cover.""" - self.values.primary.send_value(99) - - async def async_close_cover(self, **kwargs): - """Close cover.""" - self.values.primary.send_value(0) - - -class FibaroFGRM222Cover(ZWaveDeviceEntity, CoverDevice): - """Representation of a Fibaro FGRM-222 cover.""" - - @property - def supported_features(self): - """Flag supported features.""" - return SUPPORTED_FEATURES_POSITION | SUPPORTED_FEATURES_TILT - - @property - def is_closed(self): - """Return true if cover is closed.""" - return self.values.fgrm222_slat_position.value < 5 - - @property - def current_cover_position(self): - """Return the current position of cover where 0 means closed and 100 is fully open.""" - return self.values.fgrm222_slat_position.value - - @property - def current_cover_tilt_position(self): - """Return the current tilt position of the cover where 0 means closed/no tilt and 100 means open/maximum tilt.""" - return self.values.fgrm222_tilt_position.value - - async def async_open_cover(self, **kwargs): - """Open the cover.""" - self.values.fgrm222_slat_position.send_value(99) - self.values.fgrm222_tilt_position.send_value(99) - - async def async_close_cover(self, **kwargs): - """Close cover.""" - self.values.fgrm222_slat_position.send_value(0) - self.values.fgrm222_tilt_position.send_value(0) - - async def async_set_cover_position(self, **kwargs): - """Move the cover to a specific position.""" - self.values.fgrm222_slat_position.send_value(kwargs[ATTR_POSITION]) - - async def async_set_cover_tilt_position(self, **kwargs): - """Move the cover tilt to a specific position.""" - self.values.fgrm222_tilt_position.send_value(kwargs[ATTR_TILT_POSITION]) - - async def async_open_cover_tilt(self, **kwargs): - """Open the cover tilt.""" - self.values.fgrm222_tilt_position.send_value(99) - - async def async_close_cover_tilt(self, **kwargs): - """Close the cover tilt.""" - self.values.fgrm222_tilt_position.send_value(0) diff --git a/homeassistant/components/zwave_mqtt/fan.py b/homeassistant/components/zwave_mqtt/fan.py deleted file mode 100644 index 5976d326f040a..0000000000000 --- a/homeassistant/components/zwave_mqtt/fan.py +++ /dev/null @@ -1,82 +0,0 @@ -"""Support for Z-Wave fans.""" -import math - -from homeassistant.components.fan import ( - SPEED_HIGH, - SPEED_LOW, - SPEED_MEDIUM, - SPEED_OFF, - SUPPORT_SET_SPEED, - FanEntity, -) -from homeassistant.core import callback -from homeassistant.helpers.dispatcher import async_dispatcher_connect - -from .const import DATA_UNSUBSCRIBE, DOMAIN -from .entity import ZWaveDeviceEntity - -SPEED_LIST = [SPEED_OFF, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH] - -SUPPORTED_FEATURES = SUPPORT_SET_SPEED - -# Value will first be divided to an integer -VALUE_TO_SPEED = {0: SPEED_OFF, 1: SPEED_LOW, 2: SPEED_MEDIUM, 3: SPEED_HIGH} - -SPEED_TO_VALUE = {SPEED_OFF: 0, SPEED_LOW: 1, SPEED_MEDIUM: 50, SPEED_HIGH: 99} - - -async def async_setup_entry(hass, config_entry, async_add_entities): - """Set up Z-Wave Fan from Config Entry.""" - - @callback - def async_add_fan(values): - """Add Z-Wave Fan.""" - fan = ZwaveFan(values) - async_add_entities([fan]) - - hass.data[DOMAIN][config_entry.entry_id][DATA_UNSUBSCRIBE].append( - async_dispatcher_connect(hass, "zwave_new_fan", async_add_fan) - ) - - await hass.data[DOMAIN][config_entry.entry_id]["mark_platform_loaded"]("fan") - - -class ZwaveFan(ZWaveDeviceEntity, FanEntity): - """Representation of a Z-Wave fan.""" - - async def async_set_speed(self, speed): - """Set the speed of the fan.""" - self.values.primary.send_value(SPEED_TO_VALUE[speed]) - - async def async_turn_on(self, speed=None, **kwargs): - """Turn the device on.""" - if speed is None: - # Value 255 tells device to return to previous value - self.values.primary.send_value(255) - else: - await self.async_set_speed(speed) - - async def async_turn_off(self, **kwargs): - """Turn the device off.""" - self.values.primary.send_value(0) - - @property - def is_on(self): - """Return true if device is on (speed above 0).""" - return self.values.primary.value > 0 - - @property - def speed(self): - """Return the current speed.""" - value = math.ceil(self.values.primary.value * 3 / 100) - return VALUE_TO_SPEED[value] - - @property - def speed_list(self): - """Get the list of available speeds.""" - return SPEED_LIST - - @property - def supported_features(self): - """Flag supported features.""" - return SUPPORTED_FEATURES diff --git a/homeassistant/components/zwave_mqtt/light.py b/homeassistant/components/zwave_mqtt/light.py deleted file mode 100644 index 0558bbf0c800c..0000000000000 --- a/homeassistant/components/zwave_mqtt/light.py +++ /dev/null @@ -1,138 +0,0 @@ -"""Support for Z-Wave lights.""" -import logging - -from homeassistant.components.light import ( - ATTR_BRIGHTNESS, - ATTR_TRANSITION, - SUPPORT_BRIGHTNESS, - SUPPORT_TRANSITION, - Light, -) -from homeassistant.core import callback -from homeassistant.helpers.dispatcher import async_dispatcher_connect - -from .const import DATA_UNSUBSCRIBE, DOMAIN -from .entity import ZWaveDeviceEntity - -_LOGGER = logging.getLogger(__name__) - - -async def async_setup_entry(hass, config_entry, async_add_entities): - """Set up Z-Wave Light from Config Entry.""" - - @callback - def async_add_light(values): - """Add Z-Wave Light.""" - light = ZwaveDimmer(values) - async_add_entities([light]) - - hass.data[DOMAIN][config_entry.entry_id][DATA_UNSUBSCRIBE].append( - async_dispatcher_connect(hass, "zwave_new_light", async_add_light) - ) - - await hass.data[DOMAIN][config_entry.entry_id]["mark_platform_loaded"]("light") - - -def byte_to_zwave_brightness(value): - """Convert brightness in 0-255 scale to 0-99 scale. - - `value` -- (int) Brightness byte value from 0-255. - """ - if value > 0: - return max(1, int((value / 255) * 99)) - return 0 - - -class ZwaveDimmer(ZWaveDeviceEntity, Light): - """Representation of a Z-Wave dimmer.""" - - def __init__(self, values): - """Initialize the light.""" - ZWaveDeviceEntity.__init__(self, values) - self._supported_features = None - self.value_added() - - @callback - def value_added(self): - """Call when a new value is added to this entity.""" - self._supported_features = SUPPORT_BRIGHTNESS - if self.values.dimming_duration is not None: - self._supported_features |= SUPPORT_TRANSITION - - @property - def brightness(self): - """Return the brightness of this light between 0..255.""" - if "target" in self.values: - return round((self.values.target.value / 99) * 255) - return round((self.values.primary.value / 99) * 255) - - @property - def is_on(self): - """Return true if device is on (brightness above 0).""" - if "target" in self.values: - return self.values.target.value > 0 - return self.values.primary.value > 0 - - @property - def supported_features(self): - """Flag supported features.""" - return self._supported_features - - async def async_set_duration(self, **kwargs): - """Set the transition time for the brightness value. - - Zwave Dimming Duration values: - 0 = instant - 0-127 = 1 second to 127 seconds - 128-254 = 1 minute to 127 minutes - 255 = factory default - """ - if self.values.dimming_duration is None: - if ATTR_TRANSITION in kwargs: - _LOGGER.debug("Dimming not supported by %s.", self.entity_id) - return - - if ATTR_TRANSITION not in kwargs: - # no transition specified by user, use defaults - new_value = 255 - else: - # transition specified by user, convert to zwave value - transition = kwargs[ATTR_TRANSITION] - if transition <= 127: - new_value = int(transition) - elif transition > 7620: - new_value = 254 - _LOGGER.warning( - "Transition clipped to 127 minutes for %s.", self.entity_id - ) - else: - minutes = int(transition / 60) - _LOGGER.debug( - "Transition rounded to %d minutes for %s.", minutes, self.entity_id - ) - new_value = minutes + 128 - - # only send value if it differs from current - # this prevents a command for nothing - if self.values.dimming_duration.value != new_value: - self.values.dimming_duration.send_value(new_value) - - async def async_turn_on(self, **kwargs): - """Turn the device on.""" - await self.async_set_duration(**kwargs) - - # Zwave multilevel switches use a range of [0, 99] to control - # brightness. Level 255 means to set it to previous value. - if ATTR_BRIGHTNESS in kwargs: - brightness = kwargs[ATTR_BRIGHTNESS] - brightness = byte_to_zwave_brightness(brightness) - else: - brightness = 255 - - self.values.primary.send_value(brightness) - - async def async_turn_off(self, **kwargs): - """Turn the device off.""" - await self.async_set_duration(**kwargs) - - self.values.primary.send_value(0) diff --git a/homeassistant/components/zwave_mqtt/sensor.py b/homeassistant/components/zwave_mqtt/sensor.py deleted file mode 100644 index 906615192fbe2..0000000000000 --- a/homeassistant/components/zwave_mqtt/sensor.py +++ /dev/null @@ -1,130 +0,0 @@ -"""Representation of Z-Wave sensors.""" - -import logging - -from openzwavemqtt.const import CommandClass - -from homeassistant.components.sensor import ( - DEVICE_CLASS_BATTERY, - DEVICE_CLASS_HUMIDITY, - DEVICE_CLASS_ILLUMINANCE, - DEVICE_CLASS_POWER, - DEVICE_CLASS_PRESSURE, - DEVICE_CLASS_TEMPERATURE, -) -from homeassistant.const import TEMP_CELSIUS, TEMP_FAHRENHEIT -from homeassistant.core import callback -from homeassistant.helpers.dispatcher import async_dispatcher_connect - -from .const import DATA_UNSUBSCRIBE, DOMAIN -from .entity import ZWaveDeviceEntity - -_LOGGER = logging.getLogger(__name__) - - -async def async_setup_entry(hass, config_entry, async_add_entities): - """Set up Z-Wave sensor from config entry.""" - - @callback - def async_add_sensor(value): - """Add Z-Wave Sensor.""" - # Basic Sensor types - if isinstance(value.primary.value, (float, int)): - sensor = ZWaveNumericSensor(value) - - elif isinstance(value.primary.value, dict): - sensor = ZWaveListSensor(value) - - else: - _LOGGER.warning("Sensor not implemented for value %s", value.primary.label) - return - - async_add_entities([sensor]) - - hass.data[DOMAIN][config_entry.entry_id][DATA_UNSUBSCRIBE].append( - async_dispatcher_connect(hass, "zwave_new_sensor", async_add_sensor) - ) - - await hass.data[DOMAIN][config_entry.entry_id]["mark_platform_loaded"]("sensor") - - -class ZwaveSensorBase(ZWaveDeviceEntity): - """Basic Representation of a Z-Wave sensor.""" - - @property - def device_class(self): - """Return the device class of the sensor.""" - if self.values.primary.command_class == CommandClass.BATTERY: - return DEVICE_CLASS_BATTERY - if self.values.primary.command_class == CommandClass.METER: - return DEVICE_CLASS_POWER - if "Temperature" in self.values.primary.label: - return DEVICE_CLASS_TEMPERATURE - if "Illuminance" in self.values.primary.label: - return DEVICE_CLASS_ILLUMINANCE - if "Humidity" in self.values.primary.label: - return DEVICE_CLASS_HUMIDITY - if "Power" in self.values.primary.label: - return DEVICE_CLASS_POWER - if "Energy" in self.values.primary.label: - return DEVICE_CLASS_POWER - if "Electric" in self.values.primary.label: - return DEVICE_CLASS_POWER - if "Pressure" in self.values.primary.label: - return DEVICE_CLASS_PRESSURE - return None - - @property - def entity_registry_enabled_default(self) -> bool: - """Return if the entity should be enabled when first added to the entity registry.""" - # We hide some of the more advanced sensors by default to not overwhelm users - if self.values.primary.command_class in [ - CommandClass.BASIC, - CommandClass.INDICATOR, - CommandClass.NOTIFICATION, - ]: - return False - return True - - -class ZWaveNumericSensor(ZwaveSensorBase): - """Representation of a Z-Wave sensor.""" - - @property - def state(self): - """Return state of the sensor.""" - return round(self.values.primary.value, 2) - - @property - def unit_of_measurement(self): - """Return unit of measurement the value is expressed in.""" - if self.values.primary.units == "C": - return TEMP_CELSIUS - if self.values.primary.units == "F": - return TEMP_FAHRENHEIT - - return self.values.primary.units - - -class ZWaveListSensor(ZwaveSensorBase): - """Representation of a Z-Wave list sensor.""" - - @property - def state(self): - """Return the state of the sensor.""" - # We use the id as value for backwards compatibility - return self.values.primary.value["Selected_id"] - - @property - def device_state_attributes(self): - """Return the device specific state attributes.""" - attributes = super().device_state_attributes - # add the value's label as property - attributes["label"] = self.values.primary.value["Selected"] - return attributes - - @property - def entity_registry_enabled_default(self) -> bool: - """Return if the entity should be enabled when first added to the entity registry.""" - # these sensors are only here for backwards compatibility, disable them by default - return False diff --git a/tests/components/zwave_mqtt/test_light.py b/tests/components/zwave_mqtt/test_light.py deleted file mode 100644 index 71a77918b82d3..0000000000000 --- a/tests/components/zwave_mqtt/test_light.py +++ /dev/null @@ -1,45 +0,0 @@ -"""Test Z-Wave Lights.""" -from homeassistant.components.zwave_mqtt.light import byte_to_zwave_brightness - -from .common import setup_zwave - - -async def test_light(hass, sent_messages): - """Test setting up config entry.""" - await setup_zwave(hass, "generic_network_dump.csv") - - # Test loaded - state = hass.states.get("light.led_bulb_6_multi_colour_level") - assert state is not None - assert state.state == "off" - - # Test turning on - new_brightness = 45 - await hass.services.async_call( - "light", - "turn_on", - { - "entity_id": "light.led_bulb_6_multi_colour_level", - "brightness": new_brightness, - }, - blocking=True, - ) - assert len(sent_messages) == 1 - msg = sent_messages[0] - assert msg["topic"] == "OpenZWave/1/command/setvalue/" - assert msg["payload"] == { - "Value": byte_to_zwave_brightness(new_brightness), - "ValueIDKey": 659128337, - } - - # Test turning off - await hass.services.async_call( - "light", - "turn_off", - {"entity_id": "light.led_bulb_6_multi_colour_level"}, - blocking=True, - ) - assert len(sent_messages) == 2 - msg = sent_messages[1] - assert msg["topic"] == "OpenZWave/1/command/setvalue/" - assert msg["payload"] == {"Value": 0, "ValueIDKey": 659128337} diff --git a/tests/components/zwave_mqtt/test_sensor.py b/tests/components/zwave_mqtt/test_sensor.py deleted file mode 100644 index 64630723f3bb9..0000000000000 --- a/tests/components/zwave_mqtt/test_sensor.py +++ /dev/null @@ -1,18 +0,0 @@ -"""Test Z-Wave Sensors.""" -from .common import setup_zwave - - -async def test_sensor(hass, sent_messages): - """Test setting up config entry.""" - await setup_zwave(hass, "generic_network_dump.csv") - - # Test standard sensor - state = hass.states.get("sensor.smart_plug_electric_v") - assert state is not None - assert state.state == "123.9" - assert state.attributes["unit_of_measurement"] == "V" - - # Test list sensor converted to binary sensor - state = hass.states.get("binary_sensor.trisensor_motion_detected") - assert state is not None - assert state.state == "off" From fa35c06f0de2012c15ecdff95c3d66f86dbb950a Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Tue, 28 Apr 2020 17:30:35 +0200 Subject: [PATCH 10/51] Remove platforms from const --- homeassistant/components/zwave_mqtt/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/zwave_mqtt/const.py b/homeassistant/components/zwave_mqtt/const.py index f0ac0bbca5510..ca1a0112577a0 100644 --- a/homeassistant/components/zwave_mqtt/const.py +++ b/homeassistant/components/zwave_mqtt/const.py @@ -2,7 +2,7 @@ DOMAIN = "zwave_mqtt" DATA_UNSUBSCRIBE = "unsubscribe" -PLATFORMS = ["binary_sensor", "cover", "fan", "sensor", "switch", "light"] +PLATFORMS = ["switch"] # MQTT Topics TOPIC_OPENZWAVE = "OpenZWave" From 728cd34bcce35920d305e0aaf1a1925102ae8916 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Tue, 28 Apr 2020 17:31:50 +0200 Subject: [PATCH 11/51] Clean up const --- homeassistant/components/zwave_mqtt/const.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/homeassistant/components/zwave_mqtt/const.py b/homeassistant/components/zwave_mqtt/const.py index ca1a0112577a0..8719f41d26f79 100644 --- a/homeassistant/components/zwave_mqtt/const.py +++ b/homeassistant/components/zwave_mqtt/const.py @@ -1,5 +1,4 @@ """Constants for the zwave_mqtt integration.""" - DOMAIN = "zwave_mqtt" DATA_UNSUBSCRIBE = "unsubscribe" PLATFORMS = ["switch"] @@ -189,10 +188,3 @@ DISC_SPECIFIC_DEVICE_CLASS = "specific_device_class" DISC_TYPE = "type" DISC_VALUES = "values" - - -# Manufacturer IDs -MANUFACTURER_ID_FIBARO = "0x010f" - -# Product Types -PRODUCT_TYPE_FIBARO_FGRM222 = "0x0302" From b368f149925caa2a6ee23b20d50b968d7384696c Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Tue, 28 Apr 2020 17:38:31 +0200 Subject: [PATCH 12/51] Add Marcel as code owner --- CODEOWNERS | 2 +- homeassistant/components/zwave_mqtt/manifest.json | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CODEOWNERS b/CODEOWNERS index 69a6ccd518a95..1f86e89be71e3 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -461,7 +461,7 @@ homeassistant/components/zha/* @dmulcahey @adminiuga homeassistant/components/zone/* @home-assistant/core homeassistant/components/zoneminder/* @rohankapoorcom homeassistant/components/zwave/* @home-assistant/z-wave -homeassistant/components/zwave_mqtt/* @cgarwood @MartinHjelmare +homeassistant/components/zwave_mqtt/* @cgarwood @marcelveldt @MartinHjelmare # Individual files homeassistant/components/demo/weather @fabaff diff --git a/homeassistant/components/zwave_mqtt/manifest.json b/homeassistant/components/zwave_mqtt/manifest.json index 4c6fe47571b6a..4a3b23770e82b 100644 --- a/homeassistant/components/zwave_mqtt/manifest.json +++ b/homeassistant/components/zwave_mqtt/manifest.json @@ -11,6 +11,7 @@ ], "codeowners": [ "@cgarwood", + "@marcelveldt", "@MartinHjelmare" ] } From 5f2dfffe93d42738b526a716fe758ce3747187e1 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Tue, 28 Apr 2020 17:43:27 +0200 Subject: [PATCH 13/51] Clean up discovery --- .../components/zwave_mqtt/discovery.py | 271 +----------------- 1 file changed, 1 insertion(+), 270 deletions(-) diff --git a/homeassistant/components/zwave_mqtt/discovery.py b/homeassistant/components/zwave_mqtt/discovery.py index 3607abdc15939..711e474032227 100644 --- a/homeassistant/components/zwave_mqtt/discovery.py +++ b/homeassistant/components/zwave_mqtt/discovery.py @@ -1,278 +1,9 @@ """Map Z-Wave nodes and values to Home Assistant entities.""" - -import logging - -from openzwavemqtt.const import CommandClass, ValueGenre, ValueIndex, ValueType +from openzwavemqtt.const import CommandClass, ValueGenre, ValueType from . import const -_LOGGER = logging.getLogger(__name__) - DISCOVERY_SCHEMAS = [ - { # Binary sensors - const.DISC_COMPONENT: "binary_sensor", - const.DISC_GENERIC_DEVICE_CLASS: [ - const.GENERIC_TYPE_ENTRY_CONTROL, - 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_THERMOSTAT, - const.GENERIC_TYPE_SENSOR_NOTIFICATION, - ], - const.DISC_VALUES: { - const.DISC_PRIMARY: { - const.DISC_COMMAND_CLASS: [CommandClass.SENSOR_BINARY], - const.DISC_TYPE: ValueType.BOOL, - const.DISC_GENRE: ValueGenre.USER, - }, - "off_delay": { - const.DISC_COMMAND_CLASS: [CommandClass.CONFIGURATION], - const.DISC_INDEX: [9], - const.DISC_OPTIONAL: True, - }, - }, - }, - { # Notification CommandClass translates to binary_sensor - const.DISC_COMPONENT: "binary_sensor", - const.DISC_VALUES: { - const.DISC_PRIMARY: { - const.DISC_COMMAND_CLASS: [CommandClass.NOTIFICATION], - const.DISC_GENRE: ValueGenre.USER, - const.DISC_TYPE: [ValueType.BOOL, ValueType.LIST], - } - }, - }, - { # Thermostat translates to climate - const.DISC_COMPONENT: "climate", - const.DISC_GENERIC_DEVICE_CLASS: [ - const.GENERIC_TYPE_THERMOSTAT, - const.GENERIC_TYPE_SENSOR_MULTILEVEL, - ], - const.DISC_VALUES: { - const.DISC_PRIMARY: { - const.DISC_COMMAND_CLASS: [CommandClass.THERMOSTAT_SETPOINT] - }, - "temperature": { - const.DISC_COMMAND_CLASS: [CommandClass.SENSOR_MULTILEVEL], - const.DISC_INDEX: [ValueIndex.SENSOR_MULTILEVEL_TEMPERATURE], - const.DISC_OPTIONAL: True, - }, - "mode": { - const.DISC_COMMAND_CLASS: [CommandClass.THERMOSTAT_MODE], - const.DISC_OPTIONAL: True, - }, - "fan_mode": { - const.DISC_COMMAND_CLASS: [CommandClass.THERMOSTAT_FAN_MODE], - const.DISC_OPTIONAL: True, - }, - "operating_state": { - const.DISC_COMMAND_CLASS: [CommandClass.THERMOSTAT_OPERATING_STATE], - const.DISC_OPTIONAL: True, - }, - "fan_action": { - const.DISC_COMMAND_CLASS: [CommandClass.THERMOSTAT_FAN_STATE], - const.DISC_OPTIONAL: True, - }, - "zxt_120_swing_mode": { - const.DISC_COMMAND_CLASS: [CommandClass.CONFIGURATION], - const.DISC_INDEX: [33], - const.DISC_OPTIONAL: True, - }, - }, - }, - { # Rollershutter - const.DISC_COMPONENT: "cover", - const.DISC_GENERIC_DEVICE_CLASS: [ - const.GENERIC_TYPE_SWITCH_MULTILEVEL, - const.GENERIC_TYPE_ENTRY_CONTROL, - ], - const.DISC_SPECIFIC_DEVICE_CLASS: [ - 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.DISC_VALUES: { - const.DISC_PRIMARY: { - const.DISC_COMMAND_CLASS: [CommandClass.SWITCH_MULTILEVEL], - const.DISC_INDEX: [ValueIndex.SWITCH_MULTILEVEL_LEVEL], - const.DISC_GENRE: ValueGenre.USER, - }, - "open": { - const.DISC_COMMAND_CLASS: [CommandClass.SWITCH_MULTILEVEL], - const.DISC_INDEX: [ValueIndex.SWITCH_MULTILEVEL_BRIGHT], - const.DISC_OPTIONAL: True, - }, - "close": { - const.DISC_COMMAND_CLASS: [CommandClass.SWITCH_MULTILEVEL], - const.DISC_INDEX: [ValueIndex.SWITCH_MULTILEVEL_DIM], - const.DISC_OPTIONAL: True, - }, - "fgrm222_slat_position": { - const.DISC_COMMAND_CLASS: [CommandClass.MANUFACTURER_PROPRIETARY], - const.DISC_INDEX: [0], - const.DISC_OPTIONAL: True, - }, - "fgrm222_tilt_position": { - const.DISC_COMMAND_CLASS: [CommandClass.MANUFACTURER_PROPRIETARY], - const.DISC_INDEX: [1], - const.DISC_OPTIONAL: True, - }, - }, - }, - { # Garage Door Switch - const.DISC_COMPONENT: "cover", - const.DISC_GENERIC_DEVICE_CLASS: [ - const.GENERIC_TYPE_SWITCH_MULTILEVEL, - const.GENERIC_TYPE_ENTRY_CONTROL, - ], - const.DISC_SPECIFIC_DEVICE_CLASS: [ - 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.DISC_VALUES: { - const.DISC_PRIMARY: { - const.DISC_COMMAND_CLASS: [CommandClass.SWITCH_BINARY], - const.DISC_GENRE: ValueGenre.USER, - } - }, - }, - { # Garage Door Barrier - const.DISC_COMPONENT: "cover", - const.DISC_GENERIC_DEVICE_CLASS: [ - const.GENERIC_TYPE_SWITCH_MULTILEVEL, - const.GENERIC_TYPE_ENTRY_CONTROL, - ], - const.DISC_SPECIFIC_DEVICE_CLASS: [ - 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.DISC_VALUES: { - const.DISC_PRIMARY: { - const.DISC_COMMAND_CLASS: [CommandClass.BARRIER_OPERATOR], - const.DISC_INDEX: [ValueIndex.BARRIER_OPERATOR_LABEL], - } - }, - }, - { # Fan - const.DISC_COMPONENT: "fan", - const.DISC_GENERIC_DEVICE_CLASS: [const.GENERIC_TYPE_SWITCH_MULTILEVEL], - const.DISC_SPECIFIC_DEVICE_CLASS: [const.SPECIFIC_TYPE_FAN_SWITCH], - const.DISC_VALUES: { - const.DISC_PRIMARY: { - const.DISC_COMMAND_CLASS: [CommandClass.SWITCH_MULTILEVEL], - const.DISC_INDEX: [ValueIndex.SWITCH_MULTILEVEL_LEVEL], - const.DISC_TYPE: ValueType.BYTE, - } - }, - }, - { # Light - const.DISC_COMPONENT: "light", - const.DISC_GENERIC_DEVICE_CLASS: [ - const.GENERIC_TYPE_SWITCH_MULTILEVEL, - const.GENERIC_TYPE_SWITCH_REMOTE, - ], - const.DISC_SPECIFIC_DEVICE_CLASS: [ - const.SPECIFIC_TYPE_POWER_SWITCH_MULTILEVEL, - const.SPECIFIC_TYPE_SCENE_SWITCH_MULTILEVEL, - const.SPECIFIC_TYPE_NOT_USED, - ], - const.DISC_VALUES: { - const.DISC_PRIMARY: { - const.DISC_COMMAND_CLASS: [CommandClass.SWITCH_MULTILEVEL], - const.DISC_INDEX: [ValueIndex.SWITCH_MULTILEVEL_LEVEL], - const.DISC_TYPE: ValueType.BYTE, - }, - "dimming_duration": { - const.DISC_COMMAND_CLASS: [CommandClass.SWITCH_MULTILEVEL], - const.DISC_INDEX: [ValueIndex.SWITCH_MULTILEVEL_DURATION], - const.DISC_OPTIONAL: True, - }, - "color": { - const.DISC_COMMAND_CLASS: [CommandClass.SWITCH_COLOR], - const.DISC_INDEX: [ValueIndex.SWITCH_COLOR_COLOR], - const.DISC_OPTIONAL: True, - }, - "color_channels": { - const.DISC_COMMAND_CLASS: [CommandClass.SWITCH_COLOR], - const.DISC_INDEX: [ValueIndex.SWITCH_COLOR_CHANNELS], - const.DISC_OPTIONAL: True, - }, - }, - }, - { # Lock - const.DISC_COMPONENT: "lock", - const.DISC_GENERIC_DEVICE_CLASS: [const.GENERIC_TYPE_ENTRY_CONTROL], - const.DISC_SPECIFIC_DEVICE_CLASS: [ - const.SPECIFIC_TYPE_DOOR_LOCK, - const.SPECIFIC_TYPE_ADVANCED_DOOR_LOCK, - const.SPECIFIC_TYPE_SECURE_KEYPAD_DOOR_LOCK, - const.SPECIFIC_TYPE_SECURE_LOCKBOX, - ], - const.DISC_VALUES: { - const.DISC_PRIMARY: { - const.DISC_COMMAND_CLASS: [CommandClass.DOOR_LOCK], - const.DISC_INDEX: [ValueIndex.DOOR_LOCK_LOCK], - }, - "access_control": { - const.DISC_COMMAND_CLASS: [CommandClass.ALARM], - const.DISC_INDEX: [ValueIndex.ALARM_ACCESS_CONTROL], - const.DISC_OPTIONAL: True, - }, - "alarm_type": { - const.DISC_COMMAND_CLASS: [CommandClass.ALARM], - const.DISC_INDEX: [ValueIndex.ALARM_TYPE], - const.DISC_OPTIONAL: True, - }, - "alarm_level": { - const.DISC_COMMAND_CLASS: [CommandClass.ALARM], - const.DISC_INDEX: [ValueIndex.ALARM_LEVEL], - const.DISC_OPTIONAL: True, - }, - "v2btze_advanced": { - const.DISC_COMMAND_CLASS: [CommandClass.CONFIGURATION], - const.DISC_INDEX: [12], - const.DISC_OPTIONAL: True, - }, - }, - }, - { # All other text/numeric sensors - const.DISC_COMPONENT: "sensor", - const.DISC_VALUES: { - const.DISC_PRIMARY: { - const.DISC_COMMAND_CLASS: [ - CommandClass.SENSOR_MULTILEVEL, - CommandClass.METER, - CommandClass.ALARM, - CommandClass.SENSOR_ALARM, - CommandClass.INDICATOR, - CommandClass.BATTERY, - CommandClass.NOTIFICATION, - CommandClass.BASIC, - ], - const.DISC_TYPE: [ - ValueType.DECIMAL, - ValueType.INT, - ValueType.STRING, - ValueType.BYTE, - ValueType.LIST, - ], - } - }, - }, { # Switch platform const.DISC_COMPONENT: "switch", const.DISC_GENERIC_DEVICE_CLASS: [ From 1f5ca087b80f74e0fe36321ebbaea3d6c08bbaf2 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Tue, 28 Apr 2020 17:44:18 +0200 Subject: [PATCH 14/51] Use new switch base class --- homeassistant/components/zwave_mqtt/switch.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/zwave_mqtt/switch.py b/homeassistant/components/zwave_mqtt/switch.py index 30cd5828bd65f..be82a45a70b93 100644 --- a/homeassistant/components/zwave_mqtt/switch.py +++ b/homeassistant/components/zwave_mqtt/switch.py @@ -2,7 +2,7 @@ import logging -from homeassistant.components.switch import SwitchDevice +from homeassistant.components.switch import SwitchEntity from homeassistant.const import STATE_OFF, STATE_ON from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -30,7 +30,7 @@ def async_add_switch(value): await hass.data[DOMAIN][config_entry.entry_id]["mark_platform_loaded"]("switch") -class ZWaveSwitch(ZWaveDeviceEntity, SwitchDevice): +class ZWaveSwitch(ZWaveDeviceEntity, SwitchEntity): """Representation of a Z-Wave switch.""" @property From ad5a29b1652270a98be79690778853fcb1b1df68 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Tue, 28 Apr 2020 17:45:02 +0200 Subject: [PATCH 15/51] Clean up switch --- homeassistant/components/zwave_mqtt/switch.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/homeassistant/components/zwave_mqtt/switch.py b/homeassistant/components/zwave_mqtt/switch.py index be82a45a70b93..4469cd3a7887f 100644 --- a/homeassistant/components/zwave_mqtt/switch.py +++ b/homeassistant/components/zwave_mqtt/switch.py @@ -1,7 +1,4 @@ """Representation of Z-Wave switches.""" - -import logging - from homeassistant.components.switch import SwitchEntity from homeassistant.const import STATE_OFF, STATE_ON from homeassistant.core import callback @@ -10,8 +7,6 @@ from .const import DATA_UNSUBSCRIBE, DOMAIN from .entity import ZWaveDeviceEntity -_LOGGER = logging.getLogger(__name__) - async def async_setup_entry(hass, config_entry, async_add_entities): """Set up Z-Wave switch from config entry.""" From 5e985c4062d04eecb47219c3b94859920a37a09b Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Tue, 28 Apr 2020 18:01:28 +0200 Subject: [PATCH 16/51] Update to latest entity base methods --- homeassistant/components/zwave_mqtt/entity.py | 53 +++++++++++++------ 1 file changed, 36 insertions(+), 17 deletions(-) diff --git a/homeassistant/components/zwave_mqtt/entity.py b/homeassistant/components/zwave_mqtt/entity.py index eaa89019439d6..df1ddad5f8f6d 100644 --- a/homeassistant/components/zwave_mqtt/entity.py +++ b/homeassistant/components/zwave_mqtt/entity.py @@ -144,25 +144,17 @@ def __init__(self, values): self.options = values.options @callback - def value_changed(self, value): - """Call when the value is changed.""" - if value.value_id_key in (v.value_id_key for v in self.values if v): - self.async_write_ha_state() - - @callback - def value_added(self): - """Handle a new value for this entity.""" + def on_value_update(self): + """Call when a value is added/updated in the entity EntityValues Collection. - @callback - def instance_updated(self, new_status): - """Call when the instance status changes.""" - self.async_write_ha_state() + To be overridden by platforms needing this event. + """ async def async_added_to_hass(self): """Call when entity is added.""" # add dispatcher and OZW listeners callbacks, - self.options.listen(EVENT_VALUE_CHANGED, self.value_changed) - self.options.listen(EVENT_INSTANCE_STATUS_CHANGED, self.instance_updated) + self.options.listen(EVENT_VALUE_CHANGED, self._value_changed) + self.options.listen(EVENT_INSTANCE_STATUS_CHANGED, self._instance_updated) # add to on_remove so they will be cleaned up on entity removal self.async_on_remove( async_dispatcher_connect( @@ -171,7 +163,7 @@ async def async_added_to_hass(self): ) self.async_on_remove( async_dispatcher_connect( - self.hass, f"{self.values.values_id}_value_added", self.value_added + self.hass, f"{self.values.values_id}_value_added", self._value_added ) ) @@ -221,6 +213,33 @@ def available(self) -> bool: "driverAwakeNodesQueried", ] + @callback + def _value_changed(self, value): + """Call when a value from ZWaveDeviceEntityValues is changed. + + Should not be overridden by subclasses. + """ + if value.value_id_key in (v.value_id_key for v in self.values if v): + self.on_value_update() + self.async_write_ha_state() + + @callback + def _value_added(self): + """Call when a value from ZWaveDeviceEntityValues is added. + + Should not be overridden by subclasses. + """ + self.on_value_update() + + @callback + def _instance_updated(self, new_status): + """Call when the instance status changes. + + Should not be overridden by subclasses. + """ + self.on_value_update() + self.async_write_ha_state() + async def _delete_callback(self, values_id): """Remove this entity.""" if not self.values: @@ -231,9 +250,9 @@ async def _delete_callback(self, values_id): async def async_will_remove_from_hass(self) -> None: """Call when entity will be removed from hass.""" # cleanup OZW listeners - self.options.listeners[EVENT_VALUE_CHANGED].remove(self.value_changed) + self.options.listeners[EVENT_VALUE_CHANGED].remove(self._value_changed) self.options.listeners[EVENT_INSTANCE_STATUS_CHANGED].remove( - self.instance_updated + self._instance_updated ) From 82d29eb38ac6ca04e2b1abf090c2edf3d86dc75d Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Tue, 28 Apr 2020 21:00:17 +0200 Subject: [PATCH 17/51] Polish config flow test name --- tests/components/zwave_mqtt/test_config_flow.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/components/zwave_mqtt/test_config_flow.py b/tests/components/zwave_mqtt/test_config_flow.py index 89e9adb3c172c..8809a6897b474 100644 --- a/tests/components/zwave_mqtt/test_config_flow.py +++ b/tests/components/zwave_mqtt/test_config_flow.py @@ -6,8 +6,8 @@ from homeassistant.components.zwave_mqtt.const import DOMAIN -async def test_form(hass): - """Test we get the form.""" +async def test_user_create_entry(hass): + """Test the user step creates an entry.""" hass.config.components.add("mqtt") await setup.async_setup_component(hass, "persistent_notification", {}) result = await hass.config_entries.flow.async_init( From 500e5738f751b8e389f7a8c75ee28aabe7a2ed02 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Tue, 28 Apr 2020 22:10:40 +0200 Subject: [PATCH 18/51] Move generic fixture --- .../fixtures => fixtures/zwave_mqtt}/generic_network_dump.csv | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/{components/zwave_mqtt/fixtures => fixtures/zwave_mqtt}/generic_network_dump.csv (100%) diff --git a/tests/components/zwave_mqtt/fixtures/generic_network_dump.csv b/tests/fixtures/zwave_mqtt/generic_network_dump.csv similarity index 100% rename from tests/components/zwave_mqtt/fixtures/generic_network_dump.csv rename to tests/fixtures/zwave_mqtt/generic_network_dump.csv From 16f8db198c2a071012afd2ff8302dcf29cb7d72e Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Tue, 28 Apr 2020 22:13:12 +0200 Subject: [PATCH 19/51] Test switch receive --- tests/components/zwave_mqtt/common.py | 30 +++++++++++++++++----- tests/components/zwave_mqtt/conftest.py | 15 +++++++++++ tests/components/zwave_mqtt/test_switch.py | 15 +++++++++-- tests/fixtures/zwave_mqtt/switch.json | 25 ++++++++++++++++++ 4 files changed, 76 insertions(+), 9 deletions(-) create mode 100644 tests/fixtures/zwave_mqtt/switch.json diff --git a/tests/components/zwave_mqtt/common.py b/tests/components/zwave_mqtt/common.py index f1158830bef32..d927b2a262c50 100644 --- a/tests/components/zwave_mqtt/common.py +++ b/tests/components/zwave_mqtt/common.py @@ -1,13 +1,13 @@ """Helpers for tests.""" +import json import logging -from pathlib import Path from asynctest import Mock, patch from homeassistant import config_entries, core as ha from homeassistant.components.zwave_mqtt.const import DOMAIN -from tests.common import MockConfigEntry +from tests.common import MockConfigEntry, load_fixture _LOGGER = logging.getLogger(__name__) @@ -33,12 +33,11 @@ async def setup_zwave(hass, fixture=None): receive_message = mock_subscribe.mock_calls[0][1][2] if fixture is not None: - data = Path(__file__).parent / "fixtures" / fixture + data = await hass.async_add_executor_job(load_fixture, f"zwave_mqtt/{fixture}") - with data.open("rt") as fp: - for line in fp: - topic, payload = line.strip().split(",", 1) - receive_message(Mock(topic=topic, payload=payload)) + for line in data.split("\n"): + topic, payload = line.strip().split(",", 1) + receive_message(Mock(topic=topic, payload=payload)) await hass.async_block_till_done() @@ -56,3 +55,20 @@ def capture_events(event): hass.bus.async_listen(event_name, capture_events) return events + + +class MQTTMessage: + """Represent a mock MQTT message.""" + + def __init__(self, topic, payload): + """Set up message.""" + self.topic = topic + self.payload = payload + + def decode(self): + """Decode message payload from a string to a json dict.""" + self.payload = json.loads(self.payload) + + def encode(self): + """Encode message payload into a string.""" + self.payload = json.dumps(self.payload) diff --git a/tests/components/zwave_mqtt/conftest.py b/tests/components/zwave_mqtt/conftest.py index d67010f04a953..60f1277244844 100644 --- a/tests/components/zwave_mqtt/conftest.py +++ b/tests/components/zwave_mqtt/conftest.py @@ -4,6 +4,10 @@ from asynctest import patch import pytest +from .common import MQTTMessage + +from tests.common import load_fixture + @pytest.fixture(name="sent_messages") def sent_messages_fixture(): @@ -17,3 +21,14 @@ def sent_messages_fixture(): ), ): yield sent_messages + + +@pytest.fixture(name="switch_msg") +async def switch_msg_fixture(hass): + """Return a mock MQTT msg with a switch actuator message.""" + switch_json = json.loads( + await hass.async_add_executor_job(load_fixture, "zwave_mqtt/switch.json") + ) + message = MQTTMessage(topic=switch_json["topic"], payload=switch_json["payload"]) + message.encode() + return message diff --git a/tests/components/zwave_mqtt/test_switch.py b/tests/components/zwave_mqtt/test_switch.py index 01231a2fd9d88..0afac94ed208b 100644 --- a/tests/components/zwave_mqtt/test_switch.py +++ b/tests/components/zwave_mqtt/test_switch.py @@ -2,9 +2,9 @@ from .common import setup_zwave -async def test_switch(hass, sent_messages): +async def test_switch(hass, sent_messages, switch_msg): """Test setting up config entry.""" - await setup_zwave(hass, "generic_network_dump.csv") + receive_message = await setup_zwave(hass, "generic_network_dump.csv") # Test loaded state = hass.states.get("switch.smart_plug_switch") @@ -20,6 +20,17 @@ async def test_switch(hass, sent_messages): assert msg["topic"] == "OpenZWave/1/command/setvalue/" assert msg["payload"] == {"Value": True, "ValueIDKey": 541671440} + # Feedback on state + switch_msg.decode() + switch_msg.payload["Value"] = True + switch_msg.encode() + receive_message(switch_msg) + await hass.async_block_till_done() + + state = hass.states.get("switch.smart_plug_switch") + assert state is not None + assert state.state == "on" + # Test turning off await hass.services.async_call( "switch", "turn_off", {"entity_id": "switch.smart_plug_switch"}, blocking=True diff --git a/tests/fixtures/zwave_mqtt/switch.json b/tests/fixtures/zwave_mqtt/switch.json new file mode 100644 index 0000000000000..0d3fc37e9b242 --- /dev/null +++ b/tests/fixtures/zwave_mqtt/switch.json @@ -0,0 +1,25 @@ +{ + "topic": "OpenZWave/1/node/32/instance/1/commandclass/37/value/541671440/", + "payload": { + "Label": "Switch", + "Value": false, + "Units": "", + "Min": 0, + "Max": 0, + "Type": "Bool", + "Instance": 1, + "CommandClass": "COMMAND_CLASS_SWITCH_BINARY", + "Index": 0, + "Node": 32, + "Genre": "User", + "Help": "Turn On/Off Device", + "ValueIDKey": 541671440, + "ReadOnly": false, + "WriteOnly": false, + "ValueSet": false, + "ValuePolled": false, + "ChangeVerified": false, + "Event": "valueAdded", + "TimeStamp": 1579566891 + } +} From e065476b3185b3b96d4302459283e93b933867c3 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Tue, 28 Apr 2020 23:10:17 +0200 Subject: [PATCH 20/51] Allow passing in entry to setup helper --- tests/components/zwave_mqtt/common.py | 13 +++++++------ tests/components/zwave_mqtt/test_init.py | 2 +- tests/components/zwave_mqtt/test_scenes.py | 2 +- tests/components/zwave_mqtt/test_switch.py | 2 +- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/tests/components/zwave_mqtt/common.py b/tests/components/zwave_mqtt/common.py index d927b2a262c50..c22d5ac35bd94 100644 --- a/tests/components/zwave_mqtt/common.py +++ b/tests/components/zwave_mqtt/common.py @@ -12,15 +12,16 @@ _LOGGER = logging.getLogger(__name__) -async def setup_zwave(hass, fixture=None): +async def setup_zwave(hass, entry=None, fixture=None): """Set up Z-Wave and load a dump.""" hass.config.components.add("mqtt") - entry = MockConfigEntry( - domain=DOMAIN, - title="Z-Wave", - connection_class=config_entries.CONN_CLASS_LOCAL_PUSH, - ) + if entry is None: + entry = MockConfigEntry( + domain=DOMAIN, + title="Z-Wave", + connection_class=config_entries.CONN_CLASS_LOCAL_PUSH, + ) entry.add_to_hass(hass) diff --git a/tests/components/zwave_mqtt/test_init.py b/tests/components/zwave_mqtt/test_init.py index 1982a6236c1eb..405811d9a8cf6 100644 --- a/tests/components/zwave_mqtt/test_init.py +++ b/tests/components/zwave_mqtt/test_init.py @@ -6,7 +6,7 @@ async def test_init_entry(hass): """Test setting up config entry.""" - await setup_zwave(hass, "generic_network_dump.csv") + await setup_zwave(hass, fixture="generic_network_dump.csv") # Verify integration + platform loaded. assert "zwave_mqtt" in hass.config.components diff --git a/tests/components/zwave_mqtt/test_scenes.py b/tests/components/zwave_mqtt/test_scenes.py index 3f86008e089ff..2853290f18377 100644 --- a/tests/components/zwave_mqtt/test_scenes.py +++ b/tests/components/zwave_mqtt/test_scenes.py @@ -9,7 +9,7 @@ async def test_scenes(hass, sent_messages): """Test setting up config entry.""" - receive_message = await setup_zwave(hass, "generic_network_dump.csv") + receive_message = await setup_zwave(hass, fixture="generic_network_dump.csv") events = async_capture_events(hass, "zwave_mqtt.scene_activated") # Publish fake scene event on mqtt diff --git a/tests/components/zwave_mqtt/test_switch.py b/tests/components/zwave_mqtt/test_switch.py index 0afac94ed208b..77f022d0db100 100644 --- a/tests/components/zwave_mqtt/test_switch.py +++ b/tests/components/zwave_mqtt/test_switch.py @@ -4,7 +4,7 @@ async def test_switch(hass, sent_messages, switch_msg): """Test setting up config entry.""" - receive_message = await setup_zwave(hass, "generic_network_dump.csv") + receive_message = await setup_zwave(hass, fixture="generic_network_dump.csv") # Test loaded state = hass.states.get("switch.smart_plug_switch") From a1d0592f4ddcda94e17117e3c25ea81351aca36f Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Tue, 28 Apr 2020 23:13:10 +0200 Subject: [PATCH 21/51] Clean up --- tests/components/zwave_mqtt/common.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/components/zwave_mqtt/common.py b/tests/components/zwave_mqtt/common.py index c22d5ac35bd94..eaf483f4ebac3 100644 --- a/tests/components/zwave_mqtt/common.py +++ b/tests/components/zwave_mqtt/common.py @@ -1,6 +1,5 @@ """Helpers for tests.""" import json -import logging from asynctest import Mock, patch @@ -9,8 +8,6 @@ from tests.common import MockConfigEntry, load_fixture -_LOGGER = logging.getLogger(__name__) - async def setup_zwave(hass, entry=None, fixture=None): """Set up Z-Wave and load a dump.""" From 0219fdf6e3ea3ae646e2511529682d194b63a170 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Wed, 29 Apr 2020 00:29:06 +0200 Subject: [PATCH 22/51] Add unload entry test --- tests/components/zwave_mqtt/common.py | 2 +- tests/components/zwave_mqtt/test_init.py | 45 ++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/tests/components/zwave_mqtt/common.py b/tests/components/zwave_mqtt/common.py index eaf483f4ebac3..4150e78c0a607 100644 --- a/tests/components/zwave_mqtt/common.py +++ b/tests/components/zwave_mqtt/common.py @@ -20,7 +20,7 @@ async def setup_zwave(hass, entry=None, fixture=None): connection_class=config_entries.CONN_CLASS_LOCAL_PUSH, ) - entry.add_to_hass(hass) + entry.add_to_hass(hass) with patch("homeassistant.components.mqtt.async_subscribe") as mock_subscribe: await hass.config_entries.async_setup(entry.entry_id) diff --git a/tests/components/zwave_mqtt/test_init.py b/tests/components/zwave_mqtt/test_init.py index 405811d9a8cf6..d79f40d318e11 100644 --- a/tests/components/zwave_mqtt/test_init.py +++ b/tests/components/zwave_mqtt/test_init.py @@ -1,8 +1,11 @@ """Test integration initialization.""" +from homeassistant import config_entries from homeassistant.components.zwave_mqtt import DOMAIN, PLATFORMS, const from .common import setup_zwave +from tests.common import MockConfigEntry + async def test_init_entry(hass): """Test setting up config entry.""" @@ -21,3 +24,45 @@ async def test_init_entry(hass): assert hass.services.has_service(DOMAIN, const.SERVICE_REPLACE_FAILED_NODE) assert hass.services.has_service(DOMAIN, const.SERVICE_CANCEL_COMMAND) assert hass.services.has_service(DOMAIN, const.SERVICE_SET_CONFIG_PARAMETER) + + +async def test_unload_entry(hass, switch_msg, caplog): + """Test unload the config entry.""" + entry = MockConfigEntry( + domain=DOMAIN, + title="Z-Wave", + connection_class=config_entries.CONN_CLASS_LOCAL_PUSH, + ) + entry.add_to_hass(hass) + assert entry.state == config_entries.ENTRY_STATE_NOT_LOADED + + receive_message = await setup_zwave( + hass, entry=entry, fixture="generic_network_dump.csv" + ) + + assert entry.state == config_entries.ENTRY_STATE_LOADED + assert len(hass.states.async_entity_ids("switch")) == 1 + + await hass.config_entries.async_unload(entry.entry_id) + + assert entry.state == config_entries.ENTRY_STATE_NOT_LOADED + assert len(hass.states.async_entity_ids("switch")) == 0 + + # Send a message for a switch from the broker to check that + # all entity topic subscribers are unsubscribed. + receive_message(switch_msg) + await hass.async_block_till_done() + + assert len(hass.states.async_entity_ids("switch")) == 0 + + # Load the integration again and check that there are no errors when + # adding the entities. + # This asserts that we have unsubscribed the entity addition signals + # when unloading the integration previously. + await setup_zwave(hass, entry=entry, fixture="generic_network_dump.csv") + await hass.async_block_till_done() + + assert entry.state == config_entries.ENTRY_STATE_LOADED + assert len(hass.states.async_entity_ids("switch")) == 1 + for record in caplog.records: + assert record.levelname != "ERROR" From 23db18db2bb14a3ab8e058d40d8000dcf4476c3d Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Wed, 29 Apr 2020 00:41:40 +0200 Subject: [PATCH 23/51] Extract load fixture data to pytest fixture --- tests/components/zwave_mqtt/common.py | 6 ++---- tests/components/zwave_mqtt/conftest.py | 8 ++++++++ tests/components/zwave_mqtt/test_init.py | 12 +++++------- tests/components/zwave_mqtt/test_scenes.py | 4 ++-- tests/components/zwave_mqtt/test_switch.py | 4 ++-- 5 files changed, 19 insertions(+), 15 deletions(-) diff --git a/tests/components/zwave_mqtt/common.py b/tests/components/zwave_mqtt/common.py index 4150e78c0a607..f1abf21674442 100644 --- a/tests/components/zwave_mqtt/common.py +++ b/tests/components/zwave_mqtt/common.py @@ -6,7 +6,7 @@ from homeassistant import config_entries, core as ha from homeassistant.components.zwave_mqtt.const import DOMAIN -from tests.common import MockConfigEntry, load_fixture +from tests.common import MockConfigEntry async def setup_zwave(hass, entry=None, fixture=None): @@ -31,9 +31,7 @@ async def setup_zwave(hass, entry=None, fixture=None): receive_message = mock_subscribe.mock_calls[0][1][2] if fixture is not None: - data = await hass.async_add_executor_job(load_fixture, f"zwave_mqtt/{fixture}") - - for line in data.split("\n"): + for line in fixture.split("\n"): topic, payload = line.strip().split(",", 1) receive_message(Mock(topic=topic, payload=payload)) diff --git a/tests/components/zwave_mqtt/conftest.py b/tests/components/zwave_mqtt/conftest.py index 60f1277244844..c405b559450df 100644 --- a/tests/components/zwave_mqtt/conftest.py +++ b/tests/components/zwave_mqtt/conftest.py @@ -9,6 +9,14 @@ from tests.common import load_fixture +@pytest.fixture(name="generic_data") +async def generic_data_fixture(hass): + """Load generic MQTT data and return it.""" + return await hass.async_add_executor_job( + load_fixture, f"zwave_mqtt/generic_network_dump.csv" + ) + + @pytest.fixture(name="sent_messages") def sent_messages_fixture(): """Fixture to capture sent messages.""" diff --git a/tests/components/zwave_mqtt/test_init.py b/tests/components/zwave_mqtt/test_init.py index d79f40d318e11..67ad61452e81f 100644 --- a/tests/components/zwave_mqtt/test_init.py +++ b/tests/components/zwave_mqtt/test_init.py @@ -7,9 +7,9 @@ from tests.common import MockConfigEntry -async def test_init_entry(hass): +async def test_init_entry(hass, generic_data): """Test setting up config entry.""" - await setup_zwave(hass, fixture="generic_network_dump.csv") + await setup_zwave(hass, fixture=generic_data) # Verify integration + platform loaded. assert "zwave_mqtt" in hass.config.components @@ -26,7 +26,7 @@ async def test_init_entry(hass): assert hass.services.has_service(DOMAIN, const.SERVICE_SET_CONFIG_PARAMETER) -async def test_unload_entry(hass, switch_msg, caplog): +async def test_unload_entry(hass, generic_data, switch_msg, caplog): """Test unload the config entry.""" entry = MockConfigEntry( domain=DOMAIN, @@ -36,9 +36,7 @@ async def test_unload_entry(hass, switch_msg, caplog): entry.add_to_hass(hass) assert entry.state == config_entries.ENTRY_STATE_NOT_LOADED - receive_message = await setup_zwave( - hass, entry=entry, fixture="generic_network_dump.csv" - ) + receive_message = await setup_zwave(hass, entry=entry, fixture=generic_data) assert entry.state == config_entries.ENTRY_STATE_LOADED assert len(hass.states.async_entity_ids("switch")) == 1 @@ -59,7 +57,7 @@ async def test_unload_entry(hass, switch_msg, caplog): # adding the entities. # This asserts that we have unsubscribed the entity addition signals # when unloading the integration previously. - await setup_zwave(hass, entry=entry, fixture="generic_network_dump.csv") + await setup_zwave(hass, entry=entry, fixture=generic_data) await hass.async_block_till_done() assert entry.state == config_entries.ENTRY_STATE_LOADED diff --git a/tests/components/zwave_mqtt/test_scenes.py b/tests/components/zwave_mqtt/test_scenes.py index 2853290f18377..55231ae1cf456 100644 --- a/tests/components/zwave_mqtt/test_scenes.py +++ b/tests/components/zwave_mqtt/test_scenes.py @@ -6,10 +6,10 @@ from .common import async_capture_events, setup_zwave -async def test_scenes(hass, sent_messages): +async def test_scenes(hass, generic_data, sent_messages): """Test setting up config entry.""" - receive_message = await setup_zwave(hass, fixture="generic_network_dump.csv") + receive_message = await setup_zwave(hass, fixture=generic_data) events = async_capture_events(hass, "zwave_mqtt.scene_activated") # Publish fake scene event on mqtt diff --git a/tests/components/zwave_mqtt/test_switch.py b/tests/components/zwave_mqtt/test_switch.py index 77f022d0db100..84929dabe0abe 100644 --- a/tests/components/zwave_mqtt/test_switch.py +++ b/tests/components/zwave_mqtt/test_switch.py @@ -2,9 +2,9 @@ from .common import setup_zwave -async def test_switch(hass, sent_messages, switch_msg): +async def test_switch(hass, generic_data, sent_messages, switch_msg): """Test setting up config entry.""" - receive_message = await setup_zwave(hass, fixture="generic_network_dump.csv") + receive_message = await setup_zwave(hass, fixture=generic_data) # Test loaded state = hass.states.get("switch.smart_plug_switch") From 919e61271034287b40dfab43e9c7699170b6e042 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Wed, 29 Apr 2020 00:46:06 +0200 Subject: [PATCH 24/51] Bump python-openzwave-mqtt to 0.0.9 --- homeassistant/components/zwave_mqtt/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/zwave_mqtt/manifest.json b/homeassistant/components/zwave_mqtt/manifest.json index 4a3b23770e82b..86f055f55dcc6 100644 --- a/homeassistant/components/zwave_mqtt/manifest.json +++ b/homeassistant/components/zwave_mqtt/manifest.json @@ -4,7 +4,7 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/zwave_mqtt", "requirements": [ - "python-openzwave-mqtt==0.0.8" + "python-openzwave-mqtt==0.0.9" ], "dependencies": [ "mqtt" diff --git a/requirements_all.txt b/requirements_all.txt index ac4db55c6932e..79154f90d540f 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1674,7 +1674,7 @@ python-nest==4.1.0 python-nmap==0.6.1 # homeassistant.components.zwave_mqtt -python-openzwave-mqtt==0.0.8 +python-openzwave-mqtt==0.0.9 # homeassistant.components.qbittorrent python-qbittorrent==0.4.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 2417e339397a5..84a9893137230 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -656,7 +656,7 @@ python-miio==0.5.0.1 python-nest==4.1.0 # homeassistant.components.zwave_mqtt -python-openzwave-mqtt==0.0.8 +python-openzwave-mqtt==0.0.9 # homeassistant.components.synology_dsm python-synology==0.7.3 From ab62c121da05999b6eb016a6e681e3bb651ace91 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Wed, 29 Apr 2020 22:19:48 +0200 Subject: [PATCH 25/51] Handle node update --- .../components/zwave_mqtt/__init__.py | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/homeassistant/components/zwave_mqtt/__init__.py b/homeassistant/components/zwave_mqtt/__init__.py index e2d0502bdb678..19dabc448bd34 100644 --- a/homeassistant/components/zwave_mqtt/__init__.py +++ b/homeassistant/components/zwave_mqtt/__init__.py @@ -97,6 +97,9 @@ def async_node_added(node): def async_node_changed(node): _LOGGER.debug("[NODE CHANGED] node_id: %s", node.id) data_nodes[node.id] = node + # notify devices about the node change + if node.id not in removed_nodes: + hass.async_create_task(handle_node_update(hass, node)) @callback def async_node_removed(node): @@ -262,6 +265,34 @@ async def handle_remove_node(hass: HomeAssistant, node: OZWNode): dev_registry.async_remove_device(dev_id) +async def handle_node_update(hass: HomeAssistant, node: OZWNode): + """ + Handle a node updated event from OZW. + + Meaning some of the basic info like name/model is updated. + We want these changes to be pushed to the device registry. + """ + dev_registry = await get_dev_reg(hass) + # grab device in device registry attached to this node + device = dev_registry.async_get_device([(DOMAIN, node.id)], []) + if not device: + return + # update device in device registry with (updated) info + for item in dev_registry.devices.values(): + if item.id != device.id and item.via_device_id != device.id: + continue + if node.meta_data.get("Name"): + dev_name = node.meta_data["Name"] + else: + dev_name = node.node_product_name + dev_registry.async_update_device( + item.id, + manufacturer=node.node_manufacturer_name, + model=node.node_product_name, + name=dev_name, + ) + + @callback def handle_scene_activated(hass: HomeAssistant, scene_value: OZWValue): """Handle a (central) scene activation message.""" From 5f0830c05077224b00ed55d5373f8478410b7dbb Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Thu, 30 Apr 2020 16:37:38 +0200 Subject: [PATCH 26/51] Sort listens --- homeassistant/components/zwave_mqtt/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/zwave_mqtt/__init__.py b/homeassistant/components/zwave_mqtt/__init__.py index 19dabc448bd34..49959b1313904 100644 --- a/homeassistant/components/zwave_mqtt/__init__.py +++ b/homeassistant/components/zwave_mqtt/__init__.py @@ -210,9 +210,9 @@ def async_value_removed(value): # Listen to events for node and value changes options.listen(EVENT_NODE_ADDED, async_node_added) - options.listen(EVENT_VALUE_ADDED, async_value_added) options.listen(EVENT_NODE_CHANGED, async_node_changed) options.listen(EVENT_NODE_REMOVED, async_node_removed) + options.listen(EVENT_VALUE_ADDED, async_value_added) options.listen(EVENT_VALUE_CHANGED, async_value_changed) options.listen(EVENT_VALUE_REMOVED, async_value_removed) options.listen(EVENT_INSTANCE_EVENT, async_instance_event) From 4650fc54c7bf0f619555e821a59dc4f5702433c4 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Thu, 30 Apr 2020 21:58:18 +0200 Subject: [PATCH 27/51] Exclude from coverage for now --- .coveragerc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.coveragerc b/.coveragerc index e2aa4243a4630..a9b799102456f 100644 --- a/.coveragerc +++ b/.coveragerc @@ -877,6 +877,10 @@ omit = homeassistant/components/zoneminder/* homeassistant/components/supla/* homeassistant/components/zwave/util.py + homeassistant/components/zwave_mqtt/__init__.py + homeassistant/components/zwave_mqtt/discovery.py + homeassistant/components/zwave_mqtt/entity.py + homeassistant/components/zwave_mqtt/services.py [report] # Regexes for lines to exclude from consideration From 99044f727c14d444f3bcea2625c1f0cea5461d23 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Thu, 30 Apr 2020 22:30:55 +0200 Subject: [PATCH 28/51] Enhance config flow --- homeassistant/components/zwave_mqtt/__init__.py | 3 +++ homeassistant/components/zwave_mqtt/config_flow.py | 4 ++++ homeassistant/components/zwave_mqtt/manifest.json | 2 +- homeassistant/components/zwave_mqtt/strings.json | 4 ++++ homeassistant/components/zwave_mqtt/translations/en.json | 4 ++++ 5 files changed, 16 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/zwave_mqtt/__init__.py b/homeassistant/components/zwave_mqtt/__init__.py index 49959b1313904..7300b70855f18 100644 --- a/homeassistant/components/zwave_mqtt/__init__.py +++ b/homeassistant/components/zwave_mqtt/__init__.py @@ -39,6 +39,9 @@ async def async_setup(hass: HomeAssistant, config: dict): """Initialize basic config of zwave_mqtt component.""" + if "mqtt" not in hass.config.components: + _LOGGER.error("MQTT integration is not set up") + return False hass.data[DOMAIN] = {} return True diff --git a/homeassistant/components/zwave_mqtt/config_flow.py b/homeassistant/components/zwave_mqtt/config_flow.py index 8297a9925dfcc..ff6ab21994f1d 100644 --- a/homeassistant/components/zwave_mqtt/config_flow.py +++ b/homeassistant/components/zwave_mqtt/config_flow.py @@ -14,6 +14,10 @@ class DomainConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user(self, user_input=None): """Handle the initial step.""" + if self._async_current_entries(): + return self.async_abort(reason="one_instance_allowed") + if "mqtt" not in self.hass.config.components: + return self.async_abort(reason="mqtt_required") if user_input is not None: return self.async_create_entry(title=TITLE, data={}) diff --git a/homeassistant/components/zwave_mqtt/manifest.json b/homeassistant/components/zwave_mqtt/manifest.json index 86f055f55dcc6..3a988b0e42e73 100644 --- a/homeassistant/components/zwave_mqtt/manifest.json +++ b/homeassistant/components/zwave_mqtt/manifest.json @@ -6,7 +6,7 @@ "requirements": [ "python-openzwave-mqtt==0.0.9" ], - "dependencies": [ + "after_dependencies": [ "mqtt" ], "codeowners": [ diff --git a/homeassistant/components/zwave_mqtt/strings.json b/homeassistant/components/zwave_mqtt/strings.json index ed8961f608f89..949b545086b53 100644 --- a/homeassistant/components/zwave_mqtt/strings.json +++ b/homeassistant/components/zwave_mqtt/strings.json @@ -5,6 +5,10 @@ "user": { "title": "Confirm set up" } + }, + "abort": { + "one_instance_allowed": "The integration only supports one Z-Wave instance", + "mqtt_required": "The MQTT integration is not set up" } } } diff --git a/homeassistant/components/zwave_mqtt/translations/en.json b/homeassistant/components/zwave_mqtt/translations/en.json index 14ef45e42e51c..4b5b44a76bb93 100644 --- a/homeassistant/components/zwave_mqtt/translations/en.json +++ b/homeassistant/components/zwave_mqtt/translations/en.json @@ -1,5 +1,9 @@ { "config": { + "abort": { + "mqtt_required": "The MQTT integration is not set up", + "one_instance_allowed": "The integration only supports one Z-Wave instance" + }, "step": { "user": { "title": "Confirm set up" From c08897ee658c31a8bb1f503ba27085d01f646559 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Thu, 30 Apr 2020 22:37:56 +0200 Subject: [PATCH 29/51] Add config flow tests --- .../components/zwave_mqtt/test_config_flow.py | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tests/components/zwave_mqtt/test_config_flow.py b/tests/components/zwave_mqtt/test_config_flow.py index 8809a6897b474..3ad873a9a33cf 100644 --- a/tests/components/zwave_mqtt/test_config_flow.py +++ b/tests/components/zwave_mqtt/test_config_flow.py @@ -5,6 +5,8 @@ from homeassistant.components.zwave_mqtt.config_flow import TITLE from homeassistant.components.zwave_mqtt.const import DOMAIN +from tests.common import MockConfigEntry + async def test_user_create_entry(hass): """Test the user step creates an entry.""" @@ -29,3 +31,24 @@ async def test_user_create_entry(hass): await hass.async_block_till_done() assert len(mock_setup.mock_calls) == 1 assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_mqtt_not_setup(hass): + """Test that mqtt is required.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result["type"] == "abort" + assert result["reason"] == "mqtt_required" + + +async def test_one_instance_allowed(hass): + """Test that only one instance is allowed.""" + entry = MockConfigEntry(domain=DOMAIN, data={}, title=TITLE) + entry.add_to_hass(hass) + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result["type"] == "abort" + assert result["reason"] == "one_instance_allowed" From ccaf4db8aff8510bcf9c32a278dab84be5b6a754 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Thu, 30 Apr 2020 22:47:01 +0200 Subject: [PATCH 30/51] Fix permissions --- homeassistant/components/zwave_mqtt/services.yaml | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 homeassistant/components/zwave_mqtt/services.yaml diff --git a/homeassistant/components/zwave_mqtt/services.yaml b/homeassistant/components/zwave_mqtt/services.yaml old mode 100755 new mode 100644 From b400ba9da19ba712a82ea64d124f3fc4cce1124e Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Fri, 1 May 2020 00:18:24 +0200 Subject: [PATCH 31/51] Add guard clause to handle remove node --- .../components/zwave_mqtt/__init__.py | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/zwave_mqtt/__init__.py b/homeassistant/components/zwave_mqtt/__init__.py index 7300b70855f18..a947092ce4498 100644 --- a/homeassistant/components/zwave_mqtt/__init__.py +++ b/homeassistant/components/zwave_mqtt/__init__.py @@ -256,16 +256,17 @@ async def handle_remove_node(hass: HomeAssistant, node: OZWNode): # grab device in device registry attached to this node dev_id = create_device_id(node) device = dev_registry.async_get_device({(DOMAIN, dev_id)}, set()) - if device: - devices_to_remove = [device.id] - # also grab slave devices (node instances) - for item in dev_registry.devices.values(): - if item.via_device_id == device.id: - devices_to_remove.append(item.id) - # remove all devices in registry related to this node - # note: removal of entity registry is handled by core - for dev_id in devices_to_remove: - dev_registry.async_remove_device(dev_id) + if not device: + return + devices_to_remove = [device.id] + # also grab slave devices (node instances) + for item in dev_registry.devices.values(): + if item.via_device_id == device.id: + devices_to_remove.append(item.id) + # remove all devices in registry related to this node + # note: removal of entity registry is handled by core + for dev_id in devices_to_remove: + dev_registry.async_remove_device(dev_id) async def handle_node_update(hass: HomeAssistant, node: OZWNode): From 37cc103a9feef91ef7788439b0eb38e9ef2c790e Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Fri, 1 May 2020 01:31:50 +0200 Subject: [PATCH 32/51] Fix dispatch signal domain prefix --- homeassistant/components/zwave_mqtt/const.py | 4 +++- homeassistant/components/zwave_mqtt/entity.py | 10 +++++++--- homeassistant/components/zwave_mqtt/switch.py | 10 +++++++--- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/zwave_mqtt/const.py b/homeassistant/components/zwave_mqtt/const.py index 8719f41d26f79..0347c2d8c627b 100644 --- a/homeassistant/components/zwave_mqtt/const.py +++ b/homeassistant/components/zwave_mqtt/const.py @@ -1,7 +1,9 @@ """Constants for the zwave_mqtt integration.""" +from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN + DOMAIN = "zwave_mqtt" DATA_UNSUBSCRIBE = "unsubscribe" -PLATFORMS = ["switch"] +PLATFORMS = [SWITCH_DOMAIN] # MQTT Topics TOPIC_OPENZWAVE = "OpenZWave" diff --git a/homeassistant/components/zwave_mqtt/entity.py b/homeassistant/components/zwave_mqtt/entity.py index df1ddad5f8f6d..218036682adf4 100644 --- a/homeassistant/components/zwave_mqtt/entity.py +++ b/homeassistant/components/zwave_mqtt/entity.py @@ -88,7 +88,9 @@ def check_value(self, value): # If the entity has already been created, notify it of the new value. if self._entity_created: - async_dispatcher_send(self._hass, f"{self.values_id}_value_added") + async_dispatcher_send( + self._hass, f"{DOMAIN}_{self.values_id}_value_added" + ) # Check if entity has all required values and create the entity if needed. self._check_entity_ready() @@ -127,7 +129,7 @@ def _check_entity_ready(self): self._entity_created = True if component in PLATFORMS: - async_dispatcher_send(self._hass, f"zwave_new_{component}", self) + async_dispatcher_send(self._hass, f"{DOMAIN}_new_{component}", self) @property def values_id(self): @@ -163,7 +165,9 @@ async def async_added_to_hass(self): ) self.async_on_remove( async_dispatcher_connect( - self.hass, f"{self.values.values_id}_value_added", self._value_added + self.hass, + f"{DOMAIN}_{self.values.values_id}_value_added", + self._value_added, ) ) diff --git a/homeassistant/components/zwave_mqtt/switch.py b/homeassistant/components/zwave_mqtt/switch.py index 4469cd3a7887f..12a851c7941b8 100644 --- a/homeassistant/components/zwave_mqtt/switch.py +++ b/homeassistant/components/zwave_mqtt/switch.py @@ -1,5 +1,5 @@ """Representation of Z-Wave switches.""" -from homeassistant.components.switch import SwitchEntity +from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN, SwitchEntity from homeassistant.const import STATE_OFF, STATE_ON from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -19,10 +19,14 @@ def async_add_switch(value): async_add_entities([switch]) hass.data[DOMAIN][config_entry.entry_id][DATA_UNSUBSCRIBE].append( - async_dispatcher_connect(hass, "zwave_new_switch", async_add_switch) + async_dispatcher_connect( + hass, f"{DOMAIN}_new_{SWITCH_DOMAIN}", async_add_switch + ) ) - await hass.data[DOMAIN][config_entry.entry_id]["mark_platform_loaded"]("switch") + await hass.data[DOMAIN][config_entry.entry_id]["mark_platform_loaded"]( + SWITCH_DOMAIN + ) class ZWaveSwitch(ZWaveDeviceEntity, SwitchEntity): From 5e1a1ae8bb3abec12ac90ad98765d0494ce99f14 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Fri, 1 May 2020 01:56:32 +0200 Subject: [PATCH 33/51] Fix switch is on --- homeassistant/components/zwave_mqtt/switch.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/zwave_mqtt/switch.py b/homeassistant/components/zwave_mqtt/switch.py index 12a851c7941b8..ca61253e4a280 100644 --- a/homeassistant/components/zwave_mqtt/switch.py +++ b/homeassistant/components/zwave_mqtt/switch.py @@ -1,6 +1,5 @@ """Representation of Z-Wave switches.""" from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN, SwitchEntity -from homeassistant.const import STATE_OFF, STATE_ON from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -33,11 +32,9 @@ class ZWaveSwitch(ZWaveDeviceEntity, SwitchEntity): """Representation of a Z-Wave switch.""" @property - def state(self): - """Return the state of the switch.""" - if self.values.primary.value: - return STATE_ON - return STATE_OFF + def is_on(self): + """Return a boolean for the state of the switch.""" + return bool(self.values.primary.value) async def async_turn_on(self, **kwargs): """Turn the switch on.""" From 327aca7b128714b26d6a14345b1bc691c955e6ba Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Fri, 1 May 2020 02:04:39 +0200 Subject: [PATCH 34/51] Apply suggestions from code review Co-authored-by: Paulus Schoutsen --- homeassistant/components/zwave_mqtt/__init__.py | 2 +- homeassistant/components/zwave_mqtt/entity.py | 7 +++---- tests/components/zwave_mqtt/common.py | 4 ++-- tests/components/zwave_mqtt/conftest.py | 2 +- tests/components/zwave_mqtt/test_config_flow.py | 2 +- 5 files changed, 8 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/zwave_mqtt/__init__.py b/homeassistant/components/zwave_mqtt/__init__.py index a947092ce4498..f3e2ebec7816a 100644 --- a/homeassistant/components/zwave_mqtt/__init__.py +++ b/homeassistant/components/zwave_mqtt/__init__.py @@ -278,7 +278,7 @@ async def handle_node_update(hass: HomeAssistant, node: OZWNode): """ dev_registry = await get_dev_reg(hass) # grab device in device registry attached to this node - device = dev_registry.async_get_device([(DOMAIN, node.id)], []) + device = dev_registry.async_get_device({(DOMAIN, node.id)}, set()) if not device: return # update device in device registry with (updated) info diff --git a/homeassistant/components/zwave_mqtt/entity.py b/homeassistant/components/zwave_mqtt/entity.py index 218036682adf4..501a9918c21bd 100644 --- a/homeassistant/components/zwave_mqtt/entity.py +++ b/homeassistant/components/zwave_mqtt/entity.py @@ -263,10 +263,9 @@ async def async_will_remove_from_hass(self) -> None: def create_device_name(node: OZWNode): """Generate sensible (short) default device name from a OZWNode.""" if node.meta_data["Name"]: - dev_name = f'{node.meta_data["Name"]}' - else: - dev_name = f"{node.node_product_name}" - return dev_name + return node.meta_data["Name"] + + return node.node_product_name def create_device_id(node: OZWNode, node_instance: int = 1): diff --git a/tests/components/zwave_mqtt/common.py b/tests/components/zwave_mqtt/common.py index f1abf21674442..4fa8290d4f790 100644 --- a/tests/components/zwave_mqtt/common.py +++ b/tests/components/zwave_mqtt/common.py @@ -1,7 +1,7 @@ """Helpers for tests.""" import json -from asynctest import Mock, patch +from tests.async_mock import Mock, patch from homeassistant import config_entries, core as ha from homeassistant.components.zwave_mqtt.const import DOMAIN @@ -23,7 +23,7 @@ async def setup_zwave(hass, entry=None, fixture=None): entry.add_to_hass(hass) with patch("homeassistant.components.mqtt.async_subscribe") as mock_subscribe: - await hass.config_entries.async_setup(entry.entry_id) + assert await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() assert "zwave_mqtt" in hass.config.components diff --git a/tests/components/zwave_mqtt/conftest.py b/tests/components/zwave_mqtt/conftest.py index c405b559450df..72a2899ad68f7 100644 --- a/tests/components/zwave_mqtt/conftest.py +++ b/tests/components/zwave_mqtt/conftest.py @@ -1,7 +1,7 @@ """Helpers for tests.""" import json -from asynctest import patch +from tests.async_mock import patch import pytest from .common import MQTTMessage diff --git a/tests/components/zwave_mqtt/test_config_flow.py b/tests/components/zwave_mqtt/test_config_flow.py index 3ad873a9a33cf..8d79032ef7672 100644 --- a/tests/components/zwave_mqtt/test_config_flow.py +++ b/tests/components/zwave_mqtt/test_config_flow.py @@ -1,5 +1,5 @@ """Test the Z-Wave over MQTT config flow.""" -from asynctest import patch +from tests.async_mock import patch from homeassistant import config_entries, setup from homeassistant.components.zwave_mqtt.config_flow import TITLE From a51d9834a14096f3a83e9f88b88ada229b9ba91a Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Fri, 1 May 2020 17:39:26 +0200 Subject: [PATCH 35/51] Add OZW instance to unique id for value --- homeassistant/components/zwave_mqtt/__init__.py | 6 +++--- homeassistant/components/zwave_mqtt/entity.py | 9 ++++++++- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/zwave_mqtt/__init__.py b/homeassistant/components/zwave_mqtt/__init__.py index a947092ce4498..7dc38f8a62b49 100644 --- a/homeassistant/components/zwave_mqtt/__init__.py +++ b/homeassistant/components/zwave_mqtt/__init__.py @@ -28,7 +28,7 @@ from . import const from .const import DATA_UNSUBSCRIBE, DOMAIN, PLATFORMS, TOPIC_OPENZWAVE from .discovery import DISCOVERY_SCHEMAS, check_node_schema, check_value_schema -from .entity import ZWaveDeviceEntityValues, create_device_id +from .entity import ZWaveDeviceEntityValues, create_device_id, create_value_id from .services import ZWaveServices _LOGGER = logging.getLogger(__name__) @@ -150,7 +150,7 @@ def async_value_added(value): node_data_values = data_values[node_id] # Check if this value should be tracked by an existing entity - value_unique_id = f"{value.node.id}-{value.value_id_key}" + value_unique_id = create_value_id(value) for values in node_data_values: values.check_value(value) if values.values_id == value_unique_id: @@ -203,7 +203,7 @@ def async_value_removed(value): value.command_class, ) # signal all entities using this value for removal - value_unique_id = f"{value.node.id}-{value.value_id_key}" + value_unique_id = create_value_id(value) async_dispatcher_send(hass, const.SIGNAL_DELETE_ENTITY, value_unique_id) # remove value from our local list node_data_values = data_values[value.node.id] diff --git a/homeassistant/components/zwave_mqtt/entity.py b/homeassistant/components/zwave_mqtt/entity.py index 218036682adf4..f5d230d2da24b 100644 --- a/homeassistant/components/zwave_mqtt/entity.py +++ b/homeassistant/components/zwave_mqtt/entity.py @@ -5,6 +5,7 @@ from openzwavemqtt.const import EVENT_INSTANCE_STATUS_CHANGED, EVENT_VALUE_CHANGED from openzwavemqtt.models.node import OZWNode +from openzwavemqtt.models.value import OZWValue from homeassistant.core import callback from homeassistant.helpers.dispatcher import ( @@ -134,7 +135,7 @@ def _check_entity_ready(self): @property def values_id(self): """Identification for this values collection.""" - return f"{self.primary.node.id}-{self.primary.value_id_key}" + return create_value_id(self.primary) class ZWaveDeviceEntity(Entity): @@ -274,3 +275,9 @@ def create_device_id(node: OZWNode, node_instance: int = 1): ozw_instance = node.parent.id dev_id = f"{ozw_instance}.{node.node_id}.{node_instance}" return dev_id + + +def create_value_id(value: OZWValue): + """Generate unique value_id from an OZWValue.""" + # [OZW_INSTANCE_ID]-[NODE_ID]-[VALUE_ID_KEY] + return f"{value.node.parent.id}-{value.node.id}-{value.value_id_key}" From 18bfb577ee133091ab1caede3e673bed8d065614 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Fri, 1 May 2020 17:54:48 +0200 Subject: [PATCH 36/51] Remove non essential services for now --- .../components/zwave_mqtt/__init__.py | 2 +- .../components/zwave_mqtt/services.py | 122 +----------------- 2 files changed, 2 insertions(+), 122 deletions(-) diff --git a/homeassistant/components/zwave_mqtt/__init__.py b/homeassistant/components/zwave_mqtt/__init__.py index 7dc38f8a62b49..4d04b79a40354 100644 --- a/homeassistant/components/zwave_mqtt/__init__.py +++ b/homeassistant/components/zwave_mqtt/__init__.py @@ -221,7 +221,7 @@ def async_value_removed(value): options.listen(EVENT_INSTANCE_EVENT, async_instance_event) # Register Services - services = ZWaveServices(hass, manager, data_nodes) + services = ZWaveServices(hass, manager) services.register() return True diff --git a/homeassistant/components/zwave_mqtt/services.py b/homeassistant/components/zwave_mqtt/services.py index 54b823847b87d..45515746196b8 100644 --- a/homeassistant/components/zwave_mqtt/services.py +++ b/homeassistant/components/zwave_mqtt/services.py @@ -1,25 +1,18 @@ """Methods and classes related to executing Z-Wave commands and publishing these to hass.""" -import logging - -from openzwavemqtt.const import CommandClass, ValueType import voluptuous as vol from homeassistant.core import callback -import homeassistant.helpers.config_validation as cv from . import const -_LOGGER = logging.getLogger(__name__) - class ZWaveServices: """Class that holds our services ( Zwave Commands) that should be published to hass.""" - def __init__(self, hass, manager, data_nodes): + def __init__(self, hass, manager): """Initialize with both hass and ozwmanager objects.""" self._hass = hass self._manager = manager - self._data_nodes = data_nodes @callback def register(self): @@ -43,52 +36,6 @@ def register(self): {vol.Optional(const.ATTR_INSTANCE_ID, default=1): vol.Coerce(int)} ), ) - self._hass.services.async_register( - const.DOMAIN, - const.SERVICE_REMOVE_FAILED_NODE, - self.remove_failed_node, - schema=vol.Schema( - { - vol.Required(const.ATTR_NODE_ID): vol.Coerce(int), - vol.Optional(const.ATTR_INSTANCE_ID, default=1): vol.Coerce(int), - } - ), - ) - self._hass.services.async_register( - const.DOMAIN, - const.SERVICE_REPLACE_FAILED_NODE, - self.replace_failed_node, - schema=vol.Schema( - { - vol.Required(const.ATTR_NODE_ID): vol.Coerce(int), - vol.Optional(const.ATTR_INSTANCE_ID, default=1): vol.Coerce(int), - } - ), - ) - self._hass.services.async_register( - const.DOMAIN, - const.SERVICE_CANCEL_COMMAND, - self.cancel_command, - schema=vol.Schema( - {vol.Optional(const.ATTR_INSTANCE_ID, default=1): vol.Coerce(int)} - ), - ) - self._hass.services.async_register( - const.DOMAIN, - const.SERVICE_SET_CONFIG_PARAMETER, - self.set_config_parameter, - schema=vol.Schema( - { - vol.Required(const.ATTR_NODE_ID): vol.Coerce(int), - vol.Required(const.ATTR_CONFIG_PARAMETER): vol.Coerce(int), - vol.Required(const.ATTR_CONFIG_VALUE): vol.Any( - vol.Coerce(int), cv.string - ), - vol.Optional(const.ATTR_CONFIG_SIZE, default=2): vol.Coerce(int), - vol.Optional(const.ATTR_INSTANCE_ID, default=1): vol.Coerce(int), - } - ), - ) @callback def add_node(self, service): @@ -104,70 +51,3 @@ def remove_node(self, service): instance_id = service.data[const.ATTR_INSTANCE_ID] instance = self._manager.get_instance(instance_id) instance.remove_node() - - @callback - def remove_failed_node(self, service): - """Remove a failed node from the controller.""" - instance_id = service.data[const.ATTR_INSTANCE_ID] - node_id = service.data[const.ATTR_NODE_ID] - instance = self._manager.get_instance(instance_id) - instance.remove_failed_node(node_id) - - @callback - def replace_failed_node(self, service): - """Replace a failed node from the controller with a new device.""" - instance_id = service.data[const.ATTR_INSTANCE_ID] - node_id = service.data[const.ATTR_NODE_ID] - instance = self._manager.get_instance(instance_id) - instance.replace_failed_node(node_id) - - @callback - def cancel_command(self, service): - """Cancel in Controller Commands that are in progress.""" - instance_id = service.data[const.ATTR_INSTANCE_ID] - instance = self._manager.get_instance(instance_id) - instance.cancel_controller_command() - - @callback - def set_config_parameter(self, service): - """Set a config parameter to a node.""" - node_id = service.data[const.ATTR_NODE_ID] - node = self._data_nodes[node_id] - param = service.data.get(const.ATTR_CONFIG_PARAMETER) - selection = service.data.get(const.ATTR_CONFIG_VALUE) - # enumerate values until we find the param within configuration items - for value in node.values(): - if value.index != param: - continue - if value.command_class != CommandClass.CONFIGURATION: - continue - _LOGGER.info( - "Setting config parameter %s on Node %s with selection %s", - param, - node_id, - selection, - ) - # Bool value - if value.type == ValueType.BOOL: - value.send_value(int(selection == "True")) - return - # List value - if value.type == ValueType.LIST: - value.send_value(str(selection)) - return - # Button - if value.type == ValueType.BUTTON: - value.send_value(True) - value.send_value(False) - return - # Byte value - value.send_value(int(selection)) - return - - # Parameter-index not found! - _LOGGER.warning( - "Unknown config parameter %s on Node %s with selection %s", - param, - node_id, - selection, - ) From c053778fb64f9217e7843e0bf67a6ced00336dea Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Fri, 1 May 2020 21:04:32 +0200 Subject: [PATCH 37/51] Rename and prefix with async --- .../components/zwave_mqtt/__init__.py | 18 +++++++++--------- homeassistant/components/zwave_mqtt/entity.py | 12 ++++++------ .../components/zwave_mqtt/services.py | 10 +++++----- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/homeassistant/components/zwave_mqtt/__init__.py b/homeassistant/components/zwave_mqtt/__init__.py index 4d04b79a40354..30f817cbc0a34 100644 --- a/homeassistant/components/zwave_mqtt/__init__.py +++ b/homeassistant/components/zwave_mqtt/__init__.py @@ -102,7 +102,7 @@ def async_node_changed(node): data_nodes[node.id] = node # notify devices about the node change if node.id not in removed_nodes: - hass.async_create_task(handle_node_update(hass, node)) + hass.async_create_task(async_handle_node_update(hass, node)) @callback def async_node_removed(node): @@ -112,7 +112,7 @@ def async_node_removed(node): # cleanup device/entity registry if we know this node is permanently deleted # entities itself are removed by the values logic if node.id in removed_nodes: - hass.async_create_task(handle_remove_node(hass, node)) + hass.async_create_task(async_handle_remove_node(hass, node)) removed_nodes.remove(node.id) @callback @@ -152,7 +152,7 @@ def async_value_added(value): # Check if this value should be tracked by an existing entity value_unique_id = create_value_id(value) for values in node_data_values: - values.check_value(value) + values.async_check_value(value) if values.values_id == value_unique_id: return # this value already has an entity @@ -166,7 +166,7 @@ def async_value_added(value): continue values = ZWaveDeviceEntityValues(hass, options, schema, value) - values.setup() + values.async_setup() # We create a new list and update the reference here so that # the list can be safely iterated over in the main thread @@ -189,7 +189,7 @@ def async_value_changed(value): CommandClass.SCENE_ACTIVATION, CommandClass.CENTRAL_SCENE, ]: - handle_scene_activated(hass, value) + async_handle_scene_activated(hass, value) return @callback @@ -222,7 +222,7 @@ def async_value_removed(value): # Register Services services = ZWaveServices(hass, manager) - services.register() + services.async_register() return True @@ -250,7 +250,7 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): return True -async def handle_remove_node(hass: HomeAssistant, node: OZWNode): +async def async_handle_remove_node(hass: HomeAssistant, node: OZWNode): """Handle the removal of a Z-Wave node, removing all traces in device/entity registry.""" dev_registry = await get_dev_reg(hass) # grab device in device registry attached to this node @@ -269,7 +269,7 @@ async def handle_remove_node(hass: HomeAssistant, node: OZWNode): dev_registry.async_remove_device(dev_id) -async def handle_node_update(hass: HomeAssistant, node: OZWNode): +async def async_handle_node_update(hass: HomeAssistant, node: OZWNode): """ Handle a node updated event from OZW. @@ -298,7 +298,7 @@ async def handle_node_update(hass: HomeAssistant, node: OZWNode): @callback -def handle_scene_activated(hass: HomeAssistant, scene_value: OZWValue): +def async_handle_scene_activated(hass: HomeAssistant, scene_value: OZWValue): """Handle a (central) scene activation message.""" node_id = scene_value.node.id scene_id = scene_value.index diff --git a/homeassistant/components/zwave_mqtt/entity.py b/homeassistant/components/zwave_mqtt/entity.py index f5d230d2da24b..315901bcc2d36 100644 --- a/homeassistant/components/zwave_mqtt/entity.py +++ b/homeassistant/components/zwave_mqtt/entity.py @@ -42,16 +42,16 @@ def __init__(self, hass, options, schema, primary_value): self._node = primary_value.node self._schema[const.DISC_NODE_ID] = [self._node.node_id] - def setup(self): + def async_setup(self): """Set up values instance.""" # Check values that have already been discovered for node # and see if they match the schema and need added to the entity. for value in self._node.values(): - self.check_value(value) + self.async_check_value(value) # Check if all the _required_ values in the schema are present and # create the entity. - self._check_entity_ready() + self._async_check_entity_ready() def __getattr__(self, name): """Get the specified value for this entity.""" @@ -66,7 +66,7 @@ def __contains__(self, name): return name in self._values @callback - def check_value(self, value): + def async_check_value(self, value): """Check if the new value matches a missing value for this entity. If a match is found, it is added to the values mapping. @@ -94,10 +94,10 @@ def check_value(self, value): ) # Check if entity has all required values and create the entity if needed. - self._check_entity_ready() + self._async_check_entity_ready() @callback - def _check_entity_ready(self): + def _async_check_entity_ready(self): """Check if all required values are discovered and create entity.""" # Abort if the entity has already been created if self._entity_created: diff --git a/homeassistant/components/zwave_mqtt/services.py b/homeassistant/components/zwave_mqtt/services.py index 45515746196b8..a2f4ca2e5531d 100644 --- a/homeassistant/components/zwave_mqtt/services.py +++ b/homeassistant/components/zwave_mqtt/services.py @@ -15,12 +15,12 @@ def __init__(self, hass, manager): self._manager = manager @callback - def register(self): + def async_register(self): """Register all our services.""" self._hass.services.async_register( const.DOMAIN, const.SERVICE_ADD_NODE, - self.add_node, + self.async_add_node, schema=vol.Schema( { vol.Optional(const.ATTR_INSTANCE_ID, default=1): vol.Coerce(int), @@ -31,14 +31,14 @@ def register(self): self._hass.services.async_register( const.DOMAIN, const.SERVICE_REMOVE_NODE, - self.remove_node, + self.async_remove_node, schema=vol.Schema( {vol.Optional(const.ATTR_INSTANCE_ID, default=1): vol.Coerce(int)} ), ) @callback - def add_node(self, service): + def async_add_node(self, service): """Enter inclusion mode on the controller.""" instance_id = service.data[const.ATTR_INSTANCE_ID] secure = service.data[const.ATTR_SECURE] @@ -46,7 +46,7 @@ def add_node(self, service): instance.add_node(secure) @callback - def remove_node(self, service): + def async_remove_node(self, service): """Enter exclusion mode on the controller.""" instance_id = service.data[const.ATTR_INSTANCE_ID] instance = self._manager.get_instance(instance_id) From 56fb5f7eb2584160faab78216b7a9d0382239ef4 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Fri, 1 May 2020 21:06:26 +0200 Subject: [PATCH 38/51] Removea clear --- homeassistant/components/zwave_mqtt/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/homeassistant/components/zwave_mqtt/__init__.py b/homeassistant/components/zwave_mqtt/__init__.py index 30f817cbc0a34..dc0c961bb6778 100644 --- a/homeassistant/components/zwave_mqtt/__init__.py +++ b/homeassistant/components/zwave_mqtt/__init__.py @@ -244,7 +244,6 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): # unsubscribe all listeners for unsubscribe_listener in hass.data[DOMAIN][entry.entry_id][DATA_UNSUBSCRIBE]: unsubscribe_listener() - hass.data[DOMAIN][entry.entry_id][DATA_UNSUBSCRIBE].clear() hass.data[DOMAIN].pop(entry.entry_id) return True From 9d075522749abfc39cdb618733b1c9ec14435088 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Fri, 1 May 2020 21:18:11 +0200 Subject: [PATCH 39/51] Use tuples instead of lists in discovery --- .../components/zwave_mqtt/discovery.py | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/homeassistant/components/zwave_mqtt/discovery.py b/homeassistant/components/zwave_mqtt/discovery.py index 711e474032227..21c3aedda7125 100644 --- a/homeassistant/components/zwave_mqtt/discovery.py +++ b/homeassistant/components/zwave_mqtt/discovery.py @@ -3,10 +3,10 @@ from . import const -DISCOVERY_SCHEMAS = [ +DISCOVERY_SCHEMAS = ( { # Switch platform const.DISC_COMPONENT: "switch", - const.DISC_GENERIC_DEVICE_CLASS: [ + const.DISC_GENERIC_DEVICE_CLASS: ( const.GENERIC_TYPE_METER, const.GENERIC_TYPE_SENSOR_ALARM, const.GENERIC_TYPE_SENSOR_BINARY, @@ -19,16 +19,16 @@ const.GENERIC_TYPE_REPEATER_SLAVE, const.GENERIC_TYPE_THERMOSTAT, const.GENERIC_TYPE_WALL_CONTROLLER, - ], + ), const.DISC_VALUES: { const.DISC_PRIMARY: { - const.DISC_COMMAND_CLASS: [CommandClass.SWITCH_BINARY], + const.DISC_COMMAND_CLASS: (CommandClass.SWITCH_BINARY,), const.DISC_TYPE: ValueType.BOOL, const.DISC_GENRE: ValueGenre.USER, } }, }, -] +) def check_node_schema(node, schema): @@ -38,13 +38,13 @@ def check_node_schema(node, schema): if ( const.DISC_GENERIC_DEVICE_CLASS in schema and node.node_generic - not in ensure_list(schema[const.DISC_GENERIC_DEVICE_CLASS]) + not in ensure_tuple(schema[const.DISC_GENERIC_DEVICE_CLASS]) ): return False if ( const.DISC_SPECIFIC_DEVICE_CLASS in schema and node.node_specific - not in ensure_list(schema[const.DISC_SPECIFIC_DEVICE_CLASS]) + not in ensure_tuple(schema[const.DISC_SPECIFIC_DEVICE_CLASS]) ): return False return True @@ -57,19 +57,19 @@ def check_value_schema(value, schema): and value.parent.command_class_id not in schema[const.DISC_COMMAND_CLASS] ): return False - if const.DISC_TYPE in schema and value.type not in ensure_list( + if const.DISC_TYPE in schema and value.type not in ensure_tuple( schema[const.DISC_TYPE] ): return False - if const.DISC_GENRE in schema and value.genre not in ensure_list( + if const.DISC_GENRE in schema and value.genre not in ensure_tuple( schema[const.DISC_GENRE] ): return False - if const.DISC_INDEX in schema and value.index not in ensure_list( + if const.DISC_INDEX in schema and value.index not in ensure_tuple( schema[const.DISC_INDEX] ): return False - if const.DISC_INSTANCE in schema and value.instance not in ensure_list( + if const.DISC_INSTANCE in schema and value.instance not in ensure_tuple( schema[const.DISC_INSTANCE] ): return False @@ -83,8 +83,8 @@ def check_value_schema(value, schema): return True -def ensure_list(value): - """Convert a value to a list if needed.""" - if isinstance(value, list): +def ensure_tuple(value): + """Convert a value to a tuple if needed.""" + if isinstance(value, tuple): return value - return [value] + return (value,) From f68a90e7c13cbbb76aaa1deb81feb662f2fb4c04 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Fri, 1 May 2020 21:29:41 +0200 Subject: [PATCH 40/51] Waste less during discovery --- .../components/zwave_mqtt/discovery.py | 36 ++++++++----------- 1 file changed, 14 insertions(+), 22 deletions(-) diff --git a/homeassistant/components/zwave_mqtt/discovery.py b/homeassistant/components/zwave_mqtt/discovery.py index 21c3aedda7125..0aa68eefb46a2 100644 --- a/homeassistant/components/zwave_mqtt/discovery.py +++ b/homeassistant/components/zwave_mqtt/discovery.py @@ -35,16 +35,12 @@ def check_node_schema(node, schema): """Check if node matches the passed node schema.""" if const.DISC_NODE_ID in schema and node.node_id not in schema[const.DISC_NODE_ID]: return False - if ( - const.DISC_GENERIC_DEVICE_CLASS in schema - and node.node_generic - not in ensure_tuple(schema[const.DISC_GENERIC_DEVICE_CLASS]) + if const.DISC_GENERIC_DEVICE_CLASS in schema and not eq_or_in( + node.node_generic, schema[const.DISC_GENERIC_DEVICE_CLASS] ): return False - if ( - const.DISC_SPECIFIC_DEVICE_CLASS in schema - and node.node_specific - not in ensure_tuple(schema[const.DISC_SPECIFIC_DEVICE_CLASS]) + if const.DISC_SPECIFIC_DEVICE_CLASS in schema and not eq_or_in( + node.node_specific, schema[const.DISC_SPECIFIC_DEVICE_CLASS] ): return False return True @@ -57,20 +53,18 @@ def check_value_schema(value, schema): and value.parent.command_class_id not in schema[const.DISC_COMMAND_CLASS] ): return False - if const.DISC_TYPE in schema and value.type not in ensure_tuple( - schema[const.DISC_TYPE] - ): + if const.DISC_TYPE in schema and not eq_or_in(value.type, schema[const.DISC_TYPE]): return False - if const.DISC_GENRE in schema and value.genre not in ensure_tuple( - schema[const.DISC_GENRE] + if const.DISC_GENRE in schema and not eq_or_in( + value.genre, schema[const.DISC_GENRE] ): return False - if const.DISC_INDEX in schema and value.index not in ensure_tuple( - schema[const.DISC_INDEX] + if const.DISC_INDEX in schema and not eq_or_in( + value.index, schema[const.DISC_INDEX] ): return False - if const.DISC_INSTANCE in schema and value.instance not in ensure_tuple( - schema[const.DISC_INSTANCE] + if const.DISC_INSTANCE in schema and not eq_or_in( + value.instance, schema[const.DISC_INSTANCE] ): return False if const.DISC_SCHEMAS in schema: @@ -83,8 +77,6 @@ def check_value_schema(value, schema): return True -def ensure_tuple(value): - """Convert a value to a tuple if needed.""" - if isinstance(value, tuple): - return value - return (value,) +def eq_or_in(val, options): + """Return True if options contains value or if value is equal to options.""" + return val in options if isinstance(options, tuple) else val == options From 45c0e3914706bbac2e8a61995e1f0a40c3b490b9 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Fri, 1 May 2020 21:51:18 +0200 Subject: [PATCH 41/51] Bump python-openzwave-mqtt to 1.0.1 --- homeassistant/components/zwave_mqtt/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/zwave_mqtt/manifest.json b/homeassistant/components/zwave_mqtt/manifest.json index 3a988b0e42e73..8d067bf5c3541 100644 --- a/homeassistant/components/zwave_mqtt/manifest.json +++ b/homeassistant/components/zwave_mqtt/manifest.json @@ -4,7 +4,7 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/zwave_mqtt", "requirements": [ - "python-openzwave-mqtt==0.0.9" + "python-openzwave-mqtt==1.0.1" ], "after_dependencies": [ "mqtt" diff --git a/requirements_all.txt b/requirements_all.txt index 79154f90d540f..1f125c3e7353a 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1674,7 +1674,7 @@ python-nest==4.1.0 python-nmap==0.6.1 # homeassistant.components.zwave_mqtt -python-openzwave-mqtt==0.0.9 +python-openzwave-mqtt==1.0.1 # homeassistant.components.qbittorrent python-qbittorrent==0.4.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 84a9893137230..2a7f4df3da8a0 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -656,7 +656,7 @@ python-miio==0.5.0.1 python-nest==4.1.0 # homeassistant.components.zwave_mqtt -python-openzwave-mqtt==0.0.9 +python-openzwave-mqtt==1.0.1 # homeassistant.components.synology_dsm python-synology==0.7.3 From f8adb592345d880fe7da13f45cb62c8cc91548d7 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Fri, 1 May 2020 22:12:57 +0200 Subject: [PATCH 42/51] Fix node update --- homeassistant/components/zwave_mqtt/__init__.py | 15 +++++++++------ homeassistant/components/zwave_mqtt/entity.py | 11 ++++++++--- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/zwave_mqtt/__init__.py b/homeassistant/components/zwave_mqtt/__init__.py index 43f15a66a2bdd..2ae024d72a315 100644 --- a/homeassistant/components/zwave_mqtt/__init__.py +++ b/homeassistant/components/zwave_mqtt/__init__.py @@ -28,7 +28,12 @@ from . import const from .const import DATA_UNSUBSCRIBE, DOMAIN, PLATFORMS, TOPIC_OPENZWAVE from .discovery import DISCOVERY_SCHEMAS, check_node_schema, check_value_schema -from .entity import ZWaveDeviceEntityValues, create_device_id, create_value_id +from .entity import ( + ZWaveDeviceEntityValues, + create_device_id, + create_device_name, + create_value_id, +) from .services import ZWaveServices _LOGGER = logging.getLogger(__name__) @@ -277,17 +282,15 @@ async def async_handle_node_update(hass: HomeAssistant, node: OZWNode): """ dev_registry = await get_dev_reg(hass) # grab device in device registry attached to this node - device = dev_registry.async_get_device({(DOMAIN, node.id)}, set()) + dev_id = create_device_id(node) + device = dev_registry.async_get_device({(DOMAIN, dev_id)}, set()) if not device: return # update device in device registry with (updated) info for item in dev_registry.devices.values(): if item.id != device.id and item.via_device_id != device.id: continue - if node.meta_data.get("Name"): - dev_name = node.meta_data["Name"] - else: - dev_name = node.node_product_name + dev_name = create_device_name(node) dev_registry.async_update_device( item.id, manufacturer=node.node_manufacturer_name, diff --git a/homeassistant/components/zwave_mqtt/entity.py b/homeassistant/components/zwave_mqtt/entity.py index da9508d502c4b..fc1190aac0cb7 100644 --- a/homeassistant/components/zwave_mqtt/entity.py +++ b/homeassistant/components/zwave_mqtt/entity.py @@ -264,9 +264,14 @@ async def async_will_remove_from_hass(self) -> None: def create_device_name(node: OZWNode): """Generate sensible (short) default device name from a OZWNode.""" if node.meta_data["Name"]: - return node.meta_data["Name"] - - return node.node_product_name + dev_name = node.meta_data["Name"] + elif node.node_product_name: + dev_name = node.node_product_name + elif node.node_device_type_string: + dev_name = node.node_device_type_string + else: + dev_name = node.specific_string + return dev_name def create_device_id(node: OZWNode, node_instance: int = 1): From a7af47a2f51a70f295a41d8bd26064addfcfe3fd Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Fri, 1 May 2020 22:19:28 +0200 Subject: [PATCH 43/51] Use ozw mqtt lib for discovery constants --- homeassistant/components/zwave_mqtt/const.py | 142 ------------------ .../components/zwave_mqtt/discovery.py | 25 +-- 2 files changed, 13 insertions(+), 154 deletions(-) diff --git a/homeassistant/components/zwave_mqtt/const.py b/homeassistant/components/zwave_mqtt/const.py index 0347c2d8c627b..b1f36b2f31b14 100644 --- a/homeassistant/components/zwave_mqtt/const.py +++ b/homeassistant/components/zwave_mqtt/const.py @@ -35,148 +35,6 @@ SIGNAL_DELETE_ENTITY = f"{DOMAIN}_delete_entity" # Discovery Information -GENERIC_TYPE_WHATEVER = None # Match ALL -SPECIFIC_TYPE_WHATEVER = None # Match ALL -SPECIFIC_TYPE_NOT_USED = 0 # Available in all Generic types - -GENERIC_TYPE_AV_CONTROL_POINT = 3 -SPECIFIC_TYPE_DOORBELL = 18 -SPECIFIC_TYPE_SATELLITE_RECEIVER = 4 -SPECIFIC_TYPE_SATELLITE_RECEIVER_V2 = 17 - -GENERIC_TYPE_DISPLAY = 4 -SPECIFIC_TYPE_SIMPLE_DISPLAY = 1 - -GENERIC_TYPE_ENTRY_CONTROL = 64 -SPECIFIC_TYPE_DOOR_LOCK = 1 -SPECIFIC_TYPE_ADVANCED_DOOR_LOCK = 2 -SPECIFIC_TYPE_SECURE_KEYPAD_DOOR_LOCK = 3 -SPECIFIC_TYPE_SECURE_KEYPAD_DOOR_LOCK_DEADBOLT = 4 -SPECIFIC_TYPE_SECURE_DOOR = 5 -SPECIFIC_TYPE_SECURE_GATE = 6 -SPECIFIC_TYPE_SECURE_BARRIER_ADDON = 7 -SPECIFIC_TYPE_SECURE_BARRIER_OPEN_ONLY = 8 -SPECIFIC_TYPE_SECURE_BARRIER_CLOSE_ONLY = 9 -SPECIFIC_TYPE_SECURE_LOCKBOX = 10 -SPECIFIC_TYPE_SECURE_KEYPAD = 11 - -GENERIC_TYPE_GENERIC_CONTROLLER = 1 -SPECIFIC_TYPE_PORTABLE_CONTROLLER = 1 -SPECIFIC_TYPE_PORTABLE_SCENE_CONTROLLER = 2 -SPECIFIC_TYPE_PORTABLE_INSTALLER_TOOL = 3 -SPECIFIC_TYPE_REMOTE_CONTROL_AV = 4 -SPECIFIC_TYPE_REMOTE_CONTROL_SIMPLE = 6 - -GENERIC_TYPE_METER = 49 -SPECIFIC_TYPE_SIMPLE_METER = 1 -SPECIFIC_TYPE_ADV_ENERGY_CONTROL = 2 -SPECIFIC_TYPE_WHOLE_HOME_METER_SIMPLE = 3 - -GENERIC_TYPE_METER_PULSE = 48 - -GENERIC_TYPE_NON_INTEROPERABLE = 255 - -GENERIC_TYPE_REPEATER_SLAVE = 15 -SPECIFIC_TYPE_REPEATER_SLAVE = 1 -SPECIFIC_TYPE_VIRTUAL_NODE = 2 - -GENERIC_TYPE_SECURITY_PANEL = 23 -SPECIFIC_TYPE_ZONED_SECURITY_PANEL = 1 - -GENERIC_TYPE_SEMI_INTEROPERABLE = 80 -SPECIFIC_TYPE_ENERGY_PRODUCTION = 1 - -GENERIC_TYPE_SENSOR_ALARM = 161 -SPECIFIC_TYPE_ADV_ZENSOR_NET_ALARM_SENSOR = 5 -SPECIFIC_TYPE_ADV_ZENSOR_NET_SMOKE_SENSOR = 10 -SPECIFIC_TYPE_BASIC_ROUTING_ALARM_SENSOR = 1 -SPECIFIC_TYPE_BASIC_ROUTING_SMOKE_SENSOR = 6 -SPECIFIC_TYPE_BASIC_ZENSOR_NET_ALARM_SENSOR = 3 -SPECIFIC_TYPE_BASIC_ZENSOR_NET_SMOKE_SENSOR = 8 -SPECIFIC_TYPE_ROUTING_ALARM_SENSOR = 2 -SPECIFIC_TYPE_ROUTING_SMOKE_SENSOR = 7 -SPECIFIC_TYPE_ZENSOR_NET_ALARM_SENSOR = 4 -SPECIFIC_TYPE_ZENSOR_NET_SMOKE_SENSOR = 9 -SPECIFIC_TYPE_ALARM_SENSOR = 11 - -GENERIC_TYPE_SENSOR_BINARY = 32 -SPECIFIC_TYPE_ROUTING_SENSOR_BINARY = 1 - -GENERIC_TYPE_SENSOR_MULTILEVEL = 33 -SPECIFIC_TYPE_ROUTING_SENSOR_MULTILEVEL = 1 -SPECIFIC_TYPE_CHIMNEY_FAN = 2 - -GENERIC_TYPE_STATIC_CONTROLLER = 2 -SPECIFIC_TYPE_PC_CONTROLLER = 1 -SPECIFIC_TYPE_SCENE_CONTROLLER = 2 -SPECIFIC_TYPE_STATIC_INSTALLER_TOOL = 3 -SPECIFIC_TYPE_SET_TOP_BOX = 4 -SPECIFIC_TYPE_SUB_SYSTEM_CONTROLLER = 5 -SPECIFIC_TYPE_TV = 6 -SPECIFIC_TYPE_GATEWAY = 7 - -GENERIC_TYPE_SWITCH_BINARY = 16 -SPECIFIC_TYPE_POWER_SWITCH_BINARY = 1 -SPECIFIC_TYPE_SCENE_SWITCH_BINARY = 3 -SPECIFIC_TYPE_POWER_STRIP = 4 -SPECIFIC_TYPE_SIREN = 5 -SPECIFIC_TYPE_VALVE_OPEN_CLOSE = 6 -SPECIFIC_TYPE_COLOR_TUNABLE_BINARY = 2 -SPECIFIC_TYPE_IRRIGATION_CONTROLLER = 7 - -GENERIC_TYPE_SWITCH_MULTILEVEL = 17 -SPECIFIC_TYPE_CLASS_A_MOTOR_CONTROL = 5 -SPECIFIC_TYPE_CLASS_B_MOTOR_CONTROL = 6 -SPECIFIC_TYPE_CLASS_C_MOTOR_CONTROL = 7 -SPECIFIC_TYPE_MOTOR_MULTIPOSITION = 3 -SPECIFIC_TYPE_POWER_SWITCH_MULTILEVEL = 1 -SPECIFIC_TYPE_SCENE_SWITCH_MULTILEVEL = 4 -SPECIFIC_TYPE_FAN_SWITCH = 8 -SPECIFIC_TYPE_COLOR_TUNABLE_MULTILEVEL = 2 - -GENERIC_TYPE_SWITCH_REMOTE = 18 -SPECIFIC_TYPE_REMOTE_BINARY = 1 -SPECIFIC_TYPE_REMOTE_MULTILEVEL = 2 -SPECIFIC_TYPE_REMOTE_TOGGLE_BINARY = 3 -SPECIFIC_TYPE_REMOTE_TOGGLE_MULTILEVEL = 4 - -GENERIC_TYPE_SWITCH_TOGGLE = 19 -SPECIFIC_TYPE_SWITCH_TOGGLE_BINARY = 1 -SPECIFIC_TYPE_SWITCH_TOGGLE_MULTILEVEL = 2 - -GENERIC_TYPE_THERMOSTAT = 8 -SPECIFIC_TYPE_SETBACK_SCHEDULE_THERMOSTAT = 3 -SPECIFIC_TYPE_SETBACK_THERMOSTAT = 5 -SPECIFIC_TYPE_SETPOINT_THERMOSTAT = 4 -SPECIFIC_TYPE_THERMOSTAT_GENERAL = 2 -SPECIFIC_TYPE_THERMOSTAT_GENERAL_V2 = 6 -SPECIFIC_TYPE_THERMOSTAT_HEATING = 1 - -GENERIC_TYPE_VENTILATION = 22 -SPECIFIC_TYPE_RESIDENTIAL_HRV = 1 - -GENERIC_TYPE_WINDOWS_COVERING = 9 -SPECIFIC_TYPE_SIMPLE_WINDOW_COVERING = 1 - -GENERIC_TYPE_ZIP_NODE = 21 -SPECIFIC_TYPE_ZIP_ADV_NODE = 2 -SPECIFIC_TYPE_ZIP_TUN_NODE = 1 - -GENERIC_TYPE_WALL_CONTROLLER = 24 -SPECIFIC_TYPE_BASIC_WALL_CONTROLLER = 1 - -GENERIC_TYPE_NETWORK_EXTENDER = 5 -SPECIFIC_TYPE_SECURE_EXTENDER = 1 - -GENERIC_TYPE_APPLIANCE = 6 -SPECIFIC_TYPE_GENERAL_APPLIANCE = 1 -SPECIFIC_TYPE_KITCHEN_APPLIANCE = 2 -SPECIFIC_TYPE_LAUNDRY_APPLIANCE = 3 - -GENERIC_TYPE_SENSOR_NOTIFICATION = 7 -SPECIFIC_TYPE_NOTIFICATION_SENSOR = 1 - - DISC_COMMAND_CLASS = "command_class" DISC_COMPONENT = "component" DISC_GENERIC_DEVICE_CLASS = "generic_device_class" diff --git a/homeassistant/components/zwave_mqtt/discovery.py b/homeassistant/components/zwave_mqtt/discovery.py index 0aa68eefb46a2..e257e43b678a3 100644 --- a/homeassistant/components/zwave_mqtt/discovery.py +++ b/homeassistant/components/zwave_mqtt/discovery.py @@ -1,4 +1,5 @@ """Map Z-Wave nodes and values to Home Assistant entities.""" +import openzwavemqtt.const as const_ozw from openzwavemqtt.const import CommandClass, ValueGenre, ValueType from . import const @@ -7,18 +8,18 @@ { # Switch platform const.DISC_COMPONENT: "switch", const.DISC_GENERIC_DEVICE_CLASS: ( - const.GENERIC_TYPE_METER, - 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_GENERIC_CONTROLLER, - const.GENERIC_TYPE_SWITCH_REMOTE, - const.GENERIC_TYPE_REPEATER_SLAVE, - const.GENERIC_TYPE_THERMOSTAT, - const.GENERIC_TYPE_WALL_CONTROLLER, + const_ozw.GENERIC_TYPE_METER, + const_ozw.GENERIC_TYPE_SENSOR_ALARM, + const_ozw.GENERIC_TYPE_SENSOR_BINARY, + const_ozw.GENERIC_TYPE_SWITCH_BINARY, + const_ozw.GENERIC_TYPE_ENTRY_CONTROL, + const_ozw.GENERIC_TYPE_SENSOR_MULTILEVEL, + const_ozw.GENERIC_TYPE_SWITCH_MULTILEVEL, + const_ozw.GENERIC_TYPE_GENERIC_CONTROLLER, + const_ozw.GENERIC_TYPE_SWITCH_REMOTE, + const_ozw.GENERIC_TYPE_REPEATER_SLAVE, + const_ozw.GENERIC_TYPE_THERMOSTAT, + const_ozw.GENERIC_TYPE_WALL_CONTROLLER, ), const.DISC_VALUES: { const.DISC_PRIMARY: { From 45832a010dadb45522fa9d54f47d62c76d351c07 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Fri, 1 May 2020 23:13:59 +0200 Subject: [PATCH 44/51] Clean and fix tests --- tests/components/zwave_mqtt/common.py | 19 +++---------------- tests/components/zwave_mqtt/test_init.py | 4 ---- tests/components/zwave_mqtt/test_scenes.py | 4 +++- 3 files changed, 6 insertions(+), 21 deletions(-) diff --git a/tests/components/zwave_mqtt/common.py b/tests/components/zwave_mqtt/common.py index 4fa8290d4f790..ef85d2e5533f2 100644 --- a/tests/components/zwave_mqtt/common.py +++ b/tests/components/zwave_mqtt/common.py @@ -1,11 +1,10 @@ """Helpers for tests.""" import json -from tests.async_mock import Mock, patch - -from homeassistant import config_entries, core as ha +from homeassistant import config_entries from homeassistant.components.zwave_mqtt.const import DOMAIN +from tests.async_mock import Mock, patch from tests.common import MockConfigEntry @@ -23,6 +22,7 @@ async def setup_zwave(hass, entry=None, fixture=None): entry.add_to_hass(hass) with patch("homeassistant.components.mqtt.async_subscribe") as mock_subscribe: + mock_subscribe.return_value = Mock() assert await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() @@ -40,19 +40,6 @@ async def setup_zwave(hass, entry=None, fixture=None): return receive_message -def async_capture_events(hass, event_name): - """Create a helper that captures events.""" - events = [] - - @ha.callback - def capture_events(event): - events.append(event) - - hass.bus.async_listen(event_name, capture_events) - - return events - - class MQTTMessage: """Represent a mock MQTT message.""" diff --git a/tests/components/zwave_mqtt/test_init.py b/tests/components/zwave_mqtt/test_init.py index 67ad61452e81f..0b77905ab9be2 100644 --- a/tests/components/zwave_mqtt/test_init.py +++ b/tests/components/zwave_mqtt/test_init.py @@ -20,10 +20,6 @@ async def test_init_entry(hass, generic_data): # Verify services registered assert hass.services.has_service(DOMAIN, const.SERVICE_ADD_NODE) assert hass.services.has_service(DOMAIN, const.SERVICE_REMOVE_NODE) - assert hass.services.has_service(DOMAIN, const.SERVICE_REMOVE_FAILED_NODE) - assert hass.services.has_service(DOMAIN, const.SERVICE_REPLACE_FAILED_NODE) - assert hass.services.has_service(DOMAIN, const.SERVICE_CANCEL_COMMAND) - assert hass.services.has_service(DOMAIN, const.SERVICE_SET_CONFIG_PARAMETER) async def test_unload_entry(hass, generic_data, switch_msg, caplog): diff --git a/tests/components/zwave_mqtt/test_scenes.py b/tests/components/zwave_mqtt/test_scenes.py index 55231ae1cf456..38609bea98105 100644 --- a/tests/components/zwave_mqtt/test_scenes.py +++ b/tests/components/zwave_mqtt/test_scenes.py @@ -3,7 +3,9 @@ import json from unittest.mock import Mock -from .common import async_capture_events, setup_zwave +from .common import setup_zwave + +from tests.common import async_capture_events async def test_scenes(hass, generic_data, sent_messages): From 94f64887366cf9be6ff20fb85c4f67b05dc4d228 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Fri, 1 May 2020 23:18:31 +0200 Subject: [PATCH 45/51] Scope generic data fixture per session --- tests/components/zwave_mqtt/conftest.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/tests/components/zwave_mqtt/conftest.py b/tests/components/zwave_mqtt/conftest.py index 72a2899ad68f7..447348a4b6d4b 100644 --- a/tests/components/zwave_mqtt/conftest.py +++ b/tests/components/zwave_mqtt/conftest.py @@ -1,20 +1,18 @@ """Helpers for tests.""" import json -from tests.async_mock import patch import pytest from .common import MQTTMessage +from tests.async_mock import patch from tests.common import load_fixture -@pytest.fixture(name="generic_data") -async def generic_data_fixture(hass): +@pytest.fixture(name="generic_data", scope="session") +def generic_data_fixture(): """Load generic MQTT data and return it.""" - return await hass.async_add_executor_job( - load_fixture, f"zwave_mqtt/generic_network_dump.csv" - ) + return load_fixture(f"zwave_mqtt/generic_network_dump.csv") @pytest.fixture(name="sent_messages") From d01df2284a376e36dfc61f2d253b99f222c83ade Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Fri, 1 May 2020 23:25:26 +0200 Subject: [PATCH 46/51] Clean up mock mqtt message --- tests/components/zwave_mqtt/test_scenes.py | 130 ++++++++++----------- 1 file changed, 62 insertions(+), 68 deletions(-) diff --git a/tests/components/zwave_mqtt/test_scenes.py b/tests/components/zwave_mqtt/test_scenes.py index 38609bea98105..1af8d0fcd7160 100644 --- a/tests/components/zwave_mqtt/test_scenes.py +++ b/tests/components/zwave_mqtt/test_scenes.py @@ -1,9 +1,7 @@ """Test Z-Wave (central) Scenes.""" import asyncio -import json -from unittest.mock import Mock -from .common import setup_zwave +from .common import MQTTMessage, setup_zwave from tests.common import async_capture_events @@ -15,35 +13,33 @@ async def test_scenes(hass, generic_data, sent_messages): events = async_capture_events(hass, "zwave_mqtt.scene_activated") # Publish fake scene event on mqtt - receive_message( - Mock( - topic="OpenZWave/1/node/39/instance/1/commandclass/43/value/562950622511127/", - payload=json.dumps( - { - "Label": "Scene", - "Value": 16, - "Units": "", - "Min": -2147483648, - "Max": 2147483647, - "Type": "Int", - "Instance": 1, - "CommandClass": "COMMAND_CLASS_SCENE_ACTIVATION", - "Index": 0, - "Node": 7, - "Genre": "User", - "Help": "", - "ValueIDKey": 122339347, - "ReadOnly": False, - "WriteOnly": False, - "ValueSet": False, - "ValuePolled": False, - "ChangeVerified": False, - "Event": "valueChanged", - "TimeStamp": 1579630367, - } - ), - ) + message = MQTTMessage( + topic="OpenZWave/1/node/39/instance/1/commandclass/43/value/562950622511127/", + payload={ + "Label": "Scene", + "Value": 16, + "Units": "", + "Min": -2147483648, + "Max": 2147483647, + "Type": "Int", + "Instance": 1, + "CommandClass": "COMMAND_CLASS_SCENE_ACTIVATION", + "Index": 0, + "Node": 7, + "Genre": "User", + "Help": "", + "ValueIDKey": 122339347, + "ReadOnly": False, + "WriteOnly": False, + "ValueSet": False, + "ValuePolled": False, + "ChangeVerified": False, + "Event": "valueChanged", + "TimeStamp": 1579630367, + }, ) + message.encode() + receive_message(message) # wait for the event count = 0 while count < 5 and not events: @@ -53,44 +49,42 @@ async def test_scenes(hass, generic_data, sent_messages): assert events[0].data["scene_value_id"] == 16 # Publish fake central scene event on mqtt - receive_message( - Mock( - topic="OpenZWave/1/node/39/instance/1/commandclass/91/value/281476005806100/", - payload=json.dumps( - { - "Label": "Scene 1", - "Value": { - "List": [ - {"Value": 0, "Label": "Inactive"}, - {"Value": 1, "Label": "Pressed 1 Time"}, - {"Value": 2, "Label": "Key Released"}, - {"Value": 3, "Label": "Key Held down"}, - ], - "Selected": "Pressed 1 Time", - "Selected_id": 1, - }, - "Units": "", - "Min": 0, - "Max": 0, - "Type": "List", - "Instance": 1, - "CommandClass": "COMMAND_CLASS_CENTRAL_SCENE", - "Index": 1, - "Node": 61, - "Genre": "User", - "Help": "", - "ValueIDKey": 281476005806100, - "ReadOnly": False, - "WriteOnly": False, - "ValueSet": False, - "ValuePolled": False, - "ChangeVerified": False, - "Event": "valueChanged", - "TimeStamp": 1579640710, - } - ), - ) + message = MQTTMessage( + topic="OpenZWave/1/node/39/instance/1/commandclass/91/value/281476005806100/", + payload={ + "Label": "Scene 1", + "Value": { + "List": [ + {"Value": 0, "Label": "Inactive"}, + {"Value": 1, "Label": "Pressed 1 Time"}, + {"Value": 2, "Label": "Key Released"}, + {"Value": 3, "Label": "Key Held down"}, + ], + "Selected": "Pressed 1 Time", + "Selected_id": 1, + }, + "Units": "", + "Min": 0, + "Max": 0, + "Type": "List", + "Instance": 1, + "CommandClass": "COMMAND_CLASS_CENTRAL_SCENE", + "Index": 1, + "Node": 61, + "Genre": "User", + "Help": "", + "ValueIDKey": 281476005806100, + "ReadOnly": False, + "WriteOnly": False, + "ValueSet": False, + "ValuePolled": False, + "ChangeVerified": False, + "Event": "valueChanged", + "TimeStamp": 1579640710, + }, ) + message.encode() + receive_message(message) # wait for the event count = 0 while count < 5 and len(events) != 2: From 28614e66ed5d8ec81425ed882d7351e8908cc454 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Fri, 1 May 2020 23:26:35 +0200 Subject: [PATCH 47/51] Sort imports --- tests/components/zwave_mqtt/test_config_flow.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/components/zwave_mqtt/test_config_flow.py b/tests/components/zwave_mqtt/test_config_flow.py index 8d79032ef7672..fbdf4012009fb 100644 --- a/tests/components/zwave_mqtt/test_config_flow.py +++ b/tests/components/zwave_mqtt/test_config_flow.py @@ -1,10 +1,9 @@ """Test the Z-Wave over MQTT config flow.""" -from tests.async_mock import patch - from homeassistant import config_entries, setup from homeassistant.components.zwave_mqtt.config_flow import TITLE from homeassistant.components.zwave_mqtt.const import DOMAIN +from tests.async_mock import patch from tests.common import MockConfigEntry From d6b564a3ebda6c8f358497163836d66df49500d4 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Fri, 1 May 2020 23:28:46 +0200 Subject: [PATCH 48/51] Use block till done --- tests/components/zwave_mqtt/test_scenes.py | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/tests/components/zwave_mqtt/test_scenes.py b/tests/components/zwave_mqtt/test_scenes.py index 1af8d0fcd7160..10e1f94b229be 100644 --- a/tests/components/zwave_mqtt/test_scenes.py +++ b/tests/components/zwave_mqtt/test_scenes.py @@ -1,6 +1,4 @@ """Test Z-Wave (central) Scenes.""" -import asyncio - from .common import MQTTMessage, setup_zwave from tests.common import async_capture_events @@ -41,10 +39,7 @@ async def test_scenes(hass, generic_data, sent_messages): message.encode() receive_message(message) # wait for the event - count = 0 - while count < 5 and not events: - await asyncio.sleep(0.0) - count += 1 + await hass.async_block_till_done() assert len(events) == 1 assert events[0].data["scene_value_id"] == 16 @@ -86,10 +81,7 @@ async def test_scenes(hass, generic_data, sent_messages): message.encode() receive_message(message) # wait for the event - count = 0 - while count < 5 and len(events) != 2: - await asyncio.sleep(0.0) - count += 1 + await hass.async_block_till_done() assert len(events) == 2 assert events[1].data["scene_id"] == 1 assert events[1].data["scene_label"] == "Scene 1" From 4023d74a9f2c172d86747abeef3c6515109ce5c7 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Fri, 1 May 2020 23:46:12 +0200 Subject: [PATCH 49/51] Clean wait for platforms --- .../components/zwave_mqtt/__init__.py | 49 ++++++++----------- homeassistant/components/zwave_mqtt/switch.py | 4 -- 2 files changed, 21 insertions(+), 32 deletions(-) diff --git a/homeassistant/components/zwave_mqtt/__init__.py b/homeassistant/components/zwave_mqtt/__init__.py index 2ae024d72a315..8b12457475059 100644 --- a/homeassistant/components/zwave_mqtt/__init__.py +++ b/homeassistant/components/zwave_mqtt/__init__.py @@ -53,29 +53,8 @@ async def async_setup(hass: HomeAssistant, config: dict): async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): """Set up zwave_mqtt from a config entry.""" - - @callback - def async_receive_message(msg): - manager.receive_message(msg.topic, msg.payload) - - platforms_loaded = [] - - async def mark_platform_loaded(platform): - platforms_loaded.append(platform) - - if len(platforms_loaded) != len(PLATFORMS): - return - - hass.data[DOMAIN][entry.entry_id][DATA_UNSUBSCRIBE].append( - await mqtt.async_subscribe( - hass, f"{TOPIC_OPENZWAVE}/#", async_receive_message - ) - ) - - hass.data[DOMAIN][entry.entry_id] = { - "mark_platform_loaded": mark_platform_loaded, - DATA_UNSUBSCRIBE: [], - } + zwave_mqtt_data = hass.data[DOMAIN][entry.entry_id] = {} + zwave_mqtt_data[DATA_UNSUBSCRIBE] = [] data_nodes = {} data_values = {} @@ -88,11 +67,6 @@ def send_message(topic, payload): options = OZWOptions(send_message=send_message, topic_prefix=f"{TOPIC_OPENZWAVE}/") manager = OZWManager(options) - for component in PLATFORMS: - hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) - ) - @callback def async_node_added(node): # Caution: This is also called on (re)start. @@ -229,6 +203,25 @@ def async_value_removed(value): services = ZWaveServices(hass, manager) services.async_register() + @callback + def async_receive_message(msg): + manager.receive_message(msg.topic, msg.payload) + + async def start_platforms(): + await asyncio.gather( + *[ + hass.config_entries.async_forward_entry_setup(entry, component) + for component in PLATFORMS + ] + ) + zwave_mqtt_data[DATA_UNSUBSCRIBE].append( + await mqtt.async_subscribe( + hass, f"{TOPIC_OPENZWAVE}/#", async_receive_message + ) + ) + + hass.async_create_task(start_platforms()) + return True diff --git a/homeassistant/components/zwave_mqtt/switch.py b/homeassistant/components/zwave_mqtt/switch.py index ca61253e4a280..c1a0ef353b880 100644 --- a/homeassistant/components/zwave_mqtt/switch.py +++ b/homeassistant/components/zwave_mqtt/switch.py @@ -23,10 +23,6 @@ def async_add_switch(value): ) ) - await hass.data[DOMAIN][config_entry.entry_id]["mark_platform_loaded"]( - SWITCH_DOMAIN - ) - class ZWaveSwitch(ZWaveDeviceEntity, SwitchEntity): """Representation of a Z-Wave switch.""" From 2f4a69c721bb522067c59d45e29068f66dc68672 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Sat, 2 May 2020 00:07:10 +0200 Subject: [PATCH 50/51] Use ozw mqtt ready states constant --- homeassistant/components/zwave_mqtt/entity.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/zwave_mqtt/entity.py b/homeassistant/components/zwave_mqtt/entity.py index fc1190aac0cb7..0f23a573eed68 100644 --- a/homeassistant/components/zwave_mqtt/entity.py +++ b/homeassistant/components/zwave_mqtt/entity.py @@ -3,7 +3,11 @@ import copy import logging -from openzwavemqtt.const import EVENT_INSTANCE_STATUS_CHANGED, EVENT_VALUE_CHANGED +from openzwavemqtt.const import ( + EVENT_INSTANCE_STATUS_CHANGED, + EVENT_VALUE_CHANGED, + OZW_READY_STATES, +) from openzwavemqtt.models.node import OZWNode from openzwavemqtt.models.value import OZWValue @@ -212,11 +216,9 @@ def available(self) -> bool: """Return entity availability.""" # Use OZW Daemon status for availability. instance_status = self.values.primary.ozw_instance.get_status() - return instance_status and instance_status.status in [ - "driverAllNodesQueriedSomeDead", - "driverAllNodesQueried", - "driverAwakeNodesQueried", - ] + return instance_status and instance_status.status in ( + state.value for state in OZW_READY_STATES + ) @callback def _value_changed(self, value): From 5db5be07fd45c8ad02275b183cb0294ad919b19f Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Sat, 2 May 2020 00:59:19 +0200 Subject: [PATCH 51/51] Clean up services --- homeassistant/components/zwave_mqtt/const.py | 7 ---- .../components/zwave_mqtt/services.yaml | 34 ------------------- 2 files changed, 41 deletions(-) diff --git a/homeassistant/components/zwave_mqtt/const.py b/homeassistant/components/zwave_mqtt/const.py index b1f36b2f31b14..4391d53ac4c31 100644 --- a/homeassistant/components/zwave_mqtt/const.py +++ b/homeassistant/components/zwave_mqtt/const.py @@ -11,9 +11,6 @@ # Common Attributes ATTR_INSTANCE_ID = "instance_id" ATTR_SECURE = "secure" -ATTR_CONFIG_PARAMETER = "parameter" -ATTR_CONFIG_VALUE = "value" -ATTR_CONFIG_SIZE = "size" ATTR_NODE_ID = "node_id" ATTR_SCENE_ID = "scene_id" ATTR_SCENE_LABEL = "scene_label" @@ -23,10 +20,6 @@ # Service specific SERVICE_ADD_NODE = "add_node" SERVICE_REMOVE_NODE = "remove_node" -SERVICE_REMOVE_FAILED_NODE = "remove_failed_node" -SERVICE_REPLACE_FAILED_NODE = "replace_failed_node" -SERVICE_CANCEL_COMMAND = "cancel_command" -SERVICE_SET_CONFIG_PARAMETER = "set_config_parameter" # Home Assistant Events EVENT_SCENE_ACTIVATED = f"{DOMAIN}.scene_activated" diff --git a/homeassistant/components/zwave_mqtt/services.yaml b/homeassistant/components/zwave_mqtt/services.yaml index 35b59d879fc45..92685f1a46308 100644 --- a/homeassistant/components/zwave_mqtt/services.yaml +++ b/homeassistant/components/zwave_mqtt/services.yaml @@ -7,42 +7,8 @@ add_node: instance_id: description: (Optional) The OZW Instance/Controller to use, defaults to 1. -cancel_command: - description: Cancel a running Z-Wave controller command. Use this to exit add_node, if you weren't going to use it but activated it. - fields: - instance_id: - description: (Optional) The OZW Instance/Controller to use, defaults to 1. - remove_node: description: Remove a node from the Z-Wave network. Will set the controller into exclusion mode. fields: instance_id: description: (Optional) The OZW Instance/Controller to use, defaults to 1. - -remove_failed_node: - description: This command will remove a failed node from the network. The node should be on the controller's failed nodes list, otherwise this command will fail. Refer to OZW_Log.txt for progress. - fields: - node_id: - description: Node id of the device to remove (integer). - example: 10 - instance_id: - description: (Optional) The OZW Instance/Controller to use, defaults to 1. - -replace_failed_node: - description: Replace a failed node with another. If the node is not in the controller's failed nodes list, or the node responds, this command will fail. Refer to OZW_Log.txt for progress. - fields: - node_id: - description: Node id of the device to replace (integer). - example: 10 - instance_id: - description: (Optional) The OZW Instance/Controller to use, defaults to 1. - -set_config_parameter: - description: Set a config parameter to a node on the Z-Wave network. - fields: - node_id: - description: Node id of the device to set config parameter to (integer). - parameter: - description: Parameter index to set (integer). - value: - description: Value to set for parameter. (String value for list and bool parameters, integer for others).