Granit protocol implementation

This commit is contained in:
Abyss777 2016-07-05 17:16:06 +05:00
parent 2f27e1e7af
commit 387d798a8f
9 changed files with 400 additions and 0 deletions

View File

@ -431,5 +431,6 @@
<entry key='idpl.port'>5110</entry>
<entry key='huasheng.port'>5111</entry>
<entry key='l100.port'>5112</entry>
<entry key='granit.port'>5113</entry>
</properties>

View File

@ -386,5 +386,6 @@
<entry key='supermate.port'>5108</entry>
<entry key='appello.port'>5109</entry>
<entry key='idpl.port'>5110</entry>
<entry key='granit.port'>5113</entry>
</properties>

View File

@ -386,5 +386,6 @@
<entry key='supermate.port'>5108</entry>
<entry key='appello.port'>5109</entry>
<entry key='idpl.port'>5110</entry>
<entry key='granit.port'>5113</entry>
</properties>

View File

@ -21,6 +21,7 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
public class Command extends Message {
public static final String TYPE_CUSTOM = "custom";
public static final String TYPE_IDENTIFICATION = "deviceIdentification";
public static final String TYPE_POSITION_SINGLE = "positionSingle";
public static final String TYPE_POSITION_PERIODIC = "positionPeriodic";
public static final String TYPE_POSITION_STOP = "positionStop";

View File

@ -0,0 +1,40 @@
/*
* Copyright 2016 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 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.StringFinder;
public class GranitFrameDecoder extends FrameDecoder {
@Override
protected Object decode(
ChannelHandlerContext ctx, Channel channel, ChannelBuffer buf) throws Exception {
int index = buf.indexOf(buf.readerIndex(), buf.writerIndex(), new StringFinder("\r\n"));
if (index != -1) {
ChannelBuffer frame = buf.readBytes(index - buf.readerIndex());
buf.skipBytes(2);
return frame;
}
return null;
}
}

View File

@ -0,0 +1,51 @@
/*
* Copyright 2016 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 org.jboss.netty.bootstrap.ServerBootstrap;
import org.jboss.netty.channel.ChannelPipeline;
import org.traccar.BaseProtocol;
import org.traccar.TrackerServer;
import org.traccar.model.Command;
import java.nio.ByteOrder;
import java.util.List;
public class GranitProtocol extends BaseProtocol {
public GranitProtocol() {
super("granit");
setSupportedCommands(
Command.TYPE_IDENTIFICATION,
Command.TYPE_REBOOT_DEVICE,
Command.TYPE_POSITION_SINGLE);
}
@Override
public void initTrackerServers(List<TrackerServer> serverList) {
TrackerServer server = new TrackerServer(new ServerBootstrap(), this.getName()) {
@Override
protected void addSpecificHandlers(ChannelPipeline pipeline) {
pipeline.addLast("frameDecoder", new GranitFrameDecoder());
pipeline.addLast("objectEncoder", new GranitProtocolEncoder());
pipeline.addLast("objectDecoder", new GranitProtocolDecoder(GranitProtocol.this));
}
};
server.setEndianness(ByteOrder.LITTLE_ENDIAN);
serverList.add(server);
}
}

View File

@ -0,0 +1,215 @@
/*
* Copyright 2016 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 org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.channel.Channel;
import org.traccar.BaseProtocolDecoder;
import org.traccar.helper.BitUtil;
import org.traccar.helper.Checksum;
import org.traccar.model.Position;
import java.net.SocketAddress;
import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
public class GranitProtocolDecoder extends BaseProtocolDecoder {
private static final int HEADER_LENGTH = 6;
public GranitProtocolDecoder(GranitProtocol protocol) {
super(protocol);
}
public static void appendChecksum(ChannelBuffer buffer, int length) {
buffer.writeByte(0x2a); // asterisk
int checksum = Checksum.xor(buffer.toByteBuffer(0, length)) & 0xFF;
String checksumString = String.format("%02X", checksum);
buffer.writeBytes(checksumString.getBytes(StandardCharsets.US_ASCII));
buffer.writeByte(0x0D); buffer.writeByte(0x0A);
}
private static void sendResponseCurrent(Channel channel, int deviceId, long time) {
ChannelBuffer response = ChannelBuffers.directBuffer(ByteOrder.LITTLE_ENDIAN, 21);
response.writeBytes("BB+UGRC~".getBytes(StandardCharsets.US_ASCII));
response.writeShort(6); //binary length
response.writeInt((int) time);
response.writeShort(deviceId);
appendChecksum(response, 16);
channel.write(response);
}
private static void sendResponseArchive(Channel channel, int deviceId, int packNum) {
ChannelBuffer response = ChannelBuffers.directBuffer(ByteOrder.LITTLE_ENDIAN, 19);
response.writeBytes("BB+ARCF~".getBytes(StandardCharsets.US_ASCII));
response.writeShort(4); //binary length
response.writeShort(packNum);
response.writeShort(deviceId);
appendChecksum(response, 14);
channel.write(response);
}
private static void decodeStructure(ChannelBuffer buf, Position position) {
short flags = buf.readUnsignedByte();
position.setValid(BitUtil.check(flags, 7));
position.set(Position.KEY_ALARM, BitUtil.check(flags, 1));
short satDel = buf.readUnsignedByte();
position.set(Position.KEY_SATELLITES, BitUtil.from(satDel, 4));
int pdop = BitUtil.to(satDel, 4);
position.set("pdop", pdop);
int lonDegrees = buf.readUnsignedByte();
int latDegrees = buf.readUnsignedByte();
int lonMinutes = buf.readUnsignedShort();
int latMinutes = buf.readUnsignedShort();
double latitude = latDegrees + (double) latMinutes / 60000;
double longitude = lonDegrees + (double) lonMinutes / 60000;
if (!BitUtil.check(flags, 4)) {
latitude = -latDegrees;
}
if (!BitUtil.check(flags, 5)) {
longitude = -longitude;
}
position.setLongitude(longitude);
position.setLatitude(latitude);
position.setSpeed(buf.readUnsignedByte());
int course = buf.readUnsignedByte();
if (BitUtil.check(flags, 6)) {
course = course | 0x100;
}
position.setCourse(course);
position.set(Position.KEY_DISTANCE, buf.readShort());
int analogIn1 = buf.readUnsignedByte();
int analogIn2 = buf.readUnsignedByte();
int analogIn3 = buf.readUnsignedByte();
int analogIn4 = buf.readUnsignedByte();
int analogInHi = buf.readUnsignedByte();
analogIn1 = analogInHi << 8 & 0x300 | analogIn1;
analogIn2 = analogInHi << 6 & 0x300 | analogIn2;
analogIn3 = analogInHi << 4 & 0x300 | analogIn3;
analogIn4 = analogInHi << 2 & 0x300 | analogIn4;
position.set(Position.PREFIX_ADC + 1, analogIn1);
position.set(Position.PREFIX_ADC + 2, analogIn2);
position.set(Position.PREFIX_ADC + 3, analogIn3);
position.set(Position.PREFIX_ADC + 4, analogIn4);
position.setAltitude(buf.readUnsignedByte() * 10);
short diOut = buf.readUnsignedByte();
for (int i = 0; i < 8; i++) {
position.set(Position.PREFIX_IO + (i + 1), BitUtil.check(diOut, i));
}
buf.skipBytes(1); //StatMess
}
@Override
protected Object decode(Channel channel, SocketAddress remoteAddress, Object msg) throws Exception {
ChannelBuffer buf = (ChannelBuffer) msg;
if (hasDeviceId() && buf.toString(StandardCharsets.US_ASCII).contains("OK")
|| buf.toString(StandardCharsets.US_ASCII).startsWith("ERROR")
|| buf.toString(StandardCharsets.US_ASCII).startsWith("+PR")
|| buf.toString(StandardCharsets.US_ASCII).startsWith("+IDNT")
|| buf.toString(StandardCharsets.US_ASCII).startsWith("+BBMD")) {
Position position = new Position();
position.setProtocol(getProtocolName());
position.setDeviceId(getDeviceId());
position.setTime(new Date());
getLastLocation(position, new Date());
position.setValid(false);
position.set(Position.KEY_RESULT, buf.toString(StandardCharsets.US_ASCII));
return position;
}
if (buf.readableBytes() < HEADER_LENGTH) {
return null;
}
String header = buf.readBytes(HEADER_LENGTH).toString(StandardCharsets.US_ASCII);
if (header.equals("+RRCB~")) {
buf.skipBytes(2); //binary length 26
int deviceId = buf.readUnsignedShort();
if (!identify(String.valueOf(deviceId), channel, remoteAddress)) {
return null;
}
long unixTime = buf.readUnsignedInt();
if (channel != null) {
sendResponseCurrent(channel, deviceId, unixTime);
}
Position position = new Position();
position.setProtocol(getProtocolName());
position.setDeviceId(getDeviceId());
position.setTime(new Date(unixTime * 1000));
decodeStructure(buf, position);
return position;
} else if (header.equals("+DDAT~")) {
buf.skipBytes(2); //binary length
int deviceId = buf.readUnsignedShort();
if (!identify(String.valueOf(deviceId), channel, remoteAddress)) {
return null;
}
byte format = buf.readByte();
if (format != 4) {
return null;
}
byte nblocks = buf.readByte();
int packNum = buf.readUnsignedShort();
if (channel != null) {
sendResponseArchive(channel, deviceId, packNum);
}
List<Position> positions = new ArrayList<>();
while (nblocks > 0) {
nblocks--;
long unixTime = buf.readUnsignedInt();
for (int i = 0; i < 6; i++) {
if (buf.getUnsignedByte(buf.readerIndex()) != 0xFE) {
Position position = new Position();
position.setProtocol(getProtocolName());
position.setDeviceId(getDeviceId());
position.setTime(new Date(unixTime * 1000));
decodeStructure(buf, position);
positions.add(position);
} else {
buf.skipBytes(20); // skip filled 0xFE structure
}
}
buf.skipBytes(2); // increment
}
return positions;
}
return null;
}
}

View File

@ -0,0 +1,57 @@
/*
* Copyright 2016 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.StandardCharsets;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBuffers;
import org.traccar.BaseProtocolEncoder;
import org.traccar.helper.Log;
import org.traccar.model.Command;
public class GranitProtocolEncoder extends BaseProtocolEncoder {
@Override
protected Object encodeCommand(Command command) {
ChannelBuffer commandString;
switch (command.getType()) {
case Command.TYPE_IDENTIFICATION:
commandString = ChannelBuffers.directBuffer(ByteOrder.LITTLE_ENDIAN, 12);
commandString.writeBytes("BB+IDNT".getBytes(StandardCharsets.US_ASCII));
GranitProtocolDecoder.appendChecksum(commandString, 7);
return commandString;
case Command.TYPE_REBOOT_DEVICE:
commandString = ChannelBuffers.directBuffer(ByteOrder.LITTLE_ENDIAN, 13);
commandString.writeBytes("BB+RESET".getBytes(StandardCharsets.US_ASCII));
GranitProtocolDecoder.appendChecksum(commandString, 8);
return commandString;
case Command.TYPE_POSITION_SINGLE:
commandString = ChannelBuffers.directBuffer(ByteOrder.LITTLE_ENDIAN, 12);
commandString.writeBytes("BB+RRCD".getBytes(StandardCharsets.US_ASCII));
GranitProtocolDecoder.appendChecksum(commandString, 7);
return commandString;
default:
Log.warning(new UnsupportedOperationException(command.getType()));
break;
}
return null;
}
}

View File

@ -0,0 +1,33 @@
package org.traccar.protocol;
import java.nio.ByteOrder;
import org.junit.Test;
import org.traccar.ProtocolTest;
public class GranitProtocolDecoderTest extends ProtocolTest {
@Test
public void testDecode() throws Exception {
GranitProtocolDecoder decoder = new GranitProtocolDecoder(new GranitProtocol());
verifyPosition(decoder, binary(ByteOrder.LITTLE_ENDIAN,
"2b525243427e1a003e2934757c57b8b03c38d279b4e61e9bd7006b000000001c00002a45330d0a"));
verifyPositions(decoder, binary(ByteOrder.LITTLE_ENDIAN,
"2b444441547e84003e290401d01690737c57b8903c383c7fa0e5081b64006b000000001c0000b8803c388e7fe7e5102197006c000000001c0000b8813c38ad7f02e6042035006c000000001d0000b8813c38bf7f13e6001d1e006c000000001d0000b8813c38bf7f13e6001d00006c000000001d0000b8903c38977f34e6091065006c000000001e000014002a39320d0a"));
verifyPositions(decoder, binary(ByteOrder.LITTLE_ENDIAN,
"2b444441547e84003e290401d41680747c57f8a03c38987f50e6005300006c000000001c0000f8b03c38987f50e6005300006c000000001c0000fefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefe14002a43460d0a"));
//+IDNT: Navigator.04x Firmware version 0712GLN *21
verifyAttributes(decoder, binary(ByteOrder.LITTLE_ENDIAN,
"2b49444e543a204e6176696761746f722e30347820204669726d776172652076657273696f6e202030373132474c4e202a32310d0a"));
//ERROR WRONG CHECKSUM_1
verifyAttributes(decoder, binary(ByteOrder.LITTLE_ENDIAN,
"4552524f522057524f4e4720434845434b53554d5f310d0a"));
}
}