wolfplus 1 year ago
parent
commit
668c8ac3fa
  1. 23
      android/app/src/main/AndroidManifest.xml
  2. 383
      android/app/src/main/kotlin/com/yunqiinnovation/deepsound/ClassicBluetoothHelper.kt
  3. 197
      android/app/src/main/kotlin/com/yunqiinnovation/deepsound/ClassicBluetoothPlugin.kt
  4. 24
      android/app/src/main/kotlin/com/yunqiinnovation/deepsound/MainActivity.kt
  5. 17
      lib/core/bindings/initial_binding.dart
  6. 214
      lib/data/services/classic_bluetooth_service.dart
  7. 253
      lib/modules/home/controllers/home_controller.dart
  8. 2
      lib/modules/home/views/home_view.dart
  9. 274
      lib/modules/pairing/controllers/pairing_controller.dart
  10. 2
      local_plugins/agent_service/android/build.gradle.kts
  11. 4
      local_plugins/agent_service/android/settings.gradle.kts
  12. 4
      local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt
  13. 4
      local_plugins/agent_service/pubspec.yaml
  14. 2
      local_plugins/ble_service/android/build.gradle.kts
  15. 1
      local_plugins/ble_service/android/settings.gradle.kts
  16. 3
      local_plugins/ble_service/android/src/main/AndroidManifest.xml
  17. 12
      local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleConst.kt
  18. 376
      local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleForegroundService.kt
  19. 58
      local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleManagerWrapper.kt
  20. 434
      local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleService.kt
  21. 19
      local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleServicePlugin.kt
  22. 7
      local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleWakeReceiver.kt
  23. 40
      local_plugins/classic_bluetooth/android/build.gradle.kts
  24. 1
      local_plugins/classic_bluetooth/android/settings.gradle.kts
  25. 17
      local_plugins/classic_bluetooth/android/src/main/AndroidManifest.xml
  26. 266
      local_plugins/classic_bluetooth/android/src/main/kotlin/com/yunqiinnovation/classic_bluetooth/ClassicBluetoothHelper.kt
  27. 186
      local_plugins/classic_bluetooth/android/src/main/kotlin/com/yunqiinnovation/classic_bluetooth/ClassicBluetoothPlugin.kt
  28. 119
      local_plugins/classic_bluetooth/lib/classic_bluetooth.dart
  29. 23
      local_plugins/classic_bluetooth/pubspec.yaml
  30. BIN
      local_plugins/opus/.gradle/8.10/fileHashes/fileHashes.lock
  31. BIN
      local_plugins/opus/.gradle/buildOutputCleanup/buildOutputCleanup.lock
  32. 2
      local_plugins/opus/.gradle/buildOutputCleanup/cache.properties
  33. 6
      pubspec.yaml
  34. 231
      test.json

23
android/app/src/main/AndroidManifest.xml

@ -67,29 +67,6 @@
<meta-data android:name="com.facebook.sdk.ApplicationId" android:value="@string/facebook_app_id"/>
<meta-data android:name="com.facebook.sdk.ClientToken" android:value="@string/facebook_client_token"/>
<!-- 添加语音交互服务 -->
<service
android:name=".VoiceInteractionService"
android:foregroundServiceType="mediaPlayback"
android:exported="true"
android:enabled="true">
<intent-filter android:priority="1000">
<action android:name="android.intent.action.MEDIA_BUTTON" />
<!-- <action android:name="android.media.browse.MediaBrowserService" /> -->
</intent-filter>
</service>
<!-- 添加媒体按钮接收器 -->
<receiver
android:name="androidx.media.session.MediaButtonReceiver"
android:exported="true">
<intent-filter android:priority="2147483647">
<action android:name="android.intent.action.MEDIA_BUTTON" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</receiver>
<activity
android:name=".MainActivity"

383
android/app/src/main/kotlin/com/yunqiinnovation/deepsound/ClassicBluetoothHelper.kt

@ -0,0 +1,383 @@
package com.yunqiinnovation.deepsound
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothProfile
import android.content.Context
import android.content.pm.PackageManager
import android.os.Build
import android.util.Log
import android.Manifest
import androidx.core.content.ContextCompat
import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
import android.os.Handler
import android.os.Looper
import android.content.BroadcastReceiver
import android.content.Intent
import android.content.IntentFilter
/**
* 经典蓝牙助手类,负责蓝牙设备连接相关功能
*/
class ClassicBluetoothHelper(private val context: Context) {
private val TAG = "ClassicBluetoothHelper"
// 缓存蓝牙代理对象
private var a2dpProxy: BluetoothProfile? = null
private var headsetProxy: BluetoothProfile? = null
// 蓝牙状态和设备事件回调
private var stateCallback: BluetoothStateCallback? = null
// 蓝牙状态和设备连接广播接收器
private var bluetoothReceiver: BroadcastReceiver? = null
/**
* 获取已连接的A2DP设备(音频输出设备)
* @param callback 回调接口,返回设备列表或错误信息
*/
fun getConnectedA2dpDevices(callback: BluetoothDevicesCallback) {
getConnectedBluetoothDevices(BluetoothProfile.A2DP, callback)
}
/**
* 获取已连接的蓝牙耳机设备
* @param callback 回调接口,返回设备列表或错误信息
*/
fun getConnectedHeadsetDevices(callback: BluetoothDevicesCallback) {
getConnectedBluetoothDevices(BluetoothProfile.HEADSET, callback)
}
/**
* 检查是否有蓝牙连接权限
* @return 是否有权限
*/
private fun hasBluetoothConnectPermission(): Boolean {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
ContextCompat.checkSelfPermission(
context,
Manifest.permission.BLUETOOTH_CONNECT
) == PackageManager.PERMISSION_GRANTED
} else {
true
}
}
/**
* 检查蓝牙是否启用
* @return 蓝牙是否启用
*/
fun isBluetoothEnabled(): Boolean {
try {
val bluetoothAdapter = BluetoothAdapter.getDefaultAdapter()
if (bluetoothAdapter == null) {
return false
}
return bluetoothAdapter.isEnabled
} catch (e: Exception) {
Log.e(TAG, "检查蓝牙状态失败", e)
return false
}
}
/**
* 获取指定配置文件的已连接蓝牙设备
* @param profile 蓝牙配置文件类型,如 BluetoothProfile.A2DP 或 BluetoothProfile.HEADSET
* @param callback 回调接口,返回设备列表或错误信息
*/
fun getConnectedBluetoothDevices(profile: Int, callback: BluetoothDevicesCallback) {
val bluetoothAdapter = BluetoothAdapter.getDefaultAdapter()
if (bluetoothAdapter == null) {
callback.onError("NO_ADAPTER", "没有找到蓝牙适配器")
return
}
// 检查蓝牙权限
if (!hasBluetoothConnectPermission()) {
callback.onError("PERMISSION_DENIED", "缺少蓝牙连接权限")
return
}
try {
// 检查是否已经有缓存的代理对象
val cachedProxy = when (profile) {
BluetoothProfile.A2DP -> a2dpProxy
BluetoothProfile.HEADSET -> headsetProxy
else -> null
}
if (cachedProxy != null) {
// 使用已缓存的代理对象
processDeviceList(cachedProxy, profile, callback)
return
}
// 使用原子布尔值确保回调只被调用一次
val callbackCalled = AtomicBoolean(false)
// 使用Handler处理超时
val handler = Handler(Looper.getMainLooper())
val timeoutRunnable = Runnable {
if (callbackCalled.compareAndSet(false, true)) {
callback.onError("TIMEOUT", "获取蓝牙设备超时")
}
}
// 设置3秒超时
handler.postDelayed(timeoutRunnable, 3000)
// 通过 getProfileProxy 获取指定 Profile (A2DP/HEADSET) 的连接状态
bluetoothAdapter.getProfileProxy(context,
object : BluetoothProfile.ServiceListener {
override fun onServiceConnected(profileId: Int, proxy: BluetoothProfile) {
// 先移除超时处理
handler.removeCallbacks(timeoutRunnable)
if (profileId == profile) {
// 缓存代理对象
when (profileId) {
BluetoothProfile.A2DP -> a2dpProxy = proxy
BluetoothProfile.HEADSET -> headsetProxy = proxy
}
if (callbackCalled.compareAndSet(false, true)) {
try {
processDeviceList(proxy, profileId, callback)
} catch (e: Exception) {
callback.onError("DEVICE_ERROR", "处理设备信息时出错")
}
}
}
}
override fun onServiceDisconnected(profileId: Int) {
// 清除对应的缓存代理
when (profileId) {
BluetoothProfile.A2DP -> a2dpProxy = null
BluetoothProfile.HEADSET -> headsetProxy = null
}
}
}, profile
)
} catch (e: Exception) {
callback.onError("BLUETOOTH_ERROR", "获取蓝牙设备失败")
}
}
/**
* 处理设备列表
*/
private fun processDeviceList(proxy: BluetoothProfile, profileId: Int, callback: BluetoothDevicesCallback) {
// 获取已连接设备
val connectedDevices = proxy.connectedDevices
// 转换为设备信息列表
val devicesList = connectedDevices.map { device ->
mapOf(
"name" to (device.name ?: "未知设备"),
"address" to device.address
)
}
// 返回设备列表(不关闭代理)
callback.onSuccess(devicesList)
}
/**
* 检查是否有蓝牙权限
*/
fun hasBluetoothPermission(): Boolean {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
ContextCompat.checkSelfPermission(
context,
Manifest.permission.BLUETOOTH_CONNECT
) == PackageManager.PERMISSION_GRANTED
} else {
ContextCompat.checkSelfPermission(
context,
Manifest.permission.BLUETOOTH
) == PackageManager.PERMISSION_GRANTED
}
}
/**
* 开始监听蓝牙状态变化
* @param callback 蓝牙状态回调接口
*/
fun startBluetoothStateMonitoring(callback: BluetoothStateCallback) {
this.stateCallback = callback
// 注册蓝牙广播接收器(合并状态变化和设备连接/断开监听)
if (bluetoothReceiver == null) {
bluetoothReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
when (intent?.action) {
// 监听蓝牙状态变化
BluetoothAdapter.ACTION_STATE_CHANGED -> {
val state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR)
when (state) {
BluetoothAdapter.STATE_ON -> {
stateCallback?.onBluetoothStateChanged(BluetoothState.ON)
}
BluetoothAdapter.STATE_OFF -> {
stateCallback?.onBluetoothStateChanged(BluetoothState.OFF)
}
BluetoothAdapter.STATE_TURNING_ON -> {
stateCallback?.onBluetoothStateChanged(BluetoothState.TURNING_ON)
}
BluetoothAdapter.STATE_TURNING_OFF -> {
stateCallback?.onBluetoothStateChanged(BluetoothState.TURNING_OFF)
}
}
}
// 监听设备连接
BluetoothDevice.ACTION_ACL_CONNECTED -> {
val device = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE, BluetoothDevice::class.java)
} else {
@Suppress("DEPRECATION")
intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE)
}
if (device != null) {
stateCallback?.onDeviceConnected(
mapOf(
"name" to (device.name ?: "未知设备"),
"address" to device.address
)
)
}
}
// 监听设备断开
BluetoothDevice.ACTION_ACL_DISCONNECTED -> {
val device = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE, BluetoothDevice::class.java)
} else {
@Suppress("DEPRECATION")
intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE)
}
if (device != null) {
stateCallback?.onDeviceDisconnected(
mapOf(
"name" to (device.name ?: "未知设备"),
"address" to device.address
)
)
}
}
}
}
}
// 创建IntentFilter并添加所有需要监听的动作
val filter = IntentFilter().apply {
addAction(BluetoothAdapter.ACTION_STATE_CHANGED)
addAction(BluetoothDevice.ACTION_ACL_CONNECTED)
addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED)
}
context.registerReceiver(bluetoothReceiver, filter)
// 立即返回当前状态
reportCurrentBluetoothState()
}
}
/**
* 报告当前蓝牙状态
*/
private fun reportCurrentBluetoothState() {
val bluetoothAdapter = BluetoothAdapter.getDefaultAdapter()
if (bluetoothAdapter != null) {
val state = when (bluetoothAdapter.state) {
BluetoothAdapter.STATE_ON -> BluetoothState.ON
BluetoothAdapter.STATE_OFF -> BluetoothState.OFF
BluetoothAdapter.STATE_TURNING_ON -> BluetoothState.TURNING_ON
BluetoothAdapter.STATE_TURNING_OFF -> BluetoothState.TURNING_OFF
else -> BluetoothState.UNKNOWN
}
stateCallback?.onBluetoothStateChanged(state)
}
}
/**
* 停止监听蓝牙状态变化
*/
fun stopBluetoothStateMonitoring() {
// 注销蓝牙广播接收器
if (bluetoothReceiver != null) {
try {
context.unregisterReceiver(bluetoothReceiver)
} catch (e: Exception) {
Log.e(TAG, "注销蓝牙接收器失败", e)
}
bluetoothReceiver = null
}
// 清除回调
stateCallback = null
}
/**
* 释放资源
*/
fun dispose() {
val bluetoothAdapter = BluetoothAdapter.getDefaultAdapter() ?: return
// 停止蓝牙状态监听
stopBluetoothStateMonitoring()
// 释放A2DP代理
a2dpProxy?.let {
try {
bluetoothAdapter.closeProfileProxy(BluetoothProfile.A2DP, it)
} catch (e: Exception) {
Log.e(TAG, "关闭A2DP代理失败", e)
}
a2dpProxy = null
}
// 释放HEADSET代理
headsetProxy?.let {
try {
bluetoothAdapter.closeProfileProxy(BluetoothProfile.HEADSET, it)
} catch (e: Exception) {
Log.e(TAG, "关闭HEADSET代理失败", e)
}
headsetProxy = null
}
}
/**
* 蓝牙设备回调接口
*/
interface BluetoothDevicesCallback {
fun onSuccess(devices: List<Map<String, String>>)
fun onError(code: String, message: String)
}
/**
* 蓝牙状态回调接口
*/
interface BluetoothStateCallback {
fun onBluetoothStateChanged(state: BluetoothState)
fun onDeviceConnected(device: Map<String, String>)
fun onDeviceDisconnected(device: Map<String, String>)
}
/**
* 蓝牙状态枚举
*/
enum class BluetoothState {
UNKNOWN,
OFF,
ON,
TURNING_ON,
TURNING_OFF
}
}

197
android/app/src/main/kotlin/com/yunqiinnovation/deepsound/ClassicBluetoothPlugin.kt

@ -0,0 +1,197 @@
package com.yunqiinnovation.deepsound
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothProfile
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.util.Log
import androidx.annotation.NonNull
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.plugin.common.EventChannel
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.plugin.common.MethodChannel.Result
import com.yunqiinnovation.deepvoice_utils.FileLogger
/** ClassicBluetoothPlugin */
class ClassicBluetoothPlugin: FlutterPlugin, MethodCallHandler {
private val TAG = "ClassicBluetoothPlugin"
private lateinit var channel: MethodChannel
private lateinit var eventChannel: EventChannel
private lateinit var context: Context
private lateinit var bluetoothHelper: ClassicBluetoothHelper
private var eventSink: EventChannel.EventSink? = null
override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
context = flutterPluginBinding.applicationContext
bluetoothHelper = ClassicBluetoothHelper(context)
// 设置方法通道
channel = MethodChannel(flutterPluginBinding.binaryMessenger, "com.yunqiinnovation.classic_bluetooth/methods")
channel.setMethodCallHandler(this)
// 设置事件通道(用于蓝牙状态监控)
eventChannel = EventChannel(flutterPluginBinding.binaryMessenger, "com.yunqiinnovation.classic_bluetooth/events")
eventChannel.setStreamHandler(object : EventChannel.StreamHandler {
override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {
eventSink = events
startBluetoothStateMonitoring()
reportCurrentBluetoothState()
}
override fun onCancel(arguments: Any?) {
eventSink = null
bluetoothHelper.stopBluetoothStateMonitoring()
}
})
}
override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {
when (call.method) {
"getConnectedDevices" -> {
getConnectedDevices(result)
}
"isBluetoothEnabled" -> {
result.success(bluetoothHelper.isBluetoothEnabled())
}
"hasBluetoothPermission" -> {
result.success(bluetoothHelper.hasBluetoothPermission())
}
else -> {
result.notImplemented()
}
}
}
/**
* 获取所有已连接的蓝牙设备
*/
private fun getConnectedDevices(result: Result) {
if (!bluetoothHelper.isBluetoothEnabled()) {
result.success(emptyList<Map<String, String>>())
return
}
// 使用HEADSET配置文件获取设备
bluetoothHelper.getConnectedBluetoothDevices(BluetoothProfile.HEADSET, object : ClassicBluetoothHelper.BluetoothDevicesCallback {
override fun onSuccess(devices: List<Map<String, String>>) {
result.success(devices)
}
override fun onError(code: String, message: String) {
result.error(code, message, null)
}
})
}
override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {
channel.setMethodCallHandler(null)
eventChannel.setStreamHandler(null)
// 确保停止蓝牙状态监听
bluetoothHelper.stopBluetoothStateMonitoring()
bluetoothHelper.dispose()
}
/**
* 报告当前蓝牙状态
*/
private fun reportCurrentBluetoothState() {
try {
val adapter = BluetoothAdapter.getDefaultAdapter()
if (adapter != null) {
val stateString = when (adapter.state) {
BluetoothAdapter.STATE_ON -> "on"
BluetoothAdapter.STATE_OFF -> "off"
BluetoothAdapter.STATE_TURNING_ON -> "turningOn"
BluetoothAdapter.STATE_TURNING_OFF -> "turningOff"
else -> "unknown"
}
// 发送当前状态
val eventMap = mapOf(
"type" to "stateChanged",
"state" to stateString,
"timestamp" to System.currentTimeMillis()
)
sendEvent(eventMap)
}
} catch (e: Exception) {
FileLogger.e(TAG, "蓝牙状态获取失败", e)
}
}
/**
* 开始监听蓝牙状态变化
*/
private fun startBluetoothStateMonitoring() {
bluetoothHelper.startBluetoothStateMonitoring(object : ClassicBluetoothHelper.BluetoothStateCallback {
override fun onBluetoothStateChanged(state: ClassicBluetoothHelper.BluetoothState) {
val stateString = when (state) {
ClassicBluetoothHelper.BluetoothState.ON -> "on"
ClassicBluetoothHelper.BluetoothState.OFF -> "off"
ClassicBluetoothHelper.BluetoothState.TURNING_ON -> "turningOn"
ClassicBluetoothHelper.BluetoothState.TURNING_OFF -> "turningOff"
ClassicBluetoothHelper.BluetoothState.UNKNOWN -> "unknown"
}
// 发送状态变化事件
val eventMap = mapOf(
"type" to "stateChanged",
"state" to stateString,
"timestamp" to System.currentTimeMillis()
)
sendEvent(eventMap)
}
override fun onDeviceConnected(device: Map<String, String>) {
FileLogger.d(TAG, "设备连接: ${device["name"]} (${device["address"]})")
// 发送设备连接事件
val eventMap = mapOf(
"type" to "deviceConnected",
"device" to mapOf(
"name" to (device["name"] ?: "未知设备"),
"address" to (device["address"] ?: "")
),
"timestamp" to System.currentTimeMillis()
)
sendEvent(eventMap)
}
override fun onDeviceDisconnected(device: Map<String, String>) {
FileLogger.d(TAG, "设备断开: ${device["name"]} (${device["address"]})")
// 发送设备断开事件
val eventMap = mapOf(
"type" to "deviceDisconnected",
"device" to mapOf(
"name" to (device["name"] ?: "未知设备"),
"address" to (device["address"] ?: "")
),
"timestamp" to System.currentTimeMillis()
)
sendEvent(eventMap)
}
})
}
/**
* 发送事件到Flutter端
*/
private fun sendEvent(event: Map<String, Any>) {
try {
eventSink?.success(event)
} catch (e: Exception) {
FileLogger.e(TAG, "事件发送失败", e)
}
}
}

24
android/app/src/main/kotlin/com/yunqiinnovation/deepsound/MainActivity.kt

@ -1,5 +1,8 @@
package com.yunqiinnovation.deepsound
import android.app.PendingIntent
import android.bluetooth.le.ScanFilter
import android.bluetooth.le.ScanSettings
import android.content.Intent
import android.os.Build
import android.os.Bundle
@ -13,7 +16,6 @@ import com.yunqiinnovation.deepvoice_utils.FileLogger
import com.yunqiinnovation.ble_service.BleForegroundService
import com.yunqiinnovation.ble_service.BleManagerWrapper
class MainActivity: FlutterActivity() {
private val TAG = "MainActivity"
@ -49,13 +51,10 @@ class MainActivity: FlutterActivity() {
// 初始化FileLogger
FileLogger.init(applicationContext)
// 初始化ble_service后台唤醒功能
// initBleBackgroundScan()
// 加载配置
// loadConfigFromSecureStorage()
// 初始化BLE后台唤醒功能
initBleBackgroundScan()
FileLogger.d(TAG, "MainActivity.onCreate 已初始化")
FileLogger.d(TAG, "初始化完成")
}
/**
@ -63,23 +62,20 @@ class MainActivity: FlutterActivity() {
*/
private fun loadConfigFromSecureStorage() {
val data = Utils.loadAllSecureData(applicationContext)
// 将所有数据加载到全局配置字典中
configMap.clear()
configMap.putAll(data)
FileLogger.d(TAG, "已从安全存储加载配置")
}
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
GeneratedPluginRegistrant.registerWith(flutterEngine)
FileLogger.d(TAG, "Flutter 引擎配置完成")
// 注册经典蓝牙插件
flutterEngine.plugins.add(ClassicBluetoothPlugin())
}
override fun onDestroy() {
FileLogger.d(TAG, "onDestroy")
super.onDestroy()
}
@ -101,7 +97,7 @@ class MainActivity: FlutterActivity() {
FileLogger.d(TAG, "成功启动BLE后台唤醒功能")
} catch (e: Exception) {
FileLogger.e(TAG, "启动BLE后台唤醒功能失败", e)
FileLogger.e(TAG, "BLE初始化失败", e)
}
}
}

17
lib/core/bindings/initial_binding.dart

@ -39,20 +39,19 @@ class InitialBinding extends Bindings {
// 火山AI服务
Get.lazyPut<VolcanoAIService>(() => VolcanoAIService(), fenix: true);
// 经典蓝牙服务
Get.lazyPut<ClassicBluetoothService>(() => ClassicBluetoothService(),
fenix: true);
// 经典蓝牙服务 - 立即初始化以监听蓝牙状态
Get.lazyPut<ClassicBluetoothService>(() =>ClassicBluetoothService(), fenix: true);
// 蓝牙媒体按钮服务
Get.lazyPut<BluetoothMediaButtonService>(
() => BluetoothMediaButtonService(),
fenix: true);
// Get.lazyPut<BluetoothMediaButtonService>(
// () => BluetoothMediaButtonService(),
// fenix: true);
// 语音交互服务
Get.lazyPut<VoiceInteractionService>(() => VoiceInteractionService(),
fenix: true);
// // 语音交互服务
// Get.lazyPut<VoiceInteractionService>(() => VoiceInteractionService(),
// fenix: true);
// 杰理蓝牙服务

214
lib/data/services/classic_bluetooth_service.dart

@ -11,23 +11,32 @@ class BluetoothDevice {
final String address;
final String type;
BluetoothDevice(
{required this.name, required this.address, required this.type});
BluetoothDevice({
required this.name,
required this.address,
required this.type
});
factory BluetoothDevice.fromMap(Map<dynamic, dynamic> map) {
return BluetoothDevice(
name: map['name'] ?? '未知设备',
address: map['address'] ?? '',
type: map['type'] ?? 'unknown',
);
}
@override
String toString() =>
'BluetoothDevice(name: $name, address: $address, type: $type)';
String toString() => 'BluetoothDevice(name: $name, address: $address, type: $type)';
}
/// 经典蓝牙服务
/// 负责经典蓝牙设备的连接状态管理
class ClassicBluetoothService extends GetxService {
static ClassicBluetoothService get to => Get.find<ClassicBluetoothService>();
// 通道名称
final MethodChannel _channel =
const MethodChannel('com.deep_voice.classic_bluetooth');
final EventChannel _eventChannel =
const EventChannel('com.deep_voice.classic_bluetooth_events');
final _methodChannel = const MethodChannel('com.yunqiinnovation.classic_bluetooth/methods');
final _eventChannel = const EventChannel('com.yunqiinnovation.classic_bluetooth/events');
/// 当前连接的设备
final currentDevice = Rx<BluetoothDevice?>(null);
@ -35,19 +44,19 @@ class ClassicBluetoothService extends GetxService {
/// 已连接耳机设备列表
final connectedDevices = <BluetoothDevice>[].obs;
/// 蓝牙设备状态监听
StreamSubscription? _bluetoothStateListener;
/// 蓝牙事件监听
StreamSubscription? _eventSubscription;
/// 蓝牙开关状态
final bluetoothState = 'unknown'.obs;
/// 蓝牙是否可用
bool get isBluetoothOn => bluetoothState.value == 'on';
bool get isBluetoothEnabled => bluetoothState.value == 'on';
/// 服务是否已初始化
bool _isInitialized = false;
/// 初始化蓝牙服务 - 单一入口点
/// 初始化蓝牙服务
Future<bool> initialize() async {
if (_isInitialized) {
Logger.info('蓝牙服务已经初始化,跳过');
@ -57,19 +66,22 @@ class ClassicBluetoothService extends GetxService {
try {
Logger.info('正在初始化蓝牙服务...');
// 获取当前蓝牙状态
final bool isEnabled =
await _channel.invokeMethod<bool>('isBluetoothEnabled') ?? false;
// 检查蓝牙是否启用
final bool isEnabled = await _methodChannel.invokeMethod<bool>('isBluetoothEnabled') ?? false;
bluetoothState.value = isEnabled ? 'on' : 'off';
Logger.info('初始蓝牙状态: ${bluetoothState.value}');
await _channel.invokeMethod('initialize');
// 设置事件监听
_setupBluetoothStateListener();
_setupEventListener();
_isInitialized = true;
Logger.info('蓝牙服务初始化成功');
// 如果蓝牙已启用,自动获取已连接设备
if (isEnabled) {
await refreshConnectedDevices();
}
return true;
} catch (e) {
Logger.error('初始化蓝牙服务失败: $e');
@ -77,147 +89,135 @@ class ClassicBluetoothService extends GetxService {
}
}
/// 检查蓝牙权限
Future<bool> checkPermissions() async {
// 检查蓝牙相关权限
Map<Permission, PermissionStatus> statuses = await [
Permission.bluetooth,
Permission.bluetoothConnect,
Permission.bluetoothScan,
Permission.microphone,
].request();
return statuses[Permission.bluetoothConnect] == PermissionStatus.granted;
/// 设置事件监听
void _setupEventListener() {
_eventSubscription?.cancel();
_eventSubscription = _eventChannel.receiveBroadcastStream().listen((event) {
if (event is Map) {
Logger.info('收到蓝牙事件: $event');
// 根据事件类型处理
final type = event['type'] as String?;
if (type == 'stateChanged') {
final state = event['state'] as String?;
if (state != null) {
_handleBluetoothStateChange(state);
}
} else if (type == 'deviceConnected') {
final deviceMap = event['device'] as Map?;
if (deviceMap != null) {
_handleDeviceConnected(deviceMap);
}
} else if (type == 'deviceDisconnected') {
final deviceMap = event['device'] as Map?;
if (deviceMap != null) {
_handleDeviceDisconnected(deviceMap);
}
}
}
}, onError: (e) {
Logger.error('蓝牙事件监听错误: $e');
});
}
/// 刷新已连接的蓝牙耳机设备
/// 刷新已连接的蓝牙设备
Future<bool> refreshConnectedDevices() async {
// 确保服务已初始化
if (!_isInitialized && !(await initialize())) {
return false;
}
// 如果蓝牙未开启,无法获取设备
if (!isBluetoothOn) {
Logger.info('蓝牙未开启,无法获取设备');
// 如果蓝牙未启用,无法获取设备
if (!isBluetoothEnabled) {
Logger.info('蓝牙未启用,无法获取设备');
return false;
}
try {
// 只获取耳机设备
final devices = await getConnectedHeadsetDevices();
// 获取已连接的设备
final devices = await getConnectedDevices();
// 更新设备列表
connectedDevices.value = devices;
// 更新当前设备
if (connectedDevices.isNotEmpty && currentDevice.value == null) {
currentDevice.value = connectedDevices.first;
// 如果列表不为空且当前无选定设备,选择第一个
if (devices.isNotEmpty && currentDevice.value == null) {
currentDevice.value = devices.first;
}
return connectedDevices.isNotEmpty;
return devices.isNotEmpty;
} catch (e) {
Logger.error('刷新已连接设备失败: $e');
return false;
}
}
/// 获取已连接的耳机设备
Future<List<BluetoothDevice>> getConnectedHeadsetDevices() async {
/// 获取已连接的蓝牙设备
Future<List<BluetoothDevice>> getConnectedDevices() async {
try {
// 确保服务已初始化
if (!_isInitialized && !(await initialize())) {
return [];
}
final result = await _channel.invokeMethod('getConnectedHeadsetDevices');
final result = await _methodChannel.invokeMethod('getConnectedDevices');
if (result is List) {
return result
.map((device) => BluetoothDevice(
name: device['name'] ?? '未知设备',
address: device['address'] ?? '',
type: 'headset'))
.toList();
return result.map((device) => BluetoothDevice.fromMap(device)).toList();
}
return [];
} catch (e) {
Logger.error('获取已连接的耳机设备失败: $e');
Logger.error('获取已连接设备失败: $e');
return [];
}
}
/// 设置蓝牙状态监听
void _setupBluetoothStateListener() {
_bluetoothStateListener?.cancel();
_bluetoothStateListener =
_eventChannel.receiveBroadcastStream().listen((event) {
if (event is Map) {
Logger.info('蓝牙状态监听: $event');
final type = event['type'] as String?;
if (type == 'bluetoothStateChanged') {
final state = event['state'] as String?;
if (state != null) {
_handleBluetoothStateChange(state);
}
} else if (type == 'deviceConnected') {
_handleDeviceConnected(event);
} else if (type == 'deviceDisconnected') {
_handleDeviceDisconnected(event);
}
}
}, onError: (error) {
Logger.error('蓝牙状态监听错误: $error');
});
/// 检查蓝牙是否已开启
Future<bool> checkBluetoothEnabled() async {
try {
return await _methodChannel.invokeMethod<bool>('isBluetoothEnabled') ?? false;
} catch (e) {
Logger.error('检查蓝牙状态失败: $e');
return false;
}
}
/// 处理设备连接事件
void _handleDeviceConnected(Map event) {
final deviceMap = event['device'] as Map?;
if (deviceMap != null) {
final device = BluetoothDevice(
name: deviceMap['name'] ?? '未知设备',
address: deviceMap['address'] ?? '',
type: deviceMap['type'] ?? 'unknown');
void _handleDeviceConnected(Map deviceMap) {
final device = BluetoothDevice.fromMap(deviceMap);
// 更新设备列表
if (!connectedDevices.any((d) => d.address == device.address)) {
connectedDevices.add(device);
}
// 更新设备列表
if (!connectedDevices.any((d) => d.address == device.address)) {
connectedDevices.add(device);
}
// 更新当前设备
currentDevice.value = device;
// 更新当前设备
currentDevice.value = device;
Logger.info('设备已连接: ${device.name}');
}
Logger.info('设备已连接: ${device.name}');
}
/// 处理设备断开事件
void _handleDeviceDisconnected(Map event) {
final deviceMap = event['device'] as Map?;
if (deviceMap != null) {
final address = deviceMap['address'] as String?;
if (address != null) {
// 更新设备列表
connectedDevices.removeWhere((d) => d.address == address);
// 如果是当前设备,清除当前设备
if (currentDevice.value?.address == address) {
currentDevice.value =
connectedDevices.isEmpty ? null : connectedDevices.first;
Logger.info('当前设备已断开连接');
}
void _handleDeviceDisconnected(Map deviceMap) {
final address = deviceMap['address'] as String?;
if (address != null) {
// 更新设备列表
connectedDevices.removeWhere((d) => d.address == address);
// 如果是当前设备,清除当前设备
if (currentDevice.value?.address == address) {
currentDevice.value = connectedDevices.isEmpty ? null : connectedDevices.first;
Logger.info('当前设备已断开连接');
}
}
}
/// 处理蓝牙状态变化
void _handleBluetoothStateChange(String state) {
Logger.info('----蓝牙状态变化: $state');
Logger.info('蓝牙状态变化: $state');
// 更新蓝牙状态
bluetoothState.value = state;
@ -227,7 +227,7 @@ class ClassicBluetoothService extends GetxService {
connectedDevices.clear();
currentDevice.value = null;
} else if (state == 'on') {
// 蓝牙重新打开,延迟一小段时间后刷新设备
// 蓝牙重新开启,延迟一小段时间后刷新设备
Future.delayed(const Duration(milliseconds: 1000), () {
refreshConnectedDevices();
});
@ -236,8 +236,8 @@ class ClassicBluetoothService extends GetxService {
@override
void onClose() {
// 取消蓝牙状态监听
_bluetoothStateListener?.cancel();
// 取消事件监听
_eventSubscription?.cancel();
super.onClose();
}
}

253
lib/modules/home/controllers/home_controller.dart

@ -2,7 +2,6 @@ import 'package:get/get.dart';
import 'dart:async';
import '../../../routes/app_routes.dart';
import '../../../data/services/classic_bluetooth_service.dart';
import '../../../data/services/bluetooth_media_button_service.dart';
import '../../../core/utils/logger.dart';
import 'package:flutter/material.dart';
import '../../explore/controllers/explore_controller.dart';
@ -20,17 +19,7 @@ class HomeController extends GetxController {
final ExploreController exploreController = Get.find<ExploreController>();
// 经典蓝牙服务
late ClassicBluetoothService _bluetoothService;
// 蓝牙媒体按钮服务
late BluetoothMediaButtonService _mediaButtonService;
// 媒体按钮订阅
StreamSubscription<MediaButtonEvent>? _mediaButtonSubscription;
// 设备连接状态
final _isConnected = false.obs;
bool get isConnected => _isConnected.value;
final ClassicBluetoothService _bluetoothService = Get.find<ClassicBluetoothService>();
// 连接状态
final _connectionStatus = ConnectionStatus.disconnected.obs;
@ -43,201 +32,120 @@ class HomeController extends GetxController {
// 固件版本(模拟数据)
final String firmwareVersion = 'v2.4.1';
// 蓝牙设备信息
final _deviceInfo = <String, dynamic>{}.obs;
Map<String, dynamic> get deviceInfo => _deviceInfo.value;
// 连接错误信息
final _connectionErrorMsg = ''.obs;
String get connectionErrorMsg => _connectionErrorMsg.value;
// 当前连接的经典蓝牙设备
List<BluetoothDevice> get connectedClassicDevices =>
_bluetoothService.connectedDevices;
// 蓝牙状态
// 从蓝牙服务中直接获取的属性
Rx<BluetoothDevice?> get currentDevice => _bluetoothService.currentDevice;
List<BluetoothDevice> get connectedDevices => _bluetoothService.connectedDevices;
RxString get bluetoothState => _bluetoothService.bluetoothState;
// 状态更新标志,防止多次重复更新
bool _isUpdatingStatus = false;
// 蓝牙是否已连接
bool get isConnected => currentDevice.value != null;
@override
void onInit() {
super.onInit();
// 初始化蓝牙服务和状态监控
_initBluetoothService();
// 初始化蓝牙媒体按钮服务
// _initMediaButtonService();
// 电池模拟更新(实际应用中可以通过蓝牙获取)
// 初始化
_initBluetooth();
// 电池模拟更新
_simulateBatteryUpdate();
}
// 初始化蓝牙服务
void _initBluetoothService() {
// 初始化蓝牙
Future<void> _initBluetooth() async {
try {
// 获取经典蓝牙服务实例
_bluetoothService = Get.find<ClassicBluetoothService>();
// 更新当前连接状态
_updateConnectionStatus();
// 确保蓝牙服务已初始化
if (!(await _bluetoothService.initialize())) {
_connectionStatus.value = ConnectionStatus.failed;
_connectionErrorMsg.value = '初始化蓝牙服务失败';
return;
}
// 监听蓝牙连接状态变化
// 设置监听器
_setupBluetoothListeners();
// 更新初始状态
_updateConnectionStatus();
} catch (e) {
Logger.error('初始化蓝牙服务失败: $e');
Logger.error('初始化蓝牙失败: $e');
_connectionStatus.value = ConnectionStatus.failed;
_connectionErrorMsg.value = e.toString();
}
}
// 初始化蓝牙媒体按钮服务
void _initMediaButtonService() {
try {
// 获取媒体按钮服务实例
_mediaButtonService = Get.find<BluetoothMediaButtonService>();
// 初始化媒体按钮服务
_mediaButtonService.initialize().then((success) {
if (success) {
// 启动按钮监听
_mediaButtonService.startButtonListening();
// 设置按钮事件监听
_setupMediaButtonListener();
Logger.info('蓝牙媒体按钮服务初始化成功');
} else {
Logger.warning('蓝牙媒体按钮服务初始化失败');
}
});
} catch (e) {
Logger.error('初始化蓝牙媒体按钮服务失败: $e');
}
}
// 设置媒体按钮事件监听
void _setupMediaButtonListener() {
// 取消之前的订阅
_mediaButtonSubscription?.cancel();
// 设置新的订阅
_mediaButtonSubscription = _mediaButtonService.buttonEvents.listen((event) {
Logger.info('收到媒体按钮事件: $event');
// 处理按钮事件
if (event.buttonType == MediaButtonType.playPause) {}
// 设置蓝牙状态监听
void _setupBluetoothListeners() {
// 监听当前设备变化
ever(currentDevice, (_) {
_updateConnectionStatus();
});
}
// 电池电量模拟更新
void _simulateBatteryUpdate() {
// 每5分钟更新一次电池电量(模拟)
Future.delayed(const Duration(seconds: 2), () {
if (isConnected) {
final timestamp = DateTime.now().millisecondsSinceEpoch;
_deviceBattery.value = 50 + (timestamp % 50); // 50-99的随机值
} else {
_deviceBattery.value = null;
}
// 递归调用实现定时更新
_simulateBatteryUpdate();
// 监听蓝牙状态变化
ever(bluetoothState, (_) {
_updateConnectionStatus();
});
}
// 更新连接状态
void _updateConnectionStatus() {
// 如果正在更新,则跳过
if (_isUpdatingStatus) return;
_isUpdatingStatus = true;
try {
// 检查当前设备
final currentDevice = _bluetoothService.currentDevice.value;
if (currentDevice != null) {
_isConnected.value = true;
_connectionStatus.value = ConnectionStatus.connected;
} else {
_isConnected.value = false;
_connectionStatus.value = ConnectionStatus.disconnected;
}
update(); // 通知UI刷新
} finally {
_isUpdatingStatus = false;
}
}
// 设置蓝牙监听器
void _setupBluetoothListeners() {
// 只监听当前设备和蓝牙状态
// 监听当前设备状态
ever(_bluetoothService.currentDevice, (device) {
if (device != null) {
_isConnected.value = true;
_connectionStatus.value = ConnectionStatus.connected;
} else if (_isConnected.value) {
// 如果之前连接着设备但现在变为null,那么设备已断开
_isConnected.value = false;
_connectionStatus.value = ConnectionStatus.disconnected;
_deviceBattery.value = null;
}
if (bluetoothState.value == 'off') {
_connectionStatus.value = ConnectionStatus.disconnected;
_deviceBattery.value = null;
update();
});
return;
}
// 蓝牙状态变化监听
ever(_bluetoothService.bluetoothState, (state) {
if (state == 'off') {
_isConnected.value = false;
_connectionStatus.value = ConnectionStatus.disconnected;
_deviceBattery.value = null;
update();
}
});
if (currentDevice.value != null) {
_connectionStatus.value = ConnectionStatus.connected;
} else {
_connectionStatus.value = ConnectionStatus.disconnected;
_deviceBattery.value = null;
}
update();
}
// 刷新设备信息
Future<void> refreshDeviceInfo() async {
return;
// 如果正在更新,则跳过
if (_isUpdatingStatus) return;
_isUpdatingStatus = true;
// 刷新设备连接
Future<void> refreshDeviceConnection() async {
try {
// 设置状态为连接中
_connectionStatus.value = ConnectionStatus.connecting;
update();
// 刷新设备列表
final hasDevices = await _bluetoothService.refreshConnectedDevices();
// 更新状态
_updateConnectionStatus();
// 根据结果更新状态
if (!hasDevices) {
Logger.info('刷新设备:未找到设备');
final success = await _bluetoothService.refreshConnectedDevices();
if (!success) {
_connectionErrorMsg.value = '未找到已连接的设备';
} else {
_connectionErrorMsg.value = '';
Logger.info('刷新设备:成功');
}
} catch (e) {
Logger.error('刷新设备信息失败: $e');
_connectionStatus.value = ConnectionStatus.failed;
_connectionErrorMsg.value = 'failedDeviceInformation'.tr; // 无法获取设备信息
update();
Logger.error('刷新设备连接失败: $e');
_connectionErrorMsg.value = '无法刷新设备连接';
} finally {
_isUpdatingStatus = false;
_updateConnectionStatus();
}
}
// 电池电量模拟更新
void _simulateBatteryUpdate() {
Future.delayed(const Duration(seconds: 2), () {
if (isConnected) {
final timestamp = DateTime.now().millisecondsSinceEpoch;
_deviceBattery.value = 50 + (timestamp % 50); // 50-99的随机值
} else {
_deviceBattery.value = null;
}
// 递归调用实现定时更新
_simulateBatteryUpdate();
});
}
// 重新扫描设备
void rescanDevices() {
Get.toNamed(Routes.pairing);
@ -255,7 +163,6 @@ class HomeController extends GetxController {
// 展示AI功能
void exploreAIFeatures() {
// 简单实现,直接跳转到AI聊天页面
Get.toNamed(Routes.chat);
}
@ -277,7 +184,7 @@ class HomeController extends GetxController {
// 获取连接状态对应的文本
String getConnectionStatusText() {
// 如果蓝牙关闭,优先显示蓝牙状态
if (_bluetoothService.bluetoothState.value == 'off') {
if (bluetoothState.value == 'off') {
return 'bluetoothOff'.tr; // 蓝牙已关闭
}
@ -296,13 +203,12 @@ class HomeController extends GetxController {
// 获取设备名称
String getDeviceName() {
final device = _bluetoothService.currentDevice.value;
return device?.name ?? 'unknownEquipment'.tr; // 未知设备
return currentDevice.value?.name ?? 'unknownEquipment'.tr; // 未知设备
}
// 获取信号强度(模拟数据)
String getSignalStrength() {
if (!_isConnected.value) {
if (!isConnected) {
return 'zeroSignal'.tr; // 无信号
}
@ -320,15 +226,6 @@ class HomeController extends GetxController {
// 获取设备ID
String getDeviceId() {
final device = _bluetoothService.currentDevice.value;
return device?.address ?? 'unknown'.tr; // 未知
}
@override
void onClose() {
// 取消按钮事件流订阅
_mediaButtonSubscription?.cancel();
super.onClose();
return currentDevice.value?.address ?? 'unknown'.tr; // 未知
}
}

2
lib/modules/home/views/home_view.dart

@ -244,7 +244,7 @@ class HomeView extends GetView<HomeController> {
// 添加刷新按钮
SizedBox(width: 8.w),
InkWell(
onTap: controller.refreshDeviceInfo,
onTap: () => controller.refreshDeviceConnection(),
child: Icon(
Icons.refresh,
size: 16.sp,

274
lib/modules/pairing/controllers/pairing_controller.dart

@ -1,75 +1,143 @@
import 'package:app_settings/app_settings.dart';
import 'package:get/get.dart';
import 'dart:async';
import 'package:permission_handler/permission_handler.dart';
import '../../../routes/app_routes.dart';
import '../../../data/services/classic_bluetooth_service.dart';
import '../../../core/utils/logger.dart';
import 'dart:io';
import '../../../data/services/voice_interaction_service.dart';
/// 简化的配对控制器,仅用于显示已连接经典蓝牙设备和处理基本配对流程
/// 配对控制器,负责显示已连接经典蓝牙设备和处理基本配对流程
class PairingController extends GetxController {
static PairingController get to => Get.find();
// 经典蓝牙服务
final ClassicBluetoothService _bluetoothService =
Get.find<ClassicBluetoothService>();
// 是否开始搜索
final _isSearching = false.obs;
bool get isSearching => _isSearching.value;
final ClassicBluetoothService _bluetoothService = Get.find<ClassicBluetoothService>();
// 设备连接状态
final _isConnecting = false.obs;
bool get isConnecting => _isConnecting.value;
// 状态标志
final isLoading = false.obs;
// 权限是否已授予
final _hasPermissions = false.obs;
bool get hasPermissions => _hasPermissions.value;
// 蓝牙服务是否可用
final _isBluetoothServiceAvailable = true.obs;
bool get isBluetoothServiceAvailable => _isBluetoothServiceAvailable.value;
// 当前连接设备
Rx<BluetoothDevice?> get currentDevice => _bluetoothService.currentDevice;
// 连接设备列表
RxList<BluetoothDevice> get connectedDevices =>
_bluetoothService.connectedDevices;
// 蓝牙状态
RxString get bluetoothState => _bluetoothService.bluetoothState;
// 错误信息
final errorMessage = ''.obs;
// 状态描述(用于UI显示)
final statusDescription = 'initializing'.tr.obs; // 正在初始化...
// 加载状态
final isLoading = false.obs;
final statusDescription = 'initializing'.tr.obs;
// 是否正在跳转到主页
bool _isNavigatingToHome = false;
// 当前连接设备和设备列表(直接从服务获取)
Rx<BluetoothDevice?> get currentDevice => _bluetoothService.currentDevice;
RxList<BluetoothDevice> get connectedDevices => _bluetoothService.connectedDevices;
RxString get bluetoothState => _bluetoothService.bluetoothState;
@override
void onInit() {
super.onInit();
_initialize();
}
/// 设置状态变化监听
void _setupStatusListeners() {
if (!_isBluetoothServiceAvailable.value) return;
/// 初始化流程
Future<void> _initialize() async {
try {
isLoading.value = true;
statusDescription.value = 'checkingPermissions'.tr;
// 监听蓝牙开关状态
ever(_bluetoothService.bluetoothState, (state) {
_updateStatusDescription();
// 检查并请求权限
if (!await _checkAndRequestPermissions()) {
statusDescription.value = 'permissionDenied'.tr;
return;
}
statusDescription.value = 'initializing'.tr;
// 初始化蓝牙服务
if (!await _bluetoothService.initialize()) {
Logger.error('蓝牙服务初始化失败');
statusDescription.value = 'bluetoothInitializationFailure'.tr;
_isBluetoothServiceAvailable.value = false;
return;
}
// 设置状态监听
_setupStatusListeners();
// 刷新已连接设备
statusDescription.value = 'searchingDevice'.tr;
await refreshConnectedDevices();
} catch (e) {
Logger.error('初始化配对控制器失败: $e');
statusDescription.value = 'initializationFailure'.tr;
_isBluetoothServiceAvailable.value = false;
} finally {
isLoading.value = false;
if (hasPermissions) {
_updateStatusDescription();
}
}
}
/// 检查并请求必要的蓝牙权限
Future<bool> _checkAndRequestPermissions() async {
Map<Permission, PermissionStatus> statuses;
List<Permission> permissionsToRequest = [];
if (Platform.isAndroid) {
// Android 12+ (SDK 31+) 需要 SCAN 和 CONNECT
// Android 6+ (SDK 23+) 需要 LOCATION
permissionsToRequest.add(Permission.locationWhenInUse); // 基本定位权限
// Android 12及以上版本才需要请求蓝牙扫描和连接权限
if (await _getAndroidSdkVersion() >= 31) {
permissionsToRequest.add(Permission.bluetoothScan);
permissionsToRequest.add(Permission.bluetoothConnect);
}
} else if (Platform.isIOS) {
permissionsToRequest.add(Permission.bluetooth);
}
statuses = await permissionsToRequest.request();
bool allGranted = true;
statuses.forEach((permission, status) {
if (!status.isGranted) {
allGranted = false;
Logger.warning('权限 ${permission.toString()} 未被授予: $status');
}
});
_hasPermissions.value = allGranted;
return allGranted;
}
// 辅助方法获取Android SDK版本
Future<int> _getAndroidSdkVersion() async {
if (Platform.isAndroid) {
// 这里需要原生代码支持来获取确切的SDK版本,
// 或者使用 device_info_plus 插件
// 暂时返回一个较高值以触发新权限请求,如果需要精确控制则需要引入插件
// 或者,如果你的minSdkVersion >= 31,可以直接请求新权限
// 假设minSdkVersion < 31,我们需要一种方式判断当前运行设备的SDK版本
// 为了简化,我们这里先假设需要请求新权限,如果你的目标SDK较低,需要调整
return 31; // 假设运行在需要新权限的设备上
}
return 0;
}
// 监听当前设备状态
ever(_bluetoothService.currentDevice, (device) {
_updateStatusDescription();
/// 设置状态变化监听
void _setupStatusListeners() {
// 监听蓝牙状态变化
ever(bluetoothState, (_) => _updateStatusDescription());
// 如果有设备连接,自动跳转到主页
// 监听当前设备变化
ever(currentDevice, (device) {
_updateStatusDescription();
// 当设备连接时自动跳转
if (device != null) {
_autoNavigateToHome();
}
@ -78,104 +146,66 @@ class PairingController extends GetxController {
/// 更新状态描述
void _updateStatusDescription() {
if (_bluetoothService.bluetoothState.value == 'off') {
statusDescription.value = 'bluetoothTurnedOff'.tr;// 蓝牙已关闭, 请打开蓝牙
if (!hasPermissions) {
statusDescription.value = 'permissionDenied'.tr;
return;
}
if (bluetoothState.value == 'off') {
statusDescription.value = 'bluetoothTurnedOff'.tr;
return;
}
if (_bluetoothService.currentDevice.value != null) {
statusDescription.value =
'${'connectedDevice'.tr} ${_bluetoothService.currentDevice.value!.name}';// 已连接到设备:
if (currentDevice.value != null) {
statusDescription.value = '${'connectedDevice'.tr} ${currentDevice.value!.name}';
return;
}
if (_bluetoothService.connectedDevices.isNotEmpty) {
statusDescription.value =
'${'findConnectedDevice'.tr} ${_bluetoothService.connectedDevices.first.name}';// 找到已连接的设备:
if (connectedDevices.isNotEmpty) {
statusDescription.value = '${'findConnectedDevice'.tr} ${connectedDevices.first.name}';
return;
}
if (isLoading.value) {
statusDescription.value = 'searchingDevice'.tr; // 正在搜索设备...;
statusDescription.value = 'searchingDevice'.tr;
return;
}
statusDescription.value = 'notFound'.tr;//未找到已连接的蓝牙设备
}
/// 初始化流程
Future<void> _initialize() async {
try {
isLoading.value = true;
statusDescription.value = 'initializing'.tr; // 正在初始化...;
// 确保蓝牙服务已初始化
if (!await _bluetoothService.initialize()) {
Logger.error('蓝牙服务初始化失败');
statusDescription.value =
'bluetoothInitializationFailure'.tr; // 蓝牙服务初始化失败
_isBluetoothServiceAvailable.value = false;
isLoading.value = false;
return;
}
_setupStatusListeners();
// 检查权限
if (Platform.isAndroid) {
statusDescription.value = 'checkingPermissions'.tr;
final hasPermission = await _bluetoothService.checkPermissions();
if (!hasPermission) {
statusDescription.value = '缺少蓝牙权限';
isLoading.value = false;
return;
}
}
// 刷新设备列表
statusDescription.value = '正在搜索设备...';
await refreshConnectedDevices();
// 更新状态描述
_updateStatusDescription();
} catch (e) {
Logger.error('初始化配对控制器失败: $e');
statusDescription.value = 'initializationFailure'.tr; // 初始化失败
_isBluetoothServiceAvailable.value = false;
} finally {
isLoading.value = false;
}
statusDescription.value = 'notFound'.tr;
}
/// 刷新设备
/// 刷新已连接设备
Future<void> refreshConnectedDevices() async {
if (!_isBluetoothServiceAvailable.value) return;
if (!await _checkAndRequestPermissions()) {
statusDescription.value = 'permissionDenied'.tr;
return;
}
try {
isLoading.value = true;
statusDescription.value = 'searchingDevice'.tr; // 正在搜索设备...
final hasDevices = await _bluetoothService.refreshConnectedDevices();
// 更新状态描述
_updateStatusDescription();
statusDescription.value = 'searchingDevice'.tr;
await _bluetoothService.refreshConnectedDevices();
} catch (e) {
Logger.error('刷新设备失败: $e');
statusDescription.value = 'failedRefreshDevice'.tr; // 刷新设备失败
statusDescription.value = 'failedRefreshDevice'.tr;
} finally {
isLoading.value = false;
if (hasPermissions) {
_updateStatusDescription();
}
}
}
/// 自动跳转到主页
void _autoNavigateToHome() {
// 如果已经在跳转或设备列表为空,则不执行
if (_isNavigatingToHome || _bluetoothService.connectedDevices.isEmpty)
return;
// 设置跳转标志
if (_isNavigatingToHome || currentDevice.value == null) return;
_isNavigatingToHome = true;
Logger.info('已找到连接的设备,自动跳转到主页');
Logger.info('已找到连接的设备 ${currentDevice.value?.name},自动跳转到主页');
Future.delayed(const Duration(milliseconds: 500), () {
Get.offAllNamed(Routes.home);
});
@ -184,21 +214,27 @@ class PairingController extends GetxController {
/// 打开系统蓝牙设置
void openBluetoothSettings() {
try {
// 使用app_settings插件打开蓝牙设置
AppSettings.openAppSettings(type: AppSettingsType.bluetooth);
if (!hasPermissions) {
openAppSettings();
} else if (Platform.isAndroid && bluetoothState.value == 'off') {
AppSettings.openAppSettings(type: AppSettingsType.bluetooth);
} else if (Platform.isIOS) {
openAppSettings();
} else {
AppSettings.openAppSettings(type: AppSettingsType.bluetooth);
}
} catch (e) {
Logger.error('打开蓝牙设置失败: $e');
Logger.error('打开设置失败: $e');
}
}
/// 刷新设备(兼容旧代码)
Future<void> refreshDevices() async {
await refreshConnectedDevices();
}
/// 打开应用设置
Future<void> openAppSettings() async {
await AppSettings.openAppSettings();
}
/// 跳过连接
void skipConnecting() {
// 设置跳转标志防止自动跳转再次触发
_isNavigatingToHome = true;
Get.offAllNamed(Routes.home);
}

2
local_plugins/agent_service/android/build.gradle.kts

@ -37,6 +37,6 @@ dependencies {
// 依赖本地其他插件
add("compileOnly", project(":azure_speech"))
add("compileOnly", project(":open_ai_service"))
add("compileOnly", project(":common_utils"))
add("compileOnly", project(":deepvoice_utils"))
add("compileOnly", project(":chat_storage"))
}

4
local_plugins/agent_service/android/settings.gradle.kts

@ -3,11 +3,11 @@ rootProject.name = "agent_service"
// 包含项目依赖
include(":azure_speech")
include(":open_ai_service")
include(":common_utils")
include(":deepvoice_utils")
include(":chat_storage")
// 设置项目路径
project(":azure_speech").projectDir = file("../../azure_speech/android")
project(":open_ai_service").projectDir = file("../../open_ai_service/android")
project(":common_utils").projectDir = file("../../common_utils/android")
project(":deepvoice_utils").projectDir = file("../../deepvoice_utils/android")
project(":chat_storage").projectDir = file("../../chat_storage/android")

4
local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt

@ -484,7 +484,7 @@ class AgentService(
* 保存聊天消息
*/
private fun saveChatMessage(userMessage: String, assistantMessage: String) {
CoroutineScope(Dispatchers.IO).launch {
launch(Dispatchers.IO) {
try {
// 保存用户消息
val userMessageId = chatStorageHelper.saveMessage(
@ -519,7 +519,7 @@ class AgentService(
* 清除聊天历史
*/
fun clearChatHistory(callback: (Boolean) -> Unit) {
CoroutineScope(Dispatchers.IO).launch {
launch(Dispatchers.IO) {
try {
// 清除指定会话的所有消息
val success = chatStorageHelper.deleteMessages(sessionId)

4
local_plugins/agent_service/pubspec.yaml

@ -15,8 +15,8 @@ dependencies:
path: ../open_ai_service
azure_speech:
path: ../azure_speech
common_utils:
path: ../common_utils
deepvoice_utils:
path: ../deepvoice_utils
chat_storage:
path: ../chat_storage

2
local_plugins/ble_service/android/build.gradle.kts

@ -10,7 +10,7 @@ plugins {
android {
namespace = "com.yunqiinnovation.ble_service"
compileSdk = 34
compileSdk = 35
defaultConfig {
minSdk = 26

1
local_plugins/ble_service/android/settings.gradle.kts

@ -0,0 +1 @@
rootProject.name = "ble_service"

3
local_plugins/ble_service/android/src/main/AndroidManifest.xml

@ -27,7 +27,8 @@
<!-- 被系统 PendingIntent 触发的 Receiver -->
<receiver
android:name=".BleWakeReceiver"
android:exported="false">
android:exported="true"
android:enabled="true">
<intent-filter>
<action android:name="com.yunqiinnovation.ble_service.ACTION_BLE_SCAN" />
</intent-filter>

12
local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleConst.kt

@ -8,13 +8,19 @@ import java.util.*
object BleConst {
// ================== 协议相关UUID ==================
/** 主服务UUID - 文档中定义为0000ABC0-0000-1111-2222-123456789ABC */
val PRIMARY_SERVICE_UUID: UUID = UUID.fromString("0000fd7b-0000-1000-8000-00805f9b34fb")
val PRIMARY_SERVICE_UUID: UUID = UUID.fromString("0000abc0-0000-1111-2222-123456789abc")
/** 音频服务UUID - 文档中定义为00001801-0000-1000-8000-00805f9b34fb */
val AUDIO_SERVICE_UUID: UUID = UUID.fromString("0000ae00-0000-1000-8000-00805f9b34fb")
/** 接收音频特征UUID - 文档中定义为0000ABC1-0000-1111-2222-123456789ABC */
val RECEIVE_AUDIO_CHAR_UUID: UUID = UUID.fromString("0000ae02-0000-1000-8000-00805f9b34fb")
/** 写入特征UUID - 文档中定义为0000ABC1-0000-1111-2222-123456789ABC */
val WRITE_CHAR_UUID: UUID = UUID.fromString("0000fd7b-0000-1000-8000-00805f9b34fb")
val WRITE_CHAR_UUID: UUID = UUID.fromString("0000abc1-0000-1111-2222-123456789abc")
/** 通知特征UUID - 文档中定义为0000ABC2-0000-1111-2222-123456789ABC */
val NOTIFY_CHAR_UUID: UUID = UUID.fromString("0000fd7b-0000-1000-8000-00805f9b34fb")
val NOTIFY_CHAR_UUID: UUID = UUID.fromString("0000abc2-0000-1111-2222-123456789abc")
/** 客户端特征配置描述符UUID */
val CLIENT_CHAR_CONFIG_UUID: UUID = UUID.fromString("0000fd7b-0000-1000-8000-00805f9b34fb")

376
local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleForegroundService.kt

@ -1,311 +1,227 @@
package com.yunqiinnovation.ble_service
import android.app.*
import android.bluetooth.BluetoothManager
import android.content.Intent
import android.content.pm.ServiceInfo
import android.os.Build
import android.os.IBinder
import android.os.Handler
import android.os.Looper
import androidx.annotation.RequiresApi
import androidx.core.app.NotificationCompat
import com.yunqiinnovation.deepvoice_utils.FileLogger
import android.content.Context
import android.content.SharedPreferences
import kotlinx.coroutines.*
/**
* BLE前台服务
* 1. 在onCreate中启动前台服务并注册后台扫描
* 2. 在onStartCommand中处理被BleWakeReceiver拉起的重连请求
* 3. 在onDestroy中清理资源
* 4. 自动扫描连接上一次连接的设备
* 5. 响应唤醒命令,自动开启录音功能
* 职责:
* 1. 维持前台服务状态,确保系统不会杀死应用
* 2. 作为BLE被唤醒的服务入口
* 3. 在被唤醒时检查连接状态,如果未连接则使用上次成功的连接重新建立连接
*/
@RequiresApi(26)
class BleForegroundService : Service(), BleService.Callback {
companion object {
private const val TAG = "BleForegroundService"
private const val RECONNECT_DELAY = 5000L
private const val CHANNEL_ID = "ble_service_channel"
private const val NOTIFICATION_ID = 1001
}
private lateinit var bleManager: BleManagerWrapper
private val TAG = "BleForegroundService"
private val handler = Handler(Looper.getMainLooper())
private lateinit var prefs: SharedPreferences
// 扫描相关
private var isAutoConnecting = false
private var scanRetryCount = 0
private val MAX_SCAN_RETRY = 3
private val SCAN_TIMEOUT = 10000L // 10秒
private val AUTO_CONNECT_DELAY = 2000L // 2秒
// 简化协程作用域:使用SupervisorJob确保一个协程失败不会影响其他协程
private val serviceScope = CoroutineScope(SupervisorJob())
override fun onCreate() {
super.onCreate()
FileLogger.init(this)
FileLogger.i(TAG, "BleForegroundService正在创建...")
// 初始化偏好设置存储
prefs = getSharedPreferences("ble_service_prefs", Context.MODE_PRIVATE)
// 使用全局BleManagerWrapper单例,共享BleService实例
// 初始化BLE管理器
bleManager = BleManagerWrapper.instance.apply {
init(this@BleForegroundService)
addCallback(this@BleForegroundService) // 注册回调
}
// 使用正确的前台服务类型启动
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { // Android 14+
startForeground(
BleConst.FOREGROUND_ID,
buildNotification("BLE服务运行中..."),
ServiceInfo.FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE
)
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { // Android 10-13
startForeground(
BleConst.FOREGROUND_ID,
buildNotification("BLE服务运行中..."),
ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION
)
} else { // 较旧版本
startForeground(BleConst.FOREGROUND_ID, buildNotification("BLE服务运行中..."))
addCallback(this@BleForegroundService)
}
// 启动自动连接流程
startAutoConnect()
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
// 若由Receiver带MAC拉起,则立即重连
val mac = intent?.getStringExtra("mac")
FileLogger.d(TAG, "onStartCommand: $mac")
FileLogger.i(TAG, "服务onStartCommand: ${intent?.action}")
mac?.let {
FileLogger.i(TAG, "收到连接请求,正在连接设备: $it")
// 保存最后连接的设备地址
saveLastConnectedDevice(it)
// 连接设备
bleManager.connect(it)
// 1. 启动前台服务
startForegroundServiceWithNotification()
// 2. 注册后台扫描
registerBackgroundScan()
// 3. 处理连接请求
val macAddress = intent?.getStringExtra("mac")
if (!macAddress.isNullOrEmpty()) {
// 检查当前连接状态,如果未连接则发起连接
serviceScope.launch {
if (!bleManager.isConnected()) {
FileLogger.i(TAG, "开始连接到设备: $macAddress")
connectToDevice(macAddress)
} else {
FileLogger.i(TAG, "设备已连接,忽略连接请求")
}
}
}
return START_STICKY
}
// 简化协程启动,将注册逻辑提取为独立方法
private fun registerBackgroundScan() {
serviceScope.launch {
try {
bleManager.registerBackgroundScan()
FileLogger.i(TAG, "已注册后台扫描")
} catch (e: Exception) {
FileLogger.e(TAG, "注册后台扫描失败", e)
}
}
}
override fun onBind(i: Intent?): IBinder? = null
override fun onDestroy() {
FileLogger.i(TAG, "BleForegroundService正在销毁...")
// 移除回调
bleManager.removeCallback(this)
// 取消所有待处理的操作
handler.removeCallbacksAndMessages(null)
// 清理BLE资源
cleanupBleResources()
// 取消所有协程
serviceScope.cancel()
super.onDestroy()
}
/**
* 启动自动连接流程
*/
private fun startAutoConnect() {
// 如果已经连接则不需要自动连接
if (bleManager.isConnected()) {
FileLogger.i(TAG, "已连接到设备,无需自动连接")
return
}
// 获取上次连接的设备
val lastDevice = getLastConnectedDevice()
if (lastDevice.isNullOrEmpty()) {
FileLogger.i(TAG, "无上次连接设备记录,开始扫描新设备")
startScanForDevices()
return
}
// 尝试连接上次的设备
FileLogger.i(TAG, "尝试连接上次设备: $lastDevice")
bleManager.connect(lastDevice)
// 设置连接超时,如果超时则开始扫描
handler.postDelayed({
if (!bleManager.isConnected()) {
FileLogger.i(TAG, "连接上次设备超时,开始扫描新设备")
startScanForDevices()
// 封装资源清理逻辑
private fun cleanupBleResources() {
serviceScope.launch {
try {
// 关闭蓝牙连接
if (bleManager.isConnected()) {
bleManager.disconnect()
}
// 清理资源
bleManager.cleanup()
FileLogger.i(TAG, "已清理BLE资源")
} catch (e: Exception) {
FileLogger.e(TAG, "清理BLE资源失败", e)
} finally {
// 移除回调
bleManager.removeCallback(this@BleForegroundService)
}
}, AUTO_CONNECT_DELAY)
}
}
/**
* 开始扫描设备
* 启动前台服务
*/
private fun startScanForDevices() {
if (isAutoConnecting) return
isAutoConnecting = true
scanRetryCount = 0
private fun startForegroundServiceWithNotification() {
val notification = createNotification()
FileLogger.i(TAG, "开始扫描BLE设备...")
bleManager.startScan(SCAN_TIMEOUT)
// 根据Android版本使用正确的前台服务类型
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { // Android 14+
startForeground(
NOTIFICATION_ID,
notification,
ServiceInfo.FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE
)
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { // Android 10-13
startForeground(
NOTIFICATION_ID,
notification,
ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION
)
} else { // 较旧版本
startForeground(NOTIFICATION_ID, notification)
}
// 设置扫描超时
handler.postDelayed({
if (isAutoConnecting && scanRetryCount < MAX_SCAN_RETRY) {
scanRetryCount++
FileLogger.i(TAG, "扫描超时,重试 $scanRetryCount/$MAX_SCAN_RETRY")
bleManager.startScan(SCAN_TIMEOUT)
} else if (isAutoConnecting) {
FileLogger.i(TAG, "已达最大重试次数,停止自动连接")
isAutoConnecting = false
}
}, SCAN_TIMEOUT + 1000) // 比扫描超时多1秒
FileLogger.i(TAG, "已启动前台服务")
}
/**
* 保存最后连接的设备
* 创建通知
*/
private fun saveLastConnectedDevice(address: String) {
prefs.edit().putString("last_connected_device", address).apply()
}
/**
* 获取最后连接的设备
*/
private fun getLastConnectedDevice(): String? {
return prefs.getString("last_connected_device", null)
}
private fun createNotification(): Notification {
val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
/** 构建低优先级通知,满足5秒前台规则 */
private fun buildNotification(msg: String): Notification {
val nm = getSystemService(NotificationManager::class.java)
if (nm.getNotificationChannel(BleConst.CHANNEL_ID) == null) {
nm.createNotificationChannel(
NotificationChannel(
BleConst.CHANNEL_ID, "BLE前台", NotificationManager.IMPORTANCE_LOW
)
// Android 8.0+ 需要创建通知渠道
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(
CHANNEL_ID,
"蓝牙连接服务",
NotificationManager.IMPORTANCE_LOW
)
manager.createNotificationChannel(channel)
}
return NotificationCompat.Builder(this, BleConst.CHANNEL_ID)
.setSmallIcon(android.R.drawable.ic_dialog_info)
// 点击通知打开MainActivity
val intent = packageManager.getLaunchIntentForPackage(packageName)?.apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
}
val pendingIntent = if (intent != null) {
PendingIntent.getActivity(
this,
0,
intent,
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
)
} else {
null
}
return NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("DeepSound")
.setContentText(msg)
.setContentText("蓝牙连接服务正在后台运行")
.setSmallIcon(android.R.drawable.ic_dialog_info)
.apply { pendingIntent?.let { setContentIntent(it) } }
.build()
}
/**
* 更新通知内容
* 连接到指定设备
*/
private fun updateNotification(msg: String) {
val nm = getSystemService(NotificationManager::class.java)
nm.notify(BleConst.FOREGROUND_ID, buildNotification(msg))
}
// ============================== BleService.Callback 接口实现 ==============================
override fun onScanResult(devices: List<Map<String, Any>>) {
if (!isAutoConnecting || devices.isEmpty()) return
// 自动连接第一个找到的设备
val device = devices.firstOrNull()
device?.let {
val address = it["id"] as String
val name = it["name"] as String
FileLogger.i(TAG, "扫描到设备: $name ($address),正在自动连接...")
saveLastConnectedDevice(address)
bleManager.connect(address)
// 停止扫描
bleManager.stopScan()
// 延迟一段时间检查连接状态
handler.postDelayed({
if (!bleManager.isConnected()) {
FileLogger.i(TAG, "自动连接失败,继续扫描...")
if (scanRetryCount < MAX_SCAN_RETRY) {
startScanForDevices()
} else {
FileLogger.i(TAG, "已达最大重试次数,停止自动连接")
isAutoConnecting = false
}
}
}, AUTO_CONNECT_DELAY)
private fun connectToDevice(address: String) {
FileLogger.i(TAG, "开始连接设备: ${address.takeLast(4)}...")
serviceScope.launch {
try {
bleManager.connect(address)
FileLogger.i(TAG, "设备连接成功: ${address.takeLast(4)}")
} catch (e: Exception) {
FileLogger.e(TAG, "连接设备失败", e)
}
}
}
// ============================== BleService.Callback 接口实现 ==============================
override fun onConnectionStateChanged(state: Int) {
when (state) {
BleConst.STATE_CONNECTED -> {
FileLogger.i(TAG, "设备已连接")
updateNotification("已连接到BLE设备")
isAutoConnecting = false
// 获取设备信息
// bleManager.getDeviceVersion()
// bleManager.getBatteryInfo()
}
BleConst.STATE_DISCONNECTED -> {
FileLogger.i(TAG, "设备已断开")
updateNotification("BLE设备已断开")
// 如果不是主动断开,尝试重新连接
// if (!isAutoConnecting) {
// handler.postDelayed({
// startAutoConnect()
// }, 3000) // 延迟3秒重连
// }
}
BleConst.STATE_ERROR -> {
FileLogger.e(TAG, "设备连接错误")
updateNotification("BLE设备连接错误")
// 连接错误,尝试重新扫描连接
// if (!isAutoConnecting) {
// handler.postDelayed({
// startAutoConnect()
// }, 5000) // 延迟5秒重连
// }
}
}
}
override fun onWakeupSignalReceived() {
FileLogger.i(TAG, "收到唤醒信号,开启设备录音功能")
// 更新通知
updateNotification("已唤醒,正在录音...")
FileLogger.i(TAG, "收到唤醒信号")
}
override fun onCommandResult(commandType: Int, success: Boolean) {
FileLogger.i(TAG, "命令结果:类型=$commandType, 成功=$success")
}
override fun onProtocolCommandResult(commandType: Int, data: ByteArray?, success: Boolean) {
when (commandType) {
BleConst.CMD_CONTROL_CODEC -> {
if (success) {
FileLogger.i(TAG, "编解码控制命令执行成功")
} else {
FileLogger.e(TAG, "编解码控制命令执行失败")
}
}
}
}
override fun onDeviceInfoReceived(infoType: Int, infoData: String) {
when (infoType) {
BleConst.CMD_GET_VERSION -> {
FileLogger.i(TAG, "设备版本: $infoData")
updateNotification("已连接:版本 $infoData")
}
BleConst.CMD_GET_BATTERY_INFO -> {
val parts = infoData.split("|")
val batteryLevel = parts[0]
val isCharging = parts.getOrNull(1) == "1"
FileLogger.i(TAG, "电池电量: $batteryLevel%, 充电中: $isCharging")
updateNotification("已连接:电量 $batteryLevel%${if (isCharging) "(充电中)" else ""}")
}
}
}
// 以下是必须实现但当前服务不需要处理的回调方法
override fun onDataReceived(data: ByteArray) {}
override fun onScanResult(devices: List<Map<String, Any>>) {}
override fun onAudioDataReceived(data: ByteArray) {}
override fun onProtocolCommandResult(commandType: Int, data: ByteArray?, success: Boolean) {}
override fun onDeviceInfoReceived(infoType: Int, infoData: String) {}
override fun onStatusUpdate(status: Map<String, Any>) {}
}
}

58
local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleManagerWrapper.kt

@ -51,15 +51,6 @@ class BleManagerWrapper private constructor() {
}
}
override fun onDataReceived(data: ByteArray) {
for (callback in registeredCallbacks) {
try {
callback.onDataReceived(data)
} catch (e: Exception) {
FileLogger.e(TAG, "分发数据回调异常", e)
}
}
}
override fun onAudioDataReceived(data: ByteArray) {
for (callback in registeredCallbacks) {
@ -80,16 +71,7 @@ class BleManagerWrapper private constructor() {
}
}
}
override fun onCommandResult(commandType: Int, success: Boolean) {
for (callback in registeredCallbacks) {
try {
callback.onCommandResult(commandType, success)
} catch (e: Exception) {
FileLogger.e(TAG, "分发命令结果回调异常", e)
}
}
}
override fun onProtocolCommandResult(commandType: Int, data: ByteArray?, success: Boolean) {
for (callback in registeredCallbacks) {
@ -136,7 +118,7 @@ class BleManagerWrapper private constructor() {
FileLogger.d(TAG, "BLE服务初始化成功")
}
// 注册后台扫描,确保可被唤醒
bleService?.registerBackgroundScan()
// bleService?.registerBackgroundScan()
} catch (e: Exception) {
FileLogger.e(TAG, "BLE服务初始化失败", e)
@ -178,6 +160,36 @@ class BleManagerWrapper private constructor() {
fun startScan(timeout: Long = 10000): Boolean = bleService?.startScan(timeout) ?: false
fun stopScan(): Boolean = bleService?.stopScan() ?: false
/**
* 注册后台持久扫描,APP被杀死后也能唤醒
* @return 是否注册成功
*/
fun registerBackgroundScan(): Boolean {
try {
bleService?.registerBackgroundScan()
FileLogger.i(TAG, "注册后台持久扫描")
return true
} catch (e: Exception) {
FileLogger.e(TAG, "注册后台持久扫描失败: ${e.message}")
return false
}
}
/**
* 注销后台持久扫描
* @return 是否注销成功
*/
fun unregisterBackgroundScan(): Boolean {
try {
bleService?.unregisterBackgroundScan()
FileLogger.i(TAG, "注销后台持久扫描")
return true
} catch (e: Exception) {
FileLogger.e(TAG, "注销后台持久扫描失败: ${e.message}")
return false
}
}
// 连接相关
fun connect(address: String): Boolean {
bleService?.connect(address)
@ -207,12 +219,6 @@ class BleManagerWrapper private constructor() {
fun volumeUp(): Boolean = bleService?.volumeUp() ?: false
fun volumeDown(): Boolean = bleService?.volumeDown() ?: false
// 数据传输
fun sendAudioData(data: ByteArray?): Boolean {
if (data == null) return false
return bleService?.sendAudioData(data) ?: false
}
// 清理资源
fun cleanup() {
bleService?.cleanup()

434
local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleService.kt

@ -49,12 +49,10 @@ class BleService(private val context: Context) {
fun onConnectionStateChanged(state: Int)
// 数据相关回调
fun onDataReceived(data: ByteArray)
fun onAudioDataReceived(data: ByteArray)
fun onWakeupSignalReceived()
// 命令响应相关回调
fun onCommandResult(commandType: Int, success: Boolean)
fun onProtocolCommandResult(commandType: Int, data: ByteArray?, success: Boolean)
// 设备信息相关回调
@ -67,9 +65,6 @@ class BleService(private val context: Context) {
// ======================================================================================================
// 成员变量
// ======================================================================================================
// 日志与数据存储
private var outputFile: File? = null
private var fos: FileOutputStream? = null
// 蓝牙相关组件
private val bluetoothManager = context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
@ -80,6 +75,7 @@ class BleService(private val context: Context) {
private var bluetoothGatt: BluetoothGatt? = null
private var notifyChar: BluetoothGattCharacteristic? = null
private var writeChar: BluetoothGattCharacteristic? = null
private var audioChar: BluetoothGattCharacteristic? = null
// 扫描相关
private val scanHandler = Handler(Looper.getMainLooper())
@ -114,12 +110,14 @@ class BleService(private val context: Context) {
scanResults.clear()
val filter = ScanFilter.Builder()
.setServiceUuid(ParcelUuid(BleConst.PRIMARY_SERVICE_UUID))
// .setServiceUuid(ParcelUuid(BleConst.PRIMARY_SERVICE_UUID))
.setManufacturerData(0xEFAB, byteArrayOf(0xA0.toByte(), 0x01))
.build()
val settings = ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
.setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES) // 使用ALL_MATCHES获取所有结果
// .setCallbackType(ScanSettings.CALLBACK_TYPE_FIRST_MATCH)
.setCallbackType(ScanSettings.CALLBACK_TYPE_FIRST_MATCH) // 使用ALL_MATCHES获取所有结果
.setReportDelay(0) // 立即报告结果
.build()
@ -283,6 +281,7 @@ class BleService(private val context: Context) {
bluetoothGatt = null
notifyChar = null
writeChar = null
audioChar = null
}
}
@ -321,51 +320,72 @@ class BleService(private val context: Context) {
return
}
val svc = g.getService(BleConst.PRIMARY_SERVICE_UUID)
notifyChar = svc?.getCharacteristic(BleConst.NOTIFY_CHAR_UUID)
writeChar = svc?.getCharacteristic(BleConst.WRITE_CHAR_UUID)
// 获取主服务特征
val primarySvc = g.getService(BleConst.PRIMARY_SERVICE_UUID)
notifyChar = primarySvc?.getCharacteristic(BleConst.NOTIFY_CHAR_UUID)
writeChar = primarySvc?.getCharacteristic(BleConst.WRITE_CHAR_UUID)
// 获取音频服务特征
val audioSvc = g.getService(BleConst.AUDIO_SERVICE_UUID)
audioChar = audioSvc?.getCharacteristic(BleConst.RECEIVE_AUDIO_CHAR_UUID)
if (notifyChar == null || writeChar == null) {
FileLogger.e(TAG, "未找到所需特征")
FileLogger.e(TAG, "未找到主服务所需特征")
updateConnectionState(BleConst.STATE_ERROR)
return
}
writeChar?.writeType = BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE
setupNotifications(g)
// 设置主服务的通知
setupNotifications(g, notifyChar)
// 设置音频服务的通知(如果存在)
if (audioChar != null) {
setupNotifications(g, audioChar)
FileLogger.i(TAG, "音频服务特征找到并设置通知")
} else {
FileLogger.w(TAG, "音频服务特征未找到")
}
}
override fun onCharacteristicChanged(g: BluetoothGatt, c: BluetoothGattCharacteristic) {
val data = c.value ?: return
// 通用数据回调
callback?.onDataReceived(data)
// 判断数据类型
when {
// OPUS音频帧
data.isNotEmpty() && (data[0].toInt() and 0xFF == 0x4B ||
data[0].toInt() and 0xFF == 0x48) -> {
processOpusData(data)
}
// 设备响应 (0xBB开头)
data.isNotEmpty() && data[0] == BleConst.DEVICE_RESPONSE_HEADER -> {
processDeviceResponse(data)
}
// 设备主动上报 (0xCC开头)
data.isNotEmpty() && data[0] == BleConst.DEVICE_NOTIFY_HEADER -> {
processDeviceNotification(data)
// 根据特征UUID区分处理
when (c.uuid) {
// 音频特征数据
BleConst.RECEIVE_AUDIO_CHAR_UUID -> {
processAudioData(data)
}
// 唤醒信号 (0x01开头)
data.isNotEmpty() && data[0].toInt() == 0x01 -> {
FileLogger.i(TAG, "收到唤醒信号")
callback?.onWakeupSignalReceived()
// 接收到唤醒信号,打开编码器
openEncoder()
// 通知特征数据(命令和控制)
BleConst.NOTIFY_CHAR_UUID -> {
// 判断数据类型
when {
// 设备响应 (0xBB开头)
data.isNotEmpty() && data[0] == BleConst.DEVICE_RESPONSE_HEADER -> {
processDeviceResponse(data)
}
// 设备主动上报 (0xCC开头)
data.isNotEmpty() && data[0] == BleConst.DEVICE_NOTIFY_HEADER -> {
processDeviceNotification(data)
}
// 唤醒信号 (0x01开头)
data.isNotEmpty() && data[0].toInt() == 0x01 -> {
FileLogger.i(TAG, "收到唤醒信号")
callback?.onWakeupSignalReceived()
// 接收到唤醒信号,打开编码器
openEncoder()
}
// 其他未知数据
else -> {
FileLogger.d(TAG, "收到未知数据: ${data.joinToString(", ") { "0x%02X".format(it) }}")
}
}
}
// 其他未知数据
// 其他未知特征
else -> {
FileLogger.d(TAG, "收到未知数据: ${data.joinToString(", ") { "0x%02X".format(it) }}")
FileLogger.d(TAG, "收到未知特征数据: ${c.uuid}, 数据=${data.joinToString(", ") { "0x%02X".format(it) }}")
}
}
}
@ -380,174 +400,41 @@ class BleService(private val context: Context) {
g: BluetoothGatt, d: BluetoothGattDescriptor, status: Int
) {
if (status == BluetoothGatt.GATT_SUCCESS)
FileLogger.i(TAG, "通知描述符写入成功")
FileLogger.i(TAG, "通知描述符写入成功: ${d.characteristic.uuid}")
}
}
/**
* 设置通知功能
* 设置特征的通知功能
*/
private fun setupNotifications(g: BluetoothGatt) {
if (notifyChar?.properties?.and(BluetoothGattCharacteristic.PROPERTY_NOTIFY) == 0) return
g.setCharacteristicNotification(notifyChar, true)
val desc = notifyChar!!.getDescriptor(BleConst.CLIENT_CHAR_CONFIG_UUID)
desc.value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
g.writeDescriptor(desc)
}
/**
* 处理OPUS音频数据
*/
private fun processOpusData(data: ByteArray) {
try {
// 保存原始数据到文件
fos?.write(data)
// 判断是否为OPUS格式数据
val isOpus = data.size > 2 &&
(data[0].toInt() and 0xFF == 0x4B ||
data[0].toInt() and 0xFF == 0x48)
if (isOpus) {
// 回调给上层
callback?.onAudioDataReceived(data)
} else {
FileLogger.w(TAG, "收到非OPUS格式数据")
}
} catch (e: Exception) {
FileLogger.e(TAG, "处理OPUS数据异常: ${e.message}", e)
}
}
// ======================================================================================================
// 后台扫描与唤醒
// ======================================================================================================
/**
* 注册后台持久扫描,APP被杀死后也能唤醒
*/
fun registerBackgroundScan() {
val scanner = bluetoothLeScanner ?: return
try {
val filter = ScanFilter.Builder()
.setServiceUuid(ParcelUuid(BleConst.PRIMARY_SERVICE_UUID))
.build()
val settings = ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_LOW_POWER)
.setCallbackType(ScanSettings.CALLBACK_TYPE_FIRST_MATCH)
.build()
val pi = PendingIntent.getBroadcast(
context, 0,
Intent(BleConst.ACTION_SCAN_RESULT)
.setClass(context, BleWakeReceiver::class.java),
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE
)
scanner.startScan(listOf(filter), settings, pi)
FileLogger.i(TAG, "后台持久扫描已注册")
} catch (e: Exception) {
FileLogger.e(TAG, "注册后台扫描失败: ${e.message}", e)
}
}
/**
* 注销后台持久扫描
*/
fun unregisterBackgroundScan() {
val scanner = bluetoothLeScanner ?: return
try {
val pi = PendingIntent.getBroadcast(
context, 0,
Intent(BleConst.ACTION_SCAN_RESULT)
.setClass(context, BleWakeReceiver::class.java),
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE
)
scanner.stopScan(pi)
FileLogger.i(TAG, "后台持久扫描已注销")
} catch (e: Exception) {
FileLogger.e(TAG, "注销后台扫描失败: ${e.message}", e)
private fun setupNotifications(g: BluetoothGatt, characteristic: BluetoothGattCharacteristic?) {
if (characteristic == null || characteristic.properties.and(BluetoothGattCharacteristic.PROPERTY_NOTIFY) == 0) {
FileLogger.w(TAG, "特征不支持通知功能: ${characteristic?.uuid}")
return
}
g.setCharacteristicNotification(characteristic, true)
val desc = characteristic.getDescriptor(BleConst.CLIENT_CHAR_CONFIG_UUID)
desc?.value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
g.writeDescriptor(desc)
}
// ======================================================================================================
// 协议命令实现
// ======================================================================================================
/**
* 发送命令到设备 - 通用方法
* @param cmdType 命令类型
* @param cmdData 命令数据
* @return 是否成功发送
* 处理音频数据
* @param data 音频数据
*/
private fun sendCommand(cmdType: Byte, cmdData: ByteArray = byteArrayOf()): Boolean {
if (!checkConn()) {
FileLogger.e(TAG, "发送命令失败: 设备未连接")
return false
}
private fun processAudioData(data: ByteArray) {
try {
// 计算命令长度
val cmdLength = cmdData.size
// 创建命令包
val cmdPacket = ByteArray(4 + cmdLength) // 帧头 + 命令 + 长度 + 数据 + CRC
// 帧头
cmdPacket[0] = BleConst.APP_REQUEST_HEADER
// 命令类型
cmdPacket[1] = cmdType
// 命令长度
cmdPacket[2] = cmdLength.toByte()
// 填充命令数据(如果有)
if (cmdLength > 0) {
System.arraycopy(cmdData, 0, cmdPacket, 3, cmdLength)
}
// 计算CRC - 对命令类型、长度和数据进行校验
val crcData = ByteArray(2 + cmdLength)
System.arraycopy(cmdPacket, 1, crcData, 0, crcData.size)
val crc = crc8Maxim(crcData, crcData.size)
// 设置CRC
cmdPacket[3 + cmdLength] = crc
// 记录日志
FileLogger.d(TAG, "收到音频数据: ${data.size}字节")
// 记录命令详情
FileLogger.d(TAG, "发送命令: CMD=0x${(cmdType.toInt() and 0xFF).toString(16)}, 长度=$cmdLength, " +
"数据=${cmdData.joinToString(", ") { "0x${(it.toInt() and 0xFF).toString(16)}" }}, " +
"CRC=0x${(crc.toInt() and 0xFF).toString(16)}")
// 发送数据
writeChar!!.value = cmdPacket
return bluetoothGatt?.writeCharacteristic(writeChar) ?: false
// 回调给上层
callback?.onAudioDataReceived(data)
} catch (e: Exception) {
FileLogger.e(TAG, "发送命令异常: ${e.message}", e)
return false
}
}
/**
* 计算CRC8/MAXIM校验值
*/
private fun crc8Maxim(data: ByteArray, length: Int): Byte {
var crc = 0
val polynomial = 0x31
for (i in 0 until length) {
crc = crc xor (data[i].toInt() and 0xFF)
for (j in 0 until 8) {
if (crc and 0x80 != 0) {
crc = ((crc shl 1) xor polynomial) and 0xFF
} else {
crc = (crc shl 1) and 0xFF
}
}
FileLogger.e(TAG, "处理音频数据异常: ${e.message}", e)
}
return crc.toByte()
}
// ======================================================================================================
// 协议解析
// ======================================================================================================
/**
* 处理设备响应数据 - 帧头为0xBB
*/
@ -832,30 +719,6 @@ class BleService(private val context: Context) {
FileLogger.i(TAG, "设备音量减小...")
return sendCommand(BleConst.CMD_VOLUME_DOWN.toByte())
}
/**
* 发送音频数据
* @param data 音频数据
*/
fun sendAudioData(data: ByteArray): Boolean {
if (!checkConn()) {
FileLogger.e(TAG, "发送音频数据失败: 设备未连接")
return false
}
if (writeChar == null) {
FileLogger.e(TAG, "发送音频数据失败: 写特征为空")
return false
}
try {
writeChar?.value = data
return bluetoothGatt?.writeCharacteristic(writeChar) ?: false
} catch (e: Exception) {
FileLogger.e(TAG, "发送音频数据异常: ${e.message}", e)
return false
}
}
// ======================================================================================================
// 工具方法
@ -888,6 +751,72 @@ class BleService(private val context: Context) {
return bluetoothAdapter
}
// ======================================================================================================
// 后台扫描与唤醒
// ======================================================================================================
/**
* 注册后台持久扫描,APP被杀死后也能唤醒
*/
fun registerBackgroundScan() {
val scanner = bluetoothLeScanner ?: return
try {
val filter = ScanFilter.Builder()
// .setServiceUuid(ParcelUuid(BleConst.PRIMARY_SERVICE_UUID))
.setManufacturerData(0xEFAB, byteArrayOf(0xA0.toByte(), 0x01))
.build()
val settings = ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_LOW_POWER)
// .setCallbackType(ScanSettings.CALLBACK_TYPE_FIRST_MATCH)
.setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES)
.build()
// 创建Intent并设置前台接收器标志
val scanIntent = Intent(BleConst.ACTION_SCAN_RESULT)
.setClass(context, BleWakeReceiver::class.java)
.addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
val pi = PendingIntent.getBroadcast(
context, 0, scanIntent,
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE
} else {
PendingIntent.FLAG_UPDATE_CURRENT
}
)
scanner.startScan(listOf(filter), settings, pi)
FileLogger.i(TAG, "后台持久扫描已注册")
} catch (e: Exception) {
FileLogger.e(TAG, "注册后台扫描失败: ${e.message}", e)
}
}
/**
* 注销后台持久扫描
*/
fun unregisterBackgroundScan() {
val scanner = bluetoothLeScanner ?: return
try {
// 创建Intent并设置前台接收器标志
val scanIntent = Intent(BleConst.ACTION_SCAN_RESULT)
.setClass(context, BleWakeReceiver::class.java)
.addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
val pi = PendingIntent.getBroadcast(
context, 0, scanIntent,
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE
} else {
PendingIntent.FLAG_UPDATE_CURRENT
}
)
scanner.stopScan(pi)
FileLogger.i(TAG, "后台持久扫描已注销")
} catch (e: Exception) {
FileLogger.e(TAG, "注销后台扫描失败: ${e.message}", e)
}
}
// ======================================================================================================
// 资源清理
// ======================================================================================================
@ -898,8 +827,81 @@ class BleService(private val context: Context) {
stopScan()
scanHandler.removeCallbacksAndMessages(null)
disconnectGatt()
fos?.close()
fos = null
outputFile = null
}
// ======================================================================================================
// 协议命令实现
// ======================================================================================================
/**
* 发送命令到设备 - 通用方法
* @param cmdType 命令类型
* @param cmdData 命令数据
* @return 是否成功发送
*/
private fun sendCommand(cmdType: Byte, cmdData: ByteArray = byteArrayOf()): Boolean {
if (!checkConn()) {
FileLogger.e(TAG, "发送命令失败: 设备未连接")
return false
}
try {
// 计算命令长度
val cmdLength = cmdData.size
// 创建命令包
val cmdPacket = ByteArray(4 + cmdLength) // 帧头 + 命令 + 长度 + 数据 + CRC
// 帧头
cmdPacket[0] = BleConst.APP_REQUEST_HEADER
// 命令类型
cmdPacket[1] = cmdType
// 命令长度
cmdPacket[2] = cmdLength.toByte()
// 填充命令数据(如果有)
if (cmdLength > 0) {
System.arraycopy(cmdData, 0, cmdPacket, 3, cmdLength)
}
// 计算CRC - 对命令类型、长度和数据进行校验
val crcData = ByteArray(2 + cmdLength)
System.arraycopy(cmdPacket, 1, crcData, 0, crcData.size)
val crc = crc8Maxim(crcData, crcData.size)
// 设置CRC
cmdPacket[3 + cmdLength] = crc
// 记录命令详情
FileLogger.d(TAG, "发送命令: CMD=0x${(cmdType.toInt() and 0xFF).toString(16)}, 长度=$cmdLength, " +
"数据=${cmdData.joinToString(", ") { "0x${(it.toInt() and 0xFF).toString(16)}" }}, " +
"CRC=0x${(crc.toInt() and 0xFF).toString(16)}")
// 发送数据
writeChar!!.value = cmdPacket
return bluetoothGatt?.writeCharacteristic(writeChar) ?: false
} catch (e: Exception) {
FileLogger.e(TAG, "发送命令异常: ${e.message}", e)
return false
}
}
/**
* 计算CRC8/MAXIM校验值
*/
private fun crc8Maxim(data: ByteArray, length: Int): Byte {
var crc = 0
val polynomial = 0x31
for (i in 0 until length) {
crc = crc xor (data[i].toInt() and 0xFF)
for (j in 0 until 8) {
if (crc and 0x80 != 0) {
crc = ((crc shl 1) xor polynomial) and 0xFF
} else {
crc = (crc shl 1) and 0xFF
}
}
}
return crc.toByte()
}
}

19
local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleServicePlugin.kt

@ -319,15 +319,6 @@ class BleServicePlugin : FlutterPlugin, MethodCallHandler, ActivityAware, Plugin
val success = bleManager.volumeDown()
result.success(success)
}
"sendAudioData" -> {
val data = call.argument<ByteArray>("data")
if (data == null) {
result.error("INVALID_ARGUMENT", "Audio data cannot be null", null)
return
}
val success = bleManager.sendAudioData(data)
result.success(success)
}
"cleanup" -> {
bleManager.cleanup()
result.success(true)
@ -378,9 +369,6 @@ class BleServicePlugin : FlutterPlugin, MethodCallHandler, ActivityAware, Plugin
sendStatusUpdate()
}
override fun onDataReceived(data: ByteArray) {
sendEvent(dataEventSink, mapOf("type" to "rawData", "data" to data), "发送原始数据异常")
}
override fun onAudioDataReceived(data: ByteArray) {
sendEvent(dataEventSink, mapOf("type" to "audioData", "data" to data), "发送音频数据异常")
@ -390,13 +378,6 @@ class BleServicePlugin : FlutterPlugin, MethodCallHandler, ActivityAware, Plugin
sendEvent(dataEventSink, mapOf("type" to "wakeup"), "发送唤醒信号异常")
}
override fun onCommandResult(commandType: Int, success: Boolean) {
sendEvent(connectionEventSink, mapOf(
"type" to "commandResult",
"commandType" to commandType,
"success" to success
), "发送命令结果异常")
}
override fun onProtocolCommandResult(commandType: Int, data: ByteArray?, success: Boolean) {
sendEvent(connectionEventSink, mapOf(

7
local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleWakeReceiver.kt

@ -4,7 +4,7 @@ import android.content.*
import android.bluetooth.le.*
import android.os.Build
import com.yunqiinnovation.deepvoice_utils.FileLogger
import android.util.Log
/**
* BLE唤醒接收器
* 当耳机发送含特定UUID广播时,系统会触发此接收器
@ -18,16 +18,19 @@ class BleWakeReceiver : BroadcastReceiver() {
override fun onReceive(ctx: Context, intent: Intent) {
FileLogger.init(ctx)
FileLogger.d(TAG, "onReceive — 收到唤醒广播: action=${intent.action}, ${BleConst.ACTION_SCAN_RESULT}")
if (intent.action != BleConst.ACTION_SCAN_RESULT) return
val list = intent.getParcelableArrayListExtra<ScanResult>(
BluetoothLeScanner.EXTRA_LIST_SCAN_RESULT
) ?: return
)?: return
val mac = list.first().device.address
FileLogger.d(TAG, "被系统唤醒,发现设备 $mac")
// 拉起前台服务并把MAC传过去
val svc = Intent(ctx, BleForegroundService::class.java).putExtra("mac", mac)
svc.action = BleConst.ACTION_SCAN_RESULT
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
ctx.startForegroundService(svc) else ctx.startService(svc)
}

40
local_plugins/classic_bluetooth/android/build.gradle.kts

@ -1,40 +0,0 @@
plugins {
// Android Library 插件
id("com.android.library")
// Kotlin Android 插件
id("org.jetbrains.kotlin.android")
id("com.github.johnrengelman.shadow") version "8.1.1"
}
android {
// 命名空间,对应你插件的包名(需与代码内包名保持一致)
namespace = "com.yunqiinnovation.classic_bluetooth"
// 目标 SDK 版本
compileSdk = 33
defaultConfig {
// 最低 SDK 版本
minSdk = 21
targetSdk = 33
}
// Java 语言级别兼容配置
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
// Kotlin 语言级别
kotlinOptions {
jvmTarget = "11"
}
}
dependencies {
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.8.0")
implementation("androidx.core:core-ktx:1.7.0")
}

1
local_plugins/classic_bluetooth/android/settings.gradle.kts

@ -1 +0,0 @@
rootProject.name = "classic_bluetooth"

17
local_plugins/classic_bluetooth/android/src/main/AndroidManifest.xml

@ -1,17 +0,0 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.yunqiinnovation.classic_bluetooth">
<!-- 蓝牙权限 -->
<uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" android:maxSdkVersion="30" />
<!-- Android 12+蓝牙权限 -->
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" android:usesPermissionFlags="neverForLocation" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<!-- 位置权限(部分Android版本需要) -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-feature android:name="android.hardware.bluetooth" android:required="false" />
<uses-feature android:name="android.hardware.bluetooth_le" android:required="false" />
</manifest>

266
local_plugins/classic_bluetooth/android/src/main/kotlin/com/yunqiinnovation/classic_bluetooth/ClassicBluetoothHelper.kt

@ -1,266 +0,0 @@
package com.yunqiinnovation.classic_bluetooth
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothProfile
import android.content.Context
import android.content.pm.PackageManager
import android.content.BroadcastReceiver
import android.content.Intent
import android.content.IntentFilter
import android.os.Build
import android.Manifest
import androidx.core.content.ContextCompat
import android.util.Log
/**
* 经典蓝牙助手类,集成蓝牙设备管理和状态监听功能
*/
class ClassicBluetoothHelper(private val context: Context) {
private val TAG = "ClassicBluetoothHelper"
// 回调接口用于通知蓝牙事件
private var bluetoothEventListener: BluetoothEventListener? = null
// 蓝牙状态变化广播接收器
private val bluetoothStateReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
when (intent?.action) {
BluetoothDevice.ACTION_ACL_CONNECTED -> {
val device = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE, BluetoothDevice::class.java)
} else {
@Suppress("DEPRECATION")
intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE)
}
if (device != null) {
val deviceInfo = mapOf(
"name" to (device.name ?: "未知设备"),
"address" to device.address
)
log("蓝牙设备已连接: ${deviceInfo["name"]} (${deviceInfo["address"]})")
notifyEvent("deviceConnected", deviceInfo)
}
}
BluetoothDevice.ACTION_ACL_DISCONNECTED -> {
val device = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE, BluetoothDevice::class.java)
} else {
@Suppress("DEPRECATION")
intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE)
}
if (device != null) {
val deviceInfo = mapOf(
"name" to (device.name ?: "未知设备"),
"address" to device.address
)
log("蓝牙设备已断开: ${deviceInfo["name"]} (${deviceInfo["address"]})")
notifyEvent("deviceDisconnected", deviceInfo)
}
}
BluetoothAdapter.ACTION_STATE_CHANGED -> {
val state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR)
when (state) {
BluetoothAdapter.STATE_ON -> {
log("蓝牙已开启")
notifyEvent("bluetoothStateChanged", mapOf("state" to "on"))
}
BluetoothAdapter.STATE_OFF -> {
log("蓝牙已关闭")
notifyEvent("bluetoothStateChanged", mapOf("state" to "off"))
}
}
}
}
}
}
init {
// 在构造函数中注册广播接收器
registerBroadcastReceivers()
}
/**
* 注册蓝牙广播接收器
*/
private fun registerBroadcastReceivers() {
val bluetoothFilter = IntentFilter().apply {
addAction(BluetoothDevice.ACTION_ACL_CONNECTED)
addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED)
addAction(BluetoothAdapter.ACTION_STATE_CHANGED)
}
if (Build.VERSION.SDK_INT >= 34) { // Android 14 (UPSIDE_DOWN_CAKE)
context.registerReceiver(bluetoothStateReceiver, bluetoothFilter, Context.RECEIVER_NOT_EXPORTED)
} else {
context.registerReceiver(bluetoothStateReceiver, bluetoothFilter)
}
log("已注册蓝牙状态广播接收器")
}
/**
* 通知蓝牙事件
*/
private fun notifyEvent(type: String, data: Map<String, Any>) {
val event = mutableMapOf<String, Any>(
"type" to type,
"timestamp" to System.currentTimeMillis()
)
// 添加其他数据
if (type == "deviceConnected" || type == "deviceDisconnected") {
event["device"] = data
} else {
event.putAll(data)
}
// 通知监听器
bluetoothEventListener?.onBluetoothEvent(event)
}
/**
* 设置蓝牙事件监听器
*/
fun setBluetoothEventListener(listener: BluetoothEventListener?) {
this.bluetoothEventListener = listener
}
/**
* 检查蓝牙是否启用
* @return 蓝牙是否启用
*/
fun isBluetoothEnabled(): Boolean {
try {
val bluetoothAdapter = BluetoothAdapter.getDefaultAdapter()
if (bluetoothAdapter == null) {
log("设备不支持蓝牙", true)
return false
}
return bluetoothAdapter.isEnabled
} catch (e: Exception) {
log("检查蓝牙状态失败: ${e.message}", true)
return false
}
}
/**
* 获取所有连接的经典蓝牙设备
* @return 设备信息列表,如果出错则返回空列表
*/
fun getConnectedBluetoothDevices(): List<Map<String, String>> {
if (!hasBluetoothPermission()) {
log("缺少蓝牙权限", true)
return emptyList()
}
val devices = mutableSetOf<BluetoothDevice>()
val bluetoothAdapter = BluetoothAdapter.getDefaultAdapter() ?: return emptyList()
if (!bluetoothAdapter.isEnabled) {
return emptyList()
}
try {
// 获取已配对设备
val bondedDevices = bluetoothAdapter.bondedDevices ?: setOf()
// 只保留已连接的设备
devices.addAll(bondedDevices.filter { device ->
try {
val deviceClass = device.bluetoothClass
val majorDeviceClass = deviceClass.majorDeviceClass
// 检查设备是否为音频设备、穿戴设备等常见外设类型
device.bondState == BluetoothDevice.BOND_BONDED &&
(majorDeviceClass == BluetoothProfile.HEADSET ||
majorDeviceClass == BluetoothProfile.A2DP ||
majorDeviceClass == BluetoothProfile.GATT)
} catch (e: Exception) {
false
}
})
return devices.map { device ->
mapOf(
"name" to (device.name ?: "未知设备"),
"address" to device.address,
"type" to getDeviceTypeName(device)
)
}
} catch (e: Exception) {
log("获取已连接蓝牙设备失败: ${e.message}", true)
return emptyList()
}
}
/**
* 获取设备类型名称
*/
private fun getDeviceTypeName(device: BluetoothDevice): String {
return try {
val deviceClass = device.bluetoothClass
when (deviceClass.majorDeviceClass) {
BluetoothProfile.HEADSET -> "耳机"
BluetoothProfile.A2DP -> "音频设备"
BluetoothProfile.GATT -> "低功耗设备"
else -> "其他设备"
}
} catch (e: Exception) {
"未知类型"
}
}
/**
* 检查是否有蓝牙权限
*/
fun hasBluetoothPermission(): Boolean {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
ContextCompat.checkSelfPermission(
context,
Manifest.permission.BLUETOOTH_CONNECT
) == PackageManager.PERMISSION_GRANTED
} else {
ContextCompat.checkSelfPermission(
context,
Manifest.permission.BLUETOOTH
) == PackageManager.PERMISSION_GRANTED
}
}
/**
* 简单的日志函数
*/
private fun log(message: String, isError: Boolean = false) {
if (isError) {
Log.e(TAG, message)
} else {
Log.d(TAG, message)
}
}
/**
* 蓝牙事件回调接口
*/
interface BluetoothEventListener {
fun onBluetoothEvent(event: Map<String, Any>)
}
/**
* 释放资源
*/
fun dispose() {
try {
context.unregisterReceiver(bluetoothStateReceiver)
log("已注销蓝牙状态广播接收器")
} catch (e: Exception) {
log("注销蓝牙状态广播接收器失败: ${e.message}", true)
}
// 清除监听器
bluetoothEventListener = null
}
}

186
local_plugins/classic_bluetooth/android/src/main/kotlin/com/yunqiinnovation/classic_bluetooth/ClassicBluetoothPlugin.kt

@ -1,186 +0,0 @@
package com.yunqiinnovation.classic_bluetooth
import androidx.annotation.NonNull
import android.Manifest
import android.app.Activity
import android.content.Context
import android.content.pm.PackageManager
import android.os.Build
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.embedding.engine.plugins.activity.ActivityAware
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.plugin.common.MethodChannel.Result
import io.flutter.plugin.common.EventChannel
import io.flutter.plugin.common.EventChannel.EventSink
import io.flutter.plugin.common.PluginRegistry.RequestPermissionsResultListener
/** ClassicBluetoothPlugin */
class ClassicBluetoothPlugin: FlutterPlugin, MethodCallHandler, ActivityAware, RequestPermissionsResultListener, ClassicBluetoothHelper.BluetoothEventListener {
private lateinit var methodChannel : MethodChannel
private lateinit var eventChannel : EventChannel
private var eventSink: EventSink? = null
private lateinit var context: Context
private var activity: Activity? = null
private lateinit var bluetoothHelper: ClassicBluetoothHelper
private val REQUEST_BLUETOOTH_PERMISSIONS = 1003
private var pendingResult: Result? = null
private var pendingMethodCall: MethodCall? = null
override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
context = flutterPluginBinding.applicationContext
// 设置方法通道
methodChannel = MethodChannel(flutterPluginBinding.binaryMessenger, "com.yunqiinnovation.classic_bluetooth/methods")
methodChannel.setMethodCallHandler(this)
// 设置事件通道
eventChannel = EventChannel(flutterPluginBinding.binaryMessenger, "com.yunqiinnovation.classic_bluetooth/events")
eventChannel.setStreamHandler(object : EventChannel.StreamHandler {
override fun onListen(arguments: Any?, events: EventSink?) {
eventSink = events
}
override fun onCancel(arguments: Any?) {
eventSink = null
}
})
// 初始化蓝牙助手
bluetoothHelper = ClassicBluetoothHelper(context)
bluetoothHelper.setBluetoothEventListener(this)
}
override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {
when (call.method) {
"getConnectedDevices" -> {
if (!bluetoothHelper.hasBluetoothPermission()) {
if (activity != null) {
// 保存当前方法调用和结果,以便权限授予后继续执行
pendingMethodCall = call
pendingResult = result
requestBluetoothPermissions()
} else {
result.error("PERMISSION_DENIED", "缺少必要的蓝牙权限,且无法请求权限", null)
}
return
}
// 获取已连接的所有蓝牙设备
val devices = bluetoothHelper.getConnectedBluetoothDevices()
result.success(devices)
}
"isBluetoothEnabled" -> {
val isEnabled = bluetoothHelper.isBluetoothEnabled()
result.success(isEnabled)
}
else -> {
result.notImplemented()
}
}
}
/**
* 请求蓝牙权限
*/
private fun requestBluetoothPermissions() {
if (activity == null) return
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
ActivityCompat.requestPermissions(
activity!!,
arrayOf(
Manifest.permission.BLUETOOTH_SCAN,
Manifest.permission.BLUETOOTH_CONNECT
),
REQUEST_BLUETOOTH_PERMISSIONS
)
} else {
ActivityCompat.requestPermissions(
activity!!,
arrayOf(
Manifest.permission.BLUETOOTH,
Manifest.permission.BLUETOOTH_ADMIN
),
REQUEST_BLUETOOTH_PERMISSIONS
)
}
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray): Boolean {
if (requestCode == REQUEST_BLUETOOTH_PERMISSIONS) {
// 检查是否有足够的权限
val granted = grantResults.isNotEmpty() && grantResults.all { it == PackageManager.PERMISSION_GRANTED }
if (granted) {
// 权限已授予,继续执行挂起的方法调用
pendingMethodCall?.let { call ->
pendingResult?.let { result ->
onMethodCall(call, result)
pendingMethodCall = null
pendingResult = null
}
}
} else {
// 权限被拒绝
pendingResult?.error("PERMISSION_DENIED", "蓝牙权限被拒绝", null)
pendingMethodCall = null
pendingResult = null
}
return true
}
return false
}
override fun onAttachedToActivity(binding: ActivityPluginBinding) {
activity = binding.activity
binding.addRequestPermissionsResultListener(this)
}
override fun onDetachedFromActivityForConfigChanges() {
activity = null
}
override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) {
activity = binding.activity
binding.addRequestPermissionsResultListener(this)
}
override fun onDetachedFromActivity() {
activity = null
}
override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {
methodChannel.setMethodCallHandler(null)
eventChannel.setStreamHandler(null)
// 释放资源
bluetoothHelper.setBluetoothEventListener(null)
bluetoothHelper.dispose()
}
override fun onBluetoothEvent(event: Map<String, Any>) {
if (eventSink == null) return
try {
activity?.runOnUiThread {
try {
eventSink?.success(event)
} catch (e: Exception) {
// 忽略异常
}
}
} catch (e: Exception) {
// 忽略异常
}
}
}

119
local_plugins/classic_bluetooth/lib/classic_bluetooth.dart

@ -1,119 +0,0 @@
import 'dart:async';
import 'package:flutter/services.dart';
/// 经典蓝牙设备信息
class BluetoothDevice {
/// 设备名称
final String name;
/// 设备地址
final String address;
/// 设备类型
final String type;
/// 构造函数
BluetoothDevice({
required this.name,
required this.address,
required this.type,
});
/// 从Map创建设备信息
factory BluetoothDevice.fromMap(Map<String, dynamic> map) {
return BluetoothDevice(
name: map['name'] as String? ?? '未知设备',
address: map['address'] as String? ?? '',
type: map['type'] as String? ?? '未知类型',
);
}
/// 转换为Map
Map<String, dynamic> toMap() {
return {
'name': name,
'address': address,
'type': type,
};
}
@override
String toString() => '$name ($address) [$type]';
}
/// 蓝牙事件
class BluetoothEvent {
/// 事件类型
final String type;
/// 时间戳
final int timestamp;
/// 事件数据
final Map<String, dynamic> data;
/// 构造函数
BluetoothEvent({
required this.type,
required this.timestamp,
required this.data,
});
/// 从Map创建事件
factory BluetoothEvent.fromMap(Map<dynamic, dynamic> map) {
return BluetoothEvent(
type: map['type'] as String,
timestamp: map['timestamp'] as int,
data: Map<String, dynamic>.from(map)
..remove('type')
..remove('timestamp'),
);
}
/// 获取关联的设备(如果有)
BluetoothDevice? get device {
if (data.containsKey('device')) {
return BluetoothDevice.fromMap(Map<String, dynamic>.from(data['device'] as Map));
}
return null;
}
/// 获取蓝牙状态(如果是状态变化事件)
String? get bluetoothState {
if (type == 'bluetoothStateChanged') {
return data['state'] as String?;
}
return null;
}
}
/// 经典蓝牙插件类
class ClassicBluetooth {
static const MethodChannel _methodChannel =
MethodChannel('com.yunqiinnovation.classic_bluetooth/methods');
static const EventChannel _eventChannel =
EventChannel('com.yunqiinnovation.classic_bluetooth/events');
static Stream<BluetoothEvent>? _eventStream;
/// 获取蓝牙事件流
static Stream<BluetoothEvent> get events {
_eventStream ??= _eventChannel.receiveBroadcastStream()
.map<BluetoothEvent>((dynamic event) => BluetoothEvent.fromMap(Map<dynamic, dynamic>.from(event as Map)));
return _eventStream!;
}
/// 获取当前连接的蓝牙设备
static Future<List<BluetoothDevice>> getConnectedDevices() async {
final List<dynamic> devices = await _methodChannel.invokeMethod('getConnectedDevices');
return devices
.map((device) => BluetoothDevice.fromMap(Map<String, dynamic>.from(device as Map)))
.toList();
}
/// 检查蓝牙是否启用
static Future<bool> isBluetoothEnabled() async {
return await _methodChannel.invokeMethod('isBluetoothEnabled');
}
}

23
local_plugins/classic_bluetooth/pubspec.yaml

@ -1,23 +0,0 @@
name: classic_bluetooth
description: 经典蓝牙设备管理插件
version: 1.0.0
homepage: https://yunqiinnovation.com
environment:
sdk: ">=2.12.0 <3.0.0"
flutter: ">=1.20.0"
dependencies:
flutter:
sdk: flutter
dev_dependencies:
flutter_test:
sdk: flutter
flutter:
plugin:
platforms:
android:
package: com.yunqiinnovation.classic_bluetooth
pluginClass: ClassicBluetoothPlugin

BIN
local_plugins/opus/.gradle/8.10/fileHashes/fileHashes.lock

Binary file not shown.

BIN
local_plugins/opus/.gradle/buildOutputCleanup/buildOutputCleanup.lock

Binary file not shown.

2
local_plugins/opus/.gradle/buildOutputCleanup/cache.properties

@ -1,2 +1,2 @@
#Wed Apr 30 15:54:50 IST 2025
#Fri May 02 14:33:07 IST 2025
gradle.version=8.10

6
pubspec.yaml

@ -60,8 +60,6 @@ dependencies:
flutter_facebook_auth: ^7.1.1
flutter_image_compress: ^2.4.0
common_utils: ^2.1.0
deepvoice_utils:
path: local_plugins/deepvoice_utils
# image_cropper: ^9.0.0
dio: ^5.8.0+1
sqflite: ^2.4.2
@ -82,8 +80,8 @@ dependencies:
path: local_plugins/jl_opus
ble_service:
path: local_plugins/ble_service
classic_bluetooth:
path: local_plugins/classic_bluetooth
deepvoice_utils:
path: local_plugins/deepvoice_utils
dev_dependencies:

231
test.json

@ -1,225 +1,16 @@
curl 'https: //ark.cn-beijing.volces.com/api/v3/bots/chat/completions' \
-H "Authorization: Bearer 168deb3d-fd0c-4912-b9f1-aaee5c6743e6" \
-H 'Content-Type: application/json' \
-d '{
"model": "bot-20250405211523-l7c9r",
"stream": true,
"stream_options": {
"include_usage": true
},
"messages": [
{
"role": "system",
"content": "You are a helpful assistant."
},
{
"role": "user",
"content": "Hello!"
}
]
}'
intent={cmp=com.yunqiinnovation.deepsound/com.yunqiinnovation.ble_service.BleForegroundService}
com.yunqiinnovation.deepsound/com.yunqiinnovation.ble_service.BleWakeReceiver filter f42f2c2
# 1. 启动前台 Service (API 26+ 推荐用 start-foreground-service)
adb shell am start-foreground-service \
-n com.yunqiinnovation.deepsound/com.yunqiinnovation.ble_service.BleForegroundService
"amap-amap-sse": {
"url": "https://mcp.amap.com/sse?key=e5fdc9605eabdeb5626f18f5721f343d"
},
"web-search": {
"url": "http://mcp.ideapsound.com:8000/sse"
},
"deapsound-deapsound": {
"url": "https://www.deapsound.com/sse"
}
# 2. 发送广播到 BleWakeReceiver
adb shell am broadcast \
-a com.yunqiinnovation.ble_service.ACTION_BLE_SCAN \
-n com.yunqiinnovation.deepsound/com.yunqiinnovation.ble_service.BleWakeReceiver
curl 'https: //ark.cn-beijing.volces.com/api/v3/bots/chat/completions' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer 168deb3d-fd0c-4912-b9f1-aaee5c6743e6' \
-d '{
"model": "bot-20250405211523-l7c9r",
"stream": true,
"messages": [
{
"role": "assistant",
"content": "你好呀!今天想和我聊些什么呢😄 是生活趣事、学习心得,还是其他好玩的话题?"
},
{
"role": "assistant",
"content": ""
},
{
"role": "assistant",
"content": "你好呀!今天过得怎么样?有什么需要我帮忙的吗?或者只是想聊聊天?😊"
},
{
"role": "assistant",
"content": "明白啦!有什么需要我帮忙的吗?无论是查资料、聊天解闷,还是帮你处理手机上的任务,我随时待命哦~ 😊"
},
{
"role": "assistant",
"content": "好的!随时准备陪你测试,有什么需要尽管说哦~ 😊 你希望测试什么功能呢?"
},
{
"role": "assistant",
"content": "哎呀,听起来你有点小困扰呢!可能是因为网络波动、系统更新或者我偶尔“打瞌睡”了(开玩笑啦~)。不过别担心,我会努力保持最佳状态陪伴你!如果有什么具体问题,随时告诉我,我一定帮你解决得妥妥的!😊\n\n(或者你想测试什么功能?我随时待命哦!)"
},
{
"role": "assistant",
"content": "嘿嘿,我是你的贴心语音助理小语呀!随时为你提供帮助、陪你聊天、解答问题,还能给你加油打气哦~有什么需要尽管告诉我吧! 😊"
},
{
"role": "assistant",
"content": "我是小语呀,那个随时准备和你畅聊、给你排忧解难、陪你度过各种时光的手机全能语音助理😜 怎么啦,是不是刚才没记住呀🧐"
},
{
"role": "assistant",
"content": ""
},
{
"role": "user",
"content": "你好呀,你是谁呀?"
}
],
"temperature": 0.7,
"max_tokens": 2000,
"stream": true,
"tools": [
{
"type": "function",
"function": {
"name": "exit_interaction",
"description": "退出当前语音交互",
"parameters": {
"type": "object",
"properties": {},
"required": []
}
}
},
{
"type": "function",
"function": {
"name": "enter_translation_mode",
"description": "用户请求进入实时翻译模式时,启动实时翻译功能",
"parameters": {
"type": "object",
"properties": {},
"required": []
}
}
},
{
"type": "function",
"function": {
"name": "send_text_message",
"description": "发送短信",
"parameters": {
"type": "object",
"properties": {
"contact": {
"type": "string",
"description": "联系人姓名或电话号码"
},
"message": {
"type": "string",
"description": "短信内容"
}
},
"required": [
"contact",
"message"
]
}
}
},
{
"type": "function",
"function": {
"name": "make_phone_call",
"description": "拨打电话",
"parameters": {
"type": "object",
"properties": {
"contact": {
"type": "string",
"description": "联系人姓名或电话号码"
}
},
"required": [
"contact"
]
}
}
},
{
"type": "function",
"function": {
"name": "open_app",
"description": "当用户请求打开特定应用时,启动相应应用",
"parameters": {
"type": "object",
"properties": {
"app_name": {
"type": "string",
"description": "应用名称"
}
},
"required": [
"app_name"
]
}
}
}
]
}'
adb shell dumpsys package com.yunqiinnovation.deepsound | grep BleWakeReceiver
curl -X POST "https://www.deapsound.com/messages?sessionId=f156e609-6875-420b-b08d-577b7db695f1" \
-H "Accept-Charset: UTF-8" \
-H "Accept: */*" \
-H "User-Agent: ktor-client" \
-H "Content-Type: application/json" \
-d '{
"id": 6,
"method": "tools/call",
"params": {
"name": "tool_weather",
"arguments": {
"city": "上海"
},
"_meta": {},
"method": "tools/call"
},
"jsonrpc": "2.0"
}'
curl -X POST "https://www.deapsound.com/messages?sessionId=91c6e740-9a24-48f1-8c7f-62a2f99d1006" \
-H "Accept-Charset: UTF-8" \
-H "Accept: */*" \
-H "User-Agent: ktor-client" \
-H "Content-Type: application/json" \
-d '{
"method": "notifications/initialized",
"params": {
"method": "notifications/initialized"
},
"jsonrpc": "2.0"
}'
curl -X GET "https://www.deapsound.com/sse" \
-H "Accept: text/event-stream,text/event-stream" \
-H "Cache-Control: no-cache,no-store" \
-H "Accept-Charset: UTF-8" \
-H "User-Agent: ktor-client"{
"app_alive": 270180271,
"build_connection_time": 919,
"disconnect_times": 1,
"is_background": false,
"last_connect_time": 919,
"last_duration": 438,
"payload_sign_time": 0,
"report_time": 1745779540.932181,
adb shell dumpsys package com.yunqiinnovation.deepsound | grep receiver
Loading…
Cancel
Save