#ifndef GLOBAL_H
#define GLOBAL_H
#include <Arduino.h>
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <EEPROM.h>
#include "Config.h"
// -------------------- EEPROM layout (ядро) --------------------
// 0..31 - SSID (макс 32 байта, включая '\0')
// 32..95 - PASS (макс 64 байта, включая '\0')
// 96 - deviceMode (1 байт)
// 97..128 - deviceName (макс 32 байта, включая '\0')
// 129..192 - authToken (макс 64 байта, включая '\0')
// 193..224 - server IP/host (макс 32 байта, включая '\0')
// 225..??? - свободно для устройств
const uint16_t SSID_ADDR = 0;
const uint16_t SSID_MAX_LEN = 32;
const uint16_t PASS_ADDR = SSID_ADDR + SSID_MAX_LEN; // 32
const uint16_t PASS_MAX_LEN = 64; // 32..95
const uint16_t DEVICE_MODE_ADDR = PASS_ADDR + PASS_MAX_LEN; // 96
const uint16_t DEVICE_NAME_ADDR = DEVICE_MODE_ADDR + 1; // 97
const uint16_t DEVICE_NAME_MAX_LEN = 32; // 97..128
const uint16_t AUTH_TOKEN_ADDR = DEVICE_NAME_ADDR + DEVICE_NAME_MAX_LEN; // 129
const uint16_t AUTH_TOKEN_MAX_LEN = 64; // 129..192
const uint16_t SERVER_ADDR = AUTH_TOKEN_ADDR + AUTH_TOKEN_MAX_LEN; // 193
const uint16_t SERVER_MAX_LEN = 32; // 193..224
// Конец блока ядра (первый свободный адрес для устройства)
const uint16_t CORE_EEPROM_END = SERVER_ADDR + SERVER_MAX_LEN; // 225
// Общий размер EEPROM, с запасом для устройств.
// Для ESP8266 обычно можно спокойно брать 512 или 1024.
const uint16_t EEPROM_SIZE = 512;
// Диапазон EEPROM, свободный для устройств:
const uint16_t DEVICE_EEPROM_START = CORE_EEPROM_END; // 225
const uint16_t DEVICE_EEPROM_SIZE = EEPROM_SIZE - DEVICE_EEPROM_START;
// -------------------- Глобальные объекты ядра --------------------
ESP8266WebServer server(80);
// WiFi-конфиг, прочитанный из EEPROM
String savedSSID = "";
String savedPASS = "";
// -------------------- Device mode & config --------------------
enum DeviceMode : uint8_t {
DEVICE_MODE_SETUP = 0,
DEVICE_MODE_NORMAL = 1,
DEVICE_MODE_ERROR = 2,
DEVICE_MODE_UPDATING = 3
};
// Глобальная конфигурация устройства (живёт в RAM, но сохраняется в EEPROM)
DeviceMode deviceMode = DEVICE_MODE_SETUP; // по умолчанию setup
String deviceName = "Relay 1"; // дефолтное имя
String serverBaseUrl = "";
String authToken = ""; // токен авторизации
// Эти функции реализованы в других .h (WebHandlers / REST_API),
// но вызываются из ядра:
void registerWebUiRoutes();
void registerRestApiRoutes();
// -------------------- helpers для EEPROM --------------------
inline uint16_t getDeviceEepromStart() {
return DEVICE_EEPROM_START;
}
inline uint16_t getDeviceEepromSize() {
return DEVICE_EEPROM_SIZE;
}
String readStringFromEEPROM(uint16_t addr, uint16_t maxLen) {
char buf[100];
if (maxLen > sizeof(buf)) maxLen = sizeof(buf);
uint8_t first = EEPROM.read(addr);
// "чистая" флешка или пустая строка → считаем, что значения нет
if (first == 0xFF || first == 0x00) {
return String("");
}
buf[0] = first;
for (uint16_t i = 1; i < maxLen; i++) {
uint8_t b = EEPROM.read(addr + i);
if (b == 0xFF || b == 0x00) {
buf[i] = '\0';
return String(buf);
}
buf[i] = b;
}
buf[maxLen - 1] = '\0';
return String(buf);
}
inline void writeStringToEEPROM(uint16_t addr, const String &str, uint16_t maxLen) {
uint16_t len = str.length();
if (len >= maxLen) len = maxLen - 1;
for (uint16_t i = 0; i < len; i++) {
EEPROM.write(addr + i, str[i]);
}
EEPROM.write(addr + len, 0);
for (uint16_t i = len + 1; i < maxLen; i++) {
EEPROM.write(addr + i, 0);
}
}
inline void saveWiFiConfig(const String &ssid, const String &pass) {
EEPROM.begin(EEPROM_SIZE);
writeStringToEEPROM(SSID_ADDR, ssid, SSID_MAX_LEN);
writeStringToEEPROM(PASS_ADDR, pass, PASS_MAX_LEN);
EEPROM.commit();
EEPROM.end();
}
inline void loadWiFiConfig() {
EEPROM.begin(EEPROM_SIZE);
savedSSID = readStringFromEEPROM(SSID_ADDR, SSID_MAX_LEN);
savedPASS = readStringFromEEPROM(PASS_ADDR, PASS_MAX_LEN);
EEPROM.end();
}
// -------------------- Device config save/load --------------------
inline void loadDeviceConfig() {
EEPROM.begin(EEPROM_SIZE);
uint8_t rawMode = EEPROM.read(DEVICE_MODE_ADDR);
String name = readStringFromEEPROM(DEVICE_NAME_ADDR, DEVICE_NAME_MAX_LEN);
String token = readStringFromEEPROM(AUTH_TOKEN_ADDR, AUTH_TOKEN_MAX_LEN);
String server = readStringFromEEPROM(SERVER_ADDR, SERVER_MAX_LEN);
EEPROM.end();
if (rawMode <= DEVICE_MODE_UPDATING) {
deviceMode = static_cast<DeviceMode>(rawMode);
} else {
deviceMode = DEVICE_MODE_SETUP;
authToken = "";
serverBaseUrl = "";
}
if (deviceMode == DEVICE_MODE_SETUP) {
name = deviceName;
} else {
if (name.length() > 0 && name[0] > 31 && name[0] < 127) {
} else {
name = deviceName;
}
}
if (name.length() > 0) deviceName = name;
if (token.length() > 0) authToken = token;
if (server.length() > 0) serverBaseUrl = server;
}
inline void saveDeviceConfig() {
EEPROM.begin(EEPROM_SIZE);
EEPROM.write(DEVICE_MODE_ADDR, static_cast<uint8_t>(deviceMode));
writeStringToEEPROM(DEVICE_NAME_ADDR, deviceName, DEVICE_NAME_MAX_LEN);
writeStringToEEPROM(AUTH_TOKEN_ADDR, authToken, AUTH_TOKEN_MAX_LEN);
writeStringToEEPROM(SERVER_ADDR, serverBaseUrl, SERVER_MAX_LEN);
EEPROM.commit();
EEPROM.end();
}
// -------------------- WiFi helpers --------------------
inline bool tryConnectWiFi() {
if (savedSSID.length() == 0) {
Serial.println(F("No saved SSID, skipping STA connect"));
return false;
}
Serial.print(F("Connecting to WiFi SSID: "));
Serial.println(savedSSID);
WiFi.mode(WIFI_STA);
WiFi.begin(savedSSID.c_str(), savedPASS.c_str());
unsigned long start = millis();
const unsigned long timeout = 15000; // 15 сек
while (WiFi.status() != WL_CONNECTED && millis() - start < timeout) {
delay(500);
Serial.print(".");
}
Serial.println();
if (WiFi.status() == WL_CONNECTED) {
Serial.print(F("WiFi connected, IP: "));
Serial.println(WiFi.localIP());
return true;
} else {
Serial.println(F("WiFi connect failed"));
return false;
}
}
inline void startAPMode() {
WiFi.mode(WIFI_AP);
String apSSID = "ESP-RELAY-";
apSSID += String(ESP.getChipId(), HEX);
const char* apPass = "noAccess";
bool ok = WiFi.softAP(apSSID.c_str(), apPass);
if (ok) {
Serial.print(F("AP started, SSID: "));
Serial.println(apSSID);
Serial.print(F("AP IP: "));
Serial.println(WiFi.softAPIP());
} else {
Serial.println(F("AP start FAILED"));
}
}
// -------------------- Utils --------------------
inline String getUniqueID() {
return String(ESP.getChipId(), HEX);
}
inline String getMAC() {
uint8_t mac[6];
WiFi.macAddress(mac);
char buf[13];
sprintf(buf, "%02X%02X%02X%02X%02X%02X",
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
return String(buf);
}
// -------------------- Core setup / loop --------------------
inline void coreSetup() {
Serial.begin(115200);
delay(1200);
Serial.println();
Serial.println(F("Booting..."));
Serial.print(F("Firmware: "));
Serial.println(FW_VERSION);
// читаем WiFi-конфиг и состояние реле из EEPROM
loadWiFiConfig();
loadDeviceConfig();
Serial.print(F("Saved SSID: "));
Serial.println(savedSSID);
// пытаемся подключиться, иначе AP
if (!(savedSSID.length() > 0 && tryConnectWiFi())) {
startAPMode();
}
// регистрируем роуты веб-панели и REST API
registerWebUiRoutes();
registerRestApiRoutes();
server.begin();
Serial.println(F("HTTP server started"));
}
inline void coreLoop() {
server.handleClient();
// авто-реконнект WiFi
if (savedSSID.length() > 0 &&
WiFi.getMode() == WIFI_STA &&
WiFi.status() != WL_CONNECTED)
{
static uint32_t lastReconnectAttempt = 0;
if (millis() - lastReconnectAttempt > 10000UL) {
lastReconnectAttempt = millis();
tryConnectWiFi();
}
}
}
#endif // GLOBAL_H