Tracker solaire DIY perturbé par perte Wifi

La précision de suivi est uniquement géré par precision_offset, qui est finalement la bande morte de l’asservissement proportionnel.

Offset_roulis et offset_tangage, sont justement là pour corriger des imprécisions sur les 2 axes. Mais c’est quelque part redondant avec les corrections sur l’IMU. Si tu as bien calibré ton IMU, tu ne devrais plus avoir de problème à devoir corriger sur tangage et roulis. D’ailleurs dans mon code, ces deux variables sont à 0.0.

Si tu veux, remets ton code en entier que j’y rejette un coup d’oeil.

Pour avoir un ordre d’idée, voici les trajectoires suivies par mon tracker sur la journée d’hier :

ça fait maintenant 1 an qu’il tourne tous les jours.

En ce moment je suis entrain de refaire toute la structure, pour la passer en aluminium, à la place du bois. Et ajouter 1 panneau, pour passer de 2 à 3, et atteindre 1250Wc.

Et pour la petite histoire, j’injecte l’intégralité de la production dans une batterie DIY de 16kWh, puis ensuite un onduleur vient fournir une partie de la maison.

En parallèle de la structure en aluminium, je me conçois une station météo pour mettre le tracker en position storm de façon plus adéquat plutôt que de récupérer les états de météoFrance.

C’est en cours d’impression 3D, vivement ce soir :slight_smile:

Et le troisième sujet en parallèle, c’est la refonte complète du code du tracker solaire.
Je suis entrain d’intégrer le PID Climate — ESPHome , pour asservir en PID la position du tracker.
J’ai quasiment terminé tous les tests, j’en suis à la recherche des bons facteurs, Kp, Ki, et Kd.

Si intéressé par l’un des sujets, je pourrais entrer dans le détail.

Effectivement, ton suivi est vraiment optimisé.

J’ai encore du chemin à faire pour en arriver là :thinking:

Voici mon code complet :

# ESPHome configuration file for 
# solar-tracker module
# https://forum.hacf.fr/t/tracker-solaire-diy-perturbe-par-perte-wifi/47291/18

substitutions:
  esphome_name: solar-tracker
  logger_level: INFO
  logger_baud_rate: '0'
  static_ip: !secret ip_solar_tracker
  power_save_mode: none


  # CONSTANTES D'INSTALLATION
  HOME_LAT: XX.XXXXX
  HOME_LON: XX.XXXXX

  angle_min_roulis: '-60.0'
  angle_max_roulis: '60.0'
  angle_min_tangage: '-60.0'
  angle_max_tangage: '60.0'
  boucle_asservissement: '100ms'  # vitesse de la boucle d'asservissement
  boucle_mesure: '10s'            # vitesse de la boucle de mesure des roulis et tangage
  max_speed_offset: '12.0'        # si la consigne est à plus de 12°, vitesse vérin à 100%
  offset_accX: '0.02'             # Calibration grossière de l'IMU, à faire sur table nivelé au tout début
  offset_accY: '0.68'             # Calibration grossière de l'IMU, à faire sur table nivelé au tout début
  offset_accZ: '-0.05'            # Calibration grossière de l'IMU, à faire sur table nivelé au tout début
  offset_anemo: 0                 # Calibration de l'anémomètre
  offset_azimuth: '-5.0'          # mon système fonctionne qui si tu as l'axe du roulis colinéaire avec l'axe Nord-Sud, ça permet d'ajuster l'erreur
  offset_tangage: '0.0'           # permet d'ajuster des erreurs sur le tangage
  offset_roulis: '0.0'            # permet d'ajuster des erreurs sur le roulis
  precision_offset: '3.0'         # angle cône de suivi du soleil
  rapport_kmh_v: 90               # Rapport entre la vitesse du vent relevé par l'anémomètre et la tension relevée (1v - 25m/s => 90 km/h)
  repos_tangage: '-0.0'           # angle tangage repos mode Nuit/Tempête par rapport à l'horizontal
  repos_roulis: '-0.0'            # angle roulis repos mode Nuit/Tempête par rapport à l'horizontal

  vitesse_mini_verins: '20'       # Vitesse minimales d'approche des vérins
  
  PUSHED_TO_HA: 'False'
  KEEP_INTERNE: 'True'            # Basculer cette constante à False pour faire remonter tous les sensors dans HA
  PI: '3.14159265359'
  
  # GPIO
  GPIO_FREQ_PWM: '40000Hz'
  GPIO_SDA: '21'
  GPIO_SCL: '22'

packages:
  base: !include .config-base.yaml
  wifi: !include .config-wifi.yaml
  ota: !include .config-ota.yaml
  logger: !include .config-logger.yaml
  portal: !include .config-portal.yaml
  api: !include .config-api.yaml
  status: !include .config-status.yaml
  web_server: !include .config-web-server.yaml

esp32:
  board: esp32dev



# Module Definition

# Définition de la communication I2C pour le capteur MPU6050
i2c:
  sda: 
    number: ${GPIO_SDA}
  scl: 
    number: ${GPIO_SCL}
  scan: true
  id: bus_a

# Définition des sorties PWM pour le H-Bridge DBH-12V
output:
  # Moteur A = Tangage
  # Moteur A Pin IN1A du pont en H (IN1A <=> GPIO13)
  - platform: ledc
    id: tangage_pin_IN1A
    pin: GPIO13
    frequency: ${GPIO_FREQ_PWM}

  # Moteur A Pin B du pont en H (IN2A <=> GPIO12)
  - platform: ledc
    id: tangage_pin_IN2A
    pin: GPIO12
    frequency: ${GPIO_FREQ_PWM}

  # Moteur B = Roulis
  # Moteur B Pin A du pont en H (IN1B <=> GPIO14)
  - platform: ledc
    id: roulis_pin_IN1B
    pin: GPIO14
    frequency: ${GPIO_FREQ_PWM}

  # Moteur B Pin B du pont en H (IN2B <=> GPIO27)
  - platform: ledc
    id: roulis_pin_IN2B
    pin: GPIO27
    frequency: ${GPIO_FREQ_PWM}
    
# Dans Esphome, le pilotage des vérins à travers un pont en H, se fait avec la classe FAN.
# Option: 
#   un nombre incrémental à chaque action des vérins pour avoir une idée du nombre de fois
#   par jour qu’ils sont mis en action.
# Configuration du moteur via le composant fan hbridge
fan:
  # Moteur A
  - platform: hbridge
    id: tangage_verin
    name: "Verin Tangage - H-Bridge"
    pin_a: tangage_pin_IN1A
    pin_b: tangage_pin_IN2A
    decay_mode: SLOW
    internal: ${PUSHED_TO_HA}
    on_turn_on: 
      then:
        - number.increment: number_tangage

  # Moteur B
  - platform: hbridge
    id: roulis_verin
    name: "Verin Roulis - H-Bridge"
    pin_a: roulis_pin_IN1B
    pin_b: roulis_pin_IN2B
    decay_mode: SLOW
    internal: ${PUSHED_TO_HA}
    on_turn_on: 
      then:
        - number.increment: number_roulis

# Boucle d'asservissement des positions des vérins
# Arguments :
#   - offset : écart entre la position du soleil et la position réel des panneaux sur l'axe choisi
# Description :
#   En fonction du signe, le pilotage se fait dans un sens ou dans l'autre (REVERSE/FORWARD).
#   En fonction de la valeur, la vitesse des vérins est plus ou moins importante. Plus l'écart est
#   important, plus la vitesse d'approche est grande, plus on s'approche de la consigne, plus la 
#   vitesse est lente (ceci pour éviter les chocs brutaux). Plus la $boucle_asservissement est petite,
#   plus l'approche sera douce.
#   Une vitesse minimale (vitesse_mini_verins) est défini pour assurer un bon fonctionnement des vérins.
#   Cette vitesse minimale dépend du vérin, elle est défini par expérimentation.
# Rappel :
#   - Roll  : Roulis
#   - Pitch : Tangage
script:
  - id: RollPilot
    mode: single
    parameters:
      offset: float
    then:
      - lambda: |-
          if (abs(offset)>$precision_offset) {
            int speed = 0;
            if(abs(offset)>$max_speed_offset) {
              speed = 100;
            }
            else {
              speed = ${vitesse_mini_verins} + (40*abs(offset)/$max_speed_offset);
            }
            if (offset>0) {
              auto call = id(roulis_verin).turn_on();
              call.set_speed(speed);
              call.set_direction(FanDirection::FORWARD);
              call.perform();
            } else {
              auto call = id(roulis_verin).turn_on();
              call.set_speed(speed);
              call.set_direction(FanDirection::REVERSE);
              call.perform();
            }
          }
          else {
            auto call = id(roulis_verin).turn_off();
            call.perform();
          }
  - id: PitchPilot
    mode: single
    parameters:
      offset: float
    then:
      - lambda: |-
          if (abs(offset)>$precision_offset) {
            int speed = 0;
            if(abs(offset)>$max_speed_offset) {
              speed = 100;
            }
            else {
              speed = ${vitesse_mini_verins} + (40*abs(offset)/$max_speed_offset);
            }
            if (offset>0) {
              auto call = id(tangage_verin).turn_on();
              call.set_speed(speed);
              call.set_direction(FanDirection::REVERSE);
              call.perform();
            } else {
              auto call = id(tangage_verin).turn_on();
              call.set_speed(speed);
              call.set_direction(FanDirection::FORWARD);
              call.perform();
            }
          }
          else {
            auto call = id(tangage_verin).turn_off();
            call.perform();
          }

# Boutons de contrôle manuel
button:
  # Bouton pour faire avancer le Verin N/S rapidement
  - platform: template
    name: "Verin N/S - Action - Avance - 80 %"
    on_press:
      - fan.turn_on:
          id: tangage_verin
          speed: 80
          direction: FORWARD

  # Bouton pour faire avancer le Verin N/S lentement
  - platform: template
    name: "Verin N/S - Action - Avance - 20 %"
    on_press:
      - fan.turn_on:
          id: tangage_verin
          speed: 20
          direction: FORWARD

  # Bouton pour faire reculer le Verin N/S rapidement
  - platform: template
    name: "Verin N/S - Action - Recule - 80 %"
    on_press:
      - fan.turn_on:
          id: tangage_verin
          speed: 80
          direction: REVERSE

  # Bouton pour faire reculer le Verin N/S lentement
  - platform: template
    name: "Verin N/S - Action - Recule - 20 %"
    on_press:
      - fan.turn_on:
          id: tangage_verin
          speed: 20
          direction: REVERSE

  # Bouton d'arrêt du Verin N/S
  - platform: template
    name: "Verin N/S - Action - Stop"
    on_press:
      - fan.turn_off: tangage_verin

  # Bouton pour faire tourner le Verin E/O vers l'avant rapidement
  - platform: template
    name: "Verin E/O - Action - Avance - 80 %"
    on_press:
      - fan.turn_on:
          id: roulis_verin
          speed: 80
          direction: FORWARD

  # Bouton pour faire tourner le Verin E/O vers l'avant lentement
  - platform: template
    name: "Verin E/O - Action - Avance - 20 %"
    on_press:
      - fan.turn_on:
          id: roulis_verin
          speed: 20
          direction: FORWARD

  # Bouton pour faire tourner le Verin E/O vers l'arrière rapidement
  - platform: template
    name: "Verin E/O - Action - Recule - 80 %"
    on_press:
      - fan.turn_on:
          id: roulis_verin
          speed: 80
          direction: REVERSE

  # Bouton pour faire tourner le Verin E/O vers l'arrière lentement
  - platform: template
    name: "Verin E/O - Action - Recule - 20 %"
    on_press:
      - fan.turn_on:
          id: roulis_verin
          speed: 20
          direction: REVERSE

  # Bouton d'arrêt du Verin E/O
  - platform: template
    name: "Verin E/O - Action - Stop"
    on_press:
      - fan.turn_off: roulis_verin

# Capteur de statut du moteur
binary_sensor:
  - platform: template
    name: "Verin N/S - Etat - En marche"
    lambda: |-
      return id(tangage_verin).state;
  - platform: template
    name: "Verin E/O - Etat - En marche"
    lambda: |-
      return id(roulis_verin).state;

# Description :
#   Affichage MAC Address pour debugging
# text_sensor:
#   - platform: wifi_info
#     mac_address:
#       name: $device_name_friendly MAC Address
#       id:   text_sensor_mac_address

# Incréments des actions des vérins
# Description :
#   Ces nombres sont créés pour connaitre le nombre de fois qu'un vérin est actionné. Ces compteurs
#   sont remis à 0 en fin de journée par la classe SUN. Il peuvent être remontés à HA via l'attribut
#   'internal' en le passant de 'KEEP_INTERNE' à 'PUSHED_TO_HA'
number:
  - platform: template
    internal: $KEEP_INTERNE
    id: number_tangage
    restore_value: True
    min_value: 0
    max_value: 9999
    step: 1
    optimistic: True
    on_value: 
      then:
        - lambda: "return id(sensor_number_tangage).publish_state(x);"

  - platform: template
    id: number_roulis
    internal: $KEEP_INTERNE
    restore_value: True
    min_value: 0
    max_value: 9999
    step: 1
    optimistic: True
    on_value: 
      then:
        - lambda: "return id(sensor_number_roulis).publish_state(x);"

# Déclaration de capteurs
sensor:
  # Description :
  #   Récupération de la tension sur l'anémomètre
  # Utilisation :
  #   - calcul de la vitesse du vent 'vent_kmh'
  - platform: adc
    pin: GPIO34
    name: "Anémomètre"
    id: anemo_tension
    internal: $KEEP_INTERNE
    filters:
    - offset : $offset_anemo
    - clamp:
        min_value: 0.1
        max_value: 3.0
        ignore_out_of_range: False
    attenuation : 12db # Range 0,075 V ~ 3,12 V
    accuracy_decimals: 2
    unit_of_measurement: V
    update_interval: $boucle_asservissement

  # Description :
  #   Déclaration des capteurs d'actions des vérins.
  # Utilisation :
  #   Ces capteurs sont utilisés par les numbers 'number_roulis' et 'number_tangage'
  - platform: template
    name: action verin roulis
    id: sensor_number_roulis

  - platform: template
    name: action verin tangage
    id: sensor_number_tangage

  # Description
  #   Déclaration des capteurs d'accélération du MPU6050
  # Paramètrages :
  #   - address         : adresse du capteur sur le bus I2C
  #   - update_interval : intervalle de vérification du capteur. Tous les capteurs du
  #                       MPU6050 seront rafraichis à cette fréquence.
  - platform: mpu6050
    address: 0x68
    update_interval: 500ms

    # Description
    #   Déclaration des capteurs d'accélération du MPU6050
    # Utilisation :
    #   Ces capteurs 'accel_x', 'accel_y', 'accel_z' sont utilisés pour le calcul des
    #   angles des panneaux dans les capteurs 'PV_roulis' et 'PV_tangage'
    accel_x:
      id: accel_x
      name: "MPU Accel X"
      internal: $KEEP_INTERNE
      filters:
        # - multiply: -1.0
        - offset: $offset_accX
        - clamp:
            min_value: -9.81
            max_value: 9.81
        - exponential_moving_average:
            alpha: 0.20
            send_every: 2
      #  - sliding_window_moving_average:
      #     window_size: 10
      #     send_every: 5
    accel_y:
      id: accel_y
      name: "MPU Accel Y"
      internal: $KEEP_INTERNE
      filters:
        # - multiply: -1.0
        - offset: $offset_accY
        - clamp:
            min_value: -9.81
            max_value: 9.81
        - exponential_moving_average:
            alpha: 0.20
            send_every: 2
      #  - sliding_window_moving_average:
      #     window_size: 10
      #     send_every: 5
    accel_z:
      id: accel_z
      name: "MPU Accel Z"
      internal: $KEEP_INTERNE
      filters:
        - multiply: -1.0
        - offset: $offset_accZ
        - timeout: 1s
        - clamp:
            min_value: 0.01                 # IMU à l'envers, tombé
            max_value: 9.81
            ignore_out_of_range: false
        - exponential_moving_average:
            alpha: 0.20
            send_every: 2
      #  - sliding_window_moving_average:
      #     window_size: 10
      #     send_every: 5

  # Description
  #   Déclaration du capteur de température du MPU6050
  # Utilisation :
  #   Ce capteur est utilisé pour remonter la température à HA par l'intermédiaire
  #   du capteur 'MPU température'
    temperature:
      id: MPU_temp
      name: "solar-tracker Temperature"
      filters:
       - sliding_window_moving_average:
          window_size: 10
          send_every: 5

  # Description
  #   Calcul de l'offset
  # Utilisation :
  #   Le calcul des offset permet d'étalonner le capteur pour qu'il indique les bonnes
  #   valeurs d'accélération en m/s² une fois positionner sur l'installation.
  #   Une fois le capteur IMU positionné sur le traqueur, et le traqueur en 
  #   position horizontale avec l'axe Z colinéaire avec l'axe vertical, l'axe des X
  #   colinéaire avec l'axe Nord/Sud et l'axe des Y colinéaire avec l'axe Est/Ouest, 
  #   relevez ces valeurs d'offset.
  #   Ces capteurs ne servent qu'à l'étalonnage, il faut les commenter une fois 
  #   l'étalonnage effectué.
  # - platform: template
  #   id: accel_x_offset
  #   name: "MPU Accel X offset"
  #   unit_of_measurement: m/s²
  #   accuracy_decimals: 2
  #   update_interval: $boucle_asservissement
  #   lambda: return 9.81 - id(accel_x).state;
  # - platform: template
  #   id: accel_y_offset
  #   name: "MPU Accel Y offset"
  #   unit_of_measurement: m/s²
  #   accuracy_decimals: 2
  #   update_interval: $boucle_asservissement
  #   lambda: return 9.81 - id(accel_y).state;
  # - platform: template
  #   id: accel_z_offset
  #   name: "MPU Accel Z offset"
  #   unit_of_measurement: m/s²
  #   accuracy_decimals: 2
  #   update_interval: $boucle_asservissement
  #   lambda: return 9.81 - id(accel_z).state;

  # Description
  #   Déclaration des capteurs d'accélération du MPU6050 corrigé par l'offset
  # Utilisation :
  #   Ces capteurs 'accel_x', 'accel_y', 'accel_z' sont utilisés pour l'étalonnage 
  #   des valeurs d'offset.
  #   Une fois les offsets renseignés (voir plus haut 'Calcul de l'offset'), vérifiez
  #   que les valeurs remontées par ces capteurs sont bien 9.81 m/s² lorsque le traqueur
  #   est en position à plat.
  # - platform: template
  #   id: accel_x_corrected
  #   name: "MPU Accel X with offset"
  #   unit_of_measurement: m/s²
  #   accuracy_decimals: 2
  #   update_interval: $boucle_asservissement
  #   lambda: return id(accel_x).state + $offset_accX;
  # - platform: template
  #   id: accel_y_corrected
  #   name: "MPU Accel Y with offset"
  #   unit_of_measurement: m/s²
  #   accuracy_decimals: 2
  #   update_interval: $boucle_asservissement
  #   lambda: return id(accel_y).state + $offset_accY;
  # - platform: template
  #   id: accel_z_corrected
  #   name: "MPU Accel Z with offset"
  #   unit_of_measurement: m/s²
  #   accuracy_decimals: 2
  #   update_interval: $boucle_asservissement
  #   lambda: return id(accel_z).state + $offset_accZ;

  # Description
  #   Calcul de l'angle des panneaux par rapport à l'horizontal sur l'axe E/O.
  #   Ce capteur est calculé à la fréquence définie par la variable 'boucle_asservissement'
  #   , souvent très rapide, quelques millsecondes. Pour éviter la surcharge du WiFi, on les
  #   garde en interne de l'ESP par 'internal: $KEEP_INTERNE'.
  #   Si l'angle calculé n'est pas compris entre les butées logicielles 'angle_min_roulis'
  #   'angle_max_roulis', le mode manuel est activé, ce qui inhibe l'asservissement. Ceci pour
  #   protéger la mécanique des vérins si, par exemple, le capteur tombe.
  # Utilisation :
  #   - Remonté dans HA via le capteur 'PV roulis'
  #   - Par le capteur 'OffsetRoll'
  - platform: template
    id: PV_roulis
    name: Roulis PV
    internal: $KEEP_INTERNE
    unit_of_measurement: °
    update_interval: $boucle_asservissement
    lambda: return atan2(id(accel_y).state, id(accel_z).state)*180/$PI;
    filters:
      - offset : $offset_roulis
    on_value_range:                       # si calcul d'un angle supérieur au réalisable mécanique, alors problème, arrêt de l'asservissement
      - above: $angle_max_roulis          # 60.0 
        then:
          - switch.turn_on: manual_mode
      - below: $angle_min_roulis          # -60.0
        then:
          - switch.turn_on: manual_mode
            
  # Description
  #   Calcul de l'angle des panneaux par rapport à l'horizontal sur l'axe N/S.
  #   Ce capteur est calculé à la fréquence définie par la variable 'boucle_asservissement'
  #   , souvent très rapide, quelques millsecondes. Pour éviter la surcharge du WiFi, on les
  #   garde en interne de l'ESP par 'internal: $KEEP_INTERNE'.
  #   Si l'angle calculé n'est pas compris entre les butées logicielles 'angle_min_tangage'
  #   'angle_max_tangage', le mode manuel est activé, ce qui inhibe l'asservissement. Ceci pour
  #   protéger la mécanique des vérins si, par exemple, le capteur tombe.
  # Utilisation :
  #   - Remonté dans HA via le capteur 'PV tangage'
  #   - Par le capteur 'OffsetPitch'
  - platform: template
    id: PV_tangage
    name: Tangage PV
    internal: $KEEP_INTERNE
    unit_of_measurement: °
    update_interval: $boucle_asservissement
    lambda: return atan2(id(accel_x).state, id(accel_z).state)*180/$PI;
    filters:
      - offset : $offset_tangage
    on_value_range:                       # si calcul d'un angle supérieur au réalisable mécanique, alors problème, arrêt de l'asservissement
      - above: $angle_max_tangage         # 35.0
        then:
          - switch.turn_on: manual_mode
      - below: $angle_min_tangage         # -53.0
        then:
          - switch.turn_on: manual_mode

  # Description
  #   Ces capteurs sont des copies des capteurs qui sont remonter dans HA à la fréquence choisie
  #   par la variable 'boucle_mesure'
  # Utilisation :
  #   - Remontés dans HA
  - platform: template
    name: Roulis PV
    unit_of_measurement: °
    accuracy_decimals: 1
    update_interval: $boucle_mesure
    lambda: return id(PV_roulis).state;

  - platform: template
    name: Tangage PV
    unit_of_measurement: °
    accuracy_decimals: 1
    update_interval: $boucle_mesure
    lambda: return id(PV_tangage).state;

  - platform: template
    name: MPU température
    device_class: temperature
    update_interval: 60s
    lambda: return id(MPU_temp).state;

  - platform: template
    name: Vitesse vent
    unit_of_measurement: Km/h
    accuracy_decimals: 0
    update_interval: $boucle_mesure
    lambda: return id(vent_kmh).state;

  # Description
  #   Calcul de l'offset du roulis entre la valeur du soleil et la valeur des panneaux.
  #   Ce calcul est effectué à la fréquence prévue par la variable 'boucle_asservissement'.
  #   L'offset calculé est transmis au script RollPilot uniquement si le fonctionnement
  #   n'est pas en mode manuel.
  #   Ce calcul n'est pas remonté à HA ('internal: $KEEP_INTERNE')
  # Utilisation :
  #   - script RollPilot
  - platform: template
    id: OffsetRoll
    name: Roulis Offset
    internal: $KEEP_INTERNE
    update_interval: $boucle_asservissement
    lambda: return id(PV_roulis).state - id(sunRoulis).state;
    on_value: 
      - if:
          condition:
            - switch.is_off: manual_mode # vérification du mode manuel, si ON alors pas de pilotage
          then:
            - lambda: id(RollPilot)->execute(x);

  # Description
  #   Calcul de l'offset du tangage entre la valeur du soleil et la valeur des panneaux.
  #   Ce calcul est effectué à la fréquence prévue par la variable 'boucle_asservissement'.
  #   L'offset calculé est transmis au script PitchPilot uniquement si le fonctionnement
  #   n'est pas en mode manuel.
  #   Ce calcul n'est pas remonté à HA ('internal: $KEEP_INTERNE')
  # Utilisation :
  #   - script PitchPilot
  - platform: template
    id: OffsetPitch
    name: Tangage Offset
    internal: $KEEP_INTERNE
    update_interval: $boucle_asservissement
    lambda: return id(PV_tangage).state - id(sunTangage).state;
    on_value: 
      - if:
          condition:
            - switch.is_off: manual_mode # vérification du mode manuel, si ON alors pas de pilotage
          then:
            - lambda: id(PitchPilot)->execute(x);

  # Description
  #   Récupération de l'élévation du soleil en bornant sa valeur via le filtre 'clamp'.
  #   Toute valeur inférieure sera bornée par la valeur de 'min_value'.
  #   Toute valeur supérieure sera bornée par la valeur de 'max_value'.
  #   Les valeurs de 'min_value' et 'mxa_value' sont à définir en fonction de l'emplacement
  #   géographique du panneau.
  #   Ce calcul n'est pas remonté à HA ('internal: $KEEP_INTERNE')
  # Utilisation :
  #   - capteurs sunX, sunY et sunZ
  - platform: sun
    name: Sun Elevation
    id: sunElevation
    type: elevation
    internal: $KEEP_INTERNE
    filters: 
#      - lambda: |-
#          if(isnan(x)){return {};}
#          else{return x;}
      - clamp:
          min_value: 05
          max_value: 89
          ignore_out_of_range: False
      
  # Description
  #   Récupération de l'azimuth du soleil en bornant sa valeur via le filtre 'clamp'.
  #   Toute valeur inférieure sera bornée par la valeur de 'min_value'.
  #   Toute valeur supérieure sera bornée par la valeur de 'max_value'.
  #   Les valeurs de 'min_value' et 'max_value' sont à définir en fonction de l'emplacement
  #   géographique du panneau.
  #   Ce calcul n'est pas remonté à HA ('internal: $KEEP_INTERNE')
  # Utilisation :
  #   - capteurs sunX et sunY
  - platform: sun
    name: Sun Azimuth
    id: sunAzimuth
    type: azimuth
    internal: $KEEP_INTERNE
    filters: 
#      - lambda: |-
#          if(isnan(x)){return {};}
#          else{return x;}
      - offset: $offset_azimuth
      - clamp:
          min_value: 60
          max_value: 310
          ignore_out_of_range: False

  # Description
  #   Calcul de la position du soleil dans le même repère que le capteur MPU6050.
  #   Ce calcul est effectué à la fréquence choisie par la variable 'boucle_mesure'
  #   Ce calcul n'est pas remonté à HA ('internal: $KEEP_INTERNE')
  # Utilisation :
  #   - capteurs sunRoulis et sunTangage
  - platform: template
    id: sunX
    name: sunX
    internal: $KEEP_INTERNE
    lambda: return  cos(id(sunElevation).state*$PI/180)*cos(id(sunAzimuth).state*$PI/180);
    update_interval: $boucle_mesure

  - platform: template
    id: sunY
    name: sunY
    internal: $KEEP_INTERNE
    lambda: return  -cos(id(sunElevation).state*$PI/180)*sin(id(sunAzimuth).state*$PI/180);
    update_interval: $boucle_mesure

  - platform: template
    id: sunZ
    name: sunZ
    internal: $KEEP_INTERNE
    lambda: return  sin(id(sunElevation).state*$PI/180);
    update_interval: $boucle_mesure

  # Description
  #   Calcul de la position sur le roulis du soleil en dégré en bornant sa valeur via le filtre 'clamp'.
  #   Si le mode repos/tempête est activé, le calcul renverra la position de repos/tempête de roulis.
  #   Toute valeur inférieure sera bornée par la valeur de 'min_value'.
  #   Toute valeur supérieure sera bornée par la valeur de 'max_value'.
  #   Les valeurs de 'min_value' et 'max_value' sont les butées logicielles légérement inférieures
  #   au butées physiques définies par 'angle_min_roulis' et 'angle_max_roulis'.
  #   Si le soleil est en dehors de ces butées logicielles, l'asservissement est toujours actif
  #   mais en butée.
  #   Ce calcul est effectué à la fréquence de 10.1s (soit la variable boucle_messure + 0.1s)
  #   Ce calcul est remonté à HA ('internal: ${PUSHED_TO_HA}')
  # Utilisation :
  #   - capteurs OffsetRoll
  - platform: template
    id: sunRoulis
    name: Roulis soleil
    unit_of_measurement: °
    internal: ${PUSHED_TO_HA}
    update_interval: 10.1s
    lambda: |-
      if (id(storm_mode).state) return {$repos_roulis}; // Angle azimuth repos
      return -(-acos(id(sunY).state/sqrt( pow( id(sunY).state , 2) + pow( id(sunZ).state , 2)))*180/$PI+90);
    filters: 
      - clamp:
          min_value: -52
          max_value: 52
          ignore_out_of_range: False

  # Description
  #   Calcul de la position sur le tangage du soleil en dégré en bornant sa valeur via le filtre 'clamp'.
  #   Si le mode repos/tempête est activé, le calcul renverra la position de repos/tempête de tangage.
  #   Toute valeur inférieure sera bornée par la valeur de 'min_value'.
  #   Toute valeur supérieure sera bornée par la valeur de 'max_value'.
  #   Les valeurs de 'min_value' et 'max_value' sont les butées logicielles légérement inférieures
  #   au butées physiques définies par 'angle_min_tangage' et 'angle_max_tangage'.
  #   Si le soleil est en dehors de ces butées logicielles, l'asservissement est toujours actif
  #   mais en butée.
  #   Ce calcul est effectué à la fréquence de 10.1s (soit la variable boucle_messure + 0.1s)
  #   Ce calcul est remonté à HA ('internal: ${PUSHED_TO_HA}')
  # Utilisation :
  #   - capteurs OffsetPitch
  - platform: template
    id: sunTangage
    name: Tangage soleil
    unit_of_measurement: °
    internal: ${PUSHED_TO_HA}
    update_interval: 10.15s
    lambda: |-
      if (id(storm_mode).state) return {$repos_tangage}; // Angle elevation repos
      return asin(id(sunX).state)/sqrt( pow( id(sunX).state , 2) + pow( id(sunZ).state , 2))*180/$PI;
    filters: 
      - clamp:
          min_value: -50
          max_value: 30
          ignore_out_of_range: False
    
  - platform: internal_temperature
    name: "ESP Temperature"

  # Description :
  #   Calcul de la vitesse du vent en Km/h
  #   Cette vitesse du vent permet la mise en mode tempête (traqueur en position de sécurité).
  # Utilisation :
  #   - calcul de la vitesse du vent 'vent_kmh'
  - platform: template
    id: vent_kmh
    unit_of_measurement: Km/h
    internal: $KEEP_INTERNE
    update_interval: $boucle_asservissement
    lambda: return (id(anemo_tension).state)*$rapport_kmh_v;
    filters: 
    - clamp:
        min_value: 5
        max_value: 25               # 150
        ignore_out_of_range: False
    on_value_range:
      - above: 30
        then:
          - switch.turn_on: storm_mode

  # Description :
  #   Calcul de la vitesse du vent en Km/h
  #   Cette vitesse du vent permet la mise en mode tempête (traqueur en position de sécurité).
  # Utilisation :
  #   - calcul de la vitesse du vent 'vent_kmh'
  - platform: template
    name: Vent_kmh
    accuracy_decimals: 0
    unit_of_measurement: Km/h
    update_interval: $boucle_asservissement
    lambda: return id(vent_kmh).state;

# Description :
#   Déclaration des boutons
switch:
  # Description :
  #   Déclaration du bouton pour passer en mode manuel. Le passage en mode manuel coupera
  #   l'asservissement automatique des panneaux.
  #   Le mode 'optimistic: True' indique que action sur ce bouton lance immédiatement la mise à jour
  #   de son état
  # Utilisation :
  #   - OffsetRoll, OffsetPitch
  #   - PV_Roulis, PV_Tangage
  - platform: template
    id: manual_mode
    optimistic: True
    name: "Mode manuel"
    icon: "mdi:hand-back-right-outline"
    on_turn_on:
    - fan.turn_off: tangage_verin
    - fan.turn_off: roulis_verin

  # Description :
  #   Déclaration du bouton pour passer en mode repos/tempête. Le passage en mode repos/tempête enverra
  #   une position fictive du soleil qui forcera le panneau à se mettre en position repos.
  #   Le mode 'optimistic: True' indique que action sur ce bouton lance immédiatement la mise à jour
  #   de son état.
  # Utilisation :
  #   - sunRoulis, sunTangage
  - platform: template
    id: storm_mode
    name: "Mode nuit/intemperie"
    icon: "mdi:weather-lightning-rainy"
    optimistic: True
    on_turn_on:
    - lambda: return id(sunTangage).publish_state($repos_tangage);  # Angle elevation repos
    - lambda: return id(sunRoulis).publish_state($repos_roulis);    # Angle azimuth repos

# Description :
#   Récupération de l'heure de Home Assistant.
# Utilisation :
#   Obligatoire pour la récupération de la classe Sun
time:
  - platform: homeassistant

# Description :
#   Récupération de la position du soleil.
# Utilisation :
#   Utiliser pour les capteurs sunElevation et sunAzimuth
sun:
  latitude: ${HOME_LAT}
  longitude: ${HOME_LON}
  id: my_sun
  on_sunrise:
    - then:
      - switch.turn_off: storm_mode       # Asservir vers position du levée du soleil
  on_sunset:
    - then:
      - switch.turn_on: storm_mode        # Mettre en position de repos/vent à presque horizontal, avec légère pente pour écoulement pluie
      - number.to_min: number_roulis      # RàZ le soir
      - number.to_min: number_tangage     # RàZ le soir

Voici mon historique depuis ce matin, comme tu peux le voir, c’est pas aussi joli que le tien :

Ma com avec le MPU semble erratique :

Ce soir, je vais raccourcir le câble de ce capteur qui fait plein de boucle, ça corrigera peut-être la com.

Pour la sécurité anémomètre, j’avais celui livré avec le traqueur mais lorsque j’ai voulu mesurer la tension a ses bornes en le faisant tourner, je trouvais que 0 :thinking: , très bizarre, donc on verra plus tard … Donc ton projet de station météo va fortement m’intéressé :wink:

J’ai également eu beaucoup de problème à rendre stable la MPU6050.

J’ai passé rapidement ton code, je te propose ces quelques ajustements, à toi de voir ce que tu en fais :slight_smile: j’ai ajouté “#SCORPIX” à chaque ligne que j’ai modifié.

# ESPHome configuration file for 
# solar-tracker module
# https://forum.hacf.fr/t/tracker-solaire-diy-perturbe-par-perte-wifi/47291/18

substitutions:
  esphome_name: solar-tracker
  logger_level: INFO
  logger_baud_rate: '0'
  static_ip: !secret ip_solar_tracker
  power_save_mode: none


  # CONSTANTES D'INSTALLATION
  HOME_LAT: XX.XXXXX
  HOME_LON: XX.XXXXX

  angle_min_tangage: '-50.0' #SCORPIX
  angle_max_tangage: '30.0'  #SCORPIX
  angle_min_roulis: '-52.0'  #SCORPIX
  angle_max_roulis: '52.0'  #SCORPIX
  seuil_manuel_min_tangage: '-60.0'  #SCORPIX
  seuil_manuel_max_tangage: '60.0'  #SCORPIX
  seuil_manuel_min_roulis: '-60.0'  #SCORPIX
  seuil_manuel_max_roulis: '60.0' #SCORPIX
  boucle_asservissement: '100ms'  # vitesse de la boucle d'asservissement
  boucle_mesure: '10s'            # vitesse de la boucle de mesure des roulis et tangage
  max_speed_offset: '12.0'        # si la consigne est à plus de 12°, vitesse vérin à 100%
  offset_accX: '0.02'             # Calibration grossière de l'IMU, à faire sur table nivelé au tout début
  offset_accY: '0.68'             # Calibration grossière de l'IMU, à faire sur table nivelé au tout début
  offset_accZ: '-0.05'            # Calibration grossière de l'IMU, à faire sur table nivelé au tout début
  offset_anemo: 0                 # Calibration de l'anémomètre
  offset_azimuth: '-5.0'          # mon système fonctionne qui si tu as l'axe du roulis colinéaire avec l'axe Nord-Sud, ça permet d'ajuster l'erreur
  offset_tangage: '0.0'           # permet d'ajuster des erreurs sur le tangage
  offset_roulis: '0.0'            # permet d'ajuster des erreurs sur le roulis
  precision_offset: '3.0'         # angle cône de suivi du soleil
  rapport_kmh_v: 90               # Rapport entre la vitesse du vent relevé par l'anémomètre et la tension relevée (1v - 25m/s => 90 km/h)
  repos_tangage: '-0.0'           # angle tangage repos mode Nuit/Tempête par rapport à l'horizontal
  repos_roulis: '-0.0'            # angle roulis repos mode Nuit/Tempête par rapport à l'horizontal

  vitesse_mini_verins: '20'       # Vitesse minimales d'approche des vérins
  
  PUSHED_TO_HA: 'False'
  KEEP_INTERNE: 'True'            # Basculer cette constante à False pour faire remonter tous les sensors dans HA
  PI: '3.14159265359'
  
  # GPIO
  GPIO_FREQ_PWM: '40000Hz'
  GPIO_SDA: '21'
  GPIO_SCL: '22'

packages:
  base: !include .config-base.yaml
  wifi: !include .config-wifi.yaml
  ota: !include .config-ota.yaml
  logger: !include .config-logger.yaml
  portal: !include .config-portal.yaml
  api: !include .config-api.yaml
  status: !include .config-status.yaml
  web_server: !include .config-web-server.yaml

esp32:
  board: esp32dev
  framework:  #SCORPIX
    type: esp-idf #SCORPIX
    version: latest #SCORPIX
    advanced: #SCORPIX
      compiler_optimization: PERF #SCORPIX ça peut aider si ton µC est à la peine à cause des calculs



# Module Definition

# Définition de la communication I2C pour le capteur MPU6050
i2c:
  sda: 
    number: ${GPIO_SDA}
  scl: 
    number: ${GPIO_SCL}
  scan: true
  id: bus_a
  frequency: 400kHz #SCORPIX monter la fréquence m'a permis de trouver stabiliser la liaison I2C, par default 50kHz

# Définition des sorties PWM pour le H-Bridge DBH-12V
output:
  # Moteur A = Tangage
  # Moteur A Pin IN1A du pont en H (IN1A <=> GPIO13)
  - platform: ledc
    id: tangage_pin_IN1A
    pin: GPIO13
    frequency: ${GPIO_FREQ_PWM}

  # Moteur A Pin B du pont en H (IN2A <=> GPIO12)
  - platform: ledc
    id: tangage_pin_IN2A
    pin: GPIO12
    frequency: ${GPIO_FREQ_PWM}

  # Moteur B = Roulis
  # Moteur B Pin A du pont en H (IN1B <=> GPIO14)
  - platform: ledc
    id: roulis_pin_IN1B
    pin: GPIO14
    frequency: ${GPIO_FREQ_PWM}

  # Moteur B Pin B du pont en H (IN2B <=> GPIO27)
  - platform: ledc
    id: roulis_pin_IN2B
    pin: GPIO27
    frequency: ${GPIO_FREQ_PWM}
    
# Dans Esphome, le pilotage des vérins à travers un pont en H, se fait avec la classe FAN.
# Option: 
#   un nombre incrémental à chaque action des vérins pour avoir une idée du nombre de fois
#   par jour qu’ils sont mis en action.
# Configuration du moteur via le composant fan hbridge
fan:
  # Moteur A
  - platform: hbridge
    id: tangage_verin
    name: "Verin Tangage - H-Bridge"
    pin_a: tangage_pin_IN1A
    pin_b: tangage_pin_IN2A
    decay_mode: SLOW
    internal: ${PUSHED_TO_HA}
    on_turn_on: 
      then:
        - number.increment: number_tangage

  # Moteur B
  - platform: hbridge
    id: roulis_verin
    name: "Verin Roulis - H-Bridge"
    pin_a: roulis_pin_IN1B
    pin_b: roulis_pin_IN2B
    decay_mode: SLOW
    internal: ${PUSHED_TO_HA}
    on_turn_on: 
      then:
        - number.increment: number_roulis

# Boucle d'asservissement des positions des vérins
# Arguments :
#   - offset : écart entre la position du soleil et la position réel des panneaux sur l'axe choisi
# Description :
#   En fonction du signe, le pilotage se fait dans un sens ou dans l'autre (REVERSE/FORWARD).
#   En fonction de la valeur, la vitesse des vérins est plus ou moins importante. Plus l'écart est
#   important, plus la vitesse d'approche est grande, plus on s'approche de la consigne, plus la 
#   vitesse est lente (ceci pour éviter les chocs brutaux). Plus la $boucle_asservissement est petite,
#   plus l'approche sera douce.
#   Une vitesse minimale (vitesse_mini_verins) est défini pour assurer un bon fonctionnement des vérins.
#   Cette vitesse minimale dépend du vérin, elle est défini par expérimentation.
# Rappel :
#   - Roll  : Roulis
#   - Pitch : Tangage
script:
  - id: RollPilot
    mode: single
    parameters:
      offset: float
    then:
      - lambda: |-
          if (abs(offset)>$precision_offset) {
            int speed = 0;
            if(abs(offset)>$max_speed_offset) {
              speed = 100;
            }
            else {
              speed = ${vitesse_mini_verins} + (40*abs(offset)/$max_speed_offset);
            }
            if (offset>0) {
              auto call = id(roulis_verin).turn_on();
              call.set_speed(speed);
              call.set_direction(FanDirection::FORWARD);
              call.perform();
            } else {
              auto call = id(roulis_verin).turn_on();
              call.set_speed(speed);
              call.set_direction(FanDirection::REVERSE);
              call.perform();
            }
          }
          else {
            auto call = id(roulis_verin).turn_off();
            call.perform();
          }
  - id: PitchPilot
    mode: single
    parameters:
      offset: float
    then:
      - lambda: |-
          if (abs(offset)>$precision_offset) {
            int speed = 0;
            if(abs(offset)>$max_speed_offset) {
              speed = 100;
            }
            else {
              speed = ${vitesse_mini_verins} + (40*abs(offset)/$max_speed_offset);
            }
            if (offset>0) {
              auto call = id(tangage_verin).turn_on();
              call.set_speed(speed);
              call.set_direction(FanDirection::REVERSE);
              call.perform();
            } else {
              auto call = id(tangage_verin).turn_on();
              call.set_speed(speed);
              call.set_direction(FanDirection::FORWARD);
              call.perform();
            }
          }
          else {
            auto call = id(tangage_verin).turn_off();
            call.perform();
          }

# Boutons de contrôle manuel
button:
  # Bouton pour faire avancer le Verin N/S rapidement
  - platform: template
    name: "Verin N/S - Action - Avance - 80 %"
    on_press:
      - fan.turn_on:
          id: tangage_verin
          speed: 80
          direction: FORWARD

  # Bouton pour faire avancer le Verin N/S lentement
  - platform: template
    name: "Verin N/S - Action - Avance - 20 %"
    on_press:
      - fan.turn_on:
          id: tangage_verin
          speed: 20
          direction: FORWARD

  # Bouton pour faire reculer le Verin N/S rapidement
  - platform: template
    name: "Verin N/S - Action - Recule - 80 %"
    on_press:
      - fan.turn_on:
          id: tangage_verin
          speed: 80
          direction: REVERSE

  # Bouton pour faire reculer le Verin N/S lentement
  - platform: template
    name: "Verin N/S - Action - Recule - 20 %"
    on_press:
      - fan.turn_on:
          id: tangage_verin
          speed: 20
          direction: REVERSE

  # Bouton d'arrêt du Verin N/S
  - platform: template
    name: "Verin N/S - Action - Stop"
    on_press:
      - fan.turn_off: tangage_verin

  # Bouton pour faire tourner le Verin E/O vers l'avant rapidement
  - platform: template
    name: "Verin E/O - Action - Avance - 80 %"
    on_press:
      - fan.turn_on:
          id: roulis_verin
          speed: 80
          direction: FORWARD

  # Bouton pour faire tourner le Verin E/O vers l'avant lentement
  - platform: template
    name: "Verin E/O - Action - Avance - 20 %"
    on_press:
      - fan.turn_on:
          id: roulis_verin
          speed: 20
          direction: FORWARD

  # Bouton pour faire tourner le Verin E/O vers l'arrière rapidement
  - platform: template
    name: "Verin E/O - Action - Recule - 80 %"
    on_press:
      - fan.turn_on:
          id: roulis_verin
          speed: 80
          direction: REVERSE

  # Bouton pour faire tourner le Verin E/O vers l'arrière lentement
  - platform: template
    name: "Verin E/O - Action - Recule - 20 %"
    on_press:
      - fan.turn_on:
          id: roulis_verin
          speed: 20
          direction: REVERSE

  # Bouton d'arrêt du Verin E/O
  - platform: template
    name: "Verin E/O - Action - Stop"
    on_press:
      - fan.turn_off: roulis_verin

# Capteur de statut du moteur
binary_sensor:
  - platform: template
    name: "Verin N/S - Etat - En marche"
    lambda: |-
      return id(tangage_verin).state;
  - platform: template
    name: "Verin E/O - Etat - En marche"
    lambda: |-
      return id(roulis_verin).state;

# Description :
#   Affichage MAC Address pour debugging
# text_sensor:
#   - platform: wifi_info
#     mac_address:
#       name: $device_name_friendly MAC Address
#       id:   text_sensor_mac_address

# Incréments des actions des vérins
# Description :
#   Ces nombres sont créés pour connaitre le nombre de fois qu'un vérin est actionné. Ces compteurs
#   sont remis à 0 en fin de journée par la classe SUN. Il peuvent être remontés à HA via l'attribut
#   'internal' en le passant de 'KEEP_INTERNE' à 'PUSHED_TO_HA'
number:
  - platform: template
    internal: $KEEP_INTERNE
    id: number_tangage
    restore_value: True
    min_value: 0
    max_value: 9999
    step: 1
    optimistic: True
    on_value: 
      then:
        - lambda: "return id(sensor_number_tangage).publish_state(x);"

  - platform: template
    id: number_roulis
    internal: $KEEP_INTERNE
    restore_value: True
    min_value: 0
    max_value: 9999
    step: 1
    optimistic: True
    on_value: 
      then:
        - lambda: "return id(sensor_number_roulis).publish_state(x);"

# Déclaration de capteurs
sensor:
  # Description :
  #   Récupération de la tension sur l'anémomètre
  # Utilisation :
  #   - calcul de la vitesse du vent 'vent_kmh'
  - platform: adc
    pin: GPIO34
    name: "Anémomètre"
    id: anemo_tension
    internal: $KEEP_INTERNE
    filters:
    - offset : $offset_anemo
    - clamp:
        min_value: 0.1
        max_value: 3.0
        ignore_out_of_range: False
    attenuation : 12db # Range 0,075 V ~ 3,12 V
    accuracy_decimals: 2
    unit_of_measurement: V
    update_interval: $boucle_mesure #SCORPIX surcharge CPU (avant : $boucle_asservissement) tu remettras plus rapide quand tu auras trouvé comment fonctionne ton anémomètre

  # Description :
  #   Déclaration des capteurs d'actions des vérins.
  # Utilisation :
  #   Ces capteurs sont utilisés par les numbers 'number_roulis' et 'number_tangage'
  - platform: template
    name: action verin roulis
    id: sensor_number_roulis

  - platform: template
    name: action verin tangage
    id: sensor_number_tangage

  # Description
  #   Déclaration des capteurs d'accélération du MPU6050
  # Paramètrages :
  #   - address         : adresse du capteur sur le bus I2C
  #   - update_interval : intervalle de vérification du capteur. Tous les capteurs du
  #                       MPU6050 seront rafraichis à cette fréquence.
  - platform: mpu6050
    address: 0x68
    update_interval: $boucle_asservissement # SCORPIX ce sont les données d'entrée, si elles n'arrivent pas au moins aussi vite que le reste des calculs, ça ne fonctionnera pas bien (avant : 500ms)

    # Description
    #   Déclaration des capteurs d'accélération du MPU6050
    # Utilisation :
    #   Ces capteurs 'accel_x', 'accel_y', 'accel_z' sont utilisés pour le calcul des
    #   angles des panneaux dans les capteurs 'PV_roulis' et 'PV_tangage'
    accel_x:
      id: accel_x
      name: "MPU Accel X"
      internal: $KEEP_INTERNE
      filters:
        # - multiply: -1.0
        - offset: $offset_accX
        - clamp:
            min_value: -9.81
            max_value: 9.81
        - exponential_moving_average:
            alpha: 0.20
            send_every: 2
      #  - sliding_window_moving_average:
      #     window_size: 10
      #     send_every: 5
    accel_y:
      id: accel_y
      name: "MPU Accel Y"
      internal: $KEEP_INTERNE
      filters:
        # - multiply: -1.0
        - offset: $offset_accY
        - clamp:
            min_value: -9.81
            max_value: 9.81
        - exponential_moving_average:
            alpha: 0.20
            send_every: 2
      #  - sliding_window_moving_average:
      #     window_size: 10
      #     send_every: 5
    accel_z:
      id: accel_z
      name: "MPU Accel Z"
      internal: $KEEP_INTERNE
      filters:
        - multiply: -1.0
        - offset: $offset_accZ
        - timeout: 1s
        - clamp:
            min_value: 0.01                 # IMU à l'envers, tombé
            max_value: 9.81
            ignore_out_of_range: false
        - exponential_moving_average:
            alpha: 0.20
            send_every: 2
      #  - sliding_window_moving_average:
      #     window_size: 10
      #     send_every: 5

  # Description
  #   Déclaration du capteur de température du MPU6050
  # Utilisation :
  #   Ce capteur est utilisé pour remonter la température à HA par l'intermédiaire
  #   du capteur 'MPU température'
    temperature:
      id: MPU_temp
      name: "solar-tracker Temperature"
      filters:
       - sliding_window_moving_average:
          window_size: 10
          send_every: 5

  # Description
  #   Calcul de l'offset
  # Utilisation :
  #   Le calcul des offset permet d'étalonner le capteur pour qu'il indique les bonnes
  #   valeurs d'accélération en m/s² une fois positionner sur l'installation.
  #   Une fois le capteur IMU positionné sur le traqueur, et le traqueur en 
  #   position horizontale avec l'axe Z colinéaire avec l'axe vertical, l'axe des X
  #   colinéaire avec l'axe Nord/Sud et l'axe des Y colinéaire avec l'axe Est/Ouest, 
  #   relevez ces valeurs d'offset.
  #   Ces capteurs ne servent qu'à l'étalonnage, il faut les commenter une fois 
  #   l'étalonnage effectué.
  # - platform: template
  #   id: accel_x_offset
  #   name: "MPU Accel X offset"
  #   unit_of_measurement: m/s²
  #   accuracy_decimals: 2
  #   update_interval: $boucle_asservissement
  #   lambda: return 9.81 - id(accel_x).state;
  # - platform: template
  #   id: accel_y_offset
  #   name: "MPU Accel Y offset"
  #   unit_of_measurement: m/s²
  #   accuracy_decimals: 2
  #   update_interval: $boucle_asservissement
  #   lambda: return 9.81 - id(accel_y).state;
  # - platform: template
  #   id: accel_z_offset
  #   name: "MPU Accel Z offset"
  #   unit_of_measurement: m/s²
  #   accuracy_decimals: 2
  #   update_interval: $boucle_asservissement
  #   lambda: return 9.81 - id(accel_z).state;

  # Description
  #   Déclaration des capteurs d'accélération du MPU6050 corrigé par l'offset
  # Utilisation :
  #   Ces capteurs 'accel_x', 'accel_y', 'accel_z' sont utilisés pour l'étalonnage 
  #   des valeurs d'offset.
  #   Une fois les offsets renseignés (voir plus haut 'Calcul de l'offset'), vérifiez
  #   que les valeurs remontées par ces capteurs sont bien 9.81 m/s² lorsque le traqueur
  #   est en position à plat.
  # - platform: template
  #   id: accel_x_corrected
  #   name: "MPU Accel X with offset"
  #   unit_of_measurement: m/s²
  #   accuracy_decimals: 2
  #   update_interval: $boucle_asservissement
  #   lambda: return id(accel_x).state + $offset_accX;
  # - platform: template
  #   id: accel_y_corrected
  #   name: "MPU Accel Y with offset"
  #   unit_of_measurement: m/s²
  #   accuracy_decimals: 2
  #   update_interval: $boucle_asservissement
  #   lambda: return id(accel_y).state + $offset_accY;
  # - platform: template
  #   id: accel_z_corrected
  #   name: "MPU Accel Z with offset"
  #   unit_of_measurement: m/s²
  #   accuracy_decimals: 2
  #   update_interval: $boucle_asservissement
  #   lambda: return id(accel_z).state + $offset_accZ;

  # Description
  #   Calcul de l'angle des panneaux par rapport à l'horizontal sur l'axe E/O.
  #   Ce capteur est calculé à la fréquence définie par la variable 'boucle_asservissement'
  #   , souvent très rapide, quelques millsecondes. Pour éviter la surcharge du WiFi, on les
  #   garde en interne de l'ESP par 'internal: $KEEP_INTERNE'.
  #   Si l'angle calculé n'est pas compris entre les butées logicielles 'angle_min_roulis'
  #   'angle_max_roulis', le mode manuel est activé, ce qui inhibe l'asservissement. Ceci pour
  #   protéger la mécanique des vérins si, par exemple, le capteur tombe.
  # Utilisation :
  #   - Remonté dans HA via le capteur 'PV roulis'
  #   - Par le capteur 'OffsetRoll'
  - platform: template
    id: PV_roulis
    name: Roulis PV
    internal: $KEEP_INTERNE
    unit_of_measurement: °
    update_interval: $boucle_asservissement
    lambda: return atan2(id(accel_y).state, id(accel_z).state)*180/$PI;
    filters:
      - offset : $offset_roulis
    on_value_range:                       # si calcul d'un angle supérieur au réalisable mécanique, alors problème, arrêt de l'asservissement
      - above: $seuil_manuel_max_roulis          # 60.0 #SCORPIX
        then:
          - switch.turn_on: manual_mode
      - below: $seuil_manuel_min_roulis          # -60.0 #SCORPIX
        then:
          - switch.turn_on: manual_mode
            
  # Description
  #   Calcul de l'angle des panneaux par rapport à l'horizontal sur l'axe N/S.
  #   Ce capteur est calculé à la fréquence définie par la variable 'boucle_asservissement'
  #   , souvent très rapide, quelques millsecondes. Pour éviter la surcharge du WiFi, on les
  #   garde en interne de l'ESP par 'internal: $KEEP_INTERNE'.
  #   Si l'angle calculé n'est pas compris entre les butées logicielles 'angle_min_tangage'
  #   'angle_max_tangage', le mode manuel est activé, ce qui inhibe l'asservissement. Ceci pour
  #   protéger la mécanique des vérins si, par exemple, le capteur tombe.
  # Utilisation :
  #   - Remonté dans HA via le capteur 'PV tangage'
  #   - Par le capteur 'OffsetPitch'
  - platform: template
    id: PV_tangage
    name: Tangage PV
    internal: $KEEP_INTERNE
    unit_of_measurement: °
    update_interval: $boucle_asservissement
    lambda: return atan2(id(accel_x).state, id(accel_z).state)*180/$PI;
    filters:
      - offset : $offset_tangage
    on_value_range:                       # si calcul d'un angle supérieur au réalisable mécanique, alors problème, arrêt de l'asservissement
      - above: $seuil_manuel_max_tangage         # 35.0 #SCORPIX
        then:
          - switch.turn_on: manual_mode
      - below: $seuil_manuel_max_tangage         # -53.0 #SCORPIX
        then:
          - switch.turn_on: manual_mode

  # Description
  #   Ces capteurs sont des copies des capteurs qui sont remonter dans HA à la fréquence choisie
  #   par la variable 'boucle_mesure'
  # Utilisation :
  #   - Remontés dans HA
  - platform: template
    name: Roulis PV
    unit_of_measurement: °
    accuracy_decimals: 1
    update_interval: $boucle_mesure
    lambda: return id(PV_roulis).state;

  - platform: template
    name: Tangage PV
    unit_of_measurement: °
    accuracy_decimals: 1
    update_interval: $boucle_mesure
    lambda: return id(PV_tangage).state;

  - platform: template
    name: MPU température
    device_class: temperature
    update_interval: 60s
    lambda: return id(MPU_temp).state;

  - platform: template
    name: Vitesse vent
    unit_of_measurement: Km/h
    accuracy_decimals: 0
    update_interval: $boucle_mesure
    lambda: return id(vent_kmh).state;

  # Description
  #   Calcul de l'offset du roulis entre la valeur du soleil et la valeur des panneaux.
  #   Ce calcul est effectué à la fréquence prévue par la variable 'boucle_asservissement'.
  #   L'offset calculé est transmis au script RollPilot uniquement si le fonctionnement
  #   n'est pas en mode manuel.
  #   Ce calcul n'est pas remonté à HA ('internal: $KEEP_INTERNE')
  # Utilisation :
  #   - script RollPilot
  - platform: template
    id: OffsetRoll
    name: Roulis Offset
    internal: $KEEP_INTERNE
    update_interval: $boucle_asservissement
    lambda: return id(PV_roulis).state - id(sunRoulis).state;
    on_value: 
      - if:
          condition:
            - switch.is_off: manual_mode # vérification du mode manuel, si ON alors pas de pilotage
          then:
            - lambda: id(RollPilot)->execute(x);

  # Description
  #   Calcul de l'offset du tangage entre la valeur du soleil et la valeur des panneaux.
  #   Ce calcul est effectué à la fréquence prévue par la variable 'boucle_asservissement'.
  #   L'offset calculé est transmis au script PitchPilot uniquement si le fonctionnement
  #   n'est pas en mode manuel.
  #   Ce calcul n'est pas remonté à HA ('internal: $KEEP_INTERNE')
  # Utilisation :
  #   - script PitchPilot
  - platform: template
    id: OffsetPitch
    name: Tangage Offset
    internal: $KEEP_INTERNE
    update_interval: $boucle_asservissement
    lambda: return id(PV_tangage).state - id(sunTangage).state;
    on_value: 
      - if:
          condition:
            - switch.is_off: manual_mode # vérification du mode manuel, si ON alors pas de pilotage
          then:
            - lambda: id(PitchPilot)->execute(x);

  # Description
  #   Récupération de l'élévation du soleil en bornant sa valeur via le filtre 'clamp'.
  #   Toute valeur inférieure sera bornée par la valeur de 'min_value'.
  #   Toute valeur supérieure sera bornée par la valeur de 'max_value'.
  #   Les valeurs de 'min_value' et 'mxa_value' sont à définir en fonction de l'emplacement
  #   géographique du panneau.
  #   Ce calcul n'est pas remonté à HA ('internal: $KEEP_INTERNE')
  # Utilisation :
  #   - capteurs sunX, sunY et sunZ
  - platform: sun
    name: Sun Elevation
    id: sunElevation
    type: elevation
    internal: $KEEP_INTERNE
    filters: 
#      - lambda: |-
#          if(isnan(x)){return {};}
#          else{return x;}
      - clamp:
          min_value: 05
          max_value: 89
          ignore_out_of_range: False
      
  # Description
  #   Récupération de l'azimuth du soleil en bornant sa valeur via le filtre 'clamp'.
  #   Toute valeur inférieure sera bornée par la valeur de 'min_value'.
  #   Toute valeur supérieure sera bornée par la valeur de 'max_value'.
  #   Les valeurs de 'min_value' et 'max_value' sont à définir en fonction de l'emplacement
  #   géographique du panneau.
  #   Ce calcul n'est pas remonté à HA ('internal: $KEEP_INTERNE')
  # Utilisation :
  #   - capteurs sunX et sunY
  - platform: sun
    name: Sun Azimuth
    id: sunAzimuth
    type: azimuth
    internal: $KEEP_INTERNE
    filters: 
#      - lambda: |-
#          if(isnan(x)){return {};}
#          else{return x;}
      - offset: $offset_azimuth
      - clamp:
          min_value: 60
          max_value: 310
          ignore_out_of_range: False

  # Description
  #   Calcul de la position du soleil dans le même repère que le capteur MPU6050.
  #   Ce calcul est effectué à la fréquence choisie par la variable 'boucle_mesure'
  #   Ce calcul n'est pas remonté à HA ('internal: $KEEP_INTERNE')
  # Utilisation :
  #   - capteurs sunRoulis et sunTangage
  - platform: template
    id: sunX
    name: sunX
    internal: $KEEP_INTERNE
    lambda: return  cos(id(sunElevation).state*$PI/180)*cos(id(sunAzimuth).state*$PI/180);
    update_interval: $boucle_mesure

  - platform: template
    id: sunY
    name: sunY
    internal: $KEEP_INTERNE
    lambda: return  -cos(id(sunElevation).state*$PI/180)*sin(id(sunAzimuth).state*$PI/180);
    update_interval: $boucle_mesure

  - platform: template
    id: sunZ
    name: sunZ
    internal: $KEEP_INTERNE
    lambda: return  sin(id(sunElevation).state*$PI/180);
    update_interval: $boucle_mesure

  # Description
  #   Calcul de la position sur le roulis du soleil en dégré en bornant sa valeur via le filtre 'clamp'.
  #   Si le mode repos/tempête est activé, le calcul renverra la position de repos/tempête de roulis.
  #   Toute valeur inférieure sera bornée par la valeur de 'min_value'.
  #   Toute valeur supérieure sera bornée par la valeur de 'max_value'.
  #   Les valeurs de 'min_value' et 'max_value' sont les butées logicielles légérement inférieures
  #   au butées physiques définies par 'angle_min_roulis' et 'angle_max_roulis'.
  #   Si le soleil est en dehors de ces butées logicielles, l'asservissement est toujours actif
  #   mais en butée.
  #   Ce calcul est effectué à la fréquence de 10.1s (soit la variable boucle_messure + 0.1s)
  #   Ce calcul est remonté à HA ('internal: ${PUSHED_TO_HA}')
  # Utilisation :
  #   - capteurs OffsetRoll
  - platform: template
    id: sunRoulis
    name: Roulis soleil
    unit_of_measurement: °
    internal: ${PUSHED_TO_HA}
    update_interval: 10.1s
    lambda: |-
      if (id(storm_mode).state) return {$repos_roulis}; // Angle azimuth repos
      return -(-acos(id(sunY).state/sqrt( pow( id(sunY).state , 2) + pow( id(sunZ).state , 2)))*180/$PI+90);
    filters: 
      - clamp:
          min_value: $angle_min_roulis #SCORPIX
          max_value: $angle_max_roulis #SCORPIX
          ignore_out_of_range: False

  # Description
  #   Calcul de la position sur le tangage du soleil en dégré en bornant sa valeur via le filtre 'clamp'.
  #   Si le mode repos/tempête est activé, le calcul renverra la position de repos/tempête de tangage.
  #   Toute valeur inférieure sera bornée par la valeur de 'min_value'.
  #   Toute valeur supérieure sera bornée par la valeur de 'max_value'.
  #   Les valeurs de 'min_value' et 'max_value' sont les butées logicielles légérement inférieures
  #   au butées physiques définies par 'angle_min_tangage' et 'angle_max_tangage'.
  #   Si le soleil est en dehors de ces butées logicielles, l'asservissement est toujours actif
  #   mais en butée.
  #   Ce calcul est effectué à la fréquence de 10.1s (soit la variable boucle_messure + 0.1s)
  #   Ce calcul est remonté à HA ('internal: ${PUSHED_TO_HA}')
  # Utilisation :
  #   - capteurs OffsetPitch
  - platform: template
    id: sunTangage
    name: Tangage soleil
    unit_of_measurement: °
    internal: ${PUSHED_TO_HA}
    update_interval: 10.15s
    lambda: |-
      if (id(storm_mode).state) return {$repos_tangage}; // Angle elevation repos
      return asin(id(sunX).state)/sqrt( pow( id(sunX).state , 2) + pow( id(sunZ).state , 2))*180/$PI;
    filters: 
      - clamp:
          min_value: $angle_min_tangage #SCORPIX
          max_value: $angle_max_tangage #SCORPIX
          ignore_out_of_range: False
    
  - platform: internal_temperature
    name: "ESP Temperature"

  # Description :
  #   Calcul de la vitesse du vent en Km/h
  #   Cette vitesse du vent permet la mise en mode tempête (traqueur en position de sécurité).
  # Utilisation :
  #   - calcul de la vitesse du vent 'vent_kmh'
  - platform: template
    id: vent_kmh
    unit_of_measurement: Km/h
    internal: $KEEP_INTERNE
    update_interval: $boucle_mesure #SCORPIX surcharge CPU (avant : $boucle_asservissement) tu remettras plus rapide quand tu auras trouvé comment fonctionne ton anémomètre
    lambda: return (id(anemo_tension).state)*$rapport_kmh_v;
    filters: 
    - clamp:
        min_value: 5
        max_value: 25               # 150
        ignore_out_of_range: False
    on_value_range:
      - above: 30
        then:
          - switch.turn_on: storm_mode

  # Description :
  #   Calcul de la vitesse du vent en Km/h
  #   Cette vitesse du vent permet la mise en mode tempête (traqueur en position de sécurité).
  # Utilisation :
  #   - calcul de la vitesse du vent 'vent_kmh'
  - platform: template
    name: Vent_kmh
    accuracy_decimals: 0
    unit_of_measurement: Km/h
    update_interval: $boucle_mesure #SCORPIX surcharge CPU (avant : $boucle_asservissement)
    lambda: return id(vent_kmh).state;

# Description :
#   Déclaration des boutons
switch:
  # Description :
  #   Déclaration du bouton pour passer en mode manuel. Le passage en mode manuel coupera
  #   l'asservissement automatique des panneaux.
  #   Le mode 'optimistic: True' indique que action sur ce bouton lance immédiatement la mise à jour
  #   de son état
  # Utilisation :
  #   - OffsetRoll, OffsetPitch
  #   - PV_Roulis, PV_Tangage
  - platform: template
    id: manual_mode
    optimistic: True
    name: "Mode manuel"
    icon: "mdi:hand-back-right-outline"
    on_turn_on:
    - fan.turn_off: tangage_verin
    - fan.turn_off: roulis_verin

  # Description :
  #   Déclaration du bouton pour passer en mode repos/tempête. Le passage en mode repos/tempête enverra
  #   une position fictive du soleil qui forcera le panneau à se mettre en position repos.
  #   Le mode 'optimistic: True' indique que action sur ce bouton lance immédiatement la mise à jour
  #   de son état.
  # Utilisation :
  #   - sunRoulis, sunTangage
  - platform: template
    id: storm_mode
    name: "Mode nuit/intemperie"
    icon: "mdi:weather-lightning-rainy"
    optimistic: True
    on_turn_on:
    - lambda: return id(sunTangage).publish_state($repos_tangage);  # Angle elevation repos
    - lambda: return id(sunRoulis).publish_state($repos_roulis);    # Angle azimuth repos

# Description :
#   Récupération de l'heure de Home Assistant.
# Utilisation :
#   Obligatoire pour la récupération de la classe Sun
time:
  - platform: homeassistant

# Description :
#   Récupération de la position du soleil.
# Utilisation :
#   Utiliser pour les capteurs sunElevation et sunAzimuth
sun:
  latitude: ${HOME_LAT}
  longitude: ${HOME_LON}
  id: my_sun
  on_sunrise:
    - then:
      - switch.turn_off: storm_mode       # Asservir vers position du levée du soleil
  on_sunset:
    - then:
      - switch.turn_on: storm_mode        # Mettre en position de repos/vent à presque horizontal, avec légère pente pour écoulement pluie
      - number.to_min: number_roulis      # RàZ le soir
      - number.to_min: number_tangage     # RàZ le soir

Quel ESP32 utilises-tu ?

Avant d’intégrer tes modifications, voilà un état des courbes avec le panneau positionné au petit oignons avec l’ombre d’un repère :

On voit que j’ai un décalage sur les 2 consignes

Pour l’ESP, j’utilise celui-ci avec la carte de dev :

Si je comprends bien, tu as mis un axe, perpendiculaire à tes panneaux.
Si tu supprimes son ombre portée, alors tu es pil poil en direction des panneaux.

tes valeurs de Roulis soleil et Tangage soleil sont cohérentes, à 14h25 en aout.

ces erreurs sont constantes quelque soit la position des panneaux ?

Tu as bien mode_manuel à off ?

Non, je me suis mis en mode manuel pour effacer l’ombre du tuteur sur les panneaux, je dois donc être perpendiculaire au soleil, donc la position optimale par rapport au soleil.

Ensuite en comparant PV roulis et Sun roulis, je devrais avoir 0 en écart (les 2 courbes devraient se superposer, dans l’idéal), mais on voit que j’ai un écart.

Qu’est ce que je dois toucher pour réduire cet écart ? (c’est ce que je cherche :thinking: )

Je viens de pousser tes modifications sur l’ESP32, il est passer en mode auto avec des aller/retour :

Puis, il est repassé en manuel pour arriver dans cette position :

Alors que la position idéale devrait être celle-ci (photo prise au depuis le même endroit) :

Et les courbe de Roulis/Tangage :

Entre 15:00 et 15:03, il était dans la position suite au passage manuel du code, et à partir de 15:05, il est dans la position idéale (perpendiculaire au soleil) dans la laquelle je l’ai positionné.

En bref, pour une position de soleil à 15:06:04 :

  • Le tangage des panneaux est à -18° au lieu de -35°
  • Le roulis des panneaux est à -45° au lieu de -14.9°

Bizarre.
En roulis ça semble aller dans le bon sens, ta courbe rouge va vers la bleue.
Ton tangage bouge beaucoup.

J’ai re parcouru ton code, je ne vois pas comme ça.

Tu peux faire une photo rapprochée de ton IMU ? et une autre où on voit à l’intérieur l’orientation ?

Refais le coup du baton, et donnes tous les résultats de sun roulis, tangage, PV roulis, tangage, acc_x, acc_y te acc_z, …

J’ai ajouté les graphs des “Acc_”, passage en mode auto après l’upload avec l’ajout des “Acc_” :

Voici la position des panneaux :

Pour l’instant le code semble rester en auto …

Je le passe en manuel (15:24) et je fais disparaitre l’ombre, voici la position des panneaux une fois l’ombre disparue :

Et les courbes :

Pour rappel, voici la position de l’IMU (Z+ vers le bas, X+ vers le sud, et Y+ vers l’ouest) :

Les valeurs remontées par l’IMU ne sont pas bonnes.

Je vais raccourcir le fil entre l’ESP et l’IMU …

Après avoir retiré au moins 3m de câble enroulé en boucle qui reliait l’IMU à l’ESP, voici le résultat :

La com avec l’IMU semble plus stable.

On peut voir que j’ai un peu de décalage (environ 3° de plus) entre PV/Sun sur chaque suivi mais dans l’ensemble, le traqueur suit bien le soleil :+1:. J’ai trouvé d’où vient l’écart de 3° sur les courbes, de precision_offset, maintenant que je suis super stable, je le passe à 2° :wink:

La bonne nouvelle de la journée :slight_smile: :slight_smile:

Demain, je ne touche à rien et, après demain, je t’envoie les courbes, j’espère qu’elles seront aussi jolies que les tiennes :wink:

Désolé de ne pas crier victoire aujourd’hui mais j’ai tendance à avoir des galères alors, j’attend jeudi …

@Scorpix , un grand merci à toi pour tout ton travail et ton temps passé à me répondre.

La journée n’était pas très ensoleillée aujourd’hui mais le traqueur a bien suivi le soleil même s’il était derrière les nuages.

J’aurai bien aimé t’envoyer les courbes de suivi de la journée mais j’ai eu le malheur de vouloir accéder à la base de données en direct pour voir quelles étaient les variables qui remontaient le plus (sans surprise, c’était les Accel?) et HA l’a vu comme corrompue, il a fallu que je fasse une restauration de base de donnée à celle de ce matin 4h, donc pas de courbe aujourd’hui :sob: .

Sur les courbes, j’ai juste vu du “bruit” sur l’IMU de 12h30 à 16h30 qui a empêché d’être dans la cône de 2° pour le tangage. Je vérifierai demain si c’est pareil. J’ai aussi vu avec les courbes que angle_max_roulis était “caper” à 30° alors qu’il pouvait aller jusqu’à 50° (ce que j’ai changé dans les constantes d’installations).

Je vais remettre au propre mon code et y mettre à jour les explications et je le mettrai à disposition sur ce fil de discussion si tu n’y vois pas d’inconvénient :thinking:

Encore une fois un très grand MERCI pour toute l’aide que tu as pu m’apporter et j’attend avec impatience ton projet de station météo qui m’intéresse fortement.

Merci à toi @Scorpix , j’apprécie nos échanges, merci.

Merci beaucoup Sylvain, ça me fait plaisir :slight_smile:

Petit spoil, je suis entrain de préparer un article sur le sujet.
May The Sun Be With You :slight_smile:

Pour tous ceux qui veulent se lancer dans le remplacement d’un contrôleur de vérins utilisant un un capteur d’illumination, voici le code de @Scorpix que j’ai utilisé avec les commentaires pour mieux le comprendre :wink:

# ESPHome configuration file for 
# solar-tracker module
# https://forum.hacf.fr/t/tracker-solaire-diy-perturbe-par-perte-wifi/47291/18

substitutions:
  esphome_name: solar-tracker
  logger_level: INFO
  logger_baud_rate: '0'
  # api_encryption_key: !secret nom_de_l_appareil_api_encryption_key
  # api_password: !secret nom_de_l_appareil_api_password
  static_ip: !secret ip_solar_tracker
  power_save_mode: none


  # CONSTANTES D'INSTALLATION
  PI: '3.14159265359'
  HOME_LAT: XX.XXXXX                  # Latitude du Traqueur
  HOME_LON: XX.XXXXX                  # Longitude du Traqueur

  # Définitions des butées
  # Butées physiques (en degré): Ces butées, une fois atteinte, déclenche le
  # passage en mode manuel pour éviter une casse matériel.
  seuil_manuel_min_roulis: '-60.0'    # angle minimum autorisé sur le roulis
  seuil_manuel_max_roulis: '60.0'     # angle maximum autorisé sur le roulis
  seuil_manuel_min_tangage: '-60.0'   # angle minimum autorisé sur le tangage
  seuil_manuel_max_tangage: '60.0'    # angle maximum autorisé sur le tangage
  # Butées logicielles (en degré) : Ces butées sont définies pour "caper" les
  # angles min et max tout en restant asservi.
  angle_min_roulis: '-50.0'           # angle minimum autorisé sur le roulis
  angle_max_roulis: '50.0'            # angle maximum autorisé sur le roulis
                                      # (max 49.0 vérifié le 13/08/25 à 08:34:31)
  angle_min_tangage: '-52.0'          # angle minimum autorisé sur le tangage 
  angle_max_tangage: '20.0'           # angle maximum autorisé sur le tangage
                                      # (max 16.1 vérifié le 13/08/25 à 06:48:01)
  # Définitions des boucles
  boucle_asservissement: '100ms'      # vitesse de la boucle d'asservissement
                                      # (utilisée pour les calculs internes de
                                      # l'asservissement de la position des vérins)
  boucle_mesure: '10s'                # vitesse de la boucle de mesure des
                                      # données remontées dans HA
  # Définitions des compensations
  # Calibration de l'IMU MPU6050 grossière de l'IMU, à faire sur table nivelée
  offset_accX: '0.02'                 # Calibration en X 
  offset_accY: '0.68'                 # Calibration en Y
  offset_accZ: '-0.05'                # Calibration en Z
  offset_azimuth: '-5.0'              # Variable d'ajustement des erreurs liée à
                                      # la non colinéarité avec l'axe Nord-Sud
  offset_tangage: '0.0'               # Ajustements des erreurs sur le tangage
  offset_roulis: '0.0'                # Ajustements des erreurs sur le roulis
  offset_anemo: 0                     # Calibration de l'anémomètre
  # Positions repos/tempête
  repos_tangage: '-0.0'               # angle tangage par rapport à l'horizontal
  repos_roulis: '-0.0'                # angle roulis par rapport à l'horizontal
  # Définitions du suivi
  setpoint_max_speed: '12.0'          # si la consigne est à plus de 12°, la
                                      # vitesse du vérin sera passée à 100%
  precision_tracking: '2.0'           # angle cône de suivi du soleil
  vitesse_mini_verins: '20'           # Vitesse minimales d'approche des vérins
  # Défintions de l'anémomètre
  rapport_kmh_v: 90                   # Rapport entre la vitesse du vent relevé
                                      # par l'anémomètre et la tension relevée
                                      # (1v - 25m/s => 90 km/h)
  # Définitions des remontées dans HA
  PUSHED_TO_HA: 'False'               # Remonte les données dans HA
  KEEP_INTERNE: 'True'                # Ne remonte pas les données dans HA
  
  # GPIO
  GPIO_SDA: '21'
  GPIO_SCL: '22'

  # FREQUENCES
  frequence_bus_i2c: '400kHz'       # Permet d'améliorer la stabilité de la 
                                    # liaison I2C, par default 50kHz
  frequence_pwm: '40000Hz'

packages:
  base: !include .config-base.yaml
  wifi: !include .config-wifi.yaml
  ota: !include .config-ota.yaml
  logger: !include .config-logger.yaml
  portal: !include .config-portal.yaml
  api: !include .config-api.yaml
  status: !include .config-status.yaml
  web_server: !include .config-web-server.yaml
  # mqtt: !include .config-mqtt.yaml

esp32:
  board: esp32dev
  framework:
    type: esp-idf
    version: latest
    advanced:
      compiler_optimization: PERF # Aide si le µC est à la peine à cause des calculs


# Module Definition

# Définition de la communication I2C pour le capteur MPU6050
i2c:
  sda: 
    number: ${GPIO_SDA}
  scl: 
    number: ${GPIO_SCL}
  scan: true
  id: bus_a
  frequency: $frequence_bus_i2c

# Définition des sorties PWM pour le H-Bridge DBH-12V
output:
  # Moteur A = Tangage
  # Moteur A Pin IN1A du pont en H (IN1A <=> GPIO13)
  - platform: ledc
    id: tangage_pin_IN1A
    pin: GPIO13
    frequency: $frequence_pwm

  # Moteur A Pin B du pont en H (IN2A <=> GPIO12)
  - platform: ledc
    id: tangage_pin_IN2A
    pin: GPIO12
    frequency: $frequence_pwm

  # Moteur B = Roulis
  # Moteur B Pin A du pont en H (IN1B <=> GPIO14)
  - platform: ledc
    id: roulis_pin_IN1B
    pin: GPIO14
    frequency: $frequence_pwm

  # Moteur B Pin B du pont en H (IN2B <=> GPIO27)
  - platform: ledc
    id: roulis_pin_IN2B
    pin: GPIO27
    frequency: $frequence_pwm
    
# Dans Esphome, le pilotage des vérins à travers un pont en H, se fait avec la 
# classe FAN.
# Option: 
#   un nombre incrémental à chaque action des vérins pour avoir une idée du 
#   nombre de fois par jour qu’ils sont mis en action.
fan:
  # Moteur A
  - platform: hbridge
    id: tangage_verin
    name: "Verin Tangage - H-Bridge"
    pin_a: tangage_pin_IN1A
    pin_b: tangage_pin_IN2A
    decay_mode: SLOW
    internal: ${PUSHED_TO_HA}
    on_turn_on: 
      then:
        - number.increment: number_tangage

  # Moteur B
  - platform: hbridge
    id: roulis_verin
    name: "Verin Roulis - H-Bridge"
    pin_a: roulis_pin_IN1B
    pin_b: roulis_pin_IN2B
    decay_mode: SLOW
    internal: ${PUSHED_TO_HA}
    on_turn_on: 
      then:
        - number.increment: number_roulis

# Boucle d'asservissement des positions des vérins
# Arguments :
#   - offset : écart entre la position du soleil et la position réel des 
#              panneaux sur l'axe choisi
# Description :
#   En fonction du signe, le pilotage se fait dans un sens ou dans l'autre 
#   (REVERSE/FORWARD).
#   En fonction de la valeur, la vitesse des vérins est plus ou moins importante.
#   Plus l'écart est important, plus la vitesse d'approche est grande, plus on
#   s'approche de la consigne, plus la vitesse est lente (ceci pour éviter les
#   chocs brutaux). Plus la $boucle_asservissement est petite, plus l'approche
#   sera douce. Une vitesse minimale (vitesse_mini_verins) est défini pour
#   assurer un bon fonctionnement des vérins. Cette vitesse minimale dépend du
#   vérin, elle est défini par expérimentation.
# Rappel :
#   - Roll  : Roulis
#   - Pitch : Tangage
script:
  - id: RollPilot
    mode: single
    parameters:
      offset: float
    then:
      - lambda: |-
          if (abs(offset)>$precision_tracking) {
            int speed = 0;
            if(abs(offset)>$setpoint_max_speed) {
              speed = 100;
            }
            else {
              speed = ${vitesse_mini_verins} + (40*abs(offset)/$setpoint_max_speed);
            }
            if (offset>0) {
              auto call = id(roulis_verin).turn_on();
              call.set_speed(speed);
              call.set_direction(FanDirection::FORWARD);
              call.perform();
            } else {
              auto call = id(roulis_verin).turn_on();
              call.set_speed(speed);
              call.set_direction(FanDirection::REVERSE);
              call.perform();
            }
          }
          else {
            auto call = id(roulis_verin).turn_off();
            call.perform();
          }
  - id: PitchPilot
    mode: single
    parameters:
      offset: float
    then:
      - lambda: |-
          if (abs(offset)>$precision_tracking) {
            int speed = 0;
            if(abs(offset)>$setpoint_max_speed) {
              speed = 100;
            }
            else {
              speed = ${vitesse_mini_verins} + (40*abs(offset)/$setpoint_max_speed);
            }
            if (offset>0) {
              auto call = id(tangage_verin).turn_on();
              call.set_speed(speed);
              call.set_direction(FanDirection::REVERSE);
              call.perform();
            } else {
              auto call = id(tangage_verin).turn_on();
              call.set_speed(speed);
              call.set_direction(FanDirection::FORWARD);
              call.perform();
            }
          }
          else {
            auto call = id(tangage_verin).turn_off();
            call.perform();
          }

# Boutons de contrôle manuel
button:
  # Bouton pour faire avancer le Verin N/S rapidement
  - platform: template
    name: "Verin N/S - Action - Avance - 80 %"
    on_press:
      - fan.turn_on:
          id: tangage_verin
          speed: 80
          direction: FORWARD

  # Bouton pour faire avancer le Verin N/S lentement
  - platform: template
    name: "Verin N/S - Action - Avance - 20 %"
    on_press:
      - fan.turn_on:
          id: tangage_verin
          speed: 20
          direction: FORWARD

  # Bouton pour faire reculer le Verin N/S rapidement
  - platform: template
    name: "Verin N/S - Action - Recule - 80 %"
    on_press:
      - fan.turn_on:
          id: tangage_verin
          speed: 80
          direction: REVERSE

  # Bouton pour faire reculer le Verin N/S lentement
  - platform: template
    name: "Verin N/S - Action - Recule - 20 %"
    on_press:
      - fan.turn_on:
          id: tangage_verin
          speed: 20
          direction: REVERSE

  # Bouton d'arrêt du Verin N/S
  - platform: template
    name: "Verin N/S - Action - Stop"
    on_press:
      - fan.turn_off: tangage_verin

  # Bouton pour faire tourner le Verin E/O vers l'avant rapidement
  - platform: template
    name: "Verin E/O - Action - Avance - 80 %"
    on_press:
      - fan.turn_on:
          id: roulis_verin
          speed: 80
          direction: FORWARD

  # Bouton pour faire tourner le Verin E/O vers l'avant lentement
  - platform: template
    name: "Verin E/O - Action - Avance - 20 %"
    on_press:
      - fan.turn_on:
          id: roulis_verin
          speed: 20
          direction: FORWARD

  # Bouton pour faire tourner le Verin E/O vers l'arrière rapidement
  - platform: template
    name: "Verin E/O - Action - Recule - 80 %"
    on_press:
      - fan.turn_on:
          id: roulis_verin
          speed: 80
          direction: REVERSE

  # Bouton pour faire tourner le Verin E/O vers l'arrière lentement
  - platform: template
    name: "Verin E/O - Action - Recule - 20 %"
    on_press:
      - fan.turn_on:
          id: roulis_verin
          speed: 20
          direction: REVERSE

  # Bouton d'arrêt du Verin E/O
  - platform: template
    name: "Verin E/O - Action - Stop"
    on_press:
      - fan.turn_off: roulis_verin

# Capteur de statut du moteur
binary_sensor:
  - platform: template
    name: "Verin N/S - Etat - En marche"
    lambda: |-
      return id(tangage_verin).state;
  - platform: template
    name: "Verin E/O - Etat - En marche"
    lambda: |-
      return id(roulis_verin).state;

# Description :
#   Affichage MAC Address pour debugging
# text_sensor:
#   - platform: wifi_info
#     mac_address:
#       name: $device_name_friendly MAC Address
#       id:   text_sensor_mac_address

# Incréments des actions des vérins
# Description :
#   Ces nombres sont créés pour connaitre le nombre de fois qu'un vérin est
#   actionné. Ces compteurs sont remis à 0 en fin de journée par la classe SUN.
#   Ils peuvent être remontés à HA via l'attribut 'internal' en le passant de
#   'KEEP_INTERNE' à 'PUSHED_TO_HA'
number:
  - platform: template
    internal: $KEEP_INTERNE
    id: number_tangage
    restore_value: True
    min_value: 0
    max_value: 9999
    step: 1
    optimistic: True
    on_value: 
      then:
        - lambda: "return id(sensor_number_tangage).publish_state(x);"

  - platform: template
    id: number_roulis
    internal: $KEEP_INTERNE
    restore_value: True
    min_value: 0
    max_value: 9999
    step: 1
    optimistic: True
    on_value: 
      then:
        - lambda: "return id(sensor_number_roulis).publish_state(x);"

# Déclaration de capteurs
sensor:
  # Description :
  #   Récupération de la tension sur l'anémomètre
  # Utilisation :
  #   - calcul de la vitesse du vent 'vent_kmh'
  - platform: adc
    pin: GPIO34
    name: "Anémomètre"
    id: anemo_tension
    internal: $KEEP_INTERNE
    filters:
    - offset : $offset_anemo
    - clamp:
        min_value: 0.1
        max_value: 3.0
        ignore_out_of_range: False
    attenuation : 12db # Range 0,075 V ~ 3,12 V
    accuracy_decimals: 2
    unit_of_measurement: V
    update_interval: $boucle_mesure #SCORPIX surcharge CPU (avant : $boucle_asservissement) tu remettras plus rapide quand tu auras trouvé comment fonctionne ton anémomètre

  # Description :
  #   Déclaration des capteurs d'actions des vérins.
  # Utilisation :
  #   Ces capteurs sont utilisés par les numbers 'number_roulis' et
  #   'number_tangage'
  - platform: template
    name: action verin roulis
    id: sensor_number_roulis

  - platform: template
    name: action verin tangage
    id: sensor_number_tangage

  # Description
  #   Déclaration des capteurs d'accélération du MPU6050
  # Paramètrages :
  #   - address         : adresse du capteur sur le bus I2C
  #   - update_interval : intervalle de vérification du capteur. Tous les
  #                       capteurs du MPU6050 seront rafraichis à cette fréquence.
  #                       Elle doit être inférieure ou égale à la variable
  #                       $boucle_asservissement
  - platform: mpu6050
    address: 0x68
    update_interval: $boucle_asservissement

    # Description
    #   Déclaration des capteurs d'accélération du MPU6050
    # Utilisation :
    #   Ces capteurs 'accel_x', 'accel_y', 'accel_z' sont utilisés pour le calcul
    #   des angles des panneaux dans les capteurs 'PV_roulis' et 'PV_tangage'
    accel_x:
      id: accel_x
      name: "MPU Accel X"
      internal: $PUSHED_TO_HA
      filters:
        # - multiply: -1.0
        - offset: $offset_accX
        - clamp:
            min_value: -9.81
            max_value: 9.81
        - exponential_moving_average:
            alpha: 0.20
            send_every: 2
      #  - sliding_window_moving_average:
      #     window_size: 10
      #     send_every: 5
    accel_y:
      id: accel_y
      name: "MPU Accel Y"
      internal: $PUSHED_TO_HA
      filters:
        # - multiply: -1.0
        - offset: $offset_accY
        - clamp:
            min_value: -9.81
            max_value: 9.81
        - exponential_moving_average:
            alpha: 0.20
            send_every: 2
      #  - sliding_window_moving_average:
      #     window_size: 10
      #     send_every: 5
    accel_z:
      id: accel_z
      name: "MPU Accel Z"
      internal: $PUSHED_TO_HA
      filters:
        - multiply: -1.0
        - offset: $offset_accZ
        - timeout: 1s
        - clamp:
            min_value: 0.01                 # IMU à l'envers, tombé
            max_value: 9.81
            ignore_out_of_range: false
        - exponential_moving_average:
            alpha: 0.20
            send_every: 2
      #  - sliding_window_moving_average:
      #     window_size: 10
      #     send_every: 5

  # Description
  #   Déclaration du capteur de température du MPU6050
  # Utilisation :
  #   Ce capteur est utilisé pour remonter la température à HA par l'intermédiaire
  #   du capteur 'MPU température'
    temperature:
      id: MPU_temp
      name: "solar-tracker Temperature"
      filters:
       - sliding_window_moving_average:
          window_size: 10
          send_every: 5

  # Description
  #   Calcul de l'offset
  # Utilisation :
  #   Le calcul des offset permet d'étalonner le capteur pour qu'il indique les
  #   bonnes valeurs d'accélération en m/s² une fois positionner sur l'installation.
  #   Une fois le capteur IMU positionné sur le traqueur, et le traqueur en 
  #   position horizontale avec l'axe Z colinéaire avec l'axe vertical, l'axe
  #   des X colinéaire avec l'axe Nord/Sud et l'axe des Y colinéaire avec l'axe
  #   Est/Ouest, relevez ces valeurs d'offset.
  #   Ces capteurs ne servent qu'à l'étalonnage, il faut les commenter une fois 
  #   l'étalonnage effectué.
  # - platform: template
  #   id: accel_x_offset
  #   name: "MPU Accel X offset"
  #   unit_of_measurement: m/s²
  #   accuracy_decimals: 2
  #   update_interval: $boucle_asservissement
  #   lambda: return 9.81 - id(accel_x).state;
  # - platform: template
  #   id: accel_y_offset
  #   name: "MPU Accel Y offset"
  #   unit_of_measurement: m/s²
  #   accuracy_decimals: 2
  #   update_interval: $boucle_asservissement
  #   lambda: return 9.81 - id(accel_y).state;
  # - platform: template
  #   id: accel_z_offset
  #   name: "MPU Accel Z offset"
  #   unit_of_measurement: m/s²
  #   accuracy_decimals: 2
  #   update_interval: $boucle_asservissement
  #   lambda: return 9.81 - id(accel_z).state;

  # Description
  #   Déclaration des capteurs d'accélération du MPU6050 corrigé par l'offset
  # Utilisation :
  #   Ces capteurs 'accel_x', 'accel_y', 'accel_z' sont utilisés pour l'étalonnage 
  #   des valeurs d'offset.
  #   Une fois les offsets renseignés (voir plus haut 'Calcul de l'offset'),
  #   vérifiez que les valeurs remontées par ces capteurs sont bien 9.81 m/s²
  #   lorsque le traqueur est en position à plat.
  # - platform: template
  #   id: accel_x_corrected
  #   name: "MPU Accel X with offset"
  #   unit_of_measurement: m/s²
  #   accuracy_decimals: 2
  #   update_interval: $boucle_asservissement
  #   lambda: return id(accel_x).state + $offset_accX;
  # - platform: template
  #   id: accel_y_corrected
  #   name: "MPU Accel Y with offset"
  #   unit_of_measurement: m/s²
  #   accuracy_decimals: 2
  #   update_interval: $boucle_asservissement
  #   lambda: return id(accel_y).state + $offset_accY;
  # - platform: template
  #   id: accel_z_corrected
  #   name: "MPU Accel Z with offset"
  #   unit_of_measurement: m/s²
  #   accuracy_decimals: 2
  #   update_interval: $boucle_asservissement
  #   lambda: return id(accel_z).state + $offset_accZ;

  # Description
  #   Calcul de l'angle des panneaux par rapport à l'horizontal sur l'axe E/O.
  #   Ce capteur est calculé à la fréquence définie par la variable
  #   'boucle_asservissement' , souvent très rapide, quelques millsecondes. Pour
  #   éviter la surcharge du WiFi, on les garde en interne de l'ESP par
  #   'internal: $KEEP_INTERNE'. Si l'angle calculé n'est pas compris entre les
  #   butées logicielles 'seuil_manuel_min_roulis'et 'seuil_manuel_max_roulis',
  #   le mode manuel est activé, ce qui inhibe l'asservissement. Ceci pour
  #   protéger la mécanique des vérins si, par exemple, le capteur tombe.
  # Utilisation :
  #   - Remonté dans HA via le capteur 'PV roulis'
  #   - Par le capteur 'OffsetRoll'
  - platform: template
    id: PV_roulis
    name: Roulis PV
    internal: $KEEP_INTERNE
    unit_of_measurement: °
    update_interval: $boucle_asservissement
    lambda: return atan2(id(accel_y).state, id(accel_z).state)*180/$PI;
    filters:
      - offset : $offset_roulis
    on_value_range:
      - above: $seuil_manuel_max_roulis
        then:
          - switch.turn_on: manual_mode
      - below: $seuil_manuel_min_roulis
        then:
          - switch.turn_on: manual_mode
            
  # Description
  #   Calcul de l'angle des panneaux par rapport à l'horizontal sur l'axe N/S.
  #   Ce capteur est calculé à la fréquence définie par la variable
  #   'boucle_asservissement', souvent très rapide, quelques millsecondes. Pour
  #   éviter la surcharge du WiFi, on les garde en interne de l'ESP par
  #   'internal: $KEEP_INTERNE'. Si l'angle calculé n'est pas compris entre les
  #   butées logicielles 'seuil_manuel_min_tangage' et 'seuil_manuel_max_tangage',
  #   le mode manuel est activé, ce qui inhibe l'asservissement. Ceci pour
  #   protéger la mécanique des vérins si, par exemple, le capteur tombe.
  # Utilisation :
  #   - Remonté dans HA via le capteur 'PV tangage'
  #   - Par le capteur 'OffsetPitch'
  - platform: template
    id: PV_tangage
    name: Tangage PV
    internal: $KEEP_INTERNE
    unit_of_measurement: °
    update_interval: $boucle_asservissement
    lambda: return atan2(id(accel_x).state, id(accel_z).state)*180/$PI;
    filters:
      - offset : $offset_tangage
    on_value_range:
      - above: $seuil_manuel_max_tangage
        then:
          - switch.turn_on: manual_mode
      - below: $seuil_manuel_min_tangage
        then:
          - switch.turn_on: manual_mode

  # Description
  #   Ces capteurs sont des copies des capteurs qui sont remonter dans HA à la
  #   fréquence choisie par la variable 'boucle_mesure'
  # Utilisation :
  #   - Remontés dans HA
  - platform: template
    name: Roulis PV
    unit_of_measurement: °
    accuracy_decimals: 1
    update_interval: $boucle_mesure
    lambda: return id(PV_roulis).state;

  - platform: template
    name: Tangage PV
    unit_of_measurement: °
    accuracy_decimals: 1
    update_interval: $boucle_mesure
    lambda: return id(PV_tangage).state;

  - platform: template
    name: MPU température
    device_class: temperature
    update_interval: 60s
    lambda: return id(MPU_temp).state;

  - platform: template
    name: Vitesse vent
    unit_of_measurement: Km/h
    accuracy_decimals: 0
    update_interval: $boucle_mesure
    lambda: return id(vent_kmh).state;

  # Description
  #   Calcul de l'offset du roulis entre la valeur du soleil et la valeur des
  #   panneaux. Ce calcul est effectué à la fréquence prévue par la variable
  #   'boucle_asservissement'. L'offset calculé est transmis au script RollPilot
  #   uniquement si le fonctionnement n'est pas en mode manuel.
  #   Ce calcul n'est pas remonté à HA ('internal: $KEEP_INTERNE')
  # Utilisation :
  #   - script RollPilot
  - platform: template
    id: OffsetRoll
    name: Roulis Offset
    internal: $KEEP_INTERNE
    update_interval: $boucle_asservissement
    lambda: return id(PV_roulis).state - id(sunRoulis).state;
    on_value: 
      - if:
          condition:
            - switch.is_off: manual_mode # si ON alors pas de pilotage
          then:
            - lambda: id(RollPilot)->execute(x);

  # Description
  #   Calcul de l'offset du tangage entre la valeur du soleil et la valeur des
  #   panneaux. Ce calcul est effectué à la fréquence prévue par la variable
  #   'boucle_asservissement'. L'offset calculé est transmis au script PitchPilot
  #   uniquement si le fonctionnement n'est pas en mode manuel.
  #   Ce calcul n'est pas remonté à HA ('internal: $KEEP_INTERNE')
  # Utilisation :
  #   - script PitchPilot
  - platform: template
    id: OffsetPitch
    name: Tangage Offset
    internal: $KEEP_INTERNE
    update_interval: $boucle_asservissement
    lambda: return id(PV_tangage).state - id(sunTangage).state;
    on_value: 
      - if:
          condition:
            - switch.is_off: manual_mode # si ON alors pas de pilotage
          then:
            - lambda: id(PitchPilot)->execute(x);

  # Description
  #   Récupération de l'élévation du soleil en bornant sa valeur via le filtre
  #   'clamp'. Toute valeur inférieure sera bornée par la valeur de 'min_value'.
  #   Toute valeur supérieure sera bornée par la valeur de 'max_value'.
  #   Les valeurs de 'min_value' et 'mxa_value' sont à définir en fonction de
  #   l'emplacement géographique du panneau.
  #   Ce calcul n'est pas remonté à HA ('internal: $KEEP_INTERNE')
  # Utilisation :
  #   - capteurs sunX, sunY et sunZ
  - platform: sun
    name: Sun Elevation
    id: sunElevation
    type: elevation
    internal: $KEEP_INTERNE
    filters: 
#      - lambda: |-
#          if(isnan(x)){return {};}
#          else{return x;}
      - clamp:
          min_value: 05
          max_value: 89
          ignore_out_of_range: False
      
  # Description
  #   Récupération de l'azimuth du soleil en bornant sa valeur via le filtre
  #   'clamp'. Toute valeur inférieure sera bornée par la valeur de 'min_value'.
  #   Toute valeur supérieure sera bornée par la valeur de 'max_value'.
  #   Les valeurs de 'min_value' et 'mxa_value' sont à définir en fonction de
  #   l'emplacement géographique du panneau.
  #   Ce calcul n'est pas remonté à HA ('internal: $KEEP_INTERNE')
  # Utilisation :
  #   - capteurs sunX et sunY
  - platform: sun
    name: Sun Azimuth
    id: sunAzimuth
    type: azimuth
    internal: $KEEP_INTERNE
    filters: 
#      - lambda: |-
#          if(isnan(x)){return {};}
#          else{return x;}
      - offset: $offset_azimuth
      - clamp:
          min_value: 60
          max_value: 310
          ignore_out_of_range: False

  # Description
  #   Calcul de la position du soleil dans le même repère que le capteur MPU6050.
  #   Ce calcul est effectué à la fréquence choisie par la variable 'boucle_mesure'
  #   Ce calcul n'est pas remonté à HA ('internal: $KEEP_INTERNE')
  # Utilisation :
  #   - capteurs sunRoulis et sunTangage
  - platform: template
    id: sunX
    name: sunX
    internal: $KEEP_INTERNE
    lambda: return  cos(id(sunElevation).state*$PI/180)*cos(id(sunAzimuth).state*$PI/180);
    update_interval: $boucle_mesure

  - platform: template
    id: sunY
    name: sunY
    internal: $KEEP_INTERNE
    lambda: return  -cos(id(sunElevation).state*$PI/180)*sin(id(sunAzimuth).state*$PI/180);
    update_interval: $boucle_mesure

  - platform: template
    id: sunZ
    name: sunZ
    internal: $KEEP_INTERNE
    lambda: return  sin(id(sunElevation).state*$PI/180);
    update_interval: $boucle_mesure

  # Description
  #   Calcul de la position sur le roulis du soleil en dégré en bornant sa
  #   valeur via le filtre 'clamp'. Si le mode repos/tempête est activé, le
  #   calcul renverra la position de repos/tempête de roulis. Toute valeur
  #   inférieure sera bornée par la valeur de 'angle_min_roulis'. Toute valeur
  #   supérieure sera bornée par la valeur de 'angle_max_roulis'. Les valeurs
  #   de 'angle_min_roulis' et 'angle_max_roulis' sont les butées logicielles
  #   légérement inférieures au butées physiques définies par
  #   'seuil_manuel_min_roulis' et 'seuil_manuel_max_roulis'. Si le soleil est
  #   en dehors de ces butées logicielles, l'asservissement est toujours actif
  #   mais en butée.
  #   Ce calcul est effectué à la fréquence de 10.1s (soit la variable boucle_messure + 0.1s)
  #   Ce calcul est remonté à HA ('internal: ${PUSHED_TO_HA}')
  # Utilisation :
  #   - capteurs OffsetRoll
  - platform: template
    id: sunRoulis
    name: Roulis soleil
    unit_of_measurement: °
    internal: ${PUSHED_TO_HA}
    update_interval: 10.1s
    lambda: |-
      if (id(storm_mode).state) return {$repos_roulis}; // Angle azimuth repos
      return -(-acos(id(sunY).state/sqrt( pow( id(sunY).state , 2) + pow( id(sunZ).state , 2)))*180/$PI+90);
    filters: 
      - clamp:
          min_value: $angle_min_roulis
          max_value: $angle_max_roulis
          ignore_out_of_range: False

  # Description
  #   Calcul de la position sur le tangage du soleil en dégré en bornant sa
  #   valeur via le filtre 'clamp'. Si le mode repos/tempête est activé, le
  #   calcul renverra la position de repos/tempête de tangage. Toute valeur
  #   inférieure sera bornée par la valeur de 'angle_min_tangage'. Toute valeur
  #   supérieure sera bornée par la valeur de 'angle_max_tangage'. Les valeurs
  #   de 'angle_min_tangage' et 'angle_max_tangage' sont les butées logicielles
  #   légérement inférieures au butées physiques définies par
  #   'seuil_manuel_min_tangage' et 'seuil_manuel_max_tangage'. Si le soleil est
  #   en dehors de ces butées logicielles, l'asservissement est toujours actif
  #   mais en butée.
  #   Ce calcul est effectué à la fréquence de 10.1s (soit la variable boucle_messure + 0.1s)
  #   Ce calcul est remonté à HA ('internal: ${PUSHED_TO_HA}')
  # Utilisation :
  #   - capteurs OffsetPitch
  - platform: template
    id: sunTangage
    name: Tangage soleil
    unit_of_measurement: °
    internal: ${PUSHED_TO_HA}
    update_interval: 10.15s
    lambda: |-
      if (id(storm_mode).state) return {$repos_tangage}; // Angle elevation repos
      return asin(id(sunX).state)/sqrt( pow( id(sunX).state , 2) + pow( id(sunZ).state , 2))*180/$PI;
    filters: 
      - clamp:
          min_value: $angle_min_tangage
          max_value: $angle_max_tangage
          ignore_out_of_range: False
    
  - platform: internal_temperature
    name: "ESP Temperature"

  # Description :
  #   Calcul de la vitesse du vent en Km/h
  #   Cette vitesse du vent permet la mise en mode tempête (traqueur en position
  #   de sécurité).
  # Utilisation :
  #   - calcul de la vitesse du vent 'vent_kmh'
  - platform: template
    id: vent_kmh
    unit_of_measurement: Km/h
    internal: $KEEP_INTERNE
    update_interval: $boucle_mesure #SCORPIX surcharge CPU (avant : $boucle_asservissement) tu remettras plus rapide quand tu auras trouvé comment fonctionne ton anémomètre
    lambda: return (id(anemo_tension).state)*$rapport_kmh_v;
    filters: 
    - clamp:
        min_value: 5
        max_value: 25               # 150
        ignore_out_of_range: False
    on_value_range:
      - above: 30
        then:
          - switch.turn_on: storm_mode

  # Description :
  #   Calcul de la vitesse du vent en Km/h
  #   Cette vitesse du vent permet la mise en mode tempête (traqueur en position
  #   de sécurité).
  # Utilisation :
  #   - calcul de la vitesse du vent 'vent_kmh'
  - platform: template
    name: Vent_kmh
    accuracy_decimals: 0
    unit_of_measurement: Km/h
    update_interval: $boucle_mesure #SCORPIX surcharge CPU (avant : $boucle_asservissement)
    lambda: return id(vent_kmh).state;

# Description :
#   Déclaration des boutons
switch:
  # Description :
  #   Déclaration du bouton pour passer en mode manuel. Le passage en mode
  #   manuel coupera l'asservissement automatique des panneaux.
  #   Le mode 'optimistic: True' indique que action sur ce bouton lance
  #   immédiatement la mise à jour de son état
  # Utilisation :
  #   - OffsetRoll, OffsetPitch
  #   - PV_Roulis, PV_Tangage
  - platform: template
    id: manual_mode
    optimistic: True
    name: "Mode manuel"
    icon: "mdi:hand-back-right-outline"
    on_turn_on:
    - fan.turn_off: tangage_verin
    - fan.turn_off: roulis_verin

  # Description :
  #   Déclaration du bouton pour passer en mode repos/tempête. Le passage en
  #   mode repos/tempête enverra une position fictive du soleil qui forcera le
  #   panneau à se mettre en position repos. Le mode 'optimistic: True'
  #   indique que action sur ce bouton lance immédiatement la mise à jour de son état.
  # Utilisation :
  #   - sunRoulis, sunTangage
  - platform: template
    id: storm_mode
    name: "Mode nuit/intemperie"
    icon: "mdi:weather-lightning-rainy"
    optimistic: True
    on_turn_on:
    - lambda: return id(sunTangage).publish_state($repos_tangage);  # Angle elevation repos
    - lambda: return id(sunRoulis).publish_state($repos_roulis);    # Angle azimuth repos

# Description :
#   Récupération de l'heure de Home Assistant.
# Utilisation :
#   Obligatoire pour la récupération de la classe Sun
time:
  - platform: homeassistant

# Description :
#   Récupération de la position du soleil.
# Utilisation :
#   Utiliser pour les capteurs sunElevation et sunAzimuth
sun:
  latitude: ${HOME_LAT}
  longitude: ${HOME_LON}
  id: my_sun
  on_sunrise:
    - then:
      - switch.turn_off: storm_mode       # Asservir vers position du levée du soleil
  on_sunset:
    - then:
      - switch.turn_on: storm_mode        # Mettre en position de repos/vent à presque horizontal, avec légère pente pour écoulement pluie
      - number.to_min: number_roulis      # RàZ le soir
      - number.to_min: number_tangage     # RàZ le soir

J’espère que les commentaires suffiront à configurer correctement votre installation.

Un grand merci @Scorpix pour son travail :clap: :clap:

Pour l’upgrade d’un traqueur Eco-Worthy complet (matériel compris), je vais faire un article dans ce fil de discussion : Traqueur Solaire

Petit problème de “bruit” ce matin sur l’IMU qui empêche le suivi sur le roulis :

Jai une structure bois, donc les vibrations sont mieux filtrées.
Toi en fer, quand tu es en butée, les vibrations remontent mieux à l’IMU, surement la source de ce bruit.
Tu peux essayer de passer alpha à 0.15
Ou d’élargir les seuils angles max/min.

J’ai réduit à 0.15 le alpha mais le bruit était toujours présent…

J’ai remarqué que lorsque la consigne n’était pas atteinte parce que le matériel ne le permettait pas, le vérin était toujours alimenté (mais en butée hard donc pas de mal pour le vérin) mais ça causait des interférences sur l’IMU (beaucoup de bruit).
Une fois capé ces “butées physiques” par les “butées soft” (“angle_min_roulis”, etc …), le vérin n’était plus alimenté et le bruit a disparu sur l’IMU

En haut les courbe de Roulis/Tangage Soleil/PV, En bas les courbes de Accel? de l’IMU (la partie plate à gauche est lié au fait que le données Accel? ne sont plus stockées dan la base HA et je dois garder l’affichage de la page sous peine de les perdre)
Le “capage” par les “butées soft” à 14:34, on voit que le bruit à disparu, je continue de surveiller :wink:

Oui bien vu, je suis aussi dans ce cas, j’ai des butées soft, qui empêche d’aller en butée hard.