Merge PR #337: Support for configuring logging and filtering states/events
This commit is contained in:
commit
01e9ad8916
@ -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<EventTypeId> 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<EventTypeId> 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<EventTypeId> 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<EventTypeId> 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<EventTypeId> 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;
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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"));
|
||||
|
||||
@ -62,6 +62,7 @@ DeviceHandler::DeviceHandler(QObject *parent) :
|
||||
registerEnum<Types::Unit>();
|
||||
registerEnum<Types::InputType>();
|
||||
registerEnum<Types::IOType>();
|
||||
registerEnum<Types::StateValueFilter>();
|
||||
registerEnum<RuleEngine::RemovePolicy>();
|
||||
registerEnum<BrowserItem::BrowserIcon>();
|
||||
registerEnum<MediaBrowserItem::MediaBrowserIcon>();
|
||||
@ -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"));
|
||||
|
||||
@ -61,6 +61,7 @@ IntegrationsHandler::IntegrationsHandler(ThingManager *thingManager, QObject *pa
|
||||
registerEnum<Types::Unit>();
|
||||
registerEnum<Types::InputType>();
|
||||
registerEnum<Types::IOType>();
|
||||
registerEnum<Types::StateValueFilter>();
|
||||
registerEnum<RuleEngine::RemovePolicy>();
|
||||
registerEnum<BrowserItem::BrowserIcon>();
|
||||
registerEnum<MediaBrowserItem::MediaBrowserIcon>();
|
||||
@ -241,6 +242,22 @@ IntegrationsHandler::IntegrationsHandler(ThingManager *thingManager, QObject *pa
|
||||
returns.insert("thingError", enumRef<Thing::ThingError>());
|
||||
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<Thing::ThingError>());
|
||||
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<Types::StateValueFilter>());
|
||||
returns.insert("thingError", enumRef<Thing::ThingError>());
|
||||
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>();
|
||||
Types::StateValueFilter filter = static_cast<Types::StateValueFilter>(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()));
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -34,6 +34,8 @@
|
||||
#include "logging.h"
|
||||
#include "logvaluetool.h"
|
||||
|
||||
#include "integrations/thingmanager.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QSqlDatabase>
|
||||
#include <QSqlDriver>
|
||||
@ -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<LogEntry> 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());
|
||||
|
||||
|
||||
@ -38,6 +38,7 @@
|
||||
#include "types/browseritemaction.h"
|
||||
#include "types/browseraction.h"
|
||||
#include "ruleengine/rule.h"
|
||||
#include "integrations/thingmanager.h"
|
||||
|
||||
#include <QObject>
|
||||
#include <QSqlDatabase>
|
||||
@ -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<QString, QList<DatabaseJob*>> m_flaggedJobs;
|
||||
|
||||
@ -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<RuleAction> 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<RuleAction> actions;
|
||||
|
||||
@ -84,7 +84,6 @@ public:
|
||||
QPair<Thing::ThingError, QList<RuleId> >removeConfiguredThing(const ThingId &thingId, const QHash<RuleId, RuleEngine::RemovePolicy> &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);
|
||||
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -318,7 +318,11 @@ void PluginMetadata::parse(const QJsonObject &jsonObject)
|
||||
QJsonObject st = stateTypesJson.toObject();
|
||||
bool writableState = false;
|
||||
|
||||
QPair<QStringList, QStringList> 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<QStringList, QStringList> 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>() << 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<bool, QList<ParamType> > 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);
|
||||
}
|
||||
}
|
||||
|
||||
45
libnymea/integrations/statevaluefilters/statevaluefilter.cpp
Normal file
45
libnymea/integrations/statevaluefilters/statevaluefilter.cpp
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* 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()
|
||||
{
|
||||
|
||||
}
|
||||
51
libnymea/integrations/statevaluefilters/statevaluefilter.h
Normal file
51
libnymea/integrations/statevaluefilters/statevaluefilter.h
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* 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 <QVariant>
|
||||
#include <QLoggingCategory>
|
||||
|
||||
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
|
||||
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* 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 <qmath.h>
|
||||
|
||||
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 << ")";
|
||||
}
|
||||
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* 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<double> 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
|
||||
@ -133,6 +133,7 @@
|
||||
#include "thing.h"
|
||||
#include "types/event.h"
|
||||
#include "loggingcategories.h"
|
||||
#include "statevaluefilters/statevaluefilteradaptive.h"
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
@ -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<EventTypeId> 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<EventTypeId> 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<Thing*> &other)
|
||||
{
|
||||
foreach (Thing* thing, other) {
|
||||
|
||||
@ -45,6 +45,7 @@
|
||||
#include <QVariant>
|
||||
|
||||
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<EventTypeId> loggedEventTypeIds READ loggedEventTypeIds USER true)
|
||||
|
||||
public:
|
||||
enum ThingError {
|
||||
@ -133,6 +135,8 @@ public:
|
||||
|
||||
Q_INVOKABLE State state(const StateTypeId &stateTypeId) const;
|
||||
|
||||
QList<EventTypeId> 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<EventTypeId> 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<EventTypeId> m_loggedEventTypeIds;
|
||||
QHash<StateTypeId, StateValueFilter*> m_stateValueFilters;
|
||||
};
|
||||
|
||||
QDebug operator<<(QDebug dbg, Thing *device);
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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<Types::Unit>();
|
||||
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;
|
||||
|
||||
@ -2,7 +2,8 @@
|
||||
"states": [
|
||||
{
|
||||
"name": "batteryCritical",
|
||||
"type": "bool"
|
||||
"type": "bool",
|
||||
"logged": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@ -2,7 +2,8 @@
|
||||
"description": "The base for all buttons that emit a pressed event.",
|
||||
"events": [
|
||||
{
|
||||
"name": "pressed"
|
||||
"name": "pressed",
|
||||
"logged": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@ -4,7 +4,8 @@
|
||||
"states": [
|
||||
{
|
||||
"name": "closed",
|
||||
"type": "bool"
|
||||
"type": "bool",
|
||||
"logged": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@ -5,7 +5,8 @@
|
||||
{
|
||||
"name": "co2",
|
||||
"type": "double",
|
||||
"unit": "PartsPerMillion"
|
||||
"unit": "PartsPerMillion",
|
||||
"logged": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@ -4,7 +4,8 @@
|
||||
{
|
||||
"name": "conductivity",
|
||||
"type": "double",
|
||||
"unit": "MicroSiemensPerCentimeter"
|
||||
"unit": "MicroSiemensPerCentimeter",
|
||||
"logged": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@ -3,7 +3,8 @@
|
||||
{
|
||||
"name": "connected",
|
||||
"type": "bool",
|
||||
"defaultValue": false
|
||||
"defaultValue": false,
|
||||
"logged": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@ -4,7 +4,8 @@
|
||||
"states": [
|
||||
{
|
||||
"name": "daylight",
|
||||
"type": "bool"
|
||||
"type": "bool",
|
||||
"logged": true
|
||||
},
|
||||
{
|
||||
"name": "sunriseTime",
|
||||
|
||||
@ -2,7 +2,8 @@
|
||||
"description": "An interface for doorbells. Emits \"doorbellPressed\" when the doorbell is pressed.",
|
||||
"events": [
|
||||
{
|
||||
"name": "doorbellPressed"
|
||||
"name": "doorbellPressed",
|
||||
"logged": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@ -4,7 +4,8 @@
|
||||
"states": [
|
||||
{
|
||||
"name": "moving",
|
||||
"type": "bool"
|
||||
"type": "bool",
|
||||
"logged": true
|
||||
},
|
||||
{
|
||||
"name": "percentage",
|
||||
|
||||
@ -5,7 +5,8 @@
|
||||
{
|
||||
"name": "currentPower",
|
||||
"type": "double",
|
||||
"unit": "Watt"
|
||||
"unit": "Watt",
|
||||
"logged": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@ -4,7 +4,9 @@
|
||||
"states": [
|
||||
{
|
||||
"name": "currentPower",
|
||||
"type": "double"
|
||||
"type": "double",
|
||||
"unit": "Watt",
|
||||
"logged": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@ -24,7 +24,8 @@
|
||||
"PinkyRight"
|
||||
]
|
||||
}
|
||||
]
|
||||
],
|
||||
"logged": true
|
||||
}
|
||||
],
|
||||
"actions": [
|
||||
|
||||
@ -5,7 +5,8 @@
|
||||
{
|
||||
"name": "state",
|
||||
"type": "QString",
|
||||
"allowedValues": ["open", "closed", "opening", "closing"]
|
||||
"allowedValues": ["open", "closed", "opening", "closing"],
|
||||
"logged": true
|
||||
},
|
||||
{
|
||||
"name": "intermediatePosition",
|
||||
|
||||
@ -5,7 +5,8 @@
|
||||
"name": "humidity",
|
||||
"type": "double",
|
||||
"minValue": 0,
|
||||
"maxValue": 100
|
||||
"maxValue": 100,
|
||||
"logged": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
{
|
||||
"events": [
|
||||
{
|
||||
"name": "triggered"
|
||||
"name": "triggered",
|
||||
"logged": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@ -4,7 +4,8 @@
|
||||
{
|
||||
"name": "lightIntensity",
|
||||
"type": "double",
|
||||
"unit": "Lux"
|
||||
"unit": "Lux",
|
||||
"logged": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@ -3,7 +3,8 @@
|
||||
"extends": "button",
|
||||
"events": [
|
||||
{
|
||||
"name": "longPressed"
|
||||
"name": "longPressed",
|
||||
"logged": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@ -9,7 +9,8 @@
|
||||
"name": "buttonName",
|
||||
"type": "QString"
|
||||
}
|
||||
]
|
||||
],
|
||||
"logged": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@ -3,7 +3,8 @@
|
||||
"states": [
|
||||
{
|
||||
"name": "moisture",
|
||||
"type": "double"
|
||||
"type": "double",
|
||||
"logged": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@ -9,7 +9,8 @@
|
||||
"name": "buttonName",
|
||||
"type": "QString"
|
||||
}
|
||||
]
|
||||
],
|
||||
"logged": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@ -5,7 +5,8 @@
|
||||
{
|
||||
"name": "noise",
|
||||
"type": "double",
|
||||
"unit": "Dezibel"
|
||||
"unit": "Dezibel",
|
||||
"logged": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@ -11,8 +11,8 @@
|
||||
"name": "body",
|
||||
"type": "QString"
|
||||
}
|
||||
]
|
||||
],
|
||||
"logged": true
|
||||
}
|
||||
|
||||
]
|
||||
}
|
||||
|
||||
@ -3,7 +3,8 @@
|
||||
{
|
||||
"name": "power",
|
||||
"type": "bool",
|
||||
"writable": true
|
||||
"writable": true,
|
||||
"logged": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@ -4,7 +4,8 @@
|
||||
"states": [
|
||||
{
|
||||
"name": "isPresent",
|
||||
"type": "bool"
|
||||
"type": "bool",
|
||||
"logged": true
|
||||
},
|
||||
{
|
||||
"name": "lastSeenTime",
|
||||
|
||||
@ -4,7 +4,8 @@
|
||||
{
|
||||
"name": "pressure",
|
||||
"type": "double",
|
||||
"unit": "MilliBar"
|
||||
"unit": "MilliBar",
|
||||
"logged": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@ -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": [
|
||||
|
||||
@ -5,7 +5,8 @@
|
||||
{
|
||||
"name": "state",
|
||||
"type": "QString",
|
||||
"allowedValues": ["open", "closed", "opening", "closing", "intermediate"]
|
||||
"allowedValues": ["open", "closed", "opening", "closing", "intermediate"],
|
||||
"logged": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@ -4,7 +4,8 @@
|
||||
{
|
||||
"name": "temperature",
|
||||
"type": "double",
|
||||
"unit": "DegreeCelsius"
|
||||
"unit": "DegreeCelsius",
|
||||
"logged": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@ -5,7 +5,8 @@
|
||||
{
|
||||
"name": "windSpeed",
|
||||
"type": "double",
|
||||
"unit": "MeterPerSecond"
|
||||
"unit": "MeterPerSecond",
|
||||
"logged": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@ -29,7 +29,7 @@
|
||||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
#include "jsonhandler.h"
|
||||
|
||||
#include "typeutils.h"
|
||||
#include "loggingcategories.h"
|
||||
|
||||
#include <QDebug>
|
||||
@ -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<QList<QUuid>>()) {
|
||||
list << entry;
|
||||
}
|
||||
} else if (propertyTypeName == "QList<EventTypeId>") {
|
||||
foreach (const EventTypeId &entry, propertyValue.value<QList<EventTypeId>>()) {
|
||||
list << entry;
|
||||
}
|
||||
} else if (propertyTypeName == "QList<StateTypeId>") {
|
||||
foreach (const EventTypeId &entry, propertyValue.value<QList<EventTypeId>>()) {
|
||||
list << entry;
|
||||
}
|
||||
} else if (propertyTypeName == "QList<ActionTypeId>") {
|
||||
foreach (const EventTypeId &entry, propertyValue.value<QList<EventTypeId>>()) {
|
||||
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<QUuid>")) {
|
||||
} else if (metaProperty.typeName() == QStringLiteral("QList<QUuid>")
|
||||
|| metaProperty.typeName() == QStringLiteral("QList<EventTypeId>")
|
||||
|| metaProperty.typeName() == QStringLiteral("QList<StateTypeId>")
|
||||
|| metaProperty.typeName() == QStringLiteral("QList<ActionTypeId>")) {
|
||||
QList<QUuid> uuidList;
|
||||
foreach (const QVariant &val, variant.toList()) {
|
||||
uuidList.append(val.toUuid());
|
||||
|
||||
@ -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 \
|
||||
|
||||
@ -130,6 +130,26 @@ ActionTypes::ActionTypes(const QList<ActionType> &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;
|
||||
}
|
||||
|
||||
|
||||
@ -84,10 +84,13 @@ class ActionTypes: public QList<ActionType>
|
||||
public:
|
||||
ActionTypes() = default;
|
||||
ActionTypes(const QList<ActionType> &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)
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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<EventType> &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);
|
||||
}
|
||||
|
||||
@ -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<ParamType> m_paramTypes;
|
||||
bool m_logged = false;
|
||||
};
|
||||
Q_DECLARE_METATYPE(EventType)
|
||||
|
||||
@ -85,10 +89,13 @@ class EventTypes: public QList<EventType>
|
||||
public:
|
||||
EventTypes() = default;
|
||||
EventTypes(const QList<EventType> &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)
|
||||
|
||||
|
||||
@ -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<InterfaceEventType> &other):
|
||||
QList<InterfaceEventType>(other)
|
||||
{
|
||||
|
||||
@ -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<InterfaceEventType>
|
||||
|
||||
@ -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<InterfaceStateType> &other):
|
||||
QList<InterfaceStateType>(other)
|
||||
{
|
||||
|
||||
@ -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<InterfaceStateType>
|
||||
|
||||
@ -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)
|
||||
{
|
||||
|
||||
@ -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)
|
||||
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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<StateType> &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)
|
||||
|
||||
|
||||
@ -173,6 +173,12 @@ public:
|
||||
IOTypeAnalogOutput
|
||||
};
|
||||
Q_ENUM(IOType)
|
||||
|
||||
enum StateValueFilter {
|
||||
StateValueFilterNone,
|
||||
StateValueFilterAdaptive
|
||||
};
|
||||
Q_ENUM(StateValueFilter)
|
||||
};
|
||||
|
||||
Q_DECLARE_METATYPE(Types::InputType)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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")));
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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.");
|
||||
|
||||
@ -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>("stateTypeId");
|
||||
QTest::addColumn<QVariant>("initValue");
|
||||
QTest::addColumn<QVariant>("newValue");
|
||||
QTest::addColumn<bool>("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<Thing*> 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();
|
||||
|
||||
Reference in New Issue
Block a user