diff --git a/dbus-node-red-meter-einspeisung/meter_einspeisung.py b/dbus-node-red-meter-einspeisung/meter_einspeisung.py index db715a1..2a95291 100644 --- a/dbus-node-red-meter-einspeisung/meter_einspeisung.py +++ b/dbus-node-red-meter-einspeisung/meter_einspeisung.py @@ -1,163 +1,205 @@ #!/usr/bin/env python - -try: - import gobject # Python 2.x -except: - from gi.repository import GLib as gobject # Python 3.x -import platform + +# 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 -try: - import thread # for daemon = True / Python 2.x -except: - import _thread as thread # for daemon = True / Python 3.x - -# our own packages -sys.path.insert(1, os.path.join(os.path.dirname(__file__), '../ext/velib_python')) + +# 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 -path_UpdateIndex = '/UpdateIndex' - class NodeRedMeterEinspeisung: - def __init__(self, servicename, deviceinstance, paths, productname='Einspeisung', connection='NodeRED local'): - self._dbusservice = VeDbusService(servicename) + def __init__(self, servicename, deviceinstance, paths, productname='Einspeisung', connection='Node RED HTTP JSON service'): + self._dbusservice = VeDbusService("{}.http_{:02d}".format(servicename, deviceinstance)) self._paths = paths - + logging.debug("%s /DeviceInstance = %d" % (servicename, deviceinstance)) - + # 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', 0xB017) # like a EM24 - self._dbusservice.add_path('/DeviceType', 72) # like a EM24 + self._dbusservice.add_path('/ProductId', 45069) + self._dbusservice.add_path('/DeviceType', 345) self._dbusservice.add_path('/ProductName', productname) + self._dbusservice.add_path('/CustomName', productname) + self._dbusservice.add_path('/Latency', None) self._dbusservice.add_path('/FirmwareVersion', 1.0) self._dbusservice.add_path('/HardwareVersion', 0) self._dbusservice.add_path('/Connected', 1) - + 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 for path, settings in self._paths.items(): self._dbusservice.add_path( - path, settings['initial'], writeable=True, onchangecallback=self._handlechangedvalue) - - gobject.timeout_add(1000, self._update) # pause 1000ms before the next request - - def _update(self): - try: - self._dbusservice['/ErrorCode'] = 0 - nodered_url = "http://localhost:1880/meters" - nodered_r = requests.get(url=nodered_url) # request data from the Fronius PV inverter - nodered_data = nodered_r.json() # convert JSON data - self._dbusservice['/Ac/Power'] = nodered_data['einspeisung']['total_power'] - self._dbusservice['/Ac/Current'] = nodered_data['einspeisung']['total_current'] - self._dbusservice['/Ac/Energy/Forward'] = nodered_data['einspeisung']['total_import'] - self._dbusservice['/Ac/Energy/Reverse'] = nodered_data['einspeisung']['total_export'] - self._dbusservice['/Ac/L1/Voltage'] = nodered_data['einspeisung']['l1_voltage'] - self._dbusservice['/Ac/L2/Voltage'] = nodered_data['einspeisung']['l2_voltage'] - self._dbusservice['/Ac/L3/Voltage'] = nodered_data['einspeisung']['l3_voltage'] - self._dbusservice['/Ac/L1/Current'] = nodered_data['einspeisung']['l1_current'] - self._dbusservice['/Ac/L2/Current'] = nodered_data['einspeisung']['l2_current'] - self._dbusservice['/Ac/L3/Current'] = nodered_data['einspeisung']['l3_current'] - self._dbusservice['/Ac/L1/Power'] = nodered_data['einspeisung']['l1_power'] - self._dbusservice['/Ac/L2/Power'] = nodered_data['einspeisung']['l2_power'] - self._dbusservice['/Ac/L3/Power'] = nodered_data['einspeisung']['l3_power'] - self._dbusservice['/Ac/L1/Energy/Forward'] = nodered_data['einspeisung']['l1_import'] - self._dbusservice['/Ac/L2/Energy/Forward'] = nodered_data['einspeisung']['l2_import'] - self._dbusservice['/Ac/L3/Energy/Forward'] = nodered_data['einspeisung']['l3_import'] - self._dbusservice['/Ac/L1/Energy/Reverse'] = nodered_data['einspeisung']['l1_export'] - self._dbusservice['/Ac/L2/Energy/Reverse'] = nodered_data['einspeisung']['l2_export'] - self._dbusservice['/Ac/L3/Energy/Reverse'] = nodered_data['einspeisung']['l3_export'] - - 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("---"); - - index = self._dbusservice[path_UpdateIndex] + 1 # increment index - if index > 255: # maximum value of the index - index = 0 # overflow from 255 to 0 - self._dbusservice[path_UpdateIndex] = index + 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 - except Exception as e: - logging.critical("WARNING: Could not read from Node Red, check if Node Red service is running") - logging.critical('Error at %s', '_update', exc_info=e) - self._dbusservice['/ErrorCode'] = 1 - self._dbusservice['/Ac/Power'] = 0 - self._dbusservice['/Ac/Current'] = 0 - self._dbusservice['/Ac/L1/Voltage'] = 0 - self._dbusservice['/Ac/L2/Voltage'] = 0 - self._dbusservice['/Ac/L3/Voltage'] = 0 - self._dbusservice['/Ac/L1/Current'] = 0 - self._dbusservice['/Ac/L2/Current'] = 0 - self._dbusservice['/Ac/L3/Current'] = 0 - self._dbusservice['/Ac/L1/Power'] = 0 - self._dbusservice['/Ac/L2/Power'] = 0 - self._dbusservice['/Ac/L3/Power'] = 0 + # add _signOfLife 'timer' to get feedback in log every 5minutes + gobject.timeout_add(self._getSignOfLifeInterval()*60*1000, self._signOfLife) + + def _getSignOfLifeInterval(self): + 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") + + + return meter_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: + #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 + 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'] + self._dbusservice['/Ac/Energy/Forward'] = self._dbusservice['/Ac/L1/Energy/Forward'] + self._dbusservice['/Ac/L2/Energy/Forward'] + self._dbusservice['/Ac/L3/Energy/Forward'] + self._dbusservice['/Ac/Energy/Reverse'] = self._dbusservice['/Ac/L1/Energy/Reverse'] + self._dbusservice['/Ac/L2/Energy/Reverse'] + self._dbusservice['/Ac/L3/Energy/Reverse'] + + #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 + def _handlechangedvalue(self, path, value): logging.debug("someone else updated %s to %s" % (path, value)) return True # accept the change + + def main(): + #configure logging logging.basicConfig( format='%(asctime)s,%(msecs)d %(name)s %(levelname)s %(message)s', datefmt='%Y-%m-%d %H:%M:%S', - level=logging.ERROR, + level=logging.INFO, handlers=[ logging.FileHandler("%s/current.log" % (os.path.dirname(os.path.realpath(__file__)))), logging.StreamHandler() ]) - thread.daemon = True # allow the program to quit + try: + logging.info("Start"); + 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 _kwh = lambda p, v: (str(round(v, 2)) + ' KWh') - _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') - - + _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 pvac_output = NodeRedMeterEinspeisung( - servicename='com.victronenergy.grid', - deviceinstance=40, - paths={ - '/ErrorCode': {'initial': 0}, - '/Ac/Power': {'initial': 0, 'textformat': _W}, - '/Ac/Current': {'initial': 0, 'textformat': _A}, - '/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}, - '/Ac/Energy/Forward': {'initial': 0, 'textformat': _kwh}, - '/Ac/Energy/Reverse': {'initial': 0, 'textformat': _kwh}, + servicename='com.victronenergy.grid', + deviceinstance=40, + paths={ + '/Ac/Energy/Forward': {'initial': 0, 'textformat': _kwh}, # energy bought from the grid + '/Ac/Energy/Reverse': {'initial': 0, 'textformat': _kwh}, # energy sold to the grid + '/Ac/Power': {'initial': 0, 'textformat': _w}, + + '/Ac/Current': {'initial': 0, 'textformat': _a}, + '/Ac/Voltage': {'initial': 0, 'textformat': _v}, + + '/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}, '/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}, - - path_UpdateIndex: {'initial': 0}, - }) - + }) + logging.info('Connected to dbus, and switching over to gobject.MainLoop() (= event based)') mainloop = gobject.MainLoop() - mainloop.run() - + mainloop.run() except Exception as e: logging.critical('Error at %s', 'main', exc_info=e) - if __name__ == "__main__": main() \ No newline at end of file