Embedded

Обновление прошивки (OTA) на embedded Linux с Qt

Введение

Embedded-устройства, развёрнутые «в поле» (у конечных пользователей или на производственных площадках), редко имеют физический доступ для ручного обновления — необходим механизм обновления «по воздуху» (Over-The-Air, OTA), позволяющий безопасно обновить как сам Qt-софт, так и, возможно, всю систему целиком, удалённо и без выезда инженера на место. В статье разберём подходы к организации OTA-обновлений для embedded Linux устройств с Qt, на верхнем уровне затрагивающем как часть общего цикла про установку/автообновление (статьи 210–219), адаптированную к специфике embedded.

Концепция

В отличие от десктопных приложений, где обновляется отдельное приложение поверх уже существующей ОС (как разбиралось через QtIFW), embedded-устройства часто обновляют целиком всю прошивку — операционную систему вместе с приложением — как единый, атомарный образ. Распространённый паттерн — A/B-партиционирование: на устройстве есть два независимых раздела с полной системой, активен в любой момент только один из них, новое обновление полностью записывается в неактивный раздел, и переключение происходит атомарно при следующей перезагрузке — если новая версия не загружается корректно, устройство автоматически откатывается на предыдущий, заведомо рабочий раздел.

Пример кода: Обновление прошивки (OTA)

# Пример использования RAUC (Robust Auto-Update Controller) —
# популярного инструмента для A/B-обновлений embedded Linux

# Установка нового обновления (RAUC сам определяет неактивный слот для записи)
rauc install /media/usb/firmware-update-2.1.0.raucb

# Проверка статуса текущей и резервной системы
rauc status
# system.conf — манифест RAUC, описывающий структуру слотов A/B
[system]
compatible=embedded-dashboard-board
bootloader=u-boot

[slot.rootfs.0]
device=/dev/mmcblk0p2
type=ext4
bootname=system0

[slot.rootfs.1]
device=/dev/mmcblk0p3
type=ext4
bootname=system1
// Интеграция проверки и инициации OTA-обновления внутри самого Qt-приложения
#include <QProcess>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QFile>
#include <QDebug>

class OtaUpdateManager : public QObject
{
    Q_OBJECT
public:
    void checkAndDownloadUpdate(const QString &updateServerUrl)
    {
        QNetworkRequest request(QUrl(updateServerUrl + "/latest-version.json"));
        auto *reply = m_networkManager.get(request);

        connect(reply, &QNetworkReply::finished, this, [this, reply]() {
            // упрощённо: разбор ответа и сравнение версий аналогично статье 213
            const QByteArray data = reply->readAll();
            reply->deleteLater();
            downloadUpdatePackage(data);
        });
    }

private:
    void downloadUpdatePackage(const QByteArray & /*versionInfo*/)
    {
        // Скачивание .raucb-пакета во временную директорию, затем установка через RAUC
        QProcess *installProcess = new QProcess(this);
        connect(installProcess, &QProcess::finished, this, [installProcess](int exitCode, QProcess::ExitStatus) {
            if (exitCode == 0) {
                qDebug() << "Обновление успешно установлено, требуется перезагрузка";
            } else {
                qWarning() << "Ошибка установки обновления, код:" << exitCode;
            }
            installProcess->deleteLater();
        });

        installProcess->start("rauc", {"install", "/tmp/firmware-update.raucb"});
    }

    QNetworkAccessManager m_networkManager;
};

Пояснения к коду

system.conf для RAUC описывает структуру A/B-слотов — два независимых раздела (slot.rootfs.0, slot.rootfs.1), каждый из которых может содержать полную, независимую версию системы; при установке обновления через rauc install инструмент сам определяет, какой из слотов в данный момент неактивен, и записывает новый образ именно туда, не затрагивая работающую в данный момент систему. OtaUpdateManager в Qt-приложении показывает верхнеуровневую интеграцию — приложение периодически проверяет наличие новой версии через обычный сетевой запрос (аналогично подходу из статьи 213 про QtIFW maintenance tool), и при обнаружении обновления скачивает .raucb-пакет и инициирует его установку через вызов внешнего инструмента rauc посредством QProcess.

Критичное архитектурное отличие embedded OTA от десктопного автообновления — после установки требуется полная перезагрузка устройства, переключающая загрузчик на только что обновлённый слот, что качественно отличается от десктопной модели, где обычно можно обновить только отдельные файлы приложения без перезагрузки всей операционной системы.

Подводные камни

  • Сбой питания или прерывание процесса записи обновления в неактивный слот — если запись .raucb-образа в неактивный раздел прервётся (потеря питания посередине процесса), активный, рабочий раздел при этом не затрагивается, и устройство продолжит загружаться со старой, рабочей версии — это ключевое преимущество A/B-схемы, ради которого она и используется, но оно работает только если процесс действительно реализован атомарно (запись полностью в неактивный слот, переключение активного слота — отдельная, отдельная атомарная операция в загрузчике), а не как смешанное обновление файлов внутри уже активного раздела.
  • Недостаток свободного места на устройстве для хранения сразу двух полных копий системы (активной и резервной) — A/B-схема требует вдвое больше места хранения по сравнению с single-partition подходом, что может быть существенным ограничением для устройств с очень небольшим объёмом встроенной флеш-памяти; для таких устройств иногда используются более сложные, частичные (delta) обновления, требующие значительно более тщательной проверки целостности.
  • Отсутствие проверки работоспособности новой версии после переключения активного слота перед окончательным подтверждением успешности обновления — аналогично rollback-логике для десктопных приложений (статья 217), embedded-устройство должно иметь механизм «если новая версия не загрузилась корректно или крашится сразу после загрузки, автоматически вернуться на предыдущий слот», а не считать обновление успешным сразу после простой записи образа без последующей валидации.
  • Передача больших файлов обновления по нестабильному, медленному, или дорогому (например, мобильному) каналу связи устройства, развёрнутого в полевых условиях. Для устройств с ограниченной или нестабильной связью полная загрузка целого образа системы при каждом обновлении может быть непрактичной — стоит рассмотреть либо более частые, но компактные delta-обновления, либо явную проверку доступности достаточно качественного канала связи перед началом загрузки большого файла обновления, с возможностью докачки прерванной загрузки.