Energyflip API update V3->V4 (#179723)

This commit is contained in:
Thijs van Loon
2026-09-14 22:01:14 +02:00
committed by GitHub
parent 48185743a4
commit 89ea156ef6
10 changed files with 94 additions and 140 deletions
Generated
+2 -2
View File
@@ -824,8 +824,8 @@ CLAUDE.md @home-assistant/core
/tests/components/hue/ @marcelveldt
/homeassistant/components/hue_ble/ @flip-dots
/tests/components/hue_ble/ @flip-dots
/homeassistant/components/huisbaasje/ @dennisschroer
/tests/components/huisbaasje/ @dennisschroer
/homeassistant/components/huisbaasje/ @dennisschroer @DieEneSchrodinger
/tests/components/huisbaasje/ @dennisschroer @DieEneSchrodinger
/homeassistant/components/humidifier/ @home-assistant/core @Shulyaka
/tests/components/humidifier/ @home-assistant/core @Shulyaka
/homeassistant/components/humidity/ @home-assistant/core
@@ -32,6 +32,13 @@ async def async_setup_entry(hass: HomeAssistant, entry: EnergyFlipConfigEntry) -
_LOGGER.error("Authentication failed: %s", str(exception))
return False
# Immediately get customer id, since it is required for all api calls
try:
await energyflip.customer_overview()
except EnergyFlipException as exception:
_LOGGER.error("Getting customer id failed: %s", str(exception))
return False
# Create a coordinator for polling updates
coordinator = EnergyFlipUpdateCoordinator(hass, entry, energyflip)
@@ -3,8 +3,10 @@
from energyflip.const import (
SOURCE_TYPE_ELECTRICITY,
SOURCE_TYPE_ELECTRICITY_IN,
SOURCE_TYPE_ELECTRICITY_IN_HIGH,
SOURCE_TYPE_ELECTRICITY_IN_LOW,
SOURCE_TYPE_ELECTRICITY_OUT,
SOURCE_TYPE_ELECTRICITY_OUT_HIGH,
SOURCE_TYPE_ELECTRICITY_OUT_LOW,
SOURCE_TYPE_GAS,
)
@@ -27,7 +29,9 @@ SOURCE_TYPES = [
SOURCE_TYPE_ELECTRICITY,
SOURCE_TYPE_ELECTRICITY_IN,
SOURCE_TYPE_ELECTRICITY_IN_LOW,
SOURCE_TYPE_ELECTRICITY_IN_HIGH,
SOURCE_TYPE_ELECTRICITY_OUT,
SOURCE_TYPE_ELECTRICITY_OUT_LOW,
SOURCE_TYPE_ELECTRICITY_OUT_HIGH,
SOURCE_TYPE_GAS,
]
@@ -59,10 +59,7 @@ class EnergyFlipUpdateCoordinator(DataUpdateCoordinator[dict[str, dict[str, Any]
# Note: TimeoutError and aiohttp.ClientError are already
# handled by the data update coordinator.
async with asyncio.timeout(FETCH_TIMEOUT):
if not self._energyflip.is_authenticated():
_LOGGER.warning("EnergyFlip is unauthenticated. Reauthenticating")
await self._energyflip.authenticate()
# Invalidated authentications are handled by current_measurements itself
current_measurements = await self._energyflip.current_measurements()
return {
@@ -1,11 +1,11 @@
{
"domain": "huisbaasje",
"name": "EnergyFlip",
"codeowners": ["@dennisschroer"],
"codeowners": ["@dennisschroer", "@DieEneSchrodinger"],
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/huisbaasje",
"integration_type": "device",
"iot_class": "cloud_polling",
"loggers": ["energyflip"],
"requirements": ["energyflip-client==0.2.2"]
"requirements": ["energyflip-api-client==0.3.0"]
}
@@ -59,38 +59,6 @@ SENSORS_INFO = [
key=SOURCE_TYPE_ELECTRICITY,
state_class=SensorStateClass.MEASUREMENT,
),
EnergyFlipSensorEntityDescription(
translation_key="current_power_peak",
sensor_type=SENSOR_TYPE_RATE,
device_class=SensorDeviceClass.POWER,
native_unit_of_measurement=UnitOfPower.WATT,
key=SOURCE_TYPE_ELECTRICITY_IN,
state_class=SensorStateClass.MEASUREMENT,
),
EnergyFlipSensorEntityDescription(
translation_key="current_power_off_peak",
sensor_type=SENSOR_TYPE_RATE,
device_class=SensorDeviceClass.POWER,
native_unit_of_measurement=UnitOfPower.WATT,
key=SOURCE_TYPE_ELECTRICITY_IN_LOW,
state_class=SensorStateClass.MEASUREMENT,
),
EnergyFlipSensorEntityDescription(
translation_key="current_power_out_peak",
sensor_type=SENSOR_TYPE_RATE,
device_class=SensorDeviceClass.POWER,
native_unit_of_measurement=UnitOfPower.WATT,
key=SOURCE_TYPE_ELECTRICITY_OUT,
state_class=SensorStateClass.MEASUREMENT,
),
EnergyFlipSensorEntityDescription(
translation_key="current_power_out_off_peak",
sensor_type=SENSOR_TYPE_RATE,
device_class=SensorDeviceClass.POWER,
native_unit_of_measurement=UnitOfPower.WATT,
key=SOURCE_TYPE_ELECTRICITY_OUT_LOW,
state_class=SensorStateClass.MEASUREMENT,
),
EnergyFlipSensorEntityDescription(
translation_key="energy_consumption_peak_today",
device_class=SensorDeviceClass.ENERGY,
@@ -25,18 +25,6 @@
"current_power": {
"name": "Current power"
},
"current_power_off_peak": {
"name": "Current power in off-peak"
},
"current_power_out_off_peak": {
"name": "Current power out off-peak"
},
"current_power_out_peak": {
"name": "Current power out peak"
},
"current_power_peak": {
"name": "Current power in peak"
},
"energy_consumption_off_peak_today": {
"name": "Energy consumption off-peak today"
},
+1 -1
View File
@@ -950,7 +950,7 @@ emulated-roku==0.3.0
energieleser==0.1.6
# homeassistant.components.huisbaasje
energyflip-client==0.2.2
energyflip-api-client==0.3.0
# homeassistant.components.energyid
energyid-webhooks==0.0.14
+68 -16
View File
@@ -20,13 +20,14 @@ async def test_setup_entry(hass: HomeAssistant) -> None:
patch(
"energyflip.EnergyFlip.authenticate", return_value=None
) as mock_authenticate,
patch(
"energyflip.EnergyFlip.is_authenticated", return_value=True
) as mock_is_authenticated,
patch(
"energyflip.EnergyFlip.current_measurements",
return_value=MOCK_CURRENT_MEASUREMENTS,
) as mock_current_measurements,
patch(
"energyflip.EnergyFlip.customer_overview",
return_value=None,
) as mock_customer_overview,
):
config_entry = MockConfigEntry(
version=1,
@@ -50,19 +51,25 @@ async def test_setup_entry(hass: HomeAssistant) -> None:
# Assert entities are loaded
entities = hass.states.async_entity_ids("sensor")
assert len(entities) == 18
assert len(entities) == 14
# Assert mocks are called
assert len(mock_authenticate.mock_calls) == 1
assert len(mock_is_authenticated.mock_calls) == 1
assert len(mock_current_measurements.mock_calls) == 1
assert len(mock_customer_overview.mock_calls) == 1
async def test_setup_entry_error(hass: HomeAssistant) -> None:
"""Test for successfully setting a config entry."""
with patch(
"energyflip.EnergyFlip.authenticate", side_effect=EnergyFlipException
) as mock_authenticate:
async def test_setup_entry_error_authenticate(hass: HomeAssistant) -> None:
"""Test for unsuccessfully setting a config entry, erroring on authenticate."""
with (
patch(
"energyflip.EnergyFlip.authenticate", side_effect=EnergyFlipException
) as mock_authenticate,
patch(
"energyflip.EnergyFlip.customer_overview",
side_effect=EnergyFlipException,
) as mock_customer_overview,
):
config_entry = MockConfigEntry(
version=1,
domain=DOMAIN,
@@ -91,6 +98,50 @@ async def test_setup_entry_error(hass: HomeAssistant) -> None:
# Assert mocks are called
assert len(mock_authenticate.mock_calls) == 1
# Assert customer_overview wasn't called and authenticate exception was handled properly
assert len(mock_customer_overview.mock_calls) == 0
async def test_setup_entry_error_customer_overview(hass: HomeAssistant) -> None:
"""Test for unsuccessfully setting a config entry, erroring on customer_overview."""
with (
patch(
"energyflip.EnergyFlip.authenticate", return_value=None
) as mock_authenticate,
patch(
"energyflip.EnergyFlip.customer_overview",
side_effect=EnergyFlipException,
) as mock_customer_overview,
):
config_entry = MockConfigEntry(
version=1,
domain=DOMAIN,
title="userId",
data={
CONF_ID: "userId",
CONF_USERNAME: "username",
CONF_PASSWORD: "password",
},
source="test",
)
config_entry.add_to_hass(hass)
assert config_entry.state is ConfigEntryState.NOT_LOADED
await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
# Assert integration is loaded with error
assert config_entry.state is ConfigEntryState.SETUP_ERROR
assert DOMAIN not in hass.data
# Assert entities are not loaded
entities = hass.states.async_entity_ids("sensor")
assert len(entities) == 0
# Assert mocks are called
assert len(mock_authenticate.mock_calls) == 1
assert len(mock_customer_overview.mock_calls) == 1
async def test_unload_entry(hass: HomeAssistant) -> None:
"""Test for successfully unloading the config entry."""
@@ -98,13 +149,14 @@ async def test_unload_entry(hass: HomeAssistant) -> None:
patch(
"energyflip.EnergyFlip.authenticate", return_value=None
) as mock_authenticate,
patch(
"energyflip.EnergyFlip.is_authenticated", return_value=True
) as mock_is_authenticated,
patch(
"energyflip.EnergyFlip.current_measurements",
return_value=MOCK_CURRENT_MEASUREMENTS,
) as mock_current_measurements,
patch(
"energyflip.EnergyFlip.customer_overview",
return_value=None,
) as mock_customer_overview,
):
config_entry = MockConfigEntry(
version=1,
@@ -124,13 +176,13 @@ async def test_unload_entry(hass: HomeAssistant) -> None:
await hass.async_block_till_done()
assert config_entry.state is ConfigEntryState.LOADED
entities = hass.states.async_entity_ids("sensor")
assert len(entities) == 18
assert len(entities) == 14
# Unload config entry
await hass.config_entries.async_unload(config_entry.entry_id)
assert config_entry.state is ConfigEntryState.NOT_LOADED
entities = hass.states.async_entity_ids("sensor")
assert len(entities) == 18
assert len(entities) == 14
for entity in entities:
assert hass.states.get(entity).state == STATE_UNAVAILABLE
@@ -142,5 +194,5 @@ async def test_unload_entry(hass: HomeAssistant) -> None:
# Assert mocks are called
assert len(mock_authenticate.mock_calls) == 1
assert len(mock_is_authenticated.mock_calls) == 1
assert len(mock_current_measurements.mock_calls) == 1
assert len(mock_customer_overview.mock_calls) == 1
+9 -71
View File
@@ -32,13 +32,14 @@ async def test_setup_entry(hass: HomeAssistant) -> None:
patch(
"energyflip.EnergyFlip.authenticate", return_value=None
) as mock_authenticate,
patch(
"energyflip.EnergyFlip.is_authenticated", return_value=True
) as mock_is_authenticated,
patch(
"energyflip.EnergyFlip.current_measurements",
return_value=MOCK_CURRENT_MEASUREMENTS,
) as mock_current_measurements,
patch(
"energyflip.EnergyFlip.customer_overview",
return_value=None,
) as mock_customer_overview,
):
config_entry = MockConfigEntry(
version=1,
@@ -70,66 +71,6 @@ async def test_setup_entry(hass: HomeAssistant) -> None:
current_power.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == UnitOfPower.WATT
)
current_power_in = hass.states.get("sensor.current_power_in_peak")
assert current_power_in.state == "1011.66666666667"
assert (
current_power_in.attributes.get(ATTR_DEVICE_CLASS)
== SensorDeviceClass.POWER
)
assert (
current_power_in.attributes.get(ATTR_STATE_CLASS)
is SensorStateClass.MEASUREMENT
)
assert (
current_power_in.attributes.get(ATTR_UNIT_OF_MEASUREMENT)
== UnitOfPower.WATT
)
current_power_in_low = hass.states.get("sensor.current_power_in_off_peak")
assert current_power_in_low.state == "unknown"
assert (
current_power_in_low.attributes.get(ATTR_DEVICE_CLASS)
== SensorDeviceClass.POWER
)
assert (
current_power_in_low.attributes.get(ATTR_STATE_CLASS)
is SensorStateClass.MEASUREMENT
)
assert (
current_power_in_low.attributes.get(ATTR_UNIT_OF_MEASUREMENT)
== UnitOfPower.WATT
)
current_power_out = hass.states.get("sensor.current_power_out_peak")
assert current_power_out.state == "unknown"
assert (
current_power_out.attributes.get(ATTR_DEVICE_CLASS)
== SensorDeviceClass.POWER
)
assert (
current_power_out.attributes.get(ATTR_STATE_CLASS)
is SensorStateClass.MEASUREMENT
)
assert (
current_power_out.attributes.get(ATTR_UNIT_OF_MEASUREMENT)
== UnitOfPower.WATT
)
current_power_out_low = hass.states.get("sensor.current_power_out_off_peak")
assert current_power_out_low.state == "unknown"
assert (
current_power_out_low.attributes.get(ATTR_DEVICE_CLASS)
== SensorDeviceClass.POWER
)
assert (
current_power_out_low.attributes.get(ATTR_STATE_CLASS)
is SensorStateClass.MEASUREMENT
)
assert (
current_power_out_low.attributes.get(ATTR_UNIT_OF_MEASUREMENT)
== UnitOfPower.WATT
)
energy_consumption_peak_today = hass.states.get(
"sensor.energy_consumption_peak_today"
)
@@ -312,8 +253,8 @@ async def test_setup_entry(hass: HomeAssistant) -> None:
# Assert mocks are called
assert len(mock_authenticate.mock_calls) == 1
assert len(mock_is_authenticated.mock_calls) == 1
assert len(mock_current_measurements.mock_calls) == 1
assert len(mock_customer_overview.mock_calls) == 1
async def test_setup_entry_absent_measurement(hass: HomeAssistant) -> None:
@@ -323,8 +264,9 @@ async def test_setup_entry_absent_measurement(hass: HomeAssistant) -> None:
"energyflip.EnergyFlip.authenticate", return_value=None
) as mock_authenticate,
patch(
"energyflip.EnergyFlip.is_authenticated", return_value=True
) as mock_is_authenticated,
"energyflip.EnergyFlip.customer_overview",
return_value=None,
) as mock_customer_overview,
patch(
"energyflip.EnergyFlip.current_measurements",
return_value=MOCK_LIMITED_CURRENT_MEASUREMENTS,
@@ -348,10 +290,6 @@ async def test_setup_entry_absent_measurement(hass: HomeAssistant) -> None:
# Assert data is loaded
assert hass.states.get("sensor.current_power").state == "1011.66666666667"
assert hass.states.get("sensor.current_power_in_peak").state == "unknown"
assert hass.states.get("sensor.current_power_in_off_peak").state == "unknown"
assert hass.states.get("sensor.current_power_out_peak").state == "unknown"
assert hass.states.get("sensor.current_power_out_off_peak").state == "unknown"
assert hass.states.get("sensor.current_gas").state == "unknown"
assert hass.states.get("sensor.energy_today").state == "3.296665869"
assert (
@@ -370,5 +308,5 @@ async def test_setup_entry_absent_measurement(hass: HomeAssistant) -> None:
# Assert mocks are called
assert len(mock_authenticate.mock_calls) == 1
assert len(mock_is_authenticated.mock_calls) == 1
assert len(mock_current_measurements.mock_calls) == 1
assert len(mock_customer_overview.mock_calls) == 1