|
|
|
@ -10,35 +10,43 @@ import android.util.Log |
|
|
|
import androidx.lifecycle.LifecycleService |
|
|
|
import androidx.lifecycle.MutableLiveData |
|
|
|
import kotlinx.coroutines.* |
|
|
|
import java.io.File |
|
|
|
import java.io.FileOutputStream |
|
|
|
import java.text.SimpleDateFormat |
|
|
|
import java.util.* |
|
|
|
|
|
|
|
import com.yunqiinnovation.azure_speech.AzureAsrHelper |
|
|
|
import com.yunqiinnovation.azure_speech.AzureTtsHelper |
|
|
|
/** |
|
|
|
* BLE服务类,提供核心蓝牙功能 |
|
|
|
*/ |
|
|
|
class BleService(private val context: Context) { |
|
|
|
companion object { |
|
|
|
private const val TAG = "JieliBleService" |
|
|
|
|
|
|
|
|
|
|
|
// 服务和特征UUID - 根据协议规范重新定义 |
|
|
|
// private const val AUDIO_SERVICE_UUID = "0000faa0-0000-1000-8000-00805f9b34fb" // 主服务UUID |
|
|
|
private const val AUDIO_SERVICE_UUID = "0000fd7b-0000-1000-8000-00805f9b34fb" // 主服务UUID |
|
|
|
private const val NOTIFY_CHAR_UUID = "0000faa2-0000-1000-8000-00805f9b34fb" // 用于接收数据的特征(notify) |
|
|
|
private const val WRITE_CHAR_UUID = "0000faa1-0000-1000-8000-00805f9b34fb" // 用于发送命令的特征(write without response) |
|
|
|
private const val CLIENT_CHAR_CONFIG_UUID = "00002902-0000-1000-8000-00805f9b34fb" // 标准的客户端特征配置描述符 |
|
|
|
private const val AUDIO_SERVICE_UUID = "0000abc0-0000-1111-2222-123456789abc" // 主服务UUID |
|
|
|
private const val NOTIFY_CHAR_UUID = |
|
|
|
"0000abc2-0000-1111-2222-123456789abc" // 用于接收数据的特征(notify) |
|
|
|
private const val WRITE_CHAR_UUID = |
|
|
|
"0000abc1-0000-1111-2222-123456789abc" // 用于发送命令的特征(write without response) |
|
|
|
private const val CLIENT_CHAR_CONFIG_UUID = |
|
|
|
"00002902-0000-1000-8000-00805f9b34fb" // 标准的客户端特征配置描述符 |
|
|
|
|
|
|
|
// 连接状态常量 |
|
|
|
const val STATE_DISCONNECTED = 0 |
|
|
|
const val STATE_CONNECTING = 1 |
|
|
|
const val STATE_CONNECTED = 2 |
|
|
|
const val STATE_DISCONNECTING = 3 |
|
|
|
const val STATE_ERROR = 4 |
|
|
|
|
|
|
|
|
|
|
|
// 扫描结果更新间隔(毫秒) |
|
|
|
private const val SCAN_RESULT_UPDATE_INTERVAL = 500L |
|
|
|
|
|
|
|
|
|
|
|
// 命令类型常量 |
|
|
|
const val COMMAND_VOLUME = 1 |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* 回调接口 |
|
|
|
*/ |
|
|
|
@ -49,33 +57,41 @@ class BleService(private val context: Context) { |
|
|
|
fun onWakeupSignalReceived() |
|
|
|
fun onCommandResult(commandType: Int, success: Boolean) |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// region 文件相关 |
|
|
|
private var outputFile: File? = null |
|
|
|
private var fos: FileOutputStream? = null |
|
|
|
|
|
|
|
// Azure服务 |
|
|
|
private var azureAsrHelper: AzureAsrHelper? = null |
|
|
|
private var azureTtsHelper: AzureTtsHelper? = null |
|
|
|
// 蓝牙管理器和适配器 |
|
|
|
private val bluetoothManager = context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager |
|
|
|
private val bluetoothManager = |
|
|
|
context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager |
|
|
|
private val bluetoothAdapter: BluetoothAdapter? = bluetoothManager.adapter |
|
|
|
private val bluetoothLeScanner: BluetoothLeScanner? = bluetoothAdapter?.bluetoothLeScanner |
|
|
|
|
|
|
|
|
|
|
|
// GATT连接和特征 |
|
|
|
private var bluetoothGatt: BluetoothGatt? = null |
|
|
|
private var notifyChar: BluetoothGattCharacteristic? = null |
|
|
|
private var writeChar: BluetoothGattCharacteristic? = null |
|
|
|
|
|
|
|
|
|
|
|
// 扫描相关 |
|
|
|
private val scanHandler = Handler(Looper.getMainLooper()) |
|
|
|
private val scanResults = ArrayList<ScanResult>() |
|
|
|
private var isScanning = false |
|
|
|
private var isPendingScanResultUpdate = false |
|
|
|
private val scanResultUpdateRunnable = Runnable { |
|
|
|
private val scanResultUpdateRunnable = Runnable { |
|
|
|
notifyScanResults() |
|
|
|
isPendingScanResultUpdate = false |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 连接状态LiveData |
|
|
|
val connectionState = MutableLiveData<Int>(STATE_DISCONNECTED) |
|
|
|
|
|
|
|
|
|
|
|
// 统一回调接口 |
|
|
|
var callback: Callback? = null |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* 开始扫描BLE设备 |
|
|
|
*/ |
|
|
|
@ -83,32 +99,35 @@ class BleService(private val context: Context) { |
|
|
|
if (bluetoothLeScanner == null || isScanning) { |
|
|
|
return false |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
Log.i(TAG, "开始扫描BLE设备...") |
|
|
|
scanResults.clear() |
|
|
|
|
|
|
|
|
|
|
|
val serviceUuid = UUID.fromString(AUDIO_SERVICE_UUID) |
|
|
|
val scanFilter = ScanFilter.Builder() |
|
|
|
// .setServiceUuid(ParcelUuid(serviceUuid)) |
|
|
|
.build() |
|
|
|
val scanFilters = listOf(scanFilter) |
|
|
|
|
|
|
|
|
|
|
|
val scanSettings = ScanSettings.Builder() |
|
|
|
.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY) |
|
|
|
.build() |
|
|
|
|
|
|
|
|
|
|
|
bluetoothLeScanner.startScan(scanFilters, scanSettings, scanCallback) |
|
|
|
isScanning = true |
|
|
|
|
|
|
|
|
|
|
|
// 设置超时停止扫描 |
|
|
|
scanHandler.postDelayed({ |
|
|
|
Log.d(TAG, "定时器触发,准备停止扫描 [主线程:${Looper.myLooper() == Looper.getMainLooper()}]") |
|
|
|
Log.d( |
|
|
|
TAG, |
|
|
|
"定时器触发,准备停止扫描 [主线程:${Looper.myLooper() == Looper.getMainLooper()}]" |
|
|
|
) |
|
|
|
stopScan() |
|
|
|
}, scanTimeout) |
|
|
|
|
|
|
|
|
|
|
|
return true |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* 停止扫描 |
|
|
|
*/ |
|
|
|
@ -118,17 +137,17 @@ class BleService(private val context: Context) { |
|
|
|
if (bluetoothLeScanner == null || !isScanning) { |
|
|
|
return false |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
bluetoothLeScanner.stopScan(scanCallback) |
|
|
|
scanHandler.removeCallbacksAndMessages(null) |
|
|
|
isScanning = false |
|
|
|
|
|
|
|
|
|
|
|
// 最后一次发送扫描结果 |
|
|
|
notifyScanResults() |
|
|
|
|
|
|
|
|
|
|
|
return true |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* 扫描回调 |
|
|
|
*/ |
|
|
|
@ -142,8 +161,8 @@ class BleService(private val context: Context) { |
|
|
|
// if (!deviceName.contains("sanag") && !deviceName.contains("Ola Friend")) { |
|
|
|
// return |
|
|
|
// } |
|
|
|
Log.d(TAG, "============发现设备: $deviceName [$deviceAddress], RSSI: $rssi") |
|
|
|
|
|
|
|
//Log.d(TAG, "============发现设备: $deviceName [$deviceAddress], RSSI: $rssi") |
|
|
|
|
|
|
|
// 打印所有发现的UUID以便调试 |
|
|
|
val serviceUuids = scanRecord?.serviceUuids |
|
|
|
if (serviceUuids != null) { |
|
|
|
@ -151,37 +170,41 @@ class BleService(private val context: Context) { |
|
|
|
Log.d(TAG, "设备UUID: ${uuid.uuid}") |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 检查制造商数据 |
|
|
|
for (i in 0 until 65535) { |
|
|
|
val manufacturerData = scanRecord?.getManufacturerSpecificData(i) |
|
|
|
if (manufacturerData != null) { |
|
|
|
Log.d(TAG, "发现厂商ID: $i, 数据长度: ${manufacturerData.size}") |
|
|
|
// Log.d(TAG, "发现厂商ID: $i, 数据长度: ${manufacturerData.size}") |
|
|
|
// 这里可以根据已知的厂商ID和格式解析数据 |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// 避免重复添加 |
|
|
|
|
|
|
|
val existingIndex = scanResults.indexOfFirst { it.device.address == deviceAddress } |
|
|
|
|
|
|
|
if (existingIndex >= 0) { |
|
|
|
|
|
|
|
scanResults[existingIndex] = result |
|
|
|
} else { |
|
|
|
Log.d(TAG, "扫描回调A, scanResults[existingIndex]ADD") |
|
|
|
scanResults.add(result) |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 如果没有待处理的更新,安排一个 |
|
|
|
if (!isPendingScanResultUpdate) { |
|
|
|
isPendingScanResultUpdate = true |
|
|
|
scanHandler.postDelayed(scanResultUpdateRunnable, SCAN_RESULT_UPDATE_INTERVAL) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
override fun onScanFailed(errorCode: Int) { |
|
|
|
Log.e(TAG, "扫描失败,错误码: $errorCode") |
|
|
|
isScanning = false |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* 将扫描结果通知回调 |
|
|
|
*/ |
|
|
|
@ -196,7 +219,7 @@ class BleService(private val context: Context) { |
|
|
|
|
|
|
|
callback?.onScanResult(devicesJson) |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* 连接到指定设备 |
|
|
|
*/ |
|
|
|
@ -206,24 +229,25 @@ class BleService(private val context: Context) { |
|
|
|
updateConnectionState(STATE_ERROR) |
|
|
|
return |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 先断开现有连接 |
|
|
|
bluetoothGatt?.close() |
|
|
|
bluetoothGatt = null |
|
|
|
|
|
|
|
|
|
|
|
try { |
|
|
|
val device = bluetoothAdapter.getRemoteDevice(deviceAddress) |
|
|
|
updateConnectionState(STATE_CONNECTING) |
|
|
|
|
|
|
|
|
|
|
|
// 连接GATT服务器 |
|
|
|
Log.i(TAG, "正在连接设备: ${device.name ?: "未知设备"} [$deviceAddress]") |
|
|
|
bluetoothGatt = device.connectGatt(context, false, gattCallback) |
|
|
|
bluetoothGatt = |
|
|
|
device.connectGatt(context, false, gattCallback, BluetoothDevice.TRANSPORT_LE) |
|
|
|
} catch (e: Exception) { |
|
|
|
Log.e(TAG, "连接设备时发生错误: ${e.message}") |
|
|
|
updateConnectionState(STATE_ERROR) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* 断开连接 |
|
|
|
*/ |
|
|
|
@ -231,7 +255,7 @@ class BleService(private val context: Context) { |
|
|
|
bluetoothGatt?.disconnect() |
|
|
|
updateConnectionState(STATE_DISCONNECTING) |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* 更新连接状态并通知回调 |
|
|
|
*/ |
|
|
|
@ -239,22 +263,24 @@ class BleService(private val context: Context) { |
|
|
|
connectionState.postValue(state) |
|
|
|
callback?.onConnectionStateChanged(state) |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* GATT回调 |
|
|
|
*/ |
|
|
|
private val gattCallback = object : BluetoothGattCallback() { |
|
|
|
|
|
|
|
|
|
|
|
override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) { |
|
|
|
if (newState == BluetoothProfile.STATE_CONNECTED && status == BluetoothGatt.GATT_SUCCESS) { |
|
|
|
Log.i(TAG, "设备已连接,开始发现服务...") |
|
|
|
updateConnectionState(STATE_CONNECTED) |
|
|
|
|
|
|
|
|
|
|
|
// 开始搜索服务 |
|
|
|
gatt.discoverServices() |
|
|
|
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) { |
|
|
|
Log.i(TAG, "设备已断开连接") |
|
|
|
updateConnectionState(STATE_DISCONNECTED) |
|
|
|
|
|
|
|
|
|
|
|
// 清理资源 |
|
|
|
gatt.close() |
|
|
|
bluetoothGatt = null |
|
|
|
@ -263,33 +289,37 @@ class BleService(private val context: Context) { |
|
|
|
} else if (status != BluetoothGatt.GATT_SUCCESS) { |
|
|
|
Log.e(TAG, "连接失败,status=$status") |
|
|
|
updateConnectionState(STATE_ERROR) |
|
|
|
|
|
|
|
|
|
|
|
gatt.close() |
|
|
|
bluetoothGatt = null |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) { |
|
|
|
if (status == BluetoothGatt.GATT_SUCCESS) { |
|
|
|
Log.i(TAG, "服务发现成功") |
|
|
|
|
|
|
|
|
|
|
|
// 查找目标服务 |
|
|
|
val audioService = gatt.getService(UUID.fromString(AUDIO_SERVICE_UUID)) |
|
|
|
if (audioService != null) { |
|
|
|
Log.i(TAG, "找到音频服务") |
|
|
|
|
|
|
|
Log.i(TAG, "找到音频服务,开始遍历特征...") |
|
|
|
audioService.characteristics.forEach { char -> |
|
|
|
Log.i(TAG, "特征UUID: ${char.uuid}") |
|
|
|
} |
|
|
|
Log.i(TAG, "找到音频服务$") |
|
|
|
|
|
|
|
// 获取特征 |
|
|
|
notifyChar = audioService.getCharacteristic(UUID.fromString(NOTIFY_CHAR_UUID)) |
|
|
|
writeChar = audioService.getCharacteristic(UUID.fromString(WRITE_CHAR_UUID)) |
|
|
|
|
|
|
|
if (notifyChar != null && writeChar != null) { |
|
|
|
Log.i(TAG, "找到所需特征") |
|
|
|
|
|
|
|
|
|
|
|
// 设置特征写入类型为无响应模式 |
|
|
|
writeChar?.writeType = BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE |
|
|
|
|
|
|
|
|
|
|
|
// 注册特征通知 |
|
|
|
setupNotifications(gatt) |
|
|
|
|
|
|
|
} else { |
|
|
|
Log.e(TAG, "未找到所需特征") |
|
|
|
updateConnectionState(STATE_ERROR) |
|
|
|
@ -303,43 +333,66 @@ class BleService(private val context: Context) { |
|
|
|
updateConnectionState(STATE_ERROR) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
override fun onCharacteristicChanged(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic) { |
|
|
|
// 添加扩展函数(在文件顶部声明) |
|
|
|
fun List<Byte>.toByteArray(): ByteArray { |
|
|
|
return ByteArray(this.size) { this[it] } |
|
|
|
} |
|
|
|
|
|
|
|
override fun onCharacteristicChanged( |
|
|
|
gatt: BluetoothGatt, |
|
|
|
characteristic: BluetoothGattCharacteristic |
|
|
|
) { |
|
|
|
val characteristicUuid = characteristic.uuid.toString() |
|
|
|
Log.d(TAG, "收到特征变化通知: $characteristicUuid") |
|
|
|
|
|
|
|
|
|
|
|
when (characteristicUuid) { |
|
|
|
// NOTIFY_CHAR_UUID -> { |
|
|
|
// // 收到音频数据 |
|
|
|
// val audioData = characteristic.value |
|
|
|
// Log.d(TAG, "收到音频数据帧,大小: ${audioData.size} 字节") |
|
|
|
|
|
|
|
|
|
|
|
// callback?.onAudioDataReceived(audioData) |
|
|
|
// } |
|
|
|
NOTIFY_CHAR_UUID -> { |
|
|
|
// 处理控制消息 |
|
|
|
val value = characteristic.value |
|
|
|
Log.i(TAG, "收到控制通知: ${value.contentToString()}") |
|
|
|
|
|
|
|
|
|
|
|
// 判断是否收到音频数据 |
|
|
|
if (value.isNotEmpty() && (value[0].toInt() and 0xFF == 0x4b || value[0].toInt() and 0xFF == 0x48)) { |
|
|
|
// 处理设备返回的协议数据 |
|
|
|
fos?.write(value) |
|
|
|
// 方法1:手动转换 |
|
|
|
val byteArray = ByteArray(value.size) { index -> value[index] } |
|
|
|
|
|
|
|
// 或者直接使用现有 |
|
|
|
azureAsrHelper?.pushAudioData(byteArray) |
|
|
|
} |
|
|
|
// 判断是否是协议格式响应 |
|
|
|
if (value.isNotEmpty() && value[0].toInt() and 0xFF == 0xA5) { |
|
|
|
// 处理设备返回的协议数据 |
|
|
|
processResponseData(value) |
|
|
|
} |
|
|
|
} |
|
|
|
// 假设0x01表示唤醒信号 |
|
|
|
else if (value.isNotEmpty() && value[0].toInt() == 0x01) { |
|
|
|
Log.i(TAG, "收到唤醒信号") |
|
|
|
|
|
|
|
|
|
|
|
callback?.onWakeupSignalReceived() |
|
|
|
|
|
|
|
|
|
|
|
// 收到唤醒后自动发送开始录音命令 |
|
|
|
sendStartRecordingCommand() |
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
override fun onCharacteristicWrite(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, status: Int) { |
|
|
|
|
|
|
|
override fun onCharacteristicWrite( |
|
|
|
gatt: BluetoothGatt, |
|
|
|
characteristic: BluetoothGattCharacteristic, |
|
|
|
status: Int |
|
|
|
) { |
|
|
|
if (characteristic.uuid.toString() == WRITE_CHAR_UUID) { |
|
|
|
if (status == BluetoothGatt.GATT_SUCCESS) { |
|
|
|
Log.i(TAG, "命令发送成功: ${characteristic.value.contentToString()}") |
|
|
|
@ -348,8 +401,12 @@ class BleService(private val context: Context) { |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
override fun onDescriptorWrite(gatt: BluetoothGatt, descriptor: BluetoothGattDescriptor, status: Int) { |
|
|
|
|
|
|
|
override fun onDescriptorWrite( |
|
|
|
gatt: BluetoothGatt, |
|
|
|
descriptor: BluetoothGattDescriptor, |
|
|
|
status: Int |
|
|
|
) { |
|
|
|
if (status == BluetoothGatt.GATT_SUCCESS) { |
|
|
|
Log.i(TAG, "描述符写入成功: ${descriptor.uuid}") |
|
|
|
Log.i(TAG, "特征通知设置成功,设备就绪") |
|
|
|
@ -359,7 +416,7 @@ class BleService(private val context: Context) { |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* 设置特征通知 |
|
|
|
*/ |
|
|
|
@ -367,10 +424,10 @@ class BleService(private val context: Context) { |
|
|
|
// 启用 notify 特征通知 |
|
|
|
if (notifyChar?.properties?.and(BluetoothGattCharacteristic.PROPERTY_NOTIFY) != 0) { |
|
|
|
gatt.setCharacteristicNotification(notifyChar, true) |
|
|
|
|
|
|
|
|
|
|
|
val descriptor = notifyChar?.getDescriptor(UUID.fromString(CLIENT_CHAR_CONFIG_UUID)) |
|
|
|
descriptor?.value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE |
|
|
|
|
|
|
|
|
|
|
|
if (descriptor != null) { |
|
|
|
gatt.writeDescriptor(descriptor) |
|
|
|
Log.i(TAG, "设置数据通知特征通知") |
|
|
|
@ -379,10 +436,11 @@ class BleService(private val context: Context) { |
|
|
|
Log.e(TAG, "数据通知特征不支持通知") |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* 处理设备返回的数据 |
|
|
|
* 协议格式: |
|
|
|
* 协议格式: |
|
|
|
* 协议头(0xA5) + 字段类型(1byte) + 数据长度(1byte) + 数据域(N byte) + 校验和(1byte) |
|
|
|
*/ |
|
|
|
private fun processResponseData(data: ByteArray) { |
|
|
|
@ -390,36 +448,36 @@ class BleService(private val context: Context) { |
|
|
|
Log.e(TAG, "返回数据格式错误,长度不足") |
|
|
|
return |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 判断协议头 |
|
|
|
if (data[0].toInt() and 0xFF != 0xA5) { |
|
|
|
Log.e(TAG, "返回数据协议头错误: ${data[0].toInt() and 0xFF}") |
|
|
|
return |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
val fieldType = data[1].toInt() and 0xFF |
|
|
|
val dataLen = data[2].toInt() and 0xFF |
|
|
|
|
|
|
|
|
|
|
|
// 验证长度 |
|
|
|
if (data.size != dataLen + 4) { // 协议头+字段类型+数据长度+数据域+校验和 |
|
|
|
Log.e(TAG, "返回数据长度不匹配: 应为${dataLen + 4},实际为${data.size}") |
|
|
|
return |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 计算校验和 |
|
|
|
var checksum = 0 |
|
|
|
for (i in 0 until data.size - 1) { |
|
|
|
checksum += data[i].toInt() and 0xFF |
|
|
|
} |
|
|
|
checksum = checksum and 0xFF |
|
|
|
|
|
|
|
|
|
|
|
val receivedChecksum = data[data.size - 1].toInt() and 0xFF |
|
|
|
|
|
|
|
|
|
|
|
if (checksum != receivedChecksum) { |
|
|
|
Log.e(TAG, "校验和错误: 计算值=$checksum, 接收值=$receivedChecksum") |
|
|
|
return |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 解析字段类型 |
|
|
|
when (fieldType) { |
|
|
|
0x01 -> { |
|
|
|
@ -435,7 +493,7 @@ class BleService(private val context: Context) { |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* 发送开始录音命令 |
|
|
|
*/ |
|
|
|
@ -444,26 +502,43 @@ class BleService(private val context: Context) { |
|
|
|
Log.e(TAG, "无法发送开始录音命令,设备未连接") |
|
|
|
return false |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
try { |
|
|
|
val commandData = byteArrayOf(0x01) |
|
|
|
writeChar?.value = commandData |
|
|
|
|
|
|
|
// 获取外部存储目录路径 |
|
|
|
// 生成带时间戳的文件名(格式:yyyyMMdd_HHmmss_audio.opus) |
|
|
|
val timeStamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date()) |
|
|
|
val fileName = "${timeStamp}_audio.opus" |
|
|
|
val externalDir = context.getExternalFilesDir(null) |
|
|
|
outputFile = File(externalDir, fileName) |
|
|
|
fos = FileOutputStream(outputFile).also { |
|
|
|
Log.i(TAG, "音频文件创建成功:${outputFile?.absolutePath}") |
|
|
|
} |
|
|
|
|
|
|
|
// val subscriptionKey = call.argument<String>("subscriptionKey") ?: "" |
|
|
|
// val region = call.argument<String>("region") ?: "" |
|
|
|
// val supportedLanguages = call.argument<List<String>>("supportedLanguages") ?: listOf("zh-CN") |
|
|
|
|
|
|
|
// val success = azureAsrHelper.initialize(subscriptionKey, region, supportedLanguages.toTypedArray()) |
|
|
|
// result.success(success) |
|
|
|
|
|
|
|
|
|
|
|
val success = bluetoothGatt?.writeCharacteristic(writeChar) ?: false |
|
|
|
|
|
|
|
|
|
|
|
if (success) { |
|
|
|
Log.i(TAG, "开始录音命令已发送") |
|
|
|
} else { |
|
|
|
Log.e(TAG, "开始录音命令发送失败") |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return success |
|
|
|
} catch (e: Exception) { |
|
|
|
Log.e(TAG, "发送开始录音命令时发生异常: ${e.message}") |
|
|
|
return false |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* 发送停止录音命令 |
|
|
|
*/ |
|
|
|
@ -472,74 +547,78 @@ class BleService(private val context: Context) { |
|
|
|
Log.e(TAG, "无法发送停止录音命令,设备未连接") |
|
|
|
return false |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
try { |
|
|
|
val commandData = byteArrayOf(0x02) |
|
|
|
writeChar?.value = commandData |
|
|
|
|
|
|
|
|
|
|
|
val success = bluetoothGatt?.writeCharacteristic(writeChar) ?: false |
|
|
|
|
|
|
|
fos?.close() |
|
|
|
if (success) { |
|
|
|
Log.i(TAG, "停止录音命令已发送") |
|
|
|
} else { |
|
|
|
Log.e(TAG, "停止录音命令发送失败") |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return success |
|
|
|
} catch (e: Exception) { |
|
|
|
Log.e(TAG, "发送停止录音命令时发生异常: ${e.message}") |
|
|
|
return false |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* 发送音量调整命令 |
|
|
|
* 协议格式: |
|
|
|
* 协议头(0xAA) + 字段类型(0x01) + 数据长度(0x01) + 音量值(0~16) + 校验和 |
|
|
|
*/ |
|
|
|
fun adjustVolume(volume: Int): Boolean { |
|
|
|
Log.e( |
|
|
|
TAG, |
|
|
|
"adjustVolume,bluetoothGatt=${bluetoothGatt},writeChar=${writeChar},connectionState=${connectionState.value}" |
|
|
|
) |
|
|
|
if (bluetoothGatt == null || writeChar == null || connectionState.value != STATE_CONNECTED) { |
|
|
|
Log.e(TAG, "无法发送音量调整命令,设备未连接") |
|
|
|
return false |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 限制音量范围为0-16 |
|
|
|
val safeVolume = volume.coerceIn(0, 16) |
|
|
|
|
|
|
|
|
|
|
|
try { |
|
|
|
// 构建命令数据 |
|
|
|
val header: Byte = 0xAA.toByte() |
|
|
|
val fieldType: Byte = 0x01 |
|
|
|
val dataLen: Byte = 0x01 |
|
|
|
val volumeData: Byte = safeVolume.toByte() |
|
|
|
|
|
|
|
|
|
|
|
// 计算校验和 |
|
|
|
val checksum = ((header.toInt() and 0xFF) + |
|
|
|
(fieldType.toInt() and 0xFF) + |
|
|
|
(dataLen.toInt() and 0xFF) + |
|
|
|
(volumeData.toInt() and 0xFF)) and 0xFF |
|
|
|
|
|
|
|
val checksum = ((header.toInt() and 0xFF) + |
|
|
|
(fieldType.toInt() and 0xFF) + |
|
|
|
(dataLen.toInt() and 0xFF) + |
|
|
|
(volumeData.toInt() and 0xFF)) and 0xFF |
|
|
|
|
|
|
|
// 创建完整命令 |
|
|
|
val commandData = byteArrayOf(header, fieldType, dataLen, volumeData, checksum.toByte()) |
|
|
|
|
|
|
|
|
|
|
|
// 写入特征 |
|
|
|
writeChar?.value = commandData |
|
|
|
|
|
|
|
|
|
|
|
val success = bluetoothGatt?.writeCharacteristic(writeChar) ?: false |
|
|
|
|
|
|
|
|
|
|
|
if (success) { |
|
|
|
Log.i(TAG, "音量调整命令已发送: $safeVolume") |
|
|
|
} else { |
|
|
|
Log.e(TAG, "音量调整命令发送失败") |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return success |
|
|
|
} catch (e: Exception) { |
|
|
|
Log.e(TAG, "发送音量调整命令时发生异常: ${e.message}") |
|
|
|
return false |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* 增加音量 |
|
|
|
*/ |
|
|
|
@ -547,7 +626,7 @@ class BleService(private val context: Context) { |
|
|
|
// 假设当前音量保存在设备中,我们只需发送增加命令 |
|
|
|
return adjustVolume(16) // 发送最大音量命令 |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* 减小音量 |
|
|
|
*/ |
|
|
|
@ -555,7 +634,7 @@ class BleService(private val context: Context) { |
|
|
|
// 假设当前音量保存在设备中,我们只需发送减小命令 |
|
|
|
return adjustVolume(0) // 发送最小音量命令 |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* 清理资源 |
|
|
|
*/ |
|
|
|
|