Add better device setup status details to api

This commit is contained in:
Michael Zanetti 2020-01-21 22:43:02 +01:00
parent 3015130c8f
commit 6e4a0bc84b
11 changed files with 223 additions and 82 deletions

View File

@ -436,7 +436,7 @@ DeviceSetupInfo *DeviceManagerImplementation::reconfigureDeviceInternal(Device *
plugin->deviceRemoved(device); plugin->deviceRemoved(device);
// mark setup as incomplete // mark setup as incomplete
device->setSetupComplete(false); device->setSetupStatus(Device::DeviceSetupStatusInProgress, Device::DeviceErrorNoError);
// set new params // set new params
foreach (const Param &param, params) { foreach (const Param &param, params) {
@ -454,6 +454,7 @@ DeviceSetupInfo *DeviceManagerImplementation::reconfigureDeviceInternal(Device *
if (info->status() != Device::DeviceErrorNoError) { if (info->status() != Device::DeviceErrorNoError) {
qCWarning(dcDeviceManager()) << "Device reconfiguration failed for" << info->device()->name() << info->device()->id().toString() << info->status() << info->displayMessage(); 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.?? // TODO: recover old params.??
return; return;
} }
@ -461,7 +462,7 @@ DeviceSetupInfo *DeviceManagerImplementation::reconfigureDeviceInternal(Device *
storeConfiguredDevices(); storeConfiguredDevices();
postSetupDevice(info->device()); postSetupDevice(info->device());
info->device()->setupCompleted(); info->device()->setSetupStatus(Device::DeviceSetupStatusComplete, Device::DeviceErrorNoError);
emit deviceChanged(info->device()); emit deviceChanged(info->device());
@ -640,7 +641,7 @@ DevicePairingInfo *DeviceManagerImplementation::confirmPairing(const PairingTran
} }
} else { } else {
device = m_configuredDevices.value(internalInfo->deviceId()); device = m_configuredDevices.value(internalInfo->deviceId());
device->setSetupComplete(false); device->setSetupStatus(Device::DeviceSetupStatusInProgress, Device::DeviceErrorNoError);
qCDebug(dcDeviceManager()) << "Reconfiguring device" << device; qCDebug(dcDeviceManager()) << "Reconfiguring device" << device;
} }
@ -654,10 +655,17 @@ DevicePairingInfo *DeviceManagerImplementation::confirmPairing(const PairingTran
externalInfo->finish(info->status(), info->displayMessage()); externalInfo->finish(info->status(), info->displayMessage());
if (info->status() != Device::DeviceErrorNoError) { if (info->status() != Device::DeviceErrorNoError) {
qCWarning(dcDeviceManager()) << "Failed to set up device" << info->device()->name() << info->status() << info->displayMessage(); if (addNewDevice) {
info->device()->deleteLater(); 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) { 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? // TODO: restore parameters?
} }
@ -665,7 +673,7 @@ DevicePairingInfo *DeviceManagerImplementation::confirmPairing(const PairingTran
} }
qCDebug(dcDeviceManager()) << "Setup complete for device" << info->device(); qCDebug(dcDeviceManager()) << "Setup complete for device" << info->device();
info->device()->setupCompleted(); info->device()->setSetupStatus(Device::DeviceSetupStatusComplete, Device::DeviceErrorNoError);
if (addNewDevice) { if (addNewDevice) {
qCDebug(dcDeviceManager()) << "Device added:" << info->device(); qCDebug(dcDeviceManager()) << "Device added:" << info->device();
@ -749,7 +757,7 @@ DeviceSetupInfo* DeviceManagerImplementation::addConfiguredDeviceInternal(const
return; return;
} }
info->device()->setupCompleted(); info->device()->setSetupStatus(Device::DeviceSetupStatusComplete, Device::DeviceErrorNoError);
qCDebug(dcDeviceManager) << "Device setup complete."; qCDebug(dcDeviceManager) << "Device setup complete.";
m_configuredDevices.insert(info->device()->id(), info->device()); m_configuredDevices.insert(info->device()->id(), info->device());
@ -1062,7 +1070,25 @@ void DeviceManagerImplementation::loadPlugins()
if (!fi.exists()) if (!fi.exists())
continue; 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<QString(*)()>(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; QPluginLoader loader;
loader.setFileName(fi.absoluteFilePath()); loader.setFileName(fi.absoluteFilePath());
loader.setLoadHints(QLibrary::ResolveAllSymbolsHint); loader.setLoadHints(QLibrary::ResolveAllSymbolsHint);
@ -1073,29 +1099,6 @@ void DeviceManagerImplementation::loadPlugins()
continue; 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<QString(*)()>(versionFunc)();
// QString *version = reinterpret_cast<QString*>(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()); PluginMetadata metaData(loader.metaData().value("MetaData").toObject());
if (!metaData.isValid()) { if (!metaData.isValid()) {
foreach (const QString &error, metaData.validationErrors()) { foreach (const QString &error, metaData.validationErrors()) {
@ -1357,6 +1360,7 @@ void DeviceManagerImplementation::loadConfiguredDevices()
} }
Q_ASSERT(device != nullptr); Q_ASSERT(device != nullptr);
device->setSetupStatus(Device::DeviceSetupStatusInProgress, Device::DeviceErrorNoError);
DeviceSetupInfo *info = setupDevice(device); DeviceSetupInfo *info = setupDevice(device);
// Set receiving object to "device" because at startup we load it in any case, knowing that it worked at // 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 // 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) { if (info->status() != Device::DeviceErrorNoError) {
qCWarning(dcDeviceManager()) << "Error setting up device" << info->device()->name() << info->device()->id().toString() << info->status() << info->displayMessage(); 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; return;
} }
qCDebug(dcDeviceManager()) << "Setup complete for device" << info->device(); 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()); postSetupDevice(info->device());
}); });
} }
@ -1475,7 +1482,7 @@ void DeviceManagerImplementation::onAutoDevicesAppeared(const DeviceDescriptors
return; return;
} }
info->device()->setupCompleted(); info->device()->setSetupStatus(Device::DeviceSetupStatusComplete, Device::DeviceErrorNoError);
m_configuredDevices.insert(info->device()->id(), info->device()); m_configuredDevices.insert(info->device()->id(), info->device());
storeConfiguredDevices(); storeConfiguredDevices();

View File

@ -88,6 +88,7 @@ DeviceHandler::DeviceHandler(QObject *parent) :
{ {
// Enums // Enums
registerEnum<Device::DeviceError>(); registerEnum<Device::DeviceError>();
registerEnum<Device::DeviceSetupStatus>();
registerEnum<DeviceClass::SetupMethod>(); registerEnum<DeviceClass::SetupMethod>();
registerEnum<DeviceClass::CreateMethod, DeviceClass::CreateMethods>(); registerEnum<DeviceClass::CreateMethod, DeviceClass::CreateMethods>();
registerEnum<Types::Unit>(); registerEnum<Types::Unit>();
@ -374,7 +375,7 @@ DeviceHandler::DeviceHandler(QObject *parent) :
registerNotification("DeviceAdded", description, params); registerNotification("DeviceAdded", description, params);
params.clear(); returns.clear(); 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<Device>()); params.insert("device", objectRef<Device>());
registerNotification("DeviceChanged", description, params); registerNotification("DeviceChanged", description, params);
@ -627,7 +628,7 @@ JsonReply *DeviceHandler::ConfirmPairing(const QVariantMap &params, const JsonCo
return jsonReply; return jsonReply;
} }
JsonReply* DeviceHandler::GetConfiguredDevices(const QVariantMap &params) const JsonReply* DeviceHandler::GetConfiguredDevices(const QVariantMap &params, const JsonContext &context) const
{ {
QVariantMap returns; QVariantMap returns;
QVariantList configuredDeviceList; QVariantList configuredDeviceList;
@ -641,7 +642,12 @@ JsonReply* DeviceHandler::GetConfiguredDevices(const QVariantMap &params) const
} }
} else { } else {
foreach (Device *device, NymeaCore::instance()->deviceManager()->configuredDevices()) { 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); returns.insert("devices", configuredDeviceList);
@ -977,6 +983,25 @@ void DeviceHandler::deviceSettingChangedNotification(const DeviceId deviceId, co
emit DeviceSettingChanged(params); emit DeviceSettingChanged(params);
} }
QVariantMap DeviceHandler::translateNotification(const QString &notification, const QVariantMap &params, 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 DeviceHandler::statusToReply(Device::DeviceError status) const
{ {
QVariantMap returns; QVariantMap returns;

View File

@ -43,6 +43,7 @@ public:
explicit DeviceHandler(QObject *parent = nullptr); explicit DeviceHandler(QObject *parent = nullptr);
QString name() const override; QString name() const override;
QVariantMap translateNotification(const QString &notification, const QVariantMap &params, const QLocale &locale) override;
Q_INVOKABLE JsonReply *GetSupportedVendors(const QVariantMap &params, const JsonContext &context) const; Q_INVOKABLE JsonReply *GetSupportedVendors(const QVariantMap &params, const JsonContext &context) const;
Q_INVOKABLE JsonReply *GetSupportedDevices(const QVariantMap &params, const JsonContext &context) const; Q_INVOKABLE JsonReply *GetSupportedDevices(const QVariantMap &params, const JsonContext &context) const;
@ -54,7 +55,7 @@ public:
Q_INVOKABLE JsonReply *AddConfiguredDevice(const QVariantMap &params, const JsonContext &context); Q_INVOKABLE JsonReply *AddConfiguredDevice(const QVariantMap &params, const JsonContext &context);
Q_INVOKABLE JsonReply *PairDevice(const QVariantMap &params, const JsonContext &context); Q_INVOKABLE JsonReply *PairDevice(const QVariantMap &params, const JsonContext &context);
Q_INVOKABLE JsonReply *ConfirmPairing(const QVariantMap &params, const JsonContext &context); Q_INVOKABLE JsonReply *ConfirmPairing(const QVariantMap &params, const JsonContext &context);
Q_INVOKABLE JsonReply *GetConfiguredDevices(const QVariantMap &params) const; Q_INVOKABLE JsonReply *GetConfiguredDevices(const QVariantMap &params, const JsonContext &context) const;
Q_INVOKABLE JsonReply *ReconfigureDevice(const QVariantMap &params, const JsonContext &context); Q_INVOKABLE JsonReply *ReconfigureDevice(const QVariantMap &params, const JsonContext &context);
Q_INVOKABLE JsonReply *EditDevice(const QVariantMap &params); Q_INVOKABLE JsonReply *EditDevice(const QVariantMap &params);
Q_INVOKABLE JsonReply *RemoveConfiguredDevice(const QVariantMap &params); Q_INVOKABLE JsonReply *RemoveConfiguredDevice(const QVariantMap &params);

View File

@ -738,26 +738,11 @@ void JsonRPCServerImplementation::sendNotification(const QVariantMap &params)
JsonHandler *handler = qobject_cast<JsonHandler *>(sender()); JsonHandler *handler = qobject_cast<JsonHandler *>(sender());
QMetaMethod method = handler->metaObject()->method(senderSignalIndex()); QMetaMethod method = handler->metaObject()->method(senderSignalIndex());
QList<QUuid> 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; QVariantMap notification;
notification.insert("id", m_notificationId++); notification.insert("id", m_notificationId++);
notification.insert("notification", handler->name() + "." + method.name()); 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")) { 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(); 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!"; qCWarning(dcJsonRpc()) << "Client uses deprecated API. Please update client implementation!";
@ -765,11 +750,28 @@ void JsonRPCServerImplementation::sendNotification(const QVariantMap &params)
notification.insert("deprecationWarning", deprecationMessage); notification.insert("deprecationWarning", deprecationMessage);
} }
QByteArray data = QJsonDocument::fromVariant(notification).toJson(QJsonDocument::Compact); foreach (const QUuid &clientId, m_clientNotifications.keys()) {
qCDebug(dcJsonRpcTraffic()) << "Notification content:" << data;
// 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); m_clientTransports.value(clientId)->sendData(clientId, data);
} }
} }

View File

@ -122,7 +122,7 @@ JsonValidator::Result JsonValidator::validateMap(const QVariantMap &map, const Q
continue; continue;
} }
QString trimmedKey = key; QString trimmedKey = key;
trimmedKey.remove(QRegExp("^(o:|r:|d:)")); trimmedKey.remove(QRegExp("^(o:|r:|d:)*"));
if (!map.contains(trimmedKey)) { if (!map.contains(trimmedKey)) {
return Result(false, "Missing required key: " + key, key); return Result(false, "Missing required key: " + key, key);
} }

View File

@ -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. */ /*! Returns the id of this Device. */
DeviceId Device::id() const DeviceId Device::id() const
{ {
@ -366,10 +361,16 @@ void Device::setParentId(const DeviceId &parentId)
m_parentId = 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 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) */ /*! 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; 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<Device*> &other) Devices::Devices(const QList<Device*> &other)

View File

@ -50,12 +50,15 @@ class LIBNYMEA_EXPORT Device: public QObject
Q_OBJECT Q_OBJECT
Q_PROPERTY(QUuid id READ id) Q_PROPERTY(QUuid id READ id)
Q_PROPERTY(QUuid deviceClassId READ deviceClassId) Q_PROPERTY(QUuid deviceClassId READ deviceClassId)
Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged) Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged USER true)
Q_PROPERTY(ParamList params READ params WRITE setParams) Q_PROPERTY(ParamList params READ params)
Q_PROPERTY(ParamList settings READ settings WRITE setSettings) Q_PROPERTY(ParamList settings READ settings WRITE setSettings USER true)
Q_PROPERTY(States states READ states WRITE setStates) Q_PROPERTY(States states READ states)
Q_PROPERTY(bool setupComplete READ setupComplete WRITE setSetupComplete) Q_PROPERTY(bool setupComplete READ setupComplete NOTIFY setupStatusChanged REVISION 1)
Q_PROPERTY(QUuid parentId READ parentId WRITE setParentId USER true) 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: public:
enum DeviceError { enum DeviceError {
@ -89,6 +92,14 @@ public:
}; };
Q_ENUM(DeviceError) Q_ENUM(DeviceError)
enum DeviceSetupStatus {
DeviceSetupStatusNone,
DeviceSetupStatusInProgress,
DeviceSetupStatusComplete,
DeviceSetupStatusFailed,
};
Q_ENUM(DeviceSetupStatus)
DeviceId id() const; DeviceId id() const;
DeviceClassId deviceClassId() const; DeviceClassId deviceClassId() const;
PluginId pluginId() const; PluginId pluginId() const;
@ -125,13 +136,19 @@ public:
DeviceId parentId() const; DeviceId parentId() const;
void setParentId(const DeviceId &parentId); void setParentId(const DeviceId &parentId);
// Deprecated
bool setupComplete() const; bool setupComplete() const;
bool autoCreated() const; bool autoCreated() const;
DeviceSetupStatus setupStatus() const;
DeviceError setupError() const;
QString setupDisplayMessage() const;
signals: signals:
void stateValueChanged(const StateTypeId &stateTypeId, const QVariant &value); void stateValueChanged(const StateTypeId &stateTypeId, const QVariant &value);
void settingChanged(const ParamTypeId &paramTypeId, const QVariant &value); void settingChanged(const ParamTypeId &paramTypeId, const QVariant &value);
void nameChanged(); void nameChanged();
void setupStatusChanged();
private: private:
friend class DeviceManager; 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, const DeviceId &id, QObject *parent = nullptr);
Device(DevicePlugin *plugin, const DeviceClass &deviceClass, QObject *parent = nullptr); Device(DevicePlugin *plugin, const DeviceClass &deviceClass, QObject *parent = nullptr);
void setupCompleted(); void setSetupStatus(Device::DeviceSetupStatus status, Device::DeviceError setupError, const QString &displayMessage = QString());
void setSetupComplete(bool complete);
private: private:
DeviceClass m_deviceClass; DeviceClass m_deviceClass;
@ -152,8 +168,11 @@ private:
ParamList m_params; ParamList m_params;
ParamList m_settings; ParamList m_settings;
States m_states; States m_states;
bool m_setupComplete = false;
bool m_autoCreated = false; bool m_autoCreated = false;
DeviceSetupStatus m_setupStatus = DeviceSetupStatusNone;
DeviceError m_setupError = DeviceErrorNoError;
QString m_setupDisplayMessage;
}; };
QDebug operator<<(QDebug dbg, Device *device); QDebug operator<<(QDebug dbg, Device *device);

View File

@ -41,6 +41,13 @@ JsonHandler::JsonHandler(QObject *parent) : QObject(parent)
registerEnum<BasicType>(); registerEnum<BasicType>();
} }
QVariantMap JsonHandler::translateNotification(const QString &notification, const QVariantMap &params, const QLocale &locale)
{
Q_UNUSED(notification)
Q_UNUSED(locale)
return params;
}
QVariantMap JsonHandler::jsonEnums() const QVariantMap JsonHandler::jsonEnums() const
{ {
return m_enums; return m_enums;
@ -186,6 +193,9 @@ void JsonHandler::registerObject(const QMetaObject &metaObject)
if (!metaProperty.isWritable()) { if (!metaProperty.isWritable()) {
name.prepend("r:"); name.prepend("r:");
} }
if (metaProperty.revision() == 1) {
name.prepend("d:");
}
QVariant typeName; QVariant typeName;
if (metaProperty.type() == QVariant::UserType) { if (metaProperty.type() == QVariant::UserType) {
if (metaProperty.typeName() == QStringLiteral("QVariant::Type")) { if (metaProperty.typeName() == QStringLiteral("QVariant::Type")) {

View File

@ -65,6 +65,8 @@ public:
virtual QString name() const = 0; virtual QString name() const = 0;
virtual QVariantMap translateNotification(const QString &notification, const QVariantMap &params, const QLocale &locale);
QVariantMap jsonEnums() const; QVariantMap jsonEnums() const;
QVariantMap jsonFlags() const; QVariantMap jsonFlags() const;
QVariantMap jsonObjects() const; QVariantMap jsonObjects() const;

View File

@ -76,6 +76,12 @@
"DeviceErrorUnsupportedFeature", "DeviceErrorUnsupportedFeature",
"DeviceErrorTimeout" "DeviceErrorTimeout"
], ],
"DeviceSetupStatus": [
"DeviceSetupStatusNone",
"DeviceSetupStatusInProgress",
"DeviceSetupStatusComplete",
"DeviceSetupStatusFailed"
],
"InputType": [ "InputType": [
"InputTypeNone", "InputTypeNone",
"InputTypeTextLine", "InputTypeTextLine",
@ -1609,7 +1615,7 @@
} }
}, },
"Devices.DeviceChanged": { "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": { "params": {
"device": "$ref:Device" "device": "$ref:Device"
} }
@ -1904,14 +1910,17 @@
"$ref:CalendarItem" "$ref:CalendarItem"
], ],
"Device": { "Device": {
"name": "String", "d:r:setupComplete": "Bool",
"o:parentId": "Uuid", "o:name": "String",
"params": "$ref:ParamList", "o:settings": "$ref:ParamList",
"r:deviceClassId": "Uuid", "r:deviceClassId": "Uuid",
"r:id": "Uuid", "r:id": "Uuid",
"settings": "$ref:ParamList", "r:o:parentId": "Uuid",
"setupComplete": "Bool", "r:o:setupDisplayMessage": "String",
"states": "$ref:States" "r:params": "$ref:ParamList",
"r:setupError": "$ref:DeviceError",
"r:setupStatus": "$ref:DeviceSetupStatus",
"r:states": "$ref:States"
}, },
"DeviceClass": { "DeviceClass": {
"r:actionTypes": "$ref:ActionTypes", "r:actionTypes": "$ref:ActionTypes",

View File

@ -136,6 +136,8 @@ private slots:
void params(); void params();
void asyncSetupEmitsSetupStatusUpdate();
// Keep those at last as they will remove devices // Keep those at last as they will remove devices
void removeDevice_data(); void removeDevice_data();
void removeDevice(); void removeDevice();
@ -1971,6 +1973,55 @@ void TestDevices::params()
QVERIFY(!event.param(ParamTypeId::createParamTypeId()).value().isValid()); 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<QUuid> 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<QList<QVariant>> 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" #include "testdevices.moc"
QTEST_MAIN(TestDevices) QTEST_MAIN(TestDevices)