From 6e4a0bc84be3751d8c8e5bad7f178a8383597ddc Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Tue, 21 Jan 2020 22:43:02 +0100 Subject: [PATCH] Add better device setup status details to api --- .../devices/devicemanagerimplementation.cpp | 71 ++++++++++--------- libnymea-core/jsonrpc/devicehandler.cpp | 31 +++++++- libnymea-core/jsonrpc/devicehandler.h | 3 +- .../jsonrpc/jsonrpcserverimplementation.cpp | 42 +++++------ libnymea-core/jsonrpc/jsonvalidator.cpp | 2 +- libnymea/devices/device.cpp | 33 ++++++--- libnymea/devices/device.h | 37 +++++++--- libnymea/jsonrpc/jsonhandler.cpp | 10 +++ libnymea/jsonrpc/jsonhandler.h | 2 + tests/auto/api.json | 23 ++++-- tests/auto/devices/testdevices.cpp | 51 +++++++++++++ 11 files changed, 223 insertions(+), 82 deletions(-) diff --git a/libnymea-core/devices/devicemanagerimplementation.cpp b/libnymea-core/devices/devicemanagerimplementation.cpp index 4a5c1036..b2e94752 100644 --- a/libnymea-core/devices/devicemanagerimplementation.cpp +++ b/libnymea-core/devices/devicemanagerimplementation.cpp @@ -436,7 +436,7 @@ DeviceSetupInfo *DeviceManagerImplementation::reconfigureDeviceInternal(Device * plugin->deviceRemoved(device); // mark setup as incomplete - device->setSetupComplete(false); + device->setSetupStatus(Device::DeviceSetupStatusInProgress, Device::DeviceErrorNoError); // set new params foreach (const Param ¶m, params) { @@ -454,6 +454,7 @@ DeviceSetupInfo *DeviceManagerImplementation::reconfigureDeviceInternal(Device * if (info->status() != Device::DeviceErrorNoError) { qCWarning(dcDeviceManager()) << "Device reconfiguration failed for" << info->device()->name() << info->device()->id().toString() << info->status() << info->displayMessage(); + info->device()->setSetupStatus(Device::DeviceSetupStatusFailed, info->status(), info->displayMessage()); // TODO: recover old params.?? return; } @@ -461,7 +462,7 @@ DeviceSetupInfo *DeviceManagerImplementation::reconfigureDeviceInternal(Device * storeConfiguredDevices(); postSetupDevice(info->device()); - info->device()->setupCompleted(); + info->device()->setSetupStatus(Device::DeviceSetupStatusComplete, Device::DeviceErrorNoError); emit deviceChanged(info->device()); @@ -640,7 +641,7 @@ DevicePairingInfo *DeviceManagerImplementation::confirmPairing(const PairingTran } } else { device = m_configuredDevices.value(internalInfo->deviceId()); - device->setSetupComplete(false); + device->setSetupStatus(Device::DeviceSetupStatusInProgress, Device::DeviceErrorNoError); qCDebug(dcDeviceManager()) << "Reconfiguring device" << device; } @@ -654,10 +655,17 @@ DevicePairingInfo *DeviceManagerImplementation::confirmPairing(const PairingTran externalInfo->finish(info->status(), info->displayMessage()); if (info->status() != Device::DeviceErrorNoError) { - qCWarning(dcDeviceManager()) << "Failed to set up device" << info->device()->name() << info->status() << info->displayMessage(); - info->device()->deleteLater(); + if (addNewDevice) { + qCWarning(dcDeviceManager()) << "Failed to set up device" << info->device()->name() + << "Not adding device to the system. Error:" + << info->status() << info->displayMessage(); + info->device()->deleteLater(); + } if (!addNewDevice) { + qCWarning(dcDeviceManager()) << "Failed to reconfigure device" << info->device()->name() << + "Error:" << info->status() << info->displayMessage(); + info->device()->setSetupStatus(Device::DeviceSetupStatusFailed, info->status(), info->displayMessage()); // TODO: restore parameters? } @@ -665,7 +673,7 @@ DevicePairingInfo *DeviceManagerImplementation::confirmPairing(const PairingTran } qCDebug(dcDeviceManager()) << "Setup complete for device" << info->device(); - info->device()->setupCompleted(); + info->device()->setSetupStatus(Device::DeviceSetupStatusComplete, Device::DeviceErrorNoError); if (addNewDevice) { qCDebug(dcDeviceManager()) << "Device added:" << info->device(); @@ -749,7 +757,7 @@ DeviceSetupInfo* DeviceManagerImplementation::addConfiguredDeviceInternal(const return; } - info->device()->setupCompleted(); + info->device()->setSetupStatus(Device::DeviceSetupStatusComplete, Device::DeviceErrorNoError); qCDebug(dcDeviceManager) << "Device setup complete."; m_configuredDevices.insert(info->device()->id(), info->device()); @@ -1062,7 +1070,25 @@ void DeviceManagerImplementation::loadPlugins() if (!fi.exists()) continue; + // Check plugin API version compatibility + QLibrary lib(fi.absoluteFilePath()); + QFunctionPointer versionFunc = lib.resolve("libnymea_api_version"); + if (!versionFunc) { + qCWarning(dcDeviceManager()).nospace() << "Unable to resolve version in plugin " << entry << ". Not loading plugin."; + lib.unload(); + continue; + } + QString version = reinterpret_cast(versionFunc)(); + lib.unload(); + QStringList parts = version.split('.'); + QStringList coreParts = QString(LIBNYMEA_API_VERSION).split('.'); + if (parts.length() != 3 || parts.at(0).toInt() != coreParts.at(0).toInt() || parts.at(1).toInt() > coreParts.at(1).toInt()) { + qCWarning(dcDeviceManager()).nospace() << "Libnymea API mismatch for " << entry << ". Core API: " << LIBNYMEA_API_VERSION << ", Plugin API: " << version; + continue; + } + + // Version is ok. Now load the plugin QPluginLoader loader; loader.setFileName(fi.absoluteFilePath()); loader.setLoadHints(QLibrary::ResolveAllSymbolsHint); @@ -1073,29 +1099,6 @@ void DeviceManagerImplementation::loadPlugins() continue; } - // Check plugin API version compatibility - QLibrary lib(fi.absoluteFilePath()); - QFunctionPointer versionFunc = lib.resolve("libnymea_api_version"); - if (!versionFunc) { - qCWarning(dcDeviceManager()).nospace() << "Unable to resolve version in plugin " << entry << ". Not loading plugin."; - loader.unload(); - lib.unload(); - continue; - - } - QString version = reinterpret_cast(versionFunc)(); -// QString *version = reinterpret_cast(lib.resolve("libnymea_api_version")); -// if (!version) { -// } - lib.unload(); - QStringList parts = version.split('.'); - QStringList coreParts = QString(LIBNYMEA_API_VERSION).split('.'); - if (parts.length() != 3 || parts.at(0).toInt() != coreParts.at(0).toInt() || parts.at(1).toInt() > coreParts.at(1).toInt()) { - qCWarning(dcDeviceManager()).nospace() << "Libnymea API mismatch for " << entry << ". Core API: " << LIBNYMEA_API_VERSION << ", Plugin API: " << version; - loader.unload(); - continue; - } - PluginMetadata metaData(loader.metaData().value("MetaData").toObject()); if (!metaData.isValid()) { foreach (const QString &error, metaData.validationErrors()) { @@ -1357,6 +1360,7 @@ void DeviceManagerImplementation::loadConfiguredDevices() } Q_ASSERT(device != nullptr); + device->setSetupStatus(Device::DeviceSetupStatusInProgress, Device::DeviceErrorNoError); DeviceSetupInfo *info = setupDevice(device); // Set receiving object to "device" because at startup we load it in any case, knowing that it worked at // some point. However, it'll be marked as non-working until the setup succeeds so the user might delete @@ -1365,11 +1369,14 @@ void DeviceManagerImplementation::loadConfiguredDevices() if (info->status() != Device::DeviceErrorNoError) { qCWarning(dcDeviceManager()) << "Error setting up device" << info->device()->name() << info->device()->id().toString() << info->status() << info->displayMessage(); + info->device()->setSetupStatus(Device::DeviceSetupStatusFailed, info->status(), info->displayMessage()); + emit deviceChanged(info->device()); return; } qCDebug(dcDeviceManager()) << "Setup complete for device" << info->device(); - info->device()->setupCompleted(); + info->device()->setSetupStatus(Device::DeviceSetupStatusComplete, Device::DeviceErrorNoError); + emit deviceChanged(info->device()); postSetupDevice(info->device()); }); } @@ -1475,7 +1482,7 @@ void DeviceManagerImplementation::onAutoDevicesAppeared(const DeviceDescriptors return; } - info->device()->setupCompleted(); + info->device()->setSetupStatus(Device::DeviceSetupStatusComplete, Device::DeviceErrorNoError); m_configuredDevices.insert(info->device()->id(), info->device()); storeConfiguredDevices(); diff --git a/libnymea-core/jsonrpc/devicehandler.cpp b/libnymea-core/jsonrpc/devicehandler.cpp index b8488f66..d322e917 100644 --- a/libnymea-core/jsonrpc/devicehandler.cpp +++ b/libnymea-core/jsonrpc/devicehandler.cpp @@ -88,6 +88,7 @@ DeviceHandler::DeviceHandler(QObject *parent) : { // Enums registerEnum(); + registerEnum(); registerEnum(); registerEnum(); registerEnum(); @@ -374,7 +375,7 @@ DeviceHandler::DeviceHandler(QObject *parent) : registerNotification("DeviceAdded", description, params); params.clear(); returns.clear(); - description = "Emitted whenever the params or name of a Device are changed (by EditDevice or ReconfigureDevice)."; + description = "Emitted whenever the params, name or setupStatus of a Device changes."; params.insert("device", objectRef()); registerNotification("DeviceChanged", description, params); @@ -627,7 +628,7 @@ JsonReply *DeviceHandler::ConfirmPairing(const QVariantMap ¶ms, const JsonCo return jsonReply; } -JsonReply* DeviceHandler::GetConfiguredDevices(const QVariantMap ¶ms) const +JsonReply* DeviceHandler::GetConfiguredDevices(const QVariantMap ¶ms, const JsonContext &context) const { QVariantMap returns; QVariantList configuredDeviceList; @@ -641,7 +642,12 @@ JsonReply* DeviceHandler::GetConfiguredDevices(const QVariantMap ¶ms) const } } else { foreach (Device *device, NymeaCore::instance()->deviceManager()->configuredDevices()) { - configuredDeviceList.append(pack(device)); + QVariantMap packedDevice = pack(device).toMap(); + QString translatedSetupStatus = NymeaCore::instance()->deviceManager()->translate(device->pluginId(), device->setupDisplayMessage(), context.locale()); + if (!translatedSetupStatus.isEmpty()) { + packedDevice["setupDisplayMessage"] = translatedSetupStatus; + } + configuredDeviceList.append(packedDevice); } } returns.insert("devices", configuredDeviceList); @@ -977,6 +983,25 @@ void DeviceHandler::deviceSettingChangedNotification(const DeviceId deviceId, co emit DeviceSettingChanged(params); } +QVariantMap DeviceHandler::translateNotification(const QString ¬ification, const QVariantMap ¶ms, const QLocale &locale) +{ + if (notification == "DeviceChanged") { + QVariantMap deviceMap = params.value("device").toMap(); + DeviceId deviceId = params.value("device").toMap().value("id").toUuid(); + Device *device = NymeaCore::instance()->deviceManager()->findConfiguredDevice(deviceId); + QString setupDisplayMessage = params.value("device").toMap().value("setupDisplayMessage").toString(); + QString translatedSetupDisplayMessage = NymeaCore::instance()->deviceManager()->translate(device->pluginId(), setupDisplayMessage, locale); + if (!translatedSetupDisplayMessage.isEmpty()) { + deviceMap["setupDisplayMessage"] = translatedSetupDisplayMessage; + } + QVariantMap translatedParams = params; + translatedParams["device"] = deviceMap; + return translatedParams; + } + + return params; +} + QVariantMap DeviceHandler::statusToReply(Device::DeviceError status) const { QVariantMap returns; diff --git a/libnymea-core/jsonrpc/devicehandler.h b/libnymea-core/jsonrpc/devicehandler.h index b2aaa2d5..be7b0c5c 100644 --- a/libnymea-core/jsonrpc/devicehandler.h +++ b/libnymea-core/jsonrpc/devicehandler.h @@ -43,6 +43,7 @@ public: explicit DeviceHandler(QObject *parent = nullptr); QString name() const override; + QVariantMap translateNotification(const QString ¬ification, const QVariantMap ¶ms, const QLocale &locale) override; Q_INVOKABLE JsonReply *GetSupportedVendors(const QVariantMap ¶ms, const JsonContext &context) const; Q_INVOKABLE JsonReply *GetSupportedDevices(const QVariantMap ¶ms, const JsonContext &context) const; @@ -54,7 +55,7 @@ public: Q_INVOKABLE JsonReply *AddConfiguredDevice(const QVariantMap ¶ms, const JsonContext &context); Q_INVOKABLE JsonReply *PairDevice(const QVariantMap ¶ms, const JsonContext &context); Q_INVOKABLE JsonReply *ConfirmPairing(const QVariantMap ¶ms, const JsonContext &context); - Q_INVOKABLE JsonReply *GetConfiguredDevices(const QVariantMap ¶ms) const; + Q_INVOKABLE JsonReply *GetConfiguredDevices(const QVariantMap ¶ms, const JsonContext &context) const; Q_INVOKABLE JsonReply *ReconfigureDevice(const QVariantMap ¶ms, const JsonContext &context); Q_INVOKABLE JsonReply *EditDevice(const QVariantMap ¶ms); Q_INVOKABLE JsonReply *RemoveConfiguredDevice(const QVariantMap ¶ms); diff --git a/libnymea-core/jsonrpc/jsonrpcserverimplementation.cpp b/libnymea-core/jsonrpc/jsonrpcserverimplementation.cpp index 3a260acb..e5c452e5 100644 --- a/libnymea-core/jsonrpc/jsonrpcserverimplementation.cpp +++ b/libnymea-core/jsonrpc/jsonrpcserverimplementation.cpp @@ -738,26 +738,11 @@ void JsonRPCServerImplementation::sendNotification(const QVariantMap ¶ms) JsonHandler *handler = qobject_cast(sender()); QMetaMethod method = handler->metaObject()->method(senderSignalIndex()); - QList clientsToBeNotified; - foreach (const QUuid &clientId, m_clientNotifications.keys()) { - if (m_clientNotifications.value(clientId).contains(handler->name())) { - clientsToBeNotified.append(clientId); - } - } - if (clientsToBeNotified.isEmpty()) { - return; - } - QVariantMap notification; notification.insert("id", m_notificationId++); notification.insert("notification", handler->name() + "." + method.name()); - notification.insert("params", params); - - JsonValidator validator; - Q_ASSERT_X(validator.validateNotificationParams(params, handler->name() + '.' + method.name(), m_api).success(), - validator.result().where().toUtf8(), - validator.result().errorString().toUtf8() + "\nGot:" + QJsonDocument::fromVariant(params).toJson(QJsonDocument::Indented)); + // Add deprecation warning if necessary if (m_api.value("notifications").toMap().value(handler->name() + '.' + method.name()).toMap().contains("deprecated")) { QString deprecationMessage = m_api.value("notifications").toMap().value(handler->name() + '.' + method.name()).toMap().value("deprecated").toString(); qCWarning(dcJsonRpc()) << "Client uses deprecated API. Please update client implementation!"; @@ -765,11 +750,28 @@ void JsonRPCServerImplementation::sendNotification(const QVariantMap ¶ms) notification.insert("deprecationWarning", deprecationMessage); } - QByteArray data = QJsonDocument::fromVariant(notification).toJson(QJsonDocument::Compact); - qCDebug(dcJsonRpcTraffic()) << "Notification content:" << data; + foreach (const QUuid &clientId, m_clientNotifications.keys()) { + + // Check if this client wants to be notified + if (!m_clientNotifications.value(clientId).contains(handler->name())) { + continue; + } + + QLocale locale = m_clientLocales.value(clientId); + QVariantMap translatedParams = handler->translateNotification(method.name(), params, locale); + + JsonValidator validator; + Q_ASSERT_X(validator.validateNotificationParams(translatedParams, handler->name() + '.' + method.name(), m_api).success(), + validator.result().where().toUtf8(), + validator.result().errorString().toUtf8() + "\nGot:" + QJsonDocument::fromVariant(translatedParams).toJson(QJsonDocument::Indented)); + + notification.insert("params", translatedParams); + + QByteArray data = QJsonDocument::fromVariant(notification).toJson(QJsonDocument::Compact); + + qCDebug(dcJsonRpc()) << "Sending notification" << handler->name() + "." + method.name() << "to client" << clientId; + qCDebug(dcJsonRpcTraffic()) << "Notification content:" << data; - foreach (const QUuid &clientId, clientsToBeNotified) { - qCDebug(dcJsonRpc()) << "Sending notification:" << handler->name() + "." + method.name(); m_clientTransports.value(clientId)->sendData(clientId, data); } } diff --git a/libnymea-core/jsonrpc/jsonvalidator.cpp b/libnymea-core/jsonrpc/jsonvalidator.cpp index f62bc036..7a2b2409 100644 --- a/libnymea-core/jsonrpc/jsonvalidator.cpp +++ b/libnymea-core/jsonrpc/jsonvalidator.cpp @@ -122,7 +122,7 @@ JsonValidator::Result JsonValidator::validateMap(const QVariantMap &map, const Q continue; } QString trimmedKey = key; - trimmedKey.remove(QRegExp("^(o:|r:|d:)")); + trimmedKey.remove(QRegExp("^(o:|r:|d:)*")); if (!map.contains(trimmedKey)) { return Result(false, "Missing required key: " + key, key); } diff --git a/libnymea/devices/device.cpp b/libnymea/devices/device.cpp index e60b848e..c58e7ceb 100644 --- a/libnymea/devices/device.cpp +++ b/libnymea/devices/device.cpp @@ -138,11 +138,6 @@ Device::Device(DevicePlugin *plugin, const DeviceClass &deviceClass, QObject *pa } -void Device::setupCompleted() -{ - m_setupComplete = true; -} - /*! Returns the id of this Device. */ DeviceId Device::id() const { @@ -366,10 +361,16 @@ void Device::setParentId(const DeviceId &parentId) m_parentId = parentId; } -/*! Returns true, if setup of this Device is already completed. */ +/*! Returns true, if setup of this Device is already completed. This method is deprecated, use setupStatus() instead. */ bool Device::setupComplete() const { - return m_setupComplete; + return m_setupStatus == DeviceSetupStatusComplete; +} + +/*! Returns the setup error display message, if any. */ +QString Device::setupDisplayMessage() const +{ + return m_setupDisplayMessage; } /*! Returns true if this device has been auto-created (not created by the user) */ @@ -378,9 +379,23 @@ bool Device::autoCreated() const return m_autoCreated; } -void Device::setSetupComplete(bool complete) +/* Returns the current device setup status. */ +Device::DeviceSetupStatus Device::setupStatus() const { - m_setupComplete = complete; + return m_setupStatus; +} + +Device::DeviceError Device::setupError() const +{ + return m_setupError; +} + +void Device::setSetupStatus(Device::DeviceSetupStatus status, Device::DeviceError setupError, const QString &displayMessage) +{ + m_setupStatus = status; + m_setupError = setupError; + m_setupDisplayMessage = displayMessage; + emit setupStatusChanged(); } Devices::Devices(const QList &other) diff --git a/libnymea/devices/device.h b/libnymea/devices/device.h index 465c5fd9..70768ce6 100644 --- a/libnymea/devices/device.h +++ b/libnymea/devices/device.h @@ -50,12 +50,15 @@ class LIBNYMEA_EXPORT Device: public QObject Q_OBJECT Q_PROPERTY(QUuid id READ id) Q_PROPERTY(QUuid deviceClassId READ deviceClassId) - Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged) - Q_PROPERTY(ParamList params READ params WRITE setParams) - Q_PROPERTY(ParamList settings READ settings WRITE setSettings) - Q_PROPERTY(States states READ states WRITE setStates) - Q_PROPERTY(bool setupComplete READ setupComplete WRITE setSetupComplete) - Q_PROPERTY(QUuid parentId READ parentId WRITE setParentId USER true) + Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged USER true) + Q_PROPERTY(ParamList params READ params) + Q_PROPERTY(ParamList settings READ settings WRITE setSettings USER true) + Q_PROPERTY(States states READ states) + Q_PROPERTY(bool setupComplete READ setupComplete NOTIFY setupStatusChanged REVISION 1) + Q_PROPERTY(DeviceSetupStatus setupStatus READ setupStatus NOTIFY setupStatusChanged) + Q_PROPERTY(QString setupDisplayMessage READ setupDisplayMessage NOTIFY setupStatusChanged USER true) + Q_PROPERTY(DeviceError setupError READ setupError NOTIFY setupStatusChanged) + Q_PROPERTY(QUuid parentId READ parentId USER true) public: enum DeviceError { @@ -89,6 +92,14 @@ public: }; Q_ENUM(DeviceError) + enum DeviceSetupStatus { + DeviceSetupStatusNone, + DeviceSetupStatusInProgress, + DeviceSetupStatusComplete, + DeviceSetupStatusFailed, + }; + Q_ENUM(DeviceSetupStatus) + DeviceId id() const; DeviceClassId deviceClassId() const; PluginId pluginId() const; @@ -125,13 +136,19 @@ public: DeviceId parentId() const; void setParentId(const DeviceId &parentId); + // Deprecated bool setupComplete() const; bool autoCreated() const; + DeviceSetupStatus setupStatus() const; + DeviceError setupError() const; + QString setupDisplayMessage() const; + signals: void stateValueChanged(const StateTypeId &stateTypeId, const QVariant &value); void settingChanged(const ParamTypeId ¶mTypeId, const QVariant &value); void nameChanged(); + void setupStatusChanged(); private: friend class DeviceManager; @@ -139,8 +156,7 @@ private: Device(DevicePlugin *plugin, const DeviceClass &deviceClass, const DeviceId &id, QObject *parent = nullptr); Device(DevicePlugin *plugin, const DeviceClass &deviceClass, QObject *parent = nullptr); - void setupCompleted(); - void setSetupComplete(bool complete); + void setSetupStatus(Device::DeviceSetupStatus status, Device::DeviceError setupError, const QString &displayMessage = QString()); private: DeviceClass m_deviceClass; @@ -152,8 +168,11 @@ private: ParamList m_params; ParamList m_settings; States m_states; - bool m_setupComplete = false; bool m_autoCreated = false; + + DeviceSetupStatus m_setupStatus = DeviceSetupStatusNone; + DeviceError m_setupError = DeviceErrorNoError; + QString m_setupDisplayMessage; }; QDebug operator<<(QDebug dbg, Device *device); diff --git a/libnymea/jsonrpc/jsonhandler.cpp b/libnymea/jsonrpc/jsonhandler.cpp index 7781929c..ab292a9b 100644 --- a/libnymea/jsonrpc/jsonhandler.cpp +++ b/libnymea/jsonrpc/jsonhandler.cpp @@ -41,6 +41,13 @@ JsonHandler::JsonHandler(QObject *parent) : QObject(parent) registerEnum(); } +QVariantMap JsonHandler::translateNotification(const QString ¬ification, const QVariantMap ¶ms, const QLocale &locale) +{ + Q_UNUSED(notification) + Q_UNUSED(locale) + return params; +} + QVariantMap JsonHandler::jsonEnums() const { return m_enums; @@ -186,6 +193,9 @@ void JsonHandler::registerObject(const QMetaObject &metaObject) if (!metaProperty.isWritable()) { name.prepend("r:"); } + if (metaProperty.revision() == 1) { + name.prepend("d:"); + } QVariant typeName; if (metaProperty.type() == QVariant::UserType) { if (metaProperty.typeName() == QStringLiteral("QVariant::Type")) { diff --git a/libnymea/jsonrpc/jsonhandler.h b/libnymea/jsonrpc/jsonhandler.h index ec419221..980b4471 100644 --- a/libnymea/jsonrpc/jsonhandler.h +++ b/libnymea/jsonrpc/jsonhandler.h @@ -65,6 +65,8 @@ public: virtual QString name() const = 0; + virtual QVariantMap translateNotification(const QString ¬ification, const QVariantMap ¶ms, const QLocale &locale); + QVariantMap jsonEnums() const; QVariantMap jsonFlags() const; QVariantMap jsonObjects() const; diff --git a/tests/auto/api.json b/tests/auto/api.json index 8c9c78ec..befe9b5e 100644 --- a/tests/auto/api.json +++ b/tests/auto/api.json @@ -76,6 +76,12 @@ "DeviceErrorUnsupportedFeature", "DeviceErrorTimeout" ], + "DeviceSetupStatus": [ + "DeviceSetupStatusNone", + "DeviceSetupStatusInProgress", + "DeviceSetupStatusComplete", + "DeviceSetupStatusFailed" + ], "InputType": [ "InputTypeNone", "InputTypeTextLine", @@ -1609,7 +1615,7 @@ } }, "Devices.DeviceChanged": { - "description": "Emitted whenever the params or name of a Device are changed (by EditDevice or ReconfigureDevice).", + "description": "Emitted whenever the params, name or setupStatus of a Device changes.", "params": { "device": "$ref:Device" } @@ -1904,14 +1910,17 @@ "$ref:CalendarItem" ], "Device": { - "name": "String", - "o:parentId": "Uuid", - "params": "$ref:ParamList", + "d:r:setupComplete": "Bool", + "o:name": "String", + "o:settings": "$ref:ParamList", "r:deviceClassId": "Uuid", "r:id": "Uuid", - "settings": "$ref:ParamList", - "setupComplete": "Bool", - "states": "$ref:States" + "r:o:parentId": "Uuid", + "r:o:setupDisplayMessage": "String", + "r:params": "$ref:ParamList", + "r:setupError": "$ref:DeviceError", + "r:setupStatus": "$ref:DeviceSetupStatus", + "r:states": "$ref:States" }, "DeviceClass": { "r:actionTypes": "$ref:ActionTypes", diff --git a/tests/auto/devices/testdevices.cpp b/tests/auto/devices/testdevices.cpp index c3751c60..8eb78857 100644 --- a/tests/auto/devices/testdevices.cpp +++ b/tests/auto/devices/testdevices.cpp @@ -136,6 +136,8 @@ private slots: void params(); + void asyncSetupEmitsSetupStatusUpdate(); + // Keep those at last as they will remove devices void removeDevice_data(); void removeDevice(); @@ -1971,6 +1973,55 @@ void TestDevices::params() QVERIFY(!event.param(ParamTypeId::createParamTypeId()).value().isValid()); } +void TestDevices::asyncSetupEmitsSetupStatusUpdate() +{ + QVariantMap configuredDevices = injectAndWait("Devices.GetConfiguredDevices").toMap(); + foreach (const QVariant &deviceVariant, configuredDevices.value("params").toMap().value("devices").toList()) { + QVariantMap device = deviceVariant.toMap(); + qCDebug(dcTests()) << "confdiguredd device" << device.value("setupStatus"); + } + + // Restart the core instance to check if settings are loaded at startup + restartServer(); + enableNotifications({"Devices"}); + + QSignalSpy notificationSpy(m_mockTcpServer, SIGNAL(outgoingData(QUuid,QByteArray))); + + configuredDevices = injectAndWait("Devices.GetConfiguredDevices").toMap(); + QList devicesWithSetupInProgress; + foreach (const QVariant &deviceVariant, configuredDevices.value("params").toMap().value("devices").toList()) { + QVariantMap device = deviceVariant.toMap(); + qCDebug(dcTests()) << "Configured device" << device.value("name").toString() << "with setup status" << device.value("setupStatus").toString(); + if (device.value("setupStatus").toString() == "DeviceSetupStatusInProgress") { + devicesWithSetupInProgress << device.value("id").toUuid(); + } + } + QVERIFY2(devicesWithSetupInProgress.count() > 0, "This test requires at least one device that is still being set up at this point."); + + QDateTime maxTime = QDateTime::currentDateTime().addSecs(10); + while (QDateTime::currentDateTime() < maxTime && devicesWithSetupInProgress.count() > 0) { + QList> notifications = notificationSpy; + while (notifications.count() > 0) { + QByteArray notificationData = notifications.takeFirst().at(1).toByteArray(); + QVariantMap notification = QJsonDocument::fromJson(notificationData).toVariant().toMap(); + if (notification.value("notification").toString() == "Devices.DeviceChanged") { + QString setupStatus = notification.value("params").toMap().value("device").toMap().value("setupStatus").toString(); + if (setupStatus == "DeviceSetupStatusComplete") { + qCDebug(dcTests()) << "Device setup completed for" << notification.value("params").toMap().value("device").toMap().value("name").toString(); + DeviceId deviceId = notification.value("params").toMap().value("device").toMap().value("id").toUuid(); + devicesWithSetupInProgress.removeAll(deviceId); + } + } + } + notificationSpy.clear(); + if (devicesWithSetupInProgress.count() > 0) { + notificationSpy.wait(); + } + } + + QVERIFY2(devicesWithSetupInProgress.isEmpty(), "Some devices did not finish the setup!"); +} + #include "testdevices.moc" QTEST_MAIN(TestDevices)