Pilight component (#2742)

* New component to interface with a pilight-daemon for RF send/receive

* Fix bug that changed the received data, add connected flag, clean up

* New pilight switch component

* New optional whitelist filter to filter uninteressting devices

* Add pilight

* PEP8: too long lines, white spaces

* To keep up the good coverage ...

* PEP 257

* pylint enhancements

* pylint enhancements

* PEP 257

* Better HA config validation and cleanup following code review for #2742

* Fix requirenments to require fixed pilight version

* Change config validation to use voluptuous

* Pilight switch exclude not needed due to wildcard pilight exclude

* Enhance configuration parsing using voluptuous
pull/2778/head
D.-L.Pohl 2016-08-10 04:45:40 +02:00 committed by Paulus Schoutsen
parent d80c05b6b6
commit dc9f990ad2
4 changed files with 228 additions and 0 deletions

View File

@ -88,6 +88,9 @@ omit =
homeassistant/components/homematic.py
homeassistant/components/*/homematic.py
homeassistant/components/pilight.py
homeassistant/components/*/pilight.py
homeassistant/components/knx.py
homeassistant/components/switch/knx.py
homeassistant/components/binary_sensor/knx.py

View File

@ -0,0 +1,112 @@
"""
Component to create an interface to a Pilight daemon (https://pilight.org/).
Pilight can be used to send and receive signals from a radio frequency
module (RF receiver).
RF commands received by the daemon are put on the HA event bus.
RF commands can also be send with a pilight.send service call.
"""
# pylint: disable=import-error
import logging
import socket
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.config_validation import ensure_list
from homeassistant.const import EVENT_HOMEASSISTANT_START
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
from homeassistant.const import CONF_HOST, CONF_PORT
REQUIREMENTS = ['pilight==0.0.2']
DOMAIN = "pilight"
EVENT = 'pilight_received'
SERVICE_NAME = 'send'
CONF_WHITELIST = 'whitelist'
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Required(CONF_HOST, default='127.0.0.1'): cv.string,
vol.Required(CONF_PORT, default=5000): vol.Coerce(int),
vol.Optional(CONF_WHITELIST): {cv.string: [cv.string]}
}),
}, extra=vol.ALLOW_EXTRA)
# The pilight code schema depends on the protocol
# Thus only require to have the protocol information
ATTR_PROTOCOL = 'protocol'
RF_CODE_SCHEMA = vol.Schema({vol.Required(ATTR_PROTOCOL): cv.string},
extra=vol.ALLOW_EXTRA)
_LOGGER = logging.getLogger(__name__)
def setup(hass, config):
"""Setup pilight component."""
from pilight import pilight
try:
pilight_client = pilight.Client(host=config[DOMAIN][CONF_HOST],
port=config[DOMAIN][CONF_PORT])
except (socket.error, socket.timeout) as err:
_LOGGER.error(
"Unable to connect to %s on port %s: %s",
config[CONF_HOST], config[CONF_PORT], err)
return False
# Start / stop pilight-daemon connection with HA start/stop
def start_pilight_client(_):
"""Called once when home assistant starts."""
pilight_client.start()
hass.bus.listen_once(EVENT_HOMEASSISTANT_START, start_pilight_client)
def stop_pilight_client(_):
"""Called once when home assistant stops."""
pilight_client.stop()
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_pilight_client)
def send_code(call):
"""Send RF code to the pilight-daemon."""
message_data = call.data
# Patch data because of bug:
# https://github.com/pilight/pilight/issues/296
# Protocol has to be in a list otherwise segfault in pilight-daemon
message_data["protocol"] = ensure_list(message_data["protocol"])
try:
pilight_client.send_code(message_data)
except IOError:
_LOGGER.error('Pilight send failed for %s', str(message_data))
hass.services.register(DOMAIN, SERVICE_NAME,
send_code, schema=RF_CODE_SCHEMA)
# Publish received codes on the HA event bus
# A whitelist of codes to be published in the event bus
whitelist = config[DOMAIN].get('whitelist', False)
def handle_received_code(data):
"""Called when RF codes are received."""
# Unravel dict of dicts to make event_data cut in automation rule
# possible
data = dict(
{'protocol': data['protocol'],
'uuid': data['uuid']},
**data['message'])
# No whitelist defined, put data on event bus
if not whitelist:
hass.bus.fire(EVENT, data)
# Check if data matches the defined whitelist
elif all(data[key] in whitelist[key] for key in whitelist):
hass.bus.fire(EVENT, data)
pilight_client.set_callback(handle_received_code)
return True

View File

@ -0,0 +1,110 @@
"""
Support for switching devices via pilight to on and off.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/switch.pilight/
"""
import logging
from homeassistant.helpers.config_validation import ensure_list
import homeassistant.components.pilight as pilight
from homeassistant.components.switch import SwitchDevice
DEPENDENCIES = ['pilight']
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
"""Setup the pilight platform."""
# Find and return switches controlled by pilight
switches = config.get('switches', {})
devices = []
for dev_name, properties in switches.items():
devices.append(
PilightSwitch(
hass,
properties.get('name', dev_name),
properties.get('on_code'),
properties.get('off_code'),
ensure_list(properties.get('on_code_receive', False)),
ensure_list(properties.get('off_code_receive', False))))
add_devices_callback(devices)
class PilightSwitch(SwitchDevice):
"""Representation of a pilight switch."""
# pylint: disable=too-many-arguments, too-many-instance-attributes
def __init__(self, hass, name, code_on, code_off,
code_on_receive, code_off_receive):
"""Initialize the switch."""
self._hass = hass
self._name = name
self._state = False
self._code_on = code_on
self._code_off = code_off
self._code_on_receive = code_on_receive
self._code_off_receive = code_off_receive
if any(self._code_on_receive) or any(self._code_off_receive):
hass.bus.listen(pilight.EVENT, self._handle_code)
@property
def name(self):
"""Get the name of the switch."""
return self._name
@property
def should_poll(self):
"""No polling needed, state set when correct code is received."""
return False
@property
def is_on(self):
"""Return true if switch is on."""
return self._state
def _handle_code(self, call):
"""Check if received code by the pilight-daemon.
If the code matches the receive on / off codes of this switch
the switch state is changed accordingly.
"""
# Check if a on code is defined to turn this switch on
if any(self._code_on_receive):
for on_code in self._code_on_receive: # Loop through codes
# True if on_code is contained in received code dict, not
# all items have to match
if on_code.items() <= call.data.items():
self.turn_on()
# Call turn on only once, even when more than one on
# code is received
break
# Check if a off code is defined to turn this switch off
if any(self._code_off_receive):
for off_code in self._code_off_receive: # Loop through codes
# True if off_code is contained in received code dict, not
# all items have to match
if off_code.items() <= call.data.items():
self.turn_off()
# Call turn off only once, even when more than one off
# code is received
break
def turn_on(self):
"""Turn the switch on by calling pilight.send service with on code."""
self._hass.services.call(pilight.DOMAIN, pilight.SERVICE_NAME,
self._code_on, blocking=True)
self._state = True
self.update_ha_state()
def turn_off(self):
"""Turn the switch on by calling pilight.send service with off code."""
self._hass.services.call(pilight.DOMAIN, pilight.SERVICE_NAME,
self._code_off, blocking=True)
self._state = False
self.update_ha_state()

View File

@ -229,6 +229,9 @@ pexpect==4.0.1
# homeassistant.components.light.hue
phue==0.8
# homeassistant.components.pilight
pilight==0.0.2
# homeassistant.components.media_player.plex
# homeassistant.components.sensor.plex
plexapi==2.0.2