Thread ağını Google Thread Credentials API'leriyle paylaşma

1. Başlamadan önce

Thread Border Router (TBR) adlı codelab'imizde, Raspberry Pi tabanlı bir Thread Border Router'ın nasıl oluşturulacağını gösteriyoruz. Bu codelab'de

  • Thread ve kablosuz/Ethernet ağları arasında çift yönlü IP bağlantısı kurun.
  • mDNS (Kablosuz/Ethernet bağlantısında) ve SRP (Thread ağında) aracılığıyla çift yönlü hizmet keşfi sağlar.

Bu codelab, önceki codelab'in üzerine inşa edilmiştir. Kendi sınır yönlendiricinizin ve uygulamanızın, tek bir Thread ağı oluşturmak için Google API'leriyle nasıl etkileşime girebileceğini ele alır. Thread kimlik bilgilerinin birleştirilmesi, ağın sağlamlığını artırdığı ve Thread'i kullanan uygulamalarla kullanıcı etkileşimlerini basitleştirdiği için önemlidir.

Ön koşullar

  • OTBR Codelab'ini tamamlayın.
  • Linux, Android/Kotlin ve Thread ağı hakkında temel düzeyde bilgi

Neler öğreneceksiniz?

  • Kimlik bilgisi kümelerini almak ve ayarlamak için Thread Sharing API'lerini kullanma
  • Google'ın ağıyla aynı kimlik bilgilerini kullanarak kendi OpenThread Border Router'ınızı nasıl ayarlayabilirsiniz?

İhtiyacınız olanlar

  • Open Thread Border Router'ı (OTBR) çalıştıran Raspberry Pi 4 kartı veya Linux tabanlı başka bir kart
  • Radyo yardımcı işlemcisi (RCP) olarak IEEE 802.15.4 bağlantısı sağlayan kart. Farklı SoC tedarikçilerinin depolarının ve talimatlarının listesini OpenThread GitHub sayfasında bulabilirsiniz.

2. HTTP hizmetini ayarlama

İhtiyacımız olan ilk yapı taşı, etkin kimlik bilgilerini okumamıza ve bekleyen kimlik bilgilerini OTBR'nize yazmamıza olanak tanıyan bir arayüzdür. Bir TBR oluştururken, burada iki örnekle gösterildiği gibi kendi tescilli mekanizmalarınızı kullanın. İlk seçenekte, DBUS aracılığıyla OTBR aracısıyla yerel olarak nasıl arayüz oluşturulacağı gösterilirken ikinci seçenekte, OTBR üzerinde oluşturulabilen Rest API'den yararlanılır.

Her iki yöntem de güvenli değildir ve üretim ortamında olduğu gibi kullanılmamalıdır. Ancak bir satıcı, üretim ortamında kullanmak için her iki yöntemden birini kullanarak şifreleme oluşturabilir veya kendi izleme hizmetinizi, geri döngü HTTP ya da doğal olarak yerel DBUS çağrıları yapacak şekilde genişletebilirsiniz.

1. seçenek: Python komut dosyasında DBUS ve HTTP API

91e5fdeed83e9354.png

Bu adım, kimlik bilgilerini okumak ve ayarlamak için iki uç nokta sunan, nihayetinde DBUS komutlarını çağıran temel bir HTTP hizmeti oluşturur.

OTBR'niz olarak hizmet verecek RPi'ye Python 3 bağımlılıklarını yükleyin:

$ pip install dbus-python shlex json

Komut dosyasını şu şekilde çalıştırın:

$  sudo python credentials_server.py 8081
serving at port 8081

Örnek, 8081 numaralı bağlantı noktasında bir HTTP sunucusu oluşturur ve kök yolda, Thread kimlik bilgilerini almak için bir GET isteğini veya Thread kimlik bilgilerini ayarlamak için bir POST isteğini dinler. Yük her zaman TLV ile birlikte bir JSON yapısıdır.

Aşağıdaki PUT isteği, /node/dataset/pending yolunu kullanarak OTBR'ye yeni Bekleyen İş Parçacığı Kimlik Bilgileri ayarlar. Bu durumda, bekleyen kimlik bilgileri 10 saniye içinde uygulanır:

PUT /node/dataset/pending
Host: <IP>:8081
ContentType: "application/json"
acceptMimeType: "application/json"
...
{
        "ActiveDataset": "<TLV encoded new Thread Dataset>"
"PendingTimestamp": {
        "Seconds": <Unix timestamp in seconds>,
        "Ticks": 0,
        "Authoritative": false
},
"Delay": 10000 // in milliseconds
}

/node/dataset/active için bir GET isteği, şu anda etkin olan kimlik bilgilerini getirir.

GET /node/dataset/active
Host: <IP>:8081
ContentType = "application/json"
acceptMimeType = "text/plain"
...
<TLV encoded Thread Dataset>

Komut dosyası, io.openthread.BorderRouter.wpan0 veri yolu yoluna ve /io/openthread/BorderRouter/wpan0 nesne yoluna DBUS R/W komutları çağırır:

# D-BUS interface
def call_dbus_method(interface, method_name, *arguments):
    bus = dbus.SystemBus()
    obj = bus.get_object('io.openthread.BorderRouter.wpan0', '/io/openthread/BorderRouter/wpan0')
    iface = dbus.Interface(obj, interface)
    method = getattr(iface, method_name)
    res = method(*arguments)
    return res

def get_dbus_property(property_name):
    return call_dbus_method('org.freedesktop.DBus.Properties', 'Get', 'io.openthread.BorderRouter',
                                 property_name)

def set_dbus_property(property_name, property_value):
    return call_dbus_method('org.freedesktop.DBus.Properties', 'Set', 'io.openthread.BorderRouter',
                                 property_name, property_value)                               

DBUS, özelliklerinin iç gözlemine olanak tanır. Bunu şu şekilde yapabilirsiniz:

$ sudo dbus-send --system --dest=io.openthread.BorderRouter.wpan0 \
        --type=method_call --print-reply /io/openthread/BorderRouter/wpan0 \
        org.freedesktop.DBus.Introspectable.Introspect

Ayrıca, burada belgelenen desteklenen özellikleri de kontrol edebilirsiniz.

2. seçenek: OTBR aracısı yerel HTTP REST API'si

c748ca5151b6cacb.png

OpenThread Border Router, varsayılan olarak REST_API=1 işaretiyle oluşturulur ve REST API'yi etkinleştirir. Önceki bir codelab'deki derlemenizde REST API etkinleştirilmediyse RPi'nizde OTBR'yi bu işaretle derlediğinizden emin olun:

$ REST_API=1 INFRA_IF_NAME=wlan0 ./script/setup

Bir OTBR aracısı şu komut çalıştırılarak yeniden başlatılabilir:

$ sudo systemctl restart otbr-agent.service

Aracı, 8081 numaralı bağlantı noktasında bir HTTP sunucusu başlatır. Bu sunucu, bir kullanıcının veya izleme programının OTBR'de birçok görevi gerçekleştirmesine olanak tanır (burada belgelenmiştir). İçeriğini incelemek için tarayıcınızı, curl veya wget simgesini kullanabilirsiniz. Desteklenen birçok yol arasında, yukarıda açıklanan kullanım alanları da yer alır. Bu alanlarda GET fiili /node/dataset/active üzerinde, PUT fiili ise /node/dataset/pending üzerinde kullanılır.

3. Android'de Kimlik Bilgisi Çerçevesi'ni ayarlama

Tercih edilen kimlik bilgileri

Android'deki Google Play Hizmetleri, ağınızdaki tüm TBR'ler için kimlik bilgilerinin kaydedilmesine izin verir ve bu işlemi bekler. Her biri, Border Router Agent ID (BAID) ile tanımlanır. Bu görevi gerçekleştirmek için ThreadNetworkClient arayüzünün addCredentials() yöntemini kullanacaksınız. Google Play Hizmetleri depolama alanına eklenen ilk TBR, bu mobil cihaz için Tercih Edilen Kimlik Bilgileri'ni belirler.

BAID'sine bir dizi Thread ağı kimlik bilgisi ekleyen uygulama, kimlik bilgilerinin sahibi olur ve bunlara erişmek için tam izinlere sahip olur. Diğer uygulamalar tarafından eklenen kimlik bilgilerine erişmeye çalışırsanız PERMISSION_DENIED hatası alırsınız. Ancak, tercih edilen kimlik bilgileri, kullanıcı izniyle her zaman herhangi bir uygulama için kullanılabilir. Thread Border Router ağı güncellendiğinde Google Play Hizmetleri'nde depolanan kimlik bilgilerini güncel tutmanızı öneririz. Bu bilgiler şu anda kullanılmasa da gelecekte daha iyi yolculuklar sunabiliriz.

İlk TBR daha sonra hariç tutulsa bile Tercih Edilen Kimlik Bilgileri Android cihazda kalır. Ayarlama işlemi tamamlandıktan sonra, Thread kimlik bilgilerini yöneten diğer uygulamalar getPreferredCredentials() çağrısı yaparak kimlik bilgilerini alabilir.

Google TBR Sync

Android cihazlar, Google TBR'leriyle otomatik olarak senkronize edilir. Android'de kimlik bilgisi yoksa cihazlar bunları ağınızdaki Google TBR'lerinden çıkarır ve bu kimlik bilgileri, Tercih Edilen Kimlik Bilgileri olur. TBR'ler ile Android cihaz arasındaki senkronizasyon yalnızca TBR tek bir kullanıcıyla eşlenmişse veya aynı akıllı evde (yapı) bulunan iki kullanıcıyla eşlenmişse gerçekleşir.

Bu işlem, aynı yapıda bulunan başka bir Google kullanıcısı Android için GHA veya iOS için GHA'da olduğunda da gerçekleşir. iOS için GHA söz konusu olduğunda, tercih edilen kimlik bilgileri yoksa tercih edilen kimlik bilgileri iOS depolama alanında ayarlanır.

Aynı ağda farklı tercih edilen kimlik bilgileri kümelerine sahip iki Android cihaz (veya Android + iGHA) varsa TBR'yi ilk yapılandıran cihaz, TBR'de öncelikli olur.

Üçüncü taraf TBR'ye ilk katılım

Kimlik bilgisinin depolama alanı şu anda kullanıcının akıllı evine (yapı) göre belirlenmiyor. Her Android cihazın kendi BAID depolama alanı vardır. Ancak ağda bir Google TBR olduğunda, diğer Android cihazlar ve iOS için Google Home uygulamasının çalıştığı iOS cihazlar bu TBR ile senkronize olur ve telefon depolama alanında yerel kimlik bilgileri ayarlamaya çalışır.

Yeni bir OOB TBR ağ oluşturmadan önce Android'in depolama alanında tercih edilen bir ağın mevcut olup olmadığını kontrol etmek önemlidir.

  • Tercih edilen bir ağ varsa satıcı bunu kullanmalıdır. Bu sayede, Thread cihazlarının mümkün olduğunda tek bir Thread ağına bağlanması sağlanır.
  • Tercih edilen bir ağ yoksa yeni bir kimlik bilgisi kümesi oluşturun ve bunu Google Play Hizmetleri'nde TBR'nize atayın. Android, bu kimlik bilgilerini Google tabanlı tüm TBR'lerde ayarlanan standart kimlik bilgileri olarak kabul eder ve diğer satıcılar, ek cihazlarla ağınızın erişimini ve sağlamlığını artırabilir.

cd8bc726f67b1fa1.png

4. Android uygulamanızı klonlama ve değiştirme

Thread API'ye yapılabilecek başlıca olası çağrıları gösteren bir Android uygulaması oluşturduk. Bu kalıpları uygulamanızda kullanabilirsiniz. Bu codelab'de, Matter için Google Home Örnek Uygulaması'nı GitHub'dan klonlayacağız.

Burada gösterilen tüm kaynak kodu, örnek uygulamada zaten kodlanmıştır. İhtiyaçlarınıza göre değiştirebilirsiniz ancak işlevselliği incelemek için uygulamayı klonlamanız veya önceden oluşturulmuş ikili dosyaları çalıştırmanız yeterlidir.

  1. Aşağıdaki yöntemlerden birini kullanarak klonlayın:
$ git clone https://github.com/google-home/sample-apps-for-matter-android.git
  1. Android Studio'yu indirip açın.
  2. Dosya > Aç'ı tıklayın ve klonlanmış deponuzu gösterin.
  3. Android telefonunuzda geliştirici modunu etkinleştirin.
  4. USB kablosuyla bilgisayarınıza bağlayın.
  5. Android Studio'dan <Cmd+R> (OS X) veya <Ctrl+R> (Win, Linux) tuşlarına basarak uygulamayı çalıştırın.
  6. Dişli simgesi -> Geliştirici Araçları -> Thread Ağı'na gidin.
  7. Kullanılabilir farklı seçeneklerle etkileşim kurun. Aşağıdaki bölümlerde, her düğmede yürütülen kodu inceleyeceğiz.

Tercih edilen kimlik bilgileri var mı?

Bir TBR üreticisinin Google'a sorması gereken ilk soru, cihazda tercih edilen bir kimlik bilgisi grubunun zaten mevcut olup olmadığıdır. Bu, akışınızın başlangıç noktası olmalıdır. Aşağıdaki kod, kimlik bilgilerinin varlığı için GPS'e sorgu gönderir. Herhangi bir kimlik bilgisi paylaşılmadığı için kullanıcı izni istenmez.

/**
* Prompts whether credentials exist in storage or not. Consent from user is not necessary
*/

fun doGPSPreferredCredsExist(activity: FragmentActivity) {
 try {
   // Uses the ThreadNetwork interface for the preferred credentials, adding
   // a listener that will receive an intentSenderResult. If that is NULL, 
   // preferred credentials don't exist. If that isn't NULL, they exist.
   // In this case we'll not use it.

   ThreadNetwork.getClient(activity).preferredCredentials.addOnSuccessListener { intentSenderResult ->
     intentSenderResult.intentSender?.let { intentSender ->
       ToastTimber.d("threadClient: preferred credentials exist", activity)
       // don't post the intent on `threadClientIntentSender` as we do when
       // we really want to know which are the credentials. That will prompt a
       // user consent. In this case we just want to know whether they exist
     } ?: ToastTimber.d(
       "threadClient: no preferred credentials found, or no thread module found", activity
     )
   }.addOnFailureListener { e: Exception ->
     Timber.d("ERROR: [${e}]")
   }
 } catch (e: Exception) {
   ToastTimber.e("Error $e", activity)
 }
}

GPS tercih edilen kimlik bilgilerini edinme

Kimlik bilgileri varsa bunları okumak istersiniz. Önceki koddan tek farkı, intentSenderResult aldıktan sonra gönderenden gelen bu sonucu kullanarak bir amaç oluşturup başlatmak istemenizdir.

Kodumuzda, orijinal kod ViewModel'de (ThreadViewModel.kt) ve amaç gözlemcileri Activity Fragment'te (ThreadFragment.kt) olduğundan kuruluş/mimari amaçlarla MutableLiveData<IntentSender?> kullanıyoruz. Bu nedenle, intentSenderResult canlı verilere gönderildikten sonra bu gözlemcinin içeriğini yürütürüz:

viewModel.threadClientIntentSender.observe(viewLifecycleOwner) { sender ->
 Timber.d(
   "threadClient: intent observe is called with [${intentSenderToString(sender)}]"
 )
 if (sender != null) {
   Timber.d("threadClient: Launch GPS activity to get ThreadClient")
   threadClientLauncher.launch(IntentSenderRequest.Builder(sender).build())
   viewModel.consumeThreadClientIntentSender()
 }
}

Bu işlem, kimlik bilgilerinin paylaşılmasıyla ilgili kullanıcı iznini tetikler ve onaylanırsa içerikleri şu yollarla döndürür:

threadClientLauncher =
 registerForActivityResult(ActivityResultContracts.StartIntentSenderForResult()) { result ->
   if (result.resultCode == RESULT_OK) {
     val threadNetworkCredentials =
       ThreadNetworkCredentials.fromIntentSenderResultData(result.data!!)
     viewModel.threadPreferredCredentialsOperationalDataset.postValue(
       threadNetworkCredentials
     )
   } else {
     val error = "User denied request."
     Timber.d(error)
     updateThreadInfo(null, "")
   }
 }

Kimlik bilgilerinin MutableLiveData<ThreadNetworkCredentials?>'ya gönderilmesi aşağıda açıklanmıştır.

GPS kimlik bilgilerini ayarlama

TBR'niz varsa veya yoksa Google Play Hizmetleri'ne kaydetmeniz gerekir. Uygulamanız, TBR'nizin Sınır Görevlisi Kimliği ile ilişkili kimlik bilgilerini okuyabilen tek uygulama olur. Ancak TBR'niz ilk kaydedilen TBR ise bu kimlik bilgileri, Tercih Edilen Kimlik Bilgileri kümesine kopyalanır. Kullanıcı izin verdiği sürece bu bilgilere telefondaki tüm uygulamalar erişebilir.

/**
* Last step in setting the GPS thread credentials of a TBR
*/
private fun associateGPSThreadCredentialsToThreadBorderRouterAgent(
 credentials: ThreadNetworkCredentials?,
 activity: FragmentActivity,
 threadBorderAgent: ThreadBorderAgent,
) {
 credentials?.let {
   ThreadNetwork.getClient(activity).addCredentials(threadBorderAgent, credentials)
     .addOnSuccessListener {
       ToastTimber.d("threadClient: Credentials added", activity)
     }.addOnFailureListener { e: Exception ->
       ToastTimber.e("threadClient: Error adding the new credentials: $e", activity)
     }
 }
}

TBR ürününüzün kimlik bilgilerini ayarlama

Bu bölüm her tedarikçiye özeldir ve bu codelab'de DBUS+Python HTTP Rest Sunucusu veya OTBR'den yerel HTTP Rest Sunucusu ile uygulanır.

/**
* Creates credentials in the format used by the OTBR HTTP server. See its documentation in
* https://github.com/openthread/ot-br-posix/blob/main/src/rest/openapi.yaml#L215
*/
fun createJsonCredentialsObject(newCredentials: ThreadNetworkCredentials): JSONObject {
 val jsonTimestamp = JSONObject()
 jsonTimestamp.put("Seconds", System.currentTimeMillis() / 1000)
 jsonTimestamp.put("Ticks", 0)
 jsonTimestamp.put("Authoritative", false)

 val jsonQuery = JSONObject()
 jsonQuery.put(
   "ActiveDataset",
   BaseEncoding.base16().encode(newCredentials.activeOperationalDataset)
 )
 jsonQuery.put("PendingTimestamp", jsonTimestamp)
 // delay of committing the pending set into active set: 10000ms
 jsonQuery.put("Delay", 10000)

 Timber.d(jsonQuery.toString())

 return jsonQuery
}

//(...)

var response = OtbrHttpClient.createJsonHttpRequest(
 URL("http://$ipAddress:$otbrPort$otbrDatasetPendingEndpoint"),
 activity,
 OtbrHttpClient.Verbs.PUT,
 jsonQuery.toString()
)

TBR ürününüzden kimlik bilgilerini alma

Daha önce gösterildiği gibi, kimlik bilgilerini TBR'nizden almak için GET HTTP fiilini kullanın. Örnek Python komut dosyasına bakın.

Derleme ve İçe Aktarma

Android uygulamanızı oluştururken Google Play Hizmetleri İş Parçacığı Modülü'nü desteklemek için manifestinizde, derlemenizde ve içe aktarmalarınızda değişiklik yapmanız gerekir. Aşağıdaki üç snippet, eklemelerin çoğunu özetlemektedir.

Örnek uygulamamızın öncelikle Matter devreye alma için oluşturulduğunu unutmayın. Bu nedenle, Manifest ve Gradle dosyaları yalnızca Thread kimlik bilgilerini kullanmak için gereken eklemelerden daha karmaşıktır.

Manifest değişiklikleri

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    (...)
    <!-- usesCleartextTraffic needed for OTBR local unencrypted communication -->
    <!-- Not needed for Thread Module, only used for HTTP -->
    <uses-feature
    (...)
        android:usesCleartextTraffic="true">

    <application>
    (...)
    <!-- GPS automatically downloads scanner module when app is installed -->
    <!-- Not needed for Thread Module, only used for scanning QR Codes -->
    <meta-data
        android:name="com.google.mlkit.vision.DEPENDENCIES"
        android:value="barcode_ui"/>
    </application>
</manifest>

Build.gradle

// Thread Network
implementation 'com.google.android.gms:play-services-threadnetwork:16.0.0'
// Thread QR Code Scanning
implementation 'com.google.android.gms:play-services-code-scanner:16.0.0'
// Thread QR Code Generation
implementation 'com.journeyapps:zxing-android-embedded:4.1.0'
// Needed for using BaseEncoding class
implementation 'com.google.guava:guava:31.1-jre'

Alakalı İçe Aktarmalar

// Thread Network Module
import com.google.android.gms.threadnetwork.ThreadNetworkCredentials
import com.google.android.gms.threadnetwork.ThreadBorderAgent
import com.google.android.gms.threadnetwork.ThreadNetwork

// Conversion of credentials to/fro Base16 (hex)
import com.google.common.io.BaseEncoding

// HTTP
import java.io.BufferedInputStream
import java.io.InputStream
import java.net.HttpURLConnection
import java.net.URL
import java.nio.charset.StandardCharsets

// Co-routines for HTTP calls
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch


// JSON
import org.json.JSONObject

// Logs
import timber.log.Timber

// mDNS/SD
import android.net.nsd.NsdServiceInfo

// QR Code reader / writer
import com.google.mlkit.vision.barcode.common.Barcode
import com.google.mlkit.vision.codescanner.GmsBarcodeScannerOptions
import com.google.mlkit.vision.codescanner.GmsBarcodeScanning
import com.google.zxing.BarcodeFormat
import com.google.zxing.MultiFormatWriter
import com.journeyapps.barcodescanner.BarcodeEncoder

5. mDNS/SD Discovery

Örnek uygulamamız, ağdaki kullanılabilir Thread sınır yönlendiricilerinin listesini ve ilgili BAID'lerini oluşturmak için mDNS/SD keşfini kullanır.

Bu, TBR'nizin bilgilerini GPS kimlik bilgilerinin depolama alanına girerken çok faydalıdır. Ancak kullanımı bu codelab'in kapsamı dışındadır. Android Service Discovery kitaplığı NSDManager'ı kullanıyoruz ve tam kaynak kodu, ServiceDiscovery.kt konumundaki örnek uygulamada mevcuttur.

6. Konuyu toparlayacak olursak

Bu çağrıları uyguladıktan veya örnek uygulamayı kullandıktan sonra RPi OTBR'nizi tamamen kullanmaya başlayabilirsiniz. Örnek uygulamamızda 8 düğme bulunur:

91979bf065e9673d.png

TBR'nizi kullanmaya başlamak için olası bir sıra:

  1. Tercihli kimlik bilgilerinin olup olmadığını sorgulayın (mavi, 1. satır).
  2. Yanıtın içeriğine bağlı olarak
  3. GPS tercih edilen kimlik bilgilerini alma (mavi, 2. satır)
  4. GPS'te TBR kimlik bilgilerini ayarlayın (mavi, 3. satır) -> TBR'nizi seçin -> Rastgele Oluştur -> Ağ adını girin -> Tamam
  5. Artık tercih edilen kimlik bilgileriniz olduğuna göre, Set RPi OTBR credentials (RPi OTBR kimlik bilgilerini ayarlayın) komutunu kullanarak bunları OTBR'nize ayarlayın. Bu komut, kimlik bilgilerini bekleyen kümeye uygular.

Örnek uygulamada varsayılan olarak 10 saniye gecikme kullanılır. Bu nedenle, bu dönemden sonra RPi TBR'nizin (ve ağında bulunabilecek diğer düğümlerin) kimlik bilgileri yeni veri kümesine taşınır.

7. Sonuç

Bu codelab'de örnek bir Android uygulamasını klonladık ve Google Play Hizmetleri'nin Thread Storage API'lerini kullanan çeşitli kod snippet'lerini analiz ettik. Bu API'leri, bir satıcının TBR'sini gösteren bir RPi TBR'ye dahil edebileceğimiz ortak bir veri kümesi oluşturmak için kullandık.

Bir kullanıcının tüm TBR'lerinin aynı ağda olması, Thread ağının esnekliğini ve erişimini artırır. Ayrıca, uygulamaların kimlik bilgilerine erişimi olmadığı için Thread cihazlarını kullanıma hazırlayamadığı kusurlu kullanıcı yolculuklarını da önler.

Bu codelab'in ve örnek uygulamaların, kendi uygulamanızı ve Thread Border Router ürününüzü tasarlayıp geliştirmenize yardımcı olacağını umuyoruz.

8. Referanslar

RCP yardımcı işlemcisi

DBUS