Merge PR #464: Add support for dynamic min/max state values

pull/487/head
Jenkins nymea 2021-12-11 00:30:57 +01:00
commit 00cfb88a74
18 changed files with 417 additions and 50 deletions

View File

@ -1297,7 +1297,18 @@ ThingActionInfo *ThingManagerImplementation::executeAction(const Action &action)
return info;
}
Thing::ThingError paramCheck = ThingUtils::verifyParams(actionType.paramTypes(), action.params());
// If there's a stateType with the same id, we'll need to take min/max values from the state as
// they might change at runtime
ParamTypes paramTypes = actionType.paramTypes();
StateType stateType = thingClass.stateTypes().findById(action.actionTypeId());
if (!stateType.id().isNull()) {
ParamType pt = actionType.paramTypes().at(0);
pt.setMinValue(thing->state(stateType.id()).minValue());
pt.setMaxValue(thing->state(stateType.id()).maxValue());
paramTypes = ParamTypes() << pt;
}
Thing::ThingError paramCheck = ThingUtils::verifyParams(paramTypes, action.params());
if (paramCheck != Thing::ThingErrorNoError) {
qCWarning(dcThingManager()) << "Cannot execute action. Parameter verification failed.";
ThingActionInfo *info = new ThingActionInfo(thing, action, this);
@ -1813,7 +1824,7 @@ void ThingManagerImplementation::onEventTriggered(Event event)
emit eventTriggered(event);
}
void ThingManagerImplementation::slotThingStateValueChanged(const StateTypeId &stateTypeId, const QVariant &value)
void ThingManagerImplementation::slotThingStateValueChanged(const StateTypeId &stateTypeId, const QVariant &value, const QVariant &minValue, const QVariant &maxValue)
{
Thing *thing = qobject_cast<Thing*>(sender());
if (!thing || !m_configuredThings.contains(thing->id())) {
@ -1822,7 +1833,7 @@ void ThingManagerImplementation::slotThingStateValueChanged(const StateTypeId &s
}
storeThingState(thing, stateTypeId);
emit thingStateChanged(thing, stateTypeId, value);
emit thingStateChanged(thing, stateTypeId, value, minValue, maxValue);
Param valueParam(ParamTypeId(stateTypeId.toString()), value);
Event event(EventTypeId(stateTypeId.toString()), thing->id(), ParamList() << valueParam, true);
@ -2095,22 +2106,28 @@ void ThingManagerImplementation::loadThingStates(Thing *thing)
settings.beginGroup(thing->id().toString());
ThingClass thingClass = m_supportedThings.value(thing->thingClassId());
foreach (const StateType &stateType, thingClass.stateTypes()) {
if (stateType.cached()) {
QVariant value = stateType.defaultValue();
QVariant value = stateType.defaultValue();
QVariant minValue = stateType.minValue();
QVariant maxValue = stateType.maxValue();
if (settings.contains(stateType.id().toString())) {
value = settings.value(stateType.id().toString());
} else if (settings.childGroups().contains(stateType.id().toString())) {
// 0.9 - 0.22 used to store in a subgroup
if (stateType.cached()) {
if (settings.childGroups().contains(stateType.id().toString())) {
settings.beginGroup(stateType.id().toString());
value = settings.value("value");
minValue = settings.value("minValue");
maxValue = settings.value("maxValue");
settings.endGroup();
} else if (settings.contains(stateType.id().toString())) {
// Migration from < 0.30
value = settings.value(stateType.id().toString());
}
value.convert(stateType.type());
thing->setStateValue(stateType.id(), value);
} else {
thing->setStateValue(stateType.id(), stateType.defaultValue());
minValue.convert(stateType.type());
maxValue.convert(stateType.type());
}
thing->setStateValue(stateType.id(), value);
thing->setStateMinMaxValues(stateType.id(), minValue, maxValue);
thing->setStateValueFilter(stateType.id(), stateType.filter());
}
settings.endGroup();
@ -2286,7 +2303,11 @@ void ThingManagerImplementation::storeThingState(Thing *thing, const StateTypeId
{
NymeaSettings settings(NymeaSettings::SettingsRoleThingStates);
settings.beginGroup(thing->id().toString());
settings.setValue(stateTypeId.toString(), thing->stateValue(stateTypeId));
settings.beginGroup(stateTypeId.toString());
settings.setValue("value", thing->stateValue(stateTypeId));
settings.setValue("minValue", thing->state(stateTypeId).minValue());
settings.setValue("maxValue", thing->state(stateTypeId).maxValue());
settings.endGroup();
settings.endGroup();
}

View File

@ -146,7 +146,7 @@ private slots:
void onEventTriggered(Event event);
// Only connect this to Things. It will query the sender()
void slotThingStateValueChanged(const StateTypeId &stateTypeId, const QVariant &value);
void slotThingStateValueChanged(const StateTypeId &stateTypeId, const QVariant &value, const QVariant &minValue, const QVariant &maxValue);
void slotThingSettingChanged(const ParamTypeId &paramTypeId, const QVariant &value);
void slotThingNameChanged();

View File

@ -398,6 +398,8 @@ IntegrationsHandler::IntegrationsHandler(ThingManager *thingManager, QObject *pa
params.insert("thingId", enumValueName(Uuid));
params.insert("stateTypeId", enumValueName(Uuid));
params.insert("value", enumValueName(Variant));
params.insert("minValue", enumValueName(Variant));
params.insert("maxValue", enumValueName(Variant));
registerNotification("StateChanged", description, params);
params.clear(); returns.clear();
@ -1121,12 +1123,14 @@ void IntegrationsHandler::pluginConfigChanged(const PluginId &id, const ParamLis
emit PluginConfigurationChanged(params);
}
void IntegrationsHandler::thingStateChanged(Thing *thing, const QUuid &stateTypeId, const QVariant &value)
void IntegrationsHandler::thingStateChanged(Thing *thing, const QUuid &stateTypeId, const QVariant &value, const QVariant &minValue, const QVariant &maxValue)
{
QVariantMap params;
params.insert("thingId", thing->id());
params.insert("stateTypeId", stateTypeId);
params.insert("value", value);
params.insert("minValue", minValue);
params.insert("maxValue", maxValue);
emit StateChanged(params);
}

View File

@ -95,7 +95,7 @@ signals:
private slots:
void pluginConfigChanged(const PluginId &id, const ParamList &config);
void thingStateChanged(Thing *thing, const QUuid &stateTypeId, const QVariant &value);
void thingStateChanged(Thing *thing, const QUuid &stateTypeId, const QVariant &value, const QVariant &minValue, const QVariant &maxValue);
void thingRemovedNotification(const ThingId &thingId);

View File

@ -120,7 +120,7 @@ signals:
void pluginConfigChanged(const PluginId &id, const ParamList &config);
void eventTriggered(const Event &event);
void thingStateChanged(Thing *thing, const QUuid &stateTypeId, const QVariant &value);
void thingStateChanged(Thing *thing, const QUuid &stateTypeId, const QVariant &value, const QVariant &minValue, const QVariant &maxValue);
void thingRemoved(const ThingId &thingId);
void thingAdded(Thing *thing);
void thingChanged(Thing *thing);

View File

@ -332,7 +332,7 @@ bool Thing::hasState(const StateTypeId &stateTypeId) const
return false;
}
/*! For convenience, this finds the \l{State} matching the given \a stateTypeId and returns the current valie in this thing. */
/*! Finds the \l{State} matching the given \a stateTypeId in this thing and returns the current value. */
QVariant Thing::stateValue(const StateTypeId &stateTypeId) const
{
foreach (const State &state, m_states) {
@ -343,12 +343,13 @@ QVariant Thing::stateValue(const StateTypeId &stateTypeId) const
return QVariant();
}
/*! Finds the \l{State} matching the given \a stateName in this thing and returns the current value. */
QVariant Thing::stateValue(const QString &stateName) const
{
return stateValue(m_thingClass.stateTypes().findByName(stateName).id());
}
/*! For convenience, this finds the \l{State} matching the given \a stateTypeId in this thing and sets the current value to \a value. */
/*! Sets the value for the \l{State} matching the given \a stateTypeId in this thing to value. */
void Thing::setStateValue(const StateTypeId &stateTypeId, const QVariant &value)
{
StateType stateType = m_thingClass.stateTypes().findById(stateTypeId);
@ -363,13 +364,14 @@ void Thing::setStateValue(const StateTypeId &stateTypeId, const QVariant &value)
qCWarning(dcThing()).nospace() << m_name << ": Invalid value " << value << " for state " << stateType.name() << ". Type mismatch. Expected type: " << QVariant::typeToName(stateType.type()) << " (Discarding change)";
return;
}
if (stateType.minValue().isValid() && value < stateType.minValue()) {
qCWarning(dcThing()).nospace() << m_name << ": Invalid value " << value << " for state " << stateType.name() << ". Out of range: " << stateType.minValue() << " - " << stateType.maxValue() << " (Correcting to closest value within range)";
newValue = stateType.minValue();
State state = m_states.at(i);
if (state.minValue().isValid() && value < state.minValue()) {
qCWarning(dcThing()).nospace() << m_name << ": Invalid value " << value << " for state " << stateType.name() << ". Out of range: " << state.minValue() << " - " << state.maxValue() << " (Correcting to closest value within range)";
newValue = state.minValue();
}
if (stateType.maxValue().isValid() && value > stateType.maxValue()) {
qCWarning(dcThing()).nospace() << m_name << ": Invalid value " << value << " for state " << stateType.name() << ". Out of range: " << stateType.minValue() << " - " << stateType.maxValue() << " (Correcting to closest value within range)";
newValue = stateType.maxValue();
if (state.maxValue().isValid() && value > state.maxValue()) {
qCWarning(dcThing()).nospace() << m_name << ": Invalid value " << value << " for state " << stateType.name() << ". Out of range: " << state.minValue() << " - " << state.maxValue() << " (Correcting to closest value within range)";
newValue = state.maxValue();
}
if (!stateType.possibleValues().isEmpty() && !stateType.possibleValues().contains(value)) {
qCWarning(dcThing()).nospace() << m_name << ": Invalid value " << value << " for state " << stateType.name() << ". Not an accepted value. Possible values: " << stateType.possibleValues() << " (Discarding change)";
@ -390,7 +392,7 @@ void Thing::setStateValue(const StateTypeId &stateTypeId, const QVariant &value)
qCDebug(dcThing()).nospace() << m_name << ": State " << stateType.name() << " changed from " << oldValue << " to " << newValue;
m_states[i].setValue(newValue);
emit stateValueChanged(stateTypeId, newValue);
emit stateValueChanged(stateTypeId, newValue, m_states.at(i).minValue(), m_states.at(i).maxValue());
return;
}
}
@ -398,12 +400,153 @@ void Thing::setStateValue(const StateTypeId &stateTypeId, const QVariant &value)
qCWarning(dcThing).nospace() << m_name << ": Failed setting state " << stateType.name() << "to" << value;
}
/*! Sets the value for the \l{State} matching the given \a stateName in this thing to value. */
void Thing::setStateValue(const QString &stateName, const QVariant &value)
{
StateTypeId stateTypeId = m_thingClass.stateTypes().findByName(stateName).id();
setStateValue(stateTypeId, value);
}
/*! Sets the minimum value for the \l{State} matching the given \a stateTypeId in this thing to value. */
void Thing::setStateMinValue(const StateTypeId &stateTypeId, const QVariant &minValue)
{
StateType stateType = m_thingClass.stateTypes().findById(stateTypeId);
if (!stateType.isValid()) {
qCWarning(dcThing()) << "No such state type" << stateTypeId.toString() << "in" << m_name << "(" + thingClass().name() + ")";
return;
}
for (int i = 0; i < m_states.count(); ++i) {
if (m_states.at(i).stateTypeId() == stateTypeId) {
QVariant newMin = minValue.isValid() ? minValue : stateType.minValue();
if (newMin == m_states.at(i).minValue()) {
return;
}
m_states[i].setMinValue(newMin);
// Sanity check for max >= min
if (m_states.at(i).maxValue() < newMin) {
qCWarning(dcThing()) << "Adjusting state maximum value for" << stateType.name() << "from" << m_states.at(i).maxValue() << "to new minimum value of" << newMin;
m_states[i].setMaxValue(newMin);
}
if (m_states.at(i).value() < newMin) {
qCInfo(dcThing()) << "Adjusting state value for" << stateType.name() << "from" << m_states.at(i).value() << "to new minimum value of" << newMin;
m_states[i].setValue(newMin);
}
emit stateValueChanged(stateTypeId, m_states.at(i).value(), m_states.at(i).minValue(), m_states.at(i).maxValue());
return;
}
}
Q_ASSERT_X(false, m_name.toUtf8(), QString("Failed setting minimum state value %1 to %2").arg(stateType.name()).arg(minValue.toString()).toUtf8());
qCWarning(dcThing).nospace() << m_name << ": Failed setting minimum state value " << stateType.name() << " to " << minValue;
}
/*! Sets the minimum value for the \l{State} matching the given \a stateName in this thing to value. */
void Thing::setStateMinValue(const QString &stateName, const QVariant &minValue)
{
StateTypeId stateTypeId = m_thingClass.stateTypes().findByName(stateName).id();
setStateMinValue(stateTypeId, minValue);
}
/*! Sets the maximum value for the \l{State} matching the given \a stateTypeId in this thing to value. */
void Thing::setStateMaxValue(const StateTypeId &stateTypeId, const QVariant &maxValue)
{
StateType stateType = m_thingClass.stateTypes().findById(stateTypeId);
if (!stateType.isValid()) {
qCWarning(dcThing()) << "No such state type" << stateTypeId.toString() << "in" << m_name << "(" + thingClass().name() + ")";
return;
}
for (int i = 0; i < m_states.count(); ++i) {
if (m_states.at(i).stateTypeId() == stateTypeId) {
QVariant newMax = maxValue.isValid() ? maxValue : stateType.maxValue();
if (newMax == m_states.at(i).maxValue()) {
return;
}
m_states[i].setMaxValue(newMax);
if (newMax.isValid()) {
// Sanity check for min <= max
if (m_states.at(i).minValue() > newMax) {
qCWarning(dcThing()) << "Adjusting minimum state value for" << stateType.name() << "from" << m_states.at(i).minValue() << "to new maximum value of" << newMax;
m_states[i].setMinValue(newMax);
}
if (m_states.at(i).value() > newMax) {
qCInfo(dcThing()) << "Adjusting state value for" << stateType.name() << "from" << m_states.at(i).value() << "to new maximum value of" << newMax;
m_states[i].setValue(maxValue);
}
}
emit stateValueChanged(stateTypeId, m_states.at(i).value(), m_states.at(i).minValue(), m_states.at(i).maxValue());
return;
}
}
Q_ASSERT_X(false, m_name.toUtf8(), QString("Failed setting maximum state value %1 to %2").arg(stateType.name()).arg(maxValue.toString()).toUtf8());
qCWarning(dcThing).nospace() << m_name << ": Failed setting maximum state value " << stateType.name() << " t o" << maxValue;
}
/*! Sets the maximum value for the \l{State} matching the given \a stateName in this thing to value. */
void Thing::setStateMaxValue(const QString &stateName, const QVariant &maxValue)
{
StateTypeId stateTypeId = m_thingClass.stateTypes().findByName(stateName).id();
setStateMaxValue(stateTypeId, maxValue);
}
void Thing::setStateMinMaxValues(const StateTypeId &stateTypeId, const QVariant &minValue, const QVariant &maxValue)
{
StateType stateType = m_thingClass.stateTypes().findById(stateTypeId);
if (!stateType.isValid()) {
qCWarning(dcThing()) << "No such state type" << stateTypeId.toString() << "in" << m_name << "(" + thingClass().name() + ")";
return;
}
for (int i = 0; i < m_states.count(); ++i) {
if (m_states.at(i).stateTypeId() == stateTypeId) {
QVariant newMin = minValue.isValid() ? minValue : stateType.minValue();
QVariant newMax = maxValue.isValid() ? maxValue : stateType.maxValue();
if (newMin == m_states.at(i).minValue() && newMax == m_states.at(i).maxValue()) {
return;
}
m_states[i].setMinValue(newMin);
m_states[i].setMaxValue(newMax);
if (newMax.isValid() || newMax.isValid()) {
// Sanity check for min <= max
if (newMin > newMax) {
qCWarning(dcThing()) << "Adjusting maximum state value for" << stateType.name() << "from" << m_states.at(i).maxValue() << "to new minimum value of" << newMax;
m_states[i].setMaxValue(newMin);
}
if (m_states.at(i).value() < m_states.at(i).minValue()) {
qCInfo(dcThing()) << "Adjusting state value for" << stateType.name() << "from" << m_states.at(i).value() << "to new minimum value of" << m_states.at(i).minValue();
m_states[i].setValue(m_states.at(i).minValue());
}
if (m_states.at(i).value() > m_states.at(i).maxValue()) {
qCInfo(dcThing()) << "Adjusting state value for" << stateType.name() << "from" << m_states.at(i).value() << "to new maximum value of" << m_states.at(i).maxValue();
m_states[i].setValue(m_states.at(i).maxValue());
}
}
emit stateValueChanged(stateTypeId, m_states.at(i).value(), m_states.at(i).minValue(), m_states.at(i).maxValue());
return;
}
}
Q_ASSERT_X(false, m_name.toUtf8(), QString("Failed setting maximum state value %1 to %2").arg(stateType.name()).arg(maxValue.toString()).toUtf8());
qCWarning(dcThing).nospace() << m_name << ": Failed setting maximum state value " << stateType.name() << " t o" << maxValue;
}
void Thing::setStateMinMaxValues(const QString &stateName, const QVariant &minValue, const QVariant &maxValue)
{
StateTypeId stateTypeId = m_thingClass.stateTypes().findByName(stateName).id();
setStateMinMaxValues(stateTypeId, minValue, maxValue);
}
/*! Returns the \l{State} with the given \a stateTypeId of this thing. */
State Thing::state(const StateTypeId &stateTypeId) const
{

View File

@ -138,6 +138,12 @@ public:
Q_INVOKABLE QVariant stateValue(const QString &stateName) const;
Q_INVOKABLE void setStateValue(const StateTypeId &stateTypeId, const QVariant &value);
Q_INVOKABLE void setStateValue(const QString &stateName, const QVariant &value);
Q_INVOKABLE void setStateMinValue(const StateTypeId &stateTypeId, const QVariant &minValue);
Q_INVOKABLE void setStateMinValue(const QString &stateName, const QVariant &minValue);
Q_INVOKABLE void setStateMaxValue(const StateTypeId &stateTypeId, const QVariant &maxValue);
Q_INVOKABLE void setStateMaxValue(const QString &stateName, const QVariant &maxValue);
Q_INVOKABLE void setStateMinMaxValues(const StateTypeId &stateTypeId, const QVariant &minValue, const QVariant &maxValue);
Q_INVOKABLE void setStateMinMaxValues(const QString &stateName, const QVariant &minValue, const QVariant &maxValue);
Q_INVOKABLE State state(const StateTypeId &stateTypeId) const;
Q_INVOKABLE State state(const QString &stateName) const;
@ -159,7 +165,7 @@ public slots:
void emitEvent(const EventTypeId &eventTypeId, const ParamList &params = ParamList());
signals:
void stateValueChanged(const StateTypeId &stateTypeId, const QVariant &value);
void stateValueChanged(const StateTypeId &stateTypeId, const QVariant &value, const QVariant &minValue, const QVariant &maxValue);
void settingChanged(const ParamTypeId &paramTypeId, const QVariant &value);
void nameChanged();
void setupStatusChanged();

View File

@ -111,7 +111,7 @@ protected:
signals:
void pluginConfigChanged(const PluginId &id, const ParamList &config);
void eventTriggered(const Event &event);
void thingStateChanged(Thing *thing, const StateTypeId &stateTypeId, const QVariant &value);
void thingStateChanged(Thing *thing, const StateTypeId &stateTypeId, const QVariant &value, const QVariant &minValue, const QVariant &maxValue);
void thingRemoved(const ThingId &thingId);
void thingDisappeared(const ThingId &thingId);
void thingAdded(Thing *thing);

View File

@ -80,6 +80,26 @@ void State::setValue(const QVariant &value)
m_value = value;
}
void State::setMinValue(const QVariant &minValue)
{
m_minValue = minValue;
}
void State::setMaxValue(const QVariant &maxValue)
{
m_maxValue = maxValue;
}
QVariant State::minValue() const
{
return m_minValue;
}
QVariant State::maxValue() const
{
return m_maxValue;
}
Types::StateValueFilter State::filter() const
{
return m_filter;

View File

@ -43,6 +43,8 @@ class LIBNYMEA_EXPORT State
Q_PROPERTY(QUuid stateTypeId READ stateTypeId)
Q_PROPERTY(QVariant value READ value)
Q_PROPERTY(Types::StateValueFilter filter READ filter)
Q_PROPERTY(QVariant minValue READ minValue USER true)
Q_PROPERTY(QVariant maxValue READ maxValue USER true)
public:
State();
@ -52,15 +54,25 @@ public:
ThingId thingId() const;
QVariant value() const;
void setValue(const QVariant &value);
QVariant minValue() const;
QVariant maxValue() const;
Types::StateValueFilter filter() const;
private:
friend class Thing;
void setValue(const QVariant &value);
void setMinValue(const QVariant &minValue);
void setMaxValue(const QVariant &maxValue);
void setFilter(Types::StateValueFilter filter);
private:
StateTypeId m_stateTypeId;
ThingId m_thingId;
QVariant m_value;
QVariant m_minValue;
QVariant m_maxValue;
Types::StateValueFilter m_filter = Types::StateValueFilterNone;
};
Q_DECLARE_METATYPE(State)

View File

@ -5,10 +5,10 @@ NYMEA_VERSION_STRING=$$system('dpkg-parsechangelog | sed -n -e "s/^Version: //p"
# define protocol versions
JSON_PROTOCOL_VERSION_MAJOR=5
JSON_PROTOCOL_VERSION_MINOR=7
JSON_PROTOCOL_VERSION_MINOR=8
JSON_PROTOCOL_VERSION="$${JSON_PROTOCOL_VERSION_MAJOR}.$${JSON_PROTOCOL_VERSION_MINOR}"
LIBNYMEA_API_VERSION_MAJOR=7
LIBNYMEA_API_VERSION_MINOR=2
LIBNYMEA_API_VERSION_MINOR=3
LIBNYMEA_API_VERSION_PATCH=0
LIBNYMEA_API_VERSION="$${LIBNYMEA_API_VERSION_MAJOR}.$${LIBNYMEA_API_VERSION_MINOR}.$${LIBNYMEA_API_VERSION_PATCH}"

View File

@ -24,8 +24,11 @@ extern ParamTypeId mockThingHttpportParamTypeId;
extern ParamTypeId mockThingAsyncParamTypeId;
extern ParamTypeId mockThingBrokenParamTypeId;
extern ParamTypeId mockSettingsSetting1ParamTypeId;
extern ParamTypeId mockSettingsIntStateWithLimitsMinValueParamTypeId;
extern ParamTypeId mockSettingsIntStateWithLimitsMaxValueParamTypeId;
extern ParamTypeId mockDiscoveryResultCountParamTypeId;
extern StateTypeId mockIntStateTypeId;
extern StateTypeId mockIntWithLimitsStateTypeId;
extern StateTypeId mockBoolStateTypeId;
extern StateTypeId mockDoubleStateTypeId;
extern StateTypeId mockBatteryLevelStateTypeId;
@ -38,6 +41,8 @@ extern StateTypeId mockCurrentVersionStateTypeId;
extern StateTypeId mockAvailableVersionStateTypeId;
extern EventTypeId mockIntEventTypeId;
extern ParamTypeId mockIntEventIntParamTypeId;
extern EventTypeId mockIntWithLimitsEventTypeId;
extern ParamTypeId mockIntWithLimitsEventIntWithLimitsParamTypeId;
extern EventTypeId mockBoolEventTypeId;
extern ParamTypeId mockBoolEventBoolParamTypeId;
extern EventTypeId mockDoubleEventTypeId;
@ -61,6 +66,8 @@ extern ParamTypeId mockAvailableVersionEventAvailableVersionParamTypeId;
extern EventTypeId mockEvent1EventTypeId;
extern EventTypeId mockEvent2EventTypeId;
extern ParamTypeId mockEvent2EventIntParamParamTypeId;
extern ActionTypeId mockIntWithLimitsActionTypeId;
extern ParamTypeId mockIntWithLimitsActionIntWithLimitsParamTypeId;
extern ActionTypeId mockBatteryLevelActionTypeId;
extern ParamTypeId mockBatteryLevelActionBatteryLevelParamTypeId;
extern ActionTypeId mockPowerActionTypeId;

View File

@ -192,6 +192,18 @@ void IntegrationPluginMock::setupThing(ThingSetupInfo *info)
}
qCDebug(dcMock()) << "Setup complete" << info->thing()->name();
info->finish(Thing::ThingErrorNoError);
Thing *thing = info->thing();
if (info->thing()->thingClassId() == mockThingClassId) {
connect(info->thing(), &Thing::settingChanged, this, [thing](const ParamTypeId &settingTypeId, const QVariant &value) {
if (settingTypeId == mockSettingsIntStateWithLimitsMinValueParamTypeId) {
thing->setStateMinValue(mockIntWithLimitsStateTypeId, value);
}
if (settingTypeId == mockSettingsIntStateWithLimitsMaxValueParamTypeId) {
thing->setStateMaxValue(mockIntWithLimitsStateTypeId, value);
}
});
}
return;
}
@ -573,6 +585,12 @@ void IntegrationPluginMock::executeAction(ThingActionInfo *info)
return;
}
if (info->action().actionTypeId() == mockIntWithLimitsActionTypeId) {
info->thing()->setStateValue(mockIntWithLimitsStateTypeId, info->action().paramValue(mockIntWithLimitsActionIntWithLimitsParamTypeId));
info->finish(Thing::ThingErrorNoError);
return;
}
if (info->action().actionTypeId() == mockFailingActionTypeId) {
info->finish(Thing::ThingErrorSetupFailed, QT_TR_NOOP("This mock action is intentionally broken."));
return;

View File

@ -73,6 +73,20 @@
"displayName": "Setting 1",
"type": "int",
"defaultValue": 5
},
{
"id": "9c34c881-e825-4f27-bb5c-db868bc60fb1",
"name": "intStateWithLimitsMinValue",
"displayName": "Minimum value for int with limits",
"type": "int",
"defaultValue": 0
},
{
"id": "984e7ae0-6de7-447e-bc4d-5afde8a00f27",
"name": "intStateWithLimitsMaxValue",
"displayName": "Maximum value for int with limits",
"type": "int",
"defaultValue": 50
}
],
"stateTypes": [
@ -85,6 +99,19 @@
"type": "int",
"suggestLogging": true
},
{
"id": "5aa479bd-537a-4716-9852-52f6eec58722",
"name": "intWithLimits",
"displayName": "Dummy int state with limits",
"displayNameEvent": "Dummy int state with limits changed",
"displayNameAction": "Set dummy int state with limits",
"defaultValue": 10,
"type": "int",
"minValue": 0,
"maxValue": 50,
"suggestLogging": true,
"writable": true
},
{
"id": "9dd6a97c-dfd1-43dc-acbd-367932742310",
"name": "bool",

View File

@ -14,7 +14,7 @@
#include <QLoggingCategory>
#include <QObject>
extern "C" const QString libnymea_api_version() { return QString("7.0.0");}
extern "C" const QString libnymea_api_version() { return QString("7.2.0");}
Q_DECLARE_LOGGING_CATEGORY(dcMock)
Q_LOGGING_CATEGORY(dcMock, "Mock")
@ -28,8 +28,11 @@ ParamTypeId mockThingHttpportParamTypeId = ParamTypeId("{d4f06047-125e-4479-9810
ParamTypeId mockThingAsyncParamTypeId = ParamTypeId("{f2977061-4dd0-4ef5-85aa-3b7134743be3}");
ParamTypeId mockThingBrokenParamTypeId = ParamTypeId("{ae8f8901-f2c1-42a5-8111-6d2fc8e4c1e4}");
ParamTypeId mockSettingsSetting1ParamTypeId = ParamTypeId("{367f7ba4-5039-47be-abd8-59cc8eaf4b9a}");
ParamTypeId mockSettingsIntStateWithLimitsMinValueParamTypeId = ParamTypeId("{9c34c881-e825-4f27-bb5c-db868bc60fb1}");
ParamTypeId mockSettingsIntStateWithLimitsMaxValueParamTypeId = ParamTypeId("{984e7ae0-6de7-447e-bc4d-5afde8a00f27}");
ParamTypeId mockDiscoveryResultCountParamTypeId = ParamTypeId("{d222adb4-2f9c-4c3f-8655-76400d0fb6ce}");
StateTypeId mockIntStateTypeId = StateTypeId("{80baec19-54de-4948-ac46-31eabfaceb83}");
StateTypeId mockIntWithLimitsStateTypeId = StateTypeId("{5aa479bd-537a-4716-9852-52f6eec58722}");
StateTypeId mockBoolStateTypeId = StateTypeId("{9dd6a97c-dfd1-43dc-acbd-367932742310}");
StateTypeId mockDoubleStateTypeId = StateTypeId("{7cac53ee-7048-4dc9-b000-7b585390f34c}");
StateTypeId mockBatteryLevelStateTypeId = StateTypeId("{6c8ab9a6-0164-4795-b829-f4394fe4edc4}");
@ -42,6 +45,8 @@ StateTypeId mockCurrentVersionStateTypeId = StateTypeId("{9f2e1e5d-3f1f-4794-aca
StateTypeId mockAvailableVersionStateTypeId = StateTypeId("{060d7947-2b70-4a2b-b33b-a3577f71faeb}");
EventTypeId mockIntEventTypeId = EventTypeId("{80baec19-54de-4948-ac46-31eabfaceb83}");
ParamTypeId mockIntEventIntParamTypeId = ParamTypeId("{80baec19-54de-4948-ac46-31eabfaceb83}");
EventTypeId mockIntWithLimitsEventTypeId = EventTypeId("{5aa479bd-537a-4716-9852-52f6eec58722}");
ParamTypeId mockIntWithLimitsEventIntWithLimitsParamTypeId = ParamTypeId("{5aa479bd-537a-4716-9852-52f6eec58722}");
EventTypeId mockBoolEventTypeId = EventTypeId("{9dd6a97c-dfd1-43dc-acbd-367932742310}");
ParamTypeId mockBoolEventBoolParamTypeId = ParamTypeId("{9dd6a97c-dfd1-43dc-acbd-367932742310}");
EventTypeId mockDoubleEventTypeId = EventTypeId("{7cac53ee-7048-4dc9-b000-7b585390f34c}");
@ -65,6 +70,8 @@ ParamTypeId mockAvailableVersionEventAvailableVersionParamTypeId = ParamTypeId("
EventTypeId mockEvent1EventTypeId = EventTypeId("{45bf3752-0fc6-46b9-89fd-ffd878b5b22b}");
EventTypeId mockEvent2EventTypeId = EventTypeId("{863d5920-b1cf-4eb9-88bd-8f7b8583b1cf}");
ParamTypeId mockEvent2EventIntParamParamTypeId = ParamTypeId("{0550e16d-60b9-4ba5-83f4-4d3cee656121}");
ActionTypeId mockIntWithLimitsActionTypeId = ActionTypeId("{5aa479bd-537a-4716-9852-52f6eec58722}");
ParamTypeId mockIntWithLimitsActionIntWithLimitsParamTypeId = ParamTypeId("{5aa479bd-537a-4716-9852-52f6eec58722}");
ActionTypeId mockBatteryLevelActionTypeId = ActionTypeId("{6c8ab9a6-0164-4795-b829-f4394fe4edc4}");
ParamTypeId mockBatteryLevelActionBatteryLevelParamTypeId = ParamTypeId("{6c8ab9a6-0164-4795-b829-f4394fe4edc4}");
ActionTypeId mockPowerActionTypeId = ActionTypeId("{064aed0d-da4c-49d4-b236-60f97e98ff84}");
@ -524,6 +531,18 @@ const QString translations[] {
//: The name of the EventType ({80baec19-54de-4948-ac46-31eabfaceb83}) of ThingClass mock
QT_TRANSLATE_NOOP("mock", "Dummy int state changed"),
//: The name of the ParamType (ThingClass: mock, ActionType: intWithLimits, ID: {5aa479bd-537a-4716-9852-52f6eec58722})
QT_TRANSLATE_NOOP("mock", "Dummy int state with limits"),
//: The name of the ParamType (ThingClass: mock, EventType: intWithLimits, ID: {5aa479bd-537a-4716-9852-52f6eec58722})
QT_TRANSLATE_NOOP("mock", "Dummy int state with limits"),
//: The name of the StateType ({5aa479bd-537a-4716-9852-52f6eec58722}) of ThingClass mock
QT_TRANSLATE_NOOP("mock", "Dummy int state with limits"),
//: The name of the EventType ({5aa479bd-537a-4716-9852-52f6eec58722}) of ThingClass mock
QT_TRANSLATE_NOOP("mock", "Dummy int state with limits changed"),
//: The name of the ParamType (ThingClass: mock, EventType: currentVersion, ID: {9f2e1e5d-3f1f-4794-aca3-4e05b7a48842})
QT_TRANSLATE_NOOP("mock", "Firmware version"),
@ -578,9 +597,15 @@ const QString translations[] {
//: The name of the ParamType (ThingClass: virtualIoTemperatureSensorMock, Type: settings, ID: {7077c56f-c35b-4252-8c15-8fb549be04ce})
QT_TRANSLATE_NOOP("mock", "Maximum temperature"),
//: The name of the ParamType (ThingClass: mock, Type: settings, ID: {984e7ae0-6de7-447e-bc4d-5afde8a00f27})
QT_TRANSLATE_NOOP("mock", "Maximum value for int with limits"),
//: The name of the ParamType (ThingClass: virtualIoTemperatureSensorMock, Type: settings, ID: {803cddbf-94c7-4f35-bc7a-18698b03b942})
QT_TRANSLATE_NOOP("mock", "Minimum temperature"),
//: The name of the ParamType (ThingClass: mock, Type: settings, ID: {9c34c881-e825-4f27-bb5c-db868bc60fb1})
QT_TRANSLATE_NOOP("mock", "Minimum value for int with limits"),
//: The name of the ActionType ({07cd8d5f-2f65-4955-b1f9-05d7f4da488a}) of ThingClass autoMock
QT_TRANSLATE_NOOP("mock", "Mock Action 1 (with params)"),
@ -677,6 +702,9 @@ const QString translations[] {
//: The name of the Browser Item ActionType ({da6faef8-2816-430e-93bb-57e8f9582d29}) of ThingClass mock
QT_TRANSLATE_NOOP("mock", "Remove from favorites"),
//: The name of the ParamType (ThingClass: mock, Type: discovery, ID: {d222adb4-2f9c-4c3f-8655-76400d0fb6ce})
QT_TRANSLATE_NOOP("mock", "Result count"),
//: The name of the ParamType (ThingClass: inputTypeMock, Type: thing, ID: {22add8c9-ee4f-43ad-8931-58e999313ac3})
QT_TRANSLATE_NOOP("mock", "Search text"),
@ -767,6 +795,9 @@ const QString translations[] {
//: The name of the ActionType ({53cd7c55-49b7-441b-b970-9048f20f0e2c}) of ThingClass pushButtonMock
QT_TRANSLATE_NOOP("mock", "Set double value"),
//: The name of the ActionType ({5aa479bd-537a-4716-9852-52f6eec58722}) of ThingClass mock
QT_TRANSLATE_NOOP("mock", "Set dummy int state with limits"),
//: The name of the ActionType ({fd341f72-6d9a-4812-9f66-47197c48a935}) of ThingClass virtualIoTemperatureSensorMock
QT_TRANSLATE_NOOP("mock", "Set input"),
@ -1259,9 +1290,6 @@ const QString translations[] {
//: The name of the ParamType (ThingClass: pushButtonMock, Type: discovery, ID: {c40dbc59-4bba-4871-9b8e-bbd8d5d9193b})
QT_TRANSLATE_NOOP("mock", "resultCount"),
//: The name of the ParamType (ThingClass: mock, Type: discovery, ID: {d222adb4-2f9c-4c3f-8655-76400d0fb6ce})
QT_TRANSLATE_NOOP("mock", "resultCount"),
//: The name of the ActionType ({064aed0d-da4c-49d4-b236-60f97e98ff84}) of ThingClass mock
QT_TRANSLATE_NOOP("mock", "set power")
};

View File

@ -1,4 +1,4 @@
5.7
5.8
{
"enums": {
"BasicType": [
@ -2385,6 +2385,8 @@
"Integrations.StateChanged": {
"description": "Emitted whenever a state of a thing changes.",
"params": {
"maxValue": "Variant",
"minValue": "Variant",
"stateTypeId": "Uuid",
"thingId": "Uuid",
"value": "Variant"
@ -3044,6 +3046,8 @@
},
"State": {
"r:filter": "$ref:StateValueFilter",
"r:o:maxValue": "Variant",
"r:o:minValue": "Variant",
"r:stateTypeId": "Uuid",
"r:value": "Variant"
},

View File

@ -897,7 +897,7 @@ void TestDevices::getActionTypes_data()
QTest::addColumn<QList<ActionTypeId> >("actionTypeTestData");
QTest::newRow("valid deviceclass") << mockThingClassId
<< (QList<ActionTypeId>() << mockAsyncActionTypeId << mockAsyncFailingActionTypeId << mockFailingActionTypeId << mockWithoutParamsActionTypeId << mockPowerActionTypeId << mockWithoutParamsActionTypeId << mockBatteryLevelActionTypeId << mockSignalStrengthActionTypeId << mockUpdateStatusActionTypeId << mockPerformUpdateActionTypeId);
<< (QList<ActionTypeId>() << mockIntWithLimitsActionTypeId << mockAsyncActionTypeId << mockAsyncFailingActionTypeId << mockFailingActionTypeId << mockWithoutParamsActionTypeId << mockPowerActionTypeId << mockWithoutParamsActionTypeId << mockBatteryLevelActionTypeId << mockSignalStrengthActionTypeId << mockUpdateStatusActionTypeId << mockPerformUpdateActionTypeId);
QTest::newRow("invalid deviceclass") << ThingClassId("094f8024-5caa-48c1-ab6a-de486a92088f") << QList<ActionTypeId>();
}
@ -929,7 +929,7 @@ void TestDevices::getEventTypes_data()
QTest::addColumn<ThingClassId>("deviceClassId");
QTest::addColumn<int>("resultCount");
QTest::newRow("valid deviceclass") << mockThingClassId << 13;
QTest::newRow("valid deviceclass") << mockThingClassId << 14;
QTest::newRow("invalid deviceclass") << ThingClassId("094f8024-5caa-48c1-ab6a-de486a92088f") << 0;
}
@ -954,7 +954,7 @@ void TestDevices::getStateTypes_data()
QTest::addColumn<ThingClassId>("thingClassId");
QTest::addColumn<int>("resultCount");
QTest::newRow("valid deviceclass") << mockThingClassId << 11;
QTest::newRow("valid deviceclass") << mockThingClassId << 12;
QTest::newRow("invalid deviceclass") << ThingClassId("094f8024-5caa-48c1-ab6a-de486a92088f") << 0;
}
@ -1053,7 +1053,7 @@ void TestDevices::getStateValues()
QCOMPARE(response.toMap().value("params").toMap().value("deviceError").toString(), enumValueName(statusCode));
if (statusCode == Device::DeviceErrorNoError) {
QVariantList values = response.toMap().value("params").toMap().value("values").toList();
QCOMPARE(values.count(), 11); // Mock device has 11 states...
QCOMPARE(values.count(), 12); // Mock device has 12 states...
}
}
@ -1156,7 +1156,7 @@ void TestDevices::testDeviceSettings()
QVERIFY2(DeviceId(device.value("id").toString()) == deviceId, "DeviceId not matching");
QVariantList settings = device.value("settings").toList();
QCOMPARE(settings.count(), 1);
QCOMPARE(settings.count(), 3);
QCOMPARE(settings.first().toMap().value("paramTypeId").toUuid(), QUuid(mockSettingsSetting1ParamTypeId));
QVERIFY2(settings.first().toMap().value("value").toInt() == 5, "Setting 1 default value not matching");
@ -1183,7 +1183,7 @@ void TestDevices::testDeviceSettings()
QVERIFY2(DeviceId(device.value("id").toString()) == deviceId, "DeviceId not matching");
settings = device.value("settings").toList();
QCOMPARE(settings.count(), 1);
QCOMPARE(settings.count(), 3);
QCOMPARE(settings.first().toMap().value("paramTypeId").toUuid(), QUuid(mockSettingsSetting1ParamTypeId));
QVERIFY2(settings.first().toMap().value("value").toInt() == 7, "Setting 1 changed value not matching");
@ -1201,7 +1201,7 @@ void TestDevices::testDeviceSettings()
QVERIFY2(DeviceId(device.value("id").toString()) == deviceId, "DeviceId not matching");
settings = device.value("settings").toList();
QCOMPARE(settings.count(), 1);
QCOMPARE(settings.count(), 3);
QCOMPARE(settings.first().toMap().value("paramTypeId").toUuid(), QUuid(mockSettingsSetting1ParamTypeId));
QVERIFY2(settings.first().toMap().value("value").toInt() == 7, "Setting 1 changed value not persisting restart");

View File

@ -141,6 +141,8 @@ private slots:
void params();
void dynamicMinMax();
void asyncSetupEmitsSetupStatusUpdate();
void testTranslations();
@ -919,7 +921,7 @@ void TestIntegrations::getActionTypes_data()
QTest::addColumn<QList<ActionTypeId> >("actionTypeTestData");
QTest::newRow("valid thingClass") << mockThingClassId
<< (QList<ActionTypeId>() << mockAsyncActionTypeId << mockAsyncFailingActionTypeId << mockFailingActionTypeId << mockWithoutParamsActionTypeId << mockPowerActionTypeId << mockWithoutParamsActionTypeId << mockBatteryLevelActionTypeId << mockSignalStrengthActionTypeId << mockUpdateStatusActionTypeId << mockPerformUpdateActionTypeId);
<< (QList<ActionTypeId>() << mockIntWithLimitsActionTypeId << mockAsyncActionTypeId << mockAsyncFailingActionTypeId << mockFailingActionTypeId << mockWithoutParamsActionTypeId << mockPowerActionTypeId << mockWithoutParamsActionTypeId << mockBatteryLevelActionTypeId << mockSignalStrengthActionTypeId << mockUpdateStatusActionTypeId << mockPerformUpdateActionTypeId);
QTest::newRow("invalid thingClass") << ThingClassId("094f8024-5caa-48c1-ab6a-de486a92088f") << QList<ActionTypeId>();
}
@ -951,7 +953,7 @@ void TestIntegrations::getEventTypes_data()
QTest::addColumn<ThingClassId>("thingClassId");
QTest::addColumn<int>("resultCount");
QTest::newRow("valid thingClass") << mockThingClassId << 13;
QTest::newRow("valid thingClass") << mockThingClassId << 14;
QTest::newRow("invalid thingClass") << ThingClassId("094f8024-5caa-48c1-ab6a-de486a92088f") << 0;
}
@ -976,7 +978,7 @@ void TestIntegrations::getStateTypes_data()
QTest::addColumn<ThingClassId>("thingClassId");
QTest::addColumn<int>("resultCount");
QTest::newRow("valid thingClass") << mockThingClassId << 11;
QTest::newRow("valid thingClass") << mockThingClassId << 12;
QTest::newRow("invalid thingClass") << ThingClassId("094f8024-5caa-48c1-ab6a-de486a92088f") << 0;
}
@ -1046,7 +1048,7 @@ void TestIntegrations::getStateValues()
QCOMPARE(response.toMap().value("params").toMap().value("thingError").toString(), enumValueName(statusCode));
if (statusCode == Thing::ThingErrorNoError) {
QVariantList values = response.toMap().value("params").toMap().value("values").toList();
QCOMPARE(values.count(), 11); // Mock has 11 states...
QCOMPARE(values.count(), 12); // Mock has 12 states...
}
}
@ -1149,7 +1151,7 @@ void TestIntegrations::testThingSettings()
QVERIFY2(ThingId(thing.value("id").toString()) == thingId, "thingId not matching");
QVariantList settings = thing.value("settings").toList();
QCOMPARE(settings.count(), 1);
QCOMPARE(settings.count(), 3);
QCOMPARE(settings.first().toMap().value("paramTypeId").toUuid(), QUuid(mockSettingsSetting1ParamTypeId));
QVERIFY2(settings.first().toMap().value("value").toInt() == 5, "Setting 1 default value not matching");
@ -1176,7 +1178,7 @@ void TestIntegrations::testThingSettings()
QVERIFY2(ThingId(thing.value("id").toString()) == thingId, "thingId not matching");
settings = thing.value("settings").toList();
QCOMPARE(settings.count(), 1);
QCOMPARE(settings.count(), 3);
QCOMPARE(settings.first().toMap().value("paramTypeId").toUuid(), QUuid(mockSettingsSetting1ParamTypeId));
QVERIFY2(settings.first().toMap().value("value").toInt() == 7, "Setting 1 changed value not matching");
@ -1194,7 +1196,7 @@ void TestIntegrations::testThingSettings()
QVERIFY2(ThingId(thing.value("id").toString()) == thingId, "thingId not matching");
settings = thing.value("settings").toList();
QCOMPARE(settings.count(), 1);
QCOMPARE(settings.count(), 3);
QCOMPARE(settings.first().toMap().value("paramTypeId").toUuid(), QUuid(mockSettingsSetting1ParamTypeId));
QVERIFY2(settings.first().toMap().value("value").toInt() == 7, "Setting 1 changed value not persisting restart");
@ -2123,6 +2125,81 @@ void TestIntegrations::params()
QVERIFY(!event.param(ParamTypeId::createParamTypeId()).value().isValid());
}
void TestIntegrations::dynamicMinMax()
{
enableNotifications({"Integrations"});
QList<Thing*> things = NymeaCore::instance()->thingManager()->findConfiguredThings(mockThingClassId);
QVERIFY2(things.count() > 0, "There needs to be at least one configured Mock for this test");
Thing *thing = things.first();
QSignalSpy notificationSpy(m_mockTcpServer, SIGNAL(outgoingData(QUuid,QByteArray)));
// Setup connection to mock client
QNetworkAccessManager nam;
// trigger state changed event in mock device
qCDebug(dcTests()) << "Changing state in mock thing to 11";
int port = thing->paramValue(mockThingHttpportParamTypeId).toInt();
QNetworkRequest request(QUrl(QString("http://localhost:%1/setstate?%2=%3").arg(port).arg(mockIntWithLimitsStateTypeId.toString()).arg(11)));
QNetworkReply *reply = nam.get(request);
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
// Check for the notification on JSON API
notificationSpy.wait();
QVariantList notifications;
notifications = checkNotifications(notificationSpy, "Integrations.StateChanged");
QVERIFY2(notifications.count() == 1, QString("Expected 1 Integrations.StateChanged notification. Received: %1").arg(notifications.count()).toUtf8());
QVariantMap notificationContent = notifications.first().toMap().value("params").toMap();
QCOMPARE(notificationContent.value("thingId").toUuid().toString(), thing->id().toString());
QCOMPARE(notificationContent.value("stateTypeId").toUuid().toString(), mockIntWithLimitsStateTypeId.toString());
QCOMPARE(notificationContent.value("value").toInt(), 11);
QCOMPARE(notificationContent.value("minValue").toInt(), 0);
QCOMPARE(notificationContent.value("maxValue").toInt(), 50);
// set the max to 8
qCDebug(dcTests()) << "Changing state max value to 8";
notificationSpy.clear();
thing->setStateMaxValue(mockIntWithLimitsStateTypeId, 8);
// Check for the notification on JSON API, state chould adapt to new max
notificationSpy.wait();
notifications = checkNotifications(notificationSpy, "Integrations.StateChanged");
QVERIFY2(notifications.count() == 1, "Should get Integrations.StateChanged notification");
notificationContent = notifications.first().toMap().value("params").toMap();
QCOMPARE(notificationContent.value("thingId").toUuid().toString(), thing->id().toString());
QCOMPARE(notificationContent.value("stateTypeId").toUuid().toString(), mockIntWithLimitsStateTypeId.toString());
QCOMPARE(notificationContent.value("value").toInt(), 8);
// Try to execute an action on the api that exceeds the max value
qCDebug(dcTests()) << "Executing action with invalid max value (40)";
QVariantMap actionParams;
actionParams.insert("thingId", thing->id());
actionParams.insert("actionTypeId", mockIntWithLimitsActionTypeId);
QVariantMap valueParam;
valueParam.insert("paramTypeId", mockIntWithLimitsActionIntWithLimitsParamTypeId);
// intentionally between thingClass max and dynamic max
valueParam.insert("value", 40);
actionParams.insert("params", QVariantList() << valueParam);
QVariant response = injectAndWait("Integrations.ExecuteAction", actionParams);
verifyThingError(response, Thing::ThingErrorInvalidParameter);
// Set the max to 100
qCDebug(dcTests()) << "Changing max state value to 100";
thing->setStateMaxValue(mockIntWithLimitsStateTypeId, 100);
// And try to execute the action again
// intentionally greater than thingClass max
qCDebug(dcTests()) << "Executing action with valid max (52)";
valueParam.insert("value", 52);
actionParams.insert("params", QVariantList() << valueParam);
response = injectAndWait("Integrations.ExecuteAction", actionParams);
verifyThingError(response, Thing::ThingErrorNoError);
}
void TestIntegrations::asyncSetupEmitsSetupStatusUpdate()
{
QVariantMap configuredDevices = injectAndWait("Integrations.GetThings").toMap();