pH metre DIY via ESPHome

Bonjour et merci pour les réponses
A+

Délosé pour le anglais. Je n’ai parle pas francais.

I could not found how you dealed with the fact that the d1_mini A0 input deals only with 3.3v (max 3.6v), and the pH probe sends 0-5V on it. Did you put a divisor ?

TRADUCION:
Je n’ai pas trouvé comment tu as géré le fait que l’entrée d1_mini A0 ne traite que 3,3v (max 3,6v), et que la sonde pH envoie 0-5V dessus. As-tu mis un diviseur ?

Bonjour,
je ne comprend pas la notion de « package HA », ou doit-on entrer ces paramètres ?
</
###################################################################################

device_tracker:

###################################################################################
device_tracker:

  • platform: ping
    interval_seconds: 30
    hosts:
    ph_meter: 192.168.1.26
    ####################################################################################

PH meter

####################################################################################
input_boolean:
##################################
calib_ph:
name: « Calib mode »

input_select:
##################################
c_calib_ph4:
name: Calibration pH4
icon: mdi:adjust
options:
- « 4.00 »
- « 4.01 »
c_calib_ph7:
name: Calibration pH7
icon: mdi:adjust
options:
- "6.86 "
- « 7.00 »
- « 7.01 »
c_calib_ph9_10:
name: Calibration pH9-10
icon: mdi:adjust
options:
- « 9.00 »
- « 9.18 »
- « 9.21 »
- « 10.00 »
- « 10.01 »

input_number:
v_calib_mv4:
name: Calibration mV à 4
icon: mdi:gauge

initial: 4.504

min: 3.500
max: 5.000
step: 0.001
mode: box

v_calib_mv7:
name: Calibration mV à 7
icon: mdi:gauge

initial: 4.013

min: 3.500
max: 5.000
step: 0.001
mode: box

v_calib_mv9:
name: Calibration mV à 9
icon: mdi:gauge

initial: 3.703

min: 3.500
max: 5.000
step: 0.001
mode: box

sensor:

  • platform: template
    sensors:
    reef_ph:
    friendly_name: « Valeur PH »
    value_template: ‹ {{ (states.input_select.c_calib_ph9_10.state | float) - ( (((states.input_number.v_calib_mv9.state | float)-(states.sensor.reef_ph_ads.state | float))*((states.input_select.c_calib_ph7.state | float)-(states.input_select.c_calib_ph9_10.state | float))) / ((states.input_number.v_calib_mv7.state | float)-(states.input_number.v_calib_mv9.state | float)) ) | round(2) }} ›

xc (states.sensor.reef_ph_ads.state | float)

(states.input_select.c_calib_ph4.state | float)

ya (states.input_select.c_calib_ph7.state | float)

yb (states.input_select.c_calib_ph9_10.state | float)

(states.input_number.v_calib_mv4.state | float)

xa (states.input_number.v_calib_mv7.state | float)

xb (states.input_number.v_calib_mv9.state | float)

#########################################################ph_meter

ph_meter ONLINE status sensors

  ph_meter:
    value_template: '{% if is_state("device_tracker.ph_meter", "home") %}Online{% else %}offline{% endif %}'
    friendly_name: 'État ph_meter'
    icon_template: >-
      {% if is_state('device_tracker.ph_meter', 'home') %}
        mdi:power-plug
      {% else %}
        mdi:power-plug-off
      {% endif %}

automation:

Notify offline

  • alias: Notify ph_meter offline
    trigger:
    platform: state
    entity_id: device_tracker.ph_meter
    to: ‹ not_home ›
    action:
    service: notify.iosdevice
    data:
    title: « ph_meter offline »
    message: "le pH mètre est hors ligne !!! Mesures impossibles !!! "
    data:
    push:
    thread-id: « activité_reef-group »

  • alias: ‹ Notif Reef Temp ›
    initial_state: ‹ on ›
    trigger:

    • platform: time_pattern
      hours: « /1 »
      seconds: « 03 »
      condition:
      condition: and
      conditions:
      • condition: template
        value_template: >
        {% if ( states(‹ sensor.time ›) > « 08:00 » ) and ( states(‹ sensor.time ›) < « 20:00 » )%}
        True
        {% else %}
        False
        {% endif %}
      • condition: or
        conditions:
        • condition: template
          value_template: « {{states(‹ sensor.reef_temperature_2 ›)| int > 26 | int}} »
        • condition: template
          value_template: « {{states(‹ sensor.reef_temperature_2 ›)| int < 23 | int}} »
          action:
          service: notify.iosdevice
          data_template:
          title: « Reef : Alerte Température »
          message: « La température d’eau est à {{states(‹ sensor.reef_temperature_2 ›)}} alors qu’elle devrait être comprise entre 23 et 26°C »
          data:
          push:
          thread-id: « activité_reef-group »
          />
          Merci d’avance

Pour information, pour moi
Avant sans isolation

Le pH joue le yoyo
Mise en place de ce module


Le pH est super stable.

Ce module analogique fonctionne super bien
Avant j’étais en I2C et j’utilisais
atlas-scientific


Mais suite a une panne et les prix prohebitif atlas-scientific je suis repassé sur un module chinois a pas cher et l’isolateur Gravity.
Pour une utilisation grand publique ça fonctionne parfaitement.
Je confirme, Isolation obligatoire de l’alimentation du pH

2 « J'aime »

Bonjour,

Pourrais-tu partager ta configuration concernant le 1130 avecc la carte gravity ?

Finalement, Google, chat j’ai pété et moi avons trouvé une configuration pas loin de la vérité.
Si vous avez besoin, je peux la partager.

Dans ma config :

  • Un ESP32 dédié à la filtration avec quatre relais et trois Dallas DS18B20
  • Un ESP32 pour la remontée du pH et de l’ORP ; pas de remontée sans filtration, une sonde pH, une sonde ORP (je ne comprends pas pourquoi j’ai des valeurs si basses) mais ça fonctionne, un système qui permet de faire l’étalonnage. Je vais sûrement acheter une chambre d’analyse comme le suggère Lepelot.
1 « J'aime »

Tout à fait intéressé par la config PH + ORP !
Merci

Pour ne pas se compliquer la vie.

Une interface pH munie d’un circuit d’isolation électrique, et une interface orp munie également d’un circuit d’isolation identique au premier ;

Soit chez DFrobot :

1 interface pH : (avec sonde) Gravity: 7/24 Industrial Analog pH Meter for Arduino - DFRobot

1 interface orp : (avec sonde) Gravity: Arduino Analog ORP Meter - DFRobot

2 circuits d’isolement : Gravity: Analog Signal Isolator - DFRobot (prendre la version analogique)

Les circuits sortent une tension stable (non parasitée) 0/5V. Ces sorties tensions sont à raccorder aux entrées d’un ESP
Attention si votre plage de mesure fait monter la tension de sortie des modules au dessus de 3.3 V, il vous faudra installer un pont diviseur pour ne pas cramer l’entrée de l’ESP.

Reste ensuite à faire les conversions dans l’ESP (ce que j’ai fait) ou dans HA.

1 « J'aime »

Je m’en occupe demain si j’ai le temps ou dans le week-end

Voilà mon code, attention j’utilise une carte ESP32-WROOM et 4 relais avec trois sonde dallas DS18b20 et une autre carte ESP32-DevKitC-32 ESP-WROOM
Avec cette dernière j’utilise des Adaptateur pH/ORP 1130 et des et Module d’isolation de signal DFR0504
Le code remonte les données uniquement si ma pompe tourne afin d’éviter d’avoir des données stagnantes de mes tuyaux et j’ai alterné l’interogation des deux sondes pour éviter les interférences, n’ayant pas de pool Terre.

esphome:
  name: piscine-phorp
  friendly_name: Piscine pH/ORP

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

logger:
  level: DEBUG

api:
  encryption:
    key: !secret piscine_phorp_api_key

ota:
  - platform: esphome
    password: !secret piscine_phorp_ota_password

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password

captive_portal:

mdns:

time:
  - platform: homeassistant
    id: homeassistant_time

sensor:
  - platform: wifi_signal
    id: signal_wifi
    name: "Signal Wifi"
    update_interval: 10min

  # === ADC BRUTS ===
  - platform: adc
    id: ph_brut
    name: "Tension pH Brute"
    pin: GPIO35
    attenuation: 12db
    update_interval: 61s
    unit_of_measurement: "V"
    accuracy_decimals: 3
    filters: []

  - platform: adc
    id: orp_brut
    name: "Tension ORP Brute"
    pin: GPIO32
    attenuation: 12db
    update_interval: 197s
    unit_of_measurement: "V"
    accuracy_decimals: 3
    filters: []

  # === FILTRAGE (template basé sur les bruts) ===
  - platform: template
    id: ph_voltage
    name: "Tension filtrée pH"
    unit_of_measurement: "V"
    accuracy_decimals: 3
    update_interval: 60s
    lambda: |-
      return id(ph_brut).state + id(offset_ph).state;
    filters:
      - median:
          window_size: 7
          send_every: 4
          send_first_at: 1

  - platform: template
    id: orp_voltage
    name: "Tension filtrée ORP"
    unit_of_measurement: "V"
    accuracy_decimals: 3
    update_interval: 60s
    lambda: |-
      return id(orp_brut).state;
    filters:
      - median:
          window_size: 7
          send_every: 4
          send_first_at: 1

  - platform: homeassistant
    id: water_temp
    entity_id: sensor.pool_pompe_temperature_de_l_eau_de_la_piscine
    internal: true

  # === CALCULS ===
  - platform: template
    id: valeur_ph
    name: "pH (double-pente & T°)"
    accuracy_decimals: 2
    update_interval: 60s
    lambda: |-
      bool pompe_on = id(pompe_piscine).state;
      float v       = id(ph_voltage).state;
      float v4      = id(cal_ph4).state;
      float v7      = id(cal_ph7).state;
      float v9      = id(cal_ph9).state;
      float temp_w  = id(water_temp).state;

      if (!pompe_on) return id(valeur_ph).state;

      if (isnan(v) || isnan(v4) || isnan(v7) || isnan(v9)
          || fabs(v7 - v4) < 1e-6 || fabs(v9 - v7) < 1e-6) {
        return id(valeur_ph).state;
      }

      if (isnan(temp_w)) temp_w = 25.0; 
      float T_k = temp_w + 273.15;

      const float R = 8.314462618, F = 96485.3329;
      float nernst = (R * T_k) / F * log(10.0);

      float slope1 = nernst * (7.00 - 4.01) / (v7 - v4);
      float slope2 = nernst * (9.18 - 7.00) / (v9 - v7);
      float slope  = ((slope1 + slope2) * 0.5) * 0.75;  // facteur 0.75 pour adoucir

      float intercept = 7.00 - slope * v7;

      float ph_value = slope * v + intercept;

      ph_value += id(offset_ph).state;

      return round(ph_value * 100) / 100;
      
  - platform: template
    name: "Valeur ORP"
    id: valeur_orp
    unit_of_measurement: "mV"
    accuracy_decimals: 0
    update_interval: 3min
    lambda: |-
      if (!id(pompe_piscine).state) {
        return id(valeur_orp).state;
      }
  
      float v = id(orp_voltage).state;
      float ref = id(orp_ref_voltage).state;
  
      if (isnan(v) || isnan(ref)) {
        return id(valeur_orp).state;
      }
  
      return (ref - v) * 1000.0;

binary_sensor:
  - platform: homeassistant
    id: pompe_piscine
    entity_id: switch.pool_pompe_pompe_de_la_piscine

text_sensor:
  - platform: template
    name: "Qualite Desinfection"
    id: qualite_desinfection
    lambda: |-
      if (!id(pompe_piscine).state) {
        return id(qualite_desinfection).state;
      }
      float orp = id(valeur_orp).state;
      if (orp < 400) return {"Faible"};
      else if (orp < 600) return {"Bonne"};
      else return {"Forte"};
    update_interval: 183s
    icon: mdi:water-check

  - platform: template
    id: mode_affichage
    internal: true
    lambda: |-
      return {"Normal"};
    update_interval: never

  - platform: template
    name: "État Calibration pH"
    lambda: |-
      float v4 = id(cal_ph4).state;
      float v7 = id(cal_ph7).state;
      float v9 = id(cal_ph9).state;

      if (fabs(v7 - v4) < 1e-6 || fabs(v9 - v7) < 1e-6) {
        return {"Inconnue"};
      }

      float slope1 = (7.00 - 4.01) / (v7 - v4);
      float slope2 = (9.18 - 7.00) / (v9 - v7);
      float diff = abs(slope1 - slope2);
      if (diff > 0.5) return {"Incohérente"};
      return {"OK"};

number:
  - platform: template
    name: "Tension pH 4.01"
    id: cal_ph4
    min_value: 0.0
    max_value: 3.3
    step: 0.001
    mode: box
    restore_value: true
    optimistic: true
    initial_value: 1.199
    entity_category: config 

  - platform: template
    name: "Tension pH 7.00"
    id: cal_ph7
    min_value: 0.0
    max_value: 3.3
    step: 0.001
    mode: box
    restore_value: true
    optimistic: true
    initial_value: 1.721
    entity_category: config 

  - platform: template
    name: "Tension pH 9.18"
    id: cal_ph9
    min_value: 0.0
    max_value: 3.3
    step: 0.001
    mode: box
    restore_value: true
    optimistic: true
    initial_value: 2.063
    entity_category: config 

  - platform: template
    name: "Tension de reference ORP (468 mV)"
    id: orp_ref_voltage
    min_value: 0.0
    max_value: 3.3
    step: 0.001
    mode: box
    restore_value: true
    optimistic: true
    initial_value: 1.145
    entity_category: config 

  - platform: template
    name: "Offset pH"
    id: offset_ph
    min_value: -3.0
    max_value: 3.0
    step: 0.001
    mode: box
    restore_value: true
    optimistic: true
    initial_value: 0.00
    entity_category: config

switch:
  - platform: restart
    name: "Piscine pH/ORP Restart"

  - platform: template
    name: "Etalonnage PH 4.01"
    turn_on_action:
      - lambda: |-
          id(cal_ph4).publish_state(id(ph_brut).state);
          id(mode_affichage).publish_state("PH 4.01");

  - platform: template
    name: "Etalonnage PH 7.00"
    turn_on_action:
      - lambda: |-
          id(cal_ph7).publish_state(id(ph_brut).state);
          id(mode_affichage).publish_state("PH 7.00");

  - platform: template
    name: "Etalonnage PH 9.18"
    turn_on_action:
      - lambda: |-
          id(cal_ph9).publish_state(id(ph_brut).state);
          id(mode_affichage).publish_state("PH 9.18");

  - platform: template
    name: "Etalonnage ORP"
    turn_on_action:
      - lambda: |-
          float ref = id(orp_brut).state + 0.468;
          id(orp_ref_voltage).publish_state(ref);
          id(mode_affichage).publish_state("Ref ORP 468 mV");

  - platform: homeassistant
    id: pompe_piscine_switch
    name: "Pompe Piscine"
    entity_id: switch.pool_pompe_pompe_de_la_piscine

Modification de mon YAML le 6/08/25, la lecture du pH est stable, il me reste à stabiliser la lecture de l’orp (autant que possible car instable).

STRUCTURE GÉNÉRALE

Tu as :

  • Des lectures ADC brutes
  • Des capteurs filtrés via template + median
  • Un calcul mathématique précis du pH basé sur la loi de Nernst
  • Une conversion ORP en mV
  • Une calibration manuelle via des boutons et des entrées
  • Des capteurs température, pompe ON/OFF, etc.

:white_check_mark: Sécurité de lecture

Si la pompe est éteinte, les lectures de pH/ORP sont gelées (on retourne la dernière valeur connue), car l’eau n’est plus brassée → mesure non fiable.

1 « J'aime »

Bonjour,
Je suis en cours de réalisation de mon nouveau boîtier piscine à base d’esp32 alimenté en poe et je suis assez intéressé par le ph et orp mais j’ai des interrogations.
Jusqu’à présent j’étais assez mitigé pour y inclure dans mon projet car plus complexe pour moi (piscine Desjoyaux avec filtration sous terre) mais ayant une PAC attenant mon cabanon comprenant les coffret électrique et domotique piscine, je me dis que ça reste envisageable en y mettant les sondes sur la tuyauterie de la pac (l’été la filtration passe par la pac même si celle ci est éteinte)

Combien d’étalonnage par ans sont requis? Est ce fiable?
En effet, deux amis et nombreux sur des forums Jeedom ou autres ont abandonné au bout d’un temps du au variations trop aléatoire des relevés surtout concernant l’orp.
Les modules d’isolation analogique m’ont l’air de palier à tout cela, en êtes vous pleinement satisfait sur la durée?
Comment avez vous relier vos sondes?
Merci

J’avais un boîtier Oklyn (aujourd’hui HS) et j’en étais pleinement satisfait, mais il faut savoir que c’est à base d’ESP12 :stuck_out_tongue_winking_eye: (ou ESP16, j’ai un doute au moment où j’écris ces lignes).
Donc, si certains y arrivent, pourquoi pas nous ? Par contre, je ne suis pas sûr que tu doives mettre ça sur ta PAC, mais plutôt en parallèle de ton système de filtration. L’idée de ‹ Lepelot › au sujet de la chambre de mesure me semble plus pertinente.

Je suis d’accord pour la pac mais pas vraiment le choix.
Mon coffret de filtration est attenant/integré à la piscine et fonctionne ainsi:
piscine —-> chaussette de filtration —> pompe —>by-pass —-> retour piscine ET/OU PAC

Je pensais donc mettre les sondes à l’arrivée de l’eau derrière la pac puisque c’est le seul endroit où je peux accéder à la tuyauterie.
Hormis l’hiver où elle est hors d’eau, je ne pense pas que ça poserai problème étant donné que les sondes seraient également retirées.

tu ne fais jamais tourner ta pisicne l’hiver ?

Si si elle tourne environ environ 1h à 2h par jour en dessous de 10 degrés et en cas de fort gel.
Mais la pompe à chaleur elle est vidée de son eau ainsi que les tuyaux.

Donc si sondes à cet endroit, ben plus de sondes :joy:

1 « J'aime »

Vous connaissez le module Atlas EZO?
Il est I2C et parfaitement compatible esphome.

Apparemment, se serai le top pour les lectures ph et redox et bien supérieur au dfr0504 surtout si combiné avec une bonne sonde ph de même marque.

Atlas Scientific EZO est la référence haut de gamme pour les mesures pH, ORP (RedOx), conductivité, température, etc., en milieu liquide – et fonctionne parfaitement avec ESPHome et Home Assistant.

Ce sont des circuits numériques de lecture et de calibration pour capteurs chimiques (pH, ORP, etc.) :

  • Convertissent les signaux analogiques complexes en valeurs numériques stables

  • Communiquent en I²C ou UART (directement lisible par un ESP32)

  • Incluent :

    • Compensation de température
    • Calibration 1, 2 ou 3 points stockée en mémoire
    • Lecture ultra stable (algorithme interne de filtrage)
    • Durée de vie des sondes bien supérieur.
      • Très stable, filtrage interne, très précis (~±0.002 pH)

Il existe également un module tout prêt ou broché le module ph ou orp atlas ezo.



Version isolée:

uart:
  id: uart1
  tx_pin: GPIO17
  rx_pin: GPIO16
  baud_rate: 9600

sensor:
  - platform: atlas_scientific
    id: ph_sensor
    name: "pH Piscine"
    uart_id: uart1
    unit_of_measurement: "pH"
    update_interval: 10s
1 « J'aime »

Avant d’aller chez DFrobot je lorgnais sur les Atlas. Mais leur connectique ‹ mini › m’a refroidi. Difficile de trouver des sondes adaptées hors de chez eux.
Les DFrobot que j’ai indiqué au dessus font très bien le job et sont ‹ Standard ›

Concernant la stabilité, RAS c’est très fonctionnel. Mais l’Orp est tout de même une variable particulière difficile à maîtriser ou interpréter. Tout dépend de ce qu’on en attend.

2 « J'aime »

Attention à les stocker dans une solution de Kcl.Elle ne supporteront pas d’être à l’air libre.

Oui, c’est prévu d’y stocker dans une solution prévu à cet effet.

Le module est a monté et comprend également la possibilité de mettre une sonde ph sans la prise.


Il paraît que c’est plus fiable et que les sondes ph atlas ont une durée de vie bien supérieure mais en effet… le prix de la sonde :rofl:

Ta solution me plaît bien également.
C’est facilement etalonnable et rien besoin d’autre comme matériel?
Pas besoin d’ads1115?

Je pense qu’il faut alterner les mesures entre le pH et l’ORP et lisser la courbe.
Selon la documentation Oklyn, il faut également éloigner les capteurs des coudes pour limiter les turbulences.