More data

This commit is contained in:
dj7nt committed 2026-10-10 16:13:58 +02:00
1 parent b168e2da43
commit 4b22f4f034
3 files changed
+97

No files matched your search

+16
View File
@@ -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
+43
View File
@@ -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 = {}
+38
View File
@@ -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)