#ifndef HATCH_LOGIC_H
#define HATCH_LOGIC_H
#include <Arduino.h>
// -------------------- Каналы --------------------
// Канал 0: пин "открыть"
// Канал 1: пин "закрыть", SH_CH_FEEDBACK — концевик "закрыто"
#define HATCH_CH_OPEN 0
#define HATCH_CH_CLOSE 1
// -------------------- Силовое реле (хардкод) --------------------
// Реле безопасности: коммутирует +питание к приводу.
// Включается ПОСЛЕ выбора сигнального канала,
// выключается ДО снятия сигнального канала.
// GPIO 26 — безопасный пин на ESP32 WROOM/DevKit, не влияет на boot.
extern uint8_t HATCH_POWER_RELAY_PIN;
static constexpr uint8_t HATCH_POWER_RELAY_ACTIVE = HIGH;
static constexpr uint8_t HATCH_POWER_RELAY_IDLE = LOW;
inline void hatch_power_relay(bool on) {
if (HATCH_POWER_RELAY_PIN == SH_PIN_UNUSED) return;
digitalWrite(HATCH_POWER_RELAY_PIN, on ? HATCH_POWER_RELAY_ACTIVE : HATCH_POWER_RELAY_IDLE);
}
// -------------------- Ограничения --------------------
static constexpr float HATCH_LIMIT_MAX = 35.0f;
static constexpr float HATCH_LIMIT_MIN = 0.0f;
static constexpr float HATCH_CALIB_OVERDRIVE = 0.1f;
// -------------------- Состояние люка --------------------
extern float hatchPosition;
// -------------------- State machine --------------------
enum HatchState : uint8_t {
HATCH_STATE_IDLE = 0,
HATCH_STATE_CALIBRATING,
HATCH_STATE_OPENING,
HATCH_STATE_CLOSING,
};
enum HatchActionResult : uint8_t {
HATCH_OK = 0,
HATCH_ERR_ALREADY_AT_LIMIT,
HATCH_ERR_ALREADY_CLOSED,
HATCH_ERR_CALIBRATION_FAILED,
HATCH_ERR_INVALID_TIME,
HATCH_ERR_BUSY,
};
static HatchState _hatchState = HATCH_STATE_IDLE;
static uint32_t _hatchMoveStart = 0;
static uint32_t _hatchMoveDuration = 0;
static float _hatchMoveTarget = 0.0f;
static float _hatchOpenAfterCalib = 0.0f;
// -------------------- Низкоуровневое управление пинами --------------------
inline void hatch_pin_open(bool on) {
uint8_t pin = sh_channel_pin(HATCH_CH_OPEN);
if (pin == SH_PIN_UNUSED) return;
bool physical = on ^ sh_channel_is_inverted(HATCH_CH_OPEN);
digitalWrite(pin, physical ? HIGH : LOW);
}
inline void hatch_pin_close(bool on) {
uint8_t pin = sh_channel_pin(HATCH_CH_CLOSE);
if (pin == SH_PIN_UNUSED) return;
bool physical = on ^ sh_channel_is_inverted(HATCH_CH_CLOSE);
digitalWrite(pin, physical ? HIGH : LOW);
}
inline bool hatch_read_limit_switch() {
uint8_t pin = sh_channel_feedback_pin(HATCH_CH_CLOSE);
if (pin == SH_PIN_UNUSED) return false;
return (digitalRead(pin) == LOW);
}
// -------------------- Отправка событий --------------------
static void hatch_send_event(const String &event_name, const String &data_json) {
if (serverBaseUrl.length() == 0 || serverBaseUrl == "0.0.0.0") return;
if (authToken.length() == 0) return;
String body = "{";
body += "\"device_type\":\"hatch\",";
body += "\"device_id\":\"" + getUniqueID() + "\",";
body += "\"event_name\":\"" + event_name + "\",";
body += "\"data\":" + data_json;
body += "}";
int http_code = -1;
core_post_json_to_server("/events/new", body, 1500, http_code);
}
// -------------------- Сохранение/загрузка позиции --------------------
extern uint16_t HATCH_EEPROM_ADDR;
inline void hatchSavePosition() {
EEPROM.begin(EEPROM_SIZE);
EEPROM.put(HATCH_EEPROM_ADDR, hatchPosition);
EEPROM.commit();
EEPROM.end();
}
inline void hatchLoadPosition() {
EEPROM.begin(EEPROM_SIZE);
EEPROM.get(HATCH_EEPROM_ADDR, hatchPosition);
EEPROM.end();
if (isnan(hatchPosition) || hatchPosition < HATCH_LIMIT_MIN || hatchPosition > HATCH_LIMIT_MAX) {
hatchPosition = 0.0f;
hatchSavePosition();
}
}
// -------------------- Остановка --------------------
static void hatch_stop_all() {
hatch_power_relay(false);
hatch_pin_open(false);
hatch_pin_close(false);
_hatchState = HATCH_STATE_IDLE;
}
// -------------------- Запуск движения --------------------
static void hatch_start_open(float targetPos) {
float actualSec = targetPos - hatchPosition;
if (actualSec <= 0.0f) return;
Serial.print(F("[hatch_start_open] pos="));
Serial.print(hatchPosition, 2);
Serial.print(F(" target="));
Serial.print(targetPos, 2);
Serial.print(F(" actualSec="));
Serial.println(actualSec, 2);
_hatchMoveTarget = targetPos;
_hatchMoveDuration = (uint32_t)(actualSec * 1000.0f);
_hatchMoveStart = millis();
_hatchState = HATCH_STATE_OPENING;
hatch_pin_open(true);
hatch_power_relay(true);
}
static void hatch_start_close(float targetPos) {
float actualSec = hatchPosition - targetPos;
if (actualSec <= 0.0f) return;
_hatchMoveTarget = targetPos;
_hatchMoveDuration = (uint32_t)(actualSec * 1000.0f);
_hatchMoveStart = millis();
_hatchState = HATCH_STATE_CLOSING;
hatch_pin_close(true);
hatch_power_relay(true);
}
static void hatch_start_calibrate(float openAfter) {
float maxCloseTime = hatchPosition + HATCH_CALIB_OVERDRIVE;
if (maxCloseTime < HATCH_CALIB_OVERDRIVE) maxCloseTime = HATCH_CALIB_OVERDRIVE;
_hatchMoveDuration = (uint32_t)(maxCloseTime * 1000.0f);
_hatchMoveStart = millis();
_hatchOpenAfterCalib = openAfter;
_hatchState = HATCH_STATE_CALIBRATING;
hatch_pin_close(true);
hatch_power_relay(true);
}
// -------------------- Tick (вызывать из loop) --------------------
inline void hatchTick() {
if (_hatchState == HATCH_STATE_IDLE) return;
uint32_t elapsed = millis() - _hatchMoveStart;
bool timeout = (elapsed >= _hatchMoveDuration);
// --- CALIBRATING ---
if (_hatchState == HATCH_STATE_CALIBRATING) {
if (hatch_read_limit_switch()) {
hatch_stop_all();
hatchPosition = 0.0f;
hatchSavePosition();
hatch_send_event("limit_switch_activated", "{}");
if (_hatchOpenAfterCalib > 0.0f) {
float target = _hatchOpenAfterCalib;
if (target > HATCH_LIMIT_MAX) target = HATCH_LIMIT_MAX;
hatch_start_open(target);
}
return;
}
if (timeout) {
hatch_stop_all();
hatchPosition = 0.0f;
hatchSavePosition();
hatch_send_event("calibration_failed", "{}");
}
return;
}
// --- OPENING ---
if (_hatchState == HATCH_STATE_OPENING) {
if (timeout) {
hatch_stop_all();
hatchPosition = _hatchMoveTarget;
hatchSavePosition();
}
return;
}
// --- CLOSING ---
if (_hatchState == HATCH_STATE_CLOSING) {
if (hatch_read_limit_switch()) {
hatch_stop_all();
hatchPosition = 0.0f;
hatchSavePosition();
hatch_send_event("limit_switch_activated", "{}");
return;
}
if (timeout) {
hatch_stop_all();
hatchPosition = _hatchMoveTarget;
hatchSavePosition();
}
return;
}
}
// -------------------- Публичные функции --------------------
inline bool hatchIsBusy() {
return _hatchState != HATCH_STATE_IDLE;
}
inline HatchState hatchGetState() {
return _hatchState;
}
inline HatchActionResult hatchOpen(float timeSec) {
if (timeSec <= 0.0f) return HATCH_ERR_INVALID_TIME;
if (hatchIsBusy()) return HATCH_ERR_BUSY;
if (hatchPosition >= HATCH_LIMIT_MAX) return HATCH_ERR_ALREADY_AT_LIMIT;
float targetPos = hatchPosition + timeSec;
if (targetPos > HATCH_LIMIT_MAX) targetPos = HATCH_LIMIT_MAX;
if (hatch_read_limit_switch()) {
hatchPosition = 0.0f;
hatchSavePosition();
hatch_send_event("limit_switch_activated", "{}");
hatch_start_open(targetPos);
return HATCH_OK;
}
if (hatchPosition == 0.0f) {
hatch_start_calibrate(targetPos);
return HATCH_OK;
}
hatch_start_open(targetPos);
return HATCH_OK;
}
inline HatchActionResult hatchClose(float timeSec) {
if (timeSec <= 0.0f) return HATCH_ERR_INVALID_TIME;
if (hatchIsBusy()) return HATCH_ERR_BUSY;
if (hatchPosition <= HATCH_LIMIT_MIN) return HATCH_ERR_ALREADY_CLOSED;
float targetPos = hatchPosition - timeSec;
if (targetPos < HATCH_LIMIT_MIN) targetPos = HATCH_LIMIT_MIN;
hatch_start_close(targetPos);
return HATCH_OK;
}
#endif // HATCH_LOGIC_H