// ==================== MODUL 1 - VREMENSKI MODUL ZUNANJI ====================
#include <esp_now.h>
#include <WiFi.h>
#include <Wire.h>
#include <Adafruit_AHTX0.h>
#include <Adafruit_BMP280.h>
#include <Preferences.h>
// ==================== PIN DEFINICIJE ====================
#define I2C_SDA 4
#define I2C_SCL 5
#define LDR_PIN 6
#define STATUS_LED 2
#define MODULE_ID 1
#define MODULE_TYPE 1 // MODULE_WEATHER = 1
// MAC naslov glavnega sistema (master ESP32)
uint8_t masterMAC[] = {0xB4, 0x3A, 0x45, 0xF3, 0xEB, 0xF0};
// Interval pošiljanja (5 sekund)
const unsigned long SEND_INTERVAL = 5000;
// ==================== KALIBRACIJSKE KONSTANTE ====================
#define CMD_CALIBRATE_DARK 10 // Kalibriraj temno vrednost
#define CMD_CALIBRATE_BRIGHT 11 // Kalibriraj svetlo vrednost
#define CMD_SAVE_CALIBRATION 12 // Shrani kalibracijo
// ==================== ESP-NOW STRUKTURE (USKLAJENE Z GLAVNIM SISTEMOM) ====================
enum ModuleType {
MODULE_WEATHER = 1,
MODULE_IRRIGATION = 2,
MODULE_SHADE_MOTOR = 3
};
enum ErrorCode {
ERR_NONE = 0,
ERR_SENSOR_AHT = 1,
ERR_SENSOR_BMP = 2,
ERR_SENSOR_LDR = 3,
ERR_I2C = 4,
// Kalibracijske kode
CALIB_DARK = 100,
CALIB_BRIGHT = 101,
CALIB_CONFIRM = 102
};
// GLAVNA STRUKTURA Z UNIJO
struct ModuleData {
uint8_t moduleId;
ModuleType moduleType;
unsigned long timestamp;
ErrorCode errorCode;
float batteryVoltage;
union {
struct {
float temperature;
float humidity;
float pressure;
float lux;
float bmpTemperature;
} weather;
struct {
float flowRate1, flowRate2, flowRate3;
float totalFlow1, totalFlow2, totalFlow3;
bool relay1State, relay2State, relay3State;
} irrigation;
};
};
// Struktura za prejemanje ukazov
struct CommandData {
uint8_t targetModuleId;
uint8_t command;
float param1;
float param2;
};
// ==================== GLOBALNE SPREMENLJIVKE ====================
ModuleData sendData;
unsigned long lastSendTime = 0;
Adafruit_AHTX0 aht;
Adafruit_BMP280 bmp;
// LDR kalibracijski parametri
const float LDR_LUX_B = 6.5;
const float LDR_LUX_M = -0.7;
const float R_FIXED = 10000.0;
const float VCC = 3.3;
// ==================== KALIBRACIJSKE SPREMENLJIVKE ====================
int ldrMinADC = 500; // ADC vrednost pri SVETLOBI (nizek ADC)
int ldrMaxADC = 3000; // ADC vrednost pri TEMI (visok ADC)
Preferences preferences;
// ==================== FUNKCIJE ZA BRANJE SENZORJEV ====================
float readAHT20Temperature() {
sensors_event_t humidity, temp;
if (!aht.getEvent(&humidity, &temp)) return -999.0;
return temp.temperature;
}
float readAHT20Humidity() {
sensors_event_t humidity, temp;
if (!aht.getEvent(&humidity, &temp)) return -999.0;
return humidity.relative_humidity;
}
float readBMP280Pressure() {
float pressure = bmp.readPressure() / 100.0F;
if (isnan(pressure) || pressure < 300 || pressure > 1100) return -999.0;
return pressure;
}
float readBMP280Temperature() {
float temp = bmp.readTemperature();
if (isnan(temp) || temp < -40 || temp > 85) return -999.0;
return temp;
}
// ==================== LDR S KALIBRACIJO ====================
void saveLDCalibration() {
preferences.begin("ldr-calib", false);
preferences.putInt("min_adc", ldrMinADC);
preferences.putInt("max_adc", ldrMaxADC);
preferences.end();
Serial.printf("✓ Kalibracija shranjena: min=%d (svetlo), max=%d (temno)\n", ldrMinADC, ldrMaxADC);
}
void loadLDCalibration() {
preferences.begin("ldr-calib", true);
ldrMinADC = preferences.getInt("min_adc", 500);
ldrMaxADC = preferences.getInt("max_adc", 3000);
preferences.end();
Serial.printf("Nalozena kalibracija: min=%d (svetlo), max=%d (temno)\n", ldrMinADC, ldrMaxADC);
if (ldrMinADC >= ldrMaxADC) {
Serial.println("⚠ OPOZORILO: Kalibracijske vrednosti so neveljavne! Uporabljam privzete.");
ldrMinADC = 500;
ldrMaxADC = 3000;
}
}
float readLuxWithCalibration() {
int adcValue = analogRead(LDR_PIN);
if (ldrMinADC > 0 && ldrMaxADC > 0 && ldrMinADC < ldrMaxADC) {
int percent = map(adcValue, ldrMinADC, ldrMaxADC, 100, 0);
percent = constrain(percent, 0, 100);
float lux = map(percent, 0, 100, 0, 20000);
static unsigned long lastDebug = 0;
if (millis() - lastDebug > 10000) {
Serial.printf("LDR kalibriran: ADC=%d → %d%% → %.0f lx\n", adcValue, percent, lux);
lastDebug = millis();
}
return lux;
}
float voltage = (adcValue / 4095.0) * VCC;
float resistance = 0;
if (voltage > 0.01 && voltage < (VCC - 0.01)) {
resistance = (VCC - voltage) * R_FIXED / voltage;
} else if (voltage <= 0.01) {
resistance = 1000000.0;
} else {
resistance = 10.0;
}
float lux = 0;
if (resistance > 0.1 && resistance < 10000000.0) {
lux = pow(10.0, (log10(resistance) - LDR_LUX_B) / LDR_LUX_M);
lux = constrain(lux, 0.1, 20000.0);
}
return lux;
}
// ==================== INICIALIZACIJA SENZORJEV ====================
bool initSensors() {
bool allOk = true;
sendData.errorCode = ERR_NONE;
Serial.println("\n--- Inicializacija senzorjev ---");
Serial.print("I2C... ");
Wire.begin(I2C_SDA, I2C_SCL);
Wire.setClock(100000);
delay(100);
Serial.println("OK");
Serial.print("AHT20... ");
if (!aht.begin()) {
Serial.println("NAPAKA");
sendData.errorCode = ERR_SENSOR_AHT;
allOk = false;
} else {
Serial.println("OK");
}
Serial.print("BMP280... ");
if (bmp.begin(0x76) || bmp.begin(0x77)) {
Serial.println("OK");
bmp.setSampling(Adafruit_BMP280::MODE_NORMAL,
Adafruit_BMP280::SAMPLING_X2,
Adafruit_BMP280::SAMPLING_X16,
Adafruit_BMP280::FILTER_X16,
Adafruit_BMP280::STANDBY_MS_500);
} else {
Serial.println("NAPAKA");
if (sendData.errorCode == ERR_NONE) {
sendData.errorCode = ERR_SENSOR_BMP;
}
allOk = false;
}
pinMode(LDR_PIN, INPUT);
pinMode(STATUS_LED, OUTPUT);
digitalWrite(STATUS_LED, LOW);
return allOk;
}
// ==================== CALLBACK ZA PREJEM UKAZOV ====================
void onDataRecv(const esp_now_recv_info_t *recv_info, const uint8_t *incomingData, int len) {
if (len == sizeof(CommandData)) {
CommandData cmd;
memcpy(&cmd, incomingData, sizeof(cmd));
if (cmd.targetModuleId == MODULE_ID) {
Serial.printf("\n--- Prejet ukaz ---\n");
Serial.printf("Ukaz: %d\n", cmd.command);
Serial.printf("Param1: %.1f\n", cmd.param1);
Serial.printf("Param2: %.1f\n", cmd.param2);
// UKAZ 10: Izmeri TEMNO vrednost (visok ADC)
if (cmd.command == CMD_CALIBRATE_DARK) {
int darkADC = analogRead(LDR_PIN);
Serial.printf("Izmerjena TEMNA vrednost: %d\n", darkADC);
ModuleData response;
response.moduleId = MODULE_ID;
response.moduleType = MODULE_WEATHER;
response.timestamp = millis();
response.errorCode = CALIB_DARK;
response.weather.lux = darkADC;
esp_err_t result = esp_now_send(recv_info->src_addr, (uint8_t*)&response, sizeof(response));
if (result == ESP_OK) {
Serial.println("✓ Temna vrednost poslana nazaj");
digitalWrite(STATUS_LED, HIGH);
delay(50);
digitalWrite(STATUS_LED, LOW);
} else {
Serial.printf("✗ Napaka pri pošiljanju: %d\n", result);
for(int i = 0; i < 3; i++) {
digitalWrite(STATUS_LED, HIGH);
delay(50);
digitalWrite(STATUS_LED, LOW);
delay(50);
}
}
}
// UKAZ 11: Izmeri SVETLO vrednost (nizek ADC)
else if (cmd.command == CMD_CALIBRATE_BRIGHT) {
int brightADC = analogRead(LDR_PIN);
Serial.printf("Izmerjena SVETLA vrednost: %d\n", brightADC);
ModuleData response;
response.moduleId = MODULE_ID;
response.moduleType = MODULE_WEATHER;
response.timestamp = millis();
response.errorCode = CALIB_BRIGHT;
response.weather.lux = brightADC;
esp_err_t result = esp_now_send(recv_info->src_addr, (uint8_t*)&response, sizeof(response));
if (result == ESP_OK) {
Serial.println("✓ Svetla vrednost poslana nazaj");
digitalWrite(STATUS_LED, HIGH);
delay(50);
digitalWrite(STATUS_LED, LOW);
} else {
Serial.printf("✗ Napaka pri pošiljanju: %d\n", result);
for(int i = 0; i < 3; i++) {
digitalWrite(STATUS_LED, HIGH);
delay(50);
digitalWrite(STATUS_LED, LOW);
delay(50);
}
}
}
// UKAZ 12: Shrani kalibracijske vrednosti
else if (cmd.command == CMD_SAVE_CALIBRATION) {
int newMin = (int)cmd.param1;
int newMax = (int)cmd.param2;
Serial.printf("Shranjujem kalibracijo: min=%d, max=%d\n", newMin, newMax);
if (newMin >= newMax) {
Serial.println("⚠ OPOZORILO: Svetlo >= Temno! Vrednosti bodo zamenjane.");
int temp = newMin;
newMin = newMax;
newMax = temp;
}
if (newMin < 0) newMin = 0;
if (newMax > 4095) newMax = 4095;
if (newMax - newMin < 100) {
Serial.println("⚠ OPOZORILO: Razlika med vrednostmi je premajhna!");
}
ldrMinADC = newMin;
ldrMaxADC = newMax;
saveLDCalibration();
ModuleData response;
response.moduleId = MODULE_ID;
response.moduleType = MODULE_WEATHER;
response.timestamp = millis();
response.errorCode = CALIB_CONFIRM;
response.weather.lux = 0;
esp_err_t result = esp_now_send(recv_info->src_addr, (uint8_t*)&response, sizeof(response));
if (result == ESP_OK) {
Serial.println("✓ Kalibracija shranjena in potrjena");
digitalWrite(STATUS_LED, HIGH);
delay(200);
digitalWrite(STATUS_LED, LOW);
} else {
Serial.printf("✗ Napaka pri pošiljanju potrditve: %d\n", result);
}
}
else {
Serial.printf("Neznan ukaz: %d\n", cmd.command);
}
}
}
}
// ==================== SETUP ====================
void setup() {
delay(3000);
setCpuFrequencyMhz(80);
Serial.begin(115200);
delay(1000);
Serial.println("\n\n=========================================");
Serial.println("MODUL 1 - VREMENSKI MODUL (KANAL 1)");
Serial.println("=========================================");
pinMode(STATUS_LED, OUTPUT);
digitalWrite(STATUS_LED, LOW);
// ========== INICIALIZACIJA PREFERENCES ZA KALIBRACIJO ==========
Serial.println("\n--- Nalaganje kalibracije LDR ---");
loadLDCalibration();
// ========== WIFI INICIALIZACIJA ==========
Serial.println("\n--- WiFi inicializacija ---");
WiFi.mode(WIFI_STA);
WiFi.setTxPower(WIFI_POWER_5dBm);
WiFi.setChannel(1); // KANAL 1 (isti kot glavni sistem)
WiFi.disconnect();
delay(500);
Serial.print("MAC naslov: ");
Serial.println(WiFi.macAddress());
Serial.print("Nastavljen kanal: ");
Serial.println(WiFi.channel());
// ========== ESP-NOW INICIALIZACIJA ==========
Serial.println("\n--- ESP-NOW inicializacija ---");
if (esp_now_init() != ESP_OK) {
Serial.println("NAPAKA pri inicializaciji ESP-NOW!");
while(1) {
digitalWrite(STATUS_LED, HIGH);
delay(500);
digitalWrite(STATUS_LED, LOW);
delay(500);
}
}
Serial.println("ESP-NOW inicializiran");
esp_now_register_recv_cb(esp_now_recv_cb_t(onDataRecv));
// Dodaj master kot peerja
Serial.print("Dodajanje master peerja... ");
esp_now_peer_info_t peerInfo;
memset(&peerInfo, 0, sizeof(peerInfo));
memcpy(peerInfo.peer_addr, masterMAC, 6);
peerInfo.channel = 1; // KANAL 1
peerInfo.encrypt = false;
peerInfo.ifidx = WIFI_IF_STA;
esp_err_t addResult = esp_now_add_peer(&peerInfo);
if (addResult == ESP_OK) {
Serial.println("OK");
} else if (addResult == ESP_ERR_ESPNOW_EXIST) {
Serial.println("že obstaja");
} else {
Serial.printf("NAPAKA (koda: %d)\n", addResult);
}
// ========== INICIALIZACIJA SENZORJEV ==========
initSensors();
// ========== PRIPRAVI PODATKE ZA POŠILJANJE ==========
sendData.moduleId = MODULE_ID;
sendData.moduleType = MODULE_WEATHER;
sendData.batteryVoltage = 0;
Serial.println("\n=== MODUL 1 PRIPRAVLJEN ===");
Serial.println("Kanal: 1 (isti kot glavni sistem)");
Serial.println("=========================================\n");
for(int i = 0; i < 3; i++) {
digitalWrite(STATUS_LED, HIGH);
delay(100);
digitalWrite(STATUS_LED, LOW);
delay(100);
}
}
// ==================== GLAVNA ZANKA ====================
void loop() {
unsigned long now = millis();
// ========== 1. PREVERI IN POPRAVI KANAL NA 1 ==========
static unsigned long lastChannelCheck = 0;
if (now - lastChannelCheck > 10000) {
int currentChannel = WiFi.channel();
if (currentChannel != 1) {
Serial.printf("⚠ Kanal je %d, nastavljam na 1...\n", currentChannel);
WiFi.setChannel(1);
delay(50);
if (WiFi.channel() == 1) {
Serial.println("✓ Kanal uspešno nastavljen na 1");
} else {
Serial.printf("✗ Napaka pri nastavljanju kanala (trenutno: %d)\n", WiFi.channel());
}
}
lastChannelCheck = now;
}
// ========== 2. PREVERI ALI PEER OBSTAJA ==========
static unsigned long lastPeerCheck = 0;
if (now - lastPeerCheck > 30000) {
if (!esp_now_is_peer_exist(masterMAC)) {
Serial.println("⚠ Peer ne obstaja - ponovno dodajanje...");
esp_now_peer_info_t peerInfo;
memset(&peerInfo, 0, sizeof(peerInfo));
memcpy(peerInfo.peer_addr, masterMAC, 6);
peerInfo.channel = 1;
peerInfo.encrypt = false;
peerInfo.ifidx = WIFI_IF_STA;
if (esp_now_add_peer(&peerInfo) == ESP_OK) {
Serial.println("✓ Peer ponovno dodan");
}
}
lastPeerCheck = now;
}
// ========== 3. POŠILJANJE PODATKOV ==========
if (now - lastSendTime >= SEND_INTERVAL) {
Serial.println("\n--- Branje senzorjev ---");
sendData.weather.temperature = readAHT20Temperature();
if (sendData.weather.temperature < -50 || sendData.weather.temperature > 80) {
Serial.println(" AHT20 temp: NAPAKA");
sendData.errorCode = ERR_SENSOR_AHT;
} else {
Serial.printf(" AHT20 temp: %.2f°C\n", sendData.weather.temperature);
}
delay(10);
sendData.weather.humidity = readAHT20Humidity();
if (sendData.weather.humidity < 0 || sendData.weather.humidity > 100) {
Serial.println(" AHT20 vlaga: NAPAKA");
sendData.errorCode = ERR_SENSOR_AHT;
} else {
Serial.printf(" AHT20 vlaga: %.2f%%\n", sendData.weather.humidity);
}
delay(10);
sendData.weather.pressure = readBMP280Pressure();
if (sendData.weather.pressure < 800 || sendData.weather.pressure > 1100) {
Serial.println(" BMP280 tlak: NAPAKA");
if (sendData.errorCode == ERR_NONE) {
sendData.errorCode = ERR_SENSOR_BMP;
}
} else {
Serial.printf(" BMP280 tlak: %.2f hPa\n", sendData.weather.pressure);
}
delay(10);
sendData.weather.bmpTemperature = readBMP280Temperature();
if (sendData.weather.bmpTemperature < -40 || sendData.weather.bmpTemperature > 85) {
Serial.println(" BMP280 temp: NAPAKA");
} else {
Serial.printf(" BMP280 temp: %.2f°C\n", sendData.weather.bmpTemperature);
}
delay(10);
sendData.weather.lux = readLuxWithCalibration();
if (sendData.weather.lux < 0 || sendData.weather.lux > 100000) {
Serial.println(" LDR svetloba: NAPAKA");
sendData.errorCode = ERR_SENSOR_LDR;
} else {
if (sendData.weather.lux < 10) {
Serial.printf(" LDR svetloba: %.2f lx\n", sendData.weather.lux);
} else if (sendData.weather.lux < 1000) {
Serial.printf(" LDR svetloba: %.0f lx\n", sendData.weather.lux);
} else {
Serial.printf(" LDR svetloba: %.1f klx\n", sendData.weather.lux / 1000.0);
}
}
delay(10);
sendData.timestamp = now;
Serial.println("\n--- Povzetek podatkov ---");
Serial.printf("Module ID: %d\n", sendData.moduleId);
Serial.printf("Module Type: %d\n", sendData.moduleType);
Serial.printf("Error Code: %d\n", sendData.errorCode);
Serial.printf("Temperatura: %.2f°C\n", sendData.weather.temperature);
Serial.printf("Vlaga: %.2f%%\n", sendData.weather.humidity);
Serial.printf("Tlak: %.2f hPa\n", sendData.weather.pressure);
Serial.printf("Svetloba: %.2f lx\n", sendData.weather.lux);
Serial.printf("BMP Temp: %.2f°C\n", sendData.weather.bmpTemperature);
Serial.printf("Kanal: %d\n", WiFi.channel());
Serial.printf("Kalibracija: min=%d, max=%d\n", ldrMinADC, ldrMaxADC);
Serial.printf("Velikost strukture: %d bajtov\n", sizeof(sendData));
Serial.print("\nPošiljanje glavnemu sistemu... ");
if (esp_now_is_peer_exist(masterMAC)) {
esp_err_t result = esp_now_send(masterMAC, (uint8_t *)&sendData, sizeof(sendData));
if (result == ESP_OK) {
Serial.println("✓ USPEŠNO");
digitalWrite(STATUS_LED, HIGH);
delay(10);
digitalWrite(STATUS_LED, LOW);
} else {
Serial.printf("✗ NAPAKA (koda: %d)\n", result);
for(int i = 0; i < 3; i++) {
digitalWrite(STATUS_LED, HIGH);
delay(50);
digitalWrite(STATUS_LED, LOW);
delay(50);
}
}
} else {
Serial.println("✗ Peer ne obstaja!");
esp_now_peer_info_t peerInfo;
memset(&peerInfo, 0, sizeof(peerInfo));
memcpy(peerInfo.peer_addr, masterMAC, 6);
peerInfo.channel = 1;
peerInfo.encrypt = false;
peerInfo.ifidx = WIFI_IF_STA;
if (esp_now_add_peer(&peerInfo) == ESP_OK) {
Serial.println(" Peer dodan, poskušam znova...");
esp_err_t retry = esp_now_send(masterMAC, (uint8_t *)&sendData, sizeof(sendData));
if (retry == ESP_OK) {
Serial.println(" ✓ Poslano po ponovnem dodajanju");
digitalWrite(STATUS_LED, HIGH);
delay(50);
digitalWrite(STATUS_LED, LOW);
}
}
}
lastSendTime = now;
sendData.errorCode = ERR_NONE;
}
delay(50);
}Loading
esp32-s3-devkitc-1
esp32-s3-devkitc-1
Senzor Pin na modulu Povezava
AHT20 VIN 3.3V 3.3V
AHT20 GND GND GND
AHT20 SDA GPIO4 SDA
AHT20 SCL GPIO5 SCL
BMP280 VIN 3.3V 3.3V
BMP280 GND GND GND
BMP280 SDA GPIO4 SDA
BMP280 SCL GPIO5 SCL
LDR en konec 3.3V 3.3V
LDR drug konec GPIO6 A0
10k upor GPIO6 -> GND Pull-down