mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 21:27:38 +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/scene/* @home-assistant/core
|
||||||
homeassistant/components/schluter/* @prairieapps
|
homeassistant/components/schluter/* @prairieapps
|
||||||
homeassistant/components/scrape/* @fabaff
|
homeassistant/components/scrape/* @fabaff
|
||||||
homeassistant/components/screenlogic/* @dieselrabbit
|
homeassistant/components/screenlogic/* @dieselrabbit @bdraco
|
||||||
homeassistant/components/script/* @home-assistant/core
|
homeassistant/components/script/* @home-assistant/core
|
||||||
homeassistant/components/search/* @home-assistant/core
|
homeassistant/components/search/* @home-assistant/core
|
||||||
homeassistant/components/select/* @home-assistant/core
|
homeassistant/components/select/* @home-assistant/core
|
||||||
|
@ -245,6 +245,15 @@ class AugustData(AugustSubscriberMixin):
|
|||||||
device_id,
|
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):
|
async def async_unlock(self, device_id):
|
||||||
"""Unlock the device."""
|
"""Unlock the device."""
|
||||||
return await self._async_call_api_op_requires_bridge(
|
return await self._async_call_api_op_requires_bridge(
|
||||||
@ -254,6 +263,15 @@ class AugustData(AugustSubscriberMixin):
|
|||||||
device_id,
|
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(
|
async def _async_call_api_op_requires_bridge(
|
||||||
self, device_id, func, *args, **kwargs
|
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)
|
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:
|
def _retrieve_ding_state(data: AugustData, detail: DoorbellDetail) -> bool:
|
||||||
latest = data.activity_stream.get_latest_device_activity(
|
latest = data.activity_stream.get_latest_device_activity(
|
||||||
detail.device_id, {ActivityType.DOORBELL_DING}
|
detail.device_id, {ActivityType.DOORBELL_DING}
|
||||||
@ -126,6 +137,13 @@ SENSOR_TYPES_DOORBELL: tuple[AugustBinarySensorEntityDescription, ...] = (
|
|||||||
value_fn=_retrieve_motion_state,
|
value_fn=_retrieve_motion_state,
|
||||||
is_time_based=True,
|
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(
|
AugustBinarySensorEntityDescription(
|
||||||
key="doorbell_online",
|
key="doorbell_online",
|
||||||
name="Online",
|
name="Online",
|
||||||
|
@ -63,7 +63,8 @@ class AugustCamera(AugustEntityMixin, Camera):
|
|||||||
def _update_from_data(self):
|
def _update_from_data(self):
|
||||||
"""Get the latest state of the sensor."""
|
"""Get the latest state of the sensor."""
|
||||||
doorbell_activity = self._data.activity_stream.get_latest_device_activity(
|
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:
|
if doorbell_activity is not None:
|
||||||
|
@ -41,10 +41,16 @@ class AugustLock(AugustEntityMixin, RestoreEntity, LockEntity):
|
|||||||
|
|
||||||
async def async_lock(self, **kwargs):
|
async def async_lock(self, **kwargs):
|
||||||
"""Lock the device."""
|
"""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)
|
await self._call_lock_operation(self._data.async_lock)
|
||||||
|
|
||||||
async def async_unlock(self, **kwargs):
|
async def async_unlock(self, **kwargs):
|
||||||
"""Unlock the device."""
|
"""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)
|
await self._call_lock_operation(self._data.async_unlock)
|
||||||
|
|
||||||
async def _call_lock_operation(self, lock_operation):
|
async def _call_lock_operation(self, lock_operation):
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
"domain": "august",
|
"domain": "august",
|
||||||
"name": "August",
|
"name": "August",
|
||||||
"documentation": "https://www.home-assistant.io/integrations/august",
|
"documentation": "https://www.home-assistant.io/integrations/august",
|
||||||
"requirements": ["yalexs==1.1.13"],
|
"requirements": ["yalexs==1.1.17"],
|
||||||
"codeowners": ["@bdraco"],
|
"codeowners": ["@bdraco"],
|
||||||
"dhcp": [
|
"dhcp": [
|
||||||
{
|
{
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"name": "Google Cast",
|
"name": "Google Cast",
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"documentation": "https://www.home-assistant.io/integrations/cast",
|
"documentation": "https://www.home-assistant.io/integrations/cast",
|
||||||
"requirements": ["pychromecast==10.2.2"],
|
"requirements": ["pychromecast==10.2.3"],
|
||||||
"after_dependencies": [
|
"after_dependencies": [
|
||||||
"cloud",
|
"cloud",
|
||||||
"http",
|
"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 import HomeAssistantView
|
||||||
from homeassistant.components.http.data_validator import RequestDataValidator
|
from homeassistant.components.http.data_validator import RequestDataValidator
|
||||||
from homeassistant.components.websocket_api import const as ws_const
|
from homeassistant.components.websocket_api import const as ws_const
|
||||||
|
from homeassistant.util.location import async_detect_location_info
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
@ -220,8 +221,23 @@ class CloudRegisterView(HomeAssistantView):
|
|||||||
hass = request.app["hass"]
|
hass = request.app["hass"]
|
||||||
cloud = hass.data[DOMAIN]
|
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):
|
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")
|
return self.json_message("ok")
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
"domain": "cloud",
|
"domain": "cloud",
|
||||||
"name": "Home Assistant Cloud",
|
"name": "Home Assistant Cloud",
|
||||||
"documentation": "https://www.home-assistant.io/integrations/cloud",
|
"documentation": "https://www.home-assistant.io/integrations/cloud",
|
||||||
"requirements": ["hass-nabucasa==0.50.0"],
|
"requirements": ["hass-nabucasa==0.51.0"],
|
||||||
"dependencies": ["http", "webhook"],
|
"dependencies": ["http", "webhook"],
|
||||||
"after_dependencies": ["google_assistant", "alexa"],
|
"after_dependencies": ["google_assistant", "alexa"],
|
||||||
"codeowners": ["@home-assistant/cloud"],
|
"codeowners": ["@home-assistant/cloud"],
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"name": "Flux LED/MagicHome",
|
"name": "Flux LED/MagicHome",
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"documentation": "https://www.home-assistant.io/integrations/flux_led",
|
"documentation": "https://www.home-assistant.io/integrations/flux_led",
|
||||||
"requirements": ["flux_led==0.27.32"],
|
"requirements": ["flux_led==0.27.45"],
|
||||||
"quality_scale": "platinum",
|
"quality_scale": "platinum",
|
||||||
"codeowners": ["@icemanch"],
|
"codeowners": ["@icemanch"],
|
||||||
"iot_class": "local_push",
|
"iot_class": "local_push",
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"name": "Home Assistant Frontend",
|
"name": "Home Assistant Frontend",
|
||||||
"documentation": "https://www.home-assistant.io/integrations/frontend",
|
"documentation": "https://www.home-assistant.io/integrations/frontend",
|
||||||
"requirements": [
|
"requirements": [
|
||||||
"home-assistant-frontend==20211229.0"
|
"home-assistant-frontend==20211229.1"
|
||||||
],
|
],
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
"api",
|
"api",
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
"domain": "harmony",
|
"domain": "harmony",
|
||||||
"name": "Logitech Harmony Hub",
|
"name": "Logitech Harmony Hub",
|
||||||
"documentation": "https://www.home-assistant.io/integrations/harmony",
|
"documentation": "https://www.home-assistant.io/integrations/harmony",
|
||||||
"requirements": ["aioharmony==0.2.8"],
|
"requirements": ["aioharmony==0.2.9"],
|
||||||
"codeowners": [
|
"codeowners": [
|
||||||
"@ehendrix23",
|
"@ehendrix23",
|
||||||
"@bramkragten",
|
"@bramkragten",
|
||||||
|
@ -732,7 +732,12 @@ class HomeKit:
|
|||||||
"""Remove all pairings for an accessory so it can be repaired."""
|
"""Remove all pairings for an accessory so it can be repaired."""
|
||||||
state = self.driver.state
|
state = self.driver.state
|
||||||
for client_uuid in list(state.paired_clients):
|
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_persist()
|
||||||
self.driver.async_update_advertisement()
|
self.driver.async_update_advertisement()
|
||||||
self._async_show_setup_message()
|
self._async_show_setup_message()
|
||||||
|
@ -446,15 +446,25 @@ class OptionsFlowHandler(config_entries.OptionsFlow):
|
|||||||
return await self.async_step_advanced()
|
return await self.async_step_advanced()
|
||||||
|
|
||||||
entity_filter = self.hk_options.get(CONF_FILTER, {})
|
entity_filter = self.hk_options.get(CONF_FILTER, {})
|
||||||
|
entities = entity_filter.get(CONF_INCLUDE_ENTITIES, [])
|
||||||
|
|
||||||
all_supported_entities = _async_get_matching_entities(
|
all_supported_entities = _async_get_matching_entities(
|
||||||
self.hass,
|
self.hass,
|
||||||
domains=self.hk_options[CONF_DOMAINS],
|
domains=self.hk_options[CONF_DOMAINS],
|
||||||
)
|
)
|
||||||
|
|
||||||
data_schema = {}
|
data_schema = {}
|
||||||
entity_schema = vol.In
|
# Strip out entities that no longer exist to prevent error in the UI
|
||||||
entities = entity_filter.get(CONF_INCLUDE_ENTITIES, [])
|
valid_entities = [
|
||||||
if self.hk_options[CONF_HOMEKIT_MODE] != HOMEKIT_MODE_ACCESSORY:
|
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
|
include_exclude_mode = MODE_INCLUDE
|
||||||
if not entities:
|
if not entities:
|
||||||
include_exclude_mode = MODE_EXCLUDE
|
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.Required(CONF_INCLUDE_EXCLUDE_MODE, default=include_exclude_mode)
|
||||||
] = vol.In(INCLUDE_EXCLUDE_MODES)
|
] = vol.In(INCLUDE_EXCLUDE_MODES)
|
||||||
entity_schema = cv.multi_select
|
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[
|
data_schema[
|
||||||
vol.Optional(CONF_ENTITIES, default=valid_entities)
|
entities_schema_required(CONF_ENTITIES, default=default_value)
|
||||||
] = entity_schema(all_supported_entities)
|
] = entity_schema(all_supported_entities)
|
||||||
|
|
||||||
return self.async_show_form(
|
return self.async_show_form(
|
||||||
|
@ -22,8 +22,8 @@ from .const import (
|
|||||||
DEFAULT_CONSIDER_HOME,
|
DEFAULT_CONSIDER_HOME,
|
||||||
DEFAULT_NAME,
|
DEFAULT_NAME,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
MODELS_V2,
|
MODELS_PORT_80,
|
||||||
ORBI_PORT,
|
PORT_80,
|
||||||
)
|
)
|
||||||
from .errors import CannotLoginException
|
from .errors import CannotLoginException
|
||||||
from .router import get_api
|
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)
|
self._abort_if_unique_id_configured(updates=updated_data)
|
||||||
|
|
||||||
updated_data[CONF_PORT] = DEFAULT_PORT
|
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(
|
if discovery_info.upnp.get(ssdp.ATTR_UPNP_MODEL_NUMBER, "").startswith(
|
||||||
model
|
model
|
||||||
) or discovery_info.upnp.get(ssdp.ATTR_UPNP_MODEL_NAME, "").startswith(
|
) or discovery_info.upnp.get(ssdp.ATTR_UPNP_MODEL_NAME, "").startswith(
|
||||||
model
|
model
|
||||||
):
|
):
|
||||||
updated_data[CONF_PORT] = ORBI_PORT
|
updated_data[CONF_PORT] = PORT_80
|
||||||
|
|
||||||
self.placeholders.update(updated_data)
|
self.placeholders.update(updated_data)
|
||||||
self.discovered = True
|
self.discovered = True
|
||||||
|
@ -10,8 +10,8 @@ CONF_CONSIDER_HOME = "consider_home"
|
|||||||
DEFAULT_CONSIDER_HOME = timedelta(seconds=180)
|
DEFAULT_CONSIDER_HOME = timedelta(seconds=180)
|
||||||
DEFAULT_NAME = "Netgear router"
|
DEFAULT_NAME = "Netgear router"
|
||||||
|
|
||||||
# update method V2 models
|
# models using port 80 instead of 5000
|
||||||
MODELS_V2 = [
|
MODELS_PORT_80 = [
|
||||||
"Orbi",
|
"Orbi",
|
||||||
"RBK",
|
"RBK",
|
||||||
"RBR",
|
"RBR",
|
||||||
@ -29,7 +29,25 @@ MODELS_V2 = [
|
|||||||
"SXR",
|
"SXR",
|
||||||
"SXS",
|
"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
|
# Icons
|
||||||
DEVICE_ICONS = {
|
DEVICE_ICONS = {
|
||||||
|
@ -149,6 +149,14 @@ class NetgearRouter:
|
|||||||
if self.model.startswith(model):
|
if self.model.startswith(model):
|
||||||
self.method_version = 2
|
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:
|
async def async_setup(self) -> None:
|
||||||
"""Set up a Netgear router."""
|
"""Set up a Netgear router."""
|
||||||
await self.hass.async_add_executor_job(self._setup)
|
await self.hass.async_add_executor_job(self._setup)
|
||||||
|
@ -3,8 +3,8 @@
|
|||||||
"name": "Pentair ScreenLogic",
|
"name": "Pentair ScreenLogic",
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"documentation": "https://www.home-assistant.io/integrations/screenlogic",
|
"documentation": "https://www.home-assistant.io/integrations/screenlogic",
|
||||||
"requirements": ["screenlogicpy==0.5.3"],
|
"requirements": ["screenlogicpy==0.5.4"],
|
||||||
"codeowners": ["@dieselrabbit"],
|
"codeowners": ["@dieselrabbit", "@bdraco"],
|
||||||
"dhcp": [
|
"dhcp": [
|
||||||
{
|
{
|
||||||
"hostname": "pentair: *",
|
"hostname": "pentair: *",
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"name": "Sonos",
|
"name": "Sonos",
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"documentation": "https://www.home-assistant.io/integrations/sonos",
|
"documentation": "https://www.home-assistant.io/integrations/sonos",
|
||||||
"requirements": ["soco==0.25.2"],
|
"requirements": ["soco==0.25.3"],
|
||||||
"dependencies": ["ssdp"],
|
"dependencies": ["ssdp"],
|
||||||
"after_dependencies": ["plex", "zeroconf"],
|
"after_dependencies": ["plex", "zeroconf"],
|
||||||
"zeroconf": ["_sonos._tcp.local."],
|
"zeroconf": ["_sonos._tcp.local."],
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
"domain": "switchbot",
|
"domain": "switchbot",
|
||||||
"name": "SwitchBot",
|
"name": "SwitchBot",
|
||||||
"documentation": "https://www.home-assistant.io/integrations/switchbot",
|
"documentation": "https://www.home-assistant.io/integrations/switchbot",
|
||||||
"requirements": ["PySwitchbot==0.13.0"],
|
"requirements": ["PySwitchbot==0.13.2"],
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"codeowners": ["@danielhiversen", "@RenierM26"],
|
"codeowners": ["@danielhiversen", "@RenierM26"],
|
||||||
"iot_class": "local_polling"
|
"iot_class": "local_polling"
|
||||||
|
@ -113,6 +113,7 @@ class SwitchBotBotEntity(SwitchbotEntity, SwitchEntity, RestoreEntity):
|
|||||||
super().__init__(coordinator, idx, mac, name)
|
super().__init__(coordinator, idx, mac, name)
|
||||||
self._attr_unique_id = idx
|
self._attr_unique_id = idx
|
||||||
self._device = device
|
self._device = device
|
||||||
|
self._attr_is_on = False
|
||||||
|
|
||||||
async def async_added_to_hass(self) -> None:
|
async def async_added_to_hass(self) -> None:
|
||||||
"""Run when entity about to be added."""
|
"""Run when entity about to be added."""
|
||||||
@ -132,6 +133,7 @@ class SwitchBotBotEntity(SwitchbotEntity, SwitchEntity, RestoreEntity):
|
|||||||
)
|
)
|
||||||
if self._last_run_success:
|
if self._last_run_success:
|
||||||
self._attr_is_on = True
|
self._attr_is_on = True
|
||||||
|
self.async_write_ha_state()
|
||||||
|
|
||||||
async def async_turn_off(self, **kwargs: Any) -> None:
|
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||||
"""Turn device off."""
|
"""Turn device off."""
|
||||||
@ -143,6 +145,7 @@ class SwitchBotBotEntity(SwitchbotEntity, SwitchEntity, RestoreEntity):
|
|||||||
)
|
)
|
||||||
if self._last_run_success:
|
if self._last_run_success:
|
||||||
self._attr_is_on = False
|
self._attr_is_on = False
|
||||||
|
self.async_write_ha_state()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def assumed_state(self) -> bool:
|
def assumed_state(self) -> bool:
|
||||||
|
@ -142,7 +142,7 @@ class TradfriAirPurifierFan(TradfriBaseDevice, FanEntity):
|
|||||||
preset_mode: str | None = None,
|
preset_mode: str | None = None,
|
||||||
**kwargs: Any,
|
**kwargs: Any,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Turn on the fan."""
|
"""Turn on the fan. Auto-mode if no argument is given."""
|
||||||
if not self._device_control:
|
if not self._device_control:
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -150,8 +150,8 @@ class TradfriAirPurifierFan(TradfriBaseDevice, FanEntity):
|
|||||||
await self._api(self._device_control.set_mode(_from_percentage(percentage)))
|
await self._api(self._device_control.set_mode(_from_percentage(percentage)))
|
||||||
return
|
return
|
||||||
|
|
||||||
if preset_mode:
|
preset_mode = preset_mode or ATTR_AUTO
|
||||||
await self.async_set_preset_mode(preset_mode)
|
await self.async_set_preset_mode(preset_mode)
|
||||||
|
|
||||||
async def async_set_percentage(self, percentage: int) -> None:
|
async def async_set_percentage(self, percentage: int) -> None:
|
||||||
"""Set the speed percentage of the fan."""
|
"""Set the speed percentage of the fan."""
|
||||||
|
@ -148,8 +148,9 @@ class TuyaClimateEntity(TuyaEntity, ClimateEntity):
|
|||||||
):
|
):
|
||||||
self._attr_temperature_unit = TEMP_CELSIUS
|
self._attr_temperature_unit = TEMP_CELSIUS
|
||||||
if any(
|
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)
|
for dpcode in (DPCode.C_F, DPCode.TEMP_UNIT_CONVERT)
|
||||||
|
if isinstance(device.status.get(dpcode), str)
|
||||||
):
|
):
|
||||||
self._attr_temperature_unit = TEMP_FAHRENHEIT
|
self._attr_temperature_unit = TEMP_FAHRENHEIT
|
||||||
|
|
||||||
|
@ -30,6 +30,28 @@ from .const import DOMAIN, TUYA_DISCOVERY_NEW, DPCode, WorkMode
|
|||||||
from .util import remap_value
|
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
|
@dataclass
|
||||||
class TuyaLightEntityDescription(LightEntityDescription):
|
class TuyaLightEntityDescription(LightEntityDescription):
|
||||||
"""Describe an Tuya light entity."""
|
"""Describe an Tuya light entity."""
|
||||||
@ -40,6 +62,7 @@ class TuyaLightEntityDescription(LightEntityDescription):
|
|||||||
color_data: DPCode | tuple[DPCode, ...] | None = None
|
color_data: DPCode | tuple[DPCode, ...] | None = None
|
||||||
color_mode: DPCode | None = None
|
color_mode: DPCode | None = None
|
||||||
color_temp: DPCode | tuple[DPCode, ...] | None = None
|
color_temp: DPCode | tuple[DPCode, ...] | None = None
|
||||||
|
default_color_type: ColorTypeData = DEFAULT_COLOR_TYPE_DATA
|
||||||
|
|
||||||
|
|
||||||
LIGHTS: dict[str, tuple[TuyaLightEntityDescription, ...]] = {
|
LIGHTS: dict[str, tuple[TuyaLightEntityDescription, ...]] = {
|
||||||
@ -63,6 +86,7 @@ LIGHTS: dict[str, tuple[TuyaLightEntityDescription, ...]] = {
|
|||||||
brightness=DPCode.BRIGHT_VALUE,
|
brightness=DPCode.BRIGHT_VALUE,
|
||||||
color_temp=DPCode.TEMP_VALUE,
|
color_temp=DPCode.TEMP_VALUE,
|
||||||
color_data=DPCode.COLOUR_DATA,
|
color_data=DPCode.COLOUR_DATA,
|
||||||
|
default_color_type=DEFAULT_COLOR_TYPE_DATA_V2,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
# Light
|
# Light
|
||||||
@ -242,28 +266,6 @@ LIGHTS["cz"] = LIGHTS["kg"]
|
|||||||
LIGHTS["pc"] = 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
|
@dataclass
|
||||||
class ColorData:
|
class ColorData:
|
||||||
"""Color Data."""
|
"""Color Data."""
|
||||||
@ -443,7 +445,7 @@ class TuyaLightEntity(TuyaEntity, LightEntity):
|
|||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
# If no type is found, use a default one
|
# 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 (
|
if self._color_data_dpcode == DPCode.COLOUR_DATA_V2 or (
|
||||||
self._brightness_type and self._brightness_type.max > 255
|
self._brightness_type and self._brightness_type.max > 255
|
||||||
):
|
):
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
"domain": "waze_travel_time",
|
"domain": "waze_travel_time",
|
||||||
"name": "Waze Travel Time",
|
"name": "Waze Travel Time",
|
||||||
"documentation": "https://www.home-assistant.io/integrations/waze_travel_time",
|
"documentation": "https://www.home-assistant.io/integrations/waze_travel_time",
|
||||||
"requirements": ["WazeRouteCalculator==0.13"],
|
"requirements": ["WazeRouteCalculator==0.14"],
|
||||||
"codeowners": [],
|
"codeowners": [],
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"iot_class": "cloud_polling"
|
"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.components.number.const import DOMAIN as PLATFORM_DOMAIN
|
||||||
from homeassistant.const import DEGREE, ENTITY_CATEGORY_CONFIG, TIME_MINUTES
|
from homeassistant.const import DEGREE, ENTITY_CATEGORY_CONFIG, TIME_MINUTES
|
||||||
from homeassistant.core import callback
|
from homeassistant.core import callback
|
||||||
|
from homeassistant.helpers import entity_registry as er
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
CONF_DEVICE,
|
CONF_DEVICE,
|
||||||
@ -251,7 +252,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
|||||||
for feature, description in NUMBER_TYPES.items():
|
for feature, description in NUMBER_TYPES.items():
|
||||||
if feature == FEATURE_SET_LED_BRIGHTNESS and model != MODEL_FAN_ZA5:
|
if feature == FEATURE_SET_LED_BRIGHTNESS and model != MODEL_FAN_ZA5:
|
||||||
# Delete LED bightness entity created by mistake if it exists
|
# 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(
|
entity_id = entity_reg.async_get_entity_id(
|
||||||
PLATFORM_DOMAIN, DOMAIN, f"{description.key}_{config_entry.unique_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.lock import DOMAIN as LOCK_DOMAIN
|
||||||
from homeassistant.components.sensor import DOMAIN as SENSOR_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.core import Context, HomeAssistant
|
||||||
from homeassistant.exceptions import HomeAssistantError
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
from homeassistant.helpers import entity_registry
|
from homeassistant.helpers import entity_registry
|
||||||
@ -227,7 +234,22 @@ async def async_call_action_from_config(
|
|||||||
if action_type not in ACTION_TYPES:
|
if action_type not in ACTION_TYPES:
|
||||||
raise HomeAssistantError(f"Unhandled action type {action_type}")
|
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(
|
await hass.services.async_call(
|
||||||
DOMAIN, service, service_data, blocking=True, context=context
|
DOMAIN, service, service_data, blocking=True, context=context
|
||||||
)
|
)
|
||||||
@ -283,7 +305,10 @@ async def async_get_action_capabilities(
|
|||||||
"extra_fields": vol.Schema(
|
"extra_fields": vol.Schema(
|
||||||
{
|
{
|
||||||
vol.Required(ATTR_COMMAND_CLASS): vol.In(
|
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.Required(ATTR_PROPERTY): cv.string,
|
||||||
vol.Optional(ATTR_PROPERTY_KEY): cv.string,
|
vol.Optional(ATTR_PROPERTY_KEY): cv.string,
|
||||||
|
@ -7,7 +7,7 @@ from homeassistant.backports.enum import StrEnum
|
|||||||
|
|
||||||
MAJOR_VERSION: Final = 2021
|
MAJOR_VERSION: Final = 2021
|
||||||
MINOR_VERSION: Final = 12
|
MINOR_VERSION: Final = 12
|
||||||
PATCH_VERSION: Final = "8"
|
PATCH_VERSION: Final = "9"
|
||||||
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
||||||
__version__: Final = f"{__short_version__}.{PATCH_VERSION}"
|
__version__: Final = f"{__short_version__}.{PATCH_VERSION}"
|
||||||
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 8, 0)
|
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 8, 0)
|
||||||
|
@ -15,8 +15,8 @@ certifi>=2021.5.30
|
|||||||
ciso8601==2.2.0
|
ciso8601==2.2.0
|
||||||
cryptography==35.0.0
|
cryptography==35.0.0
|
||||||
emoji==1.5.0
|
emoji==1.5.0
|
||||||
hass-nabucasa==0.50.0
|
hass-nabucasa==0.51.0
|
||||||
home-assistant-frontend==20211229.0
|
home-assistant-frontend==20211229.1
|
||||||
httpx==0.21.0
|
httpx==0.21.0
|
||||||
ifaddr==0.1.7
|
ifaddr==0.1.7
|
||||||
jinja2==3.0.3
|
jinja2==3.0.3
|
||||||
|
@ -46,7 +46,7 @@ PyRMVtransport==0.3.3
|
|||||||
PySocks==1.7.1
|
PySocks==1.7.1
|
||||||
|
|
||||||
# homeassistant.components.switchbot
|
# homeassistant.components.switchbot
|
||||||
# PySwitchbot==0.13.0
|
# PySwitchbot==0.13.2
|
||||||
|
|
||||||
# homeassistant.components.transport_nsw
|
# homeassistant.components.transport_nsw
|
||||||
PyTransportNSW==0.1.1
|
PyTransportNSW==0.1.1
|
||||||
@ -83,7 +83,7 @@ TwitterAPI==2.7.5
|
|||||||
WSDiscovery==2.0.0
|
WSDiscovery==2.0.0
|
||||||
|
|
||||||
# homeassistant.components.waze_travel_time
|
# homeassistant.components.waze_travel_time
|
||||||
WazeRouteCalculator==0.13
|
WazeRouteCalculator==0.14
|
||||||
|
|
||||||
# homeassistant.components.abode
|
# homeassistant.components.abode
|
||||||
abodepy==1.2.0
|
abodepy==1.2.0
|
||||||
@ -177,7 +177,7 @@ aiogithubapi==21.11.0
|
|||||||
aioguardian==2021.11.0
|
aioguardian==2021.11.0
|
||||||
|
|
||||||
# homeassistant.components.harmony
|
# homeassistant.components.harmony
|
||||||
aioharmony==0.2.8
|
aioharmony==0.2.9
|
||||||
|
|
||||||
# homeassistant.components.homekit_controller
|
# homeassistant.components.homekit_controller
|
||||||
aiohomekit==0.6.4
|
aiohomekit==0.6.4
|
||||||
@ -659,7 +659,7 @@ fjaraskupan==1.0.2
|
|||||||
flipr-api==1.4.1
|
flipr-api==1.4.1
|
||||||
|
|
||||||
# homeassistant.components.flux_led
|
# homeassistant.components.flux_led
|
||||||
flux_led==0.27.32
|
flux_led==0.27.45
|
||||||
|
|
||||||
# homeassistant.components.homekit
|
# homeassistant.components.homekit
|
||||||
fnvhash==0.1.0
|
fnvhash==0.1.0
|
||||||
@ -787,7 +787,7 @@ habitipy==0.2.0
|
|||||||
hangups==0.4.14
|
hangups==0.4.14
|
||||||
|
|
||||||
# homeassistant.components.cloud
|
# homeassistant.components.cloud
|
||||||
hass-nabucasa==0.50.0
|
hass-nabucasa==0.51.0
|
||||||
|
|
||||||
# homeassistant.components.splunk
|
# homeassistant.components.splunk
|
||||||
hass_splunk==0.1.1
|
hass_splunk==0.1.1
|
||||||
@ -820,7 +820,7 @@ hole==0.7.0
|
|||||||
holidays==0.11.3.1
|
holidays==0.11.3.1
|
||||||
|
|
||||||
# homeassistant.components.frontend
|
# homeassistant.components.frontend
|
||||||
home-assistant-frontend==20211229.0
|
home-assistant-frontend==20211229.1
|
||||||
|
|
||||||
# homeassistant.components.zwave
|
# homeassistant.components.zwave
|
||||||
homeassistant-pyozw==0.1.10
|
homeassistant-pyozw==0.1.10
|
||||||
@ -1397,7 +1397,7 @@ pycfdns==1.2.2
|
|||||||
pychannels==1.0.0
|
pychannels==1.0.0
|
||||||
|
|
||||||
# homeassistant.components.cast
|
# homeassistant.components.cast
|
||||||
pychromecast==10.2.2
|
pychromecast==10.2.3
|
||||||
|
|
||||||
# homeassistant.components.pocketcasts
|
# homeassistant.components.pocketcasts
|
||||||
pycketcasts==1.0.0
|
pycketcasts==1.0.0
|
||||||
@ -2113,7 +2113,7 @@ scapy==2.4.5
|
|||||||
schiene==0.23
|
schiene==0.23
|
||||||
|
|
||||||
# homeassistant.components.screenlogic
|
# homeassistant.components.screenlogic
|
||||||
screenlogicpy==0.5.3
|
screenlogicpy==0.5.4
|
||||||
|
|
||||||
# homeassistant.components.scsgate
|
# homeassistant.components.scsgate
|
||||||
scsgate==0.1.0
|
scsgate==0.1.0
|
||||||
@ -2185,7 +2185,7 @@ smhi-pkg==1.0.15
|
|||||||
snapcast==2.1.3
|
snapcast==2.1.3
|
||||||
|
|
||||||
# homeassistant.components.sonos
|
# homeassistant.components.sonos
|
||||||
soco==0.25.2
|
soco==0.25.3
|
||||||
|
|
||||||
# homeassistant.components.solaredge_local
|
# homeassistant.components.solaredge_local
|
||||||
solaredge-local==0.2.0
|
solaredge-local==0.2.0
|
||||||
@ -2466,7 +2466,7 @@ xs1-api-client==3.0.0
|
|||||||
yalesmartalarmclient==0.3.4
|
yalesmartalarmclient==0.3.4
|
||||||
|
|
||||||
# homeassistant.components.august
|
# homeassistant.components.august
|
||||||
yalexs==1.1.13
|
yalexs==1.1.17
|
||||||
|
|
||||||
# homeassistant.components.yeelight
|
# homeassistant.components.yeelight
|
||||||
yeelight==0.7.8
|
yeelight==0.7.8
|
||||||
|
@ -24,7 +24,7 @@ PyQRCode==1.2.1
|
|||||||
PyRMVtransport==0.3.3
|
PyRMVtransport==0.3.3
|
||||||
|
|
||||||
# homeassistant.components.switchbot
|
# homeassistant.components.switchbot
|
||||||
# PySwitchbot==0.13.0
|
# PySwitchbot==0.13.2
|
||||||
|
|
||||||
# homeassistant.components.transport_nsw
|
# homeassistant.components.transport_nsw
|
||||||
PyTransportNSW==0.1.1
|
PyTransportNSW==0.1.1
|
||||||
@ -45,7 +45,7 @@ RtmAPI==0.7.2
|
|||||||
WSDiscovery==2.0.0
|
WSDiscovery==2.0.0
|
||||||
|
|
||||||
# homeassistant.components.waze_travel_time
|
# homeassistant.components.waze_travel_time
|
||||||
WazeRouteCalculator==0.13
|
WazeRouteCalculator==0.14
|
||||||
|
|
||||||
# homeassistant.components.abode
|
# homeassistant.components.abode
|
||||||
abodepy==1.2.0
|
abodepy==1.2.0
|
||||||
@ -121,7 +121,7 @@ aioflo==2021.11.0
|
|||||||
aioguardian==2021.11.0
|
aioguardian==2021.11.0
|
||||||
|
|
||||||
# homeassistant.components.harmony
|
# homeassistant.components.harmony
|
||||||
aioharmony==0.2.8
|
aioharmony==0.2.9
|
||||||
|
|
||||||
# homeassistant.components.homekit_controller
|
# homeassistant.components.homekit_controller
|
||||||
aiohomekit==0.6.4
|
aiohomekit==0.6.4
|
||||||
@ -399,7 +399,7 @@ fjaraskupan==1.0.2
|
|||||||
flipr-api==1.4.1
|
flipr-api==1.4.1
|
||||||
|
|
||||||
# homeassistant.components.flux_led
|
# homeassistant.components.flux_led
|
||||||
flux_led==0.27.32
|
flux_led==0.27.45
|
||||||
|
|
||||||
# homeassistant.components.homekit
|
# homeassistant.components.homekit
|
||||||
fnvhash==0.1.0
|
fnvhash==0.1.0
|
||||||
@ -494,7 +494,7 @@ habitipy==0.2.0
|
|||||||
hangups==0.4.14
|
hangups==0.4.14
|
||||||
|
|
||||||
# homeassistant.components.cloud
|
# homeassistant.components.cloud
|
||||||
hass-nabucasa==0.50.0
|
hass-nabucasa==0.51.0
|
||||||
|
|
||||||
# homeassistant.components.tasmota
|
# homeassistant.components.tasmota
|
||||||
hatasmota==0.3.1
|
hatasmota==0.3.1
|
||||||
@ -515,7 +515,7 @@ hole==0.7.0
|
|||||||
holidays==0.11.3.1
|
holidays==0.11.3.1
|
||||||
|
|
||||||
# homeassistant.components.frontend
|
# homeassistant.components.frontend
|
||||||
home-assistant-frontend==20211229.0
|
home-assistant-frontend==20211229.1
|
||||||
|
|
||||||
# homeassistant.components.zwave
|
# homeassistant.components.zwave
|
||||||
homeassistant-pyozw==0.1.10
|
homeassistant-pyozw==0.1.10
|
||||||
@ -850,7 +850,7 @@ pybotvac==0.0.22
|
|||||||
pycfdns==1.2.2
|
pycfdns==1.2.2
|
||||||
|
|
||||||
# homeassistant.components.cast
|
# homeassistant.components.cast
|
||||||
pychromecast==10.2.2
|
pychromecast==10.2.3
|
||||||
|
|
||||||
# homeassistant.components.climacell
|
# homeassistant.components.climacell
|
||||||
pyclimacell==0.18.2
|
pyclimacell==0.18.2
|
||||||
@ -1257,7 +1257,7 @@ samsungtvws==1.6.0
|
|||||||
scapy==2.4.5
|
scapy==2.4.5
|
||||||
|
|
||||||
# homeassistant.components.screenlogic
|
# homeassistant.components.screenlogic
|
||||||
screenlogicpy==0.5.3
|
screenlogicpy==0.5.4
|
||||||
|
|
||||||
# homeassistant.components.emulated_kasa
|
# homeassistant.components.emulated_kasa
|
||||||
# homeassistant.components.sense
|
# homeassistant.components.sense
|
||||||
@ -1291,7 +1291,7 @@ smarthab==0.21
|
|||||||
smhi-pkg==1.0.15
|
smhi-pkg==1.0.15
|
||||||
|
|
||||||
# homeassistant.components.sonos
|
# homeassistant.components.sonos
|
||||||
soco==0.25.2
|
soco==0.25.3
|
||||||
|
|
||||||
# homeassistant.components.solaredge
|
# homeassistant.components.solaredge
|
||||||
solaredge==0.0.2
|
solaredge==0.0.2
|
||||||
@ -1464,7 +1464,7 @@ xmltodict==0.12.0
|
|||||||
yalesmartalarmclient==0.3.4
|
yalesmartalarmclient==0.3.4
|
||||||
|
|
||||||
# homeassistant.components.august
|
# homeassistant.components.august
|
||||||
yalexs==1.1.13
|
yalexs==1.1.17
|
||||||
|
|
||||||
# homeassistant.components.yeelight
|
# homeassistant.components.yeelight
|
||||||
yeelight==0.7.8
|
yeelight==0.7.8
|
||||||
|
@ -2,8 +2,6 @@
|
|||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
|
|
||||||
# from unittest.mock import AsyncMock
|
|
||||||
from unittest.mock import AsyncMock, MagicMock, PropertyMock, patch
|
from unittest.mock import AsyncMock, MagicMock, PropertyMock, patch
|
||||||
|
|
||||||
from yalexs.activity import (
|
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"]
|
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"})
|
api_instance.async_get_user = AsyncMock(return_value={"UserID": "abc"})
|
||||||
|
|
||||||
return await _mock_setup_august(hass, api_instance, pubnub)
|
return await _mock_setup_august(hass, api_instance, pubnub)
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
"""The binary_sensor tests for the august platform."""
|
"""The binary_sensor tests for the august platform."""
|
||||||
import datetime
|
import datetime
|
||||||
|
import time
|
||||||
from unittest.mock import Mock, patch
|
from unittest.mock import Mock, patch
|
||||||
|
|
||||||
from yalexs.pubnub_async import AugustPubNub
|
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):
|
async def test_doorsense(hass):
|
||||||
"""Test creation of a lock with doorsense and bridge."""
|
"""Test creation of a lock with doorsense and bridge."""
|
||||||
lock_one = await _mock_lock_from_fixture(
|
lock_one = await _mock_lock_from_fixture(
|
||||||
@ -85,6 +90,10 @@ async def test_create_doorbell(hass):
|
|||||||
"binary_sensor.k98gidt45gul_name_motion"
|
"binary_sensor.k98gidt45gul_name_motion"
|
||||||
)
|
)
|
||||||
assert binary_sensor_k98gidt45gul_name_motion.state == STATE_OFF
|
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 = hass.states.get(
|
||||||
"binary_sensor.k98gidt45gul_name_online"
|
"binary_sensor.k98gidt45gul_name_online"
|
||||||
)
|
)
|
||||||
@ -97,6 +106,10 @@ async def test_create_doorbell(hass):
|
|||||||
"binary_sensor.k98gidt45gul_name_motion"
|
"binary_sensor.k98gidt45gul_name_motion"
|
||||||
)
|
)
|
||||||
assert binary_sensor_k98gidt45gul_name_motion.state == STATE_OFF
|
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):
|
async def test_create_doorbell_offline(hass):
|
||||||
@ -171,7 +184,7 @@ async def test_doorbell_update_via_pubnub(hass):
|
|||||||
pubnub,
|
pubnub,
|
||||||
Mock(
|
Mock(
|
||||||
channel=doorbell_one.pubsub_channel,
|
channel=doorbell_one.pubsub_channel,
|
||||||
timetoken=dt_util.utcnow().timestamp() * 10000000,
|
timetoken=_timetoken(),
|
||||||
message={
|
message={
|
||||||
"status": "imagecapture",
|
"status": "imagecapture",
|
||||||
"data": {
|
"data": {
|
||||||
@ -186,10 +199,46 @@ async def test_doorbell_update_via_pubnub(hass):
|
|||||||
|
|
||||||
await hass.async_block_till_done()
|
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 = hass.states.get(
|
||||||
"binary_sensor.k98gidt45gul_name_motion"
|
"binary_sensor.k98gidt45gul_name_motion"
|
||||||
)
|
)
|
||||||
assert binary_sensor_k98gidt45gul_name_motion.state == STATE_ON
|
assert binary_sensor_k98gidt45gul_name_motion.state == STATE_ON
|
||||||
|
|
||||||
binary_sensor_k98gidt45gul_name_ding = hass.states.get(
|
binary_sensor_k98gidt45gul_name_ding = hass.states.get(
|
||||||
"binary_sensor.k98gidt45gul_name_ding"
|
"binary_sensor.k98gidt45gul_name_ding"
|
||||||
)
|
)
|
||||||
@ -204,16 +253,16 @@ async def test_doorbell_update_via_pubnub(hass):
|
|||||||
async_fire_time_changed(hass, new_time)
|
async_fire_time_changed(hass, new_time)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
binary_sensor_k98gidt45gul_name_motion = hass.states.get(
|
binary_sensor_k98gidt45gul_name_image_capture = hass.states.get(
|
||||||
"binary_sensor.k98gidt45gul_name_motion"
|
"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.message(
|
||||||
pubnub,
|
pubnub,
|
||||||
Mock(
|
Mock(
|
||||||
channel=doorbell_one.pubsub_channel,
|
channel=doorbell_one.pubsub_channel,
|
||||||
timetoken=dt_util.utcnow().timestamp() * 10000000,
|
timetoken=_timetoken(),
|
||||||
message={
|
message={
|
||||||
"status": "buttonpush",
|
"status": "buttonpush",
|
||||||
},
|
},
|
||||||
@ -274,7 +323,7 @@ async def test_door_sense_update_via_pubnub(hass):
|
|||||||
pubnub,
|
pubnub,
|
||||||
Mock(
|
Mock(
|
||||||
channel=lock_one.pubsub_channel,
|
channel=lock_one.pubsub_channel,
|
||||||
timetoken=dt_util.utcnow().timestamp() * 10000000,
|
timetoken=_timetoken(),
|
||||||
message={"status": "kAugLockState_Unlocking", "doorState": "closed"},
|
message={"status": "kAugLockState_Unlocking", "doorState": "closed"},
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@ -289,11 +338,10 @@ async def test_door_sense_update_via_pubnub(hass):
|
|||||||
pubnub,
|
pubnub,
|
||||||
Mock(
|
Mock(
|
||||||
channel=lock_one.pubsub_channel,
|
channel=lock_one.pubsub_channel,
|
||||||
timetoken=dt_util.utcnow().timestamp() * 10000000,
|
timetoken=_timetoken(),
|
||||||
message={"status": "kAugLockState_Locking", "doorState": "open"},
|
message={"status": "kAugLockState_Locking", "doorState": "open"},
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
binary_sensor_online_with_doorsense_name = hass.states.get(
|
binary_sensor_online_with_doorsense_name = hass.states.get(
|
||||||
"binary_sensor.online_with_doorsense_name_open"
|
"binary_sensor.online_with_doorsense_name_open"
|
||||||
@ -327,7 +375,7 @@ async def test_door_sense_update_via_pubnub(hass):
|
|||||||
pubnub,
|
pubnub,
|
||||||
Mock(
|
Mock(
|
||||||
channel=lock_one.pubsub_channel,
|
channel=lock_one.pubsub_channel,
|
||||||
timetoken=dt_util.utcnow().timestamp() * 10000000,
|
timetoken=_timetoken(),
|
||||||
message={"status": "kAugLockState_Unlocking", "doorState": "open"},
|
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):
|
async def test_lock_jammed(hass):
|
||||||
"""Test lock gets jammed on unlock."""
|
"""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(
|
config_entry = await _create_august_with_devices(
|
||||||
hass, [lock_one], activities=activities, pubnub=pubnub
|
hass, [lock_one], activities=activities, pubnub=pubnub
|
||||||
)
|
)
|
||||||
|
pubnub.connected = True
|
||||||
|
|
||||||
lock_online_with_doorsense_name = hass.states.get("lock.online_with_doorsense_name")
|
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.cloud.const import DOMAIN, RequireRelink
|
||||||
from homeassistant.components.google_assistant.helpers import GoogleEntity
|
from homeassistant.components.google_assistant.helpers import GoogleEntity
|
||||||
from homeassistant.core import State
|
from homeassistant.core import State
|
||||||
|
from homeassistant.util.location import LocationInfo
|
||||||
|
|
||||||
from . import mock_cloud, mock_cloud_prefs
|
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
|
assert req.status == HTTPStatus.BAD_GATEWAY
|
||||||
|
|
||||||
|
|
||||||
async def test_register_view(mock_cognito, cloud_client):
|
async def test_register_view_no_location(mock_cognito, cloud_client):
|
||||||
"""Test logging out."""
|
"""Test register without location."""
|
||||||
req = await cloud_client.post(
|
with patch(
|
||||||
"/api/cloud/register", json={"email": "hello@bla.com", "password": "falcon42"}
|
"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 req.status == HTTPStatus.OK
|
||||||
assert len(mock_cognito.register.mock_calls) == 1
|
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_email == "hello@bla.com"
|
||||||
assert result_pass == "falcon42"
|
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):
|
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.const import CONF_NAME, CONF_PORT
|
||||||
from homeassistant.setup import async_setup_component
|
from homeassistant.setup import async_setup_component
|
||||||
|
|
||||||
|
from .util import PATH_HOMEKIT, async_init_entry
|
||||||
|
|
||||||
from tests.common import MockConfigEntry
|
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
|
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."""
|
"""Test config flow options in include mode with a single accessory."""
|
||||||
|
|
||||||
config_entry = _mock_config_entry_with_options_populated()
|
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.tv", "off")
|
||||||
hass.states.async_set("media_player.sonos", "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["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||||
assert result2["step_id"] == "include_exclude"
|
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(
|
result3 = await hass.config_entries.options.async_configure(
|
||||||
result2["flow_id"],
|
result2["flow_id"],
|
||||||
|
@ -683,6 +683,11 @@ async def test_homekit_unpair(hass, device_reg, mock_async_zeroconf):
|
|||||||
|
|
||||||
state = homekit.driver.state
|
state = homekit.driver.state
|
||||||
state.add_paired_client("client1", "any", b"1")
|
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)
|
formatted_mac = device_registry.format_mac(state.mac)
|
||||||
hk_bridge_dev = device_reg.async_get_device(
|
hk_bridge_dev = device_reg.async_get_device(
|
||||||
{}, {(device_registry.CONNECTION_NETWORK_MAC, formatted_mac)}
|
{}, {(device_registry.CONNECTION_NETWORK_MAC, formatted_mac)}
|
||||||
|
@ -6,7 +6,7 @@ import pytest
|
|||||||
|
|
||||||
from homeassistant import data_entry_flow
|
from homeassistant import data_entry_flow
|
||||||
from homeassistant.components import ssdp
|
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.config_entries import SOURCE_IMPORT, SOURCE_SSDP, SOURCE_USER
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
CONF_HOST,
|
CONF_HOST,
|
||||||
@ -252,7 +252,7 @@ async def test_ssdp(hass, service):
|
|||||||
assert result["result"].unique_id == SERIAL
|
assert result["result"].unique_id == SERIAL
|
||||||
assert result["title"] == TITLE
|
assert result["title"] == TITLE
|
||||||
assert result["data"].get(CONF_HOST) == HOST
|
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_SSL) == SSL
|
||||||
assert result["data"].get(CONF_USERNAME) == DEFAULT_USER
|
assert result["data"].get(CONF_USERNAME) == DEFAULT_USER
|
||||||
assert result["data"][CONF_PASSWORD] == PASSWORD
|
assert result["data"][CONF_PASSWORD] == PASSWORD
|
||||||
|
@ -57,7 +57,128 @@
|
|||||||
},
|
},
|
||||||
{ "nodeId": 13, "index": 2 }
|
{ "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": [
|
"values": [
|
||||||
{
|
{
|
||||||
"commandClassName": "Manufacturer Specific",
|
"commandClassName": "Manufacturer Specific",
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
"""The tests for Z-Wave JS device actions."""
|
"""The tests for Z-Wave JS device actions."""
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
import voluptuous_serialize
|
import voluptuous_serialize
|
||||||
from zwave_js_server.client import Client
|
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.helpers import config_validation as cv, device_registry
|
||||||
from homeassistant.setup import async_setup_component
|
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(
|
async def test_get_actions(
|
||||||
@ -87,8 +89,130 @@ async def test_get_actions_meter(
|
|||||||
assert len(filtered_actions) > 0
|
assert len(filtered_actions) > 0
|
||||||
|
|
||||||
|
|
||||||
async def test_action(hass: HomeAssistant) -> None:
|
async def test_actions(
|
||||||
"""Test for turn_on and turn_off 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(
|
assert await async_setup_component(
|
||||||
hass,
|
hass,
|
||||||
automation.DOMAIN,
|
automation.DOMAIN,
|
||||||
@ -102,7 +226,7 @@ async def test_action(hass: HomeAssistant) -> None:
|
|||||||
"action": {
|
"action": {
|
||||||
"domain": DOMAIN,
|
"domain": DOMAIN,
|
||||||
"type": "clear_lock_usercode",
|
"type": "clear_lock_usercode",
|
||||||
"device_id": "fake",
|
"device_id": device.id,
|
||||||
"entity_id": "lock.touchscreen_deadbolt",
|
"entity_id": "lock.touchscreen_deadbolt",
|
||||||
"code_slot": 1,
|
"code_slot": 1,
|
||||||
},
|
},
|
||||||
@ -115,97 +239,80 @@ async def test_action(hass: HomeAssistant) -> None:
|
|||||||
"action": {
|
"action": {
|
||||||
"domain": DOMAIN,
|
"domain": DOMAIN,
|
||||||
"type": "set_lock_usercode",
|
"type": "set_lock_usercode",
|
||||||
"device_id": "fake",
|
"device_id": device.id,
|
||||||
"entity_id": "lock.touchscreen_deadbolt",
|
"entity_id": "lock.touchscreen_deadbolt",
|
||||||
"code_slot": 1,
|
"code_slot": 1,
|
||||||
"usercode": "1234",
|
"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")
|
with patch("homeassistant.components.zwave_js.lock.clear_usercode") as mock_call:
|
||||||
hass.bus.async_fire("test_event_clear_lock_usercode")
|
hass.bus.async_fire("test_event_clear_lock_usercode")
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert len(clear_lock_usercode) == 1
|
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")
|
with patch("homeassistant.components.zwave_js.lock.set_usercode") as mock_call:
|
||||||
hass.bus.async_fire("test_event_set_lock_usercode")
|
hass.bus.async_fire("test_event_set_lock_usercode")
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert len(set_lock_usercode) == 1
|
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")
|
async def test_reset_meter_action(
|
||||||
hass.bus.async_fire("test_event_ping")
|
hass: HomeAssistant,
|
||||||
await hass.async_block_till_done()
|
client: Client,
|
||||||
assert len(ping) == 1
|
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")
|
assert await async_setup_component(
|
||||||
hass.bus.async_fire("test_event_set_value")
|
hass,
|
||||||
await hass.async_block_till_done()
|
automation.DOMAIN,
|
||||||
assert len(set_value) == 1
|
{
|
||||||
|
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")
|
with patch(
|
||||||
hass.bus.async_fire("test_event_set_config_parameter")
|
"zwave_js_server.model.endpoint.Endpoint.async_invoke_cc_api"
|
||||||
await hass.async_block_till_done()
|
) as mock_call:
|
||||||
assert len(set_config_parameter) == 1
|
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(
|
async def test_get_action_capabilities(
|
||||||
@ -261,7 +368,28 @@ async def test_get_action_capabilities(
|
|||||||
)
|
)
|
||||||
assert capabilities and "extra_fields" in 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(
|
assert voluptuous_serialize.convert(
|
||||||
capabilities["extra_fields"], custom_serializer=cv.custom_serializer
|
capabilities["extra_fields"], custom_serializer=cv.custom_serializer
|
||||||
|
Loading…
x
Reference in New Issue
Block a user