Created
January 1, 2018 16:15
-
-
Save izak/dcd6918cc53da6171216d0881f7f45a3 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/python -u | |
import sys, os | |
import logging | |
from functools import partial | |
from collections import Mapping | |
from datetime import datetime | |
import dbus | |
from dbus.mainloop.glib import DBusGMainLoop | |
import gobject | |
import requests | |
INTERVAL = 300000 | |
#INTERVAL = 60000 | |
PVOUTPUT = "https://pvoutput.org/service/r2/addstatus.jsp" | |
APIKEY = "YOUR_API_KEY_HERE" | |
SYSTEMID = "12345" | |
logger = logging.getLogger(__name__) | |
logger.setLevel(logging.INFO) | |
def find_services(bus, tp): | |
return [str(service) for service in bus.list_names() \ | |
if service.startswith('com.victronenergy.{}'.format(tp))] | |
class smart_dict(dict): | |
""" Dictionary that can be accessed via attributes. """ | |
def __getattr__(self, k): | |
try: | |
v = self[k] | |
if isinstance(v, Mapping): | |
return self.__class__(v) | |
return v | |
except KeyError: | |
raise AttributeError(k) | |
def __setattr__(self, k, v): | |
self[k] = v | |
dbus_int_types = (dbus.Int32, dbus.UInt32, dbus.Byte, dbus.Int16, dbus.UInt16, | |
dbus.UInt32, dbus.Int64, dbus.UInt64) | |
def unwrap_dbus_value(val): | |
"""Converts D-Bus values back to the original type. For example if val is | |
of type DBus.Double, a float will be returned.""" | |
if isinstance(val, dbus_int_types): | |
return int(val) | |
if isinstance(val, dbus.Double): | |
return float(val) | |
return val | |
def set_state(state, key, v): | |
state[key] = value = unwrap_dbus_value(v["Value"]) | |
def query(conn, service, path): | |
return conn.call_blocking(service, path, None, "GetValue", '', []) | |
def track(conn, state, service, path, target): | |
# Initialise state | |
state[target] = value = unwrap_dbus_value(query(conn, service, path)) | |
# And track it | |
conn.add_signal_receiver(partial(set_state, state, target), | |
dbus_interface='com.victronenergy.BusItem', | |
signal_name='PropertiesChanged', | |
path=path, | |
bus_name=service) | |
def main(): | |
logging.basicConfig(level=logging.INFO) | |
DBusGMainLoop(set_as_default=True) | |
conn = dbus.SystemBus() | |
generators = smart_dict() | |
consumers = smart_dict() | |
stats = smart_dict() | |
# Find solarcharger services | |
solarchargers = find_services(conn, 'solarcharger') | |
logger.info("Found solarchargers at %s", ', '.join(solarchargers)) | |
# Find grid meters | |
meters = find_services(conn, 'grid') | |
logger.info("Found grid meters at %s", ', '.join(meters)) | |
# Find vebus service | |
vebus = str(query(conn, "com.victronenergy.system", "/VebusService")) | |
logger.info("Found vebus at %s", vebus) | |
# Track solarcharger yield | |
for charger in solarchargers: | |
track(conn, generators, charger, "/Yield/User", charger) | |
# Track grid consumption | |
for meter in meters: | |
track(conn, consumers, meter, "/Ac/L1/Energy/Forward", meter) | |
# Track vebus consumption, from battery to input and output | |
track(conn, consumers, vebus, "/Energy/InverterToAcOut", "c1") | |
track(conn, consumers, vebus, "/Energy/InverterToAcIn1", "c2") | |
# Track power values | |
track(conn, stats, "com.victronenergy.system", "/Ac/Consumption/L1/Power", " | |
track(conn, stats, "com.victronenergy.system", "/Dc/Pv/Power", "pg") | |
# Periodic work | |
def _upload(): | |
energy_generated = sum(generators.values()) | |
energy_consumed = sum(consumers.values()) | |
logger.info("EG: %.2f, EC: %.2f, PG: %.2f, PC: %.2f", energy_generated, | |
energy_consumed, stats.pg, stats.pc) | |
# Post the values to pvoutput | |
now = datetime.now() | |
payload = { | |
"d": now.strftime("%Y%m%d"), | |
"t": now.strftime("%H:%M"), | |
"v1": int(energy_generated*1000), | |
"v2": int(stats.pg), | |
"v3": int(energy_consumed*1000), | |
"v4": int(stats.pc), | |
"c1": 1 | |
} | |
requests.post(PVOUTPUT, | |
headers={ | |
"X-Pvoutput-Apikey": APIKEY, | |
"X-Pvoutput-SystemId": SYSTEMID | |
}, data=payload) | |
# TODO handle response | |
return True | |
_upload() | |
gobject.timeout_add(INTERVAL, _upload) | |
gobject.MainLoop().run() | |
if __name__ == "__main__": | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment