Get Z-Wave sensors to work with Home Assistant
This commit is contained in:
parent
85f7f5589d
commit
e7b9b86c64
8 changed files with 313 additions and 42 deletions
15
Dockerfile
15
Dockerfile
|
@ -7,19 +7,6 @@ RUN apt-get update && \
|
||||||
apt-get install -y cython3 libudev-dev python-sphinx python3-setuptools mercurial && \
|
apt-get install -y cython3 libudev-dev python-sphinx python3-setuptools mercurial && \
|
||||||
apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* && \
|
apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* && \
|
||||||
pip3 install cython && \
|
pip3 install cython && \
|
||||||
cd .. && \
|
scripts/build_python_openzwave
|
||||||
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
|
|
||||||
|
|
||||||
CMD [ "python", "-m", "homeassistant", "--config", "/config" ]
|
CMD [ "python", "-m", "homeassistant", "--config", "/config" ]
|
||||||
|
|
|
@ -8,19 +8,13 @@ from datetime import timedelta
|
||||||
|
|
||||||
from homeassistant.loader import get_component
|
from homeassistant.loader import get_component
|
||||||
import homeassistant.util as util
|
import homeassistant.util as util
|
||||||
from homeassistant.const import (
|
|
||||||
STATE_OPEN)
|
|
||||||
from homeassistant.helpers import (
|
from homeassistant.helpers import (
|
||||||
platform_devices_from_config)
|
platform_devices_from_config, generate_entity_id)
|
||||||
from homeassistant.components import group, discovery, wink
|
from homeassistant.components import discovery, wink, zwave
|
||||||
|
|
||||||
DOMAIN = 'sensor'
|
DOMAIN = 'sensor'
|
||||||
DEPENDENCIES = []
|
DEPENDENCIES = []
|
||||||
|
|
||||||
GROUP_NAME_ALL_SENSORS = 'all_sensors'
|
|
||||||
ENTITY_ID_ALL_SENSORS = group.ENTITY_ID_FORMAT.format(
|
|
||||||
GROUP_NAME_ALL_SENSORS)
|
|
||||||
|
|
||||||
ENTITY_ID_FORMAT = DOMAIN + '.{}'
|
ENTITY_ID_FORMAT = DOMAIN + '.{}'
|
||||||
|
|
||||||
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=1)
|
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=1)
|
||||||
|
@ -28,18 +22,12 @@ MIN_TIME_BETWEEN_SCANS = timedelta(seconds=1)
|
||||||
# Maps discovered services to their platforms
|
# Maps discovered services to their platforms
|
||||||
DISCOVERY_PLATFORMS = {
|
DISCOVERY_PLATFORMS = {
|
||||||
wink.DISCOVER_SENSORS: 'wink',
|
wink.DISCOVER_SENSORS: 'wink',
|
||||||
|
zwave.DISCOVER_SENSORS: 'zwave',
|
||||||
}
|
}
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_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):
|
def setup(hass, config):
|
||||||
""" Track states and offer events for sensors. """
|
""" Track states and offer events for sensors. """
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
@ -58,10 +46,6 @@ def setup(hass, config):
|
||||||
|
|
||||||
update_sensor_states(None)
|
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):
|
def sensor_discovered(service, info):
|
||||||
""" Called when a sensor is discovered. """
|
""" Called when a sensor is discovered. """
|
||||||
platform = get_component("{}.{}".format(
|
platform = get_component("{}.{}".format(
|
||||||
|
@ -71,16 +55,13 @@ def setup(hass, config):
|
||||||
|
|
||||||
for sensor in discovered:
|
for sensor in discovered:
|
||||||
if sensor is not None and sensor not in sensors.values():
|
if sensor is not None and sensor not in sensors.values():
|
||||||
sensor.entity_id = util.ensure_unique_string(
|
sensor.entity_id = generate_entity_id(
|
||||||
ENTITY_ID_FORMAT.format(util.slugify(sensor.name)),
|
ENTITY_ID_FORMAT, sensor.name, sensors.keys())
|
||||||
sensors.keys())
|
|
||||||
|
|
||||||
sensors[sensor.entity_id] = sensor
|
sensors[sensor.entity_id] = sensor
|
||||||
|
|
||||||
sensor.update_ha_state(hass)
|
sensor.update_ha_state(hass)
|
||||||
|
|
||||||
sensor_group.update_tracked_entity_ids(sensors.keys())
|
|
||||||
|
|
||||||
discovery.listen(hass, DISCOVERY_PLATFORMS.keys(), sensor_discovered)
|
discovery.listen(hass, DISCOVERY_PLATFORMS.keys(), sensor_discovered)
|
||||||
|
|
||||||
# Fire every 3 seconds
|
# Fire every 3 seconds
|
||||||
|
|
154
homeassistant/components/sensor/zwave.py
Normal file
154
homeassistant/components/sensor/zwave.py
Normal file
|
@ -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
|
97
homeassistant/components/zwave.py
Normal file
97
homeassistant/components/zwave.py
Normal file
|
@ -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)
|
|
@ -71,6 +71,13 @@ TEMP_FAHRENHEIT = "°F"
|
||||||
# Contains the information that is discovered
|
# Contains the information that is discovered
|
||||||
ATTR_DISCOVERED = "discovered"
|
ATTR_DISCOVERED = "discovered"
|
||||||
|
|
||||||
|
# Location of the device/sensor
|
||||||
|
ATTR_LOCATION = "location"
|
||||||
|
|
||||||
|
ATTR_BATTERY_LEVEL = "battery_level"
|
||||||
|
|
||||||
|
LIGHT_LUX = "LUX"
|
||||||
|
|
||||||
# #### SERVICES ####
|
# #### SERVICES ####
|
||||||
SERVICE_HOMEASSISTANT_STOP = "stop"
|
SERVICE_HOMEASSISTANT_STOP = "stop"
|
||||||
|
|
||||||
|
|
28
scripts/build_python_openzwave
Executable file
28
scripts/build_python_openzwave
Executable file
|
@ -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
|
|
@ -1,3 +1,5 @@
|
||||||
|
# Build and run Home Assinstant in Docker
|
||||||
|
|
||||||
# Optional: pass in a timezone as first argument
|
# Optional: pass in a timezone as first argument
|
||||||
# If not given will attempt to mount /etc/localtime
|
# If not given will attempt to mount /etc/localtime
|
||||||
|
|
||||||
|
@ -8,8 +10,6 @@ fi
|
||||||
|
|
||||||
docker build -t home-assistant-dev .
|
docker build -t home-assistant-dev .
|
||||||
|
|
||||||
# TODO set device via command line, remove /bin/bash
|
|
||||||
|
|
||||||
if [ $# -gt 0 ]
|
if [ $# -gt 0 ]
|
||||||
then
|
then
|
||||||
docker run \
|
docker run \
|
||||||
|
@ -18,8 +18,7 @@ then
|
||||||
-e "TZ=$1" \
|
-e "TZ=$1" \
|
||||||
-v `pwd`:/usr/src/app \
|
-v `pwd`:/usr/src/app \
|
||||||
-v `pwd`/config:/config \
|
-v `pwd`/config:/config \
|
||||||
-t -i home-assistant-dev \
|
-t -i home-assistant-dev
|
||||||
/bin/bash
|
|
||||||
|
|
||||||
else
|
else
|
||||||
docker run \
|
docker run \
|
||||||
|
|
18
scripts/dev_openzwave_docker
Executable file
18
scripts/dev_openzwave_docker
Executable file
|
@ -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
|
Loading…
Add table
Add a link
Reference in a new issue