BluetoothGatt — ce este, metode și protocolul GATT BLE în Android

Autor: IT Sectr Publicat: 2026-07-16 Timp de citire: 10 min

BluetoothGatt — clasa Android care oferă API pentru funcționarea clientului GATT (Generic Attribute Profile) peste conexiunea BLE. BluetoothGatt încapsulează conexiunea la serverul GATT la distanță (dispozitiv periferic BLE) și gestionează toate operațiunile profilului: descoperirea serviciilor, citirea și scrierea caracteristicilor, abonarea la notificări și indicații. Instanța BluetoothGatt se obține prin BluetoothDevice.connectGatt() cu callback BluetoothGattCallback. Conform Android Developers, 2026, BluetoothGatt este clasa centrală pentru comunicarea BLE bidirecțională, suportând operațiuni GATT de la BLE 4.0 până la BLE 5.4.

Puncte principale

  • BluetoothGatt — clasa Android pentru clientul GATT care gestionează conexiunea BLE cu dispozitivul periferic
  • connectGatt() — metoda BluetoothDevice pentru crearea BluetoothGatt; acceptă context, autoConnect, callback și transport
  • discoverServices() — metoda pentru obținerea ierarhiei GATT: servicii (BluetoothGattService), caracteristici (BluetoothGattCharacteristic)
  • readCharacteristic/writeCharacteristic — metode de citire și scriere cu rezultat asincron prin BluetoothGattCallback
  • setCharacteristicNotification — metoda de abonare la notificări BLE cu scrierea obligatorie a descriptorului CCCD

Ce este BluetoothGatt: esența și crearea conexiunii

BluetoothGatt — este un obiect proxy care reprezintă conexiunea GATT între dispozitivul Android (central) și periferia BLE (server). Fiecare instanță BluetoothGatt corespunde unei conexiuni BLE active. Prin intermediul său se execută toate operațiunile profilului GATT: descoperire, citire, scriere, notificări. BluetoothGatt nu se creează direct — este returnat de metoda BluetoothDevice.connectGatt().

Crearea BluetoothGatt necesită patru parametri. Context — contextul aplicației (Activity sau Application). autoConnect — dacă este false, Android inițiază imediat conexiunea directă; dacă este true, Android se conectează automat la detectarea dispozitivului (util pentru conexiunea în fundal). BluetoothGattCallback — callback obligatoriu pentru toate evenimentele GATT. transport — BluetoothDevice.TRANSPORT_LE (BLE) sau TRANSPORT_BREDR (Classic). Pe dispozitivele BLE utilizați întotdeauna TRANSPORT_LE.

Ciclul de viață BluetoothGatt constă din cinci stări. DISCONNECTED — starea inițială. CONNECTING — după apelarea connectGatt, până la confirmare. CONNECTED — după onConnectionStateChange cu STATE_CONNECTED. După conectare se apelează discoverServices() pentru obținerea ierarhiei GATT. După finalizarea lucrului — disconnect() și close() pentru eliberarea resurselor sistemului. Fără apelarea close(), aplicația poate epuiza limita de conexiuni BLE Android (de obicei 4–8).

kotlin
// Crearea conexiunii BluetoothGatt
import android.bluetooth.*

class GattConnector(private val context: Context) {

    private var bluetoothGatt: BluetoothGatt? = null

    fun connect(device: BluetoothDevice): BluetoothGatt? {
        // Închide conexiunea anterioară dacă există
        close()

        bluetoothGatt = device.connectGatt(
            context,
            false,  // autoConnect = false ( )
            object : BluetoothGattCallback() {
                override fun onConnectionStateChange(
                    gatt: BluetoothGatt, status: Int, newState: Int
                ) {
                    when (newState) {
                        BluetoothProfile.STATE_CONNECTED -> {
                            // 2. Conexiune stabilită → Descoperire
                            gatt.discoverServices()
                        }
                        BluetoothProfile.STATE_DISCONNECTED -> {
                            // 3. Conexiune pierdută
                            close()
                        }
                    }
                }

                override fun onServicesDiscovered(
                    gatt: BluetoothGatt, status: Int
                ) {
                    if (status == BluetoothGatt.GATT_SUCCESS) {
                        // 4. Ierarhie GATT primită
                        onGattReady(gatt)
                    }
                }
            },
            BluetoothDevice.TRANSPORT_LE
        )
        return bluetoothGatt
    }

    private fun onGattReady(gatt: BluetoothGatt) {
        // GATT pregătit pentru operații de citire/scriere
    }

    fun close() {
        bluetoothGatt?.disconnect()
        bluetoothGatt?.close()
        bluetoothGatt = null
    }
}

Clasa GattConnector demonstrează crearea corectă a BluetoothGatt. Parametrul autoConnect=false — conexiune directă (pentru dispozitive scanate). La onConnectionStateChange cu STATE_CONNECTED se apelează imediat discoverServices(). onServicesDiscovered semnalează pregătirea GATT. close() apelează secvențial disconnect() și close() — fără close() resursele sistemului nu sunt eliberate, ceea ce duce la scurgeri de conexiuni BLE.

Descoperirea serviciilor și caracteristicilor prin BluetoothGatt

discoverServices() — prima metodă GATT apelată după conectare. Inițiază căutarea asincronă a tuturor serviciilor pe periferia BLE. Rezultatul vine în onServicesDiscovered() cu codul de stare: GATT_SUCCESS (0) — reușit, 133 — GATT_ERROR, 8 — GATT_CONNECTION_TIMEOUT. După descoperirea reușită, BluetoothGatt completează lista serviciilor disponibile prin getServices().

Fiecare BluetoothGattService conține o listă de BluetoothGattCharacteristic. Caracteristica are UUID, proprietăți (PROPERTY_READ, PROPERTY_WRITE, PROPERTY_NOTIFY) și descriptori opționali. Proprietățile determină ce operațiuni sunt permise: dacă caracteristica nu are PROPERTY_READ, apelarea readCharacteristic va returna o eroare. Pentru obținerea descriptorilor caracteristicii se utilizează getDescriptors().

kotlin
// Descoperirea serviciilor și căutarea caracteristicilor
class GattServiceExplorer {

    // Găsește serviciul după UUID
    fun findService(gatt: BluetoothGatt, uuid: UUID): BluetoothGattService? {
        return gatt.services?.firstOrNull { it.uuid == uuid }
    }

    // Găsește caracteristica în serviciu
    fun findCharacteristic(
        service: BluetoothGattService,
        uuid: UUID
    ): BluetoothGattCharacteristic? {
        return service.characteristics?.firstOrNull { it.uuid == uuid }
    }

    // Obține toate operațiunile suportate ale caracteristicii
    fun getCharacteristicProperties(chars: BluetoothGattCharacteristic): List<String> {
        val props = mutableListOf<String>()
        with(chars.properties) {
            if (and(BluetoothGattCharacteristic.PROPERTY_READ) != 0) props.add("READ")
            if (and(BluetoothGattCharacteristic.PROPERTY_WRITE) != 0) props.add("WRITE")
            if (and(BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE) != 0) props.add("WRITE_NO_RESP")
            if (and(BluetoothGattCharacteristic.PROPERTY_NOTIFY) != 0) props.add("NOTIFY")
            if (and(BluetoothGattCharacteristic.PROPERTY_INDICATE) != 0) props.add("INDICATE")
        }
        return props
    }

    // Înregistrează întreaga ierarhie GATT
    fun dumpGattTree(gatt: BluetoothGatt) {
        gatt.services?.forEach { service ->
            print("Service: ${service.uuid}")
            service.characteristics?.forEach { char ->
                print("  Characteristic: ${char.uuid}, properties: ${char.properties}")
                char.descriptors?.forEach { desc ->
                    print("    Descriptor: ${desc.uuid}")
                }
            }
        }
    }
}

Clasa GattServiceExplorer oferă utilitare pentru navigarea în ierarhia GATT. findService și findCharacteristic caută servicii și caracteristici după UUID. getCharacteristicProperties verifică măștile de biți ale proprietăților prin and(). dumpGattTree afișează ierarhia completă în log — utilă la depanarea dispozitivelor BLE. Toate operațiunile pe BluetoothGatt trebuie executate după onServicesDiscovered reușit, altfel getServices() va returna o listă goală.

Citirea caracteristicilor BLE: readCharacteristic și readDescriptor

readCharacteristic() — metoda asincronă BluetoothGatt pentru citirea valorii caracteristicii de pe dispozitivul BLE la distanță. Rezultatul vine în onCharacteristicRead() al BluetoothGattCallback. Dacă pe dispozitiv există 2+ caracteristici potrivite (puțin probabil, dar posibil), readCharacteristic() poate citi una nețintă — este mai sigur să apelați readCharacteristic() pe instanța BluetoothGattCharacteristic, nu după UUID.

readDescriptor() — metoda pentru citirea valorii descriptorului caracteristicii. Un descriptor tipic — CCCD (Client Characteristic Configuration Descriptor, UUID 0x2902) care determină dacă notificările sunt activate. Rezultatul în onDescriptorRead(). Citirea descriptorilor este rar necesară în practică — CCCD este gestionat de setCharacteristicNotification(), dar pentru descriptori personalizați (User Description 0x2901, Presentation Format 0x2904) readDescriptor() este singura modalitate de a obține metadate.

MTU și citirea datelor mari — dacă valoarea caracteristicii depășește MTU (23 de octeți pentru BLE 4.0), Android fragmentează și reasamblează automat datele printr-o secvență de cereri de citire prin stiva BLE. Pentru BLE 5.0+ cu MTU extins (până la 251 de octeți) fragmentarea nu este necesară — o singură citire returnează datele complete. Înainte de citire se poate apela requestMtu() pentru negocierea MTU maxim.

kotlin
// Citirea caracteristicii și descriptorului prin BluetoothGatt
class GattReader {

    fun readHeartRate(gatt: BluetoothGatt) {
        // UUID serviciu Heart Rate = 0x180D
        val service = gatt.getService(UUID.fromString("0000180D-0000-1000-8000-00805F9B34FB"))
            ?: return
        // UUID măsurare Heart Rate = 0x2A37
        val characteristic = service.getCharacteristic(
            UUID.fromString("00002A37-0000-1000-8000-00805F9B34FB")
        ) ?: return

        if (hasProperty(characteristic, BluetoothGattCharacteristic.PROPERTY_READ)) {
            gatt.readCharacteristic(characteristic)
        }
    }

    fun readDescriptor(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic) {
        // CCCD (0x2902)
        val cccd = characteristic.getDescriptor(
            UUID.fromString("00002902-0000-1000-8000-00805F9B34FB")
        )
        cccd?.let { gatt.readDescriptor(it) }
    }

    // Procesează datele în callback-ul onCharacteristicRead:
    fun parseHeartRate(value: ByteArray): Int {
        // BLE Heart Rate: octeți = flags, al doilea = bpm
        return if (value.isNotEmpty()) value[1].toInt() and 0xFF else 0
    }

    private fun hasProperty(char: BluetoothGattCharacteristic, prop: Int): Boolean {
        return char.properties and prop != 0
    }
}

Clasa GattReader demonstrează citirea caracteristicii Heart Rate. Serviciul 0x180D conține caracteristica 0x2A37 (Heart Rate Measurement) — profilul standard BLE Bluetooth SIG. Înainte de citire se verifică proprietatea PROPERTY_READ prin hasProperty. parseHeartRate parsează formatul BLE al pulsului: primul octet — flags (formatul datelor), al doilea — valoarea bpm. Descriptorul CCCD (0x2902) este citit pentru verificarea stării notificărilor.

Scrierea caracteristicilor: writeCharacteristic cu WriteType

writeCharacteristic() — metoda BluetoothGatt pentru scrierea datelor pe periferia BLE. Pe Android API 33+, writeCharacteristic() a fost înlocuit cu writeCharacteristic(request), unde BluetoothGattCharacteristicWriteRequest este un obiect de cerere care conține caracteristica, un array de octeți și WriteType. Metoda veche writeCharacteristic(characteristic) cu setValue() este deprecated. WriteType determină comportamentul cererii: WRITE_TYPE_DEFAULT (depinde de proprietățile caracteristicii), WRITE_TYPE_NO_RESPONSE (withoutResponse) și WRITE_TYPE_SIGNED (autorizare).

Alegerea WriteType influențează viteza și fiabilitatea. WRITE_TYPE_DEFAULT corespunde de obicei withResponse (dacă caracteristica are PROPERTY_WRITE) sau withoutResponse (dacă are PROPERTY_WRITE_NO_RESPONSE). Pentru date în flux (actualizări OTA, loguri) utilizați WRITE_TYPE_NO_RESPONSE — lățime de bandă maximă. Pentru comenzi cu garanție de livrare (activare, configurare) — WRITE_TYPE_DEFAULT cu confirmare prin onCharacteristicWrite.

kotlin
// Scrierea caracteristicii BLE pe Android API 33+
class GattWriter {

    // Scriere cu răspuns (withResponse)
    fun writeWithResponse(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, data: ByteArray) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
            // API 33+: writeCharacteristic
            val request = BluetoothGattCharacteristicWriteRequest(
                characteristic,
                data,
                BluetoothGattCharacteristicWriteRequest.WRITE_TYPE_DEFAULT,
                @android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
            )
            gatt.writeCharacteristic(request)
        } else {
            // API < 33:   (deprecated)
            characteristic.setValue(data)
            gatt.writeCharacteristic(characteristic)
        }
    }

    // Scriere fără răspuns (viteză maximă)
    fun writeWithoutResponse(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, data: ByteArray) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
            val request = BluetoothGattCharacteristicWriteRequest(
                characteristic,
                data,
                BluetoothGattCharacteristicWriteRequest.WRITE_TYPE_NO_RESPONSE,
                @android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
            )
            gatt.writeCharacteristic(request)
        } else {
            characteristic.setValue(data)
            characteristic.writeType = BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE
            gatt.writeCharacteristic(characteristic)
        }
    }

    // Callback onCharacteristicWrite (API 33+)
    private val writeCallback = object : BluetoothGattCallback() {
        override fun onCharacteristicWrite(
            gatt: BluetoothGatt,
            characteristic: BluetoothGattCharacteristic,
            value: ByteArray,
            status: Int,
            callbackType: Int
        ) {
            if (status == BluetoothGatt.GATT_SUCCESS) {
                print("Write success: ${value.size} bytes")
            }
        }
    }
}

Clasa GattWriter suportă ambele WriteType pentru diferite niveluri API. writeWithResponse utilizează WRITE_TYPE_DEFAULT — dispozitivul BLE confirmă scrierea prin onCharacteristicWrite. writeWithoutResponse utilizează WRITE_TYPE_NO_RESPONSE — datele sunt trimise fără confirmare, lățime de bandă maximă. Pe API 33+ se utilizează noul writeCharacteristic(request) cu BluetoothGattCharacteristicWriteRequest. Pe API < 33 — vechiul setValue() + writeCharacteristic().

Abonarea la notificări: setCharacteristicNotification și CCCD

setCharacteristicNotification() — metoda BluetoothGatt pentru abonarea la notificări privind modificarea caracteristicii pe periferie. După activarea abonamentului, dispozitivul BLE trimite valori noi prin onCharacteristicChanged(). Cu toate acestea, setCharacteristicNotification() activează doar notificarea locală Android — pentru a activa notificările pe dispozitivul BLE în sine, este necesar să scrieți și valoarea 0x0100 în descriptorul CCCD (0x2902).

CCCD (Client Characteristic Configuration Descriptor) — descriptorul care gestionează trimiterea notificărilor de pe periferia BLE. Valoarea 0x0000 — notificări dezactivate, 0x0100 — notificări activate (notifications), 0x0200 — indicații activate (indications). Scrierea în CCCD se execută prin writeDescriptor() pe BluetoothGatt după apelarea setCharacteristicNotification(). Android nu scrie CCCD automat — această responsabilitate revine dezvoltatorului.

kotlin
// Abonarea corectă la notificări BLE
class GattNotificationManager {

    // 1. Activează notificările
    fun enableNotification(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic) {
        // Pasul 1: abonarea locală Android
        val success = gatt.setCharacteristicNotification(characteristic, true)
        if (!success) {
            print("Abonare eșuată")
            return
        }

        // Pasul 2: scrie CCCD (0x2902) pe dispozitivul BLE
        val cccdDescriptor = characteristic.getDescriptor(
            UUID.fromString("00002902-0000-1000-8000-00805F9B34FB")
        ) ?: return

        // 0x0100 = notificare, 0x0200 = indicație
        val cccdValue = if (characteristic.properties
            and BluetoothGattCharacteristic.PROPERTY_NOTIFY != 0) {
            BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE  // [0x01, 0x00]
        } else {
            BluetoothGattDescriptor.ENABLE_INDICATION_VALUE     // [0x02, 0x00]
        }

        cccdDescriptor.setValue(cccdValue)
        gatt.writeDescriptor(cccdDescriptor)
    }

    // 2. Descoperirea caracteristicii
    fun disableNotification(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic) {
        gatt.setCharacteristicNotification(characteristic, false)
        val cccdDescriptor = characteristic.getDescriptor(
            UUID.fromString("00002902-0000-1000-8000-00805F9B34FB")
        ) ?: return
        cccdDescriptor.setValue(BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE)
        gatt.writeDescriptor(cccdDescriptor)
    }

    // 3. Callback notificare
    private val notificationCallback = object : BluetoothGattCallback() {
        override fun onCharacteristicChanged(
            gatt: BluetoothGatt,
            characteristic: BluetoothGattCharacteristic,
            value: ByteArray,
            callbackType: Int
        ) {
            // Valoare nouă de la periferia BLE
            print("Notification: ${value.size} bytes")
        }
    }
}

Clasa GattNotificationManager implementează protocolul corect în doi pași pentru abonarea la notificări BLE. enableNotification apelează mai întâi setCharacteristicNotification(true) pe Android, apoi scrie 0x0100 în descriptorul CCCD prin writeDescriptor. disableNotification execută operațiile inverse. Fără scrierea CCCD, dispozitivul BLE nu trimite notificări — aceasta este cea mai frecventă greșeală a dezvoltatorilor BLE pe Android.

Exemplu de client GATT în Kotlin cu BluetoothGattCallback

Exemplu complet de client GATT în Kotlin, care combină crearea BluetoothGatt, descoperirea, citirea și abonarea la notificări într-un singur manager folosind corutine pentru procesarea asincronă.

kotlin
// Client GATT complet cu corutine în Kotlin
class GattClient(context: Context) {

    private val context = context.applicationContext
    private var gatt: BluetoothGatt? = null

    // Conectare cu corutină
    suspend fun connect(device: BluetoothDevice): Boolean =
        suspendCoroutine { continuation ->
            gatt = device.connectGatt(
                context, false,
                object : BluetoothGattCallback() {
                    override fun onConnectionStateChange(
                        gatt: BluetoothGatt, status: Int, newState: Int
                    ) {
                        if (newState == BluetoothProfile.STATE_CONNECTED) {
                            gatt.discoverServices()
                        } else {
                            continuation.resume(false)
                        }
                    }

                    override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) {
                        continuation.resume(status == BluetoothGatt.GATT_SUCCESS)
                    }
                },
                BluetoothDevice.TRANSPORT_LE
            )
        }

    // Citește caracteristica prin corutină
    suspend fun readCharacteristicValue(char: BluetoothGattCharacteristic): ByteArray? =
        suspendCoroutine { continuation ->
            gatt?.let { gatt ->
                // Salvează eticheta caracteristicii pentru identificarea callback-ului
                gatt.setCharacteristic(char, null)  // pentru API < 33
                gatt.readCharacteristic(char)
            }
        }

    // Închide conexiunea
    fun release() {
        gatt?.disconnect()
        gatt?.close()
        gatt = null
    }
}

Clientul GATT GattClient utilizează corutine (suspendCoroutine) pentru a transforma API-ul bazat pe callback al BluetoothGatt în apeluri secvențiale. connect() așteaptă onServicesDiscovered, după care ierarhia GATT este disponibilă. readCharacteristicValue() așteaptă onCharacteristicRead. Această abordare elimină imbricarea callback-urilor și face codul BLE liniar. release() garantează eliberarea resurselor — apel obligatoriu în onDestroy al Activity sau ViewModel.onCleared.

Întrebări frecvente

Ce este BluetoothGatt în Android?

BluetoothGatt — clasa pentru clientul GATT Android care gestionează conexiunea BLE cu dispozitivul periferic. Se creează prin BluetoothDevice.connectGatt(), oferă metodele discoverServices(), readCharacteristic(), writeCharacteristic(), setCharacteristicNotification(). Rezultatele tuturor operațiunilor vin asincron prin BluetoothGattCallback. Fără BluetoothGatt, comunicarea BLE bidirecțională pe Android este imposibilă.

De ce onServicesDiscovered returnează statusul 133?

Statusul 133 (GATT_ERROR) înseamnă o eroare internă a stivei BLE Android. Cauze: dispozitivul s-a deconectat în timpul descoperirii, MTU este mai mic decât minimul (23 de octeți) sau stiva BLE este suprasolicitată. Soluție: repetați discoverServices() cu o întârziere de 500 ms, verificați RSSI-ul dispozitivului și asigurați-vă că periferia suportă descoperirea GATT în starea curentă.

Cum să scriu corect o caracteristică cu confirmare?

Pentru scrierea cu confirmare, apelați writeCharacteristic() cu WRITE_TYPE_DEFAULT (API 33+: BluetoothGattCharacteristicWriteRequest). La succes, dispozitivul BLE trimite o confirmare, iar Android apelează onCharacteristicWrite cu GATT_SUCCESS. Dacă dispozitivul nu răspunde în 30 de secunde (timeout-ul stivei), callback-ul returnează un status de eroare. Pentru watchdog, utilizați Handler cu postDelayed.

Cum să lucrez cu BLE pe Android 33+?

Pe Android 13+ (API 33), metodele BluetoothGatt s-au schimbat: writeCharacteristic() acceptă acum BluetoothGattCharacteristicWriteRequest, readCharacteristic() — BluetoothGattCharacteristicReadRequest. Vechile setValue()/writeCharacteristic() sunt deprecated. De asemenea, BluetoothGattCallback s-a schimbat: onCharacteristicRead(), onCharacteristicWrite(), onCharacteristicChanged() primesc ByteArray value și callbackType. Utilizați Build.VERSION.SDK_INT pentru ramificare.

Câte conexiuni BLE suportă Android?

Android suportă 4–8 conexiuni BLE-GATT simultane (depinde de producător și versiunea Android). Pixel/Google: până la 7, Samsung: până la 5, Xiaomi: până la 4. La depășirea limitei, connectGatt returnează null sau onConnectionStateChange cu eroare. Pentru lucrul cu un număr mare de dispozitive, utilizați conexiunea ciclică sau Bluetooth Mesh.

Rezumat

  • BluetoothGatt — clientul GATT Android pentru conexiunea BLE, creat prin BluetoothDevice.connectGatt() cu BluetoothGattCallback
  • discoverServices() — pas obligatoriu după conectare pentru obținerea serviciilor, caracteristicilor și descriptorilor dispozitivului BLE
  • Citire — readCharacteristic() cu rezultat asincron în onCharacteristicRead(); pentru date mari este necesară negocierea MTU
  • Scriere — writeCharacteristic() cu WriteType: DEFAULT (withResponse) sau NO_RESPONSE (fără confirmare)
  • Notificări — activare în doi pași: setCharacteristicNotification() + scrierea CCCD (0x2902) cu valoarea 0x0100
  • API 33+ — noi metode writeCharacteristic(request) și readCharacteristic(request) cu obiecte de cerere
  • Eliberarea resurselor — apel obligatoriu al disconnect() și close() pentru prevenirea scurgerilor de conexiuni BLE

Vom dezvolta o aplicație mobilă la cheie

IT Sectr creează aplicații iOS și Android pentru startup-uri și afaceri din 2017. Vă vom consilia și vă vom propune cea mai bună soluție.

Discutați proiectul

Citiți și