好吧,跟我说吧,因为这将是一个长时间的阅读。我有一个应用程序,我正试图将BLE集成到其中,而不是传统的蓝牙。这个应用程序是由自由职业者制作的,但我的任务是修改它。它使用MVVM架构。
最初,这个应用程序使用了经典的蓝牙技术。有一个蓝牙控制器类,它在DataStore类中被实例化。DataStore的函数使用BluetoothController的实例调用该类中的函数,这些函数请求与所需参数相对应的代码。也就是说,有一个枚举类,它具有要读取或写入的每个参数的名称,并用代码表示每个参数。
例如:
`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具有以下功能:
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工厂和一个存储库。让我来描述一下事物的流动:
所有这些都是通过传统的蓝牙技术完成的。现在我想把它改成BLE。我遵循一个指南,制作了一个小应用程序(我们称之为prototype),它扫描BLE设备,连接到所选的设备,并显示其特性和属性。我在手边的应用程序(我想改变的主要应用程序)中做了一个活动,其中包含了扫描和连接的部分。据我所见,通常有一个连接管理器类来管理与连接和数据传输相关的部分。除了这些功能和价值观之外,我不太确定这一点:
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集成的内容:
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而不是传统的蓝牙?
我完全迷路了,不知道该怎么办。有洞察力吗?或甚至列出我应该做的事情,或采取的道路作出必要的改变?
发布于 2021-12-05 23:38:52
实现您想要的东西有多种方法,但是为了简单起见,我建议您首先实现一个GATT表,其中有20个服务,每个参数都有一个。换句话说,此服务/特性将负责托管将从远程设备读取/写入的数据。因此,这些特征中的每一个应该主要有两个属性:读和写。承载数据的设备应该是可被远程设备发现的BLE外围设备。
我认为这会起作用的原因是,从根本上说,您的经典蓝牙应用程序是一个SPP应用程序,您可以从远程设备中读取/写入该应用程序,而在BLE中实现这一功能的最简单的方法是具有特性,可以从其中读取或写入。
这就是它应该如何在您的设备上工作:- 1-在应用程序的启动时,GATT表将填充您的服务和特性(即参数)。2-在这些特征之一上从远程设备接收到读取请求事件后,您将使用参数值进行答复。3-一旦从远程设备接收到这些特征之一的写请求事件,就会更新相关的特征值。
有关在Android上创建GATT表的信息,请查看以下链接:
https://stackoverflow.com/questions/70236459
复制