# Charge manuelle de la batterie en heures creuses # # LE BESOIN, tel que le proprietaire l'a pose le 18/09/2026 : « un bouton charge # HC manuel si le bilan energetique n'a pas autorise la charge HC a cause des # conditions meteo nuageuses et des journees hivernales d'ensoleillement # courtes ». C'est sa strategie d'hiver documentee (MANUEL §0), jusqu'ici tenue # a la main par des charges programmees dans le GX — desactivees le 27/08 parce # qu'elles constituaient un second automate sur la meme batterie. # # LA DECISION QU'IL A TRANCHEE : CONTRAINDRE LE BILAN, PAS L'IGNORER, pour # garder UN SEUL ARBITRE. Le bouton n'ecrit donc pas une cible : il pose un # PLANCHER que le bilan integre a son calcul, et le bilan reste seul producteur # de la cible et du manque. # # ⚠️ POURQUOI DEUX NOMBRES ET PAS UN. `..._target` est le reglage du # proprietaire ; `..._floor` est sa valeur EFFECTIVE, mise a zero hors des # fenetres d'heures creuses par l'automatisation ci-dessous. C'est ce second # nombre que le bilan lit. La raison est architecturale : le bilan repond a « de # combien la batterie a-t-elle besoin », pas a « quand est-ce bon marche » — il # ne contient aucune notion d'heure creuse (verifie : zero occurrence de # `hc_night` ou `hc_midday` dans tout `src/energy-balance/`) et doit le rester. # Home Assistant detient le calendrier tarifaire, le bilan detient l'energie. # # ⚠️ ET SANS CE FILTRAGE, LE PLANCHER FUIT DANS LA JOURNEE. `BATTERY_CLOUD_LOCKS` # calcule `eveningSocStartFloorPct = max(eveningSocStartMin, balanceRequiredSocPct)` : # un plancher a 85 % laisse actif le soir bloquerait le demarrage de la charge du # vehicule jusqu'a ce que la batterie y soit. Ce n'est pas theorique, c'est une # ligne qui s'applique tous les soirs. # # ⚠️ EFFET STRUCTUREL A CONNAITRE, qu'aucune echeance ne corrige. Le plancher # passe par `coverage()`, appelee pour le profil typique ET le profil prudent : # il releve donc aussi `required_soc_prudent`, qui est le plancher de P12. Pendant # une charge manuelle, la voiture cesse donc de descendre la batterie. C'est # coherent — on ne remplit pas la batterie au reseau pendant que la voiture la # vide — mais une nuit ou l'on veut les deux, on n'aura que l'un. # # ⚠️ AUCUN `initial:` DANS CE FICHIER. Declarer `initial:` DESACTIVE la # restauration d'etat : le helper reprend cette valeur a CHAQUE demarrage de Home # Assistant. Contrepartie connue : un helper cree de zero demarre au `min` pour un # nombre et a `off` pour un booleen — d'ou le `min: 23` ci-dessous, qui est deja # une valeur utilisable, et non 0 qui serait silencieusement sans effet. input_boolean: evcs_battery_manual_hc: name: "🔋 Charge batterie en heures creuses (manuel)" icon: mdi:battery-charging-medium input_number: # 23 au minimum : le plancher n'est pris en compte par le bilan que s'il est # STRICTEMENT au-dessus du plancher dur de 22 %. En dessous il ne contraint # rien, et un reglage sans effet est pire qu'un reglage absent. evcs_battery_manual_hc_target: name: "🔋 Cible de la charge manuelle" min: 23 max: 100 step: 5 unit_of_measurement: "%" mode: slider icon: mdi:battery-heart-variant # Ecrit par l'automatisation, jamais a la main. Zero = aucune contrainte. evcs_battery_manual_hc_floor: name: "🔋 Plancher effectif transmis au bilan" min: 0 max: 100 step: 0.5 unit_of_measurement: "%" mode: box icon: mdi:arrow-collapse-up automation: - id: evcs_battery_manual_hc_fenetre alias: "EVCS — la charge batterie manuelle ne vaut qu'en heures creuses" description: > Publie le plancher effectif lu par le bilan énergétique : la cible pendant une fenêtre d'heures creuses, zéro en dehors. Et désarme le bouton à la sortie de la fenêtre où il a servi, pour qu'une demande soit un geste ponctuel et non un réglage permanent oublié. mode: single trigger: - platform: time_pattern minutes: "/1" action: - variables: dans_hc: > {% 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 hm = now().hour * 60 + now().minute %} {% set d = namespace(v=false) %} {% if n1 <= n2 %} {% if hm >= n1 and hm < n2 %}{% set d.v = true %}{% endif %} {% else %} {% if hm >= n1 or hm < n2 %}{% set d.v = true %}{% endif %} {% endif %} {% if hm >= m1 and hm < m2 %}{% set d.v = true %}{% endif %} {{ d.v }} # 13/10/2026 : `dans_hc` arrive en BOOLEEN (types natifs de HA), pas en # chaine ; comparee a 'True' elle etait toujours fausse -> le plancher # restait a 0 et le bouton se desarmait : la charge manuelle n'a jamais # fonctionne depuis le 18/09. On compare donc `dans_hc | string`. arme: "{{ is_state('input_boolean.evcs_battery_manual_hc', 'on') }}" plancher_actuel: "{{ states('input_number.evcs_battery_manual_hc_floor') | float(0) }}" voulu: > {{ (states('input_number.evcs_battery_manual_hc_target') | float(0)) if (arme and (dans_hc | string) == 'True') else 0 }} - condition: template value_template: "{{ (voulu | float(0) - plancher_actuel | float(0)) | abs > 0.01 }}" - service: input_number.set_value target: entity_id: input_number.evcs_battery_manual_hc_floor data: value: "{{ voulu | float(0) }}" # Desarmement a la sortie de la fenetre : le plancher etait actif, il ne # l'est plus, et le bouton etait arme. Une demande de charge est un geste # ponctuel ; sans ce desarmement elle se rejouerait toutes les nuits. - if: - condition: template value_template: > {{ arme and (dans_hc | string) != 'True' and (plancher_actuel | float(0)) > 0 }} then: - service: input_boolean.turn_off target: entity_id: input_boolean.evcs_battery_manual_hc - service: logbook.log data: name: EVCS message: > Charge batterie manuelle terminée (sortie de la fenêtre d'heures creuses). SOC {{ states('sensor.victron_battery_soc') | round(0) }} %. entity_id: input_boolean.evcs_battery_manual_hc