(ссылка на репозиторий в конце статьи)
Когда я собирал систему «Умный дом» в загородном доме, столкнулся с двумя классическими проблемами, которые, наверное, знакомы многим, кто строит IoT на Wi-Fi:
1. В подвале коммутатор управления отоплением периодически «глотал» команды. Аналогичные пропуски случались и в других менее критичных системах, расположенных в дальних пыльных (от туда и название проекта) углах дома с неуверенным покрытием Wi-Fi.
2. Не все устройства одинаково хорошо переносили слишком частую отправку команд.
Тянуть Ethernet по всему дому и сверлить стены или развешивать дополнительные роутеры/повторители мне категорически не хотелось. Поэтому первую проблему нужно было решать чисто программно.
Почему стандартные возможности брокера не подошли
Многие MQTT-брокеры (в том числе популярный Mosquitto) позволяют включить «хранение последнего сообщения» (retain) и QoS 1/2. Но этого оказалось недостаточно.
Например, когда нужно передать на электронный замок целый список RFID-меток — одной «последней» командой не обойтись. Если устройство в момент отправки было недоступно, список просто терялся.
Решение: контракт с подтверждением
Я изменил договорённость между брокером и потребителем (consumer):
- При получении любого сообщения устройство обязано ответить подтверждением.
- Только после получения подтверждения сообщение считается доставленным.
- Если подтверждение не пришло за заданное время — сообщение автоматически уходит в очередь на повторную отправку.
В тот же модуль я добавил защиту от слишком частой отправки:
- Для каждого deviceId ведётся таймер.
- Если с момента предыдущей успешной отправки прошло меньше заданного интервала — новое сообщение сразу помечается как «недоставленное» и переносится в цикл доотправки.
Оптимизации, чтобы не грузить шину лишними данными
Чтобы не гонять по MQTT-шине тонны дублирующихся сообщений, добавил два простых механизма:
1. Умная очередь: если для данного deviceId уже есть недоставленные сообщения, новые команды сразу попадают в очередь доотправки. Попытка немедленной отправки не производится — экономим трафик и нагрузку на устройство. Повторная передача же ведется до первого не доставленного сообщения (для данного deviceId).
2. Три уровня надёжности (задается отдельно для каждого сообщения):
- Уровень 1 — полная доотправка всех сообщений до подтверждения.
- Уровень 2 — доотправка только последнего сообщения для данной пары deviceId + command (подробности в README).
- Уровень 3 — «выстрелил и забыл»: без повторных попыток и без ожидания подтверждения (для совсем неважных датчиков).
Результат
Система стала работать заметно стабильнее:
- Пропусков команд в подвале больше нет.
- Устройства, чувствительные к флудам, больше не сбоят.
Решение я вынес в отдельный проект Duster Broker и выложил на GitHub. Там же подробный README, примеры конфигурации и инструкция по интеграции.
