Merge pull request #73504 from home-assistant/rc

This commit is contained in:
Paulus Schoutsen 2022-06-14 14:49:29 -07:00 committed by GitHub
commit c73eca5923
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 525 additions and 187 deletions

View File

@ -3,7 +3,7 @@
"name": "Bond", "name": "Bond",
"config_flow": true, "config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/bond", "documentation": "https://www.home-assistant.io/integrations/bond",
"requirements": ["bond-async==0.1.20"], "requirements": ["bond-async==0.1.22"],
"zeroconf": ["_bond._tcp.local."], "zeroconf": ["_bond._tcp.local."],
"codeowners": ["@bdraco", "@prystupa", "@joshs85", "@marciogranzotto"], "codeowners": ["@bdraco", "@prystupa", "@joshs85", "@marciogranzotto"],
"quality_scale": "platinum", "quality_scale": "platinum",

View File

@ -5,7 +5,7 @@ import logging
from typing import Any, cast from typing import Any, cast
from aiohttp import ClientResponseError from aiohttp import ClientResponseError
from bond_async import Action, Bond from bond_async import Action, Bond, BondType
from homeassistant.util.async_ import gather_with_concurrency from homeassistant.util.async_ import gather_with_concurrency
@ -224,4 +224,5 @@ class BondHub:
@property @property
def is_bridge(self) -> bool: def is_bridge(self) -> bool:
"""Return if the Bond is a Bond Bridge.""" """Return if the Bond is a Bond Bridge."""
return bool(self._bridge) bondid = self._version["bondid"]
return bool(BondType.is_bridge_from_serial(bondid))

View File

@ -3,7 +3,7 @@
"name": "Hive", "name": "Hive",
"config_flow": true, "config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/hive", "documentation": "https://www.home-assistant.io/integrations/hive",
"requirements": ["pyhiveapi==0.5.9"], "requirements": ["pyhiveapi==0.5.10"],
"codeowners": ["@Rendili", "@KJonline"], "codeowners": ["@Rendili", "@KJonline"],
"iot_class": "cloud_polling", "iot_class": "cloud_polling",
"loggers": ["apyhiveapi"] "loggers": ["apyhiveapi"]

View File

@ -3,7 +3,7 @@
"name": "Philips Hue", "name": "Philips Hue",
"config_flow": true, "config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/hue", "documentation": "https://www.home-assistant.io/integrations/hue",
"requirements": ["aiohue==4.4.1"], "requirements": ["aiohue==4.4.2"],
"ssdp": [ "ssdp": [
{ {
"manufacturer": "Royal Philips Electronics", "manufacturer": "Royal Philips Electronics",

View File

@ -189,9 +189,10 @@ def async_subscribe_events(
def _forward_state_events_filtered(event: Event) -> None: def _forward_state_events_filtered(event: Event) -> None:
if event.data.get("old_state") is None or event.data.get("new_state") is None: if event.data.get("old_state") is None or event.data.get("new_state") is None:
return return
state: State = event.data["new_state"] new_state: State = event.data["new_state"]
if _is_state_filtered(ent_reg, state) or ( old_state: State = event.data["old_state"]
entities_filter and not entities_filter(state.entity_id) if _is_state_filtered(ent_reg, new_state, old_state) or (
entities_filter and not entities_filter(new_state.entity_id)
): ):
return return
target(event) target(event)
@ -229,17 +230,20 @@ def is_sensor_continuous(ent_reg: er.EntityRegistry, entity_id: str) -> bool:
) )
def _is_state_filtered(ent_reg: er.EntityRegistry, state: State) -> bool: def _is_state_filtered(
ent_reg: er.EntityRegistry, new_state: State, old_state: State
) -> bool:
"""Check if the logbook should filter a state. """Check if the logbook should filter a state.
Used when we are in live mode to ensure Used when we are in live mode to ensure
we only get significant changes (state.last_changed != state.last_updated) we only get significant changes (state.last_changed != state.last_updated)
""" """
return bool( return bool(
split_entity_id(state.entity_id)[0] in ALWAYS_CONTINUOUS_DOMAINS new_state.state == old_state.state
or state.last_changed != state.last_updated or split_entity_id(new_state.entity_id)[0] in ALWAYS_CONTINUOUS_DOMAINS
or ATTR_UNIT_OF_MEASUREMENT in state.attributes or new_state.last_changed != new_state.last_updated
or is_sensor_continuous(ent_reg, state.entity_id) or ATTR_UNIT_OF_MEASUREMENT in new_state.attributes
or is_sensor_continuous(ent_reg, new_state.entity_id)
) )

View File

@ -70,6 +70,7 @@ FAN_MODE_MAP = {
"OFF": FAN_OFF, "OFF": FAN_OFF,
} }
FAN_INV_MODE_MAP = {v: k for k, v in FAN_MODE_MAP.items()} FAN_INV_MODE_MAP = {v: k for k, v in FAN_MODE_MAP.items()}
FAN_INV_MODES = list(FAN_INV_MODE_MAP)
MAX_FAN_DURATION = 43200 # 15 hours is the max in the SDM API MAX_FAN_DURATION = 43200 # 15 hours is the max in the SDM API
MIN_TEMP = 10 MIN_TEMP = 10
@ -99,7 +100,7 @@ class ThermostatEntity(ClimateEntity):
"""Initialize ThermostatEntity.""" """Initialize ThermostatEntity."""
self._device = device self._device = device
self._device_info = NestDeviceInfo(device) self._device_info = NestDeviceInfo(device)
self._supported_features = 0 self._attr_supported_features = 0
@property @property
def should_poll(self) -> bool: def should_poll(self) -> bool:
@ -124,7 +125,7 @@ class ThermostatEntity(ClimateEntity):
async def async_added_to_hass(self) -> None: async def async_added_to_hass(self) -> None:
"""Run when entity is added to register update signal handler.""" """Run when entity is added to register update signal handler."""
self._supported_features = self._get_supported_features() self._attr_supported_features = self._get_supported_features()
self.async_on_remove( self.async_on_remove(
self._device.add_update_listener(self.async_write_ha_state) self._device.add_update_listener(self.async_write_ha_state)
) )
@ -198,8 +199,6 @@ class ThermostatEntity(ClimateEntity):
trait = self._device.traits[ThermostatModeTrait.NAME] trait = self._device.traits[ThermostatModeTrait.NAME]
if trait.mode in THERMOSTAT_MODE_MAP: if trait.mode in THERMOSTAT_MODE_MAP:
hvac_mode = THERMOSTAT_MODE_MAP[trait.mode] hvac_mode = THERMOSTAT_MODE_MAP[trait.mode]
if hvac_mode == HVACMode.OFF and self.fan_mode == FAN_ON:
hvac_mode = HVACMode.FAN_ONLY
return hvac_mode return hvac_mode
@property @property
@ -209,8 +208,6 @@ class ThermostatEntity(ClimateEntity):
for mode in self._get_device_hvac_modes: for mode in self._get_device_hvac_modes:
if mode in THERMOSTAT_MODE_MAP: if mode in THERMOSTAT_MODE_MAP:
supported_modes.append(THERMOSTAT_MODE_MAP[mode]) supported_modes.append(THERMOSTAT_MODE_MAP[mode])
if self.supported_features & ClimateEntityFeature.FAN_MODE:
supported_modes.append(HVACMode.FAN_ONLY)
return supported_modes return supported_modes
@property @property
@ -252,7 +249,10 @@ class ThermostatEntity(ClimateEntity):
@property @property
def fan_mode(self) -> str: def fan_mode(self) -> str:
"""Return the current fan mode.""" """Return the current fan mode."""
if FanTrait.NAME in self._device.traits: if (
self.supported_features & ClimateEntityFeature.FAN_MODE
and FanTrait.NAME in self._device.traits
):
trait = self._device.traits[FanTrait.NAME] trait = self._device.traits[FanTrait.NAME]
return FAN_MODE_MAP.get(trait.timer_mode, FAN_OFF) return FAN_MODE_MAP.get(trait.timer_mode, FAN_OFF)
return FAN_OFF return FAN_OFF
@ -260,15 +260,12 @@ class ThermostatEntity(ClimateEntity):
@property @property
def fan_modes(self) -> list[str]: def fan_modes(self) -> list[str]:
"""Return the list of available fan modes.""" """Return the list of available fan modes."""
modes = [] if (
if FanTrait.NAME in self._device.traits: self.supported_features & ClimateEntityFeature.FAN_MODE
modes = list(FAN_INV_MODE_MAP) and FanTrait.NAME in self._device.traits
return modes ):
return FAN_INV_MODES
@property return []
def supported_features(self) -> int:
"""Bitmap of supported features."""
return self._supported_features
def _get_supported_features(self) -> int: def _get_supported_features(self) -> int:
"""Compute the bitmap of supported features from the current state.""" """Compute the bitmap of supported features from the current state."""
@ -290,10 +287,6 @@ class ThermostatEntity(ClimateEntity):
"""Set new target hvac mode.""" """Set new target hvac mode."""
if hvac_mode not in self.hvac_modes: if hvac_mode not in self.hvac_modes:
raise ValueError(f"Unsupported hvac_mode '{hvac_mode}'") raise ValueError(f"Unsupported hvac_mode '{hvac_mode}'")
if hvac_mode == HVACMode.FAN_ONLY:
# Turn the fan on but also turn off the hvac if it is on
await self.async_set_fan_mode(FAN_ON)
hvac_mode = HVACMode.OFF
api_mode = THERMOSTAT_INV_MODE_MAP[hvac_mode] api_mode = THERMOSTAT_INV_MODE_MAP[hvac_mode]
trait = self._device.traits[ThermostatModeTrait.NAME] trait = self._device.traits[ThermostatModeTrait.NAME]
try: try:
@ -338,6 +331,10 @@ class ThermostatEntity(ClimateEntity):
"""Set new target fan mode.""" """Set new target fan mode."""
if fan_mode not in self.fan_modes: if fan_mode not in self.fan_modes:
raise ValueError(f"Unsupported fan_mode '{fan_mode}'") raise ValueError(f"Unsupported fan_mode '{fan_mode}'")
if fan_mode == FAN_ON and self.hvac_mode == HVACMode.OFF:
raise ValueError(
"Cannot turn on fan, please set an HVAC mode (e.g. heat/cool) first"
)
trait = self._device.traits[FanTrait.NAME] trait = self._device.traits[FanTrait.NAME]
duration = None duration = None
if fan_mode != FAN_OFF: if fan_mode != FAN_OFF:

View File

@ -134,7 +134,7 @@ class OverkizNumber(OverkizDescriptiveEntity, NumberEntity):
"""Return the entity value to represent the entity state.""" """Return the entity value to represent the entity state."""
if state := self.device.states.get(self.entity_description.key): if state := self.device.states.get(self.entity_description.key):
if self.entity_description.inverted: if self.entity_description.inverted:
return self._attr_max_value - cast(float, state.value) return self.max_value - cast(float, state.value)
return cast(float, state.value) return cast(float, state.value)
@ -143,7 +143,7 @@ class OverkizNumber(OverkizDescriptiveEntity, NumberEntity):
async def async_set_value(self, value: float) -> None: async def async_set_value(self, value: float) -> None:
"""Set new value.""" """Set new value."""
if self.entity_description.inverted: if self.entity_description.inverted:
value = self._attr_max_value - value value = self.max_value - value
await self.executor.async_execute_command( await self.executor.async_execute_command(
self.entity_description.command, value self.entity_description.command, value

View File

@ -7,6 +7,7 @@ from typing import Any
from aiohttp import CookieJar from aiohttp import CookieJar
from pyunifiprotect import NotAuthorized, NvrError, ProtectApiClient from pyunifiprotect import NotAuthorized, NvrError, ProtectApiClient
from pyunifiprotect.data import NVR from pyunifiprotect.data import NVR
from unifi_discovery import async_console_is_alive
import voluptuous as vol import voluptuous as vol
from homeassistant import config_entries from homeassistant import config_entries
@ -21,7 +22,10 @@ from homeassistant.const import (
) )
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant, callback
from homeassistant.data_entry_flow import FlowResult from homeassistant.data_entry_flow import FlowResult
from homeassistant.helpers.aiohttp_client import async_create_clientsession from homeassistant.helpers.aiohttp_client import (
async_create_clientsession,
async_get_clientsession,
)
from homeassistant.helpers.typing import DiscoveryInfoType from homeassistant.helpers.typing import DiscoveryInfoType
from homeassistant.loader import async_get_integration from homeassistant.loader import async_get_integration
from homeassistant.util.network import is_ip_address from homeassistant.util.network import is_ip_address
@ -36,11 +40,17 @@ from .const import (
MIN_REQUIRED_PROTECT_V, MIN_REQUIRED_PROTECT_V,
OUTDATED_LOG_MESSAGE, OUTDATED_LOG_MESSAGE,
) )
from .data import async_last_update_was_successful
from .discovery import async_start_discovery from .discovery import async_start_discovery
from .utils import _async_resolve, _async_short_mac, _async_unifi_mac_from_hass from .utils import _async_resolve, _async_short_mac, _async_unifi_mac_from_hass
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
ENTRY_FAILURE_STATES = (
config_entries.ConfigEntryState.SETUP_ERROR,
config_entries.ConfigEntryState.SETUP_RETRY,
)
async def async_local_user_documentation_url(hass: HomeAssistant) -> str: async def async_local_user_documentation_url(hass: HomeAssistant) -> str:
"""Get the documentation url for creating a local user.""" """Get the documentation url for creating a local user."""
@ -53,6 +63,25 @@ def _host_is_direct_connect(host: str) -> bool:
return host.endswith(".ui.direct") return host.endswith(".ui.direct")
async def _async_console_is_offline(
hass: HomeAssistant,
entry: config_entries.ConfigEntry,
) -> bool:
"""Check if a console is offline.
We define offline by the config entry
is in a failure/retry state or the updates
are failing and the console is unreachable
since protect may be updating.
"""
return bool(
entry.state in ENTRY_FAILURE_STATES
or not async_last_update_was_successful(hass, entry)
) and not await async_console_is_alive(
async_get_clientsession(hass, verify_ssl=False), entry.data[CONF_HOST]
)
class ProtectFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): class ProtectFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
"""Handle a UniFi Protect config flow.""" """Handle a UniFi Protect config flow."""
@ -110,6 +139,7 @@ class ProtectFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
not entry_has_direct_connect not entry_has_direct_connect
and is_ip_address(entry_host) and is_ip_address(entry_host)
and entry_host != source_ip and entry_host != source_ip
and await _async_console_is_offline(self.hass, entry)
): ):
new_host = source_ip new_host = source_ip
if new_host: if new_host:

View File

@ -20,11 +20,21 @@ from homeassistant.config_entries import ConfigEntry
from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback
from homeassistant.helpers.event import async_track_time_interval from homeassistant.helpers.event import async_track_time_interval
from .const import CONF_DISABLE_RTSP, DEVICES_THAT_ADOPT, DEVICES_WITH_ENTITIES from .const import CONF_DISABLE_RTSP, DEVICES_THAT_ADOPT, DEVICES_WITH_ENTITIES, DOMAIN
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@callback
def async_last_update_was_successful(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Check if the last update was successful for a config entry."""
return bool(
DOMAIN in hass.data
and entry.entry_id in hass.data[DOMAIN]
and hass.data[DOMAIN][entry.entry_id].last_update_success
)
class ProtectData: class ProtectData:
"""Coordinate updates.""" """Coordinate updates."""

View File

@ -3,7 +3,7 @@
"name": "UniFi Protect", "name": "UniFi Protect",
"config_flow": true, "config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/unifiprotect", "documentation": "https://www.home-assistant.io/integrations/unifiprotect",
"requirements": ["pyunifiprotect==3.9.2", "unifi-discovery==1.1.3"], "requirements": ["pyunifiprotect==3.9.2", "unifi-discovery==1.1.4"],
"dependencies": ["http"], "dependencies": ["http"],
"codeowners": ["@briis", "@AngellusMortis", "@bdraco"], "codeowners": ["@briis", "@AngellusMortis", "@bdraco"],
"quality_scale": "platinum", "quality_scale": "platinum",

View File

@ -72,11 +72,12 @@ CONFIG_SCHEMA = vol.Schema(
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up the Withings component.""" """Set up the Withings component."""
conf = config.get(DOMAIN, {}) if not (conf := config.get(DOMAIN)):
if not (conf := config.get(DOMAIN, {})): # Apply the defaults.
conf = CONFIG_SCHEMA({DOMAIN: {}})[DOMAIN]
hass.data[DOMAIN] = {const.CONFIG: conf}
return True return True
# Make the config available to the oauth2 config flow.
hass.data[DOMAIN] = {const.CONFIG: conf} hass.data[DOMAIN] = {const.CONFIG: conf}
# Setup the oauth2 config flow. # Setup the oauth2 config flow.

View File

@ -96,7 +96,10 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
self.hass.config_entries.async_update_entry( self.hass.config_entries.async_update_entry(
entry, data={**entry.data, CONF_HOST: self._discovered_ip} entry, data={**entry.data, CONF_HOST: self._discovered_ip}
) )
reload = True reload = entry.state in (
ConfigEntryState.SETUP_RETRY,
ConfigEntryState.LOADED,
)
if reload: if reload:
self.hass.async_create_task( self.hass.async_create_task(
self.hass.config_entries.async_reload(entry.entry_id) self.hass.config_entries.async_reload(entry.entry_id)

View File

@ -14,6 +14,7 @@ from zwave_js_server.const import (
InclusionStrategy, InclusionStrategy,
LogLevel, LogLevel,
Protocols, Protocols,
ProvisioningEntryStatus,
QRCodeVersion, QRCodeVersion,
SecurityClass, SecurityClass,
ZwaveFeature, ZwaveFeature,
@ -148,6 +149,8 @@ MAX_INCLUSION_REQUEST_INTERVAL = "max_inclusion_request_interval"
UUID = "uuid" UUID = "uuid"
SUPPORTED_PROTOCOLS = "supported_protocols" SUPPORTED_PROTOCOLS = "supported_protocols"
ADDITIONAL_PROPERTIES = "additional_properties" ADDITIONAL_PROPERTIES = "additional_properties"
STATUS = "status"
REQUESTED_SECURITY_CLASSES = "requested_security_classes"
FEATURE = "feature" FEATURE = "feature"
UNPROVISION = "unprovision" UNPROVISION = "unprovision"
@ -160,19 +163,22 @@ def convert_planned_provisioning_entry(info: dict) -> ProvisioningEntry:
"""Handle provisioning entry dict to ProvisioningEntry.""" """Handle provisioning entry dict to ProvisioningEntry."""
return ProvisioningEntry( return ProvisioningEntry(
dsk=info[DSK], dsk=info[DSK],
security_classes=[SecurityClass(sec_cls) for sec_cls in info[SECURITY_CLASSES]], security_classes=info[SECURITY_CLASSES],
status=info[STATUS],
requested_security_classes=info.get(REQUESTED_SECURITY_CLASSES),
additional_properties={ additional_properties={
k: v for k, v in info.items() if k not in (DSK, SECURITY_CLASSES) k: v
for k, v in info.items()
if k not in (DSK, SECURITY_CLASSES, STATUS, REQUESTED_SECURITY_CLASSES)
}, },
) )
def convert_qr_provisioning_information(info: dict) -> QRProvisioningInformation: def convert_qr_provisioning_information(info: dict) -> QRProvisioningInformation:
"""Convert QR provisioning information dict to QRProvisioningInformation.""" """Convert QR provisioning information dict to QRProvisioningInformation."""
protocols = [Protocols(proto) for proto in info.get(SUPPORTED_PROTOCOLS, [])]
return QRProvisioningInformation( return QRProvisioningInformation(
version=QRCodeVersion(info[VERSION]), version=info[VERSION],
security_classes=[SecurityClass(sec_cls) for sec_cls in info[SECURITY_CLASSES]], security_classes=info[SECURITY_CLASSES],
dsk=info[DSK], dsk=info[DSK],
generic_device_class=info[GENERIC_DEVICE_CLASS], generic_device_class=info[GENERIC_DEVICE_CLASS],
specific_device_class=info[SPECIFIC_DEVICE_CLASS], specific_device_class=info[SPECIFIC_DEVICE_CLASS],
@ -183,7 +189,9 @@ def convert_qr_provisioning_information(info: dict) -> QRProvisioningInformation
application_version=info[APPLICATION_VERSION], application_version=info[APPLICATION_VERSION],
max_inclusion_request_interval=info.get(MAX_INCLUSION_REQUEST_INTERVAL), max_inclusion_request_interval=info.get(MAX_INCLUSION_REQUEST_INTERVAL),
uuid=info.get(UUID), uuid=info.get(UUID),
supported_protocols=protocols if protocols else None, supported_protocols=info.get(SUPPORTED_PROTOCOLS),
status=info[STATUS],
requested_security_classes=info.get(REQUESTED_SECURITY_CLASSES),
additional_properties=info.get(ADDITIONAL_PROPERTIES, {}), additional_properties=info.get(ADDITIONAL_PROPERTIES, {}),
) )
@ -197,6 +205,12 @@ PLANNED_PROVISIONING_ENTRY_SCHEMA = vol.All(
cv.ensure_list, cv.ensure_list,
[vol.Coerce(SecurityClass)], [vol.Coerce(SecurityClass)],
), ),
vol.Optional(STATUS, default=ProvisioningEntryStatus.ACTIVE): vol.Coerce(
ProvisioningEntryStatus
),
vol.Optional(REQUESTED_SECURITY_CLASSES): vol.All(
cv.ensure_list, [vol.Coerce(SecurityClass)]
),
}, },
# Provisioning entries can have extra keys for SmartStart # Provisioning entries can have extra keys for SmartStart
extra=vol.ALLOW_EXTRA, extra=vol.ALLOW_EXTRA,
@ -226,6 +240,12 @@ QR_PROVISIONING_INFORMATION_SCHEMA = vol.All(
cv.ensure_list, cv.ensure_list,
[vol.Coerce(Protocols)], [vol.Coerce(Protocols)],
), ),
vol.Optional(STATUS, default=ProvisioningEntryStatus.ACTIVE): vol.Coerce(
ProvisioningEntryStatus
),
vol.Optional(REQUESTED_SECURITY_CLASSES): vol.All(
cv.ensure_list, [vol.Coerce(SecurityClass)]
),
vol.Optional(ADDITIONAL_PROPERTIES): dict, vol.Optional(ADDITIONAL_PROPERTIES): dict,
} }
), ),

View File

@ -7,7 +7,7 @@ from .backports.enum import StrEnum
MAJOR_VERSION: Final = 2022 MAJOR_VERSION: Final = 2022
MINOR_VERSION: Final = 6 MINOR_VERSION: Final = 6
PATCH_VERSION: Final = "5" PATCH_VERSION: Final = "6"
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}" __short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
__version__: Final = f"{__short_version__}.{PATCH_VERSION}" __version__: Final = f"{__short_version__}.{PATCH_VERSION}"
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 9, 0) REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 9, 0)

View File

@ -169,7 +169,7 @@ aiohomekit==0.7.17
aiohttp_cors==0.7.0 aiohttp_cors==0.7.0
# homeassistant.components.hue # homeassistant.components.hue
aiohue==4.4.1 aiohue==4.4.2
# homeassistant.components.imap # homeassistant.components.imap
aioimaplib==0.9.0 aioimaplib==0.9.0
@ -417,7 +417,7 @@ blockchain==1.4.4
# bluepy==1.3.0 # bluepy==1.3.0
# homeassistant.components.bond # homeassistant.components.bond
bond-async==0.1.20 bond-async==0.1.22
# homeassistant.components.bosch_shc # homeassistant.components.bosch_shc
boschshcpy==0.2.30 boschshcpy==0.2.30
@ -1538,7 +1538,7 @@ pyheos==0.7.2
pyhik==0.3.0 pyhik==0.3.0
# homeassistant.components.hive # homeassistant.components.hive
pyhiveapi==0.5.9 pyhiveapi==0.5.10
# homeassistant.components.homematic # homeassistant.components.homematic
pyhomematic==0.1.77 pyhomematic==0.1.77
@ -2358,7 +2358,7 @@ twitchAPI==2.5.2
uasiren==0.0.1 uasiren==0.0.1
# homeassistant.components.unifiprotect # homeassistant.components.unifiprotect
unifi-discovery==1.1.3 unifi-discovery==1.1.4
# homeassistant.components.unifiled # homeassistant.components.unifiled
unifiled==0.11 unifiled==0.11

View File

@ -153,7 +153,7 @@ aiohomekit==0.7.17
aiohttp_cors==0.7.0 aiohttp_cors==0.7.0
# homeassistant.components.hue # homeassistant.components.hue
aiohue==4.4.1 aiohue==4.4.2
# homeassistant.components.apache_kafka # homeassistant.components.apache_kafka
aiokafka==0.6.0 aiokafka==0.6.0
@ -318,7 +318,7 @@ blebox_uniapi==1.3.3
blinkpy==0.19.0 blinkpy==0.19.0
# homeassistant.components.bond # homeassistant.components.bond
bond-async==0.1.20 bond-async==0.1.22
# homeassistant.components.bosch_shc # homeassistant.components.bosch_shc
boschshcpy==0.2.30 boschshcpy==0.2.30
@ -1029,7 +1029,7 @@ pyhaversion==22.4.1
pyheos==0.7.2 pyheos==0.7.2
# homeassistant.components.hive # homeassistant.components.hive
pyhiveapi==0.5.9 pyhiveapi==0.5.10
# homeassistant.components.homematic # homeassistant.components.homematic
pyhomematic==0.1.77 pyhomematic==0.1.77
@ -1546,7 +1546,7 @@ twitchAPI==2.5.2
uasiren==0.0.1 uasiren==0.0.1
# homeassistant.components.unifiprotect # homeassistant.components.unifiprotect
unifi-discovery==1.1.3 unifi-discovery==1.1.4
# homeassistant.components.upb # homeassistant.components.upb
upb_lib==0.4.12 upb_lib==0.4.12

View File

@ -1,5 +1,5 @@
[metadata] [metadata]
version = 2022.6.5 version = 2022.6.6
url = https://www.home-assistant.io/ url = https://www.home-assistant.io/
[options] [options]

View File

@ -113,7 +113,7 @@ def patch_bond_version(
return nullcontext() return nullcontext()
if return_value is None: if return_value is None:
return_value = {"bondid": "test-bond-id"} return_value = {"bondid": "ZXXX12345"}
return patch( return patch(
"homeassistant.components.bond.Bond.version", "homeassistant.components.bond.Bond.version",
@ -246,3 +246,12 @@ async def help_test_entity_available(
async_fire_time_changed(hass, utcnow() + timedelta(seconds=30)) async_fire_time_changed(hass, utcnow() + timedelta(seconds=30))
await hass.async_block_till_done() await hass.async_block_till_done()
assert hass.states.get(entity_id).state != STATE_UNAVAILABLE assert hass.states.get(entity_id).state != STATE_UNAVAILABLE
def ceiling_fan(name: str):
"""Create a ceiling fan with given name."""
return {
"name": name,
"type": DeviceType.CEILING_FAN,
"actions": ["SetSpeed", "SetDirection"],
}

View File

@ -35,7 +35,7 @@ async def test_user_form(hass: core.HomeAssistant):
assert result["errors"] == {} assert result["errors"] == {}
with patch_bond_version( with patch_bond_version(
return_value={"bondid": "test-bond-id"} return_value={"bondid": "ZXXX12345"}
), patch_bond_device_ids( ), patch_bond_device_ids(
return_value=["f6776c11", "f6776c12"] return_value=["f6776c11", "f6776c12"]
), patch_bond_bridge(), patch_bond_device_properties(), patch_bond_device(), _patch_async_setup_entry() as mock_setup_entry: ), patch_bond_bridge(), patch_bond_device_properties(), patch_bond_device(), _patch_async_setup_entry() as mock_setup_entry:
@ -64,7 +64,7 @@ async def test_user_form_with_non_bridge(hass: core.HomeAssistant):
assert result["errors"] == {} assert result["errors"] == {}
with patch_bond_version( with patch_bond_version(
return_value={"bondid": "test-bond-id"} return_value={"bondid": "KXXX12345"}
), patch_bond_device_ids( ), patch_bond_device_ids(
return_value=["f6776c11"] return_value=["f6776c11"]
), patch_bond_device_properties(), patch_bond_device( ), patch_bond_device_properties(), patch_bond_device(
@ -96,7 +96,7 @@ async def test_user_form_invalid_auth(hass: core.HomeAssistant):
) )
with patch_bond_version( with patch_bond_version(
return_value={"bond_id": "test-bond-id"} return_value={"bond_id": "ZXXX12345"}
), patch_bond_bridge(), patch_bond_device_ids( ), patch_bond_bridge(), patch_bond_device_ids(
side_effect=ClientResponseError(Mock(), Mock(), status=401), side_effect=ClientResponseError(Mock(), Mock(), status=401),
): ):
@ -203,7 +203,7 @@ async def test_zeroconf_form(hass: core.HomeAssistant):
host="test-host", host="test-host",
addresses=["test-host"], addresses=["test-host"],
hostname="mock_hostname", hostname="mock_hostname",
name="test-bond-id.some-other-tail-info", name="ZXXX12345.some-other-tail-info",
port=None, port=None,
properties={}, properties={},
type="mock_type", type="mock_type",
@ -213,7 +213,7 @@ async def test_zeroconf_form(hass: core.HomeAssistant):
assert result["errors"] == {} assert result["errors"] == {}
with patch_bond_version( with patch_bond_version(
return_value={"bondid": "test-bond-id"} return_value={"bondid": "ZXXX12345"}
), patch_bond_bridge(), patch_bond_device_ids(), _patch_async_setup_entry() as mock_setup_entry: ), patch_bond_bridge(), patch_bond_device_ids(), _patch_async_setup_entry() as mock_setup_entry:
result2 = await hass.config_entries.flow.async_configure( result2 = await hass.config_entries.flow.async_configure(
result["flow_id"], result["flow_id"],
@ -241,7 +241,7 @@ async def test_zeroconf_form_token_unavailable(hass: core.HomeAssistant):
host="test-host", host="test-host",
addresses=["test-host"], addresses=["test-host"],
hostname="mock_hostname", hostname="mock_hostname",
name="test-bond-id.some-other-tail-info", name="ZXXX12345.some-other-tail-info",
port=None, port=None,
properties={}, properties={},
type="mock_type", type="mock_type",
@ -270,7 +270,7 @@ async def test_zeroconf_form_token_unavailable(hass: core.HomeAssistant):
async def test_zeroconf_form_with_token_available(hass: core.HomeAssistant): async def test_zeroconf_form_with_token_available(hass: core.HomeAssistant):
"""Test we get the discovery form when we can get the token.""" """Test we get the discovery form when we can get the token."""
with patch_bond_version(return_value={"bondid": "test-bond-id"}), patch_bond_token( with patch_bond_version(return_value={"bondid": "ZXXX12345"}), patch_bond_token(
return_value={"token": "discovered-token"} return_value={"token": "discovered-token"}
), patch_bond_bridge( ), patch_bond_bridge(
return_value={"name": "discovered-name"} return_value={"name": "discovered-name"}
@ -282,7 +282,7 @@ async def test_zeroconf_form_with_token_available(hass: core.HomeAssistant):
host="test-host", host="test-host",
addresses=["test-host"], addresses=["test-host"],
hostname="mock_hostname", hostname="mock_hostname",
name="test-bond-id.some-other-tail-info", name="ZXXX12345.some-other-tail-info",
port=None, port=None,
properties={}, properties={},
type="mock_type", type="mock_type",
@ -323,7 +323,7 @@ async def test_zeroconf_form_with_token_available_name_unavailable(
host="test-host", host="test-host",
addresses=["test-host"], addresses=["test-host"],
hostname="mock_hostname", hostname="mock_hostname",
name="test-bond-id.some-other-tail-info", name="ZXXX12345.some-other-tail-info",
port=None, port=None,
properties={}, properties={},
type="mock_type", type="mock_type",
@ -341,7 +341,7 @@ async def test_zeroconf_form_with_token_available_name_unavailable(
await hass.async_block_till_done() await hass.async_block_till_done()
assert result2["type"] == "create_entry" assert result2["type"] == "create_entry"
assert result2["title"] == "test-bond-id" assert result2["title"] == "ZXXX12345"
assert result2["data"] == { assert result2["data"] == {
CONF_HOST: "test-host", CONF_HOST: "test-host",
CONF_ACCESS_TOKEN: "discovered-token", CONF_ACCESS_TOKEN: "discovered-token",
@ -472,7 +472,7 @@ async def test_zeroconf_form_unexpected_error(hass: core.HomeAssistant):
host="test-host", host="test-host",
addresses=["test-host"], addresses=["test-host"],
hostname="mock_hostname", hostname="mock_hostname",
name="test-bond-id.some-other-tail-info", name="ZXXX12345.some-other-tail-info",
port=None, port=None,
properties={}, properties={},
type="mock_type", type="mock_type",
@ -497,7 +497,7 @@ async def _help_test_form_unexpected_error(
) )
with patch_bond_version( with patch_bond_version(
return_value={"bond_id": "test-bond-id"} return_value={"bond_id": "ZXXX12345"}
), patch_bond_device_ids(side_effect=error): ), patch_bond_device_ids(side_effect=error):
result2 = await hass.config_entries.flow.async_configure( result2 = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input result["flow_id"], user_input

View File

@ -39,5 +39,5 @@ async def test_diagnostics(hass, hass_client):
"data": {"access_token": "**REDACTED**", "host": "some host"}, "data": {"access_token": "**REDACTED**", "host": "some host"},
"title": "Mock Title", "title": "Mock Title",
}, },
"hub": {"version": {"bondid": "test-bond-id"}}, "hub": {"version": {"bondid": "ZXXX12345"}},
} }

View File

@ -7,13 +7,15 @@ from bond_async import DeviceType
import pytest import pytest
from homeassistant.components.bond.const import DOMAIN from homeassistant.components.bond.const import DOMAIN
from homeassistant.components.fan import DOMAIN as FAN_DOMAIN
from homeassistant.config_entries import ConfigEntryState from homeassistant.config_entries import ConfigEntryState
from homeassistant.const import CONF_ACCESS_TOKEN, CONF_HOST from homeassistant.const import ATTR_ASSUMED_STATE, CONF_ACCESS_TOKEN, CONF_HOST
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers import device_registry as dr from homeassistant.helpers import device_registry as dr
from homeassistant.setup import async_setup_component from homeassistant.setup import async_setup_component
from .common import ( from .common import (
ceiling_fan,
patch_bond_bridge, patch_bond_bridge,
patch_bond_device, patch_bond_device,
patch_bond_device_ids, patch_bond_device_ids,
@ -23,6 +25,7 @@ from .common import (
patch_setup_entry, patch_setup_entry,
patch_start_bpup, patch_start_bpup,
setup_bond_entity, setup_bond_entity,
setup_platform,
) )
from tests.common import MockConfigEntry from tests.common import MockConfigEntry
@ -81,7 +84,7 @@ async def test_async_setup_entry_sets_up_hub_and_supported_domains(hass: HomeAss
with patch_bond_bridge(), patch_bond_version( with patch_bond_bridge(), patch_bond_version(
return_value={ return_value={
"bondid": "test-bond-id", "bondid": "ZXXX12345",
"target": "test-model", "target": "test-model",
"fw_ver": "test-version", "fw_ver": "test-version",
"mcu_ver": "test-hw-version", "mcu_ver": "test-hw-version",
@ -99,11 +102,11 @@ async def test_async_setup_entry_sets_up_hub_and_supported_domains(hass: HomeAss
assert config_entry.entry_id in hass.data[DOMAIN] assert config_entry.entry_id in hass.data[DOMAIN]
assert config_entry.state is ConfigEntryState.LOADED assert config_entry.state is ConfigEntryState.LOADED
assert config_entry.unique_id == "test-bond-id" assert config_entry.unique_id == "ZXXX12345"
# verify hub device is registered correctly # verify hub device is registered correctly
device_registry = dr.async_get(hass) device_registry = dr.async_get(hass)
hub = device_registry.async_get_device(identifiers={(DOMAIN, "test-bond-id")}) hub = device_registry.async_get_device(identifiers={(DOMAIN, "ZXXX12345")})
assert hub.name == "bond-name" assert hub.name == "bond-name"
assert hub.manufacturer == "Olibra" assert hub.manufacturer == "Olibra"
assert hub.model == "test-model" assert hub.model == "test-model"
@ -151,7 +154,7 @@ async def test_old_identifiers_are_removed(hass: HomeAssistant):
) )
old_identifers = (DOMAIN, "device_id") old_identifers = (DOMAIN, "device_id")
new_identifiers = (DOMAIN, "test-bond-id", "device_id") new_identifiers = (DOMAIN, "ZXXX12345", "device_id")
device_registry = dr.async_get(hass) device_registry = dr.async_get(hass)
device_registry.async_get_or_create( device_registry.async_get_or_create(
config_entry_id=config_entry.entry_id, config_entry_id=config_entry.entry_id,
@ -164,7 +167,7 @@ async def test_old_identifiers_are_removed(hass: HomeAssistant):
with patch_bond_bridge(), patch_bond_version( with patch_bond_bridge(), patch_bond_version(
return_value={ return_value={
"bondid": "test-bond-id", "bondid": "ZXXX12345",
"target": "test-model", "target": "test-model",
"fw_ver": "test-version", "fw_ver": "test-version",
} }
@ -185,7 +188,7 @@ async def test_old_identifiers_are_removed(hass: HomeAssistant):
assert config_entry.entry_id in hass.data[DOMAIN] assert config_entry.entry_id in hass.data[DOMAIN]
assert config_entry.state is ConfigEntryState.LOADED assert config_entry.state is ConfigEntryState.LOADED
assert config_entry.unique_id == "test-bond-id" assert config_entry.unique_id == "ZXXX12345"
# verify the device info is cleaned up # verify the device info is cleaned up
assert device_registry.async_get_device(identifiers={old_identifers}) is None assert device_registry.async_get_device(identifiers={old_identifers}) is None
@ -205,7 +208,7 @@ async def test_smart_by_bond_device_suggested_area(hass: HomeAssistant):
side_effect=ClientResponseError(Mock(), Mock(), status=404) side_effect=ClientResponseError(Mock(), Mock(), status=404)
), patch_bond_version( ), patch_bond_version(
return_value={ return_value={
"bondid": "test-bond-id", "bondid": "KXXX12345",
"target": "test-model", "target": "test-model",
"fw_ver": "test-version", "fw_ver": "test-version",
} }
@ -227,10 +230,10 @@ async def test_smart_by_bond_device_suggested_area(hass: HomeAssistant):
assert config_entry.entry_id in hass.data[DOMAIN] assert config_entry.entry_id in hass.data[DOMAIN]
assert config_entry.state is ConfigEntryState.LOADED assert config_entry.state is ConfigEntryState.LOADED
assert config_entry.unique_id == "test-bond-id" assert config_entry.unique_id == "KXXX12345"
device_registry = dr.async_get(hass) device_registry = dr.async_get(hass)
device = device_registry.async_get_device(identifiers={(DOMAIN, "test-bond-id")}) device = device_registry.async_get_device(identifiers={(DOMAIN, "KXXX12345")})
assert device is not None assert device is not None
assert device.suggested_area == "Den" assert device.suggested_area == "Den"
@ -251,7 +254,7 @@ async def test_bridge_device_suggested_area(hass: HomeAssistant):
} }
), patch_bond_version( ), patch_bond_version(
return_value={ return_value={
"bondid": "test-bond-id", "bondid": "ZXXX12345",
"target": "test-model", "target": "test-model",
"fw_ver": "test-version", "fw_ver": "test-version",
} }
@ -273,9 +276,21 @@ async def test_bridge_device_suggested_area(hass: HomeAssistant):
assert config_entry.entry_id in hass.data[DOMAIN] assert config_entry.entry_id in hass.data[DOMAIN]
assert config_entry.state is ConfigEntryState.LOADED assert config_entry.state is ConfigEntryState.LOADED
assert config_entry.unique_id == "test-bond-id" assert config_entry.unique_id == "ZXXX12345"
device_registry = dr.async_get(hass) device_registry = dr.async_get(hass)
device = device_registry.async_get_device(identifiers={(DOMAIN, "test-bond-id")}) device = device_registry.async_get_device(identifiers={(DOMAIN, "ZXXX12345")})
assert device is not None assert device is not None
assert device.suggested_area == "Office" assert device.suggested_area == "Office"
async def test_smart_by_bond_v3_firmware(hass: HomeAssistant) -> None:
"""Test we can detect smart by bond with the v3 firmware."""
await setup_platform(
hass,
FAN_DOMAIN,
ceiling_fan("name-1"),
bond_version={"bondid": "KXXXX12345", "target": "breck-northstar"},
bond_device_id="test-device-id",
)
assert ATTR_ASSUMED_STATE not in hass.states.get("fan.name_1").attributes

View File

@ -249,7 +249,7 @@ async def test_sbb_trust_state(hass: core.HomeAssistant):
"""Assumed state should be False if device is a Smart by Bond.""" """Assumed state should be False if device is a Smart by Bond."""
version = { version = {
"model": "MR123A", "model": "MR123A",
"bondid": "test-bond-id", "bondid": "KXXX12345",
} }
await setup_platform( await setup_platform(
hass, LIGHT_DOMAIN, ceiling_fan("name-1"), bond_version=version, bridge={} hass, LIGHT_DOMAIN, ceiling_fan("name-1"), bond_version=version, bridge={}

View File

@ -2404,3 +2404,117 @@ async def test_subscribe_entities_some_have_uom_multiple(
# Check our listener got unsubscribed # Check our listener got unsubscribed
assert sum(hass.bus.async_listeners().values()) == init_count assert sum(hass.bus.async_listeners().values()) == init_count
@patch("homeassistant.components.logbook.websocket_api.EVENT_COALESCE_TIME", 0)
async def test_logbook_stream_ignores_forced_updates(
hass, recorder_mock, hass_ws_client
):
"""Test logbook live stream ignores forced updates."""
now = dt_util.utcnow()
await asyncio.gather(
*[
async_setup_component(hass, comp, {})
for comp in ("homeassistant", "logbook", "automation", "script")
]
)
await hass.async_block_till_done()
init_count = sum(hass.bus.async_listeners().values())
hass.states.async_set("binary_sensor.is_light", STATE_ON)
hass.states.async_set("binary_sensor.is_light", STATE_OFF)
state: State = hass.states.get("binary_sensor.is_light")
await hass.async_block_till_done()
await async_wait_recording_done(hass)
websocket_client = await hass_ws_client()
await websocket_client.send_json(
{"id": 7, "type": "logbook/event_stream", "start_time": now.isoformat()}
)
msg = await asyncio.wait_for(websocket_client.receive_json(), 2)
assert msg["id"] == 7
assert msg["type"] == TYPE_RESULT
assert msg["success"]
msg = await asyncio.wait_for(websocket_client.receive_json(), 2)
assert msg["id"] == 7
assert msg["type"] == "event"
assert msg["event"]["events"] == [
{
"entity_id": "binary_sensor.is_light",
"state": "off",
"when": state.last_updated.timestamp(),
}
]
assert msg["event"]["start_time"] == now.timestamp()
assert msg["event"]["end_time"] > msg["event"]["start_time"]
assert msg["event"]["partial"] is True
await hass.async_block_till_done()
msg = await asyncio.wait_for(websocket_client.receive_json(), 2)
assert msg["id"] == 7
assert msg["type"] == "event"
assert "partial" not in msg["event"]["events"]
assert msg["event"]["events"] == []
hass.states.async_set("binary_sensor.is_light", STATE_ON)
hass.states.async_set("binary_sensor.is_light", STATE_OFF)
msg = await asyncio.wait_for(websocket_client.receive_json(), 2)
assert msg["id"] == 7
assert msg["type"] == "event"
assert "partial" not in msg["event"]["events"]
assert msg["event"]["events"] == [
{
"entity_id": "binary_sensor.is_light",
"state": STATE_ON,
"when": ANY,
},
{
"entity_id": "binary_sensor.is_light",
"state": STATE_OFF,
"when": ANY,
},
]
# Now we force an update to make sure we ignore
# forced updates when the state has not actually changed
hass.states.async_set("binary_sensor.is_light", STATE_ON)
for _ in range(3):
hass.states.async_set("binary_sensor.is_light", STATE_OFF, force_update=True)
msg = await asyncio.wait_for(websocket_client.receive_json(), 2)
assert msg["id"] == 7
assert msg["type"] == "event"
assert "partial" not in msg["event"]["events"]
assert msg["event"]["events"] == [
{
"entity_id": "binary_sensor.is_light",
"state": STATE_ON,
"when": ANY,
},
# We should only get the first one and ignore
# the other forced updates since the state
# has not actually changed
{
"entity_id": "binary_sensor.is_light",
"state": STATE_OFF,
"when": ANY,
},
]
await websocket_client.send_json(
{"id": 8, "type": "unsubscribe_events", "subscription": 7}
)
msg = await asyncio.wait_for(websocket_client.receive_json(), 2)
assert msg["id"] == 8
assert msg["type"] == TYPE_RESULT
assert msg["success"]
# Check our listener got unsubscribed
assert sum(hass.bus.async_listeners().values()) == init_count

View File

@ -33,15 +33,15 @@ from homeassistant.components.climate.const import (
FAN_ON, FAN_ON,
HVAC_MODE_COOL, HVAC_MODE_COOL,
HVAC_MODE_DRY, HVAC_MODE_DRY,
HVAC_MODE_FAN_ONLY,
HVAC_MODE_HEAT, HVAC_MODE_HEAT,
HVAC_MODE_HEAT_COOL, HVAC_MODE_HEAT_COOL,
HVAC_MODE_OFF, HVAC_MODE_OFF,
PRESET_ECO, PRESET_ECO,
PRESET_NONE, PRESET_NONE,
PRESET_SLEEP, PRESET_SLEEP,
ClimateEntityFeature,
) )
from homeassistant.const import ATTR_TEMPERATURE from homeassistant.const import ATTR_SUPPORTED_FEATURES, ATTR_TEMPERATURE
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError from homeassistant.exceptions import HomeAssistantError
@ -794,7 +794,7 @@ async def test_thermostat_fan_off(
"sdm.devices.traits.ThermostatHvac": {"status": "OFF"}, "sdm.devices.traits.ThermostatHvac": {"status": "OFF"},
"sdm.devices.traits.ThermostatMode": { "sdm.devices.traits.ThermostatMode": {
"availableModes": ["HEAT", "COOL", "HEATCOOL", "OFF"], "availableModes": ["HEAT", "COOL", "HEATCOOL", "OFF"],
"mode": "OFF", "mode": "COOL",
}, },
"sdm.devices.traits.Temperature": { "sdm.devices.traits.Temperature": {
"ambientTemperatureCelsius": 16.2, "ambientTemperatureCelsius": 16.2,
@ -806,18 +806,22 @@ async def test_thermostat_fan_off(
assert len(hass.states.async_all()) == 1 assert len(hass.states.async_all()) == 1
thermostat = hass.states.get("climate.my_thermostat") thermostat = hass.states.get("climate.my_thermostat")
assert thermostat is not None assert thermostat is not None
assert thermostat.state == HVAC_MODE_OFF assert thermostat.state == HVAC_MODE_COOL
assert thermostat.attributes[ATTR_HVAC_ACTION] == CURRENT_HVAC_OFF assert thermostat.attributes[ATTR_HVAC_ACTION] == CURRENT_HVAC_IDLE
assert thermostat.attributes[ATTR_CURRENT_TEMPERATURE] == 16.2 assert thermostat.attributes[ATTR_CURRENT_TEMPERATURE] == 16.2
assert set(thermostat.attributes[ATTR_HVAC_MODES]) == { assert set(thermostat.attributes[ATTR_HVAC_MODES]) == {
HVAC_MODE_HEAT, HVAC_MODE_HEAT,
HVAC_MODE_COOL, HVAC_MODE_COOL,
HVAC_MODE_HEAT_COOL, HVAC_MODE_HEAT_COOL,
HVAC_MODE_FAN_ONLY,
HVAC_MODE_OFF, HVAC_MODE_OFF,
} }
assert thermostat.attributes[ATTR_FAN_MODE] == FAN_OFF assert thermostat.attributes[ATTR_FAN_MODE] == FAN_OFF
assert thermostat.attributes[ATTR_FAN_MODES] == [FAN_ON, FAN_OFF] assert thermostat.attributes[ATTR_FAN_MODES] == [FAN_ON, FAN_OFF]
assert thermostat.attributes[ATTR_SUPPORTED_FEATURES] == (
ClimateEntityFeature.TARGET_TEMPERATURE
| ClimateEntityFeature.TARGET_TEMPERATURE_RANGE
| ClimateEntityFeature.FAN_MODE
)
async def test_thermostat_fan_on( async def test_thermostat_fan_on(
@ -837,7 +841,7 @@ async def test_thermostat_fan_on(
}, },
"sdm.devices.traits.ThermostatMode": { "sdm.devices.traits.ThermostatMode": {
"availableModes": ["HEAT", "COOL", "HEATCOOL", "OFF"], "availableModes": ["HEAT", "COOL", "HEATCOOL", "OFF"],
"mode": "OFF", "mode": "COOL",
}, },
"sdm.devices.traits.Temperature": { "sdm.devices.traits.Temperature": {
"ambientTemperatureCelsius": 16.2, "ambientTemperatureCelsius": 16.2,
@ -849,18 +853,22 @@ async def test_thermostat_fan_on(
assert len(hass.states.async_all()) == 1 assert len(hass.states.async_all()) == 1
thermostat = hass.states.get("climate.my_thermostat") thermostat = hass.states.get("climate.my_thermostat")
assert thermostat is not None assert thermostat is not None
assert thermostat.state == HVAC_MODE_FAN_ONLY assert thermostat.state == HVAC_MODE_COOL
assert thermostat.attributes[ATTR_HVAC_ACTION] == CURRENT_HVAC_IDLE assert thermostat.attributes[ATTR_HVAC_ACTION] == CURRENT_HVAC_IDLE
assert thermostat.attributes[ATTR_CURRENT_TEMPERATURE] == 16.2 assert thermostat.attributes[ATTR_CURRENT_TEMPERATURE] == 16.2
assert set(thermostat.attributes[ATTR_HVAC_MODES]) == { assert set(thermostat.attributes[ATTR_HVAC_MODES]) == {
HVAC_MODE_HEAT, HVAC_MODE_HEAT,
HVAC_MODE_COOL, HVAC_MODE_COOL,
HVAC_MODE_HEAT_COOL, HVAC_MODE_HEAT_COOL,
HVAC_MODE_FAN_ONLY,
HVAC_MODE_OFF, HVAC_MODE_OFF,
} }
assert thermostat.attributes[ATTR_FAN_MODE] == FAN_ON assert thermostat.attributes[ATTR_FAN_MODE] == FAN_ON
assert thermostat.attributes[ATTR_FAN_MODES] == [FAN_ON, FAN_OFF] assert thermostat.attributes[ATTR_FAN_MODES] == [FAN_ON, FAN_OFF]
assert thermostat.attributes[ATTR_SUPPORTED_FEATURES] == (
ClimateEntityFeature.TARGET_TEMPERATURE
| ClimateEntityFeature.TARGET_TEMPERATURE_RANGE
| ClimateEntityFeature.FAN_MODE
)
async def test_thermostat_cool_with_fan( async def test_thermostat_cool_with_fan(
@ -895,11 +903,15 @@ async def test_thermostat_cool_with_fan(
HVAC_MODE_HEAT, HVAC_MODE_HEAT,
HVAC_MODE_COOL, HVAC_MODE_COOL,
HVAC_MODE_HEAT_COOL, HVAC_MODE_HEAT_COOL,
HVAC_MODE_FAN_ONLY,
HVAC_MODE_OFF, HVAC_MODE_OFF,
} }
assert thermostat.attributes[ATTR_FAN_MODE] == FAN_ON assert thermostat.attributes[ATTR_FAN_MODE] == FAN_ON
assert thermostat.attributes[ATTR_FAN_MODES] == [FAN_ON, FAN_OFF] assert thermostat.attributes[ATTR_FAN_MODES] == [FAN_ON, FAN_OFF]
assert thermostat.attributes[ATTR_SUPPORTED_FEATURES] == (
ClimateEntityFeature.TARGET_TEMPERATURE
| ClimateEntityFeature.TARGET_TEMPERATURE_RANGE
| ClimateEntityFeature.FAN_MODE
)
async def test_thermostat_set_fan( async def test_thermostat_set_fan(
@ -907,6 +919,68 @@ async def test_thermostat_set_fan(
setup_platform: PlatformSetup, setup_platform: PlatformSetup,
auth: FakeAuth, auth: FakeAuth,
create_device: CreateDevice, create_device: CreateDevice,
) -> None:
"""Test a thermostat enabling the fan."""
create_device.create(
{
"sdm.devices.traits.Fan": {
"timerMode": "ON",
"timerTimeout": "2019-05-10T03:22:54Z",
},
"sdm.devices.traits.ThermostatHvac": {
"status": "OFF",
},
"sdm.devices.traits.ThermostatMode": {
"availableModes": ["HEAT", "COOL", "HEATCOOL", "OFF"],
"mode": "HEAT",
},
}
)
await setup_platform()
assert len(hass.states.async_all()) == 1
thermostat = hass.states.get("climate.my_thermostat")
assert thermostat is not None
assert thermostat.state == HVAC_MODE_HEAT
assert thermostat.attributes[ATTR_FAN_MODE] == FAN_ON
assert thermostat.attributes[ATTR_FAN_MODES] == [FAN_ON, FAN_OFF]
assert thermostat.attributes[ATTR_SUPPORTED_FEATURES] == (
ClimateEntityFeature.TARGET_TEMPERATURE
| ClimateEntityFeature.TARGET_TEMPERATURE_RANGE
| ClimateEntityFeature.FAN_MODE
)
# Turn off fan mode
await common.async_set_fan_mode(hass, FAN_OFF)
await hass.async_block_till_done()
assert auth.method == "post"
assert auth.url == DEVICE_COMMAND
assert auth.json == {
"command": "sdm.devices.commands.Fan.SetTimer",
"params": {"timerMode": "OFF"},
}
# Turn on fan mode
await common.async_set_fan_mode(hass, FAN_ON)
await hass.async_block_till_done()
assert auth.method == "post"
assert auth.url == DEVICE_COMMAND
assert auth.json == {
"command": "sdm.devices.commands.Fan.SetTimer",
"params": {
"duration": "43200s",
"timerMode": "ON",
},
}
async def test_thermostat_set_fan_when_off(
hass: HomeAssistant,
setup_platform: PlatformSetup,
auth: FakeAuth,
create_device: CreateDevice,
) -> None: ) -> None:
"""Test a thermostat enabling the fan.""" """Test a thermostat enabling the fan."""
create_device.create( create_device.create(
@ -929,34 +1003,18 @@ async def test_thermostat_set_fan(
assert len(hass.states.async_all()) == 1 assert len(hass.states.async_all()) == 1
thermostat = hass.states.get("climate.my_thermostat") thermostat = hass.states.get("climate.my_thermostat")
assert thermostat is not None assert thermostat is not None
assert thermostat.state == HVAC_MODE_FAN_ONLY assert thermostat.state == HVAC_MODE_OFF
assert thermostat.attributes[ATTR_FAN_MODE] == FAN_ON assert thermostat.attributes[ATTR_FAN_MODE] == FAN_ON
assert thermostat.attributes[ATTR_FAN_MODES] == [FAN_ON, FAN_OFF] assert thermostat.attributes[ATTR_FAN_MODES] == [FAN_ON, FAN_OFF]
assert thermostat.attributes[ATTR_SUPPORTED_FEATURES] == (
ClimateEntityFeature.TARGET_TEMPERATURE
| ClimateEntityFeature.TARGET_TEMPERATURE_RANGE
| ClimateEntityFeature.FAN_MODE
)
# Turn off fan mode # Fan cannot be turned on when HVAC is off
await common.async_set_fan_mode(hass, FAN_OFF) with pytest.raises(ValueError):
await hass.async_block_till_done() await common.async_set_fan_mode(hass, FAN_ON, entity_id="climate.my_thermostat")
assert auth.method == "post"
assert auth.url == DEVICE_COMMAND
assert auth.json == {
"command": "sdm.devices.commands.Fan.SetTimer",
"params": {"timerMode": "OFF"},
}
# Turn on fan mode
await common.async_set_fan_mode(hass, FAN_ON)
await hass.async_block_till_done()
assert auth.method == "post"
assert auth.url == DEVICE_COMMAND
assert auth.json == {
"command": "sdm.devices.commands.Fan.SetTimer",
"params": {
"duration": "43200s",
"timerMode": "ON",
},
}
async def test_thermostat_fan_empty( async def test_thermostat_fan_empty(
@ -994,6 +1052,10 @@ async def test_thermostat_fan_empty(
} }
assert ATTR_FAN_MODE not in thermostat.attributes assert ATTR_FAN_MODE not in thermostat.attributes
assert ATTR_FAN_MODES not in thermostat.attributes assert ATTR_FAN_MODES not in thermostat.attributes
assert thermostat.attributes[ATTR_SUPPORTED_FEATURES] == (
ClimateEntityFeature.TARGET_TEMPERATURE
| ClimateEntityFeature.TARGET_TEMPERATURE_RANGE
)
# Ignores set_fan_mode since it is lacking SUPPORT_FAN_MODE # Ignores set_fan_mode since it is lacking SUPPORT_FAN_MODE
await common.async_set_fan_mode(hass, FAN_ON) await common.async_set_fan_mode(hass, FAN_ON)
@ -1018,7 +1080,7 @@ async def test_thermostat_invalid_fan_mode(
"sdm.devices.traits.ThermostatHvac": {"status": "OFF"}, "sdm.devices.traits.ThermostatHvac": {"status": "OFF"},
"sdm.devices.traits.ThermostatMode": { "sdm.devices.traits.ThermostatMode": {
"availableModes": ["HEAT", "COOL", "HEATCOOL", "OFF"], "availableModes": ["HEAT", "COOL", "HEATCOOL", "OFF"],
"mode": "OFF", "mode": "COOL",
}, },
"sdm.devices.traits.Temperature": { "sdm.devices.traits.Temperature": {
"ambientTemperatureCelsius": 16.2, "ambientTemperatureCelsius": 16.2,
@ -1030,14 +1092,13 @@ async def test_thermostat_invalid_fan_mode(
assert len(hass.states.async_all()) == 1 assert len(hass.states.async_all()) == 1
thermostat = hass.states.get("climate.my_thermostat") thermostat = hass.states.get("climate.my_thermostat")
assert thermostat is not None assert thermostat is not None
assert thermostat.state == HVAC_MODE_FAN_ONLY assert thermostat.state == HVAC_MODE_COOL
assert thermostat.attributes[ATTR_HVAC_ACTION] == CURRENT_HVAC_IDLE assert thermostat.attributes[ATTR_HVAC_ACTION] == CURRENT_HVAC_IDLE
assert thermostat.attributes[ATTR_CURRENT_TEMPERATURE] == 16.2 assert thermostat.attributes[ATTR_CURRENT_TEMPERATURE] == 16.2
assert set(thermostat.attributes[ATTR_HVAC_MODES]) == { assert set(thermostat.attributes[ATTR_HVAC_MODES]) == {
HVAC_MODE_HEAT, HVAC_MODE_HEAT,
HVAC_MODE_COOL, HVAC_MODE_COOL,
HVAC_MODE_HEAT_COOL, HVAC_MODE_HEAT_COOL,
HVAC_MODE_FAN_ONLY,
HVAC_MODE_OFF, HVAC_MODE_OFF,
} }
assert thermostat.attributes[ATTR_FAN_MODE] == FAN_ON assert thermostat.attributes[ATTR_FAN_MODE] == FAN_ON
@ -1048,58 +1109,6 @@ async def test_thermostat_invalid_fan_mode(
await hass.async_block_till_done() await hass.async_block_till_done()
async def test_thermostat_set_hvac_fan_only(
hass: HomeAssistant,
setup_platform: PlatformSetup,
auth: FakeAuth,
create_device: CreateDevice,
) -> None:
"""Test a thermostat enabling the fan via hvac_mode."""
create_device.create(
{
"sdm.devices.traits.Fan": {
"timerMode": "OFF",
"timerTimeout": "2019-05-10T03:22:54Z",
},
"sdm.devices.traits.ThermostatHvac": {
"status": "OFF",
},
"sdm.devices.traits.ThermostatMode": {
"availableModes": ["HEAT", "COOL", "HEATCOOL", "OFF"],
"mode": "OFF",
},
}
)
await setup_platform()
assert len(hass.states.async_all()) == 1
thermostat = hass.states.get("climate.my_thermostat")
assert thermostat is not None
assert thermostat.state == HVAC_MODE_OFF
assert thermostat.attributes[ATTR_FAN_MODE] == FAN_OFF
assert thermostat.attributes[ATTR_FAN_MODES] == [FAN_ON, FAN_OFF]
await common.async_set_hvac_mode(hass, HVAC_MODE_FAN_ONLY)
await hass.async_block_till_done()
assert len(auth.captured_requests) == 2
(method, url, json, headers) = auth.captured_requests.pop(0)
assert method == "post"
assert url == DEVICE_COMMAND
assert json == {
"command": "sdm.devices.commands.Fan.SetTimer",
"params": {"duration": "43200s", "timerMode": "ON"},
}
(method, url, json, headers) = auth.captured_requests.pop(0)
assert method == "post"
assert url == DEVICE_COMMAND
assert json == {
"command": "sdm.devices.commands.ThermostatMode.SetMode",
"params": {"mode": "OFF"},
}
async def test_thermostat_target_temp( async def test_thermostat_target_temp(
hass: HomeAssistant, hass: HomeAssistant,
setup_platform: PlatformSetup, setup_platform: PlatformSetup,
@ -1397,7 +1406,7 @@ async def test_thermostat_hvac_mode_failure(
"sdm.devices.traits.ThermostatHvac": {"status": "OFF"}, "sdm.devices.traits.ThermostatHvac": {"status": "OFF"},
"sdm.devices.traits.ThermostatMode": { "sdm.devices.traits.ThermostatMode": {
"availableModes": ["HEAT", "COOL", "HEATCOOL", "OFF"], "availableModes": ["HEAT", "COOL", "HEATCOOL", "OFF"],
"mode": "OFF", "mode": "COOL",
}, },
"sdm.devices.traits.Fan": { "sdm.devices.traits.Fan": {
"timerMode": "OFF", "timerMode": "OFF",
@ -1416,8 +1425,8 @@ async def test_thermostat_hvac_mode_failure(
assert len(hass.states.async_all()) == 1 assert len(hass.states.async_all()) == 1
thermostat = hass.states.get("climate.my_thermostat") thermostat = hass.states.get("climate.my_thermostat")
assert thermostat is not None assert thermostat is not None
assert thermostat.state == HVAC_MODE_OFF assert thermostat.state == HVAC_MODE_COOL
assert thermostat.attributes[ATTR_HVAC_ACTION] == CURRENT_HVAC_OFF assert thermostat.attributes[ATTR_HVAC_ACTION] == CURRENT_HVAC_IDLE
auth.responses = [aiohttp.web.Response(status=HTTPStatus.BAD_REQUEST)] auth.responses = [aiohttp.web.Response(status=HTTPStatus.BAD_REQUEST)]
with pytest.raises(HomeAssistantError): with pytest.raises(HomeAssistantError):

View File

@ -402,7 +402,10 @@ async def test_discovered_by_unifi_discovery_direct_connect_updated_but_not_usin
) )
mock_config.add_to_hass(hass) mock_config.add_to_hass(hass)
with _patch_discovery(): with _patch_discovery(), patch(
"homeassistant.components.unifiprotect.config_flow.async_console_is_alive",
return_value=False,
):
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, DOMAIN,
context={"source": config_entries.SOURCE_INTEGRATION_DISCOVERY}, context={"source": config_entries.SOURCE_INTEGRATION_DISCOVERY},
@ -415,6 +418,41 @@ async def test_discovered_by_unifi_discovery_direct_connect_updated_but_not_usin
assert mock_config.data[CONF_HOST] == "127.0.0.1" assert mock_config.data[CONF_HOST] == "127.0.0.1"
async def test_discovered_by_unifi_discovery_does_not_update_ip_when_console_is_still_online(
hass: HomeAssistant, mock_nvr: NVR
) -> None:
"""Test a discovery from unifi-discovery does not update the ip unless the console at the old ip is offline."""
mock_config = MockConfigEntry(
domain=DOMAIN,
data={
"host": "1.2.2.2",
"username": "test-username",
"password": "test-password",
"id": "UnifiProtect",
"port": 443,
"verify_ssl": False,
},
version=2,
unique_id=DEVICE_MAC_ADDRESS.replace(":", "").upper(),
)
mock_config.add_to_hass(hass)
with _patch_discovery(), patch(
"homeassistant.components.unifiprotect.config_flow.async_console_is_alive",
return_value=True,
):
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_INTEGRATION_DISCOVERY},
data=UNIFI_DISCOVERY_DICT,
)
await hass.async_block_till_done()
assert result["type"] == RESULT_TYPE_ABORT
assert result["reason"] == "already_configured"
assert mock_config.data[CONF_HOST] == "1.2.2.2"
async def test_discovered_host_not_updated_if_existing_is_a_hostname( async def test_discovered_host_not_updated_if_existing_is_a_hostname(
hass: HomeAssistant, mock_nvr: NVR hass: HomeAssistant, mock_nvr: NVR
) -> None: ) -> None:

View File

@ -739,7 +739,7 @@ async def test_discovered_zeroconf(hass):
async def test_discovery_updates_ip(hass: HomeAssistant): async def test_discovery_updates_ip(hass: HomeAssistant):
"""Test discovery updtes ip.""" """Test discovery updates ip."""
config_entry = MockConfigEntry( config_entry = MockConfigEntry(
domain=DOMAIN, data={CONF_HOST: "1.2.2.3"}, unique_id=ID domain=DOMAIN, data={CONF_HOST: "1.2.2.3"}, unique_id=ID
) )
@ -761,6 +761,35 @@ async def test_discovery_updates_ip(hass: HomeAssistant):
assert config_entry.data[CONF_HOST] == IP_ADDRESS assert config_entry.data[CONF_HOST] == IP_ADDRESS
async def test_discovery_updates_ip_no_reload_setup_in_progress(hass: HomeAssistant):
"""Test discovery updates ip does not reload if setup is an an error state."""
config_entry = MockConfigEntry(
domain=DOMAIN,
data={CONF_HOST: "1.2.2.3"},
unique_id=ID,
state=config_entries.ConfigEntryState.SETUP_ERROR,
)
config_entry.add_to_hass(hass)
mocked_bulb = _mocked_bulb()
with patch(
f"{MODULE}.async_setup_entry", return_value=True
) as mock_setup_entry, _patch_discovery(), _patch_discovery_interval(), patch(
f"{MODULE_CONFIG_FLOW}.AsyncBulb", return_value=mocked_bulb
):
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_ZEROCONF},
data=ZEROCONF_DATA,
)
await hass.async_block_till_done()
assert result["type"] == RESULT_TYPE_ABORT
assert result["reason"] == "already_configured"
assert config_entry.data[CONF_HOST] == IP_ADDRESS
assert len(mock_setup_entry.mock_calls) == 0
async def test_discovery_adds_missing_ip_id_only(hass: HomeAssistant): async def test_discovery_adds_missing_ip_id_only(hass: HomeAssistant):
"""Test discovery adds missing ip.""" """Test discovery adds missing ip."""
config_entry = MockConfigEntry(domain=DOMAIN, data={CONF_ID: ID}) config_entry = MockConfigEntry(domain=DOMAIN, data={CONF_ID: ID})

View File

@ -10,6 +10,7 @@ from zwave_js_server.const import (
InclusionStrategy, InclusionStrategy,
LogLevel, LogLevel,
Protocols, Protocols,
ProvisioningEntryStatus,
QRCodeVersion, QRCodeVersion,
SecurityClass, SecurityClass,
ZwaveFeature, ZwaveFeature,
@ -63,8 +64,10 @@ from homeassistant.components.zwave_js.api import (
PROPERTY_KEY, PROPERTY_KEY,
QR_CODE_STRING, QR_CODE_STRING,
QR_PROVISIONING_INFORMATION, QR_PROVISIONING_INFORMATION,
REQUESTED_SECURITY_CLASSES,
SECURITY_CLASSES, SECURITY_CLASSES,
SPECIFIC_DEVICE_CLASS, SPECIFIC_DEVICE_CLASS,
STATUS,
TYPE, TYPE,
UNPROVISION, UNPROVISION,
VALUE, VALUE,
@ -619,13 +622,68 @@ async def test_add_node(
client.async_send_command.reset_mock() client.async_send_command.reset_mock()
client.async_send_command.return_value = {"success": True} client.async_send_command.return_value = {"success": True}
# Test S2 QR code string # Test S2 QR provisioning information
await ws_client.send_json( await ws_client.send_json(
{ {
ID: 4, ID: 4,
TYPE: "zwave_js/add_node", TYPE: "zwave_js/add_node",
ENTRY_ID: entry.entry_id, ENTRY_ID: entry.entry_id,
INCLUSION_STRATEGY: InclusionStrategy.SECURITY_S2.value, INCLUSION_STRATEGY: InclusionStrategy.SECURITY_S2.value,
QR_PROVISIONING_INFORMATION: {
VERSION: 0,
SECURITY_CLASSES: [0],
DSK: "test",
GENERIC_DEVICE_CLASS: 1,
SPECIFIC_DEVICE_CLASS: 1,
INSTALLER_ICON_TYPE: 1,
MANUFACTURER_ID: 1,
PRODUCT_TYPE: 1,
PRODUCT_ID: 1,
APPLICATION_VERSION: "test",
STATUS: 1,
REQUESTED_SECURITY_CLASSES: [0],
},
}
)
msg = await ws_client.receive_json()
assert msg["success"]
assert len(client.async_send_command.call_args_list) == 1
assert client.async_send_command.call_args[0][0] == {
"command": "controller.begin_inclusion",
"options": {
"strategy": InclusionStrategy.SECURITY_S2,
"provisioning": QRProvisioningInformation(
version=QRCodeVersion.S2,
security_classes=[SecurityClass.S2_UNAUTHENTICATED],
dsk="test",
generic_device_class=1,
specific_device_class=1,
installer_icon_type=1,
manufacturer_id=1,
product_type=1,
product_id=1,
application_version="test",
max_inclusion_request_interval=None,
uuid=None,
supported_protocols=None,
status=ProvisioningEntryStatus.INACTIVE,
requested_security_classes=[SecurityClass.S2_UNAUTHENTICATED],
).to_dict(),
},
}
client.async_send_command.reset_mock()
client.async_send_command.return_value = {"success": True}
# Test S2 QR code string
await ws_client.send_json(
{
ID: 5,
TYPE: "zwave_js/add_node",
ENTRY_ID: entry.entry_id,
INCLUSION_STRATEGY: InclusionStrategy.SECURITY_S2.value,
QR_CODE_STRING: "90testtesttesttesttesttesttesttesttesttesttesttesttest", QR_CODE_STRING: "90testtesttesttesttesttesttesttesttesttesttesttesttest",
} }
) )
@ -648,7 +706,7 @@ async def test_add_node(
# Test Smart Start QR provisioning information with S2 inclusion strategy fails # Test Smart Start QR provisioning information with S2 inclusion strategy fails
await ws_client.send_json( await ws_client.send_json(
{ {
ID: 5, ID: 6,
TYPE: "zwave_js/add_node", TYPE: "zwave_js/add_node",
ENTRY_ID: entry.entry_id, ENTRY_ID: entry.entry_id,
INCLUSION_STRATEGY: InclusionStrategy.SECURITY_S2.value, INCLUSION_STRATEGY: InclusionStrategy.SECURITY_S2.value,
@ -678,7 +736,7 @@ async def test_add_node(
# Test QR provisioning information with S0 inclusion strategy fails # Test QR provisioning information with S0 inclusion strategy fails
await ws_client.send_json( await ws_client.send_json(
{ {
ID: 5, ID: 7,
TYPE: "zwave_js/add_node", TYPE: "zwave_js/add_node",
ENTRY_ID: entry.entry_id, ENTRY_ID: entry.entry_id,
INCLUSION_STRATEGY: InclusionStrategy.SECURITY_S0, INCLUSION_STRATEGY: InclusionStrategy.SECURITY_S0,
@ -708,7 +766,7 @@ async def test_add_node(
# Test ValueError is caught as failure # Test ValueError is caught as failure
await ws_client.send_json( await ws_client.send_json(
{ {
ID: 6, ID: 8,
TYPE: "zwave_js/add_node", TYPE: "zwave_js/add_node",
ENTRY_ID: entry.entry_id, ENTRY_ID: entry.entry_id,
INCLUSION_STRATEGY: InclusionStrategy.DEFAULT.value, INCLUSION_STRATEGY: InclusionStrategy.DEFAULT.value,
@ -728,7 +786,7 @@ async def test_add_node(
): ):
await ws_client.send_json( await ws_client.send_json(
{ {
ID: 7, ID: 9,
TYPE: "zwave_js/add_node", TYPE: "zwave_js/add_node",
ENTRY_ID: entry.entry_id, ENTRY_ID: entry.entry_id,
} }
@ -744,7 +802,7 @@ async def test_add_node(
await hass.async_block_till_done() await hass.async_block_till_done()
await ws_client.send_json( await ws_client.send_json(
{ID: 8, TYPE: "zwave_js/add_node", ENTRY_ID: entry.entry_id} {ID: 10, TYPE: "zwave_js/add_node", ENTRY_ID: entry.entry_id}
) )
msg = await ws_client.receive_json() msg = await ws_client.receive_json()