Насос на даче отказался включаться с дашборда в субботу утром, и я потратил на это час: перепрошил плату дважды, перечитал callback, усомнился в Wi-Fi. В понедельник та же беда заняла у меня четыре минуты — меньше, чем вскипает чайник. Разница — в инструменте: я открыл терминал на машине с брокером и запустил mosquitto_sub с маской #. И увидел: сообщение до брокера доходит, но в топик cmd/dacha/pump вместо cmd/dacha/pump/on. Опечатка в имени сидела в скетче, а я смотрел на дашборд.
mosquitto_sub -t '#' -v — рентген всей системы. Одна команда подписывается на всё и печатает каждую строку: имя топика, пробел, payload. То, что скетч думает, что шлёт, и то, что реально прилетает на брокер, впервые оказываются в одном окне, рядом по времени. Половина моих MQTT-загадок с тех пор решается до того, как я открываю скетч. Инструмент живёт в пакете mosquitto-clients, который ставился вместе с брокером в уроке про установку брокера, — докачивать ничего не нужно.
В этом уроке — весь набор консольной отладки: sub для просмотра потока, pub для команд без перепрошивки, журнал брокера для разбора трафика по именам клиентов и охота на топики-призраки, которые щёлкают реле сами по себе.
Начнём с флагов sub. -t '#' — маска на все ветки: грамматика масок та же, что в подписках платы, её разбирали в самом первом уроке о протоколе. -v включает печать имени топика: без неё вы увидите голые payload без адресов, что почти бесполезно при отладке. Работает и против локального брокера через -h localhost, и против публичного: подписка на # у test.mosquitto.org — та самая экскурсия по чужим теплицам, о которой мы говорили в уроке про публичные брокеры.
Флаги, которые у меня в мышечной памяти: -h хост, -p порт, -u и -P — логин с паролем, -i clientId — чтобы наблюдатель не воевал паспортом с настоящими клиентами: правило уникальности из урока про clientId действует и на утилиты, я один раз наступил на это, и наблюдатель уронил плату. -d печатает обмен на уровне пакетов — CONNECT, SUBACK, PINGREQ, — полезно, когда протокол не складывается до сообщений. Есть ещё -F — свой формат строки: журнал колонками, топик слева, payload справа, время отдельно; когда строк в минуту десятки, свой формат экономит зрение.
Привычка, которую я вынес из всех этих историй: пока идёт отладка, окно с sub -t '#' -v открыто всегда, рядом со скетчем. Это тот же принцип, что и журнал уровней из урока про Serial: факты должны печататься сами, а не восстанавливаться по памяти. Скорость тут ценна вдвойне: пока скетч печатает свои версии событий в монитор платы, sub печатает правду, и расхождение между двумя окнами — уже диагноз, не дожидаясь дальнейших экспериментов.
mosquitto_pub: команда вместо перепрошивки
# Терминал 1: смотреть всё, что движется по брокеруmosquitto_sub -h localhost -t '#' -v # Терминал 2: команда насосу — реле щёлкает без перепрошивкиmosquitto_pub -h localhost -t 'cmd/dacha/pump/on' -m '1' -q 1 # Подтверждение в терминале 1:# cmd/dacha/pump/on 1 # Убрать retain-призрак: пустое сообщение в тот же топикmosquitto_pub -h localhost -t 'cmd/dacha/pump/on' -n -r # Жив ли брокер и сколько у него клиентовmosquitto_sub -h localhost -t '$SYS/broker/clients/connected' -C 1pub — это обратный рентген, тест с другой стороны конвейера. Если после публикации с -q 1 реле щёлкнуло, хотя плата молчала, — связь и брокер в порядке, виноват скетч. Если строка в sub пришла, а реле не щёлкнуло, — виноват callback: белый список значений, сравнение payload, опечатка в ветке if — приёмы из урока про subscribe и callback, просто проверять их теперь можно удалённо. Сеть и логика разделяются за один тест, и перепрошивка при отладке не нужна вообще: команды публикуются из терминала, плата живёт своей жизнью.
Служебная ветка $SYS — брокер рассказывает о себе сам. Топик $SYS/broker/clients/connected показывает число живых абонентов и обновляется раз в 10 секунд по умолчанию — интервал задаётся sys_interval в конфиге. Полезно, когда подозреваете, что платы вообще отвалились: число клиентов резко просело — причина найдена раньше, чем вы открыли логи. Рядом живут и другие метрики: $SYS/broker/messages/received — счётчик принятых сообщений, $SYS/broker/bytes/received — принятые байты; по их шагам видно, что поток жив, даже когда дашборд ещё ничего не нарисовал. Я вешал этот топик на дашборд как сердцебиение всей системы.
| Симптом | Команда | Что вы увидите |
|---|---|---|
| Дашборд молчит, скетч печатает published | sub -t '#' -v | есть ли строка вашего топика и с тем ли именем |
| Реле щёлкает само по себе | sub -t '#' -v | топик-призрак с retain, о котором никто не помнит |
| Доходит ли команда вообще | pub с -m 1 и -q 1 | реле щёлкнуло — сеть в порядке, ищите в callback |
| Подозрение, что всё отвалилось | $SYS/broker/clients/connected | живое число подключённых клиентов |
| Дашборд показывает то, чего не было | sub -v в двух окнах | расхождение между тем, что шлёт плата, и тем, что видит панель |
Журнал брокера: читать трафик глазами
# /etc/mosquitto/conf.d/logging.conflog_dest file /var/log/mosquitto/mosquitto.loglog_type allconnection_messages truetimestamp truelog_type all включает полную запись: каждая операция попадает в журнал с именем клиента. Строка Received PUBLISH from dacha-esp32 означает, что сообщение вошло в брокер от вашей платы — той, что публикует показания по millis-циклу из урока про публикацию показаний; рядом в скобках идут служебные поля: q0 — качество обслуживания, r0 или r1 — retain-флаг, m17 — номер сообщения у клиента. Строка Sending PUBLISH to dash-client — брокер раздал сообщение подписчику. По парам Received и Sending путь пакета виден глазами от пина до дашборда.
Мой любимый диагноз по этим строкам: смотреть, растёт ли m-номер у клиента, который точно должен шлёть. Номер стоит на месте — скетч не публикует, дело не в сети: датчик молчит или код не доходит до publish. А r1 у строки, которую вы не просили, — признак retain-призрака: сообщение с флагом сохранённости, оставленное экспериментом месяц назад. Брокер выдаёт его каждому новому подписчику ветки — и реле щёлкает при каждом переподключении платы. Диапазон поиска сужается мгновенно: обычная публикация даёт r0, значит любая r1 — либо намеренный retain, либо забытый эксперимент.
Призраки чистятся одной командой: публикация пустого сообщения с retain в тот же топик — mosquitto_pub с -n -r. После этого подписка на # в момент подключения больше не показывает строк, которых никто не шлёт, — признак чистой системы. После того случая у меня правило: эксперименты с retain — только на тестовых ветках, чистка — сразу после опыта, потому что retain живёт в брокере вечно, дольше, чем память о том, зачем он был нужен. Отдельно про объём: полный журнал на живом брокере с десятком клиентов — десятки мегабайтов в сутки, поэтому log_type all я включаю на время отладки и гашу до notice, когда система устоялась.
Чтение трафика глазами — навык, который растёт со скоростью просмотра. Через неделю регулярного sub -v вы начинаете видеть систему целиком: какая плата что шлёт, с каким ритмом, где заканчиваются ваши ветки и начинаются чужие. На публичной площадке этот же приём — способ узнать район: строки с чужими префиксами показывают, как другие строят имена, и сразу дают понять, почему уникальный префикс — не паранойя. Ритм тоже виден: прибор, публикующий чаще, чем меняется величина, выдаёт себя пачками одинаковых строк — мой частокол из давних экспериментов выглядел в чужом журнале именно так.
Финальная связка, которую я применяю к каждой новой плате: sub -t '#' -v в первом терминале, pub во втором, полный журнал брокера в третьем. Три окна покрывают путь сообщения от пина до дашборда без единого предположения наугад. Симулятор ниже имитирует первое окно: журнал с фильтром веток, счётчиками и призраками — попробуйте его, прежде чем открывать терминал. Оговорка честности: в симуляторе строки генерирует скетч, а на живом брокере их создают настоящие клиенты; логика фильтра и счётчиков при этом та же.
- Нажмите кнопку несколько раз и посчитайте прирост my=: каждая команда добавляет одну строку out в журнал. Затем найдите строки с r1 и сверьте счётчик ghost=: это и есть призраки.
- Поменяйте период генерации строк с 2500 на 1000: поток станет плотнее, и читать его глазами станет труднее — так же труднее становится и настоящему отладчику на живом брокере.
- Добавьте в switch пятую ветку с чужой веткой вида neighbor2/sensor и посмотрите, как расходятся счётчики: журнал врёт только тогда, когда вы пишете его руками.
- Повторите четыре команды из код-блока на своей машине с брокером — от sub до очистки retain. Одна тренировка на живом брокере стоит трёх перечитанных страниц.
- sub -t '#' -v — рентген системы: каждая строка показывает топик и payload; окно открыто, пока идёт отладка
- mosquitto_pub шлёт команду без перепрошивки: -q 1 для команд, -n -r чистит retain-призрак
- log_type all печатает каждую операцию с именем клиента: q, r-флаг, m-номер — по нему видно, шлёт ли скетч на самом деле
- Призрак — retained-сообщение из старых опытов: выдаётся каждому новому подписчику и щёлкает реле при reconnect
- $SYS/broker/clients/connected обновляется раз в 10 секунд: мгновенный ответ на вопрос, живы ли клиенты
- 1. Скетч печатает published, дашборд молчит. Какая команда первая?
- 2. Что означает r1 в строке Received PUBLISH из журнала брокера?
- 3. Насос включается сам ровно один раз после каждого обрыва связи. Что произошло?
- 4. Чем грозит наблюдателю отсутствие -i clientId?
- 5. Что обновляется раз в 10 секунд по умолчанию?
Частые вопросы по теме урока
- Скетч печатает published, дашборд молчит. Какая команда первая?
- Сначала факты: строка вашего топика в sub -v либо есть — виноват подписчик, либо её нет — виноват издатель. Дальше диагностика идёт в известную сторону. mosquitto_sub -t '#' -v — посмотреть, доходит ли сообщение
- Что означает r1 в строке Received PUBLISH из журнала брокера?
- Служебные поля в скобках: q — качество обслуживания, r — retain, m — номер сообщения. r1 — источник топиков-призраков; по нему я находил забытые эксперименты месячной давности. Флаг retain: брокер сохранит сообщение и выдаст новым подписчикам
- Насос включается сам ровно один раз после каждого обрыва связи. Что произошло?
- Классика retain на командном топике: новая подписка получает последнее сохранённое сообщение. Очистка — пустой payload с retain в тот же топик. Retained-сообщение в командном топике выдаётся при каждой новой подписке
- Чем грозит наблюдателю отсутствие -i clientId?
- ClientId уникален в масштабе брокера: если утилита случайно возьмёт имя платы, соединения начнут воевать за паспорт. -i observ-1 снимает вопрос; совпадение редкое, но одного раза за вечер достаточно. Случайное совпадение паспорта с платой уронит их связь по кругу
- Что обновляется раз в 10 секунд по умолчанию?
- sys_interval задаёт период публикации метрик в $SYS. Топик clients/connected — одна из них: живой счётчик абонентов. Служебные $SYS-топики брокера