#include #include //include wifi library #include "ThingSpeak.h" //include thingspeak library #include //imports a 4 function sequencer #include //imports a 1 function sequencer #include //brings in common print statements #include //include the EZO I2C library from https://github.com/Atlas-Scientific/Ezo_I2c_lib #include //include arduinos i2c library WiFiClient client; //declare that this device connects to a Wi-Fi network,create a connection to a specified internet IP address //----------------Fill in your Wi-Fi / ThingSpeak Credentials------- const String ssid = "sedeam"; //The name of the Wi-Fi network you are connecting to const String pass = "Sede@m2025"; //Your WiFi network password const long myChannelNumber = 000000; //Your Thingspeak channel number const char * myWriteAPIKey = "xxxxxxxxxx"; //Your ThingSpeak Write API Key //------------------------------------------------------------------ int xyz=100; Ezo_board DO = Ezo_board(97, "DO"); //create a DO circuit object, who's address is 97 and name is "DO" Ezo_board ORP = Ezo_board(98, "ORP"); //create an ORP circuit object who's address is 98 and name is "ORP" Ezo_board RTD = Ezo_board(102, "RTD"); //create an RTD circuit object who's address is 102 and name is "RTD" Ezo_board PMPL = Ezo_board(109, "PMPL"); //create an PMPL circuit object who's address is 109 and name is "PMPL" Ezo_board EC = Ezo_board(100, "EC"); // EC Ezo_board device_list[] = { //an array of boards used for sending commands to all or specific boards DO, ORP, RTD, EC, PMPL }; Ezo_board* default_board = &device_list[0]; //used to store the board were talking to //gets the length of the array automatically so we dont have to change the number every time we add new boards const uint8_t device_list_len = sizeof(device_list) / sizeof(device_list[0]); //enable pins for each circuit const int EN_DO = 12; const int EN_ORP = 27; const int EN_RTD = 15; const int EN_AUX = 33; const unsigned long reading_delay = 1000; //how long we wait to receive a response, in milliseconds const unsigned long thingspeak_delay = 15000; //how long we wait to send values to thingspeak, in milliseconds unsigned int poll_delay = 2000 - reading_delay * 2 - 300; //how long to wait between polls after accounting for the times it takes to send readings //parameters for setting the pump output #define PUMP_BOARD PMPL //the pump that will do the output (if theres more than one) #define PUMP_DOSE 10 //the dose that the pump will dispense in milliliters #define EZO_BOARD DO //the circuit that will be the target of comparison #define IS_GREATER_THAN true //true means the circuit's reading has to be greater than the comparison value, false mean it has to be less than #define COMPARISON_VALUE 10 //the threshold above or below which the pump is activated float k_val = 0; //holds the k value for determining what to print in the help menu bool polling = true; //variable to determine whether or not were polling the circuits bool send_to_thingspeak = true; //variable to determine whether or not were sending data to thingspeak bool wifi_isconnected() { //function to check if wifi is connected return (WiFi.status() == WL_CONNECTED); } void reconnect_wifi() { //function to reconnect wifi if its not connected if (!wifi_isconnected()) { WiFi.begin(ssid.c_str(), pass.c_str()); //Serial.println("connecting to wifi"); xyz=100; } } void thingspeak_send() { if (send_to_thingspeak == true) { //if we're datalogging if (wifi_isconnected()) { int return_code = ThingSpeak.writeFields(myChannelNumber, myWriteAPIKey); if (return_code == 200) { //code for successful transmission //Serial.println("sent to thingspeak"); xyz=100; } else { //Serial.println("couldnt send to thingspeak"); xyz=101; } } } } void step1(); //forward declarations of functions to use them in the sequencer before defining them void step2(); void step3(); void step4(); Sequencer4 Seq(&step1, reading_delay, //calls the steps in sequence with time in between them &step2, 300, &step3, reading_delay, &step4, poll_delay); Sequencer1 Wifi_Seq(&reconnect_wifi, 10000); //calls the wifi reconnect function every 10 seconds Sequencer1 Thingspeak_seq(&thingspeak_send, thingspeak_delay); //sends data to thingspeak with the time determined by thingspeak delay void setup() { pinMode(EN_DO, OUTPUT); //set enable pins as outputs pinMode(EN_ORP, OUTPUT); pinMode(EN_RTD, OUTPUT); pinMode(EN_AUX, OUTPUT); digitalWrite(EN_DO, LOW); //set enable pins to enable the circuits digitalWrite(EN_ORP, LOW); digitalWrite(EN_RTD, HIGH); digitalWrite(EN_AUX, LOW); Wire.begin(); //start the I2C Serial.begin(9600); //start the serial communication to the computer WiFi.mode(WIFI_STA); //set ESP32 mode as a station to be connected to wifi network ThingSpeak.begin(client); //enable ThingSpeak connection Wifi_Seq.reset(); //initialize the sequencers Seq.reset(); Thingspeak_seq.reset(); } void loop() { String cmd; //variable to hold commands we send to the kit Wifi_Seq.run(); //run the sequncer to do the polling if (receive_command(cmd)) { //if we sent the kit a command it gets put into the cmd variable polling = false; //we stop polling send_to_thingspeak = false; //and sending data to thingspeak if (!process_coms(cmd)) { //then we evaluate the cmd for kit specific commands process_command(cmd, device_list, device_list_len, default_board); //then if its not kit specific, pass the cmd to the IOT command processing function } } if (polling == true) { //if polling is turned on, run the sequencer Seq.run(); Thingspeak_seq.run(); } } //function that controls the pumps activation and output void pump_function(Ezo_board &pump, Ezo_board &sensor, float value, float dose, bool greater_than) { if (sensor.get_error() == Ezo_board::SUCCESS) { //make sure we have a valid reading before we make any decisions bool comparison = false; //variable for holding the reuslt of the comparison if (greater_than) { //we do different comparisons depending on what the user wants comparison = (sensor.get_last_received_reading() >= value); //compare the reading of the circuit to the comparison value to determine whether we actiavte the pump } else { comparison = (sensor.get_last_received_reading() <= value); } if (comparison) { //if the result of the comparison means we should activate the pump pump.send_cmd_with_num("d,", dose); //dispense the dose delay(100); //wait a few milliseconds before getting pump results //Serial.print(pump.get_name()); //get pump data to tell the user if the command was received successfully //Serial.print(" "); char response[20]; if (pump.receive_cmd(response, 20) == Ezo_board::SUCCESS) { //Serial.print("pump dispensed "); xyz=100; } else { //Serial.print("pump error "); xyz=101; } //Serial.println(response); } else { pump.send_cmd("x"); //if we're not supposed to dispense, stop the pump } } } void step1() { //send a read command. we use this command instead of RTD.send_cmd("R"); //to let the library know to parse the reading RTD.send_read_cmd(); } void step2() { //receive_and_print_reading(RTD); //get the reading from the RTD circuit RTD.receive_read_cmd(); print_success_or_error(RTD, String(RTD.get_last_received_reading(), 2).c_str()); if ((RTD.get_error() == Ezo_board::SUCCESS) && (RTD.get_last_received_reading() > -1000.0)) { //if the temperature reading has been received and it is valid DO.send_cmd_with_num("T,", RTD.get_last_received_reading()); EC.send_cmd_with_num("T,", RTD.get_last_received_reading()); ThingSpeak.setField(1, String(RTD.get_last_received_reading(), 2)); //assign temperature readings to the first column of thingspeak channel } else { //if the temperature reading is invalid DO.send_cmd_with_num("T,", 20.0); EC.send_cmd_with_num("T,", 25.0); //send default temp = 25 deg C to PH sensor ThingSpeak.setField(1, String(25.0, 2)); //assign temperature readings to the first column of thingspeak channel } Serial.print(","); } void step3() { //send a read command. we use this command instead of PH.send_cmd("R"); //to let the library know to parse the reading DO.send_read_cmd(); ORP.send_read_cmd(); EC.send_read_cmd(); } void step4() { //receive_and_print_reading(DO); //get the reading from the DO circuit DO.receive_read_cmd(); print_success_or_error(DO, String(DO.get_last_received_reading(), 2).c_str()); if (DO.get_error() == Ezo_board::SUCCESS) { //if the PH reading was successful (back in step 1) ThingSpeak.setField(2, String(DO.get_last_received_reading(), 2)); //assign PH readings to the second column of thingspeak channel } Serial.print(","); //receive_and_print_reading(ORP); //get the reading from the ORP circuit ORP.receive_read_cmd(); print_success_or_error(ORP, String(ORP.get_last_received_reading(), 2).c_str()); if (ORP.get_error() == Ezo_board::SUCCESS) { //if the ORP reading was successful (back in step 1) ThingSpeak.setField(3, String(ORP.get_last_received_reading(), 0)); //assign ORP readings to the third column of thingspeak channel } Serial.print(","); //receive_and_print_reading(EC); EC.receive_read_cmd(); print_success_or_error(EC, String(EC.get_last_received_reading(), 2).c_str()); if (EC.get_error() == Ezo_board::SUCCESS){ ThingSpeak.setField(4, String(EC.get_last_received_reading(), 0)); } Serial.println(); pump_function(PUMP_BOARD, EZO_BOARD, COMPARISON_VALUE, PUMP_DOSE, IS_GREATER_THAN); } void start_datalogging() { polling = true; //set poll to true to start the polling loop send_to_thingspeak = true; Thingspeak_seq.reset(); } bool process_coms(const String &string_buffer) { //function to process commands that manipulate global variables and are specifc to certain kits if (string_buffer == "HELP") { //print_help(); return true; } else if (string_buffer.startsWith("DATALOG")) { start_datalogging(); return true; } else if (string_buffer.startsWith("POLL")) { polling = true; Seq.reset(); int16_t index = string_buffer.indexOf(','); //check if were passing a polling delay parameter if (index != -1) { //if there is a polling delay float new_delay = string_buffer.substring(index + 1).toFloat(); //turn it into a float float mintime = reading_delay * 2 + 300; if (new_delay >= (mintime / 1000.0)) { //make sure its greater than our minimum time Seq.set_step4_time((new_delay * 1000.0) - mintime); //convert to milliseconds and remove the reading delay from our wait } else { //Serial.println("delay too short"); //print an error if the polling time isnt valid xyz=100; } } return true; } return false; //return false if the command is not in the list, so we can scan the other list or pass it to the circuit } void print_help() { Serial.println(F("Atlas Scientific I2C pool kit ")); Serial.println(F("Commands: ")); Serial.println(F("datalog Takes readings of all sensors every 15 sec send to thingspeak ")); Serial.println(F(" Entering any commands stops datalog mode. ")); Serial.println(F("poll Takes readings continuously of all sensors ")); Serial.println(F(" ")); Serial.println(F("ph:cal,mid,7 calibrate to pH 7 ")); Serial.println(F("ph:cal,low,4 calibrate to pH 4 ")); Serial.println(F("ph:cal,high,10 calibrate to pH 10 ")); Serial.println(F("ph:cal,clear clear calibration ")); Serial.println(F(" ")); Serial.println(F("orp:cal,225 calibrate orp probe to 225mV ")); Serial.println(F("orp:cal,clear clear calibration ")); Serial.println(F(" ")); Serial.println(F("rtd:cal,t calibrate the temp probe to any temp value ")); Serial.println(F(" t= the temperature you have chosen ")); Serial.println(F("rtd:cal,clear clear calibration ")); }