aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--debug.xml7
-rw-r--r--src/org/traccar/BasePipelineFactory.java9
-rw-r--r--src/org/traccar/Main.java17
-rw-r--r--src/org/traccar/database/DataManager.java26
-rw-r--r--src/org/traccar/events/AlertEventHandler.java38
-rw-r--r--src/org/traccar/model/Event.java2
-rw-r--r--src/org/traccar/protocol/H02ProtocolDecoder.java16
-rw-r--r--src/org/traccar/web/WebServer.java3
-rw-r--r--tools/minify.bat5
-rwxr-xr-xtools/test-generator.py16
-rw-r--r--web/app/AttributeFormatter.js20
-rw-r--r--web/app/controller/Root.js40
-rw-r--r--web/app/view/Devices.js17
-rw-r--r--web/app/view/Report.js5
-rw-r--r--web/beep.wavbin0 -> 3047 bytes
-rw-r--r--web/l10n/en.json11
16 files changed, 218 insertions, 14 deletions
diff --git a/debug.xml b/debug.xml
index 2bd96021f..346eda292 100644
--- a/debug.xml
+++ b/debug.xml
@@ -47,6 +47,7 @@
<entry key='event.globalSpeedLimit'>90</entry>
<entry key='event.motionHandler'>true</entry>
<entry key='event.geofenceHandler'>true</entry>
+ <entry key='event.alertHandler'>true</entry>
<!--<entry key='event.forward.enable'>true</entry>
<entry key='event.forward.url'>http://localhost/</entry>
@@ -83,6 +84,8 @@
<entry key='database.changelog'>./schema/changelog-master.xml</entry>
+ <entry key='database.positionsHistoryDays'>7</entry>
+
<entry key='database.selectServers'>
SELECT * FROM server;
</entry>
@@ -320,6 +323,10 @@
DELETE FROM notifications WHERE id = :id;
</entry>
+ <entry key='database.clearPositionsHistory'>
+ DELETE FROM positions WHERE id != :positionId and deviceid = :deviceId and servertime &lt; :serverTime;
+ </entry>
+
<!-- PROTOCOL CONFIG -->
<entry key='gps103.port'>5001</entry>
diff --git a/src/org/traccar/BasePipelineFactory.java b/src/org/traccar/BasePipelineFactory.java
index 44f1b0657..31845290f 100644
--- a/src/org/traccar/BasePipelineFactory.java
+++ b/src/org/traccar/BasePipelineFactory.java
@@ -33,6 +33,7 @@ import org.traccar.events.CommandResultEventHandler;
import org.traccar.events.GeofenceEventHandler;
import org.traccar.events.MotionEventHandler;
import org.traccar.events.OverspeedEventHandler;
+import org.traccar.events.AlertEventHandler;
import org.traccar.helper.Log;
import java.net.InetSocketAddress;
@@ -52,6 +53,7 @@ public abstract class BasePipelineFactory implements ChannelPipelineFactory {
private OverspeedEventHandler overspeedEventHandler;
private MotionEventHandler motionEventHandler;
private GeofenceEventHandler geofenceEventHandler;
+ private AlertEventHandler alertEventHandler;
private static final class OpenChannelHandler extends SimpleChannelHandler {
@@ -146,6 +148,9 @@ public abstract class BasePipelineFactory implements ChannelPipelineFactory {
if (Context.getConfig().getBoolean("event.geofenceHandler")) {
geofenceEventHandler = new GeofenceEventHandler();
}
+ if (Context.getConfig().getBoolean("event.alertHandler")) {
+ alertEventHandler = new AlertEventHandler();
+ }
}
protected abstract void addSpecificHandlers(ChannelPipeline pipeline);
@@ -207,6 +212,10 @@ public abstract class BasePipelineFactory implements ChannelPipelineFactory {
pipeline.addLast("GeofenceEventHandler", geofenceEventHandler);
}
+ if (alertEventHandler != null) {
+ pipeline.addLast("AlertEventHandler", alertEventHandler);
+ }
+
pipeline.addLast("mainHandler", new MainEventHandler());
return pipeline;
}
diff --git a/src/org/traccar/Main.java b/src/org/traccar/Main.java
index 1b8d93e34..570becc2d 100644
--- a/src/org/traccar/Main.java
+++ b/src/org/traccar/Main.java
@@ -17,9 +17,14 @@ package org.traccar;
import org.traccar.helper.Log;
+import java.sql.SQLException;
+import java.util.Timer;
+import java.util.TimerTask;
import java.util.Locale;
public final class Main {
+ static final long CLEAN_DELAY = 500;
+ static final long CLEAN_PERIOD = 24 * 60 * 60 * 1000;
private Main() {
}
@@ -35,6 +40,18 @@ public final class Main {
Context.getWebServer().start();
}
+ Timer timer = new Timer();
+ timer.scheduleAtFixedRate(new TimerTask() {
+ @Override
+ public void run() {
+ try {
+ Context.getDataManager().clearPositionsHistory();
+ } catch (SQLException error) {
+ Log.warning(error);
+ }
+ }
+ }, CLEAN_DELAY, CLEAN_PERIOD);
+
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
diff --git a/src/org/traccar/database/DataManager.java b/src/org/traccar/database/DataManager.java
index be77a3d36..04d0d44ea 100644
--- a/src/org/traccar/database/DataManager.java
+++ b/src/org/traccar/database/DataManager.java
@@ -304,6 +304,32 @@ public class DataManager {
.executeQuery(Position.class);
}
+ public void clearPositionsHistory() throws SQLException {
+ int histDays = config.getInteger("database.positionsHistoryDays");
+ if (histDays == 0) {
+ return;
+ }
+
+ String sql = getQuery("database.clearPositionsHistory");
+ if (sql == null) {
+ return;
+ }
+
+ for (Device device : getAllDevices()) {
+ Date lastUpdate = device.getLastUpdate();
+ if (lastUpdate != null) {
+
+ Date dateBefore = new Date(lastUpdate.getTime() - histDays * 24 * 3600 * 1000);
+
+ QueryBuilder.create(dataSource, sql)
+ .setLong("positionId", device.getPositionId())
+ .setLong("deviceId", device.getId())
+ .setDate("serverTime", dateBefore)
+ .executeUpdate();
+ }
+ }
+ }
+
public Server getServer() throws SQLException {
return QueryBuilder.create(dataSource, getQuery("database.selectServers"))
.executeQuerySingle(Server.class);
diff --git a/src/org/traccar/events/AlertEventHandler.java b/src/org/traccar/events/AlertEventHandler.java
new file mode 100644
index 000000000..61c2d7b16
--- /dev/null
+++ b/src/org/traccar/events/AlertEventHandler.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2016 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * 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.traccar.events;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+import org.traccar.BaseEventHandler;
+import org.traccar.model.Event;
+import org.traccar.model.Position;
+
+public class AlertEventHandler extends BaseEventHandler {
+
+ @Override
+ protected Collection<Event> analyzePosition(Position position) {
+ Object alarm = position.getAttributes().get(Position.KEY_ALARM);
+ if (alarm != null) {
+ Collection<Event> events = new ArrayList<>();
+ events.add(new Event(Event.TYPE_ALARM, position.getDeviceId(), position.getId()));
+ return events;
+ }
+ return null;
+ }
+
+}
diff --git a/src/org/traccar/model/Event.java b/src/org/traccar/model/Event.java
index 235a39f9a..a2c346688 100644
--- a/src/org/traccar/model/Event.java
+++ b/src/org/traccar/model/Event.java
@@ -48,6 +48,8 @@ public class Event extends Message {
public static final String TYPE_GEOFENCE_ENTER = "geofenceEnter";
public static final String TYPE_GEOFENCE_EXIT = "geofenceExit";
+ public static final String TYPE_ALARM = "alarm";
+
private Date serverTime;
public Date getServerTime() {
diff --git a/src/org/traccar/protocol/H02ProtocolDecoder.java b/src/org/traccar/protocol/H02ProtocolDecoder.java
index b340973e6..83ed4b099 100644
--- a/src/org/traccar/protocol/H02ProtocolDecoder.java
+++ b/src/org/traccar/protocol/H02ProtocolDecoder.java
@@ -65,8 +65,20 @@ public class H02ProtocolDecoder extends BaseProtocolDecoder {
private void processStatus(Position position, long status) {
if (!BitUtil.check(status, 0) || !BitUtil.check(status, 1)
- || !BitUtil.check(status, 3) || !BitUtil.check(status, 4)) {
- position.set(Position.KEY_ALARM, true);
+ || !BitUtil.check(status, 3) || !BitUtil.check(status, 4) || !BitUtil.check(status, 7)) {
+
+ if (!BitUtil.check(status, 0)) {
+ position.set(Position.KEY_ALARM, Position.ALARM_VIBRATION);
+ } else if (!BitUtil.check(status, 1)) {
+ position.set(Position.KEY_ALARM, "robbery");
+ } else if (!BitUtil.check(status, 3)) {
+ position.set(Position.KEY_ALARM, "illegal ignition");
+ } else if (!BitUtil.check(status, 4)) {
+ position.set(Position.KEY_ALARM, "entering");
+ } else if (!BitUtil.check(status, 7)) {
+ position.set(Position.KEY_ALARM, "out");
+ }
+
}
position.set(Position.KEY_IGNITION, !BitUtil.check(status, 10));
position.set(Position.KEY_STATUS, status);
diff --git a/src/org/traccar/web/WebServer.java b/src/org/traccar/web/WebServer.java
index 5527e80f3..4ef31b1df 100644
--- a/src/org/traccar/web/WebServer.java
+++ b/src/org/traccar/web/WebServer.java
@@ -118,6 +118,9 @@ public class WebServer {
resourceHandler.setResourceBase(config.getString("web.path"));
if (config.getBoolean("web.debug")) {
resourceHandler.setWelcomeFiles(new String[] {"debug.html"});
+ //Troubleshooting Locked UI Files on Windows while app is running (like html, js, css, etc...),
+ //you can make changes to the UI Files and refresh the page in the browser without stopping the app first
+ resourceHandler.setMinMemoryMappedContentLength(-1);
} else {
resourceHandler.setWelcomeFiles(new String[] {"release.html", "index.html"});
}
diff --git a/tools/minify.bat b/tools/minify.bat
new file mode 100644
index 000000000..6ab8fd94b
--- /dev/null
+++ b/tools/minify.bat
@@ -0,0 +1,5 @@
+@echo off
+cd C:\[traccar path]\traccar\web
+set SDK=C:\[sencha path]\ext-6.0.0
+
+sencha -sdk %SDK% compile -classpath=app.js,app,%SDK%\packages\core\src,%SDK%\packages\core\overrides,%SDK%\classic\classic\src,%SDK%\classic\classic\overrides exclude -all and include -recursive -file app.js and exclude -namespace=Ext and concatenate -closure app.min.js
diff --git a/tools/test-generator.py b/tools/test-generator.py
index 8d7377c2c..cedbe9c69 100755
--- a/tools/test-generator.py
+++ b/tools/test-generator.py
@@ -3,11 +3,11 @@
import sys
import math
import urllib
-import httplib
+import urllib2
import time
id = '123456789012345'
-server = 'localhost:5055'
+server = 'http://localhost:5055'
period = 1
step = 0.001
@@ -32,10 +32,11 @@ for i in range(0, len(waypoints)):
lon = lon1 + (lon2 - lon1) * j / count
points.append((lat, lon))
-def send(conn, lat, lon, course):
+def send(lat, lon, course, alarm):
params = (('id', id), ('timestamp', int(time.time())), ('lat', lat), ('lon', lon), ('bearing', course))
- conn.request('GET', '?' + urllib.urlencode(params))
- conn.getresponse()
+ if alarm:
+ params = params + (('alarm', 'sos'),)
+ urllib2.urlopen(server + '?' + urllib.urlencode(params)).read()
def course(lat1, lon1, lat2, lon2):
lat1 = lat1 * math.pi / 180
@@ -48,11 +49,10 @@ def course(lat1, lon1, lat2, lon2):
index = 0
-conn = httplib.HTTPConnection(server)
-
while True:
(lat1, lon1) = points[index % len(points)]
(lat2, lon2) = points[(index + 1) % len(points)]
- send(conn, lat1, lon1, course(lat1, lon1, lat2, lon2))
+ alarm = ((index % 10) == 0)
+ send(lat1, lon1, course(lat1, lon1, lat2, lon2), alarm)
time.sleep(period)
index += 1
diff --git a/web/app/AttributeFormatter.js b/web/app/AttributeFormatter.js
index 3432ca1e0..c32849101 100644
--- a/web/app/AttributeFormatter.js
+++ b/web/app/AttributeFormatter.js
@@ -34,6 +34,24 @@ Ext.define('Traccar.AttributeFormatter', {
return Ext.getStore('DistanceUnits').formatValue(value, Traccar.app.getPreference('distanceUnit'));
},
+ alarmFormatter: function (attributes) {
+ var value = '';
+ if (attributes instanceof Object) {//for Traccar.view.Attributes
+ if (attributes.hasOwnProperty('alarm')) {
+ value = attributes.alarm;
+ if (typeof value === 'boolean') {
+ value = (value ? Ext.Msg.buttonText.yes : Ext.Msg.buttonText.no);
+ }
+ }
+ } else {//for Traccar.view.Report
+ value = attributes;
+ if (typeof value === 'boolean') {
+ value = (value ? Ext.Msg.buttonText.yes : Ext.Msg.buttonText.no);
+ }
+ }
+ return '<span style="color:red;">' + value + '</span>';
+ },
+
defaultFormatter: function (value) {
if (typeof value === 'number') {
return Number(value.toFixed(Traccar.Style.numberPrecision));
@@ -58,6 +76,8 @@ Ext.define('Traccar.AttributeFormatter', {
return this.courseFormatter;
} else if (key === 'distance' || key === 'odometer') {
return this.distanceFormatter;
+ } else if (key === 'alarm') {
+ return this.alarmFormatter;
} else {
return this.defaultFormatter;
}
diff --git a/web/app/controller/Root.js b/web/app/controller/Root.js
index 98ded9c47..53f3b83df 100644
--- a/web/app/controller/Root.js
+++ b/web/app/controller/Root.js
@@ -81,7 +81,7 @@ Ext.define('Traccar.controller.Root', {
this.asyncUpdate(true);
}
});
- Ext.get('attribution').remove();
+ if (Ext.get('attribution') !== null) Ext.get('attribution').remove();
if (this.isPhone) {
Ext.create('widget.mainMobile');
} else {
@@ -89,6 +89,17 @@ Ext.define('Traccar.controller.Root', {
}
},
+ beep: function () {
+ if (!this.beepSound) {
+ this.beepSound = new Audio('beep.wav');
+ }
+ this.beepSound.play();
+ },
+
+ showNotificationsSelected: function () {
+ return Ext.getCmp('showNotificationsButton') && Ext.getCmp('showNotificationsButton').pressed;
+ },
+
asyncUpdate: function (first) {
var protocol, socket, self = this;
protocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
@@ -99,7 +110,7 @@ Ext.define('Traccar.controller.Root', {
};
socket.onmessage = function (event) {
- var i, j, store, data, array, entity, device, typeKey, text, geofence;
+ var i, j, store, data, array, entity, device, typeKey, alarmKey, text, geofence;
data = Ext.decode(event.data);
@@ -145,10 +156,28 @@ Ext.define('Traccar.controller.Root', {
}
}
text = Strings.eventCommandResult + ': ' + text;
+ } else if (array[i].type === 'alarm' && data.positions) {
+ alarmKey = 'alarm';
+ text = Strings[alarmKey];
+ if (!text) {
+ text = alarmKey;
+ }
+ for (j = 0; j < data.positions.length; j++) {
+ if (data.positions[j].id === array[i].positionId && data.positions[j].attributes.alarm !== null) {
+ if (typeof data.positions[j].attributes.alarm === 'string' && data.positions[j].attributes.alarm.length >= 2) {
+ alarmKey = 'alarm' + data.positions[j].attributes.alarm.charAt(0).toUpperCase() + data.positions[j].attributes.alarm.slice(1);
+ text = Strings[alarmKey];
+ if (!text) {
+ text = alarmKey;
+ }
+ }
+ break;
+ }
+ }
} else {
typeKey = 'event' + array[i].type.charAt(0).toUpperCase() + array[i].type.slice(1);
text = Strings[typeKey];
- if (typeof text === 'undefined') {
+ if (!text) {
text = typeKey;
}
}
@@ -160,7 +189,10 @@ Ext.define('Traccar.controller.Root', {
}
device = Ext.getStore('Devices').getById(array[i].deviceId);
if (typeof device !== 'undefined') {
- Ext.toast(text, device.getData().name);
+ if (self.showNotificationsSelected()) {
+ self.beep();
+ Ext.toast(text, device.get('name'));
+ }
}
}
}
diff --git a/web/app/view/Devices.js b/web/app/view/Devices.js
index 1a70dfef8..212aa7f96 100644
--- a/web/app/view/Devices.js
+++ b/web/app/view/Devices.js
@@ -57,6 +57,23 @@ Ext.define('Traccar.view.Devices', {
tooltipType: 'title'
}, {
xtype: 'tbfill'
+ },{
+ id: 'showNotificationsButton',
+ glyph: 'xf0a2@FontAwesome',
+ tooltip: Strings.showNotifications,
+ tooltipType: 'title',
+ pressed : true,
+ enableToggle: true,
+ listeners: {
+ toggle: function (button, pressed) {
+ if (pressed) {
+ button.setGlyph('xf0a2@FontAwesome');
+ } else {
+ button.setGlyph('xf1f7@FontAwesome');
+ }
+ },
+ scope: this
+ }
}, {
id: 'deviceFollowButton',
glyph: 'xf05b@FontAwesome',
diff --git a/web/app/view/Report.js b/web/app/view/Report.js
index 4261b9040..78ff5d52f 100644
--- a/web/app/view/Report.js
+++ b/web/app/view/Report.js
@@ -115,5 +115,10 @@ Ext.define('Traccar.view.Report', {
dataIndex: 'address',
flex: 1,
renderer: Traccar.AttributeFormatter.getFormatter('address')
+ }, {
+ text: 'Alarm',
+ dataIndex: 'attributes',
+ flex: 1,
+ renderer: Traccar.AttributeFormatter.getFormatter('alarm')
}]
});
diff --git a/web/beep.wav b/web/beep.wav
new file mode 100644
index 000000000..c2364f114
--- /dev/null
+++ b/web/beep.wav
Binary files differ
diff --git a/web/l10n/en.json b/web/l10n/en.json
index e97bb45b1..d8dd016b5 100644
--- a/web/l10n/en.json
+++ b/web/l10n/en.json
@@ -19,6 +19,7 @@
"sharedSearch": "Search",
"sharedGeofence": "Geofence",
"sharedGeofences": "Geofences",
+ "showNotifications": "Show Notifications",
"sharedNotifications": "Notifications",
"sharedAttributes": "Attributes",
"sharedAttribute": "Attribute",
@@ -118,6 +119,16 @@
"eventCommandResult": "Command result",
"eventGeofenceEnter": "Device has entered geofence",
"eventGeofenceExit": "Device has exited geofence",
+ "eventAlarm": "Alarms",
+ "alarm": "Alarm",
+ "alarmSos": "SOS Alarm",
+ "alarmVibration": "Vibration Alarm",
+ "alarmMovement": "Movement Alarm",
+ "alarmOverspeed": "Overspeed Alarm",
+ "alarmFallDown": "FallDown Alarm",
+ "alarmLowBattery": "LowBattery Alarm",
+ "alarmMotion": "Motion Alarm",
+ "alarmFault": "Fault Alarm",
"notificationType": "Type of Notification",
"notificationWeb": "Send via Web",
"notificationMail": "Send via Mail"