mirror of
https://github.com/home-assistant/core.git
synced 2025-07-22 20:57:21 +00:00
Merge pull request #63867 from home-assistant/rc
This commit is contained in:
commit
b6f432645d
@ -452,7 +452,7 @@ homeassistant/components/samsungtv/* @escoand @chemelli74
|
||||
homeassistant/components/scene/* @home-assistant/core
|
||||
homeassistant/components/schluter/* @prairieapps
|
||||
homeassistant/components/scrape/* @fabaff
|
||||
homeassistant/components/screenlogic/* @dieselrabbit
|
||||
homeassistant/components/screenlogic/* @dieselrabbit @bdraco
|
||||
homeassistant/components/script/* @home-assistant/core
|
||||
homeassistant/components/search/* @home-assistant/core
|
||||
homeassistant/components/select/* @home-assistant/core
|
||||
|
@ -245,6 +245,15 @@ class AugustData(AugustSubscriberMixin):
|
||||
device_id,
|
||||
)
|
||||
|
||||
async def async_lock_async(self, device_id):
|
||||
"""Lock the device but do not wait for a response since it will come via pubnub."""
|
||||
return await self._async_call_api_op_requires_bridge(
|
||||
device_id,
|
||||
self._api.async_lock_async,
|
||||
self._august_gateway.access_token,
|
||||
device_id,
|
||||
)
|
||||
|
||||
async def async_unlock(self, device_id):
|
||||
"""Unlock the device."""
|
||||
return await self._async_call_api_op_requires_bridge(
|
||||
@ -254,6 +263,15 @@ class AugustData(AugustSubscriberMixin):
|
||||
device_id,
|
||||
)
|
||||
|
||||
async def async_unlock_async(self, device_id):
|
||||
"""Unlock the device but do not wait for a response since it will come via pubnub."""
|
||||
return await self._async_call_api_op_requires_bridge(
|
||||
device_id,
|
||||
self._api.async_unlock_async,
|
||||
self._august_gateway.access_token,
|
||||
device_id,
|
||||
)
|
||||
|
||||
async def _async_call_api_op_requires_bridge(
|
||||
self, device_id, func, *args, **kwargs
|
||||
):
|
||||
|
@ -61,6 +61,17 @@ def _retrieve_motion_state(data: AugustData, detail: DoorbellDetail) -> bool:
|
||||
return _activity_time_based_state(latest)
|
||||
|
||||
|
||||
def _retrieve_image_capture_state(data: AugustData, detail: DoorbellDetail) -> bool:
|
||||
latest = data.activity_stream.get_latest_device_activity(
|
||||
detail.device_id, {ActivityType.DOORBELL_IMAGE_CAPTURE}
|
||||
)
|
||||
|
||||
if latest is None:
|
||||
return False
|
||||
|
||||
return _activity_time_based_state(latest)
|
||||
|
||||
|
||||
def _retrieve_ding_state(data: AugustData, detail: DoorbellDetail) -> bool:
|
||||
latest = data.activity_stream.get_latest_device_activity(
|
||||
detail.device_id, {ActivityType.DOORBELL_DING}
|
||||
@ -126,6 +137,13 @@ SENSOR_TYPES_DOORBELL: tuple[AugustBinarySensorEntityDescription, ...] = (
|
||||
value_fn=_retrieve_motion_state,
|
||||
is_time_based=True,
|
||||
),
|
||||
AugustBinarySensorEntityDescription(
|
||||
key="doorbell_image_capture",
|
||||
name="Image Capture",
|
||||
icon="mdi:file-image",
|
||||
value_fn=_retrieve_image_capture_state,
|
||||
is_time_based=True,
|
||||
),
|
||||
AugustBinarySensorEntityDescription(
|
||||
key="doorbell_online",
|
||||
name="Online",
|
||||
|
@ -63,7 +63,8 @@ class AugustCamera(AugustEntityMixin, Camera):
|
||||
def _update_from_data(self):
|
||||
"""Get the latest state of the sensor."""
|
||||
doorbell_activity = self._data.activity_stream.get_latest_device_activity(
|
||||
self._device_id, {ActivityType.DOORBELL_MOTION}
|
||||
self._device_id,
|
||||
{ActivityType.DOORBELL_MOTION, ActivityType.DOORBELL_IMAGE_CAPTURE},
|
||||
)
|
||||
|
||||
if doorbell_activity is not None:
|
||||
|
@ -41,10 +41,16 @@ class AugustLock(AugustEntityMixin, RestoreEntity, LockEntity):
|
||||
|
||||
async def async_lock(self, **kwargs):
|
||||
"""Lock the device."""
|
||||
if self._data.activity_stream.pubnub.connected:
|
||||
await self._data.async_lock_async(self._device_id)
|
||||
return
|
||||
await self._call_lock_operation(self._data.async_lock)
|
||||
|
||||
async def async_unlock(self, **kwargs):
|
||||
"""Unlock the device."""
|
||||
if self._data.activity_stream.pubnub.connected:
|
||||
await self._data.async_unlock_async(self._device_id)
|
||||
return
|
||||
await self._call_lock_operation(self._data.async_unlock)
|
||||
|
||||
async def _call_lock_operation(self, lock_operation):
|
||||
|
@ -2,7 +2,7 @@
|
||||
"domain": "august",
|
||||
"name": "August",
|
||||
"documentation": "https://www.home-assistant.io/integrations/august",
|
||||
"requirements": ["yalexs==1.1.13"],
|
||||
"requirements": ["yalexs==1.1.17"],
|
||||
"codeowners": ["@bdraco"],
|
||||
"dhcp": [
|
||||
{
|
||||
|
@ -3,7 +3,7 @@
|
||||
"name": "Google Cast",
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/cast",
|
||||
"requirements": ["pychromecast==10.2.2"],
|
||||
"requirements": ["pychromecast==10.2.3"],
|
||||
"after_dependencies": [
|
||||
"cloud",
|
||||
"http",
|
||||
|
@ -21,6 +21,7 @@ from homeassistant.components.google_assistant import helpers as google_helpers
|
||||
from homeassistant.components.http import HomeAssistantView
|
||||
from homeassistant.components.http.data_validator import RequestDataValidator
|
||||
from homeassistant.components.websocket_api import const as ws_const
|
||||
from homeassistant.util.location import async_detect_location_info
|
||||
|
||||
from .const import (
|
||||
DOMAIN,
|
||||
@ -220,8 +221,23 @@ class CloudRegisterView(HomeAssistantView):
|
||||
hass = request.app["hass"]
|
||||
cloud = hass.data[DOMAIN]
|
||||
|
||||
client_metadata = None
|
||||
|
||||
if location_info := await async_detect_location_info(
|
||||
hass.helpers.aiohttp_client.async_get_clientsession()
|
||||
):
|
||||
client_metadata = {
|
||||
"NC_COUNTRY_CODE": location_info.country_code,
|
||||
"NC_REGION_CODE": location_info.region_code,
|
||||
"NC_ZIP_CODE": location_info.zip_code,
|
||||
}
|
||||
|
||||
async with async_timeout.timeout(REQUEST_TIMEOUT):
|
||||
await cloud.auth.async_register(data["email"], data["password"])
|
||||
await cloud.auth.async_register(
|
||||
data["email"],
|
||||
data["password"],
|
||||
client_metadata=client_metadata,
|
||||
)
|
||||
|
||||
return self.json_message("ok")
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
"domain": "cloud",
|
||||
"name": "Home Assistant Cloud",
|
||||
"documentation": "https://www.home-assistant.io/integrations/cloud",
|
||||
"requirements": ["hass-nabucasa==0.50.0"],
|
||||
"requirements": ["hass-nabucasa==0.51.0"],
|
||||
"dependencies": ["http", "webhook"],
|
||||
"after_dependencies": ["google_assistant", "alexa"],
|
||||
"codeowners": ["@home-assistant/cloud"],
|
||||
|
@ -3,7 +3,7 @@
|
||||
"name": "Flux LED/MagicHome",
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/flux_led",
|
||||
"requirements": ["flux_led==0.27.32"],
|
||||
"requirements": ["flux_led==0.27.45"],
|
||||
"quality_scale": "platinum",
|
||||
"codeowners": ["@icemanch"],
|
||||
"iot_class": "local_push",
|
||||
|
@ -3,7 +3,7 @@
|
||||
"name": "Home Assistant Frontend",
|
||||
"documentation": "https://www.home-assistant.io/integrations/frontend",
|
||||
"requirements": [
|
||||
"home-assistant-frontend==20211229.0"
|
||||
"home-assistant-frontend==20211229.1"
|
||||
],
|
||||
"dependencies": [
|
||||
"api",
|
||||
|
@ -2,7 +2,7 @@
|
||||
"domain": "harmony",
|
||||
"name": "Logitech Harmony Hub",
|
||||
"documentation": "https://www.home-assistant.io/integrations/harmony",
|
||||
"requirements": ["aioharmony==0.2.8"],
|
||||
"requirements": ["aioharmony==0.2.9"],
|
||||
"codeowners": [
|
||||
"@ehendrix23",
|
||||
"@bramkragten",
|
||||
|
@ -732,7 +732,12 @@ class HomeKit:
|
||||
"""Remove all pairings for an accessory so it can be repaired."""
|
||||
state = self.driver.state
|
||||
for client_uuid in list(state.paired_clients):
|
||||
state.remove_paired_client(client_uuid)
|
||||
# We need to check again since removing a single client
|
||||
# can result in removing all the clients that the client
|
||||
# granted access to if it was an admin, otherwise
|
||||
# remove_paired_client can generate a KeyError
|
||||
if client_uuid in state.paired_clients:
|
||||
state.remove_paired_client(client_uuid)
|
||||
self.driver.async_persist()
|
||||
self.driver.async_update_advertisement()
|
||||
self._async_show_setup_message()
|
||||
|
@ -446,15 +446,25 @@ class OptionsFlowHandler(config_entries.OptionsFlow):
|
||||
return await self.async_step_advanced()
|
||||
|
||||
entity_filter = self.hk_options.get(CONF_FILTER, {})
|
||||
entities = entity_filter.get(CONF_INCLUDE_ENTITIES, [])
|
||||
|
||||
all_supported_entities = _async_get_matching_entities(
|
||||
self.hass,
|
||||
domains=self.hk_options[CONF_DOMAINS],
|
||||
)
|
||||
|
||||
data_schema = {}
|
||||
entity_schema = vol.In
|
||||
entities = entity_filter.get(CONF_INCLUDE_ENTITIES, [])
|
||||
if self.hk_options[CONF_HOMEKIT_MODE] != HOMEKIT_MODE_ACCESSORY:
|
||||
# Strip out entities that no longer exist to prevent error in the UI
|
||||
valid_entities = [
|
||||
entity_id for entity_id in entities if entity_id in all_supported_entities
|
||||
]
|
||||
if self.hk_options[CONF_HOMEKIT_MODE] == HOMEKIT_MODE_ACCESSORY:
|
||||
# In accessory mode we can only have one
|
||||
default_value = valid_entities[0] if valid_entities else None
|
||||
entity_schema = vol.In
|
||||
entities_schema_required = vol.Required
|
||||
else:
|
||||
# Bridge mode
|
||||
entities_schema_required = vol.Optional
|
||||
include_exclude_mode = MODE_INCLUDE
|
||||
if not entities:
|
||||
include_exclude_mode = MODE_EXCLUDE
|
||||
@ -463,13 +473,10 @@ class OptionsFlowHandler(config_entries.OptionsFlow):
|
||||
vol.Required(CONF_INCLUDE_EXCLUDE_MODE, default=include_exclude_mode)
|
||||
] = vol.In(INCLUDE_EXCLUDE_MODES)
|
||||
entity_schema = cv.multi_select
|
||||
default_value = valid_entities
|
||||
|
||||
# Strip out entities that no longer exist to prevent error in the UI
|
||||
valid_entities = [
|
||||
entity_id for entity_id in entities if entity_id in all_supported_entities
|
||||
]
|
||||
data_schema[
|
||||
vol.Optional(CONF_ENTITIES, default=valid_entities)
|
||||
entities_schema_required(CONF_ENTITIES, default=default_value)
|
||||
] = entity_schema(all_supported_entities)
|
||||
|
||||
return self.async_show_form(
|
||||
|
@ -22,8 +22,8 @@ from .const import (
|
||||
DEFAULT_CONSIDER_HOME,
|
||||
DEFAULT_NAME,
|
||||
DOMAIN,
|
||||
MODELS_V2,
|
||||
ORBI_PORT,
|
||||
MODELS_PORT_80,
|
||||
PORT_80,
|
||||
)
|
||||
from .errors import CannotLoginException
|
||||
from .router import get_api
|
||||
@ -141,13 +141,13 @@ class NetgearFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
self._abort_if_unique_id_configured(updates=updated_data)
|
||||
|
||||
updated_data[CONF_PORT] = DEFAULT_PORT
|
||||
for model in MODELS_V2:
|
||||
for model in MODELS_PORT_80:
|
||||
if discovery_info.upnp.get(ssdp.ATTR_UPNP_MODEL_NUMBER, "").startswith(
|
||||
model
|
||||
) or discovery_info.upnp.get(ssdp.ATTR_UPNP_MODEL_NAME, "").startswith(
|
||||
model
|
||||
):
|
||||
updated_data[CONF_PORT] = ORBI_PORT
|
||||
updated_data[CONF_PORT] = PORT_80
|
||||
|
||||
self.placeholders.update(updated_data)
|
||||
self.discovered = True
|
||||
|
@ -10,8 +10,8 @@ CONF_CONSIDER_HOME = "consider_home"
|
||||
DEFAULT_CONSIDER_HOME = timedelta(seconds=180)
|
||||
DEFAULT_NAME = "Netgear router"
|
||||
|
||||
# update method V2 models
|
||||
MODELS_V2 = [
|
||||
# models using port 80 instead of 5000
|
||||
MODELS_PORT_80 = [
|
||||
"Orbi",
|
||||
"RBK",
|
||||
"RBR",
|
||||
@ -29,7 +29,25 @@ MODELS_V2 = [
|
||||
"SXR",
|
||||
"SXS",
|
||||
]
|
||||
ORBI_PORT = 80
|
||||
PORT_80 = 80
|
||||
# update method V2 models
|
||||
MODELS_V2 = [
|
||||
"Orbi",
|
||||
"RBK",
|
||||
"RBR",
|
||||
"RBS",
|
||||
"RBW",
|
||||
"LBK",
|
||||
"LBR",
|
||||
"CBK",
|
||||
"CBR",
|
||||
"SRC",
|
||||
"SRK",
|
||||
"SRS",
|
||||
"SXK",
|
||||
"SXR",
|
||||
"SXS",
|
||||
]
|
||||
|
||||
# Icons
|
||||
DEVICE_ICONS = {
|
||||
|
@ -149,6 +149,14 @@ class NetgearRouter:
|
||||
if self.model.startswith(model):
|
||||
self.method_version = 2
|
||||
|
||||
if self.method_version == 2:
|
||||
if not self._api.get_attached_devices_2():
|
||||
_LOGGER.error(
|
||||
"Netgear Model '%s' in MODELS_V2 list, but failed to get attached devices using V2",
|
||||
self.model,
|
||||
)
|
||||
self.method_version = 1
|
||||
|
||||
async def async_setup(self) -> None:
|
||||
"""Set up a Netgear router."""
|
||||
await self.hass.async_add_executor_job(self._setup)
|
||||
|
@ -3,8 +3,8 @@
|
||||
"name": "Pentair ScreenLogic",
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/screenlogic",
|
||||
"requirements": ["screenlogicpy==0.5.3"],
|
||||
"codeowners": ["@dieselrabbit"],
|
||||
"requirements": ["screenlogicpy==0.5.4"],
|
||||
"codeowners": ["@dieselrabbit", "@bdraco"],
|
||||
"dhcp": [
|
||||
{
|
||||
"hostname": "pentair: *",
|
||||
|
@ -3,7 +3,7 @@
|
||||
"name": "Sonos",
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/sonos",
|
||||
"requirements": ["soco==0.25.2"],
|
||||
"requirements": ["soco==0.25.3"],
|
||||
"dependencies": ["ssdp"],
|
||||
"after_dependencies": ["plex", "zeroconf"],
|
||||
"zeroconf": ["_sonos._tcp.local."],
|
||||
|
@ -2,7 +2,7 @@
|
||||
"domain": "switchbot",
|
||||
"name": "SwitchBot",
|
||||
"documentation": "https://www.home-assistant.io/integrations/switchbot",
|
||||
"requirements": ["PySwitchbot==0.13.0"],
|
||||
"requirements": ["PySwitchbot==0.13.2"],
|
||||
"config_flow": true,
|
||||
"codeowners": ["@danielhiversen", "@RenierM26"],
|
||||
"iot_class": "local_polling"
|
||||
|
@ -113,6 +113,7 @@ class SwitchBotBotEntity(SwitchbotEntity, SwitchEntity, RestoreEntity):
|
||||
super().__init__(coordinator, idx, mac, name)
|
||||
self._attr_unique_id = idx
|
||||
self._device = device
|
||||
self._attr_is_on = False
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""Run when entity about to be added."""
|
||||
@ -132,6 +133,7 @@ class SwitchBotBotEntity(SwitchbotEntity, SwitchEntity, RestoreEntity):
|
||||
)
|
||||
if self._last_run_success:
|
||||
self._attr_is_on = True
|
||||
self.async_write_ha_state()
|
||||
|
||||
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||
"""Turn device off."""
|
||||
@ -143,6 +145,7 @@ class SwitchBotBotEntity(SwitchbotEntity, SwitchEntity, RestoreEntity):
|
||||
)
|
||||
if self._last_run_success:
|
||||
self._attr_is_on = False
|
||||
self.async_write_ha_state()
|
||||
|
||||
@property
|
||||
def assumed_state(self) -> bool:
|
||||
|
@ -142,7 +142,7 @@ class TradfriAirPurifierFan(TradfriBaseDevice, FanEntity):
|
||||
preset_mode: str | None = None,
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
"""Turn on the fan."""
|
||||
"""Turn on the fan. Auto-mode if no argument is given."""
|
||||
if not self._device_control:
|
||||
return
|
||||
|
||||
@ -150,8 +150,8 @@ class TradfriAirPurifierFan(TradfriBaseDevice, FanEntity):
|
||||
await self._api(self._device_control.set_mode(_from_percentage(percentage)))
|
||||
return
|
||||
|
||||
if preset_mode:
|
||||
await self.async_set_preset_mode(preset_mode)
|
||||
preset_mode = preset_mode or ATTR_AUTO
|
||||
await self.async_set_preset_mode(preset_mode)
|
||||
|
||||
async def async_set_percentage(self, percentage: int) -> None:
|
||||
"""Set the speed percentage of the fan."""
|
||||
|
@ -148,8 +148,9 @@ class TuyaClimateEntity(TuyaEntity, ClimateEntity):
|
||||
):
|
||||
self._attr_temperature_unit = TEMP_CELSIUS
|
||||
if any(
|
||||
"f" in device.status.get(dpcode, "").lower()
|
||||
"f" in device.status[dpcode].lower()
|
||||
for dpcode in (DPCode.C_F, DPCode.TEMP_UNIT_CONVERT)
|
||||
if isinstance(device.status.get(dpcode), str)
|
||||
):
|
||||
self._attr_temperature_unit = TEMP_FAHRENHEIT
|
||||
|
||||
|
@ -30,6 +30,28 @@ from .const import DOMAIN, TUYA_DISCOVERY_NEW, DPCode, WorkMode
|
||||
from .util import remap_value
|
||||
|
||||
|
||||
@dataclass
|
||||
class ColorTypeData:
|
||||
"""Color Type Data."""
|
||||
|
||||
h_type: IntegerTypeData
|
||||
s_type: IntegerTypeData
|
||||
v_type: IntegerTypeData
|
||||
|
||||
|
||||
DEFAULT_COLOR_TYPE_DATA = ColorTypeData(
|
||||
h_type=IntegerTypeData(min=1, scale=0, max=360, step=1),
|
||||
s_type=IntegerTypeData(min=1, scale=0, max=255, step=1),
|
||||
v_type=IntegerTypeData(min=1, scale=0, max=255, step=1),
|
||||
)
|
||||
|
||||
DEFAULT_COLOR_TYPE_DATA_V2 = ColorTypeData(
|
||||
h_type=IntegerTypeData(min=1, scale=0, max=360, step=1),
|
||||
s_type=IntegerTypeData(min=1, scale=0, max=1000, step=1),
|
||||
v_type=IntegerTypeData(min=1, scale=0, max=1000, step=1),
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class TuyaLightEntityDescription(LightEntityDescription):
|
||||
"""Describe an Tuya light entity."""
|
||||
@ -40,6 +62,7 @@ class TuyaLightEntityDescription(LightEntityDescription):
|
||||
color_data: DPCode | tuple[DPCode, ...] | None = None
|
||||
color_mode: DPCode | None = None
|
||||
color_temp: DPCode | tuple[DPCode, ...] | None = None
|
||||
default_color_type: ColorTypeData = DEFAULT_COLOR_TYPE_DATA
|
||||
|
||||
|
||||
LIGHTS: dict[str, tuple[TuyaLightEntityDescription, ...]] = {
|
||||
@ -63,6 +86,7 @@ LIGHTS: dict[str, tuple[TuyaLightEntityDescription, ...]] = {
|
||||
brightness=DPCode.BRIGHT_VALUE,
|
||||
color_temp=DPCode.TEMP_VALUE,
|
||||
color_data=DPCode.COLOUR_DATA,
|
||||
default_color_type=DEFAULT_COLOR_TYPE_DATA_V2,
|
||||
),
|
||||
),
|
||||
# Light
|
||||
@ -242,28 +266,6 @@ LIGHTS["cz"] = LIGHTS["kg"]
|
||||
LIGHTS["pc"] = LIGHTS["kg"]
|
||||
|
||||
|
||||
@dataclass
|
||||
class ColorTypeData:
|
||||
"""Color Type Data."""
|
||||
|
||||
h_type: IntegerTypeData
|
||||
s_type: IntegerTypeData
|
||||
v_type: IntegerTypeData
|
||||
|
||||
|
||||
DEFAULT_COLOR_TYPE_DATA = ColorTypeData(
|
||||
h_type=IntegerTypeData(min=1, scale=0, max=360, step=1),
|
||||
s_type=IntegerTypeData(min=1, scale=0, max=255, step=1),
|
||||
v_type=IntegerTypeData(min=1, scale=0, max=255, step=1),
|
||||
)
|
||||
|
||||
DEFAULT_COLOR_TYPE_DATA_V2 = ColorTypeData(
|
||||
h_type=IntegerTypeData(min=1, scale=0, max=360, step=1),
|
||||
s_type=IntegerTypeData(min=1, scale=0, max=1000, step=1),
|
||||
v_type=IntegerTypeData(min=1, scale=0, max=1000, step=1),
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class ColorData:
|
||||
"""Color Data."""
|
||||
@ -443,7 +445,7 @@ class TuyaLightEntity(TuyaEntity, LightEntity):
|
||||
)
|
||||
else:
|
||||
# If no type is found, use a default one
|
||||
self._color_data_type = DEFAULT_COLOR_TYPE_DATA
|
||||
self._color_data_type = self.entity_description.default_color_type
|
||||
if self._color_data_dpcode == DPCode.COLOUR_DATA_V2 or (
|
||||
self._brightness_type and self._brightness_type.max > 255
|
||||
):
|
||||
|
@ -2,7 +2,7 @@
|
||||
"domain": "waze_travel_time",
|
||||
"name": "Waze Travel Time",
|
||||
"documentation": "https://www.home-assistant.io/integrations/waze_travel_time",
|
||||
"requirements": ["WazeRouteCalculator==0.13"],
|
||||
"requirements": ["WazeRouteCalculator==0.14"],
|
||||
"codeowners": [],
|
||||
"config_flow": true,
|
||||
"iot_class": "cloud_polling"
|
||||
|
@ -7,6 +7,7 @@ from homeassistant.components.number import NumberEntity, NumberEntityDescriptio
|
||||
from homeassistant.components.number.const import DOMAIN as PLATFORM_DOMAIN
|
||||
from homeassistant.const import DEGREE, ENTITY_CATEGORY_CONFIG, TIME_MINUTES
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
|
||||
from .const import (
|
||||
CONF_DEVICE,
|
||||
@ -251,7 +252,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
for feature, description in NUMBER_TYPES.items():
|
||||
if feature == FEATURE_SET_LED_BRIGHTNESS and model != MODEL_FAN_ZA5:
|
||||
# Delete LED bightness entity created by mistake if it exists
|
||||
entity_reg = hass.helpers.entity_registry.async_get()
|
||||
entity_reg = er.async_get(hass)
|
||||
entity_id = entity_reg.async_get_entity_id(
|
||||
PLATFORM_DOMAIN, DOMAIN, f"{description.key}_{config_entry.unique_id}"
|
||||
)
|
||||
|
@ -14,7 +14,14 @@ from zwave_js_server.util.command_class.meter import get_meter_type
|
||||
|
||||
from homeassistant.components.lock import DOMAIN as LOCK_DOMAIN
|
||||
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
|
||||
from homeassistant.const import CONF_DEVICE_ID, CONF_DOMAIN, CONF_ENTITY_ID, CONF_TYPE
|
||||
from homeassistant.const import (
|
||||
ATTR_DEVICE_ID,
|
||||
ATTR_DOMAIN,
|
||||
CONF_DEVICE_ID,
|
||||
CONF_DOMAIN,
|
||||
CONF_ENTITY_ID,
|
||||
CONF_TYPE,
|
||||
)
|
||||
from homeassistant.core import Context, HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import entity_registry
|
||||
@ -227,7 +234,22 @@ async def async_call_action_from_config(
|
||||
if action_type not in ACTION_TYPES:
|
||||
raise HomeAssistantError(f"Unhandled action type {action_type}")
|
||||
|
||||
service_data = {k: v for k, v in config.items() if v not in (None, "")}
|
||||
# Don't include domain, subtype or any null/empty values in the service call
|
||||
service_data = {
|
||||
k: v
|
||||
for k, v in config.items()
|
||||
if k not in (ATTR_DOMAIN, CONF_SUBTYPE) and v not in (None, "")
|
||||
}
|
||||
|
||||
# Entity services (including refresh value which is a fake entity service) expects
|
||||
# just an entity ID
|
||||
if action_type in (
|
||||
SERVICE_REFRESH_VALUE,
|
||||
SERVICE_SET_LOCK_USERCODE,
|
||||
SERVICE_CLEAR_LOCK_USERCODE,
|
||||
SERVICE_RESET_METER,
|
||||
):
|
||||
service_data.pop(ATTR_DEVICE_ID)
|
||||
await hass.services.async_call(
|
||||
DOMAIN, service, service_data, blocking=True, context=context
|
||||
)
|
||||
@ -283,7 +305,10 @@ async def async_get_action_capabilities(
|
||||
"extra_fields": vol.Schema(
|
||||
{
|
||||
vol.Required(ATTR_COMMAND_CLASS): vol.In(
|
||||
{cc.value: cc.name for cc in CommandClass}
|
||||
{
|
||||
CommandClass(cc.id).value: cc.name
|
||||
for cc in sorted(node.command_classes, key=lambda cc: cc.name) # type: ignore[no-any-return]
|
||||
}
|
||||
),
|
||||
vol.Required(ATTR_PROPERTY): cv.string,
|
||||
vol.Optional(ATTR_PROPERTY_KEY): cv.string,
|
||||
|
@ -7,7 +7,7 @@ from homeassistant.backports.enum import StrEnum
|
||||
|
||||
MAJOR_VERSION: Final = 2021
|
||||
MINOR_VERSION: Final = 12
|
||||
PATCH_VERSION: Final = "8"
|
||||
PATCH_VERSION: Final = "9"
|
||||
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
||||
__version__: Final = f"{__short_version__}.{PATCH_VERSION}"
|
||||
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 8, 0)
|
||||
|
@ -15,8 +15,8 @@ certifi>=2021.5.30
|
||||
ciso8601==2.2.0
|
||||
cryptography==35.0.0
|
||||
emoji==1.5.0
|
||||
hass-nabucasa==0.50.0
|
||||
home-assistant-frontend==20211229.0
|
||||
hass-nabucasa==0.51.0
|
||||
home-assistant-frontend==20211229.1
|
||||
httpx==0.21.0
|
||||
ifaddr==0.1.7
|
||||
jinja2==3.0.3
|
||||
|
@ -46,7 +46,7 @@ PyRMVtransport==0.3.3
|
||||
PySocks==1.7.1
|
||||
|
||||
# homeassistant.components.switchbot
|
||||
# PySwitchbot==0.13.0
|
||||
# PySwitchbot==0.13.2
|
||||
|
||||
# homeassistant.components.transport_nsw
|
||||
PyTransportNSW==0.1.1
|
||||
@ -83,7 +83,7 @@ TwitterAPI==2.7.5
|
||||
WSDiscovery==2.0.0
|
||||
|
||||
# homeassistant.components.waze_travel_time
|
||||
WazeRouteCalculator==0.13
|
||||
WazeRouteCalculator==0.14
|
||||
|
||||
# homeassistant.components.abode
|
||||
abodepy==1.2.0
|
||||
@ -177,7 +177,7 @@ aiogithubapi==21.11.0
|
||||
aioguardian==2021.11.0
|
||||
|
||||
# homeassistant.components.harmony
|
||||
aioharmony==0.2.8
|
||||
aioharmony==0.2.9
|
||||
|
||||
# homeassistant.components.homekit_controller
|
||||
aiohomekit==0.6.4
|
||||
@ -659,7 +659,7 @@ fjaraskupan==1.0.2
|
||||
flipr-api==1.4.1
|
||||
|
||||
# homeassistant.components.flux_led
|
||||
flux_led==0.27.32
|
||||
flux_led==0.27.45
|
||||
|
||||
# homeassistant.components.homekit
|
||||
fnvhash==0.1.0
|
||||
@ -787,7 +787,7 @@ habitipy==0.2.0
|
||||
hangups==0.4.14
|
||||
|
||||
# homeassistant.components.cloud
|
||||
hass-nabucasa==0.50.0
|
||||
hass-nabucasa==0.51.0
|
||||
|
||||
# homeassistant.components.splunk
|
||||
hass_splunk==0.1.1
|
||||
@ -820,7 +820,7 @@ hole==0.7.0
|
||||
holidays==0.11.3.1
|
||||
|
||||
# homeassistant.components.frontend
|
||||
home-assistant-frontend==20211229.0
|
||||
home-assistant-frontend==20211229.1
|
||||
|
||||
# homeassistant.components.zwave
|
||||
homeassistant-pyozw==0.1.10
|
||||
@ -1397,7 +1397,7 @@ pycfdns==1.2.2
|
||||
pychannels==1.0.0
|
||||
|
||||
# homeassistant.components.cast
|
||||
pychromecast==10.2.2
|
||||
pychromecast==10.2.3
|
||||
|
||||
# homeassistant.components.pocketcasts
|
||||
pycketcasts==1.0.0
|
||||
@ -2113,7 +2113,7 @@ scapy==2.4.5
|
||||
schiene==0.23
|
||||
|
||||
# homeassistant.components.screenlogic
|
||||
screenlogicpy==0.5.3
|
||||
screenlogicpy==0.5.4
|
||||
|
||||
# homeassistant.components.scsgate
|
||||
scsgate==0.1.0
|
||||
@ -2185,7 +2185,7 @@ smhi-pkg==1.0.15
|
||||
snapcast==2.1.3
|
||||
|
||||
# homeassistant.components.sonos
|
||||
soco==0.25.2
|
||||
soco==0.25.3
|
||||
|
||||
# homeassistant.components.solaredge_local
|
||||
solaredge-local==0.2.0
|
||||
@ -2466,7 +2466,7 @@ xs1-api-client==3.0.0
|
||||
yalesmartalarmclient==0.3.4
|
||||
|
||||
# homeassistant.components.august
|
||||
yalexs==1.1.13
|
||||
yalexs==1.1.17
|
||||
|
||||
# homeassistant.components.yeelight
|
||||
yeelight==0.7.8
|
||||
|
@ -24,7 +24,7 @@ PyQRCode==1.2.1
|
||||
PyRMVtransport==0.3.3
|
||||
|
||||
# homeassistant.components.switchbot
|
||||
# PySwitchbot==0.13.0
|
||||
# PySwitchbot==0.13.2
|
||||
|
||||
# homeassistant.components.transport_nsw
|
||||
PyTransportNSW==0.1.1
|
||||
@ -45,7 +45,7 @@ RtmAPI==0.7.2
|
||||
WSDiscovery==2.0.0
|
||||
|
||||
# homeassistant.components.waze_travel_time
|
||||
WazeRouteCalculator==0.13
|
||||
WazeRouteCalculator==0.14
|
||||
|
||||
# homeassistant.components.abode
|
||||
abodepy==1.2.0
|
||||
@ -121,7 +121,7 @@ aioflo==2021.11.0
|
||||
aioguardian==2021.11.0
|
||||
|
||||
# homeassistant.components.harmony
|
||||
aioharmony==0.2.8
|
||||
aioharmony==0.2.9
|
||||
|
||||
# homeassistant.components.homekit_controller
|
||||
aiohomekit==0.6.4
|
||||
@ -399,7 +399,7 @@ fjaraskupan==1.0.2
|
||||
flipr-api==1.4.1
|
||||
|
||||
# homeassistant.components.flux_led
|
||||
flux_led==0.27.32
|
||||
flux_led==0.27.45
|
||||
|
||||
# homeassistant.components.homekit
|
||||
fnvhash==0.1.0
|
||||
@ -494,7 +494,7 @@ habitipy==0.2.0
|
||||
hangups==0.4.14
|
||||
|
||||
# homeassistant.components.cloud
|
||||
hass-nabucasa==0.50.0
|
||||
hass-nabucasa==0.51.0
|
||||
|
||||
# homeassistant.components.tasmota
|
||||
hatasmota==0.3.1
|
||||
@ -515,7 +515,7 @@ hole==0.7.0
|
||||
holidays==0.11.3.1
|
||||
|
||||
# homeassistant.components.frontend
|
||||
home-assistant-frontend==20211229.0
|
||||
home-assistant-frontend==20211229.1
|
||||
|
||||
# homeassistant.components.zwave
|
||||
homeassistant-pyozw==0.1.10
|
||||
@ -850,7 +850,7 @@ pybotvac==0.0.22
|
||||
pycfdns==1.2.2
|
||||
|
||||
# homeassistant.components.cast
|
||||
pychromecast==10.2.2
|
||||
pychromecast==10.2.3
|
||||
|
||||
# homeassistant.components.climacell
|
||||
pyclimacell==0.18.2
|
||||
@ -1257,7 +1257,7 @@ samsungtvws==1.6.0
|
||||
scapy==2.4.5
|
||||
|
||||
# homeassistant.components.screenlogic
|
||||
screenlogicpy==0.5.3
|
||||
screenlogicpy==0.5.4
|
||||
|
||||
# homeassistant.components.emulated_kasa
|
||||
# homeassistant.components.sense
|
||||
@ -1291,7 +1291,7 @@ smarthab==0.21
|
||||
smhi-pkg==1.0.15
|
||||
|
||||
# homeassistant.components.sonos
|
||||
soco==0.25.2
|
||||
soco==0.25.3
|
||||
|
||||
# homeassistant.components.solaredge
|
||||
solaredge==0.0.2
|
||||
@ -1464,7 +1464,7 @@ xmltodict==0.12.0
|
||||
yalesmartalarmclient==0.3.4
|
||||
|
||||
# homeassistant.components.august
|
||||
yalexs==1.1.13
|
||||
yalexs==1.1.17
|
||||
|
||||
# homeassistant.components.yeelight
|
||||
yeelight==0.7.8
|
||||
|
@ -2,8 +2,6 @@
|
||||
import json
|
||||
import os
|
||||
import time
|
||||
|
||||
# from unittest.mock import AsyncMock
|
||||
from unittest.mock import AsyncMock, MagicMock, PropertyMock, patch
|
||||
|
||||
from yalexs.activity import (
|
||||
@ -207,6 +205,8 @@ async def _mock_setup_august_with_api_side_effects(hass, api_call_side_effects,
|
||||
side_effect=api_call_side_effects["unlock_return_activities"]
|
||||
)
|
||||
|
||||
api_instance.async_unlock_async = AsyncMock()
|
||||
api_instance.async_lock_async = AsyncMock()
|
||||
api_instance.async_get_user = AsyncMock(return_value={"UserID": "abc"})
|
||||
|
||||
return await _mock_setup_august(hass, api_instance, pubnub)
|
||||
|
@ -1,5 +1,6 @@
|
||||
"""The binary_sensor tests for the august platform."""
|
||||
import datetime
|
||||
import time
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
from yalexs.pubnub_async import AugustPubNub
|
||||
@ -26,6 +27,10 @@ from tests.components.august.mocks import (
|
||||
)
|
||||
|
||||
|
||||
def _timetoken():
|
||||
return str(time.time_ns())[:-2]
|
||||
|
||||
|
||||
async def test_doorsense(hass):
|
||||
"""Test creation of a lock with doorsense and bridge."""
|
||||
lock_one = await _mock_lock_from_fixture(
|
||||
@ -85,6 +90,10 @@ async def test_create_doorbell(hass):
|
||||
"binary_sensor.k98gidt45gul_name_motion"
|
||||
)
|
||||
assert binary_sensor_k98gidt45gul_name_motion.state == STATE_OFF
|
||||
binary_sensor_k98gidt45gul_name_image_capture = hass.states.get(
|
||||
"binary_sensor.k98gidt45gul_name_image_capture"
|
||||
)
|
||||
assert binary_sensor_k98gidt45gul_name_image_capture.state == STATE_OFF
|
||||
binary_sensor_k98gidt45gul_name_online = hass.states.get(
|
||||
"binary_sensor.k98gidt45gul_name_online"
|
||||
)
|
||||
@ -97,6 +106,10 @@ async def test_create_doorbell(hass):
|
||||
"binary_sensor.k98gidt45gul_name_motion"
|
||||
)
|
||||
assert binary_sensor_k98gidt45gul_name_motion.state == STATE_OFF
|
||||
binary_sensor_k98gidt45gul_name_image_capture = hass.states.get(
|
||||
"binary_sensor.k98gidt45gul_name_image_capture"
|
||||
)
|
||||
assert binary_sensor_k98gidt45gul_name_image_capture.state == STATE_OFF
|
||||
|
||||
|
||||
async def test_create_doorbell_offline(hass):
|
||||
@ -171,7 +184,7 @@ async def test_doorbell_update_via_pubnub(hass):
|
||||
pubnub,
|
||||
Mock(
|
||||
channel=doorbell_one.pubsub_channel,
|
||||
timetoken=dt_util.utcnow().timestamp() * 10000000,
|
||||
timetoken=_timetoken(),
|
||||
message={
|
||||
"status": "imagecapture",
|
||||
"data": {
|
||||
@ -186,10 +199,46 @@ async def test_doorbell_update_via_pubnub(hass):
|
||||
|
||||
await hass.async_block_till_done()
|
||||
|
||||
binary_sensor_k98gidt45gul_name_image_capture = hass.states.get(
|
||||
"binary_sensor.k98gidt45gul_name_image_capture"
|
||||
)
|
||||
assert binary_sensor_k98gidt45gul_name_image_capture.state == STATE_ON
|
||||
|
||||
pubnub.message(
|
||||
pubnub,
|
||||
Mock(
|
||||
channel=doorbell_one.pubsub_channel,
|
||||
timetoken=_timetoken(),
|
||||
message={
|
||||
"status": "doorbell_motion_detected",
|
||||
"data": {
|
||||
"event": "doorbell_motion_detected",
|
||||
"image": {
|
||||
"height": 640,
|
||||
"width": 480,
|
||||
"format": "jpg",
|
||||
"created_at": "2021-03-16T02:36:26.886Z",
|
||||
"bytes": 14061,
|
||||
"secure_url": "https://dyu7azbnaoi74.cloudfront.net/images/1f8.jpeg",
|
||||
"url": "https://dyu7azbnaoi74.cloudfront.net/images/1f8.jpeg",
|
||||
"etag": "09e839331c4ea59eef28081f2caa0e90",
|
||||
},
|
||||
"doorbellName": "Front Door",
|
||||
"callID": None,
|
||||
"origin": "mars-api",
|
||||
"mutableContent": True,
|
||||
},
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
await hass.async_block_till_done()
|
||||
|
||||
binary_sensor_k98gidt45gul_name_motion = hass.states.get(
|
||||
"binary_sensor.k98gidt45gul_name_motion"
|
||||
)
|
||||
assert binary_sensor_k98gidt45gul_name_motion.state == STATE_ON
|
||||
|
||||
binary_sensor_k98gidt45gul_name_ding = hass.states.get(
|
||||
"binary_sensor.k98gidt45gul_name_ding"
|
||||
)
|
||||
@ -204,16 +253,16 @@ async def test_doorbell_update_via_pubnub(hass):
|
||||
async_fire_time_changed(hass, new_time)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
binary_sensor_k98gidt45gul_name_motion = hass.states.get(
|
||||
"binary_sensor.k98gidt45gul_name_motion"
|
||||
binary_sensor_k98gidt45gul_name_image_capture = hass.states.get(
|
||||
"binary_sensor.k98gidt45gul_name_image_capture"
|
||||
)
|
||||
assert binary_sensor_k98gidt45gul_name_motion.state == STATE_OFF
|
||||
assert binary_sensor_k98gidt45gul_name_image_capture.state == STATE_OFF
|
||||
|
||||
pubnub.message(
|
||||
pubnub,
|
||||
Mock(
|
||||
channel=doorbell_one.pubsub_channel,
|
||||
timetoken=dt_util.utcnow().timestamp() * 10000000,
|
||||
timetoken=_timetoken(),
|
||||
message={
|
||||
"status": "buttonpush",
|
||||
},
|
||||
@ -274,7 +323,7 @@ async def test_door_sense_update_via_pubnub(hass):
|
||||
pubnub,
|
||||
Mock(
|
||||
channel=lock_one.pubsub_channel,
|
||||
timetoken=dt_util.utcnow().timestamp() * 10000000,
|
||||
timetoken=_timetoken(),
|
||||
message={"status": "kAugLockState_Unlocking", "doorState": "closed"},
|
||||
),
|
||||
)
|
||||
@ -289,11 +338,10 @@ async def test_door_sense_update_via_pubnub(hass):
|
||||
pubnub,
|
||||
Mock(
|
||||
channel=lock_one.pubsub_channel,
|
||||
timetoken=dt_util.utcnow().timestamp() * 10000000,
|
||||
timetoken=_timetoken(),
|
||||
message={"status": "kAugLockState_Locking", "doorState": "open"},
|
||||
),
|
||||
)
|
||||
|
||||
await hass.async_block_till_done()
|
||||
binary_sensor_online_with_doorsense_name = hass.states.get(
|
||||
"binary_sensor.online_with_doorsense_name_open"
|
||||
@ -327,7 +375,7 @@ async def test_door_sense_update_via_pubnub(hass):
|
||||
pubnub,
|
||||
Mock(
|
||||
channel=lock_one.pubsub_channel,
|
||||
timetoken=dt_util.utcnow().timestamp() * 10000000,
|
||||
timetoken=_timetoken(),
|
||||
message={"status": "kAugLockState_Unlocking", "doorState": "open"},
|
||||
),
|
||||
)
|
||||
|
@ -154,6 +154,86 @@ async def test_one_lock_operation(hass):
|
||||
)
|
||||
|
||||
|
||||
async def test_one_lock_operation_pubnub_connected(hass):
|
||||
"""Test lock and unlock operations are async when pubnub is connected."""
|
||||
lock_one = await _mock_doorsense_enabled_august_lock_detail(hass)
|
||||
assert lock_one.pubsub_channel == "pubsub"
|
||||
|
||||
pubnub = AugustPubNub()
|
||||
await _create_august_with_devices(hass, [lock_one], pubnub=pubnub)
|
||||
pubnub.connected = True
|
||||
|
||||
lock_online_with_doorsense_name = hass.states.get("lock.online_with_doorsense_name")
|
||||
|
||||
assert lock_online_with_doorsense_name.state == STATE_LOCKED
|
||||
|
||||
assert lock_online_with_doorsense_name.attributes.get("battery_level") == 92
|
||||
assert (
|
||||
lock_online_with_doorsense_name.attributes.get("friendly_name")
|
||||
== "online_with_doorsense Name"
|
||||
)
|
||||
|
||||
data = {ATTR_ENTITY_ID: "lock.online_with_doorsense_name"}
|
||||
assert await hass.services.async_call(
|
||||
LOCK_DOMAIN, SERVICE_UNLOCK, data, blocking=True
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
pubnub.message(
|
||||
pubnub,
|
||||
Mock(
|
||||
channel=lock_one.pubsub_channel,
|
||||
timetoken=(dt_util.utcnow().timestamp() + 1) * 10000000,
|
||||
message={
|
||||
"status": "kAugLockState_Unlocked",
|
||||
},
|
||||
),
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
await hass.async_block_till_done()
|
||||
|
||||
lock_online_with_doorsense_name = hass.states.get("lock.online_with_doorsense_name")
|
||||
assert lock_online_with_doorsense_name.state == STATE_UNLOCKED
|
||||
|
||||
assert lock_online_with_doorsense_name.attributes.get("battery_level") == 92
|
||||
assert (
|
||||
lock_online_with_doorsense_name.attributes.get("friendly_name")
|
||||
== "online_with_doorsense Name"
|
||||
)
|
||||
|
||||
assert await hass.services.async_call(
|
||||
LOCK_DOMAIN, SERVICE_LOCK, data, blocking=True
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
pubnub.message(
|
||||
pubnub,
|
||||
Mock(
|
||||
channel=lock_one.pubsub_channel,
|
||||
timetoken=(dt_util.utcnow().timestamp() + 2) * 10000000,
|
||||
message={
|
||||
"status": "kAugLockState_Locked",
|
||||
},
|
||||
),
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
await hass.async_block_till_done()
|
||||
|
||||
lock_online_with_doorsense_name = hass.states.get("lock.online_with_doorsense_name")
|
||||
assert lock_online_with_doorsense_name.state == STATE_LOCKED
|
||||
|
||||
# No activity means it will be unavailable until the activity feed has data
|
||||
entity_registry = er.async_get(hass)
|
||||
lock_operator_sensor = entity_registry.async_get(
|
||||
"sensor.online_with_doorsense_name_operator"
|
||||
)
|
||||
assert lock_operator_sensor
|
||||
assert (
|
||||
hass.states.get("sensor.online_with_doorsense_name_operator").state
|
||||
== STATE_UNKNOWN
|
||||
)
|
||||
|
||||
|
||||
async def test_lock_jammed(hass):
|
||||
"""Test lock gets jammed on unlock."""
|
||||
|
||||
@ -273,6 +353,7 @@ async def test_lock_update_via_pubnub(hass):
|
||||
config_entry = await _create_august_with_devices(
|
||||
hass, [lock_one], activities=activities, pubnub=pubnub
|
||||
)
|
||||
pubnub.connected = True
|
||||
|
||||
lock_online_with_doorsense_name = hass.states.get("lock.online_with_doorsense_name")
|
||||
|
||||
|
@ -15,6 +15,7 @@ from homeassistant.components.alexa.entities import LightCapabilities
|
||||
from homeassistant.components.cloud.const import DOMAIN, RequireRelink
|
||||
from homeassistant.components.google_assistant.helpers import GoogleEntity
|
||||
from homeassistant.core import State
|
||||
from homeassistant.util.location import LocationInfo
|
||||
|
||||
from . import mock_cloud, mock_cloud_prefs
|
||||
|
||||
@ -203,16 +204,60 @@ async def test_logout_view_unknown_error(hass, cloud_client):
|
||||
assert req.status == HTTPStatus.BAD_GATEWAY
|
||||
|
||||
|
||||
async def test_register_view(mock_cognito, cloud_client):
|
||||
"""Test logging out."""
|
||||
req = await cloud_client.post(
|
||||
"/api/cloud/register", json={"email": "hello@bla.com", "password": "falcon42"}
|
||||
)
|
||||
async def test_register_view_no_location(mock_cognito, cloud_client):
|
||||
"""Test register without location."""
|
||||
with patch(
|
||||
"homeassistant.components.cloud.http_api.async_detect_location_info",
|
||||
return_value=None,
|
||||
):
|
||||
req = await cloud_client.post(
|
||||
"/api/cloud/register",
|
||||
json={"email": "hello@bla.com", "password": "falcon42"},
|
||||
)
|
||||
assert req.status == HTTPStatus.OK
|
||||
assert len(mock_cognito.register.mock_calls) == 1
|
||||
result_email, result_pass = mock_cognito.register.mock_calls[0][1]
|
||||
call = mock_cognito.register.mock_calls[0]
|
||||
result_email, result_pass = call.args
|
||||
assert result_email == "hello@bla.com"
|
||||
assert result_pass == "falcon42"
|
||||
assert call.kwargs["client_metadata"] is None
|
||||
|
||||
|
||||
async def test_register_view_with_location(mock_cognito, cloud_client):
|
||||
"""Test register with location."""
|
||||
with patch(
|
||||
"homeassistant.components.cloud.http_api.async_detect_location_info",
|
||||
return_value=LocationInfo(
|
||||
**{
|
||||
"country_code": "XX",
|
||||
"zip_code": "12345",
|
||||
"region_code": "GH",
|
||||
"ip": "1.2.3.4",
|
||||
"city": "Gotham",
|
||||
"region_name": "Gotham",
|
||||
"time_zone": "Earth/Gotham",
|
||||
"currency": "XXX",
|
||||
"latitude": "12.34567",
|
||||
"longitude": "12.34567",
|
||||
"use_metric": True,
|
||||
}
|
||||
),
|
||||
):
|
||||
req = await cloud_client.post(
|
||||
"/api/cloud/register",
|
||||
json={"email": "hello@bla.com", "password": "falcon42"},
|
||||
)
|
||||
assert req.status == HTTPStatus.OK
|
||||
assert len(mock_cognito.register.mock_calls) == 1
|
||||
call = mock_cognito.register.mock_calls[0]
|
||||
result_email, result_pass = call.args
|
||||
assert result_email == "hello@bla.com"
|
||||
assert result_pass == "falcon42"
|
||||
assert call.kwargs["client_metadata"] == {
|
||||
"NC_COUNTRY_CODE": "XX",
|
||||
"NC_REGION_CODE": "GH",
|
||||
"NC_ZIP_CODE": "12345",
|
||||
}
|
||||
|
||||
|
||||
async def test_register_view_bad_data(mock_cognito, cloud_client):
|
||||
|
@ -7,6 +7,8 @@ from homeassistant.config_entries import SOURCE_IGNORE, SOURCE_IMPORT
|
||||
from homeassistant.const import CONF_NAME, CONF_PORT
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
from .util import PATH_HOMEKIT, async_init_entry
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
|
||||
@ -1065,11 +1067,13 @@ async def test_options_flow_blocked_when_from_yaml(hass, mock_get_source_ip):
|
||||
assert result2["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
||||
|
||||
|
||||
async def test_options_flow_include_mode_basic_accessory(hass, mock_get_source_ip):
|
||||
@patch(f"{PATH_HOMEKIT}.async_port_is_available", return_value=True)
|
||||
async def test_options_flow_include_mode_basic_accessory(
|
||||
port_mock, hass, mock_get_source_ip, hk_driver, mock_async_zeroconf
|
||||
):
|
||||
"""Test config flow options in include mode with a single accessory."""
|
||||
|
||||
config_entry = _mock_config_entry_with_options_populated()
|
||||
config_entry.add_to_hass(hass)
|
||||
await async_init_entry(hass, config_entry)
|
||||
|
||||
hass.states.async_set("media_player.tv", "off")
|
||||
hass.states.async_set("media_player.sonos", "off")
|
||||
@ -1101,7 +1105,48 @@ async def test_options_flow_include_mode_basic_accessory(hass, mock_get_source_i
|
||||
|
||||
assert result2["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||
assert result2["step_id"] == "include_exclude"
|
||||
assert _get_schema_default(result2["data_schema"].schema, "entities") == []
|
||||
assert _get_schema_default(result2["data_schema"].schema, "entities") is None
|
||||
|
||||
result3 = await hass.config_entries.options.async_configure(
|
||||
result2["flow_id"],
|
||||
user_input={"entities": "media_player.tv"},
|
||||
)
|
||||
assert result3["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
||||
assert config_entry.options == {
|
||||
"mode": "accessory",
|
||||
"filter": {
|
||||
"exclude_domains": [],
|
||||
"exclude_entities": [],
|
||||
"include_domains": [],
|
||||
"include_entities": ["media_player.tv"],
|
||||
},
|
||||
}
|
||||
|
||||
# Now we check again to make sure the single entity is still
|
||||
# preselected
|
||||
|
||||
result = await hass.config_entries.options.async_init(
|
||||
config_entry.entry_id, context={"show_advanced_options": False}
|
||||
)
|
||||
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||
assert result["step_id"] == "init"
|
||||
assert result["data_schema"]({}) == {
|
||||
"domains": ["media_player"],
|
||||
"mode": "accessory",
|
||||
}
|
||||
|
||||
result2 = await hass.config_entries.options.async_configure(
|
||||
result["flow_id"],
|
||||
user_input={"domains": ["media_player"], "mode": "accessory"},
|
||||
)
|
||||
|
||||
assert result2["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||
assert result2["step_id"] == "include_exclude"
|
||||
assert (
|
||||
_get_schema_default(result2["data_schema"].schema, "entities")
|
||||
== "media_player.tv"
|
||||
)
|
||||
|
||||
result3 = await hass.config_entries.options.async_configure(
|
||||
result2["flow_id"],
|
||||
|
@ -683,6 +683,11 @@ async def test_homekit_unpair(hass, device_reg, mock_async_zeroconf):
|
||||
|
||||
state = homekit.driver.state
|
||||
state.add_paired_client("client1", "any", b"1")
|
||||
state.add_paired_client("client2", "any", b"0")
|
||||
state.add_paired_client("client3", "any", b"1")
|
||||
state.add_paired_client("client4", "any", b"0")
|
||||
state.add_paired_client("client5", "any", b"0")
|
||||
|
||||
formatted_mac = device_registry.format_mac(state.mac)
|
||||
hk_bridge_dev = device_reg.async_get_device(
|
||||
{}, {(device_registry.CONNECTION_NETWORK_MAC, formatted_mac)}
|
||||
|
@ -6,7 +6,7 @@ import pytest
|
||||
|
||||
from homeassistant import data_entry_flow
|
||||
from homeassistant.components import ssdp
|
||||
from homeassistant.components.netgear.const import CONF_CONSIDER_HOME, DOMAIN, ORBI_PORT
|
||||
from homeassistant.components.netgear.const import CONF_CONSIDER_HOME, DOMAIN, PORT_80
|
||||
from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_SSDP, SOURCE_USER
|
||||
from homeassistant.const import (
|
||||
CONF_HOST,
|
||||
@ -252,7 +252,7 @@ async def test_ssdp(hass, service):
|
||||
assert result["result"].unique_id == SERIAL
|
||||
assert result["title"] == TITLE
|
||||
assert result["data"].get(CONF_HOST) == HOST
|
||||
assert result["data"].get(CONF_PORT) == ORBI_PORT
|
||||
assert result["data"].get(CONF_PORT) == PORT_80
|
||||
assert result["data"].get(CONF_SSL) == SSL
|
||||
assert result["data"].get(CONF_USERNAME) == DEFAULT_USER
|
||||
assert result["data"][CONF_PASSWORD] == PASSWORD
|
||||
|
@ -57,7 +57,128 @@
|
||||
},
|
||||
{ "nodeId": 13, "index": 2 }
|
||||
],
|
||||
"commandClasses": [],
|
||||
"commandClasses": [
|
||||
{
|
||||
"id": 49,
|
||||
"name": "Multilevel Sensor",
|
||||
"version": 5,
|
||||
"isSecure": false
|
||||
},
|
||||
{
|
||||
"id": 64,
|
||||
"name": "Thermostat Mode",
|
||||
"version": 2,
|
||||
"isSecure": false
|
||||
},
|
||||
{
|
||||
"id": 66,
|
||||
"name": "Thermostat Operating State",
|
||||
"version": 2,
|
||||
"isSecure": false
|
||||
},
|
||||
{
|
||||
"id": 67,
|
||||
"name": "Thermostat Setpoint",
|
||||
"version": 2,
|
||||
"isSecure": false
|
||||
},
|
||||
{
|
||||
"id": 68,
|
||||
"name": "Thermostat Fan Mode",
|
||||
"version": 1,
|
||||
"isSecure": false
|
||||
},
|
||||
{
|
||||
"id": 69,
|
||||
"name": "Thermostat Fan State",
|
||||
"version": 1,
|
||||
"isSecure": false
|
||||
},
|
||||
{
|
||||
"id": 89,
|
||||
"name": "Association Group Information",
|
||||
"version": 1,
|
||||
"isSecure": false
|
||||
},
|
||||
{
|
||||
"id": 90,
|
||||
"name": "Device Reset Locally",
|
||||
"version": 1,
|
||||
"isSecure": false
|
||||
},
|
||||
{
|
||||
"id": 94,
|
||||
"name": "Z-Wave Plus Info",
|
||||
"version": 2,
|
||||
"isSecure": false
|
||||
},
|
||||
{
|
||||
"id": 96,
|
||||
"name": "Multi Channel",
|
||||
"version": 4,
|
||||
"isSecure": false
|
||||
},
|
||||
{
|
||||
"id": 112,
|
||||
"name": "Configuration",
|
||||
"version": 1,
|
||||
"isSecure": false
|
||||
},
|
||||
{
|
||||
"id": 114,
|
||||
"name": "Manufacturer Specific",
|
||||
"version": 2,
|
||||
"isSecure": false
|
||||
},
|
||||
{
|
||||
"id": 115,
|
||||
"name": "Powerlevel",
|
||||
"version": 1,
|
||||
"isSecure": false
|
||||
},
|
||||
{
|
||||
"id": 122,
|
||||
"name": "Firmware Update Meta Data",
|
||||
"version": 3,
|
||||
"isSecure": false
|
||||
},
|
||||
{
|
||||
"id": 128,
|
||||
"name": "Battery",
|
||||
"version": 1,
|
||||
"isSecure": false
|
||||
},
|
||||
{
|
||||
"id": 129,
|
||||
"name": "Clock",
|
||||
"version": 1,
|
||||
"isSecure": false
|
||||
},
|
||||
{
|
||||
"id": 133,
|
||||
"name": "Association",
|
||||
"version": 2,
|
||||
"isSecure": false
|
||||
},
|
||||
{
|
||||
"id": 134,
|
||||
"name": "Version",
|
||||
"version": 2,
|
||||
"isSecure": false
|
||||
},
|
||||
{
|
||||
"id": 135,
|
||||
"name": "Indicator",
|
||||
"version": 1,
|
||||
"isSecure": false
|
||||
},
|
||||
{
|
||||
"id": 142,
|
||||
"name": "Multi Channel Association",
|
||||
"version": 3,
|
||||
"isSecure": false
|
||||
}
|
||||
],
|
||||
"values": [
|
||||
{
|
||||
"commandClassName": "Manufacturer Specific",
|
||||
|
@ -1,4 +1,6 @@
|
||||
"""The tests for Z-Wave JS device actions."""
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
import voluptuous_serialize
|
||||
from zwave_js_server.client import Client
|
||||
@ -14,7 +16,7 @@ from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import config_validation as cv, device_registry
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
from tests.common import async_get_device_automations, async_mock_service
|
||||
from tests.common import async_get_device_automations
|
||||
|
||||
|
||||
async def test_get_actions(
|
||||
@ -87,8 +89,130 @@ async def test_get_actions_meter(
|
||||
assert len(filtered_actions) > 0
|
||||
|
||||
|
||||
async def test_action(hass: HomeAssistant) -> None:
|
||||
"""Test for turn_on and turn_off actions."""
|
||||
async def test_actions(
|
||||
hass: HomeAssistant,
|
||||
client: Client,
|
||||
climate_radio_thermostat_ct100_plus: Node,
|
||||
integration: ConfigEntry,
|
||||
) -> None:
|
||||
"""Test actions."""
|
||||
node = climate_radio_thermostat_ct100_plus
|
||||
device_id = get_device_id(client, node)
|
||||
dev_reg = device_registry.async_get(hass)
|
||||
device = dev_reg.async_get_device({device_id})
|
||||
assert device
|
||||
|
||||
assert await async_setup_component(
|
||||
hass,
|
||||
automation.DOMAIN,
|
||||
{
|
||||
automation.DOMAIN: [
|
||||
{
|
||||
"trigger": {
|
||||
"platform": "event",
|
||||
"event_type": "test_event_refresh_value",
|
||||
},
|
||||
"action": {
|
||||
"domain": DOMAIN,
|
||||
"type": "refresh_value",
|
||||
"device_id": device.id,
|
||||
"entity_id": "climate.z_wave_thermostat",
|
||||
},
|
||||
},
|
||||
{
|
||||
"trigger": {
|
||||
"platform": "event",
|
||||
"event_type": "test_event_ping",
|
||||
},
|
||||
"action": {
|
||||
"domain": DOMAIN,
|
||||
"type": "ping",
|
||||
"device_id": device.id,
|
||||
},
|
||||
},
|
||||
{
|
||||
"trigger": {
|
||||
"platform": "event",
|
||||
"event_type": "test_event_set_value",
|
||||
},
|
||||
"action": {
|
||||
"domain": DOMAIN,
|
||||
"type": "set_value",
|
||||
"device_id": device.id,
|
||||
"command_class": 112,
|
||||
"property": 1,
|
||||
"value": 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
"trigger": {
|
||||
"platform": "event",
|
||||
"event_type": "test_event_set_config_parameter",
|
||||
},
|
||||
"action": {
|
||||
"domain": DOMAIN,
|
||||
"type": "set_config_parameter",
|
||||
"device_id": device.id,
|
||||
"parameter": 1,
|
||||
"bitmask": None,
|
||||
"subtype": "2-112-0-3 (Beeper)",
|
||||
"value": 1,
|
||||
},
|
||||
},
|
||||
]
|
||||
},
|
||||
)
|
||||
|
||||
with patch("zwave_js_server.model.node.Node.async_poll_value") as mock_call:
|
||||
hass.bus.async_fire("test_event_refresh_value")
|
||||
await hass.async_block_till_done()
|
||||
mock_call.assert_called_once()
|
||||
args = mock_call.call_args_list[0][0]
|
||||
assert len(args) == 1
|
||||
assert args[0].value_id == "13-64-1-mode"
|
||||
|
||||
with patch("zwave_js_server.model.node.Node.async_ping") as mock_call:
|
||||
hass.bus.async_fire("test_event_ping")
|
||||
await hass.async_block_till_done()
|
||||
mock_call.assert_called_once()
|
||||
args = mock_call.call_args_list[0][0]
|
||||
assert len(args) == 0
|
||||
|
||||
with patch("zwave_js_server.model.node.Node.async_set_value") as mock_call:
|
||||
hass.bus.async_fire("test_event_set_value")
|
||||
await hass.async_block_till_done()
|
||||
mock_call.assert_called_once()
|
||||
args = mock_call.call_args_list[0][0]
|
||||
assert len(args) == 2
|
||||
assert args[0] == "13-112-0-1"
|
||||
assert args[1] == 1
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.zwave_js.services.async_set_config_parameter"
|
||||
) as mock_call:
|
||||
hass.bus.async_fire("test_event_set_config_parameter")
|
||||
await hass.async_block_till_done()
|
||||
mock_call.assert_called_once()
|
||||
args = mock_call.call_args_list[0][0]
|
||||
assert len(args) == 3
|
||||
assert args[0].node_id == 13
|
||||
assert args[1] == 1
|
||||
assert args[2] == 1
|
||||
|
||||
|
||||
async def test_lock_actions(
|
||||
hass: HomeAssistant,
|
||||
client: Client,
|
||||
lock_schlage_be469: Node,
|
||||
integration: ConfigEntry,
|
||||
) -> None:
|
||||
"""Test actions for locks."""
|
||||
node = lock_schlage_be469
|
||||
device_id = get_device_id(client, node)
|
||||
dev_reg = device_registry.async_get(hass)
|
||||
device = dev_reg.async_get_device({device_id})
|
||||
assert device
|
||||
|
||||
assert await async_setup_component(
|
||||
hass,
|
||||
automation.DOMAIN,
|
||||
@ -102,7 +226,7 @@ async def test_action(hass: HomeAssistant) -> None:
|
||||
"action": {
|
||||
"domain": DOMAIN,
|
||||
"type": "clear_lock_usercode",
|
||||
"device_id": "fake",
|
||||
"device_id": device.id,
|
||||
"entity_id": "lock.touchscreen_deadbolt",
|
||||
"code_slot": 1,
|
||||
},
|
||||
@ -115,97 +239,80 @@ async def test_action(hass: HomeAssistant) -> None:
|
||||
"action": {
|
||||
"domain": DOMAIN,
|
||||
"type": "set_lock_usercode",
|
||||
"device_id": "fake",
|
||||
"device_id": device.id,
|
||||
"entity_id": "lock.touchscreen_deadbolt",
|
||||
"code_slot": 1,
|
||||
"usercode": "1234",
|
||||
},
|
||||
},
|
||||
{
|
||||
"trigger": {
|
||||
"platform": "event",
|
||||
"event_type": "test_event_refresh_value",
|
||||
},
|
||||
"action": {
|
||||
"domain": DOMAIN,
|
||||
"type": "refresh_value",
|
||||
"device_id": "fake",
|
||||
"entity_id": "lock.touchscreen_deadbolt",
|
||||
},
|
||||
},
|
||||
{
|
||||
"trigger": {
|
||||
"platform": "event",
|
||||
"event_type": "test_event_ping",
|
||||
},
|
||||
"action": {
|
||||
"domain": DOMAIN,
|
||||
"type": "ping",
|
||||
"device_id": "fake",
|
||||
},
|
||||
},
|
||||
{
|
||||
"trigger": {
|
||||
"platform": "event",
|
||||
"event_type": "test_event_set_value",
|
||||
},
|
||||
"action": {
|
||||
"domain": DOMAIN,
|
||||
"type": "set_value",
|
||||
"device_id": "fake",
|
||||
"command_class": 112,
|
||||
"property": "test",
|
||||
"value": 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
"trigger": {
|
||||
"platform": "event",
|
||||
"event_type": "test_event_set_config_parameter",
|
||||
},
|
||||
"action": {
|
||||
"domain": DOMAIN,
|
||||
"type": "set_config_parameter",
|
||||
"device_id": "fake",
|
||||
"parameter": 3,
|
||||
"bitmask": None,
|
||||
"subtype": "2-112-0-3 (Beeper)",
|
||||
"value": 255,
|
||||
},
|
||||
},
|
||||
]
|
||||
},
|
||||
)
|
||||
|
||||
clear_lock_usercode = async_mock_service(hass, "zwave_js", "clear_lock_usercode")
|
||||
hass.bus.async_fire("test_event_clear_lock_usercode")
|
||||
await hass.async_block_till_done()
|
||||
assert len(clear_lock_usercode) == 1
|
||||
with patch("homeassistant.components.zwave_js.lock.clear_usercode") as mock_call:
|
||||
hass.bus.async_fire("test_event_clear_lock_usercode")
|
||||
await hass.async_block_till_done()
|
||||
mock_call.assert_called_once()
|
||||
args = mock_call.call_args_list[0][0]
|
||||
assert len(args) == 2
|
||||
assert args[0].node_id == node.node_id
|
||||
assert args[1] == 1
|
||||
|
||||
set_lock_usercode = async_mock_service(hass, "zwave_js", "set_lock_usercode")
|
||||
hass.bus.async_fire("test_event_set_lock_usercode")
|
||||
await hass.async_block_till_done()
|
||||
assert len(set_lock_usercode) == 1
|
||||
with patch("homeassistant.components.zwave_js.lock.set_usercode") as mock_call:
|
||||
hass.bus.async_fire("test_event_set_lock_usercode")
|
||||
await hass.async_block_till_done()
|
||||
mock_call.assert_called_once()
|
||||
args = mock_call.call_args_list[0][0]
|
||||
assert len(args) == 3
|
||||
assert args[0].node_id == node.node_id
|
||||
assert args[1] == 1
|
||||
assert args[2] == "1234"
|
||||
|
||||
refresh_value = async_mock_service(hass, "zwave_js", "refresh_value")
|
||||
hass.bus.async_fire("test_event_refresh_value")
|
||||
await hass.async_block_till_done()
|
||||
assert len(refresh_value) == 1
|
||||
|
||||
ping = async_mock_service(hass, "zwave_js", "ping")
|
||||
hass.bus.async_fire("test_event_ping")
|
||||
await hass.async_block_till_done()
|
||||
assert len(ping) == 1
|
||||
async def test_reset_meter_action(
|
||||
hass: HomeAssistant,
|
||||
client: Client,
|
||||
aeon_smart_switch_6: Node,
|
||||
integration: ConfigEntry,
|
||||
) -> None:
|
||||
"""Test reset_meter action."""
|
||||
node = aeon_smart_switch_6
|
||||
device_id = get_device_id(client, node)
|
||||
dev_reg = device_registry.async_get(hass)
|
||||
device = dev_reg.async_get_device({device_id})
|
||||
assert device
|
||||
|
||||
set_value = async_mock_service(hass, "zwave_js", "set_value")
|
||||
hass.bus.async_fire("test_event_set_value")
|
||||
await hass.async_block_till_done()
|
||||
assert len(set_value) == 1
|
||||
assert await async_setup_component(
|
||||
hass,
|
||||
automation.DOMAIN,
|
||||
{
|
||||
automation.DOMAIN: [
|
||||
{
|
||||
"trigger": {
|
||||
"platform": "event",
|
||||
"event_type": "test_event_reset_meter",
|
||||
},
|
||||
"action": {
|
||||
"domain": DOMAIN,
|
||||
"type": "reset_meter",
|
||||
"device_id": device.id,
|
||||
"entity_id": "sensor.smart_switch_6_electric_consumed_kwh",
|
||||
},
|
||||
},
|
||||
]
|
||||
},
|
||||
)
|
||||
|
||||
set_config_parameter = async_mock_service(hass, "zwave_js", "set_config_parameter")
|
||||
hass.bus.async_fire("test_event_set_config_parameter")
|
||||
await hass.async_block_till_done()
|
||||
assert len(set_config_parameter) == 1
|
||||
with patch(
|
||||
"zwave_js_server.model.endpoint.Endpoint.async_invoke_cc_api"
|
||||
) as mock_call:
|
||||
hass.bus.async_fire("test_event_reset_meter")
|
||||
await hass.async_block_till_done()
|
||||
mock_call.assert_called_once()
|
||||
args = mock_call.call_args_list[0][0]
|
||||
assert len(args) == 2
|
||||
assert args[0] == CommandClass.METER
|
||||
assert args[1] == "reset"
|
||||
|
||||
|
||||
async def test_get_action_capabilities(
|
||||
@ -261,7 +368,28 @@ async def test_get_action_capabilities(
|
||||
)
|
||||
assert capabilities and "extra_fields" in capabilities
|
||||
|
||||
cc_options = [(cc.value, cc.name) for cc in CommandClass]
|
||||
cc_options = [
|
||||
(133, "Association"),
|
||||
(89, "Association Group Information"),
|
||||
(128, "Battery"),
|
||||
(129, "Clock"),
|
||||
(112, "Configuration"),
|
||||
(90, "Device Reset Locally"),
|
||||
(122, "Firmware Update Meta Data"),
|
||||
(135, "Indicator"),
|
||||
(114, "Manufacturer Specific"),
|
||||
(96, "Multi Channel"),
|
||||
(142, "Multi Channel Association"),
|
||||
(49, "Multilevel Sensor"),
|
||||
(115, "Powerlevel"),
|
||||
(68, "Thermostat Fan Mode"),
|
||||
(69, "Thermostat Fan State"),
|
||||
(64, "Thermostat Mode"),
|
||||
(66, "Thermostat Operating State"),
|
||||
(67, "Thermostat Setpoint"),
|
||||
(134, "Version"),
|
||||
(94, "Z-Wave Plus Info"),
|
||||
]
|
||||
|
||||
assert voluptuous_serialize.convert(
|
||||
capabilities["extra_fields"], custom_serializer=cv.custom_serializer
|
||||
|
Loading…
x
Reference in New Issue
Block a user