IT Академия Samsung – MQTT в Mbed OS # MQTT в Mbed В рамках данного практикума посмотрим как отправлять и получать данные через MQTT, используя плату микроконтроллера. Cделаем небольшое приложение, реализующее следующие возможности: * отправка данных по WiFi на MQTT-сервер; * получение данных по WiFi с MQTT-сервера. ## Необходимое оборудование - радиомодуль UNWR с платой-адаптером UMDK-RF: ![ud-min3](/assets/images/iot-academy/ud-min3.png) - плата-адаптер модулей связи 804-Xbee: ![804-xbee](/assets/images/iot-academy/804-xbee.png) - Модуль связи WiFi на базе ESP8266: ![xbee-wifi](/assets/images/iot-academy/xbee-wifi.png) - WiFi-точка доступа (роутер). Как и в [работе WiFi](/iot/wifi), собираем все модули, ориентируя платы по ключу: ![unwr_wifi](/assets/images/iot-academy/unwr_wifi.png) ## Список программных требований * Для выполнения этого практикума у вас уже должен быть подключен WiFi к плате, и всё должно работать, то есть должен рабоать итоговый код из практикума по [WiiFi](/iot/wifi). * Также у вас должен быть доступ к MQTT-серверу (локальный или внешний), и вам должен быть известен его IP-адрес и параметры подключения. ## Демонстрационный пример<a name="demo"></a> Будем пробовать подключиться к MQTT-серверу, используя наш уже знакомый [пример работы с WiFi](/iot/wifi). Считаем, что уже выполняется подключение к WiFi-сети и работа с сокетами. Нам нужно добавить библиотеку для работы с протоколом MQTT. <div class="info"> Пока не удалось придумать ничего лучшего под PlatformIO, как перегруппировать файлы библиотеки [mbed-mqtt](https://github.com/ARMmbed/mbed-mqtt) так, чтобы они стали доступны в дереве проекта PlarformIO. Не очень изящно, но тем не менее получилась [библиотека](https://github.com/iot-academy/pio_mbed_paho_mqtt), которую и следует добавить в каталог `lib` в корне своего проекта. </div> Для добавления библиотеки можно в терминале перейти в каталог проекта (пусть это будет `mqtt_demo_project`) и при помощи СКВ Git клонировать репозиторий с библиотекой: ```bash cd ~/Документы/PlatformIO/Projects/mqtt_demo_project git clone https://github.com/iot-academy/pio_mbed_paho_mqtt ./lib/paho_mqtt ``` Далее представлен листинг демонстрационной программы. Обратите внимание, что вместо функции `http_demo` в этой версии программы есть функция `mqtt_demo`, а также callback-функция `messageArrived`, вызывающаяся когда устройство получит сообщение топика, на которое оно подписано: ```cpp #include "mbed.h" #include "TCPSocket.h" #include "MQTTmbed.h" #include "MQTTClientMbedOs.h" volatile int arrivedcount = 0; void messageArrived(MQTT::MessageData& md) { MQTT::Message &message = md.message; printf("Message arrived: qos %d, retained %d, dup %d, packetid %d\r\n", message.qos, message.retained, message.dup, message.id); printf("Payload %.*s\r\n", message.payloadlen, (char*)message.payload); ++arrivedcount; } void mqtt_demo(NetworkInterface *net) { char* topic = "mbed-sample"; char* hostname = "192.168.1.18"; int port = 1883; float version = 0.6; TCPSocket socket; MQTTClient client(&socket); SocketAddress a; net->gethostbyname(hostname, &a); a.set_port(port); printf("Connecting to %s:%d\r\n", hostname, port); socket.open(net); printf("Opened socket\n\r"); int rc = socket.connect(a); if (rc != 0) { printf("rc from TCP connect is %d\r\n", rc); return; } else printf("Connected socket\n\r"); MQTTPacket_connectData data = MQTTPacket_connectData_initializer; data.MQTTVersion = 3; data.clientID.cstring = "random_unique_client_id"; data.username.cstring = "myusername"; data.password.cstring = "mypassword"; if ((rc = client.connect(data)) != 0) { printf("rc from MQTT connect is %d\r\n", rc); return; } else printf("MQTT client connected\n\r"); if ((rc = client.subscribe(topic, MQTT::QOS2, messageArrived)) != 0) { printf("rc from MQTT subscribe is %d\r\n", rc); return; } else printf("MQTT client subscribed\n\r"); MQTT::Message message; // QoS 0 char buf[100]; sprintf(buf, "Hello World! QoS 0 message from app version %f\r\n", version); message.qos = MQTT::QOS0; message.retained = false; message.dup = false; message.payload = (void*)buf; message.payloadlen = strlen(buf)+1; rc = client.publish(topic, message); printf("Publish with rc %d\n\r", rc); while (arrivedcount < 1) client.yield(100); // QoS 1 sprintf(buf, "Hello World! QoS 1 message from app version %f\r\n", version); message.qos = MQTT::QOS1; message.payloadlen = strlen(buf)+1; rc = client.publish(topic, message); printf("Publish with rc %d\n\r", rc); while (arrivedcount < 2) client.yield(100); while (arrivedcount < 3) client.yield(100); printf("\r\n\n"); if ((rc = client.unsubscribe(topic)) != 0) printf("rc from unsubscribe was %d\r\n", rc); else printf("MQTT client unsubscribed\r\n"); if ((rc = client.disconnect()) != 0) printf("rc from disconnect was %d\r\n", rc); else printf("MQTT client disconnected\r\n"); socket.close(); printf("MQTT example: finished %d msgs\r\n", arrivedcount); } int main() { printf("MQTT example\r\n\r\n"); SocketAddress sa; NetworkInterface *net = NetworkInterface::get_default_instance(); if (!net) { printf("Error! No network inteface found.\r\n"); return 0; } printf("\r\nConnecting...\r\n"); int ret = net->connect(); if (ret == NSAPI_ERROR_IS_CONNECTED) { printf("Already connected!\n\r"); } else if (ret != NSAPI_ERROR_OK) { printf("\r\nConnection error: %d\r\n", ret); return -1; } printf("Success\r\n\r\n"); printf("MAC: %s\r\n", net->get_mac_address()); net->get_ip_address(&sa); printf("IP: %s\r\n", sa.get_ip_address()); net->get_netmask(&sa); printf("Netmask: %s\r\n", sa.get_ip_address()); net->get_gateway(&sa); printf("Gateway: %s\r\n", sa.get_ip_address()); mqtt_demo(net); net->disconnect(); printf("\r\nDone\r\n"); } ``` Это немного переработанный пример [HelloMQTT](https://os.mbed.com/teams/mqtt/code/HelloMQTT/) (нужен VPN). Его отличие в том, что 1. из него убран лишний функционал в виде печати на LCD-экран; 2. исправлена ошибка с сокетами в соответствии с более новой документацией (видимо, пример писался для более старой версии библиотеки); 3. убрана часть с QoS&nbsp;2, поскольку она видимо нерабочая (другие пользователи тоже жалуются на проблемы с этой частью кода). Суть примера очень проста. Создается MQTT-клиент, который подписывается на топик `mbed-sample`, и сам посылает туда же сообщения. Есть callback-функция `messageArrived`, которая срабатывает при получении нового сообщения и увеличивает счетчик. Демо-сценарий такой: клиент посылает в свой же топик два сообщения, и ждет третьего извне. Вы отправляете его сами (из любого MQTT-клиента), после чего работа программы завершается. Разумеется, чтобы программа успешно заработала, необходимо еще и наличие конфигурационного файла `mbed_app.json`. Его содержимое будет точно таким же, как и в примере [работы с WiFi](/iot/wifi). <div class="danger"> Дополнительно в коде примера необходимо задать параметры подключения к MQTT-брокеру! Параметр `data.clientID.cstring` для всех клиентов MQTT-брокера должен быть уникальным (вы его придумываете сами). </div> Как несложно убедиться, в коде ничего сложного нет, и в значительной степени это повторение предыдущего демонстрационного примера с сокетами. ## Запуск демо-сценария Соберите программу и запустите, не забыв прописать в переменной hostname адрес своего MQTT-сервера. Если всё успешно, то увидите в консоли следующее: ![pio_monitor_mqtt_mbed_pub_sub](/assets/images/iot-academy/pio_monitor_mqtt_mbed_pub_sub.png) Если увидите такую ошибку: ![pio_monitor_error_3004](/assets/images/iot-academy/pio_monitor_error_3004.png) то это означает, что вы скорее всего неправильно указали адрес сервера. Проверьте, что MQTT-сервер поднят и работает. Eсли заново запустить эту программу, но предварительно подписавшись на топик `mbed-sample`, например, при помощи `mosquitto_sub`, то можно видеть, что программа отправила два сообщения Hello World в этот топик (одно с QoS 0 и одно с QoS 1): ![mosquitto_sub_mbed](/assets/images/iot-academy/mosquitto_sub_mbed.png) Дальше программа останавливается и ждет третьего сообщения. Отправьте его самостоятельно (`mosquitto_pub`), и увидите в консоли, что ваша плата его получила и отчиталась об этом: ![pio_monitor_mqtt_mbed_finish](/assets/images/iot-academy/pio_monitor_mqtt_mbed_finish.png) ## Задача<a name="task"></a> Вспомните [практикум](/iot/gpio), посвященный орагнизации работы с портами ввода-вывода. Нам понадобится модуль кнопок ![umdk-4btn](/assets/images/iot-academy/umdk-4btn.png) Необходимо создать устройство и написать программу для него, которая будет подключаться к MQTT-брокеру лаборатории (с логином и паролем). Программа должна подписаться на топик `iot_practice/<UserID>/lamp/value`, где `<UserID>` – это ваш уникальный идентификатор пользователя в IT Академии Samsung (как его узнать см. ниже), и выводить его значение в отладочную консоль всякий раз при получении. При нажатии на кнопку S3 в топик `iot_practice/<UserID>/lamp` должно в триггерном режиме отправляться значение либо `on`, либо `off` (первое нажатие – отправляется `on`, второе – `off`, третье – снова `on` и так далее). В программе должна быть переменная целого типа (например, `brightness`), и при нажатии на кнопку S1 значение этой переменной должно увеличиваться на 10, а при нажатии на кнопку S2 – уменьшаться на 10. При этом нужно контролировать, чтобы величина переменной `brightness` всегда лежала в диапазоне от 0 до 100, и при всяком изменении этой переменной ее нужно публиковать в топик `iot_practice/<UserID>/lamp/value`. Конечно, для всех кнопок должно быть предусмотрено подавление дребезга контактов, а чтобы понимать, что наше устройство работает – в отдельном потоке монотонно и неспешно (с частотой 1 раз в секунду) должен переключаться светодиод `LED1`. Кнопка S4 пока будет не задействована, но, возможно, в будущем какое-то назначение придумаем и ей. Как быстро проверить, что все работает правильно? Ответ очевиден – воспользоваться [виртуальной ламочкой](/iot/fake-lamp), ведь именно через эти топики осуществляется ее управление!