Feature: Aggregat-Gerät + Energie-Dashboard Sensoren (v1.2.0)
- main.py: AGG_SENSOR_IDS/AGGREGATE_META — Mapping sensor_id → Aggregat-Bucket _compute_aggregates() summiert alle online Geräte nach jedem Poll /api/data liefert jetzt auch "aggregates" Schlüssel - mqtt_publisher.py: publish_aggregates() + _publish_aggregate_discovery() Eigenes HA-Gerät "ShineBridge Gesamt" (device_id: shinebridge_aggregate) MQTT Topic: shinebridge/aggregate/state - index.html: renderAggregates() — "Gesamt"-Sektion oben im Live-Tab Aggregierte Sensoren (alle kompatibel mit HA Energie-Dashboard): PV: total_pv_power, total_ac_power, total_energy_today, total_energy_total Netz (SDM-630): grid_power, grid_import_kwh, grid_export_kwh Batterie (SPH): bat_charge/discharge_power/total, bat_soc (Ø) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -225,6 +225,21 @@ function esc(s) {
|
||||
return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"').replace(/'/g,''');
|
||||
}
|
||||
|
||||
const AGG_META = {
|
||||
total_pv_power: {name:"PV Gesamtleistung", unit:"W", device_class:"power", icon:"mdi:solar-power"},
|
||||
total_ac_power: {name:"AC Gesamtleistung", unit:"W", device_class:"power", icon:"mdi:flash"},
|
||||
total_energy_today: {name:"Energie Heute Gesamt", unit:"kWh", device_class:"energy", icon:"mdi:solar-power"},
|
||||
total_energy_total: {name:"Energie Gesamt", unit:"kWh", device_class:"energy", icon:"mdi:solar-power"},
|
||||
grid_power: {name:"Netzleistung", unit:"W", device_class:"power", icon:"mdi:transmission-tower"},
|
||||
grid_import_kwh: {name:"Netzbezug Gesamt", unit:"kWh", device_class:"energy", icon:"mdi:transmission-tower-import"},
|
||||
grid_export_kwh: {name:"Einspeisung Gesamt", unit:"kWh", device_class:"energy", icon:"mdi:transmission-tower-export"},
|
||||
bat_charge_power: {name:"Batterie Ladeleistung Ges.", unit:"W", device_class:"power", icon:"mdi:battery-plus"},
|
||||
bat_discharge_power: {name:"Batterie Entladeleist. Ges.", unit:"W", device_class:"power", icon:"mdi:battery-minus"},
|
||||
bat_charge_total: {name:"Batterie Ladung Gesamt", unit:"kWh", device_class:"energy", icon:"mdi:battery-plus"},
|
||||
bat_discharge_total: {name:"Batterie Entladung Gesamt", unit:"kWh", device_class:"energy", icon:"mdi:battery-minus"},
|
||||
bat_soc: {name:"Batterie Ladezustand Ø", unit:"%", device_class:"battery", icon:"mdi:battery"},
|
||||
};
|
||||
|
||||
const DC_COLORS = {
|
||||
power: '#f0c040', voltage: '#58a6ff', current: '#ffa657',
|
||||
energy: '#3fb950', temperature: '#f85149', battery: '#bc8cff',
|
||||
@@ -297,19 +312,42 @@ async function refreshData() {
|
||||
const keys = Object.keys(d.inverters || {});
|
||||
document.getElementById("subtitle").textContent =
|
||||
keys.length ? `${keys.length} Gerät${keys.length !== 1 ? "e" : ""}` : "Keine Geräte";
|
||||
renderLive(d.inverters || {});
|
||||
renderLive(d.inverters || {}, d.aggregates || {});
|
||||
} catch(e) {
|
||||
document.getElementById("pill-mqtt").className = "pill err";
|
||||
}
|
||||
}
|
||||
|
||||
function renderLive(inverters) {
|
||||
function renderAggregates(aggregates) {
|
||||
if (!aggregates || !Object.keys(aggregates).length) return '';
|
||||
const cards = Object.entries(AGG_META).map(([id, meta]) => {
|
||||
const val = aggregates[id];
|
||||
if (val === undefined) return '';
|
||||
const dcClass = meta.device_class ? `dc-${meta.device_class}` : '';
|
||||
return `<div class="sensor-card ${dcClass}">
|
||||
<div class="sensor-icon">${ICON_MAP[meta.icon]||"📊"}</div>
|
||||
<div class="sensor-name">${esc(meta.name)}</div>
|
||||
<div class="sensor-value">${fmtVal(val)}<span class="sensor-unit">${esc(meta.unit)}</span></div>
|
||||
</div>`;
|
||||
}).filter(Boolean).join('');
|
||||
if (!cards) return '';
|
||||
return `<div class="inv-section">
|
||||
<div class="inv-header">
|
||||
<div class="inv-title">Gesamt</div>
|
||||
<div class="inv-badge ok">aggregiert</div>
|
||||
</div>
|
||||
<div class="sensor-grid">${cards}</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
function renderLive(inverters, aggregates) {
|
||||
const el = document.getElementById("live-content");
|
||||
if (!Object.keys(inverters).length) {
|
||||
el.innerHTML = '<div class="no-data">Keine Geräte konfiguriert.<br>Bitte im Tab „Geräte" hinzufügen.</div>';
|
||||
return;
|
||||
}
|
||||
el.innerHTML = Object.values(inverters).map(inv => {
|
||||
const aggHtml = renderAggregates(aggregates);
|
||||
el.innerHTML = aggHtml + Object.values(inverters).map(inv => {
|
||||
const ago = inv.last_update ? Math.round(Date.now()/1000 - inv.last_update) + "s" : "—";
|
||||
const cards = (inv.sensors || []).map(s => {
|
||||
const val = inv.values[s.id];
|
||||
|
||||
Reference in New Issue
Block a user