Bonjour,
Présentation
Tout nouveau sur le forum, je me présente en quelques lignes avant de vous exposer mon problème.
J’ai découvert HA il y a tout juste 1 an, et j’en suis tombé amoureux. Néophyte, j’avance à mon rythme.
J’ai commencé par une installation sur mon DS218+, puis j’ai désormais migré sur un vieux PC, avec une installation Proxmox.
Rapidement je me suis tourné vers ESPHome. J’ai 2 Pi pico W et 3 ESP32 qui fonctionnent H24/7J.
Je pourrais vous faire un petit détail de mes installations si ça intéresse.
Sans entrer dans le détail :
-
ESP32 S2 mini, avec 1 DHT22, 2 LD2410C, 1 syrène, 2 relais.
Afin d’avoir les données Temp/Hum du RDC, et gestion de la lumière automatique avec les détecteurs, et quand absent pour la gestion de l’alarme. -
Pi pico w, avec 1 DHT, 6 relais statiques, 1 télécommande TDRC 16.
Pour avoir les données Temp/Hum de l’entrée, et gestion des volets de la maison en automatique en utilisant la sortie des relais pour shunter les boutons de la télécommande, ainsi qu’une petite soudure pour shunter un bouton dans mon portier Legrand pour gestion de mon portail à travers HA. -
ESP32 S2 mini dans mon garage, avec 2 relais, 1 DHT, 2 capteurs fin de course, 1 liaison UART Daly BMS, 1 liaison UART Victron, 1 sortir PWM pour servomoteur.
Le PWM pour gérer un servomoteur pour ouvrir et fermer automatique la porte de mon poulailler, avec capteur fin de course haut. Les 2 relais pour ouvrir et ferme la porte de mon garage et capteur de fin de course haut. Le DHT pour avoir Temp/hum extérieur que j’utilise pour gestion de ma VMC et thermostat chauffage. Les liaisons UART qui proviennent de ma batterie solaire fait maison avec 8 cellules EVE 305Ah, MPPT Victron, BMS Daly onduleur,… -
Pi pico w dans ma buanderie, avec 8 relais, 4 contacteurs 4P (2NC/2NO), 3 PZEMAC sur 1 modbus.
2 relais pour le contact sec pour demande chauffage sur ma PAC (1 étage, 1 RDC), 2 relais pour les 2 pompes de circulation du chauffage (1 étage, 1 RDC), le tout piloté par les thermostats HA.
4 autres relais pour piloter les 4 contacteurs de puissance 4P, qui me permettent de piloter la provenance de l’énergie de la maison (divisée en 4 zones), soit « EDF » soit le solaire à travers l’onduleur et sa batterie. Et les 3 PZEMAC pour avoir les données de puissance, tension, énergie de ce qui sort de l’onduleur, EDF et PAC. -
En cours, conception d’une batterie sur mesure type Ecoflow, pour aller faire du camping, avec ESP32 et ESPHome standalone.
-
Enfin, le dernier ESP32 S2 Mini, sur lequel j’ai besoin de votre aide…je vous explique
Mon problème
Début aout, j’ai eu une petite semaine de tranquillité.
Je me suis lancé dans la conception d’un tracker solaire.
J’ai rapidement fait le support, les fondations et mis le tout en place.
Structure bois, avec roulements coniques pour les liaisons, 2 vérins électriques 12V pour la dynamique.
J’ai installé dans une boite étanche, 1 ESP32 S2 mini, 2 pont en H DRV8871, 1 alimentation 230VAC/12VDC, 1 abaisseur de tension DC/DC pour passer du 12VDC à 5VDC.
Le tout avec quelques couches d’époxy pour contrer l’humidité et la corrosion, en espérant que les différences de coefficient de dilatation ne dessoudent pas les composants montés en surface.
J’ai coupé l’antenne wifi de l’ESP32, et je l’ai remplacé par une antenne extérieur pour améliorer la réception wifi.
Puis j’ai coulé dans de l’époxy, une IMU MPU 6050, dans un lego cubique, qui est collée aux panneaux solaires, afin d’avoir leur orientation, avec calibration au préalable.
Pour faire très simple, je récupère les éphémérides du soleil à travers l’objet SUN, que je transforme dans un repère cartésien (x,y,z), que je transforme à nouveau en roulis/tangage.
Avec l’IMU j’ai roulis/tangage des panneaux, ainsi je peux faire 2 boucles d’asservissements pour piloter les vérins afin d’aligner les 2 vecteurs.
Je n’utilise pas les gyro, car le logiciel ne tourne pas assez vite pour les intégrations, de plus je suis quasiment sur les cardans des 2 axes de rotation, donc très peu de bruit d’accélération linéaire.
Tout fonctionne super bien sauf … quand je perd la liaison wifi, l’ESP se met en vrac et commande mes vérins de façon non désirée.
J’ai pourtant essayé de dupliquer tous les éléments ESPHome en ayant une partie en interne et l’autre externe en espérant que si la liaison wifi est inopérant, tous les éléments internes garantiraient un bon fonctionnement.
Par exemple ci-dessous, le roulis, avec à chaque perte du wifi, ce créneau vers le bas. J’ai ajouté une « protection » qui arrête l’asservissement à chaque problème, c’est pourquoi la reprise de l’asservissement est plus ou moins rapide car je dois le faire manuellement.
En violet la consigne, en bleu le réalisé.
Un petit zoom. La perte de données est toujours entre 30 et 40 secondes, avec arrêt de la boucle d’asservissement. j’ai l’impression que le timeout sur le wifi n’est pas pris en compte.
J’ai tellement essayé de configuration que je vais avoir du mal à toutes vous les détailler.
Ce n’est pas cyclique et ça dépend des jours. Mais c’est clairement lié au wifi.
Ma configuration
Je vous mets mon code ci-dessous … si jamais vous arrivez à m’aiguiller pour trouver comment faire afin d’avoir un code qui tourne quoi qu’il arrive sur la liaison wifi.
esphome:
name: s2-tracker
friendly_name: s2_sunTracker
on_boot:
priority: 600
then:
- switch.turn_on: manual_mode
substitutions:
repos_tangage: '-20.0'
repos_roulis: '-10.0'
precision_offset: '2.0'
offset_tangage: '0.0'
offset_roulis: '7.0'
esp32:
board: lolin_s2_mini
framework:
type: arduino
# Enable logging
logger:
# Enable Home Assistant API
api:
encryption:
key: xxxxxxxxxxxxx
ota:
- platform: esphome
password: !secret s2_lumiere
web_server:
local: False
version: 3
port: 80
include_internal: True
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
reboot_timeout: 60min
on_disconnect:
- switch.turn_on: manual_mode
manual_ip:
static_ip: 192.168.1.26
gateway: 192.168.1.1
subnet: 255.255.255.0
# Enable fallback hotspot (captive portal) in case wifi connection fails
# ap:
# ssid: "S2-Lumiere Fallback Hotspot"
# password: xxxxxx
captive_portal:
i2c:
sda: GPIO40
scl: GPIO38
frequency: 50kHz
scan: True
output:
- platform: ledc
id: tangage_forward_pin
pin: GPIO39
frequency: 50000 Hz
- platform: ledc
id: tangage_reverse_pin
pin: GPIO37
frequency: 50000 Hz
- platform: ledc
id: roulis_forward_pin
pin: GPIO33
frequency: 50000 Hz
- platform: ledc
id: roulis_reverse_pin
pin: GPIO18
frequency: 50000 Hz
fan:
- platform: hbridge
id: tangage_verin
icon: mdi:sun-compass
name: "Verin tangage"
pin_b: tangage_forward_pin
pin_a: tangage_reverse_pin
decay_mode: slow
internal: False
- platform: hbridge
id: roulis_verin
icon: mdi:sun-compass
name: "Verin roulis"
pin_b: roulis_forward_pin
pin_a: roulis_reverse_pin
decay_mode: slow
internal: False
sensor:
- platform: mpu6050
address: 0x68
accel_x:
id: accel_x
name: "IMU Accel X"
internal: True
filters:
- clamp:
min_value: -9.81
max_value: 9.81
- exponential_moving_average:
alpha: 0.25
send_every: 1
accel_y:
id: accel_y
name: "IMU Accel Y"
internal: True
filters:
- clamp:
min_value: -9.81
max_value: 9.81
- exponential_moving_average:
alpha: 0.25
send_every: 1
accel_z:
id: accel_z
name: "IMU Accel z"
internal: True
filters:
- clamp:
min_value: 0.01 #IMU à l'envers, tombé
max_value: 10
ignore_out_of_range: False
- exponential_moving_average:
alpha: 0.25
send_every: 1
temperature:
name: "MPU6050 Temperature"
id: MPU_temp
internal: True
update_interval: 250ms
- platform: template
id: PV_roulis
name: PV roulis
internal: True
unit_of_measurement: °
update_interval: 250ms
lambda: return atan2(id(accel_y).state, id(accel_z).state)*180/PI;
filters:
- offset : $offset_roulis
- clamp:
min_value: -80.0
max_value: 80.0
ignore_out_of_range: False
- timeout: 800ms
- platform: template
id: PV_tangage
name: PV tangage
internal: True
unit_of_measurement: °
update_interval: 250ms
lambda: return atan2(-id(accel_x).state, id(accel_z).state)*180/PI;
filters:
- offset : $offset_tangage
- clamp:
min_value: -80
max_value: 80
ignore_out_of_range: False
- timeout: 800ms
- platform: template
name: PV roulis
unit_of_measurement: °
accuracy_decimals: 1
update_interval: 5s
lambda: return id(PV_roulis).state;
- platform: template
name: PV tangage
unit_of_measurement: °
accuracy_decimals: 1
update_interval: 5s
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
id: OffsetRoll
name: Offset Roulis
internal: True
update_interval: 250ms
lambda: |-
if(isnan(id(PV_roulis).state) || isnan(id(sunRoulis).state) || id(manual_mode).state){
return 0;
}
else {return id(PV_roulis).state - id(sunRoulis).state;}
filters:
- timeout:
timeout: 800ms
value: 0.0
on_value_range:
- below: -$precision_offset # Ecart à partir duquel on met en route l'asservissement
then:
- lambda: |-
auto call = id(roulis_verin).turn_on();
call.set_speed(100);
call.set_direction(FanDirection::FORWARD);
call.perform();
- above: -$precision_offset
below: $precision_offset
then:
- fan.turn_off: roulis_verin
- above: $precision_offset # Ecart à partir duquel on met en route l'asservissement
then:
- lambda: |-
auto call = id(roulis_verin).turn_on();
call.set_speed(100);
call.set_direction(FanDirection::REVERSE);
call.perform();
- platform: template
id: OffsetPitch
name: Offset Tangage
internal: True
update_interval: 250ms
lambda: |-
if(isnan(id(PV_tangage).state) || isnan(id(sunTangage).state) || id(manual_mode).state){
return 0;
}
else {return id(PV_tangage).state - id(sunTangage).state;}
filters:
- timeout:
timeout: 800ms
value: 0.0
on_value_range:
- below: -$precision_offset # Ecart à partir duquel on met en route l'asservissement
then:
- lambda: |-
auto call = id(tangage_verin).turn_on();
call.set_speed(80);
call.set_direction(FanDirection::FORWARD);
call.perform();
- above: -$precision_offset
below: $precision_offset
then:
- fan.turn_off: tangage_verin
- above: $precision_offset # Ecart à partir duquel on met en route l'asservissement
then:
- lambda: |-
auto call = id(tangage_verin).turn_on();
call.set_speed(80);
call.set_direction(FanDirection::REVERSE);
call.perform();
- platform: template
name: Sun Elevation filtre
id: sunElevation
internal: True
lambda: return id(sunElev).state;
filters:
- clamp:
min_value: 41
max_value: 89
ignore_out_of_range: False
- platform: template
name: Sun Azimuth filtre
id: sunAzimuth
internal: True
lambda: return id(sunAz).state;
filters:
- clamp:
min_value: 70
max_value: 290
ignore_out_of_range: False
- platform: sun
name: Sun Elevation
id: sunElev
type: elevation
internal: False
filters:
- lambda: |-
if(isnan(x)){return {};}
else{return x;}
- platform: sun
name: Sun Azimuth
id: sunAz
type: azimuth
internal: False
filters:
- lambda: |-
if(isnan(x)){return {};}
else{return x;}
- platform: template
id: sunX
name: sunX
internal: True
lambda: return cos(id(sunElevation).state*PI/180)*cos(id(sunAzimuth).state*PI/180);
update_interval: 10s
- platform: template
id: sunY
name: sunY
internal: True
lambda: return -cos(id(sunElevation).state*PI/180)*sin(id(sunAzimuth).state*PI/180);
update_interval: 10s
- platform: template
id: sunZ
name: sunZ
internal: True
lambda: return sin(id(sunElevation).state*PI/180);
update_interval: 10s
- platform: template
id: sunRoulis
unit_of_measurement: °
internal: True
update_interval: 10s
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);
- platform: template
id: sunTangage
unit_of_measurement: °
internal: True
update_interval: 10s
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;
- platform: template
name: sunRoulis
accuracy_decimals: 1
unit_of_measurement: °
update_interval: 10s
lambda: return id(sunRoulis).state;
- platform: template
name: sunTangage
accuracy_decimals: 1
unit_of_measurement: °
update_interval: 10s
lambda: return id(sunTangage).state;
- platform: internal_temperature
name: "ESP Temperature"
- platform: wifi_signal # Reports the WiFi signal strength/RSSI in dB
name: "WiFi Signal dB"
id: wifi_signal_db
update_interval: 60s
entity_category: "diagnostic"
switch:
- 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
- lambda: return id(OffsetPitch).publish_state(0.0);
- lambda: return id(OffsetRoll).publish_state(0.0);
- 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
button:
- platform: restart
name: "Restart"
time:
- platform: homeassistant
sun:
latitude: xxxxxxxxx
longitude: xxxxxxxxxxxxxx
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
Merci beaucoup par avance, et espère ne pas être trop à côté de la plaque des règles du forum pour l’édition de ce premier message.
Au plaisir de vous lire et d’échanger,
Florent