mirror of
https://github.com/home-assistant/core.git
synced 2025-07-29 08:07:45 +00:00
commit
7c784b6963
@ -16,7 +16,7 @@ from homeassistant.helpers.typing import HomeAssistantType
|
|||||||
from homeassistant.util import Throttle
|
from homeassistant.util import Throttle
|
||||||
|
|
||||||
from . import config_flow # noqa: F401
|
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__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -61,6 +61,9 @@ async def async_setup(hass, config):
|
|||||||
async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry):
|
async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry):
|
||||||
"""Establish connection with Daikin."""
|
"""Establish connection with Daikin."""
|
||||||
conf = entry.data
|
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(
|
daikin_api = await daikin_api_setup(
|
||||||
hass,
|
hass,
|
||||||
conf[CONF_HOST],
|
conf[CONF_HOST],
|
||||||
|
@ -3,5 +3,5 @@
|
|||||||
"name": "De Lijn",
|
"name": "De Lijn",
|
||||||
"documentation": "https://www.home-assistant.io/integrations/delijn",
|
"documentation": "https://www.home-assistant.io/integrations/delijn",
|
||||||
"codeowners": ["@bollewolle"],
|
"codeowners": ["@bollewolle"],
|
||||||
"requirements": ["pydelijn==0.5.1"]
|
"requirements": ["pydelijn==0.6.0"]
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
from pydelijn.api import Passages
|
from pydelijn.api import Passages
|
||||||
|
from pydelijn.common import HttpException
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.components.sensor import PLATFORM_SCHEMA
|
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):
|
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
|
||||||
"""Create the sensor."""
|
"""Create the sensor."""
|
||||||
api_key = config[CONF_API_KEY]
|
api_key = config[CONF_API_KEY]
|
||||||
name = DEFAULT_NAME
|
|
||||||
|
|
||||||
session = async_get_clientsession(hass)
|
session = async_get_clientsession(hass)
|
||||||
|
|
||||||
sensors = []
|
sensors = []
|
||||||
for nextpassage in config[CONF_NEXT_DEPARTURE]:
|
for nextpassage in config[CONF_NEXT_DEPARTURE]:
|
||||||
stop_id = nextpassage[CONF_STOP_ID]
|
sensors.append(
|
||||||
number_of_departures = nextpassage[CONF_NUMBER_OF_DEPARTURES]
|
DeLijnPublicTransportSensor(
|
||||||
line = Passages(
|
Passages(
|
||||||
hass.loop, stop_id, number_of_departures, api_key, session, True
|
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)
|
async_add_entities(sensors, True)
|
||||||
|
|
||||||
@ -60,20 +62,28 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
|
|||||||
class DeLijnPublicTransportSensor(Entity):
|
class DeLijnPublicTransportSensor(Entity):
|
||||||
"""Representation of a Ruter sensor."""
|
"""Representation of a Ruter sensor."""
|
||||||
|
|
||||||
def __init__(self, line, name):
|
def __init__(self, line):
|
||||||
"""Initialize the sensor."""
|
"""Initialize the sensor."""
|
||||||
self.line = line
|
self.line = line
|
||||||
self._attributes = {ATTR_ATTRIBUTION: ATTRIBUTION}
|
self._attributes = {ATTR_ATTRIBUTION: ATTRIBUTION}
|
||||||
self._name = name
|
self._name = None
|
||||||
self._state = None
|
self._state = None
|
||||||
self._available = False
|
self._available = True
|
||||||
|
|
||||||
async def async_update(self):
|
async def async_update(self):
|
||||||
"""Get the latest data from the De Lijn API."""
|
"""Get the latest data from the De Lijn API."""
|
||||||
await self.line.get_passages()
|
try:
|
||||||
if self.line.passages is None:
|
await self.line.get_passages()
|
||||||
_LOGGER.warning("No data received from De Lijn")
|
self._name = await self.line.get_stopname()
|
||||||
|
except HttpException:
|
||||||
|
self._available = False
|
||||||
|
_LOGGER.error("De Lijn http error")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
self._attributes["stopname"] = self._name
|
||||||
|
for passage in self.line.passages:
|
||||||
|
passage["stopname"] = self._name
|
||||||
|
|
||||||
try:
|
try:
|
||||||
first = self.line.passages[0]
|
first = self.line.passages[0]
|
||||||
if first["due_at_realtime"] is not None:
|
if first["due_at_realtime"] is not None:
|
||||||
@ -81,8 +91,6 @@ class DeLijnPublicTransportSensor(Entity):
|
|||||||
else:
|
else:
|
||||||
first_passage = first["due_at_schedule"]
|
first_passage = first["due_at_schedule"]
|
||||||
self._state = first_passage
|
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_number_public"] = first["line_number_public"]
|
||||||
self._attributes["line_transport_type"] = first["line_transport_type"]
|
self._attributes["line_transport_type"] = first["line_transport_type"]
|
||||||
self._attributes["final_destination"] = first["final_destination"]
|
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["due_at_realtime"] = first["due_at_realtime"]
|
||||||
self._attributes["next_passages"] = self.line.passages
|
self._attributes["next_passages"] = self.line.passages
|
||||||
self._available = True
|
self._available = True
|
||||||
except (KeyError, IndexError) as error:
|
except (KeyError, IndexError):
|
||||||
_LOGGER.debug("Error getting data from De Lijn: %s", error)
|
_LOGGER.error("Invalid data received from De Lijn")
|
||||||
self._available = False
|
self._available = False
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"name": "forked-daapd",
|
"name": "forked-daapd",
|
||||||
"documentation": "https://www.home-assistant.io/integrations/forked-daapd",
|
"documentation": "https://www.home-assistant.io/integrations/forked-daapd",
|
||||||
"codeowners": ["@uvjustin"],
|
"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,
|
"config_flow": true,
|
||||||
"zeroconf": ["_daap._tcp.local."]
|
"zeroconf": ["_daap._tcp.local."]
|
||||||
}
|
}
|
||||||
|
@ -794,26 +794,29 @@ class ForkedDaapdUpdater:
|
|||||||
"queue" in update_types
|
"queue" in update_types
|
||||||
): # update queue, queue before player for async_play_media
|
): # update queue, queue before player for async_play_media
|
||||||
queue = await self._api.get_request("queue")
|
queue = await self._api.get_request("queue")
|
||||||
update_events["queue"] = asyncio.Event()
|
if queue:
|
||||||
async_dispatcher_send(
|
update_events["queue"] = asyncio.Event()
|
||||||
self.hass,
|
async_dispatcher_send(
|
||||||
SIGNAL_UPDATE_QUEUE.format(self._entry_id),
|
self.hass,
|
||||||
queue,
|
SIGNAL_UPDATE_QUEUE.format(self._entry_id),
|
||||||
update_events["queue"],
|
queue,
|
||||||
)
|
update_events["queue"],
|
||||||
|
)
|
||||||
# order of below don't matter
|
# order of below don't matter
|
||||||
if not {"outputs", "volume"}.isdisjoint(update_types): # update outputs
|
if not {"outputs", "volume"}.isdisjoint(update_types): # update outputs
|
||||||
outputs = (await self._api.get_request("outputs"))["outputs"]
|
outputs = await self._api.get_request("outputs")
|
||||||
update_events[
|
if outputs:
|
||||||
"outputs"
|
outputs = outputs["outputs"]
|
||||||
] = asyncio.Event() # only for master, zones should ignore
|
update_events[
|
||||||
async_dispatcher_send(
|
"outputs"
|
||||||
self.hass,
|
] = asyncio.Event() # only for master, zones should ignore
|
||||||
SIGNAL_UPDATE_OUTPUTS.format(self._entry_id),
|
async_dispatcher_send(
|
||||||
outputs,
|
self.hass,
|
||||||
update_events["outputs"],
|
SIGNAL_UPDATE_OUTPUTS.format(self._entry_id),
|
||||||
)
|
outputs,
|
||||||
self._add_zones(outputs)
|
update_events["outputs"],
|
||||||
|
)
|
||||||
|
self._add_zones(outputs)
|
||||||
if not {"database"}.isdisjoint(update_types):
|
if not {"database"}.isdisjoint(update_types):
|
||||||
pipes, playlists = await asyncio.gather(
|
pipes, playlists = await asyncio.gather(
|
||||||
self._api.get_pipes(), self._api.get_playlists()
|
self._api.get_pipes(), self._api.get_playlists()
|
||||||
@ -832,17 +835,18 @@ class ForkedDaapdUpdater:
|
|||||||
update_types
|
update_types
|
||||||
): # update player
|
): # update player
|
||||||
player = await self._api.get_request("player")
|
player = await self._api.get_request("player")
|
||||||
update_events["player"] = asyncio.Event()
|
if player:
|
||||||
if update_events.get("queue"):
|
update_events["player"] = asyncio.Event()
|
||||||
await update_events[
|
if update_events.get("queue"):
|
||||||
"queue"
|
await update_events[
|
||||||
].wait() # make sure queue done before player for async_play_media
|
"queue"
|
||||||
async_dispatcher_send(
|
].wait() # make sure queue done before player for async_play_media
|
||||||
self.hass,
|
async_dispatcher_send(
|
||||||
SIGNAL_UPDATE_PLAYER.format(self._entry_id),
|
self.hass,
|
||||||
player,
|
SIGNAL_UPDATE_PLAYER.format(self._entry_id),
|
||||||
update_events["player"],
|
player,
|
||||||
)
|
update_events["player"],
|
||||||
|
)
|
||||||
if update_events:
|
if update_events:
|
||||||
await asyncio.wait(
|
await asyncio.wait(
|
||||||
[event.wait() for event in update_events.values()]
|
[event.wait() for event in update_events.values()]
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
"domain": "frontend",
|
"domain": "frontend",
|
||||||
"name": "Home Assistant Frontend",
|
"name": "Home Assistant Frontend",
|
||||||
"documentation": "https://www.home-assistant.io/integrations/frontend",
|
"documentation": "https://www.home-assistant.io/integrations/frontend",
|
||||||
"requirements": ["home-assistant-frontend==20200519.0"],
|
"requirements": ["home-assistant-frontend==20200519.1"],
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
"api",
|
"api",
|
||||||
"auth",
|
"auth",
|
||||||
|
@ -627,12 +627,14 @@ class HomeKit:
|
|||||||
ent_cfg = self._config.setdefault(entity_id, {})
|
ent_cfg = self._config.setdefault(entity_id, {})
|
||||||
if ent_reg_ent.device_id:
|
if ent_reg_ent.device_id:
|
||||||
dev_reg_ent = dev_reg.async_get(ent_reg_ent.device_id)
|
dev_reg_ent = dev_reg.async_get(ent_reg_ent.device_id)
|
||||||
if dev_reg_ent.manufacturer:
|
if dev_reg_ent is not None:
|
||||||
ent_cfg[ATTR_MANUFACTURER] = dev_reg_ent.manufacturer
|
# Handle missing devices
|
||||||
if dev_reg_ent.model:
|
if dev_reg_ent.manufacturer:
|
||||||
ent_cfg[ATTR_MODEL] = dev_reg_ent.model
|
ent_cfg[ATTR_MANUFACTURER] = dev_reg_ent.manufacturer
|
||||||
if dev_reg_ent.sw_version:
|
if dev_reg_ent.model:
|
||||||
ent_cfg[ATTR_SOFTWARE_VERSION] = dev_reg_ent.sw_version
|
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:
|
if ATTR_MANUFACTURER not in ent_cfg:
|
||||||
integration = await async_get_integration(self.hass, ent_reg_ent.platform)
|
integration = await async_get_integration(self.hass, ent_reg_ent.platform)
|
||||||
ent_cfg[ATTR_INTERGRATION] = integration.name
|
ent_cfg[ATTR_INTERGRATION] = integration.name
|
||||||
|
@ -111,7 +111,7 @@ class HMLight(HMDevice, LightEntity):
|
|||||||
):
|
):
|
||||||
self._hmdevice.on(self._channel)
|
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(
|
self._hmdevice.set_hs_color(
|
||||||
hue=kwargs[ATTR_HS_COLOR][0] / 360.0,
|
hue=kwargs[ATTR_HS_COLOR][0] / 360.0,
|
||||||
saturation=kwargs[ATTR_HS_COLOR][1] / 100.0,
|
saturation=kwargs[ATTR_HS_COLOR][1] / 100.0,
|
||||||
|
@ -31,9 +31,15 @@ from .const import (
|
|||||||
DEVICE_REVISION,
|
DEVICE_REVISION,
|
||||||
DEVICE_SERIAL_NUMBER,
|
DEVICE_SERIAL_NUMBER,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
|
FIRMWARE_BUILD,
|
||||||
FIRMWARE_IN_USERDATA,
|
FIRMWARE_IN_USERDATA,
|
||||||
|
FIRMWARE_SUB_REVISION,
|
||||||
HUB_EXCEPTIONS,
|
HUB_EXCEPTIONS,
|
||||||
HUB_NAME,
|
HUB_NAME,
|
||||||
|
LEGACY_DEVICE_BUILD,
|
||||||
|
LEGACY_DEVICE_MODEL,
|
||||||
|
LEGACY_DEVICE_REVISION,
|
||||||
|
LEGACY_DEVICE_SUB_REVISION,
|
||||||
MAC_ADDRESS_IN_USERDATA,
|
MAC_ADDRESS_IN_USERDATA,
|
||||||
MAINPROCESSOR_IN_USERDATA_FIRMWARE,
|
MAINPROCESSOR_IN_USERDATA_FIRMWARE,
|
||||||
MODEL_IN_MAINPROCESSOR,
|
MODEL_IN_MAINPROCESSOR,
|
||||||
@ -159,9 +165,19 @@ async def async_get_device_info(pv_request):
|
|||||||
resources = await userdata.get_resources()
|
resources = await userdata.get_resources()
|
||||||
userdata_data = resources[USER_DATA]
|
userdata_data = resources[USER_DATA]
|
||||||
|
|
||||||
main_processor_info = userdata_data[FIRMWARE_IN_USERDATA][
|
if FIRMWARE_IN_USERDATA in userdata_data:
|
||||||
MAINPROCESSOR_IN_USERDATA_FIRMWARE
|
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 {
|
return {
|
||||||
DEVICE_NAME: base64_to_unicode(userdata_data[HUB_NAME]),
|
DEVICE_NAME: base64_to_unicode(userdata_data[HUB_NAME]),
|
||||||
DEVICE_MAC_ADDRESS: userdata_data[MAC_ADDRESS_IN_USERDATA],
|
DEVICE_MAC_ADDRESS: userdata_data[MAC_ADDRESS_IN_USERDATA],
|
||||||
|
@ -65,3 +65,8 @@ PV_ROOM_DATA = "pv_room_data"
|
|||||||
COORDINATOR = "coordinator"
|
COORDINATOR = "coordinator"
|
||||||
|
|
||||||
HUB_EXCEPTIONS = (asyncio.TimeoutError, PvApiConnectionError)
|
HUB_EXCEPTIONS = (asyncio.TimeoutError, PvApiConnectionError)
|
||||||
|
|
||||||
|
LEGACY_DEVICE_SUB_REVISION = 1
|
||||||
|
LEGACY_DEVICE_REVISION = 0
|
||||||
|
LEGACY_DEVICE_BUILD = 0
|
||||||
|
LEGACY_DEVICE_MODEL = "PV Hub1.0"
|
||||||
|
@ -3,6 +3,5 @@
|
|||||||
"name": "Logbook",
|
"name": "Logbook",
|
||||||
"documentation": "https://www.home-assistant.io/integrations/logbook",
|
"documentation": "https://www.home-assistant.io/integrations/logbook",
|
||||||
"dependencies": ["frontend", "http", "recorder"],
|
"dependencies": ["frontend", "http", "recorder"],
|
||||||
"after_dependencies": ["homekit"],
|
|
||||||
"codeowners": []
|
"codeowners": []
|
||||||
}
|
}
|
||||||
|
@ -106,11 +106,6 @@ class ONVIFCameraEntity(ONVIFBaseEntity, Camera):
|
|||||||
|
|
||||||
async def stream_source(self):
|
async def stream_source(self):
|
||||||
"""Return the stream source."""
|
"""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
|
return self._stream_uri
|
||||||
|
|
||||||
async def async_camera_image(self):
|
async def async_camera_image(self):
|
||||||
@ -118,11 +113,6 @@ class ONVIFCameraEntity(ONVIFBaseEntity, Camera):
|
|||||||
image = None
|
image = None
|
||||||
|
|
||||||
if self.device.capabilities.snapshot:
|
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
|
auth = None
|
||||||
if self.device.username and self.device.password:
|
if self.device.username and self.device.password:
|
||||||
auth = HTTPDigestAuth(self.device.username, self.device.password)
|
auth = HTTPDigestAuth(self.device.username, self.device.password)
|
||||||
@ -181,6 +171,16 @@ class ONVIFCameraEntity(ONVIFBaseEntity, Camera):
|
|||||||
finally:
|
finally:
|
||||||
await stream.close()
|
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(
|
async def async_perform_ptz(
|
||||||
self,
|
self,
|
||||||
distance,
|
distance,
|
||||||
|
@ -223,7 +223,7 @@ class ONVIFDevice:
|
|||||||
try:
|
try:
|
||||||
media_service = self.device.create_media_service()
|
media_service = self.device.create_media_service()
|
||||||
media_capabilities = await media_service.GetServiceCapabilities()
|
media_capabilities = await media_service.GetServiceCapabilities()
|
||||||
snapshot = media_capabilities.SnapshotUri
|
snapshot = media_capabilities and media_capabilities.SnapshotUri
|
||||||
except (ONVIFError, Fault):
|
except (ONVIFError, Fault):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@ -231,7 +231,7 @@ class ONVIFDevice:
|
|||||||
try:
|
try:
|
||||||
event_service = self.device.create_events_service()
|
event_service = self.device.create_events_service()
|
||||||
event_capabilities = await event_service.GetServiceCapabilities()
|
event_capabilities = await event_service.GetServiceCapabilities()
|
||||||
pullpoint = event_capabilities.WSPullPointSupport
|
pullpoint = event_capabilities and event_capabilities.WSPullPointSupport
|
||||||
except (ONVIFError, Fault):
|
except (ONVIFError, Fault):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
"""Constants used by Home Assistant components."""
|
"""Constants used by Home Assistant components."""
|
||||||
MAJOR_VERSION = 0
|
MAJOR_VERSION = 0
|
||||||
MINOR_VERSION = 110
|
MINOR_VERSION = 110
|
||||||
PATCH_VERSION = "0"
|
PATCH_VERSION = "1"
|
||||||
__short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
__short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
||||||
__version__ = f"{__short_version__}.{PATCH_VERSION}"
|
__version__ = f"{__short_version__}.{PATCH_VERSION}"
|
||||||
REQUIRED_PYTHON_VER = (3, 7, 0)
|
REQUIRED_PYTHON_VER = (3, 7, 0)
|
||||||
|
@ -200,14 +200,19 @@ class Store:
|
|||||||
|
|
||||||
async def _async_handle_write_data(self, *_args):
|
async def _async_handle_write_data(self, *_args):
|
||||||
"""Handle writing the config."""
|
"""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:
|
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:
|
try:
|
||||||
await self.hass.async_add_executor_job(
|
await self.hass.async_add_executor_job(
|
||||||
self._write_data, self.path, data
|
self._write_data, self.path, data
|
||||||
|
@ -12,7 +12,7 @@ cryptography==2.9.2
|
|||||||
defusedxml==0.6.0
|
defusedxml==0.6.0
|
||||||
distro==1.5.0
|
distro==1.5.0
|
||||||
hass-nabucasa==0.34.2
|
hass-nabucasa==0.34.2
|
||||||
home-assistant-frontend==20200519.0
|
home-assistant-frontend==20200519.1
|
||||||
importlib-metadata==1.6.0
|
importlib-metadata==1.6.0
|
||||||
jinja2>=2.11.1
|
jinja2>=2.11.1
|
||||||
netdisco==2.6.0
|
netdisco==2.6.0
|
||||||
|
@ -731,7 +731,7 @@ hole==0.5.1
|
|||||||
holidays==0.10.2
|
holidays==0.10.2
|
||||||
|
|
||||||
# homeassistant.components.frontend
|
# homeassistant.components.frontend
|
||||||
home-assistant-frontend==20200519.0
|
home-assistant-frontend==20200519.1
|
||||||
|
|
||||||
# homeassistant.components.zwave
|
# homeassistant.components.zwave
|
||||||
homeassistant-pyozw==0.1.10
|
homeassistant-pyozw==0.1.10
|
||||||
@ -1272,7 +1272,7 @@ pydanfossair==0.1.0
|
|||||||
pydeconz==70
|
pydeconz==70
|
||||||
|
|
||||||
# homeassistant.components.delijn
|
# homeassistant.components.delijn
|
||||||
pydelijn==0.5.1
|
pydelijn==0.6.0
|
||||||
|
|
||||||
# homeassistant.components.zwave
|
# homeassistant.components.zwave
|
||||||
pydispatcher==2.0.5
|
pydispatcher==2.0.5
|
||||||
@ -1332,7 +1332,7 @@ pyflunearyou==1.0.7
|
|||||||
pyfnip==0.2
|
pyfnip==0.2
|
||||||
|
|
||||||
# homeassistant.components.forked_daapd
|
# homeassistant.components.forked_daapd
|
||||||
pyforked-daapd==0.1.8
|
pyforked-daapd==0.1.9
|
||||||
|
|
||||||
# homeassistant.components.fritzbox
|
# homeassistant.components.fritzbox
|
||||||
pyfritzhome==0.4.2
|
pyfritzhome==0.4.2
|
||||||
|
@ -312,7 +312,7 @@ hole==0.5.1
|
|||||||
holidays==0.10.2
|
holidays==0.10.2
|
||||||
|
|
||||||
# homeassistant.components.frontend
|
# homeassistant.components.frontend
|
||||||
home-assistant-frontend==20200519.0
|
home-assistant-frontend==20200519.1
|
||||||
|
|
||||||
# homeassistant.components.zwave
|
# homeassistant.components.zwave
|
||||||
homeassistant-pyozw==0.1.10
|
homeassistant-pyozw==0.1.10
|
||||||
@ -554,7 +554,7 @@ pyflume==0.4.0
|
|||||||
pyflunearyou==1.0.7
|
pyflunearyou==1.0.7
|
||||||
|
|
||||||
# homeassistant.components.forked_daapd
|
# homeassistant.components.forked_daapd
|
||||||
pyforked-daapd==0.1.8
|
pyforked-daapd==0.1.9
|
||||||
|
|
||||||
# homeassistant.components.fritzbox
|
# homeassistant.components.fritzbox
|
||||||
pyfritzhome==0.4.2
|
pyfritzhome==0.4.2
|
||||||
|
@ -8,6 +8,9 @@ from homeassistant.components.forked_daapd.const import (
|
|||||||
CONF_TTS_PAUSE_TIME,
|
CONF_TTS_PAUSE_TIME,
|
||||||
CONF_TTS_VOLUME,
|
CONF_TTS_VOLUME,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
|
SIGNAL_UPDATE_OUTPUTS,
|
||||||
|
SIGNAL_UPDATE_PLAYER,
|
||||||
|
SIGNAL_UPDATE_QUEUE,
|
||||||
SOURCE_NAME_CLEAR,
|
SOURCE_NAME_CLEAR,
|
||||||
SOURCE_NAME_DEFAULT,
|
SOURCE_NAME_DEFAULT,
|
||||||
SUPPORTED_FEATURES,
|
SUPPORTED_FEATURES,
|
||||||
@ -63,7 +66,7 @@ from homeassistant.const import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
from tests.async_mock import patch
|
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_MASTER_ENTITY_NAME = "media_player.forked_daapd_server"
|
||||||
TEST_ZONE_ENTITY_NAMES = [
|
TEST_ZONE_ENTITY_NAMES = [
|
||||||
@ -369,6 +372,32 @@ def test_master_state(hass, mock_api_object):
|
|||||||
assert not state.attributes[ATTR_MEDIA_SHUFFLE]
|
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(
|
async def _service_call(
|
||||||
hass, entity_name, service, additional_service_data=None, blocking=True
|
hass, entity_name, service, additional_service_data=None, blocking=True
|
||||||
):
|
):
|
||||||
|
@ -913,3 +913,83 @@ def _write_data(path: str, data: Dict) -> None:
|
|||||||
if not os.path.isdir(os.path.dirname(path)):
|
if not os.path.isdir(os.path.dirname(path)):
|
||||||
os.makedirs(os.path.dirname(path))
|
os.makedirs(os.path.dirname(path))
|
||||||
json_util.save_json(path, data)
|
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",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
34
tests/fixtures/hunterdouglas_powerview/userdata_v1.json
vendored
Normal file
34
tests/fixtures/hunterdouglas_powerview/userdata_v1.json
vendored
Normal 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
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user