Telegram-бот для контроллеров Wiren Board: команды из Telegram публикуются в MQTT, правила wb-rules на них реагируют и отвечают. Пользователи и команды настраиваются в веб-интерфейсе контроллера.
Бот — тонкий транспорт Telegram ⇄ MQTT. Что делать по команде, решают правила wb-rules; бот только доставляет вызовы и ответы.
Подходит для Wiren Board 6, 7 и 8. Модель знать не нужно: установщик сам определит архитектуру (WB8 — arm64, WB6/WB7 — armhf) и скачает нужный пакет из последнего релиза. Контроллеру нужен интернет.
-
Зайти на контроллер по SSH под
root— с компьютера в той же сети:ssh root@<адрес-контроллера>
Адрес — тот же, по которому открывается веб-интерфейс контроллера в браузере (IP вида
192.168.1.50или имя видаwirenboard-XXXXXXXX.local, гдеXXXXXXXX— серийный номер с наклейки). -
Установить бота одной командой:
curl -fsSL https://github.com/Format-C-eft/wb-telegram/releases/latest/download/install.sh | shПосле установки бот выключен —
systemctl status wb-telegramпокажетinactive, так и должно быть. -
Создать бота в Telegram: написать @BotFather команду
/newbot, придумать имя — BotFather пришлёт токен вида123456789:AAH.... -
Настроить в веб-интерфейсе контроллера: Настройки → Конфигурационные файлы → «Telegram-бот»:
- «Токен бота» — токен от BotFather;
- «Бот включён» — поставить галочку;
- «Записать». Бот запустится.
-
Добавить людей. Каждый пишет боту в Telegram что угодно, бот отвечает «Нет доступа, ваш chat_id: N». Это число вместе с именем вписывается в таблицу «Пользователи» → «Записать». «Системные уведомления» — получать ли сообщения правил, адресованные всем.
-
Добавить команды во вкладках «Команды» (например,
gate— «Открыть ворота») и правило wb-rules, которое на них отвечает. Готовый пример:cp /usr/share/doc/wb-telegram/examples/telegram-example.js /etc/wb-rules/
Подробности — в разделе Правила wb-rules.
Обновление — та же команда из шага 2, настройки и токен сохраняются (если в новой версии пакета изменился конфиг по умолчанию, он кладётся рядом как /etc/wb-telegram.conf.dpkg-dist; новые настройки бот подставляет сам).
Конкретная версия:
curl -fsSL https://github.com/Format-C-eft/wb-telegram/releases/latest/download/install.sh | WB_TELEGRAM_VERSION=v0.1.12 sh(список — на странице релизов).
Удаление: apt purge wb-telegram (вместе с токеном) или
apt remove wb-telegram (настройки и токен остаются).
Если что-то не так — журнал journalctl -u wb-telegram -f и раздел
Ошибки и диагностика.
- Нужно управлять домом на Wiren Board из Telegram нескольким людям, у каждой команды — свой список тех, кому она доступна.
- Нужны уведомления из правил wb-rules (ошибки оборудования, события) в Telegram.
- Нужно, чтобы новая команда добавлялась без пересборки бота: строка в настройках плюс правило.
- Нужны роли, группы, права сложнее «кому доступна команда».
- Нужна логика прямо в боте без правил wb-rules: без правила команда ничего не делает, пользователь получит «ответа от правил нет».
- Нужны inline-кнопки, форматирование (Markdown/HTML), графики, webhook — в первой версии этого нет.
Telegram ──/gate 22──▶ wb-telegram ──JSON──▶ /devices/telegram_bot/controls/cmd_gate ──▶ правило wb-rules
Telegram ◀──текст──── wb-telegram ◀──────── /devices/telegram_bot/controls/send/on ◀── cmd.reply(...) / tg.send(...)
- Пользователь пишет боту
/gate 22. - Бот проверяет chat_id и доступ к команде и публикует вызов JSON-ом в контрол
cmd_gateустройстваtelegram_bot. - Правило wb-rules (через модуль
telegram) получает вызов, делает своё дело и отвечаетcmd.reply("...")— это запись в контролsend. - Бот доставляет ответ автору вызова.
Форма «Telegram-бот» в веб-интерфейсе:
| Поле | По умолчанию | Описание |
|---|---|---|
| Бот включён | выкл. | Выключенный бот не запускается и убирает своё устройство из MQTT |
| Токен бота | пусто | Формат 123456:ABC.... При открытии формы всегда пустой; пусто при записи — токен не меняется |
| Токен | — | Только для чтения: «Задан» / «Не задан» |
| Удалить токен | выкл. | Удаляет сохранённый токен, бот останавливается. Одновременно с новым токеном — ошибка записи |
| Таймаут подключения к Telegram, с | 30 | 5–120. Время на TCP-соединение и TLS-рукопожатие с api.telegram.org. Увеличьте, если в журнале TLS handshake timeout |
| Таймаут опроса Telegram, с | 30 | 1–50. Сколько один запрос getUpdates ждёт новых сообщений (long polling) |
| Таймаут ответа правила, с | 10 | 1–300. Если правило не ответило за это время, пользователь узнает. Команды старше этого времени не выполняются |
| Отладочный лог | выкл. | Подробный журнал (в том числе тексты сообщений). Токен не пишется никогда |
| Пользователи | пусто | Таблица: имя (уникальное), chat_id (уникальный), «Системные уведомления» |
| Команды | пусто | До 100 команд: имя (a-z, 0-9, _, до 32 символов, кроме start и help), описание (1–256 символов, видно в меню Telegram), «Кому доступно» (пусто — всем) |
Проверки, которые не выражаются схемой формы (уникальность имён, ссылки команд
на существующих пользователей, формат токена, «новый токен + удалить токен»),
делает сам бот при записи. Если запись отклонена, веб-интерфейс показывает
«Ошибка записи», файл не меняется, причина — в журнале
journalctl -u wb-mqtt-confed.
Файлы на контроллере:
| Файл | Права | Содержимое |
|---|---|---|
/etc/wb-telegram.conf |
как пишет wb-mqtt-confed | все настройки, кроме токена |
/etc/wb-telegram/token |
0600, каталог 0700 | токен; удаляется при apt purge |
Флаги сервиса можно задать переменной WB_TELEGRAM_OPTIONS в
/etc/default/wb-telegram (файл необязателен):
| Флаг | По умолчанию | Описание |
|---|---|---|
-broker |
unix:///var/run/mosquitto/mosquitto.sock, если сокет есть, иначе tcp://localhost:1883 |
адрес MQTT-брокера |
-config |
/etc/wb-telegram.conf |
путь к конфигу |
-token-file |
/etc/wb-telegram/token |
путь к файлу токена |
-debug |
выкл. | отладочный журнал с самого старта (до чтения конфига) |
-version |
— | напечатать версию и выйти |
-to-json и -from-json — служебные режимы для wb-mqtt-confed, вручную не нужны.
Устройство telegram_bot по MQTT Conventions Wiren Board, все сообщения retained:
| Топик | Значение |
|---|---|
/devices/telegram_bot/meta |
{"driver":"wb-telegram","title":{"en":"Telegram Bot","ru":"Telegram-бот"}} |
/devices/telegram_bot/meta/error |
r — бот остановлен или потерял связь с брокером (LWT); пусто — бот готов |
/devices/telegram_bot/controls/send |
text; последнее отправленное сообщение правил |
/devices/telegram_bot/controls/send/on |
сюда правила пишут сообщения (см. ниже) |
/devices/telegram_bot/controls/send/meta/error |
w, пока Telegram не принимает сообщения; снимается после первой успешной отправки |
/devices/telegram_bot/controls/cmd_<имя> |
text, только чтение; вызов команды (см. ниже). Заголовок контрола — описание команды |
Вызов команды /temp 22 от пользователя «Супруга» публикуется в cmd_temp:
{"id":"8812345","ts":1759312345,"user":"Супруга","args":"22"}id—update_idTelegram, уникален для каждого вызова;ts— момент публикации ботом (unix, секунды);user— имя из настроек;args— текст после команды; суффикс@имяботаотрезается, имя команды приводится к нижнему регистру.
Контролы cmd_* одноразовые: при каждом (пере)подключении бот публикует в них
заглушку {}, а не последний вызов, чтобы правило не выполнило его повторно.
Не пустую строку: пустое retained-сообщение в MQTT удаляет значение, wb-rules
считает контрол удалённым, и на первый вызов после рестарта бота whenChanged
не срабатывает. Пустое значение send при старте не публикуется по той же причине.
Контролы удалённых из настроек команд убираются при старте.
Сообщение правил — запись в send/on:
| Payload | Кому |
|---|---|
| строка (не JSON-объект) | всем пользователям с «Системные уведомления» |
{"text":"...","to":["Супруга"]} |
названным людям, независимо от флага уведомлений |
{"text":"...","reply_to":"8812345"} |
автору вызова с этим id (reply_to важнее to) |
поле id в объекте |
игнорируется ботом; нужно, чтобы одинаковые сообщения подряд не склеились |
Текст отправляется как есть, без разметки. Длиннее 4096 символов — обрезается
с «…». Неизвестные имена в to и reply_to на неизвестный или слишком старый
(больше часа) вызов только пишутся в журнал.
Модуль telegram ставится пакетом в /usr/share/wb-rules-modules/:
var tg = require("telegram");
// Команда /gate должна быть заведена в настройках бота.
tg.onCommand("gate", function (cmd) { // cmd.id, cmd.user, cmd.args
dev["wb-gpio/EXT1_R3A1"] = true;
cmd.reply("Ворота открываются"); // ответ автору вызова
});
tg.send("Котёл: ошибка E04"); // всем с «Системные уведомления»
tg.sendTo(["Супруга"], "Курьер у ворот"); // адресно, по именам из настроекЗащита от повторного выполнения встроена в модуль: он молча игнорирует пустые
значения и заглушку {} (вызов без id), вызовы старше 60 с, вызовы, появившиеся до загрузки модуля (после
рестарта wb-rules), и уже обработанные id. Каждое сообщение модуль отправляет
JSON-ом с уникальным id.
Особенности:
- Если бот выключен или не запущен, устройства
telegram_botв MQTT нет, и запись вtelegram_bot/send(tg.send,cmd.reply) бросает исключение в wb-rules. В правилах, которые должны работать и без бота, оборачивайте отправку вtry { ... } catch (e) { ... }. - Возраст вызова модуль считает по часам контроллера. Если часы после загрузки
wb-rules перевели назад больше чем на секунду, новые вызовы будут отбрасываться
до перезагрузки правил (
systemctl restart wb-rules). - Правило может ответить несколько раз и даже после «ответа от правил нет» — все ответы будут доставлены.
- Эмодзи в тексте можно использовать любые: wb-rules отдаёт символы вне базовой плоскости Unicode в CESU-8, бот сам пересобирает их в UTF-8.
| Ситуация | Ответ |
|---|---|
| Незнакомый chat_id | «Нет доступа, ваш chat_id: N» |
/start, /help, обычный текст |
«Доступные команды:» и список /имя — описание |
| Нет ни одной доступной команды | «Для вас пока нет доступных команд.» |
| Команда недоступна или не существует | «Неизвестная команда.» и список команд |
| Команда старше таймаута (по времени отправки в Telegram) | «Команда /x устарела (отправлена ЧЧ:ММ), повторите» — не выполняется |
| Нет связи с MQTT | «Контроллер недоступен, команда не выполнена» — не выполняется и не откладывается |
| Правило не ответило за таймаут | «Команда /x отправлена, ответа от правил нет» |
| Стикер, фото и другие сообщения без текста | без ответа |
Меню команд в Telegram у каждого пользователя своё — только доступные ему команды.
Журнал: journalctl -u wb-telegram -f (отладка — «Отладочный лог» в форме).
| Состояние | systemd | Что делать |
|---|---|---|
| «Бот выключен в настройках» | inactive |
включить в форме |
| «Токен не задан» | inactive |
задать токен в форме |
| «Telegram отклонил токен» | inactive |
задать новый токен |
| Конфиг на диске не читается или не проходит проверку | failed после повторных попыток |
исправить в форме; причина в журнале |
| Работает | active |
— |
В трёх первых случаях бот убирает своё устройство из MQTT. Включение обратно — запись формы: веб-интерфейс перезапускает сервис.
Поведение при сбоях:
- Нет связи с Telegram. Опрос и отправка повторяются с паузой от 1 до 60 с
(на 429 — столько, сколько просит Telegram). В журнал пишутся первая ошибка и
восстановление.
send/meta/error=w, пока сообщения не уходят. Очередь исходящих — 100 сообщений, при переполнении выбрасываются самые старые (предупреждение в журнале). Сообщения, которые Telegram отверг окончательно (4xx кроме 429), не повторяются. - Нет связи с MQTT. Бот переподключается сам; на время обрыва
meta/error=r, команды отвечают «Контроллер недоступен». - Старт. Примерно первую секунду после запуска бот убирает контролы удалённых
команд и подписывается на
send/on; команды, пришедшие в это окно, получают «Контроллер недоступен». - Остановка (SIGTERM/SIGINT): опрос прекращается, очередь досылается до 5 с,
meta/error=r. Если Telegram недоступен, остановка занимает до ~6 с. - Сообщения, отправленные боту, пока он был выключен, Telegram отдаёт после запуска; те, что старше таймаута, получают ответ «устарела».
Если в журнале Telegram: ошибка связи … TLS handshake timeout, соединение с
Telegram медленное (провайдер может замедлять его): увеличьте «Таймаут подключения
к Telegram» в форме или пустите контроллер в Telegram через VPN. Проверить скорость
рукопожатия с контроллера:
curl -sS -o /dev/null -w 'tls=%{time_appconnect}s\n' https://api.telegram.org/.
Токен никогда не попадает в журнал, в том числе в отладочном режиме: адрес Bot API в ошибках HTTP-клиента маскируется.
Нужен Go 1.26+.
make test # go test -race ./... -count=1 -timeout=60s -v -short
make lint-full # golangci-lint по всему коду (ставится в ./bin)
make lint # golangci-lint только по изменениям относительно origin/master (как в CI для веток)
make build # бинарь wb-telegram под linux/arm64 (CGO_ENABLED=0)
make deb # deb-пакет в Docker (см. ниже)
make generate # моки (go.uber.org/mock)
make tidy # go mod tidy
make clean # удалить бинарь
make deploy CONTROLLER=root@<ip-контроллера> SSH_KEY=~/.ssh/<ключ> # быстрая выкладка бинаря поверх пакетаАрхитектура бинаря: make build DEB_TARGET_ARCH=armhf (WB6/WB7). Тесты не ходят в сеть:
Telegram подменяется httptest.Server, MQTT — встроенным брокером mochi-mqtt на
127.0.0.1:0.
Раскладка:
cmd/ точка входа: флаги, режимы -to-json/-from-json, сборка компонентов
internal/app/ жизненный цикл компонентов, сигналы, остановка
internal/config/ конфиг, проверки, файл токена, крючки формы
internal/wbdevice/ MQTT Conventions: устройство, контролы, /on, LWT, уборка
internal/telegram/ Bot API: опрос, очередь с лимитами, повторы, маскирование токена
internal/retry/ паузы повторов 1 → 60 с
internal/bot/ ядро: доступ, команды → JSON, send → доставка, таймауты
wb/ схема формы, модуль и пример wb-rules, конфиг по умолчанию
debian/ упаковка
CI — .github/workflows/ci.yml. Прямой push в master запрещён, всё через
pull request со squash-слиянием.
- Push в любую ветку —
make testиmake lint(lint только изменений относительноorigin/master). Это обязательная проверка «Тесты и линт»: без неё PR не вливается. - Слияние в
master—make testиmake lint-fullпо всему коду, информационно: релиз не блокируют, итог виден в сводке прогона и предупреждением — что поправить в следующих версиях. Затем сборка пакетовarm64(WB8) иarmhf(WB6/WB7) и GitHub-релизv<мажор>.<минор>.<номер сборки>(мажор и минор — изdebian/changelog). Слияние, меняющее только документацию (*.md,docs/,LICENSE), релиз не выпускает. - В релиз попадают пакеты с версией в имени, они же без версии
(
wb-telegram_arm64.deb,wb-telegram_armhf.deb— на них ссылается установщик) иinstall.sh.
Локально пакет собирается в Docker (в том числе на macOS с Apple Silicon):
make deb # dist/wb-telegram_<версия>_arm64.deb (WB8)
make deb ARCH=armhf # dist/wb-telegram_<версия>_armhf.deb (WB6/WB7)
make deb ARCH=armhf DEB_VERSION=0.1.999Внутри — scripts/build-deb.sh: dpkg-buildpackage -b -a<арх> -us -uc -d в
образе golang:1.26-trixie с кросс-binutils. -d пропускает проверку
Build-Depends: Go берётся из образа, а не из пакета golang-1.26-go
(штатный wbdev Wiren Board на macOS не работает). Пакет сжат gzip и содержит
статический бинарь, поэтому ставится на контроллеры с Debian bullseye. Ставить
собранный пакет вручную: dpkg -i wb-telegram_<версия>_<арх>.deb.
- Логика в правилах, бот — транспорт. Новая команда — строка в настройках и правило; код бота не меняется. Правила wb-rules уже умеют всё про устройства дома.
- MQTT Conventions напрямую. Go-сервисы Wiren Board работают через закрытый бинарный Go-плагин; бот реализует открытые MQTT Conventions сам, поверх paho.mqtt.golang, и собирается обычным статическим бинарём.
- Токен отдельно от конфига. wb-mqtt-confed записывает конфиги с широкими правами и отдаёт их в браузер, поэтому токен хранится в отдельном файле 0600 и никогда не возвращается в форму.
- Устаревшие команды не выполняются, чтобы ворота не открылись через час после нажатия, а повтор retained-значения после рестарта не выполнил команду второй раз.
- Минимум зависимостей: только go-telegram/bot и paho.mqtt.golang (в тестах — mochi-mqtt и go.uber.org/mock).
Подробнее — docs/adr/0001-architecture.md.
MIT.