aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--setup/default.xml1
-rw-r--r--src/main/java/org/traccar/protocol/ThurayaProtocol.java39
-rw-r--r--src/main/java/org/traccar/protocol/ThurayaProtocolDecoder.java195
-rw-r--r--src/test/java/org/traccar/protocol/ThurayaProtocolDecoderTest.java24
4 files changed, 259 insertions, 0 deletions
diff --git a/setup/default.xml b/setup/default.xml
index 607efc35f..b1417bd95 100644
--- a/setup/default.xml
+++ b/setup/default.xml
@@ -284,5 +284,6 @@
<entry key='teratrack.port'>5239</entry>
<entry key='envotech.port'>5240</entry>
<entry key='bstpl.port'>5241</entry>
+ <entry key='thuraya.port'>5242</entry>
</properties>
diff --git a/src/main/java/org/traccar/protocol/ThurayaProtocol.java b/src/main/java/org/traccar/protocol/ThurayaProtocol.java
new file mode 100644
index 000000000..f709a1183
--- /dev/null
+++ b/src/main/java/org/traccar/protocol/ThurayaProtocol.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2022 Anton Tananaev (anton@traccar.org)
+ *
+ * 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 io.netty.handler.codec.LengthFieldBasedFrameDecoder;
+import org.traccar.BaseProtocol;
+import org.traccar.PipelineBuilder;
+import org.traccar.TrackerServer;
+import org.traccar.config.Config;
+
+import javax.inject.Inject;
+
+public class ThurayaProtocol extends BaseProtocol {
+
+ @Inject
+ public ThurayaProtocol(Config config) {
+ addServer(new TrackerServer(config, getName(), false) {
+ @Override
+ protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) {
+ pipeline.addLast(new LengthFieldBasedFrameDecoder(1024, 2, 2, -4, 0));
+ pipeline.addLast(new ThurayaProtocolDecoder(ThurayaProtocol.this));
+ }
+ });
+ }
+
+}
diff --git a/src/main/java/org/traccar/protocol/ThurayaProtocolDecoder.java b/src/main/java/org/traccar/protocol/ThurayaProtocolDecoder.java
new file mode 100644
index 000000000..a287ece34
--- /dev/null
+++ b/src/main/java/org/traccar/protocol/ThurayaProtocolDecoder.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright 2022 Anton Tananaev (anton@traccar.org)
+ *
+ * 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 io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import io.netty.channel.Channel;
+import org.traccar.BaseProtocolDecoder;
+import org.traccar.NetworkMessage;
+import org.traccar.Protocol;
+import org.traccar.helper.BitUtil;
+import org.traccar.helper.DateBuilder;
+import org.traccar.model.Position;
+import org.traccar.session.DeviceSession;
+
+import java.net.SocketAddress;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.LinkedList;
+import java.util.List;
+
+public class ThurayaProtocolDecoder extends BaseProtocolDecoder {
+
+ public ThurayaProtocolDecoder(Protocol protocol) {
+ super(protocol);
+ }
+
+ public static final int MSG_EVENT = 0x5101;
+ public static final int MSG_PERIODIC_REPORT = 0x7101;
+ public static final int MSG_SETTING_RESPONSE = 0x8115;
+ public static final int MSG_ACK = 0x9901;
+
+ private static int checksum(ByteBuffer buf) {
+ int crc = 0;
+ while (buf.hasRemaining()) {
+ crc += buf.get();
+ }
+ crc = ~crc;
+ crc += 1;
+ return crc;
+ }
+
+ private void sendResponse(Channel channel, SocketAddress remoteAddress, long id, int type) {
+ if (channel != null) {
+ ByteBuf response = Unpooled.buffer();
+ response.writeCharSequence("#T", StandardCharsets.US_ASCII);
+ response.writeShort(15); // length
+ response.writeShort(MSG_ACK);
+ response.writeInt((int) id);
+ response.writeShort(type);
+ response.writeShort(1); // server ok
+ response.writeShort(checksum(response.nioBuffer()));
+ channel.writeAndFlush(new NetworkMessage(response, remoteAddress));
+ }
+ }
+
+ private void decodeLocation(ByteBuf buf, Position position) {
+
+ position.setValid(true);
+
+ DateBuilder dateBuilder = new DateBuilder();
+
+ int date = buf.readInt();
+ dateBuilder.setDay(date % 100);
+ date /= 100;
+ dateBuilder.setMonth(date % 100);
+ date /= 100;
+ dateBuilder.setYear(date);
+
+ int time = buf.readInt();
+ dateBuilder.setSecond(time % 100);
+ time /= 100;
+ dateBuilder.setMinute(time % 100);
+ time /= 100;
+ dateBuilder.setHour(time);
+
+ position.setTime(dateBuilder.getDate());
+
+ position.setLongitude(buf.readInt() / 1000000.0);
+ position.setLatitude(buf.readInt() / 1000000.0);
+
+ int data = buf.readUnsignedShort();
+
+ int ignition = BitUtil.from(data, 12);
+ if (ignition == 1) {
+ position.set(Position.KEY_IGNITION, true);
+ } else if (ignition == 2) {
+ position.set(Position.KEY_IGNITION, false);
+ }
+
+ position.setCourse(BitUtil.to(data, 12));
+ position.setSpeed(buf.readShort());
+
+ position.set(Position.KEY_RPM, buf.readShort());
+
+ position.set("data", readString(buf));
+ }
+
+ private String decodeAlarm(int event) {
+ switch (event) {
+ case 10:
+ return Position.ALARM_VIBRATION;
+ case 11:
+ return Position.ALARM_OVERSPEED;
+ case 12:
+ return Position.ALARM_POWER_CUT;
+ case 13:
+ return Position.ALARM_LOW_BATTERY;
+ case 18:
+ return Position.ALARM_GPS_ANTENNA_CUT;
+ case 20:
+ return Position.ALARM_ACCELERATION;
+ case 21:
+ return Position.ALARM_BRAKING;
+ default:
+ return null;
+ }
+ }
+
+ private String readString(ByteBuf buf) {
+ int endIndex = buf.indexOf(buf.readerIndex(), buf.writerIndex(), (byte) 0);
+ CharSequence value = buf.readCharSequence(endIndex - buf.readerIndex(), StandardCharsets.US_ASCII);
+ buf.readUnsignedByte(); // delimiter
+ return value.toString();
+ }
+
+ @Override
+ protected Object decode(
+ Channel channel, SocketAddress remoteAddress, Object msg) throws Exception {
+
+ ByteBuf buf = (ByteBuf) msg;
+
+ buf.skipBytes(2); // service
+ buf.readUnsignedShort(); // length
+ int type = buf.readUnsignedShort();
+ long id = buf.readUnsignedInt();
+
+ sendResponse(channel, remoteAddress, id, type);
+
+ DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, String.valueOf(id));
+ if (deviceSession == null) {
+ return null;
+ }
+
+ if (type == MSG_EVENT) {
+
+ Position position = new Position(getProtocolName());
+ position.setDeviceId(deviceSession.getDeviceId());
+
+ decodeLocation(buf, position);
+
+ int event = buf.readUnsignedByte();
+ position.set(Position.KEY_ALARM, decodeAlarm(event));
+ position.set(Position.KEY_EVENT, event);
+ position.set("eventData", readString(buf));
+
+ return position;
+
+ } else if (type == MSG_PERIODIC_REPORT) {
+
+ List<Position> positions = new LinkedList<>();
+
+ int count = buf.readUnsignedByte();
+ for (int i = 0; i < count; i++) {
+
+ Position position = new Position(getProtocolName());
+ position.setDeviceId(deviceSession.getDeviceId());
+
+ decodeLocation(buf, position);
+
+ positions.add(position);
+
+ }
+
+ return positions;
+
+ }
+
+ return null;
+ }
+
+}
diff --git a/src/test/java/org/traccar/protocol/ThurayaProtocolDecoderTest.java b/src/test/java/org/traccar/protocol/ThurayaProtocolDecoderTest.java
new file mode 100644
index 000000000..90431fa24
--- /dev/null
+++ b/src/test/java/org/traccar/protocol/ThurayaProtocolDecoderTest.java
@@ -0,0 +1,24 @@
+package org.traccar.protocol;
+
+import org.junit.Test;
+import org.traccar.ProtocolTest;
+
+public class ThurayaProtocolDecoderTest extends ProtocolTest {
+
+ @Test
+ public void testDecode() throws Exception {
+
+ var decoder = inject(new ThurayaProtocolDecoder(null));
+
+ verifyPositions(decoder, binary(
+ "235400437101072bca3c0201348b9a00014c9f085493fc02200c5411470000000042323a300001348b9a00014d03085493ea02200c5010000000000042323a3000f2c1"));
+
+ verifyPosition(decoder, binary(
+ "2354002b5101072bca3c01348b9a00013fba000000000000000010000000000042323a3000174f4e00f9de"));
+
+ verifyNull(decoder, binary(
+ "235400d88115071e37d691030133342e3233362e3133302e3637000000001e56313030320030000700080102030405060708020101010101020201030103030302020000007800000078000004b000001c20050a64000015b3800015b374657374696e67003132333435360002010f28393031303539383938303134373738000043383a592c43373a592c43333a592c43323a592c43313a592c42353a592c42343a592c42323a592c42313a592c41323a592c41313a590045313a592c45373a590065746973616c61742e61650047455400322e3130d6de"));
+
+ }
+
+}