# Руководство по разработке прошивки устройства

## Минимальный шаблон нового устройства

```cpp
#include <Arduino.h>

// --- Обязательные константы ядра ---
const char* DEVICE_TYPE = "my_device";   // тип устройства
const char* FW_VERSION  = "1.0 dev";
const uint8_t CHANNEL_NUM = 2;           // 0 если каналы не используются

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

// --- Хук для /status ---
void appendStatusJsonFields(String &json) {
    // json уже содержит "status":"ok"
    // добавляем дополнительные поля
    json += ",\"my_field\":42";
}

// --- Хук для /about ---
void appendAboutJsonFields(String &json) {
    json += ",\"channels\":" + String(CHANNEL_NUM);
}

// --- Обработчик /action ---
bool deviceHandleAction(const String &action,
                        const String &paramsJson,
                        String &errorCode,
                        String &errorMessage)
{
    if (action == "my_action") {
        // ...
        return true;
    }

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

// --- Сброс к заводским настройкам ---
void deviceHandleReset() {
    // очистить EEPROM устройства (диапазон DEVICE_EEPROM_START..EEPROM_SIZE)
    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() {
    coreSetup();
    // инициализация GPIO, сенсоров и т.п.
}

void loop() {
    coreLoop();
    // своя логика
}
```

---

## Использование каналов (channels schema)

Каналы читаются из EEPROM через API ядра:

```cpp
uint8_t pin      = sh_channel_pin(ch);           // GPIO пин канала
uint8_t ind      = sh_channel_indicator(ch);     // GPIO пин индикатора
uint8_t fb_pin   = sh_channel_feedback_pin(ch);  // GPIO пин обратной связи
bool    inverted = sh_channel_is_inverted(ch);   // флаг инверсии
```

Если `pin == SH_PIN_UNUSED` (0xFF) — канал не задействован:
```cpp
if (pin == SH_PIN_UNUSED) continue;
```

---

## EEPROM устройства

Ядро занимает адреса 0..~293. Для хранения данных устройства:

```cpp
const uint16_t MY_EEPROM_BASE = getDeviceEepromStart(); // = 512
const uint16_t MY_DATA_ADDR   = MY_EEPROM_BASE;          // float = 4 байта
const uint16_t MY_STATE_ADDR  = MY_EEPROM_BASE + 4;
```

---

## Отправка событий на сервер

```cpp
#include <sh_core.h>

// Пример отправки события
String json = "{\"device_id\":\"" + getUniqueID() + "\","
              "\"event_name\":\"my_event\","
              "\"data\":{\"channel\":0}}";

int http_code = 0;
core_post_json_to_server("/events/new", json, 3000, http_code);
```

Путь для событий по умолчанию `/events/new`. Можно переопределить:
```cpp
const char* core_get_event_path() { return "/events/new"; }
```

---

## Типы устройств

| `DEVICE_TYPE` | Описание |
|--------------|---------|
| `relay` | Реле (1–8 каналов), хранит состояние в EEPROM |
| `button` | Блок кнопок с RGB-индикаторами NeoPixel |
| `sensor` | Мультисенсор (radar, climate, light, mic) |
| `hatch` | Моторизованный люк (3 реле + концевик) |

---

## Поддерживаемые платформы

Библиотека `sh_core` поддерживает ESP8266 и ESP32 через платформенные заголовки:
- `devices/sh_core_esp8266/src/platform/esp8266/sh_platform_esp8266.h`
- `devices/sh_core_esp8266/src/platform/esp32/sh_platform_esp32.h`

Активный платформенный заголовок выбирается через `sh_platform.h`.

---

## Пины по умолчанию в примерах устройств

### sensor.ino (ESP32)
| Компонент | Пины |
|-----------|------|
| BH1750 (свет) | SDA=16, SCL=17, I2C addr=0x5C |
| BME280 (климат) | SDA=18, SCL=19, I2C addr=0x76 |
| LD2420 (radar) | RX=4, TX=15, 115200 baud |
| MAX4466 (mic) | ADC=34 |

### hatch.ino (ESP32)
| Компонент | Пин |
|-----------|-----|
| Силовое реле | GPIO18 (хардкод `HATCH_POWER_RELAY_PIN`) |
| Реле «открыть» (канал 0) | через channels schema |
| Реле «закрыть» (канал 1) | через channels schema |
| Концевик «закрыто» | feedback-пин канала 1 |
