Merge PR #183: Add support for browsing things

This commit is contained in:
Jenkins 2019-09-02 18:01:50 +02:00
commit 7360cd2010
64 changed files with 2258 additions and 260 deletions

5
debian/changelog vendored
View File

@ -1,3 +1,8 @@
nymea (0.15.0) UNRELEASED; urgency=medium
-- Michael Zanetti <michael.zanetti@guh.io> Tue, 09 Jul 2019 02:08:05 +0200
nymea (0.14.0) xenial; urgency=medium
[ Michael Zanetti ]
* Bump minimum required TLS version to 1.2

View File

@ -3523,8 +3523,9 @@ See also: \l{Tag}
"LoggingSourceEvents",
"LoggingSourceActions",
"LoggingSourceStates",
"LoggingSourceRules"
],
"LoggingSourceRules",
"LoggingSourceBrowserActions"
],
"NetworkDeviceState": [
"NetworkDeviceStateUnknown",
"NetworkDeviceStateUnmanaged",

View File

@ -361,7 +361,7 @@ Device::DeviceError DeviceManagerImplementation::addConfiguredDevice(const Devic
}
}
return addConfiguredDeviceInternal(deviceClassId, name, finalParams, deviceId);
return addConfiguredDeviceInternal(deviceClassId, name, finalParams, deviceId, descriptor.parentDeviceId());
}
@ -619,7 +619,7 @@ Device::DeviceError DeviceManagerImplementation::confirmPairing(const PairingTra
/*! This method will only be used from the DeviceManagerImplementation in order to add a \l{Device} with the given \a deviceClassId, \a name, \a params and \ id.
* Returns \l{DeviceError} to inform about the result. */
Device::DeviceError DeviceManagerImplementation::addConfiguredDeviceInternal(const DeviceClassId &deviceClassId, const QString &name, const ParamList &params, const DeviceId id)
Device::DeviceError DeviceManagerImplementation::addConfiguredDeviceInternal(const DeviceClassId &deviceClassId, const QString &name, const ParamList &params, const DeviceId id, const DeviceId &parentDeviceId)
{
DeviceClass deviceClass = findDeviceClass(deviceClassId);
if (deviceClass.id().isNull()) {
@ -648,6 +648,7 @@ Device::DeviceError DeviceManagerImplementation::addConfiguredDeviceInternal(con
}
Device *device = new Device(plugin, deviceClass, id, this);
device->setParentId(parentDeviceId);
if (name.isEmpty()) {
device->setName(deviceClass.name());
} else {
@ -709,6 +710,82 @@ Device::DeviceError DeviceManagerImplementation::removeConfiguredDevice(const De
return Device::DeviceErrorNoError;
}
Device::BrowseResult DeviceManagerImplementation::browseDevice(const DeviceId &deviceId, const QString &itemId, const QLocale &locale)
{
Device::BrowseResult result;
Device *device = m_configuredDevices.value(deviceId);
if (!device) {
qCWarning(dcDeviceManager()) << "Cannot browse device. No such device:" << deviceId.toString();
result.status = Device::DeviceErrorDeviceNotFound;
return result;
}
if (!device->deviceClass().browsable()) {
qCWarning(dcDeviceManager()) << "Cannot browse device. DeviceClass" << device->deviceClass().name() << "is not browsable.";
result.status = Device::DeviceErrorUnsupportedFeature;
return result;
}
result = device->plugin()->browseDevice(device, result, itemId, locale);
return result;
}
Device::BrowserItemResult DeviceManagerImplementation::browserItemDetails(const DeviceId &deviceId, const QString &itemId, const QLocale &locale)
{
Device::BrowserItemResult result;
Device *device = m_configuredDevices.value(deviceId);
if (!device) {
qCWarning(dcDeviceManager()) << "Cannot browse device. No such device:" << deviceId.toString();
result.status = Device::DeviceErrorDeviceNotFound;
return result;
}
if (!device->deviceClass().browsable()) {
qCWarning(dcDeviceManager()) << "Cannot browse device. DeviceClass" << device->deviceClass().name() << "is not browsable.";
result.status = Device::DeviceErrorUnsupportedFeature;
return result;
}
result = device->plugin()->browserItem(device, result, itemId, locale);
if (result.status == Device::DeviceErrorAsync) {
// Error or Async
return result;
}
if (result.status != Device::DeviceErrorNoError) {
qCWarning(dcDeviceManager()) << "Browse device failed:" << result.status;
return result;
}
return result;
}
Device::DeviceError DeviceManagerImplementation::executeBrowserItem(const BrowserAction &browserAction)
{
Device *device = m_configuredDevices.value(browserAction.deviceId());
if (!device) {
return Device::DeviceErrorDeviceNotFound;
}
if (!device->deviceClass().browsable()) {
return Device::DeviceErrorUnsupportedFeature;
}
return device->plugin()->executeBrowserItem(device, browserAction);
}
Device::DeviceError DeviceManagerImplementation::executeBrowserItemAction(const BrowserItemAction &browserItemAction)
{
Device *device = m_configuredDevices.value(browserItemAction.deviceId());
if (!device) {
return Device::DeviceErrorDeviceNotFound;
}
if (!device->deviceClass().browsable()) {
return Device::DeviceErrorUnsupportedFeature;
}
// TODO: check browserItemAction.params with deviceClass
return device->plugin()->executeBrowserItemAction(device, browserItemAction);
}
QString DeviceManagerImplementation::translate(const PluginId &pluginId, const QString &string, const QLocale &locale)
{
return m_translator->translate(pluginId, string, locale);
@ -842,6 +919,7 @@ void DeviceManagerImplementation::loadPlugins()
loader.setFileName(fi.absoluteFilePath());
loader.setLoadHints(QLibrary::ResolveAllSymbolsHint);
qCDebug(dcDeviceManager()) << "Loading plugin from:" << fi.absoluteFilePath();
if (!loader.load()) {
qCWarning(dcDeviceManager) << "Could not load plugin data of" << entry << "\n" << loader.errorString();
continue;
@ -971,6 +1049,10 @@ void DeviceManagerImplementation::loadPlugin(DevicePlugin *pluginIface, const Pl
connect(pluginIface, &DevicePlugin::pairingFinished, this, &DeviceManagerImplementation::slotPairingFinished);
connect(pluginIface, &DevicePlugin::autoDevicesAppeared, this, &DeviceManagerImplementation::onAutoDevicesAppeared);
connect(pluginIface, &DevicePlugin::autoDeviceDisappeared, this, &DeviceManagerImplementation::onAutoDeviceDisappeared);
connect(pluginIface, &DevicePlugin::browseRequestFinished, this, &DeviceManagerImplementation::browseRequestFinished);
connect(pluginIface, &DevicePlugin::browserItemRequestFinished, this, &DeviceManagerImplementation::browserItemRequestFinished);
connect(pluginIface, &DevicePlugin::browserItemExecutionFinished, this, &DeviceManagerImplementation::browserItemExecutionFinished);
connect(pluginIface, &DevicePlugin::browserItemActionExecutionFinished, this, &DeviceManagerImplementation::browserItemActionExecutionFinished);
}
@ -1027,6 +1109,12 @@ void DeviceManagerImplementation::loadConfiguredDevices()
params.append(Param(ParamTypeId(paramTypeIdString), settings.value(paramTypeIdString)));
}
}
// Make sure all params are around. if they aren't initialize with default values
foreach (const ParamType &paramType, deviceClass.paramTypes()) {
if (!params.hasParam(paramType.id())) {
params.append(Param(paramType.id(), paramType.defaultValue()));
}
}
device->setParams(params);
settings.endGroup(); // Params

View File

@ -97,13 +97,19 @@ public:
Device::DeviceError removeConfiguredDevice(const DeviceId &deviceId) override;
Device::DeviceError executeAction(const Action &action) override;
Device::BrowseResult browseDevice(const DeviceId &deviceId, const QString &itemId, const QLocale &locale) override;
Device::BrowserItemResult browserItemDetails(const DeviceId &deviceId, const QString &itemId, const QLocale &locale) override;
Device::DeviceError executeBrowserItem(const BrowserAction &browserAction) override;
Device::DeviceError executeBrowserItemAction(const BrowserItemAction &browserItemAction) override;
QString translate(const PluginId &pluginId, const QString &string, const QLocale &locale) override;
signals:
void loaded();
public slots:
Device::DeviceError executeAction(const Action &action);
void timeTick();
private slots:
@ -125,7 +131,7 @@ private slots:
void slotDeviceSettingChanged(const ParamTypeId &paramTypeId, const QVariant &value);
private:
Device::DeviceError addConfiguredDeviceInternal(const DeviceClassId &deviceClassId, const QString &name, const ParamList &params, const DeviceId id = DeviceId::createDeviceId());
Device::DeviceError addConfiguredDeviceInternal(const DeviceClassId &deviceClassId, const QString &name, const ParamList &params, const DeviceId id = DeviceId::createDeviceId(), const DeviceId &parentDeviceId = DeviceId());
Device::DeviceSetupStatus setupDevice(Device *device);
void postSetupDevice(Device *device);
void storeDeviceStates(Device *device);

View File

@ -62,7 +62,27 @@ ActionHandler::ActionHandler(QObject *parent) :
returns.insert("o:actionType", JsonTypes::actionTypeDescription());
setReturns("GetActionType", returns);
params.clear(); returns.clear();
setDescription("ExecuteBrowserItem", "Execute the item identified by itemId on the given device.");
params.insert("deviceId", JsonTypes::basicTypeToString(JsonTypes::Uuid));
params.insert("itemId", JsonTypes::basicTypeToString(JsonTypes::String));
setParams("ExecuteBrowserItem", params);
returns.insert("deviceError", JsonTypes::deviceErrorRef());
setReturns("ExecuteBrowserItem", returns);
params.clear(); returns.clear();
setDescription("ExecuteBrowserItemAction", "Execute the action for the browser item identified by actionTypeId and the itemId on the given device.");
params.insert("deviceId", JsonTypes::basicTypeToString(JsonTypes::Uuid));
params.insert("itemId", JsonTypes::basicTypeToString(JsonTypes::String));
params.insert("actionTypeId", JsonTypes::basicTypeToString(JsonTypes::Uuid));
params.insert("o:params", QVariantList() << JsonTypes::paramRef());
setParams("ExecuteBrowserItemAction", params);
returns.insert("deviceError", JsonTypes::deviceErrorRef());
setReturns("ExecuteBrowserItemAction", returns);
connect(NymeaCore::instance(), &NymeaCore::actionExecuted, this, &ActionHandler::actionExecuted);
connect(NymeaCore::instance(), &NymeaCore::browserItemExecuted, this, &ActionHandler::browserItemExecuted);
connect(NymeaCore::instance(), &NymeaCore::browserItemActionExecuted, this, &ActionHandler::browserItemActionExecuted);
}
/*! Returns the name of the \l{ActionHandler}. In this case \b Actions.*/
@ -119,4 +139,62 @@ void ActionHandler::actionExecuted(const ActionId &id, Device::DeviceError statu
reply->finished();
}
JsonReply *ActionHandler::ExecuteBrowserItem(const QVariantMap &params)
{
DeviceId deviceId = DeviceId(params.value("deviceId").toString());
QString itemId = params.value("itemId").toString();
BrowserAction action(deviceId, itemId);
Device::DeviceError status = NymeaCore::instance()->executeBrowserItem(action);
if (status == Device::DeviceErrorAsync) {
JsonReply *reply = createAsyncReply("ExecuteBrowserItem");
ActionId id = action.id();
connect(reply, &JsonReply::finished, [this, id](){ m_asyncActionExecutions.remove(id); });
m_asyncActionExecutions.insert(id, reply);
return reply;
}
return createReply(statusToReply(status));
}
JsonReply *ActionHandler::ExecuteBrowserItemAction(const QVariantMap &params)
{
DeviceId deviceId = DeviceId(params.value("deviceId").toString());
QString itemId = params.value("itemId").toString();
ActionTypeId actionTypeId = ActionTypeId(params.value("actionTypeId").toString());
ParamList paramList = JsonTypes::unpackParams(params.value("params").toList());
BrowserItemAction browserItemAction(deviceId, itemId, actionTypeId, paramList);
Device::DeviceError status = NymeaCore::instance()->executeBrowserItemAction(browserItemAction);
if (status == Device::DeviceErrorAsync) {
JsonReply *reply = createAsyncReply("ExecuteBrowserItemAction");
ActionId id = browserItemAction.id();
connect(reply, &JsonReply::finished, [this, id](){ m_asyncActionExecutions.remove(id); });
m_asyncActionExecutions.insert(id, reply);
return reply;
}
return createReply(statusToReply(status));
}
void ActionHandler::browserItemExecuted(const ActionId &id, Device::DeviceError status)
{
if (!m_asyncActionExecutions.contains(id)) {
return; // Not the action we are waiting for.
}
JsonReply *reply = m_asyncActionExecutions.take(id);
reply->setData(statusToReply(status));
reply->finished();
}
void ActionHandler::browserItemActionExecuted(const ActionId &id, Device::DeviceError status)
{
if (!m_asyncActionExecutions.contains(id)) {
return; // Not the action we are waiting for.
}
JsonReply *reply = m_asyncActionExecutions.take(id);
reply->setData(statusToReply(status));
reply->finished();
}
}

View File

@ -38,8 +38,13 @@ public:
Q_INVOKABLE JsonReply *ExecuteAction(const QVariantMap &params);
Q_INVOKABLE JsonReply *GetActionType(const QVariantMap &params) const;
Q_INVOKABLE JsonReply *ExecuteBrowserItem(const QVariantMap &params);
Q_INVOKABLE JsonReply *ExecuteBrowserItemAction(const QVariantMap &params);
private slots:
void actionExecuted(const ActionId &id, Device::DeviceError status);
void browserItemExecuted(const ActionId &id, Device::DeviceError status);
void browserItemActionExecuted(const ActionId &id, Device::DeviceError status);
private:
QHash<ActionId, JsonReply *> m_asyncActionExecutions;

View File

@ -283,6 +283,24 @@ DeviceHandler::DeviceHandler(QObject *parent) :
returns.insert("o:values", states);
setReturns("GetStateValues", returns);
params.clear(); returns.clear();
setDescription("BrowseDevice", "Browse a device. If a DeviceClass indicates a device is browsable, this method will return the BrowserItems. If no parameter besides the deviceId is used, the root node of this device will be returned. Any returned item which is browsable can be passed as node. Results will be children of the given node.");
params.insert("deviceId", JsonTypes::basicTypeToString(JsonTypes::Uuid));
params.insert("o:itemId", JsonTypes::basicTypeToString(JsonTypes::String));
setParams("BrowseDevice", params);
returns.insert("deviceError", JsonTypes::deviceErrorRef());
returns.insert("items", QVariantList() << JsonTypes::browserItemRef());
setReturns("BrowseDevice", returns);
params.clear(); returns.clear();
setDescription("GetBrowserItem", "Get a single item from the browser. This won't give any more info on an item than a regular browseDevice call, but it allows to fetch details of an item if only the ID is known.");
params.insert("deviceId", JsonTypes::basicTypeToString(JsonTypes::Uuid));
params.insert("o:itemId", JsonTypes::basicTypeToString(JsonTypes::String));
setParams("GetBrowserItem", params);
returns.insert("deviceError", JsonTypes::deviceErrorRef());
returns.insert("o:item", JsonTypes::browserItemRef());
setReturns("GetBrowserItem", returns);
// Notifications
params.clear(); returns.clear();
setDescription("StateChanged", "Emitted whenever a State of a device changes.");
@ -329,6 +347,8 @@ DeviceHandler::DeviceHandler(QObject *parent) :
connect(NymeaCore::instance(), &NymeaCore::deviceSetupFinished, this, &DeviceHandler::deviceSetupFinished);
connect(NymeaCore::instance(), &NymeaCore::deviceReconfigurationFinished, this, &DeviceHandler::deviceReconfigurationFinished);
connect(NymeaCore::instance(), &NymeaCore::pairingFinished, this, &DeviceHandler::pairingFinished);
connect(NymeaCore::instance()->deviceManager(), &DeviceManager::browseRequestFinished, this, &DeviceHandler::browseRequestFinished);
connect(NymeaCore::instance()->deviceManager(), &DeviceManager::browserItemRequestFinished, this, &DeviceHandler::browserItemRequestFinished);
}
/*! Returns the name of the \l{DeviceHandler}. In this case \b Devices.*/
@ -665,6 +685,52 @@ JsonReply *DeviceHandler::GetStateValues(const QVariantMap &params) const
return createReply(returns);
}
JsonReply *DeviceHandler::BrowseDevice(const QVariantMap &params) const
{
QVariantMap returns;
DeviceId deviceId = DeviceId(params.value("deviceId").toString());
QString itemId = params.value("itemId").toString();
Device::BrowseResult result = NymeaCore::instance()->deviceManager()->browseDevice(deviceId, itemId, params.value("locale").toLocale());
if (result.status == Device::DeviceErrorAsync ) {
JsonReply *reply = createAsyncReply("BrowseDevice");
m_asyncBrowseRequests.insert(result.id(), reply);
connect(reply, &JsonReply::finished, this, [this, result](){
m_asyncBrowseRequests.remove(result.id());
});
return reply;
}
returns.insert("deviceError", JsonTypes::deviceErrorToString(result.status));
returns.insert("items", JsonTypes::packBrowserItems(result.items));
return createReply(returns);
}
JsonReply *DeviceHandler::GetBrowserItem(const QVariantMap &params) const
{
QVariantMap returns;
DeviceId deviceId = DeviceId(params.value("deviceId").toString());
QString itemId = params.value("itemId").toString();
Device::BrowserItemResult result = NymeaCore::instance()->deviceManager()->browserItemDetails(deviceId, itemId, params.value("locale").toLocale());
if (result.status == Device::DeviceErrorAsync ) {
JsonReply *reply = createAsyncReply("GetBrowserItem");
m_asyncBrowseDetailsRequests.insert(result.id(), reply);
connect(reply, &JsonReply::finished, this, [this, result](){
m_asyncBrowseDetailsRequests.remove(result.id());
});
return reply;
}
returns.insert("deviceError", JsonTypes::deviceErrorToString(result.status));
if (result.status == Device::DeviceErrorNoError) {
returns.insert("item", JsonTypes::packBrowserItem(result.item));
}
return createReply(returns);
}
void DeviceHandler::pluginConfigChanged(const PluginId &id, const ParamList &config)
{
QVariantMap params;
@ -790,4 +856,35 @@ void DeviceHandler::pairingFinished(const PairingTransactionId &pairingTransacti
m_asynDeviceAdditions.insert(deviceId, reply);
}
void DeviceHandler::browseRequestFinished(const Device::BrowseResult &result)
{
if (!m_asyncBrowseRequests.contains(result.id())) {
qCWarning(dcJsonRpc()) << "No pending JsonRpc reply. Did it time out?";
return;
}
JsonReply *reply = m_asyncBrowseRequests.take(result.id());
QVariantMap params;
params.insert("items", JsonTypes::packBrowserItems(result.items));
params.insert("deviceError", JsonTypes::deviceErrorToString(result.status));
reply->setData(params);
reply->finished();
}
void DeviceHandler::browserItemRequestFinished(const Device::BrowserItemResult &result)
{
if (!m_asyncBrowseDetailsRequests.contains(result.id())) {
qCWarning(dcJsonRpc()) << "No pending JsonRpc reply for result" << result.id() << ". Did it time out?";
return;
}
JsonReply *reply = m_asyncBrowseDetailsRequests.take(result.id());
QVariantMap params;
if (result.status == Device::DeviceErrorNoError) {
params.insert("item", JsonTypes::packBrowserItem(result.item));
}
params.insert("deviceError", JsonTypes::deviceErrorToString(result.status));
reply->setData(params);
reply->finished();
}
}

View File

@ -57,6 +57,9 @@ public:
Q_INVOKABLE JsonReply *GetStateValue(const QVariantMap &params) const;
Q_INVOKABLE JsonReply *GetStateValues(const QVariantMap &params) const;
Q_INVOKABLE JsonReply *BrowseDevice(const QVariantMap &params) const;
Q_INVOKABLE JsonReply *GetBrowserItem(const QVariantMap &params) const;
signals:
void PluginConfigurationChanged(const QVariantMap &params);
void StateChanged(const QVariantMap &params);
@ -86,12 +89,18 @@ private slots:
void pairingFinished(const PairingTransactionId &pairingTransactionId, Device::DeviceError status, const DeviceId &deviceId);
void browseRequestFinished(const Device::BrowseResult &result);
void browserItemRequestFinished(const Device::BrowserItemResult &result);
private:
// A cache for async replies
mutable QHash<DeviceClassId, JsonReply*> m_discoverRequests;
mutable QHash<DeviceId, JsonReply*> m_asynDeviceAdditions;
mutable QHash<DeviceId, JsonReply*> m_asynDeviceEditAdditions;
mutable QHash<QUuid, JsonReply*> m_asyncPairingRequests;
mutable QHash<QUuid, JsonReply*> m_asyncBrowseRequests;
mutable QHash<QUuid, JsonReply*> m_asyncBrowseDetailsRequests;
};
}

View File

@ -58,6 +58,8 @@
#include "loggingcategories.h"
#include "logging/logvaluetool.h"
#include "types/mediabrowseritem.h"
#include <QStringList>
#include <QJsonDocument>
#include <QDebug>
@ -90,6 +92,8 @@ QVariantList JsonTypes::s_networkDeviceState;
QVariantList JsonTypes::s_userError;
QVariantList JsonTypes::s_tagError;
QVariantList JsonTypes::s_cloudConnectionState;
QVariantList JsonTypes::s_browserIcon;
QVariantList JsonTypes::s_mediaBrowserIcon;
QVariantMap JsonTypes::s_paramType;
QVariantMap JsonTypes::s_param;
@ -127,6 +131,7 @@ QVariantMap JsonTypes::s_tag;
QVariantMap JsonTypes::s_mqttPolicy;
QVariantMap JsonTypes::s_package;
QVariantMap JsonTypes::s_repository;
QVariantMap JsonTypes::s_browserItem;
void JsonTypes::init()
{
@ -153,6 +158,8 @@ void JsonTypes::init()
s_userError = enumToStrings(UserManager::staticMetaObject, "UserError");
s_tagError = enumToStrings(TagsStorage::staticMetaObject, "TagError");
s_cloudConnectionState = enumToStrings(CloudManager::staticMetaObject, "CloudConnectionState");
s_browserIcon = enumToStrings(BrowserItem::staticMetaObject, "BrowserIcon");
s_mediaBrowserIcon = enumToStrings(MediaBrowserItem::staticMetaObject, "MediaBrowserIcon");
// ParamType
s_paramType.insert("id", basicTypeToString(Uuid));
@ -177,6 +184,7 @@ void JsonTypes::init()
s_ruleAction.insert("o:actionTypeId", basicTypeToString(Uuid));
s_ruleAction.insert("o:interface", basicTypeToString(String));
s_ruleAction.insert("o:interfaceAction", basicTypeToString(String));
s_ruleAction.insert("o:browserItemId", basicTypeToString(String));
s_ruleAction.insert("o:ruleActionParams", QVariantList() << ruleActionParamRef());
// RuleActionParam
@ -273,11 +281,13 @@ void JsonTypes::init()
s_deviceClass.insert("name", basicTypeToString(String));
s_deviceClass.insert("displayName", basicTypeToString(String));
s_deviceClass.insert("interfaces", QVariantList() << basicTypeToString(String));
s_deviceClass.insert("browsable", basicTypeToString(Bool));
s_deviceClass.insert("setupMethod", setupMethodRef());
s_deviceClass.insert("createMethods", QVariantList() << createMethodRef());
s_deviceClass.insert("stateTypes", QVariantList() << stateTypeRef());
s_deviceClass.insert("eventTypes", QVariantList() << eventTypeRef());
s_deviceClass.insert("actionTypes", QVariantList() << actionTypeRef());
s_deviceClass.insert("browserItemActionTypes", QVariantList() << actionTypeRef());
s_deviceClass.insert("paramTypes", QVariantList() << paramTypeRef());
s_deviceClass.insert("settingsTypes", QVariantList() << paramTypeRef());
s_deviceClass.insert("discoveryParamTypes", QVariantList() << paramTypeRef());
@ -327,6 +337,7 @@ void JsonTypes::init()
s_logEntry.insert("source", loggingSourceRef());
s_logEntry.insert("o:typeId", basicTypeToString(Uuid));
s_logEntry.insert("o:deviceId", basicTypeToString(Uuid));
s_logEntry.insert("o:itemId", basicTypeToString(String));
s_logEntry.insert("o:value", basicTypeToString(String));
s_logEntry.insert("o:active", basicTypeToString(Bool));
s_logEntry.insert("o:eventType", loggingEventTypeRef());
@ -403,6 +414,7 @@ void JsonTypes::init()
s_tag.insert("tagId", basicTypeToString(QVariant::String));
s_tag.insert("o:value", basicTypeToString(QVariant::String));
// Package
s_package.insert("id", basicTypeToString(QVariant::String));
s_package.insert("displayName", basicTypeToString(QVariant::String));
s_package.insert("summary", basicTypeToString(QVariant::String));
@ -413,10 +425,23 @@ void JsonTypes::init()
s_package.insert("rollbackAvailable", basicTypeToString(QVariant::Bool));
s_package.insert("canRemove", basicTypeToString(QVariant::Bool));
// Repository
s_repository.insert("id", basicTypeToString(QVariant::String));
s_repository.insert("displayName", basicTypeToString(QVariant::String));
s_repository.insert("enabled", basicTypeToString(QVariant::Bool));
// BrowserItem
s_browserItem.insert("id", basicTypeToString(QVariant::String));
s_browserItem.insert("displayName", basicTypeToString(QVariant::String));
s_browserItem.insert("description", basicTypeToString(QVariant::String));
s_browserItem.insert("icon", browserIconRef());
s_browserItem.insert("thumbnail", basicTypeToString(QVariant::String));
s_browserItem.insert("executable", basicTypeToString(QVariant::Bool));
s_browserItem.insert("browsable", basicTypeToString(QVariant::Bool));
s_browserItem.insert("disabled", basicTypeToString(QVariant::Bool));
s_browserItem.insert("actionTypeIds", QVariantList() << basicTypeToString(QVariant::Uuid));
s_browserItem.insert("o:mediaIcon", mediaBrowserIconRef());
s_initialized = true;
}
@ -465,6 +490,8 @@ QVariantMap JsonTypes::allTypes()
allTypes.insert("UserError", userError());
allTypes.insert("TagError", tagError());
allTypes.insert("CloudConnectionState", cloudConnectionState());
allTypes.insert("BrowserIcon", browserIconRef());
allTypes.insert("MediaBrowserIcon", mediaBrowserIconRef());
allTypes.insert("StateType", stateTypeDescription());
allTypes.insert("StateDescriptor", stateDescriptorDescription());
@ -501,6 +528,7 @@ QVariantMap JsonTypes::allTypes()
allTypes.insert("MqttPolicy", mqttPolicyDescription());
allTypes.insert("Package", packageDescription());
allTypes.insert("Repository", repositoryDescription());
allTypes.insert("BrowserItem", browserItemDescription());
return allTypes;
}
@ -590,8 +618,11 @@ QVariantMap JsonTypes::packRuleAction(const RuleAction &ruleAction)
{
QVariantMap variant;
if (ruleAction.type() == RuleAction::TypeDevice) {
variant.insert("actionTypeId", ruleAction.actionTypeId().toString());
variant.insert("deviceId", ruleAction.deviceId().toString());
variant.insert("actionTypeId", ruleAction.actionTypeId().toString());
} else if (ruleAction.type() == RuleAction::TypeBrowser) {
variant.insert("deviceId", ruleAction.deviceId().toString());
variant.insert("browserItemId", ruleAction.browserItemId());
} else {
variant.insert("interface", ruleAction.interface());
variant.insert("interfaceAction", ruleAction.interfaceAction());
@ -706,6 +737,28 @@ QVariantMap JsonTypes::packParam(const Param &param)
return variantMap;
}
QVariantMap JsonTypes::packBrowserItem(const BrowserItem &item)
{
QVariantMap ret;
ret.insert("id", item.id());
ret.insert("displayName", item.displayName());
ret.insert("description", item.description());
ret.insert("icon", browserIconToString(item.icon()));
if (item.extendedPropertiesFlags().testFlag(BrowserItem::ExtendedPropertiesMedia)) {
ret.insert("mediaIcon", mediaBrowserIconToString(static_cast<MediaBrowserItem::MediaBrowserIcon>(item.extendedProperty("mediaIcon").toInt())));
}
ret.insert("thumbnail", item.thumbnail());
ret.insert("executable", item.executable());
ret.insert("browsable", item.browsable());
ret.insert("disabled", item.disabled());
QVariantList actionTypeIds;
foreach (const ActionTypeId &id, item.actionTypeIds()) {
actionTypeIds.append(id.toString());
}
ret.insert("actionTypeIds", actionTypeIds);
return ret;
}
QVariantList JsonTypes::packParams(const ParamList &paramList)
{
QVariantList ret;
@ -790,6 +843,7 @@ QVariantMap JsonTypes::packDeviceClass(const DeviceClass &deviceClass, const QLo
variant.insert("vendorId", deviceClass.vendorId().toString());
variant.insert("pluginId", deviceClass.pluginId().toString());
variant.insert("interfaces", deviceClass.interfaces());
variant.insert("browsable", deviceClass.browsable());
QVariantList stateTypes;
foreach (const StateType &stateType, deviceClass.stateTypes())
@ -803,6 +857,10 @@ QVariantMap JsonTypes::packDeviceClass(const DeviceClass &deviceClass, const QLo
foreach (const ActionType &actionType, deviceClass.actionTypes())
actionTypes.append(packActionType(actionType, deviceClass.pluginId(), locale));
QVariantList browserItemActionTypes;
foreach (const ActionType &actionType, deviceClass.browserItemActionTypes())
browserItemActionTypes.append(packActionType(actionType, deviceClass.pluginId(), locale));
QVariantList paramTypes;
foreach (const ParamType &paramType, deviceClass.paramTypes())
paramTypes.append(packParamType(paramType, deviceClass.pluginId(), locale));
@ -821,6 +879,7 @@ QVariantMap JsonTypes::packDeviceClass(const DeviceClass &deviceClass, const QLo
variant.insert("stateTypes", stateTypes);
variant.insert("eventTypes", eventTypes);
variant.insert("actionTypes", actionTypes);
variant.insert("browserItemActionTypes", browserItemActionTypes);
variant.insert("createMethods", packCreateMethods(deviceClass.createMethods()));
variant.insert("setupMethod", s_setupMethod.at(deviceClass.setupMethod()));
return variant;
@ -953,6 +1012,7 @@ QVariantMap JsonTypes::packLogEntry(const LogEntry &logEntry)
case Logging::LoggingSourceActions:
case Logging::LoggingSourceEvents:
case Logging::LoggingSourceStates:
case Logging::LoggingSourceBrowserActions:
logEntryMap.insert("errorCode", s_deviceError.at(logEntry.errorCode()));
break;
case Logging::LoggingSourceSystem:
@ -976,6 +1036,9 @@ QVariantMap JsonTypes::packLogEntry(const LogEntry &logEntry)
case Logging::LoggingSourceRules:
logEntryMap.insert("typeId", logEntry.typeId().toString());
break;
case Logging::LoggingSourceBrowserActions:
logEntryMap.insert("itemId", logEntry.value());
break;
}
return logEntryMap;
@ -1186,6 +1249,15 @@ QVariantList JsonTypes::packDeviceDescriptors(const QList<DeviceDescriptor> devi
return deviceDescriptorList;
}
QVariantList JsonTypes::packBrowserItems(const BrowserItems &items)
{
QVariantList ret;
foreach (const BrowserItem &item, items) {
ret.append(packBrowserItem(item));
}
return ret;
}
/*! Returns a variant map with the current basic configuration of the server. */
QVariantMap JsonTypes::packBasicConfiguration()
{
@ -1432,10 +1504,13 @@ RuleAction JsonTypes::unpackRuleAction(const QVariantMap &ruleActionMap)
DeviceId actionDeviceId(ruleActionMap.value("deviceId").toString());
QString interface = ruleActionMap.value("interface").toString();
QString interfaceAction = ruleActionMap.value("interfaceAction").toString();
QString browserItemId = ruleActionMap.value("browserItemId").toString();
RuleActionParamList actionParamList = JsonTypes::unpackRuleActionParams(ruleActionMap.value("ruleActionParams").toList());
if (!actionTypeId.isNull() && !actionDeviceId.isNull()) {
if (!actionDeviceId.isNull() && !actionTypeId.isNull()) {
return RuleAction(actionTypeId, actionDeviceId, actionParamList);
} else if (!actionDeviceId.isNull() && !browserItemId.isNull()) {
return RuleAction(actionDeviceId, browserItemId);
}
return RuleAction(interface, interfaceAction, actionParamList);
}
@ -2080,6 +2155,12 @@ QPair<bool, QString> JsonTypes::validateVariant(const QVariant &templateVariant,
qCWarning(dcJsonRpc) << "Repository not matching";
return result;
}
} else if (refName == browserItemRef()) {
QPair<bool, QString> result = validateMap(browserItemDescription(), variant.toMap());
if (!result.first) {
qCWarning(dcJsonRpc) << "BrowserItem not matching";
return result;
}
} else if (refName == basicTypeRef()) {
QPair<bool, QString> result = validateBasicType(variant);
if (!result.first) {
@ -2212,6 +2293,18 @@ QPair<bool, QString> JsonTypes::validateVariant(const QVariant &templateVariant,
qCWarning(dcJsonRpc()) << QString("Value %1 not allowed in %2").arg(variant.toString()).arg(cloudConnectionStateRef());
return result;
}
} else if (refName == browserIconRef()) {
QPair<bool, QString> result = validateEnum(s_browserIcon, variant);
if (!result.first) {
qCWarning(dcJsonRpc()) << QString("Value %1 not allowed in %2").arg(variant.toString()).arg(browserIconRef());
return result;
}
} else if (refName == mediaBrowserIconRef()) {
QPair<bool, QString> result = validateEnum(s_mediaBrowserIcon, variant);
if (!result.first) {
qCWarning(dcJsonRpc()) << QString("Value %1 not allowed in %2").arg(variant.toString()).arg(mediaBrowserIconRef());
return result;
}
} else {
Q_ASSERT_X(false, "JsonTypes", QString("Unhandled ref: %1").arg(refName).toLatin1().data());
return report(false, QString("Unhandled ref %1. Server implementation incomplete.").arg(refName));

View File

@ -27,6 +27,7 @@
#include "devices/devicemanager.h"
#include "ruleengine/rule.h"
#include "ruleengine/ruleengine.h"
#include "ruleengine/ruleactionparam.h"
#include "nymeaconfiguration.h"
#include "usermanager/usermanager.h"
@ -36,7 +37,7 @@
#include "types/actiontype.h"
#include "types/paramtype.h"
#include "types/paramdescriptor.h"
#include "types/ruleactionparam.h"
#include "types/mediabrowseritem.h"
#include "logging/logging.h"
#include "logging/logentry.h"
@ -90,10 +91,8 @@ namespace nymeaserver {
return s_##typeName; \
} \
static QString typeName##ToString(className::enumName value) { \
const QMetaObject &metaObject = className::staticMetaObject; \
int enumIndex = metaObject.indexOfEnumerator(enumString); \
QMetaEnum metaEnum = metaObject.enumerator(enumIndex); \
return metaEnum.valueToKey(metaEnum.value(value)); \
QMetaEnum metaEnum = QMetaEnum::fromType<className::enumName>(); \
return metaEnum.valueToKey(value); \
} \
private: \
static QVariantList s_##typeName; \
@ -102,7 +101,6 @@ namespace nymeaserver {
class JsonTypes
{
Q_GADGET
Q_ENUMS(BasicType)
public:
enum BasicType {
@ -118,6 +116,7 @@ public:
Time,
Object
};
Q_ENUM(BasicType)
static QVariantMap allTypes();
@ -143,6 +142,8 @@ public:
DECLARE_TYPE(userError, "UserError", UserManager, UserError)
DECLARE_TYPE(tagError, "TagError", TagsStorage, TagError)
DECLARE_TYPE(cloudConnectionState, "CloudConnectionState", CloudManager, CloudConnectionState)
DECLARE_TYPE(browserIcon, "BrowserIcon", BrowserItem, BrowserIcon)
DECLARE_TYPE(mediaBrowserIcon, "MediaBrowserIcon", MediaBrowserItem, MediaBrowserIcon)
DECLARE_OBJECT(paramType, "ParamType")
DECLARE_OBJECT(param, "Param")
@ -180,6 +181,7 @@ public:
DECLARE_OBJECT(mqttPolicy, "MqttPolicy")
DECLARE_OBJECT(package, "Package")
DECLARE_OBJECT(repository, "Repository")
DECLARE_OBJECT(browserItem, "BrowserItem")
// pack types
static QVariantMap packEventType(const EventType &eventType, const PluginId &pluginId, const QLocale &locale);
@ -194,7 +196,7 @@ public:
static QVariantMap packStateDescriptor(const StateDescriptor &stateDescriptor);
static QVariantMap packStateEvaluator(const StateEvaluator &stateEvaluator);
static QVariantMap packParam(const Param &param);
static QVariantList packParams(const ParamList &paramList);
static QVariantMap packBrowserItem(const BrowserItem &item);
static QVariantMap packParamType(const ParamType &paramType, const PluginId &pluginId, const QLocale &locale);
static QVariantMap packParamDescriptor(const ParamDescriptor &paramDescriptor);
static QVariantMap packVendor(const Vendor &vendor, const QLocale &locale);
@ -214,6 +216,8 @@ public:
static QVariantMap packWiredNetworkDevice(WiredNetworkDevice *networkDevice);
static QVariantMap packWirelessNetworkDevice(WirelessNetworkDevice *networkDevice);
static QVariantList packParams(const ParamList &paramList);
static QVariantList packBrowserItems(const BrowserItems &items);
static QVariantList packRules(const QList<Rule> rules);
static QVariantList packCreateMethods(DeviceClass::CreateMethods createMethods);
static QVariantList packSupportedVendors(const QLocale &locale);

View File

@ -18,9 +18,11 @@ RESOURCES += $$top_srcdir/icons.qrc \
HEADERS += nymeacore.h \
devices/devicemanagerimplementation.h \
devices/translator.h \
devices/stateevaluator.h \
ruleengine/ruleengine.h \
ruleengine/rule.h \
ruleengine/stateevaluator.h \
ruleengine/ruleaction.h \
ruleengine/ruleactionparam.h \
transportinterface.h \
nymeaconfiguration.h \
servermanager.h \
@ -102,9 +104,11 @@ HEADERS += nymeacore.h \
SOURCES += nymeacore.cpp \
devices/devicemanagerimplementation.cpp \
devices/translator.cpp \
devices/stateevaluator.cpp \
ruleengine/ruleengine.cpp \
ruleengine/rule.cpp \
ruleengine/stateevaluator.cpp \
ruleengine/ruleaction.cpp \
ruleengine/ruleactionparam.cpp \
transportinterface.cpp \
nymeaconfiguration.cpp \
servermanager.cpp \

View File

@ -108,6 +108,8 @@
This \l{LogEntry} was created from an \l{State} which hase changed.
\value LoggingSourceRules
This \l{LogEntry} represents the enable/disable event from an \l{Rule}.
\value LoggingSourceBrowserActions
This \l{LogEntry} was created from a \l{BrowserItemAction}.
*/
#include "nymeasettings.h"
@ -312,6 +314,23 @@ void LogEngine::logAction(const Action &action, Logging::LoggingLevel level, int
appendLogEntry(entry);
}
void LogEngine::logBrowserAction(const BrowserAction &browserAction, Logging::LoggingLevel level, int errorCode)
{
LogEntry entry(level, Logging::LoggingSourceBrowserActions, errorCode);
entry.setDeviceId(browserAction.deviceId());
entry.setValue(browserAction.itemId());
appendLogEntry(entry);
}
void LogEngine::logBrowserItemAction(const BrowserItemAction &browserItemAction, Logging::LoggingLevel level, int errorCode)
{
LogEntry entry(level, Logging::LoggingSourceBrowserActions, errorCode);
entry.setDeviceId(browserItemAction.deviceId());
entry.setTypeId(browserItemAction.actionTypeId());
entry.setValue(browserItemAction.itemId());
appendLogEntry(entry);
}
void LogEngine::logRuleTriggered(const Rule &rule)
{
LogEntry entry(Logging::LoggingSourceRules);

View File

@ -26,6 +26,8 @@
#include "logfilter.h"
#include "types/event.h"
#include "types/action.h"
#include "types/browseritemaction.h"
#include "types/browseraction.h"
#include "ruleengine/rule.h"
#include <QObject>
@ -49,6 +51,8 @@ public:
void logSystemEvent(const QDateTime &dateTime, bool active, Logging::LoggingLevel level = Logging::LoggingLevelInfo);
void logEvent(const Event &event);
void logAction(const Action &action, Logging::LoggingLevel level = Logging::LoggingLevelInfo, int errorCode = 0);
void logBrowserAction(const BrowserAction &browserAction, Logging::LoggingLevel level = Logging::LoggingLevelInfo, int errorCode = 0);
void logBrowserItemAction(const BrowserItemAction &browserItemAction, Logging::LoggingLevel level = Logging::LoggingLevelInfo, int errorCode = 0);
void logRuleTriggered(const Rule &rule);
void logRuleActiveChanged(const Rule &rule);
void logRuleEnabledChanged(const Rule &rule, const bool &enabled);

View File

@ -56,7 +56,7 @@ public:
DeviceId deviceId() const;
void setDeviceId(const DeviceId &deviceId);
// Valid for LoggingSourceStates
// Valid for LoggingSourceStates, LoggingSourceBrowserActions
QVariant value() const;
void setValue(const QVariant &value);

View File

@ -28,11 +28,6 @@ namespace nymeaserver {
class Logging
{
Q_GADGET
Q_ENUMS(LoggingError)
Q_ENUMS(LoggingSource)
Q_FLAGS(LoggingSources)
Q_ENUMS(LoggingLevel)
Q_ENUMS(LoggingEventType)
public:
enum LoggingError {
@ -40,20 +35,25 @@ public:
LoggingErrorLogEntryNotFound,
LoggingErrorInvalidFilterParameter
};
Q_ENUM(LoggingError)
enum LoggingSource {
LoggingSourceSystem,
LoggingSourceEvents,
LoggingSourceActions,
LoggingSourceStates,
LoggingSourceRules
LoggingSourceRules,
LoggingSourceBrowserActions,
};
Q_ENUM(LoggingSource)
Q_FLAGS(LoggingSources)
Q_DECLARE_FLAGS(LoggingSources, LoggingSource)
enum LoggingLevel {
LoggingLevelInfo,
LoggingLevelAlert
};
Q_ENUM(LoggingLevel)
enum LoggingEventType {
LoggingEventTypeTrigger,
@ -62,8 +62,9 @@ public:
LoggingEventTypeActionsExecuted,
LoggingEventTypeExitActionsExecuted
};
Q_ENUM(LoggingEventType)
Logging(QObject *parent = 0);
Logging(QObject *parent = nullptr);
};
}

View File

@ -35,9 +35,6 @@ namespace nymeaserver {
class NetworkDevice : public QObject
{
Q_OBJECT
Q_ENUMS(NetworkDeviceType)
Q_ENUMS(NetworkDeviceState)
Q_ENUMS(NetworkDeviceStateReason)
public:
enum NetworkDeviceState {
@ -55,6 +52,7 @@ public:
NetworkDeviceStateDeactivating = 110,
NetworkDeviceStateFailed = 120
};
Q_ENUM(NetworkDeviceState)
enum NetworkDeviceStateReason {
NetworkDeviceStateReasonNone = 0,
@ -121,6 +119,7 @@ public:
NetworkDeviceStateReasonParentChanged = 61,
NetworkDeviceStateReasonParentManagedChanged = 62
};
Q_ENUM(NetworkDeviceStateReason)
enum NetworkDeviceType {
NetworkDeviceTypeUnknown = 0,
@ -143,8 +142,9 @@ public:
NetworkDeviceTypeVXLan = 19,
NetworkDeviceTypeVEth = 20,
};
Q_ENUM(NetworkDeviceType)
explicit NetworkDevice(const QDBusObjectPath &objectPath, QObject *parent = 0);
explicit NetworkDevice(const QDBusObjectPath &objectPath, QObject *parent = nullptr);
QDBusObjectPath objectPath() const;

View File

@ -40,9 +40,6 @@ namespace nymeaserver {
class NetworkManager : public QObject
{
Q_OBJECT
Q_ENUMS(NetworkManagerState)
Q_ENUMS(NetworkManagerConnectivityState)
Q_ENUMS(NetworkManagerError)
public:
enum NetworkManagerState {
@ -55,6 +52,7 @@ public:
NetworkManagerStateConnectedSite = 60,
NetworkManagerStateConnectedGlobal = 70
};
Q_ENUM(NetworkManagerState)
enum NetworkManagerConnectivityState {
NetworkManagerConnectivityStateUnknown = 0,
@ -63,6 +61,7 @@ public:
NetworkManagerConnectivityStateLimited = 3,
NetworkManagerConnectivityStateFull = 4
};
Q_ENUM(NetworkManagerConnectivityState)
enum NetworkManagerError {
NetworkManagerErrorNoError,
@ -76,8 +75,9 @@ public:
NetworkManagerErrorNetworkingDisabled,
NetworkManagerErrorNetworkManagerNotAvailable
};
Q_ENUM(NetworkManagerError)
explicit NetworkManager(QObject *parent = 0);
explicit NetworkManager(QObject *parent = nullptr);
bool available();
bool wifiAvailable();

View File

@ -69,7 +69,6 @@ typedef QList<MqttPolicy> MqttPolicies;
class NymeaConfiguration : public QObject
{
Q_OBJECT
Q_ENUMS(ConfigurationError)
public:
enum ConfigurationError {
@ -82,6 +81,7 @@ public:
ConfigurationErrorBluetoothHardwareNotAvailable,
ConfigurationErrorInvalidCertificate
};
Q_ENUM(ConfigurationError)
explicit NymeaConfiguration(QObject *parent = nullptr);

View File

@ -201,6 +201,8 @@ void NymeaCore::init() {
connect(m_deviceManager, &DeviceManagerImplementation::deviceRemoved, this, &NymeaCore::deviceRemoved);
connect(m_deviceManager, &DeviceManagerImplementation::deviceDisappeared, this, &NymeaCore::onDeviceDisappeared);
connect(m_deviceManager, &DeviceManagerImplementation::actionExecutionFinished, this, &NymeaCore::actionExecutionFinished);
connect(m_deviceManager, &DeviceManagerImplementation::browserItemExecutionFinished, this, &NymeaCore::browserItemExecutionFinished);
connect(m_deviceManager, &DeviceManagerImplementation::browserItemActionExecutionFinished, this, &NymeaCore::browserItemActionExecutionFinished);
connect(m_deviceManager, &DeviceManagerImplementation::devicesDiscovered, this, &NymeaCore::devicesDiscovered);
connect(m_deviceManager, &DeviceManagerImplementation::deviceSetupFinished, this, &NymeaCore::deviceSetupFinished);
connect(m_deviceManager, &DeviceManagerImplementation::deviceReconfigurationFinished, this, &NymeaCore::deviceReconfigurationFinished);
@ -447,13 +449,44 @@ Device::DeviceError NymeaCore::executeAction(const Action &action)
return ret;
}
Device::DeviceError NymeaCore::executeBrowserItem(const BrowserAction &browserAction)
{
Device::DeviceError ret = m_deviceManager->executeBrowserItem(browserAction);
if (ret == Device::DeviceErrorNoError) {
m_logger->logBrowserAction(browserAction);
} else if (ret == Device::DeviceErrorAsync) {
m_pendingBrowserActions.insert(browserAction.id(), browserAction);
} else {
m_logger->logBrowserAction(browserAction, Logging::LoggingLevelAlert, ret);
}
return ret;
}
Device::DeviceError NymeaCore::executeBrowserItemAction(const BrowserItemAction &browserItemAction)
{
Device::DeviceError ret = m_deviceManager->executeBrowserItemAction(browserItemAction);
if (ret == Device::DeviceErrorNoError) {
m_logger->logBrowserItemAction(browserItemAction);
} else if (ret == Device::DeviceErrorAsync) {
m_pendingBrowserItemActions.insert(browserItemAction.id(), browserItemAction);
} else {
m_logger->logBrowserItemAction(browserItemAction, Logging::LoggingLevelAlert, ret);
}
return ret;
}
/*! Execute the given \a ruleActions. */
void NymeaCore::executeRuleActions(const QList<RuleAction> ruleActions)
{
QList<Action> actions;
QList<BrowserAction> browserActions;
foreach (const RuleAction &ruleAction, ruleActions) {
if (ruleAction.type() == RuleAction::TypeDevice) {
Device *device = m_deviceManager->findConfiguredDevice(ruleAction.deviceId());
if (!device) {
qCWarning(dcRuleEngine()) << "Unable to find device" << ruleAction.deviceId() << "for rule action" << ruleAction;
continue;
}
ActionTypeId actionTypeId = ruleAction.actionTypeId();
ParamList params;
bool ok = true;
@ -483,6 +516,14 @@ void NymeaCore::executeRuleActions(const QList<RuleAction> ruleActions)
Action action(actionTypeId, device->id());
action.setParams(params);
actions.append(action);
} else if (ruleAction.type() == RuleAction::TypeBrowser) {
Device *device = m_deviceManager->findConfiguredDevice(ruleAction.deviceId());
if (!device) {
qCWarning(dcRuleEngine()) << "Unable to find device" << ruleAction.deviceId() << "for rule action" << ruleAction;
continue;
}
BrowserAction browserAction(ruleAction.deviceId(), ruleAction.browserItemId());
browserActions.append(browserAction);
} else {
QList<Device*> devices = m_deviceManager->findConfiguredDevices(ruleAction.interface());
foreach (Device* device, devices) {
@ -554,6 +595,25 @@ void NymeaCore::executeRuleActions(const QList<RuleAction> ruleActions)
// if (status != Device::DeviceErrorAsync)
// m_logger->logAction(action, status == Device::DeviceErrorNoError ? Logging::LoggingLevelInfo : Logging::LoggingLevelAlert, status);
}
foreach (const BrowserAction &browserAction, browserActions) {
Device::DeviceError status = executeBrowserItem(browserAction);
switch(status) {
case Device::DeviceErrorNoError:
break;
case Device::DeviceErrorSetupFailed:
qCWarning(dcRuleEngine) << "Error executing action. Device setup failed.";
break;
case Device::DeviceErrorAsync:
qCDebug(dcRuleEngine) << "Executing asynchronous action.";
break;
case Device::DeviceErrorInvalidParameter:
qCWarning(dcRuleEngine) << "Error executing action. Invalid action parameter.";
break;
default:
qCWarning(dcRuleEngine) << "Error executing action:" << status;
}
}
}
/*! Calls the metheod RuleEngine::removeRule(\a id).
@ -797,6 +857,20 @@ void NymeaCore::actionExecutionFinished(const ActionId &id, Device::DeviceError
m_logger->logAction(action, status == Device::DeviceErrorNoError ? Logging::LoggingLevelInfo : Logging::LoggingLevelAlert, status);
}
void NymeaCore::browserItemExecutionFinished(const ActionId &id, Device::DeviceError status)
{
emit browserItemExecuted(id, status);
BrowserAction action = m_pendingBrowserActions.take(id);
m_logger->logBrowserAction(action, status == Device::DeviceErrorNoError ? Logging::LoggingLevelInfo : Logging::LoggingLevelAlert, status);
}
void NymeaCore::browserItemActionExecutionFinished(const ActionId &id, Device::DeviceError status)
{
emit browserItemActionExecuted(id, status);
BrowserItemAction action = m_pendingBrowserItemActions.take(id);
m_logger->logBrowserItemAction(action, status == Device::DeviceErrorNoError ? Logging::LoggingLevelInfo : Logging::LoggingLevelAlert, status);
}
void NymeaCore::onDeviceDisappeared(const DeviceId &deviceId)
{
Device *device = m_deviceManager->findConfiguredDevice(deviceId);

View File

@ -72,6 +72,8 @@ public:
Device::DeviceError removeConfiguredDevice(const DeviceId &deviceId, const RuleEngine::RemovePolicy &removePolicy);
Device::DeviceError executeAction(const Action &action);
Device::DeviceError executeBrowserItem(const BrowserAction &browserAction);
Device::DeviceError executeBrowserItemAction(const BrowserItemAction &browserItemAction);
void executeRuleActions(const QList<RuleAction> ruleActions);
@ -106,6 +108,8 @@ signals:
void deviceChanged(Device *device);
void deviceSettingChanged(const DeviceId deviceId, const ParamTypeId &settingParamTypeId, const QVariant &value);
void actionExecuted(const ActionId &id, Device::DeviceError status);
void browserItemExecuted(const ActionId &id, Device::DeviceError status);
void browserItemActionExecuted(const ActionId &id, Device::DeviceError status);
void devicesDiscovered(const DeviceClassId &deviceClassId, const QList<DeviceDescriptor> deviceDescriptors);
void deviceSetupFinished(Device *device, Device::DeviceError status);
@ -139,12 +143,16 @@ private:
System *m_system;
QHash<ActionId, Action> m_pendingActions;
QHash<ActionId, BrowserAction> m_pendingBrowserActions;
QHash<ActionId, BrowserItemAction> m_pendingBrowserItemActions;
QList<RuleId> m_executingRules;
private slots:
void gotEvent(const Event &event);
void onDateTimeChanged(const QDateTime &dateTime);
void actionExecutionFinished(const ActionId &id, Device::DeviceError status);
void browserItemExecutionFinished(const ActionId &id, Device::DeviceError status);
void browserItemActionExecutionFinished(const ActionId &id, Device::DeviceError status);
void onDeviceDisappeared(const DeviceId &deviceId);
void deviceManagerLoaded();

View File

@ -23,10 +23,10 @@
#define RULE_H
#include "types/state.h"
#include "types/ruleaction.h"
#include "types/eventdescriptor.h"
#include "devices/stateevaluator.h"
#include "time/timedescriptor.h"
#include "ruleaction.h"
#include "stateevaluator.h"
#include <QUuid>

View File

@ -46,17 +46,21 @@
#include "ruleaction.h"
/*! Constructs a RuleAction with the given by \a actionTypeId, \a deviceId and \a params. */
/*! Constructs a RuleAction with the given by \a actionTypeId, \a deviceId and \a params.
* Use this to create a RuleAction for regular actions, that is, identifying the Action by deviceId and actionTypeId.
*/
RuleAction::RuleAction(const ActionTypeId &actionTypeId, const DeviceId &deviceId, const RuleActionParamList &params):
m_id(ActionId::createActionId()),
m_actionTypeId(actionTypeId),
m_deviceId(deviceId),
m_actionTypeId(actionTypeId),
m_ruleActionParams(params)
{
}
/*! Constructs a RuleAction with the given by \a interface and \a interfaceAction. */
/*! Constructs a RuleAction with the given by \a interface and \a interfaceAction.
* This will create an interface based RuleAction. Meaning, the Action is idenfified by an interface and and interfaceAction.
*/
RuleAction::RuleAction(const QString &interface, const QString &interfaceAction, const RuleActionParamList &params) :
m_interface(interface),
m_interfaceAction(interfaceAction),
@ -65,11 +69,22 @@ RuleAction::RuleAction(const QString &interface, const QString &interfaceAction,
}
/*! Constructs a RuleAction with the given by \a interface and \a interfaceAction.
* Use this to create a RuleAction for executing browser items.
*/
RuleAction::RuleAction(const DeviceId &deviceId, const QString &browserItemId):
m_deviceId(deviceId),
m_browserItemId(browserItemId)
{
}
/*! Constructs a copy of the given \a other RuleAction. */
RuleAction::RuleAction(const RuleAction &other) :
m_id(other.id()),
m_actionTypeId(other.actionTypeId()),
m_deviceId(other.deviceId()),
m_actionTypeId(other.actionTypeId()),
m_browserItemId(other.browserItemId()),
m_interface(other.interface()),
m_interfaceAction(other.interfaceAction()),
m_ruleActionParams(other.ruleActionParams())
@ -86,13 +101,25 @@ ActionId RuleAction::id() const
/*! Return true, if the actionTypeId and the deviceId of this RuleAction are valid (set).*/
bool RuleAction::isValid() const
{
return (!m_actionTypeId.isNull() && !m_deviceId.isNull()) || (!m_interface.isEmpty() && !m_interfaceAction.isEmpty());
return (!m_actionTypeId.isNull() && !m_deviceId.isNull())
|| (!m_interface.isEmpty() && !m_interfaceAction.isEmpty())
|| (!m_deviceId.isNull() && !m_browserItemId.isEmpty());
}
/*! Returns whether this RuleAction is targetting a specific device or rather an interface. */
RuleAction::Type RuleAction::type() const
{
return (!m_actionTypeId.isNull() && !m_deviceId.isNull()) ? TypeDevice : TypeInterface;
if (!m_deviceId.isNull() && !m_actionTypeId.isNull()) {
return TypeDevice;
}
if (!m_deviceId.isNull() && !m_browserItemId.isEmpty()) {
return TypeBrowser;
}
if (!m_interface.isEmpty() && !m_interfaceAction.isEmpty()) {
return TypeInterface;
}
// uhmm... invalid...
return TypeDevice;
}
/*! Return true, if this RuleAction contains a \l{RuleActionParam} which is based on an EventTypeId.*/
@ -129,12 +156,25 @@ Action RuleAction::toAction() const
return action;
}
/*! Converts this \l{RuleAction} to a \l{BrowserItemAction}.
* \sa BrowserItemAction, */
BrowserItemAction RuleAction::toBrowserItemAction() const
{
return BrowserItemAction(m_deviceId, m_browserItemId);
}
/*! Returns the actionTypeId of this RuleAction. */
ActionTypeId RuleAction::actionTypeId() const
{
return m_actionTypeId;
}
/*! Returns the browserItemId of this RuleAction. */
QString RuleAction::browserItemId() const
{
return m_browserItemId;
}
/*! Returns the deviceId of this RuleAction. */
DeviceId RuleAction::deviceId() const
{
@ -204,7 +244,7 @@ void RuleAction::operator=(const RuleAction &other)
/*! Print a RuleAction including RuleActionParams to QDebug. */
QDebug operator<<(QDebug dbg, const RuleAction &ruleAction)
{
dbg.nospace() << "RuleAction(ActionTypeId:" << ruleAction.actionTypeId().toString() << ", DeviceId:" << ruleAction.deviceId().toString() << ", Interface:" << ruleAction.interface() << ", InterfaceAction:" << ruleAction.interfaceAction() << ")" << endl;
dbg.nospace() << "RuleAction(ActionTypeId:" << ruleAction.actionTypeId().toString() << ", DeviceId:" << ruleAction.deviceId().toString() << ", Interface:" << ruleAction.interface() << ", InterfaceAction:" << ruleAction.interfaceAction() << ", BrowserItemId:" << ruleAction.browserItemId() << ")" << endl;
for (int i = 0; i < ruleAction.ruleActionParams().count(); i++) {
dbg.nospace() << " " << i << ": " << ruleAction.ruleActionParams().at(i) << endl;
}

View File

@ -25,7 +25,8 @@
#define RULEACTION_H
#include "libnymea.h"
#include "action.h"
#include "types/action.h"
#include "types/browseritemaction.h"
#include "ruleactionparam.h"
class LIBNYMEA_EXPORT RuleAction
@ -33,10 +34,12 @@ class LIBNYMEA_EXPORT RuleAction
public:
enum Type {
TypeDevice,
TypeInterface
TypeInterface,
TypeBrowser
};
explicit RuleAction(const ActionTypeId &actionTypeId = ActionTypeId(), const DeviceId &deviceId = DeviceId(), const RuleActionParamList &params = RuleActionParamList());
explicit RuleAction(const QString &interface, const QString &interfaceAction, const RuleActionParamList &params = RuleActionParamList());
explicit RuleAction(const DeviceId &deviceId, const QString &browserItemId);
RuleAction(const RuleAction &other);
ActionId id() const;
@ -48,9 +51,11 @@ public:
bool isStateBased() const;
Action toAction() const;
BrowserItemAction toBrowserItemAction() const;
ActionTypeId actionTypeId() const;
DeviceId deviceId() const;
ActionTypeId actionTypeId() const;
QString browserItemId() const;
QString interface() const;
QString interfaceAction() const;
@ -64,8 +69,9 @@ public:
private:
ActionId m_id;
ActionTypeId m_actionTypeId;
DeviceId m_deviceId;
ActionTypeId m_actionTypeId;
QString m_browserItemId;
QString m_interface;
QString m_interfaceAction;
RuleActionParamList m_ruleActionParams;

View File

@ -28,7 +28,7 @@
#include <QString>
#include <QVariant>
#include "param.h"
#include "types/param.h"
#include "libnymea.h"
#include "typeutils.h"

View File

@ -912,7 +912,7 @@ bool RuleEngine::containsState(const StateEvaluator &stateEvaluator, const Event
RuleEngine::RuleError RuleEngine::checkRuleAction(const RuleAction &ruleAction, const Rule &rule)
{
if (!ruleAction.isValid()) {
qWarning(dcRuleEngine()) << "Action is incomplete. It must have either actionTypeId and deviceId, or interface and interfaceAction";
qWarning(dcRuleEngine()) << "Action is incomplete. It must have either deviceId and actionTypeId/browserItemId, or interface and interfaceAction:" << ruleAction;
return RuleErrorActionTypeNotFound;
}
@ -920,7 +920,7 @@ RuleEngine::RuleError RuleEngine::checkRuleAction(const RuleAction &ruleAction,
if (ruleAction.type() == RuleAction::TypeDevice) {
Device *device = NymeaCore::instance()->deviceManager()->findConfiguredDevice(ruleAction.deviceId());
if (!device) {
qCWarning(dcRuleEngine) << "Cannot create rule. No configured device for action with actionTypeId" << ruleAction.actionTypeId();
qCWarning(dcRuleEngine) << "Cannot create rule. No configured device with ID" << ruleAction.deviceId();
return RuleErrorDeviceNotFound;
}
@ -942,30 +942,44 @@ RuleEngine::RuleError RuleEngine::checkRuleAction(const RuleAction &ruleAction,
qCWarning(dcRuleEngine()) << "Cannot create rule. Interface" << iface.name() << "does not implement action" << ruleAction.interfaceAction();
return RuleError::RuleErrorActionTypeNotFound;
}
} else if (ruleAction.type() == RuleAction::TypeBrowser) {
Device *device = NymeaCore::instance()->deviceManager()->findConfiguredDevice(ruleAction.deviceId());
if (!device) {
qCWarning(dcRuleEngine) << "Cannot create rule. No configured device with ID" << ruleAction.deviceId();
return RuleErrorDeviceNotFound;
}
if (ruleAction.browserItemId().isEmpty()) {
qCWarning(dcRuleEngine()) << "Cannot create rule with empty browserItemId";
return RuleErrorInvalidRuleActionParameter;
}
} else {
return RuleErrorActionTypeNotFound;
}
// Verify given params
foreach (const RuleActionParam &ruleActionParam, ruleAction.ruleActionParams()) {
RuleError ruleActionParamError = checkRuleActionParam(ruleActionParam, actionType, rule);
if (ruleActionParamError != RuleErrorNoError) {
return ruleActionParamError;
}
}
// Verify all required params are given
foreach (const ParamType &paramType, actionType.paramTypes()) {
bool found = false;
// Not all rule actions might have an actiontype (e.g. browser item executions)
if (!actionType.id().isNull()) {
// Verify given params
foreach (const RuleActionParam &ruleActionParam, ruleAction.ruleActionParams()) {
if (ruleActionParam.paramTypeId() == paramType.id()
|| ruleActionParam.paramName() == paramType.name()) {
found = true;
break;
RuleError ruleActionParamError = checkRuleActionParam(ruleActionParam, actionType, rule);
if (ruleActionParamError != RuleErrorNoError) {
return ruleActionParamError;
}
}
if (!found) {
return RuleErrorMissingParameter;
// Verify all required params are given
foreach (const ParamType &paramType, actionType.paramTypes()) {
bool found = false;
foreach (const RuleActionParam &ruleActionParam, ruleAction.ruleActionParams()) {
if (ruleActionParam.paramTypeId() == paramType.id()
|| ruleActionParam.paramName() == paramType.name()) {
found = true;
break;
}
}
if (!found) {
return RuleErrorMissingParameter;
}
}
}
@ -1190,66 +1204,117 @@ void RuleEngine::saveRule(const Rule &rule)
rule.stateEvaluator().dumpToSettings(settings, "stateEvaluator");
// Save ruleActions
int i = 0;
settings.beginGroup("ruleActions");
foreach (const RuleAction &action, rule.actions()) {
settings.beginGroup(QString::number(i));
if (!action.deviceId().isNull() && !action.actionTypeId().isNull()) {
settings.setValue("deviceId", action.deviceId().toString());
settings.setValue("actionTypeId", action.actionTypeId().toString());
} else {
settings.setValue("interface", action.interface());
settings.setValue("interfaceAction", action.interfaceAction());
}
foreach (const RuleActionParam &param, action.ruleActionParams()) {
if (!param.paramTypeId().isNull()) {
settings.beginGroup("RuleActionParam-" + param.paramTypeId().toString());
} else {
settings.beginGroup("RuleActionParam-" + param.paramName());
}
settings.setValue("valueType", static_cast<int>(param.value().type()));
settings.setValue("value", param.value());
if (param.isEventBased()) {
settings.setValue("eventTypeId", param.eventTypeId().toString());
settings.setValue("eventParamTypeId", param.eventParamTypeId());
} else if (param.isStateBased()) {
settings.setValue("stateDeviceId", param.stateDeviceId().toString());
settings.setValue("stateTypeId", param.stateTypeId());
}
settings.endGroup();
}
i++;
settings.endGroup();
}
saveRuleActions(&settings, rule.actions());
settings.endGroup();
// Save ruleExitActions
settings.beginGroup("ruleExitActions");
i = 0;
foreach (const RuleAction &action, rule.exitActions()) {
settings.beginGroup(QString::number(i));
if (!action.deviceId().isNull() && !action.actionTypeId().isNull()) {
settings.setValue("deviceId", action.deviceId().toString());
settings.setValue("actionTypeId", action.actionTypeId().toString());
saveRuleActions(&settings, rule.exitActions());
settings.endGroup();
qCDebug(dcRuleEngineDebug()) << "Saved rule to config:" << rule;
}
void RuleEngine::saveRuleActions(NymeaSettings *settings, const QList<RuleAction> &ruleActions)
{
int i = 0;
foreach (const RuleAction &action, ruleActions) {
settings->beginGroup(QString::number(i));
if (action.type() == RuleAction::TypeDevice) {
settings->setValue("deviceId", action.deviceId().toString());
settings->setValue("actionTypeId", action.actionTypeId().toString());
} else if (action.type() == RuleAction::TypeBrowser) {
settings->setValue("deviceId", action.deviceId().toString());
settings->setValue("browserItemId", action.browserItemId());
} else if (action.type() == RuleAction::TypeInterface){
settings->setValue("interface", action.interface());
settings->setValue("interfaceAction", action.interfaceAction());
} else {
settings.setValue("interface", action.interface());
settings.setValue("interfaceAction", action.interfaceAction());
Q_ASSERT_X(false, "RuleEngine::saveRule", "Unhandled rule action type.");
}
foreach (const RuleActionParam &param, action.ruleActionParams()) {
if (!param.paramTypeId().isNull()) {
settings.beginGroup("RuleActionParam-" + param.paramTypeId().toString());
settings->beginGroup("RuleActionParam-" + param.paramTypeId().toString());
} else {
settings.beginGroup("RuleActionParam-" + param.paramName());
settings->beginGroup("RuleActionParam-" + param.paramName());
}
settings.setValue("valueType", static_cast<int>(param.value().type()));
settings.setValue("value", param.value());
settings.endGroup();
settings->setValue("valueType", static_cast<int>(param.value().type()));
settings->setValue("value", param.value());
if (param.isEventBased()) {
settings->setValue("eventTypeId", param.eventTypeId().toString());
settings->setValue("eventParamTypeId", param.eventParamTypeId());
} else if (param.isStateBased()) {
settings->setValue("stateDeviceId", param.stateDeviceId().toString());
settings->setValue("stateTypeId", param.stateTypeId());
}
settings->endGroup();
}
i++;
settings.endGroup();
settings->endGroup();
}
settings.endGroup();
qCDebug(dcRuleEngineDebug()) << "Saved rule to config:" << rule;
}
QList<RuleAction> RuleEngine::loadRuleActions(NymeaSettings *settings)
{
QList<RuleAction> actions;
foreach (const QString &actionNumber, settings->childGroups()) {
settings->beginGroup(actionNumber);
RuleActionParamList params;
foreach (QString paramTypeIdString, settings->childGroups()) {
if (paramTypeIdString.startsWith("RuleActionParam-")) {
settings->beginGroup(paramTypeIdString);
QString strippedParamTypeIdString = paramTypeIdString.remove(QRegExp("^RuleActionParam-"));
EventTypeId eventTypeId = EventTypeId(settings->value("eventTypeId", EventTypeId()).toString());
ParamTypeId eventParamTypeId = ParamTypeId(settings->value("eventParamTypeId", ParamTypeId()).toString());
DeviceId stateDeviceId = DeviceId(settings->value("stateDeviceId", DeviceId()).toString());
StateTypeId stateTypeId = StateTypeId(settings->value("stateTypeId", StateTypeId()).toString());
QVariant value = settings->value("value");
if (settings->contains("valueType")) {
QVariant::Type valueType = static_cast<QVariant::Type>(settings->value("valueType").toInt());
// Note: only warn, and continue with the QVariant guessed type
if (valueType == QVariant::Invalid) {
qCWarning(dcRuleEngine()) << "Could not load the value type of the rule action param " << strippedParamTypeIdString << ". The value type will be guessed by QVariant.";
} else if (!value.canConvert(static_cast<int>(valueType))) {
qCWarning(dcRuleEngine()) << "Error loading rule action. Could not convert the rule action param value" << value << "to the stored type" << valueType;
} else {
value.convert(static_cast<int>(valueType));
}
}
RuleActionParam param;
if (!ParamTypeId(strippedParamTypeIdString).isNull()) {
// By ParamTypeId
param = RuleActionParam(ParamTypeId(strippedParamTypeIdString), value);
} else {
// By param name
param = RuleActionParam(strippedParamTypeIdString, value);
}
param.setEventTypeId(eventTypeId);
param.setEventParamTypeId(eventParamTypeId);
param.setStateDeviceId(stateDeviceId);
param.setStateTypeId(stateTypeId);
params.append(param);
settings->endGroup();
}
}
if (settings->contains("actionTypeId") && settings->contains("deviceId")) {
RuleAction action = RuleAction(ActionTypeId(settings->value("actionTypeId").toString()), DeviceId(settings->value("deviceId").toString()));
action.setRuleActionParams(params);
actions.append(action);
} else if (settings->contains("deviceId") && settings->contains("browserItemId")) {
RuleAction action = RuleAction(DeviceId(settings->value("deviceId").toString()), settings->value("browserItemId").toString());
actions.append(action);
} else if (settings->contains("interface") && settings->contains("interfaceAction")){
RuleAction action = RuleAction(settings->value("interface").toString(), settings->value("interfaceAction").toString());
action.setRuleActionParams(params);
actions.append(action);
}
settings->endGroup();
}
return actions;
}
void RuleEngine::init()
@ -1411,109 +1476,13 @@ void RuleEngine::init()
// Load actions
QList<RuleAction> actions;
settings.beginGroup("ruleActions");
foreach (const QString &actionNumber, settings.childGroups()) {
settings.beginGroup(actionNumber);
RuleActionParamList params;
foreach (QString paramTypeIdString, settings.childGroups()) {
if (paramTypeIdString.startsWith("RuleActionParam-")) {
settings.beginGroup(paramTypeIdString);
QString strippedParamTypeIdString = paramTypeIdString.remove(QRegExp("^RuleActionParam-"));
EventTypeId eventTypeId = EventTypeId(settings.value("eventTypeId", EventTypeId()).toString());
ParamTypeId eventParamTypeId = ParamTypeId(settings.value("eventParamTypeId", ParamTypeId()).toString());
DeviceId stateDeviceId = DeviceId(settings.value("stateDeviceId", DeviceId()).toString());
StateTypeId stateTypeId = StateTypeId(settings.value("stateTypeId", StateTypeId()).toString());
QVariant value = settings.value("value");
if (settings.contains("valueType")) {
QVariant::Type valueType = static_cast<QVariant::Type>(settings.value("valueType").toInt());
// Note: only warn, and continue with the QVariant guessed type
if (valueType == QVariant::Invalid) {
qCWarning(dcRuleEngine()) << name << idString << "Could not load the value type of the rule action param " << strippedParamTypeIdString << ". The value type will be guessed by QVariant.";
} else if (!value.canConvert(static_cast<int>(valueType))) {
qCWarning(dcRuleEngine()) << "Error loading rule" << name << idString << ". Could not convert the rule action param value" << value << "to the stored type" << valueType;
} else {
value.convert(static_cast<int>(valueType));
}
}
RuleActionParam param;
if (!ParamTypeId(strippedParamTypeIdString).isNull()) {
// By ParamTypeId
param = RuleActionParam(ParamTypeId(strippedParamTypeIdString), value);
} else {
// By param name
param = RuleActionParam(strippedParamTypeIdString, value);
}
param.setEventTypeId(eventTypeId);
param.setEventParamTypeId(eventParamTypeId);
param.setStateDeviceId(stateDeviceId);
param.setStateTypeId(stateTypeId);
params.append(param);
settings.endGroup();
}
}
if (settings.contains("actionTypeId") && settings.contains("deviceId")) {
RuleAction action = RuleAction(ActionTypeId(settings.value("actionTypeId").toString()), DeviceId(settings.value("deviceId").toString()));
action.setRuleActionParams(params);
actions.append(action);
} else if (settings.contains("interface") && settings.contains("interfaceAction")){
RuleAction action = RuleAction(settings.value("interface").toString(), settings.value("interfaceAction").toString());
action.setRuleActionParams(params);
actions.append(action);
}
settings.endGroup();
}
actions = loadRuleActions(&settings);
settings.endGroup();
// Load exit actions
QList<RuleAction> exitActions;
settings.beginGroup("ruleExitActions");
foreach (const QString &actionNumber, settings.childGroups()) {
settings.beginGroup(actionNumber);
RuleActionParamList params;
foreach (QString paramTypeIdString, settings.childGroups()) {
if (paramTypeIdString.startsWith("RuleActionParam-")) {
settings.beginGroup(paramTypeIdString);
QString strippedParamTypeIdString = paramTypeIdString.remove(QRegExp("^RuleActionParam-"));
QVariant value = settings.value("value");
if (settings.contains("valueType")) {
QVariant::Type valueType = static_cast<QVariant::Type>(settings.value("valueType").toInt());
// Note: only warn, and continue with the QVariant guessed type
if (valueType == QVariant::Invalid) {
qCWarning(dcRuleEngine()) << name << idString << "Could not load the value type of the rule action param " << strippedParamTypeIdString << ". The value type will be guessed by QVariant.";
} else if (!value.canConvert(static_cast<int>(valueType))) {
qCWarning(dcRuleEngine()) << "Error loading rule" << name << idString << ". Could not convert the rule action param value" << value << "to the stored type" << valueType;
} else {
value.convert(static_cast<int>(valueType));
}
}
if (!ParamTypeId(strippedParamTypeIdString).isNull()) {
RuleActionParam param(ParamTypeId(strippedParamTypeIdString), value);
params.append(param);
} else {
RuleActionParam param(strippedParamTypeIdString, value);
params.append(param);
}
settings.endGroup();
}
}
if (settings.contains("actionTypeId") && settings.contains("deviceId")) {
RuleAction action = RuleAction(ActionTypeId(settings.value("actionTypeId").toString()), DeviceId(settings.value("deviceId").toString()));
action.setRuleActionParams(params);
exitActions.append(action);
} else if (settings.contains("interface") && settings.contains("interfaceAction")) {
RuleAction action = RuleAction(settings.value("interface").toString(),settings.value("interfaceAction").toString());
action.setRuleActionParams(params);
exitActions.append(action);
}
settings.endGroup();
}
exitActions = loadRuleActions(&settings);
settings.endGroup();
Rule rule;

View File

@ -23,21 +23,20 @@
#define RULEENGINE_H
#include "rule.h"
#include "stateevaluator.h"
#include "types/event.h"
#include "types/deviceclass.h"
#include "devices/stateevaluator.h"
#include <QObject>
#include <QList>
#include <QUuid>
#include <QSettings>
namespace nymeaserver {
class RuleEngine : public QObject
{
Q_OBJECT
Q_ENUMS(RuleError)
Q_ENUMS(RemovePolicy)
public:
enum RuleError {
RuleErrorNoError,
@ -62,11 +61,13 @@ public:
RuleErrorNoExitActions,
RuleErrorInterfaceNotFound
};
Q_ENUM(RuleError)
enum RemovePolicy {
RemovePolicyCascade,
RemovePolicyUpdate
};
Q_ENUM(RemovePolicy)
explicit RuleEngine(QObject *parent = nullptr);
~RuleEngine();
@ -112,6 +113,8 @@ private:
void appendRule(const Rule &rule);
void saveRule(const Rule &rule);
void saveRuleActions(NymeaSettings *settings, const QList<RuleAction> &ruleActions);
QList<RuleAction> loadRuleActions(NymeaSettings *settings);
private:
QList<RuleId> m_ruleIds; // Keeping a list of RuleIds to keep sorting order...

View File

@ -117,9 +117,10 @@ void TcpServer::sendData(const QUuid &clientId, const QByteArray &data)
QTcpSocket *client = nullptr;
client = m_clientList.value(clientId);
if (client) {
qCDebug(dcTcpServer()) << "Sending to client" << clientId.toString() << data;
client->write(data + '\n');
} else {
qWarning(dcTcpServer()) << "Client" << clientId << "unknown to this transport";
qCWarning(dcTcpServer()) << "Client" << clientId << "unknown to this transport";
}
}

View File

@ -31,7 +31,6 @@ namespace nymeaserver {
class RepeatingOption
{
Q_GADGET
Q_ENUMS(RepeatingMode)
public:
enum RepeatingMode {
@ -42,6 +41,7 @@ public:
RepeatingModeMonthly,
RepeatingModeYearly
};
Q_ENUM(RepeatingMode)
RepeatingOption();
RepeatingOption(const RepeatingMode &mode, const QList<int> &weekDays = QList<int>(), const QList<int> &monthDays = QList<int>());

View File

@ -33,7 +33,6 @@ class PushButtonDBusService;
class UserManager : public QObject
{
Q_OBJECT
Q_ENUMS(UserError)
public:
enum UserError {
UserErrorNoError,
@ -44,6 +43,7 @@ public:
UserErrorTokenNotFound,
UserErrorPermissionDenied
};
Q_ENUM(UserError)
explicit UserManager(const QString &dbName, QObject *parent = nullptr);

View File

@ -30,7 +30,6 @@
class LIBNYMEA_EXPORT CoapOption
{
Q_GADGET
Q_ENUMS(Option)
public:
// Options format: https://tools.ietf.org/html/rfc7252#section-3.1
@ -54,6 +53,7 @@ public:
ProxyScheme = 39,
Size1 = 60
};
Q_ENUM(Option)
CoapOption();

View File

@ -154,6 +154,18 @@ PluginId Device::pluginId() const
return m_plugin->pluginId();
}
/*! Returns the \l{DeviceClass} of this device. */
DeviceClass Device::deviceClass() const
{
return m_deviceClass;
}
/*! Returns the the \l{DevicePlugin} this Device is managed by. */
DevicePlugin *Device::plugin() const
{
return m_plugin;
}
/*! Returns the name of this Device. This is visible to the user. */
QString Device::name() const
{
@ -437,3 +449,14 @@ Devices Devices::filterByDeviceClassId(const DeviceClassId &deviceClassId)
}
return ret;
}
Devices Devices::filterByParentDeviceId(const DeviceId &deviceId)
{
Devices ret;
foreach (Device *device, *this) {
if (device->parentId() == deviceId) {
ret << device;
}
}
return ret;
}

View File

@ -30,6 +30,7 @@
#include "types/deviceclass.h"
#include "types/state.h"
#include "types/param.h"
#include "types/browseritem.h"
#include <QObject>
#include <QUuid>
@ -69,7 +70,10 @@ public:
DeviceErrorDeviceInRule,
DeviceErrorDeviceIsChild,
DeviceErrorPairingTransactionIdNotFound,
DeviceErrorParameterNotWritable
DeviceErrorParameterNotWritable,
DeviceErrorItemNotFound,
DeviceErrorItemNotExecutable,
DeviceErrorUnsupportedFeature,
};
Q_ENUM(DeviceError)
@ -80,12 +84,32 @@ public:
};
Q_ENUM(DeviceSetupStatus)
class BrowseResult {
public:
BrowseResult(): m_id(QUuid::createUuid()) {}
Device::DeviceError status = Device::DeviceErrorNoError;
BrowserItems items;
QUuid id() const { return m_id; }
private:
QUuid m_id;
};
class BrowserItemResult {
public:
BrowserItemResult(): m_id(QUuid::createUuid()) {}
Device::DeviceError status = Device::DeviceErrorNoError;
BrowserItem item;
QUuid id() const { return m_id; }
private:
QUuid m_id;
};
DeviceId id() const;
DeviceClassId deviceClassId() const;
PluginId pluginId() const;
DeviceClass deviceClass() const;
DevicePlugin* plugin();
DevicePlugin* plugin() const;
QString name() const;
void setName(const QString &name);
@ -156,6 +180,7 @@ public:
Device* findByParams(const ParamList &params) const;
Devices filterByParam(const ParamTypeId &paramTypeId, const QVariant &value = QVariant());
Devices filterByDeviceClassId(const DeviceClassId &deviceClassId);
Devices filterByParentDeviceId(const DeviceId &deviceId);
};
Q_DECLARE_METATYPE(Device::DeviceError)

View File

@ -29,6 +29,9 @@
#include "deviceplugin.h"
#include "types/interface.h"
#include "types/vendor.h"
#include "types/browseritem.h"
#include "types/browseraction.h"
#include "types/browseritemaction.h"
class DeviceManager : public QObject
{
@ -69,6 +72,13 @@ public:
virtual Device::DeviceError removeConfiguredDevice(const DeviceId &deviceId) = 0;
virtual Device::DeviceError executeAction(const Action &action) = 0;
virtual Device::BrowseResult browseDevice(const DeviceId &deviceId, const QString &itemId, const QLocale &locale) = 0;
virtual Device::BrowserItemResult browserItemDetails(const DeviceId &deviceId, const QString &itemId, const QLocale &locale) = 0;
virtual Device::DeviceError executeBrowserItem(const BrowserAction &browserAction) = 0;
virtual Device::DeviceError executeBrowserItemAction(const BrowserItemAction &browserItemAction) = 0;
virtual QString translate(const PluginId &pluginId, const QString &string, const QLocale &locale) = 0;
signals:
@ -85,6 +95,10 @@ signals:
void deviceReconfigurationFinished(Device *device, Device::DeviceError status);
void pairingFinished(const PairingTransactionId &pairingTransactionId, Device::DeviceError status, const DeviceId &deviceId = DeviceId());
void actionExecutionFinished(const ActionId &actionId, Device::DeviceError status);
void browseRequestFinished(const Device::BrowseResult &result);
void browserItemRequestFinished(const Device::BrowserItemResult &result);
void browserItemExecutionFinished(const ActionId &actionId, Device::DeviceError status);
void browserItemActionExecutionFinished(const ActionId &actionId, Device::DeviceError status);
};

View File

@ -248,6 +248,68 @@ Device::DeviceError DevicePlugin::executeAction(Device *device, const Action &ac
return Device::DeviceErrorNoError;
}
/*! Implement this if your devices support browsing (set "browsable" to true in the metadata).
* When the system calls this method, fill the \a result object's items list with entries from the browser.
* If \a itemId is empty it means that the root node of the file system should be returned. Each item in
* the result set shall be uniquely identifiable using its \l{BrowserItem::id}{id} property.
* The system might call this method again, with an \a itemId returned in a previous query, provided
* that item's \l{BrowserItem::browsable} property is true. In this case all children of the given
* item shall be returned. All browser \l {BrowserItem::displayName} properties shall be localized
* using the given \a locale.
* When done, set the \l{BrowserResult::status}{result's status} field approprietly. Set the result's
* status to Device::DeviceErrorAsync if this operation requires async behavior and emit
* \l{browseRequestFinished} when done.
*/
Device::BrowseResult DevicePlugin::browseDevice(Device *device, Device::BrowseResult result, const QString &itemId, const QLocale &locale)
{
Q_UNUSED(device)
Q_UNUSED(itemId)
Q_UNUSED(locale)
result.status = Device::DeviceErrorUnsupportedFeature;
return result;
}
/*! Implement this if your devices support browsing (set "browsable" to true in the metadata).
* When the system calls this method, fetch the item details required to create a BrowserItem
* for the item with the given \a id and append that one item to the \a result.
* When done, set the \l{BrowserResult::status}{result's status} field approprietly. Set the result's
* status to Device::DeviceErrorAsync if this operation requires async behavior and emit
* \l{browserItemRequestFinished} when done.
*/
Device::BrowserItemResult DevicePlugin::browserItem(Device *device, Device::BrowserItemResult result, const QString &itemId, const QLocale &locale)
{
Q_UNUSED(device)
Q_UNUSED(itemId)
Q_UNUSED(locale)
result.status = Device::DeviceErrorUnsupportedFeature;
return result;
}
/*! Implement this if your devices support browsing and execute the itemId defined in \a browserAction.
* Return Device::DeviceErrorAsync if this operation requires async behavior and emit
* \l{browserItemExecutionFinished} when done.
*/
Device::DeviceError DevicePlugin::executeBrowserItem(Device *device, const BrowserAction &browserAction)
{
Q_UNUSED(device)
Q_UNUSED(browserAction)
return Device::DeviceErrorUnsupportedFeature;
}
/*! Implement this if your devices support browsing and execute the item's action for the itemId defined
* in \a browserItemAction.
* Return Device::DeviceErrorAsync if this operation requires async behavior and emit
* \l{browserItemActionExecutionFinished} when done.
*/
Device::DeviceError DevicePlugin::executeBrowserItemAction(Device *device, const BrowserItemAction &browserItemAction)
{
Q_UNUSED(device)
Q_UNUSED(browserItemAction)
return Device::DeviceErrorUnsupportedFeature;
}
/*! Returns the configuration description of this DevicePlugin as a list of \l{ParamType}{ParamTypes}. */
ParamTypes DevicePlugin::configurationDescription() const
{

View File

@ -37,6 +37,8 @@
#include "types/vendor.h"
#include "types/param.h"
#include "types/interface.h"
#include "types/browseraction.h"
#include "types/browseritemaction.h"
#include "hardwaremanager.h"
@ -78,6 +80,11 @@ public:
virtual Device::DeviceError executeAction(Device *device, const Action &action);
virtual Device::BrowseResult browseDevice(Device *device, Device::BrowseResult result, const QString &itemId, const QLocale &locale);
virtual Device::BrowserItemResult browserItem(Device *device, Device::BrowserItemResult result, const QString &itemId, const QLocale &locale);
virtual Device::DeviceError executeBrowserItem(Device *device, const BrowserAction &browserAction);
virtual Device::DeviceError executeBrowserItemAction(Device *device, const BrowserItemAction &browserItemAction);
// Configuration
ParamTypes configurationDescription() const;
Device::DeviceError setConfiguration(const ParamList &configuration);
@ -96,6 +103,10 @@ signals:
void configValueChanged(const ParamTypeId &paramTypeId, const QVariant &value);
void autoDevicesAppeared(const DeviceClassId &deviceClassId, const QList<DeviceDescriptor> &deviceDescriptors);
void autoDeviceDisappeared(const DeviceId &deviceId);
void browseRequestFinished(const Device::BrowseResult &result);
void browserItemRequestFinished(const Device::BrowserItemResult &result);
void browserItemExecutionFinished(const ActionId &actionid, Device::DeviceError status);
void browserItemActionExecutionFinished(const ActionId &actionid, Device::DeviceError status);
protected:
Devices myDevices() const;

View File

@ -162,8 +162,8 @@ void PluginMetadata::parse(const QJsonObject &jsonObject)
QJsonObject deviceClassObject = deviceClassJson.toObject();
/*! Returns a list of all valid JSON properties a DeviceClass JSON definition can have. */
QStringList deviceClassProperties = QStringList() << "id" << "name" << "displayName" << "createMethods" << "setupMethod"
<< "interfaces" << "pairingInfo" << "discoveryParamTypes" << "discoveryParamTypes"
<< "paramTypes" << "settingsTypes" << "stateTypes" << "actionTypes" << "eventTypes";
<< "interfaces" << "browsable" << "pairingInfo" << "discoveryParamTypes" << "discoveryParamTypes"
<< "paramTypes" << "settingsTypes" << "stateTypes" << "actionTypes" << "eventTypes" << "browserItemActionTypes";
QStringList mandatoryDeviceClassProperties = QStringList() << "id" << "name" << "displayName";
QPair<QStringList, QStringList> verificationResult = verifyFields(deviceClassProperties, mandatoryDeviceClassProperties, deviceClassObject);
@ -192,6 +192,7 @@ void PluginMetadata::parse(const QJsonObject &jsonObject)
DeviceClass deviceClass(pluginId(), vendorId, deviceClassId);
deviceClass.setName(deviceClassName);
deviceClass.setDisplayName(deviceClassObject.value("displayName").toString());
deviceClass.setBrowsable(deviceClassObject.value("browsable").toBool());
// Read create methods
DeviceClass::CreateMethods createMethods;
@ -260,9 +261,10 @@ void PluginMetadata::parse(const QJsonObject &jsonObject)
// Read pairing info
deviceClass.setPairingInfo(deviceClassObject.value("pairingInfo").toString());
QList<ActionType> actionTypes;
QList<StateType> stateTypes;
QList<EventType> eventTypes;
ActionTypes actionTypes;
StateTypes stateTypes;
EventTypes eventTypes;
ActionTypes browserItemActionTypes;
// Read StateTypes
int index = 0;
@ -457,6 +459,48 @@ void PluginMetadata::parse(const QJsonObject &jsonObject)
}
deviceClass.setEventTypes(eventTypes);
// BrowserItemActionTypes
index = 0;
foreach (const QJsonValue &browserItemActionTypesJson, deviceClassObject.value("browserItemActionTypes").toArray()) {
QJsonObject at = browserItemActionTypesJson.toObject();
QPair<QStringList, QStringList> verificationResult = verifyFields(ActionType::typeProperties(), ActionType::mandatoryTypeProperties(), at);
// Check mandatory fields
if (!verificationResult.first.isEmpty()) {
qCWarning(dcPluginMetadata()) << "Device class" << deviceClass.name() << " has missing fields" << verificationResult.first.join(", ") << "in browser item action type:" << endl << at;
hasError = true;
continue;
}
// Check if there are any unknown fields
if (!verificationResult.second.isEmpty()) {
qCWarning(dcPluginMetadata()) << pluginName() << "Device class" << deviceClass.name() << "has unknown fields:" << verificationResult.second.join(", ") << "in browser item action type:" << endl << at;
hasError = true;
}
ActionTypeId actionTypeId = ActionTypeId(at.value("id").toString());
QString actionTypeName = at.value("name").toString();
if (!verifyDuplicateUuid(actionTypeId)) {
qCWarning(dcPluginMetadata()) << "Browser Action Type" << actionTypeName << "has duplicate UUID:" << actionTypeId.toString();
hasError = true;
}
ActionType actionType(actionTypeId);
actionType.setName(actionTypeName);
actionType.setDisplayName(at.value("displayName").toString());
actionType.setIndex(index++);
QPair<bool, QList<ParamType> > paramVerification = parseParamTypes(at.value("paramTypes").toArray());
if (!paramVerification.first) {
hasError = true;
break;
} else {
actionType.setParamTypes(paramVerification.second);
}
browserItemActionTypes.append(actionType);
}
deviceClass.setBrowserItemActionTypes(browserItemActionTypes);
// Read interfaces
QStringList interfaces;
foreach (const QJsonValue &value, deviceClassObject.value("interfaces").toArray()) {
@ -616,6 +660,7 @@ QPair<QStringList, QStringList> PluginMetadata::verifyFields(const QStringList &
QPair<bool, ParamTypes> PluginMetadata::parseParamTypes(const QJsonArray &array)
{
bool hasErrors = false;
int index = 0;
QList<ParamType> paramTypes;
foreach (const QJsonValue &paramTypesJson, array) {
@ -626,13 +671,14 @@ QPair<bool, ParamTypes> PluginMetadata::parseParamTypes(const QJsonArray &array)
// Check mandatory fields
if (!verificationResult.first.isEmpty()) {
qCWarning(dcPluginMetadata()) << pluginName() << "Error parsing ParamType: missing fields:" << verificationResult.first.join(", ") << endl << pt;
return QPair<bool, QList<ParamType> >(false, QList<ParamType>());
hasErrors = true;
continue;
}
// Check if there are any unknown fields
if (!verificationResult.second.isEmpty()) {
qCWarning(dcPluginMetadata()) << pluginName() << "Error parsing ParamType: unknown fields:" << verificationResult.second.join(", ") << endl << pt;
return QPair<bool, QList<ParamType> >(false, QList<ParamType>());
hasErrors = true;
}
// Check type
@ -641,14 +687,14 @@ QPair<bool, ParamTypes> PluginMetadata::parseParamTypes(const QJsonArray &array)
qCWarning(dcPluginMetadata()) << pluginName() << QString("Invalid type %1 for param %2 in json file.")
.arg(pt.value("type").toString())
.arg(pt.value("name").toString()).toLatin1().data();
return QPair<bool, QList<ParamType> >(false, QList<ParamType>());
hasErrors = true;
}
ParamTypeId paramTypeId = ParamTypeId(pt.value("id").toString());
QString paramName = pt.value("name").toString();
if (!verifyDuplicateUuid(paramTypeId)) {
qCWarning(dcPluginMetadata()) << "Param" << paramName << "has duplicate UUID:" << paramTypeId.toString();
return QPair<bool, QList<ParamType> >(false, QList<ParamType>());
hasErrors = true;
}
ParamType paramType(paramTypeId, paramName, t, pt.value("defaultValue").toVariant());
paramType.setDisplayName(pt.value("displayName").toString());
@ -665,7 +711,7 @@ QPair<bool, ParamTypes> PluginMetadata::parseParamTypes(const QJsonArray &array)
QPair<bool, Types::InputType> inputTypeVerification = loadAndVerifyInputType(pt.value("inputType").toString());
if (!inputTypeVerification.first) {
qCWarning(dcPluginMetadata()) << pluginName() << QString("Invalid inputType for paramType") << pt;
return QPair<bool, QList<ParamType> >(false, QList<ParamType>());
hasErrors = true;
} else {
paramType.setInputType(inputTypeVerification.second);
}
@ -676,7 +722,7 @@ QPair<bool, ParamTypes> PluginMetadata::parseParamTypes(const QJsonArray &array)
QPair<bool, Types::Unit> unitVerification = loadAndVerifyUnit(pt.value("unit").toString());
if (!unitVerification.first) {
qCWarning(dcPluginMetadata()) << pluginName() << QString("Invalid unit type for paramType") << pt;
return QPair<bool, QList<ParamType> >(false, QList<ParamType>());
hasErrors = true;
} else {
paramType.setUnit(unitVerification.second);
}
@ -692,7 +738,7 @@ QPair<bool, ParamTypes> PluginMetadata::parseParamTypes(const QJsonArray &array)
paramTypes.append(paramType);
}
return QPair<bool, QList<ParamType> >(true, paramTypes);
return QPair<bool, QList<ParamType> >(!hasErrors, paramTypes);
}
QPair<bool, Types::InputType> PluginMetadata::loadAndVerifyInputType(const QString &inputType)

View File

@ -15,6 +15,10 @@ HEADERS += \
libnymea.h \
platform/package.h \
platform/repository.h \
types/browseritem.h \
types/browseritemaction.h \
types/browseraction.h \
types/mediabrowseritem.h \
typeutils.h \
loggingcategories.h \
nymeasettings.h \
@ -59,8 +63,6 @@ HEADERS += \
types/paramtype.h \
types/param.h \
types/paramdescriptor.h \
types/ruleaction.h \
types/ruleactionparam.h \
types/statedescriptor.h \
types/interface.h \
hardwareresource.h \
@ -110,6 +112,10 @@ SOURCES += \
coap/corelinkparser.cpp \
coap/corelink.cpp \
coap/coapobserveresource.cpp \
types/browseritem.cpp \
types/browseritemaction.cpp \
types/browseraction.cpp \
types/mediabrowseritem.cpp \
types/deviceclass.cpp \
types/action.cpp \
types/actiontype.cpp \
@ -122,8 +128,6 @@ SOURCES += \
types/paramtype.cpp \
types/param.cpp \
types/paramdescriptor.cpp \
types/ruleaction.cpp \
types/ruleactionparam.cpp \
types/statedescriptor.cpp \
types/interface.cpp \
hardwareresource.cpp \

View File

@ -142,3 +142,9 @@ ActionType ActionTypes::findById(const ActionTypeId &id)
}
return ActionType(ActionTypeId());
}
QDebug operator<<(QDebug dbg, const ActionType &actionType)
{
dbg.nospace().noquote() << "ActionType: " << actionType.name() << actionType.displayName() << actionType.id();
return dbg;
}

View File

@ -60,6 +60,8 @@ private:
ParamTypes m_paramTypes;
};
QDebug operator<<(QDebug dbg, const ActionType &actionType);
class ActionTypes: public QList<ActionType>
{
public:

View File

@ -0,0 +1,66 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright (C) 2019 Michael Zanetti <michael.zanetti@nymea.io> *
* *
* This file is part of nymea. *
* *
* This library is free software; you can redistribute it and/or *
* modify it under the terms of the GNU Lesser General Public *
* License as published by the Free Software Foundation; either *
* version 2.1 of the License, or (at your option) any later version. *
* *
* This library 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 library; If not, see *
* <http://www.gnu.org/licenses/>. *
* *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include "browseraction.h"
BrowserAction::BrowserAction(const DeviceId &deviceId, const QString &itemId):
m_id(ActionId::createActionId()),
m_deviceId(deviceId),
m_itemId(itemId)
{
}
BrowserAction::BrowserAction(const BrowserAction &other):
m_id(other.id()),
m_deviceId(other.deviceId()),
m_itemId(other.itemId())
{
}
ActionId BrowserAction::id() const
{
return m_id;
}
bool BrowserAction::isValid() const
{
return !m_id.isNull() && !m_deviceId.isNull() && !m_itemId.isNull();
}
DeviceId BrowserAction::deviceId() const
{
return m_deviceId;
}
QString BrowserAction::itemId() const
{
return m_itemId;
}
void BrowserAction::operator=(const BrowserAction &other)
{
m_id = other.id();
m_deviceId = other.deviceId();
m_itemId = other.itemId();
}

View File

@ -0,0 +1,48 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright (C) 2019 Michael Zanetti <michael.zanetti@nymea.io> *
* *
* This file is part of nymea. *
* *
* This library is free software; you can redistribute it and/or *
* modify it under the terms of the GNU Lesser General Public *
* License as published by the Free Software Foundation; either *
* version 2.1 of the License, or (at your option) any later version. *
* *
* This library 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 library; If not, see *
* <http://www.gnu.org/licenses/>. *
* *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#ifndef BROWSERACTION_H
#define BROWSERACTION_H
#include "typeutils.h"
class BrowserAction
{
public:
explicit BrowserAction(const DeviceId &deviceId = DeviceId(), const QString &itemId = QString());
BrowserAction(const BrowserAction &other);
ActionId id() const;
bool isValid() const;
DeviceId deviceId() const;
QString itemId() const;
void operator=(const BrowserAction &other);
private:
ActionId m_id;
DeviceId m_deviceId;
QString m_itemId;
};
#endif // BROWSERACTION_H

View File

@ -0,0 +1,143 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright (C) 2019 Michael Zanetti <michael.zanetti@nymea.io> *
* *
* This file is part of nymea. *
* *
* This library is free software; you can redistribute it and/or *
* modify it under the terms of the GNU Lesser General Public *
* License as published by the Free Software Foundation; either *
* version 2.1 of the License, or (at your option) any later version. *
* *
* This library 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 library; If not, see *
* <http://www.gnu.org/licenses/>. *
* *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include "browseritem.h"
BrowserItem::BrowserItem(const QString &id, const QString &displayName, bool browsable, bool executable):
m_id(id),
m_displayName(displayName),
m_browsable(browsable),
m_executable(executable)
{
}
QString BrowserItem::id() const
{
return m_id;
}
void BrowserItem::setId(const QString &id)
{
m_id = id;
}
QString BrowserItem::displayName() const
{
return m_displayName;
}
void BrowserItem::setDisplayName(const QString &displayName)
{
m_displayName = displayName;
}
QString BrowserItem::description() const
{
return m_description;
}
void BrowserItem::setDescription(const QString &description)
{
m_description = description;
}
bool BrowserItem::executable() const
{
return m_executable;
}
void BrowserItem::setExecutable(bool executable)
{
m_executable = executable;
}
bool BrowserItem::browsable() const
{
return m_browsable;
}
void BrowserItem::setBrowsable(bool browsable)
{
m_browsable = browsable;
}
bool BrowserItem::disabled() const
{
return m_disabled;
}
void BrowserItem::setDisabled(bool disabled)
{
m_disabled = disabled;
}
BrowserItem::BrowserIcon BrowserItem::icon() const
{
return m_icon;
}
void BrowserItem::setIcon(BrowserIcon icon)
{
m_icon = icon;
}
QString BrowserItem::thumbnail() const
{
return m_thumbnail;
}
void BrowserItem::setThumbnail(const QString &thumbnail)
{
m_thumbnail = thumbnail;
}
QList<ActionTypeId> BrowserItem::actionTypeIds() const
{
return m_actionTypeIds;
}
void BrowserItem::setActionTypeIds(const QList<ActionTypeId> &actionTypeIds)
{
m_actionTypeIds = actionTypeIds;
}
BrowserItem::ExtendedPropertiesFlags BrowserItem::extendedPropertiesFlags() const
{
return m_extendedPropertiesFlags;
}
QVariant BrowserItem::extendedProperty(const QString &propertyName) const
{
return m_extendedProperties[propertyName];
}
BrowserItems::BrowserItems()
{
}
BrowserItems::BrowserItems(const QList<BrowserItem> &other): QList<BrowserItem>(other)
{
}

View File

@ -0,0 +1,118 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright (C) 2019 Michael Zanetti <michael.zanetti@nymea.io> *
* *
* This file is part of nymea. *
* *
* This library is free software; you can redistribute it and/or *
* modify it under the terms of the GNU Lesser General Public *
* License as published by the Free Software Foundation; either *
* version 2.1 of the License, or (at your option) any later version. *
* *
* This library 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 library; If not, see *
* <http://www.gnu.org/licenses/>. *
* *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#ifndef BROWSERITEM_H
#define BROWSERITEM_H
#include "libnymea.h"
#include "typeutils.h"
#include <QList>
#include <QHash>
#include <QVariant>
class LIBNYMEA_EXPORT BrowserItem
{
Q_GADGET
public:
enum BrowserIcon {
BrowserIconNone,
BrowserIconFolder,
BrowserIconFile,
BrowserIconMusic,
BrowserIconVideo,
BrowserIconPictures,
BrowserIconApplication,
BrowserIconDocument,
BrowserIconPackage,
BrowserIconFavorites,
};
Q_ENUM(BrowserIcon)
enum ExtendedProperties {
ExtendedPropertiesNone = 0x00,
ExtendedPropertiesMedia = 0x01
};
Q_ENUM(ExtendedProperties)
Q_DECLARE_FLAGS(ExtendedPropertiesFlags, ExtendedProperties)
BrowserItem(const QString &id = QString(), const QString &displayName = QString(), bool browsable = false, bool executable = false);
QString id() const;
void setId(const QString &id);
QString displayName() const;
void setDisplayName(const QString &displayName);
QString description() const;
void setDescription(const QString &description);
bool executable() const;
void setExecutable(bool executable);
bool browsable() const;
void setBrowsable(bool browsable);
bool disabled() const;
void setDisabled(bool disabled);
BrowserIcon icon() const;
void setIcon(BrowserIcon icon);
QString thumbnail() const;
void setThumbnail(const QString &thumbnail);
QList<ActionTypeId> actionTypeIds() const;
void setActionTypeIds(const QList<ActionTypeId> &actionTypeIds);
ExtendedPropertiesFlags extendedPropertiesFlags() const;
QVariant extendedProperty(const QString &propertyName) const;
private:
QString m_id;
QString m_displayName;
QString m_description;
bool m_browsable = false;
bool m_executable = false;
bool m_disabled = false;
BrowserIcon m_icon = BrowserIconNone;
QString m_thumbnail;
protected:
ExtendedPropertiesFlags m_extendedPropertiesFlags = ExtendedPropertiesNone;
QHash<QString, QVariant> m_extendedProperties;
QList<ActionTypeId> m_actionTypeIds;
};
Q_DECLARE_OPERATORS_FOR_FLAGS(BrowserItem::ExtendedPropertiesFlags)
class LIBNYMEA_EXPORT BrowserItems: public QList<BrowserItem>
{
public:
BrowserItems();
BrowserItems(const QList<BrowserItem> &other);
};
#endif // BROWSERITEM_H

View File

@ -0,0 +1,83 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright (C) 2019 Michael Zanetti <michael.zanetti@nymea.io> *
* *
* This file is part of nymea. *
* *
* This library is free software; you can redistribute it and/or *
* modify it under the terms of the GNU Lesser General Public *
* License as published by the Free Software Foundation; either *
* version 2.1 of the License, or (at your option) any later version. *
* *
* This library 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 library; If not, see *
* <http://www.gnu.org/licenses/>. *
* *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include "browseritemaction.h"
BrowserItemAction::BrowserItemAction(const DeviceId &deviceId, const QString &itemId, const ActionTypeId &actionTypeId, const ParamList &params):
m_id(ActionId::createActionId()),
m_deviceId(deviceId),
m_itemId(itemId),
m_actionTypeId(actionTypeId),
m_params(params)
{
}
BrowserItemAction::BrowserItemAction(const BrowserItemAction &other):
m_id(other.id()),
m_deviceId(other.deviceId()),
m_itemId(other.itemId()),
m_actionTypeId(other.actionTypeId()),
m_params(other.params())
{
}
ActionId BrowserItemAction::id() const
{
return m_id;
}
bool BrowserItemAction::isValid() const
{
return !m_id.isNull() && !m_deviceId.isNull() && !m_itemId.isNull();
}
DeviceId BrowserItemAction::deviceId() const
{
return m_deviceId;
}
QString BrowserItemAction::itemId() const
{
return m_itemId;
}
ActionTypeId BrowserItemAction::actionTypeId() const
{
return m_actionTypeId;
}
ParamList BrowserItemAction::params() const
{
return m_params;
}
void BrowserItemAction::operator=(const BrowserItemAction &other)
{
m_id = other.id();
m_deviceId = other.deviceId();
m_itemId = other.itemId();
m_actionTypeId = other.actionTypeId();
m_params = other.params();
}

View File

@ -0,0 +1,56 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright (C) 2019 Michael Zanetti <michael.zanetti@nymea.io> *
* *
* This file is part of nymea. *
* *
* This library is free software; you can redistribute it and/or *
* modify it under the terms of the GNU Lesser General Public *
* License as published by the Free Software Foundation; either *
* version 2.1 of the License, or (at your option) any later version. *
* *
* This library 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 library; If not, see *
* <http://www.gnu.org/licenses/>. *
* *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#ifndef BROWSERITEMACTION_H
#define BROWSERITEMACTION_H
#include "typeutils.h"
#include "types/param.h"
class BrowserItemAction
{
public:
explicit BrowserItemAction(const DeviceId &deviceId = DeviceId(), const QString &itemId = QString(), const ActionTypeId &actionTypeId = ActionTypeId(), const ParamList &params = ParamList());
BrowserItemAction(const BrowserItemAction &other);
ActionId id() const;
bool isValid() const;
DeviceId deviceId() const;
QString itemId() const;
ActionTypeId actionTypeId() const;
ParamList params() const;
void setParams(const ParamList &params);
Param param(const ParamTypeId &paramTypeId) const;
void operator=(const BrowserItemAction &other);
private:
ActionId m_id;
DeviceId m_deviceId;
QString m_itemId;
ActionTypeId m_actionTypeId;
ParamList m_params;
};
#endif // BROWSERITEMACTION_H

View File

@ -147,7 +147,7 @@ StateType DeviceClass::getStateType(const StateTypeId &stateTypeId)
/*! Set the \a stateTypes of this DeviceClass. \{Device}{Devices} created
from this \l{DeviceClass} must have their states matching to this template. */
void DeviceClass::setStateTypes(const QList<StateType> &stateTypes)
void DeviceClass::setStateTypes(const StateTypes &stateTypes)
{
m_stateTypes = stateTypes;
}
@ -172,7 +172,7 @@ EventTypes DeviceClass::eventTypes() const
/*! Set the \a eventTypes of this DeviceClass. \{Device}{Devices} created
from this \l{DeviceClass} must have their events matching to this template. */
void DeviceClass::setEventTypes(const QList<EventType> &eventTypes)
void DeviceClass::setEventTypes(const EventTypes &eventTypes)
{
m_eventTypes = eventTypes;
}
@ -197,7 +197,7 @@ ActionTypes DeviceClass::actionTypes() const
/*! Set the \a actionTypes of this DeviceClass. \{Device}{Devices} created
from this \l{DeviceClass} must have their actions matching to this template. */
void DeviceClass::setActionTypes(const QList<ActionType> &actionTypes)
void DeviceClass::setActionTypes(const ActionTypes &actionTypes)
{
m_actionTypes = actionTypes;
}
@ -213,6 +213,31 @@ bool DeviceClass::hasActionType(const ActionTypeId &actionTypeId)
return false;
}
/*! Returns the browserItemActionTypes of this DeviceClass. \{Device}{Devices} created
from this \l{DeviceClass} may set those actions to their browser items. */
ActionTypes DeviceClass::browserItemActionTypes() const
{
return m_browserItemActionTypes;
}
/*! Set the \a browserActionTypes of this DeviceClass. \{Device}{Devices} created
from this \l{DeviceClass} may set those actions to their browser items. */
void DeviceClass::setBrowserItemActionTypes(const ActionTypes &browserItemActionTypes)
{
m_browserItemActionTypes = browserItemActionTypes;
}
/*! Returns true if this DeviceClass has a \l{ActionType} with the given \a actionTypeId. */
bool DeviceClass::hasBrowserItemActionType(const ActionTypeId &actionTypeId)
{
foreach (const ActionType &actionType, m_browserItemActionTypes) {
if (actionType.id() == actionTypeId) {
return true;
}
}
return false;
}
/*! Returns the params description of this DeviceClass. \{Device}{Devices} created
from this \l{DeviceClass} must have their params matching to this template. */
ParamTypes DeviceClass::paramTypes() const
@ -308,6 +333,18 @@ void DeviceClass::setInterfaces(const QStringList &interfaces)
m_interfaces = interfaces;
}
/*! Returns whether \l{Device}{Devices} created from this \l{DeviceClass} are browsable */
bool DeviceClass::browsable() const
{
return m_browsable;
}
/*! Sets whether \l{Device}{Devices} created from this \l{DeviceClass} are browsable */
void DeviceClass::setBrowsable(bool browsable)
{
m_browsable = browsable;
}
/*! 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
{

View File

@ -38,10 +38,6 @@
class LIBNYMEA_EXPORT DeviceClass
{
Q_GADGET
Q_ENUMS(CreateMethod)
Q_ENUMS(SetupMethod)
Q_ENUMS(BasicTag)
Q_ENUMS(CreateMethods)
public:
enum CreateMethod {
@ -49,6 +45,7 @@ public:
CreateMethodAuto = 0x02,
CreateMethodDiscovery = 0x04
};
Q_ENUM(CreateMethod)
Q_DECLARE_FLAGS(CreateMethods, CreateMethod)
enum SetupMethod {
@ -57,6 +54,7 @@ public:
SetupMethodEnterPin,
SetupMethodPushButton
};
Q_ENUM(SetupMethod)
DeviceClass(const PluginId &pluginId = PluginId(), const VendorId &vendorId = VendorId(), const DeviceClassId &id = DeviceClassId());
@ -73,17 +71,24 @@ public:
StateTypes stateTypes() const;
StateType getStateType(const StateTypeId &stateTypeId);
void setStateTypes(const QList<StateType> &stateTypes);
void setStateTypes(const StateTypes &stateTypes);
bool hasStateType(const StateTypeId &stateTypeId);
EventTypes eventTypes() const;
void setEventTypes(const QList<EventType> &eventTypes);
void setEventTypes(const EventTypes &eventTypes);
bool hasEventType(const EventTypeId &eventTypeId);
ActionTypes actionTypes() const;
void setActionTypes(const QList<ActionType> &actionTypes);
void setActionTypes(const ActionTypes &actionTypes);
bool hasActionType(const ActionTypeId &actionTypeId);
bool browsable() const;
void setBrowsable(bool browsable);
ActionTypes browserItemActionTypes() const;
void setBrowserItemActionTypes(const ActionTypes &browserItemActionTypes);
bool hasBrowserItemActionType(const ActionTypeId &actionTypeId);
ParamTypes paramTypes() const;
void setParamTypes(const ParamTypes &paramTypes);
@ -113,9 +118,11 @@ private:
PluginId m_pluginId;
QString m_name;
QString m_displayName;
bool m_browsable = false;
StateTypes m_stateTypes;
EventTypes m_eventTypes;
ActionTypes m_actionTypes;
ActionTypes m_browserItemActionTypes;
ParamTypes m_paramTypes;
ParamTypes m_settingsTypes;
ParamTypes m_discoveryParamTypes;

View File

@ -0,0 +1,53 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright (C) 2019 Michael Zanetti <michael.zanetti@nymea.io> *
* *
* This file is part of nymea. *
* *
* This library is free software; you can redistribute it and/or *
* modify it under the terms of the GNU Lesser General Public *
* License as published by the Free Software Foundation; either *
* version 2.1 of the License, or (at your option) any later version. *
* *
* This library 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 library; If not, see *
* <http://www.gnu.org/licenses/>. *
* *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include "mediabrowseritem.h"
MediaBrowserItem::MediaBrowserItem(const QString &id, const QString &displayName, bool browsable):
BrowserItem(id, displayName, browsable)
{
// Init defaults
m_extendedProperties["mediaIcon"] = static_cast<int>(MediaBrowserIconNone);
m_extendedProperties["playCount"] = 0;
m_extendedPropertiesFlags = BrowserItem::ExtendedPropertiesMedia;
}
MediaBrowserItem::MediaBrowserIcon MediaBrowserItem::mediaIcon() const
{
return static_cast<MediaBrowserIcon>(m_extendedProperties.value("mediaIcon").toInt());
}
void MediaBrowserItem::setMediaIcon(MediaBrowserIcon mediaIcon)
{
m_extendedProperties["mediaIcon"] = static_cast<int>(mediaIcon);
}
int MediaBrowserItem::playCount() const
{
return m_extendedProperties.value("playCount").toInt();
}
void MediaBrowserItem::setPlayCount(int playCount)
{
m_extendedProperties["playCount"] = playCount;
}

View File

@ -0,0 +1,65 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright (C) 2019 Michael Zanetti <michael.zanetti@nymea.io> *
* *
* This file is part of nymea. *
* *
* This library is free software; you can redistribute it and/or *
* modify it under the terms of the GNU Lesser General Public *
* License as published by the Free Software Foundation; either *
* version 2.1 of the License, or (at your option) any later version. *
* *
* This library 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 library; If not, see *
* <http://www.gnu.org/licenses/>. *
* *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#ifndef MEDIABROWSERITEM_H
#define MEDIABROWSERITEM_H
#include "browseritem.h"
class MediaBrowserItem: public BrowserItem
{
Q_GADGET
public:
enum MediaBrowserIcon {
MediaBrowserIconNone = 1,
MediaBrowserIconPlaylist = 2,
MediaBrowserIconRecentlyPlayed = 3,
MediaBrowserIconLibrary = 4,
MediaBrowserIconMusicLibrary = 5,
MediaBrowserIconVideoLibrary = 6,
MediaBrowserIconPictureLibrary = 7,
MediaBrowserIconDisk = 100,
MediaBrowserIconUSB = 101,
MediaBrowserIconNetwork = 102,
MediaBrowserIconAux = 103,
MediaBrowserIconSpotify = 200,
MediaBrowserIconAmazon = 201,
MediaBrowserIconTuneIn = 202,
MediaBrowserIconSiriusXM = 203,
MediaBrowserIconVTuner = 204,
MediaBrowserIconTidal = 205,
MediaBrowserIconAirable = 206,
};
Q_ENUM(MediaBrowserIcon)
MediaBrowserItem(const QString &id = QString(), const QString &displayName = QString(), bool browsable = false);
MediaBrowserIcon mediaIcon() const;
void setMediaIcon(MediaBrowserIcon mediaIcon);
int playCount() const;
void setPlayCount(int playCount);
};
#endif // MEDIABROWSERITEM_H

View File

@ -57,6 +57,7 @@ DECLARE_TYPE_ID(ActionType)
DECLARE_TYPE_ID(Action)
DECLARE_TYPE_ID(Plugin)
DECLARE_TYPE_ID(Rule)
DECLARE_TYPE_ID(Browser)
DECLARE_TYPE_ID(PairingTransaction)
@ -151,6 +152,10 @@ public:
};
Q_ENUM(StateOperator)
enum BrowserType {
BrowserTypeGeneric,
};
Q_ENUM(BrowserType)
};
Q_DECLARE_METATYPE(Types::InputType)

View File

@ -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=2
JSON_PROTOCOL_VERSION_MINOR=3
REST_API_VERSION=1
LIBNYMEA_API_VERSION_MAJOR=2
LIBNYMEA_API_VERSION_MINOR=1

View File

@ -51,7 +51,7 @@
DevicePluginMock::DevicePluginMock()
{
generateBrowseItems();
}
DevicePluginMock::~DevicePluginMock()
@ -75,7 +75,28 @@ Device::DeviceError DevicePluginMock::discoverDevices(const DeviceClassId &devic
m_discoveredDeviceCount = params.paramValue(mockDisplayPinDiscoveryResultCountParamTypeId).toInt();
QTimer::singleShot(1000, this, SLOT(emitDisplayPinDevicesDiscovered()));
return Device::DeviceErrorAsync;
} else if (deviceClassId == mockParentDeviceClassId) {
qCDebug(dcMockDevice()) << "Starting discovery for mock device parent";
QTimer::singleShot(1000, this, [this](){
DeviceDescriptor descriptor(mockParentDeviceClassId, "Mock Parent (Discovered)");
emit devicesDiscovered(mockParentDeviceClassId, {descriptor});
});
return Device::DeviceErrorAsync;
} else if (deviceClassId == mockChildDeviceClassId) {
QTimer::singleShot(1000, this, [this](){
QList<DeviceDescriptor> descriptors;
if (!myDevices().filterByDeviceClassId(mockParentDeviceClassId).isEmpty()) {
Device *parent = myDevices().filterByDeviceClassId(mockParentDeviceClassId).first();
DeviceDescriptor descriptor(mockChildDeviceClassId, "Mock Child (Discovered)", QString(), parent->id());
descriptors.append(descriptor);
}
emit devicesDiscovered(mockChildDeviceClassId, descriptors);
});
return Device::DeviceErrorAsync;
}
qCWarning(dcMockDevice()) << "Cannot discover for deviceClassId" << deviceClassId;
return Device::DeviceErrorDeviceClassNotFound;
}
@ -218,6 +239,62 @@ Device::DeviceError DevicePluginMock::displayPin(const PairingTransactionId &pai
return Device::DeviceErrorNoError;
}
Device::BrowseResult DevicePluginMock::browseDevice(Device *device, Device::BrowseResult result, const QString &itemId, const QLocale &locale)
{
Q_UNUSED(locale)
qCDebug(dcMockDevice()) << "Browse device called" << device;
if (device->deviceClassId() == mockDeviceClassId) {
if (device->paramValue(mockDeviceAsyncParamTypeId).toBool()) {
result.status = Device::DeviceErrorAsync;
QTimer::singleShot(1000, device, [this, device, result, itemId]() mutable {
if (device->paramValue(mockDeviceBrokenParamTypeId).toBool()) {
result.status = Device::DeviceErrorHardwareFailure;
} else {
VirtualFsNode *node = m_virtualFs->findNode(itemId);
if (!node) {
result.status = Device::DeviceErrorItemNotFound;
emit browseRequestFinished(result);
return;
}
foreach (VirtualFsNode *child, node->childs) {
result.items.append(child->item);
}
result.status = Device::DeviceErrorNoError;
}
emit browseRequestFinished(result);
});
}
else if (device->paramValue(mockDeviceBrokenParamTypeId).toBool()) {
result.status = Device::DeviceErrorHardwareFailure;
} else {
VirtualFsNode *node = m_virtualFs->findNode(itemId);
if (!node) {
result.status = Device::DeviceErrorItemNotFound;
return result;
}
foreach (VirtualFsNode *child, node->childs) {
result.items.append(child->item);
}
result.status = Device::DeviceErrorNoError;
}
}
return result;
}
Device::BrowserItemResult DevicePluginMock::browserItem(Device *device, Device::BrowserItemResult result, const QString &itemId, const QLocale &locale)
{
Q_UNUSED(device)
Q_UNUSED(locale)
VirtualFsNode *node = m_virtualFs->findNode(itemId);
if (!node) {
result.status = Device::DeviceErrorItemNotFound;
return result;
}
result.item = node->item;
result.status = Device::DeviceErrorNoError;
return result;
}
Device::DeviceError DevicePluginMock::executeAction(Device *device, const Action &action)
{
if (!myDevices().contains(device))
@ -348,6 +425,68 @@ Device::DeviceError DevicePluginMock::executeAction(Device *device, const Action
return Device::DeviceErrorDeviceClassNotFound;
}
Device::DeviceError DevicePluginMock::executeBrowserItem(Device *device, const BrowserAction &browserAction)
{
qCDebug(dcMockDevice()) << "ExecuteBrowserItem called" << browserAction.itemId();
bool broken = device->paramValue(mockDeviceBrokenParamTypeId).toBool();
bool async = device->paramValue(mockDeviceAsyncParamTypeId).toBool();
VirtualFsNode *node = m_virtualFs->findNode(browserAction.itemId());
if (!node) {
return Device::DeviceErrorItemNotFound;
}
if (!node->item.executable()) {
return Device::DeviceErrorItemNotExecutable;
}
if (!async){
if (broken) {
return Device::DeviceErrorHardwareFailure;
}
return Device::DeviceErrorNoError;
}
QTimer::singleShot(2000, device, [this, broken, browserAction](){
emit this->browserItemExecutionFinished(browserAction.id(), broken ? Device::DeviceErrorHardwareFailure : Device::DeviceErrorNoError);
});
return Device::DeviceErrorAsync;
}
Device::DeviceError DevicePluginMock::executeBrowserItemAction(Device *device, const BrowserItemAction &browserItemAction)
{
qCDebug(dcMockDevice()) << "TODO" << device << browserItemAction.id();
if (browserItemAction.actionTypeId() == mockAddToFavoritesBrowserItemActionTypeId) {
VirtualFsNode *node = m_virtualFs->findNode(browserItemAction.itemId());
if (!node) {
return Device::DeviceErrorInvalidParameter;
}
VirtualFsNode *favoritesNode = m_virtualFs->findNode("favorites");
if (favoritesNode->findNode(browserItemAction.itemId())) {
return Device::DeviceErrorDeviceInUse;
}
BrowserItem newItem = node->item;
newItem.setActionTypeIds({mockRemoveFromFavoritesBrowserItemActionTypeId});
VirtualFsNode *newNode = new VirtualFsNode(newItem);
favoritesNode->addChild(newNode);
return Device::DeviceErrorNoError;
}
if (browserItemAction.actionTypeId() == mockRemoveFromFavoritesBrowserItemActionTypeId) {
VirtualFsNode *favoritesNode = m_virtualFs->findNode("favorites");
VirtualFsNode *nodeToRemove = favoritesNode->findNode(browserItemAction.itemId());
if (!nodeToRemove) {
return Device::DeviceErrorItemNotFound;
}
int idx = favoritesNode->childs.indexOf(nodeToRemove);
delete favoritesNode->childs.takeAt(idx);
return Device::DeviceErrorNoError;
}
return Device::DeviceErrorActionTypeNotFound;
}
void DevicePluginMock::setState(const StateTypeId &stateTypeId, const QVariant &value)
{
HttpDaemon *daemon = qobject_cast<HttpDaemon*>(sender());
@ -545,3 +684,55 @@ void DevicePluginMock::onPluginConfigChanged()
{
}
void DevicePluginMock::generateBrowseItems()
{
m_virtualFs = new VirtualFsNode(BrowserItem());
BrowserItem item = BrowserItem("001", "Item 0", true);
item.setDescription("I'm a folder");
item.setIcon(BrowserItem::BrowserIconFolder);
VirtualFsNode *folderNode = new VirtualFsNode(item);
m_virtualFs->addChild(folderNode);
item = BrowserItem("002", "Item 1", false, true);
item.setDescription("I'm executable");
item.setIcon(BrowserItem::BrowserIconApplication);
item.setActionTypeIds({mockAddToFavoritesBrowserItemActionTypeId});
m_virtualFs->addChild(new VirtualFsNode(item));
item = BrowserItem("003", "Item 2", false, true);
item.setDescription("I'm a file");
item.setIcon(BrowserItem::BrowserIconFile);
item.setActionTypeIds({mockAddToFavoritesBrowserItemActionTypeId});
m_virtualFs->addChild(new VirtualFsNode(item));
item = BrowserItem("004", "Item 3", false, true);
item.setDescription("I have a nice thumbnail");
item.setIcon(BrowserItem::BrowserIconFile);
item.setThumbnail("https://github.com/guh/nymea/raw/master/icons/nymea-logo-256x256.png");
item.setActionTypeIds({mockAddToFavoritesBrowserItemActionTypeId});
m_virtualFs->addChild(new VirtualFsNode(item));
item = BrowserItem("005", "Item 4", false, false);
item.setDescription("I'm disabled");
item.setDisabled(true);
item.setIcon(BrowserItem::BrowserIconFile);
m_virtualFs->addChild(new VirtualFsNode(item));
item = BrowserItem("favorites", "Favorites", true, false);
item.setDescription("Yay! I'm the best!");
item.setIcon(BrowserItem::BrowserIconFavorites);
m_virtualFs->addChild(new VirtualFsNode(item));
item = BrowserItem("sub-001", "Item Subdir 1", false, true);
item.setDescription("I'm an item in a subdir");
item.setIcon(BrowserItem::BrowserIconFile);
folderNode->addChild(new VirtualFsNode(item));
item = BrowserItem("sub-002", "Item Subdir 2", true, false);
item.setDescription("I'm a folder in a subdir");
item.setIcon(BrowserItem::BrowserIconFile);
folderNode->addChild(new VirtualFsNode(item));
}

View File

@ -52,8 +52,13 @@ public:
Device::DeviceSetupStatus confirmPairing(const PairingTransactionId &pairingTransactionId, const DeviceClassId &deviceClassId, const ParamList &params, const QString &secret) override;
Device::DeviceError displayPin(const PairingTransactionId &pairingTransactionId, const DeviceDescriptor &deviceDescriptor) override;
Device::BrowseResult browseDevice(Device *device, Device::BrowseResult result, const QString &itemId, const QLocale &locale) override;
Device::BrowserItemResult browserItem(Device *device, Device::BrowserItemResult result, const QString &itemId, const QLocale &locale) override;
public slots:
Device::DeviceError executeAction(Device *device, const Action &action) override;
Device::DeviceError executeBrowserItem(Device *device, const BrowserAction &browserAction) override;
Device::DeviceError executeBrowserItemAction(Device *device, const BrowserItemAction &browserItemAction) override;
private slots:
void setState(const StateTypeId &stateTypeId, const QVariant &value);
@ -73,6 +78,25 @@ private slots:
void onPluginConfigChanged();
private:
void generateBrowseItems();
private:
class VirtualFsNode {
public:
VirtualFsNode(const BrowserItem &item):item(item) {}
BrowserItem item;
QList<VirtualFsNode*> childs;
void addChild(VirtualFsNode* child) {childs.append(child); }
VirtualFsNode *findNode(const QString &id) {
if (item.id() == id) return this;
foreach (VirtualFsNode *child, childs) {
VirtualFsNode *node = child->findNode(id);
if (node) return node;
}
return nullptr;
}
};
QHash<Device*, HttpDaemon*> m_daemons;
QList<Device*> m_asyncSetupDevices;
QList<QPair<Action, Device*> > m_asyncActions;
@ -81,6 +105,8 @@ private:
int m_discoveredDeviceCount;
bool m_pushbuttonPressed;
VirtualFsNode* m_virtualFs = nullptr;
};
#endif // DEVICEPLUGINMOCK_H

View File

@ -32,6 +32,7 @@
"displayName": "Mock Device",
"interfaces": ["system", "light", "battery"],
"createMethods": ["user", "discovery"],
"browsable": true,
"discoveryParamTypes": [
{
"id": "d222adb4-2f9c-4c3f-8655-76400d0fb6ce",
@ -193,6 +194,18 @@
"name": "asyncFailing",
"displayName": "Mock Action 5 (async, broken)"
}
],
"browserItemActionTypes": [
{
"id": "00b8f0a8-99ca-4aa4-833d-59eb8d4d6de3",
"name": "addToFavorites",
"displayName": "Add to favorites"
},
{
"id": "da6faef8-2816-430e-93bb-57e8f9582d29",
"name": "removeFromFavorites",
"displayName": "Remove from favorites"
}
]
},
{
@ -501,7 +514,7 @@
"name": "mockParent",
"displayName": "Mock Device (Parent)",
"interfaces": ["system"],
"createMethods": ["user"],
"createMethods": ["user", "discovery"],
"paramTypes": [ ],
"stateTypes": [
{
@ -520,7 +533,7 @@
"id": "40893c9f-bc47-40c1-8bf7-b390c7c1b4fc",
"name": "mockChild",
"displayName": "Mock Device (Child)",
"createMethods": ["auto"],
"createMethods": ["auto", "discovery"],
"paramTypes": [],
"stateTypes": [
{

View File

@ -50,6 +50,8 @@ extern ActionTypeId mockWithoutParamsActionTypeId;
extern ActionTypeId mockAsyncActionTypeId;
extern ActionTypeId mockFailingActionTypeId;
extern ActionTypeId mockAsyncFailingActionTypeId;
extern ActionTypeId mockAddToFavoritesBrowserItemActionTypeId;
extern ActionTypeId mockRemoveFromFavoritesBrowserItemActionTypeId;
extern DeviceClassId mockDeviceAutoDeviceClassId;
extern ParamTypeId mockDeviceAutoDeviceHttpportParamTypeId;
extern ParamTypeId mockDeviceAutoDeviceAsyncParamTypeId;

View File

@ -4,6 +4,8 @@ QT+= network
TARGET = $$qtLibraryTarget(nymea_devicepluginmock)
OTHER_FILES += devicepluginmock.json
SOURCES += \
devicepluginmock.cpp \
httpdaemon.cpp

View File

@ -54,6 +54,8 @@ ActionTypeId mockWithoutParamsActionTypeId = ActionTypeId("{defd3ed6-1a0d-400b-8
ActionTypeId mockAsyncActionTypeId = ActionTypeId("{fbae06d3-7666-483e-a39e-ec50fe89054e}");
ActionTypeId mockFailingActionTypeId = ActionTypeId("{df3cf33d-26d5-4577-9132-9823bd33fad0}");
ActionTypeId mockAsyncFailingActionTypeId = ActionTypeId("{bfe89a1d-3497-4121-8318-e77c37537219}");
ActionTypeId mockAddToFavoritesBrowserItemActionTypeId = ActionTypeId("{00b8f0a8-99ca-4aa4-833d-59eb8d4d6de3}");
ActionTypeId mockRemoveFromFavoritesBrowserItemActionTypeId = ActionTypeId("{da6faef8-2816-430e-93bb-57e8f9582d29}");
DeviceClassId mockDeviceAutoDeviceClassId = DeviceClassId("{ab4257b3-7548-47ee-9bd4-7dc3004fd197}");
ParamTypeId mockDeviceAutoDeviceHttpportParamTypeId = ParamTypeId("{bfeb0613-dab6-408c-aa27-c362c921d0d1}");
ParamTypeId mockDeviceAutoDeviceAsyncParamTypeId = ParamTypeId("{a5c4315f-0624-4971-87c1-4bbfbfdbd16e}");
@ -247,6 +249,9 @@ ActionTypeId mockInputTypeWritableTimestampUIntActionTypeId = ActionTypeId("{45d
ParamTypeId mockInputTypeWritableTimestampUIntActionWritableTimestampUIntParamTypeId = ParamTypeId("{45d0069a-63ac-4265-8170-8152778608ee}");
const QString translations[] {
//: The name of the Browser Item ActionType ({00b8f0a8-99ca-4aa4-833d-59eb8d4d6de3}) of DeviceClass mock
QT_TRANSLATE_NOOP("mockDevice", "Add to favorites"),
//: The name of the ParamType (DeviceClass: mockInputType, EventType: bool, ID: {3bad3a09-5826-4ed7-a832-10e3e2ee2a7d})
QT_TRANSLATE_NOOP("mockDevice", "Bool"),
@ -412,6 +417,9 @@ const QString translations[] {
//: The pairing info of deviceClass mockDisplayPin
QT_TRANSLATE_NOOP("mockDevice", "Please enter the secret which normaly will be displayed on the device. For the mockdevice the pin is 243681."),
//: The name of the Browser Item ActionType ({da6faef8-2816-430e-93bb-57e8f9582d29}) of DeviceClass mock
QT_TRANSLATE_NOOP("mockDevice", "Remove from favorites"),
//: The name of the ParamType (DeviceClass: mockInputType, Type: device, ID: {22add8c9-ee4f-43ad-8931-58e999313ac3})
QT_TRANSLATE_NOOP("mockDevice", "Search text"),

View File

@ -1,4 +1,4 @@
2.2
2.3
{
"methods": {
"Actions.ExecuteAction": {
@ -14,6 +14,30 @@
"deviceError": "$ref:DeviceError"
}
},
"Actions.ExecuteBrowserItem": {
"description": "Execute the item identified by itemId on the given device.",
"params": {
"deviceId": "Uuid",
"itemId": "String"
},
"returns": {
"deviceError": "$ref:DeviceError"
}
},
"Actions.ExecuteBrowserItemAction": {
"description": "Execute the action for the browser item identified by actionTypeId and the itemId on the given device.",
"params": {
"actionTypeId": "Uuid",
"deviceId": "Uuid",
"itemId": "String",
"o:params": [
"$ref:Param"
]
},
"returns": {
"deviceError": "$ref:DeviceError"
}
},
"Actions.GetActionType": {
"description": "Get the ActionType for the given ActionTypeId",
"params": {
@ -249,6 +273,19 @@
"o:deviceId": "Uuid"
}
},
"Devices.BrowseDevice": {
"description": "Browse a device. If a DeviceClass indicates a device is browsable, this method will return the BrowserItems. If no parameter besides the deviceId is used, the root node of this device will be returned. Any returned item which is browsable can be passed as node. Results will be children of the given node.",
"params": {
"deviceId": "Uuid",
"o:itemId": "String"
},
"returns": {
"deviceError": "$ref:DeviceError",
"items": [
"$ref:BrowserItem"
]
}
},
"Devices.ConfirmPairing": {
"description": "Confirm an ongoing pairing. In case of SetupMethodEnterPin also provide the pin in the params.",
"params": {
@ -281,6 +318,17 @@
]
}
},
"Devices.GetBrowserItem": {
"description": "Get a single item from the browser. This won't give any more info on an item than a regular browseDevice call, but it allows to fetch details of an item if only the ID is known.",
"params": {
"deviceId": "Uuid",
"o:itemId": "String"
},
"returns": {
"deviceError": "$ref:DeviceError",
"o:item": "$ref:BrowserItem"
}
},
"Devices.GetConfiguredDevices": {
"description": "Returns a list of configured devices, optionally filtered by deviceId.",
"params": {
@ -1351,6 +1399,21 @@
"Time",
"Object"
],
"BrowserIcon": "$ref:BrowserIcon",
"BrowserItem": {
"actionTypeIds": [
"Uuid"
],
"browsable": "Bool",
"description": "String",
"disabled": "Bool",
"displayName": "String",
"executable": "Bool",
"icon": "$ref:BrowserIcon",
"id": "String",
"o:mediaIcon": "$ref:MediaBrowserIcon",
"thumbnail": "String"
},
"CalendarItem": {
"duration": "Uint",
"o:datetime": "Uint",
@ -1401,6 +1464,10 @@
"actionTypes": [
"$ref:ActionType"
],
"browsable": "Bool",
"browserItemActionTypes": [
"$ref:ActionType"
],
"createMethods": [
"$ref:CreateMethod"
],
@ -1462,7 +1529,10 @@
"DeviceErrorDeviceInRule",
"DeviceErrorDeviceIsChild",
"DeviceErrorPairingTransactionIdNotFound",
"DeviceErrorParameterNotWritable"
"DeviceErrorParameterNotWritable",
"DeviceErrorItemNotFound",
"DeviceErrorItemNotExecutable",
"DeviceErrorUnsupportedFeature"
],
"Event": {
"deviceId": "Uuid",
@ -1507,6 +1577,7 @@
"o:deviceId": "Uuid",
"o:errorCode": "String",
"o:eventType": "$ref:LoggingEventType",
"o:itemId": "String",
"o:typeId": "Uuid",
"o:value": "String",
"source": "$ref:LoggingSource",
@ -1533,8 +1604,10 @@
"LoggingSourceEvents",
"LoggingSourceActions",
"LoggingSourceStates",
"LoggingSourceRules"
"LoggingSourceRules",
"LoggingSourceBrowserActions"
],
"MediaBrowserIcon": "$ref:MediaBrowserIcon",
"MqttPolicy": {
"allowedPublishTopicFilters": "StringList",
"allowedSubscribeTopicFilters": "StringList",
@ -1670,6 +1743,7 @@
},
"RuleAction": {
"o:actionTypeId": "Uuid",
"o:browserItemId": "String",
"o:deviceId": "Uuid",
"o:interface": "String",
"o:interfaceAction": "String",

View File

@ -29,7 +29,13 @@ class TestDevices : public NymeaTestBase
{
Q_OBJECT
private:
DeviceId m_mockDeviceAsyncId;
private slots:
void initTestCase();
void getPlugins();
void getPluginConfig_data();
@ -95,12 +101,59 @@ private slots:
void reconfigureByDiscoveryAndPair();
void reconfigureAutodevice();
void testBrowsing_data();
void testBrowsing();
void testExecuteBrowserItem_data();
void testExecuteBrowserItem();
void testExecuteBrowserItemAction_data();
void testExecuteBrowserItemAction();
// Keep those at last as they will remove devices
void removeDevice_data();
void removeDevice();
void removeAutoDevice();
void discoverDeviceParenting();
};
void TestDevices::initTestCase()
{
NymeaTestBase::initTestCase();
QLoggingCategory::setFilterRules("*.debug=false\n"
"Tests.debug=true\n"
"MockDevice.debug=true\n"
);
// Adding an async mock device to be used in tests below
QVariantMap params;
params.insert("deviceClassId", mockDeviceClassId);
params.insert("name", "Mock Device (Async)");
QVariantList deviceParams;
QVariantMap asyncParam;
asyncParam.insert("paramTypeId", mockDeviceAsyncParamTypeId);
asyncParam.insert("value", true);
deviceParams.append(asyncParam);
QVariantMap httpParam;
httpParam.insert("paramTypeId", mockDeviceHttpportParamTypeId);
httpParam.insert("value", 8765);
deviceParams.append(httpParam);
params.insert("deviceParams", deviceParams);
QVariant response = injectAndWait("Devices.AddConfiguredDevice", params);
m_mockDeviceAsyncId = DeviceId(response.toMap().value("params").toMap().value("deviceId").toString());
QVERIFY2(!m_mockDeviceAsyncId.isNull(), "Creating an async mock device failed");
qCDebug(dcTests()) << "Created Async mock device with ID" << m_mockDeviceAsyncId;
}
void TestDevices::getPlugins()
{
QVariant response = injectAndWait("Devices.GetPlugins");
@ -320,7 +373,7 @@ void TestDevices::getConfiguredDevices()
QVariant response = injectAndWait("Devices.GetConfiguredDevices");
QVariantList devices = response.toMap().value("params").toMap().value("devices").toList();
QCOMPARE(devices.count(), 2); // There should be one auto created mock device and one created in initTestcase()
QCOMPARE(devices.count(), 3); // There should be: one auto created mock device, one created in NymeaTestBase::initTestcase() and one created in TestDevices::initTestCase()
}
void TestDevices::storedDevices()
@ -407,7 +460,7 @@ void TestDevices::discoverDevices()
}
// If we found something, lets try to add it
if (Device::DeviceErrorNoError) {
if (error == Device::DeviceErrorNoError) {
DeviceDescriptorId descriptorId = DeviceDescriptorId(response.toMap().value("params").toMap().value("deviceDescriptors").toList().first().toMap().value("id").toString());
params.clear();
@ -1433,7 +1486,8 @@ void TestDevices::removeAutoDevice()
// First try to make a manually created device disappear. It must not go away
QList<Device*> devices = NymeaCore::instance()->deviceManager()->findConfiguredDevices(mockDeviceClassId);
QVERIFY2(devices.count() > 0, "There needs to be at least one configured Mock Device for this test");
int oldCount = devices.count();
QVERIFY2(oldCount > 0, "There needs to be at least one configured Mock Device for this test");
Device *device = devices.first();
// trigger disappear signal in mock device
@ -1443,12 +1497,13 @@ void TestDevices::removeAutoDevice()
spy.wait();
QCOMPARE(spy.count(), 1);
reply->deleteLater();
QVERIFY2(NymeaCore::instance()->deviceManager()->findConfiguredDevices(mockDeviceClassId).count() == 1, "Mock device has disappeared even though it shouldn't");
QVERIFY2(NymeaCore::instance()->deviceManager()->findConfiguredDevices(mockDeviceClassId).count() == oldCount, "Mock device has disappeared even though it shouldn't");
// Ok, now do the same with an autocreated one. It should go away
devices = NymeaCore::instance()->deviceManager()->findConfiguredDevices(mockDeviceAutoDeviceClassId);
QVERIFY2(devices.count() > 0, "There needs to be at least one auto-created Mock Device for this test");
oldCount = devices.count();
QVERIFY2(oldCount > 0, "There needs to be at least one auto-created Mock Device for this test");
device = devices.first();
DeviceClass dc = NymeaCore::instance()->deviceManager()->findDeviceClass(device->deviceClassId());
@ -1463,7 +1518,227 @@ void TestDevices::removeAutoDevice()
QCOMPARE(spy.count(), 1);
reply->deleteLater();
QVERIFY2(NymeaCore::instance()->deviceManager()->findConfiguredDevices(mockDeviceAutoDeviceClassId).count() == 0, "Mock device has not disappeared even though it should have.");
// Make sure one mock device has disappeared
QCOMPARE(NymeaCore::instance()->deviceManager()->findConfiguredDevices(mockDeviceAutoDeviceClassId).count(), oldCount - 1);
}
void TestDevices::testBrowsing_data()
{
QTest::addColumn<DeviceId>("deviceId");
QTest::newRow("regular mock device") << m_mockDeviceId;
QTest::newRow("async mock device") << m_mockDeviceAsyncId;
}
void TestDevices::testBrowsing()
{
QFETCH(DeviceId, deviceId);
// Check if mockdevice is browsable
QVariant response = injectAndWait("Devices.GetSupportedDevices");
QVariantMap mockDeviceClass;
foreach (const QVariant &deviceClassVariant, response.toMap().value("params").toMap().value("deviceClasses").toList()) {
if (DeviceClassId(deviceClassVariant.toMap().value("id").toString()) == mockDeviceClassId) {
mockDeviceClass = deviceClassVariant.toMap();
}
}
QVERIFY2(DeviceClassId(mockDeviceClass.value("id").toString()) == mockDeviceClassId, "Could not find mock device");
QCOMPARE(mockDeviceClass.value("browsable").toBool(), true);
// Browse it
QVariantMap params;
params.insert("deviceId", deviceId);
response = injectAndWait("Devices.BrowseDevice", params);
QCOMPARE(response.toMap().value("params").toMap().value("deviceError").toString(), QString("DeviceErrorNoError"));
QVariantList browserEntries = response.toMap().value("params").toMap().value("items").toList();
QVERIFY2(browserEntries.count() > 0, "BrowseDevice did not return any items.");
// Browse item 001, it should be a folder with 2 items
params.insert("itemId", "001");
response = injectAndWait("Devices.BrowseDevice", params);
QCOMPARE(response.toMap().value("params").toMap().value("deviceError").toString(), QString("DeviceErrorNoError"));
browserEntries = response.toMap().value("params").toMap().value("items").toList();
QVERIFY2(browserEntries.count() == 2, "BrowseDevice did not return 2 items as childs in folder with id 001.");
// Browse a non-existent item
params["itemId"] = "this-does-not-exist";
response = injectAndWait("Devices.BrowseDevice", params);
browserEntries = response.toMap().value("params").toMap().value("items").toList();
QCOMPARE(response.toMap().value("params").toMap().value("deviceError").toString(), QString("DeviceErrorItemNotFound"));
QCOMPARE(browserEntries.count(), 0);
}
void TestDevices::discoverDeviceParenting()
{
// Try to discover a mock child device. We don't have a mockParent yet, so it should fail
QSignalSpy spy(NymeaCore::instance()->deviceManager(), &DeviceManager::devicesDiscovered);
Device::DeviceError status = NymeaCore::instance()->deviceManager()->discoverDevices(mockChildDeviceClassId, ParamList());
QCOMPARE(status, Device::DeviceErrorAsync);
spy.wait();
QCOMPARE(spy.first().at(0).value<DeviceClassId>().toString(), mockChildDeviceClassId.toString());
QList<DeviceDescriptor> descriptors = spy.first().at(1).value<QList<DeviceDescriptor> >();
QVERIFY(descriptors.count() == 0);
// Now create a mock parent by discovering...
spy.clear();
status = NymeaCore::instance()->deviceManager()->discoverDevices(mockParentDeviceClassId, ParamList());
QCOMPARE(status, Device::DeviceErrorAsync);
spy.wait();
QVERIFY(spy.count() == 1);
QCOMPARE(spy.first().at(0).value<DeviceClassId>().toString(), mockParentDeviceClassId.toString());
descriptors = spy.first().at(1).value<QList<DeviceDescriptor> >();
QVERIFY(descriptors.count() == 1);
DeviceDescriptorId descriptorId = descriptors.first().id();
QSignalSpy addSpy(NymeaCore::instance()->deviceManager(), &DeviceManager::deviceAdded);
status = NymeaCore::instance()->deviceManager()->addConfiguredDevice(mockParentDeviceClassId, "Mock Parent (Discovered)", descriptorId);
QCOMPARE(status, Device::DeviceErrorNoError);
QCOMPARE(addSpy.count(), 2); // Mock device parent will also auto-create a child instantly
Device *parentDevice = addSpy.at(1).first().value<Device*>();
qCDebug(dcTests()) << "Added device:" << parentDevice->name();
QVERIFY(parentDevice->deviceClassId() == mockParentDeviceClassId);
// Ok we have our parent device, let's discover for childs again
spy.clear();
status = NymeaCore::instance()->deviceManager()->discoverDevices(mockChildDeviceClassId, ParamList());
QCOMPARE(status, Device::DeviceErrorAsync);
spy.wait();
QCOMPARE(spy.first().at(0).value<DeviceClassId>().toString(), mockChildDeviceClassId.toString());
descriptors = spy.first().at(1).value<QList<DeviceDescriptor> >();
QVERIFY(descriptors.count() == 1);
descriptorId = descriptors.first().id();
// Found one! Adding it...
addSpy.clear();
status = NymeaCore::instance()->deviceManager()->addConfiguredDevice(mockChildDeviceClassId, "Mock Child (Discovered)", descriptorId);
QCOMPARE(status, Device::DeviceErrorNoError);
QCOMPARE(addSpy.count(), 1);
Device *childDevice = addSpy.at(0).first().value<Device*>();
qCDebug(dcTests()) << "Added device:" << childDevice->name();
QVERIFY(childDevice->deviceClassId() == mockChildDeviceClassId);
// Now delete the parent and make sure the child will be deleted too
QSignalSpy removeSpy(NymeaCore::instance(), &NymeaCore::deviceRemoved);
QPair<Device::DeviceError, QList<RuleId> > ret = NymeaCore::instance()->removeConfiguredDevice(parentDevice->id(), QHash<RuleId, RuleEngine::RemovePolicy>());
QCOMPARE(ret.first, Device::DeviceErrorNoError);
QCOMPARE(removeSpy.count(), 3); // The parent, the auto-mock and the discovered mock
}
void TestDevices::testExecuteBrowserItem_data()
{
QTest::addColumn<DeviceId>("deviceId");
QTest::addColumn<QString>("itemId");
QTest::addColumn<QString>("deviceError");
QTest::newRow("regular mock device") << m_mockDeviceId << "002" << "DeviceErrorNoError";
QTest::newRow("regular mock device") << m_mockDeviceId << "001" << "DeviceErrorItemNotExecutable";
QTest::newRow("async mock device") << m_mockDeviceAsyncId << "002" << "DeviceErrorNoError";
}
void TestDevices::testExecuteBrowserItem()
{
QFETCH(DeviceId, deviceId);
QFETCH(QString, itemId);
QFETCH(QString, deviceError);
QVariantMap params;
params.insert("deviceId", deviceId);
params.insert("itemId", itemId);
QVariant response = injectAndWait("Actions.ExecuteBrowserItem", params);
qCDebug(dcTests()) << "resp" << response;
QCOMPARE(response.toMap().value("params").toMap().value("deviceError").toString(), deviceError);
}
void TestDevices::testExecuteBrowserItemAction_data()
{
QTest::addColumn<DeviceId>("deviceId");
QTest::newRow("regular mock device") << m_mockDeviceId;
QTest::newRow("async mock device") << m_mockDeviceAsyncId;
}
void TestDevices::testExecuteBrowserItemAction()
{
QFETCH(DeviceId, deviceId);
QVariantMap getItemsParams;
getItemsParams.insert("deviceId", deviceId);
QVariant response = injectAndWait("Devices.BrowseDevice", getItemsParams);
QCOMPARE(response.toMap().value("status").toString(), QString("success"));
QVariantList browserEntries = response.toMap().value("params").toMap().value("items").toList();
QVERIFY(browserEntries.count() > 2);
QVariantMap item002; // Find the item we need for this test
foreach (const QVariant &item, browserEntries) {
if (item.toMap().value("id").toString() == "002") {
item002 = item.toMap();
break;
}
}
QVERIFY2(item002.value("id").toString() == QString("002"), "Item with context actions not found");
QVERIFY2(item002.value("actionTypeIds").toList().count() > 0, "Item doesn't have actionTypeIds");
QVERIFY2(ActionTypeId(item002.value("actionTypeIds").toList().first().toString()) == mockAddToFavoritesBrowserItemActionTypeId, "AddToFavorites action type id not found in item");
// Browse favorites
// ID is "favorites" in mockDevice
// It should be ampty at this point
getItemsParams.insert("itemId", "favorites");
response = injectAndWait("Devices.BrowseDevice", getItemsParams);
QCOMPARE(response.toMap().value("status").toString(), QString("success"));
browserEntries = response.toMap().value("params").toMap().value("items").toList();
QVERIFY2(browserEntries.count() == 0, "Favorites should be empty at this point");
// Now add an item to the favorites
QVariantMap actionParams;
actionParams.insert("deviceId", deviceId);
actionParams.insert("itemId", "002");
actionParams.insert("actionTypeId", mockAddToFavoritesBrowserItemActionTypeId);
response = injectAndWait("Actions.ExecuteBrowserItemAction", actionParams);
QCOMPARE(response.toMap().value("status").toString(), QString("success"));
QCOMPARE(response.toMap().value("params").toMap().value("deviceError").toString(), QString("DeviceErrorNoError"));
qCDebug(dcTests()) << "res" << response;
// Fetch the list again
response = injectAndWait("Devices.BrowseDevice", getItemsParams);
QCOMPARE(response.toMap().value("status").toString(), QString("success"));
browserEntries = response.toMap().value("params").toMap().value("items").toList();
QCOMPARE(browserEntries.count(), 1);
QString favoriteItemId = browserEntries.first().toMap().value("id").toString();
QVERIFY2(!favoriteItemId.isEmpty(), "ItemId is empty in favorites list");
// Now remove the again from favorites
actionParams.clear();
actionParams.insert("deviceId", deviceId);
actionParams.insert("itemId", favoriteItemId);
actionParams.insert("actionTypeId", mockRemoveFromFavoritesBrowserItemActionTypeId);
response = injectAndWait("Actions.ExecuteBrowserItemAction", actionParams);
QCOMPARE(response.toMap().value("status").toString(), QString("success"));
QCOMPARE(response.toMap().value("params").toMap().value("deviceError").toString(), QString("DeviceErrorNoError"));
// Fetch the list again
response = injectAndWait("Devices.BrowseDevice", getItemsParams);
QCOMPARE(response.toMap().value("status").toString(), QString("success"));
browserEntries = response.toMap().value("params").toMap().value("items").toList();
QCOMPARE(browserEntries.count(), 0);
}
#include "testdevices.moc"

View File

@ -228,6 +228,7 @@ void PluginInfoCompiler::writeDeviceClass(const DeviceClass &deviceClass)
writeStateTypes(deviceClass.stateTypes(), deviceClass.name());
writeEventTypes(deviceClass.eventTypes(), deviceClass.name());
writeActionTypes(deviceClass.actionTypes(), deviceClass.name());
writeBrowserItemActionTypes(deviceClass.browserItemActionTypes(), deviceClass.name());
}
void PluginInfoCompiler::writeStateTypes(const StateTypes &stateTypes, const QString &deviceClassName)
@ -276,8 +277,24 @@ void PluginInfoCompiler::writeActionTypes(const ActionTypes &actionTypes, const
writeExtern(QString("extern ActionTypeId %1;").arg(variableName));
writeParams(actionType.paramTypes(), deviceClassName, "Action", actionType.name());
}
}
}
void PluginInfoCompiler::writeBrowserItemActionTypes(const ActionTypes &actionTypes, const QString &deviceClassName)
{
foreach (const ActionType &actionType, actionTypes) {
QString variableName = QString("%1%2BrowserItemActionTypeId").arg(deviceClassName, actionType.name()[0].toUpper() + actionType.name().right(actionType.name().length() - 1));
if (m_variableNames.contains(variableName)) {
qWarning().nospace() << "Error: Duplicate name " << variableName << " for Browser Item ActionType " << actionType.name() << " in DeviceClass " << deviceClassName << ". Skipping entry.";
return;
}
m_variableNames.append(variableName);
write(QString("ActionTypeId %1 = ActionTypeId(\"%2\");").arg(variableName).arg(actionType.id().toString()));
m_translationStrings.insert(actionType.displayName(), QString("The name of the Browser Item ActionType (%1) of DeviceClass %2").arg(actionType.id().toString()).arg(deviceClassName));
writeExtern(QString("extern ActionTypeId %1;").arg(variableName));
writeParams(actionType.paramTypes(), deviceClassName, "BrowserItemAction", actionType.name());
}
}
void PluginInfoCompiler::write(const QString &line)

View File

@ -45,6 +45,7 @@ private:
void writeStateTypes(const StateTypes &stateTypes, const QString &deviceClassName);
void writeEventTypes(const EventTypes &eventTypes, const QString &deviceClassName);
void writeActionTypes(const ActionTypes &actionTypes, const QString &deviceClassName);
void writeBrowserItemActionTypes(const ActionTypes &actionTypes, const QString &deviceClassName);
void write(const QString &line = QString());
void writeExtern(const QString &line = QString());