mirror of
https://github.com/traccar/traccar.git
synced 2025-01-09 04:07:38 +08:00
Granit protocol implementation
This commit is contained in:
parent
2f27e1e7af
commit
387d798a8f
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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";
|
||||
|
40
src/org/traccar/protocol/GranitFrameDecoder.java
Normal file
40
src/org/traccar/protocol/GranitFrameDecoder.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
51
src/org/traccar/protocol/GranitProtocol.java
Normal file
51
src/org/traccar/protocol/GranitProtocol.java
Normal 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);
|
||||
}
|
||||
|
||||
}
|
215
src/org/traccar/protocol/GranitProtocolDecoder.java
Normal file
215
src/org/traccar/protocol/GranitProtocolDecoder.java
Normal 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;
|
||||
}
|
||||
}
|
57
src/org/traccar/protocol/GranitProtocolEncoder.java
Normal file
57
src/org/traccar/protocol/GranitProtocolEncoder.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
33
test/org/traccar/protocol/GranitProtocolDecoderTest.java
Normal file
33
test/org/traccar/protocol/GranitProtocolDecoderTest.java
Normal 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"));
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user