#include #include #include #include #include "RTClib.h" #include #include #include // Firebase config #define FIREBASE_HOST "https://coba-smartmedibox-default-rtdb.firebaseio.com" #define FIREBASE_AUTH "AIzaSyAUVen3yiSUrNJw7HnRif0_IBmUdeStY0c" FirebaseData fbdo; FirebaseAuth auth; FirebaseConfig config; bool signupOK = false; // RTC RTC_DS3231 rtc; LiquidCrystal_I2C lcd(0x27, 16, 2); // Stepper motor const int stepsPerRevolution = 2038; #define IN1 32 #define IN2 33 #define IN3 25 #define IN4 26 Stepper kotak1(stepsPerRevolution, IN1, IN3, IN2, IN4); #define IN5 17 #define IN6 5 #define IN7 18 #define IN8 19 Stepper kotak2(stepsPerRevolution, IN5, IN7, IN6, IN8); // Jadwal dari Firebase int kotak1Schedule[4][2]; int kotak2Schedule[4][2]; bool kotak1Activated[4] = {false, false, false, false}; bool kotak2Activated[4] = {false, false, false, false}; bool kotak1AlarmActive[4] = {false, false, false, false}; bool kotak2AlarmActive[4] = {false, false, false, false}; // Pin tambahan const int buzzerPin = 27; const int ledPin = 14; const int buttonPin1 = 34; const int buttonPin2 = 23; int lastButtonState1 = LOW; bool buttonPressed1 = false; bool lastButtonStatus1 = false; bool buttonStatus1 = false; int lastButtonState2 = LOW; bool buttonPressed2 = false; bool lastButtonStatus2 = false; bool buttonStatus2 = false; unsigned long buttonTriggerTime1 = 0; bool shouldResetFirebaseButton1 = false; unsigned long buttonTriggerTime2 = 0; bool shouldResetFirebaseButton2 = false; unsigned long lastUpdateTime = 0; const unsigned long updateInterval = 60000; // 60 detik unsigned long lastUpdateTime2 = 0; const unsigned long updateInterval2 = 30000; // 60 detik void setup() { Serial.begin(115200); Wire.begin(21, 22); lcd.init(); lcd.backlight(); lcd.begin(16, 2); lcd.clear(); lcd.setCursor(1, 0); lcd.print("Hubungkan Wifi"); WiFi.mode(WIFI_STA); WiFiManager wm; wm.resetSettings(); bool res; res = wm.autoConnect("AutoConnectAP", "password"); Serial.println("Hubungkan Wifi....."); if (!res) { Serial.println("Gagal konek"); } else { Serial.println("Berhasil konek"); } lcd.clear(); lcd.setCursor(1, 0); lcd.print("Wifi Terhubung"); delay(1000); config.database_url = FIREBASE_HOST; config.api_key = FIREBASE_AUTH; if (Firebase.signUp(&config, &auth, "", "")) { Serial.println("Firebase signup ok"); signupOK = true; } else { Serial.printf("%s\n", config.signer.signupError.message.c_str()); } Firebase.begin(&config, &auth); Firebase.reconnectWiFi(true); if (!rtc.begin()) { Serial.println("RTC tidak terdeteksi!"); while (1); } else { setRTCfromNTP(); // Sinkronkan waktu RTC dari internet saat awal nyala } kotak1.setSpeed(5); kotak2.setSpeed(5); pinMode(buzzerPin, OUTPUT); pinMode(ledPin, OUTPUT); pinMode(buttonPin1, INPUT); pinMode(buttonPin2, INPUT); lcd.clear(); lcd.setCursor(1, 0); lcd.print("Smart Medicine"); lcd.setCursor(6, 1); lcd.print("Box"); delay(2000); updateFromFirebase(); } void setRTCfromNTP() { configTime(25200, 0, "pool.ntp.org"); // WIB = UTC+7 = 25200 detik offset struct tm timeinfo; if (getLocalTime(&timeinfo)) { rtc.adjust(DateTime( timeinfo.tm_year + 1900, timeinfo.tm_mon + 1, timeinfo.tm_mday, timeinfo.tm_hour, timeinfo.tm_min, timeinfo.tm_sec )); Serial.println("RTC disinkronkan dengan waktu NTP (WIB)"); } else { Serial.println("Gagal mendapatkan waktu dari NTP"); } } void updateFromFirebase() { // Kotak 1 for (int i = 0; i < 4; i++) { String pathJam = "/jadwal/kotak1/" + String(i) + "/jam"; String pathMenit = "/jadwal/kotak1/" + String(i) + "/menit"; if (Firebase.getInt(fbdo, pathJam)) { kotak1Schedule[i][0] = fbdo.intData(); } else { kotak1Schedule[i][0] = -1; // Tandai tidak valid } if (Firebase.getInt(fbdo, pathMenit)) { kotak1Schedule[i][1] = fbdo.intData(); } else { kotak1Schedule[i][1] = -1; } } // Kotak 2 for (int i = 0; i < 4; i++) { String pathJam = "/jadwal/kotak2/" + String(i) + "/jam"; String pathMenit = "/jadwal/kotak2/" + String(i) + "/menit"; if (Firebase.getInt(fbdo, pathJam)) { kotak2Schedule[i][0] = fbdo.intData(); } else { kotak2Schedule[i][0] = -1; } if (Firebase.getInt(fbdo, pathMenit)) { kotak2Schedule[i][1] = fbdo.intData(); } else { kotak2Schedule[i][1] = -1; } } fbdo.clear(); // Bersihkan setelah digunakan } void lcdI2c() { DateTime now = rtc.now(); int waktuSekarang = now.hour() * 60 + now.minute(); int waktuTerdekat1 = 24 * 60 + 1; int indexTerdekat1 = -1; int waktuTerdekatBesok1 = 24 * 60 + 1; int indexTerdekatBesok1 = -1; int waktuTerdekat2 = 24 * 60 + 1; int indexTerdekat2 = -1; int waktuTerdekatBesok2 = 24 * 60 + 1; int indexTerdekatBesok2 = -1; // Cari jadwal terdekat kotak 1 for (int i = 0; i < 4; i++) { int jam = kotak1Schedule[i][0]; int menit = kotak1Schedule[i][1]; if (jam < 0 || menit < 0) continue; int waktuJadwal = jam * 60 + menit; if (waktuJadwal >= waktuSekarang && waktuJadwal - waktuSekarang < waktuTerdekat1) { waktuTerdekat1 = waktuJadwal - waktuSekarang; indexTerdekat1 = i; } if (waktuJadwal < waktuTerdekatBesok1) { waktuTerdekatBesok1 = waktuJadwal; indexTerdekatBesok1 = i; } } // Cari jadwal terdekat kotak 2 for (int i = 0; i < 4; i++) { int jam = kotak2Schedule[i][0]; int menit = kotak2Schedule[i][1]; if (jam < 0 || menit < 0) continue; int waktuJadwal = jam * 60 + menit; if (waktuJadwal >= waktuSekarang && waktuJadwal - waktuSekarang < waktuTerdekat2) { waktuTerdekat2 = waktuJadwal - waktuSekarang; indexTerdekat2 = i; } if (waktuJadwal < waktuTerdekatBesok2) { waktuTerdekatBesok2 = waktuJadwal; indexTerdekatBesok2 = i; } } lcd.clear(); // Tampilkan Kotak 1 lcd.setCursor(0, 0); lcd.print("Kotak 1 = "); int jam1 = -1, menit1 = -1; if (indexTerdekat1 != -1) { jam1 = kotak1Schedule[indexTerdekat1][0]; menit1 = kotak1Schedule[indexTerdekat1][1]; } else if (indexTerdekatBesok1 != -1) { jam1 = kotak1Schedule[indexTerdekatBesok1][0]; menit1 = kotak1Schedule[indexTerdekatBesok1][1]; } if (jam1 != -1 && menit1 != -1) { if (jam1 < 10) lcd.print("0"); lcd.print(jam1); lcd.print(":"); if (menit1 < 10) lcd.print("0"); lcd.print(menit1); } else { lcd.print("--:--"); } // Tampilkan Kotak 2 lcd.setCursor(0, 1); lcd.print("Kotak 2 = "); int jam2 = -1, menit2 = -1; if (indexTerdekat2 != -1) { jam2 = kotak2Schedule[indexTerdekat2][0]; menit2 = kotak2Schedule[indexTerdekat2][1]; } else if (indexTerdekatBesok2 != -1) { jam2 = kotak2Schedule[indexTerdekatBesok2][0]; menit2 = kotak2Schedule[indexTerdekatBesok2][1]; } if (jam2 != -1 && menit2 != -1) { if (jam2 < 10) lcd.print("0"); lcd.print(jam2); lcd.print(":"); if (menit2 < 10) lcd.print("0"); lcd.print(menit2); } else { lcd.print("--:--"); } } void loop() { if (!Firebase.ready()) { //Serial.println("Firebase tidak ready, mencoba reconnect..."); Firebase.begin(&config, &auth); } unsigned long currentMillis = millis(); // Perbarui jadwal dari Firebase setiap 60 detik if (currentMillis - lastUpdateTime > updateInterval) { updateFromFirebase(); lastUpdateTime = currentMillis; } unsigned long currentMillis2 = millis(); // Perbarui jadwal dari Firebase setiap 60 detik if (currentMillis2 - lastUpdateTime2 > updateInterval2) { lcdI2c(); lastUpdateTime2 = currentMillis2; } DateTime now = rtc.now(); int h = now.hour(); int m = now.minute(); // Deteksi tombol 1 fisik int currentButtonState1 = digitalRead(buttonPin1); if (currentButtonState1 == HIGH && lastButtonState1 == LOW) { buttonPressed1 = true; Serial.println("Tombol 1 fisik ditekan"); } lastButtonState1 = currentButtonState1; // Deteksi tombol 2 fisik int currentButtonState2 = digitalRead(buttonPin2); if (currentButtonState2 == HIGH && lastButtonState2 == LOW) { buttonPressed2 = true; Serial.println("Tombol 2 fisik ditekan"); } lastButtonState2 = currentButtonState2; // Baca status dari Firebase (dari APK) Firebase.getBool(fbdo, "/status/button1"); bool firebaseButton1 = fbdo.boolData(); // Baca status dari Firebase (dari APK) Firebase.getBool(fbdo, "/status/button2"); bool firebaseButton2 = fbdo.boolData(); // Jika tombol fisik ditekan if (buttonPressed1) { Firebase.setBool(fbdo, "/status/button1", true); buttonTriggerTime1 = millis(); shouldResetFirebaseButton1 = true; } // Jika tombol fisik ditekan if (buttonPressed2) { Firebase.setBool(fbdo, "/status/button2", true); buttonTriggerTime2 = millis(); shouldResetFirebaseButton2 = true; } // Jika tombol ditekan dari APK if (firebaseButton1 && !lastButtonStatus1) { buttonStatus1 = true; buttonTriggerTime1 = millis(); shouldResetFirebaseButton1 = true; } lastButtonStatus1 = firebaseButton1; // Jika tombol ditekan dari APK if (firebaseButton2 && !lastButtonStatus2) { buttonStatus2 = true; buttonTriggerTime2 = millis(); shouldResetFirebaseButton2 = true; } lastButtonStatus2 = firebaseButton2; // Cek dan jalankan alarm Stepper 1 for (int i = 0; i < 4; i++) { int th = kotak1Schedule[i][0]; int tm = kotak1Schedule[i][1]; // Jika waktunya tiba dan belum dipicu if (h == th && m == tm && !kotak1Activated[i]) { kotak1Activated[i] = true; kotak1AlarmActive[i] = true; // aktifkan alarm kotak1.step(128); String pathJumlahObat1 = "/jadwal/kotak1/jumlahObat"; if (Firebase.getInt(fbdo, pathJumlahObat1)) { int jumlahObat1 = fbdo.intData(); if (jumlahObat1 > 0) { jumlahObat1--; Firebase.setInt(fbdo, pathJumlahObat1, jumlahObat1); Serial.printf("Jumlah obat kotak1 berkurang menjadi %d\n", jumlahObat1); } else { Serial.println("Jumlah obat habis atau gagal dibaca"); } } } // 🔔 Jika alarm aktif, tapi tombol belum ditekan if (kotak1AlarmActive[i]) { if (buttonPressed1 || buttonStatus1) { kotak1AlarmActive[i] = false; // matikan alarm digitalWrite(buzzerPin, LOW); digitalWrite(ledPin, LOW); Firebase.setBool(fbdo, "/status/kotak1", true); Firebase.setBool(fbdo, "/status/buzzer", false); Firebase.setBool(fbdo, "/status/led", false); } else { digitalWrite(ledPin, HIGH); tone(buzzerPin, 1000); delay(400); noTone(buzzerPin); delay(400); Firebase.setBool(fbdo, "/status/alarm1", true); Firebase.setBool(fbdo, "/status/buzzer", true); Firebase.setBool(fbdo, "/status/led", true); } } // Reset aktivasi stepper agar bisa dipicu besok pada jam yang sama if (h != th || m != tm) { kotak1Activated[i] = false; } } // Cek dan jalankan alarm Stepper 2 for (int i = 0; i < 4; i++) { int th = kotak2Schedule[i][0]; int tm = kotak2Schedule[i][1]; // Jika waktunya tiba dan belum dipicu if (h == th && m == tm && !kotak2Activated[i]) { kotak2Activated[i] = true; kotak2AlarmActive[i] = true; // aktifkan alarm kotak2.step(128); String pathJumlahObat2 = "/jadwal/kotak2/jumlahObat"; if (Firebase.getInt(fbdo, pathJumlahObat2)) { int jumlahObat2 = fbdo.intData(); if (jumlahObat2 > 0) { jumlahObat2--; Firebase.setInt(fbdo, pathJumlahObat2, jumlahObat2); Serial.printf("Jumlah obat kotak2 berkurang menjadi %d\n", jumlahObat2); } else { Serial.println("Jumlah obat habis atau gagal dibaca"); } } } // 🔔 Jika alarm aktif, tapi tombol belum ditekan if (kotak2AlarmActive[i]) { if (buttonPressed2 || buttonStatus2) { kotak2AlarmActive[i] = false; // matikan alarm digitalWrite(buzzerPin, LOW); digitalWrite(ledPin, LOW); Firebase.setBool(fbdo, "/status/kotak2", true); Firebase.setBool(fbdo, "/status/buzzer", false); Firebase.setBool(fbdo, "/status/led", false); } else { digitalWrite(ledPin, HIGH); tone(buzzerPin, 1000); delay(400); noTone(buzzerPin); delay(400); Firebase.setBool(fbdo, "/status/alarm2", true); Firebase.setBool(fbdo, "/status/buzzer", true); Firebase.setBool(fbdo, "/status/led", true); } } // Reset aktivasi stepper agar bisa dipicu besok pada jam yang sama if (h != th || m != tm) { kotak2Activated[i] = false; } } // Reset tombol Firebase ke false setelah 1 detik if (shouldResetFirebaseButton1 && millis() - buttonTriggerTime1 >= 2500) { Firebase.setBool(fbdo, "/status/button1", false); Firebase.setBool(fbdo, "/status/kotak1", false); Firebase.setBool(fbdo, "/status/alarm1", false); shouldResetFirebaseButton1 = false; } // Reset tombol Firebase ke false setelah 1 detik if (shouldResetFirebaseButton2 && millis() - buttonTriggerTime2 >= 2500) { Firebase.setBool(fbdo, "/status/button2", false); Firebase.setBool(fbdo, "/status/kotak2", false); Firebase.setBool(fbdo, "/status/alarm2", false); shouldResetFirebaseButton2 = false; } buttonPressed1 = false; buttonStatus1 = false; buttonPressed2 = false; buttonStatus2 = false; }