Séchoir avec ventilation

Bonjour a tous, je vous présente ce sur quoi je travail en ce moment et par la même demander un peu d’aide afin d’intégrer une nouvelle « carte ».

Le projet:
Ma compagne s’installe en tisanerie (production des plants, récolte, séchage, conditionnement et vente) je suis donc mis à contribution pour le séchoir. L’idée est d’optimiser le séchage, pour cela j’ai créer une « boite » en bois avec des clayettes (la partie facile) et ensuite j’ai bossé sur la ventilation/régulation.

Le matériel:

  • ESP32 Kitdev v1 (avec un petit écran donc)
  • mosfet irlz44n
  • diode 1n4007
  • ventilateur 12v 2.89A
  • capteurs temp/hum sht31
  • bouton (pour changer les « modes »

le schéma:

le montage:

esphome:
  name: sechoir
  friendly_name: séchoir

esp32:
  board: esp32dev
  framework:
    type: arduino

# Enable logging

# Enable Home Assistant API
api:
  encryption:
    key: "xxxxxx"

ota:
  - platform: esphome
    password: "xxxxxx"

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  fast_connect: true
  reboot_timeout: 15min
  power_save_mode: none
  
  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "Sechoir Fallback Hotspot"
    password: "xxxxxx"

captive_portal:

# Configuration I2C
i2c:
  sda: 21
  scl: 22
  scan: true
  frequency: 100kHz

# Capteurs
sensor:
  - platform: sht3xd
    temperature:
      name: "Température Séchoir"
      id: temp_sechoir
      accuracy_decimals: 1
      filters:
        - sliding_window_moving_average:
            window_size: 3
            send_every: 2
    humidity:
      name: "Humidité Séchoir"
      id: humidity_sechoir
      accuracy_decimals: 1
      filters:
        - sliding_window_moving_average:
            window_size: 3
            send_every: 2
    address: 0x44
    update_interval: 15s

  - platform: template
    name: "Vitesse Ventilateurs"
    id: fan_speed_percent
    unit_of_measurement: "%"
    accuracy_decimals: 0
    
  - platform: template
    name: "Puissance Estimée"
    id: power_estimate
    unit_of_measurement: "W"
    accuracy_decimals: 1

  # Capteur de temps de fonctionnement
  - platform: template
    name: "Temps Séchage"
    id: drying_time
    unit_of_measurement: "h"
    accuracy_decimals: 1

  # AJOUT : Capteur pour diagnostiquer la vitesse réelle
  - platform: template
    name: "Vitesse Réelle Ventilateur"
    id: real_fan_speed
    unit_of_measurement: "%"
    accuracy_decimals: 0
    lambda: |-
      if (id(ventilateurs_sechoir).state) {
        return id(ventilateurs_sechoir).speed;
      } else {
        return 0;
      }
    update_interval: 5s

# Configuration des boutons physiques pour contrôle local
binary_sensor:
  # Bouton MODE (GPIO4 - avec pullup interne)
  - platform: gpio
    pin:
      number: GPIO4
      inverted: true
      mode:
        input: true
        pullup: true
    name: "Bouton Mode"
    id: button_mode
    filters:
      - delayed_on: 50ms
      - delayed_off: 50ms
    on_press:
      then:
        - lambda: |-
            // Cycle entre les modes de séchage
            int current_mode = id(drying_mode);
            current_mode = (current_mode + 1) % 4;  // 4 modes au total
            id(drying_mode) = current_mode;
            
            // Application des paramètres selon le mode
            switch(current_mode) {
              case 0: // Plantes délicates
                id(target_temp) = 35.0;
                id(max_humidity) = 45.0;
                id(min_fan_speed) = 0.2;
                ESP_LOGI("mode", "Mode: Plantes délicates (35°C, 45%% max)");
                break;
              case 1: // Standard
                id(target_temp) = 40.0;
                id(max_humidity) = 50.0;
                id(min_fan_speed) = 0.3;
                ESP_LOGI("mode", "Mode: Standard (40°C, 50%% max)");
                break;
              case 2: // Rapide
                id(target_temp) = 45.0;
                id(max_humidity) = 40.0;
                id(min_fan_speed) = 0.4;
                ESP_LOGI("mode", "Mode: Rapide (45°C, 40%% max)");
                break;
              case 3: // Personnalisé
                id(target_temp) = 42.0;
                id(max_humidity) = 45.0;
                id(min_fan_speed) = 0.35;
                ESP_LOGI("mode", "Mode: Personnalisé (42°C, 45%% max)");
                break;
            }
            
            // Force la mise à jour de l'écran
            id(mon_display).update();


  # Alertes de sécurité
  - platform: template
    name: "Alerte Température Haute"
    id: temp_high_alert
    lambda: |-
      if (!id(temp_sechoir).has_state()) return false;
      return id(temp_sechoir).state > 50;
    filters:
      - delayed_on: 60s
      - delayed_off: 30s
    on_press:
      then:
        - logger.log: 
            level: WARN
            format: "ALERTE: Température trop élevée %.1f°C - ARRÊT DE SÉCURITÉ"
            args: [ 'id(temp_sechoir).state' ]
        - switch.turn_off: sechoir_active
            
  - platform: template
    name: "Alerte Humidité Haute"  
    id: humidity_high_alert
    lambda: |-
      if (!id(humidity_sechoir).has_state()) return false;
      return id(humidity_sechoir).state > 75;
    filters:
      - delayed_on: 300s  # 5 minutes avant alerte
      - delayed_off: 60s
    on_press:
      then:
        - logger.log:
            level: WARN
            format: "ALERTE: Humidité persistante %.1f%% - Vérifier ventilation"  
            args: [ 'id(humidity_sechoir).state' ]

# Écran avec informations complètes
display:
  - platform: ssd1306_i2c
    model: "SSD1306 128x64"
    address: 0x3C
    id: mon_display
    update_interval: 2s
    lambda: |-
      // Titre et ligne de séparation
      it.printf(0, 0, id(font1), "SECHOIR PLANTES");
      it.line(0, 12, 128, 12);
      
      // Données de capteurs
      if (id(temp_sechoir).has_state() && id(humidity_sechoir).has_state()) {
        it.printf(0, 16, id(font2), "%.1f°C  %.0f%%", 
          id(temp_sechoir).state, id(humidity_sechoir).state);
      }
      
      // Mode de séchage
      const char* modes[] = {"Delicat", "Standard", "Rapide", "Perso"};
      it.printf(0, 28, id(font2), "Mode: %s", modes[id(drying_mode)]);
      
      // Cible et ventilation
      it.printf(0, 40, id(font2), "Cible: %.0f°C", id(target_temp));
      if (id(fan_speed_percent).has_state()) {
        it.printf(70, 40, id(font2), "V:%.0f%%", id(fan_speed_percent).state);
      }
      
      // État et temps
      if (id(sechoir_active).state) {
        it.printf(0, 52, id(font2), "ACTIF");
        if (id(drying_time).has_state()) {
          it.printf(50, 52, id(font2), "%.1fh", id(drying_time).state);
        }
      } else {
        it.printf(0, 52, id(font2), "PRET - APPUI ONOFF");
      }

# Polices optimisées
font:
  - file: "font/arial.ttf"
    id: font1
    size: 12
  - file: "font/arial.ttf"
    id: font2
    size: 10

# Sortie PWM pour ventilateurs
output:
  - platform: ledc
    pin: GPIO18
    id: fan_pwm_output
    frequency: 25000 Hz
    
# Contrôleur de ventilateurs - CORRIGÉ
fan:
  - platform: speed
    output: fan_pwm_output
    name: "Ventilateurs Séchoir"
    id: ventilateurs_sechoir
    speed_count: 100  # AJOUT : Force 100 niveaux de vitesse explicites

# Variables globales pour fonctionnement autonome
globals:
  # Paramètres de régulation (modifiés par les modes)
  - id: target_temp
    type: float
    restore_value: true
    initial_value: '40.0'  # Standard par défaut
  - id: temp_tolerance
    type: float
    restore_value: true
    initial_value: '2.0'
  - id: max_humidity
    type: float
    restore_value: true
    initial_value: '50.0'  # Standard par défaut
  - id: min_fan_speed
    type: float
    restore_value: true
    initial_value: '0.3'   # Standard par défaut
  
  # Mode de séchage (0=délicat, 1=standard, 2=rapide, 3=personnalisé)
  - id: drying_mode
    type: int
    restore_value: true
    initial_value: '1'  # Mode standard par défaut
  
  # Compteur de temps
  - id: start_time
    type: unsigned long
    restore_value: false
    initial_value: '0'

# Interrupteur principal (peut être contrôlé via web ou bouton)
switch:
  - platform: template
    name: "Séchoir Actif"
    id: sechoir_active
    restore_mode: RESTORE_DEFAULT_OFF
    optimistic: true
    turn_on_action:
      - logger.log: "Séchoir activé - Mode autonome"
      - lambda: 'id(start_time) = millis();'
      - component.update: mon_display
    turn_off_action:
      - logger.log: "Séchoir désactivé"
      - fan.turn_off: ventilateurs_sechoir
      - lambda: 'id(drying_time).publish_state(0);'
      - component.update: mon_display

# Automatisation principale - TOTALEMENT AUTONOME
interval:
  # Régulation principale toutes les 15 secondes
  - interval: 15s
    then:
      - lambda: |-
          // Mise à jour du temps de fonctionnement
          if (id(sechoir_active).state && id(start_time) > 0) {
            unsigned long elapsed = millis() - id(start_time);
            float hours = elapsed / 3600000.0;  // Conversion en heures
            id(drying_time).publish_state(hours);
          }
          
          // Si séchoir inactif, arrêter les ventilateurs
          if (!id(sechoir_active).state) {
            id(ventilateurs_sechoir).turn_off();
            id(fan_speed_percent).publish_state(0);
            return;
          }
          
          // Vérifier que les capteurs sont prêts
          if (!id(temp_sechoir).has_state() || !id(humidity_sechoir).has_state()) {
            ESP_LOGW("main", "Capteurs non prêts, attente...");
            return;
          }
          
          float current_temp = id(temp_sechoir).state;
          float current_humidity = id(humidity_sechoir).state;
          float target = id(target_temp);
          float tolerance = id(temp_tolerance);
          float max_hum = id(max_humidity);
          float min_speed = id(min_fan_speed);
          
          float fan_speed = 0.0;
          
          // === ALGORITHME DE RÉGULATION AVANCÉ ===
          
          // 1. Régulation basée sur la température
          if (current_temp < (target - tolerance)) {
            // Température trop basse - ventilation minimale pour circulation
            fan_speed = min_speed;
          } else if (current_temp > (target + tolerance)) {
            // Température trop haute - ventilation maximale
            fan_speed = 1.0;
          } else {
            // Dans la plage cible - régulation proportionnelle
            float temp_diff = current_temp - (target - tolerance);
            float temp_range = 2 * tolerance;
            fan_speed = min_speed + (1.0 - min_speed) * (temp_diff / temp_range);
          }
          
          // 2. Correction pour l'humidité excessive
          if (current_humidity > max_hum) {
            float humidity_excess = current_humidity - max_hum;
            float humidity_factor = humidity_excess / 25.0;  // Sur 25% d'excès
            fan_speed = std::max(fan_speed, std::min(1.0f, 0.6f + humidity_factor));
          }
          
          // 3. Boost automatique si humidité très élevée (>65%)
          if (current_humidity > 65.0) {
            fan_speed = std::max(fan_speed, 0.8f);
            ESP_LOGI("regulation", "Boost humidité activé: %.1f%%", current_humidity);
          }
          
          // 4. Protection surchauffe
          if (current_temp > 48.0) {
            fan_speed = 1.0;  // Ventilation maximale
            ESP_LOGW("regulation", "Protection surchauffe: %.1f°C", current_temp);
          }
          
          // 5. Application de la vitesse calculée - CORRIGÉ
          fan_speed = std::max(0.0f, std::min(1.0f, fan_speed));
          
          if (fan_speed > 0.05) {
            // CORRECTION PRINCIPALE : convertir explicitement en pourcentage entier
            int speed_percent = static_cast<int>(fan_speed * 100);
            speed_percent = std::max(1, std::min(100, speed_percent)); // Force entre 1-100
            
            auto call = id(ventilateurs_sechoir).turn_on();
            call.set_speed(speed_percent);  // Envoie un entier, pas un float
            call.perform();
            
            // Log pour debug
            ESP_LOGI("fan_control", "Vitesse calculée: %.2f -> Envoyé: %d%%", fan_speed, speed_percent);
          } else {
            id(ventilateurs_sechoir).turn_off();
            ESP_LOGI("fan_control", "Ventilateur arrêté");
          }
          
          // Mise à jour des capteurs de suivi
          id(fan_speed_percent).publish_state(fan_speed * 100);
          id(power_estimate).publish_state(fan_speed * 2.85 * 12 * 0.8);
          
          // Log de debug
          ESP_LOGI("regulation", "T:%.1f°C H:%.1f%% Fan:%.0f%% Mode:%d", 
            current_temp, current_humidity, fan_speed*100, id(drying_mode));

  # Cycle automatique de fin de séchage (optionnel)
  - interval: 300s  # Toutes les 5 minutes
    then:
      - lambda: |-
          // Arrêt automatique si séchage terminé (conditions optimales atteintes)
          if (id(sechoir_active).state && 
              id(temp_sechoir).has_state() && 
              id(humidity_sechoir).has_state()) {
            
            float temp = id(temp_sechoir).state;
            float humidity = id(humidity_sechoir).state;
            float target = id(target_temp);
            unsigned long elapsed = millis() - id(start_time);
            float hours = elapsed / 3600000.0;
            
            // Conditions d'arrêt automatique après minimum 4h
            if (hours > 4.0 && 
                abs(temp - target) < 1.0 && 
                humidity < (id(max_humidity) - 5.0)) {
              
              ESP_LOGI("auto_stop", "Séchage terminé automatiquement après %.1fh", hours);
              id(sechoir_active).turn_off();
            }
          }

  # Diagnostic système toutes les minutes
  - interval: 60s
    then:
      - lambda: |-
          ESP_LOGI("diagnostic", "WiFi: %s, Uptime: %ds, Free RAM: %d", 
            WiFi.isConnected() ? "OK" : "Offline",
            millis()/1000,
            ESP.getFreeHeap()
          );

# Logs système
logger:
  level: INFO
  logs:
    sensor: INFO
    fan: INFO
    wifi: WARN  # Réduit car WiFi optionnel
    api: WARN   # Réduit car API optionnelle

Pour ne rien vous cacher mes compétences en code sont nuls j’ai donc demander un code à une IA (claude 4) pour avoir une base et je l’ai retravailler pour corriger/modifier des choses.

En théorie tous marche (je dis en théorie car l’humidité la ou je test est à plus de 75% du coup le ventilateur se met à fond directement!!! Il diminue bien si je le fait manuellement reste à le mettre en place pour tester en condition!!

Prochaine étape, je souhaiterai réutiliser l’alimentation d’origine du ventilateur (actuellement je travail avec une alimentation stabilisé de bureau). Celle-ci comprend un potentiomètre, je souhaiterai modifier cette « carte » pour réutiliser l’alimentation d’origine.
Là je sèche un peu… si vous avez des idées…

la « carte »


Le branchement à la prise est en haut…

N’hésitez pas à me donner votre avis!!

edit: je l’ai mis dans vos projets plutot que question…

je relance un coup concernant la « carte » transformateur 12/220v avec le variateur… une idée pour la mixer à mon projet?