首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >问答首页 >我正试图改变我的应用程序,使其与使用BLE而不是传统蓝牙的设备交换数据。

我正试图改变我的应用程序,使其与使用BLE而不是传统蓝牙的设备交换数据。
EN

Stack Overflow用户
提问于 2021-12-05 16:49:28
回答 1查看 249关注 0票数 2

好吧,跟我说吧,因为这将是一个长时间的阅读。我有一个应用程序,我正试图将BLE集成到其中,而不是传统的蓝牙。这个应用程序是由自由职业者制作的,但我的任务是修改它。它使用MVVM架构。

最初,这个应用程序使用了经典的蓝牙技术。有一个蓝牙控制器类,它在DataStore类中被实例化。DataStore的函数使用BluetoothController的实例调用该类中的函数,这些函数请求与所需参数相对应的代码。也就是说,有一个枚举类,它具有要读取或写入的每个参数的名称,并用代码表示每个参数。

例如:

代码语言:javascript
代码运行次数:0
运行
复制
    `enum class ReadRequestCodes(val value:String) {
        KEY_ADDRESS  ("08 00 00 00 20 30 05 11 00 00 00 00 00"),
        TOOL_ADDRESS ("08 00 00 00 20 30 05 27 00 00 00 00 00"),
        RPM_THRESHOLD("08 00 00 00 20 30 05 13 00 00 00 00 00"),
        BACKLASH     ("08 00 00 00 20 30 05 22 00 00 00 00 00"),
    
        POWER_SRC_TYPE     ("08 00 00 00 20 30 05 26 00 00 00 00 00"),
        BATTERY1_PERCENTAGE("08 00 00 00 20 30 11 00 00 00 00 00 00"),
        BATTERY2_PERCENTAGE("08 00 00 00 20 30 12 00 00 00 00 00 00"),
    
        HOME_POSITION     ("08 00 00 00 20 30 05 15 00 00 00 00 00"),
        BYPASS_POSITION   ("08 00 00 00 20 30 05 17 00 00 00 00 00"),
        HC_POSITION       ("08 00 00 00 20 30 05 19 00 00 00 00 00"),
        ISOLATION_POSITION("08 00 00 00 20 30 05 1B 00 00 00 00 00"),
    
        PRESSURE_SENSOR_GAIN("08 00 00 00 20 30 05 2B 00 00 00 00 00"),
        PRESSURE_SENSOR_OFFSET("08 00 00 00 20 30 05 2C 00 00 00 00 00"),
        PRESSURE_SENSOR_RANGE("08 00 00 00 20 30 05 2A 00 00 00 00 00"),
        PRESSURE_SENSOR_EXCITATION("08 00 00 00 20 30 05 29 00 00 00 00 00"),
        PRESSURE_SENSOR_SERIAL("08 00 00 00 20 30 05 2D 00 00 00 00 00"),
        GOTO_CURRENT_LIMIT("08 00 00 00 20 30 05 50 00 00 00 00 00"),
        GOTO_RPM_LIMIT("08 00 00 00 20 30 05 52 00 00 00 00 00"),
        FW_HW_VERSION("08 00 00 00 20 30 09 00 00 00 00 00 00"),
        READ_EEPROM_MEMORY("08 00 00 00 20 30 0A 00 00 00 00 00 00")

}
`

这是在蓝牙控制器类中。DataStore具有以下功能:

代码语言:javascript
代码运行次数:0
运行
复制
 fun requestKeyAddress(){
        bluetoothController.requestReadValues(ReadRequestCodes.KEY_ADDRESS.value)
    }
    fun requestToolAddress(){
        bluetoothController.requestReadValues(ReadRequestCodes.TOOL_ADDRESS.value)
    }
    fun requestRpmThreshold(){
        bluetoothController.requestReadValues(ReadRequestCodes.RPM_THRESHOLD.value)
    }
    fun requestBacklash(){
        bluetoothController.requestReadValues(ReadRequestCodes.BACKLASH.value)
    }
    fun requestPowerSrc(){
        bluetoothController.requestReadValues(ReadRequestCodes.POWER_SRC_TYPE.value)
    }
    fun requestBattery1Percentage(){
        bluetoothController.requestReadValues(ReadRequestCodes.BATTERY1_PERCENTAGE.value)
    }
    fun requestBattery2Percentage(){
        bluetoothController.requestReadValues(ReadRequestCodes.BATTERY2_PERCENTAGE.value)
    }
    fun requestHomePos(){
        bluetoothController.requestReadValues(ReadRequestCodes.HOME_POSITION.value)
    }
    fun requestBypassPos(){
        bluetoothController.requestReadValues(ReadRequestCodes.BYPASS_POSITION.value)
    }
    fun requestIsolationPos(){
        bluetoothController.requestReadValues(ReadRequestCodes.ISOLATION_POSITION.value)
    }
    fun requestHcPos(){
        bluetoothController.requestReadValues(ReadRequestCodes.HC_POSITION.value)
    }

应用程序中的每个片段或活动都有一个视图模型、viewmodel工厂和一个存储库。让我来描述一下事物的流动:

  1. 我们通过蓝牙连接到一个设备
  2. BluetoothController处理应用程序与其连接的设备之间的数据交换。
  3. 数据存储在DataStore中
  4. UI(片段或活动)写入或请求读取某些数据
  5. 每个UI的ViewModel通过相应的存储库请求数据
  6. 特定片段的存储库从DataStore获取数据。
  7. 依此类推,这取决于我们是要从设备请求数据,还是要向设备写入数据。

所有这些都是通过传统的蓝牙技术完成的。现在我想把它改成BLE。我遵循一个指南,制作了一个小应用程序(我们称之为prototype),它扫描BLE设备,连接到所选的设备,并显示其特性和属性。我在手边的应用程序(我想改变的主要应用程序)中做了一个活动,其中包含了扫描和连接的部分。据我所见,通常有一个连接管理器类来管理与连接和数据传输相关的部分。除了这些功能和价值观之外,我不太确定这一点:

代码语言:javascript
代码运行次数:0
运行
复制
  private val operationQueue = ConcurrentLinkedQueue<BleOperationType>()
    private var pendingOperation : BleOperationType? = null      //Operations types found in the BleOperationType sealed class
    private var listeners: MutableSet<WeakReference<ConnectionEventListener>> = mutableSetOf()

    fun servicesOnDevice(device: BluetoothDevice): List<BluetoothGattService>? = deviceGattMap[device]?.services
    fun listenToBondStateChanges(context: Context) {
        context.applicationContext.registerReceiver(
            broadcastReceiver,
            IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED)
        )
    }

    fun registerListener(listener: ConnectionEventListener){
        if(listeners.map { it.get() }.contains(listener)){ return }
        listeners.add(WeakReference(listener))
        listeners = listeners.filter { it.get() != null }.toMutableSet()
        Log.d("RegisteredListener","Added listener $listener, ${listeners.size} listeners total")
    }
    fun unregisterListener(listener: ConnectionEventListener){
        // Removing elements while in a loop results in a java.util.ConcurrentModificationException
        var toRemove : WeakReference<ConnectionEventListener>? = null
        listeners.forEach{
            if (it.get() == listener){
                toRemove = it
            }
        }

        toRemove?.let {
           listeners.remove(it)
            Log.d("UnregisteredListener","Removed listener ${it.get()}, ${listeners.size} listeners total")
        }
    }


    fun connect(device: BluetoothDevice, context: Context){
        if(device.isConnected()){
            Log.e("CheckConnection","${device.name} - ${device.address} is already connected")
        }
        else{
            enqueueOperation(Connect(device, context.applicationContext))
        }
    }
    fun terminateConnection(device: BluetoothDevice){
        if(device.isConnected())
            enqueueOperation(Disconnect(device))
        else{
            Log.e("CheckConnection","Not connected to ${device.name} - ${device.address}, cannot teardown connection")
            }
    }
    fun characteristicWrite(device: BluetoothDevice, characteristic: BluetoothGattCharacteristic, payload: ByteArray){
        val writeType = when {

            //This is is to make sure that a characteristic can be written to,
            // and whether it has a response or not
            characteristic.isWritable() -> BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT
            characteristic.isWritableWithoutResponse() -> {BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE}
            else -> error("Characteristic ${characteristic.uuid} cannot be written to")
        }
        if(device.isConnected()){
            enqueueOperation(CharacteristicWrite(device, characteristic.uuid, writeType, payload))
        }else{
            Log.e("Check_Writable","Not connected to ${device.address}, cannot perform characteristic write")
            return
        }

//        bluetoothGattRef?.let { gatt ->
//            characteristic.writeType = writeType
//            characteristic.value = payload
//            gatt.writeCharacteristic(characteristic)
//        } ?: error("Not connected to a BLE device!")
    }
    fun characteristicRead(device: BluetoothDevice, characteristic: BluetoothGattCharacteristic){
         if(device.isConnected() && characteristic.isReadable()){
             enqueueOperation(CharacteristicRead(device, characteristic.uuid))
         }else if(!characteristic.isReadable()){
             Log.e("Check_Readable", "Attempting to read ${characteristic.uuid} is not readable")
         }else if(!device.isConnected()){
             Log.e("Check_Connected","Not connected to ${device.address}, cannot perform characteristic read")
         }
    }
    fun writeDescriptor(device: BluetoothDevice, descriptor:BluetoothGattDescriptor, payload: ByteArray){
         if(device.isConnected() && (descriptor.isWritable() || descriptor.isCccd())){
             enqueueOperation(DescriptorWrite(device, descriptor.uuid, payload))
         } else if (!device.isConnected()){
           Log.e("Check_Descrip_Connected","Not connected to ${device.address}, cannot perform descriptor write")
         } else if (!descriptor.isWritable() && !descriptor.isCccd()){
           Log.e("Check_Descrip_Writable","Descriptor ${descriptor.uuid} cannot be written to")
         }
//        bluetoothGattRef?.let { gatt ->
//            descriptor.value = payload
//            gatt.writeDescriptor(descriptor)
//        } ?: error("Not connected to a BLE device!")
    }
    fun readDescriptor(device: BluetoothDevice, descriptor: BluetoothGattDescriptor){
        if (device.isConnected() && descriptor.isReadable()) {
            enqueueOperation(DescriptorRead(device,descriptor.uuid))
        } else if (!descriptor.isReadable()) {
            Log.e("Check_Descrip_Readable","Attempting to read ${descriptor.uuid} that isn't readable!")
        } else if (!device.isConnected()) {
            Log.e("Check_Descrip_Connected","Not connected to ${device.address}, cannot perform descriptor read")
        }
    }

    fun enableNotifications(device: BluetoothDevice, characteristic: BluetoothGattCharacteristic) {
        if (device.isConnected() &&
            (characteristic.isIndicatable() || characteristic.isNotifiable())
        ) {
            enqueueOperation(EnableNotifications(device, characteristic.uuid))
        } else if (!device.isConnected()) {
            Log.e("Error","Not connected to ${device.address}, cannot enable notifications")
        } else if (!characteristic.isIndicatable() && !characteristic.isNotifiable()) {
            Log.e("Error","Characteristic ${characteristic.uuid} doesn't support notifications/indications")
        }
    }

    fun disableNotifications(device: BluetoothDevice, characteristic: BluetoothGattCharacteristic) {
        if (device.isConnected() &&
            (characteristic.isIndicatable() || characteristic.isNotifiable())
        ) {
            enqueueOperation(DisableNotifications(device, characteristic.uuid))
        } else if (!device.isConnected()) {
            Log.e("Error","Not connected to ${device.address}, cannot disable notifications")
        } else if (!characteristic.isIndicatable() && !characteristic.isNotifiable()) {
            Log.e("Error","Characteristic ${characteristic.uuid} doesn't support notifications/indications")
        }
    }

它实际上是一个对象,因为我只实例化ConnectionManager一次。现在,所有这些都在我的原型应用程序中,只是为了构建BLE功能的框架,它将集成到我的主应用程序中。

到目前为止,我在我的主要应用程序中添加了关于BLE集成的内容:

代码语言:javascript
代码运行次数:0
运行
复制
  class ScanForDevices : AppCompatActivity() {

    private val bluetoothAdapter: BluetoothAdapter by lazy {
        val bluetoothManager = getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
        bluetoothManager.adapter
    }

    private val bleScanner by lazy {
        bluetoothAdapter.bluetoothLeScanner
    }

    private val scanSettings = ScanSettings.Builder()
        .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
        .build()

    //Check whether the app is scanning or not
    private var isScanning = false
        set(value) {
            field = value
            runOnUiThread {
                if(value) binding.scanBtn.text = "Stop Scanning" else binding.scanBtn.text = "Start Scanning"
            }
        }

    private val scanResults = mutableListOf<ScanResult>()
    private val scanResultAdapter: ScanResultAdapter by lazy {
        ScanResultAdapter(scanResults) { result ->
            // User tapped on a scan result
            if(isScanning){
                stopBleScan()
            }
            with(result.device){
                Log.w("ScanResultAdapter", "Connecting to $address")
                Toast.makeText(this@ScanForDevices,"Connecting to $address",Toast.LENGTH_SHORT).show()
                connectGatt(this@ScanForDevices, false, gattCallback)
                //////////////////////////////////////////////////////////////////////
//                ConnectionManager.connect(this, this@ScanForDevices )
            }
        }
    }

    //To check that the permission is granted
    //It is only needed when performing a BLE scan
    private val isLocationPermissionGranted
        get() = hasPermission(android.Manifest.permission.ACCESS_FINE_LOCATION)

    private lateinit var bluetoothGattRef : BluetoothGatt


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityScanForDevicesBinding.inflate(layoutInflater)
        val view = binding.root
        setContentView(view)


        //Check for Bluetooth availability
        if(bluetoothAdapter == null){
            Log.w("BluetoothSupport","Device does not support Bluetooth")
        }else{
            Log.i("BluetoothSupport","Device supports Bluetooth")
        }

        binding.scanBtn.setOnClickListener {
            if(isScanning) stopBleScan() else startBleScan()
        }
        initRecyclerView()

    }

    override fun onResume() {
        super.onResume()
        if(!bluetoothAdapter.isEnabled){
            promptEnableBluetooth()
        }
    }
    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        when (requestCode) {
            ENABLE_BLUETOOTH_REQUEST_CODE -> {
                if (resultCode != Activity.RESULT_OK) {
                    promptEnableBluetooth()
                }
            }
        }
    }
    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<out String>,
        grantResults: IntArray
    ) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        when (requestCode) {
            LOCATION_PERMISSION_REQUEST_CODE -> {
                if (grantResults.firstOrNull() == PackageManager.PERMISSION_DENIED) {
                    requestLocationPermission()
                } else {
                    startBleScan()
                }
            }
        }
    }


    private fun promptEnableBluetooth() {
        if (!bluetoothAdapter.isEnabled) {
            val enableBtIntent = Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE)
            startActivityForResult(enableBtIntent, ENABLE_BLUETOOTH_REQUEST_CODE)
        }
    }
    //Perform BLE scan after receiving permission to do so
    private fun startBleScan() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !isLocationPermissionGranted) {
            requestLocationPermission()
        } else { /* TODO: Actually perform scan */
            scanResults.clear()
            scanResultAdapter.notifyDataSetChanged()
            bleScanner.startScan(null, scanSettings, scanCallback)
            isScanning = true //Check that the app is scanning
        }
    }
    //Stop BLE scan after the corresponding button is clicked
    private fun stopBleScan(){
        bleScanner.stopScan(scanCallback)
        isScanning = false
    }
    //request location permission
    private fun requestLocationPermission() {
        if (isLocationPermissionGranted) {
            return
        }
        runOnUiThread {
            //Using "org.jetbrains.anko:anko:0.10.8"
            alert {
                title = "Location permission required"
                message = "Starting from Android M (6.0), the system requires apps to be granted " +
                        "location access in order to scan for BLE devices."
                isCancelable = false
                positiveButton(android.R.string.ok) {
                    requestPermission(
                        android.Manifest.permission.ACCESS_FINE_LOCATION,
                        LOCATION_PERMISSION_REQUEST_CODE
                    )
                }
            }.show()
        }
    }
    //For scan results
    private fun initRecyclerView(){
        binding.scanResultsRecyclerView.apply {
            adapter = scanResultAdapter
//            val topSpacingDecoration = TopSpacingItemDecoration(30)
//            addItemDecoration(topSpacingDecoration)
            layoutManager = LinearLayoutManager(
                this@ScanForDevices,
                RecyclerView.VERTICAL,
                false
            )
            isNestedScrollingEnabled = false
        }

        val animator = binding.scanResultsRecyclerView.itemAnimator
        if(animator is SimpleItemAnimator) {
            animator.supportsChangeAnimations = false
        }
    }



    /**********************/
    /**CALLBACK FUNCTIONS**/
    /*********************/

    /** Since we didn't explicitly specify CALLBACK_TYPE_FIRST_MATCH as the callback type under our ScanSettings,
     * our onScanResult callback is flooded by ScanResults belonging to the same set of devices,
     * but with updated signal strength (RSSI) readings. In order to keep the UI up to date with the latest RSSI readings,
     * we first check to see if our scanResults List already has a scan result whose MAC address is identical to the new incoming ScanResult.
     * If so, we replace the older entry with the newer one. In the event that this is a new scan result,
     * we’ll add it to our scanResults List. For both cases,
     * we’ll inform our scanResultAdapter of the updated item so that our RecyclerView can be updated accordingly.**/
    private val scanCallback = object : ScanCallback() {
        override fun onScanResult(callbackType: Int, result: ScanResult) {
            val indexQuery = scanResults.indexOfFirst { it.device.address == result.device.address }
            if (indexQuery != -1) { // A scan result already exists with the same address
                scanResults[indexQuery] = result
//                serviceUUIDsList = getServiceUUIDsList(result)!!
//                Log.d("Service",""+ serviceUUIDsList+"\n")
                Log.d("DeviceName",""+result.device.name)
                scanResultAdapter.notifyItemChanged(indexQuery)
            } else {
                with(result.device) {
                    Log.i("ScanCallback", "Found BLE device! Name: ${name ?: "Unnamed"}, address: $address")
                }
                scanResults.add(result)
                scanResultAdapter.notifyItemInserted(scanResults.size - 1)
            }
        }

        override fun onScanFailed(errorCode: Int) {
            Log.e("ScanCallback", "onScanFailed: code $errorCode")
        }
    }

    /**we want to stop the BLE scan if it’s ongoing,
     * and we call connectGatt() on the relevant ScanResult’s BluetoothDevice handle,
     * passing in a BluetoothGattCallback object that is defined as: **/

    private val gattCallback = object : BluetoothGattCallback() {
        override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) {
            val deviceAddress = gatt.device.address

            if (status == BluetoothGatt.GATT_SUCCESS) {
                if (newState == BluetoothProfile.STATE_CONNECTED) {
                    Log.w("BluetoothGattCallback", "Successfully connected to $deviceAddress")
                    Handler(Looper.getMainLooper()).post {
                        Toast.makeText(this@ScanForDevices, "Successfully connected to $deviceAddress", Toast.LENGTH_SHORT).show()
                    }
                    // TODO: Store a reference to BluetoothGatt
                    bluetoothGattRef = gatt
                    Log.d("ConnectedDevice","")

                    runOnUiThread {
                        bluetoothGattRef?.discoverServices()
                    }
                } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
                    Log.w("BluetoothGattCallback", "Successfully disconnected from $deviceAddress")
                    gatt.close()
                }
            } else {
                Log.w("BluetoothGattCallback", "Error $status encountered for $deviceAddress! Disconnecting...")
                gatt.close()
            }
        }

        //On the discovery of the BLE device's services
        //We display a log message of the number of services and the device's address, then print
        //the GATT table in a recycler view beneath the one of the scanned devices -----> services and characteristics available on a BLE device.
        override fun onServicesDiscovered(gatt: BluetoothGatt?, status: Int) {

            with(bluetoothGattRef) {
                if (status == BluetoothGatt.GATT_SUCCESS) {
                    Log.d(
                        "BluetoothGattCallback",
                        "Discovered ${services.size} services for ${device.address}"
                    )
                    printGattTable()
                }
            }

        }
    }


}

假设我已经连接到所需的设备,如何交换我提到的片段中参数的数据--无论是从设备读取还是写入设备,还是使用BLE而不是传统的蓝牙?

我完全迷路了,不知道该怎么办。有洞察力吗?或甚至列出我应该做的事情,或采取的道路作出必要的改变?

EN

回答 1

Stack Overflow用户

发布于 2021-12-06 07:38:52

实现您想要的东西有多种方法,但是为了简单起见,我建议您首先实现一个GATT表,其中有20个服务,每个参数都有一个。换句话说,此服务/特性将负责托管将从远程设备读取/写入的数据。因此,这些特征中的每一个应该主要有两个属性:读和写。承载数据的设备应该是可被远程设备发现的BLE外围设备。

我认为这会起作用的原因是,从根本上说,您的经典蓝牙应用程序是一个SPP应用程序,您可以从远程设备中读取/写入该应用程序,而在BLE中实现这一功能的最简单的方法是具有特性,可以从其中读取或写入。

这就是它应该如何在您的设备上工作:- 1-在应用程序的启动时,GATT表将填充您的服务和特性(即参数)。2-在这些特征之一上从远程设备接收到读取请求事件后,您将使用参数值进行答复。3-一旦从远程设备接收到这些特征之一的写请求事件,就会更新相关的特征值。

有关在Android上创建GATT表的信息,请查看以下链接:

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/70236459

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档