Пингователь на libcurl+Qt+Raspberry Pi

Или как быстро начать разрабатывать на QT для Raspberry Pi и перестать беспокоиться

Kirill Avdeev
Mad Devs — блог об IT
12 min readOct 4, 2019

--

В один прекрасный день, совершенно внезапно выяснилось, что необходимо было давным давно закончить проект для одной международной горнодобывающей компании. Её рабочие регулярно жаловались на качество этих ваших интернетов в номерах жилого корпуса предприятия. Понятно, что факторов, из которых складывается качество работы с интернетом очень много, и зачастую источником проблем может являться старенький смартфон, а не IT-отдел или провайдер.

Заказчику пришла в голову идея в каждой комнате повесить экранчик, который красиво и разноцветно показывал бы пинги до известных ресурсов через WiFi в роли средства мониторинга качества интернета.

В широком смысле статья предназначена для тех, кто хочет влиться в разработку ПО на Qt под Raspberry Pi.

Оборудование

Мы рассматривали три варианта модулей для основы проекта: ESP32, RaspberryPi (малинка) и STM32.

STM32 отмели сразу: слишком низкий уровень разработки решения, большое количество времени на разработку, сложность в поддержке решения (предприятие заказчика находится в примерно 400 км от города Бишкек, на высоте около 4000 метров, туда можно забираться далеко).

ESP32 не показался надёжным и имел те же проблемы с поддержкой решения.

Мы решили пальнуть из пушки по воробьям и остановились на одноплатном компьютере Raspberry Pi.

Встраиваемый Linux на Raspberry Pi, решал очень много проблем в пуске и поддержке, но требовал немного специфического подхода к разработке.

Характеристики Raspberry Pi

Мы выбрали Raspberry Pi 3B+. Это превосходный одноплатник размером с банковскую карту, имеющий на борту 4 USB-порта, WiFi-модуль, 1Гб ОЗУ, процессор BCM2835 с четырьмя ядрами Cortex-M53, каждое частотой до 1.4 ГГц. HDMI и композитным выходом мы решили не пользоваться: чем меньше проводов, тем лучше. Не заметят и порвут. Или наоборот, порвут и не заметят. Горняки — народ суровый.

Raspberry Pi 3 B+. Изображение взято с сайта https://www.raspberrypi.org

Дисплеи

В дополнение к одноплатнику были приобретены резистивные тач-дисплеи Adafruit PiTFT. Дисплей сразу понравился. Простой, лаконичный, без излишеств. Для подключения к одноплатнику достаточно просто плотно насадить его на плату.

Так выглядит заказанный экран. Изображение взято с сайта https://www.adafruit.com

Ну и вещи, про которые принято забывать: питание и флэшка для операционной системы (ОС).

Питание

Сначала я запитывал одноплатник от ноутбука, но что-то шло не так и одноплатник просто не находился в сети. Посмотрев на то, как моргают светодиоды (повторялась одна и та же короткая последовательность), я предположил, что он постоянно перезагружается. Наскоро ограбив коллегу на зарядку от сотового телефона, я запитал одноплатник полноценными 2.5А тока. Стало понятно, что одноплатник немилосерден и прожорлив к питанию.

Флэшка

В проекте она нужна для того, чтобы записать на неё образ ОС. Четыре года назад я делал сайд-проект на Raspberry Pi 2 b+ и сдуру взял десяток флэшек от неизвестого китайского производителя. Флэшки прекрасно работали на смартфоне и ноутбуке, но на Raspberry Pi не шли ни в какую. Однако, с взятыми двумя днями позже флэшками от AData, одноплатник заработал сразу и без проблем.

Вся система в сборе в процессе отладки программного обеспечения

Программное обеспечение

В качестве фреймворка для приложения мы решили использовать Qt.

Итак, для реализации проекта с точки зрения программного обеспечения нужно было сделать следующие шаги:

  1. Установка ОС на одноплатник и её первичная настройка
  2. Включение экрана
  3. Сборка и установка Qt
  4. Установка curl
  5. Настройка среды разработки
  6. Непосредственно написание программы curler
  7. Настройка ОС таким образом, чтобы curler запускался при старте ОС и перезапускался в случае своего падения

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

В качестве рабочей машины использовался Asus ROG GL753VD с ОС Kubuntu 18.04

Этап 1. Установка и первичная настройка ОС

Здесь всё просто - как античность. Скачиваем образ Raspbian с официального сайта, распаковываем его. Копируем образ с помощью утилиты dd на флэшку. Всё, образ готов к запуску на одноплатнике. Вставляем флэшку, включаем. Одноплатник радостно моргает светодиодами.

Что дальше-то? WiFi нашего он не понимает, ip-адрес его нам неизвестен, да и всё равно по ssh не пускает даже при кабельном соединении. Просто по умолчанию так.

Снова вставляем флэшку в компьютер. Она подключится двумя разделами: загрузочным и системным. Загрузочный — маленький, в файловой системе FAT32, системный — большой, в файловой системе ext4.

Чтобы одноплатник пускал нас по ssh, в загрузочном разделе создаём файл с названием ssh. Увидев этот файл, малинка разрешит нам входить по ssh, но только до перезагрузки. После каждой загрузки этот файл удаляется.

Чтобы одноплатник увидел WiFi нужно немного другое колдовство. Заходим в системный раздел в директорию /etc/wpa_supplicant. Открываем файл wpa_supplicant.conf. Он описывает настройки подключения к беспроводным сетям. Одна сеть там описана. Добавляем описание своей WiFi-сети с реквизитами доступа дополнительным разделом network. Обязательно проследим, чтобы в конце файла не было пустой строки (один раз одноплатник не загружался именно из-за этого).

Для того, чтобы узнать ip-адрес нашей малинки мы можем использовать утилиту nmap таким образом:

Конечно, в эту команду вместо моей сети нужно будет подставить свою.

Мы увидим список всех хостов в сети, и, если малинка подключилась к WiFi, в списке найдём что-то типа такого:

Ну, теперь мы можем подключиться к малинке по ssh.

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

Далее, чтобы закончить настройку, нужно разрешить доступ по ssh на постоянной основе и расширить файловую систему на всю флэшку утилитой raspi-config. Qt весит очень много и на 4Гб у меня просто не влазил.

Выполняем команду

Появляется нехитрое приложение. Стрелками на клавиатуре выбираем пункт “Interfacing Options”, подпункт “Ssh”. Отвечаем утвердительно. Всё, у нас есть ssh.

Далее заходим в пункт “Advanced Options”, подпункт “Expand Filesystem”, отвечаем утвердительно. Ждём. Перезагружаемся командой

Имеет смысл также обновить всё, что есть на малинке. Для этого есть пара команд:

Подождём окончания выполнения этих команд и малинка готова к использованию.

Этап 2. Включение экрана

Экран просто насаживается на одноплатник. Но плотно, всё-таки электроника — это наука о контактах.

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

Выполним следующие команды:

Запускается скрипт, который предлагает нам настройки для драйвера экрана.

Первый диалог предлагает нам выбрать тип экрана. У меня экран 2.8" с резистивным тачем.

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

В третьем диалоге я указал, что HDMI мне не нужен.

Далее скрипт что-то скачивает, устанавливает, думает и просит перезагрузки. Нужно понимать, что ещё раз запустить скрипт adafruit-pitft.sh можно в любое время, это вызовет просто перенастройку экрана.

Вообще очень сильно порадовал тот факт, что экран завёлся с полпинка и неплохо работает даже без калибровки.

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

Этап 3. Сборка Qt

Ну а теперь приступаем к самой мякотке процесса. Почему вообще Qt? Потому что он:

  1. Позволяет собирать очень приличные интерфейсы пользователя (GUI)
  2. Собирается под широкий спектр ОС и архитектур компьютеров
  3. Разработку можно вести на С++ и ни о чём не думать

Сборка Qt состоит из четырёх частей: подготовки окружения, конфигурирования, сборки и установки.

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

Пути я вынес в отдельный скрипт с переменными окружения.

Скачиваем архив *.tar.xz с исходниками Qt отсюда. Распаковываем куда-нибудь.

Скачиваем компилятор gcc под ABI arm-linux-generic-elf-32bit. Я пользуюсь проверенным в бою gcc-linaro-5.4.1–2017.05-x86_64_arm-linux-gnueabihf, но попробовать можно любой, подходящий под требования Qt.

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

Получаем дерево блочных устройств, находим как называется флэшка. Ну и пишем что-то типа

Путь в параметре of можете использовать свой. Это вопрос личного удобства.

Я предпочитаю иметь полный клон файловой системы для разработки на одноплатниках на каждый значительный шаг при настройке. Неглобальные синхронизации можно проводить утилитой rsync.

Итак, мы имеем образ. Нужно его замаунтить в какую-нибудь директорию. Для этого есть такой вот скрипт:

mountImage.sh

Волшебное число 98304 было получено утилитой fdisk. Это смещение системного раздела, указанное в блоках файловой системы в образе флэшки, который мы сняли.

Итак, среда для сборки Qt сформирована.

Конфигурирование сводится к вызову скрипта configure в директории с исходниками Qt с нужными нам параметрами. Параметры подбираются исходя из требований к разрабатываемому ПО и путей к разным нужным штукам на конкретной машине разработчика. Оно нужно в первую очередь для того, чтобы выявить недостающие библиотеки для сборки модулей Qt.

Для конфигурирования Qt я использую вот такой вот скрипт:

buildQt.sh

Возможно, будет смысл назначить другое значение переменной BUILDPATH. В ней указана директория для теневой сборки.

А это наши переменные build_variables.sh:

buildVariables.sh

Нашего внимания касаются только четыре переменные:

PATH_TO_CC — путь к компилятору

RPI_ROOT — директория с примонтированной файловой системой малинки

PATH_TO_QT_SOURCES — директория с исходниками Qt

PATH_TO_QT_RPI — путь, в который будет установлен Qt на малинке.

Задаём их, запускаем скрипт и…

И я вот уверен на сто процентов, что Qt не сконфигурируется. Конфигурирование будет проваливаться из-за отсутствующих библиотек, даже если они будут на месте. Что делать?

После недолгого разглядывания библиотек до меня дошло, что:

  1. Просто отсутствуют символьные ссылки.
  2. Библиотеки есть, но лежат реально по совершенно другим путям
  3. Некоторые библиотеки не видны из-за того, что в качестве имени библиотеки используется имя символьной ссылки с абсолютным путём. Путь существует в рамках корневой файловой системы одноплатника, но его нет в корне нашей файловой системы.

В первом случае всё просто: просматриваем логи configure, находим несуществующие либы. Если в файловой системе малинки есть нужные либы с более или менее подробным указанием версии, то не хватает символьной ссылки, поэтому создаём её вручную командой ln.

Со вторым случаем всё посложнее. В параметрах configure есть такой: -device linux-rasp-pi3-g+. Он указывает на параметры сборки для конкретного типа компьютера. Можно создавать свои параметры, под кастомное железо. Они хранятся в директории с исходниками Qt, в поддиректории qtbase/mkspecs/devices. Там, в директории qtbase/mkspecs вообще много интересного. Находим файл с таким именем, изучаем, правим как надо.

А вот с последним случаем всё непросто. Переделывать все ссылки вручную — в крайней степени неразумно, поэтому пара добрых людей написала свои скрипты для этого хитрого дела и поделилась ими с сообществом.

Первый скрипт лежит здесь. На него легко наткнуться и на нём легко обломаться. Он не подходит уже как года три. Не работает адекватно.

Второй скрипт лежит тут. Он работает как швейцарские армейские командирские часы.

Запустили с указанием корневой директории малинки (из-под sudo, конечно же). Он отработал. Пожалуйста, кушать подано. Конфигуратор выкатывает все фичи Qt, которые будут в билде. Если нас всё устраивает, то переходим к сборке.

Для сборки переходим в директорию BUILDPATH и командуем:

Цифра 4 зависит от количества ядер в процессоре вашего компьютера. Но сколько-нибудь процессора себе оставить надо, для порядка. Идём, завариваем кофе, чай, или наливаем, например, чайного гриба. Выпиваем. Перекусываем. Поговорим с кем-нибудь для души. Возвращаемся. Если у нас возникли ошибки компиляции, имеет смысл запустить сборку ещё раз. Бывает, помогает.

Если ошибки продолжают возникать в тех же местах, то проблема может быть в чём угодно, от компилятора, до отсутствующих или старых хедеров на малинке. Тут подход строго индивидуальный к каждой ошибке сборки.

Если у нас всё собралось, можем приступать к установке.

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

Ждём окончания выполнения команды. Происходит копирование собранных либ и создание символьных ссылок к ним.

После окончания установки образ готов к использованию. Синхронизируем его содержимое с флэшкой или через dd или через rsync.

Если после rsync вы вдруг не можете зайти по ssh с ошибкой “Неправильный пользователь или пароль”, то скорее всего что-то случилось с файлом /etc/shadow. Необходимо копировать его с примонтированного на компьютере раздела флэшки на саму флэшку.

Этап 4. Установка curl.

Заходим на малинку, выполняем команду

Она устанавливает curl со всем необходимым для разработки в качестве библиотеки.

Этап 5. Настройка среды разработки

Теперь мы хотим как-то начать писать код приложения. Для этого мы должны настроить среду разработки QtCreator. Её можно скачать и установить отдельно. Однако, мы поставим весь комплект Qt для того, чтобы основную часть разработки и отладки вести в комфортных условиях — на компьютере. Снова скачиваем Qt, но на этот раз уже не архив, а полноценный *.run файл. Запускаем его, устанавливаем Qt.

Открываем QtCreator. Пробуем запустить какой-нибудь пример. Если всё хорошо, то всё хорошо. Однако чаще всего не хватает установленного gcc и библиотеки libgl1-mesa-dev. Устанавливаем их обычным для Ubuntu способом.

Запускаем какой-нибудь пример. Всё хорошо и красиво запускается и мы рады этому.

Идём в Tools > Options > Kits. Мы видим настройки всех пакетов Qt (А именно пока одного).

Открываем вкладку Debuggers. Добавляем отладчик arm-linux-gnueabihf-gdb из директории с нашим кросс-компилятором, называем его arm-gdb.

Добавление отладчика

Открываем вкладку Compilers, добавляем компилятор arm-linux-gnueabihf-gcc из директории с нашим кросс-компилятором, называем его arm-gcc.

Добавление компилятора

Открываем вкладку Qt versions, добавляем новую версию Qt из смонтированного образа (помните параметр PATH_TO_QT_RPI, который мы видели при сборке? Вот в этой директории на образе, в поддиректории bin лежит файл qmake, указываем его), называем её arm-qt.

Добавление версии Qt

Применяем все изменения, идём во вкладку Kits. Добавляем новый комплект. Называем его rpi-qt. Указываем в нём наши отладчик, компилятор и версию Qt.

Добавление комплекта

В Device type указываем Generic Linux Device, у Device нажимаем Manage. Откроется диалог управления устройствами.

Диалог управления устройствами

Добавляем новое Generic Linux устройство называем его как-нибудь (здесь оно curler.raspberry). Прописываем его ip-адрес и имя пользователя, которым мы ходим по ssh. Жмём test. Видим окно, в котором выводится результат проверки. Применяем. Закрываем. Мы снова на формировании комплекта. У выбора имени справа есть маленькая картинка с компьютером. Нажимаем туда и выбираем значок. Меньше путаницы возникнет дальше.

Этап 6. Написание программы

Создаём новый проект, включаем в нём оба комплекта (и для компьютера, и для малинки).

Подключаем библиотеки в pro-файле:

Ну и немного меняем правила деплоя:

Это приведёт к тому, что проект будет деплоиться на малинку в домашнюю директорию. Меньше проблем с правами во время разработки.

Пишем код, как на обычном Qt. Отлаживаем всё на компьютере. Если всё нормально, пробуем запуститься на малинке. Появляется окно на дисплее с нашей программой.

Однако нам надо было, чтобы была доступна лишь наша программа и поэтому через утилиту raspi-config мы выключили графическую оболочку, а программу запускали с параметром

И она запускалась поверх консоли. Для таких программ вообще имеет смысл использовать скрипты запуска с переменными её окружения и параметрами, но это зависит от способа разработки.

Скрипт запуска программы curler

Исходный код самого нашего приложения вы можете найти здесь.

Этап 7. Конечная настройка. Автозапуск.

В Raspbian за работу со службами отвечает systemd.

Для того, чтобы зарегистрировать службу, необходимо закинуть её описание в виде файла с расширением .service в директорию /etc/systemd/system и выполнить команду:

Где enable — это команда, а servicename — имя закинутого файла.

Команд есть целая куча: start, stop, restart, disable. Лучше всего сверяться с документацией по systemd.

Для запуска службы нужно использовать команду start.

Наше описание службы выглядит следующим образом:

Описание службы curler’а

Другие флэшки

После того, как мы отправили первую партию устройств, в радиусе шаговой доступности от офиса кончились нужные флэш-карты. Взятая новая китайская флэшка, совершенно внезапно не подошла по объёму своей памяти и dd просто отказался её копировать. Возникла необходимость уменьшить образ для записи его на флэшку. Выручил нас вот этот вот скрипт:

Итог

В статье был рассмотрен мой личный способ разработки ПО под одноплатный компьютер Raspberry Pi, однако в общих чертах он подойдёт и под прочие одноплатники подобного класса. Он подойдёт в том числе и под кастомное железо (был случай использования Qt на контроллере бурового станка фирменной разработки), с той разницей, что загружаться моему личному рабочему контроллеру было удобнее через сетевую файловую систему NFS.

Если у вас есть замечания по теме или по тексту, You are welcome.

P.S. Ну и конечно, вы можете познакомиться со всей командой Mad Devs в нашем уютненьком бложике или инстаграме :3

P.P.S. Напоследок повторю ссылку на наш репозиторий с Curler — Репозиторий с curler — https://github.com/maddevsio/QCurler

--

--

Kirill Avdeev
Mad Devs — блог об IT

Senior software developer at MadDevs. Embedded Linux, STM32, NodeMCU, Raspberry Pi, Beaglebone. Qt, C/C++, Golang, Delphi, PHP, Python…