mirror of
https://github.com/home-assistant/core.git
synced 2026-02-20 12:50:22 +00:00
Compare commits
7 Commits
matter_cle
...
homewizard
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bd095ebf0a | ||
|
|
1edfd2da23 | ||
|
|
42308f8b68 | ||
|
|
21bf96e1ad | ||
|
|
365bd95963 | ||
|
|
d889217944 | ||
|
|
6b8915dcba |
@@ -27,15 +27,36 @@ from homeassistant.core import HomeAssistant
|
||||
from homeassistant.data_entry_flow import AbortFlow
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import instance_id
|
||||
from homeassistant.helpers.selector import TextSelector
|
||||
from homeassistant.helpers.selector import (
|
||||
SelectSelector,
|
||||
SelectSelectorConfig,
|
||||
SelectSelectorMode,
|
||||
TextSelector,
|
||||
)
|
||||
from homeassistant.helpers.service_info.dhcp import DhcpServiceInfo
|
||||
from homeassistant.helpers.service_info.zeroconf import ZeroconfServiceInfo
|
||||
|
||||
from .const import CONF_PRODUCT_NAME, CONF_PRODUCT_TYPE, CONF_SERIAL, DOMAIN, LOGGER
|
||||
from .const import (
|
||||
CONF_PRODUCT_NAME,
|
||||
CONF_PRODUCT_TYPE,
|
||||
CONF_SERIAL,
|
||||
CONF_USAGE,
|
||||
DOMAIN,
|
||||
ENERGY_MONITORING_DEVICES,
|
||||
LOGGER,
|
||||
)
|
||||
|
||||
USAGE_SELECTOR = SelectSelector(
|
||||
SelectSelectorConfig(
|
||||
options=["consumption", "generation"],
|
||||
translation_key="usage",
|
||||
mode=SelectSelectorMode.LIST,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class HomeWizardConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
"""Handle a config flow for P1 meter."""
|
||||
"""Handle a config flow for HomeWizard devices."""
|
||||
|
||||
VERSION = 1
|
||||
|
||||
@@ -43,6 +64,8 @@ class HomeWizardConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
product_name: str | None = None
|
||||
product_type: str | None = None
|
||||
serial: str | None = None
|
||||
token: str | None = None
|
||||
usage: str | None = None
|
||||
|
||||
async def async_step_user(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
@@ -64,6 +87,12 @@ class HomeWizardConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
f"{device_info.product_type}_{device_info.serial}"
|
||||
)
|
||||
self._abort_if_unique_id_configured(updates=user_input)
|
||||
if device_info.product_type in ENERGY_MONITORING_DEVICES:
|
||||
self.ip_address = user_input[CONF_IP_ADDRESS]
|
||||
self.product_name = device_info.product_name
|
||||
self.product_type = device_info.product_type
|
||||
self.serial = device_info.serial
|
||||
return await self.async_step_usage()
|
||||
return self.async_create_entry(
|
||||
title=f"{device_info.product_name}",
|
||||
data=user_input,
|
||||
@@ -82,6 +111,45 @@ class HomeWizardConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
errors=errors,
|
||||
)
|
||||
|
||||
async def async_step_usage(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> ConfigFlowResult:
|
||||
"""Step where we ask how the energy monitor is used."""
|
||||
assert self.ip_address
|
||||
assert self.product_name
|
||||
assert self.product_type
|
||||
assert self.serial
|
||||
|
||||
data: dict[str, Any] = {CONF_IP_ADDRESS: self.ip_address}
|
||||
if self.token:
|
||||
data[CONF_TOKEN] = self.token
|
||||
|
||||
if user_input is not None:
|
||||
return self.async_create_entry(
|
||||
title=f"{self.product_name}",
|
||||
data=data | user_input,
|
||||
)
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="usage",
|
||||
data_schema=vol.Schema(
|
||||
{
|
||||
vol.Required(
|
||||
CONF_USAGE,
|
||||
default=user_input.get(CONF_USAGE)
|
||||
if user_input is not None
|
||||
else "consumption",
|
||||
): USAGE_SELECTOR,
|
||||
}
|
||||
),
|
||||
description_placeholders={
|
||||
CONF_PRODUCT_NAME: self.product_name,
|
||||
CONF_PRODUCT_TYPE: self.product_type,
|
||||
CONF_SERIAL: self.serial,
|
||||
CONF_IP_ADDRESS: self.ip_address,
|
||||
},
|
||||
)
|
||||
|
||||
async def async_step_authorize(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> ConfigFlowResult:
|
||||
@@ -101,8 +169,7 @@ class HomeWizardConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
|
||||
# Now we got a token, we can ask for some more info
|
||||
|
||||
async with HomeWizardEnergyV2(self.ip_address, token=token) as api:
|
||||
device_info = await api.device()
|
||||
device_info = await HomeWizardEnergyV2(self.ip_address, token=token).device()
|
||||
|
||||
data = {
|
||||
CONF_IP_ADDRESS: self.ip_address,
|
||||
@@ -113,6 +180,14 @@ class HomeWizardConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
f"{device_info.product_type}_{device_info.serial}"
|
||||
)
|
||||
self._abort_if_unique_id_configured(updates=data)
|
||||
self.product_name = device_info.product_name
|
||||
self.product_type = device_info.product_type
|
||||
self.serial = device_info.serial
|
||||
|
||||
if device_info.product_type in ENERGY_MONITORING_DEVICES:
|
||||
self.token = token
|
||||
return await self.async_step_usage()
|
||||
|
||||
return self.async_create_entry(
|
||||
title=f"{device_info.product_name}",
|
||||
data=data,
|
||||
@@ -139,6 +214,8 @@ class HomeWizardConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
self._abort_if_unique_id_configured(
|
||||
updates={CONF_IP_ADDRESS: discovery_info.host}
|
||||
)
|
||||
if self.product_type in ENERGY_MONITORING_DEVICES:
|
||||
return await self.async_step_usage()
|
||||
|
||||
return await self.async_step_discovery_confirm()
|
||||
|
||||
|
||||
@@ -5,6 +5,8 @@ from __future__ import annotations
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
|
||||
from homewizard_energy.const import Model
|
||||
|
||||
from homeassistant.const import Platform
|
||||
|
||||
DOMAIN = "homewizard"
|
||||
@@ -22,5 +24,14 @@ LOGGER = logging.getLogger(__package__)
|
||||
CONF_PRODUCT_NAME = "product_name"
|
||||
CONF_PRODUCT_TYPE = "product_type"
|
||||
CONF_SERIAL = "serial"
|
||||
CONF_USAGE = "usage"
|
||||
|
||||
UPDATE_INTERVAL = timedelta(seconds=5)
|
||||
|
||||
ENERGY_MONITORING_DEVICES = (
|
||||
Model.ENERGY_SOCKET,
|
||||
Model.ENERGY_METER_1_PHASE,
|
||||
Model.ENERGY_METER_3_PHASE,
|
||||
Model.ENERGY_METER_EASTRON_SDM230,
|
||||
Model.ENERGY_METER_EASTRON_SDM630,
|
||||
)
|
||||
|
||||
@@ -39,7 +39,7 @@ from homeassistant.helpers.typing import StateType
|
||||
from homeassistant.util.dt import utcnow
|
||||
from homeassistant.util.variance import ignore_variance
|
||||
|
||||
from .const import DOMAIN
|
||||
from .const import CONF_USAGE, DOMAIN, ENERGY_MONITORING_DEVICES
|
||||
from .coordinator import HomeWizardConfigEntry, HWEnergyDeviceUpdateCoordinator
|
||||
from .entity import HomeWizardEntity
|
||||
|
||||
@@ -267,15 +267,6 @@ SENSORS: Final[tuple[HomeWizardSensorEntityDescription, ...]] = (
|
||||
enabled_fn=lambda data: data.measurement.energy_export_t4_kwh != 0,
|
||||
value_fn=lambda data: data.measurement.energy_export_t4_kwh or None,
|
||||
),
|
||||
HomeWizardSensorEntityDescription(
|
||||
key="active_power_w",
|
||||
native_unit_of_measurement=UnitOfPower.WATT,
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
suggested_display_precision=0,
|
||||
has_fn=lambda data: data.measurement.power_w is not None,
|
||||
value_fn=lambda data: data.measurement.power_w,
|
||||
),
|
||||
HomeWizardSensorEntityDescription(
|
||||
key="active_power_l1_w",
|
||||
translation_key="active_power_phase_w",
|
||||
@@ -700,22 +691,30 @@ async def async_setup_entry(
|
||||
entry: HomeWizardConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Initialize sensors."""
|
||||
|
||||
# Initialize default sensors
|
||||
"""Cleanup deleted entrity registry item."""
|
||||
entities: list = [
|
||||
HomeWizardSensorEntity(entry.runtime_data, description)
|
||||
for description in SENSORS
|
||||
if description.has_fn(entry.runtime_data.data)
|
||||
]
|
||||
active_power_sensor_description = HomeWizardSensorEntityDescription(
|
||||
key="active_power_w",
|
||||
native_unit_of_measurement=UnitOfPower.WATT,
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
suggested_display_precision=0,
|
||||
entity_registry_enabled_default=(
|
||||
entry.runtime_data.data.device.product_type != Model.BATTERY
|
||||
and entry.data.get(CONF_USAGE, "consumption") == "consumption"
|
||||
),
|
||||
has_fn=lambda x: True,
|
||||
value_fn=lambda data: data.measurement.power_w,
|
||||
)
|
||||
|
||||
# Add optional production power sensor for supported energy monitoring devices
|
||||
# or plug-in battery
|
||||
if entry.runtime_data.data.device.product_type in (
|
||||
Model.ENERGY_SOCKET,
|
||||
Model.ENERGY_METER_1_PHASE,
|
||||
Model.ENERGY_METER_3_PHASE,
|
||||
Model.ENERGY_METER_EASTRON_SDM230,
|
||||
Model.ENERGY_METER_EASTRON_SDM630,
|
||||
*ENERGY_MONITORING_DEVICES,
|
||||
Model.BATTERY,
|
||||
):
|
||||
active_prodution_power_sensor_description = HomeWizardSensorEntityDescription(
|
||||
@@ -735,17 +734,27 @@ async def async_setup_entry(
|
||||
is not None
|
||||
and total_export > 0
|
||||
)
|
||||
or entry.data.get(CONF_USAGE, "consumption") == "generation"
|
||||
),
|
||||
has_fn=lambda x: True,
|
||||
value_fn=lambda data: (
|
||||
power_w * -1 if (power_w := data.measurement.power_w) else power_w
|
||||
),
|
||||
)
|
||||
entities.append(
|
||||
HomeWizardSensorEntity(
|
||||
entry.runtime_data, active_prodution_power_sensor_description
|
||||
entities.extend(
|
||||
(
|
||||
HomeWizardSensorEntity(
|
||||
entry.runtime_data, active_power_sensor_description
|
||||
),
|
||||
HomeWizardSensorEntity(
|
||||
entry.runtime_data, active_prodution_power_sensor_description
|
||||
),
|
||||
)
|
||||
)
|
||||
elif (data := entry.runtime_data.data) and data.measurement.power_w is not None:
|
||||
entities.append(
|
||||
HomeWizardSensorEntity(entry.runtime_data, active_power_sensor_description)
|
||||
)
|
||||
|
||||
# Initialize external devices
|
||||
measurement = entry.runtime_data.data.measurement
|
||||
|
||||
@@ -41,6 +41,16 @@
|
||||
},
|
||||
"description": "Update configuration for {title}."
|
||||
},
|
||||
"usage": {
|
||||
"data": {
|
||||
"usage": "Usage"
|
||||
},
|
||||
"data_description": {
|
||||
"usage": "This will enable either a power consumption or power production sensor the first time this device is set up."
|
||||
},
|
||||
"description": "What are you going to monitor with your {product_name} ({product_type} {serial} at {ip_address})?",
|
||||
"title": "Usage"
|
||||
},
|
||||
"user": {
|
||||
"data": {
|
||||
"ip_address": "[%key:common::config_flow::data::ip%]"
|
||||
@@ -199,5 +209,13 @@
|
||||
},
|
||||
"title": "Update the authentication method for {title}"
|
||||
}
|
||||
},
|
||||
"selector": {
|
||||
"usage": {
|
||||
"options": {
|
||||
"consumption": "Monitoring consumed energy",
|
||||
"generation": "Monitoring generated energy"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"wifi_ssid": "My Wi-Fi",
|
||||
"wifi_strength": 92,
|
||||
"total_power_import_t1_kwh": 0.003,
|
||||
"total_power_export_t1_kwh": 0.0,
|
||||
"active_power_w": 0.0,
|
||||
"active_power_l1_w": 0.0,
|
||||
"active_voltage_v": 228.472,
|
||||
"active_current_a": 0.273,
|
||||
"active_apparent_current_a": 0.0,
|
||||
"active_reactive_current_a": 0.0,
|
||||
"active_apparent_power_va": 9.0,
|
||||
"active_reactive_power_var": -9.0,
|
||||
"active_power_factor": 0.611,
|
||||
"active_frequency_hz": 50
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"product_type": "HWE-KWH1",
|
||||
"product_name": "kWh meter",
|
||||
"serial": "5c2fafabcdef",
|
||||
"firmware_version": "5.0103",
|
||||
"api_version": "v2"
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"cloud_enabled": true
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"wifi_ssid": "My Wi-Fi",
|
||||
"wifi_strength": 100,
|
||||
"total_power_import_kwh": 0.003,
|
||||
"total_power_import_t1_kwh": 0.003,
|
||||
"total_power_export_kwh": 0.0,
|
||||
"total_power_export_t1_kwh": 0.0,
|
||||
"active_power_w": 0.0,
|
||||
"active_power_l1_w": 0.0,
|
||||
"active_voltage_v": 231.539,
|
||||
"active_current_a": 0.0,
|
||||
"active_reactive_power_var": 0.0,
|
||||
"active_apparent_power_va": 0.0,
|
||||
"active_power_factor": 0.0,
|
||||
"active_frequency_hz": 50.005
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"product_type": "HWE-SKT",
|
||||
"product_name": "Energy Socket",
|
||||
"serial": "5c2fafabcdef",
|
||||
"firmware_version": "4.07",
|
||||
"api_version": "v1"
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"power_on": true,
|
||||
"switch_lock": false,
|
||||
"brightness": 255
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"cloud_enabled": true
|
||||
}
|
||||
@@ -92,56 +92,7 @@
|
||||
'version': 1,
|
||||
})
|
||||
# ---
|
||||
# name: test_discovery_flow_works
|
||||
FlowResultSnapshot({
|
||||
'context': dict({
|
||||
'confirm_only': True,
|
||||
'dismiss_protected': True,
|
||||
'source': 'zeroconf',
|
||||
'title_placeholders': dict({
|
||||
'name': 'Energy Socket (5c2fafabcdef)',
|
||||
}),
|
||||
'unique_id': 'HWE-SKT_5c2fafabcdef',
|
||||
}),
|
||||
'data': dict({
|
||||
'ip_address': '127.0.0.1',
|
||||
}),
|
||||
'description': None,
|
||||
'description_placeholders': None,
|
||||
'flow_id': <ANY>,
|
||||
'handler': 'homewizard',
|
||||
'minor_version': 1,
|
||||
'options': dict({
|
||||
}),
|
||||
'result': ConfigEntrySnapshot({
|
||||
'data': dict({
|
||||
'ip_address': '127.0.0.1',
|
||||
}),
|
||||
'disabled_by': None,
|
||||
'discovery_keys': dict({
|
||||
}),
|
||||
'domain': 'homewizard',
|
||||
'entry_id': <ANY>,
|
||||
'minor_version': 1,
|
||||
'options': dict({
|
||||
}),
|
||||
'pref_disable_new_entities': False,
|
||||
'pref_disable_polling': False,
|
||||
'source': 'zeroconf',
|
||||
'subentries': list([
|
||||
]),
|
||||
'title': 'Energy Socket',
|
||||
'unique_id': 'HWE-SKT_5c2fafabcdef',
|
||||
'version': 1,
|
||||
}),
|
||||
'subentries': tuple(
|
||||
),
|
||||
'title': 'Energy Socket',
|
||||
'type': <FlowResultType.CREATE_ENTRY: 'create_entry'>,
|
||||
'version': 1,
|
||||
})
|
||||
# ---
|
||||
# name: test_manual_flow_works
|
||||
# name: test_manual_flow_works[HWE-P1]
|
||||
FlowResultSnapshot({
|
||||
'context': dict({
|
||||
'source': 'user',
|
||||
@@ -185,3 +136,238 @@
|
||||
'version': 1,
|
||||
})
|
||||
# ---
|
||||
# name: test_manual_flow_works_device_energy_monitoring[consumption-HWE-SKT-21]
|
||||
FlowResultSnapshot({
|
||||
'context': dict({
|
||||
'source': 'user',
|
||||
'unique_id': 'HWE-SKT_5c2fafabcdef',
|
||||
}),
|
||||
'data': dict({
|
||||
'ip_address': '2.2.2.2',
|
||||
'usage': 'consumption',
|
||||
}),
|
||||
'description': None,
|
||||
'description_placeholders': None,
|
||||
'flow_id': <ANY>,
|
||||
'handler': 'homewizard',
|
||||
'minor_version': 1,
|
||||
'options': dict({
|
||||
}),
|
||||
'result': ConfigEntrySnapshot({
|
||||
'data': dict({
|
||||
'ip_address': '2.2.2.2',
|
||||
'usage': 'consumption',
|
||||
}),
|
||||
'disabled_by': None,
|
||||
'discovery_keys': dict({
|
||||
}),
|
||||
'domain': 'homewizard',
|
||||
'entry_id': <ANY>,
|
||||
'minor_version': 1,
|
||||
'options': dict({
|
||||
}),
|
||||
'pref_disable_new_entities': False,
|
||||
'pref_disable_polling': False,
|
||||
'source': 'user',
|
||||
'subentries': list([
|
||||
]),
|
||||
'title': 'Energy Socket',
|
||||
'unique_id': 'HWE-SKT_5c2fafabcdef',
|
||||
'version': 1,
|
||||
}),
|
||||
'subentries': tuple(
|
||||
),
|
||||
'title': 'Energy Socket',
|
||||
'type': <FlowResultType.CREATE_ENTRY: 'create_entry'>,
|
||||
'version': 1,
|
||||
})
|
||||
# ---
|
||||
# name: test_manual_flow_works_device_energy_monitoring[generation-HWE-SKT-21]
|
||||
FlowResultSnapshot({
|
||||
'context': dict({
|
||||
'source': 'user',
|
||||
'unique_id': 'HWE-SKT_5c2fafabcdef',
|
||||
}),
|
||||
'data': dict({
|
||||
'ip_address': '2.2.2.2',
|
||||
'usage': 'generation',
|
||||
}),
|
||||
'description': None,
|
||||
'description_placeholders': None,
|
||||
'flow_id': <ANY>,
|
||||
'handler': 'homewizard',
|
||||
'minor_version': 1,
|
||||
'options': dict({
|
||||
}),
|
||||
'result': ConfigEntrySnapshot({
|
||||
'data': dict({
|
||||
'ip_address': '2.2.2.2',
|
||||
'usage': 'generation',
|
||||
}),
|
||||
'disabled_by': None,
|
||||
'discovery_keys': dict({
|
||||
}),
|
||||
'domain': 'homewizard',
|
||||
'entry_id': <ANY>,
|
||||
'minor_version': 1,
|
||||
'options': dict({
|
||||
}),
|
||||
'pref_disable_new_entities': False,
|
||||
'pref_disable_polling': False,
|
||||
'source': 'user',
|
||||
'subentries': list([
|
||||
]),
|
||||
'title': 'Energy Socket',
|
||||
'unique_id': 'HWE-SKT_5c2fafabcdef',
|
||||
'version': 1,
|
||||
}),
|
||||
'subentries': tuple(
|
||||
),
|
||||
'title': 'Energy Socket',
|
||||
'type': <FlowResultType.CREATE_ENTRY: 'create_entry'>,
|
||||
'version': 1,
|
||||
})
|
||||
# ---
|
||||
# name: test_power_monitoring_discovery_flow_works[consumption]
|
||||
FlowResultSnapshot({
|
||||
'context': dict({
|
||||
'dismiss_protected': True,
|
||||
'source': 'zeroconf',
|
||||
'unique_id': 'HWE-SKT_5c2fafabcdef',
|
||||
}),
|
||||
'data': dict({
|
||||
'ip_address': '127.0.0.1',
|
||||
'usage': 'consumption',
|
||||
}),
|
||||
'description': None,
|
||||
'description_placeholders': None,
|
||||
'flow_id': <ANY>,
|
||||
'handler': 'homewizard',
|
||||
'minor_version': 1,
|
||||
'options': dict({
|
||||
}),
|
||||
'result': ConfigEntrySnapshot({
|
||||
'data': dict({
|
||||
'ip_address': '127.0.0.1',
|
||||
'usage': 'consumption',
|
||||
}),
|
||||
'disabled_by': None,
|
||||
'discovery_keys': dict({
|
||||
}),
|
||||
'domain': 'homewizard',
|
||||
'entry_id': <ANY>,
|
||||
'minor_version': 1,
|
||||
'options': dict({
|
||||
}),
|
||||
'pref_disable_new_entities': False,
|
||||
'pref_disable_polling': False,
|
||||
'source': 'zeroconf',
|
||||
'subentries': list([
|
||||
]),
|
||||
'title': 'Energy Socket',
|
||||
'unique_id': 'HWE-SKT_5c2fafabcdef',
|
||||
'version': 1,
|
||||
}),
|
||||
'subentries': tuple(
|
||||
),
|
||||
'title': 'Energy Socket',
|
||||
'type': <FlowResultType.CREATE_ENTRY: 'create_entry'>,
|
||||
'version': 1,
|
||||
})
|
||||
# ---
|
||||
# name: test_power_monitoring_discovery_flow_works[generation]
|
||||
FlowResultSnapshot({
|
||||
'context': dict({
|
||||
'dismiss_protected': True,
|
||||
'source': 'zeroconf',
|
||||
'unique_id': 'HWE-SKT_5c2fafabcdef',
|
||||
}),
|
||||
'data': dict({
|
||||
'ip_address': '127.0.0.1',
|
||||
'usage': 'generation',
|
||||
}),
|
||||
'description': None,
|
||||
'description_placeholders': None,
|
||||
'flow_id': <ANY>,
|
||||
'handler': 'homewizard',
|
||||
'minor_version': 1,
|
||||
'options': dict({
|
||||
}),
|
||||
'result': ConfigEntrySnapshot({
|
||||
'data': dict({
|
||||
'ip_address': '127.0.0.1',
|
||||
'usage': 'generation',
|
||||
}),
|
||||
'disabled_by': None,
|
||||
'discovery_keys': dict({
|
||||
}),
|
||||
'domain': 'homewizard',
|
||||
'entry_id': <ANY>,
|
||||
'minor_version': 1,
|
||||
'options': dict({
|
||||
}),
|
||||
'pref_disable_new_entities': False,
|
||||
'pref_disable_polling': False,
|
||||
'source': 'zeroconf',
|
||||
'subentries': list([
|
||||
]),
|
||||
'title': 'Energy Socket',
|
||||
'unique_id': 'HWE-SKT_5c2fafabcdef',
|
||||
'version': 1,
|
||||
}),
|
||||
'subentries': tuple(
|
||||
),
|
||||
'title': 'Energy Socket',
|
||||
'type': <FlowResultType.CREATE_ENTRY: 'create_entry'>,
|
||||
'version': 1,
|
||||
})
|
||||
# ---
|
||||
# name: test_water_monitoring_discovery_flow_works
|
||||
FlowResultSnapshot({
|
||||
'context': dict({
|
||||
'confirm_only': True,
|
||||
'dismiss_protected': True,
|
||||
'source': 'zeroconf',
|
||||
'title_placeholders': dict({
|
||||
'name': 'Watermeter',
|
||||
}),
|
||||
'unique_id': 'HWE-WTR_3c39efabcdef',
|
||||
}),
|
||||
'data': dict({
|
||||
'ip_address': '127.0.0.1',
|
||||
}),
|
||||
'description': None,
|
||||
'description_placeholders': None,
|
||||
'flow_id': <ANY>,
|
||||
'handler': 'homewizard',
|
||||
'minor_version': 1,
|
||||
'options': dict({
|
||||
}),
|
||||
'result': ConfigEntrySnapshot({
|
||||
'data': dict({
|
||||
'ip_address': '127.0.0.1',
|
||||
}),
|
||||
'disabled_by': None,
|
||||
'discovery_keys': dict({
|
||||
}),
|
||||
'domain': 'homewizard',
|
||||
'entry_id': <ANY>,
|
||||
'minor_version': 1,
|
||||
'options': dict({
|
||||
}),
|
||||
'pref_disable_new_entities': False,
|
||||
'pref_disable_polling': False,
|
||||
'source': 'zeroconf',
|
||||
'subentries': list([
|
||||
]),
|
||||
'title': 'Watermeter',
|
||||
'unique_id': 'HWE-WTR_3c39efabcdef',
|
||||
'version': 1,
|
||||
}),
|
||||
'subentries': tuple(
|
||||
),
|
||||
'title': 'Watermeter',
|
||||
'type': <FlowResultType.CREATE_ENTRY: 'create_entry'>,
|
||||
'version': 1,
|
||||
})
|
||||
# ---
|
||||
|
||||
@@ -24,6 +24,7 @@ from tests.common import MockConfigEntry
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("mock_setup_entry")
|
||||
@pytest.mark.parametrize(("device_fixture"), ["HWE-P1"])
|
||||
async def test_manual_flow_works(
|
||||
hass: HomeAssistant,
|
||||
mock_homewizardenergy: MagicMock,
|
||||
@@ -51,12 +52,50 @@ async def test_manual_flow_works(
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("mock_homewizardenergy", "mock_setup_entry")
|
||||
async def test_discovery_flow_works(
|
||||
@pytest.mark.usefixtures("mock_setup_entry")
|
||||
@pytest.mark.parametrize(("device_fixture"), ["HWE-SKT-21"])
|
||||
@pytest.mark.parametrize(("usage"), ["consumption", "generation"])
|
||||
async def test_manual_flow_works_device_energy_monitoring(
|
||||
hass: HomeAssistant,
|
||||
mock_homewizardenergy: MagicMock,
|
||||
mock_setup_entry: AsyncMock,
|
||||
snapshot: SnapshotAssertion,
|
||||
usage: str,
|
||||
) -> None:
|
||||
"""Test discovery setup flow works."""
|
||||
"""Test config flow accepts user configuration for energy plug."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["step_id"] == "user"
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"], {CONF_IP_ADDRESS: "2.2.2.2"}
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["step_id"] == "usage"
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"], {"usage": usage}
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.CREATE_ENTRY
|
||||
assert result == snapshot
|
||||
|
||||
assert len(hass.config_entries.async_entries(DOMAIN)) == 1
|
||||
assert len(mock_homewizardenergy.close.mock_calls) == 1
|
||||
assert len(mock_homewizardenergy.device.mock_calls) == 1
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("mock_homewizardenergy", "mock_setup_entry")
|
||||
@pytest.mark.parametrize("usage", ["consumption", "generation"])
|
||||
async def test_power_monitoring_discovery_flow_works(
|
||||
hass: HomeAssistant, snapshot: SnapshotAssertion, usage: str
|
||||
) -> None:
|
||||
"""Test discovery energy monitoring setup flow works."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_ZEROCONF},
|
||||
@@ -77,6 +116,42 @@ async def test_discovery_flow_works(
|
||||
),
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["step_id"] == "usage"
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"], user_input={"usage": usage}
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.CREATE_ENTRY
|
||||
assert result == snapshot
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("mock_homewizardenergy", "mock_setup_entry")
|
||||
async def test_water_monitoring_discovery_flow_works(
|
||||
hass: HomeAssistant, snapshot: SnapshotAssertion
|
||||
) -> None:
|
||||
"""Test discovery energy monitoring setup flow works."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_ZEROCONF},
|
||||
data=ZeroconfServiceInfo(
|
||||
ip_address=ip_address("127.0.0.1"),
|
||||
ip_addresses=[ip_address("127.0.0.1")],
|
||||
port=80,
|
||||
hostname="watermeter-ddeeff.local.",
|
||||
type="",
|
||||
name="",
|
||||
properties={
|
||||
"api_enabled": "1",
|
||||
"path": "/api/v1",
|
||||
"product_name": "Watermeter",
|
||||
"product_type": "HWE-WTR",
|
||||
"serial": "3c39efabcdef",
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["step_id"] == "discovery_confirm"
|
||||
|
||||
@@ -620,7 +695,7 @@ async def test_reconfigure_cannot_connect(
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("mock_setup_entry")
|
||||
@pytest.mark.parametrize(("device_fixture"), ["HWE-P1", "HWE-KWH1"])
|
||||
@pytest.mark.parametrize(("device_fixture"), ["HWE-P1"])
|
||||
async def test_manual_flow_works_with_v2_api_support(
|
||||
hass: HomeAssistant,
|
||||
mock_homewizardenergy_v2: MagicMock,
|
||||
@@ -652,7 +727,70 @@ async def test_manual_flow_works_with_v2_api_support(
|
||||
mock_homewizardenergy_v2.device.side_effect = None
|
||||
mock_homewizardenergy_v2.get_token.side_effect = None
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(result["flow_id"], {})
|
||||
with patch(
|
||||
"homeassistant.components.homewizard.config_flow.has_v2_api", return_value=True
|
||||
):
|
||||
result = await hass.config_entries.flow.async_configure(result["flow_id"], {})
|
||||
|
||||
assert result["type"] is FlowResultType.CREATE_ENTRY
|
||||
assert len(hass.config_entries.async_entries(DOMAIN)) == 1
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("mock_setup_entry")
|
||||
@pytest.mark.parametrize(("device_fixture"), ["HWE-KWH1"])
|
||||
async def test_manual_flow_energy_monitoring_works_with_v2_api_support(
|
||||
hass: HomeAssistant,
|
||||
mock_homewizardenergy_v2: MagicMock,
|
||||
mock_setup_entry: AsyncMock,
|
||||
) -> None:
|
||||
"""Test config flow accepts user configuration for energy monitoring.
|
||||
|
||||
This should trigger authorization when v2 support is detected.
|
||||
It should ask for usage if a energy monitoring device is configured.
|
||||
"""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["step_id"] == "user"
|
||||
|
||||
# Simulate v2 support but not authorized
|
||||
mock_homewizardenergy_v2.device.side_effect = UnauthorizedError
|
||||
mock_homewizardenergy_v2.get_token.side_effect = DisabledError
|
||||
with (
|
||||
patch(
|
||||
"homeassistant.components.homewizard.config_flow.has_v2_api",
|
||||
return_value=True,
|
||||
),
|
||||
):
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"], {CONF_IP_ADDRESS: "2.2.2.2"}
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["step_id"] == "authorize"
|
||||
|
||||
# Simulate user authorizing
|
||||
mock_homewizardenergy_v2.device.side_effect = None
|
||||
mock_homewizardenergy_v2.get_token.side_effect = None
|
||||
|
||||
with (
|
||||
patch(
|
||||
"homeassistant.components.homewizard.config_flow.has_v2_api",
|
||||
return_value=True,
|
||||
),
|
||||
):
|
||||
result = await hass.config_entries.flow.async_configure(result["flow_id"], {})
|
||||
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["step_id"] == "usage"
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{"usage": "generation"},
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.CREATE_ENTRY
|
||||
assert len(hass.config_entries.async_entries(DOMAIN)) == 1
|
||||
@@ -700,7 +838,16 @@ async def test_manual_flow_detects_failed_user_authorization(
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(result["flow_id"], {})
|
||||
|
||||
assert result["type"] is FlowResultType.CREATE_ENTRY
|
||||
# Energy monitoring devices with an with configurable usage have an extra flow step
|
||||
assert (
|
||||
result["type"] is FlowResultType.CREATE_ENTRY and result["data"][CONF_TOKEN]
|
||||
) or (result["type"] is FlowResultType.FORM and result["step_id"] == "usage")
|
||||
|
||||
if result["type"] is FlowResultType.FORM and result["step_id"] == "usage":
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"], {"usage": "generation"}
|
||||
)
|
||||
|
||||
assert len(hass.config_entries.async_entries(DOMAIN)) == 1
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
|
||||
@@ -830,10 +977,12 @@ async def test_discovery_with_v2_api_ask_authorization(
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["step_id"] == "authorize"
|
||||
|
||||
mock_homewizardenergy_v2.device.side_effect = None
|
||||
mock_homewizardenergy_v2.get_token.side_effect = None
|
||||
mock_homewizardenergy_v2.get_token.return_value = "cool_token"
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(result["flow_id"], {})
|
||||
|
||||
assert result["type"] is FlowResultType.CREATE_ENTRY
|
||||
assert result["data"][CONF_TOKEN] == "cool_token"
|
||||
# Energy monitoring devices with an with configurable usage have an extra flow step
|
||||
assert (
|
||||
result["type"] is FlowResultType.CREATE_ENTRY and result["data"][CONF_TOKEN]
|
||||
) or (result["type"] is FlowResultType.FORM and result["step_id"] == "usage")
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"""Tests for the homewizard component."""
|
||||
|
||||
from collections.abc import Iterable
|
||||
from datetime import timedelta
|
||||
from unittest.mock import MagicMock, patch
|
||||
import weakref
|
||||
@@ -11,8 +12,14 @@ import pytest
|
||||
from homeassistant.components.homewizard import get_main_device
|
||||
from homeassistant.components.homewizard.const import DOMAIN
|
||||
from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntryState
|
||||
from homeassistant.const import CONF_IP_ADDRESS
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import device_registry as dr, issue_registry as ir
|
||||
from homeassistant.helpers import (
|
||||
device_registry as dr,
|
||||
entity_registry as er,
|
||||
issue_registry as ir,
|
||||
)
|
||||
from homeassistant.helpers.entity_platform import EntityRegistry
|
||||
|
||||
from tests.common import MockConfigEntry, async_fire_time_changed
|
||||
|
||||
@@ -254,3 +261,203 @@ async def test_disablederror_reloads_integration(
|
||||
flow = flows[0]
|
||||
assert flow.get("step_id") == "reauth_enable_api"
|
||||
assert flow.get("handler") == DOMAIN
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("mock_homewizardenergy")
|
||||
@pytest.mark.parametrize(
|
||||
("device_fixture", "mock_config_entry", "enabled", "disabled"),
|
||||
[
|
||||
(
|
||||
"HWE-SKT-21-initial",
|
||||
MockConfigEntry(
|
||||
title="Device",
|
||||
domain=DOMAIN,
|
||||
data={
|
||||
CONF_IP_ADDRESS: "127.0.0.1",
|
||||
"usage": "consumption",
|
||||
},
|
||||
unique_id="HWE-SKT_5c2fafabcdef",
|
||||
),
|
||||
("sensor.device_power",),
|
||||
("sensor.device_production_power",),
|
||||
),
|
||||
(
|
||||
"HWE-SKT-21-initial",
|
||||
MockConfigEntry(
|
||||
title="Device",
|
||||
domain=DOMAIN,
|
||||
data={
|
||||
CONF_IP_ADDRESS: "127.0.0.1",
|
||||
"usage": "generation",
|
||||
},
|
||||
unique_id="HWE-SKT_5c2fafabcdef",
|
||||
),
|
||||
# we explicitly indicated that the device was monitoring
|
||||
# generated energy, so we ignore power sensor to avoid confusion
|
||||
("sensor.device_production_power",),
|
||||
("sensor.device_power",),
|
||||
),
|
||||
(
|
||||
"HWE-SKT-21",
|
||||
MockConfigEntry(
|
||||
title="Device",
|
||||
domain=DOMAIN,
|
||||
data={
|
||||
CONF_IP_ADDRESS: "127.0.0.1",
|
||||
"usage": "consumption",
|
||||
},
|
||||
unique_id="HWE-SKT_5c2fafabcdef",
|
||||
),
|
||||
# device has a non zero export, so both sensors are enabled
|
||||
(
|
||||
"sensor.device_power",
|
||||
"sensor.device_production_power",
|
||||
),
|
||||
(),
|
||||
),
|
||||
(
|
||||
"HWE-SKT-21",
|
||||
MockConfigEntry(
|
||||
title="Device",
|
||||
domain=DOMAIN,
|
||||
data={
|
||||
CONF_IP_ADDRESS: "127.0.0.1",
|
||||
"usage": "generation",
|
||||
},
|
||||
unique_id="HWE-SKT_5c2fafabcdef",
|
||||
),
|
||||
# we explicitly indicated that the device was monitoring
|
||||
# generated energy, so we ignore power sensor to avoid confusion
|
||||
("sensor.device_production_power",),
|
||||
("sensor.device_power",),
|
||||
),
|
||||
],
|
||||
ids=[
|
||||
"consumption_intital",
|
||||
"generation_initial",
|
||||
"consumption_used",
|
||||
"generation_used",
|
||||
],
|
||||
)
|
||||
async def test_setup_device_energy_monitoring_v1(
|
||||
hass: HomeAssistant,
|
||||
entity_registry: EntityRegistry,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
enabled: Iterable[str],
|
||||
disabled: Iterable[str],
|
||||
) -> None:
|
||||
"""Test correct entities are enabled by default."""
|
||||
mock_config_entry.add_to_hass(hass)
|
||||
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
for enabled_item in enabled:
|
||||
assert (entry := entity_registry.async_get(enabled_item))
|
||||
assert not entry.disabled
|
||||
|
||||
for disabled_item in disabled:
|
||||
assert (entry := entity_registry.async_get(disabled_item))
|
||||
assert entry.disabled
|
||||
assert entry.disabled_by is er.RegistryEntryDisabler.INTEGRATION
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("mock_homewizardenergy")
|
||||
@pytest.mark.parametrize(
|
||||
("device_fixture", "mock_config_entry", "enabled", "disabled"),
|
||||
[
|
||||
(
|
||||
"HWE-KWH1-initial",
|
||||
MockConfigEntry(
|
||||
title="Device",
|
||||
domain=DOMAIN,
|
||||
data={
|
||||
CONF_IP_ADDRESS: "127.0.0.1",
|
||||
"usage": "consumption",
|
||||
},
|
||||
unique_id="HWE-KWH1_5c2fafabcdef",
|
||||
),
|
||||
("sensor.device_power",),
|
||||
("sensor.device_production_power",),
|
||||
),
|
||||
(
|
||||
"HWE-KWH1-initial",
|
||||
MockConfigEntry(
|
||||
title="Device",
|
||||
domain=DOMAIN,
|
||||
data={
|
||||
CONF_IP_ADDRESS: "127.0.0.1",
|
||||
"usage": "generation",
|
||||
},
|
||||
unique_id="HWE-KWH1_5c2fafabcdef",
|
||||
),
|
||||
# we explicitly indicated that the device was monitoring
|
||||
# generated energy, so we ignore power sensor to avoid confusion
|
||||
("sensor.device_production_power",),
|
||||
("sensor.device_power",),
|
||||
),
|
||||
(
|
||||
"HWE-KWH1",
|
||||
MockConfigEntry(
|
||||
title="Device",
|
||||
domain=DOMAIN,
|
||||
data={
|
||||
CONF_IP_ADDRESS: "127.0.0.1",
|
||||
"usage": "consumption",
|
||||
},
|
||||
unique_id="HWE-KWH1_5c2fafabcdef",
|
||||
),
|
||||
# device has a non zero export, so both sensors are enabled
|
||||
(
|
||||
"sensor.device_power",
|
||||
"sensor.device_production_power",
|
||||
),
|
||||
(),
|
||||
),
|
||||
(
|
||||
"HWE-KWH1",
|
||||
MockConfigEntry(
|
||||
title="Device",
|
||||
domain=DOMAIN,
|
||||
data={
|
||||
CONF_IP_ADDRESS: "127.0.0.1",
|
||||
"usage": "generation",
|
||||
},
|
||||
unique_id="HWE-KWH1_5c2fafabcdef",
|
||||
),
|
||||
# we explicitly indicated that the device was monitoring
|
||||
# generated energy, so we ignore power sensor to avoid confusion
|
||||
("sensor.device_production_power",),
|
||||
("sensor.device_power",),
|
||||
),
|
||||
],
|
||||
ids=[
|
||||
"consumption_intital",
|
||||
"generation_initial",
|
||||
"consumption_used",
|
||||
"generation_used",
|
||||
],
|
||||
)
|
||||
async def test_setup_device_energy_monitoring_v2(
|
||||
hass: HomeAssistant,
|
||||
entity_registry: EntityRegistry,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
enabled: Iterable[str],
|
||||
disabled: Iterable[str],
|
||||
) -> None:
|
||||
"""Test correct entities are enabled by default."""
|
||||
mock_config_entry.add_to_hass(hass)
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.homewizard.config_flow.has_v2_api", return_value=True
|
||||
):
|
||||
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
await hass.async_block_till_done(wait_background_tasks=True)
|
||||
|
||||
for enabled_item in enabled:
|
||||
assert (entry := entity_registry.async_get(enabled_item))
|
||||
assert not entry.disabled
|
||||
|
||||
for disabled_item in disabled:
|
||||
assert (entry := entity_registry.async_get(disabled_item))
|
||||
assert entry.disabled
|
||||
assert entry.disabled_by is er.RegistryEntryDisabler.INTEGRATION
|
||||
|
||||
Reference in New Issue
Block a user