Events subsystem

This commit is contained in:
Abyss777
2016-05-26 16:14:52 +05:00
parent aa12e5c750
commit c6cc8a87eb
16 changed files with 503 additions and 10 deletions

View File

@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.4.xsd">
<changeSet author="author" id="changelog-3.6">
<createTable tableName="events">
<column name="id" type="INT" autoIncrement="true">
<constraints primaryKey="true" />
</column>
<column name="type" type="VARCHAR(128)">
<constraints nullable="false" />
</column>
<column name="eventtime" type="TIMESTAMP">
<constraints nullable="false" />
</column>
<column name="deviceid" type="INT" />
<column name="positionid" type="INT" />
<column name="attributes" type="VARCHAR(4096)">
<constraints nullable="false" />
</column>
</createTable>
<addForeignKeyConstraint baseTableName="events" baseColumnNames="deviceid" constraintName="fk_event_deviceid" referencedTableName="devices" referencedColumnNames="id" onDelete="CASCADE" />
<addColumn tableName="devices">
<column name="motion" type="VARCHAR(128)" />
</addColumn>
</changeSet>
</databaseChangeLog>

View File

@@ -7,4 +7,5 @@
<include file="changelog-3.3.xml" relativeToChangelogFile="true" />
<include file="changelog-3.5.xml" relativeToChangelogFile="true" />
<include file="changelog-3.6.xml" relativeToChangelogFile="true" />
</databaseChangeLog>

View File

@@ -17,6 +17,13 @@
<entry key='logger.level'>all</entry>
<entry key='logger.file'>/opt/traccar/logs/tracker-server.log</entry>
<entry key='event.suppressrepeated'>60</entry>
<entry key='event.overspeedhandler'>true</entry>
<entry key='event.globalspeedlimit'>90</entry>
<entry key='event.motionhandler'>true</entry>
<!-- DATABASE CONFIG -->
<entry key='database.driver'>org.h2.Driver</entry>
@@ -109,7 +116,7 @@
</entry>
<entry key='database.updateDeviceStatus'>
UPDATE devices SET status = :status, lastUpdate = :lastUpdate WHERE id = :id;
UPDATE devices SET status = :status, lastUpdate = :lastUpdate, motion = :motion WHERE id = :id;
</entry>
<entry key='database.deleteDevice'>
@@ -165,6 +172,23 @@
UPDATE devices SET positionId = :id WHERE id = :deviceId;
</entry>
<entry key='database.selectEvent'>
SELECT * FROM events WHERE id = :id;
</entry>
<entry key='database.insertEvent'>
INSERT INTO events (type, eventTime, deviceId, positionId, attributes)
VALUES (:type, :eventTime, :deviceId, :positionId, :attributes);
</entry>
<entry key='database.selectEvents'>
SELECT * FROM events WHERE deviceId = :deviceId AND type LIKE :type AND eventTime BETWEEN :from AND :to ORDER BY eventTime DESC;
</entry>
<entry key='database.selectLastEvents'>
SELECT * FROM events WHERE deviceId = :deviceId AND type LIKE :type AND eventTime >= NOW() - INTERVAL :interval SECOND ORDER BY eventTime DESC;
</entry>
<!-- PROTOCOL CONFIG -->
<entry key='gps103.port'>5001</entry>

View File

@@ -17,6 +17,13 @@
<entry key='logger.level'>all</entry>
<entry key='logger.file'>[LOG]</entry>
<entry key='event.suppressrepeated'>60</entry>
<entry key='event.overspeedhandler'>true</entry>
<entry key='event.globalspeedlimit'>90</entry>
<entry key='event.motionhandler'>true</entry>
<!-- DATABASE CONFIG -->
<entry key='database.driver'>org.h2.Driver</entry>
@@ -109,7 +116,7 @@
</entry>
<entry key='database.updateDeviceStatus'>
UPDATE devices SET status = :status, lastUpdate = :lastUpdate WHERE id = :id;
UPDATE devices SET status = :status, lastUpdate = :lastUpdate, motion = :motion WHERE id = :id;
</entry>
<entry key='database.deleteDevice'>
@@ -165,6 +172,23 @@
UPDATE devices SET positionId = :id WHERE id = :deviceId;
</entry>
<entry key='database.selectEvent'>
SELECT * FROM events WHERE id = :id;
</entry>
<entry key='database.insertEvent'>
INSERT INTO events (type, eventTime, deviceId, positionId, attributes)
VALUES (:type, :eventTime, :deviceId, :positionId, :attributes);
</entry>
<entry key='database.selectEvents'>
SELECT * FROM events WHERE deviceId = :deviceId AND type LIKE :type AND eventTime BETWEEN :from AND :to ORDER BY eventTime DESC;
</entry>
<entry key='database.selectLastEvents'>
SELECT * FROM events WHERE deviceId = :deviceId AND type LIKE :type AND eventTime >= NOW() - INTERVAL :interval SECOND ORDER BY eventTime DESC;
</entry>
<!-- PROTOCOL CONFIG -->
<entry key='gps103.port'>5001</entry>

View File

@@ -0,0 +1,31 @@
package org.traccar;
import org.traccar.model.Event;
import org.traccar.model.Position;
public abstract class BaseEventHandler extends BaseDataHandler {
private boolean isLastPosition = false;
public boolean isLastPosition() {
return isLastPosition;
}
@Override
protected Position handlePosition(Position position) {
Position lastPosition = Context.getConnectionManager().getLastPosition(position.getDeviceId());
if (lastPosition == null || position.getFixTime().compareTo(lastPosition.getFixTime()) >= 0) {
isLastPosition = true;
}
Event event = analizePosition(position);
if (event != null) {
Context.getConnectionManager().updateEvent(event);
}
return position;
}
protected abstract Event analizePosition(Position position);
}

View File

@@ -29,6 +29,9 @@ import org.jboss.netty.channel.MessageEvent;
import org.jboss.netty.channel.SimpleChannelHandler;
import org.jboss.netty.handler.logging.LoggingHandler;
import org.jboss.netty.handler.timeout.IdleStateHandler;
import org.traccar.events.CommandResultEventHandler;
import org.traccar.events.MotionEventHandler;
import org.traccar.events.OverspeedEventHandler;
import org.traccar.helper.Log;
import java.net.InetSocketAddress;
@@ -44,6 +47,10 @@ public abstract class BasePipelineFactory implements ChannelPipelineFactory {
private LocationProviderHandler locationProviderHandler;
private HemisphereHandler hemisphereHandler;
private CommandResultEventHandler commandResultEventHandler;
private OverspeedEventHandler overspeedEventHandler;
private MotionEventHandler motionEventHandler;
private static final class OpenChannelHandler extends SimpleChannelHandler {
private final TrackerServer server;
@@ -122,6 +129,17 @@ public abstract class BasePipelineFactory implements ChannelPipelineFactory {
|| Context.getConfig().hasKey("location.longitudeHemisphere")) {
hemisphereHandler = new HemisphereHandler();
}
commandResultEventHandler = new CommandResultEventHandler();
if (Context.getConfig().getBoolean("event.overspeedhandler")) {
overspeedEventHandler = new OverspeedEventHandler();
}
if (Context.getConfig().getBoolean("event.motionhandler")) {
motionEventHandler = new MotionEventHandler();
}
}
protected abstract void addSpecificHandlers(ChannelPipeline pipeline);
@@ -162,9 +180,23 @@ public abstract class BasePipelineFactory implements ChannelPipelineFactory {
if (Context.getDataManager() != null) {
pipeline.addLast("dataHandler", new DefaultDataHandler());
}
if (Context.getConfig().getBoolean("forward.enable")) {
pipeline.addLast("webHandler", new WebDataHandler(Context.getConfig().getString("forward.url")));
}
if (commandResultEventHandler != null) {
pipeline.addLast("CommandResultEventHandler", commandResultEventHandler);
}
if (overspeedEventHandler != null) {
pipeline.addLast("OverspeedEventHandler", overspeedEventHandler);
}
if (motionEventHandler != null) {
pipeline.addLast("MotionEventHandler", motionEventHandler);
}
pipeline.addLast("mainHandler", new MainEventHandler());
return pipeline;
}
@@ -181,5 +213,4 @@ public abstract class BasePipelineFactory implements ChannelPipelineFactory {
}
}
}
}

View File

@@ -20,6 +20,7 @@ import org.eclipse.jetty.websocket.api.WebSocketAdapter;
import org.traccar.Context;
import org.traccar.database.ConnectionManager;
import org.traccar.model.Device;
import org.traccar.model.Event;
import org.traccar.model.Position;
import org.traccar.web.JsonConverter;
@@ -32,6 +33,7 @@ public class AsyncSocket extends WebSocketAdapter implements ConnectionManager.U
private static final String KEY_DEVICES = "devices";
private static final String KEY_POSITIONS = "positions";
private static final String KEY_EVENTS = "events";
private long userId;
@@ -65,6 +67,11 @@ public class AsyncSocket extends WebSocketAdapter implements ConnectionManager.U
sendData(KEY_POSITIONS, Collections.singletonList(position));
}
@Override
public void onUpdateEvent(Event event) {
sendData(KEY_EVENTS, Collections.singletonList(event));
}
private void sendData(String key, Collection<?> data) {
if (!data.isEmpty() && isConnected()) {
JsonObjectBuilder json = Json.createObjectBuilder();

View File

@@ -0,0 +1,39 @@
package org.traccar.api.resource;
import java.sql.SQLException;
import java.util.Collection;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import org.traccar.Context;
import org.traccar.api.BaseResource;
import org.traccar.model.Event;
@Path("events")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class EventResource extends BaseResource {
@Path("{id}")
@GET
public Event get(@PathParam("id") long id) throws SQLException {
Event event = Context.getDataManager().getEvent(id);
Context.getPermissionsManager().checkDevice(getUserId(), event.getDeviceId());
return event;
}
@GET
public Collection<Event> get(
@QueryParam("deviceId") long deviceId, @QueryParam("type") String type,
@QueryParam("interval") long interval) throws SQLException {
Context.getPermissionsManager().checkDevice(getUserId(), deviceId);
return Context.getDataManager().getLastEvents(deviceId, type, interval);
}
}

View File

@@ -23,6 +23,7 @@ import org.traccar.GlobalTimer;
import org.traccar.Protocol;
import org.traccar.helper.Log;
import org.traccar.model.Device;
import org.traccar.model.Event;
import org.traccar.model.Position;
import java.net.SocketAddress;
@@ -85,14 +86,26 @@ public class ConnectionManager {
return;
}
device.setStatus(status);
if (time != null) {
device.setLastUpdate(time);
if (status.equals(Device.STATUS_MOVING) || status.equals(Device.STATUS_STOPPED)) {
device.setMotion(status);
} else {
if (!status.equals(device.getStatus())) {
Event event = new Event(Event.DEVICE_OFFLINE, deviceId);
if (status.equals(Device.STATUS_ONLINE)) {
event.setType(Event.DEVICE_ONLINE);
}
updateEvent(event);
}
device.setStatus(status);
Timeout timeout = timeouts.remove(deviceId);
if (timeout != null) {
timeout.cancel();
}
}
Timeout timeout = timeouts.remove(deviceId);
if (timeout != null) {
timeout.cancel();
if (time != null) {
device.setLastUpdate(time);
}
if (status.equals(Device.STATUS_ONLINE)) {
@@ -134,6 +147,22 @@ public class ConnectionManager {
}
}
public synchronized void updateEvent(Event event) {
long deviceId = event.getDeviceId();
try {
Context.getDataManager().addEvent(event);
} catch (SQLException error) {
Log.warning(error);
}
for (long userId : Context.getPermissionsManager().getDeviceUsers(deviceId)) {
if (listeners.containsKey(userId)) {
for (UpdateListener listener : listeners.get(userId)) {
listener.onUpdateEvent(event);
}
}
}
}
public Position getLastPosition(long deviceId) {
return positions.get(deviceId);
}
@@ -154,6 +183,7 @@ public class ConnectionManager {
public interface UpdateListener {
void onUpdateDevice(Device device);
void onUpdatePosition(Position position);
void onUpdateEvent(Event event);
}
public synchronized void addListener(long userId, UpdateListener listener) {

View File

@@ -27,6 +27,7 @@ import org.traccar.Config;
import org.traccar.Context;
import org.traccar.helper.Log;
import org.traccar.model.Device;
import org.traccar.model.Event;
import org.traccar.model.DevicePermission;
import org.traccar.model.Group;
import org.traccar.model.GroupPermission;
@@ -482,4 +483,34 @@ public class DataManager implements IdentityManager {
.setObject(server)
.executeUpdate();
}
public Event getEvent(long eventId) throws SQLException {
return QueryBuilder.create(dataSource, getQuery("database.selectEvent"))
.setLong("id", eventId)
.executeQuerySingle(Event.class);
}
public void addEvent(Event event) throws SQLException {
event.setId(QueryBuilder.create(dataSource, getQuery("database.insertEvent"), true)
.setObject(event)
.executeUpdate());
}
public Collection<Event> getEvents(long deviceId, String type, Date from, Date to) throws SQLException {
return QueryBuilder.create(dataSource, getQuery("database.selectEvents"))
.setLong("deviceId", deviceId)
.setString("type", type)
.setDate("from", from)
.setDate("to", to)
.executeQuery(Event.class);
}
public Collection<Event> getLastEvents(long deviceId, String type, long interval) throws SQLException {
return QueryBuilder.create(dataSource, getQuery("database.selectLastEvents"))
.setLong("deviceId", deviceId)
.setString("type", type)
.setLong("interval", interval)
.executeQuery(Event.class);
}
}

View File

@@ -0,0 +1,18 @@
package org.traccar.events;
import org.traccar.BaseEventHandler;
import org.traccar.model.Event;
import org.traccar.model.Position;
public class CommandResultEventHandler extends BaseEventHandler {
@Override
protected Event analizePosition(Position position) {
Object cmdResult = position.getAttributes().get(Position.KEY_RESULT);
if (cmdResult != null) {
return new Event(Event.COMMAND_RESULT, position.getDeviceId(), position.getId());
}
return null;
}
}

View File

@@ -0,0 +1,55 @@
package org.traccar.events;
import java.sql.SQLException;
import org.traccar.BaseEventHandler;
import org.traccar.Context;
import org.traccar.helper.Log;
import org.traccar.model.Device;
import org.traccar.model.Event;
import org.traccar.model.Position;
public class MotionEventHandler extends BaseEventHandler {
private static final double SPEED_THRESHOLD = 0.01;
private long suppressRepeated;
public MotionEventHandler() {
suppressRepeated = Context.getConfig().getLong("event.suppressrepeated", 60);
}
@Override
protected Event analizePosition(Position position) {
Event event = null;
if (!isLastPosition()) {
return event;
}
double speed = position.getSpeed();
boolean valid = position.getValid();
Device device = Context.getIdentityManager().getDeviceById(position.getDeviceId());
if (device == null) {
return event;
}
String motion = device.getMotion();
if (valid && speed > SPEED_THRESHOLD && !motion.equals(Device.STATUS_MOVING)) {
Context.getConnectionManager().updateDevice(position.getDeviceId(), Device.STATUS_MOVING, null);
event = new Event(Event.DEVICE_MOVING, position.getDeviceId(), position.getId());
} else if (valid && speed < SPEED_THRESHOLD && motion.equals(Device.STATUS_MOVING)) {
Context.getConnectionManager().updateDevice(position.getDeviceId(), Device.STATUS_STOPPED, null);
event = new Event(Event.DEVICE_STOPPED, position.getDeviceId(), position.getId());
}
try {
if (event != null && !Context.getDataManager().getLastEvents(
position.getDeviceId(), event.getType(), suppressRepeated).isEmpty()) {
event = null;
}
} catch (SQLException error) {
Log.warning(error);
}
return event;
}
}

View File

@@ -0,0 +1,79 @@
package org.traccar.events;
import java.sql.SQLException;
import org.traccar.BaseEventHandler;
import org.traccar.Context;
import org.traccar.model.Event;
import org.traccar.model.Position;
import org.traccar.model.Server;
import org.traccar.helper.Log;
//import org.traccar.model.Device;
//import org.traccar.model.Group;
import org.traccar.helper.UnitsConverter;
public class OverspeedEventHandler extends BaseEventHandler {
private double globalSpeedLimit;
private long suppressRepeated;
public OverspeedEventHandler() {
globalSpeedLimit = Context.getConfig().getInteger("event.globalspeedlimit", 0);
suppressRepeated = Context.getConfig().getLong("event.suppressrepeated", 60);
try {
Server server = Context.getDataManager().getServer();
String speedUnit = server.getSpeedUnit();
if (speedUnit != null) {
switch (speedUnit) {
case "kmh" : globalSpeedLimit = UnitsConverter.knotsFromKph(globalSpeedLimit);
break;
case "mph" : globalSpeedLimit = UnitsConverter.knotsFromMph(globalSpeedLimit);
default : break;
}
}
} catch (SQLException error) {
Log.warning(error);
}
}
@Override
protected Event analizePosition(Position position) {
Event event = null;
if (!isLastPosition()) {
return event;
}
double speed = position.getSpeed();
boolean valid = position.getValid();
double deviceSpeedLimit = 0;
double groupSpeedLimit = 0;
// Device device = Context.getIdentityManager().getDeviceById(position.getDeviceId());
// if (device != null) {
// deviceSpeedLimit = device.getSpeedLimit();
// try {
// Group group = Context.getDataManager().getGroupById(device.getGroupId());
// if (group != null) {
// groupSpeedLimit = group.getSpeedLimit();
// }
// } catch (SQLException error) {
// Log.warning(error);
// }
// }
if (valid && globalSpeedLimit != 0 && speed > globalSpeedLimit
|| valid && groupSpeedLimit != 0 && speed > groupSpeedLimit
|| valid && deviceSpeedLimit != 0 && speed > deviceSpeedLimit) {
try {
if (Context.getDataManager().getLastEvents(
position.getDeviceId(), Event.DEVICE_OVERSPEED, suppressRepeated).isEmpty()) {
event = new Event(Event.DEVICE_OVERSPEED, position.getDeviceId(), position.getId());
}
} catch (SQLException error) {
Log.warning(error);
}
}
return event;
}
}

View File

@@ -101,4 +101,17 @@ public class Device {
this.groupId = groupId;
}
public static final String STATUS_MOVING = "moving";
public static final String STATUS_STOPPED = "stopped";
private String motion;
public String getMotion() {
return motion;
}
public void setMotion(String motion) {
this.motion = motion;
}
}

View File

@@ -0,0 +1,74 @@
package org.traccar.model;
import java.util.Date;
public class Event extends Message {
public Event(String type, long deviceId, long positionId) {
this.setType(type);
this.setDeviceId(deviceId);
this.setPositionId(positionId);
this.eventTime = new Date();
}
public Event(String type, long deviceId) {
this.setType(type);
this.setDeviceId(deviceId);
this.eventTime = new Date();
}
public Event() {
}
private long id;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public static final String COMMAND_RESULT = "command-result";
public static final String DEVICE_ONLINE = "device-online";
public static final String DEVICE_OFFLINE = "device-offline";
public static final String DEVICE_MOVING = "device-moving";
public static final String DEVICE_STOPPED = "device-stopped";
public static final String DEVICE_OVERSPEED = "device-overspeed";
public static final String GEOFENCE_ENTER = "geofence-enter";
public static final String GEOFENCE_EXIT = "geofence-exit";
private Date eventTime;
public Date getEventTime() {
if (eventTime != null) {
return new Date(eventTime.getTime());
} else {
return null;
}
}
public void setEventTime(Date eventTime) {
if (eventTime != null) {
this.eventTime = new Date(eventTime.getTime());
} else {
this.eventTime = null;
}
}
private long positionId;
public long getPositionId() {
return positionId;
}
public void setPositionId(long positionId) {
this.positionId = positionId;
}
}

View File

@@ -44,6 +44,7 @@ import org.traccar.api.resource.GroupResource;
import org.traccar.api.resource.DeviceResource;
import org.traccar.api.resource.PositionResource;
import org.traccar.api.resource.CommandTypeResource;
import org.traccar.api.resource.EventResource;
import org.traccar.helper.Log;
import javax.naming.InitialContext;
@@ -148,7 +149,8 @@ public class WebServer {
resourceConfig.register(CorsResponseFilter.class);
resourceConfig.registerClasses(ServerResource.class, SessionResource.class, CommandResource.class,
GroupPermissionResource.class, DevicePermissionResource.class, UserResource.class,
GroupResource.class, DeviceResource.class, PositionResource.class, CommandTypeResource.class);
GroupResource.class, DeviceResource.class, PositionResource.class,
CommandTypeResource.class, EventResource.class);
servletHandler.addServlet(new ServletHolder(new ServletContainer(resourceConfig)), "/*");
handlers.addHandler(servletHandler);