Newer
Older
smart-home-server / devices / relay / relay_esp8266 / relay_esp8266.ino
#include <Arduino.h>

const char* DEVICE_TYPE = "relay";
const char* FW_VERSION = "1.0.2 beta";
const uint8_t CHANNEL_NUM = 1;

#include <sh_core_esp8266.h>
#include "RelayLogic.h"   // вот тут находятся каналы и функции управления

// --------------------- УСТРОЙСТВЕННЫЙ КОНФИГ ---------------------

const uint16_t RELAY_EEPROM_BASE = getDeviceEepromStart();

// если когда-нибудь понадобится хранить состояние каналов:
const uint16_t RELAY_STATE_ADDR = RELAY_EEPROM_BASE;        // например
// const uint16_t RELAY_OTHER_ADDR = RELAY_EEPROM_BASE + 16;


// Количество каналов уже задано в Config.h через CHANNEL_NUM.
// Здесь просто указываем, какие пины у каждого канала.
// Для одноканального ESP-01S, где реле сидит на GPIO0:
const uint8_t RELAY_PINS[CHANNEL_NUM] = { 0 };

// Состояние каналов (по умолчанию все выключены)
bool channelState[CHANNEL_NUM] = { false };

// Флаг инверта для каналов
const bool RELAY_INVERT[CHANNEL_NUM] = {
  true  // если нужно инвертировать канал
  // false  ← если модуль обычный, без инверта
};

// ---------------------------------------------------------------

void relayLoadStates() {
  EEPROM.begin(EEPROM_SIZE);

  for (uint8_t ch = 0; ch < CHANNEL_NUM; ch++) {
    uint8_t v = EEPROM.read(RELAY_STATE_ADDR + ch);

    // 0xFF считаем "ничего не записано" → по умолчанию выкл
    if (v == 0xFF) {
      channelState[ch] = false;
    } else {
      channelState[ch] = (v != 0);
    }
  }

  EEPROM.end();

  // применяем к железу
  for (uint8_t ch = 0; ch < CHANNEL_NUM; ch++) {
    applyChannelState(ch, channelState[ch]);
  }
}

void relaySaveStates() {
  EEPROM.begin(EEPROM_SIZE);

  for (uint8_t ch = 0; ch < CHANNEL_NUM; ch++) {
    EEPROM.write(RELAY_STATE_ADDR + ch, channelState[ch] ? 1 : 0);
  }

  EEPROM.commit();
  EEPROM.end();
}


void appendStatusJsonFields(String &json) {
  // Базовое поле "status":"ok" уже добавлено в REST_API.h,
  // мы лишь дополняем JSON полем channels.

  json += ",\"channels\":[";
  for (uint8_t ch = 0; ch < CHANNEL_NUM; ch++) {
    if (ch > 0) json += ",";

    json += "{";
    json += "\"id\":" + String(ch) + ",";
    json += "\"state\":\"";
    json += getChannelState(ch) ? "on" : "off";
    json += "\",\"inverted\":";
    json += RELAY_INVERT[ch] ? "true" : "false";
    json += "}";
  }
  json += "]";
}


void appendAboutJsonFields(String &json) {
  json += ",\"channels\":" + String(CHANNEL_NUM);
}

static String extractJsonStringValue(const String &body, const String &key) {
  String pattern = "\"" + key + "\"";
  int keyIndex = body.indexOf(pattern);
  if (keyIndex < 0) return "";

  int colonIndex = body.indexOf(':', keyIndex);
  if (colonIndex < 0) return "";

  int firstQuote = body.indexOf('"', colonIndex + 1);
  if (firstQuote < 0) return "";

  int secondQuote = body.indexOf('"', firstQuote + 1);
  if (secondQuote < 0) return "";

  return body.substring(firstQuote + 1, secondQuote);
}

inline int extractJsonIntValue(const String &body, const String &key) {
  String pattern = "\"" + key + "\"";
  int keyIndex = body.indexOf(pattern);
  if (keyIndex < 0) return -1;

  int colonIndex = body.indexOf(':', keyIndex);
  if (colonIndex < 0) return -1;

  int i = colonIndex + 1;

  // пропускаем пробелы
  while (i < (int)body.length() &&
         (body[i] == ' ' || body[i] == '\t' || body[i] == '\n' || body[i] == '\r')) {
    i++;
  }

  // на случай, если числа вдруг передадутся в кавычках
  bool quoted = (i < (int)body.length() && body[i] == '"');
  if (quoted) i++;

  int sign = 1;
  if (i < (int)body.length() && body[i] == '-') {
    sign = -1;
    i++;
  }

  long value = 0;
  bool anyDigit = false;
  while (i < (int)body.length() && body[i] >= '0' && body[i] <= '9') {
    anyDigit = true;
    value = value * 10 + (body[i] - '0');
    i++;
  }

  if (!anyDigit) return -1;
  return (int)(value * sign);
}


bool deviceHandleAction(const String &action,
                        const String &paramsJson,
                        String &errorCode,
                        String &errorMessage)
{
  if (action == "set_state") {
    String state = extractJsonStringValue(paramsJson, "state");

    if (state != "on" && state != "off") {
      errorCode = "IllegalActionOrParams";
      errorMessage = "Invalid state";
      return false;
    }

    setOn(state == "on");
    relaySaveStates();
    return true;
  }

  if (action == "set_channel_state") {
    // Канал
    int ch = extractJsonIntValue(paramsJson, "channel");
    if (ch < 0 || ch >= CHANNEL_NUM) {
      errorCode    = "IllegalActionOrParams";
      errorMessage = "Invalid channel index";
      return false;
    }

    // State
    String state = extractJsonStringValue(paramsJson, "state");
    state.toLowerCase();
    if (state != "on" && state != "off") {
      errorCode    = "IllegalActionOrParams";
      errorMessage = "Invalid state";
      return false;
    }

    bool newState = (state == "on");
    setChannelState(ch, newState);
    relaySaveStates();
    return true;
  }

  if (action == "toggle_channel") {
    int ch = extractJsonIntValue(paramsJson, "channel");
    if (ch < 0 || ch >= CHANNEL_NUM) {
      errorCode    = "IllegalActionOrParams";
      errorMessage = "Invalid channel index";
      return false;
    }

    bool newState = !getChannelState(ch);
    setChannelState(ch, newState);
    relaySaveStates();
    return true;
  }


  errorCode = "IllegalActionOrParams";
  errorMessage = "Unknown action";
  return false;
}

void deviceHandleReset() {
  for (uint8_t ch = 0; ch < CHANNEL_NUM; ch++) {
    channelState[ch] = false;
    applyChannelState(ch, false);
  }

  EEPROM.begin(EEPROM_SIZE);
  for (uint16_t addr = DEVICE_EEPROM_START; addr < EEPROM_SIZE; addr++) {
    EEPROM.write(addr, 0xFF);
  }
  EEPROM.commit();
  EEPROM.end();
}


void setup() {
  for (int i = 0; i < CHANNEL_NUM; i++) {
    pinMode(RELAY_PINS[i], OUTPUT);
  }

  relayLoadStates();

  coreSetup();
}

void loop() {
  coreLoop();
}