Esp32 + solaire + batterie + deepsleep + niveau de cuve

Hello,

Tjs en cours de réalisation

à base d’un capteur ultrason JSN-SR04T fonctionnel, cela va me permettre d’explorer certaines parties que je ne connais pas trop ensemble, à savoir charge solaire, batterie, deep sleep

Schéma de base opérationnel

début des tests solaire

et assemblage de l’ensemble, faut pas se planter dans l’ordre d’assemblage des éléments :smiley:

le nouveau schéma sur lequel je suis parti suivra

Liste du matériel:
3 panneaux 6V 200mA (j’ai commandé du 5V mais reçu du 6V sortie panneau).
TP4056 (charge des accus et protection.
2 batteries littium 3.7V 3500mAh + support
1 Diode Schottky 1N5819
2 résistances 1k et 560 ohms
1 condensateur polarisé 10 microF ( j’ai mis 10V)
1 condensateur 100nF
1 transistor BS170
en attente MT3608 « booster » d’alim 2/ 24V in > 5 / 28V out https://www.olimex.com/Products/Breadboarding/BB-PWR-3608/resources/MT3608.pdf

A voir si je complète pour avoir l’état des batteries qui remontent aussi dans HA.

cdt

1 « J'aime »

Re,

Pas encore complet mais on n’est pas loin

cdt

2 « J'aime »

Bonjour, pour le code tu utilises esphome?
J’ai ajouté une mesure de tension sur le GPIO34 avec un diviseur de pont.
Ça marche bien cependant ce n’est pas très stable comme le montre mon historique :

En tout cas voici mon code si ça peut aider.

Je suis aussi preneur du tien si tu veux bien pour comparer.

Mon code esphome.

esphome:
  name: esp-cuve
  friendly_name: ESP-Cuve

esp32:
  board: esp32dev
  framework:
    type: arduino

wifi:
  ssid: box
  password: secret

  
logger:

api:

ota:
  platform: esphome

# Active le proxy Bluetooth pour Home Assistant
esp32_ble_tracker:

bluetooth_proxy:

# Capteur de distance ultrasonique
sensor:
  - platform: ultrasonic
    trigger_pin: GPIO17
    echo_pin: GPIO18
    name: "Distance eau brut"
    update_interval: 900s
    unit_of_measurement: "m"
    accuracy_decimals: 3
    id: distance_eau_brut

  # Hauteur d’eau (calculée)
  - platform: template
    name: "Hauteur d'eau"
    unit_of_measurement: "m"
    accuracy_decimals: 2
    update_interval: 900s
    lambda: |-
      const float hauteur_totale = 0.94;
      if (isnan(id(distance_eau_brut).state)) return NAN;
      float distance = id(distance_eau_brut).state;
      return (distance > hauteur_totale) ? 0 : hauteur_totale - distance;

  # Niveau en litres
  - platform: template
    name: "Niveau cuve en litres"
    unit_of_measurement: "L"
    accuracy_decimals: 0
    update_interval: 900s
    lambda: |-
      const float hauteur_totale = 0.94; // m
      const float longueur = 1.10;       // m
      const float largeur = 0.90;        // m
      if (isnan(id(distance_eau_brut).state)) return NAN;
      float hauteur_eau = hauteur_totale - id(distance_eau_brut).state;
      if (hauteur_eau < 0) hauteur_eau = 0;
      float volume_m3 = hauteur_eau * longueur * largeur;
      return volume_m3 * 1000.0;

  # Mesure de tension de la batterie 12V
  - platform: adc
    pin: GPIO34
    name: "Tension batterie"
    unit_of_measurement: "V"
    update_interval: 900s
    attenuation: 12db
    accuracy_decimals: 2
    filters:
      - multiply: 4.3
    id: tension_batterie

Je cherche aussi à faire cela avec un esp32-h2 en zigbee, pour voir si c’est pas un problème de wifi mais bon là j’ai trop du mal à déjà juste faire le build avec ide-espressif.

En tout ça merci pour ton partage.

Jobe

1 « J'aime »

Re,

J’ai plusieurs « chantiers » en cours du coup je swappe de l’un à l’autre… je reviens bientôt sur ce projet :wink:

merci pour ton partage également :+1:

cdt

1 « J'aime »

Re,

Je viens de penser qu’en attendant tu peux ajouter un sensor pour surveiller le wifi

sensor:
  - platform: wifi_signal
    name: "WiFi Signal Strength"
    update_interval: 60s

il faudra sans doute adapter le temps de remontée

Et oui j’utilise esphome ( je n’avais pas pensé de répondre )

cdt

1 « J'aime »

Merci bonne idée, je vais essayer avec le sensor

Re,

Début des tests du circuit de charge du TP4056 sur 1 batterie ( les 2 sont à 2.92V ) pendant 1h je changerai de batterie au bout de 1h temps couvert et sous la tonnelle

les panneaux sont des 6V câblés en //, sortie de panneaux j’ai une tension de

sortie du régulateur de charge, il est censé me donner 4.2V max pour charger des batteries de 3.7V

« en charge » led rouge allumée, à confirmer au bout de 1h

j’ai mis un panneaux transparent au dessus des panneaux qui ne sont pas protégés par une résine
je perds 0.04V sortie de panneau

cdt

1 « J'aime »

Re,

je me suis remis dessus, tension de sortie trop forte en plein soleil, prochain test ajouter une diode schottky entre les panneaux et le 4056. Schéma actualisé et test à venir.

Attention je n’ai pas pris le schéma de la 1N5819, Anode et cathode sont inversés , du coup j’ai pris le schéma d’une diode classique. Anode aux panneaux, cathode au 4056.

1

cdt

1 « J'aime »

Re,

Je valide la partie charge / alimentation de l’esp, je n’ai pas encore regardé au deepsleep plus en avant, je vais laisser tourner, pour voir combien de temps ça tient.

Mon JSN-SR04T est en 5V, je pensais bien avoir des soucis à ce niveau et c’est le cas (malgré que ce soit une V3 ) priori pilotable en 3.3V, aucune info ne remonte du capteur et c’est explicitement marqué dessus 5V ), j’ai quand même tenté.

J’ai commandé des MT3608 https://www.olimex.com/Products/Breadboarding/BB-PWR-3608/resources/MT3608.pdf
On verra ce que ça donne.

image

A part les panneaux, à la fin tout sera invisible.
cdt

Bravo, je suis cela avec attention.
Moi j’ai fait un peu plus bourrin, j’ai utilisé le kit :
ECO-WORTHY Kit 10W 25W 12V avec Contrôleur 10A pour Porte Automatique, Pompe à l’eau, Appareil DC (10W Kit) -amazon
avec un esp32-h2 en zigbee et j’ai une remontée qui fonctionne.
J’ai remarqué que le capteur ultrason HC-SR04 donne une variabilité des mesures de quelques mm en fonction de la température.
Au moins la solution que tu proposes est plus compacte.
:+1:

1 « J'aime »

Re,

du coup il a tenu sans soucis la nuit avec 2 Batteries 3500mA (sans deepsleep).

cdt

1 « J'aime »

Partage aussi ta solution photos codes etc…
Car même si c’ets plus compact chez @freetronic ta solution peu permettre à d’autre de tenter d’implémenter l’ ESP32-H2 Zigbee pou refaire ta solution ou pour en tenter d’autres :wink: Ouvre ton post sur le sujet par contre

C’est déjà fait :wink: sur une autre conversation.
Je ne voulais pas polluer le poste car c’est pas la même approche

1 « J'aime »

Hello,

Avec un lien vers le tien ça me semble parfait :wink:

cdt

1 « J'aime »

Re,

Ajout du niveau de charge des batteries en test

image

image

cdt

2 « J'aime »

Re,

Bon ben c’est pas suffisant, va falloir passer au deepsleep :wink:

mais au moins j’ai une info bat que je n’avais pas avant. j’imagine que les dernières valeurs sont fausses, je sais que je suis parfaitement calé à 3.5V sur ce qui remonte et au multimètre.
Il faudra vérifier pour les valeurs mini et maxi ( ce que je n’ai pas encore fait ).

cdt

Re,

Reste à voir si ça fonctionne, récupération des heures de lever / coucher de soleil dans HA
pour fixer les heures de sommeil / réveil, j’ai pas besoin de savoir que j’ai de l’eau dans ma cuve la nuit :smiley:

Code de base
substitutions:
  name: esp-cuve
  friendly_name: esp-cuve

esphome:
  name: esp-cuve
  friendly_name: esp-cuve
  min_version: 2024.11.0
  name_add_mac_suffix: false

esp32:
  board: esp32dev
  framework:
    type: esp-idf

logger:
  level: INFO

# Enable Home Assistant API
api:
  encryption:
    key: xxx


# Allow Over-The-Air updates
ota:
- platform: esphome

# Example configuration entry
wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password

  # Optional manual IP
  manual_ip:
    static_ip: xxx
    gateway: xxx
    subnet: xxx

# Variables globales pour stocker les heures
globals:
  - id: sunset_hour
    type: int
    initial_value: '20'
  - id: sunset_minute
    type: int
    initial_value: '0'
  - id: sunrise_hour
    type: int
    initial_value: '7'
  - id: sunrise_minute
    type: int
    initial_value: '0'
  - id: sleep_programmed
    type: bool
    initial_value: 'false'

# Configuration Deep Sleep
deep_sleep:
  id: deep_sleep_control
  sleep_duration: 1min  # Sera remplacé dynamiquement

# Composant time
time:
  - platform: sntp
    id: sntp_time
    timezone: Europe/Paris
    on_time:
      # Déclenche la mise à jour des heures tous les jours à midi
      - seconds: 0
        minutes: 0
        hours: 12
        then:
          - homeassistant.service:
              service: homeassistant.update_entity
              data:
                entity_id: sensor.sun_next_setting
          - homeassistant.service:
              service: homeassistant.update_entity
              data:
                entity_id: sensor.sun_next_rising
          - delay: 5s
          - script.execute: update_sun_times
      # Vérifie toutes les minutes si il faut dormir
      - seconds: 0
        then:
          - script.execute: check_sleep_time

# Récupération des heures depuis Home Assistant
text_sensor:
  - platform: homeassistant
    id: ha_sunset
    entity_id: sensor.sun_next_setting
    name: "HA Coucher"
    on_value:
      then:
        - script.execute: update_sun_times
    
  - platform: homeassistant
    id: ha_sunrise
    entity_id: sensor.sun_next_rising
    name: "HA Lever"
    on_value:
      then:
        - script.execute: update_sun_times

  - platform: wifi_info
    ip_address:
      name: "IP Address"
    mac_address:
      name: "MAC Address"
  
  - platform: version
    name: "${name}_version"
  
  - platform: template
    name: "Uptime (Jours, Heures et Minutes)"
    lambda: |-
      int seconds = id(uptime_sensor).state;
      int days = seconds / 86400;
      seconds = seconds % 86400;
      int hours = seconds / 3600;
      seconds = seconds % 3600;
      int minutes = seconds / 60;
      return (days > 0 ? std::to_string(days) + " j " : "") + 
             (hours > 0 ? std::to_string(hours) + " h " : "") +
             (minutes > 0 ? std::to_string(minutes) + " min" : "0 min");
    update_interval: 60s
    entity_category: "diagnostic"

  # Affiche les heures programmées (format local)
  - platform: template
    name: "Coucher programmé"
    lambda: |-
      return str_sprintf("%02d:%02d", id(sunset_hour), id(sunset_minute));
    update_interval: 60s
    
  - platform: template
    name: "Lever programmé"
    lambda: |-
      return str_sprintf("%02d:%02d", id(sunrise_hour), id(sunrise_minute));
    update_interval: 60s

binary_sensor:
  - platform: status
    name: "Status"

sensor:
  - platform: ultrasonic
    trigger_pin: GPIO14
    echo_pin: GPIO27
    name: "Niveau_cuve"
    id: niveau_m
    update_interval: 2s
    timeout: 2m
  - platform: template
    id: esp_memory
    icon: mdi:memory
    name: ESP Free Memory
    lambda: return heap_caps_get_free_size(MALLOC_CAP_INTERNAL) / 1024;
    unit_of_measurement: 'kB'
    state_class: measurement
    entity_category: "diagnostic"
  - platform: internal_temperature
    name: "intern_temp"
  - platform: uptime
    name: "Uptime Raw"
    id: uptime_sensor
  - platform: wifi_signal
    name: "WiFi Signal Strength"
    update_interval: 60s
  - platform: adc
    pin: GPIO34 # Connecté à la sortie du diviseur de tension
    id: batt_cuve
    name: "Batt_cuve"
    update_interval: 30s
    attenuation: 12db  # OBLIGATOIRE pour mesurer plus de 1.1V
    filters:
      - multiply: 5.0 # Facteur pour convertir la tension mesurée en tension réelle
    unit_of_measurement: "V"
    accuracy_decimals: 2

# Scripts pour la gestion du deep sleep
script:
  # Mise à jour des heures depuis Home Assistant
  - id: update_sun_times
    then:
      - lambda: |-
          ESP_LOGI("sun_update", "=== MISE À JOUR DES HEURES SOLAIRES ===");
          
          // Parsing du coucher du soleil (format ISO: 2025-07-26T19:36:10+00:00)
          std::string sunset_str = id(ha_sunset).state;
          if (sunset_str.length() >= 19) {
            // Extraction de l'heure UTC
            int utc_hour = std::stoi(sunset_str.substr(11, 2));
            int utc_minute = std::stoi(sunset_str.substr(14, 2));
            
            // Conversion en heure locale (UTC+2 en été, UTC+1 en hiver)
            // Approximation simple: +2h en été (mars à octobre)
            auto time = id(sntp_time).now();
            int month = time.month;
            int hour_offset = (month >= 3 && month <= 10) ? 2 : 1;
            
            int local_hour = utc_hour + hour_offset;
            if (local_hour >= 24) local_hour -= 24;
            
            id(sunset_hour) = local_hour;
            id(sunset_minute) = utc_minute;
            
            ESP_LOGI("sun_update", "Coucher: %s UTC -> %02d:%02d local", 
                     sunset_str.c_str(), local_hour, utc_minute);
          }
          
          // Parsing du lever du soleil
          std::string sunrise_str = id(ha_sunrise).state;
          if (sunrise_str.length() >= 19) {
            int utc_hour = std::stoi(sunrise_str.substr(11, 2));
            int utc_minute = std::stoi(sunrise_str.substr(14, 2));
            
            // Conversion en heure locale
            auto time = id(sntp_time).now();
            int month = time.month;
            int hour_offset = (month >= 3 && month <= 10) ? 2 : 1;
            
            int local_hour = utc_hour + hour_offset;
            if (local_hour >= 24) local_hour -= 24;
            
            id(sunrise_hour) = local_hour;
            id(sunrise_minute) = utc_minute;
            
            ESP_LOGI("sun_update", "Lever: %s UTC -> %02d:%02d local", 
                     sunrise_str.c_str(), local_hour, utc_minute);
          }
          
          // Reset du flag de programmation pour permettre le prochain cycle
          id(sleep_programmed) = false;

  # Vérification si c'est l'heure de dormir
  - id: check_sleep_time
    then:
      - lambda: |-
          auto time = id(sntp_time).now();
          if (!time.is_valid()) return;
          
          int current_hour = time.hour;
          int current_minute = time.minute;
          
          // Vérifie si c'est l'heure du coucher
          if (current_hour == id(sunset_hour) && 
              current_minute == id(sunset_minute) && 
              !id(sleep_programmed)) {
            
            ESP_LOGI("sleep", "🌅 HEURE DU COUCHER ATTEINTE - ENDORMISSEMENT");
            id(sleep_programmed) = true;
            
            // Calcul du temps jusqu'au lever
            int current_total_minutes = current_hour * 60 + current_minute;
            int sunrise_total_minutes = id(sunrise_hour) * 60 + id(sunrise_minute);
            
            int sleep_minutes;
            if (sunrise_total_minutes <= current_total_minutes) {
              // Le lever est le lendemain
              sleep_minutes = (24 * 60) - current_total_minutes + sunrise_total_minutes;
            } else {
              // Le lever est plus tard aujourd'hui
              sleep_minutes = sunrise_total_minutes - current_total_minutes;
            }
            
            // Sécurité: minimum 10 minutes
            if (sleep_minutes < 10) sleep_minutes = 10;
            
            ESP_LOGI("sleep", "💤 Dodo pour %d minutes (%dh%02d)", 
                     sleep_minutes, sleep_minutes / 60, sleep_minutes % 60);
            
            // Programmation du deep sleep
            uint32_t sleep_ms = sleep_minutes * 60 * 1000;
            id(deep_sleep_control).set_sleep_duration(sleep_ms);
            id(deep_sleep_control).begin_sleep(true);
          }

switch:
  - platform: restart
    name: "Restart"

  - platform: template
    name: "Actualiser heures solaires"
    id: refresh_sun_times
    turn_on_action:
      - homeassistant.service:
          service: homeassistant.update_entity
          data:
            entity_id: sensor.sun_next_setting
      - homeassistant.service:
          service: homeassistant.update_entity
          data:
            entity_id: sensor.sun_next_rising
      - delay: 2s
      - script.execute: update_sun_times
      - switch.turn_off: refresh_sun_times
    
  - platform: template
    name: "Sleep immédiat (test)"
    id: test_sleep
    turn_on_action:
      - lambda: |-
          ESP_LOGI("sleep", "🧪 TEST - Deep sleep 2 minutes");
          id(deep_sleep_control).set_sleep_duration(2 * 60 * 1000);
          id(deep_sleep_control).begin_sleep(true);
      - switch.turn_off: test_sleep
      
  - platform: template
    name: "Empêcher Deep Sleep"
    id: prevent_sleep
    optimistic: true
    # Quand activé, empêche le deep sleep (utile pour maintenance)

Pour le deep sleep j’ai tenté de faire une prise de mesures toutes les heures, mais je suis vite passé sur une moyenne de 10 mesures toutes les heures car il y avait des écarts ou des faux quand je ne faisais qu’une seule mesure.
En gros l’esp se réveille, il fait 10 mesures, il calcul la moyenne, il m’envoie une donnée et se rendort.
J’avais aussi pensé à faire:

  • une plage de maintenance de 30 minutes au démarrage de l’appareil pour pouvoir accéder à ce dernier si besoin.
  • une plage de maintenance d’1 heure entre 12h et 13h pour avc aussi à l’appareil.
    Sans ces plages de maintenance, c’était trop difficile d’accéder à l’appareil à la volé.

Si ça peut aider…

Hello,

Une plage de maintenance pour quoi faire? maintenant depuis quelques versions sauf erreur, je n’ai testé qu’une fois, il me semble qu’on peut modifier le fichier de config de l’esp, et même si il est en deepsleep, si on lance l’install, ça va au bout, il me semble avoir lu quelque chose en ce sens mais je ne retrouve pas. sinon j’ai eu un énorme coup de bol quand je l’ai fait, c’est aussi possible.

cdt

Je n’arrivais pas à accéder à mon esp lorsqu’il était en deep sleep, d’où on idée de maintenance.
Je voulais aussi qu’il soit accessible pour ajouter des options plus tard, comme la commande on/off d’une mini pompe dans la cuve pour qu’elle soit pilotable depuis l’esp sur un pin libre.
Cela me permettait de me connecter à lui dans ces périodes et ne pas à avoir à le brancher en USB sur mon pc et de le laisser en place sur ma cuve.