1
0
mirror of https://github.com/home-assistant/core.git synced 2026-07-01 11:46:40 +01:00

Add optimistic updates for Indevolt (#173091)

This commit is contained in:
A. Gideonse
2026-06-13 20:20:37 +02:00
committed by GitHub
parent 483f7072dd
commit d3d883358c
9 changed files with 68 additions and 24 deletions
@@ -11,6 +11,7 @@ from indevolt_api import (
IndevoltConfig,
IndevoltEnergyMode,
IndevoltRealtimeAction,
IndevoltRealtimeState,
)
from homeassistant.config_entries import ConfigEntry
@@ -109,6 +110,10 @@ class IndevoltCoordinator(DataUpdateCoordinator[dict[str, Any]]):
"""Push/write data values to given key on the device."""
return await self.api.set_data(sensor_key, value)
def async_optimistic_update(self, read_key: str, value: Any) -> None:
"""Optimistically update coordinator data without fetching from device."""
self.async_set_updated_data({**self.data, read_key: value})
async def async_switch_energy_mode(
self, target_mode: IndevoltEnergyMode, refresh: bool = True
) -> None:
@@ -142,7 +147,9 @@ class IndevoltCoordinator(DataUpdateCoordinator[dict[str, Any]]):
)
if refresh:
await self.async_request_refresh()
self.async_optimistic_update(
IndevoltConfig.READ_ENERGY_MODE, target_mode
)
async def async_realtime_action(
self,
@@ -161,10 +168,15 @@ class IndevoltCoordinator(DataUpdateCoordinator[dict[str, Any]]):
match action:
case IndevoltRealtimeAction.CHARGE:
success = await self.api.charge(power, target_soc)
state = IndevoltRealtimeState.CHARGING
case IndevoltRealtimeAction.DISCHARGE:
success = await self.api.discharge(power, target_soc)
state = IndevoltRealtimeState.DISCHARGING
case IndevoltRealtimeAction.STOP:
success = await self.api.stop()
state = IndevoltRealtimeState.STANDBY
if not success:
raise HomeAssistantError(
@@ -172,7 +184,15 @@ class IndevoltCoordinator(DataUpdateCoordinator[dict[str, Any]]):
translation_key="failed_to_execute_realtime_action",
)
await self.async_request_refresh()
self.async_set_updated_data(
{
**self.data,
IndevoltConfig.READ_ENERGY_MODE: IndevoltEnergyMode.REAL_TIME_CONTROL,
IndevoltConfig.READ_REALTIME_STATE: state,
IndevoltConfig.READ_REALTIME_TARGET_SOC: target_soc,
IndevoltConfig.READ_REALTIME_POWER_LIMIT: power,
}
)
def get_emergency_soc(self) -> int:
"""Get the emergency SOC value."""
+3 -1
View File
@@ -136,7 +136,9 @@ class IndevoltNumberEntity(IndevoltEntity, NumberEntity):
)
if success:
await self.coordinator.async_request_refresh()
self.coordinator.async_optimistic_update(
self.entity_description.read_key, int_value
)
else:
raise HomeAssistantError(
+3 -1
View File
@@ -106,7 +106,9 @@ class IndevoltSelectEntity(IndevoltEntity, SelectEntity):
)
if success:
await self.coordinator.async_request_refresh()
self.coordinator.async_optimistic_update(
self.entity_description.read_key, value
)
else:
raise HomeAssistantError(
+1 -1
View File
@@ -86,7 +86,7 @@ SENSORS: Final = (
),
# Real-time control state
IndevoltSensorEntityDescription(
key=IndevoltConfig.READ_REALTIME_COMMAND,
key=IndevoltConfig.READ_REALTIME_STATE,
translation_key="realtime_command",
state_mapping={1000: "standby", 1001: "charging", 1002: "discharging"},
device_class=SensorDeviceClass.ENUM,
+8 -1
View File
@@ -126,7 +126,14 @@ class IndevoltSwitchEntity(IndevoltEntity, SwitchEntity):
)
if success:
await self.coordinator.async_request_refresh()
read_value = (
self.entity_description.read_on_value
if value
else self.entity_description.read_off_value
)
self.coordinator.async_optimistic_update(
self.entity_description.read_key, read_value
)
else:
raise HomeAssistantError(
+3 -4
View File
@@ -82,10 +82,8 @@ async def test_number_set_values(
# Reset mock call count for this iteration
mock_indevolt.set_data.reset_mock()
# Update mock data to reflect the new value
mock_indevolt.fetch_data.return_value[read_key] = test_value
# Call the service to set the value
fetch_count_before = mock_indevolt.fetch_data.call_count
await hass.services.async_call(
NUMBER_DOMAIN,
SERVICE_SET_VALUE,
@@ -96,7 +94,8 @@ async def test_number_set_values(
# Verify set_data was called with correct parameters
mock_indevolt.set_data.assert_called_with(write_key, test_value)
# Verify updated state
# Verify state updated optimistically without a new fetch
assert mock_indevolt.fetch_data.call_count == fetch_count_before
assert (state := hass.states.get(entity_id)) is not None
assert int(float(state.state)) == test_value
+3 -6
View File
@@ -62,12 +62,8 @@ async def test_select_option(
# Reset mock call count for this iteration
mock_indevolt.set_data.reset_mock()
# Update mock data to reflect the new value
mock_indevolt.fetch_data.return_value[IndevoltConfig.READ_ENERGY_MODE] = (
expected_value
)
# Attempt to change option
fetch_count_before = mock_indevolt.fetch_data.call_count
await hass.services.async_call(
SELECT_DOMAIN,
SERVICE_SELECT_OPTION,
@@ -80,7 +76,8 @@ async def test_select_option(
IndevoltConfig.WRITE_ENERGY_MODE, expected_value
)
# Verify updated state
# Verify state updated optimistically without a new fetch
assert mock_indevolt.fetch_data.call_count == fetch_count_before
assert (state := hass.states.get("select.cms_sf2000_energy_mode")) is not None
assert state.state == option
@@ -73,6 +73,25 @@ async def test_service_charge_discharge(
else:
mock_indevolt.discharge.assert_called_once_with(power, target_soc)
# Verify sensor states were updated optimistically
expected_rt_command = "charging" if service_name == "charge" else "discharging"
assert (state := hass.states.get("sensor.cms_sf2000_energy_mode")) is not None
assert state.state == "real_time_control"
assert (state := hass.states.get("sensor.cms_sf2000_real_time_mode")) is not None
assert state.state == expected_rt_command
assert (
state := hass.states.get("sensor.cms_sf2000_real_time_target_soc")
) is not None
assert int(float(state.state)) == target_soc
assert (
state := hass.states.get("sensor.cms_sf2000_real_time_power_limit")
) is not None
assert int(float(state.state)) == power
@pytest.mark.parametrize("generation", [1], indirect=True)
@pytest.mark.parametrize(
+6 -8
View File
@@ -83,10 +83,8 @@ async def test_switch_turn_on(
# Reset mock call count for this iteration
mock_indevolt.set_data.reset_mock()
# Update mock data to reflect the new value
mock_indevolt.fetch_data.return_value[read_key] = on_value
# Call the service to turn on
fetch_count_before = mock_indevolt.fetch_data.call_count
await hass.services.async_call(
SWITCH_DOMAIN,
SERVICE_TURN_ON,
@@ -97,7 +95,8 @@ async def test_switch_turn_on(
# Verify set_data was called with correct parameters
mock_indevolt.set_data.assert_called_with(write_key, 1)
# Verify updated state
# Verify state updated optimistically without a new fetch
assert mock_indevolt.fetch_data.call_count == fetch_count_before
assert (state := hass.states.get(entity_id)) is not None
assert state.state == STATE_ON
@@ -142,10 +141,8 @@ async def test_switch_turn_off(
# Reset mock call count for this iteration
mock_indevolt.set_data.reset_mock()
# Update mock data to reflect the new value
mock_indevolt.fetch_data.return_value[read_key] = off_value
# Call the service to turn off
fetch_count_before = mock_indevolt.fetch_data.call_count
await hass.services.async_call(
SWITCH_DOMAIN,
SERVICE_TURN_OFF,
@@ -156,7 +153,8 @@ async def test_switch_turn_off(
# Verify set_data was called with correct parameters
mock_indevolt.set_data.assert_called_with(write_key, 0)
# Verify updated state
# Verify state updated optimistically without a new fetch
assert mock_indevolt.fetch_data.call_count == fetch_count_before
assert (state := hass.states.get(entity_id)) is not None
assert state.state == STATE_OFF