More data
This commit is contained in:
3 files changed
+97
No files matched your search
@@ -50,6 +50,22 @@ Register addresses were validated by probing a SE10K-RWB48BFN4 (fw 0004.0025.001
|
|||||||
with one battery and one meter. Blocks that fail to read (flaky gateway) are
|
with one battery and one meter. Blocks that fail to read (flaky gateway) are
|
||||||
skipped for that cycle; the fields are simply absent from the point.
|
skipped for that cycle; the fields are simply absent from the point.
|
||||||
|
|
||||||
|
### Grafana dashboard
|
||||||
|
|
||||||
|
Import `grafana_dashboard.json` (Dashboards -> New -> Import). Select your
|
||||||
|
InfluxDB datasource when prompted (e.g. `InfluxDB-HJK`); the `$serial` variable
|
||||||
|
is filled from the `serialnumber` tag. InfluxQL only, InfluxDB 1.x.
|
||||||
|
|
||||||
|
The daemon also writes derived flow fields (`house_load`, `grid_import`,
|
||||||
|
`grid_export`, `battery_charge`, `battery_discharge`, `solar_to_house`) so the
|
||||||
|
dashboard's consumption-source panels are plain `mean()`/`integral()` queries.
|
||||||
|
|
||||||
|
**Sign calibration:** `meter1_power` positive = import and `battery1_power`
|
||||||
|
positive = discharge are assumptions observed once; verify on the first sunny
|
||||||
|
day (export while surplus / charge while battery fills). If inverted, flip
|
||||||
|
`METER_IMPORT_POSITIVE` / `BATTERY_DISCHARGE_POSITIVE` in
|
||||||
|
`solaredge_modbus_mqtt/__init__.py` and restart - all derived fields follow.
|
||||||
|
|
||||||
|
|
||||||
### Uninstall
|
### Uninstall
|
||||||
|
|
||||||
|
|||||||
@@ -86,6 +86,12 @@ storage_mode_map = [
|
|||||||
"Remote Control"
|
"Remote Control"
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# Observed on the probed system: meter1_power positive = grid import,
|
||||||
|
# battery1_power positive = discharge. If day-one graphs disagree, flip here -
|
||||||
|
# all derived flow fields (house_load, grid_import, ...) follow.
|
||||||
|
METER_IMPORT_POSITIVE = True
|
||||||
|
BATTERY_DISCHARGE_POSITIVE = True
|
||||||
|
|
||||||
|
|
||||||
class MyInverter:
|
class MyInverter:
|
||||||
|
|
||||||
@@ -403,6 +409,43 @@ class SolarData:
|
|||||||
def __init__(self, data):
|
def __init__(self, data):
|
||||||
self.rawData = data
|
self.rawData = data
|
||||||
self.data = self._parseData(data)
|
self.data = self._parseData(data)
|
||||||
|
self.data.update(self._flows(self.data))
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _flows(d):
|
||||||
|
'''Decompose the house supply into grid/battery/solar components.
|
||||||
|
|
||||||
|
Computed at ingest because all fields land in one InfluxDB point and
|
||||||
|
InfluxQL cannot clip series (max(x, 0)) or split them afterwards -
|
||||||
|
dashboard queries stay plain mean()/integral().
|
||||||
|
'''
|
||||||
|
pv = d.get('power_ac')
|
||||||
|
grid = d.get('meter1_power')
|
||||||
|
batt = d.get('battery1_power')
|
||||||
|
if pv is None or grid is None or batt is None:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
if not METER_IMPORT_POSITIVE:
|
||||||
|
grid = -grid
|
||||||
|
if not BATTERY_DISCHARGE_POSITIVE:
|
||||||
|
batt = -batt
|
||||||
|
|
||||||
|
load = pv + grid + batt
|
||||||
|
grid_import = max(grid, 0.0)
|
||||||
|
grid_export = max(-grid, 0.0)
|
||||||
|
battery_discharge = max(batt, 0.0)
|
||||||
|
battery_charge = max(-batt, 0.0)
|
||||||
|
solar_to_house = max(load - grid_import - battery_discharge, 0.0)
|
||||||
|
|
||||||
|
return {
|
||||||
|
# + 0.0 normalizes -0.0 from max(-x, 0.0)
|
||||||
|
'house_load': round(max(load, 0.0), 4) + 0.0,
|
||||||
|
'grid_import': round(grid_import, 4) + 0.0,
|
||||||
|
'grid_export': round(grid_export, 4) + 0.0,
|
||||||
|
'battery_discharge': round(battery_discharge, 4) + 0.0,
|
||||||
|
'battery_charge': round(battery_charge, 4) + 0.0,
|
||||||
|
'solar_to_house': round(solar_to_house, 4) + 0.0,
|
||||||
|
}
|
||||||
|
|
||||||
def _parseData(self, data):
|
def _parseData(self, data):
|
||||||
result = {}
|
result = {}
|
||||||
|
|||||||
@@ -185,6 +185,44 @@ class TestSolarData(unittest.TestCase):
|
|||||||
self.assertNotIn('storage_control_mode', solarData.data)
|
self.assertNotIn('storage_control_mode', solarData.data)
|
||||||
self.assertAlmostEqual(597.0, solarData.data['power_ac'])
|
self.assertAlmostEqual(597.0, solarData.data['power_ac'])
|
||||||
|
|
||||||
|
def testFlowsImport(self):
|
||||||
|
# probed live values: PV 861.2 W, grid +689.5 (import), battery idle
|
||||||
|
solarData = SolarData(probe_data(power_ac=8612, m1_power=6895, b1_power=0.0))
|
||||||
|
|
||||||
|
self.assertAlmostEqual(1550.7, solarData.data['house_load'])
|
||||||
|
self.assertAlmostEqual(689.5, solarData.data['grid_import'])
|
||||||
|
self.assertAlmostEqual(0.0, solarData.data['grid_export'])
|
||||||
|
self.assertAlmostEqual(0.0, solarData.data['battery_charge'])
|
||||||
|
self.assertAlmostEqual(0.0, solarData.data['battery_discharge'])
|
||||||
|
self.assertAlmostEqual(861.2, solarData.data['solar_to_house'])
|
||||||
|
|
||||||
|
def testFlowsSurplus(self):
|
||||||
|
# PV 5000, exporting 2000, charging battery with 1000
|
||||||
|
solarData = SolarData(probe_data(power_ac=50000, m1_power=-20000, b1_power=-1000.0))
|
||||||
|
|
||||||
|
self.assertAlmostEqual(2000.0, solarData.data['house_load'])
|
||||||
|
self.assertAlmostEqual(0.0, solarData.data['grid_import'])
|
||||||
|
self.assertAlmostEqual(2000.0, solarData.data['grid_export'])
|
||||||
|
self.assertAlmostEqual(1000.0, solarData.data['battery_charge'])
|
||||||
|
self.assertAlmostEqual(0.0, solarData.data['battery_discharge'])
|
||||||
|
self.assertAlmostEqual(2000.0, solarData.data['solar_to_house'])
|
||||||
|
|
||||||
|
def testFlowsNightDischarge(self):
|
||||||
|
solarData = SolarData(probe_data(power_ac=0, m1_power=0, b1_power=300.0))
|
||||||
|
|
||||||
|
self.assertAlmostEqual(300.0, solarData.data['house_load'])
|
||||||
|
self.assertAlmostEqual(300.0, solarData.data['battery_discharge'])
|
||||||
|
self.assertAlmostEqual(0.0, solarData.data['solar_to_house'])
|
||||||
|
|
||||||
|
def testFlowsSkippedWhenBlocksMissing(self):
|
||||||
|
data = probe_data()
|
||||||
|
for key in [k for k in data if k.startswith('m1_')]:
|
||||||
|
del data[key]
|
||||||
|
solarData = SolarData(data)
|
||||||
|
|
||||||
|
self.assertNotIn('house_load', solarData.data)
|
||||||
|
self.assertNotIn('grid_import', solarData.data)
|
||||||
|
|
||||||
def testLineProtocol(self):
|
def testLineProtocol(self):
|
||||||
solarData = SolarData(probe_data())
|
solarData = SolarData(probe_data())
|
||||||
line = to_line_protocol(solarData.data)
|
line = to_line_protocol(solarData.data)
|
||||||
|
|||||||
Reference in new issue
Block a user