diff --git a/libnymea-core/integrations/thingmanagerimplementation.cpp b/libnymea-core/integrations/thingmanagerimplementation.cpp index 4a4ecc84..94a9c7fe 100644 --- a/libnymea-core/integrations/thingmanagerimplementation.cpp +++ b/libnymea-core/integrations/thingmanagerimplementation.cpp @@ -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(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(); } diff --git a/libnymea-core/integrations/thingmanagerimplementation.h b/libnymea-core/integrations/thingmanagerimplementation.h index 28d84387..6f6fbbb6 100644 --- a/libnymea-core/integrations/thingmanagerimplementation.h +++ b/libnymea-core/integrations/thingmanagerimplementation.h @@ -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 ¶mTypeId, const QVariant &value); void slotThingNameChanged(); diff --git a/libnymea-core/jsonrpc/integrationshandler.cpp b/libnymea-core/jsonrpc/integrationshandler.cpp index 3110f746..20c92a6f 100644 --- a/libnymea-core/jsonrpc/integrationshandler.cpp +++ b/libnymea-core/jsonrpc/integrationshandler.cpp @@ -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); } diff --git a/libnymea-core/jsonrpc/integrationshandler.h b/libnymea-core/jsonrpc/integrationshandler.h index 98046db2..417e6e81 100644 --- a/libnymea-core/jsonrpc/integrationshandler.h +++ b/libnymea-core/jsonrpc/integrationshandler.h @@ -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); diff --git a/libnymea-core/nymeacore.h b/libnymea-core/nymeacore.h index b67ac29a..15f8d171 100644 --- a/libnymea-core/nymeacore.h +++ b/libnymea-core/nymeacore.h @@ -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); diff --git a/libnymea/integrations/thing.cpp b/libnymea/integrations/thing.cpp index 6ea129cd..eb4e5118 100644 --- a/libnymea/integrations/thing.cpp +++ b/libnymea/integrations/thing.cpp @@ -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 { diff --git a/libnymea/integrations/thing.h b/libnymea/integrations/thing.h index 036f6871..2b4d39ed 100644 --- a/libnymea/integrations/thing.h +++ b/libnymea/integrations/thing.h @@ -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 ¶ms = 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 ¶mTypeId, const QVariant &value); void nameChanged(); void setupStatusChanged(); diff --git a/libnymea/integrations/thingmanager.h b/libnymea/integrations/thingmanager.h index e564f45d..0a36d13e 100644 --- a/libnymea/integrations/thingmanager.h +++ b/libnymea/integrations/thingmanager.h @@ -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); diff --git a/libnymea/types/state.cpp b/libnymea/types/state.cpp index 20cba66f..a4fd5879 100644 --- a/libnymea/types/state.cpp +++ b/libnymea/types/state.cpp @@ -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; diff --git a/libnymea/types/state.h b/libnymea/types/state.h index a5d77d3a..84f444cd 100644 --- a/libnymea/types/state.h +++ b/libnymea/types/state.h @@ -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) diff --git a/nymea.pro b/nymea.pro index 0794868b..87383251 100644 --- a/nymea.pro +++ b/nymea.pro @@ -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}" diff --git a/plugins/mock/extern-plugininfo.h b/plugins/mock/extern-plugininfo.h index e31016e9..3be28a3d 100644 --- a/plugins/mock/extern-plugininfo.h +++ b/plugins/mock/extern-plugininfo.h @@ -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; diff --git a/plugins/mock/integrationpluginmock.cpp b/plugins/mock/integrationpluginmock.cpp index ab6da1bb..3945f932 100644 --- a/plugins/mock/integrationpluginmock.cpp +++ b/plugins/mock/integrationpluginmock.cpp @@ -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; diff --git a/plugins/mock/integrationpluginmock.json b/plugins/mock/integrationpluginmock.json index 627e895b..705c7955 100644 --- a/plugins/mock/integrationpluginmock.json +++ b/plugins/mock/integrationpluginmock.json @@ -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", diff --git a/plugins/mock/plugininfo.h b/plugins/mock/plugininfo.h index e7b3de50..7df309ca 100644 --- a/plugins/mock/plugininfo.h +++ b/plugins/mock/plugininfo.h @@ -14,7 +14,7 @@ #include #include -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") }; diff --git a/tests/auto/api.json b/tests/auto/api.json index 9d6b39ad..c30ef821 100644 --- a/tests/auto/api.json +++ b/tests/auto/api.json @@ -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" }, diff --git a/tests/auto/devices/testdevices.cpp b/tests/auto/devices/testdevices.cpp index a2c7753b..eedcb0d1 100644 --- a/tests/auto/devices/testdevices.cpp +++ b/tests/auto/devices/testdevices.cpp @@ -897,7 +897,7 @@ void TestDevices::getActionTypes_data() QTest::addColumn >("actionTypeTestData"); QTest::newRow("valid deviceclass") << mockThingClassId - << (QList() << mockAsyncActionTypeId << mockAsyncFailingActionTypeId << mockFailingActionTypeId << mockWithoutParamsActionTypeId << mockPowerActionTypeId << mockWithoutParamsActionTypeId << mockBatteryLevelActionTypeId << mockSignalStrengthActionTypeId << mockUpdateStatusActionTypeId << mockPerformUpdateActionTypeId); + << (QList() << mockIntWithLimitsActionTypeId << mockAsyncActionTypeId << mockAsyncFailingActionTypeId << mockFailingActionTypeId << mockWithoutParamsActionTypeId << mockPowerActionTypeId << mockWithoutParamsActionTypeId << mockBatteryLevelActionTypeId << mockSignalStrengthActionTypeId << mockUpdateStatusActionTypeId << mockPerformUpdateActionTypeId); QTest::newRow("invalid deviceclass") << ThingClassId("094f8024-5caa-48c1-ab6a-de486a92088f") << QList(); } @@ -929,7 +929,7 @@ void TestDevices::getEventTypes_data() QTest::addColumn("deviceClassId"); QTest::addColumn("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"); QTest::addColumn("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"); diff --git a/tests/auto/integrations/testintegrations.cpp b/tests/auto/integrations/testintegrations.cpp index e3e193ad..71f32cb0 100644 --- a/tests/auto/integrations/testintegrations.cpp +++ b/tests/auto/integrations/testintegrations.cpp @@ -141,6 +141,8 @@ private slots: void params(); + void dynamicMinMax(); + void asyncSetupEmitsSetupStatusUpdate(); void testTranslations(); @@ -919,7 +921,7 @@ void TestIntegrations::getActionTypes_data() QTest::addColumn >("actionTypeTestData"); QTest::newRow("valid thingClass") << mockThingClassId - << (QList() << mockAsyncActionTypeId << mockAsyncFailingActionTypeId << mockFailingActionTypeId << mockWithoutParamsActionTypeId << mockPowerActionTypeId << mockWithoutParamsActionTypeId << mockBatteryLevelActionTypeId << mockSignalStrengthActionTypeId << mockUpdateStatusActionTypeId << mockPerformUpdateActionTypeId); + << (QList() << mockIntWithLimitsActionTypeId << mockAsyncActionTypeId << mockAsyncFailingActionTypeId << mockFailingActionTypeId << mockWithoutParamsActionTypeId << mockPowerActionTypeId << mockWithoutParamsActionTypeId << mockBatteryLevelActionTypeId << mockSignalStrengthActionTypeId << mockUpdateStatusActionTypeId << mockPerformUpdateActionTypeId); QTest::newRow("invalid thingClass") << ThingClassId("094f8024-5caa-48c1-ab6a-de486a92088f") << QList(); } @@ -951,7 +953,7 @@ void TestIntegrations::getEventTypes_data() QTest::addColumn("thingClassId"); QTest::addColumn("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"); QTest::addColumn("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 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();