diff options
author | Anton Tananaev <anton.tananaev@gmail.com> | 2012-12-04 21:24:41 +1300 |
---|---|---|
committer | Anton Tananaev <anton.tananaev@gmail.com> | 2012-12-04 21:24:41 +1300 |
commit | 8d49ced822a1ae5c8b3f065178116e95a4f55c85 (patch) | |
tree | c5ab665fa17511ce808e34d2544ac6e1eb252061 | |
parent | 2b6d8cb12c6a307b4ee9a0af12101042ec78803c (diff) | |
download | trackermap-server-8d49ced822a1ae5c8b3f065178116e95a4f55c85.tar.gz trackermap-server-8d49ced822a1ae5c8b3f065178116e95a4f55c85.tar.bz2 trackermap-server-8d49ced822a1ae5c8b3f065178116e95a4f55c85.zip |
Added Navis protocol (fix #99)
-rw-r--r-- | default.cfg | 4 | ||||
-rw-r--r-- | src/org/traccar/Server.java | 27 | ||||
-rw-r--r-- | src/org/traccar/TrackerEventHandler.java | 58 | ||||
-rw-r--r-- | src/org/traccar/protocol/NavisProtocolDecoder.java | 348 | ||||
-rwxr-xr-x | test.sh | 2 | ||||
-rw-r--r-- | test/org/traccar/protocol/NavisProtocolDecoderTest.java | 24 |
6 files changed, 436 insertions, 27 deletions
diff --git a/default.cfg b/default.cfg index 8934d93df..8e2d11ec5 100644 --- a/default.cfg +++ b/default.cfg @@ -167,5 +167,9 @@ <!-- TR20 server configuration --> <entry key="tr20.enable">true</entry> <entry key="tr20.port">5018</entry> + + <!-- Navis server configuration --> + <entry key="navis.enable">true</entry> + <entry key="navis.port">5019</entry> </properties> diff --git a/src/org/traccar/Server.java b/src/org/traccar/Server.java index 38f0a1388..b80d22c8f 100644 --- a/src/org/traccar/Server.java +++ b/src/org/traccar/Server.java @@ -17,6 +17,7 @@ package org.traccar; import java.io.FileInputStream; import java.io.IOException; +import java.nio.ByteOrder; import java.sql.SQLException; import java.text.DateFormat; import java.text.SimpleDateFormat; @@ -105,6 +106,7 @@ public class Server { initV680Server(properties); initPt502Server(properties); initTr20Server(properties); + initNavisServer(properties); // Initialize web server if (Boolean.valueOf(properties.getProperty("http.enable"))) { @@ -517,7 +519,7 @@ public class Server { if (isProtocolEnabled(properties, protocol)) { TrackerServer server = new TrackerServer(getProtocolPort(properties, protocol)); - server.setEndianness(java.nio.ByteOrder.LITTLE_ENDIAN); + server.setEndianness(ByteOrder.LITTLE_ENDIAN); final Integer resetDelay = getProtocolResetDelay(properties, protocol); server.setPipelineFactory(new GenericPipelineFactory(server, dataManager, isLoggerEnabled(), resetDelay, geocoder) { @@ -679,4 +681,27 @@ public class Server { serverList.add(server); } } + + /** + * Init Navis server + */ + private void initNavisServer(Properties properties) throws SQLException { + + String protocol = "navis"; + if (isProtocolEnabled(properties, protocol)) { + + TrackerServer server = new TrackerServer(getProtocolPort(properties, protocol)); + server.setEndianness(ByteOrder.LITTLE_ENDIAN); + final Integer resetDelay = getProtocolResetDelay(properties, protocol); + + server.setPipelineFactory(new GenericPipelineFactory(server, dataManager, isLoggerEnabled(), resetDelay, geocoder) { + protected void addSpecificHandlers(ChannelPipeline pipeline) { + pipeline.addLast("frameDecoder", new LengthFieldBasedFrameDecoder(4 * 1024, 12, 2, 2, 0)); + pipeline.addLast("objectDecoder", new NavisProtocolDecoder(getDataManager())); + } + }); + + serverList.add(server); + } + } } diff --git a/src/org/traccar/TrackerEventHandler.java b/src/org/traccar/TrackerEventHandler.java index 6483e52e7..11cf41fad 100644 --- a/src/org/traccar/TrackerEventHandler.java +++ b/src/org/traccar/TrackerEventHandler.java @@ -15,6 +15,7 @@ */ package org.traccar; +import java.util.List; import org.jboss.netty.channel.*; import org.jboss.netty.handler.timeout.IdleStateAwareChannelHandler; import org.jboss.netty.handler.timeout.IdleStateEvent; @@ -37,36 +38,41 @@ public class TrackerEventHandler extends IdleStateAwareChannelHandler { super(); dataManager = newDataManager; } + + private void processSinglePosition(Position position) { + if (position == null) { + Log.info("null message"); + } else { + Log.info( + "id: " + position.getId() + + ", deviceId: " + position.getDeviceId() + + ", valid: " + position.getValid() + + ", time: " + position.getTime() + + ", latitude: " + position.getLatitude() + + ", longitude: " + position.getLongitude() + + ", altitude: " + position.getAltitude() + + ", speed: " + position.getSpeed() + + ", course: " + position.getCourse() + + ", power: " + position.getPower()); + } + + // Write position to database + try { + dataManager.addPosition(position); + } catch (Exception error) { + Log.info("Exception during query execution"); + Log.warning(error.getMessage()); + } + } @Override public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) { - if (e.getMessage() instanceof Position) { - - Position position = (Position) e.getMessage(); - - if (position == null) { - Log.info("null message"); - } else { - Log.info( - "id: " + position.getId() + - ", deviceId: " + position.getDeviceId() + - ", valid: " + position.getValid() + - ", time: " + position.getTime() + - ", latitude: " + position.getLatitude() + - ", longitude: " + position.getLongitude() + - ", altitude: " + position.getAltitude() + - ", speed: " + position.getSpeed() + - ", course: " + position.getCourse() + - ", power: " + position.getPower()); - } - - // Write position to database - try { - dataManager.addPosition(position); - } catch (Exception error) { - Log.info("Exception during query execution"); - Log.warning(error.getMessage()); + processSinglePosition((Position) e.getMessage()); + } else if (e.getMessage() instanceof List) { + List<Position> positions = (List<Position>) e.getMessage(); + for (Position position : positions) { + processSinglePosition(position); } } } diff --git a/src/org/traccar/protocol/NavisProtocolDecoder.java b/src/org/traccar/protocol/NavisProtocolDecoder.java new file mode 100644 index 000000000..1b3cd80f7 --- /dev/null +++ b/src/org/traccar/protocol/NavisProtocolDecoder.java @@ -0,0 +1,348 @@ +/* + * Copyright 2012 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.util.Calendar; +import java.util.LinkedList; +import java.util.List; +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.GenericProtocolDecoder; +import org.traccar.helper.Log; +import org.traccar.model.DataManager; +import org.traccar.model.Position; + +/** + * Navis protocol decoder + */ +public class NavisProtocolDecoder extends GenericProtocolDecoder { + + private String prefix; + private long deviceId, serverId; + + private static final Charset charset = Charset.defaultCharset(); + + private String imei; + private Long databaseDeviceId; + + /** + * Initialize + */ + public NavisProtocolDecoder(DataManager dataManager) { + super(dataManager); + } + + // Format types + public static final int F10 = 0x01; + public static final int F20 = 0x02; + public static final int F30 = 0x03; + public static final int F40 = 0x04; + public static final int F50 = 0x05; + public static final int F51 = 0x15; + public static final int F52 = 0x25; + + private static boolean isFormat(int type, int... types) { + for (int i : types) { + if (type == i) { + return true; + } + } + return false; + } + + private Position parsePosition(ChannelBuffer buf) { + Position position = new Position(); + StringBuilder extendedInfo = new StringBuilder("<protocol>navis</protocol>"); + + position.setDeviceId(databaseDeviceId); + position.setAltitude(0.0); + + // Format type + int format; + if (buf.getUnsignedByte(buf.readerIndex()) == 0) { + format = buf.readUnsignedShort(); + } else { + format = buf.readUnsignedByte(); + } + extendedInfo.append("<format>"); + extendedInfo.append(format); + extendedInfo.append("</format>"); + + position.setId(buf.readUnsignedInt()); // sequence number + + // Event type + extendedInfo.append("<event>"); + extendedInfo.append(buf.readUnsignedShort()); + extendedInfo.append("</event>"); + + // Event time + Calendar time = Calendar.getInstance(TimeZone.getTimeZone("UTC")); + time.clear(); + time.set(Calendar.HOUR, buf.readUnsignedByte()); + time.set(Calendar.MINUTE, buf.readUnsignedByte()); + time.set(Calendar.SECOND, buf.readUnsignedByte()); + time.set(Calendar.DAY_OF_MONTH, buf.readUnsignedByte()); + time.set(Calendar.MONTH, buf.readUnsignedByte()); + time.set(Calendar.YEAR, 2000 + buf.readUnsignedByte()); + extendedInfo.append("<time>"); + extendedInfo.append(time.getTimeInMillis()); + extendedInfo.append("</time>"); + + // Alarm status + extendedInfo.append("<alarm>"); + extendedInfo.append(buf.readUnsignedByte()); + extendedInfo.append("</alarm>"); + + // Modules status + extendedInfo.append("<status>"); + extendedInfo.append(buf.readUnsignedByte()); + extendedInfo.append("</status>"); + + // GSM signal + extendedInfo.append("<gsm>"); + extendedInfo.append(buf.readUnsignedByte()); + extendedInfo.append("</gsm>"); + + // Output + extendedInfo.append("<output>"); + if (isFormat(format, F10, F20, F30)) { + extendedInfo.append(buf.readUnsignedShort()); + } else if (isFormat(format, F40, F50, F51, F52)) { + extendedInfo.append(buf.readUnsignedByte()); + } + extendedInfo.append("</output>"); + + // Input + extendedInfo.append("<input>"); + if (isFormat(format, F10, F20, F30, F40)) { + extendedInfo.append(buf.readUnsignedShort()); + } else if (isFormat(format, F50, F51, F52)) { + extendedInfo.append(buf.readUnsignedByte()); + } + extendedInfo.append("</input>"); + + position.setPower(buf.readUnsignedShort() / 1000.0); // power + + // Battery power + extendedInfo.append("<battery>"); + extendedInfo.append(buf.readUnsignedShort()); + extendedInfo.append("</battery>"); + + // Temperature + if (isFormat(format, F10, F20, F30)) { + extendedInfo.append("<temperature>"); + extendedInfo.append(buf.readShort()); + extendedInfo.append("</temperature>"); + } + + if (isFormat(format, F10, F20, F50, F52)) { + // ADC 1 + extendedInfo.append("<adc1>"); + extendedInfo.append(buf.readUnsignedShort()); + extendedInfo.append("</adc1>"); + + // ADC 2 + extendedInfo.append("<adc2>"); + extendedInfo.append(buf.readUnsignedShort()); + extendedInfo.append("</adc2>"); + } + + if (isFormat(format, F20, F50, F51, F52)) { + // Impulse counters + buf.readUnsignedInt(); + buf.readUnsignedInt(); + } + + if (isFormat(format, F20, F50, F51, F52)) { + // Validity + int locationStatus = buf.readUnsignedByte(); + position.setValid((locationStatus & 0x02) == 0x02); + + // Location time + time.clear(); + time.set(Calendar.HOUR, buf.readUnsignedByte()); + time.set(Calendar.MINUTE, buf.readUnsignedByte()); + time.set(Calendar.SECOND, buf.readUnsignedByte()); + time.set(Calendar.DAY_OF_MONTH, buf.readUnsignedByte()); + time.set(Calendar.MONTH, buf.readUnsignedByte()); + time.set(Calendar.YEAR, 2000 + buf.readUnsignedByte()); + position.setTime(time.getTime()); + + // Location data + position.setLatitude(buf.readFloat() / Math.PI * 180); + position.setLongitude(buf.readFloat() / Math.PI * 180); + position.setSpeed((double) buf.readFloat()); + position.setCourse((double) buf.readUnsignedShort()); + + // Milage + extendedInfo.append("<milage>"); + extendedInfo.append(buf.readFloat()); + extendedInfo.append("</milage>"); + + // Last segment + extendedInfo.append("<segment>"); + extendedInfo.append(buf.readFloat()); + extendedInfo.append("</segment>"); + + // Segment times + buf.readUnsignedShort(); + buf.readUnsignedShort(); + } + + if (isFormat(format, F51, F52)) { + // Other stuff + buf.readUnsignedShort(); + buf.readByte(); + buf.readUnsignedShort(); + buf.readUnsignedShort(); + buf.readByte(); + buf.readUnsignedShort(); + buf.readUnsignedShort(); + buf.readByte(); + buf.readUnsignedShort(); + } + + if (isFormat(format, F40, F52)) { + // Four temperature sensors + buf.readByte(); + buf.readByte(); + buf.readByte(); + buf.readByte(); + } + + // Extended info + position.setExtendedInfo(extendedInfo.toString()); + + return position; + } + + private Object processSingle(Channel channel, ChannelBuffer buf) { + Position position = parsePosition(buf); + + ChannelBuffer response = ChannelBuffers.dynamicBuffer(ByteOrder.LITTLE_ENDIAN, 8); + response.writeBytes(ChannelBuffers.copiedBuffer(ByteOrder.LITTLE_ENDIAN, "*<T", charset)); + response.writeInt(position.getId().intValue()); + sendReply(channel, response); + + // No location data + if (position.getValid() == null) { + return null; + } + + return position; + } + + private Object processArray(Channel channel, ChannelBuffer buf) { + List<Position> positions = new LinkedList<Position>(); + int count = buf.readUnsignedByte(); + + for (int i = 0; i < count; i++) { + Position position = parsePosition(buf); + if (position.getValid() != null) { + positions.add(position); + } + } + + ChannelBuffer response = ChannelBuffers.dynamicBuffer(ByteOrder.LITTLE_ENDIAN, 8); + response.writeBytes(ChannelBuffers.copiedBuffer(ByteOrder.LITTLE_ENDIAN, "*<A", charset)); + response.writeByte(count); + sendReply(channel, response); + + // No location data + if (positions.isEmpty()) { + return null; + } + + return positions; + } + + private Object processHandshake(Channel channel, ChannelBuffer buf) { + buf.readByte(); // semicolon symbol + imei = buf.toString(Charset.defaultCharset()); + + try { + databaseDeviceId = getDataManager().getDeviceByImei(imei).getId(); + sendReply(channel, ChannelBuffers.copiedBuffer(ByteOrder.LITTLE_ENDIAN, "*<S", charset)); + } catch(Exception error) { + Log.warning(error.toString()); + } + return null; + } + + private static short checksum(ChannelBuffer buf) { + short sum = 0; + for (int i = 0; i < buf.readableBytes(); i++) { + sum ^= buf.getUnsignedByte(i); + } + return sum; + } + + private void sendReply(Channel channel, ChannelBuffer data) { + ChannelBuffer header = ChannelBuffers.directBuffer(ByteOrder.LITTLE_ENDIAN, 16); + header.writeBytes(ChannelBuffers.copiedBuffer(ByteOrder.LITTLE_ENDIAN, prefix, charset)); + header.writeInt((int) deviceId); + header.writeInt((int) serverId); + header.writeShort(data.readableBytes()); + header.writeByte(checksum(data)); + header.writeByte(checksum(header)); + + if (channel != null) { + channel.write(ChannelBuffers.copiedBuffer(header, data)); + } + } + + /** + * Decode message + */ + protected Object decode( + ChannelHandlerContext ctx, Channel channel, Object msg) + throws Exception { + + ChannelBuffer buf = (ChannelBuffer) msg; + + // Read header + prefix = buf.toString(buf.readerIndex(), 4, charset); + buf.skipBytes(prefix.length()); // prefix @NTC by default + serverId = buf.readUnsignedInt(); + deviceId = buf.readUnsignedInt(); + int length = buf.readUnsignedShort(); + buf.skipBytes(2); // header and data XOR checksum + + if (length == 0) { + return null; // keep alive message + } + + // Read message type + String type = buf.toString(buf.readerIndex(), 3, charset); + buf.skipBytes(type.length()); + + if (type.equals("*>T")) { + return processSingle(channel, buf); + } else if (type.equals("*>A")) { + return processArray(channel, buf); + } else if (type.equals("*>S")) { + return processHandshake(channel, buf); + } + + return null; + } + +} @@ -65,3 +65,5 @@ echo "17. pt502" echo "18. tr20" (echo -n -e "%%123456789012345,A,120101121800,N6000.0000E13000.0000,0,000,0,01034802,150,[Message]\r\n";) | nc -v localhost 5018 + +echo "19. navis" diff --git a/test/org/traccar/protocol/NavisProtocolDecoderTest.java b/test/org/traccar/protocol/NavisProtocolDecoderTest.java new file mode 100644 index 000000000..f880cde8b --- /dev/null +++ b/test/org/traccar/protocol/NavisProtocolDecoderTest.java @@ -0,0 +1,24 @@ +package org.traccar.protocol; + +import java.nio.ByteOrder; +import org.jboss.netty.buffer.ChannelBuffers; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertNotNull; +import org.junit.Test; + +public class NavisProtocolDecoderTest { + + @Test + public void testDecode() throws Exception { + + NavisProtocolDecoder decoder = new NavisProtocolDecoder(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))); + + } + +} |