Digital ORP sensor (i2C 16-bit) + espHome

Hello everyone. I am trying to connect a Digital ORP/pH sensor to an ESPHome. The sensor is like this. The sensor is digital, 16-bit, connected via I2C.

The seller provided me with separate executable files that work fine in the Arduino IDE (output data to the com port).
The main file of the executable program is as follows.

#include <Arduino.h>
#include <stdint.h>
#include <Wire.h>
#include "ADC.h"

//! I2C address of the pH/ORP module. 
//! Configured by tying the CA0, CA1 pins to H( High ) or L( Low ). Or to be left open for Float.

// I2C Slave Address               //  CA1       CA0    
// #define ADC_I2C_ADDRESS 0x14    //  Low       High       
// #define ADC_I2C_ADDRESS 0x15    //  Low       Float  
// #define ADC_I2C_ADDRESS 0x17    //  Float     High  
   #define ADC_I2C_ADDRESS 0x24    //  Float     Float(default)
// #define ADC_I2C_ADDRESS 0x26    //  High      High
// #define ADC_I2C_ADDRESS 0x27    //  High      Float

// Global variables
static int16_t  speed_mode = SLOW;                   //!< The ADC Speed Mode settings. Change it to "FAST" for higher data rate.
static float    adc_vref = 2.5;                      //!< The ADC reference voltage
static uint8_t  rejection_mode = ADC_R50;            //!< The ADC rejection mode
static uint8_t  i2c_address = ADC_I2C_ADDRESS;       //!< I2C address in 7 bit format for part
static uint16_t eoc_timeout = 300;                   //!< Timeout in ms

static float adc_offset = 0;                         //!< Add an offset value to the ADC output.

void setup()
{
  Wire.begin();                                      // wake up I2C bus
  Serial.begin(9600);                                // Initialize the serial port to the PC
}
  
void loop()
{
  uint8_t acknowledge = 0;

  acknowledge |= read_adc();
  
  if (acknowledge)
  Serial.println(F("***** I2C ERROR *****"));
  
  delay(1000);
}

//! @return 0 if successful, 1 is failure
int8_t read_adc()
{
  uint8_t adc_command;           
  int32_t adc_code = 0;           
  float   adc_voltage = 0;        
  uint8_t ack = 0;
    
  adc_command = rejection_mode| speed_mode;
          
  ack |= adc_read( i2c_address, adc_command, &adc_code, eoc_timeout );
  
  adc_voltage = adc_code_to_voltage( adc_code, adc_vref ) * 1000 + adc_offset;  //!< Convert the ADC code to mV voltage

  //!< Convert the mV signal to pH value. 
  //!< ......

  if ( !ack )
  {
    Serial.print( F("*************************\n" ) ); 
    Serial.print( "BNC Input Voltage: " );      
    Serial.print( adc_voltage, 1 );
    Serial.print( F( "mV\n" ) );
  }
  else
  {
    Serial.print( F( "Error in read" ) );
    return 1;
  }
      
  return( 0 );
}

The other two are:
ADC.h

#ifndef ADC_H
#define ADC_H


//! This union splits one int32_t (32-bit signed integer) or uint32_t (32-bit unsigned integer)
//! four uint8_t's (8-bit unsigned integers) and vice versa.
union LT_UNION_INT32_4BYTES
{
  int32_t  LT_INT32;       //!< 32-bit signed integer to be converted to four bytes
  uint32_t LT_UINT32;     //!< 32-bit unsigned integer to be converted to four bytes
  uint8_t  LT_BYTE[4];     //!< 4 bytes (unsigned 8-bit integers) to be converted to a 32-bit signed or unsigned integer
};


// Select rejection frequency - 50 and 60, 50, or 60Hz
#define ADC_R50         0b00000010
#define ADC_R60         0b00000100
#define ADC_R50_R60     0b00000000

// Speed settings is bit 7 in the 2nd byte
#define SLOW 0b00000000 // slow output rate with autozero
#define FAST 0b00000001 // fast output rate with no autozero


/*Commands
Construct a channel / resolution control word by bitwise ORing one choice from the channel configuration
and one choice from the Oversample ratio configuration. You can also enable 2Xmode, which will increase
sample rate by a factor of 2 but introduce an offset of up to 2mV. */

//! Reads from ADC.
//! @return  1 if no acknowledge, 0 if acknowledge
uint8_t adc_read(uint8_t i2c_address,   //!< I2C address (7-bit format) for part
                 uint8_t adc_command,   //!< High byte command written to ADC
                 int32_t *adc_code,     //!< 4 byte conversion code read from ADC
                 uint16_t timeout       //!< Timeout in ms
                    );

//! Calculates the voltage corresponding to an adc code, given the reference (in volts)
//! @return Returns voltage calculated from the ADC code.
float adc_code_to_voltage(int32_t adc_code,     //!< Code read from adc
                          float vref            //!< VRef (in volts)
                             );

#endif  // ADC_H

ADC.cpp

#include <stdint.h>
#include <Arduino.h>
#include <Wire.h>
#include "ADC.h"


// Write two command bytes, then receive a block of data
int8_t i2c_one_byte_command_read_block(uint8_t address, uint8_t command, uint8_t length, uint8_t *values)
{
  int8_t ret = 0;
  uint8_t i = (length-1);
  uint8_t readBack = 0;

  Wire.beginTransmission(address);
  Wire.write(byte(command));

  if (Wire.endTransmission(false)) // endTransmission(false) is a repeated start
  {
    // endTransmission returns zero on success
    Wire.endTransmission();
    return(1);
  }
  readBack = Wire.requestFrom((uint8_t)address, (uint8_t)length, (uint8_t)true);

  if (readBack == length)
  {
    while (Wire.available())
    {
      values[i] = Wire.read();
      if (i == 0)
        break;
      i--;
    }
    return (0);
  }
  else
  {
    return (1);
  }
}


//! Reads from the ADC that accepts a 8 bit configuration and returns a 24 bit result.
//! Returns the state of the acknowledge bit after the I2C address write. 0=acknowledge, 1=no acknowledge.
int8_t adc_i2c_8bit_command_24bit_data(uint8_t i2c_address,uint8_t adc_command,int32_t *adc_code,uint16_t eoc_timeout)
{
  int8_t ack;
  uint16_t  timer_count = 0;        // Timer count to wait for ACK
  LT_UNION_INT32_4BYTES data;       // ADC data
  
  while(1)
  {
    ack = i2c_one_byte_command_read_block(i2c_address, adc_command, 3, data.LT_BYTE);
    if(!ack) break; // !ack indicates success
    if (timer_count++>eoc_timeout)     // If timeout, return 1 (failure)
      return(1);
    else
      delay(1);
  }

  data.LT_BYTE[3] = data.LT_BYTE[2]; // Shift bytes up by one. We read out 24 bits,
  data.LT_BYTE[2] = data.LT_BYTE[1]; // which are loaded into bytes 2,1,0. Need to left-
  data.LT_BYTE[1] = data.LT_BYTE[0]; // justify.
  data.LT_BYTE[0] = 0x00;
  data.LT_UINT32 >>= 2;  // Shifts data 2 bits to the right; operating on unsigned member shifts in zeros.
  data.LT_BYTE[3] = data.LT_BYTE[3] & 0x3F; // Clear upper 2 bits JUST IN CASE. Now the data format matches the SPI parts.
  *adc_code = data.LT_INT32;
  return(ack); // Success
}

// Calculates the voltage corresponding to an adc code, given the reference voltage (in volts)
// This function handles all differential input parts, including the "single-ended" mode on multichannel
// differential parts. Data from I2C parts must be right-shifted by two bit positions such that the MSB
// is in bit 28 (the same as the SPI parts.)
float adc_diff_code_to_voltage(int32_t adc_code, float vref)
{
  float voltage;

  adc_code -= 0x20000000;             //! 1) Converts offset binary to binary
  voltage=(float) adc_code;
  voltage = voltage / 536870912.0;    //! 2) This calculates the input as a fraction of the reference voltage (dimensionless)
  voltage = voltage * vref;           //! 3) Multiply fraction by Vref to get the actual voltage at the input (in volts)
  return(voltage);
}

// Reads from the ADC.
uint8_t adc_read(uint8_t i2c_address, uint8_t adc_command, int32_t *adc_code, uint16_t timeout)
{
  return (adc_i2c_8bit_command_24bit_data(i2c_address, adc_command, adc_code, timeout));
}

// Calculates the voltage corresponding to an ADC code, given the reference (in volts)
float adc_code_to_voltage(int32_t adc_code, float vref)
{
  return (adc_diff_code_to_voltage(adc_code, vref));
}

But I can’t figure out how to integrate this code into ESP Home. I tried custom sensor component (Custom Sensor Component — ESPHome ), I get to step 3, and I don’t know what to do next. I’m still new to Home Assistant.

I would be grateful if someone could help me with this and guide me in the right direction.

Hello Alex, Did you ever get this thing to work with ESPHome? I am trying to do this exact same thing, so I am interested in the resullts and your experience. Bst gds, Pierre

Hello guys, I am trying to use this Ali express sensor as well and I could use some help. Did anyone succeed with the integration into ESPHome? Thanks for your help.

I am also curious if you had any luck getting these to work? I think I have the same sensors, and thus far, I have not had luck getting them to work via ESPhome as of yet.

Thank you,

Got the same PH/ORP i2c sensor… can anyone provide some guidance?

In case anyone else stumbles across this Aliexpress sensor, DO NOT BUY. Wasted HOURS on getting this working, it does not even show up in an i2c bus scan. Not sure what this is, but it does not work.