From 023a2239082d8898f9bf50af8558d3e525d79bbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Wed, 28 Mar 2018 18:31:14 +0200 Subject: [PATCH] Improve plugin json parsing and fix generate-plugininfo script --- doc/plugin-json.qdoc | 26 +-- libnymea/plugin/deviceplugin.cpp | 293 ++++++++++++++++++++++--------- libnymea/plugin/deviceplugin.h | 3 +- libnymea/types/actiontype.cpp | 12 ++ libnymea/types/actiontype.h | 3 + libnymea/types/deviceclass.cpp | 15 ++ libnymea/types/deviceclass.h | 3 + libnymea/types/eventtype.cpp | 12 ++ libnymea/types/eventtype.h | 4 + libnymea/types/paramtype.cpp | 15 +- libnymea/types/paramtype.h | 3 + libnymea/types/statetype.cpp | 14 ++ libnymea/types/statetype.h | 3 + plugins/nymea-generateplugininfo | 34 ++-- 14 files changed, 324 insertions(+), 116 deletions(-) diff --git a/doc/plugin-json.qdoc b/doc/plugin-json.qdoc index 4de4dc15..38500e71 100644 --- a/doc/plugin-json.qdoc +++ b/doc/plugin-json.qdoc @@ -140,30 +140,20 @@ "id": "uuid", "name": "deviceClassName", "displayName": "The name of the device class (translatable)", + "o:createMethods": [ ], + "o:setupMethod": "SetupMethod", "o:deviceIcon": "Icon", "o:interfaces": [ "interfacename" ], - "o:basicTags": [ - "BasicTag" - ], - "createMethods": [ - "CreateMethod" - ], - "o:interfaces" [ "connectable" ], - "o:setupMethod": "SetupMethod", + "o:basicTags": [ ], "o:pairingInfo": "Information how to pair the device. (translatable)", "o:criticalStateTypeId": "uuid", "o:primaryStateTypeId": "uuid", "o:primaryActionTypeId": "uuid", - "o:discoveryParamTypes": [ - ], - "paramTypes": [ - ], - "o:stateTypes": [ - ], - "o:actionTypes": [ - ], - "o:eventTypes": [ - ] + "o:discoveryParamTypes": [ ], + "o:paramTypes": [ ], + "o:stateTypes": [ ], + "o:actionTypes": [ ], + "o:eventTypes": [ ] } ] } diff --git a/libnymea/plugin/deviceplugin.cpp b/libnymea/plugin/deviceplugin.cpp index 8b0cc7db..68b823a2 100644 --- a/libnymea/plugin/deviceplugin.cpp +++ b/libnymea/plugin/deviceplugin.cpp @@ -302,15 +302,22 @@ void DevicePlugin::initPlugin(DeviceManager *deviceManager) QPair > DevicePlugin::parseParamTypes(const QJsonArray &array) const { + int index = 0; QList paramTypes; foreach (const QJsonValue ¶mTypesJson, array) { QJsonObject pt = paramTypesJson.toObject(); - // Check fields - int index = 0; - QStringList missingFields = verifyFields(QStringList() << "id" << "name" << "displayName" << "type", pt); - if (!missingFields.isEmpty()) { - qCWarning(dcDeviceManager) << pluginName() << "Error parsing ParamType: missing fields" << missingFields.join(", ") << endl << pt; + QPair verificationResult = verifyFields(ParamType::jsonProperties(), ParamType::mandatoryJsonProperties(), pt); + + // Check mandatory fields + if (!verificationResult.first.isEmpty()) { + qCWarning(dcDeviceManager()) << pluginName() << "Error parsing ParamType: missing fields:" << verificationResult.first.join(", ") << endl << pt; + return QPair >(false, QList()); + } + + // Check if there are any unknown fields + if (!verificationResult.second.isEmpty()) { + qCWarning(dcDeviceManager()) << pluginName() << "Error parsing ParamType: unknown fields:" << verificationResult.second.join(", ") << endl << pt; return QPair >(false, QList()); } @@ -325,7 +332,7 @@ QPair > DevicePlugin::parseParamTypes(const QJsonArray &a ParamType paramType(ParamTypeId(pt.value("id").toString()), pt.value("name").toString(), t, pt.value("defaultValue").toVariant()); paramType.setDisplayName(translateValue(m_metaData.value("name").toString(), pt.value("displayName").toString())); - paramType.setIndex(index); + // Set allowed values QVariantList allowedValues; @@ -361,12 +368,35 @@ QPair > DevicePlugin::parseParamTypes(const QJsonArray &a paramType.setAllowedValues(allowedValues); paramType.setLimits(pt.value("minValue").toVariant(), pt.value("maxValue").toVariant()); + paramType.setIndex(index++); paramTypes.append(paramType); } return QPair >(true, paramTypes); } +QPair DevicePlugin::verifyFields(const QStringList &possibleFields, const QStringList &mandatoryFields, const QJsonObject &value) const +{ + QStringList missingFields; + QStringList unknownFields; + + // Check if we have an unknown field + foreach (const QString &property, value.keys()) { + if (!possibleFields.contains(property)) { + unknownFields << property; + } + } + + // Check if a mandatory field is missing + foreach (const QString &field, mandatoryFields) { + if (!value.contains(field)) { + missingFields << field; + } + } + + return QPair(missingFields, unknownFields); +} + /*! Returns a map containing the plugin configuration. @@ -393,7 +423,7 @@ QVariant DevicePlugin::configValue(const ParamTypeId ¶mTypeId) const DeviceManager::DeviceError DevicePlugin::setConfiguration(const ParamList &configuration) { foreach (const Param ¶m, configuration) { - qCDebug(dcDeviceManager) << "* Set plugin configuration" << param; + qCDebug(dcDeviceManager()) << "* Set plugin configuration" << param; DeviceManager::DeviceError result = setConfigValue(param.paramTypeId(), param.value()); if (result != DeviceManager::DeviceErrorNoError) return result; @@ -418,7 +448,7 @@ DeviceManager::DeviceError DevicePlugin::setConfigValue(const ParamTypeId ¶m } if (!found) { - qCWarning(dcDeviceManager) << QString("Could not find plugin parameter with the id %1.").arg(paramTypeId.toString()); + qCWarning(dcDeviceManager()) << QString("Could not find plugin parameter with the id %1.").arg(paramTypeId.toString()); return DeviceManager::DeviceErrorInvalidParameter; } @@ -507,18 +537,45 @@ void DevicePlugin::loadMetaData() } - QStringList missingFields = verifyFields(QStringList() << "id" << "name" << "displayName" << "vendors", m_metaData); - if (!missingFields.isEmpty()) { - qCWarning(dcDeviceManager) << "Skipping plugin because of missing" << missingFields.join(", ") << m_metaData; + // Note: The DevicePlugin has no type class, so we define the json properties here + QStringList pluginMandatoryJsonProperties = QStringList() << "id" << "name" << "displayName" << "vendors"; + QStringList pluginJsonProperties = QStringList() << "id" << "name" << "displayName" << "vendors" << "paramtypes"; + + QPair verificationResult = verifyFields(pluginJsonProperties, pluginMandatoryJsonProperties, m_metaData); + + // Check mandatory fields + if (!verificationResult.first.isEmpty()) { + qCWarning(dcDeviceManager()) << pluginName() << "Skipping plugin because of missing fields:" << verificationResult.first.join(", ") << endl << m_metaData; return; } + // Check if there are any unknown fields + if (!verificationResult.second.isEmpty()) { + qCWarning(dcDeviceManager()) << pluginName() << "Skipping plugin because of unknown fields:" << verificationResult.second.join(", ") << endl << m_metaData; + return; + } + + foreach (const QJsonValue &vendorJson, m_metaData.value("vendors").toArray()) { bool broken = false; QJsonObject vendorObject = vendorJson.toObject(); - QStringList missingFields = verifyFields(QStringList() << "id" << "name" << "displayName", vendorObject); - if (!missingFields.isEmpty()) { - qCWarning(dcDeviceManager) << "Skipping vendor because of missing" << missingFields.join(", ") << vendorObject; + + // Note: The Vendor has no type class, so we define the json properties here + QStringList vendorMandatoryJsonProperties = QStringList() << "id" << "name" << "displayName"; + QStringList vendorJsonProperties = QStringList() << "id" << "name" << "displayName"; + + QPair verificationResult = verifyFields(vendorJsonProperties, vendorMandatoryJsonProperties, vendorObject); + + // Check mandatory fields + if (!verificationResult.first.isEmpty()) { + qCWarning(dcDeviceManager()) << pluginName() << "Skipping vendor because of missing fields:" << verificationResult.first.join(", ") << endl << vendorObject; + broken = true; + break; + } + + // Check if there are any unknown fields + if (!verificationResult.second.isEmpty()) { + qCWarning(dcDeviceManager()) << pluginName() << "Skipping vendor because of unknown fields:" << verificationResult.second.join(", ") << endl << vendorObject; broken = true; break; } @@ -526,9 +583,18 @@ void DevicePlugin::loadMetaData() VendorId vendorId = vendorObject.value("id").toString(); foreach (const QJsonValue &deviceClassJson, vendorJson.toObject().value("deviceClasses").toArray()) { QJsonObject deviceClassObject = deviceClassJson.toObject(); - QStringList missingFields = verifyFields(QStringList() << "id" << "name" << "displayName" << "createMethods" << "paramTypes", deviceClassObject); - if (!missingFields.isEmpty()) { - qCWarning(dcDeviceManager) << "Skipping DeviceClass because of missing" << missingFields.join(", ") << deviceClassObject; + QPair verificationResult = verifyFields(DeviceClass::jsonProperties(), DeviceClass::mandatoryJsonProperties(), deviceClassObject); + + // Check mandatory fields + if (!verificationResult.first.isEmpty()) { + qCWarning(dcDeviceManager()) << pluginName() << "Skipping device class because of missing fields:" << verificationResult.first.join(", ") << endl << deviceClassObject; + broken = true; + break; + } + + // Check if there are any unknown fields + if (!verificationResult.second.isEmpty()) { + qCWarning(dcDeviceManager()) << pluginName() << "Skipping device class because of unknown fields:" << verificationResult.second.join(", ") << endl << deviceClassObject; broken = true; break; } @@ -536,55 +602,80 @@ void DevicePlugin::loadMetaData() DeviceClass deviceClass(pluginId(), vendorId, deviceClassObject.value("id").toString()); deviceClass.setName(deviceClassObject.value("name").toString()); deviceClass.setDisplayName(translateValue(m_metaData.value("name").toString(), deviceClassObject.value("displayName").toString())); + + // Read create methods DeviceClass::CreateMethods createMethods; - foreach (const QJsonValue &createMethodValue, deviceClassObject.value("createMethods").toArray()) { - if (createMethodValue.toString() == "discovery") { - createMethods |= DeviceClass::CreateMethodDiscovery; - } else if (createMethodValue.toString() == "auto") { - createMethods |= DeviceClass::CreateMethodAuto; - } else if (createMethodValue.toString() == "user") { - createMethods |= DeviceClass::CreateMethodUser; - } else { - qCWarning(dcDeviceManager) << "Unknown createMehtod" << createMethodValue.toString() << - "in deviceClass " << deviceClass.name() << ". Falling back to CreateMethodUser."; - createMethods |= DeviceClass::CreateMethodUser; + if (!deviceClassObject.contains("createMethods")) { + // Default if not specified + createMethods |= DeviceClass::CreateMethodUser; + } else { + foreach (const QJsonValue &createMethodValue, deviceClassObject.value("createMethods").toArray()) { + if (createMethodValue.toString() == "Discovery" || createMethodValue.toString() == "discovery") { + createMethods |= DeviceClass::CreateMethodDiscovery; + } else if (createMethodValue.toString() == "Auto" || createMethodValue.toString() == "auto") { + createMethods |= DeviceClass::CreateMethodAuto; + } else if (createMethodValue.toString() == "User" || createMethodValue.toString() == "user") { + createMethods |= DeviceClass::CreateMethodUser; + } else { + qCWarning(dcDeviceManager()) << "Unknown createMehtod" << createMethodValue.toString() << "in deviceClass " + << deviceClass.name() << ". Falling back to CreateMethodUser."; + createMethods |= DeviceClass::CreateMethodUser; + } } } deviceClass.setCreateMethods(createMethods); + + // Read device icon QPair deviceIconVerification = loadAndVerifyDeviceIcon(deviceClassObject.value("deviceIcon").toString()); if (!deviceIconVerification.first) { broken = true; + break; } else { deviceClass.setDeviceIcon(deviceIconVerification.second); } - QPair > discoveryParamVerification = parseParamTypes(deviceClassObject.value("discoveryParamTypes").toArray()); - if (!discoveryParamVerification.first) { - broken = true; - } else { - deviceClass.setDiscoveryParamTypes(discoveryParamVerification.second); - } - - QString setupMethod = deviceClassObject.value("setupMethod").toString(); - if (setupMethod == "pushButton") { - deviceClass.setSetupMethod(DeviceClass::SetupMethodPushButton); - } else if (setupMethod == "displayPin") { - deviceClass.setSetupMethod(DeviceClass::SetupMethodDisplayPin); - } else if (setupMethod == "enterPin") { - deviceClass.setSetupMethod(DeviceClass::SetupMethodEnterPin); - } else if (setupMethod == "justAdd") { - qCWarning(dcDeviceManager) << "Unknown setupMehtod" << setupMethod << - "in deviceClass " << deviceClass.name() << ". Falling back to SetupMethodJustAdd."; - deviceClass.setSetupMethod(DeviceClass::SetupMethodJustAdd); - } - deviceClass.setPairingInfo(translateValue(m_metaData.value("name").toString(), deviceClassObject.value("pairingInfo").toString())); + // Read params QPair > paramTypesVerification = parseParamTypes(deviceClassObject.value("paramTypes").toArray()); if (!paramTypesVerification.first) { broken = true; + break; } else { deviceClass.setParamTypes(paramTypesVerification.second); } + // Read discover params + QPair > discoveryParamVerification = parseParamTypes(deviceClassObject.value("discoveryParamTypes").toArray()); + if (!discoveryParamVerification.first) { + broken = true; + break; + } else { + deviceClass.setDiscoveryParamTypes(discoveryParamVerification.second); + } + + // Read setup method + DeviceClass::SetupMethod setupMethod = DeviceClass::SetupMethodJustAdd; + if (deviceClassObject.contains("setupMethod")) { + QString setupMethodString = deviceClassObject.value("setupMethod").toString(); + if (setupMethodString == "PushButton" || setupMethodString == "pushButton") { + setupMethod = DeviceClass::SetupMethodPushButton; + } else if (setupMethodString == "DisplayPin" || setupMethodString == "displayPin") { + setupMethod = DeviceClass::SetupMethodDisplayPin; + } else if (setupMethodString == "EnterPin" || setupMethodString == "enterPin") { + setupMethod = DeviceClass::SetupMethodEnterPin; + } else if (setupMethodString == "JustAdd" || setupMethodString == "justAdd") { + setupMethod = DeviceClass::SetupMethodJustAdd; + } else { + qCWarning(dcDeviceManager()) << "Unknown setupMehtod" << setupMethod << "in deviceClass" + << deviceClass.name() << ". Falling back to SetupMethodJustAdd."; + setupMethod = DeviceClass::SetupMethodJustAdd; + } + } + deviceClass.setSetupMethod(setupMethod); + + // Read pairing info + deviceClass.setPairingInfo(translateValue(m_metaData.value("name").toString(), deviceClassObject.value("pairingInfo").toString())); + + // Read basic tags QList basicTags; foreach (const QJsonValue &basicTagJson, deviceClassObject.value("basicTags").toArray()) { QPair basicTagVerification = loadAndVerifyBasicTag(basicTagJson.toString()); @@ -601,30 +692,44 @@ void DevicePlugin::loadMetaData() QList stateTypes; QList eventTypes; - // StateTypes + // Read StateTypes int index = 0; foreach (const QJsonValue &stateTypesJson, deviceClassObject.value("stateTypes").toArray()) { QJsonObject st = stateTypesJson.toObject(); bool writableState = false; - QStringList requiredFields; - requiredFields << "type" << "id" << "name" << "displayName" << "defaultValue" << "displayNameEvent"; - if (st.contains("writable") && st.value("writable").toBool()) { - writableState = true; - requiredFields << "displayNameAction"; - } - QStringList missingFields = verifyFields(requiredFields, st); - if (!missingFields.isEmpty()) { - qCWarning(dcDeviceManager) << "Skipping device class" << deviceClass.name() << "because of missing" << missingFields.join(", ") << "in stateType" << st; + + QPair verificationResult = verifyFields(StateType::jsonProperties(), StateType::mandatoryJsonProperties(), st); + + // Check mandatory fields + if (!verificationResult.first.isEmpty()) { + qCWarning(dcDeviceManager()) << "Skipping device class" << deviceClass.name() << "because of missing" << verificationResult.first.join(", ") << "in stateType" << st; broken = true; break; } + // Check if there are any unknown fields + if (!verificationResult.second.isEmpty()) { + qCWarning(dcDeviceManager()) << "Skipping device class" << deviceClass.name() << "because of unknown properties" << verificationResult.second.join(", ") << "in stateType" << st; + broken = true; + break; + } + + if (st.contains("writable") && st.value("writable").toBool()) { + writableState = true; + if (!st.contains("displayNameAction")) { + qCWarning(dcDeviceManager()) << "Skipping device class" << deviceClass.name() << ". The state is writable, but does not define the displayNameAction property" << st; + broken = true; + break; + } + } + QVariant::Type t = QVariant::nameToType(st.value("type").toString().toLatin1().data()); if (t == QVariant::Invalid) { qCWarning(dcDeviceManager()) << "Invalid StateType type:" << st.value("type").toString(); broken = true; break; } + StateType stateType(st.value("id").toString()); stateType.setName(st.value("name").toString()); stateType.setDisplayName(translateValue(m_metaData.value("name").toString(), st.value("displayName").toString())); @@ -637,6 +742,7 @@ void DevicePlugin::loadMetaData() } else { stateType.setUnit(unitVerification.second); } + stateType.setDefaultValue(st.value("defaultValue").toVariant()); if (st.contains("minValue")) stateType.setMinValue(st.value("minValue").toVariant()); @@ -704,9 +810,19 @@ void DevicePlugin::loadMetaData() index = 0; foreach (const QJsonValue &actionTypesJson, deviceClassObject.value("actionTypes").toArray()) { QJsonObject at = actionTypesJson.toObject(); - QStringList missingFields = verifyFields(QStringList() << "id" << "name" << "displayName", at); - if (!missingFields.isEmpty()) { - qCWarning(dcDeviceManager) << "Skipping device class" << deviceClass.name() << "because of missing" << missingFields.join(", ") << "in actionTypes" << at; + + QPair verificationResult = verifyFields(ActionType::jsonProperties(), ActionType::mandatoryJsonProperties(), at); + + // Check mandatory fields + if (!verificationResult.first.isEmpty()) { + qCWarning(dcDeviceManager()) << pluginName() << "Skipping device class" << deviceClass.name() << "because of missing" << verificationResult.first.join(", ") << "in action type:" << endl << at; + broken = true; + break; + } + + // Check if there are any unknown fields + if (!verificationResult.second.isEmpty()) { + qCWarning(dcDeviceManager()) << pluginName() << "Skipping device class" << deviceClass.name() << "because of unknown fields:" << verificationResult.second.join(", ") << "in action type:" << endl << at; broken = true; break; } @@ -715,6 +831,7 @@ void DevicePlugin::loadMetaData() actionType.setName(at.value("name").toString()); actionType.setDisplayName(translateValue(m_metaData.value("name").toString(), at.value("displayName").toString())); actionType.setIndex(index++); + QPair > paramVerification = parseParamTypes(at.value("paramTypes").toArray()); if (!paramVerification.first) { broken = true; @@ -731,13 +848,24 @@ void DevicePlugin::loadMetaData() index = 0; foreach (const QJsonValue &eventTypesJson, deviceClassObject.value("eventTypes").toArray()) { QJsonObject et = eventTypesJson.toObject(); - QStringList missingFields = verifyFields(QStringList() << "id" << "name" << "displayName", et); - if (!missingFields.isEmpty()) { - qCWarning(dcDeviceManager) << "Skipping device class" << deviceClass.name() << "because of missing" << missingFields.join(", ") << "in eventTypes:" << et; + + QPair verificationResult = verifyFields(EventType::jsonProperties(), EventType::mandatoryJsonProperties(), et); + + // Check mandatory fields + if (!verificationResult.first.isEmpty()) { + qCWarning(dcDeviceManager()) << pluginName() << "Skipping device class" << deviceClass.name() << "because of missing" << verificationResult.first.join(", ") << "in event type:" << endl << et; broken = true; break; } + // Check if there are any unknown fields + if (!verificationResult.second.isEmpty()) { + qCWarning(dcDeviceManager()) << pluginName() << "Skipping device class" << deviceClass.name() << "because of unknown fields:" << verificationResult.second.join(", ") << "in event type:" << endl << et; + broken = true; + break; + } + + EventType eventType(et.value("id").toString()); eventType.setName(et.value("name").toString()); eventType.setDisplayName(translateValue(m_metaData.value("name").toString(), et.value("displayName").toString())); @@ -764,11 +892,11 @@ void DevicePlugin::loadMetaData() if (deviceClassObject.contains("criticalStateTypeId")) { StateTypeId criticalStateTypeId = StateTypeId(deviceClassObject.value("criticalStateTypeId").toString()); if (!deviceClass.hasStateType(criticalStateTypeId)) { - qCWarning(dcDeviceManager) << "Skipping device class" << deviceClass.name() << ": the definend critical stateTypeId" << criticalStateTypeId.toString() << "does not match any StateType of this DeviceClass."; + qCWarning(dcDeviceManager()) << "Skipping device class" << deviceClass.name() << ": the definend critical stateTypeId" << criticalStateTypeId.toString() << "does not match any StateType of this DeviceClass."; broken = true; } else if (deviceClass.getStateType(criticalStateTypeId).type() != QVariant::Bool) { // Make sure the critical stateType is a bool state - qCWarning(dcDeviceManager) << "Skipping device class" << deviceClass.name() << ": the definend critical stateTypeId" << criticalStateTypeId.toString() << "is not a bool StateType."; + qCWarning(dcDeviceManager()) << "Skipping device class" << deviceClass.name() << ": the definend critical stateTypeId" << criticalStateTypeId.toString() << "is not a bool StateType."; broken = true; } else { deviceClass.setCriticalStateTypeId(criticalStateTypeId); @@ -778,7 +906,7 @@ void DevicePlugin::loadMetaData() if (deviceClassObject.contains("primaryStateTypeId")) { StateTypeId primaryStateTypeId = StateTypeId(deviceClassObject.value("primaryStateTypeId").toString()); if (!deviceClass.hasStateType(primaryStateTypeId)) { - qCWarning(dcDeviceManager) << "Skipping device class" << deviceClass.name() << ": the definend primary stateTypeId" << primaryStateTypeId.toString() << "does not match any StateType of this DeviceClass."; + qCWarning(dcDeviceManager()) << "Skipping device class" << deviceClass.name() << ": the definend primary stateTypeId" << primaryStateTypeId.toString() << "does not match any StateType of this DeviceClass."; broken = true; } else { deviceClass.setPrimaryStateTypeId(primaryStateTypeId); @@ -788,13 +916,14 @@ void DevicePlugin::loadMetaData() if (deviceClassObject.contains("primaryActionTypeId")) { ActionTypeId primaryActionTypeId = ActionTypeId(deviceClassObject.value("primaryActionTypeId").toString()); if (!deviceClass.hasActionType(primaryActionTypeId)) { - qCWarning(dcDeviceManager) << "Skipping device class" << deviceClass.name() << ": the definend primary actionTypeId" << primaryActionTypeId.toString() << "does not match any ActionType of this DeviceClass."; + qCWarning(dcDeviceManager()) << "Skipping device class" << deviceClass.name() << ": the definend primary actionTypeId" << primaryActionTypeId.toString() << "does not match any ActionType of this DeviceClass."; broken = true; } else { deviceClass.setPrimaryActionTypeId(primaryActionTypeId); } } + // Read interfaces QStringList interfaces; foreach (const QJsonValue &value, deviceClassObject.value("interfaces").toArray()) { Interface iface = loadInterface(value.toString()); @@ -802,28 +931,29 @@ void DevicePlugin::loadMetaData() StateTypes stateTypes(deviceClass.stateTypes()); ActionTypes actionTypes(deviceClass.actionTypes()); EventTypes eventTypes(deviceClass.eventTypes()); + bool valid = true; foreach (const StateType &ifaceStateType, iface.stateTypes()) { StateType stateType = stateTypes.findByName(ifaceStateType.name()); if (stateType.id().isNull()) { - qCWarning(dcDeviceManager) << "DeviceClass" << deviceClass.name() << "claims to implement interface" << value.toString() << "but doesn't implement state" << ifaceStateType.name(); + qCWarning(dcDeviceManager()) << "DeviceClass" << deviceClass.name() << "claims to implement interface" << value.toString() << "but doesn't implement state" << ifaceStateType.name(); valid = false; continue; } if (ifaceStateType.type() != stateType.type()) { - qCWarning(dcDeviceManager) << "DeviceClass" << deviceClass.name() << "claims to implement interface" << value.toString() << "but state" << stateType.name() << "has not matching type" << stateType.type() << "!=" << ifaceStateType.type(); + qCWarning(dcDeviceManager()) << "DeviceClass" << deviceClass.name() << "claims to implement interface" << value.toString() << "but state" << stateType.name() << "has not matching type" << stateType.type() << "!=" << ifaceStateType.type(); valid = false; continue; } if (ifaceStateType.minValue().isValid() && !ifaceStateType.minValue().isNull()) { if (ifaceStateType.minValue().toString() == "any") { if (stateType.minValue().isNull()) { - qCWarning(dcDeviceManager) << "DeviceClass" << deviceClass.name() << "claims to implement interface" << value.toString() << "but state" << stateType.name() << "has no minimum value defined."; + qCWarning(dcDeviceManager()) << "DeviceClass" << deviceClass.name() << "claims to implement interface" << value.toString() << "but state" << stateType.name() << "has no minimum value defined."; valid = false; continue; } } else if (ifaceStateType.minValue() != stateType.minValue()) { - qCWarning(dcDeviceManager) << "DeviceClass" << deviceClass.name() << "claims to implement interface" << value.toString() << "but state" << stateType.name() << "has not matching minimum value:" << ifaceStateType.minValue() << "!=" << stateType.minValue(); + qCWarning(dcDeviceManager()) << "DeviceClass" << deviceClass.name() << "claims to implement interface" << value.toString() << "but state" << stateType.name() << "has not matching minimum value:" << ifaceStateType.minValue() << "!=" << stateType.minValue(); valid = false; continue; } @@ -831,22 +961,23 @@ void DevicePlugin::loadMetaData() if (ifaceStateType.maxValue().isValid() && !ifaceStateType.maxValue().isNull()) { if (ifaceStateType.maxValue().toString() == "any") { if (stateType.maxValue().isNull()) { - qCWarning(dcDeviceManager) << "DeviceClass" << deviceClass.name() << "claims to implement interface" << value.toString() << "but state" << stateType.name() << "has no maximum value defined."; + qCWarning(dcDeviceManager()) << "DeviceClass" << deviceClass.name() << "claims to implement interface" << value.toString() << "but state" << stateType.name() << "has no maximum value defined."; valid = false; continue; } } else if (ifaceStateType.maxValue() != stateType.maxValue()) { - qCWarning(dcDeviceManager) << "DeviceClass" << deviceClass.name() << "claims to implement interface" << value.toString() << "but state" << stateType.name() << "has not matching maximum value:" << ifaceStateType.maxValue() << "!=" << stateType.minValue(); + qCWarning(dcDeviceManager()) << "DeviceClass" << deviceClass.name() << "claims to implement interface" << value.toString() << "but state" << stateType.name() << "has not matching maximum value:" << ifaceStateType.maxValue() << "!=" << stateType.minValue(); valid = false; continue; } } if (!ifaceStateType.possibleValues().isEmpty() && ifaceStateType.possibleValues() != stateType.possibleValues()) { - qCWarning(dcDeviceManager) << "DeviceClass" << deviceClass.name() << "claims to implement interface" << value.toString() << "but state" << stateType.name() << "has not matching allowed values" << ifaceStateType.possibleValues() << "!=" << stateType.possibleValues(); + qCWarning(dcDeviceManager()) << "DeviceClass" << deviceClass.name() << "claims to implement interface" << value.toString() << "but state" << stateType.name() << "has not matching allowed values" << ifaceStateType.possibleValues() << "!=" << stateType.possibleValues(); valid = false; continue; } } + foreach (const ActionType &ifaceActionType, iface.actionTypes()) { ActionType actionType = actionTypes.findByName(ifaceActionType.name()); if (actionType.id().isNull()) { @@ -866,6 +997,7 @@ void DevicePlugin::loadMetaData() } } } + foreach (const EventType &ifaceEventType, iface.eventTypes()) { EventType eventType = eventTypes.findByName(ifaceEventType.name()); if (!eventType.isValid()) { @@ -902,17 +1034,6 @@ void DevicePlugin::loadMetaData() } } -QStringList DevicePlugin::verifyFields(const QStringList &fields, const QJsonObject &value) const -{ - QStringList ret; - foreach (const QString &field, fields) { - if (!value.contains(field)) { - ret << field; - } - } - return ret; -} - QString DevicePlugin::translateValue(const QString &context, const QString &string) const { QString translation = m_translator->translate(context.toUtf8().constData(), string.toUtf8().constData()); diff --git a/libnymea/plugin/deviceplugin.h b/libnymea/plugin/deviceplugin.h index 47d686b3..94f9f996 100644 --- a/libnymea/plugin/deviceplugin.h +++ b/libnymea/plugin/deviceplugin.h @@ -108,7 +108,8 @@ private: QPair > parseParamTypes(const QJsonArray &array) const; - QStringList verifyFields(const QStringList &fields, const QJsonObject &value) const; + // Returns + QPair verifyFields(const QStringList &possibleFields, const QStringList &mandatoryFields, const QJsonObject &value) const; QString translateValue(const QString &context, const QString &string) const; diff --git a/libnymea/types/actiontype.cpp b/libnymea/types/actiontype.cpp index 83295ce5..36bc5e39 100644 --- a/libnymea/types/actiontype.cpp +++ b/libnymea/types/actiontype.cpp @@ -104,6 +104,18 @@ void ActionType::setParamTypes(const ParamTypes ¶mTypes) m_paramTypes = paramTypes; } +/*! Returns a list of all valid JSON properties a ActionType JSON definition can have. */ +QStringList ActionType::jsonProperties() +{ + return QStringList() << "id" << "name" << "displayName" << "paramTypes"; +} + +/*! Returns a list of mandatory JSON properties a ActionType JSON definition must have. */ +QStringList ActionType::mandatoryJsonProperties() +{ + return QStringList() << "id" << "name" << "displayName"; +} + ActionTypes::ActionTypes(const QList &other) { foreach (const ActionType &at, other) { diff --git a/libnymea/types/actiontype.h b/libnymea/types/actiontype.h index 838aa42d..67bf8bd7 100644 --- a/libnymea/types/actiontype.h +++ b/libnymea/types/actiontype.h @@ -49,6 +49,9 @@ public: ParamTypes paramTypes() const; void setParamTypes(const ParamTypes ¶mTypes); + static QStringList jsonProperties(); + static QStringList mandatoryJsonProperties(); + private: ActionTypeId m_id; QString m_name; diff --git a/libnymea/types/deviceclass.cpp b/libnymea/types/deviceclass.cpp index 7bd053be..071af13a 100644 --- a/libnymea/types/deviceclass.cpp +++ b/libnymea/types/deviceclass.cpp @@ -470,3 +470,18 @@ bool DeviceClass::operator==(const DeviceClass &deviceClass) const { return m_id == deviceClass.id(); } + +/*! Returns a list of all valid JSON properties a DeviceClass JSON definition can have. */ +QStringList DeviceClass::jsonProperties() +{ + return QStringList() << "id" << "name" << "displayName" << "createMethods" << "setupMethod" << "deviceIcon" + << "interfaces" << "basicTags" << "pairingInfo" << "criticalStateTypeId" + << "primaryStateTypeId" << "primaryActionTypeId" << "discoveryParamTypes" + << "discoveryParamTypes" << "paramTypes" << "stateTypes" << "actionTypes" << "eventTypes"; +} + +/*! Returns a list of mandatory JSON properties a DeviceClass JSON definition must have. */ +QStringList DeviceClass::mandatoryJsonProperties() +{ + return QStringList() << "id" << "name" << "displayName"; +} diff --git a/libnymea/types/deviceclass.h b/libnymea/types/deviceclass.h index 5f015c7c..88b94ca5 100644 --- a/libnymea/types/deviceclass.h +++ b/libnymea/types/deviceclass.h @@ -180,6 +180,9 @@ public: bool operator==(const DeviceClass &device) const; + static QStringList jsonProperties(); + static QStringList mandatoryJsonProperties(); + private: DeviceClassId m_id; VendorId m_vendorId; diff --git a/libnymea/types/eventtype.cpp b/libnymea/types/eventtype.cpp index 6de343be..f66b607f 100644 --- a/libnymea/types/eventtype.cpp +++ b/libnymea/types/eventtype.cpp @@ -130,6 +130,18 @@ bool EventType::isValid() const return !m_id.isNull() && !m_name.isEmpty(); } +/*! Returns a list of all valid JSON properties a EventType JSON definition can have. */ +QStringList EventType::jsonProperties() +{ + return QStringList() << "id" << "name" << "displayName" << "paramTypes" << "ruleRelevant" << "graphRelevant"; +} + +/*! Returns a list of mandatory JSON properties a EventType JSON definition must have. */ +QStringList EventType::mandatoryJsonProperties() +{ + return QStringList() << "id" << "name" << "displayName"; +} + EventTypes::EventTypes(const QList &other) { foreach (const EventType &at, other) { diff --git a/libnymea/types/eventtype.h b/libnymea/types/eventtype.h index 5626be11..84799d52 100644 --- a/libnymea/types/eventtype.h +++ b/libnymea/types/eventtype.h @@ -56,6 +56,10 @@ public: void setGraphRelevant(const bool &graphRelevant); bool isValid() const; + + static QStringList jsonProperties(); + static QStringList mandatoryJsonProperties(); + private: EventTypeId m_id; QString m_name; diff --git a/libnymea/types/paramtype.cpp b/libnymea/types/paramtype.cpp index 6cfe81d1..2ccbab13 100644 --- a/libnymea/types/paramtype.cpp +++ b/libnymea/types/paramtype.cpp @@ -211,7 +211,20 @@ bool ParamType::isValid() const return !m_id.isNull() && !m_name.isEmpty() && m_type != QVariant::Invalid; } -/*! Writes the name, type defaultValue, min value, max value and readOnly of the given \a paramType to \a dbg. */ +/*! Returns a list of all valid JSON properties a ParamType JSON definition can have. */ +QStringList ParamType::jsonProperties() +{ + return QStringList() << "id" << "name" << "displayName" << "type" << "defaultValue" << "inputType" + << "unit" << "minValue" << "maxValue" << "allowedValues" << "readOnly"; +} + +/*! Returns a list of mandatory JSON properties a ParamType JSON definition must have. */ +QStringList ParamType::mandatoryJsonProperties() +{ + return QStringList() << "id" << "name" << "displayName" << "type" << "defaultValue"; +} + +/*! Writes the name, type, defaultValue, min value, max value and readOnly of the given \a paramType to \a dbg. */ QDebug operator<<(QDebug dbg, const ParamType ¶mType) { dbg.nospace() << "ParamType(Id" << paramType.id() diff --git a/libnymea/types/paramtype.h b/libnymea/types/paramtype.h index cea75070..3ee871f8 100644 --- a/libnymea/types/paramtype.h +++ b/libnymea/types/paramtype.h @@ -76,6 +76,9 @@ public: bool isValid() const; + static QStringList jsonProperties(); + static QStringList mandatoryJsonProperties(); + private: ParamTypeId m_id; QString m_name; diff --git a/libnymea/types/statetype.cpp b/libnymea/types/statetype.cpp index eddcbab7..8f91e4dc 100644 --- a/libnymea/types/statetype.cpp +++ b/libnymea/types/statetype.cpp @@ -196,6 +196,20 @@ void StateType::setCached(bool cached) m_cached = cached; } +/*! Returns a list of all valid JSON properties a DeviceClass JSON definition can have. */ +QStringList StateType::jsonProperties() +{ + return QStringList() << "id" << "name" << "displayName" << "displayNameEvent" << "type" << "defaultValue" + << "cached" << "ruleRelevant" << "eventRuleRelevant" << "graphRelevant" << "unit" + << "minValue" << "maxValue" << "possibleValues" << "writable" << "displayNameAction"; +} + +/*! Returns a list of mandatory JSON properties a DeviceClass JSON definition must have. */ +QStringList StateType::mandatoryJsonProperties() +{ + return QStringList() << "id" << "name" << "displayName" << "displayNameEvent" << "type" << "defaultValue"; +} + StateTypes::StateTypes(const QList &other) { foreach (const StateType &st, other) { diff --git a/libnymea/types/statetype.h b/libnymea/types/statetype.h index 04bc45a5..6c7b7d55 100644 --- a/libnymea/types/statetype.h +++ b/libnymea/types/statetype.h @@ -73,6 +73,9 @@ public: bool cached() const; void setCached(bool cached); + static QStringList jsonProperties(); + static QStringList mandatoryJsonProperties(); + private: StateTypeId m_id; QString m_name; diff --git a/plugins/nymea-generateplugininfo b/plugins/nymea-generateplugininfo index 0723fd83..ff1bb457 100755 --- a/plugins/nymea-generateplugininfo +++ b/plugins/nymea-generateplugininfo @@ -4,7 +4,7 @@ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # -# Copyright (C) 2015-2017 Simon Stuerz # +# Copyright (C) 2015-2018 Simon Stuerz # # Copyright (C) 2014 Michael Zanetti # # # # This file is part of nymea. # @@ -29,7 +29,7 @@ import json import os import subprocess -__version__='1.0.1' +__version__='1.0.2' ################################################################################################################## # Methods @@ -63,11 +63,12 @@ def extractPlugin(pluginMap): printInfo('Define PluginId pluginId = %s' % (pluginMap['id'])) if args.filetype is 'i': writeToFile('PluginId pluginId = PluginId(\"%s\");' % (pluginMap['id'])) - addTranslationString(pluginMap['name'], 'The name of the plugin %s (%s)' % (pluginMap['name'], pluginMap['id'])) + addTranslationString(pluginMap['displayName'], 'The name of the plugin %s (%s)' % (pluginMap['name'], pluginMap['id'])) createExternDefinition('PluginId', variableName) + # Extract plugin params (configurations) if 'paramTypes' in pluginMap: - extractParamTypes(pluginMap['paramTypes'], pluginMap['name']) + extractParamTypes(pluginMap['paramTypes'], pluginMap['name'][0].lower() + pluginMap['name'][1:]) if 'vendors' in pluginMap: extractVendors(pluginMap['vendors']) @@ -178,20 +179,21 @@ def extractStateTypes(stateTypes, deviceClassName): else: printWarning('Duplicated variable name \"%s\" for autocreated EventTypeId %s -> skipping' % (variableName, stateType['id'])) - #ParamType for EventType/ActionType - variableName = '%s%sStateParamTypeId' % (deviceClassName, stateType['name'][0].capitalize() + stateType['name'][1:]) + # ParamType for the autocreated EventType + variableName = '%s%sEventParamTypeId' % (deviceClassName, stateType['name'][0].capitalize() + stateType['name'][1:]) if not variableName in variableNames: variableNames.append(variableName) printInfo('Define ParamTypeId %s for StateType %s = %s' % (variableName, variableName, stateType['id'])) + addTranslationString(stateType['displayName'], 'The name of the ParamType of the autocreated EventType (%s) of DeviceClass %s' % (stateType['id'], deviceClassName)) if args.filetype is 'i': writeToFile('ParamTypeId %s = ParamTypeId(\"%s\");' % (variableName, stateType['id'])) createExternDefinition('ParamTypeId', variableName) - addTranslationString(stateType['name'], 'The name of the ParamType of StateType (%s) of DeviceClass %s' % (stateType['id'], deviceClassName)) else: printWarning('Duplicated variable name \"%s\" for ParamTypeId %s -> skipping' % (variableName, stateType['id'])) - # Create ActionTypeId if the state is writable + # Create ActionTypeId and ParamTypeId for action if the state is writable if 'writable' in stateType and stateType['writable']: + # Create ActionType for the writable state variableName = '%s%sActionTypeId' % (deviceClassName, stateType['name'][0].capitalize() + stateType['name'][1:]) if not variableName in variableNames: variableNames.append(variableName) @@ -203,6 +205,18 @@ def extractStateTypes(stateTypes, deviceClassName): else: printWarning('Duplicated variable name \"%s\" for autocreated ActionTypeId %s -> skipping' % (variableName, stateType['id'])) + # ParamType for the autocreated ActionType + variableName = '%s%sActionParamTypeId' % (deviceClassName, stateType['name'][0].capitalize() + stateType['name'][1:]) + if not variableName in variableNames: + variableNames.append(variableName) + printInfo('Define ParamTypeId %s for autocreated ActionType %s = %s' % (variableName, variableName, stateType['id'])) + addTranslationString(stateType['displayName'], 'The name of the autocreated ParamType of the writable StateType (%s) of DeviceClass %s' % (stateType['$ + if args.filetype is 'i': + writeToFile('ParamTypeId %s = ParamTypeId(\"%s\");' % (variableName, stateType['id'])) + createExternDefinition('ParamTypeId', variableName) + else: + printWarning('Duplicated variable name \"%s\" for ParamTypeId %s -> skipping' % (variableName, stateType['id'])) + except: pass @@ -288,7 +302,7 @@ def createTranslationFiles(): path, fileName = os.path.split(translationFile) translationOutput = (path + '/' + pluginMap['id'] + '-' + os.path.splitext(fileName)[0] + '.qm') printInfo(' --> Translation update %s' % translationFile) - printInfo(subprocess.check_output(['mkdir', '-p', path])) + printInfo(subprocess.check_output(['mkdir', '-p', path])) printInfo(subprocess.check_output(['lupdate', '-recursive', '-no-obsolete', sourceDir, (args.builddir + '/' + args.output), '-ts', translationFile])) printInfo(' --> Translation release %s' % translationOutput) printInfo(subprocess.check_output(['lrelease', translationFile, '-qm', translationOutput])) @@ -407,7 +421,7 @@ if __name__ == '__main__': except: printError('Could not open file \"%s\"' % (args.jsonfile)) exit -1 - + try: outputFile = open(args.builddir + '/' + args.output, 'w') except: