Browse Source

Merge pull request #12993 from irynamatveieva/cf-improvements

Cf improvements
pull/12999/head
Viacheslav Klimov 1 year ago
committed by GitHub
parent
commit
e61a4bba76
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 7
      application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldEntityActor.java
  2. 6
      application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldEntityMessageProcessor.java
  3. 7
      application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldManagerActor.java
  4. 20
      application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldManagerMessageProcessor.java
  5. 10
      application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java
  6. 9
      application/src/main/java/org/thingsboard/server/service/cf/ctx/state/CalculatedFieldCtx.java
  7. 2
      application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCalculatedFieldConsumerService.java
  8. 2
      common/queue/src/main/java/org/thingsboard/server/queue/provider/InMemoryMonolithQueueFactory.java
  9. 2
      common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaMonolithQueueFactory.java
  10. 12
      common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbRuleEngineQueueFactory.java
  11. 4
      common/queue/src/main/java/org/thingsboard/server/queue/provider/TbRuleEngineProducerProvider.java
  12. 4
      common/queue/src/main/java/org/thingsboard/server/queue/provider/TbRuleEngineQueueFactory.java
  13. 25
      common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbUtils.java
  14. 20
      common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbUtilsTest.java
  15. 8
      common/util/pom.xml
  16. 2
      common/util/src/main/java/org/thingsboard/common/util/geo/Coordinates.java
  17. 2
      common/util/src/main/java/org/thingsboard/common/util/geo/GeoUtil.java
  18. 2
      common/util/src/main/java/org/thingsboard/common/util/geo/Perimeter.java
  19. 2
      common/util/src/main/java/org/thingsboard/common/util/geo/PerimeterType.java
  20. 2
      common/util/src/main/java/org/thingsboard/common/util/geo/RangeUnit.java
  21. 8
      dao/src/main/java/org/thingsboard/server/dao/service/validator/CalculatedFieldDataValidator.java
  22. 41
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/cf/CalculatedFieldTest.java
  23. 8
      rule-engine/rule-engine-components/pom.xml
  24. 7
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/AbstractGeofencingNode.java
  25. 1
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingActionNodeConfiguration.java
  26. 2
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingFilterNodeConfiguration.java
  27. 26
      rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/geo/GeoUtilTest.java
  28. 3
      rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingFilterNodeTest.java
  29. 50
      ui-ngx/src/app/shared/models/ace/tbel-utils.models.ts

7
application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldEntityActor.java

@ -21,6 +21,7 @@ import org.thingsboard.server.actors.TbActorCtx;
import org.thingsboard.server.actors.TbActorException; import org.thingsboard.server.actors.TbActorException;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.msg.TbActorStopReason;
import org.thingsboard.server.common.msg.ToCalculatedFieldSystemMsg; import org.thingsboard.server.common.msg.ToCalculatedFieldSystemMsg;
import org.thingsboard.server.common.msg.cf.CalculatedFieldPartitionChangeMsg; import org.thingsboard.server.common.msg.cf.CalculatedFieldPartitionChangeMsg;
@ -47,6 +48,12 @@ public class CalculatedFieldEntityActor extends AbstractCalculatedFieldActor {
} }
} }
@Override
public void destroy(TbActorStopReason stopReason, Throwable cause) throws TbActorException {
log.debug("[{}] Stopping CF entity actor.", processor.tenantId);
processor.stop();
}
@Override @Override
protected boolean doProcessCfMsg(ToCalculatedFieldSystemMsg msg) throws CalculatedFieldException { protected boolean doProcessCfMsg(ToCalculatedFieldSystemMsg msg) throws CalculatedFieldException {
switch (msg.getMsgType()) { switch (msg.getMsgType()) {

6
application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldEntityMessageProcessor.java

@ -92,6 +92,12 @@ public class CalculatedFieldEntityMessageProcessor extends AbstractContextAwareM
this.ctx = ctx; this.ctx = ctx;
} }
public void stop() {
log.info("[{}][{}] Stopping entity actor.", tenantId, entityId);
states.clear();
ctx.stop(ctx.getSelf());
}
public void process(CalculatedFieldPartitionChangeMsg msg) { public void process(CalculatedFieldPartitionChangeMsg msg) {
if (!systemContext.getPartitionService().resolve(ServiceType.TB_RULE_ENGINE, DataConstants.CF_QUEUE_NAME, tenantId, entityId).isMyPartition()) { if (!systemContext.getPartitionService().resolve(ServiceType.TB_RULE_ENGINE, DataConstants.CF_QUEUE_NAME, tenantId, entityId).isMyPartition()) {
log.info("[{}] Stopping entity actor due to change partition event.", entityId); log.info("[{}] Stopping entity actor due to change partition event.", entityId);

7
application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldManagerActor.java

@ -20,6 +20,7 @@ import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.TbActorCtx; import org.thingsboard.server.actors.TbActorCtx;
import org.thingsboard.server.actors.TbActorException; import org.thingsboard.server.actors.TbActorException;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.msg.TbActorStopReason;
import org.thingsboard.server.common.msg.ToCalculatedFieldSystemMsg; import org.thingsboard.server.common.msg.ToCalculatedFieldSystemMsg;
import org.thingsboard.server.common.msg.cf.CalculatedFieldEntityLifecycleMsg; import org.thingsboard.server.common.msg.cf.CalculatedFieldEntityLifecycleMsg;
import org.thingsboard.server.common.msg.cf.CalculatedFieldInitMsg; import org.thingsboard.server.common.msg.cf.CalculatedFieldInitMsg;
@ -52,6 +53,12 @@ public class CalculatedFieldManagerActor extends AbstractCalculatedFieldActor {
} }
} }
@Override
public void destroy(TbActorStopReason stopReason, Throwable cause) throws TbActorException {
log.debug("[{}] Stopping CF manager actor.", processor.tenantId);
processor.stop();
}
@Override @Override
protected boolean doProcessCfMsg(ToCalculatedFieldSystemMsg msg) throws CalculatedFieldException { protected boolean doProcessCfMsg(ToCalculatedFieldSystemMsg msg) throws CalculatedFieldException {
switch (msg.getMsgType()) { switch (msg.getMsgType()) {

20
application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldManagerMessageProcessor.java

@ -56,7 +56,6 @@ import java.util.concurrent.CopyOnWriteArrayList;
import static org.thingsboard.server.utils.CalculatedFieldUtils.fromProto; import static org.thingsboard.server.utils.CalculatedFieldUtils.fromProto;
/** /**
* @author Andrew Shvayka * @author Andrew Shvayka
*/ */
@ -92,8 +91,17 @@ public class CalculatedFieldManagerMessageProcessor extends AbstractContextAware
this.ctx = ctx; this.ctx = ctx;
} }
public void stop() {
log.info("[{}] Stopping CF manager actor.", tenantId);
calculatedFields.values().forEach(CalculatedFieldCtx::stop);
calculatedFields.clear();
entityIdCalculatedFields.clear();
entityIdCalculatedFieldLinks.clear();
ctx.stop(ctx.getSelf());
}
public void onFieldInitMsg(CalculatedFieldInitMsg msg) throws CalculatedFieldException { public void onFieldInitMsg(CalculatedFieldInitMsg msg) throws CalculatedFieldException {
log.info("[{}] Processing CF init message.", msg.getCf().getId()); log.debug("[{}] Processing CF init message.", msg.getCf().getId());
var cf = msg.getCf(); var cf = msg.getCf();
var cfCtx = new CalculatedFieldCtx(cf, systemContext.getTbelInvokeService(), systemContext.getApiLimitService()); var cfCtx = new CalculatedFieldCtx(cf, systemContext.getTbelInvokeService(), systemContext.getApiLimitService());
try { try {
@ -109,7 +117,7 @@ public class CalculatedFieldManagerMessageProcessor extends AbstractContextAware
} }
public void onLinkInitMsg(CalculatedFieldLinkInitMsg msg) { public void onLinkInitMsg(CalculatedFieldLinkInitMsg msg) {
log.info("[{}] Processing CF link init message for entity [{}].", msg.getLink().getCalculatedFieldId(), msg.getLink().getEntityId()); log.debug("[{}] Processing CF link init message for entity [{}].", msg.getLink().getCalculatedFieldId(), msg.getLink().getEntityId());
var link = msg.getLink(); var link = msg.getLink();
// We use copy on write lists to safely pass the reference to another actor for the iteration. // We use copy on write lists to safely pass the reference to another actor for the iteration.
// Alternative approach would be to use any list but avoid modifications to the list (change the complete map value instead) // Alternative approach would be to use any list but avoid modifications to the list (change the complete map value instead)
@ -122,7 +130,9 @@ public class CalculatedFieldManagerMessageProcessor extends AbstractContextAware
var calculatedField = calculatedFields.get(cfId); var calculatedField = calculatedFields.get(cfId);
if (calculatedField != null) { if (calculatedField != null) {
msg.getState().setRequiredArguments(calculatedField.getArgNames()); if (msg.getState() != null) {
msg.getState().setRequiredArguments(calculatedField.getArgNames());
}
log.debug("Pushing CF state restore msg to specific actor [{}]", msg.getId().entityId()); log.debug("Pushing CF state restore msg to specific actor [{}]", msg.getId().entityId());
getOrCreateActor(msg.getId().entityId()).tell(msg); getOrCreateActor(msg.getId().entityId()).tell(msg);
} else { } else {
@ -131,7 +141,7 @@ public class CalculatedFieldManagerMessageProcessor extends AbstractContextAware
} }
public void onEntityLifecycleMsg(CalculatedFieldEntityLifecycleMsg msg) throws CalculatedFieldException { public void onEntityLifecycleMsg(CalculatedFieldEntityLifecycleMsg msg) throws CalculatedFieldException {
log.info("Processing entity lifecycle event: [{}] for entity: [{}]", msg.getData().getEvent(), msg.getData().getEntityId()); log.debug("Processing entity lifecycle event: [{}] for entity: [{}]", msg.getData().getEvent(), msg.getData().getEntityId());
var entityType = msg.getData().getEntityId().getEntityType(); var entityType = msg.getData().getEntityId().getEntityType();
var event = msg.getData().getEvent(); var event = msg.getData().getEvent();
switch (entityType) { switch (entityType) {

10
application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java

@ -28,6 +28,7 @@ import org.thingsboard.server.actors.TbEntityActorId;
import org.thingsboard.server.actors.TbEntityTypeActorIdPredicate; import org.thingsboard.server.actors.TbEntityTypeActorIdPredicate;
import org.thingsboard.server.actors.TbStringActorId; import org.thingsboard.server.actors.TbStringActorId;
import org.thingsboard.server.actors.calculatedField.CalculatedFieldManagerActorCreator; import org.thingsboard.server.actors.calculatedField.CalculatedFieldManagerActorCreator;
import org.thingsboard.server.actors.calculatedField.CalculatedFieldStateRestoreMsg;
import org.thingsboard.server.actors.device.DeviceActorCreator; import org.thingsboard.server.actors.device.DeviceActorCreator;
import org.thingsboard.server.actors.ruleChain.RuleChainManagerActor; import org.thingsboard.server.actors.ruleChain.RuleChainManagerActor;
import org.thingsboard.server.actors.service.ContextBasedCreator; import org.thingsboard.server.actors.service.ContextBasedCreator;
@ -126,6 +127,9 @@ public class TenantActor extends RuleChainManagerActor {
@Override @Override
public void destroy(TbActorStopReason stopReason, Throwable cause) { public void destroy(TbActorStopReason stopReason, Throwable cause) {
log.info("[{}] Stopping tenant actor.", tenantId); log.info("[{}] Stopping tenant actor.", tenantId);
if (cfActor != null) {
ctx.stop(cfActor.getActorId());
}
} }
@Override @Override
@ -190,7 +194,11 @@ public class TenantActor extends RuleChainManagerActor {
private void onToCalculatedFieldSystemActorMsg(ToCalculatedFieldSystemMsg msg, boolean priority) { private void onToCalculatedFieldSystemActorMsg(ToCalculatedFieldSystemMsg msg, boolean priority) {
if (cfActor == null) { if (cfActor == null) {
log.warn("[{}] CF Actor is not initialized.", tenantId); if (msg instanceof CalculatedFieldStateRestoreMsg) {
log.warn("[{}] CF Actor is not initialized. ToCalculatedFieldSystemMsg: [{}]", tenantId, msg);
} else {
log.debug("[{}] CF Actor is not initialized. ToCalculatedFieldSystemMsg: [{}]", tenantId, msg);
}
return; return;
} }
if (priority) { if (priority) {

9
application/src/main/java/org/thingsboard/server/service/cf/ctx/state/CalculatedFieldCtx.java

@ -122,6 +122,15 @@ public class CalculatedFieldCtx {
} }
} }
public void stop() {
if (calculatedFieldScriptEngine != null) {
calculatedFieldScriptEngine.destroy();
}
if (customExpression != null) {
customExpression.remove();
}
}
private CalculatedFieldScriptEngine initEngine(TenantId tenantId, String expression, TbelInvokeService tbelInvokeService) { private CalculatedFieldScriptEngine initEngine(TenantId tenantId, String expression, TbelInvokeService tbelInvokeService) {
if (tbelInvokeService == null) { if (tbelInvokeService == null) {
throw new IllegalArgumentException("TBEL script engine is disabled!"); throw new IllegalArgumentException("TBEL script engine is disabled!");

2
application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCalculatedFieldConsumerService.java

@ -210,7 +210,7 @@ public class DefaultTbCalculatedFieldConsumerService extends AbstractConsumerSer
@Override @Override
protected TbQueueConsumer<TbProtoQueueMsg<ToCalculatedFieldNotificationMsg>> createNotificationsConsumer() { protected TbQueueConsumer<TbProtoQueueMsg<ToCalculatedFieldNotificationMsg>> createNotificationsConsumer() {
return queueFactory.createToCalculatedFieldNotificationsMsgConsumer(); return queueFactory.createToCalculatedFieldNotificationMsgConsumer();
} }
@Override @Override

2
common/queue/src/main/java/org/thingsboard/server/queue/provider/InMemoryMonolithQueueFactory.java

@ -149,7 +149,7 @@ public class InMemoryMonolithQueueFactory implements TbCoreQueueFactory, TbRuleE
} }
@Override @Override
public TbQueueConsumer<TbProtoQueueMsg<TransportProtos.ToCalculatedFieldNotificationMsg>> createToCalculatedFieldNotificationsMsgConsumer() { public TbQueueConsumer<TbProtoQueueMsg<TransportProtos.ToCalculatedFieldNotificationMsg>> createToCalculatedFieldNotificationMsgConsumer() {
return new InMemoryTbQueueConsumer<>(storage, topicService.getCalculatedFieldNotificationsTopic(serviceInfoProvider.getServiceId()).getFullTopicName()); return new InMemoryTbQueueConsumer<>(storage, topicService.getCalculatedFieldNotificationsTopic(serviceInfoProvider.getServiceId()).getFullTopicName());
} }

2
common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaMonolithQueueFactory.java

@ -542,7 +542,7 @@ public class KafkaMonolithQueueFactory implements TbCoreQueueFactory, TbRuleEngi
} }
@Override @Override
public TbQueueConsumer<TbProtoQueueMsg<ToCalculatedFieldNotificationMsg>> createToCalculatedFieldNotificationsMsgConsumer() { public TbQueueConsumer<TbProtoQueueMsg<ToCalculatedFieldNotificationMsg>> createToCalculatedFieldNotificationMsgConsumer() {
TbKafkaConsumerTemplate.TbKafkaConsumerTemplateBuilder<TbProtoQueueMsg<ToCalculatedFieldNotificationMsg>> consumerBuilder = TbKafkaConsumerTemplate.builder(); TbKafkaConsumerTemplate.TbKafkaConsumerTemplateBuilder<TbProtoQueueMsg<ToCalculatedFieldNotificationMsg>> consumerBuilder = TbKafkaConsumerTemplate.builder();
consumerBuilder.settings(kafkaSettings); consumerBuilder.settings(kafkaSettings);
consumerBuilder.topic(topicService.getCalculatedFieldNotificationsTopic(serviceInfoProvider.getServiceId()).getFullTopicName()); consumerBuilder.topic(topicService.getCalculatedFieldNotificationsTopic(serviceInfoProvider.getServiceId()).getFullTopicName());

12
common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbRuleEngineQueueFactory.java

@ -337,7 +337,7 @@ public class KafkaTbRuleEngineQueueFactory implements TbRuleEngineQueueFactory {
} }
@Override @Override
public TbQueueConsumer<TbProtoQueueMsg<ToCalculatedFieldNotificationMsg>> createToCalculatedFieldNotificationsMsgConsumer() { public TbQueueConsumer<TbProtoQueueMsg<ToCalculatedFieldNotificationMsg>> createToCalculatedFieldNotificationMsgConsumer() {
TbKafkaConsumerTemplate.TbKafkaConsumerTemplateBuilder<TbProtoQueueMsg<ToCalculatedFieldNotificationMsg>> consumerBuilder = TbKafkaConsumerTemplate.builder(); TbKafkaConsumerTemplate.TbKafkaConsumerTemplateBuilder<TbProtoQueueMsg<ToCalculatedFieldNotificationMsg>> consumerBuilder = TbKafkaConsumerTemplate.builder();
consumerBuilder.settings(kafkaSettings); consumerBuilder.settings(kafkaSettings);
consumerBuilder.topic(topicService.getCalculatedFieldNotificationsTopic(serviceInfoProvider.getServiceId()).getFullTopicName()); consumerBuilder.topic(topicService.getCalculatedFieldNotificationsTopic(serviceInfoProvider.getServiceId()).getFullTopicName());
@ -349,6 +349,16 @@ public class KafkaTbRuleEngineQueueFactory implements TbRuleEngineQueueFactory {
return consumerBuilder.build(); return consumerBuilder.build();
} }
@Override
public TbQueueProducer<TbProtoQueueMsg<ToCalculatedFieldNotificationMsg>> createToCalculatedFieldNotificationMsgProducer() {
TbKafkaProducerTemplate.TbKafkaProducerTemplateBuilder<TbProtoQueueMsg<ToCalculatedFieldNotificationMsg>> requestBuilder = TbKafkaProducerTemplate.builder();
requestBuilder.settings(kafkaSettings);
requestBuilder.clientId("tb-calculated-field-notifications-producer-" + serviceInfoProvider.getServiceId());
requestBuilder.defaultTopic(topicService.getCalculatedFieldNotificationsTopic(serviceInfoProvider.getServiceId()).getFullTopicName());
requestBuilder.admin(notificationAdmin);
return requestBuilder.build();
}
@Override @Override
public TbQueueConsumer<TbProtoQueueMsg<CalculatedFieldStateProto>> createCalculatedFieldStateConsumer() { public TbQueueConsumer<TbProtoQueueMsg<CalculatedFieldStateProto>> createCalculatedFieldStateConsumer() {
return TbKafkaConsumerTemplate.<TbProtoQueueMsg<CalculatedFieldStateProto>>builder() return TbKafkaConsumerTemplate.<TbProtoQueueMsg<CalculatedFieldStateProto>>builder()

4
common/queue/src/main/java/org/thingsboard/server/queue/provider/TbRuleEngineProducerProvider.java

@ -50,6 +50,7 @@ public class TbRuleEngineProducerProvider implements TbQueueProducerProvider {
private TbQueueProducer<TbProtoQueueMsg<ToEdgeNotificationMsg>> toEdgeNotifications; private TbQueueProducer<TbProtoQueueMsg<ToEdgeNotificationMsg>> toEdgeNotifications;
private TbQueueProducer<TbProtoQueueMsg<ToEdgeEventNotificationMsg>> toEdgeEvents; private TbQueueProducer<TbProtoQueueMsg<ToEdgeEventNotificationMsg>> toEdgeEvents;
private TbQueueProducer<TbProtoQueueMsg<ToCalculatedFieldMsg>> toCalculatedFields; private TbQueueProducer<TbProtoQueueMsg<ToCalculatedFieldMsg>> toCalculatedFields;
private TbQueueProducer<TbProtoQueueMsg<ToCalculatedFieldNotificationMsg>> toCalculatedFieldNotifications;
public TbRuleEngineProducerProvider(TbRuleEngineQueueFactory tbQueueProvider) { public TbRuleEngineProducerProvider(TbRuleEngineQueueFactory tbQueueProvider) {
this.tbQueueProvider = tbQueueProvider; this.tbQueueProvider = tbQueueProvider;
@ -68,6 +69,7 @@ public class TbRuleEngineProducerProvider implements TbQueueProducerProvider {
this.toEdgeNotifications = tbQueueProvider.createEdgeNotificationsMsgProducer(); this.toEdgeNotifications = tbQueueProvider.createEdgeNotificationsMsgProducer();
this.toEdgeEvents = tbQueueProvider.createEdgeEventMsgProducer(); this.toEdgeEvents = tbQueueProvider.createEdgeEventMsgProducer();
this.toCalculatedFields = tbQueueProvider.createToCalculatedFieldMsgProducer(); this.toCalculatedFields = tbQueueProvider.createToCalculatedFieldMsgProducer();
this.toCalculatedFieldNotifications = tbQueueProvider.createToCalculatedFieldNotificationMsgProducer();
} }
@Override @Override
@ -132,7 +134,7 @@ public class TbRuleEngineProducerProvider implements TbQueueProducerProvider {
@Override @Override
public TbQueueProducer<TbProtoQueueMsg<ToCalculatedFieldNotificationMsg>> getCalculatedFieldsNotificationsMsgProducer() { public TbQueueProducer<TbProtoQueueMsg<ToCalculatedFieldNotificationMsg>> getCalculatedFieldsNotificationsMsgProducer() {
throw new RuntimeException("Not Implemented! Should not be used by Rule Engine Service!"); return toCalculatedFieldNotifications;
} }
} }

4
common/queue/src/main/java/org/thingsboard/server/queue/provider/TbRuleEngineQueueFactory.java

@ -127,7 +127,9 @@ public interface TbRuleEngineQueueFactory extends TbUsageStatsClientQueueFactory
TbQueueProducer<TbProtoQueueMsg<ToCalculatedFieldMsg>> createToCalculatedFieldMsgProducer(); TbQueueProducer<TbProtoQueueMsg<ToCalculatedFieldMsg>> createToCalculatedFieldMsgProducer();
TbQueueConsumer<TbProtoQueueMsg<ToCalculatedFieldNotificationMsg>> createToCalculatedFieldNotificationsMsgConsumer(); TbQueueConsumer<TbProtoQueueMsg<ToCalculatedFieldNotificationMsg>> createToCalculatedFieldNotificationMsgConsumer();
TbQueueProducer<TbProtoQueueMsg<ToCalculatedFieldNotificationMsg>> createToCalculatedFieldNotificationMsgProducer();
TbQueueConsumer<TbProtoQueueMsg<CalculatedFieldStateProto>> createCalculatedFieldStateConsumer(); TbQueueConsumer<TbProtoQueueMsg<CalculatedFieldStateProto>> createCalculatedFieldStateConsumer();

25
common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbUtils.java

@ -15,6 +15,7 @@
*/ */
package org.thingsboard.script.api.tbel; package org.thingsboard.script.api.tbel;
import com.fasterxml.jackson.databind.JsonNode;
import com.google.common.primitives.Bytes; import com.google.common.primitives.Bytes;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.ArrayUtils;
@ -23,6 +24,10 @@ import org.mvel2.ParserConfiguration;
import org.mvel2.execution.ExecutionArrayList; import org.mvel2.execution.ExecutionArrayList;
import org.mvel2.execution.ExecutionHashMap; import org.mvel2.execution.ExecutionHashMap;
import org.mvel2.util.MethodStub; import org.mvel2.util.MethodStub;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.common.util.geo.Coordinates;
import org.thingsboard.common.util.geo.GeoUtil;
import org.thingsboard.common.util.geo.RangeUnit;
import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.StringUtils;
import java.io.IOException; import java.io.IOException;
@ -371,6 +376,10 @@ public class TbUtils {
byte[].class, int.class))); byte[].class, int.class)));
parserConfig.addImport("parseBinaryArrayToInt", new MethodStub(TbUtils.class.getMethod("parseBinaryArrayToInt", parserConfig.addImport("parseBinaryArrayToInt", new MethodStub(TbUtils.class.getMethod("parseBinaryArrayToInt",
byte[].class, int.class, int.class))); byte[].class, int.class, int.class)));
parserConfig.addImport("isInsidePolygon", new MethodStub(TbUtils.class.getMethod("isInsidePolygon",
double.class, double.class, String.class)));
parserConfig.addImport("isInsideCircle", new MethodStub(TbUtils.class.getMethod("isInsideCircle",
double.class, double.class, String.class)));
} }
public static String btoa(String input) { public static String btoa(String input) {
@ -1437,6 +1446,22 @@ public class TbUtils {
return result; return result;
} }
public static boolean isInsidePolygon(double latitude, double longitude, String perimeter) {
return GeoUtil.contains(perimeter, new Coordinates(latitude, longitude));
}
public static boolean isInsideCircle(double latitude, double longitude, String perimeter) {
JsonNode perimeterJson = JacksonUtil.toJsonNode(perimeter);
double centerLatitude = Double.parseDouble(perimeterJson.get("latitude").asText());
double centerLongitude = Double.parseDouble(perimeterJson.get("longitude").asText());
double range = Double.parseDouble(perimeterJson.get("radius").asText());
RangeUnit rangeUnit = perimeterJson.has("radiusUnit") ? RangeUnit.valueOf(perimeterJson.get("radiusUnit").asText()) : RangeUnit.METER;
Coordinates entityCoordinates = new Coordinates(latitude, longitude);
Coordinates perimeterCoordinates = new Coordinates(centerLatitude, centerLongitude);
return range > GeoUtil.distance(entityCoordinates, perimeterCoordinates, rangeUnit);
}
private static byte isValidIntegerToByte(Integer val) { private static byte isValidIntegerToByte(Integer val) {
if (val > 255 || val < -128) { if (val > 255 || val < -128) {
throw new NumberFormatException("The value '" + val + "' could not be correctly converted to a byte. " + throw new NumberFormatException("The value '" + val + "' could not be correctly converted to a byte. " +

20
common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbUtilsTest.java

@ -1150,6 +1150,26 @@ public class TbUtilsTest {
Assertions.assertTrue(TbUtils.isNaN(Double.NaN)); Assertions.assertTrue(TbUtils.isNaN(Double.NaN));
} }
@Test
public void isInsidePolygon() {
// outside the polygon
String perimeter = "[[[50.75581142688204,29.097910166341073],[50.16785158177623,29.35066098977171],[50.164329922384674,29.773743889862114],[50.16785158177623,30.801230932938843],[50.459245308833495,30.92760634465418],[50.486522489629564,30.68548421850448],[50.703612031034005,30.872660513473573]],[[50.606017492632766,29.36165015600782],[50.54317104075835,29.762754723626013],[50.41021974600505,29.455058069014804]]]";
Assertions.assertFalse(TbUtils.isInsidePolygon(50.50869555168039, 30.80123093293884, perimeter));
// inside the polygon
Assertions.assertTrue(TbUtils.isInsidePolygon(50.50520628167696, 30.339685951022016, perimeter));
// inside the hole
Assertions.assertFalse(TbUtils.isInsidePolygon(50.52265651287081, 29.488025567723156, perimeter));
}
@Test
public void isInsideCircle() {
// outside the circle
String perimeter = "{\"latitude\":50.32254778825905,\"longitude\":28.207787701215757,\"radius\":47477.33130420423}";
Assertions.assertFalse(TbUtils.isInsideCircle(50.81490715736681, 28.05943395702824, perimeter));
// inside the circle
Assertions.assertTrue(TbUtils.isInsideCircle(50.599397971892444, 28.086906872618542, perimeter));
}
private static List<Byte> toList(byte[] data) { private static List<Byte> toList(byte[] data) {
List<Byte> result = new ArrayList<>(data.length); List<Byte> result = new ArrayList<>(data.length);
for (Byte b : data) { for (Byte b : data) {

8
common/util/pom.xml

@ -103,6 +103,14 @@
<groupId>com.fasterxml.jackson.datatype</groupId> <groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jdk8</artifactId> <artifactId>jackson-datatype-jdk8</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.locationtech.spatial4j</groupId>
<artifactId>spatial4j</artifactId>
</dependency>
<dependency>
<groupId>org.locationtech.jts</groupId>
<artifactId>jts-core</artifactId>
</dependency>
</dependencies> </dependencies>
<build> <build>

2
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/Coordinates.java → common/util/src/main/java/org/thingsboard/common/util/geo/Coordinates.java

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.thingsboard.rule.engine.geo; package org.thingsboard.common.util.geo;
import lombok.Data; import lombok.Data;

2
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/GeoUtil.java → common/util/src/main/java/org/thingsboard/common/util/geo/GeoUtil.java

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.thingsboard.rule.engine.geo; package org.thingsboard.common.util.geo;
import com.google.gson.JsonArray; import com.google.gson.JsonArray;
import com.google.gson.JsonElement; import com.google.gson.JsonElement;

2
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/Perimeter.java → common/util/src/main/java/org/thingsboard/common/util/geo/Perimeter.java

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.thingsboard.rule.engine.geo; package org.thingsboard.common.util.geo;
import lombok.Data; import lombok.Data;

2
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/PerimeterType.java → common/util/src/main/java/org/thingsboard/common/util/geo/PerimeterType.java

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.thingsboard.rule.engine.geo; package org.thingsboard.common.util.geo;
public enum PerimeterType { public enum PerimeterType {
CIRCLE, POLYGON CIRCLE, POLYGON

2
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/RangeUnit.java → common/util/src/main/java/org/thingsboard/common/util/geo/RangeUnit.java

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.thingsboard.rule.engine.geo; package org.thingsboard.common.util.geo;
public enum RangeUnit { public enum RangeUnit {
METER(1000.0), KILOMETER(1.0), FOOT(3280.84), MILE(0.62137), NAUTICAL_MILE(0.539957); METER(1000.0), KILOMETER(1.0), FOOT(3280.84), MILE(0.62137), NAUTICAL_MILE(0.539957);

8
dao/src/main/java/org/thingsboard/server/dao/service/validator/CalculatedFieldDataValidator.java

@ -39,6 +39,7 @@ public class CalculatedFieldDataValidator extends DataValidator<CalculatedField>
protected void validateCreate(TenantId tenantId, CalculatedField calculatedField) { protected void validateCreate(TenantId tenantId, CalculatedField calculatedField) {
validateNumberOfCFsPerEntity(tenantId, calculatedField.getEntityId()); validateNumberOfCFsPerEntity(tenantId, calculatedField.getEntityId());
validateNumberOfArgumentsPerCF(tenantId, calculatedField); validateNumberOfArgumentsPerCF(tenantId, calculatedField);
validateArgumentNames(calculatedField);
} }
@Override @Override
@ -48,6 +49,7 @@ public class CalculatedFieldDataValidator extends DataValidator<CalculatedField>
throw new DataValidationException("Can't update non existing calculated field!"); throw new DataValidationException("Can't update non existing calculated field!");
} }
validateNumberOfArgumentsPerCF(tenantId, calculatedField); validateNumberOfArgumentsPerCF(tenantId, calculatedField);
validateArgumentNames(calculatedField);
return old; return old;
} }
@ -71,4 +73,10 @@ public class CalculatedFieldDataValidator extends DataValidator<CalculatedField>
} }
} }
private void validateArgumentNames(CalculatedField calculatedField) {
if (calculatedField.getConfiguration().getArguments().containsKey("ctx")) {
throw new DataValidationException("Argument name 'ctx' is reserved and cannot be used.");
}
}
} }

41
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/cf/CalculatedFieldTest.java

@ -34,6 +34,7 @@ import org.thingsboard.server.common.data.cf.configuration.ArgumentType;
import org.thingsboard.server.common.data.cf.configuration.Output; import org.thingsboard.server.common.data.cf.configuration.Output;
import org.thingsboard.server.common.data.cf.configuration.OutputType; import org.thingsboard.server.common.data.cf.configuration.OutputType;
import org.thingsboard.server.common.data.cf.configuration.ReferencedEntityKey; import org.thingsboard.server.common.data.cf.configuration.ReferencedEntityKey;
import org.thingsboard.server.common.data.cf.configuration.ScriptCalculatedFieldConfiguration;
import org.thingsboard.server.common.data.cf.configuration.SimpleCalculatedFieldConfiguration; import org.thingsboard.server.common.data.cf.configuration.SimpleCalculatedFieldConfiguration;
import org.thingsboard.server.common.data.debug.DebugSettings; import org.thingsboard.server.common.data.debug.DebugSettings;
import org.thingsboard.server.common.data.device.data.DefaultDeviceConfiguration; import org.thingsboard.server.common.data.device.data.DefaultDeviceConfiguration;
@ -73,7 +74,7 @@ public class CalculatedFieldTest extends AbstractContainerTest {
" var airDensity = pressure / (287.05 * temperatureK);\n" + " var airDensity = pressure / (287.05 * temperatureK);\n" +
"\n" + "\n" +
" return {\n" + " return {\n" +
" \"airDensity\": airDensity\n" + " \"airDensity\": toFixed(airDensity, 2)\n" +
" };"; " };";
private TenantId tenantId; private TenantId tenantId;
@ -280,6 +281,34 @@ public class CalculatedFieldTest extends AbstractContainerTest {
testRestClient.deleteCalculatedFieldIfExists(savedCalculatedField.getId()); testRestClient.deleteCalculatedFieldIfExists(savedCalculatedField.getId());
} }
@Test
public void testEntityIdIsProfileAndRefEntityIsCommon() {
// login tenant admin
testRestClient.getAndSetUserToken(tenantAdminId);
CalculatedField savedCalculatedField = createScriptCalculatedField(deviceProfileId);
await().alias("create CF -> perform initial calculation for device by profile").atMost(TIMEOUT, TimeUnit.SECONDS)
.pollInterval(POLL_INTERVAL, TimeUnit.SECONDS)
.untilAsserted(() -> {
JsonNode airDensity = testRestClient.getLatestTelemetry(device.getId());
assertThat(airDensity).isNotNull();
assertThat(airDensity.get("airDensity").get(0).get("value").asText()).isEqualTo("1.05");
});
testRestClient.postTelemetryAttribute(asset.getId(), DataConstants.SERVER_SCOPE, JacksonUtil.toJsonNode("{\"altitude\":1531}"));
await().alias("create CF -> update telemetry for common entity").atMost(TIMEOUT, TimeUnit.SECONDS)
.pollInterval(POLL_INTERVAL, TimeUnit.SECONDS)
.untilAsserted(() -> {
JsonNode airDensity = testRestClient.getLatestTelemetry(device.getId());
assertThat(airDensity).isNotNull();
assertThat(airDensity.get("airDensity").get(0).get("value").asText()).isEqualTo("0.99");
});
testRestClient.deleteCalculatedFieldIfExists(savedCalculatedField.getId());
}
private CalculatedField createSimpleCalculatedField() { private CalculatedField createSimpleCalculatedField() {
return createSimpleCalculatedField(device.getId()); return createSimpleCalculatedField(device.getId());
} }
@ -323,19 +352,21 @@ public class CalculatedFieldTest extends AbstractContainerTest {
calculatedField.setName("Air density" + RandomStringUtils.randomAlphabetic(5)); calculatedField.setName("Air density" + RandomStringUtils.randomAlphabetic(5));
calculatedField.setDebugSettings(DebugSettings.all()); calculatedField.setDebugSettings(DebugSettings.all());
SimpleCalculatedFieldConfiguration config = new SimpleCalculatedFieldConfiguration(); ScriptCalculatedFieldConfiguration config = new ScriptCalculatedFieldConfiguration();
Argument argument1 = new Argument(); Argument argument1 = new Argument();
argument1.setRefEntityId(asset.getId()); argument1.setRefEntityId(asset.getId());
ReferencedEntityKey refEntityKey1 = new ReferencedEntityKey("altitude", ArgumentType.ATTRIBUTE, AttributeScope.SERVER_SCOPE); ReferencedEntityKey refEntityKey1 = new ReferencedEntityKey("altitude", ArgumentType.ATTRIBUTE, AttributeScope.SERVER_SCOPE);
argument1.setRefEntityKey(refEntityKey1); argument1.setRefEntityKey(refEntityKey1);
config.setArguments(Map.of("altitude", argument1));
Argument argument2 = new Argument(); Argument argument2 = new Argument();
ReferencedEntityKey refEntityKey2 = new ReferencedEntityKey("temperatureInF", ArgumentType.TS_ROLLING, null); ReferencedEntityKey refEntityKey2 = new ReferencedEntityKey("temperatureInF", ArgumentType.TS_ROLLING, null);
argument2.setTimeWindow(30000L);
argument2.setLimit(5);
argument2.setRefEntityKey(refEntityKey2); argument2.setRefEntityKey(refEntityKey2);
config.setArguments(Map.of("temperature", argument2));
config.setExpression("return {\"airDensity\": 5};"); config.setArguments(Map.of("altitude", argument1, "temperature", argument2));
config.setExpression(exampleScript);
Output output = new Output(); Output output = new Output();
output.setType(OutputType.TIME_SERIES); output.setType(OutputType.TIME_SERIES);

8
rule-engine/rule-engine-components/pom.xml

@ -120,14 +120,6 @@
<groupId>org.bouncycastle</groupId> <groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk18on</artifactId> <artifactId>bcpkix-jdk18on</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.locationtech.spatial4j</groupId>
<artifactId>spatial4j</artifactId>
</dependency>
<dependency>
<groupId>org.locationtech.jts</groupId>
<artifactId>jts-core</artifactId>
</dependency>
<dependency> <dependency>
<groupId>com.sun.mail</groupId> <groupId>com.sun.mail</groupId>
<artifactId>jakarta.mail</artifactId> <artifactId>jakarta.mail</artifactId>

7
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/AbstractGeofencingNode.java

@ -21,6 +21,11 @@ import com.google.gson.JsonParser;
import org.locationtech.spatial4j.context.jts.JtsSpatialContext; import org.locationtech.spatial4j.context.jts.JtsSpatialContext;
import org.locationtech.spatial4j.context.jts.JtsSpatialContextFactory; import org.locationtech.spatial4j.context.jts.JtsSpatialContextFactory;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.common.util.geo.Coordinates;
import org.thingsboard.common.util.geo.GeoUtil;
import org.thingsboard.common.util.geo.Perimeter;
import org.thingsboard.common.util.geo.PerimeterType;
import org.thingsboard.common.util.geo.RangeUnit;
import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbContext;
import org.thingsboard.rule.engine.api.TbNode; import org.thingsboard.rule.engine.api.TbNode;
import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeConfiguration;
@ -74,7 +79,7 @@ public abstract class AbstractGeofencingNode<T extends TbGpsGeofencingFilterNode
} else if (perimeter.getPerimeterType() == PerimeterType.POLYGON) { } else if (perimeter.getPerimeterType() == PerimeterType.POLYGON) {
return GeoUtil.contains(perimeter.getPolygonsDefinition(), new Coordinates(latitude, longitude)); return GeoUtil.contains(perimeter.getPolygonsDefinition(), new Coordinates(latitude, longitude));
} else { } else {
throw new TbNodeException("Unsupported perimeter type: " + perimeter.getPerimeterType() + "!"); throw new TbNodeException("Unsupported perimeter type: " + perimeter.getPerimeterType() + "!");
} }
} }

1
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingActionNodeConfiguration.java

@ -16,6 +16,7 @@
package org.thingsboard.rule.engine.geo; package org.thingsboard.rule.engine.geo;
import lombok.Data; import lombok.Data;
import org.thingsboard.common.util.geo.PerimeterType;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;

2
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingFilterNodeConfiguration.java

@ -16,6 +16,8 @@
package org.thingsboard.rule.engine.geo; package org.thingsboard.rule.engine.geo;
import lombok.Data; import lombok.Data;
import org.thingsboard.common.util.geo.PerimeterType;
import org.thingsboard.common.util.geo.RangeUnit;
import org.thingsboard.rule.engine.api.NodeConfiguration; import org.thingsboard.rule.engine.api.NodeConfiguration;
/** /**

26
rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/geo/GeoUtilTest.java

@ -19,6 +19,8 @@ import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoExtension;
import org.thingsboard.common.util.geo.Coordinates;
import org.thingsboard.common.util.geo.GeoUtil;
@ExtendWith(MockitoExtension.class) @ExtendWith(MockitoExtension.class)
public class GeoUtilTest { public class GeoUtilTest {
@ -31,18 +33,18 @@ public class GeoUtilTest {
public static final String SELF_INTERSECTING_WITH_HOLES = "[[[[47.42893833699058,27.17895466200852],[47.41407351681856,37.46095466320853],[49.63741575793274,32.47858934221699]],[[47.84342032696093,29.20045703244998],[49.124803688667576,32.38611942598416],[47.858163521459254,34.714948486085035]]],[[[51.659197648757306,27.947907653551276],[49.63741575793274,32.47858934221699],[51.71367987390804,37.46095466320853]],[[51.20718653775245,30.738363015535448],[51.317169097567344,34.58312797324915],[50.1351109330126,32.51793993882006]]]]"; public static final String SELF_INTERSECTING_WITH_HOLES = "[[[[47.42893833699058,27.17895466200852],[47.41407351681856,37.46095466320853],[49.63741575793274,32.47858934221699]],[[47.84342032696093,29.20045703244998],[49.124803688667576,32.38611942598416],[47.858163521459254,34.714948486085035]]],[[[51.659197648757306,27.947907653551276],[49.63741575793274,32.47858934221699],[51.71367987390804,37.46095466320853]],[[51.20718653775245,30.738363015535448],[51.317169097567344,34.58312797324915],[50.1351109330126,32.51793993882006]]]]";
public static final Coordinates POINT_INSIDE_SIMPLE_RECT_CENTER = new Coordinates(48.37082198780869, 32.673342414527355); public static final Coordinates POINT_INSIDE_SIMPLE_RECT_CENTER = new Coordinates(48.37082198780869, 32.673342414527355);
public static final Coordinates POINT_INSIDE_SIMPLE_RECT_NEAR_BORDER = new Coordinates(48.42916753187315,40.956064637716224); public static final Coordinates POINT_INSIDE_SIMPLE_RECT_NEAR_BORDER = new Coordinates(48.42916753187315, 40.956064637716224);
public static final Coordinates POINT_OUTSIDE_SIMPLE_RECT = new Coordinates(52.94806646045028,32.91501335472649); public static final Coordinates POINT_OUTSIDE_SIMPLE_RECT = new Coordinates(52.94806646045028, 32.91501335472649);
public static final Coordinates POINT_INSIDE_SAND_CLOCK_CENTER = new Coordinates(49.993588800145105,31.289062500000004); public static final Coordinates POINT_INSIDE_SAND_CLOCK_CENTER = new Coordinates(49.993588800145105, 31.289062500000004);
public static final Coordinates POINT_INSIDE_SAND_CLOCK_NEAR_BORDER = new Coordinates(47.798651123976306,26.895045405470082); public static final Coordinates POINT_INSIDE_SAND_CLOCK_NEAR_BORDER = new Coordinates(47.798651123976306, 26.895045405470082);
public static final Coordinates POINT_OUTSIDE_SAND_CLOCK_1 = new Coordinates(49.553754212665936,28.03748985004787); public static final Coordinates POINT_OUTSIDE_SAND_CLOCK_1 = new Coordinates(49.553754212665936, 28.03748985004787);
public static final Coordinates POINT_OUTSIDE_SAND_CLOCK_2 = new Coordinates(46.9802466961145,32.321728498980335); public static final Coordinates POINT_OUTSIDE_SAND_CLOCK_2 = new Coordinates(46.9802466961145, 32.321728498980335);
public static final Coordinates POINT_INSIDE_SELF_INTERSECTING_UPPER_CENTER = new Coordinates(50.750366308834884,32.51952867922265); public static final Coordinates POINT_INSIDE_SELF_INTERSECTING_UPPER_CENTER = new Coordinates(50.750366308834884, 32.51952867922265);
public static final Coordinates POINT_INSIDE_SELF_INTERSECTING_LOWER_CENTER = new Coordinates(48.1371117277312,32.40967825185941); public static final Coordinates POINT_INSIDE_SELF_INTERSECTING_LOWER_CENTER = new Coordinates(48.1371117277312, 32.40967825185941);
public static final Coordinates POINT_INSIDE_SELF_INTERSECTING_NEAR_BORDER = new Coordinates(51.16552151942722,35.66125090181154); public static final Coordinates POINT_INSIDE_SELF_INTERSECTING_NEAR_BORDER = new Coordinates(51.16552151942722, 35.66125090181154);
public static final Coordinates POINT_OUTSIDE_SELF_INTERSECTING_1= new Coordinates(49.66777277299077,33.26651158529272); public static final Coordinates POINT_OUTSIDE_SELF_INTERSECTING_1 = new Coordinates(49.66777277299077, 33.26651158529272);
public static final Coordinates POINT_OUTSIDE_SELF_INTERSECTING_2 = new Coordinates(47.10052840114779,32.16800731166027); public static final Coordinates POINT_OUTSIDE_SELF_INTERSECTING_2 = new Coordinates(47.10052840114779, 32.16800731166027);
public static final Coordinates POINT_OUTSIDE_SELF_INTERSECTING_3 = new Coordinates(78.76578380252519,15.646485040786361); public static final Coordinates POINT_OUTSIDE_SELF_INTERSECTING_3 = new Coordinates(78.76578380252519, 15.646485040786361);
@Test @Test

3
rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingFilterNodeTest.java

@ -21,6 +21,9 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor; import org.mockito.ArgumentCaptor;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.common.util.geo.Coordinates;
import org.thingsboard.common.util.geo.PerimeterType;
import org.thingsboard.common.util.geo.RangeUnit;
import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbContext;
import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeConfiguration;
import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.rule.engine.api.TbNodeException;

50
ui-ngx/src/app/shared/models/ace/tbel-utils.models.ts

@ -1245,6 +1245,56 @@ const tbelEditorCompletions:TbEditorCompletions = {
type: 'boolean' type: 'boolean'
} }
}, },
isInsidePolygon: {
meta: 'function',
description: 'Checks if a given point is inside a polygon.',
args: [
{
name: 'latitude',
description: 'The latitude of the point',
type: 'number'
},
{
name: 'longitude',
description: 'The longitude of the point',
type: 'number'
},
{
name: 'perimeter',
description: 'The polygon perimeter represented as a string',
type: 'string'
}
],
return: {
description: 'True if the point is inside the polygon, false otherwise.',
type: 'boolean'
}
},
isInsideCircle: {
meta: 'function',
description: 'Checks if a given point is inside a circular area.',
args: [
{
name: 'latitude',
description: 'The latitude of the point',
type: 'number'
},
{
name: 'longitude',
description: 'The longitude of the point',
type: 'number'
},
{
name: 'perimeter',
description: 'A string representation of the circle, containing center coordinates and radius',
type: 'string'
}
],
return: {
description: 'True if the point is inside the circle, false otherwise.',
type: 'boolean'
}
}
} }
export const tbelUtilsAutocompletes = new TbEditorCompleter(tbelEditorCompletions); export const tbelUtilsAutocompletes = new TbEditorCompleter(tbelEditorCompletions);

Loading…
Cancel
Save