diff --git a/libguh/devicemanager.cpp b/libguh/devicemanager.cpp index f818ca55..5fe1deed 100644 --- a/libguh/devicemanager.cpp +++ b/libguh/devicemanager.cpp @@ -195,6 +195,7 @@ #include #include #include +#include /*! Constructs the DeviceManager with the given \a locale and \a parent. There should only be one DeviceManager in the system created by \l{guhserver::GuhCore}. * Use \c guhserver::GuhCore::instance()->deviceManager() instead to access the DeviceManager. */ diff --git a/libguh/interfaces/colorlight.json b/libguh/interfaces/colorlight.json new file mode 100644 index 00000000..4054ad69 --- /dev/null +++ b/libguh/interfaces/colorlight.json @@ -0,0 +1,15 @@ +{ + "extends": "dimmablelight", + "states": [ + { + "name": "color temperature", + "type": "int", + "minValue": "any", + "maxValue": "any" + }, + { + "name": "color", + "type": "QColor" + } + ] +} diff --git a/libguh/interfaces/dimmablelight.json b/libguh/interfaces/dimmablelight.json new file mode 100644 index 00000000..1ff6aee0 --- /dev/null +++ b/libguh/interfaces/dimmablelight.json @@ -0,0 +1,12 @@ +{ + "extends": "light", + "states": [ + { + "name": "brightness", + "type": "int", + "minimumValue": 0, + "maximumValue": 100, + "writable": true + } + ] +} diff --git a/libguh/interfaces/garagegate.json b/libguh/interfaces/garagegate.json new file mode 100644 index 00000000..6bfb02d0 --- /dev/null +++ b/libguh/interfaces/garagegate.json @@ -0,0 +1,20 @@ +{ + "states": [ + { + "name": "gateState", + "type": "String", + "allowedValues": ["open", "closed", "opening", "closing"] + } + ], + "actions": [ + { + "name": "open" + }, + { + "name": "close" + }, + { + "name": "stop" + } + ] +} diff --git a/libguh/interfaces/interfaces.qrc b/libguh/interfaces/interfaces.qrc new file mode 100644 index 00000000..99218d01 --- /dev/null +++ b/libguh/interfaces/interfaces.qrc @@ -0,0 +1,9 @@ + + + mediacontroller.json + light.json + dimmablelight.json + colorlight.json + garagegate.json + + diff --git a/libguh/interfaces/light.json b/libguh/interfaces/light.json new file mode 100644 index 00000000..af95d791 --- /dev/null +++ b/libguh/interfaces/light.json @@ -0,0 +1,9 @@ +{ + "states": [ + { + "name": "power", + "type": "bool" + } + + ] +} diff --git a/libguh/interfaces/mediacontroller.json b/libguh/interfaces/mediacontroller.json new file mode 100644 index 00000000..83a95843 --- /dev/null +++ b/libguh/interfaces/mediacontroller.json @@ -0,0 +1,46 @@ +{ + "states": [ + { + "name": "mute", + "type": "bool", + "writable": true + }, + { + "name": "volume", + "type": "int", + "minValue": 0, + "maxValue": 100, + "writable": true + }, + { + "name": "playbackStatus", + "type": "string", + "allowedValues": ["Playing", "Paused", "Stopped"], + "writable": true + } + + ], + "actions": [ + { + "name": "skipBack" + }, + { + "name": "rewind" + }, + { + "name": "stop" + }, + { + "name": "play" + }, + { + "name": "pause" + }, + { + "name": "fastForward" + }, + { + "name": "skipNext" + } + ] +} diff --git a/libguh/libguh.pro b/libguh/libguh.pro index 1024463d..fc4d2715 100644 --- a/libguh/libguh.pro +++ b/libguh/libguh.pro @@ -141,3 +141,9 @@ for(header, HEADERS) { eval(headers_$${path}.path = $${path}) eval(INSTALLS *= headers_$${path}) } + +DISTFILES += \ + interfaces/mediacontroller.json + +RESOURCES += \ + interfaces/interfaces.qrc diff --git a/libguh/plugin/deviceclass.cpp b/libguh/plugin/deviceclass.cpp index c42af452..fe25819d 100644 --- a/libguh/plugin/deviceclass.cpp +++ b/libguh/plugin/deviceclass.cpp @@ -437,6 +437,16 @@ void DeviceClass::setPairingInfo(const QString &pairingInfo) m_pairingInfo = pairingInfo; } +QStringList DeviceClass::interfaces() const +{ + return m_interfaces; +} + +void DeviceClass::setInterfaces(const QStringList &interfaces) +{ + m_interfaces = interfaces; +} + /*! Compare this \a deviceClass to another. This is effectively the same as calling a.id() == b.id(). Returns true if the ids match.*/ bool DeviceClass::operator==(const DeviceClass &deviceClass) const { diff --git a/libguh/plugin/deviceclass.h b/libguh/plugin/deviceclass.h index cc5d0506..a483e1e3 100644 --- a/libguh/plugin/deviceclass.h +++ b/libguh/plugin/deviceclass.h @@ -172,6 +172,9 @@ public: QString pairingInfo() const; void setPairingInfo(const QString &pairingInfo); + QStringList interfaces() const; + void setInterfaces(const QStringList &interfaces); + bool operator==(const DeviceClass &device) const; private: @@ -192,6 +195,7 @@ private: CreateMethods m_createMethods; SetupMethod m_setupMethod; QString m_pairingInfo; + QStringList m_interfaces; }; Q_DECLARE_OPERATORS_FOR_FLAGS(DeviceClass::CreateMethods) diff --git a/libguh/plugin/deviceplugin.cpp b/libguh/plugin/deviceplugin.cpp index 36f12e6a..85242645 100644 --- a/libguh/plugin/deviceplugin.cpp +++ b/libguh/plugin/deviceplugin.cpp @@ -149,6 +149,7 @@ #include #include #include +#include /*! DevicePlugin constructor. DevicePlugins will be instantiated by the DeviceManager, its \a parent. */ DevicePlugin::DevicePlugin(QObject *parent): @@ -463,6 +464,75 @@ QList DevicePlugin::supportedDevices() const } } + QStringList interfaces; + foreach (const QJsonValue &value, deviceClassObject.value("interfaces").toArray()) { + // TODO: Check interfaces for completeness + QVariantMap interfaceMap = loadInterface(value.toString()); + QVariantList states = interfaceMap.value("states").toList(); + + StateTypes stateTypes(deviceClass.stateTypes()); + ActionTypes actionTypes(deviceClass.actionTypes()); + EventTypes eventTypes(deviceClass.eventTypes()); + bool valid = true; + foreach (const QVariant &stateVariant, states) { + StateType stateType = stateTypes.findByName(stateVariant.toMap().value("name").toString()); + QVariantMap stateMap = stateVariant.toMap(); + if (stateType.id().isNull()) { + qCWarning(dcDeviceManager) << "DeviceClass" << deviceClass.name() << "claims to implement interface" << value.toString() << "but doesn't implement state" << stateMap.value("name").toString(); + valid = false; + continue; + } + if (QVariant::nameToType(stateMap.value("type").toByteArray().data()) != stateType.type()) { + qCWarning(dcDeviceManager) << "DeviceClass" << deviceClass.name() << "claims to implement interface" << value.toString() << "but state" << stateMap.value("name").toString() << "has not matching type" << stateMap.value("type").toString(); + valid = false; + continue; + } + if (stateMap.contains("minimumValue") && stateMap.value("minimumValue") != stateType.minValue()) { + qCWarning(dcDeviceManager) << "DeviceClass" << deviceClass.name() << "claims to implement interface" << value.toString() << "but state" << stateMap.value("name").toString() << "has not matching minimum value" << stateMap.value("minimumValue") << "!=" << stateType.minValue(); + valid = false; + continue; + } + if (stateMap.contains("maximumValue") && stateMap.value("maximumValue") != stateType.maxValue()) { + qCWarning(dcDeviceManager) << "DeviceClass" << deviceClass.name() << "claims to implement interface" << value.toString() << "but state" << stateMap.value("name").toString() << "has not matching allowed value" << stateMap.value("maximumValue") << "!=" << stateType.maxValue(); + valid = false; + continue; + } + if (stateMap.contains("allowedValues") && stateMap.value("allowedValues") != stateType.possibleValues()) { + qCWarning(dcDeviceManager) << "DeviceClass" << deviceClass.name() << "claims to implement interface" << value.toString() << "but state" << stateMap.value("name").toString() << "has not matching allowed values" << stateMap.value("allowedValues") << "!=" << stateType.possibleValues(); + valid = false; + continue; + } + if (stateMap.contains("writable") && stateMap.value("writable").toBool() && actionTypes.findById(ActionTypeId(stateType.id().toString())).id().isNull()) { + qCWarning(dcDeviceManager) << "DeviceClass" << deviceClass.name() << "claims to implement interface" << value.toString() << "but state" << stateMap.value("name").toString() << "is not writable while it should be"; + valid = false; + continue; + } + } + QVariantList actions = interfaceMap.value("actions").toList(); + foreach (const QVariant &actionVariant, actions) { + QVariantMap actionMap = actionVariant.toMap(); + if (actionTypes.findByName(actionMap.value("name").toString()).id().isNull()) { + qCWarning(dcDeviceManager) << "DeviceClass" << deviceClass.name() << "claims to implement interface" << value.toString() << "but doesn't implement action" << actionMap.value("name").toString(); + valid = false; + } + // TODO: check params + } + QVariantList events = interfaceMap.value("events").toList(); + foreach (const QVariant &eventVariant, events) { + QVariantMap eventMap = eventVariant.toMap(); + if (eventTypes.findByName(eventMap.value("name").toString()).id().isNull()) { + qCWarning(dcDeviceManager) << "DeviceClass" << deviceClass.name() << "claims to implement interface" << value.toString() << "but doesn't implement event" << eventMap.value("name").toString(); + valid = false; + } + // TODO: check params + } + + if (valid) { + interfaces.append(value.toString()); + } + } + deviceClass.setInterfaces(interfaces); + if (!broken) { deviceClasses.append(deviceClass); } else { @@ -1027,3 +1097,35 @@ QPair DevicePlugin::loadAndVerifyDeviceIcon(const return QPair(true, (DeviceClass::DeviceIcon)enumValue); } + +QVariantMap DevicePlugin::loadInterface(const QString &name) const +{ + QFile f(QString(":/interfaces/%1.json").arg(name)); + if (!f.open(QFile::ReadOnly)) { + qCWarning(dcDeviceManager()) << "Failed to load interface" << name; + return QVariantMap(); + } + QJsonParseError error; + QJsonDocument jsonDoc = QJsonDocument::fromJson(f.readAll(), &error); + if (error.error != QJsonParseError::NoError) { + qCWarning(dcDeviceManager) << "Cannot load interface definition for interface" << name << ":" << error.errorString(); + return QVariantMap(); + } + QVariantMap content = jsonDoc.toVariant().toMap(); + if (content.contains("extends")) { + QVariantMap parentContent = loadInterface(content.value("extends").toString()); + + QVariantList statesList = content.value("states").toList(); + statesList.append(parentContent.value("states").toList()); + content["states"] = statesList; + + QVariantList actionsList = content.value("actions").toList(); + actionsList.append(parentContent.value("actions").toList()); + content["actions"] = actionsList; + + QVariantList eventsList = content.value("events").toList(); + eventsList.append(parentContent.value("events").toList()); + content["events"] = eventsList; + } + return content; +} diff --git a/libguh/plugin/deviceplugin.h b/libguh/plugin/deviceplugin.h index 97ad514e..5afbb865 100644 --- a/libguh/plugin/deviceplugin.h +++ b/libguh/plugin/deviceplugin.h @@ -146,6 +146,8 @@ private: QPair loadAndVerifyBasicTag(const QString &basicTag) const; QPair loadAndVerifyDeviceIcon(const QString &deviceIcon) const; + QVariantMap loadInterface(const QString &name) const; + QTranslator *m_translator; DeviceManager *m_deviceManager; diff --git a/libguh/types/actiontype.cpp b/libguh/types/actiontype.cpp index 98e81b9c..bbe34188 100644 --- a/libguh/types/actiontype.cpp +++ b/libguh/types/actiontype.cpp @@ -91,3 +91,30 @@ void ActionType::setParamTypes(const QList ¶mTypes) { m_paramTypes = paramTypes; } + +ActionTypes::ActionTypes(const QList &other) +{ + foreach (const ActionType &at, other) { + append(at); + } +} + +ActionType ActionTypes::findByName(const QString &name) +{ + foreach (const ActionType &actionType, *this) { + if (actionType.name() == name) { + return actionType; + } + } + return ActionType(ActionTypeId()); +} + +ActionType ActionTypes::findById(const ActionTypeId &id) +{ + foreach (const ActionType &actionType, *this) { + if (actionType.id() == id) { + return actionType; + } + } + return ActionType(ActionTypeId()); +} diff --git a/libguh/types/actiontype.h b/libguh/types/actiontype.h index 1c8684cb..482438d4 100644 --- a/libguh/types/actiontype.h +++ b/libguh/types/actiontype.h @@ -53,4 +53,12 @@ private: QList m_paramTypes; }; +class ActionTypes: public QList +{ +public: + ActionTypes(const QList &other); + ActionType findByName(const QString &name); + ActionType findById(const ActionTypeId &id); +}; + #endif // ACTIONTYPE_H diff --git a/libguh/types/eventtype.cpp b/libguh/types/eventtype.cpp index 145d0aa3..2bd50a82 100644 --- a/libguh/types/eventtype.cpp +++ b/libguh/types/eventtype.cpp @@ -111,3 +111,30 @@ void EventType::setGraphRelevant(const bool &graphRelevant) { m_graphRelevant = graphRelevant; } + +EventTypes::EventTypes(const QList &other) +{ + foreach (const EventType &at, other) { + append(at); + } +} + +EventType EventTypes::findByName(const QString &name) +{ + foreach (const EventType &eventType, *this) { + if (eventType.name() == name) { + return eventType; + } + } + return EventType(EventTypeId()); +} + +EventType EventTypes::findById(const EventTypeId &id) +{ + foreach (const EventType &eventType, *this) { + if (eventType.id() == id) { + return eventType; + } + } + return EventType(EventTypeId()); +} diff --git a/libguh/types/eventtype.h b/libguh/types/eventtype.h index 63c85f3e..87323c46 100644 --- a/libguh/types/eventtype.h +++ b/libguh/types/eventtype.h @@ -61,4 +61,12 @@ private: bool m_graphRelevant; }; +class EventTypes: public QList +{ +public: + EventTypes(const QList &other); + EventType findByName(const QString &name); + EventType findById(const EventTypeId &id); +}; + #endif // TRIGGERTYPE_H diff --git a/libguh/types/statetype.cpp b/libguh/types/statetype.cpp index b8004fbd..c2062d99 100644 --- a/libguh/types/statetype.cpp +++ b/libguh/types/statetype.cpp @@ -179,3 +179,31 @@ void StateType::setGraphRelevant(const bool &graphRelevant) { m_graphRelevant = graphRelevant; } + + +StateTypes::StateTypes(const QList &other) +{ + foreach (const StateType &st, other) { + append(st); + } +} + +StateType StateTypes::findByName(const QString &name) +{ + foreach (const StateType &stateType, *this) { + if (stateType.name() == name) { + return stateType; + } + } + return StateType(StateTypeId()); +} + +StateType StateTypes::findById(const StateTypeId &id) +{ + foreach (const StateType &stateType, *this) { + if (stateType.id() == id) { + return stateType; + } + } + return StateType(StateTypeId()); +} diff --git a/libguh/types/statetype.h b/libguh/types/statetype.h index 38dfdf54..4036f636 100644 --- a/libguh/types/statetype.h +++ b/libguh/types/statetype.h @@ -82,4 +82,12 @@ private: }; +class StateTypes: public QList +{ +public: + StateTypes(const QList &other); + StateType findByName(const QString &name); + StateType findById(const StateTypeId &id); +}; + #endif // STATETYPE_H diff --git a/plugins/deviceplugins/deviceplugins.pro b/plugins/deviceplugins/deviceplugins.pro index 9db2356d..eb3bee68 100644 --- a/plugins/deviceplugins/deviceplugins.pro +++ b/plugins/deviceplugins/deviceplugins.pro @@ -1,35 +1,38 @@ TEMPLATE = subdirs SUBDIRS += \ - mock \ - elro \ - intertechno \ - networkdetector \ - conrad \ - openweathermap \ - lircd \ - wakeonlan \ - mailnotification \ +# mock \ +# elro \ +# intertechno \ +# networkdetector \ +# conrad \ +# openweathermap \ +# lircd \ +# wakeonlan \ +# mailnotification \ philipshue \ - lgsmarttv \ - datetime \ - genericelements \ - commandlauncher \ - unitec \ - leynew \ - udpcommander \ +# eq-3 \ +# wemo \ +# lgsmarttv \ +# datetime \ +# genericelements \ +# commandlauncher \ +# unitec \ +# leynew \ +# udpcommander \ kodi \ - elgato \ - awattar \ - netatmo \ - dollhouse \ - plantcare \ - osdomotics \ - ws2812 \ - orderbutton \ - denon \ - avahimonitor \ - senic \ - gpio \ +# elgato \ +# awattar \ +# netatmo \ +# dollhouse \ +# plantcare \ +# osdomotics \ +# ws2812 \ +# orderbutton \ +# denon \ +# avahimonitor \ +# usbwde \ +# senic \ +# gpio \ disabletesting { SUBDIRS -= mock diff --git a/plugins/deviceplugins/kodi/devicepluginkodi.json b/plugins/deviceplugins/kodi/devicepluginkodi.json index ad62ea8b..3ca9da61 100644 --- a/plugins/deviceplugins/kodi/devicepluginkodi.json +++ b/plugins/deviceplugins/kodi/devicepluginkodi.json @@ -13,6 +13,7 @@ "idName": "kodi", "name": "Kodi", "deviceIcon": "Tv", + "interfaces": ["mediacontroller"], "basicTags": [ "Service", "Multimedia", diff --git a/plugins/deviceplugins/philipshue/devicepluginphilipshue.json b/plugins/deviceplugins/philipshue/devicepluginphilipshue.json index 30b1a55c..fc8a628f 100644 --- a/plugins/deviceplugins/philipshue/devicepluginphilipshue.json +++ b/plugins/deviceplugins/philipshue/devicepluginphilipshue.json @@ -158,6 +158,7 @@ "idName": "hueLight", "name": "Hue Light", "deviceIcon": "LightBulb", + "interfaces": ["colorlight"], "basicTags": [ "Device", "Lighting", diff --git a/server/jsonrpc/jsontypes.cpp b/server/jsonrpc/jsontypes.cpp index 11d4191b..895162ec 100644 --- a/server/jsonrpc/jsontypes.cpp +++ b/server/jsonrpc/jsontypes.cpp @@ -250,6 +250,7 @@ void JsonTypes::init() s_deviceClass.insert("pluginId", basicTypeToString(Uuid)); s_deviceClass.insert("name", basicTypeToString(String)); s_deviceClass.insert("deviceIcon", deviceIconRef()); + s_deviceClass.insert("interfaces", QVariantList() << basicTypeToString(String)); s_deviceClass.insert("basicTags", QVariantList() << basicTagRef()); s_deviceClass.insert("setupMethod", setupMethodRef()); s_deviceClass.insert("createMethods", QVariantList() << createMethodRef()); @@ -680,6 +681,7 @@ QVariantMap JsonTypes::packDeviceClass(const DeviceClass &deviceClass) variant.insert("vendorId", deviceClass.vendorId().toString()); variant.insert("pluginId", deviceClass.pluginId().toString()); variant.insert("deviceIcon", s_deviceIcon.at(deviceClass.deviceIcon())); + variant.insert("interfaces", deviceClass.interfaces()); QVariantList basicTags; foreach (const DeviceClass::BasicTag &basicTag, deviceClass.basicTags())