mirror of
https://github.com/home-assistant/core.git
synced 2025-07-18 18:57:06 +00:00
commit
8f9c2000ce
@ -88,8 +88,8 @@ CAMERA_SERVICE_PLAY_STREAM = CAMERA_SERVICE_SCHEMA.extend({
|
||||
|
||||
CAMERA_SERVICE_RECORD = CAMERA_SERVICE_SCHEMA.extend({
|
||||
vol.Required(CONF_FILENAME): cv.template,
|
||||
vol.Optional(CONF_DURATION, default=30): int,
|
||||
vol.Optional(CONF_LOOKBACK, default=0): int,
|
||||
vol.Optional(CONF_DURATION, default=30): vol.Coerce(int),
|
||||
vol.Optional(CONF_LOOKBACK, default=0): vol.Coerce(int),
|
||||
})
|
||||
|
||||
WS_TYPE_CAMERA_THUMBNAIL = 'camera_thumbnail'
|
||||
|
@ -19,6 +19,7 @@ _LOGGER = logging.getLogger(__name__)
|
||||
REQUIREMENTS = ['libpyfoscam==1.0']
|
||||
|
||||
CONF_IP = 'ip'
|
||||
CONF_RTSP_PORT = 'rtsp_port'
|
||||
|
||||
DEFAULT_NAME = 'Foscam Camera'
|
||||
DEFAULT_PORT = 88
|
||||
@ -31,6 +32,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Required(CONF_USERNAME): cv.string,
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
|
||||
vol.Optional(CONF_RTSP_PORT): cv.port
|
||||
})
|
||||
|
||||
|
||||
@ -58,11 +60,12 @@ class FoscamCam(Camera):
|
||||
self._foscam_session = FoscamCamera(
|
||||
ip_address, port, self._username, self._password, verbose=False)
|
||||
|
||||
self._rtsp_port = None
|
||||
result, response = self._foscam_session.get_port_info()
|
||||
if result == 0:
|
||||
self._rtsp_port = response.get('rtspPort') or \
|
||||
response.get('mediaPort')
|
||||
self._rtsp_port = device_info.get(CONF_RTSP_PORT)
|
||||
if not self._rtsp_port:
|
||||
result, response = self._foscam_session.get_port_info()
|
||||
if result == 0:
|
||||
self._rtsp_port = response.get('rtspPort') or \
|
||||
response.get('mediaPort')
|
||||
|
||||
def camera_image(self):
|
||||
"""Return a still image response from the camera."""
|
||||
|
@ -118,6 +118,7 @@ class HassIOIngress(HomeAssistantView):
|
||||
return web.Response(
|
||||
headers=headers,
|
||||
status=result.status,
|
||||
content_type=result.content_type,
|
||||
body=body
|
||||
)
|
||||
|
||||
@ -145,8 +146,7 @@ def _init_header(
|
||||
|
||||
# filter flags
|
||||
for name, value in request.headers.items():
|
||||
if name in (hdrs.CONTENT_LENGTH, hdrs.CONTENT_TYPE,
|
||||
hdrs.CONTENT_ENCODING):
|
||||
if name in (hdrs.CONTENT_LENGTH, hdrs.CONTENT_ENCODING):
|
||||
continue
|
||||
headers[name] = value
|
||||
|
||||
|
@ -41,9 +41,10 @@ class KonnectedSwitch(ToggleEntity):
|
||||
self._pause = self._data.get(CONF_PAUSE)
|
||||
self._repeat = self._data.get(CONF_REPEAT)
|
||||
self._state = self._boolean_state(self._data.get(ATTR_STATE))
|
||||
self._unique_id = '{}-{}'.format(device_id, hash(frozenset(
|
||||
{self._pin_num, self._momentary, self._pause, self._repeat})))
|
||||
self._name = self._data.get(CONF_NAME)
|
||||
self._unique_id = '{}-{}-{}-{}-{}'.format(
|
||||
device_id, self._pin_num, self._momentary,
|
||||
self._pause, self._repeat)
|
||||
|
||||
@property
|
||||
def unique_id(self) -> str:
|
||||
|
@ -42,6 +42,11 @@ ATTR_OS_NAME = 'os_name'
|
||||
ATTR_OS_VERSION = 'os_version'
|
||||
ATTR_PUSH_TOKEN = 'push_token'
|
||||
ATTR_PUSH_URL = 'push_url'
|
||||
ATTR_PUSH_RATE_LIMITS = 'rateLimits'
|
||||
ATTR_PUSH_RATE_LIMITS_ERRORS = 'errors'
|
||||
ATTR_PUSH_RATE_LIMITS_MAXIMUM = 'maximum'
|
||||
ATTR_PUSH_RATE_LIMITS_RESETS_AT = 'resetsAt'
|
||||
ATTR_PUSH_RATE_LIMITS_SUCCESSFUL = 'successful'
|
||||
ATTR_SUPPORTS_ENCRYPTION = 'supports_encryption'
|
||||
|
||||
ATTR_EVENT_DATA = 'event_data'
|
||||
@ -67,6 +72,8 @@ ERR_SENSOR_DUPLICATE_UNIQUE_ID = 'duplicate_unique_id'
|
||||
|
||||
WEBHOOK_TYPE_CALL_SERVICE = 'call_service'
|
||||
WEBHOOK_TYPE_FIRE_EVENT = 'fire_event'
|
||||
WEBHOOK_TYPE_GET_CONFIG = 'get_config'
|
||||
WEBHOOK_TYPE_GET_ZONES = 'get_zones'
|
||||
WEBHOOK_TYPE_REGISTER_SENSOR = 'register_sensor'
|
||||
WEBHOOK_TYPE_RENDER_TEMPLATE = 'render_template'
|
||||
WEBHOOK_TYPE_UPDATE_LOCATION = 'update_location'
|
||||
@ -74,6 +81,7 @@ WEBHOOK_TYPE_UPDATE_REGISTRATION = 'update_registration'
|
||||
WEBHOOK_TYPE_UPDATE_SENSOR_STATES = 'update_sensor_states'
|
||||
|
||||
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,
|
||||
@ -163,7 +171,7 @@ REGISTER_SENSOR_SCHEMA = vol.Schema({
|
||||
vol.Required(ATTR_SENSOR_NAME): cv.string,
|
||||
vol.Required(ATTR_SENSOR_TYPE): vol.In(SENSOR_TYPES),
|
||||
vol.Required(ATTR_SENSOR_UNIQUE_ID): cv.string,
|
||||
vol.Required(ATTR_SENSOR_UOM): cv.string,
|
||||
vol.Optional(ATTR_SENSOR_UOM): cv.string,
|
||||
vol.Required(ATTR_SENSOR_STATE): vol.Any(bool, str, int, float),
|
||||
vol.Optional(ATTR_SENSOR_ICON, default='mdi:cellphone'): cv.icon,
|
||||
})
|
||||
|
@ -6,6 +6,7 @@ from typing import Callable, Dict, Tuple
|
||||
from aiohttp.web import json_response, Response
|
||||
|
||||
from homeassistant.core import Context
|
||||
from homeassistant.helpers.json import JSONEncoder
|
||||
from homeassistant.helpers.typing import HomeAssistantType
|
||||
|
||||
from .const import (ATTR_APP_DATA, ATTR_APP_ID, ATTR_APP_NAME,
|
||||
@ -133,9 +134,9 @@ def savable_state(hass: HomeAssistantType) -> Dict:
|
||||
def webhook_response(data, *, registration: Dict, status: int = 200,
|
||||
headers: Dict = None) -> Response:
|
||||
"""Return a encrypted response if registration supports it."""
|
||||
data = json.dumps(data)
|
||||
data = json.dumps(data, cls=JSONEncoder)
|
||||
|
||||
if CONF_SECRET in registration:
|
||||
if registration[ATTR_SUPPORTS_ENCRYPTION]:
|
||||
keylen, encrypt = setup_encrypt()
|
||||
|
||||
key = registration[CONF_SECRET].encode("utf-8")
|
||||
|
@ -8,13 +8,18 @@ import async_timeout
|
||||
from homeassistant.components.notify import (
|
||||
ATTR_DATA, ATTR_MESSAGE, ATTR_TARGET, ATTR_TITLE, ATTR_TITLE_DEFAULT,
|
||||
BaseNotificationService)
|
||||
from homeassistant.components.mobile_app.const import (
|
||||
ATTR_APP_DATA, ATTR_APP_ID, ATTR_APP_VERSION, ATTR_DEVICE_NAME,
|
||||
ATTR_OS_VERSION, ATTR_PUSH_TOKEN, ATTR_PUSH_URL, DATA_CONFIG_ENTRIES,
|
||||
DOMAIN)
|
||||
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
import homeassistant.util.dt as dt_util
|
||||
|
||||
from .const import (ATTR_APP_DATA, ATTR_APP_ID, ATTR_APP_VERSION,
|
||||
ATTR_DEVICE_NAME, ATTR_OS_VERSION, ATTR_PUSH_RATE_LIMITS,
|
||||
ATTR_PUSH_RATE_LIMITS_ERRORS,
|
||||
ATTR_PUSH_RATE_LIMITS_MAXIMUM,
|
||||
ATTR_PUSH_RATE_LIMITS_RESETS_AT,
|
||||
ATTR_PUSH_RATE_LIMITS_SUCCESSFUL, ATTR_PUSH_TOKEN,
|
||||
ATTR_PUSH_URL, DATA_CONFIG_ENTRIES, DOMAIN)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DEPENDENCIES = ['mobile_app']
|
||||
@ -38,16 +43,21 @@ def push_registrations(hass):
|
||||
# pylint: disable=invalid-name
|
||||
def log_rate_limits(hass, device_name, resp, level=logging.INFO):
|
||||
"""Output rate limit log line at given level."""
|
||||
rate_limits = resp['rateLimits']
|
||||
resetsAt = dt_util.parse_datetime(rate_limits['resetsAt'])
|
||||
resetsAtTime = resetsAt - datetime.now(timezone.utc)
|
||||
if ATTR_PUSH_RATE_LIMITS not in resp:
|
||||
return
|
||||
|
||||
rate_limits = resp[ATTR_PUSH_RATE_LIMITS]
|
||||
resetsAt = rate_limits[ATTR_PUSH_RATE_LIMITS_RESETS_AT]
|
||||
resetsAtTime = (dt_util.parse_datetime(resetsAt) -
|
||||
datetime.now(timezone.utc))
|
||||
rate_limit_msg = ("mobile_app push notification rate limits for %s: "
|
||||
"%d sent, %d allowed, %d errors, "
|
||||
"resets in %s")
|
||||
_LOGGER.log(level, rate_limit_msg,
|
||||
device_name,
|
||||
rate_limits['successful'],
|
||||
rate_limits['maximum'], rate_limits['errors'],
|
||||
rate_limits[ATTR_PUSH_RATE_LIMITS_SUCCESSFUL],
|
||||
rate_limits[ATTR_PUSH_RATE_LIMITS_MAXIMUM],
|
||||
rate_limits[ATTR_PUSH_RATE_LIMITS_ERRORS],
|
||||
str(resetsAtTime).split(".")[0])
|
||||
|
||||
|
||||
|
@ -55,4 +55,4 @@ class MobileAppSensor(MobileAppEntity):
|
||||
@property
|
||||
def unit_of_measurement(self):
|
||||
"""Return the unit of measurement this sensor expresses itself in."""
|
||||
return self._config[ATTR_SENSOR_UOM]
|
||||
return self._config.get(ATTR_SENSOR_UOM)
|
||||
|
@ -8,6 +8,8 @@ from homeassistant.components.device_tracker import (ATTR_ATTRIBUTES,
|
||||
ATTR_DEV_ID,
|
||||
DOMAIN as DT_DOMAIN,
|
||||
SERVICE_SEE as DT_SEE)
|
||||
from homeassistant.components.frontend import MANIFEST_JSON
|
||||
from homeassistant.components.zone.const import DOMAIN as ZONE_DOMAIN
|
||||
|
||||
from homeassistant.const import (ATTR_DOMAIN, ATTR_SERVICE, ATTR_SERVICE_DATA,
|
||||
CONF_WEBHOOK_ID, HTTP_BAD_REQUEST,
|
||||
@ -33,8 +35,9 @@ from .const import (ATTR_ALTITUDE, ATTR_BATTERY, ATTR_COURSE, ATTR_DEVICE_ID,
|
||||
DATA_STORE, DOMAIN, ERR_ENCRYPTION_REQUIRED,
|
||||
ERR_SENSOR_DUPLICATE_UNIQUE_ID, ERR_SENSOR_NOT_REGISTERED,
|
||||
SIGNAL_SENSOR_UPDATE, WEBHOOK_PAYLOAD_SCHEMA,
|
||||
WEBHOOK_SCHEMAS, WEBHOOK_TYPE_CALL_SERVICE,
|
||||
WEBHOOK_TYPE_FIRE_EVENT, WEBHOOK_TYPE_REGISTER_SENSOR,
|
||||
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)
|
||||
@ -87,16 +90,19 @@ async def handle_webhook(hass: HomeAssistantType, webhook_id: str,
|
||||
enc_data = req_data[ATTR_WEBHOOK_ENCRYPTED_DATA]
|
||||
webhook_payload = _decrypt_payload(registration[CONF_SECRET], enc_data)
|
||||
|
||||
if webhook_type not in WEBHOOK_SCHEMAS:
|
||||
if webhook_type not in WEBHOOK_TYPES:
|
||||
_LOGGER.error('Received invalid webhook type: %s', webhook_type)
|
||||
return empty_okay_response()
|
||||
|
||||
try:
|
||||
data = WEBHOOK_SCHEMAS[webhook_type](webhook_payload)
|
||||
except vol.Invalid as ex:
|
||||
err = vol.humanize.humanize_error(webhook_payload, ex)
|
||||
_LOGGER.error('Received invalid webhook payload: %s', err)
|
||||
return empty_okay_response(headers=headers)
|
||||
data = webhook_payload
|
||||
|
||||
if webhook_type in WEBHOOK_SCHEMAS:
|
||||
try:
|
||||
data = WEBHOOK_SCHEMAS[webhook_type](webhook_payload)
|
||||
except vol.Invalid as ex:
|
||||
err = vol.humanize.humanize_error(webhook_payload, ex)
|
||||
_LOGGER.error('Received invalid webhook payload: %s', err)
|
||||
return empty_okay_response(headers=headers)
|
||||
|
||||
context = registration_context(registration)
|
||||
|
||||
@ -139,18 +145,26 @@ async def handle_webhook(hass: HomeAssistantType, webhook_id: str,
|
||||
if webhook_type == WEBHOOK_TYPE_UPDATE_LOCATION:
|
||||
see_payload = {
|
||||
ATTR_DEV_ID: registration[ATTR_DEVICE_ID],
|
||||
ATTR_LOCATION_NAME: data.get(ATTR_LOCATION_NAME),
|
||||
ATTR_GPS: data.get(ATTR_GPS),
|
||||
ATTR_GPS_ACCURACY: data.get(ATTR_GPS_ACCURACY),
|
||||
ATTR_BATTERY: data.get(ATTR_BATTERY),
|
||||
ATTR_ATTRIBUTES: {
|
||||
ATTR_SPEED: data.get(ATTR_SPEED),
|
||||
ATTR_ALTITUDE: data.get(ATTR_ALTITUDE),
|
||||
ATTR_COURSE: data.get(ATTR_COURSE),
|
||||
ATTR_VERTICAL_ACCURACY: data.get(ATTR_VERTICAL_ACCURACY),
|
||||
}
|
||||
ATTR_GPS: data[ATTR_GPS],
|
||||
ATTR_GPS_ACCURACY: data[ATTR_GPS_ACCURACY],
|
||||
}
|
||||
|
||||
for key in (ATTR_LOCATION_NAME, ATTR_BATTERY):
|
||||
value = data.get(key)
|
||||
if value is not None:
|
||||
see_payload[key] = value
|
||||
|
||||
attrs = {}
|
||||
|
||||
for key in (ATTR_ALTITUDE, ATTR_COURSE,
|
||||
ATTR_SPEED, ATTR_VERTICAL_ACCURACY):
|
||||
value = data.get(key)
|
||||
if value is not None:
|
||||
attrs[key] = value
|
||||
|
||||
if attrs:
|
||||
see_payload[ATTR_ATTRIBUTES] = attrs
|
||||
|
||||
try:
|
||||
await hass.services.async_call(DT_DOMAIN,
|
||||
DT_SEE, see_payload,
|
||||
@ -214,7 +228,7 @@ async def handle_webhook(hass: HomeAssistantType, webhook_id: str,
|
||||
data[ATTR_SENSOR_TYPE])
|
||||
async_dispatcher_send(hass, register_signal, data)
|
||||
|
||||
return webhook_response({"status": "registered"},
|
||||
return webhook_response({'success': True},
|
||||
registration=registration, status=HTTP_CREATED,
|
||||
headers=headers)
|
||||
|
||||
@ -257,7 +271,29 @@ async def handle_webhook(hass: HomeAssistantType, webhook_id: str,
|
||||
|
||||
async_dispatcher_send(hass, SIGNAL_SENSOR_UPDATE, new_state)
|
||||
|
||||
resp[unique_id] = {"status": "okay"}
|
||||
resp[unique_id] = {'success': True}
|
||||
|
||||
return webhook_response(resp, registration=registration,
|
||||
headers=headers)
|
||||
|
||||
if webhook_type == WEBHOOK_TYPE_GET_ZONES:
|
||||
zones = (hass.states.get(entity_id) for entity_id
|
||||
in sorted(hass.states.async_entity_ids(ZONE_DOMAIN)))
|
||||
return webhook_response(list(zones), registration=registration,
|
||||
headers=headers)
|
||||
|
||||
if webhook_type == WEBHOOK_TYPE_GET_CONFIG:
|
||||
|
||||
hass_config = hass.config.as_dict()
|
||||
|
||||
return webhook_response({
|
||||
'latitude': hass_config['latitude'],
|
||||
'longitude': hass_config['longitude'],
|
||||
'elevation': hass_config['elevation'],
|
||||
'unit_system': hass_config['unit_system'],
|
||||
'location_name': hass_config['location_name'],
|
||||
'time_zone': hass_config['time_zone'],
|
||||
'components': hass_config['components'],
|
||||
'version': hass_config['version'],
|
||||
'theme_color': MANIFEST_JSON['theme_color'],
|
||||
}, registration=registration, headers=headers)
|
||||
|
@ -53,12 +53,13 @@ def setup(hass, config):
|
||||
"""Set up the Mopar component."""
|
||||
import motorparts
|
||||
|
||||
conf = config[DOMAIN]
|
||||
cookie = hass.config.path(COOKIE_FILE)
|
||||
try:
|
||||
session = motorparts.get_session(
|
||||
config[CONF_USERNAME],
|
||||
config[CONF_PASSWORD],
|
||||
config[CONF_PIN],
|
||||
conf[CONF_USERNAME],
|
||||
conf[CONF_PASSWORD],
|
||||
conf[CONF_PIN],
|
||||
cookie_path=cookie
|
||||
)
|
||||
except motorparts.MoparError:
|
||||
@ -69,7 +70,7 @@ def setup(hass, config):
|
||||
data.update(now=None)
|
||||
|
||||
track_time_interval(
|
||||
hass, data.update, config[CONF_SCAN_INTERVAL]
|
||||
hass, data.update, conf[CONF_SCAN_INTERVAL]
|
||||
)
|
||||
|
||||
def handle_horn(call):
|
||||
|
@ -121,3 +121,8 @@ class TadoDataStore:
|
||||
"""Wrap for setZoneOverlay(..)."""
|
||||
self.tado.setZoneOverlay(zone_id, mode, temperature, duration)
|
||||
self.update(no_throttle=True) # pylint: disable=unexpected-keyword-arg
|
||||
|
||||
def set_zone_off(self, zone_id, mode):
|
||||
"""Set a zone to off."""
|
||||
self.tado.setZoneOverlay(zone_id, mode, None, None, 'HEATING', 'OFF')
|
||||
self.update(no_throttle=True) # pylint: disable=unexpected-keyword-arg
|
||||
|
@ -363,7 +363,7 @@ class TadoClimate(ClimateDevice):
|
||||
if self._current_operation == CONST_MODE_OFF:
|
||||
_LOGGER.info("Switching mytado.com to OFF for zone %s",
|
||||
self.zone_name)
|
||||
self._store.set_zone_overlay(self.zone_id, CONST_OVERLAY_MANUAL)
|
||||
self._store.set_zone_off(self.zone_id, CONST_OVERLAY_MANUAL)
|
||||
self._overlay_mode = self._current_operation
|
||||
return
|
||||
|
||||
|
@ -134,7 +134,7 @@ def setup(hass, config):
|
||||
discovery.listen(hass, SERVICE_YEELIGHT, device_discovered)
|
||||
|
||||
def update(event):
|
||||
for device in yeelight_data.values():
|
||||
for device in list(yeelight_data.values()):
|
||||
device.update()
|
||||
|
||||
track_time_interval(
|
||||
@ -185,8 +185,8 @@ class YeelightDevice:
|
||||
@property
|
||||
def bulb(self):
|
||||
"""Return bulb device."""
|
||||
import yeelight
|
||||
if self._bulb_device is None:
|
||||
import yeelight
|
||||
try:
|
||||
self._bulb_device = yeelight.Bulb(self._ipaddr,
|
||||
model=self._model)
|
||||
@ -241,33 +241,27 @@ class YeelightDevice:
|
||||
|
||||
def turn_on(self, duration=DEFAULT_TRANSITION, light_type=None):
|
||||
"""Turn on device."""
|
||||
import yeelight
|
||||
|
||||
if not light_type:
|
||||
light_type = yeelight.enums.LightType.Main
|
||||
from yeelight import BulbException
|
||||
|
||||
try:
|
||||
self.bulb.turn_on(duration=duration, light_type=light_type)
|
||||
except yeelight.BulbException as ex:
|
||||
except BulbException as ex:
|
||||
_LOGGER.error("Unable to turn the bulb on: %s", ex)
|
||||
return
|
||||
|
||||
def turn_off(self, duration=DEFAULT_TRANSITION, light_type=None):
|
||||
"""Turn off device."""
|
||||
import yeelight
|
||||
|
||||
if not light_type:
|
||||
light_type = yeelight.enums.LightType.Main
|
||||
from yeelight import BulbException
|
||||
|
||||
try:
|
||||
self.bulb.turn_off(duration=duration, light_type=light_type)
|
||||
except yeelight.BulbException as ex:
|
||||
except BulbException as ex:
|
||||
_LOGGER.error("Unable to turn the bulb off: %s", ex)
|
||||
return
|
||||
|
||||
def update(self):
|
||||
"""Read new properties from the device."""
|
||||
import yeelight
|
||||
from yeelight import BulbException
|
||||
|
||||
if not self.bulb:
|
||||
return
|
||||
@ -275,7 +269,7 @@ class YeelightDevice:
|
||||
try:
|
||||
self.bulb.get_properties(UPDATE_REQUEST_PROPERTIES)
|
||||
self._available = True
|
||||
except yeelight.BulbException as ex:
|
||||
except BulbException as ex:
|
||||
if self._available: # just inform once
|
||||
_LOGGER.error("Unable to update bulb status: %s", ex)
|
||||
self._available = False
|
||||
|
@ -4,7 +4,7 @@ import logging
|
||||
from homeassistant.components.binary_sensor import BinarySensorDevice
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.components.yeelight import DATA_YEELIGHT, DATA_UPDATED
|
||||
from . import DATA_YEELIGHT, DATA_UPDATED
|
||||
|
||||
DEPENDENCIES = ['yeelight']
|
||||
|
||||
|
@ -15,7 +15,7 @@ from homeassistant.components.light import (
|
||||
SUPPORT_COLOR, SUPPORT_TRANSITION, SUPPORT_COLOR_TEMP, SUPPORT_FLASH,
|
||||
SUPPORT_EFFECT, Light)
|
||||
import homeassistant.util.color as color_util
|
||||
from homeassistant.components.yeelight import (
|
||||
from . import (
|
||||
CONF_TRANSITION, DATA_YEELIGHT, CONF_MODE_MUSIC,
|
||||
CONF_SAVE_ON_CHANGE, CONF_CUSTOM_EFFECTS, DATA_UPDATED,
|
||||
YEELIGHT_SERVICE_SCHEMA, DOMAIN, ATTR_TRANSITIONS,
|
||||
@ -189,6 +189,8 @@ class YeelightLight(Light):
|
||||
|
||||
def __init__(self, device, custom_effects=None):
|
||||
"""Initialize the Yeelight light."""
|
||||
from yeelight.enums import LightType
|
||||
|
||||
self.config = device.config
|
||||
self._device = device
|
||||
|
||||
@ -202,6 +204,8 @@ class YeelightLight(Light):
|
||||
self._min_mireds = None
|
||||
self._max_mireds = None
|
||||
|
||||
self._light_type = LightType.Main
|
||||
|
||||
if custom_effects:
|
||||
self._custom_effects = custom_effects
|
||||
else:
|
||||
@ -281,8 +285,7 @@ class YeelightLight(Light):
|
||||
@property
|
||||
def light_type(self):
|
||||
"""Return light type."""
|
||||
import yeelight
|
||||
return yeelight.enums.LightType.Main
|
||||
return self._light_type
|
||||
|
||||
def _get_hs_from_properties(self):
|
||||
rgb = self._get_property('rgb')
|
||||
@ -589,21 +592,19 @@ class YeelightAmbientLight(YeelightLight):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""Initialize the Yeelight Ambient light."""
|
||||
from yeelight.enums import LightType
|
||||
|
||||
super().__init__(*args, **kwargs)
|
||||
self._min_mireds = kelvin_to_mired(6500)
|
||||
self._max_mireds = kelvin_to_mired(1700)
|
||||
|
||||
self._light_type = LightType.Ambient
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
"""Return the name of the device if any."""
|
||||
return "{} ambilight".format(self.device.name)
|
||||
|
||||
@property
|
||||
def light_type(self):
|
||||
"""Return light type."""
|
||||
import yeelight
|
||||
return yeelight.enums.LightType.Ambient
|
||||
|
||||
@property
|
||||
def _is_nightlight_enabled(self):
|
||||
return False
|
||||
|
@ -172,6 +172,7 @@ class Light(ZhaEntity, light.Light):
|
||||
duration = transition * 10 if transition else DEFAULT_DURATION
|
||||
brightness = kwargs.get(light.ATTR_BRIGHTNESS)
|
||||
|
||||
t_log = {}
|
||||
if (brightness is not None or transition) and \
|
||||
self._supported_features & light.SUPPORT_BRIGHTNESS:
|
||||
if brightness is not None:
|
||||
@ -182,7 +183,9 @@ class Light(ZhaEntity, light.Light):
|
||||
level,
|
||||
duration
|
||||
)
|
||||
t_log['move_to_level_with_on_off'] = success
|
||||
if not success:
|
||||
self.debug("turned on: %s", t_log)
|
||||
return
|
||||
self._state = bool(level)
|
||||
if level:
|
||||
@ -190,7 +193,9 @@ class Light(ZhaEntity, light.Light):
|
||||
|
||||
if brightness is None or brightness:
|
||||
success = await self._on_off_channel.on()
|
||||
t_log['on_off'] = success
|
||||
if not success:
|
||||
self.debug("turned on: %s", t_log)
|
||||
return
|
||||
self._state = True
|
||||
|
||||
@ -199,7 +204,9 @@ class Light(ZhaEntity, light.Light):
|
||||
temperature = kwargs[light.ATTR_COLOR_TEMP]
|
||||
success = await self._color_channel.move_to_color_temp(
|
||||
temperature, duration)
|
||||
t_log['move_to_color_temp'] = success
|
||||
if not success:
|
||||
self.debug("turned on: %s", t_log)
|
||||
return
|
||||
self._color_temp = temperature
|
||||
|
||||
@ -212,10 +219,13 @@ class Light(ZhaEntity, light.Light):
|
||||
int(xy_color[1] * 65535),
|
||||
duration,
|
||||
)
|
||||
t_log['move_to_color'] = success
|
||||
if not success:
|
||||
self.debug("turned on: %s", t_log)
|
||||
return
|
||||
self._hs_color = hs_color
|
||||
|
||||
self.debug("turned on: %s", t_log)
|
||||
self.async_schedule_update_ha_state()
|
||||
|
||||
async def async_turn_off(self, **kwargs):
|
||||
@ -229,7 +239,7 @@ class Light(ZhaEntity, light.Light):
|
||||
)
|
||||
else:
|
||||
success = await self._on_off_channel.off()
|
||||
_LOGGER.debug("%s was turned off: %s", self.entity_id, success)
|
||||
self.debug("turned off: %s", success)
|
||||
if not success:
|
||||
return
|
||||
self._state = False
|
||||
@ -238,13 +248,21 @@ class Light(ZhaEntity, light.Light):
|
||||
async def async_update(self):
|
||||
"""Attempt to retrieve on off state from the light."""
|
||||
await super().async_update()
|
||||
await self.async_get_state()
|
||||
|
||||
async def async_get_state(self, from_cache=True):
|
||||
"""Attempt to retrieve on off state from the light."""
|
||||
if self._on_off_channel:
|
||||
self._state = await self._on_off_channel.get_attribute_value(
|
||||
'on_off')
|
||||
'on_off', from_cache=from_cache)
|
||||
if self._level_channel:
|
||||
self._brightness = await self._level_channel.get_attribute_value(
|
||||
'current_level')
|
||||
'current_level', from_cache=from_cache)
|
||||
|
||||
async def refresh(self, time):
|
||||
"""Call async_update at an interval."""
|
||||
await self.async_update()
|
||||
"""Call async_get_state at an interval."""
|
||||
await self.async_get_state(from_cache=False)
|
||||
|
||||
def debug(self, msg, *args):
|
||||
"""Log debug message."""
|
||||
_LOGGER.debug('%s: ' + msg, self.entity_id, *args)
|
||||
|
@ -14,7 +14,7 @@ from .const import CONF_PASSIVE, DOMAIN, HOME_ZONE
|
||||
|
||||
@callback
|
||||
def configured_zones(hass):
|
||||
"""Return a set of the configured hosts."""
|
||||
"""Return a set of the configured zones."""
|
||||
return set((slugify(entry.data[CONF_NAME])) for
|
||||
entry in hass.config_entries.async_entries(DOMAIN))
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
"""Constants used by Home Assistant components."""
|
||||
MAJOR_VERSION = 0
|
||||
MINOR_VERSION = 91
|
||||
PATCH_VERSION = '1'
|
||||
PATCH_VERSION = '2'
|
||||
__short_version__ = '{}.{}'.format(MAJOR_VERSION, MINOR_VERSION)
|
||||
__version__ = '{}.{}'.format(__short_version__, PATCH_VERSION)
|
||||
REQUIRED_PYTHON_VER = (3, 5, 3)
|
||||
|
@ -35,7 +35,7 @@ async def test_sensor(hass, create_registrations, webhook_client): # noqa: F401
|
||||
assert reg_resp.status == 201
|
||||
|
||||
json = await reg_resp.json()
|
||||
assert json == {'status': 'registered'}
|
||||
assert json == {'success': True}
|
||||
|
||||
entity = hass.states.get('sensor.battery_state')
|
||||
assert entity is not None
|
||||
@ -122,7 +122,7 @@ async def test_sensor_id_no_dupes(hass, create_registrations, # noqa: F401, F81
|
||||
assert reg_resp.status == 201
|
||||
|
||||
reg_json = await reg_resp.json()
|
||||
assert reg_json == {'status': 'registered'}
|
||||
assert reg_json == {'success': True}
|
||||
|
||||
dupe_resp = await webhook_client.post(webhook_url, json=payload)
|
||||
|
||||
|
@ -4,8 +4,10 @@ import logging
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.mobile_app.const import CONF_SECRET
|
||||
from homeassistant.components.zone import DOMAIN as ZONE_DOMAIN
|
||||
from homeassistant.const import CONF_WEBHOOK_ID
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
from tests.common import async_mock_service
|
||||
|
||||
@ -100,6 +102,64 @@ async def test_webhook_update_registration(webhook_client, hass_client): # noqa
|
||||
assert CONF_SECRET not in update_json
|
||||
|
||||
|
||||
async def test_webhook_handle_get_zones(hass, create_registrations, # noqa: F401, F811, E501
|
||||
webhook_client): # noqa: F811
|
||||
"""Test that we can get zones properly."""
|
||||
await async_setup_component(hass, ZONE_DOMAIN, {
|
||||
ZONE_DOMAIN: {
|
||||
'name': 'test',
|
||||
'latitude': 32.880837,
|
||||
'longitude': -117.237561,
|
||||
'radius': 250,
|
||||
}
|
||||
})
|
||||
|
||||
resp = await webhook_client.post(
|
||||
'/api/webhook/{}'.format(create_registrations[1]['webhook_id']),
|
||||
json={'type': 'get_zones'}
|
||||
)
|
||||
|
||||
assert resp.status == 200
|
||||
|
||||
json = await resp.json()
|
||||
assert len(json) == 1
|
||||
assert json[0]['entity_id'] == 'zone.home'
|
||||
|
||||
|
||||
async def test_webhook_handle_get_config(hass, create_registrations, # noqa: F401, F811, E501
|
||||
webhook_client): # noqa: F811
|
||||
"""Test that we can get config properly."""
|
||||
resp = await webhook_client.post(
|
||||
'/api/webhook/{}'.format(create_registrations[1]['webhook_id']),
|
||||
json={'type': 'get_config'}
|
||||
)
|
||||
|
||||
assert resp.status == 200
|
||||
|
||||
json = await resp.json()
|
||||
if 'components' in json:
|
||||
json['components'] = set(json['components'])
|
||||
if 'whitelist_external_dirs' in json:
|
||||
json['whitelist_external_dirs'] = \
|
||||
set(json['whitelist_external_dirs'])
|
||||
|
||||
hass_config = hass.config.as_dict()
|
||||
|
||||
expected_dict = {
|
||||
'latitude': hass_config['latitude'],
|
||||
'longitude': hass_config['longitude'],
|
||||
'elevation': hass_config['elevation'],
|
||||
'unit_system': hass_config['unit_system'],
|
||||
'location_name': hass_config['location_name'],
|
||||
'time_zone': hass_config['time_zone'],
|
||||
'components': hass_config['components'],
|
||||
'version': hass_config['version'],
|
||||
'theme_color': '#03A9F4', # Default frontend theme color
|
||||
}
|
||||
|
||||
assert expected_dict == json
|
||||
|
||||
|
||||
async def test_webhook_returns_error_incorrect_json(webhook_client, # noqa: F401, F811, E501
|
||||
create_registrations, # noqa: F401, F811, E501
|
||||
caplog): # noqa: E501 F811
|
||||
|
Loading…
x
Reference in New Issue
Block a user