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.common.data.id.EntityId;
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.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
protected boolean doProcessCfMsg(ToCalculatedFieldSystemMsg msg) throws CalculatedFieldException {
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;
}
public void stop() {
log.info("[{}][{}] Stopping entity actor.", tenantId, entityId);
states.clear();
ctx.stop(ctx.getSelf());
}
public void process(CalculatedFieldPartitionChangeMsg msg) {
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);

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.TbActorException;
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.cf.CalculatedFieldEntityLifecycleMsg;
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
protected boolean doProcessCfMsg(ToCalculatedFieldSystemMsg msg) throws CalculatedFieldException {
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;
/**
* @author Andrew Shvayka
*/
@ -92,8 +91,17 @@ public class CalculatedFieldManagerMessageProcessor extends AbstractContextAware
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 {
log.info("[{}] Processing CF init message.", msg.getCf().getId());
log.debug("[{}] Processing CF init message.", msg.getCf().getId());
var cf = msg.getCf();
var cfCtx = new CalculatedFieldCtx(cf, systemContext.getTbelInvokeService(), systemContext.getApiLimitService());
try {
@ -109,7 +117,7 @@ public class CalculatedFieldManagerMessageProcessor extends AbstractContextAware
}
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();
// 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)
@ -122,7 +130,9 @@ public class CalculatedFieldManagerMessageProcessor extends AbstractContextAware
var calculatedField = calculatedFields.get(cfId);
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());
getOrCreateActor(msg.getId().entityId()).tell(msg);
} else {
@ -131,7 +141,7 @@ public class CalculatedFieldManagerMessageProcessor extends AbstractContextAware
}
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 event = msg.getData().getEvent();
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.TbStringActorId;
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.ruleChain.RuleChainManagerActor;
import org.thingsboard.server.actors.service.ContextBasedCreator;
@ -126,6 +127,9 @@ public class TenantActor extends RuleChainManagerActor {
@Override
public void destroy(TbActorStopReason stopReason, Throwable cause) {
log.info("[{}] Stopping tenant actor.", tenantId);
if (cfActor != null) {
ctx.stop(cfActor.getActorId());
}
}
@Override
@ -190,7 +194,11 @@ public class TenantActor extends RuleChainManagerActor {
private void onToCalculatedFieldSystemActorMsg(ToCalculatedFieldSystemMsg msg, boolean priority) {
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;
}
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) {
if (tbelInvokeService == null) {
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
protected TbQueueConsumer<TbProtoQueueMsg<ToCalculatedFieldNotificationMsg>> createNotificationsConsumer() {
return queueFactory.createToCalculatedFieldNotificationsMsgConsumer();
return queueFactory.createToCalculatedFieldNotificationMsgConsumer();
}
@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
public TbQueueConsumer<TbProtoQueueMsg<TransportProtos.ToCalculatedFieldNotificationMsg>> createToCalculatedFieldNotificationsMsgConsumer() {
public TbQueueConsumer<TbProtoQueueMsg<TransportProtos.ToCalculatedFieldNotificationMsg>> createToCalculatedFieldNotificationMsgConsumer() {
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
public TbQueueConsumer<TbProtoQueueMsg<ToCalculatedFieldNotificationMsg>> createToCalculatedFieldNotificationsMsgConsumer() {
public TbQueueConsumer<TbProtoQueueMsg<ToCalculatedFieldNotificationMsg>> createToCalculatedFieldNotificationMsgConsumer() {
TbKafkaConsumerTemplate.TbKafkaConsumerTemplateBuilder<TbProtoQueueMsg<ToCalculatedFieldNotificationMsg>> consumerBuilder = TbKafkaConsumerTemplate.builder();
consumerBuilder.settings(kafkaSettings);
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
public TbQueueConsumer<TbProtoQueueMsg<ToCalculatedFieldNotificationMsg>> createToCalculatedFieldNotificationsMsgConsumer() {
public TbQueueConsumer<TbProtoQueueMsg<ToCalculatedFieldNotificationMsg>> createToCalculatedFieldNotificationMsgConsumer() {
TbKafkaConsumerTemplate.TbKafkaConsumerTemplateBuilder<TbProtoQueueMsg<ToCalculatedFieldNotificationMsg>> consumerBuilder = TbKafkaConsumerTemplate.builder();
consumerBuilder.settings(kafkaSettings);
consumerBuilder.topic(topicService.getCalculatedFieldNotificationsTopic(serviceInfoProvider.getServiceId()).getFullTopicName());
@ -349,6 +349,16 @@ public class KafkaTbRuleEngineQueueFactory implements TbRuleEngineQueueFactory {
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
public TbQueueConsumer<TbProtoQueueMsg<CalculatedFieldStateProto>> createCalculatedFieldStateConsumer() {
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<ToEdgeEventNotificationMsg>> toEdgeEvents;
private TbQueueProducer<TbProtoQueueMsg<ToCalculatedFieldMsg>> toCalculatedFields;
private TbQueueProducer<TbProtoQueueMsg<ToCalculatedFieldNotificationMsg>> toCalculatedFieldNotifications;
public TbRuleEngineProducerProvider(TbRuleEngineQueueFactory tbQueueProvider) {
this.tbQueueProvider = tbQueueProvider;
@ -68,6 +69,7 @@ public class TbRuleEngineProducerProvider implements TbQueueProducerProvider {
this.toEdgeNotifications = tbQueueProvider.createEdgeNotificationsMsgProducer();
this.toEdgeEvents = tbQueueProvider.createEdgeEventMsgProducer();
this.toCalculatedFields = tbQueueProvider.createToCalculatedFieldMsgProducer();
this.toCalculatedFieldNotifications = tbQueueProvider.createToCalculatedFieldNotificationMsgProducer();
}
@Override
@ -132,7 +134,7 @@ public class TbRuleEngineProducerProvider implements TbQueueProducerProvider {
@Override
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();
TbQueueConsumer<TbProtoQueueMsg<ToCalculatedFieldNotificationMsg>> createToCalculatedFieldNotificationsMsgConsumer();
TbQueueConsumer<TbProtoQueueMsg<ToCalculatedFieldNotificationMsg>> createToCalculatedFieldNotificationMsgConsumer();
TbQueueProducer<TbProtoQueueMsg<ToCalculatedFieldNotificationMsg>> createToCalculatedFieldNotificationMsgProducer();
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;
import com.fasterxml.jackson.databind.JsonNode;
import com.google.common.primitives.Bytes;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ArrayUtils;
@ -23,6 +24,10 @@ import org.mvel2.ParserConfiguration;
import org.mvel2.execution.ExecutionArrayList;
import org.mvel2.execution.ExecutionHashMap;
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 java.io.IOException;
@ -371,6 +376,10 @@ public class TbUtils {
byte[].class, int.class)));
parserConfig.addImport("parseBinaryArrayToInt", new MethodStub(TbUtils.class.getMethod("parseBinaryArrayToInt",
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) {
@ -1437,6 +1446,22 @@ public class TbUtils {
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) {
if (val > 255 || val < -128) {
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));
}
@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) {
List<Byte> result = new ArrayList<>(data.length);
for (Byte b : data) {

8
common/util/pom.xml

@ -103,6 +103,14 @@
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jdk8</artifactId>
</dependency>
<dependency>
<groupId>org.locationtech.spatial4j</groupId>
<artifactId>spatial4j</artifactId>
</dependency>
<dependency>
<groupId>org.locationtech.jts</groupId>
<artifactId>jts-core</artifactId>
</dependency>
</dependencies>
<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
* limitations under the License.
*/
package org.thingsboard.rule.engine.geo;
package org.thingsboard.common.util.geo;
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
* 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.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
* limitations under the License.
*/
package org.thingsboard.rule.engine.geo;
package org.thingsboard.common.util.geo;
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
* limitations under the License.
*/
package org.thingsboard.rule.engine.geo;
package org.thingsboard.common.util.geo;
public enum PerimeterType {
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
* limitations under the License.
*/
package org.thingsboard.rule.engine.geo;
package org.thingsboard.common.util.geo;
public enum RangeUnit {
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) {
validateNumberOfCFsPerEntity(tenantId, calculatedField.getEntityId());
validateNumberOfArgumentsPerCF(tenantId, calculatedField);
validateArgumentNames(calculatedField);
}
@Override
@ -48,6 +49,7 @@ public class CalculatedFieldDataValidator extends DataValidator<CalculatedField>
throw new DataValidationException("Can't update non existing calculated field!");
}
validateNumberOfArgumentsPerCF(tenantId, calculatedField);
validateArgumentNames(calculatedField);
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.OutputType;
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.debug.DebugSettings;
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" +
"\n" +
" return {\n" +
" \"airDensity\": airDensity\n" +
" \"airDensity\": toFixed(airDensity, 2)\n" +
" };";
private TenantId tenantId;
@ -280,6 +281,34 @@ public class CalculatedFieldTest extends AbstractContainerTest {
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() {
return createSimpleCalculatedField(device.getId());
}
@ -323,19 +352,21 @@ public class CalculatedFieldTest extends AbstractContainerTest {
calculatedField.setName("Air density" + RandomStringUtils.randomAlphabetic(5));
calculatedField.setDebugSettings(DebugSettings.all());
SimpleCalculatedFieldConfiguration config = new SimpleCalculatedFieldConfiguration();
ScriptCalculatedFieldConfiguration config = new ScriptCalculatedFieldConfiguration();
Argument argument1 = new Argument();
argument1.setRefEntityId(asset.getId());
ReferencedEntityKey refEntityKey1 = new ReferencedEntityKey("altitude", ArgumentType.ATTRIBUTE, AttributeScope.SERVER_SCOPE);
argument1.setRefEntityKey(refEntityKey1);
config.setArguments(Map.of("altitude", argument1));
Argument argument2 = new Argument();
ReferencedEntityKey refEntityKey2 = new ReferencedEntityKey("temperatureInF", ArgumentType.TS_ROLLING, null);
argument2.setTimeWindow(30000L);
argument2.setLimit(5);
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.setType(OutputType.TIME_SERIES);

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

@ -120,14 +120,6 @@
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk18on</artifactId>
</dependency>
<dependency>
<groupId>org.locationtech.spatial4j</groupId>
<artifactId>spatial4j</artifactId>
</dependency>
<dependency>
<groupId>org.locationtech.jts</groupId>
<artifactId>jts-core</artifactId>
</dependency>
<dependency>
<groupId>com.sun.mail</groupId>
<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.JtsSpatialContextFactory;
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.TbNode;
import org.thingsboard.rule.engine.api.TbNodeConfiguration;
@ -74,7 +79,7 @@ public abstract class AbstractGeofencingNode<T extends TbGpsGeofencingFilterNode
} else if (perimeter.getPerimeterType() == PerimeterType.POLYGON) {
return GeoUtil.contains(perimeter.getPolygonsDefinition(), new Coordinates(latitude, longitude));
} 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;
import lombok.Data;
import org.thingsboard.common.util.geo.PerimeterType;
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;
import lombok.Data;
import org.thingsboard.common.util.geo.PerimeterType;
import org.thingsboard.common.util.geo.RangeUnit;
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.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;
import org.thingsboard.common.util.geo.Coordinates;
import org.thingsboard.common.util.geo.GeoUtil;
@ExtendWith(MockitoExtension.class)
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 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_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_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_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_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_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_3 = new Coordinates(78.76578380252519,15.646485040786361);
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_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_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_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_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_2 = new Coordinates(47.10052840114779, 32.16800731166027);
public static final Coordinates POINT_OUTSIDE_SELF_INTERSECTING_3 = new Coordinates(78.76578380252519, 15.646485040786361);
@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.mockito.ArgumentCaptor;
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.TbNodeConfiguration;
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'
}
},
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);

Loading…
Cancel
Save