Merge remote-tracking branch 'refs/remotes/home-assistant/dev' into Homematic
# Conflicts: # homeassistant/components/thermostat/homematic.py
This commit is contained in:
commit
e4d3b25f1e
43 changed files with 951 additions and 105 deletions
|
@ -117,6 +117,8 @@ omit =
|
||||||
homeassistant/components/downloader.py
|
homeassistant/components/downloader.py
|
||||||
homeassistant/components/feedreader.py
|
homeassistant/components/feedreader.py
|
||||||
homeassistant/components/garage_door/wink.py
|
homeassistant/components/garage_door/wink.py
|
||||||
|
homeassistant/components/garage_door/rpi_gpio.py
|
||||||
|
homeassistant/components/hdmi_cec.py
|
||||||
homeassistant/components/ifttt.py
|
homeassistant/components/ifttt.py
|
||||||
homeassistant/components/keyboard.py
|
homeassistant/components/keyboard.py
|
||||||
homeassistant/components/light/blinksticklight.py
|
homeassistant/components/light/blinksticklight.py
|
||||||
|
@ -128,6 +130,7 @@ omit =
|
||||||
homeassistant/components/lirc.py
|
homeassistant/components/lirc.py
|
||||||
homeassistant/components/media_player/braviatv.py
|
homeassistant/components/media_player/braviatv.py
|
||||||
homeassistant/components/media_player/cast.py
|
homeassistant/components/media_player/cast.py
|
||||||
|
homeassistant/components/media_player/cmus.py
|
||||||
homeassistant/components/media_player/denon.py
|
homeassistant/components/media_player/denon.py
|
||||||
homeassistant/components/media_player/firetv.py
|
homeassistant/components/media_player/firetv.py
|
||||||
homeassistant/components/media_player/gpmdp.py
|
homeassistant/components/media_player/gpmdp.py
|
||||||
|
@ -188,6 +191,7 @@ omit =
|
||||||
homeassistant/components/sensor/nzbget.py
|
homeassistant/components/sensor/nzbget.py
|
||||||
homeassistant/components/sensor/onewire.py
|
homeassistant/components/sensor/onewire.py
|
||||||
homeassistant/components/sensor/openweathermap.py
|
homeassistant/components/sensor/openweathermap.py
|
||||||
|
homeassistant/components/sensor/openexchangerates.py
|
||||||
homeassistant/components/sensor/plex.py
|
homeassistant/components/sensor/plex.py
|
||||||
homeassistant/components/sensor/rest.py
|
homeassistant/components/sensor/rest.py
|
||||||
homeassistant/components/sensor/sabnzbd.py
|
homeassistant/components/sensor/sabnzbd.py
|
||||||
|
|
|
@ -22,6 +22,9 @@ http:
|
||||||
# Set to 1 to enable development mode
|
# Set to 1 to enable development mode
|
||||||
# development: 1
|
# development: 1
|
||||||
|
|
||||||
|
frontend:
|
||||||
|
# enable the frontend
|
||||||
|
|
||||||
light:
|
light:
|
||||||
# platform: hue
|
# platform: hue
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
"""DO NOT MODIFY. Auto-generated by build_frontend script."""
|
"""DO NOT MODIFY. Auto-generated by build_frontend script."""
|
||||||
CORE = "7962327e4a29e51d4a6f4ee6cca9acc3"
|
CORE = "db0bb387f4d3bcace002d62b94baa348"
|
||||||
UI = "570e1b8744a58024fc4e256f5e024424"
|
UI = "5b306b7e7d36799b7b67f592cbe94703"
|
||||||
|
|
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
|
@ -1 +1 @@
|
||||||
Subproject commit 168706fdb192219d8074d6709c0ce686180d1c8a
|
Subproject commit 1e1a3a1c845713508d21d7c1cb87a7ecee6222aa
|
|
@ -29,7 +29,7 @@
|
||||||
|
|
||||||
|
|
||||||
/* eslint-disable quotes, comma-spacing */
|
/* eslint-disable quotes, comma-spacing */
|
||||||
var PrecacheConfig = [["/","595e12c9755af231fd80191e4cc74d2e"],["/devEvent","595e12c9755af231fd80191e4cc74d2e"],["/devInfo","595e12c9755af231fd80191e4cc74d2e"],["/devService","595e12c9755af231fd80191e4cc74d2e"],["/devState","595e12c9755af231fd80191e4cc74d2e"],["/devTemplate","595e12c9755af231fd80191e4cc74d2e"],["/history","595e12c9755af231fd80191e4cc74d2e"],["/logbook","595e12c9755af231fd80191e4cc74d2e"],["/map","595e12c9755af231fd80191e4cc74d2e"],["/states","595e12c9755af231fd80191e4cc74d2e"],["/static/core-7962327e4a29e51d4a6f4ee6cca9acc3.js","9c07ffb3f81cfb74f8a051b80cc8f9f0"],["/static/frontend-570e1b8744a58024fc4e256f5e024424.html","595e12c9755af231fd80191e4cc74d2e"],["/static/mdi-9ee3d4466a65bef35c2c8974e91b37c0.html","9a6846935116cd29279c91e0ee0a26d0"],["static/favicon-192x192.png","419903b8422586a7e28021bbe9011175"],["static/fonts/roboto/Roboto-Bold.ttf","d329cc8b34667f114a95422aaad1b063"],["static/fonts/roboto/Roboto-Light.ttf","7b5fb88f12bec8143f00e21bc3222124"],["static/fonts/roboto/Roboto-Medium.ttf","fe13e4170719c2fc586501e777bde143"],["static/fonts/roboto/Roboto-Regular.ttf","ac3f799d5bbaf5196fab15ab8de8431c"],["static/images/card_media_player_bg.png","a34281d1c1835d338a642e90930e61aa"],["static/webcomponents-lite.min.js","b0f32ad3c7749c40d486603f31c9d8b1"]];
|
var PrecacheConfig = [["/","70eeeca780a5f23c7632c2876dd1795a"],["/devEvent","70eeeca780a5f23c7632c2876dd1795a"],["/devInfo","70eeeca780a5f23c7632c2876dd1795a"],["/devService","70eeeca780a5f23c7632c2876dd1795a"],["/devState","70eeeca780a5f23c7632c2876dd1795a"],["/devTemplate","70eeeca780a5f23c7632c2876dd1795a"],["/history","70eeeca780a5f23c7632c2876dd1795a"],["/logbook","70eeeca780a5f23c7632c2876dd1795a"],["/map","70eeeca780a5f23c7632c2876dd1795a"],["/states","70eeeca780a5f23c7632c2876dd1795a"],["/static/core-db0bb387f4d3bcace002d62b94baa348.js","f938163a392465dc87af3a0094376621"],["/static/frontend-5b306b7e7d36799b7b67f592cbe94703.html","70eeeca780a5f23c7632c2876dd1795a"],["/static/mdi-9ee3d4466a65bef35c2c8974e91b37c0.html","9a6846935116cd29279c91e0ee0a26d0"],["static/favicon-192x192.png","419903b8422586a7e28021bbe9011175"],["static/fonts/roboto/Roboto-Bold.ttf","d329cc8b34667f114a95422aaad1b063"],["static/fonts/roboto/Roboto-Light.ttf","7b5fb88f12bec8143f00e21bc3222124"],["static/fonts/roboto/Roboto-Medium.ttf","fe13e4170719c2fc586501e777bde143"],["static/fonts/roboto/Roboto-Regular.ttf","ac3f799d5bbaf5196fab15ab8de8431c"],["static/images/card_media_player_bg.png","a34281d1c1835d338a642e90930e61aa"],["static/webcomponents-lite.min.js","b0f32ad3c7749c40d486603f31c9d8b1"]];
|
||||||
/* eslint-enable quotes, comma-spacing */
|
/* eslint-enable quotes, comma-spacing */
|
||||||
var CacheNamePrefix = 'sw-precache-v1--' + (self.registration ? self.registration.scope : '') + '-';
|
var CacheNamePrefix = 'sw-precache-v1--' + (self.registration ? self.registration.scope : '') + '-';
|
||||||
|
|
||||||
|
|
Binary file not shown.
96
homeassistant/components/garage_door/rpi_gpio.py
Normal file
96
homeassistant/components/garage_door/rpi_gpio.py
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
"""
|
||||||
|
Support for building a Raspberry Pi garage controller in HA.
|
||||||
|
|
||||||
|
Instructions for building the controller can be found here
|
||||||
|
https://github.com/andrewshilliday/garage-door-controller
|
||||||
|
|
||||||
|
For more details about this platform, please refer to the documentation at
|
||||||
|
https://home-assistant.io/components/garage_door.rpi_gpio/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from time import sleep
|
||||||
|
import voluptuous as vol
|
||||||
|
from homeassistant.components.garage_door import GarageDoorDevice
|
||||||
|
import homeassistant.components.rpi_gpio as rpi_gpio
|
||||||
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
|
||||||
|
DEPENDENCIES = ['rpi_gpio']
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
_DOORS_SCHEMA = vol.All(
|
||||||
|
cv.ensure_list,
|
||||||
|
[
|
||||||
|
vol.Schema({
|
||||||
|
'name': str,
|
||||||
|
'relay_pin': int,
|
||||||
|
'state_pin': int,
|
||||||
|
})
|
||||||
|
]
|
||||||
|
)
|
||||||
|
PLATFORM_SCHEMA = vol.Schema({
|
||||||
|
'platform': str,
|
||||||
|
vol.Required('doors'): _DOORS_SCHEMA,
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
# pylint: disable=unused-argument
|
||||||
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
|
"""Setup the garage door platform."""
|
||||||
|
doors = []
|
||||||
|
doors_conf = config.get('doors')
|
||||||
|
|
||||||
|
for door in doors_conf:
|
||||||
|
doors.append(RPiGPIOGarageDoor(door['name'], door['relay_pin'],
|
||||||
|
door['state_pin']))
|
||||||
|
add_devices(doors)
|
||||||
|
|
||||||
|
|
||||||
|
class RPiGPIOGarageDoor(GarageDoorDevice):
|
||||||
|
"""Representation of a Raspberry garage door."""
|
||||||
|
|
||||||
|
def __init__(self, name, relay_pin, state_pin):
|
||||||
|
"""Initialize the garage door."""
|
||||||
|
self._name = name
|
||||||
|
self._state = False
|
||||||
|
self._relay_pin = relay_pin
|
||||||
|
self._state_pin = state_pin
|
||||||
|
rpi_gpio.setup_output(self._relay_pin)
|
||||||
|
rpi_gpio.setup_input(self._state_pin, 'DOWN')
|
||||||
|
rpi_gpio.write_output(self._relay_pin, True)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def unique_id(self):
|
||||||
|
"""Return the ID of this garage door."""
|
||||||
|
return "{}.{}".format(self.__class__, self._name)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
"""Return the name of the garage door if any."""
|
||||||
|
return self._name
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
"""Update the state of the garage door."""
|
||||||
|
self._state = rpi_gpio.read_input(self._state_pin) is True
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_closed(self):
|
||||||
|
"""Return true if door is closed."""
|
||||||
|
return self._state
|
||||||
|
|
||||||
|
def _trigger(self):
|
||||||
|
"""Trigger the door."""
|
||||||
|
rpi_gpio.write_output(self._relay_pin, False)
|
||||||
|
sleep(0.2)
|
||||||
|
rpi_gpio.write_output(self._relay_pin, True)
|
||||||
|
|
||||||
|
def close_door(self):
|
||||||
|
"""Close the door."""
|
||||||
|
if not self.is_closed:
|
||||||
|
self._trigger()
|
||||||
|
|
||||||
|
def open_door(self):
|
||||||
|
"""Open the door."""
|
||||||
|
if self.is_closed:
|
||||||
|
self._trigger()
|
70
homeassistant/components/garage_door/zwave.py
Normal file
70
homeassistant/components/garage_door/zwave.py
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
"""
|
||||||
|
Support for Zwave garage door components.
|
||||||
|
|
||||||
|
For more details about this platform, please refer to the documentation
|
||||||
|
https://home-assistant.io/components/garagedoor.zwave/
|
||||||
|
"""
|
||||||
|
# Because we do not compile openzwave on CI
|
||||||
|
# pylint: disable=import-error
|
||||||
|
import logging
|
||||||
|
from homeassistant.components.garage_door import DOMAIN
|
||||||
|
from homeassistant.components.zwave import ZWaveDeviceEntity
|
||||||
|
from homeassistant.components import zwave
|
||||||
|
from homeassistant.components.garage_door import GarageDoorDevice
|
||||||
|
|
||||||
|
COMMAND_CLASS_SWITCH_BINARY = 0x25 # 37
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
|
"""Find and return Z-Wave garage door device."""
|
||||||
|
if discovery_info is None or zwave.NETWORK is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
node = zwave.NETWORK.nodes[discovery_info[zwave.ATTR_NODE_ID]]
|
||||||
|
value = node.values[discovery_info[zwave.ATTR_VALUE_ID]]
|
||||||
|
|
||||||
|
if value.command_class != zwave.COMMAND_CLASS_SWITCH_BINARY:
|
||||||
|
return
|
||||||
|
if value.type != zwave.TYPE_BOOL:
|
||||||
|
return
|
||||||
|
if value.genre != zwave.GENRE_USER:
|
||||||
|
return
|
||||||
|
|
||||||
|
value.set_change_verified(False)
|
||||||
|
add_devices([ZwaveGarageDoor(value)])
|
||||||
|
|
||||||
|
|
||||||
|
class ZwaveGarageDoor(zwave.ZWaveDeviceEntity, GarageDoorDevice):
|
||||||
|
"""Representation of an Zwave garage door device."""
|
||||||
|
|
||||||
|
def __init__(self, value):
|
||||||
|
"""Initialize the zwave garage door."""
|
||||||
|
from openzwave.network import ZWaveNetwork
|
||||||
|
from pydispatch import dispatcher
|
||||||
|
ZWaveDeviceEntity.__init__(self, value, DOMAIN)
|
||||||
|
self._node = value.node
|
||||||
|
self._state = value.data
|
||||||
|
dispatcher.connect(
|
||||||
|
self.value_changed, ZWaveNetwork.SIGNAL_VALUE_CHANGED)
|
||||||
|
|
||||||
|
def value_changed(self, value):
|
||||||
|
"""Called when a value has changed on the network."""
|
||||||
|
if self._value.value_id == value.value_id:
|
||||||
|
self._state = value.data
|
||||||
|
self.update_ha_state(True)
|
||||||
|
_LOGGER.debug("Value changed on network %s", value)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_closed(self):
|
||||||
|
"""Return the current position of Zwave garage door."""
|
||||||
|
return not self._state
|
||||||
|
|
||||||
|
def close_door(self):
|
||||||
|
"""Close the garage door."""
|
||||||
|
self._value.node.set_switch(self._value.value_id, False)
|
||||||
|
|
||||||
|
def open_door(self):
|
||||||
|
"""Open the garage door."""
|
||||||
|
self._value.node.set_switch(self._value.value_id, True)
|
122
homeassistant/components/hdmi_cec.py
Normal file
122
homeassistant/components/hdmi_cec.py
Normal file
|
@ -0,0 +1,122 @@
|
||||||
|
"""
|
||||||
|
CEC component.
|
||||||
|
|
||||||
|
Requires libcec + Python bindings.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import voluptuous as vol
|
||||||
|
from homeassistant.const import EVENT_HOMEASSISTANT_START
|
||||||
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
_CEC = None
|
||||||
|
DOMAIN = 'hdmi_cec'
|
||||||
|
SERVICE_SELECT_DEVICE = 'select_device'
|
||||||
|
SERVICE_POWER_ON = 'power_on'
|
||||||
|
SERVICE_STANDBY = 'standby'
|
||||||
|
CONF_DEVICES = 'devices'
|
||||||
|
ATTR_DEVICE = 'device'
|
||||||
|
MAX_DEPTH = 4
|
||||||
|
|
||||||
|
|
||||||
|
# pylint: disable=unnecessary-lambda
|
||||||
|
DEVICE_SCHEMA = vol.Schema({
|
||||||
|
vol.All(cv.positive_int): vol.Any(lambda devices: DEVICE_SCHEMA(devices),
|
||||||
|
cv.string)
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = vol.Schema({
|
||||||
|
DOMAIN: vol.Schema({
|
||||||
|
vol.Required(CONF_DEVICES): DEVICE_SCHEMA
|
||||||
|
})
|
||||||
|
}, extra=vol.ALLOW_EXTRA)
|
||||||
|
|
||||||
|
|
||||||
|
def parse_mapping(mapping, parents=None):
|
||||||
|
"""Parse configuration device mapping."""
|
||||||
|
if parents is None:
|
||||||
|
parents = []
|
||||||
|
for addr, val in mapping.items():
|
||||||
|
cur = parents + [str(addr)]
|
||||||
|
if isinstance(val, dict):
|
||||||
|
yield from parse_mapping(val, cur)
|
||||||
|
elif isinstance(val, str):
|
||||||
|
yield (val, cur)
|
||||||
|
|
||||||
|
|
||||||
|
def pad_physical_address(addr):
|
||||||
|
"""Right-pad a physical address."""
|
||||||
|
return addr + ['0'] * (MAX_DEPTH - len(addr))
|
||||||
|
|
||||||
|
|
||||||
|
def setup(hass, config):
|
||||||
|
"""Setup CEC capability."""
|
||||||
|
global _CEC
|
||||||
|
|
||||||
|
# cec is only available if libcec is properly installed
|
||||||
|
# and the Python bindings are accessible.
|
||||||
|
try:
|
||||||
|
import cec
|
||||||
|
except ImportError:
|
||||||
|
_LOGGER.error("libcec must be installed")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Parse configuration into a dict of device name
|
||||||
|
# to physical address represented as a list of
|
||||||
|
# four elements.
|
||||||
|
flat = {}
|
||||||
|
for pair in parse_mapping(config[DOMAIN].get(CONF_DEVICES, {})):
|
||||||
|
flat[pair[0]] = pad_physical_address(pair[1])
|
||||||
|
|
||||||
|
# Configure libcec.
|
||||||
|
cfg = cec.libcec_configuration()
|
||||||
|
cfg.strDeviceName = 'HASS'
|
||||||
|
cfg.bActivateSource = 0
|
||||||
|
cfg.bMonitorOnly = 1
|
||||||
|
cfg.clientVersion = cec.LIBCEC_VERSION_CURRENT
|
||||||
|
|
||||||
|
# Set up CEC adapter.
|
||||||
|
_CEC = cec.ICECAdapter.Create(cfg)
|
||||||
|
|
||||||
|
def _power_on(call):
|
||||||
|
"""Power on all devices."""
|
||||||
|
_CEC.PowerOnDevices()
|
||||||
|
|
||||||
|
def _standby(call):
|
||||||
|
"""Standby all devices."""
|
||||||
|
_CEC.StandbyDevices()
|
||||||
|
|
||||||
|
def _select_device(call):
|
||||||
|
"""Select the active device."""
|
||||||
|
path = flat.get(call.data[ATTR_DEVICE])
|
||||||
|
if not path:
|
||||||
|
_LOGGER.error("Device not found: %s", call.data[ATTR_DEVICE])
|
||||||
|
cmds = []
|
||||||
|
for i in range(1, MAX_DEPTH - 1):
|
||||||
|
addr = pad_physical_address(path[:i])
|
||||||
|
cmds.append('1f:82:{}{}:{}{}'.format(*addr))
|
||||||
|
cmds.append('1f:86:{}{}:{}{}'.format(*addr))
|
||||||
|
for cmd in cmds:
|
||||||
|
_CEC.Transmit(_CEC.CommandFromString(cmd))
|
||||||
|
_LOGGER.info("Selected %s", call.data[ATTR_DEVICE])
|
||||||
|
|
||||||
|
def _start_cec(event):
|
||||||
|
"""Open CEC adapter."""
|
||||||
|
adapters = _CEC.DetectAdapters()
|
||||||
|
if len(adapters) == 0:
|
||||||
|
_LOGGER.error("No CEC adapter found")
|
||||||
|
return
|
||||||
|
|
||||||
|
if _CEC.Open(adapters[0].strComName):
|
||||||
|
hass.services.register(DOMAIN, SERVICE_POWER_ON, _power_on)
|
||||||
|
hass.services.register(DOMAIN, SERVICE_STANDBY, _standby)
|
||||||
|
hass.services.register(DOMAIN, SERVICE_SELECT_DEVICE,
|
||||||
|
_select_device)
|
||||||
|
else:
|
||||||
|
_LOGGER.error("Failed to open adapter")
|
||||||
|
|
||||||
|
hass.bus.listen_once(EVENT_HOMEASSISTANT_START, _start_cec)
|
||||||
|
return True
|
|
@ -425,39 +425,39 @@ class HvacDevice(Entity):
|
||||||
|
|
||||||
def set_temperature(self, temperature):
|
def set_temperature(self, temperature):
|
||||||
"""Set new target temperature."""
|
"""Set new target temperature."""
|
||||||
pass
|
raise NotImplementedError()
|
||||||
|
|
||||||
def set_humidity(self, humidity):
|
def set_humidity(self, humidity):
|
||||||
"""Set new target humidity."""
|
"""Set new target humidity."""
|
||||||
pass
|
raise NotImplementedError()
|
||||||
|
|
||||||
def set_fan_mode(self, fan):
|
def set_fan_mode(self, fan):
|
||||||
"""Set new target fan mode."""
|
"""Set new target fan mode."""
|
||||||
pass
|
raise NotImplementedError()
|
||||||
|
|
||||||
def set_operation_mode(self, operation_mode):
|
def set_operation_mode(self, operation_mode):
|
||||||
"""Set new target operation mode."""
|
"""Set new target operation mode."""
|
||||||
pass
|
raise NotImplementedError()
|
||||||
|
|
||||||
def set_swing_mode(self, swing_mode):
|
def set_swing_mode(self, swing_mode):
|
||||||
"""Set new target swing operation."""
|
"""Set new target swing operation."""
|
||||||
pass
|
raise NotImplementedError()
|
||||||
|
|
||||||
def turn_away_mode_on(self):
|
def turn_away_mode_on(self):
|
||||||
"""Turn away mode on."""
|
"""Turn away mode on."""
|
||||||
pass
|
raise NotImplementedError()
|
||||||
|
|
||||||
def turn_away_mode_off(self):
|
def turn_away_mode_off(self):
|
||||||
"""Turn away mode off."""
|
"""Turn away mode off."""
|
||||||
pass
|
raise NotImplementedError()
|
||||||
|
|
||||||
def turn_aux_heat_on(self):
|
def turn_aux_heat_on(self):
|
||||||
"""Turn auxillary heater on."""
|
"""Turn auxillary heater on."""
|
||||||
pass
|
raise NotImplementedError()
|
||||||
|
|
||||||
def turn_aux_heat_off(self):
|
def turn_aux_heat_off(self):
|
||||||
"""Turn auxillary heater off."""
|
"""Turn auxillary heater off."""
|
||||||
pass
|
raise NotImplementedError()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def min_temp(self):
|
def min_temp(self):
|
||||||
|
|
|
@ -58,7 +58,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
discovery_info, zwave.NETWORK)
|
discovery_info, zwave.NETWORK)
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=too-many-arguments
|
# pylint: disable=too-many-arguments, abstract-method
|
||||||
class ZWaveHvac(ZWaveDeviceEntity, HvacDevice):
|
class ZWaveHvac(ZWaveDeviceEntity, HvacDevice):
|
||||||
"""Represents a HeatControl hvac."""
|
"""Represents a HeatControl hvac."""
|
||||||
|
|
||||||
|
@ -98,7 +98,7 @@ class ZWaveHvac(ZWaveDeviceEntity, HvacDevice):
|
||||||
|
|
||||||
def value_changed(self, value):
|
def value_changed(self, value):
|
||||||
"""Called when a value has changed on the network."""
|
"""Called when a value has changed on the network."""
|
||||||
if self._value.node == value.node:
|
if self._value.value_id == value.value_id:
|
||||||
self.update_properties()
|
self.update_properties()
|
||||||
self.update_ha_state(True)
|
self.update_ha_state(True)
|
||||||
_LOGGER.debug("Value changed on network %s", value)
|
_LOGGER.debug("Value changed on network %s", value)
|
||||||
|
|
|
@ -248,7 +248,8 @@ def setup(hass, config):
|
||||||
class Light(ToggleEntity):
|
class Light(ToggleEntity):
|
||||||
"""Representation of a light."""
|
"""Representation of a light."""
|
||||||
|
|
||||||
# pylint: disable=no-self-use
|
# pylint: disable=no-self-use, abstract-method
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def brightness(self):
|
def brightness(self):
|
||||||
"""Return the brightness of this light between 0..255."""
|
"""Return the brightness of this light between 0..255."""
|
||||||
|
|
|
@ -4,6 +4,7 @@ Support for LimitlessLED bulbs.
|
||||||
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/light.limitlessled/
|
https://home-assistant.io/components/light.limitlessled/
|
||||||
"""
|
"""
|
||||||
|
# pylint: disable=abstract-method
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from homeassistant.components.light import (
|
from homeassistant.components.light import (
|
||||||
|
|
|
@ -4,6 +4,7 @@ Support for MySensors lights.
|
||||||
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/light.mysensors/
|
https://home-assistant.io/components/light.mysensors/
|
||||||
"""
|
"""
|
||||||
|
# pylint: disable=abstract-method
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from homeassistant.components import mysensors
|
from homeassistant.components import mysensors
|
||||||
|
|
214
homeassistant/components/media_player/cmus.py
Normal file
214
homeassistant/components/media_player/cmus.py
Normal file
|
@ -0,0 +1,214 @@
|
||||||
|
"""
|
||||||
|
Support for interacting with and controlling the cmus music player.
|
||||||
|
|
||||||
|
For more details about this platform, please refer to the documentation at
|
||||||
|
https://home-assistant.io/components/media_player.mpd/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from homeassistant.components.media_player import (
|
||||||
|
MEDIA_TYPE_MUSIC, MEDIA_TYPE_PLAYLIST, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE,
|
||||||
|
SUPPORT_PREVIOUS_TRACK, SUPPORT_TURN_OFF, SUPPORT_TURN_ON,
|
||||||
|
SUPPORT_VOLUME_SET, SUPPORT_PLAY_MEDIA, SUPPORT_SEEK,
|
||||||
|
MediaPlayerDevice)
|
||||||
|
from homeassistant.const import (STATE_OFF, STATE_PAUSED, STATE_PLAYING,
|
||||||
|
CONF_HOST, CONF_NAME, CONF_PASSWORD,
|
||||||
|
CONF_PORT)
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
REQUIREMENTS = ['pycmus>=0.1.0']
|
||||||
|
|
||||||
|
SUPPORT_CMUS = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_TURN_OFF | \
|
||||||
|
SUPPORT_TURN_ON | SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK | \
|
||||||
|
SUPPORT_PLAY_MEDIA | SUPPORT_SEEK
|
||||||
|
|
||||||
|
|
||||||
|
def setup_platform(hass, config, add_devices, discover_info=None):
|
||||||
|
"""Setup the Cmus platform."""
|
||||||
|
from pycmus import exceptions
|
||||||
|
|
||||||
|
host = config.get(CONF_HOST, None)
|
||||||
|
password = config.get(CONF_PASSWORD, None)
|
||||||
|
port = config.get(CONF_PORT, None)
|
||||||
|
name = config.get(CONF_NAME, None)
|
||||||
|
if host and not password:
|
||||||
|
_LOGGER.error("A password must be set if using a remote cmus server")
|
||||||
|
return False
|
||||||
|
try:
|
||||||
|
cmus_remote = CmusDevice(host, password, port, name)
|
||||||
|
except exceptions.InvalidPassword:
|
||||||
|
_LOGGER.error("The provided password was rejected by cmus")
|
||||||
|
return False
|
||||||
|
add_devices([cmus_remote])
|
||||||
|
|
||||||
|
|
||||||
|
class CmusDevice(MediaPlayerDevice):
|
||||||
|
"""Representation of a running cmus."""
|
||||||
|
|
||||||
|
# pylint: disable=no-member, too-many-public-methods, abstract-method
|
||||||
|
def __init__(self, server, password, port, name):
|
||||||
|
"""Initialize the CMUS device."""
|
||||||
|
from pycmus import remote
|
||||||
|
|
||||||
|
if server:
|
||||||
|
port = port or 3000
|
||||||
|
self.cmus = remote.PyCmus(server=server, password=password,
|
||||||
|
port=port)
|
||||||
|
auto_name = "cmus-%s" % server
|
||||||
|
else:
|
||||||
|
self.cmus = remote.PyCmus()
|
||||||
|
auto_name = "cmus-local"
|
||||||
|
self._name = name or auto_name
|
||||||
|
self.status = {}
|
||||||
|
self.update()
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
"""Get the latest data and update the state."""
|
||||||
|
status = self.cmus.get_status_dict()
|
||||||
|
if not status:
|
||||||
|
_LOGGER.warning("Recieved no status from cmus")
|
||||||
|
else:
|
||||||
|
self.status = status
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
"""Return the name of the device."""
|
||||||
|
return self._name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def state(self):
|
||||||
|
"""Return the media state."""
|
||||||
|
if 'status' not in self.status:
|
||||||
|
self.update()
|
||||||
|
if self.status['status'] == 'playing':
|
||||||
|
return STATE_PLAYING
|
||||||
|
elif self.status['status'] == 'paused':
|
||||||
|
return STATE_PAUSED
|
||||||
|
else:
|
||||||
|
return STATE_OFF
|
||||||
|
|
||||||
|
@property
|
||||||
|
def media_content_id(self):
|
||||||
|
"""Content ID of current playing media."""
|
||||||
|
return self.status.get('file')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def content_type(self):
|
||||||
|
"""Content type of the current playing media."""
|
||||||
|
return MEDIA_TYPE_MUSIC
|
||||||
|
|
||||||
|
@property
|
||||||
|
def media_duration(self):
|
||||||
|
"""Duration of current playing media in seconds."""
|
||||||
|
return self.status.get('duration')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def media_title(self):
|
||||||
|
"""Title of current playing media."""
|
||||||
|
return self.status['tag'].get('title')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def media_artist(self):
|
||||||
|
"""Artist of current playing media, music track only."""
|
||||||
|
return self.status['tag'].get('artist')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def media_track(self):
|
||||||
|
"""Track number of current playing media, music track only."""
|
||||||
|
return self.status['tag'].get('tracknumber')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def media_album_name(self):
|
||||||
|
"""Album name of current playing media, music track only."""
|
||||||
|
return self.status['tag'].get('album')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def media_album_artist(self):
|
||||||
|
"""Album artist of current playing media, music track only."""
|
||||||
|
return self.status['tag'].get('albumartist')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def volume_level(self):
|
||||||
|
"""Return the volume level."""
|
||||||
|
left = self.status['set'].get('vol_left')[0]
|
||||||
|
right = self.status['set'].get('vol_right')[0]
|
||||||
|
if left != right:
|
||||||
|
volume = float(left + right) / 2
|
||||||
|
else:
|
||||||
|
volume = left
|
||||||
|
return int(volume)/100
|
||||||
|
|
||||||
|
@property
|
||||||
|
def supported_media_commands(self):
|
||||||
|
"""Flag of media commands that are supported."""
|
||||||
|
return SUPPORT_CMUS
|
||||||
|
|
||||||
|
def turn_off(self):
|
||||||
|
"""Service to send the CMUS the command to stop playing."""
|
||||||
|
self.cmus.player_stop()
|
||||||
|
|
||||||
|
def turn_on(self):
|
||||||
|
"""Service to send the CMUS the command to start playing."""
|
||||||
|
self.cmus.player_play()
|
||||||
|
|
||||||
|
def set_volume_level(self, volume):
|
||||||
|
"""Set volume level, range 0..1."""
|
||||||
|
self.cmus.set_volume(int(volume * 100))
|
||||||
|
|
||||||
|
def volume_up(self):
|
||||||
|
"""Function to send CMUS the command for volume up."""
|
||||||
|
left = self.status['set'].get('vol_left')
|
||||||
|
right = self.status['set'].get('vol_right')
|
||||||
|
if left != right:
|
||||||
|
current_volume = float(left + right) / 2
|
||||||
|
else:
|
||||||
|
current_volume = left
|
||||||
|
|
||||||
|
if current_volume <= 100:
|
||||||
|
self.cmus.set_volume(int(current_volume) + 5)
|
||||||
|
|
||||||
|
def volume_down(self):
|
||||||
|
"""Function to send CMUS the command for volume down."""
|
||||||
|
left = self.status['set'].get('vol_left')
|
||||||
|
right = self.status['set'].get('vol_right')
|
||||||
|
if left != right:
|
||||||
|
current_volume = float(left + right) / 2
|
||||||
|
else:
|
||||||
|
current_volume = left
|
||||||
|
|
||||||
|
if current_volume <= 100:
|
||||||
|
self.cmus.set_volume(int(current_volume) - 5)
|
||||||
|
|
||||||
|
def play_media(self, media_type, media_id, **kwargs):
|
||||||
|
"""Send the play command."""
|
||||||
|
if media_type in [MEDIA_TYPE_MUSIC, MEDIA_TYPE_PLAYLIST]:
|
||||||
|
self.cmus.player_play_file(media_id)
|
||||||
|
else:
|
||||||
|
_LOGGER.error(
|
||||||
|
"Invalid media type %s. Only %s and %s are supported",
|
||||||
|
media_type, MEDIA_TYPE_MUSIC, MEDIA_TYPE_PLAYLIST)
|
||||||
|
|
||||||
|
def media_pause(self):
|
||||||
|
"""Send the pause command."""
|
||||||
|
self.cmus.player_pause()
|
||||||
|
|
||||||
|
def media_next_track(self):
|
||||||
|
"""Send next track command."""
|
||||||
|
self.cmus.player_next()
|
||||||
|
|
||||||
|
def media_previous_track(self):
|
||||||
|
"""Send next track command."""
|
||||||
|
self.cmus.player_prev()
|
||||||
|
|
||||||
|
def media_seek(self, position):
|
||||||
|
"""Send seek command."""
|
||||||
|
self.cmus.seek(position)
|
||||||
|
|
||||||
|
def media_play(self):
|
||||||
|
"""Send the play command."""
|
||||||
|
self.cmus.player_play()
|
||||||
|
|
||||||
|
def media_stop(self):
|
||||||
|
"""Send the stop command."""
|
||||||
|
self.cmus.stop()
|
18
homeassistant/components/persistent_notification.py
Normal file
18
homeassistant/components/persistent_notification.py
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
"""
|
||||||
|
A component which is collecting configuration errors.
|
||||||
|
|
||||||
|
For more details about this component, please refer to the documentation at
|
||||||
|
https://home-assistant.io/components/persistent_notification/
|
||||||
|
"""
|
||||||
|
|
||||||
|
DOMAIN = "persistent_notification"
|
||||||
|
|
||||||
|
|
||||||
|
def create(hass, entity, msg):
|
||||||
|
"""Create a state for an error."""
|
||||||
|
hass.states.set('{}.{}'.format(DOMAIN, entity), msg)
|
||||||
|
|
||||||
|
|
||||||
|
def setup(hass, config):
|
||||||
|
"""Setup the persistent notification component."""
|
||||||
|
return True
|
|
@ -28,7 +28,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
|
|
||||||
if value.command_class != zwave.COMMAND_CLASS_SWITCH_MULTILEVEL:
|
if value.command_class != zwave.COMMAND_CLASS_SWITCH_MULTILEVEL:
|
||||||
return
|
return
|
||||||
if value.index != 1:
|
if value.index != 0:
|
||||||
return
|
return
|
||||||
|
|
||||||
value.set_change_verified(False)
|
value.set_change_verified(False)
|
||||||
|
@ -49,33 +49,22 @@ class ZwaveRollershutter(zwave.ZWaveDeviceEntity, RollershutterDevice):
|
||||||
|
|
||||||
def value_changed(self, value):
|
def value_changed(self, value):
|
||||||
"""Called when a value has changed on the network."""
|
"""Called when a value has changed on the network."""
|
||||||
if self._value.node == value.node:
|
if self._value.value_id == value.value_id:
|
||||||
self.update_ha_state(True)
|
self.update_ha_state(True)
|
||||||
_LOGGER.debug("Value changed on network %s", value)
|
_LOGGER.debug("Value changed on network %s", value)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_position(self):
|
def current_position(self):
|
||||||
"""Return the current position of Zwave roller shutter."""
|
"""Return the current position of Zwave roller shutter."""
|
||||||
for value in self._node.get_values(
|
return self._value.data
|
||||||
class_id=COMMAND_CLASS_SWITCH_MULTILEVEL).values():
|
|
||||||
if value.command_class == 38 and value.index == 0:
|
|
||||||
return value.data
|
|
||||||
|
|
||||||
def move_up(self, **kwargs):
|
def move_up(self, **kwargs):
|
||||||
"""Move the roller shutter up."""
|
"""Move the roller shutter up."""
|
||||||
for value in self._node.get_values(
|
self._node.set_dimmer(self._value.value_id, 100)
|
||||||
class_id=COMMAND_CLASS_SWITCH_MULTILEVEL).values():
|
|
||||||
if value.command_class == 38 and value.index == 0:
|
|
||||||
value.data = 255
|
|
||||||
break
|
|
||||||
|
|
||||||
def move_down(self, **kwargs):
|
def move_down(self, **kwargs):
|
||||||
"""Move the roller shutter down."""
|
"""Move the roller shutter down."""
|
||||||
for value in self._node.get_values(
|
self._node.set_dimmer(self._value.value_id, 0)
|
||||||
class_id=COMMAND_CLASS_SWITCH_MULTILEVEL).values():
|
|
||||||
if value.command_class == 38 and value.index == 0:
|
|
||||||
value.data = 0
|
|
||||||
break
|
|
||||||
|
|
||||||
def stop(self, **kwargs):
|
def stop(self, **kwargs):
|
||||||
"""Stop the roller shutter."""
|
"""Stop the roller shutter."""
|
||||||
|
@ -84,3 +73,4 @@ class ZwaveRollershutter(zwave.ZWaveDeviceEntity, RollershutterDevice):
|
||||||
# Rollershutter will toggle between UP (True), DOWN (False).
|
# Rollershutter will toggle between UP (True), DOWN (False).
|
||||||
# It also stops the shutter if the same value is sent while moving.
|
# It also stops the shutter if the same value is sent while moving.
|
||||||
value.data = value.data
|
value.data = value.data
|
||||||
|
break
|
||||||
|
|
100
homeassistant/components/sensor/openexchangerates.py
Normal file
100
homeassistant/components/sensor/openexchangerates.py
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
"""Support for openexchangerates.org exchange rates service."""
|
||||||
|
from datetime import timedelta
|
||||||
|
import logging
|
||||||
|
import requests
|
||||||
|
from homeassistant.helpers.entity import Entity
|
||||||
|
from homeassistant.util import Throttle
|
||||||
|
from homeassistant.const import CONF_API_KEY
|
||||||
|
|
||||||
|
_RESOURCE = 'https://openexchangerates.org/api/latest.json'
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
# Return cached results if last scan was less then this time ago.
|
||||||
|
MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=100)
|
||||||
|
CONF_BASE = 'base'
|
||||||
|
CONF_QUOTE = 'quote'
|
||||||
|
CONF_NAME = 'name'
|
||||||
|
DEFAULT_NAME = 'Exchange Rate Sensor'
|
||||||
|
|
||||||
|
|
||||||
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
|
"""Setup the Openexchangerates sensor."""
|
||||||
|
payload = config.get('payload', None)
|
||||||
|
rest = OpenexchangeratesData(
|
||||||
|
_RESOURCE,
|
||||||
|
config.get(CONF_API_KEY),
|
||||||
|
config.get(CONF_BASE, 'USD'),
|
||||||
|
config.get(CONF_QUOTE),
|
||||||
|
payload
|
||||||
|
)
|
||||||
|
response = requests.get(_RESOURCE, params={'base': config.get(CONF_BASE,
|
||||||
|
'USD'),
|
||||||
|
'app_id':
|
||||||
|
config.get(CONF_API_KEY)},
|
||||||
|
timeout=10)
|
||||||
|
if response.status_code != 200:
|
||||||
|
_LOGGER.error("Check your OpenExchangeRates API")
|
||||||
|
return False
|
||||||
|
rest.update()
|
||||||
|
add_devices([OpenexchangeratesSensor(rest, config.get(CONF_NAME,
|
||||||
|
DEFAULT_NAME),
|
||||||
|
config.get(CONF_QUOTE))])
|
||||||
|
|
||||||
|
|
||||||
|
class OpenexchangeratesSensor(Entity):
|
||||||
|
"""Implementing the Openexchangerates sensor."""
|
||||||
|
|
||||||
|
def __init__(self, rest, name, quote):
|
||||||
|
"""Initialize the sensor."""
|
||||||
|
self.rest = rest
|
||||||
|
self._name = name
|
||||||
|
self._quote = quote
|
||||||
|
self.update()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
"""Return the name of the sensor."""
|
||||||
|
return self._name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def state(self):
|
||||||
|
"""Return the state of the sensor."""
|
||||||
|
return self._state
|
||||||
|
|
||||||
|
@property
|
||||||
|
def device_state_attributes(self):
|
||||||
|
"""Return other attributes of the sensor."""
|
||||||
|
return self.rest.data
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
"""Update current conditions."""
|
||||||
|
self.rest.update()
|
||||||
|
value = self.rest.data
|
||||||
|
self._state = round(value[str(self._quote)], 4)
|
||||||
|
|
||||||
|
|
||||||
|
# pylint: disable=too-few-public-methods
|
||||||
|
class OpenexchangeratesData(object):
|
||||||
|
"""Get data from Openexchangerates.org."""
|
||||||
|
|
||||||
|
# pylint: disable=too-many-arguments
|
||||||
|
def __init__(self, resource, api_key, base, quote, data):
|
||||||
|
"""Initialize the data object."""
|
||||||
|
self._resource = resource
|
||||||
|
self._api_key = api_key
|
||||||
|
self._base = base
|
||||||
|
self._quote = quote
|
||||||
|
self.data = None
|
||||||
|
|
||||||
|
@Throttle(MIN_TIME_BETWEEN_UPDATES)
|
||||||
|
def update(self):
|
||||||
|
"""Get the latest data from openexchangerates."""
|
||||||
|
try:
|
||||||
|
result = requests.get(self._resource, params={'base': self._base,
|
||||||
|
'app_id':
|
||||||
|
self._api_key},
|
||||||
|
timeout=10)
|
||||||
|
self.data = result.json()['rates']
|
||||||
|
except requests.exceptions.HTTPError:
|
||||||
|
_LOGGER.error("Check Openexchangerates API Key")
|
||||||
|
self.data = None
|
||||||
|
return False
|
|
@ -108,7 +108,7 @@ def setup(hass, config):
|
||||||
class SwitchDevice(ToggleEntity):
|
class SwitchDevice(ToggleEntity):
|
||||||
"""Representation of a switch."""
|
"""Representation of a switch."""
|
||||||
|
|
||||||
# pylint: disable=no-self-use
|
# pylint: disable=no-self-use, abstract-method
|
||||||
@property
|
@property
|
||||||
def current_power_mwh(self):
|
def current_power_mwh(self):
|
||||||
"""Return the current power usage in mWh."""
|
"""Return the current power usage in mWh."""
|
||||||
|
|
|
@ -4,7 +4,6 @@ Use serial protocol of acer projector to obtain state of the projector.
|
||||||
This component allows to control almost all projectors from acer using
|
This component allows to control almost all projectors from acer using
|
||||||
their RS232 serial communication protocol.
|
their RS232 serial communication protocol.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
@ -61,7 +60,8 @@ class AcerSwitch(SwitchDevice):
|
||||||
write_timeout=write_timeout, **kwargs)
|
write_timeout=write_timeout, **kwargs)
|
||||||
self._serial_port = serial_port
|
self._serial_port = serial_port
|
||||||
self._name = name
|
self._name = name
|
||||||
self._state = STATE_UNKNOWN
|
self._state = False
|
||||||
|
self._available = False
|
||||||
self._attributes = {
|
self._attributes = {
|
||||||
LAMP_HOURS: STATE_UNKNOWN,
|
LAMP_HOURS: STATE_UNKNOWN,
|
||||||
INPUT_SOURCE: STATE_UNKNOWN,
|
INPUT_SOURCE: STATE_UNKNOWN,
|
||||||
|
@ -100,14 +100,19 @@ class AcerSwitch(SwitchDevice):
|
||||||
return match.group(1)
|
return match.group(1)
|
||||||
return STATE_UNKNOWN
|
return STATE_UNKNOWN
|
||||||
|
|
||||||
|
@property
|
||||||
|
def available(self):
|
||||||
|
"""Return if projector is available."""
|
||||||
|
return self._available
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
"""Return name of the projector."""
|
"""Return name of the projector."""
|
||||||
return self._name
|
return self._name
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def state(self):
|
def is_on(self):
|
||||||
"""Return the current state of the projector."""
|
"""Return if the projector is turned on."""
|
||||||
return self._state
|
return self._state
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -120,11 +125,13 @@ class AcerSwitch(SwitchDevice):
|
||||||
msg = CMD_DICT[LAMP]
|
msg = CMD_DICT[LAMP]
|
||||||
awns = self._write_read_format(msg)
|
awns = self._write_read_format(msg)
|
||||||
if awns == 'Lamp 1':
|
if awns == 'Lamp 1':
|
||||||
self._state = STATE_ON
|
self._state = True
|
||||||
|
self._available = True
|
||||||
elif awns == 'Lamp 0':
|
elif awns == 'Lamp 0':
|
||||||
self._state = STATE_OFF
|
self._state = False
|
||||||
|
self._available = True
|
||||||
else:
|
else:
|
||||||
self._state = STATE_UNKNOWN
|
self._available = False
|
||||||
|
|
||||||
for key in self._attributes.keys():
|
for key in self._attributes.keys():
|
||||||
msg = CMD_DICT.get(key, None)
|
msg = CMD_DICT.get(key, None)
|
||||||
|
|
|
@ -4,6 +4,7 @@ Support for device running with the aREST RESTful framework.
|
||||||
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.arest/
|
https://home-assistant.io/components/switch.arest/
|
||||||
"""
|
"""
|
||||||
|
# pylint: disable=abstract-method
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
|
|
|
@ -65,6 +65,10 @@ class WOLSwitch(SwitchDevice):
|
||||||
self._wol.send_magic_packet(self._mac_address)
|
self._wol.send_magic_packet(self._mac_address)
|
||||||
self.update_ha_state()
|
self.update_ha_state()
|
||||||
|
|
||||||
|
def turn_off(self):
|
||||||
|
"""Do nothing."""
|
||||||
|
pass
|
||||||
|
|
||||||
def update(self):
|
def update(self):
|
||||||
"""Check if device is on and update the state."""
|
"""Check if device is on and update the state."""
|
||||||
if platform.system().lower() == "windows":
|
if platform.system().lower() == "windows":
|
||||||
|
|
|
@ -273,29 +273,29 @@ class ThermostatDevice(Entity):
|
||||||
"""Return true if the fan is on."""
|
"""Return true if the fan is on."""
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def set_temperate(self, temperature):
|
def set_temperature(self, temperature):
|
||||||
"""Set new target temperature."""
|
"""Set new target temperature."""
|
||||||
pass
|
raise NotImplementedError()
|
||||||
|
|
||||||
def set_hvac_mode(self, hvac_mode):
|
def set_hvac_mode(self, hvac_mode):
|
||||||
"""Set hvac mode."""
|
"""Set hvac mode."""
|
||||||
pass
|
raise NotImplementedError()
|
||||||
|
|
||||||
def turn_away_mode_on(self):
|
def turn_away_mode_on(self):
|
||||||
"""Turn away mode on."""
|
"""Turn away mode on."""
|
||||||
pass
|
raise NotImplementedError()
|
||||||
|
|
||||||
def turn_away_mode_off(self):
|
def turn_away_mode_off(self):
|
||||||
"""Turn away mode off."""
|
"""Turn away mode off."""
|
||||||
pass
|
raise NotImplementedError()
|
||||||
|
|
||||||
def turn_fan_on(self):
|
def turn_fan_on(self):
|
||||||
"""Turn fan on."""
|
"""Turn fan on."""
|
||||||
pass
|
raise NotImplementedError()
|
||||||
|
|
||||||
def turn_fan_off(self):
|
def turn_fan_off(self):
|
||||||
"""Turn fan off."""
|
"""Turn fan off."""
|
||||||
pass
|
raise NotImplementedError()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def min_temp(self):
|
def min_temp(self):
|
||||||
|
|
|
@ -16,7 +16,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=too-many-arguments
|
# pylint: disable=too-many-arguments, abstract-method
|
||||||
class DemoThermostat(ThermostatDevice):
|
class DemoThermostat(ThermostatDevice):
|
||||||
"""Representation of a demo thermostat."""
|
"""Representation of a demo thermostat."""
|
||||||
|
|
||||||
|
|
|
@ -68,7 +68,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
schema=SET_FAN_MIN_ON_TIME_SCHEMA)
|
schema=SET_FAN_MIN_ON_TIME_SCHEMA)
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=too-many-public-methods
|
# pylint: disable=too-many-public-methods, abstract-method
|
||||||
class Thermostat(ThermostatDevice):
|
class Thermostat(ThermostatDevice):
|
||||||
"""A thermostat class for Ecobee."""
|
"""A thermostat class for Ecobee."""
|
||||||
|
|
||||||
|
|
|
@ -31,7 +31,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=too-many-instance-attributes, import-error
|
# pylint: disable=too-many-instance-attributes, import-error, abstract-method
|
||||||
class EQ3BTSmartThermostat(ThermostatDevice):
|
class EQ3BTSmartThermostat(ThermostatDevice):
|
||||||
"""Representation of a EQ3 Bluetooth Smart thermostat."""
|
"""Representation of a EQ3 Bluetooth Smart thermostat."""
|
||||||
|
|
||||||
|
|
|
@ -41,7 +41,6 @@ PLATFORM_SCHEMA = vol.Schema({
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
# 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 the heat control thermostat."""
|
"""Setup the heat control thermostat."""
|
||||||
name = config.get(CONF_NAME)
|
name = config.get(CONF_NAME)
|
||||||
|
@ -55,7 +54,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
min_temp, max_temp, target_temp)])
|
min_temp, max_temp, target_temp)])
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=too-many-instance-attributes
|
# pylint: disable=too-many-instance-attributes, abstract-method
|
||||||
class HeatControl(ThermostatDevice):
|
class HeatControl(ThermostatDevice):
|
||||||
"""Representation of a HeatControl device."""
|
"""Representation of a HeatControl device."""
|
||||||
|
|
||||||
|
|
|
@ -58,7 +58,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
class HeatmiserV3Thermostat(ThermostatDevice):
|
class HeatmiserV3Thermostat(ThermostatDevice):
|
||||||
"""Representation of a HeatmiserV3 thermostat."""
|
"""Representation of a HeatmiserV3 thermostat."""
|
||||||
|
|
||||||
# pylint: disable=too-many-instance-attributes
|
# pylint: disable=too-many-instance-attributes, abstract-method
|
||||||
def __init__(self, heatmiser, device, name, serport):
|
def __init__(self, heatmiser, device, name, serport):
|
||||||
"""Initialize the thermostat."""
|
"""Initialize the thermostat."""
|
||||||
self.heatmiser = heatmiser
|
self.heatmiser = heatmiser
|
||||||
|
|
|
@ -49,7 +49,6 @@ def _setup_round(username, password, config, add_devices):
|
||||||
|
|
||||||
|
|
||||||
# config will be used later
|
# config will be used later
|
||||||
# pylint: disable=unused-argument
|
|
||||||
def _setup_us(username, password, config, add_devices):
|
def _setup_us(username, password, config, add_devices):
|
||||||
"""Setup user."""
|
"""Setup user."""
|
||||||
import somecomfort
|
import somecomfort
|
||||||
|
@ -74,7 +73,6 @@ def _setup_us(username, password, config, add_devices):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
# 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 the honeywel thermostat."""
|
"""Setup the honeywel thermostat."""
|
||||||
username = config.get(CONF_USERNAME)
|
username = config.get(CONF_USERNAME)
|
||||||
|
@ -98,7 +96,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
class RoundThermostat(ThermostatDevice):
|
class RoundThermostat(ThermostatDevice):
|
||||||
"""Representation of a Honeywell Round Connected thermostat."""
|
"""Representation of a Honeywell Round Connected thermostat."""
|
||||||
|
|
||||||
# pylint: disable=too-many-instance-attributes
|
# pylint: disable=too-many-instance-attributes, abstract-method
|
||||||
def __init__(self, device, zone_id, master, away_temp):
|
def __init__(self, device, zone_id, master, away_temp):
|
||||||
"""Initialize the thermostat."""
|
"""Initialize the thermostat."""
|
||||||
self.device = device
|
self.device = device
|
||||||
|
@ -182,6 +180,7 @@ class RoundThermostat(ThermostatDevice):
|
||||||
self._is_dhw = False
|
self._is_dhw = False
|
||||||
|
|
||||||
|
|
||||||
|
# pylint: disable=abstract-method
|
||||||
class HoneywellUSThermostat(ThermostatDevice):
|
class HoneywellUSThermostat(ThermostatDevice):
|
||||||
"""Representation of a Honeywell US Thermostat."""
|
"""Representation of a Honeywell US Thermostat."""
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
for structure, device in nest.devices()])
|
for structure, device in nest.devices()])
|
||||||
|
|
||||||
|
|
||||||
|
# pylint: disable=abstract-method
|
||||||
class NestThermostat(ThermostatDevice):
|
class NestThermostat(ThermostatDevice):
|
||||||
"""Representation of a Nest thermostat."""
|
"""Representation of a Nest thermostat."""
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|
||||||
|
# pylint: disable=abstract-method
|
||||||
class ProliphixThermostat(ThermostatDevice):
|
class ProliphixThermostat(ThermostatDevice):
|
||||||
"""Representation a Proliphix thermostat."""
|
"""Representation a Proliphix thermostat."""
|
||||||
|
|
||||||
|
|
|
@ -45,6 +45,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
add_devices(tstats)
|
add_devices(tstats)
|
||||||
|
|
||||||
|
|
||||||
|
# pylint: disable=abstract-method
|
||||||
class RadioThermostat(ThermostatDevice):
|
class RadioThermostat(ThermostatDevice):
|
||||||
"""Representation of a Radio Thermostat."""
|
"""Representation of a Radio Thermostat."""
|
||||||
|
|
||||||
|
|
|
@ -58,6 +58,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=too-many-arguments, too-many-instance-attributes
|
# pylint: disable=too-many-arguments, too-many-instance-attributes
|
||||||
|
# pylint: disable=abstract-method
|
||||||
class ZWaveThermostat(zwave.ZWaveDeviceEntity, ThermostatDevice):
|
class ZWaveThermostat(zwave.ZWaveDeviceEntity, ThermostatDevice):
|
||||||
"""Represents a HeatControl thermostat."""
|
"""Represents a HeatControl thermostat."""
|
||||||
|
|
||||||
|
@ -80,7 +81,7 @@ class ZWaveThermostat(zwave.ZWaveDeviceEntity, ThermostatDevice):
|
||||||
|
|
||||||
def value_changed(self, value):
|
def value_changed(self, value):
|
||||||
"""Called when a value has changed on the network."""
|
"""Called when a value has changed on the network."""
|
||||||
if self._value.node == value.node:
|
if self._value.value_id == value.value_id:
|
||||||
self.update_properties()
|
self.update_properties()
|
||||||
self.update_ha_state()
|
self.update_ha_state()
|
||||||
|
|
||||||
|
|
|
@ -39,23 +39,39 @@ SERVICE_TEST_NETWORK = "test_network"
|
||||||
|
|
||||||
EVENT_SCENE_ACTIVATED = "zwave.scene_activated"
|
EVENT_SCENE_ACTIVATED = "zwave.scene_activated"
|
||||||
|
|
||||||
COMMAND_CLASS_SWITCH_MULTILEVEL = 38
|
COMMAND_CLASS_WHATEVER = None
|
||||||
COMMAND_CLASS_DOOR_LOCK = 98
|
|
||||||
COMMAND_CLASS_SWITCH_BINARY = 37
|
|
||||||
COMMAND_CLASS_SENSOR_BINARY = 48
|
|
||||||
COMMAND_CLASS_SENSOR_MULTILEVEL = 49
|
COMMAND_CLASS_SENSOR_MULTILEVEL = 49
|
||||||
COMMAND_CLASS_METER = 50
|
COMMAND_CLASS_METER = 50
|
||||||
|
COMMAND_CLASS_ALARM = 113
|
||||||
|
COMMAND_CLASS_SWITCH_BINARY = 37
|
||||||
|
COMMAND_CLASS_SENSOR_BINARY = 48
|
||||||
|
COMMAND_CLASS_SWITCH_MULTILEVEL = 38
|
||||||
|
COMMAND_CLASS_DOOR_LOCK = 98
|
||||||
|
COMMAND_CLASS_THERMOSTAT_SETPOINT = 67
|
||||||
|
COMMAND_CLASS_THERMOSTAT_FAN_MODE = 68
|
||||||
COMMAND_CLASS_BATTERY = 128
|
COMMAND_CLASS_BATTERY = 128
|
||||||
COMMAND_CLASS_ALARM = 113 # 0x71
|
|
||||||
COMMAND_CLASS_THERMOSTAT_SETPOINT = 67 # 0x43
|
GENERIC_COMMAND_CLASS_WHATEVER = None
|
||||||
COMMAND_CLASS_THERMOSTAT_FAN_MODE = 68 # 0x44
|
GENERIC_COMMAND_CLASS_MULTILEVEL_SWITCH = 17
|
||||||
|
GENERIC_COMMAND_CLASS_BINARY_SWITCH = 16
|
||||||
|
GENERIC_COMMAND_CLASS_ENTRY_CONTROL = 64
|
||||||
|
GENERIC_COMMAND_CLASS_BINARY_SENSOR = 32
|
||||||
|
GENERIC_COMMAND_CLASS_MULTILEVEL_SENSOR = 33
|
||||||
|
GENERIC_COMMAND_CLASS_METER = 49
|
||||||
|
GENERIC_COMMAND_CLASS_ALARM_SENSOR = 161
|
||||||
|
GENERIC_COMMAND_CLASS_THERMOSTAT = 8
|
||||||
|
|
||||||
SPECIFIC_DEVICE_CLASS_WHATEVER = None
|
SPECIFIC_DEVICE_CLASS_WHATEVER = None
|
||||||
|
SPECIFIC_DEVICE_CLASS_NOT_USED = 0
|
||||||
SPECIFIC_DEVICE_CLASS_MULTILEVEL_POWER_SWITCH = 1
|
SPECIFIC_DEVICE_CLASS_MULTILEVEL_POWER_SWITCH = 1
|
||||||
|
SPECIFIC_DEVICE_CLASS_ADVANCED_DOOR_LOCK = 2
|
||||||
SPECIFIC_DEVICE_CLASS_MULTIPOSITION_MOTOR = 3
|
SPECIFIC_DEVICE_CLASS_MULTIPOSITION_MOTOR = 3
|
||||||
|
SPECIFIC_DEVICE_CLASS_SECURE_KEYPAD_DOOR_LOCK = 3
|
||||||
SPECIFIC_DEVICE_CLASS_MULTILEVEL_SCENE = 4
|
SPECIFIC_DEVICE_CLASS_MULTILEVEL_SCENE = 4
|
||||||
|
SPECIFIC_DEVICE_CLASS_SECURE_DOOR = 5
|
||||||
SPECIFIC_DEVICE_CLASS_MOTOR_CONTROL_CLASS_A = 5
|
SPECIFIC_DEVICE_CLASS_MOTOR_CONTROL_CLASS_A = 5
|
||||||
SPECIFIC_DEVICE_CLASS_MOTOR_CONTROL_CLASS_B = 6
|
SPECIFIC_DEVICE_CLASS_MOTOR_CONTROL_CLASS_B = 6
|
||||||
|
SPECIFIC_DEVICE_CLASS_SECURE_BARRIER_ADD_ON = 7
|
||||||
SPECIFIC_DEVICE_CLASS_MOTOR_CONTROL_CLASS_C = 7
|
SPECIFIC_DEVICE_CLASS_MOTOR_CONTROL_CLASS_C = 7
|
||||||
|
|
||||||
GENRE_WHATEVER = None
|
GENRE_WHATEVER = None
|
||||||
|
@ -71,51 +87,68 @@ TYPE_DECIMAL = "Decimal"
|
||||||
# value type, genre type, specific device class).
|
# value type, genre type, specific device class).
|
||||||
DISCOVERY_COMPONENTS = [
|
DISCOVERY_COMPONENTS = [
|
||||||
('sensor',
|
('sensor',
|
||||||
|
[GENERIC_COMMAND_CLASS_WHATEVER],
|
||||||
|
[SPECIFIC_DEVICE_CLASS_WHATEVER],
|
||||||
[COMMAND_CLASS_SENSOR_MULTILEVEL,
|
[COMMAND_CLASS_SENSOR_MULTILEVEL,
|
||||||
COMMAND_CLASS_METER,
|
COMMAND_CLASS_METER,
|
||||||
COMMAND_CLASS_ALARM],
|
COMMAND_CLASS_ALARM],
|
||||||
TYPE_WHATEVER,
|
TYPE_WHATEVER,
|
||||||
GENRE_USER,
|
GENRE_USER),
|
||||||
SPECIFIC_DEVICE_CLASS_WHATEVER),
|
|
||||||
('light',
|
('light',
|
||||||
|
[GENERIC_COMMAND_CLASS_MULTILEVEL_SWITCH],
|
||||||
|
[SPECIFIC_DEVICE_CLASS_MULTILEVEL_POWER_SWITCH,
|
||||||
|
SPECIFIC_DEVICE_CLASS_MULTILEVEL_SCENE],
|
||||||
[COMMAND_CLASS_SWITCH_MULTILEVEL],
|
[COMMAND_CLASS_SWITCH_MULTILEVEL],
|
||||||
TYPE_BYTE,
|
TYPE_BYTE,
|
||||||
GENRE_USER,
|
GENRE_USER),
|
||||||
[SPECIFIC_DEVICE_CLASS_MULTILEVEL_POWER_SWITCH,
|
|
||||||
SPECIFIC_DEVICE_CLASS_MULTILEVEL_SCENE]),
|
|
||||||
('switch',
|
('switch',
|
||||||
|
[GENERIC_COMMAND_CLASS_BINARY_SWITCH],
|
||||||
|
[SPECIFIC_DEVICE_CLASS_WHATEVER],
|
||||||
[COMMAND_CLASS_SWITCH_BINARY],
|
[COMMAND_CLASS_SWITCH_BINARY],
|
||||||
TYPE_BOOL,
|
TYPE_BOOL,
|
||||||
GENRE_USER,
|
GENRE_USER),
|
||||||
SPECIFIC_DEVICE_CLASS_WHATEVER),
|
|
||||||
('binary_sensor',
|
('binary_sensor',
|
||||||
|
[GENERIC_COMMAND_CLASS_BINARY_SENSOR,
|
||||||
|
GENERIC_COMMAND_CLASS_MULTILEVEL_SENSOR],
|
||||||
|
[SPECIFIC_DEVICE_CLASS_WHATEVER],
|
||||||
[COMMAND_CLASS_SENSOR_BINARY],
|
[COMMAND_CLASS_SENSOR_BINARY],
|
||||||
TYPE_BOOL,
|
TYPE_BOOL,
|
||||||
GENRE_USER,
|
GENRE_USER),
|
||||||
SPECIFIC_DEVICE_CLASS_WHATEVER),
|
|
||||||
('thermostat',
|
('thermostat',
|
||||||
|
[GENERIC_COMMAND_CLASS_THERMOSTAT],
|
||||||
|
[SPECIFIC_DEVICE_CLASS_WHATEVER],
|
||||||
[COMMAND_CLASS_THERMOSTAT_SETPOINT],
|
[COMMAND_CLASS_THERMOSTAT_SETPOINT],
|
||||||
TYPE_WHATEVER,
|
TYPE_WHATEVER,
|
||||||
GENRE_WHATEVER,
|
GENRE_WHATEVER),
|
||||||
SPECIFIC_DEVICE_CLASS_WHATEVER),
|
|
||||||
('hvac',
|
('hvac',
|
||||||
|
[GENERIC_COMMAND_CLASS_THERMOSTAT],
|
||||||
|
[SPECIFIC_DEVICE_CLASS_WHATEVER],
|
||||||
[COMMAND_CLASS_THERMOSTAT_FAN_MODE],
|
[COMMAND_CLASS_THERMOSTAT_FAN_MODE],
|
||||||
TYPE_WHATEVER,
|
TYPE_WHATEVER,
|
||||||
GENRE_WHATEVER,
|
GENRE_WHATEVER),
|
||||||
SPECIFIC_DEVICE_CLASS_WHATEVER),
|
|
||||||
('lock',
|
('lock',
|
||||||
|
[GENERIC_COMMAND_CLASS_ENTRY_CONTROL],
|
||||||
|
[SPECIFIC_DEVICE_CLASS_ADVANCED_DOOR_LOCK,
|
||||||
|
SPECIFIC_DEVICE_CLASS_SECURE_KEYPAD_DOOR_LOCK],
|
||||||
[COMMAND_CLASS_DOOR_LOCK],
|
[COMMAND_CLASS_DOOR_LOCK],
|
||||||
TYPE_BOOL,
|
TYPE_BOOL,
|
||||||
GENRE_USER,
|
GENRE_USER),
|
||||||
SPECIFIC_DEVICE_CLASS_WHATEVER),
|
|
||||||
('rollershutter',
|
('rollershutter',
|
||||||
[COMMAND_CLASS_SWITCH_MULTILEVEL],
|
[GENERIC_COMMAND_CLASS_MULTILEVEL_SWITCH],
|
||||||
TYPE_WHATEVER,
|
|
||||||
GENRE_USER,
|
|
||||||
[SPECIFIC_DEVICE_CLASS_MOTOR_CONTROL_CLASS_A,
|
[SPECIFIC_DEVICE_CLASS_MOTOR_CONTROL_CLASS_A,
|
||||||
SPECIFIC_DEVICE_CLASS_MOTOR_CONTROL_CLASS_B,
|
SPECIFIC_DEVICE_CLASS_MOTOR_CONTROL_CLASS_B,
|
||||||
SPECIFIC_DEVICE_CLASS_MOTOR_CONTROL_CLASS_C,
|
SPECIFIC_DEVICE_CLASS_MOTOR_CONTROL_CLASS_C,
|
||||||
SPECIFIC_DEVICE_CLASS_MULTIPOSITION_MOTOR]),
|
SPECIFIC_DEVICE_CLASS_MULTIPOSITION_MOTOR],
|
||||||
|
[COMMAND_CLASS_WHATEVER],
|
||||||
|
TYPE_WHATEVER,
|
||||||
|
GENRE_USER),
|
||||||
|
('garage_door',
|
||||||
|
[GENERIC_COMMAND_CLASS_ENTRY_CONTROL],
|
||||||
|
[SPECIFIC_DEVICE_CLASS_SECURE_BARRIER_ADD_ON,
|
||||||
|
SPECIFIC_DEVICE_CLASS_SECURE_DOOR],
|
||||||
|
[COMMAND_CLASS_SWITCH_BINARY],
|
||||||
|
TYPE_BOOL,
|
||||||
|
GENRE_USER)
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -244,25 +277,49 @@ def setup(hass, config):
|
||||||
def value_added(node, value):
|
def value_added(node, value):
|
||||||
"""Called when a value is added to a node on the network."""
|
"""Called when a value is added to a node on the network."""
|
||||||
for (component,
|
for (component,
|
||||||
command_ids,
|
generic_device_class,
|
||||||
|
specific_device_class,
|
||||||
|
command_class,
|
||||||
value_type,
|
value_type,
|
||||||
value_genre,
|
value_genre) in DISCOVERY_COMPONENTS:
|
||||||
specific_device_class) in DISCOVERY_COMPONENTS:
|
|
||||||
|
|
||||||
if value.command_class not in command_ids:
|
_LOGGER.debug("Component=%s Node_id=%s query start",
|
||||||
|
component, node.node_id)
|
||||||
|
if node.generic not in generic_device_class and \
|
||||||
|
None not in generic_device_class:
|
||||||
|
_LOGGER.debug("node.generic %s not None and in \
|
||||||
|
generic_device_class %s",
|
||||||
|
node.generic, generic_device_class)
|
||||||
continue
|
continue
|
||||||
if value_type is not None and value_type != value.type:
|
if node.specific not in specific_device_class and \
|
||||||
|
None not in specific_device_class:
|
||||||
|
_LOGGER.debug("node.specific %s is not None and in \
|
||||||
|
specific_device_class %s", node.specific,
|
||||||
|
specific_device_class)
|
||||||
continue
|
continue
|
||||||
if value_genre is not None and value_genre != value.genre:
|
if value.command_class not in command_class and \
|
||||||
|
None not in command_class:
|
||||||
|
_LOGGER.debug("value.command_class %s is not None \
|
||||||
|
and in command_class %s",
|
||||||
|
value.command_class, command_class)
|
||||||
continue
|
continue
|
||||||
if specific_device_class is not None and \
|
if value_type != value.type and value_type is not None:
|
||||||
specific_device_class != node.specific:
|
_LOGGER.debug("value.type %s != value_type %s",
|
||||||
|
value.type, value_type)
|
||||||
|
continue
|
||||||
|
if value_genre != value.genre and value_genre is not None:
|
||||||
|
_LOGGER.debug("value.genre %s != value_genre %s",
|
||||||
|
value.genre, value_genre)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Configure node
|
# Configure node
|
||||||
_LOGGER.debug("Node_id=%s Value type=%s Genre=%s \
|
_LOGGER.debug("Adding Node_id=%s Generic_command_class=%s, \
|
||||||
Specific Device_class=%s", node.node_id,
|
Specific_command_class=%s, \
|
||||||
value.type, value.genre, specific_device_class)
|
Command_class=%s, Value type=%s, \
|
||||||
|
Genre=%s", node.node_id,
|
||||||
|
node.generic, node.specific,
|
||||||
|
value.command_class, value.type,
|
||||||
|
value.genre)
|
||||||
name = "{}.{}".format(component, _object_id(value))
|
name = "{}.{}".format(component, _object_id(value))
|
||||||
|
|
||||||
node_config = customize.get(name, {})
|
node_config = customize.get(name, {})
|
||||||
|
|
|
@ -229,17 +229,15 @@ class ToggleEntity(Entity):
|
||||||
@property
|
@property
|
||||||
def is_on(self):
|
def is_on(self):
|
||||||
"""Return True if entity is on."""
|
"""Return True if entity is on."""
|
||||||
return False
|
raise NotImplementedError()
|
||||||
|
|
||||||
def turn_on(self, **kwargs):
|
def turn_on(self, **kwargs):
|
||||||
"""Turn the entity on."""
|
"""Turn the entity on."""
|
||||||
_LOGGER.warning('Method turn_on not implemented for %s',
|
raise NotImplementedError()
|
||||||
self.entity_id)
|
|
||||||
|
|
||||||
def turn_off(self, **kwargs):
|
def turn_off(self, **kwargs):
|
||||||
"""Turn the entity off."""
|
"""Turn the entity off."""
|
||||||
_LOGGER.warning('Method turn_off not implemented for %s',
|
raise NotImplementedError()
|
||||||
self.entity_id)
|
|
||||||
|
|
||||||
def toggle(self, **kwargs):
|
def toggle(self, **kwargs):
|
||||||
"""Toggle the entity off."""
|
"""Toggle the entity off."""
|
||||||
|
|
|
@ -5,10 +5,15 @@ from collections import OrderedDict
|
||||||
|
|
||||||
import glob
|
import glob
|
||||||
import yaml
|
import yaml
|
||||||
|
try:
|
||||||
|
import keyring
|
||||||
|
except ImportError:
|
||||||
|
keyring = None
|
||||||
|
|
||||||
from homeassistant.exceptions import HomeAssistantError
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
_SECRET_NAMESPACE = 'homeassistant'
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=too-many-ancestors
|
# pylint: disable=too-many-ancestors
|
||||||
|
@ -119,10 +124,49 @@ def _env_var_yaml(loader, node):
|
||||||
raise HomeAssistantError(node.value)
|
raise HomeAssistantError(node.value)
|
||||||
|
|
||||||
|
|
||||||
|
# pylint: disable=protected-access
|
||||||
|
def _secret_yaml(loader, node):
|
||||||
|
"""Load secrets and embed it into the configuration YAML."""
|
||||||
|
# Create secret cache on loader and load secret.yaml
|
||||||
|
if not hasattr(loader, '_SECRET_CACHE'):
|
||||||
|
loader._SECRET_CACHE = {}
|
||||||
|
|
||||||
|
secret_path = os.path.join(os.path.dirname(loader.name), 'secrets.yaml')
|
||||||
|
if secret_path not in loader._SECRET_CACHE:
|
||||||
|
if os.path.isfile(secret_path):
|
||||||
|
loader._SECRET_CACHE[secret_path] = load_yaml(secret_path)
|
||||||
|
secrets = loader._SECRET_CACHE[secret_path]
|
||||||
|
if 'logger' in secrets:
|
||||||
|
logger = str(secrets['logger']).lower()
|
||||||
|
if logger == 'debug':
|
||||||
|
_LOGGER.setLevel(logging.DEBUG)
|
||||||
|
else:
|
||||||
|
_LOGGER.error("secrets.yaml: 'logger: debug' expected,"
|
||||||
|
" but 'logger: %s' found", logger)
|
||||||
|
del secrets['logger']
|
||||||
|
else:
|
||||||
|
loader._SECRET_CACHE[secret_path] = None
|
||||||
|
secrets = loader._SECRET_CACHE[secret_path]
|
||||||
|
|
||||||
|
# Retrieve secret, first from secrets.yaml, then from keyring
|
||||||
|
if secrets is not None and node.value in secrets:
|
||||||
|
_LOGGER.debug('Secret %s retrieved from secrets.yaml.', node.value)
|
||||||
|
return secrets[node.value]
|
||||||
|
elif keyring:
|
||||||
|
# do ome keyring stuff
|
||||||
|
pwd = keyring.get_password(_SECRET_NAMESPACE, node.value)
|
||||||
|
if pwd:
|
||||||
|
_LOGGER.debug('Secret %s retrieved from keyring.', node.value)
|
||||||
|
return pwd
|
||||||
|
|
||||||
|
_LOGGER.error('Secret %s not defined.', node.value)
|
||||||
|
raise HomeAssistantError(node.value)
|
||||||
|
|
||||||
yaml.SafeLoader.add_constructor('!include', _include_yaml)
|
yaml.SafeLoader.add_constructor('!include', _include_yaml)
|
||||||
yaml.SafeLoader.add_constructor(yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG,
|
yaml.SafeLoader.add_constructor(yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG,
|
||||||
_ordered_dict)
|
_ordered_dict)
|
||||||
yaml.SafeLoader.add_constructor('!env_var', _env_var_yaml)
|
yaml.SafeLoader.add_constructor('!env_var', _env_var_yaml)
|
||||||
|
yaml.SafeLoader.add_constructor('!secret', _secret_yaml)
|
||||||
yaml.SafeLoader.add_constructor('!include_dir_list', _include_dir_list_yaml)
|
yaml.SafeLoader.add_constructor('!include_dir_list', _include_dir_list_yaml)
|
||||||
yaml.SafeLoader.add_constructor('!include_dir_merge_list',
|
yaml.SafeLoader.add_constructor('!include_dir_merge_list',
|
||||||
_include_dir_merge_list_yaml)
|
_include_dir_merge_list_yaml)
|
||||||
|
|
|
@ -247,6 +247,9 @@ pyasn1==0.1.9
|
||||||
# homeassistant.components.media_player.cast
|
# homeassistant.components.media_player.cast
|
||||||
pychromecast==0.7.2
|
pychromecast==0.7.2
|
||||||
|
|
||||||
|
# homeassistant.components.media_player.cmus
|
||||||
|
pycmus>=0.1.0
|
||||||
|
|
||||||
# homeassistant.components.envisalink
|
# homeassistant.components.envisalink
|
||||||
# homeassistant.components.zwave
|
# homeassistant.components.zwave
|
||||||
pydispatcher==2.0.5
|
pydispatcher==2.0.5
|
||||||
|
|
31
tests/components/media_player/test_cmus.py
Normal file
31
tests/components/media_player/test_cmus.py
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
"""The tests for the Demo Media player platform."""
|
||||||
|
import unittest
|
||||||
|
from unittest import mock
|
||||||
|
|
||||||
|
from homeassistant.components.media_player import cmus
|
||||||
|
from homeassistant import const
|
||||||
|
|
||||||
|
from tests.common import get_test_home_assistant
|
||||||
|
|
||||||
|
entity_id = 'media_player.cmus'
|
||||||
|
|
||||||
|
|
||||||
|
class TestCmusMediaPlayer(unittest.TestCase):
|
||||||
|
"""Test the media_player module."""
|
||||||
|
|
||||||
|
def setUp(self): # pylint: disable=invalid-name
|
||||||
|
"""Setup things to be run when tests are started."""
|
||||||
|
self.hass = get_test_home_assistant()
|
||||||
|
|
||||||
|
def tearDown(self): # pylint: disable=invalid-name
|
||||||
|
"""Stop everything that was started."""
|
||||||
|
self.hass.stop()
|
||||||
|
|
||||||
|
@mock.patch('homeassistant.components.media_player.cmus.CmusDevice')
|
||||||
|
def test_password_required_with_host(self, cmus_mock):
|
||||||
|
"""Test that a password is required when specifying a remote host."""
|
||||||
|
fake_config = {
|
||||||
|
const.CONF_HOST: 'a_real_hostname',
|
||||||
|
}
|
||||||
|
self.assertFalse(
|
||||||
|
cmus.setup_platform(self.hass, fake_config, mock.MagicMock()))
|
|
@ -3,8 +3,9 @@ import io
|
||||||
import unittest
|
import unittest
|
||||||
import os
|
import os
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
from homeassistant.util import yaml
|
from homeassistant.util import yaml
|
||||||
|
import homeassistant.config as config_util
|
||||||
|
from tests.common import get_test_config_dir
|
||||||
|
|
||||||
|
|
||||||
class TestYaml(unittest.TestCase):
|
class TestYaml(unittest.TestCase):
|
||||||
|
@ -135,3 +136,81 @@ class TestYaml(unittest.TestCase):
|
||||||
"key2": "two",
|
"key2": "two",
|
||||||
"key3": "three"
|
"key3": "three"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def load_yaml(fname, string):
|
||||||
|
"""Write a string to file and return the parsed yaml."""
|
||||||
|
with open(fname, 'w') as file:
|
||||||
|
file.write(string)
|
||||||
|
return config_util.load_yaml_config_file(fname)
|
||||||
|
|
||||||
|
|
||||||
|
class FakeKeyring():
|
||||||
|
"""Fake a keyring class."""
|
||||||
|
|
||||||
|
def __init__(self, secrets_dict):
|
||||||
|
"""Store keyring dictionary."""
|
||||||
|
self._secrets = secrets_dict
|
||||||
|
|
||||||
|
# pylint: disable=protected-access
|
||||||
|
def get_password(self, domain, name):
|
||||||
|
"""Retrieve password."""
|
||||||
|
assert domain == yaml._SECRET_NAMESPACE
|
||||||
|
return self._secrets.get(name)
|
||||||
|
|
||||||
|
|
||||||
|
class TestSecrets(unittest.TestCase):
|
||||||
|
"""Test the secrets parameter in the yaml utility."""
|
||||||
|
|
||||||
|
def setUp(self): # pylint: disable=invalid-name
|
||||||
|
"""Create & load secrets file."""
|
||||||
|
config_dir = get_test_config_dir()
|
||||||
|
self._yaml_path = os.path.join(config_dir,
|
||||||
|
config_util.YAML_CONFIG_FILE)
|
||||||
|
self._secret_path = os.path.join(config_dir, 'secrets.yaml')
|
||||||
|
|
||||||
|
load_yaml(self._secret_path,
|
||||||
|
'http_pw: pwhttp\n'
|
||||||
|
'comp1_un: un1\n'
|
||||||
|
'comp1_pw: pw1\n'
|
||||||
|
'stale_pw: not_used\n'
|
||||||
|
'logger: debug\n')
|
||||||
|
self._yaml = load_yaml(self._yaml_path,
|
||||||
|
'http:\n'
|
||||||
|
' api_password: !secret http_pw\n'
|
||||||
|
'component:\n'
|
||||||
|
' username: !secret comp1_un\n'
|
||||||
|
' password: !secret comp1_pw\n'
|
||||||
|
'')
|
||||||
|
|
||||||
|
def tearDown(self): # pylint: disable=invalid-name
|
||||||
|
"""Clean up secrets."""
|
||||||
|
for path in [self._yaml_path, self._secret_path]:
|
||||||
|
if os.path.isfile(path):
|
||||||
|
os.remove(path)
|
||||||
|
|
||||||
|
def test_secrets_from_yaml(self):
|
||||||
|
"""Did secrets load ok."""
|
||||||
|
expected = {'api_password': 'pwhttp'}
|
||||||
|
self.assertEqual(expected, self._yaml['http'])
|
||||||
|
|
||||||
|
expected = {
|
||||||
|
'username': 'un1',
|
||||||
|
'password': 'pw1'}
|
||||||
|
self.assertEqual(expected, self._yaml['component'])
|
||||||
|
|
||||||
|
def test_secrets_keyring(self):
|
||||||
|
"""Test keyring fallback & get_password."""
|
||||||
|
yaml.keyring = None # Ensure its not there
|
||||||
|
yaml_str = 'http:\n api_password: !secret http_pw_keyring'
|
||||||
|
with self.assertRaises(yaml.HomeAssistantError):
|
||||||
|
load_yaml(self._yaml_path, yaml_str)
|
||||||
|
|
||||||
|
yaml.keyring = FakeKeyring({'http_pw_keyring': 'yeah'})
|
||||||
|
_yaml = load_yaml(self._yaml_path, yaml_str)
|
||||||
|
self.assertEqual({'http': {'api_password': 'yeah'}}, _yaml)
|
||||||
|
|
||||||
|
def test_secrets_logger_removed(self):
|
||||||
|
"""Ensure logger: debug was removed."""
|
||||||
|
with self.assertRaises(yaml.HomeAssistantError):
|
||||||
|
load_yaml(self._yaml_path, 'api_password: !secret logger')
|
||||||
|
|
Loading…
Add table
Reference in a new issue