Start a new topic

Off grid solar charging python script

Aloha from  Hawai'i :)  I have been searching this forum to see if anyone has posted this before, and it looks people have but didn't find a solution.

I have an off grid solar setup that I use to charge the 4 EV that our household has.  I use the Open EVSE and a Shelly UNI to modulate the EV charging rate.  I do this thru a python script.  The function of the script is to monitor the voltage of the battery connected to my solar inverter.  When the battery is full, the charge of the EV is started.  After starting the charge the script attempts to maintain a set voltage thru charge rate adjustments.  If the voltage falls below a set threshold then charging is ended until it rises above the start charge setpoint.  

I'm sure there is plenty of room for improvement and maybe an easier way to accomplish this task, however I have hopes this helps those that are looking and maybe starts a discussion for and easier way :)

The Shelly uni is only $12, it can do up to a 30V without any extras, much more with the addition of a voltage divider.

This is the script I wrote, my coding is not good and I have never had any training whatsoever in code :(

import requests

import traceback

import time

# Setpoint voltage for maintaining charging

setpoint_voltage = XX.X # Define the setpoint voltage

# Voltage at which charging should start

start_voltage = XX.X # Define the start voltage

# Voltage at which charging should stop

stop_voltage = XX.X # Define the stop voltage

# Minimum and maximum charge rates in amps

min_current = XX # Define the minimum charge rate

max_current = XX # Define the maximum charge rate

# Maximum number of retries

max_retries = 10

# Shelly device API URL

shelly_api_url = "http://192.168.1.XXX/status"

shelly_username = "XXXXXXXXX"

shelly_password = "XXXXXXXXXX"


openevse_api_url = "http://openevse-XXXX.local"

openevse_client_id = 1 # Define the OpenEVSE client ID

# Function to get current voltage from Shelly device

def get_voltage():

    retries = 0

    while retries < max_retries:


            response = requests.get(shelly_api_url, auth=(shelly_username, shelly_password))

            if response.status_code == 200:

                data = response.json()

                adc_voltage = data.get("adcs", [])[0].get("voltage", None)

                if adc_voltage is not None:

                    return adc_voltage


                    print("ADC voltage data not found in response")

                    return None


                print("Failed to fetch voltage data from Shelly device. Status code:", response.status_code)

                return None

        except requests.RequestException as e:

            print("Request failed:", e)

            retries += 1


            time.sleep(5) # Wait before retrying

    print("Max retries reached. Could not fetch voltage data.")

    return None

# Function to control OpenEVSE charging based on ADC voltage

def control_charging():

    charge_current = 0 # Start with no charging

    charging_started = False # Flag to track if charging was initiated

    voltage_below_stop = False # Flag to track if voltage fell below stop voltage after charging started

    manual_start = False # Flag to track manual start

    # Manual start option (runs once at the beginning)

    manual_start_input = input("Do you want to start charging manually? (yes/no): ").strip().lower()

    if manual_start_input == "yes":

        charging_started = True

        manual_start = True

        print("Charging started manually.")


        manual_start = False

    while True:

        voltage = get_voltage()

        if voltage is not None:

            print("Current voltage:", voltage)

            # Start charging when voltage exceeds start voltage or manually started

            if (voltage >= start_voltage and not charging_started) or manual_start:

                charge_current = min_current

                charging_started = True

                voltage_below_stop = False # Reset the flag

                manual_start = False # Reset manual start flag

                print("Starting charging at minimum rate")

            # Control charge current based on voltage

            if charging_started:

                if voltage > setpoint_voltage:

                    charge_current = min(max_current, charge_current + 1)

                    print("Increasing charge rate by 1 Amps. New charge rate:", charge_current)

                elif voltage < setpoint_voltage:

                    if charge_current > min_current:

                        charge_current = max(min_current, charge_current - 1) # Enforce min_current

                        print("Decreasing charge rate by 1 Amps. New charge rate:", charge_current)

                    elif charge_current == min_current:

                        print("Charge rate is already at minimum.")

            print("Charge current:", charge_current)

            # Stop charging if voltage falls below stop_voltage

            if charging_started and voltage <= stop_voltage:

                charge_current = 0

                voltage_below_stop = True

                charging_started = False # Ensure charging is stopped

                print("Stopping charging due to voltage below stop_voltage")

            # Restart charging if voltage goes back above start_voltage after previously stopping

            if voltage >= start_voltage and voltage_below_stop:

                voltage_below_stop = False # Reset the flag

                print("Voltage back above start_voltage. Waiting for start_voltage to restart charging.")

            # Make/update claim on OpenEVSE with adjusted charge current

            payload = {

                "state": "active" if charge_current > 0 else "disabled",

                "charge_current": charge_current,

                "max_current": max_current,

                "auto_release": True


            retries = 0

            while retries < max_retries:


                    response ="{openevse_api_url}/override", json=payload)

                    print("Response from OpenEVSE API:", response.text)


                except requests.RequestException as e:

                    print("Request failed:", e)

                    retries += 1

                    print("Retrying OpenEVSE command...")

                    time.sleep(5) # Wait before retrying

            if retries == max_retries:

                print("Max retries reached. Could not send command to OpenEVSE API.")

        time.sleep(30) # Adjust the polling interval as needed

# Start controlling charging



except Exception as e:

    print("An unexpected error occurred:", e)



















I have now added a simple GUI to the script and added some more safety features :)


I wish I could juts edit my last post, I found a bug in the logic that allowed for premature restart after low voltage events.  this has been fixed and the GUI improved :)


Aloha from Kekeha!
This is very similar to what I am trying to implement.

How are you reading the battery bank voltage?

Are you monitoring the house load and the inverter capacity as part of the EV charge current/load?
Any time of day limitations (don't start charging until batteries are a certain % of max voltage & its after 8am??)

So glad to have found your efforts. Thanks for posting! - Randy /

I have it setup to run fully off of battery voltage. In my case the time component is not critical, once the battery hits a voltage that equals 95% I want the evse on, then the logic tries to modulate the current to reach whatever equilibrium charge you set. In my case I know the voltage when I have 80% battery and set it there. Once the solar starts falling for the day and the battery drops to the 70% voltage it stops charging until it goes back up to 95%. It’s working great this way and pretty simple. The GUI has all the setpoint in it and they are easy to change. I use a Shelly Uni to measure the voltage. Cheap and simple.

My home battery off-grid system is 48VDC. So my battery range is 47 to 57.
Is yours 24VDC, or do you use a voltage divider to measure your batteries (and power the Shelly UNI?)

Thanks, Randy

I actually have this script running on 3 separate systems that are not yet tied together, long story LOL.  2 of them are 48V and 1 is 24.  The 48V systems I use a voltage divider for the ADC (voltage) measurement, and a 48VDC to 12V DC converter to power the UNI.  You could also use something simple like a wall wort power supply to feed the UNI power, I didn't because I like to see teh DC voltage even if the inverter is faulted.  

I used this voltage divider.  It gives 1/5th the supply voltage.  amazon supplied voltage divider

Login or Signup to post a comment