Особенности построения графиков Qt в «Авроре»
Привет, читатель! Графики — это удобно. Нередко при разработке они нужны мне для визуализации процесса или демонстрации критичных событий. А еще их можно использовать, чтобы отобразить изменения погоды в течение дня, колебания курса валюты или диаграмму нагрузки и доступности сервера. В большинстве случаев для построения графиков есть готовые инструменты в самой операционной системе, но если вы это читаете, то в «Авроре» я ничего такого не нашел. Как я решал проблему и с какими подводными камнями столкнулся, рассказываю под катом.
Используйте навигацию, если не хотите читать текст полностью:
→ Введение
→ Фронтенд
→ Бэкенд
→ Модули, объединяйтесь!
→ Заключение
Введение
В ходе разработки некоторых устройств нередко необходимо проверить работу, полностью «отвязавшись» от внешних источников питания. При этом важно просматривать различную отладочную информацию или вводить команды в консоли. Иногда необходимо просто сделать мобильное устройство на базе телефона. Для реализации этого есть различные модули и шилды, которые позволяют сделать прибор «беспроводным». Однако проще подцепиться кабелем к смартфону и выводить на его экран необходимую графику. В рамках реализации НИОКР и рефакторинга некоторых решений подготовил небольшое 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
Минимальная реализация готова! В функции 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 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.