Merge pull request #63867 from home-assistant/rc

This commit is contained in:
Paulus Schoutsen 2022-01-10 17:26:09 -08:00 committed by GitHub
commit b6f432645d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
40 changed files with 793 additions and 191 deletions

View File

@ -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

View File

@ -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
):

View File

@ -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",

View File

@ -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:

View File

@ -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):

View File

@ -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": [
{

View File

@ -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",

View File

@ -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")

View File

@ -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"],

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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()

View File

@ -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(

View File

@ -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

View File

@ -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 = {

View File

@ -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)

View File

@ -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: *",

View File

@ -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."],

View File

@ -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"

View File

@ -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:

View File

@ -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."""

View File

@ -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

View File

@ -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
):

View File

@ -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"

View File

@ -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}"
)

View File

@ -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,

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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"},
),
)

View File

@ -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")

View File

@ -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):

View File

@ -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"],

View File

@ -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)}

View File

@ -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

View File

@ -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",

View File

@ -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