mirror of
https://github.com/Akkudoktor-EOS/EOS.git
synced 2026-10-29 17:21:57 +00:00
fix(adapter): stamp Home Assistant device states with run time, not slot start (#1452)
* fix(adapter): stamp Home Assistant device states with run time, not slot start The Home Assistant adapter stored every reading with ems_start_datetime, which set_start_datetime() rounds down to the optimization slot (e.g. 14:00 for a run at 14:11). The GENETIC configuration request checks SoC freshness against the unrounded observation_datetime with measurement_max_age_seconds (default 300 s). Any run starting more than five minutes into a slot therefore discarded the freshly read SoC and optimized with 0 % initial SoC. Stamp device state measurements (device_measurement_entity_ids, e.g. SoC) with observation_datetime instead. Not to_datetime(): a reading taken a few seconds after the observation time would be rejected as a future sample. Meter readings (load, grid import/export, PV) keep the slot start, because their energy is aggregated per slot. With device states at their real time, a record holding only some keys can now be the newest one (e.g. the Home Assistant battery SoC while the EV SoC or a cycle count comes through the measurement API). Readers therefore skip records without the value they need: - GENETIC SoC and completed appliance cycles: such a record no longer hides a fresh value (the cycle count case aborted the optimization). This also covers a meter reading at the slot start that is newer than a SoC supplied through the measurement API. Values stored as pandas NA count as missing instead of raising TypeError; an explicit NaN stays an invalid sample. - GENETIC0 SoC: looks up the reading nearest to observation_datetime instead of the slot start, skipping records without SoC and converting only the selected sample. Before, a run late in the slot (14:40 in the 14:00 slot) would pick the previous run's reading (13:40). This also fixes SoC values PUT via the REST API with their real time. LoadAkkudoktorAdjusted anchored its hourly comparison window on the newest record of any key. An off-hour device state would shift every bucket (e.g. to :11) against hour-aligned meter readings; the window now starts and ends on full hours, and less than one full hour of data yields no adjustment instead of an error. (15-minute slot starts and SoC values PUT with their real time had the same effect before.) Regenerated openapi.json and docs/_generated/openapi.md (version only). Fixes #1447 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EYWR1HVM8a8src23xuyL3T * test(genetic0): use pv_battery instead of deprecated pv_akku mypy flags attribute access on the pv_akku computed field (attr-defined), which failed the pre-commit check of the PR. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EYWR1HVM8a8src23xuyL3T --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
5249b389f5
commit
58b968562e
@@ -415,3 +415,54 @@ async def test_profile_zero_power_phase_keeps_device_running_until_complete(
|
||||
if isinstance(item, DDBCInstruction)
|
||||
]
|
||||
assert commands == [(0, 0, "OFF"), (1, 0, "RUN"), (1, 45, "OFF")]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_meter_only_record_does_not_hide_completed_cycles(
|
||||
config_eos: ConfigEOS, isolated_measurement: Measurement
|
||||
) -> None:
|
||||
"""A newer record without a cycle count (meter reading) must not abort the run (#1447)."""
|
||||
configure(config_eos)
|
||||
config_eos.merge_settings_from_dict(
|
||||
{
|
||||
"measurement": {"load_emr_keys": ["load_meter"]},
|
||||
"devices": {
|
||||
"max_batteries": 0,
|
||||
"batteries": {},
|
||||
"max_electric_vehicles": 0,
|
||||
"electric_vehicles": {},
|
||||
"max_inverters": 1,
|
||||
"inverters": {"inv": {"max_power_w": 10000}},
|
||||
"max_home_appliances": 1,
|
||||
"home_appliances": {
|
||||
"washer": profile(
|
||||
num_cycles=2,
|
||||
cycle_time_windows={
|
||||
"windows": [
|
||||
{"start_time": "01:00", "duration": "30 minutes", "value": 0},
|
||||
{"start_time": "02:00", "duration": "30 minutes", "value": 1},
|
||||
]
|
||||
},
|
||||
)
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
run_time = get_ems().start_datetime.add(minutes=26)
|
||||
get_ems().set_start_datetime(run_time, interval_seconds=900)
|
||||
measurement = isolated_measurement
|
||||
# Cycle count through the measurement API, then the adapter's meter reading.
|
||||
await measurement.update_value(run_time.subtract(seconds=30), "washer.cycles_completed", 1.0)
|
||||
await measurement.update_value(run_time, "load_meter", 1234.5)
|
||||
prepared = await ConfigOptimizationRequest.model_validate(
|
||||
{
|
||||
"forecasts": {
|
||||
"pv_forecast_wh": [0.0] * 20,
|
||||
"total_load": [0.0] * 20,
|
||||
"electricity_price_per_wh": [0.0003] * 20,
|
||||
"feed_in_tariff_per_wh": [0.0001] * 20,
|
||||
}
|
||||
}
|
||||
).resolve()
|
||||
assert prepared.home_appliances is not None
|
||||
assert prepared.home_appliances[0].completed_cycles == 1
|
||||
|
||||
Reference in New Issue
Block a user