Le reseau force a desormais une echeance
Constate le 17/09 : force_grid_now pose a la main a 12:58 pendant les heures creuses de midi a ferme le contacteur et lance une charge batterie a 7,7 kW, et RIEN dans le systeme ne l arrete. WINDOWS_SOURCEV2 evalue `else if (forceGridRequested) sourceMode='grid_forced'` avant tout test de profil ; l hysteresis maintient l override tant que le motif « pas de demande » est absent, et `vehicle_not_connected` n en fait pas partie, si bien que le depart du vehicule ne libere rien. A 14:24 la fenetre economique se ferme et l import continue au plein tarif jusqu a ce qu un humain soit devant l ecran. Le manque n est pas une interdiction, c est une ECHEANCE — le proprietaire ferme deliberement le contacteur, soit pour charger en heures creuses, soit pour proteger le parc pendant une pointe de consommation. Deux sorties : 1. sortie de la fenetre d heures creuses DANS LAQUELLE il a ete engage. La condition porte sur l instant d engagement et pas seulement sur l heure courante, sinon un force_grid_now pose un soir de cuisson pour proteger le parc serait annule a la minute suivante ; 2. plafond de duree (180 min par defaut), filet pour le cas engage hors fenetre et oublie. Les bornes des fenetres sont LUES dans les helpers victron_evcs_hc_*, jamais recopiees : elles ont deja change une fois (01:30 -> 01:24 le 09/07) et une copie divergerait en silence. Huit cas rejoues contre le moteur de gabarits de Home Assistant, 8/8, dont les trois qui comptent : la sortie de fenetre expire, la protection du parc a 20:30 ne s annule NI a 20:35 NI a 22:00, et le plafond de duree la reprend a 23:45. Le mecanisme fait donc bien la difference entre une echeance et une interdiction. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GwpFpy2aQ2rJok6tNQSbVj
This commit is contained in:
146
packages/evcs_expiration_force_grid.yaml
Executable file
146
packages/evcs_expiration_force_grid.yaml
Executable file
@@ -0,0 +1,146 @@
|
|||||||
|
# Expiration du reseau force — le contournement manuel a desormais une fin
|
||||||
|
#
|
||||||
|
# LE DEFAUT, constate le 17/09/2026. `force_grid_now` pose a la main a 12:58
|
||||||
|
# pendant les heures creuses de midi (12:24-14:24) a ferme le contacteur et
|
||||||
|
# lance une charge batterie a 7,7 kW. Rien dans le systeme ne l'arrete :
|
||||||
|
#
|
||||||
|
# - WINDOWS_SOURCEV2 : `else if (forceGridRequested) sourceMode='grid_forced'`
|
||||||
|
# est evalue AVANT tout test de profil. La fenetre HC n'entre pas dans le
|
||||||
|
# calcul.
|
||||||
|
# - Hysteresis : `evcsExplicitGridRequest` inclut la strategie, et l'override
|
||||||
|
# ne tombe que sur un motif « pas de demande » — or `vehicle_not_connected`
|
||||||
|
# n'en fait pas partie. Le depart du vehicule ne libere donc rien.
|
||||||
|
# - Les deux coupures immediates existantes sont bloquees par cet override.
|
||||||
|
#
|
||||||
|
# Consequence : a 14:24 la fenetre economique se ferme et l'import continue AU
|
||||||
|
# PLEIN TARIF, indefiniment, jusqu'a ce qu'un humain soit devant l'ecran.
|
||||||
|
#
|
||||||
|
# LA REGLE, et pourquoi elle est formulee ainsi. Un contournement manuel n'est
|
||||||
|
# pas illegitime — le proprietaire ferme deliberement le contacteur pour
|
||||||
|
# charger en heures creuses, ou pour proteger le parc pendant une pointe. Ce
|
||||||
|
# qui manque n'est pas une interdiction, c'est une ECHEANCE. Deux sorties, et
|
||||||
|
# aucune ne repose sur un seuil invente :
|
||||||
|
#
|
||||||
|
# 1. FENETRE ECONOMIQUE. Si le reseau force a ete engage PENDANT une fenetre
|
||||||
|
# d'heures creuses, il expire a la sortie de cette fenetre. On n'achete
|
||||||
|
# pas au plein tarif ce qu'on avait decide d'acheter au tarif creux.
|
||||||
|
# ⚠️ La condition porte sur l'instant d'ENGAGEMENT, pas seulement sur
|
||||||
|
# l'heure courante : engage HORS heures creuses — pour proteger le parc un
|
||||||
|
# soir de cuisson, cf. la regle « fermer EDF pour proteger le materiel » —
|
||||||
|
# il ne doit PAS etre annule a la minute suivante. C'est la difference
|
||||||
|
# entre une echeance et une interdiction.
|
||||||
|
# 2. PLAFOND DE DUREE. Au-dela de `evcs_force_grid_max_minutes`, quelle que
|
||||||
|
# soit la raison. Filet pour le cas engage hors fenetre et oublie.
|
||||||
|
#
|
||||||
|
# Les bornes des fenetres sont LUES dans les helpers qui font foi
|
||||||
|
# (`victron_evcs_hc_*`), jamais recopiees : elles ont deja change une fois
|
||||||
|
# (01:30 -> 01:24 le 09/07/2026) et une copie divergerait en silence.
|
||||||
|
#
|
||||||
|
# ⚠️ AUCUN `initial:` DANS CE FICHIER. Declarer `initial:` DESACTIVE la
|
||||||
|
# restauration d'etat : le helper reprend cette valeur a CHAQUE demarrage de
|
||||||
|
# Home Assistant. Le 06/09/2026, cinq reglages manuels du projet se
|
||||||
|
# reinitialisaient ainsi en silence, dont deux toggles de cablage.
|
||||||
|
|
||||||
|
input_boolean:
|
||||||
|
evcs_expiration_force_grid_enabled:
|
||||||
|
name: "EVCS — expiration du réseau forcé"
|
||||||
|
icon: mdi:timer-sand
|
||||||
|
|
||||||
|
input_number:
|
||||||
|
evcs_force_grid_max_minutes:
|
||||||
|
name: "EVCS — durée maximale du réseau forcé"
|
||||||
|
min: 15
|
||||||
|
max: 720
|
||||||
|
step: 15
|
||||||
|
unit_of_measurement: min
|
||||||
|
mode: box
|
||||||
|
icon: mdi:timer-alert-outline
|
||||||
|
|
||||||
|
input_text:
|
||||||
|
# Trace lisible du dernier declenchement. Un helper `input_text` accepte 100
|
||||||
|
# caracteres par defaut et REJETTE EN SILENCE au-dela — le 08/09/2026 une
|
||||||
|
# panne de 4 h 45 n'a laisse aucune trace pour cette raison. 255 ici.
|
||||||
|
evcs_force_grid_last_expiry:
|
||||||
|
name: "EVCS — dernière expiration du réseau forcé"
|
||||||
|
max: 255
|
||||||
|
icon: mdi:history
|
||||||
|
|
||||||
|
automation:
|
||||||
|
- id: evcs_expiration_reseau_force
|
||||||
|
alias: "EVCS — le réseau forcé expire tout seul"
|
||||||
|
description: >
|
||||||
|
Repasse la stratégie en auto_solar_plus quand le réseau forcé a perdu sa
|
||||||
|
raison d'être : sortie de la fenêtre d'heures creuses dans laquelle il a
|
||||||
|
été engagé, ou dépassement de la durée maximale.
|
||||||
|
mode: single
|
||||||
|
trigger:
|
||||||
|
- platform: time_pattern
|
||||||
|
minutes: "/1"
|
||||||
|
condition:
|
||||||
|
- condition: state
|
||||||
|
entity_id: input_select.victron_evcs_strategy
|
||||||
|
state: force_grid_now
|
||||||
|
- condition: state
|
||||||
|
entity_id: input_boolean.evcs_expiration_force_grid_enabled
|
||||||
|
state: "on"
|
||||||
|
- condition: template
|
||||||
|
value_template: >
|
||||||
|
{% set s = states.input_select.victron_evcs_strategy %}
|
||||||
|
{% set ns = states('input_datetime.victron_evcs_hc_night_start').split(':') %}
|
||||||
|
{% set ne = states('input_datetime.victron_evcs_hc_night_stop').split(':') %}
|
||||||
|
{% set ms = states('input_datetime.victron_evcs_hc_midday_start').split(':') %}
|
||||||
|
{% set me = states('input_datetime.victron_evcs_hc_midday_stop').split(':') %}
|
||||||
|
{% set n1 = ns[0]|int * 60 + ns[1]|int %}
|
||||||
|
{% set n2 = ne[0]|int * 60 + ne[1]|int %}
|
||||||
|
{% set m1 = ms[0]|int * 60 + ms[1]|int %}
|
||||||
|
{% set m2 = me[0]|int * 60 + me[1]|int %}
|
||||||
|
{% set eng = s.last_changed | as_local %}
|
||||||
|
{% set em = eng.hour * 60 + eng.minute %}
|
||||||
|
{% set hm = now().hour * 60 + now().minute %}
|
||||||
|
{% set d = namespace(eng=false, now=false) %}
|
||||||
|
{% if n1 <= n2 %}
|
||||||
|
{% set d.eng = (em >= n1 and em < n2) %}
|
||||||
|
{% set d.now = (hm >= n1 and hm < n2) %}
|
||||||
|
{% else %}
|
||||||
|
{% set d.eng = (em >= n1 or em < n2) %}
|
||||||
|
{% set d.now = (hm >= n1 or hm < n2) %}
|
||||||
|
{% endif %}
|
||||||
|
{% if em >= m1 and em < m2 %}{% set d.eng = true %}{% endif %}
|
||||||
|
{% if hm >= m1 and hm < m2 %}{% set d.now = true %}{% endif %}
|
||||||
|
{% set duree = (as_timestamp(now()) - as_timestamp(s.last_changed)) / 60 %}
|
||||||
|
{% set plafond = states('input_number.evcs_force_grid_max_minutes') | float(180) %}
|
||||||
|
{{ (d.eng and not d.now) or (duree > plafond) }}
|
||||||
|
action:
|
||||||
|
- variables:
|
||||||
|
motif: >
|
||||||
|
{% set s = states.input_select.victron_evcs_strategy %}
|
||||||
|
{% set duree = ((as_timestamp(now()) - as_timestamp(s.last_changed)) / 60) | round(0) %}
|
||||||
|
{% set plafond = states('input_number.evcs_force_grid_max_minutes') | float(180) %}
|
||||||
|
{% if duree > plafond %}durée maximale de {{ plafond | round(0) }} min dépassée
|
||||||
|
{% else %}sortie de la fenêtre d'heures creuses{% endif %}
|
||||||
|
- service: input_select.select_option
|
||||||
|
target:
|
||||||
|
entity_id: input_select.victron_evcs_strategy
|
||||||
|
data:
|
||||||
|
option: auto_solar_plus
|
||||||
|
- service: input_text.set_value
|
||||||
|
target:
|
||||||
|
entity_id: input_text.evcs_force_grid_last_expiry
|
||||||
|
data:
|
||||||
|
value: >
|
||||||
|
{{ now().strftime('%d/%m %H:%M') }} — retour auto_solar_plus
|
||||||
|
({{ motif }}), SOC {{ states('sensor.victron_battery_soc') | round(0) }} %
|
||||||
|
- service: logbook.log
|
||||||
|
data:
|
||||||
|
name: EVCS
|
||||||
|
message: >
|
||||||
|
Réseau forcé expiré ({{ motif }}) — retour à auto_solar_plus.
|
||||||
|
SOC {{ states('sensor.victron_battery_soc') | round(0) }} %.
|
||||||
|
entity_id: input_select.victron_evcs_strategy
|
||||||
|
- service: persistent_notification.create
|
||||||
|
data:
|
||||||
|
title: EVCS — réseau forcé expiré
|
||||||
|
message: >
|
||||||
|
La stratégie est repassée en **auto_solar_plus** : {{ motif }}.
|
||||||
|
SOC {{ states('sensor.victron_battery_soc') | round(0) }} %.
|
||||||
|
notification_id: evcs_force_grid_expire
|
||||||
Reference in New Issue
Block a user