Initial rule nodes commit
This commit is contained in:
parent
f41cd97cbb
commit
876b9eeda4
12
pom.xml
12
pom.xml
@ -71,6 +71,8 @@
|
|||||||
<jar-plugin.version>3.0.2</jar-plugin.version>
|
<jar-plugin.version>3.0.2</jar-plugin.version>
|
||||||
<springfox-swagger.version>2.6.1</springfox-swagger.version>
|
<springfox-swagger.version>2.6.1</springfox-swagger.version>
|
||||||
<springfox-swagger-ui-rfc6570.version>1.0.0</springfox-swagger-ui-rfc6570.version>
|
<springfox-swagger-ui-rfc6570.version>1.0.0</springfox-swagger-ui-rfc6570.version>
|
||||||
|
<spatial4j.version>0.7</spatial4j.version>
|
||||||
|
<jts.version>1.15.0</jts.version>
|
||||||
<bouncycastle.version>1.56</bouncycastle.version>
|
<bouncycastle.version>1.56</bouncycastle.version>
|
||||||
<winsw.version>2.0.1</winsw.version>
|
<winsw.version>2.0.1</winsw.version>
|
||||||
<hsqldb.version>2.4.0</hsqldb.version>
|
<hsqldb.version>2.4.0</hsqldb.version>
|
||||||
@ -828,6 +830,16 @@
|
|||||||
<artifactId>springfox-swagger-ui-rfc6570</artifactId>
|
<artifactId>springfox-swagger-ui-rfc6570</artifactId>
|
||||||
<version>${springfox-swagger-ui-rfc6570.version}</version>
|
<version>${springfox-swagger-ui-rfc6570.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.locationtech.spatial4j</groupId>
|
||||||
|
<artifactId>spatial4j</artifactId>
|
||||||
|
<version>${spatial4j.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.locationtech.jts</groupId>
|
||||||
|
<artifactId>jts-core</artifactId>
|
||||||
|
<version>${jts.version}</version>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</dependencyManagement>
|
</dependencyManagement>
|
||||||
|
|
||||||
|
|||||||
@ -97,6 +97,14 @@
|
|||||||
<groupId>org.bouncycastle</groupId>
|
<groupId>org.bouncycastle</groupId>
|
||||||
<artifactId>bcpkix-jdk15on</artifactId>
|
<artifactId>bcpkix-jdk15on</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.locationtech.spatial4j</groupId>
|
||||||
|
<artifactId>spatial4j</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.locationtech.jts</groupId>
|
||||||
|
<artifactId>jts-core</artifactId>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>junit</groupId>
|
<groupId>junit</groupId>
|
||||||
<artifactId>junit</artifactId>
|
<artifactId>junit</artifactId>
|
||||||
@ -142,10 +150,10 @@
|
|||||||
<executable>true</executable>
|
<executable>true</executable>
|
||||||
<excludeDevtools>true</excludeDevtools>
|
<excludeDevtools>true</excludeDevtools>
|
||||||
<!--<embeddedLaunchScriptProperties>-->
|
<!--<embeddedLaunchScriptProperties>-->
|
||||||
<!--<confFolder>${pkg.installFolder}/conf</confFolder>-->
|
<!--<confFolder>${pkg.installFolder}/conf</confFolder>-->
|
||||||
<!--<logFolder>${pkg.unixLogFolder}</logFolder>-->
|
<!--<logFolder>${pkg.unixLogFolder}</logFolder>-->
|
||||||
<!--<logFilename>${pkg.name}.out</logFilename>-->
|
<!--<logFilename>${pkg.name}.out</logFilename>-->
|
||||||
<!--<initInfoProvides>${pkg.name}</initInfoProvides>-->
|
<!--<initInfoProvides>${pkg.name}</initInfoProvides>-->
|
||||||
<!--</embeddedLaunchScriptProperties>-->
|
<!--</embeddedLaunchScriptProperties>-->
|
||||||
</configuration>
|
</configuration>
|
||||||
<executions>
|
<executions>
|
||||||
|
|||||||
@ -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<T extends TbGpsGeofencingFilterNodeConfiguration> 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<T> 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<Perimeter> 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<Perimeter> 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@ -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;
|
||||||
|
}
|
||||||
@ -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;
|
||||||
|
|
||||||
|
}
|
||||||
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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;
|
||||||
|
|
||||||
|
}
|
||||||
@ -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
|
||||||
|
}
|
||||||
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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<TbGpsGeofencingActionNodeConfiguration> {
|
||||||
|
|
||||||
|
private final Map<EntityId, EntityGeofencingState> 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<AttributeKvEntry> 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<AttributeKvEntry> attributeKvEntryList = Collections.singletonList(entry);
|
||||||
|
ctx.getAttributesService().save(ctx.getTenantId(), entityId, DataConstants.SERVER_SCOPE, attributeKvEntryList);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void destroy() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Class<TbGpsGeofencingActionNodeConfiguration> getConfigClazz() {
|
||||||
|
return TbGpsGeofencingActionNodeConfiguration.class;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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<TbGpsGeofencingFilterNodeConfiguration> {
|
||||||
|
|
||||||
|
@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<TbGpsGeofencingFilterNodeConfiguration> getConfigClazz() {
|
||||||
|
return TbGpsGeofencingFilterNodeConfiguration.class;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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<TbGpsGeofencingFilterNodeConfiguration> {
|
||||||
|
|
||||||
|
private String latitudeKeyName;
|
||||||
|
private String longitudeKeyName;
|
||||||
|
private boolean fetchPerimeterInfoFromMessage;
|
||||||
|
private List<Perimeter> perimeters;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TbGpsGeofencingFilterNodeConfiguration defaultConfiguration() {
|
||||||
|
TbGpsGeofencingFilterNodeConfiguration configuration = new TbGpsGeofencingFilterNodeConfiguration();
|
||||||
|
configuration.setLatitudeKeyName("latitude");
|
||||||
|
configuration.setLongitudeKeyName("longitude");
|
||||||
|
configuration.setFetchPerimeterInfoFromMessage(true);
|
||||||
|
configuration.setPerimeters(Collections.emptyList());
|
||||||
|
return configuration;
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user