Merge pull request #35907 from home-assistant/rc

0.110.1
This commit is contained in:
Pascal Vizeli 2020-05-21 12:26:15 +02:00 committed by GitHub
commit 7c784b6963
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 275 additions and 90 deletions

View File

@ -16,7 +16,7 @@ from homeassistant.helpers.typing import HomeAssistantType
from homeassistant.util import Throttle
from . import config_flow # noqa: F401
from .const import CONF_KEY, CONF_UUID, TIMEOUT
from .const import CONF_KEY, CONF_UUID, KEY_MAC, TIMEOUT
_LOGGER = logging.getLogger(__name__)
@ -61,6 +61,9 @@ async def async_setup(hass, config):
async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry):
"""Establish connection with Daikin."""
conf = entry.data
# For backwards compat, set unique ID
if entry.unique_id is None:
hass.config_entries.async_update_entry(entry, unique_id=conf[KEY_MAC])
daikin_api = await daikin_api_setup(
hass,
conf[CONF_HOST],

View File

@ -3,5 +3,5 @@
"name": "De Lijn",
"documentation": "https://www.home-assistant.io/integrations/delijn",
"codeowners": ["@bollewolle"],
"requirements": ["pydelijn==0.5.1"]
"requirements": ["pydelijn==0.6.0"]
}

View File

@ -2,6 +2,7 @@
import logging
from pydelijn.api import Passages
from pydelijn.common import HttpException
import voluptuous as vol
from homeassistant.components.sensor import PLATFORM_SCHEMA
@ -37,22 +38,23 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
"""Create the sensor."""
api_key = config[CONF_API_KEY]
name = DEFAULT_NAME
session = async_get_clientsession(hass)
sensors = []
for nextpassage in config[CONF_NEXT_DEPARTURE]:
stop_id = nextpassage[CONF_STOP_ID]
number_of_departures = nextpassage[CONF_NUMBER_OF_DEPARTURES]
line = Passages(
hass.loop, stop_id, number_of_departures, api_key, session, True
sensors.append(
DeLijnPublicTransportSensor(
Passages(
hass.loop,
nextpassage[CONF_STOP_ID],
nextpassage[CONF_NUMBER_OF_DEPARTURES],
api_key,
session,
True,
)
)
)
await line.get_passages()
if line.passages is None:
_LOGGER.warning("No data received from De Lijn")
return
sensors.append(DeLijnPublicTransportSensor(line, name))
async_add_entities(sensors, True)
@ -60,20 +62,28 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
class DeLijnPublicTransportSensor(Entity):
"""Representation of a Ruter sensor."""
def __init__(self, line, name):
def __init__(self, line):
"""Initialize the sensor."""
self.line = line
self._attributes = {ATTR_ATTRIBUTION: ATTRIBUTION}
self._name = name
self._name = None
self._state = None
self._available = False
self._available = True
async def async_update(self):
"""Get the latest data from the De Lijn API."""
await self.line.get_passages()
if self.line.passages is None:
_LOGGER.warning("No data received from De Lijn")
try:
await self.line.get_passages()
self._name = await self.line.get_stopname()
except HttpException:
self._available = False
_LOGGER.error("De Lijn http error")
return
self._attributes["stopname"] = self._name
for passage in self.line.passages:
passage["stopname"] = self._name
try:
first = self.line.passages[0]
if first["due_at_realtime"] is not None:
@ -81,8 +91,6 @@ class DeLijnPublicTransportSensor(Entity):
else:
first_passage = first["due_at_schedule"]
self._state = first_passage
self._name = first["stopname"]
self._attributes["stopname"] = first["stopname"]
self._attributes["line_number_public"] = first["line_number_public"]
self._attributes["line_transport_type"] = first["line_transport_type"]
self._attributes["final_destination"] = first["final_destination"]
@ -90,8 +98,8 @@ class DeLijnPublicTransportSensor(Entity):
self._attributes["due_at_realtime"] = first["due_at_realtime"]
self._attributes["next_passages"] = self.line.passages
self._available = True
except (KeyError, IndexError) as error:
_LOGGER.debug("Error getting data from De Lijn: %s", error)
except (KeyError, IndexError):
_LOGGER.error("Invalid data received from De Lijn")
self._available = False
@property

View File

@ -3,7 +3,7 @@
"name": "forked-daapd",
"documentation": "https://www.home-assistant.io/integrations/forked-daapd",
"codeowners": ["@uvjustin"],
"requirements": ["pyforked-daapd==0.1.8", "pylibrespot-java==0.1.0"],
"requirements": ["pyforked-daapd==0.1.9", "pylibrespot-java==0.1.0"],
"config_flow": true,
"zeroconf": ["_daap._tcp.local."]
}

View File

@ -794,26 +794,29 @@ class ForkedDaapdUpdater:
"queue" in update_types
): # update queue, queue before player for async_play_media
queue = await self._api.get_request("queue")
update_events["queue"] = asyncio.Event()
async_dispatcher_send(
self.hass,
SIGNAL_UPDATE_QUEUE.format(self._entry_id),
queue,
update_events["queue"],
)
if queue:
update_events["queue"] = asyncio.Event()
async_dispatcher_send(
self.hass,
SIGNAL_UPDATE_QUEUE.format(self._entry_id),
queue,
update_events["queue"],
)
# order of below don't matter
if not {"outputs", "volume"}.isdisjoint(update_types): # update outputs
outputs = (await self._api.get_request("outputs"))["outputs"]
update_events[
"outputs"
] = asyncio.Event() # only for master, zones should ignore
async_dispatcher_send(
self.hass,
SIGNAL_UPDATE_OUTPUTS.format(self._entry_id),
outputs,
update_events["outputs"],
)
self._add_zones(outputs)
outputs = await self._api.get_request("outputs")
if outputs:
outputs = outputs["outputs"]
update_events[
"outputs"
] = asyncio.Event() # only for master, zones should ignore
async_dispatcher_send(
self.hass,
SIGNAL_UPDATE_OUTPUTS.format(self._entry_id),
outputs,
update_events["outputs"],
)
self._add_zones(outputs)
if not {"database"}.isdisjoint(update_types):
pipes, playlists = await asyncio.gather(
self._api.get_pipes(), self._api.get_playlists()
@ -832,17 +835,18 @@ class ForkedDaapdUpdater:
update_types
): # update player
player = await self._api.get_request("player")
update_events["player"] = asyncio.Event()
if update_events.get("queue"):
await update_events[
"queue"
].wait() # make sure queue done before player for async_play_media
async_dispatcher_send(
self.hass,
SIGNAL_UPDATE_PLAYER.format(self._entry_id),
player,
update_events["player"],
)
if player:
update_events["player"] = asyncio.Event()
if update_events.get("queue"):
await update_events[
"queue"
].wait() # make sure queue done before player for async_play_media
async_dispatcher_send(
self.hass,
SIGNAL_UPDATE_PLAYER.format(self._entry_id),
player,
update_events["player"],
)
if update_events:
await asyncio.wait(
[event.wait() for event in update_events.values()]

View File

@ -2,7 +2,7 @@
"domain": "frontend",
"name": "Home Assistant Frontend",
"documentation": "https://www.home-assistant.io/integrations/frontend",
"requirements": ["home-assistant-frontend==20200519.0"],
"requirements": ["home-assistant-frontend==20200519.1"],
"dependencies": [
"api",
"auth",

View File

@ -627,12 +627,14 @@ class HomeKit:
ent_cfg = self._config.setdefault(entity_id, {})
if ent_reg_ent.device_id:
dev_reg_ent = dev_reg.async_get(ent_reg_ent.device_id)
if dev_reg_ent.manufacturer:
ent_cfg[ATTR_MANUFACTURER] = dev_reg_ent.manufacturer
if dev_reg_ent.model:
ent_cfg[ATTR_MODEL] = dev_reg_ent.model
if dev_reg_ent.sw_version:
ent_cfg[ATTR_SOFTWARE_VERSION] = dev_reg_ent.sw_version
if dev_reg_ent is not None:
# Handle missing devices
if dev_reg_ent.manufacturer:
ent_cfg[ATTR_MANUFACTURER] = dev_reg_ent.manufacturer
if dev_reg_ent.model:
ent_cfg[ATTR_MODEL] = dev_reg_ent.model
if dev_reg_ent.sw_version:
ent_cfg[ATTR_SOFTWARE_VERSION] = dev_reg_ent.sw_version
if ATTR_MANUFACTURER not in ent_cfg:
integration = await async_get_integration(self.hass, ent_reg_ent.platform)
ent_cfg[ATTR_INTERGRATION] = integration.name

View File

@ -111,7 +111,7 @@ class HMLight(HMDevice, LightEntity):
):
self._hmdevice.on(self._channel)
if ATTR_HS_COLOR in kwargs:
if ATTR_HS_COLOR in kwargs and self.supported_features & SUPPORT_COLOR:
self._hmdevice.set_hs_color(
hue=kwargs[ATTR_HS_COLOR][0] / 360.0,
saturation=kwargs[ATTR_HS_COLOR][1] / 100.0,

View File

@ -31,9 +31,15 @@ from .const import (
DEVICE_REVISION,
DEVICE_SERIAL_NUMBER,
DOMAIN,
FIRMWARE_BUILD,
FIRMWARE_IN_USERDATA,
FIRMWARE_SUB_REVISION,
HUB_EXCEPTIONS,
HUB_NAME,
LEGACY_DEVICE_BUILD,
LEGACY_DEVICE_MODEL,
LEGACY_DEVICE_REVISION,
LEGACY_DEVICE_SUB_REVISION,
MAC_ADDRESS_IN_USERDATA,
MAINPROCESSOR_IN_USERDATA_FIRMWARE,
MODEL_IN_MAINPROCESSOR,
@ -159,9 +165,19 @@ async def async_get_device_info(pv_request):
resources = await userdata.get_resources()
userdata_data = resources[USER_DATA]
main_processor_info = userdata_data[FIRMWARE_IN_USERDATA][
MAINPROCESSOR_IN_USERDATA_FIRMWARE
]
if FIRMWARE_IN_USERDATA in userdata_data:
main_processor_info = userdata_data[FIRMWARE_IN_USERDATA][
MAINPROCESSOR_IN_USERDATA_FIRMWARE
]
else:
# Legacy devices
main_processor_info = {
REVISION_IN_MAINPROCESSOR: LEGACY_DEVICE_REVISION,
FIRMWARE_SUB_REVISION: LEGACY_DEVICE_SUB_REVISION,
FIRMWARE_BUILD: LEGACY_DEVICE_BUILD,
MODEL_IN_MAINPROCESSOR: LEGACY_DEVICE_MODEL,
}
return {
DEVICE_NAME: base64_to_unicode(userdata_data[HUB_NAME]),
DEVICE_MAC_ADDRESS: userdata_data[MAC_ADDRESS_IN_USERDATA],

View File

@ -65,3 +65,8 @@ PV_ROOM_DATA = "pv_room_data"
COORDINATOR = "coordinator"
HUB_EXCEPTIONS = (asyncio.TimeoutError, PvApiConnectionError)
LEGACY_DEVICE_SUB_REVISION = 1
LEGACY_DEVICE_REVISION = 0
LEGACY_DEVICE_BUILD = 0
LEGACY_DEVICE_MODEL = "PV Hub1.0"

View File

@ -3,6 +3,5 @@
"name": "Logbook",
"documentation": "https://www.home-assistant.io/integrations/logbook",
"dependencies": ["frontend", "http", "recorder"],
"after_dependencies": ["homekit"],
"codeowners": []
}

View File

@ -106,11 +106,6 @@ class ONVIFCameraEntity(ONVIFBaseEntity, Camera):
async def stream_source(self):
"""Return the stream source."""
if self._stream_uri is None:
uri_no_auth = await self.device.async_get_stream_uri(self.profile)
self._stream_uri = uri_no_auth.replace(
"rtsp://", f"rtsp://{self.device.username}:{self.device.password}@", 1
)
return self._stream_uri
async def async_camera_image(self):
@ -118,11 +113,6 @@ class ONVIFCameraEntity(ONVIFBaseEntity, Camera):
image = None
if self.device.capabilities.snapshot:
if self._snapshot_uri is None:
self._snapshot_uri = await self.device.async_get_snapshot_uri(
self.profile
)
auth = None
if self.device.username and self.device.password:
auth = HTTPDigestAuth(self.device.username, self.device.password)
@ -181,6 +171,16 @@ class ONVIFCameraEntity(ONVIFBaseEntity, Camera):
finally:
await stream.close()
async def async_added_to_hass(self):
"""Run when entity about to be added to hass."""
uri_no_auth = await self.device.async_get_stream_uri(self.profile)
self._stream_uri = uri_no_auth.replace(
"rtsp://", f"rtsp://{self.device.username}:{self.device.password}@", 1
)
if self.device.capabilities.snapshot:
self._snapshot_uri = await self.device.async_get_snapshot_uri(self.profile)
async def async_perform_ptz(
self,
distance,

View File

@ -223,7 +223,7 @@ class ONVIFDevice:
try:
media_service = self.device.create_media_service()
media_capabilities = await media_service.GetServiceCapabilities()
snapshot = media_capabilities.SnapshotUri
snapshot = media_capabilities and media_capabilities.SnapshotUri
except (ONVIFError, Fault):
pass
@ -231,7 +231,7 @@ class ONVIFDevice:
try:
event_service = self.device.create_events_service()
event_capabilities = await event_service.GetServiceCapabilities()
pullpoint = event_capabilities.WSPullPointSupport
pullpoint = event_capabilities and event_capabilities.WSPullPointSupport
except (ONVIFError, Fault):
pass

View File

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

View File

@ -200,14 +200,19 @@ class Store:
async def _async_handle_write_data(self, *_args):
"""Handle writing the config."""
data = self._data
if "data_func" in data:
data["data"] = data.pop("data_func")()
self._data = None
async with self._write_lock:
if self._data is None:
# Another write already consumed the data
return
data = self._data
if "data_func" in data:
data["data"] = data.pop("data_func")()
self._data = None
try:
await self.hass.async_add_executor_job(
self._write_data, self.path, data

View File

@ -12,7 +12,7 @@ cryptography==2.9.2
defusedxml==0.6.0
distro==1.5.0
hass-nabucasa==0.34.2
home-assistant-frontend==20200519.0
home-assistant-frontend==20200519.1
importlib-metadata==1.6.0
jinja2>=2.11.1
netdisco==2.6.0

View File

@ -731,7 +731,7 @@ hole==0.5.1
holidays==0.10.2
# homeassistant.components.frontend
home-assistant-frontend==20200519.0
home-assistant-frontend==20200519.1
# homeassistant.components.zwave
homeassistant-pyozw==0.1.10
@ -1272,7 +1272,7 @@ pydanfossair==0.1.0
pydeconz==70
# homeassistant.components.delijn
pydelijn==0.5.1
pydelijn==0.6.0
# homeassistant.components.zwave
pydispatcher==2.0.5
@ -1332,7 +1332,7 @@ pyflunearyou==1.0.7
pyfnip==0.2
# homeassistant.components.forked_daapd
pyforked-daapd==0.1.8
pyforked-daapd==0.1.9
# homeassistant.components.fritzbox
pyfritzhome==0.4.2

View File

@ -312,7 +312,7 @@ hole==0.5.1
holidays==0.10.2
# homeassistant.components.frontend
home-assistant-frontend==20200519.0
home-assistant-frontend==20200519.1
# homeassistant.components.zwave
homeassistant-pyozw==0.1.10
@ -554,7 +554,7 @@ pyflume==0.4.0
pyflunearyou==1.0.7
# homeassistant.components.forked_daapd
pyforked-daapd==0.1.8
pyforked-daapd==0.1.9
# homeassistant.components.fritzbox
pyfritzhome==0.4.2

View File

@ -8,6 +8,9 @@ from homeassistant.components.forked_daapd.const import (
CONF_TTS_PAUSE_TIME,
CONF_TTS_VOLUME,
DOMAIN,
SIGNAL_UPDATE_OUTPUTS,
SIGNAL_UPDATE_PLAYER,
SIGNAL_UPDATE_QUEUE,
SOURCE_NAME_CLEAR,
SOURCE_NAME_DEFAULT,
SUPPORTED_FEATURES,
@ -63,7 +66,7 @@ from homeassistant.const import (
)
from tests.async_mock import patch
from tests.common import MockConfigEntry
from tests.common import MockConfigEntry, async_mock_signal
TEST_MASTER_ENTITY_NAME = "media_player.forked_daapd_server"
TEST_ZONE_ENTITY_NAMES = [
@ -369,6 +372,32 @@ def test_master_state(hass, mock_api_object):
assert not state.attributes[ATTR_MEDIA_SHUFFLE]
async def test_no_update_when_get_request_returns_none(
hass, config_entry, mock_api_object
):
"""Test when get request returns None."""
async def get_request_side_effect(update_type):
return None
mock_api_object.get_request.side_effect = get_request_side_effect
updater_update = mock_api_object.start_websocket_handler.call_args[0][2]
signal_output_call = async_mock_signal(
hass, SIGNAL_UPDATE_OUTPUTS.format(config_entry.entry_id)
)
signal_player_call = async_mock_signal(
hass, SIGNAL_UPDATE_PLAYER.format(config_entry.entry_id)
)
signal_queue_call = async_mock_signal(
hass, SIGNAL_UPDATE_QUEUE.format(config_entry.entry_id)
)
await updater_update(["outputs", "player", "queue"])
await hass.async_block_till_done()
assert len(signal_output_call) == 0
assert len(signal_player_call) == 0
assert len(signal_queue_call) == 0
async def _service_call(
hass, entity_name, service, additional_service_data=None, blocking=True
):

View File

@ -913,3 +913,83 @@ def _write_data(path: str, data: Dict) -> None:
if not os.path.isdir(os.path.dirname(path)):
os.makedirs(os.path.dirname(path))
json_util.save_json(path, data)
async def test_homekit_ignored_missing_devices(
hass, hk_driver, debounce_patcher, device_reg, entity_reg
):
"""Test HomeKit handles a device in the entity registry but missing from the device registry."""
entry = await async_init_integration(hass)
homekit = HomeKit(
hass,
None,
None,
None,
{},
{"light.demo": {}},
DEFAULT_SAFE_MODE,
advertise_ip=None,
interface_choice=None,
entry_id=entry.entry_id,
)
homekit.driver = hk_driver
homekit._filter = Mock(return_value=True)
homekit.bridge = HomeBridge(hass, hk_driver, "mock_bridge")
config_entry = MockConfigEntry(domain="test", data={})
config_entry.add_to_hass(hass)
device_entry = device_reg.async_get_or_create(
config_entry_id=config_entry.entry_id,
sw_version="0.16.0",
model="Powerwall 2",
manufacturer="Tesla",
connections={(device_registry.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")},
)
entity_reg.async_get_or_create(
"binary_sensor",
"powerwall",
"battery_charging",
device_id=device_entry.id,
device_class=DEVICE_CLASS_BATTERY_CHARGING,
)
entity_reg.async_get_or_create(
"sensor",
"powerwall",
"battery",
device_id=device_entry.id,
device_class=DEVICE_CLASS_BATTERY,
)
light = entity_reg.async_get_or_create(
"light", "powerwall", "demo", device_id=device_entry.id
)
# Delete the device to make sure we fallback
# to using the platform
device_reg.async_remove_device(device_entry.id)
hass.states.async_set(light.entity_id, STATE_ON)
def _mock_get_accessory(*args, **kwargs):
return [None, "acc", None]
with patch.object(homekit.bridge, "add_accessory"), patch(
f"{PATH_HOMEKIT}.show_setup_message"
), patch(f"{PATH_HOMEKIT}.get_accessory") as mock_get_acc, patch(
"pyhap.accessory_driver.AccessoryDriver.start"
):
await homekit.async_start()
await hass.async_block_till_done()
mock_get_acc.assert_called_with(
hass,
hk_driver,
ANY,
ANY,
{
"platform": "Tesla Powerwall",
"linked_battery_charging_sensor": "binary_sensor.powerwall_battery_charging",
"linked_battery_sensor": "sensor.powerwall_battery",
},
)

View File

@ -0,0 +1,34 @@
{
"userData" : {
"enableScheduledEvents" : true,
"staticIp" : false,
"sceneControllerCount" : 0,
"accessPointCount" : 0,
"shadeCount" : 5,
"ip" : "192.168.20.9",
"groupCount" : 9,
"scheduledEventCount" : 0,
"editingEnabled" : true,
"roomCount" : 5,
"setupCompleted" : false,
"sceneCount" : 18,
"sceneControllerMemberCount" : 0,
"mask" : "255.255.255.0",
"hubName" : "UG93ZXJWaWV3IEh1YiBHZW4gMQ==",
"rfID" : "0x8B2A",
"remoteConnectEnabled" : false,
"multiSceneMemberCount" : 0,
"rfStatus" : 0,
"serialNumber" : "REMOVED",
"undefinedShadeCount" : 0,
"sceneMemberCount" : 18,
"unassignedShadeCount" : 0,
"multiSceneCount" : 0,
"addressKind" : "newPrimary",
"gateway" : "192.168.20.1",
"localTimeDataSet" : true,
"dns" : "192.168.20.1",
"macAddress" : "00:00:00:00:00:eb",
"rfIDInt" : 35626
}
}