NordicSemiconductor / Android-BLE-Library

A library that makes working with Bluetooth LE on Android a pleasure. Seriously.
BSD 3-Clause "New" or "Revised" License
1.98k stars 413 forks source link

Notification does not clear after setup a device #557

Open KevinMartinezC opened 4 months ago

KevinMartinezC commented 4 months ago

class MyBleManager(
   @ApplicationContext context: Context
) : BleManager(context), AltaBLE {
    override val firmwareVersion: MutableStateFlow<String?> = MutableStateFlow(EMPTY_STRING)
    override val serialNumber: MutableStateFlow<String?> = MutableStateFlow(EMPTY_STRING)
    override val pubKeyValue: MutableStateFlow<String?> = MutableStateFlow(EMPTY_STRING)
    override val isConnected: MutableStateFlow<Boolean> = MutableStateFlow(false)
    private val scope = CoroutineScope(Dispatchers.IO)
    private var serialNumberCharacteristic: BluetoothGattCharacteristic? = null
    private var firmwareRevisionCharacteristic: BluetoothGattCharacteristic? = null
    private var readChar: BluetoothGattCharacteristic? = null
    private var txCtlChar: BluetoothGattCharacteristic? = null
    private var writeChar: BluetoothGattCharacteristic? = null
    private var rxCredits: Int = 0x1000
    private var rxData: ByteArray = ByteArray(0)
    private var pubKey: ByteArray = ByteArray(0)
    private var pupkey: String? = null
    private var txData = ByteArray(0)

    override fun resetValues (){
        disableNotifications(readChar).enqueue()
        rxCredits = 0x1000
        pupkey = null
        txData = ByteArray(0)
        pubKey = ByteArray(0)
        isConnected.value = false
        firmwareVersion.value = null
        serialNumber.value = null
        pubKeyValue.value = null
        onServicesInvalidated()
        release()
    }

    override val state = stateAsFlow()
        .map {
            when (it) {
                is ConnectionState.Connecting,
                is ConnectionState.Initializing -> AltaBLE.State.LOADING
                is ConnectionState.Ready -> AltaBLE.State.READY
                is ConnectionState.Disconnecting,
                is ConnectionState.Disconnected -> AltaBLE.State.NOT_AVAILABLE
            }
        }
        .stateIn(scope, SharingStarted.Lazily, AltaBLE.State.NOT_AVAILABLE)

    override suspend fun connectDevice(device: BluetoothDevice) {
        Log.wtf("kevin","get connect device ${device.address}")
        connect(device)
            .retry(3, 600)
            .useAutoConnect(false)
            .timeout(6000)
            .suspend()
    }

    override fun release() {
        // Cancel all coroutines.
        scope.cancel()
        disconnect().enqueue()
    }

    override fun log(priority: Int, message: String) {
        Timber.log(priority, message)
    }

    override fun getMinLogPriority(): Int {
        // By default, the library logs only INFO or
        // higher priority messages. You may change it here.
        return Log.VERBOSE
    }

    override fun isRequiredServiceSupported(gatt: BluetoothGatt): Boolean {
        val switchService = gatt.getService(SWITCH_SERVICE)
        firmwareRevisionCharacteristic = switchService?.getCharacteristic(FIRMWARE_CHARACTERISTIC)
        serialNumberCharacteristic = switchService?.getCharacteristic(SERIAL_CHARACTERISTIC)

        val sppService = gatt.getService(SPP_UUID)
        readChar = sppService?.getCharacteristic(READ_UUID)
        txCtlChar = sppService?.getCharacteristic(TX_CTL_UUID)
        writeChar = sppService?.getCharacteristic(WRITE_UUID)

        return switchService != null && firmwareRevisionCharacteristic != null && serialNumberCharacteristic != null &&
                sppService != null && readChar != null && txCtlChar != null && writeChar != null
    }

    @OptIn(ExperimentalUnsignedTypes::class)
    private fun sendCredits() {
        rxCredits = 0x1000
        val data =
            ubyteArrayOf((rxCredits and 0xff).toUByte(), (rxCredits ushr 8).toUByte()).toByteArray()

        writeCharacteristic(txCtlChar, data, BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT)
            .with { _, _ -> rxCredits = 0 }
            .fail { _, status ->
                Timber.tag(ALTA_BLE_TAG).e("Failed to send credits, status: %s", status)
            }.enqueue()
    }

    override fun initialize() {
        requestMtu(REQUEST_MTU_DEFAULT).enqueue()

        readCharacteristic(serialNumberCharacteristic).with { _, data ->
            serialNumber.value = data.getStringValue(0)
            Timber.tag(ALTA_BLE_TAG).wtf("get serial number %s", serialNumber.value)
        }.enqueue()

        readCharacteristic(firmwareRevisionCharacteristic).with { device, data ->
            val firmwareRevision = data.getStringValue(0)
            firmwareVersion.value = firmwareRevision
            Timber.tag(ALTA_BLE_TAG).wtf("get firmware %s", firmwareRevision)
        }.enqueue()

        setNotificationCallback(readChar).with { device, data ->
            val value = data.value
            if (value != null) {
                collectDataKey(value)
            }
        }

        enableNotifications(readChar)
            .done {
            }.enqueue()

        sendCredits()
    }

    override fun onServicesInvalidated() {
        serialNumberCharacteristic = null
        firmwareRevisionCharacteristic = null
        txCtlChar = null
        readChar = null
        writeChar = null
    }

    private fun collectDataKey(value: ByteArray?) {
        value?.size?.let {
            rxCredits += it
        }
        if (value != null) {
            rxData += value
            Timber.tag("DEVICE-CHANGED-KEY").d("Value ${value.size}")
        }
        Timber.tag("DEVICE-CHANGED-KEY").d("RXData ${rxData.size}")
        if (rxData.size < THREE_INT) return

        val total: Int =
            rxData.copyOfRange(ONE_INT, THREE_INT).fold(ZER0_INT) { a, b -> (a shl 8) + b.toInt() }

        if (rxData.size < total + THREE_INT) return
        val pktType = rxData[ZER0_INT]
        val payload = rxData.copyOfRange(THREE_INT, THREE_INT + total)
        rxData = rxData.copyOfRange(THREE_INT + total, rxData.size)

        when (pktType.toUByte()) {
            DEVICE_PUB_KEY -> {
                pubKey = payload
                val pubKeyStr = pubKey.decodeToString()

                pupkey = pubKeyStr
                Timber.tag(ALTA_BLE_TAG).wtf("get pup key %s", pubKeyStr)
                if (pubKey.isNotEmpty()) {
                    pubKeyValue.value = pubKeyStr
                }
                Timber.tag("DEVICE-CHANGED-KEY").d(pubKeyStr)
            }

            DEVICE_STATE -> {
                val state = payload[ZER0_INT].toInt()
                val inet = if (state and (ONE_INT shl ZER0_INT) != ZER0_INT) INET else EMPTY_STRING
                val mesh = if (state and (ONE_INT shl ONE_INT) != ZER0_INT) MESH else EMPTY_STRING
                val cloud = if (state and (ONE_INT shl TWO_INT) != ZER0_INT) CLOUD else EMPTY_STRING
                val setup =
                    if (state and (ONE_INT shl THREE_INT) != ZER0_INT) SET_UP else EMPTY_STRING
                if (state and (ONE_INT shl THREE_INT) != ZER0_INT) {
                    Log.wtf("kevin","completed")
                    isConnected.value = true
                }
                Timber.tag("DEVICE-STATUS").d("state: $inet $mesh $cloud $setup")
            }

            else -> {}
        }
    }

    override suspend fun confirmSetup(setUpKey: String) = runBlocking {
        Timber.tag("DEVICE-CHANGED-STATE").d("internalkkey: $setUpKey")
        val decodedBytes = Base64.decode(setUpKey, Base64.DEFAULT)
        val data = decodedBytes.copyOfRange(ZER0_INT, decodedBytes.size)
        val count = data.size
        val prefixBytes =
            byteArrayOf(altaSetup.toByte(), (count shr 8).toByte(), (count and 0xff).toByte())
        val combined = prefixBytes + data
        txData += combined
        if (txData.isNotEmpty()) {
            flushData()
        }
    }

    private fun flushData() = runBlocking {
        while (txData.isNotEmpty()) {
            flushDataTxData().join()
        }
    }

    private fun flushDataTxData() = scope.launch {
        var mtu = min(mtu, REQUEST_MTU_DEFAULT) - REDUCE_BYTES_HEADER

        if (mtu < CHECK_NEGOTIATED_MTU_SWITCHES_VALUE) {
            mtu = min(MTU_SWITCHES, REQUEST_MTU_DEFAULT)
        }

        var txDataCopy = txData.copyOf()
        if (txDataCopy.size > mtu) {
            txDataCopy = txDataCopy.copyOfRange(ZER0_INT, mtu)
            txData = txData.copyOfRange(mtu, txData.size)
        } else {
            txData = ByteArray(ZER0_INT)
        }

        writeChar?.let { characteristic ->
            val writeType = BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE
            writeCharacteristic(characteristic, txDataCopy, writeType)
                .with { device, data ->
                    Timber.tag(TAG_BLE_MANAGER).d("WRITE CHAR FLUSH $txDataCopy --- ${txDataCopy.size}")
                    Timber.tag(TAG_BLE_MANAGER).d("Data written to ${device.address}")
                }
                .fail { device, status ->
                    Timber.tag(TAG_BLE_MANAGER)
                        .e("Failed to write data to ${device.address}, status: $status")
                }
                .enqueue()
        } ?: run {
            Timber.tag(TAG_BLE_MANAGER).d("Characteristic for writing not found")
        }
    }
`
KevinMartinezC commented 4 months ago

i am using the kotlin library but i have an issue when i have 2 devices to setup. it works with the first one but when try to setup a second one i get the firmware and serial number but it does not complete the process for getting the keys since like it does not clear the notification and detect it as setup

KevinMartinezC commented 4 months ago
Services discovered
Primary service found
Requesting new MTU...
gatt.requestMtu(517)
configureMTU() - device: XX:XX:XX:XX:01:59 mtu: 517
onConfigureMTU() - Device=FC:B9:23:80:01:59 mtu=247 status=0
MTU changed to: 247
Reading characteristic 00002a25-0000-1000-8000-00805f9b34fb
gatt.readCharacteristic(00002a25-0000-1000-8000-00805f9b34fb)
Read Response received from 00002a25-0000-1000-8000-00805f9b34fb, value: (0x) 62-63-62-39-32-33-38-30-30-31-35-38
Reading characteristic 00002a26-0000-1000-8000-00805f9b34fb
gatt.readCharacteristic(00002a26-0000-1000-8000-00805f9b34fb)
onConnectionUpdated() - Device=FC:B9:23:80:01:59 interval=36 latency=0 timeout=500 status=0
Connection parameters updated (interval: 45.0ms, latency: 0, timeout: 5000ms)
unable to parse scan record: [2, 1, 6, 2, -1, 50, 5, 9, 65, 108, 116, 97]
unable to parse scan record: [2, 1, 6, 2, -1, 50, 5, 9, 65, 108, 116, 97]
Read Response received from 00002a26-0000-1000-8000-00805f9b34fb, value: (0x) 32-2E-31-65
gatt.setCharacteristicNotification(0734594a-a8e7-4b1a-a6b1-cd5243059a57, true)
setCharacteristicNotification() - uuid: 0734594a-a8e7-4b1a-a6b1-cd5243059a57 enable: true
Enabling notifications for 0734594a-a8e7-4b1a-a6b1-cd5243059a57
gatt.writeDescriptor(00002902-0000-1000-8000-00805f9b34fb, value=0x01-00)
get firmware 2.1e
unable to parse scan record: [2, 1, 6, 2, -1, 50, 5, 9, 65, 108, 116, 97]
unable to parse scan record: [2, 1, 6, 2, -1, 50, 5, 9, 65, 108, 116, 97]
Data written to descr. 00002902-0000-1000-8000-00805f9b34fb
Writing characteristic ba04c4b2-892b-43be-b69c-5d13f2195392 (WRITE REQUEST)
gatt.writeCharacteristic(ba04c4b2-892b-43be-b69c-5d13f2195392, value=0x0010, WRITE REQUEST)
Data written to ba04c4b2-892b-43be-b69c-5d13f2195392
Notification received from 0734594a-a8e7-4b1a-a6b1-cd5243059a57, value: (0x) 01-01-74-41-41-41-41-42-33-4E-7A-61-43-31-79-63-32-45-41-41-41-41-44-41-51-41-42-41-41-41-42-41-51-43-54-34-4B-42-5A-2B-47-76-72-33-4C-43-71-68-51-54-52-73-37-77-78-69-41-62-4C-48-67-70-6E-6B-48-48-58-6E-34-6D-31-71-53-66-61-4A-70-52-59-4B-34-68-51-6E-73-37-4A-41-37-69-33-78-6A-62-41-6D-46-4A-72-67-6A-77-4A-2F-66-4A-68-42-6D-68-5A-77-6E-41-79-6C-41-6A-61-64-35-62-34-42-69-4B-4E-47-2B-2F-4F-37-68-35-38-48-66-6F-6C-4E-6D-52-42-68-4C-30-45-36-30-31-2F-70-2F-32-56-45-42-4D-77-74-6C-4A-74-6A-65-6E-68-70-4D-46-37-70-79-67-66-55-50-34-39-34-79-41-41-45-61-54-76-57-45-44-69-68-6E-30-47-55-58-72-4A-67-4F-77-4B-53-4C-68-52-50-73-36-6A-6A-43-30-6C-72-61-55-77-6D-68-66-69-30-4D-4F-42-4F-31-2B-4A-2F-38-75-47-48-36-4D-39-43
Value 240
RXData 248
completed
get into is connected
gatt.setCharacteristicNotification(0734594a-a8e7-4b1a-a6b1-cd5243059a57, false)
tagSocket(208) with statsTag=0xffffffff, statsUid=-1
setCharacteristicNotification() - uuid: 0734594a-a8e7-4b1a-a6b1-cd5243059a57 enable: false
Disabling notifications and indications for 0734594a-a8e7-4b1a-a6b1-cd5243059a57
gatt.writeDescriptor(00002902-0000-1000-8000-00805f9b34fb, value=0x00-00)
get into is connected

it does not continue with the process of the notifications

philips77 commented 2 months ago

I think we solved it in another issue and can close this one?