Upload project TA
This commit is contained in:
commit
5671c5302b
|
|
@ -0,0 +1,5 @@
|
|||
.pio
|
||||
.vscode/.browse.c_cpp.db*
|
||||
.vscode/c_cpp_properties.json
|
||||
.vscode/launch.json
|
||||
.vscode/ipch
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
// See http://go.microsoft.com/fwlink/?LinkId=827846
|
||||
// for the documentation about the extensions.json format
|
||||
"recommendations": [
|
||||
"platformio.platformio-ide"
|
||||
],
|
||||
"unwantedRecommendations": [
|
||||
"ms-vscode.cpptools-extension-pack"
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
|
||||
This directory is intended for project header files.
|
||||
|
||||
A header file is a file containing C declarations and macro definitions
|
||||
to be shared between several project source files. You request the use of a
|
||||
header file in your project source file (C, C++, etc) located in `src` folder
|
||||
by including it, with the C preprocessing directive `#include'.
|
||||
|
||||
```src/main.c
|
||||
|
||||
#include "header.h"
|
||||
|
||||
int main (void)
|
||||
{
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
Including a header file produces the same results as copying the header file
|
||||
into each source file that needs it. Such copying would be time-consuming
|
||||
and error-prone. With a header file, the related declarations appear
|
||||
in only one place. If they need to be changed, they can be changed in one
|
||||
place, and programs that include the header file will automatically use the
|
||||
new version when next recompiled. The header file eliminates the labor of
|
||||
finding and changing all the copies as well as the risk that a failure to
|
||||
find one copy will result in inconsistencies within a program.
|
||||
|
||||
In C, the convention is to give header files names that end with `.h'.
|
||||
|
||||
Read more about using header files in official GCC documentation:
|
||||
|
||||
* Include Syntax
|
||||
* Include Operation
|
||||
* Once-Only Headers
|
||||
* Computed Includes
|
||||
|
||||
https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
|
||||
This directory is intended for project specific (private) libraries.
|
||||
PlatformIO will compile them to static libraries and link into the executable file.
|
||||
|
||||
The source code of each library should be placed in a separate directory
|
||||
("lib/your_library_name/[Code]").
|
||||
|
||||
For example, see the structure of the following example libraries `Foo` and `Bar`:
|
||||
|
||||
|--lib
|
||||
| |
|
||||
| |--Bar
|
||||
| | |--docs
|
||||
| | |--examples
|
||||
| | |--src
|
||||
| | |- Bar.c
|
||||
| | |- Bar.h
|
||||
| | |- library.json (optional. for custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html
|
||||
| |
|
||||
| |--Foo
|
||||
| | |- Foo.c
|
||||
| | |- Foo.h
|
||||
| |
|
||||
| |- README --> THIS FILE
|
||||
|
|
||||
|- platformio.ini
|
||||
|--src
|
||||
|- main.c
|
||||
|
||||
Example contents of `src/main.c` using Foo and Bar:
|
||||
```
|
||||
#include <Foo.h>
|
||||
#include <Bar.h>
|
||||
|
||||
int main (void)
|
||||
{
|
||||
...
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
The PlatformIO Library Dependency Finder will find automatically dependent
|
||||
libraries by scanning project source files.
|
||||
|
||||
More information about PlatformIO Library Dependency Finder
|
||||
- https://docs.platformio.org/page/librarymanager/ldf.html
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
; PlatformIO Project Configuration File
|
||||
;
|
||||
; Build options: build flags, source filter
|
||||
; Upload options: custom upload port, speed and extra flags
|
||||
; Library options: dependencies, extra library storages
|
||||
; Advanced options: extra scripting
|
||||
;
|
||||
; Please visit documentation for the other options and examples
|
||||
; https://docs.platformio.org/page/projectconf.html
|
||||
|
||||
[env:esp32dev]
|
||||
platform = espressif32
|
||||
board = esp32dev
|
||||
framework = arduino
|
||||
monitor_speed = 115200
|
||||
|
||||
lib_deps =
|
||||
# WiFi Manager (Versi resmi untuk ESP32)
|
||||
tzapu/WiFiManager @ ^2.0.17
|
||||
|
||||
# Web Server & TCP (Versi Async yang stabil)
|
||||
ottowinter/ESPAsyncWebServer-esphome @ ^3.1.0
|
||||
esphome/AsyncTCP-esphome @ ^2.1.3
|
||||
|
||||
# Firebase
|
||||
mobizt/Firebase Arduino Client Library for ESP8266 and ESP32 @ ^4.4.14
|
||||
|
||||
# Sensor-sensor
|
||||
adafruit/DHT sensor library @ ^1.4.6
|
||||
adafruit/Adafruit Unified Sensor @ ^1.1.14
|
||||
claws/BH1750 @ ^1.3.0
|
||||
|
||||
# Pendukung Data
|
||||
bblanchon/ArduinoJson @ ^6.21.3
|
||||
|
||||
|
|
@ -0,0 +1,120 @@
|
|||
#define DASHBOARD_H
|
||||
#define DASHBOARD_H
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
String getHTML(
|
||||
float hum,
|
||||
float temp,
|
||||
float lux,
|
||||
float soil[5],
|
||||
float flow[5],
|
||||
bool solenoid[5],
|
||||
bool pump[3],
|
||||
String ip,
|
||||
String ssid
|
||||
) {
|
||||
|
||||
String html = "";
|
||||
html.reserve(7000);
|
||||
|
||||
html += "<!DOCTYPE html><html><head>";
|
||||
html += "<meta charset='UTF-8'>";
|
||||
html += "<meta name='viewport' content='width=device-width, initial-scale=1'>";
|
||||
|
||||
html += "<style>";
|
||||
html += "body{font-family:Arial;background:#f4f7f6;padding:20px;text-align:center;margin:0;}";
|
||||
html += ".container{max-width:1000px;margin:auto;background:white;padding:20px;border-radius:15px;box-shadow:0 4px 10px rgba(0,0,0,0.1);}";
|
||||
html += ".grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:10px;margin-bottom:20px;}";
|
||||
html += ".card{background:#ecf0f1;padding:12px;border-radius:8px;border-left:5px solid #3498db;}";
|
||||
html += ".soil{border-left-color:#8b4513;}";
|
||||
html += ".flow{border-left-color:#27ae60;}";
|
||||
html += ".relay{border-left-color:#e67e22;}";
|
||||
html += ".val{font-weight:bold;font-size:18px;color:#2c3e50;}";
|
||||
html += ".on{color:green;}";
|
||||
html += ".off{color:red;}";
|
||||
html += "h3{border-bottom:2px solid #eee;padding-bottom:5px;}";
|
||||
html += "</style>";
|
||||
|
||||
html += "<script>setInterval(()=>location.reload(),5000);</script>";
|
||||
html += "</head><body>";
|
||||
|
||||
html += "<div class='container'>";
|
||||
html += "<h2>🌿 Smart Garden Dashboard</h2>";
|
||||
|
||||
html += "<h3>🌡️ DHT Sensor</h3><div class='grid'>";
|
||||
|
||||
html += "<div class='card'>Suhu<br><span class='val'>";
|
||||
html += String(temp, 1);
|
||||
html += " °C</span></div>";
|
||||
|
||||
html += "<div class='card'>Kelembaban<br><span class='val'>";
|
||||
html += String(hum, 1);
|
||||
html += " %</span></div>";
|
||||
|
||||
html += "</div>";
|
||||
|
||||
html += "<h3>💡 Cahaya</h3><div class='grid'>";
|
||||
html += "<div class='card'>Lux<br><span class='val'>";
|
||||
html += String(lux, 0);
|
||||
html += " lx</span></div>";
|
||||
html += "</div>";
|
||||
|
||||
html += "<h3>🌱 Soil Moisture</h3><div class='grid'>";
|
||||
|
||||
for (int i = 0; i < 5; i++) {
|
||||
html += "<div class='card soil'>Soil ";
|
||||
html += String(i + 1);
|
||||
html += "<br><span class='val'>";
|
||||
html += String(soil[i], 1);
|
||||
html += "%</span></div>";
|
||||
}
|
||||
|
||||
html += "</div>";
|
||||
|
||||
html += "<h3>💧 Water Flow</h3><div class='grid'>";
|
||||
|
||||
for (int i = 0; i < 5; i++) {
|
||||
html += "<div class='card flow'>Flow ";
|
||||
html += String(i + 1);
|
||||
html += "<br><span class='val'>";
|
||||
html += String(flow[i], 2);
|
||||
html += " L/min</span></div>";
|
||||
}
|
||||
|
||||
html += "</div>";
|
||||
|
||||
html += "<h3>🚰 Solenoid Valve</h3><div class='grid'>";
|
||||
|
||||
for (int i = 0; i < 5; i++) {
|
||||
html += "<div class='card relay'>Valve ";
|
||||
html += String(i + 1);
|
||||
html += "<br><span class='val ";
|
||||
html += solenoid[i] ? "on'>ON" : "off'>OFF";
|
||||
html += "</span></div>";
|
||||
}
|
||||
|
||||
html += "</div>";
|
||||
|
||||
html += "<h3>🔌 Pompa</h3><div class='grid'>";
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
html += "<div class='card relay'>Pompa ";
|
||||
html += String(i + 1);
|
||||
html += "<br><span class='val ";
|
||||
html += pump[i] ? "on'>ON" : "off'>OFF";
|
||||
html += "</span></div>";
|
||||
}
|
||||
|
||||
html += "</div>";
|
||||
|
||||
html += "<p style='font-size:12px;color:#7f8c8d;'>WiFi: ";
|
||||
html += ssid;
|
||||
html += " | IP: ";
|
||||
html += ip;
|
||||
html += "</p>";
|
||||
|
||||
html += "</div></body></html>";
|
||||
|
||||
return html;
|
||||
}
|
||||
|
|
@ -0,0 +1,698 @@
|
|||
#include <Arduino.h>
|
||||
#include <WiFi.h>
|
||||
#include <WiFiManager.h>
|
||||
#include <Wire.h>
|
||||
#include <BH1750.h>
|
||||
#include <DHT.h>
|
||||
#include <Firebase_ESP_Client.h>
|
||||
#include "dashboard.h"
|
||||
#include <AsyncTCP.h>
|
||||
#include <ESPAsyncWebServer.h>
|
||||
|
||||
AsyncWebServer server(80);
|
||||
|
||||
#define DATABASE_URL "https://project-ta-951b4-default-rtdb.firebaseio.com/"
|
||||
#define LEGACY_TOKEN "CIsRCa88G6eiBbH0pd1J7MvFttnpjqUsZwPg4LXl"
|
||||
|
||||
// =====================================================
|
||||
#define DHT_PIN 4
|
||||
#define DHT_TYPE DHT11
|
||||
#define FLOW_PIN 16
|
||||
|
||||
const uint8_t soilPins[5] = {32, 33, 34, 35, 39};
|
||||
const uint8_t mosvetPins[8] = {27, 26, 13, 14, 17, 18, 19, 2};
|
||||
|
||||
int airValue[5] = {3260, 3260, 2600, 2600, 2600};
|
||||
int waterValue[5] = {1080, 1080, 870, 870, 870};
|
||||
|
||||
int soilLow = 30;
|
||||
int soilHigh = 70;
|
||||
|
||||
float faktorKalibrasiLux = 1.10;
|
||||
|
||||
const float LUX_MIN = 0.0;
|
||||
const float LUX_MAX = 1000.0;
|
||||
|
||||
#define LUX_SMOOTHING 10
|
||||
float luxBuffer[LUX_SMOOTHING];
|
||||
int luxIndex = 0;
|
||||
|
||||
FirebaseData fbdo;
|
||||
FirebaseConfig config;
|
||||
FirebaseAuth auth;
|
||||
|
||||
BH1750 lightMeter;
|
||||
DHT dht(DHT_PIN, DHT_TYPE);
|
||||
|
||||
int soilRaw[5] = {0, 0, 0, 0, 0};
|
||||
int soilPct[5] = {0, 0, 0, 0, 0};
|
||||
|
||||
float temp = 0;
|
||||
float hum = 0;
|
||||
float lux = 0;
|
||||
float luxPercent = 0;
|
||||
|
||||
volatile unsigned long flowPulse = 0;
|
||||
float flowRate = 0;
|
||||
|
||||
// =====================================================
|
||||
bool zoneState[5] = {false, false, false, false, false};
|
||||
bool mosvetState[8] = {false, false, false, false, false, false, false, false};
|
||||
|
||||
// =====================================================
|
||||
bool firebaseHealthy = false;
|
||||
bool lastWifiConnected = false;
|
||||
|
||||
int firebaseFailCount = 0;
|
||||
|
||||
const int FIREBASE_FAIL_LIMIT = 3;
|
||||
const unsigned long FIREBASE_RETRY_INTERVAL = 15000;
|
||||
const unsigned long FIREBASE_REINIT_INTERVAL = 10000;
|
||||
|
||||
unsigned long lastFirebaseRetry = 0;
|
||||
unsigned long lastFirebaseReinit = 0;
|
||||
|
||||
|
||||
unsigned long lastSensor = 0;
|
||||
unsigned long lastFlowCalc = 0;
|
||||
unsigned long lastFirebaseGet = 0;
|
||||
unsigned long lastFirebaseSend = 0;
|
||||
unsigned long lastPrint = 0;
|
||||
unsigned long lastWifiCheck = 0;
|
||||
|
||||
// =====================================================
|
||||
// RESET WIFI
|
||||
// =====================================================
|
||||
bool resetWifiFlag = false;
|
||||
|
||||
// =====================================================
|
||||
// ISR FLOW
|
||||
// =====================================================
|
||||
void IRAM_ATTR flowISR() {
|
||||
flowPulse++;
|
||||
}
|
||||
|
||||
// =====================================================
|
||||
// ADC AVERAGE
|
||||
// =====================================================
|
||||
int readAverage(int pin) {
|
||||
long total = 0;
|
||||
|
||||
for (int i = 0; i < 10; i++) {
|
||||
total += analogRead(pin);
|
||||
delayMicroseconds(200);
|
||||
}
|
||||
|
||||
return total / 10;
|
||||
}
|
||||
|
||||
// =====================================================
|
||||
// SOIL CONVERSION
|
||||
// =====================================================
|
||||
int soilToPercent(int raw, int i) {
|
||||
int pct = map(raw, airValue[i], waterValue[i], 0, 100);
|
||||
return constrain(pct, 0, 100);
|
||||
}
|
||||
|
||||
// =====================================================
|
||||
// MOSVET CONTROL
|
||||
// =====================================================
|
||||
void setMosvet(int i, bool state) {
|
||||
if (i < 0 || i >= 8) return;
|
||||
|
||||
digitalWrite(mosvetPins[i], state ? HIGH : LOW);
|
||||
mosvetState[i] = state;
|
||||
}
|
||||
|
||||
void reinitFirebase() {
|
||||
Serial.println("Re-init Firebase...");
|
||||
|
||||
Firebase.begin(&config, &auth);
|
||||
Firebase.reconnectWiFi(true);
|
||||
|
||||
firebaseHealthy = false;
|
||||
firebaseFailCount = FIREBASE_FAIL_LIMIT;
|
||||
lastFirebaseRetry = 0;
|
||||
lastFirebaseGet = 0;
|
||||
lastFirebaseSend = 0;
|
||||
lastFirebaseReinit = millis();
|
||||
}
|
||||
|
||||
void readSensors() {
|
||||
|
||||
for (int i = 0; i < 5; i++) {
|
||||
soilRaw[i] = readAverage(soilPins[i]);
|
||||
soilPct[i] = soilToPercent(soilRaw[i], i);
|
||||
}
|
||||
|
||||
temp = dht.readTemperature();
|
||||
hum = dht.readHumidity();
|
||||
|
||||
float luxRaw = lightMeter.readLightLevel();
|
||||
|
||||
if (isnan(temp)) temp = 0;
|
||||
if (isnan(hum)) hum = 0;
|
||||
if (isnan(luxRaw) || luxRaw < 0) luxRaw = 0;
|
||||
|
||||
float luxKal = luxRaw * faktorKalibrasiLux;
|
||||
|
||||
luxBuffer[luxIndex] = luxKal;
|
||||
luxIndex = (luxIndex + 1) % LUX_SMOOTHING;
|
||||
|
||||
lux = 0;
|
||||
for (int i = 0; i < LUX_SMOOTHING; i++) {
|
||||
lux += luxBuffer[i];
|
||||
}
|
||||
lux /= LUX_SMOOTHING;
|
||||
|
||||
luxPercent = ((lux - LUX_MIN) / (LUX_MAX - LUX_MIN)) * 100.0;
|
||||
luxPercent = constrain(luxPercent, 0.0, 100.0);
|
||||
}
|
||||
|
||||
|
||||
void calculateFlow() {
|
||||
noInterrupts();
|
||||
unsigned long pulse = flowPulse;
|
||||
flowPulse = 0;
|
||||
interrupts();
|
||||
|
||||
flowRate = pulse / 7.5;
|
||||
}
|
||||
|
||||
// =====================================================
|
||||
// FIREBASE STATUS
|
||||
// =====================================================
|
||||
void markFirebaseSuccess() {
|
||||
firebaseFailCount = 0;
|
||||
firebaseHealthy = true;
|
||||
}
|
||||
|
||||
void markFirebaseFail(String reason) {
|
||||
firebaseFailCount++;
|
||||
|
||||
Serial.print("Firebase fail ");
|
||||
Serial.print(firebaseFailCount);
|
||||
Serial.print("/");
|
||||
Serial.print(FIREBASE_FAIL_LIMIT);
|
||||
Serial.print(" : ");
|
||||
Serial.println(reason);
|
||||
|
||||
if (firebaseFailCount >= FIREBASE_FAIL_LIMIT) {
|
||||
firebaseHealthy = false;
|
||||
lastFirebaseRetry = millis();
|
||||
Serial.println("Firebase OFFLINE, masuk AUTO OFFLINE MODE");
|
||||
}
|
||||
}
|
||||
|
||||
bool canTryFirebase() {
|
||||
if (WiFi.status() != WL_CONNECTED) {
|
||||
firebaseHealthy = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Firebase.ready()) {
|
||||
firebaseHealthy = false;
|
||||
|
||||
if (millis() - lastFirebaseReinit >= FIREBASE_REINIT_INTERVAL) {
|
||||
reinitFirebase();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (firebaseHealthy) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (lastFirebaseRetry == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (millis() - lastFirebaseRetry >= FIREBASE_RETRY_INTERVAL) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// =====================================================
|
||||
// FIREBASE GET CONTROL
|
||||
// =====================================================
|
||||
void firebaseGetControl() {
|
||||
|
||||
bool allSuccess = true;
|
||||
|
||||
for (int i = 0; i < 8; i++) {
|
||||
String path = "/aktuator/mosvet_";
|
||||
path.concat(i + 1);
|
||||
|
||||
if (Firebase.RTDB.getBool(&fbdo, path.c_str())) {
|
||||
bool state = fbdo.boolData();
|
||||
setMosvet(i, state);
|
||||
} else {
|
||||
allSuccess = false;
|
||||
|
||||
Serial.print(path);
|
||||
Serial.print(" : ");
|
||||
Serial.println(fbdo.errorReason());
|
||||
}
|
||||
}
|
||||
|
||||
// reset_wifi opsional, kalau path belum ada tidak dianggap fatal
|
||||
if (Firebase.RTDB.getBool(&fbdo, "/command/reset_wifi")) {
|
||||
resetWifiFlag = fbdo.boolData();
|
||||
}
|
||||
|
||||
if (allSuccess) {
|
||||
markFirebaseSuccess();
|
||||
} else {
|
||||
markFirebaseFail(fbdo.errorReason());
|
||||
}
|
||||
}
|
||||
|
||||
// =====================================================
|
||||
// FIREBASE SEND DATA
|
||||
// =====================================================
|
||||
void firebaseSendData() {
|
||||
|
||||
bool success = true;
|
||||
|
||||
for (int i = 0; i < 5; i++) {
|
||||
String pathSoil = "/data/soil_";
|
||||
pathSoil.concat(i + 1);
|
||||
|
||||
String pathRaw = "/data/soil_raw_";
|
||||
pathRaw.concat(i + 1);
|
||||
|
||||
String pathZone = "/zone/z";
|
||||
pathZone.concat(i + 1);
|
||||
|
||||
if (!Firebase.RTDB.setInt(&fbdo, pathSoil.c_str(), soilPct[i])) success = false;
|
||||
if (!Firebase.RTDB.setInt(&fbdo, pathRaw.c_str(), soilRaw[i])) success = false;
|
||||
if (!Firebase.RTDB.setBool(&fbdo, pathZone.c_str(), zoneState[i])) success = false;
|
||||
}
|
||||
|
||||
if (!Firebase.RTDB.setFloat(&fbdo, "/data/suhu", temp)) success = false;
|
||||
if (!Firebase.RTDB.setFloat(&fbdo, "/data/kelembapan", hum)) success = false;
|
||||
if (!Firebase.RTDB.setFloat(&fbdo, "/data/ldr", lux)) success = false;
|
||||
if (!Firebase.RTDB.setFloat(&fbdo, "/data/ldr_percent", luxPercent)) success = false;
|
||||
if (!Firebase.RTDB.setFloat(&fbdo, "/data/water_flow", flowRate)) success = false;
|
||||
|
||||
for (int i = 0; i < 8; i++) {
|
||||
String pathState = "/status/mosvet_";
|
||||
pathState.concat(i + 1);
|
||||
|
||||
if (!Firebase.RTDB.setBool(&fbdo, pathState.c_str(), mosvetState[i])) {
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (success) {
|
||||
markFirebaseSuccess();
|
||||
} else {
|
||||
markFirebaseFail(fbdo.errorReason());
|
||||
}
|
||||
}
|
||||
|
||||
// =====================================================
|
||||
// OFFLINE AUTO CONTROL
|
||||
// =====================================================
|
||||
void offlineAutoControl() {
|
||||
|
||||
for (int i = 0; i < 5; i++) {
|
||||
if (soilPct[i] < soilLow) zoneState[i] = true;
|
||||
if (soilPct[i] > soilHigh) zoneState[i] = false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 5; i++) {
|
||||
setMosvet(i + 2, zoneState[i]);
|
||||
}
|
||||
|
||||
bool pump = false;
|
||||
|
||||
for (int i = 0; i < 5; i++) {
|
||||
if (zoneState[i]) {
|
||||
pump = true;
|
||||
}
|
||||
}
|
||||
|
||||
setMosvet(0, pump);
|
||||
setMosvet(1, false);
|
||||
setMosvet(7, false);
|
||||
}
|
||||
|
||||
// =====================================================
|
||||
// RESET WIFI HANDLER
|
||||
// =====================================================
|
||||
void handleResetWifi() {
|
||||
|
||||
if (!resetWifiFlag) return;
|
||||
|
||||
Serial.println("PERINTAH RESET WIFI DITERIMA");
|
||||
|
||||
if (WiFi.status() == WL_CONNECTED && Firebase.ready()) {
|
||||
Firebase.RTDB.setBool(&fbdo, "/command/reset_wifi", false);
|
||||
}
|
||||
|
||||
WiFiManager wm;
|
||||
wm.resetSettings();
|
||||
|
||||
delay(1500);
|
||||
ESP.restart();
|
||||
}
|
||||
|
||||
// =====================================================
|
||||
// WEB SERVER
|
||||
// =====================================================
|
||||
void setupWebServer() {
|
||||
|
||||
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
|
||||
|
||||
float soilDash[5];
|
||||
float flowDash[5];
|
||||
bool solenoidDash[5];
|
||||
bool pumpDash[3];
|
||||
|
||||
for (int i = 0; i < 5; i++) {
|
||||
soilDash[i] = (float)soilPct[i];
|
||||
flowDash[i] = flowRate;
|
||||
solenoidDash[i] = mosvetState[i + 2];
|
||||
}
|
||||
|
||||
pumpDash[0] = mosvetState[0];
|
||||
pumpDash[1] = mosvetState[1];
|
||||
pumpDash[2] = mosvetState[7];
|
||||
|
||||
String html = getHTML(
|
||||
hum,
|
||||
temp,
|
||||
lux,
|
||||
soilDash,
|
||||
flowDash,
|
||||
solenoidDash,
|
||||
pumpDash,
|
||||
WiFi.localIP().toString(),
|
||||
WiFi.SSID()
|
||||
);
|
||||
|
||||
request->send(200, "text/html", html);
|
||||
});
|
||||
|
||||
server.on("/data", HTTP_GET, [](AsyncWebServerRequest *request) {
|
||||
|
||||
String json = "{";
|
||||
|
||||
json += "\"temp\":";
|
||||
json += String(temp, 1);
|
||||
json += ",";
|
||||
|
||||
json += "\"hum\":";
|
||||
json += String(hum, 1);
|
||||
json += ",";
|
||||
|
||||
json += "\"lux\":";
|
||||
json += String(lux, 1);
|
||||
json += ",";
|
||||
|
||||
json += "\"luxPercent\":";
|
||||
json += String(luxPercent, 1);
|
||||
json += ",";
|
||||
|
||||
json += "\"flow\":";
|
||||
json += String(flowRate, 2);
|
||||
json += ",";
|
||||
|
||||
json += "\"wifi\":";
|
||||
json += (WiFi.status() == WL_CONNECTED) ? "true" : "false";
|
||||
json += ",";
|
||||
|
||||
json += "\"firebase\":";
|
||||
json += firebaseHealthy ? "true" : "false";
|
||||
json += ",";
|
||||
|
||||
json += "\"mode\":\"";
|
||||
json += firebaseHealthy ? "ONLINE" : "OFFLINE_AUTO";
|
||||
json += "\",";
|
||||
|
||||
json += "\"soil\":[";
|
||||
for (int i = 0; i < 5; i++) {
|
||||
if (i > 0) json += ",";
|
||||
json += String(soilPct[i]);
|
||||
}
|
||||
json += "],";
|
||||
|
||||
json += "\"soilRaw\":[";
|
||||
for (int i = 0; i < 5; i++) {
|
||||
if (i > 0) json += ",";
|
||||
json += String(soilRaw[i]);
|
||||
}
|
||||
json += "],";
|
||||
|
||||
json += "\"mosvet\":[";
|
||||
for (int i = 0; i < 8; i++) {
|
||||
if (i > 0) json += ",";
|
||||
json += mosvetState[i] ? "true" : "false";
|
||||
}
|
||||
json += "]";
|
||||
|
||||
json += "}";
|
||||
|
||||
request->send(200, "application/json", json);
|
||||
});
|
||||
|
||||
server.on("/toggle", HTTP_GET, [](AsyncWebServerRequest *request) {
|
||||
|
||||
if (!request->hasParam("pump")) {
|
||||
request->send(400, "text/plain", "Parameter pump tidak ada");
|
||||
return;
|
||||
}
|
||||
|
||||
int pump = request->getParam("pump")->value().toInt();
|
||||
|
||||
int mosvetIndex = -1;
|
||||
|
||||
if (pump == 0) mosvetIndex = 0;
|
||||
if (pump == 1) mosvetIndex = 1;
|
||||
if (pump == 2) mosvetIndex = 7;
|
||||
|
||||
if (mosvetIndex < 0) {
|
||||
request->send(400, "text/plain", "Pump tidak valid");
|
||||
return;
|
||||
}
|
||||
|
||||
bool newState = !mosvetState[mosvetIndex];
|
||||
setMosvet(mosvetIndex, newState);
|
||||
|
||||
String path = "/aktuator/mosvet_";
|
||||
path.concat(mosvetIndex + 1);
|
||||
|
||||
if (canTryFirebase()) {
|
||||
if (Firebase.RTDB.setBool(&fbdo, path.c_str(), newState)) {
|
||||
markFirebaseSuccess();
|
||||
} else {
|
||||
markFirebaseFail(fbdo.errorReason());
|
||||
}
|
||||
}
|
||||
|
||||
request->send(200, "text/plain", "OK");
|
||||
});
|
||||
|
||||
server.begin();
|
||||
|
||||
Serial.println("Web Server Started");
|
||||
Serial.print("IP Address: ");
|
||||
Serial.println(WiFi.localIP());
|
||||
}
|
||||
|
||||
// =====================================================
|
||||
// SETUP
|
||||
// =====================================================
|
||||
void setup() {
|
||||
|
||||
Serial.begin(115200);
|
||||
delay(1000);
|
||||
|
||||
Serial.println("=== SYSTEM START ===");
|
||||
|
||||
for (int i = 0; i < 8; i++) {
|
||||
pinMode(mosvetPins[i], OUTPUT);
|
||||
setMosvet(i, false);
|
||||
}
|
||||
|
||||
pinMode(FLOW_PIN, INPUT_PULLUP);
|
||||
attachInterrupt(digitalPinToInterrupt(FLOW_PIN), flowISR, RISING);
|
||||
|
||||
analogReadResolution(12);
|
||||
|
||||
dht.begin();
|
||||
|
||||
Wire.begin(21, 22);
|
||||
delay(200);
|
||||
|
||||
if (lightMeter.begin(BH1750::CONTINUOUS_HIGH_RES_MODE)) {
|
||||
Serial.println("BH1750 OK");
|
||||
} else {
|
||||
Serial.println("BH1750 ERROR - cek SDA/SCL/VCC/GND");
|
||||
}
|
||||
|
||||
for (int i = 0; i < LUX_SMOOTHING; i++) {
|
||||
luxBuffer[i] = 0;
|
||||
}
|
||||
|
||||
WiFiManager wm;
|
||||
wm.setConnectTimeout(20);
|
||||
wm.setConfigPortalTimeout(180);
|
||||
wm.autoConnect("AUTO-WATER");
|
||||
|
||||
WiFi.setSleep(false);
|
||||
WiFi.setAutoReconnect(true);
|
||||
|
||||
Serial.print("WiFi Status: ");
|
||||
Serial.println(WiFi.status());
|
||||
Serial.print("IP Address: ");
|
||||
Serial.println(WiFi.localIP());
|
||||
|
||||
setupWebServer();
|
||||
|
||||
config.database_url = DATABASE_URL;
|
||||
config.signer.tokens.legacy_token = LEGACY_TOKEN;
|
||||
|
||||
Firebase.begin(&config, &auth);
|
||||
Firebase.reconnectWiFi(true);
|
||||
|
||||
firebaseHealthy = false;
|
||||
firebaseFailCount = FIREBASE_FAIL_LIMIT;
|
||||
lastFirebaseRetry = 0;
|
||||
lastWifiConnected = (WiFi.status() == WL_CONNECTED);
|
||||
|
||||
Serial.println("Firebase Setup Done");
|
||||
}
|
||||
|
||||
// =====================================================
|
||||
// LOOP
|
||||
// =====================================================
|
||||
void loop() {
|
||||
|
||||
bool wifiConnected = (WiFi.status() == WL_CONNECTED);
|
||||
bool online = (wifiConnected && firebaseHealthy);
|
||||
|
||||
// =====================================================
|
||||
// DETEKSI WIFI PUTUS / NYAMBUNG LAGI
|
||||
// =====================================================
|
||||
if (wifiConnected != lastWifiConnected) {
|
||||
|
||||
if (!wifiConnected) {
|
||||
Serial.println("WiFi putus, masuk OFFLINE AUTO");
|
||||
firebaseHealthy = false;
|
||||
firebaseFailCount = FIREBASE_FAIL_LIMIT;
|
||||
} else {
|
||||
Serial.println("WiFi tersambung lagi, re-init Firebase");
|
||||
reinitFirebase();
|
||||
}
|
||||
|
||||
lastWifiConnected = wifiConnected;
|
||||
}
|
||||
|
||||
// =====================================================
|
||||
// FLOW SETIAP 1 DETIK
|
||||
// =====================================================
|
||||
if (millis() - lastFlowCalc >= 1000) {
|
||||
calculateFlow();
|
||||
lastFlowCalc = millis();
|
||||
}
|
||||
|
||||
// =====================================================
|
||||
// SENSOR SETIAP 500 ms
|
||||
// =====================================================
|
||||
if (millis() - lastSensor >= 500) {
|
||||
readSensors();
|
||||
lastSensor = millis();
|
||||
}
|
||||
|
||||
// =====================================================
|
||||
// CEK WIFI BERKALA
|
||||
// =====================================================
|
||||
if (millis() - lastWifiCheck >= 5000) {
|
||||
lastWifiCheck = millis();
|
||||
|
||||
if (WiFi.status() != WL_CONNECTED) {
|
||||
firebaseHealthy = false;
|
||||
firebaseFailCount = FIREBASE_FAIL_LIMIT;
|
||||
Serial.println("WiFi belum connect, mencoba reconnect...");
|
||||
WiFi.reconnect();
|
||||
}
|
||||
}
|
||||
|
||||
// =====================================================
|
||||
// COBA FIREBASE GET
|
||||
// Kalau gagal beberapa kali, tetap offline auto.
|
||||
// Kalau berhasil, otomatis balik online.
|
||||
// =====================================================
|
||||
if (canTryFirebase() && millis() - lastFirebaseGet >= 3000) {
|
||||
firebaseGetControl();
|
||||
lastFirebaseGet = millis();
|
||||
}
|
||||
|
||||
// =====================================================
|
||||
// UPDATE MODE
|
||||
// =====================================================
|
||||
online = (WiFi.status() == WL_CONNECTED && firebaseHealthy);
|
||||
|
||||
if (!online) {
|
||||
offlineAutoControl();
|
||||
}
|
||||
|
||||
// =====================================================
|
||||
// RESET WIFI
|
||||
// =====================================================
|
||||
handleResetWifi();
|
||||
|
||||
// =====================================================
|
||||
// FIREBASE SEND
|
||||
// Hanya kalau Firebase benar-benar sehat.
|
||||
// =====================================================
|
||||
if (online && millis() - lastFirebaseSend >= 5000) {
|
||||
firebaseSendData();
|
||||
lastFirebaseSend = millis();
|
||||
}
|
||||
|
||||
// =====================================================
|
||||
// DEBUG SERIAL
|
||||
// =====================================================
|
||||
if (millis() - lastPrint >= 1000) {
|
||||
|
||||
Serial.print("WiFi: ");
|
||||
Serial.print(WiFi.status() == WL_CONNECTED ? "CONNECTED" : "DISCONNECTED");
|
||||
|
||||
Serial.print(" | Firebase: ");
|
||||
Serial.print(firebaseHealthy ? "HEALTHY" : "OFFLINE");
|
||||
|
||||
Serial.print(" | MODE: ");
|
||||
Serial.println(online ? "ONLINE" : "OFFLINE AUTO");
|
||||
|
||||
Serial.println("==== SENSOR DATA ====");
|
||||
|
||||
for (int i = 0; i < 5; i++) {
|
||||
Serial.printf(
|
||||
"S%d RAW:%4d | %3d%% | Z:%d\n",
|
||||
i + 1,
|
||||
soilRaw[i],
|
||||
soilPct[i],
|
||||
zoneState[i]
|
||||
);
|
||||
}
|
||||
|
||||
Serial.printf(
|
||||
"TEMP:%.1f HUM:%.1f LUX:%.1f LUX%%:%.1f FLOW:%.2f\n",
|
||||
temp,
|
||||
hum,
|
||||
lux,
|
||||
luxPercent,
|
||||
flowRate
|
||||
);
|
||||
|
||||
Serial.println("====================\n");
|
||||
|
||||
lastPrint = millis();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
|
||||
This directory is intended for PlatformIO Test Runner and project tests.
|
||||
|
||||
Unit Testing is a software testing method by which individual units of
|
||||
source code, sets of one or more MCU program modules together with associated
|
||||
control data, usage procedures, and operating procedures, are tested to
|
||||
determine whether they are fit for use. Unit testing finds problems early
|
||||
in the development cycle.
|
||||
|
||||
More information about PlatformIO Unit Testing:
|
||||
- https://docs.platformio.org/en/latest/advanced/unit-testing/index.html
|
||||
Loading…
Reference in New Issue