Привет, читатель! Графики — это удобно. Нередко при разработке они нужны мне для визуализации процесса или демонстрации критичных событий. А еще их можно использовать, чтобы отобразить изменения погоды в течение дня, колебания курса валюты или диаграмму нагрузки и доступности сервера. В большинстве случаев для построения графиков есть готовые инструменты в самой операционной системе, но если вы это читаете, то в «Авроре» я ничего такого не нашел. Как я решал проблему и с какими подводными камнями столкнулся, рассказываю под катом.

Используйте навигацию, если не хотите читать текст полностью:

Введение
Фронтенд
Бэкенд
Модули, объединяйтесь!
Заключение

Введение


В ходе разработки некоторых устройств нередко необходимо проверить работу, полностью «отвязавшись» от внешних источников питания. При этом важно просматривать различную отладочную информацию или вводить команды в консоли. Иногда необходимо просто сделать мобильное устройство на базе телефона. Для реализации этого есть различные модули и шилды, которые позволяют сделать прибор «беспроводным». Однако проще подцепиться кабелем к смартфону и выводить на его экран необходимую графику. В рамках реализации НИОКР и рефакторинга некоторых решений подготовил небольшое pet-приложение на ОС «Аврора» для реализации этих кейсов.

В первую очередь необходимо определиться, что требуется от приложения. Это лог сообщений в виде текста, поле ввода команд и отображение в виде графика — иногда полезно посмотреть на форму сигнала и заметить аномалии.

В вузе долгое время для этих целей использовал LabVIEW и собирал .exe пакет для планшета на Windows. Подход не самый стабильный, тяжелый, но позволял быстро накидать решение и разные дополнительные функции, например спектральный анализ. Дополнительно в качестве эксперимента использовал SiminTech и его модуль связки с Arduino.

Очевидно, что подобных «лабораторных» сред под мобильные устройства нет. Под каждый кейс либо разрабатывается полноценное отдельное приложение, либо, если оно уже есть, в него добавляется функциональный модуль.

Фронтенд


Для «Авроры» существует несколько основных инструментов разработки. Сейчас популярность активно набирает Flutter — относительно новый фреймворк с открытым исходным кодом. Используя этот инструмент для разработки, легко оперативно получить готовое приложение. В нем есть необходимые графические библиотеки. Однако при погружении в эту тему я не нашел решения по подключению ко внешним устройствам. Так я вернулся к работе с привычным Qt.

Если хотите начать работать с фреймворком flutter под «Авророй», то есть отличная статья для старта, а также цикл статей на gitlab pages и примеры от ОМП.

QtCharts? Нет!


Qt предлагает IDE, адаптированный под «Аврору». Следовательно, отличным, простым и правильным решением кажется использовать QtCharts. Однако с этой библиотекой есть две небольшие проблемы:

  • она не портирована под «Аврору» или Sailfish,
  • за ее использование придется платить (по крайней мере, в коммерции).

Отсутствие библиотеки стало только стимулом собрать ее из исходников. К сожалению, попытка не увенчалась успехом: библиотека собирается, подключается к проекту, но не работает (на версии ОС 4.0.203). А еще выдает следующую ошибку: «Cбой отображения сегмента из разделяемого объекта».

Я пробовал несколько вариантов и меня порадовала работа техподдержки ОМП: сотрудники действительно стараются помочь и предложить решение. Так, на одной из ошибок мне прислали сообщение с указанием вариантов подключения библиотек и модулей.





Иные варианты


В поисках решения обратился к Google и Telegram-сообществу. В свободном распространении нашлась пара занимательных библиотек. Они мне не подошли, но оставлю здесь — возможно, кого-то они заинтересуют.

  • QCustomPlot — библиотека с виджетами для построения графиков. Выглядит немного устаревшей, но при этом отлично подходит для вставки в научные статьи. Имеются интересные варианты с розой ветров, финансовые «свечи», логарифмические шкалы. Последний релиз был в 2022 году. К сожалению, также работает через QtWidgets.
  • QWT — Qt Widgets for techical applications. Это еще одна библиотека, которая, как и следует из названия, основывается на QtWidgets. Позволяет строить различные графики с логарифмическими шкалами, спектрограммы, а также содержит интересные инструменты ввода и индикации, например спидометр, компас и альтиметр.


Источник.

D3 для всех


В чате разработчиков мне указали, что в QML можно использовать JavaScript, а также есть успешные примеры применения библиотеки D3.js для отображения информации. Что ж, пойдем этим путем.


Про использование D3 в QML есть статья на Хабре. Также применение этой библиотеки нашел в портированом Денисом Богомоловым приложении Plotter, исходный код на Git Flic. Взяв эту информацию за основу, доработал QML-страницу приложения, которое можно скопировать в свой проект с GitHub.

Для подключения графика в проект достаточно выполнить несколько простых шагов:

  • скачать необходимую версию D3.js,
  • добавить Plot.qml в папку проекта,
  • вызвать ее корректным образом.

Версии D3 не имеют обратной совместимости, поэтому для работы проекта используется версия 4.

Дерево файлов директории QML должно выглядеть примерно следующим образом:

qml
├── cover
│   ├── DefaultCoverPage.qml
├── js
│   ├── d3.v4.js
├── js_test.qml
└── pages
    ├── AboutPage.qml
    ├── MainPage.qml
    ├── Plot.qml
    └── Settings.qml

Также не забывайте подключить дополнительные файлы в RPM, чтобы после установки приложения оно могло импортировать необходимые ресурсы:

DISTFILES += \
    qml/js/*.js \
    qml/* \
    rpm/ru.auroraos.js_test.spec 

Далее в файле страницы, на которой будет отображаться график (в рамках этого примера — MainPage), подключаем заголовочный файл d3 и добавляем компонент plot:

import "../js/d3.v4.js" as D3
…
Plot {
        id: plot
        anchors.margins: Theme.horizontalPageMargin
        width: parent.width
        height: parent.height
        function drawPlot(line) {
            var arr = [];
            for (var i=0;i<exampleSin.length;i++){
               arr.push([i,exampleSin[i]])
            }
            line(arr);
        }
…

Минимальная реализация готова! В функции drawPlot происходит конвертация массива исходных данных (exampleSin) в двумерный массив arr для функции построения графиков. drawPlot вызывается каждый раз при перерисовке графика. После запуска на телефоне отображается заранее описанный синус, подобный тому, как на КДПВ.

Бэкенд


Реализация бэкенда приложения не сильно сложная: микроконтроллер собирает данные, обрабатывает и передает в телефон через конвертер интерфейсов UART-USB. На стороне микроконтроллера для эксперимента использую максимально простой код: по сигналу прерывания, настроенному на 10 миллисекунд, производится считывание и отправка в UART значения из АЦП.

Qt


В основе взаимодействия лежит библиотека CDCConnect, которая позволяет подключиться к необходимым микросхемам. Для работы реализовал небольшой класс-прослойку. Во время инициализации, после того как найдено подходящее оборудование, выполняется инициализация таймера, который каждые несколько миллисекунд опрашивает интерфейс на наличие данных. Это реализовано следующим образом:

void initTimer(){
   QThread* thread = new QThread(this);
   thread->start();
   QTimer *timer = new QTimer(0);
   timer->setInterval(timerUptime);
   timer->moveToThread(thread);
   connect(timer, SIGNAL(timeout()), this, SLOT(slotTimerAlarm()), Qt::DirectConnection);
   connect(thread, SIGNAL(started()), timer, SLOT(start()));
}

Срабатывание тайма вызывает функцию slotTimerAlarm, которая выполняет чтение строки данных, их преобразование в текстовую строку для отладки и вывода в консоль. Дополнительно эта же функция делает попытку конвертации каждого значения к типу double и записи в буфер для дальнейшей отрисовки на графике.

int error = 0;
       unsigned char buf[65];
       QString  readed = "";
       error = dev.readBytes(buf, 64, timerUptime); ///< Чтение ответного сообщения из UART
       if (error > 0)
       {
           readed = toQrealArray(buf, error);
           emit messageAvailable( readed);
                  qreal val =0;
       for (QString row: readed.split("\n")){
           val = rowreplace("\r","").toDouble();
           //qDebug() << val;
           m_arr[buffPosition]=val;
           buffPosition++;
           if (buffPosition>=(bufferSize)) buffPosition=0;
       }
       emit digitsComplete(buffPosition);
       }<
       return 0;

Для передачи информационных переменных и массива данных в QML объявлены соответствующие свойства:

   Q_PROPERTY (QString name READ getName);
   Q_PROPERTY (QString description READ getDesc);
   Q_PROPERTY (QString vidXpid READ vidXpid);
   Q_PROPERTY (int bSize READ getBuffSize);
   Q_PROPERTY (QVariantList arr READ getarr);

Модули, объединяйтесь!


Инструментарий QT позволяет быстро и нативно связывать объекты друг с другом. Для передачи данных из C++ на фронтенд QML я предпочитаю использовать свойства контекста. При таком варианте сначала создается экземпляр объекта, который передается фронтенду следующим образом:

…
QtCDC device;
…
QScopedPointer<QQuickView> view(Aurora::Application::createView());
view->rootContext()->setContextProperty("deviceCdc", &device);
view->setSource(Aurora::Application::pathTo(QStringLiteral("qml/js_test.qml")));
view->show(); 
device.init();
return application->exec();
…

Чтобы обновить данные и перестроить график, подключаюсь к сигналу digitsComplete. Также в функции drawPlot внес корректировку — вместо exampleSin используется deviceCdc.arr:

    Connections {
            target: deviceCdc
            onDigitsComplete: {
                plot.requestPaint();
            }
        }

Забавный факт: для подключения к CDC через libusb требуется добавить разрешение на работу с интернетом в файле *.desktop проекта:

[X-Application]
Permissions=Internet

Заключение


Полученное приложение позволяет использовать подключенный микроконтроллер в качестве мобильного аналога осциллоскопа или в качестве Arduino-консоли и плоттера. Периодически применяю его для отладки компонентов в стартапе, для просмотра и сохранения логов работы устройства.

В планах на следующий год — выполнить подключение к приборам с существующими открытыми исходными кодами для ПК, например Hantek365, и выпустить приложение в RuStore.

Комментарии (8)


  1. alcotel
    09.12.2024 11:07

    Не часто вижу такую "удобную" сетку. Но "уникальные" ещё встречаются)


    1. VRyabchevsky Автор
      09.12.2024 11:07

      Сейчас как раз дорабатываю отображение целых частей, а также осевых линий


      1. yappari
        09.12.2024 11:07

        Полагаю, речь об отсутствии сетки как таковой. Также сомнительно выглядит вывод чисел непосредственно в поле графика, но это наверное больше дело вкуса.


        1. NickSin
          09.12.2024 11:07

          Я думаю, это нужно в примере для "отладки" отображения.


        1. alcotel
          09.12.2024 11:07

          Стиль изображения скорее всего настраивается. Речь про цифры в авто-сетке.

          График с шагом сетки 25,6 может ещё кому-то и нормальным покажется) Но шаг 410,1 уж точно не удобен для восприятия "десятичным" человеком.

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


          1. VRyabchevsky Автор
            09.12.2024 11:07

            Речь про цифры в авто-сетке.

            Да, это проблема которую решал позже доп. алгоритмом. Если давать на вход D3 максимальное значение 4096, то он равномерно делит его на отрезки по 410,1

            Вот тут тоже обсуждение было.

            Спасибо за информацию/комментарий, приму к сведению.


  1. UFO_01
    09.12.2024 11:07

    Тоже как-то ради интереса делал рисовашку для Qt. Писал под OpenGL 3.3, весь рендер идёт в QOpenGLWindow (QOpenGLWidget даёт слишком большую нагрузку из-за лишних загрузок и проверок), которая оборачивается в QWidget. Жаль что нет времени довести до конца, потому что проектик ОЧЕНЬ сырой.

    Картинки

    График со сглаживанием через альфа-компоненту, выглядит не очень как по мне

    Без сглаживания, понятное дело, вообще жесть

    Пока помимо рисования графика из функционала только прокрутка и увеличение графика (по оси Х).


  1. Mark194
    09.12.2024 11:07

    Почему не посмотреть в пользу Charts в Qml из модуля DataVizualization, раз у вас тут QML?