2022-08-09 22:38:46 +00:00
#!/usr/bin/env python
2022-08-15 13:28:07 +00:00
# import normal packages
import platform
2022-08-09 22:38:46 +00:00
import logging
import sys
import os
2022-08-15 13:28:07 +00:00
import sys
if sys . version_info . major == 2 :
import gobject
else :
from gi . repository import GLib as gobject
import sys
import time
2022-08-09 22:38:46 +00:00
import requests # for http GET
2022-08-15 13:28:07 +00:00
# our own packages from victron
sys . path . insert ( 1 , os . path . join ( os . path . dirname ( __file__ ) , ' /opt/victronenergy/dbus-systemcalc-py/ext/velib_python ' ) )
2022-08-09 22:38:46 +00:00
from vedbus import VeDbusService
2022-08-10 00:11:15 +00:00
class NodeRedMeterEinspeisung :
2022-08-15 13:28:07 +00:00
def __init__ ( self , servicename , deviceinstance , paths , productname = ' Einspeisung ' , connection = ' Node RED HTTP JSON service ' ) :
self . _dbusservice = VeDbusService ( " {} .http_ {:02d} " . format ( servicename , deviceinstance ) )
2022-08-09 22:38:46 +00:00
self . _paths = paths
2022-08-15 13:28:07 +00:00
2022-08-09 22:38:46 +00:00
logging . debug ( " %s /DeviceInstance = %d " % ( servicename , deviceinstance ) )
2022-08-15 13:28:07 +00:00
2022-08-09 22:38:46 +00:00
# Create the management objects, as specified in the ccgx dbus-api document
self . _dbusservice . add_path ( ' /Mgmt/ProcessName ' , __file__ )
2022-08-19 21:17:24 +00:00
self . _dbusservice . add_path ( ' /Mgmt/ProcessVersion ' , ' Python ' + platform . python_version ( ) )
2022-08-09 22:38:46 +00:00
self . _dbusservice . add_path ( ' /Mgmt/Connection ' , connection )
2022-08-15 13:28:07 +00:00
2022-08-09 22:38:46 +00:00
# Create the mandatory objects
self . _dbusservice . add_path ( ' /DeviceInstance ' , deviceinstance )
2022-08-15 13:28:07 +00:00
self . _dbusservice . add_path ( ' /ProductId ' , 45069 )
self . _dbusservice . add_path ( ' /DeviceType ' , 345 )
2022-08-09 22:38:46 +00:00
self . _dbusservice . add_path ( ' /ProductName ' , productname )
2022-08-15 13:28:07 +00:00
self . _dbusservice . add_path ( ' /CustomName ' , productname )
self . _dbusservice . add_path ( ' /Latency ' , None )
2022-08-09 22:38:46 +00:00
self . _dbusservice . add_path ( ' /FirmwareVersion ' , 1.0 )
self . _dbusservice . add_path ( ' /HardwareVersion ' , 0 )
self . _dbusservice . add_path ( ' /Connected ' , 1 )
2022-08-15 13:28:07 +00:00
self . _dbusservice . add_path ( ' /Role ' , ' grid ' )
self . _dbusservice . add_path ( ' /Position ' , 0 )
self . _dbusservice . add_path ( ' /Serial ' , 12345678 )
self . _dbusservice . add_path ( ' /UpdateIndex ' , 0 )
# add path values to dbus
2022-08-09 22:38:46 +00:00
for path , settings in self . _paths . items ( ) :
self . _dbusservice . add_path (
2022-08-15 13:28:07 +00:00
path , settings [ ' initial ' ] , gettextcallback = settings [ ' textformat ' ] , writeable = True , onchangecallback = self . _handlechangedvalue )
# last update
self . _lastUpdate = 0
# add _update function 'timer'
gobject . timeout_add ( 500 , self . _update ) # pause 500ms before the next request
2022-08-10 10:09:36 +00:00
2022-08-19 21:17:24 +00:00
# add _Status 'timer' to get feedback in log every 5minutes
gobject . timeout_add ( self . _getStatusInterval ( ) * 60 * 1000 , self . _Status )
2022-08-15 13:28:07 +00:00
2022-08-19 21:17:24 +00:00
def _getStatusInterval ( self ) :
2022-08-15 13:28:07 +00:00
value = 1
if not value :
value = 0
return int ( value )
def _getNodeRedData ( self ) :
URL = " http://localhost:1880/meters "
meter_r = requests . get ( url = URL )
# check for response
if not meter_r :
raise ConnectionError ( " No response from NodeRed - %s " % ( URL ) )
meter_data = meter_r . json ( )
# check for Json
if not meter_data :
raise ValueError ( " Converting response to JSON failed " )
2022-08-10 10:09:36 +00:00
2022-08-15 13:28:07 +00:00
return meter_data
2022-08-19 21:17:24 +00:00
def _Status ( self ) :
2023-04-22 11:27:03 +00:00
logging . debug ( " Last update: %s " % ( self . _lastUpdate ) )
logging . debug ( " Last ' /Ac/Power ' : %s " % ( self . _dbusservice [ ' /Ac/Power ' ] ) )
2022-08-09 22:38:46 +00:00
return True
2022-08-15 13:28:07 +00:00
def _update ( self ) :
try :
#get data from NodeRed 3em
meter_data = self . _getNodeRedData ( )
#send data to DBus
self . _dbusservice [ ' /Ac/Power ' ] = meter_data [ ' einspeisung ' ] [ ' total_power ' ] # positive: consumption, negative: feed into grid
2022-08-15 13:28:38 +00:00
self . _dbusservice [ ' /Ac/Current ' ] = meter_data [ ' einspeisung ' ] [ ' total_current ' ]
2022-08-15 13:28:07 +00:00
self . _dbusservice [ ' /Ac/L1/Voltage ' ] = meter_data [ ' einspeisung ' ] [ ' l1_voltage ' ]
self . _dbusservice [ ' /Ac/L2/Voltage ' ] = meter_data [ ' einspeisung ' ] [ ' l2_voltage ' ]
self . _dbusservice [ ' /Ac/L3/Voltage ' ] = meter_data [ ' einspeisung ' ] [ ' l3_voltage ' ]
self . _dbusservice [ ' /Ac/L1/Current ' ] = meter_data [ ' einspeisung ' ] [ ' l1_current ' ]
self . _dbusservice [ ' /Ac/L2/Current ' ] = meter_data [ ' einspeisung ' ] [ ' l2_current ' ]
self . _dbusservice [ ' /Ac/L3/Current ' ] = meter_data [ ' einspeisung ' ] [ ' l3_current ' ]
self . _dbusservice [ ' /Ac/L1/Power ' ] = meter_data [ ' einspeisung ' ] [ ' l1_power ' ]
self . _dbusservice [ ' /Ac/L2/Power ' ] = meter_data [ ' einspeisung ' ] [ ' l2_power ' ]
self . _dbusservice [ ' /Ac/L3/Power ' ] = meter_data [ ' einspeisung ' ] [ ' l3_power ' ]
self . _dbusservice [ ' /Ac/L1/Energy/Forward ' ] = meter_data [ ' einspeisung ' ] [ ' l1_import ' ]
self . _dbusservice [ ' /Ac/L2/Energy/Forward ' ] = meter_data [ ' einspeisung ' ] [ ' l2_import ' ]
self . _dbusservice [ ' /Ac/L3/Energy/Forward ' ] = meter_data [ ' einspeisung ' ] [ ' l3_import ' ]
self . _dbusservice [ ' /Ac/L1/Energy/Reverse ' ] = meter_data [ ' einspeisung ' ] [ ' l1_export ' ]
self . _dbusservice [ ' /Ac/L2/Energy/Reverse ' ] = meter_data [ ' einspeisung ' ] [ ' l2_export ' ]
self . _dbusservice [ ' /Ac/L3/Energy/Reverse ' ] = meter_data [ ' einspeisung ' ] [ ' l3_export ' ]
2023-04-22 11:27:03 +00:00
self . _dbusservice [ ' /Ac/Energy/Forward ' ] = meter_data [ ' einspeisung ' ] [ ' total_import ' ]
self . _dbusservice [ ' /Ac/Energy/Reverse ' ] = meter_data [ ' einspeisung ' ] [ ' total_export ' ]
2022-08-15 13:28:07 +00:00
#logging
logging . debug ( " House Consumption (/Ac/Power): %s " % ( self . _dbusservice [ ' /Ac/Power ' ] ) )
logging . debug ( " House Forward (/Ac/Energy/Forward): %s " % ( self . _dbusservice [ ' /Ac/Energy/Forward ' ] ) )
logging . debug ( " House Reverse (/Ac/Energy/Revers): %s " % ( self . _dbusservice [ ' /Ac/Energy/Reverse ' ] ) )
logging . debug ( " --- " ) ;
# increment UpdateIndex - to show that new data is available
index = self . _dbusservice [ ' /UpdateIndex ' ] + 1 # increment index
if index > 255 : # maximum value of the index
index = 0 # overflow from 255 to 0
self . _dbusservice [ ' /UpdateIndex ' ] = index
#update lastupdate vars
self . _lastUpdate = time . time ( )
except Exception as e :
logging . critical ( ' Error at %s ' , ' _update ' , exc_info = e )
# return true, otherwise add_timeout will be removed from GObject - see docs http://library.isr.ist.utl.pt/docs/pygtk2reference/gobject-functions.html#function-gobject--timeout-add
return True
2022-08-09 22:38:46 +00:00
def _handlechangedvalue ( self , path , value ) :
logging . debug ( " someone else updated %s to %s " % ( path , value ) )
return True # accept the change
2022-08-15 13:28:07 +00:00
2022-08-09 22:38:46 +00:00
def main ( ) :
2022-08-15 13:28:07 +00:00
#configure logging
2022-08-10 10:09:36 +00:00
logging . basicConfig ( format = ' %(asctime)s , %(msecs)d %(name)s %(levelname)s %(message)s ' ,
datefmt = ' % Y- % m- %d % H: % M: % S ' ,
2022-08-15 13:28:07 +00:00
level = logging . INFO ,
2022-08-10 10:09:36 +00:00
handlers = [
logging . FileHandler ( " %s /current.log " % ( os . path . dirname ( os . path . realpath ( __file__ ) ) ) ) ,
logging . StreamHandler ( )
] )
2022-08-15 13:28:07 +00:00
2022-08-10 10:09:36 +00:00
try :
2022-08-15 13:28:07 +00:00
logging . info ( " Start " ) ;
2022-08-10 10:09:36 +00:00
from dbus . mainloop . glib import DBusGMainLoop
# Have a mainloop, so we can send/receive asynchronous calls to and from dbus
DBusGMainLoop ( set_as_default = True )
2022-08-15 13:28:07 +00:00
#formatting
2022-08-10 10:09:36 +00:00
_kwh = lambda p , v : ( str ( round ( v , 2 ) ) + ' KWh ' )
2022-08-15 13:28:07 +00:00
_a = lambda p , v : ( str ( round ( v , 1 ) ) + ' A ' )
_w = lambda p , v : ( str ( round ( v , 1 ) ) + ' W ' )
_v = lambda p , v : ( str ( round ( v , 1 ) ) + ' V ' )
#start our main-service
2022-08-10 10:09:36 +00:00
pvac_output = NodeRedMeterEinspeisung (
2022-08-15 13:28:07 +00:00
servicename = ' com.victronenergy.grid ' ,
deviceinstance = 40 ,
paths = {
2023-04-22 11:27:03 +00:00
' /Ac/Energy/Forward ' : { ' initial ' : 0 , ' textformat ' : _kwh } , # energy bought from the grid, summ'd over all phases
' /Ac/Energy/Reverse ' : { ' initial ' : 0 , ' textformat ' : _kwh } , # energy sold to the grid, summ'd over all phases
' /Ac/Power ' : { ' initial ' : 0 , ' textformat ' : _w } , # power accumulated over all phases for ess regulation
' /Ac/Current ' : { ' initial ' : 0 , ' textformat ' : _a } , # current accumulated over all phases
2022-08-15 13:28:07 +00:00
' /Ac/L1/Voltage ' : { ' initial ' : 0 , ' textformat ' : _v } ,
' /Ac/L2/Voltage ' : { ' initial ' : 0 , ' textformat ' : _v } ,
' /Ac/L3/Voltage ' : { ' initial ' : 0 , ' textformat ' : _v } ,
' /Ac/L1/Current ' : { ' initial ' : 0 , ' textformat ' : _a } ,
' /Ac/L2/Current ' : { ' initial ' : 0 , ' textformat ' : _a } ,
' /Ac/L3/Current ' : { ' initial ' : 0 , ' textformat ' : _a } ,
' /Ac/L1/Power ' : { ' initial ' : 0 , ' textformat ' : _w } ,
' /Ac/L2/Power ' : { ' initial ' : 0 , ' textformat ' : _w } ,
' /Ac/L3/Power ' : { ' initial ' : 0 , ' textformat ' : _w } ,
2022-08-10 10:09:36 +00:00
' /Ac/L1/Energy/Forward ' : { ' initial ' : 0 , ' textformat ' : _kwh } ,
' /Ac/L2/Energy/Forward ' : { ' initial ' : 0 , ' textformat ' : _kwh } ,
' /Ac/L3/Energy/Forward ' : { ' initial ' : 0 , ' textformat ' : _kwh } ,
' /Ac/L1/Energy/Reverse ' : { ' initial ' : 0 , ' textformat ' : _kwh } ,
' /Ac/L2/Energy/Reverse ' : { ' initial ' : 0 , ' textformat ' : _kwh } ,
' /Ac/L3/Energy/Reverse ' : { ' initial ' : 0 , ' textformat ' : _kwh } ,
2022-08-15 13:28:07 +00:00
} )
2022-08-10 10:09:36 +00:00
logging . info ( ' Connected to dbus, and switching over to gobject.MainLoop() (= event based) ' )
mainloop = gobject . MainLoop ( )
2022-08-15 13:28:07 +00:00
mainloop . run ( )
2022-08-10 10:09:36 +00:00
except Exception as e :
logging . critical ( ' Error at %s ' , ' main ' , exc_info = e )
2022-08-09 22:38:46 +00:00
if __name__ == " __main__ " :
2023-04-22 11:27:03 +00:00
main ( )