/*
Drifting Water Quality Monitor - Wokwi Test Version
Board: ESP32 DevKit
Fungsi simulasi:
- Baca pH, turbidity, TDS dari potensiometer analog
- Baca suhu dari DS18B20
- Baca GPS dari NEO-6M/Wokwi GPS melalui Serial2
- Simpan data CSV ke microSD
- Simulasi kirim LoRa melalui Serial Monitor
Catatan:
Modul LoRa RA-02 SX1278 tidak selalu tersedia sebagai simulasi penuh di Wokwi.
Karena itu, transmisi LoRa disimulasikan dengan Serial.println("[LoRa TX] ...").
*/
#include <Arduino.h>
#include <SPI.h>
#include <SD.h>
#include <OneWire.h>
#include <DallasTemperature.h>
#include <TinyGPS++.h>
// ======================= PIN SETUP =======================
// Sensor analog disimulasikan memakai potentiometer/slider
#define PH_PIN 34
#define TURB_PIN 35
#define TDS_PIN 32
// DS18B20
#define ONE_WIRE_BUS 4
// GPS UART2
#define GPS_RX_PIN 16 // ESP32 RX2, sambung ke TX GPS
#define GPS_TX_PIN 17 // ESP32 TX2, sambung ke RX GPS
#define GPS_BAUD 9600
// microSD SPI
#define SD_CS 5
#define SD_SCK 18
#define SD_MISO 19
#define SD_MOSI 23
// LoRa RA-02 SX1278, wiring fisik asli
// Catatan: pada simulasi ini belum dipakai karena LoRa disimulasikan via Serial.
#define LORA_SCK 18
#define LORA_MISO 19
#define LORA_MOSI 23
#define LORA_NSS 15
#define LORA_RST 14
#define LORA_DIO0 26
// ======================= OBJECTS =======================
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature ds18b20(&oneWire);
TinyGPSPlus gps;
// ======================= VARIABLES =======================
unsigned long lastSample = 0;
const unsigned long sampleIntervalMs = 1000;
uint32_t recordId = 0;
const char* logFile = "/waterlog.csv";
// ======================= HELPER FUNCTIONS =======================
float readVoltage(int pin) {
int adc = analogRead(pin); // 0 - 4095
return (adc / 4095.0) * 3.3; // simulasi 0 - 3.3 V
}
float simulatePH(float voltage) {
// Simulasi rentang pH 0 - 14 dari tegangan 0 - 3.3 V.
// Untuk alat asli, harus diganti persamaan kalibrasi sensor pH.
return (voltage / 3.3) * 14.0;
}
float simulateTurbidity(float voltage) {
// Simulasi NTU: makin tinggi tegangan, makin tinggi kekeruhan.
// Untuk alat asli, perlu kalibrasi terhadap standar NTU.
return (voltage / 3.3) * 1000.0;
}
float simulateTDS(float voltage) {
// Simulasi ppm.
// Untuk alat asli, pakai formula sensor TDS dan kompensasi suhu.
return (voltage / 3.3) * 1000.0;
}
float readTemperatureC() {
ds18b20.requestTemperatures();
float t = ds18b20.getTempCByIndex(0);
if (t == DEVICE_DISCONNECTED_C) return NAN;
return t;
}
String gpsTimestamp() {
if (gps.date.isValid() && gps.time.isValid()) {
char buf[25];
snprintf(
buf, sizeof(buf),
"%04d-%02d-%02d %02d:%02d:%02d",
gps.date.year(), gps.date.month(), gps.date.day(),
gps.time.hour(), gps.time.minute(), gps.time.second()
);
return String(buf);
}
// fallback jika GPS belum fix
return String(millis() / 1000);
}
void feedGPS() {
while (Serial2.available() > 0) {
gps.encode(Serial2.read());
}
}
void initSD() {
SPI.begin(SD_SCK, SD_MISO, SD_MOSI, SD_CS);
if (!SD.begin(SD_CS)) {
Serial.println("ERROR: microSD gagal diinisialisasi.");
return;
}
if (!SD.exists(logFile)) {
File f = SD.open(logFile, FILE_WRITE);
if (f) {
f.println("id,timestamp,lat,lon,sat,ph,temp_c,turbidity_ntu,tds_ppm,battery_percent,upload_status");
f.close();
}
}
Serial.println("microSD siap.");
}
bool appendLog(const String& line) {
File f = SD.open(logFile, FILE_APPEND);
if (!f) {
Serial.println("ERROR: gagal membuka file log.");
return false;
}
f.println(line);
f.close();
return true;
}
void simulateLoRaSend(const String& payload) {
// Ganti fungsi ini dengan LoRa.beginPacket(), LoRa.print(), LoRa.endPacket()
// ketika memakai modul RA-02 asli.
Serial.print("[LoRa TX] ");
Serial.println(payload);
}
void setup() {
Serial.begin(115200);
delay(1000);
Serial.println();
Serial.println("=== Drifting Water Quality Monitor - Wokwi Test ===");
analogReadResolution(12);
ds18b20.begin();
Serial2.begin(GPS_BAUD, SERIAL_8N1, GPS_RX_PIN, GPS_TX_PIN);
initSD();
Serial.println("Sistem mulai. Data dibaca setiap 1 detik.");
}
void loop() {
feedGPS();
if (millis() - lastSample >= sampleIntervalMs) {
lastSample = millis();
recordId++;
float phV = readVoltage(PH_PIN);
float turbV = readVoltage(TURB_PIN);
float tdsV = readVoltage(TDS_PIN);
float ph = simulatePH(phV);
float turbidity = simulateTurbidity(turbV);
float tds = simulateTDS(tdsV);
float tempC = readTemperatureC();
double lat = gps.location.isValid() ? gps.location.lat() : 0.0;
double lon = gps.location.isValid() ? gps.location.lng() : 0.0;
int sat = gps.satellites.isValid() ? gps.satellites.value() : 0;
// Simulasi baterai tetap 85%.
int battery = 85;
String timestamp = gpsTimestamp();
char csv[240];
snprintf(
csv, sizeof(csv),
"%lu,%s,%.6f,%.6f,%d,%.2f,%.2f,%.1f,%.1f,%d,pending",
(unsigned long)recordId,
timestamp.c_str(),
lat,
lon,
sat,
ph,
tempC,
turbidity,
tds,
battery
);
String line = String(csv);
appendLog(line);
// Payload LoRa dibuat pendek.
char payload[180];
snprintf(
payload, sizeof(payload),
"%lu,%s,%.6f,%.6f,%.2f,%.1f,%.0f,%.0f,%d",
(unsigned long)recordId,
timestamp.c_str(),
lat,
lon,
ph,
tempC,
turbidity,
tds,
battery
);
simulateLoRaSend(String(payload));
}
}