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

View File

@@ -1,5 +1,4 @@
"""Provide methods to bootstrap a Home Assistant instance."""
import asyncio
import logging
import logging.handlers
import os
@@ -17,7 +16,7 @@ from homeassistant.components import persistent_notification
from homeassistant.const import EVENT_HOMEASSISTANT_CLOSE
from homeassistant.setup import async_setup_component
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.exceptions import HomeAssistantError
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:
config_dir = os.path.abspath(config_dir)
hass.config.config_dir = config_dir
if not is_virtual_env():
hass.loop.run_until_complete(
async_mount_local_lib_path(config_dir, hass.loop))
async_mount_local_lib_path(config_dir))
# run task
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
config_dir = os.path.abspath(os.path.dirname(config_path))
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,
log_no_color)
@@ -211,9 +213,8 @@ async def async_from_config_file(config_path: str,
finally:
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)
return hass
@core.callback
@@ -308,23 +309,13 @@ def async_enable_logging(hass: core.HomeAssistant,
"Unable to setup error log %s (access denied)", err_log_path)
def 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:
async def async_mount_local_lib_path(config_dir: str) -> str:
"""Add local library to Python Path.
This function is a coroutine.
"""
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:
sys.path.insert(0, lib_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
https://home-assistant.io/components/alarm_control_panel.arlo/
"""
import asyncio
import logging
import voluptuous as vol
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 (
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 (
ATTR_ATTRIBUTION, STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME,
STATE_ALARM_DISARMED)
@@ -36,21 +38,20 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
def setup_platform(hass, config, add_devices, discovery_info=None):
"""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
home_mode_name = config.get(CONF_HOME_MODE_NAME)
away_mode_name = config.get(CONF_AWAY_MODE_NAME)
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,
away_mode_name))
async_add_devices(base_stations, True)
add_devices(base_stations, True)
class ArloBaseStation(AlarmControlPanel):
@@ -68,6 +69,16 @@ class ArloBaseStation(AlarmControlPanel):
"""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
def state(self):
"""Return the state of the device."""
@@ -75,30 +86,22 @@ class ArloBaseStation(AlarmControlPanel):
def update(self):
"""Update the state of the device."""
# PyArlo sometimes returns None for mode. So retry 3 times before
# returning None.
num_retries = 3
i = 0
while i < num_retries:
_LOGGER.debug("Updating Arlo Alarm Control Panel %s", self.name)
mode = self._base_station.mode
if mode:
self._state = self._get_state_from_mode(mode)
return
i += 1
else:
self._state = None
@asyncio.coroutine
def async_alarm_disarm(self, code=None):
async def async_alarm_disarm(self, code=None):
"""Send disarm command."""
self._base_station.mode = DISARMED
@asyncio.coroutine
def async_alarm_arm_away(self, code=None):
async def async_alarm_arm_away(self, code=None):
"""Send arm away command. Uses custom mode."""
self._base_station.mode = self._away_mode_name
@asyncio.coroutine
def async_alarm_arm_home(self, code=None):
async def async_alarm_arm_home(self, code=None):
"""Send arm home command. Uses custom mode."""
self._base_station.mode = self._home_mode_name
@@ -125,4 +128,4 @@ class ArloBaseStation(AlarmControlPanel):
return STATE_ALARM_ARMED_HOME
elif mode == self._away_mode_name:
return STATE_ALARM_ARMED_AWAY
return None
return mode

View File

@@ -18,7 +18,7 @@ from homeassistant.const import (
from homeassistant.helpers import discovery
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['amcrest==1.2.2']
REQUIREMENTS = ['amcrest==1.2.3']
DEPENDENCIES = ['ffmpeg']
_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/
"""
import logging
from datetime import timedelta
import voluptuous as vol
from requests.exceptions import HTTPError, ConnectTimeout
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__)
@@ -25,10 +29,16 @@ DOMAIN = 'arlo'
NOTIFICATION_ID = 'arlo_notification'
NOTIFICATION_TITLE = 'Arlo Component Setup'
SCAN_INTERVAL = timedelta(seconds=60)
SIGNAL_UPDATE_ARLO = "arlo_update"
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
vol.Optional(CONF_SCAN_INTERVAL, default=SCAN_INTERVAL):
cv.time_period,
}),
}, extra=vol.ALLOW_EXTRA)
@@ -38,6 +48,7 @@ def setup(hass, config):
conf = config[DOMAIN]
username = conf.get(CONF_USERNAME)
password = conf.get(CONF_PASSWORD)
scan_interval = conf.get(CONF_SCAN_INTERVAL)
try:
from pyarlo import PyArlo
@@ -45,7 +56,17 @@ def setup(hass, config):
arlo = PyArlo(username, password, preload=False)
if not arlo.is_connected:
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
except (ConnectTimeout, HTTPError) as ex:
_LOGGER.error("Unable to connect to Netgear Arlo: %s", str(ex))
hass.components.persistent_notification.create(
@@ -55,4 +76,17 @@ def setup(hass, config):
title=NOTIFICATION_TITLE,
notification_id=NOTIFICATION_ID)
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

View File

@@ -145,7 +145,7 @@ def request_configuration(hass, config, name, host, serialnumber):
def setup(hass, config):
"""Set up for Axis devices."""
def _shutdown(call): # pylint: disable=unused-argument
def _shutdown(call):
"""Stop the event stream on shutdown."""
for serialnumber, device in AXIS_DEVICES.items():
_LOGGER.info("Stopping event stream for %s.", serialnumber)
@@ -272,8 +272,7 @@ class AxisDeviceEvent(Entity):
def _update_callback(self):
"""Update the sensor's state, if needed."""
self.update()
self.schedule_update_ha_state()
self.schedule_update_ha_state(True)
@property
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):
"""Set up the Command line Binary Sensor."""
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/
"""
import logging
import asyncio
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.components.eight_sleep import (
@@ -16,8 +15,8 @@ _LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['eight_sleep']
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
async def async_setup_platform(hass, config, async_add_devices,
discovery_info=None):
"""Set up the eight sleep binary sensor."""
if discovery_info is None:
return
@@ -63,7 +62,6 @@ class EightHeatSensor(EightSleepHeatEntity, BinarySensorDevice):
"""Return true if the binary sensor is on."""
return self._state
@asyncio.coroutine
def async_update(self):
async def async_update(self):
"""Retrieve latest state."""
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):
"""Set up the GC100 devices."""
binary_sensors = []

View File

@@ -28,7 +28,6 @@ ISY_DEVICE_TYPES = {
}
# pylint: disable=unused-argument
def setup_platform(hass, config: ConfigType,
add_devices: Callable[[list], None], discovery_info=None):
"""Set up the ISY994 binary sensor platform."""
@@ -299,7 +298,6 @@ class ISYBinarySensorHeartbeat(ISYDevice, BinarySensorDevice):
# No heartbeat timer is active
pass
# pylint: disable=unused-argument
@callback
def timer_elapsed(now) -> None:
"""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.hass, timer_elapsed, point_in_time)
# pylint: disable=unused-argument
def on_update(self, event: object) -> None:
"""Ignore node status updates.

View File

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

View File

@@ -52,19 +52,18 @@ class LinodeBinarySensor(BinarySensorDevice):
self._node_id = node_id
self._state = None
self.data = None
self._attrs = {}
self._name = None
@property
def name(self):
"""Return the name of the sensor."""
if self.data is not None:
return self.data.label
return self._name
@property
def is_on(self):
"""Return true if the binary sensor is on."""
if self.data is not None:
return self.data.status == 'running'
return False
return self._state
@property
def device_class(self):
@@ -74,8 +73,18 @@ class LinodeBinarySensor(BinarySensorDevice):
@property
def device_state_attributes(self):
"""Return the state attributes of the Linode Node."""
if self.data:
return {
return self._attrs
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_NODE_ID: self.data.id,
ATTR_NODE_NAME: self.data.label,
@@ -85,12 +94,4 @@ class LinodeBinarySensor(BinarySensorDevice):
ATTR_REGION: self.data.region.country,
ATTR_VCPUS: self.data.specs.vcpus,
}
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
self._name = self.data.label

View File

@@ -6,6 +6,7 @@ https://home-assistant.io/components/binary_sensor.mqtt/
"""
import asyncio
import logging
from typing import Optional
import voluptuous as vol
@@ -24,7 +25,7 @@ import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = 'MQTT Binary sensor'
CONF_UNIQUE_ID = 'unique_id'
DEFAULT_PAYLOAD_OFF = 'OFF'
DEFAULT_PAYLOAD_ON = 'ON'
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_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA,
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)
@@ -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_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,
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."""
super().__init__(availability_topic, qos, payload_available,
payload_not_available)
@@ -83,6 +89,7 @@ class MqttBinarySensor(MqttAvailability, BinarySensorDevice):
self._qos = qos
self._force_update = force_update
self._template = value_template
self._unique_id = unique_id
@asyncio.coroutine
def async_added_to_hass(self):
@@ -134,3 +141,8 @@ class MqttBinarySensor(MqttAvailability, BinarySensorDevice):
def force_update(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'
name = 'api:mystrom'
supported_actions = ['single', 'double', 'long', 'touch']
def __init__(self, add_devices):
"""Initialize the myStrom URL endpoint."""
@@ -44,16 +45,18 @@ class MyStromView(HomeAssistantView):
@asyncio.coroutine
def _handle(self, hass, data):
"""Handle requests to the myStrom endpoint."""
button_action = list(data.keys())[0]
button_id = data[button_action]
entity_id = '{}.{}_{}'.format(DOMAIN, button_id, button_action)
button_action = next((
parameter for parameter in data
if parameter in self.supported_actions), None)
if button_action not in ['single', 'double', 'long', 'touch']:
if button_action is None:
_LOGGER.error(
"Received unidentified message from myStrom button: %s", data)
return ("Received unidentified message: {}".format(data),
HTTP_UNPROCESSABLE_ENTITY)
button_id = data[button_action]
entity_id = '{}.{}_{}'.format(DOMAIN, button_id, button_action)
if entity_id not in self.buttons:
_LOGGER.info("New myStrom button/action detected: %s/%s",
button_id, button_action)

View File

@@ -8,7 +8,8 @@ from itertools import chain
import logging
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
DEPENDENCIES = ['nest']
@@ -56,12 +57,19 @@ _LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Nest binary sensors."""
if discovery_info is None:
return
"""Set up the Nest binary sensors.
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]
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
if discovery_info == {}:
conditions = _VALID_BINARY_SENSOR_TYPES
@@ -76,6 +84,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
"for valid options.")
_LOGGER.error(wstr)
def get_binary_sensors():
"""Get the Nest binary sensors."""
sensors = []
for structure in nest.structures():
sensors += [NestBinarySensor(structure, None, variable)
@@ -101,7 +111,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
sensors += [NestActivityZoneSensor(structure,
device,
activity_zone)]
add_devices(sensors, True)
return sensors
async_add_devices(await hass.async_add_job(get_binary_sensors), True)
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):
"""Set up the access to Netatmo binary sensor."""
netatmo = hass.components.netatmo
@@ -68,12 +67,12 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
module_name = None
import lnetatmo
import pyatmo
try:
data = CameraData(netatmo.NETATMO_AUTH, home)
if not data.get_camera_names():
return None
except lnetatmo.NoDevice:
except pyatmo.NoDevice:
return None
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):
"""Set up the available OctoPrint binary sensors."""
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):
"""Set up Pilight Binary Sensor."""
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.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_RAINDELAY, TYPE_RAINSENSOR, TYPE_WEEKDAY, RainMachineEntity)
from homeassistant.const import CONF_MONITORED_CONDITIONS
@@ -20,7 +20,8 @@ DEPENDENCIES = ['rainmachine']
_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."""
if discovery_info is None:
return
@@ -33,7 +34,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
binary_sensors.append(
RainMachineBinarySensor(rainmachine, sensor_type, name, icon))
add_devices(binary_sensors, True)
async_add_devices(binary_sensors, True)
class RainMachineBinarySensor(RainMachineEntity, BinarySensorDevice):
@@ -70,16 +71,16 @@ class RainMachineBinarySensor(RainMachineEntity, BinarySensorDevice):
self.rainmachine.device_mac.replace(':', ''), self._sensor_type)
@callback
def update_data(self):
def _update_data(self):
"""Update the state."""
self.async_schedule_update_ha_state(True)
async def async_added_to_hass(self):
"""Register callbacks."""
async_dispatcher_connect(self.hass, DATA_UPDATE_TOPIC,
self.update_data)
async_dispatcher_connect(
self.hass, SENSOR_UPDATE_TOPIC, self._update_data)
def update(self):
async def async_update(self):
"""Update the state."""
if self._sensor_type == TYPE_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):
"""Set up the raspihats binary_sensor devices."""
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):
"""Set up the Raspberry PI GPIO devices."""
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._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):
"""Set up the trend 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):
"""Perform the setup for Vera controller devices."""
add_devices(
VeraBinarySensor(device, hass.data[VERA_CONTROLLER])
for device in hass.data[VERA_DEVICES]['binary_sensor'])
[VeraBinarySensor(device, hass.data[VERA_CONTROLLER])
for device in hass.data[VERA_DEVICES]['binary_sensor']], True)
class VeraBinarySensor(VeraDevice, BinarySensorDevice):

View File

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

View File

@@ -13,7 +13,7 @@ DEPENDENCIES = ['wemo']
_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):
"""Register discovered WeMo binary sensors."""
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']:
devices.append(XiaomiMotionSensor(device, hass, gateway))
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':
devices.append(XiaomiWaterLeakSensor(device, gateway))
elif model in ['smoke', 'sensor_smoke']:
@@ -43,10 +47,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
data_key = 'channel_0'
devices.append(XiaomiButton(device, 'Switch', data_key,
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',
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)',
'channel_0', hass, gateway))
devices.append(XiaomiButton(device, 'Wall Switch (Right)',
@@ -190,11 +194,11 @@ class XiaomiMotionSensor(XiaomiBinarySensor):
class XiaomiDoorSensor(XiaomiBinarySensor):
"""Representation of a XiaomiDoorSensor."""
def __init__(self, device, xiaomi_hub):
def __init__(self, device, data_key, xiaomi_hub):
"""Initialize the XiaomiDoorSensor."""
self._open_since = 0
XiaomiBinarySensor.__init__(self, device, 'Door Window Sensor',
xiaomi_hub, 'status', 'opening')
xiaomi_hub, data_key, 'opening')
@property
def device_state_attributes(self):
@@ -330,7 +334,7 @@ class XiaomiButton(XiaomiBinarySensor):
click_type = 'both'
elif value == 'shake':
click_type = 'shake'
elif value == 'long_click':
elif value in ['long_click', 'long_both_click']:
return False
else:
_LOGGER.warning("Unsupported click_type detected: %s", value)

View File

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

View File

@@ -34,7 +34,6 @@ CONFIG_SCHEMA = vol.Schema({
}, extra=vol.ALLOW_EXTRA)
# pylint: disable=unused-argument
def setup(hass, config):
"""Set up the BloomSky component."""
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
https://home-assistant.io/components/calendar/
"""
import asyncio
import logging
from datetime import timedelta
import re
from aiohttp import web
from homeassistant.components.google import (
CONF_OFFSET, CONF_DEVICE_ID, CONF_NAME)
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.template import DATE_STR_FORMAT
from homeassistant.util import dt
from homeassistant.components import http
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'calendar'
DEPENDENCIES = ['http']
ENTITY_ID_FORMAT = DOMAIN + '.{}'
SCAN_INTERVAL = timedelta(seconds=60)
@asyncio.coroutine
def async_setup(hass, config):
async def async_setup(hass, config):
"""Track states and offer events for calendars."""
component = EntityComponent(
_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
@@ -42,7 +52,14 @@ DEFAULT_CONF_TRACK_NEW = True
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):
"""A calendar event device."""
@@ -50,7 +67,6 @@ class CalendarEventDevice(Entity):
# with an update() method
data = None
# pylint: disable=too-many-arguments
def __init__(self, hass, data):
"""Create the Calendar Event Device."""
self._name = data.get(CONF_NAME)
@@ -144,15 +160,8 @@ class CalendarEventDevice(Entity):
self.cleanup()
return
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']))
start = _get_date(self.data.event['start'])
end = _get_date(self.data.event['end'])
start = get_date(self.data.event['start'])
end = get_date(self.data.event['end'])
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
self._cal_data['message'] = re.sub(' +', '', summary).strip()
self._cal_data['offset_time'] = offset_time
self._cal_data['location'] = self.data.event.get('location', '')
self._cal_data['description'] = self.data.event.get('description', '')
self._cal_data['start'] = start
self._cal_data['end'] = end
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
from homeassistant.components.calendar import (
PLATFORM_SCHEMA, CalendarEventDevice)
PLATFORM_SCHEMA, CalendarEventDevice, get_date)
from homeassistant.const import (
CONF_NAME, CONF_PASSWORD, CONF_URL, CONF_USERNAME)
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):
device_data = {
CONF_NAME: calendar.name,
CONF_DEVICE_ID: calendar.name
CONF_DEVICE_ID: calendar.name,
}
calendar_devices.append(
WebDavCalendarEventDevice(hass, device_data, calendar)
@@ -120,6 +120,10 @@ class WebDavCalendarEventDevice(CalendarEventDevice):
attributes = super().device_state_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 to utilize the calendar dav client object to get next event."""
@@ -131,6 +135,33 @@ class WebDavCalendarData(object):
self.search = search
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)
def update(self):
"""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
https://home-assistant.io/components/demo/
"""
import copy
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
@@ -15,13 +17,13 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
calendar_data_current = DemoGoogleCalendarDataCurrent()
add_devices([
DemoGoogleCalendar(hass, calendar_data_future, {
CONF_NAME: 'Future Event',
CONF_DEVICE_ID: 'future_event',
CONF_NAME: 'Calendar 1',
CONF_DEVICE_ID: 'calendar_1',
}),
DemoGoogleCalendar(hass, calendar_data_current, {
CONF_NAME: 'Current Event',
CONF_DEVICE_ID: 'current_event',
CONF_NAME: 'Calendar 2',
CONF_DEVICE_ID: 'calendar_2',
}),
])
@@ -29,11 +31,21 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
class DemoGoogleCalendarData(object):
"""Representation of a Demo Calendar element."""
event = {}
# pylint: disable=no-self-use
def update(self):
"""Return true so entity knows we have new data."""
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):
"""Representation of a Demo Calendar for a future event."""
@@ -80,3 +92,7 @@ class DemoGoogleCalendar(CalendarEventDevice):
"""Initialize Google Calendar but without the API calls."""
self.data = calendar_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)
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 to utilize calendar service object to get next event."""
@@ -64,9 +68,7 @@ class GoogleCalendarData(object):
self.ignore_availability = ignore_availability
self.event = None
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def update(self):
"""Get the latest data."""
def _prepare_query(self):
from httplib2 import ServerNotFoundError
try:
@@ -74,13 +76,41 @@ class GoogleCalendarData(object):
except ServerNotFoundError:
_LOGGER.warning("Unable to connect to Google, using cached data")
return False
params = dict(DEFAULT_GOOGLE_SEARCH_PARAMS)
params['timeMin'] = dt.now().isoformat('T')
params['calendarId'] = self.calendar_id
if 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
result = events.list(**params).execute()

View File

@@ -257,6 +257,10 @@ class TodoistProjectDevice(CalendarEventDevice):
super().cleanup()
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
def device_state_attributes(self):
"""Return the device state attributes."""
@@ -485,6 +489,31 @@ class TodoistProjectData(object):
continue
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)
def update(self):
"""Get the latest data."""

View File

@@ -1,4 +1,3 @@
# pylint: disable=too-many-lines
"""
Component to interface with cameras.
@@ -97,6 +96,7 @@ def disable_motion_detection(hass, entity_id=None):
@bind_hass
@callback
def async_snapshot(hass, filename, entity_id=None):
"""Make a snapshot from a camera."""
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')
@asyncio.coroutine
def async_setup(hass, config):
async def async_setup(hass, config):
"""Set up the camera component."""
component = hass.data[DOMAIN] = \
EntityComponent(_LOGGER, DOMAIN, hass, SCAN_INTERVAL)
@@ -142,7 +141,7 @@ def async_setup(hass, config):
SCHEMA_WS_CAMERA_THUMBNAIL
)
yield from component.async_setup(config)
await component.async_setup(config)
@callback
def update_tokens(time):
@@ -154,27 +153,25 @@ def async_setup(hass, config):
hass.helpers.event.async_track_time_interval(
update_tokens, TOKEN_CHANGE_INTERVAL)
@asyncio.coroutine
def async_handle_camera_service(service):
async def async_handle_camera_service(service):
"""Handle calls to the camera services."""
target_cameras = component.async_extract_from_service(service)
update_tasks = []
for camera in target_cameras:
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:
yield from camera.async_disable_motion_detection()
await camera.async_disable_motion_detection()
if not camera.should_poll:
continue
update_tasks.append(camera.async_update_ha_state(True))
if update_tasks:
yield from asyncio.wait(update_tasks, loop=hass.loop)
await asyncio.wait(update_tasks, loop=hass.loop)
@asyncio.coroutine
def async_handle_snapshot_service(service):
async def async_handle_snapshot_service(service):
"""Handle snapshot services calls."""
target_cameras = component.async_extract_from_service(service)
filename = service.data[ATTR_FILENAME]
@@ -190,7 +187,7 @@ def async_setup(hass, config):
"Can't write %s, no access to path!", snapshot_file)
continue
image = yield from camera.async_camera_image()
image = await camera.async_camera_image()
def _write_image(to_file, image_data):
"""Executor helper to write image."""
@@ -198,7 +195,7 @@ def async_setup(hass, config):
img_file.write(image_data)
try:
yield from hass.async_add_job(
await hass.async_add_job(
_write_image, snapshot_file, image)
except OSError as err:
_LOGGER.error("Can't write image to file: %s", err)
@@ -216,6 +213,16 @@ def async_setup(hass, config):
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):
"""The base class for camera entities."""
@@ -265,6 +272,7 @@ class Camera(Entity):
"""Return bytes of camera image."""
raise NotImplementedError()
@callback
def async_camera_image(self):
"""Return bytes of camera image.
@@ -388,8 +396,7 @@ class CameraView(HomeAssistantView):
"""Initialize a basic camera view."""
self.component = component
@asyncio.coroutine
def get(self, request, entity_id):
async def get(self, request, entity_id):
"""Start a GET request."""
camera = self.component.get_entity(entity_id)
@@ -403,11 +410,10 @@ class CameraView(HomeAssistantView):
if not authenticated:
return web.Response(status=401)
response = yield from self.handle(request, camera)
response = await self.handle(request, camera)
return response
@asyncio.coroutine
def handle(self, request, camera):
async def handle(self, request, camera):
"""Handle the camera request."""
raise NotImplementedError()
@@ -418,12 +424,11 @@ class CameraImageView(CameraView):
url = '/api/camera_proxy/{entity_id}'
name = 'api:camera:image'
@asyncio.coroutine
def handle(self, request, camera):
async def handle(self, request, camera):
"""Serve camera image."""
with suppress(asyncio.CancelledError, asyncio.TimeoutError):
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:
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
https://home-assistant.io/components/camera.arlo/
"""
import asyncio
import logging
from datetime import timedelta
import voluptuous as vol
from homeassistant.core import callback
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.ffmpeg import DATA_FFMPEG
from homeassistant.const import ATTR_BATTERY_LEVEL
from homeassistant.helpers.aiohttp_client import async_aiohttp_proxy_stream
from homeassistant.helpers.dispatcher import async_dispatcher_connect
_LOGGER = logging.getLogger(__name__)
SCAN_INTERVAL = timedelta(seconds=90)
ARLO_MODE_ARMED = 'armed'
ARLO_MODE_DISARMED = 'disarmed'
@@ -44,22 +43,19 @@ POWERSAVE_MODE_MAPPING = {
}
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_FFMPEG_ARGUMENTS):
cv.string,
vol.Optional(CONF_FFMPEG_ARGUMENTS): cv.string,
})
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up an Arlo IP Camera."""
arlo = hass.data.get(DATA_ARLO)
if not arlo:
return False
arlo = hass.data[DATA_ARLO]
cameras = []
for camera in arlo.cameras:
cameras.append(ArloCam(hass, camera, config))
add_devices(cameras, True)
add_devices(cameras)
class ArloCam(Camera):
@@ -74,31 +70,41 @@ class ArloCam(Camera):
self._ffmpeg = hass.data[DATA_FFMPEG]
self._ffmpeg_arguments = device_info.get(CONF_FFMPEG_ARGUMENTS)
self._last_refresh = None
if self._camera.base_station:
self._camera.base_station.refresh_rate = \
SCAN_INTERVAL.total_seconds()
self.attrs = {}
def camera_image(self):
"""Return a still image response from the camera."""
return self._camera.last_image
return self._camera.last_image_from_cache
@asyncio.coroutine
def handle_async_mjpeg_stream(self, request):
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()
async def handle_async_mjpeg_stream(self, request):
"""Generate an HTTP MJPEG stream from the camera."""
from haffmpeg import CameraMjpeg
video = self._camera.last_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
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)
yield from async_aiohttp_proxy_stream(
await async_aiohttp_proxy_stream(
self.hass, request, stream,
'multipart/x-mixed-replace;boundary=ffserver')
yield from stream.close()
await stream.close()
@property
def name(self):
@@ -132,11 +138,6 @@ class ArloCam(Camera):
"""Return the camera brand."""
return DEFAULT_BRAND
@property
def should_poll(self):
"""Camera should poll periodically."""
return True
@property
def motion_detection_enabled(self):
"""Return the camera motion detection status."""
@@ -164,7 +165,3 @@ class ArloCam(Camera):
"""Disable the motion detection in base station (Disarm)."""
self._motion_status = False
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']
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up access to BloomSky cameras."""
bloomsky = hass.components.bloomsky

View File

@@ -12,9 +12,10 @@ from homeassistant.components.camera import Camera
_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."""
add_devices([
async_add_devices([
DemoCamera(hass, config, 'Demo camera')
])

View File

@@ -17,9 +17,9 @@ from homeassistant.helpers.aiohttp_client import async_get_clientsession
DEPENDENCIES = ['doorbird']
_CAMERA_LAST_VISITOR = "DoorBird Last Ring"
_CAMERA_LAST_MOTION = "DoorBird Last Motion"
_CAMERA_LIVE = "DoorBird Live"
_CAMERA_LAST_VISITOR = "{} Last Ring"
_CAMERA_LAST_MOTION = "{} Last Motion"
_CAMERA_LIVE = "{} Live"
_LAST_VISITOR_INTERVAL = datetime.timedelta(minutes=1)
_LAST_MOTION_INTERVAL = datetime.timedelta(minutes=1)
_LIVE_INTERVAL = datetime.timedelta(seconds=1)
@@ -30,14 +30,20 @@ _TIMEOUT = 10 # seconds
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""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([
DoorBirdCamera(device.live_image_url, _CAMERA_LIVE, _LIVE_INTERVAL),
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),
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),
])

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):
"""Set up a Foscam IP Camera."""
add_devices([FoscamCam(config)])

View File

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

View File

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

View File

@@ -45,7 +45,7 @@ class NeatoCleaningMap(Camera):
self.update()
return self._image
@Throttle(timedelta(seconds=10))
@Throttle(timedelta(seconds=60))
def update(self):
"""Check the contents of the map list."""
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):
"""Set up a Nest Cam."""
if discovery_info is None:
return
"""Set up a Nest Cam.
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)
for structure, device in camera_devices]
add_devices(cameras, True)
async_add_devices(cameras, True)
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):
"""Set up access to Netatmo cameras."""
netatmo = hass.components.netatmo
home = config.get(CONF_HOME)
verify_ssl = config.get(CONF_VERIFY_SSL, True)
import lnetatmo
import pyatmo
try:
data = CameraData(netatmo.NETATMO_AUTH, home)
for camera_name in data.get_camera_names():
@@ -46,7 +45,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
continue
add_devices([NetatmoCamera(data, camera_name, home,
camera_type, verify_ssl)])
except lnetatmo.NoDevice:
except pyatmo.NoDevice:
return None

View File

@@ -13,6 +13,7 @@ import voluptuous as vol
from homeassistant.const import CONF_PORT
from homeassistant.components.camera import Camera, PLATFORM_SCHEMA
import homeassistant.helpers.config_validation as cv
from homeassistant.exceptions import PlatformNotReady
REQUIREMENTS = ['uvcclient==0.10.1']
@@ -41,18 +42,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
port = config[CONF_PORT]
from uvcclient import nvr
nvrconn = nvr.UVCRemote(addr, port, key)
try:
# Exceptions may be raised in all method calls to the nvr library.
nvrconn = nvr.UVCRemote(addr, port, key)
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'
# 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 = [
camera for camera in cameras
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,
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.ffmpeg import DATA_FFMPEG
from homeassistant.const import (CONF_HOST, CONF_NAME, CONF_PATH,
CONF_PASSWORD, CONF_PORT, CONF_USERNAME)
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
from homeassistant.exceptions import PlatformNotReady
REQUIREMENTS = ['aioftp==0.10.1']
DEPENDENCIES = ['ffmpeg']
_LOGGER = logging.getLogger(__name__)
@@ -38,12 +40,9 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
async def async_setup_platform(hass,
config,
async_add_devices,
discovery_info=None):
async def async_setup_platform(
hass, config, async_add_devices, discovery_info=None):
"""Set up a Yi Camera."""
_LOGGER.debug('Received configuration: %s', config)
async_add_devices([YiCamera(hass, config)], True)
@@ -54,71 +53,81 @@ class YiCamera(Camera):
"""Initialize."""
super().__init__()
self._extra_arguments = config.get(CONF_FFMPEG_ARGUMENTS)
self._ftp = None
self._last_image = None
self._last_url = None
self._manager = hass.data[DATA_FFMPEG]
self._name = config.get(CONF_NAME)
self.host = config.get(CONF_HOST)
self.port = config.get(CONF_PORT)
self.path = config.get(CONF_PATH)
self.user = config.get(CONF_USERNAME)
self.passwd = config.get(CONF_PASSWORD)
self._name = config[CONF_NAME]
self.host = config[CONF_HOST]
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
hass.async_add_job(self._connect_to_client)
@property
def brand(self):
"""Camera 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."""
from ftplib import FTP, error_perm
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
from aioftp import StatusCodeError
try:
ftp.cwd(self.path)
except error_perm as exc:
_LOGGER.error('Unable to find path: %s', self.path)
_LOGGER.debug(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
await self._ftp.change_directory(self.path)
dirs = []
for path, attrs in await self._ftp.list():
if attrs['type'] == 'dir' and '.' not in str(path):
dirs.append(path)
latest_dir = dirs[-1]
ftp.cwd(latest_dir)
videos = ftp.nlst()
await self._ftp.change_directory(latest_dir)
videos = []
for path, _ in await self._ftp.list():
videos.append(path)
if not videos:
_LOGGER.info('Video folder "%s" is empty; delaying', latest_dir)
return False
_LOGGER.info('Video folder "%s" empty; delaying', latest_dir)
return None
return 'ftp://{0}:{1}@{2}:{3}{4}/{5}/{6}'.format(
self.user, self.passwd, self.host, self.port, self.path,
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):
"""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)
url = await 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_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

View File

@@ -49,7 +49,6 @@ def _get_image_url(hass, monitor, mode):
@asyncio.coroutine
# pylint: disable=unused-argument
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Set up the ZoneMinder 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):
"""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)
async def async_away_mode_set_service(service):
@@ -456,6 +457,16 @@ async def async_setup(hass, config):
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):
"""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)
from homeassistant.components.climate import (
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 (
ATTR_TEMPERATURE, PRECISION_HALVES, TEMP_CELSIUS)
DEPENDENCIES = ['fritzbox']
_LOGGER = logging.getLogger(__name__)
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
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):
"""Set up the Fritzbox smarthome thermostat platform."""
@@ -88,6 +94,9 @@ class FritzboxThermostat(ClimateDevice):
@property
def target_temperature(self):
"""Return the temperature we try to reach."""
if self._target_temperature in (ON_API_TEMPERATURE,
OFF_API_TEMPERATURE):
return None
return self._target_temperature
def set_temperature(self, **kwargs):
@@ -102,9 +111,13 @@ class FritzboxThermostat(ClimateDevice):
@property
def current_operation(self):
"""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:
return STATE_HEAT
elif self._target_temperature == self._eco_temperature:
if self._target_temperature == self._eco_temperature:
return STATE_ECO
return STATE_MANUAL
@@ -119,6 +132,10 @@ class FritzboxThermostat(ClimateDevice):
self.set_temperature(temperature=self._comfort_temperature)
elif operation_mode == STATE_ECO:
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
def min_temp(self):

View File

@@ -14,8 +14,7 @@ from homeassistant.core import DOMAIN as HA_DOMAIN
from homeassistant.components.climate import (
STATE_HEAT, STATE_COOL, STATE_IDLE, STATE_AUTO, ClimateDevice,
ATTR_OPERATION_MODE, ATTR_AWAY_MODE, SUPPORT_OPERATION_MODE,
SUPPORT_AWAY_MODE, SUPPORT_TARGET_TEMPERATURE, PLATFORM_SCHEMA,
DEFAULT_MIN_TEMP, DEFAULT_MAX_TEMP)
SUPPORT_AWAY_MODE, SUPPORT_TARGET_TEMPERATURE, PLATFORM_SCHEMA)
from homeassistant.const import (
ATTR_UNIT_OF_MEASUREMENT, STATE_ON, STATE_OFF, ATTR_TEMPERATURE,
CONF_NAME, ATTR_ENTITY_ID, SERVICE_TURN_ON, SERVICE_TURN_OFF,
@@ -268,7 +267,8 @@ class GenericThermostat(ClimateDevice):
if self._min_temp:
return self._min_temp
return DEFAULT_MIN_TEMP
# get default temp from super class
return super().min_temp
@property
def max_temp(self):
@@ -277,7 +277,8 @@ class GenericThermostat(ClimateDevice):
if self._max_temp:
return self._max_temp
return DEFAULT_MAX_TEMP
# Get default temp from super class
return super().max_temp
@asyncio.coroutine
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."""
async def after_update_callback(device):
"""Call after device was updated."""
# pylint: disable=unused-argument
await self.async_update_ha_state()
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,
ATTR_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE, SUPPORT_OPERATION_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 (
STATE_ON, STATE_OFF, ATTR_TEMPERATURE, CONF_NAME, CONF_VALUE_TEMPLATE)
from homeassistant.components.mqtt import (
@@ -70,6 +70,9 @@ CONF_SWING_MODE_LIST = 'swing_modes'
CONF_INITIAL = 'initial'
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)
PLATFORM_SCHEMA = SCHEMA_BASE.extend({
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_PAYLOAD_ON, default="ON"): 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)
@@ -181,19 +188,22 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
config.get(CONF_PAYLOAD_OFF),
config.get(CONF_AVAILABILITY_TOPIC),
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):
"""Representation of a demo climate device."""
"""Representation of an MQTT climate device."""
def __init__(self, hass, name, topic, value_templates, qos, retain,
mode_list, fan_mode_list, swing_mode_list,
target_temperature, away, hold, current_fan_mode,
current_swing_mode, current_operation, aux, send_if_off,
payload_on, payload_off, availability_topic,
payload_available, payload_not_available):
payload_available, payload_not_available,
min_temp, max_temp):
"""Initialize the climate device."""
super().__init__(availability_topic, qos, payload_available,
payload_not_available)
@@ -219,6 +229,8 @@ class MqttClimate(MqttAvailability, ClimateDevice):
self._send_if_off = send_if_off
self._payload_on = payload_on
self._payload_off = payload_off
self._min_temp = min_temp
self._max_temp = max_temp
@asyncio.coroutine
def async_added_to_hass(self):
@@ -619,3 +631,15 @@ class MqttClimate(MqttAvailability, ClimateDevice):
support |= SUPPORT_AUX_HEAT
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):
"""Set up the Nest thermostat."""
if discovery_info is None:
return
"""Set up the Nest thermostat.
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
all_devices = [NestThermostat(structure, device, temp_unit)
for structure, device in hass.data[DATA_NEST].thermostats()]
thermostats = await hass.async_add_job(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):

View File

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

View File

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

View File

@@ -8,8 +8,8 @@ import logging
from homeassistant.const import (PRECISION_TENTHS, TEMP_CELSIUS)
from homeassistant.components.climate import (
ClimateDevice, SUPPORT_TARGET_TEMPERATURE, SUPPORT_OPERATION_MODE,
DEFAULT_MIN_TEMP, DEFAULT_MAX_TEMP)
ClimateDevice, SUPPORT_TARGET_TEMPERATURE, SUPPORT_OPERATION_MODE)
from homeassistant.util.temperature import convert as convert_temperature
from homeassistant.const import ATTR_TEMPERATURE
from homeassistant.components.tado import DATA_TADO
@@ -231,18 +231,14 @@ class TadoClimate(ClimateDevice):
@property
def min_temp(self):
"""Return the minimum temperature."""
if self._min_temp:
return self._min_temp
return DEFAULT_MIN_TEMP
return convert_temperature(self._min_temp, self._unit,
self.hass.config.units.temperature_unit)
@property
def max_temp(self):
"""Return the maximum temperature."""
if self._max_temp:
return self._max_temp
return DEFAULT_MAX_TEMP
return convert_temperature(self._max_temp, self._unit,
self.hass.config.units.temperature_unit)
def update(self):
"""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):
"""Set up of Vera thermostats."""
add_devices_callback(
VeraThermostat(device, hass.data[VERA_CONTROLLER]) for
device in hass.data[VERA_DEVICES]['climate'])
[VeraThermostat(device, hass.data[VERA_CONTROLLER]) for
device in hass.data[VERA_DEVICES]['climate']], True)
class VeraThermostat(VeraDevice, ClimateDevice):
@@ -101,10 +101,6 @@ class VeraThermostat(VeraDevice, ClimateDevice):
if power:
return convert(power, float, 0.0)
def update(self):
"""Handle state updates."""
self._state = self.vera_device.get_hvac_mode()
@property
def temperature_unit(self):
"""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)])
# pylint: disable=abstract-method
class WinkThermostat(WinkDevice, ClimateDevice):
"""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
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.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):
"""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
class ConfigManagerEntityView(HomeAssistantView):
"""View to interact with an entity registry entry."""
@callback
def websocket_get_entity(hass, connection, msg):
"""Handle get entity registry entry command.
url = '/api/config/entity_registry/{entity_id}'
name = 'api:config:entity_registry:entity'
async def get(self, request, entity_id):
"""Get the entity registry settings for an entity."""
hass = request.app['hass']
Async friendly.
"""
async def retrieve_entity():
"""Get entity from registry."""
registry = await async_get_registry(hass)
entry = registry.entities.get(entity_id)
entry = registry.entities.get(msg['entity_id'])
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({
# If passed in, we update value. Passing None will remove old value.
vol.Optional('name'): vol.Any(str, None),
}))
async def post(self, request, entity_id, data):
"""Update the entity registry settings for an entity."""
hass = request.app['hass']
hass.async_add_job(retrieve_entity())
@callback
def websocket_update_entity(hass, connection, msg):
"""Handle get camera thumbnail websocket command.
Async friendly.
"""
async def update_entity():
"""Get entity from registry."""
registry = await async_get_registry(hass)
if entity_id not in registry.entities:
return self.json_message('Entry not found', 404)
if msg['entity_id'] not in registry.entities:
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)
return self.json(_entry_dict(entry))
entry = registry.async_update_entity(
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

View File

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

View File

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

View File

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

View File

@@ -18,7 +18,6 @@ _LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['lutron_caseta']
# pylint: disable=unused-argument
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""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)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['pymyq==0.0.8']
REQUIREMENTS = ['pymyq==0.0.11']
_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):
"""Set up the RPi cover platform."""
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):
"""Set up the Vera covers."""
add_devices(
VeraCover(device, hass.data[VERA_CONTROLLER]) for
device in hass.data[VERA_DEVICES]['cover'])
[VeraCover(device, hass.data[VERA_CONTROLLER]) for
device in hass.data[VERA_DEVICES]['cover']], True)
class VeraCover(VeraDevice, CoverDevice):

View File

@@ -1,6 +1,7 @@
{
"config": {
"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",
"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"
},
"options": {
"title": "Extra configuration options for deCONZ",
"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"

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": {
"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"
},
"error": {
@@ -11,9 +13,11 @@
"data": {
"host": "H\u00e1zigazda (Host)",
"port": "Port (alap\u00e9rtelmezett \u00e9rt\u00e9k: '80')"
}
},
"title": "deCONZ \u00e1tj\u00e1r\u00f3 megad\u00e1sa"
},
"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"
}
},

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": {
"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": {
"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"
},
"options": {
"data": {
"allow_clip_sensor": "Erlaabt den Import vun virtuellen Sensoren"
},
"title": "Extra Konfiguratiouns Optiounen fir deCONZ"
}
},
"title": "deCONZ"

View File

@@ -19,6 +19,13 @@
"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",
"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"

View File

@@ -19,6 +19,12 @@
"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\"",
"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"

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": {
"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": {
"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"
},
"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"

View File

@@ -19,6 +19,12 @@
"link": {
"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"
},
"options": {
"data": {
"allow_clip_sensor": "Dovoli uvoz virtualnih senzorjev"
},
"title": "Dodatne mo\u017enosti konfiguracije za 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