Il y a 6 heures
Bonjour à tous,
Je vous sollicite pour valider la partie logicielle de mon futur projet d'installation. Actuellement, je tourne avec 6 kWc de panneaux sur micro-onduleurs et un routeur F1ATB sur mon chauffe-eau.
Le projet à venir :
Je vais installer un onduleur Deye 8K avec une batterie DIY 16S 314Ah. Le cerveau sera un mini PC sous HAOS (bare metal), relié à l'onduleur en RS485/USB.
Mon ambition :
Comme je n'y connais rien en informatique, j'ai fait travailler trois IA en collaboration. Je leur ai soumis mes idées et elles ont généré ce code YAML. Elles m'assurent qu'il est parfait, mais je préfère l'avis d'humains qui pratiquent le code au quotidien !
Logique du code :
puissance devront être ajustés une fois le matériel en place ; ce qui m'intéresse avant tout, c'est de valider la structure du code et la logique des rampes.
Le code envisagé :
# ============================================================
# FILTER SENSOR — lissage surplus brut
# ============================================================
sensor:
- platform: filter
name: "Surplus Solaire Lissé"
unique_id: surplus_solaire_lisse
entity_id: sensor.surplus_solaire_brut
filters:
- filter: moving_average
window_size: 3
precision: 0
- filter: range
lower_bound: 0
upper_bound: 8500
# ============================================================
# TEMPLATE SENSORS — surplus brut + consignes par voie
# ============================================================
template:
- sensor:
- name: "Surplus Solaire Brut"
unique_id: surplus_solaire_brut
unit_of_measurement: "W"
state_class: measurement
device_class: power
state: >
{% set pv = states('sensor.deye_pv_power') | float(0) %}
{% set load = states('sensor.deye_load_power') | float(0) %}
{% set grid = states('sensor.deye_grid_power') | float(0) %}
{{ [pv - load - grid, 0] | max | int }}
# --- Voie 1 : chauffe-eau 2000 W ---
- name: "F1ATB V1 consigne pct"
unique_id: f1atb_v1_pct
unit_of_measurement: "%"
state: >
{% set soc = states('sensor.deye_battery_soc') | float(0) %}
{% set seuil = 100 if soc > 90 else 200 %}
{% set s = states('sensor.surplus_solaire_lisse') | float(0) %}
{% set s = 0 if s < seuil else s %}
{{ [[s / 2000 * 100, 0] | max, 100] | min | round(0) | int }}
# --- Voie 2 : dalle 1 — 1500 W ---
- name: "F1ATB V2 consigne pct"
unique_id: f1atb_v2_pct
unit_of_measurement: "%"
state: >
{% set soc = states('sensor.deye_battery_soc') | float(0) %}
{% set seuil = 100 if soc > 90 else 200 %}
{% set s = states('sensor.surplus_solaire_lisse') | float(0) %}
{% set s = 0 if s < seuil else s %}
{% set reste = [s - 2000, 0] | max %}
{{ [[reste / 1500 * 100, 0] | max, 100] | min | round(0) | int }}
# --- Voie 3 : dalle 2 — 1500 W ---
- name: "F1ATB V3 consigne pct"
unique_id: f1atb_v3_pct
unit_of_measurement: "%"
state: >
{% set soc = states('sensor.deye_battery_soc') | float(0) %}
{% set seuil = 100 if soc > 90 else 200 %}
{% set s = states('sensor.surplus_solaire_lisse') | float(0) %}
{% set s = 0 if s < seuil else s %}
{% set reste = [s - 2000 - 1500, 0] | max %}
{{ [[reste / 1500 * 100, 0] | max, 100] | min | round(0) | int }}
# --- Voie 4 : dalle 3 — 1500 W ---
- name: "F1ATB V4 consigne pct"
unique_id: f1atb_v4_pct
unit_of_measurement: "%"
state: >
{% set soc = states('sensor.deye_battery_soc') | float(0) %}
{% set seuil = 100 if soc > 90 else 200 %}
{% set s = states('sensor.surplus_solaire_lisse') | float(0) %}
{% set s = 0 if s < seuil else s %}
{% set reste = [s - 2000 - 1500 - 1500, 0] | max %}
{{ [[reste / 1500 * 100, 0] | max, 100] | min | round(0) | int }}
# ============================================================
# AUTOMATIONS
# ============================================================
automation:
# 1. REGULATION PRINCIPALE — ramp adaptatif toutes les 2s
- alias: "F1ATB - Regulation triacs finale"
id: f1atb_regulation_finale
mode: queued
max: 2
trigger:
- platform: time_pattern
seconds: "/2"
- platform: event
event_type: f1atb_resume
condition:
- condition: numeric_state
entity_id: sensor.deye_battery_soc
above: 30
action:
- variables:
soc: "{{ states('sensor.deye_battery_soc') | float(0) }}"
surplus: "{{ states('sensor.surplus_solaire_lisse') | float(0) }}"
step_up: >
{% if soc > 95 %} 20
{% elif soc > 80 %} 15
{% else %} 10
{% endif %}
step_down: "{{ 15 if surplus < 500 else 8 }}"
seuil: 3
- repeat:
for_each:
- { sensor: "sensor.f1atb_v1_consigne_pct", entity: "number.f1atb_voie_1" }
- { sensor: "sensor.f1atb_v2_consigne_pct", entity: "number.f1atb_voie_2" }
- { sensor: "sensor.f1atb_v3_consigne_pct", entity: "number.f1atb_voie_3" }
- { sensor: "sensor.f1atb_v4_consigne_pct", entity: "number.f1atb_voie_4" }
sequence:
- variables:
new_val: "{{ states(repeat.item.sensor) | int(0) }}"
old_val: "{{ states(repeat.item.entity) | int(0) }}"
- if:
- condition: template
value_template: "{{ (new_val - old_val) | abs > seuil }}"
then:
- service: number.set_value
target:
entity_id: "{{ repeat.item.entity }}"
data:
value: >
{% if new_val > old_val %}
{{ [old_val + step_up, new_val] | min }}
{% else %}
{{ [old_val - step_down, new_val] | max }}
{% endif %}
# 2. COUPURE SOC BAS — tout à 0 immédiatement
- alias: "F1ATB - Coupure SOC bas"
id: f1atb_coupure_soc
mode: single
trigger:
- platform: numeric_state
entity_id: sensor.deye_battery_soc
below: 30
action:
- service: number.set_value
target:
entity_id:
- number.f1atb_voie_1
- number.f1atb_voie_2
- number.f1atb_voie_3
- number.f1atb_voie_4
data:
value: 0
# 3. REPRISE APRES SOC BAS — hysteresis 30/35%
- alias: "F1ATB - Reprise SOC"
id: f1atb_reprise_soc
mode: single
trigger:
- platform: numeric_state
entity_id: sensor.deye_battery_soc
above: 35
action:
- delay: "00:00:15"
- event: f1atb_resume
# 4. RESYNC PERIODIQUE — rattrape les dérives toutes les minutes
- alias: "F1ATB - Resync periodique"
id: f1atb_resync
mode: single
trigger:
- platform: time_pattern
minutes: "/1"
condition:
- condition: numeric_state
entity_id: sensor.deye_battery_soc
above: 30
action:
- service: number.set_value
target: { entity_id: number.f1atb_voie_1 }
data: { value: "{{ states('sensor.f1atb_v1_consigne_pct') | int(0) }}" }
- service: number.set_value
target: { entity_id: number.f1atb_voie_2 }
data: { value: "{{ states('sensor.f1atb_v2_consigne_pct') | int(0) }}" }
- service: number.set_value
target: { entity_id: number.f1atb_voie_3 }
data: { value: "{{ states('sensor.f1atb_v3_consigne_pct') | int(0) }}" }
- service: number.set_value
target: { entity_id: number.f1atb_voie_4 }
data: { value: "{{ states('sensor.f1atb_v4_consigne_pct') | int(0) }}" }
Votre avis :
Avant d'acheter tout le matériel et de lancer le montage, est-ce que cette logique de régulation via Home Assistant vous semble tenir la route ?
Merci pour votre aide et vos retours d'expérience !
Je vous sollicite pour valider la partie logicielle de mon futur projet d'installation. Actuellement, je tourne avec 6 kWc de panneaux sur micro-onduleurs et un routeur F1ATB sur mon chauffe-eau.
Le projet à venir :
Je vais installer un onduleur Deye 8K avec une batterie DIY 16S 314Ah. Le cerveau sera un mini PC sous HAOS (bare metal), relié à l'onduleur en RS485/USB.
Mon ambition :
- Off-Grid partiel : Par fierté et pour couper l'abonnement, je vise l'autonomie de début avril à fin septembre.
- Gestion de l'inertie : Utiliser le surplus pour chauffer mes dalles (très utile pour les nuits fraîches en Normandie) via un routeur F1ATB 4 voies (1 ECS 2000W + 3 dalles 1500W).
Comme je n'y connais rien en informatique, j'ai fait travailler trois IA en collaboration. Je leur ai soumis mes idées et elles ont généré ce code YAML. Elles m'assurent qu'il est parfait, mais je préfère l'avis d'humains qui pratiquent le code au quotidien !
Logique du code :
- Protection Batterie : Coupure du routage à 30% de SOC, reprise à 35%.
- Rampes Intelligentes : Montée progressive (paliers de 10 à 20%) et descente rapide (palier de 15%) pour protéger la batterie des appels de charge de la maison ou des passages nuageux.
- Calcul Hybride : Utilisation de PV - Load - Grid pour fonctionner aussi bien l'hiver (On-Grid avec pince CT réseau) que l'été (Off-Grid).
puissance devront être ajustés une fois le matériel en place ; ce qui m'intéresse avant tout, c'est de valider la structure du code et la logique des rampes.
Le code envisagé :
# ============================================================
# FILTER SENSOR — lissage surplus brut
# ============================================================
sensor:
- platform: filter
name: "Surplus Solaire Lissé"
unique_id: surplus_solaire_lisse
entity_id: sensor.surplus_solaire_brut
filters:
- filter: moving_average
window_size: 3
precision: 0
- filter: range
lower_bound: 0
upper_bound: 8500
# ============================================================
# TEMPLATE SENSORS — surplus brut + consignes par voie
# ============================================================
template:
- sensor:
- name: "Surplus Solaire Brut"
unique_id: surplus_solaire_brut
unit_of_measurement: "W"
state_class: measurement
device_class: power
state: >
{% set pv = states('sensor.deye_pv_power') | float(0) %}
{% set load = states('sensor.deye_load_power') | float(0) %}
{% set grid = states('sensor.deye_grid_power') | float(0) %}
{{ [pv - load - grid, 0] | max | int }}
# --- Voie 1 : chauffe-eau 2000 W ---
- name: "F1ATB V1 consigne pct"
unique_id: f1atb_v1_pct
unit_of_measurement: "%"
state: >
{% set soc = states('sensor.deye_battery_soc') | float(0) %}
{% set seuil = 100 if soc > 90 else 200 %}
{% set s = states('sensor.surplus_solaire_lisse') | float(0) %}
{% set s = 0 if s < seuil else s %}
{{ [[s / 2000 * 100, 0] | max, 100] | min | round(0) | int }}
# --- Voie 2 : dalle 1 — 1500 W ---
- name: "F1ATB V2 consigne pct"
unique_id: f1atb_v2_pct
unit_of_measurement: "%"
state: >
{% set soc = states('sensor.deye_battery_soc') | float(0) %}
{% set seuil = 100 if soc > 90 else 200 %}
{% set s = states('sensor.surplus_solaire_lisse') | float(0) %}
{% set s = 0 if s < seuil else s %}
{% set reste = [s - 2000, 0] | max %}
{{ [[reste / 1500 * 100, 0] | max, 100] | min | round(0) | int }}
# --- Voie 3 : dalle 2 — 1500 W ---
- name: "F1ATB V3 consigne pct"
unique_id: f1atb_v3_pct
unit_of_measurement: "%"
state: >
{% set soc = states('sensor.deye_battery_soc') | float(0) %}
{% set seuil = 100 if soc > 90 else 200 %}
{% set s = states('sensor.surplus_solaire_lisse') | float(0) %}
{% set s = 0 if s < seuil else s %}
{% set reste = [s - 2000 - 1500, 0] | max %}
{{ [[reste / 1500 * 100, 0] | max, 100] | min | round(0) | int }}
# --- Voie 4 : dalle 3 — 1500 W ---
- name: "F1ATB V4 consigne pct"
unique_id: f1atb_v4_pct
unit_of_measurement: "%"
state: >
{% set soc = states('sensor.deye_battery_soc') | float(0) %}
{% set seuil = 100 if soc > 90 else 200 %}
{% set s = states('sensor.surplus_solaire_lisse') | float(0) %}
{% set s = 0 if s < seuil else s %}
{% set reste = [s - 2000 - 1500 - 1500, 0] | max %}
{{ [[reste / 1500 * 100, 0] | max, 100] | min | round(0) | int }}
# ============================================================
# AUTOMATIONS
# ============================================================
automation:
# 1. REGULATION PRINCIPALE — ramp adaptatif toutes les 2s
- alias: "F1ATB - Regulation triacs finale"
id: f1atb_regulation_finale
mode: queued
max: 2
trigger:
- platform: time_pattern
seconds: "/2"
- platform: event
event_type: f1atb_resume
condition:
- condition: numeric_state
entity_id: sensor.deye_battery_soc
above: 30
action:
- variables:
soc: "{{ states('sensor.deye_battery_soc') | float(0) }}"
surplus: "{{ states('sensor.surplus_solaire_lisse') | float(0) }}"
step_up: >
{% if soc > 95 %} 20
{% elif soc > 80 %} 15
{% else %} 10
{% endif %}
step_down: "{{ 15 if surplus < 500 else 8 }}"
seuil: 3
- repeat:
for_each:
- { sensor: "sensor.f1atb_v1_consigne_pct", entity: "number.f1atb_voie_1" }
- { sensor: "sensor.f1atb_v2_consigne_pct", entity: "number.f1atb_voie_2" }
- { sensor: "sensor.f1atb_v3_consigne_pct", entity: "number.f1atb_voie_3" }
- { sensor: "sensor.f1atb_v4_consigne_pct", entity: "number.f1atb_voie_4" }
sequence:
- variables:
new_val: "{{ states(repeat.item.sensor) | int(0) }}"
old_val: "{{ states(repeat.item.entity) | int(0) }}"
- if:
- condition: template
value_template: "{{ (new_val - old_val) | abs > seuil }}"
then:
- service: number.set_value
target:
entity_id: "{{ repeat.item.entity }}"
data:
value: >
{% if new_val > old_val %}
{{ [old_val + step_up, new_val] | min }}
{% else %}
{{ [old_val - step_down, new_val] | max }}
{% endif %}
# 2. COUPURE SOC BAS — tout à 0 immédiatement
- alias: "F1ATB - Coupure SOC bas"
id: f1atb_coupure_soc
mode: single
trigger:
- platform: numeric_state
entity_id: sensor.deye_battery_soc
below: 30
action:
- service: number.set_value
target:
entity_id:
- number.f1atb_voie_1
- number.f1atb_voie_2
- number.f1atb_voie_3
- number.f1atb_voie_4
data:
value: 0
# 3. REPRISE APRES SOC BAS — hysteresis 30/35%
- alias: "F1ATB - Reprise SOC"
id: f1atb_reprise_soc
mode: single
trigger:
- platform: numeric_state
entity_id: sensor.deye_battery_soc
above: 35
action:
- delay: "00:00:15"
- event: f1atb_resume
# 4. RESYNC PERIODIQUE — rattrape les dérives toutes les minutes
- alias: "F1ATB - Resync periodique"
id: f1atb_resync
mode: single
trigger:
- platform: time_pattern
minutes: "/1"
condition:
- condition: numeric_state
entity_id: sensor.deye_battery_soc
above: 30
action:
- service: number.set_value
target: { entity_id: number.f1atb_voie_1 }
data: { value: "{{ states('sensor.f1atb_v1_consigne_pct') | int(0) }}" }
- service: number.set_value
target: { entity_id: number.f1atb_voie_2 }
data: { value: "{{ states('sensor.f1atb_v2_consigne_pct') | int(0) }}" }
- service: number.set_value
target: { entity_id: number.f1atb_voie_3 }
data: { value: "{{ states('sensor.f1atb_v3_consigne_pct') | int(0) }}" }
- service: number.set_value
target: { entity_id: number.f1atb_voie_4 }
data: { value: "{{ states('sensor.f1atb_v4_consigne_pct') | int(0) }}" }
Votre avis :
Avant d'acheter tout le matériel et de lancer le montage, est-ce que cette logique de régulation via Home Assistant vous semble tenir la route ?
Merci pour votre aide et vos retours d'expérience !
