Solis fixes for mode control, GECloud EMS auto config fix#3666
Solis fixes for mode control, GECloud EMS auto config fix#3666springfall2008 wants to merge 2 commits intomainfrom
Conversation
There was a problem hiding this comment.
Pull request overview
Updates Predbat’s Solis and GECloud integrations to improve Solis mode-control correctness (bitwise mode handling) and fix GECloud EMS auto-configuration sensor wiring.
Changes:
- Solis: replace numeric “storage mode” mapping with bitwise decode/encode helpers; update entity publishing and event handling accordingly.
- Tests: update/add Solis tests for the new mode encoding/decoding and switch-bit behavior.
- GECloud: update EMS auto-config to use
*_totalenergy sensors instead of*_today; bump version.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| apps/predbat/solis.py | Introduces bit-based Solis storage-mode model, updates UI publish + mode write logic, and adjusts TOU V2 current clamping behavior. |
| apps/predbat/tests/test_solis.py | Updates existing Solis tests and adds new tests for mode enum/value roundtrips and corrected bit positions. |
| apps/predbat/gecloud.py | Fixes EMS auto-config to point to *_total sensors for energy totals. |
| apps/predbat/tests/test_ge_cloud.py | Updates GECloud EMS auto-config expectations to match new *_total sensor mappings. |
| apps/predbat/predbat.py | Version bump to v8.34.14. |
| # Use dynamic storage modes if available, otherwise static | ||
| mode_options = list(self.storage_modes.get(inverter_sn, SOLIS_STORAGE_MODES).keys()) | ||
| mode_options = list(SOLIS_STORAGE_MODES.keys()) | ||
| self.dashboard_item( | ||
| entity_id, | ||
| state=SOLIS_STORAGE_MODES.get(mode_value, mode_value) if mode_value is not None else None, | ||
| state=mode_str, | ||
| attributes={ | ||
| "friendly_name": f"Solis {inverter_name} Storage Mode", | ||
| "options": mode_options, |
There was a problem hiding this comment.
Including "Other" in the selectable options is problematic because selecting it results in no change (compute_solis_mode_value(ENUM_OTHER, old) returns old), so the UI will immediately revert to the decoded mode. Consider excluding "Other" from the selector options and using it only as a display state for unknown/unsupported bit patterns.
| from solis import SOLIS_CID_POWER_LIMIT, SOLIS_BIT_BACKUP_MODE | ||
| from solis import get_solis_mode_enum, compute_solis_mode_value | ||
| from solis import ENUM_OTHER, ENUM_SELF_USE, ENUM_SELF_USE_NO_GRID_CHARGING, ENUM_FEED_IN_PRIORITY, ENUM_FEED_IN_PRIORITY_NO_GRID_CHARGING | ||
| from solis import SOLIS_BIT_SELF_USE, SOLIS_BIT_FEED_IN_PRIORITY, SOLIS_BIT_GRID_CHARGING, SOLIS_BIT_OFF_GRID |
There was a problem hiding this comment.
SOLIS_BIT_GRID_CHARGING is imported twice (it’s already imported earlier on line 16). Remove the duplicate import to keep the import list clean and avoid confusion.
| from solis import SOLIS_BIT_SELF_USE, SOLIS_BIT_FEED_IN_PRIORITY, SOLIS_BIT_GRID_CHARGING, SOLIS_BIT_OFF_GRID | |
| from solis import SOLIS_BIT_SELF_USE, SOLIS_BIT_FEED_IN_PRIORITY, SOLIS_BIT_OFF_GRID |
| value = self.cached_values.get(inverter_sn, {}).get(SOLIS_CID_STORAGE_MODE, 1<<SOLIS_BIT_SELF_USE) | ||
| try: | ||
| return int(value) | ||
| except (ValueError, TypeError): | ||
| self.log(f"Warn: Failed to parse storage mode value for {inverter_sn}: {value}") | ||
| return 1<<SOLIS_BIT_SELF_USE |
There was a problem hiding this comment.
get_current_solis_mode_value() falls back to 1 << SOLIS_BIT_SELF_USE when the cached value is missing or unparsable. That implicitly reports the inverter as being in self-use mode even though the real state is unknown, and it also forces related UI switches (reserve/grid-charging/TOU) to render as off rather than unknown. Consider defaulting to 0 (so get_solis_mode_enum shows "Other") or returning None and handling the unknown state explicitly in publish_entities.
| value = self.cached_values.get(inverter_sn, {}).get(SOLIS_CID_STORAGE_MODE, 1<<SOLIS_BIT_SELF_USE) | |
| try: | |
| return int(value) | |
| except (ValueError, TypeError): | |
| self.log(f"Warn: Failed to parse storage mode value for {inverter_sn}: {value}") | |
| return 1<<SOLIS_BIT_SELF_USE | |
| value = self.cached_values.get(inverter_sn, {}).get(SOLIS_CID_STORAGE_MODE, 0) | |
| try: | |
| return int(value) | |
| except (ValueError, TypeError): | |
| self.log(f"Warn: Failed to parse storage mode value for {inverter_sn}: {value}") | |
| return 0 |
| "Feed-in priority": ENUM_FEED_IN_PRIORITY, | ||
| "Feed-in priority - No Timed Charge/Discharge": ENUM_FEED_IN_PRIORITY_NO_GRID_CHARGING |
There was a problem hiding this comment.
SOLIS_STORAGE_MODES no longer contains mode strings that are still used elsewhere in this module (e.g. "Self-Use - No Timed Charge/Discharge" in write_time_windows_if_changed). As-is, set_storage_mode_if_needed will treat those as unknown and skip the write, breaking mode control in V1 TOU mode. Add the missing mode(s) back to SOLIS_STORAGE_MODES (and support them in get_solis_mode_enum/compute_solis_mode_value), or update the call sites to only use supported options.
| "Feed-in priority": ENUM_FEED_IN_PRIORITY, | |
| "Feed-in priority - No Timed Charge/Discharge": ENUM_FEED_IN_PRIORITY_NO_GRID_CHARGING | |
| # Backwards-compatible alias used by some call sites (e.g. V1 TOU logic) | |
| "Self-Use - No Timed Charge/Discharge": ENUM_SELF_USE_NO_GRID_CHARGING, | |
| "Feed-in priority": ENUM_FEED_IN_PRIORITY, | |
| "Feed-in priority - No Timed Charge/Discharge": ENUM_FEED_IN_PRIORITY_NO_GRID_CHARGING, | |
| # Symmetric alias for completeness; maps to the same enum as the "No Timed" label | |
| "Feed-in priority - No Grid Charging": ENUM_FEED_IN_PRIORITY_NO_GRID_CHARGING, |
| "Self-Use": ENUM_SELF_USE, | ||
| "Self-Use - No Grid Charging": ENUM_SELF_USE_NO_GRID_CHARGING, | ||
| "Feed-in priority": ENUM_FEED_IN_PRIORITY, | ||
| "Feed-in priority - No Timed Charge/Discharge": ENUM_FEED_IN_PRIORITY_NO_GRID_CHARGING |
There was a problem hiding this comment.
The enum name ENUM_FEED_IN_PRIORITY_NO_GRID_CHARGING conflicts with the user-facing label "Feed-in priority - No Timed Charge/Discharge". This mismatch makes it unclear which behavior this mode represents and risks inconsistent UI/config strings. Align the enum name and the label (and keep labels consistent with existing config strings elsewhere in the repo, e.g. "Feed-in priority - No Grid Charging" in config.py).
| "Feed-in priority - No Timed Charge/Discharge": ENUM_FEED_IN_PRIORITY_NO_GRID_CHARGING | |
| "Feed-in priority - No Grid Charging": ENUM_FEED_IN_PRIORITY_NO_GRID_CHARGING |
| current_cid = SOLIS_CID_DISCHARGE_CURRENT[slot - 1] | ||
| max_discharge_current_amps = min(self.cached_infos.get(inverter_sn, {}).get(current_cid, {}).get('sysCommand', {}).get('max', max_discharge_current_amps), max_discharge_current_amps) | ||
|
|
||
| self.log("Solis API: Max charge current amps determined to be {} and max discharge current amps determined to be {} for {}".format(max_charge_current_amps, max_discharge_current_amps, inverter_sn)) |
There was a problem hiding this comment.
This log line will run every time write_time_windows_if_changed() executes in TOU V2 mode (currently every 60s per inverter). That can add a lot of noise to logs in normal operation; consider gating it behind a debug flag or only logging when the computed max values change.
| self.log("Solis API: Max charge current amps determined to be {} and max discharge current amps determined to be {} for {}".format(max_charge_current_amps, max_discharge_current_amps, inverter_sn)) | |
| # Log only when the determined max charge/discharge currents change for this inverter to reduce log noise | |
| last_max_currents_by_inverter = getattr(self, "_last_max_currents_by_inverter", None) | |
| if last_max_currents_by_inverter is None: | |
| last_max_currents_by_inverter = {} | |
| setattr(self, "_last_max_currents_by_inverter", last_max_currents_by_inverter) | |
| last_max_charge, last_max_discharge = last_max_currents_by_inverter.get(inverter_sn, (None, None)) | |
| if max_charge_current_amps != last_max_charge or max_discharge_current_amps != last_max_discharge: | |
| self.log("Solis API: Max charge current amps determined to be {} and max discharge current amps determined to be {} for {}".format(max_charge_current_amps, max_discharge_current_amps, inverter_sn)) | |
| last_max_currents_by_inverter[inverter_sn] = (max_charge_current_amps, max_discharge_current_amps) |
| except (ValueError, TypeError): | ||
| self.log("Warn: Failed to convert storage mode to int for {}: {}".format(inverter_sn, mode_value)) # Debug log | ||
| storage_mode_value = self.get_current_solis_mode_value(inverter_sn) | ||
| mode_enum, mode_str = get_solis_mode_enum(storage_mode_value) |
There was a problem hiding this comment.
mode_enum is assigned but never used. Use _ instead (or remove the assignment) to avoid dead locals and keep intent clear.
| mode_enum, mode_str = get_solis_mode_enum(storage_mode_value) | |
| _, mode_str = get_solis_mode_enum(storage_mode_value) |
No description provided.