Compare commits

..

25 Commits

Author SHA1 Message Date
Paulus Schoutsen
e8dfc326d3 Bumped version to 0.87.0b6 2019-02-05 08:04:49 -08:00
Paulus Schoutsen
3d75e1c299 Keep cloud tokens always valid (#20762)
* Keep auth token always valid

* Remove unused refresh_auth message

* Capture EndpointConnectionError

* Lint
2019-02-05 08:04:41 -08:00
Steven Looman
7f76210549 Upgrade to async_upnp_client==0.14.4 (#20751) 2019-02-04 22:01:51 -08:00
Jason Hu
a8b4467763 Fix the line reference in config error message (#20743)
* Fix the line reference in config error message

* Fix platform config validation

* Fix test

* Handle error in error handling routine
2019-02-04 22:01:50 -08:00
Pascal Vizeli
cfa03a408e Fix cloud webhook body (#20739)
* Bugfix cloud webhooks text response

* address comments

* Fix lint
2019-02-04 22:01:49 -08:00
Paulus Schoutsen
94ab5dca7f Improve cloud error handling (#20729)
* Improve cloud error handling

* Lint
2019-02-04 22:01:48 -08:00
Jason Hu
207a050dba Fix ffmpeg v4 stream issue (#20314)
* Add ffmpeg version

* Add ffmpeg stream content type

* Change ffmpeg camera stream content type

* Change ffmpeg stream content type

* Lint

* Add a none guard

* Fix

* Fix

* Update onvif.py

* Fix version match regrex

* Fix regrex

* Upgrade ha-ffmpeg to 1.11

* Lint

* Get ffmpeg version in ffmpeg component setup
2019-02-04 22:01:47 -08:00
Paulus Schoutsen
7d334783de Bumped version to 0.87.0b5 2019-02-03 15:27:14 -08:00
David Lie
027fcf269b Revert pyfoscam back to libpyfoscam (#20727)
* Change foscam python library to pyfoscam, which is more up to date and has several critical bug fixes.

* Update requirements_all.txt to match.

* Inserting automatically generated requirements.txt

* Revert changes until pyfoscam captures recent bug fixes. The pyfoscam version pulled by pip is currently broken.

* Updated requirements_all.txt based on changing pyfoscam back to libpyfoscam.
2019-02-03 15:26:19 -08:00
Aaron Bach
e1509bcc0c Fix temperature unit conversion in Ambient PWS (#20723) 2019-02-03 15:26:18 -08:00
Andrew Sayre
9a13aafeea Add SmartThings button support via events (#20707)
* Add event support for buttons

* binary_sensor test clean-up
2019-02-03 15:26:17 -08:00
Paulus Schoutsen
5f2d209dec Updated frontend to 20190203.0 2019-02-03 11:32:26 -08:00
Paulus Schoutsen
b0200cdbfe Bumped version to 0.87.0b4 2019-02-02 20:28:03 -08:00
Diogo Gomes
c9f64af85a fix test commented in #20678 (#20680) 2019-02-02 17:05:40 -08:00
Paulus Schoutsen
e4d45bf53a Test is broken 2019-02-02 17:04:48 -08:00
Paulus Schoutsen
e984868762 Bumped version to 0.87.0b3 2019-02-02 16:32:26 -08:00
Paulus Schoutsen
e5835eb7c8 Remove fingerprint middleware (#20682)
* Remove fingerprint middleware

* Lint
2019-02-02 16:32:20 -08:00
Paulus Schoutsen
f73cb0eba5 Bumped version to 0.87.0b2 2019-02-02 14:09:55 -08:00
Andrew Sayre
d3e011ff50 Add SmartThings Binary Sensor platform (#20699)
* Add SmartThings binary_sensor platform

* Fixed comment typo.
2019-02-02 14:09:44 -08:00
emontnemery
4255f2c62f Add entity_namespace to PLATFORM_SCHEMA (#20693)
* Add entity_namespace to base platform schema

* Add test

* Fix
2019-02-02 14:09:43 -08:00
Andrew Sayre
b669e1498a Add SmartThings Fan platform (#20681)
* Add SmartThings fan

* Removed unnecessary update method

* Corrected usage of async_schedule_update_ha_state

* Clean-up/optimization
2019-02-02 14:09:42 -08:00
Rohan Kapoor
c0fd22c285 Fix allow extra in locative webhook schema validation (#20657)
* Allow extra in locative webhook schema validation (fixes #20566)

* Remove extra attribute
2019-02-02 14:09:41 -08:00
Andrew Sayre
785b42ecde Add SmartThings Light platform (#20652)
* Add SmartThings Light platform and tests

* Cleaned a few awk comments

* Updates per review feedback

* Switched to super

* Changes per review feedback
2019-02-02 14:09:40 -08:00
Paulus Schoutsen
8988ee5b34 Updated frontend to 20190202.0 2019-02-02 14:03:37 -08:00
Paulus Schoutsen
61b2f1bff0 Update translations 2019-02-02 14:03:37 -08:00
132 changed files with 2409 additions and 316 deletions

View File

@@ -0,0 +1,19 @@
{
"config": {
"error": {
"identifier_exists": "Clau d'aplicaci\u00f3 i/o clau API ja registrada",
"invalid_key": "Clau API i/o clau d'aplicaci\u00f3 inv\u00e0lida/es",
"no_devices": "No s'ha trobat cap dispositiu al compte"
},
"step": {
"user": {
"data": {
"api_key": "Clau API",
"app_key": "Clau d'aplicaci\u00f3"
},
"title": "Introdueix la teva informaci\u00f3"
}
},
"title": "Ambient PWS"
}
}

View File

@@ -0,0 +1,19 @@
{
"config": {
"error": {
"identifier_exists": "Application \ud0a4 \ud639\uc740 API \ud0a4\uac00 \uc774\ubbf8 \ub4f1\ub85d\ub418\uc5c8\uc2b5\ub2c8\ub2e4",
"invalid_key": "Application \ud0a4 \ud639\uc740 API \ud0a4\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4",
"no_devices": "\uacc4\uc815\uc5d0 \uae30\uae30\uac00 \uc874\uc7ac\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4"
},
"step": {
"user": {
"data": {
"api_key": "API \ud0a4",
"app_key": "Application \ud0a4"
},
"title": "\uc0ac\uc6a9\uc790 \uc815\ubcf4\ub97c \uc785\ub825\ud574 \uc8fc\uc138\uc694"
}
},
"title": "Ambient PWS"
}
}

View File

@@ -0,0 +1,19 @@
{
"config": {
"error": {
"identifier_exists": "Applikatioun's Schl\u00ebssel an/oder API Schl\u00ebssel ass scho registr\u00e9iert",
"invalid_key": "Ong\u00ebltegen API Schl\u00ebssel an/oder Applikatioun's Schl\u00ebssel",
"no_devices": "Keng Apparater am Kont fonnt"
},
"step": {
"user": {
"data": {
"api_key": "API Schl\u00ebssel",
"app_key": "Applikatioun's Schl\u00ebssel"
},
"title": "F\u00ebllt \u00e4r Informatiounen aus"
}
},
"title": "Ambient PWS"
}
}

View File

@@ -0,0 +1,19 @@
{
"config": {
"error": {
"identifier_exists": "\u041a\u043b\u044e\u0447 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0438/\u0438\u043b\u0438 \u043a\u043b\u044e\u0447 API \u0443\u0436\u0435 \u0437\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u043e\u0432\u0430\u043d",
"invalid_key": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u043a\u043b\u044e\u0447 API \u0438/\u0438\u043b\u0438 \u043a\u043b\u044e\u0447 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f",
"no_devices": "\u0412 \u0443\u0447\u0435\u0442\u043d\u043e\u0439 \u0437\u0430\u043f\u0438\u0441\u0438 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u044b"
},
"step": {
"user": {
"data": {
"api_key": "\u041a\u043b\u044e\u0447 API",
"app_key": "\u041a\u043b\u044e\u0447 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f"
},
"title": "Ambient PWS"
}
},
"title": "Ambient PWS"
}
}

View File

@@ -0,0 +1,19 @@
{
"config": {
"error": {
"identifier_exists": "API \u5bc6\u9470\u53ca/\u6216\u61c9\u7528\u5bc6\u9470\u5df2\u8a3b\u518a",
"invalid_key": "API \u5bc6\u9470\u53ca/\u6216\u61c9\u7528\u5bc6\u9470\u7121\u6548",
"no_devices": "\u5e33\u865f\u4e2d\u627e\u4e0d\u5230\u4efb\u4f55\u88dd\u7f6e"
},
"step": {
"user": {
"data": {
"api_key": "API \u5bc6\u9470",
"app_key": "\u61c9\u7528\u5bc6\u9470"
},
"title": "\u586b\u5beb\u8cc7\u8a0a"
}
},
"title": "\u74b0\u5883 PWS"
}
}

View File

@@ -11,7 +11,7 @@ import voluptuous as vol
from homeassistant.config_entries import SOURCE_IMPORT
from homeassistant.const import (
ATTR_NAME, ATTR_LOCATION, CONF_API_KEY, CONF_MONITORED_CONDITIONS,
CONF_UNIT_SYSTEM, EVENT_HOMEASSISTANT_STOP)
EVENT_HOMEASSISTANT_STOP)
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers import aiohttp_client, config_validation as cv
from homeassistant.helpers.dispatcher import async_dispatcher_send
@@ -19,8 +19,7 @@ from homeassistant.helpers.event import async_call_later
from .config_flow import configured_instances
from .const import (
ATTR_LAST_DATA, CONF_APP_KEY, DATA_CLIENT, DOMAIN, TOPIC_UPDATE, UNITS_SI,
UNITS_US)
ATTR_LAST_DATA, CONF_APP_KEY, DATA_CLIENT, DOMAIN, TOPIC_UPDATE)
REQUIREMENTS = ['aioambient==0.1.0']
_LOGGER = logging.getLogger(__name__)
@@ -28,36 +27,36 @@ _LOGGER = logging.getLogger(__name__)
DEFAULT_SOCKET_MIN_RETRY = 15
SENSOR_TYPES = {
'24hourrainin': ['24 Hr Rain', 'in'],
'baromabsin': ['Abs Pressure', 'inHg'],
'baromrelin': ['Rel Pressure', 'inHg'],
'battout': ['Battery', ''],
'co2': ['co2', 'ppm'],
'dailyrainin': ['Daily Rain', 'in'],
'dewPoint': ['Dew Point', ['°F', '°C']],
'eventrainin': ['Event Rain', 'in'],
'feelsLike': ['Feels Like', ['°F', '°C']],
'hourlyrainin': ['Hourly Rain Rate', 'in/hr'],
'humidity': ['Humidity', '%'],
'humidityin': ['Humidity In', '%'],
'lastRain': ['Last Rain', ''],
'maxdailygust': ['Max Gust', 'mph'],
'monthlyrainin': ['Monthly Rain', 'in'],
'solarradiation': ['Solar Rad', 'W/m^2'],
'tempf': ['Temp', ['°F', '°C']],
'tempinf': ['Inside Temp', ['°F', '°C']],
'totalrainin': ['Lifetime Rain', 'in'],
'uv': ['uv', 'Index'],
'weeklyrainin': ['Weekly Rain', 'in'],
'winddir': ['Wind Dir', '°'],
'winddir_avg10m': ['Wind Dir Avg 10m', '°'],
'winddir_avg2m': ['Wind Dir Avg 2m', 'mph'],
'windgustdir': ['Gust Dir', '°'],
'windgustmph': ['Wind Gust', 'mph'],
'windspdmph_avg10m': ['Wind Avg 10m', 'mph'],
'windspdmph_avg2m': ['Wind Avg 2m', 'mph'],
'windspeedmph': ['Wind Speed', 'mph'],
'yearlyrainin': ['Yearly Rain', 'in'],
'24hourrainin': ('24 Hr Rain', 'in'),
'baromabsin': ('Abs Pressure', 'inHg'),
'baromrelin': ('Rel Pressure', 'inHg'),
'battout': ('Battery', ''),
'co2': ('co2', 'ppm'),
'dailyrainin': ('Daily Rain', 'in'),
'dewPoint': ('Dew Point', '°F'),
'eventrainin': ('Event Rain', 'in'),
'feelsLike': ('Feels Like', '°F'),
'hourlyrainin': ('Hourly Rain Rate', 'in/hr'),
'humidity': ('Humidity', '%'),
'humidityin': ('Humidity In', '%'),
'lastRain': ('Last Rain', ''),
'maxdailygust': ('Max Gust', 'mph'),
'monthlyrainin': ('Monthly Rain', 'in'),
'solarradiation': ('Solar Rad', 'W/m^2'),
'tempf': ('Temp', '°F'),
'tempinf': ('Inside Temp', '°F'),
'totalrainin': ('Lifetime Rain', 'in'),
'uv': ('uv', 'Index'),
'weeklyrainin': ('Weekly Rain', 'in'),
'winddir': ('Wind Dir', '°'),
'winddir_avg10m': ('Wind Dir Avg 10m', '°'),
'winddir_avg2m': ('Wind Dir Avg 2m', 'mph'),
'windgustdir': ('Gust Dir', '°'),
'windgustmph': ('Wind Gust', 'mph'),
'windspdmph_avg10m': ('Wind Avg 10m', 'mph'),
'windspdmph_avg2m': ('Wind Avg 2m', 'mph'),
'windspeedmph': ('Wind Speed', 'mph'),
'yearlyrainin': ('Yearly Rain', 'in'),
}
CONFIG_SCHEMA = vol.Schema({
@@ -70,8 +69,6 @@ CONFIG_SCHEMA = vol.Schema({
vol.Optional(
CONF_MONITORED_CONDITIONS, default=list(SENSOR_TYPES)):
vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]),
vol.Optional(CONF_UNIT_SYSTEM):
vol.In([UNITS_SI, UNITS_US]),
})
}, extra=vol.ALLOW_EXTRA)
@@ -111,8 +108,7 @@ async def async_setup_entry(hass, config_entry):
config_entry.data[CONF_API_KEY],
config_entry.data[CONF_APP_KEY], session),
config_entry.data.get(
CONF_MONITORED_CONDITIONS, list(SENSOR_TYPES)),
config_entry.data.get(CONF_UNIT_SYSTEM))
CONF_MONITORED_CONDITIONS, list(SENSOR_TYPES)))
hass.loop.create_task(ambient.ws_connect())
hass.data[DOMAIN][DATA_CLIENT][config_entry.entry_id] = ambient
except WebsocketConnectionError as err:
@@ -139,9 +135,7 @@ async def async_unload_entry(hass, config_entry):
class AmbientStation:
"""Define a class to handle the Ambient websocket."""
def __init__(
self, hass, config_entry, client, monitored_conditions,
unit_system):
def __init__(self, hass, config_entry, client, monitored_conditions):
"""Initialize."""
self._config_entry = config_entry
self._hass = hass
@@ -149,7 +143,6 @@ class AmbientStation:
self.client = client
self.monitored_conditions = monitored_conditions
self.stations = {}
self.unit_system = unit_system
async def ws_connect(self):
"""Register handlers and connect to the websocket."""

View File

@@ -8,6 +8,3 @@ CONF_APP_KEY = 'app_key'
DATA_CLIENT = 'data_client'
TOPIC_UPDATE = 'update'
UNITS_SI = 'si'
UNITS_US = 'us'

View File

@@ -12,14 +12,11 @@ from homeassistant.const import ATTR_NAME
from homeassistant.core import callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from .const import (
ATTR_LAST_DATA, DATA_CLIENT, DOMAIN, TOPIC_UPDATE, UNITS_SI, UNITS_US)
from .const import ATTR_LAST_DATA, DATA_CLIENT, DOMAIN, TOPIC_UPDATE
DEPENDENCIES = ['ambient_station']
_LOGGER = logging.getLogger(__name__)
UNIT_SYSTEM = {UNITS_US: 0, UNITS_SI: 1}
async def async_setup_platform(
hass, config, async_add_entities, discovery_info=None):
@@ -31,20 +28,10 @@ async def async_setup_entry(hass, entry, async_add_entities):
"""Set up an Ambient PWS sensor based on a config entry."""
ambient = hass.data[DOMAIN][DATA_CLIENT][entry.entry_id]
if ambient.unit_system:
sys_units = ambient.unit_system
elif hass.config.units.is_metric:
sys_units = UNITS_SI
else:
sys_units = UNITS_US
sensor_list = []
for mac_address, station in ambient.stations.items():
for condition in ambient.monitored_conditions:
name, unit = SENSOR_TYPES[condition]
if isinstance(unit, list):
unit = unit[UNIT_SYSTEM[sys_units]]
sensor_list.append(
AmbientWeatherSensor(
ambient, mac_address, station[ATTR_NAME], condition, name,
@@ -58,7 +45,7 @@ class AmbientWeatherSensor(Entity):
def __init__(
self, ambient, mac_address, station_name, sensor_type, sensor_name,
units):
unit):
"""Initialize the sensor."""
self._ambient = ambient
self._async_unsub_dispatcher_connect = None
@@ -67,7 +54,7 @@ class AmbientWeatherSensor(Entity):
self._sensor_type = sensor_type
self._state = None
self._station_name = station_name
self._units = units
self._unit = unit
@property
def name(self):
@@ -87,7 +74,7 @@ class AmbientWeatherSensor(Entity):
@property
def unit_of_measurement(self):
"""Return the unit of measurement."""
return self._units
return self._unit
@property
def unique_id(self):

View File

@@ -9,7 +9,7 @@
},
"step": {
"init": {
"description": "Prosz\u0119 wybra\u0107 jedn\u0105 z us\u0142ugi powiadamiania:",
"description": "Prosz\u0119 wybra\u0107 jedn\u0105 us\u0142ug\u0119 powiadamiania:",
"title": "Skonfiguruj has\u0142o jednorazowe dostarczone przez komponent powiadomie\u0144"
},
"setup": {

View File

@@ -21,7 +21,7 @@
},
"totp": {
"error": {
"invalid_code": "Neveljavna koda, prosimo, poskusite znova. \u010ce dobite to sporo\u010dilo ve\u010dkrat, prosimo poskrbite, da bo ura va\u0161ega Home Assistenta to\u010dna."
"invalid_code": "Neveljavna koda, prosimo, poskusite znova. \u010ce dobite to sporo\u010dilo ve\u010dkrat, prosimo poskrbite, da bo ura va\u0161ega Home Assistanta to\u010dna."
},
"step": {
"init": {

View File

@@ -3,6 +3,11 @@
"notify": {
"error": {
"invalid_code": "\u041d\u0435\u0432\u0456\u0440\u043d\u0438\u0439 \u043a\u043e\u0434, \u0441\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0449\u0435 \u0440\u0430\u0437."
},
"step": {
"setup": {
"title": "\u041f\u0435\u0440\u0435\u0432\u0456\u0440\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f"
}
}
}
}

View File

@@ -82,7 +82,7 @@ class AmcrestCam(Camera):
try:
return await async_aiohttp_proxy_stream(
self.hass, request, stream,
'multipart/x-mixed-replace;boundary=ffserver')
self._ffmpeg.ffmpeg_stream_content_type)
finally:
await stream.close()

View File

@@ -104,7 +104,7 @@ class ArloCam(Camera):
try:
return await async_aiohttp_proxy_stream(
self.hass, request, stream,
'multipart/x-mixed-replace;boundary=ffserver')
self._ffmpeg.ffmpeg_stream_content_type)
finally:
await stream.close()

View File

@@ -101,7 +101,7 @@ class CanaryCamera(Camera):
try:
return await async_aiohttp_proxy_stream(
self.hass, request, stream,
'multipart/x-mixed-replace;boundary=ffserver')
self._ffmpeg.ffmpeg_stream_content_type)
finally:
await stream.close()

View File

@@ -68,7 +68,7 @@ class FFmpegCamera(Camera):
try:
return await async_aiohttp_proxy_stream(
self.hass, request, stream,
'multipart/x-mixed-replace;boundary=ffserver')
self._manager.ffmpeg_stream_content_type)
finally:
await stream.close()

View File

@@ -15,7 +15,7 @@ from homeassistant.helpers import config_validation as cv
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['pyfoscam==1.2']
REQUIREMENTS = ['libpyfoscam==1.0']
CONF_IP = 'ip'
@@ -43,7 +43,7 @@ class FoscamCam(Camera):
def __init__(self, device_info):
"""Initialize a Foscam camera."""
from foscam import FoscamCamera
from libpyfoscam import FoscamCamera
super(FoscamCam, self).__init__()

View File

@@ -213,7 +213,8 @@ class ONVIFHassCamera(Camera):
if not self._input:
return None
stream = CameraMjpeg(self.hass.data[DATA_FFMPEG].binary,
ffmpeg_manager = self.hass.data[DATA_FFMPEG]
stream = CameraMjpeg(ffmpeg_manager.binary,
loop=self.hass.loop)
await stream.open_camera(
self._input, extra_cmd=self._ffmpeg_arguments)
@@ -221,7 +222,7 @@ class ONVIFHassCamera(Camera):
try:
return await async_aiohttp_proxy_stream(
self.hass, request, stream,
'multipart/x-mixed-replace;boundary=ffserver')
ffmpeg_manager.ffmpeg_stream_content_type)
finally:
await stream.close()

View File

@@ -142,7 +142,7 @@ class RingCam(Camera):
try:
return await async_aiohttp_proxy_stream(
self.hass, request, stream,
'multipart/x-mixed-replace;boundary=ffserver')
self._ffmpeg.ffmpeg_stream_content_type)
finally:
await stream.close()

View File

@@ -161,6 +161,6 @@ class XiaomiCamera(Camera):
try:
return await async_aiohttp_proxy_stream(
self.hass, request, stream,
'multipart/x-mixed-replace;boundary=ffserver')
self._manager.ffmpeg_stream_content_type)
finally:
await stream.close()

View File

@@ -147,6 +147,6 @@ class YiCamera(Camera):
try:
return await async_aiohttp_proxy_stream(
self.hass, request, stream,
'multipart/x-mixed-replace;boundary=ffserver')
self._manager.ffmpeg_stream_content_type)
finally:
await stream.close()

View File

@@ -106,6 +106,7 @@ async def async_setup(hass, config):
)
cloud = hass.data[DOMAIN] = Cloud(hass, **kwargs)
await auth_api.async_setup(hass, cloud)
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, cloud.async_start)
await http_api.async_setup(hass)
return True
@@ -263,7 +264,7 @@ class Cloud:
self.access_token = info['access_token']
self.refresh_token = info['refresh_token']
self.hass.add_job(self.iot.connect())
self.hass.async_create_task(self.iot.connect())
def _decode_claims(self, token): # pylint: disable=no-self-use
"""Decode the claims in a token."""

View File

@@ -1,4 +1,10 @@
"""Package to communicate with the authentication API."""
import asyncio
import logging
import random
_LOGGER = logging.getLogger(__name__)
class CloudError(Exception):
@@ -39,6 +45,40 @@ AWS_EXCEPTIONS = {
}
async def async_setup(hass, cloud):
"""Configure the auth api."""
refresh_task = None
async def handle_token_refresh():
"""Handle Cloud access token refresh."""
sleep_time = 5
sleep_time = random.randint(2400, 3600)
while True:
try:
await asyncio.sleep(sleep_time)
await hass.async_add_executor_job(renew_access_token, cloud)
except CloudError as err:
_LOGGER.error("Can't refresh cloud token: %s", err)
except asyncio.CancelledError:
# Task is canceled, stop it.
break
sleep_time = random.randint(3100, 3600)
async def on_connect():
"""When the instance is connected."""
nonlocal refresh_task
refresh_task = hass.async_create_task(handle_token_refresh())
async def on_disconnect():
"""When the instance is disconnected."""
nonlocal refresh_task
refresh_task.cancel()
cloud.iot.register_on_connect(on_connect)
cloud.iot.register_on_disconnect(on_disconnect)
def _map_aws_exception(err):
"""Map AWS exception to our exceptions."""
ex = AWS_EXCEPTIONS.get(err.response['Error']['Code'], UnknownError)
@@ -47,7 +87,7 @@ def _map_aws_exception(err):
def register(cloud, email, password):
"""Register a new account."""
from botocore.exceptions import ClientError
from botocore.exceptions import ClientError, EndpointConnectionError
cognito = _cognito(cloud)
# Workaround for bug in Warrant. PR with fix:
@@ -55,13 +95,16 @@ def register(cloud, email, password):
cognito.add_base_attributes()
try:
cognito.register(email, password)
except ClientError as err:
raise _map_aws_exception(err)
except EndpointConnectionError:
raise UnknownError()
def resend_email_confirm(cloud, email):
"""Resend email confirmation."""
from botocore.exceptions import ClientError
from botocore.exceptions import ClientError, EndpointConnectionError
cognito = _cognito(cloud, username=email)
@@ -72,18 +115,23 @@ def resend_email_confirm(cloud, email):
)
except ClientError as err:
raise _map_aws_exception(err)
except EndpointConnectionError:
raise UnknownError()
def forgot_password(cloud, email):
"""Initialize forgotten password flow."""
from botocore.exceptions import ClientError
from botocore.exceptions import ClientError, EndpointConnectionError
cognito = _cognito(cloud, username=email)
try:
cognito.initiate_forgot_password()
except ClientError as err:
raise _map_aws_exception(err)
except EndpointConnectionError:
raise UnknownError()
def login(cloud, email, password):
@@ -97,7 +145,7 @@ def login(cloud, email, password):
def check_token(cloud):
"""Check that the token is valid and verify if needed."""
from botocore.exceptions import ClientError
from botocore.exceptions import ClientError, EndpointConnectionError
cognito = _cognito(
cloud,
@@ -109,13 +157,17 @@ def check_token(cloud):
cloud.id_token = cognito.id_token
cloud.access_token = cognito.access_token
cloud.write_user_info()
except ClientError as err:
raise _map_aws_exception(err)
except EndpointConnectionError:
raise UnknownError()
def renew_access_token(cloud):
"""Renew access token."""
from botocore.exceptions import ClientError
from botocore.exceptions import ClientError, EndpointConnectionError
cognito = _cognito(
cloud,
@@ -127,13 +179,17 @@ def renew_access_token(cloud):
cloud.id_token = cognito.id_token
cloud.access_token = cognito.access_token
cloud.write_user_info()
except ClientError as err:
raise _map_aws_exception(err)
except EndpointConnectionError:
raise UnknownError()
def _authenticate(cloud, email, password):
"""Log in and return an authenticated Cognito instance."""
from botocore.exceptions import ClientError
from botocore.exceptions import ClientError, EndpointConnectionError
from warrant.exceptions import ForceChangePasswordException
assert not cloud.is_logged_in, 'Cannot login if already logged in.'
@@ -145,11 +201,14 @@ def _authenticate(cloud, email, password):
return cognito
except ForceChangePasswordException:
raise PasswordChangeRequired
raise PasswordChangeRequired()
except ClientError as err:
raise _map_aws_exception(err)
except EndpointConnectionError:
raise UnknownError()
def _cognito(cloud, **kwargs):
"""Get the client credentials."""

View File

@@ -107,13 +107,16 @@ def _handle_cloud_errors(handler):
result = await handler(view, request, *args, **kwargs)
return result
except (auth_api.CloudError, asyncio.TimeoutError) as err:
except Exception as err: # pylint: disable=broad-except
err_info = _CLOUD_ERRORS.get(err.__class__)
if err_info is None:
_LOGGER.exception(
"Unexpected error processing request for %s", request.path)
err_info = (502, 'Unexpected error: {}'.format(err))
status, msg = err_info
return view.json_message(msg, status_code=status,
message_code=err.__class__.__name__)
return view.json_message(
msg, status_code=status,
message_code=err.__class__.__name__.lower())
return error_handler

View File

@@ -12,9 +12,10 @@ from homeassistant.components.alexa import smart_home as alexa
from homeassistant.components.google_assistant import smart_home as ga
from homeassistant.core import callback
from homeassistant.util.decorator import Registry
from homeassistant.util.aiohttp import MockRequest, serialize_response
from homeassistant.util.aiohttp import MockRequest
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from . import auth_api
from . import utils
from .const import MESSAGE_EXPIRATION, MESSAGE_AUTH_FAIL
HANDLERS = Registry()
@@ -61,12 +62,18 @@ class CloudIoT:
# Local code waiting for a response
self._response_handler = {}
self._on_connect = []
self._on_disconnect = []
@callback
def register_on_connect(self, on_connect_cb):
"""Register an async on_connect callback."""
self._on_connect.append(on_connect_cb)
@callback
def register_on_disconnect(self, on_disconnect_cb):
"""Register an async on_disconnect callback."""
self._on_disconnect.append(on_disconnect_cb)
@property
def connected(self):
"""Return if we're currently connected."""
@@ -101,6 +108,17 @@ class CloudIoT:
# Still adding it here to make sure we can always reconnect
_LOGGER.exception("Unexpected error")
if self.state == STATE_CONNECTED and self._on_disconnect:
try:
yield from asyncio.wait([
cb() for cb in self._on_disconnect
])
except Exception: # pylint: disable=broad-except
# Safety net. This should never hit.
# Still adding it here to make sure we don't break the flow
_LOGGER.exception(
"Unexpected error in on_disconnect callbacks")
if self.close_requested:
break
@@ -191,7 +209,13 @@ class CloudIoT:
self.state = STATE_CONNECTED
if self._on_connect:
yield from asyncio.wait([cb() for cb in self._on_connect])
try:
yield from asyncio.wait([cb() for cb in self._on_connect])
except Exception: # pylint: disable=broad-except
# Safety net. This should never hit.
# Still adding it here to make sure we don't break the flow
_LOGGER.exception(
"Unexpected error in on_connect callbacks")
while not client.closed:
msg = yield from client.receive()
@@ -325,11 +349,6 @@ async def async_handle_cloud(hass, cloud, payload):
await cloud.logout()
_LOGGER.error("You have been logged out from Home Assistant cloud: %s",
payload['reason'])
elif action == 'refresh_auth':
# Refresh the auth token between now and payload['seconds']
hass.helpers.event.async_call_later(
random.randint(0, payload['seconds']),
lambda now: auth_api.check_token(cloud))
else:
_LOGGER.warning("Received unknown cloud action: %s", action)
@@ -360,10 +379,8 @@ async def async_handle_webhook(hass, cloud, payload):
response = await hass.components.webhook.async_handle_webhook(
found['webhook_id'], request)
response_dict = serialize_response(response)
response_dict = utils.aiohttp_serialize_response(response)
body = response_dict.get('body')
if body:
body = body.decode('utf-8')
return {
'body': body,

View File

@@ -0,0 +1,13 @@
"""Helper functions for cloud components."""
from typing import Any, Dict
from aiohttp import web
def aiohttp_serialize_response(response: web.Response) -> Dict[str, Any]:
"""Serialize an aiohttp response to a dictionary."""
return {
'status': response.status,
'body': response.text,
'headers': dict(response.headers),
}

View File

@@ -0,0 +1,19 @@
{
"config": {
"abort": {
"already_configured": "\u8bbe\u5907\u5df2\u914d\u7f6e\u5b8c\u6210",
"device_fail": "\u521b\u5efa\u8bbe\u5907\u65f6\u51fa\u73b0\u610f\u5916\u9519\u8bef\u3002",
"device_timeout": "\u8fde\u63a5\u8bbe\u5907\u8d85\u65f6\u3002"
},
"step": {
"user": {
"data": {
"host": "\u4e3b\u673a"
},
"description": "\u8f93\u5165\u60a8\u7684 Daikin \u7a7a\u8c03\u7684IP\u5730\u5740\u3002",
"title": "\u914d\u7f6e Daikin \u7a7a\u8c03"
}
},
"title": "Daikin \u7a7a\u8c03"
}
}

View File

@@ -17,7 +17,7 @@
"title": "Define deCONZ gateway"
},
"link": {
"description": "Unlock your deCONZ gateway to register with Home Assistant.\n\n1. Go to deCONZ system settings\n2. Press \"Unlock Gateway\" button",
"description": "Unlock your deCONZ gateway to register with Home Assistant.\n\n1. Go to deCONZ Settings -> Gateway -> Advanced\n2. Press \"Authenticate app\" button",
"title": "Link with deCONZ"
},
"options": {

View File

@@ -12,7 +12,7 @@
"init": {
"data": {
"host": "Host",
"port": "Port (warto\u015b\u0107 domy\u015blna: \"80\")"
"port": "Port"
},
"title": "Zdefiniuj bramk\u0119 deCONZ"
},
@@ -28,6 +28,6 @@
"title": "Dodatkowe opcje konfiguracji dla deCONZ"
}
},
"title": "deCONZ"
"title": "Brama deCONZ Zigbee"
}
}

View File

@@ -1,11 +1,11 @@
{
"config": {
"abort": {
"not_internet_accessible": " \u010ce \u017eelite prejemati sporo\u010dila dialogflow, mora biti Home Assistent dostopen prek interneta.",
"not_internet_accessible": "\u010ce \u017eelite prejemati sporo\u010dila dialogflow, mora biti Home Assistant dostopen prek interneta.",
"one_instance_allowed": "Potrebna je samo ena instanca."
},
"create_entry": {
"default": "Za po\u0161iljanje dogodkov Home Assistent-u, boste morali nastaviti [webhook z dialogflow]({twilio_url}).\n\nIzpolnite naslednje informacije:\n\n- URL: `{webhook_url}`\n- Metoda: POST\n- Vrsta vsebine: application/x-www-form-urlencoded\n\nGlej [dokumentacijo]({docs_url}) za nadaljna navodila."
"default": "Za po\u0161iljanje dogodkov Home Assistant-u, boste morali nastaviti [webhook z dialogflow]({twilio_url}).\n\nIzpolnite naslednje informacije:\n\n- URL: `{webhook_url}`\n- Metoda: POST\n- Vrsta vsebine: application/x-www-form-urlencoded\n\nGlej [dokumentacijo]({docs_url}) za nadaljna navodila."
},
"step": {
"user": {

View File

@@ -1,10 +1,15 @@
{
"config": {
"abort": {
"not_internet_accessible": "\u60a8\u7684 Home Assistant \u5b9e\u4f8b\u9700\u8981\u63a5\u5165\u4e92\u8054\u7f51\u4ee5\u63a5\u6536 Dialogflow \u6d88\u606f\u3002",
"one_instance_allowed": "\u4ec5\u9700\u4e00\u4e2a\u5b9e\u4f8b"
},
"create_entry": {
"default": "\u8981\u5411 Home Assistant \u53d1\u9001\u4e8b\u4ef6\uff0c\u60a8\u9700\u8981\u914d\u7f6e [Dialogflow \u7684 Webhook \u96c6\u6210]({dialogflow_url})\u3002\n\n\u586b\u5199\u4ee5\u4e0b\u4fe1\u606f\uff1a\n\n- URL: `{webhook_url}`\n- Method: POST\n- Content Type: application/json\n\n\u8bf7\u53c2\u9605[\u6587\u6863]({docs_url})\u4ee5\u4e86\u89e3\u66f4\u591a\u4fe1\u606f\u3002"
},
"step": {
"user": {
"description": "\u60a8\u786e\u5b9a\u8981\u8bbe\u7f6e Dialogflow \u5417?",
"title": "\u8bbe\u7f6e Dialogflow Webhook"
}
},

View File

@@ -0,0 +1,17 @@
{
"config": {
"abort": {
"name_exists": "El nombre ya existe"
},
"step": {
"user": {
"data": {
"host_ip": "IP del host",
"listen_port": "Puerto de escucha",
"name": "Nombre"
},
"title": "Definir la configuraci\u00f3n del servidor"
}
}
}
}

View File

@@ -11,7 +11,7 @@
"host_ip": "\ud638\uc2a4\ud2b8 IP",
"listen_port": "\uc218\uc2e0 \ud3ec\ud2b8",
"name": "\uc774\ub984",
"upnp_bind_multicast": "\uba40\ud2f0 \uce90\uc2a4\ud2b8 \ubc14\uc778\ub4dc (\ucc38/\uac70\uc9d3)"
"upnp_bind_multicast": "\uba40\ud2f0 \uce90\uc2a4\ud2b8 \ud560\ub2f9 (\ucc38/\uac70\uc9d3)"
},
"title": "\uc11c\ubc84 \uad6c\uc131 \uc815\uc758"
}

View File

@@ -0,0 +1,21 @@
{
"config": {
"abort": {
"name_exists": "Numm g\u00ebtt et schonn"
},
"step": {
"user": {
"data": {
"advertise_ip": "IP annonc\u00e9ieren",
"advertise_port": "Port annonc\u00e9ieren",
"host_ip": "IP vum Apparat",
"listen_port": "Port lauschteren",
"name": "Numm",
"upnp_bind_multicast": "Multicast abannen (Richteg/Falsch)"
},
"title": "Server Konfiguratioun d\u00e9fin\u00e9ieren"
}
},
"title": "EmulatedRoku"
}
}

View File

@@ -0,0 +1,21 @@
{
"config": {
"abort": {
"name_exists": "Nazwa ju\u017c istnieje"
},
"step": {
"user": {
"data": {
"advertise_ip": "IP rozg\u0142aszania",
"advertise_port": "Port rozg\u0142aszania",
"host_ip": "IP hosta",
"listen_port": "Port nas\u0142uchu",
"name": "Nazwa",
"upnp_bind_multicast": "Powi\u0105\u017c multicast (prawda/fa\u0142sz)"
},
"title": "Zdefiniuj konfiguracj\u0119 serwera"
}
},
"title": "EmulatedRoku"
}
}

View File

@@ -0,0 +1,21 @@
{
"config": {
"abort": {
"name_exists": "Ime \u017ee obstaja"
},
"step": {
"user": {
"data": {
"advertise_ip": "Advertise IP",
"advertise_port": "Advertise port",
"host_ip": "IP gostitelja",
"listen_port": "Vrata naprave",
"name": "Ime",
"upnp_bind_multicast": "Vezava multicasta (True / False)"
},
"title": "Dolo\u010dite konfiguracijo stre\u017enika"
}
},
"title": "EmulatedRoku"
}
}

View File

@@ -0,0 +1,17 @@
{
"config": {
"abort": {
"name_exists": "\u540d\u79f0\u5df2\u5b58\u5728"
},
"step": {
"user": {
"data": {
"host_ip": "\u4e3b\u673aIP",
"listen_port": "\u76d1\u542c\u7aef\u53e3",
"name": "\u59d3\u540d"
},
"title": "\u5b9a\u4e49\u670d\u52a1\u5668\u914d\u7f6e"
}
}
}
}

View File

@@ -0,0 +1,21 @@
{
"config": {
"abort": {
"name_exists": "\u8a72\u540d\u7a31\u5df2\u5b58\u5728"
},
"step": {
"user": {
"data": {
"advertise_ip": "\u5ee3\u64ad\u901a\u8a0a\u57e0",
"advertise_port": "\u5ee3\u64ad\u901a\u8a0a\u57e0",
"host_ip": "\u4e3b\u6a5f IP",
"listen_port": "\u76e3\u807d\u901a\u8a0a\u57e0",
"name": "\u540d\u7a31",
"upnp_bind_multicast": "\u7d81\u5b9a\u7fa4\u64ad\uff08Multicast\uff09True/False"
},
"title": "\u5b9a\u7fa9\u4f3a\u670d\u5668\u8a2d\u5b9a"
}
},
"title": "EmulatedRoku"
}
}

View File

@@ -0,0 +1,25 @@
{
"config": {
"abort": {
"already_configured": "ESP ya est\u00e1 configurado"
},
"error": {
"invalid_password": "\u00a1Contrase\u00f1a incorrecta!"
},
"step": {
"authenticate": {
"data": {
"password": "Contrase\u00f1a"
},
"description": "Escribe la contrase\u00f1a que hayas establecido en tu configuraci\u00f3n.",
"title": "Escribe la contrase\u00f1a"
},
"user": {
"data": {
"host": "Host",
"port": "Puerto"
}
}
}
}
}

View File

@@ -4,7 +4,7 @@
"already_configured": "ESP \uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4"
},
"error": {
"connection_error": "ESP \uc5d0 \uc5f0\uacb0\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. YAML \ud30c\uc77c\uc5d0 'api :' \ub97c \uad6c\uc131\ud588\ub294\uc9c0 \ud655\uc778\ud574\uc8fc\uc138\uc694.",
"connection_error": "ESP \uc5d0 \uc5f0\uacb0\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. YAML \ud30c\uc77c\uc5d0 'api:' \ub97c \uad6c\uc131\ud588\ub294\uc9c0 \ud655\uc778\ud574\uc8fc\uc138\uc694.",
"invalid_password": "\uc798\ubabb\ub41c \ube44\ubc00\ubc88\ud638",
"resolve_error": "ESP \uc758 \uc8fc\uc18c\ub97c \ud655\uc778\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. \uc774 \uc624\ub958\uac00 \uacc4\uc18d \ubc1c\uc0dd\ud558\uba74 \uace0\uc815 IP \uc8fc\uc18c (https://esphomelib.com/esphomeyaml/components/wifi.html#manual-ips)\ub97c \uc124\uc815\ud574\uc8fc\uc138\uc694"
},

View File

@@ -1,10 +1,10 @@
{
"config": {
"abort": {
"already_configured": "ESP jest ju\u017c skonfigurowany"
"already_configured": "ESP jest ju\u017c skonfigurowane"
},
"error": {
"connection_error": "Nie mo\u017cna po\u0142\u0105czy\u0107 si\u0119 z ESP. Upewnij si\u0119, \u017ce Tw\u00f3j plik YAML zawiera lini\u0119 \"api:\".",
"connection_error": "Nie mo\u017cna po\u0142\u0105czy\u0107 si\u0119 z ESP. Upewnij si\u0119, \u017ce Tw\u00f3j plik YAML zawiera lini\u0119 'api:'.",
"invalid_password": "Nieprawid\u0142owe has\u0142o!",
"resolve_error": "Nie mo\u017cna rozpozna\u0107 adresu ESP. Je\u015bli ten b\u0142\u0105d b\u0119dzie si\u0119 powtarza\u0142, nale\u017cy ustawi\u0107 statyczny adres IP: https://esphomelib.com/esphomeyaml/components/wifi.html#manual-ips"
},
@@ -21,7 +21,7 @@
"host": "Host",
"port": "Port"
},
"description": "Wprowad\u017a ustawienia po\u0142\u0105czenia swojego [ESPHome] (https://esphomelib.com/) w\u0119z\u0142a.",
"description": "Wprowad\u017a ustawienia po\u0142\u0105czenia swojego [ESPHome](https://esphomelib.com/) w\u0119z\u0142a.",
"title": "ESPHome"
}
},

View File

@@ -0,0 +1,27 @@
{
"config": {
"error": {
"connection_error": "\u65e0\u6cd5\u8fde\u63a5\u5230ESP\u3002\u8bf7\u786e\u4fdd\u60a8\u7684YAML\u6587\u4ef6\u5305\u542b'api:'\u884c\u3002",
"invalid_password": "\u65e0\u6548\u7684\u5bc6\u7801\uff01",
"resolve_error": "\u65e0\u6cd5\u89e3\u6790ESP\u7684\u5730\u5740\u3002\u5982\u679c\u6b64\u9519\u8bef\u4ecd\u7136\u5b58\u5728\uff0c\u8bf7\u8bbe\u7f6e\u9759\u6001IP\u5730\u5740\uff1ahttps://esphomelib.com/esphomeyaml/components/wifi.html#manual-ips"
},
"step": {
"authenticate": {
"data": {
"password": "\u5bc6\u7801"
},
"description": "\u8bf7\u8f93\u5165\u60a8\u5728\u914d\u7f6e\u4e2d\u8bbe\u7f6e\u7684\u5bc6\u7801\u3002",
"title": "\u8f93\u5165\u5bc6\u7801"
},
"user": {
"data": {
"host": "\u4e3b\u673a",
"port": "\u7aef\u53e3"
},
"description": "\u8bf7\u8f93\u5165\u60a8\u7684 [ESPHome](https://esphomelib.com/) \u8282\u70b9\u7684\u8fde\u63a5\u8bbe\u7f6e\u3002",
"title": "ESPHome"
}
},
"title": "ESPHome"
}
}

View File

@@ -5,6 +5,7 @@ For more details about this component, please refer to the documentation at
https://home-assistant.io/components/ffmpeg/
"""
import logging
import re
import voluptuous as vol
@@ -16,7 +17,7 @@ from homeassistant.helpers.dispatcher import (
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
REQUIREMENTS = ['ha-ffmpeg==1.9']
REQUIREMENTS = ['ha-ffmpeg==1.11']
DOMAIN = 'ffmpeg'
@@ -60,6 +61,8 @@ async def async_setup(hass, config):
conf.get(CONF_FFMPEG_BIN, DEFAULT_BINARY)
)
await manager.async_get_version()
# Register service
async def async_service_handle(service):
"""Handle service ffmpeg process."""
@@ -96,12 +99,37 @@ class FFmpegManager:
self.hass = hass
self._cache = {}
self._bin = ffmpeg_bin
self._version = None
self._major_version = None
@property
def binary(self):
"""Return ffmpeg binary from config."""
return self._bin
async def async_get_version(self):
"""Return ffmpeg version."""
from haffmpeg.tools import FFVersion
ffversion = FFVersion(self._bin, self.hass.loop)
self._version = await ffversion.get_version()
self._major_version = None
if self._version is not None:
result = re.search(r"(\d+)\.", self._version)
if result is not None:
self._major_version = int(result.group(1))
return self._version, self._major_version
@property
def ffmpeg_stream_content_type(self):
"""Return HTTP content type for ffmpeg stream."""
if self._major_version is not None and self._major_version > 3:
return 'multipart/x-mixed-replace;boundary=ffmpeg'
return 'multipart/x-mixed-replace;boundary=ffserver'
class FFmpegBase(Entity):
"""Interface object for FFmpeg."""

View File

@@ -24,7 +24,7 @@ from homeassistant.core import callback
from homeassistant.helpers.translation import async_get_translations
from homeassistant.loader import bind_hass
REQUIREMENTS = ['home-assistant-frontend==20190201.0']
REQUIREMENTS = ['home-assistant-frontend==20190203.0']
DOMAIN = 'frontend'
DEPENDENCIES = ['api', 'websocket_api', 'http', 'system_log',

View File

@@ -0,0 +1,8 @@
{
"config": {
"abort": {
"not_internet_accessible": "Tu Home Assistant debe ser accesible desde Internet para recibir mensajes de GPSLogger.",
"one_instance_allowed": "Solo se necesita una instancia."
}
}
}

View File

@@ -0,0 +1,18 @@
{
"config": {
"abort": {
"not_internet_accessible": "Tw\u00f3j Home Assistant musi by\u0107 dost\u0119pny z Internetu, aby odbiera\u0107 komunikaty z Geofency.",
"one_instance_allowed": "Wymagana jest tylko jedna instancja."
},
"create_entry": {
"default": "Aby wysy\u0142a\u0107 zdarzenia do Home Assistant'a, musisz skonfigurowa\u0107 webhook w Geofency. \n\n Wprowad\u017a nast\u0119puj\u0105ce dane:\n\n - URL: `{webhook_url}` \n - Metoda: POST \n\nZapoznaj si\u0119 z [dokumentacj\u0105]({docs_url}) by pozna\u0107 szczeg\u00f3\u0142y."
},
"step": {
"user": {
"description": "Czy chcesz skonfigurowa\u0107 Geofency?",
"title": "Konfiguracja Geofency Webhook"
}
},
"title": "Geofency Webhook"
}
}

View File

@@ -0,0 +1,18 @@
{
"config": {
"abort": {
"not_internet_accessible": "Va\u0161 Home Assistant mora biti dostopen prek interneta, da boste lahko prejemali Geofency sporo\u010dila.",
"one_instance_allowed": "Potrebna je samo ena instanca."
},
"create_entry": {
"default": "\u010ce \u017eelite dogodke poslati v Home Assistant, morate v Geofency-ju nastaviti funkcijo webhook. \n\n Izpolnite naslednje podatke: \n\n - URL: ` {webhook_url} ` \n - Metoda: POST \n\n Za ve\u010d podrobnosti si oglejte [dokumentacijo] ( {docs_url} )."
},
"step": {
"user": {
"description": "Ali ste prepri\u010dani, da \u017eelite nastaviti geofency webhook?",
"title": "Nastavite Geofency Webhook"
}
},
"title": "Geofency Webhook"
}
}

View File

@@ -0,0 +1,15 @@
{
"config": {
"abort": {
"not_internet_accessible": "\u60a8\u7684 Home Assistant \u5b9e\u4f8b\u9700\u8981\u63a5\u5165\u4e92\u8054\u7f51\u4ee5\u63a5\u6536 Geofency \u6d88\u606f\u3002",
"one_instance_allowed": "\u4ec5\u9700\u4e00\u4e2a\u5b9e\u4f8b"
},
"step": {
"user": {
"description": "\u60a8\u786e\u5b9a\u8981\u8bbe\u7f6e Geofency Webhook \u5417?",
"title": "\u8bbe\u7f6e Geofency Webhook"
}
},
"title": "Geofency Webhook"
}
}

View File

@@ -0,0 +1,18 @@
{
"config": {
"abort": {
"not_internet_accessible": "Home Assistant \u5be6\u4f8b\u5fc5\u9808\u80fd\u5920\u7531\u7db2\u969b\u7db2\u8def\u5b58\u53d6\uff0c\u65b9\u80fd\u63a5\u53d7 Geofency \u8a0a\u606f\u3002",
"one_instance_allowed": "\u50c5\u9700\u8a2d\u5b9a\u4e00\u7d44\u7269\u4ef6\u5373\u53ef\u3002"
},
"create_entry": {
"default": "\u6b32\u50b3\u9001\u4e8b\u4ef6\u81f3 Home Assistant\uff0c\u5c07\u9700\u65bc Geofency \u5167\u8a2d\u5b9a webhook \u529f\u80fd\u3002\n\n\u8acb\u586b\u5beb\u4e0b\u5217\u8cc7\u8a0a\uff1a\n\n- URL: `{webhook_url}`\n- Method: POST\n\n\u8acb\u53c3\u95b1 [\u6587\u4ef6]({docs_url})\u4ee5\u4e86\u89e3\u66f4\u8a73\u7d30\u8cc7\u6599\u3002"
},
"step": {
"user": {
"description": "\u662f\u5426\u8981\u8a2d\u5b9a Geofency Webhook\uff1f",
"title": "\u8a2d\u5b9a Geofency Webhook"
}
},
"title": "Geofency Webhook"
}
}

View File

@@ -0,0 +1,18 @@
{
"config": {
"abort": {
"not_internet_accessible": "La inst\u00e0ncia de Home Assistant ha de ser accessible des d'Internet per rebre missatges de GPSLogger.",
"one_instance_allowed": "Nom\u00e9s cal una sola inst\u00e0ncia."
},
"create_entry": {
"default": "Per enviar esdeveniments a Home Assistant, haur\u00e0s de configurar l'opci\u00f3 webhook de GPSLogger.\n\nCompleta la seg\u00fcent informaci\u00f3:\n\n- URL: `{webhook_url}` \n- M\u00e8tode: POST \n\nConsulta la [documentaci\u00f3]({docs_url}) per a m\u00e9s detalls."
},
"step": {
"user": {
"description": "Est\u00e0s segur que vols configurar el Webhook GPSLogger?",
"title": "Configuraci\u00f3 del Webhook GPSLogger"
}
},
"title": "Webhook GPSLogger"
}
}

View File

@@ -1,18 +1,18 @@
{
"config": {
"title": "GPSLogger Webhook",
"step": {
"user": {
"title": "Set up the GPSLogger Webhook",
"description": "Are you sure you want to set up the GPSLogger Webhook?"
}
},
"abort": {
"one_instance_allowed": "Only a single instance is necessary.",
"not_internet_accessible": "Your Home Assistant instance needs to be accessible from the internet to receive messages from GPSLogger."
},
"create_entry": {
"default": "To send events to Home Assistant, you will need to setup the webhook feature in GPSLogger.\n\nFill in the following info:\n\n- URL: `{webhook_url}`\n- Method: POST\n\nSee [the documentation]({docs_url}) for further details."
"config": {
"abort": {
"not_internet_accessible": "Your Home Assistant instance needs to be accessible from the internet to receive messages from GPSLogger.",
"one_instance_allowed": "Only a single instance is necessary."
},
"create_entry": {
"default": "To send events to Home Assistant, you will need to setup the webhook feature in GPSLogger.\n\nFill in the following info:\n\n- URL: `{webhook_url}`\n- Method: POST\n\nSee [the documentation]({docs_url}) for further details."
},
"step": {
"user": {
"description": "Are you sure you want to set up the GPSLogger Webhook?",
"title": "Set up the GPSLogger Webhook"
}
},
"title": "GPSLogger Webhook"
}
}
}

View File

@@ -0,0 +1,8 @@
{
"config": {
"abort": {
"not_internet_accessible": "Tu Home Assistant debe ser accesible desde Internet para recibir mensajes de GPSLogger.",
"one_instance_allowed": "Solo se necesita una instancia."
}
}
}

View File

@@ -0,0 +1,18 @@
{
"config": {
"abort": {
"not_internet_accessible": "GPSLogger \uba54\uc2dc\uc9c0\ub97c \ubc1b\uc73c\ub824\uba74 \uc778\ud130\ub137\uc5d0\uc11c Home Assistant \uc778\uc2a4\ud134\uc2a4\uc5d0 \uc561\uc138\uc2a4 \ud560 \uc218 \uc788\uc5b4\uc57c \ud569\ub2c8\ub2e4.",
"one_instance_allowed": "\ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \ud544\uc694\ud569\ub2c8\ub2e4."
},
"create_entry": {
"default": "Home Assistant \ub85c \uc774\ubca4\ud2b8\ub97c \ubcf4\ub0b4\ub824\uba74 GPSLogger \uc5d0\uc11c Webhook \uc744 \uc124\uc815\ud574\uc57c\ud569\ub2c8\ub2e4. \n\n\ub2e4\uc74c \uc815\ubcf4\ub97c \uc785\ub825\ud574 \uc8fc\uc138\uc694. \n\n - URL: `{webhook_url}`\n - Method: POST\n \n \uc790\uc138\ud55c \uc815\ubcf4\ub294 [\uc548\ub0b4]({docs_url}) \ub97c \ucc38\uc870\ud574 \uc8fc\uc138\uc694."
},
"step": {
"user": {
"description": "GPSLogger Webhook \uc744 \uc124\uc815 \ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?",
"title": "GPSLogger Webhook \uc124\uc815"
}
},
"title": "GPSLogger Webhook"
}
}

View File

@@ -0,0 +1,18 @@
{
"config": {
"abort": {
"not_internet_accessible": "\u00c4r Home Assistant Instanz muss iwwert Internet accessibel si fir GPSLogger Noriichten z'empf\u00e4nken.",
"one_instance_allowed": "N\u00ebmmen eng eenzeg Instanz ass n\u00e9ideg."
},
"create_entry": {
"default": "Fir Evenementer un Home Assistant ze sch\u00e9cken, muss den Webhook Feature am GPSLogger ageriicht ginn.\n\nF\u00ebllt folgend Informatiounen aus:\n\n- URL: `{webhook_url}`\n- Method: POST\n\nLiest [Dokumentatioun]({docs_url}) fir w\u00e9ider D\u00e9tailer."
},
"step": {
"user": {
"description": "S\u00e9cher fir GPSLogger Webhook anzeriichten?",
"title": "GPSLogger Webhook ariichten"
}
},
"title": "GPSLogger Webhook"
}
}

View File

@@ -0,0 +1,18 @@
{
"config": {
"abort": {
"not_internet_accessible": "Home Assistant m\u00e5 v\u00e6re tilgjengelig fra internett for \u00e5 kunne motta meldinger fra GPSLogger.",
"one_instance_allowed": "Kun en enkelt forekomst er n\u00f8dvendig."
},
"create_entry": {
"default": "For \u00e5 sende hendelser til Home Assistant, m\u00e5 du sette opp webhook-funksjonen i GPSLogger. \n\n Fyll ut f\u00f8lgende informasjon: \n\n - URL: `{webhook_url}` \n - Metode: POST \n\n Se [dokumentasjonen]({docs_url}) for ytterligere detaljer."
},
"step": {
"user": {
"description": "Er du sikker p\u00e5 at du vil sette opp GPSLogger Webhook?",
"title": "Sett opp GPSLogger Webhook"
}
},
"title": "GPSLogger Webhook"
}
}

View File

@@ -0,0 +1,18 @@
{
"config": {
"abort": {
"not_internet_accessible": "Twoja instancja Home Assistant musi by\u0107 dost\u0119pna z Internetu, aby otrzymywa\u0107 wiadomo\u015bci z GPSlogger.",
"one_instance_allowed": "Wymagana jest tylko jedna instancja."
},
"create_entry": {
"default": "Aby wysy\u0142a\u0107 lokalizacje do Home Assistant'a, musisz skonfigurowa\u0107 webhook w aplikacji GPSLogger. \n\n Wprowad\u017a nast\u0119puj\u0105ce dane:\n\n - URL: `{webhook_url}` \n - Metoda: POST \n\nZapoznaj si\u0119 z [dokumentacj\u0105]({docs_url}) by pozna\u0107 szczeg\u00f3\u0142y."
},
"step": {
"user": {
"description": "Czy chcesz skonfigurowa\u0107 Geofency?",
"title": "Konfiguracja Geofency Webhook"
}
},
"title": "Konfiguracja Geofency Webhook"
}
}

View File

@@ -0,0 +1,17 @@
{
"config": {
"abort": {
"not_internet_accessible": "\u0412\u0430\u0448 Home Assistant \u0434\u043e\u043b\u0436\u0435\u043d \u0431\u044b\u0442\u044c \u0434\u043e\u0441\u0442\u0443\u043f\u0435\u043d \u0438\u0437 \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442\u0430 \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 GPSLogger."
},
"create_entry": {
"default": "\u0414\u043b\u044f \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 \u0441\u043e\u0431\u044b\u0442\u0438\u0439 \u0432 Home Assistant \u0432\u044b \u0434\u043e\u043b\u0436\u043d\u044b \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c webhooks \u0434\u043b\u044f GPSLogger.\n\n\u0414\u043b\u044f \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0443\u044e \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e:\n\n- URL: `{webhook_url}`\n- Method: POST\n\n\u041e\u0437\u043d\u0430\u043a\u043e\u043c\u044c\u0442\u0435\u0441\u044c \u0441 [\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u0435\u0439]({docs_url}) \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0431\u043e\u043b\u0435\u0435 \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u043e\u0439 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438."
},
"step": {
"user": {
"description": "\u0412\u044b \u0443\u0432\u0435\u0440\u0435\u043d\u044b, \u0447\u0442\u043e \u0445\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c GPSLogger?",
"title": "GPSLogger"
}
},
"title": "GPSLogger"
}
}

View File

@@ -0,0 +1,18 @@
{
"config": {
"abort": {
"not_internet_accessible": "Va\u0161 Home Assistant mora biti dostopek prek interneta, da boste lahko prejemali GPSlogger sporo\u010dila.",
"one_instance_allowed": "Potrebna je samo ena instanca."
},
"create_entry": {
"default": "\u010ce \u017eelite dogodke poslati v Home Assistant, morate v GPSLoggerju nastaviti funkcijo webhook. \n\n Izpolnite naslednje podatke: \n\n - URL: ` {webhook_url} ` \n - Metoda: POST \n\n Za ve\u010d podrobnosti si oglejte [dokumentacijo] ( {docs_url} )."
},
"step": {
"user": {
"description": "Ali ste prepri\u010dani, da \u017eelite nastaviti GPSloggerWebhook?",
"title": "Nastavite GPSlogger Webhook"
}
},
"title": "GPSLogger Webhook"
}
}

View File

@@ -0,0 +1,7 @@
{
"config": {
"abort": {
"one_instance_allowed": "\u53ea\u6709\u4e00\u4e2a\u5b9e\u4f8b\u662f\u5fc5\u9700\u7684\u3002"
}
}
}

View File

@@ -0,0 +1,18 @@
{
"config": {
"abort": {
"not_internet_accessible": "Home Assistant \u5be6\u4f8b\u5fc5\u9808\u80fd\u5920\u7531\u7db2\u969b\u7db2\u8def\u5b58\u53d6\uff0c\u65b9\u80fd\u63a5\u53d7 GPSLogger \u8a0a\u606f\u3002",
"one_instance_allowed": "\u50c5\u9700\u8a2d\u5b9a\u4e00\u7d44\u7269\u4ef6\u5373\u53ef\u3002"
},
"create_entry": {
"default": "\u6b32\u50b3\u9001\u4e8b\u4ef6\u81f3 Home Assistant\uff0c\u5c07\u9700\u65bc GPSLogger \u5167\u8a2d\u5b9a webhook \u529f\u80fd\u3002\n\n\u8acb\u586b\u5beb\u4e0b\u5217\u8cc7\u8a0a\uff1a\n\n- URL: `{webhook_url}`\n- Method: POST\n\n\u8acb\u53c3\u95b1 [\u6587\u4ef6]({docs_url})\u4ee5\u4e86\u89e3\u66f4\u8a73\u7d30\u8cc7\u6599\u3002"
},
"step": {
"user": {
"description": "\u662f\u5426\u8981\u8a2d\u5b9a GPSLogger Webhook\uff1f",
"title": "\u8a2d\u5b9a GPSLogger Webhook"
}
},
"title": "GPSLogger Webhook"
}
}

View File

@@ -1,7 +1,7 @@
{
"config": {
"abort": {
"already_configured": "Dostopna to\u010dka je \u017ee konfigurirana",
"already_configured": "Dostopna to\u010dka je \u017ee nastavljena",
"connection_aborted": "Povezava s stre\u017enikom HMIP ni bila mogo\u010da",
"unknown": "Pri\u0161lo je do neznane napake"
},
@@ -21,8 +21,8 @@
"title": "Izberite dostopno to\u010dko HomematicIP"
},
"link": {
"description": "Pritisnite modro tipko na dostopni to\u010dko in gumb po\u0161lji, da registrirate homematicIP s Home Assistentom. \n\n![Location of button on bridge](/static/images/config_flows/config_homematicip_cloud.png)",
"title": "Pove\u017eite dostopno to\u010dno"
"description": "Pritisnite modro tipko na dostopni to\u010dko in gumb po\u0161lji, da registrirate homematicIP s Home Assistantom. \n\n![Location of button on bridge](/static/images/config_flows/config_homematicip_cloud.png)",
"title": "Pove\u017eite dostopno to\u010dko"
}
},
"title": "HomematicIP Cloud"

View File

@@ -1,7 +1,7 @@
{
"config": {
"abort": {
"already_configured": "\u63a5\u5165\u70b9\u5df2\u7ecf\u914d\u7f6e\u5b8c\u6210",
"already_configured": "\u63a5\u5165\u70b9\u5df2\u914d\u7f6e",
"connection_aborted": "\u65e0\u6cd5\u8fde\u63a5\u5230 HMIP \u670d\u52a1\u5668",
"unknown": "\u53d1\u751f\u672a\u77e5\u9519\u8bef\u3002"
},

View File

@@ -25,8 +25,7 @@ from .auth import setup_auth
from .ban import setup_bans
from .cors import setup_cors
from .real_ip import setup_real_ip
from .static import (
CachingFileResponse, CachingStaticResource, staticresource_middleware)
from .static import CachingFileResponse, CachingStaticResource
# Import as alias
from .const import KEY_AUTHENTICATED, KEY_REAL_IP # noqa
@@ -192,8 +191,7 @@ class HomeAssistantHTTP:
use_x_forwarded_for, trusted_proxies, trusted_networks,
login_threshold, is_ban_enabled, ssl_profile):
"""Initialize the HTTP Home Assistant server."""
app = self.app = web.Application(
middlewares=[staticresource_middleware])
app = self.app = web.Application(middlewares=[])
# This order matters
setup_real_ip(app, use_x_forwarded_for, trusted_proxies)

View File

@@ -1,15 +1,10 @@
"""Static file handling for HTTP component."""
import re
from aiohttp import hdrs
from aiohttp.web import FileResponse, middleware
from aiohttp.web import FileResponse
from aiohttp.web_exceptions import HTTPNotFound
from aiohttp.web_urldispatcher import StaticResource
from yarl import URL
_FINGERPRINT = re.compile(r'^(.+)-[a-z0-9]{32}\.(\w+)$', re.IGNORECASE)
class CachingStaticResource(StaticResource):
"""Static Resource handler that will add cache headers."""
@@ -56,19 +51,3 @@ class CachingFileResponse(FileResponse):
# Overwriting like this because __init__ can change implementation.
self._sendfile = sendfile
@middleware
async def staticresource_middleware(request, handler):
"""Middleware to strip out fingerprint from fingerprinted assets."""
path = request.path
if not path.startswith('/static/') and not path.startswith('/frontend'):
return await handler(request)
fingerprinted = _FINGERPRINT.match(request.match_info['filename'])
if fingerprinted:
request.match_info['filename'] = \
'{}.{}'.format(*fingerprinted.groups())
return await handler(request)

View File

@@ -24,6 +24,6 @@
"title": "Hub Link"
}
},
"title": "Mostek Philips Hue"
"title": "Philips Hue"
}
}

View File

@@ -20,7 +20,7 @@
"title": "Izberite Hue most"
},
"link": {
"description": "Pritisnite gumb na mostu, da registrirate Philips Hue s Home Assistentom. \n\n ! [Polo\u017eaj gumba na mostu] (/static/images/config_philips_hue.jpg)",
"description": "Pritisnite gumb na mostu, da registrirate Philips Hue s Home Assistantom. \n\n ! [Polo\u017eaj gumba na mostu] (/static/images/config_philips_hue.jpg)",
"title": "Link Hub"
}
},

View File

@@ -1,11 +1,11 @@
{
"config": {
"abort": {
"not_internet_accessible": "Va\u0161 Home Assistent mora biti dostopek prek interneta, da boste lahko prejemali IFTTT sporo\u010dila.",
"not_internet_accessible": "Va\u0161 Home Assistant mora biti dostopek prek interneta, da boste lahko prejemali IFTTT sporo\u010dila.",
"one_instance_allowed": "Potrebna je samo ena instanca."
},
"create_entry": {
"default": "\u010ce \u017eelite poslati dogodke Home Assistent-u, boste morali uporabiti akcijo \u00bbNaredi spletno zahtevo\u00ab iz orodja [IFTTT Webhook applet] ( {applet_url} ). \n\n Izpolnite naslednje podatke: \n\n - URL: ` {webhook_url} ` \n - Metoda: POST \n - Vrsta vsebine: application/json \n\n Poglejte si [dokumentacijo] ( {docs_url} ) o tem, kako konfigurirati avtomatizacijo za obdelavo dohodnih podatkov."
"default": "\u010ce \u017eelite poslati dogodke Home Assistant-u, boste morali uporabiti akcijo \u00bbNaredi spletno zahtevo\u00ab iz orodja [IFTTT Webhook applet] ( {applet_url} ). \n\n Izpolnite naslednje podatke: \n\n - URL: ` {webhook_url} ` \n - Metoda: POST \n - Vrsta vsebine: application/json \n\n Poglejte si [dokumentacijo] ( {docs_url} ) o tem, kako konfigurirati avtomatizacijo za obdelavo dohodnih podatkov."
},
"step": {
"user": {

View File

@@ -0,0 +1,18 @@
{
"config": {
"abort": {
"not_internet_accessible": "La inst\u00e0ncia de Home Assistant ha de ser accessible des d'Internet per rebre missatges de Geofency.",
"one_instance_allowed": "Nom\u00e9s cal una sola inst\u00e0ncia."
},
"create_entry": {
"default": "Per enviar ubicacions a Home Assistant, haur\u00e0s de configurar l'opci\u00f3 webhook de l'aplicaci\u00f3 Locative.\n\nCompleta la seg\u00fcent informaci\u00f3:\n\n- URL: `{webhook_url}` \n- M\u00e8tode: POST \n\nConsulta la [documentaci\u00f3]({docs_url}) per a m\u00e9s detalls."
},
"step": {
"user": {
"description": "Est\u00e0s segur que vols configurar el Webhook Locative?",
"title": "Configuraci\u00f3 del Webhook Locative"
}
},
"title": "Webhook Locative"
}
}

View File

@@ -1,18 +1,18 @@
{
"config": {
"title": "Locative Webhook",
"step": {
"user": {
"title": "Set up the Locative Webhook",
"description": "Are you sure you want to set up the Locative Webhook?"
}
},
"abort": {
"one_instance_allowed": "Only a single instance is necessary.",
"not_internet_accessible": "Your Home Assistant instance needs to be accessible from the internet to receive messages from Geofency."
},
"create_entry": {
"default": "To send locations to Home Assistant, you will need to setup the webhook feature in the Locative app.\n\nFill in the following info:\n\n- URL: `{webhook_url}`\n- Method: POST\n\nSee [the documentation]({docs_url}) for further details."
"config": {
"abort": {
"not_internet_accessible": "Your Home Assistant instance needs to be accessible from the internet to receive messages from Geofency.",
"one_instance_allowed": "Only a single instance is necessary."
},
"create_entry": {
"default": "To send locations to Home Assistant, you will need to setup the webhook feature in the Locative app.\n\nFill in the following info:\n\n- URL: `{webhook_url}`\n- Method: POST\n\nSee [the documentation]({docs_url}) for further details."
},
"step": {
"user": {
"description": "Are you sure you want to set up the Locative Webhook?",
"title": "Set up the Locative Webhook"
}
},
"title": "Locative Webhook"
}
}
}

View File

@@ -0,0 +1,18 @@
{
"config": {
"abort": {
"not_internet_accessible": "Locative \uba54\uc2dc\uc9c0\ub97c \ubc1b\uc73c\ub824\uba74 \uc778\ud130\ub137\uc5d0\uc11c Home Assistant \uc778\uc2a4\ud134\uc2a4\uc5d0 \uc561\uc138\uc2a4 \ud560 \uc218 \uc788\uc5b4\uc57c \ud569\ub2c8\ub2e4.",
"one_instance_allowed": "\ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \ud544\uc694\ud569\ub2c8\ub2e4."
},
"create_entry": {
"default": "Home Assistant \ub85c \uc774\ubca4\ud2b8\ub97c \ubcf4\ub0b4\ub824\uba74 Locative \uc571\uc5d0\uc11c Webhook \uc744 \uc124\uc815\ud574\uc57c\ud569\ub2c8\ub2e4. \n\n\ub2e4\uc74c \uc815\ubcf4\ub97c \uc785\ub825\ud574 \uc8fc\uc138\uc694. \n\n - URL: `{webhook_url}`\n - Method: POST\n \n \uc790\uc138\ud55c \uc815\ubcf4\ub294 [\uc548\ub0b4]({docs_url}) \ub97c \ucc38\uc870\ud574 \uc8fc\uc138\uc694."
},
"step": {
"user": {
"description": "Locative Webhook \uc744 \uc124\uc815 \ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?",
"title": "Locative Webhook \uc124\uc815"
}
},
"title": "Locative Webhook"
}
}

View File

@@ -0,0 +1,18 @@
{
"config": {
"abort": {
"not_internet_accessible": "\u00c4r Home Assistant Instanz muss iwwert Internet accessibel si fir Geofency Noriichten z'empf\u00e4nken.",
"one_instance_allowed": "N\u00ebmmen eng eenzeg Instanz ass n\u00e9ideg."
},
"create_entry": {
"default": "Fir Plazen un Home Assistant ze sch\u00e9cken, muss den Webhook Feature an der Locative App ageriicht ginn.\n\nF\u00ebllt folgend Informatiounen aus:\n\n- URL: `{webhook_url}`\n- Method: POST\n\nLiest [Dokumentatioun]({docs_url}) fir w\u00e9ider D\u00e9tailer."
},
"step": {
"user": {
"description": "S\u00e9cher fir Locative Webhook anzeriichten?",
"title": "Locative Webhook ariichten"
}
},
"title": "Locative Webhook"
}
}

View File

@@ -0,0 +1,18 @@
{
"config": {
"abort": {
"not_internet_accessible": "Home Assistant m\u00e5 v\u00e6re tilgjengelig fra internett for \u00e5 kunne motta meldinger fra Geofency.",
"one_instance_allowed": "Kun en enkelt forekomst er n\u00f8dvendig."
},
"create_entry": {
"default": "For \u00e5 kunne sende steder til Home Assistant, m\u00e5 du sette opp webhook-funksjonen i Locative. \n\n Fyll ut f\u00f8lgende informasjon: \n\n - URL: `{webhook_url}` \n - Metode: POST \n\n Se [dokumentasjonen]({docs_url}) for ytterligere detaljer."
},
"step": {
"user": {
"description": "Er du sikker p\u00e5 at du vil sette opp Locative Webhook?",
"title": "Sett opp Lokative Webhook"
}
},
"title": "Lokative Webhook"
}
}

View File

@@ -0,0 +1,18 @@
{
"config": {
"abort": {
"not_internet_accessible": "Twoja instancja Home Assistant musi by\u0107 dost\u0119pna z Internetu, aby otrzymywa\u0107 wiadomo\u015bci z Geofency.",
"one_instance_allowed": "Wymagana jest tylko jedna instancja."
},
"create_entry": {
"default": "Aby wysy\u0142a\u0107 lokalizacje do Home Assistant'a, musisz skonfigurowa\u0107 webhook w aplikacji Locative. \n\n Wprowad\u017a nast\u0119puj\u0105ce dane:\n\n - URL: `{webhook_url}` \n - Metoda: POST \n\nZapoznaj si\u0119 z [dokumentacj\u0105]({docs_url}) by pozna\u0107 szczeg\u00f3\u0142y."
},
"step": {
"user": {
"description": "Czy na pewno chcesz skonfigurowa\u0107 Locative Webhook?",
"title": "Skonfiguruj Locative Webhook"
}
},
"title": "Locative Webhook"
}
}

View File

@@ -0,0 +1,17 @@
{
"config": {
"abort": {
"not_internet_accessible": "\u0412\u0430\u0448 Home Assistant \u0434\u043e\u043b\u0436\u0435\u043d \u0431\u044b\u0442\u044c \u0434\u043e\u0441\u0442\u0443\u043f\u0435\u043d \u0438\u0437 \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442\u0430 \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 Locative."
},
"create_entry": {
"default": "\u0414\u043b\u044f \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 \u0441\u043e\u0431\u044b\u0442\u0438\u0439 \u0432 Home Assistant \u0432\u044b \u0434\u043e\u043b\u0436\u043d\u044b \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c webhooks \u0434\u043b\u044f Locative.\n\n\u0414\u043b\u044f \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0443\u044e \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e:\n\n- URL: `{webhook_url}`\n- Method: POST\n\n\u041e\u0437\u043d\u0430\u043a\u043e\u043c\u044c\u0442\u0435\u0441\u044c \u0441 [\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u0435\u0439]({docs_url}) \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0431\u043e\u043b\u0435\u0435 \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u043e\u0439 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438."
},
"step": {
"user": {
"description": "\u0412\u044b \u0443\u0432\u0435\u0440\u0435\u043d\u044b, \u0447\u0442\u043e \u0445\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c Locative?",
"title": "Locative"
}
},
"title": "Locative"
}
}

View File

@@ -0,0 +1,18 @@
{
"config": {
"abort": {
"not_internet_accessible": "Va\u0161 Home Assistant mora biti dostopek prek interneta, da boste lahko prejemali Geofency sporo\u010dila.",
"one_instance_allowed": "Potrebna je samo ena instanca."
},
"create_entry": {
"default": "Za po\u0161iljanje lokacij v Home Assistant, morate namestiti funkcijo webhook v aplikaciji Locative. \n\n Izpolnite naslednje podatke: \n\n - URL: ` {webhook_url} ` \n - Metoda: POST \n\n Za ve\u010d podrobnosti si oglejte [dokumentacijo] ( {docs_url} )."
},
"step": {
"user": {
"description": "Ali ste prepri\u010dani, da \u017eelite nastaviti Locative Webhook?",
"title": "Nastavite Locative Webhook"
}
},
"title": "Locative Webhook"
}
}

View File

@@ -0,0 +1,15 @@
{
"config": {
"abort": {
"not_internet_accessible": "\u60a8\u7684Home Assistant\u5b9e\u4f8b\u9700\u8981\u53ef\u4ee5\u4eceInternet\u8bbf\u95ee\u4ee5\u63a5\u6536\u6765\u81eaGeofency\u7684\u6d88\u606f\u3002",
"one_instance_allowed": "\u53ea\u9700\u8981\u4e00\u4e2a\u5b9e\u4f8b\u3002"
},
"step": {
"user": {
"description": "\u60a8\u786e\u5b9a\u8981\u8bbe\u7f6e\u5b9a\u4f4d Webhook\u5417\uff1f",
"title": "\u8bbe\u7f6e\u5b9a\u4f4d Webhook"
}
},
"title": "\u5b9a\u4f4d Webhook"
}
}

View File

@@ -0,0 +1,18 @@
{
"config": {
"abort": {
"not_internet_accessible": "Home Assistant \u5be6\u4f8b\u5fc5\u9808\u80fd\u5920\u7531\u7db2\u969b\u7db2\u8def\u5b58\u53d6\uff0c\u65b9\u80fd\u63a5\u53d7 Locative \u8a0a\u606f\u3002",
"one_instance_allowed": "\u50c5\u9700\u8a2d\u5b9a\u4e00\u7d44\u7269\u4ef6\u5373\u53ef\u3002"
},
"create_entry": {
"default": "\u6b32\u50b3\u9001\u4f4d\u7f6e\u81f3 Home Assistant\uff0c\u5c07\u9700\u65bc Locative App \u5167\u8a2d\u5b9a webhook \u529f\u80fd\u3002\n\n\u8acb\u586b\u5beb\u4e0b\u5217\u8cc7\u8a0a\uff1a\n\n- URL: `{webhook_url}`\n- Method: POST\n\n\u8acb\u53c3\u95b1 [\u6587\u4ef6]({docs_url})\u4ee5\u4e86\u89e3\u66f4\u8a73\u7d30\u8cc7\u6599\u3002"
},
"step": {
"user": {
"description": "\u662f\u5426\u8981\u8a2d\u5b9a Locative Webhook\uff1f",
"title": "\u8a2d\u5b9a Locative Webhook"
}
},
"title": "Locative Webhook"
}
}

View File

@@ -48,8 +48,8 @@ WEBHOOK_SCHEMA = vol.All(
vol.Required(ATTR_LONGITUDE): cv.longitude,
vol.Required(ATTR_DEVICE_ID): cv.string,
vol.Required(ATTR_TRIGGER): cv.string,
vol.Optional(ATTR_ID): vol.All(cv.string, _id)
}),
vol.Optional(ATTR_ID): vol.All(cv.string, _id),
}, extra=vol.ALLOW_EXTRA),
_validate_test_mode
)

View File

@@ -1,11 +1,11 @@
{
"config": {
"abort": {
"not_internet_accessible": " \u010ce \u017eelite prejemati sporo\u010dila Mailgun, mora biti Home Assistent dostopen prek interneta.",
"not_internet_accessible": "\u010ce \u017eelite prejemati sporo\u010dila Mailgun, mora biti Home Assistant dostopen prek interneta.",
"one_instance_allowed": "Potrebna je samo ena instanca."
},
"create_entry": {
"default": "Za po\u0161iljanje dogodkov Home Assistentu boste morali nastaviti [Webhooks z Mailgun]({mailgun_url}).\n\nIzpolnite naslednje informacije:\n\n- URL: `{webhook_url}`\n- Metoda: POST\n- Vrsta vsebine: application/json\n\nGlej [dokumentacijo]({docs_url}) o tem, kako nastavite automations za obravnavo dohodnih podatkov."
"default": "Za po\u0161iljanje dogodkov Home Assistantu boste morali nastaviti [Webhooks z Mailgun]({mailgun_url}).\n\nIzpolnite naslednje informacije:\n\n- URL: `{webhook_url}`\n- Metoda: POST\n- Vrsta vsebine: application/json\n\nGlej [dokumentacijo]({docs_url}) o tem, kako nastavite automations za obravnavo dohodnih podatkov."
},
"step": {
"user": {

View File

@@ -6,6 +6,13 @@
},
"create_entry": {
"default": "\u8981\u5411 Home Assistant \u53d1\u9001\u4e8b\u4ef6\uff0c\u60a8\u9700\u8981\u914d\u7f6e [Mailgun \u7684 Webhook]({mailgun_url})\u3002\n\n\u586b\u5199\u4ee5\u4e0b\u4fe1\u606f\uff1a\n\n- URL: `{webhook_url}`\n- Method: POST\n- Content Type: application/json\n\n\u6709\u5173\u5982\u4f55\u914d\u7f6e\u81ea\u52a8\u5316\u4ee5\u5904\u7406\u4f20\u5165\u7684\u6570\u636e\uff0c\u8bf7\u53c2\u9605[\u6587\u6863]({docs_url})\u3002"
}
},
"step": {
"user": {
"description": "\u60a8\u786e\u5b9a\u8981\u8bbe\u7f6e Mailgun \u5417\uff1f",
"title": "\u8bbe\u7f6e Mailgun Webhook"
}
},
"title": "Mailgun"
}
}

View File

@@ -26,7 +26,7 @@ from homeassistant.helpers.aiohttp_client import async_get_clientsession
import homeassistant.helpers.config_validation as cv
from homeassistant.util import get_local_ip
REQUIREMENTS = ['async-upnp-client==0.14.3']
REQUIREMENTS = ['async-upnp-client==0.14.4']
_LOGGER = logging.getLogger(__name__)

View File

@@ -22,7 +22,7 @@
"data": {
"discovery": "Omogo\u010di odkrivanje"
},
"description": "\u017delite konfigurirati Home Assistent-a za povezavo s posrednikom MQTT, ki ga ponuja hass.io add-on {addon} ?",
"description": "\u017delite konfigurirati Home Assistant-a za povezavo s posrednikom MQTT, ki ga ponuja hass.io add-on {addon} ?",
"title": "MQTT Broker prek dodatka Hass.io"
}
},

View File

@@ -24,8 +24,8 @@
"data": {
"code": "PIN \u7801"
},
"description": "\u8981\u5173\u8054 Nest \u5e10\u6237\uff0c\u8bf7[\u6388\u6743\u5e10\u6237]({url})\u3002\n\n\u5b8c\u6210\u6388\u6743\u540e\uff0c\u5728\u4e0b\u9762\u7c98\u8d34\u83b7\u5f97\u7684 PIN \u7801\u3002",
"title": "\u5173\u8054 Nest \u5e10\u6237"
"description": "\u8981\u5173\u8054 Nest \u8d26\u6237\uff0c\u8bf7[\u6388\u6743\u8d26\u6237]({url})\u3002\n\n\u5b8c\u6210\u6388\u6743\u540e\uff0c\u5728\u4e0b\u9762\u7c98\u8d34\u83b7\u5f97\u7684 PIN \u7801\u3002",
"title": "\u5173\u8054 Nest \u8d26\u6237"
}
},
"title": "Nest"

View File

@@ -2,12 +2,12 @@
"config": {
"error": {
"identifier_exists": "Les coordenades ja estan registrades",
"invalid_api_key": "Contrasenya API no v\u00e0lida"
"invalid_api_key": "Clau API no v\u00e0lida"
},
"step": {
"user": {
"data": {
"api_key": "Contrasenya API d'OpenUV",
"api_key": "Clau API d'OpenUV",
"elevation": "Elevaci\u00f3",
"latitude": "Latitud",
"longitude": "Longitud"

View File

@@ -1,8 +1,17 @@
{
"config": {
"abort": {
"authorize_url_fail": "\u751f\u6210\u6388\u6743URL\u65f6\u53d1\u751f\u672a\u77e5\u9519\u8bef\u3002",
"authorize_url_timeout": "\u751f\u6210\u6388\u6743URL\u8d85\u65f6"
},
"error": {
"follow_link": "\u8bf7\u5728\u70b9\u51fb\u63d0\u4ea4\u524d\u6309\u7167\u94fe\u63a5\u8fdb\u884c\u8eab\u4efd\u9a8c\u8bc1",
"no_token": "\u672a\u7ecfMinut\u9a8c\u8bc1"
},
"step": {
"auth": {
"description": "\u8bf7\u8bbf\u95ee\u4e0b\u65b9\u7684\u94fe\u63a5\u5e76<b>\u5141\u8bb8</b>\u8bbf\u95ee\u60a8\u7684 Minut \u8d26\u6237\uff0c\u7136\u540e\u56de\u6765\u70b9\u51fb\u4e0b\u9762\u7684<b>\u63d0\u4ea4</b>\u3002\n\n[\u94fe\u63a5]({authorization_url})"
"description": "\u8bf7\u8bbf\u95ee\u4e0b\u65b9\u7684\u94fe\u63a5\u5e76<b>\u5141\u8bb8</b>\u8bbf\u95ee\u60a8\u7684 Minut \u8d26\u6237\uff0c\u7136\u540e\u56de\u6765\u70b9\u51fb\u4e0b\u9762\u7684<b>\u63d0\u4ea4</b>\u3002\n\n[\u94fe\u63a5]({authorization_url})",
"title": "\u8ba4\u8bc1\u70b9"
},
"user": {
"data": {

View File

@@ -1,11 +1,13 @@
{
"config": {
"error": {
"identifier_exists": "\u5e10\u6237\u5df2\u6ce8\u518c"
"identifier_exists": "\u8d26\u6237\u5df2\u6ce8\u518c",
"invalid_credentials": "\u65e0\u6548\u7684\u8eab\u4efd\u8ba4\u8bc1"
},
"step": {
"user": {
"data": {
"ip_address": "\u4e3b\u673a\u540d\u6216IP\u5730\u5740",
"password": "\u5bc6\u7801",
"port": "\u7aef\u53e3"
},

View File

@@ -1,7 +1,7 @@
{
"config": {
"error": {
"identifier_exists": "\u5e10\u6237\u5df2\u6ce8\u518c",
"identifier_exists": "\u8d26\u6237\u5df2\u6ce8\u518c",
"invalid_credentials": "\u65e0\u6548\u7684\u8eab\u4efd\u8ba4\u8bc1"
},
"step": {

View File

@@ -0,0 +1,27 @@
{
"config": {
"error": {
"app_not_installed": "Assegura't que has instal\u00b7lat i autoritzat l'aplicaci\u00f3 SmartApp de Home Assistant i torna-ho a provar.",
"app_setup_error": "No s'ha pogut configurar SmartApp. Siusplau, torna-ho a provar.",
"base_url_not_https": "L'`base_url` per al component `http` ha d'estar configurat i comen\u00e7ar amb `https://`.",
"token_already_setup": "El testimoni d'autenticaci\u00f3 ja ha estat configurat.",
"token_forbidden": "El testimoni d'autenticaci\u00f3 no t\u00e9 cont\u00e9 els apartats OAuth obligatoris.",
"token_invalid_format": "El testimoni d'autenticaci\u00f3 ha d'estar en format UID/GUID",
"token_unauthorized": "El testimoni d'autenticaci\u00f3 no \u00e9s v\u00e0lid o ja no t\u00e9 autoritzaci\u00f3."
},
"step": {
"user": {
"data": {
"access_token": "Testimoni d'acc\u00e9s"
},
"description": "Introdueix un [testimoni d'autenticaci\u00f3 d'acc\u00e9s personal] ({token_url}) de SmartThings que s'ha creat a trav\u00e9s les [instruccions] ({component_url}).",
"title": "Introdueix el testimoni d'autenticaci\u00f3 d'acc\u00e9s personal"
},
"wait_install": {
"description": "Instal\u00b7la l'SmartApp de Home Assistant en almenys una ubicaci\u00f3 i fes clic a Enviar.",
"title": "Instal\u00b7laci\u00f3 de SmartApp"
}
},
"title": "SmartThings"
}
}

View File

@@ -1,27 +1,27 @@
{
"config": {
"title": "SmartThings",
"step": {
"user": {
"title": "Enter Personal Access Token",
"description": "Please enter a SmartThings [Personal Access Token]({token_url}) that has been created per the [instructions]({component_url}).",
"data": {
"access_token": "Access Token"
}
},
"wait_install": {
"title": "Install SmartApp",
"description": "Please install the Home Assistant SmartApp in at least one location and click submit."
}
},
"error": {
"token_invalid_format": "The token must be in the UID/GUID format",
"token_unauthorized": "The token is invalid or no longer authorized.",
"token_forbidden": "The token does not have the required OAuth scopes.",
"token_already_setup": "The token has already been setup.",
"app_setup_error": "Unable to setup the SmartApp. Please try again.",
"app_not_installed": "Please ensure you have installed and authorized the Home Assistant SmartApp and try again.",
"base_url_not_https": "The `base_url` for the `http` component must be configured and start with `https://`."
"config": {
"error": {
"app_not_installed": "Please ensure you have installed and authorized the Home Assistant SmartApp and try again.",
"app_setup_error": "Unable to setup the SmartApp. Please try again.",
"base_url_not_https": "The `base_url` for the `http` component must be configured and start with `https://`.",
"token_already_setup": "The token has already been setup.",
"token_forbidden": "The token does not have the required OAuth scopes.",
"token_invalid_format": "The token must be in the UID/GUID format",
"token_unauthorized": "The token is invalid or no longer authorized."
},
"step": {
"user": {
"data": {
"access_token": "Access Token"
},
"description": "Please enter a SmartThings [Personal Access Token]({token_url}) that has been created per the [instructions]({component_url}).",
"title": "Enter Personal Access Token"
},
"wait_install": {
"description": "Please install the Home Assistant SmartApp in at least one location and click submit.",
"title": "Install SmartApp"
}
},
"title": "SmartThings"
}
}
}

View File

@@ -0,0 +1,27 @@
{
"config": {
"error": {
"app_not_installed": "Stellt w.e.g s\u00e9cher dass d'Home Assistant SmartApp install\u00e9iert an autoris\u00e9iert ass, a prob\u00e9iert nach emol.",
"app_setup_error": "Kann SmartApp net install\u00e9ieren. Prob\u00e9iert w.e.g. nach emol.",
"base_url_not_https": "`base_url` fir den `http` Komponent muss konfigur\u00e9iert sinn a mat `https://`uf\u00e4nken.",
"token_already_setup": "Den Jeton gouf schonn ageriicht.",
"token_forbidden": "De Jeton huet net d\u00e9i n\u00e9ideg OAuth M\u00e9iglechkeeten.",
"token_invalid_format": "De Jeton muss am UID/GUID Format sinn",
"token_unauthorized": "De Jeton ass ong\u00eblteg oder net m\u00e9i autoris\u00e9iert."
},
"step": {
"user": {
"data": {
"access_token": "Acc\u00e8ss Jeton"
},
"description": "Gitt w.e.g. ee [Pers\u00e9inlechen Acc\u00e8s Jeton]({token_url}) vu SmartThings an dee via [d'Instruktiounen] ({component_url}) erstallt gouf.",
"title": "Pers\u00e9inlechen Acc\u00e8ss Jeton uginn"
},
"wait_install": {
"description": "Install\u00e9iert d'Home Assistant SmartApp op mannst ee mol a klickt op Ofsch\u00e9cken.",
"title": "SmartApp install\u00e9ieren"
}
},
"title": "SmartThings"
}
}

View File

@@ -0,0 +1,10 @@
{
"config": {
"step": {
"wait_install": {
"title": "Zainstaluj SmartApp"
}
},
"title": "SmartThings"
}
}

View File

@@ -0,0 +1,27 @@
{
"config": {
"error": {
"app_not_installed": "\u8acb\u78ba\u8a8d\u5df2\u7d93\u5b89\u88dd\u4e26\u6388\u6b0a Home Assistant Smartapp \u5f8c\u518d\u8a66\u4e00\u6b21\u3002",
"app_setup_error": "\u7121\u6cd5\u8a2d\u5b9a SmartApp\uff0c\u8acb\u518d\u8a66\u4e00\u6b21\u3002",
"base_url_not_https": "\u5fc5\u9808\u8a2d\u5b9a\u300chttp\u300d\u5143\u4ef6\u4e4b\u300cbase_url\u300d\uff0c\u4e26\u4ee5\u300chttps://\u300d\u70ba\u958b\u982d\u3002",
"token_already_setup": "\u5bc6\u9470\u5df2\u8a2d\u5b9a\u904e\u3002",
"token_forbidden": "\u5bc6\u9470\u4e0d\u5177\u6240\u9700\u7684 OAuth \u7bc4\u570d\u3002",
"token_invalid_format": "\u5bc6\u9470\u5fc5\u9808\u70ba UID/GUID \u683c\u5f0f",
"token_unauthorized": "\u5bc6\u9470\u7121\u6548\u6216\u4e0d\u518d\u5177\u6709\u6388\u6b0a\u3002"
},
"step": {
"user": {
"data": {
"access_token": "\u5b58\u53d6\u5bc6\u9470"
},
"description": "\u8acb\u8f38\u5165\u8ddf\u8457[ \u6307\u5f15]({component_url})\u6240\u7522\u751f\u7684 SmartThings [\u500b\u4eba\u5b58\u53d6\u5bc6\u9470]({token_url})\u3002",
"title": "\u8f38\u5165\u500b\u4eba\u5b58\u53d6\u5bc6\u9470"
},
"wait_install": {
"description": "\u8acb\u81f3\u5c11\u65bc\u4e00\u500b\u4f4d\u7f6e\u4e2d\u5b89\u88dd Home Assistant Smartapp\uff0c\u4e26\u9ede\u9078\u50b3\u9001\u3002",
"title": "\u5b89\u88dd SmartApp"
}
},
"title": "SmartThings"
}
}

View File

@@ -19,7 +19,7 @@ from homeassistant.helpers.typing import ConfigType, HomeAssistantType
from .config_flow import SmartThingsFlowHandler # noqa
from .const import (
CONF_APP_ID, CONF_INSTALLED_APP_ID, DATA_BROKERS, DATA_MANAGER, DOMAIN,
SIGNAL_SMARTTHINGS_UPDATE, SUPPORTED_PLATFORMS)
EVENT_BUTTON, SIGNAL_SMARTTHINGS_UPDATE, SUPPORTED_PLATFORMS)
from .smartapp import (
setup_smartapp, setup_smartapp_endpoint, validate_installed_app)
@@ -154,6 +154,19 @@ class DeviceBroker:
continue
device.status.apply_attribute_update(
evt.component_id, evt.capability, evt.attribute, evt.value)
# Fire events for buttons
if evt.capability == 'button' and evt.attribute == 'button':
data = {
'component_id': evt.component_id,
'device_id': evt.device_id,
'location_id': evt.location_id,
'value': evt.value,
'name': device.label
}
self._hass.bus.async_fire(EVENT_BUTTON, data)
_LOGGER.debug("Fired button event: %s", data)
updated_devices.add(device.device_id)
_LOGGER.debug("Update received with %s events and updated %s devices",
len(req.events), len(updated_devices))

View File

@@ -0,0 +1,81 @@
"""
Support for binary sensors through the SmartThings cloud API.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/smartthings.binary_sensor/
"""
from homeassistant.components.binary_sensor import BinarySensorDevice
from . import SmartThingsEntity
from .const import DATA_BROKERS, DOMAIN
DEPENDENCIES = ['smartthings']
CAPABILITY_TO_ATTRIB = {
'accelerationSensor': 'acceleration',
'contactSensor': 'contact',
'filterStatus': 'filterStatus',
'motionSensor': 'motion',
'presenceSensor': 'presence',
'soundSensor': 'sound',
'tamperAlert': 'tamper',
'valve': 'valve',
'waterSensor': 'water'
}
ATTRIB_TO_CLASS = {
'acceleration': 'moving',
'contact': 'opening',
'filterStatus': 'problem',
'motion': 'motion',
'presence': 'presence',
'sound': 'sound',
'tamper': 'problem',
'valve': 'opening',
'water': 'moisture'
}
async def async_setup_platform(hass, config, async_add_entities,
discovery_info=None):
"""Platform uses config entry setup."""
pass
async def async_setup_entry(hass, config_entry, async_add_entities):
"""Add binary sensors for a config entry."""
broker = hass.data[DOMAIN][DATA_BROKERS][config_entry.entry_id]
sensors = []
for device in broker.devices.values():
for capability, attrib in CAPABILITY_TO_ATTRIB.items():
if capability in device.capabilities:
sensors.append(SmartThingsBinarySensor(device, attrib))
async_add_entities(sensors)
class SmartThingsBinarySensor(SmartThingsEntity, BinarySensorDevice):
"""Define a SmartThings Binary Sensor."""
def __init__(self, device, attribute):
"""Init the class."""
super().__init__(device)
self._attribute = attribute
@property
def name(self) -> str:
"""Return the name of the binary sensor."""
return '{} {}'.format(self._device.label, self._attribute)
@property
def unique_id(self) -> str:
"""Return a unique ID."""
return '{}.{}'.format(self._device.device_id, self._attribute)
@property
def is_on(self):
"""Return true if the binary sensor is on."""
return self._device.status.is_on(self._attribute)
@property
def device_class(self):
"""Return the class of this device."""
return ATTRIB_TO_CLASS[self._attribute]

View File

@@ -12,19 +12,34 @@ CONF_LOCATION_ID = 'location_id'
DATA_MANAGER = 'manager'
DATA_BROKERS = 'brokers'
DOMAIN = 'smartthings'
EVENT_BUTTON = "smartthings.button"
SIGNAL_SMARTTHINGS_UPDATE = 'smartthings_update'
SIGNAL_SMARTAPP_PREFIX = 'smartthings_smartap_'
SETTINGS_INSTANCE_ID = "hassInstanceId"
STORAGE_KEY = DOMAIN
STORAGE_VERSION = 1
SUPPORTED_PLATFORMS = [
'binary_sensor',
'fan',
'light',
'switch'
]
SUPPORTED_CAPABILITIES = [
'accelerationSensor',
'button',
'colorControl',
'colorTemperature',
'contactSensor',
'fanSpeed',
'filterStatus',
'motionSensor',
'presenceSensor',
'soundSensor',
'switch',
'switchLevel'
'switchLevel',
'tamperAlert',
'valve',
'waterSensor'
]
VAL_UID = "^(?:([0-9a-fA-F]{32})|([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]" \
"{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}))$"

View File

@@ -0,0 +1,96 @@
"""
Support for fans through the SmartThings cloud API.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/smartthings.fan/
"""
from homeassistant.components.fan import (
SPEED_HIGH, SPEED_LOW, SPEED_MEDIUM, SPEED_OFF, SUPPORT_SET_SPEED,
FanEntity)
from . import SmartThingsEntity
from .const import DATA_BROKERS, DOMAIN
DEPENDENCIES = ['smartthings']
VALUE_TO_SPEED = {
0: SPEED_OFF,
1: SPEED_LOW,
2: SPEED_MEDIUM,
3: SPEED_HIGH,
}
SPEED_TO_VALUE = {
v: k for k, v in VALUE_TO_SPEED.items()}
async def async_setup_platform(
hass, config, async_add_entities, discovery_info=None):
"""Platform uses config entry setup."""
pass
async def async_setup_entry(hass, config_entry, async_add_entities):
"""Add fans for a config entry."""
broker = hass.data[DOMAIN][DATA_BROKERS][config_entry.entry_id]
async_add_entities(
[SmartThingsFan(device) for device in broker.devices.values()
if is_fan(device)])
def is_fan(device):
"""Determine if the device should be represented as a fan."""
from pysmartthings import Capability
# Must have switch and fan_speed
return all(capability in device.capabilities
for capability in [Capability.switch, Capability.fan_speed])
class SmartThingsFan(SmartThingsEntity, FanEntity):
"""Define a SmartThings Fan."""
async def async_set_speed(self, speed: str):
"""Set the speed of the fan."""
value = SPEED_TO_VALUE[speed]
await self._device.set_fan_speed(value, set_status=True)
# State is set optimistically in the command above, therefore update
# the entity state ahead of receiving the confirming push updates
self.async_schedule_update_ha_state()
async def async_turn_on(self, speed: str = None, **kwargs) -> None:
"""Turn the fan on."""
if speed is not None:
value = SPEED_TO_VALUE[speed]
await self._device.set_fan_speed(value, set_status=True)
else:
await self._device.switch_on(set_status=True)
# State is set optimistically in the commands above, therefore update
# the entity state ahead of receiving the confirming push updates
self.async_schedule_update_ha_state()
async def async_turn_off(self, **kwargs) -> None:
"""Turn the fan off."""
await self._device.switch_off(set_status=True)
# State is set optimistically in the command above, therefore update
# the entity state ahead of receiving the confirming push updates
self.async_schedule_update_ha_state()
@property
def is_on(self) -> bool:
"""Return true if fan is on."""
return self._device.status.switch
@property
def speed(self) -> str:
"""Return the current speed."""
return VALUE_TO_SPEED[self._device.status.fan_speed]
@property
def speed_list(self) -> list:
"""Get the list of available speeds."""
return [SPEED_OFF, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH]
@property
def supported_features(self) -> int:
"""Flag supported features."""
return SUPPORT_SET_SPEED

View File

@@ -0,0 +1,215 @@
"""
Support for lights through the SmartThings cloud API.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/smartthings.light/
"""
import asyncio
from homeassistant.components.light import (
ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_HS_COLOR, ATTR_TRANSITION,
SUPPORT_BRIGHTNESS, SUPPORT_COLOR, SUPPORT_COLOR_TEMP, SUPPORT_TRANSITION,
Light)
import homeassistant.util.color as color_util
from . import SmartThingsEntity
from .const import DATA_BROKERS, DOMAIN
DEPENDENCIES = ['smartthings']
async def async_setup_platform(
hass, config, async_add_entities, discovery_info=None):
"""Platform uses config entry setup."""
pass
async def async_setup_entry(hass, config_entry, async_add_entities):
"""Add lights for a config entry."""
broker = hass.data[DOMAIN][DATA_BROKERS][config_entry.entry_id]
async_add_entities(
[SmartThingsLight(device) for device in broker.devices.values()
if is_light(device)], True)
def is_light(device):
"""Determine if the device should be represented as a light."""
from pysmartthings import Capability
# Must be able to be turned on/off.
if Capability.switch not in device.capabilities:
return False
# Not a fan (which might also have switch_level)
if Capability.fan_speed in device.capabilities:
return False
# Must have one of these
light_capabilities = [
Capability.color_control,
Capability.color_temperature,
Capability.switch_level
]
if any(capability in device.capabilities
for capability in light_capabilities):
return True
return False
def convert_scale(value, value_scale, target_scale, round_digits=4):
"""Convert a value to a different scale."""
return round(value * target_scale / value_scale, round_digits)
class SmartThingsLight(SmartThingsEntity, Light):
"""Define a SmartThings Light."""
def __init__(self, device):
"""Initialize a SmartThingsLight."""
super().__init__(device)
self._brightness = None
self._color_temp = None
self._hs_color = None
self._supported_features = self._determine_features()
def _determine_features(self):
"""Get features supported by the device."""
from pysmartthings.device import Capability
features = 0
# Brightness and transition
if Capability.switch_level in self._device.capabilities:
features |= \
SUPPORT_BRIGHTNESS | SUPPORT_TRANSITION
# Color Temperature
if Capability.color_temperature in self._device.capabilities:
features |= SUPPORT_COLOR_TEMP
# Color
if Capability.color_control in self._device.capabilities:
features |= SUPPORT_COLOR
return features
async def async_turn_on(self, **kwargs) -> None:
"""Turn the light on."""
tasks = []
# Color temperature
if self._supported_features & SUPPORT_COLOR_TEMP \
and ATTR_COLOR_TEMP in kwargs:
tasks.append(self.async_set_color_temp(
kwargs[ATTR_COLOR_TEMP]))
# Color
if self._supported_features & SUPPORT_COLOR \
and ATTR_HS_COLOR in kwargs:
tasks.append(self.async_set_color(
kwargs[ATTR_HS_COLOR]))
if tasks:
# Set temp/color first
await asyncio.gather(*tasks)
# Switch/brightness/transition
if self._supported_features & SUPPORT_BRIGHTNESS \
and ATTR_BRIGHTNESS in kwargs:
await self.async_set_level(
kwargs[ATTR_BRIGHTNESS],
kwargs.get(ATTR_TRANSITION, 0))
else:
await self._device.switch_on(set_status=True)
# State is set optimistically in the commands above, therefore update
# the entity state ahead of receiving the confirming push updates
self.async_schedule_update_ha_state(True)
async def async_turn_off(self, **kwargs) -> None:
"""Turn the light off."""
# Switch/transition
if self._supported_features & SUPPORT_TRANSITION \
and ATTR_TRANSITION in kwargs:
await self.async_set_level(0, int(kwargs[ATTR_TRANSITION]))
else:
await self._device.switch_off(set_status=True)
# State is set optimistically in the commands above, therefore update
# the entity state ahead of receiving the confirming push updates
self.async_schedule_update_ha_state(True)
async def async_update(self):
"""Update entity attributes when the device status has changed."""
# Brightness and transition
if self._supported_features & SUPPORT_BRIGHTNESS:
self._brightness = convert_scale(
self._device.status.level, 100, 255)
# Color Temperature
if self._supported_features & SUPPORT_COLOR_TEMP:
self._color_temp = color_util.color_temperature_kelvin_to_mired(
self._device.status.color_temperature)
# Color
if self._supported_features & SUPPORT_COLOR:
self._hs_color = (
convert_scale(self._device.status.hue, 100, 360),
self._device.status.saturation
)
async def async_set_color(self, hs_color):
"""Set the color of the device."""
hue = convert_scale(float(hs_color[0]), 360, 100)
hue = max(min(hue, 100.0), 0.0)
saturation = max(min(float(hs_color[1]), 100.0), 0.0)
await self._device.set_color(
hue, saturation, set_status=True)
async def async_set_color_temp(self, value: float):
"""Set the color temperature of the device."""
kelvin = color_util.color_temperature_mired_to_kelvin(value)
kelvin = max(min(kelvin, 30000.0), 1.0)
await self._device.set_color_temperature(
kelvin, set_status=True)
async def async_set_level(self, brightness: int, transition: int):
"""Set the brightness of the light over transition."""
level = int(convert_scale(brightness, 255, 100, 0))
# Due to rounding, set level to 1 (one) so we don't inadvertently
# turn off the light when a low brightness is set.
level = 1 if level == 0 and brightness > 0 else level
level = max(min(level, 100), 0)
duration = int(transition)
await self._device.set_level(level, duration, set_status=True)
@property
def brightness(self):
"""Return the brightness of this light between 0..255."""
return self._brightness
@property
def color_temp(self):
"""Return the CT color value in mireds."""
return self._color_temp
@property
def hs_color(self):
"""Return the hue and saturation color value [float, float]."""
return self._hs_color
@property
def is_on(self) -> bool:
"""Return true if light is on."""
return self._device.status.switch
@property
def max_mireds(self):
"""Return the warmest color_temp that this light supports."""
# SmartThings does not expose this attribute, instead it's
# implemented within each device-type handler. This value is the
# lowest kelvin found supported across 20+ handlers.
return 500 # 2000K
@property
def min_mireds(self):
"""Return the coldest color_temp that this light supports."""
# SmartThings does not expose this attribute, instead it's
# implemented within each device-type handler. This value is the
# highest kelvin found supported across 20+ handlers.
return 111 # 9000K
@property
def supported_features(self) -> int:
"""Flag supported features."""
return self._supported_features

View File

@@ -2,10 +2,14 @@
"config": {
"abort": {
"all_configured": "TelldusLive ja est\u00e0 configurat",
"already_setup": "TelldusLive ja est\u00e0 configurat",
"authorize_url_fail": "S'ha produ\u00eft un error desconegut al generar l'URL d'autoritzaci\u00f3.",
"authorize_url_timeout": "S'ha acabat el temps d'espera mentre \u00e9s generava l'URL d'autoritzaci\u00f3.",
"unknown": "S'ha produ\u00eft un error desconegut"
},
"error": {
"auth_error": "Error d'autenticaci\u00f3, torna-ho a provar"
},
"step": {
"auth": {
"description": "Passos per enlla\u00e7ar el teu compte de TelldusLive:\n 1. Clica l'enlla\u00e7 de sota.\n 2. Inicia sessi\u00f3 a Telldus Live.\n 3. Autoritza **{app_name}** (clica **Yes**).\n 4. Torna aqu\u00ed i clica **SUBMIT**.\n \n [Enlla\u00e7 al compte de TelldusLive]({auth_url})",

View File

@@ -1,6 +1,7 @@
{
"config": {
"abort": {
"all_configured": "TelldusLive is already configured",
"already_setup": "TelldusLive is already configured",
"authorize_url_fail": "Unknown error generating an authorize url.",
"authorize_url_timeout": "Timeout generating authorize url.",

View File

@@ -0,0 +1,10 @@
{
"config": {
"abort": {
"already_setup": "TelldusLive ya est\u00e1 configurado"
},
"error": {
"auth_error": "Error de autenticaci\u00f3n, por favor int\u00e9ntalo de nuevo"
}
}
}

View File

@@ -2,10 +2,14 @@
"config": {
"abort": {
"all_configured": "TelldusLive \uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4",
"already_setup": "TelldusLive \uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4",
"authorize_url_fail": "\uc778\uc99d url \uc0dd\uc131\uc5d0 \uc54c \uc218 \uc5c6\ub294 \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4.",
"authorize_url_timeout": "\uc778\uc99d url \uc0dd\uc131 \uc2dc\uac04\uc774 \ucd08\uacfc\ub418\uc5c8\uc2b5\ub2c8\ub2e4.",
"unknown": "\uc54c \uc218\uc5c6\ub294 \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4"
},
"error": {
"auth_error": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ub2e4\uc2dc \uc2dc\ub3c4\ud574 \uc8fc\uc138\uc694."
},
"step": {
"auth": {
"description": "TelldusLive \uacc4\uc815\uc744 \uc5f0\uacb0\ud558\ub824\uba74:\n 1. \ud558\ub2e8\uc758 \ub9c1\ud06c\ub97c \ud074\ub9ad\ud574\uc8fc\uc138\uc694\n 2. Telldus Live \uc5d0 \ub85c\uadf8\uc778 \ud558\uc138\uc694\n 3. Authorize **{app_name}** (**Yes** \ub97c \ud074\ub9ad\ud558\uc138\uc694).\n 4. \ub2e4\uc2dc \uc5ec\uae30\ub85c \ub3cc\uc544\uc640\uc11c **SUBMIT** \uc744 \ud074\ub9ad\ud558\uc138\uc694.\n\n [TelldusLive \uacc4\uc815 \uc5f0\uacb0\ud558\uae30]({auth_url})",

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