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_ в теле класса, поэтому он инициализируется первым
и освобождается последним.