338 lines
11 KiB
C++
338 lines
11 KiB
C++
#include "thingProperties.h"
|
|
#include <DHT.h>
|
|
#include <LiquidCrystal_I2C.h>
|
|
|
|
// --- DEFINISI PIN ---
|
|
#define PIN_DHT 4
|
|
#define TIPE_DHT DHT11
|
|
#define PIN_MQ3 34
|
|
#define PIN_MQ135 35
|
|
#define PIN_BUZZER 15
|
|
#define PIN_TOMBOL1 13 // Tombol List Buah
|
|
#define PIN_TOMBOL2 12 // Tombol Pindah Bawah
|
|
#define PIN_TOMBOL3 14 // Tombol Pilih/Enter
|
|
|
|
// --- KONFIGURASI LCD ---
|
|
#define ALAMAT_LCD 0x27
|
|
#define KOLOM_LCD 20
|
|
#define BARIS_LCD 4
|
|
|
|
// Inisialisasi komponen
|
|
DHT dht(PIN_DHT, TIPE_DHT);
|
|
LiquidCrystal_I2C lcd(ALAMAT_LCD, KOLOM_LCD, BARIS_LCD);
|
|
|
|
struct DataSensor {
|
|
float suhu;
|
|
float kelembaban;
|
|
int nilaiAnalogMQ3;
|
|
int nilaiAnalogMQ135;
|
|
float nilaiPpmMQ3;
|
|
float nilaiPpmMQ135;
|
|
String tingkatKematangan;
|
|
String namaBuahTerpilih;
|
|
};
|
|
|
|
// --- VARIABEL GLOBAL ---
|
|
String daftarBuah[] = {"Apel", "Pepaya", "Pisang", "Alpukat"};
|
|
int buahTerpilihLokal = 0;
|
|
bool statusBuahDipilih = false;
|
|
bool statusTampilDaftarBuah = false;
|
|
unsigned long waktuTerakhirSensorDibaca = 0;
|
|
unsigned long waktuTerakhirTombolDibaca = 0;
|
|
unsigned long waktuTerakhirCloudUpdate = 0;
|
|
const unsigned long INTERVAL_SENSOR = 2000; // 2 detik
|
|
const unsigned long INTERVAL_CLOUD = 5000; // 5 detik untuk update cloud
|
|
const unsigned long JEDA_ANTIBOUNCE = 200; // 200 milidetik
|
|
|
|
// Status tombol
|
|
bool kondisiTombol1 = HIGH, kondisiTombol2 = HIGH, kondisiTombol3 = HIGH;
|
|
bool kondisiTerakhirTombol1 = HIGH, kondisiTerakhirTombol2 = HIGH, kondisiTerakhirTombol3 = HIGH;
|
|
|
|
// Variabel untuk menyimpan tingkat kematangan sebelumnya
|
|
String tingkatKematanganSebelumnya = "";
|
|
|
|
// ==============================================================================
|
|
// KONSTANTA KALIBRASI SENSOR MQ
|
|
// ==============================================================================
|
|
const float TEGANGAN_VCC = 5.0; // Tegangan suplai modul sensor MQ (V)
|
|
const float RESISTANSI_BEBAN = 10.0; // Resistansi beban (kOhm)
|
|
const int RESOLUSI_ADC = 4095; // Resolusi ADC ESP32
|
|
|
|
// MQ3 (Alkohol/Etanol) - Ganti dengan nilai kalibrasi Anda!
|
|
const float MQ3_R0 = 60.0;
|
|
const float MQ3_SLOPE = -0.685;
|
|
const float MQ3_INTERCEPT = 1.258;
|
|
|
|
// MQ135 (Kualitas Udara) - Ganti dengan nilai kalibrasi Anda!
|
|
const float MQ135_R0 = 35.7;
|
|
const float MQ135_SLOPE = -0.395;
|
|
const float MQ135_INTERCEPT = 1.488;
|
|
// ==============================================================================
|
|
|
|
// Struktur untuk ambang batas kematangan buah (dalam PPM)
|
|
struct AmbangBatasBuah {
|
|
float mentahMQ3Maks;
|
|
float matangMQ3Min;
|
|
float matangMQ3Maks;
|
|
float busukMQ3Min;
|
|
float mentahMQ135Maks;
|
|
float matangMQ135Min;
|
|
float matangMQ135Maks;
|
|
float busukMQ135Min;
|
|
};
|
|
|
|
// Definisi ambang batas untuk setiap buah (sesuaikan berdasarkan kalibrasi)
|
|
AmbangBatasBuah daftarAmbangBatas[] = {
|
|
{29.1, 29.5, 77.0, 78.0, 11.2, 11.7, 17.5, 18.0}, // Apel
|
|
{30.0, 30.5, 90.0, 91.0, 13.0, 13.5, 24.0, 24.5}, // Pepaya
|
|
{32.0, 32.5, 102.0, 103.0, 12.0, 12.5, 19.0, 19.5}, // Pisang
|
|
{29.2, 29.7, 59.0, 60.5, 13.2, 13.7, 21.0, 21.5} // Alpukat
|
|
};
|
|
|
|
void setup() {
|
|
Serial.begin(9600);
|
|
delay(1500);
|
|
|
|
// Initialize Arduino IoT Cloud
|
|
initProperties();
|
|
ArduinoCloud.begin(ArduinoIoTPreferredConnection);
|
|
setDebugMessageLevel(2);
|
|
ArduinoCloud.printDebugInfo();
|
|
|
|
dht.begin();
|
|
lcd.init();
|
|
lcd.backlight();
|
|
|
|
pinMode(PIN_BUZZER, OUTPUT);
|
|
pinMode(PIN_TOMBOL1, INPUT_PULLUP);
|
|
pinMode(PIN_TOMBOL2, INPUT_PULLUP);
|
|
pinMode(PIN_TOMBOL3, INPUT_PULLUP);
|
|
|
|
tampilkanPesanSelamatDatang();
|
|
|
|
Serial.println("=== SISTEM DETEKSI KEMATANGAN BUAH ===");
|
|
Serial.println("Sistem siap digunakan!");
|
|
|
|
delay(5000);
|
|
tampilkanMenuUtama();
|
|
}
|
|
|
|
// Fungsi untuk menghitung resistansi sensor Rs
|
|
float hitungResistansiSensor(int nilaiAnalog, float teganganVcc, float resistansiBeban, int resolusiAdc) {
|
|
float teganganKeluar = nilaiAnalog * (teganganVcc / resolusiAdc);
|
|
if (teganganKeluar == 0) return resistansiBeban * 1000;
|
|
float resistansiSensor = (teganganVcc / teganganKeluar - 1) * resistansiBeban;
|
|
return resistansiSensor; // dalam kOhm
|
|
}
|
|
|
|
// Fungsi untuk mengkonversi Rs/R0 ke PPM
|
|
float konversiKePPM(float rasioRsR0, float slope, float intercept) {
|
|
if (rasioRsR0 <= 0) return 0.0;
|
|
float logRsR0 = log10(rasioRsR0);
|
|
return pow(10, (slope * logRsR0) + intercept);
|
|
}
|
|
|
|
void loop() {
|
|
ArduinoCloud.update();
|
|
|
|
unsigned long waktuSekarang = millis();
|
|
|
|
if (waktuSekarang - waktuTerakhirTombolDibaca >= JEDA_ANTIBOUNCE) {
|
|
kelolaTombol();
|
|
waktuTerakhirTombolDibaca = waktuSekarang;
|
|
}
|
|
|
|
if (statusBuahDipilih && (waktuSekarang - waktuTerakhirSensorDibaca >= INTERVAL_SENSOR)) {
|
|
perbaruiSensor();
|
|
waktuTerakhirSensorDibaca = waktuSekarang;
|
|
}
|
|
|
|
// Update Arduino Cloud setiap 5 detik
|
|
if (statusBuahDipilih && (waktuSekarang - waktuTerakhirCloudUpdate >= INTERVAL_CLOUD)) {
|
|
updateCloudVariables();
|
|
waktuTerakhirCloudUpdate = waktuSekarang;
|
|
}
|
|
}
|
|
|
|
void updateCloudVariables() {
|
|
DataSensor data = bacaSemuaSensor();
|
|
|
|
// Update cloud variables sesuai dengan yang ada di Arduino Cloud
|
|
mq135_ppm = data.nilaiPpmMQ135;
|
|
mq3_ppm = data.nilaiPpmMQ3;
|
|
namaBuah = daftarBuah[buahTerpilihLokal]; // Mengirim nama buah dari array
|
|
suhu = data.suhu;
|
|
tingkatKematangan = data.tingkatKematangan;
|
|
|
|
Serial.println("=== DATA DIKIRIM KE ARDUINO CLOUD ===");
|
|
Serial.println("MQ135 PPM: " + String(mq135_ppm, 4));
|
|
Serial.println("MQ3 PPM: " + String(mq3_ppm, 4));
|
|
Serial.println("Nama Buah Index: " + String(namaBuah));
|
|
Serial.println("Suhu: " + String(suhu, 1) + " °C");
|
|
Serial.println("Tingkat Kematangan: " + tingkatKematangan);
|
|
Serial.println("=====================================");
|
|
}
|
|
|
|
void tampilkanPesanSelamatDatang() {
|
|
lcd.clear();
|
|
lcd.setCursor(0, 0); lcd.print(" SISTEM PENDETEKSI ");
|
|
lcd.setCursor(0, 1); lcd.print(" KEMATANGAN BUAH ");
|
|
lcd.setCursor(0, 2); lcd.print(" BERBASIS SENSOR ");
|
|
lcd.setCursor(0, 3); lcd.print(" E-NOSE CLOUD ");
|
|
}
|
|
|
|
void tampilkanMenuUtama() {
|
|
lcd.clear();
|
|
lcd.setCursor(0, 0); lcd.print(" MENU UTAMA ");
|
|
lcd.setCursor(0, 1); lcd.print(" Pilih buah untuk ");
|
|
lcd.setCursor(0, 2); lcd.print(" mulai deteksi ");
|
|
lcd.setCursor(0, 3); lcd.print("=> tekan tombol list");
|
|
}
|
|
|
|
void tampilkanDaftarBuah() {
|
|
lcd.clear();
|
|
for (int i = 0; i < 4; i++) {
|
|
lcd.setCursor(0, i);
|
|
if (i == buahTerpilihLokal) {
|
|
lcd.print("> " + daftarBuah[i]);
|
|
} else {
|
|
lcd.print(" " + daftarBuah[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
void tampilkanHasilBacaSensor() {
|
|
DataSensor data = bacaSemuaSensor();
|
|
|
|
lcd.clear();
|
|
lcd.setCursor(0, 0); lcd.print(daftarBuah[buahTerpilihLokal] + ": " + data.tingkatKematangan);
|
|
lcd.setCursor(0, 1); lcd.print("MQ3:" + String(data.nilaiPpmMQ3, 2) + "ppm");
|
|
lcd.setCursor(0, 2); lcd.print("MQ135:" + String(data.nilaiPpmMQ135, 2) + "ppm");
|
|
lcd.setCursor(0, 3); lcd.print("T:" + String(data.suhu, 1) + "C H:" + String(data.kelembaban, 1) + "%");
|
|
|
|
// Cek jika tingkat kematangan berubah
|
|
if (data.tingkatKematangan != tingkatKematanganSebelumnya) {
|
|
// Jika status kematangan berubah, bunyikan buzzer
|
|
if (data.tingkatKematangan == "Busuk") {
|
|
bunyikanBuzzer(1, 100); // Bunyikan buzzer untuk status "Busuk"
|
|
} else if (data.tingkatKematangan == "Matang") {
|
|
bunyikanBuzzer(1, 200); // Bunyikan buzzer untuk status "Matang"
|
|
}
|
|
// Update tingkat kematangan sebelumnya
|
|
tingkatKematanganSebelumnya = data.tingkatKematangan;
|
|
}
|
|
|
|
// Tampilkan data di Serial Monitor
|
|
Serial.println("=== DATA SENSOR LOKAL ===");
|
|
Serial.println("Buah: " + data.namaBuahTerpilih);
|
|
Serial.println("Tingkat Kematangan: " + data.tingkatKematangan);
|
|
Serial.println("Suhu: " + String(data.suhu, 1) + " °C");
|
|
Serial.println("Kelembaban: " + String(data.kelembaban, 1) + " %");
|
|
Serial.println("MQ3: " + String(data.nilaiPpmMQ3, 4) + " ppm");
|
|
Serial.println("MQ135: " + String(data.nilaiPpmMQ135, 4) + " ppm");
|
|
Serial.println("========================");
|
|
}
|
|
|
|
DataSensor bacaSemuaSensor() {
|
|
DataSensor data;
|
|
|
|
data.suhu = dht.readTemperature();
|
|
data.kelembaban = dht.readHumidity();
|
|
|
|
if (isnan(data.suhu) || isnan(data.kelembaban)) {
|
|
Serial.println("Error: Gagal membaca sensor DHT11!");
|
|
data.suhu = 0.0;
|
|
data.kelembaban = 0.0;
|
|
}
|
|
|
|
data.nilaiAnalogMQ3 = analogRead(PIN_MQ3);
|
|
data.nilaiAnalogMQ135 = analogRead(PIN_MQ135);
|
|
|
|
float Rs_MQ3 = hitungResistansiSensor(data.nilaiAnalogMQ3, TEGANGAN_VCC, RESISTANSI_BEBAN, RESOLUSI_ADC);
|
|
float rasioRsR0_MQ3 = Rs_MQ3 / MQ3_R0;
|
|
data.nilaiPpmMQ3 = konversiKePPM(rasioRsR0_MQ3, MQ3_SLOPE, MQ3_INTERCEPT);
|
|
|
|
float Rs_MQ135 = hitungResistansiSensor(data.nilaiAnalogMQ135, TEGANGAN_VCC, RESISTANSI_BEBAN, RESOLUSI_ADC);
|
|
float rasioRsR0_MQ135 = Rs_MQ135 / MQ135_R0;
|
|
data.nilaiPpmMQ135 = konversiKePPM(rasioRsR0_MQ135, MQ135_SLOPE, MQ135_INTERCEPT);
|
|
|
|
data.tingkatKematangan = tentukanKematangan(data.nilaiPpmMQ3, data.nilaiPpmMQ135, buahTerpilihLokal);
|
|
data.namaBuahTerpilih = daftarBuah[buahTerpilihLokal];
|
|
|
|
return data;
|
|
}
|
|
|
|
String tentukanKematangan(float nilaiPpmMQ3, float nilaiPpmMQ135, int indeksBuah) {
|
|
AmbangBatasBuah ambangBatas = daftarAmbangBatas[indeksBuah];
|
|
|
|
if (nilaiPpmMQ3 >= ambangBatas.busukMQ3Min || nilaiPpmMQ135 >= ambangBatas.busukMQ135Min) {
|
|
return "Busuk";
|
|
} else if ((nilaiPpmMQ3 >= ambangBatas.matangMQ3Min && nilaiPpmMQ3 <= ambangBatas.matangMQ3Maks) ||
|
|
(nilaiPpmMQ135 >= ambangBatas.matangMQ135Min && nilaiPpmMQ135 <= ambangBatas.matangMQ135Maks)) {
|
|
return "Matang";
|
|
} else {
|
|
return "Mentah";
|
|
}
|
|
}
|
|
|
|
void kelolaTombol() {
|
|
bool tekanTombol1 = digitalRead(PIN_TOMBOL1);
|
|
bool tekanTombol2 = digitalRead(PIN_TOMBOL2);
|
|
bool tekanTombol3 = digitalRead(PIN_TOMBOL3);
|
|
|
|
// Tombol 1 - Tampilkan daftar buah atau kembali ke menu
|
|
if (tekanTombol1 == LOW && kondisiTerakhirTombol1 == HIGH) {
|
|
if (statusBuahDipilih) {
|
|
statusBuahDipilih = false;
|
|
statusTampilDaftarBuah = false;
|
|
buahTerpilihLokal = 0;
|
|
tampilkanMenuUtama();
|
|
} else {
|
|
statusTampilDaftarBuah = true;
|
|
buahTerpilihLokal = 0;
|
|
tampilkanDaftarBuah();
|
|
}
|
|
}
|
|
|
|
// Tombol 2 - Navigasi ke bawah
|
|
if (tekanTombol2 == LOW && kondisiTerakhirTombol2 == HIGH && statusTampilDaftarBuah && !statusBuahDipilih) {
|
|
buahTerpilihLokal = (buahTerpilihLokal + 1) % 4;
|
|
tampilkanDaftarBuah();
|
|
}
|
|
|
|
// Tombol 3 - Pilih buah
|
|
if (tekanTombol3 == LOW && kondisiTerakhirTombol3 == HIGH && statusTampilDaftarBuah && !statusBuahDipilih) {
|
|
statusBuahDipilih = true;
|
|
statusTampilDaftarBuah = false;
|
|
|
|
lcd.clear();
|
|
lcd.setCursor(0, 0); lcd.print("Buah dipilih:");
|
|
lcd.setCursor(0, 1); lcd.print(daftarBuah[buahTerpilihLokal]);
|
|
lcd.setCursor(0, 2); lcd.print("Letakkan buah...");
|
|
lcd.setCursor(0, 3); lcd.print("Membaca sensor...");
|
|
|
|
delay(2000);
|
|
waktuTerakhirSensorDibaca = millis() - INTERVAL_SENSOR;
|
|
}
|
|
|
|
kondisiTerakhirTombol1 = tekanTombol1;
|
|
kondisiTerakhirTombol2 = tekanTombol2;
|
|
kondisiTerakhirTombol3 = tekanTombol3;
|
|
}
|
|
|
|
void perbaruiSensor() {
|
|
if (statusBuahDipilih) {
|
|
tampilkanHasilBacaSensor();
|
|
}
|
|
}
|
|
|
|
void bunyikanBuzzer(int jumlahBip, int durasi) {
|
|
for (int i = 0; i < jumlahBip; i++) {
|
|
digitalWrite(PIN_BUZZER, HIGH);
|
|
delay(durasi);
|
|
digitalWrite(PIN_BUZZER, LOW);
|
|
if (i < jumlahBip - 1) {
|
|
delay(200);
|
|
}
|
|
}
|
|
} |