mirror of
https://github.com/home-assistant/core.git
synced 2025-08-04 13:15:18 +02:00
27
.coveragerc
27
.coveragerc
@@ -61,6 +61,9 @@ omit =
|
|||||||
homeassistant/components/coinbase.py
|
homeassistant/components/coinbase.py
|
||||||
homeassistant/components/sensor/coinbase.py
|
homeassistant/components/sensor/coinbase.py
|
||||||
|
|
||||||
|
homeassistant/components/cast/*
|
||||||
|
homeassistant/components/*/cast.py
|
||||||
|
|
||||||
homeassistant/components/comfoconnect.py
|
homeassistant/components/comfoconnect.py
|
||||||
homeassistant/components/*/comfoconnect.py
|
homeassistant/components/*/comfoconnect.py
|
||||||
|
|
||||||
@@ -97,7 +100,7 @@ omit =
|
|||||||
homeassistant/components/*/envisalink.py
|
homeassistant/components/*/envisalink.py
|
||||||
|
|
||||||
homeassistant/components/fritzbox.py
|
homeassistant/components/fritzbox.py
|
||||||
homeassistant/components/*/fritzbox.py
|
homeassistant/components/switch/fritzbox.py
|
||||||
|
|
||||||
homeassistant/components/eufy.py
|
homeassistant/components/eufy.py
|
||||||
homeassistant/components/*/eufy.py
|
homeassistant/components/*/eufy.py
|
||||||
@@ -195,12 +198,15 @@ omit =
|
|||||||
homeassistant/components/neato.py
|
homeassistant/components/neato.py
|
||||||
homeassistant/components/*/neato.py
|
homeassistant/components/*/neato.py
|
||||||
|
|
||||||
homeassistant/components/nest.py
|
homeassistant/components/nest/__init__.py
|
||||||
homeassistant/components/*/nest.py
|
homeassistant/components/*/nest.py
|
||||||
|
|
||||||
homeassistant/components/netatmo.py
|
homeassistant/components/netatmo.py
|
||||||
homeassistant/components/*/netatmo.py
|
homeassistant/components/*/netatmo.py
|
||||||
|
|
||||||
|
homeassistant/components/netgear_lte.py
|
||||||
|
homeassistant/components/*/netgear_lte.py
|
||||||
|
|
||||||
homeassistant/components/octoprint.py
|
homeassistant/components/octoprint.py
|
||||||
homeassistant/components/*/octoprint.py
|
homeassistant/components/*/octoprint.py
|
||||||
|
|
||||||
@@ -249,6 +255,9 @@ omit =
|
|||||||
homeassistant/components/smappee.py
|
homeassistant/components/smappee.py
|
||||||
homeassistant/components/*/smappee.py
|
homeassistant/components/*/smappee.py
|
||||||
|
|
||||||
|
homeassistant/components/sonos/__init__.py
|
||||||
|
homeassistant/components/*/sonos.py
|
||||||
|
|
||||||
homeassistant/components/tado.py
|
homeassistant/components/tado.py
|
||||||
homeassistant/components/*/tado.py
|
homeassistant/components/*/tado.py
|
||||||
|
|
||||||
@@ -311,6 +320,9 @@ omit =
|
|||||||
homeassistant/components/wink/*
|
homeassistant/components/wink/*
|
||||||
homeassistant/components/*/wink.py
|
homeassistant/components/*/wink.py
|
||||||
|
|
||||||
|
homeassistant/components/wirelesstag.py
|
||||||
|
homeassistant/components/*/wirelesstag.py
|
||||||
|
|
||||||
homeassistant/components/xiaomi_aqara.py
|
homeassistant/components/xiaomi_aqara.py
|
||||||
homeassistant/components/*/xiaomi_aqara.py
|
homeassistant/components/*/xiaomi_aqara.py
|
||||||
|
|
||||||
@@ -348,6 +360,7 @@ omit =
|
|||||||
homeassistant/components/binary_sensor/ping.py
|
homeassistant/components/binary_sensor/ping.py
|
||||||
homeassistant/components/binary_sensor/rest.py
|
homeassistant/components/binary_sensor/rest.py
|
||||||
homeassistant/components/binary_sensor/tapsaff.py
|
homeassistant/components/binary_sensor/tapsaff.py
|
||||||
|
homeassistant/components/binary_sensor/uptimerobot.py
|
||||||
homeassistant/components/browser.py
|
homeassistant/components/browser.py
|
||||||
homeassistant/components/calendar/caldav.py
|
homeassistant/components/calendar/caldav.py
|
||||||
homeassistant/components/calendar/todoist.py
|
homeassistant/components/calendar/todoist.py
|
||||||
@@ -363,6 +376,7 @@ omit =
|
|||||||
homeassistant/components/camera/rpi_camera.py
|
homeassistant/components/camera/rpi_camera.py
|
||||||
homeassistant/components/camera/synology.py
|
homeassistant/components/camera/synology.py
|
||||||
homeassistant/components/camera/xeoma.py
|
homeassistant/components/camera/xeoma.py
|
||||||
|
homeassistant/components/camera/xiaomi.py
|
||||||
homeassistant/components/camera/yi.py
|
homeassistant/components/camera/yi.py
|
||||||
homeassistant/components/climate/econet.py
|
homeassistant/components/climate/econet.py
|
||||||
homeassistant/components/climate/ephember.py
|
homeassistant/components/climate/ephember.py
|
||||||
@@ -378,6 +392,7 @@ omit =
|
|||||||
homeassistant/components/climate/sensibo.py
|
homeassistant/components/climate/sensibo.py
|
||||||
homeassistant/components/climate/touchline.py
|
homeassistant/components/climate/touchline.py
|
||||||
homeassistant/components/climate/venstar.py
|
homeassistant/components/climate/venstar.py
|
||||||
|
homeassistant/components/climate/zhong_hong.py
|
||||||
homeassistant/components/cover/garadget.py
|
homeassistant/components/cover/garadget.py
|
||||||
homeassistant/components/cover/gogogate2.py
|
homeassistant/components/cover/gogogate2.py
|
||||||
homeassistant/components/cover/homematic.py
|
homeassistant/components/cover/homematic.py
|
||||||
@@ -397,6 +412,7 @@ omit =
|
|||||||
homeassistant/components/device_tracker/bt_home_hub_5.py
|
homeassistant/components/device_tracker/bt_home_hub_5.py
|
||||||
homeassistant/components/device_tracker/cisco_ios.py
|
homeassistant/components/device_tracker/cisco_ios.py
|
||||||
homeassistant/components/device_tracker/ddwrt.py
|
homeassistant/components/device_tracker/ddwrt.py
|
||||||
|
homeassistant/components/device_tracker/freebox.py
|
||||||
homeassistant/components/device_tracker/fritz.py
|
homeassistant/components/device_tracker/fritz.py
|
||||||
homeassistant/components/device_tracker/google_maps.py
|
homeassistant/components/device_tracker/google_maps.py
|
||||||
homeassistant/components/device_tracker/gpslogger.py
|
homeassistant/components/device_tracker/gpslogger.py
|
||||||
@@ -462,6 +478,7 @@ omit =
|
|||||||
homeassistant/components/light/yeelightsunflower.py
|
homeassistant/components/light/yeelightsunflower.py
|
||||||
homeassistant/components/light/zengge.py
|
homeassistant/components/light/zengge.py
|
||||||
homeassistant/components/lirc.py
|
homeassistant/components/lirc.py
|
||||||
|
homeassistant/components/lock/kiwi.py
|
||||||
homeassistant/components/lock/lockitron.py
|
homeassistant/components/lock/lockitron.py
|
||||||
homeassistant/components/lock/nello.py
|
homeassistant/components/lock/nello.py
|
||||||
homeassistant/components/lock/nuki.py
|
homeassistant/components/lock/nuki.py
|
||||||
@@ -472,7 +489,6 @@ omit =
|
|||||||
homeassistant/components/media_player/aquostv.py
|
homeassistant/components/media_player/aquostv.py
|
||||||
homeassistant/components/media_player/bluesound.py
|
homeassistant/components/media_player/bluesound.py
|
||||||
homeassistant/components/media_player/braviatv.py
|
homeassistant/components/media_player/braviatv.py
|
||||||
homeassistant/components/media_player/cast.py
|
|
||||||
homeassistant/components/media_player/channels.py
|
homeassistant/components/media_player/channels.py
|
||||||
homeassistant/components/media_player/clementine.py
|
homeassistant/components/media_player/clementine.py
|
||||||
homeassistant/components/media_player/cmus.py
|
homeassistant/components/media_player/cmus.py
|
||||||
@@ -481,10 +497,12 @@ omit =
|
|||||||
homeassistant/components/media_player/directv.py
|
homeassistant/components/media_player/directv.py
|
||||||
homeassistant/components/media_player/dunehd.py
|
homeassistant/components/media_player/dunehd.py
|
||||||
homeassistant/components/media_player/emby.py
|
homeassistant/components/media_player/emby.py
|
||||||
|
homeassistant/components/media_player/epson.py
|
||||||
homeassistant/components/media_player/firetv.py
|
homeassistant/components/media_player/firetv.py
|
||||||
homeassistant/components/media_player/frontier_silicon.py
|
homeassistant/components/media_player/frontier_silicon.py
|
||||||
homeassistant/components/media_player/gpmdp.py
|
homeassistant/components/media_player/gpmdp.py
|
||||||
homeassistant/components/media_player/gstreamer.py
|
homeassistant/components/media_player/gstreamer.py
|
||||||
|
homeassistant/components/media_player/horizon.py
|
||||||
homeassistant/components/media_player/itunes.py
|
homeassistant/components/media_player/itunes.py
|
||||||
homeassistant/components/media_player/kodi.py
|
homeassistant/components/media_player/kodi.py
|
||||||
homeassistant/components/media_player/lg_netcast.py
|
homeassistant/components/media_player/lg_netcast.py
|
||||||
@@ -506,7 +524,6 @@ omit =
|
|||||||
homeassistant/components/media_player/russound_rnet.py
|
homeassistant/components/media_player/russound_rnet.py
|
||||||
homeassistant/components/media_player/snapcast.py
|
homeassistant/components/media_player/snapcast.py
|
||||||
homeassistant/components/media_player/songpal.py
|
homeassistant/components/media_player/songpal.py
|
||||||
homeassistant/components/media_player/sonos.py
|
|
||||||
homeassistant/components/media_player/spotify.py
|
homeassistant/components/media_player/spotify.py
|
||||||
homeassistant/components/media_player/squeezebox.py
|
homeassistant/components/media_player/squeezebox.py
|
||||||
homeassistant/components/media_player/ue_smart_radio.py
|
homeassistant/components/media_player/ue_smart_radio.py
|
||||||
@@ -644,6 +661,7 @@ omit =
|
|||||||
homeassistant/components/sensor/nederlandse_spoorwegen.py
|
homeassistant/components/sensor/nederlandse_spoorwegen.py
|
||||||
homeassistant/components/sensor/netdata.py
|
homeassistant/components/sensor/netdata.py
|
||||||
homeassistant/components/sensor/neurio_energy.py
|
homeassistant/components/sensor/neurio_energy.py
|
||||||
|
homeassistant/components/sensor/nsw_fuel_station.py
|
||||||
homeassistant/components/sensor/nut.py
|
homeassistant/components/sensor/nut.py
|
||||||
homeassistant/components/sensor/nzbget.py
|
homeassistant/components/sensor/nzbget.py
|
||||||
homeassistant/components/sensor/ohmconnect.py
|
homeassistant/components/sensor/ohmconnect.py
|
||||||
@@ -748,6 +766,7 @@ omit =
|
|||||||
homeassistant/components/tts/picotts.py
|
homeassistant/components/tts/picotts.py
|
||||||
homeassistant/components/vacuum/mqtt.py
|
homeassistant/components/vacuum/mqtt.py
|
||||||
homeassistant/components/vacuum/roomba.py
|
homeassistant/components/vacuum/roomba.py
|
||||||
|
homeassistant/components/watson_iot.py
|
||||||
homeassistant/components/weather/bom.py
|
homeassistant/components/weather/bom.py
|
||||||
homeassistant/components/weather/buienradar.py
|
homeassistant/components/weather/buienradar.py
|
||||||
homeassistant/components/weather/darksky.py
|
homeassistant/components/weather/darksky.py
|
||||||
|
@@ -70,6 +70,7 @@ homeassistant/components/sensor/filter.py @dgomes
|
|||||||
homeassistant/components/sensor/gearbest.py @HerrHofrat
|
homeassistant/components/sensor/gearbest.py @HerrHofrat
|
||||||
homeassistant/components/sensor/irish_rail_transport.py @ttroy50
|
homeassistant/components/sensor/irish_rail_transport.py @ttroy50
|
||||||
homeassistant/components/sensor/miflora.py @danielhiversen @ChristianKuehnel
|
homeassistant/components/sensor/miflora.py @danielhiversen @ChristianKuehnel
|
||||||
|
homeassistant/components/sensor/nsw_fuel_station.py @nickw444
|
||||||
homeassistant/components/sensor/pollen.py @bachya
|
homeassistant/components/sensor/pollen.py @bachya
|
||||||
homeassistant/components/sensor/qnap.py @colinodell
|
homeassistant/components/sensor/qnap.py @colinodell
|
||||||
homeassistant/components/sensor/sma.py @kellerza
|
homeassistant/components/sensor/sma.py @kellerza
|
||||||
|
@@ -1,5 +1,4 @@
|
|||||||
"""Provide methods to bootstrap a Home Assistant instance."""
|
"""Provide methods to bootstrap a Home Assistant instance."""
|
||||||
import asyncio
|
|
||||||
import logging
|
import logging
|
||||||
import logging.handlers
|
import logging.handlers
|
||||||
import os
|
import os
|
||||||
@@ -17,7 +16,7 @@ from homeassistant.components import persistent_notification
|
|||||||
from homeassistant.const import EVENT_HOMEASSISTANT_CLOSE
|
from homeassistant.const import EVENT_HOMEASSISTANT_CLOSE
|
||||||
from homeassistant.setup import async_setup_component
|
from homeassistant.setup import async_setup_component
|
||||||
from homeassistant.util.logging import AsyncHandler
|
from homeassistant.util.logging import AsyncHandler
|
||||||
from homeassistant.util.package import async_get_user_site, get_user_site
|
from homeassistant.util.package import async_get_user_site, is_virtual_env
|
||||||
from homeassistant.util.yaml import clear_secret_cache
|
from homeassistant.util.yaml import clear_secret_cache
|
||||||
from homeassistant.exceptions import HomeAssistantError
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
from homeassistant.helpers.signal import async_register_signal_handling
|
from homeassistant.helpers.signal import async_register_signal_handling
|
||||||
@@ -53,8 +52,9 @@ def from_config_dict(config: Dict[str, Any],
|
|||||||
if config_dir is not None:
|
if config_dir is not None:
|
||||||
config_dir = os.path.abspath(config_dir)
|
config_dir = os.path.abspath(config_dir)
|
||||||
hass.config.config_dir = config_dir
|
hass.config.config_dir = config_dir
|
||||||
|
if not is_virtual_env():
|
||||||
hass.loop.run_until_complete(
|
hass.loop.run_until_complete(
|
||||||
async_mount_local_lib_path(config_dir, hass.loop))
|
async_mount_local_lib_path(config_dir))
|
||||||
|
|
||||||
# run task
|
# run task
|
||||||
hass = hass.loop.run_until_complete(
|
hass = hass.loop.run_until_complete(
|
||||||
@@ -197,7 +197,9 @@ async def async_from_config_file(config_path: str,
|
|||||||
# Set config dir to directory holding config file
|
# Set config dir to directory holding config file
|
||||||
config_dir = os.path.abspath(os.path.dirname(config_path))
|
config_dir = os.path.abspath(os.path.dirname(config_path))
|
||||||
hass.config.config_dir = config_dir
|
hass.config.config_dir = config_dir
|
||||||
await async_mount_local_lib_path(config_dir, hass.loop)
|
|
||||||
|
if not is_virtual_env():
|
||||||
|
await async_mount_local_lib_path(config_dir)
|
||||||
|
|
||||||
async_enable_logging(hass, verbose, log_rotate_days, log_file,
|
async_enable_logging(hass, verbose, log_rotate_days, log_file,
|
||||||
log_no_color)
|
log_no_color)
|
||||||
@@ -211,9 +213,8 @@ async def async_from_config_file(config_path: str,
|
|||||||
finally:
|
finally:
|
||||||
clear_secret_cache()
|
clear_secret_cache()
|
||||||
|
|
||||||
hass = await async_from_config_dict(
|
return await async_from_config_dict(
|
||||||
config_dict, hass, enable_log=False, skip_pip=skip_pip)
|
config_dict, hass, enable_log=False, skip_pip=skip_pip)
|
||||||
return hass
|
|
||||||
|
|
||||||
|
|
||||||
@core.callback
|
@core.callback
|
||||||
@@ -308,23 +309,13 @@ def async_enable_logging(hass: core.HomeAssistant,
|
|||||||
"Unable to setup error log %s (access denied)", err_log_path)
|
"Unable to setup error log %s (access denied)", err_log_path)
|
||||||
|
|
||||||
|
|
||||||
def mount_local_lib_path(config_dir: str) -> str:
|
async def async_mount_local_lib_path(config_dir: str) -> str:
|
||||||
"""Add local library to Python Path."""
|
|
||||||
deps_dir = os.path.join(config_dir, 'deps')
|
|
||||||
lib_dir = get_user_site(deps_dir)
|
|
||||||
if lib_dir not in sys.path:
|
|
||||||
sys.path.insert(0, lib_dir)
|
|
||||||
return deps_dir
|
|
||||||
|
|
||||||
|
|
||||||
async def async_mount_local_lib_path(config_dir: str,
|
|
||||||
loop: asyncio.AbstractEventLoop) -> str:
|
|
||||||
"""Add local library to Python Path.
|
"""Add local library to Python Path.
|
||||||
|
|
||||||
This function is a coroutine.
|
This function is a coroutine.
|
||||||
"""
|
"""
|
||||||
deps_dir = os.path.join(config_dir, 'deps')
|
deps_dir = os.path.join(config_dir, 'deps')
|
||||||
lib_dir = await async_get_user_site(deps_dir, loop=loop)
|
lib_dir = await async_get_user_site(deps_dir)
|
||||||
if lib_dir not in sys.path:
|
if lib_dir not in sys.path:
|
||||||
sys.path.insert(0, lib_dir)
|
sys.path.insert(0, lib_dir)
|
||||||
return deps_dir
|
return deps_dir
|
||||||
|
@@ -4,15 +4,17 @@ Support for Arlo Alarm Control Panels.
|
|||||||
For more details about this platform, please refer to the documentation at
|
For more details about this platform, please refer to the documentation at
|
||||||
https://home-assistant.io/components/alarm_control_panel.arlo/
|
https://home-assistant.io/components/alarm_control_panel.arlo/
|
||||||
"""
|
"""
|
||||||
import asyncio
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
from homeassistant.core import callback
|
||||||
|
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||||
from homeassistant.components.alarm_control_panel import (
|
from homeassistant.components.alarm_control_panel import (
|
||||||
AlarmControlPanel, PLATFORM_SCHEMA)
|
AlarmControlPanel, PLATFORM_SCHEMA)
|
||||||
from homeassistant.components.arlo import (DATA_ARLO, CONF_ATTRIBUTION)
|
from homeassistant.components.arlo import (
|
||||||
|
DATA_ARLO, CONF_ATTRIBUTION, SIGNAL_UPDATE_ARLO)
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_ATTRIBUTION, STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME,
|
ATTR_ATTRIBUTION, STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME,
|
||||||
STATE_ALARM_DISARMED)
|
STATE_ALARM_DISARMED)
|
||||||
@@ -36,21 +38,20 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
|||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
@asyncio.coroutine
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
|
|
||||||
"""Set up the Arlo Alarm Control Panels."""
|
"""Set up the Arlo Alarm Control Panels."""
|
||||||
data = hass.data[DATA_ARLO]
|
arlo = hass.data[DATA_ARLO]
|
||||||
|
|
||||||
if not data.base_stations:
|
if not arlo.base_stations:
|
||||||
return
|
return
|
||||||
|
|
||||||
home_mode_name = config.get(CONF_HOME_MODE_NAME)
|
home_mode_name = config.get(CONF_HOME_MODE_NAME)
|
||||||
away_mode_name = config.get(CONF_AWAY_MODE_NAME)
|
away_mode_name = config.get(CONF_AWAY_MODE_NAME)
|
||||||
base_stations = []
|
base_stations = []
|
||||||
for base_station in data.base_stations:
|
for base_station in arlo.base_stations:
|
||||||
base_stations.append(ArloBaseStation(base_station, home_mode_name,
|
base_stations.append(ArloBaseStation(base_station, home_mode_name,
|
||||||
away_mode_name))
|
away_mode_name))
|
||||||
async_add_devices(base_stations, True)
|
add_devices(base_stations, True)
|
||||||
|
|
||||||
|
|
||||||
class ArloBaseStation(AlarmControlPanel):
|
class ArloBaseStation(AlarmControlPanel):
|
||||||
@@ -68,6 +69,16 @@ class ArloBaseStation(AlarmControlPanel):
|
|||||||
"""Return icon."""
|
"""Return icon."""
|
||||||
return ICON
|
return ICON
|
||||||
|
|
||||||
|
async def async_added_to_hass(self):
|
||||||
|
"""Register callbacks."""
|
||||||
|
async_dispatcher_connect(
|
||||||
|
self.hass, SIGNAL_UPDATE_ARLO, self._update_callback)
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def _update_callback(self):
|
||||||
|
"""Call update method."""
|
||||||
|
self.async_schedule_update_ha_state(True)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def state(self):
|
def state(self):
|
||||||
"""Return the state of the device."""
|
"""Return the state of the device."""
|
||||||
@@ -75,30 +86,22 @@ class ArloBaseStation(AlarmControlPanel):
|
|||||||
|
|
||||||
def update(self):
|
def update(self):
|
||||||
"""Update the state of the device."""
|
"""Update the state of the device."""
|
||||||
# PyArlo sometimes returns None for mode. So retry 3 times before
|
_LOGGER.debug("Updating Arlo Alarm Control Panel %s", self.name)
|
||||||
# returning None.
|
|
||||||
num_retries = 3
|
|
||||||
i = 0
|
|
||||||
while i < num_retries:
|
|
||||||
mode = self._base_station.mode
|
mode = self._base_station.mode
|
||||||
if mode:
|
if mode:
|
||||||
self._state = self._get_state_from_mode(mode)
|
self._state = self._get_state_from_mode(mode)
|
||||||
return
|
else:
|
||||||
i += 1
|
|
||||||
self._state = None
|
self._state = None
|
||||||
|
|
||||||
@asyncio.coroutine
|
async def async_alarm_disarm(self, code=None):
|
||||||
def async_alarm_disarm(self, code=None):
|
|
||||||
"""Send disarm command."""
|
"""Send disarm command."""
|
||||||
self._base_station.mode = DISARMED
|
self._base_station.mode = DISARMED
|
||||||
|
|
||||||
@asyncio.coroutine
|
async def async_alarm_arm_away(self, code=None):
|
||||||
def async_alarm_arm_away(self, code=None):
|
|
||||||
"""Send arm away command. Uses custom mode."""
|
"""Send arm away command. Uses custom mode."""
|
||||||
self._base_station.mode = self._away_mode_name
|
self._base_station.mode = self._away_mode_name
|
||||||
|
|
||||||
@asyncio.coroutine
|
async def async_alarm_arm_home(self, code=None):
|
||||||
def async_alarm_arm_home(self, code=None):
|
|
||||||
"""Send arm home command. Uses custom mode."""
|
"""Send arm home command. Uses custom mode."""
|
||||||
self._base_station.mode = self._home_mode_name
|
self._base_station.mode = self._home_mode_name
|
||||||
|
|
||||||
@@ -125,4 +128,4 @@ class ArloBaseStation(AlarmControlPanel):
|
|||||||
return STATE_ALARM_ARMED_HOME
|
return STATE_ALARM_ARMED_HOME
|
||||||
elif mode == self._away_mode_name:
|
elif mode == self._away_mode_name:
|
||||||
return STATE_ALARM_ARMED_AWAY
|
return STATE_ALARM_ARMED_AWAY
|
||||||
return None
|
return mode
|
||||||
|
@@ -18,7 +18,7 @@ from homeassistant.const import (
|
|||||||
from homeassistant.helpers import discovery
|
from homeassistant.helpers import discovery
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
|
||||||
REQUIREMENTS = ['amcrest==1.2.2']
|
REQUIREMENTS = ['amcrest==1.2.3']
|
||||||
DEPENDENCIES = ['ffmpeg']
|
DEPENDENCIES = ['ffmpeg']
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
@@ -5,14 +5,18 @@ For more details about this component, please refer to the documentation at
|
|||||||
https://home-assistant.io/components/arlo/
|
https://home-assistant.io/components/arlo/
|
||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
from requests.exceptions import HTTPError, ConnectTimeout
|
from requests.exceptions import HTTPError, ConnectTimeout
|
||||||
|
|
||||||
from homeassistant.helpers import config_validation as cv
|
from homeassistant.helpers import config_validation as cv
|
||||||
from homeassistant.const import CONF_USERNAME, CONF_PASSWORD
|
from homeassistant.const import (
|
||||||
|
CONF_USERNAME, CONF_PASSWORD, CONF_SCAN_INTERVAL)
|
||||||
|
from homeassistant.helpers.event import track_time_interval
|
||||||
|
from homeassistant.helpers.dispatcher import dispatcher_send
|
||||||
|
|
||||||
REQUIREMENTS = ['pyarlo==0.1.2']
|
REQUIREMENTS = ['pyarlo==0.1.7']
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -25,10 +29,16 @@ DOMAIN = 'arlo'
|
|||||||
NOTIFICATION_ID = 'arlo_notification'
|
NOTIFICATION_ID = 'arlo_notification'
|
||||||
NOTIFICATION_TITLE = 'Arlo Component Setup'
|
NOTIFICATION_TITLE = 'Arlo Component Setup'
|
||||||
|
|
||||||
|
SCAN_INTERVAL = timedelta(seconds=60)
|
||||||
|
|
||||||
|
SIGNAL_UPDATE_ARLO = "arlo_update"
|
||||||
|
|
||||||
CONFIG_SCHEMA = vol.Schema({
|
CONFIG_SCHEMA = vol.Schema({
|
||||||
DOMAIN: vol.Schema({
|
DOMAIN: vol.Schema({
|
||||||
vol.Required(CONF_USERNAME): cv.string,
|
vol.Required(CONF_USERNAME): cv.string,
|
||||||
vol.Required(CONF_PASSWORD): cv.string,
|
vol.Required(CONF_PASSWORD): cv.string,
|
||||||
|
vol.Optional(CONF_SCAN_INTERVAL, default=SCAN_INTERVAL):
|
||||||
|
cv.time_period,
|
||||||
}),
|
}),
|
||||||
}, extra=vol.ALLOW_EXTRA)
|
}, extra=vol.ALLOW_EXTRA)
|
||||||
|
|
||||||
@@ -38,6 +48,7 @@ def setup(hass, config):
|
|||||||
conf = config[DOMAIN]
|
conf = config[DOMAIN]
|
||||||
username = conf.get(CONF_USERNAME)
|
username = conf.get(CONF_USERNAME)
|
||||||
password = conf.get(CONF_PASSWORD)
|
password = conf.get(CONF_PASSWORD)
|
||||||
|
scan_interval = conf.get(CONF_SCAN_INTERVAL)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from pyarlo import PyArlo
|
from pyarlo import PyArlo
|
||||||
@@ -45,7 +56,17 @@ def setup(hass, config):
|
|||||||
arlo = PyArlo(username, password, preload=False)
|
arlo = PyArlo(username, password, preload=False)
|
||||||
if not arlo.is_connected:
|
if not arlo.is_connected:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
# assign refresh period to base station thread
|
||||||
|
arlo_base_station = next((
|
||||||
|
station for station in arlo.base_stations), None)
|
||||||
|
|
||||||
|
if arlo_base_station is None:
|
||||||
|
return False
|
||||||
|
|
||||||
|
arlo_base_station.refresh_rate = scan_interval.total_seconds()
|
||||||
hass.data[DATA_ARLO] = arlo
|
hass.data[DATA_ARLO] = arlo
|
||||||
|
|
||||||
except (ConnectTimeout, HTTPError) as ex:
|
except (ConnectTimeout, HTTPError) as ex:
|
||||||
_LOGGER.error("Unable to connect to Netgear Arlo: %s", str(ex))
|
_LOGGER.error("Unable to connect to Netgear Arlo: %s", str(ex))
|
||||||
hass.components.persistent_notification.create(
|
hass.components.persistent_notification.create(
|
||||||
@@ -55,4 +76,17 @@ def setup(hass, config):
|
|||||||
title=NOTIFICATION_TITLE,
|
title=NOTIFICATION_TITLE,
|
||||||
notification_id=NOTIFICATION_ID)
|
notification_id=NOTIFICATION_ID)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def hub_refresh(event_time):
|
||||||
|
"""Call ArloHub to refresh information."""
|
||||||
|
_LOGGER.info("Updating Arlo Hub component")
|
||||||
|
hass.data[DATA_ARLO].update(update_cameras=True,
|
||||||
|
update_base_station=True)
|
||||||
|
dispatcher_send(hass, SIGNAL_UPDATE_ARLO)
|
||||||
|
|
||||||
|
# register service
|
||||||
|
hass.services.register(DOMAIN, 'update', hub_refresh)
|
||||||
|
|
||||||
|
# register scan interval for ArloHub
|
||||||
|
track_time_interval(hass, hub_refresh, scan_interval)
|
||||||
return True
|
return True
|
||||||
|
@@ -145,7 +145,7 @@ def request_configuration(hass, config, name, host, serialnumber):
|
|||||||
|
|
||||||
def setup(hass, config):
|
def setup(hass, config):
|
||||||
"""Set up for Axis devices."""
|
"""Set up for Axis devices."""
|
||||||
def _shutdown(call): # pylint: disable=unused-argument
|
def _shutdown(call):
|
||||||
"""Stop the event stream on shutdown."""
|
"""Stop the event stream on shutdown."""
|
||||||
for serialnumber, device in AXIS_DEVICES.items():
|
for serialnumber, device in AXIS_DEVICES.items():
|
||||||
_LOGGER.info("Stopping event stream for %s.", serialnumber)
|
_LOGGER.info("Stopping event stream for %s.", serialnumber)
|
||||||
@@ -272,8 +272,7 @@ class AxisDeviceEvent(Entity):
|
|||||||
|
|
||||||
def _update_callback(self):
|
def _update_callback(self):
|
||||||
"""Update the sensor's state, if needed."""
|
"""Update the sensor's state, if needed."""
|
||||||
self.update()
|
self.schedule_update_ha_state(True)
|
||||||
self.schedule_update_ha_state()
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
|
@@ -35,7 +35,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
|||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=unused-argument
|
|
||||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
"""Set up the Command line Binary Sensor."""
|
"""Set up the Command line Binary Sensor."""
|
||||||
name = config.get(CONF_NAME)
|
name = config.get(CONF_NAME)
|
||||||
|
@@ -5,7 +5,6 @@ For more details about this platform, please refer to the documentation at
|
|||||||
https://home-assistant.io/components/binary_sensor.eight_sleep/
|
https://home-assistant.io/components/binary_sensor.eight_sleep/
|
||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
import asyncio
|
|
||||||
|
|
||||||
from homeassistant.components.binary_sensor import BinarySensorDevice
|
from homeassistant.components.binary_sensor import BinarySensorDevice
|
||||||
from homeassistant.components.eight_sleep import (
|
from homeassistant.components.eight_sleep import (
|
||||||
@@ -16,8 +15,8 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
DEPENDENCIES = ['eight_sleep']
|
DEPENDENCIES = ['eight_sleep']
|
||||||
|
|
||||||
|
|
||||||
@asyncio.coroutine
|
async def async_setup_platform(hass, config, async_add_devices,
|
||||||
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
|
discovery_info=None):
|
||||||
"""Set up the eight sleep binary sensor."""
|
"""Set up the eight sleep binary sensor."""
|
||||||
if discovery_info is None:
|
if discovery_info is None:
|
||||||
return
|
return
|
||||||
@@ -63,7 +62,6 @@ class EightHeatSensor(EightSleepHeatEntity, BinarySensorDevice):
|
|||||||
"""Return true if the binary sensor is on."""
|
"""Return true if the binary sensor is on."""
|
||||||
return self._state
|
return self._state
|
||||||
|
|
||||||
@asyncio.coroutine
|
async def async_update(self):
|
||||||
def async_update(self):
|
|
||||||
"""Retrieve latest state."""
|
"""Retrieve latest state."""
|
||||||
self._state = self._usrobj.bed_presence
|
self._state = self._usrobj.bed_presence
|
||||||
|
@@ -23,7 +23,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
|||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=unused-argument
|
|
||||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
"""Set up the GC100 devices."""
|
"""Set up the GC100 devices."""
|
||||||
binary_sensors = []
|
binary_sensors = []
|
||||||
|
@@ -28,7 +28,6 @@ ISY_DEVICE_TYPES = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=unused-argument
|
|
||||||
def setup_platform(hass, config: ConfigType,
|
def setup_platform(hass, config: ConfigType,
|
||||||
add_devices: Callable[[list], None], discovery_info=None):
|
add_devices: Callable[[list], None], discovery_info=None):
|
||||||
"""Set up the ISY994 binary sensor platform."""
|
"""Set up the ISY994 binary sensor platform."""
|
||||||
@@ -299,7 +298,6 @@ class ISYBinarySensorHeartbeat(ISYDevice, BinarySensorDevice):
|
|||||||
# No heartbeat timer is active
|
# No heartbeat timer is active
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# pylint: disable=unused-argument
|
|
||||||
@callback
|
@callback
|
||||||
def timer_elapsed(now) -> None:
|
def timer_elapsed(now) -> None:
|
||||||
"""Heartbeat missed; set state to indicate dead battery."""
|
"""Heartbeat missed; set state to indicate dead battery."""
|
||||||
@@ -314,7 +312,6 @@ class ISYBinarySensorHeartbeat(ISYDevice, BinarySensorDevice):
|
|||||||
self._heartbeat_timer = async_track_point_in_utc_time(
|
self._heartbeat_timer = async_track_point_in_utc_time(
|
||||||
self.hass, timer_elapsed, point_in_time)
|
self.hass, timer_elapsed, point_in_time)
|
||||||
|
|
||||||
# pylint: disable=unused-argument
|
|
||||||
def on_update(self, event: object) -> None:
|
def on_update(self, event: object) -> None:
|
||||||
"""Ignore node status updates.
|
"""Ignore node status updates.
|
||||||
|
|
||||||
|
@@ -115,7 +115,6 @@ class KNXBinarySensor(BinarySensorDevice):
|
|||||||
"""Register callbacks to update hass after device was changed."""
|
"""Register callbacks to update hass after device was changed."""
|
||||||
async def after_update_callback(device):
|
async def after_update_callback(device):
|
||||||
"""Call after device was updated."""
|
"""Call after device was updated."""
|
||||||
# pylint: disable=unused-argument
|
|
||||||
await self.async_update_ha_state()
|
await self.async_update_ha_state()
|
||||||
self.device.register_device_updated_cb(after_update_callback)
|
self.device.register_device_updated_cb(after_update_callback)
|
||||||
|
|
||||||
|
@@ -52,19 +52,18 @@ class LinodeBinarySensor(BinarySensorDevice):
|
|||||||
self._node_id = node_id
|
self._node_id = node_id
|
||||||
self._state = None
|
self._state = None
|
||||||
self.data = None
|
self.data = None
|
||||||
|
self._attrs = {}
|
||||||
|
self._name = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
"""Return the name of the sensor."""
|
"""Return the name of the sensor."""
|
||||||
if self.data is not None:
|
return self._name
|
||||||
return self.data.label
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_on(self):
|
def is_on(self):
|
||||||
"""Return true if the binary sensor is on."""
|
"""Return true if the binary sensor is on."""
|
||||||
if self.data is not None:
|
return self._state
|
||||||
return self.data.status == 'running'
|
|
||||||
return False
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def device_class(self):
|
def device_class(self):
|
||||||
@@ -74,8 +73,18 @@ class LinodeBinarySensor(BinarySensorDevice):
|
|||||||
@property
|
@property
|
||||||
def device_state_attributes(self):
|
def device_state_attributes(self):
|
||||||
"""Return the state attributes of the Linode Node."""
|
"""Return the state attributes of the Linode Node."""
|
||||||
if self.data:
|
return self._attrs
|
||||||
return {
|
|
||||||
|
def update(self):
|
||||||
|
"""Update state of sensor."""
|
||||||
|
self._linode.update()
|
||||||
|
if self._linode.data is not None:
|
||||||
|
for node in self._linode.data:
|
||||||
|
if node.id == self._node_id:
|
||||||
|
self.data = node
|
||||||
|
if self.data is not None:
|
||||||
|
self._state = self.data.status == 'running'
|
||||||
|
self._attrs = {
|
||||||
ATTR_CREATED: self.data.created,
|
ATTR_CREATED: self.data.created,
|
||||||
ATTR_NODE_ID: self.data.id,
|
ATTR_NODE_ID: self.data.id,
|
||||||
ATTR_NODE_NAME: self.data.label,
|
ATTR_NODE_NAME: self.data.label,
|
||||||
@@ -85,12 +94,4 @@ class LinodeBinarySensor(BinarySensorDevice):
|
|||||||
ATTR_REGION: self.data.region.country,
|
ATTR_REGION: self.data.region.country,
|
||||||
ATTR_VCPUS: self.data.specs.vcpus,
|
ATTR_VCPUS: self.data.specs.vcpus,
|
||||||
}
|
}
|
||||||
return {}
|
self._name = self.data.label
|
||||||
|
|
||||||
def update(self):
|
|
||||||
"""Update state of sensor."""
|
|
||||||
self._linode.update()
|
|
||||||
if self._linode.data is not None:
|
|
||||||
for node in self._linode.data:
|
|
||||||
if node.id == self._node_id:
|
|
||||||
self.data = node
|
|
||||||
|
@@ -6,6 +6,7 @@ https://home-assistant.io/components/binary_sensor.mqtt/
|
|||||||
"""
|
"""
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
@@ -24,7 +25,7 @@ import homeassistant.helpers.config_validation as cv
|
|||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
DEFAULT_NAME = 'MQTT Binary sensor'
|
DEFAULT_NAME = 'MQTT Binary sensor'
|
||||||
|
CONF_UNIQUE_ID = 'unique_id'
|
||||||
DEFAULT_PAYLOAD_OFF = 'OFF'
|
DEFAULT_PAYLOAD_OFF = 'OFF'
|
||||||
DEFAULT_PAYLOAD_ON = 'ON'
|
DEFAULT_PAYLOAD_ON = 'ON'
|
||||||
DEFAULT_FORCE_UPDATE = False
|
DEFAULT_FORCE_UPDATE = False
|
||||||
@@ -37,6 +38,9 @@ PLATFORM_SCHEMA = mqtt.MQTT_RO_PLATFORM_SCHEMA.extend({
|
|||||||
vol.Optional(CONF_PAYLOAD_ON, default=DEFAULT_PAYLOAD_ON): cv.string,
|
vol.Optional(CONF_PAYLOAD_ON, default=DEFAULT_PAYLOAD_ON): cv.string,
|
||||||
vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA,
|
vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA,
|
||||||
vol.Optional(CONF_FORCE_UPDATE, default=DEFAULT_FORCE_UPDATE): cv.boolean,
|
vol.Optional(CONF_FORCE_UPDATE, default=DEFAULT_FORCE_UPDATE): cv.boolean,
|
||||||
|
# Integrations shouldn't never expose unique_id through configuration
|
||||||
|
# this here is an exception because MQTT is a msg transport, not a protocol
|
||||||
|
vol.Optional(CONF_UNIQUE_ID): cv.string,
|
||||||
}).extend(mqtt.MQTT_AVAILABILITY_SCHEMA.schema)
|
}).extend(mqtt.MQTT_AVAILABILITY_SCHEMA.schema)
|
||||||
|
|
||||||
|
|
||||||
@@ -61,7 +65,8 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
|
|||||||
config.get(CONF_PAYLOAD_OFF),
|
config.get(CONF_PAYLOAD_OFF),
|
||||||
config.get(CONF_PAYLOAD_AVAILABLE),
|
config.get(CONF_PAYLOAD_AVAILABLE),
|
||||||
config.get(CONF_PAYLOAD_NOT_AVAILABLE),
|
config.get(CONF_PAYLOAD_NOT_AVAILABLE),
|
||||||
value_template
|
value_template,
|
||||||
|
config.get(CONF_UNIQUE_ID),
|
||||||
)])
|
)])
|
||||||
|
|
||||||
|
|
||||||
@@ -70,7 +75,8 @@ class MqttBinarySensor(MqttAvailability, BinarySensorDevice):
|
|||||||
|
|
||||||
def __init__(self, name, state_topic, availability_topic, device_class,
|
def __init__(self, name, state_topic, availability_topic, device_class,
|
||||||
qos, force_update, payload_on, payload_off, payload_available,
|
qos, force_update, payload_on, payload_off, payload_available,
|
||||||
payload_not_available, value_template):
|
payload_not_available, value_template,
|
||||||
|
unique_id: Optional[str]):
|
||||||
"""Initialize the MQTT binary sensor."""
|
"""Initialize the MQTT binary sensor."""
|
||||||
super().__init__(availability_topic, qos, payload_available,
|
super().__init__(availability_topic, qos, payload_available,
|
||||||
payload_not_available)
|
payload_not_available)
|
||||||
@@ -83,6 +89,7 @@ class MqttBinarySensor(MqttAvailability, BinarySensorDevice):
|
|||||||
self._qos = qos
|
self._qos = qos
|
||||||
self._force_update = force_update
|
self._force_update = force_update
|
||||||
self._template = value_template
|
self._template = value_template
|
||||||
|
self._unique_id = unique_id
|
||||||
|
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def async_added_to_hass(self):
|
def async_added_to_hass(self):
|
||||||
@@ -134,3 +141,8 @@ class MqttBinarySensor(MqttAvailability, BinarySensorDevice):
|
|||||||
def force_update(self):
|
def force_update(self):
|
||||||
"""Force update."""
|
"""Force update."""
|
||||||
return self._force_update
|
return self._force_update
|
||||||
|
|
||||||
|
@property
|
||||||
|
def unique_id(self):
|
||||||
|
"""Return a unique ID."""
|
||||||
|
return self._unique_id
|
||||||
|
@@ -29,6 +29,7 @@ class MyStromView(HomeAssistantView):
|
|||||||
|
|
||||||
url = '/api/mystrom'
|
url = '/api/mystrom'
|
||||||
name = 'api:mystrom'
|
name = 'api:mystrom'
|
||||||
|
supported_actions = ['single', 'double', 'long', 'touch']
|
||||||
|
|
||||||
def __init__(self, add_devices):
|
def __init__(self, add_devices):
|
||||||
"""Initialize the myStrom URL endpoint."""
|
"""Initialize the myStrom URL endpoint."""
|
||||||
@@ -44,16 +45,18 @@ class MyStromView(HomeAssistantView):
|
|||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def _handle(self, hass, data):
|
def _handle(self, hass, data):
|
||||||
"""Handle requests to the myStrom endpoint."""
|
"""Handle requests to the myStrom endpoint."""
|
||||||
button_action = list(data.keys())[0]
|
button_action = next((
|
||||||
button_id = data[button_action]
|
parameter for parameter in data
|
||||||
entity_id = '{}.{}_{}'.format(DOMAIN, button_id, button_action)
|
if parameter in self.supported_actions), None)
|
||||||
|
|
||||||
if button_action not in ['single', 'double', 'long', 'touch']:
|
if button_action is None:
|
||||||
_LOGGER.error(
|
_LOGGER.error(
|
||||||
"Received unidentified message from myStrom button: %s", data)
|
"Received unidentified message from myStrom button: %s", data)
|
||||||
return ("Received unidentified message: {}".format(data),
|
return ("Received unidentified message: {}".format(data),
|
||||||
HTTP_UNPROCESSABLE_ENTITY)
|
HTTP_UNPROCESSABLE_ENTITY)
|
||||||
|
|
||||||
|
button_id = data[button_action]
|
||||||
|
entity_id = '{}.{}_{}'.format(DOMAIN, button_id, button_action)
|
||||||
if entity_id not in self.buttons:
|
if entity_id not in self.buttons:
|
||||||
_LOGGER.info("New myStrom button/action detected: %s/%s",
|
_LOGGER.info("New myStrom button/action detected: %s/%s",
|
||||||
button_id, button_action)
|
button_id, button_action)
|
||||||
|
@@ -8,7 +8,8 @@ from itertools import chain
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
from homeassistant.components.binary_sensor import BinarySensorDevice
|
from homeassistant.components.binary_sensor import BinarySensorDevice
|
||||||
from homeassistant.components.nest import DATA_NEST, NestSensorDevice
|
from homeassistant.components.nest import (
|
||||||
|
DATA_NEST, DATA_NEST_CONFIG, CONF_BINARY_SENSORS, NestSensorDevice)
|
||||||
from homeassistant.const import CONF_MONITORED_CONDITIONS
|
from homeassistant.const import CONF_MONITORED_CONDITIONS
|
||||||
|
|
||||||
DEPENDENCIES = ['nest']
|
DEPENDENCIES = ['nest']
|
||||||
@@ -56,12 +57,19 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
|
|
||||||
|
|
||||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
"""Set up the Nest binary sensors."""
|
"""Set up the Nest binary sensors.
|
||||||
if discovery_info is None:
|
|
||||||
return
|
|
||||||
|
|
||||||
|
No longer used.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(hass, entry, async_add_devices):
|
||||||
|
"""Set up a Nest binary sensor based on a config entry."""
|
||||||
nest = hass.data[DATA_NEST]
|
nest = hass.data[DATA_NEST]
|
||||||
|
|
||||||
|
discovery_info = \
|
||||||
|
hass.data.get(DATA_NEST_CONFIG, {}).get(CONF_BINARY_SENSORS, {})
|
||||||
|
|
||||||
# Add all available binary sensors if no Nest binary sensor config is set
|
# Add all available binary sensors if no Nest binary sensor config is set
|
||||||
if discovery_info == {}:
|
if discovery_info == {}:
|
||||||
conditions = _VALID_BINARY_SENSOR_TYPES
|
conditions = _VALID_BINARY_SENSOR_TYPES
|
||||||
@@ -76,6 +84,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||||||
"for valid options.")
|
"for valid options.")
|
||||||
_LOGGER.error(wstr)
|
_LOGGER.error(wstr)
|
||||||
|
|
||||||
|
def get_binary_sensors():
|
||||||
|
"""Get the Nest binary sensors."""
|
||||||
sensors = []
|
sensors = []
|
||||||
for structure in nest.structures():
|
for structure in nest.structures():
|
||||||
sensors += [NestBinarySensor(structure, None, variable)
|
sensors += [NestBinarySensor(structure, None, variable)
|
||||||
@@ -101,7 +111,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||||||
sensors += [NestActivityZoneSensor(structure,
|
sensors += [NestActivityZoneSensor(structure,
|
||||||
device,
|
device,
|
||||||
activity_zone)]
|
activity_zone)]
|
||||||
add_devices(sensors, True)
|
|
||||||
|
return sensors
|
||||||
|
|
||||||
|
async_add_devices(await hass.async_add_job(get_binary_sensors), True)
|
||||||
|
|
||||||
|
|
||||||
class NestBinarySensor(NestSensorDevice, BinarySensorDevice):
|
class NestBinarySensor(NestSensorDevice, BinarySensorDevice):
|
||||||
|
@@ -57,7 +57,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
|||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=unused-argument
|
|
||||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
"""Set up the access to Netatmo binary sensor."""
|
"""Set up the access to Netatmo binary sensor."""
|
||||||
netatmo = hass.components.netatmo
|
netatmo = hass.components.netatmo
|
||||||
@@ -68,12 +67,12 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||||||
|
|
||||||
module_name = None
|
module_name = None
|
||||||
|
|
||||||
import lnetatmo
|
import pyatmo
|
||||||
try:
|
try:
|
||||||
data = CameraData(netatmo.NETATMO_AUTH, home)
|
data = CameraData(netatmo.NETATMO_AUTH, home)
|
||||||
if not data.get_camera_names():
|
if not data.get_camera_names():
|
||||||
return None
|
return None
|
||||||
except lnetatmo.NoDevice:
|
except pyatmo.NoDevice:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
welcome_sensors = config.get(
|
welcome_sensors = config.get(
|
||||||
|
@@ -33,7 +33,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
|||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=unused-argument
|
|
||||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
"""Set up the available OctoPrint binary sensors."""
|
"""Set up the available OctoPrint binary sensors."""
|
||||||
octoprint_api = hass.data[DOMAIN]["api"]
|
octoprint_api = hass.data[DOMAIN]["api"]
|
||||||
|
@@ -44,7 +44,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
|||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=unused-argument
|
|
||||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
"""Set up Pilight Binary Sensor."""
|
"""Set up Pilight Binary Sensor."""
|
||||||
disarm = config.get(CONF_DISARM_AFTER_TRIGGER)
|
disarm = config.get(CONF_DISARM_AFTER_TRIGGER)
|
||||||
|
@@ -8,7 +8,7 @@ import logging
|
|||||||
|
|
||||||
from homeassistant.components.binary_sensor import BinarySensorDevice
|
from homeassistant.components.binary_sensor import BinarySensorDevice
|
||||||
from homeassistant.components.rainmachine import (
|
from homeassistant.components.rainmachine import (
|
||||||
BINARY_SENSORS, DATA_RAINMACHINE, DATA_UPDATE_TOPIC, TYPE_FREEZE,
|
BINARY_SENSORS, DATA_RAINMACHINE, SENSOR_UPDATE_TOPIC, TYPE_FREEZE,
|
||||||
TYPE_FREEZE_PROTECTION, TYPE_HOT_DAYS, TYPE_HOURLY, TYPE_MONTH,
|
TYPE_FREEZE_PROTECTION, TYPE_HOT_DAYS, TYPE_HOURLY, TYPE_MONTH,
|
||||||
TYPE_RAINDELAY, TYPE_RAINSENSOR, TYPE_WEEKDAY, RainMachineEntity)
|
TYPE_RAINDELAY, TYPE_RAINSENSOR, TYPE_WEEKDAY, RainMachineEntity)
|
||||||
from homeassistant.const import CONF_MONITORED_CONDITIONS
|
from homeassistant.const import CONF_MONITORED_CONDITIONS
|
||||||
@@ -20,7 +20,8 @@ DEPENDENCIES = ['rainmachine']
|
|||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
async def async_setup_platform(
|
||||||
|
hass, config, async_add_devices, discovery_info=None):
|
||||||
"""Set up the RainMachine Switch platform."""
|
"""Set up the RainMachine Switch platform."""
|
||||||
if discovery_info is None:
|
if discovery_info is None:
|
||||||
return
|
return
|
||||||
@@ -33,7 +34,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||||||
binary_sensors.append(
|
binary_sensors.append(
|
||||||
RainMachineBinarySensor(rainmachine, sensor_type, name, icon))
|
RainMachineBinarySensor(rainmachine, sensor_type, name, icon))
|
||||||
|
|
||||||
add_devices(binary_sensors, True)
|
async_add_devices(binary_sensors, True)
|
||||||
|
|
||||||
|
|
||||||
class RainMachineBinarySensor(RainMachineEntity, BinarySensorDevice):
|
class RainMachineBinarySensor(RainMachineEntity, BinarySensorDevice):
|
||||||
@@ -70,16 +71,16 @@ class RainMachineBinarySensor(RainMachineEntity, BinarySensorDevice):
|
|||||||
self.rainmachine.device_mac.replace(':', ''), self._sensor_type)
|
self.rainmachine.device_mac.replace(':', ''), self._sensor_type)
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def update_data(self):
|
def _update_data(self):
|
||||||
"""Update the state."""
|
"""Update the state."""
|
||||||
self.async_schedule_update_ha_state(True)
|
self.async_schedule_update_ha_state(True)
|
||||||
|
|
||||||
async def async_added_to_hass(self):
|
async def async_added_to_hass(self):
|
||||||
"""Register callbacks."""
|
"""Register callbacks."""
|
||||||
async_dispatcher_connect(self.hass, DATA_UPDATE_TOPIC,
|
async_dispatcher_connect(
|
||||||
self.update_data)
|
self.hass, SENSOR_UPDATE_TOPIC, self._update_data)
|
||||||
|
|
||||||
def update(self):
|
async def async_update(self):
|
||||||
"""Update the state."""
|
"""Update the state."""
|
||||||
if self._sensor_type == TYPE_FREEZE:
|
if self._sensor_type == TYPE_FREEZE:
|
||||||
self._state = self.rainmachine.restrictions['current']['freeze']
|
self._state = self.rainmachine.restrictions['current']['freeze']
|
||||||
|
@@ -42,7 +42,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
|||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=unused-argument
|
|
||||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
"""Set up the raspihats binary_sensor devices."""
|
"""Set up the raspihats binary_sensor devices."""
|
||||||
I2CHatBinarySensor.I2C_HATS_MANAGER = hass.data[I2C_HATS_MANAGER]
|
I2CHatBinarySensor.I2C_HATS_MANAGER = hass.data[I2C_HATS_MANAGER]
|
||||||
|
@@ -39,7 +39,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
|||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=unused-argument
|
|
||||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
"""Set up the Raspberry PI GPIO devices."""
|
"""Set up the Raspberry PI GPIO devices."""
|
||||||
pull_mode = config.get(CONF_PULL_MODE)
|
pull_mode = config.get(CONF_PULL_MODE)
|
||||||
|
@@ -94,4 +94,4 @@ class SkybellBinarySensor(SkybellDevice, BinarySensorDevice):
|
|||||||
|
|
||||||
self._state = bool(event and event.get('id') != self._event.get('id'))
|
self._state = bool(event and event.get('id') != self._event.get('id'))
|
||||||
|
|
||||||
self._event = event
|
self._event = event or {}
|
||||||
|
@@ -57,7 +57,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
|||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=unused-argument
|
|
||||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
"""Set up the trend sensors."""
|
"""Set up the trend sensors."""
|
||||||
sensors = []
|
sensors = []
|
||||||
|
92
homeassistant/components/binary_sensor/uptimerobot.py
Normal file
92
homeassistant/components/binary_sensor/uptimerobot.py
Normal 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
|
@@ -19,8 +19,8 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
"""Perform the setup for Vera controller devices."""
|
"""Perform the setup for Vera controller devices."""
|
||||||
add_devices(
|
add_devices(
|
||||||
VeraBinarySensor(device, hass.data[VERA_CONTROLLER])
|
[VeraBinarySensor(device, hass.data[VERA_CONTROLLER])
|
||||||
for device in hass.data[VERA_DEVICES]['binary_sensor'])
|
for device in hass.data[VERA_DEVICES]['binary_sensor']], True)
|
||||||
|
|
||||||
|
|
||||||
class VeraBinarySensor(VeraDevice, BinarySensorDevice):
|
class VeraBinarySensor(VeraDevice, BinarySensorDevice):
|
||||||
|
@@ -54,6 +54,7 @@ class VerisureDoorWindowSensor(BinarySensorDevice):
|
|||||||
"$.doorWindow.doorWindowDevice[?(@.deviceLabel=='%s')]",
|
"$.doorWindow.doorWindowDevice[?(@.deviceLabel=='%s')]",
|
||||||
self._device_label) is not None
|
self._device_label) is not None
|
||||||
|
|
||||||
|
# pylint: disable=no-self-use
|
||||||
def update(self):
|
def update(self):
|
||||||
"""Update the state of the sensor."""
|
"""Update the state of the sensor."""
|
||||||
hub.update_overview()
|
hub.update_overview()
|
||||||
|
@@ -13,7 +13,7 @@ DEPENDENCIES = ['wemo']
|
|||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=unused-argument, too-many-function-args
|
# pylint: disable=too-many-function-args
|
||||||
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
||||||
"""Register discovered WeMo binary sensors."""
|
"""Register discovered WeMo binary sensors."""
|
||||||
import pywemo.discovery as discovery
|
import pywemo.discovery as discovery
|
||||||
|
214
homeassistant/components/binary_sensor/wirelesstag.py
Normal file
214
homeassistant/components/binary_sensor/wirelesstag.py
Normal 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()
|
@@ -28,7 +28,11 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||||||
if model in ['motion', 'sensor_motion', 'sensor_motion.aq2']:
|
if model in ['motion', 'sensor_motion', 'sensor_motion.aq2']:
|
||||||
devices.append(XiaomiMotionSensor(device, hass, gateway))
|
devices.append(XiaomiMotionSensor(device, hass, gateway))
|
||||||
elif model in ['magnet', 'sensor_magnet', 'sensor_magnet.aq2']:
|
elif model in ['magnet', 'sensor_magnet', 'sensor_magnet.aq2']:
|
||||||
devices.append(XiaomiDoorSensor(device, gateway))
|
if 'proto' not in device or int(device['proto'][0:1]) == 1:
|
||||||
|
data_key = 'status'
|
||||||
|
else:
|
||||||
|
data_key = 'window_status'
|
||||||
|
devices.append(XiaomiDoorSensor(device, data_key, gateway))
|
||||||
elif model == 'sensor_wleak.aq1':
|
elif model == 'sensor_wleak.aq1':
|
||||||
devices.append(XiaomiWaterLeakSensor(device, gateway))
|
devices.append(XiaomiWaterLeakSensor(device, gateway))
|
||||||
elif model in ['smoke', 'sensor_smoke']:
|
elif model in ['smoke', 'sensor_smoke']:
|
||||||
@@ -43,10 +47,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||||||
data_key = 'channel_0'
|
data_key = 'channel_0'
|
||||||
devices.append(XiaomiButton(device, 'Switch', data_key,
|
devices.append(XiaomiButton(device, 'Switch', data_key,
|
||||||
hass, gateway))
|
hass, gateway))
|
||||||
elif model in ['86sw1', 'sensor_86sw1.aq1']:
|
elif model in ['86sw1', 'sensor_86sw1', 'sensor_86sw1.aq1']:
|
||||||
devices.append(XiaomiButton(device, 'Wall Switch', 'channel_0',
|
devices.append(XiaomiButton(device, 'Wall Switch', 'channel_0',
|
||||||
hass, gateway))
|
hass, gateway))
|
||||||
elif model in ['86sw2', 'sensor_86sw2.aq1']:
|
elif model in ['86sw2', 'sensor_86sw2', 'sensor_86sw2.aq1']:
|
||||||
devices.append(XiaomiButton(device, 'Wall Switch (Left)',
|
devices.append(XiaomiButton(device, 'Wall Switch (Left)',
|
||||||
'channel_0', hass, gateway))
|
'channel_0', hass, gateway))
|
||||||
devices.append(XiaomiButton(device, 'Wall Switch (Right)',
|
devices.append(XiaomiButton(device, 'Wall Switch (Right)',
|
||||||
@@ -190,11 +194,11 @@ class XiaomiMotionSensor(XiaomiBinarySensor):
|
|||||||
class XiaomiDoorSensor(XiaomiBinarySensor):
|
class XiaomiDoorSensor(XiaomiBinarySensor):
|
||||||
"""Representation of a XiaomiDoorSensor."""
|
"""Representation of a XiaomiDoorSensor."""
|
||||||
|
|
||||||
def __init__(self, device, xiaomi_hub):
|
def __init__(self, device, data_key, xiaomi_hub):
|
||||||
"""Initialize the XiaomiDoorSensor."""
|
"""Initialize the XiaomiDoorSensor."""
|
||||||
self._open_since = 0
|
self._open_since = 0
|
||||||
XiaomiBinarySensor.__init__(self, device, 'Door Window Sensor',
|
XiaomiBinarySensor.__init__(self, device, 'Door Window Sensor',
|
||||||
xiaomi_hub, 'status', 'opening')
|
xiaomi_hub, data_key, 'opening')
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def device_state_attributes(self):
|
def device_state_attributes(self):
|
||||||
@@ -330,7 +334,7 @@ class XiaomiButton(XiaomiBinarySensor):
|
|||||||
click_type = 'both'
|
click_type = 'both'
|
||||||
elif value == 'shake':
|
elif value == 'shake':
|
||||||
click_type = 'shake'
|
click_type = 'shake'
|
||||||
elif value == 'long_click':
|
elif value in ['long_click', 'long_both_click']:
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
_LOGGER.warning("Unsupported click_type detected: %s", value)
|
_LOGGER.warning("Unsupported click_type detected: %s", value)
|
||||||
|
@@ -187,8 +187,8 @@ class Switch(zha.Entity, BinarySensorDevice):
|
|||||||
if args[0] == 0xff:
|
if args[0] == 0xff:
|
||||||
rate = 10 # Should read default move rate
|
rate = 10 # Should read default move rate
|
||||||
self._entity.move_level(-rate if args[0] else rate)
|
self._entity.move_level(-rate if args[0] else rate)
|
||||||
elif command_id == 0x0002: # step
|
elif command_id in (0x0002, 0x0006): # step, -with_on_off
|
||||||
# Step (technically shouldn't change on/off)
|
# Step (technically may change on/off)
|
||||||
self._entity.move_level(-args[1] if args[0] else args[1])
|
self._entity.move_level(-args[1] if args[0] else args[1])
|
||||||
|
|
||||||
def attribute_update(self, attrid, value):
|
def attribute_update(self, attrid, value):
|
||||||
|
@@ -34,7 +34,6 @@ CONFIG_SCHEMA = vol.Schema({
|
|||||||
}, extra=vol.ALLOW_EXTRA)
|
}, extra=vol.ALLOW_EXTRA)
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=unused-argument
|
|
||||||
def setup(hass, config):
|
def setup(hass, config):
|
||||||
"""Set up the BloomSky component."""
|
"""Set up the BloomSky component."""
|
||||||
api_key = config[DOMAIN][CONF_API_KEY]
|
api_key = config[DOMAIN][CONF_API_KEY]
|
||||||
|
@@ -4,11 +4,12 @@ Support for Google Calendar event device sensors.
|
|||||||
For more details about this platform, please refer to the documentation at
|
For more details about this platform, please refer to the documentation at
|
||||||
https://home-assistant.io/components/calendar/
|
https://home-assistant.io/components/calendar/
|
||||||
"""
|
"""
|
||||||
import asyncio
|
|
||||||
import logging
|
import logging
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
from aiohttp import web
|
||||||
|
|
||||||
from homeassistant.components.google import (
|
from homeassistant.components.google import (
|
||||||
CONF_OFFSET, CONF_DEVICE_ID, CONF_NAME)
|
CONF_OFFSET, CONF_DEVICE_ID, CONF_NAME)
|
||||||
from homeassistant.const import STATE_OFF, STATE_ON
|
from homeassistant.const import STATE_OFF, STATE_ON
|
||||||
@@ -18,23 +19,32 @@ from homeassistant.helpers.entity import Entity, generate_entity_id
|
|||||||
from homeassistant.helpers.entity_component import EntityComponent
|
from homeassistant.helpers.entity_component import EntityComponent
|
||||||
from homeassistant.helpers.template import DATE_STR_FORMAT
|
from homeassistant.helpers.template import DATE_STR_FORMAT
|
||||||
from homeassistant.util import dt
|
from homeassistant.util import dt
|
||||||
|
from homeassistant.components import http
|
||||||
|
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
DOMAIN = 'calendar'
|
DOMAIN = 'calendar'
|
||||||
|
|
||||||
|
DEPENDENCIES = ['http']
|
||||||
|
|
||||||
ENTITY_ID_FORMAT = DOMAIN + '.{}'
|
ENTITY_ID_FORMAT = DOMAIN + '.{}'
|
||||||
|
|
||||||
SCAN_INTERVAL = timedelta(seconds=60)
|
SCAN_INTERVAL = timedelta(seconds=60)
|
||||||
|
|
||||||
|
|
||||||
@asyncio.coroutine
|
async def async_setup(hass, config):
|
||||||
def async_setup(hass, config):
|
|
||||||
"""Track states and offer events for calendars."""
|
"""Track states and offer events for calendars."""
|
||||||
component = EntityComponent(
|
component = EntityComponent(
|
||||||
_LOGGER, DOMAIN, hass, SCAN_INTERVAL, DOMAIN)
|
_LOGGER, DOMAIN, hass, SCAN_INTERVAL, DOMAIN)
|
||||||
|
|
||||||
yield from component.async_setup(config)
|
hass.http.register_view(CalendarListView(component))
|
||||||
|
hass.http.register_view(CalendarEventView(component))
|
||||||
|
|
||||||
|
await hass.components.frontend.async_register_built_in_panel(
|
||||||
|
'calendar', 'calendar', 'hass:calendar')
|
||||||
|
|
||||||
|
await component.async_setup(config)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
@@ -42,7 +52,14 @@ DEFAULT_CONF_TRACK_NEW = True
|
|||||||
DEFAULT_CONF_OFFSET = '!!'
|
DEFAULT_CONF_OFFSET = '!!'
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=too-many-instance-attributes
|
def get_date(date):
|
||||||
|
"""Get the dateTime from date or dateTime as a local."""
|
||||||
|
if 'date' in date:
|
||||||
|
return dt.start_of_local_day(dt.dt.datetime.combine(
|
||||||
|
dt.parse_date(date['date']), dt.dt.time.min))
|
||||||
|
return dt.as_local(dt.parse_datetime(date['dateTime']))
|
||||||
|
|
||||||
|
|
||||||
class CalendarEventDevice(Entity):
|
class CalendarEventDevice(Entity):
|
||||||
"""A calendar event device."""
|
"""A calendar event device."""
|
||||||
|
|
||||||
@@ -50,7 +67,6 @@ class CalendarEventDevice(Entity):
|
|||||||
# with an update() method
|
# with an update() method
|
||||||
data = None
|
data = None
|
||||||
|
|
||||||
# pylint: disable=too-many-arguments
|
|
||||||
def __init__(self, hass, data):
|
def __init__(self, hass, data):
|
||||||
"""Create the Calendar Event Device."""
|
"""Create the Calendar Event Device."""
|
||||||
self._name = data.get(CONF_NAME)
|
self._name = data.get(CONF_NAME)
|
||||||
@@ -144,15 +160,8 @@ class CalendarEventDevice(Entity):
|
|||||||
self.cleanup()
|
self.cleanup()
|
||||||
return
|
return
|
||||||
|
|
||||||
def _get_date(date):
|
start = get_date(self.data.event['start'])
|
||||||
"""Get the dateTime from date or dateTime as a local."""
|
end = get_date(self.data.event['end'])
|
||||||
if 'date' in date:
|
|
||||||
return dt.start_of_local_day(dt.dt.datetime.combine(
|
|
||||||
dt.parse_date(date['date']), dt.dt.time.min))
|
|
||||||
return dt.as_local(dt.parse_datetime(date['dateTime']))
|
|
||||||
|
|
||||||
start = _get_date(self.data.event['start'])
|
|
||||||
end = _get_date(self.data.event['end'])
|
|
||||||
|
|
||||||
summary = self.data.event.get('summary', '')
|
summary = self.data.event.get('summary', '')
|
||||||
|
|
||||||
@@ -176,10 +185,61 @@ class CalendarEventDevice(Entity):
|
|||||||
|
|
||||||
# cleanup the string so we don't have a bunch of double+ spaces
|
# cleanup the string so we don't have a bunch of double+ spaces
|
||||||
self._cal_data['message'] = re.sub(' +', '', summary).strip()
|
self._cal_data['message'] = re.sub(' +', '', summary).strip()
|
||||||
|
|
||||||
self._cal_data['offset_time'] = offset_time
|
self._cal_data['offset_time'] = offset_time
|
||||||
self._cal_data['location'] = self.data.event.get('location', '')
|
self._cal_data['location'] = self.data.event.get('location', '')
|
||||||
self._cal_data['description'] = self.data.event.get('description', '')
|
self._cal_data['description'] = self.data.event.get('description', '')
|
||||||
self._cal_data['start'] = start
|
self._cal_data['start'] = start
|
||||||
self._cal_data['end'] = end
|
self._cal_data['end'] = end
|
||||||
self._cal_data['all_day'] = 'date' in self.data.event['start']
|
self._cal_data['all_day'] = 'date' in self.data.event['start']
|
||||||
|
|
||||||
|
|
||||||
|
class CalendarEventView(http.HomeAssistantView):
|
||||||
|
"""View to retrieve calendar content."""
|
||||||
|
|
||||||
|
url = '/api/calendars/{entity_id}'
|
||||||
|
name = 'api:calendars:calendar'
|
||||||
|
|
||||||
|
def __init__(self, component):
|
||||||
|
"""Initialize calendar view."""
|
||||||
|
self.component = component
|
||||||
|
|
||||||
|
async def get(self, request, entity_id):
|
||||||
|
"""Return calendar events."""
|
||||||
|
entity = self.component.get_entity(entity_id)
|
||||||
|
start = request.query.get('start')
|
||||||
|
end = request.query.get('end')
|
||||||
|
if None in (start, end, entity):
|
||||||
|
return web.Response(status=400)
|
||||||
|
try:
|
||||||
|
start_date = dt.parse_datetime(start)
|
||||||
|
end_date = dt.parse_datetime(end)
|
||||||
|
except (ValueError, AttributeError):
|
||||||
|
return web.Response(status=400)
|
||||||
|
event_list = await entity.async_get_events(
|
||||||
|
request.app['hass'], start_date, end_date)
|
||||||
|
return self.json(event_list)
|
||||||
|
|
||||||
|
|
||||||
|
class CalendarListView(http.HomeAssistantView):
|
||||||
|
"""View to retrieve calendar list."""
|
||||||
|
|
||||||
|
url = '/api/calendars'
|
||||||
|
name = "api:calendars"
|
||||||
|
|
||||||
|
def __init__(self, component):
|
||||||
|
"""Initialize calendar view."""
|
||||||
|
self.component = component
|
||||||
|
|
||||||
|
async def get(self, request):
|
||||||
|
"""Retrieve calendar list."""
|
||||||
|
get_state = request.app['hass'].states.get
|
||||||
|
calendar_list = []
|
||||||
|
|
||||||
|
for entity in self.component.entities:
|
||||||
|
state = get_state(entity.entity_id)
|
||||||
|
calendar_list.append({
|
||||||
|
"name": state.name,
|
||||||
|
"entity_id": entity.entity_id,
|
||||||
|
})
|
||||||
|
|
||||||
|
return self.json(sorted(calendar_list, key=lambda x: x['name']))
|
||||||
|
@@ -11,7 +11,7 @@ import re
|
|||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.components.calendar import (
|
from homeassistant.components.calendar import (
|
||||||
PLATFORM_SCHEMA, CalendarEventDevice)
|
PLATFORM_SCHEMA, CalendarEventDevice, get_date)
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
CONF_NAME, CONF_PASSWORD, CONF_URL, CONF_USERNAME)
|
CONF_NAME, CONF_PASSWORD, CONF_URL, CONF_USERNAME)
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
@@ -92,7 +92,7 @@ def setup_platform(hass, config, add_devices, disc_info=None):
|
|||||||
if not config.get(CONF_CUSTOM_CALENDARS):
|
if not config.get(CONF_CUSTOM_CALENDARS):
|
||||||
device_data = {
|
device_data = {
|
||||||
CONF_NAME: calendar.name,
|
CONF_NAME: calendar.name,
|
||||||
CONF_DEVICE_ID: calendar.name
|
CONF_DEVICE_ID: calendar.name,
|
||||||
}
|
}
|
||||||
calendar_devices.append(
|
calendar_devices.append(
|
||||||
WebDavCalendarEventDevice(hass, device_data, calendar)
|
WebDavCalendarEventDevice(hass, device_data, calendar)
|
||||||
@@ -120,6 +120,10 @@ class WebDavCalendarEventDevice(CalendarEventDevice):
|
|||||||
attributes = super().device_state_attributes
|
attributes = super().device_state_attributes
|
||||||
return attributes
|
return attributes
|
||||||
|
|
||||||
|
async def async_get_events(self, hass, start_date, end_date):
|
||||||
|
"""Get all events in a specific time frame."""
|
||||||
|
return await self.data.async_get_events(hass, start_date, end_date)
|
||||||
|
|
||||||
|
|
||||||
class WebDavCalendarData(object):
|
class WebDavCalendarData(object):
|
||||||
"""Class to utilize the calendar dav client object to get next event."""
|
"""Class to utilize the calendar dav client object to get next event."""
|
||||||
@@ -131,6 +135,33 @@ class WebDavCalendarData(object):
|
|||||||
self.search = search
|
self.search = search
|
||||||
self.event = None
|
self.event = None
|
||||||
|
|
||||||
|
async def async_get_events(self, hass, start_date, end_date):
|
||||||
|
"""Get all events in a specific time frame."""
|
||||||
|
# Get event list from the current calendar
|
||||||
|
vevent_list = await hass.async_add_job(self.calendar.date_search,
|
||||||
|
start_date, end_date)
|
||||||
|
event_list = []
|
||||||
|
for event in vevent_list:
|
||||||
|
vevent = event.instance.vevent
|
||||||
|
uid = None
|
||||||
|
if hasattr(vevent, 'uid'):
|
||||||
|
uid = vevent.uid.value
|
||||||
|
data = {
|
||||||
|
"uid": uid,
|
||||||
|
"title": vevent.summary.value,
|
||||||
|
"start": self.get_hass_date(vevent.dtstart.value),
|
||||||
|
"end": self.get_hass_date(self.get_end_date(vevent)),
|
||||||
|
"location": self.get_attr_value(vevent, "location"),
|
||||||
|
"description": self.get_attr_value(vevent, "description"),
|
||||||
|
}
|
||||||
|
|
||||||
|
data['start'] = get_date(data['start']).isoformat()
|
||||||
|
data['end'] = get_date(data['end']).isoformat()
|
||||||
|
|
||||||
|
event_list.append(data)
|
||||||
|
|
||||||
|
return event_list
|
||||||
|
|
||||||
@Throttle(MIN_TIME_BETWEEN_UPDATES)
|
@Throttle(MIN_TIME_BETWEEN_UPDATES)
|
||||||
def update(self):
|
def update(self):
|
||||||
"""Get the latest data."""
|
"""Get the latest data."""
|
||||||
|
@@ -4,8 +4,10 @@ Demo platform that has two fake binary sensors.
|
|||||||
For more details about this platform, please refer to the documentation
|
For more details about this platform, please refer to the documentation
|
||||||
https://home-assistant.io/components/demo/
|
https://home-assistant.io/components/demo/
|
||||||
"""
|
"""
|
||||||
|
import copy
|
||||||
|
|
||||||
import homeassistant.util.dt as dt_util
|
import homeassistant.util.dt as dt_util
|
||||||
from homeassistant.components.calendar import CalendarEventDevice
|
from homeassistant.components.calendar import CalendarEventDevice, get_date
|
||||||
from homeassistant.components.google import CONF_DEVICE_ID, CONF_NAME
|
from homeassistant.components.google import CONF_DEVICE_ID, CONF_NAME
|
||||||
|
|
||||||
|
|
||||||
@@ -15,13 +17,13 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||||||
calendar_data_current = DemoGoogleCalendarDataCurrent()
|
calendar_data_current = DemoGoogleCalendarDataCurrent()
|
||||||
add_devices([
|
add_devices([
|
||||||
DemoGoogleCalendar(hass, calendar_data_future, {
|
DemoGoogleCalendar(hass, calendar_data_future, {
|
||||||
CONF_NAME: 'Future Event',
|
CONF_NAME: 'Calendar 1',
|
||||||
CONF_DEVICE_ID: 'future_event',
|
CONF_DEVICE_ID: 'calendar_1',
|
||||||
}),
|
}),
|
||||||
|
|
||||||
DemoGoogleCalendar(hass, calendar_data_current, {
|
DemoGoogleCalendar(hass, calendar_data_current, {
|
||||||
CONF_NAME: 'Current Event',
|
CONF_NAME: 'Calendar 2',
|
||||||
CONF_DEVICE_ID: 'current_event',
|
CONF_DEVICE_ID: 'calendar_2',
|
||||||
}),
|
}),
|
||||||
])
|
])
|
||||||
|
|
||||||
@@ -29,11 +31,21 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||||||
class DemoGoogleCalendarData(object):
|
class DemoGoogleCalendarData(object):
|
||||||
"""Representation of a Demo Calendar element."""
|
"""Representation of a Demo Calendar element."""
|
||||||
|
|
||||||
|
event = {}
|
||||||
|
|
||||||
# pylint: disable=no-self-use
|
# pylint: disable=no-self-use
|
||||||
def update(self):
|
def update(self):
|
||||||
"""Return true so entity knows we have new data."""
|
"""Return true so entity knows we have new data."""
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
async def async_get_events(self, hass, start_date, end_date):
|
||||||
|
"""Get all events in a specific time frame."""
|
||||||
|
event = copy.copy(self.event)
|
||||||
|
event['title'] = event['summary']
|
||||||
|
event['start'] = get_date(event['start']).isoformat()
|
||||||
|
event['end'] = get_date(event['end']).isoformat()
|
||||||
|
return [event]
|
||||||
|
|
||||||
|
|
||||||
class DemoGoogleCalendarDataFuture(DemoGoogleCalendarData):
|
class DemoGoogleCalendarDataFuture(DemoGoogleCalendarData):
|
||||||
"""Representation of a Demo Calendar for a future event."""
|
"""Representation of a Demo Calendar for a future event."""
|
||||||
@@ -80,3 +92,7 @@ class DemoGoogleCalendar(CalendarEventDevice):
|
|||||||
"""Initialize Google Calendar but without the API calls."""
|
"""Initialize Google Calendar but without the API calls."""
|
||||||
self.data = calendar_data
|
self.data = calendar_data
|
||||||
super().__init__(hass, data)
|
super().__init__(hass, data)
|
||||||
|
|
||||||
|
async def async_get_events(self, hass, start_date, end_date):
|
||||||
|
"""Get all events in a specific time frame."""
|
||||||
|
return await self.data.async_get_events(hass, start_date, end_date)
|
||||||
|
@@ -51,6 +51,10 @@ class GoogleCalendarEventDevice(CalendarEventDevice):
|
|||||||
|
|
||||||
super().__init__(hass, data)
|
super().__init__(hass, data)
|
||||||
|
|
||||||
|
async def async_get_events(self, hass, start_date, end_date):
|
||||||
|
"""Get all events in a specific time frame."""
|
||||||
|
return await self.data.async_get_events(hass, start_date, end_date)
|
||||||
|
|
||||||
|
|
||||||
class GoogleCalendarData(object):
|
class GoogleCalendarData(object):
|
||||||
"""Class to utilize calendar service object to get next event."""
|
"""Class to utilize calendar service object to get next event."""
|
||||||
@@ -64,9 +68,7 @@ class GoogleCalendarData(object):
|
|||||||
self.ignore_availability = ignore_availability
|
self.ignore_availability = ignore_availability
|
||||||
self.event = None
|
self.event = None
|
||||||
|
|
||||||
@Throttle(MIN_TIME_BETWEEN_UPDATES)
|
def _prepare_query(self):
|
||||||
def update(self):
|
|
||||||
"""Get the latest data."""
|
|
||||||
from httplib2 import ServerNotFoundError
|
from httplib2 import ServerNotFoundError
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -74,13 +76,41 @@ class GoogleCalendarData(object):
|
|||||||
except ServerNotFoundError:
|
except ServerNotFoundError:
|
||||||
_LOGGER.warning("Unable to connect to Google, using cached data")
|
_LOGGER.warning("Unable to connect to Google, using cached data")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
params = dict(DEFAULT_GOOGLE_SEARCH_PARAMS)
|
params = dict(DEFAULT_GOOGLE_SEARCH_PARAMS)
|
||||||
params['timeMin'] = dt.now().isoformat('T')
|
|
||||||
params['calendarId'] = self.calendar_id
|
params['calendarId'] = self.calendar_id
|
||||||
if self.search:
|
if self.search:
|
||||||
params['q'] = self.search
|
params['q'] = self.search
|
||||||
|
|
||||||
|
return service, params
|
||||||
|
|
||||||
|
async def async_get_events(self, hass, start_date, end_date):
|
||||||
|
"""Get all events in a specific time frame."""
|
||||||
|
service, params = await hass.async_add_job(self._prepare_query)
|
||||||
|
params['timeMin'] = start_date.isoformat('T')
|
||||||
|
params['timeMax'] = end_date.isoformat('T')
|
||||||
|
|
||||||
|
# pylint: disable=no-member
|
||||||
|
events = await hass.async_add_job(service.events)
|
||||||
|
# pylint: enable=no-member
|
||||||
|
result = await hass.async_add_job(events.list(**params).execute)
|
||||||
|
|
||||||
|
items = result.get('items', [])
|
||||||
|
event_list = []
|
||||||
|
for item in items:
|
||||||
|
if (not self.ignore_availability
|
||||||
|
and 'transparency' in item.keys()):
|
||||||
|
if item['transparency'] == 'opaque':
|
||||||
|
event_list.append(item)
|
||||||
|
else:
|
||||||
|
event_list.append(item)
|
||||||
|
return event_list
|
||||||
|
|
||||||
|
@Throttle(MIN_TIME_BETWEEN_UPDATES)
|
||||||
|
def update(self):
|
||||||
|
"""Get the latest data."""
|
||||||
|
service, params = self._prepare_query()
|
||||||
|
params['timeMin'] = dt.now().isoformat('T')
|
||||||
|
|
||||||
events = service.events() # pylint: disable=no-member
|
events = service.events() # pylint: disable=no-member
|
||||||
result = events.list(**params).execute()
|
result = events.list(**params).execute()
|
||||||
|
|
||||||
|
@@ -257,6 +257,10 @@ class TodoistProjectDevice(CalendarEventDevice):
|
|||||||
super().cleanup()
|
super().cleanup()
|
||||||
self._cal_data[ALL_TASKS] = []
|
self._cal_data[ALL_TASKS] = []
|
||||||
|
|
||||||
|
async def async_get_events(self, hass, start_date, end_date):
|
||||||
|
"""Get all events in a specific time frame."""
|
||||||
|
return await self.data.async_get_events(hass, start_date, end_date)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def device_state_attributes(self):
|
def device_state_attributes(self):
|
||||||
"""Return the device state attributes."""
|
"""Return the device state attributes."""
|
||||||
@@ -485,6 +489,31 @@ class TodoistProjectData(object):
|
|||||||
continue
|
continue
|
||||||
return event
|
return event
|
||||||
|
|
||||||
|
async def async_get_events(self, hass, start_date, end_date):
|
||||||
|
"""Get all tasks in a specific time frame."""
|
||||||
|
if self._id is None:
|
||||||
|
project_task_data = [
|
||||||
|
task for task in self._api.state[TASKS]
|
||||||
|
if not self._project_id_whitelist or
|
||||||
|
task[PROJECT_ID] in self._project_id_whitelist]
|
||||||
|
else:
|
||||||
|
project_task_data = self._api.projects.get_data(self._id)[TASKS]
|
||||||
|
|
||||||
|
events = []
|
||||||
|
time_format = '%a %d %b %Y %H:%M:%S %z'
|
||||||
|
for task in project_task_data:
|
||||||
|
due_date = datetime.strptime(task['due_date_utc'], time_format)
|
||||||
|
if due_date > start_date and due_date < end_date:
|
||||||
|
event = {
|
||||||
|
'uid': task['id'],
|
||||||
|
'title': task['content'],
|
||||||
|
'start': due_date.isoformat(),
|
||||||
|
'end': due_date.isoformat(),
|
||||||
|
'allDay': True,
|
||||||
|
}
|
||||||
|
events.append(event)
|
||||||
|
return events
|
||||||
|
|
||||||
@Throttle(MIN_TIME_BETWEEN_UPDATES)
|
@Throttle(MIN_TIME_BETWEEN_UPDATES)
|
||||||
def update(self):
|
def update(self):
|
||||||
"""Get the latest data."""
|
"""Get the latest data."""
|
||||||
|
@@ -1,4 +1,3 @@
|
|||||||
# pylint: disable=too-many-lines
|
|
||||||
"""
|
"""
|
||||||
Component to interface with cameras.
|
Component to interface with cameras.
|
||||||
|
|
||||||
@@ -97,6 +96,7 @@ def disable_motion_detection(hass, entity_id=None):
|
|||||||
|
|
||||||
|
|
||||||
@bind_hass
|
@bind_hass
|
||||||
|
@callback
|
||||||
def async_snapshot(hass, filename, entity_id=None):
|
def async_snapshot(hass, filename, entity_id=None):
|
||||||
"""Make a snapshot from a camera."""
|
"""Make a snapshot from a camera."""
|
||||||
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
|
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
|
||||||
@@ -129,8 +129,7 @@ async def async_get_image(hass, entity_id, timeout=10):
|
|||||||
raise HomeAssistantError('Unable to get image')
|
raise HomeAssistantError('Unable to get image')
|
||||||
|
|
||||||
|
|
||||||
@asyncio.coroutine
|
async def async_setup(hass, config):
|
||||||
def async_setup(hass, config):
|
|
||||||
"""Set up the camera component."""
|
"""Set up the camera component."""
|
||||||
component = hass.data[DOMAIN] = \
|
component = hass.data[DOMAIN] = \
|
||||||
EntityComponent(_LOGGER, DOMAIN, hass, SCAN_INTERVAL)
|
EntityComponent(_LOGGER, DOMAIN, hass, SCAN_INTERVAL)
|
||||||
@@ -142,7 +141,7 @@ def async_setup(hass, config):
|
|||||||
SCHEMA_WS_CAMERA_THUMBNAIL
|
SCHEMA_WS_CAMERA_THUMBNAIL
|
||||||
)
|
)
|
||||||
|
|
||||||
yield from component.async_setup(config)
|
await component.async_setup(config)
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def update_tokens(time):
|
def update_tokens(time):
|
||||||
@@ -154,27 +153,25 @@ def async_setup(hass, config):
|
|||||||
hass.helpers.event.async_track_time_interval(
|
hass.helpers.event.async_track_time_interval(
|
||||||
update_tokens, TOKEN_CHANGE_INTERVAL)
|
update_tokens, TOKEN_CHANGE_INTERVAL)
|
||||||
|
|
||||||
@asyncio.coroutine
|
async def async_handle_camera_service(service):
|
||||||
def async_handle_camera_service(service):
|
|
||||||
"""Handle calls to the camera services."""
|
"""Handle calls to the camera services."""
|
||||||
target_cameras = component.async_extract_from_service(service)
|
target_cameras = component.async_extract_from_service(service)
|
||||||
|
|
||||||
update_tasks = []
|
update_tasks = []
|
||||||
for camera in target_cameras:
|
for camera in target_cameras:
|
||||||
if service.service == SERVICE_ENABLE_MOTION:
|
if service.service == SERVICE_ENABLE_MOTION:
|
||||||
yield from camera.async_enable_motion_detection()
|
await camera.async_enable_motion_detection()
|
||||||
elif service.service == SERVICE_DISABLE_MOTION:
|
elif service.service == SERVICE_DISABLE_MOTION:
|
||||||
yield from camera.async_disable_motion_detection()
|
await camera.async_disable_motion_detection()
|
||||||
|
|
||||||
if not camera.should_poll:
|
if not camera.should_poll:
|
||||||
continue
|
continue
|
||||||
update_tasks.append(camera.async_update_ha_state(True))
|
update_tasks.append(camera.async_update_ha_state(True))
|
||||||
|
|
||||||
if update_tasks:
|
if update_tasks:
|
||||||
yield from asyncio.wait(update_tasks, loop=hass.loop)
|
await asyncio.wait(update_tasks, loop=hass.loop)
|
||||||
|
|
||||||
@asyncio.coroutine
|
async def async_handle_snapshot_service(service):
|
||||||
def async_handle_snapshot_service(service):
|
|
||||||
"""Handle snapshot services calls."""
|
"""Handle snapshot services calls."""
|
||||||
target_cameras = component.async_extract_from_service(service)
|
target_cameras = component.async_extract_from_service(service)
|
||||||
filename = service.data[ATTR_FILENAME]
|
filename = service.data[ATTR_FILENAME]
|
||||||
@@ -190,7 +187,7 @@ def async_setup(hass, config):
|
|||||||
"Can't write %s, no access to path!", snapshot_file)
|
"Can't write %s, no access to path!", snapshot_file)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
image = yield from camera.async_camera_image()
|
image = await camera.async_camera_image()
|
||||||
|
|
||||||
def _write_image(to_file, image_data):
|
def _write_image(to_file, image_data):
|
||||||
"""Executor helper to write image."""
|
"""Executor helper to write image."""
|
||||||
@@ -198,7 +195,7 @@ def async_setup(hass, config):
|
|||||||
img_file.write(image_data)
|
img_file.write(image_data)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
yield from hass.async_add_job(
|
await hass.async_add_job(
|
||||||
_write_image, snapshot_file, image)
|
_write_image, snapshot_file, image)
|
||||||
except OSError as err:
|
except OSError as err:
|
||||||
_LOGGER.error("Can't write image to file: %s", err)
|
_LOGGER.error("Can't write image to file: %s", err)
|
||||||
@@ -216,6 +213,16 @@ def async_setup(hass, config):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(hass, entry):
|
||||||
|
"""Setup a config entry."""
|
||||||
|
return await hass.data[DOMAIN].async_setup_entry(entry)
|
||||||
|
|
||||||
|
|
||||||
|
async def async_unload_entry(hass, entry):
|
||||||
|
"""Unload a config entry."""
|
||||||
|
return await hass.data[DOMAIN].async_unload_entry(entry)
|
||||||
|
|
||||||
|
|
||||||
class Camera(Entity):
|
class Camera(Entity):
|
||||||
"""The base class for camera entities."""
|
"""The base class for camera entities."""
|
||||||
|
|
||||||
@@ -265,6 +272,7 @@ class Camera(Entity):
|
|||||||
"""Return bytes of camera image."""
|
"""Return bytes of camera image."""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
@callback
|
||||||
def async_camera_image(self):
|
def async_camera_image(self):
|
||||||
"""Return bytes of camera image.
|
"""Return bytes of camera image.
|
||||||
|
|
||||||
@@ -388,8 +396,7 @@ class CameraView(HomeAssistantView):
|
|||||||
"""Initialize a basic camera view."""
|
"""Initialize a basic camera view."""
|
||||||
self.component = component
|
self.component = component
|
||||||
|
|
||||||
@asyncio.coroutine
|
async def get(self, request, entity_id):
|
||||||
def get(self, request, entity_id):
|
|
||||||
"""Start a GET request."""
|
"""Start a GET request."""
|
||||||
camera = self.component.get_entity(entity_id)
|
camera = self.component.get_entity(entity_id)
|
||||||
|
|
||||||
@@ -403,11 +410,10 @@ class CameraView(HomeAssistantView):
|
|||||||
if not authenticated:
|
if not authenticated:
|
||||||
return web.Response(status=401)
|
return web.Response(status=401)
|
||||||
|
|
||||||
response = yield from self.handle(request, camera)
|
response = await self.handle(request, camera)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
@asyncio.coroutine
|
async def handle(self, request, camera):
|
||||||
def handle(self, request, camera):
|
|
||||||
"""Handle the camera request."""
|
"""Handle the camera request."""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
@@ -418,12 +424,11 @@ class CameraImageView(CameraView):
|
|||||||
url = '/api/camera_proxy/{entity_id}'
|
url = '/api/camera_proxy/{entity_id}'
|
||||||
name = 'api:camera:image'
|
name = 'api:camera:image'
|
||||||
|
|
||||||
@asyncio.coroutine
|
async def handle(self, request, camera):
|
||||||
def handle(self, request, camera):
|
|
||||||
"""Serve camera image."""
|
"""Serve camera image."""
|
||||||
with suppress(asyncio.CancelledError, asyncio.TimeoutError):
|
with suppress(asyncio.CancelledError, asyncio.TimeoutError):
|
||||||
with async_timeout.timeout(10, loop=request.app['hass'].loop):
|
with async_timeout.timeout(10, loop=request.app['hass'].loop):
|
||||||
image = yield from camera.async_camera_image()
|
image = await camera.async_camera_image()
|
||||||
|
|
||||||
if image:
|
if image:
|
||||||
return web.Response(body=image,
|
return web.Response(body=image,
|
||||||
|
@@ -4,23 +4,22 @@ Support for Netgear Arlo IP cameras.
|
|||||||
For more details about this platform, please refer to the documentation at
|
For more details about this platform, please refer to the documentation at
|
||||||
https://home-assistant.io/components/camera.arlo/
|
https://home-assistant.io/components/camera.arlo/
|
||||||
"""
|
"""
|
||||||
import asyncio
|
|
||||||
import logging
|
import logging
|
||||||
from datetime import timedelta
|
|
||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from homeassistant.core import callback
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
from homeassistant.components.arlo import DEFAULT_BRAND, DATA_ARLO
|
from homeassistant.components.arlo import (
|
||||||
|
DEFAULT_BRAND, DATA_ARLO, SIGNAL_UPDATE_ARLO)
|
||||||
from homeassistant.components.camera import Camera, PLATFORM_SCHEMA
|
from homeassistant.components.camera import Camera, PLATFORM_SCHEMA
|
||||||
from homeassistant.components.ffmpeg import DATA_FFMPEG
|
from homeassistant.components.ffmpeg import DATA_FFMPEG
|
||||||
from homeassistant.const import ATTR_BATTERY_LEVEL
|
from homeassistant.const import ATTR_BATTERY_LEVEL
|
||||||
from homeassistant.helpers.aiohttp_client import async_aiohttp_proxy_stream
|
from homeassistant.helpers.aiohttp_client import async_aiohttp_proxy_stream
|
||||||
|
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
SCAN_INTERVAL = timedelta(seconds=90)
|
|
||||||
|
|
||||||
ARLO_MODE_ARMED = 'armed'
|
ARLO_MODE_ARMED = 'armed'
|
||||||
ARLO_MODE_DISARMED = 'disarmed'
|
ARLO_MODE_DISARMED = 'disarmed'
|
||||||
|
|
||||||
@@ -44,22 +43,19 @@ POWERSAVE_MODE_MAPPING = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||||
vol.Optional(CONF_FFMPEG_ARGUMENTS):
|
vol.Optional(CONF_FFMPEG_ARGUMENTS): cv.string,
|
||||||
cv.string,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
"""Set up an Arlo IP Camera."""
|
"""Set up an Arlo IP Camera."""
|
||||||
arlo = hass.data.get(DATA_ARLO)
|
arlo = hass.data[DATA_ARLO]
|
||||||
if not arlo:
|
|
||||||
return False
|
|
||||||
|
|
||||||
cameras = []
|
cameras = []
|
||||||
for camera in arlo.cameras:
|
for camera in arlo.cameras:
|
||||||
cameras.append(ArloCam(hass, camera, config))
|
cameras.append(ArloCam(hass, camera, config))
|
||||||
|
|
||||||
add_devices(cameras, True)
|
add_devices(cameras)
|
||||||
|
|
||||||
|
|
||||||
class ArloCam(Camera):
|
class ArloCam(Camera):
|
||||||
@@ -74,31 +70,41 @@ class ArloCam(Camera):
|
|||||||
self._ffmpeg = hass.data[DATA_FFMPEG]
|
self._ffmpeg = hass.data[DATA_FFMPEG]
|
||||||
self._ffmpeg_arguments = device_info.get(CONF_FFMPEG_ARGUMENTS)
|
self._ffmpeg_arguments = device_info.get(CONF_FFMPEG_ARGUMENTS)
|
||||||
self._last_refresh = None
|
self._last_refresh = None
|
||||||
if self._camera.base_station:
|
|
||||||
self._camera.base_station.refresh_rate = \
|
|
||||||
SCAN_INTERVAL.total_seconds()
|
|
||||||
self.attrs = {}
|
self.attrs = {}
|
||||||
|
|
||||||
def camera_image(self):
|
def camera_image(self):
|
||||||
"""Return a still image response from the camera."""
|
"""Return a still image response from the camera."""
|
||||||
return self._camera.last_image
|
return self._camera.last_image_from_cache
|
||||||
|
|
||||||
@asyncio.coroutine
|
async def async_added_to_hass(self):
|
||||||
def handle_async_mjpeg_stream(self, request):
|
"""Register callbacks."""
|
||||||
|
async_dispatcher_connect(
|
||||||
|
self.hass, SIGNAL_UPDATE_ARLO, self._update_callback)
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def _update_callback(self):
|
||||||
|
"""Call update method."""
|
||||||
|
self.async_schedule_update_ha_state()
|
||||||
|
|
||||||
|
async def handle_async_mjpeg_stream(self, request):
|
||||||
"""Generate an HTTP MJPEG stream from the camera."""
|
"""Generate an HTTP MJPEG stream from the camera."""
|
||||||
from haffmpeg import CameraMjpeg
|
from haffmpeg import CameraMjpeg
|
||||||
video = self._camera.last_video
|
video = self._camera.last_video
|
||||||
if not video:
|
if not video:
|
||||||
|
error_msg = \
|
||||||
|
'Video not found for {0}. Is it older than {1} days?'.format(
|
||||||
|
self.name, self._camera.min_days_vdo_cache)
|
||||||
|
_LOGGER.error(error_msg)
|
||||||
return
|
return
|
||||||
|
|
||||||
stream = CameraMjpeg(self._ffmpeg.binary, loop=self.hass.loop)
|
stream = CameraMjpeg(self._ffmpeg.binary, loop=self.hass.loop)
|
||||||
yield from stream.open_camera(
|
await stream.open_camera(
|
||||||
video.video_url, extra_cmd=self._ffmpeg_arguments)
|
video.video_url, extra_cmd=self._ffmpeg_arguments)
|
||||||
|
|
||||||
yield from async_aiohttp_proxy_stream(
|
await async_aiohttp_proxy_stream(
|
||||||
self.hass, request, stream,
|
self.hass, request, stream,
|
||||||
'multipart/x-mixed-replace;boundary=ffserver')
|
'multipart/x-mixed-replace;boundary=ffserver')
|
||||||
yield from stream.close()
|
await stream.close()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
@@ -132,11 +138,6 @@ class ArloCam(Camera):
|
|||||||
"""Return the camera brand."""
|
"""Return the camera brand."""
|
||||||
return DEFAULT_BRAND
|
return DEFAULT_BRAND
|
||||||
|
|
||||||
@property
|
|
||||||
def should_poll(self):
|
|
||||||
"""Camera should poll periodically."""
|
|
||||||
return True
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def motion_detection_enabled(self):
|
def motion_detection_enabled(self):
|
||||||
"""Return the camera motion detection status."""
|
"""Return the camera motion detection status."""
|
||||||
@@ -164,7 +165,3 @@ class ArloCam(Camera):
|
|||||||
"""Disable the motion detection in base station (Disarm)."""
|
"""Disable the motion detection in base station (Disarm)."""
|
||||||
self._motion_status = False
|
self._motion_status = False
|
||||||
self.set_base_station_mode(ARLO_MODE_DISARMED)
|
self.set_base_station_mode(ARLO_MODE_DISARMED)
|
||||||
|
|
||||||
def update(self):
|
|
||||||
"""Add an attribute-update task to the executor pool."""
|
|
||||||
self._camera.update()
|
|
||||||
|
@@ -13,7 +13,6 @@ from homeassistant.components.camera import Camera
|
|||||||
DEPENDENCIES = ['bloomsky']
|
DEPENDENCIES = ['bloomsky']
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=unused-argument
|
|
||||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
"""Set up access to BloomSky cameras."""
|
"""Set up access to BloomSky cameras."""
|
||||||
bloomsky = hass.components.bloomsky
|
bloomsky = hass.components.bloomsky
|
||||||
|
@@ -12,9 +12,10 @@ from homeassistant.components.camera import Camera
|
|||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
async def async_setup_platform(hass, config, async_add_devices,
|
||||||
|
discovery_info=None):
|
||||||
"""Set up the Demo camera platform."""
|
"""Set up the Demo camera platform."""
|
||||||
add_devices([
|
async_add_devices([
|
||||||
DemoCamera(hass, config, 'Demo camera')
|
DemoCamera(hass, config, 'Demo camera')
|
||||||
])
|
])
|
||||||
|
|
||||||
|
@@ -17,9 +17,9 @@ from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
|||||||
|
|
||||||
DEPENDENCIES = ['doorbird']
|
DEPENDENCIES = ['doorbird']
|
||||||
|
|
||||||
_CAMERA_LAST_VISITOR = "DoorBird Last Ring"
|
_CAMERA_LAST_VISITOR = "{} Last Ring"
|
||||||
_CAMERA_LAST_MOTION = "DoorBird Last Motion"
|
_CAMERA_LAST_MOTION = "{} Last Motion"
|
||||||
_CAMERA_LIVE = "DoorBird Live"
|
_CAMERA_LIVE = "{} Live"
|
||||||
_LAST_VISITOR_INTERVAL = datetime.timedelta(minutes=1)
|
_LAST_VISITOR_INTERVAL = datetime.timedelta(minutes=1)
|
||||||
_LAST_MOTION_INTERVAL = datetime.timedelta(minutes=1)
|
_LAST_MOTION_INTERVAL = datetime.timedelta(minutes=1)
|
||||||
_LIVE_INTERVAL = datetime.timedelta(seconds=1)
|
_LIVE_INTERVAL = datetime.timedelta(seconds=1)
|
||||||
@@ -30,14 +30,20 @@ _TIMEOUT = 10 # seconds
|
|||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
|
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
|
||||||
"""Set up the DoorBird camera platform."""
|
"""Set up the DoorBird camera platform."""
|
||||||
device = hass.data.get(DOORBIRD_DOMAIN)
|
for doorstation in hass.data[DOORBIRD_DOMAIN]:
|
||||||
|
device = doorstation.device
|
||||||
async_add_devices([
|
async_add_devices([
|
||||||
DoorBirdCamera(device.live_image_url, _CAMERA_LIVE, _LIVE_INTERVAL),
|
|
||||||
DoorBirdCamera(
|
DoorBirdCamera(
|
||||||
device.history_image_url(1, 'doorbell'), _CAMERA_LAST_VISITOR,
|
device.live_image_url,
|
||||||
|
_CAMERA_LIVE.format(doorstation.name),
|
||||||
|
_LIVE_INTERVAL),
|
||||||
|
DoorBirdCamera(
|
||||||
|
device.history_image_url(1, 'doorbell'),
|
||||||
|
_CAMERA_LAST_VISITOR.format(doorstation.name),
|
||||||
_LAST_VISITOR_INTERVAL),
|
_LAST_VISITOR_INTERVAL),
|
||||||
DoorBirdCamera(
|
DoorBirdCamera(
|
||||||
device.history_image_url(1, 'motionsensor'), _CAMERA_LAST_MOTION,
|
device.history_image_url(1, 'motionsensor'),
|
||||||
|
_CAMERA_LAST_MOTION.format(doorstation.name),
|
||||||
_LAST_MOTION_INTERVAL),
|
_LAST_MOTION_INTERVAL),
|
||||||
])
|
])
|
||||||
|
|
||||||
|
@@ -33,7 +33,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
|||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=unused-argument
|
|
||||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
"""Set up a Foscam IP Camera."""
|
"""Set up a Foscam IP Camera."""
|
||||||
add_devices([FoscamCam(config)])
|
add_devices([FoscamCam(config)])
|
||||||
|
@@ -46,7 +46,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
|||||||
|
|
||||||
|
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
# pylint: disable=unused-argument
|
|
||||||
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
|
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
|
||||||
"""Set up a generic IP Camera."""
|
"""Set up a generic IP Camera."""
|
||||||
async_add_devices([GenericCamera(hass, config)])
|
async_add_devices([GenericCamera(hass, config)])
|
||||||
|
@@ -42,7 +42,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
|||||||
|
|
||||||
|
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
# pylint: disable=unused-argument
|
|
||||||
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
|
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
|
||||||
"""Set up a MJPEG IP Camera."""
|
"""Set up a MJPEG IP Camera."""
|
||||||
if discovery_info:
|
if discovery_info:
|
||||||
|
@@ -45,7 +45,7 @@ class NeatoCleaningMap(Camera):
|
|||||||
self.update()
|
self.update()
|
||||||
return self._image
|
return self._image
|
||||||
|
|
||||||
@Throttle(timedelta(seconds=10))
|
@Throttle(timedelta(seconds=60))
|
||||||
def update(self):
|
def update(self):
|
||||||
"""Check the contents of the map list."""
|
"""Check the contents of the map list."""
|
||||||
self.neato.update_robots()
|
self.neato.update_robots()
|
||||||
|
@@ -23,14 +23,19 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({})
|
|||||||
|
|
||||||
|
|
||||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
"""Set up a Nest Cam."""
|
"""Set up a Nest Cam.
|
||||||
if discovery_info is None:
|
|
||||||
return
|
|
||||||
|
|
||||||
camera_devices = hass.data[nest.DATA_NEST].cameras()
|
No longer in use.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(hass, entry, async_add_devices):
|
||||||
|
"""Set up a Nest sensor based on a config entry."""
|
||||||
|
camera_devices = \
|
||||||
|
await hass.async_add_job(hass.data[nest.DATA_NEST].cameras)
|
||||||
cameras = [NestCamera(structure, device)
|
cameras = [NestCamera(structure, device)
|
||||||
for structure, device in camera_devices]
|
for structure, device in camera_devices]
|
||||||
add_devices(cameras, True)
|
async_add_devices(cameras, True)
|
||||||
|
|
||||||
|
|
||||||
class NestCamera(Camera):
|
class NestCamera(Camera):
|
||||||
|
@@ -29,13 +29,12 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
|||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=unused-argument
|
|
||||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
"""Set up access to Netatmo cameras."""
|
"""Set up access to Netatmo cameras."""
|
||||||
netatmo = hass.components.netatmo
|
netatmo = hass.components.netatmo
|
||||||
home = config.get(CONF_HOME)
|
home = config.get(CONF_HOME)
|
||||||
verify_ssl = config.get(CONF_VERIFY_SSL, True)
|
verify_ssl = config.get(CONF_VERIFY_SSL, True)
|
||||||
import lnetatmo
|
import pyatmo
|
||||||
try:
|
try:
|
||||||
data = CameraData(netatmo.NETATMO_AUTH, home)
|
data = CameraData(netatmo.NETATMO_AUTH, home)
|
||||||
for camera_name in data.get_camera_names():
|
for camera_name in data.get_camera_names():
|
||||||
@@ -46,7 +45,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||||||
continue
|
continue
|
||||||
add_devices([NetatmoCamera(data, camera_name, home,
|
add_devices([NetatmoCamera(data, camera_name, home,
|
||||||
camera_type, verify_ssl)])
|
camera_type, verify_ssl)])
|
||||||
except lnetatmo.NoDevice:
|
except pyatmo.NoDevice:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
@@ -13,6 +13,7 @@ import voluptuous as vol
|
|||||||
from homeassistant.const import CONF_PORT
|
from homeassistant.const import CONF_PORT
|
||||||
from homeassistant.components.camera import Camera, PLATFORM_SCHEMA
|
from homeassistant.components.camera import Camera, PLATFORM_SCHEMA
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
from homeassistant.exceptions import PlatformNotReady
|
||||||
|
|
||||||
REQUIREMENTS = ['uvcclient==0.10.1']
|
REQUIREMENTS = ['uvcclient==0.10.1']
|
||||||
|
|
||||||
@@ -41,18 +42,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||||||
port = config[CONF_PORT]
|
port = config[CONF_PORT]
|
||||||
|
|
||||||
from uvcclient import nvr
|
from uvcclient import nvr
|
||||||
nvrconn = nvr.UVCRemote(addr, port, key)
|
|
||||||
try:
|
try:
|
||||||
|
# Exceptions may be raised in all method calls to the nvr library.
|
||||||
|
nvrconn = nvr.UVCRemote(addr, port, key)
|
||||||
cameras = nvrconn.index()
|
cameras = nvrconn.index()
|
||||||
except nvr.NotAuthorized:
|
|
||||||
_LOGGER.error("Authorization failure while connecting to NVR")
|
|
||||||
return False
|
|
||||||
except nvr.NvrError:
|
|
||||||
_LOGGER.error("NVR refuses to talk to me")
|
|
||||||
return False
|
|
||||||
except requests.exceptions.ConnectionError as ex:
|
|
||||||
_LOGGER.error("Unable to connect to NVR: %s", str(ex))
|
|
||||||
return False
|
|
||||||
|
|
||||||
identifier = 'id' if nvrconn.server_version >= (3, 2, 0) else 'uuid'
|
identifier = 'id' if nvrconn.server_version >= (3, 2, 0) else 'uuid'
|
||||||
# Filter out airCam models, which are not supported in the latest
|
# Filter out airCam models, which are not supported in the latest
|
||||||
@@ -60,6 +53,15 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||||||
cameras = [
|
cameras = [
|
||||||
camera for camera in cameras
|
camera for camera in cameras
|
||||||
if 'airCam' not in nvrconn.get_camera(camera[identifier])['model']]
|
if 'airCam' not in nvrconn.get_camera(camera[identifier])['model']]
|
||||||
|
except nvr.NotAuthorized:
|
||||||
|
_LOGGER.error("Authorization failure while connecting to NVR")
|
||||||
|
return False
|
||||||
|
except nvr.NvrError as ex:
|
||||||
|
_LOGGER.error("NVR refuses to talk to me: %s", str(ex))
|
||||||
|
raise PlatformNotReady
|
||||||
|
except requests.exceptions.ConnectionError as ex:
|
||||||
|
_LOGGER.error("Unable to connect to NVR: %s", str(ex))
|
||||||
|
raise PlatformNotReady
|
||||||
|
|
||||||
add_devices([UnifiVideoCamera(nvrconn,
|
add_devices([UnifiVideoCamera(nvrconn,
|
||||||
camera[identifier],
|
camera[identifier],
|
||||||
|
166
homeassistant/components/camera/xiaomi.py
Normal file
166
homeassistant/components/camera/xiaomi.py
Normal 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()
|
@@ -11,11 +11,13 @@ import voluptuous as vol
|
|||||||
|
|
||||||
from homeassistant.components.camera import Camera, PLATFORM_SCHEMA
|
from homeassistant.components.camera import Camera, PLATFORM_SCHEMA
|
||||||
from homeassistant.components.ffmpeg import DATA_FFMPEG
|
from homeassistant.components.ffmpeg import DATA_FFMPEG
|
||||||
from homeassistant.const import (CONF_HOST, CONF_NAME, CONF_PATH,
|
from homeassistant.const import (
|
||||||
CONF_PASSWORD, CONF_PORT, CONF_USERNAME)
|
CONF_HOST, CONF_NAME, CONF_PATH, CONF_PASSWORD, CONF_PORT, CONF_USERNAME)
|
||||||
from homeassistant.helpers import config_validation as cv
|
from homeassistant.helpers import config_validation as cv
|
||||||
from homeassistant.helpers.aiohttp_client import async_aiohttp_proxy_stream
|
from homeassistant.helpers.aiohttp_client import async_aiohttp_proxy_stream
|
||||||
|
from homeassistant.exceptions import PlatformNotReady
|
||||||
|
|
||||||
|
REQUIREMENTS = ['aioftp==0.10.1']
|
||||||
DEPENDENCIES = ['ffmpeg']
|
DEPENDENCIES = ['ffmpeg']
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -38,12 +40,9 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
|||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_platform(hass,
|
async def async_setup_platform(
|
||||||
config,
|
hass, config, async_add_devices, discovery_info=None):
|
||||||
async_add_devices,
|
|
||||||
discovery_info=None):
|
|
||||||
"""Set up a Yi Camera."""
|
"""Set up a Yi Camera."""
|
||||||
_LOGGER.debug('Received configuration: %s', config)
|
|
||||||
async_add_devices([YiCamera(hass, config)], True)
|
async_add_devices([YiCamera(hass, config)], True)
|
||||||
|
|
||||||
|
|
||||||
@@ -54,71 +53,81 @@ class YiCamera(Camera):
|
|||||||
"""Initialize."""
|
"""Initialize."""
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self._extra_arguments = config.get(CONF_FFMPEG_ARGUMENTS)
|
self._extra_arguments = config.get(CONF_FFMPEG_ARGUMENTS)
|
||||||
|
self._ftp = None
|
||||||
self._last_image = None
|
self._last_image = None
|
||||||
self._last_url = None
|
self._last_url = None
|
||||||
self._manager = hass.data[DATA_FFMPEG]
|
self._manager = hass.data[DATA_FFMPEG]
|
||||||
self._name = config.get(CONF_NAME)
|
self._name = config[CONF_NAME]
|
||||||
self.host = config.get(CONF_HOST)
|
self.host = config[CONF_HOST]
|
||||||
self.port = config.get(CONF_PORT)
|
self.port = config[CONF_PORT]
|
||||||
self.path = config.get(CONF_PATH)
|
self.path = config[CONF_PATH]
|
||||||
self.user = config.get(CONF_USERNAME)
|
self.user = config[CONF_USERNAME]
|
||||||
self.passwd = config.get(CONF_PASSWORD)
|
self.passwd = config[CONF_PASSWORD]
|
||||||
|
|
||||||
@property
|
hass.async_add_job(self._connect_to_client)
|
||||||
def name(self):
|
|
||||||
"""Return the name of this camera."""
|
|
||||||
return self._name
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def brand(self):
|
def brand(self):
|
||||||
"""Camera brand."""
|
"""Camera brand."""
|
||||||
return DEFAULT_BRAND
|
return DEFAULT_BRAND
|
||||||
|
|
||||||
def get_latest_video_url(self):
|
@property
|
||||||
|
def name(self):
|
||||||
|
"""Return the name of this camera."""
|
||||||
|
return self._name
|
||||||
|
|
||||||
|
async def _connect_to_client(self):
|
||||||
|
"""Attempt to establish a connection via FTP."""
|
||||||
|
from aioftp import Client, StatusCodeError
|
||||||
|
|
||||||
|
ftp = Client()
|
||||||
|
try:
|
||||||
|
await ftp.connect(self.host)
|
||||||
|
await ftp.login(self.user, self.passwd)
|
||||||
|
self._ftp = ftp
|
||||||
|
except StatusCodeError as err:
|
||||||
|
raise PlatformNotReady(err)
|
||||||
|
|
||||||
|
async def _get_latest_video_url(self):
|
||||||
"""Retrieve the latest video file from the customized Yi FTP server."""
|
"""Retrieve the latest video file from the customized Yi FTP server."""
|
||||||
from ftplib import FTP, error_perm
|
from aioftp import StatusCodeError
|
||||||
|
|
||||||
ftp = FTP(self.host)
|
|
||||||
try:
|
|
||||||
ftp.login(self.user, self.passwd)
|
|
||||||
except error_perm as exc:
|
|
||||||
_LOGGER.error('There was an error while logging into the camera')
|
|
||||||
_LOGGER.debug(exc)
|
|
||||||
return False
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
ftp.cwd(self.path)
|
await self._ftp.change_directory(self.path)
|
||||||
except error_perm as exc:
|
dirs = []
|
||||||
_LOGGER.error('Unable to find path: %s', self.path)
|
for path, attrs in await self._ftp.list():
|
||||||
_LOGGER.debug(exc)
|
if attrs['type'] == 'dir' and '.' not in str(path):
|
||||||
return False
|
dirs.append(path)
|
||||||
|
|
||||||
dirs = [d for d in ftp.nlst() if '.' not in d]
|
|
||||||
if not dirs:
|
|
||||||
_LOGGER.warning("There don't appear to be any uploaded videos")
|
|
||||||
return False
|
|
||||||
|
|
||||||
latest_dir = dirs[-1]
|
latest_dir = dirs[-1]
|
||||||
ftp.cwd(latest_dir)
|
await self._ftp.change_directory(latest_dir)
|
||||||
videos = ftp.nlst()
|
|
||||||
|
videos = []
|
||||||
|
for path, _ in await self._ftp.list():
|
||||||
|
videos.append(path)
|
||||||
if not videos:
|
if not videos:
|
||||||
_LOGGER.info('Video folder "%s" is empty; delaying', latest_dir)
|
_LOGGER.info('Video folder "%s" empty; delaying', latest_dir)
|
||||||
return False
|
return None
|
||||||
|
|
||||||
return 'ftp://{0}:{1}@{2}:{3}{4}/{5}/{6}'.format(
|
return 'ftp://{0}:{1}@{2}:{3}{4}/{5}/{6}'.format(
|
||||||
self.user, self.passwd, self.host, self.port, self.path,
|
self.user, self.passwd, self.host, self.port, self.path,
|
||||||
latest_dir, videos[-1])
|
latest_dir, videos[-1])
|
||||||
|
except (ConnectionRefusedError, StatusCodeError) as err:
|
||||||
|
_LOGGER.error('Error while fetching video: %s', err)
|
||||||
|
return None
|
||||||
|
|
||||||
async def async_camera_image(self):
|
async def async_camera_image(self):
|
||||||
"""Return a still image response from the camera."""
|
"""Return a still image response from the camera."""
|
||||||
from haffmpeg import ImageFrame, IMAGE_JPEG
|
from haffmpeg import ImageFrame, IMAGE_JPEG
|
||||||
|
|
||||||
url = await self.hass.async_add_job(self.get_latest_video_url)
|
url = await self._get_latest_video_url()
|
||||||
if url != self._last_url:
|
if url != self._last_url:
|
||||||
ffmpeg = ImageFrame(self._manager.binary, loop=self.hass.loop)
|
ffmpeg = ImageFrame(self._manager.binary, loop=self.hass.loop)
|
||||||
self._last_image = await asyncio.shield(ffmpeg.get_image(
|
self._last_image = await asyncio.shield(
|
||||||
url, output_format=IMAGE_JPEG,
|
ffmpeg.get_image(
|
||||||
extra_cmd=self._extra_arguments), loop=self.hass.loop)
|
url,
|
||||||
|
output_format=IMAGE_JPEG,
|
||||||
|
extra_cmd=self._extra_arguments),
|
||||||
|
loop=self.hass.loop)
|
||||||
self._last_url = url
|
self._last_url = url
|
||||||
|
|
||||||
return self._last_image
|
return self._last_image
|
||||||
|
@@ -49,7 +49,6 @@ def _get_image_url(hass, monitor, mode):
|
|||||||
|
|
||||||
|
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
# pylint: disable=unused-argument
|
|
||||||
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
|
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
|
||||||
"""Set up the ZoneMinder cameras."""
|
"""Set up the ZoneMinder cameras."""
|
||||||
cameras = []
|
cameras = []
|
||||||
|
15
homeassistant/components/cast/.translations/ca.json
Normal file
15
homeassistant/components/cast/.translations/ca.json
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
15
homeassistant/components/cast/.translations/en.json
Normal file
15
homeassistant/components/cast/.translations/en.json
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
15
homeassistant/components/cast/.translations/ko.json
Normal file
15
homeassistant/components/cast/.translations/ko.json
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
15
homeassistant/components/cast/.translations/no.json
Normal file
15
homeassistant/components/cast/.translations/no.json
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
15
homeassistant/components/cast/.translations/pl.json
Normal file
15
homeassistant/components/cast/.translations/pl.json
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
15
homeassistant/components/cast/.translations/ru.json
Normal file
15
homeassistant/components/cast/.translations/ru.json
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
15
homeassistant/components/cast/.translations/sv.json
Normal file
15
homeassistant/components/cast/.translations/sv.json
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
15
homeassistant/components/cast/.translations/vi.json
Normal file
15
homeassistant/components/cast/.translations/vi.json
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
15
homeassistant/components/cast/.translations/zh-Hans.json
Normal file
15
homeassistant/components/cast/.translations/zh-Hans.json
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
30
homeassistant/components/cast/__init__.py
Normal file
30
homeassistant/components/cast/__init__.py
Normal 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)
|
15
homeassistant/components/cast/strings.json
Normal file
15
homeassistant/components/cast/strings.json
Normal 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."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -246,7 +246,8 @@ def set_swing_mode(hass, swing_mode, entity_id=None):
|
|||||||
|
|
||||||
async def async_setup(hass, config):
|
async def async_setup(hass, config):
|
||||||
"""Set up climate devices."""
|
"""Set up climate devices."""
|
||||||
component = EntityComponent(_LOGGER, DOMAIN, hass, SCAN_INTERVAL)
|
component = hass.data[DOMAIN] = \
|
||||||
|
EntityComponent(_LOGGER, DOMAIN, hass, SCAN_INTERVAL)
|
||||||
await component.async_setup(config)
|
await component.async_setup(config)
|
||||||
|
|
||||||
async def async_away_mode_set_service(service):
|
async def async_away_mode_set_service(service):
|
||||||
@@ -456,6 +457,16 @@ async def async_setup(hass, config):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(hass, entry):
|
||||||
|
"""Setup a config entry."""
|
||||||
|
return await hass.data[DOMAIN].async_setup_entry(entry)
|
||||||
|
|
||||||
|
|
||||||
|
async def async_unload_entry(hass, entry):
|
||||||
|
"""Unload a config entry."""
|
||||||
|
return await hass.data[DOMAIN].async_unload_entry(entry)
|
||||||
|
|
||||||
|
|
||||||
class ClimateDevice(Entity):
|
class ClimateDevice(Entity):
|
||||||
"""Representation of a climate device."""
|
"""Representation of a climate device."""
|
||||||
|
|
||||||
|
@@ -13,21 +13,27 @@ from homeassistant.components.fritzbox import (
|
|||||||
ATTR_STATE_DEVICE_LOCKED, ATTR_STATE_BATTERY_LOW, ATTR_STATE_LOCKED)
|
ATTR_STATE_DEVICE_LOCKED, ATTR_STATE_BATTERY_LOW, ATTR_STATE_LOCKED)
|
||||||
from homeassistant.components.climate import (
|
from homeassistant.components.climate import (
|
||||||
ATTR_OPERATION_MODE, ClimateDevice, STATE_ECO, STATE_HEAT, STATE_MANUAL,
|
ATTR_OPERATION_MODE, ClimateDevice, STATE_ECO, STATE_HEAT, STATE_MANUAL,
|
||||||
SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE)
|
STATE_OFF, STATE_ON, SUPPORT_OPERATION_MODE,
|
||||||
|
SUPPORT_TARGET_TEMPERATURE)
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_TEMPERATURE, PRECISION_HALVES, TEMP_CELSIUS)
|
ATTR_TEMPERATURE, PRECISION_HALVES, TEMP_CELSIUS)
|
||||||
|
|
||||||
DEPENDENCIES = ['fritzbox']
|
DEPENDENCIES = ['fritzbox']
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE)
|
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE)
|
||||||
|
|
||||||
OPERATION_LIST = [STATE_HEAT, STATE_ECO]
|
OPERATION_LIST = [STATE_HEAT, STATE_ECO, STATE_OFF, STATE_ON]
|
||||||
|
|
||||||
MIN_TEMPERATURE = 8
|
MIN_TEMPERATURE = 8
|
||||||
MAX_TEMPERATURE = 28
|
MAX_TEMPERATURE = 28
|
||||||
|
|
||||||
|
# special temperatures for on/off in Fritz!Box API (modified by pyfritzhome)
|
||||||
|
ON_API_TEMPERATURE = 127.0
|
||||||
|
OFF_API_TEMPERATURE = 126.5
|
||||||
|
ON_REPORT_SET_TEMPERATURE = 30.0
|
||||||
|
OFF_REPORT_SET_TEMPERATURE = 0.0
|
||||||
|
|
||||||
|
|
||||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
"""Set up the Fritzbox smarthome thermostat platform."""
|
"""Set up the Fritzbox smarthome thermostat platform."""
|
||||||
@@ -88,6 +94,9 @@ class FritzboxThermostat(ClimateDevice):
|
|||||||
@property
|
@property
|
||||||
def target_temperature(self):
|
def target_temperature(self):
|
||||||
"""Return the temperature we try to reach."""
|
"""Return the temperature we try to reach."""
|
||||||
|
if self._target_temperature in (ON_API_TEMPERATURE,
|
||||||
|
OFF_API_TEMPERATURE):
|
||||||
|
return None
|
||||||
return self._target_temperature
|
return self._target_temperature
|
||||||
|
|
||||||
def set_temperature(self, **kwargs):
|
def set_temperature(self, **kwargs):
|
||||||
@@ -102,9 +111,13 @@ class FritzboxThermostat(ClimateDevice):
|
|||||||
@property
|
@property
|
||||||
def current_operation(self):
|
def current_operation(self):
|
||||||
"""Return the current operation mode."""
|
"""Return the current operation mode."""
|
||||||
|
if self._target_temperature == ON_API_TEMPERATURE:
|
||||||
|
return STATE_ON
|
||||||
|
if self._target_temperature == OFF_API_TEMPERATURE:
|
||||||
|
return STATE_OFF
|
||||||
if self._target_temperature == self._comfort_temperature:
|
if self._target_temperature == self._comfort_temperature:
|
||||||
return STATE_HEAT
|
return STATE_HEAT
|
||||||
elif self._target_temperature == self._eco_temperature:
|
if self._target_temperature == self._eco_temperature:
|
||||||
return STATE_ECO
|
return STATE_ECO
|
||||||
return STATE_MANUAL
|
return STATE_MANUAL
|
||||||
|
|
||||||
@@ -119,6 +132,10 @@ class FritzboxThermostat(ClimateDevice):
|
|||||||
self.set_temperature(temperature=self._comfort_temperature)
|
self.set_temperature(temperature=self._comfort_temperature)
|
||||||
elif operation_mode == STATE_ECO:
|
elif operation_mode == STATE_ECO:
|
||||||
self.set_temperature(temperature=self._eco_temperature)
|
self.set_temperature(temperature=self._eco_temperature)
|
||||||
|
elif operation_mode == STATE_OFF:
|
||||||
|
self.set_temperature(temperature=OFF_REPORT_SET_TEMPERATURE)
|
||||||
|
elif operation_mode == STATE_ON:
|
||||||
|
self.set_temperature(temperature=ON_REPORT_SET_TEMPERATURE)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def min_temp(self):
|
def min_temp(self):
|
||||||
|
@@ -14,8 +14,7 @@ from homeassistant.core import DOMAIN as HA_DOMAIN
|
|||||||
from homeassistant.components.climate import (
|
from homeassistant.components.climate import (
|
||||||
STATE_HEAT, STATE_COOL, STATE_IDLE, STATE_AUTO, ClimateDevice,
|
STATE_HEAT, STATE_COOL, STATE_IDLE, STATE_AUTO, ClimateDevice,
|
||||||
ATTR_OPERATION_MODE, ATTR_AWAY_MODE, SUPPORT_OPERATION_MODE,
|
ATTR_OPERATION_MODE, ATTR_AWAY_MODE, SUPPORT_OPERATION_MODE,
|
||||||
SUPPORT_AWAY_MODE, SUPPORT_TARGET_TEMPERATURE, PLATFORM_SCHEMA,
|
SUPPORT_AWAY_MODE, SUPPORT_TARGET_TEMPERATURE, PLATFORM_SCHEMA)
|
||||||
DEFAULT_MIN_TEMP, DEFAULT_MAX_TEMP)
|
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_UNIT_OF_MEASUREMENT, STATE_ON, STATE_OFF, ATTR_TEMPERATURE,
|
ATTR_UNIT_OF_MEASUREMENT, STATE_ON, STATE_OFF, ATTR_TEMPERATURE,
|
||||||
CONF_NAME, ATTR_ENTITY_ID, SERVICE_TURN_ON, SERVICE_TURN_OFF,
|
CONF_NAME, ATTR_ENTITY_ID, SERVICE_TURN_ON, SERVICE_TURN_OFF,
|
||||||
@@ -268,7 +267,8 @@ class GenericThermostat(ClimateDevice):
|
|||||||
if self._min_temp:
|
if self._min_temp:
|
||||||
return self._min_temp
|
return self._min_temp
|
||||||
|
|
||||||
return DEFAULT_MIN_TEMP
|
# get default temp from super class
|
||||||
|
return super().min_temp
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def max_temp(self):
|
def max_temp(self):
|
||||||
@@ -277,7 +277,8 @@ class GenericThermostat(ClimateDevice):
|
|||||||
if self._max_temp:
|
if self._max_temp:
|
||||||
return self._max_temp
|
return self._max_temp
|
||||||
|
|
||||||
return DEFAULT_MAX_TEMP
|
# Get default temp from super class
|
||||||
|
return super().max_temp
|
||||||
|
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def _async_sensor_changed(self, entity_id, old_state, new_state):
|
def _async_sensor_changed(self, entity_id, old_state, new_state):
|
||||||
|
@@ -136,7 +136,6 @@ class KNXClimate(ClimateDevice):
|
|||||||
"""Register callbacks to update hass after device was changed."""
|
"""Register callbacks to update hass after device was changed."""
|
||||||
async def after_update_callback(device):
|
async def after_update_callback(device):
|
||||||
"""Call after device was updated."""
|
"""Call after device was updated."""
|
||||||
# pylint: disable=unused-argument
|
|
||||||
await self.async_update_ha_state()
|
await self.async_update_ha_state()
|
||||||
self.device.register_device_updated_cb(after_update_callback)
|
self.device.register_device_updated_cb(after_update_callback)
|
||||||
|
|
||||||
|
@@ -17,7 +17,7 @@ from homeassistant.components.climate import (
|
|||||||
PLATFORM_SCHEMA as CLIMATE_PLATFORM_SCHEMA, STATE_AUTO,
|
PLATFORM_SCHEMA as CLIMATE_PLATFORM_SCHEMA, STATE_AUTO,
|
||||||
ATTR_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE, SUPPORT_OPERATION_MODE,
|
ATTR_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE, SUPPORT_OPERATION_MODE,
|
||||||
SUPPORT_SWING_MODE, SUPPORT_FAN_MODE, SUPPORT_AWAY_MODE, SUPPORT_HOLD_MODE,
|
SUPPORT_SWING_MODE, SUPPORT_FAN_MODE, SUPPORT_AWAY_MODE, SUPPORT_HOLD_MODE,
|
||||||
SUPPORT_AUX_HEAT)
|
SUPPORT_AUX_HEAT, DEFAULT_MIN_TEMP, DEFAULT_MAX_TEMP)
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
STATE_ON, STATE_OFF, ATTR_TEMPERATURE, CONF_NAME, CONF_VALUE_TEMPLATE)
|
STATE_ON, STATE_OFF, ATTR_TEMPERATURE, CONF_NAME, CONF_VALUE_TEMPLATE)
|
||||||
from homeassistant.components.mqtt import (
|
from homeassistant.components.mqtt import (
|
||||||
@@ -70,6 +70,9 @@ CONF_SWING_MODE_LIST = 'swing_modes'
|
|||||||
CONF_INITIAL = 'initial'
|
CONF_INITIAL = 'initial'
|
||||||
CONF_SEND_IF_OFF = 'send_if_off'
|
CONF_SEND_IF_OFF = 'send_if_off'
|
||||||
|
|
||||||
|
CONF_MIN_TEMP = 'min_temp'
|
||||||
|
CONF_MAX_TEMP = 'max_temp'
|
||||||
|
|
||||||
SCHEMA_BASE = CLIMATE_PLATFORM_SCHEMA.extend(MQTT_BASE_PLATFORM_SCHEMA.schema)
|
SCHEMA_BASE = CLIMATE_PLATFORM_SCHEMA.extend(MQTT_BASE_PLATFORM_SCHEMA.schema)
|
||||||
PLATFORM_SCHEMA = SCHEMA_BASE.extend({
|
PLATFORM_SCHEMA = SCHEMA_BASE.extend({
|
||||||
vol.Optional(CONF_POWER_COMMAND_TOPIC): mqtt.valid_publish_topic,
|
vol.Optional(CONF_POWER_COMMAND_TOPIC): mqtt.valid_publish_topic,
|
||||||
@@ -116,6 +119,10 @@ PLATFORM_SCHEMA = SCHEMA_BASE.extend({
|
|||||||
vol.Optional(CONF_SEND_IF_OFF, default=True): cv.boolean,
|
vol.Optional(CONF_SEND_IF_OFF, default=True): cv.boolean,
|
||||||
vol.Optional(CONF_PAYLOAD_ON, default="ON"): cv.string,
|
vol.Optional(CONF_PAYLOAD_ON, default="ON"): cv.string,
|
||||||
vol.Optional(CONF_PAYLOAD_OFF, default="OFF"): cv.string,
|
vol.Optional(CONF_PAYLOAD_OFF, default="OFF"): cv.string,
|
||||||
|
|
||||||
|
vol.Optional(CONF_MIN_TEMP, default=DEFAULT_MIN_TEMP): vol.Coerce(float),
|
||||||
|
vol.Optional(CONF_MAX_TEMP, default=DEFAULT_MAX_TEMP): vol.Coerce(float)
|
||||||
|
|
||||||
}).extend(mqtt.MQTT_AVAILABILITY_SCHEMA.schema)
|
}).extend(mqtt.MQTT_AVAILABILITY_SCHEMA.schema)
|
||||||
|
|
||||||
|
|
||||||
@@ -181,19 +188,22 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
|
|||||||
config.get(CONF_PAYLOAD_OFF),
|
config.get(CONF_PAYLOAD_OFF),
|
||||||
config.get(CONF_AVAILABILITY_TOPIC),
|
config.get(CONF_AVAILABILITY_TOPIC),
|
||||||
config.get(CONF_PAYLOAD_AVAILABLE),
|
config.get(CONF_PAYLOAD_AVAILABLE),
|
||||||
config.get(CONF_PAYLOAD_NOT_AVAILABLE))
|
config.get(CONF_PAYLOAD_NOT_AVAILABLE),
|
||||||
|
config.get(CONF_MIN_TEMP),
|
||||||
|
config.get(CONF_MAX_TEMP))
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|
||||||
class MqttClimate(MqttAvailability, ClimateDevice):
|
class MqttClimate(MqttAvailability, ClimateDevice):
|
||||||
"""Representation of a demo climate device."""
|
"""Representation of an MQTT climate device."""
|
||||||
|
|
||||||
def __init__(self, hass, name, topic, value_templates, qos, retain,
|
def __init__(self, hass, name, topic, value_templates, qos, retain,
|
||||||
mode_list, fan_mode_list, swing_mode_list,
|
mode_list, fan_mode_list, swing_mode_list,
|
||||||
target_temperature, away, hold, current_fan_mode,
|
target_temperature, away, hold, current_fan_mode,
|
||||||
current_swing_mode, current_operation, aux, send_if_off,
|
current_swing_mode, current_operation, aux, send_if_off,
|
||||||
payload_on, payload_off, availability_topic,
|
payload_on, payload_off, availability_topic,
|
||||||
payload_available, payload_not_available):
|
payload_available, payload_not_available,
|
||||||
|
min_temp, max_temp):
|
||||||
"""Initialize the climate device."""
|
"""Initialize the climate device."""
|
||||||
super().__init__(availability_topic, qos, payload_available,
|
super().__init__(availability_topic, qos, payload_available,
|
||||||
payload_not_available)
|
payload_not_available)
|
||||||
@@ -219,6 +229,8 @@ class MqttClimate(MqttAvailability, ClimateDevice):
|
|||||||
self._send_if_off = send_if_off
|
self._send_if_off = send_if_off
|
||||||
self._payload_on = payload_on
|
self._payload_on = payload_on
|
||||||
self._payload_off = payload_off
|
self._payload_off = payload_off
|
||||||
|
self._min_temp = min_temp
|
||||||
|
self._max_temp = max_temp
|
||||||
|
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def async_added_to_hass(self):
|
def async_added_to_hass(self):
|
||||||
@@ -619,3 +631,15 @@ class MqttClimate(MqttAvailability, ClimateDevice):
|
|||||||
support |= SUPPORT_AUX_HEAT
|
support |= SUPPORT_AUX_HEAT
|
||||||
|
|
||||||
return support
|
return support
|
||||||
|
|
||||||
|
@property
|
||||||
|
def min_temp(self):
|
||||||
|
"""Return the minimum temperature."""
|
||||||
|
# pylint: disable=no-member
|
||||||
|
return self._min_temp
|
||||||
|
|
||||||
|
@property
|
||||||
|
def max_temp(self):
|
||||||
|
"""Return the maximum temperature."""
|
||||||
|
# pylint: disable=no-member
|
||||||
|
return self._max_temp
|
||||||
|
@@ -32,16 +32,22 @@ NEST_MODE_HEAT_COOL = 'heat-cool'
|
|||||||
|
|
||||||
|
|
||||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
"""Set up the Nest thermostat."""
|
"""Set up the Nest thermostat.
|
||||||
if discovery_info is None:
|
|
||||||
return
|
|
||||||
|
|
||||||
|
No longer in use.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(hass, entry, async_add_devices):
|
||||||
|
"""Set up the Nest climate device based on a config entry."""
|
||||||
temp_unit = hass.config.units.temperature_unit
|
temp_unit = hass.config.units.temperature_unit
|
||||||
|
|
||||||
all_devices = [NestThermostat(structure, device, temp_unit)
|
thermostats = await hass.async_add_job(hass.data[DATA_NEST].thermostats)
|
||||||
for structure, device in hass.data[DATA_NEST].thermostats()]
|
|
||||||
|
|
||||||
add_devices(all_devices, True)
|
all_devices = [NestThermostat(structure, device, temp_unit)
|
||||||
|
for structure, device in thermostats]
|
||||||
|
|
||||||
|
async_add_devices(all_devices, True)
|
||||||
|
|
||||||
|
|
||||||
class NestThermostat(ClimateDevice):
|
class NestThermostat(ClimateDevice):
|
||||||
|
@@ -44,7 +44,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||||||
netatmo = hass.components.netatmo
|
netatmo = hass.components.netatmo
|
||||||
device = config.get(CONF_RELAY)
|
device = config.get(CONF_RELAY)
|
||||||
|
|
||||||
import lnetatmo
|
import pyatmo
|
||||||
try:
|
try:
|
||||||
data = ThermostatData(netatmo.NETATMO_AUTH, device)
|
data = ThermostatData(netatmo.NETATMO_AUTH, device)
|
||||||
for module_name in data.get_module_names():
|
for module_name in data.get_module_names():
|
||||||
@@ -53,7 +53,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||||||
module_name not in config[CONF_THERMOSTAT]:
|
module_name not in config[CONF_THERMOSTAT]:
|
||||||
continue
|
continue
|
||||||
add_devices([NetatmoThermostat(data, module_name)], True)
|
add_devices([NetatmoThermostat(data, module_name)], True)
|
||||||
except lnetatmo.NoDevice:
|
except pyatmo.NoDevice:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
@@ -168,8 +168,8 @@ class ThermostatData(object):
|
|||||||
@Throttle(MIN_TIME_BETWEEN_UPDATES)
|
@Throttle(MIN_TIME_BETWEEN_UPDATES)
|
||||||
def update(self):
|
def update(self):
|
||||||
"""Call the NetAtmo API to update the data."""
|
"""Call the NetAtmo API to update the data."""
|
||||||
import lnetatmo
|
import pyatmo
|
||||||
self.thermostatdata = lnetatmo.ThermostatData(self.auth)
|
self.thermostatdata = pyatmo.ThermostatData(self.auth)
|
||||||
self.target_temperature = self.thermostatdata.setpoint_temp
|
self.target_temperature = self.thermostatdata.setpoint_temp
|
||||||
self.setpoint_mode = self.thermostatdata.setpoint_mode
|
self.setpoint_mode = self.thermostatdata.setpoint_mode
|
||||||
self.current_temperature = self.thermostatdata.temp
|
self.current_temperature = self.thermostatdata.temp
|
||||||
|
@@ -19,7 +19,7 @@ from homeassistant.components.climate import (
|
|||||||
ATTR_CURRENT_HUMIDITY, ClimateDevice, DOMAIN, PLATFORM_SCHEMA,
|
ATTR_CURRENT_HUMIDITY, ClimateDevice, DOMAIN, PLATFORM_SCHEMA,
|
||||||
SUPPORT_TARGET_TEMPERATURE, SUPPORT_OPERATION_MODE,
|
SUPPORT_TARGET_TEMPERATURE, SUPPORT_OPERATION_MODE,
|
||||||
SUPPORT_FAN_MODE, SUPPORT_SWING_MODE,
|
SUPPORT_FAN_MODE, SUPPORT_SWING_MODE,
|
||||||
SUPPORT_ON_OFF, DEFAULT_MIN_TEMP, DEFAULT_MAX_TEMP)
|
SUPPORT_ON_OFF)
|
||||||
from homeassistant.exceptions import PlatformNotReady
|
from homeassistant.exceptions import PlatformNotReady
|
||||||
from homeassistant.helpers import config_validation as cv
|
from homeassistant.helpers import config_validation as cv
|
||||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
@@ -246,13 +246,13 @@ class SensiboClimate(ClimateDevice):
|
|||||||
def min_temp(self):
|
def min_temp(self):
|
||||||
"""Return the minimum temperature."""
|
"""Return the minimum temperature."""
|
||||||
return self._temperatures_list[0] \
|
return self._temperatures_list[0] \
|
||||||
if self._temperatures_list else DEFAULT_MIN_TEMP
|
if self._temperatures_list else super().min_temp
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def max_temp(self):
|
def max_temp(self):
|
||||||
"""Return the maximum temperature."""
|
"""Return the maximum temperature."""
|
||||||
return self._temperatures_list[-1] \
|
return self._temperatures_list[-1] \
|
||||||
if self._temperatures_list else DEFAULT_MAX_TEMP
|
if self._temperatures_list else super().max_temp
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def unique_id(self):
|
def unique_id(self):
|
||||||
|
@@ -8,8 +8,8 @@ import logging
|
|||||||
|
|
||||||
from homeassistant.const import (PRECISION_TENTHS, TEMP_CELSIUS)
|
from homeassistant.const import (PRECISION_TENTHS, TEMP_CELSIUS)
|
||||||
from homeassistant.components.climate import (
|
from homeassistant.components.climate import (
|
||||||
ClimateDevice, SUPPORT_TARGET_TEMPERATURE, SUPPORT_OPERATION_MODE,
|
ClimateDevice, SUPPORT_TARGET_TEMPERATURE, SUPPORT_OPERATION_MODE)
|
||||||
DEFAULT_MIN_TEMP, DEFAULT_MAX_TEMP)
|
from homeassistant.util.temperature import convert as convert_temperature
|
||||||
from homeassistant.const import ATTR_TEMPERATURE
|
from homeassistant.const import ATTR_TEMPERATURE
|
||||||
from homeassistant.components.tado import DATA_TADO
|
from homeassistant.components.tado import DATA_TADO
|
||||||
|
|
||||||
@@ -231,18 +231,14 @@ class TadoClimate(ClimateDevice):
|
|||||||
@property
|
@property
|
||||||
def min_temp(self):
|
def min_temp(self):
|
||||||
"""Return the minimum temperature."""
|
"""Return the minimum temperature."""
|
||||||
if self._min_temp:
|
return convert_temperature(self._min_temp, self._unit,
|
||||||
return self._min_temp
|
self.hass.config.units.temperature_unit)
|
||||||
|
|
||||||
return DEFAULT_MIN_TEMP
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def max_temp(self):
|
def max_temp(self):
|
||||||
"""Return the maximum temperature."""
|
"""Return the maximum temperature."""
|
||||||
if self._max_temp:
|
return convert_temperature(self._max_temp, self._unit,
|
||||||
return self._max_temp
|
self.hass.config.units.temperature_unit)
|
||||||
|
|
||||||
return DEFAULT_MAX_TEMP
|
|
||||||
|
|
||||||
def update(self):
|
def update(self):
|
||||||
"""Update the state of this climate device."""
|
"""Update the state of this climate device."""
|
||||||
|
@@ -32,8 +32,8 @@ SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE |
|
|||||||
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
||||||
"""Set up of Vera thermostats."""
|
"""Set up of Vera thermostats."""
|
||||||
add_devices_callback(
|
add_devices_callback(
|
||||||
VeraThermostat(device, hass.data[VERA_CONTROLLER]) for
|
[VeraThermostat(device, hass.data[VERA_CONTROLLER]) for
|
||||||
device in hass.data[VERA_DEVICES]['climate'])
|
device in hass.data[VERA_DEVICES]['climate']], True)
|
||||||
|
|
||||||
|
|
||||||
class VeraThermostat(VeraDevice, ClimateDevice):
|
class VeraThermostat(VeraDevice, ClimateDevice):
|
||||||
@@ -101,10 +101,6 @@ class VeraThermostat(VeraDevice, ClimateDevice):
|
|||||||
if power:
|
if power:
|
||||||
return convert(power, float, 0.0)
|
return convert(power, float, 0.0)
|
||||||
|
|
||||||
def update(self):
|
|
||||||
"""Handle state updates."""
|
|
||||||
self._state = self.vera_device.get_hvac_mode()
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def temperature_unit(self):
|
def temperature_unit(self):
|
||||||
"""Return the unit of measurement."""
|
"""Return the unit of measurement."""
|
||||||
|
@@ -84,7 +84,6 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||||||
add_devices([WinkWaterHeater(water_heater, hass)])
|
add_devices([WinkWaterHeater(water_heater, hass)])
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=abstract-method
|
|
||||||
class WinkThermostat(WinkDevice, ClimateDevice):
|
class WinkThermostat(WinkDevice, ClimateDevice):
|
||||||
"""Representation of a Wink thermostat."""
|
"""Representation of a Wink thermostat."""
|
||||||
|
|
||||||
|
217
homeassistant/components/climate/zhong_hong.py
Normal file
217
homeassistant/components/climate/zhong_hong.py
Normal 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)
|
@@ -2,48 +2,85 @@
|
|||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.core import callback
|
from homeassistant.core import callback
|
||||||
from homeassistant.components.http import HomeAssistantView
|
|
||||||
from homeassistant.components.http.data_validator import RequestDataValidator
|
|
||||||
from homeassistant.helpers.entity_registry import async_get_registry
|
from homeassistant.helpers.entity_registry import async_get_registry
|
||||||
|
from homeassistant.components import websocket_api
|
||||||
|
from homeassistant.helpers import config_validation as cv
|
||||||
|
|
||||||
|
DEPENDENCIES = ['websocket_api']
|
||||||
|
|
||||||
|
WS_TYPE_GET = 'config/entity_registry/get'
|
||||||
|
SCHEMA_WS_GET = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({
|
||||||
|
vol.Required('type'): WS_TYPE_GET,
|
||||||
|
vol.Required('entity_id'): cv.entity_id
|
||||||
|
})
|
||||||
|
|
||||||
|
WS_TYPE_UPDATE = 'config/entity_registry/update'
|
||||||
|
SCHEMA_WS_UPDATE = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({
|
||||||
|
vol.Required('type'): WS_TYPE_UPDATE,
|
||||||
|
vol.Required('entity_id'): cv.entity_id,
|
||||||
|
# If passed in, we update value. Passing None will remove old value.
|
||||||
|
vol.Optional('name'): vol.Any(str, None),
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
async def async_setup(hass):
|
async def async_setup(hass):
|
||||||
"""Enable the Entity Registry views."""
|
"""Enable the Entity Registry views."""
|
||||||
hass.http.register_view(ConfigManagerEntityView)
|
hass.components.websocket_api.async_register_command(
|
||||||
|
WS_TYPE_GET, websocket_get_entity,
|
||||||
|
SCHEMA_WS_GET
|
||||||
|
)
|
||||||
|
hass.components.websocket_api.async_register_command(
|
||||||
|
WS_TYPE_UPDATE, websocket_update_entity,
|
||||||
|
SCHEMA_WS_UPDATE
|
||||||
|
)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
class ConfigManagerEntityView(HomeAssistantView):
|
@callback
|
||||||
"""View to interact with an entity registry entry."""
|
def websocket_get_entity(hass, connection, msg):
|
||||||
|
"""Handle get entity registry entry command.
|
||||||
|
|
||||||
url = '/api/config/entity_registry/{entity_id}'
|
Async friendly.
|
||||||
name = 'api:config:entity_registry:entity'
|
"""
|
||||||
|
async def retrieve_entity():
|
||||||
async def get(self, request, entity_id):
|
"""Get entity from registry."""
|
||||||
"""Get the entity registry settings for an entity."""
|
|
||||||
hass = request.app['hass']
|
|
||||||
registry = await async_get_registry(hass)
|
registry = await async_get_registry(hass)
|
||||||
entry = registry.entities.get(entity_id)
|
entry = registry.entities.get(msg['entity_id'])
|
||||||
|
|
||||||
if entry is None:
|
if entry is None:
|
||||||
return self.json_message('Entry not found', 404)
|
connection.send_message_outside(websocket_api.error_message(
|
||||||
|
msg['id'], websocket_api.ERR_NOT_FOUND, 'Entity not found'))
|
||||||
|
return
|
||||||
|
|
||||||
return self.json(_entry_dict(entry))
|
connection.send_message_outside(websocket_api.result_message(
|
||||||
|
msg['id'], _entry_dict(entry)
|
||||||
|
))
|
||||||
|
|
||||||
@RequestDataValidator(vol.Schema({
|
hass.async_add_job(retrieve_entity())
|
||||||
# If passed in, we update value. Passing None will remove old value.
|
|
||||||
vol.Optional('name'): vol.Any(str, None),
|
|
||||||
}))
|
@callback
|
||||||
async def post(self, request, entity_id, data):
|
def websocket_update_entity(hass, connection, msg):
|
||||||
"""Update the entity registry settings for an entity."""
|
"""Handle get camera thumbnail websocket command.
|
||||||
hass = request.app['hass']
|
|
||||||
|
Async friendly.
|
||||||
|
"""
|
||||||
|
async def update_entity():
|
||||||
|
"""Get entity from registry."""
|
||||||
registry = await async_get_registry(hass)
|
registry = await async_get_registry(hass)
|
||||||
|
|
||||||
if entity_id not in registry.entities:
|
if msg['entity_id'] not in registry.entities:
|
||||||
return self.json_message('Entry not found', 404)
|
connection.send_message_outside(websocket_api.error_message(
|
||||||
|
msg['id'], websocket_api.ERR_NOT_FOUND, 'Entity not found'))
|
||||||
|
return
|
||||||
|
|
||||||
entry = registry.async_update_entity(entity_id, **data)
|
entry = registry.async_update_entity(
|
||||||
return self.json(_entry_dict(entry))
|
msg['entity_id'], name=msg['name'])
|
||||||
|
connection.send_message_outside(websocket_api.result_message(
|
||||||
|
msg['id'], _entry_dict(entry)
|
||||||
|
))
|
||||||
|
|
||||||
|
hass.async_add_job(update_entity())
|
||||||
|
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
|
@@ -25,7 +25,6 @@ VALUE_TO_STATE = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=unused-argument
|
|
||||||
def setup_platform(hass, config: ConfigType,
|
def setup_platform(hass, config: ConfigType,
|
||||||
add_devices: Callable[[list], None], discovery_info=None):
|
add_devices: Callable[[list], None], discovery_info=None):
|
||||||
"""Set up the ISY994 cover platform."""
|
"""Set up the ISY994 cover platform."""
|
||||||
|
@@ -107,7 +107,6 @@ class KNXCover(CoverDevice):
|
|||||||
"""Register callbacks to update hass after device was changed."""
|
"""Register callbacks to update hass after device was changed."""
|
||||||
async def after_update_callback(device):
|
async def after_update_callback(device):
|
||||||
"""Call after device was updated."""
|
"""Call after device was updated."""
|
||||||
# pylint: disable=unused-argument
|
|
||||||
await self.async_update_ha_state()
|
await self.async_update_ha_state()
|
||||||
self.device.register_device_updated_cb(after_update_callback)
|
self.device.register_device_updated_cb(after_update_callback)
|
||||||
|
|
||||||
@@ -197,7 +196,6 @@ class KNXCover(CoverDevice):
|
|||||||
@callback
|
@callback
|
||||||
def auto_updater_hook(self, now):
|
def auto_updater_hook(self, now):
|
||||||
"""Call for the autoupdater."""
|
"""Call for the autoupdater."""
|
||||||
# pylint: disable=unused-argument
|
|
||||||
self.async_schedule_update_ha_state()
|
self.async_schedule_update_ha_state()
|
||||||
if self.device.position_reached():
|
if self.device.position_reached():
|
||||||
self.stop_auto_updater()
|
self.stop_auto_updater()
|
||||||
|
@@ -17,7 +17,6 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
DEPENDENCIES = ['lutron']
|
DEPENDENCIES = ['lutron']
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=unused-argument
|
|
||||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
"""Set up the Lutron shades."""
|
"""Set up the Lutron shades."""
|
||||||
devs = []
|
devs = []
|
||||||
|
@@ -18,7 +18,6 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
DEPENDENCIES = ['lutron_caseta']
|
DEPENDENCIES = ['lutron_caseta']
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=unused-argument
|
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
|
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
|
||||||
"""Set up the Lutron Caseta shades as a cover device."""
|
"""Set up the Lutron Caseta shades as a cover device."""
|
||||||
|
@@ -13,7 +13,7 @@ from homeassistant.const import (
|
|||||||
CONF_USERNAME, CONF_PASSWORD, CONF_TYPE, STATE_CLOSED)
|
CONF_USERNAME, CONF_PASSWORD, CONF_TYPE, STATE_CLOSED)
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
|
||||||
REQUIREMENTS = ['pymyq==0.0.8']
|
REQUIREMENTS = ['pymyq==0.0.11']
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@@ -54,7 +54,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
|||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=unused-argument
|
|
||||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
"""Set up the RPi cover platform."""
|
"""Set up the RPi cover platform."""
|
||||||
relay_time = config.get(CONF_RELAY_TIME)
|
relay_time = config.get(CONF_RELAY_TIME)
|
||||||
|
@@ -19,8 +19,8 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
"""Set up the Vera covers."""
|
"""Set up the Vera covers."""
|
||||||
add_devices(
|
add_devices(
|
||||||
VeraCover(device, hass.data[VERA_CONTROLLER]) for
|
[VeraCover(device, hass.data[VERA_CONTROLLER]) for
|
||||||
device in hass.data[VERA_DEVICES]['cover'])
|
device in hass.data[VERA_DEVICES]['cover']], True)
|
||||||
|
|
||||||
|
|
||||||
class VeraCover(VeraDevice, CoverDevice):
|
class VeraCover(VeraDevice, CoverDevice):
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"config": {
|
"config": {
|
||||||
"abort": {
|
"abort": {
|
||||||
|
"already_configured": "\u041c\u043e\u0441\u0442\u044a\u0442 \u0432\u0435\u0447\u0435 \u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d",
|
||||||
"no_bridges": "\u041d\u0435 \u0441\u0430 \u043e\u0442\u043a\u0440\u0438\u0442\u0438 \u043c\u043e\u0441\u0442\u043e\u0432\u0435 deCONZ",
|
"no_bridges": "\u041d\u0435 \u0441\u0430 \u043e\u0442\u043a\u0440\u0438\u0442\u0438 \u043c\u043e\u0441\u0442\u043e\u0432\u0435 deCONZ",
|
||||||
"one_instance_only": "\u041a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 \u043f\u043e\u0434\u0434\u044a\u0440\u0436\u0430 \u0441\u0430\u043c\u043e \u0435\u0434\u043d\u043e \u043a\u043e\u043f\u0438\u0435 \u043d\u0430 deCONZ"
|
"one_instance_only": "\u041a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 \u043f\u043e\u0434\u0434\u044a\u0440\u0436\u0430 \u0441\u0430\u043c\u043e \u0435\u0434\u043d\u043e \u043a\u043e\u043f\u0438\u0435 \u043d\u0430 deCONZ"
|
||||||
},
|
},
|
||||||
|
33
homeassistant/components/deconz/.translations/ca.json
Normal file
33
homeassistant/components/deconz/.translations/ca.json
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
32
homeassistant/components/deconz/.translations/cs.json
Normal file
32
homeassistant/components/deconz/.translations/cs.json
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
@@ -21,10 +21,11 @@
|
|||||||
"title": "Link with deCONZ"
|
"title": "Link with deCONZ"
|
||||||
},
|
},
|
||||||
"options": {
|
"options": {
|
||||||
"title": "Extra configuration options for deCONZ",
|
|
||||||
"data": {
|
"data": {
|
||||||
"allow_clip_sensor": "Allow importing virtual sensors"
|
"allow_clip_sensor": "Allow importing virtual sensors",
|
||||||
}
|
"allow_deconz_groups": "Allow importing deCONZ groups"
|
||||||
|
},
|
||||||
|
"title": "Extra configuration options for deCONZ"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"title": "deCONZ Zigbee gateway"
|
"title": "deCONZ Zigbee gateway"
|
||||||
|
32
homeassistant/components/deconz/.translations/fr.json
Normal file
32
homeassistant/components/deconz/.translations/fr.json
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
@@ -1,6 +1,8 @@
|
|||||||
{
|
{
|
||||||
"config": {
|
"config": {
|
||||||
"abort": {
|
"abort": {
|
||||||
|
"already_configured": "A bridge m\u00e1r konfigur\u00e1lva van",
|
||||||
|
"no_bridges": "Nem tal\u00e1ltam deCONZ bridget",
|
||||||
"one_instance_only": "Ez a komponens csak egy deCONZ egys\u00e9get t\u00e1mogat"
|
"one_instance_only": "Ez a komponens csak egy deCONZ egys\u00e9get t\u00e1mogat"
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
@@ -11,9 +13,11 @@
|
|||||||
"data": {
|
"data": {
|
||||||
"host": "H\u00e1zigazda (Host)",
|
"host": "H\u00e1zigazda (Host)",
|
||||||
"port": "Port (alap\u00e9rtelmezett \u00e9rt\u00e9k: '80')"
|
"port": "Port (alap\u00e9rtelmezett \u00e9rt\u00e9k: '80')"
|
||||||
}
|
},
|
||||||
|
"title": "deCONZ \u00e1tj\u00e1r\u00f3 megad\u00e1sa"
|
||||||
},
|
},
|
||||||
"link": {
|
"link": {
|
||||||
|
"description": "Oldja fel a deCONZ \u00e1tj\u00e1r\u00f3t a Home Assistant-ban val\u00f3 regisztr\u00e1l\u00e1shoz.\n\n1. Menjen a deCONZ rendszer be\u00e1ll\u00edt\u00e1sokhoz\n2. Nyomja meg az \"\u00c1tj\u00e1r\u00f3 felold\u00e1sa\" gombot",
|
||||||
"title": "Kapcsol\u00f3d\u00e1s a deCONZ-hoz"
|
"title": "Kapcsol\u00f3d\u00e1s a deCONZ-hoz"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
26
homeassistant/components/deconz/.translations/it.json
Normal file
26
homeassistant/components/deconz/.translations/it.json
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
@@ -19,8 +19,15 @@
|
|||||||
"link": {
|
"link": {
|
||||||
"description": "deCONZ \uac8c\uc774\ud2b8\uc6e8\uc774\ub97c \uc5b8\ub77d\ud558\uc5ec Home Assistant \uc5d0 \uc5f0\uacb0\ud558\uae30\n\n1. deCONZ \uc2dc\uc2a4\ud15c \uc124\uc815\uc73c\ub85c \uc774\ub3d9\ud558\uc138\uc694\n2. \"Unlock Gateway\" \ubc84\ud2bc\uc744 \ub204\ub974\uc138\uc694 ",
|
"description": "deCONZ \uac8c\uc774\ud2b8\uc6e8\uc774\ub97c \uc5b8\ub77d\ud558\uc5ec Home Assistant \uc5d0 \uc5f0\uacb0\ud558\uae30\n\n1. deCONZ \uc2dc\uc2a4\ud15c \uc124\uc815\uc73c\ub85c \uc774\ub3d9\ud558\uc138\uc694\n2. \"Unlock Gateway\" \ubc84\ud2bc\uc744 \ub204\ub974\uc138\uc694 ",
|
||||||
"title": "deCONZ\uc640 \uc5f0\uacb0"
|
"title": "deCONZ\uc640 \uc5f0\uacb0"
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"data": {
|
||||||
|
"allow_clip_sensor": "\uac00\uc0c1 \uc13c\uc11c \uac00\uc838\uc624\uae30 \ud5c8\uc6a9",
|
||||||
|
"allow_deconz_groups": "deCONZ \ub0b4\uc6a9 \uac00\uc838\uc624\uae30 \ud5c8\uc6a9"
|
||||||
|
},
|
||||||
|
"title": "deCONZ\ub97c \uc704\ud55c \ucd94\uac00 \uad6c\uc131 \uc635\uc158"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"title": "deCONZ"
|
"title": "deCONZ Zigbee \uac8c\uc774\ud2b8\uc6e8\uc774"
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -19,6 +19,12 @@
|
|||||||
"link": {
|
"link": {
|
||||||
"description": "Entsperrt \u00e4r deCONZ gateway fir se mat Home Assistant ze registr\u00e9ieren.\n\n1. Gidd op\u00a0deCONZ System Astellungen\n2. Dr\u00e9ckt \"Unlock\" Gateway Kn\u00e4ppchen",
|
"description": "Entsperrt \u00e4r deCONZ gateway fir se mat Home Assistant ze registr\u00e9ieren.\n\n1. Gidd op\u00a0deCONZ System Astellungen\n2. Dr\u00e9ckt \"Unlock\" Gateway Kn\u00e4ppchen",
|
||||||
"title": "Link mat deCONZ"
|
"title": "Link mat deCONZ"
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"data": {
|
||||||
|
"allow_clip_sensor": "Erlaabt den Import vun virtuellen Sensoren"
|
||||||
|
},
|
||||||
|
"title": "Extra Konfiguratiouns Optiounen fir deCONZ"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"title": "deCONZ"
|
"title": "deCONZ"
|
||||||
|
@@ -19,6 +19,13 @@
|
|||||||
"link": {
|
"link": {
|
||||||
"description": "L\u00e5s opp deCONZ-gatewayen din for \u00e5 registrere deg med Home Assistant. \n\n 1. G\u00e5 til deCONZ-systeminnstillinger \n 2. Trykk p\u00e5 \"L\u00e5s opp gateway\" knappen",
|
"description": "L\u00e5s opp deCONZ-gatewayen din for \u00e5 registrere deg med Home Assistant. \n\n 1. G\u00e5 til deCONZ-systeminnstillinger \n 2. Trykk p\u00e5 \"L\u00e5s opp gateway\" knappen",
|
||||||
"title": "Koble til deCONZ"
|
"title": "Koble til deCONZ"
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"data": {
|
||||||
|
"allow_clip_sensor": "Tillat import av virtuelle sensorer",
|
||||||
|
"allow_deconz_groups": "Tillat import av deCONZ grupper"
|
||||||
|
},
|
||||||
|
"title": "Ekstra konfigurasjonsalternativer for deCONZ"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"title": "deCONZ"
|
"title": "deCONZ"
|
||||||
|
@@ -19,6 +19,12 @@
|
|||||||
"link": {
|
"link": {
|
||||||
"description": "Odblokuj bramk\u0119 deCONZ, aby zarejestrowa\u0107 j\u0105 w Home Assistant. \n\n 1. Przejd\u017a do ustawie\u0144 systemu deCONZ \n 2. Naci\u015bnij przycisk \"Odblokuj bramk\u0119\"",
|
"description": "Odblokuj bramk\u0119 deCONZ, aby zarejestrowa\u0107 j\u0105 w Home Assistant. \n\n 1. Przejd\u017a do ustawie\u0144 systemu deCONZ \n 2. Naci\u015bnij przycisk \"Odblokuj bramk\u0119\"",
|
||||||
"title": "Po\u0142\u0105cz z deCONZ"
|
"title": "Po\u0142\u0105cz z deCONZ"
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"data": {
|
||||||
|
"allow_clip_sensor": "Zezwalaj na importowanie wirtualnych sensor\u00f3w"
|
||||||
|
},
|
||||||
|
"title": "Dodatkowe opcje konfiguracji dla deCONZ"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"title": "deCONZ"
|
"title": "deCONZ"
|
||||||
|
32
homeassistant/components/deconz/.translations/pt-BR.json
Normal file
32
homeassistant/components/deconz/.translations/pt-BR.json
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
@@ -1,7 +1,32 @@
|
|||||||
{
|
{
|
||||||
"config": {
|
"config": {
|
||||||
"abort": {
|
"abort": {
|
||||||
"already_configured": "Bridge j\u00e1 est\u00e1 configurada"
|
"already_configured": "Bridge j\u00e1 est\u00e1 configurada",
|
||||||
}
|
"no_bridges": "Nenhum deCONZ descoberto",
|
||||||
|
"one_instance_only": "Componente suporta apenas uma conex\u00e3o deCONZ"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"no_key": "N\u00e3o foi poss\u00edvel obter uma chave de API"
|
||||||
|
},
|
||||||
|
"step": {
|
||||||
|
"init": {
|
||||||
|
"data": {
|
||||||
|
"host": "Servidor",
|
||||||
|
"port": "Porta (por omiss\u00e3o: '80')"
|
||||||
|
},
|
||||||
|
"title": "Defina o gateway deCONZ"
|
||||||
|
},
|
||||||
|
"link": {
|
||||||
|
"description": "Desbloqueie o seu gateway deCONZ para se registar no Home Assistant. \n\n 1. V\u00e1 para as configura\u00e7\u00f5es do sistema deCONZ \n 2. Pressione o bot\u00e3o \"Desbloquear Gateway\"",
|
||||||
|
"title": "Link com deCONZ"
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"data": {
|
||||||
|
"allow_clip_sensor": "Permitir a importa\u00e7\u00e3o de sensores virtuais"
|
||||||
|
},
|
||||||
|
"title": "Op\u00e7\u00f5es extra de configura\u00e7\u00e3o para deCONZ"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"title": "deCONZ"
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -19,6 +19,13 @@
|
|||||||
"link": {
|
"link": {
|
||||||
"description": "\u0420\u0430\u0437\u0431\u043b\u043e\u043a\u0438\u0440\u0443\u0439\u0442\u0435 \u0448\u043b\u044e\u0437 deCONZ \u0434\u043b\u044f \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u0438 \u0432 Home Assistant:\n\n1. \u041f\u0435\u0440\u0435\u0439\u0434\u0438\u0442\u0435 \u043a \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430\u043c \u0441\u0438\u0441\u0442\u0435\u043c\u044b deCONZ\n2. \u041d\u0430\u0436\u043c\u0438\u0442\u0435 \u043a\u043d\u043e\u043f\u043a\u0443 \u00ab\u0420\u0430\u0437\u0431\u043b\u043e\u043a\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0448\u043b\u044e\u0437\u00bb",
|
"description": "\u0420\u0430\u0437\u0431\u043b\u043e\u043a\u0438\u0440\u0443\u0439\u0442\u0435 \u0448\u043b\u044e\u0437 deCONZ \u0434\u043b\u044f \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u0438 \u0432 Home Assistant:\n\n1. \u041f\u0435\u0440\u0435\u0439\u0434\u0438\u0442\u0435 \u043a \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430\u043c \u0441\u0438\u0441\u0442\u0435\u043c\u044b deCONZ\n2. \u041d\u0430\u0436\u043c\u0438\u0442\u0435 \u043a\u043d\u043e\u043f\u043a\u0443 \u00ab\u0420\u0430\u0437\u0431\u043b\u043e\u043a\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0448\u043b\u044e\u0437\u00bb",
|
||||||
"title": "\u0421\u0432\u044f\u0437\u044c \u0441 deCONZ"
|
"title": "\u0421\u0432\u044f\u0437\u044c \u0441 deCONZ"
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"data": {
|
||||||
|
"allow_clip_sensor": "\u0420\u0430\u0437\u0440\u0435\u0448\u0438\u0442\u044c \u0438\u043c\u043f\u043e\u0440\u0442 \u0432\u0438\u0440\u0442\u0443\u0430\u043b\u044c\u043d\u044b\u0445 \u0434\u0430\u0442\u0447\u0438\u043a\u043e\u0432",
|
||||||
|
"allow_deconz_groups": "\u0420\u0430\u0437\u0440\u0435\u0448\u0438\u0442\u044c \u0438\u043c\u043f\u043e\u0440\u0442 \u0433\u0440\u0443\u043f\u043f deCONZ"
|
||||||
|
},
|
||||||
|
"title": "\u0414\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438 \u0434\u043b\u044f deCONZ"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"title": "deCONZ"
|
"title": "deCONZ"
|
||||||
|
@@ -19,6 +19,12 @@
|
|||||||
"link": {
|
"link": {
|
||||||
"description": "Odklenite va\u0161 deCONZ gateway za registracijo z Home Assistant-om. \n1. Pojdite v deCONT sistemske nastavitve\n2. Pritisnite tipko \"odkleni prehod\"",
|
"description": "Odklenite va\u0161 deCONZ gateway za registracijo z Home Assistant-om. \n1. Pojdite v deCONT sistemske nastavitve\n2. Pritisnite tipko \"odkleni prehod\"",
|
||||||
"title": "Povezava z deCONZ"
|
"title": "Povezava z deCONZ"
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"data": {
|
||||||
|
"allow_clip_sensor": "Dovoli uvoz virtualnih senzorjev"
|
||||||
|
},
|
||||||
|
"title": "Dodatne mo\u017enosti konfiguracije za deCONZ"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"title": "deCONZ"
|
"title": "deCONZ"
|
||||||
|
33
homeassistant/components/deconz/.translations/sv.json
Normal file
33
homeassistant/components/deconz/.translations/sv.json
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
26
homeassistant/components/deconz/.translations/vi.json
Normal file
26
homeassistant/components/deconz/.translations/vi.json
Normal 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
Reference in New Issue
Block a user