Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add timeouts for Tesla commands and eGauge reads. #9

Merged
merged 20 commits into from
Apr 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
9ea01b8
Adding error checking to eGauge querries to try to catch overnight hang
sftman18 Mar 3, 2024
3b1cb0b
Adding debug flag to look for errors from tesla-control
sftman18 Mar 6, 2024
62b5bce
Merge branch 'main' into debug_freezes
sftman18 Mar 7, 2024
7584d73
Merge branch 'main' into debug_freezes
sftman18 Mar 10, 2024
8e5974e
Merge branch 'main' into debug_freezes
sftman18 Mar 14, 2024
bc226de
Added timeout for calls to eGauge and tesla-control, which were causi…
sftman18 Mar 18, 2024
043acaf
Bugfix for positional argument
sftman18 Mar 18, 2024
5e253b5
Changed to using a custom return value of 'Timeout', which seems to w…
sftman18 Mar 18, 2024
feee2e6
Extend start/wake timeouts
sftman18 Mar 19, 2024
f66019b
Increased all timeouts to 10sec. Eliminiated timeout for wake command
sftman18 Mar 19, 2024
db3e79b
change verify_new_charge_rate to use == vs >=. Was always true when …
sftman18 Mar 19, 2024
02d4a33
Standarize timeouts to: 5sec for eGauge, and 10sec for tesla-control
sftman18 Mar 20, 2024
78f9259
Merge branch 'main' into debug_freezes
sftman18 Mar 27, 2024
edc3374
Merge branch 'main' into debug_freezes
sftman18 Mar 31, 2024
df98b44
Move stopit decorators for the Tesla commands, to call_sub_error_hand…
sftman18 Apr 1, 2024
0f6cc0a
Correct the location of the timout argument to be within routines.py
sftman18 Apr 1, 2024
8f7edfd
Corrected ability to detect when returning from a Timeout condition
sftman18 Apr 3, 2024
f80a015
Detect changing the charge rate when current rate = 0 (case where cha…
sftman18 Apr 3, 2024
bbcdac4
Eliminated slow charge increase code. It just delayed getting up to …
sftman18 Apr 7, 2024
eb5c87e
Moved 1% charging attempt message from DEBUG to INFO and improved wor…
sftman18 Apr 9, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 9 additions & 9 deletions PVCharge.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,9 @@
# Use round() on charge_rate_sensor to prevent constant requests when on the edge of a value
new_charge_rate = math.floor(Energy.calculate_charge_rate(new_sample=False))
logging.debug(f"Car charging, new rate calculated: {new_charge_rate}, current rate: {round(Energy.charge_rate_sensor)}")
if new_charge_rate != round(Energy.charge_rate_sensor):
if (new_charge_rate != round(Energy.charge_rate_sensor)) and (round(Energy.charge_rate_sensor) != 0):
# Set new charge rate
if Car.set_charge_rate(new_charge_rate):
if Car.set_charge_rate(new_charge_rate) == True:
if Energy.verify_new_charge_rate(new_charge_rate):
logging.info(f"Car charging, new rate: {new_charge_rate} successfully set")
Messages.client.publish(topic=config["TOPIC_CHARGE_RATE"], payload=new_charge_rate, qos=1)
Expand All @@ -67,7 +67,7 @@
else: # We don't have enough sun
if round(Energy.charge_rate_sensor) > config["MIN_CHARGE"]: # If we are charging at anything greater than min charge
# Set charge rate to min charge
if Car.set_charge_rate(config["MIN_CHARGE"]):
if Car.set_charge_rate(config["MIN_CHARGE"]) == True:
logging.info(f"Car charging, Available Energy Reduced, new rate: {config['MIN_CHARGE']} successfully set")
Messages.client.publish(topic=config["TOPIC_CHARGE_RATE"], payload=config["MIN_CHARGE"], qos=1)
else:
Expand All @@ -77,7 +77,7 @@
# Wait configured time before stopping
waited_long_enough, stop_charging_time = routines.check_elapsed_time(loop_time, stop_charging_time, config["DELAYED_STOP_TIME"])
if waited_long_enough:
if Car.stop_charging():
if Car.stop_charging() == True:
logging.info("Car charging, Available Energy Reduced, charging was successfully stopped")
car_is_charging = False
stop_charging_time = 0
Expand All @@ -100,7 +100,7 @@
time.sleep(5) # Wait until car is awake
else:
logging.warning("Car was NOT woken successfully")
if Car.start_charging():
if Car.start_charging() == True:
logging.info("Car Started Charging Successfully")
time.sleep(10) # Wait until charging is fully started
if Energy.verify_new_charge_rate(config["MIN_CHARGE"]):
Expand All @@ -113,7 +113,7 @@
else:
logging.info(f"Car is NOT charging, Energy is Available, starting in: {round(config['DELAYED_START_TIME'] - (loop_time - start_charging_time))} seconds")
else:
logging.debug("Attempting to charge with only 1% remaining")
logging.info("Car will not charge with only 1% remaining, skipping")

else: # Car is already charging, set the flag
car_is_charging = True
Expand All @@ -122,7 +122,7 @@
else: # Sun isn't generating enough power to charge
if prevent_non_solar_charge: # If true, prevent after-hours charging
if round(Energy.charge_rate_sensor) >= config["MIN_CHARGE"]:
if Car.stop_charging(): # Stop if it is charging
if Car.stop_charging() == True: # Stop if it is charging
logging.info("Fast poll, Car discovered charging and was stopped successfully")
else:
logging.warning("Fast poll, Car discovered charging and was NOT stopped successfully")
Expand All @@ -140,12 +140,12 @@

logging.debug("Slow poll wait, ensure car isn't charging")
if round(Energy.charge_rate_sensor) >= config["MIN_CHARGE"]:
if Car.stop_charging(): # Stop if it is charging
if Car.stop_charging() == True: # Stop if it is charging
logging.info("Slow poll, Car discovered charging and was stopped successfully")
time.sleep(2) # Delay to allow stop command to complete
else:
logging.warning("Slow poll, Car discovered charging and was NOT stopped successfully")
Energy.sample_sensor() # Force sensor refresh to increase accuracy of subsequent loop
Energy.sample_sensor(timeout=5) # Force sensor refresh to increase accuracy of subsequent loop
else:
# Prevent non_solar_charge or delay, wait condition
time.sleep(config["SLOW_POLLING"])
Expand Down
4 changes: 2 additions & 2 deletions example_config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ TOPIC_CHARGE_RATE = "topic_base/new_charge_rate"
# Control loop parameters
MIN_CHARGE = 7 # Slowest allowed charge rate (Amps)
MIN_SOLAR = 500 # Minimum generation to enable polling (Watts)
SLOW_POLLING = 60 # Charging disabled, control topic check interval (seconds)
FAST_POLLING = 1 # Charging enabled, loop delay (seconds)
SLOW_POLLING = 120 # Charging disabled, control topic check interval (seconds)
FAST_POLLING = 2 # Charging enabled, loop delay (seconds)
DELAYED_START_TIME = 10 # When Energy is Available how long do we wait before starting charge (seconds)
DELAYED_STOP_TIME = 90 # When Available Energy is Reduced how long do we wait before stopping charge (seconds)
REPORT_DELAY = 60 # Send status string to MQTT every x (seconds)
2 changes: 2 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
egauge-python==0.7.5
paho-mqtt==2.0.0
python-dotenv==1.0.1
stopit==1.1.2
setuptools==69.2.0
29 changes: 19 additions & 10 deletions routines.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from egauge import webapi
from egauge.webapi.device import Register, Local
import paho.mqtt.client as mqtt
from stopit import threading_timeoutable as timeoutable

# Load parameters from .env
load_dotenv()
Expand Down Expand Up @@ -48,6 +49,7 @@ def __init__(self):
sys.exit(1)
logging.info(f"Connected to eGauge {self.meter_dev} (user {self.meter_user}, rights={rights})")

@timeoutable('Timeout')
def sample_register(self):
"""Sample registers and convert kW to W"""
self.register_sample = Register(self.my_eGauge, {"rate": "True", "time": "now"})
Expand All @@ -58,6 +60,7 @@ def sample_register(self):
self.tesla_charger_reg = self.register_sample.pq_rate(self.eGauge_charger).value * 1000
logging.debug(f"Tesla charger reg: {self.tesla_charger_reg:.0f}")

@timeoutable('Timeout')
def sample_sensor(self):
self.sensor_sample = Local(self.my_eGauge, "l=L1:L2&s=all")
self.charger_voltage_sensor = (self.sensor_sample.rate("L1", "n") +
Expand All @@ -68,19 +71,24 @@ def sample_sensor(self):

def calculate_charge_rate(self, new_sample):
if new_sample:
self.sample_register()
self.sample_sensor()
if self.sample_register(timeout=5) == 'Timeout':
logging.warning("eGauge Register read timed out")
return self.new_charge_rate
if self.sample_sensor(timeout=5) == 'Timeout':
logging.warning("eGauge Sensor read timed out")
return self.new_charge_rate
# Calculate the charge rate
self.new_charge_rate = ((self.generation_reg - (self.usage_reg - self.tesla_charger_reg)) /
self.charger_voltage_sensor)
logging.debug(f"New charge rate: {self.new_charge_rate:.2f}")
return self.new_charge_rate

def verify_new_charge_rate(self, new_charge_rate):
for attempts in range(0, 5):
self.sample_sensor()
for attempts in range(0, 6):
if self.sample_sensor(timeout=5) == 'Timeout':
logging.warning("eGauge Sensor read timed out")
# Use round() on the verify step (vs math.floor()) to prevent constant requests for the same value
if round(self.charge_rate_sensor) >= new_charge_rate:
if round(self.charge_rate_sensor) == new_charge_rate:
logging.debug("New charge rate verified")
return True
time.sleep(0.5)
Expand Down Expand Up @@ -139,24 +147,25 @@ def set_charge_rate(self, charge_rate):
command = self.tesla_base_command + ['charging-set-amps']
command.append(str(charge_rate))
logging.debug(command)
return call_sub_error_handler(command)
return call_sub_error_handler(command, timeout=15)

def start_charging(self):
command = self.tesla_base_command + ['charging-start']
logging.debug(command)
return call_sub_error_handler(command)
return call_sub_error_handler(command, timeout=20)

def stop_charging(self):
command = self.tesla_base_command + ['charging-stop']
logging.debug(command)
return call_sub_error_handler(command)
return call_sub_error_handler(command, timeout=10)

def wake(self):
command = self.tesla_base_command + ['-domain', 'vcsec', 'wake']
logging.debug(command)
return call_sub_error_handler(command)
return call_sub_error_handler(command, timeout=20)


@timeoutable('Timeout')
def call_sub_error_handler(cmd):
try:
result = subprocess.run(args=cmd, capture_output=True, text=True, check=True)
Expand Down Expand Up @@ -273,7 +282,7 @@ def on_message_plugged_in(self, client, userdata, msg):
if (not self.var_topic_teslamate_plugged_in) and self.var_topic_prevent_non_solar_charge:
# If previous state was False, and prevent_non_solar_charge is True, stop charging immediately
time.sleep(4) # Delay to ensure success of the stop command
if self.car_cmd.stop_charging():
if self.car_cmd.stop_charging() == True:
logging.info("Charging stopped upon plugin, prevent_non_solar_charge active")
else:
logging.warning("Charging NOT stopped upon plugin, prevent_non_solar_charge active")
Expand Down