├── service └── run ├── restart.sh ├── uninstall.sh ├── config.ini ├── install.sh ├── LICENSE ├── test.py ├── README.md └── dbus-iobroker-smartmeter.py /service/run: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | exec 2>&1 3 | python /data/dbus-iobroker-smartmeter/dbus-iobroker-smartmeter.py 4 | -------------------------------------------------------------------------------- /restart.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | kill $(pgrep -f 'python /data/dbus-iobroker-smartmeter/dbus-iobroker-smartmeter.py') 3 | -------------------------------------------------------------------------------- /uninstall.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | rm /service/dbus-iobroker-smartmeter 4 | kill $(pgrep -f 'supervise dbus-iobroker-smartmeter') 5 | chmod a-x /data/dbus-iobroker-smartmeter/service/run 6 | ./restart.sh 7 | -------------------------------------------------------------------------------- /config.ini: -------------------------------------------------------------------------------- 1 | [DEFAULT] 2 | SignOfLifeLog = 1 3 | IOBrokerPathSmartMeterId = smartmeter.0.1-0:96_1_0__255.value 4 | IOBrokerPathOverallConsumption = smartmeter.0.1-0:16_7_0__255.value 5 | IOBrokerPathPhase1 = smartmeter.0.1-0:56_7_0__255.value 6 | IOBrokerPathPhase2 = smartmeter.0.1-0:36_7_0__255.value 7 | IOBrokerPathPhase3 = smartmeter.0.1-0:76_7_0__255.value 8 | IOBrokerPathVoltage = modbus.0.inputRegisters.227.3_Input_Voltage 1 9 | IOBrokerPathGridSold = 0_userdata.0.house_grid_sold_current 10 | IOBrokerPathGridBought = 0_userdata.0.house_grid_bought_total 11 | IOBrokerHostPath = http://192.168.178.81:8087 -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # set permissions for script files 4 | chmod a+x /data/dbus-iobroker-smartmeter/restart.sh 5 | chmod 744 /data/dbus-iobroker-smartmeter/restart.sh 6 | 7 | chmod a+x /data/dbus-iobroker-smartmeter/uninstall.sh 8 | chmod 744 /data/dbus-iobroker-smartmeter/uninstall.sh 9 | 10 | chmod a+x /data/dbus-iobroker-smartmeter/service/run 11 | chmod 755 /data/dbus-iobroker-smartmeter/service/run 12 | 13 | 14 | 15 | # create sym-link to run script in deamon 16 | ln -s /data/dbus-iobroker-smartmeter/service /service/dbus-iobroker-smartmeter 17 | 18 | 19 | 20 | # add install-script to rc.local to be ready for firmware update 21 | filename=/data/rc.local 22 | if [ ! -f $filename ] 23 | then 24 | touch $filename 25 | chmod 755 $filename 26 | echo "#!/bin/bash" >> $filename 27 | echo >> $filename 28 | fi 29 | 30 | grep -qxF '/data/dbus-iobroker-smartmeter/install.sh' $filename || echo '/data/dbus-iobroker-smartmeter/install.sh' >> $filename 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 kopierschnitte 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /test.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | import requests # for http GET 4 | 5 | 6 | class DbusIobrokerSmartmeterService: 7 | def _getIOBrokerSmartmeterData(self): 8 | URL = "http://192.168.178.81:8087/getBulk/smartmeter.0.1-0:96_1_0__255.value,smartmeter.0.1-0:16_7_0__255.value,smartmeter.0.1-0:36_7_0__255.value,smartmeter.0.1-0:56_7_0__255.value,smartmeter.0.1-0:76_7_0__255.value" 9 | 10 | headers = {} 11 | 12 | meter_r = requests.request("GET", URL, headers=headers) 13 | 14 | # check for response 15 | if not meter_r: 16 | raise ConnectionError("No response from IOBroker - %s" % (URL)) 17 | 18 | meter_data = meter_r.json() 19 | 20 | # check for Json 21 | if not meter_data: 22 | raise ValueError("Converting response to JSON failed") 23 | 24 | return meter_data 25 | 26 | 27 | pvac_output = DbusIobrokerSmartmeterService() 28 | meter_data = pvac_output._getIOBrokerSmartmeterData() 29 | 30 | 31 | total_value = next( 32 | (x for x in meter_data if x['id'] == "smartmeter.0.1-0:16_7_0__255.value"), None)['val'] 33 | phase_1 = next((x for x in meter_data if x['id'] == 34 | "smartmeter.0.1-0:36_7_0__255.value"), None)['val'] 35 | phase_2 = next((x for x in meter_data if x['id'] == 36 | "smartmeter.0.1-0:56_7_0__255.value"), None)['val'] 37 | phase_3 = next((x for x in meter_data if x['id'] == 38 | "smartmeter.0.1-0:76_7_0__255.value"), None)['val'] 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # dbus-iobroker-smartmeter 2 | 3 | Integrate any smartmeter which is connected to iobroker. 4 | 5 | ## Purpose 6 | 7 | With the scripts in this repo it should be easy possible to install, uninstall, restart a service that . 8 | Idea is pasend on @RalfZim project linked below. 9 | 10 | ## Inspiration 11 | 12 | This project is my first on GitHub and with the Victron Venus OS, so I took some ideas and approaches from the following projects - many thanks for sharing the knowledge: 13 | 14 | - https://github.com/kopierschnitte/venus.dbus-senec-enfluri 15 | - https://github.com/RalfZim/venus.dbus-fronius-smartmeter 16 | - https://github.com/victronenergy/dbus-smappee 17 | - https://github.com/Louisvdw/dbus-serialbattery 18 | - https://community.victronenergy.com/questions/85564/eastron-sdm630-modbus-energy-meter-community-editi.html 19 | 20 | ## Install & Configuration 21 | 22 | IO-Broker 23 | 24 | - configure simple api plugin for api broker 25 | - include the power consumption and energy sold/bought objects into iobroker. Implementing this differs on your situation. There are a lot of options depending on your hardware. Examples: 26 | 27 | - Shelly: https://github.com/iobroker-community-adapters/ioBroker.shelly 28 | - Fronius: https://github.com/iobroker-community-adapters/ioBroker.fronius 29 | - Kostal: https://github.com/StrathCole/ioBroker.plenticore 30 | - Senec: https://github.com/nobl/iobroker.senec/ 31 | - Smartmeter: iobroker smartmeter plugin 32 | 33 | 34 | 35 | Venus/GX 36 | 37 | - Get Superuser rights on your CCGX device (https://www.victronenergy.com/live/ccgx:root_access#set_access_level_to_superuser) 38 | - Configure root password 39 | - Get the code from github 40 | - change config.ini as per your settings (see Config-INI Details) 41 | - Connect via winscp (or similar ssh clients) and copy over the code to /data/dbus-iobroker-smartmeter 42 | - Possibly adjust the execution rights on install.sh (e.g. via winscp) 43 | - Execute install.sh script 44 | 45 | Config-INI Details 46 | 47 | - All IOBroker Paths are referencing to the _full path_ of the iobroker object 48 | - IOBrokerPathSmartMeterId --> Unique ID (serial) used - can e.g. be the serial of the smart meter 49 | - IOBrokerPathOverallConsumption --> Consumption of all three phases in W 50 | - IOBrokerPathPhase1 --> Consumption on phase 1 in W 51 | - IOBrokerPathPhase2 --> Consumption on phase 2 in W 52 | - IOBrokerPathPhase3 --> Consumption on phase 3 in W 53 | - IOBrokerPathGridSold --> Total Sold in kWh 54 | - IOBrokerPathGridBought --> Total Bought in kWh 55 | 56 | Additionally the parameter _IOBrokerHostPath_ must give the absolute URL to your IOBroker instance. 57 | 58 | ## How it works 59 | 60 | ### My setup 61 | 62 | - iobroker smartmeter plugin ( https://github.com/Apollon77/ioBroker.smartmeter ) 63 | - iobroker simple api plugin ( https://github.com/ioBroker/ioBroker.simple-api ) 64 | - any ir reader for the smartmeter https://www.ebay.de/itm/125077162292 65 | 66 | ### Details / Process 67 | 68 | As mentioned above the script is inspired by @RalfZim and @kopierschnitte fronius smartmeter implementation. 69 | So what is the script doing: 70 | 71 | - Running as a service 72 | - connecting to DBus of the Venus OS `com.victronenergy.grid.http_40` 73 | - After successful DBus connection, IOBroker smartmeter values are accessed. 74 | - Serial is taken from the response as device serial 75 | - Paths are added to the DBus with default value 0 - including some settings like name, etc 76 | - After that a "loop" is started which polls iobroker data every 1000ms from the REST-API and updates the values in the DBus 77 | 78 | Thats it 😄 79 | 80 | ## Used documentation 81 | 82 | - https://github.com/victronenergy/venus/wiki/dbus#grid DBus paths for Victron namespace 83 | - https://github.com/victronenergy/venus/wiki/dbus-api DBus API from Victron 84 | - https://www.victronenergy.com/live/ccgx:root_access How to get root access on GX device/Venus OS 85 | -------------------------------------------------------------------------------- /dbus-iobroker-smartmeter.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # import normal packages 4 | from vedbus import VeDbusService 5 | import platform 6 | import logging 7 | import sys 8 | import os 9 | import sys 10 | if sys.version_info.major == 2: 11 | import gobject 12 | else: 13 | from gi.repository import GLib as gobject 14 | import sys 15 | import time 16 | import requests # for http GET 17 | import configparser # for config/ini file 18 | import struct 19 | 20 | # our own packages from victron 21 | sys.path.insert(1, os.path.join(os.path.dirname(__file__), 22 | '/opt/victronenergy/dbus-systemcalc-py/ext/velib_python')) 23 | 24 | 25 | class DbusIobrokerSmartmeterService: 26 | def __init__(self, servicename, deviceinstance, paths, productname='Smartmeter Reader', connection='Smartmeter via IOBroker HTTP JSON service'): 27 | self._dbusservice = VeDbusService( 28 | "{}.http_{:02d}".format(servicename, deviceinstance)) 29 | self._paths = paths 30 | 31 | self._config = self._getConfig() 32 | 33 | # get data from Senec 34 | meter_data = self._getIOBrokerSmartmeterData() 35 | 36 | self.grid_sold_start = next((x for x in meter_data if x['id'] == 37 | self._getSmartMeterGridSold()), None)['val'] 38 | self.grid_bought_start = next((x for x in meter_data if x['id'] == 39 | self._getSmartMeterGridBought()), None)['val'] 40 | 41 | logging.debug("%s /DeviceInstance = %d" % 42 | (servicename, deviceinstance)) 43 | 44 | # debug: 45 | #logging.info("Senec serial: %s" % (self._getSenecSerial())) 46 | 47 | # Create the management objects, as specified in the ccgx dbus-api document 48 | self._dbusservice.add_path('/Mgmt/ProcessName', __file__) 49 | self._dbusservice.add_path( 50 | '/Mgmt/ProcessVersion', 'Unkown version, and running on Python ' + platform.python_version()) 51 | self._dbusservice.add_path('/Mgmt/Connection', connection) 52 | 53 | # Create the mandatory objects 54 | self._dbusservice.add_path('/DeviceInstance', deviceinstance) 55 | # self._dbusservice.add_path('/ProductId', 16) # value used in ac_sensor_bridge.cpp of dbus-cgwacs 56 | # self._dbusservice.add_path('/ProductId', 0xFFFF) # id assigned by Victron Support from SDM630v2.py 57 | # found on https://www.sascha-curth.de/projekte/005_Color_Control_GX.html#experiment - should be an ET340 Engerie Meter 58 | self._dbusservice.add_path('/ProductId', 45069) 59 | # found on https://www.sascha-curth.de/projekte/005_Color_Control_GX.html#experiment - should be an ET340 Engerie Meter 60 | self._dbusservice.add_path('/DeviceType', 345) 61 | self._dbusservice.add_path('/ProductName', productname) 62 | self._dbusservice.add_path('/CustomName', productname) 63 | self._dbusservice.add_path('/Latency', None) 64 | self._dbusservice.add_path('/FirmwareVersion', 0.1) 65 | self._dbusservice.add_path('/HardwareVersion', 0) 66 | self._dbusservice.add_path('/Connected', 1) 67 | self._dbusservice.add_path('/Role', 'grid') 68 | # normaly only needed for pvinverter 69 | self._dbusservice.add_path('/Position', 0) 70 | self._dbusservice.add_path('/Serial', self._getSmartMeterSerial()) 71 | self._dbusservice.add_path('/UpdateIndex', 0) 72 | 73 | # add path values to dbus 74 | for path, settings in self._paths.items(): 75 | self._dbusservice.add_path( 76 | path, settings['initial'], gettextcallback=settings['textformat'], writeable=True, onchangecallback=self._handlechangedvalue) 77 | 78 | # last update 79 | self._lastUpdate = 0 80 | 81 | # add _update function 'timer' 82 | # pause 1000ms before the next request 83 | gobject.timeout_add(500, self._update) 84 | 85 | # add _signOfLife 'timer' to get feedback in log every 5minutes 86 | gobject.timeout_add(self._getSignOfLifeInterval() 87 | * 60*1000, self._signOfLife) 88 | 89 | def _getSmartMeterSerial(self): 90 | meter_data = self._getIOBrokerSmartmeterData() 91 | 92 | device_id = next( 93 | (x for x in meter_data if x['id'] == self._getSmartMeterDeviceId()), None) 94 | 95 | if not device_id['val']: 96 | raise ValueError( 97 | "Response does not contain 'DEVICE_ID' attribute") 98 | 99 | serial = device_id['val'] 100 | return serial 101 | 102 | def _getConfig(self): 103 | config = configparser.ConfigParser() 104 | config.read("%s/config.ini" % 105 | (os.path.dirname(os.path.realpath(__file__)))) 106 | return config 107 | 108 | def _getSignOfLifeInterval(self): 109 | value = self._config['DEFAULT']['SignOfLifeLog'] 110 | 111 | if not value: 112 | value = 0 113 | 114 | return int(value) 115 | 116 | def _getSmartMeterDeviceId(self): 117 | value = self._config['DEFAULT']['IOBrokerPathSmartMeterId'] 118 | return value 119 | 120 | def _getSmartMeterOverallConsumption(self): 121 | value = self._config['DEFAULT']['IOBrokerPathOverallConsumption'] 122 | return value 123 | 124 | def _getSmartMeterPhase1Consumption(self): 125 | value = self._config['DEFAULT']['IOBrokerPathPhase1'] 126 | return value 127 | 128 | def _getSmartMeterPhase2Consumption(self): 129 | value = self._config['DEFAULT']['IOBrokerPathPhase2'] 130 | return value 131 | 132 | def _getSmartMeterPhase3Consumption(self): 133 | value = self._config['DEFAULT']['IOBrokerPathPhase3'] 134 | return value 135 | 136 | def _getSmartMeterGridSold(self): 137 | value = self._config['DEFAULT']['IOBrokerPathGridSold'] 138 | return value 139 | 140 | def _getSmartMeterGridBought(self): 141 | value = self._config['DEFAULT']['IOBrokerPathGridBought'] 142 | return value 143 | 144 | def _getSmartMeterVoltage(self): 145 | value = self._config['DEFAULT']['IOBrokerPathVoltage'] 146 | return value 147 | 148 | def _getIOBrokerPath(self): 149 | value = self._config['DEFAULT']['IOBrokerHostPath'] 150 | return value 151 | 152 | def _getIOBrokerSmartmeterData(self): 153 | URL = self._getIOBrokerPath() + "/getBulk/" + self._getSmartMeterDeviceId() + "," + self._getSmartMeterOverallConsumption() + "," + \ 154 | self._getSmartMeterPhase1Consumption() + "," + self._getSmartMeterPhase2Consumption() + \ 155 | "," + self._getSmartMeterPhase3Consumption() + "," + self._getSmartMeterGridBought() + \ 156 | "," + self._getSmartMeterGridSold() + "," + self._getSmartMeterVoltage() 157 | 158 | headers = {} 159 | 160 | meter_r = requests.request("GET", URL, headers=headers) 161 | 162 | # check for response 163 | if not meter_r: 164 | raise ConnectionError("No response from IOBroker - %s" % (URL)) 165 | 166 | meter_data = meter_r.json() 167 | 168 | # check for Json 169 | if not meter_data: 170 | raise ValueError("Converting response to JSON failed") 171 | 172 | return meter_data 173 | 174 | def _floatFromHex(self, val): 175 | 176 | return struct.unpack('!f', bytes.fromhex(val[3:]))[0] 177 | #struct.unpack('!f', (val[3:]).decode('hex'))[0] 178 | 179 | def _signOfLife(self): 180 | logging.info("--- Start: sign of life ---") 181 | logging.info("Last _update() call: %s" % (self._lastUpdate)) 182 | ##logging.info("Last '/Ac/Power': %s" % (self._dbusservice['/Ac/Power'])) 183 | logging.info("--- End: sign of life ---") 184 | return True 185 | 186 | def _update(self): 187 | try: 188 | # get data from Senec 189 | meter_data = self._getIOBrokerSmartmeterData() 190 | 191 | # send data to DBus 192 | total_value = next( 193 | (x for x in meter_data if x['id'] == self._getSmartMeterOverallConsumption()), None)['val'] 194 | phase_1 = next((x for x in meter_data if x['id'] == 195 | self._getSmartMeterPhase1Consumption()), None)['val'] 196 | phase_2 = next((x for x in meter_data if x['id'] == 197 | self._getSmartMeterPhase2Consumption()), None)['val'] 198 | phase_3 = next((x for x in meter_data if x['id'] == 199 | self._getSmartMeterPhase3Consumption()), None)['val'] 200 | grid_sold = next((x for x in meter_data if x['id'] == 201 | self._getSmartMeterGridSold()), None)['val'] - self.grid_sold_start 202 | grid_bought = next((x for x in meter_data if x['id'] == 203 | self._getSmartMeterGridBought()), None)['val'] - self.grid_bought_start 204 | voltage = next((x for x in meter_data if x['id'] == 205 | self._getSmartMeterVoltage()), None)['val'] 206 | 207 | # positive: consumption, negative: feed into grid 208 | self._dbusservice['/Ac/Power'] = total_value 209 | self._dbusservice['/Ac/L1/Voltage'] = voltage 210 | self._dbusservice['/Ac/L2/Voltage'] = voltage 211 | self._dbusservice['/Ac/L3/Voltage'] = voltage 212 | self._dbusservice['/Ac/L1/Current'] = phase_1 / voltage 213 | self._dbusservice['/Ac/L2/Current'] = phase_2 / voltage 214 | self._dbusservice['/Ac/L3/Current'] = phase_3 / voltage 215 | self._dbusservice['/Ac/L1/Power'] = phase_1 216 | self._dbusservice['/Ac/L2/Power'] = phase_2 217 | self._dbusservice['/Ac/L3/Power'] = phase_3 218 | 219 | self._dbusservice['/Ac/Current'] = total_value / voltage 220 | self._dbusservice['/Ac/Voltage'] = phase_3 221 | 222 | ##self._dbusservice['/Ac/L1/Energy/Forward'] = (meter_data['emeters'][0]['total']/1000) 223 | self._dbusservice['/Ac/Energy/Forward'] = grid_bought 224 | self._dbusservice['/Ac/Energy/Reverse'] = grid_sold 225 | 226 | # logging 227 | ##logging.info("House Consumption (/Ac/Power): %s" % (self._dbusservice['/Ac/Power'])) 228 | #logging.info("L1: %s L2: %s L3: %s" % (self._dbusservice['/Ac/L1/Power'],self._dbusservice['/Ac/L2/Power'],self._dbusservice['/Ac/L3/Power'])) 229 | ##logging.debug("House Forward (/Ac/Energy/Forward): %s" % (self._dbusservice['/Ac/Energy/Forward'])) 230 | ##logging.debug("House Reverse (/Ac/Energy/Revers): %s" % (self._dbusservice['/Ac/Energy/Reverse'])) 231 | # logging.debug("---"); 232 | 233 | # increment UpdateIndex - to show that new data is available 234 | index = self._dbusservice['/UpdateIndex'] + 1 # increment index 235 | if index > 255: # maximum value of the index 236 | index = 0 # overflow from 255 to 0 237 | self._dbusservice['/UpdateIndex'] = index 238 | 239 | # update lastupdate vars 240 | self._lastUpdate = time.time() 241 | except Exception as e: 242 | logging.critical('Error at %s', '_update', exc_info=e) 243 | 244 | # 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 245 | return True 246 | 247 | def _handlechangedvalue(self, path, value): 248 | logging.debug("someone else updated %s to %s" % (path, value)) 249 | return True # accept the change 250 | 251 | 252 | def main(): 253 | # configure logging 254 | logging.basicConfig(format='%(asctime)s,%(msecs)d %(name)s %(levelname)s %(message)s', 255 | datefmt='%Y-%m-%d %H:%M:%S', 256 | level=logging.INFO, 257 | handlers=[ 258 | logging.FileHandler( 259 | "%s/current.log" % (os.path.dirname(os.path.realpath(__file__)))), 260 | logging.StreamHandler() 261 | ]) 262 | 263 | try: 264 | logging.info("Start") 265 | 266 | from dbus.mainloop.glib import DBusGMainLoop 267 | # Have a mainloop, so we can send/receive asynchronous calls to and from dbus 268 | DBusGMainLoop(set_as_default=True) 269 | 270 | # formatting 271 | def _kwh(p, v): return (str(round(v, 2)) + ' KWh') 272 | def _a(p, v): return (str(round(v, 1)) + ' A') 273 | def _w(p, v): return (str(round(v, 1)) + ' W') 274 | def _v(p, v): return (str(round(v, 1)) + ' V') 275 | 276 | # start our main-service 277 | pvac_output = DbusIobrokerSmartmeterService( 278 | servicename='com.victronenergy.grid', 279 | deviceinstance=40, 280 | paths={ 281 | # energy bought from the grid 282 | '/Ac/Energy/Forward': {'initial': 0, 'textformat': _kwh}, 283 | # energy sold to the grid 284 | '/Ac/Energy/Reverse': {'initial': 0, 'textformat': _kwh}, 285 | '/Ac/Power': {'initial': 0, 'textformat': _w}, 286 | 287 | '/Ac/Current': {'initial': 0, 'textformat': _a}, 288 | '/Ac/Voltage': {'initial': 0, 'textformat': _v}, 289 | 290 | '/Ac/L1/Voltage': {'initial': 0, 'textformat': _v}, 291 | '/Ac/L2/Voltage': {'initial': 0, 'textformat': _v}, 292 | '/Ac/L3/Voltage': {'initial': 0, 'textformat': _v}, 293 | '/Ac/L1/Current': {'initial': 0, 'textformat': _a}, 294 | '/Ac/L2/Current': {'initial': 0, 'textformat': _a}, 295 | '/Ac/L3/Current': {'initial': 0, 'textformat': _a}, 296 | '/Ac/L1/Power': {'initial': 0, 'textformat': _w}, 297 | '/Ac/L2/Power': {'initial': 0, 'textformat': _w}, 298 | '/Ac/L3/Power': {'initial': 0, 'textformat': _w}, 299 | '/Ac/L1/Energy/Forward': {'initial': 0, 'textformat': _kwh}, 300 | '/Ac/L2/Energy/Forward': {'initial': 0, 'textformat': _kwh}, 301 | '/Ac/L3/Energy/Forward': {'initial': 0, 'textformat': _kwh}, 302 | '/Ac/L1/Energy/Reverse': {'initial': 0, 'textformat': _kwh}, 303 | '/Ac/L2/Energy/Reverse': {'initial': 0, 'textformat': _kwh}, 304 | '/Ac/L3/Energy/Reverse': {'initial': 0, 'textformat': _kwh}, 305 | }) 306 | 307 | logging.info( 308 | 'Connected to dbus, and switching over to gobject.MainLoop() (= event based)') 309 | mainloop = gobject.MainLoop() 310 | mainloop.run() 311 | except Exception as e: 312 | logging.critical('Error at %s', 'main', exc_info=e) 313 | 314 | 315 | if __name__ == "__main__": 316 | main() 317 | --------------------------------------------------------------------------------