HydroZoner IoT

This commit is contained in:
Syhxmii 2025-06-25 13:09:18 +07:00
commit d8cd33e003
12 changed files with 2537 additions and 0 deletions

426
App.h Normal file
View File

@ -0,0 +1,426 @@
//Handle main app routine
//........................
//#include "ButtonRotaryHandler.h"
//#include "MenuHandler.h"
//#include "DisplayHandler.h"
#define MENU_NONE 0
#define MENU_DASHBOARD 1
#define MENU_SELECT_OPTION 2
#define MENU_DURATION_MAIN 3
#define MENU_DURATION_POST_UV 4
#define MENU_CONNECTION 5
#define MENU_ABOUT 6
#define SUBMENU_SELECT_NONE 0
int8_t displayIdx = MENU_NONE;
int8_t menuIdx = 0;
int8_t menuDisplayLowerBound = 0;
int8_t menuDisplayUpperBound = 0;
uint32_t waitDelay = 0;
uint32_t waitTick = 0;
bool responseCleanupButton = false;
bool responseCleanupRotary = false;
const char* textStartStop[] = {
"Mulai Sterilisasi",
"Stop Sterilisasi"
};
const char* textStartStopRefill[] = {
"Mulai Refill",
"Stop Refill"
};
const char* textAutoMode[] = {
"Otomasi Steril: Ya",
"Otomasi Steril: Tidak"
};
const char* textAutoRefillMode[] = {
"Otomasi Refill: Ya",
"Otomasi Refill: Tidak"
};
const char* menus[] = {
"Mulai/Stop Sterilisasi",
"Mulai/Stop Refill",
"Otomasi Steril: Ya/Tidak",
"Otomasi Refill: Ya/Tidak",
"Durasi Sterilisasi",
"Post-Sterilisasi UV",
"Koneksi",
"Tentang Aplikasi",
"Kembali"
};
#define SUBMENU_START_STOP 0
#define SUBMENU_START_STOP_REFILL 1
#define SUBMENU_AUTOMODE 2
#define SUBMENU_AUTOMODE_REFILL 3
#define SUBMENU_DURATION 4
#define SUBMENU_POST_UV 5
#define SUBMENU_CONNECTION 6
#define SUBMENU_ABOUT 7
#define SUBMENU_BACK 8
const char* durationSelect[] = {
"5 Menit",
"10 Menit",
"15 Menit",
"20 Menit",
"25 Menit",
"30 Menit"
};
const char* durationSelect2[] = {
"Non-Aktifan",
"5 Menit",
"10 Menit",
"15 Menit",
"20 Menit",
"25 Menit",
"30 Menit"
};
const char* apSelect2[] = {
"Kembali",
"Aktifkan/Nonaktifkan",
"Password Baru"
"Tanpa Password"
};
int dialogSelectionIndex = 0;
int SelectDialog(const char *title, const char *text, const char* durationList[], int indexLimit){
//limit index
if (dialogSelectionIndex>indexLimit) dialogSelectionIndex = indexLimit;
if (dialogSelectionIndex<0) dialogSelectionIndex = 0;
if (ButtonSwitchedEvent()==BUTTON_UNPRESSED_EVENT){
Serial.println("OK");
return dialogSelectionIndex;
}
int direction = RotaryEvent();
if (direction==DIRECTION_CW){
dialogSelectionIndex++;
if (dialogSelectionIndex>indexLimit) dialogSelectionIndex = indexLimit;
Serial.println(">>");
}else if (direction==DIRECTION_CCW){
dialogSelectionIndex--;
if (dialogSelectionIndex<0) dialogSelectionIndex = 0;
Serial.println("<<");
}
ClearDisplayBuffer();
DrawCenteredText(title, 0, false, false, false, true);
DrawCenteredText(text, 2, false);
//DrawCenteredText("30 Menit", 3, true, true, true);
DrawCenteredText(durationList[dialogSelectionIndex], 3, true, (dialogSelectionIndex>0), (dialogSelectionIndex<indexLimit));
SendDisplayBuffer();
return -1;
}
void SetWaitResponse(uint32_t delayMillis, bool needButtonCleanup=false, bool needRotaryCleanup = false){
waitDelay = delayMillis;
waitTick = millis();
responseCleanupButton = needButtonCleanup;
responseCleanupRotary = needRotaryCleanup;
}
void AppStartSterilizationSystem(){
Serial.println("AppStartSterilizationSystem()");
//copy configuration and start
bool useUV = GetPostUVTimeIndex()>0;
uint32_t uvDuration = ((uint32_t)GetPostUVTimeIndex()) * (5 * 60 * 1000);
ChamberSetPostUvDuration(uvDuration);
uint32_t ozoneDuration = ((uint32_t) (1+GetSterilizationTimeIndex())) * (5 * 60 * 1000);
//uint32_t ozoneDuration = ((uint32_t) (1+GetSterilizationTimeIndex())) * (1 * 60 * 1000);
ChamberSetSterlizationDuration(ozoneDuration);
bool autoMode = GetSettingAutoMode();
StartChamberSystem(autoMode, true, useUV);
}
void AppStartRefillSystem(){
Serial.println("AppStartRefillSystem()");
//copy configuration and start
StartWaterBankSystem(GetSettingAutoRefillMode(), true);
}
//handle app logic
void AppRoutine(){
//background task
//..................
if ((millis()-waitTick)<waitDelay){
if (responseCleanupButton) ButtonResetEvent();
if (responseCleanupRotary) RotaryResetEvent();
return;
}else{
if (responseCleanupButton) {
responseCleanupButton = false;
ButtonResetEvent();
}
if (responseCleanupRotary) {
responseCleanupRotary = false;
RotaryResetEvent();
}
}
switch (displayIdx){
case MENU_NONE:{
SetWaitResponse(2000, true, true);
displayIdx = MENU_DASHBOARD;
SplashScreen();
//u8g2.setPowerSave(1); //not working :(
Serial.println("Switch: MENU_DASHBOARD");
return;
}
break;
case MENU_DASHBOARD:{
//DashboardDemoRoutine();
DashboardRoutine();
//handle button to enter select option
if (ButtonSwitchedEvent()==BUTTON_UNPRESSED_EVENT){
displayIdx = MENU_SELECT_OPTION;
menuIdx = 0;
menuDisplayLowerBound = 0;
int maxMenuCount = sizeof(menus) / sizeof(menus[0]);;
menuDisplayUpperBound = min(DrawTextRowCount(), maxMenuCount)-1;
Serial.print("Menu upper bound: ");
Serial.println(menuDisplayUpperBound);
Serial.println("Switch: MENU_SELECT_OPTION");
SetWaitResponse(50, true, true);
}else{
SetWaitResponse(5, false, false);
}
}
break;
case MENU_SELECT_OPTION:{
//handle rotary
int maxDisplayTextRow = DrawTextRowCount();
int maxMenuCount = sizeof(menus) / sizeof(menus[0]);
int boundDiff = (min(DrawTextRowCount(), maxMenuCount)-1);
int direction = RotaryEvent();
bool rotaryChanged = false;
if (direction==DIRECTION_CW){
rotaryChanged = true;
menuIdx++;
if (menuIdx>=maxMenuCount){
menuIdx = 0;
menuDisplayLowerBound = 0;
menuDisplayUpperBound = boundDiff;
}
if (menuIdx>menuDisplayUpperBound){
menuDisplayUpperBound = menuIdx;
menuDisplayLowerBound = menuDisplayUpperBound-boundDiff;
}
}else if (direction==DIRECTION_CCW){
Serial.println("handle CCW");
rotaryChanged = true;
menuIdx--;
if (menuIdx<0){
menuIdx=maxMenuCount-1;
menuDisplayUpperBound = menuIdx;
menuDisplayLowerBound = menuDisplayUpperBound-boundDiff;
}
//Serial.print("lowerbound "); Serial.println(menuDisplayLowerBound);
//Serial.print("upperbound "); Serial.println(menuDisplayUpperBound);
// if (menuDisplayUpperBound<menuIdx){
// menuDisplayUpperBound = menuIdx;
// menuDisplayLowerBound = menuDisplayUpperBound-boundDiff;
// }
if (menuIdx<menuDisplayLowerBound){
menuDisplayLowerBound = menuIdx;
menuDisplayUpperBound = menuDisplayLowerBound+boundDiff;
if (menuDisplayUpperBound>=maxMenuCount) menuDisplayUpperBound = maxMenuCount-1;
}
}
if (rotaryChanged){
SetWaitResponse(50);//next response is 50 (best)
}
//handle button
if (ButtonSwitchedEvent()==BUTTON_UNPRESSED_EVENT){
Serial.print("Clicking sub menu: ");
Serial.println(menuIdx);
switch(menuIdx){
case SUBMENU_START_STOP:{
if (IsChamberSystemRunning()){
Serial.println("Call: StopChamberSystem()");
StopChamberSystem();
}else{
Serial.println("Call: AppStartSterilizationSystem()");
ClearWaterBankError();//clear error juga buat water tank
AppStartSterilizationSystem();
}
displayIdx = MENU_DASHBOARD;
Serial.println("Switch: MENU_DASHBOARD");
SetWaitResponse(0, true, true);
return;
};
case SUBMENU_START_STOP_REFILL:{
if (IsWaterBankSystemRunning()){
StopWaterBankSystem();
}else{
ClearChamberError();//clear error juga buat chamber
AppStartRefillSystem();
}
displayIdx = MENU_DASHBOARD;
Serial.println("Switch: MENU_DASHBOARD");
SetWaitResponse(0, true, true);
return;
};
break;
case SUBMENU_AUTOMODE:{
SetSettingAutoMode(!GetSettingAutoMode());
SetWaitResponse(0, true, true);
return;
};
break;
case SUBMENU_AUTOMODE_REFILL:{
SetSettingAutoRefillMode(!GetSettingAutoRefillMode());
SetWaitResponse(0, true, true);
return;
};
break;
case SUBMENU_DURATION:{
dialogSelectionIndex = GetSterilizationTimeIndex();
displayIdx = MENU_DURATION_MAIN;
Serial.println("Switch: MENU_DURATION_MAIN");
SetWaitResponse(0, true, true);
return;
};
break;
case SUBMENU_POST_UV:{
dialogSelectionIndex = GetUVTimeIndex();
displayIdx = MENU_DURATION_POST_UV;
Serial.println("Switch: MENU_DURATION_POST_UV");
SetWaitResponse(0, true, true);
return;
};
break;
case SUBMENU_CONNECTION:{
displayIdx = MENU_CONNECTION;
Serial.println("Switch: MENU_CONNECTION");
SetWaitResponse(0, true, true);
return;
};
break;
case SUBMENU_ABOUT:{
displayIdx = MENU_ABOUT;
Serial.println("Switch: MENU_ABOUT");
SetWaitResponse(0, true, true);
return;
};
break;
case SUBMENU_BACK:{
displayIdx = MENU_DASHBOARD;
Serial.println("Switch: MENU_DASHBOARD");
SetWaitResponse(200, true, true);
return;
};
break;
}
}
//adapt state
if (!IsChamberSystemRunning()){
menus[SUBMENU_START_STOP] = textStartStop[0];
}else{
menus[SUBMENU_START_STOP] = textStartStop[1];
}
//adapt state for refill
if (!IsWaterBankSystemRunning()){
menus[SUBMENU_START_STOP_REFILL] = textStartStopRefill[0];
}else{
menus[SUBMENU_START_STOP_REFILL] = textStartStopRefill[1];
}
//adapt sterilization auto mode text:
if (GetSettingAutoRefillMode()){
menus[SUBMENU_AUTOMODE_REFILL] = textAutoRefillMode[0];
}else{
menus[SUBMENU_AUTOMODE_REFILL] = textAutoRefillMode[1];
}
//adapt refill auto mode text:
if (GetSettingAutoMode()){
menus[SUBMENU_AUTOMODE] = textAutoMode[0];
}else{
menus[SUBMENU_AUTOMODE] = textAutoMode[1];
}
ClearDisplayBuffer();
//DrawSectionPlaceholder();
int pointerIdx = menuIdx;
int row = 0;
for (int i=menuDisplayLowerBound;i<=menuDisplayUpperBound;i++){
DrawTextMenuAtRow(menus[i], row, i==pointerIdx);
row++;
}
SendDisplayBuffer();
}
break;
case MENU_DURATION_MAIN:{
int idx = SelectDialog("Durasi Sterilisasi", "Pilihan Durasi:", durationSelect, 5);
if (idx>=0){
displayIdx = MENU_SELECT_OPTION;
SetSterilizationTimeIndex(idx);
SetWaitResponse(0, true, true);
}else{
SetWaitResponse(50, false, false);
}
}
break;
case MENU_DURATION_POST_UV:{
int limit = 6;
int idx = SelectDialog("Post-Sterilisasi UV", "Pilihan Durasi:", durationSelect2, limit);
if (idx>=0){
displayIdx = MENU_SELECT_OPTION;
SetPostUVTimeIndex(idx);
SetWaitResponse(0, true, true);
}else{
SetWaitResponse(50, false, false);
}
}
break;
case MENU_CONNECTION:{
//DrawUnderConstruction();
ClearDisplayBuffer();
DrawCenteredText("Koneksi", 0, false, false, false, true);
DrawCenteredText("SSID: HYDROZONER_V1", 1, false);
String pwd = "Password: " + GetApPassword();
DrawCenteredText(pwd.c_str(), 2, false);
SendDisplayBuffer();
displayIdx = MENU_SELECT_OPTION;
SetWaitResponse(5000, true, true);
}
break;
case MENU_ABOUT:{
DrawAbout();
if (ButtonSwitchedEvent()==BUTTON_UNPRESSED_EVENT){
displayIdx = MENU_SELECT_OPTION;
}
}
break;
}
}
void InitApp(){
//cek setingan kalau mode auto maka dijalankan
if (GetSettingAutoMode()){
AppStartSterilizationSystem();
}
if (GetSettingAutoRefillMode()){
AppStartRefillSystem();
}
}

170
ButtonRotaryHandler.h Normal file
View File

@ -0,0 +1,170 @@
#define CLK_PIN 12 // BTN ENC1
#define DT_PIN 14 // BTN ENC2
#define SW_PIN 13 // BTN ENC
#define DIRECTION_NONE 0 // no change
#define DIRECTION_CCW 1 // counter-clockwise direction
#define DIRECTION_CW 2 // clockwise direction
#define BUTTON_PRESSED_EVENT 1
#define BUTTON_UNPRESSED_EVENT 2
#define BUTTON_NO_CHANGE_EVENT 0
volatile uint8_t rotaryDirection = DIRECTION_NONE;
volatile uint8_t rotaryDirectionPrev = DIRECTION_NONE;
volatile uint32_t rotaryLastTick;
volatile int rotaryCounter = 0;
volatile int rotaryCounterPrev = 0;
volatile int rotaryCounterPrevDebug = 0;
volatile bool buttonPressed = false;
volatile bool buttonPressedPrev = false;
volatile bool buttonPressedPrevDebug = false;
volatile uint32_t buttonLastTick = 0;
volatile int16_t rotaryPosition = 0;
volatile int16_t rotaryLastPosition = 0;
//volatile int16_t rotaryEncodedPrev = 0;
// Debounce time in milliseconds
const int debounceTime = 2;//2; //default is 2 (best)
volatile unsigned long lastInterruptTimeA = 0;
volatile unsigned long lastInterruptTimeB = 0;
volatile unsigned long lastInterruptTimeButton = 0;
// Variables to keep track of encoder state
volatile int lastEncoded = 0;
volatile long encoderValue = 0;
long lastEncoderValue = 0;
// ISR for encoder A pin
void IRAM_ATTR handleEncoderA() {
unsigned long currentTime = millis();
if ((currentTime - lastInterruptTimeA) > debounceTime) {
int MSB = digitalRead(CLK_PIN);
int LSB = digitalRead(DT_PIN);
int encoded = (MSB << 1) | LSB;
int sum = (lastEncoded << 2) | encoded;
if (sum == 0b1101 || sum == 0b0100 || sum == 0b0010 || sum == 0b1011){
encoderValue--;
rotaryDirection = DIRECTION_CCW;
}
if (sum == 0b1110 || sum == 0b0111 || sum == 0b0001 || sum == 0b1000){
encoderValue++;
rotaryDirection = DIRECTION_CW;
}
lastEncoded = encoded;
lastInterruptTimeA = currentTime;
rotaryCounter=encoderValue; //copy to rotaryCounter
}
}
// ISR for encoder B pin
void IRAM_ATTR handleEncoderB() {
unsigned long currentTime = millis();
if ((currentTime - lastInterruptTimeB) > debounceTime) {
handleEncoderA(); // Call handleEncoderA to handle B changes in the same way
lastInterruptTimeB = currentTime;
}
}
void InitButtonRotaryHandler(){
Serial.print("Initializing Button and Rotary Encoder: ");
buttonPressedPrev = false;
buttonPressedPrevDebug = false;
rotaryCounter = 0;
rotaryCounterPrev = 0;
rotaryCounterPrevDebug = 0;
rotaryDirection = DIRECTION_NONE;
rotaryDirectionPrev = DIRECTION_NONE;
rotaryLastTick = millis();
buttonLastTick = millis();
pinMode(DT_PIN, INPUT_PULLUP);
pinMode(CLK_PIN, INPUT_PULLUP);
pinMode(SW_PIN, INPUT_PULLUP);
// button.setDebounceTime(5);
attachInterrupt(digitalPinToInterrupt(CLK_PIN), handleEncoderA, CHANGE);
attachInterrupt(digitalPinToInterrupt(DT_PIN), handleEncoderB, CHANGE);
// Initialize button pin
//pinMode(SW_PIN, INPUT_PULLUP); // Assuming the button is active low
//attachInterrupt(digitalPinToInterrupt(SW_PIN), ButtonInterrupt, CHANGE);
Serial.println("OK");
}
bool ButtonIsPressed(){
if (millis() - buttonLastTick < 10 ) return buttonPressed; //debounce
buttonPressed = digitalRead(SW_PIN) == LOW;
buttonLastTick = millis();
return buttonPressed;
}
//return 0 = no change, 1 = pressed, 2 = unpressed
uint8_t ButtonSwitchedEvent(){
//Serial.println("A");
if (millis() - buttonLastTick < 10 ) return BUTTON_NO_CHANGE_EVENT; //debounce
//Serial.println("B");
buttonPressed = digitalRead(SW_PIN) == LOW;
buttonLastTick = millis();
if (buttonPressed!=buttonPressedPrev){
buttonPressedPrev = buttonPressed;
if (buttonPressed) return BUTTON_PRESSED_EVENT;
return BUTTON_UNPRESSED_EVENT;
}else{
return BUTTON_NO_CHANGE_EVENT; //no change
}
}
//Reset Button flag for event
void ButtonResetEvent(){
buttonPressed = digitalRead(SW_PIN) == LOW;
buttonPressedPrev = buttonPressed;
buttonLastTick = millis();
}
//Reset Rotary flag for event
void RotaryResetEvent(){
rotaryCounter = 0;
rotaryCounterPrev = 0;
encoderValue = 0;
rotaryDirection = DIRECTION_NONE;
rotaryDirectionPrev = DIRECTION_NONE;
}
//Get rotary last direction then clear flag
uint8_t RotaryEvent(){
if (rotaryDirection!=DIRECTION_NONE) lastInterruptTimeB = millis(); //debounce it again from last consume
uint8_t v = rotaryDirection;
rotaryDirection = DIRECTION_NONE;
rotaryDirectionPrev = DIRECTION_NONE;
return v;
}
//for debug testing
void ButtonRotaryDebugRoutine(){
if (buttonPressedPrevDebug!=buttonPressed){
buttonPressedPrevDebug = buttonPressed;
Serial.print("Button: ");
Serial.println((buttonPressed?"1":"0"));
}
if (rotaryCounter!=rotaryCounterPrevDebug){
rotaryCounterPrevDebug = rotaryCounter;
if (rotaryDirection!=DIRECTION_NONE){
Serial.print("Rotary: ");
Serial.print((rotaryDirection==DIRECTION_CW?">":"<"));
Serial.print(" (");
Serial.print(rotaryCounterPrevDebug);
Serial.println(")");
}
}
}

1
BuzzerHdlr.h Normal file
View File

@ -0,0 +1 @@

77
ConfigHandler.h Normal file
View File

@ -0,0 +1,77 @@
uint8_t settingSterilizationTimeIdx = 1; //in index. 1 = default
uint8_t settingUvTimeIdx = 2; //in index. 0 = off
uint8_t settingPostUvTimeIdx = 1; //in index. 0 = off
bool settingAutoMode = false;
bool settingAutoRefillMode = false;
String settingApPassword = "12345678";
int settingTick = 0;
int settingTickPrev = 0;
bool GetSettingAutoMode(){
return settingAutoMode;
}
void SetSettingAutoMode(bool v){
if (v!=settingAutoMode){
settingAutoMode = v;
settingTick++;
}
}
bool GetSettingAutoRefillMode(){
return settingAutoRefillMode;
}
void SetSettingAutoRefillMode(bool v){
if (v!=settingAutoRefillMode){
settingAutoRefillMode = v;
settingTick++;
}
}
uint8_t GetSterilizationTimeIndex(){
return settingSterilizationTimeIdx;
}
void SetSterilizationTimeIndex(int v){
if (v!=settingSterilizationTimeIdx){
settingSterilizationTimeIdx = v;
settingTick++;
}
}
uint8_t GetUVTimeIndex(){
return settingUvTimeIdx;
}
void SetUVTimeIndex(int v){
if (v!=settingUvTimeIdx){
settingUvTimeIdx = v;
settingTick++;
}
}
uint8_t GetPostUVTimeIndex(){
return settingUvTimeIdx;
}
void SetPostUVTimeIndex(int v){
if (v!=settingPostUvTimeIdx){
settingPostUvTimeIdx = v;
settingTick++;
}
}
void GenerateNewApPassword(){
//generate character with letter and number
Serial.println("not impelemented");
//................
settingTick++;
}
String GetApPassword(){
return settingApPassword;
}

348
Dashboard.h Normal file
View File

@ -0,0 +1,348 @@
#include "Raindrop.h"
const float pi = 3.14159265359;
//float displayPropellerAngle = 0;
int displayPropellerAngle = 0;
int diplayAniTick = 0;
int diplayAniDropTick = 0;
int displayStateTick3 = 0;
int displayDemoTick = 0;
uint32_t footerScrollSpeed = 3;
String footerText = "";
uint32_t footerDelayTick = 0;
int footerX = 4;
int footerY = 61;
int footerDisplayLength = 0;
uint32_t footerScrollTick = 0;
void DisplayFooterText(String v){
if (footerText!=v){
//reset and delay
footerX = 4;
footerText = v;
footerDisplayLength = DrawTextAtWitdh(v.c_str());
footerDelayTick = millis();
footerScrollTick = millis();
DrawTextAt(footerX, footerY, footerText.c_str());
}
//animation of text
if (footerDisplayLength<=120){
DrawTextAt(footerX, footerY, footerText.c_str());
}else{
//scrolling animation
if ((millis()-footerDelayTick) > 2000){
if (millis()-footerScrollTick >= 20){
footerScrollTick = millis();
footerX-=footerScrollSpeed;
if (footerX+footerDisplayLength+8<0){
footerScrollTick = millis();
footerX = 128;
}
DrawTextAt(footerX, footerY, footerText.c_str());
}
}else{
DrawTextAt(footerX, footerY, footerText.c_str());
}
}
}
String formatTime(int minutes, int seconds) {
char buffer[6];
sprintf(buffer, "%02d:%02d", minutes, seconds);
return String(buffer);
}
String MillisToMMSS(uint32_t v){
uint32_t sec = v / 1000;
uint32_t m = sec / 60;
sec = sec - (m*60);
String timeString = formatTime(m, sec);
return timeString;
}
void DashboardRoutine(){
ClearDisplayBuffer();
//gambar bingkai
DrawFrame(0,0,128,64);
DrawFrame(0,0,128,12); //kotak kiri atas
DrawFrame(0,0,43,12); //kotak tengah atas
DrawFrame(42,0,43,12); //kotak kanan atas
DrawFrame(0,52,128,12); //kotak bawah
DrawTextAt(4, 9, "pH ---"); //isi dengan PH
DrawTextAt(46, 9, "NTU ---"); //isi dengan NTU
DrawTextAt(88, 9, "Auto"); //isi dengan apalah: sinyal wifi kek, sisa batre kek ya apalah gitu. Asal jangan isi dengan perasaanmu padanya.
//draw tank ==================================
int x = 0;
int y = 15;
int w = 25;
int h = 34;
int gap = 11;
//int startX = (128 - ((2*(w+gap)) + w)) / 2; //center, ignore pump draw
int startX = (128 - ((3*(w+gap)) + 0)) / 2; //center includes pump draw
String v = "";
for (int i = 0; i<3; i++){
//tong
x = startX + (i*(w+gap));
//DrawTank(x,y,w,h);
//kotak pompa samping
DrawFrame(x+w-1, y+4, gap+2, gap+2);
//animasi ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
bool drawPump = false;
if (i==0){
//pompa in 1 (ke tong penampung setelah sterilisasi)
drawPump = GetSwitchState(STERILIZED_CHAMBER_FILL_PUMP_SWITCH);
}else if (i==1){
//pompa in 2 (tong sterilisasi)
drawPump = GetSwitchState(STERILIZATION_CHAMBER_FILL_PUMP_SWITCH);
}else{
//pompa in 3 (tong penampung air kotor)
drawPump = GetSwitchState(WATER_BANK_FILL_PUMP_SWITCH);
}
if (drawPump){
//propeller
drawPropeller(x+w+5,y+4+6,10,displayPropellerAngle);
//bulatan propeller
DrawCircle(x+w+5,y+4+6,5);
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
if (i==1){
//large propeller on tank 2
bool drawSterilizationFX = false;
drawSterilizationFX = GetSwitchState(STERILIZATION_OZONE_AND_UV_SWITCH);
if (drawSterilizationFX){
//draw big propeller
drawPropeller(x+(w/2),y+4+17,11,displayPropellerAngle);
//drawPropeller(x+(w/2),y+4+15,11,displayPropellerAngle+45);
//draw waves
DrawCircle(x+(w/2),y+4+17,0+diplayAniTick<10?7+diplayAniTick:10);
DrawCircle(x+(w/2),y+4+17,5+diplayAniTick<10?5+diplayAniTick:10);
DrawCircle(x+(w/2),y+4+17,7+diplayAniTick<10?7+diplayAniTick:10);
}
}
//wave animation on tank 1
if (i==0){
bool drawUvFX = false;
drawUvFX = GetSwitchState(STERILIZED_UV_SWITCH);
if (drawUvFX){
DrawCircle(x+(w/2),y+4+17,0+diplayAniTick<10?diplayAniTick:10);
DrawCircle(x+(w/2),y+4+17,5+diplayAniTick<10?5+diplayAniTick:10);
DrawCircle(x+(w/2),y+4+17,7+diplayAniTick<10?7+diplayAniTick:10);
}
}
//draw between empty-lower-upper with xor mode
int waterState = 0;
if (i==0){
//(tong penampung setelah sterilisasi)
if (GetInSwitchState(STERILIZED_CHAMBER_FLOAT_SWITCH_LOWER)) waterState = 1;
if (GetInSwitchState(STERILIZED_CHAMBER_FLOAT_SWITCH_UPPER)) waterState = 2;
}else if (i==1){
//(tong sterilisasi)
if (GetInSwitchState(STERILIZATION_CHAMBER_FLOAT_SWITCH_LOWER)) waterState = 1;
if (GetInSwitchState(STERILIZATION_CHAMBER_FLOAT_SWITCH_UPPER)) waterState = 2;
}else{
//(tong penampung air kotor)
if (GetInSwitchState(WATER_BANK_FLOAT_SWITCH_LOWER)) waterState = 1;
if (GetInSwitchState(WATER_BANK_FLOAT_SWITCH_UPPER)) waterState = 2;
}
//drawing
if (waterState==1){
//lower
DrawTankFilled(x+2,y+(h-10)-2,w-4,10); //h harus minimal radius*2 hedeh
}else if (waterState==2){
//upper
DrawTankFilled(x+2,y+(h-24)-1,w-4,23); //h harus minimal radius*2 hedeh
}
DrawTank(x,y,w,h);
if (drawPump){
//watering animation on all tank
initRaindrops(6, h-11);
animateRain(diplayAniDropTick, (x+w)-9, y+7, 6, h-11);
}
//angka pada tong
v = (String) (i+1);
DrawTextAt(x+10, y+8, v.c_str());
}
//===========================================
diplayAniTick++;
if (diplayAniTick>5) diplayAniTick = 0;
diplayAniDropTick++;
if (diplayAniDropTick>8) diplayAniDropTick = 8;
if (millis()-displayDemoTick>2000){
displayDemoTick = millis();
displayStateTick3++;
if (displayStateTick3>=3) displayStateTick3 = 0;
}
//footer info
//ni nih paling susah karena harus sinkronasi sama error dan perubahan text error
String footerToDisplay = "";
if (ChamberIsError()){
footerToDisplay = ChamberErrorString();
}else if (IsWaterBankError()){
footerToDisplay = WaterBankErrorString();
}else{
if (ChamberIsIdle()){
if (ChamberIsSuccess()){
footerToDisplay = "Selesai.";
}else{
if (IsWaterBankSystemRunning()){
footerToDisplay = "Mengisi Tangki 3...";
}else{
footerToDisplay = "Sistem siap."; //ready
}
}
}else if (IsChamberSystemRunning()){
//display state
if (IsChamberFilling()){
footerToDisplay = "Mengisi Tangki 2...";
}else if (IsChamberUnload()){
footerToDisplay = "Memindahkan ke Tangki 1...";
}else{
uint32_t t = GetSterilizationRemainingMillis();
if (t>0){
bool onPostUV = OnPostUV();
bool onOzone = OnOzoneStrelization();
if (onOzone){
footerToDisplay = "Sedang Sterilisasi " + MillisToMMSS(t);
}else if (onPostUV){
footerToDisplay = "Post UV " + MillisToMMSS(t);
}else{
footerToDisplay = "Sedang Sterilisasi..."; //aneh sih kalau disini, dahlah
}
}else{
footerToDisplay = "Sedang Sterilisasi...";
}
}
}
}
DisplayFooterText(footerToDisplay);
//DrawTextAt(4, 61, "Ready.");
SendDisplayBuffer();
//displayPropellerAngle+=pi/10;
//if (displayPropellerAngle>=pi) displayPropellerAngle-=pi;
displayPropellerAngle-=15;
if (displayPropellerAngle<=-360) displayPropellerAngle=-(abs(displayPropellerAngle)%360);
}
void DashboardDemoRoutine(){
ClearDisplayBuffer();
//gambar bingkai
DrawFrame(0,0,128,64);
DrawFrame(0,0,128,12); //kotak kiri atas
DrawFrame(0,0,43,12); //kotak tengah atas
DrawFrame(42,0,43,12); //kotak kanan atas
DrawFrame(0,52,128,12); //kotak bawah
//draw lines for
//DrawDashboardBackground(); //not implemented yet
DrawTextAt(4, 9, "pH 14.0"); //isi dengan PH
DrawTextAt(46, 9, "NTU 999"); //isi dengan NTU
DrawTextAt(88, 9, "Auto"); //isi dengan apalah: sinyal wifi kek, sisa batre kek
//draw tank ==================================
int x = 0;
int y = 15;
int w = 25;
int h = 34;
int gap = 11;
//int startX = (128 - ((2*(w+gap)) + w)) / 2; //center, ignore pump draw
int startX = (128 - ((3*(w+gap)) + 0)) / 2; //center includes pump draw
String v = "";
for (int i = 0; i<3; i++){
//tong
x = startX + (i*(w+gap));
//DrawTank(x,y,w,h);
//pompa samping
DrawFrame(x+w-1, y+4, gap+2, gap+2);
//propeller
drawPropeller(x+w+5,y+4+6,10,displayPropellerAngle);
//bulatan propeller
DrawCircle(x+w+5,y+4+6,5);
if (i==1){
//large propeller on tank 2
drawPropeller(x+(w/2),y+4+17,11,displayPropellerAngle);
//drawPropeller(x+(w/2),y+4+15,11,displayPropellerAngle+45);
DrawCircle(x+(w/2),y+4+17,0+diplayAniTick<10?0+diplayAniTick:10);
DrawCircle(x+(w/2),y+4+17,5+diplayAniTick<10?5+diplayAniTick:10);
DrawCircle(x+(w/2),y+4+17,7+diplayAniTick<10?7+diplayAniTick:10);
}
//wave animation on tank 1
if (i==0){
DrawCircle(x+(w/2),y+4+17,0+diplayAniTick<10?diplayAniTick:10);
DrawCircle(x+(w/2),y+4+17,5+diplayAniTick<10?5+diplayAniTick:10);
DrawCircle(x+(w/2),y+4+17,7+diplayAniTick<10?7+diplayAniTick:10);
}
//draw between empty-lower-upper with xor mode
if (displayStateTick3==1){
//lower
DrawTankFilled(x+2,y+(h-10)-2,w-4,10); //h harus minimal radius*2 hedeh
}else if (displayStateTick3==2){
//upper
DrawTankFilled(x+2,y+(h-24)-1,w-4,23); //h harus minimal radius*2 hedeh
}
DrawTank(x,y,w,h);
//watering animation on all tank
initRaindrops(6, h-11);
animateRain(diplayAniDropTick, (x+w)-9, y+7, 6, h-11);
//angka pada tong
v = (String) (i+1);
DrawTextAt(x+10, y+8, v.c_str());
}
//===========================================
diplayAniTick++;
if (diplayAniTick>5) diplayAniTick = 0;
diplayAniDropTick++;
if (diplayAniDropTick>8) diplayAniDropTick = 8;
if (millis()-displayDemoTick>2000){
displayDemoTick = millis();
displayStateTick3++;
if (displayStateTick3>=3) displayStateTick3 = 0;
}
//footer info
DrawTextAt(4, 61, "Ready.");
SendDisplayBuffer();
//displayPropellerAngle+=pi/10;
//if (displayPropellerAngle>=pi) displayPropellerAngle-=pi;
displayPropellerAngle-=15;
if (displayPropellerAngle<=-360) displayPropellerAngle=-(abs(displayPropellerAngle)%360);
}

244
DisplayHandler.h Normal file
View File

@ -0,0 +1,244 @@
#include <U8g2lib.h>
U8G2_ST7920_128X64_F_SW_SPI u8g2(U8G2_R0, 18, 23, 5, U8X8_PIN_NONE);
//splashscreen
const unsigned char logo [] U8X8_PROGMEM = {
0x00,0x00,0xff,0x07,0x00,0x00,0x00,0xe0,0x20,0x18,0x00,0x00,0x00,0x18,0x40,0x60,0x00,0x00,0x00,0x06,0x00,0x80,
0x01,0x00,0x00,0x07,0x00,0x00,0x06,0x00,0x80,0x05,0x00,0x00,0x0c,0x00,0xc0,0x00,0x40,0x00,0x08,0x00,0x40,0x00,
0x02,0x00,0x10,0x00,0x20,0x00,0x02,0x08,0x10,0x00,0x20,0x08,0x00,0x08,0x20,0x00,0x30,0x10,0x20,0x04,0x20,0x00,
0x30,0x10,0x20,0x00,0x21,0x00,0x51,0x20,0x22,0x80,0x21,0x02,0x91,0x20,0x24,0x82,0x20,0x02,0x11,0x40,0x24,0x42,
0x20,0x02,0x11,0x42,0x24,0x42,0x20,0x02,0x11,0x86,0x24,0x21,0x20,0x02,0x31,0x84,0x24,0x21,0x22,0x02,0x71,0x08,
0x25,0x11,0x23,0x02,0xd1,0x10,0x29,0x99,0x21,0x02,0x91,0x33,0xaa,0xc9,0x30,0x02,0x11,0x46,0xfe,0x65,0x2c,0x02,
0x31,0xdc,0x03,0x27,0x22,0x02,0xf1,0xf1,0x00,0x9c,0x21,0x02,0x11,0x67,0x00,0xd8,0x20,0x02,0x31,0x38,0x00,0x70,
0x20,0x02,0xf1,0x31,0x00,0x20,0x3e,0x02,0x11,0x13,0x00,0x40,0x21,0x02,0x31,0x0c,0x07,0xc0,0x23,0x02,0x71,0xe8,
0x18,0x60,0x2c,0x02,0xd1,0x39,0xe0,0x1c,0x30,0x02,0x11,0x07,0x00,0x03,0x20,0x02,0x11,0x00,0x00,0x00,0x20,0x02,
0x31,0xc0,0x1f,0xe0,0x27,0x02,0xf1,0x60,0x60,0x18,0x38,0x02,0x11,0x1f,0x80,0x07,0x20,0x02,0x11,0x00,0x00,0x00,
0x20,0x02,0x31,0x80,0x0f,0xc0,0x27,0x02,0x71,0x60,0x30,0x30,0x38,0x02,0x91,0x3f,0xc0,0x0f,0x20,0x02,0x11,0x00,
0x00,0x00,0x20,0x02,0x11,0x00,0x00,0x00,0x20,0x02,0x11,0x00,0x07,0x80,0x23,0x02,0x71,0xc0,0x18,0x60,0x3c,0x02,
0xe3,0x3f,0xe0,0x1c,0x30,0x02,0x23,0x00,0x00,0x03,0x10,0x02,0x22,0x00,0x00,0x00,0x10,0x03,0x42,0xc0,0x1f,0xe0,
0x0f,0x01,0x84,0x39,0x60,0x18,0x0c,0x01,0x04,0x07,0x80,0x07,0x86,0x01,0x08,0x06,0x00,0x00,0xc3,0x00,0x18,0x38,
0x00,0xe0,0x60,0x00,0x30,0xc0,0xff,0x1f,0x30,0x00,0xc0,0x00,0x00,0x00,0x0c,0x00,0x00,0x03,0x00,0x00,0x03,0x00,
0x00,0xfc,0xff,0xff,0x00,0x00};
void SplashScreen(){
u8g2.clearBuffer();
u8g2.setDrawColor(1); //set to 1
u8g2.setFont(u8g2_font_helvB08_tr);
u8g2.drawStr(51, 32, "HYDROZONER");
u8g2.drawXBMP(6, 4, 42, 56, logo);
u8g2.setFont(u8g2_font_profont11_tr);
u8g2.drawStr(55, 41, "PKM-KC 2024");
u8g2.sendBuffer();
//delay(2000);
}
void InitDisplay(){
//u8g2.setBusClock(1000000);
u8g2.setBusClock(8000000);
u8g2.begin();
//u8g2.clearBuffer();
u8g2.setFontMode(1);
u8g2.setBitmapMode(1);
u8g2.clear();
delay(2);
u8g2.initDisplay();
delay(2);
//u8g2.sendBuffer();
}
void ClearDisplayBuffer(){
u8g2.clearBuffer();
}
void SendDisplayBuffer(){
u8g2.sendBuffer();
}
void DrawDashboardBackground(){
//Not implemented
//u8g2.clearBuffer();
u8g2.setFont(u8g2_font_profont11_tr);
u8g2.setDrawColor(1);
u8g2.drawStr(0, 10, "Dashboard not coded!");
//u8g2.drawXBM(0, 0, 32, 32, tangki_kosong);
//u8g2.sendBuffer();
//...................................
}
void DrawSectionPlaceholder(){
u8g2.setFont(u8g2_font_profont12_tr); //8 pixel height font
u8g2.setDrawColor(1);
int x = 0;
int y = 0;
int h = 12;
int w = 128;
for (int i=0;i<5;i++){
y = (i*12);
if ((i%2)==0){
u8g2.drawBox(x,y,w,h);
u8g2.setDrawColor(2); //set to xor
//u8g2.setFontMode(1);
}else{
//u8g2.setFontMode(0);
u8g2.setDrawColor(1); //set to 1
}
y = 10 + (i*12);
u8g2.drawStr(x, y, "123456789ABCDEFGHIJKL");
}
}
int DrawTextRowCount(){
return 5;
}
void DrawTextMenuAtRow(const char *v, byte row, bool highlight){
int x = 0;
int y = 0;
int h = 12;
int w = 128;
u8g2.setFont(u8g2_font_profont12_tr); //8 pixel height font
u8g2.setDrawColor(1);
//highlight
if (highlight){
x = 0;
y = row*12;
u8g2.drawBox(x,y,w,h);
u8g2.setDrawColor(2); //set to xor
}
//text
x = 2;
y = 10 + (row*12);
u8g2.drawStr(x, y, v);
}
void DrawAbout(){
u8g2.clearBuffer();
u8g2.setDrawColor(1); //set to 1
u8g2.setFont(u8g2_font_helvB08_tr);
u8g2.drawStr(51, 22, "HYDROZONER");
u8g2.drawXBMP(6, 4, 42, 56, logo);
u8g2.setFont(u8g2_font_profont11_tr);
u8g2.drawStr(55, 35, "PKM-KC 2024");
u8g2.drawStr(55, 45, "V1.0");
u8g2.sendBuffer();
}
void DrawUnderConstruction(){
u8g2.clearBuffer();
u8g2.setFont(u8g2_font_profont12_tr); //8 pixel height font
u8g2.setDrawColor(1);
u8g2.drawStr(5, 35, "<Under Construction>");
u8g2.sendBuffer();
}
void DrawCenteredText(const char *v, byte row, bool highlight=false, bool arrowLeft = false, bool arrowRight = false, bool horizontalLine=false){
int x = 0;
int y = 0;
int h = 12;
int w = 128;
u8g2.setFont(u8g2_font_profont12_tr); //8 pixel height font
u8g2.setDrawColor(1);
//highlight
if (highlight){
x = 0;
y = row*12;
u8g2.drawBox(x,y,w,h);
u8g2.setDrawColor(2); //set to xor
}
//text
x = (128-u8g2.getStrWidth(v)) / 2;
y = 10 + (row*12);
u8g2.drawStr(x, y, v);
if (arrowLeft){
u8g2.setFont(u8g2_font_profont12_tr); //8 pixel height font
x = 10;
u8g2.drawStr(x, y, "<");
}
if (arrowRight){
u8g2.setFont(u8g2_font_profont12_tr); //8 pixel height font
x = 108;
u8g2.drawStr(x+u8g2.getStrWidth(">"), y, ">");
}
u8g2.setDrawColor(1);
if (horizontalLine){
u8g2.drawHLine(0, y+2, 128);
}
}
void DrawTank(int x, int y, int w, int h){
//draw rounded
u8g2.setDrawColor(1);
//u8g2.drawRBox(x, y, w, h, 4);
u8g2.drawRFrame(x, y, w, h, 4);
}
void DrawTankFilled(int x, int y, int w, int h){
//draw rounded
//u8g2.drawRBox(x, y, w, h, 4);
u8g2.setDrawColor(2); //set to xor
u8g2.drawRBox(x, y, w, h, 4);
}
void DrawTextAt(int x, int y, const char *v){
u8g2.setFont(u8g2_font_profont10_tr); //6 pixel height font
u8g2.setDrawColor(1);
u8g2.drawStr(x, y, v);
}
int DrawTextAtWitdh(const char *v){
u8g2.setFont(u8g2_font_profont10_tr); //6 pixel height font
return u8g2.getStrWidth(v);
}
void DrawFrame(int x, int y, int w, int h){
u8g2.setDrawColor(1);
u8g2.drawFrame(x, y, w, h);
}
// Function to draw a propeller
void drawPropeller(int x, int y, int diameter, float angle, bool xorMode=false) {
int radius = diameter / 2;
float radAngle = angle * (PI / 180.0); // Convert angle to radians
// Calculate positions of the blades
int x1 = x + radius * cos(radAngle);
int y1 = y + radius * sin(radAngle);
int x2 = x - radius * cos(radAngle);
int y2 = y - radius * sin(radAngle);
int x3 = x + radius * cos(radAngle + PI / 2);
int y3 = y + radius * sin(radAngle + PI / 2);
int x4 = x - radius * cos(radAngle + PI / 2);
int y4 = y - radius * sin(radAngle + PI / 2);
// Draw the blades
if (xorMode){
u8g2.setDrawColor(2);
}else{
u8g2.setDrawColor(1);
}
u8g2.drawLine(x, y, x1, y1);
u8g2.drawLine(x, y, x2, y2);
u8g2.drawLine(x, y, x3, y3);
u8g2.drawLine(x, y, x4, y4);
}
void DrawCircle(int x, int y, int r){
u8g2.setDrawColor(1);
u8g2.drawCircle(x,y,r);
}

87
FloatSwitchHandler.h Normal file
View File

@ -0,0 +1,87 @@
#define IN_SWITCH_INVERT false
#define IN_SWITCH_PULLUP false
#define IN_SWITCH_NUM 6
#define PINOUT_WATER_BANK_FLOAT_SWITCH_LOWER 26
#define PINOUT_WATER_BANK_FLOAT_SWITCH_UPPER 27
#define PINOUT_STERILIZATION_CHAMBER_FLOAT_SWITCH_LOWER 33
#define PINOUT_STERILIZATION_CHAMBER_FLOAT_SWITCH_UPPER 25
#define PINOUT_STERILIZED_CHAMBER_FLOAT_SWITCH_LOWER 32
#define PINOUT_STERILIZED_CHAMBER_FLOAT_SWITCH_UPPER 35
#define WATER_BANK_FLOAT_SWITCH_LOWER 0
#define WATER_BANK_FLOAT_SWITCH_UPPER 1
#define STERILIZATION_CHAMBER_FLOAT_SWITCH_LOWER 2
#define STERILIZATION_CHAMBER_FLOAT_SWITCH_UPPER 3
#define STERILIZED_CHAMBER_FLOAT_SWITCH_LOWER 4
#define STERILIZED_CHAMBER_FLOAT_SWITCH_UPPER 5
static byte IN_SWITCH_OFF = LOW;
static byte IN_SWITCH_ON = HIGH;
static int8_t inSwitchPin[IN_SWITCH_NUM] = {PINOUT_WATER_BANK_FLOAT_SWITCH_LOWER,
PINOUT_WATER_BANK_FLOAT_SWITCH_UPPER,
PINOUT_STERILIZATION_CHAMBER_FLOAT_SWITCH_LOWER,
PINOUT_STERILIZATION_CHAMBER_FLOAT_SWITCH_UPPER,
PINOUT_STERILIZED_CHAMBER_FLOAT_SWITCH_LOWER,
PINOUT_STERILIZED_CHAMBER_FLOAT_SWITCH_UPPER
};
static byte inSwitchState[IN_SWITCH_NUM];
void ClearInSwitch(){
for (int i=0; i<IN_SWITCH_NUM; i++){
int8_t pin = inSwitchPin[i];
//if (pin>=0){
inSwitchState[i] = IN_SWITCH_OFF;
//}
}
}
void InitInSwitchHandler(){
if (!IN_SWITCH_INVERT){
IN_SWITCH_OFF = LOW;
IN_SWITCH_ON = HIGH;
}else{
IN_SWITCH_OFF = LOW;
IN_SWITCH_ON = HIGH;
}
ClearInSwitch();
for (int i=0; i<IN_SWITCH_NUM; i++){
int8_t pin = inSwitchPin[i];
if ((pin>=0) && ((pin>=34 && pin<=36) || (pin==39))){
//Input only pins (should be use external pull up)
pinMode(pin, INPUT);
}else if (pin>=0){
//use internal pullup
if (IN_SWITCH_PULLUP){
pinMode(pin, INPUT_PULLUP);
}else{
pinMode(pin, INPUT_PULLDOWN);
}
}
}
}
void RefreshInSwitchState(){
for (int i=0; i<IN_SWITCH_NUM; i++){
int8_t pin = inSwitchPin[i];
if (pin>=0){
inSwitchState[i] = digitalRead(pin);
}
}
}
bool GetInSwitchState(byte idx){
if (idx<0 || idx>=IN_SWITCH_NUM) return false;
return (inSwitchState[idx] == IN_SWITCH_ON);
}
//used for unit test
void SetInSwitchState(byte idx, bool state){
if (idx<0 || idx>=IN_SWITCH_NUM) return;
inSwitchState[idx] = state?IN_SWITCH_ON:IN_SWITCH_OFF;
}

55
HydroZoner.ino Normal file
View File

@ -0,0 +1,55 @@
#include "ConfigHandler.h"
#include "BuzzerHdlr.h"
#include "SwitchHandler.h"
#include "FloatSwitchHandler.h"
#include "ButtonRotaryHandler.h"
#include "WaterBankHandler.h"
#include "SterilizationHandler.h"
#include "DisplayHandler.h"
#include "Dashboard.h"
#include "App.h"
uint32_t switchRefreshDuration = 2000;
uint32_t switchRefreshTick = 0;
void setup(){
Serial.begin(115200);
InitSwitchHandler();
InitInSwitchHandler();
InitButtonRotaryHandler();
for(int i=0; i<2; i++){
delay(1000);
InitDisplay();
}
delay(50);
InitDisplay();
//delay(2000); //wait voltage stabilize
delay(5);
//bool b = WaterBankTestUnit();
InitApp();
}
void loop(){
// SwitchDemo();
// return;
// checkRelay();
ButtonRotaryDebugRoutine();
AppRoutine();
//Wifi and AP routine here
//..............................
//lazy update floating switch states
if (millis()-switchRefreshTick>switchRefreshDuration){
switchRefreshTick = millis();
RefreshInSwitchState();
}
WaterBankSystemRoutine();
ChamberSystemRoutine();
}

58
Raindrop.h Normal file
View File

@ -0,0 +1,58 @@
const int numDrops = 8; // Number of raindrops
// Structure to store raindrop positions
struct Raindrop {
int x;
int y;
};
// Array to store raindrops
Raindrop drops[numDrops];
// Function to initialize raindrops within the specified area
void initRaindrops(int w, int h) {
for (int i = 0; i < numDrops; i++) {
//mod for like waterfall from right side
drops[i].x = random(0, w);
drops[i].y = random(0, h);
if ((drops[i].y<(w/2)) && (drops[i].x<(w/2)) ){
drops[i].x = random(w/2, w);
drops[i].y = random(w/2, w);
}
}
}
// Function to update raindrop positions within the specified area
void updateRaindrops(int w, int h) {
for (int i = 0; i < numDrops; i++) {
//drops[i].y += random(1, 4); // Update y position
drops[i].y += random(1, 3); // Update y position. 2 more looks like water from pipe
if (drops[i].y >= h) { // Reset position if it goes out of bounds
drops[i].x = random(0, w);
drops[i].y = 0;
if ((drops[i].y<(w/2)) && (drops[i].x<(w/2)) ){
drops[i].x = random(w/2, w);
drops[i].y = random(w/2, w);
}
}
}
}
// Function to draw raindrops within the specified area
void drawRaindrops(int x, int y) {
for (int i = 0; i < numDrops; i++) {
u8g2.drawPixel(drops[i].x+x, drops[i].y+y);
}
}
// Function to animate rain with keyframes, position, and size
void animateRain(int keyframes, int x, int y, int w, int h) {
for (int frame = 0; frame < keyframes; frame++) {
//u8g2.clearBuffer();
drawRaindrops(x, y);
//u8g2.sendBuffer();
updateRaindrops(w, h);
//delay(100); // Adjust delay for animation speed
}
}

629
SterilizationHandler.h Normal file
View File

@ -0,0 +1,629 @@
//Sterilization Chamber => Tank 2, used for sterilizing the water
// -> Handle the Sterilization Chamber and Post UV on storage
// -> Dependency: water bank's float switch Low and High
// chamber's float switch Low and High
// storage's float switch Low and High
#define CHAMBER_STATE_NONE 0
#define CHAMBER_STATE_IDLE 1
#define CHAMBER_STATE_START_FILLING 2
#define CHAMBER_STATE_CHECK_FILLING_TO_LOWER 3
#define CHAMBER_STATE_CHECK_FILLING_TO_UPPER 4
#define CHAMBER_STATE_STERILIZING 5
#define CHAMBER_STATE_START_UNLOAD 6
#define CHAMBER_STATE_START_UNLOAD_TO_LOWER 7
#define CHAMBER_STATE_START_UNLOAD_TO_UPPER 8
//uint32_t chamberSterilizationOzoneDuration = 1*60*1000;
uint32_t chamberSterilizationUvDuration = 5*60*1000;
//uint32_t chamberSterilizationPostUvDuration = 5*60*1000;
uint32_t chamberExpectedTimeToFilledLower = 1*60*1000;//in mulliseconds
uint32_t chamberExpectedTimeToFilledUpper = 3*60*1000;//in mulliseconds
uint32_t chamberExpectedTimeToUnloadFilledLower = 1*60*1000;//in mulliseconds
uint32_t chamberExpectedTimeToUnloadFilledUpper = 3*60*1000;//in mulliseconds
uint32_t chamberTick = 0;
bool chamberPostUvEnable = true;
uint32_t chamberPostUvDuration = 1*60*1000;
uint32_t chamberPostUvTick = 0;
uint32_t chamberInverterStartupTick = 0;
int chamberState = WATER_BANK_STATE_NONE;
bool chamberAutomatic = false;
#define CHAMBER_ERROR_NONE 0
#define CHAMBER_ERROR_LOWER_FLOAT_SWITCH 1 //lower switch error
#define CHAMBER_ERROR_UPPER_FLOAT_SWITCH 2 //upper switch error
#define CHAMBER_ERROR_UPPER_FLOAT_SWITCH_OR_NO_WATER 3
#define CHAMBER_ERROR_LOWER_FLOAT_SWITCH_OR_NO_WATER 4
#define CHAMBER_ERROR_STORAGE_LOWER_FLOAT_SWITCH 5 //lower switch error
#define CHAMBER_ERROR_STORAGE_UPPER_FLOAT_SWITCH 6 //upper switch error
#define CHAMBER_ERROR_STORAGE_UPPER_FLOAT_SWITCH_OR_NO_WATER 7
#define CHAMBER_ERROR_STORAGE_LOWER_FLOAT_SWITCH_OR_NO_WATER 8
#define CHAMBER_ERROR_TANK_LOWER_FLOAT_SWITCH 9 //lower switch error
#define CHAMBER_ERROR_TANK_UPPER_FLOAT_SWITCH 10 //upper switch error
#define CHAMBER_ERROR_TANK_UPPER_FLOAT_SWITCH_OR_NO_WATER 11
#define CHAMBER_ERROR_TANK_LOWER_FLOAT_SWITCH_OR_NO_WATER 12
#define CHAMBER_ERROR_LOWER_FLOAT_SWITCH_OR_LEAKED 13
bool chamberError = false;
byte chamberErrorCode = CHAMBER_ERROR_NONE;
String chamberErrorString = "";
//penampungan hasil sterilisasi (tank 3)
bool chamber_SterilizedLowerFloatSwitchStateCurrent = false;
bool chamber_SterilizedLowerFloatSwitchStatePrevious = false;
bool chamber_SterilizedUpperFloatSwitchStateCurrent = false;
bool chamber_SterilizedUpperFloatSwitchStatePrevious = false;
//ruang sterilisasi
bool chamber_SterilizationLowerFloatSwitchStateCurrent = false;
bool chamber_SterilizationLowerFloatSwitchStatePrevious = false;
bool chamber_SterilizationUpperFloatSwitchStateCurrent = false;
bool chamber_SterilizationUpperFloatSwitchStatePrevious = false;
//water tank sebelum sterilisasi
bool chamber_TankLowerFloatSwitchStateCurrent = false;
bool chamber_TankLowerFloatSwitchStatePrevious = false;
bool chamber_TankUpperFloatSwitchStateCurrent = false;
bool chamber_TankUpperFloatSwitchStatePrevious = false;
bool chamberSterilizationSuccess = false;
uint32_t GetSterilizationRemainingMillis(){
uint32_t t = 0;
if (chamberState==CHAMBER_STATE_STERILIZING){
t = (chamberSterilizationOzoneDuration - (millis()-chamberTick));
if (t>chamberSterilizationOzoneDuration) t = 0;
return t;
}
if (GetSwitchState(STERILIZED_UV_SWITCH)){
t = chamberPostUvDuration - (millis()-chamberPostUvTick);
if (t>chamberPostUvDuration) t = 0;
}
return t;
}
bool OnOzoneStrelization(){
if (GetSwitchState(STERILIZATION_OZONE_AND_UV_SWITCH)) return true;
return (chamberState==CHAMBER_STATE_STERILIZING);
}
bool OnPostUV(){
if (GetSwitchState(STERILIZED_UV_SWITCH)){
return true;
}
return false;
}
bool IsChamberFilling(){
return (chamberState>=CHAMBER_STATE_START_FILLING) && (chamberState<=CHAMBER_STATE_CHECK_FILLING_TO_UPPER);
}
bool IsChamberUnload(){
return (chamberState>=CHAMBER_STATE_START_UNLOAD) && (chamberState<=CHAMBER_STATE_START_UNLOAD_TO_UPPER);
}
bool ChamberIsSuccess(){
return chamberSterilizationSuccess;
}
bool ChamberIsError(){
return chamberError;
}
String ChamberErrorString(){
if (chamberError) return chamberErrorString;
return "";
}
bool ChamberIsIdle(){
return (chamberState==CHAMBER_STATE_IDLE) || (chamberState==WATER_BANK_STATE_NONE);
}
bool IsChamberSystemRunning(){
if (GetSwitchState(STERILIZATION_OZONE_AND_UV_SWITCH) || GetSwitchState(INVERTER_SWITCH)) return true;
if (!chamberAutomatic) return (chamberState!=WATER_BANK_STATE_NONE);
return (chamberState>CHAMBER_STATE_IDLE);
}
void ClearChamberError(){
chamberError = false;
chamberErrorCode = CHAMBER_ERROR_NONE;
chamberErrorString = "";
}
void ChamberTurnOffAllSwitch(bool exceptInverterAndPostUv=true){
//make sure ozone, cycling pump, fill in pump, fill out pump, uv1, uv2 is off
//inverter
if (!exceptInverterAndPostUv){
if (GetSwitchState(INVERTER_SWITCH)){
Serial.println("ChamberSystemRoutine: INVERTER_SWITCH OFF");
SetSwitchState(INVERTER_SWITCH, false);
}
}
//ozone
if (GetSwitchState(STERILIZATION_OZONE_AND_UV_SWITCH)){
Serial.println("ChamberSystemRoutine: STERILIZATION_OZONE_AND_UV_SWITCH OFF");
SetSwitchState(STERILIZATION_OZONE_AND_UV_SWITCH, false);
}
//post uv
if (!exceptInverterAndPostUv){
if (GetSwitchState(STERILIZED_UV_SWITCH)){
Serial.println("ChamberSystemRoutine: ");
SetSwitchState(STERILIZED_UV_SWITCH, false);
}
}
//fill in pump
if (GetSwitchState(STERILIZATION_CHAMBER_FILL_PUMP_SWITCH)){
Serial.println("ChamberSystemRoutine: STERILIZATION_CHAMBER_FILL_PUMP_SWITCH OFF");
SetSwitchState(STERILIZATION_CHAMBER_FILL_PUMP_SWITCH, false);
}
//fill out pump
if (GetSwitchState(STERILIZED_CHAMBER_FILL_PUMP_SWITCH)){
Serial.println("ChamberSystemRoutine: STERILIZED_CHAMBER_FILL_PUMP_SWITCH OFF");
SetSwitchState(STERILIZED_CHAMBER_FILL_PUMP_SWITCH, false);
}
//circulation pump
if (GetSwitchState(STERILIZATION_CHAMBER_CIRCULATION_PUMP_SWITCH)){
Serial.println("ChamberSystemRoutine: STERILIZATION_CHAMBER_CIRCULATION_PUMP_SWITCH OFF");
SetSwitchState(STERILIZATION_CHAMBER_CIRCULATION_PUMP_SWITCH, false);
}
}
//system running state =====================================================
void ChamberSystemRoutine(){
//handle the post uv timer independently ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
bool ctrlLowerSwitchState = GetInSwitchState(STERILIZED_CHAMBER_FLOAT_SWITCH_LOWER);
if ((!ctrlLowerSwitchState) || ((GetSwitchState(STERILIZED_UV_SWITCH) && (millis()-chamberPostUvTick)>=chamberPostUvDuration)) || (!chamberPostUvEnable)){
if (GetSwitchState(STERILIZED_UV_SWITCH)){
Serial.println("ChamberSystemRoutine: ");
SetSwitchState(STERILIZED_UV_SWITCH, false);
}
//if ((chamberState==CHAMBER_STATE_NONE) || (chamberState==CHAMBER_STATE_IDLE) || ((chamberState>=CHAMBER_STATE_START_FILLING) && (chamberState<=CHAMBER_STATE_START_FILLING_TO_UPPER))){
if (chamberState==CHAMBER_STATE_NONE){
//ChamberTurnOffAllSwitch(false);//turn off all
}else if ((chamberState!=CHAMBER_STATE_NONE) && (chamberState!=CHAMBER_STATE_STERILIZING)){
SetSwitchState(STERILIZED_UV_SWITCH, false);//turn off post uv
SetSwitchState(INVERTER_SWITCH, false);//turn off inverter
//ChamberTurnOffAllSwitch(false);//turn off all
}
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
switch(chamberState){
case CHAMBER_STATE_NONE:{
ChamberTurnOffAllSwitch(true);
};
break;
case CHAMBER_STATE_IDLE:{
//this is the state when use automatic mode
ChamberTurnOffAllSwitch(true); //except inverter if post uv if running
//in case mismatch, prevent auto monitoring
if (!chamberAutomatic){
chamberState = CHAMBER_STATE_NONE;
Serial.println("CHAMBER_STATE_IDLE -> CHAMBER_STATE_NONE");
}
//trigger condition:
// state => STORAGE_STATE_CHECK_IF_EMPTY: sterilized bank is empty
chamber_SterilizedLowerFloatSwitchStateCurrent = GetInSwitchState(STERILIZED_CHAMBER_FLOAT_SWITCH_LOWER);
bool intoFilling = false;
if ((chamber_SterilizedLowerFloatSwitchStateCurrent!=chamber_SterilizedLowerFloatSwitchStatePrevious) && (!chamber_SterilizedLowerFloatSwitchStateCurrent)){
intoFilling = true;
Serial.println("CHAMBER_STATE_IDLE -> Trigger reason: Storage empty");
}
//storage kosong dan water tank menjadi penuh
chamber_TankLowerFloatSwitchStateCurrent = GetInSwitchState(WATER_BANK_FLOAT_SWITCH_LOWER);
chamber_TankUpperFloatSwitchStateCurrent = GetInSwitchState(WATER_BANK_FLOAT_SWITCH_UPPER);
if (!chamber_SterilizedLowerFloatSwitchStateCurrent && (chamber_TankUpperFloatSwitchStateCurrent!=chamber_TankUpperFloatSwitchStatePrevious) && (chamber_TankUpperFloatSwitchStateCurrent && chamber_TankLowerFloatSwitchStateCurrent)){
intoFilling = true;
//Serial.println("Tank baru penuh dan pemampungan sterilisasi kosong.");
Serial.println("CHAMBER_STATE_IDLE -> Trigger reason: Storage empty and Water Tank just full");
}
//update state
chamber_SterilizedLowerFloatSwitchStatePrevious = chamber_SterilizedLowerFloatSwitchStateCurrent;
chamber_TankLowerFloatSwitchStatePrevious = chamber_TankLowerFloatSwitchStateCurrent;
chamber_TankUpperFloatSwitchStatePrevious = chamber_TankUpperFloatSwitchStateCurrent;
if (intoFilling){
chamberSterilizationSuccess = false;
ClearChamberError();
chamberState = CHAMBER_STATE_START_FILLING;
Serial.println("CHAMBER_STATE_IDLE -> CHAMBER_STATE_START_FILLING");
}
};
break;
case CHAMBER_STATE_START_FILLING:{
chamberSterilizationSuccess = false;
chamber_SterilizedLowerFloatSwitchStateCurrent = GetInSwitchState(STERILIZED_CHAMBER_FLOAT_SWITCH_LOWER);
chamber_SterilizedUpperFloatSwitchStateCurrent = GetInSwitchState(STERILIZED_CHAMBER_FLOAT_SWITCH_UPPER);
//cek error lain dari float switch:
// -> storage switch upper lower harus benar
// -> tank switch upper lower harus benar (water tank penuh)
//cek penampungan harus kosong
if (chamber_SterilizedLowerFloatSwitchStateCurrent){
//switch back to idle
chamberError = true;
chamberErrorString = "Penampungan harus kosong dahulu untuk memulai sterilisasi.";
chamberState = CHAMBER_STATE_IDLE;
Serial.print("Error: ");
Serial.println(chamberErrorString);
Serial.println("CHAMBER_STATE_START_FILLING -> CHAMBER_STATE_IDLE");
return;
}
//check if: full -> CHAMBER_STATE_STERILIZING
// empty -> CHAMBER_STATE_CHECK_FILLING_TO_LOWER
// lower -> CHAMBER_STATE_CHECK_FILLING_TO_UPPER
//jika chamber penuh langsung ke sterilisasi
chamber_SterilizationLowerFloatSwitchStateCurrent = GetInSwitchState(STERILIZATION_CHAMBER_FLOAT_SWITCH_LOWER);
chamber_SterilizationUpperFloatSwitchStateCurrent = GetInSwitchState(STERILIZATION_CHAMBER_FLOAT_SWITCH_UPPER);
if (chamber_SterilizationLowerFloatSwitchStateCurrent && chamber_SterilizationUpperFloatSwitchStateCurrent){
chamberTick = millis();
chamberState = CHAMBER_STATE_STERILIZING;
Serial.println("Sterilization chamber already full.");
Serial.println("CHAMBER_STATE_START_FILLING -> CHAMBER_STATE_STERILIZING");
return;
}
chamber_TankLowerFloatSwitchStateCurrent = GetInSwitchState(WATER_BANK_FLOAT_SWITCH_LOWER);
chamber_TankUpperFloatSwitchStateCurrent = GetInSwitchState(WATER_BANK_FLOAT_SWITCH_UPPER);
Serial.print("Water Tank lower float switch: "); Serial.println(chamber_TankLowerFloatSwitchStateCurrent);
Serial.print("Water Tank upper float switch: "); Serial.println(chamber_TankUpperFloatSwitchStateCurrent);
//tank penampung harus penuh dulu baru mau dong
if (chamber_TankLowerFloatSwitchStateCurrent && chamber_TankUpperFloatSwitchStateCurrent){
//cek jika chamber kosong total
if (!chamber_SterilizationLowerFloatSwitchStateCurrent && !chamber_SterilizationUpperFloatSwitchStateCurrent){
//kosong total
chamberTick = millis();
chamberState = CHAMBER_STATE_CHECK_FILLING_TO_LOWER;
Serial.println("CHAMBER_STATE_START_FILLING -> CHAMBER_STATE_CHECK_FILLING_TO_LOWER");
}else if (chamber_SterilizationLowerFloatSwitchStateCurrent && !chamber_SterilizationUpperFloatSwitchStateCurrent){
//ada sedikit, mayan
chamberTick = millis();
chamberState = CHAMBER_STATE_CHECK_FILLING_TO_UPPER;
Serial.println("CHAMBER_STATE_START_FILLING -> CHAMBER_STATE_CHECK_FILLING_TO_UPPER");
}else{
//float switch error ini hadeh
chamberError = true;
chamberErrorString = "Float switch sterilisasi error.";
chamberState = CHAMBER_STATE_IDLE;
Serial.print("Error: ");
Serial.println(chamberErrorString);
Serial.println("CHAMBER_STATE_START_FILLING -> CHAMBER_STATE_IDLE");
return;
}
}else{
chamberError = true;
chamberErrorString = "Tangki penampung air harus penuh dahulu untuk dapat memulai pengisian tangki sterilisasi.";
Serial.print("Error: ");
Serial.println(chamberErrorString);
if (chamberAutomatic){
chamberState = CHAMBER_STATE_IDLE;
Serial.println("CHAMBER_STATE_START_FILLING -> CHAMBER_STATE_IDLE");
}else{
chamberState = CHAMBER_STATE_NONE;
Serial.println("CHAMBER_STATE_START_FILLING -> CHAMBER_STATE_NONE");
}
return;
}
};
break;
case CHAMBER_STATE_CHECK_FILLING_TO_LOWER:{
//waiting for filling into lower level, or stop when timeout
//switch on the pump
if (!GetSwitchState(STERILIZATION_CHAMBER_FILL_PUMP_SWITCH)){
Serial.println("ChamberSystemRoutine: TURN ON FILL PUMP SWITCH");
SetSwitchState(STERILIZATION_CHAMBER_FILL_PUMP_SWITCH, true);
}
//monitoring water level or timed out
bool lowerSwitchState = GetInSwitchState(STERILIZATION_CHAMBER_FLOAT_SWITCH_LOWER);
bool upperSwitchState = GetInSwitchState(STERILIZATION_CHAMBER_FLOAT_SWITCH_UPPER);
//chamber_TankLowerFloatSwitchStateCurrent = GetInSwitchState(WATER_BANK_FLOAT_SWITCH_LOWER);
//if (lowerSwitchState || upperSwitchState || !chamber_TankLowerFloatSwitchStateCurrent){
if (lowerSwitchState || upperSwitchState){
//check if lower float switch on
//try to fill till full
ClearChamberError();
chamberTick = millis();
chamberState = CHAMBER_STATE_CHECK_FILLING_TO_UPPER;
Serial.println("CHAMBER_STATE_CHECK_FILLING_TO_LOWER -> CHAMBER_STATE_CHECK_FILLING_TO_UPPER");
}else{
//check if timed out
if (millis()-chamberTick>=chamberExpectedTimeToFilledLower){
chamberError = true;
chamberErrorCode = CHAMBER_ERROR_LOWER_FLOAT_SWITCH;
chamberErrorString = "Tank 2 insufficient input water, or lower float switch error, or input pump error.";
Serial.println("Chamber Error -> CHAMBER_ERROR_LOWER_FLOAT_SWITCH");
if (chamberAutomatic){
chamberState = CHAMBER_STATE_IDLE;
Serial.println("CHAMBER_STATE_CHECK_FILLING_TO_LOWER -> CHAMBER_STATE_IDLE");
}else{
chamberState = CHAMBER_STATE_NONE;
Serial.println("CHAMBER_STATE_CHECK_FILLING_TO_LOWER -> CHAMBER_STATE_NONE");
}
}
}
};
break;
case CHAMBER_STATE_CHECK_FILLING_TO_UPPER:{
//waiting for filling into lower level, or stop when timeout
//switch on the pump
if (!GetSwitchState(STERILIZATION_CHAMBER_FILL_PUMP_SWITCH)){
Serial.println("ChamberSystemRoutine: TURN ON FILL PUMP SWITCH");
SetSwitchState(STERILIZATION_CHAMBER_FILL_PUMP_SWITCH, true);
}
//monitoring water level or timed out
bool upperSwitchState = GetInSwitchState(STERILIZATION_CHAMBER_FLOAT_SWITCH_UPPER);
chamber_TankLowerFloatSwitchStateCurrent = GetInSwitchState(WATER_BANK_FLOAT_SWITCH_LOWER);
if (upperSwitchState || (!chamber_TankLowerFloatSwitchStateCurrent)){
//check if upper float switch on or chamber's water below switch
SetSwitchState(STERILIZATION_CHAMBER_FILL_PUMP_SWITCH, false); //matiin pompa
ClearChamberError();
chamberTick = millis();
chamberState = CHAMBER_STATE_STERILIZING;
Serial.println("CHAMBER_STATE_CHECK_FILLING_TO_UPPER -> CHAMBER_STATE_STERILIZING");
}else{
//check if timed out
if (millis()-chamberTick>=chamberExpectedTimeToFilledUpper){
SetSwitchState(STERILIZATION_CHAMBER_FILL_PUMP_SWITCH, false); //matiin pompa
chamberError = true;
chamberErrorCode = CHAMBER_ERROR_UPPER_FLOAT_SWITCH;
chamberErrorString = "Tank 2 insufficient input water, or upper float switch error, or input pump error.";
Serial.println("Chamber Error -> CHAMBER_ERROR_UPPER_FLOAT_SWITCH");
if (chamberAutomatic){
chamberState = CHAMBER_STATE_IDLE;
Serial.println("CHAMBER_STATE_CHECK_FILLING_TO_UPPER -> CHAMBER_STATE_IDLE");
}else{
chamberState = CHAMBER_STATE_NONE;
Serial.println("CHAMBER_STATE_CHECK_FILLING_TO_UPPER -> CHAMBER_STATE_NONE");
}
}
}
};
break;
case CHAMBER_STATE_STERILIZING:{
//make sure turn off filling in and filling out pump
if (GetSwitchState(STERILIZATION_CHAMBER_FILL_PUMP_SWITCH)) SetSwitchState(STERILIZATION_CHAMBER_FILL_PUMP_SWITCH, false);
if (GetSwitchState(STERILIZED_CHAMBER_FILL_PUMP_SWITCH)) SetSwitchState(STERILIZED_CHAMBER_FILL_PUMP_SWITCH, false);
//if inverter not turned -> switch it first, but wait for 2 seconds
if (!GetSwitchState(INVERTER_SWITCH)){
//make sure turn off ozone and circulation
if (GetSwitchState(STERILIZATION_OZONE_AND_UV_SWITCH)) SetSwitchState(STERILIZATION_OZONE_AND_UV_SWITCH, false);
if (GetSwitchState(STERILIZATION_CHAMBER_CIRCULATION_PUMP_SWITCH)) SetSwitchState(STERILIZATION_CHAMBER_CIRCULATION_PUMP_SWITCH, false);
chamberInverterStartupTick = millis();
SetSwitchState(INVERTER_SWITCH, true);
Serial.println("Starting up the inveter.");
return;
}else{
if ((millis()-chamberInverterStartupTick)<2000){
chamberTick = millis(); //keep reset it
return;
}
}
//turn on ozone and circulation pump
if (!GetSwitchState(STERILIZATION_OZONE_AND_UV_SWITCH)) SetSwitchState(STERILIZATION_OZONE_AND_UV_SWITCH, true);
if (!GetSwitchState(STERILIZATION_CHAMBER_CIRCULATION_PUMP_SWITCH)) SetSwitchState(STERILIZATION_CHAMBER_CIRCULATION_PUMP_SWITCH, true);
//wait till done, or if water is empty by accident (leaking) => back to idle with error state
bool lowerSwitchState = GetInSwitchState(STERILIZATION_CHAMBER_FLOAT_SWITCH_LOWER);
if (!lowerSwitchState){
//wah error atau bocor nih
ChamberTurnOffAllSwitch(false); //turn off all switch including inverter
chamberError = true;
chamberErrorCode = CHAMBER_ERROR_LOWER_FLOAT_SWITCH_OR_LEAKED;
chamberErrorString = "Tank 2 leaked, or lower float switch error";
Serial.print("Error: ");
Serial.println(chamberErrorString);;
chamberState = CHAMBER_STATE_NONE;
Serial.println("CHAMBER_STATE_STERILIZING -> CHAMBER_STATE_NONE");
return;
}
//tunggu hingga selesai sterilisasi
if ((millis()-chamberTick)>=chamberSterilizationOzoneDuration){
chamberPostUvTick = millis();//reset uv post tick too
bool keepInverter = chamberPostUvEnable && (chamberPostUvDuration>0);
ChamberTurnOffAllSwitch(keepInverter);
chamberTick = millis(); //keep reset it
chamberState = CHAMBER_STATE_START_UNLOAD;
Serial.println("CHAMBER_STATE_STERILIZING -> CHAMBER_STATE_START_UNLOAD");
}
};
break;
case CHAMBER_STATE_START_UNLOAD:{
bool lowerSwitchState = GetInSwitchState(STERILIZED_CHAMBER_FLOAT_SWITCH_LOWER);
bool upperSwitchState = GetInSwitchState(STERILIZED_CHAMBER_FLOAT_SWITCH_LOWER);
chamberTick = millis(); //reset it
if (!lowerSwitchState && !upperSwitchState){
//fill to lower first
chamberState = CHAMBER_STATE_START_UNLOAD_TO_LOWER;
Serial.println("CHAMBER_STATE_START_UNLOAD -> CHAMBER_STATE_START_UNLOAD_TO_LOWER");
}else if (lowerSwitchState && !upperSwitchState){
//fill to upper
chamberState = CHAMBER_STATE_START_UNLOAD_TO_UPPER;
Serial.println("CHAMBER_STATE_START_UNLOAD -> CHAMBER_STATE_START_UNLOAD_TO_UPPER");
}else if (lowerSwitchState && upperSwitchState){
//already full
ChamberTurnOffAllSwitch(false); //turn off all switch including inverter
if (chamberAutomatic){
chamberState = CHAMBER_STATE_IDLE;
Serial.println("CHAMBER_STATE_START_UNLOAD -> CHAMBER_STATE_IDLE");
}else{
chamberState = CHAMBER_STATE_NONE;
Serial.println("CHAMBER_STATE_START_UNLOAD -> CHAMBER_STATE_NONE");
}
chamberSterilizationSuccess = true;
}else{
//float switch error
ChamberTurnOffAllSwitch(false); //turn off all switch including inverter
chamberError = true;
chamberErrorCode = CHAMBER_ERROR_STORAGE_LOWER_FLOAT_SWITCH;
chamberErrorString = "Tank 3 float switch error";
Serial.print("Error: ");
Serial.println(chamberErrorString);;
chamberState = CHAMBER_STATE_NONE;
Serial.println("CHAMBER_STATE_START_UNLOAD -> CHAMBER_STATE_NONE");
return;
}
//switch post uv when it reach at least lowest level and used
if (chamberPostUvEnable && lowerSwitchState){
chamberPostUvTick = millis();//reset uv post tick too
if (chamberPostUvEnable && (chamberPostUvDuration>0)){
//turn on uv
SetSwitchState(STERILIZED_UV_SWITCH, true);
}
}
};
break;
case CHAMBER_STATE_START_UNLOAD_TO_LOWER:{
//switch on the pump
if (!GetSwitchState(STERILIZED_CHAMBER_FILL_PUMP_SWITCH)){
Serial.println("ChamberSystemRoutine: TURN ON OUT PUMP SWITCH");
SetSwitchState(STERILIZED_CHAMBER_FILL_PUMP_SWITCH, true);
}
bool lowerSwitchState = GetInSwitchState(STERILIZED_CHAMBER_FLOAT_SWITCH_LOWER);
bool upperSwitchState = GetInSwitchState(STERILIZED_CHAMBER_FLOAT_SWITCH_UPPER);
if (lowerSwitchState || upperSwitchState){
//check if lower float switch on
//try to fill till full
ClearChamberError();
chamberTick = millis();
chamberState = CHAMBER_STATE_START_UNLOAD_TO_UPPER;
Serial.println("CHAMBER_STATE_START_UNLOAD_TO_LOWER -> CHAMBER_STATE_START_UNLOAD_TO_UPPER");
}else{
//check if timed out
if (millis()-chamberTick>=chamberExpectedTimeToUnloadFilledLower){
ChamberTurnOffAllSwitch(false); //turn off all switch including inverter
chamberError = true;
chamberErrorCode = CHAMBER_ERROR_STORAGE_LOWER_FLOAT_SWITCH_OR_NO_WATER;
chamberErrorString = "Tank 2 leaked, or Tank 3 lower float switch error, or output pump error.";
Serial.println("Chamber Error -> CHAMBER_ERROR_STORAGE_LOWER_FLOAT_SWITCH_OR_NO_WATER");
if (chamberAutomatic){
chamberState = CHAMBER_STATE_IDLE;
Serial.println("CHAMBER_STATE_CHECK_FILLING_TO_LOWER -> CHAMBER_STATE_IDLE");
}else{
chamberState = CHAMBER_STATE_NONE;
Serial.println("CHAMBER_STATE_CHECK_FILLING_TO_LOWER -> CHAMBER_STATE_NONE");
}
return;
}
//check if tank 2 empty. indicate leaking
chamber_SterilizationLowerFloatSwitchStateCurrent = GetInSwitchState(STERILIZATION_CHAMBER_FLOAT_SWITCH_LOWER);
if (!chamber_SterilizationLowerFloatSwitchStateCurrent){
ChamberTurnOffAllSwitch(false); //turn off all switch including inverter
chamberError = true;
chamberErrorCode = CHAMBER_ERROR_LOWER_FLOAT_SWITCH;
chamberErrorString = "Tank 2 leaked or lower float switch error";
Serial.println("Chamber Error -> CHAMBER_ERROR_LOWER_FLOAT_SWITCH");
if (chamberAutomatic){
chamberState = CHAMBER_STATE_IDLE;
Serial.println("CHAMBER_STATE_CHECK_FILLING_TO_LOWER -> CHAMBER_STATE_IDLE");
}else{
chamberState = CHAMBER_STATE_NONE;
Serial.println("CHAMBER_STATE_CHECK_FILLING_TO_LOWER -> CHAMBER_STATE_NONE");
}
return;
}
}
//manage post uv
if (chamberPostUvEnable && lowerSwitchState){
chamberPostUvTick = millis();//reset uv post tick too
if (chamberPostUvEnable && (chamberPostUvDuration>0)){
//turn on uv
SetSwitchState(STERILIZED_UV_SWITCH, true);
}
}
};
break;
case CHAMBER_STATE_START_UNLOAD_TO_UPPER:{
//switch on the pump
if (!GetSwitchState(STERILIZED_CHAMBER_FILL_PUMP_SWITCH)){
Serial.println("ChamberSystemRoutine: TURN ON OUT PUMP SWITCH");
SetSwitchState(STERILIZED_CHAMBER_FILL_PUMP_SWITCH, true);
}
//manage post uv
bool lowerSwitchState = GetInSwitchState(STERILIZED_CHAMBER_FLOAT_SWITCH_LOWER);
if (chamberPostUvEnable && lowerSwitchState){
if (chamberPostUvEnable && (chamberPostUvDuration>0) && (!GetSwitchState(STERILIZED_UV_SWITCH) && (millis()-chamberPostUvTick<chamberPostUvDuration))){
//turn on uv but do not update the timer
SetSwitchState(STERILIZED_UV_SWITCH, true);
}
}
//bool lowerSwitchState = GetInSwitchState(STERILIZED_CHAMBER_FLOAT_SWITCH_LOWER);
bool upperSwitchState = GetInSwitchState(STERILIZED_CHAMBER_FLOAT_SWITCH_UPPER);
chamber_SterilizationLowerFloatSwitchStateCurrent = GetInSwitchState(STERILIZATION_CHAMBER_FLOAT_SWITCH_LOWER);
if (upperSwitchState || !chamber_SterilizationLowerFloatSwitchStateCurrent){
chamberSterilizationSuccess = false; //done!
ChamberTurnOffAllSwitch(true); //turn off all switch excluding inverter
ClearChamberError();
chamberTick = millis();
if (chamberAutomatic){
chamberState = CHAMBER_STATE_IDLE;
Serial.println("CHAMBER_STATE_START_UNLOAD_TO_UPPER -> CHAMBER_STATE_IDLE");
}else{
chamberState = CHAMBER_STATE_NONE;
Serial.println("CHAMBER_STATE_START_UNLOAD_TO_UPPER -> CHAMBER_STATE_NONE");
}
}else{
//check if timed out
if (millis()-chamberTick>=chamberExpectedTimeToUnloadFilledUpper){
ChamberTurnOffAllSwitch(false); //turn off all switch including inverter
chamberError = true;
chamberErrorCode = CHAMBER_ERROR_STORAGE_UPPER_FLOAT_SWITCH_OR_NO_WATER;
chamberErrorString = "Tank 2 leaked, or Tank 1 upper float switch error, or output pump error.";
Serial.println("Chamber Error -> CHAMBER_ERROR_STORAGE_UPPER_FLOAT_SWITCH_OR_NO_WATER");
if (chamberAutomatic){
chamberState = CHAMBER_STATE_IDLE;
Serial.println("CHAMBER_STATE_CHECK_FILLING_TO_UPPER -> CHAMBER_STATE_IDLE");
}else{
chamberState = CHAMBER_STATE_NONE;
Serial.println("CHAMBER_STATE_CHECK_FILLING_TO_UPPER -> CHAMBER_STATE_NONE");
}
return;
}
}
};
break;
}
}
//==========================================================================
void StartChamberSystem(bool automatic, bool forceRunIfAutomatic = false, bool usePostUv=false){
ClearChamberError();
chamberAutomatic = automatic;
chamberPostUvEnable = usePostUv;
chamberSterilizationSuccess = false;
bool runNow = false;
if (chamberAutomatic){
RefreshInSwitchState();
chamberState = CHAMBER_STATE_IDLE;
runNow = forceRunIfAutomatic;
if (!runNow) Serial.println("StartChamberSystem -> CHAMBER_STATE_IDLE");
}else{
runNow = true;
}
if (runNow){
RefreshInSwitchState();
chamberState = CHAMBER_STATE_START_FILLING;
Serial.println("StartChamberSystem -> CHAMBER_STATE_START_FILLING");
}
}
void StopChamberSystem(){
ClearChamberError();
chamberState = CHAMBER_STATE_NONE;
ChamberSystemRoutine(); //call it once
ChamberTurnOffAllSwitch(false);//make sure to turn off all
}
void ChamberSetPostUvDuration(uint32_t v){
chamberPostUvDuration = v;
}
void ChamberSetSterlizationDuration(uint32_t v){
chamberSterilizationOzoneDuration = v;
}

131
SwitchHandler.h Normal file
View File

@ -0,0 +1,131 @@
#define SWITCH_NUM 8
#define SWITCH_INVERT true
#define PINOUT_WATER_BANK_FILL_PUMP_SWITCH 1
#define PINOUT_STERILIZATION_CHAMBER_FILL_PUMP_SWITCH 3
#define PINOUT_STERILIZATION_CHAMBER_CIRCULATION_PUMP_SWITCH 17
#define PINOUT_STERILIZED_CHAMBER_FILL_PUMP_SWITCH 16
#define PINOUT_STERILIZATION_OZONE_AND_UV_SWITCH 4
#define PINOUT_STERILIZED_UV_SWITCH 0
#define PINOUT_INVERTER_SWITCH 2
#define PINOUT_INSTRUMENTATION_SWITCH 15
static byte SWITCH_OFF = LOW;
static byte SWITCH_ON = HIGH;
static byte switchState[SWITCH_NUM];
static int8_t switchPin[SWITCH_NUM] = {PINOUT_WATER_BANK_FILL_PUMP_SWITCH,
PINOUT_STERILIZATION_CHAMBER_FILL_PUMP_SWITCH,
PINOUT_STERILIZATION_CHAMBER_CIRCULATION_PUMP_SWITCH,
PINOUT_STERILIZED_CHAMBER_FILL_PUMP_SWITCH,
PINOUT_STERILIZATION_OZONE_AND_UV_SWITCH,
PINOUT_STERILIZED_UV_SWITCH,
PINOUT_INVERTER_SWITCH,
PINOUT_INSTRUMENTATION_SWITCH}; //set -1 to mark as unused
//old code
//#define WATER_BANK_FILL_PUMP_SWITCH 0
//#define STERILIZATION_CHAMBER_FILL_PUMP_SWITCH 1
//#define STERILIZATION_CHAMBER_CIRCULATION_PUMP_SWITCH 2
//#define STERILIZED_CHAMBER_FILL_PUMP_SWITCH 3
//#define STERILIZATION_OZONE_AND_UV_SWITCH 4
//#define STERILIZED_UV_SWITCH 5 //aka post uv switch
//#define INVERTER_SWITCH 6
//#define INSTRUMENTATION_SWITCH 7 //for sensors and display power
#define WATER_BANK_FILL_PUMP_SWITCH 0
#define STERILIZATION_CHAMBER_FILL_PUMP_SWITCH 1
#define STERILIZATION_CHAMBER_CIRCULATION_PUMP_SWITCH 2
#define STERILIZED_CHAMBER_FILL_PUMP_SWITCH 3
#define STERILIZATION_OZONE_AND_UV_SWITCH 4
#define STERILIZED_UV_SWITCH 5 //aka post uv switch
#define INSTRUMENTATION_SWITCH 6 //for sensors and display power
#define INVERTER_SWITCH 7
void TurnOffAllRelay(){
for (int i=0; i<SWITCH_NUM; i++){
if (switchPin[i]>=0){
digitalWrite(switchPin[i], SWITCH_OFF);
}
switchState[i] = SWITCH_OFF;
}
}
void InitSwitchHandler(){
if (!SWITCH_INVERT){
SWITCH_OFF = LOW;
SWITCH_ON = HIGH;
}else{
SWITCH_OFF = HIGH;
SWITCH_ON = LOW;
}
Serial.println("switch init");
for (int i=0; i<SWITCH_NUM; i++){
if (switchPin[i]>=0){
pinMode(switchPin[i], OUTPUT);
digitalWrite(switchPin[i], SWITCH_OFF);
}
switchState[i] = SWITCH_OFF;
}
}
void checkRelay(){
for(int i=0; i < SWITCH_NUM; i++){
digitalWrite(PINOUT_WATER_BANK_FILL_PUMP_SWITCH, SWITCH_OFF);
delay(1000);
}
for(int i=0; i<SWITCH_NUM;i++){
digitalWrite(PINOUT_WATER_BANK_FILL_PUMP_SWITCH, SWITCH_ON);
delay(1000);
}
// digitalWrite(PINOUT_WATER_BANK_FILL_PUMP_SWITCH, SWITCH_OFF);
// digitalWrite(PINOUT_STERILIZED_CHAMBER_FILL_PUMP_SWITCH, SWITCH_ON);
// delay(1000);
// digitalWrite(PINOUT_WATER_BANK_FILL_PUMP_SWITCH, SWITCH_ON);
// digitalWrite(PINOUT_STERILIZED_CHAMBER_FILL_PUMP_SWITCH, SWITCH_OFF);
// delay(1000);
// for (int i=0; i<SWITCH_NUM; i++){
// Serial.println(switchPin[i]);
// // digitalWrite(switchPin[i], SWITCH_ON);
// // delay(1000);
// digitalWrite(switchPin[i], SWITCH_OFF);
// delay(350);
// }
}
bool GetSwitchState(int idx){
if (idx<0 || idx>=SWITCH_NUM) return false;
return (switchState[idx]==SWITCH_ON?true:false);
}
void SetSwitchState(int idx, bool v){
if (idx<0 || idx>=SWITCH_NUM) return;
byte state = v?SWITCH_ON:SWITCH_OFF;
if (switchPin[idx]>=0) digitalWrite(switchPin[idx], state);
switchState[idx] = state;
}
//used for unit test
void SetSwitchStateSimulated(int idx, bool v){
if (idx<0 || idx>=SWITCH_NUM) return;
byte state = v?SWITCH_ON:SWITCH_OFF;
switchState[idx] = state;
}
uint32_t swDemoT = 0;
int swx = 0;
void SwitchDemo(){
if (millis()-swDemoT<1000) return;
swDemoT = millis();
for(int i=0;i<SWITCH_NUM;i++){
SetSwitchState(i,i==swx?true:false);
}
swx++;
if (swx>=SWITCH_NUM) swx = 0;
}

311
WaterBankHandler.h Normal file
View File

@ -0,0 +1,311 @@
//Water Bank => Tank 3, used for unsterilized water storage
// -> Handle the Refill Water Bank Pump
// -> Dependency: water bank's float switch Low and High
//STERILIZATION Bank => Tank 2, used for sterilizing the water
//PURED Bank => Tank 3, used for store sterilized water
#define WATER_BANK_STATE_NONE 0
#define WATER_BANK_STATE_IDLE 1
#define WATER_BANK_STATE_START_FILLING 2
#define WATER_BANK_STATE_CHECK_FILLING_TO_LOWER 3
#define WATER_BANK_STATE_CHECK_FILLING_TO_UPPER 4
uint32_t waterBankExpectedTimeToFilledLower = 1*60*1000;//in mulliseconds
uint32_t waterBankExpectedTimeToFilledUpper = 3*60*1000;//in mulliseconds
uint32_t waterBankTick = 0;
bool waterBankSterilizationFloatSwitchStatePrevious = false;
bool waterBankSterilizationFloatSwitchStateCurrent = false;
bool waterBankSterilizedFloatSwitchStatePrevious = false;
bool waterBankSterilizedFloatSwitchStateCurrent = false;
bool waterBankFloatSwitchStatePrevious = false;
bool waterBankFloatSwitchStateCurrent = false;
bool waterBankSterilizationUpperFloatSwitchStatePrevious = false;
bool waterBankSterilizationUpperFloatSwitchStateCurrent = false;
//bool waterBankSystemIsRunning = false;//if true, water bank is starting once
//bool waterBankSystemIsAutomatic = false; //if true, it will keep check float switch for water refill
int waterBankState = WATER_BANK_STATE_NONE;
bool waterBankAutomatic = false;
#define WATER_BANK_ERROR_NONE 0
#define WATER_BANK_ERROR_LOWER_FLOAT_SWITCH 1 //lower switch error
#define WATER_BANK_ERROR_UPPER_FLOAT_SWITCH 2 //upper switch error
#define WATER_BANK_ERROR_UPPER_FLOAT_SWITCH_OR_NO_WATER 3
#define WATER_BANK_ERROR_LOWER_FLOAT_SWITCH_OR_NO_WATER 4
bool waterBankError = false;
byte waterBankErrorCode = WATER_BANK_ERROR_NONE;
String waterBankErrorString = "";
bool IsWaterBankError(){
return waterBankError;
}
String WaterBankErrorString(){
if (waterBankError) return waterBankErrorString;
return "";
}
bool IsWaterBankSystemRunning(){
if (!waterBankAutomatic) return (waterBankState!=WATER_BANK_STATE_NONE);
return (waterBankState>WATER_BANK_STATE_IDLE);
}
void ClearWaterBankError(){
waterBankError = false;
waterBankErrorCode = WATER_BANK_ERROR_NONE;
waterBankErrorString = "";
}
//system running state =====================================================
void WaterBankSystemRoutine(){
switch(waterBankState){
case WATER_BANK_STATE_NONE:{
//do nothing but make sure the pump is off
if (GetSwitchState(WATER_BANK_FILL_PUMP_SWITCH)){
Serial.println("WaterBank: TURN OFF PUMP SWITCH");
SetSwitchState(WATER_BANK_FILL_PUMP_SWITCH, false);
}
};
break;
case WATER_BANK_STATE_IDLE:{
if (waterBankAutomatic){
//make sure the pump is off first
if (GetSwitchState(WATER_BANK_FILL_PUMP_SWITCH)){
Serial.println("WaterBank: TURN OFF PUMP SWITCH");
SetSwitchState(WATER_BANK_FILL_PUMP_SWITCH, false);
}
//trigger condition:
// state => WATER_BANK_STATE_CHECK_IF_EMPTY: sterilization bank is empty or sterilized bank is empty or water bank is empty
waterBankFloatSwitchStateCurrent = GetInSwitchState(WATER_BANK_FLOAT_SWITCH_LOWER);
waterBankSterilizationFloatSwitchStateCurrent = GetInSwitchState(STERILIZATION_CHAMBER_FLOAT_SWITCH_LOWER);
waterBankSterilizedFloatSwitchStateCurrent = GetInSwitchState(STERILIZED_CHAMBER_FLOAT_SWITCH_LOWER);
waterBankSterilizationUpperFloatSwitchStateCurrent = GetInSwitchState(STERILIZATION_CHAMBER_FLOAT_SWITCH_UPPER);
bool intoFillingWaterBank = false;
//check if water bank empty
if ((waterBankFloatSwitchStateCurrent!=waterBankFloatSwitchStatePrevious) && (!waterBankFloatSwitchStatePrevious)){
intoFillingWaterBank = true;
Serial.println("WaterBank -> Trigger reason: Water Bank empty");
}
//check if sterilization chamber empty
if ((waterBankSterilizationFloatSwitchStateCurrent!=waterBankSterilizationFloatSwitchStatePrevious) && (!waterBankSterilizationFloatSwitchStateCurrent)){
intoFillingWaterBank = true;
Serial.println("WaterBank -> Trigger reason: Sterilization chamber empty");
}
//check if sterilized chamber empty
if ((waterBankSterilizedFloatSwitchStateCurrent!=waterBankSterilizedFloatSwitchStatePrevious) && (!waterBankSterilizedFloatSwitchStatePrevious)){
intoFillingWaterBank = true;
Serial.println("WaterBank -> Trigger reason: Sterilized chamber empty");
}
//check if sterilization chamber just full lower switch 1, and upper switch from 1 to 0 (indicate begin to unload)
if (waterBankSterilizationFloatSwitchStateCurrent && (waterBankSterilizationUpperFloatSwitchStateCurrent!=waterBankSterilizationUpperFloatSwitchStatePrevious) && (!waterBankSterilizationUpperFloatSwitchStateCurrent)){
intoFillingWaterBank = true;
Serial.println("WaterBank -> Trigger reason: Sterilized chamber unload");
}
//update state
waterBankFloatSwitchStatePrevious = waterBankFloatSwitchStateCurrent;
waterBankSterilizationFloatSwitchStatePrevious = waterBankSterilizationFloatSwitchStateCurrent;
waterBankSterilizedFloatSwitchStatePrevious = waterBankSterilizedFloatSwitchStateCurrent;
waterBankSterilizationUpperFloatSwitchStatePrevious = waterBankSterilizationUpperFloatSwitchStateCurrent;
if (intoFillingWaterBank){
ClearWaterBankError();
waterBankState = WATER_BANK_STATE_START_FILLING;
Serial.println("WaterBank -> WATER_BANK_STATE_START_FILLING");
}
}else{
waterBankState = WATER_BANK_STATE_NONE;
Serial.println("WaterBank -> WATER_BANK_STATE_NONE");
}
};
break;
case WATER_BANK_STATE_START_FILLING:{
//call to this state to check if water is fully empty or lower level is filled
bool lowerSwitchState = GetInSwitchState(WATER_BANK_FLOAT_SWITCH_LOWER);
bool upperSwitchState = GetInSwitchState(WATER_BANK_FLOAT_SWITCH_UPPER);
if (lowerSwitchState && (!upperSwitchState)){
//try to fill till full
ClearWaterBankError();
waterBankTick = millis();
waterBankState = WATER_BANK_STATE_CHECK_FILLING_TO_UPPER;
Serial.println("WaterBank -> WATER_BANK_STATE_CHECK_FILLING_TO_UPPER");
}else if ((!lowerSwitchState) && (!upperSwitchState)){
//try to fill to lower first
ClearWaterBankError();
waterBankTick = millis();
waterBankState = WATER_BANK_STATE_CHECK_FILLING_TO_LOWER;
Serial.println("WaterBank -> WATER_BANK_STATE_CHECK_FILLING_TO_LOWER");
}else if (!lowerSwitchState && upperSwitchState){
//float switch error! Cancel the action
waterBankError = true;
waterBankErrorCode = WATER_BANK_ERROR_LOWER_FLOAT_SWITCH;
//waterBankErrorString = "Tank 1 lower float switch error.";
waterBankErrorString = "Tangki 3 float switch bawah error.";
Serial.println("WaterBank Error -> WATER_BANK_ERROR_LOWER_FLOAT_SWITCH");
if (waterBankAutomatic){
waterBankState = WATER_BANK_STATE_IDLE;
Serial.println("WaterBank -> WATER_BANK_STATE_IDLE");
}else{
waterBankState = WATER_BANK_STATE_NONE;
Serial.println("WaterBank -> WATER_BANK_STATE_NONE");
}
}
};
break;
case WATER_BANK_STATE_CHECK_FILLING_TO_LOWER:{
//waiting for filling into lower level, or stop when timeout
//switch on the pump
if (!GetSwitchState(WATER_BANK_FILL_PUMP_SWITCH)){
Serial.println("WaterBank: TURN ON PUMP SWITCH");
SetSwitchState(WATER_BANK_FILL_PUMP_SWITCH, true);
}
bool lowerSwitchState = GetInSwitchState(WATER_BANK_FLOAT_SWITCH_LOWER);
bool upperSwitchState = GetInSwitchState(WATER_BANK_FLOAT_SWITCH_UPPER);
if (lowerSwitchState || upperSwitchState){
//check if lower float switch on
//try to fill till full
ClearWaterBankError();
waterBankTick = millis();
waterBankState = WATER_BANK_STATE_CHECK_FILLING_TO_UPPER;
Serial.println("WaterBank -> WATER_BANK_STATE_CHECK_FILLING_TO_UPPER");
}else{
//check if timed out
if (millis()-waterBankTick>=waterBankExpectedTimeToFilledLower){
//error
waterBankError = true;
waterBankErrorCode = WATER_BANK_ERROR_LOWER_FLOAT_SWITCH_OR_NO_WATER;
//waterBankErrorString = "Tank 1 insufficient input water, or lower float switch error, or input pump error.";
waterBankErrorString = "Tidak ada air untuk mengisi tangki 3, atau float switch error, atau pompa rusak.";
Serial.println("WaterBank Error -> WATER_BANK_ERROR_LOWER_FLOAT_SWITCH");
if (waterBankAutomatic){
waterBankState = WATER_BANK_STATE_IDLE;
Serial.println("WaterBank -> WATER_BANK_STATE_IDLE");
}else{
waterBankState = WATER_BANK_STATE_NONE;
Serial.println("WaterBank -> WATER_BANK_STATE_NONE");
}
}
}
};
break;
case WATER_BANK_STATE_CHECK_FILLING_TO_UPPER:{
//waiting for filling into upper level, or stop when timeout
//switch on the pump
if (!GetSwitchState(WATER_BANK_FILL_PUMP_SWITCH)){
Serial.println("WaterBank: TURN ON PUMP SWITCH");
SetSwitchState(WATER_BANK_FILL_PUMP_SWITCH, true);
}
bool upperSwitchState = GetInSwitchState(WATER_BANK_FLOAT_SWITCH_UPPER);
bool stopFill = false;
if (upperSwitchState){
//check if upper float switch on
ClearWaterBankError();
stopFill = true;
Serial.println("WaterBank -> Full");
}else{
//check if timed out
if (millis()-waterBankTick>=waterBankExpectedTimeToFilledUpper){
//error
stopFill = true;
waterBankError = true;
waterBankErrorCode = WATER_BANK_ERROR_UPPER_FLOAT_SWITCH_OR_NO_WATER;
waterBankErrorString = "Tank 3 insufficient input water, or upper float switch error, or input pump error.";
Serial.println("WaterBank Error -> WATER_BANK_ERROR_UPPER_FLOAT_SWITCH_OR_NO_WATER");
}
}
if (stopFill){
SetSwitchState(WATER_BANK_FILL_PUMP_SWITCH, false); //matiin pompa
if (waterBankAutomatic){
waterBankState = WATER_BANK_STATE_IDLE;
Serial.println("WaterBank -> WATER_BANK_STATE_IDLE");
}else{
waterBankState = WATER_BANK_STATE_NONE;
Serial.println("WaterBank -> WATER_BANK_STATE_NONE");
}
}
};
break;
}
}
void StartWaterBankSystem(bool automatic, bool forceRunIfAutomatic = false){
RefreshInSwitchState();
ClearWaterBankError();
waterBankAutomatic = automatic;
bool runNow = false;
if (waterBankAutomatic){
waterBankState = WATER_BANK_STATE_IDLE;
runNow = forceRunIfAutomatic;
if (!runNow) Serial.println("StartWaterBankSystem -> WATER_BANK_STATE_IDLE");
}else{
runNow = true;
}
if (runNow){
waterBankState = WATER_BANK_STATE_START_FILLING;
Serial.println("StartWaterBankSystem -> WATER_BANK_STATE_START_FILLING");
}
}
void StopWaterBankSystem(){
waterBankState = WATER_BANK_STATE_NONE;
ClearWaterBankError();
WaterBankSystemRoutine(); //call it once to stop the switch
}
//=========================================================================
//Test unit for WaterBankSystemRoutine
bool WaterBankTestUnit(){
bool testA = false;
bool testB = false;
bool testC = false;
bool testD = false;
bool testE = false;
//normal test with automatic mode
ClearInSwitch(); //clear floating switch state
StopWaterBankSystem();
StartWaterBankSystem(true, false);
WaterBankSystemRoutine();
testA = waterBankState==WATER_BANK_STATE_IDLE;
Serial.println("Test idle state " + (String) (testA?"OK":"FAIL"));
//simulate float switching
SetInSwitchState(WATER_BANK_FLOAT_SWITCH_LOWER, true);
WaterBankSystemRoutine();
SetInSwitchState(WATER_BANK_FLOAT_SWITCH_LOWER, false); //simulate trigger to fill water
WaterBankSystemRoutine();
testB = (waterBankState==WATER_BANK_STATE_START_FILLING) || (waterBankState==WATER_BANK_STATE_CHECK_FILLING_TO_LOWER);
Serial.println("Test start filling " + (String) (testB?"OK":"FAIL"));
WaterBankSystemRoutine();
testC = waterBankState==WATER_BANK_STATE_CHECK_FILLING_TO_LOWER;
Serial.println("Test filling to lower " + (String) (testC?"OK":"FAIL"));
//simulate water on lower
SetInSwitchState(WATER_BANK_FLOAT_SWITCH_LOWER, true);
WaterBankSystemRoutine();
testD = waterBankState==WATER_BANK_STATE_CHECK_FILLING_TO_UPPER;
Serial.println("Test filling to upper " + (String) (testD?"OK":"FAIL"));
//simulate tank full
SetInSwitchState(WATER_BANK_FLOAT_SWITCH_UPPER, true);
WaterBankSystemRoutine();
testE = waterBankState==WATER_BANK_STATE_IDLE;
Serial.println("Test filling to full " + (String) (testE?"OK":"FAIL"));
StopWaterBankSystem();
bool passNormalTest = testA && testB && testC && testD && testE;
Serial.println("All Normal Test: " + (String) (passNormalTest?"OK":"FAIL"));
//bool passFloatSwitchErrorTest = false;
//bool passFillLowerTimeoutTest = false;
//bool passFillUpperTimeoutTest = false;
// return passNormalTest;
//cleanup test
ClearInSwitch(); //clear floating switch state
StopWaterBankSystem();
return true;
}
//=========================================================================