From 0fa340eb62473efa698ddaed783340535f4b2ee0 Mon Sep 17 00:00:00 2001 From: Anton Tananaev Date: Wed, 21 Aug 2013 22:49:34 +1200 Subject: Support TianQIN protocol --- src/org/traccar/ServerManager.java | 5 +- src/org/traccar/protocol/H02FrameDecoder.java | 56 ++++++++++ src/org/traccar/protocol/H02ProtocolDecoder.java | 113 ++++++++++++++++++--- .../traccar/protocol/H02ProtocolDecoderTest.java | 65 +++++++++--- 4 files changed, 208 insertions(+), 31 deletions(-) create mode 100644 src/org/traccar/protocol/H02FrameDecoder.java diff --git a/src/org/traccar/ServerManager.java b/src/org/traccar/ServerManager.java index 95f8c7b58..d149e3ea8 100644 --- a/src/org/traccar/ServerManager.java +++ b/src/org/traccar/ServerManager.java @@ -401,10 +401,7 @@ public class ServerManager { serverList.add(new TrackerServer(this, new ServerBootstrap(), protocol) { @Override protected void addSpecificHandlers(ChannelPipeline pipeline) { - byte delimiter[] = { (byte) '#' }; - pipeline.addLast("frameDecoder", - new DelimiterBasedFrameDecoder(1024, ChannelBuffers.wrappedBuffer(delimiter))); - pipeline.addLast("stringDecoder", new StringDecoder()); + pipeline.addLast("frameDecoder", new H02FrameDecoder()); pipeline.addLast("objectDecoder", new H02ProtocolDecoder(ServerManager.this)); } }); diff --git a/src/org/traccar/protocol/H02FrameDecoder.java b/src/org/traccar/protocol/H02FrameDecoder.java new file mode 100644 index 000000000..ef28e9986 --- /dev/null +++ b/src/org/traccar/protocol/H02FrameDecoder.java @@ -0,0 +1,56 @@ +/* + * 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.charset.Charset; +import org.jboss.netty.buffer.ChannelBuffer; +import org.jboss.netty.channel.Channel; +import org.jboss.netty.channel.ChannelHandlerContext; +import org.jboss.netty.handler.codec.frame.FrameDecoder; +import org.traccar.helper.ChannelBufferTools; + +public class H02FrameDecoder extends FrameDecoder { + + private static final int MESSAGE_LENGTH = 32; + + @Override + protected Object decode( + ChannelHandlerContext ctx, + Channel channel, + ChannelBuffer buf) throws Exception { + + String marker = buf.toString(buf.readerIndex(), 1, Charset.defaultCharset()); + if (marker.equals("*")) { + + // Return text message + Integer index = ChannelBufferTools.find(buf, buf.readerIndex(), buf.readableBytes(), "#"); + if (index != null) { + return buf.readBytes(index + 1 - buf.readerIndex()); + } + + } else if (marker.equals("$")) { + + // Return binary message + if (buf.readableBytes() >= MESSAGE_LENGTH) { + return buf.readBytes(MESSAGE_LENGTH); + } + + } + + return null; + } + +} diff --git a/src/org/traccar/protocol/H02ProtocolDecoder.java b/src/org/traccar/protocol/H02ProtocolDecoder.java index b997d5e28..7bf589bef 100644 --- a/src/org/traccar/protocol/H02ProtocolDecoder.java +++ b/src/org/traccar/protocol/H02ProtocolDecoder.java @@ -15,14 +15,17 @@ */ package org.traccar.protocol; +import java.nio.charset.Charset; import java.util.Calendar; import java.util.TimeZone; import java.util.regex.Matcher; import java.util.regex.Pattern; +import org.jboss.netty.buffer.ChannelBuffer; import org.jboss.netty.channel.Channel; import org.jboss.netty.channel.ChannelHandlerContext; import org.traccar.BaseProtocolDecoder; import org.traccar.ServerManager; +import org.traccar.helper.ChannelBufferTools; import org.traccar.helper.Log; import org.traccar.model.ExtendedInfoFormatter; import org.traccar.model.Position; @@ -32,14 +35,83 @@ public class H02ProtocolDecoder extends BaseProtocolDecoder { public H02ProtocolDecoder(ServerManager serverManager) { super(serverManager); } + + private static double readCoordinate(ChannelBuffer buf, boolean lon) { + + int degrees = ChannelBufferTools.readHexInteger(buf, 2); + if (lon) { + degrees = degrees * 10 + (buf.getByte(buf.readerIndex()) >> 4); + } + + double result = 0; + if (lon) { + result = buf.readUnsignedByte() & 0x0f; + } + result = result * 10 + ChannelBufferTools.readHexInteger(buf, lon ? 5 : 6) * 0.0001; + + result /= 60; + result += degrees; + + return result; + } + + private Position decodeBinary(ChannelBuffer buf) { + + // Create new position + Position position = new Position(); + ExtendedInfoFormatter extendedInfo = new ExtendedInfoFormatter("h02"); + + buf.readByte(); // marker + + // Identification + String id = String.valueOf( + (buf.readUnsignedInt() << 8) + buf.readUnsignedByte()); + try { + position.setDeviceId(getDataManager().getDeviceByImei(id).getId()); + } catch(Exception error) { + Log.warning("Unknown device - " + id); + return null; + } + + // Time + Calendar time = Calendar.getInstance(TimeZone.getTimeZone("UTC")); + time.clear(); + time.set(Calendar.HOUR, ChannelBufferTools.readHexInteger(buf, 2)); + time.set(Calendar.MINUTE, ChannelBufferTools.readHexInteger(buf, 2)); + time.set(Calendar.SECOND, ChannelBufferTools.readHexInteger(buf, 2)); + time.set(Calendar.DAY_OF_MONTH, ChannelBufferTools.readHexInteger(buf, 2)); + time.set(Calendar.MONTH, ChannelBufferTools.readHexInteger(buf, 2) - 1); + time.set(Calendar.YEAR, 2000 + ChannelBufferTools.readHexInteger(buf, 2)); + position.setTime(time.getTime()); + + // Location + double latitude = readCoordinate(buf, false); + int x = buf.readByte(); // reserved + double longitude = readCoordinate(buf, true); + int flags = buf.readUnsignedByte() & 0x0f; + position.setValid((flags & 0x02) != 0); + if ((flags & 0x04) == 0) latitude = -latitude; + if ((flags & 0x08) == 0) longitude = -longitude; + position.setLatitude(latitude); + position.setLongitude(longitude); + position.setAltitude(0.0); + + // Speed and course + position.setSpeed((double) ChannelBufferTools.readHexInteger(buf, 3)); + position.setCourse((buf.readUnsignedByte() & 0x0f) * 100.0 + ChannelBufferTools.readHexInteger(buf, 2)); + + // Status + extendedInfo.set("status", ChannelBufferTools.readHexString(buf, 8)); + + position.setExtendedInfo(extendedInfo.toString()); + return position; + } - /** - * Regular expressions pattern - */ static private Pattern pattern = Pattern.compile( - "\\*HQ," + + "\\*..," + // Manufacturer "(\\d+)," + // IMEI "V\\d," + // Version? + ".*" + "(\\d{2})(\\d{2})(\\d{2})," + // Time (HHMMSS) "([AV])," + // Validity "(\\d+)(\\d{2}.\\d{4})," + // Latitude (DDMM.MMMM) @@ -49,15 +121,12 @@ public class H02ProtocolDecoder extends BaseProtocolDecoder { "(\\d+.\\d+)," + // Speed "(\\d+.\\d+)?," + // Course "(\\d{2})(\\d{2})(\\d{2})," + // Date (DDMMYY) + "(\\p{XDigit}{8})" + // Status ".*"); - - @Override - protected Object decode( - ChannelHandlerContext ctx, Channel channel, Object msg) - throws Exception { + + private Position decodeText(String sentence) { // Parse message - String sentence = (String) msg; Matcher parser = pattern.matcher(sentence); if (!parser.matches()) { return null; @@ -119,11 +188,31 @@ public class H02ProtocolDecoder extends BaseProtocolDecoder { time.set(Calendar.MONTH, Integer.valueOf(parser.group(index++)) - 1); time.set(Calendar.YEAR, 2000 + Integer.valueOf(parser.group(index++))); position.setTime(time.getTime()); + + // Status + extendedInfo.set("status", parser.group(index++)); - // Extended info position.setExtendedInfo(extendedInfo.toString()); - return position; } + @Override + protected Object decode( + ChannelHandlerContext ctx, Channel channel, Object msg) + throws Exception { + + ChannelBuffer buf = (ChannelBuffer) msg; + String marker = buf.toString(0, 1, Charset.defaultCharset()); + + // TODO X mode? + + if (marker.equals("*")) { + return decodeText(buf.toString(Charset.defaultCharset())); + } else if (marker.equals("$")) { + return decodeBinary(buf); + } + + return null; + } + } diff --git a/test/org/traccar/protocol/H02ProtocolDecoderTest.java b/test/org/traccar/protocol/H02ProtocolDecoderTest.java index c01cb9212..8c998c655 100644 --- a/test/org/traccar/protocol/H02ProtocolDecoderTest.java +++ b/test/org/traccar/protocol/H02ProtocolDecoderTest.java @@ -1,8 +1,11 @@ package org.traccar.protocol; +import java.nio.charset.Charset; +import org.jboss.netty.buffer.ChannelBuffers; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import org.junit.Test; +import org.traccar.helper.ChannelBufferTools; public class H02ProtocolDecoderTest { @@ -12,27 +15,59 @@ public class H02ProtocolDecoderTest { H02ProtocolDecoder decoder = new H02ProtocolDecoder(null); decoder.setDataManager(new TestDataManager()); - assertNull(decoder.decode(null, null, - "*HQ,353588020068342,V1,000000,V,0.0000,0,0.0000,0,0.00,0.00,000000,ffffffff,000106,000002,000203,004c87,16")); + assertNull(decoder.decode(null, null, ChannelBuffers.copiedBuffer( + "*HQ,353588020068342,V1,000000,V,0.0000,0,0.0000,0,0.00,0.00,000000,ffffffff,000106,000002,000203,004c87,16#", Charset.defaultCharset()))); - assertNotNull(decoder.decode(null, null, - "*HQ,3800008786,V1,062507,V,3048.2437,N,03058.5617,E,000.00,000,250413,FFFFFBFF")); + assertNotNull(decoder.decode(null, null, ChannelBuffers.copiedBuffer( + "*HQ,3800008786,V1,062507,V,3048.2437,N,03058.5617,E,000.00,000,250413,FFFFFBFF#", Charset.defaultCharset()))); - assertNotNull(decoder.decode(null, null, - "*HQ,123456789012345,V1,155850,A,5214.5346,N,2117.4683,E,0.00,270.90,131012,ffffffff,000000,000000,000000,000000")); + assertNotNull(decoder.decode(null, null, ChannelBuffers.copiedBuffer( + "*HQ,123456789012345,V1,155850,A,5214.5346,N,2117.4683,E,0.00,270.90,131012,ffffffff,000000,000000,000000,000000#", Charset.defaultCharset()))); - assertNotNull(decoder.decode(null, null, - "*HQ,353588010001689,V1,221116,A,1548.8220,S,4753.1679,W,0.00,0.00,300413,ffffffff,0002d4,000004,0001cd,000047")); + assertNotNull(decoder.decode(null, null, ChannelBuffers.copiedBuffer( + "*HQ,353588010001689,V1,221116,A,1548.8220,S,4753.1679,W,0.00,0.00,300413,ffffffff,0002d4,000004,0001cd,000047#", Charset.defaultCharset()))); - assertNotNull(decoder.decode(null, null, - "*HQ,354188045498669,V1,195200,A,701.8915,S,3450.3399,W,0.00,205.70,050213,ffffffff,000243,000000,000000")); + assertNotNull(decoder.decode(null, null, ChannelBuffers.copiedBuffer( + "*HQ,354188045498669,V1,195200,A,701.8915,S,3450.3399,W,0.00,205.70,050213,ffffffff,000243,000000,000000#", Charset.defaultCharset()))); - assertNotNull(decoder.decode(null, null, - "*HQ,2705171109,V1,213324,A,5002.5849,N,01433.7822,E,0.00,000,140613,FFFFFFFF")); + assertNotNull(decoder.decode(null, null, ChannelBuffers.copiedBuffer( + "*HQ,2705171109,V1,213324,A,5002.5849,N,01433.7822,E,0.00,000,140613,FFFFFFFF#", Charset.defaultCharset()))); - // binary? - // 2427051711092133391406135002584900014337822e000000ffffffffff0000 - // 2427051711092134091406135002584900014337822e000000ffffffffff0000 + assertNotNull(decoder.decode(null, null, ChannelBuffers.copiedBuffer( + "*TH,2020916012,V1,050316,A,2212.8745,N,11346.6574,E,14.28,028,220902,FFFFFBFF#", Charset.defaultCharset()))); + + assertNotNull(decoder.decode(null, null, ChannelBuffers.copiedBuffer( + "*TH,2020916012,V4,S17,130305,050316,A,2212.8745,N,11346.6574,E,14.28,028,220902,FFFFFBFF#", Charset.defaultCharset()))); + + assertNotNull(decoder.decode(null, null, ChannelBuffers.copiedBuffer( + "*TH,2020916012,V4,S14,100,10,1,3,130305,050316,A,2212.8745,N,11346.6574,E,14.28,028,220902,FFFFFBFF#", Charset.defaultCharset()))); + + assertNotNull(decoder.decode(null, null, ChannelBuffers.copiedBuffer( + "*TH,2020916012,V4,S20,ERROR,130305,050316,A,2212.8745,N,11346.6574,E,14.28,028,220902,FFFFFBFF#", Charset.defaultCharset()))); + + assertNotNull(decoder.decode(null, null, ChannelBuffers.copiedBuffer( + "*TH,2020916012,V4,S20,DONE,130305,050316,A,2212.8745,N,11346.6574,E,14.28,028,220902,F7FFFBFF#", Charset.defaultCharset()))); + + assertNotNull(decoder.decode(null, null, ChannelBuffers.copiedBuffer( + "*TH,2020916012,V4,R8,ERROR,130305,050316,A,2212.8745,N,11346.6574,E,14.28,028,220902,FFFFFBFF#", Charset.defaultCharset()))); + + assertNotNull(decoder.decode(null, null, ChannelBuffers.copiedBuffer( + "*TH,2020916012,V4,S23,165.165.33.250:8800,130305,050316,A,2212.8745,N,11346.6574,E,14.28,028,220902,FFFFFBFF#", Charset.defaultCharset()))); + + assertNotNull(decoder.decode(null, null, ChannelBuffers.copiedBuffer( + "*TH,2020916012,V4,S24,thit.gd,130305,050316,A,2212.8745,N,11346.6574,E,14.28,028,220902,FFFFFBFF#", Charset.defaultCharset()))); + + assertNotNull(decoder.decode(null, null, ChannelBuffers.copiedBuffer( + "*TH,2020916012,V4,S1,OK,pass_word,130305,050316,A,2212.8745,N,11346.6574,E,14.28,028,220902,FFFFFBFD#", Charset.defaultCharset()))); + + int[] buf1 = {0x24,0x27,0x05,0x17,0x11,0x09,0x21,0x33,0x39,0x14,0x06,0x13,0x50,0x02,0x58,0x49,0x00,0x01,0x43,0x37,0x82,0x2e,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0x00,0x00}; + assertNotNull(decoder.decode(null, null, ChannelBuffers.wrappedBuffer(ChannelBufferTools.convertArray(buf1)))); + + int[] buf2 = {0x24,0x27,0x05,0x17,0x11,0x09,0x21,0x34,0x09,0x14,0x06,0x13,0x50,0x02,0x58,0x49,0x00,0x01,0x43,0x37,0x82,0x2e,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0x00,0x00}; + assertNotNull(decoder.decode(null, null, ChannelBuffers.wrappedBuffer(ChannelBufferTools.convertArray(buf2)))); + + int[] buf3 = {0x24,0x10,0x30,0x73,0x10,0x01,0x05,0x03,0x16,0x22,0x09,0x02,0x22,0x12,0x87,0x45,0x00,0x11,0x34,0x66,0x57,0x4C,0x01,0x40,0x28,0xff,0xff,0xfb,0xff,0xff,0x00,0x00}; + assertNotNull(decoder.decode(null, null, ChannelBuffers.wrappedBuffer(ChannelBufferTools.convertArray(buf3)))); } -- cgit v1.2.3