Browse Source

Merge branch 'new_dev' of https://github.com/deepcloud2048/deep_voice into new_dev

weicu
lxm 1 year ago
parent
commit
d54ec087e5
  1. 141
      lib/modules/meeting/views/ai_bottom_sheet.dart
  2. 18
      lib/modules/meeting/views/overview_tab.dart
  3. 19
      lib/modules/meeting/views/speech_tab.dart
  4. 18
      lib/modules/meeting/views/summary_tab.dart
  5. 44
      lib/modules/settings/views/settings_view.dart
  6. 33
      local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt
  7. BIN
      local_plugins/agent_service/android/src/main/res/raw/await.mp3
  8. 1
      local_plugins/agent_service/ios/agent_service/Package.swift
  9. 15
      local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleConst.kt
  10. 20
      local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleService.kt
  11. 176
      local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/CustomSseClientTransport.kt
  12. 63
      local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/MCPSubClient.kt
  13. 2
      local_plugins/ota/android/src/main/kotlin/com/example/ota/tool/ota/OTAManager.kt

141
lib/modules/meeting/views/ai_bottom_sheet.dart

@ -0,0 +1,141 @@
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import '../controllers/meeting_details_controller.dart';
class AIBottomSheet extends StatefulWidget {
const AIBottomSheet({super.key});
@override
State<AIBottomSheet> createState() => _AIBottomSheetState();
}
class _AIBottomSheetState extends State<AIBottomSheet> {
final _controller = Get.find<MeetingDetailsController>();
List _dataList = [];
final List _cruxList = [
'添加代办事项',
'提取结论',
'生成关键指标',
];
@override
Widget build(BuildContext context) {
return Container(
height: 1.sh - 50.h,
padding: EdgeInsets.symmetric(horizontal: 12.w),
child: Column(
children: [
Container(
padding: EdgeInsets.symmetric(vertical: 10.w),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
26.horizontalSpace,
Text(
'Ask AI',
style: TextStyle(
fontSize: 16.sp,
color: Colors.black87,
fontWeight: FontWeight.bold,
),
),
GestureDetector(
onTap: () => Get.back(),
child: Container(
width: 26.w,
height: 26.w,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(26.r),
),
child: Icon(
Icons.close,
size: 18.w,
color: Colors.grey[400],
),
),
)
],
),
),
Expanded(
child: _dataList.isNotEmpty ? SizedBox() : _empty(),
),
_inputBox(),
],
),
);
}
Widget _empty() {
return Padding(
padding: EdgeInsets.symmetric(vertical: 10.w),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'今天我能帮您做点什么呢?',
style: TextStyle(
fontSize: 18.sp,
fontWeight: FontWeight.bold,
),
),
Padding(
padding: EdgeInsets.only(top: 10.w, bottom: 30.w),
child: Row(
children: [
Icon(
Icons.audio_file_outlined,
size: 14.w,
color: Colors.grey[500],
),
2.horizontalSpace,
Text(
_controller.meetingData.value.title,
style: TextStyle(
color: Colors.grey[500],
fontSize: 12.sp,
),
),
],
),
),
...List.generate(
_cruxList.length,
(int index) => Container(
width: double.infinity,
padding: EdgeInsets.symmetric(horizontal: 15.w, vertical: 8.w),
margin: EdgeInsets.symmetric(vertical: 5.w),
decoration: BoxDecoration(
color: Colors.grey[100],
borderRadius: BorderRadius.circular(20.r),
border: Border.all(
color: Colors.grey[300]!,
),
),
child: Text(
_cruxList[index],
style: TextStyle(fontSize: 12.sp),
),
),
),
],
),
);
}
Widget _inputBox() {
return Container(
height: 40.w,
padding: EdgeInsets.symmetric(horizontal: 10.w),
decoration: BoxDecoration(),
child: Row(
children: [],
),
);
}
}

18
lib/modules/meeting/views/overview_tab.dart

@ -4,6 +4,7 @@ import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import '../controllers/meeting_details_controller.dart';
import 'ai_bottom_sheet.dart';
import 'generate_bottom_sheet.dart';
class OverviewTab extends StatefulWidget {
@ -94,6 +95,23 @@ class _OverviewTabState extends State<OverviewTab> {
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (_controller.meetingDetails.value.tasktype >= 3)
_button(
Icons.auto_awesome,
'Ask AI',
() {
Get.bottomSheet(
const AIBottomSheet(),
isScrollControlled: true,
backgroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(
top: Radius.circular(12.r),
),
),
);
},
),
if (_controller.meetingDetails.value.tasktype == 0)
SizedBox(
width: 150.w,

19
lib/modules/meeting/views/speech_tab.dart

@ -5,6 +5,7 @@ import 'package:get/get.dart';
import 'package:lottie/lottie.dart';
import '../controllers/meeting_details_controller.dart';
import 'ai_bottom_sheet.dart';
import 'generate_bottom_sheet.dart';
import 'speaker_all_bottom_sheet.dart';
import 'speaker_bottom_sheet.dart';
@ -278,6 +279,24 @@ class SpeechTab extends GetView<MeetingDetailsController> {
);
},
),
if (controller.meetingDetails.value.tasktype >= 2 &&
!controller.isSpeakerText.value)
_button(
Icons.auto_awesome,
'Ask AI',
() {
Get.bottomSheet(
const AIBottomSheet(),
isScrollControlled: true,
backgroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(
top: Radius.circular(12.r),
),
),
);
},
),
if (controller.meetingDetails.value.tasktype == 0)
SizedBox(
width: 150.w,

18
lib/modules/meeting/views/summary_tab.dart

@ -6,6 +6,7 @@ import 'package:get/get.dart';
import '../../../data/services/db/sqflite_api.dart';
import '../controllers/meeting_details_controller.dart';
import 'ai_bottom_sheet.dart';
import 'generate_bottom_sheet.dart';
import 'speaker_all_bottom_sheet.dart';
@ -310,6 +311,23 @@ class _SummaryTabState extends State<SummaryTab> {
);
},
),
if (_controller.meetingDetails.value.tasktype >= 3)
_button(
Icons.auto_awesome,
'Ask AI',
() {
Get.bottomSheet(
const AIBottomSheet(),
isScrollControlled: true,
backgroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(
top: Radius.circular(12.r),
),
),
);
},
),
if (_controller.meetingDetails.value.tasktype == 0)
SizedBox(
width: 150.w,

44
lib/modules/settings/views/settings_view.dart

@ -569,28 +569,28 @@ class SettingsView extends GetView<SettingsController> {
// color: isDarkMode
// ? Colors.white.withOpacity(0.1)
// : Colors.grey[200]),
// // BLE测试
// _buildSimpleNavigationSetting(
// title: 'opus解码测试',
// subtitle: '测试opus解码',
// icon: Icons.bluetooth_searching,
// iconBgColor: isDarkMode
// ? Colors.green[900]!.withOpacity(0.3)
// : Colors.green[100]!,
// iconColor:
// isDarkMode ? Colors.green[300]! : Colors.green[600]!,
// titleColor: isDarkMode ? Colors.white : null,
// subtitleColor: isDarkMode ? Colors.white70 : null,
// onTap: () {
// Get.toNamed(Routes.opusTest);
// },
// isDarkMode: isDarkMode,
// ),
// Divider(
// height: 1,
// color: isDarkMode
// ? Colors.white.withOpacity(0.1)
// : Colors.grey[200]),
// BLE测试
_buildSimpleNavigationSetting(
title: 'opus解码测试',
subtitle: '测试opus解码',
icon: Icons.bluetooth_searching,
iconBgColor: isDarkMode
? Colors.green[900]!.withOpacity(0.3)
: Colors.green[100]!,
iconColor:
isDarkMode ? Colors.green[300]! : Colors.green[600]!,
titleColor: isDarkMode ? Colors.white : null,
subtitleColor: isDarkMode ? Colors.white70 : null,
onTap: () {
Get.toNamed(Routes.opusTest);
},
isDarkMode: isDarkMode,
),
Divider(
height: 1,
color: isDarkMode
? Colors.white.withOpacity(0.1)
: Colors.grey[200]),
// BLE测试
// _buildSimpleNavigationSetting(

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

@ -668,7 +668,7 @@ object AgentService : CoroutineScope {
try {
// 设置状态为正在流式输出
_isAiStreaming.set(true)
audioPlayer?.playAudio(R.raw.await, true,0.3f)
// 使用历史记录作为上下文发送到OpenAI
val responseBuilder = StringBuilder()
var aiMetadata:String = ""
@ -736,6 +736,9 @@ object AgentService : CoroutineScope {
responseBuilder.append(token)
if (speakResponse && broadcast) {
ttsService?.speakStream(token)
if (token.length > 0){
audioPlayer?.stopAudio()
}
}
if (broadcast){
// 发送流式回复token
@ -783,7 +786,7 @@ object AgentService : CoroutineScope {
override fun onFunctionCall(call: JSONObject) {
try {
audioPlayer?.playAudio(R.raw.calling, true)
// audioPlayer?.playAudio(R.raw.calling, true)
val name = call.getString("name")
sendEvent("function_call", mapOf(
"name" to name,
@ -1120,34 +1123,32 @@ object AgentService : CoroutineScope {
/**
* 播放音频资源
* @param resId 资源ID
* @param isLooping 是否循环播放
* @param volume 音量大小,范围0.0-1.0,默认1.0
*/
fun playAudio(resId: Int, isLooping: Boolean = false) {
fun playAudio(resId: Int, isLooping: Boolean = false, volume: Float = 1.0f) {
try {
// 释放之前的资源
release()
// 创建播放器并设置资源
mediaPlayer = MediaPlayer().apply {
// 设置资源
context.resources.openRawResourceFd(resId)?.use { fd ->
setDataSource(fd.fileDescriptor, fd.startOffset, fd.length)
}
// 播放完成后自动释放资源
this.isLooping = isLooping // 设置循环属性
setVolume(volume, volume) // 设置音量(左声道,右声道)
setOnCompletionListener {
release()
}
// 准备并播放
prepare()
if (isLooping) {
start()
} else {
start()
setOnCompletionListener {
if (!isLooping) {
release()
}
}
prepare()
start()
}
} catch (e: Exception) {
Log.e(TAG, "播放音频资源异常: ${e.message}", e)

BIN
local_plugins/agent_service/android/src/main/res/raw/await.mp3

Binary file not shown.

1
local_plugins/agent_service/ios/agent_service/Package.swift

@ -31,6 +31,7 @@ let package = Package(
path: "Sources/agent_service",
resources: [
.copy("Resources/calling.mp3"),
.copy("Resources/await.mp3"),
.copy("Resources/start.mp3"),
.copy("Resources/stop.mp3")
]

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

@ -10,11 +10,18 @@ object BleConst {
/** 主服务UUID - 文档中定义为0000ABC0-0000-1111-2222-123456789ABC */
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("0000ABC0-0001-1111-2222-123456789ABC")
/** 音频服务UUID - 文档中定义为0000ABC0-0001-1111-2222-123456789ABC */
val AUDIO_SERVICE_UUID1: UUID = UUID.fromString("0000ABC0-0001-1111-2222-123456789ABC")
/** 接收音频特征UUID - 文档中定义为0000ABC2-0001-1111-2222-123456789ABC */
val RECEIVE_AUDIO_CHAR_UUID1: UUID = UUID.fromString("0000ABC2-0001-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("0000ABC2-0001-1111-2222-123456789ABC")
/** 接收音频特征UUID - 文档中定义为0000ABC2-0001-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("0000abc1-0000-1111-2222-123456789abc")

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

@ -72,6 +72,7 @@ object BleService {
private var notifyChar: BluetoothGattCharacteristic? = null
private var writeChar: BluetoothGattCharacteristic? = null
private var audioChar: BluetoothGattCharacteristic? = null
private var audioChar1: BluetoothGattCharacteristic? = null
var recordfile: RecordingFile? = null
// 扫描相关
@ -419,6 +420,7 @@ object BleService {
notifyChar = null
writeChar = null
audioChar = null
audioChar1 = null
}
}
@ -491,6 +493,10 @@ object BleService {
val audioSvc = g.getService(BleConst.AUDIO_SERVICE_UUID)
audioChar = audioSvc?.getCharacteristic(BleConst.RECEIVE_AUDIO_CHAR_UUID)
// 获取音频服务1特征
val audioSvc1 = g.getService(BleConst.AUDIO_SERVICE_UUID1)
audioChar1 = audioSvc1?.getCharacteristic(BleConst.RECEIVE_AUDIO_CHAR_UUID1)
if (notifyChar == null || writeChar == null) {
Log.e(TAG, "未找到主服务所需特征")
updateConnectionState(BleConst.STATE_ERROR)
@ -505,6 +511,12 @@ object BleService {
Log.i(TAG, "音频服务特征找到并设置通知")
} else {
Log.w(TAG, "音频服务特征未找到")
}
if (audioChar1 != null) {
setupNotifications(g, audioChar1)
Log.i(TAG, "音频服务特征找到并设置通知")
} else {
Log.w(TAG, "音频服务特征未找到")
}
// 设置主服务的通知
setupNotifications(g, notifyChar)
@ -518,10 +530,16 @@ object BleService {
// 根据特征UUID区分处理
when (c.uuid) {
// 音频特征数据
BleConst.RECEIVE_AUDIO_CHAR_UUID1 -> {
Log.i(TAG, "RECEIVE_AUDIO_CHAR_UUID1")
processAudioData(data)
}
// 音频特征数据
BleConst.RECEIVE_AUDIO_CHAR_UUID -> {
// Log.i(TAG, "收到音频特征数据")
Log.i(TAG, "RECEIVE_AUDIO_CHAR_UUID")
processAudioData(data)
}
// 通知特征数据(命令和控制)

176
local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/CustomSseClientTransport.kt

@ -25,6 +25,7 @@ class CustomSseClientTransport(
private val urlString: String?,
private val reconnectionTime: Duration? = null,
private val requestBuilder: HttpRequestBuilder.() -> Unit = {},
private val onConnectionLost: (() -> Unit)? = null
) : AbstractTransport() {
private val TAG = "CustomSseClientTransport"
@ -35,8 +36,10 @@ class CustomSseClientTransport(
private val initialized = AtomicBoolean(false)
private var session: ClientSSESession by Delegates.notNull()
private val endpoint = CompletableDeferred<String>()
private val isConnected = AtomicBoolean(false)
private var job: Job? = null
private var connectionMonitorJob: Job? = null
// 创建JSON解析器
private val json = Json {
@ -95,74 +98,118 @@ class CustomSseClientTransport(
*/
private suspend fun collectEvents() {
job = scope.launch(CoroutineName("CustomSseMcpClientTransport.collect#${hashCode()}")) {
session.incoming.collect { event ->
when (event.event) {
"error" -> {
val e = IllegalStateException("SSE error: ${event.data}")
Log.e(TAG, "SSE错误: ${event.data}")
_onError(e)
throw e
}
try {
session.incoming.collect { event ->
when (event.event) {
"error" -> {
Log.e(TAG, "SSE错误: ${event.data}")
isConnected.set(false)
val exception = Exception("SSE Error: ${event.data}")
_onError(exception)
onConnectionLost?.invoke()
throw exception
}
"open" -> {
// SSE连接已打开
}
"ping" -> {
// 心跳
}
"endpoint" -> {
try {
val eventData = event.data ?: ""
"open" -> {
// SSE连接已打开
Log.d(TAG, "SSE连接已打开")
isConnected.set(true)
}
"ping" -> {
// 心跳
}
"endpoint" -> {
try {
val eventData = event.data ?: ""
// 构建完整的端点URL
val fullEndpoint = if (eventData.contains(hostPart)) {
eventData
} else if (eventData.startsWith("/")) {
"$hostPart$eventData"
} else {
eventData
}
// 添加查询参数
val endpointWithParams = if (queryParams.isNotEmpty()) {
if (fullEndpoint.contains("?")) {
val queryString = queryParams.entries.joinToString("&") { "${it.key}=${it.value}" }
"$fullEndpoint&$queryString"
// 构建完整的端点URL
val fullEndpoint = if (eventData.contains(hostPart)) {
eventData
} else if (eventData.startsWith("/")) {
"$hostPart$eventData"
} else {
val queryString = queryParams.entries.joinToString("&") { "${it.key}=${it.value}" }
"$fullEndpoint?$queryString"
eventData
}
} else {
fullEndpoint
// 添加查询参数
val endpointWithParams = if (queryParams.isNotEmpty()) {
if (fullEndpoint.contains("?")) {
val queryString = queryParams.entries.joinToString("&") { "${it.key}=${it.value}" }
"$fullEndpoint&$queryString"
} else {
val queryString = queryParams.entries.joinToString("&") { "${it.key}=${it.value}" }
"$fullEndpoint?$queryString"
}
} else {
fullEndpoint
}
endpoint.complete(endpointWithParams)
} catch (e: Exception) {
Log.e(TAG, "处理endpoint事件失败: ${e.message}", e)
_onError(e)
close()
error(e)
}
endpoint.complete(endpointWithParams)
} catch (e: Exception) {
Log.e(TAG, "处理endpoint事件失败: ${e.message}", e)
_onError(e)
close()
error(e)
}
}
else -> {
try {
val data = event.data
if (data != null) {
try {
val message = json.decodeFromString<JSONRPCMessage>(data)
_onMessage(message)
} catch (e: Exception) {
Log.e(TAG, "解析JSON-RPC消息失败: ${e.message}", e)
_onError(e)
else -> {
try {
val data = event.data
if (data != null) {
try {
val message = json.decodeFromString<JSONRPCMessage>(data)
_onMessage(message)
} catch (e: Exception) {
Log.e(TAG, "解析JSON-RPC消息失败: ${e.message}", e)
_onError(e)
}
}
} catch (e: Exception) {
Log.e(TAG, "处理事件失败: ${e.message}", e)
_onError(e)
}
} catch (e: Exception) {
Log.e(TAG, "处理事件失败: ${e.message}", e)
_onError(e)
}
}
}
} catch (e: CancellationException) {
Log.d(TAG, "SSE事件收集被取消")
throw e
} catch (e: Exception) {
Log.e(TAG, "SSE连接异常断开: ${e.message}", e)
isConnected.set(false)
_onError(e)
onConnectionLost?.invoke()
throw e
}
}
// 启动连接监控
startConnectionMonitor()
}
/**
* 启动连接监控,定期检查连接状态
*/
private fun startConnectionMonitor() {
connectionMonitorJob = scope.launch {
while (isActive && isConnected.get()) {
try {
delay(10000) // 每10秒检查一次
// 检查session是否仍然活跃
if (session.coroutineContext[Job]?.isCancelled == true) {
Log.w(TAG, "检测到SSE会话已取消")
isConnected.set(false)
onConnectionLost?.invoke()
break
}
} catch (e: Exception) {
Log.e(TAG, "连接监控异常: ${e.message}", e)
isConnected.set(false)
onConnectionLost?.invoke()
break
}
}
}
}
@ -212,6 +259,8 @@ class CustomSseClientTransport(
// 等待endpoint就绪
endpoint.await()
Log.d(TAG, "CustomSseClientTransport启动完成")
}
/**
@ -245,6 +294,13 @@ class CustomSseClientTransport(
}
}
/**
* 检查连接状态
*/
fun isConnectionActive(): Boolean {
return isConnected.get() && session.coroutineContext[Job]?.isActive == true
}
/**
* 关闭传输层
*/
@ -253,8 +309,14 @@ class CustomSseClientTransport(
Log.e(TAG, "关闭失败: 传输层未初始化")
error("CustomSseClientTransport is not initialized!")
}
isConnected.set(false)
connectionMonitorJob?.cancel()
session.cancel()
_onClose()
job?.cancelAndJoin()
connectionMonitorJob?.cancelAndJoin()
Log.d(TAG, "CustomSseClientTransport已关闭")
}
}
}

63
local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/MCPSubClient.kt

@ -44,13 +44,14 @@ class MCPSubClient(
private var mcpClient: Client? = null
private var isConnected = false
private var availableTools = mutableListOf<Tool>()
private var transport: CustomSseClientTransport? = null
/**
* 连接到MCP服务器
*/
suspend fun connect(): Boolean = connectionMutex.withLock {
if (isConnected) return true
Log.e(TAG, "[$serverId] 开始连接mcp服务器: $serverUrl")
return try {
// 创建MCP客户端实例
val client = Client(
@ -61,13 +62,20 @@ class MCPSubClient(
)
// 根据URL类型选择传输方式
val transport = when {
val newTransport = when {
serverUrl.startsWith("http://") || serverUrl.startsWith("https://") -> {
// SSE传输 - 使用自定义的CustomSseClientTransport
val mcpHttpClient = httpClient ?: createMcpHttpClient()
CustomSseClientTransport(
client = mcpHttpClient,
urlString = serverUrl
urlString = serverUrl,
onConnectionLost = {
// 连接断开回调
Log.w(TAG, "[$serverId] 检测到连接断开")
scope.launch {
handleConnectionLost()
}
}
)
}
else -> {
@ -76,8 +84,10 @@ class MCPSubClient(
}
}
transport = newTransport
// 连接到服务器
client.connect(transport)
client.connect(newTransport)
// 获取可用工具列表
try {
@ -101,9 +111,9 @@ class MCPSubClient(
retryCount = 0
currentReconnectDelay = initialReconnectDelay
// 启动心跳检测
startHeartbeat()
// startHeartbeat()
Log.e(TAG, "[$serverId] 连接mcp服务器成功: $serverUrl")
true
} catch (e: Exception) {
Log.e(TAG, "[$serverId] MCP连接失败: ${e.message}", e)
false
@ -321,7 +331,15 @@ class MCPSubClient(
*/
suspend fun checkConnection(): Boolean {
if (!isConnected) {
Log.d(TAG, "当前未连接,尝试重新连接...")
Log.d(TAG, "[$serverId] 当前未连接,尝试重新连接...")
return connect()
}
// 检查传输层连接状态
val transportActive = transport?.isConnectionActive() ?: false
if (!transportActive) {
Log.w(TAG, "[$serverId] 传输层连接已断开")
isConnected = false
return connect()
}
@ -330,11 +348,34 @@ class MCPSubClient(
mcpClient?.ping()
return true
} catch (e: Exception) {
Log.e(TAG, "连接检查失败: ${e.message}")
Log.e(TAG, "[$serverId] 连接检查失败: ${e.message}")
isConnected = false
return false
}
}
/**
* 处理连接断开事件
*/
private suspend fun handleConnectionLost() {
connectionMutex.withLock {
if (isConnected) {
Log.w(TAG, "[$serverId] 连接已断开,更新状态")
isConnected = false
stopHeartbeat()
// 可以在这里添加自动重连逻辑
// 或者通知上层应用连接已断开
}
}
}
/**
* 获取连接状态
*/
fun getConnectionStatus(): Boolean {
return isConnected && (transport?.isConnectionActive() ?: false)
}
/**
* 停止心跳检测
*/
@ -393,10 +434,14 @@ class MCPSubClient(
scope.launch {
connectionMutex.withLock {
try {
stopHeartbeat()
mcpClient?.close()
transport?.close()
mcpClient = null
transport = null
isConnected = false
availableTools.clear()
Log.d(TAG, "[$serverId] MCP连接已关闭")
} catch (e: Exception) {
Log.e(TAG, "[$serverId] 关闭MCP连接时出错: ${e.message}", e)
}
@ -404,4 +449,4 @@ class MCPSubClient(
}
scope.cancel()
}
}
}

2
local_plugins/ota/android/src/main/kotlin/com/example/ota/tool/ota/OTAManager.kt

@ -106,7 +106,7 @@ class OTAManager(context: Context) : BluetoothOTAManager(context) {
"是否启用设备认证流程(与固件工程师确认) >>> ${configHelper.isUseDeviceAuth()} >>> ${bluetoothOption.isUseReconnect} "
)
//是否启用设备认证流程(与固件工程师确认)
bluetoothOption.isUseAuthDevice = configHelper.isUseDeviceAuth()
bluetoothOption.isUseAuthDevice = false
//设置BLE的MTU
bluetoothOption.mtu = BluetoothConstant.BLE_MTU_MIN
//是否需要改变BLE的MTU

Loading…
Cancel
Save