Skip to content

Commit 7460ae3

Browse files
Merge pull request #706 from davidusb-geek/davidusb-geek/fix/typical_lists_warning_logs
Fix typical load when maximum_power_from_grid is a list and uneeded w…
2 parents 029d795 + 4258174 commit 7460ae3

File tree

6 files changed

+59
-18
lines changed

6 files changed

+59
-18
lines changed

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
11
# Changelog
22

3+
## 0.16.2 - 2026-01-29
4+
### Improvement
5+
- Added support for a thermal inertia parameter for the basic thermal model
6+
- Handle time-dependent battery weights (@rmounce)
7+
### Fix
8+
- Fix Docker signal handling with tini and pin uvicorn version (@rmounce)
9+
- Fix "typical" load forecast method fails with maximum_power_from_grid as vector (list)
10+
- Fix warning logging messages on sensor_replace_zero
11+
- Fix for correct handling of open-meteo temp_air variable
12+
313
## 0.16.1 - 2026-01-23
414
### Fix
515
- Hot fix for InfluxDB not passed credentials

conda/meta.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{% set name = "emhass" %}
2-
{% set version = "0.16.1" %}
2+
{% set version = "0.16.2" %}
33

44
package:
55
name: {{ name|lower }}
@@ -45,7 +45,7 @@ requirements:
4545
- aiohttp
4646
- orjson
4747
- websockets
48-
- uvicorn >=0.30.0
48+
- uvicorn==0.30.6
4949
- influxdb >=5.3.1
5050

5151
test:

docs/conf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
author = "David HERNANDEZ TORRES"
2424

2525
# The full version, including alpha/beta/rc tags
26-
release = "0.16.1"
26+
release = "0.16.2"
2727

2828
# -- General configuration ---------------------------------------------------
2929

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "emhass"
3-
version = "0.16.1"
3+
version = "0.16.2"
44
description = "An Energy Management System for Home Assistant"
55
readme = "README.md"
66
requires-python = ">=3.10, <3.13"

src/emhass/forecast.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1380,14 +1380,30 @@ async def _get_load_forecast_typical(self) -> pd.DataFrame:
13801380
data.columns = ["load"]
13811381
forecast_tmp, used_days = Forecast.get_typical_load_forecast(data, forecast_date)
13821382
self.logger.debug(f"Using {len(used_days)} days of data to generate the forecast.")
1383-
forecast_tmp = forecast_tmp * self.plant_conf["maximum_power_from_grid"] / 9000
13841383
if len(forecast) == 0:
13851384
forecast = forecast_tmp
13861385
else:
13871386
forecast = pd.concat([forecast, forecast_tmp], axis=0)
13881387
forecast_out = forecast.loc[forecast.index.intersection(self.forecast_dates)]
13891388
forecast_out.index = self.forecast_dates
13901389
forecast_out.index.name = "ts"
1390+
max_power = self.plant_conf["maximum_power_from_grid"]
1391+
# Normalize list/array inputs to a Series aligned with forecast index
1392+
if isinstance(max_power, list | tuple | np.ndarray):
1393+
# Validate length to prevent confusing errors later
1394+
if len(max_power) != len(forecast_out):
1395+
raise ValueError(
1396+
f"The length of 'maximum_power_from_grid' ({len(max_power)}) "
1397+
f"does not match the forecast horizon length ({len(forecast_out)})."
1398+
)
1399+
# Create Series for explicit temporal alignment
1400+
scaling_factor = pd.Series(max_power, index=forecast_out.index)
1401+
else:
1402+
# Assume scalar (int/float)
1403+
scaling_factor = max_power
1404+
# Apply scaling
1405+
# The '9000' divisor appears to be a normalization constant specific to the 'typical' model data
1406+
forecast_out["load"] = forecast_out["load"] * scaling_factor / 9000
13911407
return forecast_out.rename(columns={"load": "yhat"})
13921408

13931409
def _get_load_forecast_naive(self, df: pd.DataFrame) -> pd.DataFrame:

src/emhass/retrieve_hass.py

Lines changed: 28 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -846,15 +846,23 @@ def _map_variable_names(
846846
) -> list | None:
847847
"""Helper to map old variable names to new ones (if renaming occurred)."""
848848
if not target_list:
849-
self.logger.warning(f"Unable to find all the sensors in {param_name} parameter")
849+
# Dynamic Warning Message
850+
# Don't hardcode "sensor_power_photovoltaics". Use the actual parameter name.
851+
self.logger.warning(f"The list of sensors for parameter '{param_name}' is empty.")
850852
self.logger.warning(
851-
f"Confirm sure all sensors in {param_name} are sensor_power_photovoltaics and/or sensor_power_load_no_var_loads"
853+
f"Please verify that the sensors defined in '{param_name}' match "
854+
"the sensors connected to EMHASS."
852855
)
853856
return None
854857
new_list = []
855858
for string in target_list:
856859
if not skip_renaming:
857-
new_list.append(string.replace(var_load, var_load + "_positive"))
860+
# Exact Match Logic
861+
# Prevent dangerous substring replacements (e.g. 'sensor.power' inside 'sensor.power_meter')
862+
if string == var_load:
863+
new_list.append(var_load + "_positive")
864+
else:
865+
new_list.append(string)
858866
else:
859867
new_list.append(string)
860868
return new_list
@@ -896,8 +904,13 @@ def prepare_data(
896904
# Instead of calling self._validate_sensor_list (which warns),
897905
# we silently drop sensors that are not in the current fetched data.
898906
if var_replace_zero:
907+
# Optional: Log if we are dropping items to help debugging
908+
missing_sensors = [x for x in var_replace_zero if x not in self.var_list]
909+
if missing_sensors:
910+
self.logger.debug(
911+
f"Sensors in 'sensor_replace_zero' not found in data: {missing_sensors}"
912+
)
899913
var_replace_zero = [x for x in var_replace_zero if x in self.var_list]
900-
901914
if var_interp:
902915
var_interp = [x for x in var_interp if x in self.var_list]
903916
# Rename Load Columns (Handle sign change)
@@ -908,17 +921,19 @@ def prepare_data(
908921
self.df_final.clip(lower=0.0, inplace=True, axis=1)
909922
self.df_final.replace(to_replace=0.0, value=np.nan, inplace=True)
910923
# Map Variable Names (Update lists to match new column names)
911-
# Note: We still use _map_variable_names to handle the renaming logic
912-
# (e.g. adding '_positive' suffix), but the input lists are now clean.
913-
new_var_replace_zero = self._map_variable_names(
914-
var_replace_zero, var_load, skip_renaming, "sensor_replace_zero"
915-
)
916-
new_var_interp = self._map_variable_names(
917-
var_interp, var_load, skip_renaming, "sensor_linear_interp"
918-
)
924+
# Only call mapping if the list is not empty to avoid spurious warnings
925+
new_var_replace_zero = None
926+
if var_replace_zero:
927+
new_var_replace_zero = self._map_variable_names(
928+
var_replace_zero, var_load, skip_renaming, "sensor_replace_zero"
929+
)
930+
new_var_interp = None
931+
if var_interp:
932+
new_var_interp = self._map_variable_names(
933+
var_interp, var_load, skip_renaming, "sensor_linear_interp"
934+
)
919935
# Apply Data Cleaning (FillNA / Interpolate)
920936
if new_var_replace_zero:
921-
# Intersection check to ensure columns exist before assigning
922937
cols_to_fix = [c for c in new_var_replace_zero if c in self.df_final.columns]
923938
if cols_to_fix:
924939
self.df_final[cols_to_fix] = self.df_final[cols_to_fix].fillna(0.0)

0 commit comments

Comments
 (0)