diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingActionNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingActionNode.java
index abdcc5b299..cd88a1eda7 100644
--- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingActionNode.java
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingActionNode.java
@@ -15,6 +15,8 @@
*/
package org.thingsboard.rule.engine.geo;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
@@ -28,6 +30,7 @@ import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
import org.thingsboard.server.common.data.kv.StringDataEntry;
import org.thingsboard.server.common.data.plugin.ComponentType;
+import org.thingsboard.server.common.data.util.TbPair;
import org.thingsboard.server.common.msg.TbMsg;
import java.util.Collections;
@@ -39,6 +42,11 @@ import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
+import static org.thingsboard.rule.engine.util.GpsGeofencingEvents.ENTERED;
+import static org.thingsboard.rule.engine.util.GpsGeofencingEvents.INSIDE;
+import static org.thingsboard.rule.engine.util.GpsGeofencingEvents.LEFT;
+import static org.thingsboard.rule.engine.util.GpsGeofencingEvents.OUTSIDE;
+
/**
* Created by ashvayka on 19.01.18.
*/
@@ -46,15 +54,22 @@ import java.util.concurrent.TimeoutException;
@RuleNode(
type = ComponentType.ACTION,
name = "gps geofencing events",
+ version = 1,
configClazz = TbGpsGeofencingActionNodeConfiguration.class,
relationTypes = {"Success", "Entered", "Left", "Inside", "Outside"},
nodeDescription = "Produces incoming messages using GPS based geofencing",
- nodeDetails = "Extracts latitude and longitude parameters from incoming message and returns different events based on configuration parameters",
+ nodeDetails = "Extracts latitude and longitude parameters from incoming message and returns different events based on configuration parameters. " +
+ "
" +
+ "If an object with coordinates extracted from incoming message enters the geofence, sends a message with the type Entered. " +
+ "If an object leaves the geofence, sends a message with the type Left. " +
+ "If the presence monitoring strategy \"On first message\" is selected, sends messages with types Inside or Outside only the first time the geofencing and duration conditions are satisfied; otherwise Success. " +
+ "If the presence monitoring strategy \"On each message\" is selected, sends messages with types Inside or Outside every time the geofencing condition is satisfied.",
uiResources = {"static/rulenode/rulenode-core-config.js"},
configDirective = "tbActionNodeGpsGeofencingConfig"
)
public class TbGpsGeofencingActionNode extends AbstractGeofencingNode {
+ private static final String PRESENCE_MONITORING_STRATEGY_ON_EACH_MESSAGE = "presenceMonitoringStrategyOnEachMessage";
private final Map entityStates = new HashMap<>();
private final Gson gson = new Gson();
private final JsonParser parser = new JsonParser();
@@ -83,18 +98,21 @@ public class TbGpsGeofencingActionNode extends AbstractGeofencingNode (entityState.isInside() ?
TimeUnit.valueOf(config.getMinInsideDurationTimeUnit()).toMillis(config.getMinInsideDuration()) : TimeUnit.valueOf(config.getMinOutsideDurationTimeUnit()).toMillis(config.getMinOutsideDuration()))) {
setStaid(ctx, msg.getOriginator(), entityState);
- ctx.tellNext(msg, entityState.isInside() ? "Inside" : "Outside");
+ ctx.tellNext(msg, entityState.isInside() ? INSIDE : OUTSIDE);
told = true;
}
}
+ } else {
+ ctx.tellNext(msg, entityState.isInside() ? INSIDE : OUTSIDE);
+ told = true;
}
if (!told) {
ctx.tellSuccess(msg);
@@ -127,4 +145,17 @@ public class TbGpsGeofencingActionNode extends AbstractGeofencingNode getConfigClazz() {
return TbGpsGeofencingActionNodeConfiguration.class;
}
+
+ @Override
+ public TbPair upgrade(int fromVersion, JsonNode oldConfiguration) throws TbNodeException {
+ boolean hasChanges = false;
+ if (fromVersion == 0) {
+ if (!oldConfiguration.has(PRESENCE_MONITORING_STRATEGY_ON_EACH_MESSAGE)) {
+ hasChanges = true;
+ ((ObjectNode) oldConfiguration).put(PRESENCE_MONITORING_STRATEGY_ON_EACH_MESSAGE, false);
+ }
+ }
+ return new TbPair<>(hasChanges, oldConfiguration);
+ }
+
}
diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingActionNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingActionNodeConfiguration.java
index d0adad8996..ee375083b4 100644
--- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingActionNodeConfiguration.java
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingActionNodeConfiguration.java
@@ -31,6 +31,8 @@ public class TbGpsGeofencingActionNodeConfiguration extends TbGpsGeofencingFilte
private String minInsideDurationTimeUnit;
private String minOutsideDurationTimeUnit;
+ private boolean presenceMonitoringStrategyOnEachMessage;
+
@Override
public TbGpsGeofencingActionNodeConfiguration defaultConfiguration() {
TbGpsGeofencingActionNodeConfiguration configuration = new TbGpsGeofencingActionNodeConfiguration();
@@ -43,6 +45,7 @@ public class TbGpsGeofencingActionNodeConfiguration extends TbGpsGeofencingFilte
configuration.setMinOutsideDurationTimeUnit(TimeUnit.MINUTES.name());
configuration.setMinInsideDuration(1);
configuration.setMinOutsideDuration(1);
+ configuration.setPresenceMonitoringStrategyOnEachMessage(false);
return configuration;
}
}
diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/GpsGeofencingEvents.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/GpsGeofencingEvents.java
new file mode 100644
index 0000000000..db5ddaf698
--- /dev/null
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/GpsGeofencingEvents.java
@@ -0,0 +1,23 @@
+/**
+ * Copyright © 2016-2024 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.rule.engine.util;
+
+public class GpsGeofencingEvents {
+ public static final String ENTERED = "Entered";
+ public static final String INSIDE = "Inside";
+ public static final String LEFT = "Left";
+ public static final String OUTSIDE = "Outside";
+}
diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingActionNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingActionNodeTest.java
new file mode 100644
index 0000000000..dd7536f4ee
--- /dev/null
+++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingActionNodeTest.java
@@ -0,0 +1,264 @@
+/**
+ * Copyright © 2016-2024 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.rule.engine.geo;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.google.common.util.concurrent.Futures;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.mockito.ArgumentCaptor;
+import org.thingsboard.common.util.JacksonUtil;
+import org.thingsboard.rule.engine.api.TbContext;
+import org.thingsboard.rule.engine.api.TbNodeConfiguration;
+import org.thingsboard.rule.engine.api.TbNodeException;
+import org.thingsboard.server.common.data.DataConstants;
+import org.thingsboard.server.common.data.id.DeviceId;
+import org.thingsboard.server.common.data.id.EntityId;
+import org.thingsboard.server.common.data.msg.TbMsgType;
+import org.thingsboard.server.common.data.util.TbPair;
+import org.thingsboard.server.common.msg.TbMsg;
+import org.thingsboard.server.common.msg.TbMsgMetaData;
+import org.thingsboard.server.dao.attributes.AttributesService;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.UUID;
+import java.util.stream.Stream;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.thingsboard.rule.engine.util.GpsGeofencingEvents.ENTERED;
+import static org.thingsboard.rule.engine.util.GpsGeofencingEvents.INSIDE;
+import static org.thingsboard.server.common.data.msg.TbNodeConnectionType.SUCCESS;
+
+class TbGpsGeofencingActionNodeTest {
+ private TbContext ctx;
+ private TbGpsGeofencingActionNode node;
+ private AttributesService attributesService;
+
+ @BeforeEach
+ void setUp() {
+ ctx = mock(TbContext.class);
+ attributesService = mock(AttributesService.class);
+ node = new TbGpsGeofencingActionNode();
+ }
+
+ @AfterEach
+ void tearDown() {
+ node.destroy();
+ }
+
+ private static Stream givenPresenceMonitoringStrategyOnEachMessage_whenOnMsg_thenVerifyOutputMsgTypes() {
+ return Stream.of(
+ // default config with presenceMonitoringStrategyOnEachMessage false
+ Arguments.of(false, List.of(
+ Map.of(ENTERED, 0, INSIDE, 0, SUCCESS, 0),
+ Map.of(ENTERED, 1, INSIDE, 0, SUCCESS, 0),
+ Map.of(ENTERED, 1, INSIDE, 0, SUCCESS, 1),
+ Map.of(ENTERED, 1, INSIDE, 1, SUCCESS, 1),
+ Map.of(ENTERED, 1, INSIDE, 1, SUCCESS, 2)
+ )),
+ // default config with presenceMonitoringStrategyOnEachMessage true
+ Arguments.of(true, List.of(
+ Map.of(ENTERED, 0, INSIDE, 0, SUCCESS, 0),
+ Map.of(ENTERED, 1, INSIDE, 0, SUCCESS, 0),
+ Map.of(ENTERED, 1, INSIDE, 1, SUCCESS, 0),
+ Map.of(ENTERED, 1, INSIDE, 2, SUCCESS, 0),
+ Map.of(ENTERED, 1, INSIDE, 3, SUCCESS, 0)
+ ))
+ );
+ }
+
+ @ParameterizedTest
+ @MethodSource
+ void givenPresenceMonitoringStrategyOnEachMessage_whenOnMsg_thenVerifyOutputMsgTypes(
+ boolean presenceMonitoringStrategyOnEachMessage,
+ List