Browse Source

修复1.ble连接异常问题 2.第二次进入扫描异常问题

weicu
fdp 2 years ago
parent
commit
3f90a2870c
  1. 6
      android/app/src/main/kotlin/com/yunqiinnovation/deepsound/BleBroadcastReceiver.kt
  2. 4
      android/app/src/main/kotlin/com/yunqiinnovation/deepsound/BleWakeService.kt
  3. 4
      android/app/src/main/kotlin/com/yunqiinnovation/deepsound/VoiceInteractionService.kt
  4. 4
      local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureAsrHelper.kt
  5. 1
      local_plugins/jieli/android/build.gradle.kts
  6. 287
      local_plugins/jieli/android/src/main/kotlin/com/yunqiinnovation/jieli/BleService.kt
  7. 80
      local_plugins/jieli/lib/jieli.dart

6
android/app/src/main/kotlin/com/yunqiinnovation/deepsound/BleBroadcastReceiver.kt

@ -22,13 +22,13 @@ class BleBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) { override fun onReceive(context: Context, intent: Intent) {
FileLogger.init(context) FileLogger.init(context)
FileLogger.d(TAG, "接收到广播: ${intent.action}") //FileLogger.d(TAG, "接收到广播: ${intent.action}")
if (intent.action == ACTION_BLE_SCAN_RESULT) { if (intent.action == ACTION_BLE_SCAN_RESULT) {
FileLogger.d(TAG, "收到系统BLE扫描唤醒事件") //FileLogger.d(TAG, "收到系统BLE扫描唤醒事件")
// 打印日志,表明唤醒成功 // 打印日志,表明唤醒成功
FileLogger.d(TAG, "应用被蓝牙BLE广播唤醒成功") //FileLogger.d(TAG, "应用被蓝牙BLE广播唤醒成功")
// 启动BLE唤醒服务,该服务将执行后续操作 // 启动BLE唤醒服务,该服务将执行后续操作
val serviceIntent = Intent(context, BleWakeService::class.java) val serviceIntent = Intent(context, BleWakeService::class.java)

4
android/app/src/main/kotlin/com/yunqiinnovation/deepsound/BleWakeService.kt

@ -78,11 +78,11 @@ class BleWakeService : Service() {
} }
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
FileLogger.d(TAG, "收到onStartCommand: intent=$intent") // FileLogger.d(TAG, "收到onStartCommand: intent=$intent")
// 如果是PendingIntent触发(BLE广播唤醒),打印日志 // 如果是PendingIntent触发(BLE广播唤醒),打印日志
if (intent?.action == "android.bluetooth.adapter.action.REQUEST_ENABLE") { if (intent?.action == "android.bluetooth.adapter.action.REQUEST_ENABLE") {
FileLogger.d(TAG, "收到BLE广播唤醒事件,应用被唤醒") //FileLogger.d(TAG, "收到BLE广播唤醒事件,应用被唤醒")
// 在这里可以执行唤醒后的动作,如启动语音交互服务 // 在这里可以执行唤醒后的动作,如启动语音交互服务
val serviceIntent = Intent(this, VoiceInteractionService::class.java) val serviceIntent = Intent(this, VoiceInteractionService::class.java)

4
android/app/src/main/kotlin/com/yunqiinnovation/deepsound/VoiceInteractionService.kt

@ -481,7 +481,7 @@ class VoiceInteractionService : Service() {
* 当 Service 被启动时调用 * 当 Service 被启动时调用
*/ */
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
FileLogger.d(TAG, "onStartCommand: intent=$intent, action=${intent?.action}") //FileLogger.d(TAG, "onStartCommand: intent=$intent, action=${intent?.action}")
intent?.let {receivedIntent -> intent?.let {receivedIntent ->
when(receivedIntent.action) { when(receivedIntent.action) {
@ -497,7 +497,7 @@ class VoiceInteractionService : Service() {
} }
else -> { else -> {
FileLogger.d(TAG, "onStartCommand收到未知Intent: action=${receivedIntent.action}") //FileLogger.d(TAG, "onStartCommand收到未知Intent: action=${receivedIntent.action}")
} }
} }
} }

4
local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureAsrHelper.kt

@ -34,6 +34,10 @@ class AzureAsrHelper(private val context: Context) {
private var customAudioProcessor: CustomAudioProcessor? = null private var customAudioProcessor: CustomAudioProcessor? = null
private var pushStream: PushAudioInputStream? = null private var pushStream: PushAudioInputStream? = null
private var audioConfig: AudioConfig? = null private var audioConfig: AudioConfig? = null
// 公共方法:允许外部写入音频数据
fun pushAudioData(data: ByteArray) {
pushStream?.write(data) // 使用 SDK 的正确方法(如 push)
}
// 初始化SDK并创建recognizer // 初始化SDK并创建recognizer
fun initialize(subscriptionKey: String, region: String, fun initialize(subscriptionKey: String, region: String,

1
local_plugins/jieli/android/build.gradle.kts

@ -52,4 +52,5 @@ dependencies {
implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.4.0") implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.4.0")
implementation("androidx.lifecycle:lifecycle-service:2.4.0") implementation("androidx.lifecycle:lifecycle-service:2.4.0")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2")
implementation(project(":azure_speech"))
} }

287
local_plugins/jieli/android/src/main/kotlin/com/yunqiinnovation/jieli/BleService.kt

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

80
local_plugins/jieli/lib/jieli.dart

@ -51,34 +51,37 @@ class EventType {
/// 杰理BLE插件主类 /// 杰理BLE插件主类
class Jieli { class Jieli {
static const MethodChannel _methodChannel = MethodChannel('com.yunqiinnovation.jieli'); static const MethodChannel _methodChannel =
static const EventChannel _eventChannel = EventChannel('com.yunqiinnovation.jieli/events'); MethodChannel('com.yunqiinnovation.jieli');
static const EventChannel _eventChannel =
EventChannel('com.yunqiinnovation.jieli/events');
/// 扫描结果流 /// 扫描结果流
static final StreamController<List<BleDevice>> _scanController = static final StreamController<List<BleDevice>> _scanController =
StreamController<List<BleDevice>>.broadcast(); StreamController<List<BleDevice>>.broadcast();
Stream<List<BleDevice>> get scanResults => _scanController.stream; Stream<List<BleDevice>> get scanResults => _scanController.stream;
/// 连接状态变化流 /// 连接状态变化流
static final StreamController<ConnectionState> _connectionController = static final StreamController<ConnectionState> _connectionController =
StreamController<ConnectionState>.broadcast(); StreamController<ConnectionState>.broadcast();
Stream<ConnectionState> get connectionState => _connectionController.stream; Stream<ConnectionState> get connectionState => _connectionController.stream;
/// 音频数据流 /// 音频数据流
static final StreamController<Uint8List> _audioController = static final StreamController<Uint8List> _audioController =
StreamController<Uint8List>.broadcast(); StreamController<Uint8List>.broadcast();
Stream<Uint8List> get audioDataStream => _audioController.stream; Stream<Uint8List> get audioDataStream => _audioController.stream;
/// 唤醒信号流 /// 唤醒信号流
static final StreamController<bool> _wakeupController = static final StreamController<bool> _wakeupController =
StreamController<bool>.broadcast(); StreamController<bool>.broadcast();
Stream<bool> get wakeupStream => _wakeupController.stream; Stream<bool> get wakeupStream => _wakeupController.stream;
/// 命令结果流 /// 命令结果流
static final StreamController<CommandResult> _commandResultController = static final StreamController<CommandResult> _commandResultController =
StreamController<CommandResult>.broadcast(); StreamController<CommandResult>.broadcast();
Stream<CommandResult> get commandResultStream => _commandResultController.stream; Stream<CommandResult> get commandResultStream =>
_commandResultController.stream;
/// 原始事件流 /// 原始事件流
Stream<Map<String, dynamic>>? _eventStream; Stream<Map<String, dynamic>>? _eventStream;
@ -95,22 +98,24 @@ class Jieli {
/// 设置事件监听器 /// 设置事件监听器
void _setupEventListener() { void _setupEventListener() {
_eventSubscription = _eventChannel.receiveBroadcastStream().listen(_handleEvent); _eventSubscription =
_eventChannel.receiveBroadcastStream().listen(_handleEvent);
} }
/// 处理来自原生平台的事件 /// 处理来自原生平台的事件
void _handleEvent(dynamic event) { void _handleEvent(dynamic event) {
if (event is! Map<dynamic, dynamic>) return; if (event is! Map<dynamic, dynamic>) return;
final eventData = Map<String, dynamic>.from(event); final eventData = Map<String, dynamic>.from(event);
final String eventType = eventData['type'] as String; final String eventType = eventData['type'] as String;
final dynamic data = eventData['data']; final dynamic data = eventData['data'];
switch (eventType) { switch (eventType) {
case EventType.scanResult: case EventType.scanResult:
if (data is List<dynamic>) { if (data is List<dynamic>) {
final deviceList = data final deviceList = data
.map((item) => BleDevice.fromJson(Map<String, dynamic>.from(item))) .map(
(item) => BleDevice.fromJson(Map<String, dynamic>.from(item)))
.toList(); .toList();
_scanController.add(deviceList); _scanController.add(deviceList);
} }
@ -175,37 +180,41 @@ class Jieli {
/// 发送开始录音命令 /// 发送开始录音命令
Future<bool> sendStartRecordingCommand() async { Future<bool> sendStartRecordingCommand() async {
final result = await _methodChannel.invokeMethod<bool>('sendStartRecordingCommand'); final result =
await _methodChannel.invokeMethod<bool>('sendStartRecordingCommand');
return result ?? false; return result ?? false;
} }
/// 发送停止录音命令 /// 发送停止录音命令
Future<bool> sendStopRecordingCommand() async { Future<bool> sendStopRecordingCommand() async {
final result = await _methodChannel.invokeMethod<bool>('sendStopRecordingCommand'); final result =
await _methodChannel.invokeMethod<bool>('sendStopRecordingCommand');
return result ?? false; return result ?? false;
} }
/// 关闭插件,释放资源 /// 关闭插件,释放资源
Future<void> dispose() async { Future<void> dispose() async {
await stopScan(); await stopScan();
await disconnect(); // await disconnect();
_eventSubscription?.cancel(); // _eventSubscription?.cancel();
_scanController.close(); // _scanController.close();
_connectionController.close(); // _connectionController.close();
_audioController.close(); // _audioController.close();
_wakeupController.close(); // _wakeupController.close();
_commandResultController.close(); // _commandResultController.close();
} }
/// 检查蓝牙是否已启用 /// 检查蓝牙是否已启用
Future<bool> isBluetoothEnabled() async { Future<bool> isBluetoothEnabled() async {
final result = await _methodChannel.invokeMethod<bool>('isBluetoothEnabled'); final result =
await _methodChannel.invokeMethod<bool>('isBluetoothEnabled');
return result ?? false; return result ?? false;
} }
/// 请求打开蓝牙 /// 请求打开蓝牙
Future<bool> requestBluetoothEnable() async { Future<bool> requestBluetoothEnable() async {
final result = await _methodChannel.invokeMethod<bool>('requestBluetoothEnable'); final result =
await _methodChannel.invokeMethod<bool>('requestBluetoothEnable');
return result ?? false; return result ?? false;
} }
@ -223,7 +232,7 @@ class Jieli {
return false; return false;
} }
} }
/// 增加音量 /// 增加音量
Future<bool> increaseVolume() async { Future<bool> increaseVolume() async {
try { try {
@ -234,7 +243,7 @@ class Jieli {
return false; return false;
} }
} }
/// 减小音量 /// 减小音量
Future<bool> decreaseVolume() async { Future<bool> decreaseVolume() async {
try { try {
@ -261,12 +270,13 @@ class BluetoothDevice {
class CommandResult { class CommandResult {
final int commandType; final int commandType;
final bool success; final bool success;
CommandResult({ CommandResult({
required this.commandType, required this.commandType,
required this.success, required this.success,
}); });
@override @override
String toString() => 'CommandResult{commandType: $commandType, success: $success}'; String toString() =>
} 'CommandResult{commandType: $commandType, success: $success}';
}

Loading…
Cancel
Save