aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--debug.xml9
-rw-r--r--src/org/traccar/BasePipelineFactory.java34
-rw-r--r--src/org/traccar/BaseProtocolDecoder.java2
-rw-r--r--src/org/traccar/Context.java23
-rw-r--r--src/org/traccar/FilterHandler.java17
-rw-r--r--src/org/traccar/LocationProviderHandler.java71
-rw-r--r--src/org/traccar/MainEventHandler.java4
-rw-r--r--src/org/traccar/database/ConnectionManager.java78
-rw-r--r--src/org/traccar/database/DataManager.java6
-rw-r--r--src/org/traccar/location/BaseLocationProvider.java52
-rw-r--r--src/org/traccar/location/LocationProvider.java32
-rw-r--r--src/org/traccar/location/MozillaLocationProvider.java75
-rw-r--r--src/org/traccar/location/OpenCellIdLocationProvider.java63
-rw-r--r--src/org/traccar/model/Event.java3
-rw-r--r--src/org/traccar/model/Position.java10
-rw-r--r--src/org/traccar/protocol/EelinkProtocolDecoder.java5
-rw-r--r--src/org/traccar/protocol/FlextrackProtocolDecoder.java4
-rw-r--r--src/org/traccar/protocol/Gl200ProtocolDecoder.java14
-rw-r--r--src/org/traccar/protocol/Gps103ProtocolDecoder.java36
-rw-r--r--src/org/traccar/protocol/Gt02ProtocolDecoder.java4
-rw-r--r--src/org/traccar/protocol/HuabaoProtocolDecoder.java113
-rw-r--r--src/org/traccar/protocol/Jt600ProtocolDecoder.java9
-rw-r--r--src/org/traccar/protocol/MegastekProtocolDecoder.java28
-rw-r--r--src/org/traccar/protocol/MeiligaoFrameDecoder.java4
-rw-r--r--src/org/traccar/protocol/MeitrackProtocolDecoder.java10
-rw-r--r--src/org/traccar/protocol/SuntechProtocolDecoder.java2
-rw-r--r--src/org/traccar/protocol/T800xProtocolDecoder.java12
-rw-r--r--src/org/traccar/protocol/TeltonikaProtocolDecoder.java2
-rw-r--r--src/org/traccar/protocol/Tk103ProtocolDecoder.java36
-rw-r--r--src/org/traccar/protocol/Tlt2hProtocolDecoder.java2
-rw-r--r--src/org/traccar/protocol/TotemProtocolDecoder.java21
-rw-r--r--src/org/traccar/protocol/WatchProtocolDecoder.java8
-rw-r--r--src/org/traccar/web/AsyncServlet.java47
-rw-r--r--src/org/traccar/web/JsonConverter.java2
-rw-r--r--test/org/traccar/FilterHandlerTest.java4
-rw-r--r--test/org/traccar/ProtocolDecoderTest.java13
-rw-r--r--test/org/traccar/geocode/ReverseGeocoderTest.java4
-rw-r--r--test/org/traccar/location/LocationProviderTest.java49
-rw-r--r--test/org/traccar/protocol/Gps103ProtocolDecoderTest.java6
-rw-r--r--test/org/traccar/protocol/HuabaoProtocolDecoderTest.java21
-rw-r--r--test/org/traccar/protocol/MeiligaoProtocolDecoderTest.java5
-rw-r--r--test/org/traccar/protocol/Tk103ProtocolDecoderTest.java3
-rw-r--r--test/org/traccar/protocol/WatchProtocolDecoderTest.java3
-rw-r--r--test/org/traccar/web/AsyncServletTest.java13
-rw-r--r--web/app.css48
-rw-r--r--web/app/Style.js10
-rw-r--r--web/app/controller/Root.js29
-rw-r--r--web/app/model/Device.js6
-rw-r--r--web/app/view/Devices.js29
-rw-r--r--web/app/view/Login.js2
-rw-r--r--web/app/view/Main.js5
-rw-r--r--web/app/view/Map.js32
-rw-r--r--web/app/view/MapController.js79
-rw-r--r--web/blank.html0
-rw-r--r--web/debug.html7
-rw-r--r--web/l10n/en.json2
-rw-r--r--web/l10n/hu.json1
-rw-r--r--web/l10n/no.json79
-rw-r--r--web/l10n/ro.json80
-rw-r--r--web/l10n/si.json16
-rw-r--r--web/locale.js2
-rw-r--r--web/release.html7
-rw-r--r--web/tests.html11
-rw-r--r--web/tests/010_sanity.t.js2
-rw-r--r--web/tests/index.js6
65 files changed, 1251 insertions, 161 deletions
diff --git a/debug.xml b/debug.xml
index 47f17bdae..426dadc4c 100644
--- a/debug.xml
+++ b/debug.xml
@@ -18,6 +18,11 @@
<entry key='geocoder.type'>nominatim</entry>
<entry key='geocoder.url'>http://nominatim.openstreetmap.org/reverse</entry>
+ <entry key='location.enable'>false</entry>
+ <entry key='location.key'>fake</entry>
+ <entry key='location.mcc'>260</entry>
+ <entry key='location.mnc'>2</entry>
+
<entry key='distance.enable'>true</entry>
<!--<entry key='filter.enable'>true</entry>
@@ -209,6 +214,10 @@
UPDATE device SET name = :name, uniqueId = :uniqueId WHERE id = :id;
</entry>
+ <entry key='database.updateDeviceStatus'>
+ UPDATE device SET status = :status, lastUpdate = :lastUpdate WHERE id = :id;
+ </entry>
+
<entry key='database.deleteDevice'>
DELETE FROM device WHERE id = :id;
</entry>
diff --git a/src/org/traccar/BasePipelineFactory.java b/src/org/traccar/BasePipelineFactory.java
index c0952db86..c011fb015 100644
--- a/src/org/traccar/BasePipelineFactory.java
+++ b/src/org/traccar/BasePipelineFactory.java
@@ -19,6 +19,7 @@ import java.net.InetSocketAddress;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.channel.ChannelEvent;
+import org.jboss.netty.channel.ChannelHandler;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.ChannelPipeline;
import org.jboss.netty.channel.ChannelPipelineFactory;
@@ -39,6 +40,7 @@ public abstract class BasePipelineFactory implements ChannelPipelineFactory {
private FilterHandler filterHandler;
private DistanceHandler distanceHandler;
private ReverseGeocoderHandler reverseGeocoderHandler;
+ private LocationProviderHandler locationProviderHandler;
private static final class OpenChannelHandler extends SimpleChannelHandler {
@@ -102,6 +104,10 @@ public abstract class BasePipelineFactory implements ChannelPipelineFactory {
Context.getReverseGeocoder(), Context.getConfig().getBoolean("geocode.processInvalidPositions"));
}
+ if (Context.getLocationProvider() != null) {
+ locationProviderHandler = new LocationProviderHandler(Context.getLocationProvider());
+ }
+
if (Context.getConfig().getBoolean("distance.enable")) {
distanceHandler = new DistanceHandler();
}
@@ -119,17 +125,26 @@ public abstract class BasePipelineFactory implements ChannelPipelineFactory {
if (Context.isLoggerEnabled()) {
pipeline.addLast("logger", new StandardLoggingHandler());
}
+
addSpecificHandlers(pipeline);
- if (filterHandler != null) {
- pipeline.addLast("filter", filterHandler);
- }
+
if (distanceHandler != null) {
pipeline.addLast("distance", distanceHandler);
}
if (reverseGeocoderHandler != null) {
pipeline.addLast("geocoder", reverseGeocoderHandler);
}
+ if (locationProviderHandler != null) {
+ pipeline.addLast("location", locationProviderHandler);
+ }
pipeline.addLast("remoteAddress", new RemoteAddressHandler());
+
+ addDynamicHandlers(pipeline);
+
+ if (filterHandler != null) {
+ pipeline.addLast("filter", filterHandler);
+ }
+
if (Context.getDataManager() != null) {
pipeline.addLast("dataHandler", new DefaultDataHandler());
}
@@ -140,4 +155,17 @@ public abstract class BasePipelineFactory implements ChannelPipelineFactory {
return pipeline;
}
+ private void addDynamicHandlers(ChannelPipeline pipeline) {
+ if (Context.getConfig().hasKey("extra.handlers")) {
+ String[] handlers = Context.getConfig().getString("extra.handlers").split(",");
+ for (int i = 0; i < handlers.length; i++) {
+ try {
+ pipeline.addLast("extraHandler." + i, (ChannelHandler) Class.forName(handlers[i]).newInstance());
+ } catch (ClassNotFoundException | InstantiationException | IllegalAccessException error) {
+ Log.warning(error);
+ }
+ }
+ }
+ }
+
}
diff --git a/src/org/traccar/BaseProtocolDecoder.java b/src/org/traccar/BaseProtocolDecoder.java
index f8abdcc85..bd91f5e09 100644
--- a/src/org/traccar/BaseProtocolDecoder.java
+++ b/src/org/traccar/BaseProtocolDecoder.java
@@ -75,6 +75,8 @@ public abstract class BaseProtocolDecoder extends ExtendedObjectDecoder {
}
public void getLastLocation(Position position, Date deviceTime) {
+ position.setOutdated(true);
+
Position last = Context.getConnectionManager().getLastPosition(getDeviceId());
if (last != null) {
position.setFixTime(last.getFixTime());
diff --git a/src/org/traccar/Context.java b/src/org/traccar/Context.java
index 113ed02ec..c833d0c2e 100644
--- a/src/org/traccar/Context.java
+++ b/src/org/traccar/Context.java
@@ -29,6 +29,9 @@ import org.traccar.geocode.NominatimReverseGeocoder;
import org.traccar.geocode.OpenCageReverseGeocoder;
import org.traccar.geocode.ReverseGeocoder;
import org.traccar.helper.Log;
+import org.traccar.location.LocationProvider;
+import org.traccar.location.MozillaLocationProvider;
+import org.traccar.location.OpenCellIdLocationProvider;
import org.traccar.web.WebServer;
public final class Context {
@@ -78,6 +81,12 @@ public final class Context {
return reverseGeocoder;
}
+ private static LocationProvider locationProvider;
+
+ public static LocationProvider getLocationProvider() {
+ return locationProvider;
+ }
+
private static WebServer webServer;
public static WebServer getWebServer() {
@@ -144,6 +153,20 @@ public final class Context {
}
}
+ if (config.getBoolean("location.enable")) {
+ String type = config.getString("location.type", "opencellid");
+ String key = config.getString("location.key");
+
+ switch (type) {
+ case "mozilla":
+ locationProvider = new MozillaLocationProvider();
+ break;
+ default:
+ locationProvider = new OpenCellIdLocationProvider(key);
+ break;
+ }
+ }
+
if (config.getBoolean("web.enable")) {
if (config.getString("web.type", "new").equals("new")
|| config.getString("web.type", "new").equals("api")) {
diff --git a/src/org/traccar/FilterHandler.java b/src/org/traccar/FilterHandler.java
index 79f40c979..75f2bfd2c 100644
--- a/src/org/traccar/FilterHandler.java
+++ b/src/org/traccar/FilterHandler.java
@@ -17,6 +17,7 @@ package org.traccar;
import org.traccar.helper.DistanceCalculator;
import org.traccar.helper.Log;
+import org.traccar.model.Event;
import org.traccar.model.Position;
public class FilterHandler extends BaseDataHandler {
@@ -27,18 +28,20 @@ public class FilterHandler extends BaseDataHandler {
private final boolean filterZero;
private final boolean filterDuplicate;
private final boolean filterFuture;
+ private final boolean filterApproximate;
private final int filterDistance;
private final long filterLimit;
public FilterHandler(
- boolean filterInvalid, boolean filterZero, boolean filterDuplicate,
- boolean filterFuture, int filterDistance, long filterLimit) {
+ boolean filterInvalid, boolean filterZero, boolean filterDuplicate, boolean filterFuture,
+ boolean filterApproximate, int filterDistance, long filterLimit) {
this.filterInvalid = filterInvalid;
this.filterZero = filterZero;
this.filterDuplicate = filterDuplicate;
this.filterDistance = filterDistance;
this.filterFuture = filterFuture;
+ this.filterApproximate = filterApproximate;
this.filterLimit = filterLimit;
}
@@ -49,6 +52,7 @@ public class FilterHandler extends BaseDataHandler {
filterZero = config.getBoolean("filter.zero");
filterDuplicate = config.getBoolean("filter.duplicate");
filterFuture = config.getBoolean("filter.future");
+ filterApproximate = config.getBoolean("filter.approximate");
filterDistance = config.getInteger("filter.distance");
filterLimit = config.getLong("filter.limit") * 1000;
}
@@ -85,6 +89,11 @@ public class FilterHandler extends BaseDataHandler {
return filterFuture && position.getFixTime().getTime() > System.currentTimeMillis() + FILTER_FUTURE_LIMIT;
}
+ private boolean filterApproximate(Position position) {
+ Boolean approximate = (Boolean) position.getAttributes().get(Event.KEY_APPROXIMATE);
+ return filterApproximate && approximate != null && approximate;
+ }
+
private boolean filterDistance(Position position) {
if (filterDistance != 0) {
Position last = getLastPosition(position.getDeviceId());
@@ -116,8 +125,8 @@ public class FilterHandler extends BaseDataHandler {
private boolean filter(Position p) {
- boolean result = filterInvalid(p) || filterZero(p)
- || filterDuplicate(p) || filterFuture(p) || filterDistance(p);
+ boolean result = filterInvalid(p) || filterZero(p) || filterDuplicate(p)
+ || filterFuture(p) || filterApproximate(p) || filterDistance(p);
if (filterLimit(p)) {
result = false;
diff --git a/src/org/traccar/LocationProviderHandler.java b/src/org/traccar/LocationProviderHandler.java
new file mode 100644
index 000000000..62d5f1d04
--- /dev/null
+++ b/src/org/traccar/LocationProviderHandler.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2015 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;
+
+import org.jboss.netty.channel.ChannelEvent;
+import org.jboss.netty.channel.ChannelHandlerContext;
+import org.jboss.netty.channel.ChannelUpstreamHandler;
+import org.jboss.netty.channel.Channels;
+import org.jboss.netty.channel.MessageEvent;
+import org.traccar.location.LocationProvider;
+import org.traccar.model.Event;
+import org.traccar.model.Position;
+
+public class LocationProviderHandler implements ChannelUpstreamHandler {
+
+ private final LocationProvider locationProvider;
+
+ public LocationProviderHandler(LocationProvider locationProvider) {
+ this.locationProvider = locationProvider;
+ }
+
+ @Override
+ public void handleUpstream(final ChannelHandlerContext ctx, ChannelEvent evt) throws Exception {
+ if (!(evt instanceof MessageEvent)) {
+ ctx.sendUpstream(evt);
+ return;
+ }
+
+ final MessageEvent e = (MessageEvent) evt;
+ Object message = e.getMessage();
+ if (message instanceof Position) {
+ final Position position = (Position) message;
+ if (locationProvider != null && position.getOutdated()) {
+ locationProvider.getLocation(position.getAttributes(), new LocationProvider.LocationProviderCallback() {
+ @Override
+ public void onSuccess(double latitude, double longitude) {
+ position.set(Event.KEY_APPROXIMATE, true);
+ position.setValid(true);
+ position.setFixTime(position.getDeviceTime());
+ position.setLatitude(latitude);
+ position.setLongitude(longitude);
+ Channels.fireMessageReceived(ctx, position, e.getRemoteAddress());
+ }
+
+ @Override
+ public void onFailure() {
+ Channels.fireMessageReceived(ctx, position, e.getRemoteAddress());
+ }
+ });
+ } else {
+ Channels.fireMessageReceived(ctx, position, e.getRemoteAddress());
+ }
+ } else {
+ Channels.fireMessageReceived(ctx, message, e.getRemoteAddress());
+ }
+ }
+
+}
diff --git a/src/org/traccar/MainEventHandler.java b/src/org/traccar/MainEventHandler.java
index 37f0ee387..f0ef36e5b 100644
--- a/src/org/traccar/MainEventHandler.java
+++ b/src/org/traccar/MainEventHandler.java
@@ -66,7 +66,9 @@ public class MainEventHandler extends IdleStateAwareChannelHandler {
Log.info(formatChannel(e.getChannel()) + " disconnected");
closeChannel(e.getChannel());
- Context.getConnectionManager().removeActiveDevice(e.getChannel());
+ if (ctx.getPipeline().get("httpDecoder") == null) {
+ Context.getConnectionManager().removeActiveDevice(e.getChannel());
+ }
}
@Override
diff --git a/src/org/traccar/database/ConnectionManager.java b/src/org/traccar/database/ConnectionManager.java
index 450f2f61f..e03fd1663 100644
--- a/src/org/traccar/database/ConnectionManager.java
+++ b/src/org/traccar/database/ConnectionManager.java
@@ -25,7 +25,13 @@ import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.concurrent.TimeUnit;
+
import org.jboss.netty.channel.Channel;
+import org.jboss.netty.util.Timeout;
+import org.jboss.netty.util.TimerTask;
+import org.traccar.Context;
+import org.traccar.GlobalTimer;
import org.traccar.Protocol;
import org.traccar.helper.Log;
import org.traccar.model.Device;
@@ -33,15 +39,21 @@ import org.traccar.model.Position;
public class ConnectionManager {
+ private static final long DEFAULT_TIMEOUT = 600;
+
+ private final long deviceTimeout;
+
private final Map<Long, ActiveDevice> activeDevices = new HashMap<>();
private final Map<Long, Position> positions = new HashMap<>();
- private final Map<Long, Set<DataCacheListener>> listeners = new HashMap<>();
+ private final Map<Long, Set<UpdateListener>> listeners = new HashMap<>();
+ private final Map<Long, Timeout> timeouts = new HashMap<>();
public ConnectionManager(DataManager dataManager) {
+ deviceTimeout = Context.getConfig().getLong("status.timeout", DEFAULT_TIMEOUT) * 1000;
if (dataManager != null) {
try {
for (Position position : dataManager.getLatestPositions()) {
- this.positions.put(position.getDeviceId(), position);
+ positions.put(position.getDeviceId(), position);
}
} catch (SQLException error) {
Log.warning(error);
@@ -56,7 +68,7 @@ public class ConnectionManager {
public void removeActiveDevice(Channel channel) {
for (ActiveDevice activeDevice : activeDevices.values()) {
if (activeDevice.getChannel() == channel) {
- updateDevice(activeDevice.getDeviceId(), Device.STATUS_OFFLINE, new Date());
+ updateDevice(activeDevice.getDeviceId(), Device.STATUS_OFFLINE, null);
activeDevices.remove(activeDevice.getDeviceId());
break;
}
@@ -67,17 +79,52 @@ public class ConnectionManager {
return activeDevices.get(deviceId);
}
- public synchronized void updateDevice(long deviceId, String status, Date time) {
- // TODO update cache and call listener
- /*Log.debug(deviceId + " " + status + " "
- + new SimpleDateFormat(Log.DATE_FORMAT).format(time));*/
+ public synchronized void updateDevice(final long deviceId, String status, Date time) {
+ Device device = Context.getIdentityManager().getDeviceById(deviceId);
+ if (device == null) {
+ return;
+ }
+
+ device.setStatus(status);
+ if (time != null) {
+ device.setLastUpdate(time);
+ }
+
+ Timeout timeout = timeouts.remove(deviceId);
+ if (timeout != null) {
+ timeout.cancel();
+ }
+
+ if (status.equals(Device.STATUS_ONLINE)) {
+ timeouts.put(deviceId, GlobalTimer.getTimer().newTimeout(new TimerTask() {
+ @Override
+ public void run(Timeout timeout) throws Exception {
+ if (!timeout.isCancelled()) {
+ updateDevice(deviceId, Device.STATUS_UNKNOWN, null);
+ }
+ }
+ }, deviceTimeout, TimeUnit.MILLISECONDS));
+ }
+
+ try {
+ Context.getDataManager().updateDeviceStatus(device);
+ } catch (SQLException error) {
+ Log.warning(error);
+ }
+
+ if (listeners.containsKey(deviceId)) {
+ for (UpdateListener listener : listeners.get(deviceId)) {
+ listener.onUpdateDevice(device);
+ }
+ }
}
public synchronized void updatePosition(Position position) {
long deviceId = position.getDeviceId();
positions.put(deviceId, position);
+
if (listeners.containsKey(deviceId)) {
- for (DataCacheListener listener : listeners.get(deviceId)) {
+ for (UpdateListener listener : listeners.get(deviceId)) {
listener.onUpdatePosition(position);
}
}
@@ -100,32 +147,33 @@ public class ConnectionManager {
return result;
}
- public interface DataCacheListener {
+ public interface UpdateListener {
+ void onUpdateDevice(Device device);
void onUpdatePosition(Position position);
}
- public void addListener(Collection<Long> devices, DataCacheListener listener) {
+ public void addListener(Collection<Long> devices, UpdateListener listener) {
for (long deviceId : devices) {
addListener(deviceId, listener);
}
}
- public synchronized void addListener(long deviceId, DataCacheListener listener) {
+ public synchronized void addListener(long deviceId, UpdateListener listener) {
if (!listeners.containsKey(deviceId)) {
- listeners.put(deviceId, new HashSet<DataCacheListener>());
+ listeners.put(deviceId, new HashSet<UpdateListener>());
}
listeners.get(deviceId).add(listener);
}
- public void removeListener(Collection<Long> devices, DataCacheListener listener) {
+ public void removeListener(Collection<Long> devices, UpdateListener listener) {
for (long deviceId : devices) {
removeListener(deviceId, listener);
}
}
- public synchronized void removeListener(long deviceId, DataCacheListener listener) {
+ public synchronized void removeListener(long deviceId, UpdateListener listener) {
if (!listeners.containsKey(deviceId)) {
- listeners.put(deviceId, new HashSet<DataCacheListener>());
+ listeners.put(deviceId, new HashSet<UpdateListener>());
}
listeners.get(deviceId).remove(listener);
}
diff --git a/src/org/traccar/database/DataManager.java b/src/org/traccar/database/DataManager.java
index 767582604..530ec1779 100644
--- a/src/org/traccar/database/DataManager.java
+++ b/src/org/traccar/database/DataManager.java
@@ -310,6 +310,12 @@ public class DataManager implements IdentityManager {
.executeUpdate();
}
+ public void updateDeviceStatus(Device device) throws SQLException {
+ QueryBuilder.create(dataSource, getQuery("database.updateDeviceStatus"))
+ .setObject(device)
+ .executeUpdate();
+ }
+
public void removeDevice(Device device) throws SQLException {
QueryBuilder.create(dataSource, getQuery("database.deleteDevice"))
.setObject(device)
diff --git a/src/org/traccar/location/BaseLocationProvider.java b/src/org/traccar/location/BaseLocationProvider.java
new file mode 100644
index 000000000..c422ed50a
--- /dev/null
+++ b/src/org/traccar/location/BaseLocationProvider.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2015 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.location;
+
+import org.traccar.Config;
+import org.traccar.Context;
+import org.traccar.model.Event;
+
+import java.util.Map;
+
+public abstract class BaseLocationProvider implements LocationProvider {
+
+ @Override
+ public void getLocation(Map<String, Object> attributes, LocationProviderCallback callback) {
+
+ Config config = Context.getConfig();
+
+ Number mcc = (Number) attributes.get(Event.KEY_MCC);
+ if (mcc == null && config.hasKey("location.mcc")) {
+ mcc = config.getInteger("location.mcc");
+ }
+
+ Number mnc = (Number) attributes.get(Event.KEY_MNC);
+ if (mnc == null && config.hasKey("location.mnc")) {
+ mnc = config.getInteger("location.mnc");
+ }
+
+ Number lac = (Number) attributes.get(Event.KEY_LAC);
+ Number cid = (Number) attributes.get(Event.KEY_CID);
+
+ if (mcc != null && mnc != null && lac != null && cid != null) {
+ getLocation(mcc.intValue(), mnc.intValue(), lac.longValue(), cid.longValue(), callback);
+ }
+
+ }
+
+ protected abstract void getLocation(int mcc, int mnc, long lac, long cid, LocationProviderCallback callback);
+
+}
diff --git a/src/org/traccar/location/LocationProvider.java b/src/org/traccar/location/LocationProvider.java
new file mode 100644
index 000000000..2bff1a7ca
--- /dev/null
+++ b/src/org/traccar/location/LocationProvider.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2015 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.location;
+
+import java.util.Map;
+
+public interface LocationProvider {
+
+ interface LocationProviderCallback {
+
+ void onSuccess(double latitude, double longitude);
+
+ void onFailure();
+
+ }
+
+ void getLocation(Map<String, Object> attributes, LocationProviderCallback callback);
+
+}
diff --git a/src/org/traccar/location/MozillaLocationProvider.java b/src/org/traccar/location/MozillaLocationProvider.java
new file mode 100644
index 000000000..d30fbf642
--- /dev/null
+++ b/src/org/traccar/location/MozillaLocationProvider.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2015 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.location;
+
+import com.ning.http.client.AsyncCompletionHandler;
+import com.ning.http.client.Response;
+import org.traccar.Context;
+
+import javax.json.Json;
+import javax.json.JsonObject;
+import javax.json.JsonReader;
+
+public class MozillaLocationProvider extends BaseLocationProvider {
+
+ private String url;
+ private String template;
+
+ public MozillaLocationProvider() {
+ this("https://location.services.mozilla.com/v1/geolocate", "test");
+ }
+
+ public MozillaLocationProvider(String url, String key) {
+ this.url = url + "?key=" + key;
+
+ template = new StringBuilder()
+ .append("{\"cellTowers\":[{")
+ .append("\"radioType\":\"gsm\",")
+ .append("\"mobileCountryCode\":%d,")
+ .append("\"mobileNetworkCode\":%d,")
+ .append("\"locationAreaCode\":%d,")
+ .append("\"cellId\":%d")
+ .append("}]}")
+ .toString();
+ }
+
+ protected void getLocation(int mcc, int mnc, long lac, long cid, final LocationProviderCallback callback) {
+ String body = String.format(template, mcc, mnc, lac, cid);
+ Context.getAsyncHttpClient().preparePost(url).setBody(body).execute(new AsyncCompletionHandler() {
+ @Override
+ public Object onCompleted(Response response) throws Exception {
+ try (JsonReader reader = Json.createReader(response.getResponseBodyAsStream())) {
+ JsonObject json = reader.readObject().getJsonObject("location");
+ if (json.containsKey("lat") && json.containsKey("lon")) {
+ callback.onSuccess(
+ json.getJsonNumber("lat").doubleValue(),
+ json.getJsonNumber("lon").doubleValue());
+ } else {
+ callback.onFailure();
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public void onThrowable(Throwable t) {
+ callback.onFailure();
+ }
+ });
+
+ }
+
+}
diff --git a/src/org/traccar/location/OpenCellIdLocationProvider.java b/src/org/traccar/location/OpenCellIdLocationProvider.java
new file mode 100644
index 000000000..94cc1a4e4
--- /dev/null
+++ b/src/org/traccar/location/OpenCellIdLocationProvider.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2015 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.location;
+
+import com.ning.http.client.AsyncCompletionHandler;
+import com.ning.http.client.Response;
+import org.traccar.Context;
+
+import javax.json.Json;
+import javax.json.JsonObject;
+import javax.json.JsonReader;
+
+public class OpenCellIdLocationProvider extends BaseLocationProvider {
+
+ private String url;
+
+ public OpenCellIdLocationProvider(String key) {
+ this("http://opencellid.org/cell/get", key);
+ }
+
+ public OpenCellIdLocationProvider(String url, String key) {
+ this.url = url + "?format=json&mcc=%d&mnc=%d&lac=%d&cellid=%d&key=" + key;
+ }
+
+ protected void getLocation(int mcc, int mnc, long lac, long cid, final LocationProviderCallback callback) {
+ Context.getAsyncHttpClient().prepareGet(String.format(url, mcc, mnc, lac, cid))
+ .execute(new AsyncCompletionHandler() {
+ @Override
+ public Object onCompleted(Response response) throws Exception {
+ try (JsonReader reader = Json.createReader(response.getResponseBodyAsStream())) {
+ JsonObject json = reader.readObject();
+ if (json.containsKey("lat") && json.containsKey("lon")) {
+ callback.onSuccess(
+ json.getJsonNumber("lat").doubleValue(),
+ json.getJsonNumber("lon").doubleValue());
+ } else {
+ callback.onFailure();
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public void onThrowable(Throwable t) {
+ callback.onFailure();
+ }
+ });
+ }
+
+}
diff --git a/src/org/traccar/model/Event.java b/src/org/traccar/model/Event.java
index 1dbfe5812..6e3aa7f22 100644
--- a/src/org/traccar/model/Event.java
+++ b/src/org/traccar/model/Event.java
@@ -47,10 +47,9 @@ public abstract class Event extends Extensible {
public static final String KEY_IP = "ip";
public static final String KEY_ARCHIVE = "archive";
public static final String KEY_DISTANCE = "distance";
- public static final String KEY_DOOR = "door";
public static final String KEY_RPM = "rpm";
- public static final String KEY_HOURS = "hours";
public static final String KEY_VIN = "vin";
+ public static final String KEY_APPROXIMATE = "approximate";
public static final String KEY_OBD_SPEED = "obd-speed";
public static final String KEY_OBD_ODOMETER = "obd-odometer";
diff --git a/src/org/traccar/model/Position.java b/src/org/traccar/model/Position.java
index 1ed559ecd..9494da042 100644
--- a/src/org/traccar/model/Position.java
+++ b/src/org/traccar/model/Position.java
@@ -47,6 +47,16 @@ public class Position extends Event implements Factory {
setFixTime(time);
}
+ private boolean outdated;
+
+ public boolean getOutdated() {
+ return outdated;
+ }
+
+ public void setOutdated(boolean outdated) {
+ this.outdated = outdated;
+ }
+
private boolean valid;
public boolean getValid() {
diff --git a/src/org/traccar/protocol/EelinkProtocolDecoder.java b/src/org/traccar/protocol/EelinkProtocolDecoder.java
index b3a062db3..b75587743 100644
--- a/src/org/traccar/protocol/EelinkProtocolDecoder.java
+++ b/src/org/traccar/protocol/EelinkProtocolDecoder.java
@@ -86,7 +86,10 @@ public class EelinkProtocolDecoder extends BaseProtocolDecoder {
position.setSpeed(UnitsConverter.knotsFromKph(buf.readUnsignedByte()));
position.setCourse(buf.readUnsignedShort());
- position.set(Event.KEY_CID, ChannelBuffers.hexDump(buf.readBytes(9)));
+ position.set(Event.KEY_MCC, buf.readUnsignedShort());
+ position.set(Event.KEY_MNC, buf.readUnsignedShort());
+ position.set(Event.KEY_LAC, buf.readUnsignedShort());
+ position.set(Event.KEY_CID, buf.readUnsignedShort());
position.setValid((buf.readUnsignedByte() & 0x01) != 0);
diff --git a/src/org/traccar/protocol/FlextrackProtocolDecoder.java b/src/org/traccar/protocol/FlextrackProtocolDecoder.java
index 363d5502c..bc9825b49 100644
--- a/src/org/traccar/protocol/FlextrackProtocolDecoder.java
+++ b/src/org/traccar/protocol/FlextrackProtocolDecoder.java
@@ -125,8 +125,8 @@ public class FlextrackProtocolDecoder extends BaseProtocolDecoder {
position.setAltitude(parser.nextInt());
position.set(Event.KEY_HDOP, parser.nextInt() * 0.1);
- position.set(Event.KEY_CID, parser.next());
- position.set(Event.KEY_LAC, parser.next());
+ position.set(Event.KEY_CID, parser.nextInt(16));
+ position.set(Event.KEY_LAC, parser.nextInt(16));
position.set(Event.KEY_ODOMETER, parser.nextInt());
return position;
diff --git a/src/org/traccar/protocol/Gl200ProtocolDecoder.java b/src/org/traccar/protocol/Gl200ProtocolDecoder.java
index 29884bb00..42786d4fe 100644
--- a/src/org/traccar/protocol/Gl200ProtocolDecoder.java
+++ b/src/org/traccar/protocol/Gl200ProtocolDecoder.java
@@ -116,7 +116,9 @@ public class Gl200ProtocolDecoder extends BaseProtocolDecoder {
.text(",")
.number("(0ddd)?,") // mcc
.number("(0ddd)?,") // mnc
- .number("(xxxx|x{8})?,") // loc
+ .number("(?:xxxx)?")
+ .number("(xxxx)").optional(2) // lac
+ .text(",")
.number("(xxxx)?,") // cell
.groupBegin()
.number("(d+.d)?,") // odometer
@@ -215,10 +217,12 @@ public class Gl200ProtocolDecoder extends BaseProtocolDecoder {
getLastLocation(position, null);
}
- position.set(Event.KEY_MCC, parser.next());
- position.set(Event.KEY_MNC, parser.next());
- position.set(Event.KEY_LAC, parser.next());
- position.set(Event.KEY_CID, parser.next());
+ if (parser.hasNext(4)) {
+ position.set(Event.KEY_MCC, parser.nextInt());
+ position.set(Event.KEY_MNC, parser.nextInt());
+ position.set(Event.KEY_LAC, parser.nextInt(16));
+ position.set(Event.KEY_CID, parser.nextInt(16));
+ }
position.set(Event.KEY_ODOMETER, parser.next());
position.set(Event.KEY_BATTERY, parser.next());
diff --git a/src/org/traccar/protocol/Gps103ProtocolDecoder.java b/src/org/traccar/protocol/Gps103ProtocolDecoder.java
index aa693f42e..a91848f5b 100644
--- a/src/org/traccar/protocol/Gps103ProtocolDecoder.java
+++ b/src/org/traccar/protocol/Gps103ProtocolDecoder.java
@@ -63,6 +63,17 @@ public class Gps103ProtocolDecoder extends BaseProtocolDecoder {
.any()
.compile();
+ private static final Pattern PATTERN_NETWORK = new PatternBuilder()
+ .text("imei:")
+ .number("(d+),") // imei
+ .expression("[^,]+,") // alarm
+ .number("d+,,")
+ .text("L,,,")
+ .number("(x+),,") // lac
+ .number("(x+),,,") // cid
+ .any()
+ .compile();
+
private static final Pattern PATTERN_HANDSHAKE = new PatternBuilder()
.number("##,imei:(d+),A")
.compile();
@@ -93,14 +104,31 @@ public class Gps103ProtocolDecoder extends BaseProtocolDecoder {
return null;
}
- Parser parser = new Parser(PATTERN, sentence);
+ Position position = new Position();
+ position.setProtocol(getProtocolName());
+
+ Parser parser = new Parser(PATTERN_NETWORK, sentence);
+ if (parser.matches()) {
+
+ if (!identify(parser.next(), channel, remoteAddress)) {
+ return null;
+ }
+ position.setDeviceId(getDeviceId());
+
+ getLastLocation(position, null);
+
+ position.set(Event.KEY_LAC, parser.nextInt(16));
+ position.set(Event.KEY_CID, parser.nextInt(16));
+
+ return position;
+
+ }
+
+ parser = new Parser(PATTERN, sentence);
if (!parser.matches()) {
return null;
}
- Position position = new Position();
- position.setProtocol(getProtocolName());
-
String imei = parser.next();
if (!identify(imei, channel, remoteAddress)) {
return null;
diff --git a/src/org/traccar/protocol/Gt02ProtocolDecoder.java b/src/org/traccar/protocol/Gt02ProtocolDecoder.java
index d15d999cf..53739ee1c 100644
--- a/src/org/traccar/protocol/Gt02ProtocolDecoder.java
+++ b/src/org/traccar/protocol/Gt02ProtocolDecoder.java
@@ -100,6 +100,10 @@ public class Gt02ProtocolDecoder extends BaseProtocolDecoder {
position.setLatitude(latitude);
position.setLongitude(longitude);
+ } else {
+
+ return null;
+
}
return position;
diff --git a/src/org/traccar/protocol/HuabaoProtocolDecoder.java b/src/org/traccar/protocol/HuabaoProtocolDecoder.java
index 931f985a5..792c89c8b 100644
--- a/src/org/traccar/protocol/HuabaoProtocolDecoder.java
+++ b/src/org/traccar/protocol/HuabaoProtocolDecoder.java
@@ -16,10 +16,20 @@
package org.traccar.protocol;
import org.jboss.netty.buffer.ChannelBuffer;
+import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.channel.Channel;
import org.traccar.BaseProtocolDecoder;
+import org.traccar.helper.BitUtil;
+import org.traccar.helper.ChannelBufferTools;
+import org.traccar.helper.Checksum;
+import org.traccar.helper.DateBuilder;
+import org.traccar.helper.UnitsConverter;
+import org.traccar.model.Event;
+import org.traccar.model.Position;
import java.net.SocketAddress;
+import java.nio.charset.Charset;
+import java.util.TimeZone;
public class HuabaoProtocolDecoder extends BaseProtocolDecoder {
@@ -27,7 +37,38 @@ public class HuabaoProtocolDecoder extends BaseProtocolDecoder {
super(protocol);
}
+ public static final int MSG_GENERAL_RESPONSE = 0x8001;
public static final int MSG_TERMINAL_REGISTER = 0x0100;
+ public static final int MSG_TERMINAL_REGISTER_RESPONSE = 0x8100;
+ public static final int MSG_TERMINAL_AUTH = 0x0102;
+ public static final int MSG_LOCATION_REPORT = 0x0200;
+
+ public static final int RESULT_SUCCESS = 0;
+
+ private void sendResponse(
+ Channel channel, SocketAddress remoteAddress, int type, ChannelBuffer id, ChannelBuffer data) {
+ ChannelBuffer response = ChannelBuffers.dynamicBuffer();
+ response.writeByte(0x7e);
+ response.writeShort(type);
+ response.writeShort(data.readableBytes());
+ response.writeBytes(id);
+ response.writeShort(1); // index
+ response.writeBytes(data);
+ response.writeByte(Checksum.xor(response.toByteBuffer(1, response.readableBytes() - 1)));
+ response.writeByte(0x7e);
+ if (channel != null) {
+ channel.write(response, remoteAddress);
+ }
+ }
+
+ private void sendGeneralResponse(
+ Channel channel, SocketAddress remoteAddress, ChannelBuffer id, int type, int index) {
+ ChannelBuffer response = ChannelBuffers.dynamicBuffer();
+ response.writeShort(index);
+ response.writeShort(type);
+ response.writeByte(RESULT_SUCCESS);
+ sendResponse(channel, remoteAddress, MSG_GENERAL_RESPONSE, id, response);
+ }
@Override
protected Object decode(
@@ -36,10 +77,74 @@ public class HuabaoProtocolDecoder extends BaseProtocolDecoder {
ChannelBuffer buf = (ChannelBuffer) msg;
buf.readUnsignedByte(); // start marker
- //int type = buf.readUnsignedShort();
- //int flags = buf.readUnsignedShort();
- buf.skipBytes(6); // phone number
- buf.readUnsignedShort(); // index
+ int type = buf.readUnsignedShort();
+ buf.readUnsignedShort(); // body length
+ ChannelBuffer id = buf.readBytes(6); // phone number
+ int index = buf.readUnsignedShort();
+
+ if (!identify(id.toString(Charset.defaultCharset()), channel, remoteAddress)) {
+ return null;
+ }
+
+ if (type == MSG_TERMINAL_REGISTER) {
+
+ ChannelBuffer response = ChannelBuffers.dynamicBuffer();
+ response.writeShort(index);
+ response.writeByte(RESULT_SUCCESS);
+ response.writeBytes("authentication".getBytes(Charset.defaultCharset()));
+ sendResponse(channel, remoteAddress, MSG_TERMINAL_REGISTER_RESPONSE, id, response);
+
+ } else if (type == MSG_TERMINAL_AUTH) {
+
+ sendGeneralResponse(channel, remoteAddress, id, type, index);
+
+ } else if (type == MSG_LOCATION_REPORT) {
+
+ Position position = new Position();
+ position.setProtocol(getProtocolName());
+ position.setDeviceId(getDeviceId());
+
+ position.set(Event.KEY_ALARM, buf.readUnsignedInt());
+
+ int flags = buf.readInt();
+
+ position.set(Event.KEY_IGNITION, BitUtil.check(flags, 0));
+
+ position.setValid(BitUtil.check(flags, 1));
+
+ double lat = buf.readUnsignedInt() * 0.000001;
+ double lon = buf.readUnsignedInt() * 0.000001;
+
+ if (BitUtil.check(flags, 2)) {
+ position.setLatitude(-lat);
+ } else {
+ position.setLatitude(lat);
+ }
+
+ if (BitUtil.check(flags, 3)) {
+ position.setLongitude(-lon);
+ } else {
+ position.setLongitude(lon);
+ }
+
+ position.setAltitude(buf.readShort());
+ position.setSpeed(UnitsConverter.knotsFromKph(buf.readUnsignedShort() * 0.1));
+ position.setCourse(buf.readUnsignedShort());
+
+ DateBuilder dateBuilder = new DateBuilder(TimeZone.getTimeZone("GMT+8"))
+ .setYear(ChannelBufferTools.readHexInteger(buf, 2))
+ .setMonth(ChannelBufferTools.readHexInteger(buf, 2))
+ .setDay(ChannelBufferTools.readHexInteger(buf, 2))
+ .setHour(ChannelBufferTools.readHexInteger(buf, 2))
+ .setMinute(ChannelBufferTools.readHexInteger(buf, 2))
+ .setSecond(ChannelBufferTools.readHexInteger(buf, 2));
+ position.setTime(dateBuilder.getDate());
+
+ // additional information
+
+ return position;
+
+ }
return null;
}
diff --git a/src/org/traccar/protocol/Jt600ProtocolDecoder.java b/src/org/traccar/protocol/Jt600ProtocolDecoder.java
index d9e31fe48..b576b063d 100644
--- a/src/org/traccar/protocol/Jt600ProtocolDecoder.java
+++ b/src/org/traccar/protocol/Jt600ProtocolDecoder.java
@@ -94,8 +94,13 @@ public class Jt600ProtocolDecoder extends BaseProtocolDecoder {
position.setAltitude(buf.readUnsignedShort());
- position.set(Event.KEY_CID, buf.readUnsignedShort());
- position.set(Event.KEY_LAC, buf.readUnsignedShort());
+ int cid = buf.readUnsignedShort();
+ int lac = buf.readUnsignedShort();
+ if (cid != 0 && lac != 0) {
+ position.set(Event.KEY_CID, cid);
+ position.set(Event.KEY_LAC, lac);
+ }
+
position.set(Event.KEY_GSM, buf.readUnsignedByte());
} else if (version == 2) {
diff --git a/src/org/traccar/protocol/MegastekProtocolDecoder.java b/src/org/traccar/protocol/MegastekProtocolDecoder.java
index a58e57703..dc9e6056e 100644
--- a/src/org/traccar/protocol/MegastekProtocolDecoder.java
+++ b/src/org/traccar/protocol/MegastekProtocolDecoder.java
@@ -53,14 +53,16 @@ public class MegastekProtocolDecoder extends BaseProtocolDecoder {
.number("(d)?,") // charger
.number("(d+)?,") // mcc
.number("(d+)?,") // mnc
- .number("(xxxx,xxxx);") // location code
+ .number("(xxxx),") // lac
+ .number("(xxxx);") // cid
.any() // checksum
.compile();
private static final Pattern PATTERN_ALTERNATIVE = new PatternBuilder()
.number("(d+),") // mcc
.number("(d+),") // mnc
- .number("(xxxx,xxxx),") // location code
+ .number("(xxxx),") // lac
+ .number("(xxxx),") // cid
.number("(d+),") // gsm signal
.number("(d+),") // battery
.number("(d+),") // flags
@@ -171,9 +173,12 @@ public class MegastekProtocolDecoder extends BaseProtocolDecoder {
position.set(Event.KEY_CHARGE, Integer.parseInt(charger) == 1);
}
- position.set(Event.KEY_MCC, parser.next());
- position.set(Event.KEY_MNC, parser.next());
- position.set(Event.KEY_LAC, parser.next());
+ if (parser.hasNext(3)) {
+ position.set(Event.KEY_MCC, parser.nextInt());
+ position.set(Event.KEY_MNC, parser.nextInt());
+ position.set(Event.KEY_LAC, parser.nextInt(16));
+ position.set(Event.KEY_CID, parser.nextInt(16));
+ }
} else {
@@ -194,9 +199,10 @@ public class MegastekProtocolDecoder extends BaseProtocolDecoder {
}
position.setDeviceId(getDeviceId());
- position.set(Event.KEY_MCC, parser.next());
- position.set(Event.KEY_MNC, parser.next());
- position.set(Event.KEY_LAC, parser.next());
+ position.set(Event.KEY_MCC, parser.nextInt());
+ position.set(Event.KEY_MNC, parser.nextInt());
+ position.set(Event.KEY_LAC, parser.nextInt(16));
+ position.set(Event.KEY_CID, parser.nextInt(16));
position.set(Event.KEY_GSM, parser.next());
position.set(Event.KEY_BATTERY, Double.parseDouble(parser.next()));
@@ -236,7 +242,8 @@ public class MegastekProtocolDecoder extends BaseProtocolDecoder {
.number("(d+.d+),") // odometer
.number("(d+),") // mcc
.number("(d+),") // mnc
- .number("(xxxx,xxxx),") // cell
+ .number("(xxxx),") // lac
+ .number("(xxxx),") // cid
.number("(d+)?,") // gsm
.expression("([01]+),") // input
.expression("([01]+),") // output
@@ -295,7 +302,8 @@ public class MegastekProtocolDecoder extends BaseProtocolDecoder {
position.set(Event.KEY_ODOMETER, parser.nextDouble());
position.set(Event.KEY_MCC, parser.nextInt());
position.set(Event.KEY_MNC, parser.nextInt());
- position.set(Event.KEY_CID, parser.next());
+ position.set(Event.KEY_LAC, parser.nextInt(16));
+ position.set(Event.KEY_CID, parser.nextInt(16));
String gsm = parser.next();
if (gsm != null) {
diff --git a/src/org/traccar/protocol/MeiligaoFrameDecoder.java b/src/org/traccar/protocol/MeiligaoFrameDecoder.java
index 6dcc6fd9c..9fb530f8a 100644
--- a/src/org/traccar/protocol/MeiligaoFrameDecoder.java
+++ b/src/org/traccar/protocol/MeiligaoFrameDecoder.java
@@ -26,9 +26,7 @@ public class MeiligaoFrameDecoder extends FrameDecoder {
@Override
protected Object decode(
- ChannelHandlerContext ctx,
- Channel channel,
- ChannelBuffer buf) throws Exception {
+ ChannelHandlerContext ctx, Channel channel, ChannelBuffer buf) throws Exception {
// Strip not '$' (0x24) bytes from the beginning
while (buf.readable() && buf.getUnsignedByte(buf.readerIndex()) != 0x24) {
diff --git a/src/org/traccar/protocol/MeitrackProtocolDecoder.java b/src/org/traccar/protocol/MeitrackProtocolDecoder.java
index 2b8460fc7..4bde5cf75 100644
--- a/src/org/traccar/protocol/MeitrackProtocolDecoder.java
+++ b/src/org/traccar/protocol/MeitrackProtocolDecoder.java
@@ -119,10 +119,10 @@ public class MeitrackProtocolDecoder extends BaseProtocolDecoder {
position.set(Event.KEY_ODOMETER, parser.next());
position.set("runtime", parser.next());
- position.set(Event.KEY_MCC, parser.next());
- position.set(Event.KEY_MCC, parser.next());
- position.set(Event.KEY_LAC, parser.next());
- position.set(Event.KEY_CID, parser.next());
+ position.set(Event.KEY_MCC, parser.nextInt());
+ position.set(Event.KEY_MNC, parser.nextInt());
+ position.set(Event.KEY_LAC, parser.nextInt(16));
+ position.set(Event.KEY_CID, parser.nextInt(16));
position.set(Event.KEY_STATUS, parser.next());
for (int i = 1; i <= 3; i++) {
@@ -196,7 +196,7 @@ public class MeitrackProtocolDecoder extends BaseProtocolDecoder {
position.set(Event.KEY_ODOMETER, buf.readUnsignedInt());
position.set("runtime", buf.readUnsignedInt());
position.set(Event.KEY_MCC, buf.readUnsignedShort());
- position.set(Event.KEY_MCC, buf.readUnsignedShort());
+ position.set(Event.KEY_MNC, buf.readUnsignedShort());
position.set(Event.KEY_LAC, buf.readUnsignedShort());
position.set(Event.KEY_CID, buf.readUnsignedShort());
position.set(Event.KEY_STATUS, buf.readUnsignedShort());
diff --git a/src/org/traccar/protocol/SuntechProtocolDecoder.java b/src/org/traccar/protocol/SuntechProtocolDecoder.java
index b0d85b101..38f771eec 100644
--- a/src/org/traccar/protocol/SuntechProtocolDecoder.java
+++ b/src/org/traccar/protocol/SuntechProtocolDecoder.java
@@ -83,7 +83,7 @@ public class SuntechProtocolDecoder extends BaseProtocolDecoder {
.setTime(parser.nextInt(), parser.nextInt(), parser.nextInt());
position.setTime(dateBuilder.getDate());
- position.set(Event.KEY_CID, parser.next());
+ parser.next(); // location code + bsic
position.setValid(true);
position.setLatitude(parser.nextDouble());
diff --git a/src/org/traccar/protocol/T800xProtocolDecoder.java b/src/org/traccar/protocol/T800xProtocolDecoder.java
index 2127be331..83d815e0f 100644
--- a/src/org/traccar/protocol/T800xProtocolDecoder.java
+++ b/src/org/traccar/protocol/T800xProtocolDecoder.java
@@ -143,10 +143,14 @@ public class T800xProtocolDecoder extends BaseProtocolDecoder {
getLastLocation(position, dateBuilder.getDate());
- position.set(Event.KEY_MCC, buf.readUnsignedShort());
- position.set(Event.KEY_MNC, buf.readUnsignedShort());
- position.set(Event.KEY_LAC, buf.readUnsignedShort());
- position.set(Event.KEY_CID, buf.readUnsignedShort());
+ byte[] array = new byte[16];
+ buf.readBytes(array);
+ ChannelBuffer swapped = ChannelBuffers.wrappedBuffer(ByteOrder.LITTLE_ENDIAN, array);
+
+ position.set(Event.KEY_MCC, swapped.readUnsignedShort());
+ position.set(Event.KEY_MNC, swapped.readUnsignedShort());
+ position.set(Event.KEY_LAC, swapped.readUnsignedShort());
+ position.set(Event.KEY_CID, swapped.readUnsignedShort());
// two more cell towers
diff --git a/src/org/traccar/protocol/TeltonikaProtocolDecoder.java b/src/org/traccar/protocol/TeltonikaProtocolDecoder.java
index 3592cae79..2217b5ce4 100644
--- a/src/org/traccar/protocol/TeltonikaProtocolDecoder.java
+++ b/src/org/traccar/protocol/TeltonikaProtocolDecoder.java
@@ -114,7 +114,7 @@ public class TeltonikaProtocolDecoder extends BaseProtocolDecoder {
}
if (BitUtil.check(locationMask, 5)) {
- position.set("area", buf.readUnsignedShort());
+ position.set(Event.KEY_LAC, buf.readUnsignedShort());
position.set(Event.KEY_CID, buf.readUnsignedShort());
}
diff --git a/src/org/traccar/protocol/Tk103ProtocolDecoder.java b/src/org/traccar/protocol/Tk103ProtocolDecoder.java
index 95728c447..6fa4edb06 100644
--- a/src/org/traccar/protocol/Tk103ProtocolDecoder.java
+++ b/src/org/traccar/protocol/Tk103ProtocolDecoder.java
@@ -64,6 +64,16 @@ public class Tk103ProtocolDecoder extends BaseProtocolDecoder {
.number("d+") // installed
.compile();
+ private static final Pattern PATTERN_NETWORK = new PatternBuilder()
+ .number("(d{12})") // device id
+ .text("BZ00,")
+ .number("(d+),") // mcc
+ .number("(d+),") // mnc
+ .number("(x+),") // lac
+ .number("(x+),") // cid
+ .any()
+ .compile();
+
@Override
protected Object decode(
Channel channel, SocketAddress remoteAddress, Object msg) throws Exception {
@@ -88,11 +98,11 @@ public class Tk103ProtocolDecoder extends BaseProtocolDecoder {
}
}
+ Position position = new Position();
+ position.setProtocol(getProtocolName());
+
Parser parser = new Parser(PATTERN_BATTERY, sentence);
if (parser.matches()) {
- Position position = new Position();
- position.setProtocol(getProtocolName());
-
if (!identify(parser.next(), channel)) {
return null;
}
@@ -117,14 +127,28 @@ public class Tk103ProtocolDecoder extends BaseProtocolDecoder {
return position;
}
+ parser = new Parser(PATTERN_NETWORK, sentence);
+ if (parser.matches()) {
+ if (!identify(parser.next(), channel)) {
+ return null;
+ }
+ position.setDeviceId(getDeviceId());
+
+ getLastLocation(position, null);
+
+ position.set(Event.KEY_MCC, parser.nextInt());
+ position.set(Event.KEY_MNC, parser.nextInt());
+ position.set(Event.KEY_LAC, parser.nextInt(16));
+ position.set(Event.KEY_CID, parser.nextInt(16));
+
+ return position;
+ }
+
parser = new Parser(PATTERN, sentence);
if (!parser.matches()) {
return null;
}
- Position position = new Position();
- position.setProtocol(getProtocolName());
-
if (!identify(parser.next(), channel)) {
return null;
}
diff --git a/src/org/traccar/protocol/Tlt2hProtocolDecoder.java b/src/org/traccar/protocol/Tlt2hProtocolDecoder.java
index d9d50947e..24e9893bb 100644
--- a/src/org/traccar/protocol/Tlt2hProtocolDecoder.java
+++ b/src/org/traccar/protocol/Tlt2hProtocolDecoder.java
@@ -86,7 +86,7 @@ public class Tlt2hProtocolDecoder extends BaseProtocolDecoder {
position.setProtocol(getProtocolName());
position.setDeviceId(getDeviceId());
- position.set(Event.KEY_CID, parser.next());
+ parser.next(); // base station info
DateBuilder dateBuilder = new DateBuilder()
.setTime(parser.nextInt(), parser.nextInt(), parser.nextInt());
diff --git a/src/org/traccar/protocol/TotemProtocolDecoder.java b/src/org/traccar/protocol/TotemProtocolDecoder.java
index 321b78b7c..88eecab70 100644
--- a/src/org/traccar/protocol/TotemProtocolDecoder.java
+++ b/src/org/traccar/protocol/TotemProtocolDecoder.java
@@ -55,7 +55,8 @@ public class TotemProtocolDecoder extends BaseProtocolDecoder {
.number("(ddd)") // battery
.number("(dddd)|") // power
.number("(d+)|").optional() // adc
- .number("(x+)|") // location code
+ .number("x*(xxxx)") // lac
+ .number("(xxxx)|") // cid
.number("(d+)|") // temperature
.number("(d+.d+)|") // odometer
.number("d+|") // serial number
@@ -84,7 +85,8 @@ public class TotemProtocolDecoder extends BaseProtocolDecoder {
.number("(dd)") // battery
.number("(dd)|") // external power
.number("(d+)|") // adc
- .number("(x{8})|") // location code
+ .number("(xxxx)") // lac
+ .number("(xxxx)|") // cid
.number("(d+)|") // temperature
.number("(d+.d+)|") // odometer
.number("d+|") // serial number
@@ -107,7 +109,8 @@ public class TotemProtocolDecoder extends BaseProtocolDecoder {
.number("(dddd)") // adc 2
.number("(ddd)") // temperature 1
.number("(ddd)") // temperature 2
- .number("(x{8})") // location code
+ .number("(xxxx)") // lac
+ .number("(xxxx)") // cid
.expression("([AV])") // validity
.number("(dd)") // satellites
.number("(ddd)") // course
@@ -185,7 +188,14 @@ public class TotemProtocolDecoder extends BaseProtocolDecoder {
position.set(Event.KEY_BATTERY, parser.next());
position.set(Event.KEY_POWER, parser.nextDouble());
position.set(Event.PREFIX_ADC + 1, parser.next());
- position.set(Event.KEY_LAC, parser.next());
+
+ int lac = parser.nextInt(16);
+ int cid = parser.nextInt(16);
+ if (lac != 0 && cid != 0) {
+ position.set(Event.KEY_LAC, lac);
+ position.set(Event.KEY_CID, cid);
+ }
+
position.set(Event.PREFIX_TEMP + 1, parser.next());
position.set(Event.KEY_ODOMETER, parser.next());
@@ -203,7 +213,8 @@ public class TotemProtocolDecoder extends BaseProtocolDecoder {
position.set(Event.PREFIX_ADC + 2, parser.next());
position.set(Event.PREFIX_TEMP + 1, parser.next());
position.set(Event.PREFIX_TEMP + 2, parser.next());
- position.set(Event.KEY_LAC, parser.next());
+ position.set(Event.KEY_LAC, parser.nextInt(16));
+ position.set(Event.KEY_CID, parser.nextInt(16));
position.setValid(parser.next().equals("A"));
position.set(Event.KEY_SATELLITES, parser.next());
diff --git a/src/org/traccar/protocol/WatchProtocolDecoder.java b/src/org/traccar/protocol/WatchProtocolDecoder.java
index a24d0a56b..92b7638fb 100644
--- a/src/org/traccar/protocol/WatchProtocolDecoder.java
+++ b/src/org/traccar/protocol/WatchProtocolDecoder.java
@@ -64,8 +64,8 @@ public class WatchProtocolDecoder extends BaseProtocolDecoder {
private void sendResponse(Channel channel, String manufacturer, String id, String content) {
if (channel != null) {
- channel.write(
- String.format("[%s*%s*%04x*%s]", manufacturer, id, content.length(), content));
+ channel.write(String.format(
+ "[%s*%s*%04x*%s]", manufacturer, id, content.length(), content));
}
}
@@ -138,6 +138,10 @@ public class WatchProtocolDecoder extends BaseProtocolDecoder {
return position;
+ } else if (type.equals("TKQ")) {
+
+ sendResponse(channel, manufacturer, id, "TKQ");
+
}
return null;
diff --git a/src/org/traccar/web/AsyncServlet.java b/src/org/traccar/web/AsyncServlet.java
index 63b08ff3e..c55fd6e75 100644
--- a/src/org/traccar/web/AsyncServlet.java
+++ b/src/org/traccar/web/AsyncServlet.java
@@ -36,6 +36,7 @@ import org.traccar.Context;
import org.traccar.GlobalTimer;
import org.traccar.database.ConnectionManager;
import org.traccar.helper.Log;
+import org.traccar.model.Device;
import org.traccar.model.Position;
public class AsyncServlet extends BaseServlet {
@@ -50,7 +51,7 @@ public class AsyncServlet extends BaseServlet {
public static class AsyncSession {
- private static final boolean DEBUG_ASYNC = true;
+ public static final boolean DEBUG_ASYNC = false;
private static final long SESSION_TIMEOUT = 30;
private static final long REQUEST_TIMEOUT = 20;
@@ -60,7 +61,8 @@ public class AsyncServlet extends BaseServlet {
private final Set<Long> devices = new HashSet<>();
private Timeout sessionTimeout;
private Timeout requestTimeout;
- private final Map<Long, Position> positions = new HashMap<>();
+ private final Set<Device> deviceUpdates = new HashSet<>();
+ private final Set<Position> positionUpdates = new HashSet<>();
private AsyncContext activeContext;
private void logEvent(String message) {
@@ -76,7 +78,7 @@ public class AsyncServlet extends BaseServlet {
Collection<Position> initialPositions = Context.getConnectionManager().getInitialState(devices);
for (Position position : initialPositions) {
- positions.put(position.getDeviceId(), position);
+ positionUpdates.add(position);
}
Context.getConnectionManager().addListener(devices, dataListener);
@@ -86,17 +88,34 @@ public class AsyncServlet extends BaseServlet {
return devices.contains(deviceId);
}
- private final ConnectionManager.DataCacheListener dataListener = new ConnectionManager.DataCacheListener() {
+ private final ConnectionManager.UpdateListener dataListener = new ConnectionManager.UpdateListener() {
+ @Override
+ public void onUpdateDevice(Device device) {
+ synchronized (AsyncSession.this) {
+ logEvent("onUpdateDevice deviceId: " + device.getId());
+ if (!destroyed) {
+ if (requestTimeout != null) {
+ requestTimeout.cancel();
+ requestTimeout = null;
+ }
+ deviceUpdates.add(device);
+ if (activeContext != null) {
+ response();
+ }
+ }
+ }
+ }
+
@Override
public void onUpdatePosition(Position position) {
synchronized (AsyncSession.this) {
- logEvent("onUpdate deviceId: " + position.getDeviceId());
+ logEvent("onUpdatePosition deviceId: " + position.getDeviceId());
if (!destroyed) {
if (requestTimeout != null) {
requestTimeout.cancel();
requestTimeout = null;
}
- positions.put(position.getDeviceId(), position);
+ positionUpdates.add(position);
if (activeContext != null) {
response();
}
@@ -142,7 +161,7 @@ public class AsyncServlet extends BaseServlet {
sessionTimeout = null;
}
- if (!positions.isEmpty()) {
+ if (!deviceUpdates.isEmpty() || !positionUpdates.isEmpty()) {
response();
} else {
requestTimeout = GlobalTimer.getTimer().newTimeout(
@@ -158,8 +177,18 @@ public class AsyncServlet extends BaseServlet {
JsonObjectBuilder result = Json.createObjectBuilder();
result.add("success", true);
- result.add("data", JsonConverter.arrayToJson(positions.values()));
- positions.clear();
+
+ if (Context.getConfig().getBoolean("web.oldAsyncFormat")) {
+ result.add("data", JsonConverter.arrayToJson(positionUpdates));
+ } else {
+ JsonObjectBuilder data = Json.createObjectBuilder();
+ data.add("devices", JsonConverter.arrayToJson(deviceUpdates));
+ data.add("positions", JsonConverter.arrayToJson(positionUpdates));
+ result.add("data", data.build());
+ }
+
+ deviceUpdates.clear();
+ positionUpdates.clear();
try {
response.getWriter().println(result.build().toString());
diff --git a/src/org/traccar/web/JsonConverter.java b/src/org/traccar/web/JsonConverter.java
index d39e6b488..a8b68613b 100644
--- a/src/org/traccar/web/JsonConverter.java
+++ b/src/org/traccar/web/JsonConverter.java
@@ -64,7 +64,7 @@ public final class JsonConverter {
final String name = Introspector.decapitalize(method.getName().substring(3));
Class<?> parameterType = method.getParameterTypes()[0];
- if (json.containsKey(name)) {
+ if (json.containsKey(name) && !json.isNull(name)) {
try {
if (parameterType.equals(boolean.class)) {
method.invoke(object, json.getBoolean(name));
diff --git a/test/org/traccar/FilterHandlerTest.java b/test/org/traccar/FilterHandlerTest.java
index f860c489f..b1e4fcb16 100644
--- a/test/org/traccar/FilterHandlerTest.java
+++ b/test/org/traccar/FilterHandlerTest.java
@@ -15,8 +15,8 @@ public class FilterHandlerTest {
@Before
public void setUp() {
- filtingHandler = new FilterHandler(true, true, true, true, 10, 10);
- passingHandler = new FilterHandler(false, false, false, false, 0, 0);
+ filtingHandler = new FilterHandler(true, true, true, true, true, 10, 10);
+ passingHandler = new FilterHandler(false, false, false, false, false, 0, 0);
}
@After
diff --git a/test/org/traccar/ProtocolDecoderTest.java b/test/org/traccar/ProtocolDecoderTest.java
index 2906f2002..8f7ed628b 100644
--- a/test/org/traccar/ProtocolDecoderTest.java
+++ b/test/org/traccar/ProtocolDecoderTest.java
@@ -187,12 +187,17 @@ public class ProtocolDecoderTest {
Assert.assertFalse("no attributes", attributes.isEmpty());
}
- /*if (attributes.containsKey(Event.KEY_LAC) || attributes.containsKey(Event.KEY_CID)) {
+ if (attributes.containsKey(Event.KEY_LAC) || attributes.containsKey(Event.KEY_CID)) {
+ checkInteger(attributes.get(Event.KEY_LAC), 1, 65535);
+ checkInteger(attributes.get(Event.KEY_CID), 1, 268435455);
+ }
+
+ if (attributes.containsKey(Event.KEY_MCC) || attributes.containsKey(Event.KEY_MNC)) {
checkInteger(attributes.get(Event.KEY_MCC), 100, 999);
checkInteger(attributes.get(Event.KEY_MNC), 0, 999);
- checkInteger(attributes.get(Event.KEY_LAC), 1, 65535);
- checkInteger(attributes.get(Event.KEY_CID), 1, 65535);
- }*/
+ Assert.assertTrue("value missing", attributes.containsKey(Event.KEY_LAC));
+ Assert.assertTrue("value missing", attributes.containsKey(Event.KEY_CID));
+ }
}
diff --git a/test/org/traccar/geocode/ReverseGeocoderTest.java b/test/org/traccar/geocode/ReverseGeocoderTest.java
index 2336418d8..a572b0456 100644
--- a/test/org/traccar/geocode/ReverseGeocoderTest.java
+++ b/test/org/traccar/geocode/ReverseGeocoderTest.java
@@ -14,11 +14,9 @@ public class ReverseGeocoderTest {
testNominatim();
testGisgraphy();
}
-
}
public void testGoogle() {
-
ReverseGeocoder reverseGeocoder = new GoogleReverseGeocoder();
reverseGeocoder.getAddress(new AddressFormat(), 37.4217550, -122.0846330, new ReverseGeocoder.ReverseGeocoderCallback() {
@@ -30,7 +28,6 @@ public class ReverseGeocoderTest {
}
public void testNominatim() {
-
ReverseGeocoder reverseGeocoder = new NominatimReverseGeocoder();
reverseGeocoder.getAddress(new AddressFormat(), 40.7337807, -73.9974401, new ReverseGeocoder.ReverseGeocoderCallback() {
@@ -42,7 +39,6 @@ public class ReverseGeocoderTest {
}
public void testGisgraphy() {
-
ReverseGeocoder reverseGeocoder = new GisgraphyReverseGeocoder();
reverseGeocoder.getAddress(new AddressFormat(), 48.8530000, 2.3400000, new ReverseGeocoder.ReverseGeocoderCallback() {
diff --git a/test/org/traccar/location/LocationProviderTest.java b/test/org/traccar/location/LocationProviderTest.java
new file mode 100644
index 000000000..b7280cfea
--- /dev/null
+++ b/test/org/traccar/location/LocationProviderTest.java
@@ -0,0 +1,49 @@
+package org.traccar.location;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.traccar.geocode.AddressFormat;
+import org.traccar.geocode.GisgraphyReverseGeocoder;
+import org.traccar.geocode.GoogleReverseGeocoder;
+import org.traccar.geocode.NominatimReverseGeocoder;
+import org.traccar.geocode.ReverseGeocoder;
+import org.traccar.model.Event;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class LocationProviderTest {
+
+ private boolean enable = false;
+
+ @Test
+ public void test() {
+ if (enable) {
+ testOpenCellId();
+ }
+ }
+
+ public void testOpenCellId() {
+ OpenCellIdLocationProvider locationProvider = new OpenCellIdLocationProvider("fake");
+
+ Map<String, Object> attributes = new HashMap<>();
+ attributes.put(Event.KEY_MCC, 260);
+ attributes.put(Event.KEY_MNC, 2);
+ attributes.put(Event.KEY_LAC, 10250);
+ attributes.put(Event.KEY_CID, 26511);
+
+ locationProvider.getLocation(attributes, new LocationProvider.LocationProviderCallback() {
+ @Override
+ public void onSuccess(double latitude, double longitude) {
+ Assert.assertEquals(60.07254, latitude, 0.00001);
+ Assert.assertEquals(30.30996, longitude, 0.00001);
+ }
+
+ @Override
+ public void onFailure() {
+ Assert.fail();
+ }
+ });
+ }
+
+}
diff --git a/test/org/traccar/protocol/Gps103ProtocolDecoderTest.java b/test/org/traccar/protocol/Gps103ProtocolDecoderTest.java
index 80d1424fc..371b137fd 100644
--- a/test/org/traccar/protocol/Gps103ProtocolDecoderTest.java
+++ b/test/org/traccar/protocol/Gps103ProtocolDecoderTest.java
@@ -10,6 +10,12 @@ public class Gps103ProtocolDecoderTest extends ProtocolDecoderTest {
Gps103ProtocolDecoder decoder = new Gps103ProtocolDecoder(new Gps103Protocol());
+ verifyAttributes(decoder, text(
+ "imei:359710041100000,tracker,000000000,,L,,,fa8,,c9af,,,,,0,0,0.00%,,"));
+
+ verifyAttributes(decoder, text(
+ "imei:863070016871385,tracker,0000000119,,L,,,0FB6,,CB5D,,,"));
+
verifyPosition(decoder, text(
"imei:359710045559474,tracker,151030080103,,F,000101.000,A,5443.3834,N,02512.9071,E,0.00,0;"),
position("2015-10-30 00:01:01.000", true, 54.72306, 25.21512));
diff --git a/test/org/traccar/protocol/HuabaoProtocolDecoderTest.java b/test/org/traccar/protocol/HuabaoProtocolDecoderTest.java
index 0968d6d9c..b24b844d9 100644
--- a/test/org/traccar/protocol/HuabaoProtocolDecoderTest.java
+++ b/test/org/traccar/protocol/HuabaoProtocolDecoderTest.java
@@ -11,8 +11,29 @@ public class HuabaoProtocolDecoderTest extends ProtocolDecoderTest {
HuabaoProtocolDecoder decoder = new HuabaoProtocolDecoder(new HuabaoProtocol());
verifyNothing(decoder, binary(
+ "7E0100002D013511221122000500000000373031303748422D52303347424400000000000000000000003233363631303402CBD5424136383630387E"));
+
+ verifyNothing(decoder, binary(
"7e0100002d007089994489002800000000000000000048422d523033474244000000000000000000000031393036373531024142433030303030d17e"));
+ verifyNothing(decoder, binary(
+ "7E0102000E013511221122000661757468656E7469636174696F6E3F7E"));
+
+ verifyPosition(decoder, binary(
+ "7E02000032013511221122000700000000000C000301578CC006CA3A5C00470000000014072317201501040000000030011631010BD07E"));
+
+ verifyNothing(decoder, binary(
+ "7E010200100940278494700084323031313131303831313333323139369F7E"));
+
+ verifyNothing(decoder, binary(
+ "7e010000190940278494700012000000000000000000000000000000000000094027849470000a7e"));
+
+ verifyPosition(decoder, binary(
+ "7e0200002e094027587492000a000000010000000a03083db7001329f3000000140000130412164952010400000012360a0002341502cb0c20085c107e"));
+
+ verifyPosition(decoder, binary(
+ "7e020000220014012499170007000000000000400e012af16f02cbd2ba000000000000150101194257010400000077a97e"));
+
}
}
diff --git a/test/org/traccar/protocol/MeiligaoProtocolDecoderTest.java b/test/org/traccar/protocol/MeiligaoProtocolDecoderTest.java
index 6d0ba50d5..0c3c570f5 100644
--- a/test/org/traccar/protocol/MeiligaoProtocolDecoderTest.java
+++ b/test/org/traccar/protocol/MeiligaoProtocolDecoderTest.java
@@ -12,8 +12,11 @@ public class MeiligaoProtocolDecoderTest extends ProtocolDecoderTest {
MeiligaoProtocolDecoder decoder = new MeiligaoProtocolDecoder(new MeiligaoProtocol());
+ verifyNothing(decoder, binary(
+ "24240011671440258855405000b24d0d0a"));
+
verifyPosition(decoder, binary(
- "242400706796502079108999553131333131382e3030302c412c313033372e393637382c4e2c30363132312e353637392c572c302e35342c322e34322c3330303931352c2c2c412a37307c302e37377c392e397c303030307c303030302c303161327c3030313138373132374cae0d0a"),
+ "242400706796502079108999553131333131382e3030302c412c313033372e393637382c4e2c30363132312e353637392c572c302e35342c322e34322c3330303931352c2c2c412a37307c302e37377c392e397c303030307c303030302c303161327c3030313138373132374cae0d0a"),
position("2015-09-30 11:31:18.000", true, 10.63280, -61.35947));
verifyPosition(decoder, binary(
diff --git a/test/org/traccar/protocol/Tk103ProtocolDecoderTest.java b/test/org/traccar/protocol/Tk103ProtocolDecoderTest.java
index d9152ecf0..2bffbcd9a 100644
--- a/test/org/traccar/protocol/Tk103ProtocolDecoderTest.java
+++ b/test/org/traccar/protocol/Tk103ProtocolDecoderTest.java
@@ -11,6 +11,9 @@ public class Tk103ProtocolDecoderTest extends ProtocolDecoderTest {
Tk103ProtocolDecoder decoder = new Tk103ProtocolDecoder(new Tk103Protocol());
verifyAttributes(decoder, text(
+ "(088047194605BZ00,510,010,36e6,932c,43,36e6,766b,36,36e6,7668,32"));
+
+ verifyAttributes(decoder, text(
"(013632651491,ZC20,040613,040137,6,42,112,0"));
verifyAttributes(decoder, text(
diff --git a/test/org/traccar/protocol/WatchProtocolDecoderTest.java b/test/org/traccar/protocol/WatchProtocolDecoderTest.java
index 41af104dd..16074d36f 100644
--- a/test/org/traccar/protocol/WatchProtocolDecoderTest.java
+++ b/test/org/traccar/protocol/WatchProtocolDecoderTest.java
@@ -10,6 +10,9 @@ public class WatchProtocolDecoderTest extends ProtocolDecoderTest {
WatchProtocolDecoder decoder = new WatchProtocolDecoder(new WatchProtocol());
+ verifyNothing(decoder, text(
+ "[3G*8800000015*0003*TKQ"));
+
verifyPosition(decoder, text(
"[3G*4700186508*00B1*UD,301015,084840,V,45.853100,N,14.6224899,E,0.00,0.0,0.0,0,84,61,0,11,00000008,7,255,293,70,60,6453,139,60,6432,139,60,6431,132,60,6457,127,60,16353,126,60,6451,121,60,16352,118"));
diff --git a/test/org/traccar/web/AsyncServletTest.java b/test/org/traccar/web/AsyncServletTest.java
new file mode 100644
index 000000000..e2f7b2f21
--- /dev/null
+++ b/test/org/traccar/web/AsyncServletTest.java
@@ -0,0 +1,13 @@
+package org.traccar.web;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class AsyncServletTest {
+
+ @Test
+ public void testDebugDisabled() {
+ Assert.assertFalse("debugging enabled", AsyncServlet.AsyncSession.DEBUG_ASYNC);
+ }
+
+}
diff --git a/web/app.css b/web/app.css
index 897fd0cbc..eb0fdf136 100644
--- a/web/app.css
+++ b/web/app.css
@@ -1,3 +1,13 @@
+.status-color-online {
+ background-color: rgba(77, 250, 144, 0.3);
+}
+.status-color-unknown {
+ background-color: rgba(250, 190, 77, 0.3);
+}
+.status-color-offline {
+ background-color: rgba(255, 84, 104, 0.3);
+}
+
.state-indicator {
position: absolute;
top: -999em;
@@ -12,8 +22,44 @@
}
#attribution {
- position: fixed;
+ position: absolute;
bottom: 10px;
right: 15px;
font-size: x-small;
}
+
+#spinner {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ height: 60px;
+ width: 60px;
+ margin-top: -30px;
+ margin-left: -30px;
+ -webkit-animation: rotation .8s infinite linear;
+ -moz-animation: rotation .8s infinite linear;
+ -o-animation: rotation .8s infinite linear;
+ animation: rotation .8s infinite linear;
+ border-left: 6px solid rgba(56, 146, 212, .15);
+ border-right: 6px solid rgba(56, 146, 212, .15);
+ border-bottom: 6px solid rgba(56, 146, 212, .15);
+ border-top: 6px solid rgba(56, 146, 212, .8);
+ border-radius: 100%;
+}
+
+@-webkit-keyframes rotation {
+ from { -webkit-transform: rotate(0deg); }
+ to { -webkit-transform: rotate(359deg); }
+}
+@-moz-keyframes rotation {
+ from { -moz-transform: rotate(0deg); }
+ to { -moz-transform: rotate(359deg); }
+}
+@-o-keyframes rotation {
+ from { -o-transform: rotate(0deg); }
+ to { -o-transform: rotate(359deg); }
+}
+@keyframes rotation {
+ from { transform: rotate(0deg); }
+ to { transform: rotate(359deg); }
+}
diff --git a/web/app/Style.js b/web/app/Style.js
index 58ab059ad..5202a3fb2 100644
--- a/web/app/Style.js
+++ b/web/app/Style.js
@@ -48,13 +48,13 @@ Ext.define('Traccar.Style', {
mapTextOffset: 10,
mapTextFont: 'bold 12px sans-serif',
- mapColorOnline: '#4DFA90',
- mapColorUnknown: '#FABE4D',
- mapColorOffline: '#FF5468',
+ mapColorOnline: 'rgba(77, 250, 144, 1.0)',
+ mapColorUnknown: 'rgba(250, 190, 77, 1.0)',
+ mapColorOffline: 'rgba(255, 84, 104, 1.0)',
mapColorReport: 'rgba(21, 127, 204, 1.0)',
- mapRadiusNormal: 10,
- mapRadiusSelected: 15,
+ mapRadiusNormal: 9,
+ mapRadiusSelected: 14,
mapMaxZoom: 19,
mapDelay: 500
diff --git a/web/app/controller/Root.js b/web/app/controller/Root.js
index 79827b8a6..733055cdf 100644
--- a/web/app/controller/Root.js
+++ b/web/app/controller/Root.js
@@ -40,6 +40,7 @@ Ext.define('Traccar.controller.Root', {
onServerReturn: function (options, success, response) {
var result;
+ Ext.get('spinner').remove();
if (Traccar.ErrorManager.check(success, response)) {
result = Ext.decode(response.responseText);
if (result.success) {
@@ -96,19 +97,35 @@ Ext.define('Traccar.controller.Root', {
first: first
},
callback: Traccar.app.getErrorHandler(this, function (options, success, response) {
- var i, store, data, position;
+ var i, deviceStore, positionStore, data, devices, positions, device, position;
if (success) {
- store = Ext.getStore('LatestPositions');
+ deviceStore = Ext.getStore('Devices');
+ positionStore = Ext.getStore('LatestPositions');
data = Ext.decode(response.responseText).data;
+ devices = data.devices;
+ positions = data.positions;
- for (i = 0; i < data.length; i++) {
- position = store.findRecord('deviceId', data[i].deviceId, 0, false, false, true);
+ for (i = 0; i < devices.length; i++) {
+ device = deviceStore.findRecord('id', devices[i].id, 0, false, false, true);
+ if (device) {
+ device.set({
+ status: devices[i].status,
+ lastUpdate: devices[i].lastUpdate
+ }, {
+ dirty: false
+ });
+ }
+ }
+
+ for (i = 0; i < positions.length; i++) {
+ position = positionStore.findRecord('deviceId', positions[i].deviceId, 0, false, false, true);
if (position) {
- position.set(data[i]);
+ position.set(positions[i]);
} else {
- store.add(Ext.create('Traccar.model.Position', data[i]));
+ positionStore.add(Ext.create('Traccar.model.Position', positions[i]));
}
}
+
this.asyncUpdate(false);
}
})
diff --git a/web/app/model/Device.js b/web/app/model/Device.js
index e9ed1f680..983e3e62e 100644
--- a/web/app/model/Device.js
+++ b/web/app/model/Device.js
@@ -27,5 +27,11 @@ Ext.define('Traccar.model.Device', {
}, {
name: 'uniqueId',
type: 'string'
+ }, {
+ name: 'status',
+ type: 'string'
+ }, {
+ name: 'lastUpdate',
+ type: 'date'
}]
});
diff --git a/web/app/view/Devices.js b/web/app/view/Devices.js
index 9dcd76a55..21bea6980 100644
--- a/web/app/view/Devices.js
+++ b/web/app/view/Devices.js
@@ -42,6 +42,12 @@ Ext.define('Traccar.view.Devices', {
}, {
xtype: 'tbfill'
}, {
+ id:'deviceFollowButton',
+ glyph: 'xf05b@FontAwesome',
+ tooltip: Strings.deviceFollow,
+ tooltipType: 'title',
+ enableToggle: true
+ }, {
xtype: 'settingsMenu'
}]
},
@@ -52,10 +58,27 @@ Ext.define('Traccar.view.Devices', {
columns: [{
text: Strings.deviceName,
- dataIndex: 'name', flex: 1
+ dataIndex: 'name',
+ flex: 1
}, {
- text: Strings.deviceIdentifier,
- dataIndex: 'uniqueId', flex: 1
+ text: Strings.deviceLastUpdate,
+ dataIndex: 'lastUpdate',
+ flex: 1,
+ renderer: function (value, metaData, record) {
+ var status = record.get('status');
+ switch (status) {
+ case 'online':
+ metaData.tdCls = 'status-color-online';
+ break;
+ case 'offline':
+ metaData.tdCls = 'status-color-offline';
+ break;
+ default:
+ metaData.tdCls = 'status-color-unknown';
+ break;
+ }
+ return Ext.Date.format(value, Traccar.Style.dateTimeFormat);
+ }
}]
});
diff --git a/web/app/view/Login.js b/web/app/view/Login.js
index 14a3fa41f..9c8187ed6 100644
--- a/web/app/view/Login.js
+++ b/web/app/view/Login.js
@@ -35,7 +35,7 @@ Ext.define('Traccar.view.Login', {
autoEl: {
tag: 'form',
method: 'GET',
- action: 'favicon.ico',
+ action: 'blank.html',
target: 'submitTarget'
},
diff --git a/web/app/view/Main.js b/web/app/view/Main.js
index 3bd7256f7..6b4daf138 100644
--- a/web/app/view/Main.js
+++ b/web/app/view/Main.js
@@ -54,7 +54,10 @@ Ext.define('Traccar.view.Main', {
}, {
region: 'south',
xtype: 'reportView',
- height: Traccar.Style.reportHeight
+ height: Traccar.Style.reportHeight,
+ collapsed: true,
+ titleCollapse: true,
+ floatable: false
}, {
region: 'center',
xtype: 'mapView',
diff --git a/web/app/view/Map.js b/web/app/view/Map.js
index 7d81699a3..b8c0d8dc5 100644
--- a/web/app/view/Map.js
+++ b/web/app/view/Map.js
@@ -35,13 +35,21 @@ Ext.define('Traccar.view.Map', {
return this.mapView;
},
- getVectorSource: function () {
- return this.vectorSource;
+ getLatestSource: function () {
+ return this.latestSource;
+ },
+
+ getRouteSource: function () {
+ return this.routeSource;
+ },
+
+ getReportSource: function () {
+ return this.reportSource;
},
listeners: {
afterrender: function () {
- var user, server, layer, type, bingKey, vectorLayer, lat, lon, zoom, target;
+ var user, server, layer, type, bingKey, latestLayer, routeLayer, reportLayer, lat, lon, zoom, target;
user = Traccar.app.getUser();
server = Traccar.app.getServer();
@@ -78,9 +86,19 @@ Ext.define('Traccar.view.Map', {
});
}
- this.vectorSource = new ol.source.Vector({});
- vectorLayer = new ol.layer.Vector({
- source: this.vectorSource
+ this.latestSource = new ol.source.Vector({});
+ latestLayer = new ol.layer.Vector({
+ source: this.latestSource
+ });
+
+ this.routeSource = new ol.source.Vector({});
+ routeLayer = new ol.layer.Vector({
+ source: this.routeSource
+ });
+
+ this.reportSource = new ol.source.Vector({});
+ reportLayer = new ol.layer.Vector({
+ source: this.reportSource
});
lat = user.get('latitude') || server.get('latitude') || Traccar.Style.mapDefaultLat;
@@ -95,7 +113,7 @@ Ext.define('Traccar.view.Map', {
this.map = new ol.Map({
target: this.body.dom.id,
- layers: [layer, vectorLayer],
+ layers: [layer, routeLayer, reportLayer, latestLayer],
view: this.mapView
});
diff --git a/web/app/view/MapController.js b/web/app/view/MapController.js
index c153ebd45..e4ce53b57 100644
--- a/web/app/view/MapController.js
+++ b/web/app/view/MapController.js
@@ -27,6 +27,10 @@ Ext.define('Traccar.view.MapController', {
}
},
store: {
+ '#Devices': {
+ add: 'updateDevice',
+ update: 'updateDevice'
+ },
'#LatestPositions': {
add: 'updateLatest',
update: 'updateLatest'
@@ -49,6 +53,54 @@ Ext.define('Traccar.view.MapController', {
this.reportMarkers = {};
},
+ getDeviceColor: function (device) {
+ switch (device.get('status')) {
+ case 'online':
+ return Traccar.Style.mapColorOnline;
+ case 'offline':
+ return Traccar.Style.mapColorOffline;
+ default:
+ return Traccar.Style.mapColorUnknown;
+ }
+ },
+
+ changeMarkerColor: function (style, color) {
+ return new ol.style.Style({
+ image: new ol.style.Arrow({
+ radius: style.getImage().getRadius(),
+ fill: new ol.style.Fill({
+ color: color
+ }),
+ stroke: style.getImage().getStroke(),
+ rotation: style.getImage().getRotation()
+ }),
+ text: style.getText()
+ });
+ },
+
+ updateDevice: function (store, data) {
+ var i, device, deviceId;
+
+ if (!Ext.isArray(data)) {
+ data = [data];
+ }
+
+ for (i = 0; i < data.length; i++) {
+ device = data[i];
+ deviceId = device.get('id');
+
+ if (deviceId in this.latestMarkers) {
+ marker = this.latestMarkers[deviceId];
+ marker.setStyle(
+ this.changeMarkerColor(marker.getStyle(), this.getDeviceColor(device)));
+ }
+ }
+ },
+
+ followSelected: function () {
+ return Ext.getCmp('deviceFollowButton') && Ext.getCmp('deviceFollowButton').pressed;
+ },
+
updateLatest: function (store, data) {
var i, position, geometry, device, deviceId, marker, style;
@@ -73,14 +125,18 @@ Ext.define('Traccar.view.MapController', {
marker = new ol.Feature(geometry);
marker.set('record', device);
this.latestMarkers[deviceId] = marker;
- this.getView().getVectorSource().addFeature(marker);
+ this.getView().getLatestSource().addFeature(marker);
- style = this.getLatestMarker();
+ style = this.getLatestMarker(this.getDeviceColor(device));
style.getText().setText(device.get('name'));
marker.setStyle(style);
}
marker.getStyle().getImage().setRotation(position.get('course') * Math.PI / 180);
+
+ if (marker === this.selectedMarker && this.followSelected()) {
+ this.getView().getMapView().setCenter(marker.getGeometry().getCoordinates());
+ }
}
},
@@ -93,7 +149,7 @@ Ext.define('Traccar.view.MapController', {
geometry: new ol.geom.LineString([])
});
this.reportRoute.setStyle(this.getRouteStyle());
- this.getView().getVectorSource().addFeature(this.reportRoute);
+ this.getView().getRouteSource().addFeature(this.reportRoute);
for (i = 0; i < data.length; i++) {
position = data[i];
@@ -107,12 +163,12 @@ Ext.define('Traccar.view.MapController', {
marker = new ol.Feature(geometry);
marker.set('record', position);
this.reportMarkers[position.get('id')] = marker;
- this.getView().getVectorSource().addFeature(marker);
+ this.getView().getReportSource().addFeature(marker);
style = this.getReportMarker();
style.getImage().setRotation(position.get('course') * Math.PI / 180);
- style.getText().setText(
- Ext.Date.format(position.get('fixTime'), Traccar.Style.dateTimeFormat));
+ /*style.getText().setText(
+ Ext.Date.format(position.get('fixTime'), Traccar.Style.dateTimeFormat));*/
marker.setStyle(style);
@@ -121,18 +177,17 @@ Ext.define('Traccar.view.MapController', {
},
clearReport: function (store) {
- var vectorSource, key;
- vectorSource = this.getView().getVectorSource();
+ var key;
if (this.reportRoute) {
- vectorSource.removeFeature(this.reportRoute);
+ this.getView().getRouteSource().removeFeature(this.reportRoute);
this.reportRoute = null;
}
if (this.reportMarkers) {
for (key in this.reportMarkers) {
if (this.reportMarkers.hasOwnProperty(key)) {
- vectorSource.removeFeature(this.reportMarkers[key]);
+ this.getView().getReportSource().removeFeature(this.reportMarkers[key]);
}
}
this.reportMarkers = {};
@@ -175,9 +230,9 @@ Ext.define('Traccar.view.MapController', {
});
},
- getLatestMarker: function () {
+ getLatestMarker: function (color) {
return this.getMarkerStyle(
- Traccar.Style.mapRadiusNormal, Traccar.Style.mapColorUnknown);
+ Traccar.Style.mapRadiusNormal, color);
},
getReportMarker: function () {
diff --git a/web/blank.html b/web/blank.html
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/web/blank.html
diff --git a/web/debug.html b/web/debug.html
index de63d32a2..c4340af97 100644
--- a/web/debug.html
+++ b/web/debug.html
@@ -8,14 +8,15 @@
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/ol3/3.11.1/ol.css">
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/font-awesome/4.4.0/css/font-awesome.min.css">
<link rel="stylesheet" href="app.css">
+</head>
+<body>
+<div id="spinner"></div>
+<div id="attribution">Powered by <a href="https://www.traccar.org/">Traccar GPS Tracking System</a></div>
<script src="//cdnjs.cloudflare.com/ajax/libs/extjs/6.0.0/ext-all-debug.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/extjs/6.0.0/classic/theme-neptune/theme-neptune.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/ol3/3.11.1/ol-debug.js"></script>
<script src="arrowstyle.js"></script>
<script src="locale.js"></script>
<script src="app.js"></script>
-</head>
-<body>
-<div id="attribution">Powered by <a href="https://www.traccar.org/">Traccar GPS Tracking System</a></div>
</body>
</html>
diff --git a/web/l10n/en.json b/web/l10n/en.json
index a3489fbe1..d70e97a07 100644
--- a/web/l10n/en.json
+++ b/web/l10n/en.json
@@ -31,7 +31,9 @@
"deviceTitle": "Devices",
"deviceName": "Name",
"deviceIdentifier": "Identifier",
+ "deviceLastUpdate": "Last Update",
"deviceCommand": "Command",
+ "deviceFollow": "Follow",
"settingsTitle": "Settings",
"settingsUser": "Account",
"settingsServer": "Server",
diff --git a/web/l10n/hu.json b/web/l10n/hu.json
index 8fb48a830..56ba28ae9 100644
--- a/web/l10n/hu.json
+++ b/web/l10n/hu.json
@@ -31,6 +31,7 @@
"deviceTitle": "Eszközök",
"deviceName": "Eszköznév",
"deviceIdentifier": "Azonosító",
+ "deviceLastUpdate": "Utolsó frissítés",
"deviceCommand": "Parancs",
"settingsTitle": "Beállítások",
"settingsUser": "Fiók",
diff --git a/web/l10n/no.json b/web/l10n/no.json
new file mode 100644
index 000000000..8a53e1213
--- /dev/null
+++ b/web/l10n/no.json
@@ -0,0 +1,79 @@
+{
+ "sharedLoading": "Laster...",
+ "sharedSave": "Lagre",
+ "sharedCancel": "Avbryte",
+ "sharedAdd": "Legg til",
+ "sharedEdit": "Endre",
+ "sharedRemove": "Fjerne",
+ "sharedRemoveConfirm": "Fjerne element?",
+ "sharedKm": "km",
+ "sharedMi": "mi",
+ "sharedKmh": "km/t",
+ "sharedMph": "mph",
+ "sharedHour": "Time",
+ "sharedMinute": "Minutt",
+ "sharedSecond": "Sekund",
+ "errorTitle": "Feil",
+ "errorUnknown": "Ukjent feil",
+ "errorConnection": "Forbindelse feilet",
+ "userName": "Navn",
+ "userEmail": "Epost",
+ "userPassword": "Passord",
+ "userAdmin": "Admin",
+ "loginTitle": "Logg inn",
+ "loginLanguage": "Språk",
+ "loginRegister": "Registrere",
+ "loginLogin": "Logg inn",
+ "loginFailed": "Feil epost eller passord",
+ "loginCreated": "Ny bruker har blitt registrert",
+ "loginLogout": "Logg ut",
+ "deviceDialog": "Enhet",
+ "deviceTitle": "Enheter",
+ "deviceName": "Navn",
+ "deviceIdentifier": "Identitet",
+ "deviceCommand": "Kommando",
+ "settingsTitle": "Innstillinger",
+ "settingsUser": "Konto",
+ "settingsServer": "Server",
+ "settingsUsers": "Brukere",
+ "settingsDistanceUnit": "Avstand",
+ "settingsSpeedUnit": "Hastighet",
+ "reportTitle": "Rapporter",
+ "reportDevice": "Enhet",
+ "reportFrom": "Fra",
+ "reportTo": "Til",
+ "reportShow": "Vis",
+ "reportClear": "Nullstille",
+ "positionFixTime": "Tid",
+ "positionValid": "Gyldig",
+ "positionLatitude": "Latitude",
+ "positionLongitude": "Longitude",
+ "positionAltitude": "Altitude",
+ "positionSpeed": "Hastighet",
+ "positionCourse": "Retning",
+ "positionAddress": "Adresse",
+ "positionProtocol": "Protokoll",
+ "serverTitle": "Server Instillinger",
+ "serverZoom": "Zoom",
+ "serverRegistration": "Registere",
+ "mapTitle": "Kart",
+ "mapLayer": "Kart Lag",
+ "mapCustom": "Egendefinert Kart",
+ "mapOsm": "Open Street Kart",
+ "mapBingKey": "Bing Maps Nøkkel",
+ "mapBingRoad": "Bing Maps Vei",
+ "mapBingAerial": "Bing Maps Fly",
+ "stateTitle": "Stat",
+ "stateName": "Egenskap",
+ "stateValue": "Verdi",
+ "commandTitle": "Kommando",
+ "commandSend": "Sende",
+ "commandType": "Type",
+ "commandSent": "Kommando har blitt sendt",
+ "commandPositionPeriodic": "Periodisk Rapportering",
+ "commandPositionStop": "Stoppe Rapportering",
+ "commandEngineStop": "Motor Stop",
+ "commandEngineResume": "Motor Restarte",
+ "commandFrequency": "Frekvens",
+ "commandUnit": "Enhet"
+} \ No newline at end of file
diff --git a/web/l10n/ro.json b/web/l10n/ro.json
new file mode 100644
index 000000000..ba1d4b0a6
--- /dev/null
+++ b/web/l10n/ro.json
@@ -0,0 +1,80 @@
+{
+ "sharedLoading": "Încărcare...",
+ "sharedSave": "Salvare",
+ "sharedCancel": "Anulare",
+ "sharedAdd": "Adăuga",
+ "sharedEdit": "Edita",
+ "sharedRemove": "Elimina",
+ "sharedRemoveConfirm": "Eliminati?",
+ "sharedKm": "km",
+ "sharedMi": "mi",
+ "sharedKmh": "km/h",
+ "sharedMph": "mph",
+ "sharedHour": "Oră",
+ "sharedMinute": "Minut",
+ "sharedSecond": "Secundă",
+ "errorTitle": "Eroare",
+ "errorUnknown": "Eroare necunoscută",
+ "errorConnection": "Eroare de conexiune",
+ "userName": "Nume",
+ "userEmail": "Email",
+ "userPassword": "Parolă",
+ "userAdmin": "Admin",
+ "loginTitle": "Intră in cont",
+ "loginLanguage": "Limbă",
+ "loginRegister": "Înregistrare",
+ "loginLogin": "Intră in cont",
+ "loginFailed": "E-mail sau parolă incorectă",
+ "loginCreated": "Nou utilizator a fost înregistrată",
+ "loginLogout": "Deconectare",
+ "deviceDialog": "Dispozitiv",
+ "deviceTitle": "Dispozitive",
+ "deviceName": "Nume",
+ "deviceIdentifier": "Identifier",
+ "deviceLastUpdate": "Ultima modificare",
+ "deviceCommand": "Comandă",
+ "settingsTitle": "Setări",
+ "settingsUser": "Cont",
+ "settingsServer": "Server",
+ "settingsUsers": "Utilizatori",
+ "settingsDistanceUnit": "Distanţă",
+ "settingsSpeedUnit": "Viteză",
+ "reportTitle": "Rapoarte",
+ "reportDevice": "Dispozitiv",
+ "reportFrom": "De la ",
+ "reportTo": "Până la",
+ "reportShow": "Arată",
+ "reportClear": "șterge",
+ "positionFixTime": "Timp",
+ "positionValid": "Valabil",
+ "positionLatitude": "Latitudine",
+ "positionLongitude": "Longitudine",
+ "positionAltitude": "Altitudine",
+ "positionSpeed": "Viteză",
+ "positionCourse": "Curs",
+ "positionAddress": "Adresă",
+ "positionProtocol": "Protocol",
+ "serverTitle": "Setări server",
+ "serverZoom": "Zoom",
+ "serverRegistration": "Înregistrare",
+ "mapTitle": "Hartă",
+ "mapLayer": "Stratul de Hartă",
+ "mapCustom": "Hartă Editare",
+ "mapOsm": "Open Street Map",
+ "mapBingKey": "Bing Maps Key",
+ "mapBingRoad": "Bing Maps Road",
+ "mapBingAerial": "Bing Maps aeriene",
+ "stateTitle": "Stare",
+ "stateName": "Proprietate",
+ "stateValue": "Valoare",
+ "commandTitle": "Comandă",
+ "commandSend": "Trimite",
+ "commandType": "Tip",
+ "commandSent": "Comandă a fost trimis",
+ "commandPositionPeriodic": "Raportarea Periodică",
+ "commandPositionStop": "Oprire de raportare",
+ "commandEngineStop": "Oprire Motor",
+ "commandEngineResume": "Motor Continuă",
+ "commandFrequency": "Frecvenţă",
+ "commandUnit": "Unitate"
+} \ No newline at end of file
diff --git a/web/l10n/si.json b/web/l10n/si.json
index 8e565423b..db6381586 100644
--- a/web/l10n/si.json
+++ b/web/l10n/si.json
@@ -1,5 +1,5 @@
{
- "sharedLoading": "පූරණය ...",
+ "sharedLoading": "පූරණය කරමින් ...",
"sharedSave": "සුරකින්න",
"sharedCancel": "අවලංගු කරන්න",
"sharedAdd": "එක් කරන්න",
@@ -13,19 +13,19 @@
"sharedHour": "පැය",
"sharedMinute": "මිනිත්තු",
"sharedSecond": "තත්පර",
- "errorTitle": "දෝෂයක්",
- "errorUnknown": "නොදන්නා දෝෂයක්",
- "errorConnection": "සම්බන්ධතා දෝෂයක්",
+ "errorTitle": "දෝෂයක් ",
+ "errorUnknown": "නොදන්නා දෝෂයක් !",
+ "errorConnection": "සම්බන්ධතා දෝෂයක් !",
"userName": "නම",
"userEmail": "විද්යුත් තැපෑල",
"userPassword": "මුරපදය",
"userAdmin": "පරිපාලක",
- "loginTitle": "පිවිසුම",
+ "loginTitle": "පිවිසුම් ගිණුම",
"loginLanguage": "භාෂාව",
"loginRegister": "ලියාපදිංචි කරන්න",
"loginLogin": "පිවිසුම",
- "loginFailed": "ඊ-මේල් ලිපිනය හෝ මුරපදය වැරදිය",
- "loginCreated": "නව පරිශීලක ලියාපදිංචි කරන ලදි",
+ "loginFailed": "ඊ-මේල් ලිපිනය හෝ මුරපදය වැරදිය !",
+ "loginCreated": "නව පරිශීලක ලියාපදිංචි කරන ලදි !",
"loginLogout": "ඉවත්වන්න",
"deviceDialog": "උපාංගය",
"deviceTitle": "උපාංග",
@@ -69,7 +69,7 @@
"commandTitle": "විධානය",
"commandSend": "යවන්න",
"commandType": "වර්ගය",
- "commandSent": "අණ යවා ඇත",
+ "commandSent": "විධානය යවා ඇත",
"commandPositionPeriodic": "ආවර්තිතව වාර්තා කරන්න",
"commandPositionStop": "වාර්තා කිරීම නවත්වන්න",
"commandEngineStop": "එන්ජිම නවත්වන්න",
diff --git a/web/locale.js b/web/locale.js
index a5acb0ad3..a4bc9918d 100644
--- a/web/locale.js
+++ b/web/locale.js
@@ -33,9 +33,11 @@ Locale.languages = {
'hu': { name: 'Magyar', code: 'hu' },
'lt': { name: 'Lietuvių', code: 'lt' },
'nl': { name: 'Nederlands', code: 'nl' },
+ 'no': { name: 'Norsk', code: 'no_NB' },
'pl': { name: 'Polski', code: 'pl' },
'pt': { name: 'Português', code: 'pt' },
'pt_BR': { name: 'Português (Brasil)', code: 'pt_BR' },
+ 'ro': { name: 'Română', code: 'ro' },
'ru': { name: 'Русский', code: 'ru' },
'si': { name: 'සිංහල', code: 'en' },
'sk': { name: 'Slovenčina', code: 'sk' },
diff --git a/web/release.html b/web/release.html
index 27200b142..100849130 100644
--- a/web/release.html
+++ b/web/release.html
@@ -8,6 +8,10 @@
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/ol3/3.11.1/ol.min.css">
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/font-awesome/4.4.0/css/font-awesome.min.css">
<link rel="stylesheet" href="app.css">
+</head>
+<body>
+<div id="spinner"></div>
+<div id="attribution">Powered by <a href="https://www.traccar.org/">Traccar GPS Tracking System</a></div>
<script src="//cdnjs.cloudflare.com/ajax/libs/extjs/6.0.0/ext-all.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/extjs/6.0.0/classic/theme-neptune/theme-neptune.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/ol3/3.11.1/ol-debug.min.js"></script>
@@ -16,8 +20,5 @@
<script type="text/javascript">
Ext.Loader.loadScript('app.min.js');
</script>
-</head>
-<body>
-<div id="attribution">Powered by <a href="https://www.traccar.org/">Traccar GPS Tracking System</a></div>
</body>
</html>
diff --git a/web/tests.html b/web/tests.html
index 2e89e5e58..5df01347d 100644
--- a/web/tests.html
+++ b/web/tests.html
@@ -1,11 +1,12 @@
<!DOCTYPE html>
<html>
<head>
-<link rel="stylesheet" type="text/css" href="//cdn.sencha.com/ext/gpl/5.1.0/packages/ext-theme-crisp/build/resources/ext-theme-crisp-all.css" />
-<link rel="stylesheet" type="text/css" href="//cdn.traccar.org/siesta/siesta-3.1.0-lite/resources/css/siesta-all.css">
-<script type="text/javascript" src="//cdn.sencha.com/ext/gpl/5.1.0/build/ext-all.js"></script>
-<script type="text/javascript" src="//cdn.traccar.org/siesta/siesta-3.1.0-lite/siesta-all.js"></script>
-<script type="text/javascript" src="tests/index.js"></script>
+<link rel="stylesheet" type="text/css" href="http://bryntum.com/examples/extjs-6.0.1/build/classic/theme-triton/resources/theme-triton-all.css">
+<link rel="stylesheet" type="text/css" href="//cdn.traccar.org/siesta/siesta-4.0.0-lite/resources/css/siesta-all.css">
+<script src="http://bryntum.com/examples/extjs-6.0.1/build/classic/theme-triton/theme-triton.js" type="text/javascript"></script>
+<script src="http://bryntum.com/examples/extjs-6.0.1/build/ext-all.js" type="text/javascript"></script>
+<script src="//cdn.traccar.org/siesta/siesta-4.0.0-lite/siesta-all.js"></script>
+<script src="tests/index.js"></script>
</head>
<body>
</body>
diff --git a/web/tests/010_sanity.t.js b/web/tests/010_sanity.t.js
index b2773ee19..a7c732c20 100644
--- a/web/tests/010_sanity.t.js
+++ b/web/tests/010_sanity.t.js
@@ -8,7 +8,7 @@ StartTest(function (t) {
t.ok(Traccar.Application, 'Traccar.Application is defined');
t.ok(Strings, 'Strings are defined');
- t.ok(Traccar.Styles, 'Traccar.Styles are defined');
+ t.ok(Traccar.Style, 'Traccar.Style are defined');
t.done();
});
diff --git a/web/tests/index.js b/web/tests/index.js
index a75d6e4df..8e502c269 100644
--- a/web/tests/index.js
+++ b/web/tests/index.js
@@ -1,6 +1,6 @@
-var Harness = Siesta.Harness.Browser.ExtJS;
+var harness = new Siesta.Harness.Browser.ExtJS();
-Harness.configure({
+harness.configure({
title: 'Traccar Test Suite',
preload: [
'//cdnjs.cloudflare.com/ajax/libs/extjs/6.0.0/ext-all.js',
@@ -9,6 +9,6 @@ Harness.configure({
]
});
-Harness.start(
+harness.start(
'tests/010_sanity.t.js'
);