Или как быстро начать разрабатывать на QT для Raspberry Pi и перестать беспокоиться
В один прекрасный день, совершенно внезапно выяснилось, что необходимо было давным давно закончить проект для одной международной горнодобывающей компании. Её рабочие регулярно жаловались на качество этих ваших интернетов в номерах жилого корпуса предприятия. Понятно, что факторов, из которых складывается качество работы с интернетом очень много, и зачастую источником проблем может являться старенький смартфон, а не 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 и композитным выходом мы решили не пользоваться: чем меньше проводов, тем лучше. Не заметят и порвут. Или наоборот, порвут и не заметят. Горняки — народ суровый.
Дисплеи
В дополнение к одноплатнику были приобретены резистивные тач-дисплеи Adafruit PiTFT. Дисплей сразу понравился. Простой, лаконичный, без излишеств. Для подключения к одноплатнику достаточно просто плотно насадить его на плату.
Ну и вещи, про которые принято забывать: питание и флэшка для операционной системы (ОС).
Питание
Сначала я запитывал одноплатник от ноутбука, но что-то шло не так и одноплатник просто не находился в сети. Посмотрев на то, как моргают светодиоды (повторялась одна и та же короткая последовательность), я предположил, что он постоянно перезагружается. Наскоро ограбив коллегу на зарядку от сотового телефона, я запитал одноплатник полноценными 2.5А тока. Стало понятно, что одноплатник немилосерден и прожорлив к питанию.
Флэшка
В проекте она нужна для того, чтобы записать на неё образ ОС. Четыре года назад я делал сайд-проект на Raspberry Pi 2 b+ и сдуру взял десяток флэшек от неизвестого китайского производителя. Флэшки прекрасно работали на смартфоне и ноутбуке, но на Raspberry Pi не шли ни в какую. Однако, с взятыми двумя днями позже флэшками от AData, одноплатник заработал сразу и без проблем.
Программное обеспечение
В качестве фреймворка для приложения мы решили использовать Qt.
Итак, для реализации проекта с точки зрения программного обеспечения нужно было сделать следующие шаги:
- Установка ОС на одноплатник и её первичная настройка
- Включение экрана
- Сборка и установка Qt
- Установка curl
- Настройка среды разработки
- Непосредственно написание программы curler
- Настройка ОС таким образом, чтобы 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 таким образом:
sudo nmap -sP 192.168.20.0/24
Конечно, в эту команду вместо моей сети нужно будет подставить свою.
Мы увидим список всех хостов в сети, и, если малинка подключилась к WiFi, в списке найдём что-то типа такого:
Nmap scan report for 192.168.20.49
Host is up (0.0013s latency).
MAC Address: B8:27:EB:A0:50:9E (Raspberry Pi Foundation)
Ну, теперь мы можем подключиться к малинке по ssh.
ssh pi@192.168.20.49
Одноплатник спросит пароль. По умолчанию в качестве пароля используется raspberry. Настоятельно рекомендуется его сразу поменять.
Далее, чтобы закончить настройку, нужно разрешить доступ по ssh на постоянной основе и расширить файловую систему на всю флэшку утилитой raspi-config. Qt весит очень много и на 4Гб у меня просто не влазил.
Выполняем команду
sudo raspi-config
Появляется нехитрое приложение. Стрелками на клавиатуре выбираем пункт “Interfacing Options”, подпункт “Ssh”. Отвечаем утвердительно. Всё, у нас есть ssh.
Далее заходим в пункт “Advanced Options”, подпункт “Expand Filesystem”, отвечаем утвердительно. Ждём. Перезагружаемся командой
sudo reboot -h now
Имеет смысл также обновить всё, что есть на малинке. Для этого есть пара команд:
sudo apt updatesudo apt upgrade
Подождём окончания выполнения этих команд и малинка готова к использованию.
Этап 2. Включение экрана
Экран просто насаживается на одноплатник. Но плотно, всё-таки электроника — это наука о контактах.
Собственно, шаги по софтверному включению экрана описаны здесь, но имеет смысл коротко привести их и тут.
Выполним следующие команды:
cd ~wget https://raw.githubusercontent.com/adafruit/Raspberry-Pi-Installer-Scripts/master/adafruit-pitft.shchmod +x adafruit-pitft.shsudo ./adafruit-pitft.sh
Запускается скрипт, который предлагает нам настройки для драйвера экрана.
Первый диалог предлагает нам выбрать тип экрана. У меня экран 2.8" с резистивным тачем.
Второй диалог предлагает выбрать поворот экрана. Для начала можно выбрать просто что-нибудь. А потом-то уже подобрать поворот, который нужен.
В третьем диалоге я указал, что HDMI мне не нужен.
Далее скрипт что-то скачивает, устанавливает, думает и просит перезагрузки. Нужно понимать, что ещё раз запустить скрипт adafruit-pitft.sh можно в любое время, это вызовет просто перенастройку экрана.
Вообще очень сильно порадовал тот факт, что экран завёлся с полпинка и неплохо работает даже без калибровки.
После перезагрузки мы видим, что экран радостно показывает нам консоль, а затем и графическую оболочку.
Этап 3. Сборка Qt
Ну а теперь приступаем к самой мякотке процесса. Почему вообще Qt? Потому что он:
- Позволяет собирать очень приличные интерфейсы пользователя (GUI)
- Собирается под широкий спектр ОС и архитектур компьютеров
- Разработку можно вести на С++ и ни о чём не думать
Сборка 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. Втыкаем флэшку в компьютер и выполняем
lsblk
Получаем дерево блочных устройств, находим как называется флэшка. Ну и пишем что-то типа
dd if=/dev/mmcblk01 of=~/images/rpi_working.img bs=100M status=progress
Путь в параметре 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 не сконфигурируется. Конфигурирование будет проваливаться из-за отсутствующих библиотек, даже если они будут на месте. Что делать?
После недолгого разглядывания библиотек до меня дошло, что:
- Просто отсутствуют символьные ссылки.
- Библиотеки есть, но лежат реально по совершенно другим путям
- Некоторые библиотеки не видны из-за того, что в качестве имени библиотеки используется имя символьной ссылки с абсолютным путём. Путь существует в рамках корневой файловой системы одноплатника, но его нет в корне нашей файловой системы.
В первом случае всё просто: просматриваем логи configure, находим несуществующие либы. Если в файловой системе малинки есть нужные либы с более или менее подробным указанием версии, то не хватает символьной ссылки, поэтому создаём её вручную командой ln.
Со вторым случаем всё посложнее. В параметрах configure есть такой: -device linux-rasp-pi3-g+. Он указывает на параметры сборки для конкретного типа компьютера. Можно создавать свои параметры, под кастомное железо. Они хранятся в директории с исходниками Qt, в поддиректории qtbase/mkspecs/devices. Там, в директории qtbase/mkspecs вообще много интересного. Находим файл с таким именем, изучаем, правим как надо.
А вот с последним случаем всё непросто. Переделывать все ссылки вручную — в крайней степени неразумно, поэтому пара добрых людей написала свои скрипты для этого хитрого дела и поделилась ими с сообществом.
Первый скрипт лежит здесь. На него легко наткнуться и на нём легко обломаться. Он не подходит уже как года три. Не работает адекватно.
Второй скрипт лежит тут. Он работает как швейцарские армейские командирские часы.
Запустили с указанием корневой директории малинки (из-под sudo, конечно же). Он отработал. Пожалуйста, кушать подано. Конфигуратор выкатывает все фичи Qt, которые будут в билде. Если нас всё устраивает, то переходим к сборке.
Для сборки переходим в директорию BUILDPATH и командуем:
make -j4
Цифра 4 зависит от количества ядер в процессоре вашего компьютера. Но сколько-нибудь процессора себе оставить надо, для порядка. Идём, завариваем кофе, чай, или наливаем, например, чайного гриба. Выпиваем. Перекусываем. Поговорим с кем-нибудь для души. Возвращаемся. Если у нас возникли ошибки компиляции, имеет смысл запустить сборку ещё раз. Бывает, помогает.
Если ошибки продолжают возникать в тех же местах, то проблема может быть в чём угодно, от компилятора, до отсутствующих или старых хедеров на малинке. Тут подход строго индивидуальный к каждой ошибке сборки.
Если у нас всё собралось, можем приступать к установке.
Установка производится из той же директории BUILDPATH командой
sudo make install
Ждём окончания выполнения команды. Происходит копирование собранных либ и создание символьных ссылок к ним.
После окончания установки образ готов к использованию. Синхронизируем его содержимое с флэшкой или через dd или через rsync.
Если после rsync вы вдруг не можете зайти по ssh с ошибкой “Неправильный пользователь или пароль”, то скорее всего что-то случилось с файлом /etc/shadow. Необходимо копировать его с примонтированного на компьютере раздела флэшки на саму флэшку.
Этап 4. Установка curl.
Заходим на малинку, выполняем команду
sudo apt install libcurl4-gnutls-dev
Она устанавливает 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.
Применяем все изменения, идём во вкладку Kits. Добавляем новый комплект. Называем его rpi-qt. Указываем в нём наши отладчик, компилятор и версию Qt.
В Device type указываем Generic Linux Device, у Device нажимаем Manage. Откроется диалог управления устройствами.
Добавляем новое Generic Linux устройство называем его как-нибудь (здесь оно curler.raspberry). Прописываем его ip-адрес и имя пользователя, которым мы ходим по ssh. Жмём test. Видим окно, в котором выводится результат проверки. Применяем. Закрываем. Мы снова на формировании комплекта. У выбора имени справа есть маленькая картинка с компьютером. Нажимаем туда и выбираем значок. Меньше путаницы возникнет дальше.
Этап 6. Написание программы
Создаём новый проект, включаем в нём оба комплекта (и для компьютера, и для малинки).
Подключаем библиотеки в pro-файле:
LIBS += -lcurl
Ну и немного меняем правила деплоя:
target.path = /home/pi/qcurler/qcurler
INSTALLS += target
Это приведёт к тому, что проект будет деплоиться на малинку в домашнюю директорию. Меньше проблем с правами во время разработки.
Пишем код, как на обычном Qt. Отлаживаем всё на компьютере. Если всё нормально, пробуем запуститься на малинке. Появляется окно на дисплее с нашей программой.
Однако нам надо было, чтобы была доступна лишь наша программа и поэтому через утилиту raspi-config мы выключили графическую оболочку, а программу запускали с параметром
-platform linuxfb
И она запускалась поверх консоли. Для таких программ вообще имеет смысл использовать скрипты запуска с переменными её окружения и параметрами, но это зависит от способа разработки.
Скрипт запуска программы curler
Исходный код самого нашего приложения вы можете найти здесь.
Этап 7. Конечная настройка. Автозапуск.
В Raspbian за работу со службами отвечает systemd.
Для того, чтобы зарегистрировать службу, необходимо закинуть её описание в виде файла с расширением .service в директорию /etc/systemd/system и выполнить команду:
sudo systemctl enable servicename.service
Где 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
Ранее статья была опубликована тут.