Search code examples
androidreactjsreact-nativebluetooth-lowenergy

React Native BLE Manager (Android) status code 14 on write to characteristic


I'm using the react native ble manager package to build a react native app that communicates with a python client over BLE.

When writing to a characteristic on Android (this bug does not seem to appear on IOS) the write is successful but shortly after it I receive this error:

ERROR Error writing eeee2a38-0000-1000-8000-00805f9b34fb status=14

This is the simplified code that handles connecting, notifications and writing on the Android side:

import { NativeModules, NativeEventEmitter, Platform } from 'react-native'
import BleManager, { Peripheral } from 'react-native-ble-manager'
import { END } from 'redux-saga'
import { bytesToString } from 'convert-string'

const UPDATE_SERVICE_UUID = '0000180d-aaaa-1000-8000-00805f9b34fb'

export const Characteristic =
   {
        WIFI_STATUS_UUID: 'bbbb2a38-0000-1000-8000-00805f9b34fb',
        WIFI_CREDS_UUID: 'aaaa2a38-0000-1000-8000-00805f9b34fb',
        VERSION_UUID: 'cccc2a38-0000-1000-8000-00805f9b34fb',
        UPDATE_STATUS_UUID: 'dddd2a38-0000-1000-8000-00805f9b34fb',
        DO_UPDATE_UUID: 'eeee2a38-0000-1000-8000-00805f9b34fb',
        ERROR_UUID: 'ffff2a38-0000-1000-8000-00805f9b34fb',
      }

class BLEManager {
  bleManagerModule: any
  bleManagerEmitter: any
  scanning: boolean
  dispatch: any
  stopScanListener: any
  peripheralDiscoverListener: any
  characteristicUpdateListener: any
  onDisconnectListener: any
  connectTimeout: any

  constructor() {
    BleManager.start({ showAlert: false })

    this.bleManagerModule = NativeModules.BleManager
    this.bleManagerEmitter = new NativeEventEmitter(this.bleManagerModule)
    this.scanning = false
  }

  startScan = (onPeripheralFound: (peripheral: Peripheral | null) => void) => {
    if (!this.scanning) {
      BleManager.scan([], 3, true)
        .then(() => {
          console.log('Scanning...')
          this.scanning = true
          this.peripheralDiscoverListener = this.bleManagerEmitter.addListener(
            'BleManagerDiscoverPeripheral',
            onPeripheralFound,
          )

          this.stopScanListener = this.bleManagerEmitter.addListener(
            'BleManagerStopScan',
            () => {
              onPeripheralFound(END)
            },
          )
          return
        })
        .catch(err => {
          console.error(err)
        })
    } else {
      console.log('already scanning')
    }
    return () => {
      console.log('stopped scanning')
      this.peripheralDiscoverListener.remove()
      this.stopScanListener.remove()
    }
  }

  getBondedDevices = (onGetBondedPeripherals: any) => {
    BleManager.getBondedPeripherals().then(bondedPeripheralsArray => {
      onGetBondedPeripherals(bondedPeripheralsArray)
      // TODO: is the END message here necessary?
      onGetBondedPeripherals(END)
      return
    })
    return () => {}
  }

  connectToPeripheral = async (peripheralID: string) => {
    try {
      await new Promise(async (resolve, reject) => {
        this.connectTimeout = setTimeout(reject, 3000)

        console.log('connecting to ' + peripheralID)

        try {
          await BleManager.connect(peripheralID)
          await BleManager.retrieveServices(peripheralID)
        } catch (error) {
          reject()
        }

        if (this.connectTimeout) {
          clearTimeout(this.connectTimeout)

          this.connectTimeout = null

          this.onDisconnectListener = this.bleManagerEmitter.addListener(
            'BleManagerDisconnectPeripheral',
            this.onDisconnectPeripheral,
          )

          resolve()
        }
      })
    } catch (err) {
      clearTimeout(this.connectTimeout)

      this.connectTimeout = null

      console.error('Could not connect to device.')

      throw new Error(err)
    }

    return
  }

  watchForCharacteristicsUpdates = async (
    updateCharValue: (arg0: { payload: any }) => void,
    peripheralID: string,
  ) => {
    try {
      await BleManager.startNotification(
        peripheralID,
        UPDATE_SERVICE_UUID,
        Characteristic.ERROR_UUID,
      )
      await BleManager.startNotification(
        peripheralID,
        UPDATE_SERVICE_UUID,
        Characteristic.VERSION_UUID,
      )
      await BleManager.startNotification(
        peripheralID,
        UPDATE_SERVICE_UUID,
        Characteristic.UPDATE_STATUS_UUID,
      )
    } catch (e) {
      updateCharValue(new Error(e))
      console.error(e)
    }

    console.log('watch for notifications')

    this.characteristicUpdateListener = this.bleManagerEmitter.addListener(
      'BleManagerDidUpdateValueForCharacteristic',
      ({ value, characteristic }) => {
        // Convert bytes array to string
        const data = bytesToString(value)

        console.log(
          `Received ${data} (${value}) for characteristic ${characteristic}`,
        )

        updateCharValue({
          payload: {
            characteristic: characteristic,
            data: data,
          },
        })
      },
    )
  }

  disconnectFromPeripheral = async (peripheralID: string) => {
    await BleManager.disconnect(peripheralID)
    this.characteristicUpdateListener.remove()
  }

  onDisconnectPeripheral = (peripheralID: string) => {
    console.log(peripheralID + ' disconnected')
    this.onDisconnectListener.remove()
  }

  checkIfConnected = async (peripheralID: string) => {
    return await BleManager.isPeripheralConnected(peripheralID, [])
  }

  triggerUpdateCheck = async (peripheralID: string) => {
    return await BleManager.write(
      peripheralID,
      UPDATE_SERVICE_UUID,
      Characteristic.WIFI_STATUS_UUID,
      [1],
    )
  }

  runUpdate = async (peripheralID: string) => {
    return await BleManager.write(
      peripheralID,
      UPDATE_SERVICE_UUID,
      Characteristic.DO_UPDATE_UUID,
      [1],
    )
  }
}

const bleManager = new BLEManager()

export default bleManager

I've researched this a bit and it seems that some people have the problem but I could not find an explanation or solution to it.

I'm even unsure where to start debugging. Any suggestions are welcome.

Details:

  • Device: [Pixel 6]
  • OS: [Android 12]
  • react-native-ble-manager version: ^8.4.1
  • react-native version: 0.67.4

Note: I've also asked this question on Github: https://github.com/innoveit/react-native-ble-manager/issues/887


Solution

  • The problem (as mentioned by Martijn) was the bug in Bluez which is fixed in 5.65. Simply upgrading and clearing the Bluetooth cache fixed it.