Search the Community
Showing results for tags 'mqtt'.
Found 6 results
-
Всем привет, в этой статье поговорим об уже надоевшей всем теме - "Метеостанция". Каждый пытается сделать что-то свое, вот и я не стал исключением и попытался материализовать свои эротические фантазии на контроллере ESP8266. Тема задумывалась уже давно как некое обновление для предыдущего проекта этой тематики, но из-за своей неспешности переросла в нечто самостоятельное. При всей привлекательности микроконтроллера ESP8266 с его большим объемом памяти, железной поддержкой Wi-Fi и массой разных плюшек, он не лишен недостатков. Самый основной - ограниченное количество поддерживаемых одновременных TCP соединений равное 5. Если превысить этот лимит, то контроллер потеряет связь с окружающим миром, при этом watchdog будет думать, что все в порядке, а следовательно, даже не попытается нам помочь. Будем стараться это помнить! Стоит начать с концепции Доступ к данным метеостанции нужно получать без установки внешних приложений и под любой операционной системой. Для этих целей подойдет практически любой современный браузер. Меня всем устраивает Chrome. Раз уж за основу взят HTTP протокол, стоит озаботиться экономией трафика и ограничением числа TCP соединений. Хорошим тоном будет передача всего необходимого для формирования страницы контента только при первом обращении, а все последующие операции, такие как отображение показаний с датчиков или настройку контроллера, производить через API. В этом нам поможет JQuery. А вот, чтобы ослабить болевые ощущения от передачи файлов с SPI Flash в браузер, стоит предусмотреть систему кэширования, например, Etag. Это позволит отдавать тяжелый контент единожды, а при последующих загрузках страницы просто подтверждать его актуальность на уровне Web сервера микроконтроллера и кэш браузера вступит в игру, неимоверно уменьшив время загрузки страницы! "Вы были правы в одном, Мастер: переговоры были недолгими." © Звездные войны. Эпизод 1 Из-за того, что метеостанция с датчиками и контроллером должна располагаться на улице, жизненно необходимо предусмотреть возможность обновлять прошивку ESP через Web интерфейс. Аналогичным образом должны обновляться файлы Web сервера расположенные на SPI Flash. Этот и предыдущий пункт вкупе позволят обновлять функционал микроконтроллера из домашней сети или из интернета, если конечно в этом возникнет острая необходимость. Чтобы никто посторонний не могу вмешаться в работу устройства или изменить файлы Web сервера, последний должен хотя бы как-то себя защищать. Пускать в панель управления только после авторизации, блокировать доступ при попытках брутфорса пароля. В конце концов, контроллер обязан самостоятельно генерировать ключи (salt) для авторизации, дабы сделать алгоритм непредсказуемым и исключить потенциальный взлом, в случае если злодей завладеет исходниками проекта. Понятно, что кому она там нужна, эта метеостанция, если её не завязывать с умным домом, если только из-за спортивного интереса, но как говориться “Береженого Бог бережет”. Датчики стоит расположить по уму - в метеобудке, а вот контроллер в сухом и закрытом боксе. Объединить их между собой, как мне кажется, удобнее по I2C шине - минимум проводов, максимум удобства. Практически на всех вариантах плат ESP-xx имеется штатный светодиод, можно воспользоваться им как для индикации режимов и состояния микроконтроллера, так и для вывода какой-либо промежуточной информации. Что касаемо режимов работы ESP8266, как ни странно, но он должен находить домашнюю Wi-Fi сеть и подключаться к ней. Если вдруг звезды не были к нам благосклонны, и домашняя беспроводная сеть приказала долго жить, контроллер обязан перейти в режим точки доступа (AP) дабы к нему можно было подключиться с какого-либо устройства и перенастроить его на другую сеть. А вот пока последнее не произошло, ESP должен периодически сканировать эфир в поисках долгожданной домашней точки доступа и, если боги были к нам милосердны, и домашняя сеть появилась в эфире, незамедлительно переключиться в режим клиента (STA) и в пылу страсти воссоединиться с ней. Ну и естественно, как же без отправки данных на внешние ресурсы, сейчас без этого не обходится ни одна уважающая себя кофеварка, не говоря уже о метеостанции. Думаю, что основным блюдом станет протокол MQTT, это уже облегчает возможность интеграции с умным домом, стулом или той же кофеваркой. Ну а на закуску добавим поддержку "ThingSpeak" и "Народного мониторинга". При желании можно нарастить функционал, благо памяти у микроконтроллера еще много. Как я себе это представляю Учтите, что на видео, данные с датчиков, эмитируются самим микроконтроллером, это нужно для наглядности. В жизни метеорологическая обстановка намного спокойнее слава Богу. Перейдем к физической сборки устройства Как по мне, так самый оптимальный вариант, это воспользоваться отладочной платой NodeMCU V3 и базой для неё. Таким образом, мы получим отличный комплект с разведенной на его борту всей необходимой обвязкой и возможностью питать устройство от 5 до 24 Вольт. Отладочная плата на базе, и смотрится хорошо, и удобства хоть отбавляй. Заливаем прошивку, образ SPI Flash и подключаем четырьмя проводами датчики. Справится даже ребенок. Ссылки: Базовая плата для NodeMCU V3 с преобразователем питания 5-24V в 5V Отладочная плата ESP8266 от NodeMCU Естественно никто не запрещает Вам развести свою плату. Если Вы это сделаете, скиньте нам свое творение, возможно мы перейдем на него. В идеале, все должно размещаться в метеобудке. Датчики взятые за основу Теперь настал момент озаботиться, где описанные выше ребята будут жить. В прошлый раз мы использовали для этих целей, найденную в подножном корме, электрическую распределительную коробку. Кроме дешевизны в этом решении нет ничего положительного. В этот раз мы воспользуемся более серьезным вариантом – "Метеорологическая будка Стивенсона". Она способна защитить датчики от прямых воздействий окружающей среды, но при этом имеет открытую структуру со стенками в виде жалюзи. Удобно, красиво и самое главное – правильно! Будка печатается на 3D принтере по эскизам опубликованным на Thingiverse неким kowomike, спасибо добрый человек! Архив с эскизами можно будет скачать в конце поста. Фото готовой будки Шпилька М8 крепится через зажимной хомут к мачте уличной антенны. Примерка. Шпилька практически не укорачивалась, чтобы не закрывать будку параболической Wi-Fi антенной. Хотя в моем случае все это сделано не правильно т.к это солнечная сторона дома. Доступа на теневую сторону дома у меня нет, поэтому приходиться довольствоваться тем, что имеем. По прошлой метеостанции мне говорили "на солнечной стороне все эти измерения - сферический конь в вакууме, слепи %описание-многА-букАв% и закрепи на теневой стороне дома". Я пока живу в панельном многоквартирном доме, как и не малая часть нашей страны. Доступ к теневой стороне дома (а для меня, по факту, это окна в подъезде) - прямой вызов всем гопникам района трущимся рядом, любопытным соседям с бегающими глазками и всей элите человечества скрашивающей фоном мою унылую и слишком простую, по их мнению, жизнь. Думаю, что мысль я донес. Датчики располагаются на разных уровнях. В основании находится датчик освещенности BH1750 и смотрит ровно вниз. Мне кажется, так он будет меньше пачкаться и покрываться пылью и при этом смотреть наружу сквозь минимальное количество препятствий для солнечного света. Вообще размещение этого датчика, это целая головная боль. Как не крути, все будет не то. Оставил так, ведь по сути важны не сами показания, а тенденция изменения. Хотя кого я пытаюсь обмануть, точность важна всегда! Предлагайте свои варианты. Намного проще обстоят дела с датчиком атмосферного давления BMP180 и влажности SI7021, кстати, с последнего мы также будем забирать данные о температуре. Их размещаем в оставшемся свободном пространстве будки, благо его там с избытком, но не в конусе т.к пространство в нем менее проветриваемое. Все хозяйство подключается между собой следующим образом NodeMCU | ESP 07/12 | Датчики ----------------------------- D2 | GPIO 4 | SDA D1 | GPIO 5 | SCL 3.3V | 3.3V | 3.3V GND | GND | GND ВАЖНО: при финальном монтаже устройства на его место службы, обязательно установите перемычку между пинами GPIO 0 (D3) и питанием 3.3 Вольта. Причины её установки описаны в закрепленном сообщении с описание обновления от 12.08.2017. Сам микроконтроллер будет спрятан в уже знаменитую распределительную коробку, закрепленную на шпильке, чуть ниже будки Стивенсона. У меня все находится на стадии неторопливой сборки с попутным поиском более удачных идей. Плата расширения, на которой будет установлена плата NodeMCU, закреплена через ножки для крепления компьютерных материнских плат в корпусах. Разъемы для подключения внешних датчиков и питающей линии установил на местах где была пара штатных заглушек. Закрепил все через переходную пластину, выпиленную из куска фольгированного текстолита. Естественно, предварительно пластина была протравлена, а вся медь искоренена, ибо в этом случае она нам не друг. Также была предусмотрена проставка из полиэтиленового поролона (используется в качестве упаковочного материала при транспортировке грузов) между текстолитом и корпусом, общей толщиной 5мм, а после затяжки крепежных винтов, его толщина не превышает 1мм. Это было сделано из-за опыта эксплуатации предыдущего (временного) бокса для этой метеостанции. Без проставки влага быстро найдет путь вовнутрь, и срок службы устройства снизится. Производим примерку. При окончательном монтаже обязательно необходимо удалить все не плотно прилегающие части полиэтиленового поролона, то есть те части, которые располагаются снаружи и не сдавлены крепежной текстолитовой пластиной. Это необходимо сделать для препятствования накоплению влаги в доступных для неё полостях. Также пришлось увеличить число крепежных болтов для более надежного прилегания текстолита, в противном случае он может выгибаться. Все самое сложное позади, остается только вывести на один разъем шину i2c с питание 3.3 Вольта, а на другой подвести пины питания платы расширения. Но т.к у меня валялся "хвост" отрезанный когда-то от не рабочего блока питания маршрутизатора, и я не побрезговал им воспользоваться по прямому назначению. Далее останется все подравнять, проверить качество монтажа, возможность замены платы NodeMCU, если это будет необходимо при эксплуатации и самое главное, дважды проверить, что и куда припаяно. Мои кривые руки и невнимательность уже наказывали меня, а т.к ждать новые запчасти долго, повторять не хочется. Общий вид получился таким А вот как все выглядит в боевых условиях. Кстати, могу предложить идею с помещением в бокс мешочка содержащий впитывающий влагу гель, они часто встречаются в коробках с обувью. Если все герметично, то он впитает остатки влаги, а если нет, то лишним уж точно не будет. Требования (!!!Читать обязательно!!!) Arduino IDE с поддержкой контроллера ESP8266, версия 2.6.2 (на версиях выше работоспособность не проверялась) Установленный модуль в Arduino IDE для загрузки файлов во Flash память микроконтроллера. Как установить описано тут. Для работы модуля загрузки файлов во Flash может понадобится последняя версия Python https://www.python.org/downloads/ Любой модуль на базе ESP8266 c Flash 4MB (3MB выделяем под SPIFFS) В параметрах выставляем lwIP версии 2 и максимальную производительность (lwIP v2 Higher Bandwidth) Сам архив с последней версией проекта. Скачать можно в конце статьи или по этой ссылке. Обязательные библиотеки (!!!Читать обязательно!!!) ArduinoJson (v5.13.5) PubSubClient Ссылки на библиотеки сенсоров указаны в комментариях к коду. Сами библиотеки, как и обслуживаемые ими сенсоры, не являются обязательными. Вы вольны использовать любые датчики, как физические, так и программные. Порядок установки (!!!Читать обязательно!!!) Изучите файлы проекта с примерами использования тех или иных сенсоров. Все файлы с примерами начинаются с префикса users_, это users_auto.h, users_bme280_x2.h и т.д. Загрузите необходимые Вам библиотеки или используйте эти файлы как пример для добавления иных датчиков. Выставите необходимые настройки для контроллера в среде разработки Arduino IDE. Пример настроек указан на скриншоте выше. Обязательно убедитесь, что выбрано правильное распределение места для внутренней файловой системы, это значит, что 3MB должно быть выделено под файловую систему. Также проверяем, чтобы использовался lwIP v2 в режиме максимальной производительности (lwIP v2 Higher Bandwidth). Произведите загрузку программы с помощью среды разработки (Ctrl + U). Произведите загрузку содержимого каталога data в файловую систему. Меню/Инструменты/ESP8266 Sketch Data Upload Перед тем как устанавливать метеостанцию на постоянное место жительства, подтянуть GPIO-0 (пин D3 на плате NodeMCU) к питанию 3.3V. Во время данной процедуры, питание на контроллере должно отсутствовать. Первый запуск (!!!Читать обязательно!!!) Помните, что вся конфигурация микроконтроллера производится исключительно через web интерфейс. Никаких изменений значений тех или иных параметров в коде не требуется, а подобную практику будем считать плохим тоном. И так, после запуска микроконтроллера он сразу перейдет в аварийный режим и поднимет собственную точку доступа с именем WeatherStation. Это нормальное поведение т.к подразумевается использование метеостанции в домашней беспроводной сети, ну а раз о ней пока ничего не известно, то и подключаться не к чему. Подключитесь к данной сети с любого удобного устройства и перейдите в панель управления (для этого имеется соответствующая иконка, запутаться невозможно), контроллер будет доступен по адресу http://espws.local или http://192.168.4.1 При попытке входа в панель управления будет запрошено имя пользователя и пароль, по умолчанию admin/admin. После входа в панель управления перейдите в раздел "Основные настройки WiFi" и укажите имя и пароль Вашей домашней сети, а также, при необходимости, укажите пароль для подключения к точке доступа поднимаемой контроллером в аварийном режиме. Если все сделано правильно, то контроллер подключится к домашней сети в течении 5-и минут. Если Ваша домашняя сеть скрыта, то после первоначальной настройки необходимо перезагрузить контроллер. Это необходимо из-за частичной поддержки работы со скрытыми сетями. После перезагрузки контроллер увидит Вашу сеть и запомнит её MAC адрес. Помните об этом если захотите сменить домашний маршрутизатор. Хотите помочь проекту или спонсировать новый? Yandex.Money PayPal.me Файлы
- 859 replies
-
- 5
-
- web server
- mqtt
- (and 8 more)
-
Всем привет. Начнем как всегда с предыстории! Цель, сбор данных с различных устройств на очень большой территории промышленного предприятия. Естественно финансирование просто огромное, хватит на большую пачку семечек и пачку индийского чая, возможно даже сдача останется, но не факт. А Вы как думали? В сказку попали? В связи с этим устройства самодельные, на базе популярных микроконтроллеров фирмы ATmel с прикрученным Ethernet. И естественно самый простой способ обмена не критической информацией - MQTT протокол. Т.к. предприятие не малое, цеха большие, то IT оборудование раскидано на агрегатах и для защиты от воздействия внешней среды, оно находится в гермозонах. Температура внутри поддерживается кондиционерами, но они, как и все в этом мире, стремятся раствориться в небытии, и с легкостью потянут за собой все оборудование, что трудится в гермозонах на благо предприятия. И так. Для начала Вам понадобится свой брокер c MQTT клиентом. Хотя у Вас он скорее всего уже есть. Его роль может выполнять как сам zabbix сервер, так и отдельная машина. В последнем случае дополнительно потребуется установка zabbix агента. Все инструкции и дистрибутивы имеются на официальном сайте mosquitto.org Я использую в качестве операционной системы Linux Ubuntu и для меня все очень просто. sudo apt-get install mosquitto mosquitto-clients Настройка брокера - это отдельная тема и затрагивать её здесь не будем. Вносим дополнения в конец файла конфигурации zabbix агента на сервере nano /etc/zabbix/zabbix_agentd.conf Добавляем пользовательский параметр MQTT UserParameter=mqtt[*],/usr/bin/mosquitto_sub -h 127.0.0.1 -t $1 -C 1 -N 2>/dev/null Сброс ошибок в /dev/null необходим из-за возможности их появления в случае запуска клиента от пользователя, у которого нет собственного домашнего каталога. В противном случае можно получить в довесок к выводу сопроводительное письмо со следующим текстом. Переходим в панель управления Zabbix. Теперь нам доступна возможность создавать элементы данных содержащих ключи следующего вида. mqtt[адресс необходимого топика] mqtt[/room213/door1/lock/status] mqtt[/garage/smoke/level] mqtt[/room/sensors/temperature1] Каждый ключ соответствует адресу топика с которого нужно взять ту или иную информацию. И сама структура топика просто идеальна для иерархии оборудования или ветвления расположений объектов. Но Вы и так это видите. Создаем узел сети с ip адресом Zabbix сервера или просто 127.0.0.1, тип проверки "Zabbix агент". Указываем интересующий нас топик и слушаем. ВАЖНО: Клиенты рассылающие сообщения должны использовать параметр "-r, --retain" для сохранения сообщения у брокера. Без этого параметра Zabbix не сможет получить данные т.к не поддерживает постоянную связь с брокером, а лишь забирает последние данные по установленному интервалу времени. Команда для отправки тестовой строки "message" в топик "test/string" /usr/bin/mosquitto_pub -h 127.0.0.1 -r -t test/string -m "message" Для начала мне потребовалось снимать показания температуры внутри и снаружи гермозон. Это экстренная необходимость т.к в случае ч.п, в зимнее время, температуры внутри может превышать 30 градусов Цельсия, а в летнее температура пола рядом со зданием в котором находится гермозона может превышать 70-80 градусов Цельсия, а про температуру оборудования я вообще молчу. И да, мы не конфеты делаем, к сожалению... Для меня удобнее создать шаблон, который будет добавлен к серверу с MQTT брокером. Это дает гибкость в случае замены самого брокера или миграции сервера на другую платформу. Интересующий нас ключ отмечен на рисунке, все остальное на Ваше усмотрение. Все данные показанные ниже идут с тестового стенда, установленного за пределами экстремальных зон! Так выглядят первые элементы данных. Это показания с двух датчиков температуры и данные с внутреннего таймера микроконтроллера. Последние показания нужны для отслеживания зависания устройства. такое и мало вероятно. Но оно и имеет сторожевой таймер, лишняя перестраховка в промышленных масштабах не помешает. Текущие показания. Ну и естественно график, куда без него. Прост и так скудный) Можно организовать скрипт, поддерживающий несколько входных параметров, но это уже вопрос желания и необходимости.
-
Доброе время суток. Сегодня пойдет речь о том, как переправлять данные с MQTT брокера в базу данных MySQL. Транспортировать будем как сами адреса, так и значения всех топиков, на которые оформлена подписка. Данную задачу нельзя назвать распространенной, но все же, она имеет место быть и может пригодиться в том случае, если данные востребованы в системах не способных работать с MQTT протоколом самостоятельно или брокер находится в изолированной системе, а данные востребованы, например, в GUI за её приделами. Для осуществления задуманного нам потребуется самостоятельный процесс, который сыграет роль транспортного узла между MQTT брокером и базой данный MySQL. А значит, его придется где-то держать. В моем случае, это сервер под управлением операционной системой Linux Ubuntu 16.04.3 и дальнейшее описание будет под неё, но для других ОС действия аналогичные. Сам демон будет написан на Python и для его работы нам потребуется: python3 python-pip python-dev libmysqlclient-dev библиотека paho-mqtt для python https://pypi.python.org/pypi/paho-mqtt библиотека mysqlclient для python https://github.com/PyMySQL/mysqlclient-python Но начнем мы, в первую очередь, с подготовки базы данных. На плечи MySQL ляжет не только хранение, но и частичная обработка данных. Для этих целей нам потребуется отдельная база, хранимая процедура и функция с реализацией небольшой логики, и пользователь с ограниченными правами под чьим именем мы будем обращаться к ним. В конце поста, кроме самого демона, будет опубликован .sql файл, который достаточно просто импортировать, например с помощью стандартный средств базы данных mysql -uroot -p Вводим пароль администратора MySQL и импортируем наш .sql файл, но прежде, дочитайте статью до конца, возможно, Вы захотите внести свои изменения. mysql> source /media/mqttMySqlClient.sql После этого будет получен следующий результат: Создана база (схема) с именем mqtt Пользователь с именем mqtt-agent и паролем p@$$w0rd имеющий возможность подключаться с внешних адресов Пользователю будут назначены ограниченные права (только EXECUTE) в этой схеме Будет добавлена процедура update_topic, на которую ляжет задача добавления и обновления данных Будет добавлена функция get_topic для упрощения поиска данных На тот случай, если Вы захотите внести изменения или создать все ручками, рассмотрим содержимое sql файла. Если схема mqtt не существует, она будет создана CREATE DATABASE IF NOT EXISTS `mqtt`; Аналогичным образом будет создан пользователь mqtt-agent. Если необходимо конкретизировать, с какого адреса будет производиться подключение под этим пользователем, то замените % на доменное имя или ip адрес хоста. Если планируется использовать демона на том же сервере где установлен MySQL, замените % на localhost. Также разрешено не более 2 активных подключений, измените это значение на необходимое Вам. CREATE USER IF NOT EXISTS 'mqtt-agent'@'%' IDENTIFIED BY 'p@$$w0rd' WITH MAX_USER_CONNECTIONS 2; Пользователю будут выставлены ограниченные права. Ему будет разрешено пользоваться только хранимыми процедурами и функциями. Никакие самостоятельные запросы выполнять нельзя. GRANT EXECUTE ON `mqtt`.* TO 'mqtt-agent'@'%'; Переходим к работе с самой схемой USE `mqtt`; Будет создана таблица topics со следующей структурой. md5 - содержит уникальный одноименный хеш полученный из полного адреса топика. Именно по этому ключу и будет производиться поиск данных. Почему именно по нему, а не по самому имени? Дело в том, что md5 хеш имеет фиксированную, заранее известную, длину, что нельзя сказать о имени топика. Именно это ограничение не позволит сделать имя топика первичным ключом и явно идентифицировать данные в таблице. time - содержит UNIX время добавления/обновления данных по конкретному топику (по умолчанию GMT+0) topic - адрес топика. В контексте, упомянутого ранее, поля md5, не несет для нас никакой смысловой нагрузки. value - данные опубликованные в топике. DROP TABLE IF EXISTS `topics`; CREATE TABLE `topics` ( `md5` varchar(32) NOT NULL, `time` bigint(20) DEFAULT NULL, `topic` text, `value` text, PRIMARY KEY (`md5`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; Теперь необходимо создать хранимые процедуры и функции, но сделать это будет невозможно из-за присутствие в их синтаксисе разделителя совпадающего с концом данных в sql запросе - ";" Чтобы избежать этот неловкий момент, изменяем разделить на произвольный. DELIMITER $$ Создает процедуру update_topic. Она принимает в качестве входных параметров два значения, адрес топика и опубликованные данные. Оба параметра являются текстовыми. Процедура, вычисляет md5 хеш из адреса топика и уже по нему производит поиск записи в таблице. Если запись не будет найдена, она будет создана, в противном случае данные в поле value будут обновлены. Данная процедура должна ускорить работу демона и избавить его от задержек, которые были бы неминуемы при выполнении этих же запросов на стороне клиента. DROP PROCEDURE IF EXISTS `update_topic`$$ CREATE DEFINER=CURRENT_USER() PROCEDURE `update_topic`(topic text, value text) BEGIN declare nMD5 varchar(32) default md5(topic); declare NUM bit; declare uTime bigint(20) default UNIX_TIMESTAMP(); SELECT COUNT(t.md5) INTO NUM FROM topics t WHERE t.md5 = nMD5; if NUM <> 1 then INSERT INTO topics VALUES(nMD5, uTime, topic, value); else UPDATE topics t SET t.time = uTime, t.value = value WHERE t.md5 = nMD5; end if; END$$ Также будет добавлена функция get_topic. Она необходима для запроса данных от имени созданного ранее пользователя и ограничений, наложенных на него. Функция принимает адрес топика в текстовом виде, производит вычисление md5 хеша и основываясь на его совпадении с имеющимися записями выводит значение поля value искомого топика. DROP FUNCTION IF EXISTS `get_topic`$$ CREATE DEFINER=CURRENT_USER() FUNCTION `get_topic` (topic text) RETURNS text BEGIN declare hMD5 varchar(32) default md5(topic); declare topicValue text; SELECT t.value INTO topicValue FROM topics t WHERE t.md5 = hMD5; RETURN topicValue; END$$ И в завершении всего, будет восстановлено стандартное значение разделителя. DELIMITER ; На этом разбор sql файла можно считать законченным. Он не содержит каких-либо сложным манипуляций и должен быть понятен. Все эти операции можно выполнить руками, но я совету воспользоваться импортом, как и было описано ранее. Переходим к демону В первую очередь устанавливаем необходимые пакеты. sudo apt-get install python3 python-pip python-dev libmysqlclient-dev Устанавливаем недостающие библиотеки для Python pip install paho-mqtt mysqlclient Добавим пользователя из-под которого будет запускаться демон sudo useradd --shell /usr/sbin/nologin --system mqtt-agent Выставляем все необходимые права (каталог /media как пример) sudo chown mqtt-agent:mqtt-agent /media/mqttMySqlClient.py sudo chmod 0700 /media/mqttMySqlClient.py Добавляем демона в автозагрузку через планировщик задач и от имени созданного пользователя sudo crontab -u mqtt-agent -e Добавляем в конец следующую запись @reboot /media/mqttMySqlClient.py start Запускаем демона от имени все того же пользователя sudo -u mqtt-agent /media/mqttMySqlClient.py start Это основное, что требуется сделать на сервере для организации работы демона. Переходим к разбору настроек программы Т.к изначально за основу была взята концепция другого демона из ветки Zabbix, то конфигурация перекочевала оттуда и аналогично разбита на несколько секций. """ Настройки MQTT """ mqtt_server = "mqtt.it4it.club" mqtt_port = 1883 mqtt_login = "" mqtt_password = "" mqtt_client_id = "mqttMySqlClient" Настройки подключения к брокеру. Все должно быть интуитивно понятным. Помните, что при совпадении идентификаторов клиентов, они начнут конкурировать за подключение и по очереди терять связь. Не допускайте их совпадений. """ Список топиков для подписки """ subscribe = { '$SYS/#', '#', } Список топиков для подписки указывается через запятую и в кавычках. """ Настройки MySQL """ mysql_host = "127.0.0.1" mysql_port = 3306 mysql_user = "mqtt-agent" mysql_passwd = "p@$$w0rd" mysql_schema = "mqtt" mysql_log_file = "/var/log/mqttMySqlClient.log" Настройки подключения с MySQL серверу также не должны вызывать вопросов. """ Настройки общие """ pid_file = "/tmp/mqttMySqlClient.pid" Последний параметр указывает на размещение .pid файла демона. Команды управления классические start - запуск в режиме демона stop - остановка в режиме демона restart - перезапуск в режиме демона window - запуск в оконном режиме, также позволяет запускать процесс в операционных системах Windows После запуска, демон пытается установить связь с MQTT брокером и пока это не произойдет, связь с MySQL сервером устанавливаться не будет. Если во время работы, связь с брокером будет потеряна то в принудительном порядке, будет разорвано соединение с базой данных. Таким образом, по активным сессиям MySQL сервера можно судить о наличии связи у демона с брокером. Во время простоя, а в нашем случае, это отсутствие потока данных от брокера, для проверки связи с сервером базы данных будет использована процедура эмуляции ping для MySQL сервера. Она представляет из себя простую арифметическую задачу на сложение не приводящей к работе с данными в базе. Операция выполняется крайне быстро и её удачное выполнение сигнализирует клиенту о наличии связи с базой, а базе об активности клиента. В связи с этим, периодическая активность клиента при отсутствие данных от брокера, является показателем нормальной работы. И на последок Если вы хотите произвести выборку из таблицы под именем пользователя используемым по умолчанию и с его ограничениями. Необходимо воспользоваться функцией get_topic с указанием полного адреса, интересующего топика. select mqtt.get_topic('$SYS/broker/version'); В ответ мы получим +---------------------------------------+ | mqtt.get_topic('$SYS/broker/version') | +---------------------------------------+ | mosquitto version 1.4.8 | +---------------------------------------+ 1 row in set (0,00 sec) На этом пока все. Файлы проекта: PS: Это тестовая версия демона и возможно она будет претерпевать некоторые изменения.
-
-
-
Привет друзья. В данной теме пойдет речь о конфигурации микроконтроллера через UART (Universal Asynchronous Receiver-Transmitter) интерфейс. А рассмотрим мы это на примере MQTT логгера. В данном случае, это будет логгер температуры. Мне это устройство потребовалось на работе, даже не мне, а моим коллегам, и оно действительно работает и приносит огромную пользу т.к контроль температуры производится совместно с отличной, на мой взгляд, системой мониторинга Zabbix с оперативными оповещениями, построением графиков, блэк-джеком и... Подробнее о дружбе Arduino и Zabbix можно почитать тут Но как всегда, есть нюансы. А заключаются они в том, что в будущем, обслуживать армию мелких контроллеров придется людям, которые заняты своими задачами и им попросту некогда изучать Arduino, не говоря уже о серьезных альтернативах, разбираться в том, как прописать нужные значения переменных в программу и загрузить её в микроконтроллер. Все настройки необходимо производить быстро, с явным указанием изменяемого параметра и его значения. Ровно также, как это делается с любым промышленным оборудованием. И тут на помощь приходит UART Микросхема UART to USB имеется в большинстве плат семейства Arduino, а там, где её нет, обычно выведены соответствующие "пины". И все это очень облегчает жизнь т.к позволяет общаться с контроллером, просто подключив его к компьютеру напрямую или через переходник, благо их везде навалом, да и стоят они как пачка семечек. Остается только запустить любой терминал, который умеет доставлять в конец строки символ "перевод строки", что известен в народе как "\n", а в ASCII таблице имеет номер 0A. Кстати, в Serial мониторе Arduino IDE выставить символ конца строки можно так Ну а дальше только, что и остается, как общаться с устройством на той стороне. И тут мы переходим к основному алгоритму программы. Но перед этим хочу отметить, и это ВАЖНО, что за любое упрощение жизни, всякие красивости и прочее, приходиться платить, и цена довольно высока! В данном случае, это ОЗУ микроконтроллера. Поэтому не раскатываем губы, а если очень хочется, то берем следующий по характеристикам микроконтроллер. А начинать мы будем с ATmega328P, что известен в народе как Arduino UNO, Arduino Nano, IBoard v1.1 и т.д по списку. Заканчивать Вы можете чем угодно, хоть ATmega2560, ESP8266 или ESP32. В противном случае, производим оптимизацию кода, отказываемся от громоздких библиотек, или вообще, от Arduino IDE. Что мы хотим получить Вся конфигурация микроконтроллера должна храниться в энергонезависимой памяти (ПЗУ) известной нам как EEPROMM. Если в ПЗУ конфигурация отсутствует, необходимо иметь резервный план. И им станет сброс конфигурации на настройки по умолчанию. Это поведение знакомо всем, особенно по домашним дешевым маршрутизаторам, а значит, интуитивно понятно. Выводить справку при начале общения пользователя и устройства, на мой взгляд, как манеры высшего общества. Контроллер должен представляться и сообщать всю необходимую информацию о себе и о том, как с ним вести диалог. Все команды должны быть просты и иметь не двусмысленное значение. И конечно, мы должны иметь возможность просмотра текущего состояния датчиков или процессов, которыми занимается устройство в свободное от общения с нами время. Как сохранять конфигурацию в EEPROM Пожалуй, стоит начать с того, как сохранить конфигурацию микроконтроллера в энергонезависимую память. Для этих целей, в стандартный набор инструментов Arduino IDE входит библиотека для работы с EEPROM. #include <EEPROM.h> На данный момент нас интересуют две функции, это чтение и запись EEPROM.get(address, variable); EEPROM.put(address, variable); Обе принимают два параметра: Адрес, начиная с которого будет произведено чтение или запись данных в память Переменная чье содержимое надо сохранить или в которую нужно из памяти прочитать Особенность работы этих функция заключается в том, что в зависимости от типа переданной им переменной во втором параметре, будет произведено чтение или запись ровно того количества данных которое соответствует размеру типа этой самой переменной. На простом языке это означает, что если переменная variable будет иметь типа byte, то и работать мы будем с объемом памяти в 1 байт. И тоже самое произойдет с абсолютно любым типом данных пока мы не упремся в размеры самого EEPROM или ОЗУ микроконтролера. Из этого всего следует, что мы можем создать свой собственный тип данных, разместить в нем необходимую нам информацию и всего лишь двумя функциями помещать его в память и извлекать обратно. И в этом нам поможет пользовательский составной тип - структура (struct). Данный тип позволяет объединить в себе различные типы данных, упорядочить их и присвоить им понятные имена. Это общий пример для большего понимания, как объединить несколько типов данных в одной структуре, получить к ним доступ, записать и прочитать их из EEPROM. Наша структура будет немного сложнее, но суть остается той же самой. // Дополнительная структура описывающая IPv4 адреса struct addres { byte a; byte b; byte c; byte d; }; // Структура объекта конфига для хранения в EEPROM struct configObj { addres ip; addres subnet; addres gateway; addres dns; byte mac[6]; byte hex; char server[40]; char topic[40]; } config; Данная структура хранит сетевые настройки для работы с Ethernet модулем (w5100 и выше) Arduino, базовые настройки для связи с MQTT брокером. Сразу при описании структуры мы объявили новую переменную с именем config с типом нашей структуры. ВАЖНО: кроме наших данных в структуре имеется дополнительная переменная с именем hex. Её задача, это контроль наличия наших данных в EEPROM. Она всегда должна содержать одно и тоже значение. Представьте ситуацию, что вы взяли контроллер в EEPROM которого находится какая-либо информация (может там чисто, но мы этого не знаем наверняка) и мы прочитаем данные и поместим их в нашу переменную. В итоге мы получим данные которым нельзя доверять, а что еще хуже, это если эти самые данные нарушат работу внешнего оборудования. Более правильным, на мой взгляд, будет проверка значений по конкретно определенным адресам. Например, мы знаем, что в 16 байте должно быть значение 0xAA и если оно действительно там, то мы убеждаемся, что это наша информация. Естественно, что контрольных точек может быть несколько и разумеется с разными значениями, это увеличит гарантию того, что данные являются нашими, но 100% гарантии не даст. Для более серьезных проектов есть более серьезные методы, например, подсчет контрольной суммы всего набора данных. Также структура может иметь вложенные структуры, у нас ими являются: ip, subnet, gateway, dns. Вы можете отказаться от такого варианта и записывать данные просто в массив байт, как это было сделано с MAC адресом. Естественно, что обращаться к этим полям нужно по-разному. Запись данных в поле subnet config.subnet = {255, 255, 255, 0}; Запись данных в поле mac byte mac[] = {0x00, 0xAA, 0xBB, 0xCC, 0xDE, 0x02}; memcpy(config.mac, mac, 6); С записью данных в поле server все еще проще config.server = "mqtt.it4it.club"; Функция, которая возвращает нашу структуру данных с полностью заполненными полями. // Начальный конфиг configObj defaultConfig() { configObj config = { {192, 168, 0, 200}, {255, 255, 255, 0}, {192, 168, 0, 1}, {192, 168, 0, 1}, {0x00, 0xAA, 0xBB, 0xCC, 0xDE, 0x02}, 0xAA, // Не трогать! Используется для проверки конфигурации в EEPROM F("mqtt.it4it.club"), F("arduino/serial/config") }; return config; } К примеру, два последних значения записывать не обязательно, тогда эти поля останутся пусты если использовать данную функцию для возврата к "заводским" настройкам. Вот пример того, как используя описанную нами структуру, мы проверяем целостность настроек в EEPROM и в случае не совпадения hex значений, загружаем настройки по умолчанию. const byte startingAddress = 9; bool configured = false; void loadConfig() { EEPROM.get(startingAddress, config); if (config.hex == 170) configured = true; else config = defaultConfig(); configEthernet(); // Функция производящая настройку сети } Как контроллеру начать понимать, что от него хотят В Arduino имеется функция, вызываемая каждый раз, когда в передаваемый буфер данных попадает знакомый нам символ перевода строки. void serialEvent() { // Вызывается каждый раз, когда что-то прилетает по UART // Данные передаются посимвольно. Если в строке 100 символов, то функция будет вызвана 100 раз } И в контексте обсуждаемой нами программы, мы можем представить ее в следующем виде void serialEvent() { serialEventTime = millis(); if (console.available()) { char c = (char)console.read(); if (inputCommands.length() < inputCommandsLength) { if (c != '\n') inputCommands += c; else if (inputCommands.length()) inputCommandsComplete = true; } } } Её задача, символ за символом, собрать в кучу все переданные нами данные и при получении заветного символа перевода строки (именно он даст нам понять, что передача сообщения завершена) сообщить, что команда получена и передать накопленный буфер данных своей напарнице по цеху. Но перед тем как это сделать стоит рассмотреть альтернативную ветку развития событий, а именно тот факт, что нам попросту могут прислать огромную, кучу шлака без волшебного символа, а раз могут, значит рано или поздно пришлют. И мы бесполезно потратим ценные ресурсы микроконтроллера, что может привести к непредсказуемым результатам в дальнейшем. Поэтому, в логику функции, мы добавим дополнительное ограничение на количество переданных символов, если оно достигнуто, то попросту перестаем воспринимать последующие данные. Останется только избавиться от них, и самым удобным моментом будет, когда этот поток шлака прекратиться. Чтобы об этом узнать мы будем запоминать время, когда пришел каждый из символов переданной строки перезаписывая соответствующую временную переменную данными о следующем символе и т.д пока поток не иссякнет. И как только расхождение текущего времени CPU и времени, когда поступил последний символ превысит некоторое значение, пусть это будет 1 секунда, мы очистим нашу память. Этот простой механизм напоминающий амнезию позволит избавить нас от лишних проблем. Переменная отвечающая за размер принимаемого буфера const byte inputCommandsLength = 60; Теперь можно переходить к напарнице предыдущей героини и, по совместительству, основной рабочей лошадки - функции обрабатывающей адекватно полученные данные. void serialEventHandler() { // вызывается в loop и проверяет взведена ли переменная inputCommandsComplete // в полученных данных пытается распознать команды } По началу я хотел описать данную функцию в упрощенном варианте, но в процессе понял, что ничего хорошего из этого не выйдет, и я решил описать только ключевые моменты, но их будет достаточно, на мой взгляд. Разбор serialEventHandler Полученные данные будут переданы нам в переменной inputCommands с типом String В первую очередь стоит почистить ее от лишних пробельных символов. Они часто встречаются в начале и в конце строки если пользователь копирует текст, а не набирает его самостоятельно. Это распространенная ситуация, приводящая к отказу принятия команды и бороться с ней очень просто. inputCommands.trim(); Далее стоит отсеять команды, не несущие никакой динамической информации, например, help, restart, reset и т.п это предписывающие команды которые заставляют контроллер выполнять строго описанные функции без вмешательства в их работу. if (inputCommands == F("help")) { consoleHelp(); } else if (inputCommands == F("restart")) { resetFunc(); } else { // Все сложные команды обрабатываются в этом блоке } Как Вы видите, все очень просто и скучно. Но не в том случае если команда динамическая, то есть содержит не только саму команду (заголовок) но и полезную нагрузку (параметр) которая может меняться раз от раза. Простой пример это команда изменения ip адреса и её варианты: ip 37.140.198.90 ip 192.168.0.244 ip 10.10.10.88 В данном случае, нам стоит понять, относится ли данная команда именно к ip адресу. Для этого в наборе String имеется отличный метод, позволяющий производить сравнение переданного ему параметра с началом строки. if (inputCommands.startsWith(F("ip"))) { // Строка inputCommands начинается с пары символов "ip" } Если все идет так, как мы задумали, то нам стоит отделить динамическую часть - наш параметр, от заголовка и получить полезную нагрузку. В этом нам поможет, опять же из набора String, метод substring позволяющий получать часть строки с указанием начального и конечного символа подстроки. Последний параметр указывать не обязательно и в таком случае мы получим всю строку начиная с указанного символа. inputCommands.substring(4) В данном случае начиная с 4-его и заканчивая последним. И как Вы успели заметить, отсчет мы начинаем не с третьего символа, что соответствует нашей строке без вступительного "ip", а на один больше т.к между заголовком и параметром имеется разделяющий символ в виде пробела. Далее, полученную строку мы передадим в функцию, занимающуюся разбором на компоненты и принимающую следующие параметры: Указатель на переменную с типом char, для этого нам потребуется преобразовать наш тип String Символ разделителя, что для IPv4 является точка "." Указатель на массив типа byte, которому будет присвоен результат разбора Количество искомых элементов в строке И система счисления, подразумеваемая в качестве исходной для записи элементов подстроки /* Парсинг https://stackoverflow.com/questions/35227449/convert-ip-or-mac-address-from-string-to-byte-array-arduino-or-c */ void parseBytes(const char* str, char sep, byte* bytes, int maxBytes, int base) { for (int i = 0; i < maxBytes; i++) { bytes[i] = strtoul(str, NULL, base); str = strchr(str, sep); if (str == NULL || *str == '\0') break; str++; } } В нашем случае выглядеть это будет следующим образом byte ip[4]; parseBytes(inputCommands.substring(4).c_str(), '.', ip, 4, 10); А дале все становится еще проще, попросту проверить попадает ли наш ip адрес, в список правильных адресов. И самой простой проверкой послужит проверка первого байта адреса на несоответствие не угодным нам сетям (0, 127, 255) if (ip[0] != 127 and ip[0] != 255 and ip[0] != 0) { // Производим необходимые нам действия с ip адресом, например, запись в конфиг config.ip = {ip[0], ip[1], ip[2], ip[3]}; } Вы в праве реализовать собственные проверки, какие только душе угодны. Также хотелось бы отметить, что обрабатывать некоторые параметры проще и быстрее через их короткие записи. К таким можно отнести маску подсети устройства. Например, привычный дня нас адрес 192.168.0.1 с маской подсети 255.255.255.0 можно записать в виде 192.168.0.1/24, где цифра 24 указывает нашу подсеть в краткой форме. А, следовательно, мы можем записать несколько кратких форм масок подсети в следующем виде: subnet 255.255.255.0 или subnet 24 subnet 255.255.0.0 или subnet 16 subnet 255.0.0.0 или subnet 8 Это основные маски, и я не описывал все существующие т.к в этом нет нужды, но если Вам интересно, то почитать про них можно в wikipedia. if (inputCommands.startsWith(F("subnet"))) { String input = inputCommands.substring(8); if (input == F("24")) config.subnet = {255, 255, 255, 0}; else if (input == F("16")) config.subnet = {255, 255, 0, 0}; else if (input == F("8")) config.subnet = {255, 0, 0, 0}; else { // Все остальные маски попадают в этот блок byte subnet[4]; parseBytes(input.c_str(), '.', subnet, 4, 10); config.subnet = {subnet[0], subnet[1], subnet[2], subnet[3]}; } } MAC адрес хранится у нас в виде массива байт. Его перезапись другим массивом производится с помощью функции memcpy if (inputCommands.startsWith(F("mac"))) { byte mac[6]; parseBytes(inputCommands.substring(4).c_str(), ':', mac, 6, 16); memcpy(config.mac, mac, 6); } Изменение адреса MQTT сервера if (inputCommands.startsWith(F("server"))) { String server = inputCommands.substring(8); server.trim(); if (server.length() < 40) server.toCharArray(config.server, 40); } В принципе теперь понятно, как производить получение, разбор и сохранение конфигурации в EEPROM микроконтроллера. Как это выглядит на практике Заливаем программу в микроконтроллер и подключаемся к Arduino по usb или через переходник. Открываем терминал и нас приветствуют краткой справкой с описанием доступных команд. - --------------------------------------------------------------------------------------- # Sensor with data sending to mqtt server (c) it4it.club # Use the "config" command to view the current configuration # To change the configuration, specify the parameter name and its new value with a space, # for example "ip 192.168.0.200", "subnet 255.255.255.0" or "mac AA:BB:CC:DD:EE:FF" # You can also specify a subnet using the mask 24, 16 or 8 # Additional commands: # sensors - view current data from sensors # config - view current configuration # save - saves the current configuration # reset - resets all settings # restart - restarts the device # eeprom clear - removes all contents of eeprom # help - view this help - --------------------------------------------------------------------------------------- Т.к. в EEPROM микроконтроллера не была обнаружена конфигурация (волшебный hex байт нам подсказал), то были задействованы стандартные настройки. Просмотреть текущую конфигурацию можно командой config config # ip: 192.168.0.200 # subnet: 255.255.255.0 # gateway: 192.168.0.1 # dns: 192.168.0.1 # mac: 00:AA:BB:CC:DE:02 # server: mqtt.it4it.club # topic: arduino/serial/config чтобы изменить значение любого из этих параметров необходимо передать имя параметра и его новое значение, разделенные между собой пробелом. ip 10.10.10.99 # ok gateway 10.10.10.1 # ok dns 10.10.10.1 # ok После окончания конфигурации необходимо сохранить настройки и если были затронуты критические параметры, например, сеть, то перезапустить Arduino соответствующей командой. save # ok restart # ok # restarting device... Если параметр был успешно принят, то контроллер ответит нам "ok", а в противном случае ругнется. ip 127.0.0.1 # bad ip Также мы получим негативный ответ если команда не была распознана. qwerqwer1243 # bad command С остальными командами Вы разберетесь самостоятельно. Исходник: MQTT_CLIENT_328_SERIAL_CONFIG.zip PS: в общем то это статья родилась только для того, чтобы в соседнем форуме с системой мониторинга Zabbix появилась ссылка на устройство, но я надеюсь, что она также станет полезна любителям домашней автоматизации и не только.
-
- 3
-
- zabbix
- serial config
-
(and 3 more)
Tagged with: