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:
@@ -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."""
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user