diff --git a/libnymea-core/jsonrpc/devicehandler.cpp b/libnymea-core/jsonrpc/devicehandler.cpp index 21b8ad28..53463c0a 100644 --- a/libnymea-core/jsonrpc/devicehandler.cpp +++ b/libnymea-core/jsonrpc/devicehandler.cpp @@ -211,6 +211,14 @@ DeviceHandler::DeviceHandler(QObject *parent) : returns.insert("deviceError", JsonTypes::deviceErrorRef()); setReturns("EditDevice", returns); + params.clear(); returns.clear(); + setDescription("SetDeviceSettings", "Change the settings of a device."); + params.insert("deviceId", JsonTypes::basicTypeToString(JsonTypes::Uuid)); + params.insert("settings", QVariantList() << JsonTypes::paramRef()); + setParams("SetDeviceSettings", params); + returns.insert("deviceError", JsonTypes::deviceErrorRef()); + setReturns("SetDeviceSettings", returns); + params.clear(); returns.clear(); setDescription("RemoveConfiguredDevice", "Remove a device from the system."); params.insert("deviceId", JsonTypes::basicTypeToString(JsonTypes::Uuid)); @@ -294,10 +302,17 @@ DeviceHandler::DeviceHandler(QObject *parent) : setParams("DeviceAdded", params); params.clear(); returns.clear(); - setDescription("DeviceChanged", "Emitted whenever the params or name of a Device changed (by EditDevice or ReconfigureDevice)."); + setDescription("DeviceChanged", "Emitted whenever the params or name of a Device are changed (by EditDevice or ReconfigureDevice)."); params.insert("device", JsonTypes::deviceRef()); setParams("DeviceChanged", params); + params.clear(); returns.clear(); + setDescription("DeviceSettingChanged", "Emitted whenever the setting of a Device is changed."); + params.insert("deviceId", JsonTypes::basicTypeToString(JsonTypes::Uuid)); + params.insert("paramTypeId", JsonTypes::basicTypeToString(JsonTypes::Uuid)); + params.insert("value", JsonTypes::basicTypeToString(JsonTypes::Variant)); + setParams("DeviceSettingChanged", params); + params.clear(); returns.clear(); setDescription("PluginConfigurationChanged", "Emitted whenever a plugin's configuration is changed."); params.insert("pluginId", JsonTypes::basicTypeToString(JsonTypes::Uuid)); @@ -309,6 +324,7 @@ DeviceHandler::DeviceHandler(QObject *parent) : connect(NymeaCore::instance(), &NymeaCore::deviceRemoved, this, &DeviceHandler::deviceRemovedNotification); connect(NymeaCore::instance(), &NymeaCore::deviceAdded, this, &DeviceHandler::deviceAddedNotification); connect(NymeaCore::instance(), &NymeaCore::deviceChanged, this, &DeviceHandler::deviceChangedNotification); + connect(NymeaCore::instance(), &NymeaCore::deviceSettingChanged, this, &DeviceHandler::deviceSettingChangedNotification); connect(NymeaCore::instance(), &NymeaCore::devicesDiscovered, this, &DeviceHandler::devicesDiscovered, Qt::QueuedConnection); connect(NymeaCore::instance(), &NymeaCore::deviceSetupFinished, this, &DeviceHandler::deviceSetupFinished); connect(NymeaCore::instance(), &NymeaCore::deviceReconfigurationFinished, this, &DeviceHandler::deviceReconfigurationFinished); @@ -565,6 +581,16 @@ JsonReply* DeviceHandler::RemoveConfiguredDevice(const QVariantMap ¶ms) return createReply(returns); } +JsonReply *DeviceHandler::SetDeviceSettings(const QVariantMap ¶ms) +{ + QVariantMap returns; + DeviceId deviceId = DeviceId(params.value("deviceId").toString()); + ParamList settings = JsonTypes::unpackParams(params.value("settings").toList()); + DeviceManager::DeviceError status = NymeaCore::instance()->deviceManager()->setDeviceSettings(deviceId, settings); + returns.insert("deviceError", JsonTypes::deviceErrorToString(status)); + return createReply(returns); +} + JsonReply* DeviceHandler::GetEventTypes(const QVariantMap ¶ms) const { QVariantMap returns; @@ -685,6 +711,15 @@ void DeviceHandler::deviceChangedNotification(Device *device) emit DeviceChanged(params); } +void DeviceHandler::deviceSettingChangedNotification(const DeviceId deviceId, const ParamTypeId ¶mTypeId, const QVariant &value) +{ + QVariantMap params; + params.insert("deviceId", deviceId); + params.insert("paramTypeId", paramTypeId.toString()); + params.insert("value", value); + emit DeviceSettingChanged(params); +} + void DeviceHandler::devicesDiscovered(const DeviceClassId &deviceClassId, const QList deviceDescriptors) { if (!m_discoverRequests.contains(deviceClassId)) { diff --git a/libnymea-core/jsonrpc/devicehandler.h b/libnymea-core/jsonrpc/devicehandler.h index 0e5c6df2..c06a7bc7 100644 --- a/libnymea-core/jsonrpc/devicehandler.h +++ b/libnymea-core/jsonrpc/devicehandler.h @@ -31,7 +31,7 @@ class DeviceHandler : public JsonHandler { Q_OBJECT public: - explicit DeviceHandler(QObject *parent = 0); + explicit DeviceHandler(QObject *parent = nullptr); QString name() const override; @@ -49,6 +49,7 @@ public: Q_INVOKABLE JsonReply *ReconfigureDevice(const QVariantMap ¶ms); Q_INVOKABLE JsonReply *EditDevice(const QVariantMap ¶ms); Q_INVOKABLE JsonReply *RemoveConfiguredDevice(const QVariantMap ¶ms); + Q_INVOKABLE JsonReply *SetDeviceSettings(const QVariantMap ¶ms); Q_INVOKABLE JsonReply *GetEventTypes(const QVariantMap ¶ms) const; Q_INVOKABLE JsonReply *GetActionTypes(const QVariantMap ¶ms) const; @@ -62,6 +63,7 @@ signals: void DeviceRemoved(const QVariantMap ¶ms); void DeviceAdded(const QVariantMap ¶ms); void DeviceChanged(const QVariantMap ¶ms); + void DeviceSettingChanged(const QVariantMap ¶ms); private slots: void pluginConfigChanged(const PluginId &id, const ParamList &config); @@ -74,6 +76,8 @@ private slots: void deviceChangedNotification(Device *device); + void deviceSettingChangedNotification(const DeviceId deviceId, const ParamTypeId ¶mTypeId, const QVariant &value); + void devicesDiscovered(const DeviceClassId &deviceClassId, const QList deviceDescriptors); void deviceSetupFinished(Device *device, DeviceManager::DeviceError status); diff --git a/libnymea-core/jsonrpc/jsontypes.cpp b/libnymea-core/jsonrpc/jsontypes.cpp index e7de6f8b..4c19bab5 100644 --- a/libnymea-core/jsonrpc/jsontypes.cpp +++ b/libnymea-core/jsonrpc/jsontypes.cpp @@ -280,6 +280,7 @@ void JsonTypes::init() s_deviceClass.insert("eventTypes", QVariantList() << eventTypeRef()); s_deviceClass.insert("actionTypes", QVariantList() << actionTypeRef()); s_deviceClass.insert("paramTypes", QVariantList() << paramTypeRef()); + s_deviceClass.insert("settingsTypes", QVariantList() << paramTypeRef()); s_deviceClass.insert("discoveryParamTypes", QVariantList() << paramTypeRef()); // Device @@ -287,6 +288,7 @@ void JsonTypes::init() s_device.insert("deviceClassId", basicTypeToString(Uuid)); s_device.insert("name", basicTypeToString(String)); s_device.insert("params", QVariantList() << paramRef()); + s_device.insert("settings", QVariantList() << paramRef()); QVariantMap stateValues; stateValues.insert("stateTypeId", basicTypeToString(Uuid)); stateValues.insert("value", basicTypeToString(Variant)); @@ -705,6 +707,15 @@ QVariantMap JsonTypes::packParam(const Param ¶m) return variantMap; } +QVariantList JsonTypes::packParams(const ParamList ¶mList) +{ + QVariantList ret; + foreach (const Param ¶m, paramList) { + ret << packParam(param); + } + return ret; +} + /*! Returns a variant map of the given \a paramDescriptor. */ QVariantMap JsonTypes::packParamDescriptor(const ParamDescriptor ¶mDescriptor) { @@ -797,11 +808,16 @@ QVariantMap JsonTypes::packDeviceClass(const DeviceClass &deviceClass, const QLo foreach (const ParamType ¶mType, deviceClass.paramTypes()) paramTypes.append(packParamType(paramType, deviceClass.pluginId(), locale)); + QVariantList settingsTypes; + foreach (const ParamType &settingsType, deviceClass.settingsTypes()) + settingsTypes.append(packParamType(settingsType, deviceClass.pluginId(), locale)); + QVariantList discoveryParamTypes; foreach (const ParamType ¶mType, deviceClass.discoveryParamTypes()) discoveryParamTypes.append(packParamType(paramType, deviceClass.pluginId(), locale)); variant.insert("paramTypes", paramTypes); + variant.insert("settingsTypes", settingsTypes); variant.insert("discoveryParamTypes", discoveryParamTypes); variant.insert("stateTypes", stateTypes); variant.insert("eventTypes", eventTypes); @@ -834,14 +850,12 @@ QVariantMap JsonTypes::packDevice(Device *device) variant.insert("id", device->id().toString()); variant.insert("deviceClassId", device->deviceClassId().toString()); variant.insert("name", device->name()); - QVariantList params; - foreach (const Param ¶m, device->params()) - params.append(packParam(param)); + variant.insert("params", packParams(device->params())); + variant.insert("settings", packParams(device->settings())); if (!device->parentId().isNull()) variant.insert("parentId", device->parentId().toString()); - variant.insert("params", params); variant.insert("states", packDeviceStates(device)); variant.insert("setupComplete", device->setupComplete()); return variant; diff --git a/libnymea-core/jsonrpc/jsontypes.h b/libnymea-core/jsonrpc/jsontypes.h index 5be24709..fcf31e93 100644 --- a/libnymea-core/jsonrpc/jsontypes.h +++ b/libnymea-core/jsonrpc/jsontypes.h @@ -194,6 +194,7 @@ public: static QVariantMap packStateDescriptor(const StateDescriptor &stateDescriptor); static QVariantMap packStateEvaluator(const StateEvaluator &stateEvaluator); static QVariantMap packParam(const Param ¶m); + static QVariantList packParams(const ParamList ¶mList); static QVariantMap packParamType(const ParamType ¶mType, const PluginId &pluginId, const QLocale &locale); static QVariantMap packParamDescriptor(const ParamDescriptor ¶mDescriptor); static QVariantMap packVendor(const Vendor &vendor, const QLocale &locale); diff --git a/libnymea-core/nymeacore.cpp b/libnymea-core/nymeacore.cpp index 1968a8b1..5ab176a1 100644 --- a/libnymea-core/nymeacore.cpp +++ b/libnymea-core/nymeacore.cpp @@ -197,6 +197,7 @@ void NymeaCore::init() { connect(m_deviceManager, &DeviceManager::deviceStateChanged, this, &NymeaCore::deviceStateChanged); connect(m_deviceManager, &DeviceManager::deviceAdded, this, &NymeaCore::deviceAdded); connect(m_deviceManager, &DeviceManager::deviceChanged, this, &NymeaCore::deviceChanged); + connect(m_deviceManager, &DeviceManager::deviceSettingChanged, this, &NymeaCore::deviceSettingChanged); connect(m_deviceManager, &DeviceManager::deviceRemoved, this, &NymeaCore::deviceRemoved); connect(m_deviceManager, &DeviceManager::deviceDisappeared, this, &NymeaCore::onDeviceDisappeared); connect(m_deviceManager, &DeviceManager::actionExecutionFinished, this, &NymeaCore::actionExecutionFinished); diff --git a/libnymea-core/nymeacore.h b/libnymea-core/nymeacore.h index 4c1be9ec..f12fcc7d 100644 --- a/libnymea-core/nymeacore.h +++ b/libnymea-core/nymeacore.h @@ -103,6 +103,7 @@ signals: void deviceRemoved(const DeviceId &deviceId); void deviceAdded(Device *device); void deviceChanged(Device *device); + void deviceSettingChanged(const DeviceId deviceId, const ParamTypeId &settingParamTypeId, const QVariant &value); void actionExecuted(const ActionId &id, DeviceManager::DeviceError status); void devicesDiscovered(const DeviceClassId &deviceClassId, const QList deviceDescriptors); diff --git a/libnymea/devicemanager.cpp b/libnymea/devicemanager.cpp index 5aa20b49..3f973d61 100644 --- a/libnymea/devicemanager.cpp +++ b/libnymea/devicemanager.cpp @@ -590,6 +590,25 @@ DeviceManager::DeviceError DeviceManager::editDevice(const DeviceId &deviceId, c return DeviceErrorNoError; } +DeviceManager::DeviceError DeviceManager::setDeviceSettings(const DeviceId &deviceId, const ParamList &settings) +{ + Device *device = findConfiguredDevice(deviceId); + if (!device) { + qCWarning(dcDeviceManager()) << "Cannot set device settings. Device" << deviceId.toString() << "not found"; + return DeviceErrorDeviceNotFound; + } + ParamList effectiveSettings = settings; + DeviceManager::DeviceError status = verifyParams(findDeviceClass(device->deviceClassId()).settingsTypes(), effectiveSettings); + if (status != DeviceManager::DeviceErrorNoError) { + qCWarning(dcDeviceManager()) << "Error setting device settings for device" << device->name() << device->id().toString(); + return status; + } + foreach (const Param &setting, settings) { + device->setSettingValue(setting.paramTypeId(), setting.value()); + } + return DeviceErrorNoError; +} + /*! Initiates a pairing with a \l{DeviceClass}{Device} with the given \a pairingTransactionId, \a deviceClassId, \a name and \a params. * Returns \l{DeviceManager::DeviceError}{DeviceError} to inform about the result. */ DeviceManager::DeviceError DeviceManager::pairDevice(const PairingTransactionId &pairingTransactionId, const DeviceClassId &deviceClassId, const QString &name, const ParamList ¶ms) @@ -701,7 +720,6 @@ DeviceManager::DeviceError DeviceManager::confirmPairing(const PairingTransactio * Returns \l{DeviceError} to inform about the result. */ DeviceManager::DeviceError DeviceManager::addConfiguredDeviceInternal(const DeviceClassId &deviceClassId, const QString &name, const ParamList ¶ms, const DeviceId id) { - ParamList effectiveParams = params; DeviceClass deviceClass = findDeviceClass(deviceClassId); if (deviceClass.id().isNull()) { return DeviceErrorDeviceClassNotFound; @@ -711,11 +729,6 @@ DeviceManager::DeviceError DeviceManager::addConfiguredDeviceInternal(const Devi return DeviceErrorCreationMethodNotSupported; } - DeviceError result = verifyParams(deviceClass.paramTypes(), effectiveParams); - if (result != DeviceErrorNoError) { - return result; - } - foreach(Device *device, m_configuredDevices) { if (device->id() == id) { return DeviceErrorDuplicateUuid; @@ -727,6 +740,12 @@ DeviceManager::DeviceError DeviceManager::addConfiguredDeviceInternal(const Devi return DeviceErrorPluginNotFound; } + ParamList effectiveParams = params; + DeviceError paramsResult = verifyParams(deviceClass.paramTypes(), effectiveParams); + if (paramsResult != DeviceErrorNoError) { + return paramsResult; + } + Device *device = new Device(plugin->pluginId(), id, deviceClassId, this); if (name.isEmpty()) { device->setName(deviceClass.name()); @@ -735,6 +754,11 @@ DeviceManager::DeviceError DeviceManager::addConfiguredDeviceInternal(const Devi } device->setParams(effectiveParams); + ParamList settings; + verifyParams(deviceClass.settingsTypes(), settings); + qCDebug(dcDeviceManager()) << "Adding device settings" << settings; + device->setSettings(settings); + DeviceSetupStatus status = setupDevice(device); switch (status) { case DeviceSetupStatusFailure: @@ -1191,9 +1215,37 @@ void DeviceManager::loadConfiguredDevices() params.append(Param(ParamTypeId(paramTypeIdString), settings.value(paramTypeIdString))); } } - device->setParams(params); settings.endGroup(); // Params + + ParamList deviceSettings; + settings.beginGroup("Settings"); + if (!settings.childGroups().isEmpty()) { + foreach (const QString ¶mTypeIdString, settings.childGroups()) { + ParamTypeId paramTypeId(paramTypeIdString); + ParamType paramType = deviceClass.settingsTypes().findById(paramTypeId); + if (!paramType.isValid()) { + qCWarning(dcDeviceManager()) << "Not loading Setting for device" << device << "because the ParamType for the saved Setting" << ParamTypeId(paramTypeIdString).toString() << "could not be found."; + continue; + } + + // Note: since nymea 0.12.2 + QVariant paramValue; + settings.beginGroup(paramTypeIdString); + paramValue = settings.value("value", paramType.defaultValue()); + paramValue.convert(settings.value("type").toInt()); + deviceSettings.append(Param(paramTypeId, paramValue)); + settings.endGroup(); // ParamId + } + } else { + foreach (const QString ¶mTypeIdString, settings.allKeys()) { + params.append(Param(ParamTypeId(paramTypeIdString), settings.value(paramTypeIdString))); + } + } + verifyParams(deviceClass.settingsTypes(), deviceSettings); + device->setSettings(deviceSettings); + + settings.endGroup(); // Settings settings.endGroup(); // DeviceId // We always add the device to the list in this case. If its in the storedDevices @@ -1246,6 +1298,16 @@ void DeviceManager::storeConfiguredDevices() } settings.endGroup(); // Params + settings.beginGroup("Settings"); + foreach (const Param ¶m, device->settings()) { + settings.beginGroup(param.paramTypeId().toString()); + settings.setValue("type", static_cast(param.value().type())); + settings.setValue("value", param.value()); + settings.endGroup(); // ParamTypeId + } + settings.endGroup(); // Settings + + settings.endGroup(); // DeviceId } settings.endGroup(); // DeviceConfig @@ -1333,7 +1395,8 @@ void DeviceManager::slotDeviceSetupFinished(Device *device, DeviceManager::Devic return; } - connect(device, SIGNAL(stateValueChanged(QUuid,QVariant)), this, SLOT(slotDeviceStateValueChanged(QUuid,QVariant))); + connect(device, &Device::stateValueChanged, this, &DeviceManager::slotDeviceStateValueChanged); + connect(device, &Device::settingChanged, this, &DeviceManager::slotDeviceSettingChanged); device->setupCompleted(); emit deviceSetupFinished(device, DeviceManager::DeviceErrorNoError); @@ -1417,6 +1480,10 @@ void DeviceManager::slotPairingFinished(const PairingTransactionId &pairingTrans emit pairingFinished(pairingTransactionId, DeviceErrorNoError, deviceId); device->setParams(params); + ParamList settings; + // Use verifyParams to populate it with defaults + verifyParams(deviceClass.settingsTypes(), settings); + device->setSettings(settings); DeviceSetupStatus setupStatus = setupDevice(device); switch (setupStatus) { @@ -1482,6 +1549,9 @@ void DeviceManager::onAutoDevicesAppeared(const DeviceClassId &deviceClassId, co device->m_autoCreated = true; device->setName(deviceDescriptor.title()); device->setParams(deviceDescriptor.params()); + ParamList settings; + verifyParams(deviceClass.settingsTypes(), settings); + device->setSettings(settings); device->setParentId(deviceDescriptor.parentDeviceId()); DeviceSetupStatus setupStatus = setupDevice(device); @@ -1551,7 +1621,7 @@ void DeviceManager::cleanupDeviceStateCache() } } -void DeviceManager::slotDeviceStateValueChanged(const QUuid &stateTypeId, const QVariant &value) +void DeviceManager::slotDeviceStateValueChanged(const StateTypeId &stateTypeId, const QVariant &value) { Device *device = qobject_cast(sender()); if (!device) { @@ -1564,6 +1634,16 @@ void DeviceManager::slotDeviceStateValueChanged(const QUuid &stateTypeId, const emit eventTriggered(event); } +void DeviceManager::slotDeviceSettingChanged(const ParamTypeId ¶mTypeId, const QVariant &value) +{ + Device *device = qobject_cast(sender()); + if (!device) { + return; + } + storeConfiguredDevices(); + emit deviceSettingChanged(device->id(), paramTypeId, value); +} + bool DeviceManager::verifyPluginMetadata(const QJsonObject &data) { QStringList requiredFields; @@ -1602,7 +1682,8 @@ DeviceManager::DeviceSetupStatus DeviceManager::setupDevice(Device *device) return status; } - connect(device, SIGNAL(stateValueChanged(QUuid,QVariant)), this, SLOT(slotDeviceStateValueChanged(QUuid,QVariant))); + connect(device, &Device::stateValueChanged, this, &DeviceManager::slotDeviceStateValueChanged); + connect(device, &Device::settingChanged, this, &DeviceManager::slotDeviceSettingChanged); device->setupCompleted(); return status; diff --git a/libnymea/devicemanager.h b/libnymea/devicemanager.h index db1998fa..feec853e 100644 --- a/libnymea/devicemanager.h +++ b/libnymea/devicemanager.h @@ -119,6 +119,7 @@ public: DeviceError reconfigureDevice(const DeviceId &deviceId, const DeviceDescriptorId &deviceDescriptorId); DeviceError editDevice(const DeviceId &deviceId, const QString &name); + DeviceError setDeviceSettings(const DeviceId &deviceId, const ParamList &settings); DeviceError pairDevice(const PairingTransactionId &pairingTransactionId, const DeviceClassId &deviceClassId, const QString &name, const ParamList ¶ms); DeviceError pairDevice(const PairingTransactionId &pairingTransactionId, const DeviceClassId &deviceClassId, const QString &name, const DeviceDescriptorId &deviceDescriptorId); @@ -141,11 +142,12 @@ signals: void loaded(); void pluginConfigChanged(const PluginId &id, const ParamList &config); void eventTriggered(const Event &event); - void deviceStateChanged(Device *device, const QUuid &stateTypeId, const QVariant &value); + void deviceStateChanged(Device *device, const StateTypeId &stateTypeId, const QVariant &value); void deviceRemoved(const DeviceId &deviceId); void deviceDisappeared(const DeviceId &deviceId); void deviceAdded(Device *device); void deviceChanged(Device *device); + void deviceSettingChanged(const DeviceId deviceId, const ParamTypeId &settingParamTypeId, const QVariant &value); void devicesDiscovered(const DeviceClassId &deviceClassId, const QList &devices); void deviceSetupFinished(Device *device, DeviceError status); void deviceReconfigurationFinished(Device *device, DeviceError status); @@ -171,7 +173,8 @@ private slots: void cleanupDeviceStateCache(); // Only connect this to Devices. It will query the sender() - void slotDeviceStateValueChanged(const QUuid &stateTypeId, const QVariant &value); + void slotDeviceStateValueChanged(const StateTypeId &stateTypeId, const QVariant &value); + void slotDeviceSettingChanged(const ParamTypeId ¶mTypeId, const QVariant &value); private: bool verifyPluginMetadata(const QJsonObject &data); diff --git a/libnymea/plugin/device.cpp b/libnymea/plugin/device.cpp index 60e239e0..76b8107a 100644 --- a/libnymea/plugin/device.cpp +++ b/libnymea/plugin/device.cpp @@ -34,7 +34,7 @@ \sa DeviceClass, DeviceDescriptor */ -/*! \fn void Device::stateValueChanged(const QUuid &stateTypeId, const QVariant &value) +/*! \fn void Device::stateValueChanged(const StateTypeId &stateTypeId, const QVariant &value) This signal is emitted when the \l{State} with the given \a stateTypeId changed. The \a value parameter describes the new value of the State. */ @@ -138,6 +138,59 @@ void Device::setParamValue(const ParamTypeId ¶mTypeId, const QVariant &value m_params = params; } +ParamList Device::settings() const +{ + return m_settings; +} + +bool Device::hasSetting(const ParamTypeId ¶mTypeId) const +{ + return m_settings.hasParam(paramTypeId); +} + +void Device::setSettings(const ParamList &settings) +{ + m_settings = settings; + foreach (const Param ¶m, m_settings) { + emit settingChanged(param.paramTypeId(), param.value()); + } +} + +QVariant Device::setting(const ParamTypeId ¶mTypeId) const +{ + foreach (Param setting, m_settings) { + if (setting.paramTypeId() == paramTypeId) { + return setting.value(); + } + } + return QVariant(); +} + +void Device::setSettingValue(const ParamTypeId ¶mTypeId, const QVariant &value) +{ + ParamList settings; + bool found = false; + bool changed = false; + foreach (Param param, m_settings) { + if (param.paramTypeId() == paramTypeId) { + found = true; + if (param.value() != value) { + param.setValue(value); + changed = true; + } + } + settings << param; + } + if (!found) { + qCWarning(dcDeviceManager()) << "Device" << m_name << "(" << m_id.toString() << ") does not have a setting with id" << paramTypeId; + return; + } + if (changed) { + m_settings = settings; + emit settingChanged(paramTypeId, value); + } +} + /*! Returns the states of this Device. It must match the \l{StateType} description in the associated \l{DeviceClass}. */ QList Device::states() const { diff --git a/libnymea/plugin/device.h b/libnymea/plugin/device.h index bf142b1d..6410916b 100644 --- a/libnymea/plugin/device.h +++ b/libnymea/plugin/device.h @@ -56,6 +56,13 @@ public: QVariant paramValue(const ParamTypeId ¶mTypeId) const; void setParamValue(const ParamTypeId ¶mName, const QVariant &value); + ParamList settings() const; + bool hasSetting(const ParamTypeId ¶mTypeId) const; + void setSettings(const ParamList &settings); + + QVariant setting(const ParamTypeId ¶mTypeId) const; + void setSettingValue(const ParamTypeId ¶mTypeId, const QVariant &value); + QList states() const; bool hasState(const StateTypeId &stateTypeId) const; void setStates(const QList &states); @@ -72,7 +79,8 @@ public: bool autoCreated() const; signals: - void stateValueChanged(const QUuid &stateTypeId, const QVariant &value); + void stateValueChanged(const StateTypeId &stateTypeId, const QVariant &value); + void settingChanged(const ParamTypeId ¶mTypeId, const QVariant &value); void nameChanged(); private: @@ -89,6 +97,7 @@ private: PluginId m_pluginId; QString m_name; ParamList m_params; + ParamList m_settings; QList m_states; bool m_setupComplete = false; bool m_autoCreated = false; diff --git a/libnymea/plugin/deviceplugin.cpp b/libnymea/plugin/deviceplugin.cpp index 2f8a449d..58f675c4 100644 --- a/libnymea/plugin/deviceplugin.cpp +++ b/libnymea/plugin/deviceplugin.cpp @@ -603,6 +603,15 @@ void DevicePlugin::loadMetaData() deviceClass.setParamTypes(paramTypesVerification.second); } + // Read settings + QPair > settingsTypesVerification = parseParamTypes(deviceClassObject.value("settingsTypes").toArray()); + if (!settingsTypesVerification.first) { + broken = true; + break; + } else { + deviceClass.setSettingsTypes(settingsTypesVerification.second); + } + // Read discover params QPair > discoveryParamVerification = parseParamTypes(deviceClassObject.value("discoveryParamTypes").toArray()); if (!discoveryParamVerification.first) { diff --git a/libnymea/plugin/nymea-generateplugininfo b/libnymea/plugin/nymea-generateplugininfo index 9b157996..208cee55 100755 --- a/libnymea/plugin/nymea-generateplugininfo +++ b/libnymea/plugin/nymea-generateplugininfo @@ -5,7 +5,7 @@ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # Copyright (C) 2015-2018 Simon Stuerz # -# Copyright (C) 2014-2018 Michael Zanetti # +# Copyright (C) 2014-2019 Michael Zanetti # # # # This file is part of nymea. # # # @@ -79,6 +79,7 @@ def extractParamTypes(paramTypes, deviceClassName, typeClass, typeName): for paramType in paramTypes: try: variableName = '%sParamTypeId' % (deviceClassName + typeName[0].capitalize() + typeName[1:] + typeClass + paramType['name'][0].capitalize() + paramType['name'][1:]) + print("....variable:", variableName) if not variableName in variableNames: variableNames.append(variableName) printInfo('Define ParamTypeId %s = %s' % (variableName, paramType['id'])) @@ -138,6 +139,9 @@ def extractDeviceClasses(deviceClasses): if 'paramTypes' in deviceClass: extractParamTypes(deviceClass['paramTypes'], deviceClass['name'], "", 'device') + if 'settingsTypes' in deviceClass: + extractParamTypes(deviceClass['settingsTypes'], deviceClass['name'], "", 'settings') + if 'discoveryParamTypes' in deviceClass: extractParamTypes(deviceClass['discoveryParamTypes'], deviceClass['name'], "", 'discovery') diff --git a/libnymea/types/deviceclass.cpp b/libnymea/types/deviceclass.cpp index 52230010..c3013d4d 100644 --- a/libnymea/types/deviceclass.cpp +++ b/libnymea/types/deviceclass.cpp @@ -220,13 +220,27 @@ ParamTypes DeviceClass::paramTypes() const return m_paramTypes; } -/*! Set the \a params of this DeviceClass. \{Device}{Devices} created - from this \l{DeviceClass} must have their actions matching to this template. */ +/*! Set the \a paramsTypes of this DeviceClass. \{Device}{Devices} created + from this \l{DeviceClass} must have their params matching to this template. */ void DeviceClass::setParamTypes(const ParamTypes ¶ms) { m_paramTypes = params; } +/*! Returns the settings description of this DeviceClass. \{Device}{Devices} created + from this \l{DeviceClass} must have their settings matching to this template. */ +ParamTypes DeviceClass::settingsTypes() const +{ + return m_settingsTypes; +} + +/*! Set the \a settingsTypes of this DeviceClass. \{Device}{Devices} created + from this \l{DeviceClass} must have their settings matching to this template. */ +void DeviceClass::setSettingsTypes(const ParamTypes &settingsTypes) +{ + m_settingsTypes = settingsTypes; +} + /*! Returns the discovery params description of this DeviceClass. \{Device}{Devices} created from this \l{DeviceClass} must have their params matching to this template. */ ParamTypes DeviceClass::discoveryParamTypes() const @@ -305,7 +319,7 @@ QStringList DeviceClass::typeProperties() { return QStringList() << "id" << "name" << "displayName" << "createMethods" << "setupMethod" << "interfaces" << "pairingInfo" << "discoveryParamTypes" << "discoveryParamTypes" - << "paramTypes" << "stateTypes" << "actionTypes" << "eventTypes"; + << "paramTypes" << "settingsTypes" << "stateTypes" << "actionTypes" << "eventTypes"; } /*! Returns a list of mandatory JSON properties a DeviceClass JSON definition must have. */ diff --git a/libnymea/types/deviceclass.h b/libnymea/types/deviceclass.h index a5231f29..298d6dd5 100644 --- a/libnymea/types/deviceclass.h +++ b/libnymea/types/deviceclass.h @@ -87,6 +87,9 @@ public: ParamTypes paramTypes() const; void setParamTypes(const ParamTypes ¶mTypes); + ParamTypes settingsTypes() const; + void setSettingsTypes(const ParamTypes &settingsTypes); + ParamTypes discoveryParamTypes() const; void setDiscoveryParamTypes(const ParamTypes ¶mTypes); @@ -117,6 +120,7 @@ private: EventTypes m_eventTypes; ActionTypes m_actionTypes; ParamTypes m_paramTypes; + ParamTypes m_settingsTypes; ParamTypes m_discoveryParamTypes; CreateMethods m_createMethods; SetupMethod m_setupMethod; diff --git a/nymea.pri b/nymea.pri index 914914a5..87cbb365 100644 --- a/nymea.pri +++ b/nymea.pri @@ -3,7 +3,7 @@ NYMEA_VERSION_STRING=$$system('dpkg-parsechangelog | sed -n -e "s/^Version: //p" # define protocol versions JSON_PROTOCOL_VERSION_MAJOR=2 -JSON_PROTOCOL_VERSION_MINOR=1 +JSON_PROTOCOL_VERSION_MINOR=2 REST_API_VERSION=1 COPYRIGHT_YEAR_FROM=2013 diff --git a/plugins/mock/devicepluginmock.json b/plugins/mock/devicepluginmock.json index 7e63e1b3..651531d6 100644 --- a/plugins/mock/devicepluginmock.json +++ b/plugins/mock/devicepluginmock.json @@ -65,6 +65,15 @@ "defaultValue": false } ], + "settingsTypes": [ + { + "id": "367f7ba4-5039-47be-abd8-59cc8eaf4b9a", + "name": "setting1", + "displayName": "Setting 1", + "type": "int", + "defaultValue": 5 + } + ], "stateTypes": [ { "id": "80baec19-54de-4948-ac46-31eabfaceb83", diff --git a/tests/auto/api.json b/tests/auto/api.json index 6211ef97..448c61be 100644 --- a/tests/auto/api.json +++ b/tests/auto/api.json @@ -1,4 +1,4 @@ -2.1 +2.2 { "methods": { "Actions.ExecuteAction": { @@ -447,6 +447,18 @@ ] } }, + "Devices.SetDeviceSettings": { + "description": "Change the settings of a device.", + "params": { + "deviceId": "Uuid", + "settings": [ + "$ref:Param" + ] + }, + "returns": { + "deviceError": "$ref:DeviceError" + } + }, "Devices.SetPluginConfiguration": { "description": "Set a plugin's params.", "params": { @@ -1101,7 +1113,7 @@ } }, "Devices.DeviceChanged": { - "description": "Emitted whenever the params or name of a Device changed (by EditDevice or ReconfigureDevice).", + "description": "Emitted whenever the params or name of a Device are changed (by EditDevice or ReconfigureDevice).", "params": { "device": "$ref:Device" } @@ -1112,6 +1124,14 @@ "deviceId": "Uuid" } }, + "Devices.DeviceSettingChanged": { + "description": "Emitted whenever the setting of a Device is changed.", + "params": { + "deviceId": "Uuid", + "paramTypeId": "Uuid", + "value": "Variant" + } + }, "Devices.PluginConfigurationChanged": { "description": "Emitted whenever a plugin's configuration is changed.", "params": { @@ -1366,6 +1386,9 @@ "params": [ "$ref:Param" ], + "settings": [ + "$ref:Param" + ], "setupComplete": "Bool", "states": [ { @@ -1397,6 +1420,9 @@ "$ref:ParamType" ], "pluginId": "Uuid", + "settingsTypes": [ + "$ref:ParamType" + ], "setupMethod": "$ref:SetupMethod", "stateTypes": [ "$ref:StateType" diff --git a/tests/auto/devices/testdevices.cpp b/tests/auto/devices/testdevices.cpp index 3c1abbd7..cb65782e 100644 --- a/tests/auto/devices/testdevices.cpp +++ b/tests/auto/devices/testdevices.cpp @@ -84,6 +84,8 @@ private slots: void editDevices_data(); void editDevices(); + void testDeviceSettings(); + void reconfigureDevices_data(); void reconfigureDevices(); @@ -867,6 +869,86 @@ void TestDevices::editDevices() verifyDeviceError(response); } +void TestDevices::testDeviceSettings() +{ + // add device + QVariantList deviceParams; + QVariantMap httpportParam; + httpportParam.insert("paramTypeId", httpportParamTypeId); + httpportParam.insert("value", 8889); + deviceParams.append(httpportParam); + + QVariantMap params; + params.insert("deviceClassId", mockDeviceClassId); + params.insert("name", "Mock"); + params.insert("deviceParams", deviceParams); + QVariant response = injectAndWait("Devices.AddConfiguredDevice", params); + verifyDeviceError(response); + DeviceId deviceId = DeviceId(response.toMap().value("params").toMap().value("deviceId").toString()); + + // check if default settings are loaded + params.clear(); + params.insert("deviceId", deviceId); + response = injectAndWait("Devices.GetConfiguredDevices", params); + QVariantList devices = response.toMap().value("params").toMap().value("devices").toList(); + QVERIFY2(devices.count() == 1, "Error creating device"); + + QVariantMap device = devices.first().toMap(); + QVERIFY2(DeviceId(device.value("id").toString()) == deviceId, "DeviceId not matching"); + + QVariantList settings = device.value("settings").toList(); + QCOMPARE(settings.count(), 1); + + QCOMPARE(settings.first().toMap().value("paramTypeId").toString(), mockSetting1ParamTypeId.toString()); + QVERIFY2(settings.first().toMap().value("value").toInt() == 5, "Setting 1 default value not matching"); + + // change a setting + params.clear(); + params.insert("deviceId", deviceId); + settings.clear(); + QVariantMap setting; + setting.insert("paramTypeId", mockSetting1ParamTypeId); + setting.insert("value", 7); + settings.append(setting); + params.insert("settings", settings); + response = injectAndWait("Devices.SetDeviceSettings", params); + + // Check if the change happened + params.clear(); + params.insert("deviceId", deviceId); + response = injectAndWait("Devices.GetConfiguredDevices", params); + devices = response.toMap().value("params").toMap().value("devices").toList(); + QVERIFY2(devices.count() == 1, "Error creating device"); + + device = devices.first().toMap(); + QVERIFY2(DeviceId(device.value("id").toString()) == deviceId, "DeviceId not matching"); + + settings = device.value("settings").toList(); + QCOMPARE(settings.count(), 1); + + QCOMPARE(settings.first().toMap().value("paramTypeId").toString(), mockSetting1ParamTypeId.toString()); + QVERIFY2(settings.first().toMap().value("value").toInt() == 7, "Setting 1 changed value not matching"); + + restartServer(); + + // Check if the change persisted + params.clear(); + params.insert("deviceId", deviceId); + response = injectAndWait("Devices.GetConfiguredDevices", params); + devices = response.toMap().value("params").toMap().value("devices").toList(); + QVERIFY2(devices.count() == 1, "Error creating device"); + + device = devices.first().toMap(); + QVERIFY2(DeviceId(device.value("id").toString()) == deviceId, "DeviceId not matching"); + + settings = device.value("settings").toList(); + QCOMPARE(settings.count(), 1); + + QCOMPARE(settings.first().toMap().value("paramTypeId").toString(), mockSetting1ParamTypeId.toString()); + QVERIFY2(settings.first().toMap().value("value").toInt() == 7, "Setting 1 changed value not persisting restart"); + +} + void TestDevices::reconfigureDevices_data() { QVariantList asyncChangeDeviceParams; diff --git a/tests/testlib/nymeatestbase.cpp b/tests/testlib/nymeatestbase.cpp index 1fbdf188..762a90e1 100644 --- a/tests/testlib/nymeatestbase.cpp +++ b/tests/testlib/nymeatestbase.cpp @@ -80,6 +80,7 @@ ParamTypeId ip4ParamTypeId = ParamTypeId("9e5f86a0-4bb3-4892-bff8-3fc4032af6e2") ParamTypeId ip6ParamTypeId = ParamTypeId("43bf3832-dd48-4090-a836-656e8b60216e"); ParamTypeId urlParamTypeId = ParamTypeId("fa67229f-fcef-496f-b671-59a4b48f3ab5"); ParamTypeId macParamTypeId = ParamTypeId("e93db587-7919-48f3-8c88-1651de63c765"); +ParamTypeId mockSetting1ParamTypeId = ParamTypeId("367f7ba4-5039-47be-abd8-59cc8eaf4b9a"); // Parent device diff --git a/tests/testlib/nymeatestbase.h b/tests/testlib/nymeatestbase.h index 15d7910a..a1985a88 100644 --- a/tests/testlib/nymeatestbase.h +++ b/tests/testlib/nymeatestbase.h @@ -82,6 +82,7 @@ extern ParamTypeId ip4ParamTypeId; extern ParamTypeId ip6ParamTypeId; extern ParamTypeId urlParamTypeId; extern ParamTypeId macParamTypeId; +extern ParamTypeId mockSetting1ParamTypeId; // Parent / Child device extern EventTypeId mockParentChildEventId;