/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Copyright 2013 - 2020, nymea GmbH * Contact: contact@nymea.io * * This file is part of nymea. * This project including source code and documentation is protected by * copyright law, and remains the property of nymea GmbH. All rights, including * reproduction, publication, editing and translation, are reserved. The use of * this project is subject to the terms of a license agreement to be concluded * with nymea GmbH in accordance with the terms of use of nymea GmbH, available * under https://nymea.io/license * * GNU Lesser General Public License Usage * Alternatively, this project may be redistributed and/or modified under the * terms of the GNU Lesser General Public License as published by the Free * Software Foundation; version 3. This project is distributed in the hope that * it will be useful, but WITHOUT ANY WARRANTY; without even the implied * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this project. If not, see . * * For any further details and any questions please contact us under * contact@nymea.io or see our FAQ/Licensing Information on * https://nymea.io/license/faq * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include "thingutils.h" #include "loggingcategories.h" #include #include #include #include ThingUtils::ThingUtils() { } /*! Verify if the given \a params matches the given \a paramTypes.*/ Thing::ThingError ThingUtils::verifyParams(const QList paramTypes, const ParamList ¶ms) { foreach (const Param ¶m, params) { Thing::ThingError result = verifyParam(paramTypes, param); if (result != Thing::ThingErrorNoError) { return result; } } foreach (const ParamType ¶mType, paramTypes) { bool found = !paramType.defaultValue().isNull(); foreach (const Param ¶m, params) { if (paramType.id() == param.paramTypeId()) { found = true; break; } } if (!found) { qCWarning(dcThing) << "Missing parameter:" << paramType.name() << params; return Thing::ThingErrorMissingParameter; } } return Thing::ThingErrorNoError; } /*! Verify if the given \a param matches one of the given \a paramTypes. Returns \l{Device::DeviceError} to inform about the result.*/ Thing::ThingError ThingUtils::verifyParam(const QList paramTypes, const Param ¶m) { foreach (const ParamType ¶mType, paramTypes) { if (paramType.id() == param.paramTypeId()) { return verifyParam(paramType, param); } } qCWarning(dcThing) << "Invalid parameter" << param.paramTypeId().toString() << "in parameter list"; return Thing::ThingErrorInvalidParameter; } /*! Verify if the given \a param matches the given \a paramType. Returns \l{Device::DeviceError} to inform about the result.*/ Thing::ThingError ThingUtils::verifyParam(const ParamType ¶mType, const Param ¶m) { if (paramType.id() != param.paramTypeId()) { qCWarning(dcThing) << "Parameter id" << param.paramTypeId().toString() << "does not match with ParamType id" << paramType.id().toString(); return Thing::ThingErrorInvalidParameter; } if (!param.value().canConvert(static_cast(paramType.type()))) { qCWarning(dcThing) << "Wrong parameter type for param" << param.paramTypeId().toString() << " Got:" << param.value() << " Expected:" << QVariant::typeToName(static_cast(paramType.type())); return Thing::ThingErrorInvalidParameter; } if (!param.value().convert(static_cast(paramType.type()))) { qCWarning(dcThing) << "Could not convert value of param" << param.paramTypeId().toString() << " to:" << QVariant::typeToName(static_cast(paramType.type())) << " Got:" << param.value(); return Thing::ThingErrorInvalidParameter; } if (paramType.type() == QVariant::Int) { if (paramType.maxValue().isValid() && param.value().toInt() > paramType.maxValue().toInt()) { qCWarning(dcThing) << "Value out of range for param" << param.paramTypeId().toString() << " Got:" << param.value() << " Max:" << paramType.maxValue(); return Thing::ThingErrorInvalidParameter; } if (paramType.minValue().isValid() && param.value().toInt() < paramType.minValue().toInt()) { qCWarning(dcThing) << "Value out of range for param" << param.paramTypeId().toString() << " Got:" << param.value() << " Min:" << paramType.minValue(); return Thing::ThingErrorInvalidParameter; } } else if (paramType.type() == QVariant::UInt) { if (paramType.maxValue().isValid() && param.value().toUInt() > paramType.maxValue().toUInt()) { qCWarning(dcThing) << "Value out of range for param" << param.paramTypeId().toString() << " Got:" << param.value() << " Max:" << paramType.maxValue(); return Thing::ThingErrorInvalidParameter; } if (paramType.minValue().isValid() && param.value().toUInt() < paramType.minValue().toUInt()) { qCWarning(dcThing) << "Value out of range for param" << param.paramTypeId().toString() << " Got:" << param.value() << " Min:" << paramType.minValue(); return Thing::ThingErrorInvalidParameter; } } else if (paramType.type() == QVariant::Double) { if (paramType.maxValue().isValid() && param.value().toDouble() > paramType.maxValue().toDouble()) { qCWarning(dcThing) << "Value out of range for param" << param.paramTypeId().toString() << " Got:" << param.value() << " Max:" << paramType.maxValue(); return Thing::ThingErrorInvalidParameter; } if (paramType.minValue().isValid() && param.value().toDouble() < paramType.minValue().toDouble()) { qCWarning(dcThing) << "Value out of range for param" << param.paramTypeId().toString() << " Got:" << param.value() << " Min:" << paramType.minValue(); return Thing::ThingErrorInvalidParameter; } } else { if (paramType.maxValue().isValid() && param.value() > paramType.maxValue()) { qCWarning(dcThing) << "Value out of range for param" << param.paramTypeId().toString() << " Got:" << param.value() << " Max:" << paramType.maxValue(); return Thing::ThingErrorInvalidParameter; } if (paramType.minValue().isValid() && param.value() < paramType.minValue()) { qCWarning(dcThing) << "Value out of range for param" << param.paramTypeId().toString() << " Got:" << param.value() << " Min:" << paramType.minValue(); return Thing::ThingErrorInvalidParameter; } } if (!paramType.allowedValues().isEmpty() && !paramType.allowedValues().contains(param.value())) { QStringList allowedValues; foreach (const QVariant &value, paramType.allowedValues()) { allowedValues.append(value.toString()); } qCWarning(dcThing) << "Value not in allowed values for param" << param.paramTypeId().toString() << " Got:" << param.value() << " Allowed:" << allowedValues.join(","); return Thing::ThingErrorInvalidParameter; } return Thing::ThingErrorNoError; } Interfaces ThingUtils::allInterfaces() { Interfaces ret; QDir dir(":/interfaces/"); foreach (const QFileInfo &ifaceFile, dir.entryInfoList()) { ret.append(loadInterface(ifaceFile.baseName())); } return ret; } Interface ThingUtils::loadInterface(const QString &name) { Interface iface; QFile f(QString(":/interfaces/%1.json").arg(name)); if (!f.open(QFile::ReadOnly)) { qCWarning(dcThingManager()) << "Failed to load interface" << name; return iface; } QJsonParseError error; QJsonDocument jsonDoc = QJsonDocument::fromJson(f.readAll(), &error); if (error.error != QJsonParseError::NoError) { qCWarning(dcThingManager) << "Cannot load interface definition for interface" << name << ":" << error.errorString(); return iface; } QVariantMap content = jsonDoc.toVariant().toMap(); if (content.contains("extends")) { if (!content.value("extends").toString().isEmpty()) { iface = loadInterface(content.value("extends").toString()); } else if (content.value("extends").toList().count() > 0) { foreach (const QVariant &extendedIface, content.value("extends").toList()) { Interface tmp = loadInterface(extendedIface.toString()); iface = mergeInterfaces(iface, tmp); } } } InterfaceStateTypes stateTypes; InterfaceActionTypes actionTypes; InterfaceEventTypes eventTypes; foreach (const QVariant &stateVariant, content.value("states").toList()) { QVariantMap stateMap = stateVariant.toMap(); InterfaceStateType stateType; stateType.setName(stateMap.value("name").toString()); stateType.setType(QVariant::nameToType(stateMap.value("type").toByteArray())); stateType.setPossibleValues(stateMap.value("allowedValues").toList()); stateType.setMinValue(stateMap.value("minValue")); stateType.setMaxValue(stateMap.value("maxValue")); stateType.setOptional(stateMap.value("optional", false).toBool()); if (stateMap.contains("unit")) { QMetaEnum unitEnum = QMetaEnum::fromType(); int enumValue = unitEnum.keyToValue("Unit" + stateMap.value("unit").toByteArray()); if (enumValue == -1) { qCWarning(dcThingManager) << "Invalid unit" << stateMap.value("unit").toString() << "in interface" << name; } else { stateType.setUnit(static_cast(enumValue)); } } ParamType stateChangeActionParamType; stateChangeActionParamType.setName(stateType.name()); stateChangeActionParamType.setType(stateType.type()); stateChangeActionParamType.setAllowedValues(stateType.possibleValues()); stateChangeActionParamType.setMinValue(stateType.minValue()); stateChangeActionParamType.setMaxValue(stateType.maxValue()); if (stateMap.value("writable", false).toBool()) { InterfaceActionType stateChangeActionType; stateChangeActionType.setName(stateType.name()); stateChangeActionType.setOptional(stateType.optional()); stateChangeActionType.setParamTypes(ParamTypes() << stateChangeActionParamType); actionTypes.append(stateChangeActionType); } if (stateMap.contains("logged")) { stateType.setLoggingOverride(true); stateType.setSuggestLogging(stateMap.value("logged", false).toBool()); } stateTypes.append(stateType); } foreach (const QVariant &actionVariant, content.value("actions").toList()) { InterfaceActionType actionType; actionType.setName(actionVariant.toMap().value("name").toString()); actionType.setOptional(actionVariant.toMap().value("optional").toBool()); // if (actionVariant.toMap().contains("logged")) { // actionType.setLoggingOverride(true); // actionType.setSuggestLogging(actionVariant.toMap().value("logged").toBool()); // } ParamTypes paramTypes; foreach (const QVariant &actionParamVariant, actionVariant.toMap().value("params").toList()) { ParamType paramType; paramType.setName(actionParamVariant.toMap().value("name").toString()); paramType.setType(QVariant::nameToType(actionParamVariant.toMap().value("type").toByteArray())); paramType.setAllowedValues(actionParamVariant.toMap().value("allowedValues").toList()); paramType.setMinValue(actionParamVariant.toMap().value("min")); paramType.setDefaultValue(actionParamVariant.toMap().value("defaultValue")); paramTypes.append(paramType); } actionType.setParamTypes(paramTypes); actionTypes.append(actionType); } foreach (const QVariant &eventVariant, content.value("events").toList()) { InterfaceEventType eventType; eventType.setName(eventVariant.toMap().value("name").toString()); eventType.setOptional(eventVariant.toMap().value("optional").toBool()); if (eventVariant.toMap().contains("logged")) { eventType.setLoggingOverride(true); eventType.setSuggestLogging(eventVariant.toMap().value("logged").toBool()); } ParamTypes paramTypes; foreach (const QVariant &eventParamVariant, eventVariant.toMap().value("params").toList()) { ParamType paramType; paramType.setName(eventParamVariant.toMap().value("name").toString()); paramType.setType(QVariant::nameToType(eventParamVariant.toMap().value("type").toByteArray())); paramType.setAllowedValues(eventParamVariant.toMap().value("allowedValues").toList()); paramType.setMinValue(eventParamVariant.toMap().value("minValue")); paramType.setMaxValue(eventParamVariant.toMap().value("maxValue")); paramType.setDefaultValue(eventParamVariant.toMap().value("defaultValue")); paramTypes.append(paramType); } eventType.setParamTypes(paramTypes); eventTypes.append(eventType); } return Interface(name, iface.actionTypes() << actionTypes, iface.eventTypes() << eventTypes, iface.stateTypes() << stateTypes); } Interface ThingUtils::mergeInterfaces(const Interface &iface1, const Interface &iface2) { InterfaceEventTypes eventTypes = iface1.eventTypes(); foreach (const InterfaceEventType &et, iface2.eventTypes()) { if (eventTypes.findByName(et.name()).name().isEmpty()) { eventTypes.append(et); } } InterfaceStateTypes stateTypes = iface1.stateTypes(); foreach (const InterfaceStateType &st, iface2.stateTypes()) { if (stateTypes.findByName(st.name()).name().isEmpty()) { stateTypes.append(st); } } InterfaceActionTypes actionTypes = iface1.actionTypes(); foreach (const InterfaceActionType &at, iface2.actionTypes()) { if (actionTypes.findByName(at.name()).name().isEmpty()) { actionTypes.append(at); } } return Interface(QString(), actionTypes, eventTypes, stateTypes); } QStringList ThingUtils::generateInterfaceParentList(const QString &interface) { QFile f(QString(":/interfaces/%1.json").arg(interface)); if (!f.open(QFile::ReadOnly)) { qCWarning(dcThingManager()) << "Failed to load interface" << interface; return QStringList(); } QJsonParseError error; QJsonDocument jsonDoc = QJsonDocument::fromJson(f.readAll(), &error); if (error.error != QJsonParseError::NoError) { qCWarning(dcThingManager) << "Cannot load interface definition for interface" << interface << ":" << error.errorString(); return QStringList(); } QStringList ret = {interface}; QVariantMap content = jsonDoc.toVariant().toMap(); if (content.contains("extends")) { if (!content.value("extends").toString().isEmpty()) { ret << generateInterfaceParentList(content.value("extends").toString()); } else if (content.value("extends").toList().count() > 0) { foreach (const QVariant &extendedIface, content.value("extends").toList()) { ret << generateInterfaceParentList(extendedIface.toString()); } } } return ret; }