diff --git a/Arduino/medibox.ino b/Arduino/medibox.ino new file mode 100644 index 0000000..75df04d --- /dev/null +++ b/Arduino/medibox.ino @@ -0,0 +1,497 @@ +#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; +}