Analog PH Sensor -> Arduino Uno -> ESP8266 (Wemos D1 Mini) -> ESPhome -> Home Assistant

I have project for a microbrewery, where the aim is to be able to monitor pH during a certain step of the process, and trigger notifications when certain pH thresholds are met. I’ve managed to source a cheap Analog 5V pH-sensor, which is hooked up to an Arduino and converts the voltage to a pH value. I’m also connecting the Arduino to an LCD-screen, so you can see the current pH-value locally.

My intention is now send that Arduino pH value to ESPhome. The ultimate goal is to have a sensor in Home Assistant, which displays the same value shown in the LCD. I’ve tried to work my way through understanding the Custom UART component in ESPhome, but I’m not sure where I go wrong. I’ve connected the Arduino and Wemos using the RX and TX pins (RX -> TX and TX->RX). This is the sketch for the Arduino:

#include <Wire.h> 
#include <LiquidCrystal_I2C.h>

const int rs = 12, en = 11, d4 = 5, d5 = 4, d6 = 3, d7 = 2;
//LiquidCrystal lcd(rs, en, d4, d5, d6, d7);
LiquidCrystal_I2C lcd(0x3F, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE);  // Set the LCD I2C address
float calibration = 21.42; //change this value to calibrate
const int analogInPin = A0; 
int sensorValue = 0; 
unsigned long int avgValue; 
float b;
int buf[10],temp;
void setup() {
 lcd.begin(16, 2);
 Serial.begin(9600);
}
 
void loop() {
 for(int i=0;i<10;i++) 
 { 
 buf[i]=analogRead(analogInPin);
 delay(30);
 }
 for(int i=0;i<9;i++)
 {
 for(int j=i+1;j<10;j++)
 {
 if(buf[i]>buf[j])
 {
 temp=buf[i];
 buf[i]=buf[j];
 buf[j]=temp;
 }
 }
   }
 avgValue=0;
 for(int i=2;i<8;i++)
 avgValue+=buf[i];
 float pHVol=(float)avgValue*5.0/1024/6;
 float phValue = -5.70 * pHVol + calibration;
 Serial.println(phValue);
 lcd.print("pH: ");
 lcd.print(phValue);
 lcd.setCursor(0,1);
 if (phValue < 4) {
  lcd.print("Very acidic");
 }
 else if (phValue >= 4 && phValue < 5) {
  lcd.print("Acidic");
 }
 else if (phValue >= 5 && phValue < 7) {
  lcd.print("Acidic-ish");
 }
 else if (phValue >= 7 && phValue < 8) {
  lcd.print("Neutral");
 }
 else if (phValue >= 8 && phValue < 10) {
  lcd.print("Alkaline-ish");
 }
 else if (phValue >= 10 && phValue < 11) {
  lcd.print("Alkaline");
 }
 else if (phValue >= 11) {
  lcd.print("Very alkaline");
 }
 delay(3000);
 lcd.clear();
 
}

So my question is; Do I need to add something in the Arduino code and what configurations do I need to do on the ESP side to read that pHValue as a sensor value for Home Assistant?

I’m just getting started on ESP8266 but my two cents:

Send data from Arduino to ESP2866 by serial comm.


From ESP2866 to HA
This is a sample project to send sensor data to HA
https://adonno.com/salt-level-sensor/

What you might consider is eliminating the Arduino completely and simply use an ESP. The I/O capability on the ESP should be sufficient for doing what you want, especially an ESP32. The trick will be reading the PH sensor. But I think you could do that with a simple analog sensor with the right filters. By eliminating the Arduino you improve the reliability of your setup (less moving parts) and eliminate the need for inter-device communication. ESPHome supports both LCD and OLED displays.

See https://esphome.io/cookbook/display_time_temp_oled.html for an example of using a local display with an external sensor. I’m currently working on a similar project for my bathroom where I’m creating a humidity controlled exhaust fan controller plus a clock.

@ssieb was kind enough to write me the entire code to get this working. Link to custom component: http://sieb.net/serial.zip

That’s a cool solution. But I’m still wondering if it’s unnecessarily complex. You can hook the LCD and PH sensor up to your Wemos D1 mini and skip the Arduino altogether. Is there a reason you have to use the Arduino? (I know there are always additional constraints and conditions I’m not aware of.)

The ESPHome YAML would be pretty straight forward with a little bit of Lambda for displaying on the LCD and the pH calculation. But then you have one less hardware device, no custom components, and a lot more direct control.

From what I can tell of your Arduino code, it takes 10 readings, sorts them, and throws out the two highest and two lowest readings. Then it averages the remaining 6 readings and calculates the pH value. I’m not sure if throwing out the high and low values is critical. You could do that with ESPHome, but I did not do that in my example below. I did throw in an averaging filter though.

I have NOT tested this YAML (although it compiles fine), but it should give you an idea of how to proceed if you want to go this route.

sensor:
  # https://esphome.io/components/sensor/adc.html
  - platform: adc
    pin: A0
    id: ph
    name: "pH Sensor"
    update_interval: 1s
    unit_of_measurement: pH
    # https://esphome.io/components/sensor/index.html#sensor-filters
    filters:
      #adjust this forumula for your needs, the /6 in the original was part of the averaging so I skipped it
      - lambda: return -5.70 * ( x * 5.0 / 1024) + 21.42; 
      - sliding_window_moving_average:
          window_size: 10
          send_every: 10

display:
  # https://esphome.io/components/display/lcd_display.html
  - platform: lcd_gpio
    dimensions: 18x4
    data_pins:
      - D0
      - D1
      - D2
      - D3
    enable_pin: D4
    rs_pin: D5
    lambda: |-
      it.printf(0, 0, "pH: %.1f", id(ph).state);
      if (id(ph).state < 4) {
        it.print(1, 0, "Very acidic");
      }
      else if (id(ph).state >= 4 && id(ph).state < 5) {
        it.print(1, 0, "Acidic");
      }
      else if (id(ph).state >= 5 && id(ph).state < 7) {
        it.print(1, 0, "Acidic-ish");
      }
      else if (id(ph).state >= 7 && id(ph).state < 8) {
        it.print(1, 0, "Neutral");
      }
      else if (id(ph).state >= 8 && id(ph).state < 10) {
        it.print(1, 0, "Alkaline-ish");
      }
      else if (id(ph).state >= 10 && id(ph).state < 11) {
        it.print(1, 0, "Alkaline");
      }
      else if (id(ph).state >= 11) {
        it.print(1, 0, "Very alkaline");
      }

I missed you were using an i2c LCD, so you would need to switch to that display component vs the gpio one I have in my code above.

The main reason for using the Arduino originally, was that it can receive 5V on the A0. And during the time when I was investigating what to use for the project, I didn’t even know what a ESP-board was and what it could do. Now though, it actually seems like the Arduino would be unnecessary, if I were to use a voltage divider to bring down the voltage to 3.3V, which the Wemos handle on the A0 pin. I would definitely appreciate the leanest possible solution.

Thanks for taking the time to put together the config. Do you know if there is a possibility to include a variable in the lambda, which could be defined directly in the HA UI using the number_input (https://www.home-assistant.io/integrations/input_number/)? That way I could modify the calibration value (currently set at 21.42) without compiling the yaml each time.

Take a look at the last post for someone doing just that: Esphome automation help re: on_value_range

I’m a little unsure on the HA side how you add it to the UI for editing though.

One other comment, I think you might actually be better served by the median filter instead of the simple average. This would help compensate for outlier readings similar to how the original code threw out the top and bottom two readings.

  filters:
    - median:
        window_size: 7
        send_every: 4
        send_first_at: 3

So, at the end of day I went with no Arduino at all. It didn’t really make any sense to use it since the ESP could handle everything by itself. I used a potentiometer to scale down the voltage from the pH sensor from 5V to 3.3V. Here are some pics of the final product (https://imgur.com/gallery/CBLpdwX)

Thanks @coderanger for your yaml suggestions. The final yaml is as follows:

esphome:
  name: phsensor_rev
  platform: ESP8266
  board: d1_mini

wifi:
  ssid: "xxx"
  password: "xxx"

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "xxx"
    password: "xxx"

captive_portal:

# Enable logging
logger:

# Enable Home Assistant API
api:

ota:

sensor:
  # https://esphome.io/components/sensor/adc.html
  - platform: adc
    pin: A0
    id: ph
    name: "pH Sensor"
    update_interval: 1s
    unit_of_measurement: pH
    # https://esphome.io/components/sensor/index.html#sensor-filters
    filters:
      - median:
          window_size: 7
          send_every: 4
          send_first_at: 3
      # Measured voltage -> Actual pH (buffer solution)
      - calibrate_linear:
          - 0.59 -> 7.0
          - 0.71 -> 4.0

i2c:
  sda: D2
  scl: D1
  scan: true
  
display:
  - platform: lcd_pcf8574
    dimensions: 16x2
    lambda: |-
      it.printf(0, 0, "pH: %.2f", id(ph).state);
      if (id(ph).state < 4) {
        it.print(0, 1, "Very acidic");
      }
      else if (id(ph).state >= 4 && id(ph).state < 5) {
        it.print(0, 1, "Acidic");
      }
      else if (id(ph).state >= 5 && id(ph).state < 7) {
        it.print(0, 1, "Acidic-ish");
      }
      else if (id(ph).state >= 7 && id(ph).state < 8) {
        it.print(0, 1, "Neutral");
      }
      else if (id(ph).state >= 8 && id(ph).state < 10) {
        it.print(0, 1, "Alkaline-ish");
      }
      else if (id(ph).state >= 10 && id(ph).state < 11) {
        it.print(0, 1, "Alkaline");
      }
      else if (id(ph).state >= 11) {
        it.print(0, 1, "Very alkaline");
      }
1 Like

Hello,
this looks very interesting! Do you have any schematic or parts list to share?

Hi @frasskungin. Nice project !
What kind of potentiometer did you use ?
How did you proceed to make your ajustement with it ?
Did you use a calibration automation ?
brgds

Hi. You can check the wire diagram and parts list on my git page GitHub - frasskungin/esphome-phsensor: ESP8266 with analog pH sensor

I used a 5K Ohm potentiometer.

At the end of the day the project was not successful fit for the application I had planned it for. The thing was that long submersion of the probe caused the sensor values to go off the charts. For shorter periods of measuring the sensor works pretty well.

You might find that if you use an external ADC it will be more accurate. Something about the pH scale means every 1 point in pH is an exponential increase (or decrease), so the higher or lower you get from natural pH7 the hydrogen ion activity is doubling so a pH of 5 has 4x the ion activity of a natural pH of 7, not double, and pH 4 is 8 times more than pH 7. This means you need to divide the voltage more times than you can with pin A0 because of the limitations of the internal ADC. The pH probe being submerged is not the problem the ADC is.

Nice approach, much cleaner than the random tutorials. Once again, I’m impressed by EspHome’s design. I ended up with this:

  - platform: adc
    pin: A0
    id: horti3_ph
    name: "Horti3 PH Meter"
    update_interval: 1s
    unit_of_measurement: pH
    # https://esphome.io/components/sensor/index.html#sensor-filters
    filters:
      - median:
          window_size: 7
          send_every: 4
          send_first_at: 3
      - lambda: |-
           ESP_LOGD("ph", "volatge=%fv", x);
           return x;
      # Measured voltage -> Actual pH (buffer solution)
      # Lower the potentiometer (counter-clockwise) at pH 7.
      # Pick a baseline voltage (the log entry above) on the low end of the range to leave room for the higher voltage of low pH.
      # Then, switch to pH 4 and note the voltage. Do not worry about expected voltages (e.g. 2.5v) found in random tutorials.
      - calibrate_linear:
          - 0.79 -> 7.0
          - 0.93 -> 4.0
      - lambda: |-
           ESP_LOGD("ph", "read=%fpH", x);
           return x;

This is a ESP8266 NodeMCU. So far, even with a CCC probe, it’s in-line with my Apera meter.

You only can use it for once in a while measuring the water ?

Meanwhile it’s morge than a year, is it still working ? I mean is there a expiration date of this sensor ?