🎯 Στόχος, 1η φάση
Η συσκευή:
- Κοιμάται (deep sleep) για εξοικονόμηση ενέργειας
- Ξυπνάει κάθε 1 ώρα (ή πιο γρήγορα για δοκιμή)
- Παίρνει μετρήσεις από:
- DHT11 (Θερμοκρασία, Υγρασία αέρα)
- Light sensor (Φως)
- Soil sensor (Υγρασία εδάφους)
- Κάθε φορά ενημερώνει και εμφανίζει:
- τρέχον μέσο όρο (μέχρι στιγμής στον “κύκλο ημέρας”)
- ελάχιστη τιμή
- μέγιστη τιμή
- Αν πατηθεί ο touch sensor, η συσκευή:
- ξυπνάει άμεσα
- στέλνει (στη Φάση 1: τυπώνει) τα στατιστικά ημέρας (avg/min/max)
- και μετά ξανακοιμάται
📌 “Ημέρα” στη Φάση 1 = 24 μετρήσεις. Μετά μηδενίζει και ξεκινά νέο κύκλο.
🧰 Υλικά
- FireBeetle 2 ESP32-C6
- DHT11 (Acebott module)
- DFRobot Ambient Light Sensor (DFR0026)
- DFRobot Capacitive Soil Moisture (SEN0193)
- DFRobot Capacitive Touch Sensor (DFR0030)
- breadboard / perfboard
- καλώδια Dupont
- USB-C
🔌 Συνδεσμολογία (όλα μαζί)
Τροφοδοσία
- Όλοι οι αισθητήρες: VCC → 3.3V, GND → GND
Pins δεδομένων (όπως έχουμε ορίσει)
| Αισθητήρας | Pin |
|---|---|
| DHT11 DATA | GPIO15 (D13) |
| Light AO | GPIO2 (A1 / ADC) |
| Soil AO | GPIO3 (A2 / ADC) |
| Touch OUT (wake) | GPIO6 (D12 / LP GPIO) |
💻 Προετοιμασία Arduino IDE
- Board: ESP32 by Espressif
- Επιλογή board: FireBeetle 2 ESP32-C6
- Library Manager: εγκατάσταση
- DHT sensor library (Adafruit)
- Adafruit Unified Sensor (αν ζητηθεί)
🧑💻 Κώδικας (Deep Sleep + 24-cycle stats + Touch send)
✅ Για δοκιμή στο εργαστήριο, ΔΕΝ περιμένουμε 1 ώρα.
Θα βάλουμε προσωρινά π.χ. 10 δευτερόλεπτα και μετά το γυρίζουμε σε 3600s.
#include <DHT.h>
#include <Preferences.h>
#include <esp_sleep.h>
#include <math.h>
// ---------------- Pins ----------------
#define DHTPIN 15
#define DHTTYPE DHT11
#define LIGHT_PIN 2 // A1 / ADC
#define SOIL_PIN 3 // A2 / ADC
#define TOUCH_PIN 6 // LP GPIO -> wake on touch
// ---------------- Sleep interval ----------------
// Για δοκιμή στο εργαστήριο βάλε 10 ή 30.
// Για κανονική λειτουργία: 3600 (1 ώρα).
#define WAKE_INTERVAL_SECONDS 10
// ---------------- Day cycle ----------------
#define SAMPLES_PER_DAY 24
// ---------------- Globals ----------------
DHT dht(DHTPIN, DHTTYPE);
Preferences prefs;
// Δομή στατιστικών (θα αποθηκεύεται σε NVS)
struct Stats {
uint8_t count;
float t_sum, h_sum;
int light_sum, soil_sum;
float t_min, t_max;
float h_min, h_max;
int light_min, light_max;
int soil_min, soil_max;
};
Stats st;
// ---------------- Helpers ----------------
void resetStats() {
st.count = 0;
st.t_sum = 0; st.h_sum = 0;
st.light_sum = 0; st.soil_sum = 0;
st.t_min = 9999; st.t_max = -9999;
st.h_min = 9999; st.h_max = -9999;
st.light_min = 999999; st.light_max = -999999;
st.soil_min = 999999; st.soil_max = -999999;
}
void loadStats() {
prefs.begin("envstats", true); // read-only
size_t len = prefs.getBytesLength("stats");
prefs.end();
if (len == sizeof(Stats)) {
prefs.begin("envstats", true);
prefs.getBytes("stats", &st, sizeof(Stats));
prefs.end();
} else {
resetStats();
}
}
void saveStats() {
prefs.begin("envstats", false);
prefs.putBytes("stats", &st, sizeof(Stats));
prefs.end();
}
void updateMinMaxFloat(float v, float &vmin, float &vmax) {
if (v < vmin) vmin = v;
if (v > vmax) vmax = v;
}
void updateMinMaxInt(int v, int &vmin, int &vmax) {
if (v < vmin) vmin = v;
if (v > vmax) vmax = v;
}
void printStats(const char* title) {
Serial.println();
Serial.println(title);
Serial.print("Μετρήσεις σήμερα: ");
Serial.print(st.count);
Serial.print("/");
Serial.println(SAMPLES_PER_DAY);
if (st.count == 0) {
Serial.println("Δεν υπάρχουν ακόμα δεδομένα.");
return;
}
float t_avg = st.t_sum / st.count;
float h_avg = st.h_sum / st.count;
float light_avg = (float)st.light_sum / st.count;
float soil_avg = (float)st.soil_sum / st.count;
Serial.print("Θερμοκρασία avg/min/max: ");
Serial.print(t_avg, 1); Serial.print(" / ");
Serial.print(st.t_min, 1); Serial.print(" / ");
Serial.print(st.t_max, 1); Serial.println(" °C");
Serial.print("Υγρασία αέρα avg/min/max: ");
Serial.print(h_avg, 1); Serial.print(" / ");
Serial.print(st.h_min, 1); Serial.print(" / ");
Serial.print(st.h_max, 1); Serial.println(" %");
Serial.print("Φως avg/min/max: ");
Serial.print(light_avg, 0); Serial.print(" / ");
Serial.print(st.light_min); Serial.print(" / ");
Serial.println(st.light_max);
Serial.print("Υγρασία εδάφους avg/min/max: ");
Serial.print(soil_avg, 0); Serial.print(" / ");
Serial.print(st.soil_min); Serial.print(" / ");
Serial.println(st.soil_max);
}
// Placeholder: στη Φάση 2 εδώ θα μπει BLE send προς app
void sendToApp() {
Serial.println();
Serial.println("📤 Αποστολή στην εφαρμογή (Φάση 1: Serial output)...");
printStats("📊 Στατιστικά ημέρας (κύκλος 24 μετρήσεων)");
}
// ---------------- Setup sleep ----------------
void goToSleep() {
// wake από timer
esp_sleep_enable_timer_wakeup((uint64_t)WAKE_INTERVAL_SECONDS * 1000000ULL);
// wake από touch (pin HIGH)
esp_sleep_enable_ext0_wakeup((gpio_num_t)TOUCH_PIN, 1);
Serial.println("😴 Deep sleep...");
Serial.flush();
esp_deep_sleep_start();
}
// ---------------- Main ----------------
void setup() {
Serial.begin(115200);
delay(300);
dht.begin();
loadStats();
esp_sleep_wakeup_cause_t cause = esp_sleep_get_wakeup_cause();
// Αν ξύπνησε από touch -> στείλε stats, μείνε λίγο “available”, μετά sleep
if (cause == ESP_SLEEP_WAKEUP_EXT0) {
Serial.println("👆 Ξύπνησα από TOUCH!");
sendToApp();
// μικρό “παράθυρο” για app σύνδεση στη Φάση 2
delay(5000);
goToSleep();
return;
}
// Αν ξύπνησε από timer (ή power-on) -> πάρε μετρήσεις και ενημέρωσε stats
if (cause == ESP_SLEEP_WAKEUP_TIMER) {
Serial.println("⏰ Ξύπνησα από TIMER (ώρα για μέτρηση)!");
} else {
Serial.println("🔌 Εκκίνηση (power on / reset).");
}
// Αν ολοκληρώθηκαν 24 μετρήσεις, ξεκινά νέα “ημέρα”
if (st.count >= SAMPLES_PER_DAY) {
Serial.println("🗓️ Νέος κύκλος ημέρας (reset στατιστικών).");
resetStats();
}
// Διαβάζουμε αισθητήρες
float t = dht.readTemperature();
float h = dht.readHumidity();
int lightVal = analogRead(LIGHT_PIN);
int soilVal = analogRead(SOIL_PIN);
// Έλεγχος DHT
if (isnan(t) || isnan(h)) {
Serial.println("⚠️ Σφάλμα ανάγνωσης DHT11. Δεν ενημερώνω stats αυτή τη φορά.");
// Παρ’ όλα αυτά κοιμήσου και δοκίμασε στην επόμενη αφύπνιση
goToSleep();
return;
}
// Ενημέρωση stats
st.count++;
st.t_sum += t;
st.h_sum += h;
st.light_sum += lightVal;
st.soil_sum += soilVal;
updateMinMaxFloat(t, st.t_min, st.t_max);
updateMinMaxFloat(h, st.h_min, st.h_max);
updateMinMaxInt(lightVal, st.light_min, st.light_max);
updateMinMaxInt(soilVal, st.soil_min, st.soil_max);
saveStats();
// Εμφάνιση “τρέχοντος” μέσου όρου + min/max
printStats("✅ Νέα μέτρηση καταγράφηκε. Τρέχοντα στατιστικά ημέρας:");
// Sleep ξανά
goToSleep();
}
void loop() {
// Δεν θα εκτελεστεί (μπαίνουμε πάντα σε deep sleep)
}
▶️ Δοκιμή στο εργαστήριο (χωρίς να περιμένουμε 1 ώρα)
- Βάζουμε προσωρινά:
#define WAKE_INTERVAL_SECONDS 10
- Upload
- Serial Monitor: 115200
- Θα δεις κύκλο:
- ξύπνα → μέτρηση → stats → deep sleep
- κάθε ~10s μια νέα “ώρα”
- Αγγίζεις τον touch sensor:
- ξυπνάει αμέσως
- τυπώνει “Αποστολή στην εφαρμογή (Φάση 1: Serial output)”
- δείχνει avg/min/max
- Όταν επιβεβαιωθεί ότι δουλεύει, αλλάζεις:
WAKE_INTERVAL_SECONDS 3600
✅ Τι πετύχαμε
- Στήσαμε σύστημα χαμηλής κατανάλωσης
- Φτιάξαμε στατιστικά ημέρας χωρίς να κρατάμε “όλες τις τιμές”
- Βάλαμε δύο τρόπους αφύπνισης:
- timer wake
- touch wake (manual “send”)
🧩 Σημειώσεις
- Στη Φάση 1 “ημέρα” = 24 μετρήσεις.
- Τα δεδομένα δεν χάνονται στο deep sleep, γιατί αποθηκεύονται σε NVS (Preferences).
🚀 Φάση 2 (όταν θα βάλετε RTC)
Τότε θα κάνουμε:
- πραγματική ημερομηνία/ώρα
- reset στατιστικών όταν αλλάζει η ημερολογιακή μέρα
- (προαιρετικό) timestamp σε κάθε μέτρηση
- Αποστολή μέσω BLE στο κινητό
