mirror of
https://github.com/home-assistant/core.git
synced 2025-04-24 09:17:53 +00:00
Merge pull request #1023 from flyte/zigbee-pr
Add ZigBee integration and components.
This commit is contained in:
commit
f87c7d6732
@ -35,6 +35,9 @@ omit =
|
||||
homeassistant/components/wink.py
|
||||
homeassistant/components/*/wink.py
|
||||
|
||||
homeassistant/components/zigbee.py
|
||||
homeassistant/components/*/zigbee.py
|
||||
|
||||
homeassistant/components/zwave.py
|
||||
homeassistant/components/*/zwave.py
|
||||
|
||||
|
29
homeassistant/components/binary_sensor/zigbee.py
Normal file
29
homeassistant/components/binary_sensor/zigbee.py
Normal file
@ -0,0 +1,29 @@
|
||||
"""
|
||||
homeassistant.components.binary_sensor.zigbee
|
||||
|
||||
Contains functionality to use a ZigBee device as a binary sensor.
|
||||
"""
|
||||
|
||||
from homeassistant.components.binary_sensor import BinarySensorDevice
|
||||
from homeassistant.components.zigbee import (
|
||||
ZigBeeDigitalIn, ZigBeeDigitalInConfig)
|
||||
|
||||
|
||||
DEPENDENCIES = ["zigbee"]
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
"""
|
||||
Create and add an entity based on the configuration.
|
||||
"""
|
||||
add_entities([
|
||||
ZigBeeBinarySensor(hass, ZigBeeDigitalInConfig(config))
|
||||
])
|
||||
|
||||
|
||||
class ZigBeeBinarySensor(ZigBeeDigitalIn, BinarySensorDevice):
|
||||
"""
|
||||
Use multiple inheritance to turn a ZigBeeDigitalIn into a
|
||||
BinarySensorDevice.
|
||||
"""
|
||||
pass
|
29
homeassistant/components/light/zigbee.py
Normal file
29
homeassistant/components/light/zigbee.py
Normal file
@ -0,0 +1,29 @@
|
||||
"""
|
||||
homeassistant.components.light.zigbee
|
||||
|
||||
Contains functionality to use a ZigBee device as a light.
|
||||
"""
|
||||
|
||||
from homeassistant.components.light import Light
|
||||
from homeassistant.components.zigbee import (
|
||||
ZigBeeDigitalOut, ZigBeeDigitalOutConfig)
|
||||
|
||||
|
||||
DEPENDENCIES = ["zigbee"]
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
"""
|
||||
Create and add an entity based on the configuration.
|
||||
"""
|
||||
add_entities([
|
||||
ZigBeeLight(hass, ZigBeeDigitalOutConfig(config))
|
||||
])
|
||||
|
||||
|
||||
class ZigBeeLight(ZigBeeDigitalOut, Light):
|
||||
"""
|
||||
Use multiple inheritance to turn an instance of ZigBeeDigitalOut into a
|
||||
Light.
|
||||
"""
|
||||
pass
|
77
homeassistant/components/sensor/zigbee.py
Normal file
77
homeassistant/components/sensor/zigbee.py
Normal file
@ -0,0 +1,77 @@
|
||||
"""
|
||||
homeassistant.components.sensor.zigbee
|
||||
|
||||
Contains functionality to use a ZigBee device as a sensor.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from binascii import hexlify
|
||||
|
||||
from homeassistant.core import JobPriority
|
||||
from homeassistant.const import TEMP_CELCIUS
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.components import zigbee
|
||||
|
||||
|
||||
DEPENDENCIES = ["zigbee"]
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
"""
|
||||
Uses the 'type' config value to work out which type of ZigBee sensor we're
|
||||
dealing with and instantiates the relevant classes to handle it.
|
||||
"""
|
||||
typ = config.get("type", "").lower()
|
||||
if not typ:
|
||||
_LOGGER.exception(
|
||||
"Must include 'type' when configuring a ZigBee sensor.")
|
||||
return
|
||||
try:
|
||||
sensor_class, config_class = TYPE_CLASSES[typ]
|
||||
except KeyError:
|
||||
_LOGGER.exception("Unknown ZigBee sensor type: %s", typ)
|
||||
return
|
||||
add_entities([sensor_class(hass, config_class(config))])
|
||||
|
||||
|
||||
class ZigBeeTemperatureSensor(Entity):
|
||||
"""
|
||||
Allows usage of an XBee Pro as a temperature sensor.
|
||||
"""
|
||||
def __init__(self, hass, config):
|
||||
self._config = config
|
||||
self._temp = None
|
||||
# Get initial state
|
||||
hass.pool.add_job(
|
||||
JobPriority.EVENT_STATE, (self.update_ha_state, True))
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self._config.name
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
return self._temp
|
||||
|
||||
@property
|
||||
def unit_of_measurement(self):
|
||||
return TEMP_CELCIUS
|
||||
|
||||
def update(self, *args):
|
||||
try:
|
||||
self._temp = zigbee.DEVICE.get_temperature(self._config.address)
|
||||
except zigbee.ZIGBEE_TX_FAILURE:
|
||||
_LOGGER.warning(
|
||||
"Transmission failure when attempting to get sample from "
|
||||
"ZigBee device at address: %s", hexlify(self._config.address))
|
||||
except zigbee.ZIGBEE_EXCEPTION as exc:
|
||||
_LOGGER.exception(
|
||||
"Unable to get sample from ZigBee device: %s", exc)
|
||||
|
||||
|
||||
# This must be below the classes to which it refers.
|
||||
TYPE_CLASSES = {
|
||||
"temperature": (ZigBeeTemperatureSensor, zigbee.ZigBeeConfig),
|
||||
"analog": (zigbee.ZigBeeAnalogIn, zigbee.ZigBeeAnalogInConfig)
|
||||
}
|
28
homeassistant/components/switch/zigbee.py
Normal file
28
homeassistant/components/switch/zigbee.py
Normal file
@ -0,0 +1,28 @@
|
||||
"""
|
||||
homeassistant.components.switch.zigbee
|
||||
|
||||
Contains functionality to use a ZigBee device as a switch.
|
||||
"""
|
||||
|
||||
from homeassistant.components.switch import SwitchDevice
|
||||
from homeassistant.components.zigbee import (
|
||||
ZigBeeDigitalOut, ZigBeeDigitalOutConfig)
|
||||
|
||||
|
||||
DEPENDENCIES = ["zigbee"]
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
"""
|
||||
Create and add an entity based on the configuration.
|
||||
"""
|
||||
add_entities([
|
||||
ZigBeeSwitch(hass, ZigBeeDigitalOutConfig(config))
|
||||
])
|
||||
|
||||
|
||||
class ZigBeeSwitch(ZigBeeDigitalOut, SwitchDevice):
|
||||
"""
|
||||
Use multiple inheritance to turn a ZigBeeDigitalOut into a SwitchDevice.
|
||||
"""
|
||||
pass
|
327
homeassistant/components/zigbee.py
Normal file
327
homeassistant/components/zigbee.py
Normal file
@ -0,0 +1,327 @@
|
||||
"""
|
||||
homeassistant.components.zigbee
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Sets up and provides access to a ZigBee device and contains generic entity
|
||||
classes.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from binascii import hexlify, unhexlify
|
||||
|
||||
from homeassistant.core import JobPriority
|
||||
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
|
||||
from homeassistant.helpers.entity import Entity
|
||||
|
||||
|
||||
DOMAIN = "zigbee"
|
||||
REQUIREMENTS = ("xbee-helper==0.0.6",)
|
||||
|
||||
CONF_DEVICE = "device"
|
||||
CONF_BAUD = "baud"
|
||||
|
||||
DEFAULT_DEVICE = "/dev/ttyUSB0"
|
||||
DEFAULT_BAUD = 9600
|
||||
DEFAULT_ADC_MAX_VOLTS = 1.2
|
||||
|
||||
# Copied from xbee_helper during setup()
|
||||
GPIO_DIGITAL_OUTPUT_LOW = None
|
||||
GPIO_DIGITAL_OUTPUT_HIGH = None
|
||||
ADC_PERCENTAGE = None
|
||||
ZIGBEE_EXCEPTION = None
|
||||
ZIGBEE_TX_FAILURE = None
|
||||
|
||||
DEVICE = None
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def setup(hass, config):
|
||||
"""
|
||||
Set up the connection to the ZigBee device and instantiate the helper
|
||||
class for it.
|
||||
"""
|
||||
global DEVICE
|
||||
global GPIO_DIGITAL_OUTPUT_LOW
|
||||
global GPIO_DIGITAL_OUTPUT_HIGH
|
||||
global ADC_PERCENTAGE
|
||||
global ZIGBEE_EXCEPTION
|
||||
global ZIGBEE_TX_FAILURE
|
||||
|
||||
import xbee_helper.const as xb_const
|
||||
from xbee_helper import ZigBee
|
||||
from xbee_helper.exceptions import ZigBeeException, ZigBeeTxFailure
|
||||
from serial import Serial, SerialException
|
||||
|
||||
GPIO_DIGITAL_OUTPUT_LOW = xb_const.GPIO_DIGITAL_OUTPUT_LOW
|
||||
GPIO_DIGITAL_OUTPUT_HIGH = xb_const.GPIO_DIGITAL_OUTPUT_HIGH
|
||||
ADC_PERCENTAGE = xb_const.ADC_PERCENTAGE
|
||||
ZIGBEE_EXCEPTION = ZigBeeException
|
||||
ZIGBEE_TX_FAILURE = ZigBeeTxFailure
|
||||
|
||||
usb_device = config[DOMAIN].get(CONF_DEVICE, DEFAULT_DEVICE)
|
||||
baud = int(config[DOMAIN].get(CONF_BAUD, DEFAULT_BAUD))
|
||||
try:
|
||||
ser = Serial(usb_device, baud)
|
||||
except SerialException as exc:
|
||||
_LOGGER.exception("Unable to open serial port for ZigBee: %s", exc)
|
||||
return False
|
||||
DEVICE = ZigBee(ser)
|
||||
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, close_serial_port)
|
||||
return True
|
||||
|
||||
|
||||
def close_serial_port(*args):
|
||||
"""
|
||||
Close the serial port we're using to communicate with the ZigBee.
|
||||
"""
|
||||
DEVICE.zb.serial.close()
|
||||
|
||||
|
||||
class ZigBeeConfig(object):
|
||||
"""
|
||||
Handles the fetching of configuration from the config file for any ZigBee
|
||||
entity.
|
||||
"""
|
||||
def __init__(self, config):
|
||||
self._config = config
|
||||
self._should_poll = config.get("poll", True)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""
|
||||
The name given to the entity.
|
||||
"""
|
||||
return self._config["name"]
|
||||
|
||||
@property
|
||||
def address(self):
|
||||
"""
|
||||
If an address has been provided, unhexlify it, otherwise return None
|
||||
as we're talking to our local ZigBee device.
|
||||
"""
|
||||
address = self._config.get("address")
|
||||
if address is not None:
|
||||
address = unhexlify(address)
|
||||
return address
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
"""
|
||||
A bool depicting whether HA should repeatedly poll this device for its
|
||||
value.
|
||||
"""
|
||||
return self._should_poll
|
||||
|
||||
|
||||
class ZigBeePinConfig(ZigBeeConfig):
|
||||
"""
|
||||
Handles the fetching of configuration from the config file for a ZigBee
|
||||
GPIO pin.
|
||||
"""
|
||||
@property
|
||||
def pin(self):
|
||||
"""
|
||||
The GPIO pin number.
|
||||
"""
|
||||
return self._config["pin"]
|
||||
|
||||
|
||||
class ZigBeeDigitalPinConfig(ZigBeePinConfig):
|
||||
"""
|
||||
Handles the fetching of configuration from the config file for a ZigBee
|
||||
GPIO pin set to digital in or out.
|
||||
"""
|
||||
def __init__(self, config):
|
||||
super(ZigBeeDigitalPinConfig, self).__init__(config)
|
||||
self._bool2state, self._state2bool = self.boolean_maps
|
||||
|
||||
@property
|
||||
def boolean_maps(self):
|
||||
"""
|
||||
Create dicts to map booleans to pin high/low and vice versa. Depends on
|
||||
the config item "on_state" which should be set to "low" or "high".
|
||||
"""
|
||||
if self._config.get("on_state", "").lower() == "low":
|
||||
bool2state = {
|
||||
True: GPIO_DIGITAL_OUTPUT_LOW,
|
||||
False: GPIO_DIGITAL_OUTPUT_HIGH
|
||||
}
|
||||
else:
|
||||
bool2state = {
|
||||
True: GPIO_DIGITAL_OUTPUT_HIGH,
|
||||
False: GPIO_DIGITAL_OUTPUT_LOW
|
||||
}
|
||||
state2bool = {v: k for k, v in bool2state.items()}
|
||||
return bool2state, state2bool
|
||||
|
||||
@property
|
||||
def bool2state(self):
|
||||
"""
|
||||
A dictionary mapping booleans to GPIOSetting objects to translate
|
||||
on/off as being pin high or low.
|
||||
"""
|
||||
return self._bool2state
|
||||
|
||||
@property
|
||||
def state2bool(self):
|
||||
"""
|
||||
A dictionary mapping GPIOSetting objects to booleans to translate
|
||||
pin high/low as being on or off.
|
||||
"""
|
||||
return self._state2bool
|
||||
|
||||
# Create an alias so that ZigBeeDigitalOutConfig has a logical opposite.
|
||||
ZigBeeDigitalInConfig = ZigBeeDigitalPinConfig
|
||||
|
||||
|
||||
class ZigBeeDigitalOutConfig(ZigBeeDigitalPinConfig):
|
||||
"""
|
||||
A subclass of ZigBeeDigitalPinConfig which sets _should_poll to default as
|
||||
False instead of True. The value will still be overridden by the presence
|
||||
of a 'poll' config entry.
|
||||
"""
|
||||
def __init__(self, config):
|
||||
super(ZigBeeDigitalOutConfig, self).__init__(config)
|
||||
self._should_poll = config.get("poll", False)
|
||||
|
||||
|
||||
class ZigBeeAnalogInConfig(ZigBeePinConfig):
|
||||
"""
|
||||
Handles the fetching of configuration from the config file for a ZigBee
|
||||
GPIO pin set to analog in.
|
||||
"""
|
||||
@property
|
||||
def max_voltage(self):
|
||||
"""
|
||||
The voltage at which the ADC will report its highest value.
|
||||
"""
|
||||
return float(self._config.get("max_volts", DEFAULT_ADC_MAX_VOLTS))
|
||||
|
||||
|
||||
class ZigBeeDigitalIn(Entity):
|
||||
"""
|
||||
Represents a GPIO pin configured as a digital input.
|
||||
"""
|
||||
def __init__(self, hass, config):
|
||||
self._config = config
|
||||
self._state = False
|
||||
# Get initial state
|
||||
hass.pool.add_job(
|
||||
JobPriority.EVENT_STATE, (self.update_ha_state, True))
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self._config.name
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
return self._config.should_poll
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""
|
||||
Returns True if the Entity is on, else False.
|
||||
"""
|
||||
return self._state
|
||||
|
||||
def update(self):
|
||||
"""
|
||||
Ask the ZigBee device what its output is set to.
|
||||
"""
|
||||
try:
|
||||
pin_state = DEVICE.get_gpio_pin(
|
||||
self._config.pin,
|
||||
self._config.address)
|
||||
except ZIGBEE_TX_FAILURE:
|
||||
_LOGGER.warning(
|
||||
"Transmission failure when attempting to get sample from "
|
||||
"ZigBee device at address: %s", hexlify(self._config.address))
|
||||
return
|
||||
except ZIGBEE_EXCEPTION as exc:
|
||||
_LOGGER.exception(
|
||||
"Unable to get sample from ZigBee device: %s", exc)
|
||||
return
|
||||
self._state = self._config.state2bool[pin_state]
|
||||
|
||||
|
||||
class ZigBeeDigitalOut(ZigBeeDigitalIn):
|
||||
"""
|
||||
Adds functionality to ZigBeeDigitalIn to control an output.
|
||||
"""
|
||||
def _set_state(self, state):
|
||||
try:
|
||||
DEVICE.set_gpio_pin(
|
||||
self._config.pin,
|
||||
self._config.bool2state[state],
|
||||
self._config.address)
|
||||
except ZIGBEE_TX_FAILURE:
|
||||
_LOGGER.warning(
|
||||
"Transmission failure when attempting to set output pin on "
|
||||
"ZigBee device at address: %s", hexlify(self._config.address))
|
||||
return
|
||||
except ZIGBEE_EXCEPTION as exc:
|
||||
_LOGGER.exception(
|
||||
"Unable to set digital pin on ZigBee device: %s", exc)
|
||||
return
|
||||
self._state = state
|
||||
if not self.should_poll:
|
||||
self.update_ha_state()
|
||||
|
||||
def turn_on(self, **kwargs):
|
||||
"""
|
||||
Set the digital output to its 'on' state.
|
||||
"""
|
||||
self._set_state(True)
|
||||
|
||||
def turn_off(self, **kwargs):
|
||||
"""
|
||||
Set the digital output to its 'off' state.
|
||||
"""
|
||||
self._set_state(False)
|
||||
|
||||
|
||||
class ZigBeeAnalogIn(Entity):
|
||||
"""
|
||||
Represents a GPIO pin configured as an analog input.
|
||||
"""
|
||||
def __init__(self, hass, config):
|
||||
self._config = config
|
||||
self._value = None
|
||||
# Get initial state
|
||||
hass.pool.add_job(
|
||||
JobPriority.EVENT_STATE, (self.update_ha_state, True))
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self._config.name
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
return self._config.should_poll
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
return self._value
|
||||
|
||||
@property
|
||||
def unit_of_measurement(self):
|
||||
return "%"
|
||||
|
||||
def update(self):
|
||||
"""
|
||||
Get the latest reading from the ADC.
|
||||
"""
|
||||
try:
|
||||
self._value = DEVICE.read_analog_pin(
|
||||
self._config.pin,
|
||||
self._config.max_voltage,
|
||||
self._config.address,
|
||||
ADC_PERCENTAGE)
|
||||
except ZIGBEE_TX_FAILURE:
|
||||
_LOGGER.warning(
|
||||
"Transmission failure when attempting to get sample from "
|
||||
"ZigBee device at address: %s", hexlify(self._config.address))
|
||||
except ZIGBEE_EXCEPTION as exc:
|
||||
_LOGGER.exception(
|
||||
"Unable to get sample from ZigBee device: %s", exc)
|
@ -219,5 +219,8 @@ radiotherm==1.2
|
||||
# homeassistant.components.verisure
|
||||
vsure==0.4.8
|
||||
|
||||
# homeassistant.components.zigbee
|
||||
xbee-helper==0.0.6
|
||||
|
||||
# homeassistant.components.zwave
|
||||
pydispatcher==2.0.5
|
||||
|
Loading…
x
Reference in New Issue
Block a user