diff options
-rw-r--r-- | debug.xml | 7 | ||||
-rw-r--r-- | src/org/traccar/BasePipelineFactory.java | 9 | ||||
-rw-r--r-- | src/org/traccar/Main.java | 17 | ||||
-rw-r--r-- | src/org/traccar/database/DataManager.java | 26 | ||||
-rw-r--r-- | src/org/traccar/events/AlertEventHandler.java | 38 | ||||
-rw-r--r-- | src/org/traccar/model/Event.java | 2 | ||||
-rw-r--r-- | src/org/traccar/protocol/H02ProtocolDecoder.java | 16 | ||||
-rw-r--r-- | src/org/traccar/web/WebServer.java | 3 | ||||
-rw-r--r-- | tools/minify.bat | 5 | ||||
-rwxr-xr-x | tools/test-generator.py | 16 | ||||
-rw-r--r-- | web/app/AttributeFormatter.js | 20 | ||||
-rw-r--r-- | web/app/controller/Root.js | 40 | ||||
-rw-r--r-- | web/app/view/Devices.js | 17 | ||||
-rw-r--r-- | web/app/view/Report.js | 5 | ||||
-rw-r--r-- | web/beep.wav | bin | 0 -> 3047 bytes | |||
-rw-r--r-- | web/l10n/en.json | 11 |
16 files changed, 218 insertions, 14 deletions
@@ -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 < :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 Binary files differnew file mode 100644 index 000000000..c2364f114 --- /dev/null +++ b/web/beep.wav 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" |