Skip to content

ICommandSink — точка расширения для устройств без UART

cloud::CommandHandler парсит входящие MQTT-команды и нормализует их в UART-payload (CommandPayload, ProfilePayload, ConfigChunkPayload). Сам CommandHandler не знает, куда эти payload отправлять — это делает реализация интерфейса ICommandSink, которую ему передают в конструктор.

Это позволяет одной и той же логике парсинга команд жить во всех прошивках, построенных на idryer-protocol — с UART-компаньоном (iDryer) и без него (iHeater Link II, модуль телеметрии и т.п.).

Интерфейс

Файл: src/cloud/command_sink.h.

namespace idryer::cloud {

class ICommandSink
{
public:
    virtual ~ICommandSink() = default;

    virtual void sendCommand(const DryerUart::CommandPayload& payload,
                             bool ackRequired) = 0;

    virtual void sendProfileCommand(const DryerUart::ProfilePayload& payload,
                                    bool ackRequired) = 0;

    virtual void sendConfigPushChunk(const DryerUart::ConfigChunkPayload& payload,
                                     uint8_t dataLen, uint8_t flags) = 0;
};

} // namespace idryer::cloud

Сигнатуры 1-в-1 совпадают с соответствующими методами DryerUart::UartBridge — миграция существующих потребителей механическая.

Реализация для устройств с UART (iDryer)

Файл: src/cloud/uart_command_sink.h. Используется по умолчанию.

UartBridge uart(...);
cloud::UartCommandSink sink(&uart);
cloud::CommandHandler cmdHandler(&sink);

cmdHandler.handleMqttCommand("drying", jsonData);
// → sink.sendCommand(...) → uart.sendCommand(...) → RP2040

UartCommandSink содержит null-guard: если UART-bridge не сконфигурирован, вызовы молча игнорируются.

Реализация для устройств без UART

Потребитель библиотеки (прикладной код, не библиотека) пишет свой sink и применяет команды локально. Пример для iHeater Link II:

// В прикладном коде прошивки Link II, не в libdryer-protocol.
class LocalHeaterSink : public idryer::cloud::ICommandSink
{
public:
    void sendCommand(const DryerUart::CommandPayload& p, bool /*ack*/) override
    {
        switch (p.command)
        {
        case DryerUart::CommandCode::Start:
            // p.arg0 = target temperature × 10
            applyHeaterDuty(p.arg0 / 10.0f);
            break;
        case DryerUart::CommandCode::Stop:
            stopHeater();
            break;
        default:
            break;
        }
    }

    void sendProfileCommand(const DryerUart::ProfilePayload&, bool) override {}
    void sendConfigPushChunk(const DryerUart::ConfigChunkPayload&,
                             uint8_t, uint8_t) override {}
};

LocalHeaterSink sink;
cloud::CommandHandler cmdHandler(&sink);

Библиотека про эту реализацию ничего не знает.

Что НЕ делает ICommandSink

  • Не заменяет UART-протокол. ICommandSink транслирует те же payload-структуры, что UART шлёт на провод — на случай, когда те же сигнатуры удобно переиспользовать в применителе команды.
  • Не добавляет высокоуровневый API (startDrying(unitId, tempC, durationMin)) — это отдельная задача на будущее.
  • Не скрывает UartBridge от приложения. На устройствах с MCU приложение по-прежнему держит UartBridge для приёма телеметрии; UartCommandSink лишь оборачивает исходящий канал команд.

Жизненный цикл

Потребитель создаёт sink и передаёт CommandHandler указатель на него. Sink должен жить не короче, чем CommandHandler. На iDryer оба — члены класса IdryerDevice: sink объявлен до cmdHandler_ в теле класса, поэтому он инициализируется первым и освобождается последним.