#include <Wire.h>
#include <Arduino.h>
#include <ESP32Servo.h>
#include <Adafruit_GFX.h>
#include <ESP32Encoder.h>
#include <Adafruit_SSD1306.h>
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_ADDR 0x3C
#define ENCODER_DT 17
#define ENCODER_CLK 16
#define ENCODER_BTN 5
#define BUZZER_PIN 15
#define MAX_MAIN_MENU 3
#define VREF 3.3
#define ADC_RESOLUTION 4095.0
#define VOLTAGE_DIVIDER_RATIO 2.0
#define MQ9 34
#define MQ2 35
#define MQ7 32
#define MQ135 33
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);
ESP32Encoder encoder;
//ESP32PWM
ESP32PWM greenPWM;
ESP32PWM redPWM;
ESP32PWM greenPWM2;
ESP32PWM redPWM2;
ESP32PWM greenPWM3;
ESP32PWM redPWM3;
ESP32PWM greenPWM4;
ESP32PWM redPWM4;
const int greenPin = 26;
const int redPin = 25;
const int greenPin2 = 14;
const int redPin2 = 27;
const int greenPin3 = 13;
const int redPin3 = 12;
const int greenPin4 = 18;
const int redPin4 = 19;
int DelayTime = 1000; // Delay Time
int freq = 1000; // PWM frequency (1 kHz)
int resolution = 10; // 10-bit resolution (0-1023)
//encoder
long lastEncoderPos = 0;
int currentSelection = 0;
bool buttonPressed = false;
bool executeOnce = false;
//Sistem
unsigned long lastBlinkTime = 0;
bool ledState = LOW;
//Menu
int currentMenu = 0;
int activeSubMenu = -1;
int activeSystem = -1;
int dummySensorValue = 9;
int Loopcase3 = false;
String lastActionText = "";
String mainMenuNames[MAX_MAIN_MENU] = {"Pembacaan Emisi", "Pembersihan Sensor", "Inisialisasi System"};
const char* tips[] = {
"Tip: module ini lebih\nsensitif dari mantan.",
"Tahukah kamu? Sensor \nMQ butuh pemanasan \nkaya kopi di pagihari",
"Tip: kalo motor sudah\nberasap jangan di cek\ntapi di servis",
"Tip: Jangan lupa kali\nbrasi setiap setelah \nmembaca emisi",
"Tip: Kalibrasi alat \npada ruangan ber-AC \nagar hasil akurat"
};
int totalTips = sizeof(tips) / sizeof(tips[0]);
//sensors
float RL = 10.0;
// R0 = nilai Rs di udara bersih
float R0_MQ2 = 10.0;
float R0_MQ7 = 10.0;
float R0_MQ9 = 10.0;
float R0_MQ135 = 10.0;
// Konstanta dari datasheet
float a_MQ2 = 605.18, b_MQ2 = -2.074;
float a_MQ7 = 99.042, b_MQ7 = -1.518;
float a_MQ9 = 1000.0, b_MQ9 = -2.2;
float a_MQ135 = 110.47, b_MQ135 = -2.862;
float ppm_MQ2;
float ppm_MQ7;
float ppm_MQ9;
float ppm_MQ135;
String statusText = "OK";
bool cleaningMode = false;
int Check135 = 0;
int Check7 = 0;
int Check2 = 0;
int Check9 = 0;
bool Checks135 = false;
bool Checks7 = false;
bool Checks2 = false;
bool Checks9 = false;
int Sensorclean = false;
//fuzzy
String output_MQ2 = "";
String output_MQ7 = "";
String output_MQ9 = "";
String output_MQ135 = "";
struct FuzzyRange {
float aman_min;
float aman_max;
float waspada_min;
float waspada_mid;
float waspada_max;
float bahaya_min;
float bahaya_max;
};
// Global loading progress
int loading_progress = 0;
//bias
enum BiasMode {
BIAS_NORMAL, // 50:50 chance
BIAS_LOW, // Cenderung rendah
BIAS_HIGH // Cenderung tinggi
};
BiasMode currentBias = BIAS_LOW;
void IRAM_ATTR onButtonPress() {
static unsigned long lastPress = 0;
if (millis() - lastPress > 250) {
buttonPressed = true;
lastPress = millis();
}
}
void beep(int times = 1, int Dh = 30, int Dl = 50) {
for (int i = 0; i < times; i++) {
digitalWrite(BUZZER_PIN, HIGH);
delay(Dh);
digitalWrite(BUZZER_PIN, LOW);
delay(Dl);
}
}
void setup() {
Wire.begin(21, 22);
Serial.begin(115200);
// Allocate PWM timers
ESP32PWM::allocateTimer(0);
ESP32PWM::allocateTimer(1);
ESP32PWM::allocateTimer(2);
ESP32PWM::allocateTimer(3);
ESP32PWM::allocateTimer(4);
ESP32PWM::allocateTimer(5);
ESP32PWM::allocateTimer(6);
ESP32PWM::allocateTimer(7);
// Attach pins to PWM
greenPWM.attachPin(greenPin, freq, resolution);
redPWM.attachPin(redPin, freq, resolution);
greenPWM2.attachPin(greenPin2, freq, resolution);
redPWM2.attachPin(redPin2, freq, resolution);
greenPWM3.attachPin(greenPin3, freq, resolution);
redPWM3.attachPin(redPin3, freq, resolution);
greenPWM4.attachPin(greenPin4, freq, resolution);
redPWM4.attachPin(redPin4, freq, resolution);
// PinSetup
pinMode(2, OUTPUT);
pinMode(BUZZER_PIN, OUTPUT);
pinMode(ENCODER_BTN, INPUT_PULLUP);
// Encoder
attachInterrupt(digitalPinToInterrupt(ENCODER_BTN), onButtonPress, FALLING);
ESP32Encoder::useInternalWeakPullResistors = puType::up;
encoder.attachHalfQuad(ENCODER_DT, ENCODER_CLK);
encoder.setCount(0);
// OLEDSETUP
if (!display.begin(SSD1306_SWITCHCAPVCC, OLED_ADDR)) {
Serial.println(F("OLED failed"));
while (1);
}
randomSeed(analogRead(34)); // seed random
int tipIndex = random(totalTips);
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.setCursor(0, 0);
display.printf("Pengaturan suhu kerja\nsedang berjalan...\nTunggu beberapa saat\n");
display.println("");
display.println(tips[tipIndex]);
display.display();
// delay(2*60*1000); // 2 menit
delay(2*1000); // 2 DETIK
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.display();
delay(500);
}
void loop() {
long encoderPos = encoder.getCount() / 2;
FuzzyRange MQ2_range = { // MQ-2 → LPG / Alkohol / Asap
0, 40, // AMAN: 0–40 ppm
30, 60, 90, // WASPADA: segitiga fuzzy
80, 150 // BAHAYA: di atas 80+
};
FuzzyRange MQ7_range = { // MQ-7 → Karbon Monoksida (CO)
0, 20, // AMAN: 0–20 ppm
15, 35, 50, // WASPADA
45, 100 // BAHAYA
};
FuzzyRange MQ9_range = { // MQ-9 → CO + Propana + LPG
0, 30, // AMAN: 0–30 ppm
25, 60, 90, // WASPADA
85, 150 // BAHAYA
};
FuzzyRange MQ135_range = { // MQ-135 → CO2, NH3, Benzene, dll
0, 350, // AMAN: 0–350 ppm (CO2/NH3)
300, 600, 1000, // WASPADA
900, 2000 // BAHAYA
};
if (encoderPos != lastEncoderPos) {
// ====== [NAVIGASI MENU] ======
currentSelection += (encoderPos > lastEncoderPos) ? 1 : -1;
if (currentSelection < 0) currentSelection = 0;
if (currentMenu == 0 && currentSelection >= MAX_MAIN_MENU)
currentSelection = MAX_MAIN_MENU - 1;
else if (currentMenu == 1 && currentSelection >= 2)
currentSelection = 1;
beep();
lastEncoderPos = encoderPos;
}
// ====== [BUTTON INPUT / ENTER / SELECT] ======
if (buttonPressed) {
beep(2);
buttonPressed = false;
// ====== [MASUK SUBMENU] ======
if (currentMenu == 0) {
activeSubMenu = currentSelection;
currentMenu = 1;
currentSelection = 0;
} else {
if (activeSubMenu == 0) { // --- Submenu 1 ---
if (currentSelection == 0){
// 0–80%
progressBar(300, 80);
ppm_MQ2 = bacaPPM(MQ2, 0, 150);
ppm_MQ7 = bacaPPM(MQ7, 0, 100);
ppm_MQ9 = bacaPPM(MQ9, 0, 150);
ppm_MQ135 = bacaPPM(MQ135, 0, 2000);
fuzzyStatus("MQ-2", ppm_MQ2, MQ2_range, output_MQ2);
fuzzyStatus("MQ-7", ppm_MQ7, MQ7_range, output_MQ7);
fuzzyStatus("MQ-9", ppm_MQ9, MQ9_range, output_MQ9);
fuzzyStatus("MQ-135", ppm_MQ135, MQ135_range, output_MQ135);
activeSystem = 1;
// 80–100%
progressBar(300, 100);
} else backToMainMenu();
} else if (activeSubMenu == 1) { // --- Submenu 2 ---
if (currentSelection == 0) {
cleaningMode = !cleaningMode;
lastActionText = cleaningMode ?
"Cleaning dimulai..." :
"Cleaning dibatalkan.";
} else {
backToMainMenu();
Sensorclean = false;
}
} else if (activeSubMenu == 2) { // --- Submenu 3 ---
if (currentSelection == 0) {
LEDtest(2);
delay(200);
Check135 = analogRead(MQ135);
Check7 = analogRead(MQ7);
Check2 = analogRead(MQ2);
Check9 = analogRead(MQ9);
beep(1, 100, 25);
if (Check135 >= 5){
Checks135 = true;
MQ135LEDs(1);
} delay(500);
beep(2, 75, 50);
if (Check7 >= 5){
Checks7 = true;
MQ7LEDs(1);
} delay(500);
beep(3, 50, 75);
if (Check2 >= 5){
Checks2 = true;
MQ2LEDs(1);
} delay(500);
beep(4, 25, 100);
if (Check9 >= 5){
Checks9 = true;
MQ9LEDs(1);
} delay(500);
beep(4, 25, 100);
} else {
backToMainMenu();
}
}
}
}
displayMenu();
}
float bacaPPM(int pin, float minppm, float maxppm){
int raw = analogRead(pin); // 0–4095 (ESP32 ADC)
float scaled = (float)raw / 4095.0; // jadi 0.0 – 1.0
return scaled * (maxppm - minppm) + minppm;
}
// float bacaPPM(int pin, float R0, float a, float b) {
// int adc = analogRead(pin);
// float Vout = ((adc * VREF) / ADC_RESOLUTION) * VOLTAGE_DIVIDER_RATIO;
// if (adc < 50 || adc > 4000 || Vout <= 0.01) {
// return 0.0;
// }
// float Rs = RL * ((VREF * VOLTAGE_DIVIDER_RATIO - Vout) / Vout);
// float ratio = Rs / R0;
// return a * pow(ratio, b);
// }
int biasRandom(int low, int high, BiasMode mode) {
int range = high - low;
int r = random(0, 100); // peluang
switch (mode) {
case BIAS_LOW:
if (r < 80) return random(low, low + range / 3); // 70% ke rendah
else return random(low + range / 3, high); // sisanya normal
case BIAS_HIGH:
if (r < 50) return random(high - range / 3, high); // 70% ke tinggi
else return random(low, high - range / 3); // sisanya normal
case BIAS_NORMAL:
default:
return random(low, high); // normal full range
}
}