// SPDX-License-Identifier: LGPL-3.0-or-later /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * nymea-zigbee * Zigbee integration module for nymea * * Copyright (C) 2013 - 2024, nymea GmbH * Copyright (C) 2024 - 2025, chargebyte austria GmbH * * This file is part of nymea-zigbee. * * nymea-zigbee is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation, either version 3 * of the License, or (at your option) any later version. * * nymea-zigbee is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with nymea-zigbee. If not, see . * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include "firmwareupdatehandlernxp.h" #include "loggingcategory.h" #include #include #include #include /* Example update provider configuration, if initiallyFlashed is false, a factory reset will be performed in any case [UpdateProvider] updateCommand=/usr/bin/maveo-zigbee-flasher -s /dev/ttymxc2 -p 131 -r 132 updateReleaseFile=/usr/share/maveo-zigbee-flasher/firmware/release.json factoryResetCommand=/usr/bin/maveo-zigbee-flasher -s /dev/ttymxc2 -p 131 -r 132 -e initiallyFlashed=false */ FirmwareUpdateHandlerNxp::FirmwareUpdateHandlerNxp(QObject *parent) : QObject(parent) { } FirmwareUpdateHandlerNxp::FirmwareUpdateHandlerNxp(const QFileInfo &updateProviderConfgigurationFileInfo, QObject *parent) : QObject(parent), m_updateProviderConfgigurationFileInfo(updateProviderConfgigurationFileInfo) { qCDebug(dcZigbeeController()) << "Initialize NXP firmware update provider from" << updateProviderConfgigurationFileInfo.absoluteFilePath(); QSettings configuration(updateProviderConfgigurationFileInfo.absoluteFilePath(), QSettings::IniFormat, this); configuration.beginGroup("UpdateProvider"); // Verify the update command QString updateCommand = configuration.value("updateCommand").toString(); if (updateCommand.isEmpty()) { qCWarning(dcZigbeeController()) << "Update provider configuration available but the update command is not specified in" << m_updateProviderConfgigurationFileInfo.absoluteFilePath(); return; } QStringList updateCommandTokens = updateCommand.split(" "); //qCDebug(dcZigbeeController()) << "Update command tokens" << updateCommandTokens; if (updateCommandTokens.count() == 0) { qCWarning(dcZigbeeController()) << "Update provider configuration available but the update command could not be parsed correctly" << m_updateProviderConfgigurationFileInfo.absoluteFilePath(); return; } m_updateProgram = updateCommandTokens.takeFirst(); m_updateProgramParameters << updateCommandTokens; qCDebug(dcZigbeeController()) << "Update program:" << m_updateProgram << "Parameters:" << m_updateProgramParameters; QFileInfo updateProgramFileInfo(m_updateProgram); if (!updateProgramFileInfo.exists()) { qCWarning(dcZigbeeController()) << "Update provider configuration available but the update binary does not exist" << updateProgramFileInfo.absoluteFilePath(); return; } if (!updateProgramFileInfo.isExecutable()) { qCWarning(dcZigbeeController()) << "Update provider configuration available but the update binary is not executable" << updateProgramFileInfo.absoluteFilePath(); return; } // Verify the factory reset command QString factoryResetCommand = configuration.value("factoryResetCommand").toString(); if (factoryResetCommand.isEmpty()) { qCWarning(dcZigbeeController()) << "Update provider configuration available but the factory reset command is not specified in" << m_updateProviderConfgigurationFileInfo.absoluteFilePath(); return; } QStringList factoryResetCommandTokens = factoryResetCommand.split(" "); //qCDebug(dcZigbeeController()) << "Factory reset command tokens" << factoryResetCommandTokens; if (factoryResetCommandTokens.count() == 0) { qCWarning(dcZigbeeController()) << "Update provider configuration available but the factory reset command could not be parsed correctly" << m_updateProviderConfgigurationFileInfo.absoluteFilePath(); return; } m_factoryResetProgram = factoryResetCommandTokens.takeFirst(); m_factoryResetParameters << factoryResetCommandTokens; qCDebug(dcZigbeeController()) << "Factory reset program:" << m_factoryResetProgram << "Parameters:" << m_factoryResetParameters; QFileInfo factoryResetProgramFileInfo(m_factoryResetProgram); if (!factoryResetProgramFileInfo.exists()) { qCWarning(dcZigbeeController()) << "Update provider configuration available but the factory reset binary does not exist" << factoryResetProgramFileInfo.absoluteFilePath(); return; } if (!factoryResetProgramFileInfo.isExecutable()) { qCWarning(dcZigbeeController()) << "Update provider configuration available but the factory reset binary is not executable" << updateProgramFileInfo.absoluteFilePath(); return; } // Verify update release file and corresponding update file and version m_updateReleaseFilePath = configuration.value("updateReleaseFile").toString(); QFileInfo releaseFileInfo(m_updateReleaseFilePath); if (! releaseFileInfo.exists()) { qCWarning(dcZigbeeController()) << "Update provider configuration available but the release file does not exist" << releaseFileInfo.absoluteFilePath(); return; } // Read the release file QFile releaseFile(releaseFileInfo.absoluteFilePath()); if (!releaseFile.open(QFile::ReadOnly)) { qCWarning(dcZigbeeController()) << "Update provider configuration available but could not open update release file" << releaseFileInfo.absoluteFilePath() << releaseFile.errorString(); return; } QByteArray releaseFileData = releaseFile.readAll(); releaseFile.close(); QJsonParseError jsonError; QJsonDocument jsonDoc = QJsonDocument::fromJson(releaseFileData, &jsonError); if (jsonError.error != QJsonParseError::NoError) { qCWarning(dcZigbeeController()) << "Update provider configuration available but could not parse the release file" << releaseFileInfo.absoluteFilePath() << jsonError.errorString(); return; } QVariantMap releaseMap = jsonDoc.toVariant().toMap(); if (!releaseMap.contains("version")) { qCWarning(dcZigbeeController()) << "Update provider configuration available but the release file does not contain available version information" << releaseFileInfo.absoluteFilePath(); return; } m_availableFirmwareVersion = releaseMap.value("version").toString(); if (m_availableFirmwareVersion.isEmpty()) { qCWarning(dcZigbeeController()) << "Update provider configuration available but the version in the release file is empty" << releaseFileInfo.absoluteFilePath(); return; } if (releaseMap.value("releases").toList().isEmpty()) { qCWarning(dcZigbeeController()) << "Update provider configuration available but the release file does not contain available releases" << releaseFileInfo.absoluteFilePath(); return; } // Check if the controller has been initially flashed m_initiallyFlashed = configuration.value("initiallyFlashed", false).toBool(); configuration.endGroup(); qCDebug(dcZigbeeController()) << "Firmware update provider available with version:" << m_availableFirmwareVersion << "Initially flashed:" << m_initiallyFlashed; // Set up update process m_updateProcess = new QProcess(this); m_updateProcess->setProcessChannelMode(QProcess::MergedChannels); m_updateProcess->setProgram(m_updateProgram); m_updateProcess->setArguments(m_updateProgramParameters); connect(m_updateProcess, static_cast(&QProcess::finished), this, [=](int exitCode, QProcess::ExitStatus exitStatus) { qCDebug(dcZigbeeController()) << "Update process finihed" << exitCode << exitStatus; if (exitCode != 0) { emit updateFinished(false); } else { emit updateFinished(true); } }); connect(m_updateProcess, &QProcess::readyRead, this, [=]() { qCDebug(dcZigbeeController()) << "Update process:" << qUtf8Printable(m_updateProcess->readAll()); }); // Set up factory reset process m_factoryResetProcess = new QProcess(this); m_factoryResetProcess->setProcessChannelMode(QProcess::MergedChannels); m_factoryResetProcess->setProgram(m_factoryResetProgram); m_factoryResetProcess->setArguments(m_factoryResetParameters); connect(m_factoryResetProcess, static_cast(&QProcess::finished), this, [=](int exitCode, QProcess::ExitStatus exitStatus) { qCDebug(dcZigbeeController()) << "Factory reset process finihed" << exitCode << exitStatus; if (exitCode != 0) { emit updateFinished(false); } else { // The factory reset has been finished successfully QSettings configuration(m_updateProviderConfgigurationFileInfo.absoluteFilePath(), QSettings::IniFormat, this); configuration.beginGroup("UpdateProvider"); configuration.setValue("initiallyFlashed", true); configuration.endGroup(); m_initiallyFlashed = true; emit initiallyFlashedChanged(m_initiallyFlashed); emit updateFinished(true); } }); connect(m_factoryResetProcess, &QProcess::readyRead, this, [=]() { qCDebug(dcZigbeeController()) << "Factory reset process:" << qUtf8Printable(m_factoryResetProcess->readAll()); }); m_valid = true; } bool FirmwareUpdateHandlerNxp::initiallyFlashed() const { return m_initiallyFlashed; } bool FirmwareUpdateHandlerNxp::updateRunning() const { return m_updateProcess->state() != QProcess::NotRunning; } bool FirmwareUpdateHandlerNxp::isValid() const { return m_valid; } QString FirmwareUpdateHandlerNxp::availableFirmwareVersion() const { return m_availableFirmwareVersion; } void FirmwareUpdateHandlerNxp::startUpdate() { if (!m_updateProcess) { qCWarning(dcZigbeeController()) << "Cannot start update process. The updater is not vaild."; return; } qCDebug(dcZigbeeController()) << "Starting firmware update for NXP controller"; qCDebug(dcZigbeeController()) << "Firmware version:" << m_availableFirmwareVersion; m_updateProcess->start(); } void FirmwareUpdateHandlerNxp::startFactoryReset() { if (!m_factoryResetProcess) { qCWarning(dcZigbeeController()) << "Cannot start factory reset process. The update provider is not vaild."; return; } qCDebug(dcZigbeeController()) << "Starting factory reset for NXP controller"; qCDebug(dcZigbeeController()) << "Firmware version:" << m_availableFirmwareVersion; m_factoryResetProcess->start(); }