以下是处理扫描并将结果发送到注入它的 viewModel 的类:
@SuppressLint("MissingPermission")
class BLEReceiverManagerImpl
@Inject
constructor(
private val bluetoothAdapter: BluetoothAdapter,
private val context: Context,
) : BLEReceiverManager {
private val TAG = BLEReceiverManagerImpl::class.java.simpleName
private val _data = MutableSharedFlow<Resource<Beacon>>()
override val data: SharedFlow<Resource<Beacon>> get() = _data
/**
* A characteristic contains a single value and optional descriptors that describe the characteristic’s value.
*/
private val SERVICE_UUID = ""
/**
* A characteristic contains a single value and optional descriptors that describe the characteristic’s value.
*/
private val CHARACTERISTICS_UUID = ""
/**
* This class provides methods to perform scan-related operations for Bluetooth LE devices.
*/
private val bleScanner by lazy {
bluetoothAdapter.bluetoothLeScanner
}
private val scanSettings = ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_LOW_POWER)
.build()
private val scanFilters = listOf(
ScanFilter.Builder()
.build()
)
/**
* The GATT profile is a general specification for sending and receiving short pieces of data
* known as “attributes” over a BLE link. Using this profile we can transmit data between BLE devices.
*/
private var gatt: BluetoothGatt? = null
private var isScanning = false
private val coroutineScope = CoroutineScope(Dispatchers.Default)
private val scanResults =
mutableListOf<ScanResult>()
private fun calculateSignalQuality(rssi: Int): String {
val result = when {
rssi >= -30 -> "Eccellente "
rssi in -31 downTo -60 -> "Forte"
rssi in -61 downTo -70 -> "Moderato"
else -> "Debole"
}
return "$result (RSSI: $rssi dBm)"
}
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
} else {
with(result.device) {
Log.i(
TAG,
"Found BLE device! Name: ${name ?: "Unnamed"}, address (MAC): $address, Signal strength Quality: ${
calculateSignalQuality(
result.rssi
)
}"
)
val scanRecord = result.scanRecord
scanRecord?.serviceUuids?.let {
Log.i(
TAG,
"UUIDS: $it, Manifacturer Data: ${scanRecord.manufacturerSpecificData}"
)
}
}
scanResults.add(result)
}
}
override fun onScanFailed(errorCode: Int) {
super.onScanFailed(errorCode)
Log.e(TAG, "onScanFailed: Error code ($errorCode)")
}
}
private var currentConnectionAttempt = 1
private val MAXIMUM_CONNECTION_ATTEMPTS = 5
private val gattCallback = object : BluetoothGattCallback() {
override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) {
val deviceAddress = gatt.device.address
if (status == BluetoothGatt.GATT_SUCCESS) {
Log.d("BluetoothGattCallback", "GATT SUCCESS")
if (newState == BluetoothProfile.STATE_CONNECTED) {
Log.w("BluetoothGattCallback", "Successfully CONNECTED to $deviceAddress")
coroutineScope.launch {
_data.emit(Resource.Loading(message = "Discovering services..."))
}
Log.d("BluetoothGattCallback", "Discovering services...")
gatt.discoverServices()
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
Log.w("BluetoothGattCallback", "Successfully disconnected from $deviceAddress")
coroutineScope.launch {
val beaconExample = Beacon(
macAddress = "lectus",
uuid = null,
major = null,
minor = null,
idReservation = "null",
connectionState = ConnectionState.Disconnected
)
_data.emit(
Resource.Success(
data = beaconExample
)
)
}
gatt.close()
}
} else {
Log.w(
"BluetoothGattCallback",
"Error $status encountered for $deviceAddress! Disconnecting..."
)
gatt.close()
currentConnectionAttempt += 1
coroutineScope.launch {
_data.emit(Resource.Loading(message = "Attempting to connect $currentConnectionAttempt/$MAXIMUM_CONNECTION_ATTEMPTS"))
}
if (currentConnectionAttempt <= MAXIMUM_CONNECTION_ATTEMPTS) {
Log.d(
"BluetoothGattCallback",
"Attempting to connect $currentConnectionAttempt/$MAXIMUM_CONNECTION_ATTEMPTS"
)
startReceiving()
} else {
Log.e("BluetoothGattCallback", "Connection Exception")
coroutineScope.launch {
_data.emit(Resource.Error(BluetoothExceptions.ConnectionException))
}
}
}
}
override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) {
with(gatt) {
//Mostra quali servizi il BLE device può fornirci
Log.w(
"BluetoothGattCallback",
"Discovered ${services.size} services for ${device.address}"
)
printGattTable()
coroutineScope.launch {
_data.emit(Resource.Loading(message = "Adjusting MTU space..."))
}
gatt.requestMtu(GATT_MAX_MTU_SIZE)
}
}
override fun onMtuChanged(gatt: BluetoothGatt, mtu: Int, status: Int) {
Log.w(
"BluetoothGattCallback",
"ATT MTU changed to $mtu, success: ${status == BluetoothGatt.GATT_SUCCESS}"
)
val characteristic =
findCharacteristics(SERVICE_UUID, CHARACTERISTICS_UUID)
if (characteristic == null) {
Log.e(TAG, "onMtuChanged: Characteristics not found")
coroutineScope.launch {
_data.emit(Resource.Error(BluetoothExceptions.CharacteristicNotFoundException))
}
return
}
}
override fun onCharacteristicRead(
gatt: BluetoothGatt,
characteristic: BluetoothGattCharacteristic,
value: ByteArray,
status: Int,
) {
with(characteristic) {
when (status) {
BluetoothGatt.GATT_SUCCESS -> {
Log.i(
"BluetoothGattCallback",
"Read characteristic $uuid:\n${value.toHexString()}"
)
}
BluetoothGatt.GATT_READ_NOT_PERMITTED -> {
Log.e("BluetoothGattCallback", "Read not permitted for $uuid!")
}
else -> {
Log.e(
"BluetoothGattCallback",
"Characteristic read failed for $uuid, error: $status"
)
}
}
}
}
override fun onCharacteristicChanged(
gatt: BluetoothGatt,
characteristic: BluetoothGattCharacteristic,
value: ByteArray,
) {
with(characteristic) {
Log.i(
"BluetoothGattCallback",
"Characteristic $uuid changed | value: ${value.toHexString()}"
)
when (uuid) {
UUID.fromString(CHARACTERISTICS_UUID) -> {
//TODO emit result here
}
else -> Unit// Non facciamo nulla
}
}
}
}
private fun ByteArray.toHexString(): String =
joinToString(separator = " ", prefix = "0x") { String.format("%02X", it) }
private fun findCharacteristics(
serviceUUID: String,
characteristicUUID: String,
): BluetoothGattCharacteristic? {
return gatt?.services?.find { service ->
service.uuid.toString() == serviceUUID
}?.characteristics?.find { characteristics ->
characteristics.uuid.toString() == characteristicUUID
}
}
override fun startReceiving() {
coroutineScope.launch {
_data.emit(Resource.Loading(message = "Scanning BLE devices..."))
}
isScanning = true
Log.d(TAG, "startReceiving: Start scan")
bleScanner.startScan(scanFilters, scanSettings, scanCallback)
}
override fun stopReceiving() {
coroutineScope.launch {
_data.emit(Resource.Loading(message = "Stopping scanning BLE devices..."))
}
isScanning = false
Log.d(TAG, "stopReceiving: ")
bleScanner.stopScan(scanCallback)
}
override fun reconnect() {
Log.d(TAG, "reconnection...")
gatt?.connect()
}
override fun disconnect() {
Log.d(TAG, "disconnection..")
gatt?.disconnect()
}
override fun closeConnection() {
Log.d(TAG, "closeConnection..")
bleScanner.stopScan(scanCallback)
val characteristic =
findCharacteristics(SERVICE_UUID, CHARACTERISTICS_UUID)
if (characteristic != null) {
//Ci sono delle caratteristiche che devo disattivare
// Altrimenti drenano la batteria
disconnectCharacteristic(characteristic)
}
gatt?.close()
}
}
问题是这个类做了它应该做的事情——也就是说,它找到附近的 BLE 设备,甚至可能使用名称或 MAC 连接到其中一个设备。
它找不到的是安装了模拟 iBeacon、AltBeacon 或 Eddystone Beacon 应用程序的手机。
此设备不以任何方式可见。我哪里出错了?我应该指定信标的类型吗?如果是的话在哪里?
https://github.com/AltBeacon/android-beacon-library。
在扫描之前,请确保询问每个 Android 版本所需的所有权限。您可以在上述图书馆的网站文档中找到此信息。