๐ About This Project
ESP8266 D1 Mini with DHT22 sensor for temperature and humidity monitoring. Features 5 independent relay controls on dedicated GPIO pins (D1=GPIO5, D2=GPIO4, D3=GPIO0, D6=GPIO12, D5=GPIO14). Connects to OceanRemote platform for remote access with token authentication, session management, and offline backup.
**Features:**
- Real-time temperature and humidity monitoring
- 5 independent relay controls (no pin conflicts)
- Secure token-based authentication
- Session management with auto-refresh
- Offline backup (stores last relay states)
- WiFi auto-reconnect
- Persistent storage in EEPROM
- DHT22 sensor support
**Pin Mapping:**
- Relay 1: GPIO5 (D1)
- Relay 2: GPIO4 (D2)
- Relay 3: GPIO0 (D3)
- Relay 4: GPIO12 (D6)
- Relay 5: GPIO14 (D5)
- DHT22 DATA: GPIO4 (D2)
**Wiring Instructions:**
- DHT22 VCC โ ESP8266 3.3V
- DHT22 DATA โ GPIO4 (D2)
- DHT22 GND โ ESP8266 GND
- Add 10kฮฉ pull-up resistor between DATA and VCC
- Relays connect to 5V power supply
- Relay signal pins to GPIO5, GPIO4, GPIO0, GPIO12, GPIO14
**Use Cases:**
- Home climate control
- Greenhouse automation
- Server room monitoring
- Industrial temperature logging
- Remote relay switching
๐ป Firmware Code
// OceanicRemote Secure Firmware - ESP8266 D1 Mini
// Version 5.1 - CORRECTED INDEPENDENT GPIO CONTROL
// Pin mapping: D1=GPIO5, D2=GPIO4, D3=GPIO0, D6=GPIO12, D5=GPIO14
#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>
#include <WiFiClientSecure.h>
#include <ArduinoJson.h>
#include <EEPROM.h>
#include <DHT.h>
#define DHTPIN 4 // D2 = GPIO4
#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 FIRMWARE_VERSION 51
// ========== RELAY NAMES ==========
const char* relayNames[5] = {
"FAN",
"Relay 2",
"Relay 3",
"Relay 4",
"Relay 5"
};
// ========== DEVICE STATE ==========
enum DeviceState {
STATE_PROVISIONING = 0,
STATE_REGISTERED = 1,
STATE_ERROR = 2
};
DeviceState currentState = STATE_PROVISIONING;
// ========== WIFI CONFIGURATION ==========
const char* WIFI_SSID = "SSID_WIFI";
const char* WIFI_PASSWORD = "PASSWORD_!"#ยค%&/(";
// ========== SERVER CONFIGURATION ==========
const char* serverHost = "www.oceanremote.net";
// ========== REGISTRATION TOKEN ==========
const char* REGISTRATION_TOKEN = "oc_reg_jf6Q3kXv3T5mWul0pQsDhbywkgtsYtNCweDmJrm7iBE";
// ========== 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;
// ========== 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;
// ***** CORRECT ESP8266 D1 MINI PIN DEFINITIONS *****
// D1 = GPIO5, D2 = GPIO4, D3 = GPIO0, D6 = GPIO12, D5 = GPIO14
const int relayPins[] = {5, 4, 0, 12, 14};
const int relayCount = 5;
#define LED_PIN 2 // Built-in LED on GPIO2 (active LOW) - NOT used for relays anymore
// ========== 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(LED_PIN, LOW); // LED ON (active LOW)
delay(500);
digitalWrite(LED_PIN, HIGH); // LED OFF
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\n");
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();
}
// ========== HELPER FUNCTIONS ==========
String generateDeviceId() {
uint8_t mac[6];
WiFi.macAddress(mac);
macAddress = String(mac[0], HEX) + String(mac[1], HEX) + String(mac[2], HEX) +
String(mac[3], HEX) + String(mac[4], HEX) + String(mac[5], HEX);
macAddress.toUpperCase();
return "ESP_" + macAddress;
}
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 with independent pin control
// Each case controls ONE and ONLY ONE pin
switch(index) {
case 0:
// Relay 1: Positive logic (HIGH = ON) - D1/GPIO5
digitalWrite(5, state ? HIGH : LOW);
break;
case 1:
// Relay 2: Positive logic (HIGH = ON) - D2/GPIO4
digitalWrite(4, state ? HIGH : LOW);
break;
case 2:
// Relay 3: Positive logic (HIGH = ON) - D3/GPIO0
digitalWrite(0, state ? HIGH : LOW);
break;
case 3:
// Relay 4: Positive logic (HIGH = ON) - D6/GPIO12
digitalWrite(12, state ? HIGH : LOW);
break;
case 4:
// Relay 5: Positive logic (HIGH = ON) - D5/GPIO14
digitalWrite(14, state ? HIGH : LOW);
break;
default:
break;
}
relayStates[index] = state;
}
void initPins() {
// Built-in LED on GPIO2 - active LOW, NOT used for relays
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, HIGH); // LED OFF (active LOW)
// Initialize relay pins with proper initial states
pinMode(5, OUTPUT);
digitalWrite(5, LOW); // Relay 1 OFF (D1/GPIO5, positive logic)
pinMode(4, OUTPUT);
digitalWrite(4, LOW); // Relay 2 OFF (D2/GPIO4, positive logic)
pinMode(0, OUTPUT);
digitalWrite(0, LOW); // Relay 3 OFF (D3/GPIO0, positive logic)
pinMode(12, OUTPUT);
digitalWrite(12, LOW); // Relay 4 OFF (D6/GPIO12, positive logic)
pinMode(14, OUTPUT);
digitalWrite(14, LOW); // Relay 5 OFF (D5/GPIO14, positive logic)
dht.begin();
Serial.println("");
Serial.println("[INIT] Pins initialized");
Serial.println("[RELAY] ESP8266 D1 Mini GPIO mappings:");
Serial.println(" Relay1: GPIO5 (D1) - Physical Pin D1 โ
Safe");
Serial.println(" Relay2: GPIO4 (D2) - Physical Pin D2 โ
Safe");
Serial.println(" Relay3: GPIO0 (D3) - Physical Pin D3 โ ๏ธ Boot strapping pin");
Serial.println(" Relay4: GPIO12 (D6) - Physical Pin D6 โ
Safe - NO LED conflict!");
Serial.println(" Relay5: GPIO14 (D5) - Physical Pin D5 โ
Safe");
Serial.println("");
Serial.println("[RELAY] Custom relay names:");
for (int i = 0; i < 5; i++) {
Serial.printf(" Relay %d: %s\n", i+1, relayNames[i]);
}
Serial.println("");
Serial.println("[INFO] Built-in LED on GPIO2 is INDEPENDENT (not used for relays)");
Serial.println("[INFO] All relays control their own pins independently!");
}
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 {
Serial.printf("[SENSOR] DHT22 - Temp: %.1fยฐC, Hum: %.1f%%\n", temperature, humidity);
}
}
}
bool connectToWiFi() {
if (WiFi.status() == WL_CONNECTED) return true;
Serial.print("[WiFi] Connecting to ");
Serial.print(WIFI_SSID);
Serial.print("... ");
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: %s\n", WiFi.localIP().toString().c_str());
wifiRetryCount = 0;
return true;
}
Serial.println(" Failed!");
wifiRetryCount++;
return false;
}
// ========== SSL/TLS Helper for ESP8266 ==========
WiFiClientSecure* getSecureClient() {
WiFiClientSecure* client = new WiFiClientSecure();
client->setInsecure();
return client;
}
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...");
WiFiClientSecure* client = getSecureClient();
HTTPClient http;
String url = "https://" + String(serverHost) + "/api/device/register/";
JsonDocument doc;
doc["token"] = REGISTRATION_TOKEN;
doc["device_id"] = deviceId;
doc["mac_address"] = macAddress;
doc["ip"] = WiFi.localIP().toString();
String jsonString;
serializeJson(doc, jsonString);
if (http.begin(*client, url)) {
http.addHeader("Content-Type", "application/json");
int code = http.POST(jsonString);
Serial.printf("[REG] Response: %d\n", code);
if (code == 200) {
String payload = http.getString();
JsonDocument response;
deserializeJson(response, payload);
if (response["success"]) {
permanentToken = response["token"].as<String>();
deviceRegistered = true;
registrationTokenUsed = true;
currentState = STATE_REGISTERED;
saveTokenToEEPROM(permanentToken);
markRegistrationUsed();
Serial.println("[REG] โ Registered!");
registrationRetryCount = 0;
getSession();
}
} else if (code == 429) {
registrationRetryCount++;
}
http.end();
}
delete client;
}
void getSession() {
if (WiFi.status() != WL_CONNECTED) return;
if (permanentToken.length() == 0) return;
Serial.println("[SESSION] Getting session...");
WiFiClientSecure* client = getSecureClient();
HTTPClient http;
String url = "https://" + String(serverHost) + "/api/device/state/";
url += "?token=" + permanentToken;
url += "&device_id=" + deviceId;
url += "&ip=" + WiFi.localIP().toString();
if (http.begin(*client, url)) {
int code = http.GET();
if (code == 200) {
String payload = http.getString();
JsonDocument doc;
deserializeJson(doc, payload);
if (doc["session"].is<String>()) {
sessionId = doc["session"].as<String>();
sessionValid = true;
saveSessionToEEPROM(sessionId);
Serial.printf("[SESSION] Got session: %s\n", sessionId.c_str());
}
}
http.end();
}
delete client;
}
void updateDeviceState() {
if (WiFi.status() != WL_CONNECTED) return;
if (!deviceRegistered) return;
if (currentState != STATE_REGISTERED) return;
readSensor();
WiFiClientSecure* client = getSecureClient();
HTTPClient http;
String url = "https://" + String(serverHost) + "/api/device/state/";
if (sessionValid && sessionId.length() > 0) {
url += "?session=" + sessionId;
} else if (permanentToken.length() > 0) {
url += "?token=" + permanentToken;
} else {
delete client;
return;
}
url += "&device_id=" + deviceId;
url += "&s=" + String(packRelays(), HEX);
int tempInt = (int)(temperature * 10);
url += "&t=" + String(tempInt);
if (humidity >= 0 && humidity <= 100) {
url += "&h=" + String((int)humidity);
}
url += "&ip=" + WiFi.localIP().toString();
if (http.begin(*client, url)) {
http.setTimeout(5000);
int code = http.GET();
if (code == 200) {
String payload = http.getString();
JsonDocument doc;
deserializeJson(doc, payload);
if (doc["session"].is<String>()) {
sessionId = doc["session"].as<String>();
sessionValid = true;
saveSessionToEEPROM(sessionId);
}
// Process relay states independently
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 -> %s\n", relayNames[i], newState ? "ON" : "OFF");
}
}
}
// Also handle packed format as backup
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);
}
}
// Brief blink to show activity
digitalWrite(LED_PIN, LOW);
delay(20);
digitalWrite(LED_PIN, HIGH);
} else if (code == 401) {
Serial.println("[STATE] Token invalid");
deviceRegistered = false;
sessionValid = false;
permanentToken = "";
sessionId = "";
clearAllStorage();
currentState = STATE_PROVISIONING;
}
http.end();
}
delete client;
}
void setup() {
Serial.begin(115200);
delay(2000);
Serial.println("");
Serial.println("########################################");
Serial.println("# OceanicRemote v5.1 - ESP8266 D1 Mini #");
Serial.println("# D6 REPLACES D4 - NO LED CONFLICT #");
Serial.println("########################################");
initEEPROM();
randomSeed(analogRead(A0) + micros());
deviceId = generateDeviceId();
initPins();
Serial.printf("[SYSTEM] Device ID: %s\n", deviceId.c_str());
Serial.printf("[SYSTEM] Server: %s\n", serverHost);
Serial.printf("[SYSTEM] WiFi SSID: %s\n", WIFI_SSID);
Serial.printf("[SYSTEM] Polling: %d ms\n", BASE_POLL_INTERVAL);
String savedToken = loadTokenFromEEPROM();
if (savedToken.length() > 0) {
permanentToken = savedToken;
deviceRegistered = true;
currentState = STATE_REGISTERED;
Serial.printf("[BOOT] Found saved token\n");
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 token - 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 - restarting");
delay(5000);
ESP.restart();
}
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...");
}
void loop() {
if (currentState == STATE_ERROR) {
delay(5000);
return;
}
unsigned long currentMillis = millis();
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 (currentMillis - lastWiFiReconnect >= wifiReconnectInterval) {
lastWiFiReconnect = currentMillis;
if (wifiRetryCount < MAX_WIFI_RETRIES) {
Serial.print("[WiFi] Reconnecting... ");
connectToWiFi();
} else {
Serial.println("[WiFi] Max retries - restarting");
delay(1000);
ESP.restart();
}
}
}
delay(10);
}