mirror of
https://github.com/home-assistant/core.git
synced 2025-07-14 00:37:13 +00:00
Add support for HomeWizard enable/disable cloud feature (#82573)
This commit is contained in:
parent
093bd00807
commit
27bd1520e8
@ -5,7 +5,7 @@ from datetime import timedelta
|
|||||||
from typing import TypedDict
|
from typing import TypedDict
|
||||||
|
|
||||||
# Set up.
|
# Set up.
|
||||||
from homewizard_energy.models import Data, Device, State
|
from homewizard_energy.models import Data, Device, State, System
|
||||||
|
|
||||||
from homeassistant.const import Platform
|
from homeassistant.const import Platform
|
||||||
|
|
||||||
@ -30,3 +30,4 @@ class DeviceResponseEntry(TypedDict):
|
|||||||
device: Device
|
device: Device
|
||||||
data: Data
|
data: Data
|
||||||
state: State
|
state: State
|
||||||
|
system: System
|
||||||
|
@ -39,8 +39,13 @@ class HWEnergyDeviceUpdateCoordinator(DataUpdateCoordinator[DeviceResponseEntry]
|
|||||||
"device": await self.api.device(),
|
"device": await self.api.device(),
|
||||||
"data": await self.api.data(),
|
"data": await self.api.data(),
|
||||||
"state": await self.api.state(),
|
"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:
|
except RequestError as ex:
|
||||||
raise UpdateFailed("Device did not respond as expected") from ex
|
raise UpdateFailed("Device did not respond as expected") from ex
|
||||||
|
|
||||||
|
@ -27,6 +27,9 @@ async def async_get_config_entry_diagnostics(
|
|||||||
"state": asdict(coordinator.data["state"])
|
"state": asdict(coordinator.data["state"])
|
||||||
if coordinator.data["state"] is not None
|
if coordinator.data["state"] is not None
|
||||||
else None,
|
else None,
|
||||||
|
"system": asdict(coordinator.data["system"])
|
||||||
|
if coordinator.data["system"] is not None
|
||||||
|
else None,
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/homewizard",
|
"documentation": "https://www.home-assistant.io/integrations/homewizard",
|
||||||
"codeowners": ["@DCSBL"],
|
"codeowners": ["@DCSBL"],
|
||||||
"dependencies": [],
|
"dependencies": [],
|
||||||
"requirements": ["python-homewizard-energy==1.1.0"],
|
"requirements": ["python-homewizard-energy==1.3.1"],
|
||||||
"zeroconf": ["_hwenergy._tcp.local."],
|
"zeroconf": ["_hwenergy._tcp.local."],
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
|
@ -30,6 +30,13 @@ async def async_setup_entry(
|
|||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if coordinator.data["system"]:
|
||||||
|
async_add_entities(
|
||||||
|
[
|
||||||
|
HWEnergyEnableCloudEntity(hass, coordinator, entry),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class HWEnergySwitchEntity(
|
class HWEnergySwitchEntity(
|
||||||
CoordinatorEntity[HWEnergyDeviceUpdateCoordinator], SwitchEntity
|
CoordinatorEntity[HWEnergyDeviceUpdateCoordinator], SwitchEntity
|
||||||
@ -124,3 +131,47 @@ class HWEnergySwitchLockEntity(HWEnergySwitchEntity):
|
|||||||
def is_on(self) -> bool:
|
def is_on(self) -> bool:
|
||||||
"""Return true if switch is on."""
|
"""Return true if switch is on."""
|
||||||
return bool(self.coordinator.data["state"].switch_lock)
|
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)
|
||||||
|
@ -2003,7 +2003,7 @@ python-gc100==1.0.3a0
|
|||||||
python-gitlab==1.6.0
|
python-gitlab==1.6.0
|
||||||
|
|
||||||
# homeassistant.components.homewizard
|
# homeassistant.components.homewizard
|
||||||
python-homewizard-energy==1.1.0
|
python-homewizard-energy==1.3.1
|
||||||
|
|
||||||
# homeassistant.components.hp_ilo
|
# homeassistant.components.hp_ilo
|
||||||
python-hpilo==4.3
|
python-hpilo==4.3
|
||||||
|
@ -1399,7 +1399,7 @@ python-forecastio==1.4.0
|
|||||||
python-fullykiosk==0.0.11
|
python-fullykiosk==0.0.11
|
||||||
|
|
||||||
# homeassistant.components.homewizard
|
# homeassistant.components.homewizard
|
||||||
python-homewizard-energy==1.1.0
|
python-homewizard-energy==1.3.1
|
||||||
|
|
||||||
# homeassistant.components.izone
|
# homeassistant.components.izone
|
||||||
python-izone==1.2.9
|
python-izone==1.2.9
|
||||||
|
@ -2,7 +2,8 @@
|
|||||||
import json
|
import json
|
||||||
from unittest.mock import AsyncMock, patch
|
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
|
import pytest
|
||||||
|
|
||||||
from homeassistant.components.homewizard.const import DOMAIN
|
from homeassistant.components.homewizard.const import DOMAIN
|
||||||
@ -37,26 +38,32 @@ def mock_config_entry() -> MockConfigEntry:
|
|||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def mock_homewizardenergy():
|
def mock_homewizardenergy():
|
||||||
"""Return a mocked P1 meter."""
|
"""Return a mocked all-feature device."""
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.homewizard.coordinator.HomeWizardEnergy",
|
"homeassistant.components.homewizard.coordinator.HomeWizardEnergy",
|
||||||
) as device:
|
) as device:
|
||||||
client = device.return_value
|
client = device.return_value
|
||||||
|
client.features = AsyncMock(return_value=Features("HWE-SKT", "3.01"))
|
||||||
client.device = AsyncMock(
|
client.device = AsyncMock(
|
||||||
return_value=Device.from_dict(
|
side_effect=lambda: Device.from_dict(
|
||||||
json.loads(load_fixture("homewizard/device.json"))
|
json.loads(load_fixture("homewizard/device.json"))
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
client.data = AsyncMock(
|
client.data = AsyncMock(
|
||||||
return_value=Data.from_dict(
|
side_effect=lambda: Data.from_dict(
|
||||||
json.loads(load_fixture("homewizard/data.json"))
|
json.loads(load_fixture("homewizard/data.json"))
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
client.state = AsyncMock(
|
client.state = AsyncMock(
|
||||||
return_value=State.from_dict(
|
side_effect=lambda: State.from_dict(
|
||||||
json.loads(load_fixture("homewizard/state.json"))
|
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
|
yield device
|
||||||
|
|
||||||
|
|
||||||
|
3
tests/components/homewizard/fixtures/system.json
Normal file
3
tests/components/homewizard/fixtures/system.json
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"cloud_enabled": true
|
||||||
|
}
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
from unittest.mock import AsyncMock
|
from unittest.mock import AsyncMock
|
||||||
|
|
||||||
|
from homewizard_energy.features import Features
|
||||||
from homewizard_energy.models import Device
|
from homewizard_energy.models import Device
|
||||||
|
|
||||||
|
|
||||||
@ -10,6 +11,7 @@ def get_mock_device(
|
|||||||
host="1.2.3.4",
|
host="1.2.3.4",
|
||||||
product_name="P1 meter",
|
product_name="P1 meter",
|
||||||
product_type="HWE-P1",
|
product_type="HWE-P1",
|
||||||
|
firmware_version="1.00",
|
||||||
):
|
):
|
||||||
"""Return a mock bridge."""
|
"""Return a mock bridge."""
|
||||||
mock_device = AsyncMock()
|
mock_device = AsyncMock()
|
||||||
@ -21,11 +23,15 @@ def get_mock_device(
|
|||||||
product_type=product_type,
|
product_type=product_type,
|
||||||
serial=serial,
|
serial=serial,
|
||||||
api_version="V1",
|
api_version="V1",
|
||||||
firmware_version="1.00",
|
firmware_version=firmware_version,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
mock_device.data = AsyncMock(return_value=None)
|
mock_device.data = AsyncMock(return_value=None)
|
||||||
mock_device.state = 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()
|
mock_device.close = AsyncMock()
|
||||||
|
|
||||||
|
@ -45,5 +45,6 @@ async def test_diagnostics(
|
|||||||
"total_liter_m3": 1234.567,
|
"total_liter_m3": 1234.567,
|
||||||
},
|
},
|
||||||
"state": {"power_on": True, "switch_lock": False, "brightness": 255},
|
"state": {"power_on": True, "switch_lock": False, "brightness": 255},
|
||||||
|
"system": {"cloud_enabled": True},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
from unittest.mock import AsyncMock, patch
|
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 import switch
|
||||||
from homeassistant.components.switch import SwitchDeviceClass
|
from homeassistant.components.switch import SwitchDeviceClass
|
||||||
@ -286,3 +286,63 @@ async def test_switch_lock_sets_power_on_unavailable(
|
|||||||
== STATE_OFF
|
== STATE_OFF
|
||||||
)
|
)
|
||||||
assert len(api.state_set.mock_calls) == 2
|
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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user