Softivation

C# yer istasyonunda uçuş modu STABILIZE'e atlıyor

C# yer istasyonunda uçuş modu GUIDED ile STABILIZE arasında atlıyorsa Pi'nin HEARTBEAT'ini de okuyorsun. Otopilot mesajını ayıran filtre ve kodu burada.

Furkan IŞIKAY 11 dk okuma

Kendi yazdığın C# yer istasyonunda drone'u GUIDED'e alıyorsun. Mod etiketi bir an GUIDED yazıyor, sonra STABILIZE'e dönüyor, sonra yine GUIDED. Mission Planner aynı anda sabit GUIDED gösteriyor ve kumandaya dokunan yok.

Bu çoğu zaman drone'a Raspberry Pi takıldığı hafta başlar. Etiketi bozan, aynı hattan gelen ikinci bir HEARTBEAT; drone'un modu yerinde duruyor.

Mod etiketi neden gidip geliyor

Otopilot saniyede bir HEARTBEAT mesajı gönderir ve uçuş modunu bu mesajın custom_mode alanına sayı olarak yazar. Copter'da 0 STABILIZE, 4 GUIDED, 5 LOITER demektir.

HEARTBEAT'i yalnızca otopilot göndermez. Pi'de çalışan MAVProxy ya da mavlink-router, MAVLink konuşan gimbal ve kamera da kendi HEARTBEAT'ini yollar. Bunlar uçuş modu taşımaz; custom_mode alanına çoğu zaman 0, bazen kendi anlamında bir sayı yazarlar. 0 da Copter tablosunda STABILIZE'e denk gelir.

Pixhawk, TELEM2'ye takılı Pi'den gelen HEARTBEAT'i öteki telemetri portlarına da iletir. YKİ'n TELEM1'deki radyodan dinliyorsa iki kaynağın mesajını aynı hatta art arda alır. Gelen her HEARTBEAT'ten modu okuyan kod, etiketi her mesajda yeniden yazar.

Aynı hatta iki HEARTBEAT, ekranda iki farklı etiket

Aynı hatta iki HEARTBEAT, ekranda iki farklı etiket
Kırmızı noktaların hizasına bak: ortadaki etiket her birinde STABILIZE'e düşüyor, alttaki yerinde kalıyor.

Şeklin üst şeridi hattan gelen sırayı gösteriyor. Pi'nin her paketi, filtresiz kodun etiketini bir sonraki otopilot mesajına kadar STABILIZE yapıyor; filtreli kod o paketleri hiç hesaba katmıyor.

Savaşan İHA görevinde Pi kameradan hedefi bulup drone'u GUIDED'de yönlendirirken pilot ekranda STABILIZE görür ve drone istediğin modda uçarken kumandaya uzanır.

Otopilotun mesajını öbürlerinden ayır

Her MAVLink paketi kimden geldiğini yanında taşır: sistem numarası (sysid) ve bileşen numarası (compid). Otopilot compid 1 ile konuşur. Şekildeki Pi ise 191 kullanıyor, yani yardımcı bilgisayar numarasını.

İkinci işaret HEARTBEAT'in autopilot alanı. MAVLink kuralına göre otopilot olmayan her cihaz buraya 8 yazar, ArduPilot ise 3 gönderir. MAVLink'in heartbeat sayfası otopilotu tanımanın önerilen yolunu da bu alanın 8 olmaması diye verir.

Modu ekrana yalnızca üç koşul birlikte tutunca yaz: sysid senin aracın, compid 1, autopilot 8 değil.

Masa başında sorun görünmeyebilir. Pixhawk mesajları yalnızca kendisine en az bir paket gelmiş porta iletir. Hiç paket göndermeyen bir YKİ, Pi'nin HEARTBEAT'ini hiç almaz. İlk ARM ya da mod komutundan sonra o mesajlar da gelmeye başlar ve etiket bozulur.

Filtreli C# kodu

Aşağıdaki program seri porttan ya da TCP'den MAVLink okur ve her HEARTBEAT için iki sütun basar: her mesajı okuyan kodun göstereceği mod ve filtreli kodun gösterdiği mod. Saniyede bir kendi HEARTBEAT'ini de gönderir; böylece Pixhawk o portu aktif sayar ve gerçek sahadaki trafiği görürsün.

  • Gir: dotnet new console ile yeni bir proje aç, dotnet add package System.IO.Ports ile seri port paketini ekle, Program.cs'nin yerine bu kodu koy. HedefSysid'i aracının MAV_SYSID değerine eşitle.
  • Gör: her satırda kaynağın sysid:compid'i, tip ve ap alanları, iki sütunlu mod.
  • Değilse: listede ikinci bir kaynak yoksa ama etiket yine atlıyorsa ap sütununa bak. 3 yazan iki farklı satır varsa aynı radyo ağında iki araç aynı MAV_SYSID'i kullanıyordur.
C# · Program.cs
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Ports;
using System.Net.Sockets;

// Kullanim: dotnet run -- COM5 57600   ya da   dotnet run -- tcp 127.0.0.1 5760
Stream akis;
if (args[0] == "tcp")
    akis = new TcpClient(args[1], int.Parse(args[2])).GetStream();
else
{
    var port = new SerialPort(args[0], int.Parse(args[1]));
    port.Open();
    akis = port.BaseStream;
}

const byte HedefSysid = 1;          // aracinin MAV_SYSID degeri
string ekrandakiMod = "?";

var copterModlari = new Dictionary<uint, string> {
    [0] = "STABILIZE", [1] = "ACRO", [2] = "ALT_HOLD", [3] = "AUTO", [4] = "GUIDED",
    [5] = "LOITER", [6] = "RTL", [7] = "CIRCLE", [9] = "LAND", [11] = "DRIFT",
    [13] = "SPORT", [14] = "FLIP", [15] = "AUTOTUNE", [16] = "POSHOLD", [17] = "BRAKE",
    [18] = "THROW", [19] = "AVOID_ADSB", [20] = "GUIDED_NOGPS", [21] = "SMART_RTL",
    [22] = "FLOWHOLD", [23] = "FOLLOW", [24] = "ZIGZAG", [25] = "SYSTEMID",
    [26] = "AUTOROTATE", [27] = "AUTO_RTL", [28] = "TURTLE" };

string ModAdi(uint m) => copterModlari.TryGetValue(m, out var ad) ? ad : $"MOD_{m}";

// Otopilotun HEARTBEAT'i mi? Bilgisayar, gimbal ve yer istasyonu autopilot=8 gonderir.
bool OtopilotMu(byte sysid, byte compid, byte autopilot) =>
    sysid == HedefSysid && compid == 1 && autopilot != 8;

// Otopilot, mesajlari yalnizca kendisine paket gelen porta iletir.
// Bu yuzden yer istasyonu saniyede bir kendi HEARTBEAT'ini gonderir.
byte sira = 0;
using var hbZamanlayici = new System.Threading.Timer(_ =>
{
    var paket = GcsHeartbeat(sira++);
    try { akis.Write(paket, 0, paket.Length); } catch (IOException) { }
}, null, 0, 1000);

var tampon = new List<byte>();
var oku = new byte[512];
while (true)
{
    int n;
    try { n = akis.Read(oku, 0, oku.Length); }
    catch (IOException) { break; }                      // baglanti koptu
    if (n <= 0) break;
    for (int i = 0; i < n; i++) tampon.Add(oku[i]);
    while (CerceveAl(tampon, out byte sys, out byte comp, out uint msgid, out byte[] yuk))
    {
        if (msgid != 0) continue;                       // yalnizca HEARTBEAT
        var p = new byte[9];
        Array.Copy(yuk, p, Math.Min(yuk.Length, 9));    // MAVLink2 sondaki sifirlari kirpar
        uint customMode = BitConverter.ToUInt32(p, 0);
        byte tip = p[4], autopilot = p[5];
        string ham = ModAdi(customMode);
        string not = "";
        if (OtopilotMu(sys, comp, autopilot)) ekrandakiMod = ham;
        else not = " (atlandi)";
        Console.WriteLine($"{sys,3}:{comp,-3} tip={tip,-2} ap={autopilot} mod={customMode,-2} | ham: {ham,-9} | filtreli: {ekrandakiMod}{not}");
    }
}

// Tampondan tek bir MAVLink cercevesi cikarir, CRC tutmazsa cerceveyi atar.
static bool CerceveAl(List<byte> t, out byte sys, out byte comp, out uint msgid, out byte[] yuk)
{
    sys = comp = 0; msgid = 0; yuk = Array.Empty<byte>();
    while (t.Count > 0)
    {
        int bas = t.FindIndex(b => b == 0xFD || b == 0xFE);
        if (bas < 0) { t.Clear(); return false; }
        if (bas > 0) t.RemoveRange(0, bas);
        bool v2 = t[0] == 0xFD;
        int baslik = v2 ? 10 : 6;
        if (t.Count < baslik) return false;
        int len = t[1];
        int imza = v2 && (t[2] & 0x01) != 0 ? 13 : 0;
        int toplam = baslik + len + 2 + imza;
        if (t.Count < toplam) return false;
        msgid = v2 ? (uint)(t[7] | t[8] << 8 | t[9] << 16) : t[5];
        sys = v2 ? t[5] : t[3];
        comp = v2 ? t[6] : t[4];
        ushort crc = 0xFFFF;
        for (int i = 1; i < baslik + len; i++) crc = X25(crc, t[i]);
        // CRC yalnizca HEARTBEAT icin kontrol edilir (crc_extra = 50)
        bool tamam = msgid != 0 || (ushort)(t[baslik + len] | t[baslik + len + 1] << 8) == X25(crc, 50);
        yuk = t.GetRange(baslik, len).ToArray();
        t.RemoveRange(0, tamam ? toplam : 1);
        if (tamam) return true;
    }
    return false;
}

// MAVLink2 HEARTBEAT: sysid 255, compid 190, tip=6 (GCS), autopilot=8 (INVALID)
static byte[] GcsHeartbeat(byte seq)
{
    byte[] p = { 0xFD, 9, 0, 0, seq, 255, 190, 0, 0, 0,
                 0, 0, 0, 0, 6, 8, 0, 0, 3, 0, 0 };
    ushort crc = 0xFFFF;
    for (int i = 1; i < 19; i++) crc = X25(crc, p[i]);
    crc = X25(crc, 50);
    p[19] = (byte)crc; p[20] = (byte)(crc >> 8);
    return p;
}

static ushort X25(ushort crc, byte b)
{
    byte tmp = (byte)(b ^ (crc & 0xFF));
    tmp ^= (byte)(tmp << 4);
    return (ushort)((crc >> 8) ^ (tmp << 8) ^ (tmp << 3) ^ (tmp >> 4));
}

Radyonun portuyla başlatırsan ve drone GUIDED'deyken Pi TELEM2'den HEARTBEAT gönderiyorsa şunu görürsün:

Kod
dotnet run -- COM5 57600
  1:1   tip=2  ap=3 mod=4  | ham: GUIDED    | filtreli: GUIDED
  1:191 tip=18 ap=8 mod=0  | ham: STABILIZE | filtreli: GUIDED (atlandi)
  1:1   tip=2  ap=3 mod=4  | ham: GUIDED    | filtreli: GUIDED

Ortadaki satırın ham sütunu, etiketini her HEARTBEAT'ten güncelleyen YKİ'nin ekranda gösterdiği değerdir.

Pi'nin HEARTBEAT'ini Pixhawk'ta kesmek

Pi yer istasyonuyla hiç konuşmuyorsa iletimi otopilotta da kapatabilirsin. ArduPilot 4.7'de bu ayar MAVn_OPTIONS parametresinin 1. biti. Pi'nin bağlı olduğu kanalda değeri 2 yap ve Pixhawk'ı yeniden başlat; yeniden başlatmadan iletim sürer.

Buradaki n, MAVLink konuşan portların sırası. USB, TELEM1 ve TELEM2 üçü de MAVLink'teyse TELEM2 MAV3 olur. Ayar iki yönü birden keser, yer istasyonundan Pi'ye de mesaj gitmez. Pi'nin YKİ'ye veri yollaması gerekiyorsa bu ayara dokunma, kodu düzelt.

Hata Mesajları

Eski forum cevapları iletimi kesmek için SERIALn_OPTIONS'a 1024 eklemeyi söyler. 4.7'de bu bit MAVn_OPTIONS'a taşındı. Eski biti elle açarsan drone ARM olmaz ve mesajda SERIAL1 yerine hangi portu ayarladıysan onun numarası yazar.

SERIALn_OPTIONS'tan 1024'ü çıkar, ilgili MAVn_OPTIONS'a 2 ekle, Pixhawk'ı yeniden başlat. PreArm satırı kalkar.

Kursta ne var

Hedef sysid'i kendisi bulan, mod tablosunu araç tipine göre Copter ya da Plane'den seçen sürümü, Pixhawk'ın HEARTBEAT'i hangi porta neden ilettiğini kaynak koduyla ve hızlı teşhis tablosunu Sıfırdan İleri Seviyeye C# Eğitimi içindeki Güncel Eklemeler bölümünde bulursun.

Özel Türkçe Yer Kontrol İstasyonu Kütüphanesi: C# ile yazılmış, Türkçe yorum satırları ve dokümantasyonu olan MAVLink YKİ kütüphanesi.

Bu yazı işinize yaradı mı?

Ücretsiz kaynakTEKNOFEST İHA yazılım hazırlık kontrol listesiİHA yazılımı hazırlığını aşama aşama işaretleyin; yazdırılabilir liste.