// Edit from https://randomnerdtutorials.com/esp32-plot-readings-charts-multiple/
// https://chatgpt.com/share/69af1926-88f8-8006-bf28-2463ff38d7a2
/* ESP32 HTTP IoT Server Example for Wokwi.com
https://wokwi.com/projects/320964045035274834
To test, you need the Wokwi IoT Gateway, as explained here:
https://docs.wokwi.com/guides/esp32-wifi#the-private-gateway
Then start the simulation, and open http://localhost:9080
in another browser tab.
Note that the IoT Gateway requires a Wokwi Club subscription.
To purchase a Wokwi Club subscription, go to https://wokwi.com/club
*/
#define ARDUINO_ARCH_ESP32
//------------------------------------------------------------------------------
// Define Options
//------------------------------------------------------------------------------
//#define demo // random numbers for temperature and flow
//#define activeLow // inputs
//#define debug // enable serial
#define incNextion // enable Nextion Serial data
#define autoFaultReset true // reset fault automaticlly (else use RESET button)
const long interval = 250; // 500ms = 0.5 second
#define slowInterval 15000 // 15000ms = 15 second
unsigned int logInterval = 10; // seconds
unsigned int sleepInterval = 60; //delay in seconds before sleep
#define f_temperature
#define FS300A_PULSE 508 // Number of pulses per liter
#define FS300A_FLOW_RATE 60 // Maximum flow rate in liters per minute
const float factor = 60.0F / 508.0F; // Conversion factor from pulses to flow rate (FS300A_FLOW_RATE / FS300A_PULSE)
#define serialBaud 115200 // Baud rate for serial monitor
#define serialDebug Serial // define the serialport for debuggin
#define nexSerial Serial
//----------------------------------------------------------------------
// Libraries
//----------------------------------------------------------------------
//#include "nextion.h"
#include <Wire.h>
#include <WiFi.h>
#include <WiFiClient.h>
#include <WebServer.h> // https://wokwi.com/projects/320964045035274834
#include <uri/UriBraces.h>
#include <Adafruit_MCP23X17.h>
#include <Adafruit_ADS1X15.h>
#include <PulseFlowMeter.h> // https://github.com/lysek01/PulseFlowMeter
#include <thermistor.h> // https://github.com/miguel5612/ThermistorLibrary
#include <RTClib.h> // https://github.com/adafruit/RTClib
#include "Clock.h" // https://wokwi.com/projects/297787059514376717
//#include <EEPROM.h>
#include <TimeLib.h> // https://github.com/PaulStoffregen/Time
//#include <DS1307RTC.h> // https://github.com/PaulStoffregen/DS1307RTC
// https://github.com/MileBuurmeijer/DS1307newAlarms
#include <Preferences.h>
#include "FS.h"
#include "SD.h"
#include "SPI.h"
#include <time.h> // https://github.com/PaulStoffregen/Time
#include <EasyNextionLibrary.h> // https://github.com/Seithan/EasyNextionLibrary
//----------------------------------------------------------------------
// Pins
//----------------------------------------------------------------------
#define nextion_serial 2 // Serial2 on pins GPIO 16 (RX) and GPIO 17 (TX):
#define mcp_chiller_running 0
#define mcp_water_low 1
#define mcp_water_med 2
#define mcp_water_high 3
#define mcp_water_leak 4
#define mcp_stack_light_siren 8
#define mcp_stack_light_red 9
#define mcp_stack_light_orange 10
#define mcp_stack_light_green 11
#define mcp_spare_relay_12 12
#define mcp_spare_relay_13 13
#define mcp_fill_valve 14
#define mcp_drain_valve 15
#define water_flow_pin 34 // Input pin
#define water_fill_pin 35 // Input pin
#define LED1 26
#define LED2 27
#define sd_cs 5
#define sd_di 23 //mosi
#define sd_sck 18
#define sd_do 19 //miso
#define sd_cd 32
//----------------------------------------------------------------------
// Nextion Display
//----------------------------------------------------------------------
EasyNex myNex(Serial);
#define NextionBaud 115200
const int REFRESH_TIME = 100; // time to refresh the Nextion page every 100 ms
unsigned long refresh_timer = millis(); // timer for refreshing Nextion's page
byte currentPageId = 99;
byte lastCurrentPageId = 99;
bool newPageLoaded = false;
bool resetSleepTimer = false;
unsigned long lastSleep = 0;
bool sleeping = false;
byte progressBar = 25; // 20= empty, 85=full
byte prev_progressBar = 0;
bool logNOW = false;
bool manual_fill_button = false;
bool manual_drain_button = false;
bool reset_button = false;
bool filling_light = false;
bool draining_light = false;
bool spare_light_1 = false;
bool spare_light_2 = false;
bool fault_light = false;
bool settings_light = false;
bool settings_button = false;
bool chiller_light = false;
bool low_light = false;
bool leak_light = false;
bool level_indicator = false;
bool red_gauge = false;
bool prev_red_gauge = false;
bool prev_reset_button = false;
bool prev_filling_light = false;
bool prev_draining_light = false;
bool prev_spare_light_1 = false;
bool prev_spare_light_2 = false;
bool prev_fault_light = false;
bool prev_settings_light = false;
bool prev_settings_button = false;
bool prev_chiller_light = false;
bool prev_low_light = false;
bool prev_leak_light = false;
String V_scrolling_message;
String TV_text_0;
String TV_text_1;
String TV_text_2;
String TV_text_4;
bool msgSent = false;
unsigned int return_temperature = 0;
unsigned int return_temperature_DELTA = 0;
unsigned int supply_temperature = 0;
unsigned int supply_flow = 0;
const int numFlowReadings = 10;
int flowLMreadings[numFlowReadings]; // the flowLMreadings from the analog input
int flowReadIndex = 0; // the index of the current reading
float flowTotal = 0; // the running flowTotal
float flowAverage = 0; // the flowAverage
unsigned long currentMillis = 0;
unsigned long currentSec = 0;
//=============================================================================
//
//-----------------------------------------------------------------------------
#ifdef debug
#warning "Debug-printing is active"
#define PRINTdebug(s) { serialDebug.print(s); }
#define PRINTdebugln(s) { serialDebug.println(s); }
#else
#warning "Debug-printing is disabled"
#define PRINTdebug(s)
#define PRINTdebugln(s)
#endif
//----------------------------------------------------------------------
// Saved Data
//----------------------------------------------------------------------
Preferences preferences;
typedef struct {
uint32_t variable1;
float variable2;
char variable3[20];
} DeviceSettings;
DeviceSettings settings;
//=============================================================================
//
//-----------------------------------------------------------------------------
void saveSettings() {
// Open Preferences with a namespace "config" in read/write mode (false)
preferences.begin("config", false);
// Save each member individually
preferences.putUInt("var1", settings.variable1);
preferences.putFloat("var2", settings.variable2);
preferences.putString("var3", settings.variable3); // Use putString for strings/char arrays
// Close the preferences
preferences.end();
}
//=============================================================================
//
//-----------------------------------------------------------------------------
void loadSettings() {
// Open Preferences with a namespace "config" in read-only mode (true)
// If the namespace doesn't exist or is invalid, use default values
preferences.begin("config", true);
// Load each member individually, providing a default value if the key is not found
settings.variable1 = preferences.getUInt("var1", 0); // Default to 0
settings.variable2 = preferences.getFloat("var2", 0.0); // Default to 0.0
// Load string into char array buffer
String tempString = preferences.getString("var3", "default_value");
strncpy(settings.variable3, tempString.c_str(), sizeof(settings.variable3));
// Close the preferences
preferences.end();
}
//=============================================================================
// NETWORK
//-----------------------------------------------------------------------------
#define WIFI_SSID "Wokwi-GUEST"
#define WIFI_PASSWORD ""
// Defining the WiFi channel speeds up the connection:
#define WIFI_CHANNEL 6
WebServer server(80);
bool html_fill_control = false;
bool html_drain_control = false;
//=============================================================================
// HTML Variables
//-----------------------------------------------------------------------------
bool led1State = false;
bool led2State = false;
bool ledState = false;
unsigned long previousMillis = 0;
unsigned long previousMilli = 0;
unsigned long logSec = 0;
unsigned long slowMilli = 0;
//=============================================================================
// RTC and NTP
//-----------------------------------------------------------------------------
// https://wokwi.com/projects/321525495180034642
// NTP server details
const char* ntpServer = "pool.ntp.org";
const long gmtOffset_sec = -21600; // 60sec * 60min * 6hr = 21600; // Offset for GMT in seconds
const int daylightOffset_sec = 3600; // Daylight savings time in seconds
//-----------------------------------------------------------------------------
void NTPsetup() {
configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
}
//-----------------------------------------------------------------------------
RTC_DS1307 rtc;
// https://randomnerdtutorials.com/esp32-ds1307-real-time-clock-rtc-arduino/
char daysOfTheWeek[7][12] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};
char monthOfTheYear[12][12] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
// Global timeinfo struct and last sync timestamp
struct tm timeinfo;
unsigned long lastSyncMillis = 0; // Last sync time in milliseconds
//=============================================================================
// ADS1115
//-----------------------------------------------------------------------------
#define ads_address 0x48 // 72
Adafruit_ADS1115 ads; /* Use ADS1115 for the 16-bit version, -32,768 to +32,767*/
int16_t adc0, adc1, adc2, adc3;
float volts0, volts1, volts2, volts3;
//=============================================================================
// Thermistor
//-----------------------------------------------------------------------------
//thermistor ambient_temperature(A0, 10000, 3950); // Pin, Nominal Resistance, Beta
//thermistor supply_temperature(A1, 10000, 3950); // Pin, Nominal Resistance, Beta
//thermistor return_temperature(A2, 10000, 3950); // Pin, Nominal Resistance, Beta
const float BETA = 3950; // should match the Beta Coefficient of the thermistor
//=============================================================================
// MCP23017
//------------------------------------------------------------------------------
#define mcp_address 0x20 // 32
Adafruit_MCP23X17 mcp;
unsigned int mcp_test_pin = 8;
//=============================================================================
// SD Card
//------------------------------------------------------------------------------
bool sd_mounted = false;
String logfile = "/data.csv";
unsigned long lastLog = 0;
#define numSDvariables 10 //
int SD_data[10] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
//=============================================================================
// DATA
//------------------------------------------------------------------------------
struct MCPinputs {
bool low = false;
bool good = false;
bool high = false;
bool chiller = false;
bool leak = false;
bool prevlow = false;
bool prevgood = false;
bool prevhigh = false;
bool prevchiller = false;
bool prevleak = false;
};
MCPinputs water;
//=============================================================================
//
//------------------------------------------------------------------------------
struct TEMPERATURE {
float supply = 0.0;
float returned = 0.0;
float ambient = 0.0;
float delta = 0.0;
float prevsupply = 0.0;
float prevreturned = 0.0;
float prevambient = 0.0;
float prevdelta = 0.0;
};
TEMPERATURE temperature;
//=============================================================================
//
//------------------------------------------------------------------------------
enum TANKlevel {
empty, // assigned 0 by default
low, // assigned 1 by default
good, // assigned 2 by default
high, // assigned 3 by default
leaking, // assigned 4 by default
error // assigned 5 by default
};
TANKlevel level = empty; // Initialize the state variable
byte prevLevel = 9;
byte lastLevel = 0;
//=============================================================================
//
//------------------------------------------------------------------------------
struct FLOW {
float lm = 0.00;
float gpm = 0.00;
float gph = 0.00;
float m3h = 0.00;
float total = 0.00;
float prevgph = 0.00;
float prevgpm = 0.00;
};
FLOW flow;
FLOW fill;
//=============================================================================
// Pulse Flow
//------------------------------------------------------------------------------
PulseFlowMeter cycle_meter;
PulseFlowMeter fill_meter;
#define water_flow_K 25.613 // Pulses per liter parameter of flowmeter
#define water_fill_K 25.613 // Pulses per liter parameter of flowmeter
#define MIN_FLOW 0.1 // Minimal flowrate treshold
#define MAX_FLOW 50 // Maximal flowrate treshold
#define MIN_FILL 0.1 // Minimal flowrate treshold
#define MAX_FILL 50 // Maximal flowrate treshold
//=============================================================================
// Global variables
//-----------------------------------------------------------------------------
bool setup_complete = false;
bool wifiConnected = false;
bool prevDrainState = false;
bool prevRefillState = false;
bool chiller_running = false;
bool water_low = false;
bool water_med = false;
bool water_high = false;
bool water_leak = false;
bool draining = false;
bool filling = false;
bool manual_mode = false;
bool manual_drain = false;
bool faulted = false;
unsigned int fault_num = 0;
unsigned int prev_fault_num = 0;
String fault_code;
unsigned int resetTimer = 10000; // when errored, the time after good state for autoreset
//██████████████████████████████████████████████████████████████████████████████
void getTxt(String TextComponent) {
String _Textcomp = TextComponent;
nexSerial.print("get ");
nexSerial.print(_Textcomp); // The String of a component you want to read on Nextion
nexSerial.print("\xFF\xFF\xFF");
}
//=============================================================================
// verify and send nextion text
//------------------------------------------------------------------------------
void setMessage(String msg, byte line) {
String msgLine = "t" + String(line) + ".txt";
//String FromNextion = myNex.readStr(msgLine);
//Serial.print("FromNextion= "); Serial.println(FromNextion);
//if (FromNextion != msg) {
myNex.writeStr(msgLine, msg);
// Serial.print("write= "); Serial.println(msg);
/*
nexSerial.print(F("t"));
nexSerial.print(line);
nexSerial.print(F(".txt=\""));
nexSerial.print(msg);
nexSerial.print(F("\""));
nexSerial.print(F("\xFF\xFF\xFF"));
*/
// }
}
//=============================================================================
// Set Scrolling Text
//------------------------------------------------------------------------------
void setMsgHeader(String msg) {
//String FromNextion = myNex.readStr("g0.txt");
//if (FromNextion != msg) {
nexSerial.print(F("g0.txt=\""));
nexSerial.print(msg);
nexSerial.print(F("\""));
nexSerial.print(F("\xFF\xFF\xFF"));
//}
}
//------------------------------------------------------------------------------
// Faults
//------------------------------------------------------------------------------
void faultCode() {
if (prev_fault_num != fault_num) {
if (fault_num > 0) {
setMessage("Faulted", 1);
if (fault_num == 1) {
setMessage("Sensor state", 2);
setMessage("not possible", 3);
} else if (fault_num == 2) {
setMessage("Leak", 2);
setMessage("Detected", 3);
}
} else {
setMessage("Running", 1);
setMessage("", 2);
setMessage("", 3);
}
prev_fault_num = fault_num;
}
}
//=============================================================================
// RESET all
//-----------------------------------------------------------------------------
void resetAll() {
faulted = false;
fault_num = 0;
myNex.writeStr("t0.val", ""); delay(50);
myNex.writeNum("t1.val", 0); delay(50);
myNex.writeNum("t2.val", 0); delay(50);
myNex.writeNum("t3.val", 0); delay(50);
myNex.writeNum("j0.val", 0); delay(50);
myNex.writeNum("b3.picc", 0); delay(50);
myNex.writeNum("b4.picc", 0); delay(50);
myNex.writeNum("b5.picc", 0); delay(50);
myNex.writeNum("b6.picc", 0); delay(50);
myNex.writeNum("b9.picc", 0); delay(50);
myNex.writeNum("b10.picc", 0); delay(50);
myNex.writeNum("b11.picc", 0); delay(50);
/*
setMessage("", 0);
setMessage("", 1);
setMessage("", 2);
setMessage("", 3);
setMessage("", 4);
*/
temperature.prevreturned = 0;
temperature.prevdelta = 0;
temperature.prevsupply = 0;
flow.prevgpm = 0;
prev_progressBar = 0;
prev_filling_light = 0;
prev_draining_light = 0;
prev_spare_light_1 = 0;
prev_spare_light_2 = 0;
prev_settings_light = 0;
water.prevchiller = 0;
water.prevlow = 0;
water.prevleak = 0;
String nxtTxt = myNex.readStr("g0.txt");
if (nxtTxt != "MUUS Chiller Controller") {
setMsgHeader("MUUS Chiller Controller");
}
}
//------------------------------------------------------------------------------
// Temperature
//------------------------------------------------------------------------------
// Analog Pin which is connected to the 3950 temperature sensor, and 0 represents TEMP_SENSOR_0
//thermistor supplyTherm(temperature_supply_pin, 0);
// Analog Pin which is connected to the 3950 temperature sensor, and 0 represents TEMP_SENSOR_0
//thermistor returnTherm(temperature_return_pin, 0);
int supply_temp = 0;
int prev_supply_temp = 0;
int return_temp = 0;
int prev_return_temp = 0;
bool temp_update = false;
int delta_temp = 0;
long tempUpdateTime = 0;
#define tempTime 5000 // how often in mS to update the temperature
// Pulse input
uint16_t pulse; // Variable to store pulse count
uint16_t count; // Variable to store pulse count for calculation
float frequency; // Variable to store frequency
float flowRate; // Variable to store flow rate
bool busy; // Flag to indicate interrupt handling status
String ipString;
//----------------------------------------------------------------------
// HTML
//----------------------------------------------------------------------
//----------------------------------------------------------------------
// Get Timestamp
//----------------------------------------------------------------------
String getTimestamp() {
struct tm timeinfo;
if (!getLocalTime(&timeinfo))
return "0000-00-00 00:00:00";
char buffer[25];
strftime(buffer, 25, "%Y-%m-%d %H:%M:%S", &timeinfo);
return String(buffer);
}
//----------------------------------------------------------------------
// Log Data
//----------------------------------------------------------------------
void logData() {
#ifdef demo
SD_data[0] = random(70, 75);
SD_data[1] = random(50, 60);
SD_data[2] = random(70, 90);
SD_data[3] = random(5, 10);
SD_data[4] = random(0, 8);
SD_data[5] = random(0, 8);
SD_data[6] = random(0, 100);
SD_data[7] = random(0, 100);
SD_data[8] = random(0, 100);
SD_data[9] = random(0, 100);
#else
SD_data[0] = temperature.ambient;
SD_data[1] = temperature.supply;
SD_data[2] = temperature.returned;
SD_data[3] = flow.gph;
SD_data[4] = fill.gph;
SD_data[5] = level;
SD_data[6] = flow.total;
SD_data[7] = fill.total;
SD_data[8] = 0;
SD_data[9] = 0;
#endif
File file = SD.open(logfile, FILE_APPEND);
if (!file) return;
String line = getTimestamp();
for (int i = 0; i < numSDvariables; i++) {
int value = SD_data[i];
line += ",";
line += String(value);
}
PRINTdebug("SD Write: "); PRINTdebugln(line);
file.println(line);
file.close();
}
//----------------------------------------------------------------------
// Time Stamp Range
//----------------------------------------------------------------------
bool timestampInRange(String ts, String start, String end) {
if (start == "" || end == "") return true;
if (ts >= start && ts <= end) return true;
return false;
}
//----------------------------------------------------------------------
// Read Filtered CSV
//----------------------------------------------------------------------
String readFilteredCSV(String start, String end) {
File file = SD.open(logfile);
if (!file) return "";
String json = "{";
//json += "\"time\":[],\"SD_ambient_temperature\":[],\"SD_supply_temperature\":[],\"SD_return_temperature\":[],\"SD_flow\":[],\"SD_fill\":[]",\"SD_level\":[]";
json += "\"time\":[],\"SD_ambient_temperature\":[],\"SD_supply_temperature\":[],\"SD_return_temperature\":[],\"SD_flow\":[],\"SD_fill\":[],\"SD_level\":[]";
json += "}";
//,\"SD_flow_total\":[],\"String SD_fill_total\":[],\"String SD_spare_1\":[],\"String SD_spare_2\":[]
String out = "";
String timeArray = "";
String SD_ambient_temperature = "";
String SD_supply_temperature = "";
String SD_return_temperature = "";
String SD_flow = "";
String SD_fill = "";
String SD_level = "";
while (file.available()) {
String line = file.readStringUntil('\n');
if (line.length() < 10) continue;
int idx1 = line.indexOf(',');
String ts = line.substring(0, idx1);
if (!timestampInRange(ts, start, end)) continue;
int values[numSDvariables];
int last = idx1 + 1;
for (int i = 0; i < numSDvariables; i++) {
int next = line.indexOf(',', last);
if (next == -1) next = line.length();
values[i] = line.substring(last, next).toInt();
last = next + 1;
}
timeArray += "\"" + ts + "\",";
SD_ambient_temperature += String(values[0]) + ",";
SD_supply_temperature += String(values[1]) + ",";
SD_return_temperature += String(values[2]) + ",";
SD_flow += String(values[3]) + ",";
SD_fill += String(values[4]) + ",";
SD_level += String(values[5]) + ",";
}
file.close();
if (timeArray.endsWith(",")) timeArray.remove(timeArray.length() - 1);
if (SD_ambient_temperature.endsWith(",")) SD_ambient_temperature.remove(SD_ambient_temperature.length() - 1);
if (SD_supply_temperature.endsWith(",")) SD_supply_temperature.remove(SD_supply_temperature.length() - 1);
if (SD_return_temperature.endsWith(",")) SD_return_temperature.remove(SD_return_temperature.length() - 1);
if (SD_flow.endsWith(",")) SD_flow.remove(SD_flow.length() - 1);
if (SD_fill.endsWith(",")) SD_fill.remove(SD_fill.length() - 1);
if (SD_level.endsWith(",")) SD_level.remove(SD_level.length() - 1);
out = "{";
out += "\"time\":[" + timeArray + "],";
out += "\"SD_ambient_temperature\":[" + SD_ambient_temperature + "],";
out += "\"SD_supply_temperature\":[" + SD_supply_temperature + "],";
out += "\"SD_return_temperature\":[" + SD_return_temperature + "],";
out += "\"SD_flow\":[" + SD_flow + "],";
out += "\"SD_fill\":[" + SD_fill + "],";
out += "\"SD_level\":[" + SD_level + "]";
out += "}";
return out;
}
//----------------------------------------------------------------------
// Handle Data
//----------------------------------------------------------------------
void handleData() {
String start = server.arg("start");
String end = server.arg("end");
start.replace("T", " ");
end.replace("T", " ");
String json = readFilteredCSV(start, end);
server.send(200, "application/json", json);
}
//=============================================================================
//=============================================================================
// Functions
//=============================================================================
//=============================================================================
//=============================================================================
// Check WIFI Status
//-----------------------------------------------------------------------------
void checkWifi() {
if (WiFi.status() == WL_CONNECTED) {
wifiConnected = true;
ipString = WiFi.localIP().toString();
//PRINTdebug(" IP address: ");
//PRINTdebugln(ipString);
#ifdef incNextion
setMessage("IP: " + ipString, 4);
#endif
} else {
wifiConnected = false;
#ifdef incNextion
setMessage("WiFi not connected", 4);
#endif
}
}
//=============================================================================
// NTP
//-----------------------------------------------------------------------------
void getNTP() {
struct tm timeinfo;
if (!getLocalTime(&timeinfo)) {
PRINTdebugln("NTP time failed");
return;
} else {
//PRINTdebug("NTP:\t");
//PRINTdebug(&timeinfo, "%H:%M:%S");
//PRINTdebug("\t");
//PRINTdebugln(&timeinfo, "%d/%m/%Y");// %Z");
}
#ifdef incNextion
//setMessage(String(tm), 3);
#endif
}
//=============================================================================
// RTC
//-----------------------------------------------------------------------------
void getRTC() {
DateTime now = rtc.now();
PRINTdebug("RTC:\t");
PRINTdebug(daysOfTheWeek[now.dayOfTheWeek()]);
PRINTdebug(" ");
PRINTdebug(monthOfTheYear[now.month()]);
PRINTdebug(" ");
PRINTdebug(now.day());
PRINTdebug(" ");
PRINTdebug(now.year());
PRINTdebug(", ");
PRINTdebug(now.hour());
PRINTdebug(':');
if (now.minute() < 10)
PRINTdebug("0");
PRINTdebug(now.minute());
PRINTdebug(':');
if (now.second() < 10)
PRINTdebug("0");
PRINTdebug(now.second());
PRINTdebugln();
}
//=============================================================================
// Set RTC from PC time
//-----------------------------------------------------------------------------
void setRTCpc() {
// When time needs to be set on a new device, or after a power loss, the
// following line sets the RTC to the date & time this sketch was compiled
rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
}
//=============================================================================
// Manually set RTC
//-----------------------------------------------------------------------------
void setRTC(unsigned int YYYY, byte MM, byte DD, byte hh, byte mm, byte ss) {
// This line sets the RTC with an explicit date & time, for example to set
// January 21, 2014 at 3am you would call:
//rtc.adjust(DateTime(2014, 1, 21, 3, 0, 0));
rtc.adjust(DateTime(YYYY, MM, DD, hh, mm, ss));
}
//=============================================================================
// FLOW
//-----------------------------------------------------------------------------
void get_flow() {
flowTotal = flowTotal - flowLMreadings[flowReadIndex];
flowLMreadings[flowReadIndex] = cycle_meter.getFlowRateLiterMinute();
flowTotal = flowTotal + flowLMreadings[flowReadIndex];
flowReadIndex = flowReadIndex + 1;
if (flowReadIndex >= numFlowReadings) {
flowReadIndex = 0;
}
flowAverage = flowTotal / numFlowReadings;
flow.lm = flowAverage;
//flow.lm = cycle_meter.getFlowRateLiterMinute();
flow.gpm = cycle_meter.getFlowRateLiterMinute() / 3.785;
flow.gph = cycle_meter.getFlowRateLiterMinute() / 3.785 * 60;
flow.m3h = cycle_meter.getFlowRateCubicHour();
flow.total = cycle_meter.getTotalVolume();
fill.lm = fill_meter.getFlowRateLiterMinute();
fill.gpm = fill_meter.getFlowRateLiterMinute() / 3.785;
fill.gph = fill_meter.getFlowRateLiterMinute() / 3.785 * 60;
fill.m3h = fill_meter.getFlowRateCubicHour();
fill.total = fill_meter.getTotalVolume();
}
//=============================================================================
// Read ADS1115
//-----------------------------------------------------------------------------
void read_analog() {
adc0 = ads.readADC_SingleEnded(0);
adc1 = ads.readADC_SingleEnded(1);
adc2 = ads.readADC_SingleEnded(2);
adc3 = ads.readADC_SingleEnded(3);
volts0 = ads.computeVolts(adc0);
volts1 = ads.computeVolts(adc1);
volts2 = ads.computeVolts(adc2);
volts3 = ads.computeVolts(adc3);
}
//=============================================================================
// Convert ADS to Thermistor
//-----------------------------------------------------------------------------
void read_thermistor() {
temperature.supply = 1 / (log(1 / (26667. / adc1 - 1)) / BETA + 1.0 / 298.15) - 273.15;
temperature.returned = 1 / (log(1 / (26667. / adc2 - 1)) / BETA + 1.0 / 298.15) - 273.15;
temperature.ambient = 1 / (log(1 / (26667. / adc0 - 1)) / BETA + 1.0 / 298.15) - 273.15;
temperature.delta = temperature.returned - temperature.supply;
}
//=============================================================================
// Read MCP digital inputs
//-----------------------------------------------------------------------------
void read_mcp_inputs() {
water.low = mcp.digitalRead(mcp_water_low);
water.good = mcp.digitalRead(mcp_water_med);
water.high = mcp.digitalRead(mcp_water_high);
water.leak = mcp.digitalRead(mcp_water_leak);
water.chiller = mcp.digitalRead(mcp_chiller_running);
PRINTdebug("water.low:"); PRINTdebug(water.low);
PRINTdebug("\t"); PRINTdebug("water.good:"); PRINTdebug(water.good);
PRINTdebug("\t"); PRINTdebug("water.high:"); PRINTdebug(water.high);
PRINTdebug("\t"); PRINTdebug("water.leak :"); PRINTdebug(water.leak );
PRINTdebug("\t"); PRINTdebug("water.chiller:"); PRINTdebug(water.chiller);
PRINTdebugln("");
}
//=============================================================================
// Tank Drain
//-----------------------------------------------------------------------------
void drain(bool state) {
bool changed;
if (prevDrainState != state) {
changed = true;
prevDrainState = state;
} else {
changed = false;
}
if (state) {
#ifdef activeLow
mcp.digitalWrite(mcp_drain_valve, LOW);
#else
mcp.digitalWrite(mcp_drain_valve, HIGH);
#endif
draining = true;
draining_light = true;
//if (changed)
//PRINTdebugln("draining");
}
if (!state) {
#ifdef activeLow
mcp.digitalWrite(mcp_drain_valve, HIGH);
#else
mcp.digitalWrite(mcp_drain_valve, LOW);
#endif
draining = false;
draining_light = false;
//if (changed)
//PRINTdebugln("done draining");
delay(50);
}
}
//=============================================================================
// tank refill
//-----------------------------------------------------------------------------
void refill(bool state) {
bool changed;
if (prevRefillState != state) {
changed = true;
prevRefillState = state;
} else {
changed = false;
}
if (state) {
#ifdef activeLow
mcp.digitalWrite(mcp_fill_valve, LOW);
#else
mcp.digitalWrite(mcp_fill_valve, HIGH);
#endif
//if (changed)
//PRINTdebugln("refilling");
filling = true;
filling_light = true;
}
if (!state) {
#ifdef activeLow
mcp.digitalWrite(mcp_fill_valve, HIGH);
#else
mcp.digitalWrite(mcp_fill_valve, LOW);
#endif
//if (changed)
//PRINTdebugln("done refilling");
filling = false;
filling_light = false;
}
}
//=============================================================================
// get tank level
//-----------------------------------------------------------------------------
void runTankLeveler() {
if (water.chiller) {
if (prevLevel != level) {
if (level == empty) {
refill(true);
progressBar = 25;
PRINTdebugln("water level empty");
}
if (level == low) {
drain(false);
progressBar = 35;
PRINTdebugln("water level low");
if (autoFaultReset) {
faulted = false;
fault_num = 0;
}
}
if (level == good) {
refill(false);
progressBar = 55;
PRINTdebugln("water level good");
if (autoFaultReset) {
faulted = false;
fault_num = 0;
}
}
if (level == high) {
drain(true);
refill(false);
progressBar = 75;
PRINTdebugln("water too high");
if (autoFaultReset) {
faulted = false;
fault_num = 0;
}
}
if (level == leaking) {
drain(false);
refill(false);
PRINTdebugln("Leak Detected");
faulted = true;
fault_num = 2;
myNex.writeNum("j0.ppic", 7);
red_gauge = true;
} else if (level == error) {
drain(false);
refill(false);
progressBar = 85;
PRINTdebugln("Sensor Error");
faulted = true;
fault_num = 1;
myNex.writeNum("j0.ppic", 7);
red_gauge = true;
} else {
myNex.writeNum("j0.ppic", 4);
red_gauge = false;
}
prevLevel = level;
}
} else {
PRINTdebugln("Chiller Off");
if (!manual_mode) {
drain(false);
refill(false);
}
}
}
//=============================================================================
// check Tank IO sensor validity
//-----------------------------------------------------------------------------
void checkTankLevel() {
if (water.low && water.good && water.high) { // tank over full
level = high; // assigned 3 by default
} else if (water.low && water.good && !water.high) { // tank good
level = good; // assigned 2 by default
} else if (water.low && !water.good && !water.high) { // tank low
level = low; // assigned 1 by default
} else if (!water.low && !water.good && !water.high) { // tank empty
level = empty; // assigned 0 by default
} else if (!water.low && water.good && water.high) { // ERROR, not possible
level = error; // assigned 5 by default
} else if (water.low && !water.good && water.high) { // ERROR, not possible
level = error; // assigned 5 by default
} else if (!water.low && !water.good && water.high) { // ERROR, not possible
level = error; // assigned 5 by default
} else if (!water.low && water.good && !water.high) { // ERROR, not possible
level = error; // assigned 5 by default
}
if (lastLevel != level) {
lastLevel = level;
logData(); // Log level change
PRINTdebug("Level:"); PRINTdebug(level); PRINTdebug("/"); PRINTdebugln(lastLevel);
//runTankLeveler();
}
}
//=============================================================================
// Set light tower
//-----------------------------------------------------------------------------
void runLightTower() {
// All outputs are active low, so true = on, false = off
if (water.chiller == true && (level == error || faulted)) {
mcp.digitalWrite(mcp_stack_light_siren, true); // Buzzer on
mcp.digitalWrite(mcp_stack_light_red, millis() % 500 > 250); // Red blinking
mcp.digitalWrite(mcp_stack_light_orange, false); // Orange off
mcp.digitalWrite(mcp_stack_light_green, false); // Green off
} else if (water.chiller == true && (filling || draining)) {
mcp.digitalWrite(mcp_stack_light_siren, false); // Buzzer off
mcp.digitalWrite(mcp_stack_light_red, false); // Red off
mcp.digitalWrite(mcp_stack_light_orange, millis() % 500 > 250); // Orange on
mcp.digitalWrite(mcp_stack_light_green, true); // Green off
} else if (water.chiller == true && level == low) {
mcp.digitalWrite(mcp_stack_light_siren, false); // Buzzer off
mcp.digitalWrite(mcp_stack_light_red, false); // Red ogg
mcp.digitalWrite(mcp_stack_light_orange, true); // Orange off
mcp.digitalWrite(mcp_stack_light_green, true); // Green ON
} else if (water.chiller == true) {
mcp.digitalWrite(mcp_stack_light_siren, false); // Buzzer off
mcp.digitalWrite(mcp_stack_light_red, false); // Red ogg
mcp.digitalWrite(mcp_stack_light_orange, false); // Orange off
mcp.digitalWrite(mcp_stack_light_green, true); // Green ON
} else if (water.chiller == false && level != error && !faulted) {
mcp.digitalWrite(mcp_stack_light_siren, false); // Buzzer off
mcp.digitalWrite(mcp_stack_light_red, false); // Red off
mcp.digitalWrite(mcp_stack_light_orange, true); // Orange blinking
mcp.digitalWrite(mcp_stack_light_green, false); // Green off
} else if (water.chiller == false && level != error && !faulted) {
mcp.digitalWrite(mcp_stack_light_siren, false); // Buzzer off
mcp.digitalWrite(mcp_stack_light_red, millis() % 400 > 200); // Red off
mcp.digitalWrite(mcp_stack_light_orange, false); // Orange blinking
mcp.digitalWrite(mcp_stack_light_green, false);
} else if (level == error) {
mcp.digitalWrite(mcp_stack_light_siren, false); // Buzzer off
mcp.digitalWrite(mcp_stack_light_red, millis() % 400 > 200); // Red off
mcp.digitalWrite(mcp_stack_light_orange, false); // Orange blinking
mcp.digitalWrite(mcp_stack_light_green, false); // Green off
}
//else {
// Normal state: water level between full and overfull
//mcp.digitalWrite(mcp_stack_light_siren, false); // Buzzer off
//mcp.digitalWrite(mcp_stack_light_red, millis() % 700 > 350); // Red blink
//mcp.digitalWrite(mcp_stack_light_orange, false); // Orange off
//mcp.digitalWrite(mcp_stack_light_green, true); // Green on
//}
}
//=============================================================================
// Function to convert Celsius to Fahrenheit
//-----------------------------------------------------------------------------
float c2f(float celsius) {
return (celsius * 9.0 / 5.0) + 32.0;
}
//=============================================================================
// Serial print the input io status
//-----------------------------------------------------------------------------
void printInputs() {
}
//=============================================================================
//
//-----------------------------------------------------------------------------
void FlowInterrupt() {
busy = true; // Set busy flag to indicate interrupt handling
pulse++; // Increment pulse count
busy = false; // Clear busy flag after interrupt handling
}
//=============================================================================
//
//-----------------------------------------------------------------------------
uint8_t SREG;
void Frequency() {
static unsigned long startTime;
if (micros() - startTime < 1000000UL ) return; // Check if 1000 milliseconds interval has passed
startTime = micros();
while (busy) {}; // Wait for interrupt handling to complete
uint8_t sreg = SREG; // Save status register
cli(); // Disable interrupts
count = pulse; // Save pulse count for calculation
pulse = 0; // Reset pulse count
SREG = sreg; // Restore status register
frequency = count / 2.0f; // Calculate frequency
flowRate = frequency * factor; // Calculate flow rate
//PlotInfo(); // Display information
}
//=============================================================================
//
//-----------------------------------------------------------------------------
void PlotInfo() {
//PRINTdebug("Freq.:= " + String(frequency, 2) + " Hz"); // Print frequency with 2 decimal places
//PRINTdebugln(", FLow:= " + String(flowRate, 3) + " L/min"); // Print flow rate with 3 decimal places
}
//=============================================================================
//
//-----------------------------------------------------------------------------
String nextionText;
void nextion_page1_update() {
if (temperature.prevreturned != temperature.returned) {
myNex.writeNum("n0.val", temperature.returned);
temperature.prevreturned = temperature.returned;
}
if (temperature.prevdelta != temperature.delta) {
myNex.writeNum("n1.val", temperature.delta);
temperature.prevdelta = temperature.delta;
}
if (temperature.prevsupply != temperature.supply) {
myNex.writeNum("n2.val", temperature.supply);
temperature.prevsupply = temperature.supply;
}
if (flow.prevgpm != flow.lm) {
myNex.writeNum("n3.val", flow.lm);
flow.prevgpm = flow.lm;
}
if (prev_progressBar != progressBar) {
myNex.writeNum("j0.val", progressBar);
prev_progressBar = progressBar;
}
if (prev_filling_light != filling_light) {
myNex.writeNum("b3.picc", filling_light);
prev_filling_light = filling_light;
}
if (prev_draining_light != draining_light) {
myNex.writeNum("b4.picc", draining_light);
prev_draining_light = draining_light;
}
if (prev_spare_light_1 != spare_light_1) {
myNex.writeNum("b5.picc", spare_light_1);
prev_spare_light_1 = spare_light_1;
}
if (prev_spare_light_2 != spare_light_2) {
myNex.writeNum("b6.picc", spare_light_2);
prev_spare_light_2 = spare_light_2;
}
if (prev_settings_light != settings_light) {
myNex.writeNum("b8.picc", settings_light);
prev_settings_light = settings_light;
}
if (water.prevchiller != water.chiller) {
myNex.writeNum("b9.picc", water.chiller);
water.prevchiller = water.chiller;
}
if (water.prevlow != water.low) {
myNex.writeNum("b10.picc", !water.low);
water.prevlow = water.low;
}
if (water.prevleak != water.leak) {
myNex.writeNum("b11.picc", water.leak);
water.prevleak = water.leak;
}
}
//=============================================================================
//
//-----------------------------------------------------------------------------
void nextion_page2_update() {
myNex.writeNum("n0.val", flow.lm);
myNex.writeNum("n1.val", flow.total);
myNex.writeNum("n2.val", fill.lm);
myNex.writeNum("n3.val", fill.total);
}
//=============================================================================
// Blackout Nextion Screen
//-----------------------------------------------------------------------------
void start_sleep() {
Serial.print("dim=0");
Serial.write(0xff);
Serial.write(0xff);
Serial.write(0xff);
sleeping = true;
}
//=============================================================================
//
//-----------------------------------------------------------------------------
void stop_sleep() {
Serial.print("dim=100");
Serial.write(0xff);
Serial.write(0xff);
Serial.write(0xff);
sleeping = false;
/*
read_mcp_inputs();
DateTime now = rtc.now();
get_flow();
read_analog();
read_thermistor();
checkTankLevel();
runTankLeveler();
*/
}
//=============================================================================
// Nextion Triggers
//-----------------------------------------------------------------------------
// This function is automatically called when the command 'printh 23 02 54 01' is received
void trigger1() { // printh 23 02 54 01 manual fill button
filling_light = true;
refill(true);
manual_mode = true;
}
//-----------------------------------------------------------------------------
void trigger2() { // printh 23 02 54 02 manual fill button release
filling_light = false;
refill(false);
manual_mode = false;
}
//-----------------------------------------------------------------------------
void trigger3() { // printh 23 02 54 03 manual drain button
draining_light = true;
drain(true);
manual_mode = true;
}
//-----------------------------------------------------------------------------
void trigger4() { // printh 23 02 54 04 manual drain button release
draining_light = false;
drain(false);
manual_mode = false;
}
//-----------------------------------------------------------------------------
void trigger5() { // printh 23 02 54 05 timer going to sleep
}
//-----------------------------------------------------------------------------
void trigger6() { // printh 23 02 54 06 screen touched. wake from sleep
if (sleeping) {
stop_sleep();
resetSleepTimer = true;
delay(100);
resetAll();
delay(100);
nextion_page1_update();
delay(100);
resetSleepTimer = false;
}
lastSleep = currentSec;
}
//-----------------------------------------------------------------------------
void trigger7() { // printh 23 02 54 07 reset button
reset_button = true;
resetAll();
}
//=============================================================================
//█████████████████████████████████████████████████████████████████████████████
//=============================================================================
// __ _
// / _\ ___ | |_ _ _ _ __
// \ \ / _ \| __|| | | || '_ \
// _\ \| __/| |_ | |_| || |_) |
// \__/ \___| \__| \__,_|| .__/
// |_|
//=============================================================================
//█████████████████████████████████████████████████████████████████████████████
//=============================================================================
void setup(void) {
setup_complete = false;
serialDebug.begin(serialBaud);
saveSettings();
delay(100);
loadSettings();
delay(100);
Wire.begin();
pinMode(LED1, OUTPUT);
pinMode(LED2, OUTPUT);
//----------------------------------------------------------------------
// Search for I2C components
//----------------------------------------------------------------------
//find_i2c();
//----------------------------------------------------------------------
// HTML Setup
//----------------------------------------------------------------------
WiFi.begin(WIFI_SSID, WIFI_PASSWORD, WIFI_CHANNEL);
PRINTdebug("Connecting to WiFi ");
PRINTdebug(WIFI_SSID);
// Wait for connection
while (WiFi.status() != WL_CONNECTED) {
delay(100);
PRINTdebug(".");
}
if (WiFi.status() == WL_CONNECTED) {
PRINTdebugln("");
PRINTdebugln("=================================");
PRINTdebugln(" Connected!");
PRINTdebug(" IP address: ");
PRINTdebugln(WiFi.localIP());
PRINTdebugln("=================================");
}
server.on("/", handleRoot);
server.on("/data", handleData);
server.begin();
PRINTdebugln("HTTP server started");
//----------------------------------------------------------------------
// NTP Setup
//----------------------------------------------------------------------
NTPsetup();
//----------------------------------------------------------------------
// Real Time Clock (RTC) Setup
//----------------------------------------------------------------------
if (!rtc.begin()) {
PRINTdebugln("Error: Realtime clock not found");
Serial.println("Error: Realtime clock not found");
delay(5000);
serialDebug.flush();
abort();
} else {
PRINTdebug("Realtime clock found at address ");
PRINTdebugln(mcp_address);
}
setRTCpc();
//rtc.begin();
//rtc.writeSqwPinMode(DS1307_SquareWave1HZ); // 1.000 Hz
//rtc.writeSqwPinMode(DS1307_SquareWave4kHz); // 4.096 kHz
//rtc.writeSqwPinMode(DS1307_SquareWave8kHz); // 8.192 kHz
//rtc.writeSqwPinMode(DS1307_SquareWave32kHz); // 32.768 kHz
//----------------------------------------------------------------------
// MCP23017 Setup
//----------------------------------------------------------------------
PRINTdebugln("MCP23017 Setup");
if (!mcp.begin_I2C(mcp_address)) {
PRINTdebugln("Error: MCP23017 not found");
while (1);
} else {
PRINTdebug("MCP23017 found at address ");
PRINTdebugln(mcp_address);
}
PRINTdebugln("Setup MCP inputs");
mcp.pinMode(mcp_chiller_running, INPUT);
mcp.pinMode(mcp_water_low, INPUT);
mcp.pinMode(mcp_water_med, INPUT);
mcp.pinMode(mcp_water_high, INPUT);
mcp.pinMode(mcp_water_leak, INPUT);
PRINTdebugln("Setup MCP outputs");
mcp.pinMode(mcp_stack_light_siren, OUTPUT);
mcp.pinMode(mcp_stack_light_red, OUTPUT);
mcp.pinMode(mcp_stack_light_orange, OUTPUT);
mcp.pinMode(mcp_stack_light_green, OUTPUT);
mcp.pinMode(mcp_spare_relay_12, OUTPUT);
mcp.pinMode(mcp_spare_relay_13, OUTPUT);
mcp.pinMode(mcp_fill_valve, OUTPUT);
mcp.pinMode(mcp_drain_valve, OUTPUT);
//----------------------------------------------------------------------
// ADS1115 Setup
//----------------------------------------------------------------------
// The ADC input range (or gain) can be changed via the following
// functions, but be careful never to exceed VDD +0.3V max, or to
// exceed the upper and lower limits if you adjust the input range!
// Setting these values incorrectly may destroy your ADC!
// ADS1015 ADS1115
// ------- -------
ads.setGain(GAIN_TWOTHIRDS); // 2/3x gain +/- 6.144V 1 bit = 3mV 0.1875mV (default)
//ads.setGain(GAIN_ONE); // 1x gain +/- 4.096V 1 bit = 2mV 0.125mV
// ads.setGain(GAIN_TWO); // 2x gain +/- 2.048V 1 bit = 1mV 0.0625mV
// ads.setGain(GAIN_FOUR); // 4x gain +/- 1.024V 1 bit = 0.5mV 0.03125mV
// ads.setGain(GAIN_EIGHT); // 8x gain +/- 0.512V 1 bit = 0.25mV 0.015625mV
// ads.setGain(GAIN_SIXTEEN); // 16x gain +/- 0.256V 1 bit = 0.125mV 0.0078125mV
// 5.0/6.144=0.8138, 0.8138 * 32768 = 26666.6666
if (!ads.begin()) {
PRINTdebugln("Error: ADS not found");
while (1);
} else {
PRINTdebug("ADS found at address ");
PRINTdebugln(ads_address);
}
//----------------------------------------------------------------------
// SD Setup
//----------------------------------------------------------------------
configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
if (!SD.begin(sd_cs)) {
PRINTdebugln("SD failed");
return;
}
if (!SD.exists(logfile)) {
File file = SD.open(logfile, FILE_WRITE);
file.println("timestamp,SD_ambient_temperature,SD_supply_temperature,SD_return_temperature,SD_flow,SD_fill,SD_level,SD_flow_total,SD_fill_total,SD_spare_1,SD_spare_2");
file.close();
}
//----------------------------------------------------------------------
// Pulse Flow Setup
//----------------------------------------------------------------------
PRINTdebugln("Pulse flow setup");
cycle_meter.begin(water_flow_pin, water_flow_K);
cycle_meter.setTresholds(MIN_FLOW, MAX_FLOW);
fill_meter.begin(water_fill_pin, water_fill_K);
fill_meter.setTresholds(MIN_FILL, MAX_FILL);
#ifdef incNextion
//----------------------------------------------------------------------
// Nextion Setup
//----------------------------------------------------------------------
PRINTdebugln("Nextion setup");
//nextion_setup();
myNex.begin(NextionBaud);
delay(500); // give Nextion some time to finish initialize
myNex.writeStr("page 0"); // For synchronizing Nextion page in case of reset to Arduino
delay(50);
//myNex.lastCurrentPageId = 1;
//-----------------
delay(500); // give Nextion some time to finish initialize
myNex.writeStr("page page0"); // For synchronizing Nextion page in case of reset to Arduino
delay(2000);
myNex.writeStr("page 1");
myNex.lastCurrentPageId = 0; // At the first run of the loop, the currentPageId and the lastCurrentPageId
// must have different values, due to run the function firstRefresh()
//----------------------------------------------------------------------
setMsgHeader("MUUS Chiller Controller");
#endif
stop_sleep();
delay(100);
resetAll();
delay(1000);
nextion_page1_update();
delay(1000);
resetSleepTimer = false;
checkWifi();
setup_complete = true;
PRINTdebugln("Setup Complete");
}
//=============================================================================
//█████████████████████████████████████████████████████████████████████████████
//=============================================================================
// __ ___ ___ ___
// / / /___\ /___\ / _ \
// / / // //// /// /_)/
// / /___/ \_/// \_/// ___/
// \____/\___/ \___/ \/
//=============================================================================
//█████████████████████████████████████████████████████████████████████████████
//=============================================================================
void loop(void) {
server.handleClient();
currentMillis = millis();
currentSec = currentMillis / 1000;
myNex.NextionListen(); // This function must be called repeatedly to response touch events
read_mcp_inputs();
if (faulted) {
faultCode();
}
if (currentMillis - previousMillis >= interval) {
DateTime now = rtc.now();
get_flow();
read_analog();
read_thermistor();
checkTankLevel();
runTankLeveler();
previousMillis = currentMillis;
}
if (currentMillis - slowMilli >= slowInterval) {
checkWifi();
if (wifiConnected)
getNTP();
String line = getTimestamp();
setMsgHeader(line);
slowMilli = currentMillis;
}
// Log data to SD Card
if ((currentSec - logSec >= logInterval) || logNOW) {
if (water.chiller)
logData();
logSec = currentMillis;
logNOW = false;
}
currentPageId = myNex.currentPageId;
if (lastCurrentPageId != currentPageId) {
currentPageId = myNex.currentPageId;
lastCurrentPageId = currentPageId;
}
// page 0 initial Splash screen
// page 1 page0 Homepage
// page 2 page1 settings
// page 3 page2 chart
if (currentPageId == 0)
myNex.writeStr("page 1");
if (currentPageId == 1)
nextion_page1_update();
if (currentPageId == 2)
nextion_page2_update();
if (currentSec > lastSleep + sleepInterval) {
if (!sleeping) {
start_sleep();
}
}
runLightTower();
}
//=============================================================================
//
//-----------------------------------------------------------------------------
water_high
water_med
water_low
chiller_running
Water Return Temperature
Ambient Temperature
Siren
Red
Orange
Green
(click to edit)
Water Fill Valve
Drain Valve
Cycle Flow
water_leak
Water Supply Temperature
optional RTC, if online pool fails
_____________________________
________________
Stack Light
________________
(click to edit)