diff --git a/homeassistant/components/concord232/binary_sensor.py b/homeassistant/components/concord232/binary_sensor.py index 26f35d60305..3e4d0526db4 100644 --- a/homeassistant/components/concord232/binary_sensor.py +++ b/homeassistant/components/concord232/binary_sensor.py @@ -79,7 +79,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): ) ) - add_entities(sensors, True) + add_entities(sensors, True) def get_opening_type(zone): diff --git a/homeassistant/components/hassio/ingress.py b/homeassistant/components/hassio/ingress.py index 4f7c99618c1..f4cc97c3853 100644 --- a/homeassistant/components/hassio/ingress.py +++ b/homeassistant/components/hassio/ingress.py @@ -70,7 +70,18 @@ class HassIOIngress(HomeAssistantView): self, request: web.Request, token: str, path: str ) -> web.WebSocketResponse: """Ingress route for websocket.""" - ws_server = web.WebSocketResponse() + if hdrs.SEC_WEBSOCKET_PROTOCOL in request.headers: + req_protocols = [ + str(proto.strip()) + for proto in + request.headers[hdrs.SEC_WEBSOCKET_PROTOCOL].split(",") + ] + else: + req_protocols = () + + ws_server = web.WebSocketResponse( + protocols=req_protocols, autoclose=False, autoping=False + ) await ws_server.prepare(request) # Preparing @@ -83,7 +94,8 @@ class HassIOIngress(HomeAssistantView): # Start proxy async with self._websession.ws_connect( - url, headers=source_header + url, headers=source_header, protocols=req_protocols, + autoclose=False, autoping=False, ) as ws_client: # Proxy requests await asyncio.wait( @@ -205,14 +217,17 @@ def _is_websocket(request: web.Request) -> bool: async def _websocket_forward(ws_from, ws_to): """Handle websocket message directly.""" - async for msg in ws_from: - if msg.type == aiohttp.WSMsgType.TEXT: - await ws_to.send_str(msg.data) - elif msg.type == aiohttp.WSMsgType.BINARY: - await ws_to.send_bytes(msg.data) - elif msg.type == aiohttp.WSMsgType.PING: - await ws_to.ping() - elif msg.type == aiohttp.WSMsgType.PONG: - await ws_to.pong() - elif ws_to.closed: - await ws_to.close(code=ws_to.close_code, message=msg.extra) + try: + async for msg in ws_from: + if msg.type == aiohttp.WSMsgType.TEXT: + await ws_to.send_str(msg.data) + elif msg.type == aiohttp.WSMsgType.BINARY: + await ws_to.send_bytes(msg.data) + elif msg.type == aiohttp.WSMsgType.PING: + await ws_to.ping() + elif msg.type == aiohttp.WSMsgType.PONG: + await ws_to.pong() + elif ws_to.closed: + await ws_to.close(code=ws_to.close_code, message=msg.extra) + except RuntimeError: + _LOGGER.debug("Ingress Websocket runtime error") diff --git a/homeassistant/components/mobile_app/binary_sensor.py b/homeassistant/components/mobile_app/binary_sensor.py index 289a50584c9..50943bb6504 100644 --- a/homeassistant/components/mobile_app/binary_sensor.py +++ b/homeassistant/components/mobile_app/binary_sensor.py @@ -8,9 +8,10 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect from .const import (ATTR_SENSOR_STATE, ATTR_SENSOR_TYPE_BINARY_SENSOR as ENTITY_TYPE, + ATTR_SENSOR_UNIQUE_ID, DATA_DEVICES, DOMAIN) -from .entity import MobileAppEntity +from .entity import MobileAppEntity, sensor_id DEPENDENCIES = ['mobile_app'] @@ -36,6 +37,16 @@ async def async_setup_entry(hass, config_entry, async_add_entities): if data[CONF_WEBHOOK_ID] != webhook_id: return + unique_id = sensor_id(data[CONF_WEBHOOK_ID], + data[ATTR_SENSOR_UNIQUE_ID]) + + entity = hass.data[DOMAIN][ENTITY_TYPE][unique_id] + + if 'added' in entity: + return + + entity['added'] = True + device = hass.data[DOMAIN][DATA_DEVICES][data[CONF_WEBHOOK_ID]] async_add_entities([MobileAppBinarySensor(data, device, config_entry)]) diff --git a/homeassistant/components/mobile_app/entity.py b/homeassistant/components/mobile_app/entity.py index 05736b3a689..eca9d2b024b 100644 --- a/homeassistant/components/mobile_app/entity.py +++ b/homeassistant/components/mobile_app/entity.py @@ -13,6 +13,11 @@ from .const import (ATTR_DEVICE_ID, ATTR_DEVICE_NAME, ATTR_MANUFACTURER, DOMAIN, SIGNAL_SENSOR_UPDATE) +def sensor_id(webhook_id, unique_id): + """Return a unique sensor ID.""" + return "{}_{}".format(webhook_id, unique_id) + + class MobileAppEntity(Entity): """Representation of an mobile app entity.""" @@ -22,8 +27,8 @@ class MobileAppEntity(Entity): self._device = device self._entry = entry self._registration = entry.data - self._sensor_id = "{}_{}".format(self._registration[CONF_WEBHOOK_ID], - config[ATTR_SENSOR_UNIQUE_ID]) + self._sensor_id = sensor_id(self._registration[CONF_WEBHOOK_ID], + config[ATTR_SENSOR_UNIQUE_ID]) self._entity_type = config[ATTR_SENSOR_TYPE] self.unsub_dispatcher = None @@ -94,5 +99,10 @@ class MobileAppEntity(Entity): @callback def _handle_update(self, data): """Handle async event updates.""" + incoming_id = sensor_id(data[CONF_WEBHOOK_ID], + data[ATTR_SENSOR_UNIQUE_ID]) + if incoming_id != self._sensor_id: + return + self._config = data self.async_schedule_update_ha_state() diff --git a/homeassistant/components/mobile_app/sensor.py b/homeassistant/components/mobile_app/sensor.py index b2846a6002b..64ad69c5758 100644 --- a/homeassistant/components/mobile_app/sensor.py +++ b/homeassistant/components/mobile_app/sensor.py @@ -7,9 +7,10 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect from .const import (ATTR_SENSOR_STATE, ATTR_SENSOR_TYPE_SENSOR as ENTITY_TYPE, - ATTR_SENSOR_UOM, DATA_DEVICES, DOMAIN) + ATTR_SENSOR_UNIQUE_ID, ATTR_SENSOR_UOM, DATA_DEVICES, + DOMAIN) -from .entity import MobileAppEntity +from .entity import MobileAppEntity, sensor_id DEPENDENCIES = ['mobile_app'] @@ -35,6 +36,16 @@ async def async_setup_entry(hass, config_entry, async_add_entities): if data[CONF_WEBHOOK_ID] != webhook_id: return + unique_id = sensor_id(data[CONF_WEBHOOK_ID], + data[ATTR_SENSOR_UNIQUE_ID]) + + entity = hass.data[DOMAIN][ENTITY_TYPE][unique_id] + + if 'added' in entity: + return + + entity['added'] = True + device = hass.data[DOMAIN][DATA_DEVICES][data[CONF_WEBHOOK_ID]] async_add_entities([MobileAppSensor(data, device, config_entry)]) diff --git a/homeassistant/components/mobile_app/webhook.py b/homeassistant/components/mobile_app/webhook.py index 28ef6bccd6a..1ef5f4ce531 100644 --- a/homeassistant/components/mobile_app/webhook.py +++ b/homeassistant/components/mobile_app/webhook.py @@ -4,6 +4,8 @@ import logging from aiohttp.web import HTTPBadRequest, Response, Request import voluptuous as vol +from homeassistant.components.cloud import (async_remote_ui_url, + CloudNotAvailable) from homeassistant.components.device_tracker import (ATTR_ATTRIBUTES, ATTR_DEV_ID, DOMAIN as DT_DOMAIN, @@ -31,14 +33,15 @@ from .const import (ATTR_ALTITUDE, ATTR_BATTERY, ATTR_COURSE, ATTR_DEVICE_ID, ATTR_TEMPLATE_VARIABLES, ATTR_VERTICAL_ACCURACY, ATTR_WEBHOOK_DATA, ATTR_WEBHOOK_ENCRYPTED, ATTR_WEBHOOK_ENCRYPTED_DATA, ATTR_WEBHOOK_TYPE, - CONF_SECRET, DATA_CONFIG_ENTRIES, DATA_DELETED_IDS, - DATA_STORE, DOMAIN, ERR_ENCRYPTION_REQUIRED, - ERR_SENSOR_DUPLICATE_UNIQUE_ID, ERR_SENSOR_NOT_REGISTERED, - SIGNAL_SENSOR_UPDATE, WEBHOOK_PAYLOAD_SCHEMA, - WEBHOOK_SCHEMAS, WEBHOOK_TYPES, WEBHOOK_TYPE_CALL_SERVICE, - WEBHOOK_TYPE_FIRE_EVENT, WEBHOOK_TYPE_GET_CONFIG, - WEBHOOK_TYPE_GET_ZONES, WEBHOOK_TYPE_REGISTER_SENSOR, - WEBHOOK_TYPE_RENDER_TEMPLATE, WEBHOOK_TYPE_UPDATE_LOCATION, + CONF_CLOUDHOOK_URL, CONF_REMOTE_UI_URL, CONF_SECRET, + DATA_CONFIG_ENTRIES, DATA_DELETED_IDS, DATA_STORE, DOMAIN, + ERR_ENCRYPTION_REQUIRED, ERR_SENSOR_DUPLICATE_UNIQUE_ID, + ERR_SENSOR_NOT_REGISTERED, SIGNAL_SENSOR_UPDATE, + WEBHOOK_PAYLOAD_SCHEMA, WEBHOOK_SCHEMAS, WEBHOOK_TYPES, + WEBHOOK_TYPE_CALL_SERVICE, WEBHOOK_TYPE_FIRE_EVENT, + WEBHOOK_TYPE_GET_CONFIG, WEBHOOK_TYPE_GET_ZONES, + WEBHOOK_TYPE_REGISTER_SENSOR, WEBHOOK_TYPE_RENDER_TEMPLATE, + WEBHOOK_TYPE_UPDATE_LOCATION, WEBHOOK_TYPE_UPDATE_REGISTRATION, WEBHOOK_TYPE_UPDATE_SENSOR_STATES) @@ -96,6 +99,9 @@ async def handle_webhook(hass: HomeAssistantType, webhook_id: str, data = webhook_payload + _LOGGER.debug("Received webhook payload for type %s: %s", webhook_type, + data) + if webhook_type in WEBHOOK_SCHEMAS: try: data = WEBHOOK_SCHEMAS[webhook_type](webhook_payload) @@ -286,7 +292,7 @@ async def handle_webhook(hass: HomeAssistantType, webhook_id: str, hass_config = hass.config.as_dict() - return webhook_response({ + resp = { 'latitude': hass_config['latitude'], 'longitude': hass_config['longitude'], 'elevation': hass_config['elevation'], @@ -296,4 +302,15 @@ async def handle_webhook(hass: HomeAssistantType, webhook_id: str, 'components': hass_config['components'], 'version': hass_config['version'], 'theme_color': MANIFEST_JSON['theme_color'], - }, registration=registration, headers=headers) + } + + if CONF_CLOUDHOOK_URL in registration: + resp[CONF_CLOUDHOOK_URL] = registration[CONF_CLOUDHOOK_URL] + + try: + resp[CONF_REMOTE_UI_URL] = async_remote_ui_url(hass) + except CloudNotAvailable: + pass + + return webhook_response(resp, registration=registration, + headers=headers) diff --git a/homeassistant/const.py b/homeassistant/const.py index 28bdf2f7fc3..6a8a7fe6a1d 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -2,7 +2,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 91 -PATCH_VERSION = '2' +PATCH_VERSION = '3' __short_version__ = '{}.{}'.format(MAJOR_VERSION, MINOR_VERSION) __version__ = '{}.{}'.format(__short_version__, PATCH_VERSION) REQUIRED_PYTHON_VER = (3, 5, 3)