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