#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
// -------------------- Ограничения --------------------
static constexpr float HATCH_LIMIT_MAX = 35.0f; // макс. открытие (сек)
static constexpr float HATCH_LIMIT_MIN = 0.0f; // полностью закрыто
static constexpr float HATCH_CALIB_OVERDRIVE = 0.1f; // доп. время закрытия при калибровке
// totalValue не опускается ниже -HATCH_CALIB_OVERDRIVE
// -------------------- Состояние люка --------------------
extern float hatchPosition; // текущая позиция в секундах [0 .. HATCH_LIMIT_MAX]
// -------------------- Низкоуровневое управление пинами --------------------
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);
}
// Считать концевик "закрыто" (feedback канала 1)
// Возвращает true если люк в положении "закрыто"
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) == HIGH);
}
// -------------------- Отправка событий --------------------
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; // задаётся в .ino
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();
}
}
// -------------------- Основная логика движения --------------------
// Закрываем люк до срабатывания концевика.
// Максимальное время закрытия: (hatchPosition + HATCH_CALIB_OVERDRIVE) сек.
// Возвращает true если концевик сработал (калибровка успешна).
inline bool hatchCalibrate() {
float maxCloseTime = hatchPosition + HATCH_CALIB_OVERDRIVE;
if (maxCloseTime < HATCH_CALIB_OVERDRIVE) maxCloseTime = HATCH_CALIB_OVERDRIVE;
uint32_t duration_ms = (uint32_t)(maxCloseTime * 1000.0f);
uint32_t start = millis();
hatch_pin_close(true);
bool switched = false;
while (millis() - start < duration_ms) {
if (hatch_read_limit_switch()) {
switched = true;
break;
}
delay(10);
}
hatch_pin_close(false);
if (switched) {
hatchPosition = 0.0f;
hatchSavePosition();
hatch_send_event("limit_switch_activated", "{}");
}
return switched;
}
// Открыть люк на заданное время (с учётом текущей позиции и лимита).
// Возвращает реально затраченное время.
inline float hatchDoOpen(float requestedSec) {
float newPos = hatchPosition + requestedSec;
if (newPos > HATCH_LIMIT_MAX) newPos = HATCH_LIMIT_MAX;
float actualSec = newPos - hatchPosition;
if (actualSec <= 0.0f) return 0.0f;
hatch_pin_open(true);
delay((uint32_t)(actualSec * 1000.0f));
hatch_pin_open(false);
hatchPosition = newPos;
hatchSavePosition();
return actualSec;
}
// Закрыть люк на заданное время (с учётом текущей позиции и лимита 0).
// При срабатывании концевика — останавливаем досрочно.
inline float hatchDoClose(float requestedSec) {
float newPos = hatchPosition - requestedSec;
if (newPos < HATCH_LIMIT_MIN) newPos = HATCH_LIMIT_MIN;
float actualSec = hatchPosition - newPos;
if (actualSec <= 0.0f) return 0.0f;
uint32_t duration_ms = (uint32_t)(actualSec * 1000.0f);
uint32_t start = millis();
hatch_pin_close(true);
while (millis() - start < duration_ms) {
if (hatch_read_limit_switch()) {
hatch_pin_close(false);
hatchPosition = 0.0f;
hatchSavePosition();
hatch_send_event("limit_switch_activated", "{}");
return (float)(millis() - start) / 1000.0f;
}
delay(10);
}
hatch_pin_close(false);
hatchPosition = newPos;
hatchSavePosition();
return actualSec;
}
// -------------------- Высокоуровневые команды --------------------
// Результат выполнения команды
enum HatchActionResult : uint8_t {
HATCH_OK = 0,
HATCH_ERR_ALREADY_AT_LIMIT, // уже на максимуме
HATCH_ERR_ALREADY_CLOSED, // уже закрыт
HATCH_ERR_CALIBRATION_FAILED, // концевик не сработал при калибровке
HATCH_ERR_INVALID_TIME, // некорректное время
};
inline HatchActionResult hatchOpen(float timeSec) {
if (timeSec <= 0.0f) return HATCH_ERR_INVALID_TIME;
// Уже на максимуме
if (hatchPosition >= HATCH_LIMIT_MAX) return HATCH_ERR_ALREADY_AT_LIMIT;
// Если концевик активен — мы точно в 0
if (hatch_read_limit_switch()) {
hatchPosition = 0.0f;
hatchSavePosition();
hatch_send_event("limit_switch_activated", "{}");
hatchDoOpen(timeSec);
return HATCH_OK;
}
// Люк закрыт (по учёту), но концевик не активен — нужна калибровка
if (hatchPosition == 0.0f) {
bool ok = hatchCalibrate();
if (!ok) {
hatch_send_event("calibration_failed", "{}");
return HATCH_ERR_CALIBRATION_FAILED;
}
hatchDoOpen(timeSec);
return HATCH_OK;
}
// Люк уже открыт — просто открываем дальше (min(pos + time, 35))
hatchDoOpen(timeSec);
return HATCH_OK;
}
inline HatchActionResult hatchClose(float timeSec) {
if (timeSec <= 0.0f) return HATCH_ERR_INVALID_TIME;
if (hatchPosition <= HATCH_LIMIT_MIN) return HATCH_ERR_ALREADY_CLOSED;
hatchDoClose(timeSec);
return HATCH_OK;
}
#endif // HATCH_LOGIC_H