Add support for HomeWizard enable/disable cloud feature (#82573)

This commit is contained in:
Duco Sebel 2022-11-27 20:26:15 +01:00 committed by GitHub
parent 093bd00807
commit 27bd1520e8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 148 additions and 11 deletions

View file

@ -5,7 +5,7 @@ from datetime import timedelta
from typing import TypedDict
# Set up.
from homewizard_energy.models import Data, Device, State
from homewizard_energy.models import Data, Device, State, System
from homeassistant.const import Platform
@ -30,3 +30,4 @@ class DeviceResponseEntry(TypedDict):
device: Device
data: Data
state: State
system: System

View file

@ -39,8 +39,13 @@ class HWEnergyDeviceUpdateCoordinator(DataUpdateCoordinator[DeviceResponseEntry]
"device": await self.api.device(),
"data": await self.api.data(),
"state": await self.api.state(),
"system": None,
}
features = await self.api.features()
if features.has_system:
data["system"] = await self.api.system()
except RequestError as ex:
raise UpdateFailed("Device did not respond as expected") from ex

View file

@ -27,6 +27,9 @@ async def async_get_config_entry_diagnostics(
"state": asdict(coordinator.data["state"])
if coordinator.data["state"] is not None
else None,
"system": asdict(coordinator.data["system"])
if coordinator.data["system"] is not None
else None,
}
return {

View file

@ -4,7 +4,7 @@
"documentation": "https://www.home-assistant.io/integrations/homewizard",
"codeowners": ["@DCSBL"],
"dependencies": [],
"requirements": ["python-homewizard-energy==1.1.0"],
"requirements": ["python-homewizard-energy==1.3.1"],
"zeroconf": ["_hwenergy._tcp.local."],
"config_flow": true,
"iot_class": "local_polling",

View file

@ -30,6 +30,13 @@ async def async_setup_entry(
]
)
if coordinator.data["system"]:
async_add_entities(
[
HWEnergyEnableCloudEntity(hass, coordinator, entry),
]
)
class HWEnergySwitchEntity(
CoordinatorEntity[HWEnergyDeviceUpdateCoordinator], SwitchEntity
@ -124,3 +131,47 @@ class HWEnergySwitchLockEntity(HWEnergySwitchEntity):
def is_on(self) -> bool:
"""Return true if switch is on."""
return bool(self.coordinator.data["state"].switch_lock)
class HWEnergyEnableCloudEntity(HWEnergySwitchEntity):
"""
Representation of the enable cloud configuration.
Turning off 'cloud connection' turns off all communication to HomeWizard Cloud.
At this point, the device is fully local.
"""
_attr_name = "Cloud connection"
_attr_device_class = SwitchDeviceClass.SWITCH
_attr_entity_category = EntityCategory.CONFIG
def __init__(
self,
hass: HomeAssistant,
coordinator: HWEnergyDeviceUpdateCoordinator,
entry: ConfigEntry,
) -> None:
"""Initialize the switch."""
super().__init__(coordinator, entry, "cloud_connection")
self.hass = hass
self.entry = entry
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn cloud connection on."""
await self.coordinator.api.system_set(cloud_enabled=True)
await self.coordinator.async_refresh()
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn cloud connection off."""
await self.coordinator.api.system_set(cloud_enabled=False)
await self.coordinator.async_refresh()
@property
def icon(self) -> str | None:
"""Return the icon."""
return "mdi:cloud" if self.is_on else "mdi:cloud-off-outline"
@property
def is_on(self) -> bool:
"""Return true if cloud connection is active."""
return bool(self.coordinator.data["system"].cloud_enabled)

View file

@ -2003,7 +2003,7 @@ python-gc100==1.0.3a0
python-gitlab==1.6.0
# homeassistant.components.homewizard
python-homewizard-energy==1.1.0
python-homewizard-energy==1.3.1
# homeassistant.components.hp_ilo
python-hpilo==4.3

View file

@ -1399,7 +1399,7 @@ python-forecastio==1.4.0
python-fullykiosk==0.0.11
# homeassistant.components.homewizard
python-homewizard-energy==1.1.0
python-homewizard-energy==1.3.1
# homeassistant.components.izone
python-izone==1.2.9

View file

@ -2,7 +2,8 @@
import json
from unittest.mock import AsyncMock, patch
from homewizard_energy.models import Data, Device, State
from homewizard_energy.features import Features
from homewizard_energy.models import Data, Device, State, System
import pytest
from homeassistant.components.homewizard.const import DOMAIN
@ -37,26 +38,32 @@ def mock_config_entry() -> MockConfigEntry:
@pytest.fixture
def mock_homewizardenergy():
"""Return a mocked P1 meter."""
"""Return a mocked all-feature device."""
with patch(
"homeassistant.components.homewizard.coordinator.HomeWizardEnergy",
) as device:
client = device.return_value
client.features = AsyncMock(return_value=Features("HWE-SKT", "3.01"))
client.device = AsyncMock(
return_value=Device.from_dict(
side_effect=lambda: Device.from_dict(
json.loads(load_fixture("homewizard/device.json"))
)
)
client.data = AsyncMock(
return_value=Data.from_dict(
side_effect=lambda: Data.from_dict(
json.loads(load_fixture("homewizard/data.json"))
)
)
client.state = AsyncMock(
return_value=State.from_dict(
side_effect=lambda: State.from_dict(
json.loads(load_fixture("homewizard/state.json"))
)
)
client.system = AsyncMock(
side_effect=lambda: System.from_dict(
json.loads(load_fixture("homewizard/system.json"))
)
)
yield device

View file

@ -0,0 +1,3 @@
{
"cloud_enabled": true
}

View file

@ -2,6 +2,7 @@
from unittest.mock import AsyncMock
from homewizard_energy.features import Features
from homewizard_energy.models import Device
@ -10,6 +11,7 @@ def get_mock_device(
host="1.2.3.4",
product_name="P1 meter",
product_type="HWE-P1",
firmware_version="1.00",
):
"""Return a mock bridge."""
mock_device = AsyncMock()
@ -21,11 +23,15 @@ def get_mock_device(
product_type=product_type,
serial=serial,
api_version="V1",
firmware_version="1.00",
firmware_version=firmware_version,
)
)
mock_device.data = AsyncMock(return_value=None)
mock_device.state = AsyncMock(return_value=None)
mock_device.system = AsyncMock(return_value=None)
mock_device.features = AsyncMock(
return_value=Features(product_type, firmware_version)
)
mock_device.close = AsyncMock()

View file

@ -45,5 +45,6 @@ async def test_diagnostics(
"total_liter_m3": 1234.567,
},
"state": {"power_on": True, "switch_lock": False, "brightness": 255},
"system": {"cloud_enabled": True},
},
}

View file

@ -2,7 +2,7 @@
from unittest.mock import AsyncMock, patch
from homewizard_energy.models import State
from homewizard_energy.models import State, System
from homeassistant.components import switch
from homeassistant.components.switch import SwitchDeviceClass
@ -286,3 +286,63 @@ async def test_switch_lock_sets_power_on_unavailable(
== STATE_OFF
)
assert len(api.state_set.mock_calls) == 2
async def test_cloud_connection_on_off(hass, mock_config_entry_data, mock_config_entry):
"""Test entity turns switch on and off."""
api = get_mock_device(product_type="HWE-SKT", firmware_version="3.02")
api.system = AsyncMock(return_value=System.from_dict({"cloud_enabled": False}))
def system_set(cloud_enabled):
api.system = AsyncMock(
return_value=System.from_dict({"cloud_enabled": cloud_enabled})
)
api.system_set = AsyncMock(side_effect=system_set)
with patch(
"homeassistant.components.homewizard.coordinator.HomeWizardEnergy",
return_value=api,
):
entry = mock_config_entry
entry.data = mock_config_entry_data
entry.add_to_hass(hass)
await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
assert (
hass.states.get("switch.product_name_aabbccddeeff_cloud_connection").state
== STATE_OFF
)
# Enable cloud
await hass.services.async_call(
switch.DOMAIN,
SERVICE_TURN_ON,
{"entity_id": "switch.product_name_aabbccddeeff_cloud_connection"},
blocking=True,
)
await hass.async_block_till_done()
assert len(api.system_set.mock_calls) == 1
assert (
hass.states.get("switch.product_name_aabbccddeeff_cloud_connection").state
== STATE_ON
)
# Disable cloud
await hass.services.async_call(
switch.DOMAIN,
SERVICE_TURN_OFF,
{"entity_id": "switch.product_name_aabbccddeeff_cloud_connection"},
blocking=True,
)
await hass.async_block_till_done()
assert (
hass.states.get("switch.product_name_aabbccddeeff_cloud_connection").state
== STATE_OFF
)
assert len(api.system_set.mock_calls) == 2