Use voluptuous for Pilight switch (#3819)

* Migrate to voluptuous

* Add protocol

* Update
This commit is contained in:
Fabian Affolter 2016-10-31 13:18:47 +01:00 committed by GitHub
parent a89e635bf3
commit 4484a7a94b
5 changed files with 96 additions and 74 deletions

View File

@ -1,5 +1,5 @@
""" """
Component to create an interface to a Pilight daemon (https://pilight.org/). Component to create an interface to a Pilight daemon.
For more details about this component, please refer to the documentation at For more details about this component, please refer to the documentation at
https://home-assistant.io/components/pilight/ https://home-assistant.io/components/pilight/
@ -12,40 +12,38 @@ import voluptuous as vol
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from homeassistant.const import ( from homeassistant.const import (
EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, CONF_HOST, CONF_PORT, EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, CONF_HOST, CONF_PORT,
CONF_WHITELIST) CONF_WHITELIST, CONF_PROTOCOL)
REQUIREMENTS = ['pilight==0.1.1'] REQUIREMENTS = ['pilight==0.1.1']
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
ATTR_PROTOCOL = 'protocol'
DEFAULT_HOST = '127.0.0.1' DEFAULT_HOST = '127.0.0.1'
DEFAULT_PORT = 5000 DEFAULT_PORT = 5000
DOMAIN = 'pilight' DOMAIN = 'pilight'
EVENT = 'pilight_received' EVENT = 'pilight_received'
# The pilight code schema depends on the protocol # The Pilight code schema depends on the protocol. Thus only require to have
# Thus only require to have the protocol information # the protocol information. Ensure that protocol is in a list otherwise
# Ensure that protocol is in a list otherwise segfault in pilight-daemon # segfault in pilight-daemon, https://github.com/pilight/pilight/issues/296
# https://github.com/pilight/pilight/issues/296 RF_CODE_SCHEMA = vol.Schema({
RF_CODE_SCHEMA = vol.Schema({vol.Required(ATTR_PROTOCOL): vol.Required(CONF_PROTOCOL): vol.All(cv.ensure_list, [cv.string]),
vol.All(cv.ensure_list, [cv.string])}, }, extra=vol.ALLOW_EXTRA)
extra=vol.ALLOW_EXTRA)
SERVICE_NAME = 'send' SERVICE_NAME = 'send'
CONFIG_SCHEMA = vol.Schema({ CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({ DOMAIN: vol.Schema({
vol.Required(CONF_HOST, default=DEFAULT_HOST): cv.string, vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string,
vol.Required(CONF_PORT, default=DEFAULT_PORT): cv.port, vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
vol.Optional(CONF_WHITELIST, default={}): {cv.string: [cv.string]} vol.Optional(CONF_WHITELIST, default={}): {cv.string: [cv.string]}
}), }),
}, extra=vol.ALLOW_EXTRA) }, extra=vol.ALLOW_EXTRA)
def setup(hass, config): def setup(hass, config):
"""Setup the pilight component.""" """Setup the Pilight component."""
from pilight import pilight from pilight import pilight
host = config[DOMAIN][CONF_HOST] host = config[DOMAIN][CONF_HOST]
@ -58,23 +56,22 @@ def setup(hass, config):
host, port, err) host, port, err)
return False return False
# Start / stop pilight-daemon connection with HA start/stop
def start_pilight_client(_): def start_pilight_client(_):
"""Called once when home assistant starts.""" """Called once when Home Assistant starts."""
pilight_client.start() pilight_client.start()
hass.bus.listen_once(EVENT_HOMEASSISTANT_START, start_pilight_client) hass.bus.listen_once(EVENT_HOMEASSISTANT_START, start_pilight_client)
def stop_pilight_client(_): def stop_pilight_client(_):
"""Called once when home assistant stops.""" """Called once when Home Assistant stops."""
pilight_client.stop() pilight_client.stop()
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_pilight_client) hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_pilight_client)
def send_code(call): def send_code(call):
"""Send RF code to the pilight-daemon.""" """Send RF code to the pilight-daemon."""
# Change type to dict from mappingproxy # Change type to dict from mappingproxy since data has to be JSON
# since data has to be JSON serializable # serializable
message_data = dict(call.data) message_data = dict(call.data)
try: try:
@ -82,8 +79,8 @@ def setup(hass, config):
except IOError: except IOError:
_LOGGER.error('Pilight send failed for %s', str(message_data)) _LOGGER.error('Pilight send failed for %s', str(message_data))
hass.services.register(DOMAIN, SERVICE_NAME, hass.services.register(
send_code, schema=RF_CODE_SCHEMA) DOMAIN, SERVICE_NAME, send_code, schema=RF_CODE_SCHEMA)
# Publish received codes on the HA event bus # Publish received codes on the HA event bus
# A whitelist of codes to be published in the event bus # A whitelist of codes to be published in the event bus
@ -93,10 +90,8 @@ def setup(hass, config):
"""Called when RF codes are received.""" """Called when RF codes are received."""
# Unravel dict of dicts to make event_data cut in automation rule # Unravel dict of dicts to make event_data cut in automation rule
# possible # possible
data = dict( data = dict({'protocol': data['protocol'], 'uuid': data['uuid']},
{'protocol': data['protocol'], **data['message'])
'uuid': data['uuid']},
**data['message'])
# No whitelist defined, put data on event bus # No whitelist defined, put data on event bus
if not whitelist: if not whitelist:

View File

@ -1,5 +1,5 @@
""" """
Support for pilight sensors. Support for Pilight sensors.
For more details about this platform, please refer to the documentation at For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.pilight/ https://home-assistant.io/components/sensor.pilight/
@ -9,8 +9,7 @@ import logging
import voluptuous as vol import voluptuous as vol
from homeassistant.const import ( from homeassistant.const import (
CONF_NAME, STATE_UNKNOWN, CONF_UNIT_OF_MEASUREMENT, CONF_NAME, STATE_UNKNOWN, CONF_UNIT_OF_MEASUREMENT, CONF_PAYLOAD)
CONF_PAYLOAD)
from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
import homeassistant.components.pilight as pilight import homeassistant.components.pilight as pilight
@ -18,11 +17,13 @@ import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
CONF_VARIABLE = 'variable'
DEFAULT_NAME = 'Pilight Sensor' DEFAULT_NAME = 'Pilight Sensor'
DEPENDENCIES = ['pilight'] DEPENDENCIES = ['pilight']
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required("variable"): cv.string, vol.Required(CONF_VARIABLE): cv.string,
vol.Required(CONF_PAYLOAD): vol.Schema(dict), vol.Required(CONF_PAYLOAD): vol.Schema(dict),
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_UNIT_OF_MEASUREMENT, default=None): cv.string, vol.Optional(CONF_UNIT_OF_MEASUREMENT, default=None): cv.string,
@ -31,18 +32,18 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
# pylint: disable=unused-argument # pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup pilight Sensor.""" """Set up Pilight Sensor."""
add_devices([PilightSensor( add_devices([PilightSensor(
hass=hass, hass=hass,
name=config.get(CONF_NAME), name=config.get(CONF_NAME),
variable=config.get("variable"), variable=config.get(CONF_VARIABLE),
payload=config.get(CONF_PAYLOAD), payload=config.get(CONF_PAYLOAD),
unit_of_measurement=config.get(CONF_UNIT_OF_MEASUREMENT) unit_of_measurement=config.get(CONF_UNIT_OF_MEASUREMENT)
)]) )])
class PilightSensor(Entity): class PilightSensor(Entity):
"""Representation of a sensor that can be updated using pilight.""" """Representation of a sensor that can be updated using Pilight."""
def __init__(self, hass, name, variable, payload, unit_of_measurement): def __init__(self, hass, name, variable, payload, unit_of_measurement):
"""Initialize the sensor.""" """Initialize the sensor."""

View File

@ -1,44 +1,77 @@
""" """
Support for switching devices via pilight to on and off. Support for switching devices via Pilight to on and off.
For more details about this platform, please refer to the documentation at For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/switch.pilight/ https://home-assistant.io/components/switch.pilight/
""" """
import logging import logging
from homeassistant.helpers.config_validation import ensure_list import voluptuous as vol
import homeassistant.components.pilight as pilight
from homeassistant.components.switch import SwitchDevice
DEPENDENCIES = ['pilight'] import homeassistant.helpers.config_validation as cv
import homeassistant.components.pilight as pilight
from homeassistant.components.switch import (SwitchDevice, PLATFORM_SCHEMA)
from homeassistant.const import (CONF_NAME, CONF_ID, CONF_SWITCHES, CONF_STATE)
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
CONF_OFF_CODE = 'off_code'
CONF_OFF_CODE_RECIEVE = 'off_code_receive'
CONF_ON_CODE = 'on_code'
CONF_ON_CODE_RECIEVE = 'on_code_receive'
CONF_SYSTEMCODE = 'systemcode'
CONF_UNIT = 'unit'
def setup_platform(hass, config, add_devices_callback, discovery_info=None): DEPENDENCIES = ['pilight']
"""Setup the pilight platform."""
# Find and return switches controlled by pilight COMMAND_SCHEMA = pilight.RF_CODE_SCHEMA.extend({
switches = config.get('switches', {}) vol.Optional('on'): cv.positive_int,
vol.Optional('off'): cv.positive_int,
vol.Optional(CONF_UNIT): cv.string,
vol.Optional(CONF_ID): cv.positive_int,
vol.Optional(CONF_STATE): cv.string,
vol.Optional(CONF_SYSTEMCODE): cv.string,
})
SWITCHES_SCHEMA = vol.Schema({
vol.Required(CONF_ON_CODE): COMMAND_SCHEMA,
vol.Required(CONF_OFF_CODE): COMMAND_SCHEMA,
vol.Optional(CONF_NAME): cv.string,
vol.Optional(CONF_OFF_CODE_RECIEVE): COMMAND_SCHEMA,
vol.Optional(CONF_ON_CODE_RECIEVE): COMMAND_SCHEMA,
})
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_SWITCHES):
vol.Schema({cv.string: SWITCHES_SCHEMA}),
})
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Pilight platform."""
switches = config.get(CONF_SWITCHES)
devices = [] devices = []
for dev_name, properties in switches.items(): for dev_name, properties in switches.items():
devices.append( devices.append(
PilightSwitch( PilightSwitch(
hass, hass,
properties.get('name', dev_name), properties.get(CONF_NAME, dev_name),
properties.get('on_code'), properties.get(CONF_ON_CODE),
properties.get('off_code'), properties.get(CONF_OFF_CODE),
ensure_list(properties.get('on_code_receive', False)), properties.get(CONF_ON_CODE_RECIEVE),
ensure_list(properties.get('off_code_receive', False)))) properties.get(CONF_OFF_CODE_RECIEVE)
)
)
add_devices_callback(devices) add_devices(devices)
class PilightSwitch(SwitchDevice): class PilightSwitch(SwitchDevice):
"""Representation of a pilight switch.""" """Representation of a Pilight switch."""
def __init__(self, hass, name, code_on, code_off, def __init__(self, hass, name, code_on, code_off, code_on_receive,
code_on_receive, code_off_receive): code_off_receive):
"""Initialize the switch.""" """Initialize the switch."""
self._hass = hass self._hass = hass
self._name = name self._name = name
@ -69,29 +102,22 @@ class PilightSwitch(SwitchDevice):
def _handle_code(self, call): def _handle_code(self, call):
"""Check if received code by the pilight-daemon. """Check if received code by the pilight-daemon.
If the code matches the receive on / off codes of this switch If the code matches the receive on/off codes of this switch the switch
the switch state is changed accordingly. state is changed accordingly.
""" """
# Check if a on code is defined to turn this switch on # - True if off_code/on_code is contained in received code dict, not
# all items have to match.
# - Call turn on/off only once, even if more than one code is received
if any(self._code_on_receive): if any(self._code_on_receive):
for on_code in self._code_on_receive: # Loop through codes for on_code in self._code_on_receive:
# True if on_code is contained in received code dict, not
# all items have to match
if on_code.items() <= call.data.items(): if on_code.items() <= call.data.items():
self.turn_on() self.turn_on()
# Call turn on only once, even when more than one on
# code is received
break break
# Check if a off code is defined to turn this switch off
if any(self._code_off_receive): if any(self._code_off_receive):
for off_code in self._code_off_receive: # Loop through codes for off_code in self._code_off_receive:
# True if off_code is contained in received code dict, not
# all items have to match
if off_code.items() <= call.data.items(): if off_code.items() <= call.data.items():
self.turn_off() self.turn_off()
# Call turn off only once, even when more than one off
# code is received
break break
def turn_on(self): def turn_on(self):

View File

@ -114,6 +114,7 @@ CONF_PIN = 'pin'
CONF_PLATFORM = 'platform' CONF_PLATFORM = 'platform'
CONF_PORT = 'port' CONF_PORT = 'port'
CONF_PREFIX = 'prefix' CONF_PREFIX = 'prefix'
CONF_PROTOCOL = 'protocol'
CONF_QUOTE = 'quote' CONF_QUOTE = 'quote'
CONF_RECIPIENT = 'recipient' CONF_RECIPIENT = 'recipient'
CONF_RESOURCE = 'resource' CONF_RESOURCE = 'resource'

View File

@ -11,8 +11,8 @@ HASS = None
def fire_pilight_message(protocol, data): def fire_pilight_message(protocol, data):
"""Fire the fake pilight message.""" """Fire the fake Pilight message."""
message = {pilight.ATTR_PROTOCOL: protocol} message = {pilight.CONF_PROTOCOL: protocol}
message.update(data) message.update(data)
HASS.bus.fire(pilight.EVENT, message) HASS.bus.fire(pilight.EVENT, message)
@ -67,23 +67,23 @@ def test_disregard_wrong_payload():
'platform': 'pilight', 'platform': 'pilight',
'name': 'test_2', 'name': 'test_2',
'variable': 'test', 'variable': 'test',
'payload': {'uuid': '1-2-3-4', 'payload': {
'protocol': 'test-protocol_2'} 'uuid': '1-2-3-4',
'protocol': 'test-protocol_2'
}
} }
}) })
# Try set value from data with incorrect payload # Try set value from data with incorrect payload
fire_pilight_message(protocol='test-protocol_2', fire_pilight_message(protocol='test-protocol_2',
data={'test': 'data', data={'test': 'data', 'uuid': '0-0-0-0'})
'uuid': '0-0-0-0'})
HASS.block_till_done() HASS.block_till_done()
state = HASS.states.get('sensor.test_2') state = HASS.states.get('sensor.test_2')
assert state.state == 'unknown' assert state.state == 'unknown'
# Try set value from data with partially matched payload # Try set value from data with partially matched payload
fire_pilight_message(protocol='wrong-protocol', fire_pilight_message(protocol='wrong-protocol',
data={'test': 'data', data={'test': 'data', 'uuid': '1-2-3-4'})
'uuid': '1-2-3-4'})
HASS.block_till_done() HASS.block_till_done()
state = HASS.states.get('sensor.test_2') state = HASS.states.get('sensor.test_2')
assert state.state == 'unknown' assert state.state == 'unknown'
@ -113,8 +113,7 @@ def test_variable_missing(caplog):
# Create code without sensor variable # Create code without sensor variable
fire_pilight_message(protocol='test-protocol', fire_pilight_message(protocol='test-protocol',
data={'uuid': '1-2-3-4', data={'uuid': '1-2-3-4', 'other_variable': 3.141})
'other_variable': 3.141})
HASS.block_till_done() HASS.block_till_done()
logs = caplog.text logs = caplog.text