Added relation creation support
This commit is contained in:
parent
409328dbe3
commit
3643b54985
@ -321,33 +321,17 @@ public class CalculatedFieldEntityMessageProcessor extends AbstractContextAwareM
|
|||||||
boolean stateSizeChecked = false;
|
boolean stateSizeChecked = false;
|
||||||
try {
|
try {
|
||||||
if (ctx.isInitialized() && state.isReady()) {
|
if (ctx.isInitialized() && state.isReady()) {
|
||||||
List<CalculatedFieldResult> calculationResults = state.performCalculation(ctx).get(systemContext.getCfCalculationResultTimeout(), TimeUnit.SECONDS);
|
CalculatedFieldResult calculationResult = state.performCalculation(ctx).get(systemContext.getCfCalculationResultTimeout(), TimeUnit.SECONDS);
|
||||||
state.checkStateSize(ctxId, ctx.getMaxStateSize());
|
state.checkStateSize(ctxId, ctx.getMaxStateSize());
|
||||||
stateSizeChecked = true;
|
stateSizeChecked = true;
|
||||||
if (state.isSizeOk()) {
|
if (state.isSizeOk()) {
|
||||||
if (calculationResults.isEmpty()) {
|
if (!calculationResult.isEmpty()) {
|
||||||
callback.onSuccess();
|
cfService.pushMsgToRuleEngine(tenantId, entityId, calculationResult, cfIdList, callback);
|
||||||
} else {
|
} else {
|
||||||
TbCallback effectiveCallback = calculationResults.size() > 1 ?
|
callback.onSuccess();
|
||||||
new MultipleTbCallback(calculationResults.size(), callback) : callback;
|
|
||||||
for (CalculatedFieldResult calculationResult : calculationResults) {
|
|
||||||
if (calculationResult.isEmpty()) {
|
|
||||||
effectiveCallback.onSuccess();
|
|
||||||
} else {
|
|
||||||
cfService.pushMsgToRuleEngine(tenantId, entityId, calculationResult, cfIdList, effectiveCallback);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (DebugModeUtil.isDebugAllAvailable(ctx.getCalculatedField())) {
|
if (DebugModeUtil.isDebugAllAvailable(ctx.getCalculatedField())) {
|
||||||
if (calculationResults.isEmpty()) {
|
systemContext.persistCalculatedFieldDebugEvent(tenantId, ctx.getCfId(), entityId, state.getArguments(), tbMsgId, tbMsgType, calculationResult.getResult().toString(), null);
|
||||||
systemContext.persistCalculatedFieldDebugEvent(tenantId, ctx.getCfId(), entityId,
|
|
||||||
state.getArguments(), tbMsgId, tbMsgType, null, null);
|
|
||||||
} else {
|
|
||||||
for (CalculatedFieldResult calculationResult : calculationResults) {
|
|
||||||
systemContext.persistCalculatedFieldDebugEvent(tenantId, ctx.getCfId(), entityId,
|
|
||||||
state.getArguments(), tbMsgId, tbMsgType, calculationResult.getResultAsString(), null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -136,7 +136,7 @@ public class CalculatedFieldManagerMessageProcessor extends AbstractContextAware
|
|||||||
public void onFieldInitMsg(CalculatedFieldInitMsg msg) throws CalculatedFieldException {
|
public void onFieldInitMsg(CalculatedFieldInitMsg msg) throws CalculatedFieldException {
|
||||||
log.debug("[{}] 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 = getCfCtx(cf);
|
||||||
try {
|
try {
|
||||||
cfCtx.init();
|
cfCtx.init();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
@ -297,7 +297,7 @@ public class CalculatedFieldManagerMessageProcessor extends AbstractContextAware
|
|||||||
log.debug("[{}] Failed to lookup CF by id [{}]", tenantId, cfId);
|
log.debug("[{}] Failed to lookup CF by id [{}]", tenantId, cfId);
|
||||||
callback.onSuccess();
|
callback.onSuccess();
|
||||||
} else {
|
} else {
|
||||||
var cfCtx = new CalculatedFieldCtx(cf, systemContext.getTbelInvokeService(), systemContext.getApiLimitService());
|
var cfCtx = getCfCtx(cf);
|
||||||
try {
|
try {
|
||||||
cfCtx.init();
|
cfCtx.init();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
@ -313,6 +313,10 @@ public class CalculatedFieldManagerMessageProcessor extends AbstractContextAware
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private CalculatedFieldCtx getCfCtx(CalculatedField cf) {
|
||||||
|
return new CalculatedFieldCtx(cf, systemContext.getTbelInvokeService(), systemContext.getApiLimitService(), systemContext.getRelationService());
|
||||||
|
}
|
||||||
|
|
||||||
private void onCfUpdated(ComponentLifecycleMsg msg, TbCallback callback) throws CalculatedFieldException {
|
private void onCfUpdated(ComponentLifecycleMsg msg, TbCallback callback) throws CalculatedFieldException {
|
||||||
var cfId = new CalculatedFieldId(msg.getEntityId().getId());
|
var cfId = new CalculatedFieldId(msg.getEntityId().getId());
|
||||||
var oldCfCtx = calculatedFields.get(cfId);
|
var oldCfCtx = calculatedFields.get(cfId);
|
||||||
@ -324,7 +328,7 @@ public class CalculatedFieldManagerMessageProcessor extends AbstractContextAware
|
|||||||
log.debug("[{}] Failed to lookup CF by id [{}]", tenantId, cfId);
|
log.debug("[{}] Failed to lookup CF by id [{}]", tenantId, cfId);
|
||||||
callback.onSuccess();
|
callback.onSuccess();
|
||||||
} else {
|
} else {
|
||||||
var newCfCtx = new CalculatedFieldCtx(newCf, systemContext.getTbelInvokeService(), systemContext.getApiLimitService());
|
var newCfCtx = getCfCtx(newCf);
|
||||||
try {
|
try {
|
||||||
newCfCtx.init();
|
newCfCtx.init();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
|||||||
@ -34,6 +34,7 @@ import org.thingsboard.server.common.data.page.PageDataIterable;
|
|||||||
import org.thingsboard.server.common.msg.cf.CalculatedFieldInitMsg;
|
import org.thingsboard.server.common.msg.cf.CalculatedFieldInitMsg;
|
||||||
import org.thingsboard.server.common.msg.cf.CalculatedFieldLinkInitMsg;
|
import org.thingsboard.server.common.msg.cf.CalculatedFieldLinkInitMsg;
|
||||||
import org.thingsboard.server.dao.cf.CalculatedFieldService;
|
import org.thingsboard.server.dao.cf.CalculatedFieldService;
|
||||||
|
import org.thingsboard.server.dao.relation.RelationService;
|
||||||
import org.thingsboard.server.dao.usagerecord.ApiLimitService;
|
import org.thingsboard.server.dao.usagerecord.ApiLimitService;
|
||||||
import org.thingsboard.server.queue.util.AfterStartUp;
|
import org.thingsboard.server.queue.util.AfterStartUp;
|
||||||
import org.thingsboard.server.service.cf.ctx.state.CalculatedFieldCtx;
|
import org.thingsboard.server.service.cf.ctx.state.CalculatedFieldCtx;
|
||||||
@ -56,6 +57,7 @@ public class DefaultCalculatedFieldCache implements CalculatedFieldCache {
|
|||||||
private final CalculatedFieldService calculatedFieldService;
|
private final CalculatedFieldService calculatedFieldService;
|
||||||
private final TbelInvokeService tbelInvokeService;
|
private final TbelInvokeService tbelInvokeService;
|
||||||
private final ApiLimitService apiLimitService;
|
private final ApiLimitService apiLimitService;
|
||||||
|
private final RelationService relationService;
|
||||||
@Lazy
|
@Lazy
|
||||||
private final ActorSystemContext actorSystemContext;
|
private final ActorSystemContext actorSystemContext;
|
||||||
|
|
||||||
@ -119,7 +121,7 @@ public class DefaultCalculatedFieldCache implements CalculatedFieldCache {
|
|||||||
if (ctx == null) {
|
if (ctx == null) {
|
||||||
CalculatedField calculatedField = getCalculatedField(calculatedFieldId);
|
CalculatedField calculatedField = getCalculatedField(calculatedFieldId);
|
||||||
if (calculatedField != null) {
|
if (calculatedField != null) {
|
||||||
ctx = new CalculatedFieldCtx(calculatedField, tbelInvokeService, apiLimitService);
|
ctx = new CalculatedFieldCtx(calculatedField, tbelInvokeService, apiLimitService, relationService);
|
||||||
calculatedFieldsCtx.put(calculatedFieldId, ctx);
|
calculatedFieldsCtx.put(calculatedFieldId, ctx);
|
||||||
log.debug("[{}] Put calculated field ctx into cache: {}", calculatedFieldId, ctx);
|
log.debug("[{}] Put calculated field ctx into cache: {}", calculatedFieldId, ctx);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -36,6 +36,7 @@ import org.thingsboard.server.common.data.kv.AttributeKvEntry;
|
|||||||
import org.thingsboard.server.common.data.kv.TsKvEntry;
|
import org.thingsboard.server.common.data.kv.TsKvEntry;
|
||||||
import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
|
import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
|
||||||
import org.thingsboard.server.common.util.ProtoUtils;
|
import org.thingsboard.server.common.util.ProtoUtils;
|
||||||
|
import org.thingsboard.server.dao.relation.RelationService;
|
||||||
import org.thingsboard.server.dao.usagerecord.ApiLimitService;
|
import org.thingsboard.server.dao.usagerecord.ApiLimitService;
|
||||||
import org.thingsboard.server.gen.transport.TransportProtos.CalculatedFieldTelemetryMsgProto;
|
import org.thingsboard.server.gen.transport.TransportProtos.CalculatedFieldTelemetryMsgProto;
|
||||||
import org.thingsboard.server.service.cf.ctx.CalculatedFieldEntityCtxId;
|
import org.thingsboard.server.service.cf.ctx.CalculatedFieldEntityCtxId;
|
||||||
@ -68,13 +69,15 @@ public class CalculatedFieldCtx {
|
|||||||
private CalculatedFieldScriptEngine calculatedFieldScriptEngine;
|
private CalculatedFieldScriptEngine calculatedFieldScriptEngine;
|
||||||
private ThreadLocal<Expression> customExpression;
|
private ThreadLocal<Expression> customExpression;
|
||||||
|
|
||||||
|
private RelationService relationService;
|
||||||
|
|
||||||
private boolean initialized;
|
private boolean initialized;
|
||||||
|
|
||||||
private long maxDataPointsPerRollingArg;
|
private long maxDataPointsPerRollingArg;
|
||||||
private long maxStateSize;
|
private long maxStateSize;
|
||||||
private long maxSingleValueArgumentSize;
|
private long maxSingleValueArgumentSize;
|
||||||
|
|
||||||
public CalculatedFieldCtx(CalculatedField calculatedField, TbelInvokeService tbelInvokeService, ApiLimitService apiLimitService) {
|
public CalculatedFieldCtx(CalculatedField calculatedField, TbelInvokeService tbelInvokeService, ApiLimitService apiLimitService, RelationService relationService) {
|
||||||
this.calculatedField = calculatedField;
|
this.calculatedField = calculatedField;
|
||||||
|
|
||||||
this.cfId = calculatedField.getId();
|
this.cfId = calculatedField.getId();
|
||||||
@ -102,6 +105,7 @@ public class CalculatedFieldCtx {
|
|||||||
this.expression = configuration.getExpression();
|
this.expression = configuration.getExpression();
|
||||||
this.useLatestTs = CalculatedFieldType.SIMPLE.equals(calculatedField.getType()) && ((SimpleCalculatedFieldConfiguration) configuration).isUseLatestTs();
|
this.useLatestTs = CalculatedFieldType.SIMPLE.equals(calculatedField.getType()) && ((SimpleCalculatedFieldConfiguration) configuration).isUseLatestTs();
|
||||||
this.tbelInvokeService = tbelInvokeService;
|
this.tbelInvokeService = tbelInvokeService;
|
||||||
|
this.relationService = relationService;
|
||||||
|
|
||||||
this.maxDataPointsPerRollingArg = apiLimitService.getLimit(tenantId, DefaultTenantProfileConfiguration::getMaxDataPointsPerRollingArg);
|
this.maxDataPointsPerRollingArg = apiLimitService.getLimit(tenantId, DefaultTenantProfileConfiguration::getMaxDataPointsPerRollingArg);
|
||||||
this.maxStateSize = apiLimitService.getLimit(tenantId, DefaultTenantProfileConfiguration::getMaxStateSizeInKBytes) * 1024;
|
this.maxStateSize = apiLimitService.getLimit(tenantId, DefaultTenantProfileConfiguration::getMaxStateSizeInKBytes) * 1024;
|
||||||
|
|||||||
@ -55,7 +55,7 @@ public interface CalculatedFieldState {
|
|||||||
|
|
||||||
boolean updateState(CalculatedFieldCtx ctx, Map<String, ArgumentEntry> argumentValues);
|
boolean updateState(CalculatedFieldCtx ctx, Map<String, ArgumentEntry> argumentValues);
|
||||||
|
|
||||||
ListenableFuture<List<CalculatedFieldResult>> performCalculation(CalculatedFieldCtx ctx);
|
ListenableFuture<CalculatedFieldResult> performCalculation(CalculatedFieldCtx ctx);
|
||||||
|
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
boolean isReady();
|
boolean isReady();
|
||||||
|
|||||||
@ -18,6 +18,7 @@ package org.thingsboard.server.service.cf.ctx.state;
|
|||||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||||
import com.google.common.util.concurrent.Futures;
|
import com.google.common.util.concurrent.Futures;
|
||||||
import com.google.common.util.concurrent.ListenableFuture;
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
|
import com.google.common.util.concurrent.MoreExecutors;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import org.thingsboard.common.util.JacksonUtil;
|
import org.thingsboard.common.util.JacksonUtil;
|
||||||
@ -25,13 +26,15 @@ import org.thingsboard.common.util.geo.Coordinates;
|
|||||||
import org.thingsboard.server.common.data.cf.CalculatedFieldType;
|
import org.thingsboard.server.common.data.cf.CalculatedFieldType;
|
||||||
import org.thingsboard.server.common.data.cf.configuration.GeofencingCalculatedFieldConfiguration;
|
import org.thingsboard.server.common.data.cf.configuration.GeofencingCalculatedFieldConfiguration;
|
||||||
import org.thingsboard.server.common.data.cf.configuration.GeofencingEvent;
|
import org.thingsboard.server.common.data.cf.configuration.GeofencingEvent;
|
||||||
import org.thingsboard.server.common.data.cf.configuration.GeofencingZoneGroupConfiguration;
|
import org.thingsboard.server.common.data.id.EntityId;
|
||||||
|
import org.thingsboard.server.common.data.relation.EntityRelation;
|
||||||
import org.thingsboard.server.service.cf.CalculatedFieldResult;
|
import org.thingsboard.server.service.cf.CalculatedFieldResult;
|
||||||
import org.thingsboard.server.service.cf.ctx.CalculatedFieldEntityCtxId;
|
import org.thingsboard.server.service.cf.ctx.CalculatedFieldEntityCtxId;
|
||||||
import org.thingsboard.server.utils.CalculatedFieldUtils;
|
import org.thingsboard.server.utils.CalculatedFieldUtils;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
@ -112,33 +115,93 @@ public class GeofencingCalculatedFieldState implements CalculatedFieldState {
|
|||||||
return stateUpdated;
|
return stateUpdated;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// TODO: Probably returning list of CalculatedFieldResult no needed anymore,
|
|
||||||
// since logic changed to use zone groups with telemetry prefix.
|
|
||||||
@Override
|
@Override
|
||||||
public ListenableFuture<List<CalculatedFieldResult>> performCalculation(CalculatedFieldCtx ctx) {
|
public ListenableFuture<CalculatedFieldResult> performCalculation(CalculatedFieldCtx ctx) {
|
||||||
double latitude = (double) arguments.get(ENTITY_ID_LATITUDE_ARGUMENT_KEY).getValue();
|
double latitude = (double) arguments.get(ENTITY_ID_LATITUDE_ARGUMENT_KEY).getValue();
|
||||||
double longitude = (double) arguments.get(ENTITY_ID_LONGITUDE_ARGUMENT_KEY).getValue();
|
double longitude = (double) arguments.get(ENTITY_ID_LONGITUDE_ARGUMENT_KEY).getValue();
|
||||||
Coordinates entityCoordinates = new Coordinates(latitude, longitude);
|
Coordinates entityCoordinates = new Coordinates(latitude, longitude);
|
||||||
|
|
||||||
var configuration = (GeofencingCalculatedFieldConfiguration) ctx.getCalculatedField().getConfiguration();
|
var configuration = (GeofencingCalculatedFieldConfiguration) ctx.getCalculatedField().getConfiguration();
|
||||||
Map<String, GeofencingZoneGroupConfiguration> geofencingZoneGroupConfigurations = configuration.getGeofencingZoneGroupConfigurations();
|
if (configuration.isTrackRelationToZones()) {
|
||||||
|
// TODO: currently creates relation to device profile if CF created for profile)
|
||||||
|
return calculateWithRelations(ctx, entityCoordinates, configuration);
|
||||||
|
}
|
||||||
|
return calculateWithoutRelations(ctx, entityCoordinates, configuration);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ListenableFuture<CalculatedFieldResult> calculateWithRelations(
|
||||||
|
CalculatedFieldCtx ctx,
|
||||||
|
Coordinates entityCoordinates,
|
||||||
|
GeofencingCalculatedFieldConfiguration configuration) {
|
||||||
|
|
||||||
|
var geofencingZoneGroupConfigurations = configuration.getGeofencingZoneGroupConfigurations();
|
||||||
|
|
||||||
|
Map<EntityId, GeofencingEvent> zoneEventMap = new HashMap<>();
|
||||||
ObjectNode resultNode = JacksonUtil.newObjectNode();
|
ObjectNode resultNode = JacksonUtil.newObjectNode();
|
||||||
|
|
||||||
getGeofencingArguments().forEach((argumentKey, argumentEntry) -> {
|
getGeofencingArguments().forEach((argumentKey, argumentEntry) -> {
|
||||||
var zoneGroupConfig = geofencingZoneGroupConfigurations.get(argumentKey);
|
var zoneGroupConfig = geofencingZoneGroupConfigurations.get(argumentKey);
|
||||||
Set<GeofencingEvent> zoneEvents = argumentEntry.getZoneStates()
|
Set<GeofencingEvent> groupEvents = new HashSet<>();
|
||||||
.values()
|
|
||||||
.stream()
|
argumentEntry.getZoneStates().forEach((zoneId, zoneState) -> {
|
||||||
.map(zoneState -> zoneState.evaluate(entityCoordinates))
|
GeofencingEvent event = zoneState.evaluate(entityCoordinates);
|
||||||
.collect(Collectors.toSet());
|
zoneEventMap.put(zoneId, event);
|
||||||
aggregateZoneGroupEvent(zoneEvents)
|
groupEvents.add(event);
|
||||||
.filter(geofencingEvent -> zoneGroupConfig.getReportEvents().contains(geofencingEvent))
|
});
|
||||||
.ifPresent(event ->
|
|
||||||
resultNode.put(zoneGroupConfig.getReportTelemetryPrefix() + "Event", event.name())
|
aggregateZoneGroupEvent(groupEvents)
|
||||||
);
|
.filter(zoneGroupConfig.getReportEvents()::contains)
|
||||||
|
.ifPresent(geofencingGroupEvent ->
|
||||||
|
resultNode.put(zoneGroupConfig.getReportTelemetryPrefix() + "Event", geofencingGroupEvent.name()));
|
||||||
});
|
});
|
||||||
return Futures.immediateFuture(List.of(new CalculatedFieldResult(ctx.getOutput().getType(), ctx.getOutput().getScope(), resultNode)));
|
|
||||||
|
var result = calculationResult(ctx, resultNode);
|
||||||
|
|
||||||
|
List<ListenableFuture<Boolean>> relationFutures = zoneEventMap.entrySet().stream()
|
||||||
|
.filter(entry -> entry.getValue().isTransitionEvent())
|
||||||
|
.map(entry -> {
|
||||||
|
EntityRelation relation = toRelation(entry.getKey(), ctx, configuration);
|
||||||
|
return switch (entry.getValue()) {
|
||||||
|
case ENTERED -> ctx.getRelationService().saveRelationAsync(ctx.getTenantId(), relation);
|
||||||
|
case LEFT -> ctx.getRelationService().deleteRelationAsync(ctx.getTenantId(), relation);
|
||||||
|
default -> throw new IllegalStateException("Unexpected transition event: " + entry.getValue());
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
if (relationFutures.isEmpty()) {
|
||||||
|
return Futures.immediateFuture(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Futures.whenAllComplete(relationFutures).call(() ->
|
||||||
|
new CalculatedFieldResult(ctx.getOutput().getType(), ctx.getOutput().getScope(), resultNode),
|
||||||
|
MoreExecutors.directExecutor());
|
||||||
|
}
|
||||||
|
|
||||||
|
private ListenableFuture<CalculatedFieldResult> calculateWithoutRelations(
|
||||||
|
CalculatedFieldCtx ctx,
|
||||||
|
Coordinates entityCoordinates,
|
||||||
|
GeofencingCalculatedFieldConfiguration configuration) {
|
||||||
|
|
||||||
|
var geofencingZoneGroupConfigurations = configuration.getGeofencingZoneGroupConfigurations();
|
||||||
|
ObjectNode resultNode = JacksonUtil.newObjectNode();
|
||||||
|
|
||||||
|
getGeofencingArguments().forEach((argumentKey, argumentEntry) -> {
|
||||||
|
var zoneGroupConfig = geofencingZoneGroupConfigurations.get(argumentKey);
|
||||||
|
Set<GeofencingEvent> groupEvents = argumentEntry.getZoneStates().values().stream()
|
||||||
|
.map(zs -> zs.evaluate(entityCoordinates))
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
aggregateZoneGroupEvent(groupEvents)
|
||||||
|
.filter(zoneGroupConfig.getReportEvents()::contains)
|
||||||
|
.ifPresent(e -> resultNode.put(
|
||||||
|
zoneGroupConfig.getReportTelemetryPrefix() + "Event",
|
||||||
|
e.name()));
|
||||||
|
});
|
||||||
|
return Futures.immediateFuture(calculationResult(ctx, resultNode));
|
||||||
|
}
|
||||||
|
|
||||||
|
private CalculatedFieldResult calculationResult(CalculatedFieldCtx ctx, ObjectNode resultNode) {
|
||||||
|
return new CalculatedFieldResult(ctx.getOutput().getType(), ctx.getOutput().getScope(), resultNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -196,4 +259,11 @@ public class GeofencingCalculatedFieldState implements CalculatedFieldState {
|
|||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private EntityRelation toRelation(EntityId zoneId, CalculatedFieldCtx ctx, GeofencingCalculatedFieldConfiguration configuration) {
|
||||||
|
return switch (configuration.getZoneRelationDirection()) {
|
||||||
|
case TO -> new EntityRelation(zoneId, ctx.getEntityId(), configuration.getZoneRelationType());
|
||||||
|
case FROM -> new EntityRelation(ctx.getEntityId(), zoneId, configuration.getZoneRelationType());
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -53,7 +53,7 @@ public class ScriptCalculatedFieldState extends BaseCalculatedFieldState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ListenableFuture<List<CalculatedFieldResult>> performCalculation(CalculatedFieldCtx ctx) {
|
public ListenableFuture<CalculatedFieldResult> performCalculation(CalculatedFieldCtx ctx) {
|
||||||
Map<String, TbelCfArg> arguments = new LinkedHashMap<>();
|
Map<String, TbelCfArg> arguments = new LinkedHashMap<>();
|
||||||
List<Object> args = new ArrayList<>(ctx.getArgNames().size() + 1);
|
List<Object> args = new ArrayList<>(ctx.getArgNames().size() + 1);
|
||||||
args.add(new Object()); // first element is a ctx, but we will set it later;
|
args.add(new Object()); // first element is a ctx, but we will set it later;
|
||||||
@ -70,7 +70,7 @@ public class ScriptCalculatedFieldState extends BaseCalculatedFieldState {
|
|||||||
ListenableFuture<JsonNode> resultFuture = ctx.getCalculatedFieldScriptEngine().executeJsonAsync(args.toArray());
|
ListenableFuture<JsonNode> resultFuture = ctx.getCalculatedFieldScriptEngine().executeJsonAsync(args.toArray());
|
||||||
Output output = ctx.getOutput();
|
Output output = ctx.getOutput();
|
||||||
return Futures.transform(resultFuture,
|
return Futures.transform(resultFuture,
|
||||||
result -> List.of(new CalculatedFieldResult(output.getType(), output.getScope(), result)),
|
result -> new CalculatedFieldResult(output.getType(), output.getScope(), result),
|
||||||
MoreExecutors.directExecutor()
|
MoreExecutors.directExecutor()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -52,7 +52,7 @@ public class SimpleCalculatedFieldState extends BaseCalculatedFieldState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ListenableFuture<List<CalculatedFieldResult>> performCalculation(CalculatedFieldCtx ctx) {
|
public ListenableFuture<CalculatedFieldResult> performCalculation(CalculatedFieldCtx ctx) {
|
||||||
var expr = ctx.getCustomExpression().get();
|
var expr = ctx.getCustomExpression().get();
|
||||||
|
|
||||||
for (Map.Entry<String, ArgumentEntry> entry : this.arguments.entrySet()) {
|
for (Map.Entry<String, ArgumentEntry> entry : this.arguments.entrySet()) {
|
||||||
@ -76,7 +76,7 @@ public class SimpleCalculatedFieldState extends BaseCalculatedFieldState {
|
|||||||
Object result = formatResult(expressionResult, output.getDecimalsByDefault());
|
Object result = formatResult(expressionResult, output.getDecimalsByDefault());
|
||||||
JsonNode outputResult = createResultJson(ctx.isUseLatestTs(), output.getName(), result);
|
JsonNode outputResult = createResultJson(ctx.isUseLatestTs(), output.getName(), result);
|
||||||
|
|
||||||
return Futures.immediateFuture(List.of(new CalculatedFieldResult(output.getType(), output.getScope(), outputResult)));
|
return Futures.immediateFuture(new CalculatedFieldResult(output.getType(), output.getScope(), outputResult));
|
||||||
}
|
}
|
||||||
|
|
||||||
private Object formatResult(double expressionResult, Integer decimals) {
|
private Object formatResult(double expressionResult, Integer decimals) {
|
||||||
|
|||||||
@ -78,7 +78,7 @@ public class ScriptCalculatedFieldStateTest {
|
|||||||
@BeforeEach
|
@BeforeEach
|
||||||
void setUp() {
|
void setUp() {
|
||||||
when(apiLimitService.getLimit(any(), any())).thenReturn(1000L);
|
when(apiLimitService.getLimit(any(), any())).thenReturn(1000L);
|
||||||
ctx = new CalculatedFieldCtx(getCalculatedField(), tbelInvokeService, apiLimitService);
|
ctx = new CalculatedFieldCtx(getCalculatedField(), tbelInvokeService, apiLimitService, null);
|
||||||
ctx.init();
|
ctx.init();
|
||||||
state = new ScriptCalculatedFieldState(ctx.getArgNames());
|
state = new ScriptCalculatedFieldState(ctx.getArgNames());
|
||||||
}
|
}
|
||||||
@ -125,10 +125,9 @@ public class ScriptCalculatedFieldStateTest {
|
|||||||
void testPerformCalculation() throws ExecutionException, InterruptedException {
|
void testPerformCalculation() throws ExecutionException, InterruptedException {
|
||||||
state.arguments = new HashMap<>(Map.of("deviceTemperature", deviceTemperatureArgEntry, "assetHumidity", assetHumidityArgEntry));
|
state.arguments = new HashMap<>(Map.of("deviceTemperature", deviceTemperatureArgEntry, "assetHumidity", assetHumidityArgEntry));
|
||||||
|
|
||||||
List<CalculatedFieldResult> resultList = state.performCalculation(ctx).get();
|
CalculatedFieldResult result = state.performCalculation(ctx).get();
|
||||||
|
|
||||||
assertThat(resultList).isNotNull().hasSize(1);
|
assertThat(result).isNotNull();
|
||||||
CalculatedFieldResult result = resultList.get(0);
|
|
||||||
Output output = getCalculatedFieldConfig().getOutput();
|
Output output = getCalculatedFieldConfig().getOutput();
|
||||||
assertThat(result.getType()).isEqualTo(output.getType());
|
assertThat(result.getType()).isEqualTo(output.getType());
|
||||||
assertThat(result.getScope()).isEqualTo(output.getScope());
|
assertThat(result.getScope()).isEqualTo(output.getScope());
|
||||||
|
|||||||
@ -42,7 +42,6 @@ import org.thingsboard.server.dao.usagerecord.ApiLimitService;
|
|||||||
import org.thingsboard.server.service.cf.CalculatedFieldResult;
|
import org.thingsboard.server.service.cf.CalculatedFieldResult;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
@ -72,7 +71,7 @@ public class SimpleCalculatedFieldStateTest {
|
|||||||
@BeforeEach
|
@BeforeEach
|
||||||
void setUp() {
|
void setUp() {
|
||||||
when(apiLimitService.getLimit(any(), any())).thenReturn(1000L);
|
when(apiLimitService.getLimit(any(), any())).thenReturn(1000L);
|
||||||
ctx = new CalculatedFieldCtx(getCalculatedField(), null, apiLimitService);
|
ctx = new CalculatedFieldCtx(getCalculatedField(), null, apiLimitService, null);
|
||||||
ctx.init();
|
ctx.init();
|
||||||
state = new SimpleCalculatedFieldState(ctx.getArgNames());
|
state = new SimpleCalculatedFieldState(ctx.getArgNames());
|
||||||
}
|
}
|
||||||
@ -135,10 +134,9 @@ public class SimpleCalculatedFieldStateTest {
|
|||||||
"key3", key3ArgEntry
|
"key3", key3ArgEntry
|
||||||
));
|
));
|
||||||
|
|
||||||
List<CalculatedFieldResult> resultList = state.performCalculation(ctx).get();
|
CalculatedFieldResult result = state.performCalculation(ctx).get();
|
||||||
|
|
||||||
assertThat(resultList).isNotNull().hasSize(1);
|
assertThat(result).isNotNull();
|
||||||
CalculatedFieldResult result = resultList.get(0);
|
|
||||||
Output output = getCalculatedFieldConfig().getOutput();
|
Output output = getCalculatedFieldConfig().getOutput();
|
||||||
assertThat(result.getType()).isEqualTo(output.getType());
|
assertThat(result.getType()).isEqualTo(output.getType());
|
||||||
assertThat(result.getScope()).isEqualTo(output.getScope());
|
assertThat(result.getScope()).isEqualTo(output.getScope());
|
||||||
@ -166,10 +164,9 @@ public class SimpleCalculatedFieldStateTest {
|
|||||||
"key3", key3ArgEntry
|
"key3", key3ArgEntry
|
||||||
));
|
));
|
||||||
|
|
||||||
List<CalculatedFieldResult> resultList = state.performCalculation(ctx).get();
|
CalculatedFieldResult result = state.performCalculation(ctx).get();
|
||||||
|
|
||||||
assertThat(resultList).isNotNull().hasSize(1);
|
assertThat(result).isNotNull();
|
||||||
CalculatedFieldResult result = resultList.get(0);
|
|
||||||
Output output = getCalculatedFieldConfig().getOutput();
|
Output output = getCalculatedFieldConfig().getOutput();
|
||||||
assertThat(result.getType()).isEqualTo(output.getType());
|
assertThat(result.getType()).isEqualTo(output.getType());
|
||||||
assertThat(result.getScope()).isEqualTo(output.getScope());
|
assertThat(result.getScope()).isEqualTo(output.getScope());
|
||||||
@ -188,10 +185,9 @@ public class SimpleCalculatedFieldStateTest {
|
|||||||
output.setDecimalsByDefault(3);
|
output.setDecimalsByDefault(3);
|
||||||
ctx.setOutput(output);
|
ctx.setOutput(output);
|
||||||
|
|
||||||
List<CalculatedFieldResult> resultList = state.performCalculation(ctx).get();
|
CalculatedFieldResult result = state.performCalculation(ctx).get();
|
||||||
|
|
||||||
assertThat(resultList).isNotNull().hasSize(1);
|
assertThat(result).isNotNull();
|
||||||
CalculatedFieldResult result = resultList.get(0);
|
|
||||||
assertThat(result.getType()).isEqualTo(output.getType());
|
assertThat(result.getType()).isEqualTo(output.getType());
|
||||||
assertThat(result.getScope()).isEqualTo(output.getScope());
|
assertThat(result.getScope()).isEqualTo(output.getScope());
|
||||||
assertThat(result.getResult()).isEqualTo(JacksonUtil.valueToTree(Map.of("output", 49.546)));
|
assertThat(result.getResult()).isEqualTo(JacksonUtil.valueToTree(Map.of("output", 49.546)));
|
||||||
|
|||||||
@ -19,6 +19,7 @@ import lombok.Data;
|
|||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
import org.thingsboard.server.common.data.StringUtils;
|
import org.thingsboard.server.common.data.StringUtils;
|
||||||
import org.thingsboard.server.common.data.cf.CalculatedFieldType;
|
import org.thingsboard.server.common.data.cf.CalculatedFieldType;
|
||||||
|
import org.thingsboard.server.common.data.relation.EntitySearchDirection;
|
||||||
import org.thingsboard.server.common.data.util.CollectionsUtil;
|
import org.thingsboard.server.common.data.util.CollectionsUtil;
|
||||||
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
@ -40,8 +41,9 @@ public class GeofencingCalculatedFieldConfiguration extends BaseCalculatedFieldC
|
|||||||
ENTITY_ID_LONGITUDE_ARGUMENT_KEY
|
ENTITY_ID_LONGITUDE_ARGUMENT_KEY
|
||||||
);
|
);
|
||||||
|
|
||||||
|
private boolean trackRelationToZones;
|
||||||
private String zoneRelationType;
|
private String zoneRelationType;
|
||||||
private boolean trackZoneRelations;
|
private EntitySearchDirection zoneRelationDirection;
|
||||||
private Map<String, GeofencingZoneGroupConfiguration> geofencingZoneGroupConfigurations;
|
private Map<String, GeofencingZoneGroupConfiguration> geofencingZoneGroupConfigurations;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -50,6 +52,7 @@ public class GeofencingCalculatedFieldConfiguration extends BaseCalculatedFieldC
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: update validate method in PE version.
|
// TODO: update validate method in PE version.
|
||||||
|
// Add relation tracking configuration validation
|
||||||
@Override
|
@Override
|
||||||
public void validate() {
|
public void validate() {
|
||||||
if (arguments == null) {
|
if (arguments == null) {
|
||||||
|
|||||||
@ -15,8 +15,16 @@
|
|||||||
*/
|
*/
|
||||||
package org.thingsboard.server.common.data.cf.configuration;
|
package org.thingsboard.server.common.data.cf.configuration;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
public enum GeofencingEvent {
|
public enum GeofencingEvent {
|
||||||
|
|
||||||
ENTERED, LEFT, INSIDE, OUTSIDE;
|
ENTERED(true), LEFT(true), INSIDE(false), OUTSIDE(false);
|
||||||
|
|
||||||
|
private final boolean transitionEvent;
|
||||||
|
|
||||||
|
GeofencingEvent(boolean transitionEvent) {
|
||||||
|
this.transitionEvent = transitionEvent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user