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

1. Başlamadan önce

Thread Kenarlık Yönlendirici (TBR) codelab'inde, Raspberry Pi'ye dayalı bir İş Parçacığı Yönlendiricisi oluşturma konusunu göstereceğiz. Bu codelab'de

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

Bu codelab'de, kendi sınır yönlendiricinizin ve uygulamanızın, tek bir Thread Ağı oluşturmak için Google API'leriyle nasıl etkileşimde bulunabileceği ele alınmaktadır. Thread kimlik bilgilerinin birleştirilmesi, ağ dayanıklılığını artırdığı ve Thread'dan yararlanan uygulamalarla kullanıcı etkileşimlerini basitleştirdiği için önemlidir.

Ön koşullar

  • OTBR Codelab'i 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 Share API'leri kullanma
  • Google'ın ağıyla aynı kimlik bilgileriyle kendi OpenThread Sınır Yönlendiricinizi ayarlama

Gerekenler

  • Raspberry Pi 4 kart veya Open Thread Border Router (OTBR) çalıştıran başka bir Linux tabanlı kart
  • Radyo Ortak İşlemcisi (RCP) olarak IEEE 802.15.4 bağlantısı sağlayan kurul. OpenThread GitHub sayfasında farklı SoC tedarikçi firmalarının depolarının listesini ve talimatlarını görün

2. HTTP hizmetini kurma

İhtiyacımız olan ilk yapı taşı, Etkin Kimlik Bilgilerini okumamızı ve Beklemedeki Kimlik Bilgilerini OTBR'nize yazmamızı sağlayan bir arayüzdür. TBR oluştururken burada iki örnekle gösterildiği gibi kendi özel mekanizmalarınızı kullanın. İlk seçenek OTBR aracısıyla DBUS üzerinden yerel olarak nasıl arayüz kurulacağını gösterirken, ikinci seçenek OTBR üzerinde oluşturulabilen Rest API'yi kullanır.

İki yöntem de güvenli değildir ve üretim ortamında olduğu gibi kullanılmamalıdır. Ancak tedarikçi firma, yöntemi bir üretim ortamında kullanmak için her iki yöntem etrafında da şifreleme oluşturabilir veya kendi izleme hizmetinizi, geri dönüş HTTP ya da doğal olarak yerel DBUS çağrıları gönderecek şekilde genişletebilir.

1. seçenek: Python Komut Dosyası üzerinde DBUS ve HTTP API

91e5fdeed83e9354.png

Bu adım, kimlik bilgilerini okumak ve ayarlamak için iki uç noktayı kullanıma sunan, daha sonra DBUS komutlarını çağıran sade bir HTTP hizmeti oluşturur.

OTBR'niz olarak kullanılacak RPi'de 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 Thread kimlik bilgilerini almak için GET isteğini ya da Thread kimlik bilgilerini ayarlamak için POST isteğini kök yolunda dinler. Yük her zaman TLV ile JSON yapısıdır.

Aşağıdaki PUT isteği, /node/dataset/pending yolunu kullanarak yeni Beklemedeki İş Parçacığı Kimlik Bilgilerini OTBR'ye 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 yapılan GET isteği, geçerli olarak 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 yolu /io/openthread/BorderRouter/wpan0 nesne yoluna DBUS R/W komutlarını çağırıyor

# 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, yeteneklerinin iç gözleme alınmasını sağlıyor. Bu işlemi aşağıdaki ş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

Burada belirtilen desteklenen özellikleri de kontrol edebilirsiniz.

2. seçenek: OTBR Aracısı yerel HTTP Rest API

c748ca5151b6cacb.png

OpenThread Sınır Yönlendirici, varsayılan olarak REST_API=1 işaretiyle oluşturulur ve REST API'yi etkinleştirir. Önceki bir codelab'den oluşturulan derlemeniz REST API'yi etkinleştirmediyse RPi'nizde aşağıdaki işaretle OTBR derlediğinizden emin olun:

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

Bir OTBR aracısı şu ç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 açıklanmıştır). İçeriğini incelemek için tarayıcınızı (curl veya wget) kullanabilirsiniz. Desteklenen birçok yol arasında, /node/dataset/active üzerinde GET fiili ve /node/dataset/pending'de PUT fiilinin yer aldığı, yukarıda açıklanan kullanım alanları da bulunmaktadır.

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

Tercih Edilen Kimlik Bilgileri

Android'de Google Play Hizmetleri, ağınızdaki tüm TBR'ler için kimlik bilgilerinin kaydedilmesine izin verir ve bunu bekler. Her biri kendi Sınır Yönlendirici Aracısı Kimliği (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 Bilgilerini belirler.

BAID'ine bir dizi Thread ağ kimlik bilgisi ekleyen uygulama, kimlik bilgilerinin sahibi olur ve bu bilgilere erişmek için tüm izinlere sahip olur. Başka 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ı izni alındığında tüm uygulamalarda her zaman kullanılabilir. İş Parçacığı Yönlendirici 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. Ayarlandıktan sonra, Thread kimlik bilgilerini yöneten diğer uygulamalar bir getPreferredCredentials() çağrısından kimlik bilgilerini alabilir.

Google TBR Senkronizasyonu

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

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

Aynı ağda farklı tercih edilen kimlik bilgileri içeren iki Android cihaz (veya Android + iGHA) varsa TBR'yi başlangıçta yapılandıran cihaz TBR'de öncelikli olur.

3. taraf TBR ilk katılım

Kimlik bilgisinin depolama alanı şu anda kullanıcının Akıllı Ev cihazı ( Yapı) kapsamında değil. Her Android cihazın BAID depolama alanı olur. Ancak, ağda bir Google TBR olduğunda, iOS için Google Home uygulamasını çalıştıran diğer Android cihazlar ve iOS cihazlar bu TBR ile senkronize edilir ve telefon depolama alanında yerel kimlik bilgileri belirlemeye çalışır.

Yeni bir OOB TBR bir 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, mümkün olduğunda Thread cihazlarının tek bir Thread ağına bağlanmasını sağlar.
  • Tercih edilen ağ bulunmadığında yeni bir kimlik bilgisi grubu oluşturun ve bunu Google Play Hizmetleri'nde TBR'nize atayın. Android, bu kimlik bilgilerini Google tabanlı tüm TBR'lerde belirlenen standart kimlik bilgileri olarak kabul eder. Diğer tedarikçiler ise daha fazla cihazla ağ erişiminizi ve dayanıklılığınızı artırabilir.

cd8bc726f67b1fa1.png

4. Android Uygulamanızı Klonlama ve Değiştirme

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

Burada gösterilen tüm kaynak kodları, örnek uygulamada zaten kodlanmıştır. Uygulamayı kendi ihtiyaçlarınıza göre değiştirmeye davet edildiniz ancak işlevselliği incelemek için uygulamayı klonlayabilir veya önceden oluşturulmuş ikili programları çalıştırabilirsiniz.

  1. Şunu 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 > Klonlanan deponuzu açın ve işaret edin.
  3. Android telefonunuzda geliştirici modunu etkinleştirin.
  4. Bir USB kablosuyla bilgisayarınıza bağlayın.
  5. Uygulamayı Android Studio'da <Cmd+R> tuşlarına basarak çalıştırın (OS X) veya <Ctrl+R> (Win, Linux)
  6. Direksiyona gidin -> Geliştirici Yardımcı Programları -> Thread Ağı
  7. Mevcut farklı seçeneklerle etkileşime geçin. Aşağıdaki bölümlerde, her düğmede yürütülen kodu açacağız.

Tercih edilen kimlik bilgileri mevcut mu?

Bir TBR üreticisinin Google'a sorması gereken ilk soru, cihazda tercih edilen bir kimlik bilgisi grubunun mevcut olup olmadığıdır. Bu, akışınızın başlangıç noktası olmalıdır. Aşağıdaki kod, kimlik bilgilerinin varlığıyla ilgili GPS'i sorgular. Kimlik bilgisi paylaşılmadığından kullanıcı izni istemiyor.

/**
* 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 alma

Böyle bir durum söz konusuysa kimlik bilgilerini okumak istersiniz. Önceki kodla tek fark, intentSenderResult öğesini aldıktan sonra, gönderenden gelen bu sonucu kullanarak bir amaç oluşturmak ve başlatmak istemenizdir.

Kodumuzda, orijinal kod ViewModel'de (ThreadViewModel.kt) ve intent gözlemcileri Etkinlik Bölümü'nde ( ThreadFragment.kt) olduğundan kuruluş/mimari amacıyla bir MutableLiveData<IntentSender?> kullanırız. Dolayısıyla, intentSenderResult canlı verilere yayınlandığında, bu gözlemcinin içeriğini yürüteceğiz:

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 kullanıcı iznini tetikler ve onaylanırsa şunun üzerinden içerik 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?> ile nasıl paylaşılacağı aşağıda açıklanmaktadır.

GPS kimlik bilgilerini ayarlama

Mevcut olup olmamasına bakılmaksızın TBR'nizi Google Play Hizmetleri'ne kaydetmeniz gerekir. Uygulamanız, TBR'nizin Sınır Aracısı Kimliği ile ilişkili kimlik bilgilerini okuyabilen tek uygulama olur. Ancak TBR'niz kaydeden ilk kullanıcı olursa bu kimlik bilgileri, Tercih Edilen Kimlik Bilgileri kümesine kopyalanır. Kullanıcı yetki verdiği sürece bu bilgilere telefondaki herhangi bir Uygulama tarafından erişilebilir.

/**
* 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 için kimlik bilgilerini ayarlama

Bu bölüm her tedarikçi firmanın özelindedir. Bu codelab'de ise DBUS+Python HTTP Rest Server veya OTBR'deki yerel HTTP Dinlenme Sunucusu aracılığıyla uyguluyoruz.

/**
* 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, TBR'nizden kimlik bilgilerini almak için GET HTTP fiilini kullanın. Örnek Python komut dosyasını inceleyin.

Derleme ve İçe Aktarma

Android uygulamanızı oluştururken, Google Play Hizmetleri İş Parçacığı Modülü'nü desteklemek için manifest, derleme ve içe aktarma işlemlerinizde değişiklik yapmanız gerekir. Aşağıdaki üç snippet'te yapılan eklemelerin çoğu özetlenmektedir.

Örnek uygulamamızın temel olarak Matter devreye alma işlemi için tasarlandığını 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'

İlgili iç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 Keşfi

Örnek uygulamamız, ağdaki kullanılabilir İş Parçacığı Sınırı Yönlendiricileri ve ilgili BAID'lerinin listesini oluşturmak için mDNS/SD keşfini kullanır.

Bu, TBR'nizin bilgilerini GPS kimlik bilgilerinin depolama alanına girerken çok yararlı olur. Ancak kullanımı bu codelab'in kapsamı dışındadır. NSDManager Android Service Discovery kitaplığını kullanıyoruz. Kaynak kodunun tamamına ServiceDiscovery.kt'teki Örnek Uygulama'dan ulaşabilirsiniz.

6. Konuyu toparlamak gerekirse

Bu görüşmeleri uyguladıktan veya Örnek Uygulamayı kullandıktan sonra RPi OTBR'yi kullanmaya tamamen başlayabilirsiniz. Örnek Uygulamamızda 8 düğme gösteriliyor:

91979bf065e9673d.png

TBR'nize ilk katılım sırası şu şekildedir:

  1. Tercihli kimlik bilgilerinin mevcut olup olmadığını sorgu (mavi, 1. satır)
  2. Yanıta bağlı olarak
  3. GPS tercih edilen kimlik bilgilerini al (mavi, 2. satır)
  4. GPS'de TBR kimlik bilgilerini ayarla (mavi, 3. satır) -> TBR'nizi seçin -> Rastgele Oluştur -> Ağ adını girin -> Tamam
  5. Tercih ettiğiniz kimlik bilgilerini belirlediğinize göre Set RPi OTBR credentials (RPi OTBR kimlik bilgilerini ayarla) seçeneğini kullanarak bunları OTBR'nize ayarlayın. Bu işlem, bu kimlik bilgilerini bekleyen gruba uygular.

Örnek uygulama için varsayılan olarak 10 saniye gecikme kullanılır. Bu nedenle, bu sürenin ardından 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ı klonladık ve Google Play Hizmetleri'ndeki Thread Storage API'leri. Bu API'leri kullanarak, bir tedarikçinin TBR'sini gösteren ve bir RPi TBR'de ilk katılım sürecini yürütmek için kullanabileceğimiz ortak bir veri kümesi oluşturduk.

Bir kullanıcının tüm TBR'lerinin aynı ağda olması, Thread Network'ün direncini ve erişimini artırır. Ayrıca, kimlik bilgilerine erişimleri olmadığından uygulamaların Thread Cihazları'na dahil edemediği hatalı kullanıcı yolculuklarını da önler.

Bu codelab'in ve Örnek Uygulamalar'ın, kendi Uygulamanızı ve İş Parçacığı Yönlendirici ürününüzü tasarlamanıza ve geliştirmenize yardımcı olacağını umuyoruz.

8. Referanslar

RCP ortak işleyeni

DBUS