mirror of
https://github.com/home-assistant/core.git
synced 2025-07-18 10:47:10 +00:00
Merge remote-tracking branch 'balloob/dev' into update_notify
Conflicts: homeassistant/components/frontend/version.py homeassistant/components/frontend/www_static/frontend.html
This commit is contained in:
commit
9b5385c565
@ -84,6 +84,7 @@ omit =
|
|||||||
homeassistant/components/sensor/glances.py
|
homeassistant/components/sensor/glances.py
|
||||||
homeassistant/components/sensor/mysensors.py
|
homeassistant/components/sensor/mysensors.py
|
||||||
homeassistant/components/sensor/openweathermap.py
|
homeassistant/components/sensor/openweathermap.py
|
||||||
|
homeassistant/components/switch/orvibo.py
|
||||||
homeassistant/components/sensor/rest.py
|
homeassistant/components/sensor/rest.py
|
||||||
homeassistant/components/sensor/rpi_gpio.py
|
homeassistant/components/sensor/rpi_gpio.py
|
||||||
homeassistant/components/sensor/sabnzbd.py
|
homeassistant/components/sensor/sabnzbd.py
|
||||||
|
@ -87,9 +87,12 @@ def setup(hass, config):
|
|||||||
|
|
||||||
if camera:
|
if camera:
|
||||||
response = camera.camera_image()
|
response = camera.camera_image()
|
||||||
|
if response is not None:
|
||||||
handler.wfile.write(response)
|
handler.wfile.write(response)
|
||||||
else:
|
else:
|
||||||
handler.send_response(HTTP_NOT_FOUND)
|
handler.send_response(HTTP_NOT_FOUND)
|
||||||
|
else:
|
||||||
|
handler.send_response(HTTP_NOT_FOUND)
|
||||||
|
|
||||||
hass.http.register_path(
|
hass.http.register_path(
|
||||||
'GET',
|
'GET',
|
||||||
@ -129,7 +132,8 @@ def setup(hass, config):
|
|||||||
while True:
|
while True:
|
||||||
|
|
||||||
img_bytes = camera.camera_image()
|
img_bytes = camera.camera_image()
|
||||||
|
if img_bytes is None:
|
||||||
|
continue
|
||||||
headers_str = '\r\n'.join((
|
headers_str = '\r\n'.join((
|
||||||
'Content-length: {}'.format(len(img_bytes)),
|
'Content-length: {}'.format(len(img_bytes)),
|
||||||
'Content-type: image/jpeg',
|
'Content-type: image/jpeg',
|
||||||
|
@ -42,11 +42,19 @@ class GenericCamera(Camera):
|
|||||||
def camera_image(self):
|
def camera_image(self):
|
||||||
""" Return a still image reponse from the camera. """
|
""" Return a still image reponse from the camera. """
|
||||||
if self._username and self._password:
|
if self._username and self._password:
|
||||||
|
try:
|
||||||
response = requests.get(
|
response = requests.get(
|
||||||
self._still_image_url,
|
self._still_image_url,
|
||||||
auth=HTTPBasicAuth(self._username, self._password))
|
auth=HTTPBasicAuth(self._username, self._password))
|
||||||
|
except requests.exceptions.RequestException as error:
|
||||||
|
_LOGGER.error('Error getting camera image: %s', error)
|
||||||
|
return None
|
||||||
else:
|
else:
|
||||||
|
try:
|
||||||
response = requests.get(self._still_image_url)
|
response = requests.get(self._still_image_url)
|
||||||
|
except requests.exceptions.RequestException as error:
|
||||||
|
_LOGGER.error('Error getting camera image: %s', error)
|
||||||
|
return None
|
||||||
|
|
||||||
return response.content
|
return response.content
|
||||||
|
|
||||||
|
@ -37,6 +37,7 @@ CONF_EVENT_DATA = "event_data"
|
|||||||
CONF_DELAY = "delay"
|
CONF_DELAY = "delay"
|
||||||
|
|
||||||
ATTR_LAST_ACTION = 'last_action'
|
ATTR_LAST_ACTION = 'last_action'
|
||||||
|
ATTR_CAN_CANCEL = 'can_cancel'
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -113,6 +114,8 @@ class Script(ToggleEntity):
|
|||||||
self._cur = -1
|
self._cur = -1
|
||||||
self._last_action = None
|
self._last_action = None
|
||||||
self._listener = None
|
self._listener = None
|
||||||
|
self._can_cancel = not any(CONF_DELAY in action for action
|
||||||
|
in self.sequence)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def should_poll(self):
|
def should_poll(self):
|
||||||
@ -126,7 +129,9 @@ class Script(ToggleEntity):
|
|||||||
@property
|
@property
|
||||||
def state_attributes(self):
|
def state_attributes(self):
|
||||||
""" Returns the state attributes. """
|
""" Returns the state attributes. """
|
||||||
attrs = {}
|
attrs = {
|
||||||
|
ATTR_CAN_CANCEL: self._can_cancel
|
||||||
|
}
|
||||||
|
|
||||||
if self._last_action:
|
if self._last_action:
|
||||||
attrs[ATTR_LAST_ACTION] = self._last_action
|
attrs[ATTR_LAST_ACTION] = self._last_action
|
||||||
|
@ -70,6 +70,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||||||
elif value.command_class == zwave.COMMAND_CLASS_SENSOR_MULTILEVEL:
|
elif value.command_class == zwave.COMMAND_CLASS_SENSOR_MULTILEVEL:
|
||||||
add_devices([ZWaveMultilevelSensor(value)])
|
add_devices([ZWaveMultilevelSensor(value)])
|
||||||
|
|
||||||
|
elif (value.command_class == zwave.COMMAND_CLASS_METER and
|
||||||
|
value.type == zwave.TYPE_DECIMAL):
|
||||||
|
add_devices([ZWaveMultilevelSensor(value)])
|
||||||
|
|
||||||
|
|
||||||
class ZWaveSensor(Entity):
|
class ZWaveSensor(Entity):
|
||||||
""" Represents a Z-Wave sensor. """
|
""" Represents a Z-Wave sensor. """
|
||||||
|
@ -16,7 +16,8 @@ from homeassistant.helpers.entity import ToggleEntity
|
|||||||
|
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
STATE_ON, SERVICE_TURN_ON, SERVICE_TURN_OFF, ATTR_ENTITY_ID)
|
STATE_ON, SERVICE_TURN_ON, SERVICE_TURN_OFF, ATTR_ENTITY_ID)
|
||||||
from homeassistant.components import group, discovery, wink, isy994, verisure
|
from homeassistant.components import (
|
||||||
|
group, discovery, wink, isy994, verisure, zwave)
|
||||||
|
|
||||||
DOMAIN = 'switch'
|
DOMAIN = 'switch'
|
||||||
DEPENDENCIES = []
|
DEPENDENCIES = []
|
||||||
@ -38,7 +39,8 @@ DISCOVERY_PLATFORMS = {
|
|||||||
discovery.SERVICE_WEMO: 'wemo',
|
discovery.SERVICE_WEMO: 'wemo',
|
||||||
wink.DISCOVER_SWITCHES: 'wink',
|
wink.DISCOVER_SWITCHES: 'wink',
|
||||||
isy994.DISCOVER_SWITCHES: 'isy994',
|
isy994.DISCOVER_SWITCHES: 'isy994',
|
||||||
verisure.DISCOVER_SWITCHES: 'verisure'
|
verisure.DISCOVER_SWITCHES: 'verisure',
|
||||||
|
zwave.DISCOVER_SWITCHES: 'zwave',
|
||||||
}
|
}
|
||||||
|
|
||||||
PROP_TO_ATTR = {
|
PROP_TO_ATTR = {
|
||||||
|
75
homeassistant/components/switch/orvibo.py
Normal file
75
homeassistant/components/switch/orvibo.py
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
"""
|
||||||
|
homeassistant.components.switch.orvibo
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
Support for Orvibo S20 Wifi Smart Switches.
|
||||||
|
|
||||||
|
For more details about this platform, please refer to the documentation at
|
||||||
|
https://home-assistant.io/components/switch.orvibo/
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from homeassistant.components.switch import SwitchDevice
|
||||||
|
|
||||||
|
from orvibo.s20 import S20, S20Exception
|
||||||
|
|
||||||
|
DEFAULT_NAME = "Orvibo S20 Switch"
|
||||||
|
REQUIREMENTS = ['orvibo==1.0.0']
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
# pylint: disable=unused-argument
|
||||||
|
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
||||||
|
""" Find and return S20 switches. """
|
||||||
|
if config.get('host') is None:
|
||||||
|
_LOGGER.error("Missing required variable: host")
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
s20 = S20(config.get('host'))
|
||||||
|
add_devices_callback([S20Switch(config.get('name', DEFAULT_NAME),
|
||||||
|
s20)])
|
||||||
|
except S20Exception:
|
||||||
|
_LOGGER.exception("S20 couldn't be initialized")
|
||||||
|
|
||||||
|
|
||||||
|
class S20Switch(SwitchDevice):
|
||||||
|
""" Represents an S20 switch. """
|
||||||
|
def __init__(self, name, s20):
|
||||||
|
self._name = name
|
||||||
|
self._s20 = s20
|
||||||
|
self._state = False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def should_poll(self):
|
||||||
|
""" Poll. """
|
||||||
|
return True
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
""" The name of the switch. """
|
||||||
|
return self._name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_on(self):
|
||||||
|
""" True if device is on. """
|
||||||
|
return self._state
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
""" Update device state. """
|
||||||
|
try:
|
||||||
|
self._state = self._s20.on
|
||||||
|
except S20Exception:
|
||||||
|
_LOGGER.exception("Error while fetching S20 state")
|
||||||
|
|
||||||
|
def turn_on(self, **kwargs):
|
||||||
|
""" Turn the device on. """
|
||||||
|
try:
|
||||||
|
self._s20.on = True
|
||||||
|
except S20Exception:
|
||||||
|
_LOGGER.exception("Error while turning on S20")
|
||||||
|
|
||||||
|
def turn_off(self, **kwargs):
|
||||||
|
""" Turn the device off. """
|
||||||
|
try:
|
||||||
|
self._s20.on = False
|
||||||
|
except S20Exception:
|
||||||
|
_LOGGER.exception("Error while turning off S20")
|
76
homeassistant/components/switch/zwave.py
Normal file
76
homeassistant/components/switch/zwave.py
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
"""
|
||||||
|
homeassistant.components.switch.zwave
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Zwave platform that handles simple binary switches.
|
||||||
|
"""
|
||||||
|
# pylint: disable=import-error
|
||||||
|
from openzwave.network import ZWaveNetwork
|
||||||
|
from pydispatch import dispatcher
|
||||||
|
|
||||||
|
import homeassistant.components.zwave as zwave
|
||||||
|
|
||||||
|
from homeassistant.components.switch import SwitchDevice
|
||||||
|
|
||||||
|
|
||||||
|
# pylint: disable=unused-argument
|
||||||
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
|
""" Find and return demo switches. """
|
||||||
|
if discovery_info 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([ZwaveSwitch(value)])
|
||||||
|
|
||||||
|
|
||||||
|
class ZwaveSwitch(SwitchDevice):
|
||||||
|
""" Provides a zwave switch. """
|
||||||
|
def __init__(self, value):
|
||||||
|
self._value = value
|
||||||
|
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()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def should_poll(self):
|
||||||
|
""" No polling needed for a demo switch. """
|
||||||
|
return False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
""" Returns the name of the device if any. """
|
||||||
|
name = self._node.name or "{}".format(self._node.product_name)
|
||||||
|
|
||||||
|
return "{}".format(name or self._value.label)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_on(self):
|
||||||
|
""" True if device is on. """
|
||||||
|
return self._state
|
||||||
|
|
||||||
|
def turn_on(self, **kwargs):
|
||||||
|
""" Turn the device on. """
|
||||||
|
self._node.set_switch(self._value.value_id, True)
|
||||||
|
|
||||||
|
def turn_off(self, **kwargs):
|
||||||
|
""" Turn the device off. """
|
||||||
|
self._node.set_switch(self._value.value_id, False)
|
@ -20,13 +20,18 @@ REQUIREMENTS = ['pydispatcher==2.0.5']
|
|||||||
CONF_USB_STICK_PATH = "usb_path"
|
CONF_USB_STICK_PATH = "usb_path"
|
||||||
DEFAULT_CONF_USB_STICK_PATH = "/zwaveusbstick"
|
DEFAULT_CONF_USB_STICK_PATH = "/zwaveusbstick"
|
||||||
CONF_DEBUG = "debug"
|
CONF_DEBUG = "debug"
|
||||||
|
CONF_POLLING_INTERVAL = "polling_interval"
|
||||||
|
|
||||||
DISCOVER_SENSORS = "zwave.sensors"
|
DISCOVER_SENSORS = "zwave.sensors"
|
||||||
|
DISCOVER_SWITCHES = "zwave.switch"
|
||||||
DISCOVER_LIGHTS = "zwave.light"
|
DISCOVER_LIGHTS = "zwave.light"
|
||||||
|
|
||||||
COMMAND_CLASS_SWITCH_MULTILEVEL = 38
|
COMMAND_CLASS_SWITCH_MULTILEVEL = 38
|
||||||
|
|
||||||
|
COMMAND_CLASS_SWITCH_BINARY = 37
|
||||||
COMMAND_CLASS_SENSOR_BINARY = 48
|
COMMAND_CLASS_SENSOR_BINARY = 48
|
||||||
COMMAND_CLASS_SENSOR_MULTILEVEL = 49
|
COMMAND_CLASS_SENSOR_MULTILEVEL = 49
|
||||||
|
COMMAND_CLASS_METER = 50
|
||||||
COMMAND_CLASS_BATTERY = 128
|
COMMAND_CLASS_BATTERY = 128
|
||||||
|
|
||||||
GENRE_WHATEVER = None
|
GENRE_WHATEVER = None
|
||||||
@ -35,20 +40,28 @@ GENRE_USER = "User"
|
|||||||
TYPE_WHATEVER = None
|
TYPE_WHATEVER = None
|
||||||
TYPE_BYTE = "Byte"
|
TYPE_BYTE = "Byte"
|
||||||
TYPE_BOOL = "Bool"
|
TYPE_BOOL = "Bool"
|
||||||
|
TYPE_DECIMAL = "Decimal"
|
||||||
|
|
||||||
# list of tuple (DOMAIN, discovered service, supported command
|
# list of tuple (DOMAIN, discovered service, supported command
|
||||||
# classes, value type)
|
# classes, value type)
|
||||||
DISCOVERY_COMPONENTS = [
|
DISCOVERY_COMPONENTS = [
|
||||||
('sensor',
|
('sensor',
|
||||||
DISCOVER_SENSORS,
|
DISCOVER_SENSORS,
|
||||||
[COMMAND_CLASS_SENSOR_BINARY, COMMAND_CLASS_SENSOR_MULTILEVEL],
|
[COMMAND_CLASS_SENSOR_BINARY,
|
||||||
|
COMMAND_CLASS_SENSOR_MULTILEVEL,
|
||||||
|
COMMAND_CLASS_METER],
|
||||||
TYPE_WHATEVER,
|
TYPE_WHATEVER,
|
||||||
GENRE_WHATEVER),
|
GENRE_USER),
|
||||||
('light',
|
('light',
|
||||||
DISCOVER_LIGHTS,
|
DISCOVER_LIGHTS,
|
||||||
[COMMAND_CLASS_SWITCH_MULTILEVEL],
|
[COMMAND_CLASS_SWITCH_MULTILEVEL],
|
||||||
TYPE_BYTE,
|
TYPE_BYTE,
|
||||||
GENRE_USER),
|
GENRE_USER),
|
||||||
|
('switch',
|
||||||
|
DISCOVER_SWITCHES,
|
||||||
|
[COMMAND_CLASS_SWITCH_BINARY],
|
||||||
|
TYPE_BOOL,
|
||||||
|
GENRE_USER),
|
||||||
]
|
]
|
||||||
|
|
||||||
ATTR_NODE_ID = "node_id"
|
ATTR_NODE_ID = "node_id"
|
||||||
@ -165,6 +178,10 @@ def setup(hass, config):
|
|||||||
""" Called when Home Assistant starts up. """
|
""" Called when Home Assistant starts up. """
|
||||||
NETWORK.start()
|
NETWORK.start()
|
||||||
|
|
||||||
|
polling_interval = config[DOMAIN].get(CONF_POLLING_INTERVAL, None)
|
||||||
|
if polling_interval is not None:
|
||||||
|
NETWORK.setPollInterval(polling_interval)
|
||||||
|
|
||||||
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_zwave)
|
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_zwave)
|
||||||
|
|
||||||
hass.bus.listen_once(EVENT_HOMEASSISTANT_START, start_zwave)
|
hass.bus.listen_once(EVENT_HOMEASSISTANT_START, start_zwave)
|
||||||
|
@ -156,3 +156,6 @@ evohomeclient==0.2.3
|
|||||||
|
|
||||||
# Pushetta (notify.pushetta)
|
# Pushetta (notify.pushetta)
|
||||||
pushetta==1.0.15
|
pushetta==1.0.15
|
||||||
|
|
||||||
|
# Orvibo S10
|
||||||
|
orvibo==1.0.0
|
||||||
|
@ -88,6 +88,8 @@ class TestScript(unittest.TestCase):
|
|||||||
|
|
||||||
self.assertEqual(1, len(calls))
|
self.assertEqual(1, len(calls))
|
||||||
self.assertEqual('world', calls[0].data.get('hello'))
|
self.assertEqual('world', calls[0].data.get('hello'))
|
||||||
|
self.assertEqual(
|
||||||
|
True, self.hass.states.get(ENTITY_ID).attributes.get('can_cancel'))
|
||||||
|
|
||||||
def test_calling_service_old(self):
|
def test_calling_service_old(self):
|
||||||
calls = []
|
calls = []
|
||||||
@ -172,6 +174,9 @@ class TestScript(unittest.TestCase):
|
|||||||
self.hass.pool.block_till_done()
|
self.hass.pool.block_till_done()
|
||||||
|
|
||||||
self.assertTrue(script.is_on(self.hass, ENTITY_ID))
|
self.assertTrue(script.is_on(self.hass, ENTITY_ID))
|
||||||
|
self.assertEqual(
|
||||||
|
False,
|
||||||
|
self.hass.states.get(ENTITY_ID).attributes.get('can_cancel'))
|
||||||
|
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
event,
|
event,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user