From 7918d4de2de17763073920c822525166c8fdabc8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Sat, 24 Oct 2020 11:08:41 +0200 Subject: [PATCH] Implement initially flashed property and reqork update provider --- .../backends/nxp/firmwareupdatehandlernxp.cpp | 139 +++++++++++++++--- .../backends/nxp/firmwareupdatehandlernxp.h | 19 ++- .../nxp/zigbeebridgecontrollernxp.cpp | 21 +++ .../backends/nxp/zigbeebridgecontrollernxp.h | 1 + .../backends/nxp/zigbeenetworknxp.cpp | 7 + libnymea-zigbee/zigbeebridgecontroller.cpp | 12 +- libnymea-zigbee/zigbeebridgecontroller.h | 4 + libnymea-zigbee/zigbeenetwork.cpp | 3 +- libnymea-zigbee/zigbeenetwork.h | 2 +- 9 files changed, 179 insertions(+), 29 deletions(-) diff --git a/libnymea-zigbee/backends/nxp/firmwareupdatehandlernxp.cpp b/libnymea-zigbee/backends/nxp/firmwareupdatehandlernxp.cpp index 01b95a5..b4a2558 100644 --- a/libnymea-zigbee/backends/nxp/firmwareupdatehandlernxp.cpp +++ b/libnymea-zigbee/backends/nxp/firmwareupdatehandlernxp.cpp @@ -34,10 +34,16 @@ #include /* - * [UpdateProvider] - * updateBinary=/usr/bin/maveo-zigbee-flasher - * updateReleaseFile=/usr/share/maveo-zigbee-flasher/firmware/release.json - */ + +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) @@ -49,26 +55,68 @@ FirmwareUpdateHandlerNxp::FirmwareUpdateHandlerNxp(const QFileInfo &updateProvid QObject(parent), m_updateProviderConfgigurationFileInfo(updateProviderConfgigurationFileInfo) { - qCDebug(dcZigbeeController()) << "Initialize NXP firmware update provider"; + qCDebug(dcZigbeeController()) << "Initialize NXP firmware update provider from" << updateProviderConfgigurationFileInfo.absoluteFilePath(); QSettings configuration(updateProviderConfgigurationFileInfo.absoluteFilePath(), QSettings::IniFormat, this); configuration.beginGroup("UpdateProvider"); - // Verify the update provider binary - m_updateBinary = configuration.value("updateBinary").toString(); - if (m_updateBinary.isEmpty()) { - qCWarning(dcZigbeeController()) << "Update provider configuration available but the update binary is not specified in" << m_updateProviderConfgigurationFileInfo.absoluteFilePath(); + // 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; } - QFileInfo updateBinaryFileInfo(m_updateBinary); - if (!updateBinaryFileInfo.exists()) { - qCWarning(dcZigbeeController()) << "Update provider configuration available but the update binary does not exist" << updateBinaryFileInfo.absoluteFilePath(); + 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; } - if (!updateBinaryFileInfo.isExecutable()) { - qCWarning(dcZigbeeController()) << "Update provider configuration available but the update binary is not executable" << updateBinaryFileInfo.absoluteFilePath(); + 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; } @@ -126,12 +174,18 @@ FirmwareUpdateHandlerNxp::FirmwareUpdateHandlerNxp(const QFileInfo &updateProvid return; } - qCDebug(dcZigbeeController()) << "Firmware update prvider available:" << firmwareFileInfo.absoluteFilePath() << "Version:" << m_availableFirmwareVersion; + // Check if the controller has been initially flashed + m_initiallyFlashed = configuration.value("initiallyFlashed", false).toBool(); + configuration.endGroup(); + qCDebug(dcZigbeeController()) << "Firmware update provider available:" << firmwareFileInfo.fileName() << "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_updateBinary); + m_updateProcess->setProgram(m_updateProgram); + m_updateProcess->setArguments(m_updateProgramParameters); connect(m_updateProcess, static_cast(&QProcess::finished), [=](int exitCode, QProcess::ExitStatus exitStatus) { qCDebug(dcZigbeeController()) << "Update process finihed" << exitCode << exitStatus; @@ -143,12 +197,45 @@ FirmwareUpdateHandlerNxp::FirmwareUpdateHandlerNxp(const QFileInfo &updateProvid }); connect(m_updateProcess, &QProcess::readyRead, [=]() { - qCDebug(dcZigbeeController()) << "Update process:" << qUtf8Printable(m_updateProcess->readAll().replace(0x1B, '\r')); + 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), [=](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, [=]() { + 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; @@ -159,11 +246,6 @@ bool FirmwareUpdateHandlerNxp::isValid() const return m_valid; } -QString FirmwareUpdateHandlerNxp::updateBinary() const -{ - return m_updateBinary; -} - QString FirmwareUpdateHandlerNxp::availableFirmwareFileName() const { return m_availableFirmwareFileName; @@ -186,3 +268,16 @@ void FirmwareUpdateHandlerNxp::startUpdate() 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 file:" << m_availableFirmwareFileName; + qCDebug(dcZigbeeController()) << "Firmware version:" << m_availableFirmwareVersion; + m_factoryResetProcess->start(); +} diff --git a/libnymea-zigbee/backends/nxp/firmwareupdatehandlernxp.h b/libnymea-zigbee/backends/nxp/firmwareupdatehandlernxp.h index b42e7f0..595078c 100644 --- a/libnymea-zigbee/backends/nxp/firmwareupdatehandlernxp.h +++ b/libnymea-zigbee/backends/nxp/firmwareupdatehandlernxp.h @@ -39,25 +39,36 @@ public: explicit FirmwareUpdateHandlerNxp(QObject *parent = nullptr); FirmwareUpdateHandlerNxp(const QFileInfo &updateProviderConfgigurationFileInfo, QObject *parent = nullptr); + bool initiallyFlashed() const; bool updateRunning() const; bool isValid() const; - QString updateBinary() const; QString availableFirmwareFileName() const; QString availableFirmwareVersion() const; void startUpdate(); + void startFactoryReset(); signals: void updateRunningChanged(bool updateRunning); void updateFinished(bool success); + void initiallyFlashedChanged(bool initiallyFlashed); private: QFileInfo m_updateProviderConfgigurationFileInfo; - bool m_valid = false; - QProcess *m_updateProcess = nullptr; - QString m_updateBinary; + bool m_initiallyFlashed = false; + bool m_valid = false; + + QProcess *m_updateProcess = nullptr; + QProcess *m_factoryResetProcess = nullptr; + + QString m_updateProgram; + QStringList m_updateProgramParameters; + + QString m_factoryResetProgram; + QStringList m_factoryResetParameters; + QString m_updateReleaseFilePath; QString m_availableFirmwareFileName; QString m_availableFirmwareVersion; diff --git a/libnymea-zigbee/backends/nxp/zigbeebridgecontrollernxp.cpp b/libnymea-zigbee/backends/nxp/zigbeebridgecontrollernxp.cpp index 92861c6..e385d9f 100644 --- a/libnymea-zigbee/backends/nxp/zigbeebridgecontrollernxp.cpp +++ b/libnymea-zigbee/backends/nxp/zigbeebridgecontrollernxp.cpp @@ -266,6 +266,20 @@ void ZigbeeBridgeControllerNxp::startFirmwareUpdate() m_firmwareUpdateHandler->startUpdate(); } +void ZigbeeBridgeControllerNxp::startFactoryResetUpdate() +{ + if (!m_firmwareUpdateHandler) + return; + + m_updateRunning = true; + emit updateRunningChanged(m_updateRunning); + + qCDebug(dcZigbeeController()) << "Disable UART interface for factory reset update..."; + m_interface->disable(); + + m_firmwareUpdateHandler->startFactoryReset(); +} + ZigbeeInterfaceNxpReply *ZigbeeBridgeControllerNxp::createReply(Nxp::Command command, quint8 sequenceNumber, const QString &requestName, const QByteArray &requestData, QObject *parent) { // Create the reply @@ -440,9 +454,16 @@ void ZigbeeBridgeControllerNxp::initializeUpdateProvider() } }); + connect(m_firmwareUpdateHandler, &FirmwareUpdateHandlerNxp::initiallyFlashedChanged, this, [this](bool initiallyFlashed){ + qCDebug(dcZigbeeController()) << "Firmware initially flashed changed to" << initiallyFlashed; + m_initiallyFlashed = initiallyFlashed; + }); + qCDebug(dcZigbeeController()) << "The firmware update provider is valid. The firmware of this NXP controller can be updated."; m_canUpdate = true; emit canUpdateChanged(m_canUpdate); + + m_initiallyFlashed = m_firmwareUpdateHandler->initiallyFlashed(); } void ZigbeeBridgeControllerNxp::onInterfaceAvailableChanged(bool available) diff --git a/libnymea-zigbee/backends/nxp/zigbeebridgecontrollernxp.h b/libnymea-zigbee/backends/nxp/zigbeebridgecontrollernxp.h index e39f42f..7981484 100644 --- a/libnymea-zigbee/backends/nxp/zigbeebridgecontrollernxp.h +++ b/libnymea-zigbee/backends/nxp/zigbeebridgecontrollernxp.h @@ -86,6 +86,7 @@ public: bool updateAvailable(const QString ¤tVersion) override; QString updateFirmwareVersion() const override; void startFirmwareUpdate() override; + void startFactoryResetUpdate() override; signals: void controllerStateChanged(ControllerState controllerState); diff --git a/libnymea-zigbee/backends/nxp/zigbeenetworknxp.cpp b/libnymea-zigbee/backends/nxp/zigbeenetworknxp.cpp index 35fcb55..d02f15c 100644 --- a/libnymea-zigbee/backends/nxp/zigbeenetworknxp.cpp +++ b/libnymea-zigbee/backends/nxp/zigbeenetworknxp.cpp @@ -148,6 +148,7 @@ bool ZigbeeNetworkNxp::processVersionReply(ZigbeeInterfaceNxpReply *reply) if (m_controller->canUpdate()) { qCDebug(dcZigbeeNetwork()) << "Unable to get controller version."; qCDebug(dcZigbeeNetwork()) << "Firmware update provider available. Try to flash the firmware, maybe that fixes the problem."; + // FIXME: try 3 times, then give up or perform a factory flash if (!m_controller->updateRunning()) { clearSettings(); qCDebug(dcZigbeeNetwork()) << "Starting firmware update..."; @@ -249,6 +250,12 @@ void ZigbeeNetworkNxp::onControllerAvailableChanged(bool available) { qCDebug(dcZigbeeNetwork()) << "Controller is" << (available ? "now available" : "not available any more"); if (available) { + if (m_controller->canUpdate() && !m_controller->initiallyFlashed()) { + qCDebug(dcZigbeeNetwork()) << "The firmware of the controller can be updated and has not been initially flashed. Perform a factory reset flash procedure..."; + m_controller->startFactoryResetUpdate(); + return; + } + m_reconnectCounter = 0; ZigbeeInterfaceNxpReply *reply = m_controller->requestVersion(); connect(reply, &ZigbeeInterfaceNxpReply::finished, this, [this, reply](){ diff --git a/libnymea-zigbee/zigbeebridgecontroller.cpp b/libnymea-zigbee/zigbeebridgecontroller.cpp index 271e8ee..8b62d2e 100644 --- a/libnymea-zigbee/zigbeebridgecontroller.cpp +++ b/libnymea-zigbee/zigbeebridgecontroller.cpp @@ -48,6 +48,11 @@ bool ZigbeeBridgeController::canUpdate() const return m_canUpdate; } +bool ZigbeeBridgeController::initiallyFlashed() const +{ + return m_initiallyFlashed; +} + bool ZigbeeBridgeController::updateRunning() const { return m_updateRunning; @@ -69,9 +74,14 @@ void ZigbeeBridgeController::startFirmwareUpdate() qCWarning(dcZigbeeController()) << "Cannot start firmware update. The feature is not implemented for this controller."; } +void ZigbeeBridgeController::startFactoryResetUpdate() +{ + qCWarning(dcZigbeeController()) << "Cannot start firmware factory reset update. The feature is not implemented for this controller."; +} + void ZigbeeBridgeController::setSettingsDirectory(const QDir &settingsDirectory) { - qCDebug(dcZigbeeController()) << "Using settings directory" << settingsDirectory.canonicalPath(); + qCDebug(dcZigbeeController()) << "Using settings directory" << settingsDirectory.absolutePath(); m_settingsDirectory = settingsDirectory; // Check if we have an update provider for the controller, if so, diff --git a/libnymea-zigbee/zigbeebridgecontroller.h b/libnymea-zigbee/zigbeebridgecontroller.h index 849e4ad..e01af22 100644 --- a/libnymea-zigbee/zigbeebridgecontroller.h +++ b/libnymea-zigbee/zigbeebridgecontroller.h @@ -49,16 +49,20 @@ public: bool available() const; bool canUpdate() const; + bool initiallyFlashed() const; bool updateRunning() const; + // Optional update/initialize procedure for the zigbee controller virtual bool updateAvailable(const QString ¤tVersion); virtual QString updateFirmwareVersion() const; virtual void startFirmwareUpdate(); + virtual void startFactoryResetUpdate(); protected: QString m_firmwareVersion; bool m_available = false; bool m_canUpdate = false; + bool m_initiallyFlashed = false; bool m_updateRunning = false; QDir m_settingsDirectory = QDir("/etc/nymea/"); diff --git a/libnymea-zigbee/zigbeenetwork.cpp b/libnymea-zigbee/zigbeenetwork.cpp index b157c94..2f57db4 100644 --- a/libnymea-zigbee/zigbeenetwork.cpp +++ b/libnymea-zigbee/zigbeenetwork.cpp @@ -56,10 +56,11 @@ QString ZigbeeNetwork::settingsFilenName() const void ZigbeeNetwork::setSettingsFileName(const QString &settingsFileName) { + qCDebug(dcZigbeeNetwork()) << "Using settings file" << settingsFileName; m_settingsFileName = settingsFileName; emit settingsFileNameChanged(m_settingsFileName); - m_settingsDirectory = QFileInfo(m_settingsFileName).canonicalPath(); + m_settingsDirectory = QDir(QFileInfo(m_settingsFileName).absolutePath()); bridgeController()->setSettingsDirectory(m_settingsDirectory); } diff --git a/libnymea-zigbee/zigbeenetwork.h b/libnymea-zigbee/zigbeenetwork.h index 6ff911f..d5158d9 100644 --- a/libnymea-zigbee/zigbeenetwork.h +++ b/libnymea-zigbee/zigbeenetwork.h @@ -136,7 +136,7 @@ private: // Network storage QString m_settingsFileName = "/etc/nymea/nymea-zigbee.conf"; - QDir m_settingsDirectory; + QDir m_settingsDirectory = QDir("/etc/nymea/"); QList m_nodes; QList m_uninitializedNodes;