Category Archives: VEDirect

Using Home Assistant Kasa Matter Plugs and a Victron MPPT VEDirect cable to deplete your batteries for next day — Updated Version

In my last version I used home assistant to connect to KASA matter plugs, I have found nothing but problems with matter, especially kp125M plugs, that use matter. One day I was turning on and off grid breakers and then I had to delete devices from google and home assistant because they could not connect using matter anymore! In this version we will use non-matter KASA smart plugs and skip home assistant. You can review what we did before here: https://blog.sunsaturn.com/freebsd/using-home-assistant-kasa-matter-plugs-and-a-victron-mppt-vedirect-cable-to-deplete-your-batteries-for-next-day/

In this version since we used 2 different scripts accessing a lot of same functions, makes no sense to keep it that way, so let’s stick our functions for KASA plugs and Victron’s VEDIRECT in a separate class file, and remake our 2 scripts for our cronjob.

Let’s start by creating a file called victronclass.py to hold our 2 functions, and add following to it:

import vedirect
from kasa import SmartPlug

class VictronApp:
    def __init__(self):
        pass

    async def victron(self, serial_port, serial_baud):
        try:
            device = vedirect.VEDirect(serial_port, serial_baud)
        except Exception as e:
            print(f"VICTRON [FAIL] : Read bad data: {e}")
            return False
        try:
            float(device.battery_volts)
        except Exception as e:
            print(f"VICTRON [FAIL] : Return type was not a float: {e}")
            return False
        if not device.battery_volts > 0:
            print(f"VICTRON [FAIL] : Voltage not greater than 0!")
            return False
        print(f"VICTRON [SUCCESS] : Voltage is {device.battery_volts}")
        return device.battery_volts


    # Usage: status = await kasa_plug(IPADDRESS, "on/off/check")
    # Returns True if plug turned on or off or if "check" is used returns True if plug is on
    async def kasa_plug(self, ip, cmd):
        p = SmartPlug(ip)
        try:
            await p.update()
            if cmd == "check":
                if p.is_on:
                    print("KASA [CHECK] : Plug is on")
                    return True
                else:
                    print("KASA [CHECK] : Plug is off")
                    return False
        except Exception as e:
            print(f"KASA [FAIL] : Exception received connecting: {e}")
            return False
        if cmd == "on":
            if p.is_on:
                print("KASA [SUCCESS] : Plug already on doing nothing")
                return True
            else:
                try:
                    await p.turn_on()
                    await p.update()
                    if p.is_on:
                        print("KASA [SUCCESS] : Turned Plug on")
                    else:
                        print("KASA [FAIL] : Plug still off after turning it on")
                    return p.is_on
                except Exception as e:
                    print(f"KASA [FAIL] : Exception Received Turning on Plug: {e}")
                    return False
        elif cmd == "off":
            if p.is_off:
                print("KASA [SUCCESS] : Plug already off doing nothing")
                return True
            else:
                try:
                    await p.turn_off()
                    await p.update()
                    if p.is_off:
                        print("KASA [SUCCESS] : Turned Plug off")
                    else:
                        print("KASA [FAIL] : Plug still on after turning it off")
                    return p.is_off
                except Exception as e:
                    print(f"KASA [FAIL] : Exception Received Turning off Plug: {e}")
                    return False
        else:
            print("KASA [FAIL] : INVALID ARGUMENT: use [on] [off] or [check]")
            return False

We created 1 class to hold both our KASA and victron vedirect functions, that way we can reuse these in our 2 main files. Let’s create our first main file for our cronjob and call it victron.py:

import asyncio
from victronclass import VictronApp

async def main():
    # Config START
    # VICTRON
    # trying 25.9(35% spring getting 4KWh per day atm)
    voltage_max = 25.9          # we don't want voltage higher than this for morning solar charging--leaving it at 50% each night
    # https://batteryfinds.com/lifepo4-voltage-chart-3-2v-12v-24v-48v/
    serial_port = '/dev/cuaU0'  # serial_port for VEDIRECT, on linux try "setserial -g /dev/ttyUSB[01]" to find your serial port
    serial_baud = '19200'       # serial baud rate, 19200 seems recommended setting from Victron
    # KASA PLUG
    # kasa_ip = '192.168.0.x'    # Ip address of your Kasa plug
    kasa_ip = 'grid_plug'       # Ip address or /etc/hosts name of your Kasa plug
    # Config END

    v = VictronApp()
    # check voltage of battery
    volts = await v.victron(serial_port, serial_baud)  
    if not volts:
        print(f"ERROR: no volts, exiting program....")
        exit(1)
    kasa = await v.kasa_plug(ip=kasa_ip, cmd="check")

    if kasa:  # if plug is on
        # plug is on, check it we need to turn it off
        if volts > voltage_max:
            print(f"Voltage is greater than {voltage_max} Turning off Grid Plug")
            await v.kasa_plug(ip=kasa_ip, cmd="off")
        else:
            print(f"Grid Plug is on and voltage less than {voltage_max} DOING NOTHING")
    else:
        # plug is off, check it we need to turn it on
        if volts < voltage_max:
            print(f"Voltage is less than {voltage_max} Turning on Grid Plug")
            await v.kasa_plug(ip=kasa_ip, cmd="on")
        else:
            print(f"Grid Plug is off and voltage greater than {voltage_max} DOING NOTHING")

asyncio.run(main())

Now you can add this to cron as follows:

*/10 23 * * * (/root/cronjobs/victron.py) > /dev/null 2>&1
*/10 0-6 * * * (/root/cronjobs/victron.py) > /dev/null 2>&1

This should allow you to depleted your batteries between 11pm and 6:50am each day.

Now let’s create our program to turn on the grid KASA plug no matter what at 7am, let’s call it grid_on.py:

import asyncio
from victronclass import VictronApp

async def main():
    # Config START
    # kasa_ip = '192.168.0.x'    # Ip address of your Kasa plug
    kasa_ip = 'grid_plug'       # Ip address or /etc/hosts name of your Kasa plug

    # Config END

    v = VictronApp()
    kasa = await v.kasa_plug(ip=kasa_ip, cmd="on")

asyncio.run(main())

and add that to your crontab as follows:

0 7 * * * (/root/cronjobs/grid_on.py) > /dev/null 2>&1

Great your all set with new improved version not using home assistant for KASA anymore. After reading up on matter protocol I am not to fond of it. Not only does it use IPV6, it takes a IPV4 address on your home network if it becomes a border router. Most devices try to become a border router, in fact different companies fight over fact they should become it, then you got all these devices that are border routers.

So to make more of a headache for you , now you have to track those down and deal with them, whether you VLAN them off or just block them completely on your firewall.

I think what is safer is just have 1 hub communicating with a bunch of zigbee devices, at least zigbee devices don’t suddenly attempt to become border routers on your network and do as they please. Like phillip hues lights for instance, you just have one hub to deal with if it’s misbehaving in anyway, not have to deal with every single light trying to become a border router lol. For smart plugs you definitely want full control at all times with a definite IP address. Things like light bulbs should never be matter devices attempting to become border routers. If someone is going to hack your home network , they shouldn’t be able to plant a backdoor on your light bulb that became a border router, they should need to be in range, not in China!

Ultimate security is keep everything zigbee etc, and use your own server to control all the zigbee devices on home assistant, then you don’t even have to worry about a phillips hue hub etc doing bad things on your network. Or if your crazy security conscious I guess look for a smart plug that is zigbee/bluetooth etc controllable.

So probably your best bet with KASA is their kp125 without the “M” on the end. This way you get energy stats as well turn plugs on and off as you please. Other versions like their ep25 etc stay far away from, if you loose internet at your house, they require authentication to cloud to use them. WHY ON EARTH? No idea, great right, internet goes out and now you lost control over your devices! Unreal.

I mean this is whole reason people use Home Assistant etc in first place. If internet goes out, telling google to turn on a simple light is replied with, “Sorry I don’t care about you anymore your internet is down blah blah”….. let’s hope it’s not someone’s pacemaker one day!

As for more revisions to this program, if I get my hands on a Cerbo GX one day, I’ll probably update for that using TCP instead of serial for vedirect. Or I may update it to be just one long running program at some point that accommodates for winter/spring/summer/fall, as well as maybe weather conditions. I mean you can get like 4KW a day on 4 panels or more when sun in shining but in winter and cloudy days you get almost nothing, so after I get a year’s history of my solar I’ll have a better idea how to proceed.

Keep in mind you could always use a hybrid inverter for switching, although I don’t think it would support depleting your batteries to a certain voltage at night lol. Also honestly they all suck, their switching times are horrendous, like 10-20ms, that would knock out my old servers, and any old electronics instantly. Better is a straight up inverter and use a real PDU like a Tripplite on ebay. You can get them fairly cheap these days, and you get sub 2ms switching times, then your golden…

Till next time…

SunSaturn

Using Home Assistant Kasa Matter Plugs and a Victron MPPT VEDirect cable to deplete your batteries for next day

Wouldn’t it be nice to automate depleting your batteries at night so that you have unwasted electrons coming in next day from your solar panels? My setup is a Kasa matter plug for grid and a Kasa matter plug for solar going into a PDU as an automatic transfer switch. So at any point on my phone I could turn off the grid plug and let the batteries drain for next day, but that is manual labor so we should automate it with python.

I am going to do this on FreeBSD, but would work for Linux etc. as well by changing your serial port for VEDirect, so we’ll just make that a configuration option. Another reason I’m going to use home assistant for turning plug on or off, is they are matter plugs, the kp125m to be exact, so just using python-kasa or whatever is not going to work for matter plugs(not currently anyways). However we can use Home Assistant API calls to turn them off or on.

And I know there is going to be someone saying why not just use a Victron Cerbo GX instead and use a TCP port on network. Fair enough, just don’t have one yet, so I’ll just run the VEDirect cable to my servers USB port and do it that way for now.

Alright let’s get started, figuring out voltage max…

First thing we have to do is figure out a battery voltage max that we want to have for next day before sun comes out. So I’m going to do this by example so you can figure out yours. In my setup I have 280AH lifepov4 batteries @24V. Now I know right now it’s winter time and most I’m going to get a day is 2Kw a day off my 920watts of solar panels from my Victron history graphs on the app.

so math: 1Kw = 1000w / 24V = 41.6 amps (24V might actually be 26.blahV for absorption voltage, but whatever) So 41.6 x 2 = 83.33 amps for 2Kw. So I need to drain batteries 280ah – 83.33ah = 197 AH. Now that I know I need it at 197AH each day to not have wasted electrons, I need to look at a chart:

https://batteryfinds.com/lifepo4-voltage-chart-3-2v-12v-24v-48v/

Chart is in percentages, so now I need to convert that 197AH to a percentage so I can match up voltages with it. So let’s do that, 197AH/280AH*100=70.35%. Great now I have a percentage so I can cross reference that chart at 70% to see what my max voltage should be. According to chart, 70% is 26.4V. Perfect we are golden, but remember to adjust this for summertime when your getting 6-7 Kw a day instead!

Installing pre-requisite packages

For VEDirect we’ll use the vedirect module. For Home Assistant all we’ll need is pythons requests and json libraries to keep it simple. For FreeBSD:

pkg install py39-pip py39-requests
pip install vedirect

Figuring out what we need to code

So what I’m thinking is we need 2 python scripts, one to turn on or off the grid plug depending on the voltage, and a 2nd to turn it on no matter what before sun comes out. We will toss the 2 scripts in a cronjob , so first one runs every 5 minutes between 11pm and 6:50am, and 2nd one to run at 7am to turn on grid plug no matter what.

Now we will need 2 things from Home Assistant, so let’s login there. First we need an API long lived token for accessing the API, you can find this by clicking under developer tools and settings on your nav bar. Just click on your name at very bottom. Now scroll all way to bottom and click “create token”. Copy it and save it for later.

Next we need to know what Home Assistant actually calls our plug, not the name we gave it, also called the “entity”. For that just go to your home screen, click on your plug and go to settings icon. In there you should see something called “entity ID”. Copy that as well, that’s how we talk to home assistant.

Let’s code the 2nd script first as it should be shorter….

First Python script to turn on the plug

Let’s call this one ha_grid_on.py :

#!/usr/local/bin/python3.9

#pkg install py39-pip py39-requests

import requests
import json
import time

def plug(address,token,entity,state):
   #returns on/off with "state=check" else 1
   if state == "on":
      url = "http://" + address + "/api/services/switch/turn_on"
      req = "requests.post(url, headers=headers, json=data)"
   elif state == "off":
      url = "http://" + address + "/api/services/switch/turn_off"
      req = "requests.post(url, headers=headers, json=data)"
   elif state == "check":
      url = "http://" + address + "/api/states/" + entity
      req = "requests.get(url, headers=headers)"
   else:
      print(f"SunSaturn# [FAIL] : Only off/on/check should be called")
      exit(1) #exit program no point doing anything
   #print(f"url is {url}")
   #print(f"req is {req}")
   data = {"entity_id": entity}
   #print(data['entity_id'])
   headers = {
      "Authorization": "Bearer "+ token,
      "content-type": "application/json",
   }
   try:
      response = eval(req)
   except Exception as e:
      print(f"SunSaturn# [FAIL] : Failed connect to home assistant: {e}")
      exit(1) #exit program no point doing anything   
   if not response.ok:
      print(f"SunSaturn# [FAIL] : Failed response code not 200: {response.text}")
      exit(1) #exit program no point doing anything
   check = json.loads(response.text)
   if state == "check":
      check = json.loads(response.text)
      return check['state']
   return 1


#CONFIG - EDIT ME
#######################
#HOME ASSISTANT
#HA assistant token
address='ha:8123'                     #Home Assistant API [IP:PORT]
                                      #HA token, create one in HA
token='eyJhbGciOiJIUzI1NiIsInR5dCI6IkpXVCJ9.eyJpc3MiOiIyYmQ1MDM0YTg2NjY0NDIzOWZlZjg2NmZiNGY4N2E7MyIsImlyuCI6MTcwMTk5MTU3OCwiZXhwIjoyMDE3MzUxNTc4fQ.swUTIoUFWbXEul3JuWRRiKpE-Ene-kKeM1ch8uNQF5o'
entity='switch.kasa_smart_wi_fi_plug' #grab entity of Grid Plug from settings for device on homepage of HA
#######################

plug(address,token,entity,"on") #last arguement can be 1 of on/off/check
print(f"Turned on Plug, checking if it's really on...")
time.sleep(1) #we need a sleep of 1 to give HA time to update
check = plug(address,token,entity,"check")
if check == "on":
   print(f"YEP")
else:
   print(f"NOPE")

Ok I stuffed most of logic in a function, also since all we care about is the script just failing anywhere it fails, we just stuff a exit(1) anywhere to stop execution instead of returning from function. Next we setup configuration variables to edit like the token and entity we got from HA already. Only other config we need is the IP:port. Adjust this for your own HA, I have just ha in /etc/hosts pointing to its real IP.

Next we turn the plug on, then do a check to see if it’s actually on as well. Pretty simple script, first one done.

2nd python script

Let’s called this one victron.py:

#!/usr/local/bin/python3.9

#pkg install py39-pip py39-requests
#pip install vedirect 




import vedirect
import requests
import json

def victron(serial_port, serial_baud):
   try:
      device = vedirect.VEDirect(serial_port,serial_baud)
   except Exception as e:
      print(f"SunSaturn# [FAIL] : Victron read bad data: {e}")
      exit(1) #exit program no point doing anything
   try:
      float(device.battery_volts)
   except Exception as e:
      print(f"SunSaturn# [FAIL] : Return type was not a float: {e}")
      exit(1) #exit program no point doing anything
   if not device.battery_volts > 0:
      print(f"SunSaturn# [FAIL] : Voltage not greater than 0!")
      exit(1) #exit program no point doing anything
   #print(dir(device))
   #print(type(device.battery_volts))
   return device.battery_volts


#http://ha:8123/api/states/switch.kasa_smart_wi_fi_plug
#http://ha:8123/api/services/switch/turn_on
#http://ha:8123/api/services/switch/turn_off
def plug(address,token,entity,state):
   #returns on/off with "state=check" else 1
   if state == "on":
      url = "http://" + address + "/api/services/switch/turn_on"
      req = "requests.post(url, headers=headers, json=data)"
   elif state == "off":
      url = "http://" + address + "/api/services/switch/turn_off"
      req = "requests.post(url, headers=headers, json=data)"
   elif state == "check":
      url = "http://" + address + "/api/states/" + entity
      req = "requests.get(url, headers=headers)"
   else:
      print(f"SunSaturn# [FAIL] : Only off/on/check should be called")
      exit(1) #exit program no point doing anything
   #print(f"url is {url}")
   #print(f"req is {req}")
   data = {"entity_id": entity}
   #print(data['entity_id'])
   headers = {
      "Authorization": "Bearer "+ token,
      "content-type": "application/json",
   }
   try:
      response = eval(req)
   except Exception as e:
      print(f"SunSaturn# [FAIL] : Failed connect to home assistant: {e}")
      exit(1) #exit program no point doing anything   
   if not response.ok:
      print(f"SunSaturn# [FAIL] : Failed response code not 200: {response.text}")
      exit(1) #exit program no point doing anything
   check = json.loads(response.text)
   if state == "check":
      check = json.loads(response.text)
      return check['state']
   return 1


#CONFIG - EDIT ME
#######################
#HOME ASSISTANT
#HA assistant token
address='ha:8123'                     #Home Assistant API [IP:PORT]
                                      #HA token, create one in HA
token='eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiIyYmQ1MDM0YTg2NjY0NDIzOWZlZjg2NmZiNGY4N2E1MyIsImlhdCI6MTcwMTk5MTU3OCwiZXhwIjoyMDE3MzUxNTc4fQ.swUTIoUFWbXEul3JuWRRiKpE-Ene-kKeM1ch8uNQF5o'
entity='switch.kasa_smart_wi_fi_plug' #grab entity of Grid Plug from settings for device on homepage of HA
#VICTRON
voltage_max=26.1                      #we don't want voltage higher than this for morning solar charging
serial_port='/dev/cuaU0'              #serial_port for VEDIRECT, on linux try "setserial -g /dev/ttyUSB[01]" to find your serial port
serial_baud='19200'                   #serial baud rate, 19200 seems recommended setting from Victron
#######################

#check voltage of battery
volts=victron(serial_port,serial_baud) #will exit program if any failures with exit(1)
#print(f"Volts is: {volts} ",type(volts))

#check if plug is on or off
check = plug(address,token,entity,"check") #last arguement can be 1 of on/off/check
print(f"Battery voltage is {volts}, checking what to do...")

if check == "on":
   #plug is on, check it we need to turn it off
   if volts > voltage_max:
      plug(address,token,entity,"off")
      print(f"Voltage is greater than {voltage_max} Turned off Grid Plug")
   else:
      print(f"Grid Plug is on and voltage less than {voltage_max} DOING NOTHING")
else:
   #plug is off, check it we need to turn it on
   if volts < voltage_max:
      plug(address,token,entity,"on")
      print(f"Voltage is less than {voltage_max} Turned on Grid Plug")
   else:
      print(f"Grid Plug is off and voltage greater than {voltage_max} DOING NOTHING")

Again we reuse the Home Assistant function, but add an extra function for Victron to pull battery voltage off the solar charge controller. New config options are max_voltage we figured out earlier, and serial_port and serial_baud rate. Adjust these to work with your setup. So what we are doing is checking if plug is on or off first, then depending on that we check voltage against voltage_max to decide to turn the plug on or off. Pretty simple 🙂

Now we are almost done….Personally I do everything as root(you may need to so you can access serial port), so I created a /root/cronjobs directory and stuck victron.py and ha_grid_on.py in there. Last thing to do is toss them in cron: “crontab -e”

# run every 15 minutes between 11pm and 6:59 am, suppress logging
*/15 23 * * * (/root/cronjobs/victron.py) > /dev/null 2>&1
*/15 0-6 * * * (/root/cronjobs/victron.py) > /dev/null 2>&1
#make sure grid plug on no matter what by 7am
0 7 * * * (/root/cronjobs/ha_grid_on.py) > /dev/null 2>&1

And don’t forget to “chmod 700 *.py” 🙂

Keep in mind you have to accommodate for “voltage rebound”, this happens when no load is present, battery voltage will jump up .3-.4, so you will want to lower your voltage for day 🙂 Unfortunately this means every 5 min its on solar, every 5 min on grid because of the voltage rebound. To compensate I use to run cron every 5 minutes, now I run every 15 minutes, long as not running a really high load should be fine.

And that’s it, all automated, never worry about it again 🙂 Of course adjust $max_voltage come summertime, but other than that, your golden 🙂 I’ll leave adjusting python script to actually use different max_voltages depending on the month as an exercise to the reader….

Till next time ….

SunSaturn