Разработка устройства для Alterozoom IoT на базе микроконтроллера Arduino

Цель работы

Научиться разрабатывать устройства, совместимые с концепцией IoT от Alterozoom

Задачи работы

  1. Установка библитеки для протокола взаимодействия с приложением Alterozoom.
  2. Разработка скетча.
  3. Проверка взаимодействия с устройством через монитор последовательного порта.

Инструменты для выполения работы

  1. Компьютер с подключением к сети Internet.
  2. Плата Arduino с USB выходом (например, Arduino Uno).

Теоретическая часть

Для упрощения разработки устройств IoT и унификации их подключения к платформе Alterozoom был разработан простой текстовый протокол для взаимодействия устройства с компьютером через различные каналы связи, предназначенные для последовательной передачи информации. Протокол предназначен для организации взаимодействия двух устройств (точка-точка) посредством обмена простыми текстовыми сообщениями. Использование текстовых сообщений упрощает разработку и отладку устройства, так как позволяет взаимодействовать с ним без специализированного ПО из командной строки или монитора последовательного порта. Описание протокола можно найти по ссылке.
Так же была разработана библиотека для Arduino IDE, реализующая данный протокол.

Выполнение работы

Установка библитеки для протокола взаимодействия с приложением Alterozoom

Скачиваем архив с библиотекой для Arduino IDE по ссылке: http://wl.unn.ru/ftp/public/IoT/Alterozoom/ARpc.zip Устанавливаем библиотеку через менеджер библиотек: выбираем пункт меню "Скетч -> Управление библиотеками -> Добавить .ZIP библиотеку" и находим скачанный архив ARpc.zip.

Разработка скетча

В рамках работы будет создан скетч, позволяющий мигать светодиодом из интерфейса приложения Alterozoom и передающий раз в пол-секунды "измерения" (сгенерированный двумерный сигнал (sin(t);cos(t)) ). Так же у устройства будет еще один "датчик" - счетчик миганий светодиодом.
Для взаимодействия с компьютером через последовательный порт мы создадим объект класса ARpc и определим для него две callback-функции - одна для обработки команд от ПК, вторая для отправки сообщений на компьютер. Данные функции будут вызываться самой библиотекой при необходимости. Для однозначной идентификации устройства так же необходимо указать идентификатор и имя устройства. А для обеспечения возможность управления устройством необходимо разработать xml-описание панели управления устройством.
Создаем новый скетч и сохраняем под именем AlterozoomTest. Проверяем, что правильно указана плата и порт. Подключаем к скетчу библиотеку ARpc (Скетч -> Подключить библиотеку -> ARpc). В начале файла должен был появиться нужный #include <ARpc.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;//пин светодиода
unsigned long blinksCount=0;//число миганий
Для того, чтобы в дальнейшем для устройства был доступен интерфейс управления устройством, необходимо разработать xml-описание. Подробно про это можно почитать в pdf-описании (ссылка из теоретической части). Для нашего сценария необходима одна кнопка, передающая на контроллер команду "blink", которая описывается следующим образом:
const char *interfaceStr="<controls><group title=\"Device controls\"><control sync=\"0\" title=\"Blink\" command=\"blink\"/></group></controls>";
Так же для получения данных с устройства необходимо подготовить описание датчиков. Для нашего устройства оно выглядит вот так:
const char *sensorsDef="<sensors>"
"<sensor name=\"blinks_count\" type=\"single\"/>"//датчик blinks_count
"<sensor name=\"sin_x\" type=\"single\"><constraints dims=\"2\"/></sensor>"//датчик 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);
    }
}wcb;
 
Объект этого класса используется, когда библиотеке нужно передать какие-либо данные от устройства. В данном случае это данные, которые мы увидим в мониторе порта. Обратите внимание, что не используется println, так как эта функция добавляет лишний перевод строки, который будет мешать нормальной обработке сообщений.

Далее мы объявляем объект класса ARpc и передаем ему ссылки на созданные выше переменные и функции:
ARpcDevice parser(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;
            parser.disp().writeMeasurement("blinks_count",String(blinksCount).c_str());
            parser.disp().writeOk();
        }
        else parser.disp().writeErr("Unknown cmd");//неизвестная команда
    }
}ccb;
Здесь мы обрабатываем одну команду - "blink", при приходе которой мигаем штатным светодиодом на 13 порту и передаем новое "измерение" счетчика миганий.
Далее подготавливается функция для генерации отсчетов sin и cos
int t=0;
void writeSinVal()
{
    const char *strs[2];
    String sinVal(sin(0.1*t)),cosVal(cos(0.1*t));
    parser.disp().writeMeasurement("sin_x",sinVal.c_str(),cosVal.c_str());
    ++t;
}
300 - размер буфера для одного сообщения. Невозможно передать на контроллер сообщение размера больше указанного.
Размер буфера нужно подбирать, исходя из доступного объема памяти. На микроконтроллерах с большим объемом памяти можно использовать больший размер буфера.
Проихводим инициальзацию пина и последовательного порта в функции setup() и установить описание датчиков и интерфейса управления:
void setup()
{
    Serial.begin(9600);
    pinMode(ledPin,OUTPUT);
    parser.disp().installDevEventsHandler(&ccb);
    parser.disp().setControls(interfaceStr);
    parser.disp().setSensors(sensorsDef);
}
И наконец, в функции loop() необходимо проверять последовательный порт на наличие новых данных, передавать их объекту parser, сгенерировать новый отсчет sin, после чего сделать задержку на пол-секунды, чтобы отсчеты не генерировались слишком часто.poizzzennn@mail.ru
void loop()
{
    while(Serial.available())
        parser.putByte(Serial.read());
    writeSinVal();
    delay(500);
}
Загружаем полученный скетч на микроконтроллер.

Проверка взаимодействия с устройством через монитор последовательного порта.

Открываем монитор порта. В нем должны регулярно появляться сообщения "meas" с новыми значениями sin и cos.

Проверяем, чтобы внизу было выбрано "Новая строка", а не "Нет конца строки".
Пишем в поле ввода "identify" и нажимаем Отправить. В ответ должно появиться сообщение deviceinfo.

В этом сообщении должны быть идентификатор и имя устройства, указанные в скетче.
Дополнительное задание: передать сообщение на устройство с командой мигания светодиодом "blink", запросить с устройства список датчиков и описание интерфейса управления.