diff options
Diffstat (limited to 'src/main/java/org')
112 files changed, 4243 insertions, 549 deletions
diff --git a/src/main/java/org/traccar/Main.java b/src/main/java/org/traccar/Main.java index 6ebd1d399..47d6e91df 100644 --- a/src/main/java/org/traccar/Main.java +++ b/src/main/java/org/traccar/Main.java @@ -1,5 +1,5 @@ /* - * Copyright 2012 - 2019 Anton Tananaev (anton@traccar.org) + * Copyright 2012 - 2020 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,6 +19,7 @@ import com.google.inject.Guice; import com.google.inject.Injector; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.traccar.api.HealthCheckService; import java.lang.management.ManagementFactory; import java.lang.management.MemoryMXBean; @@ -105,6 +106,27 @@ public final class Main { } } + private static void scheduleHealthCheck() { + HealthCheckService service = new HealthCheckService(); + if (service.isEnabled()) { + new Timer().scheduleAtFixedRate( + service.createTask(), service.getPeriod(), service.getPeriod()); + } + } + + private static void scheduleDatabaseCleanup() { + new Timer().scheduleAtFixedRate(new TimerTask() { + @Override + public void run() { + try { + Context.getDataManager().clearHistory(); + } catch (SQLException error) { + LOGGER.warn("Clear history error", error); + } + } + }, 0, CLEAN_PERIOD); + } + public static void run(String configFile) { try { Context.init(configFile); @@ -118,35 +140,19 @@ public final class Main { Context.getWebServer().start(); } - new Timer().scheduleAtFixedRate(new TimerTask() { - @Override - public void run() { - try { - Context.getDataManager().clearHistory(); - } catch (SQLException error) { - LOGGER.warn("Clear history error", error); - } - } - }, 0, CLEAN_PERIOD); + scheduleHealthCheck(); + scheduleDatabaseCleanup(); - Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { - @Override - public void uncaughtException(Thread t, Throwable e) { - LOGGER.error("Thread exception", e); - } - }); + Thread.setDefaultUncaughtExceptionHandler((t, e) -> LOGGER.error("Thread exception", e)); - Runtime.getRuntime().addShutdownHook(new Thread() { - @Override - public void run() { - LOGGER.info("Shutting down server..."); + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + LOGGER.info("Shutting down server..."); - if (Context.getWebServer() != null) { - Context.getWebServer().stop(); - } - Context.getServerManager().stop(); + if (Context.getWebServer() != null) { + Context.getWebServer().stop(); } - }); + Context.getServerManager().stop(); + })); } catch (Exception e) { LOGGER.error("Main method error", e); throw new RuntimeException(e); diff --git a/src/main/java/org/traccar/MainModule.java b/src/main/java/org/traccar/MainModule.java index 1d5508f5a..c3c0182d7 100644 --- a/src/main/java/org/traccar/MainModule.java +++ b/src/main/java/org/traccar/MainModule.java @@ -171,7 +171,7 @@ public class MainModule extends AbstractModule { case "ban": return new BanGeocoder(cacheSize, addressFormat); case "here": - return new HereGeocoder(id, key, language, cacheSize, addressFormat); + return new HereGeocoder(url, id, key, language, cacheSize, addressFormat); case "mapmyindia": return new MapmyIndiaGeocoder(url, key, cacheSize, addressFormat); default: @@ -192,7 +192,7 @@ public class MainModule extends AbstractModule { case "google": return new GoogleGeolocationProvider(key); case "opencellid": - return new OpenCellIdGeolocationProvider(key); + return new OpenCellIdGeolocationProvider(url, key); case "unwired": return new UnwiredGeolocationProvider(url, key); default: diff --git a/src/main/java/org/traccar/StringProtocolEncoder.java b/src/main/java/org/traccar/StringProtocolEncoder.java index d9acce7f0..e9fb65500 100644 --- a/src/main/java/org/traccar/StringProtocolEncoder.java +++ b/src/main/java/org/traccar/StringProtocolEncoder.java @@ -17,8 +17,6 @@ package org.traccar; import org.traccar.model.Command; -import java.util.Map; - public abstract class StringProtocolEncoder extends BaseProtocolEncoder { public StringProtocolEncoder(Protocol protocol) { @@ -31,21 +29,27 @@ public abstract class StringProtocolEncoder extends BaseProtocolEncoder { protected String formatCommand(Command command, String format, ValueFormatter valueFormatter, String... keys) { - String result = String.format(format, (Object[]) keys); - - result = result.replaceAll("\\{" + Command.KEY_UNIQUE_ID + "}", getUniqueId(command.getDeviceId())); - for (Map.Entry<String, Object> entry : command.getAttributes().entrySet()) { + Object[] values = new String[keys.length]; + for (int i = 0; i < keys.length; i++) { String value = null; - if (valueFormatter != null) { - value = valueFormatter.formatValue(entry.getKey(), entry.getValue()); - } - if (value == null) { - value = entry.getValue().toString(); + if (keys[i].equals(Command.KEY_UNIQUE_ID)) { + value = getUniqueId(command.getDeviceId()); + } else { + Object object = command.getAttributes().get(keys[i]); + if (valueFormatter != null) { + value = valueFormatter.formatValue(keys[i], object); + } + if (value == null && object != null) { + value = object.toString(); + } + if (value == null) { + value = ""; + } } - result = result.replaceAll("\\{" + entry.getKey() + "}", value); + values[i] = value; } - return result; + return String.format(format, values); } protected String formatCommand(Command command, String format, String... keys) { diff --git a/src/main/java/org/traccar/api/ExtendedObjectResource.java b/src/main/java/org/traccar/api/ExtendedObjectResource.java index 007a7b1bd..9e554217e 100644 --- a/src/main/java/org/traccar/api/ExtendedObjectResource.java +++ b/src/main/java/org/traccar/api/ExtendedObjectResource.java @@ -55,8 +55,8 @@ public class ExtendedObjectResource<T extends BaseModel> extends BaseObjectResou Context.getPermissionsManager().checkDevice(getUserId(), deviceId); result.retainAll(manager.getDeviceItems(deviceId)); } - return manager.getItems(result); + return manager.getItems(result); } } diff --git a/src/main/java/org/traccar/api/HealthCheckService.java b/src/main/java/org/traccar/api/HealthCheckService.java new file mode 100644 index 000000000..1e8f0d731 --- /dev/null +++ b/src/main/java/org/traccar/api/HealthCheckService.java @@ -0,0 +1,91 @@ +/* + * Copyright 2020 Anton Tananaev (anton@traccar.org) + * + * 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.api; + +import com.sun.jna.Library; +import com.sun.jna.Native; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.traccar.Context; + +import java.util.TimerTask; + +public class HealthCheckService { + + private static final Logger LOGGER = LoggerFactory.getLogger(HealthCheckService.class); + + private SystemD systemD; + + private boolean enabled; + private long period; + + public HealthCheckService() { + if (Context.getConfig().getBoolean("web.healthCheck") + && System.getProperty("os.name").toLowerCase().startsWith("linux")) { + try { + systemD = Native.load("systemd", SystemD.class); + String watchdogTimer = System.getenv("WATCHDOG_USEC"); + if (watchdogTimer != null && !watchdogTimer.isEmpty()) { + period = Long.parseLong(watchdogTimer) / 1000 * 4 / 5; + } + if (period > 0) { + LOGGER.info("Health check enabled with period {}", period); + enabled = true; + } + } catch (UnsatisfiedLinkError e) { + LOGGER.warn("No systemd support", e); + } + } + } + + public boolean isEnabled() { + return enabled; + } + + public long getPeriod() { + return period; + } + + private String getUrl() { + String address = Context.getConfig().getString("web.address", "localhost"); + int port = Context.getConfig().getInteger("web.port", 8082); + return "http://" + address + ":" + port + "/api/server"; + } + + public TimerTask createTask() { + return new TimerTask() { + @Override + public void run() { + LOGGER.debug("Health check running"); + int status = Context.getClient().target(getUrl()).request().get().getStatus(); + if (status == 200) { + int result = systemD.sd_notify(0, "WATCHDOG=1"); + if (result < 0) { + LOGGER.warn("Health check notify error {}", result); + } + } else { + LOGGER.warn("Health check failed with status {}", status); + } + } + }; + } + + interface SystemD extends Library { + @SuppressWarnings("checkstyle:MethodName") + int sd_notify(@SuppressWarnings("checkstyle:ParameterName") int unset_environment, String state); + } + +} diff --git a/src/main/java/org/traccar/api/resource/SessionResource.java b/src/main/java/org/traccar/api/resource/SessionResource.java index fd331c766..e3c5d457f 100644 --- a/src/main/java/org/traccar/api/resource/SessionResource.java +++ b/src/main/java/org/traccar/api/resource/SessionResource.java @@ -18,6 +18,7 @@ package org.traccar.api.resource; import org.traccar.Context; import org.traccar.api.BaseResource; import org.traccar.helper.DataConverter; +import org.traccar.helper.ServletHelper; import org.traccar.helper.LogAction; import org.traccar.model.User; @@ -106,6 +107,7 @@ public class SessionResource extends BaseResource { LogAction.login(user.getId()); return user; } else { + LogAction.failedLogin(ServletHelper.retrieveRemoteAddress(request)); throw new WebApplicationException(Response.status(Response.Status.UNAUTHORIZED).build()); } } diff --git a/src/main/java/org/traccar/database/BaseObjectManager.java b/src/main/java/org/traccar/database/BaseObjectManager.java index 8bf9ef860..e274e5aba 100644 --- a/src/main/java/org/traccar/database/BaseObjectManager.java +++ b/src/main/java/org/traccar/database/BaseObjectManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Anton Tananaev (anton@traccar.org) + * Copyright 2017 - 2020 Anton Tananaev (anton@traccar.org) * Copyright 2017 Andrey Kunitsyn (andrey@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,6 +23,8 @@ import java.util.LinkedList; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -32,6 +34,8 @@ public class BaseObjectManager<T extends BaseModel> { private static final Logger LOGGER = LoggerFactory.getLogger(BaseObjectManager.class); + private final ReadWriteLock lock = new ReentrantReadWriteLock(); + private final DataManager dataManager; private Map<Long, T> items; @@ -43,6 +47,22 @@ public class BaseObjectManager<T extends BaseModel> { refreshItems(); } + protected final void readLock() { + lock.readLock().lock(); + } + + protected final void readUnlock() { + lock.readLock().unlock(); + } + + protected final void writeLock() { + lock.writeLock().lock(); + } + + protected final void writeUnlock() { + lock.writeLock().unlock(); + } + protected final DataManager getDataManager() { return dataManager; } @@ -52,12 +72,18 @@ public class BaseObjectManager<T extends BaseModel> { } public T getById(long itemId) { - return items.get(itemId); + try { + readLock(); + return items.get(itemId); + } finally { + readUnlock(); + } } public void refreshItems() { if (dataManager != null) { try { + writeLock(); Collection<T> databaseItems = dataManager.getObjects(baseClass); if (items == null) { items = new ConcurrentHashMap<>(databaseItems.size()); @@ -78,12 +104,19 @@ public class BaseObjectManager<T extends BaseModel> { } } catch (SQLException error) { LOGGER.warn("Error refreshing items", error); + } finally { + writeUnlock(); } } } protected void addNewItem(T item) { - items.put(item.getId(), item); + try { + writeLock(); + items.put(item.getId(), item); + } finally { + writeUnlock(); + } } public void addItem(T item) throws SQLException { @@ -92,7 +125,12 @@ public class BaseObjectManager<T extends BaseModel> { } protected void updateCachedItem(T item) { - items.put(item.getId(), item); + try { + writeLock(); + items.put(item.getId(), item); + } finally { + writeUnlock(); + } } public void updateItem(T item) throws SQLException { @@ -101,7 +139,12 @@ public class BaseObjectManager<T extends BaseModel> { } protected void removeCachedItem(long itemId) { - items.remove(itemId); + try { + writeLock(); + items.remove(itemId); + } finally { + writeUnlock(); + } } public void removeItem(long itemId) throws SQLException { @@ -121,7 +164,12 @@ public class BaseObjectManager<T extends BaseModel> { } public Set<Long> getAllItems() { - return items.keySet(); + try { + readLock(); + return items.keySet(); + } finally { + readUnlock(); + } } } diff --git a/src/main/java/org/traccar/database/CommandsManager.java b/src/main/java/org/traccar/database/CommandsManager.java index dc9512d9e..de6eeeba8 100644 --- a/src/main/java/org/traccar/database/CommandsManager.java +++ b/src/main/java/org/traccar/database/CommandsManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Anton Tananaev (anton@traccar.org) + * Copyright 2017 - 2020 Anton Tananaev (anton@traccar.org) * Copyright 2017 Andrey Kunitsyn (andrey@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -142,14 +142,33 @@ public class CommandsManager extends ExtendedObjectManager<Command> { } private Queue<Command> getDeviceQueue(long deviceId) { - if (!deviceQueues.containsKey(deviceId)) { - deviceQueues.put(deviceId, new ConcurrentLinkedQueue<Command>()); + Queue<Command> deviceQueue; + try { + readLock(); + deviceQueue = deviceQueues.get(deviceId); + } finally { + readUnlock(); + } + if (deviceQueue != null) { + return deviceQueue; + } else { + try { + writeLock(); + return deviceQueues.computeIfAbsent(deviceId, key -> new ConcurrentLinkedQueue<>()); + } finally { + writeUnlock(); + } } - return deviceQueues.get(deviceId); } public void sendQueuedCommands(ActiveDevice activeDevice) { - Queue<Command> deviceQueue = deviceQueues.get(activeDevice.getDeviceId()); + Queue<Command> deviceQueue; + try { + readLock(); + deviceQueue = deviceQueues.get(activeDevice.getDeviceId()); + } finally { + readUnlock(); + } if (deviceQueue != null) { Command command = deviceQueue.poll(); while (command != null) { diff --git a/src/main/java/org/traccar/database/DeviceManager.java b/src/main/java/org/traccar/database/DeviceManager.java index fa95adeb2..fe17f7ced 100644 --- a/src/main/java/org/traccar/database/DeviceManager.java +++ b/src/main/java/org/traccar/database/DeviceManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 - 2019 Anton Tananaev (anton@traccar.org) + * Copyright 2016 - 2020 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -58,11 +58,16 @@ public class DeviceManager extends BaseObjectManager<Device> implements Identity public DeviceManager(DataManager dataManager) { super(dataManager, Device.class); this.config = Context.getConfig(); - if (devicesByPhone == null) { - devicesByPhone = new ConcurrentHashMap<>(); - } - if (devicesByUniqueId == null) { - devicesByUniqueId = new ConcurrentHashMap<>(); + try { + writeLock(); + if (devicesByPhone == null) { + devicesByPhone = new ConcurrentHashMap<>(); + } + if (devicesByUniqueId == null) { + devicesByUniqueId = new ConcurrentHashMap<>(); + } + } finally { + writeUnlock(); } dataRefreshDelay = config.getLong("database.refreshDelay", DEFAULT_REFRESH_DELAY) * 1000; lookupGroupsAttribute = config.getBoolean("deviceManager.lookupGroupsAttribute"); @@ -108,11 +113,20 @@ public class DeviceManager extends BaseObjectManager<Device> implements Identity @Override public Device getByUniqueId(String uniqueId) throws SQLException { - boolean forceUpdate = !devicesByUniqueId.containsKey(uniqueId) && !config.getBoolean("database.ignoreUnknown"); - + boolean forceUpdate; + try { + readLock(); + forceUpdate = !devicesByUniqueId.containsKey(uniqueId) && !config.getBoolean("database.ignoreUnknown"); + } finally { + readUnlock(); + } updateDeviceCache(forceUpdate); - - return devicesByUniqueId.get(uniqueId); + try { + readLock(); + return devicesByUniqueId.get(uniqueId); + } finally { + readUnlock(); + } } @Override @@ -134,7 +148,12 @@ public class DeviceManager extends BaseObjectManager<Device> implements Identity } public Device getDeviceByPhone(String phone) { - return devicesByPhone.get(phone); + try { + readLock(); + return devicesByPhone.get(phone); + } finally { + readUnlock(); + } } @Override @@ -176,8 +195,7 @@ public class DeviceManager extends BaseObjectManager<Device> implements Identity } public Set<Long> getAllManagedItems(long userId) { - Set<Long> result = new HashSet<>(); - result.addAll(getAllUserItems(userId)); + Set<Long> result = new HashSet<>(getAllUserItems(userId)); for (long managedUserId : Context.getUsersManager().getUserItems(userId)) { result.addAll(getAllUserItems(managedUserId)); } @@ -186,34 +204,68 @@ public class DeviceManager extends BaseObjectManager<Device> implements Identity @Override public Set<Long> getManagedItems(long userId) { - Set<Long> result = new HashSet<>(); - result.addAll(getUserItems(userId)); + Set<Long> result = new HashSet<>(getUserItems(userId)); for (long managedUserId : Context.getUsersManager().getUserItems(userId)) { result.addAll(getUserItems(managedUserId)); } return result; } - private void putUniqueDeviceId(Device device) { - if (devicesByUniqueId == null) { - devicesByUniqueId = new ConcurrentHashMap<>(getAllItems().size()); + private void addByUniqueId(Device device) { + try { + writeLock(); + if (devicesByUniqueId == null) { + devicesByUniqueId = new ConcurrentHashMap<>(); + } + devicesByUniqueId.put(device.getUniqueId(), device); + } finally { + writeUnlock(); + } + } + + private void removeByUniqueId(String deviceUniqueId) { + try { + writeLock(); + if (devicesByUniqueId != null) { + devicesByUniqueId.remove(deviceUniqueId); + } + } finally { + writeUnlock(); + } + } + + private void addByPhone(Device device) { + try { + writeLock(); + if (devicesByPhone == null) { + devicesByPhone = new ConcurrentHashMap<>(); + } + devicesByPhone.put(device.getPhone(), device); + } finally { + writeUnlock(); } - devicesByUniqueId.put(device.getUniqueId(), device); } - private void putPhone(Device device) { - if (devicesByPhone == null) { - devicesByPhone = new ConcurrentHashMap<>(getAllItems().size()); + private void removeByPhone(String phone) { + if (phone == null || phone.isEmpty()) { + return; + } + try { + writeLock(); + if (devicesByPhone != null) { + devicesByPhone.remove(phone); + } + } finally { + writeUnlock(); } - devicesByPhone.put(device.getPhone(), device); } @Override protected void addNewItem(Device device) { super.addNewItem(device); - putUniqueDeviceId(device); + addByUniqueId(device); if (device.getPhone() != null && !device.getPhone().isEmpty()) { - putPhone(device); + addByPhone(device); } if (Context.getGeofenceManager() != null) { Position lastPosition = getLastPosition(device.getId()); @@ -234,18 +286,16 @@ public class DeviceManager extends BaseObjectManager<Device> implements Identity cachedDevice.setDisabled(device.getDisabled()); cachedDevice.setAttributes(device.getAttributes()); if (!device.getUniqueId().equals(cachedDevice.getUniqueId())) { - devicesByUniqueId.remove(cachedDevice.getUniqueId()); + removeByUniqueId(cachedDevice.getUniqueId()); cachedDevice.setUniqueId(device.getUniqueId()); - putUniqueDeviceId(cachedDevice); + addByUniqueId(cachedDevice); } if (device.getPhone() != null && !device.getPhone().isEmpty() && !device.getPhone().equals(cachedDevice.getPhone())) { String phone = cachedDevice.getPhone(); - if (phone != null && !phone.isEmpty()) { - devicesByPhone.remove(phone); - } + removeByPhone(phone); cachedDevice.setPhone(device.getPhone()); - putPhone(cachedDevice); + addByPhone(cachedDevice); } } @@ -256,10 +306,8 @@ public class DeviceManager extends BaseObjectManager<Device> implements Identity String deviceUniqueId = cachedDevice.getUniqueId(); String phone = cachedDevice.getPhone(); super.removeCachedItem(deviceId); - devicesByUniqueId.remove(deviceUniqueId); - if (phone != null && !phone.isEmpty()) { - devicesByPhone.remove(phone); - } + removeByUniqueId(deviceUniqueId); + removeByPhone(phone); } positions.remove(deviceId); } diff --git a/src/main/java/org/traccar/database/DriversManager.java b/src/main/java/org/traccar/database/DriversManager.java index 930951460..d111cd643 100644 --- a/src/main/java/org/traccar/database/DriversManager.java +++ b/src/main/java/org/traccar/database/DriversManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Anton Tananaev (anton@traccar.org) + * Copyright 2017 - 2020 Anton Tananaev (anton@traccar.org) * Copyright 2017 Andrey Kunitsyn (andrey@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -27,22 +27,44 @@ public class DriversManager extends ExtendedObjectManager<Driver> { public DriversManager(DataManager dataManager) { super(dataManager, Driver.class); - if (driversByUniqueId == null) { - driversByUniqueId = new ConcurrentHashMap<>(); + try { + writeLock(); + if (driversByUniqueId == null) { + driversByUniqueId = new ConcurrentHashMap<>(); + } + } finally { + writeUnlock(); } } - private void putUniqueDriverId(Driver driver) { - if (driversByUniqueId == null) { - driversByUniqueId = new ConcurrentHashMap<>(getAllItems().size()); + private void addByUniqueId(Driver driver) { + try { + writeLock(); + if (driversByUniqueId == null) { + driversByUniqueId = new ConcurrentHashMap<>(); + } + driversByUniqueId.put(driver.getUniqueId(), driver); + } finally { + writeUnlock(); + } + } + + private void removeByUniqueId(String driverUniqueId) { + try { + writeLock(); + if (driversByUniqueId == null) { + driversByUniqueId = new ConcurrentHashMap<>(); + } + driversByUniqueId.remove(driverUniqueId); + } finally { + writeUnlock(); } - driversByUniqueId.put(driver.getUniqueId(), driver); } @Override protected void addNewItem(Driver driver) { super.addNewItem(driver); - putUniqueDriverId(driver); + addByUniqueId(driver); } @Override @@ -50,9 +72,9 @@ public class DriversManager extends ExtendedObjectManager<Driver> { Driver cachedDriver = getById(driver.getId()); cachedDriver.setName(driver.getName()); if (!driver.getUniqueId().equals(cachedDriver.getUniqueId())) { - driversByUniqueId.remove(cachedDriver.getUniqueId()); + removeByUniqueId(cachedDriver.getUniqueId()); cachedDriver.setUniqueId(driver.getUniqueId()); - putUniqueDriverId(cachedDriver); + addByUniqueId(cachedDriver); } cachedDriver.setAttributes(driver.getAttributes()); } @@ -63,11 +85,16 @@ public class DriversManager extends ExtendedObjectManager<Driver> { if (cachedDriver != null) { String driverUniqueId = cachedDriver.getUniqueId(); super.removeCachedItem(driverId); - driversByUniqueId.remove(driverUniqueId); + removeByUniqueId(driverUniqueId); } } public Driver getDriverByUniqueId(String uniqueId) { - return driversByUniqueId.get(uniqueId); + try { + readLock(); + return driversByUniqueId.get(uniqueId); + } finally { + readUnlock(); + } } } diff --git a/src/main/java/org/traccar/database/ExtendedObjectManager.java b/src/main/java/org/traccar/database/ExtendedObjectManager.java index ceb85b537..93e5820fb 100644 --- a/src/main/java/org/traccar/database/ExtendedObjectManager.java +++ b/src/main/java/org/traccar/database/ExtendedObjectManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Anton Tananaev (anton@traccar.org) + * Copyright 2017 - 2020 Anton Tananaev (anton@traccar.org) * Copyright 2017 Andrey Kunitsyn (andrey@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -45,24 +45,45 @@ public abstract class ExtendedObjectManager<T extends BaseModel> extends SimpleO } public final Set<Long> getGroupItems(long groupId) { - if (!groupItems.containsKey(groupId)) { - groupItems.put(groupId, new HashSet<Long>()); + try { + readLock(); + Set<Long> result = groupItems.get(groupId); + if (result != null) { + return new HashSet<>(result); + } else { + return new HashSet<>(); + } + } finally { + readUnlock(); } - return groupItems.get(groupId); } public final Set<Long> getDeviceItems(long deviceId) { - if (!deviceItems.containsKey(deviceId)) { - deviceItems.put(deviceId, new HashSet<Long>()); + try { + readLock(); + Set<Long> result = deviceItems.get(deviceId); + if (result != null) { + return new HashSet<>(result); + } else { + return new HashSet<>(); + } + } finally { + readUnlock(); } - return deviceItems.get(deviceId); } public Set<Long> getAllDeviceItems(long deviceId) { - if (!deviceItemsWithGroups.containsKey(deviceId)) { - deviceItemsWithGroups.put(deviceId, new HashSet<Long>()); + try { + readLock(); + Set<Long> result = deviceItemsWithGroups.get(deviceId); + if (result != null) { + return new HashSet<>(result); + } else { + return new HashSet<>(); + } + } finally { + readUnlock(); } - return deviceItemsWithGroups.get(deviceId); } @Override @@ -74,41 +95,48 @@ public abstract class ExtendedObjectManager<T extends BaseModel> extends SimpleO public void refreshExtendedPermissions() { if (getDataManager() != null) { try { - Collection<Permission> databaseGroupPermissions = getDataManager().getPermissions(Group.class, getBaseClass()); - groupItems.clear(); - for (Permission groupPermission : databaseGroupPermissions) { - getGroupItems(groupPermission.getOwnerId()).add(groupPermission.getPropertyId()); - } - Collection<Permission> databaseDevicePermissions = getDataManager().getPermissions(Device.class, getBaseClass()); + writeLock(); + + groupItems.clear(); deviceItems.clear(); deviceItemsWithGroups.clear(); + for (Permission groupPermission : databaseGroupPermissions) { + groupItems + .computeIfAbsent(groupPermission.getOwnerId(), key -> new HashSet<>()) + .add(groupPermission.getPropertyId()); + } + for (Permission devicePermission : databaseDevicePermissions) { - getDeviceItems(devicePermission.getOwnerId()).add(devicePermission.getPropertyId()); - getAllDeviceItems(devicePermission.getOwnerId()).add(devicePermission.getPropertyId()); + deviceItems + .computeIfAbsent(devicePermission.getOwnerId(), key -> new HashSet<>()) + .add(devicePermission.getPropertyId()); + deviceItemsWithGroups + .computeIfAbsent(devicePermission.getOwnerId(), key -> new HashSet<>()) + .add(devicePermission.getPropertyId()); } for (Device device : Context.getDeviceManager().getAllDevices()) { long groupId = device.getGroupId(); - while (groupId != 0) { - getAllDeviceItems(device.getId()).addAll(getGroupItems(groupId)); + while (groupId > 0) { + deviceItemsWithGroups + .computeIfAbsent(device.getId(), key -> new HashSet<>()) + .addAll(groupItems.getOrDefault(groupId, new HashSet<>())); Group group = Context.getGroupsManager().getById(groupId); - if (group != null) { - groupId = group.getGroupId(); - } else { - groupId = 0; - } + groupId = group != null ? group.getGroupId() : 0; } } } catch (SQLException | ClassNotFoundException error) { LOGGER.warn("Refresh permissions error", error); + } finally { + writeUnlock(); } } } diff --git a/src/main/java/org/traccar/database/GroupsManager.java b/src/main/java/org/traccar/database/GroupsManager.java index d8404c614..81f1968aa 100644 --- a/src/main/java/org/traccar/database/GroupsManager.java +++ b/src/main/java/org/traccar/database/GroupsManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Anton Tananaev (anton@traccar.org) + * Copyright 2017 - 2020 Anton Tananaev (anton@traccar.org) * Copyright 2017 Andrey Kunitsyn (andrey@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -95,8 +95,7 @@ public class GroupsManager extends BaseObjectManager<Group> implements Managable @Override public Set<Long> getManagedItems(long userId) { - Set<Long> result = new HashSet<>(); - result.addAll(getUserItems(userId)); + Set<Long> result = getUserItems(userId); for (long managedUserId : Context.getUsersManager().getUserItems(userId)) { result.addAll(getUserItems(managedUserId)); } diff --git a/src/main/java/org/traccar/database/SimpleObjectManager.java b/src/main/java/org/traccar/database/SimpleObjectManager.java index 15dda4520..eb8284d4e 100644 --- a/src/main/java/org/traccar/database/SimpleObjectManager.java +++ b/src/main/java/org/traccar/database/SimpleObjectManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Anton Tananaev (anton@traccar.org) + * Copyright 2017 - 2020 Anton Tananaev (anton@traccar.org) * Copyright 2017 Andrey Kunitsyn (andrey@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -42,16 +42,22 @@ public abstract class SimpleObjectManager<T extends BaseModel> extends BaseObjec @Override public final Set<Long> getUserItems(long userId) { - if (!userItems.containsKey(userId)) { - userItems.put(userId, new HashSet<Long>()); + try { + readLock(); + Set<Long> result = userItems.get(userId); + if (result != null) { + return new HashSet<>(result); + } else { + return new HashSet<>(); + } + } finally { + readUnlock(); } - return userItems.get(userId); } @Override public Set<Long> getManagedItems(long userId) { - Set<Long> result = new HashSet<>(); - result.addAll(getUserItems(userId)); + Set<Long> result = getUserItems(userId); for (long managedUserId : Context.getUsersManager().getUserItems(userId)) { result.addAll(getUserItems(managedUserId)); } @@ -71,16 +77,16 @@ public abstract class SimpleObjectManager<T extends BaseModel> extends BaseObjec public final void refreshUserItems() { if (getDataManager() != null) { try { - if (userItems != null) { - userItems.clear(); - } else { - userItems = new ConcurrentHashMap<>(); - } + writeLock(); + userItems = new ConcurrentHashMap<>(); for (Permission permission : getDataManager().getPermissions(User.class, getBaseClass())) { - getUserItems(permission.getOwnerId()).add(permission.getPropertyId()); + Set<Long> items = userItems.computeIfAbsent(permission.getOwnerId(), key -> new HashSet<>()); + items.add(permission.getPropertyId()); } } catch (SQLException | ClassNotFoundException error) { LOGGER.warn("Error getting permissions", error); + } finally { + writeUnlock(); } } } diff --git a/src/main/java/org/traccar/database/UsersManager.java b/src/main/java/org/traccar/database/UsersManager.java index 576a9e6c7..b741a85b6 100644 --- a/src/main/java/org/traccar/database/UsersManager.java +++ b/src/main/java/org/traccar/database/UsersManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Anton Tananaev (anton@traccar.org) + * Copyright 2017 - 2020 Anton Tananaev (anton@traccar.org) * Copyright 2017 Andrey Kunitsyn (andrey@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,7 +16,6 @@ */ package org.traccar.database; -import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; @@ -73,8 +72,7 @@ public class UsersManager extends SimpleObjectManager<User> { @Override public Set<Long> getManagedItems(long userId) { - Set<Long> result = new HashSet<>(); - result.addAll(getUserItems(userId)); + Set<Long> result = getUserItems(userId); result.add(userId); return result; } diff --git a/src/main/java/org/traccar/geocoder/FactualGeocoder.java b/src/main/java/org/traccar/geocoder/FactualGeocoder.java index c7a68c293..f540eb8fe 100644 --- a/src/main/java/org/traccar/geocoder/FactualGeocoder.java +++ b/src/main/java/org/traccar/geocoder/FactualGeocoder.java @@ -1,6 +1,6 @@ /* * Copyright 2014 - 2015 Stefaan Van Dooren (stefaan.vandooren@gmail.com) - * Copyright 2017 Anton Tananaev (anton@traccar.org) + * Copyright 2017 - 2020 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,8 +20,16 @@ import javax.json.JsonObject; public class FactualGeocoder extends JsonGeocoder { + private static String formatUrl(String url, String key) { + if (url == null) { + url = "https://api.factual.com/geotag"; + } + url += "?latitude=%f&longitude=%f&KEY=" + key; + return url; + } + public FactualGeocoder(String url, String key, int cacheSize, AddressFormat addressFormat) { - super(url + "?latitude=%f&longitude=%f&KEY=" + key, cacheSize, addressFormat); + super(formatUrl(url, key), cacheSize, addressFormat); } @Override diff --git a/src/main/java/org/traccar/geocoder/GisgraphyGeocoder.java b/src/main/java/org/traccar/geocoder/GisgraphyGeocoder.java index 3a173f985..b4881a006 100644 --- a/src/main/java/org/traccar/geocoder/GisgraphyGeocoder.java +++ b/src/main/java/org/traccar/geocoder/GisgraphyGeocoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 - 2017 Anton Tananaev (anton@traccar.org) + * Copyright 2015 - 2020 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,12 +19,16 @@ import javax.json.JsonObject; public class GisgraphyGeocoder extends JsonGeocoder { - public GisgraphyGeocoder(AddressFormat addressFormat) { - this("http://services.gisgraphy.com/reversegeocoding/search", 0, addressFormat); + private static String formatUrl(String url) { + if (url == null) { + url = "http://services.gisgraphy.com/reversegeocoding/search"; + } + url += "?format=json&lat=%f&lng=%f&from=1&to=1"; + return url; } public GisgraphyGeocoder(String url, int cacheSize, AddressFormat addressFormat) { - super(url + "?format=json&lat=%f&lng=%f&from=1&to=1", cacheSize, addressFormat); + super(formatUrl(url), cacheSize, addressFormat); } @Override diff --git a/src/main/java/org/traccar/geocoder/HereGeocoder.java b/src/main/java/org/traccar/geocoder/HereGeocoder.java index 756260b52..aaf11d74d 100644 --- a/src/main/java/org/traccar/geocoder/HereGeocoder.java +++ b/src/main/java/org/traccar/geocoder/HereGeocoder.java @@ -19,20 +19,24 @@ import javax.json.JsonObject; public class HereGeocoder extends JsonGeocoder { - private static String formatUrl(String id, String key, String language) { - String url = "https://reverse.geocoder.api.here.com/6.2/reversegeocode.json"; + private static String formatUrl(String url, String id, String key, String language) { + if (url == null) { + url = "https://reverse.geocoder.ls.hereapi.com/6.2/reversegeocode.json"; + } url += "?mode=retrieveAddresses&maxresults=1"; url += "&prox=%f,%f,0"; url += "&app_id=" + id; url += "&app_code=" + key; + url += "&apiKey=" + key; if (language != null) { url += "&language=" + language; } return url; } - public HereGeocoder(String id, String key, String language, int cacheSize, AddressFormat addressFormat) { - super(formatUrl(id, key, language), cacheSize, addressFormat); + public HereGeocoder( + String url, String id, String key, String language, int cacheSize, AddressFormat addressFormat) { + super(formatUrl(url, id, key, language), cacheSize, addressFormat); } @Override diff --git a/src/main/java/org/traccar/geocoder/MapQuestGeocoder.java b/src/main/java/org/traccar/geocoder/MapQuestGeocoder.java index 4029e3f07..8dc3f76f0 100644 --- a/src/main/java/org/traccar/geocoder/MapQuestGeocoder.java +++ b/src/main/java/org/traccar/geocoder/MapQuestGeocoder.java @@ -1,6 +1,6 @@ /* * Copyright 2014 - 2015 Stefaan Van Dooren (stefaan.vandooren@gmail.com) - * Copyright 2017 Anton Tananaev (anton@traccar.org) + * Copyright 2017 - 2020 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,8 +21,16 @@ import javax.json.JsonObject; public class MapQuestGeocoder extends JsonGeocoder { + private static String formatUrl(String url, String key) { + if (url == null) { + url = "http://www.mapquestapi.com/geocoding/v1/reverse"; + } + url += "?key=" + key + "&location=%f,%f"; + return url; + } + public MapQuestGeocoder(String url, String key, int cacheSize, AddressFormat addressFormat) { - super(url + "?key=" + key + "&location=%f,%f", cacheSize, addressFormat); + super(formatUrl(url, key), cacheSize, addressFormat); } @Override diff --git a/src/main/java/org/traccar/geocoder/OpenCageGeocoder.java b/src/main/java/org/traccar/geocoder/OpenCageGeocoder.java index 822b6e91e..56161e52c 100644 --- a/src/main/java/org/traccar/geocoder/OpenCageGeocoder.java +++ b/src/main/java/org/traccar/geocoder/OpenCageGeocoder.java @@ -1,6 +1,6 @@ /* * Copyright 2014 - 2015 Stefaan Van Dooren (stefaan.vandooren@gmail.com) - * Copyright 2017 Anton Tananaev (anton@traccar.org) + * Copyright 2017 - 2020 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,8 +21,16 @@ import javax.json.JsonObject; public class OpenCageGeocoder extends JsonGeocoder { + private static String formatUrl(String url, String key) { + if (url == null) { + url = "https://api.opencagedata.com/geocode/v1"; + } + url += "/json?q=%f,%f&no_annotations=1&key=" + key; + return url; + } + public OpenCageGeocoder(String url, String key, int cacheSize, AddressFormat addressFormat) { - super(url + "/json?q=%f,%f&no_annotations=1&key=" + key, cacheSize, addressFormat); + super(formatUrl(url, key), cacheSize, addressFormat); } @Override diff --git a/src/main/java/org/traccar/geolocation/OpenCellIdGeolocationProvider.java b/src/main/java/org/traccar/geolocation/OpenCellIdGeolocationProvider.java index 768aaf6a2..cb3094e16 100644 --- a/src/main/java/org/traccar/geolocation/OpenCellIdGeolocationProvider.java +++ b/src/main/java/org/traccar/geolocation/OpenCellIdGeolocationProvider.java @@ -26,11 +26,10 @@ public class OpenCellIdGeolocationProvider implements GeolocationProvider { private String url; - public OpenCellIdGeolocationProvider(String key) { - this("http://opencellid.org/cell/get", key); - } - public OpenCellIdGeolocationProvider(String url, String key) { + if (url == null) { + url = "http://opencellid.org/cell/get"; + } this.url = url + "?format=json&mcc=%d&mnc=%d&lac=%d&cellid=%d&key=" + key; } diff --git a/src/main/java/org/traccar/helper/Checksum.java b/src/main/java/org/traccar/helper/Checksum.java index adfa697c5..d41dc2992 100644 --- a/src/main/java/org/traccar/helper/Checksum.java +++ b/src/main/java/org/traccar/helper/Checksum.java @@ -168,6 +168,14 @@ public final class Checksum { return checksum; } + public static int modulo256(ByteBuffer buf) { + int checksum = 0; + while (buf.hasRemaining()) { + checksum = (checksum + buf.get()) & 0xFF; + } + return checksum; + } + public static String sum(String msg) { byte checksum = 0; for (byte b : msg.getBytes(StandardCharsets.US_ASCII)) { diff --git a/src/main/java/org/traccar/helper/LogAction.java b/src/main/java/org/traccar/helper/LogAction.java index db13337b8..16d55ec60 100644 --- a/src/main/java/org/traccar/helper/LogAction.java +++ b/src/main/java/org/traccar/helper/LogAction.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Anton Tananaev (anton@traccar.org) + * Copyright 2017 - 2020 Anton Tananaev (anton@traccar.org) * Copyright 2017 Andrey Kunitsyn (andrey@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -44,6 +44,7 @@ public final class LogAction { private static final String PATTERN_OBJECT = "user: %d, action: %s, object: %s, id: %d"; private static final String PATTERN_LINK = "user: %d, action: %s, owner: %s, id: %d, property: %s, id: %d"; private static final String PATTERN_LOGIN = "user: %d, action: %s"; + private static final String PATTERN_LOGIN_FAILED = "login failed from: %s"; private static final String PATTERN_DEVICE_ACCUMULATORS = "user: %d, action: %s, deviceId: %d"; public static void create(long userId, BaseModel object) { @@ -74,6 +75,13 @@ public final class LogAction { logLoginAction(ACTION_LOGOUT, userId); } + public static void failedLogin(String remoteAddress) { + if (remoteAddress == null || remoteAddress.isEmpty()) { + remoteAddress = "unknown"; + } + LOGGER.info(String.format(PATTERN_LOGIN_FAILED, remoteAddress)); + } + public static void resetDeviceAccumulators(long userId, long deviceId) { LOGGER.info(String.format( PATTERN_DEVICE_ACCUMULATORS, userId, ACTION_DEVICE_ACCUMULATORS, deviceId)); @@ -85,7 +93,7 @@ public final class LogAction { } private static void logLinkAction(String action, long userId, - Class<?> owner, long ownerId, Class<?> property, long propertyId) { + Class<?> owner, long ownerId, Class<?> property, long propertyId) { LOGGER.info(String.format( PATTERN_LINK, userId, action, Introspector.decapitalize(owner.getSimpleName()), ownerId, diff --git a/src/main/java/org/traccar/helper/ServletHelper.java b/src/main/java/org/traccar/helper/ServletHelper.java new file mode 100644 index 000000000..b6c587ec3 --- /dev/null +++ b/src/main/java/org/traccar/helper/ServletHelper.java @@ -0,0 +1,45 @@ +/* + * Copyright 2020 Anton Tananaev (anton@traccar.org) + * + * 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.helper; + +import javax.servlet.http.HttpServletRequest; + +public final class ServletHelper { + + private ServletHelper() { + } + + public static String retrieveRemoteAddress(HttpServletRequest request) { + + if (request != null) { + String remoteAddress = request.getHeader("X-FORWARDED-FOR"); + + if (remoteAddress != null && !remoteAddress.isEmpty()) { + int separatorIndex = remoteAddress.indexOf(","); + if (separatorIndex > 0) { + return remoteAddress.substring(0, separatorIndex); // remove the additional data + } else { + return remoteAddress; + } + } else { + return request.getRemoteAddr(); + } + } else { + return null; + } + } + +} diff --git a/src/main/java/org/traccar/model/Command.java b/src/main/java/org/traccar/model/Command.java index 336fc61f4..abe538a10 100644 --- a/src/main/java/org/traccar/model/Command.java +++ b/src/main/java/org/traccar/model/Command.java @@ -68,6 +68,7 @@ public class Command extends Message implements Cloneable { public static final String KEY_UNIQUE_ID = "uniqueId"; public static final String KEY_FREQUENCY = "frequency"; + public static final String KEY_LANGUAGE = "language"; public static final String KEY_TIMEZONE = "timezone"; public static final String KEY_DEVICE_PASSWORD = "devicePassword"; public static final String KEY_RADIUS = "radius"; diff --git a/src/main/java/org/traccar/protocol/AdmProtocolEncoder.java b/src/main/java/org/traccar/protocol/AdmProtocolEncoder.java index 1c3dfc156..c02fa4112 100644 --- a/src/main/java/org/traccar/protocol/AdmProtocolEncoder.java +++ b/src/main/java/org/traccar/protocol/AdmProtocolEncoder.java @@ -34,7 +34,7 @@ public class AdmProtocolEncoder extends StringProtocolEncoder { return formatCommand(command, "STATUS\r\n"); case Command.TYPE_CUSTOM: - return formatCommand(command, "{%s}\r\n", Command.KEY_DATA); + return formatCommand(command, "%s\r\n", Command.KEY_DATA); default: return null; diff --git a/src/main/java/org/traccar/protocol/ArnaviBinaryProtocolDecoder.java b/src/main/java/org/traccar/protocol/ArnaviBinaryProtocolDecoder.java new file mode 100644 index 000000000..e957a6911 --- /dev/null +++ b/src/main/java/org/traccar/protocol/ArnaviBinaryProtocolDecoder.java @@ -0,0 +1,179 @@ +/* + * Copyright 2020 Anton Tananaev (anton@traccar.org) + * Copyright 2017 Ivan Muratov (binakot@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.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.NetworkMessage; +import org.traccar.Protocol; +import org.traccar.helper.Checksum; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.nio.Buffer; +import java.nio.ByteBuffer; +import java.util.Date; +import java.util.LinkedList; +import java.util.List; + +public class ArnaviBinaryProtocolDecoder extends BaseProtocolDecoder { + + private static final byte HEADER_START_SIGN = (byte) 0xff; + private static final byte HEADER_VERSION_1 = 0x22; + private static final byte HEADER_VERSION_2 = 0x23; + + private static final byte RECORD_PING = 0x00; + private static final byte RECORD_DATA = 0x01; + private static final byte RECORD_TEXT = 0x03; + private static final byte RECORD_FILE = 0x04; + private static final byte RECORD_BINARY = 0x06; + + private static final byte TAG_LATITUDE = 3; + private static final byte TAG_LONGITUDE = 4; + private static final byte TAG_COORD_PARAMS = 5; + + public ArnaviBinaryProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private void sendResponse(Channel channel, byte version, int index) { + if (channel != null) { + ByteBuf response = Unpooled.buffer(); + response.writeByte(0x7b); + if (version == HEADER_VERSION_1) { + response.writeByte(0x00); + response.writeByte((byte) index); + } else if (version == HEADER_VERSION_2) { + response.writeByte(0x04); + response.writeByte(0x00); + ByteBuffer time = ByteBuffer.allocate(4).putInt((int) (System.currentTimeMillis() / 1000)); + ((Buffer) time).position(0); + response.writeByte(Checksum.modulo256(time.slice())); + response.writeBytes(time); + } + response.writeByte(0x7d); + channel.writeAndFlush(new NetworkMessage(response, channel.remoteAddress())); + } + } + + private Position decodePosition(DeviceSession deviceSession, ByteBuf buf, int length, Date time) { + + final Position position = new Position(); + position.setProtocol(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + position.setTime(time); + + int readBytes = 0; + while (readBytes < length) { + short tag = buf.readUnsignedByte(); + switch (tag) { + case TAG_LATITUDE: + position.setLatitude(buf.readFloatLE()); + position.setValid(true); + break; + + case TAG_LONGITUDE: + position.setLongitude(buf.readFloatLE()); + position.setValid(true); + break; + + case TAG_COORD_PARAMS: + position.setCourse(buf.readUnsignedByte() * 2); + position.setAltitude(buf.readUnsignedByte() * 10); + byte satellites = buf.readByte(); + position.set(Position.KEY_SATELLITES, satellites & 0x0F + (satellites >> 4) & 0x0F); + position.setSpeed(buf.readUnsignedByte()); + break; + + default: + buf.skipBytes(4); + break; + } + + readBytes += 1 + 4; + } + + return position; + } + + @Override + protected Object decode(Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + + byte startSign = buf.readByte(); + + if (startSign == HEADER_START_SIGN) { + + byte version = buf.readByte(); + + String imei = String.valueOf(buf.readLongLE()); + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, imei); + + if (deviceSession != null) { + sendResponse(channel, version, 0); + } + + return null; + } + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress); + if (deviceSession == null) { + return null; + } + + List<Position> positions = new LinkedList<>(); + + int index = buf.readUnsignedByte(); + + byte recordType = buf.readByte(); + while (buf.readableBytes() > 0) { + switch (recordType) { + case RECORD_PING: + case RECORD_DATA: + case RECORD_TEXT: + case RECORD_FILE: + case RECORD_BINARY: + int length = buf.readUnsignedShortLE(); + Date time = new Date(buf.readUnsignedIntLE() * 1000); + + if (recordType == RECORD_DATA) { + positions.add(decodePosition(deviceSession, buf, length, time)); + } else { + buf.readBytes(length); + } + + buf.readUnsignedByte(); // checksum + break; + + default: + return null; + } + + recordType = buf.readByte(); + } + + sendResponse(channel, HEADER_VERSION_1, index); + + return positions; + } + +} diff --git a/src/main/java/org/traccar/protocol/ArnaviFrameDecoder.java b/src/main/java/org/traccar/protocol/ArnaviFrameDecoder.java new file mode 100644 index 000000000..473e8b2c7 --- /dev/null +++ b/src/main/java/org/traccar/protocol/ArnaviFrameDecoder.java @@ -0,0 +1,82 @@ +/* + * Copyright 2020 Anton Tananaev (anton@traccar.org) + * Copyright 2017 Ivan Muratov (binakot@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.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import org.traccar.BaseFrameDecoder; +import org.traccar.helper.BufferUtil; + +public class ArnaviFrameDecoder extends BaseFrameDecoder { + + private static final int HEADER_LENGTH = 10; + private static final int PACKET_WRAPPER_LENGTH = 8; + private static final int RESULT_TYPE = 0xfd; + private static final byte PACKAGE_END_SIGN = 0x5d; + + private boolean firstPacket = true; + + @Override + protected Object decode(ChannelHandlerContext ctx, Channel channel, ByteBuf buf) throws Exception { + + if (buf.readableBytes() < 4) { + return null; + } + + if (buf.getByte(buf.readerIndex()) == '$') { + + int index = BufferUtil.indexOf("\r\n", buf); + if (index > 0) { + ByteBuf frame = buf.readRetainedSlice(index - buf.readerIndex()); + buf.skipBytes(2); + return frame; + } + + } else { + + int length; + if (firstPacket) { + firstPacket = false; + length = HEADER_LENGTH; + } else { + int type = buf.getUnsignedByte(1); + if (type == RESULT_TYPE) { + length = 4; + } else { + int index = 2; + while (index + PACKET_WRAPPER_LENGTH < buf.readableBytes() + && buf.getByte(index) != PACKAGE_END_SIGN) { + index += PACKET_WRAPPER_LENGTH + buf.getUnsignedShortLE(index + 1); + } + if (buf.getByte(index) != PACKAGE_END_SIGN) { + return null; + } + length = index + 1; + } + } + + if (buf.readableBytes() >= length) { + return buf.readRetainedSlice(length); + } + + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/ArnaviProtocol.java b/src/main/java/org/traccar/protocol/ArnaviProtocol.java index afe491865..aecb42c8c 100644 --- a/src/main/java/org/traccar/protocol/ArnaviProtocol.java +++ b/src/main/java/org/traccar/protocol/ArnaviProtocol.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * Copyright 2015 - 2020 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,9 +15,6 @@ */ package org.traccar.protocol; -import io.netty.handler.codec.LineBasedFrameDecoder; -import io.netty.handler.codec.string.StringDecoder; -import io.netty.handler.codec.string.StringEncoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; @@ -28,9 +25,7 @@ public class ArnaviProtocol extends BaseProtocol { addServer(new TrackerServer(false, getName()) { @Override protected void addProtocolHandlers(PipelineBuilder pipeline) { - pipeline.addLast(new LineBasedFrameDecoder(1024)); - pipeline.addLast(new StringDecoder()); - pipeline.addLast(new StringEncoder()); + pipeline.addLast(new ArnaviFrameDecoder()); pipeline.addLast(new ArnaviProtocolDecoder(ArnaviProtocol.this)); } }); diff --git a/src/main/java/org/traccar/protocol/ArnaviProtocolDecoder.java b/src/main/java/org/traccar/protocol/ArnaviProtocolDecoder.java index 7996cf429..68a70c944 100644 --- a/src/main/java/org/traccar/protocol/ArnaviProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/ArnaviProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 - 2019 Anton Tananaev (anton@traccar.org) + * Copyright 2020 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,91 +15,35 @@ */ package org.traccar.protocol; +import io.netty.buffer.ByteBuf; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; import org.traccar.Protocol; -import org.traccar.helper.DateBuilder; -import org.traccar.helper.Parser; -import org.traccar.helper.PatternBuilder; -import org.traccar.model.Position; import java.net.SocketAddress; -import java.util.regex.Pattern; public class ArnaviProtocolDecoder extends BaseProtocolDecoder { + private final ArnaviTextProtocolDecoder textProtocolDecoder; + private final ArnaviBinaryProtocolDecoder binaryProtocolDecoder; + public ArnaviProtocolDecoder(Protocol protocol) { super(protocol); + textProtocolDecoder = new ArnaviTextProtocolDecoder(protocol); + binaryProtocolDecoder = new ArnaviBinaryProtocolDecoder(protocol); } - private static final Pattern PATTERN = new PatternBuilder() - .text("$AV,") - .number("Vd,") // type - .number("(d+),") // device id - .number("(d+),") // index - .number("(d+),") // power - .number("(d+),") // battery - .number("-?d+,") - .expression("[01],") // movement - .expression("([01]),") // ignition - .number("(d+),") // input - .number("d+,d+,") // input 1 - .number("d+,d+,").optional() // input 2 - .expression("[01],") // fix type - .number("(d+),") // satellites - .groupBegin() - .number("(d+.d+)?,") // altitude - .number("(?:d+.d+)?,") // geoid height - .groupEnd("?") - .number("(dd)(dd)(dd),") // time (hhmmss) - .number("(dd)(dd.d+)([NS]),") // latitude - .number("(ddd)(dd.d+)([EW]),") // longitude - .number("(d+.d+),") // speed - .number("(d+.d+),") // course - .number("(dd)(dd)(dd)") // date (ddmmyy) - .any() - .compile(); - @Override protected Object decode( Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { - Parser parser = new Parser(PATTERN, (String) msg); - if (!parser.matches()) { - return null; - } + ByteBuf buf = (ByteBuf) msg; - Position position = new Position(getProtocolName()); - - DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); - if (deviceSession == null) { - return null; + if (buf.getByte(buf.readerIndex()) == '$') { + return textProtocolDecoder.decode(channel, remoteAddress, msg); + } else { + return binaryProtocolDecoder.decode(channel, remoteAddress, msg); } - position.setDeviceId(deviceSession.getDeviceId()); - - position.set(Position.KEY_INDEX, parser.nextInt()); - position.set(Position.KEY_POWER, parser.nextInt() * 0.01); - position.set(Position.KEY_BATTERY, parser.nextInt() * 0.01); - position.set(Position.KEY_IGNITION, parser.nextInt() == 1); - position.set(Position.KEY_INPUT, parser.nextInt()); - position.set(Position.KEY_SATELLITES, parser.nextInt()); - - position.setAltitude(parser.nextDouble(0)); - - DateBuilder dateBuilder = new DateBuilder() - .setTime(parser.nextInt(), parser.nextInt(), parser.nextInt()); - - position.setValid(true); - position.setLatitude(parser.nextCoordinate()); - position.setLongitude(parser.nextCoordinate()); - position.setSpeed(parser.nextDouble()); - position.setCourse(parser.nextDouble()); - - dateBuilder.setDateReverse(parser.nextInt(), parser.nextInt(), parser.nextInt()); - position.setTime(dateBuilder.getDate()); - - return position; } } diff --git a/src/main/java/org/traccar/protocol/ArnaviTextProtocolDecoder.java b/src/main/java/org/traccar/protocol/ArnaviTextProtocolDecoder.java new file mode 100644 index 000000000..b99869e6e --- /dev/null +++ b/src/main/java/org/traccar/protocol/ArnaviTextProtocolDecoder.java @@ -0,0 +1,108 @@ +/* + * Copyright 2015 - 2020 Anton Tananaev (anton@traccar.org) + * + * 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.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.Protocol; +import org.traccar.helper.DateBuilder; +import org.traccar.helper.Parser; +import org.traccar.helper.PatternBuilder; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; +import java.util.regex.Pattern; + +public class ArnaviTextProtocolDecoder extends BaseProtocolDecoder { + + public ArnaviTextProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN = new PatternBuilder() + .text("$AV,") + .number("Vd,") // type + .number("(d+),") // device id + .number("(d+),") // index + .number("(d+),") // power + .number("(d+),") // battery + .number("-?d+,") + .expression("[01],") // movement + .expression("([01]),") // ignition + .number("(d+),") // input + .number("d+,d+,") // input 1 + .number("d+,d+,").optional() // input 2 + .expression("[01],") // fix type + .number("(d+),") // satellites + .groupBegin() + .number("(d+.d+)?,") // altitude + .number("(?:d+.d+)?,") // geoid height + .groupEnd("?") + .number("(dd)(dd)(dd),") // time (hhmmss) + .number("(dd)(dd.d+)([NS]),") // latitude + .number("(ddd)(dd.d+)([EW]),") // longitude + .number("(d+.d+),") // speed + .number("(d+.d+),") // course + .number("(dd)(dd)(dd)") // date (ddmmyy) + .any() + .compile(); + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + Parser parser = new Parser(PATTERN, buf.toString(StandardCharsets.US_ASCII)); + if (!parser.matches()) { + return null; + } + + Position position = new Position(getProtocolName()); + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); + if (deviceSession == null) { + return null; + } + position.setDeviceId(deviceSession.getDeviceId()); + + position.set(Position.KEY_INDEX, parser.nextInt()); + position.set(Position.KEY_POWER, parser.nextInt() * 0.01); + position.set(Position.KEY_BATTERY, parser.nextInt() * 0.01); + position.set(Position.KEY_IGNITION, parser.nextInt() == 1); + position.set(Position.KEY_INPUT, parser.nextInt()); + position.set(Position.KEY_SATELLITES, parser.nextInt()); + + position.setAltitude(parser.nextDouble(0)); + + DateBuilder dateBuilder = new DateBuilder() + .setTime(parser.nextInt(), parser.nextInt(), parser.nextInt()); + + position.setValid(true); + position.setLatitude(parser.nextCoordinate()); + position.setLongitude(parser.nextCoordinate()); + position.setSpeed(parser.nextDouble()); + position.setCourse(parser.nextDouble()); + + dateBuilder.setDateReverse(parser.nextInt(), parser.nextInt(), parser.nextInt()); + position.setTime(dateBuilder.getDate()); + + return position; + } + +} diff --git a/src/main/java/org/traccar/protocol/BceProtocolDecoder.java b/src/main/java/org/traccar/protocol/BceProtocolDecoder.java index f07338937..54136382c 100644 --- a/src/main/java/org/traccar/protocol/BceProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/BceProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 - 2019 Anton Tananaev (anton@traccar.org) + * Copyright 2015 - 2020 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,6 +29,7 @@ import org.traccar.model.Network; import org.traccar.model.Position; import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; import java.util.Date; import java.util.LinkedList; import java.util.List; @@ -82,10 +83,10 @@ public class BceProtocolDecoder extends BaseProtocolDecoder { buf.skipBytes(4); } if (BitUtil.check(mask, 12)) { - buf.skipBytes(2); + position.set("fuel1", buf.readUnsignedShort()); } if (BitUtil.check(mask, 13)) { - buf.skipBytes(2); + position.set("fuel2", buf.readUnsignedShort()); } if (BitUtil.check(mask, 14)) { @@ -97,7 +98,7 @@ public class BceProtocolDecoder extends BaseProtocolDecoder { } } - private void decodeMask2(ByteBuf buf, int mask) { + private void decodeMask2(ByteBuf buf, int mask, Position position) { if (BitUtil.check(mask, 0)) { buf.readUnsignedShortLE(); // wheel speed @@ -106,31 +107,31 @@ public class BceProtocolDecoder extends BaseProtocolDecoder { buf.readUnsignedByte(); // acceleration pedal } if (BitUtil.check(mask, 2)) { - buf.readUnsignedIntLE(); // total fuel used + position.set(Position.KEY_FUEL_USED, buf.readUnsignedIntLE()); } if (BitUtil.check(mask, 3)) { - buf.readUnsignedByte(); // fuel level + position.set(Position.KEY_FUEL_LEVEL, buf.readUnsignedByte()); } if (BitUtil.check(mask, 4)) { - buf.readUnsignedShortLE(); // engine speed + position.set(Position.KEY_RPM, buf.readUnsignedShortLE() * 0.0125); } if (BitUtil.check(mask, 5)) { - buf.readUnsignedIntLE(); // total hours + position.set(Position.KEY_HOURS, buf.readUnsignedIntLE()); } if (BitUtil.check(mask, 6)) { - buf.readUnsignedIntLE(); // total distance + position.set(Position.KEY_ODOMETER, buf.readUnsignedIntLE()); } if (BitUtil.check(mask, 7)) { - buf.readUnsignedByte(); // engine coolant + position.set(Position.KEY_COOLANT_TEMP, buf.readByte() - 40); } if (BitUtil.check(mask, 8)) { - buf.readUnsignedByte(); // fuel level 2 + position.set("fuel2", buf.readUnsignedByte()); } if (BitUtil.check(mask, 9)) { - buf.readUnsignedByte(); // engine load + position.set(Position.KEY_ENGINE_LOAD, buf.readUnsignedByte()); } if (BitUtil.check(mask, 10)) { - buf.readUnsignedShortLE(); // service distance + position.set(Position.KEY_ODOMETER_SERVICE, buf.readUnsignedShortLE()); } if (BitUtil.check(mask, 11)) { buf.skipBytes(8); // sensors @@ -142,7 +143,7 @@ public class BceProtocolDecoder extends BaseProtocolDecoder { buf.skipBytes(8); // trailer id } if (BitUtil.check(mask, 14)) { - buf.readUnsignedShortLE(); // fuel rate + position.set(Position.KEY_FUEL_CONSUMPTION, buf.readUnsignedShortLE()); } } @@ -152,10 +153,10 @@ public class BceProtocolDecoder extends BaseProtocolDecoder { buf.readUnsignedShortLE(); // fuel economy } if (BitUtil.check(mask, 1)) { - buf.readUnsignedIntLE(); // fuel consumption + position.set(Position.KEY_FUEL_CONSUMPTION, buf.readUnsignedIntLE()); } if (BitUtil.check(mask, 2)) { - buf.readUnsignedMediumLE(); // axle weight + position.set(Position.KEY_AXLE_WEIGHT, buf.readUnsignedMediumLE()); } if (BitUtil.check(mask, 3)) { buf.readUnsignedByte(); // mil status @@ -169,6 +170,70 @@ public class BceProtocolDecoder extends BaseProtocolDecoder { if (BitUtil.check(mask, 6)) { position.set(Position.KEY_DRIVER_UNIQUE_ID, String.valueOf(buf.readLongLE())); } + if (BitUtil.check(mask, 7)) { + buf.readUnsignedShortLE(); // dallas temperature + } + if (BitUtil.check(mask, 8)) { + buf.readUnsignedShortLE(); // dallas humidity + } + if (BitUtil.check(mask, 9)) { + buf.skipBytes(6); // lls group 1 + } + if (BitUtil.check(mask, 10)) { + buf.skipBytes(6); // lls group 2 + } + if (BitUtil.check(mask, 11)) { + buf.skipBytes(21); // j1979 group 1 + } + if (BitUtil.check(mask, 12)) { + buf.skipBytes(20); // j1979 dtc + } + if (BitUtil.check(mask, 13)) { + buf.skipBytes(9); // j1708 group 1 + } + if (BitUtil.check(mask, 14)) { + buf.skipBytes(21); // driving quality + } + } + + private void decodeMask4(ByteBuf buf, int mask, Position position) { + + if (BitUtil.check(mask, 0)) { + buf.readUnsignedIntLE(); + } + if (BitUtil.check(mask, 1)) { + buf.skipBytes(30); // lls group 3 + } + if (BitUtil.check(mask, 2)) { + buf.readUnsignedIntLE(); // instant fuel consumption + } + if (BitUtil.check(mask, 3)) { + buf.skipBytes(10); // axle weight group + } + if (BitUtil.check(mask, 4)) { + buf.readUnsignedByte(); + } + if (BitUtil.check(mask, 5)) { + buf.readUnsignedShortLE(); + } + if (BitUtil.check(mask, 6)) { + position.set("maxAcceleration", buf.readUnsignedByte()); + position.set("maxBraking", buf.readUnsignedByte()); + position.set("maxCornering", buf.readUnsignedByte()); + } + if (BitUtil.check(mask, 7)) { + buf.skipBytes(16); + } + if (BitUtil.check(mask, 8)) { + buf.skipBytes(40); // temperature sensors + } + if (BitUtil.check(mask, 9)) { + position.set("driver1", buf.readCharSequence(16, StandardCharsets.US_ASCII).toString().trim()); + position.set("driver2", buf.readCharSequence(16, StandardCharsets.US_ASCII).toString().trim()); + } + if (BitUtil.check(mask, 10)) { + position.set(Position.KEY_ODOMETER, buf.readUnsignedIntLE()); + } } @Override @@ -223,13 +288,18 @@ public class BceProtocolDecoder extends BaseProtocolDecoder { if (masks.size() >= 2) { mask = masks.get(1); - decodeMask2(buf, mask); + decodeMask2(buf, mask, position); } if (masks.size() >= 3) { mask = masks.get(2); decodeMask3(buf, mask, position); } + + if (masks.size() >= 4) { + mask = masks.get(3); + decodeMask4(buf, mask, position); + } } buf.readerIndex(structEnd); diff --git a/src/main/java/org/traccar/protocol/BlueProtocol.java b/src/main/java/org/traccar/protocol/BlueProtocol.java new file mode 100644 index 000000000..d5dc5c421 --- /dev/null +++ b/src/main/java/org/traccar/protocol/BlueProtocol.java @@ -0,0 +1,35 @@ +/* + * Copyright 2019 Anton Tananaev (anton@traccar.org) + * + * 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.protocol; + +import io.netty.handler.codec.LengthFieldBasedFrameDecoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class BlueProtocol extends BaseProtocol { + + public BlueProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new LengthFieldBasedFrameDecoder(1024, 1, 2, -2, 0)); + pipeline.addLast(new BlueProtocolDecoder(BlueProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/BlueProtocolDecoder.java b/src/main/java/org/traccar/protocol/BlueProtocolDecoder.java new file mode 100644 index 000000000..f35ac6fbe --- /dev/null +++ b/src/main/java/org/traccar/protocol/BlueProtocolDecoder.java @@ -0,0 +1,170 @@ +/* + * Copyright 2019 - 2020 Anton Tananaev (anton@traccar.org) + * + * 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.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.NetworkMessage; +import org.traccar.Protocol; +import org.traccar.helper.BitUtil; +import org.traccar.helper.Checksum; +import org.traccar.helper.DateBuilder; +import org.traccar.model.Position; + +import java.net.SocketAddress; + +public class BlueProtocolDecoder extends BaseProtocolDecoder { + + public BlueProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private double readCoordinate(ByteBuf buf, boolean negative) { + + int value = buf.readUnsignedShort(); + int degrees = value / 100; + double minutes = value % 100 + buf.readUnsignedShort() * 0.0001; + double coordinate = degrees + minutes / 60; + return negative ? -coordinate : coordinate; + } + + private void sendResponse(Channel channel, int deviceIndex) { + if (channel != null) { + + ByteBuf response = Unpooled.buffer(); + response.writeByte(0xaa); + response.writeShort(2 + 1 + 1 + 6 + 1); + response.writeByte(0x86); // version + response.writeByte(0); + + response.writeByte(6); // data length + response.writeByte(0xa4); // type + response.writeByte(0); // server index + response.writeByte(deviceIndex); + response.writeByte(0); + response.writeByte(0); + + response.writeByte(Checksum.xor(response.nioBuffer(1, response.writerIndex() - 1))); + + channel.writeAndFlush(new NetworkMessage(response, channel.remoteAddress())); + } + } + + private String decodeAlarm(int value) { + switch (value) { + case 1: + return Position.ALARM_SOS; + case 8: + return Position.ALARM_OVERSPEED; + case 19: + return Position.ALARM_LOW_POWER; + default: + return null; + } + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + + buf.readUnsignedByte(); // header + buf.readUnsignedShort(); // length + buf.readUnsignedByte(); // version + buf.readUnsignedByte(); + + String id = String.valueOf(buf.readUnsignedInt()); + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, id); + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + while (buf.readableBytes() > 1) { + + int frameEnd = buf.readerIndex() + buf.readUnsignedByte(); + + int type = buf.readUnsignedByte(); + int index = buf.readUnsignedByte(); + buf.readUnsignedByte(); + buf.readUnsignedByte(); // flags + + if (type == 0x01) { + + buf.readUnsignedByte(); // reserved + int flags = buf.readUnsignedByte(); + + position.setValid(BitUtil.check(flags, 7)); + position.setLatitude(readCoordinate(buf, BitUtil.check(flags, 6))); + position.setLongitude(readCoordinate(buf, BitUtil.check(flags, 5))); + position.setSpeed(buf.readUnsignedShort() + buf.readUnsignedShort() * 0.001); + position.setCourse(buf.readUnsignedShort() + buf.readUnsignedByte() * 0.01); + + DateBuilder dateBuilder = new DateBuilder() + .setDate(buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte()) + .setTime(buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte()); + position.setTime(dateBuilder.getDate()); + + buf.readUnsignedShort(); // lac + buf.readUnsignedShort(); // cid + + } else if (type == 0x12) { + + int status; + + status = buf.readUnsignedByte(); // status 1 + position.set(Position.KEY_ALARM, BitUtil.check(status, 1) ? Position.ALARM_VIBRATION : null); + + buf.readUnsignedByte(); // status 2 + buf.readUnsignedByte(); // status 3 + + status = buf.readUnsignedByte(); // status 4 + int ignition = BitUtil.between(status, 2, 4); + if (ignition == 0b01) { + position.set(Position.KEY_IGNITION, false); + } + if (ignition == 0b10) { + position.set(Position.KEY_IGNITION, true); + } + + buf.readUnsignedByte(); // status 5 + buf.readUnsignedByte(); // status 6 + + position.set(Position.KEY_STATUS, buf.readUnsignedShort()); + + } else if (type == 0x81) { + + position.set(Position.KEY_ALARM, decodeAlarm(buf.readUnsignedByte())); + + } else if (type == 0x84) { + + sendResponse(channel, index); + + } + + buf.readerIndex(frameEnd); + } + + return position.getFixTime() != null ? position : null; + } + +} diff --git a/src/main/java/org/traccar/protocol/CarcellProtocolEncoder.java b/src/main/java/org/traccar/protocol/CarcellProtocolEncoder.java index 083fe9a9e..78dbe7e91 100644 --- a/src/main/java/org/traccar/protocol/CarcellProtocolEncoder.java +++ b/src/main/java/org/traccar/protocol/CarcellProtocolEncoder.java @@ -30,9 +30,9 @@ public class CarcellProtocolEncoder extends StringProtocolEncoder { switch (command.getType()) { case Command.TYPE_ENGINE_STOP: - return formatCommand(command, "$SRVCMD,{%s},BA#\r\n", Command.KEY_UNIQUE_ID); + return formatCommand(command, "$SRVCMD,%s,BA#\r\n", Command.KEY_UNIQUE_ID); case Command.TYPE_ENGINE_RESUME: - return formatCommand(command, "$SRVCMD,{%s},BD#\r\n", Command.KEY_UNIQUE_ID); + return formatCommand(command, "$SRVCMD,%s,BD#\r\n", Command.KEY_UNIQUE_ID); default: return null; } diff --git a/src/main/java/org/traccar/protocol/CastelProtocolDecoder.java b/src/main/java/org/traccar/protocol/CastelProtocolDecoder.java index 03e4b25fd..23401b5ee 100644 --- a/src/main/java/org/traccar/protocol/CastelProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/CastelProtocolDecoder.java @@ -23,6 +23,7 @@ import org.traccar.BaseProtocolDecoder; import org.traccar.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; +import org.traccar.helper.BitUtil; import org.traccar.helper.Checksum; import org.traccar.helper.DateBuilder; import org.traccar.helper.ObdDecoder; @@ -185,7 +186,14 @@ public class CastelProtocolDecoder extends BaseProtocolDecoder { position.set(Position.KEY_ODOMETER_TRIP, buf.readUnsignedIntLE()); position.set(Position.KEY_FUEL_CONSUMPTION, buf.readUnsignedIntLE()); buf.readUnsignedShortLE(); // current fuel consumption - position.set(Position.KEY_STATUS, buf.readUnsignedIntLE()); + + long state = buf.readUnsignedIntLE(); + position.set(Position.KEY_ALARM, BitUtil.check(state, 4) ? Position.ALARM_ACCELERATION : null); + position.set(Position.KEY_ALARM, BitUtil.check(state, 5) ? Position.ALARM_BRAKING : null); + position.set(Position.KEY_ALARM, BitUtil.check(state, 6) ? Position.ALARM_IDLE : null); + position.set(Position.KEY_IGNITION, BitUtil.check(state, 2 * 8 + 2)); + position.set(Position.KEY_STATUS, state); + buf.skipBytes(8); } diff --git a/src/main/java/org/traccar/protocol/CellocatorProtocolDecoder.java b/src/main/java/org/traccar/protocol/CellocatorProtocolDecoder.java index ea4975d3e..aa13a0aa2 100644 --- a/src/main/java/org/traccar/protocol/CellocatorProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/CellocatorProtocolDecoder.java @@ -88,7 +88,7 @@ public class CellocatorProtocolDecoder extends BaseProtocolDecoder { content.writeByte(0); // ack content.writeShortLE(0); // reserved - ByteBuf reply = encodeContent(MSG_SERVER_ACKNOWLEDGE, (int) deviceId, packetNumber, content); + ByteBuf reply = encodeContent(MSG_CLIENT_MODULAR_EXT, (int) deviceId, packetNumber, content); channel.writeAndFlush(new NetworkMessage(reply, remoteAddress)); } } diff --git a/src/main/java/org/traccar/protocol/DmtProtocolDecoder.java b/src/main/java/org/traccar/protocol/DmtProtocolDecoder.java index c04e90f1d..58a5a88e3 100644 --- a/src/main/java/org/traccar/protocol/DmtProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/DmtProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 - 2018 Anton Tananaev (anton@traccar.org) + * Copyright 2017 - 2019 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -208,10 +208,21 @@ public class DmtProtocolDecoder extends BaseProtocolDecoder { position.set("solarPower", buf.readUnsignedShortLE() * 0.001); break; default: + buf.readUnsignedShortLE(); // other break; } } + } else if (fieldId == 26) { + + position.set(Position.KEY_ODOMETER_TRIP, buf.readUnsignedIntLE()); + position.set("tripHours", buf.readUnsignedIntLE() * 1000); + + } else if (fieldId == 27) { + + position.set(Position.KEY_ODOMETER, buf.readUnsignedIntLE()); + position.set(Position.KEY_HOURS, buf.readUnsignedIntLE() * 1000); + } buf.readerIndex(fieldEnd); diff --git a/src/main/java/org/traccar/protocol/EelinkProtocolDecoder.java b/src/main/java/org/traccar/protocol/EelinkProtocolDecoder.java index a6fd94b83..41d76f37f 100644 --- a/src/main/java/org/traccar/protocol/EelinkProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/EelinkProtocolDecoder.java @@ -412,7 +412,8 @@ public class EelinkProtocolDecoder extends BaseProtocolDecoder { return decodeNew(deviceSession, buf, type, index); - } else if (type == MSG_HEARTBEAT && buf.readableBytes() >= 2) { + } else if (type == MSG_HEARTBEAT && buf.readableBytes() >= 2 + || type == MSG_OBD && buf.readableBytes() == 4) { Position position = new Position(getProtocolName()); position.setDeviceId(deviceSession.getDeviceId()); diff --git a/src/main/java/org/traccar/protocol/EsealProtocolEncoder.java b/src/main/java/org/traccar/protocol/EsealProtocolEncoder.java index 6ee305ed8..74f9e22ab 100644 --- a/src/main/java/org/traccar/protocol/EsealProtocolEncoder.java +++ b/src/main/java/org/traccar/protocol/EsealProtocolEncoder.java @@ -31,13 +31,13 @@ public class EsealProtocolEncoder extends StringProtocolEncoder { switch (command.getType()) { case Command.TYPE_CUSTOM: return formatCommand( - command, "##S,eSeal,{%s},256,3.0.8,{%s},E##", Command.KEY_UNIQUE_ID, Command.KEY_DATA); + command, "##S,eSeal,%s,256,3.0.8,%s,E##", Command.KEY_UNIQUE_ID, Command.KEY_DATA); case Command.TYPE_ALARM_ARM: return formatCommand( - command, "##S,eSeal,{%s},256,3.0.8,RC-Power Control,Power OFF,E##", Command.KEY_UNIQUE_ID); + command, "##S,eSeal,%s,256,3.0.8,RC-Power Control,Power OFF,E##", Command.KEY_UNIQUE_ID); case Command.TYPE_ALARM_DISARM: return formatCommand( - command, "##S,eSeal,{%s},256,3.0.8,RC-Unlock,E##", Command.KEY_UNIQUE_ID); + command, "##S,eSeal,%s,256,3.0.8,RC-Unlock,E##", Command.KEY_UNIQUE_ID); default: return null; } diff --git a/src/main/java/org/traccar/protocol/EskyProtocol.java b/src/main/java/org/traccar/protocol/EskyProtocol.java index aaa92da58..fb047c207 100644 --- a/src/main/java/org/traccar/protocol/EskyProtocol.java +++ b/src/main/java/org/traccar/protocol/EskyProtocol.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 - 2018 Anton Tananaev (anton@traccar.org) + * Copyright 2017 - 2020 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -33,6 +33,14 @@ public class EskyProtocol extends BaseProtocol { pipeline.addLast(new EskyProtocolDecoder(EskyProtocol.this)); } }); + addServer(new TrackerServer(true, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new EskyProtocolDecoder(EskyProtocol.this)); + } + }); } } diff --git a/src/main/java/org/traccar/protocol/EskyProtocolDecoder.java b/src/main/java/org/traccar/protocol/EskyProtocolDecoder.java index 641b2e28f..d9de110f4 100644 --- a/src/main/java/org/traccar/protocol/EskyProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/EskyProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 - 2018 Anton Tananaev (anton@traccar.org) + * Copyright 2017 - 2020 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,8 +16,10 @@ package org.traccar.protocol; import io.netty.channel.Channel; +import io.netty.channel.socket.DatagramChannel; import org.traccar.BaseProtocolDecoder; import org.traccar.DeviceSession; +import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.Parser; import org.traccar.helper.PatternBuilder; @@ -35,7 +37,7 @@ public class EskyProtocolDecoder extends BaseProtocolDecoder { private static final Pattern PATTERN = new PatternBuilder() .expression("..;") // header - .number("d+;") // index + .number("(d+);") // index .number("(d+);") // imei .text("R;") // data type .number("(d+)[+;]") // satellites @@ -63,6 +65,11 @@ public class EskyProtocolDecoder extends BaseProtocolDecoder { return null; } + int index = parser.nextInt(); + if (channel instanceof DatagramChannel) { + channel.writeAndFlush(new NetworkMessage("ACK," + index + "#", remoteAddress)); + } + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); if (deviceSession == null) { return null; diff --git a/src/main/java/org/traccar/protocol/FifotrackProtocolDecoder.java b/src/main/java/org/traccar/protocol/FifotrackProtocolDecoder.java index 458e92065..40e146e0b 100644 --- a/src/main/java/org/traccar/protocol/FifotrackProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/FifotrackProtocolDecoder.java @@ -60,7 +60,7 @@ public class FifotrackProtocolDecoder extends BaseProtocolDecoder { .number("(-?d+),") // altitude .number("(d+),") // odometer .number("d+,") // runtime - .number("(xxxx),") // status + .number("(x{4,8}),") // status .number("(x+)?,") // input .number("(x+)?,") // output .number("(d+)|") // mcc @@ -151,7 +151,7 @@ public class FifotrackProtocolDecoder extends BaseProtocolDecoder { position.setAltitude(parser.nextInt()); position.set(Position.KEY_ODOMETER, parser.nextLong()); - position.set(Position.KEY_STATUS, parser.nextHexInt()); + position.set(Position.KEY_STATUS, parser.nextHexLong()); position.set(Position.KEY_INPUT, parser.nextHexInt()); position.set(Position.KEY_OUTPUT, parser.nextHexInt()); diff --git a/src/main/java/org/traccar/protocol/GalileoProtocolDecoder.java b/src/main/java/org/traccar/protocol/GalileoProtocolDecoder.java index 5b843324c..dfaedd695 100644 --- a/src/main/java/org/traccar/protocol/GalileoProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/GalileoProtocolDecoder.java @@ -318,7 +318,9 @@ public class GalileoProtocolDecoder extends BaseProtocolDecoder { buf.readUnsignedByte(); // part number photo.writeBytes(buf, length - 1); - } else { + sendResponse(channel, 0x07, buf.readUnsignedShortLE()); + + } else if (photo != null) { DeviceSession deviceSession = getDeviceSession(channel, remoteAddress); String uniqueId = Context.getIdentityManager().getById(deviceSession.getDeviceId()).getUniqueId(); @@ -334,8 +336,6 @@ public class GalileoProtocolDecoder extends BaseProtocolDecoder { } - sendResponse(channel, 0x07, buf.readUnsignedShortLE()); - return position; } diff --git a/src/main/java/org/traccar/protocol/GatorProtocolDecoder.java b/src/main/java/org/traccar/protocol/GatorProtocolDecoder.java index 31500bae6..087861635 100644 --- a/src/main/java/org/traccar/protocol/GatorProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/GatorProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 - 2018 Anton Tananaev (anton@traccar.org) + * Copyright 2013 - 2020 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,6 +23,7 @@ import org.traccar.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.BcdUtil; +import org.traccar.helper.Checksum; import org.traccar.helper.DateBuilder; import org.traccar.helper.UnitsConverter; import org.traccar.model.Position; @@ -58,16 +59,16 @@ public class GatorProtocolDecoder extends BaseProtocolDecoder { return String.format("%02d%02d%02d%02d%02d", d1, d2, d3, d4, d5); } - private void sendResponse(Channel channel, SocketAddress remoteAddress, byte calibration) { + private void sendResponse(Channel channel, SocketAddress remoteAddress, int type, int checksum) { if (channel != null) { ByteBuf response = Unpooled.buffer(); - response.writeByte(0x24); response.writeByte(0x24); // header - response.writeByte(MSG_HEARTBEAT); // size - response.writeShort(5); - response.writeByte(calibration); - response.writeByte(0); // main order - response.writeByte(0); // slave order - response.writeByte(1); // calibration + response.writeShort(0x2424); // header + response.writeByte(MSG_HEARTBEAT); + response.writeShort(5); // length + response.writeByte(checksum); + response.writeByte(type); + response.writeByte(0); // subtype + response.writeByte(Checksum.xor(response.nioBuffer(2, response.writerIndex()))); response.writeByte(0x0D); channel.writeAndFlush(new NetworkMessage(response, remoteAddress)); } @@ -87,7 +88,7 @@ public class GatorProtocolDecoder extends BaseProtocolDecoder { buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte()); - sendResponse(channel, remoteAddress, buf.getByte(buf.writerIndex() - 2)); + sendResponse(channel, remoteAddress, type, buf.getByte(buf.writerIndex() - 2)); if (type == MSG_POSITION_DATA || type == MSG_ROLLCALL_RESPONSE || type == MSG_ALARM_DATA || type == MSG_BLIND_AREA) { @@ -120,8 +121,10 @@ public class GatorProtocolDecoder extends BaseProtocolDecoder { position.set(Position.KEY_STATUS, buf.readUnsignedByte()); position.set("key", buf.readUnsignedByte()); - position.set("oil", buf.readUnsignedShort() / 10.0); - position.set(Position.KEY_POWER, buf.readUnsignedByte() + buf.readUnsignedByte() * 0.01); + + position.set(Position.PREFIX_ADC + 1, buf.readUnsignedByte() + buf.readUnsignedByte() * 0.01); + position.set(Position.PREFIX_ADC + 2, buf.readUnsignedByte() + buf.readUnsignedByte() * 0.01); + position.set(Position.KEY_ODOMETER, buf.readUnsignedInt()); return position; diff --git a/src/main/java/org/traccar/protocol/Gl200ProtocolEncoder.java b/src/main/java/org/traccar/protocol/Gl200ProtocolEncoder.java index 32307446b..dd0672c23 100644 --- a/src/main/java/org/traccar/protocol/Gl200ProtocolEncoder.java +++ b/src/main/java/org/traccar/protocol/Gl200ProtocolEncoder.java @@ -32,17 +32,17 @@ public class Gl200ProtocolEncoder extends StringProtocolEncoder { switch (command.getType()) { case Command.TYPE_POSITION_SINGLE: - return formatCommand(command, "AT+GTRTO={%s},1,,,,,,FFFF$", Command.KEY_DEVICE_PASSWORD); + return formatCommand(command, "AT+GTRTO=%s,1,,,,,,FFFF$", Command.KEY_DEVICE_PASSWORD); case Command.TYPE_ENGINE_STOP: - return formatCommand(command, "AT+GTOUT={%s},1,,,0,0,0,0,0,0,0,,,,,,,FFFF$", + return formatCommand(command, "AT+GTOUT=%s,1,,,0,0,0,0,0,0,0,,,,,,,FFFF$", Command.KEY_DEVICE_PASSWORD); case Command.TYPE_ENGINE_RESUME: - return formatCommand(command, "AT+GTOUT={%s},0,,,0,0,0,0,0,0,0,,,,,,,FFFF$", + return formatCommand(command, "AT+GTOUT=%s,0,,,0,0,0,0,0,0,0,,,,,,,FFFF$", Command.KEY_DEVICE_PASSWORD); case Command.TYPE_IDENTIFICATION: - return formatCommand(command, "AT+GTRTO={%s},8,,,,,,FFFF$", Command.KEY_DEVICE_PASSWORD); + return formatCommand(command, "AT+GTRTO=%s,8,,,,,,FFFF$", Command.KEY_DEVICE_PASSWORD); case Command.TYPE_REBOOT_DEVICE: - return formatCommand(command, "AT+GTRTO={%s},3,,,,,,FFFF$", Command.KEY_DEVICE_PASSWORD); + return formatCommand(command, "AT+GTRTO=%s,3,,,,,,FFFF$", Command.KEY_DEVICE_PASSWORD); default: return null; } diff --git a/src/main/java/org/traccar/protocol/GlobalstarProtocolDecoder.java b/src/main/java/org/traccar/protocol/GlobalstarProtocolDecoder.java index 5739a1224..382509793 100644 --- a/src/main/java/org/traccar/protocol/GlobalstarProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/GlobalstarProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 Anton Tananaev (anton@traccar.org) + * Copyright 2019 - 2020 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,28 +17,41 @@ package org.traccar.protocol; import com.fasterxml.jackson.databind.util.ByteBufferBackedInputStream; import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufOutputStream; import io.netty.buffer.Unpooled; import io.netty.channel.Channel; +import io.netty.handler.codec.http.DefaultFullHttpResponse; import io.netty.handler.codec.http.FullHttpRequest; +import io.netty.handler.codec.http.FullHttpResponse; +import io.netty.handler.codec.http.HttpHeaderNames; import io.netty.handler.codec.http.HttpResponseStatus; +import io.netty.handler.codec.http.HttpVersion; import org.traccar.BaseHttpProtocolDecoder; import org.traccar.DeviceSession; +import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.DataConverter; import org.traccar.model.Position; import org.w3c.dom.Document; +import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathExpression; import javax.xml.xpath.XPathExpressionException; import javax.xml.xpath.XPathFactory; import java.net.SocketAddress; +import java.text.SimpleDateFormat; import java.util.Date; import java.util.LinkedList; import java.util.List; @@ -67,6 +80,40 @@ public class GlobalstarProtocolDecoder extends BaseHttpProtocolDecoder { } } + private void sendResponse(Channel channel, String messageId) throws TransformerException { + + Document document = documentBuilder.newDocument(); + Element rootElement = document.createElement("stuResponseMsg"); + rootElement.setAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance"); + rootElement.setAttribute( + "xsi:noNamespaceSchemaLocation", "http://cody.glpconnect.com/XSD/StuResponse_Rev1_0.xsd"); + rootElement.setAttribute("deliveryTimeStamp", new SimpleDateFormat("dd/MM/yyyy hh:mm:ss z").format(new Date())); + rootElement.setAttribute("messageID", "00000000000000000000000000000000"); + rootElement.setAttribute("correlationID", messageId); + document.appendChild(rootElement); + + Element state = document.createElement("state"); + state.appendChild(document.createTextNode("pass")); + rootElement.appendChild(state); + + Element stateMessage = document.createElement("stateMessage"); + stateMessage.appendChild(document.createTextNode("Messages received and stored successfully")); + rootElement.appendChild(stateMessage); + + Transformer transformer = TransformerFactory.newInstance().newTransformer(); + ByteBuf content = Unpooled.buffer(); + transformer.transform(new DOMSource(document), new StreamResult(new ByteBufOutputStream(content))); + + if (channel != null) { + FullHttpResponse response = new DefaultFullHttpResponse( + HttpVersion.HTTP_1_1, HttpResponseStatus.OK, content); + response.headers() + .add(HttpHeaderNames.CONTENT_LENGTH, content.readableBytes()) + .add(HttpHeaderNames.CONTENT_TYPE, "text/xml"); + channel.writeAndFlush(new NetworkMessage(response, channel.remoteAddress())); + } + } + @Override protected Object decode( Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { @@ -112,7 +159,7 @@ public class GlobalstarProtocolDecoder extends BaseHttpProtocolDecoder { } } - sendResponse(channel, HttpResponseStatus.OK); + sendResponse(channel, document.getFirstChild().getAttributes().getNamedItem("messageID").getNodeValue()); return positions; } diff --git a/src/main/java/org/traccar/protocol/Gps103ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Gps103ProtocolDecoder.java index aa02e8ad4..e00d83061 100644 --- a/src/main/java/org/traccar/protocol/Gps103ProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/Gps103ProtocolDecoder.java @@ -77,7 +77,7 @@ public class Gps103ProtocolDecoder extends BaseProtocolDecoder { .expression("([EW])?,").optional() .number("(d+.?d*)?").optional() // speed .number(",(d+.?d*)?").optional() // course - .number(",(d+.?d*)?").optional() // altitude + .number(",(-?d+.?d*)?").optional() // altitude .number(",([01])?").optional() // ignition .number(",([01])?").optional() // door .groupBegin() diff --git a/src/main/java/org/traccar/protocol/Gps103ProtocolEncoder.java b/src/main/java/org/traccar/protocol/Gps103ProtocolEncoder.java index 7128823ed..e662e9b04 100644 --- a/src/main/java/org/traccar/protocol/Gps103ProtocolEncoder.java +++ b/src/main/java/org/traccar/protocol/Gps103ProtocolEncoder.java @@ -47,24 +47,24 @@ public class Gps103ProtocolEncoder extends StringProtocolEncoder implements Stri switch (command.getType()) { case Command.TYPE_CUSTOM: - return formatCommand(command, "**,imei:{%s},{%s}", Command.KEY_UNIQUE_ID, Command.KEY_DATA); + return formatCommand(command, "**,imei:%s,%s", Command.KEY_UNIQUE_ID, Command.KEY_DATA); case Command.TYPE_POSITION_STOP: - return formatCommand(command, "**,imei:{%s},A", Command.KEY_UNIQUE_ID); + return formatCommand(command, "**,imei:%s,A", Command.KEY_UNIQUE_ID); case Command.TYPE_POSITION_SINGLE: - return formatCommand(command, "**,imei:{%s},B", Command.KEY_UNIQUE_ID); + return formatCommand(command, "**,imei:%s,B", Command.KEY_UNIQUE_ID); case Command.TYPE_POSITION_PERIODIC: return formatCommand( - command, "**,imei:{%s},C,{%s}", this, Command.KEY_UNIQUE_ID, Command.KEY_FREQUENCY); + command, "**,imei:%s,C,%s", this, Command.KEY_UNIQUE_ID, Command.KEY_FREQUENCY); case Command.TYPE_ENGINE_STOP: - return formatCommand(command, "**,imei:{%s},J", Command.KEY_UNIQUE_ID); + return formatCommand(command, "**,imei:%s,J", Command.KEY_UNIQUE_ID); case Command.TYPE_ENGINE_RESUME: - return formatCommand(command, "**,imei:{%s},K", Command.KEY_UNIQUE_ID); + return formatCommand(command, "**,imei:%s,K", Command.KEY_UNIQUE_ID); case Command.TYPE_ALARM_ARM: - return formatCommand(command, "**,imei:{%s},L", Command.KEY_UNIQUE_ID); + return formatCommand(command, "**,imei:%s,L", Command.KEY_UNIQUE_ID); case Command.TYPE_ALARM_DISARM: - return formatCommand(command, "**,imei:{%s},M", Command.KEY_UNIQUE_ID); + return formatCommand(command, "**,imei:%s,M", Command.KEY_UNIQUE_ID); case Command.TYPE_REQUEST_PHOTO: - return formatCommand(command, "**,imei:{%s},160", Command.KEY_UNIQUE_ID); + return formatCommand(command, "**,imei:%s,160", Command.KEY_UNIQUE_ID); default: return null; } diff --git a/src/main/java/org/traccar/protocol/GranitProtocolSmsEncoder.java b/src/main/java/org/traccar/protocol/GranitProtocolSmsEncoder.java index 7dd4b2d77..be0ab5130 100644 --- a/src/main/java/org/traccar/protocol/GranitProtocolSmsEncoder.java +++ b/src/main/java/org/traccar/protocol/GranitProtocolSmsEncoder.java @@ -32,7 +32,7 @@ public class GranitProtocolSmsEncoder extends StringProtocolEncoder { case Command.TYPE_REBOOT_DEVICE: return "BB+RESET"; case Command.TYPE_POSITION_PERIODIC: - return formatCommand(command, "BB+BBMD={%s}", Command.KEY_FREQUENCY); + return formatCommand(command, "BB+BBMD=%s", Command.KEY_FREQUENCY); default: return null; } diff --git a/src/main/java/org/traccar/protocol/Gt06ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Gt06ProtocolDecoder.java index dc7a95955..946652b03 100644 --- a/src/main/java/org/traccar/protocol/Gt06ProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/Gt06ProtocolDecoder.java @@ -103,6 +103,7 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder { public static final int MSG_MULTIMEDIA = 0x21; public static final int MSG_BMS_2 = 0x40; public static final int MSG_MULTIMEDIA_2 = 0x41; + public static final int MSG_ALARM = 0x95; private static boolean isSupported(int type) { return hasGps(type) || hasLbs(type) || hasStatus(type); @@ -208,7 +209,7 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder { sendResponse(channel, false, MSG_X1_PHOTO_DATA, 0, content); } - private boolean decodeGps(Position position, ByteBuf buf, boolean hasLength, TimeZone timezone) { + public static boolean decodeGps(Position position, ByteBuf buf, boolean hasLength, TimeZone timezone) { DateBuilder dateBuilder = new DateBuilder(timezone) .setDate(buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte()) @@ -547,6 +548,15 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder { position.set(Position.KEY_BATTERY, buf.readUnsignedShort() * 0.01); position.set(Position.KEY_POWER, buf.readUnsignedShort() * 0.01); + long portInfo = buf.readUnsignedInt(); + + position.set(Position.KEY_INPUT, buf.readUnsignedByte()); + position.set(Position.KEY_OUTPUT, buf.readUnsignedByte()); + + for (int i = 1; i <= BitUtil.between(portInfo, 20, 24); i++) { + position.set(Position.PREFIX_ADC + i, buf.readUnsignedShort() * 0.01); + } + return position; } else if (type == MSG_X1_PHOTO_INFO) { @@ -720,6 +730,14 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder { decodeStatus(position, buf); } + if (type == MSG_GPS_LBS_1 && buf.readableBytes() > 75 + 6) { + position.set(Position.KEY_ODOMETER, buf.readUnsignedInt()); + String data = buf.readCharSequence(buf.readUnsignedByte(), StandardCharsets.US_ASCII).toString(); + buf.readUnsignedByte(); // alarm + buf.readUnsignedByte(); // swiped + position.set("driverLicense", data.trim()); + } + if (type == MSG_GPS_LBS_1 && buf.readableBytes() == 2 + 6) { int mask = buf.readUnsignedShort(); position.set(Position.KEY_IGNITION, BitUtil.check(mask, 8 + 7)); @@ -750,6 +768,39 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder { position.set(Position.KEY_ARCHIVE, buf.readUnsignedByte() > 0); } + } else if (type == MSG_ALARM) { + + DateBuilder dateBuilder = new DateBuilder(deviceSession.getTimeZone()) + .setDate(buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte()) + .setTime(buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte()); + + getLastLocation(position, dateBuilder.getDate()); + + short alarmType = buf.readUnsignedByte(); + + switch (alarmType) { + case 0x80: + position.set(Position.KEY_ALARM, Position.ALARM_VIBRATION); + break; + case 0x87: + position.set(Position.KEY_ALARM, Position.ALARM_OVERSPEED); + break; + case 0x90: + position.set(Position.KEY_ALARM, Position.ALARM_ACCELERATION); + break; + case 0x91: + position.set(Position.KEY_ALARM, Position.ALARM_BRAKING); + break; + case 0x92: + position.set(Position.KEY_ALARM, Position.ALARM_CORNERING); + break; + default: + position.set(Position.KEY_ALARM, Position.ALARM_GENERAL); + break; + } + + position.set("alarmValue", buf.readShort()); + } else { if (dataLength > 0) { @@ -958,7 +1009,7 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder { } else if (type == MSG_GPS_MODULAR) { - return decodeExtendedModular(channel, buf, deviceSession, type); + return decodeExtendedModular(buf, deviceSession); } else { @@ -969,7 +1020,7 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder { return null; } - private Object decodeExtendedModular(Channel channel, ByteBuf buf, DeviceSession deviceSession, int type) { + private Object decodeExtendedModular(ByteBuf buf, DeviceSession deviceSession) { Position position = new Position(getProtocolName()); position.setDeviceId(deviceSession.getDeviceId()); @@ -977,7 +1028,28 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder { while (buf.readableBytes() > 6) { int moduleType = buf.readUnsignedShort(); int moduleLength = buf.readUnsignedShort(); + switch (moduleType) { + case 0x03: + position.set(Position.KEY_ICCID, ByteBufUtil.hexDump(buf.readSlice(10))); + break; + case 0x09: + position.set(Position.KEY_SATELLITES, buf.readUnsignedByte()); + break; + case 0x0a: + position.set(Position.KEY_SATELLITES_VISIBLE, buf.readUnsignedByte()); + break; + case 0x11: + CellTower cellTower = CellTower.from( + buf.readUnsignedShort(), + buf.readUnsignedShort(), + buf.readUnsignedShort(), + buf.readUnsignedMedium(), + buf.readUnsignedByte()); + if (cellTower.getCellId() > 0) { + position.setNetwork(new Network(cellTower)); + } + break; case 0x18: position.set(Position.KEY_BATTERY, buf.readUnsignedShort() * 0.01); break; @@ -988,7 +1060,9 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder { position.set(Position.KEY_INDEX, buf.readUnsignedInt()); break; case 0x2a: - position.set(Position.KEY_INPUT, buf.readUnsignedByte()); + int input = buf.readUnsignedByte(); + position.set(Position.KEY_DOOR, BitUtil.to(input, 4) > 0); + position.set("tamper", BitUtil.from(input, 4) > 0); break; case 0x2b: int event = buf.readUnsignedByte(); @@ -1036,6 +1110,11 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder { position.setLatitude(latitude); position.setLongitude(longitude); break; + case 0x34: + position.set(Position.KEY_EVENT, buf.readUnsignedByte()); + buf.readUnsignedIntLE(); // time + buf.skipBytes(buf.readUnsignedByte()); // content + break; default: buf.skipBytes(moduleLength); break; diff --git a/src/main/java/org/traccar/protocol/H02ProtocolDecoder.java b/src/main/java/org/traccar/protocol/H02ProtocolDecoder.java index 137689a67..320fe991d 100644 --- a/src/main/java/org/traccar/protocol/H02ProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/H02ProtocolDecoder.java @@ -19,6 +19,7 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufUtil; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; +import org.traccar.Context; import org.traccar.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; @@ -185,7 +186,7 @@ public class H02ProtocolDecoder extends BaseProtocolDecoder { .number("(d+)(dd.d+),") // longitude .groupEnd() .expression("([EW]),") - .number("(d+.?d*),") // speed + .number(" *(d+.?d*),") // speed .number("(d+.?d*)?,") // course .number("(?:d+,)?") // battery .number("(?:(dd)(dd)(dd))?") // date (ddmmyy) @@ -223,7 +224,7 @@ public class H02ProtocolDecoder extends BaseProtocolDecoder { .number("(d+),") // mnc .number("d+,") // gsm delay time .number("d+,") // count - .number("((?:d+,d+,d+,)+)") // cells + .number("((?:d+,d+,-?d+,)+)") // cells .number("(dd)(dd)(dd),") // date (ddmmyy) .number("(x{8})") // status .any() @@ -287,9 +288,15 @@ public class H02ProtocolDecoder extends BaseProtocolDecoder { private void sendResponse(Channel channel, SocketAddress remoteAddress, String id, String type) { if (channel != null && id != null) { - DateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmss"); + String response; + DateFormat dateFormat = new SimpleDateFormat(type.equals("R12") ? "HHmmss" : "yyyyMMddHHmmss"); dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); - String response = String.format("*HQ,%s,V4,%s,%s#", id, type, dateFormat.format(new Date())); + String time = dateFormat.format(new Date()); + if (type.equals("R12")) { + response = String.format("*HQ,%s,%s,%s#", id, type, time); + } else { + response = String.format("*HQ,%s,V4,%s,%s#", id, type, time); + } channel.writeAndFlush(new NetworkMessage(response, remoteAddress)); } } @@ -316,6 +323,8 @@ public class H02ProtocolDecoder extends BaseProtocolDecoder { if (parser.hasNext() && parser.next().equals("V1")) { sendResponse(channel, remoteAddress, id, "V1"); + } else if (Context.getConfig().getBoolean(getProtocolName() + ".ack")) { + sendResponse(channel, remoteAddress, id, "R12"); } DateBuilder dateBuilder = new DateBuilder(); diff --git a/src/main/java/org/traccar/protocol/HuaShengProtocolDecoder.java b/src/main/java/org/traccar/protocol/HuaShengProtocolDecoder.java index 9449e2d5c..eac06bbfe 100644 --- a/src/main/java/org/traccar/protocol/HuaShengProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/HuaShengProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 - 2018 Anton Tananaev (anton@traccar.org) + * Copyright 2016 - 2020 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,7 +25,10 @@ import org.traccar.Protocol; import org.traccar.helper.BitUtil; import org.traccar.helper.DateBuilder; import org.traccar.helper.UnitsConverter; +import org.traccar.model.CellTower; +import org.traccar.model.Network; import org.traccar.model.Position; +import org.traccar.model.WifiAccessPoint; import java.net.SocketAddress; import java.nio.charset.StandardCharsets; @@ -137,6 +140,8 @@ public class HuaShengProtocolDecoder extends BaseProtocolDecoder { position.set(Position.KEY_ODOMETER, buf.readUnsignedShort() * 1000); + Network network = new Network(); + while (buf.readableBytes() > 4) { int subtype = buf.readUnsignedShort(); int length = buf.readUnsignedShort() - 4; @@ -161,12 +166,34 @@ public class HuaShengProtocolDecoder extends BaseProtocolDecoder { position.set( Position.KEY_VIN, buf.readCharSequence(length, StandardCharsets.US_ASCII).toString()); break; + case 0x0020: + String[] cells = buf.readCharSequence( + length, StandardCharsets.US_ASCII).toString().split("\\+"); + for (String cell : cells) { + String[] values = cell.split("@"); + network.addCellTower(CellTower.from( + Integer.parseInt(values[0]), Integer.parseInt(values[1]), + Integer.parseInt(values[2], 16), Integer.parseInt(values[3], 16))); + } + break; + case 0x0021: + String[] points = buf.readCharSequence( + length, StandardCharsets.US_ASCII).toString().split("\\+"); + for (String point : points) { + String[] values = point.split("@"); + network.addWifiAccessPoint(WifiAccessPoint.from(values[0], Integer.parseInt(values[1]))); + } + break; default: buf.skipBytes(length); break; } } + if (network.getCellTowers() != null || network.getWifiAccessPoints() != null) { + position.setNetwork(network); + } + sendResponse(channel, MSG_POSITION_RSP, index, null); return position; diff --git a/src/main/java/org/traccar/protocol/HuabaoProtocolDecoder.java b/src/main/java/org/traccar/protocol/HuabaoProtocolDecoder.java index 6e2e1377b..bf5b6d89a 100644 --- a/src/main/java/org/traccar/protocol/HuabaoProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/HuabaoProtocolDecoder.java @@ -58,7 +58,7 @@ public class HuabaoProtocolDecoder extends BaseProtocolDecoder { buf.writeShort(type); buf.writeShort(data.readableBytes()); buf.writeBytes(id); - buf.writeShort(1); // index + buf.writeShort(0); // index buf.writeBytes(data); data.release(); buf.writeByte(Checksum.xor(buf.nioBuffer(1, buf.readableBytes() - 1))); @@ -98,6 +98,9 @@ public class HuabaoProtocolDecoder extends BaseProtocolDecoder { if (BitUtil.check(value, 20)) { return Position.ALARM_GEOFENCE; } + if (BitUtil.check(value, 28)) { + return Position.ALARM_MOVEMENT; + } if (BitUtil.check(value, 29)) { return Position.ALARM_ACCIDENT; } @@ -131,7 +134,7 @@ public class HuabaoProtocolDecoder extends BaseProtocolDecoder { ByteBuf response = Unpooled.buffer(); response.writeShort(index); response.writeByte(RESULT_SUCCESS); - response.writeBytes("authentication".getBytes(StandardCharsets.US_ASCII)); + response.writeBytes(ByteBufUtil.hexDump(id).getBytes(StandardCharsets.US_ASCII)); channel.writeAndFlush(new NetworkMessage( formatMessage(MSG_TERMINAL_REGISTER_RESPONSE, id, response), remoteAddress)); } @@ -160,22 +163,23 @@ public class HuabaoProtocolDecoder extends BaseProtocolDecoder { position.set(Position.KEY_ALARM, decodeAlarm(buf.readUnsignedInt())); - int flags = buf.readInt(); + int status = buf.readInt(); - position.set(Position.KEY_IGNITION, BitUtil.check(flags, 0)); + position.set(Position.KEY_IGNITION, BitUtil.check(status, 0)); + position.set(Position.KEY_BLOCKED, BitUtil.check(status, 10)); - position.setValid(BitUtil.check(flags, 1)); + position.setValid(BitUtil.check(status, 1)); double lat = buf.readUnsignedInt() * 0.000001; double lon = buf.readUnsignedInt() * 0.000001; - if (BitUtil.check(flags, 2)) { + if (BitUtil.check(status, 2)) { position.setLatitude(-lat); } else { position.setLatitude(lat); } - if (BitUtil.check(flags, 3)) { + if (BitUtil.check(status, 3)) { position.setLongitude(-lon); } else { position.setLongitude(lon); @@ -197,20 +201,80 @@ public class HuabaoProtocolDecoder extends BaseProtocolDecoder { while (buf.readableBytes() > 2) { int subtype = buf.readUnsignedByte(); int length = buf.readUnsignedByte(); + int endIndex = buf.readerIndex() + length; switch (subtype) { case 0x01: position.set(Position.KEY_ODOMETER, buf.readUnsignedInt() * 100); break; + case 0x02: + position.set(Position.KEY_FUEL_LEVEL, buf.readUnsignedShort() * 0.1); + break; case 0x30: position.set(Position.KEY_RSSI, buf.readUnsignedByte()); break; case 0x31: position.set(Position.KEY_SATELLITES, buf.readUnsignedByte()); break; + case 0x33: + String sentence = buf.readCharSequence(length, StandardCharsets.US_ASCII).toString(); + if (sentence.startsWith("*M00")) { + String lockStatus = sentence.substring(8, 8 + 7); + position.set(Position.KEY_BATTERY, Integer.parseInt(lockStatus.substring(2, 5)) * 0.01); + } + break; + case 0x91: + position.set(Position.KEY_BATTERY, buf.readUnsignedShort() * 0.1); + position.set(Position.KEY_RPM, buf.readUnsignedShort()); + position.set(Position.KEY_OBD_SPEED, buf.readUnsignedByte()); + position.set(Position.KEY_THROTTLE, buf.readUnsignedByte() * 100 / 255); + position.set(Position.KEY_ENGINE_LOAD, buf.readUnsignedByte() * 100 / 255); + position.set(Position.KEY_COOLANT_TEMP, buf.readUnsignedByte() - 40); + buf.readUnsignedShort(); + position.set(Position.KEY_FUEL_CONSUMPTION, buf.readUnsignedShort() * 0.01); + buf.readUnsignedShort(); + buf.readUnsignedInt(); + buf.readUnsignedShort(); + position.set(Position.KEY_FUEL_USED, buf.readUnsignedShort() * 0.01); + break; + case 0x94: + if (length > 0) { + position.set( + Position.KEY_VIN, buf.readCharSequence(length, StandardCharsets.US_ASCII).toString()); + } + break; + case 0xD0: + long userStatus = buf.readUnsignedInt(); + if (BitUtil.check(userStatus, 3)) { + position.set(Position.KEY_ALARM, Position.ALARM_VIBRATION); + } + break; + case 0xD3: + position.set(Position.KEY_POWER, buf.readUnsignedShort() * 0.1); + break; + case 0xEB: + while (buf.readerIndex() < endIndex) { + int tenetLength = buf.readUnsignedShort(); + int tenetType = buf.readUnsignedShort(); + switch (tenetType) { + case 0x0001: + position.set("fuel1", buf.readUnsignedShort() * 0.1); + buf.readUnsignedByte(); // unused + break; + case 0x0023: + buf.skipBytes(4); // unused + position.set("fuel2", Double.parseDouble( + buf.readCharSequence(2, StandardCharsets.US_ASCII).toString())); + break; + default: + buf.skipBytes(tenetLength - 2); + break; + } + } + break; default: - buf.skipBytes(length); break; } + buf.readerIndex(endIndex); } return position; diff --git a/src/main/java/org/traccar/protocol/ItsProtocolDecoder.java b/src/main/java/org/traccar/protocol/ItsProtocolDecoder.java index d76d9c92e..e8d77f1a8 100644 --- a/src/main/java/org/traccar/protocol/ItsProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/ItsProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 - 2019 Anton Tananaev (anton@traccar.org) + * Copyright 2018 - 2020 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -41,7 +41,7 @@ public class ItsProtocolDecoder extends BaseProtocolDecoder { .text("$") .expression(",?[^,]+,") // event .groupBegin() - .expression("[^,]+,") // vendor + .expression("[^,]*,") // vendor .expression("[^,]+,") // firmware version .expression("(..),") // status .number("(d+),").optional() // event @@ -81,8 +81,8 @@ public class ItsProtocolDecoder extends BaseProtocolDecoder { .number("([01]{2}),") // outputs .groupBegin() .number("d+,") // index - .number("(d+.d+),") // adc1 - .number("(d+.d+),") // adc2 + .number("(d+.?d*),") // adc1 + .number("(d+.?d*),") // adc2 .groupEnd("?") .groupEnd("?") .or() @@ -195,22 +195,25 @@ public class ItsProtocolDecoder extends BaseProtocolDecoder { position.set("emergency", parser.nextInt() > 0); - String[] cells = parser.next().split(","); - int mcc = Integer.parseInt(cells[1]); - int mnc = Integer.parseInt(cells[2]); - int lac = Integer.parseInt(cells[3], 16); - int cid = Integer.parseInt(cells[4], 16); - Network network = new Network(CellTower.from(mcc, mnc, lac, cid, Integer.parseInt(cells[0]))); - if (!cells[5].startsWith("(")) { - for (int i = 0; i < 4; i++) { - lac = Integer.parseInt(cells[5 + 3 * i + 1], 16); - cid = Integer.parseInt(cells[5 + 3 * i + 2], 16); - if (lac > 0 && cid > 0) { - network.addCellTower(CellTower.from(mcc, mnc, lac, cid)); + String cellsString = parser.next(); + if (!cellsString.contains("x")) { + String[] cells = cellsString.split(","); + int mcc = Integer.parseInt(cells[1]); + int mnc = Integer.parseInt(cells[2]); + int lac = Integer.parseInt(cells[3], 16); + int cid = Integer.parseInt(cells[4], 16); + Network network = new Network(CellTower.from(mcc, mnc, lac, cid, Integer.parseInt(cells[0]))); + if (!cells[5].startsWith("(")) { + for (int i = 0; i < 4; i++) { + lac = Integer.parseInt(cells[5 + 3 * i + 1], 16); + cid = Integer.parseInt(cells[5 + 3 * i + 2], 16); + if (lac > 0 && cid > 0) { + network.addCellTower(CellTower.from(mcc, mnc, lac, cid)); + } } } + position.setNetwork(network); } - position.setNetwork(network); String input = parser.next(); if (input.charAt(input.length() - 1) == '2') { diff --git a/src/main/java/org/traccar/protocol/Jt600FrameDecoder.java b/src/main/java/org/traccar/protocol/Jt600FrameDecoder.java index b5d060ecc..bfefb94a7 100644 --- a/src/main/java/org/traccar/protocol/Jt600FrameDecoder.java +++ b/src/main/java/org/traccar/protocol/Jt600FrameDecoder.java @@ -35,7 +35,7 @@ public class Jt600FrameDecoder extends BaseFrameDecoder { char type = (char) buf.getByte(buf.readerIndex()); if (type == '$') { - boolean longFormat = buf.getUnsignedByte(buf.readerIndex() + 1) == 0x75; + boolean longFormat = Jt600ProtocolDecoder.isLongFormat(buf, buf.readerIndex() + 1); int length = buf.getUnsignedShort(buf.readerIndex() + (longFormat ? 8 : 7)) + 10; if (length <= buf.readableBytes()) { return buf.readRetainedSlice(length); diff --git a/src/main/java/org/traccar/protocol/Jt600ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Jt600ProtocolDecoder.java index da055dff3..f456cd1ef 100644 --- a/src/main/java/org/traccar/protocol/Jt600ProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/Jt600ProtocolDecoder.java @@ -86,13 +86,17 @@ public class Jt600ProtocolDecoder extends BaseProtocolDecoder { } + static boolean isLongFormat(ByteBuf buf, int flagIndex) { + return buf.getUnsignedByte(flagIndex) >> 4 == 0x7; + } + private List<Position> decodeBinary(ByteBuf buf, Channel channel, SocketAddress remoteAddress) { List<Position> positions = new LinkedList<>(); buf.readByte(); // header - boolean longFormat = buf.getUnsignedByte(buf.readerIndex()) == 0x75; + boolean longFormat = isLongFormat(buf, buf.readerIndex()); String id = String.valueOf(Long.parseLong(ByteBufUtil.hexDump(buf.readSlice(5)))); DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, id); diff --git a/src/main/java/org/traccar/protocol/LaipacProtocolEncoder.java b/src/main/java/org/traccar/protocol/LaipacProtocolEncoder.java index 343ac9431..0c9f8ebb8 100644 --- a/src/main/java/org/traccar/protocol/LaipacProtocolEncoder.java +++ b/src/main/java/org/traccar/protocol/LaipacProtocolEncoder.java @@ -40,13 +40,13 @@ public class LaipacProtocolEncoder extends StringProtocolEncoder { switch (command.getType()) { case Command.TYPE_CUSTOM: - return formatCommand(command, "{%s}", + return formatCommand(command, "%s", Command.KEY_DATA); case Command.TYPE_POSITION_SINGLE: - return formatCommand(command, "AVREQ,{%s},1", + return formatCommand(command, "AVREQ,%s,1", Command.KEY_DEVICE_PASSWORD); case Command.TYPE_REBOOT_DEVICE: - return formatCommand(command, "AVRESET,{%s},{%s}", + return formatCommand(command, "AVRESET,%s,%s", Command.KEY_UNIQUE_ID, Command.KEY_DEVICE_PASSWORD); default: return null; diff --git a/src/main/java/org/traccar/protocol/MeiligaoProtocolDecoder.java b/src/main/java/org/traccar/protocol/MeiligaoProtocolDecoder.java index cbfc3660a..bd66cdc4b 100644 --- a/src/main/java/org/traccar/protocol/MeiligaoProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/MeiligaoProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2012 - 2018 Anton Tananaev (anton@traccar.org) + * Copyright 2012 - 2020 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -47,7 +47,7 @@ public class MeiligaoProtocolDecoder extends BaseProtocolDecoder { } private static final Pattern PATTERN = new PatternBuilder() - .number("(dd)(dd)(dd).?d*,") // time (hhmmss) + .number("(d+)(dd)(dd).?d*,") // time (hhmmss) .expression("([AV]),") // validity .number("(d+)(dd.d+),") // latitude .expression("([NS]),") diff --git a/src/main/java/org/traccar/protocol/MeitrackProtocolDecoder.java b/src/main/java/org/traccar/protocol/MeitrackProtocolDecoder.java index 55260ef0c..529496928 100644 --- a/src/main/java/org/traccar/protocol/MeitrackProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/MeitrackProtocolDecoder.java @@ -70,7 +70,8 @@ public class MeitrackProtocolDecoder extends BaseProtocolDecoder { .number("(d+)|") // mnc .number("(x+)|") // lac .number("(x+),") // cid - .number("(x+),") // state + .number("(xx)") // input + .number("(xx),") // output .number("(x+)?|") // adc1 .number("(x+)?|") // adc2 .number("(x+)?|") // adc3 @@ -149,39 +150,38 @@ public class MeitrackProtocolDecoder extends BaseProtocolDecoder { } position.setDeviceId(deviceSession.getDeviceId()); - int event = parser.nextInt(0); + int event = parser.nextInt(); position.set(Position.KEY_EVENT, event); position.set(Position.KEY_ALARM, decodeAlarm(event)); - position.setLatitude(parser.nextDouble(0)); - position.setLongitude(parser.nextDouble(0)); + position.setLatitude(parser.nextDouble()); + position.setLongitude(parser.nextDouble()); position.setTime(parser.nextDateTime()); position.setValid(parser.next().equals("A")); position.set(Position.KEY_SATELLITES, parser.nextInt()); - int rssi = parser.nextInt(0); + int rssi = parser.nextInt(); - position.setSpeed(UnitsConverter.knotsFromKph(parser.nextDouble(0))); - position.setCourse(parser.nextDouble(0)); + position.setSpeed(UnitsConverter.knotsFromKph(parser.nextDouble())); + position.setCourse(parser.nextDouble()); position.set(Position.KEY_HDOP, parser.nextDouble()); - position.setAltitude(parser.nextDouble(0)); + position.setAltitude(parser.nextDouble()); - position.set(Position.KEY_ODOMETER, parser.nextInt(0)); + position.set(Position.KEY_ODOMETER, parser.nextInt()); position.set("runtime", parser.next()); position.setNetwork(new Network(CellTower.from( - parser.nextInt(0), parser.nextInt(0), parser.nextHexInt(0), parser.nextHexInt(0), rssi))); + parser.nextInt(), parser.nextInt(), parser.nextHexInt(), parser.nextHexInt(), rssi))); - position.set(Position.KEY_STATUS, parser.next()); + position.set(Position.KEY_OUTPUT, parser.nextHexInt()); + position.set(Position.KEY_INPUT, parser.nextHexInt()); for (int i = 1; i <= 3; i++) { - if (parser.hasNext()) { - position.set(Position.PREFIX_ADC + i, parser.nextHexInt(0)); - } + position.set(Position.PREFIX_ADC + i, parser.nextHexInt()); } String deviceModel = Context.getIdentityManager().getById(deviceSession.getDeviceId()).getModel(); diff --git a/src/main/java/org/traccar/protocol/MiniFinderProtocolEncoder.java b/src/main/java/org/traccar/protocol/MiniFinderProtocolEncoder.java index 36fb9fc2f..059f688f7 100644 --- a/src/main/java/org/traccar/protocol/MiniFinderProtocolEncoder.java +++ b/src/main/java/org/traccar/protocol/MiniFinderProtocolEncoder.java @@ -57,28 +57,28 @@ public class MiniFinderProtocolEncoder extends StringProtocolEncoder implements switch (command.getType()) { case Command.TYPE_SET_TIMEZONE: - return formatCommand(command, "{%s}L{%s}", this, Command.KEY_DEVICE_PASSWORD, Command.KEY_TIMEZONE); + return formatCommand(command, "%sL%s", this, Command.KEY_DEVICE_PASSWORD, Command.KEY_TIMEZONE); case Command.TYPE_VOICE_MONITORING: - return formatCommand(command, "{%s}P{%s}", this, Command.KEY_DEVICE_PASSWORD, Command.KEY_ENABLE); + return formatCommand(command, "%sP%s", this, Command.KEY_DEVICE_PASSWORD, Command.KEY_ENABLE); case Command.TYPE_ALARM_SPEED: - return formatCommand(command, "{%s}J1{%s}", Command.KEY_DEVICE_PASSWORD, Command.KEY_DATA); + return formatCommand(command, "%sJ1%s", Command.KEY_DEVICE_PASSWORD, Command.KEY_DATA); case Command.TYPE_ALARM_GEOFENCE: - return formatCommand(command, "{%s}R1{%s}", Command.KEY_DEVICE_PASSWORD, Command.KEY_RADIUS); + return formatCommand(command, "%sR1%s", Command.KEY_DEVICE_PASSWORD, Command.KEY_RADIUS); case Command.TYPE_ALARM_VIBRATION: - return formatCommand(command, "{%s}W1,{%s}", Command.KEY_DEVICE_PASSWORD, Command.KEY_DATA); + return formatCommand(command, "%sW1,%s", Command.KEY_DEVICE_PASSWORD, Command.KEY_DATA); case Command.TYPE_SET_AGPS: - return formatCommand(command, "{%s}AGPS{%s}", this, Command.KEY_DEVICE_PASSWORD, Command.KEY_ENABLE); + return formatCommand(command, "%sAGPS%s", this, Command.KEY_DEVICE_PASSWORD, Command.KEY_ENABLE); case Command.TYPE_ALARM_FALL: - return formatCommand(command, "{%s}F{%s}", this, Command.KEY_DEVICE_PASSWORD, Command.KEY_ENABLE); + return formatCommand(command, "%sF%s", this, Command.KEY_DEVICE_PASSWORD, Command.KEY_ENABLE); case Command.TYPE_MODE_POWER_SAVING: - return formatCommand(command, "{%s}SP{%s}", this, Command.KEY_DEVICE_PASSWORD, Command.KEY_ENABLE); + return formatCommand(command, "%sSP%s", this, Command.KEY_DEVICE_PASSWORD, Command.KEY_ENABLE); case Command.TYPE_MODE_DEEP_SLEEP: - return formatCommand(command, "{%s}DS{%s}", this, Command.KEY_DEVICE_PASSWORD, Command.KEY_ENABLE); + return formatCommand(command, "%sDS%s", this, Command.KEY_DEVICE_PASSWORD, Command.KEY_ENABLE); case Command.TYPE_SOS_NUMBER: - return formatCommand(command, "{%s}{%s}1,{%s}", this, + return formatCommand(command, "%s%s1,%s", this, Command.KEY_DEVICE_PASSWORD, Command.KEY_INDEX, Command.KEY_PHONE); case Command.TYPE_SET_INDICATOR: - return formatCommand(command, "{%s}LED{%s}", Command.KEY_DEVICE_PASSWORD, Command.KEY_DATA); + return formatCommand(command, "%sLED%s", Command.KEY_DEVICE_PASSWORD, Command.KEY_DATA); default: return null; } diff --git a/src/main/java/org/traccar/protocol/Minifinder2ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Minifinder2ProtocolDecoder.java index 75c8b11d3..b8ab134c5 100644 --- a/src/main/java/org/traccar/protocol/Minifinder2ProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/Minifinder2ProtocolDecoder.java @@ -117,6 +117,10 @@ public class Minifinder2ProtocolDecoder extends BaseProtocolDecoder { case 0x02: position.set(Position.KEY_ALARM, decodeAlarm(buf.readIntLE())); break; + case 0x14: + position.set(Position.KEY_BATTERY_LEVEL, buf.readUnsignedByte()); + position.set(Position.KEY_BATTERY, buf.readUnsignedShortLE() * 0.001); + break; case 0x20: position.setLatitude(buf.readIntLE() * 0.0000001); position.setLongitude(buf.readIntLE() * 0.0000001); @@ -150,9 +154,20 @@ public class Minifinder2ProtocolDecoder extends BaseProtocolDecoder { mac.substring(0, mac.length() - 1), rssi)); } break; + case 0x23: + if (endIndex > buf.readerIndex()) { + buf.skipBytes(6); // mac + } + if (endIndex > buf.readerIndex()) { + position.setLatitude(buf.readIntLE() * 0.0000001); + position.setLongitude(buf.readIntLE() * 0.0000001); + } + break; case 0x24: position.setTime(new Date(buf.readUnsignedIntLE() * 1000)); - position.set(Position.KEY_STATUS, buf.readUnsignedIntLE()); + long status = buf.readUnsignedIntLE(); + position.set(Position.KEY_BATTERY_LEVEL, BitUtil.from(status, 24)); + position.set(Position.KEY_STATUS, status); break; case 0x40: buf.readUnsignedIntLE(); // timestamp diff --git a/src/main/java/org/traccar/protocol/MotorProtocol.java b/src/main/java/org/traccar/protocol/MotorProtocol.java new file mode 100644 index 000000000..680687e15 --- /dev/null +++ b/src/main/java/org/traccar/protocol/MotorProtocol.java @@ -0,0 +1,37 @@ +/* + * Copyright 2019 Anton Tananaev (anton@traccar.org) + * + * 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.protocol; + +import io.netty.handler.codec.LineBasedFrameDecoder; +import io.netty.handler.codec.string.StringDecoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class MotorProtocol extends BaseProtocol { + + public MotorProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new LineBasedFrameDecoder(1024)); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new MotorProtocolDecoder(MotorProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/MotorProtocolDecoder.java b/src/main/java/org/traccar/protocol/MotorProtocolDecoder.java new file mode 100644 index 000000000..8ce4fe8b1 --- /dev/null +++ b/src/main/java/org/traccar/protocol/MotorProtocolDecoder.java @@ -0,0 +1,85 @@ +/* + * Copyright 2019 Anton Tananaev (anton@traccar.org) + * + * 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.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.Protocol; +import org.traccar.helper.BcdUtil; +import org.traccar.helper.BitUtil; +import org.traccar.helper.DataConverter; +import org.traccar.helper.DateBuilder; +import org.traccar.model.Position; + +import java.net.SocketAddress; + +public class MotorProtocolDecoder extends BaseProtocolDecoder { + + public MotorProtocolDecoder(Protocol protocol) { + super(protocol); + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + String sentence = (String) msg; + ByteBuf buf = Unpooled.wrappedBuffer(DataConverter.parseHex(sentence)); + + String id = String.format("%08x", buf.readUnsignedIntLE()); + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, id); + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + buf.skipBytes(2); // divider + + position.set(Position.KEY_STATUS, buf.readUnsignedShortLE()); + + buf.skipBytes(2); // divider + buf.readUnsignedMediumLE(); // command + + int flags = buf.readUnsignedByte(); + position.setValid(BitUtil.check(flags, 7)); + if (BitUtil.check(flags, 0)) { + position.set(Position.KEY_ALARM, Position.ALARM_GENERAL); + } + + position.setLatitude(BcdUtil.readInteger(buf, 2) + BcdUtil.readInteger(buf, 6) * 0.0001 / 60); + position.setLongitude(BcdUtil.readInteger(buf, 4) + BcdUtil.readInteger(buf, 6) * 0.0001 / 60); + position.setSpeed(BcdUtil.readInteger(buf, 4) * 0.1); + position.setCourse(BcdUtil.readInteger(buf, 4) * 0.1); + + position.setTime(new DateBuilder() + .setYear(BcdUtil.readInteger(buf, 2)) + .setMonth(BcdUtil.readInteger(buf, 2)) + .setDay(BcdUtil.readInteger(buf, 2)) + .setHour(BcdUtil.readInteger(buf, 2)) + .setMinute(BcdUtil.readInteger(buf, 2)) + .setSecond(BcdUtil.readInteger(buf, 2)).getDate()); + + position.set(Position.KEY_RSSI, BcdUtil.readInteger(buf, 2)); + + return position; + } + +} diff --git a/src/main/java/org/traccar/protocol/OmnicommFrameDecoder.java b/src/main/java/org/traccar/protocol/OmnicommFrameDecoder.java new file mode 100644 index 000000000..314f19757 --- /dev/null +++ b/src/main/java/org/traccar/protocol/OmnicommFrameDecoder.java @@ -0,0 +1,58 @@ +/* + * Copyright 2019 Anton Tananaev (anton@traccar.org) + * + * 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.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import org.traccar.BaseFrameDecoder; + +public class OmnicommFrameDecoder extends BaseFrameDecoder { + + @Override + protected Object decode( + ChannelHandlerContext ctx, Channel channel, ByteBuf buf) throws Exception { + + if (buf.readableBytes() < 6) { + return null; + } + + int endIndex = buf.getUnsignedShortLE(buf.readerIndex() + 2) + buf.readerIndex() + 6; + if (buf.writerIndex() < endIndex) { + return null; + } + + ByteBuf result = Unpooled.buffer(); + result.writeByte(buf.readUnsignedByte()); + while (buf.readerIndex() < endIndex) { + int b = buf.readUnsignedByte(); + if (b == 0xDB) { + int ext = buf.readUnsignedByte(); + if (ext == 0xDC) { + result.writeByte(0xC0); + } else if (ext == 0xDD) { + result.writeByte(0xDB); + } + endIndex += 1; + } else { + result.writeByte(b); + } + } + return result; + } + +} diff --git a/src/main/java/org/traccar/protocol/OmnicommProtocol.java b/src/main/java/org/traccar/protocol/OmnicommProtocol.java new file mode 100644 index 000000000..96660cb59 --- /dev/null +++ b/src/main/java/org/traccar/protocol/OmnicommProtocol.java @@ -0,0 +1,34 @@ +/* + * Copyright 2019 Anton Tananaev (anton@traccar.org) + * + * 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.protocol; + +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class OmnicommProtocol extends BaseProtocol { + + public OmnicommProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new OmnicommFrameDecoder()); + pipeline.addLast(new OmnicommProtocolDecoder(OmnicommProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/OmnicommProtocolDecoder.java b/src/main/java/org/traccar/protocol/OmnicommProtocolDecoder.java new file mode 100644 index 000000000..cd8b74c9a --- /dev/null +++ b/src/main/java/org/traccar/protocol/OmnicommProtocolDecoder.java @@ -0,0 +1,142 @@ +/* + * Copyright 2019 Anton Tananaev (anton@traccar.org) + * + * 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.protocol; + +import com.google.protobuf.InvalidProtocolBufferException; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufUtil; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.NetworkMessage; +import org.traccar.Protocol; +import org.traccar.helper.Checksum; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.Position; +import org.traccar.protobuf.OmnicommMessageOuterClass; + +import java.net.SocketAddress; +import java.util.Date; +import java.util.LinkedList; +import java.util.List; + +public class OmnicommProtocolDecoder extends BaseProtocolDecoder { + + public OmnicommProtocolDecoder(Protocol protocol) { + super(protocol); + } + + public static final int MSG_IDENTIFICATION = 0x80; + public static final int MSG_ARCHIVE_INQUIRY = 0x85; + public static final int MSG_ARCHIVE_DATA = 0x86; + public static final int MSG_REMOVE_ARCHIVE_INQUIRY = 0x87; + + private OmnicommMessageOuterClass.OmnicommMessage parseProto( + ByteBuf buf, int length) throws InvalidProtocolBufferException { + + final byte[] array; + final int offset; + if (buf.hasArray()) { + array = buf.array(); + offset = buf.arrayOffset() + buf.readerIndex(); + } else { + array = ByteBufUtil.getBytes(buf, buf.readerIndex(), length, false); + offset = 0; + } + buf.skipBytes(length); + + return OmnicommMessageOuterClass.OmnicommMessage + .getDefaultInstance().getParserForType().parseFrom(array, offset, length); + } + + private void sendResponse(Channel channel, int type, long index) { + if (channel != null) { + ByteBuf response = Unpooled.buffer(); + response.writeByte(0xC0); + response.writeByte(type); + response.writeShortLE(4); + response.writeIntLE((int) index); + response.writeShortLE(Checksum.crc16(Checksum.CRC16_CCITT_FALSE, + response.nioBuffer(1, response.writerIndex() - 1))); + channel.writeAndFlush(new NetworkMessage(response, channel.remoteAddress())); + } + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + + buf.readUnsignedByte(); // prefix + int type = buf.readUnsignedByte(); + buf.readUnsignedShortLE(); // length + + if (type == MSG_IDENTIFICATION) { + + getDeviceSession(channel, remoteAddress, String.valueOf(buf.readUnsignedIntLE())); + sendResponse(channel, MSG_ARCHIVE_INQUIRY, 0); + + } else if (type == MSG_ARCHIVE_DATA) { + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress); + if (deviceSession == null) { + return null; + } + + long index = buf.readUnsignedIntLE(); + buf.readUnsignedIntLE(); // time + buf.readUnsignedByte(); // priority + + List<Position> positions = new LinkedList<>(); + + while (buf.readableBytes() > 2) { + + OmnicommMessageOuterClass.OmnicommMessage message = parseProto(buf, buf.readUnsignedShortLE()); + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + if (message.hasNAV()) { + OmnicommMessageOuterClass.OmnicommMessage.NAV nav = message.getNAV(); + position.setValid(true); + position.setTime(new Date((nav.getGPSTime() + 1230768000) * 1000L)); // from 2009-01-01 12:00 + position.setLatitude(nav.getLAT() * 0.0000001); + position.setLongitude(nav.getLON() * 0.0000001); + position.setSpeed(UnitsConverter.knotsFromKph(nav.getGPSVel() * 0.1)); + position.setCourse(nav.getGPSDir()); + position.setAltitude(nav.getGPSAlt() * 0.1); + position.set(Position.KEY_SATELLITES, nav.getGPSNSat()); + } + + if (position.getFixTime() != null) { + positions.add(position); + } + } + + if (positions.isEmpty()) { + sendResponse(channel, MSG_REMOVE_ARCHIVE_INQUIRY, index + 1); + return null; + } else { + return positions; + } + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/OutsafeProtocol.java b/src/main/java/org/traccar/protocol/OutsafeProtocol.java new file mode 100644 index 000000000..c728a404d --- /dev/null +++ b/src/main/java/org/traccar/protocol/OutsafeProtocol.java @@ -0,0 +1,39 @@ +/* + * Copyright 2019 Anton Tananaev (anton@traccar.org) + * + * 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.protocol; + +import io.netty.handler.codec.http.HttpObjectAggregator; +import io.netty.handler.codec.http.HttpRequestDecoder; +import io.netty.handler.codec.http.HttpResponseEncoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class OutsafeProtocol extends BaseProtocol { + + public OutsafeProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new HttpResponseEncoder()); + pipeline.addLast(new HttpRequestDecoder()); + pipeline.addLast(new HttpObjectAggregator(65535)); + pipeline.addLast(new OutsafeProtocolDecoder(OutsafeProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/OutsafeProtocolDecoder.java b/src/main/java/org/traccar/protocol/OutsafeProtocolDecoder.java new file mode 100644 index 000000000..5e95270e8 --- /dev/null +++ b/src/main/java/org/traccar/protocol/OutsafeProtocolDecoder.java @@ -0,0 +1,69 @@ +/* + * Copyright 2019 Anton Tananaev (anton@traccar.org) + * + * 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.protocol; + +import io.netty.channel.Channel; +import io.netty.handler.codec.http.FullHttpRequest; +import io.netty.handler.codec.http.HttpResponseStatus; +import org.traccar.BaseHttpProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.Protocol; +import org.traccar.model.Position; + +import javax.json.Json; +import javax.json.JsonObject; +import java.io.StringReader; +import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; +import java.util.Date; + +public class OutsafeProtocolDecoder extends BaseHttpProtocolDecoder { + + public OutsafeProtocolDecoder(Protocol protocol) { + super(protocol); + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + FullHttpRequest request = (FullHttpRequest) msg; + String content = request.content().toString(StandardCharsets.UTF_8); + JsonObject json = Json.createReader(new StringReader(content)).readObject(); + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, json.getString("device")); + if (deviceSession == null) { + sendResponse(channel, HttpResponseStatus.BAD_REQUEST); + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + position.setTime(new Date()); + position.setValid(true); + position.setLatitude(json.getJsonNumber("latitude").doubleValue()); + position.setLongitude(json.getJsonNumber("longitude").doubleValue()); + position.setAltitude(json.getJsonNumber("altitude").doubleValue()); + position.setCourse(json.getJsonNumber("heading").intValue()); + + position.set(Position.KEY_RSSI, json.getJsonNumber("rssi").intValue()); + + sendResponse(channel, HttpResponseStatus.OK); + return position; + } + +} diff --git a/src/main/java/org/traccar/protocol/PacificTrackProtocol.java b/src/main/java/org/traccar/protocol/PacificTrackProtocol.java new file mode 100644 index 000000000..08991ab64 --- /dev/null +++ b/src/main/java/org/traccar/protocol/PacificTrackProtocol.java @@ -0,0 +1,33 @@ +/* + * Copyright 2019 Anton Tananaev (anton@traccar.org) + * + * 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.protocol; + +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class PacificTrackProtocol extends BaseProtocol { + + public PacificTrackProtocol() { + addServer(new TrackerServer(true, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new PacificTrackProtocolDecoder(PacificTrackProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/PacificTrackProtocolDecoder.java b/src/main/java/org/traccar/protocol/PacificTrackProtocolDecoder.java new file mode 100644 index 000000000..d49a73a86 --- /dev/null +++ b/src/main/java/org/traccar/protocol/PacificTrackProtocolDecoder.java @@ -0,0 +1,152 @@ +/* + * Copyright 2019 - 2020 Anton Tananaev (anton@traccar.org) + * + * 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.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufUtil; +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.Protocol; +import org.traccar.helper.BitUtil; +import org.traccar.helper.DateBuilder; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.Position; + +import java.net.SocketAddress; + +public class PacificTrackProtocolDecoder extends BaseProtocolDecoder { + + public PacificTrackProtocolDecoder(Protocol protocol) { + super(protocol); + } + + public static int readBitExt(ByteBuf buf) { + int result = 0; + while (buf.isReadable()) { + int b = buf.readUnsignedByte(); + result <<= 7; + result += BitUtil.to(b, 7); + if (BitUtil.check(b, 7)) { + break; + } + } + return result; + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + + buf.readByte(); // frame start + readBitExt(buf); // frame control + readBitExt(buf); // frame length + + DeviceSession deviceSession = null; + Position position = new Position(getProtocolName()); + + while (buf.isReadable()) { + + int segmentId = readBitExt(buf); + int segmentEnd = readBitExt(buf) + buf.readerIndex(); + + switch (segmentId) { + case 0x01: + position.set(Position.KEY_EVENT, readBitExt(buf)); + break; + case 0x10: + position.setValid(BitUtil.check(buf.readUnsignedByte(), 4)); + int date = buf.readUnsignedByte(); + DateBuilder dateBuilder = new DateBuilder() + .setDate(2010 + BitUtil.from(date, 4), BitUtil.to(date, 4), buf.readUnsignedByte()) + .setTime(buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte()); + position.setTime(dateBuilder.getDate()); + position.setLatitude(buf.readUnsignedInt() / 1000000.0 - 90.0); + position.setLongitude(buf.readUnsignedInt() / 1000000.0 - 180.0); + int speedAndCourse = buf.readUnsignedMedium(); + position.setCourse(BitUtil.from(speedAndCourse, 12)); + position.setSpeed(UnitsConverter.knotsFromKph(BitUtil.to(speedAndCourse, 12) * 0.1)); + position.set(Position.KEY_INDEX, buf.readUnsignedShort()); + break; + case 0x92: + while (buf.readerIndex() < segmentEnd) { + int field = buf.readUnsignedByte(); + int fieldPrefix = BitUtil.from(field, 5); + if (fieldPrefix < 0b100) { + switch (BitUtil.between(field, 2, 5)) { + case 0b000: + position.set("bus", BitUtil.to(field, 2)); + case 0b001: + position.set("currentGear", BitUtil.to(field, 2)); + break; + default: + break; + } + } else if (fieldPrefix < 0b101) { + switch (BitUtil.to(field, 5)) { + case 0b00000: + position.set(Position.KEY_OBD_SPEED, buf.readUnsignedByte()); + break; + case 0b00001: + position.set(Position.KEY_RPM, buf.readUnsignedByte() * 32); + break; + default: + buf.readUnsignedByte(); + break; + } + } else if (fieldPrefix < 0b110) { + buf.readUnsignedShort(); + } else if (fieldPrefix < 0b111) { + switch (BitUtil.to(field, 5)) { + case 0b00000: + position.set(Position.KEY_ODOMETER, buf.readUnsignedInt() * 100); + break; + case 0b00001: + position.set(Position.KEY_HOURS, buf.readUnsignedInt() * 180); + break; + case 0b00010: + position.set("idleHours", buf.readUnsignedInt() * 180); + break; + default: + buf.readUnsignedInt(); + break; + } + } else { + buf.skipBytes(buf.readUnsignedByte()); + } + } + break; + case 0x100: + String imei = ByteBufUtil.hexDump(buf.readSlice(8)).substring(0, 15); + deviceSession = getDeviceSession(channel, remoteAddress, imei); + break; + default: + buf.readerIndex(segmentEnd); + break; + } + } + + if (deviceSession != null) { + position.setDeviceId(deviceSession.getDeviceId()); + return position; + } else { + return null; + } + } + +} diff --git a/src/main/java/org/traccar/protocol/PluginProtocolDecoder.java b/src/main/java/org/traccar/protocol/PluginProtocolDecoder.java index 106889ee0..949c994ee 100644 --- a/src/main/java/org/traccar/protocol/PluginProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/PluginProtocolDecoder.java @@ -19,6 +19,7 @@ import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; import org.traccar.DeviceSession; import org.traccar.Protocol; +import org.traccar.helper.BitUtil; import org.traccar.helper.Parser; import org.traccar.helper.PatternBuilder; import org.traccar.helper.UnitsConverter; @@ -40,17 +41,30 @@ public class PluginProtocolDecoder extends BaseProtocolDecoder { .number("(dd)(dd)(dd),") // time (hhmmss) .number("(-?d+.d+),") // longitude .number("(-?d+.d+),") // latitude - .number("(d+),") // speed + .number("(d+.?d*),") // speed .number("(d+),") // course .number("(-?d+),") // altitude .number("(-?d+),") // satellites .number("d+,") // type - .number("(d+),") // odometer + .number("(d+.?d*),") // odometer .number("(d+),") // status - .expression("[^,]*,") + .number("(d+.?d*),") // fuel .expression("[^,]*,") .text("0") .groupBegin() + .number(",(-?d+.?d*)") // temperature 1 + .number(",(-?d+.?d*)") // temperature 2 + .number(",d+") // oil level + .number(",(d+)") // rpm + .number(",(d+)") // obd speed + .number(",d+") // people up + .number(",d+") // people down + .number(",d+") // obd status + .number(",d+") // fuel intake air temperature + .number(",(d+)") // throttle + .number(",(d+)") // battery + .groupEnd("?") + .groupBegin() .text(",+,") .number("(d+),") // event .groupEnd("?") @@ -74,18 +88,50 @@ public class PluginProtocolDecoder extends BaseProtocolDecoder { Position position = new Position(getProtocolName()); position.setDeviceId(deviceSession.getDeviceId()); - position.setValid(true); position.setTime(parser.nextDateTime()); position.setLongitude(parser.nextDouble()); position.setLatitude(parser.nextDouble()); - position.setSpeed(UnitsConverter.knotsFromKph(parser.nextInt())); + position.setSpeed(UnitsConverter.knotsFromKph(parser.nextDouble())); position.setCourse(parser.nextInt()); position.setAltitude(parser.nextInt()); position.set(Position.KEY_SATELLITES, parser.nextInt()); - position.set(Position.KEY_ODOMETER, parser.nextInt()); - position.set(Position.KEY_STATUS, parser.nextInt()); - position.set(Position.KEY_EVENT, parser.nextInt()); + position.set(Position.KEY_ODOMETER, (long) (parser.nextDouble() * 1000)); + + int status = parser.nextInt(); + position.setValid(BitUtil.check(status, 0)); + position.set(Position.KEY_IGNITION, BitUtil.check(status, 1)); + for (int i = 0; i < 4; i++) { + position.set(Position.PREFIX_IN + (i + 1), BitUtil.check(status, 20 + i)); + } + position.set(Position.KEY_STATUS, status); + + position.set(Position.KEY_FUEL_LEVEL, parser.nextDouble()); + + if (parser.hasNext(6)) { + position.set(Position.PREFIX_TEMP + 1, parser.nextDouble()); + position.set(Position.PREFIX_TEMP + 2, parser.nextDouble()); + position.set(Position.KEY_RPM, parser.nextInt()); + position.set(Position.KEY_OBD_SPEED, parser.nextInt()); + position.set(Position.KEY_THROTTLE, parser.nextInt() * 0.1); + position.set(Position.KEY_POWER, parser.nextInt() * 0.1); + } + + if (parser.hasNext()) { + int event = parser.nextInt(); + switch (event) { + case 11317: + position.set(Position.KEY_ALARM, Position.ALARM_ACCELERATION); + break; + case 11319: + position.set(Position.KEY_ALARM, Position.ALARM_BRAKING); + break; + default: + break; + + } + position.set(Position.KEY_EVENT, event); + } return position; } diff --git a/src/main/java/org/traccar/protocol/PstFrameDecoder.java b/src/main/java/org/traccar/protocol/PstFrameDecoder.java new file mode 100644 index 000000000..dbafd1476 --- /dev/null +++ b/src/main/java/org/traccar/protocol/PstFrameDecoder.java @@ -0,0 +1,51 @@ +/* + * Copyright 2019 Anton Tananaev (anton@traccar.org) + * + * 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.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import org.traccar.BaseFrameDecoder; + +public class PstFrameDecoder extends BaseFrameDecoder { + + @Override + protected Object decode( + ChannelHandlerContext ctx, Channel channel, ByteBuf buf) throws Exception { + + while (buf.isReadable() && buf.getByte(buf.readerIndex()) == 0x28) { + buf.skipBytes(1); + } + + int endIndex = buf.indexOf(buf.readerIndex(), buf.writerIndex(), (byte) 0x29); + if (endIndex > 0) { + ByteBuf result = Unpooled.buffer(endIndex - buf.readerIndex()); + while (buf.readerIndex() < endIndex) { + int b = buf.readUnsignedByte(); + if (b == 0x27) { + b = buf.readUnsignedByte() ^ 0x40; + } + result.writeByte(b); + } + buf.skipBytes(1); + return result; + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/PstProtocol.java b/src/main/java/org/traccar/protocol/PstProtocol.java new file mode 100644 index 000000000..0ed9affd8 --- /dev/null +++ b/src/main/java/org/traccar/protocol/PstProtocol.java @@ -0,0 +1,40 @@ +/* + * Copyright 2019 Anton Tananaev (anton@traccar.org) + * + * 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.protocol; + +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class PstProtocol extends BaseProtocol { + + public PstProtocol() { + addServer(new TrackerServer(true, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new PstProtocolDecoder(PstProtocol.this)); + } + }); + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new PstFrameDecoder()); + pipeline.addLast(new PstProtocolDecoder(PstProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/PstProtocolDecoder.java b/src/main/java/org/traccar/protocol/PstProtocolDecoder.java new file mode 100644 index 000000000..23e2afbbd --- /dev/null +++ b/src/main/java/org/traccar/protocol/PstProtocolDecoder.java @@ -0,0 +1,116 @@ +/* + * Copyright 2019 Anton Tananaev (anton@traccar.org) + * + * 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.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.Protocol; +import org.traccar.helper.BitUtil; +import org.traccar.helper.DateBuilder; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.util.Date; + +public class PstProtocolDecoder extends BaseProtocolDecoder { + + public PstProtocolDecoder(Protocol protocol) { + super(protocol); + } + + public static final int MSG_STATUS = 0x05; + + private Date readDate(ByteBuf buf) { + long value = buf.readUnsignedInt(); + return new DateBuilder() + .setYear((int) BitUtil.between(value, 26, 32)) + .setMonth((int) BitUtil.between(value, 22, 26)) + .setDay((int) BitUtil.between(value, 17, 22)) + .setHour((int) BitUtil.between(value, 12, 17)) + .setMinute((int) BitUtil.between(value, 6, 12)) + .setSecond((int) BitUtil.between(value, 0, 6)).getDate(); + } + + private double readCoordinate(ByteBuf buf) { + long value = buf.readUnsignedInt(); + int sign = BitUtil.check(value, 31) ? -1 : 1; + value = BitUtil.to(value, 31); + return sign * (BitUtil.from(value, 16) + BitUtil.to(value, 16) * 0.00001) / 60; + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + + String id = String.valueOf(buf.readUnsignedInt()); + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, id); + if (deviceSession == null) { + return null; + } + + buf.readUnsignedByte(); // version + buf.readUnsignedInt(); // index + + int type = buf.readUnsignedByte(); + + if (type == MSG_STATUS) { + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + position.setDeviceTime(readDate(buf)); + + buf.readUnsignedByte(); + + int count = buf.readUnsignedByte(); + for (int i = 0; i < count; i++) { + + int tag = buf.readUnsignedByte(); + int length = buf.readUnsignedByte(); + + switch (tag) { + case 0x0D: + int battery = buf.readUnsignedByte(); + if (battery <= 20) { + position.set(Position.KEY_BATTERY_LEVEL, buf.readUnsignedByte() * 5); + } + break; + case 0x10: + position.setFixTime(readDate(buf)); + position.setLatitude(readCoordinate(buf)); + position.setLongitude(readCoordinate(buf)); + position.setSpeed(buf.readUnsignedByte()); + position.setCourse(buf.readUnsignedByte() * 2); + position.setAltitude(buf.readShort()); + buf.readUnsignedInt(); // gps condition + break; + default: + buf.skipBytes(length); + break; + } + } + + return position.getFixTime() != null ? position : null; + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/Pt215Protocol.java b/src/main/java/org/traccar/protocol/Pt215Protocol.java new file mode 100644 index 000000000..09bd9b121 --- /dev/null +++ b/src/main/java/org/traccar/protocol/Pt215Protocol.java @@ -0,0 +1,35 @@ +/* + * Copyright 2019 Anton Tananaev (anton@traccar.org) + * + * 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.protocol; + +import io.netty.handler.codec.LengthFieldBasedFrameDecoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class Pt215Protocol extends BaseProtocol { + + public Pt215Protocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new LengthFieldBasedFrameDecoder(1024, 2, 1, -1, 0)); + pipeline.addLast(new Pt215ProtocolDecoder(Pt215Protocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/Pt215ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Pt215ProtocolDecoder.java new file mode 100644 index 000000000..48ce7dede --- /dev/null +++ b/src/main/java/org/traccar/protocol/Pt215ProtocolDecoder.java @@ -0,0 +1,118 @@ +/* + * Copyright 2019 Anton Tananaev (anton@traccar.org) + * + * 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.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufUtil; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.NetworkMessage; +import org.traccar.Protocol; +import org.traccar.helper.BitUtil; +import org.traccar.helper.DateBuilder; +import org.traccar.model.Position; + +import java.net.SocketAddress; + +public class Pt215ProtocolDecoder extends BaseProtocolDecoder { + + public Pt215ProtocolDecoder(Protocol protocol) { + super(protocol); + } + + public static final int MSG_LOGIN = 0x01; + public static final int MSG_HEARTBEAT = 0x08; + public static final int MSG_GPS_REALTIME = 0x10; + public static final int MSG_GPS_OFFLINE = 0x11; + public static final int MSG_STATUS = 0x13; + + private void sendResponse( + Channel channel, SocketAddress remoteAddress, int type, ByteBuf content) { + if (channel != null) { + ByteBuf response = Unpooled.buffer(); + response.writeByte('X'); + response.writeByte('X'); + response.writeByte(content != null ? 1 + content.readableBytes() : 1); + response.writeByte(type); + if (content != null) { + response.writeBytes(content); + content.release(); + } + response.writeByte('\r'); + response.writeByte('\n'); + channel.writeAndFlush(new NetworkMessage(response, remoteAddress)); + } + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + + buf.skipBytes(2); // header + buf.readUnsignedByte(); // length + int type = buf.readUnsignedByte(); + + if (type == MSG_LOGIN) { + + getDeviceSession(channel, remoteAddress, ByteBufUtil.hexDump(buf.readSlice(8)).substring(1)); + sendResponse(channel, remoteAddress, type, null); + + } else if (type == MSG_GPS_OFFLINE || type == MSG_GPS_REALTIME) { + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress); + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + sendResponse(channel, remoteAddress, type, buf.retainedSlice(buf.readerIndex(), 6)); + + DateBuilder dateBuilder = new DateBuilder() + .setDate(buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte()) + .setTime(buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte()); + position.setTime(dateBuilder.getDate()); + + double latitude = buf.readUnsignedInt() / 60.0 / 30000.0; + double longitude = buf.readUnsignedInt() / 60.0 / 30000.0; + + int flags = buf.readUnsignedShort(); + position.setCourse(BitUtil.to(flags, 10)); + position.setValid(BitUtil.check(flags, 12)); + + if (!BitUtil.check(flags, 10)) { + latitude = -latitude; + } + if (BitUtil.check(flags, 11)) { + longitude = -longitude; + } + + position.setLatitude(latitude); + position.setLongitude(longitude); + + return position; + + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/Pt502ProtocolEncoder.java b/src/main/java/org/traccar/protocol/Pt502ProtocolEncoder.java index ba08b16ae..364ecae86 100644 --- a/src/main/java/org/traccar/protocol/Pt502ProtocolEncoder.java +++ b/src/main/java/org/traccar/protocol/Pt502ProtocolEncoder.java @@ -46,13 +46,13 @@ public class Pt502ProtocolEncoder extends StringProtocolEncoder implements Strin switch (command.getType()) { case Command.TYPE_CUSTOM: - return formatCommand(command, "{%s}\r\n", Command.KEY_DATA); + return formatCommand(command, "%s\r\n", Command.KEY_DATA); case Command.TYPE_OUTPUT_CONTROL: - return formatCommand(command, "#OPC{%s},{%s}\r\n", Command.KEY_INDEX, Command.KEY_DATA); + return formatCommand(command, "#OPC%s,%s\r\n", Command.KEY_INDEX, Command.KEY_DATA); case Command.TYPE_SET_TIMEZONE: - return formatCommand(command, "#TMZ{%s}\r\n", Command.KEY_TIMEZONE); + return formatCommand(command, "#TMZ%s\r\n", Command.KEY_TIMEZONE); case Command.TYPE_ALARM_SPEED: - return formatCommand(command, "#SPD{%s}\r\n", Command.KEY_DATA); + return formatCommand(command, "#SPD%s\r\n", Command.KEY_DATA); case Command.TYPE_REQUEST_PHOTO: return formatCommand(command, "#PHO\r\n"); default: diff --git a/src/main/java/org/traccar/protocol/RaceDynamicsProtocol.java b/src/main/java/org/traccar/protocol/RaceDynamicsProtocol.java new file mode 100644 index 000000000..c9db10610 --- /dev/null +++ b/src/main/java/org/traccar/protocol/RaceDynamicsProtocol.java @@ -0,0 +1,39 @@ +/* + * Copyright 2019 Anton Tananaev (anton@traccar.org) + * + * 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.protocol; + +import io.netty.handler.codec.LineBasedFrameDecoder; +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class RaceDynamicsProtocol extends BaseProtocol { + + public RaceDynamicsProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new LineBasedFrameDecoder(1500)); + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new RaceDynamicsProtocolDecoder(RaceDynamicsProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/RaceDynamicsProtocolDecoder.java b/src/main/java/org/traccar/protocol/RaceDynamicsProtocolDecoder.java new file mode 100644 index 000000000..f441bf8ed --- /dev/null +++ b/src/main/java/org/traccar/protocol/RaceDynamicsProtocolDecoder.java @@ -0,0 +1,171 @@ +/* + * Copyright 2019 Anton Tananaev (anton@traccar.org) + * + * 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.protocol; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.NetworkMessage; +import org.traccar.Protocol; +import org.traccar.helper.DateBuilder; +import org.traccar.helper.Parser; +import org.traccar.helper.PatternBuilder; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.util.Date; +import java.util.LinkedList; +import java.util.List; +import java.util.regex.Pattern; + +public class RaceDynamicsProtocolDecoder extends BaseProtocolDecoder { + + public RaceDynamicsProtocolDecoder(Protocol protocol) { + super(protocol); + } + + public static final int MSG_LOGIN = 12; + public static final int MSG_LOCATION = 15; + + private static final Pattern PATTERN_LOGIN = new PatternBuilder() + .text("$GPRMC,") + .number("d+,") // type + .number("d{6},") // date + .number("d{6},") // time + .number("(d{15}),") + .compile(); + + private static final Pattern PATTERN_LOCATION = new PatternBuilder() + .number("(dd)(dd)(dd),") // time (hhmmss) + .expression("([AV]),") // validity + .number("(dd)(dd.d+),") // latitude + .expression("([NS]),") + .number("(ddd)(dd.d+),") // longitude + .expression("([EW]),") + .number("(d+),") // speed + .number("(dd)(dd)(dd),") // date (ddmmyy) + .number("(-?d+),") // altitude + .number("(d+),") // satellites + .number("([01]),") // ignition + .number("(d+),") // index + .text("%,") + .number("([^,]+),") // ibutton + .number("d+,") // acceleration + .number("d+,") // deceleration + .number("[01],") // cruise control + .number("[01],") // seat belt + .number("[01],") // wrong ibutton + .number("(d+),") // power + .number("[01],") // power status + .number("(d+),") // battery + .number("([01]),") // panic + .number("d+,") + .number("d+,") + .number("(d),") // overspeed + .number("d+,") // speed limit + .number("d+,") // tachometer + .number("d+,d+,d+,") // aux + .number("d+,") // geofence id + .number("d+,") // road speed type + .number("d+,") // ibutton count + .number("(d),") // overdriver alert + .any() + .compile(); + + private String imei; + + private void sendResponse(Channel channel, SocketAddress remoteAddress, int type) { + if (channel != null) { + String response = String.format( + "$GPRMC,%1$d,%2$td%2$tm%2$ty,%2$tH%2$tM%2$tS,%3$s,\r\n", type, new Date(), imei); + channel.writeAndFlush(new NetworkMessage(response, remoteAddress)); + } + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + String sentence = (String) msg; + + int type = Integer.parseInt(sentence.substring(7, 9)); + + if (type == MSG_LOGIN) { + + Parser parser = new Parser(PATTERN_LOGIN, sentence); + if (parser.matches()) { + imei = parser.next(); + getDeviceSession(channel, remoteAddress, imei); + sendResponse(channel, remoteAddress, type); + } + + } else if (type == MSG_LOCATION) { + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress); + if (deviceSession == null) { + return null; + } + + List<Position> positions = new LinkedList<>(); + + for (String data : sentence.substring(17, sentence.length() - 3).split(",#,#,")) { + Parser parser = new Parser(PATTERN_LOCATION, data); + if (parser.matches()) { + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + DateBuilder dateBuilder = new DateBuilder() + .setTime(parser.nextInt(), parser.nextInt(), parser.nextInt()); + + position.setValid(parser.next().equals("A")); + position.setLatitude(parser.nextCoordinate()); + position.setLongitude(parser.nextCoordinate()); + position.setSpeed(parser.nextDouble()); + + dateBuilder.setDateReverse(parser.nextInt(), parser.nextInt(), parser.nextInt()); + position.setTime(dateBuilder.getDate()); + + position.setAltitude(parser.nextInt()); + position.set(Position.KEY_SATELLITES, parser.nextInt()); + position.set(Position.KEY_IGNITION, parser.nextInt() == 1); + position.set(Position.KEY_INDEX, parser.nextInt()); + position.set(Position.KEY_DRIVER_UNIQUE_ID, parser.next()); + position.set(Position.KEY_POWER, parser.nextInt() * 0.01); + position.set(Position.KEY_BATTERY, parser.nextInt() * 0.01); + position.set(Position.KEY_ALARM, parser.nextInt() > 0 ? Position.ALARM_SOS : null); + position.set(Position.KEY_ALARM, parser.nextInt() > 0 ? Position.ALARM_OVERSPEED : null); + + int overDriver = parser.nextInt(); + if (overDriver > 0) { + position.set("overDriver", overDriver); + } + + positions.add(position); + + } + } + + sendResponse(channel, remoteAddress, type); + + return positions; + + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/RstProtocol.java b/src/main/java/org/traccar/protocol/RstProtocol.java new file mode 100644 index 000000000..10d11d493 --- /dev/null +++ b/src/main/java/org/traccar/protocol/RstProtocol.java @@ -0,0 +1,37 @@ +/* + * Copyright 2019 Anton Tananaev (anton@traccar.org) + * + * 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.protocol; + +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class RstProtocol extends BaseProtocol { + + public RstProtocol() { + addServer(new TrackerServer(true, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new RstProtocolDecoder(RstProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/RstProtocolDecoder.java b/src/main/java/org/traccar/protocol/RstProtocolDecoder.java new file mode 100644 index 000000000..05601ed51 --- /dev/null +++ b/src/main/java/org/traccar/protocol/RstProtocolDecoder.java @@ -0,0 +1,132 @@ +/* + * Copyright 2019 - 2020 Anton Tananaev (anton@traccar.org) + * + * 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.protocol; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.NetworkMessage; +import org.traccar.Protocol; +import org.traccar.helper.BitUtil; +import org.traccar.helper.Parser; +import org.traccar.helper.PatternBuilder; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.util.regex.Pattern; + +public class RstProtocolDecoder extends BaseProtocolDecoder { + + public RstProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN = new PatternBuilder() + .text("RST;") + .expression("([AL]);") // archive + .expression("([^,]+);") // model + .expression("(.{5});") // firmware + .number("(d{9});") // serial number + .number("(d+);") // index + .number("(d+);") // type + .number("(dd)-(dd)-(dddd) ") // event date + .number("(dd):(dd):(dd);") // event time + .number("(dd)-(dd)-(dddd) ") // fix date + .number("(dd):(dd):(dd);") // fix time + .number("(-?d+.d+);") // latitude + .number("(-?d+.d+);") // longitude + .number("(d+);") // speed + .number("(d+);") // course + .number("(-?d+);") // altitude + .number("([01]);") // valid + .number("(d+);") // satellites + .number("(d+);") // hdop + .number("(xx);") // inputs 1 + .number("(xx);") // inputs 2 + .number("(xx);") // inputs 3 + .number("(xx);") // outputs 1 + .number("(xx);") // outputs 2 + .number("(d+.d+);") // power + .number("(d+.d+);") // battery + .number("(d+);") // odometer + .number("(d+);") // rssi + .number("(xx);") // temperature + .number("x{4};") // sensors + .number("(xx);") // status 1 + .number("(xx);") // status 2 + .any() + .compile(); + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + Parser parser = new Parser(PATTERN, (String) msg); + if (!parser.matches()) { + return null; + } + + String archive = parser.next(); + String model = parser.next(); + String firmware = parser.next(); + String serial = parser.next(); + int index = parser.nextInt(); + parser.nextInt(); // type + + if (channel != null && archive.equals("A")) { + String response = "RST;A;" + model + ";" + firmware + ";" + serial + ";" + index + ";6;FIM;"; + channel.writeAndFlush(new NetworkMessage(response, remoteAddress)); + } + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, serial); + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + position.setDeviceTime(parser.nextDateTime(Parser.DateTimeFormat.DMY_HMS)); + position.setFixTime(parser.nextDateTime(Parser.DateTimeFormat.DMY_HMS)); + position.setLatitude(parser.nextDouble()); + position.setLongitude(parser.nextDouble()); + position.setSpeed(UnitsConverter.knotsFromKph(parser.nextInt())); + position.setCourse(parser.nextInt()); + position.setAltitude(parser.nextInt()); + position.setValid(parser.nextInt() > 0); + + position.set(Position.KEY_SATELLITES, parser.nextInt()); + position.set(Position.KEY_HDOP, parser.nextInt()); + position.set(Position.PREFIX_IN + 1, parser.nextHexInt()); + position.set(Position.PREFIX_IN + 2, parser.nextHexInt()); + position.set(Position.PREFIX_IN + 3, parser.nextHexInt()); + position.set(Position.PREFIX_OUT + 1, parser.nextHexInt()); + position.set(Position.PREFIX_OUT + 2, parser.nextHexInt()); + position.set(Position.KEY_POWER, parser.nextDouble()); + position.set(Position.KEY_BATTERY, parser.nextDouble()); + position.set(Position.KEY_ODOMETER, parser.nextInt()); + position.set(Position.KEY_RSSI, parser.nextInt()); + position.set(Position.PREFIX_TEMP + 1, (int) parser.nextHexInt().byteValue()); + + int status = (parser.nextHexInt() << 8) + parser.nextHexInt(); + position.set(Position.KEY_IGNITION, BitUtil.check(status, 7)); + position.set(Position.KEY_STATUS, status); + + return position; + } + +} diff --git a/src/main/java/org/traccar/protocol/RuptelaProtocolDecoder.java b/src/main/java/org/traccar/protocol/RuptelaProtocolDecoder.java index 2d476427d..227a9ac91 100644 --- a/src/main/java/org/traccar/protocol/RuptelaProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/RuptelaProtocolDecoder.java @@ -19,6 +19,7 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; +import org.traccar.Context; import org.traccar.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; @@ -34,6 +35,8 @@ import java.util.List; public class RuptelaProtocolDecoder extends BaseProtocolDecoder { + private ByteBuf photo; + public RuptelaProtocolDecoder(Protocol protocol) { super(protocol); } @@ -247,6 +250,43 @@ public class RuptelaProtocolDecoder extends BaseProtocolDecoder { return positions; + } else if (type == MSG_FILES) { + + int subtype = buf.readUnsignedByte(); + int source = buf.readUnsignedByte(); + + if (subtype == 2) { + ByteBuf filename = buf.readSlice(8); + int total = buf.readUnsignedShort(); + int current = buf.readUnsignedShort(); + if (photo == null) { + photo = Unpooled.buffer(); + } + photo.writeBytes(buf.readSlice(buf.readableBytes() - 2)); + if (current < total - 1) { + ByteBuf content = Unpooled.buffer(); + content.writeByte(subtype); + content.writeByte(source); + content.writeBytes(filename); + content.writeShort(current + 1); + ByteBuf response = RuptelaProtocolEncoder.encodeContent(type, content); + content.release(); + if (channel != null) { + channel.writeAndFlush(new NetworkMessage(response, remoteAddress)); + } + } else { + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + getLastLocation(position, null); + position.set(Position.KEY_IMAGE, Context.getMediaManager().writeFile(imei, photo, "jpg")); + photo.release(); + photo = null; + return position; + } + } + + return null; + } else { return decodeCommandResponse(deviceSession, type, buf); diff --git a/src/main/java/org/traccar/protocol/RuptelaProtocolEncoder.java b/src/main/java/org/traccar/protocol/RuptelaProtocolEncoder.java index 51967403d..fb0dcf690 100644 --- a/src/main/java/org/traccar/protocol/RuptelaProtocolEncoder.java +++ b/src/main/java/org/traccar/protocol/RuptelaProtocolEncoder.java @@ -19,6 +19,7 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import org.traccar.BaseProtocolEncoder; import org.traccar.helper.Checksum; +import org.traccar.helper.DataConverter; import org.traccar.model.Command; import org.traccar.Protocol; @@ -30,7 +31,7 @@ public class RuptelaProtocolEncoder extends BaseProtocolEncoder { super(protocol); } - private ByteBuf encodeContent(int type, ByteBuf content) { + public static ByteBuf encodeContent(int type, ByteBuf content) { ByteBuf buf = Unpooled.buffer(); @@ -49,8 +50,14 @@ public class RuptelaProtocolEncoder extends BaseProtocolEncoder { switch (command.getType()) { case Command.TYPE_CUSTOM: - content.writeBytes(command.getString(Command.KEY_DATA).getBytes(StandardCharsets.US_ASCII)); - return encodeContent(RuptelaProtocolDecoder.MSG_SMS_VIA_GPRS, content); + String data = command.getString(Command.KEY_DATA); + if (data.matches("(\\p{XDigit}{2})+")) { + content.writeBytes(DataConverter.parseHex(data)); + return content; + } else { + content.writeBytes(data.getBytes(StandardCharsets.US_ASCII)); + return encodeContent(RuptelaProtocolDecoder.MSG_SMS_VIA_GPRS, content); + } case Command.TYPE_REQUEST_PHOTO: content.writeByte(1); // sub-command content.writeByte(0); // source diff --git a/src/main/java/org/traccar/protocol/S168Protocol.java b/src/main/java/org/traccar/protocol/S168Protocol.java new file mode 100644 index 000000000..e78664c40 --- /dev/null +++ b/src/main/java/org/traccar/protocol/S168Protocol.java @@ -0,0 +1,39 @@ +/* + * Copyright 2019 Anton Tananaev (anton@traccar.org) + * + * 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.protocol; + +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.CharacterDelimiterFrameDecoder; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class S168Protocol extends BaseProtocol { + + public S168Protocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new CharacterDelimiterFrameDecoder(1024, '$')); + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new S168ProtocolDecoder(S168Protocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/S168ProtocolDecoder.java b/src/main/java/org/traccar/protocol/S168ProtocolDecoder.java new file mode 100644 index 000000000..71aff1a65 --- /dev/null +++ b/src/main/java/org/traccar/protocol/S168ProtocolDecoder.java @@ -0,0 +1,81 @@ +/* + * Copyright 2019 Anton Tananaev (anton@traccar.org) + * + * 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.protocol; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.Protocol; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.TimeZone; + +public class S168ProtocolDecoder extends BaseProtocolDecoder { + + public S168ProtocolDecoder(Protocol protocol) { + super(protocol); + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + String sentence = (String) msg; + String[] values = sentence.split("#"); + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, values[1]); + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + String content = values[4]; + String[] fragments = content.split(";"); + for (String fragment : fragments) { + + int dataIndex = fragment.indexOf(':'); + String type = fragment.substring(0, dataIndex); + values = fragment.substring(dataIndex + 1).split(","); + int index = 0; + + switch (type) { + case "GDATA": + position.setValid(values[index++].equals("A")); + position.set(Position.KEY_SATELLITES, Integer.parseInt(values[index++])); + DateFormat dateFormat = new SimpleDateFormat("yyMMddHHmmss"); + dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); + position.setTime(dateFormat.parse(values[index++])); + position.setLatitude(Double.parseDouble(values[index++])); + position.setLongitude(Double.parseDouble(values[index++])); + position.setSpeed(UnitsConverter.knotsFromKph(Double.parseDouble(values[index++]))); + position.setCourse(Integer.parseInt(values[index++])); + position.setAltitude(Integer.parseInt(values[index++])); + break; + default: + break; + } + } + + return position.getAttributes().containsKey(Position.KEY_SATELLITES) ? position : null; + } + +} diff --git a/src/main/java/org/traccar/protocol/SigfoxProtocolDecoder.java b/src/main/java/org/traccar/protocol/SigfoxProtocolDecoder.java index 7e0e3e110..304f61836 100644 --- a/src/main/java/org/traccar/protocol/SigfoxProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/SigfoxProtocolDecoder.java @@ -24,6 +24,7 @@ import io.netty.handler.codec.http.HttpResponseStatus; import org.traccar.BaseHttpProtocolDecoder; import org.traccar.DeviceSession; import org.traccar.Protocol; +import org.traccar.helper.BitUtil; import org.traccar.helper.DataConverter; import org.traccar.helper.UnitsConverter; import org.traccar.model.Network; @@ -31,7 +32,10 @@ import org.traccar.model.Position; import org.traccar.model.WifiAccessPoint; import javax.json.Json; +import javax.json.JsonNumber; import javax.json.JsonObject; +import javax.json.JsonString; +import javax.json.JsonValue; import java.io.StringReader; import java.net.SocketAddress; import java.net.URLDecoder; @@ -44,6 +48,30 @@ public class SigfoxProtocolDecoder extends BaseHttpProtocolDecoder { super(protocol); } + private int getJsonInt(JsonObject json, String key) { + JsonValue value = json.get(key); + if (value != null) { + if (value.getValueType() == JsonValue.ValueType.NUMBER) { + return ((JsonNumber) value).intValue(); + } else if (value.getValueType() == JsonValue.ValueType.STRING) { + return Integer.parseInt(((JsonString) value).getString()); + } + } + return 0; + } + + private double getJsonDouble(JsonObject json, String key) { + JsonValue value = json.get(key); + if (value != null) { + if (value.getValueType() == JsonValue.ValueType.NUMBER) { + return ((JsonNumber) value).doubleValue(); + } else if (value.getValueType() == JsonValue.ValueType.STRING) { + return Double.parseDouble(((JsonString) value).getString()); + } + } + return 0; + } + @Override protected Object decode( Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { @@ -64,71 +92,113 @@ public class SigfoxProtocolDecoder extends BaseHttpProtocolDecoder { Position position = new Position(getProtocolName()); position.setDeviceId(deviceSession.getDeviceId()); - position.setTime(new Date(json.getInt("time") * 1000L)); + if (json.containsKey("time")) { + position.setTime(new Date(getJsonInt(json, "time") * 1000L)); + } else { + position.setTime(new Date()); + } + + if (json.containsKey("location") + || json.containsKey("lat") && json.containsKey("lng") && !json.containsKey("data")) { - String data = json.getString(json.containsKey("data") ? "data" : "payload"); - ByteBuf buf = Unpooled.wrappedBuffer(DataConverter.parseHex(data)); - try { - int event = buf.readUnsignedByte(); - if (event >> 4 == 0) { + JsonObject location; + if (json.containsKey("location")) { + location = json.getJsonObject("location"); + } else { + location = json; + } - position.setValid(true); - position.setLatitude(buf.readIntLE() * 0.0000001); - position.setLongitude(buf.readIntLE() * 0.0000001); - position.setCourse(buf.readUnsignedByte() * 2); - position.setSpeed(UnitsConverter.knotsFromKph(buf.readUnsignedByte())); + position.setValid(true); + position.setLatitude(getJsonDouble(location, "lat")); + position.setLongitude(getJsonDouble(location, "lng")); - position.set(Position.KEY_BATTERY, buf.readUnsignedByte() * 0.025); + } else { - } else { + String data = json.getString(json.containsKey("data") ? "data" : "payload"); + ByteBuf buf = Unpooled.wrappedBuffer(DataConverter.parseHex(data)); + try { + int event = buf.readUnsignedByte(); + if (event == 0x0f || event == 0x1f) { - position.set(Position.KEY_EVENT, event); - - while (buf.isReadable()) { - int type = buf.readUnsignedByte(); - switch (type) { - case 0x01: - position.setValid(true); - position.setLatitude(buf.readMedium()); - position.setLongitude(buf.readMedium()); - break; - case 0x02: - position.setValid(true); - position.setLatitude(buf.readFloat()); - position.setLongitude(buf.readFloat()); - break; - case 0x03: - position.set(Position.PREFIX_TEMP + 1, buf.readByte() * 0.5); - break; - case 0x04: - position.set(Position.KEY_BATTERY, buf.readUnsignedByte() * 0.1); - break; - case 0x05: - position.set(Position.KEY_BATTERY_LEVEL, buf.readUnsignedByte()); - break; - case 0x06: - String mac = ByteBufUtil.hexDump(buf.readSlice(6)).replaceAll("(..)", "$1:"); - position.setNetwork(new Network(WifiAccessPoint.from( - mac.substring(0, mac.length() - 1), buf.readUnsignedByte()))); - break; - case 0x07: - buf.skipBytes(10); // wifi extended - break; - case 0x08: - buf.skipBytes(6); // accelerometer - break; - case 0x09: - position.setSpeed(UnitsConverter.knotsFromKph(buf.readUnsignedByte())); - break; - default: - buf.readUnsignedByte(); // fence number - break; + position.setValid(event >> 4 > 0); + + long value; + value = buf.readUnsignedInt(); + position.setLatitude(BitUtil.to(value, 31) * 0.000001); + if (BitUtil.check(value, 31)) { + position.setLatitude(-position.getLatitude()); } - } + value = buf.readUnsignedInt(); + position.setLongitude(BitUtil.to(value, 31) * 0.000001); + if (BitUtil.check(value, 31)) { + position.setLongitude(-position.getLongitude()); + } + + position.set(Position.KEY_BATTERY, (int) buf.readUnsignedByte()); + + } else if (event >> 4 == 0) { + + position.setValid(true); + position.setLatitude(buf.readIntLE() * 0.0000001); + position.setLongitude(buf.readIntLE() * 0.0000001); + position.setCourse(buf.readUnsignedByte() * 2); + position.setSpeed(UnitsConverter.knotsFromKph(buf.readUnsignedByte())); + + position.set(Position.KEY_BATTERY, buf.readUnsignedByte() * 0.025); + } else { + + position.set(Position.KEY_EVENT, event); + if (event == 0x22 || event == 0x62) { + position.set(Position.KEY_ALARM, Position.ALARM_SOS); + } + + while (buf.isReadable()) { + int type = buf.readUnsignedByte(); + switch (type) { + case 0x01: + position.setValid(true); + position.setLatitude(buf.readMedium()); + position.setLongitude(buf.readMedium()); + break; + case 0x02: + position.setValid(true); + position.setLatitude(buf.readFloat()); + position.setLongitude(buf.readFloat()); + break; + case 0x03: + position.set(Position.PREFIX_TEMP + 1, buf.readByte() * 0.5); + break; + case 0x04: + position.set(Position.KEY_BATTERY, buf.readUnsignedByte() * 0.1); + break; + case 0x05: + position.set(Position.KEY_BATTERY_LEVEL, buf.readUnsignedByte()); + break; + case 0x06: + String mac = ByteBufUtil.hexDump(buf.readSlice(6)).replaceAll("(..)", "$1:"); + position.setNetwork(new Network(WifiAccessPoint.from( + mac.substring(0, mac.length() - 1), buf.readUnsignedByte()))); + break; + case 0x07: + buf.skipBytes(10); // wifi extended + break; + case 0x08: + buf.skipBytes(6); // accelerometer + break; + case 0x09: + position.setSpeed(UnitsConverter.knotsFromKph(buf.readUnsignedByte())); + break; + default: + buf.readUnsignedByte(); // fence number + break; + } + } + + } + } finally { + buf.release(); } - } finally { - buf.release(); } if (position.getLatitude() == 0 && position.getLongitude() == 0) { @@ -136,10 +206,10 @@ public class SigfoxProtocolDecoder extends BaseHttpProtocolDecoder { } if (json.containsKey("rssi")) { - position.set(Position.KEY_RSSI, json.getJsonNumber("rssi").doubleValue()); + position.set(Position.KEY_RSSI, getJsonDouble(json, "rssi")); } if (json.containsKey("seqNumber")) { - position.set(Position.KEY_INDEX, json.getInt("seqNumber")); + position.set(Position.KEY_INDEX, getJsonInt(json, "seqNumber")); } sendResponse(channel, HttpResponseStatus.OK); diff --git a/src/main/java/org/traccar/protocol/SolarPoweredProtocol.java b/src/main/java/org/traccar/protocol/SolarPoweredProtocol.java new file mode 100644 index 000000000..53a948cdc --- /dev/null +++ b/src/main/java/org/traccar/protocol/SolarPoweredProtocol.java @@ -0,0 +1,34 @@ +/* + * Copyright 2019 Anton Tananaev (anton@traccar.org) + * + * 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.protocol; + +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class SolarPoweredProtocol extends BaseProtocol { + + public SolarPoweredProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new HuabaoFrameDecoder()); + pipeline.addLast(new SolarPoweredProtocolDecoder(SolarPoweredProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/SolarPoweredProtocolDecoder.java b/src/main/java/org/traccar/protocol/SolarPoweredProtocolDecoder.java new file mode 100644 index 000000000..eae37386a --- /dev/null +++ b/src/main/java/org/traccar/protocol/SolarPoweredProtocolDecoder.java @@ -0,0 +1,97 @@ +/* + * Copyright 2019 Anton Tananaev (anton@traccar.org) + * + * 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.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufUtil; +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.Protocol; +import org.traccar.helper.BitUtil; +import org.traccar.helper.DateBuilder; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.Position; + +import java.net.SocketAddress; + +public class SolarPoweredProtocolDecoder extends BaseProtocolDecoder { + + public SolarPoweredProtocolDecoder(Protocol protocol) { + super(protocol); + } + + public static final int MSG_ACTIVE_REPORTING = 0x11; + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + + buf.readUnsignedByte(); // start marker + + String imei = ByteBufUtil.hexDump(buf.readSlice(8)).substring(0, 15); + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, imei); + if (deviceSession == null) { + return null; + } + + int type = buf.readUnsignedByte(); + buf.readUnsignedShort(); // attributes + + if (type == MSG_ACTIVE_REPORTING) { + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + while (buf.readableBytes() > 2) { + int tag = buf.readUnsignedByte(); + int length = buf.readUnsignedByte(); + switch (tag) { + case 0x81: + int status = buf.readUnsignedByte(); + DateBuilder dateBuilder = new DateBuilder() + .setDate(buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte()) + .setTime(buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte()); + position.setTime(dateBuilder.getDate()); + position.setLatitude(buf.readUnsignedInt() * 0.000001); + if (BitUtil.check(status, 3)) { + position.setLatitude(-position.getLatitude()); + } + position.setLongitude(buf.readUnsignedInt() * 0.000001); + if (BitUtil.check(status, 2)) { + position.setLongitude(-position.getLongitude()); + } + position.setSpeed(UnitsConverter.knotsFromKph(buf.readUnsignedByte())); + position.set(Position.KEY_DEVICE_TEMP, (int) buf.readByte()); + position.set(Position.KEY_BATTERY, buf.readUnsignedByte() * 0.02); + position.setCourse(buf.readUnsignedByte()); + break; + default: + buf.skipBytes(length); + break; + } + } + + return position; + + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/StarcomProtocolDecoder.java b/src/main/java/org/traccar/protocol/StarcomProtocolDecoder.java index 90151d061..5ffddb318 100644 --- a/src/main/java/org/traccar/protocol/StarcomProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/StarcomProtocolDecoder.java @@ -18,6 +18,7 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; import org.traccar.DeviceSession; +import org.traccar.helper.UnitsConverter; import org.traccar.model.Position; import java.net.SocketAddress; @@ -66,7 +67,7 @@ public class StarcomProtocolDecoder extends BaseProtocolDecoder { position.setAltitude(Double.parseDouble(value)); break; case "velocity": - position.setSpeed(Integer.parseInt(value)); + position.setSpeed(UnitsConverter.knotsFromKph(Integer.parseInt(value))); break; case "heading": position.setCourse(Integer.parseInt(value)); @@ -74,8 +75,8 @@ public class StarcomProtocolDecoder extends BaseProtocolDecoder { case "eventid": position.set(Position.KEY_EVENT, Integer.parseInt(value)); break; - case "odometer": - position.set(Position.KEY_ODOMETER, Long.parseLong(value)); + case "mileage": + position.set(Position.KEY_ODOMETER, (long) (Double.parseDouble(value) * 1000)); break; case "satellites": position.set(Position.KEY_SATELLITES, Integer.parseInt(value)); diff --git a/src/main/java/org/traccar/protocol/SuntechFrameDecoder.java b/src/main/java/org/traccar/protocol/SuntechFrameDecoder.java index de8de17f0..8748ecc61 100644 --- a/src/main/java/org/traccar/protocol/SuntechFrameDecoder.java +++ b/src/main/java/org/traccar/protocol/SuntechFrameDecoder.java @@ -32,13 +32,24 @@ public class SuntechFrameDecoder extends BaseFrameDecoder { protected Object decode( ChannelHandlerContext ctx, Channel channel, ByteBuf buf) throws Exception { - int delimiterIndex = buf.indexOf(buf.readerIndex(), buf.writerIndex(), (byte) '\r'); - while (delimiterIndex > 0) { - if (delimiterIndex + 1 < buf.writerIndex() && buf.getByte(delimiterIndex + 1) == '\n') { - delimiterIndex = buf.indexOf(delimiterIndex + 1, buf.writerIndex(), (byte) '\r'); - } else { - return readFrame(buf, delimiterIndex); + if (buf.getByte(buf.readerIndex() + 1) == 0) { + + int length = 1 + 2 + buf.getShort(buf.readerIndex() + 1); + if (buf.readableBytes() >= length) { + return buf.readRetainedSlice(length); } + + } else { + + int delimiterIndex = buf.indexOf(buf.readerIndex(), buf.writerIndex(), (byte) '\r'); + while (delimiterIndex > 0) { + if (delimiterIndex + 1 < buf.writerIndex() && buf.getByte(delimiterIndex + 1) == '\n') { + delimiterIndex = buf.indexOf(delimiterIndex + 1, buf.writerIndex(), (byte) '\r'); + } else { + return readFrame(buf, delimiterIndex); + } + } + } return null; diff --git a/src/main/java/org/traccar/protocol/SuntechProtocol.java b/src/main/java/org/traccar/protocol/SuntechProtocol.java index 7e2c20e6f..199885537 100644 --- a/src/main/java/org/traccar/protocol/SuntechProtocol.java +++ b/src/main/java/org/traccar/protocol/SuntechProtocol.java @@ -15,7 +15,6 @@ */ package org.traccar.protocol; -import io.netty.handler.codec.string.StringDecoder; import io.netty.handler.codec.string.StringEncoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; @@ -38,7 +37,6 @@ public class SuntechProtocol extends BaseProtocol { protected void addProtocolHandlers(PipelineBuilder pipeline) { pipeline.addLast(new SuntechFrameDecoder()); pipeline.addLast(new StringEncoder()); - pipeline.addLast(new StringDecoder()); pipeline.addLast(new SuntechProtocolEncoder(SuntechProtocol.this)); pipeline.addLast(new SuntechProtocolDecoder(SuntechProtocol.this)); } diff --git a/src/main/java/org/traccar/protocol/SuntechProtocolDecoder.java b/src/main/java/org/traccar/protocol/SuntechProtocolDecoder.java index e40096a77..978d768be 100644 --- a/src/main/java/org/traccar/protocol/SuntechProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/SuntechProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 - 2019 Anton Tananaev (anton@traccar.org) + * Copyright 2013 - 2020 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,18 +15,22 @@ */ package org.traccar.protocol; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufUtil; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; import org.traccar.Context; import org.traccar.DeviceSession; import org.traccar.Protocol; import org.traccar.helper.BitUtil; +import org.traccar.helper.DateBuilder; import org.traccar.helper.UnitsConverter; import org.traccar.model.CellTower; import org.traccar.model.Network; import org.traccar.model.Position; import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; @@ -34,6 +38,8 @@ import java.util.TimeZone; public class SuntechProtocolDecoder extends BaseProtocolDecoder { + private String prefix; + private int protocolType; private boolean hbm; private boolean includeAdc; @@ -44,6 +50,10 @@ public class SuntechProtocolDecoder extends BaseProtocolDecoder { super(protocol); } + public String getPrefix() { + return prefix; + } + public void setProtocolType(int protocolType) { this.protocolType = protocolType; } @@ -232,6 +242,10 @@ public class SuntechProtocolDecoder extends BaseProtocolDecoder { position.set(Position.KEY_INDEX, Integer.parseInt(values[index++])); position.set(Position.KEY_STATUS, Integer.parseInt(values[index++])); + if (values[index].length() == 3) { + index += 1; // collaborative network + } + DateFormat dateFormat = new SimpleDateFormat("yyyyMMddHH:mm:ss"); dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); position.setTime(dateFormat.parse(values[index++] + values[index++])); @@ -268,7 +282,7 @@ public class SuntechProtocolDecoder extends BaseProtocolDecoder { position.setDeviceId(deviceSession.getDeviceId()); position.set(Position.KEY_TYPE, type); - if (protocol.equals("ST300") || protocol.equals("ST500") || protocol.equals("ST600")) { + if (protocol.startsWith("ST3") || protocol.equals("ST500") || protocol.equals("ST600")) { index += 1; // model } @@ -300,7 +314,7 @@ public class SuntechProtocolDecoder extends BaseProtocolDecoder { position.set(Position.KEY_POWER, Double.parseDouble(values[index++])); String io = values[index++]; - if (io.length() == 6) { + if (io.length() >= 6) { position.set(Position.KEY_IGNITION, io.charAt(0) == '1'); position.set(Position.PREFIX_IN + 1, io.charAt(1) == '1'); position.set(Position.PREFIX_IN + 2, io.charAt(2) == '1'); @@ -361,6 +375,7 @@ public class SuntechProtocolDecoder extends BaseProtocolDecoder { } remaining -= attribute.length() + 1; } + index += 1; // checksum break; default: break; @@ -382,7 +397,7 @@ public class SuntechProtocolDecoder extends BaseProtocolDecoder { if (isIncludeAdc(deviceSession.getDeviceId())) { for (int i = 1; i <= 3; i++) { - if (!values[index++].isEmpty()) { + if (index < values.length && !values[index++].isEmpty()) { position.set(Position.PREFIX_ADC + i, Double.parseDouble(values[index - 1])); } } @@ -501,20 +516,149 @@ public class SuntechProtocolDecoder extends BaseProtocolDecoder { return position; } + private Position decodeBinary( + Channel channel, SocketAddress remoteAddress, ByteBuf buf) { + + int type = buf.readUnsignedByte(); + buf.readUnsignedShort(); // length + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, ByteBufUtil.hexDump(buf.readSlice(5))); + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + int mask = buf.readUnsignedMedium(); + + if (BitUtil.check(mask, 1)) { + buf.readUnsignedByte(); // model + } + + if (BitUtil.check(mask, 2)) { + position.set(Position.KEY_VERSION_FW, String.format("%d.%d.%d", + buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte())); + } + + if (BitUtil.check(mask, 3) && buf.readUnsignedByte() == 0) { + position.set(Position.KEY_ARCHIVE, true); + } + + if (BitUtil.check(mask, 4) && BitUtil.check(mask, 5)) { + position.setTime(new DateBuilder() + .setDate(buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte()) + .setTime(buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte()) + .getDate()); + } + + if (BitUtil.check(mask, 6)) { + buf.readUnsignedInt(); // cell + } + + if (BitUtil.check(mask, 7)) { + buf.readUnsignedShort(); // mcc + } + + if (BitUtil.check(mask, 8)) { + buf.readUnsignedShort(); // mnc + } + + if (BitUtil.check(mask, 9)) { + buf.readUnsignedShort(); // lac + } + + if (BitUtil.check(mask, 10)) { + position.set(Position.KEY_RSSI, buf.readUnsignedByte()); + } + + if (BitUtil.check(mask, 11)) { + long value = buf.readUnsignedInt(); + if (BitUtil.check(value, 31)) { + value = -BitUtil.to(value, 31); + } + position.setLatitude(value / 1000000.0); + } + + if (BitUtil.check(mask, 12)) { + long value = buf.readUnsignedInt(); + if (BitUtil.check(value, 31)) { + value = -BitUtil.to(value, 31); + } + position.setLongitude(value / 1000000.0); + } + + if (BitUtil.check(mask, 13)) { + position.setSpeed(UnitsConverter.knotsFromKph(buf.readUnsignedShort() / 100.0)); + } + + if (BitUtil.check(mask, 14)) { + position.setCourse(buf.readUnsignedShort() / 100.0); + } + + if (BitUtil.check(mask, 15)) { + position.set(Position.KEY_SATELLITES, buf.readUnsignedByte()); + } + + if (BitUtil.check(mask, 16)) { + position.setValid(buf.readUnsignedByte() > 0); + } + + if (BitUtil.check(mask, 17)) { + int input = buf.readUnsignedByte(); + position.set(Position.KEY_IGNITION, BitUtil.check(input, 0)); + position.set(Position.KEY_INPUT, input); + } + + if (BitUtil.check(mask, 18)) { + position.set(Position.KEY_OUTPUT, buf.readUnsignedByte()); + } + + int alertId = 0; + if (BitUtil.check(mask, 19)) { + alertId = buf.readUnsignedByte(); + if (type == 0x82) { + position.set(Position.KEY_ALARM, decodeAlert(alertId)); + } + } + + if (BitUtil.check(mask, 20)) { + buf.readUnsignedShort(); // alert mod + } + + if (BitUtil.check(mask, 21)) { + if (alertId == 59) { + position.set(Position.KEY_DRIVER_UNIQUE_ID, ByteBufUtil.hexDump(buf.readSlice(8))); + } + } + + return position; + } + @Override protected Object decode( Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { - String[] values = ((String) msg).split(";"); + ByteBuf buf = (ByteBuf) msg; + + if (buf.getByte(buf.readerIndex() + 1) == 0) { + + return decodeBinary(channel, remoteAddress, buf); - if (values[0].length() < 5) { - return decodeUniversal(channel, remoteAddress, values); - } else if (values[0].startsWith("ST9")) { - return decode9(channel, remoteAddress, values); - } else if (values[0].startsWith("ST4")) { - return decode4(channel, remoteAddress, values); } else { - return decode2356(channel, remoteAddress, values[0].substring(0, 5), values); + + String[] values = buf.toString(StandardCharsets.US_ASCII).split(";"); + prefix = values[0]; + + if (prefix.length() < 5) { + return decodeUniversal(channel, remoteAddress, values); + } else if (prefix.startsWith("ST9")) { + return decode9(channel, remoteAddress, values); + } else if (prefix.startsWith("ST4")) { + return decode4(channel, remoteAddress, values); + } else { + return decode2356(channel, remoteAddress, prefix.substring(0, 5), values); + } } } diff --git a/src/main/java/org/traccar/protocol/SuntechProtocolEncoder.java b/src/main/java/org/traccar/protocol/SuntechProtocolEncoder.java index 6dae42ad5..3b4995110 100644 --- a/src/main/java/org/traccar/protocol/SuntechProtocolEncoder.java +++ b/src/main/java/org/traccar/protocol/SuntechProtocolEncoder.java @@ -15,6 +15,8 @@ */ package org.traccar.protocol; +import io.netty.channel.Channel; +import org.traccar.BasePipelineFactory; import org.traccar.StringProtocolEncoder; import org.traccar.model.Command; import org.traccar.Protocol; @@ -25,32 +27,49 @@ public class SuntechProtocolEncoder extends StringProtocolEncoder { super(protocol); } + private String getPrefix(Channel channel) { + String prefix = "SA200CMD"; + if (channel != null) { + SuntechProtocolDecoder protocolDecoder = + BasePipelineFactory.getHandler(channel.pipeline(), SuntechProtocolDecoder.class); + if (protocolDecoder != null) { + String decoderPrefix = protocolDecoder.getPrefix(); + if (decoderPrefix != null && decoderPrefix.length() > 5) { + prefix = decoderPrefix.substring(0, decoderPrefix.length() - 3) + "CMD"; + } + } + } + return prefix; + } + @Override - protected Object encodeCommand(Command command) { + protected Object encodeCommand(Channel channel, Command command) { + + String prefix = getPrefix(channel); switch (command.getType()) { case Command.TYPE_REBOOT_DEVICE: - return formatCommand(command, "SA200CMD;{%s};02;Reboot\r", Command.KEY_UNIQUE_ID); + return formatCommand(command, prefix + ";%s;02;Reboot\r", Command.KEY_UNIQUE_ID); case Command.TYPE_POSITION_SINGLE: - return formatCommand(command, "SA200GTR;{%s};02;\r", Command.KEY_UNIQUE_ID); + return formatCommand(command, prefix + ";%s;02;\r", Command.KEY_UNIQUE_ID); case Command.TYPE_OUTPUT_CONTROL: if (command.getAttributes().containsKey(Command.KEY_DATA)) { if (command.getAttributes().get(Command.KEY_DATA).equals("1")) { - return formatCommand(command, "SA200CMD;{%s};02;Enable{%s}\r", + return formatCommand(command, prefix + ";%s;02;Enable%s\r", Command.KEY_UNIQUE_ID, Command.KEY_INDEX); } else { - return formatCommand(command, "SA200CMD;{%s};02;Disable{%s}\r", + return formatCommand(command, prefix + ";%s;02;Disable%s\r", Command.KEY_UNIQUE_ID, Command.KEY_INDEX); } } case Command.TYPE_ENGINE_STOP: - return formatCommand(command, "SA200CMD;{%s};02;Enable1\r", Command.KEY_UNIQUE_ID); + return formatCommand(command, prefix + ";%s;02;Enable1\r", Command.KEY_UNIQUE_ID); case Command.TYPE_ENGINE_RESUME: - return formatCommand(command, "SA200CMD;{%s};02;Disable1\r", Command.KEY_UNIQUE_ID); + return formatCommand(command, prefix + ";%s;02;Disable1\r", Command.KEY_UNIQUE_ID); case Command.TYPE_ALARM_ARM: - return formatCommand(command, "SA200CMD;{%s};02;Enable2\r", Command.KEY_UNIQUE_ID); + return formatCommand(command, prefix + ";%s;02;Enable2\r", Command.KEY_UNIQUE_ID); case Command.TYPE_ALARM_DISARM: - return formatCommand(command, "SA200CMD;{%s};02;Disable2\r", Command.KEY_UNIQUE_ID); + return formatCommand(command, prefix + ";%s;02;Disable2\r", Command.KEY_UNIQUE_ID); default: return null; } diff --git a/src/main/java/org/traccar/protocol/SviasProtocolEncoder.java b/src/main/java/org/traccar/protocol/SviasProtocolEncoder.java index 2607d7bd1..d218f63ce 100644 --- a/src/main/java/org/traccar/protocol/SviasProtocolEncoder.java +++ b/src/main/java/org/traccar/protocol/SviasProtocolEncoder.java @@ -30,11 +30,11 @@ public class SviasProtocolEncoder extends StringProtocolEncoder { protected Object encodeCommand(Command command) { switch (command.getType()) { case Command.TYPE_CUSTOM: - return formatCommand(command, "{%s}", Command.KEY_DATA); + return formatCommand(command, "%s", Command.KEY_DATA); case Command.TYPE_POSITION_SINGLE: return formatCommand(command, "AT+STR=1*"); case Command.TYPE_SET_ODOMETER: - return formatCommand(command, "AT+ODT={%s}*", Command.KEY_DATA); + return formatCommand(command, "AT+ODT=%s*", Command.KEY_DATA); case Command.TYPE_ENGINE_STOP: return formatCommand(command, "AT+OUT=1,1*"); case Command.TYPE_ENGINE_RESUME: diff --git a/src/main/java/org/traccar/protocol/T55ProtocolDecoder.java b/src/main/java/org/traccar/protocol/T55ProtocolDecoder.java index e25f9e11a..b75addfae 100644 --- a/src/main/java/org/traccar/protocol/T55ProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/T55ProtocolDecoder.java @@ -96,6 +96,19 @@ public class T55ProtocolDecoder extends BaseProtocolDecoder { .any() .compile(); + private static final Pattern PATTERN_GPIOP = new PatternBuilder() + .text("$GPIOP,") + .number("[01]{8},") // inputs + .number("[01]{8},") // outputs + .number("d+.d+,") // adc 1 + .number("d+.d+,") // adc 2 + .number("d+.d+,") // adc 3 + .number("d+.d+,") // adc 4 + .number("(d+.d+),") // power + .number("(d+.d+)") // battery + .any() + .compile(); + private Position position = null; private Position decodeGprmc( @@ -225,6 +238,24 @@ public class T55ProtocolDecoder extends BaseProtocolDecoder { return position; } + private Position decodeGpiop(DeviceSession deviceSession, String sentence) { + + Parser parser = new Parser(PATTERN_GPIOP, sentence); + if (!parser.matches()) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + getLastLocation(position, null); + + position.set(Position.KEY_POWER, parser.nextDouble()); + position.set(Position.KEY_BATTERY, parser.nextDouble()); + + return position; + } + @Override protected Object decode( Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { @@ -275,6 +306,8 @@ public class T55ProtocolDecoder extends BaseProtocolDecoder { return decodeGprma(deviceSession, sentence); } else if (sentence.startsWith("$TRCCR") && deviceSession != null) { return decodeTrccr(deviceSession, sentence); + } else if (sentence.startsWith("$GPIOP")) { + return decodeGpiop(deviceSession, sentence); } return null; diff --git a/src/main/java/org/traccar/protocol/TeltonikaProtocolDecoder.java b/src/main/java/org/traccar/protocol/TeltonikaProtocolDecoder.java index 485df833a..cc2868a20 100644 --- a/src/main/java/org/traccar/protocol/TeltonikaProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/TeltonikaProtocolDecoder.java @@ -157,7 +157,19 @@ public class TeltonikaProtocolDecoder extends BaseProtocolDecoder { } if (readable) { - position.set(Position.KEY_RESULT, buf.readSlice(length).toString(StandardCharsets.US_ASCII)); + String data = buf.readSlice(length).toString(StandardCharsets.US_ASCII).trim(); + if (data.startsWith("UUUUww") && data.endsWith("SSS")) { + String[] values = data.substring(6, data.length() - 4).split(";"); + for (int i = 0; i < 8; i++) { + position.set("axle" + (i + 1), Double.parseDouble(values[i])); + } + position.set("loadTruck", Double.parseDouble(values[8])); + position.set("loadTrailer", Double.parseDouble(values[9])); + position.set("totalTruck", Double.parseDouble(values[10])); + position.set("totalTrailer", Double.parseDouble(values[11])); + } else { + position.set(Position.KEY_RESULT, data); + } } else { position.set(Position.KEY_RESULT, ByteBufUtil.hexDump(buf.readSlice(length))); } @@ -188,6 +200,9 @@ public class TeltonikaProtocolDecoder extends BaseProtocolDecoder { case 9: position.set(Position.PREFIX_ADC + 1, readValue(buf, length, false)); break; + case 10: + position.set(Position.PREFIX_ADC + 2, readValue(buf, length, false)); + break; case 17: position.set("axisX", readValue(buf, length, true)); break; diff --git a/src/main/java/org/traccar/protocol/Tk103ProtocolEncoder.java b/src/main/java/org/traccar/protocol/Tk103ProtocolEncoder.java index a8aa84105..5d7e63920 100644 --- a/src/main/java/org/traccar/protocol/Tk103ProtocolEncoder.java +++ b/src/main/java/org/traccar/protocol/Tk103ProtocolEncoder.java @@ -50,7 +50,7 @@ public class Tk103ProtocolEncoder extends StringProtocolEncoder { if (alternative) { switch (command.getType()) { case Command.TYPE_CUSTOM: - return formatAlt(command, "{%s}", Command.KEY_DATA); + return formatAlt(command, "%s", Command.KEY_DATA); case Command.TYPE_GET_VERSION: return formatAlt(command, "*about*"); case Command.TYPE_POWER_OFF: @@ -74,36 +74,36 @@ public class Tk103ProtocolEncoder extends StringProtocolEncoder { case Command.TYPE_ALARM_SOS: return formatAlt(command, command.getBoolean(Command.KEY_ENABLE) ? "*soson*" : "*sosoff*"); case Command.TYPE_SET_CONNECTION: - return formatAlt(command, "*setip*%s*{%s}*", - command.getString(Command.KEY_SERVER).replace(".", "*"), Command.KEY_PORT); + String server = command.getString(Command.KEY_SERVER).replace(".", "*"); + return formatAlt(command, "*setip*" + server + "*%s*", Command.KEY_PORT); case Command.TYPE_SOS_NUMBER: - return formatAlt(command, "*master*{%s}*{%s}*", Command.KEY_DEVICE_PASSWORD, Command.KEY_PHONE); + return formatAlt(command, "*master*%s*%s*", Command.KEY_DEVICE_PASSWORD, Command.KEY_PHONE); default: return null; } } else { switch (command.getType()) { case Command.TYPE_CUSTOM: - return formatCommand(command, "({%s}{%s})", Command.KEY_UNIQUE_ID, Command.KEY_DATA); + return formatCommand(command, "(%s%s)", Command.KEY_UNIQUE_ID, Command.KEY_DATA); case Command.TYPE_GET_VERSION: - return formatCommand(command, "({%s}AP07)", Command.KEY_UNIQUE_ID); + return formatCommand(command, "(%sAP07)", Command.KEY_UNIQUE_ID); case Command.TYPE_REBOOT_DEVICE: - return formatCommand(command, "({%s}AT00)", Command.KEY_UNIQUE_ID); + return formatCommand(command, "(%sAT00)", Command.KEY_UNIQUE_ID); case Command.TYPE_SET_ODOMETER: - return formatCommand(command, "({%s}AX01)", Command.KEY_UNIQUE_ID); + return formatCommand(command, "(%sAX01)", Command.KEY_UNIQUE_ID); case Command.TYPE_POSITION_SINGLE: - return formatCommand(command, "({%s}AP00)", Command.KEY_UNIQUE_ID); + return formatCommand(command, "(%sAP00)", Command.KEY_UNIQUE_ID); case Command.TYPE_POSITION_PERIODIC: - return formatCommand(command, "({%s}AR00%s0000)", Command.KEY_UNIQUE_ID, - String.format("%04X", command.getInteger(Command.KEY_FREQUENCY))); + String frequency = String.format("%04X", command.getInteger(Command.KEY_FREQUENCY)); + return formatCommand(command, "(%sAR00" + frequency + "0000)", Command.KEY_UNIQUE_ID); case Command.TYPE_POSITION_STOP: - return formatCommand(command, "({%s}AR0000000000)", Command.KEY_UNIQUE_ID); + return formatCommand(command, "(%sAR0000000000)", Command.KEY_UNIQUE_ID); case Command.TYPE_ENGINE_STOP: - return formatCommand(command, "({%s}AV010)", Command.KEY_UNIQUE_ID); + return formatCommand(command, "(%sAV010)", Command.KEY_UNIQUE_ID); case Command.TYPE_ENGINE_RESUME: - return formatCommand(command, "({%s}AV011)", Command.KEY_UNIQUE_ID); + return formatCommand(command, "(%sAV011)", Command.KEY_UNIQUE_ID); case Command.TYPE_OUTPUT_CONTROL: - return formatCommand(command, "({%s}AV00{%s})", Command.KEY_UNIQUE_ID, Command.KEY_DATA); + return formatCommand(command, "(%sAV00%s)", Command.KEY_UNIQUE_ID, Command.KEY_DATA); default: return null; } diff --git a/src/main/java/org/traccar/protocol/Tlt2hProtocolDecoder.java b/src/main/java/org/traccar/protocol/Tlt2hProtocolDecoder.java index 2a06d35e5..3ce6438f8 100644 --- a/src/main/java/org/traccar/protocol/Tlt2hProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/Tlt2hProtocolDecoder.java @@ -36,10 +36,18 @@ public class Tlt2hProtocolDecoder extends BaseProtocolDecoder { } private static final Pattern PATTERN_HEADER = new PatternBuilder() - .number("#(d+)#") // imei - .any() - .expression("#([^#]+)#") // status - .number("d+") // number of records + .number("#(d+)") // imei + .expression("#[^#]*") // user + .number("#d*") // password + .groupBegin() + .number("#([01])") // door + .number("#(d+)") // fuel voltage + .number("#(d+)") // power + .number("#(d+)") // battery + .number("#(d+)") // temperature + .groupEnd("?") + .expression("#([^#]+)") // status + .number("#d+") // number of records .compile(); private static final Pattern PATTERN_POSITION = new PatternBuilder() @@ -114,6 +122,19 @@ public class Tlt2hProtocolDecoder extends BaseProtocolDecoder { return null; } + Boolean door = null; + Double adc = null; + Double power = null; + Double battery = null; + Double temperature = null; + if (parser.hasNext(5)) { + door = parser.nextInt() == 1; + adc = parser.nextInt() * 0.1; + power = parser.nextInt() * 0.1; + battery = parser.nextInt() * 0.1; + temperature = parser.nextInt() * 0.1; + } + String status = parser.next(); String[] messages = sentence.substring(sentence.indexOf('\n') + 1).split("\r\n"); @@ -140,6 +161,11 @@ public class Tlt2hProtocolDecoder extends BaseProtocolDecoder { dateBuilder.setDateReverse(parser.nextInt(0), parser.nextInt(0), parser.nextInt(0)); position.setTime(dateBuilder.getDate()); + position.set(Position.KEY_DOOR, door); + position.set(Position.PREFIX_ADC + 1, adc); + position.set(Position.KEY_POWER, power); + position.set(Position.KEY_BATTERY, battery); + position.set(Position.PREFIX_TEMP + 1, temperature); decodeStatus(position, status); positions.add(position); diff --git a/src/main/java/org/traccar/protocol/TopinProtocol.java b/src/main/java/org/traccar/protocol/TopinProtocol.java new file mode 100644 index 000000000..844dd7518 --- /dev/null +++ b/src/main/java/org/traccar/protocol/TopinProtocol.java @@ -0,0 +1,33 @@ +/* + * Copyright 2019 Anton Tananaev (anton@traccar.org) + * + * 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.protocol; + +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class TopinProtocol extends BaseProtocol { + + public TopinProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new TopinProtocolDecoder(TopinProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/TopinProtocolDecoder.java b/src/main/java/org/traccar/protocol/TopinProtocolDecoder.java new file mode 100644 index 000000000..eee0e9ae8 --- /dev/null +++ b/src/main/java/org/traccar/protocol/TopinProtocolDecoder.java @@ -0,0 +1,182 @@ +/* + * Copyright 2019 - 2020 Anton Tananaev (anton@traccar.org) + * + * 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.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufUtil; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.NetworkMessage; +import org.traccar.Protocol; +import org.traccar.helper.BcdUtil; +import org.traccar.helper.DateBuilder; +import org.traccar.model.CellTower; +import org.traccar.model.Network; +import org.traccar.model.Position; +import org.traccar.model.WifiAccessPoint; + +import java.net.SocketAddress; +import java.util.TimeZone; + +public class TopinProtocolDecoder extends BaseProtocolDecoder { + + public TopinProtocolDecoder(Protocol protocol) { + super(protocol); + } + + public static final int MSG_LOGIN = 0x01; + public static final int MSG_GPS = 0x10; + public static final int MSG_GPS_OFFLINE = 0x11; + public static final int MSG_STATUS = 0x13; + public static final int MSG_WIFI_OFFLINE = 0x17; + public static final int MSG_WIFI = 0x69; + + private void sendResponse(Channel channel, int length, int type, ByteBuf content) { + if (channel != null) { + ByteBuf response = Unpooled.buffer(); + response.writeShort(0x7878); + response.writeByte(length); + response.writeByte(type); + response.writeBytes(content); + response.writeByte('\r'); + response.writeByte('\n'); + content.release(); + channel.writeAndFlush(new NetworkMessage(response, channel.remoteAddress())); + } + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + + buf.skipBytes(2); // header + int length = buf.readUnsignedByte(); + + int type = buf.readUnsignedByte(); + + DeviceSession deviceSession; + if (type == MSG_LOGIN) { + String imei = ByteBufUtil.hexDump(buf.readSlice(8)).substring(1); + deviceSession = getDeviceSession(channel, remoteAddress, imei); + ByteBuf content = Unpooled.buffer(); + content.writeByte(deviceSession != null ? 0x01 : 0x44); + sendResponse(channel, length, type, content); + return null; + } else { + deviceSession = getDeviceSession(channel, remoteAddress); + if (deviceSession == null) { + return null; + } + } + + if (type == MSG_GPS || type == MSG_GPS_OFFLINE) { + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + ByteBuf time = buf.slice(buf.readerIndex(), 6); + + Gt06ProtocolDecoder.decodeGps(position, buf, false, TimeZone.getTimeZone("UTC")); + + ByteBuf content = Unpooled.buffer(); + content.writeBytes(time); + sendResponse(channel, length, type, content); + + return position; + + } else if (type == MSG_STATUS) { + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + getLastLocation(position, null); + + int battery = buf.readUnsignedByte(); + int firmware = buf.readUnsignedByte(); + int timezone = buf.readUnsignedByte(); + int interval = buf.readUnsignedByte(); + int signal = 0; + if (length >= 7) { + signal = buf.readUnsignedByte(); + position.set(Position.KEY_RSSI, signal); + } + + position.set(Position.KEY_BATTERY_LEVEL, battery); + position.set(Position.KEY_VERSION_FW, firmware); + + ByteBuf content = Unpooled.buffer(); + content.writeByte(battery); + content.writeByte(firmware); + content.writeByte(timezone); + content.writeByte(interval); + if (length >= 7) { + content.writeByte(signal); + } + sendResponse(channel, length, type, content); + + return position; + + } else if (type == MSG_WIFI || type == MSG_WIFI_OFFLINE) { + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + ByteBuf time = buf.readSlice(6); + DateBuilder dateBuilder = new DateBuilder() + .setYear(BcdUtil.readInteger(time, 2)) + .setMonth(BcdUtil.readInteger(time, 2)) + .setDay(BcdUtil.readInteger(time, 2)) + .setHour(BcdUtil.readInteger(time, 2)) + .setMinute(BcdUtil.readInteger(time, 2)) + .setSecond(BcdUtil.readInteger(time, 2)); + time.resetReaderIndex(); + + getLastLocation(position, dateBuilder.getDate()); + + Network network = new Network(); + for (int i = 0; i < length; i++) { + String mac = String.format("%02x:%02x:%02x:%02x:%02x:%02x", + buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte(), + buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte()); + network.addWifiAccessPoint(WifiAccessPoint.from(mac, buf.readUnsignedByte())); + } + + int cellCount = buf.readUnsignedByte(); + int mcc = buf.readUnsignedShort(); + int mnc = buf.readUnsignedByte(); + for (int i = 0; i < cellCount; i++) { + network.addCellTower(CellTower.from( + mcc, mnc, buf.readUnsignedShort(), buf.readUnsignedShort(), buf.readUnsignedByte())); + } + + position.setNetwork(network); + + ByteBuf content = Unpooled.buffer(); + content.writeBytes(time); + sendResponse(channel, length, type, content); + + return position; + + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/TotemProtocolEncoder.java b/src/main/java/org/traccar/protocol/TotemProtocolEncoder.java index 3bbb92031..a96dd1ee3 100644 --- a/src/main/java/org/traccar/protocol/TotemProtocolEncoder.java +++ b/src/main/java/org/traccar/protocol/TotemProtocolEncoder.java @@ -34,9 +34,9 @@ public class TotemProtocolEncoder extends StringProtocolEncoder { switch (command.getType()) { // Assuming PIN 8 (Output C) is the power wire, like manual says but it can be PIN 5,7,8 case Command.TYPE_ENGINE_STOP: - return formatCommand(command, "*{%s},025,C,1#", Command.KEY_DEVICE_PASSWORD); + return formatCommand(command, "*%s,025,C,1#", Command.KEY_DEVICE_PASSWORD); case Command.TYPE_ENGINE_RESUME: - return formatCommand(command, "*{%s},025,C,0#", Command.KEY_DEVICE_PASSWORD); + return formatCommand(command, "*%s,025,C,0#", Command.KEY_DEVICE_PASSWORD); default: return null; } diff --git a/src/main/java/org/traccar/protocol/TzoneProtocolDecoder.java b/src/main/java/org/traccar/protocol/TzoneProtocolDecoder.java index 87b44a4b2..4f6854098 100644 --- a/src/main/java/org/traccar/protocol/TzoneProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/TzoneProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * Copyright 2015 - 2019 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,6 +23,7 @@ import org.traccar.DeviceSession; import org.traccar.Protocol; import org.traccar.helper.BitUtil; import org.traccar.helper.DateBuilder; +import org.traccar.helper.UnitsConverter; import org.traccar.model.CellTower; import org.traccar.model.Network; import org.traccar.model.Position; @@ -67,7 +68,17 @@ public class TzoneProtocolDecoder extends BaseProtocolDecoder { return false; } - position.set(Position.KEY_SATELLITES, buf.readUnsignedByte()); + if (hardware == 0x413) { + buf.readUnsignedByte(); // status + } else { + position.set(Position.KEY_SATELLITES, buf.readUnsignedByte()); + } + + if (hardware == 0x413) { + position.setFixTime(new DateBuilder() + .setDate(buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte()) + .setTime(buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte()).getDate()); + } double lat; double lon; @@ -80,25 +91,39 @@ public class TzoneProtocolDecoder extends BaseProtocolDecoder { lon = buf.readUnsignedInt() / 100000.0 / 60.0; } - position.setFixTime(new DateBuilder() - .setDate(buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte()) - .setTime(buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte()).getDate()); + if (hardware == 0x413) { - position.setSpeed(buf.readUnsignedShort() * 0.01); + position.set(Position.KEY_HDOP, buf.readUnsignedShort() * 0.1); - position.set(Position.KEY_ODOMETER, buf.readUnsignedMedium()); + position.setAltitude(buf.readUnsignedShort()); + position.setCourse(buf.readUnsignedShort()); + position.setSpeed(UnitsConverter.knotsFromKph(buf.readUnsignedShort() * 0.1)); + + position.set(Position.KEY_SATELLITES, buf.readUnsignedByte()); + + } else { + + position.setFixTime(new DateBuilder() + .setDate(buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte()) + .setTime(buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte()).getDate()); + + position.setSpeed(buf.readUnsignedShort() * 0.01); + + position.set(Position.KEY_ODOMETER, buf.readUnsignedMedium()); + + int flags = buf.readUnsignedShort(); + position.setCourse(BitUtil.to(flags, 9)); + if (!BitUtil.check(flags, 10)) { + lat = -lat; + } + position.setLatitude(lat); + if (BitUtil.check(flags, 9)) { + lon = -lon; + } + position.setLongitude(lon); + position.setValid(BitUtil.check(flags, 11)); - int flags = buf.readUnsignedShort(); - position.setCourse(BitUtil.to(flags, 9)); - if (!BitUtil.check(flags, 10)) { - lat = -lat; - } - position.setLatitude(lat); - if (BitUtil.check(flags, 9)) { - lon = -lon; } - position.setLongitude(lon); - position.setValid(BitUtil.check(flags, 11)); buf.readerIndex(blockEnd); diff --git a/src/main/java/org/traccar/protocol/UlbotechProtocolDecoder.java b/src/main/java/org/traccar/protocol/UlbotechProtocolDecoder.java index 0a2a59e23..7fec0bf8b 100644 --- a/src/main/java/org/traccar/protocol/UlbotechProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/UlbotechProtocolDecoder.java @@ -237,12 +237,13 @@ public class UlbotechProtocolDecoder extends BaseProtocolDecoder { case DATA_GPS: hasLocation = true; - position.setValid(true); position.setLatitude(buf.readInt() / 1000000.0); position.setLongitude(buf.readInt() / 1000000.0); position.setSpeed(UnitsConverter.knotsFromKph(buf.readUnsignedShort())); position.setCourse(buf.readUnsignedShort()); - position.set(Position.KEY_HDOP, buf.readUnsignedShort()); + int hdop = buf.readUnsignedShort(); + position.setValid(hdop < 9999); + position.set(Position.KEY_HDOP, hdop * 0.01); break; case DATA_LBS: diff --git a/src/main/java/org/traccar/protocol/UproProtocolDecoder.java b/src/main/java/org/traccar/protocol/UproProtocolDecoder.java index 873b22006..a17003fc8 100644 --- a/src/main/java/org/traccar/protocol/UproProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/UproProtocolDecoder.java @@ -171,12 +171,31 @@ public class UproProtocolDecoder extends BaseProtocolDecoder { position.setAltitude( Integer.parseInt(data.readSlice(6).toString(StandardCharsets.US_ASCII)) * 0.1); break; + case 'J': + if (data.readableBytes() == 6) { + char index = (char) data.readUnsignedByte(); + int status = data.readUnsignedByte(); + double value = Integer.parseInt(data.readSlice(4).toString(StandardCharsets.US_ASCII)) * 0.1; + if (BitUtil.check(status, 0)) { + value = -value; + } + position.set(Position.PREFIX_TEMP + index, value); + } + break; case 'K': position.set("statusExtended", data.toString(StandardCharsets.US_ASCII)); break; case 'M': - position.set(Position.KEY_BATTERY_LEVEL, - Integer.parseInt(data.readSlice(3).toString(StandardCharsets.US_ASCII)) * 0.1); + if (data.readableBytes() == 3) { + position.set(Position.KEY_BATTERY_LEVEL, + Integer.parseInt(data.readSlice(3).toString(StandardCharsets.US_ASCII)) * 0.1); + } else if (data.readableBytes() == 4) { + char index = (char) data.readUnsignedByte(); + data.readUnsignedByte(); // status + position.set( + "humidity" + index, + Integer.parseInt(data.readSlice(2).toString(StandardCharsets.US_ASCII))); + } break; case 'N': position.set(Position.KEY_RSSI, diff --git a/src/main/java/org/traccar/protocol/VnetProtocol.java b/src/main/java/org/traccar/protocol/VnetProtocol.java new file mode 100644 index 000000000..0fed30e13 --- /dev/null +++ b/src/main/java/org/traccar/protocol/VnetProtocol.java @@ -0,0 +1,37 @@ +/* + * Copyright 2019 Anton Tananaev (anton@traccar.org) + * + * 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.protocol; + +import io.netty.handler.codec.LengthFieldBasedFrameDecoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +import java.nio.ByteOrder; + +public class VnetProtocol extends BaseProtocol { + + public VnetProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new LengthFieldBasedFrameDecoder(ByteOrder.LITTLE_ENDIAN, 1500, 4, 2, 12, 0, true)); + pipeline.addLast(new VnetProtocolDecoder(VnetProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/VnetProtocolDecoder.java b/src/main/java/org/traccar/protocol/VnetProtocolDecoder.java new file mode 100644 index 000000000..1ee00bd3f --- /dev/null +++ b/src/main/java/org/traccar/protocol/VnetProtocolDecoder.java @@ -0,0 +1,105 @@ +/* + * Copyright 2019 Anton Tananaev (anton@traccar.org) + * + * 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.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufUtil; +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.NetworkMessage; +import org.traccar.Protocol; +import org.traccar.helper.BcdUtil; +import org.traccar.helper.BitUtil; +import org.traccar.helper.DateBuilder; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.Position; + +import java.net.SocketAddress; + +public class VnetProtocolDecoder extends BaseProtocolDecoder { + + public VnetProtocolDecoder(Protocol protocol) { + super(protocol); + } + + public static final int MSG_LOGIN = 0x0000; + public static final int MSG_LBS = 0x32; + public static final int MSG_GPS = 0x33; + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + + buf.skipBytes(2); // header + int type = BitUtil.to(buf.readUnsignedShortLE(), 15); + buf.readUnsignedShortLE(); // length + + DateBuilder dateBuilder = new DateBuilder() + .setDateReverse(BcdUtil.readInteger(buf, 2), BcdUtil.readInteger(buf, 2), BcdUtil.readInteger(buf, 2)) + .setTime(BcdUtil.readInteger(buf, 2), BcdUtil.readInteger(buf, 2), BcdUtil.readInteger(buf, 2)); + + if (type == MSG_LOGIN) { + + String imei = ByteBufUtil.hexDump(buf.readSlice(8)).substring(0, 15); + getDeviceSession(channel, remoteAddress, imei); + if (channel != null) { + channel.writeAndFlush(new NetworkMessage( + buf.retainedSlice(0, buf.writerIndex()), channel.remoteAddress())); + } + + } else if (type == MSG_GPS) { + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress); + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + position.setTime(dateBuilder.getDate()); + + int value; + int degrees; + + value = BcdUtil.readInteger(buf, 8); + degrees = value / 1000000; + double lat = degrees + value % 1000000 * 0.0001 / 60; + + value = BcdUtil.readInteger(buf, 10); + degrees = value / 10000000; + double lon = degrees + value % 10000000 * 0.00001 / 60; + + int flags = buf.readUnsignedByte(); + position.setValid(BitUtil.check(flags, 0)); + position.setLatitude(BitUtil.check(flags, 1) ? lat : -lat); + position.setLongitude(BitUtil.check(flags, 2) ? lon : -lon); + + position.set(Position.KEY_SATELLITES, buf.readUnsignedByte()); + position.setSpeed(UnitsConverter.knotsFromKph(buf.readUnsignedByte())); + position.set(Position.KEY_ODOMETER, buf.readUnsignedIntLE()); + position.setCourse(buf.readUnsignedByte() * 2); + + return position; + + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/WatchProtocolDecoder.java b/src/main/java/org/traccar/protocol/WatchProtocolDecoder.java index 70b207e9b..0647afdee 100644 --- a/src/main/java/org/traccar/protocol/WatchProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/WatchProtocolDecoder.java @@ -249,8 +249,7 @@ public class WatchProtocolDecoder extends BaseProtocolDecoder { } } - } else if (type.equals("UD") || type.equals("UD2") || type.equals("UD3") - || type.equals("AL") || type.equals("WT")) { + } else if (type.startsWith("UD") || type.equals("AL") || type.equals("WT")) { Position position = decodePosition(deviceSession, buf.toString(StandardCharsets.US_ASCII)); @@ -267,7 +266,10 @@ public class WatchProtocolDecoder extends BaseProtocolDecoder { sendResponse(channel, id, index, "TKQ"); - } else if (type.equals("PULSE") || type.equals("heart") || type.equals("bphrt")) { + } else if (type.equalsIgnoreCase("PULSE") + || type.equalsIgnoreCase("HEART") + || type.equalsIgnoreCase("BLOOD") + || type.equalsIgnoreCase("BPHRT")) { if (buf.isReadable()) { @@ -279,11 +281,14 @@ public class WatchProtocolDecoder extends BaseProtocolDecoder { String[] values = buf.toString(StandardCharsets.US_ASCII).split(","); int valueIndex = 0; - if (type.equals("bphrt")) { + if (type.equalsIgnoreCase("BPHRT") || type.equalsIgnoreCase("BLOOD")) { position.set("pressureHigh", values[valueIndex++]); position.set("pressureLow", values[valueIndex++]); } - position.set(Position.KEY_HEART_RATE, Integer.parseInt(values[valueIndex])); + + if (valueIndex <= values.length - 1) { + position.set(Position.KEY_HEART_RATE, Integer.parseInt(values[valueIndex])); + } return position; diff --git a/src/main/java/org/traccar/protocol/WatchProtocolEncoder.java b/src/main/java/org/traccar/protocol/WatchProtocolEncoder.java index b433dfd2a..f285267ba 100644 --- a/src/main/java/org/traccar/protocol/WatchProtocolEncoder.java +++ b/src/main/java/org/traccar/protocol/WatchProtocolEncoder.java @@ -137,33 +137,33 @@ public class WatchProtocolEncoder extends StringProtocolEncoder implements Strin case Command.TYPE_POSITION_SINGLE: return formatTextCommand(channel, command, "RG"); case Command.TYPE_SOS_NUMBER: - return formatTextCommand(channel, command, "SOS{%s},{%s}", Command.KEY_INDEX, Command.KEY_PHONE); + return formatTextCommand(channel, command, "SOS%s,%s", Command.KEY_INDEX, Command.KEY_PHONE); case Command.TYPE_ALARM_SOS: - return formatTextCommand(channel, command, "SOSSMS,{%s}", Command.KEY_ENABLE); + return formatTextCommand(channel, command, "SOSSMS,%s", Command.KEY_ENABLE); case Command.TYPE_ALARM_BATTERY: - return formatTextCommand(channel, command, "LOWBAT,{%s}", Command.KEY_ENABLE); + return formatTextCommand(channel, command, "LOWBAT,%s", Command.KEY_ENABLE); case Command.TYPE_REBOOT_DEVICE: return formatTextCommand(channel, command, "RESET"); case Command.TYPE_POWER_OFF: return formatTextCommand(channel, command, "POWEROFF"); case Command.TYPE_ALARM_REMOVE: - return formatTextCommand(channel, command, "REMOVE,{%s}", Command.KEY_ENABLE); + return formatTextCommand(channel, command, "REMOVE,%s", Command.KEY_ENABLE); case Command.TYPE_SILENCE_TIME: - return formatTextCommand(channel, command, "SILENCETIME,{%s}", Command.KEY_DATA); + return formatTextCommand(channel, command, "SILENCETIME,%s", Command.KEY_DATA); case Command.TYPE_ALARM_CLOCK: - return formatTextCommand(channel, command, "REMIND,{%s}", Command.KEY_DATA); + return formatTextCommand(channel, command, "REMIND,%s", Command.KEY_DATA); case Command.TYPE_SET_PHONEBOOK: - return formatTextCommand(channel, command, "PHB,{%s}", Command.KEY_DATA); + return formatTextCommand(channel, command, "PHB,%s", Command.KEY_DATA); case Command.TYPE_MESSAGE: - return formatTextCommand(channel, command, "MESSAGE,{%s}", Command.KEY_MESSAGE); + return formatTextCommand(channel, command, "MESSAGE,%s", Command.KEY_MESSAGE); case Command.TYPE_VOICE_MESSAGE: return formatBinaryCommand(channel, command, "TK,", getBinaryData(command)); case Command.TYPE_POSITION_PERIODIC: - return formatTextCommand(channel, command, "UPLOAD,{%s}", Command.KEY_FREQUENCY); + return formatTextCommand(channel, command, "UPLOAD,%s", Command.KEY_FREQUENCY); case Command.TYPE_SET_TIMEZONE: - return formatTextCommand(channel, command, "LZ,,{%s}", Command.KEY_TIMEZONE); + return formatTextCommand(channel, command, "LZ,%s,%s", Command.KEY_LANGUAGE, Command.KEY_TIMEZONE); case Command.TYPE_SET_INDICATOR: - return formatTextCommand(channel, command, "FLOWER,{%s}", Command.KEY_DATA); + return formatTextCommand(channel, command, "FLOWER,%s", Command.KEY_DATA); default: return null; } diff --git a/src/main/java/org/traccar/protocol/WialonProtocolEncoder.java b/src/main/java/org/traccar/protocol/WialonProtocolEncoder.java index c45edf00d..93086bf8a 100644 --- a/src/main/java/org/traccar/protocol/WialonProtocolEncoder.java +++ b/src/main/java/org/traccar/protocol/WialonProtocolEncoder.java @@ -32,11 +32,11 @@ public class WialonProtocolEncoder extends StringProtocolEncoder { case Command.TYPE_REBOOT_DEVICE: return formatCommand(command, "reboot\r\n"); case Command.TYPE_SEND_USSD: - return formatCommand(command, "USSD:{%s}\r\n", Command.KEY_PHONE); + return formatCommand(command, "USSD:%s\r\n", Command.KEY_PHONE); case Command.TYPE_IDENTIFICATION: return formatCommand(command, "VER?\r\n"); case Command.TYPE_OUTPUT_CONTROL: - return formatCommand(command, "L{%s}={%s}\r\n", Command.KEY_INDEX, Command.KEY_DATA); + return formatCommand(command, "L%s=%s\r\n", Command.KEY_INDEX, Command.KEY_DATA); default: return null; } diff --git a/src/main/java/org/traccar/protocol/WondexProtocolEncoder.java b/src/main/java/org/traccar/protocol/WondexProtocolEncoder.java index e9bb23d15..21f1ee321 100644 --- a/src/main/java/org/traccar/protocol/WondexProtocolEncoder.java +++ b/src/main/java/org/traccar/protocol/WondexProtocolEncoder.java @@ -32,17 +32,17 @@ public class WondexProtocolEncoder extends StringProtocolEncoder { switch (command.getType()) { case Command.TYPE_REBOOT_DEVICE: - return formatCommand(command, "$WP+REBOOT={%s}", Command.KEY_DEVICE_PASSWORD); + return formatCommand(command, "$WP+REBOOT=%s", Command.KEY_DEVICE_PASSWORD); case Command.TYPE_GET_DEVICE_STATUS: - return formatCommand(command, "$WP+TEST={%s}", Command.KEY_DEVICE_PASSWORD); + return formatCommand(command, "$WP+TEST=%s", Command.KEY_DEVICE_PASSWORD); case Command.TYPE_GET_MODEM_STATUS: - return formatCommand(command, "$WP+GSMINFO={%s}", Command.KEY_DEVICE_PASSWORD); + return formatCommand(command, "$WP+GSMINFO=%s", Command.KEY_DEVICE_PASSWORD); case Command.TYPE_IDENTIFICATION: - return formatCommand(command, "$WP+IMEI={%s}", Command.KEY_DEVICE_PASSWORD); + return formatCommand(command, "$WP+IMEI=%s", Command.KEY_DEVICE_PASSWORD); case Command.TYPE_POSITION_SINGLE: - return formatCommand(command, "$WP+GETLOCATION={%s}", Command.KEY_DEVICE_PASSWORD); + return formatCommand(command, "$WP+GETLOCATION=%s", Command.KEY_DEVICE_PASSWORD); case Command.TYPE_GET_VERSION: - return formatCommand(command, "$WP+VER={%s}", Command.KEY_DEVICE_PASSWORD); + return formatCommand(command, "$WP+VER=%s", Command.KEY_DEVICE_PASSWORD); default: return null; } diff --git a/src/main/java/org/traccar/protocol/XexunProtocolEncoder.java b/src/main/java/org/traccar/protocol/XexunProtocolEncoder.java index fc849fe15..4f2707c2a 100644 --- a/src/main/java/org/traccar/protocol/XexunProtocolEncoder.java +++ b/src/main/java/org/traccar/protocol/XexunProtocolEncoder.java @@ -32,9 +32,9 @@ public class XexunProtocolEncoder extends StringProtocolEncoder { switch (command.getType()) { case Command.TYPE_ENGINE_STOP: - return formatCommand(command, "powercar{%s} 11", Command.KEY_DEVICE_PASSWORD); + return formatCommand(command, "powercar%s 11", Command.KEY_DEVICE_PASSWORD); case Command.TYPE_ENGINE_RESUME: - return formatCommand(command, "powercar{%s} 00", Command.KEY_DEVICE_PASSWORD); + return formatCommand(command, "powercar%s 00", Command.KEY_DEVICE_PASSWORD); default: return null; } diff --git a/src/main/java/org/traccar/protocol/Xrb28ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Xrb28ProtocolDecoder.java index 938394d6b..69e5b7372 100644 --- a/src/main/java/org/traccar/protocol/Xrb28ProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/Xrb28ProtocolDecoder.java @@ -47,7 +47,7 @@ public class Xrb28ProtocolDecoder extends BaseProtocolDecoder { .expression("..,") // vendor .number("d{15},") // imei .expression("..,") // type - .number("0,") // reserved + .number("[01],") // reserved .number("(dd)(dd)(dd).d+,") // time (hhmmss) .expression("([AV]),") // validity .number("(dd)(dd.d+),") // latitude |