diff options
65 files changed, 1251 insertions, 161 deletions
@@ -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' ); |