diff options
-rw-r--r-- | default.cfg | 4 | ||||
-rw-r--r-- | src/org/traccar/ServerManager.java | 12 | ||||
-rw-r--r-- | src/org/traccar/protocol/CalAmpProtocolDecoder.java | 240 | ||||
-rw-r--r-- | test/org/traccar/protocol/CalAmpProtocolDecoderTest.java | 26 |
4 files changed, 282 insertions, 0 deletions
diff --git a/default.cfg b/default.cfg index 97bc61005..502431b1f 100644 --- a/default.cfg +++ b/default.cfg @@ -399,4 +399,8 @@ <entry key='xirgo.enable'>true</entry> <entry key='xirgo.port'>5081</entry> + <!-- CalAmp server configuration --> + <entry key='calamp.enable'>true</entry> + <entry key='calamp.port'>5082</entry> + </properties> diff --git a/src/org/traccar/ServerManager.java b/src/org/traccar/ServerManager.java index 23a587340..238cd808e 100644 --- a/src/org/traccar/ServerManager.java +++ b/src/org/traccar/ServerManager.java @@ -182,6 +182,7 @@ public class ServerManager { initAutoFon45Server("autofon45"); initBceServer("bce"); initXirgoServer("xirgo"); + initCalAmpServer("calamp"); initProtocolDetector(); @@ -1353,4 +1354,15 @@ public class ServerManager { } } + private void initCalAmpServer(final String protocol) throws SQLException { + if (isProtocolEnabled(properties, protocol)) { + serverList.add(new TrackerServer(this, new ConnectionlessBootstrap(), protocol) { + @Override + protected void addSpecificHandlers(ChannelPipeline pipeline) { + pipeline.addLast("objectDecoder", new CalAmpProtocolDecoder(dataManager, protocol, properties)); + } + }); + } + } + } diff --git a/src/org/traccar/protocol/CalAmpProtocolDecoder.java b/src/org/traccar/protocol/CalAmpProtocolDecoder.java new file mode 100644 index 000000000..7850de1ca --- /dev/null +++ b/src/org/traccar/protocol/CalAmpProtocolDecoder.java @@ -0,0 +1,240 @@ +package org.traccar.protocol; + +import java.net.SocketAddress; +import java.util.Date; +import java.util.Properties; +import org.jboss.netty.buffer.ChannelBuffer; +import org.jboss.netty.buffer.ChannelBuffers; +import org.jboss.netty.channel.Channel; +import org.jboss.netty.channel.ChannelEvent; +import org.jboss.netty.channel.ChannelHandlerContext; +import org.jboss.netty.channel.Channels; +import org.jboss.netty.channel.MessageEvent; +import org.traccar.BaseProtocolDecoder; +import org.traccar.database.DataManager; +import org.traccar.helper.Log; +import org.traccar.model.ExtendedInfoFormatter; +import org.traccar.model.Position; + +public class CalAmpProtocolDecoder extends BaseProtocolDecoder { + + public CalAmpProtocolDecoder(DataManager dataManager, String protocol, Properties properties) { + super(dataManager, protocol, properties); + } + + private static final int MSG_NULL = 0; + private static final int MSG_ACK = 1; + private static final int MSG_EVENT_REPORT = 2; + private static final int MSG_ID_REPORT = 3; + private static final int MSG_USER_DATA = 4; + private static final int MSG_APP_DATA = 5; + private static final int MSG_CONFIG = 6; + private static final int MSG_UNIT_REQUEST = 7; + private static final int MSG_LOCATE_REPORT = 8; + private static final int MSG_USER_DATA_ACC = 9; + private static final int MSG_MINI_EVENT_REPORT = 10; + private static final int MSG_MINI_USER_DATA = 11; + + private static final int SERVICE_UNACKNOWLEDGED = 0; + private static final int SERVICE_ACKNOWLEDGED = 1; + private static final int SERVICE_RESPONSE = 2; + + @Override + public void handleUpstream( + ChannelHandlerContext ctx, ChannelEvent evt) + throws Exception { + + if (!(evt instanceof MessageEvent)) { + ctx.sendUpstream(evt); + return; + } + + MessageEvent e = (MessageEvent) evt; + Object decodedMessage = decode(ctx, e.getChannel(), e.getMessage(), e.getRemoteAddress()); + if (decodedMessage != null) { + Channels.fireMessageReceived(ctx, decodedMessage, e.getRemoteAddress()); + } + } + + private void sendResponse(Channel channel, SocketAddress remoteAddress, int type, int index, int result) { + if (channel != null) { + ChannelBuffer response = ChannelBuffers.directBuffer(10); + response.writeByte(SERVICE_RESPONSE); + response.writeByte(MSG_ACK); + response.writeShort(index); + response.writeByte(type); + response.writeByte(result); + response.writeByte(0); + response.writeMedium(0); + channel.write(response, remoteAddress); + } + } + + protected Object decode( + ChannelHandlerContext ctx, Channel channel, Object msg, SocketAddress remoteAddress) + throws Exception { + + ChannelBuffer buf = (ChannelBuffer) msg; + + Long deviceId = null; + + // Check options header + if ((buf.getByte(buf.readerIndex()) & 0x80) != 0) { + + int content = buf.readUnsignedByte(); + + // Identifier + if ((content & 0x01) != 0) { + + // Read identifier + int length = buf.readUnsignedByte(); + long id = 0; + for (int i = 0; i < length; i++) { + int b = buf.readUnsignedByte(); + id = id * 10 + (b >> 4); + if ((b & 0xf) != 0xf) { + id = id * 10 + (b & 0xf); + } + } + + // Find device in database + String stringId = String.valueOf(id); + try { + deviceId = getDataManager().getDeviceByImei(stringId).getId(); + } catch(Exception error) { + Log.warning("Unknown device - " + stringId); + } + + } + + // Identifier type + if ((content & 0x02) != 0) { + buf.skipBytes(buf.readUnsignedByte()); + } + + // Authentication + if ((content & 0x04) != 0) { + buf.skipBytes(buf.readUnsignedByte()); + } + + // Routing + if ((content & 0x08) != 0) { + buf.skipBytes(buf.readUnsignedByte()); + } + + // Forwarding + if ((content & 0x10) != 0) { + buf.skipBytes(buf.readUnsignedByte()); + } + + // Responce redirection + if ((content & 0x20) != 0) { + buf.skipBytes(buf.readUnsignedByte()); + } + + } + + // Unidentified device + if (deviceId == null) { + Log.warning("Unknown device"); + return null; + } + + int service = buf.readUnsignedByte(); + int type = buf.readUnsignedByte(); + int index = buf.readUnsignedShort(); + + // Send acknowledgement + if (service == SERVICE_ACKNOWLEDGED) { + sendResponse(channel, remoteAddress, type, index, 0); + } + + if (type == MSG_EVENT_REPORT || + type == MSG_LOCATE_REPORT || + type == MSG_MINI_EVENT_REPORT) { + + // Create new position + Position position = new Position(); + position.setDeviceId(deviceId); + ExtendedInfoFormatter extendedInfo = new ExtendedInfoFormatter("calamp"); + + // Location data + position.setTime(new Date(buf.readUnsignedInt() * 1000)); + if (type != MSG_MINI_EVENT_REPORT) { + buf.readUnsignedInt(); // fix time + } + position.setLatitude(buf.readInt() * 0.0000001); + position.setLongitude(buf.readInt() * 0.0000001); + if (type != MSG_MINI_EVENT_REPORT) { + position.setAltitude(buf.readInt() * 0.01); + position.setSpeed(buf.readUnsignedInt() * 0.0194384449); // cm/s + } + position.setCourse((double) buf.readShort()); + if (type == MSG_MINI_EVENT_REPORT) { + position.setAltitude(0.0); + position.setSpeed(buf.readUnsignedByte() * 0.539957); // km/h + } + + // Fix status + if (type == MSG_MINI_EVENT_REPORT) { + extendedInfo.set("satellites", buf.getUnsignedByte(buf.readerIndex()) & 0xf); + position.setValid((buf.readUnsignedByte() & 0x20) == 0); + } else { + extendedInfo.set("satellites", buf.readUnsignedByte()); + position.setValid((buf.readUnsignedByte() & 0x08) == 0); + } + + if (type != MSG_MINI_EVENT_REPORT) { + + // Carrier + extendedInfo.set("carrier", buf.readUnsignedShort()); + + // Cell signal + extendedInfo.set("gsm", buf.readShort()); + + } + + // Modem state + extendedInfo.set("modem", buf.readUnsignedByte()); + + // HDOP + if (type != MSG_MINI_EVENT_REPORT) { + extendedInfo.set("hdop", buf.readUnsignedByte()); + } + + // Inputs + extendedInfo.set("input", buf.readUnsignedByte()); + + // Unit status + if (type != MSG_MINI_EVENT_REPORT) { + extendedInfo.set("status", buf.readUnsignedByte()); + } + + // Event code and status + if (type == MSG_EVENT_REPORT || type == MSG_MINI_EVENT_REPORT) { + extendedInfo.set("event", buf.readUnsignedByte()); + } + + // Accumulators + /*int accCount = buf.readUnsignedByte(); + int accType = accCount >> 6; + accCount &= 0x3f; + + if (accType == 1) { + buf.readUnsignedInt(); // threshold + buf.readUnsignedInt(); // mask + } + + for (int i = 0; i < accCount; i++) { + extendedInfo.set("acc" + i, buf.readUnsignedInt()); + }*/ + + position.setExtendedInfo(extendedInfo.toString()); + return position; + + } + + return null; + } + +} diff --git a/test/org/traccar/protocol/CalAmpProtocolDecoderTest.java b/test/org/traccar/protocol/CalAmpProtocolDecoderTest.java new file mode 100644 index 000000000..31336a66c --- /dev/null +++ b/test/org/traccar/protocol/CalAmpProtocolDecoderTest.java @@ -0,0 +1,26 @@ +package org.traccar.protocol; + +import org.jboss.netty.buffer.ChannelBuffers; +import org.junit.Test; +import org.traccar.helper.ChannelBufferTools; +import org.traccar.helper.TestDataManager; + +import static org.junit.Assert.assertNull; +import static org.traccar.helper.DecoderVerifier.verify; + +public class CalAmpProtocolDecoderTest { + + @Test + public void testDecode() throws Exception { + + CalAmpProtocolDecoder decoder = new CalAmpProtocolDecoder(new TestDataManager(), null, null); + + verify(decoder.decode(null, null, ChannelBuffers.wrappedBuffer(ChannelBufferTools.convertHexString( + "830545420185450101010200075517fb335516c5c40fb1aea4cf4cbf250000000000000000008900260015ffb10f001108110a0000")), null)); + + verify(decoder.decode(null, null, ChannelBuffers.wrappedBuffer(ChannelBufferTools.convertHexString( + "830543321494750101010A00085492798A0EC4F9E71BDA3B81005600040F1F33050000030000000076000000000000000000000000")), null)); + + } + +} |