mirror of https://github.com/nymea/nymea.git
initial work on interfaces
have some interfaces defined and in use by DeviceManager and the API. this can be used to build first prototypes in apps using the interfaces stuff. Currently the lights interfaces are mostly defined and fully implemented by the Hue plugin. TODO: more interfaces to be defined, make more plugins follow interfaces. TODO: tests for the interface code TODO: docs for the interface codepull/135/head
parent
e9818e9caf
commit
45caf66851
|
|
@ -195,6 +195,7 @@
|
|||
#include <QCoreApplication>
|
||||
#include <QStandardPaths>
|
||||
#include <QDir>
|
||||
#include <QJsonDocument>
|
||||
|
||||
/*! 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. */
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"extends": "dimmablelight",
|
||||
"states": [
|
||||
{
|
||||
"name": "color temperature",
|
||||
"type": "int",
|
||||
"minValue": "any",
|
||||
"maxValue": "any"
|
||||
},
|
||||
{
|
||||
"name": "color",
|
||||
"type": "QColor"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"extends": "light",
|
||||
"states": [
|
||||
{
|
||||
"name": "brightness",
|
||||
"type": "int",
|
||||
"minimumValue": 0,
|
||||
"maximumValue": 100,
|
||||
"writable": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"states": [
|
||||
{
|
||||
"name": "gateState",
|
||||
"type": "String",
|
||||
"allowedValues": ["open", "closed", "opening", "closing"]
|
||||
}
|
||||
],
|
||||
"actions": [
|
||||
{
|
||||
"name": "open"
|
||||
},
|
||||
{
|
||||
"name": "close"
|
||||
},
|
||||
{
|
||||
"name": "stop"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
<RCC>
|
||||
<qresource prefix="/interfaces">
|
||||
<file>mediacontroller.json</file>
|
||||
<file>light.json</file>
|
||||
<file>dimmablelight.json</file>
|
||||
<file>colorlight.json</file>
|
||||
<file>garagegate.json</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"states": [
|
||||
{
|
||||
"name": "power",
|
||||
"type": "bool"
|
||||
}
|
||||
|
||||
]
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -141,3 +141,9 @@ for(header, HEADERS) {
|
|||
eval(headers_$${path}.path = $${path})
|
||||
eval(INSTALLS *= headers_$${path})
|
||||
}
|
||||
|
||||
DISTFILES += \
|
||||
interfaces/mediacontroller.json
|
||||
|
||||
RESOURCES += \
|
||||
interfaces/interfaces.qrc
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -149,6 +149,7 @@
|
|||
#include <QDir>
|
||||
#include <QCoreApplication>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
|
||||
/*! DevicePlugin constructor. DevicePlugins will be instantiated by the DeviceManager, its \a parent. */
|
||||
DevicePlugin::DevicePlugin(QObject *parent):
|
||||
|
|
@ -463,6 +464,75 @@ QList<DeviceClass> 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<bool, DeviceClass::DeviceIcon> DevicePlugin::loadAndVerifyDeviceIcon(const
|
|||
|
||||
return QPair<bool, DeviceClass::DeviceIcon>(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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -146,6 +146,8 @@ private:
|
|||
QPair<bool, DeviceClass::BasicTag> loadAndVerifyBasicTag(const QString &basicTag) const;
|
||||
QPair<bool, DeviceClass::DeviceIcon> loadAndVerifyDeviceIcon(const QString &deviceIcon) const;
|
||||
|
||||
QVariantMap loadInterface(const QString &name) const;
|
||||
|
||||
QTranslator *m_translator;
|
||||
DeviceManager *m_deviceManager;
|
||||
|
||||
|
|
|
|||
|
|
@ -91,3 +91,30 @@ void ActionType::setParamTypes(const QList<ParamType> ¶mTypes)
|
|||
{
|
||||
m_paramTypes = paramTypes;
|
||||
}
|
||||
|
||||
ActionTypes::ActionTypes(const QList<ActionType> &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());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,4 +53,12 @@ private:
|
|||
QList<ParamType> m_paramTypes;
|
||||
};
|
||||
|
||||
class ActionTypes: public QList<ActionType>
|
||||
{
|
||||
public:
|
||||
ActionTypes(const QList<ActionType> &other);
|
||||
ActionType findByName(const QString &name);
|
||||
ActionType findById(const ActionTypeId &id);
|
||||
};
|
||||
|
||||
#endif // ACTIONTYPE_H
|
||||
|
|
|
|||
|
|
@ -111,3 +111,30 @@ void EventType::setGraphRelevant(const bool &graphRelevant)
|
|||
{
|
||||
m_graphRelevant = graphRelevant;
|
||||
}
|
||||
|
||||
EventTypes::EventTypes(const QList<EventType> &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());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -61,4 +61,12 @@ private:
|
|||
bool m_graphRelevant;
|
||||
};
|
||||
|
||||
class EventTypes: public QList<EventType>
|
||||
{
|
||||
public:
|
||||
EventTypes(const QList<EventType> &other);
|
||||
EventType findByName(const QString &name);
|
||||
EventType findById(const EventTypeId &id);
|
||||
};
|
||||
|
||||
#endif // TRIGGERTYPE_H
|
||||
|
|
|
|||
|
|
@ -179,3 +179,31 @@ void StateType::setGraphRelevant(const bool &graphRelevant)
|
|||
{
|
||||
m_graphRelevant = graphRelevant;
|
||||
}
|
||||
|
||||
|
||||
StateTypes::StateTypes(const QList<StateType> &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());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -82,4 +82,12 @@ private:
|
|||
|
||||
};
|
||||
|
||||
class StateTypes: public QList<StateType>
|
||||
{
|
||||
public:
|
||||
StateTypes(const QList<StateType> &other);
|
||||
StateType findByName(const QString &name);
|
||||
StateType findById(const StateTypeId &id);
|
||||
};
|
||||
|
||||
#endif // STATETYPE_H
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
"idName": "kodi",
|
||||
"name": "Kodi",
|
||||
"deviceIcon": "Tv",
|
||||
"interfaces": ["mediacontroller"],
|
||||
"basicTags": [
|
||||
"Service",
|
||||
"Multimedia",
|
||||
|
|
|
|||
|
|
@ -158,6 +158,7 @@
|
|||
"idName": "hueLight",
|
||||
"name": "Hue Light",
|
||||
"deviceIcon": "LightBulb",
|
||||
"interfaces": ["colorlight"],
|
||||
"basicTags": [
|
||||
"Device",
|
||||
"Lighting",
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
|
|
|
|||
Loading…
Reference in New Issue