From 6011cee49072c35f59be98ce0f25560966500c24 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sat, 21 Feb 2015 16:59:17 -0800 Subject: [PATCH 1/7] Get python-openzwave working in Docker --- Dockerfile | 20 ++++++++++++++++++++ scripts/dev_docker | 6 +++++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 1c13ac9542e..001c2cbcf87 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,4 +3,24 @@ MAINTAINER Paulus Schoutsen VOLUME /config +RUN apt-get update && \ + apt-get install -y cython3 libudev-dev python-sphinx python3-setuptools mercurial && \ + apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* && \ + pip3 install cython && \ + cd .. && \ + git clone https://github.com/Artanis/louie.git && \ + cd louie && \ + python setup.py install && \ + cd .. && \ + hg clone https://code.google.com/p/python-openzwave/ && \ + cd python-openzwave && \ + ./update.sh && \ + sed -i '253s/.*//' openzwave/cpp/src/value_classes/ValueID.h && \ + 2to3 --no-diffs -w -n api examples && \ + ./compile.sh && \ + ./install.sh + +# L18 sed is to apply a patch to make openzwave compile +# L19 2to3 to have the api code work in Python 3 + CMD [ "python", "-m", "homeassistant", "--config", "/config" ] diff --git a/scripts/dev_docker b/scripts/dev_docker index 52e839a270f..da13662d3b6 100755 --- a/scripts/dev_docker +++ b/scripts/dev_docker @@ -8,14 +8,18 @@ fi docker build -t home-assistant-dev . +# TODO set device via command line, remove /bin/bash + if [ $# -gt 0 ] then docker run \ --net=host \ -e "TZ=$1" \ + --device=/dev/bus/usb/002:/usbstick:rwm \ -v `pwd`:/usr/src/app \ -v `pwd`/config:/config \ - -t -i home-assistant-dev + -t -i home-assistant-dev \ + /bin/bash else docker run \ From 3239c04368ffb1408c5652c2a7cab74c6afffe5d Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sat, 21 Feb 2015 23:21:44 -0800 Subject: [PATCH 2/7] Working zwave! Open docker, go to /usr/src/balloob-python-openzwave/examples, run python3 api_demo.py --device=/zwaveusbstick --log=Debug --- Dockerfile | 5 ++--- scripts/dev_docker | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/Dockerfile b/Dockerfile index 001c2cbcf87..6d34e1e4506 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,11 +12,10 @@ RUN apt-get update && \ cd louie && \ python setup.py install && \ cd .. && \ - hg clone https://code.google.com/p/python-openzwave/ && \ - cd python-openzwave && \ + hg clone https://code.google.com/r/balloob-python-openzwave/ && \ + cd balloob-python-openzwave && \ ./update.sh && \ sed -i '253s/.*//' openzwave/cpp/src/value_classes/ValueID.h && \ - 2to3 --no-diffs -w -n api examples && \ ./compile.sh && \ ./install.sh diff --git a/scripts/dev_docker b/scripts/dev_docker index da13662d3b6..26cdc7beed9 100755 --- a/scripts/dev_docker +++ b/scripts/dev_docker @@ -14,8 +14,8 @@ if [ $# -gt 0 ] then docker run \ --net=host \ + --device=/dev/ttyUSB0:/zwaveusbstick:rwm \ -e "TZ=$1" \ - --device=/dev/bus/usb/002:/usbstick:rwm \ -v `pwd`:/usr/src/app \ -v `pwd`/config:/config \ -t -i home-assistant-dev \ From 7f7a1f2740468e9dc7050a7d8b5fb1288686c1b3 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 22 Feb 2015 17:33:01 -0800 Subject: [PATCH 3/7] Minor documentation updates --- homeassistant/components/wink.py | 2 +- homeassistant/const.py | 2 -- scripts/build_frontend | 1 + scripts/build_js | 2 ++ scripts/check_style | 2 ++ scripts/dev_js | 2 ++ 6 files changed, 8 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/wink.py b/homeassistant/components/wink.py index 20b3e0b75f9..643af935f94 100644 --- a/homeassistant/components/wink.py +++ b/homeassistant/components/wink.py @@ -92,7 +92,7 @@ class WinkSensorDevice(Device): class WinkToggleDevice(ToggleDevice): - """ represents a WeMo switch within home assistant. """ + """ represents a Wink switch within home assistant. """ def __init__(self, wink): self.wink = wink diff --git a/homeassistant/const.py b/homeassistant/const.py index d64033f4ba3..8b8372aaf61 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -65,8 +65,6 @@ ATTR_UNIT_OF_MEASUREMENT = "unit_of_measurement" # Temperature attribute ATTR_TEMPERATURE = "temperature" - -# #### MISC #### TEMP_CELCIUS = "°C" TEMP_FAHRENHEIT = "°F" diff --git a/scripts/build_frontend b/scripts/build_frontend index 97d5165b8e5..406aed45f46 100755 --- a/scripts/build_frontend +++ b/scripts/build_frontend @@ -1,3 +1,4 @@ +# Builds the frontend for production # Call 'build_frontend demo' to build a demo frontend. # If current pwd is scripts, go 1 up. diff --git a/scripts/build_js b/scripts/build_js index 792f82516a2..a75b48f9bf9 100755 --- a/scripts/build_js +++ b/scripts/build_js @@ -1,3 +1,5 @@ +# Builds the JS for production + # If current pwd is scripts, go 1 up. if [ ${PWD##*/} == "scripts" ]; then cd .. diff --git a/scripts/check_style b/scripts/check_style index d7dbae02baf..cacebba15a1 100755 --- a/scripts/check_style +++ b/scripts/check_style @@ -1,3 +1,5 @@ +# Run style checks + # If current pwd is scripts, go 1 up. if [ ${PWD##*/} == "scripts" ]; then cd .. diff --git a/scripts/dev_js b/scripts/dev_js index cadc794a483..a62f0d85ef3 100755 --- a/scripts/dev_js +++ b/scripts/dev_js @@ -1,3 +1,5 @@ +# Builds the JS for developing, rebuilds when files change + # If current pwd is scripts, go 1 up. if [ ${PWD##*/} == "scripts" ]; then cd .. From a013ccf806267c0079e8fe5b9d9fe4197e7ee362 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 22 Feb 2015 17:36:28 -0800 Subject: [PATCH 4/7] Get Z-Wave sensors to work with Home Assistant --- Dockerfile | 15 +- homeassistant/components/sensor/__init__.py | 31 +--- homeassistant/components/sensor/zwave.py | 154 ++++++++++++++++++++ homeassistant/components/zwave.py | 97 ++++++++++++ homeassistant/const.py | 7 + scripts/build_python_openzwave | 28 ++++ scripts/dev_docker | 7 +- scripts/dev_openzwave_docker | 18 +++ 8 files changed, 314 insertions(+), 43 deletions(-) create mode 100644 homeassistant/components/sensor/zwave.py create mode 100644 homeassistant/components/zwave.py create mode 100755 scripts/build_python_openzwave create mode 100755 scripts/dev_openzwave_docker diff --git a/Dockerfile b/Dockerfile index 6d34e1e4506..a78322bbd80 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,19 +7,6 @@ RUN apt-get update && \ apt-get install -y cython3 libudev-dev python-sphinx python3-setuptools mercurial && \ apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* && \ pip3 install cython && \ - cd .. && \ - git clone https://github.com/Artanis/louie.git && \ - cd louie && \ - python setup.py install && \ - cd .. && \ - hg clone https://code.google.com/r/balloob-python-openzwave/ && \ - cd balloob-python-openzwave && \ - ./update.sh && \ - sed -i '253s/.*//' openzwave/cpp/src/value_classes/ValueID.h && \ - ./compile.sh && \ - ./install.sh - -# L18 sed is to apply a patch to make openzwave compile -# L19 2to3 to have the api code work in Python 3 + scripts/build_python_openzwave CMD [ "python", "-m", "homeassistant", "--config", "/config" ] diff --git a/homeassistant/components/sensor/__init__.py b/homeassistant/components/sensor/__init__.py index daf0945e289..59061aa812b 100644 --- a/homeassistant/components/sensor/__init__.py +++ b/homeassistant/components/sensor/__init__.py @@ -8,19 +8,13 @@ from datetime import timedelta from homeassistant.loader import get_component import homeassistant.util as util -from homeassistant.const import ( - STATE_OPEN) from homeassistant.helpers import ( - platform_devices_from_config) -from homeassistant.components import group, discovery, wink + platform_devices_from_config, generate_entity_id) +from homeassistant.components import discovery, wink, zwave DOMAIN = 'sensor' DEPENDENCIES = [] -GROUP_NAME_ALL_SENSORS = 'all_sensors' -ENTITY_ID_ALL_SENSORS = group.ENTITY_ID_FORMAT.format( - GROUP_NAME_ALL_SENSORS) - ENTITY_ID_FORMAT = DOMAIN + '.{}' MIN_TIME_BETWEEN_SCANS = timedelta(seconds=1) @@ -28,18 +22,12 @@ MIN_TIME_BETWEEN_SCANS = timedelta(seconds=1) # Maps discovered services to their platforms DISCOVERY_PLATFORMS = { wink.DISCOVER_SENSORS: 'wink', + zwave.DISCOVER_SENSORS: 'zwave', } _LOGGER = logging.getLogger(__name__) -def is_on(hass, entity_id=None): - """ Returns if the sensor is open based on the statemachine. """ - entity_id = entity_id or ENTITY_ID_ALL_SENSORS - - return hass.states.is_state(entity_id, STATE_OPEN) - - def setup(hass, config): """ Track states and offer events for sensors. """ logger = logging.getLogger(__name__) @@ -58,10 +46,6 @@ def setup(hass, config): update_sensor_states(None) - # Track all sensors in a group - sensor_group = group.Group( - hass, GROUP_NAME_ALL_SENSORS, sensors.keys(), False) - def sensor_discovered(service, info): """ Called when a sensor is discovered. """ platform = get_component("{}.{}".format( @@ -71,19 +55,16 @@ def setup(hass, config): for sensor in discovered: if sensor is not None and sensor not in sensors.values(): - sensor.entity_id = util.ensure_unique_string( - ENTITY_ID_FORMAT.format(util.slugify(sensor.name)), - sensors.keys()) + sensor.entity_id = generate_entity_id( + ENTITY_ID_FORMAT, sensor.name, sensors.keys()) sensors[sensor.entity_id] = sensor sensor.update_ha_state(hass) - sensor_group.update_tracked_entity_ids(sensors.keys()) - discovery.listen(hass, DISCOVERY_PLATFORMS.keys(), sensor_discovered) # Fire every 3 seconds - hass.track_time_change(update_sensor_states, seconds=range(0, 60, 3)) + hass.track_time_change(update_sensor_states, second=range(0, 60, 3)) return True diff --git a/homeassistant/components/sensor/zwave.py b/homeassistant/components/sensor/zwave.py new file mode 100644 index 00000000000..82ba8d31e39 --- /dev/null +++ b/homeassistant/components/sensor/zwave.py @@ -0,0 +1,154 @@ +import homeassistant.components.zwave as zwave +from homeassistant.helpers import Device +from homeassistant.const import ( + ATTR_FRIENDLY_NAME, ATTR_BATTERY_LEVEL, ATTR_UNIT_OF_MEASUREMENT, + TEMP_CELCIUS, TEMP_FAHRENHEIT, LIGHT_LUX, ATTR_LOCATION) + + +def devices_discovered(hass, config, info): + """ """ + from louie import connect + from openzwave.network import ZWaveNetwork + + VALUE_CLASS_MAP = { + zwave.VALUE_TEMPERATURE: ZWaveTemperatureSensor, + zwave.VALUE_LUMINANCE: ZWaveLuminanceSensor, + zwave.VALUE_RELATIVE_HUMIDITY: ZWaveRelativeHumiditySensor, + } + + sensors = [] + + for node in zwave.NETWORK.nodes.values(): + for value, klass in VALUE_CLASS_MAP.items(): + if value in node.values: + sensors.append(klass(node)) + + if sensors[-1] is None: + print("") + print("WTF BBQ") + print(node, klass) + print("") + continue + + def value_changed(network, node, value): + """ """ + print("") + print("") + print("") + print("ValueChanged in sensor !!", node, value) + print("") + print("") + print("") + + # triggered when sensors have updated + connect(value_changed, ZWaveNetwork.SIGNAL_VALUE_CHANGED, weak=False) + + return sensors + + +class ZWaveSensor(Device): + def __init__(self, node, sensor_value): + self._node = node + self._value = node.values[sensor_value] + + @property + def unique_id(self): + """ Returns a unique id. """ + return "ZWAVE-{}-{}".format(self._node.node_id, self._value) + + @property + def name(self): + """ Returns the name of the device. """ + name = self._node.name or "{} {}".format( + self._node.manufacturer_name, self._node.product_name) + + return "{} {}".format(name, self._value.label) + + @property + def state(self): + """ Returns the state of the sensor. """ + return self._value.data + + @property + def state_attributes(self): + """ Returns the state attributes. """ + attrs = { + ATTR_FRIENDLY_NAME: self.name + } + + battery_level = zwave.get_node_value( + self._node, zwave.VALUE_BATTERY_LEVEL) + + if battery_level is not None: + attrs[ATTR_BATTERY_LEVEL] = battery_level + + unit = self.unit + + if unit is not None: + attrs[ATTR_UNIT_OF_MEASUREMENT] = unit + + location = self._node.location + + if location: + attrs[ATTR_LOCATION] = location + + attrs.update(self.get_sensor_attributes()) + + return attrs + + @property + def unit(self): + """ Unit if sensor has one. """ + return None + + def get_sensor_attributes(self): + """ Get sensor attributes. """ + return {} + + +class ZWaveTemperatureSensor(ZWaveSensor): + """ Represents a ZWave Temperature Sensor. """ + + def __init__(self, node): + super().__init__(node, zwave.VALUE_TEMPERATURE) + + @property + def state(self): + """ Returns the state of the sensor. """ + return round(self._value.data/1000, 1) + + @property + def unit(self): + """ Unit of this sensor. """ + unit = self._value.units + + if unit == 'C': + return TEMP_CELCIUS + elif unit == 'F': + return TEMP_FAHRENHEIT + else: + return None + + +class ZWaveRelativeHumiditySensor(ZWaveSensor): + """ Represents a ZWave Relative Humidity Sensor. """ + + def __init__(self, node): + super().__init__(node, zwave.VALUE_RELATIVE_HUMIDITY) + + @property + def unit(self): + """ Unit of this sensor. """ + return '%' + + +class ZWaveLuminanceSensor(ZWaveSensor): + """ Represents a ZWave luminance Sensor. """ + + def __init__(self, node): + super().__init__(node, zwave.VALUE_LUMINANCE) + + @property + def unit(self): + """ Unit of this sensor. """ + return LIGHT_LUX diff --git a/homeassistant/components/zwave.py b/homeassistant/components/zwave.py new file mode 100644 index 00000000000..3381f6772a8 --- /dev/null +++ b/homeassistant/components/zwave.py @@ -0,0 +1,97 @@ +from homeassistant import bootstrap +from homeassistant.loader import get_component +from homeassistant.const import ( + EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, + EVENT_PLATFORM_DISCOVERED, ATTR_SERVICE, ATTR_DISCOVERED) + +DOMAIN = "zwave" +DEPENDENCIES = [] + +CONF_USB_STICK_PATH = "usb_path" +DEFAULT_CONF_USB_STICK_PATH = "/zwaveusbstick" +CONF_DEBUG = "debug" + +DISCOVER_SENSORS = "zwave.sensors" + +VALUE_SENSOR = 72057594076463104 +VALUE_TEMPERATURE = 72057594076479506 +VALUE_LUMINANCE = 72057594076479538 +VALUE_RELATIVE_HUMIDITY = 72057594076479570 +VALUE_BATTERY_LEVEL = 72057594077773825 + +NETWORK = None + + +def get_node_value(node, key): + """ Helper function to get a node value. """ + return node.values[key].data if key in node.values else None + + +def setup(hass, config): + """ + Setup Z-wave. + Will automatically load components to support devices found on the network. + """ + global NETWORK + + from louie import connect + from openzwave.option import ZWaveOption + from openzwave.network import ZWaveNetwork + + # Setup options + options = ZWaveOption( + config[DOMAIN].get(CONF_USB_STICK_PATH, DEFAULT_CONF_USB_STICK_PATH), + user_path=hass.config_dir) + + if config[DOMAIN].get(CONF_DEBUG) == '1': + options.set_console_output(True) + + options.lock() + + NETWORK = ZWaveNetwork(options, autostart=False) + + def log_all(signal): + print("") + print("LOG ALL") + print(signal) + print("") + print("") + print("") + + connect(log_all, weak=False) + + def zwave_init_done(network): + """ Called when Z-Wave has initialized. """ + init_sensor = False + + # This should be rewritten more efficient when supporting more types + for node in network.nodes.values(): + if get_node_value(node, VALUE_SENSOR) and not init_sensor: + init_sensor = True + + component = get_component('sensor') + + # Ensure component is loaded + if component.DOMAIN not in hass.components: + bootstrap.setup_component(hass, component.DOMAIN, config) + + # Fire discovery event + hass.bus.fire(EVENT_PLATFORM_DISCOVERED, { + ATTR_SERVICE: DISCOVER_SENSORS, + ATTR_DISCOVERED: {} + }) + + connect( + zwave_init_done, ZWaveNetwork.SIGNAL_NETWORK_READY, weak=False) + + def stop_zwave(event): + """ Stop Z-wave. """ + NETWORK.stop() + + def start_zwave(event): + """ Called when Home Assistant starts up. """ + NETWORK.start() + + hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_zwave) + + hass.bus.listen_once(EVENT_HOMEASSISTANT_START, start_zwave) diff --git a/homeassistant/const.py b/homeassistant/const.py index 8b8372aaf61..b0ec0a04c31 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -71,6 +71,13 @@ TEMP_FAHRENHEIT = "°F" # Contains the information that is discovered ATTR_DISCOVERED = "discovered" +# Location of the device/sensor +ATTR_LOCATION = "location" + +ATTR_BATTERY_LEVEL = "battery_level" + +LIGHT_LUX = "LUX" + # #### SERVICES #### SERVICE_HOMEASSISTANT_STOP = "stop" diff --git a/scripts/build_python_openzwave b/scripts/build_python_openzwave new file mode 100755 index 00000000000..76b9f5c3371 --- /dev/null +++ b/scripts/build_python_openzwave @@ -0,0 +1,28 @@ +# Sets up and builds python open zwave to be used with Home Assistant +# Dependencies that need to be installed: +# apt-get install cython3 libudev-dev python-sphinx python3-setuptools mercurial +# pip3 install cython + +# If current pwd is scripts, go 1 up. +if [ ${PWD##*/} == "scripts" ]; then + cd .. +fi + +cd .. + +# We need to install louie here or else python-openzwave install +# will download louie from PIP and that one is not compatible with Python 3 +git clone https://github.com/balloob/louie.git +cd louie +python setup.py install +cd .. + +hg clone https://code.google.com/r/balloob-python-openzwave/ +cd balloob-python-openzwave +./update.sh + +# Fix an issue with openzwave +sed -i '253s/.*//' openzwave/cpp/src/value_classes/ValueID.h + +./compile.sh +./install.sh diff --git a/scripts/dev_docker b/scripts/dev_docker index 26cdc7beed9..b3672e56095 100755 --- a/scripts/dev_docker +++ b/scripts/dev_docker @@ -1,3 +1,5 @@ +# Build and run Home Assinstant in Docker + # Optional: pass in a timezone as first argument # If not given will attempt to mount /etc/localtime @@ -8,8 +10,6 @@ fi docker build -t home-assistant-dev . -# TODO set device via command line, remove /bin/bash - if [ $# -gt 0 ] then docker run \ @@ -18,8 +18,7 @@ then -e "TZ=$1" \ -v `pwd`:/usr/src/app \ -v `pwd`/config:/config \ - -t -i home-assistant-dev \ - /bin/bash + -t -i home-assistant-dev else docker run \ diff --git a/scripts/dev_openzwave_docker b/scripts/dev_openzwave_docker new file mode 100755 index 00000000000..9e9d5ba5441 --- /dev/null +++ b/scripts/dev_openzwave_docker @@ -0,0 +1,18 @@ +# Open a docker that can be used to debug/dev python-openzwave +# Pass in a command line argument to skip build + +# If current pwd is scripts, go 1 up. +if [ ${PWD##*/} == "scripts" ]; then + cd .. +fi + +if [ $# -gt 0 ] +then + docker build -t home-assistant-dev . +fi + +docker run \ + --device=/dev/ttyUSB0:/zwaveusbstick:rwm \ + -v `pwd`:/usr/src/app \ + -t -i home-assistant-dev \ + /bin/bash From e9218e2eb2e177f8f9a1cfd0e7195105afec523f Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 23 Feb 2015 00:01:04 -0800 Subject: [PATCH 5/7] Register to Z-Wave sensor updates --- homeassistant/components/sensor/__init__.py | 2 - homeassistant/components/sensor/zwave.py | 77 ++++++++++----------- homeassistant/components/zwave.py | 50 ++++++++++--- homeassistant/const.py | 2 +- 4 files changed, 76 insertions(+), 55 deletions(-) diff --git a/homeassistant/components/sensor/__init__.py b/homeassistant/components/sensor/__init__.py index 59061aa812b..ff3937b54b6 100644 --- a/homeassistant/components/sensor/__init__.py +++ b/homeassistant/components/sensor/__init__.py @@ -39,8 +39,6 @@ def setup(hass, config): def update_sensor_states(now): """ Update states of all sensors. """ if sensors: - logger.info("Updating sensor states") - for sensor in sensors.values(): sensor.update_ha_state(hass, True) diff --git a/homeassistant/components/sensor/zwave.py b/homeassistant/components/sensor/zwave.py index 82ba8d31e39..36b3fcdb06d 100644 --- a/homeassistant/components/sensor/zwave.py +++ b/homeassistant/components/sensor/zwave.py @@ -4,46 +4,11 @@ from homeassistant.const import ( ATTR_FRIENDLY_NAME, ATTR_BATTERY_LEVEL, ATTR_UNIT_OF_MEASUREMENT, TEMP_CELCIUS, TEMP_FAHRENHEIT, LIGHT_LUX, ATTR_LOCATION) - -def devices_discovered(hass, config, info): - """ """ - from louie import connect - from openzwave.network import ZWaveNetwork - - VALUE_CLASS_MAP = { - zwave.VALUE_TEMPERATURE: ZWaveTemperatureSensor, - zwave.VALUE_LUMINANCE: ZWaveLuminanceSensor, - zwave.VALUE_RELATIVE_HUMIDITY: ZWaveRelativeHumiditySensor, - } - - sensors = [] - - for node in zwave.NETWORK.nodes.values(): - for value, klass in VALUE_CLASS_MAP.items(): - if value in node.values: - sensors.append(klass(node)) - - if sensors[-1] is None: - print("") - print("WTF BBQ") - print(node, klass) - print("") - continue - - def value_changed(network, node, value): - """ """ - print("") - print("") - print("") - print("ValueChanged in sensor !!", node, value) - print("") - print("") - print("") - - # triggered when sensors have updated - connect(value_changed, ZWaveNetwork.SIGNAL_VALUE_CHANGED, weak=False) - - return sensors +VALUE_REPORT = 72057594081707603 +REPORT_BATTERY = 1 +REPORT_TEMPERATURE = 1 << 5 +REPORT_HUMIDITY = 1 << 6 +REPORT_LUMINOSITY = 1 << 7 class ZWaveSensor(Device): @@ -115,7 +80,7 @@ class ZWaveTemperatureSensor(ZWaveSensor): @property def state(self): """ Returns the state of the sensor. """ - return round(self._value.data/1000, 1) + return round(self._value.data, 1) @property def unit(self): @@ -152,3 +117,33 @@ class ZWaveLuminanceSensor(ZWaveSensor): def unit(self): """ Unit of this sensor. """ return LIGHT_LUX + + +VALUE_CLASS_MAP = [ + (zwave.VALUE_TEMPERATURE, ZWaveTemperatureSensor, REPORT_TEMPERATURE), + (zwave.VALUE_LUMINANCE, ZWaveLuminanceSensor, REPORT_LUMINOSITY), + (zwave.VALUE_RELATIVE_HUMIDITY, ZWaveRelativeHumiditySensor, + REPORT_HUMIDITY), +] + + +def devices_discovered(hass, config, info): + """ """ + # from louie import connect + # from openzwave.network import ZWaveNetwork + + sensors = [] + + for node in zwave.NETWORK.nodes.values(): + report_mask = REPORT_BATTERY + + for value, klass, sensor_report_mask in VALUE_CLASS_MAP: + + if value in node.get_sensors(): + sensors.append(klass(node)) + report_mask |= sensor_report_mask + + if report_mask != REPORT_BATTERY and VALUE_REPORT in node.values: + node.values[VALUE_REPORT].data = report_mask + + return sensors diff --git a/homeassistant/components/zwave.py b/homeassistant/components/zwave.py index 3381f6772a8..ff3f9eb0bbd 100644 --- a/homeassistant/components/zwave.py +++ b/homeassistant/components/zwave.py @@ -27,6 +27,32 @@ def get_node_value(node, key): return node.values[key].data if key in node.values else None +def nice_print_node(node): + """ Prints a nice formatted node to the output """ + from pprint import pprint + + print("") + print("") + print("") + print("FOUND NODE", node.product_name) + pprint({key: getattr(node, key) for key + in dir(node) + if key != 'values' and + not hasattr(getattr(node, key), '__call__')}) + print("") + print("") + print("VALUES") + pprint({ + value_id: {key: getattr(value, key) for key + in dir(value) + if key[0] != '_' and + not hasattr(getattr(value, key), '__call__')} + for value_id, value in node.values.items()}) + + print("") + print("") + + def setup(hass, config): """ Setup Z-wave. @@ -38,27 +64,26 @@ def setup(hass, config): from openzwave.option import ZWaveOption from openzwave.network import ZWaveNetwork + use_debug = config[DOMAIN].get(CONF_DEBUG) == '1' + # Setup options options = ZWaveOption( config[DOMAIN].get(CONF_USB_STICK_PATH, DEFAULT_CONF_USB_STICK_PATH), user_path=hass.config_dir) - if config[DOMAIN].get(CONF_DEBUG) == '1': - options.set_console_output(True) - + options.set_associate(True) + options.set_console_output(use_debug) options.lock() NETWORK = ZWaveNetwork(options, autostart=False) - def log_all(signal): - print("") - print("LOG ALL") - print(signal) - print("") - print("") - print("") + if use_debug: + def log_all(signal): + print("") + print("LOUIE SIGNAL *****", signal) + print("") - connect(log_all, weak=False) + connect(log_all, weak=False) def zwave_init_done(network): """ Called when Z-Wave has initialized. """ @@ -66,6 +91,9 @@ def setup(hass, config): # This should be rewritten more efficient when supporting more types for node in network.nodes.values(): + if use_debug: + nice_print_node(node) + if get_node_value(node, VALUE_SENSOR) and not init_sensor: init_sensor = True diff --git a/homeassistant/const.py b/homeassistant/const.py index b0ec0a04c31..a4efc5611b9 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -76,7 +76,7 @@ ATTR_LOCATION = "location" ATTR_BATTERY_LEVEL = "battery_level" -LIGHT_LUX = "LUX" +LIGHT_LUX = "lux" # #### SERVICES #### SERVICE_HOMEASSISTANT_STOP = "stop" From 71ca07363a9b521523b893dd26841cb1ac5ac8a5 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 25 Feb 2015 23:27:17 -0800 Subject: [PATCH 6/7] Z-Wave sensors should work now --- homeassistant/components/sensor/zwave.py | 122 +++++++++-------------- homeassistant/components/zwave.py | 101 ++++++++++--------- homeassistant/const.py | 2 - 3 files changed, 96 insertions(+), 129 deletions(-) diff --git a/homeassistant/components/sensor/zwave.py b/homeassistant/components/sensor/zwave.py index 36b3fcdb06d..b39ef5cbb32 100644 --- a/homeassistant/components/sensor/zwave.py +++ b/homeassistant/components/sensor/zwave.py @@ -1,25 +1,26 @@ +""" +homeassistant.components.sensor.zwave +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Interfaces with Z-Wave sensors. +""" import homeassistant.components.zwave as zwave from homeassistant.helpers import Device from homeassistant.const import ( ATTR_FRIENDLY_NAME, ATTR_BATTERY_LEVEL, ATTR_UNIT_OF_MEASUREMENT, - TEMP_CELCIUS, TEMP_FAHRENHEIT, LIGHT_LUX, ATTR_LOCATION) - -VALUE_REPORT = 72057594081707603 -REPORT_BATTERY = 1 -REPORT_TEMPERATURE = 1 << 5 -REPORT_HUMIDITY = 1 << 6 -REPORT_LUMINOSITY = 1 << 7 + TEMP_CELCIUS, TEMP_FAHRENHEIT, ATTR_LOCATION, STATE_ON, STATE_OFF) class ZWaveSensor(Device): - def __init__(self, node, sensor_value): - self._node = node - self._value = node.values[sensor_value] + """ Represents a Z-Wave sensor. """ + def __init__(self, sensor_value): + self._value = sensor_value + self._node = sensor_value.node @property def unique_id(self): """ Returns a unique id. """ - return "ZWAVE-{}-{}".format(self._node.node_id, self._value) + return "ZWAVE-{}-{}".format(self._node.node_id, self._value.object_id) @property def name(self): @@ -38,18 +39,18 @@ class ZWaveSensor(Device): def state_attributes(self): """ Returns the state attributes. """ attrs = { - ATTR_FRIENDLY_NAME: self.name + ATTR_FRIENDLY_NAME: self.name, + zwave.ATTR_NODE_ID: self._node.node_id, } - battery_level = zwave.get_node_value( - self._node, zwave.VALUE_BATTERY_LEVEL) + battery_level = self._node.get_battery_level() if battery_level is not None: attrs[ATTR_BATTERY_LEVEL] = battery_level unit = self.unit - if unit is not None: + if unit: attrs[ATTR_UNIT_OF_MEASUREMENT] = unit location = self._node.location @@ -57,30 +58,36 @@ class ZWaveSensor(Device): if location: attrs[ATTR_LOCATION] = location - attrs.update(self.get_sensor_attributes()) - return attrs @property def unit(self): """ Unit if sensor has one. """ - return None - - def get_sensor_attributes(self): - """ Get sensor attributes. """ - return {} + return self._value.units -class ZWaveTemperatureSensor(ZWaveSensor): - """ Represents a ZWave Temperature Sensor. """ - - def __init__(self, node): - super().__init__(node, zwave.VALUE_TEMPERATURE) +# pylint: disable=too-few-public-methods +class ZWaveBinarySensor(ZWaveSensor): + """ Represents a binary sensor within Z-Wave. """ @property def state(self): """ Returns the state of the sensor. """ - return round(self._value.data, 1) + return STATE_ON if self._value.data else STATE_OFF + + +class ZWaveMultilevelSensor(ZWaveSensor): + """ Represents a multi level sensor Z-Wave sensor. """ + + @property + def state(self): + """ Returns the state of the sensor. """ + value = self._value.data + + if self._value.units in ('C', 'F'): + return round(value, 1) + + return value @property def unit(self): @@ -92,58 +99,21 @@ class ZWaveTemperatureSensor(ZWaveSensor): elif unit == 'F': return TEMP_FAHRENHEIT else: - return None - - -class ZWaveRelativeHumiditySensor(ZWaveSensor): - """ Represents a ZWave Relative Humidity Sensor. """ - - def __init__(self, node): - super().__init__(node, zwave.VALUE_RELATIVE_HUMIDITY) - - @property - def unit(self): - """ Unit of this sensor. """ - return '%' - - -class ZWaveLuminanceSensor(ZWaveSensor): - """ Represents a ZWave luminance Sensor. """ - - def __init__(self, node): - super().__init__(node, zwave.VALUE_LUMINANCE) - - @property - def unit(self): - """ Unit of this sensor. """ - return LIGHT_LUX - - -VALUE_CLASS_MAP = [ - (zwave.VALUE_TEMPERATURE, ZWaveTemperatureSensor, REPORT_TEMPERATURE), - (zwave.VALUE_LUMINANCE, ZWaveLuminanceSensor, REPORT_LUMINOSITY), - (zwave.VALUE_RELATIVE_HUMIDITY, ZWaveRelativeHumiditySensor, - REPORT_HUMIDITY), -] + return unit def devices_discovered(hass, config, info): - """ """ - # from louie import connect - # from openzwave.network import ZWaveNetwork + """ Called when a device is discovered. """ + node = zwave.NETWORK.nodes[info[zwave.ATTR_NODE_ID]] + value = node.values[info[zwave.ATTR_VALUE_ID]] - sensors = [] + value.set_change_verified(False) - for node in zwave.NETWORK.nodes.values(): - report_mask = REPORT_BATTERY + if zwave.NETWORK.controller.node_id not in node.groups[1].associations: + node.groups[1].add_association(zwave.NETWORK.controller.node_id) - for value, klass, sensor_report_mask in VALUE_CLASS_MAP: + if value.command_class == zwave.COMMAND_CLASS_SENSOR_BINARY: + return [ZWaveBinarySensor(value)] - if value in node.get_sensors(): - sensors.append(klass(node)) - report_mask |= sensor_report_mask - - if report_mask != REPORT_BATTERY and VALUE_REPORT in node.values: - node.values[VALUE_REPORT].data = report_mask - - return sensors + elif value.command_class == zwave.COMMAND_CLASS_SENSOR_MULTILEVEL: + return [ZWaveMultilevelSensor(value)] diff --git a/homeassistant/components/zwave.py b/homeassistant/components/zwave.py index ff3f9eb0bbd..8e2861cbb21 100644 --- a/homeassistant/components/zwave.py +++ b/homeassistant/components/zwave.py @@ -1,5 +1,12 @@ +""" +homeassistant.components.zwave +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Connects Home Assistant to a Z-Wave network. +""" +from pprint import pprint + from homeassistant import bootstrap -from homeassistant.loader import get_component from homeassistant.const import ( EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, EVENT_PLATFORM_DISCOVERED, ATTR_SERVICE, ATTR_DISCOVERED) @@ -13,44 +20,39 @@ CONF_DEBUG = "debug" DISCOVER_SENSORS = "zwave.sensors" -VALUE_SENSOR = 72057594076463104 -VALUE_TEMPERATURE = 72057594076479506 -VALUE_LUMINANCE = 72057594076479538 -VALUE_RELATIVE_HUMIDITY = 72057594076479570 -VALUE_BATTERY_LEVEL = 72057594077773825 +COMMAND_CLASS_SENSOR_BINARY = 48 +COMMAND_CLASS_SENSOR_MULTILEVEL = 49 +COMMAND_CLASS_BATTERY = 128 + +# list of tuple (DOMAIN, discovered service, supported command classes) +DISCOVERY_COMPONENTS = [ + ('sensor', DISCOVER_SENSORS, + [COMMAND_CLASS_SENSOR_BINARY, COMMAND_CLASS_SENSOR_MULTILEVEL]), +] + +ATTR_NODE_ID = "node_id" +ATTR_VALUE_ID = "value_id" NETWORK = None -def get_node_value(node, key): - """ Helper function to get a node value. """ - return node.values[key].data if key in node.values else None +def _obj_to_dict(obj): + """ Converts an obj into a hash for debug. """ + return {key: getattr(obj, key) for key + in dir(obj) + if key[0] != '_' and not hasattr(getattr(obj, key), '__call__')} def nice_print_node(node): - """ Prints a nice formatted node to the output """ - from pprint import pprint + """ Prints a nice formatted node to the output (debug method) """ + node_dict = _obj_to_dict(node) + node_dict['values'] = {value_id: _obj_to_dict(value) + for value_id, value in node.values.items()} - print("") - print("") - print("") + print("\n\n\n") print("FOUND NODE", node.product_name) - pprint({key: getattr(node, key) for key - in dir(node) - if key != 'values' and - not hasattr(getattr(node, key), '__call__')}) - print("") - print("") - print("VALUES") - pprint({ - value_id: {key: getattr(value, key) for key - in dir(value) - if key[0] != '_' and - not hasattr(getattr(value, key), '__call__')} - for value_id, value in node.values.items()}) - - print("") - print("") + pprint(node_dict) + print("\n\n\n") def setup(hass, config): @@ -58,6 +60,7 @@ def setup(hass, config): Setup Z-wave. Will automatically load components to support devices found on the network. """ + # pylint: disable=global-statement, import-error global NETWORK from louie import connect @@ -71,46 +74,42 @@ def setup(hass, config): config[DOMAIN].get(CONF_USB_STICK_PATH, DEFAULT_CONF_USB_STICK_PATH), user_path=hass.config_dir) - options.set_associate(True) options.set_console_output(use_debug) options.lock() NETWORK = ZWaveNetwork(options, autostart=False) if use_debug: - def log_all(signal): + def log_all(signal, value=None): + """ Log all the louie signals. """ print("") print("LOUIE SIGNAL *****", signal) + if value and signal in (ZWaveNetwork.SIGNAL_VALUE_CHANGED, + ZWaveNetwork.SIGNAL_VALUE_ADDED): + pprint(_obj_to_dict(value)) print("") connect(log_all, weak=False) - def zwave_init_done(network): - """ Called when Z-Wave has initialized. """ - init_sensor = False - - # This should be rewritten more efficient when supporting more types - for node in network.nodes.values(): - if use_debug: - nice_print_node(node) - - if get_node_value(node, VALUE_SENSOR) and not init_sensor: - init_sensor = True - - component = get_component('sensor') - + def value_added(node, value): + """ Called when a value is added to a node on the network. """ + for component, discovery_service, command_ids in DISCOVERY_COMPONENTS: + if value.command_class in command_ids: # Ensure component is loaded - if component.DOMAIN not in hass.components: - bootstrap.setup_component(hass, component.DOMAIN, config) + if component not in hass.components: + bootstrap.setup_component(hass, component, config) # Fire discovery event hass.bus.fire(EVENT_PLATFORM_DISCOVERED, { - ATTR_SERVICE: DISCOVER_SENSORS, - ATTR_DISCOVERED: {} + ATTR_SERVICE: discovery_service, + ATTR_DISCOVERED: { + ATTR_NODE_ID: node.node_id, + ATTR_VALUE_ID: value.value_id, + } }) connect( - zwave_init_done, ZWaveNetwork.SIGNAL_NETWORK_READY, weak=False) + value_added, ZWaveNetwork.SIGNAL_VALUE_ADDED, weak=False) def stop_zwave(event): """ Stop Z-wave. """ diff --git a/homeassistant/const.py b/homeassistant/const.py index a4efc5611b9..6bce196437f 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -76,8 +76,6 @@ ATTR_LOCATION = "location" ATTR_BATTERY_LEVEL = "battery_level" -LIGHT_LUX = "lux" - # #### SERVICES #### SERVICE_HOMEASSISTANT_STOP = "stop" From 98f3e6ed647b7f3c3f33947a2f469639ac50d99d Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sat, 28 Feb 2015 09:09:32 -0800 Subject: [PATCH 7/7] Update zwave related scripts --- scripts/build_python_openzwave | 5 +++-- scripts/dev_openzwave_docker | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/scripts/build_python_openzwave b/scripts/build_python_openzwave index 76b9f5c3371..fc2b96608de 100755 --- a/scripts/build_python_openzwave +++ b/scripts/build_python_openzwave @@ -8,13 +8,14 @@ if [ ${PWD##*/} == "scripts" ]; then cd .. fi -cd .. +mkdir build +cd build # We need to install louie here or else python-openzwave install # will download louie from PIP and that one is not compatible with Python 3 git clone https://github.com/balloob/louie.git cd louie -python setup.py install +python3 setup.py install cd .. hg clone https://code.google.com/r/balloob-python-openzwave/ diff --git a/scripts/dev_openzwave_docker b/scripts/dev_openzwave_docker index 9e9d5ba5441..f27816a8e39 100755 --- a/scripts/dev_openzwave_docker +++ b/scripts/dev_openzwave_docker @@ -1,5 +1,5 @@ # Open a docker that can be used to debug/dev python-openzwave -# Pass in a command line argument to skip build +# Pass in a command line argument to build # If current pwd is scripts, go 1 up. if [ ${PWD##*/} == "scripts" ]; then @@ -14,5 +14,6 @@ fi docker run \ --device=/dev/ttyUSB0:/zwaveusbstick:rwm \ -v `pwd`:/usr/src/app \ + -p 8123:8123 \ -t -i home-assistant-dev \ /bin/bash