Цель работы
Научиться разрабатывать устройства, совместимые с IoT частью системы.
Задачи работы
- Установка библитеки для протокола взаимодействия с приложением для компьютера.
- Разработка скетча.
- Проверка взаимодействия с устройством через монитор последовательного порта.
Инструменты для выполения работы
- Компьютер с подключением к сети Internet.
- Плата Arduino с USB выходом (например, Arduino Uno).
Теоретическая часть
Для упрощения разработки устройств IoT и унификации их подключения к IoT-части информационной системы был разработан простой текстовый протокол для взаимодействия устройства с компьютером через различные каналы связи, предназначенные для последовательной передачи информации. Протокол предназначен для организации взаимодействия двух устройств (точка-точка) посредством обмена простыми текстовыми сообщениями. Использование текстовых сообщений упрощает разработку и отладку устройства, так как позволяет взаимодействовать с ним без специализированного ПО из командной строки или монитора последовательного порта. Так же была разработана библиотека для Arduino IDE, реализующая данный протокол.
Выполнение работы
Установка библитеки для протокола взаимодействия с приложением для компьютера
- Скачиваем архив с исходными кодами по адресу https://github.com/ooolms/wl_iot_framework (кнопка "Clone or download" на странице) или клонируем репозиторий с помощью git.
- Разархивируем этот архив, должна появиться папка wl_iot_framework.
- Устанавливаем библиотеку через менеджер библиотек: выбираем пункт меню "Скетч -> Управление библиотеками -> Добавить .ZIP библиотеку" и находим архив ARpc.zip внутри папки wl_iot_framework в подпапке ArduinoIdeLibrary.
Разработка скетча
В рамках работы будет создан скетч, позволяющий мигать светодиодом из интерфейса приложения для компьютера и передающий раз в пол-секунды "измерения" (сгенерированный двумерный сигнал (sin(t);cos(t)) ). Так же у устройства будет еще один "датчик" - счетчик миганий светодиодом.
Для взаимодействия с компьютером через последовательный порт мы создадим объект класса ARpc и определим для него две callback-функции - одна для обработки команд от ПК, вторая для отправки сообщений на компьютер. Данные функции будут вызываться самой библиотекой при необходимости. Для однозначной идентификации устройства так же необходимо указать идентификатор и имя устройства. А для обеспечения возможность управления устройством необходимо разработать xml-описание панели управления устройством.
Создаем новый скетч и сохраняем под именем IotDeviceTest. Проверяем, что правильно указана плата и порт. Подключаем к скетчу библиотеку ARpc (Скетч -> Подключить библиотеку -> ARpc). В начале файла должен был появиться нужный #include <ARpcDevice.h>.
Генерируем уникальный идентификатор в формате UUID (например, можно воспользоваться сервисом https://www.uuidgenerator.net/version4, при открытии страницы вверху будет готовый UUID). Добавляем две глобальных переменных для идентификатора и имени устройства:
const char *deviceName="led_blink_test";//имя устройства
const ARpcUuid deviceId("{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}");//идентификатор устройства
xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx заменяем на полученный UUID, фигурные скобки должны остаться.
Так же определяем дополнителные глобальные переменные:
int ledPin=13;//пин светодиода
uint32_t blinksCount=0;//число миганий
Для того, чтобы в дальнейшем для устройства был доступен интерфейс управления устройством, необходимо разработать xml-описание. Подробно про это можно почитать в pdf-описании (ссылка из теоретической части). Для нашего сценария необходима одна кнопка, передающая на контроллер команду "blink", которая описывается следующим образом:
const char *interfaceStr="<controls><group title=\"Device controls\"><control title=\"Blink\" command=\"blink\"/></group></controls>";
Так же для получения данных с устройства необходимо подготовить описание датчиков. Для нашего устройства оно выглядит вот так:
const char *sensorsDef="<sensors>"
"<sensor name=\"blinks_count\" type=\"u32_sv\"/>"//датчик blinks_count
"<sensor name=\"sin_x\" type=\"f32_sv_d2\"/>"//датчик sin_x (двумерный)
"</sensors>";
Определяем класс для передачи сообщений к ПК через последовательный порт:
class WriteCallback
:public ARpcIWriteCallback
{
public:
virtual void writeData(const char *data,unsigned long sz)
{
Serial.write(data,sz);
}
virtual void writeStr(const char *str)
{
Serial.print(str);
}
virtual void writeStr(const __FlashStringHelper *str)
{
Serial.print(str);
}
}wcb;
Объект этого класса используется, когда библиотеке нужно передать какие-либо данные от устройства. В данном случае это данные, которые мы увидим в мониторе порта. Обратите внимание, что не используется println, так как эта функция добавляет лишний перевод строки, который будет мешать нормальной обработке сообщений.
Далее мы объявляем объект класса ARpc и передаем ему ссылки на созданные выше переменные и функции:
ARpcDevice dev(300,&wcb,&deviceId,deviceName);
Определяем класс обработки команд, передаваемых устройству. Библиотека будет использовать объект этого класса, когда на устройство будут приходить команды, например, введенные нами в мониторе порта. Функция-обработчик принимает команду, аргументы команды и количество аргументов:
class CommandCallback
:public ARpcIDevEventsCallback
{
public:
virtual void processCommand(const char *cmd,const char *args[],unsigned char argsCount)
{
if(strcmp(cmd,"blink")==0)//команда blink, проверяем что есть аргумент
{
digitalWrite(13,HIGH);
delay(500);
digitalWrite(13,LOW);
++blinksCount;
dev.disp().writeMeasurement("blinks_count",String(blinksCount).c_str());
dev.disp().writeOk();
}
else dev.disp().writeErr("Unknown cmd");//неизвестная команда
}
}ccb;
Здесь мы обрабатываем одну команду - "blink", при приходе которой мигаем штатным светодиодом на 13 порту и передаем новое "измерение" счетчика миганий.
Далее подготавливается функция для генерации отсчетов sin и cos
int t=0;
float sVal[2];
void writeSinVal()
{
sVal[0]=sin(0.1*t);
sVal[1]=cos(0.1*t);
dev.disp().writeMeasurementB("sin_x",sVal,2);
++t;
}
300 - размер буфера для одного сообщения. Невозможно передать на контроллер сообщение размера больше указанного.
Размер буфера нужно подбирать, исходя из доступного объема памяти. На микроконтроллерах с большим объемом памяти можно использовать больший размер буфера.
Проихводим инициальзацию пина и последовательного порта в функции setup() и установить описание датчиков и интерфейса управления:
void setup()
{
Serial.begin(9600);
pinMode(ledPin,OUTPUT);
dev.disp().installDevEventsHandler(&ccb);
dev.disp().setControls(interfaceStr);
dev.disp().setSensors(sensorsDef);
}
И наконец, в функции loop() необходимо проверять последовательный порт на наличие новых данных, передавать их объекту parser, сгенерировать новый отсчет sin, после чего сделать задержку на пол-секунды, чтобы отсчеты не генерировались слишком часто.
void loop()
{
while(Serial.available())
dev.putByte(Serial.read());
writeSinVal();
delay(500);
}
Загружаем полученный скетч на микроконтроллер.
Проверка взаимодействия с устройством через монитор последовательного порта.
Открываем монитор порта. В нем должны регулярно появляться сообщения "meas" с новыми значениями sin и cos.
Проверяем, чтобы внизу было выбрано "Новая строка", а не "Нет конца строки".
Пишем в поле ввода "identify" и нажимаем Отправить. В ответ должно появиться сообщение deviceinfo.
В этом сообщении должны быть идентификатор и имя устройства, указанные в скетче.