question

hypercoffeedude avatar image
hypercoffeedude asked

Connecting RPi with VenusOS MQTT to Home Assistant?

Hello everyone,

It seems almost any information a person would need from the Venus OS device is published to it's own local MQTT server. I have accessed that MQTT server from a client and was able to get voltages and other information out of it. The Raspberry Pi is connected to a Victron 100/15 SmartSolar via a USB to TTL adapter and I can access the information and settings for it from Venus so that is not a problem. I understand the Venus MQTT requires a keep alive message to be sent about once every 60 seconds. I also already run an MQTT server and Home Assistant on another Raspberry Pi that I use for everything else.

How can I get the data I want (or all of it) into Home Assistant? Preferably, I just need to get Venus to send the messages to my normal MQTT server instead of it's own, or maybe a script that subscribes to the Venus MQTT, publishes a keep alive once every 60 seconds, and then forwards all received messages to the normal MQTT?

Venus OSRaspberry PiAssistantsMQTT
2 |3000

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

5 Answers
sinbad avatar image
sinbad answered ·

HomeAssistant has Modbus functionality built in :)

Just enable modbus-TCP on your Venus device, and config is like so:

Main config file: add

modbus: !include modbus.yaml


Modbus config file:


# cat modbus.yaml
name: victron
type: tcp
host: <ip of venus goes here>
port: 502


Then in your sensors config:

- platform: modbus
  registers:
    - name: Grid power
      hub: victron
      unit_of_measurement: "W"
      slave: 100
      register: 820
    - name: Solar power
      hub: victron
      unit_of_measurement: "W"
      slave: 100
      register: 850
    - name: Load
      hub: victron
      unit_of_measurement: "W"
      slave: 100
      register: 817
    - name: Battery
      hub: victron
      unit_of_measurement: "%"
      slave: 100
      register: 843


Some of the slave numbers may differ if you're using something other than a CCGX - not sure.


You can also write the settable modbus registers, I use a switch to set the Grid power setpoint:

in switches.yaml:

- platform: modbus
  registers:
    - name: Charge
      hub: victron
      slave: 100
      register: 2700
      command_on: 4000
      command_off: 80
      verify_state: false
10 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.

hypercoffeedude avatar image hypercoffeedude commented ·

Thanks so much! This worked perfectly! I had no idea it would be that easy :)

0 Likes 0 ·
sinbad avatar image sinbad hypercoffeedude commented ·

Only a pleasure :D

0 Likes 0 ·
fenix avatar image fenix sinbad commented ·

Hi


how do we define the unique id to read ?

0 Likes 0 ·
georgelza avatar image georgelza commented ·

how where do we get the relevant registers to use ?

G

0 Likes 0 ·
georgelza avatar image georgelza commented ·

Where would we get the register numbers to use, and what they map to.

I see you've used slave: 100 throughout, how where do we check this value for our own implementation ?

That command_on: 4000 and command_off: 80, where do we confirm that ours are the same or is that default.

G

0 Likes 0 ·
sinbad avatar image sinbad georgelza commented ·

Hi

There are links elsewhere to the excel spreadsheet that has the addresses for slave and register number.

The 4000 and 80 are the target grid values I want to set - so 80W when running normally, and 4000W when charging manually. It's not the most logical way of doing it, but it works for me. The system puts the difference between (grid target + solar - house draw) into charging the battery.

0 Likes 0 ·
denzel avatar image denzel commented ·

Hi,


Does this code go into the configuration.yaml file or is there a new file created. Please can you post the steps.


Regards

0 Likes 0 ·
fenix avatar image fenix commented ·

hello


is there a way to show the text base on the value in Home Assistant

i did the mapping, everything work but sytem retour value.. would be nice to see the text

example :

dbus-service-name = com.victronenergy.solarcharger

description = Charge state

Address = 775

0=Off;2=Fault;3=Bulk;4=Absorption;5=Float;6=Storage;7=Equalize;11=Other (Hub-1);252=External control




0 Likes 0 ·
Keith Weinheimer avatar image Keith Weinheimer fenix commented ·

just learning this myself but you can map the numbers to text using a mapper like this where "solar_small_state_num" is the name of your sensor reading the numeric state output from the mppt

# -- RV SYSTEM
# Modbus device (slave) IDs: /!\ Only for my system
# BMV 245
# MPPT Big Panels 239
# MPPT Small Panels 243
# Quattro 242
# Main System 100

# MPPT Small Panels 243
    - name: "Solar Small State Num"
      hub: VenusGX
      slave: 243
      register: 775


- platform: template
  sensors:
    solar_small_state_text:
        friendly_name: "Solar Small State"
        # 0=Off;2=Fault;3=Bulk;4=Absorption;5=Float;6=Storage;7=Equalize;11=Other (Hub-1);252=External control
        value_template: >-
          {% set mapper =  {
              '0' : 'Off',
              '2' : 'Fault',
              '3' : 'Bulk',
              '4' : 'Absorption',
              '5' : 'Float' } %}
          {% set state = states.sensor.solar_small_state_num.state %}
          {
                     { mapper[state] if state in mapper else 'Unknown' }}


0 Likes 0 ·
Todd Wackford avatar image
Todd Wackford answered ·

While I do not use home assistant, I use SmartThings for my IoT flavor of choice. But it should be pretty similar in functionality. Do you have API access and the ability to create device definitions in HA?

What I did is create a python script that reads the Victron modbus tcp registers every 5 minutes and send that data to an self defined device end points in SmartThings. I set the python scrip to fire via a cron job on the Rpi. Once the SmartThings devices receive the data, I can create all kinds of rules based on data thresholds. Like turn off certain outlets if SOC gets down to certain point. Or, flash some lights when SOC is 100%. etc, etc.

Anyway, google "victron python modbus tcp". That will get you going down the "rabbit hole" as they say.

BTW, here's some screenshots of the devices I created. One for my BMV-712 and one for my Morningstar MPPT 60 Solar Charge controller.


Solar


1 comment
2 |3000

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

groundattack avatar image groundattack commented ·

I'd love to get a copy of that Python script. You have done what I was about to try to do.

0 Likes 0 ·
georgelza avatar image
georgelza answered ·

I think I'm with you on that one GroundAttack, @Todd Wackford, any chance you might be willing to post the script here.
G

1 comment
2 |3000

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

groundattack avatar image groundattack commented ·

I have just been using Node Red addon for HomeAssistant and using MQTT to query and control Venus.

I tried other routes, but for me that is the simplest.

0 Likes 0 ·
Todd Wackford avatar image
Todd Wackford answered ·

@georgelza,

Here is the Python script. I have it run from a cron job every 5 minutes. I'm assuming you have SmartThings. You will need to change lines 131 and 133 to input your credentials and ID for SmartThings. Hope this helps you out.


#!/usr/bin/env python

#

from pymodbus.client.sync import ModbusTcpClient as ModbusClient
from pymodbus.constants import Endian
from pymodbus.payload import BinaryPayloadDecoder
from pymodbus.payload import BinaryPayloadBuilder
from pymodbus.client.sync import ModbusTcpClient as ModbusClient
from pymodbus.compat import iteritems
import time
import sys
import json
import httplib

def ConvertSectoDay(n):
    if n == 0:
        return "infinite"
    
    day = n // (24 * 3600)
    n = n % (24 * 3600)
    hour = n // 3600

    n %= 3600
    minutes = n // 60

    n %= 60
    seconds = n
    #need to  add if statement to send minutes when less then one day
    if day > 0:
        return(str(int(day)) + 'd ' + str(int(hour)) + 'h')
    else:
        return(str(int(hour)) + 'h ' + str(int(minutes)) + 'm')

        
####################           Main section of code           ####################
# configure the client logging
import logging
logging.basicConfig()
log = logging.getLogger('./modbus.error')
log.setLevel(logging.ERROR)


#Define variables for the connection
host = '192.168.10.92' #this is IP of the venus GX
port = 502
systemUnitId = 238   

#Make the connection to the Venus Device
client = ModbusClient(host,port)
client.connect()

# Define the output json object and initialize with some SmartThings needed information
jsonOut = { "event" : "batteryUpdate", "dni" : "bmv712", "data": {} }

#map out our scale values to use for the measurement types
v_scale =   100 #volts
i_scale =   10  #current
t_scale =   10  #temperature
ah_scale =  -10 #Amp hours
no_scale =  1   #No scaling
time2Go_scale = .01 #Time to go

# Setup up a data mapping object to use for processing and name/value pairs
from collections import namedtuple

# Read the registers and add to our records object
measurementType = namedtuple('measurementType', 'name address scale type')
records = []
records.append(measurementType(name='voltageMeasurement', address=259, scale=v_scale, type='uint'))
records.append(measurementType(name='current', address=261, scale=i_scale, type='int'))
records.append(measurementType(name='temperature', address=262, scale=t_scale, type='int'))
records.append(measurementType(name='consumedAmpHours', address=265, scale=ah_scale, type='uint'))
records.append(measurementType(name='battery', address=266, scale=i_scale, type='uint'))
records.append(measurementType(name='alarm', address=268, scale=no_scale, type='uint'))
records.append(measurementType(name='relayStatus', address=280, scale=no_scale, type='uint'))
records.append(measurementType(name='deepestDischarge', address=281, scale=ah_scale, type='uint'))
records.append(measurementType(name='lastDischarge', address=282, scale=ah_scale, type='uint'))
records.append(measurementType(name='averageDischarge', address=283, scale=ah_scale, type='uint'))
records.append(measurementType(name='chargeCycles', address=284, scale=no_scale, type='uint'))
records.append(measurementType(name='batteryTimeToGo', address=303, scale=time2Go_scale, type='uint'))

# Cycle thru records, scale the values and populate the json object
for record in records:
    result = client.read_holding_registers(record.address, count=1,  unit=systemUnitId)
    decoder = BinaryPayloadDecoder.fromRegisters(result.registers, Endian.Big)
    if record.type == 'uint':
        value = decoder.decode_16bit_uint()
    else:
        value = decoder.decode_16bit_int()
        
    jsonOut['data'][record.name] = float(value)/record.scale
    
#Now handle any additions or reformatting that we may need to do
#Battery Power
jsonOut['data']['power'] = round(jsonOut['data']['current']*jsonOut['data']['voltageMeasurement'], 1)

#Relay status
relayStates = ['Open', 'Closed']
jsonOut['data']['relayStatus'] = relayStates[int(jsonOut['data']['relayStatus'])]

#alarm status
alarmStates = ['OK', 'Alert']
if jsonOut['data']['alarm'] > 0.5:
    state = 1
else:
    state = 0
jsonOut['data']['alarm'] = alarmStates[state]

#Time to go
jsonOut['data']['batteryTimeToGo']  = ConvertSectoDay(jsonOut['data']['batteryTimeToGo'])

#we have all the data.
batteryJson = json.dumps(jsonOut)
client.close()

#show the json data on the console
print("---------------- JSON of Battery Data from Venus GX ----------------------")
print(batteryJson)
print("----------------              END OF JSON           ----------------------")


#Now open a connection to SmartThings and dump the data Venus (Connect) App.
conn = httplib.HTTPSConnection("graph.api.smartthings.com:443")
payload = batteryJson
headers = {
    'content-type': "application/json",
    'authorization': "Bearer #ST TOKEN HERE#"
    }
conn.request("POST", "/api/smartapps/installations/#installation ID here#/appHook", payload, headers)
r1 = conn.getresponse()
print r1.status, r1.reason

#TODO: Need to do some sort of error handling if SmartThings response has error.
conn.close()
5 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.

georgelza avatar image georgelza commented ·

awesome, thanks.

Question, the address values, guessing thats my device id's ? as per the VRM interface ?

or... where do I get a list of all the addresses and what they are mapped to

G

0 Likes 0 ·
Todd Wackford avatar image Todd Wackford georgelza commented ·
0 Likes 0 ·
groundattack avatar image groundattack Todd Wackford commented ·

Double awesome!

I'd love to see the Smartthings Groovy code. It would save a lot of bald head scratching! When you get a chance...

0 Likes 0 ·
gilbert avatar image gilbert Todd Wackford commented ·

Tod can you share the work you did here? I would love to pull this into smartthings. Have you managed to set automations from this? Ie let the geyser run when there is excess production etc?


0 Likes 0 ·
irwinr avatar image irwinr Todd Wackford commented ·

I'd like to second the request for the Groovy code as well, if it's available. :)

0 Likes 0 ·
mkh avatar image
mkh answered ·

Using modbus to get data into Home asistant is easy and simple way. Unfortunately has one restriction. Unlike MQTT it will work only if Venus and Home assistant are on the same local network.

I have instalation in camper, so if I left home wifi network, cerbo gx will get different ip from LTE modem and modbus connection will not work.

I would rather prefere if cerbo GX can transfer data to my MQTT server.

1 comment
2 |3000

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

Sam Wutschke avatar image Sam Wutschke commented ·

Totally true, and it can!

In the same settings, you can enable MQTT in the Venus OS.

0 Likes 0 ·