mirror of
https://github.com/home-assistant/core.git
synced 2025-07-24 05:37:44 +00:00
Merge pull request #36238 from home-assistant/rc
This commit is contained in:
commit
7194b74580
@ -375,6 +375,11 @@ class AirVisualEntity(Entity):
|
||||
"""Return the icon."""
|
||||
return self._icon
|
||||
|
||||
@property
|
||||
def should_poll(self) -> bool:
|
||||
"""Disable polling."""
|
||||
return False
|
||||
|
||||
@property
|
||||
def unit_of_measurement(self):
|
||||
"""Return the unit the value is expressed in."""
|
||||
|
@ -452,6 +452,11 @@ class ADBDevice(MediaPlayerEntity):
|
||||
"""Provide the last ADB command's response as an attribute."""
|
||||
return {"adb_response": self._adb_response}
|
||||
|
||||
@property
|
||||
def media_image_hash(self):
|
||||
"""Hash value for media image."""
|
||||
return f"{datetime.now().timestamp()}" if self._screencap else None
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the device name."""
|
||||
@ -497,11 +502,6 @@ class ADBDevice(MediaPlayerEntity):
|
||||
"""Raw image data."""
|
||||
return self.aftv.adb_screencap()
|
||||
|
||||
@property
|
||||
def media_image_hash(self):
|
||||
"""Hash value for media image."""
|
||||
return f"{datetime.now().timestamp()}"
|
||||
|
||||
@adb_decorator()
|
||||
def media_play(self):
|
||||
"""Send play command."""
|
||||
|
@ -85,9 +85,10 @@ def setup_internal_discovery(hass: HomeAssistant) -> None:
|
||||
|
||||
_LOGGER.debug("Starting internal pychromecast discovery.")
|
||||
listener, browser = pychromecast.start_discovery(
|
||||
internal_add_callback, internal_remove_callback
|
||||
internal_add_callback,
|
||||
internal_remove_callback,
|
||||
ChromeCastZeroconf.get_zeroconf(),
|
||||
)
|
||||
ChromeCastZeroconf.set_zeroconf(browser.zc)
|
||||
|
||||
def stop_discovery(event):
|
||||
"""Stop discovery of new chromecasts."""
|
||||
|
@ -3,8 +3,8 @@
|
||||
"name": "Google Cast",
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/cast",
|
||||
"requirements": ["pychromecast==5.1.0"],
|
||||
"after_dependencies": ["cloud"],
|
||||
"requirements": ["pychromecast==5.3.0"],
|
||||
"after_dependencies": ["cloud","zeroconf"],
|
||||
"zeroconf": ["_googlecast._tcp.local."],
|
||||
"codeowners": ["@emontnemery"]
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ from pychromecast.socket_client import (
|
||||
)
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components import zeroconf
|
||||
from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerEntity
|
||||
from homeassistant.components.media_player.const import (
|
||||
MEDIA_TYPE_MOVIE,
|
||||
@ -170,6 +171,7 @@ async def _async_setup_platform(
|
||||
for chromecast in list(hass.data[KNOWN_CHROMECAST_INFO_KEY]):
|
||||
async_cast_discovered(chromecast)
|
||||
|
||||
ChromeCastZeroconf.set_zeroconf(await zeroconf.async_get_instance(hass))
|
||||
hass.async_add_executor_job(setup_internal_discovery, hass)
|
||||
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
"domain": "devolo_home_control",
|
||||
"name": "devolo_home_control",
|
||||
"documentation": "https://www.home-assistant.io/integrations/devolo_home_control",
|
||||
"requirements": ["devolo-home-control-api==0.10.0"],
|
||||
"requirements": ["devolo-home-control-api==0.11.0"],
|
||||
"config_flow": true,
|
||||
"codeowners": [
|
||||
"@2Fake",
|
||||
|
@ -17,10 +17,6 @@ from homeassistant.const import (
|
||||
)
|
||||
from homeassistant.core import CoreState, callback
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.dispatcher import (
|
||||
async_dispatcher_connect,
|
||||
async_dispatcher_send,
|
||||
)
|
||||
from homeassistant.helpers.entity import Entity
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
@ -113,7 +109,11 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
|
||||
|
||||
async_add_entities(devices)
|
||||
|
||||
update_entities_telegram = partial(async_dispatcher_send, hass, DOMAIN)
|
||||
def update_entities_telegram(telegram):
|
||||
"""Update entities with latest telegram and trigger state update."""
|
||||
# Make all device entities aware of new telegram
|
||||
for device in devices:
|
||||
device.update_data(telegram)
|
||||
|
||||
# Creates an asyncio.Protocol factory for reading DSMR telegrams from
|
||||
# serial and calls update_entities_telegram to update entities on arrival
|
||||
@ -187,17 +187,12 @@ class DSMREntity(Entity):
|
||||
self._config = config
|
||||
self.telegram = {}
|
||||
|
||||
async def async_added_to_hass(self):
|
||||
"""When entity is added to hass."""
|
||||
self.async_on_remove(
|
||||
async_dispatcher_connect(self.hass, DOMAIN, self.update_data)
|
||||
)
|
||||
|
||||
@callback
|
||||
def update_data(self, telegram):
|
||||
"""Update data."""
|
||||
self.telegram = telegram
|
||||
self.async_write_ha_state()
|
||||
if self.hass:
|
||||
self.async_write_ha_state()
|
||||
|
||||
def get_dsmr_object_attr(self, attribute):
|
||||
"""Read attribute from last received telegram for this DSMR object."""
|
||||
|
@ -61,6 +61,10 @@ async def async_setup(hass: HomeAssistantType, config: ConfigType):
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry):
|
||||
"""Initialize config entry which represents the HEOS controller."""
|
||||
# For backwards compat
|
||||
if entry.unique_id is None:
|
||||
hass.config_entries.async_update_entry(entry, unique_id=DOMAIN)
|
||||
|
||||
host = entry.data[CONF_HOST]
|
||||
# Setting all_progress_events=False ensures that we only receive a
|
||||
# media position update upon start of playback or when media changes
|
||||
|
@ -33,12 +33,16 @@ class HeosFlowHandler(config_entries.ConfigFlow):
|
||||
# Abort if other flows in progress or an entry already exists
|
||||
if self._async_in_progress() or self._async_current_entries():
|
||||
return self.async_abort(reason="already_setup")
|
||||
await self.async_set_unique_id(DOMAIN)
|
||||
# Show selection form
|
||||
return self.async_show_form(step_id="user")
|
||||
|
||||
async def async_step_import(self, user_input=None):
|
||||
"""Occurs when an entry is setup through config."""
|
||||
host = user_input[CONF_HOST]
|
||||
# raise_on_progress is False here in case ssdp discovers
|
||||
# heos first which would block the import
|
||||
await self.async_set_unique_id(DOMAIN, raise_on_progress=False)
|
||||
return self.async_create_entry(title=format_title(host), data={CONF_HOST: host})
|
||||
|
||||
async def async_step_user(self, user_input=None):
|
||||
|
@ -29,11 +29,7 @@ from homeassistant.const import (
|
||||
EVENT_HOMEASSISTANT_STOP,
|
||||
)
|
||||
from homeassistant.core import Event, ServiceCall, callback
|
||||
from homeassistant.exceptions import (
|
||||
ConfigEntryNotReady,
|
||||
HomeAssistantError,
|
||||
Unauthorized,
|
||||
)
|
||||
from homeassistant.exceptions import HomeAssistantError, Unauthorized
|
||||
from homeassistant.helpers import config_validation as cv, event, template
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.helpers.entity import Entity
|
||||
@ -649,13 +645,7 @@ async def async_setup_entry(hass, entry):
|
||||
tls_version=tls_version,
|
||||
)
|
||||
|
||||
result: str = await hass.data[DATA_MQTT].async_connect()
|
||||
|
||||
if result == CONNECTION_FAILED:
|
||||
return False
|
||||
|
||||
if result == CONNECTION_FAILED_RECOVERABLE:
|
||||
raise ConfigEntryNotReady
|
||||
await hass.data[DATA_MQTT].async_connect()
|
||||
|
||||
async def async_stop_mqtt(_event: Event):
|
||||
"""Stop MQTT component."""
|
||||
@ -835,15 +825,14 @@ class MQTT:
|
||||
self._mqttc.connect, self.broker, self.port, self.keepalive
|
||||
)
|
||||
except OSError as err:
|
||||
_LOGGER.error("Failed to connect due to exception: %s", err)
|
||||
return CONNECTION_FAILED_RECOVERABLE
|
||||
_LOGGER.error("Failed to connect to MQTT server due to exception: %s", err)
|
||||
|
||||
if result != 0:
|
||||
_LOGGER.error("Failed to connect: %s", mqtt.error_string(result))
|
||||
return CONNECTION_FAILED
|
||||
if result is not None and result != 0:
|
||||
_LOGGER.error(
|
||||
"Failed to connect to MQTT server: %s", mqtt.error_string(result)
|
||||
)
|
||||
|
||||
self._mqttc.loop_start()
|
||||
return CONNECTION_SUCCESS
|
||||
|
||||
async def async_disconnect(self):
|
||||
"""Stop the MQTT client."""
|
||||
@ -898,6 +887,7 @@ class MQTT:
|
||||
|
||||
This method is a coroutine.
|
||||
"""
|
||||
_LOGGER.debug("Unsubscribing from %s", topic)
|
||||
async with self._paho_lock:
|
||||
result: int = None
|
||||
result, _ = await self.hass.async_add_executor_job(
|
||||
@ -933,6 +923,7 @@ class MQTT:
|
||||
return
|
||||
|
||||
self.connected = True
|
||||
_LOGGER.info("Connected to MQTT server (%s)", result_code)
|
||||
|
||||
# Group subscriptions to only re-subscribe once for each topic.
|
||||
keyfunc = attrgetter("topic")
|
||||
@ -999,7 +990,7 @@ class MQTT:
|
||||
def _mqtt_on_disconnect(self, _mqttc, _userdata, result_code: int) -> None:
|
||||
"""Disconnected callback."""
|
||||
self.connected = False
|
||||
_LOGGER.warning("Disconnected from MQTT (%s).", result_code)
|
||||
_LOGGER.warning("Disconnected from MQTT server (%s)", result_code)
|
||||
|
||||
|
||||
def _raise_on_error(result_code: int) -> None:
|
||||
|
@ -530,7 +530,7 @@ class MqttCover(
|
||||
|
||||
async def async_set_cover_tilt_position(self, **kwargs):
|
||||
"""Move the cover tilt to a specific position."""
|
||||
position = float(kwargs[ATTR_TILT_POSITION])
|
||||
position = kwargs[ATTR_TILT_POSITION]
|
||||
|
||||
# The position needs to be between min and max
|
||||
level = self.find_in_range_from_percent(position)
|
||||
@ -550,10 +550,7 @@ class MqttCover(
|
||||
percentage_position = position
|
||||
if set_position_template is not None:
|
||||
position = set_position_template.async_render(**kwargs)
|
||||
elif (
|
||||
self._config[CONF_POSITION_OPEN] != 100
|
||||
and self._config[CONF_POSITION_CLOSED] != 0
|
||||
):
|
||||
else:
|
||||
position = self.find_in_range_from_percent(position, COVER_PAYLOAD)
|
||||
|
||||
mqtt.async_publish(
|
||||
|
@ -138,13 +138,17 @@ class Trigger:
|
||||
self.remove_signal = remove_signal
|
||||
self.type = config[CONF_TYPE]
|
||||
self.subtype = config[CONF_SUBTYPE]
|
||||
self.topic = config[CONF_TOPIC]
|
||||
self.payload = config[CONF_PAYLOAD]
|
||||
self.qos = config[CONF_QOS]
|
||||
topic_changed = self.topic != config[CONF_TOPIC]
|
||||
self.topic = config[CONF_TOPIC]
|
||||
|
||||
# Unsubscribe+subscribe if this trigger is in use
|
||||
for trig in self.trigger_instances:
|
||||
await trig.async_attach_trigger()
|
||||
# Unsubscribe+subscribe if this trigger is in use and topic has changed
|
||||
# If topic is same unsubscribe+subscribe will execute in the wrong order
|
||||
# because unsubscribe is done with help of async_create_task
|
||||
if topic_changed:
|
||||
for trig in self.trigger_instances:
|
||||
await trig.async_attach_trigger()
|
||||
|
||||
def detach_trigger(self):
|
||||
"""Remove MQTT device trigger."""
|
||||
|
@ -297,7 +297,7 @@ class ONVIFDevice:
|
||||
try:
|
||||
ptz_service = self.device.create_ptz_service()
|
||||
presets = await ptz_service.GetPresets(profile.token)
|
||||
profile.ptz.presets = [preset.token for preset in presets]
|
||||
profile.ptz.presets = [preset.token for preset in presets if preset]
|
||||
except (Fault, ServerDisconnectedError):
|
||||
# It's OK if Presets aren't supported
|
||||
profile.ptz.presets = []
|
||||
|
@ -222,7 +222,12 @@ class RokuMediaPlayer(RokuEntity, MediaPlayerEntity):
|
||||
await self.coordinator.roku.remote("home")
|
||||
|
||||
appl = next(
|
||||
(app for app in self.coordinator.data.apps if app.name == source), None
|
||||
(
|
||||
app
|
||||
for app in self.coordinator.data.apps
|
||||
if source in (app.name, app.app_id)
|
||||
),
|
||||
None,
|
||||
)
|
||||
|
||||
if appl is not None:
|
||||
|
@ -500,6 +500,8 @@ class TodoistProjectData:
|
||||
|
||||
events = []
|
||||
for task in project_task_data:
|
||||
if task["due"] is None:
|
||||
continue
|
||||
due_date = _parse_due_date(task["due"])
|
||||
if start_date < due_date < end_date:
|
||||
event = {
|
||||
|
@ -97,7 +97,7 @@ class HaServiceBrowser(ServiceBrowser):
|
||||
# To avoid overwhemling the system we pre-filter here and only process
|
||||
# DNSPointers for the configured record name (type)
|
||||
#
|
||||
if record.name != self.type or not isinstance(record, DNSPointer):
|
||||
if record.name not in self.types or not isinstance(record, DNSPointer):
|
||||
return
|
||||
super().update_record(zc, now, record)
|
||||
|
||||
@ -181,6 +181,7 @@ def setup(hass, config):
|
||||
if not service_info:
|
||||
# Prevent the browser thread from collapsing as
|
||||
# service_info can be None
|
||||
_LOGGER.debug("Failed to get info for device %s", name)
|
||||
return
|
||||
|
||||
info = info_from_service(service_info)
|
||||
@ -216,11 +217,12 @@ def setup(hass, config):
|
||||
)
|
||||
)
|
||||
|
||||
for service in ZEROCONF:
|
||||
HaServiceBrowser(zeroconf, service, handlers=[service_update])
|
||||
types = list(ZEROCONF)
|
||||
|
||||
if HOMEKIT_TYPE not in ZEROCONF:
|
||||
HaServiceBrowser(zeroconf, HOMEKIT_TYPE, handlers=[service_update])
|
||||
types.append(HOMEKIT_TYPE)
|
||||
|
||||
HaServiceBrowser(zeroconf, types, handlers=[service_update])
|
||||
|
||||
return True
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
"domain": "zeroconf",
|
||||
"name": "Zero-configuration networking (zeroconf)",
|
||||
"documentation": "https://www.home-assistant.io/integrations/zeroconf",
|
||||
"requirements": ["zeroconf==0.26.1"],
|
||||
"requirements": ["zeroconf==0.26.3"],
|
||||
"dependencies": ["api"],
|
||||
"codeowners": ["@robbiet480", "@Kane610"],
|
||||
"quality_scale": "internal"
|
||||
|
@ -1,7 +1,7 @@
|
||||
"""Constants used by Home Assistant components."""
|
||||
MAJOR_VERSION = 0
|
||||
MINOR_VERSION = 110
|
||||
PATCH_VERSION = "3"
|
||||
PATCH_VERSION = "4"
|
||||
__short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
||||
__version__ = f"{__short_version__}.{PATCH_VERSION}"
|
||||
REQUIRED_PYTHON_VER = (3, 7, 0)
|
||||
|
@ -25,7 +25,7 @@ ruamel.yaml==0.15.100
|
||||
sqlalchemy==1.3.16
|
||||
voluptuous-serialize==2.3.0
|
||||
voluptuous==0.11.7
|
||||
zeroconf==0.26.1
|
||||
zeroconf==0.26.3
|
||||
|
||||
pycryptodome>=3.6.6
|
||||
|
||||
|
@ -469,7 +469,7 @@ deluge-client==1.7.1
|
||||
denonavr==0.8.1
|
||||
|
||||
# homeassistant.components.devolo_home_control
|
||||
devolo-home-control-api==0.10.0
|
||||
devolo-home-control-api==0.11.0
|
||||
|
||||
# homeassistant.components.directv
|
||||
directv==0.3.0
|
||||
@ -1245,7 +1245,7 @@ pycfdns==0.0.1
|
||||
pychannels==1.0.0
|
||||
|
||||
# homeassistant.components.cast
|
||||
pychromecast==5.1.0
|
||||
pychromecast==5.3.0
|
||||
|
||||
# homeassistant.components.cmus
|
||||
pycmus==0.1.1
|
||||
@ -2239,7 +2239,7 @@ youtube_dl==2020.05.08
|
||||
zengge==0.2
|
||||
|
||||
# homeassistant.components.zeroconf
|
||||
zeroconf==0.26.1
|
||||
zeroconf==0.26.3
|
||||
|
||||
# homeassistant.components.zha
|
||||
zha-quirks==0.0.39
|
||||
|
@ -203,7 +203,7 @@ defusedxml==0.6.0
|
||||
denonavr==0.8.1
|
||||
|
||||
# homeassistant.components.devolo_home_control
|
||||
devolo-home-control-api==0.10.0
|
||||
devolo-home-control-api==0.11.0
|
||||
|
||||
# homeassistant.components.directv
|
||||
directv==0.3.0
|
||||
@ -527,7 +527,7 @@ pyblackbird==0.5
|
||||
pybotvac==0.0.17
|
||||
|
||||
# homeassistant.components.cast
|
||||
pychromecast==5.1.0
|
||||
pychromecast==5.3.0
|
||||
|
||||
# homeassistant.components.coolmaster
|
||||
pycoolmasternet==0.0.4
|
||||
@ -900,7 +900,7 @@ xmltodict==0.12.0
|
||||
ya_ma==0.3.8
|
||||
|
||||
# homeassistant.components.zeroconf
|
||||
zeroconf==0.26.1
|
||||
zeroconf==0.26.3
|
||||
|
||||
# homeassistant.components.zha
|
||||
zha-quirks==0.0.39
|
||||
|
@ -6,7 +6,8 @@ from pyheos import HeosError
|
||||
from homeassistant import data_entry_flow
|
||||
from homeassistant.components import heos, ssdp
|
||||
from homeassistant.components.heos.config_flow import HeosFlowHandler
|
||||
from homeassistant.components.heos.const import DATA_DISCOVERED_HOSTS
|
||||
from homeassistant.components.heos.const import DATA_DISCOVERED_HOSTS, DOMAIN
|
||||
from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_SSDP
|
||||
from homeassistant.const import CONF_HOST
|
||||
|
||||
from tests.async_mock import patch
|
||||
@ -55,6 +56,7 @@ async def test_create_entry_when_host_valid(hass, controller):
|
||||
heos.DOMAIN, context={"source": "user"}, data=data
|
||||
)
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
||||
assert result["result"].unique_id == DOMAIN
|
||||
assert result["title"] == "Controller (127.0.0.1)"
|
||||
assert result["data"] == data
|
||||
assert controller.connect.call_count == 1
|
||||
@ -70,6 +72,7 @@ async def test_create_entry_when_friendly_name_valid(hass, controller):
|
||||
heos.DOMAIN, context={"source": "user"}, data=data
|
||||
)
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
||||
assert result["result"].unique_id == DOMAIN
|
||||
assert result["title"] == "Controller (127.0.0.1)"
|
||||
assert result["data"] == {CONF_HOST: "127.0.0.1"}
|
||||
assert controller.connect.call_count == 1
|
||||
@ -79,28 +82,34 @@ async def test_create_entry_when_friendly_name_valid(hass, controller):
|
||||
|
||||
async def test_discovery_shows_create_form(hass, controller, discovery_data):
|
||||
"""Test discovery shows form to confirm setup and subsequent abort."""
|
||||
|
||||
await hass.config_entries.flow.async_init(
|
||||
heos.DOMAIN, context={"source": "ssdp"}, data=discovery_data
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert len(hass.config_entries.flow.async_progress()) == 1
|
||||
flows_in_progress = hass.config_entries.flow.async_progress()
|
||||
assert flows_in_progress[0]["context"]["unique_id"] == DOMAIN
|
||||
assert len(flows_in_progress) == 1
|
||||
assert hass.data[DATA_DISCOVERED_HOSTS] == {"Office (127.0.0.1)": "127.0.0.1"}
|
||||
|
||||
port = urlparse(discovery_data[ssdp.ATTR_SSDP_LOCATION]).port
|
||||
discovery_data[ssdp.ATTR_SSDP_LOCATION] = f"http://127.0.0.2:{port}/"
|
||||
discovery_data[ssdp.ATTR_UPNP_FRIENDLY_NAME] = "Bedroom"
|
||||
|
||||
await hass.config_entries.flow.async_init(
|
||||
heos.DOMAIN, context={"source": "ssdp"}, data=discovery_data
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert len(hass.config_entries.flow.async_progress()) == 1
|
||||
flows_in_progress = hass.config_entries.flow.async_progress()
|
||||
assert flows_in_progress[0]["context"]["unique_id"] == DOMAIN
|
||||
assert len(flows_in_progress) == 1
|
||||
assert hass.data[DATA_DISCOVERED_HOSTS] == {
|
||||
"Office (127.0.0.1)": "127.0.0.1",
|
||||
"Bedroom (127.0.0.2)": "127.0.0.2",
|
||||
}
|
||||
|
||||
|
||||
async def test_disovery_flow_aborts_already_setup(
|
||||
async def test_discovery_flow_aborts_already_setup(
|
||||
hass, controller, discovery_data, config_entry
|
||||
):
|
||||
"""Test discovery flow aborts when entry already setup."""
|
||||
@ -110,3 +119,34 @@ async def test_disovery_flow_aborts_already_setup(
|
||||
result = await flow.async_step_ssdp(discovery_data)
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT
|
||||
assert result["reason"] == "already_setup"
|
||||
|
||||
|
||||
async def test_discovery_sets_the_unique_id(hass, controller, discovery_data):
|
||||
"""Test discovery sets the unique id."""
|
||||
|
||||
port = urlparse(discovery_data[ssdp.ATTR_SSDP_LOCATION]).port
|
||||
discovery_data[ssdp.ATTR_SSDP_LOCATION] = f"http://127.0.0.2:{port}/"
|
||||
discovery_data[ssdp.ATTR_UPNP_FRIENDLY_NAME] = "Bedroom"
|
||||
|
||||
await hass.config_entries.flow.async_init(
|
||||
heos.DOMAIN, context={"source": SOURCE_SSDP}, data=discovery_data
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
flows_in_progress = hass.config_entries.flow.async_progress()
|
||||
assert flows_in_progress[0]["context"]["unique_id"] == DOMAIN
|
||||
assert len(flows_in_progress) == 1
|
||||
assert hass.data[DATA_DISCOVERED_HOSTS] == {"Bedroom (127.0.0.2)": "127.0.0.2"}
|
||||
|
||||
|
||||
async def test_import_sets_the_unique_id(hass, controller):
|
||||
"""Test import sets the unique id."""
|
||||
|
||||
with patch("homeassistant.components.heos.async_setup_entry", return_value=True):
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
heos.DOMAIN,
|
||||
context={"source": SOURCE_IMPORT},
|
||||
data={CONF_HOST: "127.0.0.2"},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
||||
assert result["result"].unique_id == DOMAIN
|
||||
|
@ -31,6 +31,7 @@ async def test_async_setup_creates_entry(hass, config):
|
||||
entry = entries[0]
|
||||
assert entry.title == "Controller (127.0.0.1)"
|
||||
assert entry.data == {CONF_HOST: "127.0.0.1"}
|
||||
assert entry.unique_id == DOMAIN
|
||||
|
||||
|
||||
async def test_async_setup_updates_entry(hass, config_entry, config, controller):
|
||||
@ -44,6 +45,7 @@ async def test_async_setup_updates_entry(hass, config_entry, config, controller)
|
||||
entry = entries[0]
|
||||
assert entry.title == "Controller (127.0.0.2)"
|
||||
assert entry.data == {CONF_HOST: "127.0.0.2"}
|
||||
assert entry.unique_id == DOMAIN
|
||||
|
||||
|
||||
async def test_async_setup_returns_true(hass, config_entry, config):
|
||||
|
@ -14,6 +14,7 @@ from tests.common import (
|
||||
assert_lists_same,
|
||||
async_fire_mqtt_message,
|
||||
async_get_device_automations,
|
||||
async_mock_mqtt_component,
|
||||
async_mock_service,
|
||||
mock_device_registry,
|
||||
mock_registry,
|
||||
@ -456,7 +457,7 @@ async def test_if_fires_on_mqtt_message_after_update(
|
||||
await hass.async_block_till_done()
|
||||
assert len(calls) == 1
|
||||
|
||||
# Update the trigger
|
||||
# Update the trigger with different topic
|
||||
async_fire_mqtt_message(hass, "homeassistant/device_automation/bla1/config", data2)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
@ -468,6 +469,65 @@ async def test_if_fires_on_mqtt_message_after_update(
|
||||
await hass.async_block_till_done()
|
||||
assert len(calls) == 2
|
||||
|
||||
# Update the trigger with same topic
|
||||
async_fire_mqtt_message(hass, "homeassistant/device_automation/bla1/config", data2)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
async_fire_mqtt_message(hass, "foobar/triggers/button1", "")
|
||||
await hass.async_block_till_done()
|
||||
assert len(calls) == 2
|
||||
|
||||
async_fire_mqtt_message(hass, "foobar/triggers/buttonOne", "")
|
||||
await hass.async_block_till_done()
|
||||
assert len(calls) == 3
|
||||
|
||||
|
||||
async def test_no_resubscribe_same_topic(hass, device_reg, mqtt_mock):
|
||||
"""Test subscription to topics without change."""
|
||||
mock_mqtt = await async_mock_mqtt_component(hass)
|
||||
config_entry = MockConfigEntry(domain=DOMAIN, data={})
|
||||
config_entry.add_to_hass(hass)
|
||||
await async_start(hass, "homeassistant", {}, config_entry)
|
||||
|
||||
data1 = (
|
||||
'{ "automation_type":"trigger",'
|
||||
' "device":{"identifiers":["0AFFD2"]},'
|
||||
' "topic": "foobar/triggers/button1",'
|
||||
' "type": "button_short_press",'
|
||||
' "subtype": "button_1" }'
|
||||
)
|
||||
async_fire_mqtt_message(hass, "homeassistant/device_automation/bla1/config", data1)
|
||||
await hass.async_block_till_done()
|
||||
device_entry = device_reg.async_get_device({("mqtt", "0AFFD2")}, set())
|
||||
|
||||
assert await async_setup_component(
|
||||
hass,
|
||||
automation.DOMAIN,
|
||||
{
|
||||
automation.DOMAIN: [
|
||||
{
|
||||
"trigger": {
|
||||
"platform": "device",
|
||||
"domain": DOMAIN,
|
||||
"device_id": device_entry.id,
|
||||
"discovery_id": "bla1",
|
||||
"type": "button_short_press",
|
||||
"subtype": "button_1",
|
||||
},
|
||||
"action": {
|
||||
"service": "test.automation",
|
||||
"data_template": {"some": ("short_press")},
|
||||
},
|
||||
},
|
||||
]
|
||||
},
|
||||
)
|
||||
|
||||
call_count = mock_mqtt.async_subscribe.call_count
|
||||
async_fire_mqtt_message(hass, "homeassistant/device_automation/bla1/config", data1)
|
||||
await hass.async_block_till_done()
|
||||
assert mock_mqtt.async_subscribe.call_count == call_count
|
||||
|
||||
|
||||
async def test_not_fires_on_mqtt_message_after_remove_by_mqtt(
|
||||
hass, device_reg, calls, mqtt_mock
|
||||
|
@ -18,7 +18,6 @@ from homeassistant.const import (
|
||||
TEMP_CELSIUS,
|
||||
)
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.exceptions import ConfigEntryNotReady
|
||||
from homeassistant.helpers import device_registry
|
||||
from homeassistant.setup import async_setup_component
|
||||
from homeassistant.util.dt import utcnow
|
||||
@ -678,23 +677,24 @@ async def test_setup_embedded_with_embedded(hass):
|
||||
assert _start.call_count == 1
|
||||
|
||||
|
||||
async def test_setup_fails_if_no_connect_broker(hass):
|
||||
async def test_setup_logs_error_if_no_connect_broker(hass, caplog):
|
||||
"""Test for setup failure if connection to broker is missing."""
|
||||
entry = MockConfigEntry(domain=mqtt.DOMAIN, data={mqtt.CONF_BROKER: "test-broker"})
|
||||
|
||||
with patch("paho.mqtt.client.Client") as mock_client:
|
||||
mock_client().connect = lambda *args: 1
|
||||
assert not await mqtt.async_setup_entry(hass, entry)
|
||||
assert await mqtt.async_setup_entry(hass, entry)
|
||||
assert "Failed to connect to MQTT server:" in caplog.text
|
||||
|
||||
|
||||
async def test_setup_raises_ConfigEntryNotReady_if_no_connect_broker(hass):
|
||||
async def test_setup_raises_ConfigEntryNotReady_if_no_connect_broker(hass, caplog):
|
||||
"""Test for setup failure if connection to broker is missing."""
|
||||
entry = MockConfigEntry(domain=mqtt.DOMAIN, data={mqtt.CONF_BROKER: "test-broker"})
|
||||
|
||||
with patch("paho.mqtt.client.Client") as mock_client:
|
||||
mock_client().connect = MagicMock(side_effect=OSError("Connection error"))
|
||||
with pytest.raises(ConfigEntryNotReady):
|
||||
await mqtt.async_setup_entry(hass, entry)
|
||||
assert await mqtt.async_setup_entry(hass, entry)
|
||||
assert "Failed to connect to MQTT server due to exception:" in caplog.text
|
||||
|
||||
|
||||
async def test_setup_uses_certificate_on_certificate_set_to_auto(hass, mock_mqtt):
|
||||
|
@ -332,7 +332,7 @@ async def test_services(
|
||||
|
||||
remote_mock.assert_called_once_with("home")
|
||||
|
||||
with patch("homeassistant.components.roku.Roku.launch") as remote_mock:
|
||||
with patch("homeassistant.components.roku.Roku.launch") as launch_mock:
|
||||
await hass.services.async_call(
|
||||
MP_DOMAIN,
|
||||
SERVICE_SELECT_SOURCE,
|
||||
@ -340,7 +340,17 @@ async def test_services(
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
remote_mock.assert_called_once_with("12")
|
||||
launch_mock.assert_called_once_with("12")
|
||||
|
||||
with patch("homeassistant.components.roku.Roku.launch") as launch_mock:
|
||||
await hass.services.async_call(
|
||||
MP_DOMAIN,
|
||||
SERVICE_SELECT_SOURCE,
|
||||
{ATTR_ENTITY_ID: MAIN_ENTITY_ID, ATTR_INPUT_SOURCE: 12},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
launch_mock.assert_called_once_with("12")
|
||||
|
||||
|
||||
async def test_tv_services(
|
||||
|
@ -29,9 +29,10 @@ def mock_zeroconf():
|
||||
yield mock_zc.return_value
|
||||
|
||||
|
||||
def service_update_mock(zeroconf, service, handlers):
|
||||
def service_update_mock(zeroconf, services, handlers):
|
||||
"""Call service update handler."""
|
||||
handlers[0](zeroconf, service, f"name.{service}", ServiceStateChange.Added)
|
||||
for service in services:
|
||||
handlers[0](zeroconf, service, f"name.{service}", ServiceStateChange.Added)
|
||||
|
||||
|
||||
def get_service_info_mock(service_type, name):
|
||||
@ -76,7 +77,7 @@ async def test_setup(hass, mock_zeroconf):
|
||||
mock_zeroconf.get_service_info.side_effect = get_service_info_mock
|
||||
assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}})
|
||||
|
||||
assert len(mock_service_browser.mock_calls) == len(zc_gen.ZEROCONF)
|
||||
assert len(mock_service_browser.mock_calls) == 1
|
||||
expected_flow_calls = 0
|
||||
for matching_components in zc_gen.ZEROCONF.values():
|
||||
expected_flow_calls += len(matching_components)
|
||||
|
Loading…
x
Reference in New Issue
Block a user