hello my fellow reefers
i want to share with you my DIY KH doser , i am not that expert in programing and its just a hobby for me so lets get started
first i wanted something to be testing KH regularly and to be accurate as much as possible , think for using KH acid titration to certain PH value as all commercial products doing
so i started with buying 3 kamoer dosing pumps 12v from here then used small driver its very cheap but good and doing the job ULN2003 driver board from here then small DC fan pump to be used as magnetic stirrer from here size 4010 or any size you want and magnet from here and the stirrer magnet from here and ph probe with driver here make sure it is DFRobot Gravity Analog pH Sensor Meter Kit V2 and then the esp32 board from here and then last thing for now is the DC-DC buck converter to convert 12V to 5V from here now you bought everything you need uh still need 250ml biker here
we will use arduino IOT sign up there
then you need to creat this thing i will attch pictures
this maybe required you to to be charged for all this things but you can use only two things i used all of that to make everything accessible for me to do the calibration and pump priming but if you will use premium account you can use this iot for anything in your tank so it really worth the money and its not much based on the price we are paying for our marine stuff !
then in the skitch you can go to full editor
then add libraries
click there
then include
EEPROM
and SimpleTimer
also DFRobot_ESP_PH_WITH_ADC
now you upload the code
here is my code we can continue later i may be make some good picture and will try to make video for it Enjoy and happy reefing uhh forget if you new to arduino iot cloud i will give you like to some one will make you expert so make sure you see this video before start coding so you get familiar of it here
also forget to show you my dashboard so you have to make the same dashboard with the widget to get it work Note: stirrer is button to activate manual test
i want to share with you my DIY KH doser , i am not that expert in programing and its just a hobby for me so lets get started
first i wanted something to be testing KH regularly and to be accurate as much as possible , think for using KH acid titration to certain PH value as all commercial products doing
so i started with buying 3 kamoer dosing pumps 12v from here then used small driver its very cheap but good and doing the job ULN2003 driver board from here then small DC fan pump to be used as magnetic stirrer from here size 4010 or any size you want and magnet from here and the stirrer magnet from here and ph probe with driver here make sure it is DFRobot Gravity Analog pH Sensor Meter Kit V2 and then the esp32 board from here and then last thing for now is the DC-DC buck converter to convert 12V to 5V from here now you bought everything you need uh still need 250ml biker here
we will use arduino IOT sign up there
then you need to creat this thing i will attch pictures
this maybe required you to to be charged for all this things but you can use only two things i used all of that to make everything accessible for me to do the calibration and pump priming but if you will use premium account you can use this iot for anything in your tank so it really worth the money and its not much based on the price we are paying for our marine stuff !
then in the skitch you can go to full editor
then add libraries
then include
EEPROM
and SimpleTimer
also DFRobot_ESP_PH_WITH_ADC
now you upload the code
#include "SimpleTimer.h"
#include "EEPROM.h"
SimpleTimer timer;
// DFRobot_ESP_PH_WITH_ADC_BY_GREENPONIK - Version: Latest
#include <DFRobot_ESP_PH_WITH_ADC.h>
#include "EEPROM.h"
DFRobot_ESP_PH_WITH_ADC ph;
#define ESPADC 4096.0 //the esp Analog Digital Convertion value
#define ESPVOLTAGE 3300 //the esp voltage supply value
#define PH_PIN 35 //the esp gpio data pin number
#define PHVALUEADDR 0
float voltage, phValue, temperature = 25;
const int pumpPin[4] = { 25, 26, 27, 33};
bool pumpPinState[4] = { 0, 0, 0, 0};
bool pumpRunning = false;
bool r1 = 0, r2 = 0, r3 = 0;
bool Eventor = 0;
float Reject_Time;
float Sampling_Time;
float Reagent_volume_per_second;
float calsample_eeprom, calreject_eeprom, calreagent_eeprom, correction_eeprom, reagent_volume, Kh;
unsigned long start_titration_time = 0;
unsigned long int avgval;
unsigned long t1 = 0 ;
unsigned long t_1 = 0 ;
long previousMillis = 0 ;
unsigned long currentMillis;
long interval = 10000;
const unsigned long period = 10000;
int buffer_arr[20], temp;
unsigned long s1;
unsigned long s2;
/*
Sketch generated by the Arduino IoT Cloud Thing "Untitled"
https://create.arduino.cc/cloud/things/882fa250-9400-472d-b8e5-9eca9629491f
Arduino IoT Cloud Variables description
The following variables are automatically generated and updated when changes are made to the Thing
float correctionValue;
float kH;
float reagentCalValue;
float rejectCalValue;
float sampleCalValue;
CloudSchedule schedule_test;
bool calReagent;
bool calReject;
bool calSample;
bool reagentPump;
bool rejectPump;
bool samplePump;
bool stirrer;
CloudTime time_read;
Variables which are marked as READ/WRITE in the Cloud Thing will also have functions
which are called when their values are changed from the Dashboard.
These functions are generated with the Thing and added at the end of this sketch.
*/
#include "thingProperties.h"
void setup() {
// Initialize serial and wait for port to open:
Serial.begin(9600);
// This delay gives the chance to wait for a Serial Monitor without blocking if none is found
delay(1500);
EEPROM.begin(512);//needed to permit storage of calibration value in eeprom
ph.begin();
for (int p = 0; p <= 3; p++)
{
pinMode(pumpPin[p], OUTPUT);
}
// Defined in thingProperties.h
initProperties();
// Connect to Arduino IoT Cloud
ArduinoCloud.begin(ArduinoIoTPreferredConnection);
/*
The following function allows you to obtain more information
related to the state of network and IoT Cloud connection and errors
the higher number the more granular information you’ll get.
The default is 0 (only errors).
Maximum is 4
*/
setDebugMessageLevel(2);
ArduinoCloud.printDebugInfo();
}
void loop() {
ArduinoCloud.update();
timer.run();
static unsigned long timepoint = millis();
if (millis() - timepoint > 30U)
{
timepoint = millis();
for (int i = 0; i < 20; i++)
buffer_arr = analogRead(PH_PIN) / ESPADC * ESPVOLTAGE; // read the voltage;
for (int i = 0; i < 19; i++)
{
for (int j = i + 1; j < 20; j++)
{
if (buffer_arr > buffer_arr[j])
{
temp = buffer_arr;
buffer_arr = buffer_arr[j];
buffer_arr[j] = temp;
}
}
}
avgval = 0;
for (int i = 2; i < 18; i++)
avgval += buffer_arr;
float volt = (float)avgval / 16;
voltage = volt;
phValue = ph.readPH(voltage, temperature); // convert voltage to pH with temperature compensation
/*Serial.print("voltage:");
Serial.println(voltage);
Serial.print("PhValue:");
Serial.println(phValue);*/
}
ph.calibration(voltage, temperature); // calibration process by Serail CMD
if (stirrer == 1) {
Eventor = 1;
KH_Test();
kH = Kh;
}
}
/*
Since Led is READ_WRITE variable, onLedChange() is
executed every time a new value is received from IoT Cloud.
*/
void onLedChange() {
// Add your code here to act upon Led change
}
/*
Since SamplePump is READ_WRITE variable, onSamplePumpChange() is
executed every time a new value is received from IoT Cloud.
*/
void onSamplePumpChange() {
if (samplePump == 1)
{
digitalWrite(pumpPin[0], HIGH);
}
else
digitalWrite(pumpPin[0], LOW);
}
/*
Since ReagentPump is READ_WRITE variable, onReagentPumpChange() is
executed every time a new value is received from IoT Cloud.
*/
void onReagentPumpChange() {
if (reagentPump == 1)
{
digitalWrite(pumpPin[1], HIGH);
}
else
digitalWrite(pumpPin[1], LOW);
}
/*
Since RejectPump is READ_WRITE variable, onRejectPumpChange() is
executed every time a new value is received from IoT Cloud.
*/
void onRejectPumpChange() {
if (rejectPump == 1)
{
digitalWrite(pumpPin[2], HIGH);
}
else
digitalWrite(pumpPin[2], LOW);
}
/*
Since CalSample is READ_WRITE variable, onCalSampleChange() is
executed every time a new value is received from IoT Cloud.
*/
void onCalSampleChange() {
if (calSample == 1) { // 10 sec timer to run the sample pump for caliberation
previousMillis = millis();
currentMillis = previousMillis ;
digitalWrite(pumpPin[0], HIGH);
while (currentMillis - previousMillis < interval )
{
currentMillis = millis();
Serial.println(((currentMillis - previousMillis) / 1000 )) ;
calSample = 0;
}
Serial.println((currentMillis - previousMillis) / 1000 );
digitalWrite(pumpPin[0], LOW);
calSample = 0;
}
}
/*cal1test = 1 ;
if (cal1test == 1) { // 10 sec timer for caliberation of sampling pump
digitalWrite(pumpPin[0], HIGH);
Serial.println("sample pump calibration started");
timer.setTimeout(10000L, []()
{
digitalWrite(pumpPin[0], LOW);
Serial.println(" calibration for sample pump finished");
calSample = 0 ;
Serial.println(" calsample false");
cal1test = 0;
});
}
}
}*/
/*
Since CalReagent is READ_WRITE variable, onCalReagentChange() is
executed every time a new value is received from IoT Cloud.
*/
void onCalReagentChange() {
if ( calReagent == 1 ) {
previousMillis = millis();
currentMillis = previousMillis ;
digitalWrite(pumpPin[1], HIGH);
while (currentMillis - previousMillis < interval )
{
currentMillis = millis();
Serial.println(((currentMillis - previousMillis) / 1000 )) ;
calReagent = 0;
}
Serial.println((currentMillis - previousMillis) / 1000 );
digitalWrite(pumpPin[1], LOW);
calReagent = 0;
}
}
/*cal2test = 1 ;
if (cal2test == 1) { // 10 sec timer for caliberation of reagent pump
digitalWrite(pumpPin[1], HIGH);
Serial.println("reagent pump calibration started");
timer.setTimeout(10000L, []()
{
digitalWrite(pumpPin[1], LOW);
Serial.println(" calibration for reagent pump finished");
calReagent = 0 ;
cal2test = 0;
});
}
}
}*/
/*
Since CalReject is READ_WRITE variable, onCalRejectChange() is
executed every time a new value is received from IoT Cloud.
*/
void onCalRejectChange() {
if ( calReject == 1 ) {
previousMillis = millis();
currentMillis = previousMillis ;
digitalWrite(pumpPin[1], HIGH);
while (currentMillis - previousMillis < interval )
{
currentMillis = millis();
Serial.println(((currentMillis - previousMillis) / 1000 )) ;
calReagent = 0;
}
Serial.println((currentMillis - previousMillis) / 1000 );
digitalWrite(pumpPin[1], LOW);
calReject = 0;
}
}
/*
Since SampleCalValue is READ_WRITE variable, onSampleCalValueChange() is
executed every time a new value is received from IoT Cloud.
*/
void onSampleCalValueChange() {
// Add your code here to act upon SampleCalValue change
calsample_eeprom = EEPROM.readFloat(PHVALUEADDR + sizeof(float) + sizeof(float));
if (sampleCalValue != calsample_eeprom)
{
EEPROM.writeFloat(PHVALUEADDR + sizeof(float) + sizeof(float), sampleCalValue);
EEPROM.commit();
Serial.println("SampleCalValue = ");
Serial.println(EEPROM.readFloat(PHVALUEADDR + sizeof(float) + sizeof(float)));
}
}
/*
Since RejectCalValue is READ_WRITE variable, onRejectCalValueChange() is
executed every time a new value is received from IoT Cloud.
*/
void onRejectCalValueChange() {
calreject_eeprom = EEPROM.readFloat(PHVALUEADDR + sizeof(float) + sizeof(float) + sizeof(float));
if (rejectCalValue != calreject_eeprom)
{
EEPROM.writeFloat(PHVALUEADDR + sizeof(float) + sizeof(float) + sizeof(float), rejectCalValue);
EEPROM.commit();
Serial.println("rejectCalValue = ");
Serial.println(EEPROM.readFloat(PHVALUEADDR + sizeof(float) + sizeof(float) + sizeof(float)));
}
}
/*
Since ReagentCalValue is READ_WRITE variable, onReagentCalValueChange() is
executed every time a new value is received from IoT Cloud.
*/
void onReagentCalValueChange() {
calreagent_eeprom = EEPROM.readFloat(PHVALUEADDR + sizeof(float) + sizeof(float) + sizeof(float) + sizeof(float));
if (reagentCalValue != calreagent_eeprom)
{
EEPROM.writeFloat(PHVALUEADDR + sizeof(float) + sizeof(float) + sizeof(float) + sizeof(float), reagentCalValue);
EEPROM.commit();
Serial.println("reagentCalValue = ");
Serial.println(EEPROM.readFloat(PHVALUEADDR + sizeof(float) + sizeof(float) + sizeof(float) + sizeof(float)));
}
}
/*
Since Eventor is READ_WRITE variable, onEventorChange() is
executed every time a new value is received from IoT Cloud.
*/
void onEventorChange() {
// Add your code here to act upon Eventor change
}
/*
Since Stirrer is READ_WRITE variable, onStirrerChange() is
executed every time a new value is received from IoT Cloud.
*/
void onStirrerChange() {
if (stirrer == 1) {
stirrer = 1 ;
}
}
/*
Since ScheduleTest is READ_WRITE variable, onScheduleTestChange() is
executed every time a new value is received from IoT Cloud.
*/
void onScheduleTestChange() {
if (schedule_test.isActive()) {
Eventor = 1;
pumpRunning = false;
void KH_Test();
}
}
void KH_Test() {
if ( Eventor == 1 && pumpRunning == false) {
Serial.print("acid voltage:");
Serial.print(EEPROM.readFloat(PHVALUEADDR + sizeof(float)));
Serial.println("neutral voltage:");
Serial.print(EEPROM.readFloat(PHVALUEADDR));
calsample_eeprom = EEPROM.readFloat(PHVALUEADDR + sizeof(float) + sizeof(float));
calreject_eeprom = EEPROM.readFloat(PHVALUEADDR + sizeof(float) + sizeof(float) + sizeof(float));
calreagent_eeprom = EEPROM.readFloat(PHVALUEADDR + sizeof(float) + sizeof(float) + sizeof(float) + sizeof(float));
correction_eeprom = EEPROM.readFloat(PHVALUEADDR + sizeof(float) + sizeof(float) + sizeof(float) + sizeof(float) + sizeof(float));
Sampling_Time = ( 100 / (calsample_eeprom / 10)) * 1000 ;
Reject_Time = ( 130 / (calreject_eeprom / 10)) * 1000 ;
Reagent_volume_per_second = (calreagent_eeprom / 10) ; // reagent volume per second
ArduinoCloud.update();
// start the reject/waste pump to empity the measuring beacker
pumpRunning = true;
previousMillis = millis();
currentMillis = previousMillis;
digitalWrite(pumpPin[2], HIGH);
pumpPinState[2] = 1;
Serial.println("reject pump started");
while (currentMillis - previousMillis < Reject_Time )
{
currentMillis = millis();
Serial.print((currentMillis - previousMillis)/(Reject_Time/45)) ;
Serial.println("ml");
}
digitalWrite(pumpPin[2], LOW);
pumpPinState[2] = 0 ;
Serial.println("reject pump stopped");
r1 = 1 ;
}
ArduinoCloud.update();
if ( Eventor == 1 && pumpPinState[2] == 0 && r1 == 1 )// taking water sample after 3 sec. of reject pump stopped
{
previousMillis = millis();
currentMillis = previousMillis ;
Serial.println("3 Sec. Delay before starting sample pump");
while (currentMillis - previousMillis < 3000 )
{
currentMillis = millis();
Serial.print(((currentMillis - previousMillis)/1000 )) ;
Serial.println("sec");
}
previousMillis = millis();
currentMillis = previousMillis ;
digitalWrite(pumpPin[0], HIGH); // sampling pump started here
Serial.println("Taking Water Sample");
pumpPinState[0] = 1;
while (currentMillis - previousMillis < Sampling_Time )
{
currentMillis = millis();
Serial.print((currentMillis - previousMillis)/(Sampling_Time/30)) ;
Serial.println("ml");
}
digitalWrite(pumpPin[0], LOW); // sampling pump will stop based on the sampling timmer
Serial.println(" Water Sampling finished");
pumpPinState[0] = 0;
r1 = 0 ;
r2 = 1 ;
ArduinoCloud.update();
}
if (pumpPinState[0] == 0 && pumpPinState[2] == 0 && Eventor == 1 && pumpRunning == true && r2 == 1 && r1 == 0 )
{
r2 = 0;
r3 = 1;
if (r3 == 1 && pumpPinState[3] == 0)
{
pumpPinState[3] = 1;
previousMillis = millis();
currentMillis = previousMillis ;
Serial.println("3sec delay");
while (currentMillis - previousMillis < 3000 )
{
currentMillis = millis();
Serial.print(((currentMillis - previousMillis)/1000 )) ;
Serial.println("ml");
}
digitalWrite(pumpPin[3], HIGH); //start the magnetic stirrer
digitalWrite(pumpPin[1], HIGH); // start the titration pump after 3 second of sample finished
unsigned long start_titration_time = millis();
s1 = start_titration_time;
Serial.print ("start_titration_time = ");
Serial.println (start_titration_time);
pumpPinState[1] = 0;
r3 = 0 ;
}
}
ArduinoCloud.update();
if (phValue <= 4.2 && pumpPinState[3] == 1)
{
Serial.println ("titration start and calculation");
digitalWrite(pumpPin[1], LOW);
digitalWrite(pumpPin[3], LOW);
unsigned long Stop_titration_time = millis();
s2 = Stop_titration_time ;
Serial.print ("Stop_titration_time = ");
Serial.println (Stop_titration_time);
unsigned long Titration_time = (s2 - s1) ;
Serial.print ("Titration_time = ");
Serial.println (Titration_time);
Serial.print ("Reagent_volume_per_second = ");
Serial.println (Reagent_volume_per_second);
reagent_volume = (Titration_time / 1000) * Reagent_volume_per_second ; // titration_time in milisec /1000 to covert to sec
Kh = ((reagent_volume / 100) * 70) + correction_eeprom ;
Serial.print ("reagent_volume = ");
Serial.println (reagent_volume);
Serial.print ("KH/perDKH = ");
Serial.println (Kh);
kH = Kh;
pumpPinState[1] = 0;
pumpPinState[3] = 0;
Stop_titration_time = 0;
start_titration_time = 0;
pumpRunning = false;
Eventor = 0 ;
stirrer = 0 ;
r1 = 0 ;
r2 = 0 ;
r3 = 0 ;
}
}
/*
Since CorrectionValue is READ_WRITE variable, onCorrectionValueChange() is
executed every time a new value is received from IoT Cloud.
*/
void onCorrectionValueChange() {
correction_eeprom = EEPROM.readFloat(PHVALUEADDR + sizeof(float) + sizeof(float) + sizeof(float) + sizeof(float) + sizeof(float));
if (correctionValue != correction_eeprom)
{
EEPROM.writeFloat(PHVALUEADDR + sizeof(float) + sizeof(float) + sizeof(float) + sizeof(float) + sizeof(float), correctionValue);
EEPROM.commit();
Serial.println("correctionValue = ");
Serial.println(EEPROM.readFloat(PHVALUEADDR + sizeof(float) + sizeof(float) + sizeof(float) + sizeof(float) + sizeof(float)));
}
}
here is my code we can continue later i may be make some good picture and will try to make video for it Enjoy and happy reefing uhh forget if you new to arduino iot cloud i will give you like to some one will make you expert so make sure you see this video before start coding so you get familiar of it here
also forget to show you my dashboard so you have to make the same dashboard with the widget to get it work Note: stirrer is button to activate manual test