commit d8cd33e0036bbb2b1b36ded7373be8e7e19fe680 Author: Syhxmii Date: Wed Jun 25 13:09:18 2025 +0700 HydroZoner IoT 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; +} +//========================================================================= +