diff --git a/dashboards/bilan_energetique.yaml b/dashboards/bilan_energetique.yaml new file mode 100644 index 0000000..4d4e3c3 --- /dev/null +++ b/dashboards/bilan_energetique.yaml @@ -0,0 +1,205 @@ +views: + # =================================================================== + - title: Bilan + path: bilan + icon: mdi:scale-balance + cards: + - type: vertical-stack + cards: + - type: custom:mushroom-title-card + title: Bilan énergétique + subtitle: Mode ombre — observe et publie, ne pilote rien + + - type: conditional + conditions: + - entity: input_text.evcs_balance_status + state_not: "ok" + card: + type: markdown + content: > + ## ⚠️ Bilan indisponible + + {{ states('input_text.evcs_balance_reason') }} + + Les contrôleurs fonctionnent sur leurs règles historiques. + + # --- LA comparaison qui décidera du câblage --------------------- + - type: markdown + title: Seuil calculé contre seuil fixe + content: > + {% set req = states('input_number.evcs_balance_required_soc') | float(0) %} + + {% set fixe = states('input_number.victron_grid_soc_off') | float(0) %} + + {% set soc = states('sensor.victron_battery_soc') | float(0) %} + + {% set ecart = req - fixe %} + + | | | + |---|---:| + | SOC batterie **maintenant** | **{{ soc | round(0) }} %** | + | Seuil **calculé** par le bilan | **{{ req | round(1) }} %** | + | Seuil **fixe** encore en vigueur | {{ fixe | round(0) }} % | + | Écart | {{ '+' if ecart >= 0 else '' }}{{ ecart | round(1) }} points | + + {% if req > fixe %} + Le seuil fixe est aujourd'hui **trop bas** : il laisserait la batterie + descendre sous ce qu'il faut pour tenir jusqu'au croisement. + {% elif req < fixe %} + Le seuil fixe est aujourd'hui **trop haut** : il fait importer du + réseau au-delà du nécessaire. + {% else %} + Les deux coïncident. + {% endif %} + + *C'est l'accumulation de cet écart, jour après jour, qui décidera si + le bilan prend la main.* + + # --- Décision immédiate ---------------------------------------- + - type: entities + title: Décision immédiate + state_color: true + entities: + - entity: input_boolean.evcs_balance_grid_needed + name: Réseau nécessaire + - entity: input_text.evcs_balance_crossing_hour + name: Croisement production / consommation + - entity: input_number.evcs_balance_energy_to_cover_kwh + name: Énergie à couvrir d'ici là + - entity: input_number.evcs_balance_grid_energy_needed_kwh + name: Énergie réseau strictement nécessaire + - type: divider + - entity: input_number.evcs_balance_required_soc + name: SOC requis (décision) + - entity: input_number.evcs_balance_required_soc_prudent + name: SOC requis (planification) + + # --- La nuit qui vient ------------------------------------------ + - type: entities + title: Prochaine traversée déficitaire + entities: + - entity: input_text.evcs_balance_next_deficit_window + name: Fenêtre + - entity: input_number.evcs_balance_next_deficit_kwh + name: Énergie à fournir + - entity: input_number.evcs_balance_next_deficit_required_soc + name: SOC à atteindre avant + - entity: input_number.evcs_balance_storage_shortfall_kwh + name: Capacité de stockage manquante + + - type: conditional + conditions: + - entity: input_number.evcs_balance_storage_shortfall_kwh + state_not: "0.0" + card: + type: markdown + content: > + ### 🔋 Stockage insuffisant pour cette traversée + + Il manque **{{ states('input_number.evcs_balance_storage_shortfall_kwh') }} kWh** + de capacité utile : même chargée à 100 %, la batterie actuelle + ({{ states('input_number.evcs_balance_usable_capacity_kwh') }} kWh) + ne passe pas la fenêtre sans réseau. + + *Un module US2000C ≈ 2,4 kWh nominal.* + + # --- Ce qu'on jette --------------------------------------------- + - type: markdown + title: Production + content: > + {% set bride = is_state('input_boolean.evcs_balance_pv_censored', 'on') %} + + {% if bride %} + ### 🔻 Production actuellement **bridée** + + Motif : *{{ states('input_text.evcs_balance_censor_reason') }}* + + Sans injection réseau, l'onduleur ne produit que ce que le système + peut absorber. Le soleil est là, il manque un preneur. + {% else %} + ### ☀️ Production **libre** + + Rien ne bride l'onduleur en ce moment : la mesure de production + reflète la ressource réelle. + {% endif %} + + Surplus attendu pour le véhicule aujourd'hui : + **{{ states('input_number.evcs_balance_vehicle_surplus_kwh') }} kWh** + + - type: history-graph + title: SOC réel contre seuil calculé + hours_to_show: 48 + entities: + - entity: sensor.victron_battery_soc + name: SOC batterie + - entity: input_number.evcs_balance_required_soc + name: Seuil calculé + - entity: input_number.victron_grid_soc_off + name: Seuil fixe + + - type: history-graph + title: Bridage de la production + hours_to_show: 48 + entities: + - entity: input_boolean.evcs_balance_pv_censored + name: Production bridée + - entity: switch.edf_general_ok_switch + name: Réseau EDF + + # =================================================================== + - title: Profils & réglages + path: profils + icon: mdi:chart-bell-curve + cards: + - type: custom:mushroom-title-card + title: Profils mesurés + subtitle: Recalculés chaque nuit à 03:30 sur l'historique réel + + - type: markdown + title: Profil horaire de consommation (kWh/h, hors véhicule) + content: > + {% set p50 = states('input_text.evcs_balance_consumption_p50').split(',') %} + + {% set p80 = states('input_text.evcs_balance_consumption_p80').split(',') %} + + {% set env = states('input_text.evcs_balance_envelope').split(',') %} + + {% set cen = states('input_text.evcs_balance_censorship_rate').split(',') %} + + | h | conso typique | conso prudente | production max | censure | + |---:|---:|---:|---:|---:| + + {% for i in range(24) %} + | {{ '%02d' | format(i) }} | {{ p50[i] if p50 | length == 24 else '—' }} | {{ p80[i] if p80 | length == 24 else '—' }} | {{ env[i] if env | length == 24 else '—' }} | {{ (cen[i] | float(1) * 100) | round(0) if cen | length == 24 else '—' }} % | + {% endfor %} + + *La colonne « censure » indique la part des jours où la batterie était + pleine à cette heure : au-delà de 25 %, la production maximale + observée est minorée et le bilan le signale.* + + - type: entities + title: Réglages + entities: + - entity: input_number.evcs_balance_usable_capacity_kwh + name: Capacité batterie utile + - entity: input_boolean.evcs_balance_enabled + name: Bilan actif (kill-switch) + - type: divider + - entity: input_text.evcs_balance_status + name: Statut + - entity: input_text.evcs_balance_last_run + name: Dernière exécution + - entity: input_text.evcs_balance_reason + name: Explication + + - type: markdown + content: > + ### À propos + + Ce bilan remplace des seuils fixes par des grandeurs mesurées. Il + **n'écrit que dans ses propres entités** `evcs_balance_*` : aucun + interrupteur, aucune consigne de borne, aucune stratégie. Cette + garantie est vérifiée par la suite de tests du dépôt, qui échoue si + une écriture sort de ce périmètre. + + Détail de la démarche : `docs/CHANTIER_BILAN_ENERGETIQUE.md`. diff --git a/docs/CHANTIER_BILAN_ENERGETIQUE.md b/docs/CHANTIER_BILAN_ENERGETIQUE.md index 0048dbc..9727192 100644 --- a/docs/CHANTIER_BILAN_ENERGETIQUE.md +++ b/docs/CHANTIER_BILAN_ENERGETIQUE.md @@ -211,7 +211,18 @@ calcul en saison basse. écrêté à 100 %** : au-delà, même une batterie pleine ne passe pas, et l'écart est publié comme `storage_shortfall_kwh`. C'est le chiffre du dimensionnement du stockage. -- [ ] Étape 6 — dashboard, après quelques jours d'observation. +- [x] **Étape 6 — tableau de bord** (24/08). Dashboard YAML dédié + `bilan-energetique` (icône balance, barre latérale), copie versionnée dans + `dashboards/bilan_energetique.yaml` du dépôt. + Vue **Bilan** : la comparaison seuil calculé / seuil fixe / SOC réel avec son + commentaire automatique, la décision immédiate, la prochaine traversée + déficitaire, une alerte conditionnelle quand le stockage est insuffisant, + l'état de bridage de la production, et deux graphiques d'historique + (SOC contre seuils, bridage contre réseau EDF). + Vue **Profils & réglages** : le tableau horaire des quatre profils avec le + taux de censure par heure, la capacité batterie (modifiable), le kill-switch. + Déclaré dans `configuration.yaml` (sauvegarde dans `SAVE_CONFIG/`), + redémarrage HA effectué, aucune erreur au journal. ## Backlog