NATURAL SOUND JAV PROJECT
NATURAL SOUND JAV PROJECT
/*
* ============================================================
* ARDUINO UNO - 5 SENSOR + 5 LED PWM (UPDATE REAL-TIME)
* ============================================================
* Fitur:
* - Membaca 5 sensor setiap 1 detik.
* - Menampilkan semua nilai di Serial Monitor (9600 baud).
* - Menggerakkan LED secara linear sesuai nilai sensor.
* - Sensitif terhadap perubahan sekecil apapun (tanpa filter).
*
* Sensor:
* - Soil capacitive (A0)
* - MQ3 gas (A1)
* - LM35 suhu (A2)
* - DHT11 suhu & kelembapan (pin 2)
*
* LED PWM (kecerahan 0–255):
* - Soil → pin 3
* - DHT Suhu → pin 5
* - DHT Hum → pin 6
* - MQ3 → pin 9
* - LM35 → pin 10
* ============================================================
*/
#include <DHT.h>
#define DHTPIN 2
#define DHTTYPE DHT11
const int soilPin = A0;
const int mq3Pin = A1;
const int lm35Pin = A2;
const int ledSoil = 3;
const int ledDHTTemp = 5;
const int ledDHTHum = 6;
const int ledMQ3 = 9;
const int ledLM35 = 10;
DHT dht(DHTPIN, DHTTYPE);
void setup() {
Serial.begin(9600);
dht.begin();
pinMode(ledSoil, OUTPUT);
pinMode(ledDHTTemp, OUTPUT);
pinMode(ledDHTHum, OUTPUT);
pinMode(ledMQ3, OUTPUT);
pinMode(ledLM35, OUTPUT);
Serial.println("========================================");
Serial.println(" MONITORING REAL-TIME (setiap detik)");
Serial.println("========================================");
delay(1000);
}
void loop() {
// ---------- Baca sensor ----------
int soilRaw = analogRead(soilPin); // 0–1023
int mq3Raw = analogRead(mq3Pin); // 0–1023
int lm35Raw = analogRead(lm35Pin); // 0–1023
float h = dht.readHumidity(); // 0–100 %
float t = dht.readTemperature(); // °C
// Jika DHT gagal, set ke 0 dan beri peringatan
if (isnan(h) || isnan(t)) {
Serial.println("[WARNING] DHT11 error, nilai diset 0");
h = 0;
t = 0;
}
// Konversi LM35 ke suhu Celcius
float lm35Volt = lm35Raw * (5.0 / 1023.0);
float lm35Temp = lm35Volt * 100.0;
// ---------- Map ke PWM (0–255) secara linear ----------
int pwmSoil = map(soilRaw, 0, 1023, 0, 255);
pwmSoil = constrain(pwmSoil, 0, 255);
int pwmDHTTemp = map((int)t, 0, 50, 0, 255);
pwmDHTTemp = constrain(pwmDHTTemp, 0, 255);
int pwmDHTHum = map((int)h, 0, 100, 0, 255);
pwmDHTHum = constrain(pwmDHTHum, 0, 255);
int pwmMQ3 = map(mq3Raw, 0, 1023, 0, 255);
pwmMQ3 = constrain(pwmMQ3, 0, 255);
int pwmLM35 = map((int)lm35Temp, 0, 100, 0, 255);
pwmLM35 = constrain(pwmLM35, 0, 255);
// ---------- Tulis ke LED ----------
analogWrite(ledSoil, pwmSoil);
analogWrite(ledDHTTemp, pwmDHTTemp);
analogWrite(ledDHTHum, pwmDHTHum);
analogWrite(ledMQ3, pwmMQ3);
analogWrite(ledLM35, pwmLM35);
// ---------- Tampilkan semua data ke Serial Monitor ----------
Serial.println("----------------------------------------");
Serial.print("Soil : "); Serial.print(soilRaw);
Serial.print(" → PWM "); Serial.println(pwmSoil);
Serial.print("DHT Suhu: "); Serial.print(t, 1);
Serial.print("°C → PWM "); Serial.println(pwmDHTTemp);
Serial.print("DHT Hum : "); Serial.print(h, 1);
Serial.print("% → PWM "); Serial.println(pwmDHTHum);
Serial.print("MQ3 : "); Serial.print(mq3Raw);
Serial.print(" → PWM "); Serial.println(pwmMQ3);
Serial.print("LM35 : "); Serial.print(lm35Temp, 1);
Serial.print("°C → PWM "); Serial.println(pwmLM35);
Serial.println("----------------------------------------");
// ---------- Jeda 1 detik sebelum loop berikutnya ----------
delay(1000);
}
/*
* ====================================================================
* ARDUINO UNO - 5 SENSOR DENGAN POLA PEMBACAAN BERGANTIAN (500 ms)
* ====================================================================
* Konsep:
* - 5 sensor: Soil (A0), DHT11 suhu & hum (pin2), MQ3 (A1), LM35 (A2)
* - 5 LED PWM: Soil (3), DHT Suhu (5), DHT Hum (6), MQ3 (9), LM35 (10)
* - Setiap 500 ms, satu sensor dibaca (bergantian) sesuai urutan.
* - Urutan berubah setiap 5 kali pembacaan (2,5 detik) – total 50 variasi.
* - Serial Monitor mencetak data setiap kali pembacaan.
* - LED menyala dengan kecerahan sesuai nilai sensor terakhir.
* ====================================================================
*/
#include <DHT.h>
// ---------- PIN ----------
#define DHTPIN 2
#define DHTTYPE DHT11
const int soilPin = A0;
const int mq3Pin = A1;
const int lm35Pin = A2;
const int ledSoil = 3;
const int ledDHTTemp = 5;
const int ledDHTHum = 6;
const int ledMQ3 = 9;
const int ledLM35 = 10;
DHT dht(DHTPIN, DHTTYPE);
// ---------- VARIABEL GLOBAL ----------
const int NUM_SENSORS = 5;
const int NUM_SEQUENCES = 50;
// Indeks sensor: 0=Soil, 1=DHT Suhu, 2=DHT Hum, 3=MQ3, 4=LM35
int sequences[NUM_SEQUENCES][NUM_SENSORS];
int currentSeq = 0; // indeks urutan saat ini (0-49)
int step = 0; // langkah dalam urutan (0-4)
unsigned long lastReadTime = 0;
const long interval = 500; // 500 ms
// Nilai terakhir setiap sensor (untuk DHT dan lainnya)
int lastSoil = 0;
int lastMQ3 = 0;
int lastLM35 = 0;
float lastDHTTemp = 0;
float lastDHTHum = 0;
unsigned long lastDHTReadTime = 0;
const long dhtInterval = 1000; // baca DHT maksimal setiap 1 detik
// ---------- FUNGSI UNTUK MEMBANGUN 50 PERMUTASI ACAK ----------
void generateSequences() {
// Isi sequences dengan permutasi acak dari 0-4
int base[] = {0,1,2,3,4};
for (int i = 0; i < NUM_SEQUENCES; i++) {
// Salin base ke sequences[i]
for (int j = 0; j < NUM_SENSORS; j++) {
sequences[i][j] = base[j];
}
// Acak dengan Fisher-Yates
for (int j = NUM_SENSORS - 1; j > 0; j--) {
int r = random(j + 1);
int temp = sequences[i][j];
sequences[i][j] = sequences[i][r];
sequences[i][r] = temp;
}
}
}
// ---------- FUNGSI BACA SENSOR BERDASARKAN INDEKS ----------
void readSensor(int idx) {
switch(idx) {
case 0: { // Soil
lastSoil = analogRead(soilPin);
int pwm = map(lastSoil, 0, 1023, 0, 255);
pwm = constrain(pwm, 0, 255);
analogWrite(ledSoil, pwm);
Serial.print("Soil : "); Serial.print(lastSoil);
Serial.print(" → PWM "); Serial.println(pwm);
break;
}
case 1: { // DHT Suhu
// Baca DHT hanya jika sudah lewat interval
if (millis() - lastDHTReadTime >= dhtInterval) {
float h = dht.readHumidity();
float t = dht.readTemperature();
if (!isnan(h) && !isnan(t)) {
lastDHTHum = h;
lastDHTTemp = t;
lastDHTReadTime = millis();
} else {
Serial.println("[DHT] Error, gunakan nilai terakhir");
}
}
// Gunakan nilai terakhir
int pwm = map((int)lastDHTTemp, 0, 50, 0, 255);
pwm = constrain(pwm, 0, 255);
analogWrite(ledDHTTemp, pwm);
Serial.print("DHT Suhu: "); Serial.print(lastDHTTemp, 1);
Serial.print("°C → PWM "); Serial.println(pwm);
break;
}
case 2: { // DHT Kelembaban
// Sama seperti case 1, tetapi gunakan lastDHTHum
if (millis() - lastDHTReadTime >= dhtInterval) {
float h = dht.readHumidity();
float t = dht.readTemperature();
if (!isnan(h) && !isnan(t)) {
lastDHTHum = h;
lastDHTTemp = t;
lastDHTReadTime = millis();
} else {
Serial.println("[DHT] Error, gunakan nilai terakhir");
}
}
int pwm = map((int)lastDHTHum, 0, 100, 0, 255);
pwm = constrain(pwm, 0, 255);
analogWrite(ledDHTHum, pwm);
Serial.print("DHT Hum : "); Serial.print(lastDHTHum, 1);
Serial.print("% → PWM "); Serial.println(pwm);
break;
}
case 3: { // MQ3
lastMQ3 = analogRead(mq3Pin);
int pwm = map(lastMQ3, 0, 1023, 0, 255);
pwm = constrain(pwm, 0, 255);
analogWrite(ledMQ3, pwm);
Serial.print("MQ3 : "); Serial.print(lastMQ3);
Serial.print(" → PWM "); Serial.println(pwm);
break;
}
case 4: { // LM35
lastLM35 = analogRead(lm35Pin);
float volt = lastLM35 * (5.0 / 1023.0);
float temp = volt * 100.0;
int pwm = map((int)temp, 0, 100, 0, 255);
pwm = constrain(pwm, 0, 255);
analogWrite(ledLM35, pwm);
Serial.print("LM35 : "); Serial.print(temp, 1);
Serial.print("°C → PWM "); Serial.println(pwm);
break;
}
}
}
// ---------- SETUP ----------
void setup() {
Serial.begin(9600);
dht.begin();
pinMode(ledSoil, OUTPUT);
pinMode(ledDHTTemp, OUTPUT);
pinMode(ledDHTHum, OUTPUT);
pinMode(ledMQ3, OUTPUT);
pinMode(ledLM35, OUTPUT);
// Generate 50 urutan acak
randomSeed(analogRead(A5)); // seed dari pin kosong
generateSequences();
Serial.println("========================================");
Serial.println(" POLA PEMBACAAN SENSOR BERGANTIAN");
Serial.println(" 500 ms per sensor, 50 variasi urutan");
Serial.println("========================================");
delay(1000);
}
// ---------- LOOP ----------
void loop() {
unsigned long now = millis();
if (now - lastReadTime >= interval) {
lastReadTime = now;
// Ambil sensor yang harus dibaca dari urutan saat ini
int sensorIdx = sequences[currentSeq][step];
// Tampilkan header urutan dan langkah
Serial.print("[Urutan "); Serial.print(currentSeq+1);
Serial.print(" | Langkah "); Serial.print(step+1);
Serial.print(" dari 5] → ");
// Baca sensor dan update LED
readSensor(sensorIdx);
// Pindah ke langkah berikutnya
step++;
if (step >= NUM_SENSORS) {
step = 0;
// Pindah ke urutan berikutnya (50 variasi)
currentSeq++;
if (currentSeq >= NUM_SEQUENCES) {
currentSeq = 0; // loop kembali ke awal
}
// Tampilkan pergantian urutan
Serial.println("--- Ganti urutan ke " + String(currentSeq+1) + " ---");
}
}
}
/*
* ====================================================================
* ARDUINO UNO - 5 SENSOR DENGAN POLA PEMBACAAN BERGANTIAN (500 ms)
* ====================================================================
* Konsep:
* - 5 sensor: Soil (A0), DHT11 suhu & hum (pin2), MQ3 (A1), LM35 (A2)
* - 5 LED PWM: Soil (3), DHT Suhu (5), DHT Hum (6), MQ3 (9), LM35 (10)
* - Setiap 500 ms, satu sensor dibaca (bergantian) sesuai urutan.
* - Urutan berubah setiap 5 kali pembacaan (2,5 detik) – total 50 variasi.
* - Serial Monitor mencetak data setiap kali pembacaan.
* - LED menyala dengan kecerahan sesuai nilai sensor terakhir.
* ====================================================================
*/
#include <DHT.h>
// ---------- PIN ----------
#define DHTPIN 2
#define DHTTYPE DHT11
const int soilPin = A0;
const int mq3Pin = A1;
const int lm35Pin = A2;
const int ledSoil = 3;
const int ledDHTTemp = 5;
const int ledDHTHum = 6;
const int ledMQ3 = 9;
const int ledLM35 = 10;
DHT dht(DHTPIN, DHTTYPE);
// ---------- VARIABEL GLOBAL ----------
const int NUM_SENSORS = 5;
const int NUM_SEQUENCES = 50;
// Indeks sensor: 0=Soil, 1=DHT Suhu, 2=DHT Hum, 3=MQ3, 4=LM35
int sequences[NUM_SEQUENCES][NUM_SENSORS];
int currentSeq = 0; // indeks urutan saat ini (0-49)
int step = 0; // langkah dalam urutan (0-4)
unsigned long lastReadTime = 0;
const long interval = 500; // 500 ms
// Nilai terakhir setiap sensor (untuk DHT dan lainnya)
int lastSoil = 0;
int lastMQ3 = 0;
int lastLM35 = 0;
float lastDHTTemp = 0;
float lastDHTHum = 0;
unsigned long lastDHTReadTime = 0;
const long dhtInterval = 1000; // baca DHT maksimal setiap 1 detik
// ---------- FUNGSI UNTUK MEMBANGUN 50 PERMUTASI ACAK ----------
void generateSequences() {
// Isi sequences dengan permutasi acak dari 0-4
int base[] = {0,1,2,3,4};
for (int i = 0; i < NUM_SEQUENCES; i++) {
// Salin base ke sequences[i]
for (int j = 0; j < NUM_SENSORS; j++) {
sequences[i][j] = base[j];
}
// Acak dengan Fisher-Yates
for (int j = NUM_SENSORS - 1; j > 0; j--) {
int r = random(j + 1);
int temp = sequences[i][j];
sequences[i][j] = sequences[i][r];
sequences[i][r] = temp;
}
}
}
// ---------- FUNGSI BACA SENSOR BERDASARKAN INDEKS ----------
void readSensor(int idx) {
switch(idx) {
case 0: { // Soil
lastSoil = analogRead(soilPin);
int pwm = map(lastSoil, 0, 1023, 0, 255);
pwm = constrain(pwm, 0, 255);
analogWrite(ledSoil, pwm);
Serial.print("Soil : "); Serial.print(lastSoil);
Serial.print(" → PWM "); Serial.println(pwm);
break;
}
case 1: { // DHT Suhu
// Baca DHT hanya jika sudah lewat interval
if (millis() - lastDHTReadTime >= dhtInterval) {
float h = dht.readHumidity();
float t = dht.readTemperature();
if (!isnan(h) && !isnan(t)) {
lastDHTHum = h;
lastDHTTemp = t;
lastDHTReadTime = millis();
} else {
Serial.println("[DHT] Error, gunakan nilai terakhir");
}
}
// Gunakan nilai terakhir
int pwm = map((int)lastDHTTemp, 0, 50, 0, 255);
pwm = constrain(pwm, 0, 255);
analogWrite(ledDHTTemp, pwm);
Serial.print("DHT Suhu: "); Serial.print(lastDHTTemp, 1);
Serial.print("°C → PWM "); Serial.println(pwm);
break;
}
case 2: { // DHT Kelembaban
// Sama seperti case 1, tetapi gunakan lastDHTHum
if (millis() - lastDHTReadTime >= dhtInterval) {
float h = dht.readHumidity();
float t = dht.readTemperature();
if (!isnan(h) && !isnan(t)) {
lastDHTHum = h;
lastDHTTemp = t;
lastDHTReadTime = millis();
} else {
Serial.println("[DHT] Error, gunakan nilai terakhir");
}
}
int pwm = map((int)lastDHTHum, 0, 100, 0, 255);
pwm = constrain(pwm, 0, 255);
analogWrite(ledDHTHum, pwm);
Serial.print("DHT Hum : "); Serial.print(lastDHTHum, 1);
Serial.print("% → PWM "); Serial.println(pwm);
break;
}
case 3: { // MQ3
lastMQ3 = analogRead(mq3Pin);
int pwm = map(lastMQ3, 0, 1023, 0, 255);
pwm = constrain(pwm, 0, 255);
analogWrite(ledMQ3, pwm);
Serial.print("MQ3 : "); Serial.print(lastMQ3);
Serial.print(" → PWM "); Serial.println(pwm);
break;
}
case 4: { // LM35
lastLM35 = analogRead(lm35Pin);
float volt = lastLM35 * (5.0 / 1023.0);
float temp = volt * 100.0;
int pwm = map((int)temp, 0, 100, 0, 255);
pwm = constrain(pwm, 0, 255);
analogWrite(ledLM35, pwm);
Serial.print("LM35 : "); Serial.print(temp, 1);
Serial.print("°C → PWM "); Serial.println(pwm);
break;
}
}
}
// ---------- SETUP ----------
void setup() {
Serial.begin(9600);
dht.begin();
pinMode(ledSoil, OUTPUT);
pinMode(ledDHTTemp, OUTPUT);
pinMode(ledDHTHum, OUTPUT);
pinMode(ledMQ3, OUTPUT);
pinMode(ledLM35, OUTPUT);
// Generate 50 urutan acak
randomSeed(analogRead(A5)); // seed dari pin kosong
generateSequences();
Serial.println("========================================");
Serial.println(" POLA PEMBACAAN SENSOR BERGANTIAN");
Serial.println(" 500 ms per sensor, 50 variasi urutan");
Serial.println("========================================");
delay(1000);
}
// ---------- LOOP ----------
void loop() {
unsigned long now = millis();
if (now - lastReadTime >= interval) {
lastReadTime = now;
// Ambil sensor yang harus dibaca dari urutan saat ini
int sensorIdx = sequences[currentSeq][step];
// Tampilkan header urutan dan langkah
Serial.print("[Urutan "); Serial.print(currentSeq+1);
Serial.print(" | Langkah "); Serial.print(step+1);
Serial.print(" dari 5] → ");
// Baca sensor dan update LED
readSensor(sensorIdx);
// Pindah ke langkah berikutnya
step++;
if (step >= NUM_SENSORS) {
step = 0;
// Pindah ke urutan berikutnya (50 variasi)
currentSeq++;
if (currentSeq >= NUM_SEQUENCES) {
currentSeq = 0; // loop kembali ke awal
}
// Tampilkan pergantian urutan
Serial.println("--- Ganti urutan ke " + String(currentSeq+1) + " ---");
}
}
}
===============================================================
Pembuatan 50 Urutan Acak
Di setup(), fungsi generateSequences() membuat 50 permutasi berbeda dari indeks 0..4 (0=Soil, 1=DHT Suhu, 2=DHT Hum, 3=MQ3, 4=LM35). Setiap urutan adalah array 5 angka yang menentukan urutan pembacaan sensor.
Timer 500 ms
Menggunakan millis() non-blocking. Setiap 500 ms, program mengambil satu sensor dari urutan saat ini (sequences[currentSeq][step]) dan membacanya.
Pembacaan Sensor
Sensor analog (Soil, MQ3, LM35) dibaca langsung setiap kali gilirannya.
DHT11 dibaca hanya jika sudah lewat 1 detik dari pembacaan terakhir (untuk menghindari error). Jika belum waktunya, program menggunakan nilai terakhir yang tersimpan (lastDHTTemp, lastDHTHum). Ini memastikan data tetap tersedia tanpa mengganggu ritme 500 ms.
Update LED
Setelah membaca, nilai sensor dipetakan secara linear ke PWM 0–255 dan ditulis ke pin LED yang sesuai. LED akan menyala dengan kecerahan sesuai nilai terakhir sensor tersebut.
Pergantian Urutan
Setelah 5 langkah (semua sensor dalam satu urutan selesai dibaca), step kembali ke 0 dan currentSeq bertambah, sehingga urutan pembacaan berubah ke variasi berikutnya. Ada 50 variasi, setelah itu kembali ke variasi pertama.
Serial Monitor
Setiap kali pembacaan (500 ms), Serial Monitor menampilkan sensor yang dibaca, nilai mentah, dan nilai PWM. Saat urutan berganti, muncul pesan "Ganti urutan ke X". Ini membuat tampilan selalu bergerak dan informatif.
/*
* ====================================================================
* POLA PEMBACAAN SENSOR BERGANTIAN DENGAN EFEK FADE-IN / FADE-OUT
* Periode: 2 detik per sensor, urutan tetap.
* LED menyala naik-turun membentuk gelombang segitiga.
* ====================================================================
* Sensor dan LED:
* - DHT11 suhu (pin 2) → LED pin 5
* - DHT11 hum (pin 2) → LED pin 6
* - LM35 (A2) → LED pin 10
* - Soil (A0) → LED pin 3
* - MQ3 (A1) → LED pin 9
* ====================================================================
*/
#include <DHT.h>
#define DHTPIN 2
#define DHTTYPE DHT11
// Pin sensor
const int soilPin = A0;
const int mq3Pin = A1;
const int lm35Pin = A2;
// Pin LED (PWM)
const int ledSoil = 3;
const int ledDHTTemp = 5;
const int ledDHTHum = 6;
const int ledMQ3 = 9;
const int ledLM35 = 10;
DHT dht(DHTPIN, DHTTYPE);
// ---------- Konfigurasi waktu ----------
const unsigned long SLOT_DURATION = 2000; // 2 detik per sensor
const unsigned long UPDATE_INTERVAL = 50; // update LED setiap 50 ms agar halus
// Urutan sensor (0=DHT Suhu, 1=DHT Hum, 2=LM35, 3=Soil, 4=MQ3)
const int sensorOrder[] = {0, 1, 2, 3, 4};
const int NUM_SENSORS = 5;
// Variabel waktu
unsigned long slotStartTime = 0;
int currentSlotIndex = 0; // indeks dalam sensorOrder (0-4)
unsigned long lastUpdateTime = 0;
// Nilai target PWM untuk setiap sensor (0-255)
int targetPWM[NUM_SENSORS] = {0, 0, 0, 0, 0};
// Nilai sensor terakhir untuk ditampilkan
float lastDHTTemp = 0;
float lastDHTHum = 0;
int lastSoil = 0;
int lastMQ3 = 0;
float lastLM35Temp = 0;
void setup() {
Serial.begin(9600);
dht.begin();
pinMode(ledSoil, OUTPUT);
pinMode(ledDHTTemp, OUTPUT);
pinMode(ledDHTHum, OUTPUT);
pinMode(ledMQ3, OUTPUT);
pinMode(ledLM35, OUTPUT);
// Matikan semua LED awal
analogWrite(ledSoil, 0);
analogWrite(ledDHTTemp, 0);
analogWrite(ledDHTHum, 0);
analogWrite(ledMQ3, 0);
analogWrite(ledLM35, 0);
Serial.println("========================================");
Serial.println(" POLA FADE-IN / FADE-OUT PER 2 DETIK");
Serial.println(" Urutan: DHT Suhu → DHT Hum → LM35 → Soil → MQ3");
Serial.println("========================================");
// Inisialisasi slot pertama
slotStartTime = millis();
readSensorForSlot(sensorOrder[0]);
currentSlotIndex = 0;
}
void loop() {
unsigned long now = millis();
// Cek apakah sudah waktunya ganti slot (2 detik)
if (now - slotStartTime >= SLOT_DURATION) {
slotStartTime = now;
currentSlotIndex = (currentSlotIndex + 1) % NUM_SENSORS;
int sensorIdx = sensorOrder[currentSlotIndex];
readSensorForSlot(sensorIdx); // baca sensor untuk slot baru
Serial.println("--- Ganti ke sensor berikutnya ---");
}
// Update LED setiap UPDATE_INTERVAL ms
if (now - lastUpdateTime >= UPDATE_INTERVAL) {
lastUpdateTime = now;
updateAllLEDs();
}
}
// ---------- Fungsi membaca sensor untuk slot tertentu ----------
void readSensorForSlot(int sensorIdx) {
switch (sensorIdx) {
case 0: { // DHT Suhu
float h = dht.readHumidity();
float t = dht.readTemperature();
if (!isnan(h) && !isnan(t)) {
lastDHTHum = h;
lastDHTTemp = t;
} else {
Serial.println("[DHT] Error, gunakan nilai sebelumnya");
}
// Map suhu 0-50°C ke PWM 0-255
targetPWM[0] = map((int)lastDHTTemp, 0, 50, 0, 255);
targetPWM[0] = constrain(targetPWM[0], 0, 255);
Serial.print("DHT Suhu: "); Serial.print(lastDHTTemp, 1);
Serial.print("°C, target PWM = "); Serial.println(targetPWM[0]);
break;
}
case 1: { // DHT Kelembaban
// Baca ulang DHT untuk mendapatkan data terbaru
float h = dht.readHumidity();
float t = dht.readTemperature();
if (!isnan(h) && !isnan(t)) {
lastDHTHum = h;
lastDHTTemp = t;
} else {
Serial.println("[DHT] Error, gunakan nilai sebelumnya");
}
targetPWM[1] = map((int)lastDHTHum, 0, 100, 0, 255);
targetPWM[1] = constrain(targetPWM[1], 0, 255);
Serial.print("DHT Hum : "); Serial.print(lastDHTHum, 1);
Serial.print("%, target PWM = "); Serial.println(targetPWM[1]);
break;
}
case 2: { // LM35
int raw = analogRead(lm35Pin);
float volt = raw * (5.0 / 1023.0);
lastLM35Temp = volt * 100.0;
targetPWM[2] = map((int)lastLM35Temp, 0, 100, 0, 255);
targetPWM[2] = constrain(targetPWM[2], 0, 255);
Serial.print("LM35 : "); Serial.print(lastLM35Temp, 1);
Serial.print("°C, target PWM = "); Serial.println(targetPWM[2]);
break;
}
case 3: { // Soil
lastSoil = analogRead(soilPin);
targetPWM[3] = map(lastSoil, 0, 1023, 0, 255);
targetPWM[3] = constrain(targetPWM[3], 0, 255);
Serial.print("Soil : "); Serial.print(lastSoil);
Serial.print(", target PWM = "); Serial.println(targetPWM[3]);
break;
}
case 4: { // MQ3
lastMQ3 = analogRead(mq3Pin);
targetPWM[4] = map(lastMQ3, 0, 1023, 0, 255);
targetPWM[4] = constrain(targetPWM[4], 0, 255);
Serial.print("MQ3 : "); Serial.print(lastMQ3);
Serial.print(", target PWM = "); Serial.println(targetPWM[4]);
break;
}
}
}
// ---------- Fungsi update semua LED dengan efek fade naik-turun ----------
void updateAllLEDs() {
unsigned long now = millis();
unsigned long elapsed = now - slotStartTime; // 0 ~ 2000 ms
// Hitung faktor posisi dalam slot (0.0 - 1.0)
float t = (float)elapsed / SLOT_DURATION; // 0~1
// Buat envelope segitiga: naik dari 0 ke 1 pada t=0->0.5, turun dari 1 ke 0 pada t=0.5->1
float envelope;
if (t <= 0.5) {
envelope = t / 0.5; // 0 → 1
} else {
envelope = (1.0 - t) / 0.5; // 1 → 0
}
// Update setiap LED dengan target masing-masing dikali envelope
// Hanya LED yang sesuai dengan sensor aktif yang akan menyala (yang lain tetap 0)
// Namun karena kita ingin semua LED mati kecuali yang sedang aktif, kita set yang lain ke 0.
// Tapi efeknya lebih baik jika hanya LED sensor aktif yang menyala.
// Kita bisa set semua LED = target * envelope, tetapi hanya untuk sensor aktif.
// Untuk kemudahan, kita atur langsung:
// Matikan semua LED terlebih dahulu
analogWrite(ledSoil, 0);
analogWrite(ledDHTTemp, 0);
analogWrite(ledDHTHum, 0);
analogWrite(ledMQ3, 0);
analogWrite(ledLM35, 0);
// Nyalakan LED sesuai sensor aktif
int activeSensor = sensorOrder[currentSlotIndex];
int pwmValue = (int)(targetPWM[activeSensor] * envelope);
pwmValue = constrain(pwmValue, 0, 255);
switch (activeSensor) {
case 0: analogWrite(ledDHTTemp, pwmValue); break;
case 1: analogWrite(ledDHTHum, pwmValue); break;
case 2: analogWrite(ledLM35, pwmValue); break;
case 3: analogWrite(ledSoil, pwmValue); break;
case 4: analogWrite(ledMQ3, pwmValue); break;
}
// Optional: tampilkan nilai PWM di Serial setiap 100 ms untuk debugging
// (cukup tampilkan setiap 200 ms agar tidak terlalu penuh)
static unsigned long lastPrint = 0;
if (now - lastPrint >= 200) {
lastPrint = now;
Serial.print("Slot: "); Serial.print(activeSensor);
Serial.print(" | PWM: "); Serial.println(pwmValue);
}
}
===========================================================================================================================================================
Slot Waktu: Setiap sensor mendapat 2 detik penuh.
slotStartTime mencatat awal slot.
SLOT_DURATION = 2000 ms.
Pembacaan Sensor: Di awal setiap slot, fungsi readSensorForSlot() membaca sensor yang sesuai dan menyimpan nilai target PWM dalam array targetPWM[].
DHT11 dibaca ulang setiap kali slot suhu atau kelembapan tiba (dengan jeda otomatis >1 detik karena slot 2 detik).
Sensor analog dibaca langsung.
Envelope Segitiga:
elapsed adalah waktu yang telah berlalu sejak awal slot (0–2000 ms).
t = elapsed / 2000 → 0.0 hingga 1.0.
Jika t <= 0.5 (0–1 detik), envelope = t / 0.5 (naik 0→1).
Jika t > 0.5 (1–2 detik), envelope = (1 - t) / 0.5 (turun 1→0).
Ini menghasilkan bentuk segitiga sempurna.
Update LED: Setiap 50 ms, fungsi updateAllLEDs() menghitung pwmValue = targetPWM[activeSensor] * envelope dan menulis ke pin LED yang sesuai.
Hanya LED sensor aktif yang menyala; yang lain dimatikan.
Serial Monitor mencetak nilai PWM setiap 200 ms agar tidak terlalu padat.
Pergantian Sensor: Setelah 2 detik, currentSlotIndex bertambah dan sensor berikutnya diaktifkan, sehingga LED berganti secara otomatis.
LED menyala dari gelap ke terang secara halus, mencapai puncak di tengah periode, lalu meredup kembali.
Irama tetap karena durasi sama untuk semua sensor (2 detik).
Nilai puncak mencerminkan kondisi sensor aktual, sehingga variasi lingkungan menghasilkan pola cahaya yang berbeda-beda.
Serial Monitor membantu Anda memonitor setiap transisi.
/*
* ====================================================================
* POLA PEMBACAAN SENSOR BERGANTIAN DENGAN EFEK FADE-IN / FADE-OUT
* Periode: 2 detik per sensor, urutan tetap.
* LED menyala naik-turun membentuk gelombang segitiga.
* ====================================================================
* Sensor dan LED:
* - DHT11 suhu (pin 2) → LED pin 5
* - DHT11 hum (pin 2) → LED pin 6
* - LM35 (A2) → LED pin 10
* - Soil (A0) → LED pin 3
* - MQ3 (A1) → LED pin 9
* ====================================================================
*/
#include <DHT.h>
#define DHTPIN 2
#define DHTTYPE DHT11
// Pin sensor
const int soilPin = A0;
const int mq3Pin = A1;
const int lm35Pin = A2;
// Pin LED (PWM)
const int ledSoil = 3;
const int ledDHTTemp = 5;
const int ledDHTHum = 6;
const int ledMQ3 = 9;
const int ledLM35 = 10;
DHT dht(DHTPIN, DHTTYPE);
// ---------- Konfigurasi waktu ----------
const unsigned long SLOT_DURATION = 2000; // 2 detik per sensor
const unsigned long UPDATE_INTERVAL = 50; // update LED setiap 50 ms agar halus
// Urutan sensor (0=DHT Suhu, 1=DHT Hum, 2=LM35, 3=Soil, 4=MQ3)
const int sensorOrder[] = {0, 1, 2, 3, 4};
const int NUM_SENSORS = 5;
// Variabel waktu
unsigned long slotStartTime = 0;
int currentSlotIndex = 0; // indeks dalam sensorOrder (0-4)
unsigned long lastUpdateTime = 0;
// Nilai target PWM untuk setiap sensor (0-255)
int targetPWM[NUM_SENSORS] = {0, 0, 0, 0, 0};
// Nilai sensor terakhir untuk ditampilkan
float lastDHTTemp = 0;
float lastDHTHum = 0;
int lastSoil = 0;
int lastMQ3 = 0;
float lastLM35Temp = 0;
void setup() {
Serial.begin(9600);
dht.begin();
pinMode(ledSoil, OUTPUT);
pinMode(ledDHTTemp, OUTPUT);
pinMode(ledDHTHum, OUTPUT);
pinMode(ledMQ3, OUTPUT);
pinMode(ledLM35, OUTPUT);
// Matikan semua LED awal
analogWrite(ledSoil, 0);
analogWrite(ledDHTTemp, 0);
analogWrite(ledDHTHum, 0);
analogWrite(ledMQ3, 0);
analogWrite(ledLM35, 0);
Serial.println("========================================");
Serial.println(" POLA FADE-IN / FADE-OUT PER 2 DETIK");
Serial.println(" Urutan: DHT Suhu → DHT Hum → LM35 → Soil → MQ3");
Serial.println("========================================");
// Inisialisasi slot pertama
slotStartTime = millis();
readSensorForSlot(sensorOrder[0]);
currentSlotIndex = 0;
}
void loop() {
unsigned long now = millis();
// Cek apakah sudah waktunya ganti slot (2 detik)
if (now - slotStartTime >= SLOT_DURATION) {
slotStartTime = now;
currentSlotIndex = (currentSlotIndex + 1) % NUM_SENSORS;
int sensorIdx = sensorOrder[currentSlotIndex];
readSensorForSlot(sensorIdx); // baca sensor untuk slot baru
Serial.println("--- Ganti ke sensor berikutnya ---");
}
// Update LED setiap UPDATE_INTERVAL ms
if (now - lastUpdateTime >= UPDATE_INTERVAL) {
lastUpdateTime = now;
updateAllLEDs();
}
}
// ---------- Fungsi membaca sensor untuk slot tertentu ----------
void readSensorForSlot(int sensorIdx) {
switch (sensorIdx) {
case 0: { // DHT Suhu
float h = dht.readHumidity();
float t = dht.readTemperature();
if (!isnan(h) && !isnan(t)) {
lastDHTHum = h;
lastDHTTemp = t;
} else {
Serial.println("[DHT] Error, gunakan nilai sebelumnya");
}
// Map suhu 0-50°C ke PWM 0-255
targetPWM[0] = map((int)lastDHTTemp, 0, 50, 0, 255);
targetPWM[0] = constrain(targetPWM[0], 0, 255);
Serial.print("DHT Suhu: "); Serial.print(lastDHTTemp, 1);
Serial.print("°C, target PWM = "); Serial.println(targetPWM[0]);
break;
}
case 1: { // DHT Kelembaban
// Baca ulang DHT untuk mendapatkan data terbaru
float h = dht.readHumidity();
float t = dht.readTemperature();
if (!isnan(h) && !isnan(t)) {
lastDHTHum = h;
lastDHTTemp = t;
} else {
Serial.println("[DHT] Error, gunakan nilai sebelumnya");
}
targetPWM[1] = map((int)lastDHTHum, 0, 100, 0, 255);
targetPWM[1] = constrain(targetPWM[1], 0, 255);
Serial.print("DHT Hum : "); Serial.print(lastDHTHum, 1);
Serial.print("%, target PWM = "); Serial.println(targetPWM[1]);
break;
}
case 2: { // LM35
int raw = analogRead(lm35Pin);
float volt = raw * (5.0 / 1023.0);
lastLM35Temp = volt * 100.0;
targetPWM[2] = map((int)lastLM35Temp, 0, 100, 0, 255);
targetPWM[2] = constrain(targetPWM[2], 0, 255);
Serial.print("LM35 : "); Serial.print(lastLM35Temp, 1);
Serial.print("°C, target PWM = "); Serial.println(targetPWM[2]);
break;
}
case 3: { // Soil
lastSoil = analogRead(soilPin);
targetPWM[3] = map(lastSoil, 0, 1023, 0, 255);
targetPWM[3] = constrain(targetPWM[3], 0, 255);
Serial.print("Soil : "); Serial.print(lastSoil);
Serial.print(", target PWM = "); Serial.println(targetPWM[3]);
break;
}
case 4: { // MQ3
lastMQ3 = analogRead(mq3Pin);
targetPWM[4] = map(lastMQ3, 0, 1023, 0, 255);
targetPWM[4] = constrain(targetPWM[4], 0, 255);
Serial.print("MQ3 : "); Serial.print(lastMQ3);
Serial.print(", target PWM = "); Serial.println(targetPWM[4]);
break;
}
}
}
// ---------- Fungsi update semua LED dengan efek fade naik-turun ----------
void updateAllLEDs() {
unsigned long now = millis();
unsigned long elapsed = now - slotStartTime; // 0 ~ 2000 ms
// Hitung faktor posisi dalam slot (0.0 - 1.0)
float t = (float)elapsed / SLOT_DURATION; // 0~1
// Buat envelope segitiga: naik dari 0 ke 1 pada t=0->0.5, turun dari 1 ke 0 pada t=0.5->1
float envelope;
if (t <= 0.5) {
envelope = t / 0.5; // 0 → 1
} else {
envelope = (1.0 - t) / 0.5; // 1 → 0
}
// Update setiap LED dengan target masing-masing dikali envelope
// Hanya LED yang sesuai dengan sensor aktif yang akan menyala (yang lain tetap 0)
// Namun karena kita ingin semua LED mati kecuali yang sedang aktif, kita set yang lain ke 0.
// Tapi efeknya lebih baik jika hanya LED sensor aktif yang menyala.
// Kita bisa set semua LED = target * envelope, tetapi hanya untuk sensor aktif.
// Untuk kemudahan, kita atur langsung:
// Matikan semua LED terlebih dahulu
analogWrite(ledSoil, 0);
analogWrite(ledDHTTemp, 0);
analogWrite(ledDHTHum, 0);
analogWrite(ledMQ3, 0);
analogWrite(ledLM35, 0);
// Nyalakan LED sesuai sensor aktif
int activeSensor = sensorOrder[currentSlotIndex];
int pwmValue = (int)(targetPWM[activeSensor] * envelope);
pwmValue = constrain(pwmValue, 0, 255);
switch (activeSensor) {
case 0: analogWrite(ledDHTTemp, pwmValue); break;
case 1: analogWrite(ledDHTHum, pwmValue); break;
case 2: analogWrite(ledLM35, pwmValue); break;
case 3: analogWrite(ledSoil, pwmValue); break;
case 4: analogWrite(ledMQ3, pwmValue); break;
}
// Optional: tampilkan nilai PWM di Serial setiap 100 ms untuk debugging
// (cukup tampilkan setiap 200 ms agar tidak terlalu penuh)
static unsigned long lastPrint = 0;
if (now - lastPrint >= 200) {
lastPrint = now;
Serial.print("Slot: "); Serial.print(activeSensor);
Serial.print(" | PWM: "); Serial.println(pwmValue);
}
}
===========================================================================================================================================================
Slot Waktu: Setiap sensor mendapat 2 detik penuh.
slotStartTime mencatat awal slot.
SLOT_DURATION = 2000 ms.
Pembacaan Sensor: Di awal setiap slot, fungsi readSensorForSlot() membaca sensor yang sesuai dan menyimpan nilai target PWM dalam array targetPWM[].
DHT11 dibaca ulang setiap kali slot suhu atau kelembapan tiba (dengan jeda otomatis >1 detik karena slot 2 detik).
Sensor analog dibaca langsung.
Envelope Segitiga:
elapsed adalah waktu yang telah berlalu sejak awal slot (0–2000 ms).
t = elapsed / 2000 → 0.0 hingga 1.0.
Jika t <= 0.5 (0–1 detik), envelope = t / 0.5 (naik 0→1).
Jika t > 0.5 (1–2 detik), envelope = (1 - t) / 0.5 (turun 1→0).
Ini menghasilkan bentuk segitiga sempurna.
Update LED: Setiap 50 ms, fungsi updateAllLEDs() menghitung pwmValue = targetPWM[activeSensor] * envelope dan menulis ke pin LED yang sesuai.
Hanya LED sensor aktif yang menyala; yang lain dimatikan.
Serial Monitor mencetak nilai PWM setiap 200 ms agar tidak terlalu padat.
Pergantian Sensor: Setelah 2 detik, currentSlotIndex bertambah dan sensor berikutnya diaktifkan, sehingga LED berganti secara otomatis.
LED menyala dari gelap ke terang secara halus, mencapai puncak di tengah periode, lalu meredup kembali.
Irama tetap karena durasi sama untuk semua sensor (2 detik).
Nilai puncak mencerminkan kondisi sensor aktual, sehingga variasi lingkungan menghasilkan pola cahaya yang berbeda-beda.
Serial Monitor membantu Anda memonitor setiap transisi.
/*
* ====================================================================
* SEMUA LED FADE-IN / FADE-OUT BERSAMAAN
* Setiap 2 detik, semua sensor dibaca, lalu semua LED naik-turun
* bersama dengan intensitas proporsional terhadap nilai masing-masing.
* ====================================================================
* Sensor: DHT11 (suhu & hum), LM35, Soil, MQ3
* LED: masing-masing pin PWM
* ====================================================================
*/
#include <DHT.h>
#define DHTPIN 2
#define DHTTYPE DHT11
// Pin sensor
const int soilPin = A0;
const int mq3Pin = A1;
const int lm35Pin = A2;
// Pin LED
const int ledSoil = 3;
const int ledDHTTemp = 5;
const int ledDHTHum = 6;
const int ledMQ3 = 9;
const int ledLM35 = 10;
DHT dht(DHTPIN, DHTTYPE);
// Waktu
const unsigned long CYCLE_DURATION = 2000; // 2 detik per siklus naik-turun
const unsigned long READ_INTERVAL = 2000; // baca sensor setiap 2 detik juga
const unsigned long UPDATE_INTERVAL = 50; // update LED setiap 50 ms
unsigned long cycleStartTime = 0;
unsigned long lastReadTime = 0;
unsigned long lastUpdateTime = 0;
// Target PWM untuk setiap sensor (0-255)
int targetPWM[5] = {0,0,0,0,0}; // indeks: 0=DHT suhu, 1=DHT hum, 2=LM35, 3=Soil, 4=MQ3
// Nilai sensor untuk tampilan
float dhtTemp = 0, dhtHum = 0, lm35Temp = 0;
int soilVal = 0, mq3Val = 0;
// ---------- Fungsi mapping khusus ----------
int mapSensorToPWM(int sensorIdx, float value) {
int pwm = 0;
switch (sensorIdx) {
case 0: pwm = map((int)value, 25, 40, 0, 255); break; // DHT suhu
case 1: pwm = map((int)value, 50, 90, 0, 255); break; // DHT hum
case 2: pwm = map((int)value, 25, 40, 0, 255); break; // LM35
case 3: pwm = map((int)value, 270, 700, 0, 255); break; // Soil
case 4: pwm = map((int)value, 300, 500, 0, 255); break; // MQ3
}
return constrain(pwm, 0, 255);
}
// ---------- Baca semua sensor dan update target ----------
void readAllSensors() {
// Baca DHT
float h = dht.readHumidity();
float t = dht.readTemperature();
if (!isnan(h) && !isnan(t)) {
dhtHum = h;
dhtTemp = t;
} else {
Serial.println("[DHT] Error, gunakan nilai sebelumnya");
}
targetPWM[0] = mapSensorToPWM(0, dhtTemp);
targetPWM[1] = mapSensorToPWM(1, dhtHum);
// Baca LM35
int rawLM35 = analogRead(lm35Pin);
float volt = rawLM35 * (5.0 / 1023.0);
lm35Temp = volt * 100.0;
targetPWM[2] = mapSensorToPWM(2, lm35Temp);
// Baca Soil
soilVal = analogRead(soilPin);
targetPWM[3] = mapSensorToPWM(3, soilVal);
// Baca MQ3
mq3Val = analogRead(mq3Pin);
targetPWM[4] = mapSensorToPWM(4, mq3Val);
// Tampilkan di Serial Monitor
Serial.println("===== Pembacaan Sensor =====");
Serial.print("DHT Suhu: "); Serial.print(dhtTemp, 1); Serial.print("°C → PWM "); Serial.println(targetPWM[0]);
Serial.print("DHT Hum : "); Serial.print(dhtHum, 1); Serial.print("% → PWM "); Serial.println(targetPWM[1]);
Serial.print("LM35 : "); Serial.print(lm35Temp, 1); Serial.print("°C → PWM "); Serial.println(targetPWM[2]);
Serial.print("Soil : "); Serial.print(soilVal); Serial.print(" → PWM "); Serial.println(targetPWM[3]);
Serial.print("MQ3 : "); Serial.print(mq3Val); Serial.print(" → PWM "); Serial.println(targetPWM[4]);
Serial.println("-----------------------------");
}
// ---------- Update semua LED dengan envelope segitiga ----------
void updateAllLEDs() {
unsigned long now = millis();
unsigned long elapsed = now - cycleStartTime;
float t = (float)elapsed / CYCLE_DURATION; // 0..1
// Envelope segitiga: naik 0→1 pada t=0..0.5, turun 1→0 pada t=0.5..1
float envelope = (t <= 0.5) ? (t / 0.5) : ((1.0 - t) / 0.5);
// Hitung PWM untuk setiap LED
int pwm0 = (int)(targetPWM[0] * envelope);
int pwm1 = (int)(targetPWM[1] * envelope);
int pwm2 = (int)(targetPWM[2] * envelope);
int pwm3 = (int)(targetPWM[3] * envelope);
int pwm4 = (int)(targetPWM[4] * envelope);
// Tulis ke pin
analogWrite(ledDHTTemp, constrain(pwm0,0,255));
analogWrite(ledDHTHum, constrain(pwm1,0,255));
analogWrite(ledLM35, constrain(pwm2,0,255));
analogWrite(ledSoil, constrain(pwm3,0,255));
analogWrite(ledMQ3, constrain(pwm4,0,255));
// Tampilkan nilai PWM setiap 200 ms (opsional)
static unsigned long lastPrint = 0;
if (now - lastPrint >= 200) {
lastPrint = now;
Serial.print("PWM: ");
Serial.print(pwm0); Serial.print(" ");
Serial.print(pwm1); Serial.print(" ");
Serial.print(pwm2); Serial.print(" ");
Serial.print(pwm3); Serial.print(" ");
Serial.println(pwm4);
}
}
void setup() {
Serial.begin(9600);
dht.begin();
pinMode(ledSoil, OUTPUT);
pinMode(ledDHTTemp, OUTPUT);
pinMode(ledDHTHum, OUTPUT);
pinMode(ledMQ3, OUTPUT);
pinMode(ledLM35, OUTPUT);
// Matikan semua LED
analogWrite(ledSoil, 0);
analogWrite(ledDHTTemp, 0);
analogWrite(ledDHTHum, 0);
analogWrite(ledMQ3, 0);
analogWrite(ledLM35, 0);
Serial.println("========================================");
Serial.println(" SEMUA LED FADE BERSAMAAN (2 DETIK)");
Serial.println(" Naik-turun serempak dengan intensitas");
Serial.println(" proporsional terhadap nilai sensor.");
Serial.println("========================================");
cycleStartTime = millis();
lastReadTime = millis();
readAllSensors(); // baca awal
}
void loop() {
unsigned long now = millis();
// Baca ulang semua sensor setiap READ_INTERVAL (2 detik)
if (now - lastReadTime >= READ_INTERVAL) {
lastReadTime = now;
readAllSensors();
// Reset cycle start agar LED memulai fade dari 0 pada pembacaan baru
// Namun kita ingin siklus tetap berjalan, tidak reset. Biarkan saja cycle terus berjalan.
// Jika ingin sinkron, bisa reset cycleStartTime = now; tapi lebih baik tetap kontinu.
// Karena kita baca setiap 2 detik, dan cycle juga 2 detik, mereka sinkron.
// Untuk memastikan sinkron, kita set cycleStartTime = now;
cycleStartTime = now;
}
// Update LED setiap UPDATE_INTERVAL
if (now - lastUpdateTime >= UPDATE_INTERVAL) {
lastUpdateTime = now;
updateAllLEDs();
}
}
=======================================================================================
Penjelasan:
Semua sensor dibaca bersamaan setiap 2 detik (sinkron dengan siklus fade).
Target PWM untuk setiap sensor dihitung dengan mapping spesifik.
Envelope segitiga yang sama diterapkan ke semua target, sehingga semua LED naik dan turun bersama-sama, tetapi dengan intensitas puncak yang berbeda sesuai nilai sensor.
Serial Monitor menampilkan pembacaan sensor setiap 2 detik dan nilai PWM setiap 200 ms.
/*
* ====================================================================
* POLA FADE-IN / FADE-OUT PER 2 DETIK
* Dengan rentang sensor spesifik agar LED lebih sensitif.
* ====================================================================
* Urutan: DHT Suhu → DHT Hum → LM35 → Soil → MQ3 (ulang)
* Periode: 2 detik per sensor.
* LED: naik ke puncak di detik ke-1, turun ke 0 di detik ke-2.
* ====================================================================
*/
#include <DHT.h>
#define DHTPIN 2
#define DHTTYPE DHT11
// Pin sensor
const int soilPin = A0;
const int mq3Pin = A1;
const int lm35Pin = A2;
// Pin LED (PWM)
const int ledSoil = 3;
const int ledDHTTemp = 5;
const int ledDHTHum = 6;
const int ledMQ3 = 9;
const int ledLM35 = 10;
DHT dht(DHTPIN, DHTTYPE);
// ---------- Konfigurasi waktu ----------
const unsigned long SLOT_DURATION = 2000; // 2 detik per sensor
const unsigned long UPDATE_INTERVAL = 50; // update LED setiap 50 ms
// Urutan sensor (0=DHT Suhu, 1=DHT Hum, 2=LM35, 3=Soil, 4=MQ3)
const int sensorOrder[] = {0, 1, 2, 3, 4};
const int NUM_SENSORS = 5;
unsigned long slotStartTime = 0;
int currentSlotIndex = 0;
unsigned long lastUpdateTime = 0;
// Target PWM untuk setiap sensor (0-255)
int targetPWM[NUM_SENSORS] = {0, 0, 0, 0, 0};
// Nilai sensor terakhir (untuk tampilan)
float lastDHTTemp = 0;
float lastDHTHum = 0;
int lastSoil = 0;
int lastMQ3 = 0;
float lastLM35Temp = 0;
// ---------- Fungsi mapping khusus ----------
int mapSensorToPWM(int sensorIdx, float value) {
int pwm = 0;
switch (sensorIdx) {
case 0: // DHT Suhu (25-40 °C)
pwm = map((int)value, 25, 40, 0, 255);
break;
case 1: // DHT Kelembapan (50-90 %)
pwm = map((int)value, 50, 90, 0, 255);
break;
case 2: // LM35 (25-40 °C)
pwm = map((int)value, 25, 40, 0, 255);
break;
case 3: // Soil (270-700)
pwm = map((int)value, 270, 700, 0, 255);
break;
case 4: // MQ3 (300-500)
pwm = map((int)value, 300, 500, 0, 255);
break;
}
return constrain(pwm, 0, 255);
}
void setup() {
Serial.begin(9600);
dht.begin();
pinMode(ledSoil, OUTPUT);
pinMode(ledDHTTemp, OUTPUT);
pinMode(ledDHTHum, OUTPUT);
pinMode(ledMQ3, OUTPUT);
pinMode(ledLM35, OUTPUT);
// Matikan semua LED
analogWrite(ledSoil, 0);
analogWrite(ledDHTTemp, 0);
analogWrite(ledDHTHum, 0);
analogWrite(ledMQ3, 0);
analogWrite(ledLM35, 0);
Serial.println("========================================");
Serial.println(" FADE-IN/OUT PER 2 DETIK (MAP SENSITIF)");
Serial.println(" Urutan: DHT Suhu → DHT Hum → LM35 → Soil → MQ3");
Serial.println("========================================");
slotStartTime = millis();
readSensorForSlot(sensorOrder[0]);
currentSlotIndex = 0;
}
void loop() {
unsigned long now = millis();
// Ganti slot setiap 2 detik
if (now - slotStartTime >= SLOT_DURATION) {
slotStartTime = now;
currentSlotIndex = (currentSlotIndex + 1) % NUM_SENSORS;
int sensorIdx = sensorOrder[currentSlotIndex];
readSensorForSlot(sensorIdx);
Serial.println("--- Ganti sensor ---");
}
// Update LED setiap 50 ms
if (now - lastUpdateTime >= UPDATE_INTERVAL) {
lastUpdateTime = now;
updateAllLEDs();
}
}
// ---------- Baca sensor dan simpan target PWM ----------
void readSensorForSlot(int sensorIdx) {
switch (sensorIdx) {
case 0: { // DHT Suhu
float h = dht.readHumidity();
float t = dht.readTemperature();
if (!isnan(h) && !isnan(t)) {
lastDHTHum = h;
lastDHTTemp = t;
} else {
Serial.println("[DHT] Error, gunakan nilai sebelumnya");
}
targetPWM[0] = mapSensorToPWM(0, lastDHTTemp);
Serial.print("DHT Suhu: "); Serial.print(lastDHTTemp, 1);
Serial.print("°C → PWM "); Serial.println(targetPWM[0]);
break;
}
case 1: { // DHT Kelembaban
float h = dht.readHumidity();
float t = dht.readTemperature();
if (!isnan(h) && !isnan(t)) {
lastDHTHum = h;
lastDHTTemp = t;
} else {
Serial.println("[DHT] Error, gunakan nilai sebelumnya");
}
targetPWM[1] = mapSensorToPWM(1, lastDHTHum);
Serial.print("DHT Hum : "); Serial.print(lastDHTHum, 1);
Serial.print("% → PWM "); Serial.println(targetPWM[1]);
break;
}
case 2: { // LM35
int raw = analogRead(lm35Pin);
float volt = raw * (5.0 / 1023.0);
lastLM35Temp = volt * 100.0;
targetPWM[2] = mapSensorToPWM(2, lastLM35Temp);
Serial.print("LM35 : "); Serial.print(lastLM35Temp, 1);
Serial.print("°C → PWM "); Serial.println(targetPWM[2]);
break;
}
case 3: { // Soil
lastSoil = analogRead(soilPin);
targetPWM[3] = mapSensorToPWM(3, lastSoil);
Serial.print("Soil : "); Serial.print(lastSoil);
Serial.print(" → PWM "); Serial.println(targetPWM[3]);
break;
}
case 4: { // MQ3
lastMQ3 = analogRead(mq3Pin);
targetPWM[4] = mapSensorToPWM(4, lastMQ3);
Serial.print("MQ3 : "); Serial.print(lastMQ3);
Serial.print(" → PWM "); Serial.println(targetPWM[4]);
break;
}
}
}
// ---------- Update LED dengan envelope segitiga ----------
void updateAllLEDs() {
unsigned long now = millis();
unsigned long elapsed = now - slotStartTime;
float t = (float)elapsed / SLOT_DURATION; // 0..1
// Envelope segitiga: naik 0→1 di t=0..0.5, turun 1→0 di t=0.5..1
float envelope = (t <= 0.5) ? (t / 0.5) : ((1.0 - t) / 0.5);
// Matikan semua LED
analogWrite(ledSoil, 0);
analogWrite(ledDHTTemp, 0);
analogWrite(ledDHTHum, 0);
analogWrite(ledMQ3, 0);
analogWrite(ledLM35, 0);
// Nyalakan LED sensor aktif dengan envelope
int activeSensor = sensorOrder[currentSlotIndex];
int pwmValue = (int)(targetPWM[activeSensor] * envelope);
pwmValue = constrain(pwmValue, 0, 255);
switch (activeSensor) {
case 0: analogWrite(ledDHTTemp, pwmValue); break;
case 1: analogWrite(ledDHTHum, pwmValue); break;
case 2: analogWrite(ledLM35, pwmValue); break;
case 3: analogWrite(ledSoil, pwmValue); break;
case 4: analogWrite(ledMQ3, pwmValue); break;
}
// Tampilkan nilai PWM setiap 200 ms (opsional)
static unsigned long lastPrint = 0;
if (now - lastPrint >= 200) {
lastPrint = now;
Serial.print("→ PWM aktif: "); Serial.println(pwmValue);
}
}
Fungsi mapSensorToPWM() – menggantikan map() standar dengan rentang spesifik:
DHT Suhu: 25–40 → 0–255
DHT Hum : 50–90 → 0–255
LM35 : 25–40 → 0–255
Soil : 270–700 → 0–255
MQ3 : 300–500 → 0–255
Nilai di luar rentang akan di‑constrain ke 0 atau 255, sehingga LED tidak pernah melewati batas.
Dengan rentang yang sempit, perubahan 1°C pada suhu menghasilkan perubahan PWM sekitar 17 (karena 255/(40-25) ≈ 17), sehingga LED akan terlihat berubah signifikan meskipun suhu naik sedikit.
/*
* ====================================================================
* SEMUA LED MENYALA DENGAN RITME BERBEDA (FASE BERGANDA)
* 100 variasi irama untuk menghasilkan kombinasi bunyi tak terhingga
* Setiap sensor memiliki fase offset sendiri, diubah secara periodik
* ====================================================================
* Sensor : DHT11 (suhu & hum), LM35, Soil, MQ3
* LED : pin 3,5,6,9,10 (PWM)
* ====================================================================
*/
#include <DHT.h>
#define DHTPIN 2
#define DHTTYPE DHT11
// Pin sensor
const int soilPin = A0;
const int mq3Pin = A1;
const int lm35Pin = A2;
// Pin LED
const int ledSoil = 3;
const int ledDHTTemp = 5;
const int ledDHTHum = 6;
const int ledMQ3 = 9;
const int ledLM35 = 10;
DHT dht(DHTPIN, DHTTYPE);
// ---------- Konfigurasi waktu ----------
const unsigned long CYCLE_DURATION = 2000; // 2 detik per siklus penuh (naik+turun)
const unsigned long READ_INTERVAL = 2000; // baca sensor setiap 2 detik
const unsigned long UPDATE_INTERVAL = 50; // update LED setiap 50 ms
const unsigned long PHASE_CHANGE_INTERVAL = 5; // ganti fase setiap 5 siklus (10 detik)
unsigned long cycleStartTime = 0;
unsigned long lastReadTime = 0;
unsigned long lastUpdateTime = 0;
unsigned long cycleCounter = 0;
// Target PWM untuk setiap sensor (indeks: 0=DHT suhu, 1=DHT hum, 2=LM35, 3=Soil, 4=MQ3)
int targetPWM[5] = {0,0,0,0,0};
// Variabel untuk menyimpan fase offset (dalam milidetik) untuk setiap sensor
unsigned long phaseOffset[5] = {0,0,0,0,0};
// Nilai sensor untuk tampilan
float dhtTemp = 0, dhtHum = 0, lm35Temp = 0;
int soilVal = 0, mq3Val = 0;
// ---------- Fungsi mapping spesifik per sensor ----------
int mapSensorToPWM(int sensorIdx, float value) {
int pwm = 0;
switch (sensorIdx) {
case 0: pwm = map((int)value, 25, 40, 0, 255); break; // DHT suhu
case 1: pwm = map((int)value, 50, 90, 0, 255); break; // DHT hum
case 2: pwm = map((int)value, 25, 40, 0, 255); break; // LM35
case 3: pwm = map((int)value, 270, 700, 0, 255); break; // Soil
case 4: pwm = map((int)value, 300, 500, 0, 255); break; // MQ3
}
return constrain(pwm, 0, 255);
}
// ---------- Baca semua sensor dan perbarui target PWM ----------
void readAllSensors() {
// Baca DHT
float h = dht.readHumidity();
float t = dht.readTemperature();
if (!isnan(h) && !isnan(t)) {
dhtHum = h;
dhtTemp = t;
} else {
Serial.println("[DHT] Error, gunakan nilai sebelumnya");
}
targetPWM[0] = mapSensorToPWM(0, dhtTemp);
targetPWM[1] = mapSensorToPWM(1, dhtHum);
// Baca LM35
int rawLM35 = analogRead(lm35Pin);
float volt = rawLM35 * (5.0 / 1023.0);
lm35Temp = volt * 100.0;
targetPWM[2] = mapSensorToPWM(2, lm35Temp);
// Baca Soil
soilVal = analogRead(soilPin);
targetPWM[3] = mapSensorToPWM(3, soilVal);
// Baca MQ3
mq3Val = analogRead(mq3Pin);
targetPWM[4] = mapSensorToPWM(4, mq3Val);
// Tampilkan di Serial Monitor
Serial.println("===== Pembacaan Sensor =====");
Serial.print("DHT Suhu: "); Serial.print(dhtTemp, 1); Serial.print("°C → PWM "); Serial.println(targetPWM[0]);
Serial.print("DHT Hum : "); Serial.print(dhtHum, 1); Serial.print("% → PWM "); Serial.println(targetPWM[1]);
Serial.print("LM35 : "); Serial.print(lm35Temp, 1); Serial.print("°C → PWM "); Serial.println(targetPWM[2]);
Serial.print("Soil : "); Serial.print(soilVal); Serial.print(" → PWM "); Serial.println(targetPWM[3]);
Serial.print("MQ3 : "); Serial.print(mq3Val); Serial.print(" → PWM "); Serial.println(targetPWM[4]);
Serial.println("-----------------------------");
}
// ---------- Generate fase acak untuk semua LED ----------
void generateNewPhases() {
randomSeed(analogRead(A5) + millis()); // seed dari pin kosong
for (int i=0; i<5; i++) {
// Fase antara 0 hingga CYCLE_DURATION (0-2000 ms)
phaseOffset[i] = random(CYCLE_DURATION);
}
Serial.print("Fase baru: ");
for (int i=0; i<5; i++) {
Serial.print(phaseOffset[i]); Serial.print(" ");
}
Serial.println();
}
// ---------- Hitung envelope segitiga dengan fase offset ----------
float getEnvelope(int sensorIdx, unsigned long now) {
// Waktu efektif = waktu sekarang + fase offset, kemudian modulo siklus
unsigned long effectiveTime = (now + phaseOffset[sensorIdx]) % CYCLE_DURATION;
float t = (float)effectiveTime / CYCLE_DURATION; // 0..1
// Envelope segitiga: naik 0→1 di 0..0.5, turun 1→0 di 0.5..1
if (t <= 0.5) {
return t / 0.5;
} else {
return (1.0 - t) / 0.5;
}
}
// ---------- Update semua LED dengan ritme berbeda ----------
void updateAllLEDs() {
unsigned long now = millis();
// Hitung envelope untuk setiap sensor berdasarkan fase masing-masing
float env[5];
for (int i=0; i<5; i++) {
env[i] = getEnvelope(i, now);
}
// Hitung PWM untuk setiap LED
int pwm0 = (int)(targetPWM[0] * env[0]);
int pwm1 = (int)(targetPWM[1] * env[1]);
int pwm2 = (int)(targetPWM[2] * env[2]);
int pwm3 = (int)(targetPWM[3] * env[3]);
int pwm4 = (int)(targetPWM[4] * env[4]);
// Tulis ke pin
analogWrite(ledDHTTemp, constrain(pwm0,0,255));
analogWrite(ledDHTHum, constrain(pwm1,0,255));
analogWrite(ledLM35, constrain(pwm2,0,255));
analogWrite(ledSoil, constrain(pwm3,0,255));
analogWrite(ledMQ3, constrain(pwm4,0,255));
// Tampilkan nilai PWM setiap 200 ms (opsional)
static unsigned long lastPrint = 0;
if (now - lastPrint >= 200) {
lastPrint = now;
Serial.print("PWM: ");
Serial.print(pwm0); Serial.print(" ");
Serial.print(pwm1); Serial.print(" ");
Serial.print(pwm2); Serial.print(" ");
Serial.print(pwm3); Serial.print(" ");
Serial.println(pwm4);
}
}
void setup() {
Serial.begin(9600);
dht.begin();
pinMode(ledSoil, OUTPUT);
pinMode(ledDHTTemp, OUTPUT);
pinMode(ledDHTHum, OUTPUT);
pinMode(ledMQ3, OUTPUT);
pinMode(ledLM35, OUTPUT);
// Matikan semua LED
analogWrite(ledSoil, 0);
analogWrite(ledDHTTemp, 0);
analogWrite(ledDHTHum, 0);
analogWrite(ledMQ3, 0);
analogWrite(ledLM35, 0);
Serial.println("========================================");
Serial.println(" RITME BERBEDA PER SENSOR (FASE UNIK)");
Serial.println(" 100 variasi irama dengan fase acak");
Serial.println(" Perubahan fase setiap 10 detik");
Serial.println("========================================");
cycleStartTime = millis();
lastReadTime = millis();
readAllSensors();
generateNewPhases(); // fase awal
}
void loop() {
unsigned long now = millis();
// Baca ulang semua sensor setiap READ_INTERVAL (2 detik)
if (now - lastReadTime >= READ_INTERVAL) {
lastReadTime = now;
readAllSensors();
// Kita tidak mereset cycleStartTime, karena fase sudah mengatur ritme
}
// Hitung siklus yang telah berlalu untuk mengganti fase
if (now - cycleStartTime >= CYCLE_DURATION) {
cycleStartTime = now;
cycleCounter++;
// Setiap beberapa siklus, ganti fase untuk menghasilkan 100 variasi
if (cycleCounter % PHASE_CHANGE_INTERVAL == 0) {
generateNewPhases();
Serial.print("Siklus ke-"); Serial.print(cycleCounter);
Serial.println(" : Fase baru dihasilkan.");
}
}
// Update LED setiap UPDATE_INTERVAL
if (now - lastUpdateTime >= UPDATE_INTERVAL) {
lastUpdateTime = now;
updateAllLEDs();
}
}
======================================================
Fase Offset: Setiap sensor (indeks 0-4) memiliki variabel phaseOffset[i] yang berisi nilai antara 0 hingga 2000 ms. Ini membuat setiap LED memiliki titik awal gelombang yang berbeda, sehingga ritme menyala dan meredupnya berbeda-beda.
Envelope Segitiga dengan Fase: Fungsi getEnvelope(sensorIdx, now) menghitung posisi dalam siklus (0-2000 ms) dengan menambahkan fase offset. Kemudian menghasilkan nilai antara 0.0 dan 1.0 berbentuk segitiga.
Pembacaan Sensor: Dilakukan setiap 2 detik untuk memperbarui nilai target PWM.
Perubahan Fase: Setiap 5 siklus (10 detik), fase-fase baru di-generate secara acak, menghasilkan 100 variasi irama (karena setiap kali menghasilkan kombinasi baru, bisa terus berubah). Dengan 5 sensor dan fase acak, kombinasi yang mungkin sangat banyak (tak terhingga).
Serial Monitor: Menampilkan pembacaan sensor dan nilai PWM aktual setiap 200 ms untuk monitoring.
Semua LED menyala dan meredup terus menerus, tetapi masing-masing dengan ritme yang berbeda karena pergeseran fase.
Setiap 10 detik, pola ritme berubah total, memberikan variasi yang tak terbatas.
Intensitas puncak setiap LED tetap proporsional terhadap nilai sensor terkini, sehingga perilaku sensor tetap memengaruhi nyala.
/* //4s86
* ====================================================================
* SEMUA LED MENYALA DENGAN RITME BERBEDA (FASE BERGANDA)
* 100 variasi irama untuk menghasilkan kombinasi bunyi tak terhingga
* Setiap sensor memiliki fase offset sendiri, diubah secara periodik
* ====================================================================
* Sensor : DHT11 (suhu & hum), LM35, Soil, MQ3 (analog)
* LED : pin 5,6,9,10,11 (PWM)
* LCD : I2C 16x2 dengan tampilan bergantian 5 halaman
* Master Switch : Soil > 550 → semua LED mati
* Mapping non-linear
* Startup : Welcome LED test (bergantian)
* ====================================================================
*/
#include <DHT.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
// ========== LCD I2C ==========
LiquidCrystal_I2C lcd(0x27, 16, 2); // Alamat 0x27 atau 0x3F
// ========== PIN SENSOR ==========
#define DHTPIN 2
#define DHTTYPE DHT11
const int soilPin = A0; // Soil analog
const int lm35Pin = A1; // LM35 analog
const int mq3Pin = A2; // MQ3 analog
// ========== PIN LED (PWM) ==========
const int ledSoil = 11;
const int ledDHTTemp = 5;
const int ledDHTHum = 6;
const int ledMQ3 = 9;
const int ledLM35 = 10;
DHT dht(DHTPIN, DHTTYPE);
// ---------- Waktu ----------
const unsigned long CYCLE_DURATION = 2000;
const unsigned long READ_INTERVAL = 2000;
const unsigned long UPDATE_INTERVAL = 50;
const unsigned long PHASE_CHANGE_INTERVAL = 5;
const unsigned long PAGE_CHANGE_INTERVAL = 2000;
unsigned long cycleStartTime = 0;
unsigned long lastReadTime = 0;
unsigned long lastUpdateTime = 0;
unsigned long cycleCounter = 0;
int lcdPage = 0;
const int LCD_PAGES = 5;
unsigned long lastPageChange = 0;
int targetPWM[5] = {0,0,0,0,0}; // indeks: 0=DHT suhu, 1=DHT hum, 2=LM35, 3=Soil, 4=MQ3
unsigned long phaseOffset[5] = {0,0,0,0,0};
float dhtTemp = 0, dhtHum = 0, lm35Temp = 0;
int soilVal = 0;
int mq3Analog = 0;
// ---------- FUNGSI MAPPING SPESIFIK PER SENSOR ----------
// MQ3: nilai 0-1023
int mapMQ3(int value) {
int pwm;
if (value < 30) {
pwm = 30; // redup
} else if (value <= 150) {
pwm = map(value, 30, 150, 30, 255);
} else {
pwm = 255;
}
return constrain(pwm, 0, 255);
}
// Soil: nilai 0-1023
int mapSoil(int value) {
int pwm;
if (value >= 550) {
pwm = 0;
} else if (value <= 200) {
pwm = 255;
} else {
pwm = map(value, 200, 550, 255, 0); // 200->255, 550->0
}
return constrain(pwm, 0, 255);
}
// LM35: suhu °C
int mapLM35(float temp) {
int pwm;
if (temp <= 28) {
pwm = 255;
} else if (temp >= 49) {
pwm = 0;
} else {
pwm = map((int)(temp * 10), 280, 490, 255, 0); // skala 28.0-49.0
}
return constrain(pwm, 0, 255);
}
// DHT suhu: sama dengan LM35
int mapDHTTemp(float temp) {
return mapLM35(temp); // aturan sama
}
// DHT kelembapan: %
int mapDHTHum(float hum) {
int pwm;
if (hum <= 30) {
pwm = 0;
} else if (hum >= 80) {
pwm = 255;
} else {
pwm = map((int)(hum * 10), 300, 800, 0, 255); // skala 30.0-80.0
}
return constrain(pwm, 0, 255);
}
// ---------- Baca semua sensor ----------
void readAllSensors() {
// DHT11
float h = dht.readHumidity();
float t = dht.readTemperature();
if (!isnan(h) && !isnan(t)) {
dhtHum = h;
dhtTemp = t;
} else {
Serial.println("[DHT] Error, gunakan nilai sebelumnya");
}
targetPWM[0] = mapDHTTemp(dhtTemp);
targetPWM[1] = mapDHTHum(dhtHum);
// LM35
int rawLM35 = analogRead(lm35Pin);
lm35Temp = (rawLM35 * 5000.0 / 1023.0) / 10.0; // mV/10 = °C
targetPWM[2] = mapLM35(lm35Temp);
// Soil
soilVal = analogRead(soilPin);
targetPWM[3] = mapSoil(soilVal);
// MQ3
mq3Analog = analogRead(mq3Pin);
targetPWM[4] = mapMQ3(mq3Analog);
// Serial Monitor
Serial.println("===== Pembacaan Sensor =====");
Serial.print("DHT Suhu: "); Serial.print(dhtTemp, 1); Serial.print("°C → PWM "); Serial.println(targetPWM[0]);
Serial.print("DHT Hum : "); Serial.print(dhtHum, 1); Serial.print("% → PWM "); Serial.println(targetPWM[1]);
Serial.print("LM35 : "); Serial.print(lm35Temp, 1); Serial.print("°C → PWM "); Serial.println(targetPWM[2]);
Serial.print("Soil : "); Serial.print(soilVal); Serial.print(" → PWM "); Serial.println(targetPWM[3]);
Serial.print("MQ3 : "); Serial.print(mq3Analog); Serial.print(" → PWM "); Serial.println(targetPWM[4]);
Serial.println("-----------------------------");
updateLCD();
}
// ---------- Tampilan LCD (bergantian) ----------
void updateLCD() {
lcd.clear();
lcd.setCursor(0, 0);
switch(lcdPage) {
case 0:
lcd.print("Project Natural");
lcd.setCursor(0, 1);
lcd.print("Sound JAV");
break;
case 1:
lcd.print("DHT: T=");
lcd.print(dhtTemp, 1);
lcd.print("C");
lcd.setCursor(0, 1);
lcd.print("H=");
lcd.print(dhtHum, 0);
lcd.print("%");
break;
case 2:
lcd.print("MQ3: ");
lcd.print(mq3Analog);
lcd.setCursor(0, 1);
lcd.print("PWM=");
lcd.print(targetPWM[4]);
break;
case 3:
lcd.print("Soil: ");
lcd.print(soilVal);
lcd.setCursor(0, 1);
if (soilVal > 550) {
lcd.print("SYS OFF (soil>550)");
} else {
lcd.print("SYS ON");
}
break;
case 4:
lcd.print("LM35: ");
lcd.print(lm35Temp, 1);
lcd.print("C");
lcd.setCursor(0, 1);
lcd.print("PWM=");
lcd.print(targetPWM[2]);
break;
}
}
// ---------- Fase acak ----------
void generateNewPhases() {
randomSeed(analogRead(A5) + millis());
for (int i=0; i<5; i++) {
phaseOffset[i] = random(CYCLE_DURATION);
}
Serial.print("Fase baru: ");
for (int i=0; i<5; i++) {
Serial.print(phaseOffset[i]); Serial.print(" ");
}
Serial.println();
}
// ---------- Envelope segitiga ----------
float getEnvelope(int sensorIdx, unsigned long now) {
unsigned long effectiveTime = (now + phaseOffset[sensorIdx]) % CYCLE_DURATION;
float t = (float)effectiveTime / CYCLE_DURATION;
if (t <= 0.5) return t / 0.5;
else return (1.0 - t) / 0.5;
}
// ---------- Update LED (dengan master switch Soil) ----------
void updateAllLEDs() {
unsigned long now = millis();
// Master switch: jika soilVal > 550, semua LED mati
bool systemActive = (soilVal <= 550);
int pwm0, pwm1, pwm2, pwm3, pwm4;
if (!systemActive) {
pwm0 = pwm1 = pwm2 = pwm3 = pwm4 = 0;
} else {
float env[5];
for (int i=0; i<5; i++) env[i] = getEnvelope(i, now);
pwm0 = (int)(targetPWM[0] * env[0]);
pwm1 = (int)(targetPWM[1] * env[1]);
pwm2 = (int)(targetPWM[2] * env[2]);
pwm3 = (int)(targetPWM[3] * env[3]);
pwm4 = (int)(targetPWM[4] * env[4]);
}
analogWrite(ledDHTTemp, constrain(pwm0,0,255));
analogWrite(ledDHTHum, constrain(pwm1,0,255));
analogWrite(ledLM35, constrain(pwm2,0,255));
analogWrite(ledSoil, constrain(pwm3,0,255));
analogWrite(ledMQ3, constrain(pwm4,0,255));
static unsigned long lastPrint = 0;
if (now - lastPrint >= 200) {
lastPrint = now;
Serial.print("PWM: ");
Serial.print(pwm0); Serial.print(" ");
Serial.print(pwm1); Serial.print(" ");
Serial.print(pwm2); Serial.print(" ");
Serial.print(pwm3); Serial.print(" ");
Serial.print(pwm4);
if (!systemActive) Serial.print(" (SYSTEM OFF)");
Serial.println();
}
}
// ---------- Welcome LED Test (startup) ----------
void welcomeLED() {
int ledPins[] = {ledSoil, ledDHTTemp, ledDHTHum, ledMQ3, ledLM35};
for (int i = 0; i < 5; i++) {
analogWrite(ledPins[i], 255);
delay(300);
analogWrite(ledPins[i], 0);
delay(100);
}
for (int i = 0; i < 5; i++) analogWrite(ledPins[i], 255);
delay(500);
for (int i = 0; i < 5; i++) analogWrite(ledPins[i], 0);
delay(200);
}
// ========== SETUP ==========
void setup() {
Serial.begin(9600);
dht.begin();
lcd.init();
lcd.backlight();
lcd.clear();
lcd.print("Starting...");
delay(1000);
pinMode(ledSoil, OUTPUT);
pinMode(ledDHTTemp, OUTPUT);
pinMode(ledDHTHum, OUTPUT);
pinMode(ledMQ3, OUTPUT);
pinMode(ledLM35, OUTPUT);
analogWrite(ledSoil, 0);
analogWrite(ledDHTTemp, 0);
analogWrite(ledDHTHum, 0);
analogWrite(ledMQ3, 0);
analogWrite(ledLM35, 0);
welcomeLED();
Serial.println("========================================");
Serial.println(" RITME BERBEDA PER SENSOR (FASE UNIK)");
Serial.println(" Mapping non-linear sesuai permintaan");
Serial.println(" Master Switch: Soil > 550 → LED mati");
Serial.println("========================================");
cycleStartTime = millis();
lastReadTime = millis();
lastPageChange = millis();
readAllSensors();
generateNewPhases();
}
// ========== LOOP ==========
void loop() {
unsigned long now = millis();
if (now - lastReadTime >= READ_INTERVAL) {
lastReadTime = now;
readAllSensors();
}
if (now - lastPageChange >= PAGE_CHANGE_INTERVAL) {
lastPageChange = now;
lcdPage++;
if (lcdPage >= LCD_PAGES) lcdPage = 0;
updateLCD();
}
if (now - cycleStartTime >= CYCLE_DURATION) {
cycleStartTime = now;
cycleCounter++;
if (cycleCounter % PHASE_CHANGE_INTERVAL == 0) {
generateNewPhases();
Serial.print("Siklus ke-"); Serial.print(cycleCounter);
Serial.println(" : Fase baru dihasilkan.");
}
}
if (now - lastUpdateTime >= UPDATE_INTERVAL) {
lastUpdateTime = now;
updateAllLEDs();
}
}
#include <DHT.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
// ========== LCD I2C ==========
LiquidCrystal_I2C lcd(0x27, 16, 2); // Alamat 0x27 atau 0x3F
// ========== PIN SENSOR ==========
#define DHTPIN 2
#define DHTTYPE DHT11
const int soilPin = A0; // Soil analog
const int lm35Pin = A1; // LM35 analog
const int mq3Pin = A2; // MQ3 analog
// ========== PIN LED (PWM) ==========
const int ledSoil = 11;
const int ledDHTTemp = 5;
const int ledDHTHum = 6;
const int ledMQ3 = 9;
const int ledLM35 = 10;
DHT dht(DHTPIN, DHTTYPE);
// ---------- Waktu ----------
const unsigned long CYCLE_DURATION = 2000;
const unsigned long READ_INTERVAL = 2000;
const unsigned long UPDATE_INTERVAL = 50;
const unsigned long PHASE_CHANGE_INTERVAL = 5;
const unsigned long PAGE_CHANGE_INTERVAL = 2000;
const unsigned long BLINK_DURATION = 200; // durasi tiap LED blink (total 600ms per siklus)
unsigned long cycleStartTime = 0;
unsigned long lastReadTime = 0;
unsigned long lastUpdateTime = 0;
unsigned long cycleCounter = 0;
int lcdPage = 0;
const int LCD_PAGES = 5;
unsigned long lastPageChange = 0;
int targetPWM[5] = {0,0,0,0,0}; // indeks: 0=DHT suhu, 1=DHT hum, 2=LM35, 3=Soil, 4=MQ3
unsigned long phaseOffset[5] = {0,0,0,0,0};
float dhtTemp = 0, dhtHum = 0, lm35Temp = 0;
int soilVal = 0;
int mq3Analog = 0;
// ---------- FUNGSI MAPPING SPESIFIK PER SENSOR ----------
// MQ3: nilai 0-1023
int mapMQ3(int value) {
int pwm;
if (value < 30) {
pwm = 30; // redup
} else if (value <= 150) {
pwm = map(value, 30, 150, 30, 255);
} else {
pwm = 255;
}
return constrain(pwm, 0, 255);
}
// Soil: nilai 0-1023
int mapSoil(int value) {
int pwm;
if (value >= 550) {
pwm = 0;
} else if (value <= 200) {
pwm = 255;
} else {
pwm = map(value, 200, 550, 255, 0); // 200->255, 550->0
}
return constrain(pwm, 0, 255);
}
// LM35: suhu °C
int mapLM35(float temp) {
int pwm;
if (temp <= 28) {
pwm = 255;
} else if (temp >= 49) {
pwm = 0;
} else {
pwm = map((int)(temp * 10), 280, 490, 255, 0); // skala 28.0-49.0
}
return constrain(pwm, 0, 255);
}
// DHT suhu: sama dengan LM35
int mapDHTTemp(float temp) {
return mapLM35(temp); // aturan sama
}
// DHT kelembapan: %
int mapDHTHum(float hum) {
int pwm;
if (hum <= 30) {
pwm = 0;
} else if (hum >= 80) {
pwm = 255;
} else {
pwm = map((int)(hum * 10), 300, 800, 0, 255); // skala 30.0-80.0
}
return constrain(pwm, 0, 255);
}
// ---------- Baca semua sensor ----------
void readAllSensors() {
// DHT11
float h = dht.readHumidity();
float t = dht.readTemperature();
if (!isnan(h) && !isnan(t)) {
dhtHum = h;
dhtTemp = t;
} else {
Serial.println("[DHT] Error, gunakan nilai sebelumnya");
}
targetPWM[0] = mapDHTTemp(dhtTemp);
targetPWM[1] = mapDHTHum(dhtHum);
// LM35
int rawLM35 = analogRead(lm35Pin);
lm35Temp = (rawLM35 * 5000.0 / 1023.0) / 10.0; // mV/10 = °C
targetPWM[2] = mapLM35(lm35Temp);
// Soil
soilVal = analogRead(soilPin);
targetPWM[3] = mapSoil(soilVal);
// MQ3
mq3Analog = analogRead(mq3Pin);
targetPWM[4] = mapMQ3(mq3Analog);
// Serial Monitor
Serial.println("===== Pembacaan Sensor =====");
Serial.print("DHT Suhu: "); Serial.print(dhtTemp, 1); Serial.print("°C → PWM "); Serial.println(targetPWM[0]);
Serial.print("DHT Hum : "); Serial.print(dhtHum, 1); Serial.print("% → PWM "); Serial.println(targetPWM[1]);
Serial.print("LM35 : "); Serial.print(lm35Temp, 1); Serial.print("°C → PWM "); Serial.println(targetPWM[2]);
Serial.print("Soil : "); Serial.print(soilVal); Serial.print(" → PWM "); Serial.println(targetPWM[3]);
Serial.print("MQ3 : "); Serial.print(mq3Analog); Serial.print(" → PWM "); Serial.println(targetPWM[4]);
Serial.println("-----------------------------");
updateLCD();
}
// ---------- Tampilan LCD (bergantian) ----------
void updateLCD() {
lcd.clear();
lcd.setCursor(0, 0);
switch(lcdPage) {
case 0:
lcd.print("Project Natural");
lcd.setCursor(0, 1);
lcd.print("Sound JAV");
break;
case 1:
lcd.print("DHT: T=");
lcd.print(dhtTemp, 1);
lcd.print("C");
lcd.setCursor(0, 1);
lcd.print("H=");
lcd.print(dhtHum, 0);
lcd.print("%");
break;
case 2:
lcd.print("MQ3: ");
lcd.print(mq3Analog);
lcd.setCursor(0, 1);
lcd.print("PWM=");
lcd.print(targetPWM[4]);
break;
case 3:
lcd.print("Soil: ");
lcd.print(soilVal);
lcd.setCursor(0, 1);
if (soilVal > 550) {
lcd.print("SYS OFF (soil>550)");
} else {
lcd.print("SYS ON");
}
break;
case 4:
lcd.print("LM35: ");
lcd.print(lm35Temp, 1);
lcd.print("C");
lcd.setCursor(0, 1);
lcd.print("PWM=");
lcd.print(targetPWM[2]);
break;
}
}
// ---------- Fase acak ----------
void generateNewPhases() {
randomSeed(analogRead(A5) + millis());
for (int i=0; i<5; i++) {
phaseOffset[i] = random(CYCLE_DURATION);
}
Serial.print("Fase baru: ");
for (int i=0; i<5; i++) {
Serial.print(phaseOffset[i]); Serial.print(" ");
}
Serial.println();
}
// ---------- Envelope segitiga ----------
float getEnvelope(int sensorIdx, unsigned long now) {
unsigned long effectiveTime = (now + phaseOffset[sensorIdx]) % CYCLE_DURATION;
float t = (float)effectiveTime / CYCLE_DURATION;
if (t <= 0.5) return t / 0.5;
else return (1.0 - t) / 0.5;
}
// ---------- Update LED (dengan master switch Soil) ----------
void updateAllLEDs() {
unsigned long now = millis();
// Master switch: jika soilVal > 550, semua LED mati
bool systemActive = (soilVal <= 550);
int pwm0, pwm1, pwm2, pwm3, pwm4;
if (!systemActive) {
pwm0 = pwm1 = pwm2 = pwm3 = pwm4 = 0;
} else {
// --- DHT11 tetap fade (envelope) ---
float env[5];
for (int i = 0; i < 5; i++) env[i] = getEnvelope(i, now);
pwm0 = (int)(targetPWM[0] * env[0]); // DHT suhu
pwm1 = (int)(targetPWM[1] * env[1]); // DHT kelembapan
// --- Soil, MQ3, LM35 bergantian (blink) ---
// Urutan: soil (indeks 3) → MQ3 (indeks 4) → LM35 (indeks 2)
unsigned long cycleTime = now % (BLINK_DURATION * 3);
int activeIdx = -1;
if (cycleTime < BLINK_DURATION) {
activeIdx = 3; // soil
} else if (cycleTime < 2 * BLINK_DURATION) {
activeIdx = 4; // MQ3
} else {
activeIdx = 2; // LM35
}
pwm3 = (activeIdx == 3) ? targetPWM[3] : 0; // soil
pwm4 = (activeIdx == 4) ? targetPWM[4] : 0; // MQ3
pwm2 = (activeIdx == 2) ? targetPWM[2] : 0; // LM35
}
// Tulis ke pin LED
analogWrite(ledDHTTemp, constrain(pwm0, 0, 255));
analogWrite(ledDHTHum, constrain(pwm1, 0, 255));
analogWrite(ledLM35, constrain(pwm2, 0, 255));
analogWrite(ledSoil, constrain(pwm3, 0, 255));
analogWrite(ledMQ3, constrain(pwm4, 0, 255));
// Serial monitor (opsional)
static unsigned long lastPrint = 0;
if (now - lastPrint >= 200) {
lastPrint = now;
Serial.print("PWM: ");
Serial.print(pwm0); Serial.print(" ");
Serial.print(pwm1); Serial.print(" ");
Serial.print(pwm2); Serial.print(" ");
Serial.print(pwm3); Serial.print(" ");
Serial.print(pwm4);
if (!systemActive) Serial.print(" (SYSTEM OFF)");
Serial.println();
}
}
// ---------- Welcome LED Test (startup) ----------
void welcomeLED() {
int ledPins[] = {ledSoil, ledDHTTemp, ledDHTHum, ledMQ3, ledLM35};
for (int i = 0; i < 5; i++) {
analogWrite(ledPins[i], 255);
delay(300);
analogWrite(ledPins[i], 0);
delay(100);
}
for (int i = 0; i < 5; i++) analogWrite(ledPins[i], 255);
delay(500);
for (int i = 0; i < 5; i++) analogWrite(ledPins[i], 0);
delay(200);
}
// ========== SETUP ==========
void setup() {
Serial.begin(9600);
dht.begin();
lcd.init();
lcd.backlight();
lcd.clear();
lcd.print("Starting...");
delay(1000);
pinMode(ledSoil, OUTPUT);
pinMode(ledDHTTemp, OUTPUT);
pinMode(ledDHTHum, OUTPUT);
pinMode(ledMQ3, OUTPUT);
pinMode(ledLM35, OUTPUT);
analogWrite(ledSoil, 0);
analogWrite(ledDHTTemp, 0);
analogWrite(ledDHTHum, 0);
analogWrite(ledMQ3, 0);
analogWrite(ledLM35, 0);
welcomeLED();
Serial.println("========================================");
Serial.println(" RITME BERBEDA PER SENSOR (FASE UNIK)");
Serial.println(" Mapping non-linear sesuai permintaan");
Serial.println(" Master Switch: Soil > 550 → LED mati");
Serial.println(" LED Soil, MQ3, LM35 bergantian (blink)");
Serial.println(" LED DHT tetap fade");
Serial.println("========================================");
cycleStartTime = millis();
lastReadTime = millis();
lastPageChange = millis();
readAllSensors();
generateNewPhases();
}
// ========== LOOP ==========
void loop() {
unsigned long now = millis();
if (now - lastReadTime >= READ_INTERVAL) {
lastReadTime = now;
readAllSensors();
}
if (now - lastPageChange >= PAGE_CHANGE_INTERVAL) {
lastPageChange = now;
lcdPage++;
if (lcdPage >= LCD_PAGES) lcdPage = 0;
updateLCD();
}
if (now - cycleStartTime >= CYCLE_DURATION) {
cycleStartTime = now;
cycleCounter++;
if (cycleCounter % PHASE_CHANGE_INTERVAL == 0) {
generateNewPhases();
Serial.print("Siklus ke-"); Serial.print(cycleCounter);
Serial.println(" : Fase baru dihasilkan.");
}
}
if (now - lastUpdateTime >= UPDATE_INTERVAL) {
lastUpdateTime = now;
updateAllLEDs();
}
}
#include <DHT.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
// ========== LCD I2C ==========
LiquidCrystal_I2C lcd(0x27, 16, 2); // Alamat 0x27 atau 0x3F
// ========== PIN SENSOR ==========
#define DHTPIN 2
#define DHTTYPE DHT11
const int soilPin = A0; // Soil analog
const int lm35Pin = A1; // LM35 analog
const int mq3Pin = A2; // MQ3 analog
// ========== PIN LED (PWM) ==========
const int ledSoil = 11;
const int ledDHTTemp = 5;
const int ledDHTHum = 6;
const int ledMQ3 = 9;
const int ledLM35 = 10;
DHT dht(DHTPIN, DHTTYPE);
// ---------- Waktu ----------
const unsigned long READ_INTERVAL = 2000;
const unsigned long UPDATE_INTERVAL = 50;
const unsigned long PAGE_CHANGE_INTERVAL = 2000;
// ---------- Parameter DHT Breathing ----------
const unsigned long BREATH_DURATION = 5000; // 5 detik naik-turun
const unsigned long JEDA_DURATION = 1000; // 1 detik mati
// ---------- Parameter Blink untuk Soil, MQ3, LM35 ----------
const int NUM_RITMES = 50; // 50 pola ritme (cukup variatif)
uint16_t durasiBlink[3][NUM_RITMES]; // [0=Soil, 1=MQ3, 2=LM35] durasi dalam ms (max 500)
int blinkIndex = 0;
unsigned long blinkStartTime = 0;
int blinkActiveLED = -1; // -1 = belum mulai, 0=Soil, 1=MQ3, 2=LM35
// ---------- State untuk DHT Breathing ----------
enum DHTState { BREATH_SUHU, JEDA_SUHU, BREATH_LEMBAB, JEDA_LEMBAB };
DHTState dhtState = BREATH_SUHU;
unsigned long dhtStateStart = 0;
unsigned long lastReadTime = 0;
unsigned long lastUpdateTime = 0;
int lcdPage = 0;
const int LCD_PAGES = 5;
unsigned long lastPageChange = 0;
int targetPWM[5] = {0,0,0,0,0}; // indeks: 0=DHT suhu, 1=DHT hum, 2=LM35, 3=Soil, 4=MQ3
float dhtTemp = 0, dhtHum = 0, lm35Temp = 0;
int soilVal = 0;
int mq3Analog = 0;
// ---------- FUNGSI MAPPING SPESIFIK PER SENSOR ----------
int mapMQ3(int value) {
int pwm;
if (value < 30) {
pwm = 30;
} else if (value <= 150) {
pwm = map(value, 30, 150, 30, 255);
} else {
pwm = 255;
}
return constrain(pwm, 0, 255);
}
int mapSoil(int value) {
int pwm;
if (value >= 550) {
pwm = 0;
} else if (value <= 200) {
pwm = 255;
} else {
pwm = map(value, 200, 550, 255, 0);
}
return constrain(pwm, 0, 255);
}
int mapLM35(float temp) {
int pwm;
if (temp <= 28) {
pwm = 255;
} else if (temp >= 49) {
pwm = 0;
} else {
pwm = map((int)(temp * 10), 280, 490, 255, 0);
}
return constrain(pwm, 0, 255);
}
int mapDHTTemp(float temp) {
return mapLM35(temp);
}
int mapDHTHum(float hum) {
int pwm;
if (hum <= 30) {
pwm = 0;
} else if (hum >= 80) {
pwm = 255;
} else {
pwm = map((int)(hum * 10), 300, 800, 0, 255);
}
return constrain(pwm, 0, 255);
}
// ---------- Baca semua sensor ----------
void readAllSensors() {
float h = dht.readHumidity();
float t = dht.readTemperature();
if (!isnan(h) && !isnan(t)) {
dhtHum = h;
dhtTemp = t;
} else {
Serial.println("[DHT] Error, gunakan nilai sebelumnya");
}
targetPWM[0] = mapDHTTemp(dhtTemp);
targetPWM[1] = mapDHTHum(dhtHum);
int rawLM35 = analogRead(lm35Pin);
lm35Temp = (rawLM35 * 5000.0 / 1023.0) / 10.0;
targetPWM[2] = mapLM35(lm35Temp);
soilVal = analogRead(soilPin);
targetPWM[3] = mapSoil(soilVal);
mq3Analog = analogRead(mq3Pin);
targetPWM[4] = mapMQ3(mq3Analog);
Serial.println("===== Pembacaan Sensor =====");
Serial.print("DHT Suhu: "); Serial.print(dhtTemp, 1); Serial.print("°C → PWM "); Serial.println(targetPWM[0]);
Serial.print("DHT Hum : "); Serial.print(dhtHum, 1); Serial.print("% → PWM "); Serial.println(targetPWM[1]);
Serial.print("LM35 : "); Serial.print(lm35Temp, 1); Serial.print("°C → PWM "); Serial.println(targetPWM[2]);
Serial.print("Soil : "); Serial.print(soilVal); Serial.print(" → PWM "); Serial.println(targetPWM[3]);
Serial.print("MQ3 : "); Serial.print(mq3Analog); Serial.print(" → PWM "); Serial.println(targetPWM[4]);
Serial.println("-----------------------------");
updateLCD();
}
// ---------- Tampilan LCD ----------
void updateLCD() {
lcd.clear();
lcd.setCursor(0, 0);
switch(lcdPage) {
case 0:
lcd.print("Project Natural");
lcd.setCursor(0, 1);
lcd.print("Sound JAV");
break;
case 1:
lcd.print("DHT: T=");
lcd.print(dhtTemp, 1);
lcd.print("C");
lcd.setCursor(0, 1);
lcd.print("H=");
lcd.print(dhtHum, 0);
lcd.print("%");
break;
case 2:
lcd.print("MQ3: ");
lcd.print(mq3Analog);
lcd.setCursor(0, 1);
lcd.print("PWM=");
lcd.print(targetPWM[4]);
break;
case 3:
lcd.print("Soil: ");
lcd.print(soilVal);
lcd.setCursor(0, 1);
if (soilVal > 550) {
lcd.print("SYS OFF (soil>550)");
} else {
lcd.print("SYS ON");
}
break;
case 4:
lcd.print("LM35: ");
lcd.print(lm35Temp, 1);
lcd.print("C");
lcd.setCursor(0, 1);
lcd.print("PWM=");
lcd.print(targetPWM[2]);
break;
}
}
// ---------- Envelope segitiga untuk breathing ----------
float getEnvelope(unsigned long elapsed, unsigned long duration) {
if (elapsed >= duration) return 0.0;
float t = (float)elapsed / duration;
if (t <= 0.5) return t / 0.5;
else return (1.0 - t) / 0.5;
}
// ---------- Update LED ----------
void updateAllLEDs() {
unsigned long now = millis();
bool systemActive = (soilVal <= 550);
int pwm0, pwm1, pwm2, pwm3, pwm4;
if (!systemActive) {
pwm0 = pwm1 = pwm2 = pwm3 = pwm4 = 0;
} else {
// ===== DHT Breathing =====
unsigned long elapsed = now - dhtStateStart;
float env = 0.0;
switch (dhtState) {
case BREATH_SUHU:
if (elapsed >= BREATH_DURATION) {
dhtState = JEDA_SUHU;
dhtStateStart = now;
elapsed = 0;
env = 0.0;
} else {
env = getEnvelope(elapsed, BREATH_DURATION);
}
pwm0 = (int)(targetPWM[0] * env);
pwm1 = 0;
break;
case JEDA_SUHU:
if (elapsed >= JEDA_DURATION) {
dhtState = BREATH_LEMBAB;
dhtStateStart = now;
elapsed = 0;
pwm0 = 0;
pwm1 = 0;
} else {
pwm0 = 0;
pwm1 = 0;
}
break;
case BREATH_LEMBAB:
if (elapsed >= BREATH_DURATION) {
dhtState = JEDA_LEMBAB;
dhtStateStart = now;
elapsed = 0;
pwm0 = 0;
pwm1 = 0;
} else {
env = getEnvelope(elapsed, BREATH_DURATION);
pwm0 = 0;
pwm1 = (int)(targetPWM[1] * env);
}
break;
case JEDA_LEMBAB:
if (elapsed >= JEDA_DURATION) {
dhtState = BREATH_SUHU;
dhtStateStart = now;
elapsed = 0;
pwm0 = 0;
pwm1 = 0;
} else {
pwm0 = 0;
pwm1 = 0;
}
break;
}
// ===== Blink Soil, MQ3, LM35 =====
if (blinkActiveLED == -1) {
blinkActiveLED = 0; // mulai dengan Soil
blinkStartTime = now;
}
unsigned long durasi = durasiBlink[blinkActiveLED][blinkIndex % NUM_RITMES];
if (now - blinkStartTime >= durasi) {
blinkActiveLED = (blinkActiveLED + 1) % 3;
blinkStartTime = now;
blinkIndex = (blinkIndex + 1) % NUM_RITMES;
}
int pwmSoil = (blinkActiveLED == 0) ? targetPWM[3] : 0;
int pwmMQ3 = (blinkActiveLED == 1) ? targetPWM[4] : 0;
int pwmLM35 = (blinkActiveLED == 2) ? targetPWM[2] : 0;
pwm2 = pwmLM35;
pwm3 = pwmSoil;
pwm4 = pwmMQ3;
}
analogWrite(ledDHTTemp, constrain(pwm0, 0, 255));
analogWrite(ledDHTHum, constrain(pwm1, 0, 255));
analogWrite(ledLM35, constrain(pwm2, 0, 255));
analogWrite(ledSoil, constrain(pwm3, 0, 255));
analogWrite(ledMQ3, constrain(pwm4, 0, 255));
static unsigned long lastPrint = 0;
if (now - lastPrint >= 200) {
lastPrint = now;
Serial.print("PWM: ");
Serial.print(pwm0); Serial.print(" ");
Serial.print(pwm1); Serial.print(" ");
Serial.print(pwm2); Serial.print(" ");
Serial.print(pwm3); Serial.print(" ");
Serial.print(pwm4);
if (!systemActive) Serial.print(" (SYSTEM OFF)");
Serial.println();
}
}
// ---------- Inisialisasi ritme acak untuk blink ----------
void generateBlinkRitmes() {
randomSeed(analogRead(A5) + millis());
for (int i = 0; i < NUM_RITMES; i++) {
durasiBlink[0][i] = random(100, 501); // 100-500 ms
durasiBlink[1][i] = random(100, 501);
durasiBlink[2][i] = random(100, 501);
}
Serial.println("Ritme blink acak telah di-generate.");
}
// ---------- Welcome LED ----------
void welcomeLED() {
int ledPins[] = {ledSoil, ledDHTTemp, ledDHTHum, ledMQ3, ledLM35};
for (int i = 0; i < 5; i++) {
analogWrite(ledPins[i], 255);
delay(300);
analogWrite(ledPins[i], 0);
delay(100);
}
for (int i = 0; i < 5; i++) analogWrite(ledPins[i], 255);
delay(500);
for (int i = 0; i < 5; i++) analogWrite(ledPins[i], 0);
delay(200);
}
// ========== SETUP ==========
void setup() {
Serial.begin(9600);
dht.begin();
lcd.init();
lcd.backlight();
lcd.clear();
lcd.print("Starting...");
delay(1000);
pinMode(ledSoil, OUTPUT);
pinMode(ledDHTTemp, OUTPUT);
pinMode(ledDHTHum, OUTPUT);
pinMode(ledMQ3, OUTPUT);
pinMode(ledLM35, OUTPUT);
analogWrite(ledSoil, 0);
analogWrite(ledDHTTemp, 0);
analogWrite(ledDHTHum, 0);
analogWrite(ledMQ3, 0);
analogWrite(ledLM35, 0);
welcomeLED();
generateBlinkRitmes();
dhtState = BREATH_SUHU;
dhtStateStart = millis();
blinkActiveLED = -1;
Serial.println("========================================");
Serial.println(" DHT Breathing bergantian (5s naik-turun, 1s jeda)");
Serial.println(" LED Soil, MQ3, LM35 blink bergantian dengan 50 ritme acak");
Serial.println(" Master Switch: Soil > 550 → LED mati");
Serial.println("========================================");
lastReadTime = millis();
lastPageChange = millis();
readAllSensors();
}
// ========== LOOP ==========
void loop() {
unsigned long now = millis();
if (now - lastReadTime >= READ_INTERVAL) {
lastReadTime = now;
readAllSensors();
}
if (now - lastPageChange >= PAGE_CHANGE_INTERVAL) {
lastPageChange = now;
lcdPage++;
if (lcdPage >= LCD_PAGES) lcdPage = 0;
updateLCD();
}
if (now - lastUpdateTime >= UPDATE_INTERVAL) {
lastUpdateTime = now;
updateAllLEDs();
}
}
/*
* ====================================================================
* LED dengan pola dinamis: mati total, nyala sebentar, variasi
* Setiap saluran memiliki duty cycle dan amplitude acak
* Parameter berubah setiap 4-6 detik
* Master switch: Soil > 550 → semua LED mati
* ====================================================================
*/
#include <DHT.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
// ========== LCD I2C ==========
LiquidCrystal_I2C lcd(0x27, 16, 2);
// ========== PIN SENSOR ==========
#define DHTPIN 2
#define DHTTYPE DHT11
const int soilPin = A0;
const int lm35Pin = A1;
const int mq3Pin = A2;
// ========== PIN LED (PWM) ==========
const int ledSoil = 11;
const int ledDHTTemp = 5;
const int ledDHTHum = 6;
const int ledMQ3 = 9;
const int ledLM35 = 10;
DHT dht(DHTPIN, DHTTYPE);
// ---------- Waktu ----------
const unsigned long READ_INTERVAL = 2000;
const unsigned long UPDATE_INTERVAL = 50;
const unsigned long PAGE_CHANGE_INTERVAL = 2000;
unsigned long lastReadTime = 0;
unsigned long lastUpdateTime = 0;
int lcdPage = 0;
const int LCD_PAGES = 5;
unsigned long lastPageChange = 0;
int targetPWM[5] = {0,0,0,0,0}; // indeks: 0=DHT suhu, 1=DHT hum, 2=LM35, 3=Soil, 4=MQ3
float dhtTemp = 0, dhtHum = 0, lm35Temp = 0;
int soilVal = 0, mq3Analog = 0;
bool systemActive = true;
// ====================================================================
// GENERATOR GELOMBANG DENGAN DUTY CYCLE DAN AMPLITUDE
// ====================================================================
enum WaveType { WAVE_SIN, WAVE_TRI, WAVE_SQUARE, WAVE_RAMP, WAVE_FLASH };
struct WaveParams {
WaveType type;
unsigned long period; // ms
unsigned long phase; // ms
float duty; // 0..1, persentase waktu menyala dalam satu siklus (khusus square)
float amplitude; // 0..1, faktor skala maksimum
};
WaveParams params[5];
unsigned long lastParamChange = 0;
unsigned long nextParamChange = 4000;
const unsigned long PARAM_CHANGE_MIN = 4000;
const unsigned long PARAM_CHANGE_MAX = 6000;
// Fungsi untuk menghasilkan nilai gelombang antara 0 dan 1
float getWaveValue(WaveParams p, unsigned long time) {
if (p.period == 0) p.period = 1;
unsigned long t = (time + p.phase) % p.period;
float pos = (float)t / p.period; // 0..1
float val = 0;
switch (p.type) {
case WAVE_SIN:
val = 0.5 + 0.5 * sin(pos * 2 * PI);
break;
case WAVE_TRI:
if (pos < 0.5) val = pos * 2;
else val = 2 - pos * 2;
break;
case WAVE_SQUARE:
// duty cycle: berapa lama ON dalam satu periode
val = (pos < p.duty) ? 1.0 : 0.0;
break;
case WAVE_RAMP:
val = pos; // naik dari 0 ke 1
break;
case WAVE_FLASH:
// kilat: ON selama 50ms lalu OFF, berulang dengan periode acak
// tapi kita sederhanakan: ON jika t < 50, else OFF
val = (t < 50) ? 1.0 : 0.0;
break;
default:
val = 0.5;
}
// Terapkan amplitude (nilai maksimum dikalikan dengan amplitude)
val = val * p.amplitude;
// Pastikan 0..1
return constrain(val, 0.0, 1.0);
}
void generateRandomParams(int idx) {
params[idx].type = (WaveType)random(0, 5);
params[idx].period = random(500, 8001);
params[idx].phase = random(0, params[idx].period);
params[idx].duty = (float)random(10, 91) / 100.0; // 0.1 .. 0.9
// amplitude antara 0.5 sampai 1.0, agar seringkali terang tapi tidak selalu
params[idx].amplitude = (float)random(50, 101) / 100.0; // 0.5 .. 1.0
// Untuk jenis tertentu, kita bisa modifikasi duty agar lebih ekstrim
if (params[idx].type == WAVE_SQUARE) {
// duty bisa sangat kecil atau besar untuk efek mati lama atau nyala lama
params[idx].duty = (float)random(5, 96) / 100.0; // 0.05 .. 0.95
}
if (params[idx].type == WAVE_FLASH) {
// flash sudah memiliki duty sendiri, kita abaikan
params[idx].duty = 0.5; // tidak digunakan
}
}
void initWaveParams() {
randomSeed(analogRead(A5) + millis());
for (int i = 0; i < 5; i++) {
generateRandomParams(i);
}
nextParamChange = random(PARAM_CHANGE_MIN, PARAM_CHANGE_MAX);
lastParamChange = millis();
}
void updateWaveParams() {
unsigned long now = millis();
if (now - lastParamChange >= nextParamChange) {
// Ubah 2-3 saluran secara acak
int numChange = random(2, 4);
for (int i = 0; i < numChange; i++) {
int idx = random(0, 5);
generateRandomParams(idx);
}
nextParamChange = random(PARAM_CHANGE_MIN, PARAM_CHANGE_MAX);
lastParamChange = now;
Serial.println("Parameter gelombang diubah (beberapa saluran)");
}
}
// ====================================================================
// MAPPING SENSOR (tetap)
// ====================================================================
int mapMQ3(int value) {
int pwm;
if (value < 30) pwm = 30;
else if (value <= 150) pwm = map(value, 30, 150, 30, 255);
else pwm = 255;
return constrain(pwm, 0, 255);
}
int mapSoil(int value) {
int pwm;
if (value >= 550) pwm = 0;
else if (value <= 200) pwm = 255;
else pwm = map(value, 200, 550, 255, 0);
return constrain(pwm, 0, 255);
}
int mapLM35(float temp) {
int pwm;
if (temp <= 28) pwm = 255;
else if (temp >= 49) pwm = 0;
else pwm = map((int)(temp * 10), 280, 490, 255, 0);
return constrain(pwm, 0, 255);
}
int mapDHTTemp(float temp) { return mapLM35(temp); }
int mapDHTHum(float hum) {
int pwm;
if (hum <= 30) pwm = 0;
else if (hum >= 80) pwm = 255;
else pwm = map((int)(hum * 10), 300, 800, 0, 255);
return constrain(pwm, 0, 255);
}
// ====================================================================
// BACA SENSOR
// ====================================================================
void readAllSensors() {
float h = dht.readHumidity();
float t = dht.readTemperature();
if (!isnan(h) && !isnan(t)) {
dhtHum = h;
dhtTemp = t;
} else {
Serial.println("[DHT] Error, gunakan nilai sebelumnya");
}
targetPWM[0] = mapDHTTemp(dhtTemp);
targetPWM[1] = mapDHTHum(dhtHum);
int rawLM35 = analogRead(lm35Pin);
lm35Temp = (rawLM35 * 5000.0 / 1023.0) / 10.0;
targetPWM[2] = mapLM35(lm35Temp);
soilVal = analogRead(soilPin);
targetPWM[3] = mapSoil(soilVal);
mq3Analog = analogRead(mq3Pin);
targetPWM[4] = mapMQ3(mq3Analog);
systemActive = (soilVal <= 550);
Serial.println("===== Pembacaan Sensor =====");
Serial.print("DHT Suhu: "); Serial.print(dhtTemp, 1); Serial.print("°C → PWM "); Serial.println(targetPWM[0]);
Serial.print("DHT Hum : "); Serial.print(dhtHum, 1); Serial.print("% → PWM "); Serial.println(targetPWM[1]);
Serial.print("LM35 : "); Serial.print(lm35Temp, 1); Serial.print("°C → PWM "); Serial.println(targetPWM[2]);
Serial.print("Soil : "); Serial.print(soilVal); Serial.print(" → PWM "); Serial.println(targetPWM[3]);
Serial.print("MQ3 : "); Serial.print(mq3Analog); Serial.print(" → PWM "); Serial.println(targetPWM[4]);
Serial.println("-----------------------------");
updateLCD();
}
// ====================================================================
// LCD
// ====================================================================
void updateLCD() {
lcd.clear();
lcd.setCursor(0, 0);
switch(lcdPage) {
case 0:
lcd.print("Project Natural");
lcd.setCursor(0, 1);
lcd.print("Sound JAV");
break;
case 1:
lcd.print("DHT: T=");
lcd.print(dhtTemp, 1);
lcd.print("C");
lcd.setCursor(0, 1);
lcd.print("H=");
lcd.print(dhtHum, 0);
lcd.print("%");
break;
case 2:
lcd.print("MQ3: ");
lcd.print(mq3Analog);
lcd.setCursor(0, 1);
lcd.print("PWM=");
lcd.print(targetPWM[4]);
break;
case 3:
lcd.print("Soil: ");
lcd.print(soilVal);
lcd.setCursor(0, 1);
if (soilVal > 550) lcd.print("SYS OFF");
else lcd.print("SYS ON");
break;
case 4:
lcd.print("LM35: ");
lcd.print(lm35Temp, 1);
lcd.print("C");
lcd.setCursor(0, 1);
lcd.print("PWM=");
lcd.print(targetPWM[2]);
break;
}
}
// ====================================================================
// UPDATE LED
// ====================================================================
void updateAllLEDs() {
unsigned long now = millis();
int pwm0, pwm1, pwm2, pwm3, pwm4;
if (!systemActive) {
pwm0 = pwm1 = pwm2 = pwm3 = pwm4 = 0;
} else {
// Perbarui parameter
updateWaveParams();
// Hitung envelope
float env[5];
for (int i = 0; i < 5; i++) {
env[i] = getWaveValue(params[i], now);
}
// PWM = target * envelope
pwm0 = (int)(targetPWM[0] * env[0]);
pwm1 = (int)(targetPWM[1] * env[1]);
pwm2 = (int)(targetPWM[2] * env[2]);
pwm3 = (int)(targetPWM[3] * env[3]);
pwm4 = (int)(targetPWM[4] * env[4]);
}
// Tulis ke pin
analogWrite(ledDHTTemp, constrain(pwm0, 0, 255));
analogWrite(ledDHTHum, constrain(pwm1, 0, 255));
analogWrite(ledLM35, constrain(pwm2, 0, 255));
analogWrite(ledSoil, constrain(pwm3, 0, 255));
analogWrite(ledMQ3, constrain(pwm4, 0, 255));
static unsigned long lastPrint = 0;
if (now - lastPrint >= 200) {
lastPrint = now;
Serial.print("PWM: ");
Serial.print(pwm0); Serial.print(" ");
Serial.print(pwm1); Serial.print(" ");
Serial.print(pwm2); Serial.print(" ");
Serial.print(pwm3); Serial.print(" ");
Serial.print(pwm4);
if (!systemActive) Serial.print(" (SYSTEM OFF)");
Serial.println();
}
}
// ====================================================================
// WELCOME LED
// ====================================================================
void welcomeLED() {
int ledPins[] = {ledSoil, ledDHTTemp, ledDHTHum, ledMQ3, ledLM35};
for (int i = 0; i < 5; i++) {
analogWrite(ledPins[i], 255);
delay(300);
analogWrite(ledPins[i], 0);
delay(100);
}
for (int i = 0; i < 5; i++) analogWrite(ledPins[i], 255);
delay(500);
for (int i = 0; i < 5; i++) analogWrite(ledPins[i], 0);
delay(200);
}
// ====================================================================
// SETUP
// ====================================================================
void setup() {
Serial.begin(9600);
dht.begin();
lcd.init();
lcd.backlight();
lcd.clear();
lcd.print("Starting...");
delay(1000);
pinMode(ledSoil, OUTPUT);
pinMode(ledDHTTemp, OUTPUT);
pinMode(ledDHTHum, OUTPUT);
pinMode(ledMQ3, OUTPUT);
pinMode(ledLM35, OUTPUT);
analogWrite(ledSoil, 0);
analogWrite(ledDHTTemp, 0);
analogWrite(ledDHTHum, 0);
analogWrite(ledMQ3, 0);
analogWrite(ledLM35, 0);
welcomeLED();
initWaveParams();
Serial.println("========================================");
Serial.println(" LED dengan pola mati/nyala variatif");
Serial.println(" Duty cycle dan amplitude acak");
Serial.println(" Master Switch: Soil > 550 → LED mati");
Serial.println("========================================");
lastReadTime = millis();
lastPageChange = millis();
readAllSensors();
}
// ====================================================================
// LOOP
// ====================================================================
void loop() {
unsigned long now = millis();
if (now - lastReadTime >= READ_INTERVAL) {
lastReadTime = now;
readAllSensors();
}
if (now - lastPageChange >= PAGE_CHANGE_INTERVAL) {
lastPageChange = now;
lcdPage++;
if (lcdPage >= LCD_PAGES) lcdPage = 0;
updateLCD();
}
if (now - lastUpdateTime >= UPDATE_INTERVAL) {
lastUpdateTime = now;
updateAllLEDs();
}
}
Alat ini adalah sebuah sistem elektronik yang membaca data dari beberapa sensor lingkungan (suhu, kelembapan, kadar alkohol, kelembapan tanah, dan suhu dari LM35) lalu mengubah data tersebut menjadi pola cahaya yang selalu berubah dari 5 buah LED. Pola cahaya ini kemudian diarahkan ke LDR (Light Dependent Resistor) yang terhubung ke perangkat efek suara (misalnya pedal echo atau mixer). Perubahan intensitas cahaya yang mengenai LDR akan mengubah nilai resistansinya, sehingga secara otomatis mengatur parameter efek suara seperti speed dan repeat menjadi lebih hidup dan bervariasi.
Dengan kata lain, alat ini adalah jembatan antara sensor lingkungan dan efek audio, sehingga suara yang dihasilkan tidak monoton, tetapi selalu berubah mengikuti kondisi sekitar.
Arduino Uno (pengendali utama)
Sensor DHT11 : mengukur suhu dan kelembapan udara.
Sensor LM35 : mengukur suhu ruangan dengan presisi lebih tinggi.
Sensor kelembapan tanah : mengukur kadar air di tanah (nilai 0–1023).
Sensor MQ3 : mendeteksi kadar alkohol (dalam bentuk analog).
5 buah LED (bisa berupa 2 LED RGB atau 5 LED biasa) yang terhubung ke pin PWM (5, 6, 9, 10, 11).
LCD I2C 16x2 : menampilkan data sensor secara bergantian.
LDR (tidak terhubung ke Arduino, melainkan ke perangkat efek suara) yang ditempatkan dekat dengan LED.
Setiap sensor dibaca setiap 2 detik. Nilai mentah dari sensor kemudian dipetakan ke rentang 0–255 (PWM) dengan aturan tertentu:
DHT suhu : 25°C → 255 (terang), 40°C → 0 (mati), dan linear di antaranya.
DHT kelembapan : 30% → 0 (mati), 80% → 255 (terang), linear.
LM35 : 28°C → 255, 49°C → 0, linear.
Soil : 200 → 255, 550 → 0, linear (nilai >550 mematikan seluruh sistem).
MQ3 : <30 → 30 (redup), 30–150 → linear 30–255, >150 → 255 (terang).
Nilai-nilai ini menjadi target kecerahan maksimum untuk setiap LED. Namun LED tidak menyala konstan, melainkan berdenyut mengikuti gelombang tertentu.
Agar cahaya selalu berubah dan tidak statis, setiap LED dikendalikan oleh sebuah generator gelombang yang menghasilkan nilai antara 0 hingga 1. Nilai ini dikalikan dengan target PWM dari sensor, sehingga LED akan menyala dari mati total (0) hingga terang penuh (target) secara bergantian.
Ada 5 jenis gelombang yang bisa dipilih secara acak:
Sinus : naik-turun halus seperti ombak.
Segitiga : naik lurus lalu turun lurus.
Kotak : menyala penuh lalu mati mendadak.
Random : nilai berubah-ubah acak setiap 50 ms.
Ramp : naik perlahan dari 0 ke 1, lalu kembali ke 0.
Setiap LED memiliki periode gelombang (lambat/cepat), fase (waktu mulai), dan rentang intensitas (misal 0.2–0.8) yang berbeda-beda. Yang terpenting, parameter ini diubah secara otomatis setiap 4–6 detik oleh program. Jadi pola nyala LED tidak pernah berulang persis, selalu ada variasi baru.
Contoh efek yang terlihat:
LED A berkedip cepat seperti kilat.
LED B menyala redup lalu memudar perlahan.
LED C mati beberapa detik lalu menyala terang.
Dua LED bisa menyala bersamaan atau bergantian, tergantung fase dan jenis gelombang.
Dengan 5 saluran independen, kombinasi pola yang dihasilkan sangat banyak (ratusan variasi).
Sensor tanah tidak hanya menghasilkan sinyal untuk LED-nya sendiri, tetapi juga bertindak sebagai pengaman utama. Jika nilai kelembapan tanah melebihi 550 (artinya tanah sangat basah), maka semua LED akan mati total. Ini berguna untuk menghemat energi atau mencegah efek suara yang tidak diinginkan dalam kondisi tertentu. Begitu nilai tanah turun di bawah 550, LED akan kembali menyala dengan pola terbaru.
LCD 16x2 menampilkan data sensor secara bergantian setiap 2 detik, dengan 5 halaman:
Judul proyek "Project Natural Sound JAV"
Suhu dan kelembapan DHT
Nilai MQ3 dan PWM-nya
Nilai tanah dan status sistem (ON/OFF)
Suhu LM35 dan PWM-nya
Serial Monitor di komputer menampilkan data yang lebih lengkap, termasuk nilai PWM setiap LED dan informasi perubahan parameter gelombang. Ini memudahkan pengguna untuk memantau dan menyesuaikan alat.
LED yang menyala dengan pola dinamis ditempatkan dekat dengan LDR yang terhubung ke perangkat efek suara (misalnya pedal echo). LDR adalah komponen yang nilai resistansinya berubah sesuai intensitas cahaya. Semakin terang cahaya yang mengenai LDR, semakin kecil resistansinya. Resistansi ini berfungsi seperti potensiometer pada pedal efek untuk mengatur speed (kecepatan gema) dan repeat (jumlah ulangan).
Karena pola cahaya LED selalu berubah-ubah (nyala, mati, redup, terang, cepat, lambat), maka resistansi LDR juga ikut berubah secara dinamis. Akibatnya, efek suara yang dihasilkan akan selalu berbeda dan tidak pernah monoton. Misalnya, saat LED menyala terang, echo menjadi cepat dan berulang banyak; saat LED redup, echo melambat dan berkurang. Variasi ini membuat suara menjadi lebih hidup, dramatis, dan cocok untuk pertunjukan musik atau instalasi seni.
Alat ini menggabungkan pembacaan sensor lingkungan dengan pola cahaya yang selalu berubah untuk mengontrol efek audio secara otomatis. Dengan memanfaatkan generator gelombang acak yang merespons nilai sensor, alat ini menghasilkan variasi tak terhingga yang membuat suara selalu terasa segar dan menarik. Semua proses berjalan otomatis tanpa campur tangan pengguna, cocok untuk aplikasi seni, pertunjukan, atau instalasi interaktif.
/*
* ====================================================================
* LED dengan generator gelombang dinamis – 5 saluran PWM
* Pola berubah setiap 4-6 detik, merespons nilai sensor
* Master switch: Soil > 550 → semua LED mati
* REDUKSI KECERAHAN 20% (global)
* Grup Soil/LM35/MQ3:
* - Normal lambat (6-15s)
* - Kilat bergantian dengan jeda bervariasi (800,900,1000,1200 ms)
* - Kilat lambat (jeda 500 ms)
* Grup DHT (suhu & kelembapan):
* - Breathing (fade)
* - Kilat 500 ms
* - Efek speed/repeat bergantian
* ====================================================================
*/
#include <DHT.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
// ========== LCD I2C ==========
LiquidCrystal_I2C lcd(0x27, 16, 2);
// ========== PIN SENSOR ==========
#define DHTPIN 2
#define DHTTYPE DHT11
const int soilPin = A0;
const int lm35Pin = A1;
const int mq3Pin = A2;
// ========== PIN LED (PWM) ==========
const int ledSoil = 11;
const int ledDHTTemp = 5;
const int ledDHTHum = 6;
const int ledMQ3 = 9;
const int ledLM35 = 10;
DHT dht(DHTPIN, DHTTYPE);
// ---------- Waktu ----------
const unsigned long READ_INTERVAL = 2000;
const unsigned long UPDATE_INTERVAL = 50;
const unsigned long PAGE_CHANGE_INTERVAL = 2000;
unsigned long lastReadTime = 0;
unsigned long lastUpdateTime = 0;
int lcdPage = 0;
const int LCD_PAGES = 5;
unsigned long lastPageChange = 0;
// ---------- Data sensor ----------
int targetPWM[5] = {0,0,0,0,0}; // indeks: 0=DHT suhu, 1=DHT hum, 2=LM35, 3=Soil, 4=MQ3
float dhtTemp = 0, dhtHum = 0, lm35Temp = 0;
int soilVal = 0, mq3Analog = 0;
bool systemActive = true;
// ====================================================================
// GENERATOR GELOMBANG
// ====================================================================
enum WaveType { WAVE_SIN, WAVE_TRI, WAVE_SQUARE, WAVE_RANDOM, WAVE_RAMP };
struct WaveParams {
WaveType type;
unsigned long period;
unsigned long phase;
float minVal;
float maxVal;
float pauseFraction; // 0..1: bagian awal siklus yang mati total
};
WaveParams params[5];
// Parameter untuk mode kilat
struct FlashParams {
bool active; // apakah saluran dalam mode kilat
unsigned long onTime; // durasi nyala (ms)
unsigned long offTime; // durasi mati (ms)
unsigned long phaseOffset; // phase offset untuk sinkronisasi
};
FlashParams flash[5]; // untuk 5 saluran
unsigned long lastParamChange = 0;
const unsigned long PARAM_CHANGE_INTERVAL_MIN = 4000;
const unsigned long PARAM_CHANGE_INTERVAL_MAX = 6000;
unsigned long nextParamChange = 4000;
// Variabel untuk mode speed/repeat bergantian pada DHT
bool speedRepeatMode = false; // true jika DHT temp cepat, DHT hum lambat, atau sebaliknya
// ====================================================================
// FUNGSI GELOMBANG DENGAN PAUSE DAN DUKUNGAN KILAT
// ====================================================================
float getWaveValue(int idx, unsigned long time) {
// Jika saluran dalam mode kilat, gunakan logika kilat
if (flash[idx].active) {
unsigned long cycle = flash[idx].onTime + flash[idx].offTime;
if (cycle == 0) return 0.0;
unsigned long t = (time + flash[idx].phaseOffset) % cycle;
if (t < flash[idx].onTime) {
// Nyala penuh
return 1.0;
} else {
return 0.0;
}
}
// Mode normal (breathing/fade)
WaveParams p = params[idx];
if (p.period == 0) p.period = 1;
unsigned long t = (time + p.phase) % p.period;
float pos = (float)t / p.period;
if (pos < p.pauseFraction) return 0.0;
float activePos = (pos - p.pauseFraction) / (1.0 - p.pauseFraction);
float val;
switch (p.type) {
case WAVE_SIN: val = 0.5 + 0.5 * sin(activePos * 2 * PI); break;
case WAVE_TRI: val = (activePos < 0.5) ? activePos * 2 : 2 - activePos * 2; break;
case WAVE_SQUARE: val = (activePos < 0.5) ? 1.0 : 0.0; break;
case WAVE_RANDOM: val = (float)((time / 50) % 101) / 100.0; break;
case WAVE_RAMP: val = activePos; break;
default: val = 0.5;
}
val = p.minVal + val * (p.maxVal - p.minVal);
return constrain(val, 0.0, 1.0);
}
// ====================================================================
// FUNGSI UNTUK MENGATUR MODE KILAT (GRUP LAMBAT DAN DHT)
// ====================================================================
// Atur mode kilat untuk grup lambat (idx 2,3,4) dengan jeda tertentu
void setSlowGroupFlash(unsigned long onTime, unsigned long offTime, bool staggered = true) {
// onTime dan offTime dalam ms
for (int i = 2; i <= 4; i++) {
flash[i].active = true;
flash[i].onTime = onTime;
flash[i].offTime = offTime;
if (staggered) {
// Beri fase agar bergantian: phase offset = (i-2) * (onTime + offTime) / 3
flash[i].phaseOffset = (i - 2) * (onTime + offTime) / 3;
} else {
flash[i].phaseOffset = 0;
}
}
// Matikan mode normal untuk saluran ini (tidak perlu, getWaveValue akan override)
}
// Atur mode kilat untuk DHT (idx 0 dan 1)
void setDHTFlash(unsigned long onTime, unsigned long offTime, bool opposite = false) {
for (int i = 0; i <= 1; i++) {
flash[i].active = true;
flash[i].onTime = onTime;
flash[i].offTime = offTime;
if (opposite) {
// Satu nyala saat yang lain mati
flash[i].phaseOffset = (i == 0) ? 0 : (onTime + offTime) / 2;
} else {
flash[i].phaseOffset = 0;
}
}
}
// Matikan mode kilat untuk semua saluran
void disableAllFlash() {
for (int i = 0; i < 5; i++) {
flash[i].active = false;
}
speedRepeatMode = false;
}
// ====================================================================
// GENERATE PARAMETER ACAC UNTUK SATU SALURAN
// ====================================================================
void generateRandomParams(int idx) {
// Jika idx di grup lambat (2,3,4)
if (idx >= 2 && idx <= 4) {
// Pilih mode: 30% normal lambat, 40% kilat bergantian (dengan jeda bervariasi), 30% kilat lambat (500ms)
int mode = random(0, 100);
if (mode < 30) {
// Normal lambat
disableAllFlash(); // matikan semua flash dulu (tapi bisa di-override oleh saluran lain)
// Kita set normal untuk saluran ini (tapi jika saluran lain masih flash, kita biarkan)
// Lebih aman: set flash[idx].active = false
flash[idx].active = false;
params[idx].type = (WaveType)random(0, 5);
params[idx].period = random(6000, 15001);
params[idx].pauseFraction = (float)random(0, 51) / 100.0;
params[idx].phase = random(0, params[idx].period);
float minV = (float)random(0, 30) / 100.0;
float maxV = (float)random(70, 101) / 100.0;
if (maxV <= minV) { maxV = minV + 0.3; if (maxV > 1.0) maxV = 1.0; }
params[idx].minVal = minV;
params[idx].maxVal = maxV;
} else if (mode < 70) {
// Kilat bergantian dengan jeda bervariasi (800, 900, 1000, 1200 ms)
// Pilih onTime = 300ms (nyala), offTime = salah satu dari daftar
unsigned long offTimes[] = {800, 900, 1000, 1200};
int pick = random(0, 4);
unsigned long off = offTimes[pick];
setSlowGroupFlash(300, off, true); // staggered = true
} else {
// Kilat lambat (jeda 500 ms)
setSlowGroupFlash(500, 500, true); // staggered true agar bergantian
}
} else {
// DHT (idx 0 dan 1)
// Pilih mode: 40% breathing, 30% kilat 500ms, 30% speed/repeat (satu cepat, satu lambat)
int mode = random(0, 100);
if (mode < 40) {
// Breathing
flash[idx].active = false;
params[idx].type = (WaveType)random(0, 5);
params[idx].period = random(3000, 10001);
params[idx].pauseFraction = (float)random(0, 51) / 100.0;
params[idx].phase = random(0, params[idx].period);
float minV = (float)random(0, 30) / 100.0;
float maxV = (float)random(70, 101) / 100.0;
if (maxV <= minV) { maxV = minV + 0.3; if (maxV > 1.0) maxV = 1.0; }
params[idx].minVal = minV;
params[idx].maxVal = maxV;
} else if (mode < 70) {
// Kilat 500ms (nyala 500, mati 500)
// Aktifkan untuk kedua DHT dengan phase berbeda (bergantian)
setDHTFlash(500, 500, true); // opposite = true agar satu nyala satu mati
} else {
// Speed/repeat: satu cepat (300ms on, 300ms off), satu lambat (1000ms on, 1000ms off)
// Atau sebaliknya
if (random(0, 2) == 0) {
// Saluran 0 cepat, saluran 1 lambat
flash[0].active = true;
flash[0].onTime = 300; flash[0].offTime = 300; flash[0].phaseOffset = 0;
flash[1].active = true;
flash[1].onTime = 1000; flash[1].offTime = 1000; flash[1].phaseOffset = 0;
} else {
// Saluran 0 lambat, saluran 1 cepat
flash[0].active = true;
flash[0].onTime = 1000; flash[0].offTime = 1000; flash[0].phaseOffset = 0;
flash[1].active = true;
flash[1].onTime = 300; flash[1].offTime = 300; flash[1].phaseOffset = 0;
}
speedRepeatMode = true;
}
}
}
void initWaveParams() {
randomSeed(analogRead(A5) + millis());
// Inisialisasi semua saluran dengan normal lambat/sedang
for (int i = 0; i < 5; i++) {
flash[i].active = false;
if (i >= 2) {
params[i].type = (WaveType)random(0, 5);
params[i].period = random(6000, 15001);
params[i].pauseFraction = (float)random(0, 51) / 100.0;
params[i].phase = random(0, params[i].period);
float minV = (float)random(0, 30) / 100.0;
float maxV = (float)random(70, 101) / 100.0;
if (maxV <= minV) { maxV = minV + 0.3; if (maxV > 1.0) maxV = 1.0; }
params[i].minVal = minV;
params[i].maxVal = maxV;
} else {
params[i].type = (WaveType)random(0, 5);
params[i].period = random(3000, 10001);
params[i].pauseFraction = (float)random(0, 51) / 100.0;
params[i].phase = random(0, params[i].period);
float minV = (float)random(0, 30) / 100.0;
float maxV = (float)random(70, 101) / 100.0;
if (maxV <= minV) { maxV = minV + 0.3; if (maxV > 1.0) maxV = 1.0; }
params[i].minVal = minV;
params[i].maxVal = maxV;
}
}
nextParamChange = random(PARAM_CHANGE_INTERVAL_MIN, PARAM_CHANGE_INTERVAL_MAX);
lastParamChange = millis();
}
void updateWaveParams() {
unsigned long now = millis();
if (now - lastParamChange >= nextParamChange) {
int numChange = random(2, 4);
for (int i = 0; i < numChange; i++) {
int idx = random(0, 5);
generateRandomParams(idx);
}
// Reset mode flash jika perlu
// (generateRandomParams sudah mengatur flash masing-masing saluran)
nextParamChange = random(PARAM_CHANGE_INTERVAL_MIN, PARAM_CHANGE_INTERVAL_MAX);
lastParamChange = now;
Serial.println("Parameter gelombang diubah");
}
}
// ====================================================================
// FUNGSI MAPPING SENSOR (tetap)
// ====================================================================
int mapMQ3(int value) {
int pwm;
if (value < 30) pwm = 30;
else if (value <= 150) pwm = map(value, 30, 150, 30, 255);
else pwm = 255;
return constrain(pwm, 0, 255);
}
int mapSoil(int value) {
int pwm;
if (value >= 550) pwm = 0;
else if (value <= 200) pwm = 255;
else pwm = map(value, 200, 550, 255, 0);
return constrain(pwm, 0, 255);
}
int mapLM35(float temp) {
int pwm;
if (temp <= 28) pwm = 255;
else if (temp >= 49) pwm = 0;
else pwm = map((int)(temp * 10), 280, 490, 255, 0);
return constrain(pwm, 0, 255);
}
int mapDHTTemp(float temp) { return mapLM35(temp); }
int mapDHTHum(float hum) {
int pwm;
if (hum <= 30) pwm = 0;
else if (hum >= 80) pwm = 255;
else pwm = map((int)(hum * 10), 300, 800, 0, 255);
return constrain(pwm, 0, 255);
}
// ====================================================================
// BACA SENSOR
// ====================================================================
void readAllSensors() {
float h = dht.readHumidity();
float t = dht.readTemperature();
if (!isnan(h) && !isnan(t)) {
dhtHum = h;
dhtTemp = t;
} else {
Serial.println("[DHT] Error, gunakan nilai sebelumnya");
}
targetPWM[0] = mapDHTTemp(dhtTemp);
targetPWM[1] = mapDHTHum(dhtHum);
int rawLM35 = analogRead(lm35Pin);
lm35Temp = (rawLM35 * 5000.0 / 1023.0) / 10.0;
targetPWM[2] = mapLM35(lm35Temp);
soilVal = analogRead(soilPin);
targetPWM[3] = mapSoil(soilVal);
mq3Analog = analogRead(mq3Pin);
targetPWM[4] = mapMQ3(mq3Analog);
systemActive = (soilVal <= 550);
Serial.println("===== Pembacaan Sensor =====");
Serial.print("DHT Suhu: "); Serial.print(dhtTemp, 1); Serial.print("°C → PWM "); Serial.println(targetPWM[0]);
Serial.print("DHT Hum : "); Serial.print(dhtHum, 1); Serial.print("% → PWM "); Serial.println(targetPWM[1]);
Serial.print("LM35 : "); Serial.print(lm35Temp, 1); Serial.print("°C → PWM "); Serial.println(targetPWM[2]);
Serial.print("Soil : "); Serial.print(soilVal); Serial.print(" → PWM "); Serial.println(targetPWM[3]);
Serial.print("MQ3 : "); Serial.print(mq3Analog); Serial.print(" → PWM "); Serial.println(targetPWM[4]);
Serial.println("-----------------------------");
updateLCD();
}
// ====================================================================
// LCD
// ====================================================================
void updateLCD() {
lcd.clear();
lcd.setCursor(0, 0);
switch(lcdPage) {
case 0:
lcd.print("Project Natural");
lcd.setCursor(0, 1);
lcd.print("Sound JAV");
break;
case 1:
lcd.print("DHT: T=");
lcd.print(dhtTemp, 1);
lcd.print("C");
lcd.setCursor(0, 1);
lcd.print("H=");
lcd.print(dhtHum, 0);
lcd.print("%");
break;
case 2:
lcd.print("MQ3: ");
lcd.print(mq3Analog);
lcd.setCursor(0, 1);
lcd.print("PWM=");
lcd.print(targetPWM[4]);
break;
case 3:
lcd.print("Soil: ");
lcd.print(soilVal);
lcd.setCursor(0, 1);
if (soilVal > 550) lcd.print("SYS OFF");
else lcd.print("SYS ON");
break;
case 4:
lcd.print("LM35: ");
lcd.print(lm35Temp, 1);
lcd.print("C");
lcd.setCursor(0, 1);
lcd.print("PWM=");
lcd.print(targetPWM[2]);
break;
}
}
// ====================================================================
// UPDATE LED DENGAN REDUKSI 20% DAN REDUKSI TAMBAHAN JIKA BERSAMAAN
// ====================================================================
void updateAllLEDs() {
unsigned long now = millis();
int pwm0, pwm1, pwm2, pwm3, pwm4;
if (!systemActive) {
pwm0 = pwm1 = pwm2 = pwm3 = pwm4 = 0;
} else {
updateWaveParams();
// Hitung envelope untuk masing-masing saluran
float env0 = getWaveValue(0, now);
float env1 = getWaveValue(1, now);
float env2 = getWaveValue(2, now);
float env3 = getWaveValue(3, now);
float env4 = getWaveValue(4, now);
// PWM dasar dengan reduksi global 20%
pwm0 = (int)(targetPWM[0] * env0 * 0.8);
pwm1 = (int)(targetPWM[1] * env1 * 0.8);
pwm2 = (int)(targetPWM[2] * env2 * 0.8);
pwm3 = (int)(targetPWM[3] * env3 * 0.8);
pwm4 = (int)(targetPWM[4] * env4 * 0.8);
// ---- Penanganan khusus untuk grup lambat (LM35, Soil, MQ3) ----
int count = 0;
if (pwm2 > 0) count++;
if (pwm3 > 0) count++;
if (pwm4 > 0) count++;
if (count > 1) {
float extraReduction = 0.7; // 70% dari nilai saat ini
pwm2 = (int)(pwm2 * extraReduction);
pwm3 = (int)(pwm3 * extraReduction);
pwm4 = (int)(pwm4 * extraReduction);
}
}
analogWrite(ledDHTTemp, constrain(pwm0, 0, 255));
analogWrite(ledDHTHum, constrain(pwm1, 0, 255));
analogWrite(ledLM35, constrain(pwm2, 0, 255));
analogWrite(ledSoil, constrain(pwm3, 0, 255));
analogWrite(ledMQ3, constrain(pwm4, 0, 255));
static unsigned long lastPrint = 0;
if (now - lastPrint >= 200) {
lastPrint = now;
Serial.print("PWM (80%): ");
Serial.print(pwm0); Serial.print(" ");
Serial.print(pwm1); Serial.print(" ");
Serial.print(pwm2); Serial.print(" ");
Serial.print(pwm3); Serial.print(" ");
Serial.print(pwm4);
if (!systemActive) Serial.print(" (SYS OFF)");
Serial.println();
}
}
// ====================================================================
// WELCOME LED
// ====================================================================
void welcomeLED() {
int ledPins[] = {ledSoil, ledDHTTemp, ledDHTHum, ledMQ3, ledLM35};
for (int i = 0; i < 5; i++) {
analogWrite(ledPins[i], 255);
delay(300);
analogWrite(ledPins[i], 0);
delay(100);
}
for (int i = 0; i < 5; i++) analogWrite(ledPins[i], 255);
delay(500);
for (int i = 0; i < 5; i++) analogWrite(ledPins[i], 0);
delay(200);
}
// ====================================================================
// SETUP
// ====================================================================
void setup() {
Serial.begin(9600);
dht.begin();
lcd.init();
lcd.backlight();
lcd.clear();
lcd.print("Starting...");
delay(1000);
pinMode(ledSoil, OUTPUT);
pinMode(ledDHTTemp, OUTPUT);
pinMode(ledDHTHum, OUTPUT);
pinMode(ledMQ3, OUTPUT);
pinMode(ledLM35, OUTPUT);
analogWrite(ledSoil, 0);
analogWrite(ledDHTTemp, 0);
analogWrite(ledDHTHum, 0);
analogWrite(ledMQ3, 0);
analogWrite(ledLM35, 0);
welcomeLED();
initWaveParams();
Serial.println("========================================");
Serial.println(" LED dengan variasi kilat & breathing");
Serial.println(" Grup lambat: jeda 800,900,1000,1200,500");
Serial.println(" DHT: kilat 500 & speed/repeat");
Serial.println(" Reduksi global 20%, tambahan jika bersama");
Serial.println(" Master Switch: Soil > 550 → LED mati");
Serial.println("========================================");
lastReadTime = millis();
lastPageChange = millis();
readAllSensors();
}
// ====================================================================
// LOOP
// ====================================================================
void loop() {
unsigned long now = millis();
if (now - lastReadTime >= READ_INTERVAL) {
lastReadTime = now;
readAllSensors();
}
if (now - lastPageChange >= PAGE_CHANGE_INTERVAL) {
lastPageChange = now;
lcdPage++;
if (lcdPage >= LCD_PAGES) lcdPage = 0;
updateLCD();
}
if (now - lastUpdateTime >= UPDATE_INTERVAL) {
lastUpdateTime = now;
updateAllLEDs();
}
}
/*
* ====================================================================
* LED dengan generator gelombang dinamis – 5 saluran PWM
* Pola berubah setiap 4-6 detik, merespons nilai sensor
* Master switch: Soil > 550 → semua LED mati
* REDUKSI KECERAHAN 20% (global)
* Grup Soil/LM35/MQ3:
* - Normal lambat (6-15s)
* - Kilat bergantian dengan jeda bervariasi (800,900,1000,1200 ms)
* - Kilat lambat (jeda 500 ms)
* Grup DHT (suhu & kelembapan):
* - Breathing (fade)
* - Kilat 500 ms
* - Efek speed/repeat bergantian
* ====================================================================
* MODIFIKASI LCD:
* - Baris pertama: status 5 LED dalam persentase (0-100%)
* - Baris kedua: data sensor sesuai halaman (bergantian tiap 2 detik)
* - LCD diperbarui setiap 200 ms agar sinkron dengan nyala LED
* ====================================================================
*/
#include <DHT.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
// ========== LCD I2C ==========
LiquidCrystal_I2C lcd(0x27, 16, 2);
// ========== PIN SENSOR ==========
#define DHTPIN 2
#define DHTTYPE DHT11
const int soilPin = A0;
const int lm35Pin = A1;
const int mq3Pin = A2;
// ========== PIN LED (PWM) ==========
const int ledSoil = 11;
const int ledDHTTemp = 5;
const int ledDHTHum = 6;
const int ledMQ3 = 9;
const int ledLM35 = 10;
DHT dht(DHTPIN, DHTTYPE);
// ---------- Waktu ----------
const unsigned long READ_INTERVAL = 2000;
const unsigned long UPDATE_INTERVAL = 50;
const unsigned long PAGE_CHANGE_INTERVAL = 2000;
const unsigned long LCD_UPDATE_INTERVAL = 200; // update LCD setiap 200 ms
unsigned long lastReadTime = 0;
unsigned long lastUpdateTime = 0;
int lcdPage = 0;
const int LCD_PAGES = 5;
unsigned long lastPageChange = 0;
unsigned long lastLCDUpdate = 0; // untuk update LCD berkala
// ---------- Data sensor ----------
int targetPWM[5] = {0,0,0,0,0}; // indeks: 0=DHT suhu, 1=DHT hum, 2=LM35, 3=Soil, 4=MQ3
float dhtTemp = 0, dhtHum = 0, lm35Temp = 0;
int soilVal = 0, mq3Analog = 0;
bool systemActive = true;
// ====================================================================
// VARIABEL UNTUK MENYIMPAN NILAI PWM TERAKHIR (untuk ditampilkan di LCD)
// ====================================================================
int lastPWM[5] = {0,0,0,0,0};
// ====================================================================
// GENERATOR GELOMBANG
// ====================================================================
enum WaveType { WAVE_SIN, WAVE_TRI, WAVE_SQUARE, WAVE_RANDOM, WAVE_RAMP };
struct WaveParams {
WaveType type;
unsigned long period;
unsigned long phase;
float minVal;
float maxVal;
float pauseFraction; // 0..1: bagian awal siklus yang mati total
};
WaveParams params[5];
// Parameter untuk mode kilat
struct FlashParams {
bool active; // apakah saluran dalam mode kilat
unsigned long onTime; // durasi nyala (ms)
unsigned long offTime; // durasi mati (ms)
unsigned long phaseOffset; // phase offset untuk sinkronisasi
};
FlashParams flash[5]; // untuk 5 saluran
unsigned long lastParamChange = 0;
const unsigned long PARAM_CHANGE_INTERVAL_MIN = 4000;
const unsigned long PARAM_CHANGE_INTERVAL_MAX = 6000;
unsigned long nextParamChange = 4000;
// Variabel untuk mode speed/repeat bergantian pada DHT
bool speedRepeatMode = false;
// ====================================================================
// FUNGSI GELOMBANG DENGAN PAUSE DAN DUKUNGAN KILAT
// ====================================================================
float getWaveValue(int idx, unsigned long time) {
// Jika saluran dalam mode kilat, gunakan logika kilat
if (flash[idx].active) {
unsigned long cycle = flash[idx].onTime + flash[idx].offTime;
if (cycle == 0) return 0.0;
unsigned long t = (time + flash[idx].phaseOffset) % cycle;
if (t < flash[idx].onTime) {
// Nyala penuh
return 1.0;
} else {
return 0.0;
}
}
// Mode normal (breathing/fade)
WaveParams p = params[idx];
if (p.period == 0) p.period = 1;
unsigned long t = (time + p.phase) % p.period;
float pos = (float)t / p.period;
if (pos < p.pauseFraction) return 0.0;
float activePos = (pos - p.pauseFraction) / (1.0 - p.pauseFraction);
float val;
switch (p.type) {
case WAVE_SIN: val = 0.5 + 0.5 * sin(activePos * 2 * PI); break;
case WAVE_TRI: val = (activePos < 0.5) ? activePos * 2 : 2 - activePos * 2; break;
case WAVE_SQUARE: val = (activePos < 0.5) ? 1.0 : 0.0; break;
case WAVE_RANDOM: val = (float)((time / 50) % 101) / 100.0; break;
case WAVE_RAMP: val = activePos; break;
default: val = 0.5;
}
val = p.minVal + val * (p.maxVal - p.minVal);
return constrain(val, 0.0, 1.0);
}
// ====================================================================
// FUNGSI UNTUK MENGATUR MODE KILAT (GRUP LAMBAT DAN DHT)
// ====================================================================
// Atur mode kilat untuk grup lambat (idx 2,3,4) dengan jeda tertentu
void setSlowGroupFlash(unsigned long onTime, unsigned long offTime, bool staggered = true) {
// onTime dan offTime dalam ms
for (int i = 2; i <= 4; i++) {
flash[i].active = true;
flash[i].onTime = onTime;
flash[i].offTime = offTime;
if (staggered) {
// Beri fase agar bergantian: phase offset = (i-2) * (onTime + offTime) / 3
flash[i].phaseOffset = (i - 2) * (onTime + offTime) / 3;
} else {
flash[i].phaseOffset = 0;
}
}
}
// Atur mode kilat untuk DHT (idx 0 dan 1)
void setDHTFlash(unsigned long onTime, unsigned long offTime, bool opposite = false) {
for (int i = 0; i <= 1; i++) {
flash[i].active = true;
flash[i].onTime = onTime;
flash[i].offTime = offTime;
if (opposite) {
// Satu nyala saat yang lain mati
flash[i].phaseOffset = (i == 0) ? 0 : (onTime + offTime) / 2;
} else {
flash[i].phaseOffset = 0;
}
}
}
// Matikan mode kilat untuk semua saluran
void disableAllFlash() {
for (int i = 0; i < 5; i++) {
flash[i].active = false;
}
speedRepeatMode = false;
}
// ====================================================================
// GENERATE PARAMETER ACAC UNTUK SATU SALURAN
// ====================================================================
void generateRandomParams(int idx) {
// Jika idx di grup lambat (2,3,4)
if (idx >= 2 && idx <= 4) {
// Pilih mode: 30% normal lambat, 40% kilat bergantian (dengan jeda bervariasi), 30% kilat lambat (500ms)
int mode = random(0, 100);
if (mode < 30) {
// Normal lambat
disableAllFlash();
flash[idx].active = false;
params[idx].type = (WaveType)random(0, 5);
params[idx].period = random(6000, 15001);
params[idx].pauseFraction = (float)random(0, 51) / 100.0;
params[idx].phase = random(0, params[idx].period);
float minV = (float)random(0, 30) / 100.0;
float maxV = (float)random(70, 101) / 100.0;
if (maxV <= minV) { maxV = minV + 0.3; if (maxV > 1.0) maxV = 1.0; }
params[idx].minVal = minV;
params[idx].maxVal = maxV;
} else if (mode < 70) {
// Kilat bergantian dengan jeda bervariasi (800, 900, 1000, 1200 ms)
unsigned long offTimes[] = {800, 900, 1000, 1200};
int pick = random(0, 4);
unsigned long off = offTimes[pick];
setSlowGroupFlash(300, off, true);
} else {
// Kilat lambat (jeda 500 ms)
setSlowGroupFlash(500, 500, true);
}
} else {
// DHT (idx 0 dan 1)
int mode = random(0, 100);
if (mode < 40) {
// Breathing
flash[idx].active = false;
params[idx].type = (WaveType)random(0, 5);
params[idx].period = random(3000, 10001);
params[idx].pauseFraction = (float)random(0, 51) / 100.0;
params[idx].phase = random(0, params[idx].period);
float minV = (float)random(0, 30) / 100.0;
float maxV = (float)random(70, 101) / 100.0;
if (maxV <= minV) { maxV = minV + 0.3; if (maxV > 1.0) maxV = 1.0; }
params[idx].minVal = minV;
params[idx].maxVal = maxV;
} else if (mode < 70) {
// Kilat 500ms (nyala 500, mati 500)
setDHTFlash(500, 500, true);
} else {
// Speed/repeat: satu cepat (300ms on, 300ms off), satu lambat (1000ms on, 1000ms off)
if (random(0, 2) == 0) {
flash[0].active = true;
flash[0].onTime = 300; flash[0].offTime = 300; flash[0].phaseOffset = 0;
flash[1].active = true;
flash[1].onTime = 1000; flash[1].offTime = 1000; flash[1].phaseOffset = 0;
} else {
flash[0].active = true;
flash[0].onTime = 1000; flash[0].offTime = 1000; flash[0].phaseOffset = 0;
flash[1].active = true;
flash[1].onTime = 300; flash[1].offTime = 300; flash[1].phaseOffset = 0;
}
speedRepeatMode = true;
}
}
}
void initWaveParams() {
randomSeed(analogRead(A5) + millis());
for (int i = 0; i < 5; i++) {
flash[i].active = false;
if (i >= 2) {
params[i].type = (WaveType)random(0, 5);
params[i].period = random(6000, 15001);
params[i].pauseFraction = (float)random(0, 51) / 100.0;
params[i].phase = random(0, params[i].period);
float minV = (float)random(0, 30) / 100.0;
float maxV = (float)random(70, 101) / 100.0;
if (maxV <= minV) { maxV = minV + 0.3; if (maxV > 1.0) maxV = 1.0; }
params[i].minVal = minV;
params[i].maxVal = maxV;
} else {
params[i].type = (WaveType)random(0, 5);
params[i].period = random(3000, 10001);
params[i].pauseFraction = (float)random(0, 51) / 100.0;
params[i].phase = random(0, params[i].period);
float minV = (float)random(0, 30) / 100.0;
float maxV = (float)random(70, 101) / 100.0;
if (maxV <= minV) { maxV = minV + 0.3; if (maxV > 1.0) maxV = 1.0; }
params[i].minVal = minV;
params[i].maxVal = maxV;
}
}
nextParamChange = random(PARAM_CHANGE_INTERVAL_MIN, PARAM_CHANGE_INTERVAL_MAX);
lastParamChange = millis();
}
void updateWaveParams() {
unsigned long now = millis();
if (now - lastParamChange >= nextParamChange) {
int numChange = random(2, 4);
for (int i = 0; i < numChange; i++) {
int idx = random(0, 5);
generateRandomParams(idx);
}
nextParamChange = random(PARAM_CHANGE_INTERVAL_MIN, PARAM_CHANGE_INTERVAL_MAX);
lastParamChange = now;
Serial.println("Parameter gelombang diubah");
}
}
// ====================================================================
// FUNGSI MAPPING SENSOR (tetap)
// ====================================================================
int mapMQ3(int value) {
int pwm;
if (value < 30) pwm = 30;
else if (value <= 150) pwm = map(value, 30, 150, 30, 255);
else pwm = 255;
return constrain(pwm, 0, 255);
}
int mapSoil(int value) {
int pwm;
if (value >= 550) pwm = 0;
else if (value <= 200) pwm = 255;
else pwm = map(value, 200, 550, 255, 0);
return constrain(pwm, 0, 255);
}
int mapLM35(float temp) {
int pwm;
if (temp <= 28) pwm = 255;
else if (temp >= 49) pwm = 0;
else pwm = map((int)(temp * 10), 280, 490, 255, 0);
return constrain(pwm, 0, 255);
}
int mapDHTTemp(float temp) { return mapLM35(temp); }
int mapDHTHum(float hum) {
int pwm;
if (hum <= 30) pwm = 0;
else if (hum >= 80) pwm = 255;
else pwm = map((int)(hum * 10), 300, 800, 0, 255);
return constrain(pwm, 0, 255);
}
// ====================================================================
// BACA SENSOR
// ====================================================================
void readAllSensors() {
float h = dht.readHumidity();
float t = dht.readTemperature();
if (!isnan(h) && !isnan(t)) {
dhtHum = h;
dhtTemp = t;
} else {
Serial.println("[DHT] Error, gunakan nilai sebelumnya");
}
targetPWM[0] = mapDHTTemp(dhtTemp);
targetPWM[1] = mapDHTHum(dhtHum);
int rawLM35 = analogRead(lm35Pin);
lm35Temp = (rawLM35 * 5000.0 / 1023.0) / 10.0;
targetPWM[2] = mapLM35(lm35Temp);
soilVal = analogRead(soilPin);
targetPWM[3] = mapSoil(soilVal);
mq3Analog = analogRead(mq3Pin);
targetPWM[4] = mapMQ3(mq3Analog);
systemActive = (soilVal <= 550);
Serial.println("===== Pembacaan Sensor =====");
Serial.print("DHT Suhu: "); Serial.print(dhtTemp, 1); Serial.print("°C → PWM "); Serial.println(targetPWM[0]);
Serial.print("DHT Hum : "); Serial.print(dhtHum, 1); Serial.print("% → PWM "); Serial.println(targetPWM[1]);
Serial.print("LM35 : "); Serial.print(lm35Temp, 1); Serial.print("°C → PWM "); Serial.println(targetPWM[2]);
Serial.print("Soil : "); Serial.print(soilVal); Serial.print(" → PWM "); Serial.println(targetPWM[3]);
Serial.print("MQ3 : "); Serial.print(mq3Analog); Serial.print(" → PWM "); Serial.println(targetPWM[4]);
Serial.println("-----------------------------");
// updateLCD() dipanggil secara berkala di loop, tidak perlu dipanggil di sini
}
// ====================================================================
// LCD – BARIS PERTAMA STATUS LED, BARIS KEDUA DATA SENSOR
// ====================================================================
void updateLCD() {
lcd.clear();
// ---- Baris pertama: status 5 LED dalam persentase (0-100%) ----
lcd.setCursor(0, 0);
char buf[17];
sprintf(buf, "%2d %2d %2d %2d %2d",
(int)(lastPWM[0] / 2.55), // konversi 0-255 ke 0-100
(int)(lastPWM[1] / 2.55),
(int)(lastPWM[2] / 2.55),
(int)(lastPWM[3] / 2.55),
(int)(lastPWM[4] / 2.55));
lcd.print(buf);
// ---- Baris kedua: data sensor sesuai halaman ----
lcd.setCursor(0, 1);
switch(lcdPage) {
case 0:
lcd.print("asun86.RFI.JAV");
break;
case 1:
lcd.print("T=");
lcd.print(dhtTemp, 1);
lcd.print("C H=");
lcd.print(dhtHum, 0);
lcd.print("%");
break;
case 2:
lcd.print("MQ3:");
lcd.print(mq3Analog);
lcd.print(" P=");
lcd.print(targetPWM[4]);
break;
case 3:
lcd.print("Soil:");
lcd.print(soilVal);
if (soilVal > 550) lcd.print(" OFF");
else lcd.print(" ON ");
break;
case 4:
lcd.print("LM35:");
lcd.print(lm35Temp, 1);
lcd.print("C P=");
lcd.print(targetPWM[2]);
break;
}
}
// ====================================================================
// UPDATE LED DENGAN REDUKSI 20% DAN REDUKSI TAMBAHAN JIKA BERSAMAAN
// ====================================================================
void updateAllLEDs() {
unsigned long now = millis();
int pwm0, pwm1, pwm2, pwm3, pwm4;
if (!systemActive) {
pwm0 = pwm1 = pwm2 = pwm3 = pwm4 = 0;
} else {
updateWaveParams();
float env0 = getWaveValue(0, now);
float env1 = getWaveValue(1, now);
float env2 = getWaveValue(2, now);
float env3 = getWaveValue(3, now);
float env4 = getWaveValue(4, now);
pwm0 = (int)(targetPWM[0] * env0 * 0.8);
pwm1 = (int)(targetPWM[1] * env1 * 0.8);
pwm2 = (int)(targetPWM[2] * env2 * 0.8);
pwm3 = (int)(targetPWM[3] * env3 * 0.8);
pwm4 = (int)(targetPWM[4] * env4 * 0.8);
// Reduksi tambahan jika lebih dari satu LED grup lambat menyala
int count = 0;
if (pwm2 > 0) count++;
if (pwm3 > 0) count++;
if (pwm4 > 0) count++;
if (count > 1) {
float extra = 0.7;
pwm2 = (int)(pwm2 * extra);
pwm3 = (int)(pwm3 * extra);
pwm4 = (int)(pwm4 * extra);
}
}
// Simpan nilai PWM terakhir untuk ditampilkan di LCD
lastPWM[0] = constrain(pwm0, 0, 255);
lastPWM[1] = constrain(pwm1, 0, 255);
lastPWM[2] = constrain(pwm2, 0, 255);
lastPWM[3] = constrain(pwm3, 0, 255);
lastPWM[4] = constrain(pwm4, 0, 255);
analogWrite(ledDHTTemp, lastPWM[0]);
analogWrite(ledDHTHum, lastPWM[1]);
analogWrite(ledLM35, lastPWM[2]);
analogWrite(ledSoil, lastPWM[3]);
analogWrite(ledMQ3, lastPWM[4]);
static unsigned long lastPrint = 0;
if (now - lastPrint >= 200) {
lastPrint = now;
Serial.print("PWM (80%): ");
Serial.print(lastPWM[0]); Serial.print(" ");
Serial.print(lastPWM[1]); Serial.print(" ");
Serial.print(lastPWM[2]); Serial.print(" ");
Serial.print(lastPWM[3]); Serial.print(" ");
Serial.print(lastPWM[4]);
if (!systemActive) Serial.print(" (SYS OFF)");
Serial.println();
}
}
// ====================================================================
// WELCOME LED
// ====================================================================
void welcomeLED() {
int ledPins[] = {ledSoil, ledDHTTemp, ledDHTHum, ledMQ3, ledLM35};
for (int i = 0; i < 5; i++) {
analogWrite(ledPins[i], 255);
delay(300);
analogWrite(ledPins[i], 0);
delay(100);
}
for (int i = 0; i < 5; i++) analogWrite(ledPins[i], 255);
delay(500);
for (int i = 0; i < 5; i++) analogWrite(ledPins[i], 0);
delay(200);
}
// ====================================================================
// SETUP
// ====================================================================
void setup() {
Serial.begin(9600);
dht.begin();
lcd.init();
lcd.backlight();
lcd.clear();
lcd.print("Starting...");
delay(1000);
pinMode(ledSoil, OUTPUT);
pinMode(ledDHTTemp, OUTPUT);
pinMode(ledDHTHum, OUTPUT);
pinMode(ledMQ3, OUTPUT);
pinMode(ledLM35, OUTPUT);
analogWrite(ledSoil, 0);
analogWrite(ledDHTTemp, 0);
analogWrite(ledDHTHum, 0);
analogWrite(ledMQ3, 0);
analogWrite(ledLM35, 0);
welcomeLED();
initWaveParams();
Serial.println("========================================");
Serial.println(" LED dengan variasi kilat & breathing");
Serial.println(" Grup lambat: jeda 800,900,1000,1200,500");
Serial.println(" DHT: kilat 500 & speed/repeat");
Serial.println(" Reduksi global 20%, tambahan jika bersama");
Serial.println(" Master Switch: Soil > 550 → LED mati");
Serial.println(" LCD: baris pertama status LED (%%)");
Serial.println("========================================");
lastReadTime = millis();
lastPageChange = millis();
readAllSensors();
}
// ====================================================================
// LOOP
// ====================================================================
void loop() {
unsigned long now = millis();
// Baca sensor setiap 2 detik
if (now - lastReadTime >= READ_INTERVAL) {
lastReadTime = now;
readAllSensors();
}
// Ganti halaman LCD setiap 2 detik
if (now - lastPageChange >= PAGE_CHANGE_INTERVAL) {
lastPageChange = now;
lcdPage++;
if (lcdPage >= LCD_PAGES) lcdPage = 0;
// updateLCD() akan dipanggil oleh timer berikutnya
}
// Update LED setiap 50 ms
if (now - lastUpdateTime >= UPDATE_INTERVAL) {
lastUpdateTime = now;
updateAllLEDs();
}
// Update LCD setiap 200 ms (agar sinkron dengan perubahan LED)
if (now - lastLCDUpdate >= LCD_UPDATE_INTERVAL) {
lastLCDUpdate = now;
updateLCD();
}
}