Compare commits
15 Commits
stable-1.5
...
main
Author | SHA1 | Date |
---|---|---|
Carsten Schmiemann | edc90b79cb | |
Carsten Schmiemann | e261a24a17 | |
Carsten Schmiemann | 5abff3afa6 | |
Carsten Schmiemann | c7f41413b7 | |
Carsten Schmiemann | 0771aacf20 | |
Carsten Schmiemann | 4897387ab0 | |
Carsten Schmiemann | 02b5503270 | |
Carsten Schmiemann | d02c67e3d3 | |
Carsten Schmiemann | 88fbc49fcb | |
Carsten Schmiemann | fdc54769d6 | |
Carsten Schmiemann | 41d57160be | |
Carsten Schmiemann | bbf202b74d | |
Carsten Schmiemann | 01746e83fe | |
Carsten Schmiemann | 27830903d9 | |
Carsten Schmiemann | d58adfd99b |
16
README.md
16
README.md
|
@ -37,13 +37,9 @@ I try to integrate every datapoint from Batriums BMS to fill Victrons registers.
|
|||
- capacity to full or to empty is not transmitted by Batrium (transmission of 0x00111900 is missing against documentation)
|
||||
- Ah, kWh charged and discharged is calcualated by script (see above)
|
||||
- Capacity of battery must be entered into script batrium.py (see above)
|
||||
- dbus script is started by rc.local which gets executed last, so all mppts and multiplus' showing temporarily "BMS missing" and need to be cleared after (re-)boot of Venus OS
|
||||
- <del>dbus script is started by rc.local which gets executed last, so all mppts and multiplus' showing temporarily "BMS missing" and need to be cleared after (re-)boot of Venus OS</del> fixed
|
||||
- include calculation of State Of Health, based on Ah discharge and SoC State after full charge, because Batrium does not have
|
||||
|
||||
### Victrons datapoints
|
||||
This script will emulate one of Victrons own BMS, therefore are many registers to fill, but I dont know how to fill all.
|
||||
For example what means average Ah discharged. I implemented datapoints to my best knowledge and it is working for me.
|
||||
|
||||
# dbus-node-red-XXX Service files
|
||||
|
||||
### Configuration
|
||||
|
@ -109,6 +105,16 @@ If you want to restart the script, for example after changing it, kill running s
|
|||
|
||||
The daemon-tools will restart the scriptwithin a few seconds.
|
||||
|
||||
# Backup
|
||||
|
||||
You can insert a SD card with FAT32 filesystem, or a blank one and format it with the GX device itself.
|
||||
|
||||
Simply add to cron following command, to copy data folder with some exceptions for node-red node modules every day at 2 am
|
||||
|
||||
`crontab -e`
|
||||
|
||||
`0 2 * * * /usr/bin/rsync rsync -rlptD --progress --exclude /data/home/nodered/.cache/ --exclude /data/home/nodered/.npm --exclude /data/home/nodered/.node-red/node_modules /data /run/media/mmcblk0p1/data-backup`
|
||||
|
||||
### Sources
|
||||
|
||||
Used https://github.com/victronenergy/velib_python/blob/master/dbusdummyservice.py as basis for my custom services.
|
||||
|
|
|
@ -100,6 +100,8 @@ class DbusBatteryService:
|
|||
'MinCellVoltage': ['/Settings/Batrium/MinCellVoltage', 0.0,0.0,0.0],
|
||||
'MaxCellVoltage': ['/Settings/Batrium/MaxCellVoltage', 0.0,0.0,0.0],
|
||||
'TargetChargeVoltage': ['/Settings/Batrium/TargetChargeVoltage', 0.0,0.0,0.0],
|
||||
'HistoryChargedEnergy': ['/Settings/Batrium/HistoryChargedEnergy', 0.0,0.0,0.0],
|
||||
'HistoryDischargedEnergy': ['/Settings/Batrium/HistoryDischargedEnergy', 0.0,0.0,0.0],
|
||||
'interval': ['/Settings/Batrium/Interval', 200, 200, 200]
|
||||
},
|
||||
eventCallback=handle_changed_setting)
|
||||
|
@ -133,6 +135,10 @@ class DbusBatteryService:
|
|||
## Load settings from Venus OS config xml ##
|
||||
self._dbusservice['/History/AverageDischarge'] = self._settings['AvgDischarge']
|
||||
self._dbusservice['/History/TotalAhDrawn'] = self._settings['TotalAhDrawn']
|
||||
self._dbusservice['/History/ChargedEnergy'] = self._settings['HistoryChargedEnergy']
|
||||
self._dbusservice['/History/DischargedEnergy'] = self._settings['HistoryDischargedEnergy']
|
||||
self.ChargedEnergy = self._settings['HistoryChargedEnergy']
|
||||
self.DischargedEnergy = self._settings['HistoryDischargedEnergy'] * -1
|
||||
self._dbusservice.add_path('/History/TimeSinceLastFullCharge', 0)
|
||||
self._dbusservice.add_path('/History/MinCellVoltage', self._settings['MinCellVoltage'])
|
||||
self._dbusservice.add_path('/History/MaxCellVoltage', self._settings['MaxCellVoltage'])
|
||||
|
@ -170,6 +176,8 @@ class DbusBatteryService:
|
|||
self._settings['MinCellVoltage'] = self._dbusservice['/History/MinCellVoltage']
|
||||
self._settings['MaxCellVoltage'] = self._dbusservice['/History/MaxCellVoltage']
|
||||
self._settings['TargetChargeVoltage'] = self._dbusservice['/Info/MaxChargeVoltage']
|
||||
self._settings['HistoryChargedEnergy'] = self._dbusservice['/History/ChargedEnergy']
|
||||
self._settings['HistoryDischargedEnergy'] = self._dbusservice['/History/DischargedEnergy']
|
||||
|
||||
def _daily_stats(self):
|
||||
## Update daily statistics and reset energy counters
|
||||
|
|
|
@ -1,208 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import sys
|
||||
import os
|
||||
import logging
|
||||
import itertools
|
||||
|
||||
from time import time
|
||||
from datetime import datetime
|
||||
import can
|
||||
import struct
|
||||
from argparse import ArgumentParser
|
||||
|
||||
VERSION = '1.0'
|
||||
|
||||
class BatriumBattery(can.Listener):
|
||||
def __init__(self, connection):
|
||||
self.hw_version = 0
|
||||
self.sw_version = 0
|
||||
self.capacity = 2.2
|
||||
self.maxChargeVoltage = 0
|
||||
self.numberOfModules = 3
|
||||
self.soc = 0
|
||||
self.soh = 0
|
||||
self.voltage = 0
|
||||
self.current = 0
|
||||
self.temperature = 0
|
||||
self.maxCellTemperature = 0
|
||||
self.maxCellTemperatureId = 0
|
||||
self.minCellTemperature = 0
|
||||
self.minCellTemperatureId = 0
|
||||
#self.cellVoltages_min = dict()
|
||||
#self.cellVoltages_max = dict()
|
||||
#self.cellTemperatures = dict()
|
||||
#self.cellBypassTemperatures = dict()
|
||||
#self.cellBypassPWM = dict()
|
||||
self.maxCellVoltage = 0
|
||||
self.maxCellVoltageId = 0
|
||||
self.minCellVoltage = 0
|
||||
self.minCellVoltageId = 0
|
||||
self.maxChargeCurrent = 0
|
||||
self.maxDischargeCurrent = 0
|
||||
self.updated = -1
|
||||
#self.TimeToEmpty = 0
|
||||
#self.TimeToFull = 0
|
||||
self.AhToEmpty = 0
|
||||
#self.AhToFull = 0
|
||||
self.NumberInBypass = 0
|
||||
#self.alarm_high_temperature = 0
|
||||
#self.alarm_high_voltage = 0
|
||||
#self.alarm_low_temperature = 0
|
||||
#self.alarm_low_voltage = 0
|
||||
#self.alarm_low_soc = 0
|
||||
#self.alarm_high_charge_current = 0
|
||||
#self.alarm_high_discharge_current = 0
|
||||
|
||||
self._ci = can.interface.Bus(channel=connection, bustype='socketcan')
|
||||
|
||||
# check connection and that reported system voltage roughly matches configuration
|
||||
found = False
|
||||
msg = None
|
||||
|
||||
while True:
|
||||
try:
|
||||
msg = self._ci.recv(timeout=10)
|
||||
except can.CanError:
|
||||
logging.error("Canbus error")
|
||||
|
||||
if msg == None:
|
||||
#timeout no system connected
|
||||
logging.error("No messages on canbus %s received. Check connection and speed." % connection)
|
||||
break;
|
||||
|
||||
elif msg.arbitration_id == 0x01:
|
||||
logging.info("Found Batrium BMS on %s" % connection)
|
||||
found = True
|
||||
break;
|
||||
|
||||
def on_message_received(self, msg):
|
||||
self.updated = msg.timestamp
|
||||
|
||||
if msg.arbitration_id == 0x00:
|
||||
self.hw_version = (((msg.data[1] * 256 + (1 << 8)) + msg.data[0] + (1 << 8)) )
|
||||
self.sw_version = (((msg.data[3] * 256 + (1 << 8)) + msg.data[2] + (1 << 8)) )
|
||||
self.serial = ((msg.data[7] * 256) + (msg.data[6] * 256) + (msg.data[5] * 256) + msg.data[4]) + (1 << 32)
|
||||
logging.debug("HW Version: %d, SW Version: %d, Serialnumber: %d", self.hw_version, self.sw_version, self.serial)
|
||||
|
||||
elif msg.arbitration_id == 0x01:
|
||||
self.minCellVoltage = ((msg.data[1] * 256) + msg.data[0]) * 0.001
|
||||
self.maxCellVoltage = ((msg.data[3] * 256) + msg.data[2]) * 0.001
|
||||
#self.avgCellVoltage = ((msg.data[5] * 256) + msg.data[4]) * 0.01
|
||||
self.minCellVoltageId = msg.data[6]
|
||||
self.maxCellVoltageId = msg.data[7]
|
||||
logging.debug("MinCellVolt %1.3fV (ID: %d), MaxCellVolt %1.3fV (ID: %d)", self.minCellVoltage, self.minCellVoltageId, self.maxCellVoltage, self.maxCellVoltageId)
|
||||
|
||||
elif msg.arbitration_id == 0x02:
|
||||
self.minCellTemperature = msg.data[0] - 40
|
||||
self.maxCellTemperature = msg.data[1] - 40
|
||||
self.temperature = msg.data[2] - 40
|
||||
self.minCellTemperatureId = msg.data[3]
|
||||
self.maxCellTemperatureId = msg.data[4]
|
||||
logging.debug("MinCellTemp: %d (ID: %d), MaxCellTemp: %d (ID: %d), AverageTemp: %d", self.minCellTemperature, self.minCellTemperatureId, self.maxCellTemperature, self.maxCellTemperatureId, self.temperature)
|
||||
|
||||
elif msg.arbitration_id == 0x03:
|
||||
self.NumberInBypass = msg.data[0]
|
||||
logging.debug("Cell number in bypass: %d", self.NumberInBypass)
|
||||
|
||||
if msg.arbitration_id == 0x04:
|
||||
logging.debug(msg)
|
||||
self.voltage = ((msg.data[1] * 256) + msg.data[0]) * 0.1
|
||||
self.current = int(((msg.data[3] * 256) + msg.data[2]) * 0.001)
|
||||
self.power = int(((msg.data[5] * 256) + msg.data[4]) * 0.1)
|
||||
logging.debug("Voltage: %2.2fV, Current: %2.2fA, Power: %2.2fW", self.voltage, self.current, self.power)
|
||||
|
||||
if msg.arbitration_id == 0x05:
|
||||
self.soc = (msg.data[1] * 256 + msg.data[0]) * 0.01
|
||||
self.soh = (msg.data[3] * 256 + msg.data[2]) * 0.01
|
||||
self.AhToEmpty = (msg.data[5] * 256 + msg.data[4]) * 0.1
|
||||
self.capacity = (msg.data[7] * 256 + msg.data[6]) * 0.1
|
||||
logging.debug("SOC: %2.2f%%, SOH: %2.2f%%, Ah Remain: %2.2fAh, Capacity: %2.2fAh", self.soc, self.soh, self.AhToEmpty, self.capacity)
|
||||
|
||||
elif msg.arbitration_id == 0x06:
|
||||
self.maxChargeVoltage = ((msg.data[1] * 256) + msg.data[0]) * 0.1
|
||||
self.maxChargeCurrent = ((msg.data[3] * 256) + msg.data[2]) * 0.1
|
||||
#self.maxDischargeVoltage = ((msg.data[5] * 256) + msg.data[4]) * 0.01
|
||||
self.maxDischargeCurrent = ((msg.data[7] * 256) + msg.data[6]) * 0.1
|
||||
logging.debug("MaxChargeVoltage (CVL) %2.2fA, MaxChargeCurrent (CCL) %2.2fV, MaxDischargeCurrent %2.2fA", self.maxChargeCurrent, self.maxChargeVoltage, self.maxDischargeCurrent)
|
||||
|
||||
#elif msg.arbitration_id == 0x00111900:
|
||||
# logging.debug("Ah 0x00111900 received")
|
||||
|
||||
# message never arrived, batrium fw bug?
|
||||
|
||||
#self.AhToFull = round((struct.unpack('>f', bytearray([msg.data[3], msg.data[2], msg.data[1], msg.data[0]])))[0] * 0.001, 2)
|
||||
#self.AhToEmpty = round((struct.unpack('>f', bytearray([msg.data[7], msg.data[6], msg.data[5], msg.data[4]])))[0] * 0.001, 2)
|
||||
#self.capacity = self.AhToEmpty + self.AhToFull
|
||||
#logging.debug("Ah to Full: %2.2f", self.AhToFull)
|
||||
#logging.debug("Ah to Empty: %2.2f", self.AhToEmpty)
|
||||
#logging.debug("Capacity: %2.2f", self.capacity)
|
||||
|
||||
#elif msg.arbitration_id == 0x00140100:
|
||||
# self.alarm_high_temperature = (msg.data[2] >> 3) & 1
|
||||
# logging.debug("High Temp Alarm: %d", self.alarm_high_temperature)
|
||||
# self.alarm_high_voltage = (msg.data[2] >> 1) & 1
|
||||
# logging.debug("High Voltage Alarm: %d", self.alarm_high_voltage)
|
||||
# self.alarm_low_temperature = (msg.data[2] >> 2) & 1
|
||||
# logging.debug("Low Temperature Alarm: %d", self.alarm_low_temperature)
|
||||
# self.alarm_low_voltage = (msg.data[2] >> 0) & 1
|
||||
# logging.debug("Low Voltage Alarm: %d", self.alarm_low_voltage)
|
||||
# self.alarm_high_charge_current = (msg.data[3] >> 3) & 1
|
||||
# logging.debug("High Charge Current Alarm: %d", self.alarm_high_charge_current)
|
||||
# self.alarm_high_discharge_current = (msg.data[3] >> 4) & 1
|
||||
# logging.debug("High Discharge Current Alarm: %d", self.alarm_high_discharge_current)
|
||||
|
||||
#elif msg.arbitration_id == 0x00111800:
|
||||
# self.TimeToEmpty = ((msg.data[5] * 256) + msg.data[4] * 60)
|
||||
# logging.debug("Time to empty: %dmin", self.TimeToEmpty)
|
||||
|
||||
#elif msg.arbitration_id == 0x00111700:
|
||||
# self.TimeToFull = ((msg.data[5] * 256) + msg.data[4] * 60)
|
||||
# logging.debug("Time to full: %dmin", self.TimeToFull)
|
||||
|
||||
#elif ((msg.arbitration_id >= 0x001D1101) and (msg.arbitration_id <= 0x001D11F9)):
|
||||
# cell_id = msg.arbitration_id & 0xff
|
||||
#logging.debug("Cell ID: %d, Voltage_min: %2.2f, Voltage_max: %2.2f, Temperature: %d, Bypass_Temperature: %d, BypassPWM: %d", cell_id, ((msg.data[1] * 256) + msg.data[0]) * 0.001, ((msg.data[3] * 256) + msg.data[2]) * 0.001, msg.data[4] - 40, msg.data[5] - 40, msg.data[6])
|
||||
# self.cellVoltages_min[cell_id] = ((msg.data[1] * 256) + msg.data[0]) * 0.001
|
||||
# self.cellVoltages_max[cell_id] = ((msg.data[3] * 256) + msg.data[2]) * 0.001
|
||||
# self.cellTemperatures[cell_id] = msg.data[4] - 40
|
||||
# self.cellBypassTemperatures[cell_id] = msg.data[5] - 40
|
||||
# self.cellBypassPWM[cell_id] = msg.data[6]
|
||||
# self.numberOfModules = len(self.cellVoltages_min.keys())
|
||||
|
||||
def prnt(self):
|
||||
print("SOC: %2d, I: %2.2fA, U: %2.2fV, T:%2.1fC" % (self.soc, self.current, self.voltage, self.maxCellTemperature))
|
||||
print("MinCellVolt: %1.2f, MaxCellVolt: %1.2f CellVoltDelta: %1.2f" % (self.minCellVoltage, self.maxCellVoltage, self.maxCellVoltage-self.minCellVoltage))
|
||||
print("MaxChargeCurrent (CCL): %4.1f A MaxChargeVoltage (CVL): %2.2f V" % (self.maxChargeCurrent, self.maxChargeVoltage))
|
||||
print("Number of modules found: %1.0f" % (self.numberOfModules))
|
||||
|
||||
# === All code below is to simply run it from the commandline for debugging purposes ===
|
||||
def main():
|
||||
logging.basicConfig( format='%(asctime)s,%(msecs)d %(name)s %(levelname)s %(message)s',
|
||||
datefmt='%Y-%m-%d %H:%M:%S',
|
||||
level=logging.DEBUG,
|
||||
stream=sys.stdout,
|
||||
)
|
||||
|
||||
logging.info('Starting dbus_batrium_native listener')
|
||||
|
||||
logger = can.Logger('logfile.asc')
|
||||
|
||||
bat = BatriumBattery(connection='can0')
|
||||
|
||||
listeners = [
|
||||
logger, # Regular Listener object
|
||||
bat
|
||||
]
|
||||
|
||||
notifier = can.Notifier(bat._ci, listeners)
|
||||
for msg in bat._ci:
|
||||
if msg.arbitration_id == 0x001:
|
||||
bat.prnt()
|
||||
|
||||
# Clean-up
|
||||
notifier.stop()
|
||||
bat._ci.shutdown()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
|
@ -1,308 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
"""
|
||||
A class to put a battery service on the dbus, according to victron standards, with constantly updating
|
||||
paths.
|
||||
|
||||
"""
|
||||
from gi.repository import GLib
|
||||
import platform
|
||||
import argparse
|
||||
import logging
|
||||
import sys
|
||||
import os
|
||||
import dbus
|
||||
import itertools
|
||||
import math
|
||||
|
||||
from time import time
|
||||
from datetime import datetime
|
||||
import struct
|
||||
from argparse import ArgumentParser
|
||||
|
||||
from batrium import *
|
||||
|
||||
|
||||
# our own packages
|
||||
sys.path.insert(1, os.path.join(os.path.dirname(__file__), '/opt/victronenergy/dbus-systemcalc-py/ext/velib_python'))
|
||||
from vedbus import VeDbusService
|
||||
from ve_utils import exit_on_error
|
||||
from settingsdevice import SettingsDevice
|
||||
|
||||
VERSION = '1.0'
|
||||
|
||||
def handle_changed_setting(setting, oldvalue, newvalue):
|
||||
logging.debug('setting changed, setting: %s, old: %s, new: %s' % (setting, oldvalue, newvalue))
|
||||
|
||||
|
||||
class DbusBatteryService:
|
||||
def __init__(self, servicename, deviceinstance, productname='Batrium BMS', connection='can0'):
|
||||
self.minUpdateDone = 0
|
||||
self.secUpdateDone = 0
|
||||
self.dailyResetDone = 0
|
||||
self.lastUpdated = 0
|
||||
self.cell_balanced = 0
|
||||
self.ChargedEnergy = 0
|
||||
self.DischargedEnergy = 0
|
||||
self._bat = BatriumBattery(connection=connection)
|
||||
self.notifier = can.Notifier(self._bat._ci, [self._bat])
|
||||
|
||||
try:
|
||||
self._dbusservice = VeDbusService(servicename+'.socketcan_'+connection+'_di'+str(deviceinstance))
|
||||
except:
|
||||
exit
|
||||
|
||||
# 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', VERSION + ' 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', 0xB038)
|
||||
self._dbusservice.add_path('/ProductName', productname)
|
||||
self._dbusservice.add_path('/FirmwareVersion', None, writeable=True)
|
||||
self._dbusservice.add_path('/HardwareVersion', None, writeable=True)
|
||||
self._dbusservice.add_path('/Connected', 0)
|
||||
# Create battery specific objects
|
||||
self._dbusservice.add_path('/Status', None, writeable=True)
|
||||
self._dbusservice.add_path('/Dc/0/Temperature', None, writeable=True)
|
||||
self._dbusservice.add_path('/Info/BatteryLowVoltage', None, writeable=True)
|
||||
self._dbusservice.add_path('/Alarms/CellImbalance', None, writeable=True)
|
||||
self._dbusservice.add_path('/Alarms/LowVoltage', None, writeable=True)
|
||||
self._dbusservice.add_path('/Alarms/HighVoltage', None, writeable=True)
|
||||
self._dbusservice.add_path('/Alarms/HighDischargeCurrent', None, writeable=True)
|
||||
self._dbusservice.add_path('/Alarms/HighChargeCurrent', None, writeable=True)
|
||||
self._dbusservice.add_path('/Alarms/LowSoc', None, writeable=True)
|
||||
self._dbusservice.add_path('/Alarms/LowTemperature', None, writeable=True)
|
||||
self._dbusservice.add_path('/Alarms/HighTemperature', None, writeable=True)
|
||||
self._dbusservice.add_path('/Balancing', None, writeable=True)
|
||||
self._dbusservice.add_path('/System/HasTemperature', 1)
|
||||
self._dbusservice.add_path('/System/NrOfBatteries', 1)
|
||||
self._dbusservice.add_path('/System/NrOfModulesOnline', None, writeable=True)
|
||||
self._dbusservice.add_path('/System/NrOfModulesOffline', 0)
|
||||
self._dbusservice.add_path('/System/NrOfModulesBlockingDischarge', 0)
|
||||
self._dbusservice.add_path('/System/NrOfModulesBlockingCharge', 0)
|
||||
self._dbusservice.add_path('/System/NrOfBatteriesBalancing', None, writeable=True)
|
||||
self._dbusservice.add_path('/System/BatteriesSeries', None, writeable=True)
|
||||
self._dbusservice.add_path('/System/MinVoltageCellId', None, writeable=True)
|
||||
self._dbusservice.add_path('/System/MaxVoltageCellId', None, writeable=True)
|
||||
self._dbusservice.add_path("/System/MinTemperatureCellId", None, writeable=True)
|
||||
self._dbusservice.add_path("/System/MaxTemperatureCellId", None, writeable=True)
|
||||
|
||||
self._settings = SettingsDevice(
|
||||
bus=dbus.SystemBus() if (platform.machine() == 'armv7l') else dbus.SessionBus(),
|
||||
supportedSettings={
|
||||
'AvgDischarge': ['/Settings/Batrium/AvgerageDischarge', 0.0,0,0],
|
||||
'TotalAhDrawn': ['/Settings/Batrium/TotalAhDrawn', 0.0,0,0],
|
||||
'TimeLastFull': ['/Settings/Batrium/TimeLastFull', 0.0 ,0,0],
|
||||
'MinCellVoltage': ['/Settings/Batrium/MinCellVoltage', 0.0,0.0,0.0],
|
||||
'MaxCellVoltage': ['/Settings/Batrium/MaxCellVoltage', 0.0,0.0,0.0],
|
||||
'interval': ['/Settings/Batrium/Interval', 200, 200, 200]
|
||||
},
|
||||
eventCallback=handle_changed_setting)
|
||||
|
||||
|
||||
self._summeditems = {
|
||||
'/System/MaxCellVoltage': {'gettext': '%.3F V'},
|
||||
'/System/MinCellVoltage': {'gettext': '%.3F V'},
|
||||
'/Dc/0/Voltage': {'gettext': '%.2F V'},
|
||||
'/Dc/0/Current': {'gettext': '%.2F A'},
|
||||
'/Dc/0/Power': {'gettext': '%.0F W'},
|
||||
'/Soc': {'gettext': '%.0F%%'},
|
||||
'/Soh': {'gettext': '%.0F%%'},
|
||||
'/History/TotalAhDrawn': {'gettext': '%.0F Ah'},
|
||||
'/History/DischargedEnergy': {'gettext': '%.2F kWh'},
|
||||
'/History/ChargedEnergy': {'gettext': '%.2F kWh'},
|
||||
'/History/AverageDischarge': {'gettext': '%.2F kWh'},
|
||||
'/TimeToGo': {'gettext': '%.0F s'},
|
||||
'/ConsumedAmphours': {'gettext': '%.1F Ah'},
|
||||
'/System/MinCellTemperature': {'gettext': '%.1F °C'},
|
||||
'/System/MaxCellTemperature': {'gettext': '%.1F °C'},
|
||||
'/Capacity': {'gettext': '%.1F Ah'},
|
||||
'/InstalledCapacity': {'gettext': '%.1F Ah'},
|
||||
'/Info/MaxChargeVoltage': {'gettext': '%.1F V'},
|
||||
'/Info/MaxChargeCurrent': {'gettext': '%.1F A'},
|
||||
'/Info/MaxDischargeCurrent': {'gettext': '%.1F A'}
|
||||
}
|
||||
|
||||
for path in self._summeditems.keys():
|
||||
self._dbusservice.add_path(path, value=None, gettextcallback=self._gettext)
|
||||
|
||||
self._dbusservice['/History/AverageDischarge'] = self._settings['AvgDischarge']
|
||||
self._dbusservice['/History/TotalAhDrawn'] = self._settings['TotalAhDrawn']
|
||||
self._dbusservice.add_path('/History/TimeSinceLastFullCharge', 0)
|
||||
self._dbusservice.add_path('/History/MinCellVoltage', self._settings['MinCellVoltage'])
|
||||
self._dbusservice.add_path('/History/MaxCellVoltage', self._settings['MaxCellVoltage'])
|
||||
|
||||
self._dbusservice['/Status'] = 0
|
||||
|
||||
logging.info("History cell voltage min: %.3f, max: %.3f, totalAhDrawn: %d",
|
||||
self._settings['MinCellVoltage'], self._settings['MaxCellVoltage'], self._settings['TotalAhDrawn'])
|
||||
|
||||
if self._bat.soc <= 99:
|
||||
self._dbusservice['/Soc'] = self._bat.soc
|
||||
elif self._bat.soc > 100:
|
||||
self._dbusservice['/Soc'] = 100
|
||||
|
||||
GLib.timeout_add( self._settings['interval'], exit_on_error, self._update)
|
||||
|
||||
def _gettext(self, path, value):
|
||||
item = self._summeditems.get(path)
|
||||
if item is not None:
|
||||
return item['gettext'] % value
|
||||
return str(value)
|
||||
|
||||
def __del__(self):
|
||||
self._safe_history()
|
||||
logging.info('Stopping dbus_Batrium')
|
||||
|
||||
def _safe_history(self):
|
||||
logging.debug('Saving history to localsettings')
|
||||
self._settings['AvgDischarge'] = self._dbusservice['/History/AverageDischarge']
|
||||
self._settings['TotalAhDrawn'] = self._dbusservice['/History/TotalAhDrawn']
|
||||
self._settings['MinCellVoltage'] = self._dbusservice['/History/MinCellVoltage']
|
||||
self._settings['MaxCellVoltage'] = self._dbusservice['/History/MaxCellVoltage']
|
||||
|
||||
def _daily_stats(self):
|
||||
if (self._dbusservice['/History/DischargedEnergy'] == 0): return
|
||||
logging.info("Updating stats, SOC: %d, Discharged: %.2f, Charged: %.2f ",self._bat.soc, self._dbusservice['/History/DischargedEnergy'], self._dbusservice['/History/ChargedEnergy'])
|
||||
self._dbusservice['/History/AverageDischarge'] = (6*self._dbusservice['/History/AverageDischarge'] + self._dbusservice['/History/DischargedEnergy'])/7 #rolling week
|
||||
self._dbusservice['/History/ChargedEnergy'] = 0
|
||||
self.ChargedEnergy = 0
|
||||
self._dbusservice['/History/DischargedEnergy'] = 0
|
||||
self.DischargedEnergy = 0
|
||||
dt = datetime.now() - datetime.fromtimestamp( float(self._settings['TimeLastFull']) )
|
||||
|
||||
self.dailyResetDone = datetime.now().day
|
||||
|
||||
def _update(self):
|
||||
#logging.debug("Update CAN %d, Time Now: %d", self._bat.updated, self.lastUpdated)
|
||||
time = datetime.now()
|
||||
self.lastUpdated = time.timestamp()
|
||||
|
||||
if (self._bat.updated != -1 and self.lastUpdated == 0) or ((self.lastUpdated - self._bat.updated) < 10):
|
||||
self._dbusservice['/Connected'] = 1
|
||||
else:
|
||||
self._dbusservice['/Connected'] = 0
|
||||
|
||||
if self._bat.soc <= 99:
|
||||
self._dbusservice['/Soc'] = self._bat.soc
|
||||
elif (self._bat.soc > 99) and (self.cell_balanced):
|
||||
self._dbusservice['/Soc'] = 100
|
||||
|
||||
self._dbusservice['/Soh'] = self._bat.soh
|
||||
|
||||
if self._bat.NumberInBypass != 0:
|
||||
self._dbusservice['/Balancing'] = 1
|
||||
else:
|
||||
self._dbusservice['/Balancing'] = 0
|
||||
|
||||
self._dbusservice['/FirmwareVersion'] = self._bat.sw_version
|
||||
self._dbusservice['/HardwareVersion'] = self._bat.hw_version
|
||||
self._dbusservice['/ConsumedAmphours'] = ((100 - self._dbusservice['/Soc']) / 100) * self._bat.capacity
|
||||
self._dbusservice['/InstalledCapacity'] = self._bat.capacity
|
||||
self._dbusservice['/Capacity'] = self._bat.capacity
|
||||
self._dbusservice['/Dc/0/Current'] = self._bat.current
|
||||
self._dbusservice['/Dc/0/Voltage'] = self._bat.voltage
|
||||
self._dbusservice['/Dc/0/Power'] = self._bat.power
|
||||
self._dbusservice['/Dc/0/Temperature'] = self._bat.maxCellTemperature
|
||||
|
||||
self._dbusservice['/Info/MaxChargeCurrent'] = self._bat.maxChargeCurrent
|
||||
self._dbusservice['/Info/MaxDischargeCurrent'] = self._bat.maxDischargeCurrent
|
||||
|
||||
if self._bat.maxChargeVoltage != 0:
|
||||
self._dbusservice['/Info/MaxChargeVoltage'] = self._bat.maxChargeVoltage
|
||||
|
||||
self._dbusservice['/System/NrOfModulesOnline'] = self._bat.numberOfModules
|
||||
self._dbusservice['/System/NrOfBatteriesBalancing'] = self._bat.NumberInBypass
|
||||
self._dbusservice['/System/BatteriesSeries'] = self._bat.numberOfModules
|
||||
|
||||
#update energy statistics daily at 6:00,
|
||||
if datetime.now().hour == 6 and datetime.now().minute == 0 and datetime.now().day != self.dailyResetDone :
|
||||
self._daily_stats()
|
||||
|
||||
now = datetime.now().time()
|
||||
if now.minute != self.minUpdateDone:
|
||||
self.minUpdateDone = now.minute
|
||||
|
||||
self._dbusservice['/System/MinCellTemperature'] = self._bat.minCellTemperature
|
||||
self._dbusservice['/System/MaxCellTemperature'] = self._bat.maxCellTemperature
|
||||
self._dbusservice['/System/MinTemperatureCellId'] = self._bat.minCellTemperatureId
|
||||
self._dbusservice['/System/MaxTemperatureCellId'] = self._bat.maxCellTemperatureId
|
||||
|
||||
self._dbusservice['/System/MaxCellVoltage'] = self._bat.maxCellVoltage
|
||||
if (self._bat.maxCellVoltage > self._dbusservice['/History/MaxCellVoltage'] ):
|
||||
self._dbusservice['/History/MaxCellVoltage'] = self._bat.maxCellVoltage
|
||||
logging.info("New maximum cell voltage: %f", self._bat.maxCellVoltage)
|
||||
self._dbusservice['/System/MinCellVoltage'] = self._bat.minCellVoltage
|
||||
if (0 < self._bat.minCellVoltage < self._dbusservice['/History/MinCellVoltage'] ):
|
||||
self._dbusservice['/History/MinCellVoltage'] = self._bat.minCellVoltage
|
||||
logging.info("New minimum cell voltage: %f", self._bat.minCellVoltage)
|
||||
|
||||
self._dbusservice['/System/MaxVoltageCellId'] = self._bat.maxCellVoltageId
|
||||
self._dbusservice['/System/MinVoltageCellId'] = self._bat.minCellVoltageId
|
||||
|
||||
if self._bat.NumberInBypass == self._bat.numberOfModules:
|
||||
self.cell_balanced = True
|
||||
elif self._bat.NumberInBypass == 0:
|
||||
self.cell_balanced = False
|
||||
|
||||
self._safe_history()
|
||||
|
||||
if now.second != self.secUpdateDone:
|
||||
self.secUpdateDone = now.second
|
||||
#Workaround because of missing messages of Batrium BMS
|
||||
if self._bat.power > 0:
|
||||
self.ChargedEnergy += (self._bat.power / 3600.0) / 1000
|
||||
if self._bat.power < 0:
|
||||
self.DischargedEnergy += (self._bat.power / 3600.0) / 1000
|
||||
self._dbusservice['/History/ChargedEnergy'] = abs(self.ChargedEnergy)
|
||||
self._dbusservice['/History/DischargedEnergy'] = abs(self.DischargedEnergy)
|
||||
|
||||
return True
|
||||
|
||||
def main():
|
||||
parser = ArgumentParser(description='dbus_batrium', add_help=True)
|
||||
parser.add_argument('-d', '--debug', help='enable debug logging',
|
||||
action='store_true')
|
||||
parser.add_argument('-i', '--interface', help='CAN interface')
|
||||
parser.add_argument('-p', '--print', help='print only')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
logging.basicConfig( format='%(asctime)s,%(msecs)d %(name)s %(levelname)s %(message)s',
|
||||
datefmt='%Y-%m-%d %H:%M:%S',
|
||||
level=(logging.DEBUG if args.debug else logging.INFO),
|
||||
handlers=[
|
||||
logging.FileHandler("%s/current.log" % (os.path.dirname(os.path.realpath(__file__)))),
|
||||
logging.StreamHandler()
|
||||
])
|
||||
|
||||
if not args.interface:
|
||||
logging.info('No CAN interface specified, using default can0')
|
||||
args.interface = 'can0'
|
||||
|
||||
logging.info('Starting dbus_batrium %s on %s ' %
|
||||
(VERSION, args.interface))
|
||||
|
||||
logging.debug('Setup main loop')
|
||||
|
||||
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)
|
||||
|
||||
logging.debug('Setup dbus service')
|
||||
|
||||
battery_output = DbusBatteryService(
|
||||
servicename='com.victronenergy.battery',
|
||||
connection = args.interface,
|
||||
deviceinstance = 0,
|
||||
)
|
||||
|
||||
logging.debug('Connected to dbus, and switching over to GLib.MainLoop() (= event based)')
|
||||
mainloop = GLib.MainLoop()
|
||||
mainloop.run()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
|
@ -1,3 +0,0 @@
|
|||
opkg update
|
||||
opkg install python3-pip
|
||||
pip3 install python-can
|
|
@ -1,3 +0,0 @@
|
|||
#!/bin/sh
|
||||
exec 2>&1
|
||||
exec softlimit -d 100000000 -s 1000000 -a 100000000 python /data/dbus-batrium-native2/dbus_batrium_native2.py
|
|
@ -19,8 +19,8 @@ sys.path.insert(1, os.path.join(os.path.dirname(__file__), '/opt/victronenergy/d
|
|||
from vedbus import VeDbusService
|
||||
|
||||
|
||||
class NodeRedMetergrid-meter:
|
||||
def __init__(self, servicename, deviceinstance, paths, productname='grid-meter', connection='Node RED HTTP JSON service'):
|
||||
class NodeRedGridMeter:
|
||||
def __init__(self, servicename, deviceinstance, paths, productname='Zähler Einspeisung', connection='Node RED HTTP JSON service'):
|
||||
self._dbusservice = VeDbusService("{}.http_{:02d}".format(servicename, deviceinstance))
|
||||
self._paths = paths
|
||||
|
||||
|
@ -68,21 +68,38 @@ class NodeRedMetergrid-meter:
|
|||
|
||||
return int(value)
|
||||
|
||||
# if there is a grid meter disconnect or some other error, set mandatory values to None to stop ESS and set MPs to passthrough
|
||||
def _errorState(self):
|
||||
self._dbusservice['/Connected'] = 0
|
||||
self._dbusservice['/Ac/Power'] = None
|
||||
self._dbusservice['/Ac/Current'] = None
|
||||
self._dbusservice['/Ac/L1/Voltage'] = None
|
||||
self._dbusservice['/Ac/L2/Voltage'] = None
|
||||
self._dbusservice['/Ac/L3/Voltage'] = None
|
||||
self._dbusservice['/Ac/L1/Current'] = None
|
||||
self._dbusservice['/Ac/L2/Current'] = None
|
||||
self._dbusservice['/Ac/L3/Current'] = None
|
||||
self._dbusservice['/Ac/L1/Power'] = None
|
||||
self._dbusservice['/Ac/L2/Power'] = None
|
||||
self._dbusservice['/Ac/L3/Power'] = None
|
||||
return True
|
||||
|
||||
def _getNodeRedData(self):
|
||||
URL = "http://localhost:1880/meters"
|
||||
try:
|
||||
meter_r = requests.get(url = URL, timeout=5)
|
||||
except requests.exceptions.RequestException as e:
|
||||
self._dbusservice['/Connected'] = 0
|
||||
self._errorState()
|
||||
raise ConnectionError("No response from NodeRed - %s" % (URL))
|
||||
|
||||
meter_data = meter_r.json()
|
||||
|
||||
# check for Json
|
||||
if not meter_data:
|
||||
self._dbusservice['/Connected'] = 0
|
||||
self._errorState()
|
||||
raise ValueError("Converting response to JSON failed")
|
||||
|
||||
# if there is not error parsing data, set connected to true
|
||||
self._dbusservice['/Connected'] = 1
|
||||
return meter_data
|
||||
|
||||
|
@ -94,29 +111,29 @@ class NodeRedMetergrid-meter:
|
|||
|
||||
def _update(self):
|
||||
try:
|
||||
#get data from NodeRed 3em
|
||||
#get parsed data from NodeRed fuction
|
||||
meter_data = self._getNodeRedData()
|
||||
|
||||
#send data to DBus
|
||||
self._dbusservice['/Ac/Power'] = meter_data['grid-meter']['total_power'] # positive: consumption, negative: feed into grid
|
||||
self._dbusservice['/Ac/Current'] = meter_data['grid-meter']['total_current']
|
||||
self._dbusservice['/Ac/L1/Voltage'] = meter_data['grid-meter']['l1_voltage']
|
||||
self._dbusservice['/Ac/L2/Voltage'] = meter_data['grid-meter']['l2_voltage']
|
||||
self._dbusservice['/Ac/L3/Voltage'] = meter_data['grid-meter']['l3_voltage']
|
||||
self._dbusservice['/Ac/L1/Current'] = meter_data['grid-meter']['l1_current']
|
||||
self._dbusservice['/Ac/L2/Current'] = meter_data['grid-meter']['l2_current']
|
||||
self._dbusservice['/Ac/L3/Current'] = meter_data['grid-meter']['l3_current']
|
||||
self._dbusservice['/Ac/L1/Power'] = meter_data['grid-meter']['l1_power']
|
||||
self._dbusservice['/Ac/L2/Power'] = meter_data['grid-meter']['l2_power']
|
||||
self._dbusservice['/Ac/L3/Power'] = meter_data['grid-meter']['l3_power']
|
||||
self._dbusservice['/Ac/L1/Energy/Forward'] = meter_data['grid-meter']['l1_import']
|
||||
self._dbusservice['/Ac/L2/Energy/Forward'] = meter_data['grid-meter']['l2_import']
|
||||
self._dbusservice['/Ac/L3/Energy/Forward'] = meter_data['grid-meter']['l3_import']
|
||||
self._dbusservice['/Ac/L1/Energy/Reverse'] = meter_data['grid-meter']['l1_export']
|
||||
self._dbusservice['/Ac/L2/Energy/Reverse'] = meter_data['grid-meter']['l2_export']
|
||||
self._dbusservice['/Ac/L3/Energy/Reverse'] = meter_data['grid-meter']['l3_export']
|
||||
self._dbusservice['/Ac/Energy/Forward'] = meter_data['grid-meter']['total_import']
|
||||
self._dbusservice['/Ac/Energy/Reverse'] = meter_data['grid-meter']['total_export']
|
||||
self._dbusservice['/Ac/Power'] = meter_data['grid_meter']['total_power'] # positive: consumption, negative: feed into grid
|
||||
self._dbusservice['/Ac/Current'] = meter_data['grid_meter']['total_current']
|
||||
self._dbusservice['/Ac/L1/Voltage'] = meter_data['grid_meter']['l1_voltage']
|
||||
self._dbusservice['/Ac/L2/Voltage'] = meter_data['grid_meter']['l2_voltage']
|
||||
self._dbusservice['/Ac/L3/Voltage'] = meter_data['grid_meter']['l3_voltage']
|
||||
self._dbusservice['/Ac/L1/Current'] = meter_data['grid_meter']['l1_current']
|
||||
self._dbusservice['/Ac/L2/Current'] = meter_data['grid_meter']['l2_current']
|
||||
self._dbusservice['/Ac/L3/Current'] = meter_data['grid_meter']['l3_current']
|
||||
self._dbusservice['/Ac/L1/Power'] = meter_data['grid_meter']['l1_power']
|
||||
self._dbusservice['/Ac/L2/Power'] = meter_data['grid_meter']['l2_power']
|
||||
self._dbusservice['/Ac/L3/Power'] = meter_data['grid_meter']['l3_power']
|
||||
self._dbusservice['/Ac/L1/Energy/Forward'] = meter_data['grid_meter']['l1_import']
|
||||
self._dbusservice['/Ac/L2/Energy/Forward'] = meter_data['grid_meter']['l2_import']
|
||||
self._dbusservice['/Ac/L3/Energy/Forward'] = meter_data['grid_meter']['l3_import']
|
||||
self._dbusservice['/Ac/L1/Energy/Reverse'] = meter_data['grid_meter']['l1_export']
|
||||
self._dbusservice['/Ac/L2/Energy/Reverse'] = meter_data['grid_meter']['l2_export']
|
||||
self._dbusservice['/Ac/L3/Energy/Reverse'] = meter_data['grid_meter']['l3_export']
|
||||
self._dbusservice['/Ac/Energy/Forward'] = meter_data['grid_meter']['total_import']
|
||||
self._dbusservice['/Ac/Energy/Reverse'] = meter_data['grid_meter']['total_export']
|
||||
|
||||
#logging
|
||||
logging.debug("House Consumption (/Ac/Power): %s" % (self._dbusservice['/Ac/Power']))
|
||||
|
@ -125,14 +142,17 @@ class NodeRedMetergrid-meter:
|
|||
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
|
||||
if self._dbusservice['/Connected'] == 1 :
|
||||
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()
|
||||
self._lastUpdate = time.time()
|
||||
|
||||
except Exception as e:
|
||||
self._errorState()
|
||||
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
|
||||
|
@ -141,18 +161,17 @@ class NodeRedMetergrid-meter:
|
|||
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.INFO,
|
||||
handlers=[
|
||||
logging.FileHandler("%s/current.log" % (os.path.dirname(os.path.realpath(__file__)))),
|
||||
logging.StreamHandler()
|
||||
])
|
||||
#handlers=[
|
||||
# logging.FileHandler("%s/current.log" % (os.path.dirname(os.path.realpath(__file__)))),
|
||||
# logging.StreamHandler()
|
||||
#]
|
||||
)
|
||||
|
||||
try:
|
||||
logging.info("Start");
|
||||
|
@ -168,7 +187,7 @@ def main():
|
|||
_v = lambda p, v: (str(round(v, 1)) + ' V')
|
||||
|
||||
#start our main-service
|
||||
pvac_output = NodeRedMetergrid-meter(
|
||||
pvac_output = NodeRedGridMeter(
|
||||
servicename='com.victronenergy.grid',
|
||||
deviceinstance=40,
|
||||
paths={
|
||||
|
|
|
@ -38,7 +38,7 @@ class NodeRedPVinverter:
|
|||
self._dbusservice.add_path('/CustomName', productname)
|
||||
self._dbusservice.add_path('/FirmwareVersion', 1.0)
|
||||
self._dbusservice.add_path('/HardwareVersion', 0)
|
||||
self._dbusservice.add_path('/Connected', 1)
|
||||
self._dbusservice.add_path('/Connected', 0)
|
||||
self._dbusservice.add_path('/Position', 0)
|
||||
self._dbusservice.add_path('/Serial', 12345678)
|
||||
self._dbusservice.add_path('/UpdateIndex', 0)
|
||||
|
@ -66,21 +66,35 @@ class NodeRedPVinverter:
|
|||
|
||||
return int(value)
|
||||
|
||||
# if there is a pv meter disconnect or some other error, set mandatory values to None to tidy up vrm view
|
||||
def _errorState(self):
|
||||
self._dbusservice['/Connected'] = 0
|
||||
self._dbusservice['/Ac/Voltage'] = None
|
||||
self._dbusservice['/Ac/L1/Voltage'] = None
|
||||
self._dbusservice['/Ac/Current'] = None
|
||||
self._dbusservice['/Ac/L1/Current'] = None
|
||||
self._dbusservice['/Ac/Power'] = None
|
||||
self._dbusservice['/Ac/L1/Power'] = None
|
||||
return True
|
||||
|
||||
def _getNodeRedData(self):
|
||||
URL = "http://localhost:1880/meters"
|
||||
meter_r = requests.get(url = URL)
|
||||
|
||||
# check for response
|
||||
if not meter_r:
|
||||
self._errorState()
|
||||
raise ConnectionError("No response from NodeRed - %s" % (URL))
|
||||
|
||||
meter_data = meter_r.json()
|
||||
|
||||
# check for Json
|
||||
if not meter_data:
|
||||
self._errorState()
|
||||
raise ValueError("Converting response to JSON failed")
|
||||
|
||||
|
||||
# if there is no error parsing data, set connected to true
|
||||
self._dbusservice['/Connected'] = 1
|
||||
return meter_data
|
||||
|
||||
|
||||
|
@ -91,7 +105,7 @@ class NodeRedPVinverter:
|
|||
|
||||
def _update(self):
|
||||
try:
|
||||
#get data from NodeRed 3em
|
||||
#get data from NodeRed
|
||||
meter_data = self._getNodeRedData()
|
||||
|
||||
#send data to DBus
|
||||
|
@ -118,6 +132,7 @@ class NodeRedPVinverter:
|
|||
#update lastupdate vars
|
||||
self._lastUpdate = time.time()
|
||||
except Exception as e:
|
||||
self._errorState()
|
||||
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
|
||||
|
@ -134,10 +149,11 @@ def main():
|
|||
logging.basicConfig( format='%(asctime)s,%(msecs)d %(name)s %(levelname)s %(message)s',
|
||||
datefmt='%Y-%m-%d %H:%M:%S',
|
||||
level=logging.INFO,
|
||||
handlers=[
|
||||
logging.FileHandler("%s/current.log" % (os.path.dirname(os.path.realpath(__file__)))),
|
||||
logging.StreamHandler()
|
||||
])
|
||||
#handlers=[
|
||||
# logging.FileHandler("%s/current.log" % (os.path.dirname(os.path.realpath(__file__)))),
|
||||
# logging.StreamHandler()
|
||||
#])
|
||||
)
|
||||
|
||||
try:
|
||||
logging.info("Start");
|
||||
|
@ -158,20 +174,20 @@ def main():
|
|||
deviceinstance=46,
|
||||
paths={
|
||||
'/Ac/Energy/Forward': {'initial': None, 'textformat': _kwh}, # energy produced by pv inverter
|
||||
'/Ac/Power': {'initial': 0, 'textformat': _w},
|
||||
'/Ac/Power': {'initial': None, 'textformat': _w},
|
||||
|
||||
'/Ac/Current': {'initial': 0, 'textformat': _a},
|
||||
'/Ac/Voltage': {'initial': 0, 'textformat': _v},
|
||||
'/Ac/Current': {'initial': None, 'textformat': _a},
|
||||
'/Ac/Voltage': {'initial': None, '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/Voltage': {'initial': None, 'textformat': _v},
|
||||
'/Ac/L2/Voltage': {'initial': None, 'textformat': _v},
|
||||
'/Ac/L3/Voltage': {'initial': None, 'textformat': _v},
|
||||
'/Ac/L1/Current': {'initial': None, 'textformat': _a},
|
||||
'/Ac/L2/Current': {'initial': None, 'textformat': _a},
|
||||
'/Ac/L3/Current': {'initial': None, 'textformat': _a},
|
||||
'/Ac/L1/Power': {'initial': None, 'textformat': _w},
|
||||
'/Ac/L2/Power': {'initial': None, 'textformat': _w},
|
||||
'/Ac/L3/Power': {'initial': None, 'textformat': _w},
|
||||
'/Ac/L1/Energy/Forward': {'initial': None, 'textformat': _kwh},
|
||||
'/Ac/L2/Energy/Forward': {'initial': None, 'textformat': _kwh},
|
||||
'/Ac/L3/Energy/Forward': {'initial': None, 'textformat': _kwh},
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,418 @@
|
|||
[
|
||||
{
|
||||
"id": "8d1576fc0be96b4a",
|
||||
"type": "http request",
|
||||
"z": "154ae691bb56f6e7",
|
||||
"name": "http req. Tibber Pulse SML",
|
||||
"method": "GET",
|
||||
"ret": "bin",
|
||||
"paytoqs": "ignore",
|
||||
"url": "http://10.1.0.11/data.json?node_id=1",
|
||||
"tls": "",
|
||||
"persist": false,
|
||||
"proxy": "",
|
||||
"insecureHTTPParser": false,
|
||||
"authType": "basic",
|
||||
"senderr": false,
|
||||
"headers": [],
|
||||
"credentials": {},
|
||||
"x": 420,
|
||||
"y": 300,
|
||||
"wires": [
|
||||
[
|
||||
"a82fbeb90cddaba3"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "66b56bf77c1e2db4",
|
||||
"type": "inject",
|
||||
"z": "154ae691bb56f6e7",
|
||||
"name": "Request every 3s",
|
||||
"props": [
|
||||
{
|
||||
"p": "payload"
|
||||
},
|
||||
{
|
||||
"p": "topic",
|
||||
"vt": "str"
|
||||
}
|
||||
],
|
||||
"repeat": "3",
|
||||
"crontab": "",
|
||||
"once": false,
|
||||
"onceDelay": 0.1,
|
||||
"topic": "",
|
||||
"payload": "",
|
||||
"payloadType": "date",
|
||||
"x": 130,
|
||||
"y": 300,
|
||||
"wires": [
|
||||
[
|
||||
"8d1576fc0be96b4a"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "af078be60e1168a2",
|
||||
"type": "smartmeter",
|
||||
"z": "154ae691bb56f6e7",
|
||||
"name": "parse SML",
|
||||
"datasource": "2cc5d556b9271b80",
|
||||
"protocol": "SmlProtocol",
|
||||
"transport": "LocalFileTransport",
|
||||
"requestInterval": "99999",
|
||||
"d0WakeupCharacters": "",
|
||||
"d0SignOnMessage": "",
|
||||
"d0BaudrateChangeoverOverwrite": "",
|
||||
"protocolSmlIgnoreInvalidCRC": false,
|
||||
"debugging": false,
|
||||
"x": 870,
|
||||
"y": 300,
|
||||
"wires": [
|
||||
[
|
||||
"bcb656baff6e2422",
|
||||
"90e11ee613d8a8d8"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "a82fbeb90cddaba3",
|
||||
"type": "file",
|
||||
"z": "154ae691bb56f6e7",
|
||||
"name": "",
|
||||
"filename": "/tmp/tibber_sml.data",
|
||||
"filenameType": "str",
|
||||
"appendNewline": false,
|
||||
"createDir": false,
|
||||
"overwriteFile": "true",
|
||||
"encoding": "none",
|
||||
"x": 680,
|
||||
"y": 300,
|
||||
"wires": [
|
||||
[
|
||||
"af078be60e1168a2"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "bcb656baff6e2422",
|
||||
"type": "function",
|
||||
"z": "154ae691bb56f6e7",
|
||||
"name": "filter 1.8.0",
|
||||
"func": "msg.payload = msg.payload[\"1-0:1.8.0*255\"].values[0].value;\nreturn msg;",
|
||||
"outputs": 1,
|
||||
"noerr": 0,
|
||||
"initialize": "",
|
||||
"finalize": "",
|
||||
"libs": [],
|
||||
"x": 800,
|
||||
"y": 380,
|
||||
"wires": [
|
||||
[
|
||||
"99439952c9f0698e"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "90e11ee613d8a8d8",
|
||||
"type": "function",
|
||||
"z": "154ae691bb56f6e7",
|
||||
"name": "filter 16.7.0",
|
||||
"func": "msg.payload = msg.payload[\"1-0:16.7.0*255\"].values[0].value;\nreturn msg;",
|
||||
"outputs": 1,
|
||||
"noerr": 0,
|
||||
"initialize": "",
|
||||
"finalize": "",
|
||||
"libs": [],
|
||||
"x": 810,
|
||||
"y": 420,
|
||||
"wires": [
|
||||
[
|
||||
"3416a1bf3d6e4db5"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "609c6f6c087622eb",
|
||||
"type": "influxdb out",
|
||||
"z": "154ae691bb56f6e7",
|
||||
"influxdb": "dee207f7.f8e2b8",
|
||||
"name": "",
|
||||
"measurement": "evu_1_8_0",
|
||||
"precision": "",
|
||||
"retentionPolicy": "",
|
||||
"database": "database",
|
||||
"precisionV18FluxV20": "ms",
|
||||
"retentionPolicyV18Flux": "",
|
||||
"org": "Klotz",
|
||||
"bucket": "energy_meter",
|
||||
"x": 1300,
|
||||
"y": 380,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
"id": "99439952c9f0698e",
|
||||
"type": "delay",
|
||||
"z": "154ae691bb56f6e7",
|
||||
"name": "",
|
||||
"pauseType": "rate",
|
||||
"timeout": "5",
|
||||
"timeoutUnits": "seconds",
|
||||
"rate": "1",
|
||||
"nbRateUnits": "60",
|
||||
"rateUnits": "second",
|
||||
"randomFirst": "1",
|
||||
"randomLast": "5",
|
||||
"randomUnits": "seconds",
|
||||
"drop": true,
|
||||
"allowrate": false,
|
||||
"outputs": 1,
|
||||
"x": 1030,
|
||||
"y": 380,
|
||||
"wires": [
|
||||
[
|
||||
"609c6f6c087622eb"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "cf1e5fe6d9e2b8cb",
|
||||
"type": "influxdb out",
|
||||
"z": "154ae691bb56f6e7",
|
||||
"influxdb": "dee207f7.f8e2b8",
|
||||
"name": "",
|
||||
"measurement": "evu_16_7_0",
|
||||
"precision": "",
|
||||
"retentionPolicy": "",
|
||||
"database": "database",
|
||||
"precisionV18FluxV20": "ms",
|
||||
"retentionPolicyV18Flux": "",
|
||||
"org": "Klotz",
|
||||
"bucket": "energy_meter",
|
||||
"x": 1310,
|
||||
"y": 420,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
"id": "7d112572b3df1bc9",
|
||||
"type": "http request",
|
||||
"z": "154ae691bb56f6e7",
|
||||
"name": "http req. Tibber Pulse Metric",
|
||||
"method": "GET",
|
||||
"ret": "obj",
|
||||
"paytoqs": "ignore",
|
||||
"url": "http://10.1.0.11/metrics.json?node_id=1",
|
||||
"tls": "",
|
||||
"persist": false,
|
||||
"proxy": "",
|
||||
"insecureHTTPParser": false,
|
||||
"authType": "basic",
|
||||
"senderr": false,
|
||||
"headers": [],
|
||||
"credentials": {},
|
||||
"x": 420,
|
||||
"y": 600,
|
||||
"wires": [
|
||||
[
|
||||
"9de9ed09efa9fc09",
|
||||
"dc55cf9772b06948",
|
||||
"128089ad6b44d136"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "1a000ee3584d95a9",
|
||||
"type": "inject",
|
||||
"z": "154ae691bb56f6e7",
|
||||
"name": "Request every 1m",
|
||||
"props": [
|
||||
{
|
||||
"p": "payload"
|
||||
},
|
||||
{
|
||||
"p": "topic",
|
||||
"vt": "str"
|
||||
}
|
||||
],
|
||||
"repeat": "60",
|
||||
"crontab": "",
|
||||
"once": false,
|
||||
"onceDelay": 0.1,
|
||||
"topic": "",
|
||||
"payload": "",
|
||||
"payloadType": "date",
|
||||
"x": 140,
|
||||
"y": 600,
|
||||
"wires": [
|
||||
[
|
||||
"7d112572b3df1bc9"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "9de9ed09efa9fc09",
|
||||
"type": "function",
|
||||
"z": "154ae691bb56f6e7",
|
||||
"name": "battery voltage",
|
||||
"func": "msg.payload = msg.payload.node_status.node_battery_voltage;\nreturn msg;",
|
||||
"outputs": 1,
|
||||
"noerr": 0,
|
||||
"initialize": "",
|
||||
"finalize": "",
|
||||
"libs": [],
|
||||
"x": 680,
|
||||
"y": 600,
|
||||
"wires": [
|
||||
[
|
||||
"3418ac83e0994fd7"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "dc55cf9772b06948",
|
||||
"type": "function",
|
||||
"z": "154ae691bb56f6e7",
|
||||
"name": "temperature",
|
||||
"func": "msg.payload = msg.payload.node_status.node_temperature;\nreturn msg;",
|
||||
"outputs": 1,
|
||||
"noerr": 0,
|
||||
"initialize": "",
|
||||
"finalize": "",
|
||||
"libs": [],
|
||||
"x": 670,
|
||||
"y": 640,
|
||||
"wires": [
|
||||
[
|
||||
"edd18b7ad90a3bb5"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "128089ad6b44d136",
|
||||
"type": "function",
|
||||
"z": "154ae691bb56f6e7",
|
||||
"name": "RSSI",
|
||||
"func": "msg.payload = msg.payload.node_status.node_avg_rssi;\nreturn msg;",
|
||||
"outputs": 1,
|
||||
"noerr": 0,
|
||||
"initialize": "",
|
||||
"finalize": "",
|
||||
"libs": [],
|
||||
"x": 650,
|
||||
"y": 680,
|
||||
"wires": [
|
||||
[
|
||||
"0af0bfae91209540"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "3418ac83e0994fd7",
|
||||
"type": "influxdb out",
|
||||
"z": "154ae691bb56f6e7",
|
||||
"influxdb": "dee207f7.f8e2b8",
|
||||
"name": "",
|
||||
"measurement": "node_battery_voltage",
|
||||
"precision": "",
|
||||
"retentionPolicy": "",
|
||||
"database": "database",
|
||||
"precisionV18FluxV20": "ms",
|
||||
"retentionPolicyV18Flux": "",
|
||||
"org": "Klotz",
|
||||
"bucket": "tibber",
|
||||
"x": 1080,
|
||||
"y": 600,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
"id": "edd18b7ad90a3bb5",
|
||||
"type": "influxdb out",
|
||||
"z": "154ae691bb56f6e7",
|
||||
"influxdb": "dee207f7.f8e2b8",
|
||||
"name": "",
|
||||
"measurement": "node_temperature",
|
||||
"precision": "",
|
||||
"retentionPolicy": "",
|
||||
"database": "database",
|
||||
"precisionV18FluxV20": "ms",
|
||||
"retentionPolicyV18Flux": "",
|
||||
"org": "Klotz",
|
||||
"bucket": "tibber",
|
||||
"x": 1070,
|
||||
"y": 640,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
"id": "0af0bfae91209540",
|
||||
"type": "influxdb out",
|
||||
"z": "154ae691bb56f6e7",
|
||||
"influxdb": "dee207f7.f8e2b8",
|
||||
"name": "",
|
||||
"measurement": "node_rssi",
|
||||
"precision": "",
|
||||
"retentionPolicy": "",
|
||||
"database": "database",
|
||||
"precisionV18FluxV20": "ms",
|
||||
"retentionPolicyV18Flux": "",
|
||||
"org": "Klotz",
|
||||
"bucket": "tibber",
|
||||
"x": 1040,
|
||||
"y": 680,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
"id": "3416a1bf3d6e4db5",
|
||||
"type": "delay",
|
||||
"z": "154ae691bb56f6e7",
|
||||
"name": "",
|
||||
"pauseType": "rate",
|
||||
"timeout": "5",
|
||||
"timeoutUnits": "seconds",
|
||||
"rate": "1",
|
||||
"nbRateUnits": "3",
|
||||
"rateUnits": "second",
|
||||
"randomFirst": "1",
|
||||
"randomLast": "5",
|
||||
"randomUnits": "seconds",
|
||||
"drop": true,
|
||||
"allowrate": false,
|
||||
"outputs": 1,
|
||||
"x": 1020,
|
||||
"y": 420,
|
||||
"wires": [
|
||||
[
|
||||
"cf1e5fe6d9e2b8cb"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "2cc5d556b9271b80",
|
||||
"type": "smartmeter-connection",
|
||||
"sourcetype": "file",
|
||||
"serialport": "/dev/null",
|
||||
"serialbaud": "9600",
|
||||
"databits": "8",
|
||||
"parity": "none",
|
||||
"stopbits": "1",
|
||||
"httphost": "localhost",
|
||||
"httpport": "80",
|
||||
"tcphost": "localhost",
|
||||
"tcpport": "502",
|
||||
"filepath": "/tmp/tibber_sml.data"
|
||||
},
|
||||
{
|
||||
"id": "dee207f7.f8e2b8",
|
||||
"type": "influxdb",
|
||||
"hostname": "127.0.0.1",
|
||||
"port": "8086",
|
||||
"protocol": "http",
|
||||
"database": "database",
|
||||
"name": "Influx Devil",
|
||||
"usetls": false,
|
||||
"tls": "",
|
||||
"influxdbVersion": "2.0",
|
||||
"url": "http://10.1.0.36:8086",
|
||||
"rejectUnauthorized": false
|
||||
}
|
||||
]
|
Loading…
Reference in New Issue