From d8cd33e0036bbb2b1b36ded7373be8e7e19fe680 Mon Sep 17 00:00:00 2001 From: Syhxmii Date: Wed, 25 Jun 2025 13:09:18 +0700 Subject: [PATCH] HydroZoner IoT --- App.h | 426 ++++++++++++++++++++++++++++ ButtonRotaryHandler.h | 170 +++++++++++ BuzzerHdlr.h | 1 + ConfigHandler.h | 77 +++++ Dashboard.h | 348 +++++++++++++++++++++++ DisplayHandler.h | 244 ++++++++++++++++ FloatSwitchHandler.h | 87 ++++++ HydroZoner.ino | 55 ++++ Raindrop.h | 58 ++++ SterilizationHandler.h | 629 +++++++++++++++++++++++++++++++++++++++++ SwitchHandler.h | 131 +++++++++ WaterBankHandler.h | 311 ++++++++++++++++++++ 12 files changed, 2537 insertions(+) create mode 100644 App.h create mode 100644 ButtonRotaryHandler.h create mode 100644 BuzzerHdlr.h create mode 100644 ConfigHandler.h create mode 100644 Dashboard.h create mode 100644 DisplayHandler.h create mode 100644 FloatSwitchHandler.h create mode 100644 HydroZoner.ino create mode 100644 Raindrop.h create mode 100644 SterilizationHandler.h create mode 100644 SwitchHandler.h create mode 100644 WaterBankHandler.h diff --git a/App.h b/App.h new file mode 100644 index 0000000..d3cb28c --- /dev/null +++ b/App.h @@ -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), (dialogSelectionIndex0; + 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)=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=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(); + } +} diff --git a/ButtonRotaryHandler.h b/ButtonRotaryHandler.h new file mode 100644 index 0000000..009ef50 --- /dev/null +++ b/ButtonRotaryHandler.h @@ -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(")"); + } + } + +} diff --git a/BuzzerHdlr.h b/BuzzerHdlr.h new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/BuzzerHdlr.h @@ -0,0 +1 @@ + diff --git a/ConfigHandler.h b/ConfigHandler.h new file mode 100644 index 0000000..950f235 --- /dev/null +++ b/ConfigHandler.h @@ -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; +} + diff --git a/Dashboard.h b/Dashboard.h new file mode 100644 index 0000000..f4b3061 --- /dev/null +++ b/Dashboard.h @@ -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); +} + diff --git a/DisplayHandler.h b/DisplayHandler.h new file mode 100644 index 0000000..2f67563 --- /dev/null +++ b/DisplayHandler.h @@ -0,0 +1,244 @@ +#include + +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, ""); + 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); +} + diff --git a/FloatSwitchHandler.h b/FloatSwitchHandler.h new file mode 100644 index 0000000..380ff78 --- /dev/null +++ b/FloatSwitchHandler.h @@ -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=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=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=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; +} + diff --git a/HydroZoner.ino b/HydroZoner.ino new file mode 100644 index 0000000..b11a6be --- /dev/null +++ b/HydroZoner.ino @@ -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(); + +} diff --git a/Raindrop.h b/Raindrop.h new file mode 100644 index 0000000..ae041c4 --- /dev/null +++ b/Raindrop.h @@ -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 + } +} + diff --git a/SterilizationHandler.h b/SterilizationHandler.h new file mode 100644 index 0000000..dc8727c --- /dev/null +++ b/SterilizationHandler.h @@ -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 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; +} diff --git a/SwitchHandler.h b/SwitchHandler.h new file mode 100644 index 0000000..893f834 --- /dev/null +++ b/SwitchHandler.h @@ -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=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=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) 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) swx = 0; +} \ No newline at end of file diff --git a/WaterBankHandler.h b/WaterBankHandler.h new file mode 100644 index 0000000..948acd6 --- /dev/null +++ b/WaterBankHandler.h @@ -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; +} +//========================================================================= +