From 876b9eeda4253926ee797609aa33187d6e2cf4c1 Mon Sep 17 00:00:00 2001 From: Andrew Shvayka Date: Thu, 28 Mar 2019 20:38:35 +0200 Subject: [PATCH] Initial rule nodes commit --- pom.xml | 12 ++ rule-engine/rule-engine-components/pom.xml | 16 ++- .../engine/geo/AbstractGeofencingNode.java | 120 +++++++++++++++++ .../rule/engine/geo/Coordinates.java | 24 ++++ .../engine/geo/EntityGeofencingState.java | 29 +++++ .../thingsboard/rule/engine/geo/GeoUtil.java | 68 ++++++++++ .../rule/engine/geo/Perimeter.java | 34 +++++ .../rule/engine/geo/PerimeterType.java | 20 +++ .../rule/engine/geo/RangeUnit.java | 30 +++++ .../engine/geo/TbGpsGeofencingActionNode.java | 121 ++++++++++++++++++ ...bGpsGeofencingActionNodeConfiguration.java | 45 +++++++ .../engine/geo/TbGpsGeofencingFilterNode.java | 71 ++++++++++ ...bGpsGeofencingFilterNodeConfiguration.java | 47 +++++++ 13 files changed, 633 insertions(+), 4 deletions(-) create mode 100644 rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/AbstractGeofencingNode.java create mode 100644 rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/Coordinates.java create mode 100644 rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/EntityGeofencingState.java create mode 100644 rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/GeoUtil.java create mode 100644 rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/Perimeter.java create mode 100644 rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/PerimeterType.java create mode 100644 rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/RangeUnit.java create mode 100644 rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingActionNode.java create mode 100644 rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingActionNodeConfiguration.java create mode 100644 rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingFilterNode.java create mode 100644 rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingFilterNodeConfiguration.java diff --git a/pom.xml b/pom.xml index 67a2eef187..1298edab9d 100755 --- a/pom.xml +++ b/pom.xml @@ -71,6 +71,8 @@ 3.0.2 2.6.1 1.0.0 + 0.7 + 1.15.0 1.56 2.0.1 2.4.0 @@ -828,6 +830,16 @@ springfox-swagger-ui-rfc6570 ${springfox-swagger-ui-rfc6570.version} + + org.locationtech.spatial4j + spatial4j + ${spatial4j.version} + + + org.locationtech.jts + jts-core + ${jts.version} + diff --git a/rule-engine/rule-engine-components/pom.xml b/rule-engine/rule-engine-components/pom.xml index 01253f0acc..64a6548dc8 100644 --- a/rule-engine/rule-engine-components/pom.xml +++ b/rule-engine/rule-engine-components/pom.xml @@ -97,6 +97,14 @@ org.bouncycastle bcpkix-jdk15on + + org.locationtech.spatial4j + spatial4j + + + org.locationtech.jts + jts-core + junit junit @@ -142,10 +150,10 @@ true true - - - - + + + + diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/AbstractGeofencingNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/AbstractGeofencingNode.java new file mode 100644 index 0000000000..f876967917 --- /dev/null +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/AbstractGeofencingNode.java @@ -0,0 +1,120 @@ +/** + * Copyright © 2016-2019 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.rule.engine.geo; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import org.locationtech.spatial4j.context.jts.JtsSpatialContext; +import org.locationtech.spatial4j.context.jts.JtsSpatialContextFactory; +import org.springframework.util.StringUtils; +import org.thingsboard.rule.engine.api.TbContext; +import org.thingsboard.rule.engine.api.TbNode; +import org.thingsboard.rule.engine.api.TbNodeConfiguration; +import org.thingsboard.rule.engine.api.TbNodeException; +import org.thingsboard.rule.engine.api.util.TbNodeUtils; +import org.thingsboard.server.common.msg.TbMsg; + +import java.util.Collections; +import java.util.List; + +public abstract class AbstractGeofencingNode implements TbNode { + + protected T config; + protected JtsSpatialContext jtsCtx; + + @Override + public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException { + this.config = TbNodeUtils.convert(configuration, getConfigClazz()); + JtsSpatialContextFactory factory = new JtsSpatialContextFactory(); + factory.normWrapLongitude = true; + jtsCtx = factory.newSpatialContext(); + } + + abstract protected Class getConfigClazz(); + + protected boolean checkMatches(TbMsg msg) throws TbNodeException { + JsonElement msgDataElement = new JsonParser().parse(msg.getData()); + if (!msgDataElement.isJsonObject()) { + throw new TbNodeException("Incoming Message is not a valid JSON object"); + } + JsonObject msgDataObj = msgDataElement.getAsJsonObject(); + double latitude = getValueFromMessageByName(msg, msgDataObj, config.getLatitudeKeyName()); + double longitude = getValueFromMessageByName(msg, msgDataObj, config.getLongitudeKeyName()); + List perimeters = getPerimeters(msg, msgDataObj); + boolean matches = false; + for (Perimeter perimeter : perimeters) { + if (checkMatches(perimeter, latitude, longitude)) { + matches = true; + break; + } + } + return matches; + } + + protected boolean checkMatches(Perimeter perimeter, double latitude, double longitude) throws TbNodeException { + if (perimeter.getPerimeterType() == PerimeterType.CIRCLE) { + Coordinates entityCoordinates = new Coordinates(latitude, longitude); + Coordinates perimeterCoordinates = new Coordinates(perimeter.getCenterLatitude(), perimeter.getCenterLongitude()); + return perimeter.getRange() > GeoUtil.distance(entityCoordinates, perimeterCoordinates, perimeter.getRangeUnit()); + } else if (perimeter.getPerimeterType() == PerimeterType.POLYGON) { + return GeoUtil.contains(perimeter.getPolygonsDefinition(), new Coordinates(latitude, longitude)); + } else { + throw new TbNodeException("Unsupported perimeter type: " + perimeter.getPerimeterType()); + } + } + + protected List getPerimeters(TbMsg msg, JsonObject msgDataObj) throws TbNodeException { + if (config.isFetchPerimeterInfoFromMessage()) { + //TODO: add fetching perimeters from the message itself, if configuration is empty. + if (!StringUtils.isEmpty(msg.getMetaData().getValue("perimeter"))) { + Perimeter perimeter = new Perimeter(); + perimeter.setPerimeterType(PerimeterType.POLYGON); + perimeter.setPolygonsDefinition(msg.getMetaData().getValue("perimeter")); + return Collections.singletonList(perimeter); + } else if (!StringUtils.isEmpty(msg.getMetaData().getValue("centerLatitude"))) { + Perimeter perimeter = new Perimeter(); + perimeter.setPerimeterType(PerimeterType.CIRCLE); + perimeter.setCenterLatitude(Double.parseDouble(msg.getMetaData().getValue("centerLatitude"))); + perimeter.setCenterLongitude(Double.parseDouble(msg.getMetaData().getValue("centerLongitude"))); + perimeter.setRange(Double.parseDouble(msg.getMetaData().getValue("range"))); + perimeter.setRangeUnit(RangeUnit.valueOf(msg.getMetaData().getValue("rangeUnit"))); + return Collections.singletonList(perimeter); + } else { + throw new TbNodeException("Missing perimeter definition!"); + } + } else { + return config.getPerimeters(); + } + } + + protected Double getValueFromMessageByName(TbMsg msg, JsonObject msgDataObj, String keyName) throws TbNodeException { + double value; + if (msgDataObj.has(keyName) && msgDataObj.get(keyName).isJsonPrimitive()) { + value = msgDataObj.get(keyName).getAsDouble(); + } else { + String valueStr = msg.getMetaData().getValue(keyName); + if (!StringUtils.isEmpty(valueStr)) { + value = Double.parseDouble(valueStr); + } else { + throw new TbNodeException("Incoming Message has no " + keyName + " in data or metadata!"); + } + } + return value; + } + + +} diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/Coordinates.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/Coordinates.java new file mode 100644 index 0000000000..2dc4bcf6b2 --- /dev/null +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/Coordinates.java @@ -0,0 +1,24 @@ +/** + * Copyright © 2016-2019 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.rule.engine.geo; + +import lombok.Data; + +@Data +public class Coordinates { + private final double latitude; + private final double longitude; +} diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/EntityGeofencingState.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/EntityGeofencingState.java new file mode 100644 index 0000000000..4024cbf22e --- /dev/null +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/EntityGeofencingState.java @@ -0,0 +1,29 @@ +/** + * Copyright © 2016-2019 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.rule.engine.geo; + +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +@AllArgsConstructor +public class EntityGeofencingState { + + private boolean inside; + private long stateSwitchTime; + private boolean staid; + +} diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/GeoUtil.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/GeoUtil.java new file mode 100644 index 0000000000..3e1918e6bf --- /dev/null +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/GeoUtil.java @@ -0,0 +1,68 @@ +/** + * Copyright © 2016-2019 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.rule.engine.geo; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonParser; +import org.locationtech.spatial4j.context.SpatialContext; +import org.locationtech.spatial4j.context.jts.JtsSpatialContext; +import org.locationtech.spatial4j.context.jts.JtsSpatialContextFactory; +import org.locationtech.spatial4j.distance.DistanceUtils; +import org.locationtech.spatial4j.shape.Point; +import org.locationtech.spatial4j.shape.Shape; +import org.locationtech.spatial4j.shape.ShapeFactory; +import org.locationtech.spatial4j.shape.SpatialRelation; + +public class GeoUtil { + + private static final SpatialContext distCtx = SpatialContext.GEO; + private static final JtsSpatialContext jtsCtx; + + static { + JtsSpatialContextFactory factory = new JtsSpatialContextFactory(); + factory.normWrapLongitude = true; + jtsCtx = factory.newSpatialContext(); + } + + public static synchronized double distance(Coordinates x, Coordinates y, RangeUnit unit) { + Point xLL = distCtx.getShapeFactory().pointXY(x.getLongitude(), x.getLatitude()); + Point yLL = distCtx.getShapeFactory().pointXY(y.getLongitude(), y.getLatitude()); + return unit.fromKm(distCtx.getDistCalc().distance(xLL, yLL) * DistanceUtils.DEG_TO_KM); + } + + public static synchronized boolean contains(String polygon, Coordinates coordinates) { + ShapeFactory.PolygonBuilder polygonBuilder = jtsCtx.getShapeFactory().polygon(); + JsonArray polygonArray = new JsonParser().parse(polygon).getAsJsonArray(); + boolean first = true; + double firstLat = 0.0; + double firstLng = 0.0; + for (JsonElement jsonElement : polygonArray) { + double lat = jsonElement.getAsJsonArray().get(0).getAsDouble(); + double lng = jsonElement.getAsJsonArray().get(1).getAsDouble(); + if (first) { + firstLat = lat; + firstLng = lng; + first = false; + } + polygonBuilder.pointXY(jtsCtx.getShapeFactory().normX(lng), jtsCtx.getShapeFactory().normY(lat)); + } + polygonBuilder.pointXY(jtsCtx.getShapeFactory().normX(firstLng), jtsCtx.getShapeFactory().normY(firstLat)); + Shape shape = polygonBuilder.buildOrRect(); + Point point = jtsCtx.makePoint(coordinates.getLongitude(), coordinates.getLatitude()); + return shape.relate(point).equals(SpatialRelation.CONTAINS); + } +} diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/Perimeter.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/Perimeter.java new file mode 100644 index 0000000000..6ae3372d9f --- /dev/null +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/Perimeter.java @@ -0,0 +1,34 @@ +/** + * Copyright © 2016-2019 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.rule.engine.geo; + +import lombok.Data; + +@Data +public class Perimeter { + + private PerimeterType perimeterType; + + //For Polygons + private String polygonsDefinition; + + //For Circles + private Double centerLatitude; + private Double centerLongitude; + private Double range; + private RangeUnit rangeUnit; + +} diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/PerimeterType.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/PerimeterType.java new file mode 100644 index 0000000000..8f9d86370d --- /dev/null +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/PerimeterType.java @@ -0,0 +1,20 @@ +/** + * Copyright © 2016-2019 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.rule.engine.geo; + +public enum PerimeterType { + CIRCLE, POLYGON +} diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/RangeUnit.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/RangeUnit.java new file mode 100644 index 0000000000..509d829552 --- /dev/null +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/RangeUnit.java @@ -0,0 +1,30 @@ +/** + * Copyright © 2016-2019 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.rule.engine.geo; + +public enum RangeUnit { + METER(1000.0), KILOMETER(1.0), FOOT(3280.84), MILE(0.62137), NAUTICAL_MILE(0.539957); + + private final double fromKm; + + RangeUnit(double fromKm) { + this.fromKm = fromKm; + } + + public double fromKm(double v) { + return v * fromKm; + } +} diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingActionNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingActionNode.java new file mode 100644 index 0000000000..952109286f --- /dev/null +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingActionNode.java @@ -0,0 +1,121 @@ +/** + * Copyright © 2016-2019 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.rule.engine.geo; + +import com.google.gson.Gson; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import lombok.extern.slf4j.Slf4j; +import org.thingsboard.rule.engine.api.RuleNode; +import org.thingsboard.rule.engine.api.TbContext; +import org.thingsboard.rule.engine.api.TbNodeException; +import org.thingsboard.server.common.data.DataConstants; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.kv.AttributeKvEntry; +import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; +import org.thingsboard.server.common.data.kv.StringDataEntry; +import org.thingsboard.server.common.data.plugin.ComponentType; +import org.thingsboard.server.common.msg.TbMsg; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +/** + * Created by ashvayka on 19.01.18. + */ +@Slf4j +@RuleNode( + type = ComponentType.ACTION, + name = "gps geofencing events", + configClazz = TbGpsGeofencingFilterNodeConfiguration.class, + relationTypes = {"Entered", "Left", "Stays Inside", "Stays Outside"}, + nodeDescription = "Produces incoming messages using GPS based geofencing", + nodeDetails = "Extracts latitude and longitude parameters from incoming message and returns different events based on configuration parameters") +public class TbGpsGeofencingActionNode extends AbstractGeofencingNode { + + private final Map entityStates = new HashMap<>(); + private final Gson gson = new Gson(); + private final JsonParser parser = new JsonParser(); + + @Override + public void onMsg(TbContext ctx, TbMsg msg) throws TbNodeException { + boolean matches = checkMatches(msg); + long ts = System.currentTimeMillis(); + + EntityGeofencingState entityState = entityStates.computeIfAbsent(msg.getOriginator(), key -> { + try { + Optional entry = ctx.getAttributesService() + .find(ctx.getTenantId(), msg.getOriginator(), DataConstants.SERVER_SCOPE, ctx.getNodeId()) + .get(1, TimeUnit.MINUTES); + if (entry.isPresent()) { + JsonObject element = parser.parse(entry.get().getValueAsString()).getAsJsonObject(); + return new EntityGeofencingState(element.get("inside").getAsBoolean(), element.get("stateSwitchTime").getAsLong(), element.get("staid").getAsBoolean()); + } else { + return new EntityGeofencingState(false, 0L, false); + } + } catch (InterruptedException | TimeoutException | ExecutionException e) { + throw new RuntimeException(e); + } + }); + if (entityState.getStateSwitchTime() == 0L || entityState.isInside() != matches) { + switchState(ctx, msg.getOriginator(), entityState, matches, ts); + ctx.tellNext(msg, matches ? "Entered" : "Left"); + } else if (!entityState.isStaid()) { + long stayTime = ts - entityState.getStateSwitchTime(); + if (stayTime > (entityState.isInside() ? config.getMinInsideDuration() : config.getMinOutsideDuration())) { + setStaid(ctx, msg.getOriginator(), entityState); + } + } + } + + private void switchState(TbContext ctx, EntityId entityId, EntityGeofencingState entityState, boolean matches, long ts) { + entityState.setInside(matches); + entityState.setStateSwitchTime(ts); + entityState.setStaid(false); + persist(ctx, entityId, entityState); + } + + private void setStaid(TbContext ctx, EntityId entityId, EntityGeofencingState entityState) { + entityState.setStaid(true); + persist(ctx, entityId, entityState); + } + + private void persist(TbContext ctx, EntityId entityId, EntityGeofencingState entityState) { + JsonObject object = new JsonObject(); + object.addProperty("inside", entityState.isInside()); + object.addProperty("stateSwitchTime", entityState.getStateSwitchTime()); + object.addProperty("staid", entityState.isStaid()); + AttributeKvEntry entry = new BaseAttributeKvEntry(new StringDataEntry(ctx.getNodeId(), gson.toJson(object)), System.currentTimeMillis()); + List attributeKvEntryList = Collections.singletonList(entry); + ctx.getAttributesService().save(ctx.getTenantId(), entityId, DataConstants.SERVER_SCOPE, attributeKvEntryList); + } + + @Override + public void destroy() { + + } + + @Override + protected Class getConfigClazz() { + return TbGpsGeofencingActionNodeConfiguration.class; + } +} diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingActionNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingActionNodeConfiguration.java new file mode 100644 index 0000000000..dac9e7d23d --- /dev/null +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingActionNodeConfiguration.java @@ -0,0 +1,45 @@ +/** + * Copyright © 2016-2019 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.rule.engine.geo; + +import lombok.Data; +import org.thingsboard.rule.engine.api.NodeConfiguration; + +import java.util.Collections; +import java.util.List; +import java.util.concurrent.TimeUnit; + +/** + * Created by ashvayka on 19.01.18. + */ +@Data +public class TbGpsGeofencingActionNodeConfiguration extends TbGpsGeofencingFilterNodeConfiguration { + + private double minInsideDuration; + private double minOutsideDuration; + + @Override + public TbGpsGeofencingActionNodeConfiguration defaultConfiguration() { + TbGpsGeofencingActionNodeConfiguration configuration = new TbGpsGeofencingActionNodeConfiguration(); + configuration.setLatitudeKeyName("latitude"); + configuration.setLongitudeKeyName("longitude"); + configuration.setFetchPerimeterInfoFromMessage(true); + configuration.setPerimeters(Collections.emptyList()); + configuration.setMinInsideDuration(TimeUnit.MINUTES.toMillis(1)); + configuration.setMinOutsideDuration(TimeUnit.MINUTES.toMillis(1)); + return configuration; + } +} diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingFilterNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingFilterNode.java new file mode 100644 index 0000000000..66a23ca2a8 --- /dev/null +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingFilterNode.java @@ -0,0 +1,71 @@ +/** + * Copyright © 2016-2019 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.rule.engine.geo; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import lombok.extern.slf4j.Slf4j; +import org.locationtech.spatial4j.context.jts.JtsSpatialContext; +import org.locationtech.spatial4j.context.jts.JtsSpatialContextFactory; +import org.locationtech.spatial4j.shape.Point; +import org.locationtech.spatial4j.shape.Shape; +import org.locationtech.spatial4j.shape.ShapeFactory; +import org.locationtech.spatial4j.shape.SpatialRelation; +import org.springframework.util.StringUtils; +import org.thingsboard.rule.engine.api.RuleNode; +import org.thingsboard.rule.engine.api.TbContext; +import org.thingsboard.rule.engine.api.TbNode; +import org.thingsboard.rule.engine.api.TbNodeConfiguration; +import org.thingsboard.rule.engine.api.TbNodeException; +import org.thingsboard.rule.engine.api.util.TbNodeUtils; +import org.thingsboard.rule.engine.filter.TbMsgTypeFilterNodeConfiguration; +import org.thingsboard.server.common.data.plugin.ComponentType; +import org.thingsboard.server.common.msg.TbMsg; + +import java.util.Collections; +import java.util.List; + +/** + * Created by ashvayka on 19.01.18. + */ +@Slf4j +@RuleNode( + type = ComponentType.FILTER, + name = "gps geofencing filter", + configClazz = TbGpsGeofencingFilterNodeConfiguration.class, + relationTypes = {"True", "False"}, + nodeDescription = "Filter incoming messages by GPS based geofencing", + nodeDetails = "Extracts latitude and longitude parameters from incoming message and returns 'True' if they are inside configured perimeters, 'False' otherwise.") +public class TbGpsGeofencingFilterNode extends AbstractGeofencingNode { + + @Override + public void onMsg(TbContext ctx, TbMsg msg) throws TbNodeException { + boolean matches = checkMatches(msg); + ctx.tellNext(msg, matches ? "True" : "False"); + } + + @Override + public void destroy() { + + } + + @Override + protected Class getConfigClazz() { + return TbGpsGeofencingFilterNodeConfiguration.class; + } +} diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingFilterNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingFilterNodeConfiguration.java new file mode 100644 index 0000000000..7d54699741 --- /dev/null +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingFilterNodeConfiguration.java @@ -0,0 +1,47 @@ +/** + * Copyright © 2016-2019 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.rule.engine.geo; + +import lombok.Data; +import org.thingsboard.rule.engine.api.NodeConfiguration; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.msg.session.SessionMsgType; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/** + * Created by ashvayka on 19.01.18. + */ +@Data +public class TbGpsGeofencingFilterNodeConfiguration implements NodeConfiguration { + + private String latitudeKeyName; + private String longitudeKeyName; + private boolean fetchPerimeterInfoFromMessage; + private List perimeters; + + @Override + public TbGpsGeofencingFilterNodeConfiguration defaultConfiguration() { + TbGpsGeofencingFilterNodeConfiguration configuration = new TbGpsGeofencingFilterNodeConfiguration(); + configuration.setLatitudeKeyName("latitude"); + configuration.setLongitudeKeyName("longitude"); + configuration.setFetchPerimeterInfoFromMessage(true); + configuration.setPerimeters(Collections.emptyList()); + return configuration; + } +}