Updated to use zone groups
This commit is contained in:
parent
ede9fd5e05
commit
c783176e71
@ -35,6 +35,8 @@ 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.cf.configuration.Argument;
|
import org.thingsboard.server.common.data.cf.configuration.Argument;
|
||||||
import org.thingsboard.server.common.data.cf.configuration.ArgumentType;
|
import org.thingsboard.server.common.data.cf.configuration.ArgumentType;
|
||||||
|
import org.thingsboard.server.common.data.cf.configuration.GeofencingCalculatedFieldConfiguration;
|
||||||
|
import org.thingsboard.server.common.data.cf.configuration.GeofencingZoneGroupConfiguration;
|
||||||
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.RelationQueryDynamicSourceConfiguration;
|
import org.thingsboard.server.common.data.cf.configuration.RelationQueryDynamicSourceConfiguration;
|
||||||
import org.thingsboard.server.common.data.id.CalculatedFieldId;
|
import org.thingsboard.server.common.data.id.CalculatedFieldId;
|
||||||
@ -95,8 +97,6 @@ import java.util.stream.Collectors;
|
|||||||
import static org.thingsboard.server.common.data.DataConstants.SCOPE;
|
import static org.thingsboard.server.common.data.DataConstants.SCOPE;
|
||||||
import static org.thingsboard.server.common.data.cf.configuration.GeofencingCalculatedFieldConfiguration.ENTITY_ID_LATITUDE_ARGUMENT_KEY;
|
import static org.thingsboard.server.common.data.cf.configuration.GeofencingCalculatedFieldConfiguration.ENTITY_ID_LATITUDE_ARGUMENT_KEY;
|
||||||
import static org.thingsboard.server.common.data.cf.configuration.GeofencingCalculatedFieldConfiguration.ENTITY_ID_LONGITUDE_ARGUMENT_KEY;
|
import static org.thingsboard.server.common.data.cf.configuration.GeofencingCalculatedFieldConfiguration.ENTITY_ID_LONGITUDE_ARGUMENT_KEY;
|
||||||
import static org.thingsboard.server.common.data.cf.configuration.GeofencingCalculatedFieldConfiguration.RESTRICTED_ZONES_ARGUMENT_KEY;
|
|
||||||
import static org.thingsboard.server.common.data.cf.configuration.GeofencingCalculatedFieldConfiguration.ALLOWED_ZONES_ARGUMENT_KEY;
|
|
||||||
import static org.thingsboard.server.utils.CalculatedFieldUtils.toProto;
|
import static org.thingsboard.server.utils.CalculatedFieldUtils.toProto;
|
||||||
|
|
||||||
@TbRuleEngineComponent
|
@TbRuleEngineComponent
|
||||||
@ -132,16 +132,17 @@ public class DefaultCalculatedFieldProcessingService implements CalculatedFieldP
|
|||||||
Map<String, ListenableFuture<ArgumentEntry>> argFutures = new HashMap<>();
|
Map<String, ListenableFuture<ArgumentEntry>> argFutures = new HashMap<>();
|
||||||
|
|
||||||
if (ctx.getCalculatedField().getType().equals(CalculatedFieldType.GEOFENCING)) {
|
if (ctx.getCalculatedField().getType().equals(CalculatedFieldType.GEOFENCING)) {
|
||||||
// Ignoring any other arguments except ENTITY_ID_LATITUDE_ARGUMENT_KEY,
|
var configuration = (GeofencingCalculatedFieldConfiguration) ctx.getCalculatedField().getConfiguration();
|
||||||
// ENTITY_ID_LONGITUDE_ARGUMENT_KEY, SAVE_ZONES_ARGUMENT_KEY, RESTRICTED_ZONES_ARGUMENT_KEY.
|
var zoneGroupConfigs = configuration.getGeofencingZoneGroupConfigurations();
|
||||||
for (var entry : ctx.getArguments().entrySet()) {
|
for (var entry : ctx.getArguments().entrySet()) {
|
||||||
switch (entry.getKey()) {
|
switch (entry.getKey()) {
|
||||||
case ENTITY_ID_LATITUDE_ARGUMENT_KEY, ENTITY_ID_LONGITUDE_ARGUMENT_KEY ->
|
case ENTITY_ID_LATITUDE_ARGUMENT_KEY, ENTITY_ID_LONGITUDE_ARGUMENT_KEY ->
|
||||||
argFutures.put(entry.getKey(), fetchKvEntry(ctx.getTenantId(), resolveEntityId(entityId, entry), entry.getValue()));
|
argFutures.put(entry.getKey(), fetchKvEntry(ctx.getTenantId(), resolveEntityId(entityId, entry), entry.getValue()));
|
||||||
case ALLOWED_ZONES_ARGUMENT_KEY, RESTRICTED_ZONES_ARGUMENT_KEY -> {
|
default -> {
|
||||||
|
var zoneGroupConfiguration = zoneGroupConfigs.get(entry.getKey());
|
||||||
var resolvedEntityIdsFuture = resolveGeofencingEntityIds(ctx.getTenantId(), entityId, entry);
|
var resolvedEntityIdsFuture = resolveGeofencingEntityIds(ctx.getTenantId(), entityId, entry);
|
||||||
argFutures.put(entry.getKey(), Futures.transformAsync(resolvedEntityIdsFuture, resolvedEntityIds ->
|
argFutures.put(entry.getKey(), Futures.transformAsync(resolvedEntityIdsFuture, resolvedEntityIds ->
|
||||||
fetchGeofencingKvEntry(ctx.getTenantId(), resolvedEntityIds, entry.getValue()), calculatedFieldCallbackExecutor));
|
fetchGeofencingKvEntry(ctx.getTenantId(), resolvedEntityIds, entry.getValue(), zoneGroupConfiguration), calculatedFieldCallbackExecutor));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -304,7 +305,8 @@ public class DefaultCalculatedFieldProcessingService implements CalculatedFieldP
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private ListenableFuture<ArgumentEntry> fetchGeofencingKvEntry(TenantId tenantId, List<EntityId> geofencingEntities, Argument argument) {
|
private ListenableFuture<ArgumentEntry> fetchGeofencingKvEntry(TenantId tenantId, List<EntityId> geofencingEntities,
|
||||||
|
Argument argument, GeofencingZoneGroupConfiguration zoneGroupConfiguration) {
|
||||||
if (argument.getRefEntityKey().getType() != ArgumentType.ATTRIBUTE) {
|
if (argument.getRefEntityKey().getType() != ArgumentType.ATTRIBUTE) {
|
||||||
throw new IllegalStateException("Unsupported argument key type: " + argument.getRefEntityKey().getType());
|
throw new IllegalStateException("Unsupported argument key type: " + argument.getRefEntityKey().getType());
|
||||||
}
|
}
|
||||||
@ -326,7 +328,7 @@ public class DefaultCalculatedFieldProcessingService implements CalculatedFieldP
|
|||||||
ListenableFuture<List<Map.Entry<EntityId, AttributeKvEntry>>> allFutures = Futures.allAsList(kvFutures);
|
ListenableFuture<List<Map.Entry<EntityId, AttributeKvEntry>>> allFutures = Futures.allAsList(kvFutures);
|
||||||
|
|
||||||
return Futures.transform(allFutures, entries -> ArgumentEntry.createGeofencingValueArgument(entries.stream()
|
return Futures.transform(allFutures, entries -> ArgumentEntry.createGeofencingValueArgument(entries.stream()
|
||||||
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue))),
|
.collect(Collectors.toMap(Entry::getKey, Entry::getValue)), zoneGroupConfiguration),
|
||||||
calculatedFieldCallbackExecutor
|
calculatedFieldCallbackExecutor
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,6 +19,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
|
|||||||
import com.fasterxml.jackson.annotation.JsonSubTypes;
|
import com.fasterxml.jackson.annotation.JsonSubTypes;
|
||||||
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||||
import org.thingsboard.script.api.tbel.TbelCfArg;
|
import org.thingsboard.script.api.tbel.TbelCfArg;
|
||||||
|
import org.thingsboard.server.common.data.cf.configuration.GeofencingZoneGroupConfiguration;
|
||||||
import org.thingsboard.server.common.data.id.EntityId;
|
import org.thingsboard.server.common.data.id.EntityId;
|
||||||
import org.thingsboard.server.common.data.kv.KvEntry;
|
import org.thingsboard.server.common.data.kv.KvEntry;
|
||||||
import org.thingsboard.server.common.data.kv.TsKvEntry;
|
import org.thingsboard.server.common.data.kv.TsKvEntry;
|
||||||
@ -61,8 +62,8 @@ public interface ArgumentEntry {
|
|||||||
return new TsRollingArgumentEntry(kvEntries, limit, timeWindow);
|
return new TsRollingArgumentEntry(kvEntries, limit, timeWindow);
|
||||||
}
|
}
|
||||||
|
|
||||||
static ArgumentEntry createGeofencingValueArgument(Map<EntityId, KvEntry> entityIdkvEntryMap) {
|
static ArgumentEntry createGeofencingValueArgument(Map<EntityId, KvEntry> entityIdkvEntryMap, GeofencingZoneGroupConfiguration zoneGroupConfiguration) {
|
||||||
return new GeofencingArgumentEntry(entityIdkvEntryMap);
|
return new GeofencingArgumentEntry(entityIdkvEntryMap, zoneGroupConfiguration);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,6 +19,7 @@ import lombok.Data;
|
|||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.thingsboard.script.api.tbel.TbelCfArg;
|
import org.thingsboard.script.api.tbel.TbelCfArg;
|
||||||
import org.thingsboard.script.api.tbel.TbelCfTsGeofencingArg;
|
import org.thingsboard.script.api.tbel.TbelCfTsGeofencingArg;
|
||||||
|
import org.thingsboard.server.common.data.cf.configuration.GeofencingZoneGroupConfiguration;
|
||||||
import org.thingsboard.server.common.data.id.EntityId;
|
import org.thingsboard.server.common.data.id.EntityId;
|
||||||
import org.thingsboard.server.common.data.kv.KvEntry;
|
import org.thingsboard.server.common.data.kv.KvEntry;
|
||||||
|
|
||||||
@ -31,13 +32,17 @@ import java.util.stream.Collectors;
|
|||||||
public class GeofencingArgumentEntry implements ArgumentEntry {
|
public class GeofencingArgumentEntry implements ArgumentEntry {
|
||||||
|
|
||||||
private Map<EntityId, GeofencingZoneState> zoneStates;
|
private Map<EntityId, GeofencingZoneState> zoneStates;
|
||||||
|
private GeofencingZoneGroupConfiguration zoneGroupConfiguration;
|
||||||
|
|
||||||
private boolean forceResetPrevious;
|
private boolean forceResetPrevious;
|
||||||
|
|
||||||
public GeofencingArgumentEntry() {
|
public GeofencingArgumentEntry() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public GeofencingArgumentEntry(Map<EntityId, KvEntry> entityIdKvEntryMap) {
|
public GeofencingArgumentEntry(Map<EntityId, KvEntry> entityIdkvEntryMap,
|
||||||
this.zoneStates = toZones(entityIdKvEntryMap);
|
GeofencingZoneGroupConfiguration zoneGroupConfiguration) {
|
||||||
|
this.zoneStates = toZones(entityIdkvEntryMap);
|
||||||
|
this.zoneGroupConfiguration = zoneGroupConfiguration;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@ -23,6 +23,7 @@ import lombok.Data;
|
|||||||
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.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.GeofencingEvent;
|
||||||
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;
|
||||||
@ -31,11 +32,13 @@ import java.util.ArrayList;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import static org.thingsboard.server.common.data.cf.configuration.GeofencingCalculatedFieldConfiguration.ALLOWED_ZONES_ARGUMENT_KEY;
|
|
||||||
import static org.thingsboard.server.common.data.cf.configuration.GeofencingCalculatedFieldConfiguration.ENTITY_ID_LATITUDE_ARGUMENT_KEY;
|
import static org.thingsboard.server.common.data.cf.configuration.GeofencingCalculatedFieldConfiguration.ENTITY_ID_LATITUDE_ARGUMENT_KEY;
|
||||||
import static org.thingsboard.server.common.data.cf.configuration.GeofencingCalculatedFieldConfiguration.ENTITY_ID_LONGITUDE_ARGUMENT_KEY;
|
import static org.thingsboard.server.common.data.cf.configuration.GeofencingCalculatedFieldConfiguration.ENTITY_ID_LONGITUDE_ARGUMENT_KEY;
|
||||||
import static org.thingsboard.server.common.data.cf.configuration.GeofencingCalculatedFieldConfiguration.RESTRICTED_ZONES_ARGUMENT_KEY;
|
import static org.thingsboard.server.common.data.cf.configuration.GeofencingCalculatedFieldConfiguration.coordinateKeys;
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
@ -70,7 +73,7 @@ public class GeofencingCalculatedFieldState implements CalculatedFieldState {
|
|||||||
|
|
||||||
boolean stateUpdated = false;
|
boolean stateUpdated = false;
|
||||||
|
|
||||||
for (Map.Entry<String, ArgumentEntry> entry : argumentValues.entrySet()) {
|
for (var entry : argumentValues.entrySet()) {
|
||||||
String key = entry.getKey();
|
String key = entry.getKey();
|
||||||
ArgumentEntry newEntry = entry.getValue();
|
ArgumentEntry newEntry = entry.getValue();
|
||||||
|
|
||||||
@ -80,26 +83,22 @@ public class GeofencingCalculatedFieldState implements CalculatedFieldState {
|
|||||||
boolean entryUpdated;
|
boolean entryUpdated;
|
||||||
|
|
||||||
if (existingEntry == null || newEntry.isForceResetPrevious()) {
|
if (existingEntry == null || newEntry.isForceResetPrevious()) {
|
||||||
switch (key) {
|
entryUpdated = switch (key) {
|
||||||
case ENTITY_ID_LATITUDE_ARGUMENT_KEY:
|
case ENTITY_ID_LATITUDE_ARGUMENT_KEY, ENTITY_ID_LONGITUDE_ARGUMENT_KEY -> {
|
||||||
case ENTITY_ID_LONGITUDE_ARGUMENT_KEY:
|
|
||||||
if (!(newEntry instanceof SingleValueArgumentEntry singleValueArgumentEntry)) {
|
if (!(newEntry instanceof SingleValueArgumentEntry singleValueArgumentEntry)) {
|
||||||
throw new IllegalArgumentException(key + " argument must be a single value argument.");
|
throw new IllegalArgumentException(key + " argument must be a single value argument.");
|
||||||
}
|
}
|
||||||
arguments.put(key, singleValueArgumentEntry);
|
arguments.put(key, singleValueArgumentEntry);
|
||||||
entryUpdated = true;
|
yield true;
|
||||||
break;
|
}
|
||||||
case ALLOWED_ZONES_ARGUMENT_KEY:
|
default -> {
|
||||||
case RESTRICTED_ZONES_ARGUMENT_KEY:
|
|
||||||
if (!(newEntry instanceof GeofencingArgumentEntry geofencingArgumentEntry)) {
|
if (!(newEntry instanceof GeofencingArgumentEntry geofencingArgumentEntry)) {
|
||||||
throw new IllegalArgumentException(key + " argument must be a geofencing argument entry.");
|
throw new IllegalArgumentException(key + " argument must be a geofencing argument entry.");
|
||||||
}
|
}
|
||||||
arguments.put(key, geofencingArgumentEntry);
|
arguments.put(key, geofencingArgumentEntry);
|
||||||
entryUpdated = true;
|
yield true;
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new IllegalArgumentException("Unsupported argument: " + key);
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
} else {
|
} else {
|
||||||
entryUpdated = existingEntry.updateEntry(newEntry);
|
entryUpdated = existingEntry.updateEntry(newEntry);
|
||||||
}
|
}
|
||||||
@ -111,21 +110,60 @@ public class GeofencingCalculatedFieldState implements CalculatedFieldState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 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<List<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);
|
||||||
|
|
||||||
List<CalculatedFieldResult> savedZonesStatesResults = updateGeofencingZonesState(ctx, entityCoordinates, false);
|
ObjectNode resultNode = JacksonUtil.newObjectNode();
|
||||||
List<CalculatedFieldResult> restrictedZonesStatesResults = updateGeofencingZonesState(ctx, entityCoordinates, true);
|
getGeofencingArguments().forEach((argumentKey, argumentEntry) -> {
|
||||||
|
var zoneGroupConfig = argumentEntry.getZoneGroupConfiguration();
|
||||||
|
Set<GeofencingEvent> zoneEvents = argumentEntry.getZoneStates()
|
||||||
|
.values()
|
||||||
|
.stream()
|
||||||
|
.map(zoneState -> zoneState.evaluate(entityCoordinates))
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
aggregateZoneGroupEvent(zoneEvents).ifPresent(event ->
|
||||||
|
resultNode.put(zoneGroupConfig.getReportTelemetryPrefix() + "Event", event.name())
|
||||||
|
);
|
||||||
|
});
|
||||||
|
return Futures.immediateFuture(List.of(new CalculatedFieldResult(ctx.getOutput().getType(), ctx.getOutput().getScope(), resultNode)));
|
||||||
|
}
|
||||||
|
|
||||||
List<CalculatedFieldResult> allZoneStatesResults =
|
private Optional<GeofencingEvent> aggregateZoneGroupEvent(Set<GeofencingEvent> zoneEvents) {
|
||||||
new ArrayList<>(savedZonesStatesResults.size() + restrictedZonesStatesResults.size());
|
boolean hasEntered = false;
|
||||||
allZoneStatesResults.addAll(savedZonesStatesResults);
|
boolean hasLeft = false;
|
||||||
allZoneStatesResults.addAll(restrictedZonesStatesResults);
|
boolean hasInside = false;
|
||||||
|
boolean hasOutside = false;
|
||||||
|
|
||||||
return Futures.immediateFuture(allZoneStatesResults);
|
for (GeofencingEvent event : zoneEvents) {
|
||||||
|
if (event == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
switch (event) {
|
||||||
|
case ENTERED -> hasEntered = true;
|
||||||
|
case LEFT -> hasLeft = true;
|
||||||
|
case INSIDE -> hasInside = true;
|
||||||
|
case OUTSIDE -> hasOutside = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasOutside && !hasInside && !hasEntered && !hasLeft) {
|
||||||
|
return Optional.of(GeofencingEvent.OUTSIDE);
|
||||||
|
}
|
||||||
|
if (hasLeft && !hasEntered && !hasInside) {
|
||||||
|
return Optional.of(GeofencingEvent.LEFT);
|
||||||
|
}
|
||||||
|
if (hasEntered && !hasLeft && !hasInside) {
|
||||||
|
return Optional.of(GeofencingEvent.ENTERED);
|
||||||
|
}
|
||||||
|
if (hasInside || hasEntered) {
|
||||||
|
return Optional.of(GeofencingEvent.INSIDE);
|
||||||
|
}
|
||||||
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -142,26 +180,12 @@ public class GeofencingCalculatedFieldState implements CalculatedFieldState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<CalculatedFieldResult> updateGeofencingZonesState(CalculatedFieldCtx ctx, Coordinates entityCoordinates, boolean restricted) {
|
// TODO: Create a new class field to not do this on each calculation.
|
||||||
String zoneKey = restricted ? RESTRICTED_ZONES_ARGUMENT_KEY : ALLOWED_ZONES_ARGUMENT_KEY;
|
private Map<String, GeofencingArgumentEntry> getGeofencingArguments() {
|
||||||
GeofencingArgumentEntry zonesEntry = (GeofencingArgumentEntry) arguments.get(zoneKey);
|
return arguments.entrySet()
|
||||||
|
.stream()
|
||||||
if (zonesEntry == null) {
|
.filter(entry -> !coordinateKeys.contains(entry.getKey()))
|
||||||
return List.of();
|
.collect(Collectors.toMap(Map.Entry::getKey, entry -> (GeofencingArgumentEntry) entry.getValue()));
|
||||||
}
|
|
||||||
|
|
||||||
var results = new ArrayList<CalculatedFieldResult>();
|
|
||||||
for (var zoneEntry : zonesEntry.getZoneStates().entrySet()) {
|
|
||||||
GeofencingZoneState state = zoneEntry.getValue();
|
|
||||||
String event = state.evaluate(entityCoordinates);
|
|
||||||
ObjectNode stateNode = JacksonUtil.newObjectNode();
|
|
||||||
stateNode.put("entityId", ctx.getEntityId().toString());
|
|
||||||
stateNode.put("zoneId", state.getZoneId().toString());
|
|
||||||
stateNode.put("restricted", restricted);
|
|
||||||
stateNode.put("event", event);
|
|
||||||
results.add(new CalculatedFieldResult(ctx.getOutput().getType(), ctx.getOutput().getScope(), stateNode));
|
|
||||||
}
|
|
||||||
return results;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -20,7 +20,7 @@ import lombok.EqualsAndHashCode;
|
|||||||
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.Coordinates;
|
||||||
import org.thingsboard.common.util.geo.PerimeterDefinition;
|
import org.thingsboard.common.util.geo.PerimeterDefinition;
|
||||||
import org.thingsboard.rule.engine.util.GpsGeofencingEvents;
|
import org.thingsboard.server.common.data.cf.configuration.GeofencingEvent;
|
||||||
import org.thingsboard.server.common.data.id.EntityId;
|
import org.thingsboard.server.common.data.id.EntityId;
|
||||||
import org.thingsboard.server.common.data.id.EntityIdFactory;
|
import org.thingsboard.server.common.data.id.EntityIdFactory;
|
||||||
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
|
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
|
||||||
@ -81,16 +81,20 @@ public class GeofencingZoneState {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String evaluate(Coordinates entityCoordinates) {
|
public GeofencingEvent evaluate(Coordinates entityCoordinates) {
|
||||||
boolean inside = perimeterDefinition.checkMatches(entityCoordinates);
|
boolean inside = perimeterDefinition.checkMatches(entityCoordinates);
|
||||||
// TODO: maybe handle this.inside == null as ENTERED or OUTSIDE.
|
// Initial evaluation — no prior state
|
||||||
// Since if this.inside == null then we don't have a state for this zone yet
|
if (this.inside == null) {
|
||||||
// and logically say that we are OUTSIDE instead of LEFT.
|
|
||||||
if (this.inside == null || this.inside != inside) {
|
|
||||||
this.inside = inside;
|
this.inside = inside;
|
||||||
return inside ? GpsGeofencingEvents.ENTERED : GpsGeofencingEvents.LEFT;
|
return inside ? GeofencingEvent.ENTERED : GeofencingEvent.OUTSIDE;
|
||||||
}
|
}
|
||||||
return inside ? GpsGeofencingEvents.INSIDE : GpsGeofencingEvents.OUTSIDE;
|
// State changed
|
||||||
|
if (this.inside != inside) {
|
||||||
|
this.inside = inside;
|
||||||
|
return inside ? GeofencingEvent.ENTERED : GeofencingEvent.LEFT;
|
||||||
|
}
|
||||||
|
// State unchanged
|
||||||
|
return inside ? GeofencingEvent.INSIDE : GeofencingEvent.OUTSIDE;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,6 +18,8 @@ package org.thingsboard.server.utils;
|
|||||||
import org.thingsboard.common.util.JacksonUtil;
|
import org.thingsboard.common.util.JacksonUtil;
|
||||||
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.cf.configuration.GeofencingEvent;
|
||||||
|
import org.thingsboard.server.common.data.cf.configuration.GeofencingZoneGroupConfiguration;
|
||||||
import org.thingsboard.server.common.data.id.CalculatedFieldId;
|
import org.thingsboard.server.common.data.id.CalculatedFieldId;
|
||||||
import org.thingsboard.server.common.data.id.EntityId;
|
import org.thingsboard.server.common.data.id.EntityId;
|
||||||
import org.thingsboard.server.common.data.id.EntityIdFactory;
|
import org.thingsboard.server.common.data.id.EntityIdFactory;
|
||||||
@ -28,6 +30,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.CalculatedFieldEntit
|
|||||||
import org.thingsboard.server.gen.transport.TransportProtos.CalculatedFieldIdProto;
|
import org.thingsboard.server.gen.transport.TransportProtos.CalculatedFieldIdProto;
|
||||||
import org.thingsboard.server.gen.transport.TransportProtos.CalculatedFieldStateProto;
|
import org.thingsboard.server.gen.transport.TransportProtos.CalculatedFieldStateProto;
|
||||||
import org.thingsboard.server.gen.transport.TransportProtos.GeofencingArgumentProto;
|
import org.thingsboard.server.gen.transport.TransportProtos.GeofencingArgumentProto;
|
||||||
|
import org.thingsboard.server.gen.transport.TransportProtos.GeofencingEventProto;
|
||||||
import org.thingsboard.server.gen.transport.TransportProtos.GeofencingZoneIdProto;
|
import org.thingsboard.server.gen.transport.TransportProtos.GeofencingZoneIdProto;
|
||||||
import org.thingsboard.server.gen.transport.TransportProtos.GeofencingZoneProto;
|
import org.thingsboard.server.gen.transport.TransportProtos.GeofencingZoneProto;
|
||||||
import org.thingsboard.server.gen.transport.TransportProtos.SingleValueArgumentProto;
|
import org.thingsboard.server.gen.transport.TransportProtos.SingleValueArgumentProto;
|
||||||
@ -45,6 +48,7 @@ import org.thingsboard.server.service.cf.ctx.state.SimpleCalculatedFieldState;
|
|||||||
import org.thingsboard.server.service.cf.ctx.state.SingleValueArgumentEntry;
|
import org.thingsboard.server.service.cf.ctx.state.SingleValueArgumentEntry;
|
||||||
import org.thingsboard.server.service.cf.ctx.state.TsRollingArgumentEntry;
|
import org.thingsboard.server.service.cf.ctx.state.TsRollingArgumentEntry;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.TreeMap;
|
import java.util.TreeMap;
|
||||||
@ -123,12 +127,15 @@ public class CalculatedFieldUtils {
|
|||||||
|
|
||||||
|
|
||||||
private static GeofencingArgumentProto toGeofencingArgumentProto(String argName, GeofencingArgumentEntry geofencingArgumentEntry) {
|
private static GeofencingArgumentProto toGeofencingArgumentProto(String argName, GeofencingArgumentEntry geofencingArgumentEntry) {
|
||||||
GeofencingArgumentProto.Builder builder = GeofencingArgumentProto.newBuilder()
|
var zoneGroupConfiguration = geofencingArgumentEntry.getZoneGroupConfiguration();
|
||||||
.setArgName(argName);
|
|
||||||
Map<EntityId, GeofencingZoneState> zoneStates = geofencingArgumentEntry.getZoneStates();
|
Map<EntityId, GeofencingZoneState> zoneStates = geofencingArgumentEntry.getZoneStates();
|
||||||
zoneStates.forEach((entityId, zoneState) -> {
|
GeofencingArgumentProto.Builder builder = GeofencingArgumentProto.newBuilder()
|
||||||
builder.addZones(toGeofencingZoneProto(entityId, zoneState));
|
.setArgName(argName)
|
||||||
});
|
.setTelemetryPrefix(zoneGroupConfiguration.getReportTelemetryPrefix());
|
||||||
|
zoneStates.forEach((entityId, zoneState) ->
|
||||||
|
builder.addZones(toGeofencingZoneProto(entityId, zoneState)));
|
||||||
|
zoneGroupConfiguration.getReportEvents().forEach(event ->
|
||||||
|
builder.addReportEvents(GeofencingEventProto.forNumber(event.getProtoNumber())));
|
||||||
return builder.build();
|
return builder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -206,8 +213,14 @@ public class CalculatedFieldUtils {
|
|||||||
.stream()
|
.stream()
|
||||||
.map(GeofencingZoneState::new)
|
.map(GeofencingZoneState::new)
|
||||||
.collect(Collectors.toMap(GeofencingZoneState::getZoneId, Function.identity()));
|
.collect(Collectors.toMap(GeofencingZoneState::getZoneId, Function.identity()));
|
||||||
|
List<GeofencingEvent> geofencingEvents = proto.getReportEventsList()
|
||||||
|
.stream()
|
||||||
|
.map(geofencingEventProto -> GeofencingEvent.fromProtoNumber(geofencingEventProto.getNumber()))
|
||||||
|
.toList();
|
||||||
|
var zoneGroupConfiguration = new GeofencingZoneGroupConfiguration(proto.getTelemetryPrefix(), geofencingEvents);
|
||||||
GeofencingArgumentEntry geofencingArgumentEntry = new GeofencingArgumentEntry();
|
GeofencingArgumentEntry geofencingArgumentEntry = new GeofencingArgumentEntry();
|
||||||
geofencingArgumentEntry.setZoneStates(zoneStates);
|
geofencingArgumentEntry.setZoneStates(zoneStates);
|
||||||
|
geofencingArgumentEntry.setZoneGroupConfiguration(zoneGroupConfiguration);
|
||||||
return geofencingArgumentEntry;
|
return geofencingArgumentEntry;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -17,10 +17,14 @@ package org.thingsboard.server.common.data.cf.configuration;
|
|||||||
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
|
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.util.CollectionsUtil;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import static org.thingsboard.server.common.data.cf.configuration.CFArgumentDynamicSourceType.RELATION_QUERY;
|
import static org.thingsboard.server.common.data.cf.configuration.CFArgumentDynamicSourceType.RELATION_QUERY;
|
||||||
|
|
||||||
@ -30,21 +34,14 @@ public class GeofencingCalculatedFieldConfiguration extends BaseCalculatedFieldC
|
|||||||
|
|
||||||
public static final String ENTITY_ID_LATITUDE_ARGUMENT_KEY = "latitude";
|
public static final String ENTITY_ID_LATITUDE_ARGUMENT_KEY = "latitude";
|
||||||
public static final String ENTITY_ID_LONGITUDE_ARGUMENT_KEY = "longitude";
|
public static final String ENTITY_ID_LONGITUDE_ARGUMENT_KEY = "longitude";
|
||||||
public static final String ALLOWED_ZONES_ARGUMENT_KEY = "allowedZones";
|
|
||||||
public static final String RESTRICTED_ZONES_ARGUMENT_KEY = "restrictedZones";
|
|
||||||
|
|
||||||
private static final Set<String> allowedKeys = Set.of(
|
public static final Set<String> coordinateKeys = Set.of(
|
||||||
ENTITY_ID_LATITUDE_ARGUMENT_KEY,
|
|
||||||
ENTITY_ID_LONGITUDE_ARGUMENT_KEY,
|
|
||||||
ALLOWED_ZONES_ARGUMENT_KEY,
|
|
||||||
RESTRICTED_ZONES_ARGUMENT_KEY
|
|
||||||
);
|
|
||||||
|
|
||||||
private static final Set<String> requiredKeys = Set.of(
|
|
||||||
ENTITY_ID_LATITUDE_ARGUMENT_KEY,
|
ENTITY_ID_LATITUDE_ARGUMENT_KEY,
|
||||||
ENTITY_ID_LONGITUDE_ARGUMENT_KEY
|
ENTITY_ID_LONGITUDE_ARGUMENT_KEY
|
||||||
);
|
);
|
||||||
|
|
||||||
|
Map<String, GeofencingZoneGroupConfiguration> geofencingZoneGroupConfigurations;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CalculatedFieldType getType() {
|
public CalculatedFieldType getType() {
|
||||||
return CalculatedFieldType.GEOFENCING;
|
return CalculatedFieldType.GEOFENCING;
|
||||||
@ -56,63 +53,76 @@ public class GeofencingCalculatedFieldConfiguration extends BaseCalculatedFieldC
|
|||||||
if (arguments == null) {
|
if (arguments == null) {
|
||||||
throw new IllegalArgumentException("Geofencing calculated field arguments are empty!");
|
throw new IllegalArgumentException("Geofencing calculated field arguments are empty!");
|
||||||
}
|
}
|
||||||
|
if (arguments.size() < 3) {
|
||||||
|
throw new IllegalArgumentException("Geofencing calculated field must contain at least 3 arguments!");
|
||||||
|
}
|
||||||
|
if (arguments.size() > 5) {
|
||||||
|
throw new IllegalArgumentException("Geofencing calculated field size exceeds limit of 5 arguments!");
|
||||||
|
}
|
||||||
|
validateCoordinateArguments();
|
||||||
|
|
||||||
// Check key count
|
Map<String, Argument> zoneGroupsArguments = getZoneGroupArguments();
|
||||||
if (arguments.size() < 3 || arguments.size() > 4) {
|
if (zoneGroupsArguments.isEmpty()) {
|
||||||
throw new IllegalArgumentException("Geofencing calculated field must contain 3 or 4 arguments: " + allowedKeys);
|
throw new IllegalArgumentException("Geofencing calculated field must contain at least one geofencing zone group defined!");
|
||||||
|
}
|
||||||
|
validateZoneGroupAruguments(zoneGroupsArguments);
|
||||||
|
validateZoneGroupConfigurations(zoneGroupsArguments);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for unsupported argument keys
|
private void validateZoneGroupConfigurations(Map<String, Argument> zoneGroupsArguments) {
|
||||||
for (String key : arguments.keySet()) {
|
if (geofencingZoneGroupConfigurations == null) {
|
||||||
if (!allowedKeys.contains(key)) {
|
throw new IllegalArgumentException("Geofencing calculated field zone group configurations are empty!");
|
||||||
throw new IllegalArgumentException("Unsupported argument key: '" + key + "'. Allowed keys: " + allowedKeys);
|
|
||||||
}
|
}
|
||||||
|
Set<String> usedPrefixes = new HashSet<>();
|
||||||
|
geofencingZoneGroupConfigurations.forEach((zoneGroupName, config) -> {
|
||||||
|
Argument zoneGroupArgument = zoneGroupsArguments.get(zoneGroupName);
|
||||||
|
if (zoneGroupArgument == null) {
|
||||||
|
throw new IllegalArgumentException("Geofencing calculated field zone group configuration is not configured for zone group: " + zoneGroupName);
|
||||||
|
}
|
||||||
|
if (config == null) {
|
||||||
|
throw new IllegalArgumentException("Zone group configuration is not configured for zone group: " + zoneGroupName);
|
||||||
|
}
|
||||||
|
if (CollectionsUtil.isEmpty(config.getReportEvents())) {
|
||||||
|
throw new IllegalArgumentException("Zone group configuration report events must be specified for zone group: " + zoneGroupName);
|
||||||
|
}
|
||||||
|
String prefix = config.getReportTelemetryPrefix();
|
||||||
|
if (StringUtils.isBlank(prefix)) {
|
||||||
|
throw new IllegalArgumentException("Report telemetry prefix should be specified for zone group: " + zoneGroupName);
|
||||||
|
}
|
||||||
|
if (!usedPrefixes.add(prefix)) {
|
||||||
|
throw new IllegalArgumentException("Duplicate report telemetry prefix found: '" + prefix + "'. Must be unique!");
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check required fields: latitude and longitude
|
private void validateCoordinateArguments() {
|
||||||
for (String requiredKey : requiredKeys) {
|
for (String coordinateKey : coordinateKeys) {
|
||||||
if (!arguments.containsKey(requiredKey)) {
|
Argument argument = arguments.get(coordinateKey);
|
||||||
throw new IllegalArgumentException("Missing required argument: " + requiredKey);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure at least one of the zone types is configured
|
|
||||||
boolean hasAllowedZones = arguments.containsKey(ALLOWED_ZONES_ARGUMENT_KEY);
|
|
||||||
boolean hasRestrictedZones = arguments.containsKey(RESTRICTED_ZONES_ARGUMENT_KEY);
|
|
||||||
|
|
||||||
if (!hasAllowedZones && !hasRestrictedZones) {
|
|
||||||
throw new IllegalArgumentException("Geofencing calculated field must contain at least one of the following arguments: 'allowedZones' or 'restrictedZones'");
|
|
||||||
}
|
|
||||||
|
|
||||||
for (Map.Entry<String, Argument> entry : arguments.entrySet()) {
|
|
||||||
String argumentKey = entry.getKey();
|
|
||||||
Argument argument = entry.getValue();
|
|
||||||
if (argument == null) {
|
if (argument == null) {
|
||||||
throw new IllegalArgumentException("Missing required argument: " + argumentKey);
|
throw new IllegalArgumentException("Missing required coordinates argument: " + coordinateKey);
|
||||||
}
|
}
|
||||||
ReferencedEntityKey refEntityKey = argument.getRefEntityKey();
|
ReferencedEntityKey refEntityKey = validateAndGetRefEntityKey(argument, coordinateKey);
|
||||||
if (refEntityKey == null || refEntityKey.getType() == null) {
|
|
||||||
throw new IllegalArgumentException("Missing or invalid reference entity key for argument: " + argumentKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (argumentKey) {
|
|
||||||
case ENTITY_ID_LATITUDE_ARGUMENT_KEY,
|
|
||||||
ENTITY_ID_LONGITUDE_ARGUMENT_KEY -> {
|
|
||||||
if (!ArgumentType.TS_LATEST.equals(refEntityKey.getType())) {
|
if (!ArgumentType.TS_LATEST.equals(refEntityKey.getType())) {
|
||||||
throw new IllegalArgumentException("Argument '" + argumentKey + "' must be of type TS_LATEST.");
|
throw new IllegalArgumentException("Argument '" + coordinateKey + "' must be of type TS_LATEST.");
|
||||||
}
|
}
|
||||||
if (argument.getRefDynamicSource() != null) {
|
if (argument.getRefDynamicSource() != null) {
|
||||||
throw new IllegalArgumentException("Dynamic source is not allowed for argument: '" + argumentKey + "'.");
|
throw new IllegalArgumentException("Dynamic source is not allowed for argument: '" + coordinateKey + "'.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case ALLOWED_ZONES_ARGUMENT_KEY,
|
}
|
||||||
RESTRICTED_ZONES_ARGUMENT_KEY -> {
|
|
||||||
|
private void validateZoneGroupAruguments(Map<String, Argument> zoneGroupsArguments) {
|
||||||
|
zoneGroupsArguments.forEach((argumentKey, argument) -> {
|
||||||
|
if (argument == null) {
|
||||||
|
throw new IllegalArgumentException("Zone group argument is not configured: " + argumentKey);
|
||||||
|
}
|
||||||
|
ReferencedEntityKey refEntityKey = validateAndGetRefEntityKey(argument, argumentKey);
|
||||||
if (!ArgumentType.ATTRIBUTE.equals(refEntityKey.getType())) {
|
if (!ArgumentType.ATTRIBUTE.equals(refEntityKey.getType())) {
|
||||||
throw new IllegalArgumentException("Argument '" + argumentKey + "' must be of type ATTRIBUTE.");
|
throw new IllegalArgumentException("Argument '" + argumentKey + "' must be of type ATTRIBUTE.");
|
||||||
}
|
}
|
||||||
var dynamicSource = argument.getRefDynamicSource();
|
var dynamicSource = argument.getRefDynamicSource();
|
||||||
if (dynamicSource == null) {
|
if (dynamicSource == null) {
|
||||||
continue;
|
return;
|
||||||
}
|
}
|
||||||
if (!RELATION_QUERY.equals(dynamicSource)) {
|
if (!RELATION_QUERY.equals(dynamicSource)) {
|
||||||
throw new IllegalArgumentException("Only relation query dynamic source is supported for argument: '" + argumentKey + "'.");
|
throw new IllegalArgumentException("Only relation query dynamic source is supported for argument: '" + argumentKey + "'.");
|
||||||
@ -121,9 +131,22 @@ public class GeofencingCalculatedFieldConfiguration extends BaseCalculatedFieldC
|
|||||||
throw new IllegalArgumentException("Missing dynamic source configuration for argument: '" + argumentKey + "'.");
|
throw new IllegalArgumentException("Missing dynamic source configuration for argument: '" + argumentKey + "'.");
|
||||||
}
|
}
|
||||||
argument.getRefDynamicSourceConfiguration().validate();
|
argument.getRefDynamicSourceConfiguration().validate();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Map<String, Argument> getZoneGroupArguments() {
|
||||||
|
return arguments.entrySet()
|
||||||
|
.stream()
|
||||||
|
.filter(entry -> !coordinateKeys.contains(entry.getKey()))
|
||||||
|
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static ReferencedEntityKey validateAndGetRefEntityKey(Argument argument, String argumentKey) {
|
||||||
|
ReferencedEntityKey refEntityKey = argument.getRefEntityKey();
|
||||||
|
if (refEntityKey == null || refEntityKey.getType() == null) {
|
||||||
|
throw new IllegalArgumentException("Missing or invalid reference entity key for argument: " + argumentKey);
|
||||||
}
|
}
|
||||||
|
return refEntityKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,49 @@
|
|||||||
|
/**
|
||||||
|
* Copyright © 2016-2025 The Thingsboard Authors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.thingsboard.server.common.data.cf.configuration;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
public enum GeofencingEvent {
|
||||||
|
|
||||||
|
ENTERED(0), LEFT(1), INSIDE(2), OUTSIDE(3);
|
||||||
|
|
||||||
|
private final int protoNumber; // Corresponds to GeofencingEvent
|
||||||
|
|
||||||
|
GeofencingEvent(int protoNumber) {
|
||||||
|
this.protoNumber = protoNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final GeofencingEvent[] BY_PROTO;
|
||||||
|
|
||||||
|
static {
|
||||||
|
BY_PROTO = new GeofencingEvent[Arrays.stream(values()).mapToInt(GeofencingEvent::getProtoNumber).max().orElse(0) + 1];
|
||||||
|
for (var event : values()) {
|
||||||
|
BY_PROTO[event.getProtoNumber()] = event;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static GeofencingEvent fromProtoNumber(int protoNumber) {
|
||||||
|
if (protoNumber < 0 || protoNumber >= BY_PROTO.length) {
|
||||||
|
throw new IllegalArgumentException("Invalid GeofencingEvent proto number " + protoNumber);
|
||||||
|
}
|
||||||
|
return BY_PROTO[protoNumber];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,28 @@
|
|||||||
|
/**
|
||||||
|
* Copyright © 2016-2025 The Thingsboard Authors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.thingsboard.server.common.data.cf.configuration;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class GeofencingZoneGroupConfiguration {
|
||||||
|
|
||||||
|
private final String reportTelemetryPrefix;
|
||||||
|
private final List<GeofencingEvent> reportEvents;
|
||||||
|
|
||||||
|
}
|
||||||
@ -908,9 +908,18 @@ message GeofencingZoneProto {
|
|||||||
optional bool inside = 5;
|
optional bool inside = 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum GeofencingEventProto {
|
||||||
|
ENTERED = 0;
|
||||||
|
LEFT = 1;
|
||||||
|
INSIDE = 2;
|
||||||
|
OUTSIDE = 3;
|
||||||
|
}
|
||||||
|
|
||||||
message GeofencingArgumentProto {
|
message GeofencingArgumentProto {
|
||||||
string argName = 1; // e.g., "restrictedZones" or "allowedZones"
|
string argName = 1;
|
||||||
repeated GeofencingZoneProto zones = 2;
|
string telemetryPrefix = 2;
|
||||||
|
repeated GeofencingEventProto reportEvents = 3;
|
||||||
|
repeated GeofencingZoneProto zones = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
message CalculatedFieldStateProto {
|
message CalculatedFieldStateProto {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user