Browse Source

Merge pull request #14647 from thingsboard/geofencing-cf-eval-logic-change

Updated geofencing CF evaluation logic
pull/14650/head
Viacheslav Klimov 8 months ago
committed by GitHub
parent
commit
90f0eecdc9
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 10
      application/src/main/java/org/thingsboard/server/service/cf/ctx/state/geofencing/GeofencingCalculatedFieldState.java
  2. 6
      application/src/main/java/org/thingsboard/server/service/cf/ctx/state/geofencing/GeofencingZoneState.java
  3. 33
      application/src/test/java/org/thingsboard/server/cf/CalculatedFieldIntegrationTest.java
  4. 5
      application/src/test/java/org/thingsboard/server/service/cf/ctx/state/GeofencingCalculatedFieldStateTest.java
  5. 8
      application/src/test/java/org/thingsboard/server/service/cf/ctx/state/GeofencingZoneStateTest.java
  6. 7
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/cf/CalculatedFieldTest.java

10
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; return;
} }
GeofencingTransitionEvent transitionEvent = eval.transition(); GeofencingTransitionEvent transitionEvent = eval.transition();
if (transitionEvent == null && !firstEval) { if (transitionEvent == null) {
return; if (!firstEval) {
return;
}
transitionEvent = eval.status() == INSIDE ?
GeofencingTransitionEvent.ENTERED :
GeofencingTransitionEvent.LEFT;
} }
transitionEvent = transitionEvent == null ? GeofencingTransitionEvent.LEFT : transitionEvent;
EntityRelation relation = switch (zoneGroupCfg.getDirection()) { EntityRelation relation = switch (zoneGroupCfg.getDirection()) {
case TO -> new EntityRelation(zoneId, entityId, zoneGroupCfg.getRelationType()); case TO -> new EntityRelation(zoneId, entityId, zoneGroupCfg.getRelationType());
case FROM -> new EntityRelation(entityId, zoneId, zoneGroupCfg.getRelationType()); case FROM -> new EntityRelation(entityId, zoneId, zoneGroupCfg.getRelationType());

6
application/src/main/java/org/thingsboard/server/service/cf/ctx/state/geofencing/GeofencingZoneState.java

@ -89,11 +89,7 @@ public class GeofencingZoneState {
// first evaluation // first evaluation
if (this.lastPresence == null) { if (this.lastPresence == null) {
this.lastPresence = status; this.lastPresence = status;
GeofencingTransitionEvent transition = null; return new GeofencingEvalResult(null, status);
if (status == GeofencingPresenceStatus.INSIDE) {
transition = GeofencingTransitionEvent.ENTERED;
}
return new GeofencingEvalResult(transition, status);
} }
// State changed // State changed
if (this.lastPresence != status) { if (this.lastPresence != status) {

33
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); doPost("/api/calculatedField", cf, CalculatedField.class);
// --- Assert initial evaluation (ENTERED / OUTSIDE) --- // --- Assert initial evaluation ---
await().alias("initial geofencing evaluation") await().alias("initial geofencing evaluation")
.atMost(TIMEOUT, TimeUnit.SECONDS) .atMost(TIMEOUT, TimeUnit.SECONDS)
.pollInterval(POLL_INTERVAL, TimeUnit.SECONDS) .pollInterval(POLL_INTERVAL, TimeUnit.SECONDS)
.untilAsserted(() -> { .untilAsserted(() -> {
ArrayNode attrs = getServerAttributes(device.getId(), ArrayNode attrs = getServerAttributes(device.getId(),
"allowedZonesEvent", "allowedZonesStatus", "restrictedZonesStatus", "restrictedZonesEvent"); "allowedZonesEvent", "allowedZonesStatus", "restrictedZonesStatus", "restrictedZonesEvent");
// --- no restrictedZonesEvent as no transition happened yet // --- no transition events as no transitions happened yet
assertThat(attrs).isNotNull().isNotEmpty().hasSize(3); assertThat(attrs).isNotNull().isNotEmpty().hasSize(2);
Map<String, String> m = kv(attrs); Map<String, String> m = kv(attrs);
assertThat(m).containsEntry("allowedZonesEvent", "ENTERED") assertThat(m).containsEntry("allowedZonesStatus", "INSIDE")
.containsEntry("allowedZonesStatus", "INSIDE")
.containsEntry("restrictedZonesStatus", "OUTSIDE"); .containsEntry("restrictedZonesStatus", "OUTSIDE");
}); });
// --- delete attributes reported in previous evaluation // --- 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 // --- Update restrictedZone by 'restrictedZone' attribute update
doPost("/api/plugins/telemetry/DEVICE/" + device.getUuidId() + "/attributes/" + DataConstants.SERVER_SCOPE, 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); doPost("/api/calculatedField", cf, CalculatedField.class);
// --- Assert initial evaluation (ENTERED / OUTSIDE) --- // --- Assert initial evaluation ---
await().alias("initial geofencing evaluation") await().alias("initial geofencing evaluation")
.atMost(TIMEOUT, TimeUnit.SECONDS) .atMost(TIMEOUT, TimeUnit.SECONDS)
.pollInterval(POLL_INTERVAL, TimeUnit.SECONDS) .pollInterval(POLL_INTERVAL, TimeUnit.SECONDS)
.untilAsserted(() -> { .untilAsserted(() -> {
ArrayNode attrs = getServerAttributes(device.getId(), ArrayNode attrs = getServerAttributes(device.getId(),
"allowedZonesEvent", "allowedZonesStatus", "restrictedZonesStatus"); "allowedZonesEvent", "allowedZonesStatus", "restrictedZonesStatus", "restrictedZonesEvent");
assertThat(attrs).isNotNull().isNotEmpty().hasSize(3); assertThat(attrs).isNotNull().isNotEmpty().hasSize(2);
Map<String, String> m = kv(attrs); Map<String, String> m = kv(attrs);
assertThat(m).containsEntry("allowedZonesEvent", "ENTERED") assertThat(m).containsEntry("allowedZonesStatus", "INSIDE")
.containsEntry("allowedZonesStatus", "INSIDE")
.containsEntry("restrictedZonesStatus", "OUTSIDE"); .containsEntry("restrictedZonesStatus", "OUTSIDE");
}); });
@ -1014,16 +1012,15 @@ public class CalculatedFieldIntegrationTest extends CalculatedFieldControllerTes
var geofencingConfiguration = (GeofencingCalculatedFieldConfiguration) configuration; var geofencingConfiguration = (GeofencingCalculatedFieldConfiguration) configuration;
assertThat(geofencingConfiguration.isScheduledUpdateEnabled()).isTrue(); assertThat(geofencingConfiguration.isScheduledUpdateEnabled()).isTrue();
// --- Assert initial evaluation (ENTERED) --- // --- Assert initial evaluation ---
await().alias("initial geofencing evaluation") await().alias("initial geofencing evaluation")
.atMost(TIMEOUT, TimeUnit.SECONDS) .atMost(TIMEOUT, TimeUnit.SECONDS)
.pollInterval(POLL_INTERVAL, TimeUnit.SECONDS) .pollInterval(POLL_INTERVAL, TimeUnit.SECONDS)
.untilAsserted(() -> { .untilAsserted(() -> {
ArrayNode attrs = getServerAttributes(device.getId(), "allowedZonesEvent", "allowedZonesStatus"); ArrayNode attrs = getServerAttributes(device.getId(), "allowedZonesEvent", "allowedZonesStatus");
assertThat(attrs).isNotNull().isNotEmpty().hasSize(2); assertThat(attrs).isNotNull().isNotEmpty().hasSize(1);
Map<String, String> m = kv(attrs); Map<String, String> m = kv(attrs);
assertThat(m).containsEntry("allowedZonesEvent", "ENTERED") assertThat(m).containsEntry("allowedZonesStatus", "INSIDE");
.containsEntry("allowedZonesStatus", "INSIDE");
}); });
// --- Move device OUTSIDE Zone A (expect LEFT) --- // --- Move device OUTSIDE Zone A (expect LEFT) ---
@ -1057,11 +1054,11 @@ public class CalculatedFieldIntegrationTest extends CalculatedFieldControllerTes
awaitForCalculatedFieldEntityMessageProcessorToRegisterCfStateAsReadyToRefreshDynamicArguments(device.getId(), savedCalculatedField.getId(), minAllowedScheduledUpdateIntervalInSecForCF); 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", doPost("/api/plugins/telemetry/DEVICE/" + device.getUuidId() + "/timeseries/unusedScope",
JacksonUtil.toJsonNode("{\"latitude\":50.4760,\"longitude\":30.5110}")).andExpect(status().isOk()); 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") await().alias("dynamic refresh rebinds allowedZones")
.atMost(TIMEOUT, TimeUnit.SECONDS) .atMost(TIMEOUT, TimeUnit.SECONDS)
.pollInterval(1, TimeUnit.SECONDS) .pollInterval(1, TimeUnit.SECONDS)
@ -1069,7 +1066,7 @@ public class CalculatedFieldIntegrationTest extends CalculatedFieldControllerTes
ArrayNode attrs = getServerAttributes(device.getId(), "allowedZonesEvent", "allowedZonesStatus"); ArrayNode attrs = getServerAttributes(device.getId(), "allowedZonesEvent", "allowedZonesStatus");
assertThat(attrs).isNotNull().isNotEmpty().hasSize(2); assertThat(attrs).isNotNull().isNotEmpty().hasSize(2);
Map<String, String> m = kv(attrs); Map<String, String> m = kv(attrs);
assertThat(m).containsEntry("allowedZonesEvent", "ENTERED") assertThat(m).containsEntry("allowedZonesEvent", "LEFT") // attribute from previous eval with outdated ts.
.containsEntry("allowedZonesStatus", "INSIDE"); .containsEntry("allowedZonesStatus", "INSIDE");
}); });
} }

5
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.getScope()).isEqualTo(output.getScope());
assertThat(result.getResult().get("values")).isEqualTo( assertThat(result.getResult().get("values")).isEqualTo(
JacksonUtil.newObjectNode() JacksonUtil.newObjectNode()
.put("allowedZonesEvent", "ENTERED")
.put("allowedZonesStatus", "INSIDE") .put("allowedZonesStatus", "INSIDE")
.put("restrictedZonesStatus", "OUTSIDE") .put("restrictedZonesStatus", "OUTSIDE")
); );
@ -329,9 +328,7 @@ public class GeofencingCalculatedFieldStateTest {
assertThat(result).isNotNull(); assertThat(result).isNotNull();
assertThat(result.getType()).isEqualTo(output.getType()); assertThat(result.getType()).isEqualTo(output.getType());
assertThat(result.getScope()).isEqualTo(output.getScope()); assertThat(result.getScope()).isEqualTo(output.getScope());
assertThat(result.getResult().get("values")).isEqualTo( assertThat(result.getResult().get("values")).isEmpty();
JacksonUtil.newObjectNode().put("allowedZonesEvent", "ENTERED")
);
SingleValueArgumentEntry newLatitude = new SingleValueArgumentEntry(System.currentTimeMillis(), new DoubleDataEntry("latitude", 50.4760), 146L); SingleValueArgumentEntry newLatitude = new SingleValueArgumentEntry(System.currentTimeMillis(), new DoubleDataEntry("latitude", 50.4760), 146L);
SingleValueArgumentEntry newLongitude = new SingleValueArgumentEntry(System.currentTimeMillis(), new DoubleDataEntry("longitude", 30.5110), 166L); SingleValueArgumentEntry newLongitude = new SingleValueArgumentEntry(System.currentTimeMillis(), new DoubleDataEntry("longitude", 30.5110), 166L);

8
application/src/test/java/org/thingsboard/server/service/cf/ctx/state/GeofencingZoneStateTest.java

@ -47,8 +47,8 @@ public class GeofencingZoneStateTest {
@Test @Test
void evaluate_initialInside_thenInsideAgain() { void evaluate_initialInside_thenInsideAgain() {
var inside = new Coordinates(50.4730, 30.5050); var inside = new Coordinates(50.4730, 30.5050);
// first evaluation: no prior state -> ENTERED // first evaluation: no prior state -> INSIDE
assertThat(state.evaluate(inside)).isEqualTo(new GeofencingEvalResult(ENTERED, INSIDE)); assertThat(state.evaluate(inside)).isEqualTo(new GeofencingEvalResult(null, INSIDE));
// same position again -> INSIDE (steady state) // same position again -> INSIDE (steady state)
assertThat(state.evaluate(inside)).isEqualTo(new GeofencingEvalResult(null, INSIDE)); assertThat(state.evaluate(inside)).isEqualTo(new GeofencingEvalResult(null, INSIDE));
} }
@ -66,8 +66,8 @@ public class GeofencingZoneStateTest {
void evaluate_inside_thenLeave() { void evaluate_inside_thenLeave() {
var inside = new Coordinates(50.4730, 30.5050); var inside = new Coordinates(50.4730, 30.5050);
var outside = new Coordinates(50.4760, 30.5110); var outside = new Coordinates(50.4760, 30.5110);
// enter // initial eval
assertThat(state.evaluate(inside)).isEqualTo(new GeofencingEvalResult(ENTERED, INSIDE)); assertThat(state.evaluate(inside)).isEqualTo(new GeofencingEvalResult(null, INSIDE));
// leave -> LEFT // leave -> LEFT
assertThat(state.evaluate(outside)).isEqualTo(new GeofencingEvalResult(LEFT, OUTSIDE)); assertThat(state.evaluate(outside)).isEqualTo(new GeofencingEvalResult(LEFT, OUTSIDE));
// still outside -> OUTSIDE // still outside -> OUTSIDE

7
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) .pollInterval(POLL_INTERVAL, TimeUnit.SECONDS)
.untilAsserted(() -> { .untilAsserted(() -> {
ArrayNode attrs = testRestClient.getAttributes(device.getId(), SERVER_SCOPE, ArrayNode attrs = testRestClient.getAttributes(device.getId(), SERVER_SCOPE,
"allowedZonesEvent,allowedZonesStatus,restrictedZonesStatus"); "allowedZonesEvent,allowedZonesStatus,restrictedZonesEvent,restrictedZonesStatus");
assertThat(attrs).isNotNull().hasSize(3); assertThat(attrs).isNotNull().hasSize(2);
Map<String, String> m = kv(attrs); Map<String, String> m = kv(attrs);
assertThat(m).containsEntry("allowedZonesEvent", "ENTERED") assertThat(m).containsEntry("allowedZonesStatus", "INSIDE")
.containsEntry("allowedZonesStatus", "INSIDE")
.containsEntry("restrictedZonesStatus", "OUTSIDE"); .containsEntry("restrictedZonesStatus", "OUTSIDE");
}); });

Loading…
Cancel
Save