Membaca Data Protokol MAVLink v1 di Arduino (TANPA LIBRARY)

 


Halo semua! Apa kabar? Semoga kalian baik-baik saja.

Tepat pada saat artikel ini saya publikasi, keadaan Indonesia perlahan mulai membaik. Menunjukkan perkembangan yang luar biasa. Dapat dilihat kegiatan mudik tahun ini, kegiatan wisata, car free day, dan kegiatan-kegiatan serupa sudah mulai diperbolehkan. Yep, Indonesia sudah terbebas dari musibah wabah penyakit menular (saya tidak akan menyebutkan nama penyakit atau sejenisnya, Google cukup sensitif).

Oke, tidak berlama-lama, saya ada sedikit informasi yang ingin saya sampaikan. Pernahkah kalian khususnya yang suka utak-atik drone kemudian iseng ingin membaca isi data dari flightcontrol kalian? Bisa?

Ada banyak tutorial bertebaran di internet sebenarnya. Namun hingga saat artikel ini saya tulis, saya masih belum menemukan program yang sederhana dan tidak memberatkan Arduino. Saya yakin yang kalian temukan di internet pasti menggunakan kode program serupa seperti di bawah ini, iya kan? Wkwk

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
//function called by arduino to read any MAVlink messages sent by serial communication from flight controller to arduino
uint8_t received_sysid;
uint8_t received_compid;
uint8_t received_msgid;

void px_to_mp() { 
  if(Serial2.available()) {
    mavlink_message_t msg;
    mavlink_status_t status;
    uint8_t c = Serial2.read();
    if(mavlink_parse_char(MAVLINK_COMM_0, c, &msg, &status)) {
      switch(msg.msgid) {
        case 0: {
          mavlink_heartbeat_t packet;
          mavlink_msg_heartbeat_decode(&msg, &packet);
          received_sysid = msg.sysid; // sysid compid msgid terpaksa dibuat -
          received_compid = msg.compid; // variabel tersendiri untuk menghindari -
          received_msgid = msg.msgid; // kerusakan data. belum diketahui penyebab -
                                      // 3 byte ini riskan rusak.
          struct combine {
            header head_data;
            hb payload_data;
          }__attribute__((packed)); struct combine merge; // paket header dan payload digabung 1 frame
                                                          // paket checksum digabungkan dengan payload
          merge.head_data = {msg.magic, msg.len, msg.seq, received_sysid, received_compid, received_msgid};
          merge.payload_data = {packet.custom_mode, packet.type, packet.autopilot, packet.base_mode,
            packet.system_status, packet.mavlink_version, msg.checksum};
          //payloadSize digunakan untuk pembentukan keystream dan tag 
          //sizeMerge adalah total data dari head-cksum yang dikirim ke MP 
          //sizeMerge = sizeof(merge); payloadSize = sizeof(merge.payload_data);
          char _data[sizeof(merge)];
          memcpy(_data, &merge, sizeof(merge));
          encryption_start(_data, sizeof(merge), sizeof(merge.payload_data));
        }break;

Saya baru menemukan satu kode program yang dituliskan tanpa library, baris program yang relatif pendek, namun sayangnya kode program tersebut ditulis menggunakan bahasa pemrograman Python. Yang mana program yang saya temukan itu tidak mungkin untuk diimplementasikan di Arduino atau ESP32 sekalipun menggunakan MicroPython.

Saya yang buta dengan bahasa ular sanca ini dengan terpaksa menerjemakannya perlahan. Saking pelannya tidak terasa 2 tahun lewat, (sejak 2020), saya baru bisa menerjemahkan 67%-nya saja. Bahkan sampai lupa siapa pemilik kode programnya. Jika kalian mengetahuinya, silahkan kirim komentar.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
#-----------------------------------------------------------------------
# python mavlink testing
#
# this program demonstrates how to connect to a mavlink device,
# receive, decode, and verify mavlink messages.
#-----------------------------------------------------------------------

import sys
import array
import struct
import serial    # install from https://pypi.python.org/pypi/pyserial

#-----------------------------------------------------------------------
# set your port and baud rates here
#-----------------------------------------------------------------------

PORT='COM10'                       # typical windows port
PORT='/dev/ttyACM0'        # typical mac port
#PORT='/dev/tty.usbmodem621'        # typical mac port
BAUD=57600                         # baud rate for radio connection
BAUD=115200                        # baud rate for direct usb connection

#-----------------------------------------------------------------------
# manifest constants
#-----------------------------------------------------------------------

MAV_STARTB=0xfe      # this byte indicates start of mavlink packet

#-----------------------------------------------------------------------
# utility stuff
#-----------------------------------------------------------------------

xdumpf=''.join([(len(repr(chr(x)))==3) and chr(x) or '.' for x in range(256)])
def xdump(src, length=16):
    """dump a string in classic hexdump format
       adapted from http://code.activestate.com/recipes/142812-hex-dumper
    """
    N=0;
    while src:
        s,src = src[:length],src[length:]
        hexa = ' '.join(["%02X"%ord(x) for x in s])
        s = s.translate(xdumpf)
        print "%04X   %-*s   %s" % (N, length*3, hexa, s)
        N+=length

#-----------------------------------------------------------------------
# checksum special note:
#
# each message type (0,1,2,...) has an extra one-byte magic number.
# heartbeat (message 0) has magic number of 50, so you can index this
# table with the message number to get the magic number.  After
# accumulating the bytes of the message, accumulate the magic number.
# this table is copied from the mavlink source.
#-----------------------------------------------------------------------

MAVLINK_MESSAGE_CRCS=[
  50,124,137,0,237,217,104,119,0,0,0,89,0,0,0,0,0,0,0,0,214,159,220,168,
  24,23,170,144,67,115,39,246,185,104,237,244,222,212,9,254,230,28,28,
  132,221,232,11,153,41,39,214,223,141,33,15,3,100,24,239,238,30,240,183,
  130,130,0,148,21,0,243,124,0,0,0,20,0,152,143,0,0,127,106,0,0,0,0,0,0,
  0,231,183,63,54,0,0,0,0,0,0,0,175,102,158,208,56,93,0,0,0,0,235,93,124,
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,42,
  241,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,204,49,
  170,44,83,46,0]

#-----------------------------------------------------------------------
class x25crc(object):
    """x25 CRC - based on checksum.h from mavlink library"""

    def __init__(self, buf=''):
        self.crc = 0xffff
        self.accumulate(buf)

    def accumulate(self, buf):
        '''add in some more bytes'''
        bytes = array.array('B')
        if isinstance(buf, array.array):
            bytes.extend(buf)
        else:
            bytes.fromstring(buf)
        accum = self.crc
        for b in bytes:
            tmp = b ^ (accum & 0xff)
            tmp = (tmp ^ (tmp<<4)) & 0xFF
            accum = (accum>>8) ^ (tmp<<8) ^ (tmp<<3) ^ (tmp>>4)
            accum = accum & 0xFFFF
        self.crc = accum

#-----------------------------------------------------------------------
def decode2(buf):
    """decode and process a command"""
    # we don't do anything here
    pass

#-----------------------------------------------------------------------
lastseq=255
def decode(buf):
    """decode a mavlink message"""

    global lastseq
    print '-----------'
    xdump(buf)
    magic, mlen, seq, srcSystem, srcComponent, msgId = struct.unpack('<6B', buf[:6])
    b0,b1=struct.unpack('2B',buf[6+mlen:])
    givenCk=b0+b1*256
    crc=x25crc()
    crc.accumulate(buf[1:len(buf)-2]) # skip magic and cksum
    crc.accumulate(chr(MAVLINK_MESSAGE_CRCS[msgId]))

    print 'mlen=%d, seq=%d, sys=(%d,%d), msgId=%d, sums=(%04x,%04x)'%\
        (mlen,seq,srcSystem,srcComponent,msgId,givenCk,crc.crc)

    if crc.crc == givenCk:
        # good message, process it
        if seq != (lastseq+1)%256:
            print 'WARNING, lost message? seq=%d,lastseq=%d'%(seq,lastseq)
        lastseq=seq
        decode2(buf)
    else:
        # complain!
        print 'BAD CRC on message'

#-----------------------------------------------------------------------
def timedread(ser,n):
    """read octets, complain upon timeout"""

    while True:
        x = ser.read(n)
        if len(x) == 0:
            print "TIMEOUT"
        else:
            return x

#-----------------------------------------------------------------------
def process(ser):
    """process the mavlink input stream"""

    while True:
        # scan stream until we see sync byte
        x = timedread(ser,1)
        if ord(x) == MAV_STARTB:
            #  read the length and rest of message, and process
            len=timedread(ser,1)
            rest=timedread(ser,4+ord(len)+2)
            buf=x
            buf+=len
            buf+=rest
            decode(buf)

#-----------------------------------------------------------------------
def connect():
    """connect to the mavlink device"""

    autoselect=1

    # here's some autoselect logic for mac
    if autoselect and sys.platform == 'darwin':
        import glob
        candidates=glob.glob('/dev/tty*usb*')
        print 'candidate ports (%d):'%(len(candidates))
        for c in candidates:
            print '   ',c
        myport=candidates[0]
        mybaud=57600
        mybaud=115200
    else:
        myport=PORT
        mybaud=BAUD

    print 'connecting to:', myport
    ser = serial.Serial()
    ser.port=myport
    ser.baudrate=mybaud
    ser.parity=serial.PARITY_NONE
    ser.stopbits=serial.STOPBITS_ONE
    ser.bytesize=serial.EIGHTBITS
    ser.timeout=2
    ser.open()
    return ser

#-----------------------------------------------------------------------
def main():
    """the main thing!"""

    ser=connect()
    print 'STARTING'
    process(ser)

#-----------------------------------------------------------------------
if __name__=="__main__":
    main()

Jika diperhatikan kode program Python di atas terdapat 3 bagian utama. Pertama pembacaan header MAVLink. Ini penting untuk mengidentifikasi frame data yang lewat dan untuk proses parsing. Bagian kedua adalah pengidentifikasian panjang data. Ini juga menjadi satu bagian dengan proses pengumpulan byte data berdasarkan informasi panjang data yang diidentifikasi. Terakhir yaitu penghitungan checksum. Proses ini berguna untuk pencocokan apakah data yang sudah dikumpulkan adalah benar, atau terjadi perubahan atau kerusakan? Jika data benar, maka proses dapat dilanjutkan untuk frame selanjutnya.

Dari 3 bagian itu, saya hanya berhasil menerjemahkan bagian pertama dan kedua saja. Bagian terakhir atau proses penghitungan checksum masih belum mampu saya pahami. Mungkin diantara kalian yang membaca artikel ini bisa menyempurnakan, silahkan komentar.

Buah dari proses penerjemahan itu saya tuliskan berupa kode program Arduino seperti yang saya tuliskan di bawah ini:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
void setup() {
  Serial.begin(57600);
  Serial2.begin(57600); // opens serial port, sets data rate to 9600 bps
}

void loop() {
  // check if data is available
  if (Serial2.available()) {
    // read the incoming byte:
    uint8_t stx = Serial2.read();
    if ((Serial2.available()) && stx == 0xFE) {
      uint8_t len = Serial2.read();
      
      /* +6 for stx, len, seq, sysid, compid, msgid; 
      len for total payload; 
      +2 for cksum */
      uint8_t mav[6+len+2];
      mav[0] = stx;
      mav[1] = len;

      for (int x = 2; x < sizeof(mav); x++) {
        if (Serial2.available()) {
          mav[x] = Serial2.read();
        }
      }
//      Serial.write(mav, sizeof(mav));

      for (int x = 0; x < sizeof(mav); x++) {
        Serial.print(mav[x], HEX);
      }
      Serial.print("\n");
    }
  }

  delay(1);
  
  if (Serial.available() > 0) {
    uint8_t mp = Serial.read();
    Serial2.write(mp);
  }
}

Bandingkan kode di atas dengan kode program yang kalian temukan di internet. Tentu jauh lebih ringkas kode program dari saya, kan? Pastinya ini meringankan beban kerja dari si Arduino itu sendiri.

Penjelasan

Baik, kita langsung saja loncat ke baris 8. Perintah di baris itu berfungsi untuk mendeteksi apakah koneksi serial yang sudah terhubung terdapat aliran data atau tidak. Jika tidak ada aliran data, maka program akan menunggu hingga data melintas. Kemudian di baris 10, data yang melintas akan dimasukkan ke dalam variabel 'stx' yang kemudian di baris ke 11, variabel 'stx' akan dilakukan identifikasi apakah isi dari variabel tersebut bernilai 0xFE atau yang lain. Jika identifikasinya sama, maka lanjut di baris program berikutnya. Jika tidak, maka akan menunggu kembali sampai cocok.

Di baris 11 juga dilakukan hal yang sama yaitu mendeteksi kembali aliran data di koneksi serial. Kali ini yang ditunggu adalah data atau byte selanjutnya. Dimana byte ini di protokol MAVLink memuat informasi panjang payload dari satu frame MAVLink. Jika data berhasil didapat, maka akan dimasukkan ke dalam variabel 'len'. Apabila kedua informasi yaitu 'stx' dan 'len' berhasil didapatkan, maka selanjutnya adalah proses pengumpulan data yang tersisa dalam 1 frame untuk di "rakit" kembali pada variabel array 'mav' di baris program 17. Perhatikan, alokasi ruang untuk variabel array 'mav' sejumlah 6+len+2. Enam adalah jumlah byte yang terdapat pada bagian header MAVLink v1. Terdiri dari stx, len, sequence, compid, sysid, msgid. Kemudian 'len' sendiri mewakili panjang payload yang dimuat dalam 1 frame. Jika 'len' bernilai 9, maka alokasi 'len' untuk variabel array 'mav' adalah 9 byte. Kemudian +2 disediakan alokasi untuk menempatkan checksum.

Proses perakitan antara 'stx', 'len', dan sisa data selanjutnya terjadi di baris 18-19, dan 21-23. Di baris 21-23 data per-byte yang dibaca dari port serial dimasukkan satu persatu ke dalam variabel 'mav' sesuai dengan urutan elemen array-nya. Jika semua selesai di rakit, selanjutnya terserah kalian. Akan kalian apakan data itu? Kalian enkripsi kah, kalian analisis kah, kalian kirimkan lah, kalian implementasikan langsung di sistem Drone kalian? Terserah, bebas. Tapi disini saya mencoba untuk memunculkannya di serial monitor. Baris programnya di 28-31. Dan seperti ini hasilnya:

Pertanyaan, mengapa cukup banyak memanggil fungsi Serial.available? Alasannya karena jika fungsi Serial.available hanya diterapkan sekali, misal di baris 8 saja, maka data selanjutnya apabila terjadi kekosongan aliran data beberapa detik, maka data yang diterima pun menjadi data 'null'. Ini dibuktikan dengan munculnya nilai hexa FFFFFFF...FF di serial monitor.

Ada beberapa cara untuk menanggulanginya, salah satunya yang termudah adalah menggunakan fungsi Serial.available di setiap proses pembacaan data serialnya.

Okee, sekian dulu. Demikian yang bisa saya sampaikan, kurang lebihnya mohon maaf.

Terima kasih.

Posting Komentar

0 Komentar