Search This Blog

Monday, 14 July 2025

CARGER AKI CONTROLER INA226-LIBARY WE ROTARI SWITCH DAN PUS LCD 16X 2 SUKSES NORMAL SELISIH 0.2 VOLT

 #include <Wire.h> 

#include <LiquidCrystal_I2C.h> 

#include <INA226_WE.h> 

#include <Preferences.h> 


// --- DEKLARASI PIN ESP32 ---

LiquidCrystal_I2C lcd(0x27, 16, 2); 


// Pin untuk Rotary Encoder

#define ROTARY_A_PIN 32

#define ROTARY_B_PIN 33

#define ROTARY_SW_PIN 25


// Pin untuk Relay

#define RELAY_PIN 26


// --- OBJEK SENSOR ---

INA226_WE ina226;


// --- OBJEK NVS ---

Preferences preferences;


// --- PARAMETER AKI & BATAS ---

float batasTeganganBawah = 10.80; 

float batasTeganganAtas = 14.80; 


// --- VARIABEL PENGUKURAN ---

float teganganAki = 0.0;


// --- VARIABEL KONTROL MODE & UI ---

enum ModeAlat {

  MODE_NORMAL,

  MODE_SETTING_SELECT,      

  MODE_SETTING_BAWAH_EDIT,  

  MODE_SETTING_ATAS_EDIT    

};

ModeAlat currentMode = MODE_NORMAL;


bool manualRelayOn = false; 

unsigned long manualControlStartTime = 0; 

const long MANUAL_CONTROL_TIMEOUT = 300000;


volatile int encoderPos = 0;

int lastEncoderPos = 0;      


volatile int lastEncoded = 0;

const int ENCODER_DEBOUNCE_DELAY = 1; 


const int ENCODER_STEPS_PER_CLICK = 4;

int encoderClickCounter = 0;


volatile unsigned long pushButtonPressTime = 0;

volatile bool buttonIsCurrentlyPressed = false;


unsigned long lastButtonStateChangeTime = 0;

const long BUTTON_DEBOUNCE_MS = 150;


volatile bool shortPressDetected = false;

volatile bool longPressDetected = false;


const long LONG_PRESS_DURATION = 3000;


bool blinkState = false;

unsigned long lastBlinkMillis = 0;

const long BLINK_INTERVAL = 500;


int selectedSetting = 0;


unsigned long messageDisplayStartTime = 0;

const long MESSAGE_DISPLAY_DURATION = 1000;


// --- VARIABEL UNTUK DELAY RELAY OFF ---

unsigned long relayOffDelayStartTime = 0;

bool relayOffDelayActive = false;

const long RELAY_OFF_DELAY_MS = 15000;


// --- VARIABEL UNTUK OPTIMALISASI UPDATE LCD ---

char lastLine0[17] = "";

char lastLine1[17] = "";

ModeAlat lastMode = MODE_NORMAL;

int lastSelectedSetting = -1;

float lastTeganganAki = -1.0;

bool displayNeedsUpdate = true;


// --- Tambahkan variabel untuk melacak status relay yang terakhir ditampilkan ---

// Asumsi: HIGH = ON, LOW = OFF. SESUAIKAN JIKA MODUL RELAY ANDA AKTIF LOW.

// Jika aktif LOW, maka true = LOW (ON), false = HIGH (OFF)

bool lastRelayStateDisplayed = false; 



// --- PROTOTYPE FUNGSI ---

void IRAM_ATTR readEncoder();

void IRAM_ATTR handleButtonInterrupt();

void saveSettings();

void loadSettings();

void updateLCDDisplay();


// --- FUNGSI SETUP ---

void setup() {

  Serial.begin(115200);


  // --- Inisialisasi LCD ---

  lcd.init();      

  lcd.backlight(); 

  lcd.clear();

  lcd.setCursor(0, 0);

  lcd.print("Inisialisasi...");

  Serial.println("Inisialisasi LCD...");

  delay(1000);


  // --- Inisialisasi INA226 ---

  Wire.begin();

  Wire.setClock(50000);


  ina226.init();


  Serial.println("INA226 ditemukan (asumsi koneksi OK).");


  ina226.setAverage(AVERAGE_4);

  ina226.setConversionTime(CONV_TIME_1100);

  ina226.setMeasureMode(CONTINOUS);


  // --- Inisialisasi Rotary Encoder ---

  pinMode(ROTARY_A_PIN, INPUT_PULLUP);

  pinMode(ROTARY_B_PIN, INPUT_PULLUP);

  pinMode(ROTARY_SW_PIN, INPUT_PULLUP);


  attachInterrupt(digitalPinToInterrupt(ROTARY_A_PIN), readEncoder, CHANGE);

  attachInterrupt(digitalPinToInterrupt(ROTARY_B_PIN), readEncoder, CHANGE);

  attachInterrupt(digitalPinToInterrupt(ROTARY_SW_PIN), handleButtonInterrupt, CHANGE);


  // --- Inisialisasi Relay ---

  pinMode(RELAY_PIN, OUTPUT);

  digitalWrite(RELAY_PIN, LOW); // Pastikan relay OFF saat startup (jika aktif HIGH)

                               // Ubah menjadi HIGH jika modul Anda aktif LOW

  // Inisialisasi status relay yang ditampilkan

  lastRelayStateDisplayed = (digitalRead(RELAY_PIN) == HIGH); // Sesuaikan ini dengan jenis relay Anda


  // --- Muat Pengaturan dari NVS ---

  loadSettings();

  Serial.print("Batas Bawah: "); Serial.println(batasTeganganBawah, 2);

  Serial.print("Batas Atas: "); Serial.println(batasTeganganAtas, 2);


  int initialEncoderAPinState = digitalRead(ROTARY_A_PIN);

  int initialEncoderBPinState = digitalRead(ROTARY_B_PIN);

  lastEncoded = (initialEncoderAPinState << 1) | initialEncoderBPinState;


  lcd.clear();

  displayNeedsUpdate = true; // Paksa update pertama setelah setup

}


// --- FUNGSI LOOP ---

void loop() {

  unsigned long currentTime = millis();


  // --- BACA SENSOR ---

  teganganAki = ina226.getBusVoltage_V();

  Serial.print("Volt Aki Terbaca: ");

  Serial.print(teganganAki, 3);

  Serial.print(" V | Batas Atas: ");

  Serial.print(batasTeganganAtas, 3);

  Serial.print(" V | Batas Bawah: ");

  Serial.print(batasTeganganBawah, 3);

  Serial.print(" V | Status Pin RELAY: ");

  Serial.println(digitalRead(RELAY_PIN) == HIGH ? "HIGH (ON)" : "LOW (OFF)"); // Sesuaikan pesan ini dengan relay Anda


  // Periksa apakah tegangan aki berubah signifikan untuk update LCD

  if (abs(teganganAki - lastTeganganAki) > 0.01) { 

    displayNeedsUpdate = true;

  }


  // Tangani putaran Rotary Encoder

  if (encoderPos != lastEncoderPos) {

    int encoderDelta = encoderPos - lastEncoderPos;

    encoderClickCounter += encoderDelta;


    if (abs(encoderClickCounter) >= ENCODER_STEPS_PER_CLICK) {

      int numClicks = encoderClickCounter / ENCODER_STEPS_PER_CLICK;

      encoderClickCounter %= ENCODER_STEPS_PER_CLICK;


      if (currentMode == MODE_SETTING_BAWAH_EDIT) {

        batasTeganganBawah += (float)numClicks * 0.01;

        batasTeganganBawah = round(batasTeganganBawah * 100.0) / 100.0;

        batasTeganganBawah = constrain(batasTeganganBawah, 0.0, 36.0);


        blinkState = true;

        lastBlinkMillis = currentTime;

      } else if (currentMode == MODE_SETTING_ATAS_EDIT) {

        batasTeganganAtas += (float)numClicks * 0.01;

        batasTeganganAtas = round(batasTeganganAtas * 100.0) / 100.0;

        batasTeganganAtas = constrain(batasTeganganAtas, 0.0, 36.0);


        blinkState = true;

        lastBlinkMillis = currentTime;

      } else if (currentMode == MODE_SETTING_SELECT) {

        selectedSetting += numClicks;

        if (selectedSetting < 0) selectedSetting = 1;

        if (selectedSetting > 1) selectedSetting = 0;

        

        blinkState = true;

      }

      displayNeedsUpdate = true;

    }

    lastEncoderPos = encoderPos;

  }


  // --- LOGIKA KONTROL RELAY ---

  if (currentMode == MODE_NORMAL) {

    if (manualRelayOn && (currentTime - manualControlStartTime > MANUAL_CONTROL_TIMEOUT)) {

      manualRelayOn = false;

      Serial.println("Kontrol manual relay nonaktif (timeout).");

      digitalWrite(RELAY_PIN, LOW); // Sesuaikan ini jika relay Anda aktif LOW

      displayNeedsUpdate = true;

      relayOffDelayActive = false;

    }


    if (manualRelayOn) {

      if (teganganAki >= batasTeganganBawah && teganganAki <= batasTeganganAtas) {

        if (digitalRead(RELAY_PIN) == LOW) { // Sesuaikan ini jika relay Anda aktif LOW

          digitalWrite(RELAY_PIN, HIGH); // Sesuaikan ini jika relay Anda aktif LOW

          Serial.println("Relay MANUAL ON.");

        }

        relayOffDelayActive = false;

      } else {

        if (digitalRead(RELAY_PIN) == HIGH) { // Sesuaikan ini jika relay Anda aktif LOW

          digitalWrite(RELAY_PIN, LOW); // Sesuaikan ini jika relay Anda aktif LOW

          Serial.println("Relay MANUAL OFF karena di luar batas aman.");

        }

        manualRelayOn = false;

        displayNeedsUpdate = true;

        relayOffDelayActive = false;

        lcd.clear();

        lcd.setCursor(0,0); lcd.print("Tegangan Di Luar");

        lcd.setCursor(0,1); lcd.print("Batas Aman!");

        messageDisplayStartTime = currentTime;

      }

    } else {

      // Periksa perubahan status relay di sini untuk memicu update LCD

      bool currentRelayPhysicalState = (digitalRead(RELAY_PIN) == HIGH); // Asumsi HIGH = ON

      // ^^^ Sesuaikan ini dengan jenis relay Anda: `(digitalRead(RELAY_PIN) == LOW)` jika aktif LOW


      if (teganganAki < batasTeganganBawah) { // Nyalakan relay jika tegangan rendah

        if (digitalRead(RELAY_PIN) == LOW) { // Asumsi: LOW = OFF. Ubah ke HIGH jika aktif LOW

          digitalWrite(RELAY_PIN, HIGH); // Asumsi: HIGH = ON. Ubah ke LOW jika aktif LOW

          Serial.println("Relay OTOMATIS ON (Tegangan rendah).");

          displayNeedsUpdate = true; // Paksa update LCD

        }

        relayOffDelayActive = false;

      } else if (teganganAki >= batasTeganganAtas) { // Mematikan relay jika tegangan mencapai/melebihi batas atas

                                                    // Perhatikan operator >=

        if (!relayOffDelayActive) {

          relayOffDelayStartTime = currentTime;

          relayOffDelayActive = true;

          Serial.println("Tegangan tinggi terdeteksi. Memulai hitung mundur untuk OFF relay...");

          displayNeedsUpdate = true; // Paksa update LCD (opsional, bisa juga hanya di akhir delay)

        }


        if (relayOffDelayActive && (currentTime - relayOffDelayStartTime >= RELAY_OFF_DELAY_MS)) {

          if (digitalRead(RELAY_PIN) == HIGH) { // Asumsi: HIGH = ON. Ubah ke LOW jika aktif LOW

            digitalWrite(RELAY_PIN, LOW); // Asumsi: LOW = OFF. Ubah ke HIGH jika aktif LOW

            Serial.println("Relay OTOMATIS OFF (Tegangan tinggi setelah delay).");

            displayNeedsUpdate = true; // Paksa update LCD

          }

          relayOffDelayActive = false;

        }

      } else {

        // Tegangan di antara batas bawah dan batas atas (area histeresis)

        // Relay tidak akan berubah statusnya di sini kecuali jika kondisi sebelumnya memaksa perubahan.

        // Penting: Pastikan delay dimatikan jika tegangan kembali ke area normal/aman sebelum timeout

        if (relayOffDelayActive) { // Jika delay aktif tapi tegangan sudah kembali normal

            relayOffDelayActive = false;

            Serial.println("Delay OFF relay dibatalkan, tegangan kembali normal.");

            displayNeedsUpdate = true; // Paksa update LCD jika delay dibatalkan

        }

      }


      // Pastikan lastRelayStateDisplayed diperbarui dan memicu displayNeedsUpdate

      bool newRelayState = (digitalRead(RELAY_PIN) == HIGH); // Asumsi HIGH = ON. Ubah ke LOW jika aktif LOW

      if (newRelayState != lastRelayStateDisplayed) {

          displayNeedsUpdate = true;

          lastRelayStateDisplayed = newRelayState;

          Serial.print("Relay state changed! New state: "); Serial.println(newRelayState ? "ON" : "OFF");

      }


    } // End of manualRelayOn else

  } else { // currentMode != MODE_NORMAL (mode pengaturan)

    if (digitalRead(RELAY_PIN) == HIGH) { // Asumsi: HIGH = ON. Ubah ke LOW jika aktif LOW

      digitalWrite(RELAY_PIN, LOW); // Asumsi: LOW = OFF. Ubah ke HIGH jika aktif LOW

      Serial.println("Relay OFF (Di mode pengaturan).");

      displayNeedsUpdate = true;

    }

    manualRelayOn = false;

    displayNeedsUpdate = true;

    relayOffDelayActive = false;

  }


  // --- LOGIKA PENANGANAN TOMBOL PUSH ROTARY ---

  if (buttonIsCurrentlyPressed) {

    if (!longPressDetected && (currentTime - pushButtonPressTime >= LONG_PRESS_DURATION)) {

      longPressDetected = true;

      shortPressDetected = false;


      if (currentMode == MODE_NORMAL) {

        currentMode = MODE_SETTING_SELECT;

        Serial.println("Mode: Pilih Pengaturan (Long Press)");

        blinkState = true;

        selectedSetting = 0;

        displayNeedsUpdate = true;

      } else {

        currentMode = MODE_NORMAL;

        saveSettings();

        Serial.println("Mode: Normal (Long Press Keluar, Pengaturan Disimpan)");

        messageDisplayStartTime = currentTime;

        blinkState = true;

        displayNeedsUpdate = true;

      }

      lastButtonStateChangeTime = currentTime;

    }

  } else {

    if (pushButtonPressTime != 0 && (currentTime - pushButtonPressTime < LONG_PRESS_DURATION) && !longPressDetected) {

      if (currentTime - lastButtonStateChangeTime > BUTTON_DEBOUNCE_MS) {

        shortPressDetected = true;

        lastButtonStateChangeTime = currentTime;

      }

    }

    pushButtonPressTime = 0;

    longPressDetected = false;

  }


  // --- TANGANI AKSI SHORT PRESS (SETELAH DEBOUNCE) ---

  if (shortPressDetected) {

    shortPressDetected = false;


    // Logika untuk pengaturan (TETAP AKTIF)

    if (currentMode == MODE_SETTING_SELECT) {

      if (selectedSetting == 0) {

        currentMode = MODE_SETTING_BAWAH_EDIT;

        Serial.println("Mode: Edit Batas Bawah");

      } else {

        currentMode = MODE_SETTING_ATAS_EDIT;

        Serial.println("Mode: Edit Batas Atas");

      }

      blinkState = true;

      lastBlinkMillis = currentTime;

      displayNeedsUpdate = true;

    } else if (currentMode == MODE_SETTING_BAWAH_EDIT || currentMode == MODE_SETTING_ATAS_EDIT) {

      currentMode = MODE_SETTING_SELECT;

      Serial.println("Mode: Kembali ke Pilih Pengaturan");

      blinkState = true;

      displayNeedsUpdate = true;

    }

  }


  // --- UPDATE TAMPILAN LCD ---

  if (messageDisplayStartTime != 0 && (currentTime - messageDisplayStartTime < MESSAGE_DISPLAY_DURATION)) {

    // Pesan sementara sedang ditampilkan, tidak perlu panggil updateLCDDisplay

  } else {

    updateLCDDisplay();

    messageDisplayStartTime = 0; // Reset setelah waktu habis

  }


  delay(10);

}


// --- FUNGSI INTERRUPT ROTARY ENCODER ---

void IRAM_ATTR readEncoder() {

  int encoderAPinState = digitalRead(ROTARY_A_PIN);

  int encoderBPinState = digitalRead(ROTARY_B_PIN);


  int encoded = (encoderAPinState << 1) | encoderBPinState;


  if (encoded == 0b00) {

    if (lastEncoded == 0b01) encoderPos--;

    else if (lastEncoded == 0b10) encoderPos++;

  } else if (encoded == 0b01) {

    if (lastEncoded == 0b00) encoderPos++;

    else if (lastEncoded == 0b11) encoderPos--;

  } else if (encoded == 0b11) {

    if (lastEncoded == 0b01) encoderPos++;

    else if (lastEncoded == 0b10) encoderPos--;

  } else if (encoded == 0b10) {

    if (lastEncoded == 0b11) encoderPos++;

    else if (lastEncoded == 0b00) encoderPos--;

  }

  

  lastEncoded = encoded;

}


// --- FUNGSI INTERRUPT TOMBOL PUSH ROTARY ---

void IRAM_ATTR handleButtonInterrupt() {

  unsigned long currentTime = millis();


  if (digitalRead(ROTARY_SW_PIN) == LOW) {

    if (!buttonIsCurrentlyPressed) {

      buttonIsCurrentlyPressed = true;

      pushButtonPressTime = currentTime;

    }

  } else {

    buttonIsCurrentlyPressed = false;

  }

}


// --- FUNGSI UPDATE TAMPILAN LCD ---

void updateLCDDisplay() {

  // Tambahkan ini di awal fungsi untuk debugging

  Serial.println("updateLCDDisplay() called.");


  char currentLine0[17];

  char currentLine1[17];


  if (currentMode == MODE_SETTING_BAWAH_EDIT || currentMode == MODE_SETTING_ATAS_EDIT) {

    if (millis() - lastBlinkMillis > BLINK_INTERVAL) {

      blinkState = !blinkState;

      lastBlinkMillis = millis();

      displayNeedsUpdate = true;

    }

  } else {

    if (!blinkState) {

        blinkState = true;

        displayNeedsUpdate = true;

    }

  }

  

  if (currentMode == MODE_NORMAL) {

    snprintf(currentLine0, sizeof(currentLine0), "Volt Aki: %.1f V", teganganAki); 

    

    // Asumsi: HIGH = ON. Jika aktif LOW, ganti HIGH dengan LOW untuk mengecek status ON

    if (digitalRead(RELAY_PIN) == HIGH) { 

      snprintf(currentLine1, sizeof(currentLine1), "RELAY: ON       ");

    } else {

      snprintf(currentLine1, sizeof(currentLine1), "RELAY: OFF      ");

    }

  } else if (currentMode == MODE_SETTING_SELECT) {

    snprintf(currentLine0, sizeof(currentLine0), "PILIH PENGATURAN");

    if (selectedSetting == 0) {

      snprintf(currentLine1, sizeof(currentLine1), "> Batas Bawah   ");

    } else {

      snprintf(currentLine1, sizeof(currentLine1), "> Batas Atas    ");

    }

  } else if (currentMode == MODE_SETTING_BAWAH_EDIT) {

    snprintf(currentLine0, sizeof(currentLine0), "EDIT BATAS BAWAH");

    if (blinkState) {

      snprintf(currentLine1, sizeof(currentLine1), "%.2f V", batasTeganganBawah); 

    } else {

      snprintf(currentLine1, sizeof(currentLine1), "                "); 

    }

  } else if (currentMode == MODE_SETTING_ATAS_EDIT) {

    snprintf(currentLine0, sizeof(currentLine0), "EDIT BATAS ATAS ");

    if (blinkState) {

      snprintf(currentLine1, sizeof(currentLine1), "%.2f V", batasTeganganAtas); 

    } else {

      snprintf(currentLine1, sizeof(currentLine1), "                "); 

    }

  }


  // --- HANYA TULIS KE LCD JIKA ADA PERUBAHAN ATAU DIPAKSA UPDATE ---

  if (currentMode != lastMode || displayNeedsUpdate || (strcmp(currentLine0, lastLine0) != 0) || (strcmp(currentLine1, lastLine1) != 0)) {

    Serial.println("  LCD actually updated due to change."); // Pesan ini jika LCD benar-benar ditulis ulang

    lcd.setCursor(0, 0); for(int i=0; i<16; i++) lcd.print(" ");

    lcd.setCursor(0, 1); for(int i=0; i<16; i++) lcd.print(" ");


    lcd.setCursor(0, 0); lcd.print(currentLine0);

    lcd.setCursor(0, 1); lcd.print(currentLine1);


    strcpy(lastLine0, currentLine0);

    strcpy(lastLine1, currentLine1);


    displayNeedsUpdate = false;

  }


  lastMode = currentMode;

  lastSelectedSetting = selectedSetting;

  lastTeganganAki = teganganAki;

}


// --- FUNGSI SIMPAN/MUAT PENGATURAN KE NVS ---

void saveSettings() {

  preferences.begin("charger_app", false);

  preferences.putFloat("batasBawah", batasTeganganBawah);

  preferences.putFloat("batasAtas", batasTeganganAtas);

  preferences.end();

  Serial.println("Pengaturan disimpan.");

}


void loadSettings() {

  preferences.begin("charger_app", true);

  batasTeganganBawah = preferences.getFloat("batasBawah", 10.80);

  batasTeganganAtas = preferences.getFloat("batasAtas", 14.80);

  preferences.end();

  Serial.println("Pengaturan dimuat.");

}

No comments:

Post a Comment

CARGER AKI CONTROLER INA226-LIBARY WE ROTARI SW+OLED 0.96 SUKSES TAMPILAN LEBIH BAIK

 #include <Wire.h>  #include <Adafruit_GFX.h>    // Library grafis Adafruit #include <Adafruit_SSD1306.h> // Library untuk...