diff --git a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/geofencing/GeofencingCalculatedFieldState.java b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/geofencing/GeofencingCalculatedFieldState.java index e336672877..a6bf9f8d6a 100644 --- a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/geofencing/GeofencingCalculatedFieldState.java +++ b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/geofencing/GeofencingCalculatedFieldState.java @@ -110,10 +110,14 @@ public class GeofencingCalculatedFieldState extends BaseCalculatedFieldState imp return; } GeofencingTransitionEvent transitionEvent = eval.transition(); - if (transitionEvent == null && !firstEval) { - return; + if (transitionEvent == null) { + if (!firstEval) { + return; + } + transitionEvent = eval.status() == INSIDE ? + GeofencingTransitionEvent.ENTERED : + GeofencingTransitionEvent.LEFT; } - transitionEvent = transitionEvent == null ? GeofencingTransitionEvent.LEFT : transitionEvent; EntityRelation relation = switch (zoneGroupCfg.getDirection()) { case TO -> new EntityRelation(zoneId, entityId, zoneGroupCfg.getRelationType()); case FROM -> new EntityRelation(entityId, zoneId, zoneGroupCfg.getRelationType()); diff --git a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/geofencing/GeofencingZoneState.java b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/geofencing/GeofencingZoneState.java index ca4108570c..f4b303a7bb 100644 --- a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/geofencing/GeofencingZoneState.java +++ b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/geofencing/GeofencingZoneState.java @@ -89,11 +89,7 @@ public class GeofencingZoneState { // first evaluation if (this.lastPresence == null) { this.lastPresence = status; - GeofencingTransitionEvent transition = null; - if (status == GeofencingPresenceStatus.INSIDE) { - transition = GeofencingTransitionEvent.ENTERED; - } - return new GeofencingEvalResult(transition, status); + return new GeofencingEvalResult(null, status); } // State changed if (this.lastPresence != status) { diff --git a/application/src/test/java/org/thingsboard/server/cf/CalculatedFieldIntegrationTest.java b/application/src/test/java/org/thingsboard/server/cf/CalculatedFieldIntegrationTest.java index a194680ca1..97d3ccfc1e 100644 --- a/application/src/test/java/org/thingsboard/server/cf/CalculatedFieldIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/cf/CalculatedFieldIntegrationTest.java @@ -799,23 +799,22 @@ public class CalculatedFieldIntegrationTest extends CalculatedFieldControllerTes doPost("/api/calculatedField", cf, CalculatedField.class); - // --- Assert initial evaluation (ENTERED / OUTSIDE) --- + // --- Assert initial evaluation --- await().alias("initial geofencing evaluation") .atMost(TIMEOUT, TimeUnit.SECONDS) .pollInterval(POLL_INTERVAL, TimeUnit.SECONDS) .untilAsserted(() -> { ArrayNode attrs = getServerAttributes(device.getId(), "allowedZonesEvent", "allowedZonesStatus", "restrictedZonesStatus", "restrictedZonesEvent"); - // --- no restrictedZonesEvent as no transition happened yet - assertThat(attrs).isNotNull().isNotEmpty().hasSize(3); + // --- no transition events as no transitions happened yet + assertThat(attrs).isNotNull().isNotEmpty().hasSize(2); Map m = kv(attrs); - assertThat(m).containsEntry("allowedZonesEvent", "ENTERED") - .containsEntry("allowedZonesStatus", "INSIDE") + assertThat(m).containsEntry("allowedZonesStatus", "INSIDE") .containsEntry("restrictedZonesStatus", "OUTSIDE"); }); // --- delete attributes reported in previous evaluation - doDelete("/api/plugins/telemetry/DEVICE/" + device.getUuidId() + "/SERVER_SCOPE?keys=allowedZonesEvent,allowedZonesStatus,restrictedZonesStatus", String.class); + doDelete("/api/plugins/telemetry/DEVICE/" + device.getUuidId() + "/SERVER_SCOPE?keys=allowedZonesStatus,restrictedZonesStatus", String.class); // --- Update restrictedZone by 'restrictedZone' attribute update doPost("/api/plugins/telemetry/DEVICE/" + device.getUuidId() + "/attributes/" + DataConstants.SERVER_SCOPE, @@ -909,17 +908,16 @@ public class CalculatedFieldIntegrationTest extends CalculatedFieldControllerTes doPost("/api/calculatedField", cf, CalculatedField.class); - // --- Assert initial evaluation (ENTERED / OUTSIDE) --- + // --- Assert initial evaluation --- await().alias("initial geofencing evaluation") .atMost(TIMEOUT, TimeUnit.SECONDS) .pollInterval(POLL_INTERVAL, TimeUnit.SECONDS) .untilAsserted(() -> { ArrayNode attrs = getServerAttributes(device.getId(), - "allowedZonesEvent", "allowedZonesStatus", "restrictedZonesStatus"); - assertThat(attrs).isNotNull().isNotEmpty().hasSize(3); + "allowedZonesEvent", "allowedZonesStatus", "restrictedZonesStatus", "restrictedZonesEvent"); + assertThat(attrs).isNotNull().isNotEmpty().hasSize(2); Map m = kv(attrs); - assertThat(m).containsEntry("allowedZonesEvent", "ENTERED") - .containsEntry("allowedZonesStatus", "INSIDE") + assertThat(m).containsEntry("allowedZonesStatus", "INSIDE") .containsEntry("restrictedZonesStatus", "OUTSIDE"); }); @@ -1014,16 +1012,15 @@ public class CalculatedFieldIntegrationTest extends CalculatedFieldControllerTes var geofencingConfiguration = (GeofencingCalculatedFieldConfiguration) configuration; assertThat(geofencingConfiguration.isScheduledUpdateEnabled()).isTrue(); - // --- Assert initial evaluation (ENTERED) --- + // --- Assert initial evaluation --- await().alias("initial geofencing evaluation") .atMost(TIMEOUT, TimeUnit.SECONDS) .pollInterval(POLL_INTERVAL, TimeUnit.SECONDS) .untilAsserted(() -> { ArrayNode attrs = getServerAttributes(device.getId(), "allowedZonesEvent", "allowedZonesStatus"); - assertThat(attrs).isNotNull().isNotEmpty().hasSize(2); + assertThat(attrs).isNotNull().isNotEmpty().hasSize(1); Map m = kv(attrs); - assertThat(m).containsEntry("allowedZonesEvent", "ENTERED") - .containsEntry("allowedZonesStatus", "INSIDE"); + assertThat(m).containsEntry("allowedZonesStatus", "INSIDE"); }); // --- Move device OUTSIDE Zone A (expect LEFT) --- @@ -1057,11 +1054,11 @@ public class CalculatedFieldIntegrationTest extends CalculatedFieldControllerTes awaitForCalculatedFieldEntityMessageProcessorToRegisterCfStateAsReadyToRefreshDynamicArguments(device.getId(), savedCalculatedField.getId(), minAllowedScheduledUpdateIntervalInSecForCF); - // --- Same coordinates as before, but now we expect ENTERED since a new zone is registered --- + // --- Same coordinates as before, but now we expect INSIDE group status since a new zone is registered --- doPost("/api/plugins/telemetry/DEVICE/" + device.getUuidId() + "/timeseries/unusedScope", JacksonUtil.toJsonNode("{\"latitude\":50.4760,\"longitude\":30.5110}")).andExpect(status().isOk()); - // --- Assert dynamic refresh picks up new relation and flips event back to ENTERED on the next telemetry update --- + // --- Assert dynamic refresh picks up a new relation and flips status back to INSIDE on the next telemetry update --- await().alias("dynamic refresh rebinds allowedZones") .atMost(TIMEOUT, TimeUnit.SECONDS) .pollInterval(1, TimeUnit.SECONDS) @@ -1069,7 +1066,7 @@ public class CalculatedFieldIntegrationTest extends CalculatedFieldControllerTes ArrayNode attrs = getServerAttributes(device.getId(), "allowedZonesEvent", "allowedZonesStatus"); assertThat(attrs).isNotNull().isNotEmpty().hasSize(2); Map m = kv(attrs); - assertThat(m).containsEntry("allowedZonesEvent", "ENTERED") + assertThat(m).containsEntry("allowedZonesEvent", "LEFT") // attribute from previous eval with outdated ts. .containsEntry("allowedZonesStatus", "INSIDE"); }); } diff --git a/application/src/test/java/org/thingsboard/server/service/cf/ctx/state/GeofencingCalculatedFieldStateTest.java b/application/src/test/java/org/thingsboard/server/service/cf/ctx/state/GeofencingCalculatedFieldStateTest.java index e717392979..6a3d57d839 100644 --- a/application/src/test/java/org/thingsboard/server/service/cf/ctx/state/GeofencingCalculatedFieldStateTest.java +++ b/application/src/test/java/org/thingsboard/server/service/cf/ctx/state/GeofencingCalculatedFieldStateTest.java @@ -249,7 +249,6 @@ public class GeofencingCalculatedFieldStateTest { assertThat(result.getScope()).isEqualTo(output.getScope()); assertThat(result.getResult().get("values")).isEqualTo( JacksonUtil.newObjectNode() - .put("allowedZonesEvent", "ENTERED") .put("allowedZonesStatus", "INSIDE") .put("restrictedZonesStatus", "OUTSIDE") ); @@ -329,9 +328,7 @@ public class GeofencingCalculatedFieldStateTest { assertThat(result).isNotNull(); assertThat(result.getType()).isEqualTo(output.getType()); assertThat(result.getScope()).isEqualTo(output.getScope()); - assertThat(result.getResult().get("values")).isEqualTo( - JacksonUtil.newObjectNode().put("allowedZonesEvent", "ENTERED") - ); + assertThat(result.getResult().get("values")).isEmpty(); SingleValueArgumentEntry newLatitude = new SingleValueArgumentEntry(System.currentTimeMillis(), new DoubleDataEntry("latitude", 50.4760), 146L); SingleValueArgumentEntry newLongitude = new SingleValueArgumentEntry(System.currentTimeMillis(), new DoubleDataEntry("longitude", 30.5110), 166L); diff --git a/application/src/test/java/org/thingsboard/server/service/cf/ctx/state/GeofencingZoneStateTest.java b/application/src/test/java/org/thingsboard/server/service/cf/ctx/state/GeofencingZoneStateTest.java index f6c6778ced..9b15fbc98a 100644 --- a/application/src/test/java/org/thingsboard/server/service/cf/ctx/state/GeofencingZoneStateTest.java +++ b/application/src/test/java/org/thingsboard/server/service/cf/ctx/state/GeofencingZoneStateTest.java @@ -47,8 +47,8 @@ public class GeofencingZoneStateTest { @Test void evaluate_initialInside_thenInsideAgain() { var inside = new Coordinates(50.4730, 30.5050); - // first evaluation: no prior state -> ENTERED - assertThat(state.evaluate(inside)).isEqualTo(new GeofencingEvalResult(ENTERED, INSIDE)); + // first evaluation: no prior state -> INSIDE + assertThat(state.evaluate(inside)).isEqualTo(new GeofencingEvalResult(null, INSIDE)); // same position again -> INSIDE (steady state) assertThat(state.evaluate(inside)).isEqualTo(new GeofencingEvalResult(null, INSIDE)); } @@ -66,8 +66,8 @@ public class GeofencingZoneStateTest { void evaluate_inside_thenLeave() { var inside = new Coordinates(50.4730, 30.5050); var outside = new Coordinates(50.4760, 30.5110); - // enter - assertThat(state.evaluate(inside)).isEqualTo(new GeofencingEvalResult(ENTERED, INSIDE)); + // initial eval + assertThat(state.evaluate(inside)).isEqualTo(new GeofencingEvalResult(null, INSIDE)); // leave -> LEFT assertThat(state.evaluate(outside)).isEqualTo(new GeofencingEvalResult(LEFT, OUTSIDE)); // still outside -> OUTSIDE diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/cf/CalculatedFieldTest.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/cf/CalculatedFieldTest.java index 43d9a159fa..43fcbda5af 100644 --- a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/cf/CalculatedFieldTest.java +++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/cf/CalculatedFieldTest.java @@ -409,11 +409,10 @@ public class CalculatedFieldTest extends AbstractContainerTest { .pollInterval(POLL_INTERVAL, TimeUnit.SECONDS) .untilAsserted(() -> { ArrayNode attrs = testRestClient.getAttributes(device.getId(), SERVER_SCOPE, - "allowedZonesEvent,allowedZonesStatus,restrictedZonesStatus"); - assertThat(attrs).isNotNull().hasSize(3); + "allowedZonesEvent,allowedZonesStatus,restrictedZonesEvent,restrictedZonesStatus"); + assertThat(attrs).isNotNull().hasSize(2); Map m = kv(attrs); - assertThat(m).containsEntry("allowedZonesEvent", "ENTERED") - .containsEntry("allowedZonesStatus", "INSIDE") + assertThat(m).containsEntry("allowedZonesStatus", "INSIDE") .containsEntry("restrictedZonesStatus", "OUTSIDE"); });