forked from home-assistant/core
Compare commits
16 Commits
2021.4.0b0
...
2021.4.0b2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
74357d9760 | ||
|
|
231a55d416 | ||
|
|
e760c23f37 | ||
|
|
39f68de5fa | ||
|
|
68b189cf9f | ||
|
|
8d0941ba65 | ||
|
|
d1a48c7c5c | ||
|
|
f0f8b79be0 | ||
|
|
c2d17a72b7 | ||
|
|
f0bd3c577f | ||
|
|
947ac514b9 | ||
|
|
5df90b32fc | ||
|
|
f08e7dccdf | ||
|
|
3982849275 | ||
|
|
07827ca55d | ||
|
|
16da181692 |
@@ -1,6 +1,5 @@
|
||||
"""Support for Ambient Weather Station sensors."""
|
||||
from homeassistant.components.binary_sensor import DOMAIN as SENSOR
|
||||
from homeassistant.components.sensor import SensorEntity
|
||||
from homeassistant.components.sensor import DOMAIN as SENSOR, SensorEntity
|
||||
from homeassistant.const import ATTR_NAME
|
||||
from homeassistant.core import callback
|
||||
|
||||
|
||||
@@ -57,6 +57,7 @@ from homeassistant.helpers.script_variables import ScriptVariables
|
||||
from homeassistant.helpers.service import async_register_admin_service
|
||||
from homeassistant.helpers.trace import (
|
||||
TraceElement,
|
||||
script_execution_set,
|
||||
trace_append_element,
|
||||
trace_get,
|
||||
trace_path,
|
||||
@@ -272,6 +273,7 @@ class AutomationEntity(ToggleEntity, RestoreEntity):
|
||||
variables,
|
||||
trigger_variables,
|
||||
raw_config,
|
||||
blueprint_inputs,
|
||||
):
|
||||
"""Initialize an automation entity."""
|
||||
self._id = automation_id
|
||||
@@ -289,6 +291,7 @@ class AutomationEntity(ToggleEntity, RestoreEntity):
|
||||
self._variables: ScriptVariables = variables
|
||||
self._trigger_variables: ScriptVariables = trigger_variables
|
||||
self._raw_config = raw_config
|
||||
self._blueprint_inputs = blueprint_inputs
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
@@ -436,7 +439,11 @@ class AutomationEntity(ToggleEntity, RestoreEntity):
|
||||
trigger_context = Context(parent_id=parent_id)
|
||||
|
||||
with trace_automation(
|
||||
self.hass, self.unique_id, self._raw_config, trigger_context
|
||||
self.hass,
|
||||
self.unique_id,
|
||||
self._raw_config,
|
||||
self._blueprint_inputs,
|
||||
trigger_context,
|
||||
) as automation_trace:
|
||||
if self._variables:
|
||||
try:
|
||||
@@ -471,6 +478,7 @@ class AutomationEntity(ToggleEntity, RestoreEntity):
|
||||
"Conditions not met, aborting automation. Condition summary: %s",
|
||||
trace_get(clear=False),
|
||||
)
|
||||
script_execution_set("failed_conditions")
|
||||
return
|
||||
|
||||
self.async_set_context(trigger_context)
|
||||
@@ -601,10 +609,12 @@ async def _async_process_config(
|
||||
]
|
||||
|
||||
for list_no, config_block in enumerate(conf):
|
||||
raw_blueprint_inputs = None
|
||||
raw_config = None
|
||||
if isinstance(config_block, blueprint.BlueprintInputs): # type: ignore
|
||||
blueprints_used = True
|
||||
blueprint_inputs = config_block
|
||||
raw_blueprint_inputs = blueprint_inputs.config_with_inputs
|
||||
|
||||
try:
|
||||
raw_config = blueprint_inputs.async_substitute()
|
||||
@@ -673,6 +683,7 @@ async def _async_process_config(
|
||||
variables,
|
||||
config_block.get(CONF_TRIGGER_VARIABLES),
|
||||
raw_config,
|
||||
raw_blueprint_inputs,
|
||||
)
|
||||
|
||||
entities.append(entity)
|
||||
|
||||
@@ -18,11 +18,12 @@ class AutomationTrace(ActionTrace):
|
||||
self,
|
||||
item_id: str,
|
||||
config: dict[str, Any],
|
||||
blueprint_inputs: dict[str, Any],
|
||||
context: Context,
|
||||
):
|
||||
"""Container for automation trace."""
|
||||
key = ("automation", item_id)
|
||||
super().__init__(key, config, context)
|
||||
super().__init__(key, config, blueprint_inputs, context)
|
||||
self._trigger_description: str | None = None
|
||||
|
||||
def set_trigger_description(self, trigger: str) -> None:
|
||||
@@ -37,9 +38,9 @@ class AutomationTrace(ActionTrace):
|
||||
|
||||
|
||||
@contextmanager
|
||||
def trace_automation(hass, automation_id, config, context):
|
||||
def trace_automation(hass, automation_id, config, blueprint_inputs, context):
|
||||
"""Trace action execution of automation with automation_id."""
|
||||
trace = AutomationTrace(automation_id, config, context)
|
||||
trace = AutomationTrace(automation_id, config, blueprint_inputs, context)
|
||||
async_store_trace(hass, trace)
|
||||
|
||||
try:
|
||||
|
||||
@@ -304,7 +304,7 @@ async def get_device(hass, host, port, username, password):
|
||||
)
|
||||
|
||||
try:
|
||||
with async_timeout.timeout(15):
|
||||
with async_timeout.timeout(30):
|
||||
await device.vapix.initialize()
|
||||
|
||||
return device
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"name": "Axis",
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/axis",
|
||||
"requirements": ["axis==43"],
|
||||
"requirements": ["axis==44"],
|
||||
"dhcp": [
|
||||
{ "hostname": "axis-00408c*", "macaddress": "00408C*" },
|
||||
{ "hostname": "axis-accc8e*", "macaddress": "ACCC8E*" },
|
||||
|
||||
@@ -97,6 +97,17 @@ class ConfigManagerEntryResourceReloadView(HomeAssistantView):
|
||||
return self.json({"require_restart": not result})
|
||||
|
||||
|
||||
def _prepare_config_flow_result_json(result, prepare_result_json):
|
||||
"""Convert result to JSON."""
|
||||
if result["type"] != data_entry_flow.RESULT_TYPE_CREATE_ENTRY:
|
||||
return prepare_result_json(result)
|
||||
|
||||
data = result.copy()
|
||||
data["result"] = entry_json(result["result"])
|
||||
data.pop("data")
|
||||
return data
|
||||
|
||||
|
||||
class ConfigManagerFlowIndexView(FlowManagerIndexView):
|
||||
"""View to create config flows."""
|
||||
|
||||
@@ -118,13 +129,7 @@ class ConfigManagerFlowIndexView(FlowManagerIndexView):
|
||||
|
||||
def _prepare_result_json(self, result):
|
||||
"""Convert result to JSON."""
|
||||
if result["type"] != data_entry_flow.RESULT_TYPE_CREATE_ENTRY:
|
||||
return super()._prepare_result_json(result)
|
||||
|
||||
data = result.copy()
|
||||
data["result"] = data["result"].entry_id
|
||||
data.pop("data")
|
||||
return data
|
||||
return _prepare_config_flow_result_json(result, super()._prepare_result_json)
|
||||
|
||||
|
||||
class ConfigManagerFlowResourceView(FlowManagerResourceView):
|
||||
@@ -151,13 +156,7 @@ class ConfigManagerFlowResourceView(FlowManagerResourceView):
|
||||
|
||||
def _prepare_result_json(self, result):
|
||||
"""Convert result to JSON."""
|
||||
if result["type"] != data_entry_flow.RESULT_TYPE_CREATE_ENTRY:
|
||||
return super()._prepare_result_json(result)
|
||||
|
||||
data = result.copy()
|
||||
data["result"] = entry_json(result["result"])
|
||||
data.pop("data")
|
||||
return data
|
||||
return _prepare_config_flow_result_json(result, super()._prepare_result_json)
|
||||
|
||||
|
||||
class ConfigManagerAvailableFlowView(HomeAssistantView):
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
"name": "Default Config",
|
||||
"documentation": "https://www.home-assistant.io/integrations/default_config",
|
||||
"dependencies": [
|
||||
"analytics",
|
||||
"automation",
|
||||
"cloud",
|
||||
"counter",
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"name": "Home Assistant Frontend",
|
||||
"documentation": "https://www.home-assistant.io/integrations/frontend",
|
||||
"requirements": [
|
||||
"home-assistant-frontend==20210331.0"
|
||||
"home-assistant-frontend==20210402.0"
|
||||
],
|
||||
"dependencies": [
|
||||
"api",
|
||||
|
||||
@@ -3,6 +3,6 @@
|
||||
"name": "IQVIA",
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/iqvia",
|
||||
"requirements": ["numpy==1.19.2", "pyiqvia==0.3.1"],
|
||||
"requirements": ["numpy==1.20.2", "pyiqvia==0.3.1"],
|
||||
"codeowners": ["@bachya"]
|
||||
}
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
"""Binary sensor platform for mobile_app."""
|
||||
from functools import partial
|
||||
|
||||
from homeassistant.components.binary_sensor import BinarySensorEntity
|
||||
from homeassistant.const import CONF_NAME, CONF_UNIQUE_ID, CONF_WEBHOOK_ID, STATE_ON
|
||||
from homeassistant.core import callback
|
||||
@@ -48,7 +46,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
async_add_entities(entities)
|
||||
|
||||
@callback
|
||||
def handle_sensor_registration(webhook_id, data):
|
||||
def handle_sensor_registration(data):
|
||||
if data[CONF_WEBHOOK_ID] != webhook_id:
|
||||
return
|
||||
|
||||
@@ -66,7 +64,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
async_dispatcher_connect(
|
||||
hass,
|
||||
f"{DOMAIN}_{ENTITY_TYPE}_register",
|
||||
partial(handle_sensor_registration, webhook_id),
|
||||
handle_sensor_registration,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -34,13 +34,14 @@ class MobileAppEntity(RestoreEntity):
|
||||
self._registration = entry.data
|
||||
self._unique_id = config[CONF_UNIQUE_ID]
|
||||
self._entity_type = config[ATTR_SENSOR_TYPE]
|
||||
self.unsub_dispatcher = None
|
||||
self._name = config[CONF_NAME]
|
||||
|
||||
async def async_added_to_hass(self):
|
||||
"""Register callbacks."""
|
||||
self.unsub_dispatcher = async_dispatcher_connect(
|
||||
self.hass, SIGNAL_SENSOR_UPDATE, self._handle_update
|
||||
self.async_on_remove(
|
||||
async_dispatcher_connect(
|
||||
self.hass, SIGNAL_SENSOR_UPDATE, self._handle_update
|
||||
)
|
||||
)
|
||||
state = await self.async_get_last_state()
|
||||
|
||||
@@ -49,11 +50,6 @@ class MobileAppEntity(RestoreEntity):
|
||||
|
||||
self.async_restore_last_state(state)
|
||||
|
||||
async def async_will_remove_from_hass(self):
|
||||
"""Disconnect dispatcher listener when removed."""
|
||||
if self.unsub_dispatcher is not None:
|
||||
self.unsub_dispatcher()
|
||||
|
||||
@callback
|
||||
def async_restore_last_state(self, last_state):
|
||||
"""Restore previous state."""
|
||||
|
||||
@@ -84,17 +84,16 @@ def log_rate_limits(hass, device_name, resp, level=logging.INFO):
|
||||
|
||||
async def async_get_service(hass, config, discovery_info=None):
|
||||
"""Get the mobile_app notification service."""
|
||||
session = async_get_clientsession(hass)
|
||||
service = hass.data[DOMAIN][DATA_NOTIFY] = MobileAppNotificationService(session)
|
||||
service = hass.data[DOMAIN][DATA_NOTIFY] = MobileAppNotificationService(hass)
|
||||
return service
|
||||
|
||||
|
||||
class MobileAppNotificationService(BaseNotificationService):
|
||||
"""Implement the notification service for mobile_app."""
|
||||
|
||||
def __init__(self, session):
|
||||
def __init__(self, hass):
|
||||
"""Initialize the service."""
|
||||
self._session = session
|
||||
self._hass = hass
|
||||
|
||||
@property
|
||||
def targets(self):
|
||||
@@ -141,7 +140,9 @@ class MobileAppNotificationService(BaseNotificationService):
|
||||
|
||||
try:
|
||||
with async_timeout.timeout(10):
|
||||
response = await self._session.post(push_url, json=data)
|
||||
response = await async_get_clientsession(self._hass).post(
|
||||
push_url, json=data
|
||||
)
|
||||
result = await response.json()
|
||||
|
||||
if response.status in [HTTP_OK, HTTP_CREATED, HTTP_ACCEPTED]:
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
"""Sensor platform for mobile_app."""
|
||||
from functools import partial
|
||||
|
||||
from homeassistant.components.sensor import SensorEntity
|
||||
from homeassistant.const import CONF_NAME, CONF_UNIQUE_ID, CONF_WEBHOOK_ID
|
||||
from homeassistant.core import callback
|
||||
@@ -50,7 +48,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
async_add_entities(entities)
|
||||
|
||||
@callback
|
||||
def handle_sensor_registration(webhook_id, data):
|
||||
def handle_sensor_registration(data):
|
||||
if data[CONF_WEBHOOK_ID] != webhook_id:
|
||||
return
|
||||
|
||||
@@ -68,7 +66,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
async_dispatcher_connect(
|
||||
hass,
|
||||
f"{DOMAIN}_{ENTITY_TYPE}_register",
|
||||
partial(handle_sensor_registration, webhook_id),
|
||||
handle_sensor_registration,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -472,6 +472,7 @@ async def webhook_update_sensor_states(hass, config_entry, data):
|
||||
|
||||
device_name = config_entry.data[ATTR_DEVICE_NAME]
|
||||
resp = {}
|
||||
|
||||
for sensor in data:
|
||||
entity_type = sensor[ATTR_SENSOR_TYPE]
|
||||
|
||||
@@ -495,8 +496,6 @@ async def webhook_update_sensor_states(hass, config_entry, data):
|
||||
}
|
||||
continue
|
||||
|
||||
entry = {CONF_WEBHOOK_ID: config_entry.data[CONF_WEBHOOK_ID]}
|
||||
|
||||
try:
|
||||
sensor = sensor_schema_full(sensor)
|
||||
except vol.Invalid as err:
|
||||
@@ -513,9 +512,8 @@ async def webhook_update_sensor_states(hass, config_entry, data):
|
||||
}
|
||||
continue
|
||||
|
||||
new_state = {**entry, **sensor}
|
||||
|
||||
async_dispatcher_send(hass, SIGNAL_SENSOR_UPDATE, new_state)
|
||||
sensor[CONF_WEBHOOK_ID] = config_entry.data[CONF_WEBHOOK_ID]
|
||||
async_dispatcher_send(hass, SIGNAL_SENSOR_UPDATE, sensor)
|
||||
|
||||
resp[unique_id] = {"success": True}
|
||||
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
"""The 1-Wire component."""
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.exceptions import ConfigEntryNotReady
|
||||
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
||||
from homeassistant.helpers.typing import HomeAssistantType
|
||||
|
||||
from .const import DOMAIN, PLATFORMS
|
||||
from .onewirehub import CannotConnect, OneWireHub
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def async_setup(hass, config):
|
||||
"""Set up 1-Wire integrations."""
|
||||
@@ -26,10 +30,43 @@ async def async_setup_entry(hass: HomeAssistantType, config_entry: ConfigEntry):
|
||||
|
||||
hass.data[DOMAIN][config_entry.unique_id] = onewirehub
|
||||
|
||||
for platform in PLATFORMS:
|
||||
hass.async_create_task(
|
||||
hass.config_entries.async_forward_entry_setup(config_entry, platform)
|
||||
async def cleanup_registry() -> None:
|
||||
# Get registries
|
||||
device_registry, entity_registry = await asyncio.gather(
|
||||
hass.helpers.device_registry.async_get_registry(),
|
||||
hass.helpers.entity_registry.async_get_registry(),
|
||||
)
|
||||
# Generate list of all device entries
|
||||
registry_devices = [
|
||||
entry.id
|
||||
for entry in dr.async_entries_for_config_entry(
|
||||
device_registry, config_entry.entry_id
|
||||
)
|
||||
]
|
||||
# Remove devices that don't belong to any entity
|
||||
for device_id in registry_devices:
|
||||
if not er.async_entries_for_device(
|
||||
entity_registry, device_id, include_disabled_entities=True
|
||||
):
|
||||
_LOGGER.debug(
|
||||
"Removing device `%s` because it does not have any entities",
|
||||
device_id,
|
||||
)
|
||||
device_registry.async_remove_device(device_id)
|
||||
|
||||
async def start_platforms() -> None:
|
||||
"""Start platforms and cleanup devices."""
|
||||
# wait until all required platforms are ready
|
||||
await asyncio.gather(
|
||||
*[
|
||||
hass.config_entries.async_forward_entry_setup(config_entry, platform)
|
||||
for platform in PLATFORMS
|
||||
]
|
||||
)
|
||||
await cleanup_registry()
|
||||
|
||||
hass.async_create_task(start_platforms())
|
||||
|
||||
return True
|
||||
|
||||
|
||||
|
||||
@@ -2,6 +2,6 @@
|
||||
"domain": "opencv",
|
||||
"name": "OpenCV",
|
||||
"documentation": "https://www.home-assistant.io/integrations/opencv",
|
||||
"requirements": ["numpy==1.19.2", "opencv-python-headless==4.3.0.36"],
|
||||
"requirements": ["numpy==1.20.2", "opencv-python-headless==4.3.0.36"],
|
||||
"codeowners": []
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ class ScriptTrace(ActionTrace):
|
||||
):
|
||||
"""Container for automation trace."""
|
||||
key = ("script", item_id)
|
||||
super().__init__(key, config, context)
|
||||
super().__init__(key, config, None, context)
|
||||
|
||||
|
||||
@contextmanager
|
||||
|
||||
@@ -19,7 +19,6 @@ async def async_setup(hass: HomeAssistant, config: dict):
|
||||
return True
|
||||
|
||||
|
||||
@websocket_api.async_response
|
||||
@websocket_api.websocket_command(
|
||||
{
|
||||
vol.Required("type"): "search/related",
|
||||
@@ -38,6 +37,7 @@ async def async_setup(hass: HomeAssistant, config: dict):
|
||||
vol.Required("item_id"): str,
|
||||
}
|
||||
)
|
||||
@callback
|
||||
def websocket_search_related(hass, connection, msg):
|
||||
"""Handle search."""
|
||||
searcher = Searcher(
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"tensorflow==2.3.0",
|
||||
"tf-models-official==2.3.0",
|
||||
"pycocotools==2.0.1",
|
||||
"numpy==1.19.2",
|
||||
"numpy==1.20.2",
|
||||
"pillow==8.1.2"
|
||||
],
|
||||
"codeowners": []
|
||||
|
||||
@@ -327,7 +327,9 @@ class Timer(RestoreEntity):
|
||||
if self._state != STATUS_ACTIVE:
|
||||
return
|
||||
|
||||
self._listener = None
|
||||
if self._listener:
|
||||
self._listener()
|
||||
self._listener = None
|
||||
self._state = STATUS_IDLE
|
||||
self._end = None
|
||||
self._remaining = None
|
||||
|
||||
@@ -8,6 +8,7 @@ from typing import Any, Deque
|
||||
from homeassistant.core import Context
|
||||
from homeassistant.helpers.trace import (
|
||||
TraceElement,
|
||||
script_execution_get,
|
||||
trace_id_get,
|
||||
trace_id_set,
|
||||
trace_set_child_id,
|
||||
@@ -47,14 +48,17 @@ class ActionTrace:
|
||||
self,
|
||||
key: tuple[str, str],
|
||||
config: dict[str, Any],
|
||||
blueprint_inputs: dict[str, Any],
|
||||
context: Context,
|
||||
):
|
||||
"""Container for script trace."""
|
||||
self._trace: dict[str, Deque[TraceElement]] | None = None
|
||||
self._config: dict[str, Any] = config
|
||||
self._blueprint_inputs: dict[str, Any] = blueprint_inputs
|
||||
self.context: Context = context
|
||||
self._error: Exception | None = None
|
||||
self._state: str = "running"
|
||||
self._script_execution: str | None = None
|
||||
self.run_id: str = str(next(self._run_ids))
|
||||
self._timestamp_finish: dt.datetime | None = None
|
||||
self._timestamp_start: dt.datetime = dt_util.utcnow()
|
||||
@@ -75,6 +79,7 @@ class ActionTrace:
|
||||
"""Set finish time."""
|
||||
self._timestamp_finish = dt_util.utcnow()
|
||||
self._state = "stopped"
|
||||
self._script_execution = script_execution_get()
|
||||
|
||||
def as_dict(self) -> dict[str, Any]:
|
||||
"""Return dictionary version of this ActionTrace."""
|
||||
@@ -90,6 +95,7 @@ class ActionTrace:
|
||||
{
|
||||
"trace": traces,
|
||||
"config": self._config,
|
||||
"blueprint_inputs": self._blueprint_inputs,
|
||||
"context": self.context,
|
||||
}
|
||||
)
|
||||
@@ -109,6 +115,7 @@ class ActionTrace:
|
||||
"last_step": last_step,
|
||||
"run_id": self.run_id,
|
||||
"state": self._state,
|
||||
"script_execution": self._script_execution,
|
||||
"timestamp": {
|
||||
"start": self._timestamp_start,
|
||||
"finish": self._timestamp_finish,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"domain": "trend",
|
||||
"name": "Trend",
|
||||
"documentation": "https://www.home-assistant.io/integrations/trend",
|
||||
"requirements": ["numpy==1.19.2"],
|
||||
"requirements": ["numpy==1.20.2"],
|
||||
"codeowners": [],
|
||||
"quality_scale": "internal"
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ import async_timeout
|
||||
from homeassistant.components.device_tracker import DOMAIN as TRACKER_DOMAIN
|
||||
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
|
||||
from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN
|
||||
from homeassistant.components.unifi.switch import BLOCK_SWITCH, POE_SWITCH
|
||||
from homeassistant.config_entries import SOURCE_REAUTH
|
||||
from homeassistant.const import (
|
||||
CONF_HOST,
|
||||
@@ -347,7 +348,10 @@ class UniFiController:
|
||||
):
|
||||
if entry.domain == TRACKER_DOMAIN:
|
||||
mac = entry.unique_id.split("-", 1)[0]
|
||||
elif entry.domain == SWITCH_DOMAIN:
|
||||
elif entry.domain == SWITCH_DOMAIN and (
|
||||
entry.unique_id.startswith(BLOCK_SWITCH)
|
||||
or entry.unique_id.startswith(POE_SWITCH)
|
||||
):
|
||||
mac = entry.unique_id.split("-", 1)[1]
|
||||
else:
|
||||
continue
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""Constants used by Home Assistant components."""
|
||||
MAJOR_VERSION = 2021
|
||||
MINOR_VERSION = 4
|
||||
PATCH_VERSION = "0b0"
|
||||
PATCH_VERSION = "0b2"
|
||||
__short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
||||
__version__ = f"{__short_version__}.{PATCH_VERSION}"
|
||||
REQUIRED_PYTHON_VER = (3, 8, 0)
|
||||
|
||||
@@ -916,7 +916,7 @@ SERVICE_SCHEMA = vol.All(
|
||||
vol.Optional("data"): vol.All(dict, template_complex),
|
||||
vol.Optional("data_template"): vol.All(dict, template_complex),
|
||||
vol.Optional(CONF_ENTITY_ID): comp_entity_ids,
|
||||
vol.Optional(CONF_TARGET): ENTITY_SERVICE_FIELDS,
|
||||
vol.Optional(CONF_TARGET): vol.Any(ENTITY_SERVICE_FIELDS, dynamic_template),
|
||||
}
|
||||
),
|
||||
has_at_least_one_key(CONF_SERVICE, CONF_SERVICE_TEMPLATE),
|
||||
|
||||
@@ -63,6 +63,7 @@ from homeassistant.helpers.dispatcher import (
|
||||
)
|
||||
from homeassistant.helpers.event import async_call_later, async_track_template
|
||||
from homeassistant.helpers.script_variables import ScriptVariables
|
||||
from homeassistant.helpers.trace import script_execution_set
|
||||
from homeassistant.helpers.trigger import (
|
||||
async_initialize_triggers,
|
||||
async_validate_trigger_config,
|
||||
@@ -332,15 +333,19 @@ class _ScriptRun:
|
||||
async def async_run(self) -> None:
|
||||
"""Run script."""
|
||||
try:
|
||||
if self._stop.is_set():
|
||||
return
|
||||
self._log("Running %s", self._script.running_description)
|
||||
for self._step, self._action in enumerate(self._script.sequence):
|
||||
if self._stop.is_set():
|
||||
script_execution_set("cancelled")
|
||||
break
|
||||
await self._async_step(log_exceptions=False)
|
||||
else:
|
||||
script_execution_set("finished")
|
||||
except _StopScript:
|
||||
pass
|
||||
script_execution_set("aborted")
|
||||
except Exception:
|
||||
script_execution_set("error")
|
||||
raise
|
||||
finally:
|
||||
self._finish()
|
||||
|
||||
@@ -1137,6 +1142,7 @@ class Script:
|
||||
if self.script_mode == SCRIPT_MODE_SINGLE:
|
||||
if self._max_exceeded != "SILENT":
|
||||
self._log("Already running", level=LOGSEVERITY[self._max_exceeded])
|
||||
script_execution_set("failed_single")
|
||||
return
|
||||
if self.script_mode == SCRIPT_MODE_RESTART:
|
||||
self._log("Restarting")
|
||||
@@ -1147,6 +1153,7 @@ class Script:
|
||||
"Maximum number of runs exceeded",
|
||||
level=LOGSEVERITY[self._max_exceeded],
|
||||
)
|
||||
script_execution_set("failed_max_runs")
|
||||
return
|
||||
|
||||
# If this is a top level Script then make a copy of the variables in case they
|
||||
|
||||
@@ -204,10 +204,15 @@ def async_prepare_call_from_config(
|
||||
|
||||
target = {}
|
||||
if CONF_TARGET in config:
|
||||
conf = config.get(CONF_TARGET)
|
||||
conf = config[CONF_TARGET]
|
||||
try:
|
||||
template.attach(hass, conf)
|
||||
target.update(template.render_complex(conf, variables))
|
||||
if isinstance(conf, template.Template):
|
||||
conf.hass = hass
|
||||
target.update(conf.async_render(variables))
|
||||
else:
|
||||
template.attach(hass, conf)
|
||||
target.update(template.render_complex(conf, variables))
|
||||
|
||||
if CONF_ENTITY_ID in target:
|
||||
target[CONF_ENTITY_ID] = cv.comp_entity_ids(target[CONF_ENTITY_ID])
|
||||
except TemplateError as ex:
|
||||
|
||||
@@ -88,6 +88,10 @@ variables_cv: ContextVar[Any | None] = ContextVar("variables_cv", default=None)
|
||||
trace_id_cv: ContextVar[tuple[str, str] | None] = ContextVar(
|
||||
"trace_id_cv", default=None
|
||||
)
|
||||
# Reason for stopped script execution
|
||||
script_execution_cv: ContextVar[StopReason | None] = ContextVar(
|
||||
"script_execution_cv", default=None
|
||||
)
|
||||
|
||||
|
||||
def trace_id_set(trace_id: tuple[str, str]) -> None:
|
||||
@@ -172,6 +176,7 @@ def trace_clear() -> None:
|
||||
trace_stack_cv.set(None)
|
||||
trace_path_stack_cv.set(None)
|
||||
variables_cv.set(None)
|
||||
script_execution_cv.set(StopReason())
|
||||
|
||||
|
||||
def trace_set_child_id(child_key: tuple[str, str], child_run_id: str) -> None:
|
||||
@@ -187,6 +192,28 @@ def trace_set_result(**kwargs: Any) -> None:
|
||||
node.set_result(**kwargs)
|
||||
|
||||
|
||||
class StopReason:
|
||||
"""Mutable container class for script_execution."""
|
||||
|
||||
script_execution: str | None = None
|
||||
|
||||
|
||||
def script_execution_set(reason: str) -> None:
|
||||
"""Set stop reason."""
|
||||
data = script_execution_cv.get()
|
||||
if data is None:
|
||||
return
|
||||
data.script_execution = reason
|
||||
|
||||
|
||||
def script_execution_get() -> str | None:
|
||||
"""Return the current trace."""
|
||||
data = script_execution_cv.get()
|
||||
if data is None:
|
||||
return None
|
||||
return data.script_execution
|
||||
|
||||
|
||||
@contextmanager
|
||||
def trace_path(suffix: str | list[str]) -> Generator:
|
||||
"""Go deeper in the config tree.
|
||||
|
||||
@@ -16,7 +16,7 @@ defusedxml==0.6.0
|
||||
distro==1.5.0
|
||||
emoji==1.2.0
|
||||
hass-nabucasa==0.42.0
|
||||
home-assistant-frontend==20210331.0
|
||||
home-assistant-frontend==20210402.0
|
||||
httpx==0.17.1
|
||||
jinja2>=2.11.3
|
||||
netdisco==2.8.2
|
||||
|
||||
@@ -11,7 +11,7 @@ import traceback
|
||||
from typing import Any, Awaitable, Callable, Coroutine, cast, overload
|
||||
|
||||
from homeassistant.const import EVENT_HOMEASSISTANT_CLOSE
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.core import HomeAssistant, callback, is_callback
|
||||
|
||||
|
||||
class HideSensitiveDataFilter(logging.Filter):
|
||||
@@ -138,6 +138,7 @@ def catch_log_exception(
|
||||
log_exception(format_err, *args)
|
||||
|
||||
wrapper_func = async_wrapper
|
||||
|
||||
else:
|
||||
|
||||
@wraps(func)
|
||||
@@ -148,6 +149,9 @@ def catch_log_exception(
|
||||
except Exception: # pylint: disable=broad-except
|
||||
log_exception(format_err, *args)
|
||||
|
||||
if is_callback(check_func):
|
||||
wrapper = callback(wrapper)
|
||||
|
||||
wrapper_func = wrapper
|
||||
return wrapper_func
|
||||
|
||||
|
||||
@@ -313,7 +313,7 @@ av==8.0.3
|
||||
# avion==0.10
|
||||
|
||||
# homeassistant.components.axis
|
||||
axis==43
|
||||
axis==44
|
||||
|
||||
# homeassistant.components.azure_event_hub
|
||||
azure-eventhub==5.1.0
|
||||
@@ -763,7 +763,7 @@ hole==0.5.1
|
||||
holidays==0.10.5.2
|
||||
|
||||
# homeassistant.components.frontend
|
||||
home-assistant-frontend==20210331.0
|
||||
home-assistant-frontend==20210402.0
|
||||
|
||||
# homeassistant.components.zwave
|
||||
homeassistant-pyozw==0.1.10
|
||||
@@ -1019,7 +1019,7 @@ numato-gpio==0.10.0
|
||||
# homeassistant.components.opencv
|
||||
# homeassistant.components.tensorflow
|
||||
# homeassistant.components.trend
|
||||
numpy==1.19.2
|
||||
numpy==1.20.2
|
||||
|
||||
# homeassistant.components.oasa_telematics
|
||||
oasatelematics==0.3
|
||||
|
||||
@@ -187,7 +187,7 @@ auroranoaa==0.0.2
|
||||
av==8.0.3
|
||||
|
||||
# homeassistant.components.axis
|
||||
axis==43
|
||||
axis==44
|
||||
|
||||
# homeassistant.components.azure_event_hub
|
||||
azure-eventhub==5.1.0
|
||||
@@ -412,7 +412,7 @@ hole==0.5.1
|
||||
holidays==0.10.5.2
|
||||
|
||||
# homeassistant.components.frontend
|
||||
home-assistant-frontend==20210331.0
|
||||
home-assistant-frontend==20210402.0
|
||||
|
||||
# homeassistant.components.zwave
|
||||
homeassistant-pyozw==0.1.10
|
||||
@@ -531,7 +531,7 @@ numato-gpio==0.10.0
|
||||
# homeassistant.components.opencv
|
||||
# homeassistant.components.tensorflow
|
||||
# homeassistant.components.trend
|
||||
numpy==1.19.2
|
||||
numpy==1.20.2
|
||||
|
||||
# homeassistant.components.google
|
||||
oauth2client==4.0.0
|
||||
|
||||
@@ -320,7 +320,17 @@ async def test_create_account(hass, client):
|
||||
"title": "Test Entry",
|
||||
"type": "create_entry",
|
||||
"version": 1,
|
||||
"result": entries[0].entry_id,
|
||||
"result": {
|
||||
"connection_class": "unknown",
|
||||
"disabled_by": None,
|
||||
"domain": "test",
|
||||
"entry_id": entries[0].entry_id,
|
||||
"source": "user",
|
||||
"state": "loaded",
|
||||
"supports_options": False,
|
||||
"supports_unload": False,
|
||||
"title": "Test Entry",
|
||||
},
|
||||
"description": None,
|
||||
"description_placeholders": None,
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
from pyownet.protocol import ProtocolError
|
||||
|
||||
from homeassistant.components.onewire.const import (
|
||||
CONF_MOUNT_DIR,
|
||||
CONF_NAMES,
|
||||
@@ -13,6 +15,8 @@ from homeassistant.components.onewire.const import (
|
||||
from homeassistant.config_entries import CONN_CLASS_LOCAL_POLL
|
||||
from homeassistant.const import CONF_HOST, CONF_PORT, CONF_TYPE
|
||||
|
||||
from .const import MOCK_OWPROXY_DEVICES
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
|
||||
@@ -89,3 +93,35 @@ async def setup_onewire_patched_owserver_integration(hass):
|
||||
await hass.async_block_till_done()
|
||||
|
||||
return config_entry
|
||||
|
||||
|
||||
def setup_owproxy_mock_devices(owproxy, domain, device_ids) -> None:
|
||||
"""Set up mock for owproxy."""
|
||||
dir_return_value = []
|
||||
main_read_side_effect = []
|
||||
sub_read_side_effect = []
|
||||
|
||||
for device_id in device_ids:
|
||||
mock_device = MOCK_OWPROXY_DEVICES[device_id]
|
||||
|
||||
# Setup directory listing
|
||||
dir_return_value += [f"/{device_id}/"]
|
||||
|
||||
# Setup device reads
|
||||
main_read_side_effect += [device_id[0:2].encode()]
|
||||
if "inject_reads" in mock_device:
|
||||
main_read_side_effect += mock_device["inject_reads"]
|
||||
|
||||
# Setup sub-device reads
|
||||
device_sensors = mock_device.get(domain, [])
|
||||
for expected_sensor in device_sensors:
|
||||
sub_read_side_effect.append(expected_sensor["injected_value"])
|
||||
|
||||
# Ensure enough read side effect
|
||||
read_side_effect = (
|
||||
main_read_side_effect
|
||||
+ sub_read_side_effect
|
||||
+ [ProtocolError("Missing injected value")] * 20
|
||||
)
|
||||
owproxy.return_value.dir.return_value = dir_return_value
|
||||
owproxy.return_value.read.side_effect = read_side_effect
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
"""Tests for 1-Wire devices connected on OWServer."""
|
||||
from unittest.mock import patch
|
||||
"""Constants for 1-Wire integration."""
|
||||
|
||||
from pi1wire import InvalidCRCException, UnsupportResponseException
|
||||
from pyownet.protocol import Error as ProtocolError
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN
|
||||
from homeassistant.components.onewire.const import DOMAIN, PLATFORMS, PRESSURE_CBAR
|
||||
from homeassistant.components.onewire.const import DOMAIN, PRESSURE_CBAR
|
||||
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
|
||||
from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN
|
||||
from homeassistant.const import (
|
||||
@@ -24,13 +23,8 @@ from homeassistant.const import (
|
||||
TEMP_CELSIUS,
|
||||
VOLT,
|
||||
)
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
from . import setup_onewire_patched_owserver_integration
|
||||
|
||||
from tests.common import mock_device_registry, mock_registry
|
||||
|
||||
MOCK_DEVICE_SENSORS = {
|
||||
MOCK_OWPROXY_DEVICES = {
|
||||
"00.111111111111": {
|
||||
"inject_reads": [
|
||||
b"", # read device type
|
||||
@@ -186,7 +180,42 @@ MOCK_DEVICE_SENSORS = {
|
||||
"model": "DS2409",
|
||||
"name": "1F.111111111111",
|
||||
},
|
||||
SENSOR_DOMAIN: [],
|
||||
"branches": {
|
||||
"aux": {},
|
||||
"main": {
|
||||
"1D.111111111111": {
|
||||
"inject_reads": [
|
||||
b"DS2423", # read device type
|
||||
],
|
||||
"device_info": {
|
||||
"identifiers": {(DOMAIN, "1D.111111111111")},
|
||||
"manufacturer": "Maxim Integrated",
|
||||
"model": "DS2423",
|
||||
"name": "1D.111111111111",
|
||||
},
|
||||
SENSOR_DOMAIN: [
|
||||
{
|
||||
"entity_id": "sensor.1d_111111111111_counter_a",
|
||||
"device_file": "/1F.111111111111/main/1D.111111111111/counter.A",
|
||||
"unique_id": "/1D.111111111111/counter.A",
|
||||
"injected_value": b" 251123",
|
||||
"result": "251123",
|
||||
"unit": "count",
|
||||
"class": None,
|
||||
},
|
||||
{
|
||||
"entity_id": "sensor.1d_111111111111_counter_b",
|
||||
"device_file": "/1F.111111111111/main/1D.111111111111/counter.B",
|
||||
"unique_id": "/1D.111111111111/counter.B",
|
||||
"injected_value": b" 248125",
|
||||
"result": "248125",
|
||||
"unit": "count",
|
||||
"class": None,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"22.111111111111": {
|
||||
"inject_reads": [
|
||||
@@ -748,65 +777,106 @@ MOCK_DEVICE_SENSORS = {
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.parametrize("device_id", MOCK_DEVICE_SENSORS.keys())
|
||||
@pytest.mark.parametrize("platform", PLATFORMS)
|
||||
@patch("homeassistant.components.onewire.onewirehub.protocol.proxy")
|
||||
async def test_owserver_setup_valid_device(owproxy, hass, device_id, platform):
|
||||
"""Test for 1-Wire device.
|
||||
|
||||
As they would be on a clean setup: all binary-sensors and switches disabled.
|
||||
"""
|
||||
await async_setup_component(hass, "persistent_notification", {})
|
||||
entity_registry = mock_registry(hass)
|
||||
device_registry = mock_device_registry(hass)
|
||||
|
||||
mock_device_sensor = MOCK_DEVICE_SENSORS[device_id]
|
||||
|
||||
device_family = device_id[0:2]
|
||||
dir_return_value = [f"/{device_id}/"]
|
||||
read_side_effect = [device_family.encode()]
|
||||
if "inject_reads" in mock_device_sensor:
|
||||
read_side_effect += mock_device_sensor["inject_reads"]
|
||||
|
||||
expected_sensors = mock_device_sensor.get(platform, [])
|
||||
for expected_sensor in expected_sensors:
|
||||
read_side_effect.append(expected_sensor["injected_value"])
|
||||
|
||||
# Ensure enough read side effect
|
||||
read_side_effect.extend([ProtocolError("Missing injected value")] * 20)
|
||||
owproxy.return_value.dir.return_value = dir_return_value
|
||||
owproxy.return_value.read.side_effect = read_side_effect
|
||||
|
||||
with patch("homeassistant.components.onewire.PLATFORMS", [platform]):
|
||||
await setup_onewire_patched_owserver_integration(hass)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert len(entity_registry.entities) == len(expected_sensors)
|
||||
|
||||
if len(expected_sensors) > 0:
|
||||
device_info = mock_device_sensor["device_info"]
|
||||
assert len(device_registry.devices) == 1
|
||||
registry_entry = device_registry.async_get_device({(DOMAIN, device_id)})
|
||||
assert registry_entry is not None
|
||||
assert registry_entry.identifiers == {(DOMAIN, device_id)}
|
||||
assert registry_entry.manufacturer == device_info["manufacturer"]
|
||||
assert registry_entry.name == device_info["name"]
|
||||
assert registry_entry.model == device_info["model"]
|
||||
|
||||
for expected_sensor in expected_sensors:
|
||||
entity_id = expected_sensor["entity_id"]
|
||||
registry_entry = entity_registry.entities.get(entity_id)
|
||||
assert registry_entry is not None
|
||||
assert registry_entry.unique_id == expected_sensor["unique_id"]
|
||||
assert registry_entry.unit_of_measurement == expected_sensor["unit"]
|
||||
assert registry_entry.device_class == expected_sensor["class"]
|
||||
assert registry_entry.disabled == expected_sensor.get("disabled", False)
|
||||
state = hass.states.get(entity_id)
|
||||
if registry_entry.disabled:
|
||||
assert state is None
|
||||
else:
|
||||
assert state.state == expected_sensor["result"]
|
||||
assert state.attributes["device_file"] == expected_sensor.get(
|
||||
"device_file", registry_entry.unique_id
|
||||
)
|
||||
MOCK_SYSBUS_DEVICES = {
|
||||
"00-111111111111": {"sensors": []},
|
||||
"10-111111111111": {
|
||||
"device_info": {
|
||||
"identifiers": {(DOMAIN, "10-111111111111")},
|
||||
"manufacturer": "Maxim Integrated",
|
||||
"model": "10",
|
||||
"name": "10-111111111111",
|
||||
},
|
||||
"sensors": [
|
||||
{
|
||||
"entity_id": "sensor.my_ds18b20_temperature",
|
||||
"unique_id": "/sys/bus/w1/devices/10-111111111111/w1_slave",
|
||||
"injected_value": 25.123,
|
||||
"result": "25.1",
|
||||
"unit": TEMP_CELSIUS,
|
||||
"class": DEVICE_CLASS_TEMPERATURE,
|
||||
},
|
||||
],
|
||||
},
|
||||
"12-111111111111": {"sensors": []},
|
||||
"1D-111111111111": {"sensors": []},
|
||||
"22-111111111111": {
|
||||
"device_info": {
|
||||
"identifiers": {(DOMAIN, "22-111111111111")},
|
||||
"manufacturer": "Maxim Integrated",
|
||||
"model": "22",
|
||||
"name": "22-111111111111",
|
||||
},
|
||||
"sensors": [
|
||||
{
|
||||
"entity_id": "sensor.22_111111111111_temperature",
|
||||
"unique_id": "/sys/bus/w1/devices/22-111111111111/w1_slave",
|
||||
"injected_value": FileNotFoundError,
|
||||
"result": "unknown",
|
||||
"unit": TEMP_CELSIUS,
|
||||
"class": DEVICE_CLASS_TEMPERATURE,
|
||||
},
|
||||
],
|
||||
},
|
||||
"26-111111111111": {"sensors": []},
|
||||
"28-111111111111": {
|
||||
"device_info": {
|
||||
"identifiers": {(DOMAIN, "28-111111111111")},
|
||||
"manufacturer": "Maxim Integrated",
|
||||
"model": "28",
|
||||
"name": "28-111111111111",
|
||||
},
|
||||
"sensors": [
|
||||
{
|
||||
"entity_id": "sensor.28_111111111111_temperature",
|
||||
"unique_id": "/sys/bus/w1/devices/28-111111111111/w1_slave",
|
||||
"injected_value": InvalidCRCException,
|
||||
"result": "unknown",
|
||||
"unit": TEMP_CELSIUS,
|
||||
"class": DEVICE_CLASS_TEMPERATURE,
|
||||
},
|
||||
],
|
||||
},
|
||||
"29-111111111111": {"sensors": []},
|
||||
"3B-111111111111": {
|
||||
"device_info": {
|
||||
"identifiers": {(DOMAIN, "3B-111111111111")},
|
||||
"manufacturer": "Maxim Integrated",
|
||||
"model": "3B",
|
||||
"name": "3B-111111111111",
|
||||
},
|
||||
"sensors": [
|
||||
{
|
||||
"entity_id": "sensor.3b_111111111111_temperature",
|
||||
"unique_id": "/sys/bus/w1/devices/3B-111111111111/w1_slave",
|
||||
"injected_value": 29.993,
|
||||
"result": "30.0",
|
||||
"unit": TEMP_CELSIUS,
|
||||
"class": DEVICE_CLASS_TEMPERATURE,
|
||||
},
|
||||
],
|
||||
},
|
||||
"42-111111111111": {
|
||||
"device_info": {
|
||||
"identifiers": {(DOMAIN, "42-111111111111")},
|
||||
"manufacturer": "Maxim Integrated",
|
||||
"model": "42",
|
||||
"name": "42-111111111111",
|
||||
},
|
||||
"sensors": [
|
||||
{
|
||||
"entity_id": "sensor.42_111111111111_temperature",
|
||||
"unique_id": "/sys/bus/w1/devices/42-111111111111/w1_slave",
|
||||
"injected_value": UnsupportResponseException,
|
||||
"result": "unknown",
|
||||
"unit": TEMP_CELSIUS,
|
||||
"class": DEVICE_CLASS_TEMPERATURE,
|
||||
},
|
||||
],
|
||||
},
|
||||
"EF-111111111111": {
|
||||
"sensors": [],
|
||||
},
|
||||
"EF-111111111112": {
|
||||
"sensors": [],
|
||||
},
|
||||
}
|
||||
@@ -2,40 +2,25 @@
|
||||
import copy
|
||||
from unittest.mock import patch
|
||||
|
||||
from pyownet.protocol import Error as ProtocolError
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN
|
||||
from homeassistant.components.onewire.binary_sensor import DEVICE_BINARY_SENSORS
|
||||
from homeassistant.const import STATE_OFF, STATE_ON
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
from . import setup_onewire_patched_owserver_integration
|
||||
from . import setup_onewire_patched_owserver_integration, setup_owproxy_mock_devices
|
||||
from .const import MOCK_OWPROXY_DEVICES
|
||||
|
||||
from tests.common import mock_registry
|
||||
|
||||
MOCK_DEVICE_SENSORS = {
|
||||
"12.111111111111": {
|
||||
"inject_reads": [
|
||||
b"DS2406", # read device type
|
||||
],
|
||||
BINARY_SENSOR_DOMAIN: [
|
||||
{
|
||||
"entity_id": "binary_sensor.12_111111111111_sensed_a",
|
||||
"injected_value": b" 1",
|
||||
"result": STATE_ON,
|
||||
},
|
||||
{
|
||||
"entity_id": "binary_sensor.12_111111111111_sensed_b",
|
||||
"injected_value": b" 0",
|
||||
"result": STATE_OFF,
|
||||
},
|
||||
],
|
||||
},
|
||||
MOCK_BINARY_SENSORS = {
|
||||
key: value
|
||||
for (key, value) in MOCK_OWPROXY_DEVICES.items()
|
||||
if BINARY_SENSOR_DOMAIN in value
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.parametrize("device_id", MOCK_DEVICE_SENSORS.keys())
|
||||
@pytest.mark.parametrize("device_id", MOCK_BINARY_SENSORS.keys())
|
||||
@patch("homeassistant.components.onewire.onewirehub.protocol.proxy")
|
||||
async def test_owserver_binary_sensor(owproxy, hass, device_id):
|
||||
"""Test for 1-Wire binary sensor.
|
||||
@@ -45,26 +30,14 @@ async def test_owserver_binary_sensor(owproxy, hass, device_id):
|
||||
await async_setup_component(hass, "persistent_notification", {})
|
||||
entity_registry = mock_registry(hass)
|
||||
|
||||
mock_device_sensor = MOCK_DEVICE_SENSORS[device_id]
|
||||
setup_owproxy_mock_devices(owproxy, BINARY_SENSOR_DOMAIN, [device_id])
|
||||
|
||||
device_family = device_id[0:2]
|
||||
dir_return_value = [f"/{device_id}/"]
|
||||
read_side_effect = [device_family.encode()]
|
||||
if "inject_reads" in mock_device_sensor:
|
||||
read_side_effect += mock_device_sensor["inject_reads"]
|
||||
|
||||
expected_sensors = mock_device_sensor[BINARY_SENSOR_DOMAIN]
|
||||
for expected_sensor in expected_sensors:
|
||||
read_side_effect.append(expected_sensor["injected_value"])
|
||||
|
||||
# Ensure enough read side effect
|
||||
read_side_effect.extend([ProtocolError("Missing injected value")] * 10)
|
||||
owproxy.return_value.dir.return_value = dir_return_value
|
||||
owproxy.return_value.read.side_effect = read_side_effect
|
||||
mock_device = MOCK_BINARY_SENSORS[device_id]
|
||||
expected_entities = mock_device[BINARY_SENSOR_DOMAIN]
|
||||
|
||||
# Force enable binary sensors
|
||||
patch_device_binary_sensors = copy.deepcopy(DEVICE_BINARY_SENSORS)
|
||||
for item in patch_device_binary_sensors[device_family]:
|
||||
for item in patch_device_binary_sensors[device_id[0:2]]:
|
||||
item["default_disabled"] = False
|
||||
|
||||
with patch(
|
||||
@@ -76,14 +49,14 @@ async def test_owserver_binary_sensor(owproxy, hass, device_id):
|
||||
await setup_onewire_patched_owserver_integration(hass)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert len(entity_registry.entities) == len(expected_sensors)
|
||||
assert len(entity_registry.entities) == len(expected_entities)
|
||||
|
||||
for expected_sensor in expected_sensors:
|
||||
entity_id = expected_sensor["entity_id"]
|
||||
for expected_entity in expected_entities:
|
||||
entity_id = expected_entity["entity_id"]
|
||||
registry_entry = entity_registry.entities.get(entity_id)
|
||||
assert registry_entry is not None
|
||||
state = hass.states.get(entity_id)
|
||||
assert state.state == expected_sensor["result"]
|
||||
assert state.attributes["device_file"] == expected_sensor.get(
|
||||
assert state.state == expected_entity["result"]
|
||||
assert state.attributes["device_file"] == expected_entity.get(
|
||||
"device_file", registry_entry.unique_id
|
||||
)
|
||||
|
||||
@@ -1,175 +0,0 @@
|
||||
"""Tests for 1-Wire devices connected on SysBus."""
|
||||
from unittest.mock import patch
|
||||
|
||||
from pi1wire import InvalidCRCException, UnsupportResponseException
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.onewire.const import DEFAULT_SYSBUS_MOUNT_DIR, DOMAIN
|
||||
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
|
||||
from homeassistant.const import DEVICE_CLASS_TEMPERATURE, TEMP_CELSIUS
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
from tests.common import mock_device_registry, mock_registry
|
||||
|
||||
MOCK_CONFIG = {
|
||||
SENSOR_DOMAIN: {
|
||||
"platform": DOMAIN,
|
||||
"mount_dir": DEFAULT_SYSBUS_MOUNT_DIR,
|
||||
"names": {
|
||||
"10-111111111111": "My DS18B20",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
MOCK_DEVICE_SENSORS = {
|
||||
"00-111111111111": {"sensors": []},
|
||||
"10-111111111111": {
|
||||
"device_info": {
|
||||
"identifiers": {(DOMAIN, "10-111111111111")},
|
||||
"manufacturer": "Maxim Integrated",
|
||||
"model": "10",
|
||||
"name": "10-111111111111",
|
||||
},
|
||||
"sensors": [
|
||||
{
|
||||
"entity_id": "sensor.my_ds18b20_temperature",
|
||||
"unique_id": "/sys/bus/w1/devices/10-111111111111/w1_slave",
|
||||
"injected_value": 25.123,
|
||||
"result": "25.1",
|
||||
"unit": TEMP_CELSIUS,
|
||||
"class": DEVICE_CLASS_TEMPERATURE,
|
||||
},
|
||||
],
|
||||
},
|
||||
"12-111111111111": {"sensors": []},
|
||||
"1D-111111111111": {"sensors": []},
|
||||
"22-111111111111": {
|
||||
"device_info": {
|
||||
"identifiers": {(DOMAIN, "22-111111111111")},
|
||||
"manufacturer": "Maxim Integrated",
|
||||
"model": "22",
|
||||
"name": "22-111111111111",
|
||||
},
|
||||
"sensors": [
|
||||
{
|
||||
"entity_id": "sensor.22_111111111111_temperature",
|
||||
"unique_id": "/sys/bus/w1/devices/22-111111111111/w1_slave",
|
||||
"injected_value": FileNotFoundError,
|
||||
"result": "unknown",
|
||||
"unit": TEMP_CELSIUS,
|
||||
"class": DEVICE_CLASS_TEMPERATURE,
|
||||
},
|
||||
],
|
||||
},
|
||||
"26-111111111111": {"sensors": []},
|
||||
"28-111111111111": {
|
||||
"device_info": {
|
||||
"identifiers": {(DOMAIN, "28-111111111111")},
|
||||
"manufacturer": "Maxim Integrated",
|
||||
"model": "28",
|
||||
"name": "28-111111111111",
|
||||
},
|
||||
"sensors": [
|
||||
{
|
||||
"entity_id": "sensor.28_111111111111_temperature",
|
||||
"unique_id": "/sys/bus/w1/devices/28-111111111111/w1_slave",
|
||||
"injected_value": InvalidCRCException,
|
||||
"result": "unknown",
|
||||
"unit": TEMP_CELSIUS,
|
||||
"class": DEVICE_CLASS_TEMPERATURE,
|
||||
},
|
||||
],
|
||||
},
|
||||
"29-111111111111": {"sensors": []},
|
||||
"3B-111111111111": {
|
||||
"device_info": {
|
||||
"identifiers": {(DOMAIN, "3B-111111111111")},
|
||||
"manufacturer": "Maxim Integrated",
|
||||
"model": "3B",
|
||||
"name": "3B-111111111111",
|
||||
},
|
||||
"sensors": [
|
||||
{
|
||||
"entity_id": "sensor.3b_111111111111_temperature",
|
||||
"unique_id": "/sys/bus/w1/devices/3B-111111111111/w1_slave",
|
||||
"injected_value": 29.993,
|
||||
"result": "30.0",
|
||||
"unit": TEMP_CELSIUS,
|
||||
"class": DEVICE_CLASS_TEMPERATURE,
|
||||
},
|
||||
],
|
||||
},
|
||||
"42-111111111111": {
|
||||
"device_info": {
|
||||
"identifiers": {(DOMAIN, "42-111111111111")},
|
||||
"manufacturer": "Maxim Integrated",
|
||||
"model": "42",
|
||||
"name": "42-111111111111",
|
||||
},
|
||||
"sensors": [
|
||||
{
|
||||
"entity_id": "sensor.42_111111111111_temperature",
|
||||
"unique_id": "/sys/bus/w1/devices/42-111111111111/w1_slave",
|
||||
"injected_value": UnsupportResponseException,
|
||||
"result": "unknown",
|
||||
"unit": TEMP_CELSIUS,
|
||||
"class": DEVICE_CLASS_TEMPERATURE,
|
||||
},
|
||||
],
|
||||
},
|
||||
"EF-111111111111": {
|
||||
"sensors": [],
|
||||
},
|
||||
"EF-111111111112": {
|
||||
"sensors": [],
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.parametrize("device_id", MOCK_DEVICE_SENSORS.keys())
|
||||
async def test_onewiredirect_setup_valid_device(hass, device_id):
|
||||
"""Test that sysbus config entry works correctly."""
|
||||
entity_registry = mock_registry(hass)
|
||||
device_registry = mock_device_registry(hass)
|
||||
|
||||
mock_device_sensor = MOCK_DEVICE_SENSORS[device_id]
|
||||
|
||||
glob_result = [f"/{DEFAULT_SYSBUS_MOUNT_DIR}/{device_id}"]
|
||||
read_side_effect = []
|
||||
expected_sensors = mock_device_sensor["sensors"]
|
||||
for expected_sensor in expected_sensors:
|
||||
read_side_effect.append(expected_sensor["injected_value"])
|
||||
|
||||
# Ensure enough read side effect
|
||||
read_side_effect.extend([FileNotFoundError("Missing injected value")] * 20)
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.onewire.onewirehub.os.path.isdir", return_value=True
|
||||
), patch("pi1wire._finder.glob.glob", return_value=glob_result,), patch(
|
||||
"pi1wire.OneWire.get_temperature",
|
||||
side_effect=read_side_effect,
|
||||
):
|
||||
assert await async_setup_component(hass, SENSOR_DOMAIN, MOCK_CONFIG)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert len(entity_registry.entities) == len(expected_sensors)
|
||||
|
||||
if len(expected_sensors) > 0:
|
||||
device_info = mock_device_sensor["device_info"]
|
||||
assert len(device_registry.devices) == 1
|
||||
registry_entry = device_registry.async_get_device({(DOMAIN, device_id)})
|
||||
assert registry_entry is not None
|
||||
assert registry_entry.identifiers == {(DOMAIN, device_id)}
|
||||
assert registry_entry.manufacturer == device_info["manufacturer"]
|
||||
assert registry_entry.name == device_info["name"]
|
||||
assert registry_entry.model == device_info["model"]
|
||||
|
||||
for expected_sensor in expected_sensors:
|
||||
entity_id = expected_sensor["entity_id"]
|
||||
registry_entry = entity_registry.entities.get(entity_id)
|
||||
assert registry_entry is not None
|
||||
assert registry_entry.unique_id == expected_sensor["unique_id"]
|
||||
assert registry_entry.unit_of_measurement == expected_sensor["unit"]
|
||||
assert registry_entry.device_class == expected_sensor["class"]
|
||||
state = hass.states.get(entity_id)
|
||||
assert state.state == expected_sensor["result"]
|
||||
@@ -4,6 +4,7 @@ from unittest.mock import patch
|
||||
from pyownet.protocol import ConnError, OwnetError
|
||||
|
||||
from homeassistant.components.onewire.const import CONF_TYPE_OWSERVER, DOMAIN
|
||||
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
|
||||
from homeassistant.config_entries import (
|
||||
CONN_CLASS_LOCAL_POLL,
|
||||
ENTRY_STATE_LOADED,
|
||||
@@ -11,10 +12,17 @@ from homeassistant.config_entries import (
|
||||
ENTRY_STATE_SETUP_RETRY,
|
||||
)
|
||||
from homeassistant.const import CONF_HOST, CONF_PORT, CONF_TYPE
|
||||
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
from . import setup_onewire_owserver_integration, setup_onewire_sysbus_integration
|
||||
from . import (
|
||||
setup_onewire_owserver_integration,
|
||||
setup_onewire_patched_owserver_integration,
|
||||
setup_onewire_sysbus_integration,
|
||||
setup_owproxy_mock_devices,
|
||||
)
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
from tests.common import MockConfigEntry, mock_device_registry, mock_registry
|
||||
|
||||
|
||||
async def test_owserver_connect_failure(hass):
|
||||
@@ -87,3 +95,41 @@ async def test_unload_entry(hass):
|
||||
assert config_entry_owserver.state == ENTRY_STATE_NOT_LOADED
|
||||
assert config_entry_sysbus.state == ENTRY_STATE_NOT_LOADED
|
||||
assert not hass.data.get(DOMAIN)
|
||||
|
||||
|
||||
@patch("homeassistant.components.onewire.onewirehub.protocol.proxy")
|
||||
async def test_registry_cleanup(owproxy, hass):
|
||||
"""Test for 1-Wire device.
|
||||
|
||||
As they would be on a clean setup: all binary-sensors and switches disabled.
|
||||
"""
|
||||
await async_setup_component(hass, "persistent_notification", {})
|
||||
entity_registry = mock_registry(hass)
|
||||
device_registry = mock_device_registry(hass)
|
||||
|
||||
# Initialise with two components
|
||||
setup_owproxy_mock_devices(
|
||||
owproxy, SENSOR_DOMAIN, ["10.111111111111", "28.111111111111"]
|
||||
)
|
||||
with patch("homeassistant.components.onewire.PLATFORMS", [SENSOR_DOMAIN]):
|
||||
await setup_onewire_patched_owserver_integration(hass)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert len(dr.async_entries_for_config_entry(device_registry, "2")) == 2
|
||||
assert len(er.async_entries_for_config_entry(entity_registry, "2")) == 2
|
||||
|
||||
# Second item has disappeared from bus, and was removed manually from the front-end
|
||||
setup_owproxy_mock_devices(owproxy, SENSOR_DOMAIN, ["10.111111111111"])
|
||||
entity_registry.async_remove("sensor.28_111111111111_temperature")
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert len(er.async_entries_for_config_entry(entity_registry, "2")) == 1
|
||||
assert len(dr.async_entries_for_config_entry(device_registry, "2")) == 2
|
||||
|
||||
# Second item has disappeared from bus, and was removed manually from the front-end
|
||||
with patch("homeassistant.components.onewire.PLATFORMS", [SENSOR_DOMAIN]):
|
||||
await hass.config_entries.async_reload("2")
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert len(er.async_entries_for_config_entry(entity_registry, "2")) == 1
|
||||
assert len(dr.async_entries_for_config_entry(device_registry, "2")) == 1
|
||||
|
||||
@@ -4,54 +4,29 @@ from unittest.mock import patch
|
||||
from pyownet.protocol import Error as ProtocolError
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.onewire.const import DEFAULT_SYSBUS_MOUNT_DIR, DOMAIN
|
||||
from homeassistant.components.onewire.const import (
|
||||
DEFAULT_SYSBUS_MOUNT_DIR,
|
||||
DOMAIN,
|
||||
PLATFORMS,
|
||||
)
|
||||
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
from . import setup_onewire_patched_owserver_integration
|
||||
from . import setup_onewire_patched_owserver_integration, setup_owproxy_mock_devices
|
||||
from .const import MOCK_OWPROXY_DEVICES, MOCK_SYSBUS_DEVICES
|
||||
|
||||
from tests.common import assert_setup_component, mock_registry
|
||||
from tests.common import assert_setup_component, mock_device_registry, mock_registry
|
||||
|
||||
MOCK_COUPLERS = {
|
||||
"1F.111111111111": {
|
||||
"inject_reads": [
|
||||
b"DS2409", # read device type
|
||||
],
|
||||
"branches": {
|
||||
"aux": {},
|
||||
"main": {
|
||||
"1D.111111111111": {
|
||||
"inject_reads": [
|
||||
b"DS2423", # read device type
|
||||
],
|
||||
"device_info": {
|
||||
"identifiers": {(DOMAIN, "1D.111111111111")},
|
||||
"manufacturer": "Maxim Integrated",
|
||||
"model": "DS2423",
|
||||
"name": "1D.111111111111",
|
||||
},
|
||||
SENSOR_DOMAIN: [
|
||||
{
|
||||
"entity_id": "sensor.1d_111111111111_counter_a",
|
||||
"device_file": "/1F.111111111111/main/1D.111111111111/counter.A",
|
||||
"unique_id": "/1D.111111111111/counter.A",
|
||||
"injected_value": b" 251123",
|
||||
"result": "251123",
|
||||
"unit": "count",
|
||||
"class": None,
|
||||
},
|
||||
{
|
||||
"entity_id": "sensor.1d_111111111111_counter_b",
|
||||
"device_file": "/1F.111111111111/main/1D.111111111111/counter.B",
|
||||
"unique_id": "/1D.111111111111/counter.B",
|
||||
"injected_value": b" 248125",
|
||||
"result": "248125",
|
||||
"unit": "count",
|
||||
"class": None,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
key: value for (key, value) in MOCK_OWPROXY_DEVICES.items() if "branches" in value
|
||||
}
|
||||
|
||||
MOCK_SYSBUS_CONFIG = {
|
||||
SENSOR_DOMAIN: {
|
||||
"platform": DOMAIN,
|
||||
"mount_dir": DEFAULT_SYSBUS_MOUNT_DIR,
|
||||
"names": {
|
||||
"10-111111111111": "My DS18B20",
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -154,3 +129,103 @@ async def test_sensors_on_owserver_coupler(owproxy, hass, device_id):
|
||||
else:
|
||||
assert state.state == expected_sensor["result"]
|
||||
assert state.attributes["device_file"] == expected_sensor["device_file"]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("device_id", MOCK_OWPROXY_DEVICES.keys())
|
||||
@pytest.mark.parametrize("platform", PLATFORMS)
|
||||
@patch("homeassistant.components.onewire.onewirehub.protocol.proxy")
|
||||
async def test_owserver_setup_valid_device(owproxy, hass, device_id, platform):
|
||||
"""Test for 1-Wire device.
|
||||
|
||||
As they would be on a clean setup: all binary-sensors and switches disabled.
|
||||
"""
|
||||
await async_setup_component(hass, "persistent_notification", {})
|
||||
entity_registry = mock_registry(hass)
|
||||
device_registry = mock_device_registry(hass)
|
||||
|
||||
setup_owproxy_mock_devices(owproxy, platform, [device_id])
|
||||
|
||||
mock_device = MOCK_OWPROXY_DEVICES[device_id]
|
||||
expected_entities = mock_device.get(platform, [])
|
||||
|
||||
with patch("homeassistant.components.onewire.PLATFORMS", [platform]):
|
||||
await setup_onewire_patched_owserver_integration(hass)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert len(entity_registry.entities) == len(expected_entities)
|
||||
|
||||
if len(expected_entities) > 0:
|
||||
device_info = mock_device["device_info"]
|
||||
assert len(device_registry.devices) == 1
|
||||
registry_entry = device_registry.async_get_device({(DOMAIN, device_id)})
|
||||
assert registry_entry is not None
|
||||
assert registry_entry.identifiers == {(DOMAIN, device_id)}
|
||||
assert registry_entry.manufacturer == device_info["manufacturer"]
|
||||
assert registry_entry.name == device_info["name"]
|
||||
assert registry_entry.model == device_info["model"]
|
||||
|
||||
for expected_entity in expected_entities:
|
||||
entity_id = expected_entity["entity_id"]
|
||||
registry_entry = entity_registry.entities.get(entity_id)
|
||||
assert registry_entry is not None
|
||||
assert registry_entry.unique_id == expected_entity["unique_id"]
|
||||
assert registry_entry.unit_of_measurement == expected_entity["unit"]
|
||||
assert registry_entry.device_class == expected_entity["class"]
|
||||
assert registry_entry.disabled == expected_entity.get("disabled", False)
|
||||
state = hass.states.get(entity_id)
|
||||
if registry_entry.disabled:
|
||||
assert state is None
|
||||
else:
|
||||
assert state.state == expected_entity["result"]
|
||||
assert state.attributes["device_file"] == expected_entity.get(
|
||||
"device_file", registry_entry.unique_id
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("device_id", MOCK_SYSBUS_DEVICES.keys())
|
||||
async def test_onewiredirect_setup_valid_device(hass, device_id):
|
||||
"""Test that sysbus config entry works correctly."""
|
||||
entity_registry = mock_registry(hass)
|
||||
device_registry = mock_device_registry(hass)
|
||||
|
||||
mock_device_sensor = MOCK_SYSBUS_DEVICES[device_id]
|
||||
|
||||
glob_result = [f"/{DEFAULT_SYSBUS_MOUNT_DIR}/{device_id}"]
|
||||
read_side_effect = []
|
||||
expected_sensors = mock_device_sensor["sensors"]
|
||||
for expected_sensor in expected_sensors:
|
||||
read_side_effect.append(expected_sensor["injected_value"])
|
||||
|
||||
# Ensure enough read side effect
|
||||
read_side_effect.extend([FileNotFoundError("Missing injected value")] * 20)
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.onewire.onewirehub.os.path.isdir", return_value=True
|
||||
), patch("pi1wire._finder.glob.glob", return_value=glob_result,), patch(
|
||||
"pi1wire.OneWire.get_temperature",
|
||||
side_effect=read_side_effect,
|
||||
):
|
||||
assert await async_setup_component(hass, SENSOR_DOMAIN, MOCK_SYSBUS_CONFIG)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert len(entity_registry.entities) == len(expected_sensors)
|
||||
|
||||
if len(expected_sensors) > 0:
|
||||
device_info = mock_device_sensor["device_info"]
|
||||
assert len(device_registry.devices) == 1
|
||||
registry_entry = device_registry.async_get_device({(DOMAIN, device_id)})
|
||||
assert registry_entry is not None
|
||||
assert registry_entry.identifiers == {(DOMAIN, device_id)}
|
||||
assert registry_entry.manufacturer == device_info["manufacturer"]
|
||||
assert registry_entry.name == device_info["name"]
|
||||
assert registry_entry.model == device_info["model"]
|
||||
|
||||
for expected_sensor in expected_sensors:
|
||||
entity_id = expected_sensor["entity_id"]
|
||||
registry_entry = entity_registry.entities.get(entity_id)
|
||||
assert registry_entry is not None
|
||||
assert registry_entry.unique_id == expected_sensor["unique_id"]
|
||||
assert registry_entry.unit_of_measurement == expected_sensor["unit"]
|
||||
assert registry_entry.device_class == expected_sensor["class"]
|
||||
state = hass.states.get(entity_id)
|
||||
assert state.state == expected_sensor["result"]
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
import copy
|
||||
from unittest.mock import patch
|
||||
|
||||
from pyownet.protocol import Error as ProtocolError
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.onewire.switch import DEVICE_SWITCHES
|
||||
@@ -10,58 +9,19 @@ from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN
|
||||
from homeassistant.const import ATTR_ENTITY_ID, SERVICE_TOGGLE, STATE_OFF, STATE_ON
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
from . import setup_onewire_patched_owserver_integration
|
||||
from . import setup_onewire_patched_owserver_integration, setup_owproxy_mock_devices
|
||||
from .const import MOCK_OWPROXY_DEVICES
|
||||
|
||||
from tests.common import mock_registry
|
||||
|
||||
MOCK_DEVICE_SENSORS = {
|
||||
"12.111111111111": {
|
||||
"inject_reads": [
|
||||
b"DS2406", # read device type
|
||||
],
|
||||
SWITCH_DOMAIN: [
|
||||
{
|
||||
"entity_id": "switch.12_111111111111_pio_a",
|
||||
"unique_id": "/12.111111111111/PIO.A",
|
||||
"injected_value": b" 1",
|
||||
"result": STATE_ON,
|
||||
"unit": None,
|
||||
"class": None,
|
||||
"disabled": True,
|
||||
},
|
||||
{
|
||||
"entity_id": "switch.12_111111111111_pio_b",
|
||||
"unique_id": "/12.111111111111/PIO.B",
|
||||
"injected_value": b" 0",
|
||||
"result": STATE_OFF,
|
||||
"unit": None,
|
||||
"class": None,
|
||||
"disabled": True,
|
||||
},
|
||||
{
|
||||
"entity_id": "switch.12_111111111111_latch_a",
|
||||
"unique_id": "/12.111111111111/latch.A",
|
||||
"injected_value": b" 1",
|
||||
"result": STATE_ON,
|
||||
"unit": None,
|
||||
"class": None,
|
||||
"disabled": True,
|
||||
},
|
||||
{
|
||||
"entity_id": "switch.12_111111111111_latch_b",
|
||||
"unique_id": "/12.111111111111/latch.B",
|
||||
"injected_value": b" 0",
|
||||
"result": STATE_OFF,
|
||||
"unit": None,
|
||||
"class": None,
|
||||
"disabled": True,
|
||||
},
|
||||
],
|
||||
}
|
||||
MOCK_SWITCHES = {
|
||||
key: value
|
||||
for (key, value) in MOCK_OWPROXY_DEVICES.items()
|
||||
if SWITCH_DOMAIN in value
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.parametrize("device_id", ["12.111111111111"])
|
||||
@pytest.mark.parametrize("device_id", MOCK_SWITCHES.keys())
|
||||
@patch("homeassistant.components.onewire.onewirehub.protocol.proxy")
|
||||
async def test_owserver_switch(owproxy, hass, device_id):
|
||||
"""Test for 1-Wire switch.
|
||||
@@ -71,26 +31,14 @@ async def test_owserver_switch(owproxy, hass, device_id):
|
||||
await async_setup_component(hass, "persistent_notification", {})
|
||||
entity_registry = mock_registry(hass)
|
||||
|
||||
mock_device_sensor = MOCK_DEVICE_SENSORS[device_id]
|
||||
setup_owproxy_mock_devices(owproxy, SWITCH_DOMAIN, [device_id])
|
||||
|
||||
device_family = device_id[0:2]
|
||||
dir_return_value = [f"/{device_id}/"]
|
||||
read_side_effect = [device_family.encode()]
|
||||
if "inject_reads" in mock_device_sensor:
|
||||
read_side_effect += mock_device_sensor["inject_reads"]
|
||||
|
||||
expected_sensors = mock_device_sensor[SWITCH_DOMAIN]
|
||||
for expected_sensor in expected_sensors:
|
||||
read_side_effect.append(expected_sensor["injected_value"])
|
||||
|
||||
# Ensure enough read side effect
|
||||
read_side_effect.extend([ProtocolError("Missing injected value")] * 10)
|
||||
owproxy.return_value.dir.return_value = dir_return_value
|
||||
owproxy.return_value.read.side_effect = read_side_effect
|
||||
mock_device = MOCK_SWITCHES[device_id]
|
||||
expected_entities = mock_device[SWITCH_DOMAIN]
|
||||
|
||||
# Force enable switches
|
||||
patch_device_switches = copy.deepcopy(DEVICE_SWITCHES)
|
||||
for item in patch_device_switches[device_family]:
|
||||
for item in patch_device_switches[device_id[0:2]]:
|
||||
item["default_disabled"] = False
|
||||
|
||||
with patch(
|
||||
@@ -101,21 +49,21 @@ async def test_owserver_switch(owproxy, hass, device_id):
|
||||
await setup_onewire_patched_owserver_integration(hass)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert len(entity_registry.entities) == len(expected_sensors)
|
||||
assert len(entity_registry.entities) == len(expected_entities)
|
||||
|
||||
for expected_sensor in expected_sensors:
|
||||
entity_id = expected_sensor["entity_id"]
|
||||
for expected_entity in expected_entities:
|
||||
entity_id = expected_entity["entity_id"]
|
||||
registry_entry = entity_registry.entities.get(entity_id)
|
||||
assert registry_entry is not None
|
||||
state = hass.states.get(entity_id)
|
||||
assert state.state == expected_sensor["result"]
|
||||
assert state.state == expected_entity["result"]
|
||||
|
||||
if state.state == STATE_ON:
|
||||
owproxy.return_value.read.side_effect = [b" 0"]
|
||||
expected_sensor["result"] = STATE_OFF
|
||||
expected_entity["result"] = STATE_OFF
|
||||
elif state.state == STATE_OFF:
|
||||
owproxy.return_value.read.side_effect = [b" 1"]
|
||||
expected_sensor["result"] = STATE_ON
|
||||
expected_entity["result"] = STATE_ON
|
||||
|
||||
await hass.services.async_call(
|
||||
SWITCH_DOMAIN,
|
||||
@@ -126,7 +74,7 @@ async def test_owserver_switch(owproxy, hass, device_id):
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(entity_id)
|
||||
assert state.state == expected_sensor["result"]
|
||||
assert state.attributes["device_file"] == expected_sensor.get(
|
||||
assert state.state == expected_entity["result"]
|
||||
assert state.attributes["device_file"] == expected_entity.get(
|
||||
"device_file", registry_entry.unique_id
|
||||
)
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
"""Test Trace websocket API."""
|
||||
import asyncio
|
||||
|
||||
import pytest
|
||||
|
||||
from homeassistant.bootstrap import async_setup_component
|
||||
from homeassistant.components.trace.const import STORED_TRACES
|
||||
from homeassistant.core import Context
|
||||
from homeassistant.core import Context, callback
|
||||
from homeassistant.helpers.typing import UNDEFINED
|
||||
|
||||
from tests.common import assert_lists_same
|
||||
@@ -167,9 +169,11 @@ async def test_get_trace(
|
||||
assert trace["trace"][f"{prefix}/0"][0]["error"]
|
||||
assert trace["trace"][f"{prefix}/0"][0]["result"] == sun_action
|
||||
_assert_raw_config(domain, sun_config, trace)
|
||||
assert trace["blueprint_inputs"] is None
|
||||
assert trace["context"]
|
||||
assert trace["error"] == "Unable to find service test.automation"
|
||||
assert trace["state"] == "stopped"
|
||||
assert trace["script_execution"] == "error"
|
||||
assert trace["item_id"] == "sun"
|
||||
assert trace["context"][context_key] == context.id
|
||||
assert trace.get("trigger", UNDEFINED) == trigger[0]
|
||||
@@ -207,9 +211,11 @@ async def test_get_trace(
|
||||
assert "error" not in trace["trace"][f"{prefix}/0"][0]
|
||||
assert trace["trace"][f"{prefix}/0"][0]["result"] == moon_action
|
||||
_assert_raw_config(domain, moon_config, trace)
|
||||
assert trace["blueprint_inputs"] is None
|
||||
assert trace["context"]
|
||||
assert "error" not in trace
|
||||
assert trace["state"] == "stopped"
|
||||
assert trace["script_execution"] == "finished"
|
||||
assert trace["item_id"] == "moon"
|
||||
|
||||
assert trace.get("trigger", UNDEFINED) == trigger[1]
|
||||
@@ -260,6 +266,7 @@ async def test_get_trace(
|
||||
assert trace["context"]
|
||||
assert "error" not in trace
|
||||
assert trace["state"] == "stopped"
|
||||
assert trace["script_execution"] == "failed_conditions"
|
||||
assert trace["trigger"] == "event 'test_event3'"
|
||||
assert trace["item_id"] == "moon"
|
||||
contexts[trace["context"]["id"]] = {
|
||||
@@ -301,6 +308,7 @@ async def test_get_trace(
|
||||
assert trace["context"]
|
||||
assert "error" not in trace
|
||||
assert trace["state"] == "stopped"
|
||||
assert trace["script_execution"] == "finished"
|
||||
assert trace["trigger"] == "event 'test_event2'"
|
||||
assert trace["item_id"] == "moon"
|
||||
contexts[trace["context"]["id"]] = {
|
||||
@@ -391,7 +399,7 @@ async def test_trace_overflow(hass, hass_ws_client, domain):
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"domain, prefix, trigger, last_step",
|
||||
"domain, prefix, trigger, last_step, script_execution",
|
||||
[
|
||||
(
|
||||
"automation",
|
||||
@@ -403,16 +411,20 @@ async def test_trace_overflow(hass, hass_ws_client, domain):
|
||||
"event 'test_event2'",
|
||||
],
|
||||
["{prefix}/0", "{prefix}/0", "condition/0", "{prefix}/0"],
|
||||
["error", "finished", "failed_conditions", "finished"],
|
||||
),
|
||||
(
|
||||
"script",
|
||||
"sequence",
|
||||
[UNDEFINED, UNDEFINED, UNDEFINED, UNDEFINED],
|
||||
["{prefix}/0", "{prefix}/0", "{prefix}/0", "{prefix}/0"],
|
||||
["error", "finished", "finished", "finished"],
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_list_traces(hass, hass_ws_client, domain, prefix, trigger, last_step):
|
||||
async def test_list_traces(
|
||||
hass, hass_ws_client, domain, prefix, trigger, last_step, script_execution
|
||||
):
|
||||
"""Test listing script and automation traces."""
|
||||
id = 1
|
||||
|
||||
@@ -458,7 +470,7 @@ async def test_list_traces(hass, hass_ws_client, domain, prefix, trigger, last_s
|
||||
await _run_automation_or_script(hass, domain, sun_config, "test_event")
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Get trace
|
||||
# List traces
|
||||
await client.send_json({"id": next_id(), "type": "trace/list", "domain": domain})
|
||||
response = await client.receive_json()
|
||||
assert response["success"]
|
||||
@@ -492,7 +504,7 @@ async def test_list_traces(hass, hass_ws_client, domain, prefix, trigger, last_s
|
||||
await _run_automation_or_script(hass, domain, moon_config, "test_event2")
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Get trace
|
||||
# List traces
|
||||
await client.send_json({"id": next_id(), "type": "trace/list", "domain": domain})
|
||||
response = await client.receive_json()
|
||||
assert response["success"]
|
||||
@@ -502,6 +514,7 @@ async def test_list_traces(hass, hass_ws_client, domain, prefix, trigger, last_s
|
||||
assert trace["last_step"] == last_step[0].format(prefix=prefix)
|
||||
assert trace["error"] == "Unable to find service test.automation"
|
||||
assert trace["state"] == "stopped"
|
||||
assert trace["script_execution"] == script_execution[0]
|
||||
assert trace["timestamp"]
|
||||
assert trace["item_id"] == "sun"
|
||||
assert trace.get("trigger", UNDEFINED) == trigger[0]
|
||||
@@ -510,6 +523,7 @@ async def test_list_traces(hass, hass_ws_client, domain, prefix, trigger, last_s
|
||||
assert trace["last_step"] == last_step[1].format(prefix=prefix)
|
||||
assert "error" not in trace
|
||||
assert trace["state"] == "stopped"
|
||||
assert trace["script_execution"] == script_execution[1]
|
||||
assert trace["timestamp"]
|
||||
assert trace["item_id"] == "moon"
|
||||
assert trace.get("trigger", UNDEFINED) == trigger[1]
|
||||
@@ -518,6 +532,7 @@ async def test_list_traces(hass, hass_ws_client, domain, prefix, trigger, last_s
|
||||
assert trace["last_step"] == last_step[2].format(prefix=prefix)
|
||||
assert "error" not in trace
|
||||
assert trace["state"] == "stopped"
|
||||
assert trace["script_execution"] == script_execution[2]
|
||||
assert trace["timestamp"]
|
||||
assert trace["item_id"] == "moon"
|
||||
assert trace.get("trigger", UNDEFINED) == trigger[2]
|
||||
@@ -526,6 +541,7 @@ async def test_list_traces(hass, hass_ws_client, domain, prefix, trigger, last_s
|
||||
assert trace["last_step"] == last_step[3].format(prefix=prefix)
|
||||
assert "error" not in trace
|
||||
assert trace["state"] == "stopped"
|
||||
assert trace["script_execution"] == script_execution[3]
|
||||
assert trace["timestamp"]
|
||||
assert trace["item_id"] == "moon"
|
||||
assert trace.get("trigger", UNDEFINED) == trigger[3]
|
||||
@@ -1006,3 +1022,213 @@ async def test_breakpoints_3(hass, hass_ws_client, domain, prefix):
|
||||
"node": f"{prefix}/5",
|
||||
"run_id": run_id,
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"script_mode,max_runs,script_execution",
|
||||
[
|
||||
({"mode": "single"}, 1, "failed_single"),
|
||||
({"mode": "parallel", "max": 2}, 2, "failed_max_runs"),
|
||||
],
|
||||
)
|
||||
async def test_script_mode(
|
||||
hass, hass_ws_client, script_mode, max_runs, script_execution
|
||||
):
|
||||
"""Test overlapping runs with max_runs > 1."""
|
||||
id = 1
|
||||
|
||||
def next_id():
|
||||
nonlocal id
|
||||
id += 1
|
||||
return id
|
||||
|
||||
flag = asyncio.Event()
|
||||
|
||||
@callback
|
||||
def _handle_event(_):
|
||||
flag.set()
|
||||
|
||||
event = "test_event"
|
||||
script_config = {
|
||||
"script1": {
|
||||
"sequence": [
|
||||
{"event": event, "event_data": {"value": 1}},
|
||||
{"wait_template": "{{ states.switch.test.state == 'off' }}"},
|
||||
{"event": event, "event_data": {"value": 2}},
|
||||
],
|
||||
**script_mode,
|
||||
},
|
||||
}
|
||||
client = await hass_ws_client()
|
||||
hass.bus.async_listen(event, _handle_event)
|
||||
assert await async_setup_component(hass, "script", {"script": script_config})
|
||||
|
||||
for _ in range(max_runs):
|
||||
hass.states.async_set("switch.test", "on")
|
||||
await hass.services.async_call("script", "script1")
|
||||
await asyncio.wait_for(flag.wait(), 1)
|
||||
|
||||
# List traces
|
||||
await client.send_json({"id": next_id(), "type": "trace/list", "domain": "script"})
|
||||
response = await client.receive_json()
|
||||
assert response["success"]
|
||||
traces = _find_traces(response["result"], "script", "script1")
|
||||
assert len(traces) == max_runs
|
||||
for trace in traces:
|
||||
assert trace["state"] == "running"
|
||||
|
||||
# Start additional run of script while first runs are suspended in wait_template.
|
||||
|
||||
flag.clear()
|
||||
await hass.services.async_call("script", "script1")
|
||||
|
||||
# List traces
|
||||
await client.send_json({"id": next_id(), "type": "trace/list", "domain": "script"})
|
||||
response = await client.receive_json()
|
||||
assert response["success"]
|
||||
traces = _find_traces(response["result"], "script", "script1")
|
||||
assert len(traces) == max_runs + 1
|
||||
assert traces[-1]["state"] == "stopped"
|
||||
assert traces[-1]["script_execution"] == script_execution
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"script_mode,script_execution",
|
||||
[("restart", "cancelled"), ("parallel", "finished")],
|
||||
)
|
||||
async def test_script_mode_2(hass, hass_ws_client, script_mode, script_execution):
|
||||
"""Test overlapping runs with max_runs > 1."""
|
||||
id = 1
|
||||
|
||||
def next_id():
|
||||
nonlocal id
|
||||
id += 1
|
||||
return id
|
||||
|
||||
flag = asyncio.Event()
|
||||
|
||||
@callback
|
||||
def _handle_event(_):
|
||||
flag.set()
|
||||
|
||||
event = "test_event"
|
||||
script_config = {
|
||||
"script1": {
|
||||
"sequence": [
|
||||
{"event": event, "event_data": {"value": 1}},
|
||||
{"wait_template": "{{ states.switch.test.state == 'off' }}"},
|
||||
{"event": event, "event_data": {"value": 2}},
|
||||
],
|
||||
"mode": script_mode,
|
||||
}
|
||||
}
|
||||
client = await hass_ws_client()
|
||||
hass.bus.async_listen(event, _handle_event)
|
||||
assert await async_setup_component(hass, "script", {"script": script_config})
|
||||
|
||||
hass.states.async_set("switch.test", "on")
|
||||
await hass.services.async_call("script", "script1")
|
||||
await asyncio.wait_for(flag.wait(), 1)
|
||||
|
||||
# List traces
|
||||
await client.send_json({"id": next_id(), "type": "trace/list", "domain": "script"})
|
||||
response = await client.receive_json()
|
||||
assert response["success"]
|
||||
trace = _find_traces(response["result"], "script", "script1")[0]
|
||||
assert trace["state"] == "running"
|
||||
|
||||
# Start second run of script while first run is suspended in wait_template.
|
||||
|
||||
flag.clear()
|
||||
await hass.services.async_call("script", "script1")
|
||||
await asyncio.wait_for(flag.wait(), 1)
|
||||
|
||||
# List traces
|
||||
await client.send_json({"id": next_id(), "type": "trace/list", "domain": "script"})
|
||||
response = await client.receive_json()
|
||||
assert response["success"]
|
||||
trace = _find_traces(response["result"], "script", "script1")[1]
|
||||
assert trace["state"] == "running"
|
||||
|
||||
# Let both scripts finish
|
||||
hass.states.async_set("switch.test", "off")
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# List traces
|
||||
await client.send_json({"id": next_id(), "type": "trace/list", "domain": "script"})
|
||||
response = await client.receive_json()
|
||||
assert response["success"]
|
||||
trace = _find_traces(response["result"], "script", "script1")[0]
|
||||
assert trace["state"] == "stopped"
|
||||
assert trace["script_execution"] == script_execution
|
||||
trace = _find_traces(response["result"], "script", "script1")[1]
|
||||
assert trace["state"] == "stopped"
|
||||
assert trace["script_execution"] == "finished"
|
||||
|
||||
|
||||
async def test_trace_blueprint_automation(hass, hass_ws_client):
|
||||
"""Test trace of blueprint automation."""
|
||||
id = 1
|
||||
|
||||
def next_id():
|
||||
nonlocal id
|
||||
id += 1
|
||||
return id
|
||||
|
||||
domain = "automation"
|
||||
sun_config = {
|
||||
"id": "sun",
|
||||
"use_blueprint": {
|
||||
"path": "test_event_service.yaml",
|
||||
"input": {
|
||||
"trigger_event": "blueprint_event",
|
||||
"service_to_call": "test.automation",
|
||||
},
|
||||
},
|
||||
}
|
||||
sun_action = {
|
||||
"limit": 10,
|
||||
"params": {
|
||||
"domain": "test",
|
||||
"service": "automation",
|
||||
"service_data": {},
|
||||
"target": {"entity_id": ["light.kitchen"]},
|
||||
},
|
||||
"running_script": False,
|
||||
}
|
||||
assert await async_setup_component(hass, "automation", {"automation": sun_config})
|
||||
client = await hass_ws_client()
|
||||
hass.bus.async_fire("blueprint_event")
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# List traces
|
||||
await client.send_json({"id": next_id(), "type": "trace/list", "domain": domain})
|
||||
response = await client.receive_json()
|
||||
assert response["success"]
|
||||
run_id = _find_run_id(response["result"], domain, "sun")
|
||||
|
||||
# Get trace
|
||||
await client.send_json(
|
||||
{
|
||||
"id": next_id(),
|
||||
"type": "trace/get",
|
||||
"domain": domain,
|
||||
"item_id": "sun",
|
||||
"run_id": run_id,
|
||||
}
|
||||
)
|
||||
response = await client.receive_json()
|
||||
assert response["success"]
|
||||
trace = response["result"]
|
||||
assert set(trace["trace"]) == {"trigger/0", "action/0"}
|
||||
assert len(trace["trace"]["action/0"]) == 1
|
||||
assert trace["trace"]["action/0"][0]["error"]
|
||||
assert trace["trace"]["action/0"][0]["result"] == sun_action
|
||||
assert trace["config"]["id"] == "sun"
|
||||
assert trace["blueprint_inputs"] == sun_config
|
||||
assert trace["context"]
|
||||
assert trace["error"] == "Unable to find service test.automation"
|
||||
assert trace["state"] == "stopped"
|
||||
assert trace["script_execution"] == "error"
|
||||
assert trace["item_id"] == "sun"
|
||||
assert trace.get("trigger", UNDEFINED) == "event 'blueprint_event'"
|
||||
|
||||
@@ -86,9 +86,10 @@ def assert_element(trace_element, expected_element, path):
|
||||
assert not trace_element._variables
|
||||
|
||||
|
||||
def assert_action_trace(expected):
|
||||
def assert_action_trace(expected, expected_script_execution="finished"):
|
||||
"""Assert a trace condition sequence is as expected."""
|
||||
action_trace = trace.trace_get(clear=False)
|
||||
script_execution = trace.script_execution_get()
|
||||
trace.trace_clear()
|
||||
expected_trace_keys = list(expected.keys())
|
||||
assert list(action_trace.keys()) == expected_trace_keys
|
||||
@@ -98,6 +99,8 @@ def assert_action_trace(expected):
|
||||
path = f"[{trace_key_index}][{index}]"
|
||||
assert_element(action_trace[key][index], element, path)
|
||||
|
||||
assert script_execution == expected_script_execution
|
||||
|
||||
|
||||
def async_watch_for_action(script_obj, message):
|
||||
"""Watch for message in last_action."""
|
||||
@@ -620,7 +623,8 @@ async def test_delay_template_invalid(hass, caplog):
|
||||
{
|
||||
"0": [{"result": {"event": "test_event", "event_data": {}}}],
|
||||
"1": [{"error_type": script._StopScript}],
|
||||
}
|
||||
},
|
||||
expected_script_execution="aborted",
|
||||
)
|
||||
|
||||
|
||||
@@ -680,7 +684,8 @@ async def test_delay_template_complex_invalid(hass, caplog):
|
||||
{
|
||||
"0": [{"result": {"event": "test_event", "event_data": {}}}],
|
||||
"1": [{"error_type": script._StopScript}],
|
||||
}
|
||||
},
|
||||
expected_script_execution="aborted",
|
||||
)
|
||||
|
||||
|
||||
@@ -717,7 +722,8 @@ async def test_cancel_delay(hass):
|
||||
assert_action_trace(
|
||||
{
|
||||
"0": [{"result": {"delay": 5.0, "done": False}}],
|
||||
}
|
||||
},
|
||||
expected_script_execution="cancelled",
|
||||
)
|
||||
|
||||
|
||||
@@ -969,13 +975,15 @@ async def test_cancel_wait(hass, action_type):
|
||||
assert_action_trace(
|
||||
{
|
||||
"0": [{"result": {"wait": {"completed": False, "remaining": None}}}],
|
||||
}
|
||||
},
|
||||
expected_script_execution="cancelled",
|
||||
)
|
||||
else:
|
||||
assert_action_trace(
|
||||
{
|
||||
"0": [{"result": {"wait": {"trigger": None, "remaining": None}}}],
|
||||
}
|
||||
},
|
||||
expected_script_execution="cancelled",
|
||||
)
|
||||
|
||||
|
||||
@@ -1131,6 +1139,7 @@ async def test_wait_continue_on_timeout(
|
||||
if continue_on_timeout is False:
|
||||
expected_trace["0"][0]["result"]["timeout"] = True
|
||||
expected_trace["0"][0]["error_type"] = script._StopScript
|
||||
expected_script_execution = "aborted"
|
||||
else:
|
||||
expected_trace["1"] = [
|
||||
{
|
||||
@@ -1138,7 +1147,8 @@ async def test_wait_continue_on_timeout(
|
||||
"variables": variable_wait,
|
||||
}
|
||||
]
|
||||
assert_action_trace(expected_trace)
|
||||
expected_script_execution = "finished"
|
||||
assert_action_trace(expected_trace, expected_script_execution)
|
||||
|
||||
|
||||
async def test_wait_template_variables_in(hass):
|
||||
@@ -1404,7 +1414,8 @@ async def test_condition_warning(hass, caplog):
|
||||
"1": [{"error_type": script._StopScript, "result": {"result": False}}],
|
||||
"1/condition": [{"error_type": ConditionError}],
|
||||
"1/condition/entity_id/0": [{"error_type": ConditionError}],
|
||||
}
|
||||
},
|
||||
expected_script_execution="aborted",
|
||||
)
|
||||
|
||||
|
||||
@@ -1456,7 +1467,8 @@ async def test_condition_basic(hass, caplog):
|
||||
"0": [{"result": {"event": "test_event", "event_data": {}}}],
|
||||
"1": [{"error_type": script._StopScript, "result": {"result": False}}],
|
||||
"1/condition": [{"result": {"result": False}}],
|
||||
}
|
||||
},
|
||||
expected_script_execution="aborted",
|
||||
)
|
||||
|
||||
|
||||
@@ -2141,7 +2153,7 @@ async def test_propagate_error_service_not_found(hass):
|
||||
}
|
||||
],
|
||||
}
|
||||
assert_action_trace(expected_trace)
|
||||
assert_action_trace(expected_trace, expected_script_execution="error")
|
||||
|
||||
|
||||
async def test_propagate_error_invalid_service_data(hass):
|
||||
@@ -2178,7 +2190,7 @@ async def test_propagate_error_invalid_service_data(hass):
|
||||
}
|
||||
],
|
||||
}
|
||||
assert_action_trace(expected_trace)
|
||||
assert_action_trace(expected_trace, expected_script_execution="error")
|
||||
|
||||
|
||||
async def test_propagate_error_service_exception(hass):
|
||||
@@ -2219,7 +2231,7 @@ async def test_propagate_error_service_exception(hass):
|
||||
}
|
||||
],
|
||||
}
|
||||
assert_action_trace(expected_trace)
|
||||
assert_action_trace(expected_trace, expected_script_execution="error")
|
||||
|
||||
|
||||
async def test_referenced_entities(hass):
|
||||
|
||||
@@ -213,6 +213,30 @@ class TestServiceHelpers(unittest.TestCase):
|
||||
"entity_id": ["light.static", "light.dynamic"],
|
||||
}
|
||||
|
||||
config = {
|
||||
"service": "{{ 'test_domain.test_service' }}",
|
||||
"target": "{{ var_target }}",
|
||||
}
|
||||
|
||||
service.call_from_config(
|
||||
self.hass,
|
||||
config,
|
||||
variables={
|
||||
"var_target": {
|
||||
"entity_id": "light.static",
|
||||
"area_id": ["area-42", "area-51"],
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
service.call_from_config(self.hass, config)
|
||||
self.hass.block_till_done()
|
||||
|
||||
assert dict(self.calls[2].data) == {
|
||||
"area_id": ["area-42", "area-51"],
|
||||
"entity_id": ["light.static"],
|
||||
}
|
||||
|
||||
def test_service_template_service_call(self):
|
||||
"""Test legacy service_template call with templating."""
|
||||
config = {
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
"""Test Home Assistant logging util methods."""
|
||||
import asyncio
|
||||
from functools import partial
|
||||
import logging
|
||||
import queue
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from homeassistant.core import callback, is_callback
|
||||
import homeassistant.util.logging as logging_util
|
||||
|
||||
|
||||
@@ -80,3 +82,30 @@ async def test_async_create_catching_coro(hass, caplog):
|
||||
await hass.async_block_till_done()
|
||||
assert "This is a bad coroutine" in caplog.text
|
||||
assert "in test_async_create_catching_coro" in caplog.text
|
||||
|
||||
|
||||
def test_catch_log_exception():
|
||||
"""Test it is still a callback after wrapping including partial."""
|
||||
|
||||
async def async_meth():
|
||||
pass
|
||||
|
||||
assert asyncio.iscoroutinefunction(
|
||||
logging_util.catch_log_exception(partial(async_meth), lambda: None)
|
||||
)
|
||||
|
||||
@callback
|
||||
def callback_meth():
|
||||
pass
|
||||
|
||||
assert is_callback(
|
||||
logging_util.catch_log_exception(partial(callback_meth), lambda: None)
|
||||
)
|
||||
|
||||
def sync_meth():
|
||||
pass
|
||||
|
||||
wrapped = logging_util.catch_log_exception(partial(sync_meth), lambda: None)
|
||||
|
||||
assert not is_callback(wrapped)
|
||||
assert not asyncio.iscoroutinefunction(wrapped)
|
||||
|
||||
Reference in New Issue
Block a user