[Open source, Go, Интернет вещей, DIY или Сделай сам] (Не)очередной MQTT телеграм бот для IoT
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Всем привет! Сегодня хочу поделиться опытом разработки универсального телеграм бота для получения информации и управления IoT устройствами посредством протокола MQTT.
Почему (не)очередной? Потому что это не просто бот с двумя захардкоженными кнопками для управление лампочкой, примеров которых в интернете много, а это бот, который поддерживает гибкую настройку подписок и компанд для управления прямо из своего меню, без изменения исходного кода. NoCode solution, так сказать.
Бот разрабатывался на языке Go, исходный код выложен в свободный доступ на гитхаб под лицензией MIT. В статье хочу рассказать о некоторых технических моментах реализации и получившемся функционале с примерами использования.
Зачем?
Тут сошлись несколько факторов. Во-первых, я давно хотел перевести свой умный домофон на что-то более легкое и универсальное. Одним телеграм ботом покрывается весь зоопарк устройств и операционных систем, оставляя проблемы совместимости товарищу Дурову. Наконец-то смогу открывать домофон с ноутбука.
Во-вторых, очень хотелось попрактиковаться в написании бота, меня очень привлекает сама идея управления чем-либо через месседжер. Как, например, Вастрик в своем клубе сделал админку через телеграм, это ли не круто?
Функционал
Писать бота с несколькими захардкоженными кнопками — скучно. Решил сделать более универсальное решение с кастомизированным пользовательским меню и возможностью выводить графики.
Итого, минимальной целью был следующий функционал:
- Многоуровневое пользовательское меню с возможностью создания следующих кнопок:
- Каталог (как раз для древовидного меню)
- Команда с отправкой одного значения
- Переключатель (отправка чередующихся значений)
- Команда с несколькими значениями (выпадает меню на выбор)
- Показать последнее принятое сообщение для топика
- Построить график для подписки
- Подписка на произвольные топики (в т.ч приём изображений)
- Хранение истории значений
- Ручная отправка сообщения в топик
Весь этот функционал доступен прямо из меню бота без необходимости внесения изменений в исходный код. На лету редактируем меню и пользуемся:
Можете попробовать мою копию бота по адресу @mqtg-bot, но он в любой момент может упасть под нагрузкой, т.к развернут на бесплатном Heroku. Если вдруг не отвечает на /start, то так оно и случилось. Вы можете запустить своего бота по инструкции в README.
Переключатель
Самое простое, что можно было придумать, это включать/выключать реле. Для этих целей я даже собрал небольшую декоративную настольную лампу с WiFi реле Sonoff Mini внутри. Добавляем в меню бота переключатель и вуаля:
Гифка бесконечно моргающей лампы
SPL
Построение графиков
Как мне кажется, каждый человек, заинтересовавшийся темой интернета-вещей, первым делом подключает к ардуино или esp датчик влажности и температуры. Мы вот с женой таким образом следили за влажностью воздуха в квартире после рождения дочки, ибо в зимний период при централизованном отоплении дома это было проблемой.
Отправим/примем эти данные по MQTT и построим график:
Прием фото с камеры
Один из примеров использования бота — получение изображений с IP камеры. По MQTT можно передавать любые бинарные данные в пределах 256Мб на одно сообщение. Вариантов использования тут может быть много. Например, можно делать захват нескольких изображений с IP камеры по сигналу с датчика движения и отправлять фотографии себе в телеграм. Я в качестве примера сделал отправку изображений с модуля ESP32-CAM по запросу. Ардуиновский скетч лежит в папке examples репозитория (он же отправляет данные с датчика влажности и температуры AM2301).
Вот как это выглядит:
Пара особенностей реализации
Больше всего времени заняло написание редактируемого пользовательского меню, его хранения и загрузки.
В качестве основы я создал интерфейс ButtonI, который описывает ряд методов, необходимых для работы кнопок.
Интерфейс кнопки с небольшими пояснениями:
type ButtonI interface {
GetType() button_types.ButtonType // каждя кнопка должна знать свой тип для маршаллинга
GetName() string
GetFullName() string // пришлось добавить отдельный метод только для кнопки TOGGLE, т.к иногда есть потребность выводить ее сдвоенное имя
// методы для работы с "командами" кнопок, это те действия, которые выполняются при нажатии
GetCurrentCommand() *CommandType
GetCommands() []*CommandType
AddNewCommand(*CommandType)
DeleteCommand(int)
// .... тут есть еще несколько методов ....
// методы для работы с деревом меню
SetParent(*FolderButton)
GetParent() *FolderButton
GetButtons() *[]ButtonI
AddButton(ButtonI)
DelButton(int32)
// переопределение методов маршаллинга/анмаршаллинга json
MarshalJSON() ([]byte, error)
UnmarshalJSON([]byte) error
}
Сериализация/десериализация меню
Как вы могли заметить, интерфейс переопределяет методы Marshal/Unmarshal JSON, они используются для сохранения/загрузки пользовательского меню в/из базы.
При сохранении каждому объекту обязательно проставляется его тип, чтобы можно было потом десериализовать.
func (b *FolderButton) MarshalJSON() ([]byte, error) {
b.Type = b.GetType()
return json.Marshal(*b)
}
С десериализацией немного сложнее, т.к заранее не известна структура пользовательского меню. Приходится рекурсивно десериализовать json в map[string]interface{} и кастовать каждый объект в кнопку определенного типа.
Небольшая портянка кода парсера
SPL
func parseDataMap(dataMap *map[string]interface{}) ButtonI {
var outButton ButtonI
fType, _ := (*dataMap)["Type"].(float64)
switch button_types.ButtonType(fType) {
case button_types.MULTI_VALUE:
var multiValueButton MultiValueButton
// заполнение нужных полей
outButton = &multiValueButton
case button_types.SINGLE_VALUE:
var singleValueButton SingleValueButton
// заполнение нужных полей
outButton = &singleValueButton
case button_types.TOGGLE:
var toggleButton ToggleButton
// заполнение нужных полей
outButton = &toggleButton
case button_types.FOLDER:
var folderButton FolderButton
folderButton.Name, _ = (*dataMap)["Name"].(string)
buttons, _ := (*dataMap)["Buttons"].([]interface{})
// рекурсивный вызов парсинга для дерева
folderButton.Buttons = make([]ButtonI, 0, len(buttons))
for _, button := range buttons {
buttonMap, ok := button.(map[string]interface{})
if ok {
ButtonI := parseDataMap(&buttonMap)
folderButton.Buttons = append(folderButton.Buttons, ButtonI)
}
}
outButton = &folderButton
case button_types.SYSTEM:
var systemButton SystemButton
// заполнение нужных полей
outButton = &systemButton
case button_types.PRINT_LAST_SUB_VALUE:
var printLastValueButton PrintLastValueButton
// заполнение нужных полей
outButton = &printLastValueButton
case button_types.DRAW_CHART:
var showCharButton DrawChartButton
// заполнение нужных полей
outButton = &showCharButton
return outButton
}
Есть огромное предчувствие, что сохранять/восстанавливать меню таким способом немного костыльно, но лучше пока ничего не придумал. Буду искренне рад советам, как можно хранить пользовательское меню с разными типами кнопок и потенциально неограниченной вложенностью.
Кодируем информацию в коллбэках
Длина данных в коллбэке inline клавиатуры в телеграме (это меню, прикрепленное к сообщению) ограничина 64 символами.
Для добавления в коллбэк к каждой кнопке всей необходимой информации я завел структуру следующего вида:
type QueryDataType struct {
MessageId int64
Keyboard KeyboardType // тип inline клавиатуры
Path []int32 // где мы находимся (для навигации по многоуровневому меню)
Action ActionType // основное действие
IntValue int32 //
BoolValue bool // вспомогательные данные к действию
Index int32 //
}
Данная структура сериализуется в protobuf и кодируется в base64. Итого 64 символа мне вполне хватает. А как бы сделали вы?
Что дальше?
Есть следующие идеи по улучшению функционала:
- Более гибкая настройка графиков (имена для легенд, мин/макс значения для осей, пользовательский формат временной оси и т.д)
- Управление историей сохраненных сообщений
- Ответное действие на принятое сообщение (например, отправка команды при получении определенных данных в топик)
- Парсинг и хранение только нужных данных из сообщения
- Прием аудио данных
Буду рад любым вашим идеям в комментариях.
Заключение
Надеюсь, что статья была хоть немного полезна и не выглядит самопиаром. Славы и денег не ищу, это лишь мой небольшой вклад в open source и хабрасообщество. Два года назад я с головой ушел в веб-разработку, но все еще немного скучаю по железу, так и родился данный проект. Конечно, бот может быть еще сыроватым, но по реакции на статью я смогу понять, нужно это кому-то или нет, и стоит ли мне развивать его дальше. Всем добра!
Ссылки:
===========
Источник:
habr.com
===========
Похожие новости:
- [Open source, ERP-системы, CRM-системы, Облачные сервисы, Умный дом] Концепция Облачной операционной системы
- [Криптография, Python, SQLite, GitHub, Криптовалюты] Внедряем оплату BTC куда угодно (Python)
- [Open source, IT-инфраструктура, IT-компании] МТС выбрала OpenStack и Canonical для своей облачной инфраструктуры нового поколения
- [Разработка веб-сайтов, Поисковые технологии, Python, Программирование] Делаем поиск в веб-приложении с нуля
- [Open source, Виртуализация, Искусственный интеллект, Openshift] Еще немного про C# 8.0, шпаргалка по Red Hat OpenShift Container Platform и создаем конвейер upstream-to-downstream
- [Open source, Lisp, Функциональное программирование, Учебный процесс в IT, Искусственный интеллект] SRFI-216: Поддержка курса SICP. Обсудим?
- [Алгоритмы, Искусственный интеллект, Будущее здесь, IT-компании] Правильный выбор с первой попытки: ИИ помогает выбирать лучшего актёра на роль в фильме
- [Python, Машинное обучение, Искусственный интеллект, Natural Language Processing] Команда МФТИ второй год подряд в конкурсе от Amazon — Alexa Prize Socialbot Grand Challenge 4
- [Open source, Администрирование баз данных, Хранение данных] openGauss: новая СУБД от Huawei для нагруженных enterprise-проектов прибавила в функциональности
- [Монетизация мобильных приложений, Законодательство в IT, IT-компании] Российский IT-бизнес попросил не поддерживать законопроект об ограничениях магазинов Apple и Google
Теги для поиска: #_open_source, #_go, #_internet_veschej (Интернет вещей), #_diy_ili_sdelaj_sam (DIY или Сделай сам), #_telegram, #_mqtt, #_iot, #_diy, #_open_source, #_bot, #_esp8266, #_esp32, #_umnyj_dom (умный дом), #_open_source, #_go, #_internet_veschej (
Интернет вещей
), #_diy_ili_sdelaj_sam (
DIY или Сделай сам
)
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 26-Ноя 10:15
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Всем привет! Сегодня хочу поделиться опытом разработки универсального телеграм бота для получения информации и управления IoT устройствами посредством протокола MQTT. Почему (не)очередной? Потому что это не просто бот с двумя захардкоженными кнопками для управление лампочкой, примеров которых в интернете много, а это бот, который поддерживает гибкую настройку подписок и компанд для управления прямо из своего меню, без изменения исходного кода. NoCode solution, так сказать. Бот разрабатывался на языке Go, исходный код выложен в свободный доступ на гитхаб под лицензией MIT. В статье хочу рассказать о некоторых технических моментах реализации и получившемся функционале с примерами использования. Зачем? Тут сошлись несколько факторов. Во-первых, я давно хотел перевести свой умный домофон на что-то более легкое и универсальное. Одним телеграм ботом покрывается весь зоопарк устройств и операционных систем, оставляя проблемы совместимости товарищу Дурову. Наконец-то смогу открывать домофон с ноутбука. Во-вторых, очень хотелось попрактиковаться в написании бота, меня очень привлекает сама идея управления чем-либо через месседжер. Как, например, Вастрик в своем клубе сделал админку через телеграм, это ли не круто? Функционал Писать бота с несколькими захардкоженными кнопками — скучно. Решил сделать более универсальное решение с кастомизированным пользовательским меню и возможностью выводить графики. Итого, минимальной целью был следующий функционал:
Весь этот функционал доступен прямо из меню бота без необходимости внесения изменений в исходный код. На лету редактируем меню и пользуемся: Можете попробовать мою копию бота по адресу @mqtg-bot, но он в любой момент может упасть под нагрузкой, т.к развернут на бесплатном Heroku. Если вдруг не отвечает на /start, то так оно и случилось. Вы можете запустить своего бота по инструкции в README. Переключатель Самое простое, что можно было придумать, это включать/выключать реле. Для этих целей я даже собрал небольшую декоративную настольную лампу с WiFi реле Sonoff Mini внутри. Добавляем в меню бота переключатель и вуаля: Гифка бесконечно моргающей лампыSPLПостроение графиков Как мне кажется, каждый человек, заинтересовавшийся темой интернета-вещей, первым делом подключает к ардуино или esp датчик влажности и температуры. Мы вот с женой таким образом следили за влажностью воздуха в квартире после рождения дочки, ибо в зимний период при централизованном отоплении дома это было проблемой. Отправим/примем эти данные по MQTT и построим график: Прием фото с камеры Один из примеров использования бота — получение изображений с IP камеры. По MQTT можно передавать любые бинарные данные в пределах 256Мб на одно сообщение. Вариантов использования тут может быть много. Например, можно делать захват нескольких изображений с IP камеры по сигналу с датчика движения и отправлять фотографии себе в телеграм. Я в качестве примера сделал отправку изображений с модуля ESP32-CAM по запросу. Ардуиновский скетч лежит в папке examples репозитория (он же отправляет данные с датчика влажности и температуры AM2301). Вот как это выглядит: Пара особенностей реализации Больше всего времени заняло написание редактируемого пользовательского меню, его хранения и загрузки. В качестве основы я создал интерфейс ButtonI, который описывает ряд методов, необходимых для работы кнопок. Интерфейс кнопки с небольшими пояснениями: type ButtonI interface {
GetType() button_types.ButtonType // каждя кнопка должна знать свой тип для маршаллинга GetName() string GetFullName() string // пришлось добавить отдельный метод только для кнопки TOGGLE, т.к иногда есть потребность выводить ее сдвоенное имя // методы для работы с "командами" кнопок, это те действия, которые выполняются при нажатии GetCurrentCommand() *CommandType GetCommands() []*CommandType AddNewCommand(*CommandType) DeleteCommand(int) // .... тут есть еще несколько методов .... // методы для работы с деревом меню SetParent(*FolderButton) GetParent() *FolderButton GetButtons() *[]ButtonI AddButton(ButtonI) DelButton(int32) // переопределение методов маршаллинга/анмаршаллинга json MarshalJSON() ([]byte, error) UnmarshalJSON([]byte) error } Сериализация/десериализация меню Как вы могли заметить, интерфейс переопределяет методы Marshal/Unmarshal JSON, они используются для сохранения/загрузки пользовательского меню в/из базы. При сохранении каждому объекту обязательно проставляется его тип, чтобы можно было потом десериализовать. func (b *FolderButton) MarshalJSON() ([]byte, error) {
b.Type = b.GetType() return json.Marshal(*b) } С десериализацией немного сложнее, т.к заранее не известна структура пользовательского меню. Приходится рекурсивно десериализовать json в map[string]interface{} и кастовать каждый объект в кнопку определенного типа. Небольшая портянка кода парсераSPLfunc parseDataMap(dataMap *map[string]interface{}) ButtonI {
var outButton ButtonI fType, _ := (*dataMap)["Type"].(float64) switch button_types.ButtonType(fType) { case button_types.MULTI_VALUE: var multiValueButton MultiValueButton // заполнение нужных полей outButton = &multiValueButton case button_types.SINGLE_VALUE: var singleValueButton SingleValueButton // заполнение нужных полей outButton = &singleValueButton case button_types.TOGGLE: var toggleButton ToggleButton // заполнение нужных полей outButton = &toggleButton case button_types.FOLDER: var folderButton FolderButton folderButton.Name, _ = (*dataMap)["Name"].(string) buttons, _ := (*dataMap)["Buttons"].([]interface{}) // рекурсивный вызов парсинга для дерева folderButton.Buttons = make([]ButtonI, 0, len(buttons)) for _, button := range buttons { buttonMap, ok := button.(map[string]interface{}) if ok { ButtonI := parseDataMap(&buttonMap) folderButton.Buttons = append(folderButton.Buttons, ButtonI) } } outButton = &folderButton case button_types.SYSTEM: var systemButton SystemButton // заполнение нужных полей outButton = &systemButton case button_types.PRINT_LAST_SUB_VALUE: var printLastValueButton PrintLastValueButton // заполнение нужных полей outButton = &printLastValueButton case button_types.DRAW_CHART: var showCharButton DrawChartButton // заполнение нужных полей outButton = &showCharButton return outButton } Есть огромное предчувствие, что сохранять/восстанавливать меню таким способом немного костыльно, но лучше пока ничего не придумал. Буду искренне рад советам, как можно хранить пользовательское меню с разными типами кнопок и потенциально неограниченной вложенностью. Кодируем информацию в коллбэках Длина данных в коллбэке inline клавиатуры в телеграме (это меню, прикрепленное к сообщению) ограничина 64 символами. Для добавления в коллбэк к каждой кнопке всей необходимой информации я завел структуру следующего вида: type QueryDataType struct {
MessageId int64 Keyboard KeyboardType // тип inline клавиатуры Path []int32 // где мы находимся (для навигации по многоуровневому меню) Action ActionType // основное действие IntValue int32 // BoolValue bool // вспомогательные данные к действию Index int32 // } Данная структура сериализуется в protobuf и кодируется в base64. Итого 64 символа мне вполне хватает. А как бы сделали вы? Что дальше? Есть следующие идеи по улучшению функционала:
Буду рад любым вашим идеям в комментариях. Заключение Надеюсь, что статья была хоть немного полезна и не выглядит самопиаром. Славы и денег не ищу, это лишь мой небольшой вклад в open source и хабрасообщество. Два года назад я с головой ушел в веб-разработку, но все еще немного скучаю по железу, так и родился данный проект. Конечно, бот может быть еще сыроватым, но по реакции на статью я смогу понять, нужно это кому-то или нет, и стоит ли мне развивать его дальше. Всем добра! Ссылки: =========== Источник: habr.com =========== Похожие новости:
Интернет вещей ), #_diy_ili_sdelaj_sam ( DIY или Сделай сам ) |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 26-Ноя 10:15
Часовой пояс: UTC + 5