question

outsourcedguru avatar image
outsourcedguru asked

Python code to query charge state on VenusOS

I've just spent much of the day trying to do what I thought should be simple: query the system to determine the charge state... and then programmatically do something about that.

It honestly took hours to figure out that the MODBUS interface could be useful for this. It took another hour or two to read how to query this type of (overly-engineered) third-party interface.

  • I created a Raspberry Pi 3B with the VenusOS system on it and configured it for my wi-fi
  • I plugged a VE.Direct USB interface from the Pi to my SmartSolar MPPT 150/100 Charge Controller
  • I powered ON the Pi in my solar shed
  • I visited the web interface from my browser via http://venus.local
  • Under Settings -> Services, I enabled Modbus TCP and noted under Available Services that com.victronenergy.system is Unit ID 100 and com.victronenergy.solarcharger is Unit ID 239
  • Under Device List, I review the information available under SmartSolar Charger MPPT 150/100 rev 2, (not that this is important for this tutorial)
  • On https://victronenergy.com I visit Downloads -> Technical information and download the Excel spreadsheet called "Modbus-TCP register list" and review the first worksheet "Field list", find the section for com.victronenergy.solarcharger, look for Charge State in the Description field and see that it's under address = 775.

On my MacBook, I create a folder ~/sites/venusos/dbus, change to it and create a text file in this called GetMode.py for running this code.

I run the following command to install the library.

pip install pymodbus

I create the Python file I mentioned before, noting that I've hard-coded the Unit ID for my SmartSolar charger as well as the hostname:

#!/usr/local/bin/python
# GetMode is a command line utility to query the current charge state
# on a VenusOS system.
# Requirements: python 2.7 and the pymodbus module (https://github.com/bashwork/pymodbus).
from pymodbus.client.sync import ModbusTcpClient

client = ModbusTcpClient(host='venus.local', port=502)
result = client.read_holding_registers(address=775, count=1, unit=239)
if result.function_code >= 0x80:
    print('Error in function_code: {} {}'.format(hex(result.function_code), hex(result.exception_code)))
else:
    print(result.registers)

I mark the script so that it can be executed:

chmod +x GetMode.py

Finally, I run the command in this folder:

./GetMode.py
[3]

Referring back to the Excel spreadsheet from before, here's how to interpret that response.

0=Off, 2=Fault, 3=Bulk, 4=Absorption, 5=Float, 6=Storage, 7=Equalize, 11=Other, 252=External Control

I confirm that my system is now in the Bulk charging mode as was seen from the MODBUS query I created.

-------

Okay, so why would I want to do this? What I intend to do is to migrate this code over to the VenusOS—based Raspberry Pi computer and run it as a service. I might run this so that once my batteries are fully charged that I can toggle on something that would use the (otherwise) wasted electricity that my panels are generating. This service could then toggle off that something once the solar has gone below a threshold.

I'm fond of the TP-Link brand of programmable power outlets and I have several of these in my home. What I usually do manually is to dump my excess power into subsystems of batteries with their own dedicated inverters which look something like this:

Power outlet on my primary system -> TP-Link -> Battery Charger -> 12VDC battery -> Third-Party Inverter

And then of course I could install another TP-Link to that downstream inverter and programmatically turn on, say, something to grow plants.

Note that you could use the script as-is by forwarding its output to something like awk, for example:

./GetMode.py | awk '$1 == "[5]" {print "float"}'
MPPT SmartSolarVenus OSRaspberry Pimppt statepython
4 comments
2 |3000

Up to 8 attachments (including images) can be used with a maximum of 190.8 MiB each and 286.6 MiB total.

matt1309 avatar image matt1309 commented ·

Hi @OutsourcedGuru


Just out of curiosity is there a reason you're opting for python over node red for this kind of setup? Node red is already installed on venus os large.

It seems you've tackled it in python now however node red would have provided you the data seen in modbus straight out of the box without any configuration needed. Rather than needing the python script to monitor modbus. (MQTT also option).


I have similar automations for smart switching. However my brand of choice is shelly. (I tend to use their HTTP api to activate, however they support websockets, MQTT as well)

1 Like 1 ·
outsourcedguru avatar image outsourcedguru matt1309 commented ·

I'm not convinced that I installed the "large" version of the VenusOS image, for what it's worth. I program in a variety of languages; I taught software development. Node JS is probably my preferred in case you were wondering.

Whenever I'm doing a project in the IoT world, I'm likely to use Python because of the availability of libraries to talk to GPIO pins and such. I wouldn't want to limit future plans by my language choice.

I include a link here for Shelly for those who might be curious.

0 Likes 0 ·
matt1309 avatar image matt1309 outsourcedguru commented ·

No worries, was just in case you hadnt heard about victrons nodes :)


Definitely worth a look if you haven't dabbled especially if js is your preferred.


Another one to flag I suppose if folk are stumbling upon this is if you're making a script specifically for Venus OS then you could also read data directly from DBUS rather than adding the modbus as the middle man. (ie how drivers tend to be written for venus os devices).

(Can use this approach on lan if it's safe to enable dbus-tcp)

Might be a bit easier to make code a little more event based going dbus route vs modbus.

Some nice examples of dbus route here:

https://github.com/victronenergy/velib_python/tree/master




1 Like 1 ·
outsourcedguru avatar image outsourcedguru matt1309 commented ·

In case you guys need it, here's the python code I wrote yesterday to toggle ON a TP-Link SmartPlug for an isolated battery charger on a second set of equipment. I'll be dumping waste energy into downstream systems like this.

ChargerOn.py:

#!/usr/bin/env python3

import asyncio
from kasa import SmartPlug
from pprint import pformat as pf

async def main():
    charger = "192.168.11.65"
    plug = SmartPlug(charger)
    # plug.timeout = 20
    await plug.update()
    # print("Hardware: %s" % pf(plug.hw_info))
    # print("Full sysinfo: %s" % pf(plug.sys_info))
    if (plug.is_off):
        await plug.turn_on()
    else:
        print("Plug was already ON")

if __name__ == "__main__":
    asyncio.run(main())
0 Likes 0 ·
1 Answer
dmsims avatar image
dmsims answered ·

Just use Home assistant - all the integrations you need already exist

2 |3000

Up to 8 attachments (including images) can be used with a maximum of 190.8 MiB each and 286.6 MiB total.