đ About This Project
Control 5 relays with your ESP32 using negative logic (LOW = ON, HIGH = OFF). Perfect for home automation, industrial control, and smart switching. Each relay can control lights, fans, pumps, garage doors, or any 5V compatible device. The module connects directly to ESP32 GPIO pins with optocoupler isolation for safety. Includes EEPROM persistent storage, token authentication, and WiFi auto-reconnect. Works with OceanRemote platform for remote access from anywhere.
đť Firmware Code
C++ (Arduino)
// OceanicRemote Secure Firmware - ESP32
// Version 5.0 - Persistent Device ID in EEPROM
// Error values: -999 = sensor read failed or out of range
// Using EEPROM (Reliable Storage)
#include <WiFi.h>
#include <HTTPClient.h>
#include <WiFiClientSecure.h>
#include <ArduinoJson.h>
#include <EEPROM.h>
#include <DHT.h>
#define DHTPIN 15
#define DHTTYPE DHT22
DHT dht(DHTPIN, DHTTYPE);
// ========== EEPROM CONFIGURATION ==========
#define EEPROM_SIZE 512
#define TOKEN_ADDRESS 0
#define TOKEN_MAGIC 0xAA55
#define SESSION_ADDRESS 128
#define SESSION_MAGIC 0xBB66
#define FLAG_ADDRESS 256
#define FLAG_MAGIC 0xCC77
#define VERSION_ADDRESS 260
#define DEVICE_ID_ADDRESS 300
#define DEVICE_ID_MAGIC 0xDD88
#define FIRMWARE_VERSION 50
// ========== RELAY NAMES ==========
const char* relayNames[5] = {
"FAN1",
"Relay 2",
"Relay 3",
"Relay 4",
"Relay 5"
};
// ========== DEVICE STATE ==========
enum DeviceState {
STATE_PROVISIONING = 0,
STATE_REGISTERED = 1,
STATE_ERROR = 2,
STATE_RECONNECTING = 3
};
DeviceState currentState = STATE_PROVISIONING;
// ========== WIFI CONFIGURATION ==========
const char* WIFI_SSID = "SSID_WIF";
const char* WIFI_PASSWORD = "PASSWROD_WIFI";
// ========== SERVER CONFIGURATION ==========
const char* serverHost = "www.oceanremote.net";
// ========== REGISTRATION TOKEN ==========
const char* REGISTRATION_TOKEN = "oc_reg_gi3vKyldRMBQYWndp0by4ZNluFAI7vwMTWB2pi9klBU";
// ========== OPTIMIZED TIMING ==========
const unsigned long BASE_POLL_INTERVAL = 3000;
unsigned long nextCheckTime = 0;
const int JITTER_RANGE_MS = 1500;
unsigned long lastWiFiReconnect = 0;
const unsigned long wifiReconnectInterval = 30000;
int wifiRetryCount = 0;
const int MAX_WIFI_RETRIES = 5;
int registrationRetryCount = 0;
const int MAX_REGISTRATION_RETRIES = 3;
int consecutiveFailures = 0;
// ========== SESSION MANAGEMENT ==========
String sessionId = "";
String permanentToken = "";
bool sessionValid = false;
bool registrationTokenUsed = false;
// ========== DEVICE ID ==========
String deviceId = "";
String macAddress = "";
// ========== RELAY STATES ==========
bool relayStates[5] = {false, false, false, false, false};
bool deviceRegistered = false;
// ========== PIN DEFINITIONS ==========
const int relayPins[] = {2, 4, 5, 12, 13};
const int relayCount = 5;
#define LED_PIN 2
#define ERROR_LED_PIN 2
// ========== SENSOR VARIABLES ==========
float temperature = -999;
float humidity = -999;
// ========== ERROR HANDLING ==========
void enterErrorState(String reason) {
Serial.println("");
Serial.println("========================================");
Serial.print("[ERROR] Entering error state: ");
Serial.println(reason);
Serial.println("========================================");
currentState = STATE_ERROR;
for (int i = 0; i < 3; i++) {
digitalWrite(ERROR_LED_PIN, HIGH);
delay(500);
digitalWrite(ERROR_LED_PIN, LOW);
delay(500);
}
WiFi.disconnect(true);
WiFi.mode(WIFI_OFF);
}
// ========== EEPROM STORAGE FUNCTIONS ==========
void initEEPROM() {
Serial.println("[EEPROM] Initializing...");
EEPROM.begin(EEPROM_SIZE);
int storedVersion;
EEPROM.get(VERSION_ADDRESS, storedVersion);
Serial.printf("[EEPROM] Stored version: %d, Current: %d\n", storedVersion, FIRMWARE_VERSION);
if (storedVersion != FIRMWARE_VERSION) {
Serial.println("[EEPROM] Version mismatch - CLEARING EEPROM!");
for (int i = 0; i < EEPROM_SIZE; i++) {
EEPROM.write(i, 0);
}
EEPROM.put(VERSION_ADDRESS, FIRMWARE_VERSION);
EEPROM.commit();
Serial.println("[EEPROM] EEPROM cleared!");
}
}
void saveTokenToEEPROM(String token) {
Serial.println("[EEPROM] Saving token...");
uint16_t magic = TOKEN_MAGIC;
EEPROM.put(TOKEN_ADDRESS, magic);
int addr = TOKEN_ADDRESS + sizeof(magic);
int len = token.length();
EEPROM.put(addr, len);
addr += sizeof(len);
for (int i = 0; i < len; i++) {
EEPROM.write(addr + i, token[i]);
}
uint16_t checksum = 0;
for (int i = 0; i < len; i++) {
checksum += token[i];
}
EEPROM.put(addr + len, checksum);
EEPROM.commit();
Serial.printf("[EEPROM] Token saved\n");
}
String loadTokenFromEEPROM() {
Serial.println("[EEPROM] Loading token...");
uint16_t magic;
EEPROM.get(TOKEN_ADDRESS, magic);
if (magic != TOKEN_MAGIC) {
Serial.println("[EEPROM] No valid token found");
return "";
}
int addr = TOKEN_ADDRESS + sizeof(magic);
int len;
EEPROM.get(addr, len);
if (len <= 0 || len > 200) {
Serial.println("[EEPROM] Invalid token length");
return "";
}
addr += sizeof(len);
char buffer[256];
for (int i = 0; i < len; i++) {
buffer[i] = EEPROM.read(addr + i);
}
buffer[len] = '\0';
uint16_t storedChecksum;
EEPROM.get(addr + len, storedChecksum);
uint16_t calculatedChecksum = 0;
for (int i = 0; i < len; i++) {
calculatedChecksum += buffer[i];
}
if (storedChecksum != calculatedChecksum) {
Serial.println("[EEPROM] Checksum mismatch!");
return "";
}
String token = String(buffer);
Serial.printf("[EEPROM] Loaded token (length: %d)\n", token.length());
return token;
}
void clearTokenFromEEPROM() {
Serial.println("[EEPROM] Clearing token...");
for (int i = 0; i < 64; i++) {
EEPROM.write(TOKEN_ADDRESS + i, 0);
}
EEPROM.commit();
}
void saveSessionToEEPROM(String session) {
Serial.printf("[EEPROM] Saving session: %s\n", session.c_str());
uint16_t magic = SESSION_MAGIC;
EEPROM.put(SESSION_ADDRESS, magic);
int addr = SESSION_ADDRESS + sizeof(magic);
int len = session.length();
EEPROM.put(addr, len);
addr += sizeof(len);
for (int i = 0; i < len; i++) {
EEPROM.write(addr + i, session[i]);
}
EEPROM.commit();
}
String loadSessionFromEEPROM() {
uint16_t magic;
EEPROM.get(SESSION_ADDRESS, magic);
if (magic != SESSION_MAGIC) return "";
int addr = SESSION_ADDRESS + sizeof(magic);
int len;
EEPROM.get(addr, len);
if (len <= 0 || len > 200) return "";
addr += sizeof(len);
char buffer[256];
for (int i = 0; i < len; i++) {
buffer[i] = EEPROM.read(addr + i);
}
buffer[len] = '\0';
return String(buffer);
}
void clearSessionFromEEPROM() {
for (int i = 0; i < 32; i++) {
EEPROM.write(SESSION_ADDRESS + i, 0);
}
EEPROM.commit();
}
void markRegistrationUsed() {
Serial.println("[EEPROM] Marking registration as used...");
uint16_t magic = FLAG_MAGIC;
EEPROM.put(FLAG_ADDRESS, magic);
EEPROM.commit();
}
bool isRegistrationUsed() {
uint16_t magic;
EEPROM.get(FLAG_ADDRESS, magic);
return (magic == FLAG_MAGIC);
}
void clearAllStorage() {
Serial.println("[EEPROM] Clearing all storage...");
clearTokenFromEEPROM();
clearSessionFromEEPROM();
for (int i = 0; i < 32; i++) {
EEPROM.write(FLAG_ADDRESS + i, 0);
}
EEPROM.commit();
}
// ========== PERSISTENT DEVICE ID FUNCTIONS ==========
void saveDeviceIdToEEPROM(String id) {
Serial.println("[EEPROM] Saving Device ID...");
uint16_t magic = DEVICE_ID_MAGIC;
EEPROM.put(DEVICE_ID_ADDRESS, magic);
int addr = DEVICE_ID_ADDRESS + sizeof(magic);
int len = id.length();
EEPROM.put(addr, len);
addr += sizeof(len);
for (int i = 0; i < len; i++) {
EEPROM.write(addr + i, id[i]);
}
uint16_t checksum = 0;
for (int i = 0; i < len; i++) {
checksum += id[i];
}
EEPROM.put(addr + len, checksum);
EEPROM.commit();
Serial.printf("[EEPROM] Device ID saved: %s\n", id.c_str());
}
String loadDeviceIdFromEEPROM() {
uint16_t magic;
EEPROM.get(DEVICE_ID_ADDRESS, magic);
if (magic != DEVICE_ID_MAGIC) {
Serial.println("[EEPROM] No saved Device ID found");
return "";
}
int addr = DEVICE_ID_ADDRESS + sizeof(magic);
int len;
EEPROM.get(addr, len);
if (len <= 0 || len > 50) return "";
addr += sizeof(len);
char buffer[64];
for (int i = 0; i < len; i++) {
buffer[i] = EEPROM.read(addr + i);
}
buffer[len] = '\0';
uint16_t storedChecksum;
EEPROM.get(addr + len, storedChecksum);
uint16_t calculatedChecksum = 0;
for (int i = 0; i < len; i++) {
calculatedChecksum += buffer[i];
}
if (storedChecksum != calculatedChecksum) {
Serial.println("[EEPROM] Device ID checksum mismatch!");
return "";
}
String id = String(buffer);
Serial.printf("[EEPROM] Loaded Device ID: %s\n", id.c_str());
return id;
}
String generateRandomDeviceId() {
uint8_t mac[6];
WiFi.macAddress(mac);
// Generate random numbers using esp_random() for uniqueness
uint32_t random1 = esp_random();
uint32_t random2 = esp_random();
// Create a unique ID combining MAC and random numbers
char buffer[32];
snprintf(buffer, sizeof(buffer), "ESP32_%02X%02X%02X%02X%02X%02X_%08X",
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5], random1);
String deviceId = String(buffer);
Serial.printf("[DEVICE_ID] Generated random ID: %s\n", deviceId.c_str());
return deviceId;
}
String getOrCreateDeviceId() {
// First, try to load from EEPROM
String savedId = loadDeviceIdFromEEPROM();
if (savedId.length() > 0) {
return savedId;
}
// If not found, generate a new random ID and save it
String newId = generateRandomDeviceId();
saveDeviceIdToEEPROM(newId);
return newId;
}
// ========== HELPER FUNCTIONS ==========
uint8_t packRelays() {
uint8_t packed = 0;
if (relayStates[0]) packed |= 1 << 0;
if (relayStates[1]) packed |= 1 << 1;
if (relayStates[2]) packed |= 1 << 2;
if (relayStates[3]) packed |= 1 << 3;
if (relayStates[4]) packed |= 1 << 4;
return packed;
}
void unpackRelays(uint8_t packed) {
for (int i = 0; i < 5; i++) {
bool state = (packed >> i) & 1;
if (state != relayStates[i]) {
setRelay(i, state);
Serial.printf("[RELAY] %s set to %s\n", relayNames[i], state ? "ON" : "OFF");
}
}
}
void setRelay(int index, bool state) {
// Apply per-relay logic
switch (index) {
case 0: // Relay 1: Negative logic (LOW = ON)
digitalWrite(relayPins[0], state ? LOW : HIGH);
break;
case 1: // Relay 2: Positive logic (HIGH = ON)
digitalWrite(relayPins[1], state ? HIGH : LOW);
break;
case 2: // Relay 3: Positive logic (HIGH = ON)
digitalWrite(relayPins[2], state ? HIGH : LOW);
break;
case 3: // Relay 4: Positive logic (HIGH = ON)
digitalWrite(relayPins[3], state ? HIGH : LOW);
break;
case 4: // Relay 5: Positive logic (HIGH = ON)
digitalWrite(relayPins[4], state ? HIGH : LOW);
break;
default:
return;
}
relayStates[index] = state;
}
void initPins() {
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, LOW);
for (int i = 0; i < relayCount; i++) {
pinMode(relayPins[i], OUTPUT);
setRelay(i, false);
}
dht.begin();
Serial.println("[INIT] Pins initialized");
Serial.println("[RELAY] Custom relay names configured:");
for (int i = 0; i < 5; i++) {
Serial.printf(" Relay %d: %s\n", i+1, relayNames[i]);
}
}
void readSensor() {
static unsigned long lastDHTRead = 0;
unsigned long now = millis();
if (now - lastDHTRead >= 2000) {
lastDHTRead = now;
temperature = dht.readTemperature();
humidity = dht.readHumidity();
if (isnan(temperature) || isnan(humidity)) {
temperature = -999;
humidity = -999;
Serial.println("[SENSOR] DHT22 read failed - sending error code -999");
} else if (temperature < -50 || temperature > 80) {
temperature = -999;
Serial.println("[SENSOR] DHT22 temperature out of range - sending error code -999");
} else {
Serial.printf("[SENSOR] DHT22 - Temp: %.1f°C, Hum: %.1f%%\n", temperature, humidity);
}
}
}
bool connectToWiFi() {
if (WiFi.status() == WL_CONNECTED) return true;
Serial.printf("[WiFi] Attempting connection to SSID: %s\n", WIFI_SSID);
Serial.print("[WiFi] Connecting");
WiFi.mode(WIFI_STA);
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
int attempts = 0;
while (WiFi.status() != WL_CONNECTED && attempts < 40) {
delay(500);
Serial.print(".");
attempts++;
}
if (WiFi.status() == WL_CONNECTED) {
Serial.println(" Connected!");
Serial.printf("[WiFi] IP Address: %s\n", WiFi.localIP().toString().c_str());
Serial.printf("[WiFi] Signal Strength (RSSI): %d dBm\n", WiFi.RSSI());
wifiRetryCount = 0;
consecutiveFailures = 0;
return true;
}
Serial.printf(" Failed! (Reason: %d)\n", WiFi.status());
wifiRetryCount++;
return false;
}
void registerDevice() {
if (currentState != STATE_PROVISIONING) return;
if (registrationTokenUsed) return;
if (registrationRetryCount >= MAX_REGISTRATION_RETRIES) {
enterErrorState("Registration failed");
return;
}
if (WiFi.status() != WL_CONNECTED) return;
Serial.println("[REG] Registering device...");
Serial.printf("[REG] Registration token: %s\n", REGISTRATION_TOKEN);
Serial.printf("[REG] Device ID: %s\n", deviceId.c_str());
WiFiClientSecure client;
client.setInsecure();
HTTPClient http;
String url = "https://";
url += serverHost;
url += "/api/device/register/";
Serial.printf("[REG] URL: %s\n", url.c_str());
JsonDocument doc;
doc["token"] = REGISTRATION_TOKEN;
doc["device_id"] = deviceId;
doc["mac_address"] = "";
doc["ip"] = WiFi.localIP().toString();
String jsonString;
serializeJson(doc, jsonString);
Serial.printf("[REG] Payload: %s\n", jsonString.c_str());
if (http.begin(client, url)) {
http.addHeader("Content-Type", "application/json");
int code = http.POST(jsonString);
Serial.printf("[REG] HTTP Response Code: %d\n", code);
if (code == 200) {
String payload = http.getString();
Serial.printf("[REG] Response: %s\n", payload.c_str());
JsonDocument response;
deserializeJson(response, payload);
if (response["success"]) {
permanentToken = response["token"].as<String>();
deviceRegistered = true;
registrationTokenUsed = true;
currentState = STATE_REGISTERED;
saveTokenToEEPROM(permanentToken);
markRegistrationUsed();
String verifyToken = loadTokenFromEEPROM();
if (verifyToken == permanentToken) {
Serial.println("[EEPROM] Token verified!");
} else {
Serial.println("[EEPROM] Token verification FAILED!");
}
Serial.println("[REG] â Registration successful!");
registrationRetryCount = 0;
getSession();
} else {
Serial.printf("[REG] Registration failed: %s\n", response["error"].as<String>().c_str());
}
} else if (code == 429) {
registrationRetryCount++;
Serial.printf("[REG] Rate limited, retry %d/%d\n", registrationRetryCount, MAX_REGISTRATION_RETRIES);
} else if (code == 401) {
Serial.println("[REG] Token already used or expired");
registrationTokenUsed = true;
} else {
Serial.printf("[REG] Unexpected response code: %d\n", code);
}
http.end();
} else {
Serial.println("[REG] HTTP connection failed!");
}
}
void getSession() {
if (WiFi.status() != WL_CONNECTED) return;
if (permanentToken.length() == 0) return;
Serial.println("[SESSION] Getting session...");
WiFiClientSecure client;
client.setInsecure();
HTTPClient http;
String url = "https://";
url += serverHost;
url += "/api/device/state/?token=";
url += permanentToken;
url += "&device_id=";
url += deviceId;
url += "&ip=";
url += WiFi.localIP().toString();
url += "&cpu_freq=";
url += String(ESP.getCpuFreqMHz());
url += "&free_heap=";
url += String(ESP.getFreeHeap());
url += "&flash_size=";
url += String(ESP.getFlashChipSize());
Serial.printf("[SESSION] URL: %s\n", url.c_str());
if (http.begin(client, url)) {
int code = http.GET();
Serial.printf("[SESSION] HTTP Response: %d\n", code);
if (code == 200) {
String payload = http.getString();
Serial.printf("[SESSION] Response: %s\n", payload.c_str());
JsonDocument doc;
deserializeJson(doc, payload);
if (doc["session"].is<String>()) {
sessionId = doc["session"].as<String>();
sessionValid = true;
saveSessionToEEPROM(sessionId);
Serial.printf("[SESSION] Got session ID: %s\n", sessionId.c_str());
} else {
Serial.println("[SESSION] No session ID in response");
}
} else if (code == 401 || code == 404) {
Serial.println("[SESSION] Token invalid - will re-register");
deviceRegistered = false;
sessionValid = false;
permanentToken = "";
sessionId = "";
clearAllStorage();
currentState = STATE_PROVISIONING;
} else {
Serial.printf("[SESSION] Unexpected response code: %d\n", code);
}
http.end();
} else {
Serial.println("[SESSION] HTTP connection failed!");
}
}
void updateDeviceState() {
if (WiFi.status() != WL_CONNECTED) {
Serial.println("[UPDATE] WiFi not connected - skipping update");
return;
}
if (!deviceRegistered) {
Serial.println("[UPDATE] Device not registered - skipping update");
return;
}
if (currentState != STATE_REGISTERED) {
Serial.printf("[UPDATE] Invalid state: %d - skipping update\n", currentState);
return;
}
readSensor();
WiFiClientSecure client;
client.setInsecure();
HTTPClient http;
String url = "https://";
url += serverHost;
url += "/api/device/state/";
if (sessionValid && sessionId.length() > 0) {
url += "?session=";
url += sessionId;
Serial.printf("[UPDATE] Using session: %s\n", sessionId.c_str());
} else if (permanentToken.length() > 0) {
url += "?token=";
url += permanentToken;
Serial.printf("[UPDATE] Using token\n");
} else {
Serial.println("[UPDATE] No session or token available!");
return;
}
url += "&device_id=";
url += deviceId;
uint8_t packed = packRelays();
url += "&s=";
url += String(packed, HEX);
// ALWAYS send temperature data - even if it's -999 (error code)
int tempInt = (int)(temperature * 10);
url += "&t=";
url += String(tempInt);
Serial.printf("[UPDATE] Sending temperature: %.1f°C (raw: %d)\n", temperature, tempInt);
if (humidity >= 0 && humidity <= 100) {
url += "&h=";
url += String((int)humidity);
}
url += "&ip=";
url += WiFi.localIP().toString();
url += "&cpu_freq=";
url += String(ESP.getCpuFreqMHz());
url += "&free_heap=";
url += String(ESP.getFreeHeap());
url += "&flash_size=";
url += String(ESP.getFlashChipSize());
Serial.println("[UPDATE] ========== FULL URL ==========");
Serial.println(url);
Serial.println("[UPDATE] ===============================");
if (http.begin(client, url)) {
http.setTimeout(8000);
int code = http.GET();
Serial.printf("[UPDATE] HTTP Response Code: %d\n", code);
if (code == 200) {
String payload = http.getString();
JsonDocument doc;
deserializeJson(doc, payload);
Serial.printf("[UPDATE] Response received, size: %d\n", payload.length());
if (doc["session"].is<String>()) {
sessionId = doc["session"].as<String>();
sessionValid = true;
saveSessionToEEPROM(sessionId);
Serial.printf("[UPDATE] New session received: %s\n", sessionId.c_str());
}
// Process relay states
bool relayChanged = false;
for (int i = 0; i < 5; i++) {
String key = "relay" + String(i+1);
if (doc[key].is<bool>()) {
bool newState = doc[key];
if (newState != relayStates[i]) {
setRelay(i, newState);
relayChanged = true;
Serial.printf("[RELAY] %s set to %s (from server)\n", relayNames[i], newState ? "ON" : "OFF");
}
}
}
if (!relayChanged && doc["s"].is<String>()) {
uint8_t serverState = (uint8_t)strtol(doc["s"], NULL, 16);
uint8_t currentRelayState = packRelays();
if (serverState != currentRelayState) {
unpackRelays(serverState);
Serial.printf("[RELAY] Updated from packed state: %02X\n", serverState);
}
}
consecutiveFailures = 0;
Serial.print(".");
} else if (code == 401 || code == 404) {
Serial.println("[UPDATE] Session invalid - attempting to recover with token");
// Try to get a new session using the permanent token
if (permanentToken.length() > 0) {
getSession();
if (sessionValid) {
Serial.println("[UPDATE] Successfully renewed session!");
consecutiveFailures = 0;
} else {
Serial.println("[UPDATE] Failed to renew session. Will re-register.");
deviceRegistered = false;
sessionValid = false;
currentState = STATE_PROVISIONING;
clearAllStorage();
}
} else {
Serial.println("[UPDATE] No permanent token available. Forcing re-registration.");
deviceRegistered = false;
sessionValid = false;
currentState = STATE_PROVISIONING;
clearAllStorage();
}
consecutiveFailures++;
} else if (code == 0) {
Serial.println("[UPDATE] Connection failed - no response");
consecutiveFailures++;
} else {
Serial.printf("[UPDATE] Unexpected response: %d\n", code);
consecutiveFailures++;
}
http.end();
} else {
Serial.println("[UPDATE] HTTP connection initialization failed!");
consecutiveFailures++;
}
// If too many failures, force re-registration
if (consecutiveFailures > 10) {
Serial.println("[UPDATE] Too many failures - forcing re-registration");
deviceRegistered = false;
sessionValid = false;
permanentToken = "";
sessionId = "";
clearAllStorage();
currentState = STATE_PROVISIONING;
consecutiveFailures = 0;
}
}
void checkWiFiAndReconnect() {
if (WiFi.status() != WL_CONNECTED) {
Serial.printf("[WiFi] Connection lost! Status: %d\n", WiFi.status());
if (connectToWiFi()) {
Serial.println("[WiFi] Reconnected successfully!");
// After reconnection, try to recover session
if (deviceRegistered && !sessionValid) {
getSession();
}
} else {
Serial.println("[WiFi] Reconnection failed!");
}
}
}
void setup() {
Serial.begin(115200);
delay(2000);
Serial.println("");
Serial.println("########################################");
Serial.println("# OceanicRemote v5.0 - ESP32 #");
Serial.println("# Persistent Device ID #");
Serial.println("########################################");
Serial.printf("[BOOT] Firmware version: %d\n", FIRMWARE_VERSION);
Serial.printf("[BOOT] Free heap: %d bytes\n", ESP.getFreeHeap());
initEEPROM();
randomSeed(esp_random());
// Get or create persistent Device ID (stored in EEPROM)
deviceId = getOrCreateDeviceId();
initPins();
Serial.printf("[SYSTEM] Device ID: %s\n", deviceId.c_str());
Serial.printf("[SYSTEM] Server: %s\n", serverHost);
Serial.printf("[SYSTEM] Polling interval: %d ms\n", BASE_POLL_INTERVAL);
Serial.printf("[SYSTEM] WiFi SSID: %s\n", WIFI_SSID);
Serial.printf("[SYSTEM] ESP32 Chip Model: %s\n", ESP.getChipModel());
Serial.printf("[SYSTEM] CPU Frequency: %d MHz\n", ESP.getCpuFreqMHz());
Serial.printf("[SYSTEM] Flash Size: %d bytes\n", ESP.getFlashChipSize());
String savedToken = loadTokenFromEEPROM();
if (savedToken.length() > 0) {
permanentToken = savedToken;
deviceRegistered = true;
currentState = STATE_REGISTERED;
Serial.printf("[BOOT] Found saved token (length: %d)\n", permanentToken.length());
String savedSession = loadSessionFromEEPROM();
if (savedSession.length() > 0) {
sessionId = savedSession;
sessionValid = true;
Serial.printf("[BOOT] Found saved session: %s\n", sessionId.c_str());
} else {
Serial.println("[BOOT] No saved session found");
}
} else {
if (isRegistrationUsed()) {
Serial.println("[BOOT] Device was registered but token missing! Will re-register.");
} else {
Serial.println("[BOOT] No token found - will register as NEW device");
currentState = STATE_PROVISIONING;
registrationTokenUsed = false;
registrationRetryCount = 0;
}
}
Serial.println("[PHASE 1] Connecting to WiFi...");
if (!connectToWiFi()) {
Serial.println("[ERROR] Cannot connect to WiFi - will retry in loop");
}
Serial.println("[PHASE 2] Establishing server connection...");
if (!deviceRegistered && currentState == STATE_PROVISIONING) {
registerDevice();
}
if (deviceRegistered && currentState == STATE_REGISTERED) {
if (!sessionValid || sessionId.length() == 0) {
getSession();
}
updateDeviceState();
}
nextCheckTime = millis() + random(0, 5000);
Serial.println("[PHASE 3] Entering main loop...");
Serial.println("[INFO] Device will now poll server every 3 seconds");
}
void loop() {
if (currentState == STATE_ERROR) {
delay(5000);
return;
}
unsigned long currentMillis = millis();
// Check WiFi connection periodically
static unsigned long lastWiFiCheck = 0;
if (currentMillis - lastWiFiCheck > 10000) {
lastWiFiCheck = currentMillis;
checkWiFiAndReconnect();
}
if (WiFi.status() == WL_CONNECTED) {
wifiRetryCount = 0;
if (currentMillis >= nextCheckTime) {
int jitter = random(-JITTER_RANGE_MS, JITTER_RANGE_MS);
nextCheckTime = currentMillis + BASE_POLL_INTERVAL + jitter;
if (deviceRegistered && currentState == STATE_REGISTERED) {
updateDeviceState();
} else if (!deviceRegistered && currentState == STATE_PROVISIONING) {
registerDevice();
}
}
} else {
if (currentMillis - lastWiFiReconnect >= wifiReconnectInterval) {
lastWiFiReconnect = currentMillis;
if (wifiRetryCount < MAX_WIFI_RETRIES) {
Serial.printf("[WiFi] Attempting reconnect (%d/%d)...\n", wifiRetryCount + 1, MAX_WIFI_RETRIES);
connectToWiFi();
if (WiFi.status() == WL_CONNECTED && deviceRegistered && !sessionValid) {
getSession();
}
} else {
Serial.println("[WiFi] Max retries reached. Restarting...");
delay(1000);
ESP.restart();
}
}
}
delay(10);
}