From b3d221372103a677002428a57686b61e3facf32c Mon Sep 17 00:00:00 2001 From: Anton Tananaev Date: Wed, 15 May 2013 22:18:04 +1200 Subject: Initial apel support --- default.cfg | 4 + src/org/traccar/ServerManager.java | 15 ++ src/org/traccar/protocol/ApelProtocolDecoder.java | 298 +++++++++++++++++++++ .../traccar/protocol/ApelProtocolDecoderTest.java | 28 ++ 4 files changed, 345 insertions(+) create mode 100644 src/org/traccar/protocol/ApelProtocolDecoder.java create mode 100644 test/org/traccar/protocol/ApelProtocolDecoderTest.java diff --git a/default.cfg b/default.cfg index 487e9fe23..112a76931 100644 --- a/default.cfg +++ b/default.cfg @@ -225,4 +225,8 @@ true 5040 + + true + 5041 + diff --git a/src/org/traccar/ServerManager.java b/src/org/traccar/ServerManager.java index ac97da4f2..971859099 100644 --- a/src/org/traccar/ServerManager.java +++ b/src/org/traccar/ServerManager.java @@ -147,6 +147,7 @@ public class ServerManager { initXt7Server("xt7"); initWialonServer("wialon"); initCarscopServer("carscop"); + initApelServer("apel"); // Initialize web server if (Boolean.valueOf(properties.getProperty("http.enable"))) { @@ -842,4 +843,18 @@ public class ServerManager { } } + private void initApelServer(String protocol) throws SQLException { + if (isProtocolEnabled(properties, protocol)) { + TrackerServer server = new TrackerServer(this, new ServerBootstrap(), protocol) { + @Override + protected void addSpecificHandlers(ChannelPipeline pipeline) { + pipeline.addLast("frameDecoder", new LengthFieldBasedFrameDecoder(1024, 2, 2, 4, 0)); + pipeline.addLast("objectDecoder", new ApelProtocolDecoder(ServerManager.this)); + } + }; + server.setEndianness(ByteOrder.LITTLE_ENDIAN); + serverList.add(server); + } + } + } diff --git a/src/org/traccar/protocol/ApelProtocolDecoder.java b/src/org/traccar/protocol/ApelProtocolDecoder.java new file mode 100644 index 000000000..fec92b33e --- /dev/null +++ b/src/org/traccar/protocol/ApelProtocolDecoder.java @@ -0,0 +1,298 @@ +/* + * Copyright 2013 Anton Tananaev (anton.tananaev@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import java.nio.ByteOrder; +import java.nio.charset.Charset; +import java.sql.ResultSet; +import java.util.Calendar; +import java.util.LinkedList; +import java.util.List; +import java.util.Properties; +import java.util.TimeZone; +import org.jboss.netty.buffer.ChannelBuffer; +import org.jboss.netty.buffer.ChannelBuffers; +import org.jboss.netty.channel.Channel; +import org.jboss.netty.channel.ChannelHandlerContext; +import org.traccar.BaseProtocolDecoder; +import org.traccar.ServerManager; +import org.traccar.helper.AdvancedConnection; +import org.traccar.helper.Log; +import org.traccar.helper.NamedParameterStatement; +import org.traccar.model.ExtendedInfoFormatter; +import org.traccar.model.Position; + +public class ApelProtocolDecoder extends BaseProtocolDecoder { + + private long deviceId; + private long lastIndex; + private long newIndex; + + public ApelProtocolDecoder(ServerManager serverManager) { + super(serverManager); + } + + /* + * Message types + */ + /*private static final int MSG_NULL = 0; + private static final int MSG_IDENT = 1; + private static final int MSG_IDENT_FULL = 2; + private static final int MSG_POINT = 10; + private static final int MSG_LOG_SYNC = 100; + private static final int MSG_LOGMSG = 101; + private static final int MSG_TEXT = 102; + private static final int MSG_ALARM = 200; + private static final int MSG_ALARM_RECIEVED = 201;*/ + private static final int MSG_TYPE_NULL = 0; + private static final int MSG_TYPE_REQUEST_TRACKER_ID = 10; + private static final int MSG_TYPE_TRACKER_ID = 11; + private static final int MSG_TYPE_TRACKER_ID_EXT = 12; + private static final int MSG_TYPE_DISCONNECT = 20; + private static final int MSG_TYPE_REQUEST_PASSWORD = 30; + private static final int MSG_TYPE_PASSWORD = 31; + private static final int MSG_TYPE_REQUEST_STATE_FULL_INFO = 90; + private static final int MSG_TYPE_STATE_FULL_INFO_T104 = 92; + private static final int MSG_TYPE_REQUEST_CURRENT_GPS_DATA = 100; + private static final int MSG_TYPE_CURRENT_GPS_DATA = 101; + private static final int MSG_TYPE_REQUEST_SENSORS_STATE = 110; + private static final int MSG_TYPE_SENSORS_STATE = 111; + private static final int MSG_TYPE_SENSORS_STATE_T100 = 112; + private static final int MSG_TYPE_SENSORS_STATE_T100_4 = 113; + private static final int MSG_TYPE_REQUEST_LAST_LOG_INDEX = 120; + private static final int MSG_TYPE_LAST_LOG_INDEX = 121; + private static final int MSG_TYPE_REQUEST_LOG_RECORDS = 130; + private static final int MSG_TYPE_LOG_RECORDS = 131; + private static final int MSG_TYPE_EVENT = 141; + private static final int MSG_TYPE_TEXT = 150; + private static final int MSG_TYPE_ACK_ALARM = 160; + private static final int MSG_TYPE_SET_TRACKER_MODE = 170; + private static final int MSG_TYPE_GPRS_COMMAND = 180; + + private static final String HEX_CHARS = "0123456789ABCDEF"; + + /*private void loadLastIndex() { + try { + Properties p = getServerManager().getProperties(); + if (p.contains("database.selectLastIndex")) { + AdvancedConnection connection = new AdvancedConnection( + p.getProperty("database.url"), p.getProperty("database.user"), p.getProperty("database.password")); + NamedParameterStatement queryLastIndex = new NamedParameterStatement(connection, p.getProperty("database.selectLastIndex")); + queryLastIndex.prepare(); + queryLastIndex.setLong("device_id", deviceId); + ResultSet result = queryLastIndex.executeQuery(); + if (result.next()) { + lastIndex = result.getLong(1); + } + } + } catch(Exception error) { + } + }*/ + + /*private void requestArchive(Channel channel) { + if (lastIndex == 0) { + lastIndex = newIndex; + } else if (newIndex > lastIndex) { + ChannelBuffer request = ChannelBuffers.directBuffer(ByteOrder.LITTLE_ENDIAN, 12); + request.writeShort(MSG_LOG_SYNC); + request.writeShort(4); + request.writeInt((int) lastIndex); + request.writeInt(0); + channel.write(request); + } + }*/ + + @Override + protected Object decode( + ChannelHandlerContext ctx, Channel channel, Object msg) + throws Exception { + + ChannelBuffer buf = (ChannelBuffer) msg; + int type = buf.readUnsignedShort(); + boolean alarm = (type & 0x8000) != 0; + type = type & 0x7FFF; + buf.readUnsignedShort(); // length + + if (type == MSG_TYPE_TRACKER_ID) { + Log.warning("Unsupported authentication type"); + return null; + } + + if (type == MSG_TYPE_TRACKER_ID_EXT) { + long id = buf.readUnsignedInt(); + int length = buf.readUnsignedShort(); + buf.skipBytes(length); + length = buf.readUnsignedShort(); + String imei = buf.readBytes(length).toString(Charset.defaultCharset()); + try { + deviceId = getDataManager().getDeviceByImei(imei).getId(); + //loadLastIndex(); + } catch(Exception error) { + Log.warning("Unknown device - " + imei + " (id - " + id + ")"); + } + } + + // Position + else if (deviceId != 0 && (type == MSG_TYPE_CURRENT_GPS_DATA || type == MSG_TYPE_STATE_FULL_INFO_T104)) { + List positions = new LinkedList(); + + int recordCount = 1; + /*if (type == MSG_LOGMSG) { + recordCount = buf.readUnsignedShort(); + }*/ + + for (int j = 0; j < recordCount; j++) { + Position position = new Position(); + ExtendedInfoFormatter extendedInfo = new ExtendedInfoFormatter("apel"); + position.setDeviceId(deviceId); + + // Message index + /*if (type == MSG_LOGMSG) { + extendedInfo.set("archive", true); + int subtype = buf.readUnsignedShort(); + if (subtype == MSG_ALARM) { + extendedInfo.set("alarm", true); + } + if (buf.readUnsignedShort() > buf.readableBytes()) { + lastIndex += 1; + break; // workaround for device bug + } + lastIndex = buf.readUnsignedInt(); + position.setId(lastIndex); + } else { + newIndex = buf.readUnsignedInt(); + }*/ + + // Time + Calendar time = Calendar.getInstance(TimeZone.getTimeZone("UTC")); + time.clear(); + time.setTimeInMillis(buf.readUnsignedInt() * 1000); + position.setTime(time.getTime()); + + // Latitude + position.setLatitude(buf.readInt() * 180.0 / 0x7FFFFFFF); + + // Longitude + position.setLongitude(buf.readInt() * 180.0 / 0x7FFFFFFF); + + // Speed and Validity + if (type == MSG_TYPE_CURRENT_GPS_DATA) { + int speed = buf.readShort(); + position.setValid(speed != -1); + position.setSpeed(speed / 100.0 * 0.539957); + } else if (type == MSG_TYPE_STATE_FULL_INFO_T104) { + int speed = buf.readUnsignedByte(); + position.setValid(speed != 255); + position.setSpeed(speed * 0.539957); + extendedInfo.set("hdop", buf.readByte()); + } + + // Course + position.setCourse(buf.readShort() / 100.0); + + // Altitude + position.setAltitude((double) buf.readShort()); + + if (type == MSG_TYPE_STATE_FULL_INFO_T104) { + + // Satellites + extendedInfo.set("satellites", buf.readUnsignedByte()); + + // Cell signal + extendedInfo.set("gsm", buf.readUnsignedByte()); + + // Event type + extendedInfo.set("event", buf.readUnsignedShort()); + + // Milage + extendedInfo.set("milage", buf.readUnsignedInt()); + + // Input/Output + extendedInfo.set("input", buf.readUnsignedByte()); + extendedInfo.set("output", buf.readUnsignedByte()); + + // Analog sensors + for (int i = 1; i <= 8; i++) { + extendedInfo.set("adc" + i, buf.readUnsignedShort()); + } + + // Counters + extendedInfo.set("c0", buf.readUnsignedInt()); + extendedInfo.set("c1", buf.readUnsignedInt()); + extendedInfo.set("c2", buf.readUnsignedInt()); + + /*long extraFlags = buf.readLong(); + + // Analog inputs + if ((extraFlags & 0x1) == 0x1) { + int count = buf.readUnsignedShort(); + for (int i = 1; i <= count; i++) { + extendedInfo.set("adc" + i, buf.readUnsignedShort()); + } + + } + + // CAN adapter + if ((extraFlags & 0x2) == 0x2) { + int size = buf.readUnsignedShort(); + extendedInfo.set("can", buf.toString(buf.readerIndex(), size, Charset.defaultCharset())); + buf.skipBytes(size); + } + + // Passenger sensor + if ((extraFlags & 0x4) == 0x4) { + int size = buf.readUnsignedShort(); + + // Convert binary data to hex + StringBuilder hex = new StringBuilder(); + for (int i = buf.readerIndex(); i < buf.readerIndex() + size; i++) { + byte b = buf.getByte(i); + hex.append(HEX_CHARS.charAt((b & 0xf0) >> 4)); + hex.append(HEX_CHARS.charAt((b & 0x0F))); + } + + extendedInfo.set("passenger", hex); + + buf.skipBytes(size); + } + + // Send response for alarm message + if (type == MSG_ALARM) { + byte[] response = {(byte)0xC9,0x00,0x00,0x00,0x00,0x00,0x00,0x00}; + channel.write(ChannelBuffers.wrappedBuffer(response)); + + extendedInfo.set("alarm", true); + }*/ + } + + // Skip CRC + buf.readUnsignedInt(); + + // Extended info + position.setExtendedInfo(extendedInfo.toString()); + + positions.add(position); + } + + //requestArchive(channel); + + return positions; + } + + return null; + } + +} diff --git a/test/org/traccar/protocol/ApelProtocolDecoderTest.java b/test/org/traccar/protocol/ApelProtocolDecoderTest.java new file mode 100644 index 000000000..1cd1291f4 --- /dev/null +++ b/test/org/traccar/protocol/ApelProtocolDecoderTest.java @@ -0,0 +1,28 @@ +package org.traccar.protocol; + +import java.nio.ByteOrder; +import org.jboss.netty.buffer.ChannelBuffers; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import org.junit.Test; + +public class ApelProtocolDecoderTest { + + @Test + public void testDecode() throws Exception { + + ApelProtocolDecoder decoder = new ApelProtocolDecoder(null); + decoder.setDataManager(new TestDataManager()); + + /*byte[] buf1 = {0x40,0x4E,0x54,0x43,0x01,0x00,0x00,0x00,0x7B,0x00,0x00,0x00,0x13,0x00,0x44,0x34,0x2A,0x3E,0x53,0x3A,0x38,0x36,0x31,0x37,0x38,0x35,0x30,0x30,0x35,0x32,0x30,0x35,0x30,0x37,0x39}; + assertNull(decoder.decode(null, null, ChannelBuffers.wrappedBuffer(ByteOrder.LITTLE_ENDIAN, buf1))); + + byte[] buf2 = {0x40,0x4E,0x54,0x43,0x01,0x00,0x00,0x00,0x7B,0x00,0x00,0x00,0x5A,0x00,0x50,0x69,0x2A,0x3E,0x41,0x01,0x25,(byte)0xDB,0x0E,0x00,0x00,0x00,0x15,0x11,0x07,0x07,0x11,0x0A,0x0C,0x08,(byte)0x80,0x63,0x00,0x00,(byte)0xAA,0x39,(byte)0xA2,0x38,0x16,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0C,0x11,0x07,0x08,0x11,0x0A,0x0C,(byte)0xB3,(byte)0x89,(byte)0x79,0x3F,0x1A,(byte)0xEF,0x26,0x3F,0x00,0x00,0x00,0x00,0x12,0x00,0x34,(byte)0xF5,0x16,0x44,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,(byte)0xFA,(byte)0xFF,0x00,0x00,0x00,(byte)0xFA,(byte)0xFF,0x00,0x00,0x00,(byte)0xFA,(byte)0xFF,(byte)0x80,(byte)0x80,(byte)0x80,(byte)0x80}; + assertNotNull(decoder.decode(null, null, ChannelBuffers.wrappedBuffer(ByteOrder.LITTLE_ENDIAN, buf2))); + + byte[] buf3 = {0x40,0x4E,0x54,0x43,0x01,0x00,0x00,0x00,0x7B,0x00,0x00,0x00,0x13,0x00,0x47,0x37,0x2A,0x3E,0x53,0x3A,0x38,0x36,0x31,0x37,0x38,0x35,0x30,0x30,0x35,0x31,0x32,0x36,0x30,0x36,0x39}; + assertNull(decoder.decode(null, null, ChannelBuffers.wrappedBuffer(ByteOrder.LITTLE_ENDIAN, buf3)));*/ + + } + +} -- cgit v1.2.3