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

const char* DEVICE_TYPE = "hatch";
const char* FW_VERSION  = "1.0 dev";
const uint8_t CHANNEL_NUM = 2;  // 0: открыть, 1: закрыть

#include <sh_core.h>
#include "HatchLogic.h"

// -------------------- EEPROM --------------------
const uint16_t HATCH_EEPROM_BASE = getDeviceEepromStart();
uint16_t HATCH_EEPROM_ADDR = HATCH_EEPROM_BASE;  // float, 4 байта

// -------------------- Состояние --------------------
float hatchPosition = 0.0f;

// -------------------- JSON helpers --------------------

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);
}

static float extractJsonFloatValue(const String &body, const String &key) {
  String pattern = "\"" + key + "\"";
  int keyIndex = body.indexOf(pattern);
  if (keyIndex < 0) return -1.0f;
  int colonIndex = body.indexOf(':', keyIndex);
  if (colonIndex < 0) return -1.0f;

  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++;

  String numStr = "";
  while (i < (int)body.length() &&
         (body[i] == '-' || body[i] == '.' || (body[i] >= '0' && body[i] <= '9'))) {
    numStr += body[i++];
  }
  if (numStr.length() == 0) return -1.0f;
  return numStr.toFloat();
}

// -------------------- appendStatusJsonFields --------------------

void appendStatusJsonFields(String &json) {
  uint8_t pct = (uint8_t)((hatchPosition / HATCH_LIMIT_MAX) * 100.0f + 0.5f);
  if (pct > 100) pct = 100;

  json += ",\"hatch\":{";
  json += "\"state\":\"";
  json += (hatchPosition > 0.0f) ? "open" : "closed";
  json += "\",";
  json += "\"position_sec\":";
  json += String(hatchPosition, 2);
  json += ",\"position_pct\":";
  json += String(pct);
  json += "}";
}

// -------------------- appendAboutJsonFields --------------------

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

// -------------------- deviceHandleAction --------------------

bool deviceHandleAction(const String &action,
                        const String &paramsJson,
                        String &errorCode,
                        String &errorMessage)
{
  if (action == "open") {
    float timeSec = extractJsonFloatValue(paramsJson, "time");
    if (timeSec <= 0.0f) {
      errorCode    = "IllegalActionOrParams";
      errorMessage = "Parameter 'time' must be > 0";
      return false;
    }

    HatchActionResult res = hatchOpen(timeSec);

    switch (res) {
      case HATCH_OK:
        return true;

      case HATCH_ERR_ALREADY_AT_LIMIT:
        errorCode    = "IllegalActionOrParams";
        errorMessage = "Hatch is already fully open";
        return false;

      case HATCH_ERR_CALIBRATION_FAILED:
        errorCode    = "CalibrationFailed";
        errorMessage = "Limit switch did not trigger during calibration";
        return false;

      default:
        errorCode    = "IllegalActionOrParams";
        errorMessage = "Open failed";
        return false;
    }
  }

  if (action == "close") {
    float timeSec = extractJsonFloatValue(paramsJson, "time");
    if (timeSec <= 0.0f) {
      errorCode    = "IllegalActionOrParams";
      errorMessage = "Parameter 'time' must be > 0";
      return false;
    }

    HatchActionResult res = hatchClose(timeSec);

    switch (res) {
      case HATCH_OK:
        return true;

      case HATCH_ERR_ALREADY_CLOSED:
        errorCode    = "IllegalActionOrParams";
        errorMessage = "Hatch is already closed";
        return false;

      default:
        errorCode    = "IllegalActionOrParams";
        errorMessage = "Close failed";
        return false;
    }
  }

  errorCode    = "IllegalActionOrParams";
  errorMessage = "Unknown action. Supported: open, close";
  return false;
}

// -------------------- deviceHandleReset --------------------

void deviceHandleReset() {
  hatchPosition = 0.0f;
  hatchSavePosition();

  // Сбрасываем EEPROM устройства
  EEPROM.begin(EEPROM_SIZE);
  for (uint16_t addr = DEVICE_EEPROM_START; addr < EEPROM_SIZE; addr++) {
    EEPROM.write(addr, 0xFF);
  }
  EEPROM.commit();
  EEPROM.end();
}

// -------------------- setup / loop --------------------

void setup() {
  coreSetup();

  // Инициализация пинов управления
  for (uint8_t ch = 0; ch < CHANNEL_NUM; ch++) {
    uint8_t pin = sh_channel_pin(ch);
    if (pin == SH_PIN_UNUSED) continue;
    pinMode(pin, OUTPUT);
    digitalWrite(pin, LOW);
  }

  // Инициализация пина концевика
  uint8_t fbPin = sh_channel_feedback_pin(HATCH_CH_CLOSE);
  if (fbPin != SH_PIN_UNUSED) {
    pinMode(fbPin, INPUT_PULLDOWN);
  }

  // Загрузка сохранённой позиции
  hatchLoadPosition();

  Serial.print(F("Hatch position loaded: "));
  Serial.print(hatchPosition, 2);
  Serial.println(F(" sec"));
}

void loop() {
  coreLoop();
}