Add support for state based value comparison in rules

pull/646/head
Michael Zanetti 2020-11-02 14:00:13 +01:00
parent bc2b603d3d
commit d31dd54f74
10 changed files with 233 additions and 53 deletions

View File

@ -280,7 +280,7 @@ void RuleManager::parseEventDescriptors(const QVariantList &eventDescriptorList,
StateEvaluator *RuleManager::parseStateEvaluator(const QVariantMap &stateEvaluatorMap)
{
// qDebug() << "Parsing state evaluator. Child count:" << stateEvaluatorMap.value("childEvaluators").toList().count();
qDebug() << "Parsing state evaluator:" << qUtf8Printable(QJsonDocument::fromVariant(stateEvaluatorMap).toJson());
if (!stateEvaluatorMap.contains("stateDescriptor")) {
return nullptr;
}
@ -295,6 +295,11 @@ StateEvaluator *RuleManager::parseStateEvaluator(const QVariantMap &stateEvaluat
} else {
sd = new StateDescriptor(sdMap.value("interface").toString(), sdMap.value("interfaceState").toString(), op, sdMap.value("value"), stateEvaluator);
}
if (sdMap.contains("valueThingId") && sdMap.contains("valueStateTypeId")) {
sd->setValueThingId(sdMap.value("valueThingId").toUuid());
sd->setValueStateTypeId(sdMap.value("valueStateTypeId").toUuid());
}
qDebug() << "Parsing stuff" << sd->valueThingId() << sd->valueStateTypeId();
stateEvaluator->setStateDescriptor(sd);
foreach (const QVariant &childEvaluatorVariant, stateEvaluatorMap.value("childEvaluators").toList()) {
@ -572,7 +577,12 @@ QVariantMap RuleManager::packStateEvaluator(StateEvaluator *stateEvaluator)
}
QMetaEnum valueOperatorEnum = QMetaEnum::fromType<StateDescriptor::ValueOperator>();
stateDescriptor.insert("operator", valueOperatorEnum.valueToKeys(stateEvaluator->stateDescriptor()->valueOperator()));
stateDescriptor.insert("value", stateEvaluator->stateDescriptor()->value());
if (!stateEvaluator->stateDescriptor()->value().isNull()) {
stateDescriptor.insert("value", stateEvaluator->stateDescriptor()->value());
} else {
stateDescriptor.insert("valueThingId", stateEvaluator->stateDescriptor()->valueThingId());
stateDescriptor.insert("valueStateTypeId", stateEvaluator->stateDescriptor()->valueStateTypeId());
}
ret.insert("stateDescriptor", stateDescriptor);
QVariantList childEvaluators;
for (int i = 0; i < stateEvaluator->childEvaluators()->rowCount(); i++) {

View File

@ -330,7 +330,7 @@ QDebug printStateEvaluator(QDebug &dbg, StateEvaluator *stateEvaluator, int inde
dbg << ">=";
break;
}
dbg << stateEvaluator->stateDescriptor()->value() << endl;
dbg << stateEvaluator->stateDescriptor()->value() << '/' << stateEvaluator->stateDescriptor()->valueThingId() << stateEvaluator->stateDescriptor()->valueStateTypeId() << endl;
}
if (stateEvaluator->childEvaluators()->rowCount() > 0) {
for (int i = 0; i < indentLevel; i++) { dbg << " "; }

View File

@ -135,11 +135,39 @@ void StateDescriptor::setValue(const QVariant &value)
}
}
QUuid StateDescriptor::valueThingId() const
{
return m_valueThingId;
}
void StateDescriptor::setValueThingId(const QUuid &valueThingId)
{
if (m_valueThingId != valueThingId) {
m_valueThingId = valueThingId;
emit valueThingIdChanged();
}
}
QUuid StateDescriptor::valueStateTypeId() const
{
return m_valueStateTypeId;
}
void StateDescriptor::setValueStateTypeId(const QUuid &valueStateTypeId)
{
if (m_valueStateTypeId != valueStateTypeId) {
m_valueStateTypeId = valueStateTypeId;
emit valueStateTypeIdChanged();
}
}
StateDescriptor *StateDescriptor::clone() const
{
StateDescriptor *ret = new StateDescriptor(thingId(), stateTypeId(), valueOperator(), value());
ret->setInterfaceName(interfaceName());
ret->setInterfaceState(interfaceState());
ret->setValueThingId(valueThingId());
ret->setValueStateTypeId(valueStateTypeId());
return ret;
}
@ -147,11 +175,13 @@ StateDescriptor *StateDescriptor::clone() const
#define COMPARE_PTR(a, b) if (!a->operator==(b)) { qDebug() << a << "!=" << b; return false; }
bool StateDescriptor::operator==(StateDescriptor *other) const
{
COMPARE(m_thingId, other->thingId());
COMPARE(m_stateTypeId, other->stateTypeId());
COMPARE(m_interfaceName, other->interfaceName());
COMPARE(m_interfaceState, other->interfaceState());
COMPARE(m_operator, other->valueOperator());
COMPARE(m_value, other->value());
COMPARE(m_thingId, other->thingId())
COMPARE(m_stateTypeId, other->stateTypeId())
COMPARE(m_interfaceName, other->interfaceName())
COMPARE(m_interfaceState, other->interfaceState())
COMPARE(m_operator, other->valueOperator())
COMPARE(m_value, other->value())
COMPARE(m_valueThingId, other->valueThingId())
COMPARE(m_valueStateTypeId, other->valueStateTypeId())
return true;
}

View File

@ -44,6 +44,8 @@ class StateDescriptor : public QObject
Q_PROPERTY(QString interfaceState READ interfaceState WRITE setInterfaceState NOTIFY interfaceStateChanged)
Q_PROPERTY(ValueOperator valueOperator READ valueOperator WRITE setValueOperator NOTIFY valueOperatorChanged)
Q_PROPERTY(QVariant value READ value WRITE setValue NOTIFY valueChanged)
Q_PROPERTY(QUuid valueThingId READ valueThingId WRITE setValueThingId NOTIFY valueThingIdChanged)
Q_PROPERTY(QUuid valueStateTypeId READ valueStateTypeId WRITE setValueStateTypeId NOTIFY valueStateTypeIdChanged)
public:
enum ValueOperator {
@ -78,6 +80,12 @@ public:
QVariant value() const;
void setValue(const QVariant &value);
QUuid valueThingId() const;
void setValueThingId(const QUuid &valueThingId);
QUuid valueStateTypeId() const;
void setValueStateTypeId(const QUuid &valueStateTypeId);
StateDescriptor* clone() const;
bool operator==(StateDescriptor *other) const;
@ -88,6 +96,8 @@ signals:
void interfaceStateChanged();
void valueOperatorChanged();
void valueChanged();
void valueThingIdChanged();
void valueStateTypeIdChanged();
private:
QUuid m_thingId;
@ -96,6 +106,8 @@ private:
QString m_interfaceState;
ValueOperator m_operator = ValueOperatorEquals;
QVariant m_value;
QUuid m_valueThingId;
QUuid m_valueStateTypeId;
};
#endif // STATEDESCRIPTOR_H

View File

@ -96,12 +96,8 @@ StateEvaluator *StateEvaluator::clone() const
{
StateEvaluator *ret = new StateEvaluator();
ret->m_operator = this->m_operator;
ret->m_stateDescriptor->setThingId(this->m_stateDescriptor->thingId());
ret->m_stateDescriptor->setStateTypeId(this->m_stateDescriptor->stateTypeId());
ret->m_stateDescriptor->setInterfaceName(this->m_stateDescriptor->interfaceName());
ret->m_stateDescriptor->setInterfaceState(this->m_stateDescriptor->interfaceState());
ret->m_stateDescriptor->setValueOperator(this->m_stateDescriptor->valueOperator());
ret->m_stateDescriptor->setValue(this->m_stateDescriptor->value());
delete ret->m_stateDescriptor; // Delete existing empty one and replace it with a clone of the
ret->m_stateDescriptor = this->m_stateDescriptor->clone();
for (int i = 0; i < this->m_childEvaluators->rowCount(); i++) {
ret->m_childEvaluators->addStateEvaluator(this->m_childEvaluators->get(i)->clone());
}

View File

@ -137,18 +137,26 @@ ItemDelegate {
}
Component {
id: boolComponent
Switch {
checked: root.param.value === true
Component.onCompleted: {
if (root.param.value === undefined) {
Item {
implicitHeight: theSwitch.implicitHeight
implicitWidth: theSwitch.implicitWidth
Switch {
id: theSwitch
anchors { top: parent.top; right: parent.right; bottom: parent.bottom }
width: Math.min(parent.width, implicitiWidth)
checked: root.param.value === true
Component.onCompleted: {
if (root.param.value === undefined) {
root.param.value = checked;
}
}
onClicked: {
root.param.value = checked;
}
}
onClicked: {
root.param.value = checked;
}
}
}
Component {
id: sliderComponent

View File

@ -122,6 +122,8 @@ Page {
ParamDelegate {
id: paramDelegate
Layout.fillWidth: true
hoverEnabled: false
padding: 0
paramType: root.actionType.paramTypes.get(index)
enabled: staticParamRadioButton.checked
nameVisible: false

View File

@ -38,38 +38,151 @@ import Nymea 1.0
Page {
id: root
// Needs to be set and filled in with thingId and eventTypeId
property var stateDescriptor: null
property StateDescriptor stateDescriptor: null
readonly property Thing thing: stateDescriptor && stateDescriptor.thingId ? engine.thingManager.things.getThing(stateDescriptor.thingId) : null
readonly property var iface: stateDescriptor && stateDescriptor.interfaceName ? Interfaces.findByName(stateDescriptor.interfaceName) : null
readonly property var stateType: thing ? thing.thingClass.stateTypes.getStateType(stateDescriptor.stateTypeId)
readonly property Interface iface: stateDescriptor && stateDescriptor.interfaceName ? Interfaces.findByName(stateDescriptor.interfaceName) : null
readonly property StateType stateType: thing ? thing.thingClass.stateTypes.getStateType(stateDescriptor.stateTypeId)
: iface ? iface.stateTypes.findByName(stateDescriptor.interfaceState) : null
signal backPressed();
signal completed();
header: NymeaHeader {
text: qsTr("Options")
text: qsTr("Condition")
onBackPressed: root.backPressed();
}
ColumnLayout {
anchors.fill: parent
ParamDescriptorDelegate {
id: paramDelegate
Layout.fillWidth: true
paramType: root.stateType
value: paramType.defaultValue
QtObject {
id: d
property var value: root.stateType.defaultValue
}
GroupBox {
anchors {
left: parent.left
top: parent.top
right: parent.right
margins: Style.margins
}
Button {
text: qsTr("OK")
Layout.fillWidth: true
Layout.margins: app.margins
onClicked: {
root.stateDescriptor.valueOperator = paramDelegate.operatorType
root.stateDescriptor.value = paramDelegate.value
root.completed()
GridLayout {
anchors.fill: parent
columns: width > 600 ? 2 : 1
Label {
Layout.fillWidth: true
text: "%1, %2".arg(root.thing.name).arg(root.stateType.displayName)
}
ComboBox {
id: operatorComboBox
Layout.fillWidth: true
property bool isNumeric: {
switch (root.stateType.type.toLowerCase()) {
case "bool":
case "string":
case "qstring":
case "color":
return false;
case "int":
case "double":
return true;
}
console.warn("ParamDescriptorDelegate: Unhandled data type:", root.stateType.type.toLowerCase());
return false;
}
model: isNumeric ?
[qsTr("is equal to"), qsTr("is not equal to"), qsTr("is smaller than"), qsTr("is greater than"), qsTr("is smaller or equal than"), qsTr("is greater or equal than")]
: [qsTr("is"), qsTr("is not")];
}
GroupBox {
Layout.columnSpan: parent.columns
Layout.fillWidth: true
GridLayout {
anchors.fill: parent
columns: root.width > 600 ? 2 : 1
RadioButton {
id: staticValueRadioButton
Layout.fillWidth: true
checked: true
text: qsTr("a static value:")
font.pixelSize: app.smallFont
}
RadioButton {
id: stateValueRadioButton
Layout.fillWidth: true
text: qsTr("another thing's state:")
font.pixelSize: app.smallFont
visible: engine.jsonRpcClient.ensureServerVersion("5.3")
}
ThinDivider { Layout.columnSpan: parent.columns }
ParamDelegate {
Layout.fillWidth: true
hoverEnabled: false
padding: 0
paramType: root.thing.thingClass.eventTypes.getEventType(root.stateType.id).paramTypes.getParamType(root.stateType.id)
enabled: staticValueRadioButton.checked
nameVisible: false
value: d.value
visible: staticValueRadioButton.checked
placeholderText: qsTr("Insert value here")
}
NymeaItemDelegate {
id: statePickerDelegate
Layout.fillWidth: true
text: thingId === null || stateTypeId === null
? qsTr("Select a state")
: thing.name + " - " + thing.thingClass.stateTypes.getStateType(stateTypeId).displayName
visible: stateValueRadioButton.checked
property var thingId: null
property var stateTypeId: null
readonly property Thing thing: engine.thingManager.things.getThing(thingId)
onClicked: {
var page = pageStack.push(Qt.resolvedUrl("SelectThingPage.qml"), {showStates: true, showEvents: false, showActions: false });
page.thingSelected.connect(function(thing) {
print("Thing selected", thing.name);
statePickerDelegate.thingId = thing.id
var selectStatePage = pageStack.replace(Qt.resolvedUrl("SelectStatePage.qml"), {thing: thing})
selectStatePage.stateSelected.connect(function(stateTypeId) {
print("State selected", stateTypeId)
pageStack.pop();
statePickerDelegate.stateTypeId = stateTypeId;
})
})
page.backPressed.connect(function() {
pageStack.pop();
})
}
}
}
}
Button {
text: qsTr("OK")
Layout.fillWidth: true
Layout.margins: app.margins
onClicked: {
root.stateDescriptor.valueOperator = operatorComboBox.currentIndex
if (staticValueRadioButton.checked) {
root.stateDescriptor.value = d.value
} else {
root.stateDescriptor.valueThingId = statePickerDelegate.thingId
root.stateDescriptor.valueStateTypeId = statePickerDelegate.stateTypeId
}
root.completed()
}
}
}
}
}

View File

@ -49,7 +49,7 @@ Page {
ListView {
anchors.fill: parent
model: thing.thingClass.stateTypes
model: root.thing.thingClass.stateTypes
delegate: NymeaSwipeDelegate {
width: parent.width

View File

@ -39,12 +39,12 @@ SwipeDelegate {
Layout.fillWidth: true
clip: true
property var stateEvaluator: null
property StateEvaluator stateEvaluator: null
property bool showChilds: false
readonly property Thing thing: stateEvaluator ? engine.thingManager.things.getThing(stateEvaluator.stateDescriptor.thingId) : null
readonly property var iface: stateEvaluator ? Interfaces.findByName(stateEvaluator.stateDescriptor.interfaceName) : null
readonly property var stateType: thing ? thing.thingClass.stateTypes.getStateType(stateEvaluator.stateDescriptor.stateTypeId)
readonly property Interface iface: stateEvaluator ? Interfaces.findByName(stateEvaluator.stateDescriptor.interfaceName) : null
readonly property StateType stateType: thing ? thing.thingClass.stateTypes.getStateType(stateEvaluator.stateDescriptor.stateTypeId)
: iface ? iface.stateTypes.findByName(stateEvaluator.stateDescriptor.interfaceState) : null
signal deleteClicked();
@ -96,17 +96,26 @@ SwipeDelegate {
if (!root.stateType) {
return qsTr("Press to edit condition")
}
var valueText = root.stateEvaluator.stateDescriptor.value;
switch (root.stateType.type.toLowerCase()) {
case "bool":
valueText = root.stateEvaluator.stateDescriptor.value === true ? qsTr("True") : qsTr("False")
break;
var valueText;
if (root.stateEvaluator.stateDescriptor.value !== undefined) {
valueText = root.stateEvaluator.stateDescriptor.value;
switch (root.stateType.type.toLowerCase()) {
case "bool":
valueText = root.stateEvaluator.stateDescriptor.value === true ? qsTr("True") : qsTr("False")
break;
}
} else {
print("value thing id:", root.stateEvaluator.stateDescriptor.valueThingId)
var valueThing = engine.thingManager.things.getThing(root.stateEvaluator.stateDescriptor.valueThingId)
valueText = valueThing.name
valueText += ", " + valueThing.thingClass.stateTypes.getStateType(root.stateEvaluator.stateDescriptor.valueStateTypeId).displayName
}
if (root.thing) {
return qsTr("%1: %2 %3 %4").arg(root.thing.name).arg(root.stateType.displayName).arg(operatorString).arg(valueText)
return "%1: %2 %3 %4".arg(root.thing.name).arg(root.stateType.displayName).arg(operatorString).arg(valueText)
} else if (root.iface) {
return qsTr("%1: %2 %3 %4").arg(root.iface.displayName).arg(root.stateType.displayName).arg(operatorString).arg(valueText)
return "%1, %2 %3 %4".arg(root.iface.displayName).arg(root.stateType.displayName).arg(operatorString).arg(valueText)
}
return "--";
}