kizniche / Mycodo

An environmental monitoring and regulation system
http://kylegabriel.com/projects/
GNU General Public License v3.0
2.95k stars 494 forks source link

ADS1115 3 pH calibration points #1224

Open Tomberer opened 2 years ago

Tomberer commented 2 years ago

Hi, I see that Anyleaf and Atlas Scientific pH sensors allow 3 pH calibration points, would it be possible for the 'ADS1115 pH/EC' input to have this option as well (it only seems to have 2 as of now).

Many thanks,

kizniche commented 2 years ago

It is certainly possible, but I'm not very familiar with the software calibration to create a new Input with an additional calibration point. If you look at the Atlas Scientific (AS) or Anyleaf code, you will see the AS calibration is stored and calculated on the chip itself, and the Anyleaf calibration is performed with the Anyleaf Python library (which could very well also store/calculate it on the chip, I haven't looked). You're welcome to create a new module with 3 pt calibration for inclusion in the builtin set.

Tomberer commented 2 years ago

Thanks for the quick response, I'm rather illiterate when it comes to programming so I think I'll stick to just 2 calibration points.

Thanks for all the effort you've put into building this software by the way, I really can't properly express how grateful I am for your work.

kizniche commented 2 years ago

I'll take a look at the ADS pH inputs (I didn't create those) and see if a 3 pt could be easily imemented. No guarantees, but if I can get it to work, I'll let you know.

kizniche commented 2 years ago

I've created some test code that implements a 3-pt calibration. The 2-pt calibration is from ads1115_analog_ph_ec.py and the 3-pt code I pulled from anyleaf/init.py#L555, referenced by https://www.anyleaf.org/blog/how-to-calibrate-ph-sensors.


def nernst_correction(volt, temp):
    """Apply temperature correction for pH. This provides the voltage as if it were measured at 25C.
    Based on the Nernst equation: E = E0 - ln(10) * RT/nF * pH; this gives E = E0 - 0.198 * T * pH.
    The correction is a simple ratio of absolute temperature."""
    volt_25C = volt * 298 / (temp + 273)

    return volt_25C

def lg(pt0: (float, float), pt1: (float, float), pt2: (float, float), v: float) -> float:
    """Compute the result of a Lagrange polynomial of order 3.
    Algorithm created from the `P(x)` eq
    [here](https://mathworld.wolfram.com/LagrangeInterpolatingPolynomial.html)."""
    result = 0.0

    x = [pt0[0], pt1[0], pt2[0]]
    y = [pt0[1], pt1[1], pt2[1]]

    for j in range(3):
        c = 1.0
        for i in range(3):
            if j == i:
                continue
            c *= (v - x[i]) / (x[j] - x[i])
        result += y[j] * c

    return result

def convert_volt_to_ph(volt, temp, ph_cal_ph1, ph_cal_v1, ph_cal_t1, ph_cal_ph2, ph_cal_v2, ph_cal_t2, ph_cal_ph3, ph_cal_v3, ph_cal_t3):
    """Convert voltage to pH."""
    if ph_cal_ph3:
        # 3-Pt calibration
        ph = lg(
            (nernst_correction(ph_cal_v1, ph_cal_t1), ph_cal_ph1),
            (nernst_correction(ph_cal_v2, ph_cal_t2), ph_cal_ph2),
            (nernst_correction(ph_cal_v3, ph_cal_t3), ph_cal_ph3),
            nernst_correction(volt, temp)
        )
    else:
        # 2-Pt calibration: Calculate slope and intercept from calibration points.
        slope = (
            (ph_cal_ph1 - ph_cal_ph2) /
            (nernst_correction(ph_cal_v1, ph_cal_t1) -
             nernst_correction(ph_cal_v2, ph_cal_t2))
        )

        intercept = (ph_cal_ph1 -
                     slope *
                     nernst_correction(ph_cal_v1, ph_cal_t1))

        if temp is not None:
            # Perform temperature corrections
            ph = slope * nernst_correction(volt, temp) + intercept
        else:
            # Don't perform temperature corrections
            ph = slope * volt + intercept

    return ph

test_2_pt = [
    (10.0, 1, 27.0),
    (4.0, 3, 14.0)
]

test_3_pt = [
    (10.0, 1, 27.0),
    (7.0, 2.7, 30.0),
    (4.0, 3, 14.0)
]

print(convert_volt_to_ph(3, 20.0, test_2_pt[0][0], test_2_pt[0][1], test_2_pt[0][2], test_2_pt[1][0], test_2_pt[1][1], test_2_pt[1][2], None, None, None))
print()
print(convert_volt_to_ph(3, 20.0, test_3_pt[0][0], test_3_pt[0][1], test_3_pt[0][2], test_3_pt[1][0], test_3_pt[1][1], test_3_pt[1][2], test_3_pt[2][0], test_3_pt[2][1], test_3_pt[2][2]))

Output:

pi@rpi:~/Mycodo/test $ ../env/bin/python ./test_2_3_pt_cal_01.py
4.18039185118786

4.472628297516382

I'm not completely confident that this is correct, so I'll try to do some more testing to determine if it is indeed working as expected and accurate.

kizniche commented 1 year ago

Here is test module v01 that incorporates the 3-pt pH calibration. You'll need to change the extension from .txt to .py, then import on the Input Import page. If you do test, let me know how it goes.

ads1115_analog_ph_ec_3pt_calibration.txt

kizniche commented 1 year ago

This issue has been mentioned on Radical DIY Forum. There might be relevant details there:

https://forum.radicaldiy.com/t/how-to-create-a-custom-input-with-calibration-using-ads1115-adc-and-gravity-analog-ph-and-ec-sensors/55/42

kizniche commented 1 year ago

Checking to see if anyone has tested this new input.