mirror of
https://github.com/home-assistant/core.git
synced 2025-07-08 05:47:10 +00:00
commit
c209236f47
@ -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):
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
||||
|
@ -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:
|
||||
|
@ -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,
|
||||
|
@ -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)]
|
||||
|
||||
|
@ -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'
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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_
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user