#include <WiFi.h>
#define NTP_SERVER "pool.ntp.org"
#define UTC_OFFSET 28800
#define UTC_OFFSET_DST 0
#define rchannel 4
uint8_t priority = 1;
String ch_mode[rchannel] = {"sched", "auto", "sched", "auto"};
String ch_auto[rchannel][5] = { //key*,low thold*,high thold*,duration*(d30),pause*(d60)
{"ph", "*", "8", "60", "60"},
{"ph", "*", "8", "60", "60"},
{"ph", "*", "8", "60", "60"},
{"ec", "*", "80", "120", "60"}
};
String ch_schedule[rchannel][7] = { //DD,MM,hh*,mm*(d00),week,duration
{"*", "*", "18", "48", "*", "120"},
{"*", "*", "15", "28", "*", "100"},
{"*", "*", "18", "50", "*", "90"},
{"*", "*", "15", "29", "*", "90"}
} ;
uint8_t ch_status[rchannel] = { 0, 0, 0, 0 }; //0: not run, 1: mark need turn on 2: status on 3: status off and mark pause
unsigned long ch_timer[rchannel] = { 0, 0, 0, 0 }; //relay turn-on time
uint16_t ch_remainduration[rchannel] = { 0, 0, 0, 0 } ; //remain duration for prority stop it
float ph = 0.0;
float ec = 0.0;
String channel = "";
void saveChannelConfig() {
bool debug = false;
channel = "";
if (debug) Serial.println("save configuration to spiffs...");
for (int i = 0; i < rchannel; i++) {
channel += ch_mode[i]; channel += ",";
//Serial.print(ch_mode[i]); Serial.print(",");
for (int j = 0; j < 5; j++) {
channel += ch_auto[i][j]; channel += ",";
//Serial.print(ch_auto[i][j]); Serial.print(",");
}
for (int j = 0; j < 6; j++) {
channel += ch_schedule[i][j]; channel += ",";
//Serial.print(ch_schedule[i][j]); Serial.print(",");
}
channel += "\n";
//Serial.println();
}
if (debug) Serial.println("got channel.cfg : ");
if (debug) Serial.println(channel);
}
void loadChannelConfig() {
bool debug = true;
uint8_t pointer = 0;
uint8_t col = 0;
uint8_t row = 0;
if (debug) Serial.println("got channel.cfg : ");
if (debug) Serial.println(channel);
if (debug) Serial.println("apply configuration to array...");
for (int i = 0; i < channel.length(); i++) {
if (channel.charAt(i) == ',') {
if (row == 0) {
if (debug) Serial.printf("ch_mode[%d] : %s\n", col, channel.substring(pointer, i));
ch_mode[col] = channel.substring(pointer, i);
}
if ((row >= 1) && (row < 6)) {
if (debug) Serial.printf("ch_auto[%d][%d] : %s\n", col, row - 1, channel.substring(pointer, i) );
ch_auto[col][row - 1] = channel.substring(pointer, i);
}
if (row >= 6) {
if (debug) Serial.printf("ch_schedule[%d][%d] : %s\n", col, row - 6, channel.substring(pointer, i) );
ch_schedule[col][row - 6] = channel.substring(pointer, i);
}
pointer = i + 1; row ++;
}
if (channel.charAt(i) == '\n') {
if (debug) Serial.println("");
pointer = i + 1; col ++; row = 0;
}
}
}
void setup() {
Serial.begin(115200);
WiFi.begin("Wokwi-GUEST", "", 6);
while (WiFi.status() != WL_CONNECTED) {
delay(250);
}
saveChannelConfig();
loadChannelConfig();
Serial.println("");
Serial.println("WiFi connected");
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
configTime(UTC_OFFSET, UTC_OFFSET_DST, NTP_SERVER);
Serial.println("----- setup down -----");
}
void loop() {
ph = (float) random(50, 100) / 10;
Serial.print("ph : ");
Serial.println(ph);
ec = (float) random(500, 1000) / 10;
Serial.print("ec : ");
Serial.println(ec);
handleRelayTask();
delay(5000);
}
void handleRelayTask() {
bool debug = false;
Serial.println("----------------------------\n Handle Relay Channel Task\n----------------------------");
verifySchedule() ;
for (int i = 0; i < rchannel; i++) { //Do all channels
if (i == priority) if (debug) Serial.print("[ *"); else if (debug) Serial.print("[ ");
if (debug) Serial.printf("Channel #%02d ] %5s mode ", i, ch_mode[i].c_str() );
if (ch_mode[i] == "auto") { //mode : auto mode
//default
if ( ch_auto[i][3] == "*") ch_auto[i][3] = "30"; //default druration 30s
if ( ch_auto[i][4] == "*") ch_auto[i][4] = "60"; //default pause time 60s
if (debug) Serial.printf("| %s ( %s - %s )\n", ch_auto[i][0], ch_auto[i][1], ch_auto[i][2]);
if (ch_status[i] == 0) { //auto status 0 : normal
if (checkThreshold(i, ch_auto[i][1], ch_auto[i][2])) ch_status[i] = 1;
}
if (ch_status[i] == 1) {
if (ch_status[priority] == 2) { //when active, priority running so do nothing
Serial.printf("\n!! relay %d terminate because first priority are running...\n", i);
ch_status[i] = 0;
} else { //when active, no priority running so go to switch relay on
Serial.printf("need to switch on relay %d duration %s s\n", i, ch_auto[i][3].c_str());
if (i == priority) { //this is priority, try to switch of other relay
Serial.println("!! priority lock & terminate other channel...");
for (int i = 0; i < rchannel; i++) { //search all channel
if (i != priority) { //non priority so check run status
if ((ch_status[i] == 2) || (ch_status[i] == 5)) { //when relay running stop it
if (ch_mode[i] == "sched") {
ch_remainduration[i] = ch_schedule[i][5].toInt() - (millis() - ch_timer[i]) / 1000 ;
Serial.printf("!! relay %d are running... turn it off... remain duration : %d s\n", i, ch_remainduration[i]);
ch_status[i] = 4;
} else {
Serial.printf("!! relay %d are running... turn it off...\n", i);
ch_status[i] = 0;
}
//switch off relay
}
}
}
}
//switch on relay
ch_status[i] = 2;
ch_timer[i] = millis();
}
}
if (ch_status[i] == 2) {
Serial.printf("!! relay %d status : on\n", i);
if ( millis() - ch_timer[i] > ch_auto[i][3].toInt() * 1000 ) { //turn off relay after duration
Serial.printf("!! time's up... switch off relay %d and pause status for %s s\n", i, ch_auto[i][4].c_str());
//switch off relay
ch_status[i] = 3;
ch_timer[i] = millis();
}
}
if (ch_status[i] == 3) {
Serial.printf("!! waiting for relay %d pause status time's up...\n", i);
if ( millis() - ch_timer[i] > ch_auto[i][4].toInt() * 1000 ) { //clear status after duration time
Serial.printf("!! time's up... release relay %d pause status...\n", i);
ch_status[i] = 0;
}
}
}
else if (ch_mode[i] == "sched") { //mode : sched mode
//default
if ( ch_schedule[i][3] == "*") ch_schedule[i][3] = "00"; //default second 00
if ( ch_schedule[i][5] == "*") ch_schedule[i][5] = "30"; //default druration 30s
if (debug) Serial.printf("| %s/%s %s:%s (%s)\n", ch_schedule[i][0].c_str(), ch_schedule[i][1].c_str(), ch_schedule[i][2].c_str(), ch_schedule[i][3].c_str(), ch_schedule[i][4].c_str());
if (ch_status[i] == 1) {
if (ch_status[priority] == 2) { //when active, priority running so do nothing
Serial.printf("\n!! relay %d terminate because first priority are running...\n", i);
ch_status[i] = 0;
} else { //when active, no priority running so go to switch relay on
Serial.printf("!! work according to schedule... switch on relay %d duration : %s s\n", i, ch_schedule[i][5].c_str());
if (i == priority) { //this is priority, try to switch of other relay
Serial.println("!! priority lock & terminate other channel...");
for (int i = 0; i < rchannel; i++) { //search all channel
if (i != priority) { //non priority so check run status
if ((ch_status[i] == 2) || (ch_status[i] == 5)) { //when relay running stop it
if (ch_mode[i] == "sched") {
ch_remainduration[i] = ch_schedule[i][5].toInt() - (millis() - ch_timer[i]) / 1000 ;
Serial.printf("!! relay %d are running... turn it off... remain duration : %d s\n", i, ch_remainduration[i]);
ch_status[i] = 4;
} else {
Serial.printf("!! relay %d are running... turn it off...\n", i);
ch_status[i] = 0;
}
//switch off relay
}
}
}
}
//switch on relay
ch_status[i] = 2;
ch_timer[i] = millis();
}
}
if (ch_status[i] == 2) {
Serial.printf("!! relay %d status : on\n", i);
if ( millis() - ch_timer[i] > ch_schedule[i][5].toInt() * 1000 ) { //turn off relay after duration
Serial.printf("!! time's up... switch off relay %d pause status for a while\n", i);
//switch off relay
ch_status[i] = 3;
}
}
if (ch_status[i] == 3) {
Serial.printf("!! waiting for relay %d pause status time's up...\n", i);
if ( millis() - ch_timer[i] > 60000 ) { //clear status after least 1 minutes
Serial.printf("!! time's up... release relay %d pause status...\n", i);
ch_status[i] = 0;
}
}
if (ch_status[i] == 4) {
if (ch_status[priority] != 2) { //when active, priority running so do nothing
Serial.printf("!! turn on relay %d for remain duration : %d s\n", i, ch_remainduration[i]);
//turn on relay
ch_status[i] = 5;
ch_timer[i] = millis();
}
}
if (ch_status[i] == 5) {
Serial.printf("!! relay %d status : on\n", i);
if ( millis() - ch_timer[i] > ch_remainduration[i] * 1000 ) { //turn off relay after duration
Serial.printf("!! time's up... switch off relay %d pause status for a while\n", i);
//switch off relay
ch_status[i] = 3;
}
}
}
else if (ch_mode[i] == "*") {
if (debug) Serial.println("| free slot");
}
}
Serial.println("");
}
bool checkThreshold(int idx, String minThold, String maxThold ) {
bool succ = false;
if (ch_auto[idx][0] == "ph") {
if ((minThold != "*") && (ph <= minThold.toInt())) {
Serial.printf("!! ph : %.2f <= %s | min threshold trigger ", ph, minThold.c_str()); succ = true;
}
if ((maxThold != "*") && (ph >= maxThold.toInt())) {
Serial.printf("!! ph : %.2f >= %s | max threshold trigger ", ph, maxThold.c_str()); succ = true;
}
} else if (ch_auto[idx][0] == "ec") {
if ((minThold != "*") && (ec <= minThold.toInt())) {
Serial.printf("!! ec : %.2f <= %s | min threshold trigger ", ec, minThold.c_str()); succ = true;
}
if ((maxThold != "*") && (ec >= maxThold.toInt())) {
Serial.printf("!! ec : %.2f >= %s | max threshold trigger ", ec, maxThold.c_str()); succ = true;
}
}
return succ;
}
void verifySchedule() {
struct tm timeinfo;
if (!getLocalTime(&timeinfo)) {
Serial.println("Failed to obtain time"); return;
}
char timeVar[16];
strftime( timeVar, sizeof(timeVar), "%d,%m,%H,%M,%a", &timeinfo ); //eg: 02,23,10,45,Fri
String timeVal[5];
char* token = strtok(timeVar, ",");
uint8_t i = 0;
while (token != NULL) {
timeVal[i] = String(token); token = strtok(NULL, ","); i++;
}
for (int i = 0; i < rchannel; i++) {
if (ch_mode[i] != "auto") {
bool succ = true;
for (int j = 0; j < 5; j++) {
//Serial.print(timeVal[j]); Serial.print(" - "); Serial.println(ch_schedule[i][j]);
if ((ch_schedule[i][j] != "*") && (ch_schedule[i][j] != timeVal[j])) succ = false;
//Serial.print("Result : "); Serial.println(succ);
}
//Serial.print("Final Result : "); Serial.println(succ);
if ( ch_status[i] == 0 ) ch_status[i] = succ;
}
}
}