Skip to content

Commit ec6cff9

Browse files
Merge pull request #3787 from CraigCallender/fix/prevent-overlapping-charge-discharge-windows
SolisCloud: Clear time windows for charge/discharge to prevent overlapping windows
2 parents ea306d3 + df3526f commit ec6cff9

3 files changed

Lines changed: 218 additions & 70 deletions

File tree

‎.cspell/custom-dictionary-workspace.txt‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,7 @@ hypervolt
160160
iboost
161161
idag
162162
idetails
163+
idxs
163164
iflux
164165
inday
165166
INTELLI

‎apps/predbat/solis.py‎

Lines changed: 101 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -624,6 +624,8 @@ async def write_time_windows_if_changed(self, inverter_sn):
624624
current_cid = SOLIS_CID_DISCHARGE_CURRENT[slot - 1]
625625
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)
626626

627+
# Prep: extract active currents from slot 1 and zero out times for disabled slots
628+
# so that the two-pass write below has clean data to compare against.
627629
for slot in slots_to_check:
628630
slot_data = time_windows.get(slot)
629631
if not slot_data:
@@ -634,80 +636,123 @@ async def write_time_windows_if_changed(self, inverter_sn):
634636
charge_current = slot_data.get("charge_current", charge_current)
635637
discharge_current = slot_data.get("discharge_current", discharge_current)
636638

637-
# Check and write charge enable if changed
638-
if "charge_enable" in slot_data:
639+
# When a slot is disabled, zero out its times so the inverter shows a clean 00:00-00:00
640+
if not slot_data.get("charge_enable", 0):
641+
slot_data["charge_start_time"] = "00:00"
642+
slot_data["charge_end_time"] = "00:00"
643+
if not slot_data.get("discharge_enable", 0):
644+
slot_data["discharge_start_time"] = "00:00"
645+
slot_data["discharge_end_time"] = "00:00"
646+
647+
# Pass 1: Clear all disabled slots first.
648+
# This prevents stale times on the inverter from blocking active-slot writes due to overlap conflicts.
649+
for slot in slots_to_check:
650+
slot_data = time_windows.get(slot)
651+
if not slot_data:
652+
continue
653+
654+
if not slot_data.get("charge_enable", 0):
639655
enable_cid = SOLIS_CID_CHARGE_ENABLE_BASE + (slot - 1)
640-
new_enable_str = str(int(slot_data['charge_enable']))
641656
cached_enable = self.cached_values.get(inverter_sn, {}).get(enable_cid)
642-
if cached_enable != new_enable_str:
643-
result = await self.read_and_write_cid(inverter_sn, enable_cid, new_enable_str, field_description=f"charge slot {slot} enable")
657+
if cached_enable != "0":
658+
result = await self.read_and_write_cid(inverter_sn, enable_cid, "0", field_description=f"charge slot {slot} enable")
644659
success &= result
645-
646-
# Check and write charge time if changed
647-
if "charge_start_time" in slot_data and "charge_end_time" in slot_data:
648660
time_cid = SOLIS_CID_CHARGE_TIME[slot - 1]
649-
new_time_str = f"{slot_data['charge_start_time']}-{slot_data['charge_end_time']}"
650661
cached_time = self.cached_values.get(inverter_sn, {}).get(time_cid)
651-
if cached_time != new_time_str:
652-
result = await self.read_and_write_cid(inverter_sn, time_cid, new_time_str, field_description=f"charge slot {slot} time")
653-
success &= result
654-
655-
# Check and write charge SOC if changed
656-
if "charge_soc" in slot_data:
657-
soc_cid = SOLIS_CID_CHARGE_SOC_BASE + (slot - 1)
658-
new_soc_str = str(int(slot_data['charge_soc']))
659-
cached_soc = self.cached_values.get(inverter_sn, {}).get(soc_cid)
660-
if cached_soc != new_soc_str:
661-
result = await self.read_and_write_cid(inverter_sn, soc_cid, new_soc_str, field_description=f"charge slot {slot} SOC")
662+
if cached_time != "00:00-00:00":
663+
result = await self.read_and_write_cid(inverter_sn, time_cid, "00:00-00:00", field_description=f"charge slot {slot} time")
662664
success &= result
663665

664-
# Check and write charge current if changed
665-
if "charge_current" in slot_data:
666-
current_cid = SOLIS_CID_CHARGE_CURRENT[slot - 1]
667-
new_current = float(slot_data['charge_current'])
668-
new_current = min(new_current, max_charge_current_amps)
669-
cached_current = float(self.cached_values.get(inverter_sn, {}).get(current_cid, max_charge_current_amps))
670-
if round(cached_current, 1) != round(new_current, 1):
671-
result = await self.read_and_write_cid(inverter_sn, current_cid, new_current, field_description=f"charge slot {slot} current")
672-
success &= result
673-
674-
# Check and write discharge enable if changed
675-
if "discharge_enable" in slot_data:
666+
if not slot_data.get("discharge_enable", 0):
676667
enable_cid = SOLIS_CID_DISCHARGE_ENABLE_BASE + (slot - 1)
677-
new_enable_str = str(int(slot_data['discharge_enable']))
678668
cached_enable = self.cached_values.get(inverter_sn, {}).get(enable_cid)
679-
if cached_enable != new_enable_str:
680-
result = await self.read_and_write_cid(inverter_sn, enable_cid, new_enable_str, field_description=f"discharge slot {slot} enable")
669+
if cached_enable != "0":
670+
result = await self.read_and_write_cid(inverter_sn, enable_cid, "0", field_description=f"discharge slot {slot} enable")
681671
success &= result
682-
683-
# Check and write discharge time if changed
684-
if "discharge_start_time" in slot_data and "discharge_end_time" in slot_data:
685672
time_cid = SOLIS_CID_DISCHARGE_TIME[slot - 1]
686-
new_time_str = f"{slot_data['discharge_start_time']}-{slot_data['discharge_end_time']}"
687673
cached_time = self.cached_values.get(inverter_sn, {}).get(time_cid)
688-
if cached_time != new_time_str:
689-
result = await self.read_and_write_cid(inverter_sn, time_cid, new_time_str, field_description=f"discharge slot {slot} time")
674+
if cached_time != "00:00-00:00":
675+
result = await self.read_and_write_cid(inverter_sn, time_cid, "00:00-00:00", field_description=f"discharge slot {slot} time")
690676
success &= result
691677

692-
# Check and write discharge SOC if changed
693-
if "discharge_soc" in slot_data:
694-
soc_cid = SOLIS_CID_DISCHARGE_SOC[slot - 1]
695-
new_soc_str = str(int(slot_data['discharge_soc']))
696-
cached_soc = self.cached_values.get(inverter_sn, {}).get(soc_cid)
697-
if cached_soc != new_soc_str:
698-
result = await self.read_and_write_cid(inverter_sn, soc_cid, new_soc_str, field_description=f"discharge slot {slot} SOC")
678+
# Pass 2: Write active slot settings (enable, time, SOC, current).
679+
# By this point all disabled slots should have been cleared on the inverter,
680+
# so there is no risk of a time-overlap rejection from the API.
681+
for slot in slots_to_check:
682+
slot_data = time_windows.get(slot)
683+
if not slot_data:
684+
continue
685+
686+
if slot_data.get("charge_enable", 0):
687+
# Check and write charge enable if changed
688+
enable_cid = SOLIS_CID_CHARGE_ENABLE_BASE + (slot - 1)
689+
cached_enable = self.cached_values.get(inverter_sn, {}).get(enable_cid)
690+
if cached_enable != "1":
691+
result = await self.read_and_write_cid(inverter_sn, enable_cid, "1", field_description=f"charge slot {slot} enable")
699692
success &= result
700693

701-
# Check and write discharge current if changed
702-
if "discharge_current" in slot_data:
703-
current_cid = SOLIS_CID_DISCHARGE_CURRENT[slot - 1]
704-
new_current = float(slot_data['discharge_current'])
705-
new_current = min(new_current, max_discharge_current_amps)
706-
cached_current = float(self.cached_values.get(inverter_sn, {}).get(current_cid, max_discharge_current_amps))
707-
if round(cached_current, 1) != round(new_current, 1):
708-
result = await self.read_and_write_cid(inverter_sn, current_cid, new_current, field_description=f"discharge slot {slot} current")
694+
# Check and write charge time if changed
695+
if "charge_start_time" in slot_data and "charge_end_time" in slot_data:
696+
time_cid = SOLIS_CID_CHARGE_TIME[slot - 1]
697+
new_time_str = f"{slot_data['charge_start_time']}-{slot_data['charge_end_time']}"
698+
cached_time = self.cached_values.get(inverter_sn, {}).get(time_cid)
699+
if cached_time != new_time_str:
700+
result = await self.read_and_write_cid(inverter_sn, time_cid, new_time_str, field_description=f"charge slot {slot} time")
701+
success &= result
702+
703+
# Check and write charge SOC if changed
704+
if "charge_soc" in slot_data:
705+
soc_cid = SOLIS_CID_CHARGE_SOC_BASE + (slot - 1)
706+
new_soc_str = str(int(slot_data['charge_soc']))
707+
cached_soc = self.cached_values.get(inverter_sn, {}).get(soc_cid)
708+
if cached_soc != new_soc_str:
709+
result = await self.read_and_write_cid(inverter_sn, soc_cid, new_soc_str, field_description=f"charge slot {slot} SOC")
710+
success &= result
711+
712+
# Check and write charge current if changed
713+
if "charge_current" in slot_data:
714+
current_cid = SOLIS_CID_CHARGE_CURRENT[slot - 1]
715+
new_current = min(float(slot_data['charge_current']), max_charge_current_amps)
716+
cached_current = float(self.cached_values.get(inverter_sn, {}).get(current_cid, max_charge_current_amps))
717+
if round(cached_current, 1) != round(new_current, 1):
718+
result = await self.read_and_write_cid(inverter_sn, current_cid, new_current, field_description=f"charge slot {slot} current")
719+
success &= result
720+
721+
if slot_data.get("discharge_enable", 0):
722+
# Check and write discharge enable if changed
723+
enable_cid = SOLIS_CID_DISCHARGE_ENABLE_BASE + (slot - 1)
724+
cached_enable = self.cached_values.get(inverter_sn, {}).get(enable_cid)
725+
if cached_enable != "1":
726+
result = await self.read_and_write_cid(inverter_sn, enable_cid, "1", field_description=f"discharge slot {slot} enable")
709727
success &= result
710728

729+
# Check and write discharge time if changed
730+
if "discharge_start_time" in slot_data and "discharge_end_time" in slot_data:
731+
time_cid = SOLIS_CID_DISCHARGE_TIME[slot - 1]
732+
new_time_str = f"{slot_data['discharge_start_time']}-{slot_data['discharge_end_time']}"
733+
cached_time = self.cached_values.get(inverter_sn, {}).get(time_cid)
734+
if cached_time != new_time_str:
735+
result = await self.read_and_write_cid(inverter_sn, time_cid, new_time_str, field_description=f"discharge slot {slot} time")
736+
success &= result
737+
738+
# Check and write discharge SOC if changed
739+
if "discharge_soc" in slot_data:
740+
soc_cid = SOLIS_CID_DISCHARGE_SOC[slot - 1]
741+
new_soc_str = str(int(slot_data['discharge_soc']))
742+
cached_soc = self.cached_values.get(inverter_sn, {}).get(soc_cid)
743+
if cached_soc != new_soc_str:
744+
result = await self.read_and_write_cid(inverter_sn, soc_cid, new_soc_str, field_description=f"discharge slot {slot} SOC")
745+
success &= result
746+
747+
# Check and write discharge current if changed
748+
if "discharge_current" in slot_data:
749+
current_cid = SOLIS_CID_DISCHARGE_CURRENT[slot - 1]
750+
new_current = min(float(slot_data['discharge_current']), max_discharge_current_amps)
751+
cached_current = float(self.cached_values.get(inverter_sn, {}).get(current_cid, max_discharge_current_amps))
752+
if round(cached_current, 1) != round(new_current, 1):
753+
result = await self.read_and_write_cid(inverter_sn, current_cid, new_current, field_description=f"discharge slot {slot} current")
754+
success &= result
755+
711756
# Decide if Solar charges the batter or exports
712757
if charge_current == 0:
713758
self.log(f"Solis API: Charge current is 0A for {inverter_sn}, setting storage mode to 'Feed-in priority'")

0 commit comments

Comments
 (0)