# 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-адрес и параметры подключения.
## Демонстрационный пример
Будем пробовать подключиться к MQTT-серверу, используя наш уже знакомый [пример работы с WiFi](/iot/wifi).
Считаем, что уже выполняется подключение к WiFi-сети и работа с сокетами.
Нам нужно добавить библиотеку для работы с протоколом MQTT.
Пока не удалось придумать ничего лучшего под PlatformIO, как перегруппировать файлы библиотеки [mbed-mqtt](https://github.com/ARMmbed/mbed-mqtt) так,
чтобы они стали доступны в дереве проекта PlarformIO. Не очень изящно, но тем не менее получилась [библиотека](https://github.com/iot-academy/pio_mbed_paho_mqtt), которую и следует добавить в каталог `lib` в корне своего проекта.
Для добавления библиотеки можно в терминале перейти в каталог проекта (пусть это будет `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 2, поскольку она видимо нерабочая (другие пользователи тоже жалуются на проблемы с этой частью кода).
Суть примера очень проста. Создается MQTT-клиент, который подписывается на топик `mbed-sample`, и сам посылает туда же сообщения.
Есть callback-функция `messageArrived`, которая срабатывает при получении нового сообщения и увеличивает счетчик.
Демо-сценарий такой: клиент посылает в свой же топик два сообщения, и ждет третьего извне.
Вы отправляете его сами (из любого MQTT-клиента), после чего работа программы завершается.
Разумеется, чтобы программа успешно заработала, необходимо еще и наличие конфигурационного файла `mbed_app.json`. Его содержимое будет точно таким же,
как и в примере [работы с WiFi](/iot/wifi).
Дополнительно в коде примера необходимо задать параметры подключения к MQTT-брокеру!
Параметр `data.clientID.cstring` для всех клиентов MQTT-брокера должен быть уникальным (вы его придумываете сами).
Как несложно убедиться, в коде ничего сложного нет, и в значительной степени это повторение предыдущего демонстрационного примера с сокетами.
## Запуск демо-сценария
Соберите программу и запустите, не забыв прописать в переменной 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)
## Задача
Вспомните [практикум](/iot/gpio), посвященный орагнизации работы с портами ввода-вывода. Нам понадобится модуль кнопок
![umdk-4btn](/assets/images/iot-academy/umdk-4btn.png)
Необходимо создать устройство и написать программу для него, которая будет подключаться к MQTT-брокеру лаборатории (с логином и паролем).
Программа должна подписаться на топик `iot_practice//lamp/value`, где `` – это ваш уникальный идентификатор пользователя
в IT Академии Samsung (как его узнать см. ниже), и выводить его значение в отладочную консоль всякий раз при получении.
При нажатии на кнопку S3 в топик `iot_practice//lamp` должно в триггерном режиме отправляться
значение либо `on`, либо `off` (первое нажатие – отправляется `on`, второе – `off`, третье – снова `on` и так далее).
В программе должна быть переменная целого типа (например, `brightness`), и при нажатии на кнопку S1 значение этой переменной должно увеличиваться на 10,
а при нажатии на кнопку S2 – уменьшаться на 10. При этом нужно контролировать, чтобы величина переменной `brightness` всегда лежала в диапазоне от 0 до 100,
и при всяком изменении этой переменной ее нужно публиковать в топик `iot_practice//lamp/value`.
Конечно, для всех кнопок должно быть предусмотрено подавление дребезга контактов, а чтобы понимать, что наше устройство работает – в отдельном
потоке монотонно и неспешно (с частотой 1 раз в секунду) должен переключаться светодиод `LED1`.
Кнопка S4 пока будет не задействована, но, возможно, в будущем какое-то назначение придумаем и ей.
Как быстро проверить, что все работает правильно? Ответ очевиден – воспользоваться [виртуальной ламочкой](/iot/fake-lamp),
ведь именно через эти топики осуществляется ее управление!