Compare commits

...

17 Commits

Author SHA1 Message Date
Paulus Schoutsen
a69938afa2 Bumped version to 0.111.0b5 2020-06-09 12:39:29 -07:00
Paulus Schoutsen
94c3d9bac0 Fix default for loading games file ps4 (#36592) 2020-06-09 12:39:08 -07:00
Bas Nijholt
fb7af0384f bump aiokef to 0.2.10 (#36574)
0.2.9 generated a lot of calls on the event loop.
2020-06-09 12:39:07 -07:00
Donnie
5a9a95abe4 Fix nanoleaf incorrect effect update (#36517) 2020-06-09 12:39:06 -07:00
Paulus Schoutsen
e1ad108b6d Merge remote-tracking branch 'origin/master' into rc 2020-06-09 12:38:49 -07:00
Paulus Schoutsen
a76620b76f Merge pull request #36607 from home-assistant/110.7 2020-06-09 12:10:20 -07:00
Paulus Schoutsen
13fd80affa Bumped version to 0.110.7 2020-06-09 11:07:32 -07:00
Franck Nijhof
d576749530 Fix mobile_app missing state in sensor registration (#36604) 2020-06-09 11:07:27 -07:00
Paulus Schoutsen
111a00aeeb Bumped version to 0.111.0b4 2020-06-08 12:28:53 -07:00
Franck Nijhof
b3d5717df8 Fix mobile_app sensor re-registration handling (#36567) 2020-06-08 12:27:12 -07:00
Paulus Schoutsen
a84378bb79 Mobile app fixes (#36559) 2020-06-08 12:27:11 -07:00
shbatm
53ba45cc8f Add Z-Wave Notification Sensor support to ISY994 (#36548) 2020-06-08 12:27:10 -07:00
Jörg Thalheim
5d77eb1839 Fix intent component initialisation (#36064)
The intent component expect this method from every module that is called intent.
Fixes #35522
2020-06-08 12:27:09 -07:00
Paulus Schoutsen
517159dc4e Merge remote-tracking branch 'origin/master' into rc 2020-06-08 11:17:27 -07:00
Paulus Schoutsen
a9287b7117 Merge pull request #36579 from home-assistant/110.6 2020-06-08 11:15:35 -07:00
Paulus Schoutsen
0b7bcc87df Bumped version to 0.110.6 2020-06-08 10:14:45 -07:00
Franck Nijhof
482661f82c Fix mobile_app registering/update sensor values with an unknown state (#36566) 2020-06-08 10:14:40 -07:00
10 changed files with 191 additions and 43 deletions

View File

@@ -40,6 +40,16 @@ def async_setup(hass):
hass.http.register_view(AlexaIntentsView)
async def async_setup_intents(hass):
"""
Do intents setup.
Right now this module does not expose any, but the intent component breaks
without it.
"""
pass # pylint: disable=unnecessary-pass
class UnknownRequest(HomeAssistantError):
"""When an unknown Alexa request is passed in."""

View File

@@ -232,7 +232,7 @@ NODE_FILTERS = {
"RemoteLinc2_ADV",
],
FILTER_INSTEON_TYPE: ["0.16.", "0.17.", "0.18.", "9.0.", "9.7."],
FILTER_ZWAVE_CAT: (["118", "143"] + list(map(str, range(180, 185)))),
FILTER_ZWAVE_CAT: (["118", "143"] + list(map(str, range(180, 186)))),
},
LOCK: {
FILTER_UOM: ["11"],

View File

@@ -3,5 +3,5 @@
"name": "KEF",
"documentation": "https://www.home-assistant.io/integrations/kef",
"codeowners": ["@basnijholt"],
"requirements": ["aiokef==0.2.9", "getmac==0.8.2"]
"requirements": ["aiokef==0.2.10", "getmac==0.8.2"]
}

View File

@@ -56,7 +56,6 @@ ERR_ENCRYPTION_ALREADY_ENABLED = "encryption_already_enabled"
ERR_ENCRYPTION_NOT_AVAILABLE = "encryption_not_available"
ERR_ENCRYPTION_REQUIRED = "encryption_required"
ERR_SENSOR_NOT_REGISTERED = "not_registered"
ERR_SENSOR_DUPLICATE_UNIQUE_ID = "duplicate_unique_id"
ERR_INVALID_FORMAT = "invalid_format"

View File

@@ -1,4 +1,5 @@
"""Webhook handlers for mobile_app."""
import asyncio
from functools import wraps
import logging
import secrets
@@ -28,7 +29,7 @@ from homeassistant.const import (
HTTP_CREATED,
)
from homeassistant.core import EventOrigin
from homeassistant.exceptions import HomeAssistantError, ServiceNotFound, TemplateError
from homeassistant.exceptions import ServiceNotFound, TemplateError
from homeassistant.helpers import config_validation as cv, device_registry as dr
from homeassistant.helpers.dispatcher import async_dispatcher_send
from homeassistant.helpers.template import attach
@@ -77,7 +78,6 @@ from .const import (
ERR_ENCRYPTION_NOT_AVAILABLE,
ERR_ENCRYPTION_REQUIRED,
ERR_INVALID_FORMAT,
ERR_SENSOR_DUPLICATE_UNIQUE_ID,
ERR_SENSOR_NOT_REGISTERED,
SIGNAL_LOCATION_UPDATE,
SIGNAL_SENSOR_UPDATE,
@@ -95,6 +95,7 @@ from .helpers import (
_LOGGER = logging.getLogger(__name__)
DELAY_SAVE = 10
WEBHOOK_COMMANDS = Registry()
@@ -184,7 +185,10 @@ async def handle_webhook(
"Received webhook payload for type %s: %s", webhook_type, webhook_payload
)
return await WEBHOOK_COMMANDS[webhook_type](hass, config_entry, webhook_payload)
# Shield so we make sure we finish the webhook, even if sender hangs up.
return await asyncio.shield(
WEBHOOK_COMMANDS[webhook_type](hass, config_entry, webhook_payload)
)
@WEBHOOK_COMMANDS.register("call_service")
@@ -352,38 +356,39 @@ async def webhook_enable_encryption(hass, config_entry, data):
vol.Required(ATTR_SENSOR_TYPE): vol.In(SENSOR_TYPES),
vol.Required(ATTR_SENSOR_UNIQUE_ID): cv.string,
vol.Optional(ATTR_SENSOR_UOM): cv.string,
vol.Required(ATTR_SENSOR_STATE): vol.Any(bool, str, int, float),
vol.Optional(ATTR_SENSOR_STATE, default=None): vol.Any(
None, bool, str, int, float
),
vol.Optional(ATTR_SENSOR_ICON, default="mdi:cellphone"): cv.icon,
}
)
async def webhook_register_sensor(hass, config_entry, data):
"""Handle a register sensor webhook."""
entity_type = data[ATTR_SENSOR_TYPE]
unique_id = data[ATTR_SENSOR_UNIQUE_ID]
unique_store_key = f"{config_entry.data[CONF_WEBHOOK_ID]}_{unique_id}"
if unique_store_key in hass.data[DOMAIN][entity_type]:
_LOGGER.error("Refusing to re-register existing sensor %s!", unique_id)
return error_response(
ERR_SENSOR_DUPLICATE_UNIQUE_ID,
f"{entity_type} {unique_id} already exists!",
status=409,
)
existing_sensor = unique_store_key in hass.data[DOMAIN][entity_type]
data[CONF_WEBHOOK_ID] = config_entry.data[CONF_WEBHOOK_ID]
# If sensor already is registered, update current state instead
if existing_sensor:
_LOGGER.debug("Re-register existing sensor %s", unique_id)
entry = hass.data[DOMAIN][entity_type][unique_store_key]
data = {**entry, **data}
hass.data[DOMAIN][entity_type][unique_store_key] = data
try:
await hass.data[DOMAIN][DATA_STORE].async_save(savable_state(hass))
except HomeAssistantError as ex:
_LOGGER.error("Error registering sensor: %s", ex)
return empty_okay_response()
hass.data[DOMAIN][DATA_STORE].async_delay_save(
lambda: savable_state(hass), DELAY_SAVE
)
register_signal = f"{DOMAIN}_{data[ATTR_SENSOR_TYPE]}_register"
async_dispatcher_send(hass, register_signal, data)
if existing_sensor:
async_dispatcher_send(hass, SIGNAL_SENSOR_UPDATE, data)
else:
register_signal = f"{DOMAIN}_{data[ATTR_SENSOR_TYPE]}_register"
async_dispatcher_send(hass, register_signal, data)
return webhook_response(
{"success": True}, registration=config_entry.data, status=HTTP_CREATED,
@@ -414,7 +419,7 @@ async def webhook_update_sensor_states(hass, config_entry, data):
{
vol.Optional(ATTR_SENSOR_ATTRIBUTES, default={}): dict,
vol.Optional(ATTR_SENSOR_ICON, default="mdi:cellphone"): cv.icon,
vol.Required(ATTR_SENSOR_STATE): vol.Any(bool, str, int, float),
vol.Required(ATTR_SENSOR_STATE): vol.Any(None, bool, str, int, float),
vol.Required(ATTR_SENSOR_TYPE): vol.In(SENSOR_TYPES),
vol.Required(ATTR_SENSOR_UNIQUE_ID): cv.string,
}
@@ -458,18 +463,14 @@ async def webhook_update_sensor_states(hass, config_entry, data):
hass.data[DOMAIN][entity_type][unique_store_key] = new_state
safe = savable_state(hass)
try:
await hass.data[DOMAIN][DATA_STORE].async_save(safe)
except HomeAssistantError as ex:
_LOGGER.error("Error updating mobile_app registration: %s", ex)
return empty_okay_response()
async_dispatcher_send(hass, SIGNAL_SENSOR_UPDATE, new_state)
resp[unique_id] = {"success": True}
hass.data[DOMAIN][DATA_STORE].async_delay_save(
lambda: savable_state(hass), DELAY_SAVE
)
return webhook_response(resp, registration=config_entry.data)

View File

@@ -210,6 +210,10 @@ class NanoleafLight(LightEntity):
self._light.brightness = int(brightness / 2.55)
if effect:
if effect not in self._effects_list:
raise ValueError(
f"Attempting to apply effect not in the effect list: '{effect}'"
)
self._light.effect = effect
def turn_off(self, **kwargs):
@@ -227,8 +231,13 @@ class NanoleafLight(LightEntity):
self._available = self._light.available
self._brightness = self._light.brightness
self._color_temp = self._light.color_temperature
self._effect = self._light.effect
self._effects_list = self._light.effects
# Nanoleaf api returns non-existent effect named "*Solid*" when light set to solid color.
# This causes various issues with scening (see https://github.com/home-assistant/core/issues/36359).
# Until fixed at the library level, we should ensure the effect exists before saving to light properties
self._effect = (
self._light.effect if self._light.effect in self._effects_list else None
)
self._hs_color = self._light.hue, self._light.saturation
self._state = self._light.on
except Unavailable as err:

View File

@@ -161,7 +161,7 @@ def load_games(hass: HomeAssistantType, unique_id: str) -> dict:
"""Load games for sources."""
g_file = hass.config.path(GAMES_FILE.format(unique_id))
try:
games = load_json(g_file, dict)
games = load_json(g_file)
except HomeAssistantError as error:
games = {}
_LOGGER.error("Failed to load games file: %s", error)

View File

@@ -1,7 +1,7 @@
"""Constants used by Home Assistant components."""
MAJOR_VERSION = 0
MINOR_VERSION = 111
PATCH_VERSION = "0b3"
PATCH_VERSION = "0b5"
__short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}"
__version__ = f"{__short_version__}.{PATCH_VERSION}"
REQUIRED_PYTHON_VER = (3, 7, 0)

View File

@@ -197,7 +197,7 @@ aioimaplib==0.7.15
aiokafka==0.5.1
# homeassistant.components.kef
aiokef==0.2.9
aiokef==0.2.10
# homeassistant.components.lifx
aiolifx==0.6.7

View File

@@ -2,7 +2,7 @@
import logging
from homeassistant.const import UNIT_PERCENTAGE
from homeassistant.const import STATE_UNKNOWN, UNIT_PERCENTAGE
from homeassistant.helpers import device_registry
_LOGGER = logging.getLogger(__name__)
@@ -95,8 +95,8 @@ async def test_sensor_must_register(hass, create_registrations, webhook_client):
assert json["battery_state"]["error"]["code"] == "not_registered"
async def test_sensor_id_no_dupes(hass, create_registrations, webhook_client):
"""Test that sensors must have a unique ID."""
async def test_sensor_id_no_dupes(hass, create_registrations, webhook_client, caplog):
"""Test that a duplicate unique ID in registration updates the sensor."""
webhook_id = create_registrations[1]["webhook_id"]
webhook_url = f"/api/webhook/{webhook_id}"
@@ -120,11 +120,140 @@ async def test_sensor_id_no_dupes(hass, create_registrations, webhook_client):
reg_json = await reg_resp.json()
assert reg_json == {"success": True}
await hass.async_block_till_done()
assert "Re-register existing sensor" not in caplog.text
entity = hass.states.get("sensor.test_1_battery_state")
assert entity is not None
assert entity.attributes["device_class"] == "battery"
assert entity.attributes["icon"] == "mdi:battery"
assert entity.attributes["unit_of_measurement"] == UNIT_PERCENTAGE
assert entity.attributes["foo"] == "bar"
assert entity.domain == "sensor"
assert entity.name == "Test 1 Battery State"
assert entity.state == "100"
payload["data"]["state"] = 99
dupe_resp = await webhook_client.post(webhook_url, json=payload)
assert dupe_resp.status == 409
assert dupe_resp.status == 201
dupe_reg_json = await dupe_resp.json()
assert dupe_reg_json == {"success": True}
await hass.async_block_till_done()
dupe_json = await dupe_resp.json()
assert dupe_json["success"] is False
assert dupe_json["error"]["code"] == "duplicate_unique_id"
assert "Re-register existing sensor" in caplog.text
entity = hass.states.get("sensor.test_1_battery_state")
assert entity is not None
assert entity.attributes["device_class"] == "battery"
assert entity.attributes["icon"] == "mdi:battery"
assert entity.attributes["unit_of_measurement"] == UNIT_PERCENTAGE
assert entity.attributes["foo"] == "bar"
assert entity.domain == "sensor"
assert entity.name == "Test 1 Battery State"
assert entity.state == "99"
async def test_register_sensor_no_state(hass, create_registrations, webhook_client):
"""Test that sensors can be registered, when there is no (unknown) state."""
webhook_id = create_registrations[1]["webhook_id"]
webhook_url = f"/api/webhook/{webhook_id}"
reg_resp = await webhook_client.post(
webhook_url,
json={
"type": "register_sensor",
"data": {
"name": "Battery State",
"state": None,
"type": "sensor",
"unique_id": "battery_state",
},
},
)
assert reg_resp.status == 201
json = await reg_resp.json()
assert json == {"success": True}
await hass.async_block_till_done()
entity = hass.states.get("sensor.test_1_battery_state")
assert entity is not None
assert entity.domain == "sensor"
assert entity.name == "Test 1 Battery State"
assert entity.state == STATE_UNKNOWN
reg_resp = await webhook_client.post(
webhook_url,
json={
"type": "register_sensor",
"data": {
"name": "Backup Battery State",
"type": "sensor",
"unique_id": "backup_battery_state",
},
},
)
assert reg_resp.status == 201
json = await reg_resp.json()
assert json == {"success": True}
await hass.async_block_till_done()
entity = hass.states.get("sensor.test_1_backup_battery_state")
assert entity
assert entity.domain == "sensor"
assert entity.name == "Test 1 Backup Battery State"
assert entity.state == STATE_UNKNOWN
async def test_update_sensor_no_state(hass, create_registrations, webhook_client):
"""Test that sensors can be updated, when there is no (unknown) state."""
webhook_id = create_registrations[1]["webhook_id"]
webhook_url = f"/api/webhook/{webhook_id}"
reg_resp = await webhook_client.post(
webhook_url,
json={
"type": "register_sensor",
"data": {
"name": "Battery State",
"state": 100,
"type": "sensor",
"unique_id": "battery_state",
},
},
)
assert reg_resp.status == 201
json = await reg_resp.json()
assert json == {"success": True}
await hass.async_block_till_done()
entity = hass.states.get("sensor.test_1_battery_state")
assert entity is not None
assert entity.state == "100"
update_resp = await webhook_client.post(
webhook_url,
json={
"type": "update_sensor_states",
"data": [{"state": None, "type": "sensor", "unique_id": "battery_state"}],
},
)
assert update_resp.status == 200
json = await update_resp.json()
assert json == {"battery_state": {"success": True}}
updated_entity = hass.states.get("sensor.test_1_battery_state")
assert updated_entity.state == STATE_UNKNOWN