diff --git a/homeassistant/components/ambient_station/__init__.py b/homeassistant/components/ambient_station/__init__.py index 2383d011945..cb0a2067d9f 100644 --- a/homeassistant/components/ambient_station/__init__.py +++ b/homeassistant/components/ambient_station/__init__.py @@ -20,7 +20,7 @@ from .const import ( ATTR_LAST_DATA, CONF_APP_KEY, DATA_CLIENT, DOMAIN, TOPIC_UPDATE, TYPE_BINARY_SENSOR, TYPE_SENSOR) -REQUIREMENTS = ['aioambient==0.1.3'] +REQUIREMENTS = ['aioambient==0.2.0'] _LOGGER = logging.getLogger(__name__) @@ -417,9 +417,8 @@ class AmbientWeatherEntity(Entity): @property def available(self): """Return True if entity is available.""" - return bool( - self._ambient.stations[self._mac_address][ATTR_LAST_DATA].get( - self._sensor_type)) + return self._ambient.stations[self._mac_address][ATTR_LAST_DATA].get( + self._sensor_type) is not None @property def device_info(self): diff --git a/homeassistant/components/daikin/config_flow.py b/homeassistant/components/daikin/config_flow.py index 590b5a02738..3c5daac4653 100644 --- a/homeassistant/components/daikin/config_flow.py +++ b/homeassistant/components/daikin/config_flow.py @@ -38,9 +38,12 @@ class FlowHandler(config_entries.ConfigFlow): """Create device.""" from pydaikin.appliance import Appliance try: + device = Appliance( + host, + self.hass.helpers.aiohttp_client.async_get_clientsession(), + ) with async_timeout.timeout(10): - device = await self.hass.async_add_executor_job( - Appliance, host) + await device.init() except asyncio.TimeoutError: return self.async_abort(reason='device_timeout') except Exception: # pylint: disable=broad-except diff --git a/homeassistant/components/foscam/camera.py b/homeassistant/components/foscam/camera.py index b6f2162d57a..1b4ee039053 100644 --- a/homeassistant/components/foscam/camera.py +++ b/homeassistant/components/foscam/camera.py @@ -58,10 +58,11 @@ class FoscamCam(Camera): self._foscam_session = FoscamCamera( ip_address, port, self._username, self._password, verbose=False) - self._media_port = None + self._rtsp_port = None result, response = self._foscam_session.get_port_info() if result == 0: - self._media_port = response['mediaPort'] + self._rtsp_port = response.get('rtspPort') or \ + response.get('mediaPort') def camera_image(self): """Return a still image response from the camera.""" @@ -76,19 +77,19 @@ class FoscamCam(Camera): @property def supported_features(self): """Return supported features.""" - if self._media_port: + if self._rtsp_port: return SUPPORT_STREAM return 0 @property def stream_source(self): """Return the stream source.""" - if self._media_port: + if self._rtsp_port: return 'rtsp://{}:{}@{}:{}/videoMain'.format( self._username, self._password, self._foscam_session.host, - self._media_port) + self._rtsp_port) return None @property diff --git a/homeassistant/components/hassio/http.py b/homeassistant/components/hassio/http.py index 7284004d72f..a798d312c25 100644 --- a/homeassistant/components/hassio/http.py +++ b/homeassistant/components/hassio/http.py @@ -85,7 +85,6 @@ class HassIOView(HomeAssistantView): "http://{}/{}".format(self._host, path), data=data, headers=headers, timeout=read_timeout ) - print(client.headers) # Simple request if int(client.headers.get(CONTENT_LENGTH, 0)) < 4194000: diff --git a/homeassistant/components/hassio/ingress.py b/homeassistant/components/hassio/ingress.py index 04241f53de9..91224c6f54d 100644 --- a/homeassistant/components/hassio/ingress.py +++ b/homeassistant/components/hassio/ingress.py @@ -1,21 +1,23 @@ """Hass.io Add-on ingress service.""" import asyncio -from ipaddress import ip_address +import logging import os +from ipaddress import ip_address from typing import Dict, Union import aiohttp -from aiohttp import web -from aiohttp import hdrs +from aiohttp import hdrs, web from aiohttp.web_exceptions import HTTPBadGateway from multidict import CIMultiDict -from homeassistant.core import callback from homeassistant.components.http import HomeAssistantView +from homeassistant.core import callback from homeassistant.helpers.typing import HomeAssistantType from .const import X_HASSIO, X_INGRESS_PATH +_LOGGER = logging.getLogger(__name__) + @callback def async_setup_ingress(hass: HomeAssistantType, host: str): @@ -30,7 +32,7 @@ class HassIOIngress(HomeAssistantView): """Hass.io view to handle base part.""" name = "api:hassio:ingress" - url = "/api/hassio_ingress/{addon}/{path:.+}" + url = "/api/hassio_ingress/{token}/{path:.*}" requires_auth = False def __init__(self, host: str, websession: aiohttp.ClientSession): @@ -38,24 +40,24 @@ class HassIOIngress(HomeAssistantView): self._host = host self._websession = websession - def _create_url(self, addon: str, path: str) -> str: + def _create_url(self, token: str, path: str) -> str: """Create URL to service.""" - return "http://{}/addons/{}/web/{}".format(self._host, addon, path) + return "http://{}/ingress/{}/{}".format(self._host, token, path) async def _handle( - self, request: web.Request, addon: str, path: str + self, request: web.Request, token: str, path: str ) -> Union[web.Response, web.StreamResponse, web.WebSocketResponse]: """Route data to Hass.io ingress service.""" try: # Websocket if _is_websocket(request): - return await self._handle_websocket(request, addon, path) + return await self._handle_websocket(request, token, path) # Request - return await self._handle_request(request, addon, path) + return await self._handle_request(request, token, path) - except aiohttp.ClientError: - pass + except aiohttp.ClientError as err: + _LOGGER.debug("Ingress error with %s / %s: %s", token, path, err) raise HTTPBadGateway() from None @@ -65,15 +67,15 @@ class HassIOIngress(HomeAssistantView): delete = _handle async def _handle_websocket( - self, request: web.Request, addon: str, path: str + self, request: web.Request, token: str, path: str ) -> web.WebSocketResponse: """Ingress route for websocket.""" ws_server = web.WebSocketResponse() await ws_server.prepare(request) # Preparing - url = self._create_url(addon, path) - source_header = _init_header(request, addon) + url = self._create_url(token, path) + source_header = _init_header(request, token) # Support GET query if request.query_string: @@ -95,16 +97,16 @@ class HassIOIngress(HomeAssistantView): return ws_server async def _handle_request( - self, request: web.Request, addon: str, path: str + self, request: web.Request, token: str, path: str ) -> Union[web.Response, web.StreamResponse]: """Ingress route for request.""" - url = self._create_url(addon, path) + url = self._create_url(token, path) data = await request.read() - source_header = _init_header(request, addon) + source_header = _init_header(request, token) async with self._websession.request( request.method, url, headers=source_header, - params=request.query, data=data, cookies=request.cookies + params=request.query, data=data ) as result: headers = _response_header(result) @@ -126,24 +128,25 @@ class HassIOIngress(HomeAssistantView): try: await response.prepare(request) - async for data in result.content: + async for data in result.content.iter_chunked(4096): await response.write(data) - except (aiohttp.ClientError, aiohttp.ClientPayloadError): - pass + except (aiohttp.ClientError, aiohttp.ClientPayloadError) as err: + _LOGGER.debug("Stream error %s / %s: %s", token, path, err) return response def _init_header( - request: web.Request, addon: str + request: web.Request, token: str ) -> Union[CIMultiDict, Dict[str, str]]: """Create initial header.""" headers = {} # filter flags for name, value in request.headers.items(): - if name in (hdrs.CONTENT_LENGTH, hdrs.CONTENT_TYPE): + if name in (hdrs.CONTENT_LENGTH, hdrs.CONTENT_TYPE, + hdrs.CONTENT_ENCODING): continue headers[name] = value @@ -151,7 +154,7 @@ def _init_header( headers[X_HASSIO] = os.environ.get('HASSIO_TOKEN', "") # Ingress information - headers[X_INGRESS_PATH] = "/api/hassio_ingress/{}".format(addon) + headers[X_INGRESS_PATH] = "/api/hassio_ingress/{}".format(token) # Set X-Forwarded-For forward_for = request.headers.get(hdrs.X_FORWARDED_FOR) @@ -183,7 +186,7 @@ def _response_header(response: aiohttp.ClientResponse) -> Dict[str, str]: for name, value in response.headers.items(): if name in (hdrs.TRANSFER_ENCODING, hdrs.CONTENT_LENGTH, - hdrs.CONTENT_TYPE): + hdrs.CONTENT_TYPE, hdrs.CONTENT_ENCODING): continue headers[name] = value diff --git a/homeassistant/components/stream/worker.py b/homeassistant/components/stream/worker.py index 3ca8ac079e3..0292fd30596 100644 --- a/homeassistant/components/stream/worker.py +++ b/homeassistant/components/stream/worker.py @@ -29,11 +29,7 @@ def create_stream_buffer(stream_output, video_stream, audio_frame): segment = io.BytesIO() output = av.open( segment, mode='w', format=stream_output.format) - vstream = output.add_stream( - stream_output.video_codec, video_stream.rate) - # Fix format - vstream.codec_context.format = \ - video_stream.codec_context.format + vstream = output.add_stream(template=video_stream) # Check if audio is requested astream = None if stream_output.audio_codec: diff --git a/homeassistant/components/yeelight/__init__.py b/homeassistant/components/yeelight/__init__.py index fb218a67698..99382bb1da9 100644 --- a/homeassistant/components/yeelight/__init__.py +++ b/homeassistant/components/yeelight/__init__.py @@ -111,37 +111,6 @@ UPDATE_REQUEST_PROPERTIES = [ ] -def _transitions_config_parser(transitions): - """Parse transitions config into initialized objects.""" - import yeelight - - transition_objects = [] - for transition_config in transitions: - transition, params = list(transition_config.items())[0] - transition_objects.append(getattr(yeelight, transition)(*params)) - - return transition_objects - - -def _parse_custom_effects(effects_config): - import yeelight - - effects = {} - for config in effects_config: - params = config[CONF_FLOW_PARAMS] - action = yeelight.Flow.actions[params[ATTR_ACTION]] - transitions = _transitions_config_parser( - params[ATTR_TRANSITIONS]) - - effects[config[CONF_NAME]] = { - ATTR_COUNT: params[ATTR_COUNT], - ATTR_ACTION: action, - ATTR_TRANSITIONS: transitions - } - - return effects - - def setup(hass, config): """Set up the Yeelight bulbs.""" conf = config.get(DOMAIN, {}) @@ -192,9 +161,8 @@ def _setup_device(hass, hass_config, ipaddr, device_config): platform_config = device_config.copy() platform_config[CONF_HOST] = ipaddr - platform_config[CONF_CUSTOM_EFFECTS] = _parse_custom_effects( + platform_config[CONF_CUSTOM_EFFECTS] = \ hass_config.get(DOMAIN, {}).get(CONF_CUSTOM_EFFECTS, {}) - ) load_platform(hass, LIGHT_DOMAIN, DOMAIN, platform_config, hass_config) load_platform(hass, BINARY_SENSOR_DOMAIN, DOMAIN, platform_config, diff --git a/homeassistant/components/yeelight/light.py b/homeassistant/components/yeelight/light.py index 92b668c6987..912a4f99c92 100644 --- a/homeassistant/components/yeelight/light.py +++ b/homeassistant/components/yeelight/light.py @@ -7,7 +7,7 @@ from homeassistant.helpers.service import extract_entity_ids from homeassistant.util.color import ( color_temperature_mired_to_kelvin as mired_to_kelvin, color_temperature_kelvin_to_mired as kelvin_to_mired) -from homeassistant.const import CONF_HOST, ATTR_ENTITY_ID +from homeassistant.const import CONF_HOST, ATTR_ENTITY_ID, CONF_NAME from homeassistant.core import callback from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_HS_COLOR, ATTR_TRANSITION, ATTR_COLOR_TEMP, @@ -19,8 +19,8 @@ from homeassistant.components.yeelight import ( CONF_TRANSITION, DATA_YEELIGHT, CONF_MODE_MUSIC, CONF_SAVE_ON_CHANGE, CONF_CUSTOM_EFFECTS, DATA_UPDATED, YEELIGHT_SERVICE_SCHEMA, DOMAIN, ATTR_TRANSITIONS, - YEELIGHT_FLOW_TRANSITION_SCHEMA, _transitions_config_parser, - ACTION_RECOVER) + YEELIGHT_FLOW_TRANSITION_SCHEMA, ACTION_RECOVER, CONF_FLOW_PARAMS, + ATTR_ACTION, ATTR_COUNT) DEPENDENCIES = ['yeelight'] @@ -81,6 +81,37 @@ YEELIGHT_EFFECT_LIST = [ EFFECT_STOP] +def _transitions_config_parser(transitions): + """Parse transitions config into initialized objects.""" + import yeelight + + transition_objects = [] + for transition_config in transitions: + transition, params = list(transition_config.items())[0] + transition_objects.append(getattr(yeelight, transition)(*params)) + + return transition_objects + + +def _parse_custom_effects(effects_config): + import yeelight + + effects = {} + for config in effects_config: + params = config[CONF_FLOW_PARAMS] + action = yeelight.Flow.actions[params[ATTR_ACTION]] + transitions = _transitions_config_parser( + params[ATTR_TRANSITIONS]) + + effects[config[CONF_NAME]] = { + ATTR_COUNT: params[ATTR_COUNT], + ATTR_ACTION: action, + ATTR_TRANSITIONS: transitions + } + + return effects + + def _cmd(func): """Define a wrapper to catch exceptions from the bulb.""" def _wrap(self, *args, **kwargs): @@ -109,7 +140,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): device = hass.data[DATA_YEELIGHT][discovery_info[CONF_HOST]] _LOGGER.debug("Adding %s", device.name) - custom_effects = discovery_info[CONF_CUSTOM_EFFECTS] + custom_effects = _parse_custom_effects(discovery_info[CONF_CUSTOM_EFFECTS]) lights = [YeelightLight(device, custom_effects=custom_effects)] diff --git a/homeassistant/components/zha/core/const.py b/homeassistant/components/zha/core/const.py index b7f418253d8..a34a049fad2 100644 --- a/homeassistant/components/zha/core/const.py +++ b/homeassistant/components/zha/core/const.py @@ -77,7 +77,6 @@ ELECTRICAL_MEASUREMENT = 'electrical_measurement' GENERIC = 'generic' UNKNOWN = 'unknown' OPENING = 'opening' -ZONE = 'zone' OCCUPANCY = 'occupancy' ACCELERATION = 'acceleration' @@ -90,7 +89,7 @@ BASIC_CHANNEL = 'basic' COLOR_CHANNEL = 'light_color' FAN_CHANNEL = 'fan' LEVEL_CHANNEL = ATTR_LEVEL -ZONE_CHANNEL = 'ias_zone' +ZONE_CHANNEL = ZONE = 'ias_zone' ELECTRICAL_MEASUREMENT_CHANNEL = 'electrical_measurement' POWER_CONFIGURATION_CHANNEL = 'power' EVENT_RELAY_CHANNEL = 'event_relay' diff --git a/homeassistant/const.py b/homeassistant/const.py index 36c61a51916..6ece276307e 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 = '0' +PATCH_VERSION = '1' __short_version__ = '{}.{}'.format(MAJOR_VERSION, MINOR_VERSION) __version__ = '{}.{}'.format(__short_version__, PATCH_VERSION) REQUIRED_PYTHON_VER = (3, 5, 3) diff --git a/requirements_all.txt b/requirements_all.txt index a773c9c8c31..231637fef8d 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -97,7 +97,7 @@ abodepy==0.15.0 afsapi==0.0.4 # homeassistant.components.ambient_station -aioambient==0.1.3 +aioambient==0.2.0 # homeassistant.components.asuswrt aioasuswrt==1.1.21 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 27d96c5e606..48390ff101a 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -35,7 +35,7 @@ PyTransportNSW==0.1.1 YesssSMS==0.2.3 # homeassistant.components.ambient_station -aioambient==0.1.3 +aioambient==0.2.0 # homeassistant.components.automatic.device_tracker aioautomatic==0.6.5 diff --git a/tests/components/daikin/test_config_flow.py b/tests/components/daikin/test_config_flow.py index f6b2f0b1d41..fa288f6c2ef 100644 --- a/tests/components/daikin/test_config_flow.py +++ b/tests/components/daikin/test_config_flow.py @@ -24,9 +24,14 @@ def init_config_flow(hass): @pytest.fixture def mock_daikin(): - """Mock tellduslive.""" + """Mock pydaikin.""" + async def mock_daikin_init(): + """Mock the init function in pydaikin.""" + pass + with MockDependency('pydaikin.appliance') as mock_daikin_: mock_daikin_.Appliance().values.get.return_value = 'AABBCCDDEEFF' + mock_daikin_.Appliance().init = mock_daikin_init yield mock_daikin_ diff --git a/tests/components/hassio/test_ingress.py b/tests/components/hassio/test_ingress.py index 4b72eda4c25..343068375de 100644 --- a/tests/components/hassio/test_ingress.py +++ b/tests/components/hassio/test_ingress.py @@ -8,12 +8,13 @@ import pytest @pytest.mark.parametrize( 'build_type', [ ("a3_vl", "test/beer/ping?index=1"), ("core", "index.html"), - ("local", "panel/config"), ("jk_921", "editor.php?idx=3&ping=5") + ("local", "panel/config"), ("jk_921", "editor.php?idx=3&ping=5"), + ("fsadjf10312", "") ]) async def test_ingress_request_get( hassio_client, build_type, aioclient_mock): """Test no auth needed for .""" - aioclient_mock.get("http://127.0.0.1/addons/{}/web/{}".format( + aioclient_mock.get("http://127.0.0.1/ingress/{}/{}".format( build_type[0], build_type[1]), text="test") resp = await hassio_client.get( @@ -40,12 +41,13 @@ async def test_ingress_request_get( @pytest.mark.parametrize( 'build_type', [ ("a3_vl", "test/beer/ping?index=1"), ("core", "index.html"), - ("local", "panel/config"), ("jk_921", "editor.php?idx=3&ping=5") + ("local", "panel/config"), ("jk_921", "editor.php?idx=3&ping=5"), + ("fsadjf10312", "") ]) async def test_ingress_request_post( hassio_client, build_type, aioclient_mock): """Test no auth needed for .""" - aioclient_mock.post("http://127.0.0.1/addons/{}/web/{}".format( + aioclient_mock.post("http://127.0.0.1/ingress/{}/{}".format( build_type[0], build_type[1]), text="test") resp = await hassio_client.post( @@ -72,12 +74,13 @@ async def test_ingress_request_post( @pytest.mark.parametrize( 'build_type', [ ("a3_vl", "test/beer/ping?index=1"), ("core", "index.html"), - ("local", "panel/config"), ("jk_921", "editor.php?idx=3&ping=5") + ("local", "panel/config"), ("jk_921", "editor.php?idx=3&ping=5"), + ("fsadjf10312", "") ]) async def test_ingress_request_put( hassio_client, build_type, aioclient_mock): """Test no auth needed for .""" - aioclient_mock.put("http://127.0.0.1/addons/{}/web/{}".format( + aioclient_mock.put("http://127.0.0.1/ingress/{}/{}".format( build_type[0], build_type[1]), text="test") resp = await hassio_client.put( @@ -104,12 +107,13 @@ async def test_ingress_request_put( @pytest.mark.parametrize( 'build_type', [ ("a3_vl", "test/beer/ping?index=1"), ("core", "index.html"), - ("local", "panel/config"), ("jk_921", "editor.php?idx=3&ping=5") + ("local", "panel/config"), ("jk_921", "editor.php?idx=3&ping=5"), + ("fsadjf10312", "") ]) async def test_ingress_request_delete( hassio_client, build_type, aioclient_mock): """Test no auth needed for .""" - aioclient_mock.delete("http://127.0.0.1/addons/{}/web/{}".format( + aioclient_mock.delete("http://127.0.0.1/ingress/{}/{}".format( build_type[0], build_type[1]), text="test") resp = await hassio_client.delete( @@ -142,7 +146,7 @@ async def test_ingress_request_delete( async def test_ingress_websocket( hassio_client, build_type, aioclient_mock): """Test no auth needed for .""" - aioclient_mock.get("http://127.0.0.1/addons/{}/web/{}".format( + aioclient_mock.get("http://127.0.0.1/ingress/{}/{}".format( build_type[0], build_type[1])) # Ignore error because we can setup a full IO infrastructure