Skip to content

Commit

Permalink
Added EV monitoring for IO ad hoc charge periods
Browse files Browse the repository at this point in the history
Added code to pause discharge/start charging battery in daytime Intelligent Octopus timeslots using Shelly Energy Monitor
  • Loading branch information
salewis38 authored Sep 29, 2023
1 parent 25dce1d commit ddce77a
Show file tree
Hide file tree
Showing 2 changed files with 75 additions and 30 deletions.
98 changes: 69 additions & 29 deletions palm.py
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,27 @@ def set_mihome_switch(device_id: str, turn_on: bool) -> bool:

# End of set_mihome_switch ()

def ev_active() -> bool:
"""Polls Shelly EM used for monitoring charge power"""

url:str = str(stgs.Shelly.em0_url)
if url == "":
return False

try:
resp = requests.put(url, timeout=5)
resp.raise_for_status()
except requests.exceptions.RequestException as error:
logger.error("Missing response from Shelly EM"+ error)
return False

parsed = json.loads(resp.content.decode('utf-8'))
logger.debug(str(parsed))

if parsed['is_valid'] == True and int(parsed['power']) > 500:
logger.warning("EV charging detected, power = "+ str(parsed['power']))
return True
return False

def put_pv_output():
"""Upload generation/consumption data to PVOutput.org."""
Expand Down Expand Up @@ -488,7 +509,7 @@ def balance_loads():
if MESSAGE != "":
logger.critical(MESSAGE)


EV_CHARGING_VAR: bool = False # State of EV charger
MANUAL_HOLD_VAR: bool = False # Fix for inverter hunting after hitting SoC target

while True: # Main Loop
Expand All @@ -498,6 +519,10 @@ def balance_loads():
stgs.pg.t_now: str = stgs.pg.long_t_now[11:]
stgs.pg.t_now_mins: int = t_to_mins(stgs.pg.t_now)

OFF_PEAK_VAR = t_to_mins(stgs.GE.start_time) < stgs.pg.t_now_mins < t_to_mins(stgs.GE.end_time) or \
stgs.pg.t_now_mins > t_to_mins(stgs.GE.start_time) > t_to_mins(stgs.GE.end_time) or \
t_to_mins(stgs.GE.start_time) > t_to_mins(stgs.GE.end_time) > stgs.pg.t_now_mins

if stgs.pg.loop_counter == 0: # Initialise
logger.critical("Initialising at: "+ stgs.pg.long_t_now)
logger.critical("")
Expand Down Expand Up @@ -574,38 +599,53 @@ def balance_loads():
logger.info("PALM Once Mode complete. Exiting...")
sys.exit()

if stgs.pg.once_mode is False:

# Reset sunrise and sunset for next day
env_obj.reset_sr_ss()

# Pause/resume battery once charged to compensate for AC3 inverter oscillation bug
# Covers charge period both in early-morning and spanning midnight
if MANUAL_HOLD_VAR is False and \
(t_to_mins(stgs.GE.start_time) < stgs.pg.t_now_mins < t_to_mins(stgs.GE.end_time) or \
stgs.pg.t_now_mins > t_to_mins(stgs.GE.start_time) > t_to_mins(stgs.GE.end_time) or \
t_to_mins(stgs.GE.start_time) > t_to_mins(stgs.GE.end_time) > stgs.pg.t_now_mins):
if -2 < (inverter.soc - inverter.tgt_soc) < 2: # Within 2% avoids sampling issues
inverter.set_mode("pause")
MANUAL_HOLD_VAR = True
if stgs.pg.month not in stgs.GE.winter and stgs.pg.t_now_mins == t_to_mins(stgs.GE.end_time) or \
stgs.pg.month in stgs.GE.winter and stgs.pg.t_now_mins == t_to_mins(stgs.GE.end_time_winter) or \
stgs.pg.t_now_mins + 60 == t_to_mins(stgs.GE.end_time):
inverter.set_mode("resume")
MANUAL_HOLD_VAR = False

# Afternoon battery boost in shoulder/winter months to load shift from peak period,
# useful for Cosy Octopus, etc
if stgs.GE.boost_start != "": # Only execute if parameter has been set
if stgs.pg.t_now_mins == t_to_mins(stgs.GE.boost_start) and stgs.pg.month in stgs.GE.winter:
logger.info("Enabling afternoon battery boost (winter)")
inverter.set_mode("charge_now")
elif stgs.pg.t_now_mins == t_to_mins(stgs.GE.boost_start) and stgs.pg.month in stgs.GE.shoulder:
logger.info("Enabling afternoon battery boost (shoulder)")
inverter.tgt_soc = int(stgs.GE.max_soc_target)
inverter.set_mode("charge_now_soc")
if stgs.pg.t_now_mins == t_to_mins(stgs.GE.boost_finish):
inverter.set_mode("set_soc_winter") # Set inverter for next timed charge period
# Pause/resume battery once charged to compensate for AC3 inverter oscillation bug
# Covers charge period both in early-morning and spanning midnight
if MANUAL_HOLD_VAR is False and OFF_PEAK_VAR is True:
if -2 < (inverter.soc - inverter.tgt_soc) < 2: # Within 2% avoids sampling issues
inverter.set_mode("pause")
MANUAL_HOLD_VAR = True
if stgs.pg.month not in stgs.GE.winter and stgs.pg.t_now_mins == t_to_mins(stgs.GE.end_time) or \
stgs.pg.month in stgs.GE.winter and stgs.pg.t_now_mins == t_to_mins(stgs.GE.end_time_winter) or \
stgs.pg.t_now_mins + 60 == t_to_mins(stgs.GE.end_time):
inverter.set_mode("resume")
MANUAL_HOLD_VAR = False

# Poll car charger during additional Intelligent Octopus slots
# If car is charging, either pause or charge inverter, depending on battery state
if OFF_PEAK_VAR is False:
EV_ACTIVE_VAR = ev_active()
if EV_ACTIVE_VAR is True and EV_CHARGING_VAR is False:
EV_CHARGING_VAR = True
if stgs.pg.month in stgs.GE.winter: # top up battery in parallel with EV charging
logger.info("EV charging active: enabling battery boost")
inverter.set_mode("charge_now")
else:
logger.info("EV charging active: pausing battery discharge")
inverter.set_mode("pause_discharge")
else: # Check every 15 minutes
if stgs.pg.t_now_mins % 15 == 0 and EV_ACTIVE_VAR is False and EV_CHARGING_VAR is True:
logger.info("EV charging inactive, resuming ECO battery mode")
inverter.set_mode("resume")

# Afternoon battery boost in shoulder/winter months to load shift from peak period,
# useful for Cosy Octopus, etc
if stgs.GE.boost_start != "": # Only execute if parameter has been set
if stgs.pg.t_now_mins == t_to_mins(stgs.GE.boost_start) and stgs.pg.month in stgs.GE.winter:
logger.info("Enabling afternoon battery boost (winter)")
inverter.set_mode("charge_now")
elif stgs.pg.t_now_mins == t_to_mins(stgs.GE.boost_start) and stgs.pg.month in stgs.GE.shoulder:
logger.info("Enabling afternoon battery boost (shoulder)")
inverter.tgt_soc = int(stgs.GE.max_soc_target)
inverter.set_mode("charge_now_soc")
if stgs.pg.t_now_mins == t_to_mins(stgs.GE.boost_finish):
inverter.set_mode("set_soc_winter") # Set inverter for next timed charge period

if stgs.pg.once_mode is False:
# Update carbon intensity every 15 mins as background task
if stgs.CarbonIntensity.enable is True and stgs.pg.loop_counter % 15 == 14:
do_get_carbon_intensity = threading.Thread(target=env_obj.update_co2())
Expand Down
7 changes: 6 additions & 1 deletion palm_settings.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# version 2023.08.07
# version 2023.09.29
"""
Settings file for use with palm.py: Compatible with v0.9, v0.10, v1.0.x and v1.1.x
2023.09.29: added Shelly power monitor
"""
######
# Do not edit this class definition, it is used to share global variables between components of the PALM system
Expand Down Expand Up @@ -126,6 +127,10 @@ class GE:
boost_start = "13:03"
boost_finish = "16:44"

class Shelly:
em0_url = "http://192.168.1.21/emeter/0"


# MiHome devices are used to activate various loads
class MiHome:
enable = True
Expand Down

0 comments on commit ddce77a

Please sign in to comment.