#include <Wire.h>
#include <LiquidCrystal_I2C.h>
// ==================================================
// LCD I2C
// ==================================================
LiquidCrystal_I2C lcd(0x27, 16, 2); // kalau tak jalan cuba 0x3F
// ==================================================
// INPUT
// ==================================================
const int PIR_PIN = 34;
const int POT_PIN = 32;
const int TRIG_PIN = 25;
const int ECHO_PIN = 26;
// ==================================================
// OUTPUT
// ==================================================
const int GREEN_LED = 18;
const int RED_LED = 19;
const int BUZZER = 27;
// ==================================================
// LCD I2C PINS
// ==================================================
const int SDA_PIN = 21;
const int SCL_PIN = 22;
// ==================================================
// SETTINGS
// ==================================================
const int SOAP_LOW_THRESHOLD = 1200;
const int DISTANCE_ALERT_CM = 10;
// dispense control
bool dispensing = false;
bool pirLatched = false;
unsigned long dispenseStart = 0;
const unsigned long DISPENSE_DURATION = 1500; // 1.5 saat
// ultrasonic alert timer
unsigned long lastBeepTime = 0;
const unsigned long BEEP_INTERVAL = 400;
// LCD refresh timer
unsigned long lastLCDUpdate = 0;
const unsigned long LCD_INTERVAL = 500; // lebih slow, kurang flicker
// ==================================================
// SETUP
// ==================================================
void setup() {
Serial.begin(115200);
// Input
pinMode(PIR_PIN, INPUT);
pinMode(POT_PIN, INPUT);
pinMode(TRIG_PIN, OUTPUT);
pinMode(ECHO_PIN, INPUT);
// Output
pinMode(GREEN_LED, OUTPUT);
pinMode(RED_LED, OUTPUT);
pinMode(BUZZER, OUTPUT);
digitalWrite(GREEN_LED, LOW);
digitalWrite(RED_LED, LOW);
digitalWrite(BUZZER, LOW);
// LCD setup
Wire.begin(SDA_PIN, SCL_PIN);
lcd.init();
lcd.backlight();
lcd.setCursor(0, 0);
lcd.print("SMART SINK");
lcd.setCursor(0, 1);
lcd.print("System Start");
delay(2000);
lcd.setCursor(0, 0);
lcd.print("SYSTEM READY ");
lcd.setCursor(0, 1);
lcd.print("P:0 D:-- S:OK ");
Serial.println("SMART SINK SIMULATOR START");
Serial.println("System Ready...");
}
// ==================================================
// LOOP
// ==================================================
void loop() {
int pirState = digitalRead(PIR_PIN);
int potValue = analogRead(POT_PIN);
float distance = readDistanceCM();
bool soapLow = (potValue < SOAP_LOW_THRESHOLD);
bool distanceAlert = (distance > 0 && distance < DISTANCE_ALERT_CM);
// -----------------------------
// SERIAL MONITOR STATUS
// -----------------------------
Serial.print("PIR: ");
Serial.print(pirState);
Serial.print(" | POT: ");
Serial.print(potValue);
Serial.print(" | DIST: ");
Serial.print(distance);
Serial.print(" cm");
Serial.print(" | SOAP LOW: ");
Serial.print(soapLow ? "YES" : "NO");
Serial.print(" | ALERT: ");
Serial.println(distanceAlert ? "YES" : "NO");
// -----------------------------
// 1. SOAP LOW WARNING
// -----------------------------
if (soapLow) {
digitalWrite(RED_LED, HIGH);
digitalWrite(BUZZER, HIGH);
} else {
digitalWrite(RED_LED, LOW);
digitalWrite(BUZZER, LOW);
}
// -----------------------------
// 2. DISPENSE SOAP (PIR)
// -----------------------------
if (pirState == HIGH && !soapLow && !dispensing && !pirLatched) {
dispensing = true;
pirLatched = true;
dispenseStart = millis();
digitalWrite(GREEN_LED, HIGH);
Serial.println("HAND DETECTED -> DISPENSING SOAP");
}
if (dispensing && millis() - dispenseStart >= DISPENSE_DURATION) {
dispensing = false;
digitalWrite(GREEN_LED, LOW);
Serial.println("DISPENSE DONE");
}
if (pirState == LOW) {
pirLatched = false;
}
// -----------------------------
// 3. ULTRASONIC ALERT
// -----------------------------
if (distanceAlert && !soapLow) {
if (millis() - lastBeepTime >= BEEP_INTERVAL) {
lastBeepTime = millis();
digitalWrite(BUZZER, HIGH);
delay(80);
digitalWrite(BUZZER, LOW);
Serial.println("DISTANCE ALERT TRIGGERED");
}
}
// -----------------------------
// 4. LCD DISPLAY
// -----------------------------
if (millis() - lastLCDUpdate >= LCD_INTERVAL) {
lastLCDUpdate = millis();
updateLCD(pirState, distance, soapLow, distanceAlert);
}
delay(150);
}
// ==================================================
// FUNCTION: READ DISTANCE FROM HC-SR04
// ==================================================
float readDistanceCM() {
digitalWrite(TRIG_PIN, LOW);
delayMicroseconds(2);
digitalWrite(TRIG_PIN, HIGH);
delayMicroseconds(10);
digitalWrite(TRIG_PIN, LOW);
long duration = pulseIn(ECHO_PIN, HIGH, 30000);
if (duration <= 0) {
return -1;
}
float distance = duration * 0.0343 / 2.0;
return distance;
}
// ==================================================
// FUNCTION: UPDATE LCD (NO FLICKER)
// ==================================================
void updateLCD(int pirState, float distance, bool soapLow, bool distanceAlert) {
static String lastLine1 = "";
static String lastLine2 = "";
String line1 = "";
String line2 = "";
// Line 1 status utama
if (soapLow) {
line1 = "SOAP LOW!";
}
else if (dispensing) {
line1 = "DISPENSING...";
}
else if (distanceAlert) {
line1 = "OBJ TOO CLOSE";
}
else {
line1 = "SYSTEM READY";
}
// Line 2 info sensor
line2 = "P:";
line2 += String(pirState);
line2 += " D:";
if (distance < 0) {
line2 += "--";
} else {
line2 += String((int)distance);
}
line2 += " S:";
line2 += (soapLow ? "LOW" : "OK");
// Pastikan 16 char penuh
while (line1.length() < 16) line1 += " ";
while (line2.length() < 16) line2 += " ";
// Update hanya bila berubah
if (line1 != lastLine1) {
lcd.setCursor(0, 0);
lcd.print(line1);
lastLine1 = line1;
}
if (line2 != lastLine2) {
lcd.setCursor(0, 1);
lcd.print(line2);
lastLine2 = line2;
}
}