2022-04-01 10:05:18 +02:00
#!/usr/bin/env python
# import normal packages
import platform
import logging
import sys
import os
import sys
if sys . version_info . major == 2 :
import gobject
else :
from gi . repository import GLib as gobject
import sys
import time
import requests # for http GET
import configparser # for config/ini file
# our own packages from victron
sys . path . insert ( 1 , os . path . join ( os . path . dirname ( __file__ ) , ' /opt/victronenergy/dbus-systemcalc-py/ext/velib_python ' ) )
from vedbus import VeDbusService
2024-09-20 17:34:22 +02:00
class DbusGoeControllerService :
def __init__ ( self , servicename , paths , productname = ' go-eController ' , connection = ' go-eController HTTP JSON service ' ) :
2022-04-01 10:05:18 +02:00
config = self . _getConfig ( )
deviceinstance = int ( config [ ' DEFAULT ' ] [ ' Deviceinstance ' ] )
2023-05-17 22:54:28 +02:00
2022-04-01 10:05:18 +02:00
self . _dbusservice = VeDbusService ( " {} .http_ {:02d} " . format ( servicename , deviceinstance ) )
self . _paths = paths
logging . debug ( " %s /DeviceInstance = %d " % ( servicename , deviceinstance ) )
paths_wo_unit = [
2022-04-03 20:43:22 +02:00
' /Status ' , # value 'car' 1: charging station ready, no vehicle 2: vehicle loads 3: Waiting for vehicle 4: Charge finished, vehicle still connected
' /Mode '
2022-04-01 10:05:18 +02:00
]
2024-09-20 17:34:22 +02:00
#get data from go-eController
data = self . _getGoeControllerData ( )
2022-04-03 20:43:22 +02:00
2022-04-01 10:05:18 +02:00
# Create the management objects, as specified in the ccgx dbus-api document
self . _dbusservice . add_path ( ' /Mgmt/ProcessName ' , __file__ )
self . _dbusservice . add_path ( ' /Mgmt/ProcessVersion ' , ' Unkown version, and running on Python ' + platform . python_version ( ) )
self . _dbusservice . add_path ( ' /Mgmt/Connection ' , connection )
# Create the mandatory objects
self . _dbusservice . add_path ( ' /DeviceInstance ' , deviceinstance )
self . _dbusservice . add_path ( ' /ProductId ' , 0xFFFF ) #
self . _dbusservice . add_path ( ' /ProductName ' , productname )
2023-11-05 12:42:29 +01:00
if data :
2024-09-20 20:08:25 +02:00
self . _dbusservice . add_path ( ' /FirmwareVersion ' , data [ ' fwv ' ] )
self . _dbusservice . add_path ( ' /Serial ' , data [ ' sse ' ] )
self . _dbusservice . add_path ( ' /CustomName ' , data [ ' fna ' ] )
else :
self . _dbusservice . add_path ( ' /CustomName ' , productname )
2022-04-01 10:05:18 +02:00
self . _dbusservice . add_path ( ' /Connected ' , 1 )
self . _dbusservice . add_path ( ' /UpdateIndex ' , 0 )
# add paths without units
for path in paths_wo_unit :
self . _dbusservice . add_path ( path , None )
# add path values to dbus
for path , settings in self . _paths . items ( ) :
self . _dbusservice . add_path (
path , settings [ ' initial ' ] , gettextcallback = settings [ ' textformat ' ] , writeable = True , onchangecallback = self . _handlechangedvalue )
# last update
self . _lastUpdate = 0
2022-04-05 13:12:29 +02:00
2022-04-10 19:56:12 +02:00
# charging time in float
self . _chargingTime = 0.0
2022-04-01 10:05:18 +02:00
# add _update function 'timer'
gobject . timeout_add ( 250 , self . _update ) # pause 250ms before the next request
# add _signOfLife 'timer' to get feedback in log every 5minutes
gobject . timeout_add ( self . _getSignOfLifeInterval ( ) * 60 * 1000 , self . _signOfLife )
def _getConfig ( self ) :
config = configparser . ConfigParser ( )
config . read ( " %s /config.ini " % ( os . path . dirname ( os . path . realpath ( __file__ ) ) ) )
2022-04-03 20:43:22 +02:00
return config
2022-04-01 10:05:18 +02:00
def _getSignOfLifeInterval ( self ) :
config = self . _getConfig ( )
value = config [ ' DEFAULT ' ] [ ' SignOfLifeLog ' ]
if not value :
value = 0
return int ( value )
2024-09-20 17:34:22 +02:00
def _getGoeControllerStatusUrl ( self ) :
2022-04-01 10:05:18 +02:00
config = self . _getConfig ( )
accessType = config [ ' DEFAULT ' ] [ ' AccessType ' ]
if accessType == ' OnPremise ' :
2024-09-20 20:08:25 +02:00
URL = " http:// %s /api/status?filter=sse,fna,ccn,ccp,cpc,cec,usv,fwv " % ( config [ ' ONPREMISE ' ] [ ' Host ' ] )
2022-04-01 10:05:18 +02:00
else :
raise ValueError ( " AccessType %s is not supported " % ( config [ ' DEFAULT ' ] [ ' AccessType ' ] ) )
return URL
2022-04-05 21:19:43 +02:00
2024-09-20 17:34:22 +02:00
def _getGoeControllerSetUrl ( self , parameter , value ) :
2022-04-05 21:19:43 +02:00
config = self . _getConfig ( )
accessType = config [ ' DEFAULT ' ] [ ' AccessType ' ]
if accessType == ' OnPremise ' :
2024-09-20 17:34:22 +02:00
URL = " http:// %s /api/set? %s = %s " % ( config [ ' ONPREMISE ' ] [ ' Host ' ] , parameter , value )
2022-04-05 21:19:43 +02:00
else :
raise ValueError ( " AccessType %s is not supported " % ( config [ ' DEFAULT ' ] [ ' AccessType ' ] ) )
return URL
2024-09-20 17:34:22 +02:00
def _setGoeControllerValue ( self , parameter , value ) :
URL = self . _getGoeControllerSetUrl ( parameter , str ( value ) )
2022-04-05 21:19:43 +02:00
request_data = requests . get ( url = URL )
# check for response
if not request_data :
2024-09-20 17:34:22 +02:00
raise ConnectionError ( " No response from go-eController - %s " % ( URL ) )
2022-04-05 21:19:43 +02:00
json_data = request_data . json ( )
# check for Json
if not json_data :
raise ValueError ( " Converting response to JSON failed " )
if json_data [ parameter ] == str ( value ) :
return True
else :
2024-09-20 17:34:22 +02:00
logging . warning ( " go-eController parameter %s not set to %s " % ( parameter , str ( value ) ) )
2022-04-05 21:19:43 +02:00
return False
2022-04-01 10:05:18 +02:00
2024-09-20 17:34:22 +02:00
def _getGoeControllerData ( self ) :
URL = self . _getGoeControllerStatusUrl ( )
2023-11-05 12:42:29 +01:00
try :
request_data = requests . get ( url = URL , timeout = 5 )
except Exception :
return None
2022-04-01 10:05:18 +02:00
# check for response
if not request_data :
2024-09-20 17:34:22 +02:00
raise ConnectionError ( " No response from go-eController - %s " % ( URL ) )
2022-04-01 10:05:18 +02:00
json_data = request_data . json ( )
# check for Json
if not json_data :
raise ValueError ( " Converting response to JSON failed " )
return json_data
def _signOfLife ( self ) :
logging . info ( " --- Start: sign of life --- " )
logging . info ( " Last _update() call: %s " % ( self . _lastUpdate ) )
logging . info ( " Last ' /Ac/Power ' : %s " % ( self . _dbusservice [ ' /Ac/Power ' ] ) )
logging . info ( " --- End: sign of life --- " )
return True
def _update ( self ) :
try :
2024-09-20 17:34:22 +02:00
#get data from go-eController
data = self . _getGoeControllerData ( )
2022-04-01 10:05:18 +02:00
2023-11-05 12:42:29 +01:00
if data is not None :
2024-09-20 20:08:25 +02:00
#send data to DBus
2024-09-20 19:04:52 +02:00
grid_category_index = 1
2022-04-03 20:43:22 +02:00
2024-09-20 20:08:25 +02:00
current_l1 = data [ ' cpc ' ] [ grid_category_index ] [ 0 ]
current_l2 = data [ ' cpc ' ] [ grid_category_index ] [ 1 ]
current_l3 = data [ ' cpc ' ] [ grid_category_index ] [ 2 ]
self . _dbusservice [ ' /Ac/L1/Current ' ] = current_l1
self . _dbusservice [ ' /Ac/L2/Current ' ] = current_l2
self . _dbusservice [ ' /Ac/L3/Current ' ] = current_l3
sum_power = data [ ' ccp ' ] [ grid_category_index ]
current_sum = current_l1 + current_l2 + current_l3
self . _dbusservice [ ' /Ac/L1/Power ' ] = current_l1 / current_sum * sum_power
self . _dbusservice [ ' /Ac/L2/Power ' ] = current_l2 / current_sum * sum_power
self . _dbusservice [ ' /Ac/L3/Power ' ] = current_l3 / current_sum * sum_power
self . _dbusservice [ ' /Ac/Power ' ] = sum_power
2024-09-20 19:04:52 +02:00
self . _dbusservice [ ' /Ac/L1/Voltage ' ] = data [ ' usv ' ] [ 0 ] [ ' u1 ' ]
self . _dbusservice [ ' /Ac/L2/Voltage ' ] = data [ ' usv ' ] [ 0 ] [ ' u2 ' ]
self . _dbusservice [ ' /Ac/L3/Voltage ' ] = data [ ' usv ' ] [ 0 ] [ ' u3 ' ]
self . _dbusservice [ ' /Ac/N/Voltage ' ] = data [ ' usv ' ] [ 0 ] [ ' uN ' ]
2022-04-03 20:43:22 +02:00
2024-09-20 19:04:52 +02:00
self . _dbusservice [ ' /Ac/Energy/Forward ' ] = data [ ' cec ' ] [ grid_category_index ] [ 0 ] / 1000.
self . _dbusservice [ ' /Ac/Energy/Backward ' ] = data [ ' cec ' ] [ grid_category_index ] [ 1 ] / 1000.
2022-04-01 10:05:18 +02:00
2023-11-05 12:42:29 +01:00
#logging
2024-09-20 19:04:52 +02:00
logging . debug ( " Smartmeter Consumption (/Ac/Power): %s " % ( self . _dbusservice [ ' /Ac/Power ' ] ) )
logging . debug ( " Smartmeter Forward (/Ac/Energy/Forward): %s " % ( self . _dbusservice [ ' /Ac/Energy/Forward ' ] ) )
2023-11-05 12:42:29 +01:00
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 ( )
else :
logging . debug ( " Wallbox is not available " )
2022-04-01 10:05:18 +02:00
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
def _handlechangedvalue ( self , path , value ) :
2022-04-05 20:25:39 +02:00
logging . info ( " someone else updated %s to %s " % ( path , value ) )
2022-04-05 21:19:43 +02:00
if path == ' /SetCurrent ' :
2024-09-20 17:34:22 +02:00
return self . _setGoeControllerValue ( ' amp ' , value )
2022-04-05 21:19:43 +02:00
elif path == ' /StartStop ' :
2024-09-20 17:34:22 +02:00
return self . _setGoeControllerValue ( ' alw ' , value )
2022-04-05 21:19:43 +02:00
elif path == ' /MaxCurrent ' :
2024-09-20 17:34:22 +02:00
return self . _setGoeControllerValue ( ' ama ' , value )
2022-04-05 21:19:43 +02:00
else :
logging . info ( " mapping for evcharger path %s does not exist " % ( path ) )
return False
2022-04-01 10:05:18 +02:00
def main ( ) :
#configure logging
logging . basicConfig ( format = ' %(asctime)s , %(msecs)d %(name)s %(levelname)s %(message)s ' ,
datefmt = ' % Y- % m- %d % H: % M: % S ' ,
2022-04-05 20:25:39 +02:00
level = logging . INFO ,
2022-04-01 10:05:18 +02:00
handlers = [
logging . FileHandler ( " %s /current.log " % ( os . path . dirname ( os . path . realpath ( __file__ ) ) ) ) ,
logging . StreamHandler ( )
] )
try :
2022-04-03 20:43:22 +02:00
logging . info ( " Start " )
2022-04-01 10:05:18 +02: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 )
#formatting
2022-04-14 10:42:22 +02:00
_kwh = lambda p , v : ( str ( round ( v , 2 ) ) + ' kWh ' )
2022-04-01 10:05:18 +02:00
_a = lambda p , v : ( str ( round ( v , 1 ) ) + ' A ' )
_w = lambda p , v : ( str ( round ( v , 1 ) ) + ' W ' )
2022-04-03 20:43:22 +02:00
_v = lambda p , v : ( str ( round ( v , 1 ) ) + ' V ' )
_degC = lambda p , v : ( str ( v ) + ' °C ' )
2022-04-14 10:42:22 +02:00
_s = lambda p , v : ( str ( v ) + ' s ' )
2022-04-01 10:05:18 +02:00
#start our main-service
2024-09-20 17:34:22 +02:00
pvac_output = DbusGoeControllerService (
2024-09-20 19:04:52 +02:00
servicename = ' com.victronenergy.grid ' ,
2022-04-03 20:43:22 +02:00
paths = {
2024-09-20 19:04:52 +02:00
' /Ac/L1/Current ' : { ' initial ' : 0 , ' textformat ' : _a } ,
' /Ac/L2/Current ' : { ' initial ' : 0 , ' textformat ' : _a } ,
' /Ac/L3/Current ' : { ' initial ' : 0 , ' textformat ' : _a } ,
2024-09-20 20:08:25 +02:00
' /Ac/L1/Power ' : { ' initial ' : 0 , ' textformat ' : _w } ,
' /Ac/L2/Power ' : { ' initial ' : 0 , ' textformat ' : _w } ,
' /Ac/L3/Power ' : { ' initial ' : 0 , ' textformat ' : _w } ,
2022-04-01 10:05:18 +02:00
' /Ac/Power ' : { ' initial ' : 0 , ' textformat ' : _w } ,
2024-09-20 19:04:52 +02:00
' /Ac/L1/Voltage ' : { ' initial ' : 0 , ' textformat ' : _v } ,
' /Ac/L2/Voltage ' : { ' initial ' : 0 , ' textformat ' : _v } ,
' /Ac/L3/Voltage ' : { ' initial ' : 0 , ' textformat ' : _v } ,
' /Ac/N/Voltage ' : { ' initial ' : 0 , ' textformat ' : _v } ,
2022-04-01 10:05:18 +02:00
' /Ac/Energy/Forward ' : { ' initial ' : 0 , ' textformat ' : _kwh } ,
2024-09-20 19:04:52 +02:00
' /Ac/Energy/Backward ' : { ' initial ' : 0 , ' textformat ' : _kwh } ,
2022-04-03 20:43:22 +02:00
}
2024-09-20 19:04:52 +02:00
)
2022-04-01 10:05:18 +02:00
logging . info ( ' Connected to dbus, and switching over to gobject.MainLoop() (= event based) ' )
mainloop = gobject . MainLoop ( )
2024-09-20 17:34:22 +02:00
mainloop . run ( )
2022-04-01 10:05:18 +02:00
except Exception as e :
logging . critical ( ' Error at %s ' , ' main ' , exc_info = e )
if __name__ == " __main__ " :
main ( )