Merge pull request #15088 from home-assistant/rc

0.72
This commit is contained in:
Paulus Schoutsen
2018-06-22 13:38:44 -04:00
committed by GitHub
460 changed files with 9375 additions and 2223 deletions

View File

@@ -61,6 +61,9 @@ omit =
homeassistant/components/coinbase.py homeassistant/components/coinbase.py
homeassistant/components/sensor/coinbase.py homeassistant/components/sensor/coinbase.py
homeassistant/components/cast/*
homeassistant/components/*/cast.py
homeassistant/components/comfoconnect.py homeassistant/components/comfoconnect.py
homeassistant/components/*/comfoconnect.py homeassistant/components/*/comfoconnect.py
@@ -97,7 +100,7 @@ omit =
homeassistant/components/*/envisalink.py homeassistant/components/*/envisalink.py
homeassistant/components/fritzbox.py homeassistant/components/fritzbox.py
homeassistant/components/*/fritzbox.py homeassistant/components/switch/fritzbox.py
homeassistant/components/eufy.py homeassistant/components/eufy.py
homeassistant/components/*/eufy.py homeassistant/components/*/eufy.py
@@ -195,12 +198,15 @@ omit =
homeassistant/components/neato.py homeassistant/components/neato.py
homeassistant/components/*/neato.py homeassistant/components/*/neato.py
homeassistant/components/nest.py homeassistant/components/nest/__init__.py
homeassistant/components/*/nest.py homeassistant/components/*/nest.py
homeassistant/components/netatmo.py homeassistant/components/netatmo.py
homeassistant/components/*/netatmo.py homeassistant/components/*/netatmo.py
homeassistant/components/netgear_lte.py
homeassistant/components/*/netgear_lte.py
homeassistant/components/octoprint.py homeassistant/components/octoprint.py
homeassistant/components/*/octoprint.py homeassistant/components/*/octoprint.py
@@ -249,6 +255,9 @@ omit =
homeassistant/components/smappee.py homeassistant/components/smappee.py
homeassistant/components/*/smappee.py homeassistant/components/*/smappee.py
homeassistant/components/sonos/__init__.py
homeassistant/components/*/sonos.py
homeassistant/components/tado.py homeassistant/components/tado.py
homeassistant/components/*/tado.py homeassistant/components/*/tado.py
@@ -311,6 +320,9 @@ omit =
homeassistant/components/wink/* homeassistant/components/wink/*
homeassistant/components/*/wink.py homeassistant/components/*/wink.py
homeassistant/components/wirelesstag.py
homeassistant/components/*/wirelesstag.py
homeassistant/components/xiaomi_aqara.py homeassistant/components/xiaomi_aqara.py
homeassistant/components/*/xiaomi_aqara.py homeassistant/components/*/xiaomi_aqara.py
@@ -348,6 +360,7 @@ omit =
homeassistant/components/binary_sensor/ping.py homeassistant/components/binary_sensor/ping.py
homeassistant/components/binary_sensor/rest.py homeassistant/components/binary_sensor/rest.py
homeassistant/components/binary_sensor/tapsaff.py homeassistant/components/binary_sensor/tapsaff.py
homeassistant/components/binary_sensor/uptimerobot.py
homeassistant/components/browser.py homeassistant/components/browser.py
homeassistant/components/calendar/caldav.py homeassistant/components/calendar/caldav.py
homeassistant/components/calendar/todoist.py homeassistant/components/calendar/todoist.py
@@ -363,6 +376,7 @@ omit =
homeassistant/components/camera/rpi_camera.py homeassistant/components/camera/rpi_camera.py
homeassistant/components/camera/synology.py homeassistant/components/camera/synology.py
homeassistant/components/camera/xeoma.py homeassistant/components/camera/xeoma.py
homeassistant/components/camera/xiaomi.py
homeassistant/components/camera/yi.py homeassistant/components/camera/yi.py
homeassistant/components/climate/econet.py homeassistant/components/climate/econet.py
homeassistant/components/climate/ephember.py homeassistant/components/climate/ephember.py
@@ -378,6 +392,7 @@ omit =
homeassistant/components/climate/sensibo.py homeassistant/components/climate/sensibo.py
homeassistant/components/climate/touchline.py homeassistant/components/climate/touchline.py
homeassistant/components/climate/venstar.py homeassistant/components/climate/venstar.py
homeassistant/components/climate/zhong_hong.py
homeassistant/components/cover/garadget.py homeassistant/components/cover/garadget.py
homeassistant/components/cover/gogogate2.py homeassistant/components/cover/gogogate2.py
homeassistant/components/cover/homematic.py homeassistant/components/cover/homematic.py
@@ -397,6 +412,7 @@ omit =
homeassistant/components/device_tracker/bt_home_hub_5.py homeassistant/components/device_tracker/bt_home_hub_5.py
homeassistant/components/device_tracker/cisco_ios.py homeassistant/components/device_tracker/cisco_ios.py
homeassistant/components/device_tracker/ddwrt.py homeassistant/components/device_tracker/ddwrt.py
homeassistant/components/device_tracker/freebox.py
homeassistant/components/device_tracker/fritz.py homeassistant/components/device_tracker/fritz.py
homeassistant/components/device_tracker/google_maps.py homeassistant/components/device_tracker/google_maps.py
homeassistant/components/device_tracker/gpslogger.py homeassistant/components/device_tracker/gpslogger.py
@@ -462,6 +478,7 @@ omit =
homeassistant/components/light/yeelightsunflower.py homeassistant/components/light/yeelightsunflower.py
homeassistant/components/light/zengge.py homeassistant/components/light/zengge.py
homeassistant/components/lirc.py homeassistant/components/lirc.py
homeassistant/components/lock/kiwi.py
homeassistant/components/lock/lockitron.py homeassistant/components/lock/lockitron.py
homeassistant/components/lock/nello.py homeassistant/components/lock/nello.py
homeassistant/components/lock/nuki.py homeassistant/components/lock/nuki.py
@@ -472,7 +489,6 @@ omit =
homeassistant/components/media_player/aquostv.py homeassistant/components/media_player/aquostv.py
homeassistant/components/media_player/bluesound.py homeassistant/components/media_player/bluesound.py
homeassistant/components/media_player/braviatv.py homeassistant/components/media_player/braviatv.py
homeassistant/components/media_player/cast.py
homeassistant/components/media_player/channels.py homeassistant/components/media_player/channels.py
homeassistant/components/media_player/clementine.py homeassistant/components/media_player/clementine.py
homeassistant/components/media_player/cmus.py homeassistant/components/media_player/cmus.py
@@ -481,10 +497,12 @@ omit =
homeassistant/components/media_player/directv.py homeassistant/components/media_player/directv.py
homeassistant/components/media_player/dunehd.py homeassistant/components/media_player/dunehd.py
homeassistant/components/media_player/emby.py homeassistant/components/media_player/emby.py
homeassistant/components/media_player/epson.py
homeassistant/components/media_player/firetv.py homeassistant/components/media_player/firetv.py
homeassistant/components/media_player/frontier_silicon.py homeassistant/components/media_player/frontier_silicon.py
homeassistant/components/media_player/gpmdp.py homeassistant/components/media_player/gpmdp.py
homeassistant/components/media_player/gstreamer.py homeassistant/components/media_player/gstreamer.py
homeassistant/components/media_player/horizon.py
homeassistant/components/media_player/itunes.py homeassistant/components/media_player/itunes.py
homeassistant/components/media_player/kodi.py homeassistant/components/media_player/kodi.py
homeassistant/components/media_player/lg_netcast.py homeassistant/components/media_player/lg_netcast.py
@@ -506,7 +524,6 @@ omit =
homeassistant/components/media_player/russound_rnet.py homeassistant/components/media_player/russound_rnet.py
homeassistant/components/media_player/snapcast.py homeassistant/components/media_player/snapcast.py
homeassistant/components/media_player/songpal.py homeassistant/components/media_player/songpal.py
homeassistant/components/media_player/sonos.py
homeassistant/components/media_player/spotify.py homeassistant/components/media_player/spotify.py
homeassistant/components/media_player/squeezebox.py homeassistant/components/media_player/squeezebox.py
homeassistant/components/media_player/ue_smart_radio.py homeassistant/components/media_player/ue_smart_radio.py
@@ -644,6 +661,7 @@ omit =
homeassistant/components/sensor/nederlandse_spoorwegen.py homeassistant/components/sensor/nederlandse_spoorwegen.py
homeassistant/components/sensor/netdata.py homeassistant/components/sensor/netdata.py
homeassistant/components/sensor/neurio_energy.py homeassistant/components/sensor/neurio_energy.py
homeassistant/components/sensor/nsw_fuel_station.py
homeassistant/components/sensor/nut.py homeassistant/components/sensor/nut.py
homeassistant/components/sensor/nzbget.py homeassistant/components/sensor/nzbget.py
homeassistant/components/sensor/ohmconnect.py homeassistant/components/sensor/ohmconnect.py
@@ -748,6 +766,7 @@ omit =
homeassistant/components/tts/picotts.py homeassistant/components/tts/picotts.py
homeassistant/components/vacuum/mqtt.py homeassistant/components/vacuum/mqtt.py
homeassistant/components/vacuum/roomba.py homeassistant/components/vacuum/roomba.py
homeassistant/components/watson_iot.py
homeassistant/components/weather/bom.py homeassistant/components/weather/bom.py
homeassistant/components/weather/buienradar.py homeassistant/components/weather/buienradar.py
homeassistant/components/weather/darksky.py homeassistant/components/weather/darksky.py

View File

@@ -70,6 +70,7 @@ homeassistant/components/sensor/filter.py @dgomes
homeassistant/components/sensor/gearbest.py @HerrHofrat homeassistant/components/sensor/gearbest.py @HerrHofrat
homeassistant/components/sensor/irish_rail_transport.py @ttroy50 homeassistant/components/sensor/irish_rail_transport.py @ttroy50
homeassistant/components/sensor/miflora.py @danielhiversen @ChristianKuehnel homeassistant/components/sensor/miflora.py @danielhiversen @ChristianKuehnel
homeassistant/components/sensor/nsw_fuel_station.py @nickw444
homeassistant/components/sensor/pollen.py @bachya homeassistant/components/sensor/pollen.py @bachya
homeassistant/components/sensor/qnap.py @colinodell homeassistant/components/sensor/qnap.py @colinodell
homeassistant/components/sensor/sma.py @kellerza homeassistant/components/sensor/sma.py @kellerza

View File

@@ -1,5 +1,4 @@
"""Provide methods to bootstrap a Home Assistant instance.""" """Provide methods to bootstrap a Home Assistant instance."""
import asyncio
import logging import logging
import logging.handlers import logging.handlers
import os import os
@@ -17,7 +16,7 @@ from homeassistant.components import persistent_notification
from homeassistant.const import EVENT_HOMEASSISTANT_CLOSE from homeassistant.const import EVENT_HOMEASSISTANT_CLOSE
from homeassistant.setup import async_setup_component from homeassistant.setup import async_setup_component
from homeassistant.util.logging import AsyncHandler from homeassistant.util.logging import AsyncHandler
from homeassistant.util.package import async_get_user_site, get_user_site from homeassistant.util.package import async_get_user_site, is_virtual_env
from homeassistant.util.yaml import clear_secret_cache from homeassistant.util.yaml import clear_secret_cache
from homeassistant.exceptions import HomeAssistantError from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.signal import async_register_signal_handling from homeassistant.helpers.signal import async_register_signal_handling
@@ -53,8 +52,9 @@ def from_config_dict(config: Dict[str, Any],
if config_dir is not None: if config_dir is not None:
config_dir = os.path.abspath(config_dir) config_dir = os.path.abspath(config_dir)
hass.config.config_dir = config_dir hass.config.config_dir = config_dir
if not is_virtual_env():
hass.loop.run_until_complete( hass.loop.run_until_complete(
async_mount_local_lib_path(config_dir, hass.loop)) async_mount_local_lib_path(config_dir))
# run task # run task
hass = hass.loop.run_until_complete( hass = hass.loop.run_until_complete(
@@ -197,7 +197,9 @@ async def async_from_config_file(config_path: str,
# Set config dir to directory holding config file # Set config dir to directory holding config file
config_dir = os.path.abspath(os.path.dirname(config_path)) config_dir = os.path.abspath(os.path.dirname(config_path))
hass.config.config_dir = config_dir hass.config.config_dir = config_dir
await async_mount_local_lib_path(config_dir, hass.loop)
if not is_virtual_env():
await async_mount_local_lib_path(config_dir)
async_enable_logging(hass, verbose, log_rotate_days, log_file, async_enable_logging(hass, verbose, log_rotate_days, log_file,
log_no_color) log_no_color)
@@ -211,9 +213,8 @@ async def async_from_config_file(config_path: str,
finally: finally:
clear_secret_cache() clear_secret_cache()
hass = await async_from_config_dict( return await async_from_config_dict(
config_dict, hass, enable_log=False, skip_pip=skip_pip) config_dict, hass, enable_log=False, skip_pip=skip_pip)
return hass
@core.callback @core.callback
@@ -308,23 +309,13 @@ def async_enable_logging(hass: core.HomeAssistant,
"Unable to setup error log %s (access denied)", err_log_path) "Unable to setup error log %s (access denied)", err_log_path)
def mount_local_lib_path(config_dir: str) -> str: async def async_mount_local_lib_path(config_dir: str) -> str:
"""Add local library to Python Path."""
deps_dir = os.path.join(config_dir, 'deps')
lib_dir = get_user_site(deps_dir)
if lib_dir not in sys.path:
sys.path.insert(0, lib_dir)
return deps_dir
async def async_mount_local_lib_path(config_dir: str,
loop: asyncio.AbstractEventLoop) -> str:
"""Add local library to Python Path. """Add local library to Python Path.
This function is a coroutine. This function is a coroutine.
""" """
deps_dir = os.path.join(config_dir, 'deps') deps_dir = os.path.join(config_dir, 'deps')
lib_dir = await async_get_user_site(deps_dir, loop=loop) lib_dir = await async_get_user_site(deps_dir)
if lib_dir not in sys.path: if lib_dir not in sys.path:
sys.path.insert(0, lib_dir) sys.path.insert(0, lib_dir)
return deps_dir return deps_dir

View File

@@ -4,15 +4,17 @@ Support for Arlo Alarm Control Panels.
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/alarm_control_panel.arlo/ https://home-assistant.io/components/alarm_control_panel.arlo/
""" """
import asyncio
import logging import logging
import voluptuous as vol import voluptuous as vol
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from homeassistant.core import callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.components.alarm_control_panel import ( from homeassistant.components.alarm_control_panel import (
AlarmControlPanel, PLATFORM_SCHEMA) AlarmControlPanel, PLATFORM_SCHEMA)
from homeassistant.components.arlo import (DATA_ARLO, CONF_ATTRIBUTION) from homeassistant.components.arlo import (
DATA_ARLO, CONF_ATTRIBUTION, SIGNAL_UPDATE_ARLO)
from homeassistant.const import ( from homeassistant.const import (
ATTR_ATTRIBUTION, STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, ATTR_ATTRIBUTION, STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME,
STATE_ALARM_DISARMED) STATE_ALARM_DISARMED)
@@ -36,21 +38,20 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
}) })
@asyncio.coroutine def setup_platform(hass, config, add_devices, discovery_info=None):
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Set up the Arlo Alarm Control Panels.""" """Set up the Arlo Alarm Control Panels."""
data = hass.data[DATA_ARLO] arlo = hass.data[DATA_ARLO]
if not data.base_stations: if not arlo.base_stations:
return return
home_mode_name = config.get(CONF_HOME_MODE_NAME) home_mode_name = config.get(CONF_HOME_MODE_NAME)
away_mode_name = config.get(CONF_AWAY_MODE_NAME) away_mode_name = config.get(CONF_AWAY_MODE_NAME)
base_stations = [] base_stations = []
for base_station in data.base_stations: for base_station in arlo.base_stations:
base_stations.append(ArloBaseStation(base_station, home_mode_name, base_stations.append(ArloBaseStation(base_station, home_mode_name,
away_mode_name)) away_mode_name))
async_add_devices(base_stations, True) add_devices(base_stations, True)
class ArloBaseStation(AlarmControlPanel): class ArloBaseStation(AlarmControlPanel):
@@ -68,6 +69,16 @@ class ArloBaseStation(AlarmControlPanel):
"""Return icon.""" """Return icon."""
return ICON return ICON
async def async_added_to_hass(self):
"""Register callbacks."""
async_dispatcher_connect(
self.hass, SIGNAL_UPDATE_ARLO, self._update_callback)
@callback
def _update_callback(self):
"""Call update method."""
self.async_schedule_update_ha_state(True)
@property @property
def state(self): def state(self):
"""Return the state of the device.""" """Return the state of the device."""
@@ -75,30 +86,22 @@ class ArloBaseStation(AlarmControlPanel):
def update(self): def update(self):
"""Update the state of the device.""" """Update the state of the device."""
# PyArlo sometimes returns None for mode. So retry 3 times before _LOGGER.debug("Updating Arlo Alarm Control Panel %s", self.name)
# returning None.
num_retries = 3
i = 0
while i < num_retries:
mode = self._base_station.mode mode = self._base_station.mode
if mode: if mode:
self._state = self._get_state_from_mode(mode) self._state = self._get_state_from_mode(mode)
return else:
i += 1
self._state = None self._state = None
@asyncio.coroutine async def async_alarm_disarm(self, code=None):
def async_alarm_disarm(self, code=None):
"""Send disarm command.""" """Send disarm command."""
self._base_station.mode = DISARMED self._base_station.mode = DISARMED
@asyncio.coroutine async def async_alarm_arm_away(self, code=None):
def async_alarm_arm_away(self, code=None):
"""Send arm away command. Uses custom mode.""" """Send arm away command. Uses custom mode."""
self._base_station.mode = self._away_mode_name self._base_station.mode = self._away_mode_name
@asyncio.coroutine async def async_alarm_arm_home(self, code=None):
def async_alarm_arm_home(self, code=None):
"""Send arm home command. Uses custom mode.""" """Send arm home command. Uses custom mode."""
self._base_station.mode = self._home_mode_name self._base_station.mode = self._home_mode_name
@@ -125,4 +128,4 @@ class ArloBaseStation(AlarmControlPanel):
return STATE_ALARM_ARMED_HOME return STATE_ALARM_ARMED_HOME
elif mode == self._away_mode_name: elif mode == self._away_mode_name:
return STATE_ALARM_ARMED_AWAY return STATE_ALARM_ARMED_AWAY
return None return mode

View File

@@ -18,7 +18,7 @@ from homeassistant.const import (
from homeassistant.helpers import discovery from homeassistant.helpers import discovery
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['amcrest==1.2.2'] REQUIREMENTS = ['amcrest==1.2.3']
DEPENDENCIES = ['ffmpeg'] DEPENDENCIES = ['ffmpeg']
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)

View File

@@ -5,14 +5,18 @@ For more details about this component, please refer to the documentation at
https://home-assistant.io/components/arlo/ https://home-assistant.io/components/arlo/
""" """
import logging import logging
from datetime import timedelta
import voluptuous as vol import voluptuous as vol
from requests.exceptions import HTTPError, ConnectTimeout from requests.exceptions import HTTPError, ConnectTimeout
from homeassistant.helpers import config_validation as cv from homeassistant.helpers import config_validation as cv
from homeassistant.const import CONF_USERNAME, CONF_PASSWORD from homeassistant.const import (
CONF_USERNAME, CONF_PASSWORD, CONF_SCAN_INTERVAL)
from homeassistant.helpers.event import track_time_interval
from homeassistant.helpers.dispatcher import dispatcher_send
REQUIREMENTS = ['pyarlo==0.1.2'] REQUIREMENTS = ['pyarlo==0.1.7']
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@@ -25,10 +29,16 @@ DOMAIN = 'arlo'
NOTIFICATION_ID = 'arlo_notification' NOTIFICATION_ID = 'arlo_notification'
NOTIFICATION_TITLE = 'Arlo Component Setup' NOTIFICATION_TITLE = 'Arlo Component Setup'
SCAN_INTERVAL = timedelta(seconds=60)
SIGNAL_UPDATE_ARLO = "arlo_update"
CONFIG_SCHEMA = vol.Schema({ CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({ DOMAIN: vol.Schema({
vol.Required(CONF_USERNAME): cv.string, vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string, vol.Required(CONF_PASSWORD): cv.string,
vol.Optional(CONF_SCAN_INTERVAL, default=SCAN_INTERVAL):
cv.time_period,
}), }),
}, extra=vol.ALLOW_EXTRA) }, extra=vol.ALLOW_EXTRA)
@@ -38,6 +48,7 @@ def setup(hass, config):
conf = config[DOMAIN] conf = config[DOMAIN]
username = conf.get(CONF_USERNAME) username = conf.get(CONF_USERNAME)
password = conf.get(CONF_PASSWORD) password = conf.get(CONF_PASSWORD)
scan_interval = conf.get(CONF_SCAN_INTERVAL)
try: try:
from pyarlo import PyArlo from pyarlo import PyArlo
@@ -45,7 +56,17 @@ def setup(hass, config):
arlo = PyArlo(username, password, preload=False) arlo = PyArlo(username, password, preload=False)
if not arlo.is_connected: if not arlo.is_connected:
return False return False
# assign refresh period to base station thread
arlo_base_station = next((
station for station in arlo.base_stations), None)
if arlo_base_station is None:
return False
arlo_base_station.refresh_rate = scan_interval.total_seconds()
hass.data[DATA_ARLO] = arlo hass.data[DATA_ARLO] = arlo
except (ConnectTimeout, HTTPError) as ex: except (ConnectTimeout, HTTPError) as ex:
_LOGGER.error("Unable to connect to Netgear Arlo: %s", str(ex)) _LOGGER.error("Unable to connect to Netgear Arlo: %s", str(ex))
hass.components.persistent_notification.create( hass.components.persistent_notification.create(
@@ -55,4 +76,17 @@ def setup(hass, config):
title=NOTIFICATION_TITLE, title=NOTIFICATION_TITLE,
notification_id=NOTIFICATION_ID) notification_id=NOTIFICATION_ID)
return False return False
def hub_refresh(event_time):
"""Call ArloHub to refresh information."""
_LOGGER.info("Updating Arlo Hub component")
hass.data[DATA_ARLO].update(update_cameras=True,
update_base_station=True)
dispatcher_send(hass, SIGNAL_UPDATE_ARLO)
# register service
hass.services.register(DOMAIN, 'update', hub_refresh)
# register scan interval for ArloHub
track_time_interval(hass, hub_refresh, scan_interval)
return True return True

View File

@@ -145,7 +145,7 @@ def request_configuration(hass, config, name, host, serialnumber):
def setup(hass, config): def setup(hass, config):
"""Set up for Axis devices.""" """Set up for Axis devices."""
def _shutdown(call): # pylint: disable=unused-argument def _shutdown(call):
"""Stop the event stream on shutdown.""" """Stop the event stream on shutdown."""
for serialnumber, device in AXIS_DEVICES.items(): for serialnumber, device in AXIS_DEVICES.items():
_LOGGER.info("Stopping event stream for %s.", serialnumber) _LOGGER.info("Stopping event stream for %s.", serialnumber)
@@ -272,8 +272,7 @@ class AxisDeviceEvent(Entity):
def _update_callback(self): def _update_callback(self):
"""Update the sensor's state, if needed.""" """Update the sensor's state, if needed."""
self.update() self.schedule_update_ha_state(True)
self.schedule_update_ha_state()
@property @property
def name(self): def name(self):

View File

@@ -35,7 +35,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
}) })
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Command line Binary Sensor.""" """Set up the Command line Binary Sensor."""
name = config.get(CONF_NAME) name = config.get(CONF_NAME)

View File

@@ -5,7 +5,6 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.eight_sleep/ https://home-assistant.io/components/binary_sensor.eight_sleep/
""" """
import logging import logging
import asyncio
from homeassistant.components.binary_sensor import BinarySensorDevice from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.components.eight_sleep import ( from homeassistant.components.eight_sleep import (
@@ -16,8 +15,8 @@ _LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['eight_sleep'] DEPENDENCIES = ['eight_sleep']
@asyncio.coroutine async def async_setup_platform(hass, config, async_add_devices,
def async_setup_platform(hass, config, async_add_devices, discovery_info=None): discovery_info=None):
"""Set up the eight sleep binary sensor.""" """Set up the eight sleep binary sensor."""
if discovery_info is None: if discovery_info is None:
return return
@@ -63,7 +62,6 @@ class EightHeatSensor(EightSleepHeatEntity, BinarySensorDevice):
"""Return true if the binary sensor is on.""" """Return true if the binary sensor is on."""
return self._state return self._state
@asyncio.coroutine async def async_update(self):
def async_update(self):
"""Retrieve latest state.""" """Retrieve latest state."""
self._state = self._usrobj.bed_presence self._state = self._usrobj.bed_presence

View File

@@ -23,7 +23,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
}) })
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the GC100 devices.""" """Set up the GC100 devices."""
binary_sensors = [] binary_sensors = []

View File

@@ -28,7 +28,6 @@ ISY_DEVICE_TYPES = {
} }
# pylint: disable=unused-argument
def setup_platform(hass, config: ConfigType, def setup_platform(hass, config: ConfigType,
add_devices: Callable[[list], None], discovery_info=None): add_devices: Callable[[list], None], discovery_info=None):
"""Set up the ISY994 binary sensor platform.""" """Set up the ISY994 binary sensor platform."""
@@ -299,7 +298,6 @@ class ISYBinarySensorHeartbeat(ISYDevice, BinarySensorDevice):
# No heartbeat timer is active # No heartbeat timer is active
pass pass
# pylint: disable=unused-argument
@callback @callback
def timer_elapsed(now) -> None: def timer_elapsed(now) -> None:
"""Heartbeat missed; set state to indicate dead battery.""" """Heartbeat missed; set state to indicate dead battery."""
@@ -314,7 +312,6 @@ class ISYBinarySensorHeartbeat(ISYDevice, BinarySensorDevice):
self._heartbeat_timer = async_track_point_in_utc_time( self._heartbeat_timer = async_track_point_in_utc_time(
self.hass, timer_elapsed, point_in_time) self.hass, timer_elapsed, point_in_time)
# pylint: disable=unused-argument
def on_update(self, event: object) -> None: def on_update(self, event: object) -> None:
"""Ignore node status updates. """Ignore node status updates.

View File

@@ -115,7 +115,6 @@ class KNXBinarySensor(BinarySensorDevice):
"""Register callbacks to update hass after device was changed.""" """Register callbacks to update hass after device was changed."""
async def after_update_callback(device): async def after_update_callback(device):
"""Call after device was updated.""" """Call after device was updated."""
# pylint: disable=unused-argument
await self.async_update_ha_state() await self.async_update_ha_state()
self.device.register_device_updated_cb(after_update_callback) self.device.register_device_updated_cb(after_update_callback)

View File

@@ -52,19 +52,18 @@ class LinodeBinarySensor(BinarySensorDevice):
self._node_id = node_id self._node_id = node_id
self._state = None self._state = None
self.data = None self.data = None
self._attrs = {}
self._name = None
@property @property
def name(self): def name(self):
"""Return the name of the sensor.""" """Return the name of the sensor."""
if self.data is not None: return self._name
return self.data.label
@property @property
def is_on(self): def is_on(self):
"""Return true if the binary sensor is on.""" """Return true if the binary sensor is on."""
if self.data is not None: return self._state
return self.data.status == 'running'
return False
@property @property
def device_class(self): def device_class(self):
@@ -74,8 +73,18 @@ class LinodeBinarySensor(BinarySensorDevice):
@property @property
def device_state_attributes(self): def device_state_attributes(self):
"""Return the state attributes of the Linode Node.""" """Return the state attributes of the Linode Node."""
if self.data: return self._attrs
return {
def update(self):
"""Update state of sensor."""
self._linode.update()
if self._linode.data is not None:
for node in self._linode.data:
if node.id == self._node_id:
self.data = node
if self.data is not None:
self._state = self.data.status == 'running'
self._attrs = {
ATTR_CREATED: self.data.created, ATTR_CREATED: self.data.created,
ATTR_NODE_ID: self.data.id, ATTR_NODE_ID: self.data.id,
ATTR_NODE_NAME: self.data.label, ATTR_NODE_NAME: self.data.label,
@@ -85,12 +94,4 @@ class LinodeBinarySensor(BinarySensorDevice):
ATTR_REGION: self.data.region.country, ATTR_REGION: self.data.region.country,
ATTR_VCPUS: self.data.specs.vcpus, ATTR_VCPUS: self.data.specs.vcpus,
} }
return {} self._name = self.data.label
def update(self):
"""Update state of sensor."""
self._linode.update()
if self._linode.data is not None:
for node in self._linode.data:
if node.id == self._node_id:
self.data = node

View File

@@ -6,6 +6,7 @@ https://home-assistant.io/components/binary_sensor.mqtt/
""" """
import asyncio import asyncio
import logging import logging
from typing import Optional
import voluptuous as vol import voluptuous as vol
@@ -24,7 +25,7 @@ import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = 'MQTT Binary sensor' DEFAULT_NAME = 'MQTT Binary sensor'
CONF_UNIQUE_ID = 'unique_id'
DEFAULT_PAYLOAD_OFF = 'OFF' DEFAULT_PAYLOAD_OFF = 'OFF'
DEFAULT_PAYLOAD_ON = 'ON' DEFAULT_PAYLOAD_ON = 'ON'
DEFAULT_FORCE_UPDATE = False DEFAULT_FORCE_UPDATE = False
@@ -37,6 +38,9 @@ PLATFORM_SCHEMA = mqtt.MQTT_RO_PLATFORM_SCHEMA.extend({
vol.Optional(CONF_PAYLOAD_ON, default=DEFAULT_PAYLOAD_ON): cv.string, vol.Optional(CONF_PAYLOAD_ON, default=DEFAULT_PAYLOAD_ON): cv.string,
vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA, vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA,
vol.Optional(CONF_FORCE_UPDATE, default=DEFAULT_FORCE_UPDATE): cv.boolean, vol.Optional(CONF_FORCE_UPDATE, default=DEFAULT_FORCE_UPDATE): cv.boolean,
# Integrations shouldn't never expose unique_id through configuration
# this here is an exception because MQTT is a msg transport, not a protocol
vol.Optional(CONF_UNIQUE_ID): cv.string,
}).extend(mqtt.MQTT_AVAILABILITY_SCHEMA.schema) }).extend(mqtt.MQTT_AVAILABILITY_SCHEMA.schema)
@@ -61,7 +65,8 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
config.get(CONF_PAYLOAD_OFF), config.get(CONF_PAYLOAD_OFF),
config.get(CONF_PAYLOAD_AVAILABLE), config.get(CONF_PAYLOAD_AVAILABLE),
config.get(CONF_PAYLOAD_NOT_AVAILABLE), config.get(CONF_PAYLOAD_NOT_AVAILABLE),
value_template value_template,
config.get(CONF_UNIQUE_ID),
)]) )])
@@ -70,7 +75,8 @@ class MqttBinarySensor(MqttAvailability, BinarySensorDevice):
def __init__(self, name, state_topic, availability_topic, device_class, def __init__(self, name, state_topic, availability_topic, device_class,
qos, force_update, payload_on, payload_off, payload_available, qos, force_update, payload_on, payload_off, payload_available,
payload_not_available, value_template): payload_not_available, value_template,
unique_id: Optional[str]):
"""Initialize the MQTT binary sensor.""" """Initialize the MQTT binary sensor."""
super().__init__(availability_topic, qos, payload_available, super().__init__(availability_topic, qos, payload_available,
payload_not_available) payload_not_available)
@@ -83,6 +89,7 @@ class MqttBinarySensor(MqttAvailability, BinarySensorDevice):
self._qos = qos self._qos = qos
self._force_update = force_update self._force_update = force_update
self._template = value_template self._template = value_template
self._unique_id = unique_id
@asyncio.coroutine @asyncio.coroutine
def async_added_to_hass(self): def async_added_to_hass(self):
@@ -134,3 +141,8 @@ class MqttBinarySensor(MqttAvailability, BinarySensorDevice):
def force_update(self): def force_update(self):
"""Force update.""" """Force update."""
return self._force_update return self._force_update
@property
def unique_id(self):
"""Return a unique ID."""
return self._unique_id

View File

@@ -29,6 +29,7 @@ class MyStromView(HomeAssistantView):
url = '/api/mystrom' url = '/api/mystrom'
name = 'api:mystrom' name = 'api:mystrom'
supported_actions = ['single', 'double', 'long', 'touch']
def __init__(self, add_devices): def __init__(self, add_devices):
"""Initialize the myStrom URL endpoint.""" """Initialize the myStrom URL endpoint."""
@@ -44,16 +45,18 @@ class MyStromView(HomeAssistantView):
@asyncio.coroutine @asyncio.coroutine
def _handle(self, hass, data): def _handle(self, hass, data):
"""Handle requests to the myStrom endpoint.""" """Handle requests to the myStrom endpoint."""
button_action = list(data.keys())[0] button_action = next((
button_id = data[button_action] parameter for parameter in data
entity_id = '{}.{}_{}'.format(DOMAIN, button_id, button_action) if parameter in self.supported_actions), None)
if button_action not in ['single', 'double', 'long', 'touch']: if button_action is None:
_LOGGER.error( _LOGGER.error(
"Received unidentified message from myStrom button: %s", data) "Received unidentified message from myStrom button: %s", data)
return ("Received unidentified message: {}".format(data), return ("Received unidentified message: {}".format(data),
HTTP_UNPROCESSABLE_ENTITY) HTTP_UNPROCESSABLE_ENTITY)
button_id = data[button_action]
entity_id = '{}.{}_{}'.format(DOMAIN, button_id, button_action)
if entity_id not in self.buttons: if entity_id not in self.buttons:
_LOGGER.info("New myStrom button/action detected: %s/%s", _LOGGER.info("New myStrom button/action detected: %s/%s",
button_id, button_action) button_id, button_action)

View File

@@ -8,7 +8,8 @@ from itertools import chain
import logging import logging
from homeassistant.components.binary_sensor import BinarySensorDevice from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.components.nest import DATA_NEST, NestSensorDevice from homeassistant.components.nest import (
DATA_NEST, DATA_NEST_CONFIG, CONF_BINARY_SENSORS, NestSensorDevice)
from homeassistant.const import CONF_MONITORED_CONDITIONS from homeassistant.const import CONF_MONITORED_CONDITIONS
DEPENDENCIES = ['nest'] DEPENDENCIES = ['nest']
@@ -56,12 +57,19 @@ _LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Nest binary sensors.""" """Set up the Nest binary sensors.
if discovery_info is None:
return
No longer used.
"""
async def async_setup_entry(hass, entry, async_add_devices):
"""Set up a Nest binary sensor based on a config entry."""
nest = hass.data[DATA_NEST] nest = hass.data[DATA_NEST]
discovery_info = \
hass.data.get(DATA_NEST_CONFIG, {}).get(CONF_BINARY_SENSORS, {})
# Add all available binary sensors if no Nest binary sensor config is set # Add all available binary sensors if no Nest binary sensor config is set
if discovery_info == {}: if discovery_info == {}:
conditions = _VALID_BINARY_SENSOR_TYPES conditions = _VALID_BINARY_SENSOR_TYPES
@@ -76,6 +84,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
"for valid options.") "for valid options.")
_LOGGER.error(wstr) _LOGGER.error(wstr)
def get_binary_sensors():
"""Get the Nest binary sensors."""
sensors = [] sensors = []
for structure in nest.structures(): for structure in nest.structures():
sensors += [NestBinarySensor(structure, None, variable) sensors += [NestBinarySensor(structure, None, variable)
@@ -101,7 +111,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
sensors += [NestActivityZoneSensor(structure, sensors += [NestActivityZoneSensor(structure,
device, device,
activity_zone)] activity_zone)]
add_devices(sensors, True)
return sensors
async_add_devices(await hass.async_add_job(get_binary_sensors), True)
class NestBinarySensor(NestSensorDevice, BinarySensorDevice): class NestBinarySensor(NestSensorDevice, BinarySensorDevice):

View File

@@ -57,7 +57,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
}) })
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the access to Netatmo binary sensor.""" """Set up the access to Netatmo binary sensor."""
netatmo = hass.components.netatmo netatmo = hass.components.netatmo
@@ -68,12 +67,12 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
module_name = None module_name = None
import lnetatmo import pyatmo
try: try:
data = CameraData(netatmo.NETATMO_AUTH, home) data = CameraData(netatmo.NETATMO_AUTH, home)
if not data.get_camera_names(): if not data.get_camera_names():
return None return None
except lnetatmo.NoDevice: except pyatmo.NoDevice:
return None return None
welcome_sensors = config.get( welcome_sensors = config.get(

View File

@@ -33,7 +33,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
}) })
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the available OctoPrint binary sensors.""" """Set up the available OctoPrint binary sensors."""
octoprint_api = hass.data[DOMAIN]["api"] octoprint_api = hass.data[DOMAIN]["api"]

View File

@@ -44,7 +44,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
}) })
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up Pilight Binary Sensor.""" """Set up Pilight Binary Sensor."""
disarm = config.get(CONF_DISARM_AFTER_TRIGGER) disarm = config.get(CONF_DISARM_AFTER_TRIGGER)

View File

@@ -8,7 +8,7 @@ import logging
from homeassistant.components.binary_sensor import BinarySensorDevice from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.components.rainmachine import ( from homeassistant.components.rainmachine import (
BINARY_SENSORS, DATA_RAINMACHINE, DATA_UPDATE_TOPIC, TYPE_FREEZE, BINARY_SENSORS, DATA_RAINMACHINE, SENSOR_UPDATE_TOPIC, TYPE_FREEZE,
TYPE_FREEZE_PROTECTION, TYPE_HOT_DAYS, TYPE_HOURLY, TYPE_MONTH, TYPE_FREEZE_PROTECTION, TYPE_HOT_DAYS, TYPE_HOURLY, TYPE_MONTH,
TYPE_RAINDELAY, TYPE_RAINSENSOR, TYPE_WEEKDAY, RainMachineEntity) TYPE_RAINDELAY, TYPE_RAINSENSOR, TYPE_WEEKDAY, RainMachineEntity)
from homeassistant.const import CONF_MONITORED_CONDITIONS from homeassistant.const import CONF_MONITORED_CONDITIONS
@@ -20,7 +20,8 @@ DEPENDENCIES = ['rainmachine']
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_devices, discovery_info=None): async def async_setup_platform(
hass, config, async_add_devices, discovery_info=None):
"""Set up the RainMachine Switch platform.""" """Set up the RainMachine Switch platform."""
if discovery_info is None: if discovery_info is None:
return return
@@ -33,7 +34,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
binary_sensors.append( binary_sensors.append(
RainMachineBinarySensor(rainmachine, sensor_type, name, icon)) RainMachineBinarySensor(rainmachine, sensor_type, name, icon))
add_devices(binary_sensors, True) async_add_devices(binary_sensors, True)
class RainMachineBinarySensor(RainMachineEntity, BinarySensorDevice): class RainMachineBinarySensor(RainMachineEntity, BinarySensorDevice):
@@ -70,16 +71,16 @@ class RainMachineBinarySensor(RainMachineEntity, BinarySensorDevice):
self.rainmachine.device_mac.replace(':', ''), self._sensor_type) self.rainmachine.device_mac.replace(':', ''), self._sensor_type)
@callback @callback
def update_data(self): def _update_data(self):
"""Update the state.""" """Update the state."""
self.async_schedule_update_ha_state(True) self.async_schedule_update_ha_state(True)
async def async_added_to_hass(self): async def async_added_to_hass(self):
"""Register callbacks.""" """Register callbacks."""
async_dispatcher_connect(self.hass, DATA_UPDATE_TOPIC, async_dispatcher_connect(
self.update_data) self.hass, SENSOR_UPDATE_TOPIC, self._update_data)
def update(self): async def async_update(self):
"""Update the state.""" """Update the state."""
if self._sensor_type == TYPE_FREEZE: if self._sensor_type == TYPE_FREEZE:
self._state = self.rainmachine.restrictions['current']['freeze'] self._state = self.rainmachine.restrictions['current']['freeze']

View File

@@ -42,7 +42,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
}) })
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the raspihats binary_sensor devices.""" """Set up the raspihats binary_sensor devices."""
I2CHatBinarySensor.I2C_HATS_MANAGER = hass.data[I2C_HATS_MANAGER] I2CHatBinarySensor.I2C_HATS_MANAGER = hass.data[I2C_HATS_MANAGER]

View File

@@ -39,7 +39,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
}) })
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Raspberry PI GPIO devices.""" """Set up the Raspberry PI GPIO devices."""
pull_mode = config.get(CONF_PULL_MODE) pull_mode = config.get(CONF_PULL_MODE)

View File

@@ -94,4 +94,4 @@ class SkybellBinarySensor(SkybellDevice, BinarySensorDevice):
self._state = bool(event and event.get('id') != self._event.get('id')) self._state = bool(event and event.get('id') != self._event.get('id'))
self._event = event self._event = event or {}

View File

@@ -57,7 +57,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
}) })
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the trend sensors.""" """Set up the trend sensors."""
sensors = [] sensors = []

View File

@@ -0,0 +1,92 @@
"""
A platform that to monitor Uptime Robot monitors.
For more details about this platform, please refer to the documentation at
https://www.home-assistant.io/components/binary_sensor.uptimerobot/
"""
import logging
import voluptuous as vol
from homeassistant.components.binary_sensor import (
PLATFORM_SCHEMA, BinarySensorDevice)
from homeassistant.const import ATTR_ATTRIBUTION, CONF_API_KEY
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['pyuptimerobot==0.0.5']
_LOGGER = logging.getLogger(__name__)
ATTR_TARGET = 'target'
CONF_ATTRIBUTION = "Data provided by Uptime Robot"
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_API_KEY): cv.string,
})
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Uptime Robot binary_sensors."""
from pyuptimerobot import UptimeRobot
up_robot = UptimeRobot()
api_key = config.get(CONF_API_KEY)
monitors = up_robot.getMonitors(api_key)
devices = []
if not monitors or monitors.get('stat') != 'ok':
_LOGGER.error("Error connecting to Uptime Robot")
return
for monitor in monitors['monitors']:
devices.append(UptimeRobotBinarySensor(
api_key, up_robot, monitor['id'], monitor['friendly_name'],
monitor['url']))
add_devices(devices, True)
class UptimeRobotBinarySensor(BinarySensorDevice):
"""Representation of a Uptime Robot binary sensor."""
def __init__(self, api_key, up_robot, monitor_id, name, target):
"""Initialize Uptime Robot the binary sensor."""
self._api_key = api_key
self._monitor_id = str(monitor_id)
self._name = name
self._target = target
self._up_robot = up_robot
self._state = None
@property
def name(self):
"""Return the name of the binary sensor."""
return self._name
@property
def is_on(self):
"""Return the state of the binary sensor."""
return self._state
@property
def device_class(self):
"""Return the class of this device, from component DEVICE_CLASSES."""
return 'connectivity'
@property
def device_state_attributes(self):
"""Return the state attributes of the binary sensor."""
return {
ATTR_ATTRIBUTION: CONF_ATTRIBUTION,
ATTR_TARGET: self._target,
}
def update(self):
"""Get the latest state of the binary sensor."""
monitor = self._up_robot.getMonitors(self._api_key, self._monitor_id)
if not monitor or monitor.get('stat') != 'ok':
_LOGGER.warning("Failed to get new state")
return
status = monitor['monitors'][0]['status']
self._state = 1 if status == 2 else 0

View File

@@ -19,8 +19,8 @@ _LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
"""Perform the setup for Vera controller devices.""" """Perform the setup for Vera controller devices."""
add_devices( add_devices(
VeraBinarySensor(device, hass.data[VERA_CONTROLLER]) [VeraBinarySensor(device, hass.data[VERA_CONTROLLER])
for device in hass.data[VERA_DEVICES]['binary_sensor']) for device in hass.data[VERA_DEVICES]['binary_sensor']], True)
class VeraBinarySensor(VeraDevice, BinarySensorDevice): class VeraBinarySensor(VeraDevice, BinarySensorDevice):

View File

@@ -54,6 +54,7 @@ class VerisureDoorWindowSensor(BinarySensorDevice):
"$.doorWindow.doorWindowDevice[?(@.deviceLabel=='%s')]", "$.doorWindow.doorWindowDevice[?(@.deviceLabel=='%s')]",
self._device_label) is not None self._device_label) is not None
# pylint: disable=no-self-use
def update(self): def update(self):
"""Update the state of the sensor.""" """Update the state of the sensor."""
hub.update_overview() hub.update_overview()

View File

@@ -13,7 +13,7 @@ DEPENDENCIES = ['wemo']
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
# pylint: disable=unused-argument, too-many-function-args # pylint: disable=too-many-function-args
def setup_platform(hass, config, add_devices_callback, discovery_info=None): def setup_platform(hass, config, add_devices_callback, discovery_info=None):
"""Register discovered WeMo binary sensors.""" """Register discovered WeMo binary sensors."""
import pywemo.discovery as discovery import pywemo.discovery as discovery

View File

@@ -0,0 +1,214 @@
"""
Binary sensor support for Wireless Sensor Tags.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.wirelesstag/
"""
import logging
import voluptuous as vol
from homeassistant.core import callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.components.binary_sensor import (
BinarySensorDevice, PLATFORM_SCHEMA)
from homeassistant.components.wirelesstag import (
DOMAIN as WIRELESSTAG_DOMAIN,
WIRELESSTAG_TYPE_13BIT, WIRELESSTAG_TYPE_WATER,
WIRELESSTAG_TYPE_ALSPRO,
WIRELESSTAG_TYPE_WEMO_DEVICE,
SIGNAL_BINARY_EVENT_UPDATE,
WirelessTagBaseSensor)
from homeassistant.const import (
CONF_MONITORED_CONDITIONS, STATE_ON, STATE_OFF)
import homeassistant.helpers.config_validation as cv
DEPENDENCIES = ['wirelesstag']
_LOGGER = logging.getLogger(__name__)
# On means in range, Off means out of range
SENSOR_PRESENCE = 'presence'
# On means motion detected, Off means cear
SENSOR_MOTION = 'motion'
# On means open, Off means closed
SENSOR_DOOR = 'door'
# On means temperature become too cold, Off means normal
SENSOR_COLD = 'cold'
# On means hot, Off means normal
SENSOR_HEAT = 'heat'
# On means too dry (humidity), Off means normal
SENSOR_DRY = 'dry'
# On means too wet (humidity), Off means normal
SENSOR_WET = 'wet'
# On means light detected, Off means no light
SENSOR_LIGHT = 'light'
# On means moisture detected (wet), Off means no moisture (dry)
SENSOR_MOISTURE = 'moisture'
# On means tag battery is low, Off means normal
SENSOR_BATTERY = 'low_battery'
# Sensor types: Name, device_class, push notification type representing 'on',
# attr to check
SENSOR_TYPES = {
SENSOR_PRESENCE: ['Presence', 'presence', 'is_in_range', {
"on": "oor",
"off": "back_in_range"
}, 2],
SENSOR_MOTION: ['Motion', 'motion', 'is_moved', {
"on": "motion_detected",
}, 5],
SENSOR_DOOR: ['Door', 'door', 'is_door_open', {
"on": "door_opened",
"off": "door_closed"
}, 5],
SENSOR_COLD: ['Cold', 'cold', 'is_cold', {
"on": "temp_toolow",
"off": "temp_normal"
}, 4],
SENSOR_HEAT: ['Heat', 'heat', 'is_heat', {
"on": "temp_toohigh",
"off": "temp_normal"
}, 4],
SENSOR_DRY: ['Too dry', 'dry', 'is_too_dry', {
"on": "too_dry",
"off": "cap_normal"
}, 2],
SENSOR_WET: ['Too wet', 'wet', 'is_too_humid', {
"on": "too_humid",
"off": "cap_normal"
}, 2],
SENSOR_LIGHT: ['Light', 'light', 'is_light_on', {
"on": "too_bright",
"off": "light_normal"
}, 1],
SENSOR_MOISTURE: ['Leak', 'moisture', 'is_leaking', {
"on": "water_detected",
"off": "water_dried",
}, 1],
SENSOR_BATTERY: ['Low Battery', 'battery', 'is_battery_low', {
"on": "low_battery"
}, 3]
}
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_MONITORED_CONDITIONS, default=[]):
vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]),
})
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the platform for a WirelessTags."""
platform = hass.data.get(WIRELESSTAG_DOMAIN)
sensors = []
tags = platform.tags
for tag in tags.values():
allowed_sensor_types = WirelessTagBinarySensor.allowed_sensors(tag)
for sensor_type in config.get(CONF_MONITORED_CONDITIONS):
if sensor_type in allowed_sensor_types:
sensors.append(WirelessTagBinarySensor(platform, tag,
sensor_type))
add_devices(sensors, True)
hass.add_job(platform.install_push_notifications, sensors)
class WirelessTagBinarySensor(WirelessTagBaseSensor, BinarySensorDevice):
"""A binary sensor implementation for WirelessTags."""
@classmethod
def allowed_sensors(cls, tag):
"""Return list of allowed sensor types for specific tag type."""
sensors_map = {
# 13-bit tag - allows everything but not light and moisture
WIRELESSTAG_TYPE_13BIT: [
SENSOR_PRESENCE, SENSOR_BATTERY,
SENSOR_MOTION, SENSOR_DOOR,
SENSOR_COLD, SENSOR_HEAT,
SENSOR_DRY, SENSOR_WET],
# Moister/water sensor - temperature and moisture only
WIRELESSTAG_TYPE_WATER: [
SENSOR_PRESENCE, SENSOR_BATTERY,
SENSOR_COLD, SENSOR_HEAT,
SENSOR_MOISTURE],
# ALS Pro: allows everything, but not moisture
WIRELESSTAG_TYPE_ALSPRO: [
SENSOR_PRESENCE, SENSOR_BATTERY,
SENSOR_MOTION, SENSOR_DOOR,
SENSOR_COLD, SENSOR_HEAT,
SENSOR_DRY, SENSOR_WET,
SENSOR_LIGHT],
# Wemo are power switches.
WIRELESSTAG_TYPE_WEMO_DEVICE: [SENSOR_PRESENCE]
}
# allow everything if tag type is unknown
# (i just dont have full catalog of them :))
tag_type = tag.tag_type
fullset = SENSOR_TYPES.keys()
return sensors_map[tag_type] if tag_type in sensors_map else fullset
def __init__(self, api, tag, sensor_type):
"""Initialize a binary sensor for a Wireless Sensor Tags."""
super().__init__(api, tag)
self._sensor_type = sensor_type
self._name = '{0} {1}'.format(self._tag.name,
SENSOR_TYPES[self._sensor_type][0])
self._device_class = SENSOR_TYPES[self._sensor_type][1]
self._tag_attr = SENSOR_TYPES[self._sensor_type][2]
self.binary_spec = SENSOR_TYPES[self._sensor_type][3]
self.tag_id_index_template = SENSOR_TYPES[self._sensor_type][4]
async def async_added_to_hass(self):
"""Register callbacks."""
tag_id = self.tag_id
event_type = self.device_class
async_dispatcher_connect(
self.hass,
SIGNAL_BINARY_EVENT_UPDATE.format(tag_id, event_type),
self._on_binary_event_callback)
@property
def is_on(self):
"""Return True if the binary sensor is on."""
return self._state == STATE_ON
@property
def device_class(self):
"""Return the class of the binary sensor."""
return self._device_class
@property
def principal_value(self):
"""Return value of tag.
Subclasses need override based on type of sensor.
"""
return (
STATE_ON if getattr(self._tag, self._tag_attr, False)
else STATE_OFF)
def updated_state_value(self):
"""Use raw princial value."""
return self.principal_value
@callback
def _on_binary_event_callback(self, event):
"""Update state from arrive push notification."""
# state should be 'on' or 'off'
self._state = event.data.get('state')
self.async_schedule_update_ha_state()

View File

@@ -28,7 +28,11 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
if model in ['motion', 'sensor_motion', 'sensor_motion.aq2']: if model in ['motion', 'sensor_motion', 'sensor_motion.aq2']:
devices.append(XiaomiMotionSensor(device, hass, gateway)) devices.append(XiaomiMotionSensor(device, hass, gateway))
elif model in ['magnet', 'sensor_magnet', 'sensor_magnet.aq2']: elif model in ['magnet', 'sensor_magnet', 'sensor_magnet.aq2']:
devices.append(XiaomiDoorSensor(device, gateway)) if 'proto' not in device or int(device['proto'][0:1]) == 1:
data_key = 'status'
else:
data_key = 'window_status'
devices.append(XiaomiDoorSensor(device, data_key, gateway))
elif model == 'sensor_wleak.aq1': elif model == 'sensor_wleak.aq1':
devices.append(XiaomiWaterLeakSensor(device, gateway)) devices.append(XiaomiWaterLeakSensor(device, gateway))
elif model in ['smoke', 'sensor_smoke']: elif model in ['smoke', 'sensor_smoke']:
@@ -43,10 +47,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
data_key = 'channel_0' data_key = 'channel_0'
devices.append(XiaomiButton(device, 'Switch', data_key, devices.append(XiaomiButton(device, 'Switch', data_key,
hass, gateway)) hass, gateway))
elif model in ['86sw1', 'sensor_86sw1.aq1']: elif model in ['86sw1', 'sensor_86sw1', 'sensor_86sw1.aq1']:
devices.append(XiaomiButton(device, 'Wall Switch', 'channel_0', devices.append(XiaomiButton(device, 'Wall Switch', 'channel_0',
hass, gateway)) hass, gateway))
elif model in ['86sw2', 'sensor_86sw2.aq1']: elif model in ['86sw2', 'sensor_86sw2', 'sensor_86sw2.aq1']:
devices.append(XiaomiButton(device, 'Wall Switch (Left)', devices.append(XiaomiButton(device, 'Wall Switch (Left)',
'channel_0', hass, gateway)) 'channel_0', hass, gateway))
devices.append(XiaomiButton(device, 'Wall Switch (Right)', devices.append(XiaomiButton(device, 'Wall Switch (Right)',
@@ -190,11 +194,11 @@ class XiaomiMotionSensor(XiaomiBinarySensor):
class XiaomiDoorSensor(XiaomiBinarySensor): class XiaomiDoorSensor(XiaomiBinarySensor):
"""Representation of a XiaomiDoorSensor.""" """Representation of a XiaomiDoorSensor."""
def __init__(self, device, xiaomi_hub): def __init__(self, device, data_key, xiaomi_hub):
"""Initialize the XiaomiDoorSensor.""" """Initialize the XiaomiDoorSensor."""
self._open_since = 0 self._open_since = 0
XiaomiBinarySensor.__init__(self, device, 'Door Window Sensor', XiaomiBinarySensor.__init__(self, device, 'Door Window Sensor',
xiaomi_hub, 'status', 'opening') xiaomi_hub, data_key, 'opening')
@property @property
def device_state_attributes(self): def device_state_attributes(self):
@@ -330,7 +334,7 @@ class XiaomiButton(XiaomiBinarySensor):
click_type = 'both' click_type = 'both'
elif value == 'shake': elif value == 'shake':
click_type = 'shake' click_type = 'shake'
elif value == 'long_click': elif value in ['long_click', 'long_both_click']:
return False return False
else: else:
_LOGGER.warning("Unsupported click_type detected: %s", value) _LOGGER.warning("Unsupported click_type detected: %s", value)

View File

@@ -187,8 +187,8 @@ class Switch(zha.Entity, BinarySensorDevice):
if args[0] == 0xff: if args[0] == 0xff:
rate = 10 # Should read default move rate rate = 10 # Should read default move rate
self._entity.move_level(-rate if args[0] else rate) self._entity.move_level(-rate if args[0] else rate)
elif command_id == 0x0002: # step elif command_id in (0x0002, 0x0006): # step, -with_on_off
# Step (technically shouldn't change on/off) # Step (technically may change on/off)
self._entity.move_level(-args[1] if args[0] else args[1]) self._entity.move_level(-args[1] if args[0] else args[1])
def attribute_update(self, attrid, value): def attribute_update(self, attrid, value):

View File

@@ -34,7 +34,6 @@ CONFIG_SCHEMA = vol.Schema({
}, extra=vol.ALLOW_EXTRA) }, extra=vol.ALLOW_EXTRA)
# pylint: disable=unused-argument
def setup(hass, config): def setup(hass, config):
"""Set up the BloomSky component.""" """Set up the BloomSky component."""
api_key = config[DOMAIN][CONF_API_KEY] api_key = config[DOMAIN][CONF_API_KEY]

View File

@@ -4,11 +4,12 @@ Support for Google Calendar event device 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/calendar/ https://home-assistant.io/components/calendar/
""" """
import asyncio
import logging import logging
from datetime import timedelta from datetime import timedelta
import re import re
from aiohttp import web
from homeassistant.components.google import ( from homeassistant.components.google import (
CONF_OFFSET, CONF_DEVICE_ID, CONF_NAME) CONF_OFFSET, CONF_DEVICE_ID, CONF_NAME)
from homeassistant.const import STATE_OFF, STATE_ON from homeassistant.const import STATE_OFF, STATE_ON
@@ -18,23 +19,32 @@ from homeassistant.helpers.entity import Entity, generate_entity_id
from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.template import DATE_STR_FORMAT from homeassistant.helpers.template import DATE_STR_FORMAT
from homeassistant.util import dt from homeassistant.util import dt
from homeassistant.components import http
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
DOMAIN = 'calendar' DOMAIN = 'calendar'
DEPENDENCIES = ['http']
ENTITY_ID_FORMAT = DOMAIN + '.{}' ENTITY_ID_FORMAT = DOMAIN + '.{}'
SCAN_INTERVAL = timedelta(seconds=60) SCAN_INTERVAL = timedelta(seconds=60)
@asyncio.coroutine async def async_setup(hass, config):
def async_setup(hass, config):
"""Track states and offer events for calendars.""" """Track states and offer events for calendars."""
component = EntityComponent( component = EntityComponent(
_LOGGER, DOMAIN, hass, SCAN_INTERVAL, DOMAIN) _LOGGER, DOMAIN, hass, SCAN_INTERVAL, DOMAIN)
yield from component.async_setup(config) hass.http.register_view(CalendarListView(component))
hass.http.register_view(CalendarEventView(component))
await hass.components.frontend.async_register_built_in_panel(
'calendar', 'calendar', 'hass:calendar')
await component.async_setup(config)
return True return True
@@ -42,7 +52,14 @@ DEFAULT_CONF_TRACK_NEW = True
DEFAULT_CONF_OFFSET = '!!' DEFAULT_CONF_OFFSET = '!!'
# pylint: disable=too-many-instance-attributes def get_date(date):
"""Get the dateTime from date or dateTime as a local."""
if 'date' in date:
return dt.start_of_local_day(dt.dt.datetime.combine(
dt.parse_date(date['date']), dt.dt.time.min))
return dt.as_local(dt.parse_datetime(date['dateTime']))
class CalendarEventDevice(Entity): class CalendarEventDevice(Entity):
"""A calendar event device.""" """A calendar event device."""
@@ -50,7 +67,6 @@ class CalendarEventDevice(Entity):
# with an update() method # with an update() method
data = None data = None
# pylint: disable=too-many-arguments
def __init__(self, hass, data): def __init__(self, hass, data):
"""Create the Calendar Event Device.""" """Create the Calendar Event Device."""
self._name = data.get(CONF_NAME) self._name = data.get(CONF_NAME)
@@ -144,15 +160,8 @@ class CalendarEventDevice(Entity):
self.cleanup() self.cleanup()
return return
def _get_date(date): start = get_date(self.data.event['start'])
"""Get the dateTime from date or dateTime as a local.""" end = get_date(self.data.event['end'])
if 'date' in date:
return dt.start_of_local_day(dt.dt.datetime.combine(
dt.parse_date(date['date']), dt.dt.time.min))
return dt.as_local(dt.parse_datetime(date['dateTime']))
start = _get_date(self.data.event['start'])
end = _get_date(self.data.event['end'])
summary = self.data.event.get('summary', '') summary = self.data.event.get('summary', '')
@@ -176,10 +185,61 @@ class CalendarEventDevice(Entity):
# cleanup the string so we don't have a bunch of double+ spaces # cleanup the string so we don't have a bunch of double+ spaces
self._cal_data['message'] = re.sub(' +', '', summary).strip() self._cal_data['message'] = re.sub(' +', '', summary).strip()
self._cal_data['offset_time'] = offset_time self._cal_data['offset_time'] = offset_time
self._cal_data['location'] = self.data.event.get('location', '') self._cal_data['location'] = self.data.event.get('location', '')
self._cal_data['description'] = self.data.event.get('description', '') self._cal_data['description'] = self.data.event.get('description', '')
self._cal_data['start'] = start self._cal_data['start'] = start
self._cal_data['end'] = end self._cal_data['end'] = end
self._cal_data['all_day'] = 'date' in self.data.event['start'] self._cal_data['all_day'] = 'date' in self.data.event['start']
class CalendarEventView(http.HomeAssistantView):
"""View to retrieve calendar content."""
url = '/api/calendars/{entity_id}'
name = 'api:calendars:calendar'
def __init__(self, component):
"""Initialize calendar view."""
self.component = component
async def get(self, request, entity_id):
"""Return calendar events."""
entity = self.component.get_entity(entity_id)
start = request.query.get('start')
end = request.query.get('end')
if None in (start, end, entity):
return web.Response(status=400)
try:
start_date = dt.parse_datetime(start)
end_date = dt.parse_datetime(end)
except (ValueError, AttributeError):
return web.Response(status=400)
event_list = await entity.async_get_events(
request.app['hass'], start_date, end_date)
return self.json(event_list)
class CalendarListView(http.HomeAssistantView):
"""View to retrieve calendar list."""
url = '/api/calendars'
name = "api:calendars"
def __init__(self, component):
"""Initialize calendar view."""
self.component = component
async def get(self, request):
"""Retrieve calendar list."""
get_state = request.app['hass'].states.get
calendar_list = []
for entity in self.component.entities:
state = get_state(entity.entity_id)
calendar_list.append({
"name": state.name,
"entity_id": entity.entity_id,
})
return self.json(sorted(calendar_list, key=lambda x: x['name']))

View File

@@ -11,7 +11,7 @@ import re
import voluptuous as vol import voluptuous as vol
from homeassistant.components.calendar import ( from homeassistant.components.calendar import (
PLATFORM_SCHEMA, CalendarEventDevice) PLATFORM_SCHEMA, CalendarEventDevice, get_date)
from homeassistant.const import ( from homeassistant.const import (
CONF_NAME, CONF_PASSWORD, CONF_URL, CONF_USERNAME) CONF_NAME, CONF_PASSWORD, CONF_URL, CONF_USERNAME)
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
@@ -92,7 +92,7 @@ def setup_platform(hass, config, add_devices, disc_info=None):
if not config.get(CONF_CUSTOM_CALENDARS): if not config.get(CONF_CUSTOM_CALENDARS):
device_data = { device_data = {
CONF_NAME: calendar.name, CONF_NAME: calendar.name,
CONF_DEVICE_ID: calendar.name CONF_DEVICE_ID: calendar.name,
} }
calendar_devices.append( calendar_devices.append(
WebDavCalendarEventDevice(hass, device_data, calendar) WebDavCalendarEventDevice(hass, device_data, calendar)
@@ -120,6 +120,10 @@ class WebDavCalendarEventDevice(CalendarEventDevice):
attributes = super().device_state_attributes attributes = super().device_state_attributes
return attributes return attributes
async def async_get_events(self, hass, start_date, end_date):
"""Get all events in a specific time frame."""
return await self.data.async_get_events(hass, start_date, end_date)
class WebDavCalendarData(object): class WebDavCalendarData(object):
"""Class to utilize the calendar dav client object to get next event.""" """Class to utilize the calendar dav client object to get next event."""
@@ -131,6 +135,33 @@ class WebDavCalendarData(object):
self.search = search self.search = search
self.event = None self.event = None
async def async_get_events(self, hass, start_date, end_date):
"""Get all events in a specific time frame."""
# Get event list from the current calendar
vevent_list = await hass.async_add_job(self.calendar.date_search,
start_date, end_date)
event_list = []
for event in vevent_list:
vevent = event.instance.vevent
uid = None
if hasattr(vevent, 'uid'):
uid = vevent.uid.value
data = {
"uid": uid,
"title": vevent.summary.value,
"start": self.get_hass_date(vevent.dtstart.value),
"end": self.get_hass_date(self.get_end_date(vevent)),
"location": self.get_attr_value(vevent, "location"),
"description": self.get_attr_value(vevent, "description"),
}
data['start'] = get_date(data['start']).isoformat()
data['end'] = get_date(data['end']).isoformat()
event_list.append(data)
return event_list
@Throttle(MIN_TIME_BETWEEN_UPDATES) @Throttle(MIN_TIME_BETWEEN_UPDATES)
def update(self): def update(self):
"""Get the latest data.""" """Get the latest data."""

View File

@@ -4,8 +4,10 @@ Demo platform that has two fake binary sensors.
For more details about this platform, please refer to the documentation For more details about this platform, please refer to the documentation
https://home-assistant.io/components/demo/ https://home-assistant.io/components/demo/
""" """
import copy
import homeassistant.util.dt as dt_util import homeassistant.util.dt as dt_util
from homeassistant.components.calendar import CalendarEventDevice from homeassistant.components.calendar import CalendarEventDevice, get_date
from homeassistant.components.google import CONF_DEVICE_ID, CONF_NAME from homeassistant.components.google import CONF_DEVICE_ID, CONF_NAME
@@ -15,13 +17,13 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
calendar_data_current = DemoGoogleCalendarDataCurrent() calendar_data_current = DemoGoogleCalendarDataCurrent()
add_devices([ add_devices([
DemoGoogleCalendar(hass, calendar_data_future, { DemoGoogleCalendar(hass, calendar_data_future, {
CONF_NAME: 'Future Event', CONF_NAME: 'Calendar 1',
CONF_DEVICE_ID: 'future_event', CONF_DEVICE_ID: 'calendar_1',
}), }),
DemoGoogleCalendar(hass, calendar_data_current, { DemoGoogleCalendar(hass, calendar_data_current, {
CONF_NAME: 'Current Event', CONF_NAME: 'Calendar 2',
CONF_DEVICE_ID: 'current_event', CONF_DEVICE_ID: 'calendar_2',
}), }),
]) ])
@@ -29,11 +31,21 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
class DemoGoogleCalendarData(object): class DemoGoogleCalendarData(object):
"""Representation of a Demo Calendar element.""" """Representation of a Demo Calendar element."""
event = {}
# pylint: disable=no-self-use # pylint: disable=no-self-use
def update(self): def update(self):
"""Return true so entity knows we have new data.""" """Return true so entity knows we have new data."""
return True return True
async def async_get_events(self, hass, start_date, end_date):
"""Get all events in a specific time frame."""
event = copy.copy(self.event)
event['title'] = event['summary']
event['start'] = get_date(event['start']).isoformat()
event['end'] = get_date(event['end']).isoformat()
return [event]
class DemoGoogleCalendarDataFuture(DemoGoogleCalendarData): class DemoGoogleCalendarDataFuture(DemoGoogleCalendarData):
"""Representation of a Demo Calendar for a future event.""" """Representation of a Demo Calendar for a future event."""
@@ -80,3 +92,7 @@ class DemoGoogleCalendar(CalendarEventDevice):
"""Initialize Google Calendar but without the API calls.""" """Initialize Google Calendar but without the API calls."""
self.data = calendar_data self.data = calendar_data
super().__init__(hass, data) super().__init__(hass, data)
async def async_get_events(self, hass, start_date, end_date):
"""Get all events in a specific time frame."""
return await self.data.async_get_events(hass, start_date, end_date)

View File

@@ -51,6 +51,10 @@ class GoogleCalendarEventDevice(CalendarEventDevice):
super().__init__(hass, data) super().__init__(hass, data)
async def async_get_events(self, hass, start_date, end_date):
"""Get all events in a specific time frame."""
return await self.data.async_get_events(hass, start_date, end_date)
class GoogleCalendarData(object): class GoogleCalendarData(object):
"""Class to utilize calendar service object to get next event.""" """Class to utilize calendar service object to get next event."""
@@ -64,9 +68,7 @@ class GoogleCalendarData(object):
self.ignore_availability = ignore_availability self.ignore_availability = ignore_availability
self.event = None self.event = None
@Throttle(MIN_TIME_BETWEEN_UPDATES) def _prepare_query(self):
def update(self):
"""Get the latest data."""
from httplib2 import ServerNotFoundError from httplib2 import ServerNotFoundError
try: try:
@@ -74,13 +76,41 @@ class GoogleCalendarData(object):
except ServerNotFoundError: except ServerNotFoundError:
_LOGGER.warning("Unable to connect to Google, using cached data") _LOGGER.warning("Unable to connect to Google, using cached data")
return False return False
params = dict(DEFAULT_GOOGLE_SEARCH_PARAMS) params = dict(DEFAULT_GOOGLE_SEARCH_PARAMS)
params['timeMin'] = dt.now().isoformat('T')
params['calendarId'] = self.calendar_id params['calendarId'] = self.calendar_id
if self.search: if self.search:
params['q'] = self.search params['q'] = self.search
return service, params
async def async_get_events(self, hass, start_date, end_date):
"""Get all events in a specific time frame."""
service, params = await hass.async_add_job(self._prepare_query)
params['timeMin'] = start_date.isoformat('T')
params['timeMax'] = end_date.isoformat('T')
# pylint: disable=no-member
events = await hass.async_add_job(service.events)
# pylint: enable=no-member
result = await hass.async_add_job(events.list(**params).execute)
items = result.get('items', [])
event_list = []
for item in items:
if (not self.ignore_availability
and 'transparency' in item.keys()):
if item['transparency'] == 'opaque':
event_list.append(item)
else:
event_list.append(item)
return event_list
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def update(self):
"""Get the latest data."""
service, params = self._prepare_query()
params['timeMin'] = dt.now().isoformat('T')
events = service.events() # pylint: disable=no-member events = service.events() # pylint: disable=no-member
result = events.list(**params).execute() result = events.list(**params).execute()

View File

@@ -257,6 +257,10 @@ class TodoistProjectDevice(CalendarEventDevice):
super().cleanup() super().cleanup()
self._cal_data[ALL_TASKS] = [] self._cal_data[ALL_TASKS] = []
async def async_get_events(self, hass, start_date, end_date):
"""Get all events in a specific time frame."""
return await self.data.async_get_events(hass, start_date, end_date)
@property @property
def device_state_attributes(self): def device_state_attributes(self):
"""Return the device state attributes.""" """Return the device state attributes."""
@@ -485,6 +489,31 @@ class TodoistProjectData(object):
continue continue
return event return event
async def async_get_events(self, hass, start_date, end_date):
"""Get all tasks in a specific time frame."""
if self._id is None:
project_task_data = [
task for task in self._api.state[TASKS]
if not self._project_id_whitelist or
task[PROJECT_ID] in self._project_id_whitelist]
else:
project_task_data = self._api.projects.get_data(self._id)[TASKS]
events = []
time_format = '%a %d %b %Y %H:%M:%S %z'
for task in project_task_data:
due_date = datetime.strptime(task['due_date_utc'], time_format)
if due_date > start_date and due_date < end_date:
event = {
'uid': task['id'],
'title': task['content'],
'start': due_date.isoformat(),
'end': due_date.isoformat(),
'allDay': True,
}
events.append(event)
return events
@Throttle(MIN_TIME_BETWEEN_UPDATES) @Throttle(MIN_TIME_BETWEEN_UPDATES)
def update(self): def update(self):
"""Get the latest data.""" """Get the latest data."""

View File

@@ -1,4 +1,3 @@
# pylint: disable=too-many-lines
""" """
Component to interface with cameras. Component to interface with cameras.
@@ -97,6 +96,7 @@ def disable_motion_detection(hass, entity_id=None):
@bind_hass @bind_hass
@callback
def async_snapshot(hass, filename, entity_id=None): def async_snapshot(hass, filename, entity_id=None):
"""Make a snapshot from a camera.""" """Make a snapshot from a camera."""
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {} data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
@@ -129,8 +129,7 @@ async def async_get_image(hass, entity_id, timeout=10):
raise HomeAssistantError('Unable to get image') raise HomeAssistantError('Unable to get image')
@asyncio.coroutine async def async_setup(hass, config):
def async_setup(hass, config):
"""Set up the camera component.""" """Set up the camera component."""
component = hass.data[DOMAIN] = \ component = hass.data[DOMAIN] = \
EntityComponent(_LOGGER, DOMAIN, hass, SCAN_INTERVAL) EntityComponent(_LOGGER, DOMAIN, hass, SCAN_INTERVAL)
@@ -142,7 +141,7 @@ def async_setup(hass, config):
SCHEMA_WS_CAMERA_THUMBNAIL SCHEMA_WS_CAMERA_THUMBNAIL
) )
yield from component.async_setup(config) await component.async_setup(config)
@callback @callback
def update_tokens(time): def update_tokens(time):
@@ -154,27 +153,25 @@ def async_setup(hass, config):
hass.helpers.event.async_track_time_interval( hass.helpers.event.async_track_time_interval(
update_tokens, TOKEN_CHANGE_INTERVAL) update_tokens, TOKEN_CHANGE_INTERVAL)
@asyncio.coroutine async def async_handle_camera_service(service):
def async_handle_camera_service(service):
"""Handle calls to the camera services.""" """Handle calls to the camera services."""
target_cameras = component.async_extract_from_service(service) target_cameras = component.async_extract_from_service(service)
update_tasks = [] update_tasks = []
for camera in target_cameras: for camera in target_cameras:
if service.service == SERVICE_ENABLE_MOTION: if service.service == SERVICE_ENABLE_MOTION:
yield from camera.async_enable_motion_detection() await camera.async_enable_motion_detection()
elif service.service == SERVICE_DISABLE_MOTION: elif service.service == SERVICE_DISABLE_MOTION:
yield from camera.async_disable_motion_detection() await camera.async_disable_motion_detection()
if not camera.should_poll: if not camera.should_poll:
continue continue
update_tasks.append(camera.async_update_ha_state(True)) update_tasks.append(camera.async_update_ha_state(True))
if update_tasks: if update_tasks:
yield from asyncio.wait(update_tasks, loop=hass.loop) await asyncio.wait(update_tasks, loop=hass.loop)
@asyncio.coroutine async def async_handle_snapshot_service(service):
def async_handle_snapshot_service(service):
"""Handle snapshot services calls.""" """Handle snapshot services calls."""
target_cameras = component.async_extract_from_service(service) target_cameras = component.async_extract_from_service(service)
filename = service.data[ATTR_FILENAME] filename = service.data[ATTR_FILENAME]
@@ -190,7 +187,7 @@ def async_setup(hass, config):
"Can't write %s, no access to path!", snapshot_file) "Can't write %s, no access to path!", snapshot_file)
continue continue
image = yield from camera.async_camera_image() image = await camera.async_camera_image()
def _write_image(to_file, image_data): def _write_image(to_file, image_data):
"""Executor helper to write image.""" """Executor helper to write image."""
@@ -198,7 +195,7 @@ def async_setup(hass, config):
img_file.write(image_data) img_file.write(image_data)
try: try:
yield from hass.async_add_job( await hass.async_add_job(
_write_image, snapshot_file, image) _write_image, snapshot_file, image)
except OSError as err: except OSError as err:
_LOGGER.error("Can't write image to file: %s", err) _LOGGER.error("Can't write image to file: %s", err)
@@ -216,6 +213,16 @@ def async_setup(hass, config):
return True return True
async def async_setup_entry(hass, entry):
"""Setup a config entry."""
return await hass.data[DOMAIN].async_setup_entry(entry)
async def async_unload_entry(hass, entry):
"""Unload a config entry."""
return await hass.data[DOMAIN].async_unload_entry(entry)
class Camera(Entity): class Camera(Entity):
"""The base class for camera entities.""" """The base class for camera entities."""
@@ -265,6 +272,7 @@ class Camera(Entity):
"""Return bytes of camera image.""" """Return bytes of camera image."""
raise NotImplementedError() raise NotImplementedError()
@callback
def async_camera_image(self): def async_camera_image(self):
"""Return bytes of camera image. """Return bytes of camera image.
@@ -388,8 +396,7 @@ class CameraView(HomeAssistantView):
"""Initialize a basic camera view.""" """Initialize a basic camera view."""
self.component = component self.component = component
@asyncio.coroutine async def get(self, request, entity_id):
def get(self, request, entity_id):
"""Start a GET request.""" """Start a GET request."""
camera = self.component.get_entity(entity_id) camera = self.component.get_entity(entity_id)
@@ -403,11 +410,10 @@ class CameraView(HomeAssistantView):
if not authenticated: if not authenticated:
return web.Response(status=401) return web.Response(status=401)
response = yield from self.handle(request, camera) response = await self.handle(request, camera)
return response return response
@asyncio.coroutine async def handle(self, request, camera):
def handle(self, request, camera):
"""Handle the camera request.""" """Handle the camera request."""
raise NotImplementedError() raise NotImplementedError()
@@ -418,12 +424,11 @@ class CameraImageView(CameraView):
url = '/api/camera_proxy/{entity_id}' url = '/api/camera_proxy/{entity_id}'
name = 'api:camera:image' name = 'api:camera:image'
@asyncio.coroutine async def handle(self, request, camera):
def handle(self, request, camera):
"""Serve camera image.""" """Serve camera image."""
with suppress(asyncio.CancelledError, asyncio.TimeoutError): with suppress(asyncio.CancelledError, asyncio.TimeoutError):
with async_timeout.timeout(10, loop=request.app['hass'].loop): with async_timeout.timeout(10, loop=request.app['hass'].loop):
image = yield from camera.async_camera_image() image = await camera.async_camera_image()
if image: if image:
return web.Response(body=image, return web.Response(body=image,

View File

@@ -4,23 +4,22 @@ Support for Netgear Arlo IP cameras.
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/camera.arlo/ https://home-assistant.io/components/camera.arlo/
""" """
import asyncio
import logging import logging
from datetime import timedelta
import voluptuous as vol import voluptuous as vol
from homeassistant.core import callback
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from homeassistant.components.arlo import DEFAULT_BRAND, DATA_ARLO from homeassistant.components.arlo import (
DEFAULT_BRAND, DATA_ARLO, SIGNAL_UPDATE_ARLO)
from homeassistant.components.camera import Camera, PLATFORM_SCHEMA from homeassistant.components.camera import Camera, PLATFORM_SCHEMA
from homeassistant.components.ffmpeg import DATA_FFMPEG from homeassistant.components.ffmpeg import DATA_FFMPEG
from homeassistant.const import ATTR_BATTERY_LEVEL from homeassistant.const import ATTR_BATTERY_LEVEL
from homeassistant.helpers.aiohttp_client import async_aiohttp_proxy_stream from homeassistant.helpers.aiohttp_client import async_aiohttp_proxy_stream
from homeassistant.helpers.dispatcher import async_dispatcher_connect
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
SCAN_INTERVAL = timedelta(seconds=90)
ARLO_MODE_ARMED = 'armed' ARLO_MODE_ARMED = 'armed'
ARLO_MODE_DISARMED = 'disarmed' ARLO_MODE_DISARMED = 'disarmed'
@@ -44,22 +43,19 @@ POWERSAVE_MODE_MAPPING = {
} }
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_FFMPEG_ARGUMENTS): vol.Optional(CONF_FFMPEG_ARGUMENTS): cv.string,
cv.string,
}) })
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up an Arlo IP Camera.""" """Set up an Arlo IP Camera."""
arlo = hass.data.get(DATA_ARLO) arlo = hass.data[DATA_ARLO]
if not arlo:
return False
cameras = [] cameras = []
for camera in arlo.cameras: for camera in arlo.cameras:
cameras.append(ArloCam(hass, camera, config)) cameras.append(ArloCam(hass, camera, config))
add_devices(cameras, True) add_devices(cameras)
class ArloCam(Camera): class ArloCam(Camera):
@@ -74,31 +70,41 @@ class ArloCam(Camera):
self._ffmpeg = hass.data[DATA_FFMPEG] self._ffmpeg = hass.data[DATA_FFMPEG]
self._ffmpeg_arguments = device_info.get(CONF_FFMPEG_ARGUMENTS) self._ffmpeg_arguments = device_info.get(CONF_FFMPEG_ARGUMENTS)
self._last_refresh = None self._last_refresh = None
if self._camera.base_station:
self._camera.base_station.refresh_rate = \
SCAN_INTERVAL.total_seconds()
self.attrs = {} self.attrs = {}
def camera_image(self): def camera_image(self):
"""Return a still image response from the camera.""" """Return a still image response from the camera."""
return self._camera.last_image return self._camera.last_image_from_cache
@asyncio.coroutine async def async_added_to_hass(self):
def handle_async_mjpeg_stream(self, request): """Register callbacks."""
async_dispatcher_connect(
self.hass, SIGNAL_UPDATE_ARLO, self._update_callback)
@callback
def _update_callback(self):
"""Call update method."""
self.async_schedule_update_ha_state()
async def handle_async_mjpeg_stream(self, request):
"""Generate an HTTP MJPEG stream from the camera.""" """Generate an HTTP MJPEG stream from the camera."""
from haffmpeg import CameraMjpeg from haffmpeg import CameraMjpeg
video = self._camera.last_video video = self._camera.last_video
if not video: if not video:
error_msg = \
'Video not found for {0}. Is it older than {1} days?'.format(
self.name, self._camera.min_days_vdo_cache)
_LOGGER.error(error_msg)
return return
stream = CameraMjpeg(self._ffmpeg.binary, loop=self.hass.loop) stream = CameraMjpeg(self._ffmpeg.binary, loop=self.hass.loop)
yield from stream.open_camera( await stream.open_camera(
video.video_url, extra_cmd=self._ffmpeg_arguments) video.video_url, extra_cmd=self._ffmpeg_arguments)
yield from async_aiohttp_proxy_stream( await async_aiohttp_proxy_stream(
self.hass, request, stream, self.hass, request, stream,
'multipart/x-mixed-replace;boundary=ffserver') 'multipart/x-mixed-replace;boundary=ffserver')
yield from stream.close() await stream.close()
@property @property
def name(self): def name(self):
@@ -132,11 +138,6 @@ class ArloCam(Camera):
"""Return the camera brand.""" """Return the camera brand."""
return DEFAULT_BRAND return DEFAULT_BRAND
@property
def should_poll(self):
"""Camera should poll periodically."""
return True
@property @property
def motion_detection_enabled(self): def motion_detection_enabled(self):
"""Return the camera motion detection status.""" """Return the camera motion detection status."""
@@ -164,7 +165,3 @@ class ArloCam(Camera):
"""Disable the motion detection in base station (Disarm).""" """Disable the motion detection in base station (Disarm)."""
self._motion_status = False self._motion_status = False
self.set_base_station_mode(ARLO_MODE_DISARMED) self.set_base_station_mode(ARLO_MODE_DISARMED)
def update(self):
"""Add an attribute-update task to the executor pool."""
self._camera.update()

View File

@@ -13,7 +13,6 @@ from homeassistant.components.camera import Camera
DEPENDENCIES = ['bloomsky'] DEPENDENCIES = ['bloomsky']
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up access to BloomSky cameras.""" """Set up access to BloomSky cameras."""
bloomsky = hass.components.bloomsky bloomsky = hass.components.bloomsky

View File

@@ -12,9 +12,10 @@ from homeassistant.components.camera import Camera
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_devices, discovery_info=None): async def async_setup_platform(hass, config, async_add_devices,
discovery_info=None):
"""Set up the Demo camera platform.""" """Set up the Demo camera platform."""
add_devices([ async_add_devices([
DemoCamera(hass, config, 'Demo camera') DemoCamera(hass, config, 'Demo camera')
]) ])

View File

@@ -17,9 +17,9 @@ from homeassistant.helpers.aiohttp_client import async_get_clientsession
DEPENDENCIES = ['doorbird'] DEPENDENCIES = ['doorbird']
_CAMERA_LAST_VISITOR = "DoorBird Last Ring" _CAMERA_LAST_VISITOR = "{} Last Ring"
_CAMERA_LAST_MOTION = "DoorBird Last Motion" _CAMERA_LAST_MOTION = "{} Last Motion"
_CAMERA_LIVE = "DoorBird Live" _CAMERA_LIVE = "{} Live"
_LAST_VISITOR_INTERVAL = datetime.timedelta(minutes=1) _LAST_VISITOR_INTERVAL = datetime.timedelta(minutes=1)
_LAST_MOTION_INTERVAL = datetime.timedelta(minutes=1) _LAST_MOTION_INTERVAL = datetime.timedelta(minutes=1)
_LIVE_INTERVAL = datetime.timedelta(seconds=1) _LIVE_INTERVAL = datetime.timedelta(seconds=1)
@@ -30,14 +30,20 @@ _TIMEOUT = 10 # seconds
@asyncio.coroutine @asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None): def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Set up the DoorBird camera platform.""" """Set up the DoorBird camera platform."""
device = hass.data.get(DOORBIRD_DOMAIN) for doorstation in hass.data[DOORBIRD_DOMAIN]:
device = doorstation.device
async_add_devices([ async_add_devices([
DoorBirdCamera(device.live_image_url, _CAMERA_LIVE, _LIVE_INTERVAL),
DoorBirdCamera( DoorBirdCamera(
device.history_image_url(1, 'doorbell'), _CAMERA_LAST_VISITOR, device.live_image_url,
_CAMERA_LIVE.format(doorstation.name),
_LIVE_INTERVAL),
DoorBirdCamera(
device.history_image_url(1, 'doorbell'),
_CAMERA_LAST_VISITOR.format(doorstation.name),
_LAST_VISITOR_INTERVAL), _LAST_VISITOR_INTERVAL),
DoorBirdCamera( DoorBirdCamera(
device.history_image_url(1, 'motionsensor'), _CAMERA_LAST_MOTION, device.history_image_url(1, 'motionsensor'),
_CAMERA_LAST_MOTION.format(doorstation.name),
_LAST_MOTION_INTERVAL), _LAST_MOTION_INTERVAL),
]) ])

View File

@@ -33,7 +33,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
}) })
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up a Foscam IP Camera.""" """Set up a Foscam IP Camera."""
add_devices([FoscamCam(config)]) add_devices([FoscamCam(config)])

View File

@@ -46,7 +46,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
@asyncio.coroutine @asyncio.coroutine
# pylint: disable=unused-argument
def async_setup_platform(hass, config, async_add_devices, discovery_info=None): def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Set up a generic IP Camera.""" """Set up a generic IP Camera."""
async_add_devices([GenericCamera(hass, config)]) async_add_devices([GenericCamera(hass, config)])

View File

@@ -42,7 +42,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
@asyncio.coroutine @asyncio.coroutine
# pylint: disable=unused-argument
def async_setup_platform(hass, config, async_add_devices, discovery_info=None): def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Set up a MJPEG IP Camera.""" """Set up a MJPEG IP Camera."""
if discovery_info: if discovery_info:

View File

@@ -45,7 +45,7 @@ class NeatoCleaningMap(Camera):
self.update() self.update()
return self._image return self._image
@Throttle(timedelta(seconds=10)) @Throttle(timedelta(seconds=60))
def update(self): def update(self):
"""Check the contents of the map list.""" """Check the contents of the map list."""
self.neato.update_robots() self.neato.update_robots()

View File

@@ -23,14 +23,19 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({})
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up a Nest Cam.""" """Set up a Nest Cam.
if discovery_info is None:
return
camera_devices = hass.data[nest.DATA_NEST].cameras() No longer in use.
"""
async def async_setup_entry(hass, entry, async_add_devices):
"""Set up a Nest sensor based on a config entry."""
camera_devices = \
await hass.async_add_job(hass.data[nest.DATA_NEST].cameras)
cameras = [NestCamera(structure, device) cameras = [NestCamera(structure, device)
for structure, device in camera_devices] for structure, device in camera_devices]
add_devices(cameras, True) async_add_devices(cameras, True)
class NestCamera(Camera): class NestCamera(Camera):

View File

@@ -29,13 +29,12 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
}) })
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up access to Netatmo cameras.""" """Set up access to Netatmo cameras."""
netatmo = hass.components.netatmo netatmo = hass.components.netatmo
home = config.get(CONF_HOME) home = config.get(CONF_HOME)
verify_ssl = config.get(CONF_VERIFY_SSL, True) verify_ssl = config.get(CONF_VERIFY_SSL, True)
import lnetatmo import pyatmo
try: try:
data = CameraData(netatmo.NETATMO_AUTH, home) data = CameraData(netatmo.NETATMO_AUTH, home)
for camera_name in data.get_camera_names(): for camera_name in data.get_camera_names():
@@ -46,7 +45,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
continue continue
add_devices([NetatmoCamera(data, camera_name, home, add_devices([NetatmoCamera(data, camera_name, home,
camera_type, verify_ssl)]) camera_type, verify_ssl)])
except lnetatmo.NoDevice: except pyatmo.NoDevice:
return None return None

View File

@@ -13,6 +13,7 @@ import voluptuous as vol
from homeassistant.const import CONF_PORT from homeassistant.const import CONF_PORT
from homeassistant.components.camera import Camera, PLATFORM_SCHEMA from homeassistant.components.camera import Camera, PLATFORM_SCHEMA
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from homeassistant.exceptions import PlatformNotReady
REQUIREMENTS = ['uvcclient==0.10.1'] REQUIREMENTS = ['uvcclient==0.10.1']
@@ -41,18 +42,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
port = config[CONF_PORT] port = config[CONF_PORT]
from uvcclient import nvr from uvcclient import nvr
nvrconn = nvr.UVCRemote(addr, port, key)
try: try:
# Exceptions may be raised in all method calls to the nvr library.
nvrconn = nvr.UVCRemote(addr, port, key)
cameras = nvrconn.index() cameras = nvrconn.index()
except nvr.NotAuthorized:
_LOGGER.error("Authorization failure while connecting to NVR")
return False
except nvr.NvrError:
_LOGGER.error("NVR refuses to talk to me")
return False
except requests.exceptions.ConnectionError as ex:
_LOGGER.error("Unable to connect to NVR: %s", str(ex))
return False
identifier = 'id' if nvrconn.server_version >= (3, 2, 0) else 'uuid' identifier = 'id' if nvrconn.server_version >= (3, 2, 0) else 'uuid'
# Filter out airCam models, which are not supported in the latest # Filter out airCam models, which are not supported in the latest
@@ -60,6 +53,15 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
cameras = [ cameras = [
camera for camera in cameras camera for camera in cameras
if 'airCam' not in nvrconn.get_camera(camera[identifier])['model']] if 'airCam' not in nvrconn.get_camera(camera[identifier])['model']]
except nvr.NotAuthorized:
_LOGGER.error("Authorization failure while connecting to NVR")
return False
except nvr.NvrError as ex:
_LOGGER.error("NVR refuses to talk to me: %s", str(ex))
raise PlatformNotReady
except requests.exceptions.ConnectionError as ex:
_LOGGER.error("Unable to connect to NVR: %s", str(ex))
raise PlatformNotReady
add_devices([UnifiVideoCamera(nvrconn, add_devices([UnifiVideoCamera(nvrconn,
camera[identifier], camera[identifier],

View File

@@ -0,0 +1,166 @@
"""
This component provides support for Xiaomi Cameras.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/camera.xiaomi/
"""
import asyncio
import logging
import voluptuous as vol
from homeassistant.components.camera import Camera, PLATFORM_SCHEMA
from homeassistant.components.ffmpeg import DATA_FFMPEG
from homeassistant.const import (CONF_HOST, CONF_NAME, CONF_PATH,
CONF_PASSWORD, CONF_PORT, CONF_USERNAME)
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.aiohttp_client import async_aiohttp_proxy_stream
DEPENDENCIES = ['ffmpeg']
_LOGGER = logging.getLogger(__name__)
DEFAULT_BRAND = 'Xiaomi Home Camera'
DEFAULT_PATH = '/media/mmcblk0p1/record'
DEFAULT_PORT = 21
DEFAULT_USERNAME = 'root'
CONF_FFMPEG_ARGUMENTS = 'ffmpeg_arguments'
CONF_MODEL = 'model'
MODEL_YI = 'yi'
MODEL_XIAOFANG = 'xiaofang'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_NAME): cv.string,
vol.Required(CONF_HOST): cv.string,
vol.Required(CONF_MODEL): vol.Any(MODEL_YI,
MODEL_XIAOFANG),
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.string,
vol.Optional(CONF_PATH, default=DEFAULT_PATH): cv.string,
vol.Optional(CONF_USERNAME, default=DEFAULT_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
vol.Optional(CONF_FFMPEG_ARGUMENTS): cv.string
})
async def async_setup_platform(hass,
config,
async_add_devices,
discovery_info=None):
"""Set up a Xiaomi Camera."""
_LOGGER.debug('Received configuration for model %s', config[CONF_MODEL])
async_add_devices([XiaomiCamera(hass, config)])
class XiaomiCamera(Camera):
"""Define an implementation of a Xiaomi Camera."""
def __init__(self, hass, config):
"""Initialize."""
super().__init__()
self._extra_arguments = config.get(CONF_FFMPEG_ARGUMENTS)
self._last_image = None
self._last_url = None
self._manager = hass.data[DATA_FFMPEG]
self._name = config[CONF_NAME]
self.host = config[CONF_HOST]
self._model = config[CONF_MODEL]
self.port = config[CONF_PORT]
self.path = config[CONF_PATH]
self.user = config[CONF_USERNAME]
self.passwd = config[CONF_PASSWORD]
@property
def name(self):
"""Return the name of this camera."""
return self._name
@property
def brand(self):
"""Return the camera brand."""
return DEFAULT_BRAND
@property
def model(self):
"""Return the camera model."""
return self._model
def get_latest_video_url(self):
"""Retrieve the latest video file from the Xiaomi Camera FTP server."""
from ftplib import FTP, error_perm
ftp = FTP(self.host)
try:
ftp.login(self.user, self.passwd)
except error_perm as exc:
_LOGGER.error('Camera login failed: %s', exc)
return False
try:
ftp.cwd(self.path)
except error_perm as exc:
_LOGGER.error('Unable to find path: %s - %s', self.path, exc)
return False
dirs = [d for d in ftp.nlst() if '.' not in d]
if not dirs:
if self._model == MODEL_YI:
_LOGGER.warning("There don't appear to be any uploaded videos")
return False
elif self._model == MODEL_XIAOFANG:
_LOGGER.warning("There don't appear to be any folders")
return False
first_dir = dirs[-1]
try:
ftp.cwd(first_dir)
except error_perm as exc:
_LOGGER.error('Unable to find path: %s - %s', first_dir, exc)
return False
dirs = [d for d in ftp.nlst() if '.' not in d]
if not dirs:
_LOGGER.warning("There don't appear to be any uploaded videos")
return False
latest_dir = dirs[-1]
ftp.cwd(latest_dir)
videos = [v for v in ftp.nlst() if '.tmp' not in v]
if not videos:
_LOGGER.info('Video folder "%s" is empty; delaying', latest_dir)
return False
if self._model == MODEL_XIAOFANG:
video = videos[-2]
else:
video = videos[-1]
return 'ftp://{0}:{1}@{2}:{3}{4}/{5}'.format(
self.user, self.passwd, self.host, self.port, ftp.pwd(), video)
async def async_camera_image(self):
"""Return a still image response from the camera."""
from haffmpeg import ImageFrame, IMAGE_JPEG
url = await self.hass.async_add_job(self.get_latest_video_url)
if url != self._last_url:
ffmpeg = ImageFrame(self._manager.binary, loop=self.hass.loop)
self._last_image = await asyncio.shield(ffmpeg.get_image(
url, output_format=IMAGE_JPEG,
extra_cmd=self._extra_arguments), loop=self.hass.loop)
self._last_url = url
return self._last_image
async def handle_async_mjpeg_stream(self, request):
"""Generate an HTTP MJPEG stream from the camera."""
from haffmpeg import CameraMjpeg
stream = CameraMjpeg(self._manager.binary, loop=self.hass.loop)
await stream.open_camera(
self._last_url, extra_cmd=self._extra_arguments)
await async_aiohttp_proxy_stream(
self.hass, request, stream,
'multipart/x-mixed-replace;boundary=ffserver')
await stream.close()

View File

@@ -11,11 +11,13 @@ import voluptuous as vol
from homeassistant.components.camera import Camera, PLATFORM_SCHEMA from homeassistant.components.camera import Camera, PLATFORM_SCHEMA
from homeassistant.components.ffmpeg import DATA_FFMPEG from homeassistant.components.ffmpeg import DATA_FFMPEG
from homeassistant.const import (CONF_HOST, CONF_NAME, CONF_PATH, from homeassistant.const import (
CONF_PASSWORD, CONF_PORT, CONF_USERNAME) CONF_HOST, CONF_NAME, CONF_PATH, CONF_PASSWORD, CONF_PORT, CONF_USERNAME)
from homeassistant.helpers import config_validation as cv from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.aiohttp_client import async_aiohttp_proxy_stream from homeassistant.helpers.aiohttp_client import async_aiohttp_proxy_stream
from homeassistant.exceptions import PlatformNotReady
REQUIREMENTS = ['aioftp==0.10.1']
DEPENDENCIES = ['ffmpeg'] DEPENDENCIES = ['ffmpeg']
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@@ -38,12 +40,9 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
}) })
async def async_setup_platform(hass, async def async_setup_platform(
config, hass, config, async_add_devices, discovery_info=None):
async_add_devices,
discovery_info=None):
"""Set up a Yi Camera.""" """Set up a Yi Camera."""
_LOGGER.debug('Received configuration: %s', config)
async_add_devices([YiCamera(hass, config)], True) async_add_devices([YiCamera(hass, config)], True)
@@ -54,71 +53,81 @@ class YiCamera(Camera):
"""Initialize.""" """Initialize."""
super().__init__() super().__init__()
self._extra_arguments = config.get(CONF_FFMPEG_ARGUMENTS) self._extra_arguments = config.get(CONF_FFMPEG_ARGUMENTS)
self._ftp = None
self._last_image = None self._last_image = None
self._last_url = None self._last_url = None
self._manager = hass.data[DATA_FFMPEG] self._manager = hass.data[DATA_FFMPEG]
self._name = config.get(CONF_NAME) self._name = config[CONF_NAME]
self.host = config.get(CONF_HOST) self.host = config[CONF_HOST]
self.port = config.get(CONF_PORT) self.port = config[CONF_PORT]
self.path = config.get(CONF_PATH) self.path = config[CONF_PATH]
self.user = config.get(CONF_USERNAME) self.user = config[CONF_USERNAME]
self.passwd = config.get(CONF_PASSWORD) self.passwd = config[CONF_PASSWORD]
@property hass.async_add_job(self._connect_to_client)
def name(self):
"""Return the name of this camera."""
return self._name
@property @property
def brand(self): def brand(self):
"""Camera brand.""" """Camera brand."""
return DEFAULT_BRAND return DEFAULT_BRAND
def get_latest_video_url(self): @property
def name(self):
"""Return the name of this camera."""
return self._name
async def _connect_to_client(self):
"""Attempt to establish a connection via FTP."""
from aioftp import Client, StatusCodeError
ftp = Client()
try:
await ftp.connect(self.host)
await ftp.login(self.user, self.passwd)
self._ftp = ftp
except StatusCodeError as err:
raise PlatformNotReady(err)
async def _get_latest_video_url(self):
"""Retrieve the latest video file from the customized Yi FTP server.""" """Retrieve the latest video file from the customized Yi FTP server."""
from ftplib import FTP, error_perm from aioftp import StatusCodeError
ftp = FTP(self.host)
try:
ftp.login(self.user, self.passwd)
except error_perm as exc:
_LOGGER.error('There was an error while logging into the camera')
_LOGGER.debug(exc)
return False
try: try:
ftp.cwd(self.path) await self._ftp.change_directory(self.path)
except error_perm as exc: dirs = []
_LOGGER.error('Unable to find path: %s', self.path) for path, attrs in await self._ftp.list():
_LOGGER.debug(exc) if attrs['type'] == 'dir' and '.' not in str(path):
return False dirs.append(path)
dirs = [d for d in ftp.nlst() if '.' not in d]
if not dirs:
_LOGGER.warning("There don't appear to be any uploaded videos")
return False
latest_dir = dirs[-1] latest_dir = dirs[-1]
ftp.cwd(latest_dir) await self._ftp.change_directory(latest_dir)
videos = ftp.nlst()
videos = []
for path, _ in await self._ftp.list():
videos.append(path)
if not videos: if not videos:
_LOGGER.info('Video folder "%s" is empty; delaying', latest_dir) _LOGGER.info('Video folder "%s" empty; delaying', latest_dir)
return False return None
return 'ftp://{0}:{1}@{2}:{3}{4}/{5}/{6}'.format( return 'ftp://{0}:{1}@{2}:{3}{4}/{5}/{6}'.format(
self.user, self.passwd, self.host, self.port, self.path, self.user, self.passwd, self.host, self.port, self.path,
latest_dir, videos[-1]) latest_dir, videos[-1])
except (ConnectionRefusedError, StatusCodeError) as err:
_LOGGER.error('Error while fetching video: %s', err)
return None
async def async_camera_image(self): async def async_camera_image(self):
"""Return a still image response from the camera.""" """Return a still image response from the camera."""
from haffmpeg import ImageFrame, IMAGE_JPEG from haffmpeg import ImageFrame, IMAGE_JPEG
url = await self.hass.async_add_job(self.get_latest_video_url) url = await self._get_latest_video_url()
if url != self._last_url: if url != self._last_url:
ffmpeg = ImageFrame(self._manager.binary, loop=self.hass.loop) ffmpeg = ImageFrame(self._manager.binary, loop=self.hass.loop)
self._last_image = await asyncio.shield(ffmpeg.get_image( self._last_image = await asyncio.shield(
url, output_format=IMAGE_JPEG, ffmpeg.get_image(
extra_cmd=self._extra_arguments), loop=self.hass.loop) url,
output_format=IMAGE_JPEG,
extra_cmd=self._extra_arguments),
loop=self.hass.loop)
self._last_url = url self._last_url = url
return self._last_image return self._last_image

View File

@@ -49,7 +49,6 @@ def _get_image_url(hass, monitor, mode):
@asyncio.coroutine @asyncio.coroutine
# pylint: disable=unused-argument
def async_setup_platform(hass, config, async_add_devices, discovery_info=None): def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Set up the ZoneMinder cameras.""" """Set up the ZoneMinder cameras."""
cameras = [] cameras = []

View File

@@ -0,0 +1,15 @@
{
"config": {
"abort": {
"no_devices_found": "No s'han trobat dispositius de Google Cast a la xarxa.",
"single_instance_allowed": "Nom\u00e9s cal una \u00fanica configuraci\u00f3 de Google Cast."
},
"step": {
"confirm": {
"description": "Voleu configurar Google Cast?",
"title": "Google Cast"
}
},
"title": "Google Cast"
}
}

View File

@@ -0,0 +1,15 @@
{
"config": {
"abort": {
"no_devices_found": "No Google Cast devices found on the network.",
"single_instance_allowed": "Only a single configuration of Google Cast is necessary."
},
"step": {
"confirm": {
"description": "Do you want to setup Google Cast?",
"title": "Google Cast"
}
},
"title": "Google Cast"
}
}

View File

@@ -0,0 +1,15 @@
{
"config": {
"abort": {
"no_devices_found": "Googgle Cast \uc7a5\uce58\uac00 \ub124\ud2b8\uc6cc\ud06c\uc5d0\uc11c \ubc1c\uacac\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4.",
"single_instance_allowed": "Google Cast\uc758 \ub2e8\uc77c \uad6c\uc131 \ub9cc \ud544\uc694\ud569\ub2c8\ub2e4."
},
"step": {
"confirm": {
"description": "Google Cast\ub97c \uc124\uc815 \ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?",
"title": "Google Cast"
}
},
"title": "Google Cast"
}
}

View File

@@ -0,0 +1,15 @@
{
"config": {
"abort": {
"no_devices_found": "Ingen Google Cast enheter funnet p\u00e5 nettverket.",
"single_instance_allowed": "Kun en enkelt konfigurasjon av Google Cast er n\u00f8dvendig."
},
"step": {
"confirm": {
"description": "\u00d8nsker du \u00e5 sette opp Google Cast?",
"title": "Google Cast"
}
},
"title": "Google Cast"
}
}

View File

@@ -0,0 +1,15 @@
{
"config": {
"abort": {
"no_devices_found": "Nie znaleziono w sieci urz\u0105dze\u0144 Google Cast.",
"single_instance_allowed": "Wymagana jest tylko jedna konfiguracja Google Cast."
},
"step": {
"confirm": {
"description": "Czy chcesz skonfigurowa\u0107 Google Cast?",
"title": "Google Cast"
}
},
"title": "Google Cast"
}
}

View File

@@ -0,0 +1,15 @@
{
"config": {
"abort": {
"no_devices_found": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 Google Cast \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u044b.",
"single_instance_allowed": "\u041d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u0430 \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f Google Cast."
},
"step": {
"confirm": {
"description": "\u0412\u044b \u0445\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c Google Cast?",
"title": "Google Cast"
}
},
"title": "Google Cast"
}
}

View File

@@ -0,0 +1,15 @@
{
"config": {
"abort": {
"no_devices_found": "Inga Google Cast-enheter hittades i n\u00e4tverket.",
"single_instance_allowed": "Endast en enda konfiguration av Google Cast \u00e4r n\u00f6dv\u00e4ndig."
},
"step": {
"confirm": {
"description": "Vill du konfigurera Google Cast?",
"title": "Google Cast"
}
},
"title": "Google Cast"
}
}

View File

@@ -0,0 +1,15 @@
{
"config": {
"abort": {
"no_devices_found": "Kh\u00f4ng t\u00ecm th\u1ea5y thi\u1ebft b\u1ecb Google Cast n\u00e0o tr\u00ean m\u1ea1ng.",
"single_instance_allowed": "Ch\u1ec9 c\u1ea7n m\u1ed9t c\u1ea5u h\u00ecnh duy nh\u1ea5t c\u1ee7a Google Cast l\u00e0 \u0111\u1ee7."
},
"step": {
"confirm": {
"description": "B\u1ea1n c\u00f3 mu\u1ed1n thi\u1ebft l\u1eadp Google Cast kh\u00f4ng?",
"title": "Google Cast"
}
},
"title": "Google Cast"
}
}

View File

@@ -0,0 +1,15 @@
{
"config": {
"abort": {
"no_devices_found": "\u6ca1\u6709\u5728\u7f51\u7edc\u4e0a\u627e\u5230 Google Cast \u8bbe\u5907\u3002",
"single_instance_allowed": "\u53ea\u6709\u4e00\u6b21 Google Cast \u914d\u7f6e\u662f\u5fc5\u8981\u7684\u3002"
},
"step": {
"confirm": {
"description": "\u60a8\u60f3\u8981\u914d\u7f6e Google Cast \u5417\uff1f",
"title": "Google Cast"
}
},
"title": "Google Cast"
}
}

View File

@@ -0,0 +1,30 @@
"""Component to embed Google Cast."""
from homeassistant.helpers import config_entry_flow
DOMAIN = 'cast'
REQUIREMENTS = ['pychromecast==2.1.0']
async def async_setup(hass, config):
"""Set up the Cast component."""
hass.data[DOMAIN] = config.get(DOMAIN, {})
return True
async def async_setup_entry(hass, entry):
"""Set up Cast from a config entry."""
hass.async_add_job(hass.config_entries.async_forward_entry_setup(
entry, 'media_player'))
return True
async def _async_has_devices(hass):
"""Return if there are devices that can be discovered."""
from pychromecast.discovery import discover_chromecasts
return await hass.async_add_job(discover_chromecasts)
config_entry_flow.register_discovery_flow(
DOMAIN, 'Google Cast', _async_has_devices)

View File

@@ -0,0 +1,15 @@
{
"config": {
"title": "Google Cast",
"step": {
"confirm": {
"title": "Google Cast",
"description": "Do you want to setup Google Cast?"
}
},
"abort": {
"single_instance_allowed": "Only a single configuration of Google Cast is necessary.",
"no_devices_found": "No Google Cast devices found on the network."
}
}
}

View File

@@ -246,7 +246,8 @@ def set_swing_mode(hass, swing_mode, entity_id=None):
async def async_setup(hass, config): async def async_setup(hass, config):
"""Set up climate devices.""" """Set up climate devices."""
component = EntityComponent(_LOGGER, DOMAIN, hass, SCAN_INTERVAL) component = hass.data[DOMAIN] = \
EntityComponent(_LOGGER, DOMAIN, hass, SCAN_INTERVAL)
await component.async_setup(config) await component.async_setup(config)
async def async_away_mode_set_service(service): async def async_away_mode_set_service(service):
@@ -456,6 +457,16 @@ async def async_setup(hass, config):
return True return True
async def async_setup_entry(hass, entry):
"""Setup a config entry."""
return await hass.data[DOMAIN].async_setup_entry(entry)
async def async_unload_entry(hass, entry):
"""Unload a config entry."""
return await hass.data[DOMAIN].async_unload_entry(entry)
class ClimateDevice(Entity): class ClimateDevice(Entity):
"""Representation of a climate device.""" """Representation of a climate device."""

View File

@@ -13,21 +13,27 @@ from homeassistant.components.fritzbox import (
ATTR_STATE_DEVICE_LOCKED, ATTR_STATE_BATTERY_LOW, ATTR_STATE_LOCKED) ATTR_STATE_DEVICE_LOCKED, ATTR_STATE_BATTERY_LOW, ATTR_STATE_LOCKED)
from homeassistant.components.climate import ( from homeassistant.components.climate import (
ATTR_OPERATION_MODE, ClimateDevice, STATE_ECO, STATE_HEAT, STATE_MANUAL, ATTR_OPERATION_MODE, ClimateDevice, STATE_ECO, STATE_HEAT, STATE_MANUAL,
SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE) STATE_OFF, STATE_ON, SUPPORT_OPERATION_MODE,
SUPPORT_TARGET_TEMPERATURE)
from homeassistant.const import ( from homeassistant.const import (
ATTR_TEMPERATURE, PRECISION_HALVES, TEMP_CELSIUS) ATTR_TEMPERATURE, PRECISION_HALVES, TEMP_CELSIUS)
DEPENDENCIES = ['fritzbox'] DEPENDENCIES = ['fritzbox']
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE) SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE)
OPERATION_LIST = [STATE_HEAT, STATE_ECO] OPERATION_LIST = [STATE_HEAT, STATE_ECO, STATE_OFF, STATE_ON]
MIN_TEMPERATURE = 8 MIN_TEMPERATURE = 8
MAX_TEMPERATURE = 28 MAX_TEMPERATURE = 28
# special temperatures for on/off in Fritz!Box API (modified by pyfritzhome)
ON_API_TEMPERATURE = 127.0
OFF_API_TEMPERATURE = 126.5
ON_REPORT_SET_TEMPERATURE = 30.0
OFF_REPORT_SET_TEMPERATURE = 0.0
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Fritzbox smarthome thermostat platform.""" """Set up the Fritzbox smarthome thermostat platform."""
@@ -88,6 +94,9 @@ class FritzboxThermostat(ClimateDevice):
@property @property
def target_temperature(self): def target_temperature(self):
"""Return the temperature we try to reach.""" """Return the temperature we try to reach."""
if self._target_temperature in (ON_API_TEMPERATURE,
OFF_API_TEMPERATURE):
return None
return self._target_temperature return self._target_temperature
def set_temperature(self, **kwargs): def set_temperature(self, **kwargs):
@@ -102,9 +111,13 @@ class FritzboxThermostat(ClimateDevice):
@property @property
def current_operation(self): def current_operation(self):
"""Return the current operation mode.""" """Return the current operation mode."""
if self._target_temperature == ON_API_TEMPERATURE:
return STATE_ON
if self._target_temperature == OFF_API_TEMPERATURE:
return STATE_OFF
if self._target_temperature == self._comfort_temperature: if self._target_temperature == self._comfort_temperature:
return STATE_HEAT return STATE_HEAT
elif self._target_temperature == self._eco_temperature: if self._target_temperature == self._eco_temperature:
return STATE_ECO return STATE_ECO
return STATE_MANUAL return STATE_MANUAL
@@ -119,6 +132,10 @@ class FritzboxThermostat(ClimateDevice):
self.set_temperature(temperature=self._comfort_temperature) self.set_temperature(temperature=self._comfort_temperature)
elif operation_mode == STATE_ECO: elif operation_mode == STATE_ECO:
self.set_temperature(temperature=self._eco_temperature) self.set_temperature(temperature=self._eco_temperature)
elif operation_mode == STATE_OFF:
self.set_temperature(temperature=OFF_REPORT_SET_TEMPERATURE)
elif operation_mode == STATE_ON:
self.set_temperature(temperature=ON_REPORT_SET_TEMPERATURE)
@property @property
def min_temp(self): def min_temp(self):

View File

@@ -14,8 +14,7 @@ from homeassistant.core import DOMAIN as HA_DOMAIN
from homeassistant.components.climate import ( from homeassistant.components.climate import (
STATE_HEAT, STATE_COOL, STATE_IDLE, STATE_AUTO, ClimateDevice, STATE_HEAT, STATE_COOL, STATE_IDLE, STATE_AUTO, ClimateDevice,
ATTR_OPERATION_MODE, ATTR_AWAY_MODE, SUPPORT_OPERATION_MODE, ATTR_OPERATION_MODE, ATTR_AWAY_MODE, SUPPORT_OPERATION_MODE,
SUPPORT_AWAY_MODE, SUPPORT_TARGET_TEMPERATURE, PLATFORM_SCHEMA, SUPPORT_AWAY_MODE, SUPPORT_TARGET_TEMPERATURE, PLATFORM_SCHEMA)
DEFAULT_MIN_TEMP, DEFAULT_MAX_TEMP)
from homeassistant.const import ( from homeassistant.const import (
ATTR_UNIT_OF_MEASUREMENT, STATE_ON, STATE_OFF, ATTR_TEMPERATURE, ATTR_UNIT_OF_MEASUREMENT, STATE_ON, STATE_OFF, ATTR_TEMPERATURE,
CONF_NAME, ATTR_ENTITY_ID, SERVICE_TURN_ON, SERVICE_TURN_OFF, CONF_NAME, ATTR_ENTITY_ID, SERVICE_TURN_ON, SERVICE_TURN_OFF,
@@ -268,7 +267,8 @@ class GenericThermostat(ClimateDevice):
if self._min_temp: if self._min_temp:
return self._min_temp return self._min_temp
return DEFAULT_MIN_TEMP # get default temp from super class
return super().min_temp
@property @property
def max_temp(self): def max_temp(self):
@@ -277,7 +277,8 @@ class GenericThermostat(ClimateDevice):
if self._max_temp: if self._max_temp:
return self._max_temp return self._max_temp
return DEFAULT_MAX_TEMP # Get default temp from super class
return super().max_temp
@asyncio.coroutine @asyncio.coroutine
def _async_sensor_changed(self, entity_id, old_state, new_state): def _async_sensor_changed(self, entity_id, old_state, new_state):

View File

@@ -136,7 +136,6 @@ class KNXClimate(ClimateDevice):
"""Register callbacks to update hass after device was changed.""" """Register callbacks to update hass after device was changed."""
async def after_update_callback(device): async def after_update_callback(device):
"""Call after device was updated.""" """Call after device was updated."""
# pylint: disable=unused-argument
await self.async_update_ha_state() await self.async_update_ha_state()
self.device.register_device_updated_cb(after_update_callback) self.device.register_device_updated_cb(after_update_callback)

View File

@@ -17,7 +17,7 @@ from homeassistant.components.climate import (
PLATFORM_SCHEMA as CLIMATE_PLATFORM_SCHEMA, STATE_AUTO, PLATFORM_SCHEMA as CLIMATE_PLATFORM_SCHEMA, STATE_AUTO,
ATTR_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE, SUPPORT_OPERATION_MODE, ATTR_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE, SUPPORT_OPERATION_MODE,
SUPPORT_SWING_MODE, SUPPORT_FAN_MODE, SUPPORT_AWAY_MODE, SUPPORT_HOLD_MODE, SUPPORT_SWING_MODE, SUPPORT_FAN_MODE, SUPPORT_AWAY_MODE, SUPPORT_HOLD_MODE,
SUPPORT_AUX_HEAT) SUPPORT_AUX_HEAT, DEFAULT_MIN_TEMP, DEFAULT_MAX_TEMP)
from homeassistant.const import ( from homeassistant.const import (
STATE_ON, STATE_OFF, ATTR_TEMPERATURE, CONF_NAME, CONF_VALUE_TEMPLATE) STATE_ON, STATE_OFF, ATTR_TEMPERATURE, CONF_NAME, CONF_VALUE_TEMPLATE)
from homeassistant.components.mqtt import ( from homeassistant.components.mqtt import (
@@ -70,6 +70,9 @@ CONF_SWING_MODE_LIST = 'swing_modes'
CONF_INITIAL = 'initial' CONF_INITIAL = 'initial'
CONF_SEND_IF_OFF = 'send_if_off' CONF_SEND_IF_OFF = 'send_if_off'
CONF_MIN_TEMP = 'min_temp'
CONF_MAX_TEMP = 'max_temp'
SCHEMA_BASE = CLIMATE_PLATFORM_SCHEMA.extend(MQTT_BASE_PLATFORM_SCHEMA.schema) SCHEMA_BASE = CLIMATE_PLATFORM_SCHEMA.extend(MQTT_BASE_PLATFORM_SCHEMA.schema)
PLATFORM_SCHEMA = SCHEMA_BASE.extend({ PLATFORM_SCHEMA = SCHEMA_BASE.extend({
vol.Optional(CONF_POWER_COMMAND_TOPIC): mqtt.valid_publish_topic, vol.Optional(CONF_POWER_COMMAND_TOPIC): mqtt.valid_publish_topic,
@@ -116,6 +119,10 @@ PLATFORM_SCHEMA = SCHEMA_BASE.extend({
vol.Optional(CONF_SEND_IF_OFF, default=True): cv.boolean, vol.Optional(CONF_SEND_IF_OFF, default=True): cv.boolean,
vol.Optional(CONF_PAYLOAD_ON, default="ON"): cv.string, vol.Optional(CONF_PAYLOAD_ON, default="ON"): cv.string,
vol.Optional(CONF_PAYLOAD_OFF, default="OFF"): cv.string, vol.Optional(CONF_PAYLOAD_OFF, default="OFF"): cv.string,
vol.Optional(CONF_MIN_TEMP, default=DEFAULT_MIN_TEMP): vol.Coerce(float),
vol.Optional(CONF_MAX_TEMP, default=DEFAULT_MAX_TEMP): vol.Coerce(float)
}).extend(mqtt.MQTT_AVAILABILITY_SCHEMA.schema) }).extend(mqtt.MQTT_AVAILABILITY_SCHEMA.schema)
@@ -181,19 +188,22 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
config.get(CONF_PAYLOAD_OFF), config.get(CONF_PAYLOAD_OFF),
config.get(CONF_AVAILABILITY_TOPIC), config.get(CONF_AVAILABILITY_TOPIC),
config.get(CONF_PAYLOAD_AVAILABLE), config.get(CONF_PAYLOAD_AVAILABLE),
config.get(CONF_PAYLOAD_NOT_AVAILABLE)) config.get(CONF_PAYLOAD_NOT_AVAILABLE),
config.get(CONF_MIN_TEMP),
config.get(CONF_MAX_TEMP))
]) ])
class MqttClimate(MqttAvailability, ClimateDevice): class MqttClimate(MqttAvailability, ClimateDevice):
"""Representation of a demo climate device.""" """Representation of an MQTT climate device."""
def __init__(self, hass, name, topic, value_templates, qos, retain, def __init__(self, hass, name, topic, value_templates, qos, retain,
mode_list, fan_mode_list, swing_mode_list, mode_list, fan_mode_list, swing_mode_list,
target_temperature, away, hold, current_fan_mode, target_temperature, away, hold, current_fan_mode,
current_swing_mode, current_operation, aux, send_if_off, current_swing_mode, current_operation, aux, send_if_off,
payload_on, payload_off, availability_topic, payload_on, payload_off, availability_topic,
payload_available, payload_not_available): payload_available, payload_not_available,
min_temp, max_temp):
"""Initialize the climate device.""" """Initialize the climate device."""
super().__init__(availability_topic, qos, payload_available, super().__init__(availability_topic, qos, payload_available,
payload_not_available) payload_not_available)
@@ -219,6 +229,8 @@ class MqttClimate(MqttAvailability, ClimateDevice):
self._send_if_off = send_if_off self._send_if_off = send_if_off
self._payload_on = payload_on self._payload_on = payload_on
self._payload_off = payload_off self._payload_off = payload_off
self._min_temp = min_temp
self._max_temp = max_temp
@asyncio.coroutine @asyncio.coroutine
def async_added_to_hass(self): def async_added_to_hass(self):
@@ -619,3 +631,15 @@ class MqttClimate(MqttAvailability, ClimateDevice):
support |= SUPPORT_AUX_HEAT support |= SUPPORT_AUX_HEAT
return support return support
@property
def min_temp(self):
"""Return the minimum temperature."""
# pylint: disable=no-member
return self._min_temp
@property
def max_temp(self):
"""Return the maximum temperature."""
# pylint: disable=no-member
return self._max_temp

View File

@@ -32,16 +32,22 @@ NEST_MODE_HEAT_COOL = 'heat-cool'
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Nest thermostat.""" """Set up the Nest thermostat.
if discovery_info is None:
return
No longer in use.
"""
async def async_setup_entry(hass, entry, async_add_devices):
"""Set up the Nest climate device based on a config entry."""
temp_unit = hass.config.units.temperature_unit temp_unit = hass.config.units.temperature_unit
all_devices = [NestThermostat(structure, device, temp_unit) thermostats = await hass.async_add_job(hass.data[DATA_NEST].thermostats)
for structure, device in hass.data[DATA_NEST].thermostats()]
add_devices(all_devices, True) all_devices = [NestThermostat(structure, device, temp_unit)
for structure, device in thermostats]
async_add_devices(all_devices, True)
class NestThermostat(ClimateDevice): class NestThermostat(ClimateDevice):

View File

@@ -44,7 +44,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
netatmo = hass.components.netatmo netatmo = hass.components.netatmo
device = config.get(CONF_RELAY) device = config.get(CONF_RELAY)
import lnetatmo import pyatmo
try: try:
data = ThermostatData(netatmo.NETATMO_AUTH, device) data = ThermostatData(netatmo.NETATMO_AUTH, device)
for module_name in data.get_module_names(): for module_name in data.get_module_names():
@@ -53,7 +53,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
module_name not in config[CONF_THERMOSTAT]: module_name not in config[CONF_THERMOSTAT]:
continue continue
add_devices([NetatmoThermostat(data, module_name)], True) add_devices([NetatmoThermostat(data, module_name)], True)
except lnetatmo.NoDevice: except pyatmo.NoDevice:
return None return None
@@ -168,8 +168,8 @@ class ThermostatData(object):
@Throttle(MIN_TIME_BETWEEN_UPDATES) @Throttle(MIN_TIME_BETWEEN_UPDATES)
def update(self): def update(self):
"""Call the NetAtmo API to update the data.""" """Call the NetAtmo API to update the data."""
import lnetatmo import pyatmo
self.thermostatdata = lnetatmo.ThermostatData(self.auth) self.thermostatdata = pyatmo.ThermostatData(self.auth)
self.target_temperature = self.thermostatdata.setpoint_temp self.target_temperature = self.thermostatdata.setpoint_temp
self.setpoint_mode = self.thermostatdata.setpoint_mode self.setpoint_mode = self.thermostatdata.setpoint_mode
self.current_temperature = self.thermostatdata.temp self.current_temperature = self.thermostatdata.temp

View File

@@ -19,7 +19,7 @@ from homeassistant.components.climate import (
ATTR_CURRENT_HUMIDITY, ClimateDevice, DOMAIN, PLATFORM_SCHEMA, ATTR_CURRENT_HUMIDITY, ClimateDevice, DOMAIN, PLATFORM_SCHEMA,
SUPPORT_TARGET_TEMPERATURE, SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE, SUPPORT_OPERATION_MODE,
SUPPORT_FAN_MODE, SUPPORT_SWING_MODE, SUPPORT_FAN_MODE, SUPPORT_SWING_MODE,
SUPPORT_ON_OFF, DEFAULT_MIN_TEMP, DEFAULT_MAX_TEMP) SUPPORT_ON_OFF)
from homeassistant.exceptions import PlatformNotReady from homeassistant.exceptions import PlatformNotReady
from homeassistant.helpers import config_validation as cv from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.aiohttp_client import async_get_clientsession
@@ -246,13 +246,13 @@ class SensiboClimate(ClimateDevice):
def min_temp(self): def min_temp(self):
"""Return the minimum temperature.""" """Return the minimum temperature."""
return self._temperatures_list[0] \ return self._temperatures_list[0] \
if self._temperatures_list else DEFAULT_MIN_TEMP if self._temperatures_list else super().min_temp
@property @property
def max_temp(self): def max_temp(self):
"""Return the maximum temperature.""" """Return the maximum temperature."""
return self._temperatures_list[-1] \ return self._temperatures_list[-1] \
if self._temperatures_list else DEFAULT_MAX_TEMP if self._temperatures_list else super().max_temp
@property @property
def unique_id(self): def unique_id(self):

View File

@@ -8,8 +8,8 @@ import logging
from homeassistant.const import (PRECISION_TENTHS, TEMP_CELSIUS) from homeassistant.const import (PRECISION_TENTHS, TEMP_CELSIUS)
from homeassistant.components.climate import ( from homeassistant.components.climate import (
ClimateDevice, SUPPORT_TARGET_TEMPERATURE, SUPPORT_OPERATION_MODE, ClimateDevice, SUPPORT_TARGET_TEMPERATURE, SUPPORT_OPERATION_MODE)
DEFAULT_MIN_TEMP, DEFAULT_MAX_TEMP) from homeassistant.util.temperature import convert as convert_temperature
from homeassistant.const import ATTR_TEMPERATURE from homeassistant.const import ATTR_TEMPERATURE
from homeassistant.components.tado import DATA_TADO from homeassistant.components.tado import DATA_TADO
@@ -231,18 +231,14 @@ class TadoClimate(ClimateDevice):
@property @property
def min_temp(self): def min_temp(self):
"""Return the minimum temperature.""" """Return the minimum temperature."""
if self._min_temp: return convert_temperature(self._min_temp, self._unit,
return self._min_temp self.hass.config.units.temperature_unit)
return DEFAULT_MIN_TEMP
@property @property
def max_temp(self): def max_temp(self):
"""Return the maximum temperature.""" """Return the maximum temperature."""
if self._max_temp: return convert_temperature(self._max_temp, self._unit,
return self._max_temp self.hass.config.units.temperature_unit)
return DEFAULT_MAX_TEMP
def update(self): def update(self):
"""Update the state of this climate device.""" """Update the state of this climate device."""

View File

@@ -32,8 +32,8 @@ SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE |
def setup_platform(hass, config, add_devices_callback, discovery_info=None): def setup_platform(hass, config, add_devices_callback, discovery_info=None):
"""Set up of Vera thermostats.""" """Set up of Vera thermostats."""
add_devices_callback( add_devices_callback(
VeraThermostat(device, hass.data[VERA_CONTROLLER]) for [VeraThermostat(device, hass.data[VERA_CONTROLLER]) for
device in hass.data[VERA_DEVICES]['climate']) device in hass.data[VERA_DEVICES]['climate']], True)
class VeraThermostat(VeraDevice, ClimateDevice): class VeraThermostat(VeraDevice, ClimateDevice):
@@ -101,10 +101,6 @@ class VeraThermostat(VeraDevice, ClimateDevice):
if power: if power:
return convert(power, float, 0.0) return convert(power, float, 0.0)
def update(self):
"""Handle state updates."""
self._state = self.vera_device.get_hvac_mode()
@property @property
def temperature_unit(self): def temperature_unit(self):
"""Return the unit of measurement.""" """Return the unit of measurement."""

View File

@@ -84,7 +84,6 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
add_devices([WinkWaterHeater(water_heater, hass)]) add_devices([WinkWaterHeater(water_heater, hass)])
# pylint: disable=abstract-method
class WinkThermostat(WinkDevice, ClimateDevice): class WinkThermostat(WinkDevice, ClimateDevice):
"""Representation of a Wink thermostat.""" """Representation of a Wink thermostat."""

View File

@@ -0,0 +1,217 @@
"""
Support for ZhongHong HVAC Controller.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/climate.zhong_hong/
"""
import logging
import voluptuous as vol
from homeassistant.components.climate import (
ATTR_OPERATION_MODE, PLATFORM_SCHEMA, STATE_COOL, STATE_DRY,
STATE_FAN_ONLY, STATE_HEAT, SUPPORT_FAN_MODE, SUPPORT_ON_OFF,
SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE, ClimateDevice)
from homeassistant.const import (ATTR_TEMPERATURE, CONF_HOST, CONF_PORT,
EVENT_HOMEASSISTANT_STOP, TEMP_CELSIUS)
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.dispatcher import (async_dispatcher_connect,
async_dispatcher_send)
_LOGGER = logging.getLogger(__name__)
CONF_GATEWAY_ADDRRESS = 'gateway_address'
REQUIREMENTS = ['zhong_hong_hvac==1.0.9']
SIGNAL_DEVICE_ADDED = 'zhong_hong_device_added'
SIGNAL_ZHONG_HONG_HUB_START = 'zhong_hong_hub_start'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_HOST):
cv.string,
vol.Optional(CONF_PORT, default=9999):
vol.Coerce(int),
vol.Optional(CONF_GATEWAY_ADDRRESS, default=1):
vol.Coerce(int),
})
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the ZhongHong HVAC platform."""
from zhong_hong_hvac.hub import ZhongHongGateway
host = config.get(CONF_HOST)
port = config.get(CONF_PORT)
gw_addr = config.get(CONF_GATEWAY_ADDRRESS)
hub = ZhongHongGateway(host, port, gw_addr)
devices = [
ZhongHongClimate(hub, addr_out, addr_in)
for (addr_out, addr_in) in hub.discovery_ac()
]
_LOGGER.debug("We got %s zhong_hong climate devices", len(devices))
hub_is_initialized = False
async def startup():
"""Start hub socket after all climate entity is setted up."""
nonlocal hub_is_initialized
if not all([device.is_initialized for device in devices]):
return
if hub_is_initialized:
return
_LOGGER.debug("zhong_hong hub start listen event")
await hass.async_add_job(hub.start_listen)
await hass.async_add_job(hub.query_all_status)
hub_is_initialized = True
async_dispatcher_connect(hass, SIGNAL_DEVICE_ADDED, startup)
# add devices after SIGNAL_DEVICE_SETTED_UP event is listend
add_devices(devices)
def stop_listen(event):
"""Stop ZhongHongHub socket."""
hub.stop_listen()
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_listen)
class ZhongHongClimate(ClimateDevice):
"""Representation of a ZhongHong controller support HVAC."""
def __init__(self, hub, addr_out, addr_in):
"""Set up the ZhongHong climate devices."""
from zhong_hong_hvac.hvac import HVAC
self._device = HVAC(hub, addr_out, addr_in)
self._hub = hub
self._current_operation = None
self._current_temperature = None
self._target_temperature = None
self._current_fan_mode = None
self._is_on = None
self.is_initialized = False
async def async_added_to_hass(self):
"""Register callbacks."""
self._device.register_update_callback(self._after_update)
self.is_initialized = True
async_dispatcher_send(self.hass, SIGNAL_DEVICE_ADDED)
def _after_update(self, climate):
"""Callback to update state."""
_LOGGER.debug("async update ha state")
if self._device.current_operation:
self._current_operation = self._device.current_operation.lower()
if self._device.current_temperature:
self._current_temperature = self._device.current_temperature
if self._device.current_fan_mode:
self._current_fan_mode = self._device.current_fan_mode
if self._device.target_temperature:
self._target_temperature = self._device.target_temperature
self._is_on = self._device.is_on
self.schedule_update_ha_state()
@property
def should_poll(self):
"""Return the polling state."""
return False
@property
def name(self):
"""Return the name of the thermostat, if any."""
return self.unique_id
@property
def unique_id(self):
"""Return the unique ID of the HVAC."""
return "zhong_hong_hvac_{}_{}".format(self._device.addr_out,
self._device.addr_in)
@property
def supported_features(self):
"""Return the list of supported features."""
return (SUPPORT_TARGET_TEMPERATURE | SUPPORT_FAN_MODE
| SUPPORT_OPERATION_MODE | SUPPORT_ON_OFF)
@property
def temperature_unit(self):
"""Return the unit of measurement used by the platform."""
return TEMP_CELSIUS
@property
def current_operation(self):
"""Return current operation ie. heat, cool, idle."""
return self._current_operation
@property
def operation_list(self):
"""Return the list of available operation modes."""
return [STATE_COOL, STATE_HEAT, STATE_DRY, STATE_FAN_ONLY]
@property
def current_temperature(self):
"""Return the current temperature."""
return self._current_temperature
@property
def target_temperature(self):
"""Return the temperature we try to reach."""
return self._target_temperature
@property
def target_temperature_step(self):
"""Return the supported step of target temperature."""
return 1
@property
def is_on(self):
"""Return true if on."""
return self._device.is_on
@property
def current_fan_mode(self):
"""Return the fan setting."""
return self._current_fan_mode
@property
def fan_list(self):
"""Return the list of available fan modes."""
return self._device.fan_list
@property
def min_temp(self):
"""Return the minimum temperature."""
return self._device.min_temp
@property
def max_temp(self):
"""Return the maximum temperature."""
return self._device.max_temp
def turn_on(self):
"""Turn on ac."""
return self._device.turn_on()
def turn_off(self):
"""Turn off ac."""
return self._device.turn_off()
def set_temperature(self, **kwargs):
"""Set new target temperature."""
temperature = kwargs.get(ATTR_TEMPERATURE)
if temperature is not None:
self._device.set_temperature(temperature)
operation_mode = kwargs.get(ATTR_OPERATION_MODE)
if operation_mode is not None:
self.set_operation_mode(operation_mode)
def set_operation_mode(self, operation_mode):
"""Set new target operation mode."""
self._device.set_operation_mode(operation_mode.upper())
def set_fan_mode(self, fan_mode):
"""Set new target fan mode."""
self._device.set_fan_mode(fan_mode)

View File

@@ -2,48 +2,85 @@
import voluptuous as vol import voluptuous as vol
from homeassistant.core import callback from homeassistant.core import callback
from homeassistant.components.http import HomeAssistantView
from homeassistant.components.http.data_validator import RequestDataValidator
from homeassistant.helpers.entity_registry import async_get_registry from homeassistant.helpers.entity_registry import async_get_registry
from homeassistant.components import websocket_api
from homeassistant.helpers import config_validation as cv
DEPENDENCIES = ['websocket_api']
WS_TYPE_GET = 'config/entity_registry/get'
SCHEMA_WS_GET = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({
vol.Required('type'): WS_TYPE_GET,
vol.Required('entity_id'): cv.entity_id
})
WS_TYPE_UPDATE = 'config/entity_registry/update'
SCHEMA_WS_UPDATE = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({
vol.Required('type'): WS_TYPE_UPDATE,
vol.Required('entity_id'): cv.entity_id,
# If passed in, we update value. Passing None will remove old value.
vol.Optional('name'): vol.Any(str, None),
})
async def async_setup(hass): async def async_setup(hass):
"""Enable the Entity Registry views.""" """Enable the Entity Registry views."""
hass.http.register_view(ConfigManagerEntityView) hass.components.websocket_api.async_register_command(
WS_TYPE_GET, websocket_get_entity,
SCHEMA_WS_GET
)
hass.components.websocket_api.async_register_command(
WS_TYPE_UPDATE, websocket_update_entity,
SCHEMA_WS_UPDATE
)
return True return True
class ConfigManagerEntityView(HomeAssistantView): @callback
"""View to interact with an entity registry entry.""" def websocket_get_entity(hass, connection, msg):
"""Handle get entity registry entry command.
url = '/api/config/entity_registry/{entity_id}' Async friendly.
name = 'api:config:entity_registry:entity' """
async def retrieve_entity():
async def get(self, request, entity_id): """Get entity from registry."""
"""Get the entity registry settings for an entity."""
hass = request.app['hass']
registry = await async_get_registry(hass) registry = await async_get_registry(hass)
entry = registry.entities.get(entity_id) entry = registry.entities.get(msg['entity_id'])
if entry is None: if entry is None:
return self.json_message('Entry not found', 404) connection.send_message_outside(websocket_api.error_message(
msg['id'], websocket_api.ERR_NOT_FOUND, 'Entity not found'))
return
return self.json(_entry_dict(entry)) connection.send_message_outside(websocket_api.result_message(
msg['id'], _entry_dict(entry)
))
@RequestDataValidator(vol.Schema({ hass.async_add_job(retrieve_entity())
# If passed in, we update value. Passing None will remove old value.
vol.Optional('name'): vol.Any(str, None),
})) @callback
async def post(self, request, entity_id, data): def websocket_update_entity(hass, connection, msg):
"""Update the entity registry settings for an entity.""" """Handle get camera thumbnail websocket command.
hass = request.app['hass']
Async friendly.
"""
async def update_entity():
"""Get entity from registry."""
registry = await async_get_registry(hass) registry = await async_get_registry(hass)
if entity_id not in registry.entities: if msg['entity_id'] not in registry.entities:
return self.json_message('Entry not found', 404) connection.send_message_outside(websocket_api.error_message(
msg['id'], websocket_api.ERR_NOT_FOUND, 'Entity not found'))
return
entry = registry.async_update_entity(entity_id, **data) entry = registry.async_update_entity(
return self.json(_entry_dict(entry)) msg['entity_id'], name=msg['name'])
connection.send_message_outside(websocket_api.result_message(
msg['id'], _entry_dict(entry)
))
hass.async_add_job(update_entity())
@callback @callback

View File

@@ -25,7 +25,6 @@ VALUE_TO_STATE = {
} }
# pylint: disable=unused-argument
def setup_platform(hass, config: ConfigType, def setup_platform(hass, config: ConfigType,
add_devices: Callable[[list], None], discovery_info=None): add_devices: Callable[[list], None], discovery_info=None):
"""Set up the ISY994 cover platform.""" """Set up the ISY994 cover platform."""

View File

@@ -107,7 +107,6 @@ class KNXCover(CoverDevice):
"""Register callbacks to update hass after device was changed.""" """Register callbacks to update hass after device was changed."""
async def after_update_callback(device): async def after_update_callback(device):
"""Call after device was updated.""" """Call after device was updated."""
# pylint: disable=unused-argument
await self.async_update_ha_state() await self.async_update_ha_state()
self.device.register_device_updated_cb(after_update_callback) self.device.register_device_updated_cb(after_update_callback)
@@ -197,7 +196,6 @@ class KNXCover(CoverDevice):
@callback @callback
def auto_updater_hook(self, now): def auto_updater_hook(self, now):
"""Call for the autoupdater.""" """Call for the autoupdater."""
# pylint: disable=unused-argument
self.async_schedule_update_ha_state() self.async_schedule_update_ha_state()
if self.device.position_reached(): if self.device.position_reached():
self.stop_auto_updater() self.stop_auto_updater()

View File

@@ -17,7 +17,6 @@ _LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['lutron'] DEPENDENCIES = ['lutron']
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Lutron shades.""" """Set up the Lutron shades."""
devs = [] devs = []

View File

@@ -18,7 +18,6 @@ _LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['lutron_caseta'] DEPENDENCIES = ['lutron_caseta']
# pylint: disable=unused-argument
@asyncio.coroutine @asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None): def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Set up the Lutron Caseta shades as a cover device.""" """Set up the Lutron Caseta shades as a cover device."""

View File

@@ -13,7 +13,7 @@ from homeassistant.const import (
CONF_USERNAME, CONF_PASSWORD, CONF_TYPE, STATE_CLOSED) CONF_USERNAME, CONF_PASSWORD, CONF_TYPE, STATE_CLOSED)
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['pymyq==0.0.8'] REQUIREMENTS = ['pymyq==0.0.11']
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)

View File

@@ -54,7 +54,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
}) })
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the RPi cover platform.""" """Set up the RPi cover platform."""
relay_time = config.get(CONF_RELAY_TIME) relay_time = config.get(CONF_RELAY_TIME)

View File

@@ -19,8 +19,8 @@ _LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Vera covers.""" """Set up the Vera covers."""
add_devices( add_devices(
VeraCover(device, hass.data[VERA_CONTROLLER]) for [VeraCover(device, hass.data[VERA_CONTROLLER]) for
device in hass.data[VERA_DEVICES]['cover']) device in hass.data[VERA_DEVICES]['cover']], True)
class VeraCover(VeraDevice, CoverDevice): class VeraCover(VeraDevice, CoverDevice):

View File

@@ -1,6 +1,7 @@
{ {
"config": { "config": {
"abort": { "abort": {
"already_configured": "\u041c\u043e\u0441\u0442\u044a\u0442 \u0432\u0435\u0447\u0435 \u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d",
"no_bridges": "\u041d\u0435 \u0441\u0430 \u043e\u0442\u043a\u0440\u0438\u0442\u0438 \u043c\u043e\u0441\u0442\u043e\u0432\u0435 deCONZ", "no_bridges": "\u041d\u0435 \u0441\u0430 \u043e\u0442\u043a\u0440\u0438\u0442\u0438 \u043c\u043e\u0441\u0442\u043e\u0432\u0435 deCONZ",
"one_instance_only": "\u041a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 \u043f\u043e\u0434\u0434\u044a\u0440\u0436\u0430 \u0441\u0430\u043c\u043e \u0435\u0434\u043d\u043e \u043a\u043e\u043f\u0438\u0435 \u043d\u0430 deCONZ" "one_instance_only": "\u041a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 \u043f\u043e\u0434\u0434\u044a\u0440\u0436\u0430 \u0441\u0430\u043c\u043e \u0435\u0434\u043d\u043e \u043a\u043e\u043f\u0438\u0435 \u043d\u0430 deCONZ"
}, },

View File

@@ -0,0 +1,33 @@
{
"config": {
"abort": {
"already_configured": "L'enlla\u00e7 ja est\u00e0 configurat",
"no_bridges": "No s'han descobert enlla\u00e7os amb deCONZ",
"one_instance_only": "El component nom\u00e9s admet una inst\u00e0ncia deCONZ"
},
"error": {
"no_key": "No s'ha pogut obtenir una clau API"
},
"step": {
"init": {
"data": {
"host": "Amfitri\u00f3",
"port": "Port (predeterminat: '80')"
},
"title": "Definiu la passarel\u00b7la deCONZ"
},
"link": {
"description": "Desbloqueja la teva passarel\u00b7la d'enlla\u00e7 deCONZ per a registrar-te amb Home Assistant.\n\n1. V\u00e9s a la configuraci\u00f3 del sistema deCONZ\n2. Prem el bot\u00f3 \"Desbloquejar passarel\u00b7la\"",
"title": "Vincular amb deCONZ"
},
"options": {
"data": {
"allow_clip_sensor": "Permet la importaci\u00f3 de sensors virtuals",
"allow_deconz_groups": "Permet la importaci\u00f3 de grups deCONZ"
},
"title": "Opcions de configuraci\u00f3 addicionals per deCONZ"
}
},
"title": "deCONZ"
}
}

View File

@@ -0,0 +1,32 @@
{
"config": {
"abort": {
"already_configured": "P\u0159emost\u011bn\u00ed je ji\u017e nakonfigurov\u00e1no",
"no_bridges": "\u017d\u00e1dn\u00e9 deCONZ p\u0159emost\u011bn\u00ed nebyly nalezeny",
"one_instance_only": "Komponent podporuje pouze jednu instanci deCONZ"
},
"error": {
"no_key": "Nelze z\u00edskat kl\u00ed\u010d API"
},
"step": {
"init": {
"data": {
"host": "Hostitel",
"port": "Port (v\u00fdchoz\u00ed hodnota: '80')"
},
"title": "Definujte br\u00e1nu deCONZ"
},
"link": {
"description": "Odemkn\u011bte br\u00e1nu deCONZ, pro registraci v Home Assistant. \n\n 1. P\u0159ejd\u011bte do nastaven\u00ed syst\u00e9mu deCONZ \n 2. Stiskn\u011bte tla\u010d\u00edtko \"Unlock Gateway\"",
"title": "Propojit s deCONZ"
},
"options": {
"data": {
"allow_clip_sensor": "Povolit import virtu\u00e1ln\u00edch \u010didel"
},
"title": "Dal\u0161\u00ed mo\u017enosti konfigurace pro deCONZ"
}
},
"title": "Br\u00e1na deCONZ Zigbee"
}
}

View File

@@ -21,10 +21,11 @@
"title": "Link with deCONZ" "title": "Link with deCONZ"
}, },
"options": { "options": {
"title": "Extra configuration options for deCONZ",
"data": { "data": {
"allow_clip_sensor": "Allow importing virtual sensors" "allow_clip_sensor": "Allow importing virtual sensors",
} "allow_deconz_groups": "Allow importing deCONZ groups"
},
"title": "Extra configuration options for deCONZ"
} }
}, },
"title": "deCONZ Zigbee gateway" "title": "deCONZ Zigbee gateway"

View File

@@ -0,0 +1,32 @@
{
"config": {
"abort": {
"already_configured": "Ce pont est d\u00e9j\u00e0 configur\u00e9",
"no_bridges": "Aucun pont deCONZ n'a \u00e9t\u00e9 d\u00e9couvert",
"one_instance_only": "Le composant prend uniquement en charge une instance deCONZ"
},
"error": {
"no_key": "Impossible d'obtenir une cl\u00e9 d'API"
},
"step": {
"init": {
"data": {
"host": "H\u00f4te",
"port": "Port (valeur par d\u00e9faut : 80)"
},
"title": "Initialiser la passerelle deCONZ"
},
"link": {
"description": "D\u00e9verrouillez votre passerelle deCONZ pour vous enregistrer aupr\u00e8s de Home Assistant. \n\n 1. Acc\u00e9dez aux param\u00e8tres du syst\u00e8me deCONZ \n 2. Cliquez sur \"D\u00e9verrouiller la passerelle\"",
"title": "Lien vers deCONZ"
},
"options": {
"data": {
"allow_clip_sensor": "Autoriser l'importation de capteurs virtuels"
},
"title": "Options de configuration suppl\u00e9mentaires pour deCONZ"
}
},
"title": "Passerelle deCONZ Zigbee"
}
}

View File

@@ -1,6 +1,8 @@
{ {
"config": { "config": {
"abort": { "abort": {
"already_configured": "A bridge m\u00e1r konfigur\u00e1lva van",
"no_bridges": "Nem tal\u00e1ltam deCONZ bridget",
"one_instance_only": "Ez a komponens csak egy deCONZ egys\u00e9get t\u00e1mogat" "one_instance_only": "Ez a komponens csak egy deCONZ egys\u00e9get t\u00e1mogat"
}, },
"error": { "error": {
@@ -11,9 +13,11 @@
"data": { "data": {
"host": "H\u00e1zigazda (Host)", "host": "H\u00e1zigazda (Host)",
"port": "Port (alap\u00e9rtelmezett \u00e9rt\u00e9k: '80')" "port": "Port (alap\u00e9rtelmezett \u00e9rt\u00e9k: '80')"
} },
"title": "deCONZ \u00e1tj\u00e1r\u00f3 megad\u00e1sa"
}, },
"link": { "link": {
"description": "Oldja fel a deCONZ \u00e1tj\u00e1r\u00f3t a Home Assistant-ban val\u00f3 regisztr\u00e1l\u00e1shoz.\n\n1. Menjen a deCONZ rendszer be\u00e1ll\u00edt\u00e1sokhoz\n2. Nyomja meg az \"\u00c1tj\u00e1r\u00f3 felold\u00e1sa\" gombot",
"title": "Kapcsol\u00f3d\u00e1s a deCONZ-hoz" "title": "Kapcsol\u00f3d\u00e1s a deCONZ-hoz"
} }
}, },

View File

@@ -0,0 +1,26 @@
{
"config": {
"abort": {
"already_configured": "Il Bridge \u00e8 gi\u00e0 configurato",
"no_bridges": "Nessun bridge deCONZ rilevato",
"one_instance_only": "Il componente supporto solo un'istanza di deCONZ"
},
"error": {
"no_key": "Impossibile ottenere una API key"
},
"step": {
"init": {
"data": {
"host": "Host",
"port": "Porta (valore di default: '80')"
},
"title": "Definisci il gateway deCONZ"
},
"link": {
"description": "Sblocca il tuo gateway deCONZ per registrarlo in Home Assistant.\n\n1. Vai nelle impostazioni di sistema di deCONZ\n2. Premi il bottone \"Unlock Gateway\"",
"title": "Collega con deCONZ"
}
},
"title": "deCONZ"
}
}

View File

@@ -18,9 +18,16 @@
}, },
"link": { "link": {
"description": "deCONZ \uac8c\uc774\ud2b8\uc6e8\uc774\ub97c \uc5b8\ub77d\ud558\uc5ec Home Assistant \uc5d0 \uc5f0\uacb0\ud558\uae30\n\n1. deCONZ \uc2dc\uc2a4\ud15c \uc124\uc815\uc73c\ub85c \uc774\ub3d9\ud558\uc138\uc694\n2. \"Unlock Gateway\" \ubc84\ud2bc\uc744 \ub204\ub974\uc138\uc694 ", "description": "deCONZ \uac8c\uc774\ud2b8\uc6e8\uc774\ub97c \uc5b8\ub77d\ud558\uc5ec Home Assistant \uc5d0 \uc5f0\uacb0\ud558\uae30\n\n1. deCONZ \uc2dc\uc2a4\ud15c \uc124\uc815\uc73c\ub85c \uc774\ub3d9\ud558\uc138\uc694\n2. \"Unlock Gateway\" \ubc84\ud2bc\uc744 \ub204\ub974\uc138\uc694 ",
"title": "deCONZ \uc640 \uc5f0\uacb0" "title": "deCONZ\uc640 \uc5f0\uacb0"
},
"options": {
"data": {
"allow_clip_sensor": "\uac00\uc0c1 \uc13c\uc11c \uac00\uc838\uc624\uae30 \ud5c8\uc6a9",
"allow_deconz_groups": "deCONZ \ub0b4\uc6a9 \uac00\uc838\uc624\uae30 \ud5c8\uc6a9"
},
"title": "deCONZ\ub97c \uc704\ud55c \ucd94\uac00 \uad6c\uc131 \uc635\uc158"
} }
}, },
"title": "deCONZ" "title": "deCONZ Zigbee \uac8c\uc774\ud2b8\uc6e8\uc774"
} }
} }

View File

@@ -19,6 +19,12 @@
"link": { "link": {
"description": "Entsperrt \u00e4r deCONZ gateway fir se mat Home Assistant ze registr\u00e9ieren.\n\n1. Gidd op\u00a0deCONZ System Astellungen\n2. Dr\u00e9ckt \"Unlock\" Gateway Kn\u00e4ppchen", "description": "Entsperrt \u00e4r deCONZ gateway fir se mat Home Assistant ze registr\u00e9ieren.\n\n1. Gidd op\u00a0deCONZ System Astellungen\n2. Dr\u00e9ckt \"Unlock\" Gateway Kn\u00e4ppchen",
"title": "Link mat deCONZ" "title": "Link mat deCONZ"
},
"options": {
"data": {
"allow_clip_sensor": "Erlaabt den Import vun virtuellen Sensoren"
},
"title": "Extra Konfiguratiouns Optiounen fir deCONZ"
} }
}, },
"title": "deCONZ" "title": "deCONZ"

View File

@@ -19,6 +19,13 @@
"link": { "link": {
"description": "L\u00e5s opp deCONZ-gatewayen din for \u00e5 registrere deg med Home Assistant. \n\n 1. G\u00e5 til deCONZ-systeminnstillinger \n 2. Trykk p\u00e5 \"L\u00e5s opp gateway\" knappen", "description": "L\u00e5s opp deCONZ-gatewayen din for \u00e5 registrere deg med Home Assistant. \n\n 1. G\u00e5 til deCONZ-systeminnstillinger \n 2. Trykk p\u00e5 \"L\u00e5s opp gateway\" knappen",
"title": "Koble til deCONZ" "title": "Koble til deCONZ"
},
"options": {
"data": {
"allow_clip_sensor": "Tillat import av virtuelle sensorer",
"allow_deconz_groups": "Tillat import av deCONZ grupper"
},
"title": "Ekstra konfigurasjonsalternativer for deCONZ"
} }
}, },
"title": "deCONZ" "title": "deCONZ"

View File

@@ -19,6 +19,12 @@
"link": { "link": {
"description": "Odblokuj bramk\u0119 deCONZ, aby zarejestrowa\u0107 j\u0105 w Home Assistant. \n\n 1. Przejd\u017a do ustawie\u0144 systemu deCONZ \n 2. Naci\u015bnij przycisk \"Odblokuj bramk\u0119\"", "description": "Odblokuj bramk\u0119 deCONZ, aby zarejestrowa\u0107 j\u0105 w Home Assistant. \n\n 1. Przejd\u017a do ustawie\u0144 systemu deCONZ \n 2. Naci\u015bnij przycisk \"Odblokuj bramk\u0119\"",
"title": "Po\u0142\u0105cz z deCONZ" "title": "Po\u0142\u0105cz z deCONZ"
},
"options": {
"data": {
"allow_clip_sensor": "Zezwalaj na importowanie wirtualnych sensor\u00f3w"
},
"title": "Dodatkowe opcje konfiguracji dla deCONZ"
} }
}, },
"title": "deCONZ" "title": "deCONZ"

View File

@@ -0,0 +1,32 @@
{
"config": {
"abort": {
"already_configured": "A ponte j\u00e1 est\u00e1 configurada",
"no_bridges": "N\u00e3o h\u00e1 pontes de deCONZ descobertas",
"one_instance_only": "Componente suporta apenas uma inst\u00e2ncia deCONZ"
},
"error": {
"no_key": "N\u00e3o foi poss\u00edvel obter uma chave de API"
},
"step": {
"init": {
"data": {
"host": "Hospedeiro",
"port": "Porta (valor padr\u00e3o: '80')"
},
"title": "Defina o gateway deCONZ"
},
"link": {
"description": "Desbloqueie o seu gateway deCONZ para se registar no Home Assistant. \n\n 1. V\u00e1 para as configura\u00e7\u00f5es do sistema deCONZ \n 2. Pressione o bot\u00e3o \"Desbloquear Gateway\"",
"title": "Linkar com deCONZ"
},
"options": {
"data": {
"allow_clip_sensor": "Permitir a importa\u00e7\u00e3o de sensores virtuais"
},
"title": "Op\u00e7\u00f5es extras de configura\u00e7\u00e3o para deCONZ"
}
},
"title": "Gateway deCONZ Zigbee"
}
}

View File

@@ -1,7 +1,32 @@
{ {
"config": { "config": {
"abort": { "abort": {
"already_configured": "Bridge j\u00e1 est\u00e1 configurada" "already_configured": "Bridge j\u00e1 est\u00e1 configurada",
"no_bridges": "Nenhum deCONZ descoberto",
"one_instance_only": "Componente suporta apenas uma conex\u00e3o deCONZ"
},
"error": {
"no_key": "N\u00e3o foi poss\u00edvel obter uma chave de API"
},
"step": {
"init": {
"data": {
"host": "Servidor",
"port": "Porta (por omiss\u00e3o: '80')"
},
"title": "Defina o gateway deCONZ"
},
"link": {
"description": "Desbloqueie o seu gateway deCONZ para se registar no Home Assistant. \n\n 1. V\u00e1 para as configura\u00e7\u00f5es do sistema deCONZ \n 2. Pressione o bot\u00e3o \"Desbloquear Gateway\"",
"title": "Link com deCONZ"
},
"options": {
"data": {
"allow_clip_sensor": "Permitir a importa\u00e7\u00e3o de sensores virtuais"
},
"title": "Op\u00e7\u00f5es extra de configura\u00e7\u00e3o para deCONZ"
} }
},
"title": "deCONZ"
} }
} }

View File

@@ -19,6 +19,13 @@
"link": { "link": {
"description": "\u0420\u0430\u0437\u0431\u043b\u043e\u043a\u0438\u0440\u0443\u0439\u0442\u0435 \u0448\u043b\u044e\u0437 deCONZ \u0434\u043b\u044f \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u0438 \u0432 Home Assistant:\n\n1. \u041f\u0435\u0440\u0435\u0439\u0434\u0438\u0442\u0435 \u043a \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430\u043c \u0441\u0438\u0441\u0442\u0435\u043c\u044b deCONZ\n2. \u041d\u0430\u0436\u043c\u0438\u0442\u0435 \u043a\u043d\u043e\u043f\u043a\u0443 \u00ab\u0420\u0430\u0437\u0431\u043b\u043e\u043a\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0448\u043b\u044e\u0437\u00bb", "description": "\u0420\u0430\u0437\u0431\u043b\u043e\u043a\u0438\u0440\u0443\u0439\u0442\u0435 \u0448\u043b\u044e\u0437 deCONZ \u0434\u043b\u044f \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u0438 \u0432 Home Assistant:\n\n1. \u041f\u0435\u0440\u0435\u0439\u0434\u0438\u0442\u0435 \u043a \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430\u043c \u0441\u0438\u0441\u0442\u0435\u043c\u044b deCONZ\n2. \u041d\u0430\u0436\u043c\u0438\u0442\u0435 \u043a\u043d\u043e\u043f\u043a\u0443 \u00ab\u0420\u0430\u0437\u0431\u043b\u043e\u043a\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0448\u043b\u044e\u0437\u00bb",
"title": "\u0421\u0432\u044f\u0437\u044c \u0441 deCONZ" "title": "\u0421\u0432\u044f\u0437\u044c \u0441 deCONZ"
},
"options": {
"data": {
"allow_clip_sensor": "\u0420\u0430\u0437\u0440\u0435\u0448\u0438\u0442\u044c \u0438\u043c\u043f\u043e\u0440\u0442 \u0432\u0438\u0440\u0442\u0443\u0430\u043b\u044c\u043d\u044b\u0445 \u0434\u0430\u0442\u0447\u0438\u043a\u043e\u0432",
"allow_deconz_groups": "\u0420\u0430\u0437\u0440\u0435\u0448\u0438\u0442\u044c \u0438\u043c\u043f\u043e\u0440\u0442 \u0433\u0440\u0443\u043f\u043f deCONZ"
},
"title": "\u0414\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438 \u0434\u043b\u044f deCONZ"
} }
}, },
"title": "deCONZ" "title": "deCONZ"

View File

@@ -19,6 +19,12 @@
"link": { "link": {
"description": "Odklenite va\u0161 deCONZ gateway za registracijo z Home Assistant-om. \n1. Pojdite v deCONT sistemske nastavitve\n2. Pritisnite tipko \"odkleni prehod\"", "description": "Odklenite va\u0161 deCONZ gateway za registracijo z Home Assistant-om. \n1. Pojdite v deCONT sistemske nastavitve\n2. Pritisnite tipko \"odkleni prehod\"",
"title": "Povezava z deCONZ" "title": "Povezava z deCONZ"
},
"options": {
"data": {
"allow_clip_sensor": "Dovoli uvoz virtualnih senzorjev"
},
"title": "Dodatne mo\u017enosti konfiguracije za deCONZ"
} }
}, },
"title": "deCONZ" "title": "deCONZ"

View File

@@ -0,0 +1,33 @@
{
"config": {
"abort": {
"already_configured": "Bryggan \u00e4r redan konfigurerad",
"no_bridges": "Inga deCONZ-bryggor uppt\u00e4cktes",
"one_instance_only": "Komponenten st\u00f6djer endast en deCONZ-instans"
},
"error": {
"no_key": "Det gick inte att ta emot en API-nyckel"
},
"step": {
"init": {
"data": {
"host": "V\u00e4rd",
"port": "Port (standardv\u00e4rde: '80')"
},
"title": "Definiera deCONZ-gatewaye"
},
"link": {
"description": "L\u00e5s upp din deCONZ-gateway f\u00f6r att registrera dig med Home Assistant. \n\n 1. G\u00e5 till deCONZ-systeminst\u00e4llningarna \n 2. Tryck p\u00e5 \"L\u00e5s upp gateway\"-knappen",
"title": "L\u00e4nka med deCONZ"
},
"options": {
"data": {
"allow_clip_sensor": "Till\u00e5t import av virtuella sensorer",
"allow_deconz_groups": "Till\u00e5t import av deCONZ-grupper"
},
"title": "Extra konfigurationsalternativ f\u00f6r deCONZ"
}
},
"title": "deCONZ"
}
}

View File

@@ -0,0 +1,26 @@
{
"config": {
"abort": {
"already_configured": "C\u1ea7u \u0111\u00e3 \u0111\u01b0\u1ee3c c\u1ea5u h\u00ecnh",
"no_bridges": "Kh\u00f4ng t\u00ecm th\u1ea5y c\u1ea7u deCONZ n\u00e0o",
"one_instance_only": "Th\u00e0nh ph\u1ea7n ch\u1ec9 h\u1ed7 tr\u1ee3 m\u1ed9t c\u00e1 th\u1ec3 deCONZ"
},
"error": {
"no_key": "Kh\u00f4ng th\u1ec3 l\u1ea5y kh\u00f3a API"
},
"step": {
"init": {
"data": {
"port": "C\u1ed5ng (gi\u00e1 tr\u1ecb m\u1eb7c \u0111\u1ecbnh: '80')"
}
},
"options": {
"data": {
"allow_clip_sensor": "Cho ph\u00e9p nh\u1eadp c\u1ea3m bi\u1ebfn \u1ea3o",
"allow_deconz_groups": "Cho ph\u00e9p nh\u1eadp c\u00e1c nh\u00f3m deCONZ"
},
"title": "T\u00f9y ch\u1ecdn c\u1ea5u h\u00ecnh b\u1ed5 sung cho deCONZ"
}
}
}
}

Some files were not shown because too many files have changed in this diff Show More