diff --git a/libnymea-core/integrations/thingmanagerimplementation.cpp b/libnymea-core/integrations/thingmanagerimplementation.cpp index 694fd9a3..f87c78d3 100644 --- a/libnymea-core/integrations/thingmanagerimplementation.cpp +++ b/libnymea-core/integrations/thingmanagerimplementation.cpp @@ -506,6 +506,47 @@ Thing::ThingError ThingManagerImplementation::setThingSettings(const ThingId &th return Thing::ThingErrorNoError; } +Thing::ThingError ThingManagerImplementation::setEventLogging(const ThingId &thingId, const EventTypeId &eventTypeId, bool enabled) +{ + Thing *thing = m_configuredThings.value(thingId); + if (!thing) { + qCWarning(dcThingManager()) << "Cannot configure event logging. Thing" << thingId.toString() << "not found"; + return Thing::ThingErrorThingNotFound; + } + if (!thing->thingClass().eventTypes().findById(eventTypeId).isValid()) { + qCWarning(dcThingManager()) << "Cannot configure event logging. Thing" << thingId.toString() << "has no event type with id" << eventTypeId; + return Thing::ThingErrorEventTypeNotFound; + } + QList loggedEventTypes = thing->loggedEventTypeIds(); + if (enabled && !loggedEventTypes.contains(eventTypeId)) { + loggedEventTypes.append(eventTypeId); + thing->setLoggedEventTypeIds(loggedEventTypes); + emit thingChanged(thing); + } else if (!enabled && loggedEventTypes.contains(eventTypeId)) { + loggedEventTypes.removeAll(eventTypeId); + thing->setLoggedEventTypeIds(loggedEventTypes); + emit thingChanged(thing); + } + return Thing::ThingErrorNoError; +} + +Thing::ThingError ThingManagerImplementation::setStateFilter(const ThingId &thingId, const StateTypeId &stateTypeId, Types::StateValueFilter filter) +{ + Thing *thing = m_configuredThings.value(thingId); + if (!thing) { + qCWarning(dcThingManager()) << "Cannot configure state filter. Thing" << thingId.toString() << "not found"; + return Thing::ThingErrorThingNotFound; + } + if (!thing->thingClass().stateTypes().findById(stateTypeId).isValid()) { + qCWarning(dcThingManager()) << "Cannot configure state filter. Thing" << thingId.toString() << "has no state type with id" << stateTypeId; + return Thing::ThingErrorEventTypeNotFound; + } + + thing->setStateValueFilter(stateTypeId, filter); + emit thingChanged(thing); + return Thing::ThingErrorNoError; +} + ThingPairingInfo* ThingManagerImplementation::pairThing(const ThingClassId &thingClassId, const ParamList ¶ms, const QString &name) { PairingTransactionId transactionId = PairingTransactionId::createPairingTransactionId(); @@ -650,6 +691,14 @@ ThingPairingInfo *ThingManagerImplementation::confirmPairing(const PairingTransa ParamList settings = buildParams(thingClass.settingsTypes(), ParamList()); thing->setSettings(settings); + QList loggedEventTypeIds; + foreach (const EventType &eventType, thingClass.eventTypes()) { + if (eventType.suggestLogging()) { + loggedEventTypeIds.append(eventType.id()); + } + } + thing->setLoggedEventTypeIds(loggedEventTypeIds); + ThingSetupInfo *info = setupThing(thing); connect(info, &ThingSetupInfo::finished, thing, [this, info, externalInfo, addNewThing](){ @@ -746,6 +795,14 @@ ThingSetupInfo* ThingManagerImplementation::addConfiguredThingInternal(const Thi ParamList settings = buildParams(thingClass.settingsTypes(), ParamList()); thing->setSettings(settings); + QList loggedEventTypeIds; + foreach (const EventType &eventType, thingClass.eventTypes()) { + if (eventType.suggestLogging()) { + loggedEventTypeIds.append(eventType.id()); + } + } + thing->setLoggedEventTypeIds(loggedEventTypeIds); + ThingSetupInfo *info = setupThing(thing); connect(info, &ThingSetupInfo::finished, this, [this, info](){ if (info->status() != Thing::ThingErrorNoError) { @@ -1258,6 +1315,10 @@ ThingActionInfo *ThingManagerImplementation::executeAction(const Action &action) return info; } + connect(info, &ThingActionInfo::finished, this, [=](){ + emit actionExecuted(action, info->status()); + }); + plugin->executeAction(info); return info; @@ -1541,6 +1602,14 @@ void ThingManagerImplementation::loadConfiguredThings() thing->setSettings(thingSettings); + QList loggedEventTypeIds; + foreach (const EventType &eventType, thingClass.eventTypes()) { + if (eventType.suggestLogging()) { + loggedEventTypeIds.append(eventType.id()); + } + } + thing->setLoggedEventTypeIds(loggedEventTypeIds); + settings.endGroup(); // ThingId // We always add the thing to the list in this case. If it's in the stored things @@ -1659,6 +1728,14 @@ void ThingManagerImplementation::onAutoThingsAppeared(const ThingDescriptors &th thing->setSettings(settings); thing->setParentId(thingDescriptor.parentId()); + QList loggedEventTypeIds; + foreach (const EventType &eventType, thingClass.eventTypes()) { + if (eventType.suggestLogging()) { + loggedEventTypeIds.append(eventType.id()); + } + } + thing->setLoggedEventTypeIds(loggedEventTypeIds); + qCDebug(dcThingManager()) << "Setting up auto thing:" << thing->name() << thing->id().toString(); ThingSetupInfo *info = setupThing(thing); @@ -1725,7 +1802,7 @@ void ThingManagerImplementation::cleanupThingStateCache() } } -void ThingManagerImplementation::onEventTriggered(const Event &event) +void ThingManagerImplementation::onEventTriggered(Event event) { // Doing some sanity checks here... Thing *thing = m_configuredThings.value(event.thingId()); @@ -1738,7 +1815,12 @@ void ThingManagerImplementation::onEventTriggered(const Event &event) qCWarning(dcThingManager()) << "The given thing does not have an event type of id " + event.eventTypeId().toString() + ". Not forwarding event."; return; } - // All good, forward the event + // configure logging + if (thing->loggedEventTypeIds().contains(event.eventTypeId())) { + event.setLogged(true); + } + + // Forward the event emit eventTriggered(event); } @@ -1755,7 +1837,7 @@ void ThingManagerImplementation::slotThingStateValueChanged(const StateTypeId &s Param valueParam(ParamTypeId(stateTypeId.toString()), value); Event event(EventTypeId(stateTypeId.toString()), thing->id(), ParamList() << valueParam, true); - emit eventTriggered(event); + onEventTriggered(event); syncIOConnection(thing, stateTypeId); } @@ -2022,6 +2104,7 @@ void ThingManagerImplementation::loadThingStates(Thing *thing) } else { thing->setStateValue(stateType.id(), stateType.defaultValue()); } + thing->setStateValueFilter(stateType.id(), stateType.filter()); } settings.endGroup(); } @@ -2179,7 +2262,7 @@ IntegrationPlugin *ThingManagerImplementation::createCppIntegrationPlugin(const return nullptr; } - pluginIface->setMetaData(PluginMetadata(pluginInfo)); + pluginIface->setMetaData(metaData); return pluginIface; } diff --git a/libnymea-core/integrations/thingmanagerimplementation.h b/libnymea-core/integrations/thingmanagerimplementation.h index e8628e81..61f87407 100644 --- a/libnymea-core/integrations/thingmanagerimplementation.h +++ b/libnymea-core/integrations/thingmanagerimplementation.h @@ -106,6 +106,9 @@ public: Thing::ThingError editThing(const ThingId &thingId, const QString &name) override; Thing::ThingError setThingSettings(const ThingId &thingId, const ParamList &settings) override; + Thing::ThingError setEventLogging(const ThingId &thingId, const EventTypeId &eventTypeId, bool enabled) override; + Thing::ThingError setStateFilter(const ThingId &thingId, const StateTypeId &stateTypeId, Types::StateValueFilter filter) override; + Thing::ThingError removeConfiguredThing(const ThingId &thingId) override; ThingActionInfo* executeAction(const Action &action) override; @@ -140,7 +143,7 @@ private slots: void onAutoThingDisappeared(const ThingId &thingId); void onLoaded(); void cleanupThingStateCache(); - void onEventTriggered(const Event &event); + void onEventTriggered(Event event); // Only connect this to Things. It will query the sender() void slotThingStateValueChanged(const StateTypeId &stateTypeId, const QVariant &value); diff --git a/libnymea-core/jsonrpc/actionhandler.cpp b/libnymea-core/jsonrpc/actionhandler.cpp index 4a6721fe..b9f2999f 100644 --- a/libnymea-core/jsonrpc/actionhandler.cpp +++ b/libnymea-core/jsonrpc/actionhandler.cpp @@ -107,7 +107,7 @@ JsonReply* ActionHandler::ExecuteAction(const QVariantMap ¶ms, const JsonCon JsonReply *jsonReply = createAsyncReply("ExecuteAction"); - ThingActionInfo *info = NymeaCore::instance()->executeAction(action); + ThingActionInfo *info = NymeaCore::instance()->thingManager()->executeAction(action); connect(info, &ThingActionInfo::finished, jsonReply, [info, jsonReply, locale](){ QVariantMap data; data.insert("deviceError", enumValueName(info->status()).replace("Thing", "Device")); diff --git a/libnymea-core/jsonrpc/devicehandler.cpp b/libnymea-core/jsonrpc/devicehandler.cpp index c8b86c67..281869a7 100644 --- a/libnymea-core/jsonrpc/devicehandler.cpp +++ b/libnymea-core/jsonrpc/devicehandler.cpp @@ -62,6 +62,7 @@ DeviceHandler::DeviceHandler(QObject *parent) : registerEnum(); registerEnum(); registerEnum(); + registerEnum(); registerEnum(); registerEnum(); registerEnum(); @@ -900,7 +901,7 @@ JsonReply *DeviceHandler::ExecuteAction(const QVariantMap ¶ms, const JsonCon JsonReply *jsonReply = createAsyncReply("ExecuteAction"); - ThingActionInfo *info = NymeaCore::instance()->executeAction(action); + ThingActionInfo *info = NymeaCore::instance()->thingManager()->executeAction(action); connect(info, &ThingActionInfo::finished, jsonReply, [info, jsonReply, locale](){ QVariantMap data; data.insert("deviceError", enumValueName(info->status()).replace("Thing", "Device")); diff --git a/libnymea-core/jsonrpc/integrationshandler.cpp b/libnymea-core/jsonrpc/integrationshandler.cpp index 08243330..3110f746 100644 --- a/libnymea-core/jsonrpc/integrationshandler.cpp +++ b/libnymea-core/jsonrpc/integrationshandler.cpp @@ -61,6 +61,7 @@ IntegrationsHandler::IntegrationsHandler(ThingManager *thingManager, QObject *pa registerEnum(); registerEnum(); registerEnum(); + registerEnum(); registerEnum(); registerEnum(); registerEnum(); @@ -241,6 +242,22 @@ IntegrationsHandler::IntegrationsHandler(ThingManager *thingManager, QObject *pa returns.insert("thingError", enumRef()); registerMethod("SetThingSettings", description, params, returns); + params.clear(); returns.clear(); + description = "Enable/disable logging for the given event type on the given thing."; + params.insert("thingId", enumValueName(Uuid)); + params.insert("eventTypeId", enumValueName(Uuid)); + params.insert("enabled", enumValueName(Bool)); + returns.insert("thingError", enumRef()); + registerMethod("SetEventLogging", description, params, returns); + + params.clear(); returns.clear(); + description = "Set the filter for the given state on the given thing."; + params.insert("thingId", enumValueName(Uuid)); + params.insert("stateTypeId", enumValueName(Uuid)); + params.insert("filter", enumRef()); + returns.insert("thingError", enumRef()); + registerMethod("SetStateFilter", description, params, returns); + params.clear(); returns.clear(); description = "Remove a thing from the system."; params.insert("thingId", enumValueName(Uuid)); @@ -838,6 +855,26 @@ JsonReply *IntegrationsHandler::SetThingSettings(const QVariantMap ¶ms) return createReply(statusToReply(status)); } +JsonReply *IntegrationsHandler::SetEventLogging(const QVariantMap ¶ms) +{ + ThingId thingId = ThingId(params.value("thingId").toString()); + EventTypeId eventTypeId = EventTypeId(params.value("eventTypeId").toUuid()); + bool enabled = params.value("enabled").toBool(); + Thing::ThingError status = NymeaCore::instance()->thingManager()->setEventLogging(thingId, eventTypeId, enabled); + return createReply(statusToReply(status)); +} + +JsonReply *IntegrationsHandler::SetStateFilter(const QVariantMap ¶ms) +{ + ThingId thingId = ThingId(params.value("thingId").toString()); + StateTypeId stateTypeId = StateTypeId(params.value("stateTypeId").toUuid()); + QString filterString = params.value("filter").toString(); + QMetaEnum metaEnum = QMetaEnum::fromType(); + Types::StateValueFilter filter = static_cast(metaEnum.keyToValue(filterString.toUtf8())); + Thing::ThingError status = NymeaCore::instance()->thingManager()->setStateFilter(thingId, stateTypeId, filter); + return createReply(statusToReply(status)); +} + JsonReply* IntegrationsHandler::GetEventTypes(const QVariantMap ¶ms, const JsonContext &context) const { ThingClass thingClass = NymeaCore::instance()->thingManager()->findThingClass(ThingClassId(params.value("thingClassId").toString())); @@ -958,7 +995,7 @@ JsonReply *IntegrationsHandler::ExecuteAction(const QVariantMap ¶ms, const J JsonReply *jsonReply = createAsyncReply("ExecuteAction"); - ThingActionInfo *info = NymeaCore::instance()->executeAction(action); + ThingActionInfo *info = NymeaCore::instance()->thingManager()->executeAction(action); connect(info, &ThingActionInfo::finished, jsonReply, [info, jsonReply, locale](){ QVariantMap data; data.insert("thingError", enumValueName(info->status())); diff --git a/libnymea-core/jsonrpc/integrationshandler.h b/libnymea-core/jsonrpc/integrationshandler.h index f61233f0..98046db2 100644 --- a/libnymea-core/jsonrpc/integrationshandler.h +++ b/libnymea-core/jsonrpc/integrationshandler.h @@ -59,6 +59,8 @@ public: Q_INVOKABLE JsonReply *EditThing(const QVariantMap ¶ms); Q_INVOKABLE JsonReply *RemoveThing(const QVariantMap ¶ms); Q_INVOKABLE JsonReply *SetThingSettings(const QVariantMap ¶ms); + Q_INVOKABLE JsonReply *SetEventLogging(const QVariantMap ¶ms); + Q_INVOKABLE JsonReply *SetStateFilter(const QVariantMap ¶ms); Q_INVOKABLE JsonReply *GetEventTypes(const QVariantMap ¶ms, const JsonContext &context) const; Q_INVOKABLE JsonReply *GetActionTypes(const QVariantMap ¶ms, const JsonContext &context) const; diff --git a/libnymea-core/logging/logengine.cpp b/libnymea-core/logging/logengine.cpp index 5f6630fc..173f4be9 100644 --- a/libnymea-core/logging/logengine.cpp +++ b/libnymea-core/logging/logengine.cpp @@ -34,6 +34,8 @@ #include "logging.h" #include "logvaluetool.h" +#include "integrations/thingmanager.h" + #include #include #include @@ -104,6 +106,13 @@ LogEngine::~LogEngine() m_db.close(); } +void LogEngine::setThingManager(ThingManager *thingManager) +{ + m_thingManager = thingManager; + connect(thingManager, &ThingManager::eventTriggered, this, &LogEngine::logEvent); + connect(thingManager, &ThingManager::actionExecuted, this, &LogEngine::logAction); +} + LogEntriesFetchJob *LogEngine::fetchLogEntries(const LogFilter &filter) { QList results; @@ -214,6 +223,7 @@ void LogEngine::clearDatabase() void LogEngine::logSystemEvent(const QDateTime &dateTime, bool active, Logging::LoggingLevel level) { + qCDebug(dcLogEngine()) << "Logging system event:" << active; LogEntry entry(dateTime, level, Logging::LoggingSourceSystem); entry.setEventType(Logging::LoggingEventTypeActiveChange); entry.setActive(active); @@ -222,6 +232,10 @@ void LogEngine::logSystemEvent(const QDateTime &dateTime, bool active, Logging:: void LogEngine::logEvent(const Event &event) { + if (!event.logged()) { + return; + } + QVariantList valueList; Logging::LoggingSource sourceType; if (event.isStateChangeEvent()) { @@ -249,9 +263,10 @@ void LogEngine::logEvent(const Event &event) appendLogEntry(entry); } -void LogEngine::logAction(const Action &action, Logging::LoggingLevel level, int errorCode) +void LogEngine::logAction(const Action &action, Thing::ThingError status) { - LogEntry entry(level, Logging::LoggingSourceActions, errorCode); + Logging::LoggingLevel level = status == Thing::ThingErrorNoError ? Logging::LoggingLevelInfo : Logging::LoggingLevelAlert; + LogEntry entry(QDateTime::currentDateTime(), level, Logging::LoggingSourceActions, status); entry.setTypeId(action.actionTypeId()); entry.setThingId(action.thingId()); diff --git a/libnymea-core/logging/logengine.h b/libnymea-core/logging/logengine.h index 8ce94c8c..a3749a2e 100644 --- a/libnymea-core/logging/logengine.h +++ b/libnymea-core/logging/logengine.h @@ -38,6 +38,7 @@ #include "types/browseritemaction.h" #include "types/browseraction.h" #include "ruleengine/rule.h" +#include "integrations/thingmanager.h" #include #include @@ -60,6 +61,8 @@ public: LogEngine(const QString &driver, const QString &dbName, const QString &hostname = QString("127.0.0.1"), const QString &username = QString(), const QString &password = QString(), int maxDBSize = 50000, QObject *parent = nullptr); ~LogEngine(); + void setThingManager(ThingManager *thingManager); + LogEntriesFetchJob *fetchLogEntries(const LogFilter &filter = LogFilter()); ThingsFetchJob *fetchThings(); @@ -68,9 +71,11 @@ public: void setMaxLogEntries(int maxLogEntries, int trimSize); void clearDatabase(); + void removeThingLogs(const ThingId &thingId); + void removeRuleLogs(const RuleId &ruleId); + +public slots: 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); @@ -78,8 +83,10 @@ public: void logRuleEnabledChanged(const Rule &rule, const bool &enabled); void logRuleActionsExecuted(const Rule &rule); void logRuleExitActionsExecuted(const Rule &rule); - void removeThingLogs(const ThingId &thingId); - void removeRuleLogs(const RuleId &ruleId); + +private slots: + void logEvent(const Event &event); + void logAction(const Action &action, Thing::ThingError status); signals: void logEntryAdded(const LogEntry &logEntry); @@ -114,6 +121,8 @@ private: bool m_initialized = false; bool m_dbMalformed = false; + ThingManager *m_thingManager = nullptr; + // When maxQueueLength is exceeded, jobs will be flagged and discarded if this source logs more events int m_maxQueueLength; QHash> m_flaggedJobs; diff --git a/libnymea-core/nymeacore.cpp b/libnymea-core/nymeacore.cpp index 4d0f05f7..a746f8b8 100644 --- a/libnymea-core/nymeacore.cpp +++ b/libnymea-core/nymeacore.cpp @@ -99,9 +99,6 @@ void NymeaCore::init(const QStringList &additionalInterfaces) { } m_timeManager = new TimeManager(this); - qCDebug(dcCore) << "Creating Log Engine"; - m_logger = new LogEngine(m_configuration->logDBDriver(), m_configuration->logDBName(), m_configuration->logDBHost(), m_configuration->logDBUser(), m_configuration->logDBPassword(), m_configuration->logDBMaxEntries(), this); - qCDebug(dcCore()) << "Creating User Manager"; m_userManager = new UserManager(NymeaSettings::settingsPath() + "/user-db.sqlite", this); @@ -120,6 +117,10 @@ void NymeaCore::init(const QStringList &additionalInterfaces) { qCDebug(dcCore) << "Creating Rule Engine"; m_ruleEngine = new RuleEngine(this); + qCDebug(dcCore) << "Creating Log Engine"; + m_logger = new LogEngine(m_configuration->logDBDriver(), m_configuration->logDBName(), m_configuration->logDBHost(), m_configuration->logDBUser(), m_configuration->logDBPassword(), m_configuration->logDBMaxEntries(), this); + m_logger->setThingManager(m_thingManager); + qCDebug(dcCore()) << "Creating Script Engine"; m_scriptEngine = new ScriptEngine(m_thingManager, this); m_serverManager->jsonServer()->registerHandler(new ScriptsHandler(m_scriptEngine, m_scriptEngine)); @@ -380,20 +381,6 @@ Thing::ThingError NymeaCore::removeConfiguredThing(const ThingId &thingId, const return removeError; } -ThingActionInfo* NymeaCore::executeAction(const Action &action) -{ - ThingActionInfo *info = m_thingManager->executeAction(action); - connect(info, &ThingActionInfo::finished, this, [this, info](){ - if (info->status() == Thing::ThingErrorNoError) { - m_logger->logAction(info->action()); - } else { - m_logger->logAction(info->action(), Logging::LoggingLevelAlert, info->status()); - } - }); - - return info; -} - BrowserActionInfo* NymeaCore::executeBrowserItem(const BrowserAction &browserAction) { BrowserActionInfo *info = m_thingManager->executeBrowserItem(browserAction); @@ -512,7 +499,7 @@ void NymeaCore::executeRuleActions(const QList ruleActions) foreach (const Action &action, actions) { qCDebug(dcRuleEngine) << "Executing action" << action.actionTypeId() << action.params(); - ThingActionInfo *info = executeAction(action); + ThingActionInfo *info = m_thingManager->executeAction(action); connect(info, &ThingActionInfo::finished, this, [info](){ if (info->status() != Thing::ThingErrorNoError) { qCWarning(dcRuleEngine) << "Error executing action:" << info->status() << info->displayMessage(); @@ -660,7 +647,6 @@ ZigbeeManager *NymeaCore::zigbeeManager() const void NymeaCore::gotEvent(const Event &event) { - m_logger->logEvent(event); emit eventTriggered(event); QList actions; diff --git a/libnymea-core/nymeacore.h b/libnymea-core/nymeacore.h index 3391c258..3201b4d6 100644 --- a/libnymea-core/nymeacore.h +++ b/libnymea-core/nymeacore.h @@ -84,7 +84,6 @@ public: QPair >removeConfiguredThing(const ThingId &thingId, const QHash &removePolicyList); Thing::ThingError removeConfiguredThing(const ThingId &thingId, const RuleEngine::RemovePolicy &removePolicy); - ThingActionInfo *executeAction(const Action &action); BrowserActionInfo* executeBrowserItem(const BrowserAction &browserAction); BrowserItemActionInfo* executeBrowserItemAction(const BrowserItemAction &browserItemAction); diff --git a/libnymea-core/servers/webserver.cpp b/libnymea-core/servers/webserver.cpp index 2ac0f391..7a93037c 100644 --- a/libnymea-core/servers/webserver.cpp +++ b/libnymea-core/servers/webserver.cpp @@ -301,7 +301,6 @@ void WebServer::readClient() HttpRequest request; if (m_incompleteRequests.contains(socket)) { - qCDebug(dcWebServer()) << "Append data to incomlete request"; request = m_incompleteRequests.take(socket); request.appendData(data); } else { diff --git a/libnymea/integrations/pluginmetadata.cpp b/libnymea/integrations/pluginmetadata.cpp index e63ad508..5d6163be 100644 --- a/libnymea/integrations/pluginmetadata.cpp +++ b/libnymea/integrations/pluginmetadata.cpp @@ -318,7 +318,11 @@ void PluginMetadata::parse(const QJsonObject &jsonObject) QJsonObject st = stateTypesJson.toObject(); bool writableState = false; - QPair verificationResult = verifyFields(StateType::typeProperties(), StateType::mandatoryTypeProperties(), st); + QStringList stateTypeProperties = {"id", "name", "displayName", "displayNameEvent", "type", "defaultValue", "cached", + "unit", "minValue", "maxValue", "possibleValues", "writable", "displayNameAction", + "ioType", "suggestLogging", "filter"}; + QStringList mandatoryStateTypeProperties = {"id", "name", "displayName", "displayNameEvent", "type", "defaultValue"}; + QPair verificationResult = verifyFields(stateTypeProperties, mandatoryStateTypeProperties, st); // Check mandatory fields if (!verificationResult.first.isEmpty()) { @@ -470,6 +474,18 @@ void PluginMetadata::parse(const QJsonObject &jsonObject) } stateType.setIOType(ioType); } + + stateType.setSuggestLogging(st.value("suggestLogging").toBool()); + + if (st.contains("filter")) { + QString filter = st.value("filter").toString(); + if (filter == "adaptive") { + stateType.setFilter(Types::StateValueFilterAdaptive); + } else if (!filter.isEmpty()) { + m_validationErrors.append("Thing class \"" + thingClass.name() + "\" state type \"" + stateTypeName + "\" has invalid filter value \"" + filter + "\". Supported filters are: \"adaptive\""); + hasError = true; + } + } stateTypes.append(stateType); // Events for state changed (Not checking for duplicate UUID, this is expected to be the same as the state!) @@ -485,6 +501,7 @@ void PluginMetadata::parse(const QJsonObject &jsonObject) paramType.setUnit(stateType.unit()); eventType.setParamTypes(QList() << paramType); eventType.setIndex(stateType.index()); + eventType.setSuggestLogging(st.value("suggestLogging").toBool()); eventTypes.append(eventType); // ActionTypes for writeable StateTypes @@ -497,7 +514,6 @@ void PluginMetadata::parse(const QJsonObject &jsonObject) actionTypes.append(actionType); } } - thingClass.setStateTypes(stateTypes); // ActionTypes index = 0; @@ -544,7 +560,6 @@ void PluginMetadata::parse(const QJsonObject &jsonObject) actionTypes.append(actionType); } - thingClass.setActionTypes(actionTypes); // EventTypes index = 0; @@ -580,6 +595,7 @@ void PluginMetadata::parse(const QJsonObject &jsonObject) EventType eventType(eventTypeId); eventType.setName(eventTypeName); eventType.setDisplayName(et.value("displayName").toString()); + eventType.setSuggestLogging(et.value("suggestLogging").toBool()); eventType.setIndex(index++); QPair > paramVerification = parseParamTypes(et.value("paramTypes").toArray()); @@ -590,7 +606,6 @@ void PluginMetadata::parse(const QJsonObject &jsonObject) } eventTypes.append(eventType); } - thingClass.setEventTypes(eventTypes); // BrowserItemActionTypes index = 0; @@ -637,7 +652,6 @@ void PluginMetadata::parse(const QJsonObject &jsonObject) browserItemActionTypes.append(actionType); } - thingClass.setBrowserItemActionTypes(browserItemActionTypes); // Read interfaces QStringList interfaces; @@ -646,22 +660,18 @@ void PluginMetadata::parse(const QJsonObject &jsonObject) if (!iface.isValid()) { m_validationErrors.append("Thing class \"" + thingClass.name() + "\" uses non-existing interface \"" + value.toString() + "\""); hasError = true; + continue; } - StateTypes stateTypes(thingClass.stateTypes()); - ActionTypes actionTypes(thingClass.actionTypes()); - EventTypes eventTypes(thingClass.eventTypes()); - foreach (const InterfaceStateType &ifaceStateType, iface.stateTypes()) { - StateType stateType = stateTypes.findByName(ifaceStateType.name()); - if (stateType.id().isNull()) { + if (!stateTypes.contains(ifaceStateType.name())) { if (!ifaceStateType.optional()) { m_validationErrors.append("Thing class \"" + thingClass.name() + "\" claims to implement interface \"" + value.toString() + "\" but doesn't implement state \"" + ifaceStateType.name() + "\""); hasError = true; - } else { - continue; } + continue; } + StateType &stateType = stateTypes[ifaceStateType.name()]; if (ifaceStateType.type() != stateType.type()) { m_validationErrors.append("Thing class \"" + thingClass.name() + "\" claims to implement interface \"" + value.toString() + "\" but state \"" + stateType.name() + "\" has not matching type: \"" + QVariant::typeToName(stateType.type()) + "\" != \"" + QVariant::typeToName(ifaceStateType.type()) + "\""); hasError = true; @@ -697,18 +707,22 @@ void PluginMetadata::parse(const QJsonObject &jsonObject) m_validationErrors.append("Thing class \"" + thingClass.name() + "\" claims to implement interface \"" + value.toString() + "\" but state \"" + stateType.name() + "\" has not matching unit: \"" + unitEnum.valueToKey(ifaceStateType.unit()) + "\" != \"" + unitEnum.valueToKey(stateType.unit())); hasError = true; } + + // Override logged property as the interface has higher priority than the plugin dev + if (ifaceStateType.loggingOverride()) { + stateType.setSuggestLogging(ifaceStateType.suggestLogging()); + } } foreach (const InterfaceActionType &ifaceActionType, iface.actionTypes()) { - ActionType actionType = actionTypes.findByName(ifaceActionType.name()); - if (actionType.id().isNull()) { + if (!actionTypes.contains(ifaceActionType.name())) { if (!ifaceActionType.optional()) { m_validationErrors.append("Thing class \"" + thingClass.name() + "\" claims to implement interface \"" + value.toString() + "\" but doesn't implement action \"" + ifaceActionType.name() + "\""); hasError = true; - } else { - continue; } + continue; } + ActionType &actionType = actionTypes[ifaceActionType.name()]; // Verify the params as required by the interface are available foreach (const ParamType &ifaceActionParamType, ifaceActionType.paramTypes()) { ParamType paramType = actionType.paramTypes().findByName(ifaceActionParamType.name()); @@ -779,15 +793,14 @@ void PluginMetadata::parse(const QJsonObject &jsonObject) } foreach (const InterfaceEventType &ifaceEventType, iface.eventTypes()) { - EventType eventType = eventTypes.findByName(ifaceEventType.name()); - if (!eventType.isValid()) { + if (!eventTypes.contains(ifaceEventType.name())) { if (!ifaceEventType.optional()) { m_validationErrors.append("Thing class \"" + thingClass.name() + "\" claims to implement interface \"" + value.toString() + "\" but doesn't implement event \"" + ifaceEventType.name() + "\""); hasError = true; - } else { - continue; } + continue; } + EventType &eventType = eventTypes[ifaceEventType.name()]; // Verify all the params as required by the interface are available foreach (const ParamType &ifaceEventParamType, ifaceEventType.paramTypes()) { @@ -842,6 +855,11 @@ void PluginMetadata::parse(const QJsonObject &jsonObject) } } + // Override logging + if (ifaceEventType.loggingOverride()) { + eventType.setSuggestLogging(ifaceEventType.suggestLogging()); + } + // Note: No need to check for default values (as with actions) for additional params as // an emitted event always needs to have params filled with values. The client might use them or not... } @@ -851,6 +869,11 @@ void PluginMetadata::parse(const QJsonObject &jsonObject) interfaces.removeDuplicates(); thingClass.setInterfaces(interfaces); + thingClass.setStateTypes(stateTypes); + thingClass.setActionTypes(actionTypes); + thingClass.setEventTypes(eventTypes); + thingClass.setBrowserItemActionTypes(browserItemActionTypes); + m_thingClasses.append(thingClass); } } diff --git a/libnymea/integrations/statevaluefilters/statevaluefilter.cpp b/libnymea/integrations/statevaluefilters/statevaluefilter.cpp new file mode 100644 index 00000000..618f8c5b --- /dev/null +++ b/libnymea/integrations/statevaluefilters/statevaluefilter.cpp @@ -0,0 +1,45 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2021, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU Lesser General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU Lesser General Public License as published by the Free +* Software Foundation; version 3. This project is distributed in the hope that +* it will be useful, but WITHOUT ANY WARRANTY; without even the implied +* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "statevaluefilter.h" + +#include "loggingcategories.h" + +NYMEA_LOGGING_CATEGORY(dcStateValueFilter, "StateValueFilter") + +StateValueFilter::StateValueFilter() +{ + +} + +StateValueFilter::~StateValueFilter() +{ + +} diff --git a/libnymea/integrations/statevaluefilters/statevaluefilter.h b/libnymea/integrations/statevaluefilters/statevaluefilter.h new file mode 100644 index 00000000..239d5402 --- /dev/null +++ b/libnymea/integrations/statevaluefilters/statevaluefilter.h @@ -0,0 +1,51 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2021, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU Lesser General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU Lesser General Public License as published by the Free +* Software Foundation; version 3. This project is distributed in the hope that +* it will be useful, but WITHOUT ANY WARRANTY; without even the implied +* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef STATEVALUEFILTER_H +#define STATEVALUEFILTER_H + +#include +#include + +Q_DECLARE_LOGGING_CATEGORY(dcStateValueFilter) + +class StateValueFilter +{ +public: + StateValueFilter(); + virtual ~StateValueFilter(); + + virtual void addValue(const QVariant &value) = 0; + + virtual QVariant filteredValue() const = 0; + +}; + +#endif // STATEVALUEFILTER_H diff --git a/libnymea/integrations/statevaluefilters/statevaluefilteradaptive.cpp b/libnymea/integrations/statevaluefilters/statevaluefilteradaptive.cpp new file mode 100644 index 00000000..e24bd052 --- /dev/null +++ b/libnymea/integrations/statevaluefilters/statevaluefilteradaptive.cpp @@ -0,0 +1,142 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2021, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU Lesser General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU Lesser General Public License as published by the Free +* Software Foundation; version 3. This project is distributed in the hope that +* it will be useful, but WITHOUT ANY WARRANTY; without even the implied +* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "statevaluefilteradaptive.h" + +#include + +StateValueFilterAdaptive::StateValueFilterAdaptive() +{ + +} + +void StateValueFilterAdaptive::addValue(const QVariant &value) +{ + m_inputValues.prepend(value.toDouble()); + m_inputValueCount++; + update(); +} + +QVariant StateValueFilterAdaptive::filteredValue() const +{ + return m_outputValue; +} + +void StateValueFilterAdaptive::update() +{ + while (m_inputValues.count() > m_windowSize) { + m_inputValues.removeLast(); + } + + if (m_inputValues.isEmpty()) { + m_outputValue = 0; + return; + } + + if (m_inputValues.count() == 1) { + // Not enough data + m_outputValue = m_inputValues.first(); + m_outputValueCount++; + return; + } + + double currentValue = m_inputValues.first(); + if (qFuzzyCompare(currentValue, 0)) { + // If we went to 0, follow right away. + m_outputValue = 0; + return; + } + + // Calculate average of history, for all values and for all but the last one + double sum = 0; + for (int i = 0; i < m_inputValues.count(); i++) { + sum += m_inputValues.at(i); + } + double normalizedValue = sum / m_inputValues.count(); + double previousNormalizedValue = (sum - m_inputValues.first()) / (m_inputValues.count() - 1); + + if (qFuzzyCompare(previousNormalizedValue, 0)) { + // We can't calculate anything if the history is at 0. Follow right away to the new value. + m_outputValue = currentValue; + m_outputValueCount++; + return; + } + + // Calculate change ratio of the last value compared to the previous one, unflitered and filtered + double changeRatioToAverage = 1 - qAbs(currentValue / previousNormalizedValue); + double changeRatioToCurrentOutput = 1 - qAbs(currentValue / m_outputValue); + double changeRatioFiltered = 1 - qAbs(normalizedValue / previousNormalizedValue); + + + // If the unfiltered value changes for more than 3 times the standard deviation of the jittering values + // it's a 99% chance a big change happened that's not jitter (e.g turned on/off) + // Discard the history and follow the new value right away + if (qAbs(changeRatioToAverage) > m_standardDeviation * 3) { + m_inputValues.clear(); + m_inputValues.prepend(currentValue); + m_totalDeviation = 0; + if (!qFuzzyCompare(m_outputValue, normalizedValue)) { + m_outputValue = currentValue; + qCDebug(dcStateValueFilter()) << "Updating output value:" << m_outputValue << "(input exceeds max jitter)"; + m_outputValueCount++; + } + + // We're considering it jitter + } else { + // Add up the deviation from the current actual value to the currently filtered value + m_totalDeviation += changeRatioToCurrentOutput; + + // If the filtered value changed for more than the the standard deviation, follow slowly + // In order to not get stuck on being off for the standard deviation forever, also move closer + // to the new value when the summed up deviation exceeds the maximum allowed total deviation + if (qAbs(changeRatioFiltered) > m_standardDeviation || qAbs(m_totalDeviation) > m_maxTotalDeviation) { + m_totalDeviation = 0; + if (!qFuzzyCompare(m_outputValue, normalizedValue)) { + qCDebug(dcStateValueFilter()) << "Updating output value:" << normalizedValue << "(drift compensation)"; + m_outputValue = normalizedValue; + m_outputValueCount++; + } + } + + // Poor mans solution to calculate standard deviation. Not as precise, but much faster than looping over history again + m_standardDeviation = ((m_standardDeviation * m_windowSize) + qAbs(changeRatioToAverage)) / (m_windowSize + 1); + } + + // reset stats on overflow of counters + if (m_inputValueCount < m_outputValueCount) { + m_outputValueCount = 0; + } + + qCDebug(dcStateValueFilter()) << "Filter statistics for" << this; + qCDebug(dcStateValueFilter()) << "Input:" << currentValue << "AVG:" << previousNormalizedValue << "Filtered:" << normalizedValue; + qCDebug(dcStateValueFilter()) << "Change ratios: Input/average:" << changeRatioToAverage << "Filtered/average:" << changeRatioFiltered << "Input/output:" << changeRatioToCurrentOutput; + qCDebug(dcStateValueFilter()) << "Std deviation:" << m_standardDeviation << "Total deviation:" << m_totalDeviation; + qCDebug(dcStateValueFilter()) << "Compression ratio:" << (1.0 * m_inputValueCount / m_outputValueCount) << "(" << m_outputValueCount << "/" << m_inputValueCount << ")"; +} diff --git a/libnymea/integrations/statevaluefilters/statevaluefilteradaptive.h b/libnymea/integrations/statevaluefilters/statevaluefilteradaptive.h new file mode 100644 index 00000000..aebc89f6 --- /dev/null +++ b/libnymea/integrations/statevaluefilters/statevaluefilteradaptive.h @@ -0,0 +1,65 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2021, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU Lesser General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU Lesser General Public License as published by the Free +* Software Foundation; version 3. This project is distributed in the hope that +* it will be useful, but WITHOUT ANY WARRANTY; without even the implied +* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef STATEVALUEFILTERADAPTIVE_H +#define STATEVALUEFILTERADAPTIVE_H + +#include "statevaluefilter.h" + +class StateValueFilterAdaptive : public StateValueFilter +{ +public: + StateValueFilterAdaptive(); + + void addValue(const QVariant &value) override; + QVariant filteredValue() const override; + +private: + void update(); + +private: + QList m_inputValues; + + int m_windowSize = 20; + double m_standardDeviation = 0.05; + double m_maxTotalDeviation = 0.4; + + double m_totalDeviation = 0; + + double m_outputValue = 0; + + // Stats for debugging + quint64 m_inputValueCount = 0; + quint64 m_outputValueCount = 0; + + +}; + +#endif // STATEVALUEFILTERADAPTIVE_H diff --git a/libnymea/integrations/thing.cpp b/libnymea/integrations/thing.cpp index fb62b57f..17bee3fe 100644 --- a/libnymea/integrations/thing.cpp +++ b/libnymea/integrations/thing.cpp @@ -133,6 +133,7 @@ #include "thing.h" #include "types/event.h" #include "loggingcategories.h" +#include "statevaluefilters/statevaluefilteradaptive.h" #include @@ -334,9 +335,6 @@ void Thing::setStateValue(const StateTypeId &stateTypeId, const QVariant &value) } for (int i = 0; i < m_states.count(); ++i) { if (m_states.at(i).stateTypeId() == stateTypeId) { - if (m_states.at(i).value() == value) - return; - QVariant newValue = value; if (!newValue.convert(stateType.type())) { qCWarning(dcThing()).nospace() << m_name << ": Invalid value " << value << " for state " << stateType.name() << ". Type mismatch. Expected type: " << QVariant::typeToName(stateType.type()) << " (Discarding change)"; @@ -355,8 +353,13 @@ void Thing::setStateValue(const StateTypeId &stateTypeId, const QVariant &value) return; } - QVariant oldValue = m_states.at(i).value(); + StateValueFilter *filter = m_stateValueFilters.value(stateTypeId); + if (filter) { + filter->addValue(newValue); + newValue = filter->filteredValue(); + } + QVariant oldValue = m_states.at(i).value(); if (oldValue == newValue) { qCDebug(dcThing()).nospace() << m_name << ": Discarding state change for " << stateType.name() << " as the value did not actually change. Old value:" << oldValue << "New value:" << newValue; return; @@ -383,6 +386,11 @@ State Thing::state(const StateTypeId &stateTypeId) const return State(StateTypeId(), ThingId()); } +QList Thing::loggedEventTypeIds() const +{ + return m_loggedEventTypeIds; +} + /*! Returns the \l{ThingId} of the parent of this thing. If the parentId is not set, this thing does not have a parent. */ @@ -442,6 +450,27 @@ void Thing::setSetupStatus(Thing::ThingSetupStatus status, Thing::ThingError set emit setupStatusChanged(); } +void Thing::setLoggedEventTypeIds(const QList loggedEventTypeIds) +{ + m_loggedEventTypeIds = loggedEventTypeIds; +} + +void Thing::setStateValueFilter(const StateTypeId &stateTypeId, Types::StateValueFilter filter) +{ + for (int i = 0; i < m_states.count(); i++) { + if (m_states.at(i).stateTypeId() == stateTypeId) { + m_states[i].setFilter(filter); + StateValueFilter *stateValueFilter = m_stateValueFilters.take(stateTypeId); + if (stateValueFilter) { + delete stateValueFilter; + } + if (filter == Types::StateValueFilterAdaptive) { + m_stateValueFilters.insert(stateTypeId, new StateValueFilterAdaptive()); + } + } + } +} + Things::Things(const QList &other) { foreach (Thing* thing, other) { diff --git a/libnymea/integrations/thing.h b/libnymea/integrations/thing.h index 2e739aa0..efe941c7 100644 --- a/libnymea/integrations/thing.h +++ b/libnymea/integrations/thing.h @@ -45,6 +45,7 @@ #include class IntegrationPlugin; +class StateValueFilter; class LIBNYMEA_EXPORT Thing: public QObject { @@ -60,6 +61,7 @@ class LIBNYMEA_EXPORT Thing: public QObject Q_PROPERTY(QString setupDisplayMessage READ setupDisplayMessage NOTIFY setupStatusChanged USER true) Q_PROPERTY(ThingError setupError READ setupError NOTIFY setupStatusChanged) Q_PROPERTY(QUuid parentId READ parentId USER true) + Q_PROPERTY(QList loggedEventTypeIds READ loggedEventTypeIds USER true) public: enum ThingError { @@ -133,6 +135,8 @@ public: Q_INVOKABLE State state(const StateTypeId &stateTypeId) const; + QList loggedEventTypeIds() const; + ThingId parentId() const; void setParentId(const ThingId &parentId); @@ -161,6 +165,8 @@ private: Thing(const PluginId &pluginId, const ThingClass &thingClass, QObject *parent = nullptr); void setSetupStatus(ThingSetupStatus status, ThingError setupError, const QString &displayMessage = QString()); + void setLoggedEventTypeIds(const QList loggedEventTypeIds); + void setStateValueFilter(const StateTypeId &stateTypeId, Types::StateValueFilter filter); private: ThingClass m_thingClass; @@ -176,6 +182,9 @@ private: ThingSetupStatus m_setupStatus = ThingSetupStatusNone; ThingError m_setupError = ThingErrorNoError; QString m_setupDisplayMessage; + + QList m_loggedEventTypeIds; + QHash m_stateValueFilters; }; QDebug operator<<(QDebug dbg, Thing *device); diff --git a/libnymea/integrations/thingmanager.h b/libnymea/integrations/thingmanager.h index 06e78647..e564f45d 100644 --- a/libnymea/integrations/thingmanager.h +++ b/libnymea/integrations/thingmanager.h @@ -81,6 +81,9 @@ public: virtual Thing::ThingError editThing(const ThingId &thingId, const QString &name) = 0; virtual Thing::ThingError setThingSettings(const ThingId &thingId, const ParamList &settings) = 0; + virtual Thing::ThingError setEventLogging(const ThingId &thingId, const EventTypeId &eventTypeId, bool enabled) = 0; + virtual Thing::ThingError setStateFilter(const ThingId &thingId, const StateTypeId &stateTypeId, Types::StateValueFilter filter) = 0; + virtual Thing::ThingError removeConfiguredThing(const ThingId &thingId) = 0; virtual ThingActionInfo* executeAction(const Action &action) = 0; @@ -112,10 +115,11 @@ signals: void thingRemoved(const ThingId &thingId); void thingDisappeared(const ThingId &thingId); void thingAdded(Thing *thing); - void thingChanged(Thing *device); + void thingChanged(Thing *thing); void thingSettingChanged(const ThingId &thingId, const ParamTypeId &settingParamTypeId, const QVariant &value); void ioConnectionAdded(const IOConnection &ioConnection); void ioConnectionRemoved(const IOConnectionId &ioConnectionId); + void actionExecuted(const Action &action, Thing::ThingError status); }; #endif // THINGMANAGER_H diff --git a/libnymea/integrations/thingutils.cpp b/libnymea/integrations/thingutils.cpp index cdf52435..59c728af 100644 --- a/libnymea/integrations/thingutils.cpp +++ b/libnymea/integrations/thingutils.cpp @@ -200,6 +200,10 @@ Interface ThingUtils::loadInterface(const QString &name) stateType.setMinValue(stateVariant.toMap().value("minValue")); stateType.setMaxValue(stateVariant.toMap().value("maxValue")); stateType.setOptional(stateVariant.toMap().value("optional", false).toBool()); + if (stateVariant.toMap().contains("logged")) { + stateType.setLoggingOverride(true); + stateType.setSuggestLogging(stateVariant.toMap().value("logged", false).toBool()); + } if (stateVariant.toMap().contains("unit")) { QMetaEnum unitEnum = QMetaEnum::fromType(); int enumValue = unitEnum.keyToValue("Unit" + stateVariant.toMap().value("unit").toByteArray()); @@ -214,6 +218,8 @@ Interface ThingUtils::loadInterface(const QString &name) InterfaceEventType stateChangeEventType; stateChangeEventType.setName(stateType.name()); stateChangeEventType.setOptional(stateType.optional()); + stateChangeEventType.setSuggestLogging(stateType.suggestLogging()); + stateChangeEventType.setLoggingOverride(stateType.loggingOverride()); ParamType stateChangeEventParamType; stateChangeEventParamType.setName(stateType.name()); stateChangeEventParamType.setType(stateType.type()); @@ -236,6 +242,10 @@ Interface ThingUtils::loadInterface(const QString &name) InterfaceActionType actionType; actionType.setName(actionVariant.toMap().value("name").toString()); actionType.setOptional(actionVariant.toMap().value("optional").toBool()); +// if (actionVariant.toMap().contains("logged")) { +// actionType.setLoggingOverride(true); +// actionType.setSuggestLogging(actionVariant.toMap().value("logged").toBool()); +// } ParamTypes paramTypes; foreach (const QVariant &actionParamVariant, actionVariant.toMap().value("params").toList()) { ParamType paramType; @@ -254,6 +264,10 @@ Interface ThingUtils::loadInterface(const QString &name) InterfaceEventType eventType; eventType.setName(eventVariant.toMap().value("name").toString()); eventType.setOptional(eventVariant.toMap().value("optional").toBool()); + if (eventVariant.toMap().contains("logged")) { + eventType.setLoggingOverride(true); + eventType.setSuggestLogging(eventVariant.toMap().value("logged").toBool()); + } ParamTypes paramTypes; foreach (const QVariant &eventParamVariant, eventVariant.toMap().value("params").toList()) { ParamType paramType; diff --git a/libnymea/interfaces/battery.json b/libnymea/interfaces/battery.json index 8c580479..e6c0e3b2 100644 --- a/libnymea/interfaces/battery.json +++ b/libnymea/interfaces/battery.json @@ -2,7 +2,8 @@ "states": [ { "name": "batteryCritical", - "type": "bool" + "type": "bool", + "logged": true } ] } diff --git a/libnymea/interfaces/button.json b/libnymea/interfaces/button.json index 72211093..d98b2573 100644 --- a/libnymea/interfaces/button.json +++ b/libnymea/interfaces/button.json @@ -2,7 +2,8 @@ "description": "The base for all buttons that emit a pressed event.", "events": [ { - "name": "pressed" + "name": "pressed", + "logged": true } ] } diff --git a/libnymea/interfaces/closablesensor.json b/libnymea/interfaces/closablesensor.json index b8ef5b55..e8320fc5 100644 --- a/libnymea/interfaces/closablesensor.json +++ b/libnymea/interfaces/closablesensor.json @@ -4,7 +4,8 @@ "states": [ { "name": "closed", - "type": "bool" + "type": "bool", + "logged": true } ] } diff --git a/libnymea/interfaces/co2sensor.json b/libnymea/interfaces/co2sensor.json index 2174ebb8..e4673292 100644 --- a/libnymea/interfaces/co2sensor.json +++ b/libnymea/interfaces/co2sensor.json @@ -5,7 +5,8 @@ { "name": "co2", "type": "double", - "unit": "PartsPerMillion" + "unit": "PartsPerMillion", + "logged": true } ] } diff --git a/libnymea/interfaces/conductivitysensor.json b/libnymea/interfaces/conductivitysensor.json index d1d9e63c..4d4f7533 100644 --- a/libnymea/interfaces/conductivitysensor.json +++ b/libnymea/interfaces/conductivitysensor.json @@ -4,7 +4,8 @@ { "name": "conductivity", "type": "double", - "unit": "MicroSiemensPerCentimeter" + "unit": "MicroSiemensPerCentimeter", + "logged": true } ] } diff --git a/libnymea/interfaces/connectable.json b/libnymea/interfaces/connectable.json index bc061381..aca9566c 100644 --- a/libnymea/interfaces/connectable.json +++ b/libnymea/interfaces/connectable.json @@ -3,7 +3,8 @@ { "name": "connected", "type": "bool", - "defaultValue": false + "defaultValue": false, + "logged": true } ] } diff --git a/libnymea/interfaces/daylightsensor.json b/libnymea/interfaces/daylightsensor.json index db82c096..e391bec0 100644 --- a/libnymea/interfaces/daylightsensor.json +++ b/libnymea/interfaces/daylightsensor.json @@ -4,7 +4,8 @@ "states": [ { "name": "daylight", - "type": "bool" + "type": "bool", + "logged": true }, { "name": "sunriseTime", diff --git a/libnymea/interfaces/doorbell.json b/libnymea/interfaces/doorbell.json index e90aad65..bc6dd97d 100644 --- a/libnymea/interfaces/doorbell.json +++ b/libnymea/interfaces/doorbell.json @@ -2,7 +2,8 @@ "description": "An interface for doorbells. Emits \"doorbellPressed\" when the doorbell is pressed.", "events": [ { - "name": "doorbellPressed" + "name": "doorbellPressed", + "logged": true } ] } diff --git a/libnymea/interfaces/extendedclosable.json b/libnymea/interfaces/extendedclosable.json index d5ca5085..48b6342b 100644 --- a/libnymea/interfaces/extendedclosable.json +++ b/libnymea/interfaces/extendedclosable.json @@ -4,7 +4,8 @@ "states": [ { "name": "moving", - "type": "bool" + "type": "bool", + "logged": true }, { "name": "percentage", diff --git a/libnymea/interfaces/extendedsmartmeterconsumer.json b/libnymea/interfaces/extendedsmartmeterconsumer.json index f476bd9e..6aa31b31 100644 --- a/libnymea/interfaces/extendedsmartmeterconsumer.json +++ b/libnymea/interfaces/extendedsmartmeterconsumer.json @@ -5,7 +5,8 @@ { "name": "currentPower", "type": "double", - "unit": "Watt" + "unit": "Watt", + "logged": true } ] } diff --git a/libnymea/interfaces/extendedsmartmeterproducer.json b/libnymea/interfaces/extendedsmartmeterproducer.json index 0c8ad6c7..218f3bf0 100644 --- a/libnymea/interfaces/extendedsmartmeterproducer.json +++ b/libnymea/interfaces/extendedsmartmeterproducer.json @@ -4,7 +4,9 @@ "states": [ { "name": "currentPower", - "type": "double" + "type": "double", + "unit": "Watt", + "logged": true } ] } diff --git a/libnymea/interfaces/fingerprintreader.json b/libnymea/interfaces/fingerprintreader.json index 953222b5..5af7d805 100644 --- a/libnymea/interfaces/fingerprintreader.json +++ b/libnymea/interfaces/fingerprintreader.json @@ -24,7 +24,8 @@ "PinkyRight" ] } - ] + ], + "logged": true } ], "actions": [ diff --git a/libnymea/interfaces/garagegate.json b/libnymea/interfaces/garagegate.json index e692e7e9..653c68f8 100644 --- a/libnymea/interfaces/garagegate.json +++ b/libnymea/interfaces/garagegate.json @@ -5,7 +5,8 @@ { "name": "state", "type": "QString", - "allowedValues": ["open", "closed", "opening", "closing"] + "allowedValues": ["open", "closed", "opening", "closing"], + "logged": true }, { "name": "intermediatePosition", diff --git a/libnymea/interfaces/humiditysensor.json b/libnymea/interfaces/humiditysensor.json index 0f15fd86..a3629bf7 100644 --- a/libnymea/interfaces/humiditysensor.json +++ b/libnymea/interfaces/humiditysensor.json @@ -5,7 +5,8 @@ "name": "humidity", "type": "double", "minValue": 0, - "maxValue": 100 + "maxValue": 100, + "logged": true } ] } diff --git a/libnymea/interfaces/impulsegaragedoor.json b/libnymea/interfaces/impulsegaragedoor.json index 15155c3c..a662c9f2 100644 --- a/libnymea/interfaces/impulsegaragedoor.json +++ b/libnymea/interfaces/impulsegaragedoor.json @@ -3,7 +3,8 @@ "description": "This interface is for the simplest form of garage doors which can be controlled only via an impulse. Triggering the impulse will start moving the door, triggering it again will stop the movement. Triggering it yet another time will start movement in the reverse direction. Note that there is no feedback channel on such devices. The system has no chance of knowing the current state this device is actually in.", "actions": [ { - "name": "triggerImpulse" + "name": "triggerImpulse", + "logged": true } ] } diff --git a/libnymea/interfaces/inputtrigger.json b/libnymea/interfaces/inputtrigger.json index 393e47da..884a3797 100644 --- a/libnymea/interfaces/inputtrigger.json +++ b/libnymea/interfaces/inputtrigger.json @@ -1,7 +1,8 @@ { "events": [ { - "name": "triggered" + "name": "triggered", + "logged": true } ] } diff --git a/libnymea/interfaces/lightsensor.json b/libnymea/interfaces/lightsensor.json index 146b149a..54a425b2 100644 --- a/libnymea/interfaces/lightsensor.json +++ b/libnymea/interfaces/lightsensor.json @@ -4,7 +4,8 @@ { "name": "lightIntensity", "type": "double", - "unit": "Lux" + "unit": "Lux", + "logged": true } ] } diff --git a/libnymea/interfaces/longpressbutton.json b/libnymea/interfaces/longpressbutton.json index 75efeb8d..4a4ea4b5 100644 --- a/libnymea/interfaces/longpressbutton.json +++ b/libnymea/interfaces/longpressbutton.json @@ -3,7 +3,8 @@ "extends": "button", "events": [ { - "name": "longPressed" + "name": "longPressed", + "logged": true } ] } diff --git a/libnymea/interfaces/longpressmultibutton.json b/libnymea/interfaces/longpressmultibutton.json index 60fd2c7a..e696de50 100644 --- a/libnymea/interfaces/longpressmultibutton.json +++ b/libnymea/interfaces/longpressmultibutton.json @@ -9,7 +9,8 @@ "name": "buttonName", "type": "QString" } - ] + ], + "logged": true } ] } diff --git a/libnymea/interfaces/moisturesensor.json b/libnymea/interfaces/moisturesensor.json index bd01c0b9..a6c47379 100644 --- a/libnymea/interfaces/moisturesensor.json +++ b/libnymea/interfaces/moisturesensor.json @@ -3,7 +3,8 @@ "states": [ { "name": "moisture", - "type": "double" + "type": "double", + "logged": true } ] } diff --git a/libnymea/interfaces/multibutton.json b/libnymea/interfaces/multibutton.json index 8fc5ae08..20b94171 100644 --- a/libnymea/interfaces/multibutton.json +++ b/libnymea/interfaces/multibutton.json @@ -9,7 +9,8 @@ "name": "buttonName", "type": "QString" } - ] + ], + "logged": true } ] } diff --git a/libnymea/interfaces/noisesensor.json b/libnymea/interfaces/noisesensor.json index b69d0461..ec578352 100644 --- a/libnymea/interfaces/noisesensor.json +++ b/libnymea/interfaces/noisesensor.json @@ -5,7 +5,8 @@ { "name": "noise", "type": "double", - "unit": "Dezibel" + "unit": "Dezibel", + "logged": true } ] } diff --git a/libnymea/interfaces/notifications.json b/libnymea/interfaces/notifications.json index 1341ce3b..dc85d015 100644 --- a/libnymea/interfaces/notifications.json +++ b/libnymea/interfaces/notifications.json @@ -11,8 +11,8 @@ "name": "body", "type": "QString" } - ] + ], + "logged": true } - ] } diff --git a/libnymea/interfaces/power.json b/libnymea/interfaces/power.json index 160f9432..a70fbabf 100644 --- a/libnymea/interfaces/power.json +++ b/libnymea/interfaces/power.json @@ -3,7 +3,8 @@ { "name": "power", "type": "bool", - "writable": true + "writable": true, + "logged": true } ] } diff --git a/libnymea/interfaces/presencesensor.json b/libnymea/interfaces/presencesensor.json index 7806b3d9..6fcb8c00 100644 --- a/libnymea/interfaces/presencesensor.json +++ b/libnymea/interfaces/presencesensor.json @@ -4,7 +4,8 @@ "states": [ { "name": "isPresent", - "type": "bool" + "type": "bool", + "logged": true }, { "name": "lastSeenTime", diff --git a/libnymea/interfaces/pressuresensor.json b/libnymea/interfaces/pressuresensor.json index a8b45887..64802ed7 100644 --- a/libnymea/interfaces/pressuresensor.json +++ b/libnymea/interfaces/pressuresensor.json @@ -4,7 +4,8 @@ { "name": "pressure", "type": "double", - "unit": "MilliBar" + "unit": "MilliBar", + "logged": true } ] } diff --git a/libnymea/interfaces/smartlock.json b/libnymea/interfaces/smartlock.json index 498950d3..95c71fc2 100644 --- a/libnymea/interfaces/smartlock.json +++ b/libnymea/interfaces/smartlock.json @@ -4,7 +4,8 @@ { "name": "state", "type": "QString", - "allowedValues": ["locked", "locking", "unlocked", "unlocking", "unlatched", "unlatching"] + "allowedValues": ["locked", "locking", "unlocked", "unlocking", "unlatched", "unlatching"], + "logged": true } ], "actions": [ diff --git a/libnymea/interfaces/statefulgaragedoor.json b/libnymea/interfaces/statefulgaragedoor.json index cd0c9ecb..ed1271cd 100644 --- a/libnymea/interfaces/statefulgaragedoor.json +++ b/libnymea/interfaces/statefulgaragedoor.json @@ -5,7 +5,8 @@ { "name": "state", "type": "QString", - "allowedValues": ["open", "closed", "opening", "closing", "intermediate"] + "allowedValues": ["open", "closed", "opening", "closing", "intermediate"], + "logged": true } ] } diff --git a/libnymea/interfaces/temperaturesensor.json b/libnymea/interfaces/temperaturesensor.json index 04fafa42..7edad7f9 100644 --- a/libnymea/interfaces/temperaturesensor.json +++ b/libnymea/interfaces/temperaturesensor.json @@ -4,7 +4,8 @@ { "name": "temperature", "type": "double", - "unit": "DegreeCelsius" + "unit": "DegreeCelsius", + "logged": true } ] } diff --git a/libnymea/interfaces/useraccesscontrol.json b/libnymea/interfaces/useraccesscontrol.json index b1059651..5d43dccf 100644 --- a/libnymea/interfaces/useraccesscontrol.json +++ b/libnymea/interfaces/useraccesscontrol.json @@ -14,7 +14,8 @@ "name": "userId", "type": "QString" } - ] + ], + "logged": true } ], "actions": [ @@ -25,8 +26,8 @@ "name": "userId", "type": "QString" } - - ] + ], + "logged": true }, { "name": "removeUser", @@ -36,7 +37,8 @@ "type": "QString" } - ] + ], + "logged": true } ] } diff --git a/libnymea/interfaces/weather.json b/libnymea/interfaces/weather.json index 5560ed43..49d82623 100644 --- a/libnymea/interfaces/weather.json +++ b/libnymea/interfaces/weather.json @@ -2,32 +2,39 @@ "states": [ { "name": "weatherDescription", - "type": "QString" + "type": "QString", + "logged": true }, { "name": "weatherCondition", "type": "QString", - "allowedValues": ["clear-day", "clear-night", "few-clouds-day", "few-clouds-night", "clouds", "overcast", "light-rain", "shower-rain", "thunderstorm", "snow", "fog"] + "allowedValues": ["clear-day", "clear-night", "few-clouds-day", "few-clouds-night", "clouds", "overcast", "light-rain", "shower-rain", "thunderstorm", "snow", "fog"], + "logged": true }, { "name": "temperature", - "type": "double" + "type": "double", + "logged": true }, { "name": "humidity", - "type": "int" + "type": "int", + "logged": true }, { "name": "pressure", - "type": "double" + "type": "double", + "logged": true }, { "name": "windSpeed", - "type": "double" + "type": "double", + "logged": true }, { "name": "windDirection", - "type": "int" + "type": "int", + "logged": true } ] } diff --git a/libnymea/interfaces/windspeedsensor.json b/libnymea/interfaces/windspeedsensor.json index 83bf0a01..62a61404 100644 --- a/libnymea/interfaces/windspeedsensor.json +++ b/libnymea/interfaces/windspeedsensor.json @@ -5,7 +5,8 @@ { "name": "windSpeed", "type": "double", - "unit": "MeterPerSecond" + "unit": "MeterPerSecond", + "logged": true } ] } diff --git a/libnymea/jsonrpc/jsonhandler.cpp b/libnymea/jsonrpc/jsonhandler.cpp index 1dc446ee..48bb5b44 100644 --- a/libnymea/jsonrpc/jsonhandler.cpp +++ b/libnymea/jsonrpc/jsonhandler.cpp @@ -29,7 +29,7 @@ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include "jsonhandler.h" - +#include "typeutils.h" #include "loggingcategories.h" #include @@ -207,6 +207,9 @@ void JsonHandler::registerObject(const QMetaObject &metaObject) typeName = QString("$ref:BasicType"); } else if (QString(metaProperty.typeName()).startsWith("QList")) { QString elementType = QString(metaProperty.typeName()).remove("QList<").remove(">"); + if (elementType == "EventTypeId" || elementType == "StateTypeId" || elementType == "ActionTypeId") { + elementType = "QUuid"; + } QVariant::Type variantType = QVariant::nameToType(elementType.toUtf8()); typeName = QVariantList() << enumValueName(variantTypeToBasicType(variantType)); } else { @@ -344,6 +347,18 @@ QVariant JsonHandler::pack(const QMetaObject &metaObject, const void *value) con foreach (const QUuid &entry, propertyValue.value>()) { list << entry; } + } else if (propertyTypeName == "QList") { + foreach (const EventTypeId &entry, propertyValue.value>()) { + list << entry; + } + } else if (propertyTypeName == "QList") { + foreach (const EventTypeId &entry, propertyValue.value>()) { + list << entry; + } + } else if (propertyTypeName == "QList") { + foreach (const EventTypeId &entry, propertyValue.value>()) { + list << entry; + } } else { Q_ASSERT_X(false, this->metaObject()->className(), QString("Unhandled list type: %1").arg(propertyTypeName).toUtf8()); qCWarning(dcJsonRpc()) << "Cannot pack property of unhandled list type" << propertyTypeName; @@ -455,7 +470,10 @@ QVariant JsonHandler::unpack(const QMetaObject &metaObject, const QVariant &valu intList.append(val.toInt()); } metaProperty.writeOnGadget(ptr, QVariant::fromValue(intList)); - } else if (metaProperty.typeName() == QStringLiteral("QList")) { + } else if (metaProperty.typeName() == QStringLiteral("QList") + || metaProperty.typeName() == QStringLiteral("QList") + || metaProperty.typeName() == QStringLiteral("QList") + || metaProperty.typeName() == QStringLiteral("QList")) { QList uuidList; foreach (const QVariant &val, variant.toList()) { uuidList.append(val.toUuid()); diff --git a/libnymea/libnymea.pro b/libnymea/libnymea.pro index 74b577ca..8db49aff 100644 --- a/libnymea/libnymea.pro +++ b/libnymea/libnymea.pro @@ -126,6 +126,8 @@ SOURCES += \ integrations/thingsetupinfo.cpp \ integrations/thingutils.cpp \ integrations/servicedata.cpp \ + integrations/statevaluefilters/statevaluefilter.cpp \ + integrations/statevaluefilters/statevaluefilteradaptive.cpp \ jsonrpc/jsoncontext.cpp \ jsonrpc/jsonhandler.cpp \ jsonrpc/jsonreply.cpp \ diff --git a/libnymea/types/actiontype.cpp b/libnymea/types/actiontype.cpp index cb303e23..fe5f3045 100644 --- a/libnymea/types/actiontype.cpp +++ b/libnymea/types/actiontype.cpp @@ -130,6 +130,26 @@ ActionTypes::ActionTypes(const QList &other) } } +bool ActionTypes::contains(const ActionTypeId &id) const +{ + foreach (const ActionType &actionType, *this) { + if (actionType.id() == id) { + return true; + } + } + return false; +} + +bool ActionTypes::contains(const QString &name) const +{ + foreach (const ActionType &actionType, *this) { + if (actionType.name() == name) { + return true; + } + } + return false; +} + QVariant ActionTypes::get(int index) const { return QVariant::fromValue(at(index)); @@ -160,8 +180,21 @@ ActionType ActionTypes::findById(const ActionTypeId &id) return ActionType(ActionTypeId()); } +ActionType &ActionTypes::operator[](const QString &name) +{ + int index = -1; + for (int i = 0; i < count(); i++) { + if (at(i).name() == name) { + index = i; + break; + } + } + return QList::operator[](index); +} + QDebug operator<<(QDebug dbg, const ActionType &actionType) { dbg.nospace().noquote() << "ActionType: " << actionType.name() << actionType.displayName() << actionType.id(); return dbg; } + diff --git a/libnymea/types/actiontype.h b/libnymea/types/actiontype.h index f5ef88f5..9a216ab2 100644 --- a/libnymea/types/actiontype.h +++ b/libnymea/types/actiontype.h @@ -84,10 +84,13 @@ class ActionTypes: public QList public: ActionTypes() = default; ActionTypes(const QList &other); + bool contains(const ActionTypeId &id) const; + bool contains(const QString &name) const; Q_INVOKABLE QVariant get(int index) const; Q_INVOKABLE void put(const QVariant &variant); ActionType findByName(const QString &name); ActionType findById(const ActionTypeId &id); + ActionType &operator[](const QString &name); }; Q_DECLARE_METATYPE(ActionTypes) diff --git a/libnymea/types/event.cpp b/libnymea/types/event.cpp index da3e687f..72360375 100644 --- a/libnymea/types/event.cpp +++ b/libnymea/types/event.cpp @@ -126,6 +126,16 @@ bool Event::isStateChangeEvent() const return m_isStateChangeEvent; } +bool Event::logged() const +{ + return m_logged; +} + +void Event::setLogged(bool logged) +{ + m_logged = logged; +} + /*! Compare this Event to the Event given by \a other. * Events are equal (returns true) if eventTypeId, deviceId and params match. */ bool Event::operator ==(const Event &other) const diff --git a/libnymea/types/event.h b/libnymea/types/event.h index 93590b89..62ee3d33 100644 --- a/libnymea/types/event.h +++ b/libnymea/types/event.h @@ -65,12 +65,16 @@ public: bool isStateChangeEvent() const; + bool logged() const; + void setLogged(bool logged); + private: EventTypeId m_eventTypeId; ThingId m_thingId; ParamList m_params; bool m_isStateChangeEvent; + bool m_logged = false; }; Q_DECLARE_METATYPE(Event) QDebug operator<<(QDebug dbg, const Event &event); diff --git a/libnymea/types/eventtype.cpp b/libnymea/types/eventtype.cpp index 9001837b..42e589bb 100644 --- a/libnymea/types/eventtype.cpp +++ b/libnymea/types/eventtype.cpp @@ -110,6 +110,16 @@ void EventType::setParamTypes(const ParamTypes ¶mTypes) m_paramTypes = paramTypes; } +bool EventType::suggestLogging() const +{ + return m_logged; +} + +void EventType::setSuggestLogging(bool logged) +{ + m_logged = logged; +} + /*! Returns true if this EventType has a valid id and name */ bool EventType::isValid() const { @@ -130,11 +140,31 @@ QStringList EventType::mandatoryTypeProperties() EventTypes::EventTypes(const QList &other) { - foreach (const EventType &at, other) { - append(at); + foreach (const EventType &et, other) { + append(et); } } +bool EventTypes::contains(const EventTypeId &id) const +{ + foreach (const EventType &eventType, *this) { + if (eventType.id() == id) { + return true; + } + } + return false; +} + +bool EventTypes::contains(const QString &name) const +{ + foreach (const EventType &eventType, *this) { + if (eventType.name() == name) { + return true; + } + } + return false; +} + QVariant EventTypes::get(int index) const { return QVariant::fromValue(at(index)); @@ -164,3 +194,15 @@ EventType EventTypes::findById(const EventTypeId &id) } return EventType(EventTypeId()); } + +EventType &EventTypes::operator[](const QString &name) +{ + int index = -1; + for (int i = 0; i < count(); i++) { + if (at(i).name() == name) { + index = i; + break; + } + } + return QList::operator[](index); +} diff --git a/libnymea/types/eventtype.h b/libnymea/types/eventtype.h index cbcee65d..237bb9bb 100644 --- a/libnymea/types/eventtype.h +++ b/libnymea/types/eventtype.h @@ -64,6 +64,9 @@ public: ParamTypes paramTypes() const; void setParamTypes(const ParamTypes ¶mTypes); + bool suggestLogging() const; + void setSuggestLogging(bool logged); + bool isValid() const; static QStringList typeProperties(); @@ -75,6 +78,7 @@ private: QString m_displayName; int m_index; QList m_paramTypes; + bool m_logged = false; }; Q_DECLARE_METATYPE(EventType) @@ -85,10 +89,13 @@ class EventTypes: public QList public: EventTypes() = default; EventTypes(const QList &other); + bool contains(const EventTypeId &id) const; + bool contains(const QString &name) const; Q_INVOKABLE QVariant get(int index) const; Q_INVOKABLE void put(const QVariant &variant); EventType findByName(const QString &name); EventType findById(const EventTypeId &id); + EventType &operator[](const QString &name); }; Q_DECLARE_METATYPE(EventTypes) diff --git a/libnymea/types/interfaceeventtype.cpp b/libnymea/types/interfaceeventtype.cpp index 1ee3f9ac..6b1017d3 100644 --- a/libnymea/types/interfaceeventtype.cpp +++ b/libnymea/types/interfaceeventtype.cpp @@ -15,6 +15,16 @@ void InterfaceEventType::setOptional(bool optional) m_optional = optional; } +bool InterfaceEventType::loggingOverride() const +{ + return m_loggingOverride; +} + +void InterfaceEventType::setLoggingOverride(bool loggingOverride) +{ + m_loggingOverride = loggingOverride; +} + InterfaceEventTypes::InterfaceEventTypes(const QList &other): QList(other) { diff --git a/libnymea/types/interfaceeventtype.h b/libnymea/types/interfaceeventtype.h index c81305b4..84eca98f 100644 --- a/libnymea/types/interfaceeventtype.h +++ b/libnymea/types/interfaceeventtype.h @@ -11,8 +11,12 @@ public: bool optional() const; void setOptional(bool optional); + bool loggingOverride() const; + void setLoggingOverride(bool loggingOverride); + private: bool m_optional = false; + bool m_loggingOverride = false; }; class InterfaceEventTypes: public QList diff --git a/libnymea/types/interfacestatetype.cpp b/libnymea/types/interfacestatetype.cpp index 97981bd1..02c72567 100644 --- a/libnymea/types/interfacestatetype.cpp +++ b/libnymea/types/interfacestatetype.cpp @@ -15,6 +15,16 @@ void InterfaceStateType::setOptional(bool optional) m_optional = optional; } +bool InterfaceStateType::loggingOverride() const +{ + return m_loggingOverride; +} + +void InterfaceStateType::setLoggingOverride(bool loggingOverride) +{ + m_loggingOverride = loggingOverride; +} + InterfaceStateTypes::InterfaceStateTypes(const QList &other): QList(other) { diff --git a/libnymea/types/interfacestatetype.h b/libnymea/types/interfacestatetype.h index 247f3b48..946ea404 100644 --- a/libnymea/types/interfacestatetype.h +++ b/libnymea/types/interfacestatetype.h @@ -11,8 +11,12 @@ public: bool optional() const; void setOptional(bool optional); + bool loggingOverride() const; + void setLoggingOverride(bool loggingOverride); + private: bool m_optional = false; + bool m_loggingOverride = false; }; class InterfaceStateTypes: public QList diff --git a/libnymea/types/state.cpp b/libnymea/types/state.cpp index 8df85f32..20cba66f 100644 --- a/libnymea/types/state.cpp +++ b/libnymea/types/state.cpp @@ -80,6 +80,16 @@ void State::setValue(const QVariant &value) m_value = value; } +Types::StateValueFilter State::filter() const +{ + return m_filter; +} + +void State::setFilter(Types::StateValueFilter filter) +{ + m_filter = filter; +} + /*! Writes the stateTypeId, the deviceId and the value of the given \a state to \a dbg. */ QDebug operator<<(QDebug dbg, const State &state) { diff --git a/libnymea/types/state.h b/libnymea/types/state.h index f6b43659..a5d77d3a 100644 --- a/libnymea/types/state.h +++ b/libnymea/types/state.h @@ -42,6 +42,7 @@ class LIBNYMEA_EXPORT State Q_GADGET Q_PROPERTY(QUuid stateTypeId READ stateTypeId) Q_PROPERTY(QVariant value READ value) + Q_PROPERTY(Types::StateValueFilter filter READ filter) public: State(); @@ -53,10 +54,14 @@ public: QVariant value() const; void setValue(const QVariant &value); + Types::StateValueFilter filter() const; + void setFilter(Types::StateValueFilter filter); + private: StateTypeId m_stateTypeId; ThingId m_thingId; QVariant m_value; + Types::StateValueFilter m_filter = Types::StateValueFilterNone; }; Q_DECLARE_METATYPE(State) diff --git a/libnymea/types/statetype.cpp b/libnymea/types/statetype.cpp index 2550380b..997e7592 100644 --- a/libnymea/types/statetype.cpp +++ b/libnymea/types/statetype.cpp @@ -208,18 +208,24 @@ void StateType::setCached(bool cached) m_cached = cached; } -/*! Returns a list of all valid properties a DeviceClass definition can have. */ -QStringList StateType::typeProperties() +bool StateType::suggestLogging() const { - return QStringList() << "id" << "name" << "displayName" << "displayNameEvent" << "type" << "defaultValue" - << "cached" << "unit" << "minValue" << "maxValue" << "possibleValues" << "writable" - << "displayNameAction" << "ioType"; + return m_logged; } -/*! Returns a list of mandatory properties a DeviceClass definition must have. */ -QStringList StateType::mandatoryTypeProperties() +void StateType::setSuggestLogging(bool logged) { - return QStringList() << "id" << "name" << "displayName" << "displayNameEvent" << "type" << "defaultValue"; + m_logged = logged; +} + +Types::StateValueFilter StateType::filter() const +{ + return m_filter; +} + +void StateType::setFilter(Types::StateValueFilter filter) +{ + m_filter = filter; } /*! Returns true if this state type has an ID, a type and a name set. */ @@ -245,6 +251,16 @@ bool StateTypes::contains(const StateTypeId &stateTypeId) return false; } +bool StateTypes::contains(const QString &name) +{ + foreach (const StateType &stateType, *this) { + if (stateType.name() == name) { + return true; + } + } + return false; +} + QVariant StateTypes::get(int index) const { return QVariant::fromValue(at(index)); @@ -274,3 +290,15 @@ StateType StateTypes::findById(const StateTypeId &id) } return StateType(StateTypeId()); } + +StateType &StateTypes::operator[](const QString &name) +{ + int index = -1; + for (int i = 0; i < count(); i++) { + if (at(i).name() == name) { + index = i; + break; + } + } + return QList::operator[](index); +} diff --git a/libnymea/types/statetype.h b/libnymea/types/statetype.h index 1532a434..aff27735 100644 --- a/libnymea/types/statetype.h +++ b/libnymea/types/statetype.h @@ -93,8 +93,11 @@ public: bool cached() const; void setCached(bool cached); - static QStringList typeProperties(); - static QStringList mandatoryTypeProperties(); + bool suggestLogging() const; + void setSuggestLogging(bool logged); + + Types::StateValueFilter filter() const; + void setFilter(Types::StateValueFilter filter); bool isValid() const; @@ -112,6 +115,8 @@ private: Types::IOType m_ioType = Types::IOTypeNone; bool m_writable = false; bool m_cached = true; + bool m_logged = false; + Types::StateValueFilter m_filter = Types::StateValueFilterNone; }; Q_DECLARE_METATYPE(StateType) @@ -123,10 +128,12 @@ public: StateTypes() = default; StateTypes(const QList &other); bool contains(const StateTypeId &stateTypeId); + bool contains(const QString &name); Q_INVOKABLE QVariant get(int index) const; Q_INVOKABLE void put(const QVariant &variant); StateType findByName(const QString &name); StateType findById(const StateTypeId &id); + StateType &operator[](const QString &name); }; Q_DECLARE_METATYPE(StateTypes) diff --git a/libnymea/typeutils.h b/libnymea/typeutils.h index 39f914b7..eaa8cddb 100644 --- a/libnymea/typeutils.h +++ b/libnymea/typeutils.h @@ -173,6 +173,12 @@ public: IOTypeAnalogOutput }; Q_ENUM(IOType) + + enum StateValueFilter { + StateValueFilterNone, + StateValueFilterAdaptive + }; + Q_ENUM(StateValueFilter) }; Q_DECLARE_METATYPE(Types::InputType) diff --git a/nymea.pro b/nymea.pro index cecb7ecd..fcf3eea8 100644 --- a/nymea.pro +++ b/nymea.pro @@ -5,7 +5,7 @@ NYMEA_VERSION_STRING=$$system('dpkg-parsechangelog | sed -n -e "s/^Version: //p" # define protocol versions JSON_PROTOCOL_VERSION_MAJOR=5 -JSON_PROTOCOL_VERSION_MINOR=3 +JSON_PROTOCOL_VERSION_MINOR=4 JSON_PROTOCOL_VERSION="$${JSON_PROTOCOL_VERSION_MAJOR}.$${JSON_PROTOCOL_VERSION_MINOR}" LIBNYMEA_API_VERSION_MAJOR=7 LIBNYMEA_API_VERSION_MINOR=0 diff --git a/plugins/mock/extern-plugininfo.h b/plugins/mock/extern-plugininfo.h index b7ba912a..e31016e9 100644 --- a/plugins/mock/extern-plugininfo.h +++ b/plugins/mock/extern-plugininfo.h @@ -1,5 +1,10 @@ /* This file is generated by the nymea build system. Any changes to this file will * - * be lost. If you want to change this file, edit the plugin's json file. */ + * be lost. If you want to change this file, edit the plugin's json file. * + * * + * NOTE: This file can be included only once per plugin. If you need to access * + * definitions from this file in multiple source files, use * + * #include extern-plugininfo.h * + * instead and re-run qmake. */ #ifndef EXTERNPLUGININFO_H #define EXTERNPLUGININFO_H diff --git a/plugins/mock/httpdaemon.cpp b/plugins/mock/httpdaemon.cpp index 4d684002..5d5ab4c9 100644 --- a/plugins/mock/httpdaemon.cpp +++ b/plugins/mock/httpdaemon.cpp @@ -104,7 +104,7 @@ void HttpDaemon::readClient() } else if (stateTypeId == mockDoubleStateTypeId) { stateValue.convert(QVariant::Double); } - qCDebug(dcMock()) << "Set state value" << stateValue; + qCDebug(dcMock()) << "Setting state value" << stateValue; emit setState(stateTypeId, stateValue); } else if (url.path() == "/generateevent") { emit triggerEvent(EventTypeId(query.queryItemValue("eventtypeid"))); diff --git a/plugins/mock/integrationpluginmock.json b/plugins/mock/integrationpluginmock.json index c3afe9e9..527672fb 100644 --- a/plugins/mock/integrationpluginmock.json +++ b/plugins/mock/integrationpluginmock.json @@ -82,7 +82,8 @@ "displayName": "Dummy int state", "displayNameEvent": "Dummy int state changed", "defaultValue": 10, - "type": "int" + "type": "int", + "suggestLogging": true }, { "id": "9dd6a97c-dfd1-43dc-acbd-367932742310", @@ -152,7 +153,8 @@ "minValue": 0, "maxValue": 100, "defaultValue": 50, - "writable": true + "writable": true, + "filter": "adaptive" }, { "id": "ebc41327-53d5-40c2-8e7b-1164a8ff359e", @@ -307,7 +309,8 @@ "displayName": "Dummy int state", "displayNameEvent": "Dummy int state changed", "defaultValue": 10, - "type": "int" + "type": "int", + "suggestLogging": true }, { "id": "978b0ba5-d008-41bd-b63d-a3bd23cb6469", diff --git a/plugins/mock/plugininfo.h b/plugins/mock/plugininfo.h index 51ee9c17..e7b3de50 100644 --- a/plugins/mock/plugininfo.h +++ b/plugins/mock/plugininfo.h @@ -1,5 +1,10 @@ /* This file is generated by the nymea build system. Any changes to this file will * - * be lost. If you want to change this file, edit the plugin's json file. */ + * be lost. If you want to change this file, edit the plugin's json file. * + * * + * NOTE: This file can be included only once per plugin. If you need to access * + * definitions from this file in multiple source files, use * + * #include extern-plugininfo.h * + * instead and re-run qmake. */ #ifndef PLUGININFO_H #define PLUGININFO_H diff --git a/tests/auto/api.json b/tests/auto/api.json index c6cdbf91..1c5f22d6 100644 --- a/tests/auto/api.json +++ b/tests/auto/api.json @@ -1,4 +1,4 @@ -5.3 +5.4 { "enums": { "BasicType": [ @@ -244,6 +244,10 @@ "StateOperatorAnd", "StateOperatorOr" ], + "StateValueFilter": [ + "StateValueFilterNone", + "StateValueFilterAdaptive" + ], "TagError": [ "TagErrorNoError", "TagErrorThingNotFound", @@ -1215,6 +1219,17 @@ "thingError": "$ref:ThingError" } }, + "Integrations.SetEventLogging": { + "description": "Enable/disable logging for the given event type on the given thing.", + "params": { + "enabled": "Bool", + "eventTypeId": "Uuid", + "thingId": "Uuid" + }, + "returns": { + "thingError": "$ref:ThingError" + } + }, "Integrations.SetPluginConfiguration": { "description": "Set a plugin's params.", "params": { @@ -1225,6 +1240,17 @@ "thingError": "$ref:ThingError" } }, + "Integrations.SetStateFilter": { + "description": "Set the filter for the given state on the given thing.", + "params": { + "filter": "$ref:StateValueFilter", + "stateTypeId": "Uuid", + "thingId": "Uuid" + }, + "returns": { + "thingError": "$ref:ThingError" + } + }, "Integrations.SetThingSettings": { "description": "Change the settings of a thing.", "params": { @@ -2521,6 +2547,9 @@ "o:settings": "$ref:ParamList", "r:deviceClassId": "Uuid", "r:id": "Uuid", + "r:o:loggedEventTypeIds": [ + "Uuid" + ], "r:o:parentId": "Uuid", "r:o:setupDisplayMessage": "String", "r:params": "$ref:ParamList", @@ -2774,6 +2803,7 @@ "sslEnabled": "Bool" }, "State": { + "r:filter": "$ref:StateValueFilter", "r:stateTypeId": "Uuid", "r:value": "Variant" }, @@ -2833,6 +2863,9 @@ "o:name": "String", "o:settings": "$ref:ParamList", "r:id": "Uuid", + "r:o:loggedEventTypeIds": [ + "Uuid" + ], "r:o:parentId": "Uuid", "r:o:setupDisplayMessage": "String", "r:params": "$ref:ParamList", diff --git a/tests/auto/jsonrpc/testjsonrpc.cpp b/tests/auto/jsonrpc/testjsonrpc.cpp index 4f7280bd..ab46d928 100644 --- a/tests/auto/jsonrpc/testjsonrpc.cpp +++ b/tests/auto/jsonrpc/testjsonrpc.cpp @@ -852,22 +852,6 @@ void TestJSONRPC::ruleActiveChangedNotifications() clientSpy.wait(); - waitForDBSync(); - - // Make sure the logg notification contains all the stuff we expect - QVariantList logEntryAddedVariants = checkNotifications(clientSpy, "Logging.LogEntryAdded"); - QVERIFY2(!logEntryAddedVariants.isEmpty(), "Did not get Logging.LogEntryAdded notification."); - bool found = false; - foreach (const QVariant &loggEntryAddedVariant, logEntryAddedVariants) { - if (loggEntryAddedVariant.toMap().value("params").toMap().value("logEntry").toMap().value("typeId").toUuid() == mockIntStateTypeId) { - found = true; - QCOMPARE(loggEntryAddedVariant.toMap().value("params").toMap().value("logEntry").toMap().value("source").toString(), QString("LoggingSourceStates")); - QCOMPARE(loggEntryAddedVariant.toMap().value("params").toMap().value("logEntry").toMap().value("value").toInt(), 20); - break; - } - } - QVERIFY2(found, "LogEntryAdded notification not received"); - spy.clear(); clientSpy.clear(); // set the rule inactive @@ -889,22 +873,6 @@ void TestJSONRPC::ruleActiveChangedNotifications() clientSpy.wait(); } - // Make sure the logg notification contains all the stuff we expect - logEntryAddedVariants = checkNotifications(clientSpy, "Logging.LogEntryAdded"); - QVERIFY2(!logEntryAddedVariants.isEmpty(), "Did not get Logging.LogEntryAdded notification."); - found = false; - foreach (const QVariant &logEntryAddedVariant, logEntryAddedVariants) { - qCDebug(dcTests()) << "Checking log entry" << mockIntStateTypeId << qUtf8Printable(QJsonDocument::fromVariant(logEntryAddedVariant).toJson()); - if (logEntryAddedVariant.toMap().value("params").toMap().value("logEntry").toMap().value("typeId").toUuid() == mockIntStateTypeId) { - found = true; - QCOMPARE(logEntryAddedVariant.toMap().value("params").toMap().value("logEntry").toMap().value("source").toString(), QString("LoggingSourceStates")); - QCOMPARE(logEntryAddedVariant.toMap().value("params").toMap().value("logEntry").toMap().value("value").toInt(), 42); - break; - } - } - QVERIFY2(found, "LogEntryAdded notification not received"); - - if (clientSpy.count() == 0) clientSpy.wait(); notificationVariant = checkNotification(clientSpy, "Rules.RuleActiveChanged"); verifyRuleError(response); @@ -960,26 +928,10 @@ void TestJSONRPC::stateChangeEmitsNotifications() // Devices.StateChanged // Devices.EventTriggered // Events.EventTriggered <-- deprecated - // Logging.LogEntryAdded - while (clientSpy.count() < 4) { + while (clientSpy.count() < 3) { clientSpy.wait(); } - // Make sure the logg notification contains all the stuff we expect - QVariantList logEntryAddedVariants = checkNotifications(clientSpy, "Logging.LogEntryAdded"); - QVERIFY2(!logEntryAddedVariants.isEmpty(), "Did not get Logging.LogEntryAdded notification."); - found = false; - foreach (const QVariant &loggEntryAddedVariant, logEntryAddedVariants) { - if (loggEntryAddedVariant.toMap().value("params").toMap().value("logEntry").toMap().value("typeId").toUuid() == stateTypeId) { - found = true; - QCOMPARE(loggEntryAddedVariant.toMap().value("params").toMap().value("logEntry").toMap().value("source").toString(), QString("LoggingSourceStates")); - QCOMPARE(loggEntryAddedVariant.toMap().value("params").toMap().value("logEntry").toMap().value("value").toInt(), newVal); - break; - } - } - - QVERIFY2(found, "Could not find the corresponding Logging.LogEntryAdded notification"); - // Make sure the notification contains all the stuff we expect QVariantList eventTriggeredVariants = checkNotifications(clientSpy, "Events.EventTriggered"); QVERIFY2(!eventTriggeredVariants.isEmpty(), "Did not get Events.EventTriggered notification."); diff --git a/tests/auto/logging/testlogging.cpp b/tests/auto/logging/testlogging.cpp index 10045f6d..9c9ac62a 100644 --- a/tests/auto/logging/testlogging.cpp +++ b/tests/auto/logging/testlogging.cpp @@ -71,6 +71,7 @@ private slots: void invalidFilter_data(); void invalidFilter(); + void eventLogs_data(); void eventLogs(); void actionLog(); @@ -198,6 +199,7 @@ void TestLogging::systemLogs() qWarning() << "Restarting server"; restartServer(); qWarning() << "Restart done"; + waitForDBSync(); // there should be 2 log entries, one for shutdown, one for startup (from server restart) response = injectAndWait("Logging.GetLogEntries", params); @@ -261,24 +263,53 @@ void TestLogging::invalidFilter() qDebug() << response.toMap().value("error").toString(); } +void TestLogging::eventLogs_data() +{ + QTest::addColumn("stateTypeId"); + QTest::addColumn("initValue"); + QTest::addColumn("newValue"); + QTest::addColumn("expectLogEntry"); + + QTest::newRow("logged event") << mockConnectedStateTypeId << QVariant(false) << QVariant(true) << true; + QTest::newRow("not logged event") << mockSignalStrengthStateTypeId << QVariant(10) << QVariant(20) << false; +} void TestLogging::eventLogs() { + QFETCH(StateTypeId, stateTypeId); + QFETCH(QVariant, initValue); + QFETCH(QVariant, newValue); + QFETCH(bool, expectLogEntry); + QList devices = NymeaCore::instance()->thingManager()->findConfiguredThings(mockThingClassId); QVERIFY2(devices.count() > 0, "There needs to be at least one configured Mock Device for this test"); Thing *device = devices.first(); - enableNotifications({"Events", "Logging"}); - // Setup connection to mock client QNetworkAccessManager nam; + int port = device->paramValue(mockThingHttpportParamTypeId).toInt(); + + // init state in mock device + QNetworkRequest request(QUrl(QString("http://localhost:%1/setstate?%2=%3").arg(port).arg(stateTypeId.toString()).arg(initValue.toString()))); + QNetworkReply *reply = nam.get(request); + { + QSignalSpy finishedSpy(reply, &QNetworkReply::finished); + finishedSpy.wait(); + } + + // Now snoop in for the events + clearLoggingDatabase(); + enableNotifications({"Events", "Logging"}); QSignalSpy clientSpy(m_mockTcpServer, SIGNAL(outgoingData(QUuid,QByteArray))); - // trigger event in mock device - int port = device->paramValue(mockThingHttpportParamTypeId).toInt(); - QNetworkRequest request(QUrl(QString("http://localhost:%1/generateevent?eventtypeid=%2").arg(port).arg(mockEvent1EventTypeId.toString()))); - QNetworkReply *reply = nam.get(request); + // trigger state change in mock device + request = QNetworkRequest(QUrl(QString("http://localhost:%1/setstate?%2=%3").arg(port).arg(stateTypeId.toString()).arg(newValue.toString()))); + reply = nam.get(request); + { + QSignalSpy finishedSpy(reply, &QNetworkReply::finished); + finishedSpy.wait(); + } // Lets wait for the notification QTest::qWait(200); @@ -286,41 +317,42 @@ void TestLogging::eventLogs() reply->deleteLater(); // Make sure the logg notification contains all the stuff we expect - QVariantList loggEntryAddedVariants = checkNotifications(clientSpy, "Logging.LogEntryAdded"); - QVERIFY2(!loggEntryAddedVariants.isEmpty(), "Did not get Logging.LogEntryAdded notification."); - qDebug() << "got" << loggEntryAddedVariants.count() << "Logging.LogEntryAdded notifications"; + QVariantList logEntryAddedVariants = checkNotifications(clientSpy, "Logging.LogEntryAdded"); + qDebug() << "got" << logEntryAddedVariants.count() << "Logging.LogEntryAdded notifications"; bool found = false; - qDebug() << "got" << loggEntryAddedVariants.count() << "Logging.LogEntryAdded"; - foreach (const QVariant &loggEntryAddedVariant, loggEntryAddedVariants) { - QVariantMap logEntry = loggEntryAddedVariant.toMap().value("params").toMap().value("logEntry").toMap(); + qDebug() << "got" << logEntryAddedVariants.count() << "Logging.LogEntryAdded"; + foreach (const QVariant &logEntryAddedVariant, logEntryAddedVariants) { + QVariantMap logEntry = logEntryAddedVariant.toMap().value("params").toMap().value("logEntry").toMap(); if (logEntry.value("thingId").toUuid() == device->id()) { found = true; // Make sure the notification contains all the stuff we expect - QCOMPARE(logEntry.value("typeId").toUuid().toString(), mockEvent1EventTypeId.toString()); + QCOMPARE(logEntry.value("typeId").toUuid().toString(), stateTypeId.toString()); QCOMPARE(logEntry.value("eventType").toString(), enumValueName(Logging::LoggingEventTypeTrigger)); - QCOMPARE(logEntry.value("source").toString(), enumValueName(Logging::LoggingSourceEvents)); + QCOMPARE(logEntry.value("source").toString(), enumValueName(Logging::LoggingSourceStates)); QCOMPARE(logEntry.value("loggingLevel").toString(), enumValueName(Logging::LoggingLevelInfo)); break; } } - if (!found) - qDebug() << QJsonDocument::fromVariant(loggEntryAddedVariants).toJson(); - QVERIFY2(found, "Could not find the corresponding Logging.LogEntryAdded notification"); + QVERIFY2(found == expectLogEntry, "Could not find the corresponding Logging.LogEntryAdded notification"); - // get this logentry with filter - QVariantMap params; - params.insert("thingIds", QVariantList() << device->id()); - params.insert("loggingSources", QVariantList() << enumValueName(Logging::LoggingSourceEvents)); - params.insert("eventTypes", QVariantList() << enumValueName(Logging::LoggingEventTypeTrigger)); - params.insert("typeIds", QVariantList() << mockEvent1EventTypeId); + if (expectLogEntry) { + // get this logentry with filter + QVariantMap params; + params.insert("thingIds", QVariantList() << device->id()); + params.insert("loggingSources", QVariantList() << enumValueName(Logging::LoggingSourceStates)); + params.insert("eventTypes", QVariantList() << enumValueName(Logging::LoggingEventTypeTrigger)); + params.insert("typeIds", QVariantList() << stateTypeId); - QVariant response = injectAndWait("Logging.GetLogEntries", params); - verifyLoggingError(response); + QVariant response = injectAndWait("Logging.GetLogEntries", params); + verifyLoggingError(response); + + QVariantList logEntries = response.toMap().value("params").toMap().value("logEntries").toList(); + qCDebug(dcTests()) << qUtf8Printable(QJsonDocument::fromVariant(logEntries).toJson()); + QCOMPARE(logEntries.count(), 1); + } - QVariantList logEntries = response.toMap().value("params").toMap().value("logEntries").toList(); - QVERIFY(logEntries.count() == 1); // disable notifications QCOMPARE(disableNotifications(), true); @@ -629,7 +661,7 @@ void TestLogging::testHouseKeeping() // Trigger something that creates a logging entry QNetworkAccessManager nam; QSignalSpy spy(&nam, SIGNAL(finished(QNetworkReply*))); - QNetworkRequest request(QUrl(QString("http://localhost:%1/setstate?%2=%3").arg(6667).arg(mockIntStateTypeId.toString()).arg(4321))); + QNetworkRequest request(QUrl(QString("http://localhost:%1/setstate?%2=%3").arg(6667).arg(mockConnectedStateTypeId.toString()).arg(false))); QNetworkReply *reply = nam.get(request); connect(reply, SIGNAL(finished()), reply, SLOT(deleteLater())); spy.wait();