Newer
Older
smart-home-server / devices / relay / relay_esp8266 / Global.h
#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