mirror of
https://github.com/home-assistant/core.git
synced 2025-07-26 22:57:17 +00:00
Merge pull request #73504 from home-assistant/rc
This commit is contained in:
commit
c73eca5923
@ -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",
|
||||||
|
@ -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))
|
||||||
|
@ -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"]
|
||||||
|
@ -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",
|
||||||
|
@ -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)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -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:
|
||||||
|
@ -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
|
||||||
|
@ -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:
|
||||||
|
@ -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."""
|
||||||
|
|
||||||
|
@ -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",
|
||||||
|
@ -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.
|
||||||
|
@ -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)
|
||||||
|
@ -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,
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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]
|
||||||
|
@ -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"],
|
||||||
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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"}},
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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={}
|
||||||
|
@ -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
|
||||||
|
@ -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):
|
||||||
|
@ -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:
|
||||||
|
@ -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})
|
||||||
|
@ -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()
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user