#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <WiFi.h>
#include <WebServer.h>
#include <Preferences.h>
#include <HTTPClient.h>
// --- Konfigurasi OLED ---
#define SCREEN_WIDTH 128 // Lebar layar OLED
#define SCREEN_HEIGHT 64 // Tinggi layar OLED
// Inisialisasi objek display OLED
// Alamat I2C default untuk SSD1306 adalah 0x3C
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);
// --- Konfigurasi Pembacaan Tegangan ---
#define NUM_SAMPLES 500 // Jumlah sampel untuk rata-rata dan RMS
#define VREF 3.3 // Tegangan referensi ADC ESP32
#define ADC_RES 4095.0 // Resolusi ADC (12-bit)
#define NOISE_THRESHOLD_VOLTAGE 50.0 // Ambang batas tegangan (misal, di bawah 50V dianggap mati)
#define VOLTAGE_CALIBRATION 312.0 // Faktor kalibrasi untuk mengubah nilai ADC menjadi tegangan AC
// Sesuaikan ini dengan modul sensor tegangan AC Anda
// Pin GPIO untuk sensor tegangan
const int pinUPS1 = 32;
const int pinUPS2 = 33;
const int pinPLN = 34;
const int pinGENSET = 35;
// Variabel untuk menyimpan nilai tegangan yang dibaca
float vUPS1 = 0, vUPS2 = 0, vPLN = 0, vGENSET = 0;
// --- Objek & Variabel Global ---
Preferences preferences; // Untuk menyimpan konfigurasi di NVS (Non-Volatile Storage)
WebServer server; // Objek Web Server
HTTPClient http; // Objek HTTPClient, diinisialisasi sekali
// --- Variabel Konfigurasi (akan dimuat dari NVS) ---
String ssid = ""; // SSID WiFi
String password = ""; // Password WiFi
String ipStr = ""; // IP statis (opsional)
String gwStr = ""; // Gateway statis (opsional)
String subnetStr = ""; // Subnet statis (opsional)
int serverPort = 80; // Port web server
String targetIP = "192.168.1.2"; // IP tujuan untuk notifikasi HTTP GET
// Pesan notifikasi default (dapat diubah melalui web UI)
String msgUPS1 = "UPS1 MATI";
String msgUPS2 = "UPS2 MATI";
String msgPLN = "PLN MATI";
String msgGENSET = "GENSET MATI";
// Flag untuk mencegah pengiriman notifikasi berulang
bool ups1Sent = false;
bool ups2Sent = false;
bool plnSent = false;
bool gensetSent = false;
// Variabel untuk interval loop non-blocking
unsigned long previousMillis = 0;
const long interval = 1000; // Cek setiap 1 detik
// --- Fungsi URL Encoding (Ditambahkan Baru) ---
// Melakukan URL encoding pada string, terutama mengganti spasi dengan %20
String urlEncode(const String& str) {
String encodedString = "";
char c;
char code0;
char code1;
for (int i = 0; i < str.length(); i++) {
c = str.charAt(i);
if (c == ' ') { // Ganti spasi dengan %20
encodedString += "%20";
} else if (isalnum(c)) { // Karakter alfanumerik tidak perlu di-encode
encodedString += c;
} else { // Encode karakter lainnya (minimalis untuk kasus ini)
code0 = (c & 0xf) + '0';
if ((c & 0xf) > 9) {
code0 = (c & 0xf) - 10 + 'A';
}
c = (c >> 4) & 0xf;
code1 = c + '0';
if (c > 9) {
code1 = c - 10 + 'A';
}
encodedString += '%';
encodedString += code1;
encodedString += code0;
}
}
return encodedString;
}
// --- Fungsi Pembacaan Tegangan AC ---
// Fungsi ini mengukur tegangan AC RMS dari pin ADC
float readVoltageAC(int pin) {
float sumOfSquares = 0;
for (int i = 0; i < NUM_SAMPLES; i++) {
int sensorValue = analogRead(pin);
sumOfSquares += pow(sensorValue, 2);
delayMicroseconds(100);
}
float avgSquare = sumOfSquares / NUM_SAMPLES;
float rmsADC = sqrt(avgSquare);
float acVoltage = rmsADC * (VREF / ADC_RES) * VOLTAGE_CALIBRATION;
return acVoltage < NOISE_THRESHOLD_VOLTAGE ? 0.0 : acVoltage;
}
// --- Fungsi Pengiriman Notifikasi ---
// Mengirim notifikasi HTTP GET ke IP target dengan pesan tertentu
void sendNotification(String message) {
if (WiFi.status() == WL_CONNECTED) {
// --- PERUBAHAN DI SINI: Panggil fungsi urlEncode yang baru kita buat ---
String encodedMessage = urlEncode(message);
String url = "http://" + targetIP + "/notif?src=" + encodedMessage;
Serial.print("Mengirim notifikasi: ");
Serial.println(url);
http.begin(url);
int httpCode = http.GET();
if (httpCode > 0) {
Serial.printf("[HTTP] GET... code: %d\n", httpCode);
if (httpCode == HTTP_CODE_OK) {
String payload = http.getString();
Serial.println(payload);
}
} else {
Serial.printf("[HTTP] GET... failed, error: %s\n", http.errorToString(httpCode).c_str());
}
http.end();
} else {
Serial.println("Tidak terhubung ke WiFi, notifikasi tidak terkirim.");
}
}
// --- Web Server Handlers ---
// Menampilkan halaman web konfigurasi
void handleRoot() {
String html = "<!DOCTYPE html><html><head><meta charset='UTF-8'>"
"<meta name='viewport' content='width=device-width, initial-scale=1.0'>"
"<title>PUTU-TECKNO Config</title>"
"<style>"
"body { font-family: Arial, sans-serif; background:#f9f9f9; margin:0; padding:20px; }"
".container { max-width: 600px; margin: auto; background: white; padding: 20px; border-radius: 10px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }"
"h1, h2 { font-size: 22px; color: #333; }"
"form label { display: block; margin-top: 10px; font-weight: bold; }"
"form input[type='text'], form input[type='password'], form input[type='number'] { width: calc(100% - 16px); padding: 8px; margin-top: 5px; border: 1px solid #ccc; border-radius: 5px; }"
"form input[type='submit'] { background-color: #4CAF50; color: white; border: none; padding: 10px 20px; border-radius: 5px; cursor: pointer; font-size: 16px; margin-top: 20px; }"
"form input[type='submit']:hover { background-color: #45a049; }"
".section { margin-top: 30px; border-top: 1px solid #eee; padding-top: 20px; }"
".nilai { font-size: 24px; font-weight: bold; color: #222; margin: 10px 0; }"
".off { color: red; font-weight: bold; }"
".status-box { background-color: #e6f7ff; border: 1px solid #91d5ff; padding: 10px; border-radius: 5px; margin-bottom: 20px; }"
".status-box p { margin: 5px 0; }"
"</style></head><body>";
html += "<div class='container'>";
html += "<h1>PUTU-TECKNO - WiFi & Notifikasi Settings</h1>";
// Tampilkan Status Koneksi WiFi
html += "<div class='status-box'>";
html += "<h2>Status Koneksi</h2>";
if (WiFi.status() == WL_CONNECTED) {
html += "<p><strong>Status:</strong> Terhubung ke WiFi</p>";
html += "<p><strong>SSID:</strong> " + WiFi.SSID() + "</p>";
html += "<p><strong>IP Lokal:</strong> " + WiFi.localIP().toString() + "</p>";
} else {
html += "<p><strong>Status:</strong> Mode Access Point Aktif</p>";
html += "<p><strong>SSID AP:</strong> PUTU@TECKNO-4AC</p>";
html += "<p><strong>IP AP:</strong> " + WiFi.softAPIP().toString() + "</p>";
html += "<p><strong>Password AP:</strong> rejeki88</p>";
}
html += "<p><strong>Port Web:</strong> " + String(serverPort) + "</p>";
html += "</div>";
html += "<form action='/save' method='post'>";
html += "<div class='section'><h2>Pengaturan WiFi</h2>";
html += "<label for='ssid'>SSID:</label><input type='text' name='ssid' value='" + ssid + "'><br>";
html += "<label for='password'>Password:</label><input type='password' name='password' value='" + password + "'><br>";
html += "</div>";
html += "<div class='section'><h2>Pengaturan Jaringan Statis (Opsional)</h2>";
html += "<label for='ip'>IP Address (kosongkan untuk DHCP):</label><input type='text' name='ip' value='" + ipStr + "'><br>";
html += "<label for='gw'>Gateway:</label><input type='text' name='gw' value='" + gwStr + "'><br>";
html += "<label for='subnet'>Subnet Mask:</label><input type='text' name='subnet' value='" + subnetStr + "'><br>";
html += "</div>";
html += "<div class='section'><h2>Pengaturan Server</h2>";
html += "<label for='port'>Port Web Server:</label><input type='number' name='port' value='" + String(serverPort) + "'><br>";
html += "<label for='target_ip'>Target IP Notifikasi (misal: 192.168.1.100):</label><input type='text' name='target_ip' value='" + targetIP + "'><br>";
html += "</div>";
html += "<div class='section'><h2>Pesan Notifikasi Kustom</h2>";
html += "<label for='msg_ups1'>Pesan UPS1 Mati:</label><input type='text' name='msg_ups1' value='" + msgUPS1 + "'><br>";
html += "<label for='msg_ups2'>Pesan UPS2 Mati:</label><input type='text' name='msg_ups2' value='" + msgUPS2 + "'><br>";
html += "<label for='msg_pln'>Pesan PLN Mati:</label><input type='text' name='msg_pln' value='" + msgPLN + "'><br>";
html += "<label for='msg_genset'>Pesan GENSET Mati:</label><input type='text' name='msg_genset' value='" + msgGENSET + "'><br>";
html += "</div>";
html += "<input type='submit' value='Simpan Pengaturan & Restart'></form>";
html += "<div class='section'><h1>Monitoring Tegangan Server</h1>";
// Perbaikan tampilan nilai tegangan
html += "<div class='nilai'>UPS1 : " + String(vUPS1 < NOISE_THRESHOLD_VOLTAGE ? "<span class='off'>OFF</span>" : (String(vUPS1, 1) + "V").c_str()) + "</div>";
html += "<div class='nilai'>UPS2 : " + String(vUPS2 < NOISE_THRESHOLD_VOLTAGE ? "<span class='off'>OFF</span>" : (String(vUPS2, 1) + "V").c_str()) + "</div>";
html += "<div class='nilai'>PLN : " + String(vPLN < NOISE_THRESHOLD_VOLTAGE ? "<span class='off'>OFF</span>" : (String(vPLN, 1) + "V").c_str()) + "</div>";
html += "<div class='nilai'>GENSET: " + String(vGENSET < NOISE_THRESHOLD_VOLTAGE ? "<span class='off'>OFF</span>" : (String(vGENSET, 1) + "V").c_str()) + "</div>";
html += "</div></div></body></html>";
server.send(200, "text/html", html);
}
// Menyimpan konfigurasi dari form
void handleSave() {
ssid = server.arg("ssid");
password = server.arg("password");
ipStr = server.arg("ip");
gwStr = server.arg("gw");
subnetStr = server.arg("subnet");
serverPort = server.arg("port").toInt();
targetIP = server.arg("target_ip");
msgUPS1 = server.arg("msg_ups1");
msgUPS2 = server.arg("msg_ups2");
msgPLN = server.arg("msg_pln");
msgGENSET = server.arg("msg_genset");
preferences.begin("wifi", false); // Buka NVS dalam mode baca/tulis
preferences.putString("ssid", ssid);
preferences.putString("password", password);
preferences.putString("ip", ipStr);
preferences.putString("gw", gwStr);
preferences.putString("subnet", subnetStr);
preferences.putInt("port", serverPort);
preferences.putString("targetIP", targetIP);
preferences.putString("msgUPS1", msgUPS1);
preferences.putString("msgUPS2", msgUPS2);
preferences.putString("msgPLN", msgPLN);
preferences.putString("msgGENSET", msgGENSET);
preferences.end(); // Tutup NVS
server.send(200, "text/html", "<html><body><h1>Settings Saved! Restarting...</h1><p>Mohon tunggu beberapa saat.</p></body></html>");
delay(2000);
ESP.restart(); // Restart ESP32 untuk menerapkan pengaturan baru
}
// Memuat pengaturan dari NVS dan menginisialisasi WiFi
void setupWiFi() {
preferences.begin("wifi", true); // Buka NVS dalam mode baca saja
ssid = preferences.getString("ssid", "");
password = preferences.getString("password", "");
ipStr = preferences.getString("ip", "");
gwStr = preferences.getString("gw", "");
subnetStr = preferences.getString("subnet", "");
serverPort = preferences.getInt("port", 80);
targetIP = preferences.getString("targetIP", "192.168.1.2"); // Default IP receiver notifikasi
msgUPS1 = preferences.getString("msgUPS1", "UPS1 MATI");
msgUPS2 = preferences.getString("msgUPS2", "UPS2 MATI");
msgPLN = preferences.getString("msgPLN", "PLN MATI");
msgGENSET = preferences.getString("msgGENSET", "GENSET MATI");
preferences.end(); // Tutup NVS
if (ssid != "") {
Serial.print("Mencoba menyambungkan ke WiFi: ");
Serial.println(ssid);
if (ipStr != "" && gwStr != "" && subnetStr != "") {
IPAddress ip, gw, sn;
if (ip.fromString(ipStr) && gw.fromString(gwStr) && sn.fromString(subnetStr)) {
WiFi.config(ip, gw, sn);
Serial.println("Menggunakan IP Statis.");
} else {
Serial.println("Konfigurasi IP statis tidak valid. Menggunakan DHCP.");
// Bersihkan string IP statis agar kembali ke DHCP
ipStr = "";
gwStr = "";
subnetStr = "";
}
} else {
Serial.println("Tidak ada konfigurasi IP statis. Menggunakan DHCP.");
}
WiFi.begin(ssid.c_str(), password.c_str());
int attempts = 0;
while (WiFi.status() != WL_CONNECTED && attempts < 20) { // Coba 10 detik
delay(500);
Serial.print(".");
attempts++;
}
Serial.println();
if (WiFi.status() == WL_CONNECTED) {
Serial.println("WiFi Tersambung!");
Serial.print("IP Address: ");
Serial.println(WiFi.localIP());
} else {
Serial.println("Gagal terhubung ke WiFi yang dikonfigurasi.");
}
} else {
Serial.println("Tidak ada SSID tersimpan. Memulai mode Access Point.");
}
// Jika tidak terhubung ke WiFi atau tidak ada SSID tersimpan, mulai AP
if (WiFi.status() != WL_CONNECTED) {
WiFi.mode(WIFI_AP);
WiFi.softAP("PUTU@TECKNO-4AC", "rejeki88"); // SSID & Pass untuk AP
Serial.print("AP Mode Aktif. IP: ");
Serial.println(WiFi.softAPIP());
}
server.on("/", HTTP_GET, handleRoot);
server.on("/save", HTTP_POST, handleSave);
server.begin(serverPort);
Serial.print("Web server dimulai pada port: ");
Serial.println(serverPort);
}
// --- Setup Awal ---
void setup() {
Serial.begin(115200); // Inisialisasi Serial Monitor
analogReadResolution(12); // Set resolusi ADC ke 12-bit
// Inisialisasi OLED
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // 0x3C adalah alamat I2C umum
Serial.println(F("OLED tidak ditemukan atau gagal inisialisasi!"));
while (true); // Berhenti jika OLED tidak terdeteksi
}
display.clearDisplay();
display.setTextColor(WHITE);
display.setTextSize(1);
display.setCursor(10, 10);
display.println("Monitoring 4 Tegangan");
display.println("PUTU-TECKNO");
display.display();
delay(2000); // Tampilkan pesan startup sebentar
setupWiFi(); // Konfigurasi WiFi dan Web Server
}
// --- Loop Utama ---
void loop() {
unsigned long currentMillis = millis();
// Membaca tegangan setiap interval (non-blocking)
if (currentMillis - previousMillis >= interval) {
previousMillis = currentMillis; // Simpan waktu terakhir
vUPS1 = readVoltageAC(pinUPS1);
vUPS2 = readVoltageAC(pinUPS2);
vPLN = readVoltageAC(pinPLN);
vGENSET = readVoltageAC(pinGENSET);
// Update tampilan OLED
display.clearDisplay();
display.setTextSize(1);
display.setCursor(0, 0);
display.printf("UPS1 : %s\n", vUPS1 < NOISE_THRESHOLD_VOLTAGE ? "OFF" : (String(vUPS1, 1) + "V").c_str());
display.setCursor(0, 16);
display.printf("UPS2 : %s\n", vUPS2 < NOISE_THRESHOLD_VOLTAGE ? "OFF" : (String(vUPS2, 1) + "V").c_str());
display.setCursor(0, 32);
display.printf("PLN : %s\n", vPLN < NOISE_THRESHOLD_VOLTAGE ? "OFF" : (String(vPLN, 1) + "V").c_str());
display.setCursor(0, 48);
display.printf("GENSET : %s\n", vGENSET < NOISE_THRESHOLD_VOLTAGE ? "OFF" : (String(vGENSET, 1) + "V").c_str());
display.display();
// Log ke Serial Monitor
Serial.printf("UPS1: %.1fV | UPS2: %.1fV | PLN: %.1fV | GENSET: %.1fV\n", vUPS1, vUPS2, vPLN, vGENSET);
// Kirim notifikasi jika terhubung ke WiFi
if (WiFi.status() == WL_CONNECTED) {
if (vUPS1 < NOISE_THRESHOLD_VOLTAGE && !ups1Sent) {
sendNotification(msgUPS1);
ups1Sent = true;
} else if (vUPS1 >= NOISE_THRESHOLD_VOLTAGE) {
ups1Sent = false;
}
if (vUPS2 < NOISE_THRESHOLD_VOLTAGE && !ups2Sent) {
sendNotification(msgUPS2);
ups2Sent = true;
} else if (vUPS2 >= NOISE_THRESHOLD_VOLTAGE) {
ups2Sent = false;
}
if (vPLN < NOISE_THRESHOLD_VOLTAGE && !plnSent) {
sendNotification(msgPLN);
plnSent = true;
} else if (vPLN >= NOISE_THRESHOLD_VOLTAGE) {
plnSent = false;
}
if (vGENSET < NOISE_THRESHOLD_VOLTAGE && !gensetSent) {
sendNotification(msgGENSET);
gensetSent = true;
} else if (vGENSET >= NOISE_THRESHOLD_VOLTAGE) {
gensetSent = false;
}
}
}
server.handleClient(); // Tangani permintaan web server (harus selalu ada di loop)
}
No comments:
Post a Comment