diff --git a/application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldEntityMessageProcessor.java b/application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldEntityMessageProcessor.java index ff5799b91a..2da66afe31 100644 --- a/application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldEntityMessageProcessor.java +++ b/application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldEntityMessageProcessor.java @@ -321,7 +321,7 @@ public class CalculatedFieldEntityMessageProcessor extends AbstractContextAwareM boolean stateSizeChecked = false; try { if (ctx.isInitialized() && state.isReady()) { - CalculatedFieldResult calculationResult = state.performCalculation(ctx).get(systemContext.getCfCalculationResultTimeout(), TimeUnit.SECONDS); + CalculatedFieldResult calculationResult = state.performCalculation(entityId, ctx).get(systemContext.getCfCalculationResultTimeout(), TimeUnit.SECONDS); state.checkStateSize(ctxId, ctx.getMaxStateSize()); stateSizeChecked = true; if (state.isSizeOk()) { diff --git a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/BaseCalculatedFieldState.java b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/BaseCalculatedFieldState.java index fa7e628ab3..eb87d375c5 100644 --- a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/BaseCalculatedFieldState.java +++ b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/BaseCalculatedFieldState.java @@ -35,15 +35,13 @@ public abstract class BaseCalculatedFieldState implements CalculatedFieldState { protected long latestTimestamp = -1; - private boolean dirty; - public BaseCalculatedFieldState(List requiredArguments) { this.requiredArguments = requiredArguments; this.arguments = new HashMap<>(); } public BaseCalculatedFieldState() { - this(new ArrayList<>(), new HashMap<>(), false, -1, false); + this(new ArrayList<>(), new HashMap<>(), false, -1); } @Override diff --git a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/CalculatedFieldState.java b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/CalculatedFieldState.java index 2fdd0c00d6..e58ca699e2 100644 --- a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/CalculatedFieldState.java +++ b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/CalculatedFieldState.java @@ -20,6 +20,7 @@ import com.fasterxml.jackson.annotation.JsonSubTypes; import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.google.common.util.concurrent.ListenableFuture; import org.thingsboard.server.common.data.cf.CalculatedFieldType; +import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.service.cf.CalculatedFieldResult; import org.thingsboard.server.service.cf.ctx.CalculatedFieldEntityCtxId; @@ -47,15 +48,18 @@ public interface CalculatedFieldState { long getLatestTimestamp(); - void setDirty(boolean dirty); + default void setDirty(boolean dirty) { + } - boolean isDirty(); + default boolean isDirty() { + return false; + } void setRequiredArguments(List requiredArguments); boolean updateState(CalculatedFieldCtx ctx, Map argumentValues); - ListenableFuture performCalculation(CalculatedFieldCtx ctx); + ListenableFuture performCalculation(EntityId entityId, CalculatedFieldCtx ctx); @JsonIgnore boolean isReady(); @@ -70,7 +74,6 @@ public interface CalculatedFieldState { void checkStateSize(CalculatedFieldEntityCtxId ctxId, long maxStateSize); default void checkArgumentSize(String name, ArgumentEntry entry, CalculatedFieldCtx ctx) { - // TODO: Do we need to restrict the size of Geofencing arguments? Number of zones? if (entry instanceof TsRollingArgumentEntry || entry instanceof GeofencingArgumentEntry) { return; } diff --git a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/GeofencingCalculatedFieldState.java b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/GeofencingCalculatedFieldState.java index 32c49eeb54..f0b1bb7594 100644 --- a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/GeofencingCalculatedFieldState.java +++ b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/GeofencingCalculatedFieldState.java @@ -43,7 +43,6 @@ import java.util.stream.Collectors; import static org.thingsboard.server.common.data.cf.configuration.GeofencingCalculatedFieldConfiguration.ENTITY_ID_LATITUDE_ARGUMENT_KEY; import static org.thingsboard.server.common.data.cf.configuration.GeofencingCalculatedFieldConfiguration.ENTITY_ID_LONGITUDE_ARGUMENT_KEY; -import static org.thingsboard.server.common.data.cf.configuration.GeofencingCalculatedFieldConfiguration.coordinateKeys; @Data @AllArgsConstructor @@ -116,20 +115,20 @@ public class GeofencingCalculatedFieldState implements CalculatedFieldState { } @Override - public ListenableFuture performCalculation(CalculatedFieldCtx ctx) { + public ListenableFuture performCalculation(EntityId entityId, CalculatedFieldCtx ctx) { double latitude = (double) arguments.get(ENTITY_ID_LATITUDE_ARGUMENT_KEY).getValue(); double longitude = (double) arguments.get(ENTITY_ID_LONGITUDE_ARGUMENT_KEY).getValue(); Coordinates entityCoordinates = new Coordinates(latitude, longitude); var configuration = (GeofencingCalculatedFieldConfiguration) ctx.getCalculatedField().getConfiguration(); - if (configuration.isTrackRelationToZones()) { - // TODO: currently creates relation to device profile if CF created for profile) - return calculateWithRelations(ctx, entityCoordinates, configuration); + if (configuration.isCreateRelationsWithMatchedZones()) { + return calculateWithRelations(entityId, ctx, entityCoordinates, configuration); } return calculateWithoutRelations(ctx, entityCoordinates, configuration); } private ListenableFuture calculateWithRelations( + EntityId entityId, CalculatedFieldCtx ctx, Coordinates entityCoordinates, GeofencingCalculatedFieldConfiguration configuration) { @@ -160,7 +159,7 @@ public class GeofencingCalculatedFieldState implements CalculatedFieldState { List> relationFutures = zoneEventMap.entrySet().stream() .filter(entry -> entry.getValue().isTransitionEvent()) .map(entry -> { - EntityRelation relation = toRelation(entry.getKey(), ctx, configuration); + EntityRelation relation = toRelation(entry.getKey(), entityId, configuration); return switch (entry.getValue()) { case ENTERED -> ctx.getRelationService().saveRelationAsync(ctx.getTenantId(), relation); case LEFT -> ctx.getRelationService().deleteRelationAsync(ctx.getTenantId(), relation); @@ -218,11 +217,10 @@ public class GeofencingCalculatedFieldState implements CalculatedFieldState { } } - // TODO: Create a new class field to not do this on each calculation. private Map getGeofencingArguments() { return arguments.entrySet() .stream() - .filter(entry -> !coordinateKeys.contains(entry.getKey())) + .filter(entry -> entry.getValue().getType().equals(ArgumentEntryType.GEOFENCING)) .collect(Collectors.toMap(Map.Entry::getKey, entry -> (GeofencingArgumentEntry) entry.getValue())); } @@ -259,10 +257,10 @@ public class GeofencingCalculatedFieldState implements CalculatedFieldState { return Optional.empty(); } - private EntityRelation toRelation(EntityId zoneId, CalculatedFieldCtx ctx, GeofencingCalculatedFieldConfiguration configuration) { + private EntityRelation toRelation(EntityId zoneId, EntityId entityId, GeofencingCalculatedFieldConfiguration configuration) { return switch (configuration.getZoneRelationDirection()) { - case TO -> new EntityRelation(zoneId, ctx.getEntityId(), configuration.getZoneRelationType()); - case FROM -> new EntityRelation(ctx.getEntityId(), zoneId, configuration.getZoneRelationType()); + case TO -> new EntityRelation(zoneId, entityId, configuration.getZoneRelationType()); + case FROM -> new EntityRelation(entityId, zoneId, configuration.getZoneRelationType()); }; } diff --git a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/ScriptCalculatedFieldState.java b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/ScriptCalculatedFieldState.java index 84dce627ae..e1f1305c48 100644 --- a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/ScriptCalculatedFieldState.java +++ b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/ScriptCalculatedFieldState.java @@ -27,6 +27,7 @@ import org.thingsboard.script.api.tbel.TbelCfCtx; import org.thingsboard.script.api.tbel.TbelCfSingleValueArg; import org.thingsboard.server.common.data.cf.CalculatedFieldType; import org.thingsboard.server.common.data.cf.configuration.Output; +import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.service.cf.CalculatedFieldResult; import java.util.ArrayList; @@ -53,7 +54,7 @@ public class ScriptCalculatedFieldState extends BaseCalculatedFieldState { } @Override - public ListenableFuture performCalculation(CalculatedFieldCtx ctx) { + public ListenableFuture performCalculation(EntityId entityId, CalculatedFieldCtx ctx) { Map arguments = new LinkedHashMap<>(); List args = new ArrayList<>(ctx.getArgNames().size() + 1); args.add(new Object()); // first element is a ctx, but we will set it later; diff --git a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/SimpleCalculatedFieldState.java b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/SimpleCalculatedFieldState.java index 577ff80219..76839a3cbc 100644 --- a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/SimpleCalculatedFieldState.java +++ b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/SimpleCalculatedFieldState.java @@ -25,6 +25,7 @@ import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.script.api.tbel.TbUtils; import org.thingsboard.server.common.data.cf.CalculatedFieldType; import org.thingsboard.server.common.data.cf.configuration.Output; +import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.kv.BasicKvEntry; import org.thingsboard.server.service.cf.CalculatedFieldResult; @@ -52,7 +53,7 @@ public class SimpleCalculatedFieldState extends BaseCalculatedFieldState { } @Override - public ListenableFuture performCalculation(CalculatedFieldCtx ctx) { + public ListenableFuture performCalculation(EntityId entityId, CalculatedFieldCtx ctx) { var expr = ctx.getCustomExpression().get(); for (Map.Entry entry : this.arguments.entrySet()) { diff --git a/application/src/test/java/org/thingsboard/server/service/cf/ctx/state/ScriptCalculatedFieldStateTest.java b/application/src/test/java/org/thingsboard/server/service/cf/ctx/state/ScriptCalculatedFieldStateTest.java index b82d407d3e..280bac6bd4 100644 --- a/application/src/test/java/org/thingsboard/server/service/cf/ctx/state/ScriptCalculatedFieldStateTest.java +++ b/application/src/test/java/org/thingsboard/server/service/cf/ctx/state/ScriptCalculatedFieldStateTest.java @@ -125,7 +125,7 @@ public class ScriptCalculatedFieldStateTest { void testPerformCalculation() throws ExecutionException, InterruptedException { state.arguments = new HashMap<>(Map.of("deviceTemperature", deviceTemperatureArgEntry, "assetHumidity", assetHumidityArgEntry)); - CalculatedFieldResult result = state.performCalculation(ctx).get(); + CalculatedFieldResult result = state.performCalculation(ctx.getEntityId(), ctx).get(); assertThat(result).isNotNull(); Output output = getCalculatedFieldConfig().getOutput(); diff --git a/application/src/test/java/org/thingsboard/server/service/cf/ctx/state/SimpleCalculatedFieldStateTest.java b/application/src/test/java/org/thingsboard/server/service/cf/ctx/state/SimpleCalculatedFieldStateTest.java index 3f69b6fc3a..8c631ecf6f 100644 --- a/application/src/test/java/org/thingsboard/server/service/cf/ctx/state/SimpleCalculatedFieldStateTest.java +++ b/application/src/test/java/org/thingsboard/server/service/cf/ctx/state/SimpleCalculatedFieldStateTest.java @@ -134,7 +134,7 @@ public class SimpleCalculatedFieldStateTest { "key3", key3ArgEntry )); - CalculatedFieldResult result = state.performCalculation(ctx).get(); + CalculatedFieldResult result = state.performCalculation(ctx.getEntityId(), ctx).get(); assertThat(result).isNotNull(); Output output = getCalculatedFieldConfig().getOutput(); @@ -151,7 +151,7 @@ public class SimpleCalculatedFieldStateTest { "key3", key3ArgEntry )); - assertThatThrownBy(() -> state.performCalculation(ctx)) + assertThatThrownBy(() -> state.performCalculation(ctx.getEntityId(), ctx)) .isInstanceOf(IllegalArgumentException.class) .hasMessage("Argument 'key2' is not a number."); } @@ -164,7 +164,7 @@ public class SimpleCalculatedFieldStateTest { "key3", key3ArgEntry )); - CalculatedFieldResult result = state.performCalculation(ctx).get(); + CalculatedFieldResult result = state.performCalculation(ctx.getEntityId(), ctx).get(); assertThat(result).isNotNull(); Output output = getCalculatedFieldConfig().getOutput(); @@ -185,7 +185,7 @@ public class SimpleCalculatedFieldStateTest { output.setDecimalsByDefault(3); ctx.setOutput(output); - CalculatedFieldResult result = state.performCalculation(ctx).get(); + CalculatedFieldResult result = state.performCalculation(ctx.getEntityId(), ctx).get(); assertThat(result).isNotNull(); assertThat(result.getType()).isEqualTo(output.getType()); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/cf/configuration/GeofencingCalculatedFieldConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/cf/configuration/GeofencingCalculatedFieldConfiguration.java index d9b3621ceb..652d6b2182 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/cf/configuration/GeofencingCalculatedFieldConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/cf/configuration/GeofencingCalculatedFieldConfiguration.java @@ -41,7 +41,7 @@ public class GeofencingCalculatedFieldConfiguration extends BaseCalculatedFieldC ENTITY_ID_LONGITUDE_ARGUMENT_KEY ); - private boolean trackRelationToZones; + private boolean createRelationsWithMatchedZones; private String zoneRelationType; private EntitySearchDirection zoneRelationDirection; private Map geofencingZoneGroupConfigurations; @@ -52,7 +52,6 @@ public class GeofencingCalculatedFieldConfiguration extends BaseCalculatedFieldC } // TODO: update validate method in PE version. - // Add relation tracking configuration validation @Override public void validate() { if (arguments == null) { @@ -72,6 +71,19 @@ public class GeofencingCalculatedFieldConfiguration extends BaseCalculatedFieldC } validateZoneGroupAruguments(zoneGroupsArguments); validateZoneGroupConfigurations(zoneGroupsArguments); + validateZoneRelationsConfiguration(); + } + + private void validateZoneRelationsConfiguration() { + if (!createRelationsWithMatchedZones) { + return; + } + if (StringUtils.isBlank(zoneRelationType)) { + throw new IllegalArgumentException("Zone relation type must be specified when to maintain relations with matched zones!"); + } + if (zoneRelationDirection == null) { + throw new IllegalArgumentException("Zone relation direction must be specified to maintain relations with matched zones!"); + } } private void validateZoneGroupConfigurations(Map zoneGroupsArguments) { @@ -146,7 +158,7 @@ public class GeofencingCalculatedFieldConfiguration extends BaseCalculatedFieldC .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); } - private static ReferencedEntityKey validateAndGetRefEntityKey(Argument argument, String argumentKey) { + private ReferencedEntityKey validateAndGetRefEntityKey(Argument argument, String argumentKey) { ReferencedEntityKey refEntityKey = argument.getRefEntityKey(); if (refEntityKey == null || refEntityKey.getType() == null) { throw new IllegalArgumentException("Missing or invalid reference entity key for argument: " + argumentKey);