286 lines
7.6 KiB
C++
286 lines
7.6 KiB
C++
#include <WiFi.h>
|
|
#include <Firebase_ESP_Client.h>
|
|
#include <ModbusMaster.h>
|
|
#include <HardwareSerial.h>
|
|
#include <Adafruit_GFX.h>
|
|
#include <Adafruit_SSD1306.h>
|
|
#include "time.h"
|
|
|
|
// ========== OLED ==========
|
|
#define SCREEN_WIDTH 128
|
|
#define SCREEN_HEIGHT 64
|
|
#define OLED_RESET -1
|
|
#define SCREEN_ADDRESS 0x3C
|
|
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
|
|
|
|
// ========== WIFI ==========
|
|
#define WIFI_SSID "AGIL"
|
|
#define WIFI_PASSWORD "agil2626"
|
|
|
|
// ========== FIREBASE ==========
|
|
#define API_KEY "AIzaSyCefzDqzoTLblHtOsxMiDw3BB1QAox0VIQ"
|
|
#define DATABASE_URL "https://nutriplant-579c9-default-rtdb.firebaseio.com/"
|
|
#define LEGACY_TOKEN "yEFKFhgPmBoLWrdkRSf2pgiYY3ebS12uWDoikwkl"
|
|
|
|
// ========== LED & BUTTON ==========
|
|
#define LED_PIN 15
|
|
#define BUTTON_PIN 14
|
|
bool lastButtonState = HIGH;
|
|
unsigned long lastDebounceTime = 0;
|
|
const unsigned long debounceDelay = 50;
|
|
bool manualMode = false;
|
|
|
|
// ========== MODBUS RS485 ==========
|
|
#define RXD2 16
|
|
#define TXD2 17
|
|
#define RE_PIN 19
|
|
#define DE_PIN 18
|
|
HardwareSerial MySerial(2);
|
|
ModbusMaster node;
|
|
|
|
// ========== SOIL SENSOR ==========
|
|
#define SOIL_PIN 34
|
|
|
|
// Firebase objects
|
|
FirebaseData fbdo;
|
|
FirebaseAuth auth;
|
|
FirebaseConfig config;
|
|
|
|
// Variables
|
|
uint16_t nitrogen = 0, phosphorus = 0, potassium = 0;
|
|
float soilPercent = 0;
|
|
int currentPage = 0;
|
|
const int totalPages = 4;
|
|
unsigned long lastSensorDisplayTime = 0;
|
|
|
|
// Fungsi Modbus TX control
|
|
void preTransmission() {
|
|
digitalWrite(DE_PIN, HIGH);
|
|
digitalWrite(RE_PIN, HIGH);
|
|
}
|
|
void postTransmission() {
|
|
digitalWrite(DE_PIN, LOW);
|
|
digitalWrite(RE_PIN, LOW);
|
|
}
|
|
|
|
// Fungsi teks rata tengah
|
|
void centerText(const String &text, int y, int textSize = 2) {
|
|
display.setTextSize(textSize);
|
|
int16_t x1, y1;
|
|
uint16_t w, h;
|
|
display.getTextBounds(text, 0, y, &x1, &y1, &w, &h);
|
|
int x = (SCREEN_WIDTH - w) / 2;
|
|
display.setCursor(x, y);
|
|
display.print(text);
|
|
}
|
|
|
|
// Fungsi menampilkan ikon WiFi
|
|
void drawWiFiIcon() {
|
|
display.drawPixel(118, 0, WHITE);
|
|
display.drawLine(117, 1, 119, 1, WHITE);
|
|
display.drawLine(116, 2, 120, 2, WHITE);
|
|
display.drawLine(115, 3, 121, 3, WHITE);
|
|
}
|
|
|
|
// Fungsi waktu update ke string
|
|
String getFormattedTime() {
|
|
time_t now;
|
|
struct tm timeinfo;
|
|
if (!getLocalTime(&timeinfo)) {
|
|
return "unknown";
|
|
}
|
|
char buffer[30];
|
|
strftime(buffer, sizeof(buffer), "%d/%m/%Y %H:%M:%S", &timeinfo);
|
|
return String(buffer);
|
|
}
|
|
|
|
void setup() {
|
|
Serial.begin(115200);
|
|
|
|
// OLED
|
|
if (!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
|
|
Serial.println(F("OLED failed"));
|
|
while (true);
|
|
}
|
|
display.clearDisplay();
|
|
display.setTextColor(WHITE);
|
|
centerText("Nutriplant", 20, 2);
|
|
display.display();
|
|
delay(1500);
|
|
display.clearDisplay();
|
|
|
|
// Pin setup
|
|
pinMode(LED_PIN, OUTPUT);
|
|
pinMode(BUTTON_PIN, INPUT_PULLUP);
|
|
pinMode(RE_PIN, OUTPUT);
|
|
pinMode(DE_PIN, OUTPUT);
|
|
digitalWrite(RE_PIN, LOW);
|
|
digitalWrite(DE_PIN, LOW);
|
|
|
|
// WiFi
|
|
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
|
|
Serial.print("Connecting to WiFi");
|
|
unsigned long start = millis();
|
|
while (WiFi.status() != WL_CONNECTED && millis() - start < 10000) {
|
|
Serial.print(".");
|
|
digitalWrite(LED_PIN, !digitalRead(LED_PIN));
|
|
delay(500);
|
|
}
|
|
digitalWrite(LED_PIN, LOW);
|
|
if (WiFi.status() == WL_CONNECTED) {
|
|
Serial.println("\nWiFi connected");
|
|
manualMode = false;
|
|
configTime(25200, 0, "pool.ntp.org"); // GMT+7 (WIB)
|
|
} else {
|
|
Serial.println("\nWiFi not connected - Manual Mode");
|
|
manualMode = true;
|
|
}
|
|
|
|
// Firebase
|
|
config.api_key = API_KEY;
|
|
config.database_url = DATABASE_URL;
|
|
config.signer.tokens.legacy_token = LEGACY_TOKEN;
|
|
Firebase.begin(&config, &auth);
|
|
Firebase.reconnectWiFi(true);
|
|
|
|
// RS485 setup
|
|
MySerial.begin(4800, SERIAL_8N1, RXD2, TXD2);
|
|
node.begin(1, MySerial);
|
|
node.preTransmission(preTransmission);
|
|
node.postTransmission(postTransmission);
|
|
|
|
delay(1000);
|
|
}
|
|
|
|
void loop() {
|
|
unsigned long currentMillis = millis();
|
|
static unsigned long wifiDisconnectedSince = 0;
|
|
|
|
// === Read NPK ===
|
|
uint8_t result = node.readHoldingRegisters(0x0000, 3);
|
|
if (result == node.ku8MBSuccess) {
|
|
nitrogen = node.getResponseBuffer(0);
|
|
phosphorus = node.getResponseBuffer(1);
|
|
potassium = node.getResponseBuffer(2);
|
|
|
|
if (!manualMode) {
|
|
Firebase.RTDB.setInt(&fbdo, "sensor_data/nitrogen", nitrogen);
|
|
Firebase.RTDB.setInt(&fbdo, "sensor_data/phosphorus", phosphorus);
|
|
Firebase.RTDB.setInt(&fbdo, "sensor_data/potassium", potassium);
|
|
Firebase.RTDB.setString(&fbdo, "sensor_data/error_npk", "");
|
|
|
|
// Set waktu update
|
|
String timeNow = getFormattedTime();
|
|
Firebase.RTDB.setString(&fbdo, "sensor_data/update", timeNow);
|
|
}
|
|
} else {
|
|
if (!manualMode) {
|
|
Firebase.RTDB.setString(&fbdo, "sensor_data/error_npk", "Modbus Error: " + String(result));
|
|
}
|
|
}
|
|
|
|
// === Read Soil Moisture ===
|
|
int soilAnalog = analogRead(SOIL_PIN);
|
|
soilPercent = map(soilAnalog, 4095, 1500, 0, 100);
|
|
soilPercent = constrain(soilPercent, 0, 100);
|
|
if (!manualMode) {
|
|
Firebase.RTDB.setFloat(&fbdo, "sensor_data/soil_moisture", soilPercent);
|
|
|
|
// Update waktu juga
|
|
String timeNow = getFormattedTime();
|
|
Firebase.RTDB.setString(&fbdo, "sensor_data/update", timeNow);
|
|
}
|
|
|
|
// === Tombol untuk reconnect WiFi ===
|
|
bool reading = digitalRead(BUTTON_PIN);
|
|
if (reading != lastButtonState) {
|
|
lastDebounceTime = currentMillis;
|
|
}
|
|
|
|
if ((currentMillis - lastDebounceTime) > debounceDelay) {
|
|
if (lastButtonState == HIGH && reading == LOW) {
|
|
Serial.println("Reconnect button pressed");
|
|
|
|
display.clearDisplay();
|
|
centerText("Connecting WiFi", 10);
|
|
display.display();
|
|
|
|
WiFi.disconnect();
|
|
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
|
|
unsigned long reconnectStart = millis();
|
|
bool reconnected = false;
|
|
|
|
while (millis() - reconnectStart < 10000) {
|
|
if (WiFi.status() == WL_CONNECTED) {
|
|
reconnected = true;
|
|
break;
|
|
}
|
|
delay(500);
|
|
}
|
|
|
|
manualMode = !reconnected;
|
|
|
|
display.clearDisplay();
|
|
if (reconnected) {
|
|
centerText("WiFi Connected", 10);
|
|
Serial.println("Reconnected to WiFi, online mode.");
|
|
} else {
|
|
centerText("Reconnect Failed", 10);
|
|
Serial.println("Reconnect failed, still in manual mode.");
|
|
}
|
|
display.display();
|
|
delay(1500);
|
|
}
|
|
}
|
|
lastButtonState = reading;
|
|
|
|
// === Auto-switch ke Manual jika WiFi putus ===
|
|
if (WiFi.status() != WL_CONNECTED) {
|
|
if (wifiDisconnectedSince == 0) {
|
|
wifiDisconnectedSince = currentMillis;
|
|
} else if (!manualMode && currentMillis - wifiDisconnectedSince >= 10000) {
|
|
manualMode = true;
|
|
display.clearDisplay();
|
|
centerText("No WiFi - Manual", 10);
|
|
display.display();
|
|
Serial.println("Auto-switched to Manual Mode");
|
|
}
|
|
} else {
|
|
wifiDisconnectedSince = 0;
|
|
}
|
|
|
|
// === Tampilkan Sensor Setiap 5 Detik ===
|
|
if (currentMillis - lastSensorDisplayTime >= 5000) {
|
|
lastSensorDisplayTime = currentMillis;
|
|
currentPage = (currentPage + 1) % totalPages;
|
|
|
|
display.clearDisplay();
|
|
switch (currentPage) {
|
|
case 0:
|
|
centerText("N (mg/kg)", 5, 2);
|
|
centerText(String(nitrogen), 30, 3);
|
|
break;
|
|
case 1:
|
|
centerText("P (mg/kg)", 5, 2);
|
|
centerText(String(phosphorus), 30, 3);
|
|
break;
|
|
case 2:
|
|
centerText("K (mg/kg)", 5, 2);
|
|
centerText(String(potassium), 30, 3);
|
|
break;
|
|
case 3:
|
|
centerText("Soil Moisture", 5, 1);
|
|
centerText(String(soilPercent, 1) + "%", 30, 3);
|
|
break;
|
|
}
|
|
|
|
if (!manualMode) {
|
|
drawWiFiIcon();
|
|
}
|
|
|
|
display.display();
|
|
}
|
|
|
|
digitalWrite(LED_PIN, manualMode ? LOW : HIGH);
|
|
delay(100);
|
|
} |