From 0d8f2af5ea9bafeec92a54d10b190165fa0b5d91 Mon Sep 17 00:00:00 2001 From: AuroraRAS Date: Sun, 2 May 2021 01:36:22 +0800 Subject: Add mavlink2 protocol support Mavlink2 is a common protocol for unmanned vehicles and flight controllers like Pixhawk devices. --- .../org/traccar/protocol/Mavlink2Protocol.java | 23 ++++ .../traccar/protocol/Mavlink2ProtocolDecoder.java | 128 +++++++++++++++++++++ 2 files changed, 151 insertions(+) create mode 100644 src/main/java/org/traccar/protocol/Mavlink2Protocol.java create mode 100644 src/main/java/org/traccar/protocol/Mavlink2ProtocolDecoder.java (limited to 'src/main/java') diff --git a/src/main/java/org/traccar/protocol/Mavlink2Protocol.java b/src/main/java/org/traccar/protocol/Mavlink2Protocol.java new file mode 100644 index 000000000..ba051a854 --- /dev/null +++ b/src/main/java/org/traccar/protocol/Mavlink2Protocol.java @@ -0,0 +1,23 @@ +package org.traccar.protocol; + +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +import io.netty.handler.codec.bytes.ByteArrayDecoder; +import io.netty.handler.codec.bytes.ByteArrayEncoder; + +public class Mavlink2Protocol extends BaseProtocol { + + public Mavlink2Protocol() { + addServer(new TrackerServer(true, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new ByteArrayEncoder()); + pipeline.addLast(new ByteArrayDecoder()); + pipeline.addLast(new Mavlink2ProtocolDecoder(Mavlink2Protocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/Mavlink2ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Mavlink2ProtocolDecoder.java new file mode 100644 index 000000000..d23398bce --- /dev/null +++ b/src/main/java/org/traccar/protocol/Mavlink2ProtocolDecoder.java @@ -0,0 +1,128 @@ +package org.traccar.protocol; + +import java.net.SocketAddress; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.time.Instant; +import java.util.Arrays; +import java.util.Date; + +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.Protocol; +import org.traccar.model.Position; + +import io.netty.channel.Channel; + +public class Mavlink2ProtocolDecoder extends BaseProtocolDecoder { + + public Mavlink2ProtocolDecoder(Protocol protocol) { + super(protocol); + } + + @Override + protected Object decode(Channel channel, SocketAddress remoteAddress, Object pkt) throws Exception { + byte[] packet = (byte[]) pkt; + /* + * 0 uint8_t magic Packet start marker 0xFD Protocol-specific start-of-text + * (STX) marker used to indicate the beginning of a new packet. Any system that + * does not understand protocol version will skip the packet. + * * + * 1 uint8_t len Payload length 0 - 255 Indicates length of the following + * payload section. This may be affected by payload truncation. + * * + * 2 uint8_t incompat_flags Incompatibility Flags Flags that must be understood + * for MAVLink compatibility (implementation discards packet if it does not + * understand flag). + * * + * 3 uint8_t compat_flags Compatibility Flags Flags that can be ignored if not + * understood (implementation can still handle packet even if it does not + * understand flag). + * * + * 4 uint8_t seq Packet sequence number 0 - 255 Used to detect packet loss. + * Components increment value for each message sent. + * * + * 5 uint8_t sysid System ID (sender) 1 - 255 ID of system (vehicle) sending the + * message. Used to differentiate systems on network. Note that the broadcast + * address 0 may not be used in this field as it is an invalid source address. + * * + * 6 uint8_t compid Component ID (sender) 1 - 255 ID of component sending the + * message. Used to differentiate components in a system (e.g. autopilot and a + * camera). Use appropriate values in MAV_COMPONENT. Note that the broadcast + * address MAV_COMP_ID_ALL may not be used in this field as it is an invalid + * source address. + * * + * 7 to 9 uint32_t msgid:24 Message ID (low, middle, high bytes) 0 - 16777215 ID + * of message type in payload. Used to decode data back into message object. + * * + * 10 to (n+10) uint8_t payload[max 255] Payload Message data. Depends on + * message type (i.e. Message ID) and contents. + * * + * (n+10) to (n+11) uint16_t checksum Checksum (low byte, high byte) + * CRC-16/MCRF4XX for message (excluding magic byte). Includes CRC_EXTRA byte. + * * + * (n+12) to (n+25) uint8_t signature[13] Signature (Optional) Signature to + * ensure the link is tamper-proof. + */ + + if (packet[0] != (byte) 0xfd) { + return null; + } + + int msgid = (packet[7] & 0xff) | ((packet[8] & 0xff) << 8) | ((packet[9] & 0x0f) << 16); + int len = packet[1] & 0xff; + int sysid = packet[5] & 0xff; + // GLOBAL_POSITION_INT ( #33 ) + if (msgid == 33) { + byte[] message = Arrays.copyOfRange(packet, 10, 10 + len); + /* + * time_boot_ms uint32_t ms Timestamp (time since system boot). + * * + * lat int32_t degE7 Latitude, expressed + * * + * lon int32_t degE7 Longitude, expressed + * * + * alt int32_t mm Altitude (MSL). Note that virtually all GPS modules provide + * both WGS84 and MSL. + * * + * relative_alt int32_t mm Altitude above ground + * * + * vx int16_t cm/s Ground X Speed (Latitude, positive north) + * * + * vy int16_t cm/s Ground Y Speed (Longitude, positive east) + * * + * vz int16_t cm/s Ground Z Speed (Altitude, positive down) + * * + * hdg uint16_t cdeg Vehicle heading (yaw angle), 0.0..359.99 degrees. If + * unknown, set to: UINT16_MAX + */ + + // int timeBootms = ByteBuffer.wrap(message, 0, 4).order(ByteOrder.LITTLE_ENDIAN).getInt(); + int lat = ByteBuffer.wrap(message, 4, 4).order(ByteOrder.LITTLE_ENDIAN).getInt(); + int lon = ByteBuffer.wrap(message, 8, 4).order(ByteOrder.LITTLE_ENDIAN).getInt(); + int alt = ByteBuffer.wrap(message, 12, 4).order(ByteOrder.LITTLE_ENDIAN).getInt(); + int vx = ByteBuffer.wrap(message, 20, 2).order(ByteOrder.LITTLE_ENDIAN).getShort(); + int vy = ByteBuffer.wrap(message, 22, 2).order(ByteOrder.LITTLE_ENDIAN).getShort(); + int hdg = ByteBuffer.wrap(message, 26, 2).order(ByteOrder.LITTLE_ENDIAN).getShort(); + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, Integer.toString(sysid)); + if (deviceSession == null) { + return null; + } + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + position.setValid(true); + // position.setTime(Date.from(Instant.ofEpochMilli(timeBootms))); + position.setTime(Date.from(Instant.now())); + position.setLatitude(lat / 1e7); + position.setLongitude(lon / 1e7); + position.setAltitude(alt / 1e3); + double speed = Math.sqrt(Math.pow(vx, 2) + Math.pow(vy, 2)); + // cm/s to kn + position.setSpeed(speed * 0.01943844); + position.setCourse(hdg / 1e2); + return position; + } + return null; + } + +} -- cgit v1.2.3 From 2f398690b1c4ddb837ff59a92ed6caea54d35772 Mon Sep 17 00:00:00 2001 From: AuroraRAS Date: Thu, 6 May 2021 01:41:50 +0800 Subject: Make the code more compatible with Traccar's standards --- .../org/traccar/protocol/Mavlink2Protocol.java | 21 +++- .../traccar/protocol/Mavlink2ProtocolDecoder.java | 139 +++++++-------------- .../protocol/Mavlink2ProtocolDecoderTest.java | 46 +------ 3 files changed, 62 insertions(+), 144 deletions(-) (limited to 'src/main/java') diff --git a/src/main/java/org/traccar/protocol/Mavlink2Protocol.java b/src/main/java/org/traccar/protocol/Mavlink2Protocol.java index ba051a854..d779648e4 100644 --- a/src/main/java/org/traccar/protocol/Mavlink2Protocol.java +++ b/src/main/java/org/traccar/protocol/Mavlink2Protocol.java @@ -1,11 +1,25 @@ +/* + * Copyright 2021 Chipeng Li (chplee@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.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; -import io.netty.handler.codec.bytes.ByteArrayDecoder; -import io.netty.handler.codec.bytes.ByteArrayEncoder; +import io.netty.handler.codec.LengthFieldBasedFrameDecoder; public class Mavlink2Protocol extends BaseProtocol { @@ -13,8 +27,7 @@ public class Mavlink2Protocol extends BaseProtocol { addServer(new TrackerServer(true, getName()) { @Override protected void addProtocolHandlers(PipelineBuilder pipeline) { - pipeline.addLast(new ByteArrayEncoder()); - pipeline.addLast(new ByteArrayDecoder()); + pipeline.addLast(new LengthFieldBasedFrameDecoder(1024, 1, 1, 10, 0)); pipeline.addLast(new Mavlink2ProtocolDecoder(Mavlink2Protocol.this)); } }); diff --git a/src/main/java/org/traccar/protocol/Mavlink2ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Mavlink2ProtocolDecoder.java index d23398bce..666538479 100644 --- a/src/main/java/org/traccar/protocol/Mavlink2ProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/Mavlink2ProtocolDecoder.java @@ -1,17 +1,31 @@ +/* + * Copyright 2021 Chipeng Li (chplee@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.net.SocketAddress; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; import java.time.Instant; -import java.util.Arrays; import java.util.Date; import org.traccar.BaseProtocolDecoder; import org.traccar.DeviceSession; import org.traccar.Protocol; +import org.traccar.helper.UnitsConverter; import org.traccar.model.Position; +import io.netty.buffer.ByteBuf; import io.netty.channel.Channel; public class Mavlink2ProtocolDecoder extends BaseProtocolDecoder { @@ -21,105 +35,38 @@ public class Mavlink2ProtocolDecoder extends BaseProtocolDecoder { } @Override - protected Object decode(Channel channel, SocketAddress remoteAddress, Object pkt) throws Exception { - byte[] packet = (byte[]) pkt; - /* - * 0 uint8_t magic Packet start marker 0xFD Protocol-specific start-of-text - * (STX) marker used to indicate the beginning of a new packet. Any system that - * does not understand protocol version will skip the packet. - * * - * 1 uint8_t len Payload length 0 - 255 Indicates length of the following - * payload section. This may be affected by payload truncation. - * * - * 2 uint8_t incompat_flags Incompatibility Flags Flags that must be understood - * for MAVLink compatibility (implementation discards packet if it does not - * understand flag). - * * - * 3 uint8_t compat_flags Compatibility Flags Flags that can be ignored if not - * understood (implementation can still handle packet even if it does not - * understand flag). - * * - * 4 uint8_t seq Packet sequence number 0 - 255 Used to detect packet loss. - * Components increment value for each message sent. - * * - * 5 uint8_t sysid System ID (sender) 1 - 255 ID of system (vehicle) sending the - * message. Used to differentiate systems on network. Note that the broadcast - * address 0 may not be used in this field as it is an invalid source address. - * * - * 6 uint8_t compid Component ID (sender) 1 - 255 ID of component sending the - * message. Used to differentiate components in a system (e.g. autopilot and a - * camera). Use appropriate values in MAV_COMPONENT. Note that the broadcast - * address MAV_COMP_ID_ALL may not be used in this field as it is an invalid - * source address. - * * - * 7 to 9 uint32_t msgid:24 Message ID (low, middle, high bytes) 0 - 16777215 ID - * of message type in payload. Used to decode data back into message object. - * * - * 10 to (n+10) uint8_t payload[max 255] Payload Message data. Depends on - * message type (i.e. Message ID) and contents. - * * - * (n+10) to (n+11) uint16_t checksum Checksum (low byte, high byte) - * CRC-16/MCRF4XX for message (excluding magic byte). Includes CRC_EXTRA byte. - * * - * (n+12) to (n+25) uint8_t signature[13] Signature (Optional) Signature to - * ensure the link is tamper-proof. - */ - - if (packet[0] != (byte) 0xfd) { + protected Object decode(Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + ByteBuf buf = (ByteBuf) msg; + if (buf.readUnsignedByte() != 0xFD) { //Packet start marker return null; } - - int msgid = (packet[7] & 0xff) | ((packet[8] & 0xff) << 8) | ((packet[9] & 0x0f) << 16); - int len = packet[1] & 0xff; - int sysid = packet[5] & 0xff; - // GLOBAL_POSITION_INT ( #33 ) - if (msgid == 33) { - byte[] message = Arrays.copyOfRange(packet, 10, 10 + len); - /* - * time_boot_ms uint32_t ms Timestamp (time since system boot). - * * - * lat int32_t degE7 Latitude, expressed - * * - * lon int32_t degE7 Longitude, expressed - * * - * alt int32_t mm Altitude (MSL). Note that virtually all GPS modules provide - * both WGS84 and MSL. - * * - * relative_alt int32_t mm Altitude above ground - * * - * vx int16_t cm/s Ground X Speed (Latitude, positive north) - * * - * vy int16_t cm/s Ground Y Speed (Longitude, positive east) - * * - * vz int16_t cm/s Ground Z Speed (Altitude, positive down) - * * - * hdg uint16_t cdeg Vehicle heading (yaw angle), 0.0..359.99 degrees. If - * unknown, set to: UINT16_MAX - */ - - // int timeBootms = ByteBuffer.wrap(message, 0, 4).order(ByteOrder.LITTLE_ENDIAN).getInt(); - int lat = ByteBuffer.wrap(message, 4, 4).order(ByteOrder.LITTLE_ENDIAN).getInt(); - int lon = ByteBuffer.wrap(message, 8, 4).order(ByteOrder.LITTLE_ENDIAN).getInt(); - int alt = ByteBuffer.wrap(message, 12, 4).order(ByteOrder.LITTLE_ENDIAN).getInt(); - int vx = ByteBuffer.wrap(message, 20, 2).order(ByteOrder.LITTLE_ENDIAN).getShort(); - int vy = ByteBuffer.wrap(message, 22, 2).order(ByteOrder.LITTLE_ENDIAN).getShort(); - int hdg = ByteBuffer.wrap(message, 26, 2).order(ByteOrder.LITTLE_ENDIAN).getShort(); - DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, Integer.toString(sysid)); - if (deviceSession == null) { - return null; - } + buf.readUnsignedByte(); // Payload length + buf.readUnsignedByte(); // Incompatibility Flags + buf.readUnsignedByte(); // Compatibility Flags + buf.readUnsignedByte(); // Packet sequence number + int senderSystemId = buf.readUnsignedByte(); // System ID (sender) + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, Integer.toString(senderSystemId)); + buf.readUnsignedByte(); // Component ID (sender) + if (deviceSession == null) { + return null; + } + int messageId = buf.readUnsignedMediumLE(); // Message ID (low, middle, high bytes) + if (messageId == 33) { Position position = new Position(getProtocolName()); position.setDeviceId(deviceSession.getDeviceId()); position.setValid(true); - // position.setTime(Date.from(Instant.ofEpochMilli(timeBootms))); position.setTime(Date.from(Instant.now())); - position.setLatitude(lat / 1e7); - position.setLongitude(lon / 1e7); - position.setAltitude(alt / 1e3); - double speed = Math.sqrt(Math.pow(vx, 2) + Math.pow(vy, 2)); - // cm/s to kn - position.setSpeed(speed * 0.01943844); - position.setCourse(hdg / 1e2); + position.set("timeBootms", buf.readUnsignedIntLE()); // Timestamp (time since system boot). + position.setLatitude(buf.readIntLE() / 10000000.0); + position.setLongitude(buf.readIntLE() / 10000000.0); + position.setAltitude(buf.readIntLE() / 1000.0); // Altitude (MSL). + position.set("relativeAltitude", buf.readIntLE() / 1000.0); // Altitude above ground + int groundSpeedX = buf.readShortLE(); + int groundSpeedY = buf.readShortLE(); + buf.readShortLE(); // Ground Z Speed + double speed = Math.sqrt(Math.pow(groundSpeedX, 2) + Math.pow(groundSpeedY, 2)); + position.setSpeed(UnitsConverter.knotsFromCps(speed)); + position.setCourse(buf.readUnsignedShortLE() / 100.0); return position; } return null; diff --git a/src/test/java/org/traccar/protocol/Mavlink2ProtocolDecoderTest.java b/src/test/java/org/traccar/protocol/Mavlink2ProtocolDecoderTest.java index 2f824b603..f4b6d8229 100644 --- a/src/test/java/org/traccar/protocol/Mavlink2ProtocolDecoderTest.java +++ b/src/test/java/org/traccar/protocol/Mavlink2ProtocolDecoderTest.java @@ -7,50 +7,8 @@ public class Mavlink2ProtocolDecoderTest extends ProtocolTest { @Test public void testDecode() throws Exception { - var decoder = new Mavlink2ProtocolDecoder(null); - - byte[] pkt = { - // Packet start marker - (byte) 0xfd, - // Payload length - (byte) 0x1c, - // Incompatibility Flags - (byte) 0x00, - // Compatibility Flags - (byte) 0x00, - // Packet sequence - (byte) 0x74, - // System ID (sender) - (byte) 0x01, - // Component ID (sender) - (byte) 0x01, - // Message ID (low, middle, high bytes) - (byte) 0x21, (byte) 0x00, (byte) 0x00, - // Payload Message data - // Timestamp (time since system boot). - (byte) 0xcc, (byte) 0xae, (byte) 0x08, (byte) 0x00, - // degE7 Latitude - (byte) 0x40, (byte) 0x05, (byte) 0xd3, (byte) 0x23, - // degE7 Longitude - (byte) 0xb8, (byte) 0x9a, (byte) 0xa3, (byte) 0x0e, - // mm Altitude (MSL) - (byte) 0x2e, (byte) 0xd0, (byte) 0x06, (byte) 0x00, - // mm Altitude above ground - (byte) 0x67, (byte) 0x00, (byte) 0x00, (byte) 0x00, - // cm/s Ground X Speed - (byte) 0x8b, (byte) 0xff, - // cm/s Ground Y Speed - (byte) 0x05, (byte) 0x00, - // cm/s Ground Z Speed - (byte) 0x03, (byte) 0x00, - // cdeg Vehicle heading (yaw angle) - (byte) 0x4f, (byte) 0x2d, - // Checksum (low byte, high byte) - (byte) 0x00, (byte) 0x00 - }; - verifyPosition(decoder, pkt); - + verifyAttributes(decoder, binary("fd1c0000ce01012100004da91f004005d323b89aa30ea6ed070099fb0100f7fffdff0000942c4a88")); + verifyAttributes(decoder, binary("fd1c0000e7010121000047aa1f004005d323b89aa30e9ced070093fb0100f8fffdff0000952c70ff")); } - } -- cgit v1.2.3