Browse Source

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

weicu
lxm 1 year ago
parent
commit
52e9fbf7a4
  1. 12
      .metadata
  2. 7
      android/app/src/debug/AndroidManifest.xml
  3. 5
      android/app/src/main/kotlin/com/yunqiinnovation/deep_voice/MainActivity.kt
  4. 7
      android/app/src/profile/AndroidManifest.xml
  5. 53
      lib/data/services/location_manager.dart
  6. 25
      lib/modules/ota_upgrade/controllers/ota_upgrade_controller.dart
  7. 1
      local_plugins/agent_service/android/build.gradle.kts
  8. 15
      local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt
  9. 13
      local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/utils.kt
  10. 2
      local_plugins/amap_service/android/build.gradle
  11. 3
      local_plugins/amap_service/android/src/main/kotlin/com/yunqiinnovation/amap_service/NavigationServiceHelper.kt
  12. 39
      local_plugins/ble_service/ios/ble_service/Sources/ble_service/BleService.swift
  13. 11
      local_plugins/ble_service/ios/ble_service/Sources/ble_service/SwiftBleServicePlugin.swift
  14. 56
      local_plugins/ble_service/lib/ble_service.dart
  15. 33
      local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/ChatApiService.kt
  16. 4
      local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/CustomSseClientTransport.kt
  17. 9
      local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/MCPSubClient.kt
  18. 20
      local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/SystemFunctionHandler.kt
  19. 33
      local_plugins/location_service/.gitignore
  20. 33
      local_plugins/location_service/.metadata
  21. 3
      local_plugins/location_service/CHANGELOG.md
  22. 1
      local_plugins/location_service/LICENSE
  23. 15
      local_plugins/location_service/README.md
  24. 4
      local_plugins/location_service/analysis_options.yaml
  25. 9
      local_plugins/location_service/android/.gitignore
  26. 70
      local_plugins/location_service/android/build.gradle
  27. 1
      local_plugins/location_service/android/settings.gradle
  28. 7
      local_plugins/location_service/android/src/main/AndroidManifest.xml
  29. 87
      local_plugins/location_service/android/src/main/kotlin/com/yunqiinnovation/location_service/LocationService.kt
  30. 32
      local_plugins/location_service/android/src/main/kotlin/com/yunqiinnovation/location_service/LocationServicePlugin.kt
  31. 38
      local_plugins/location_service/ios/.gitignore
  32. 29
      local_plugins/location_service/ios/location_service.podspec
  33. 32
      local_plugins/location_service/ios/location_service/Package.swift
  34. 19
      local_plugins/location_service/ios/location_service/Sources/location_service/LocationServicePlugin.swift
  35. 14
      local_plugins/location_service/ios/location_service/Sources/location_service/PrivacyInfo.xcprivacy
  36. 49
      local_plugins/location_service/lib/location_service.dart
  37. 72
      local_plugins/location_service/pubspec.yaml
  38. 3
      local_plugins/ota/ios/Classes/BleManager/JLBleManager.h
  39. 89
      local_plugins/ota/ios/Classes/BleManager/JLBleManager.m
  40. 35
      local_plugins/ota/ios/Classes/OtaPlugin.swift
  41. 4
      local_plugins/ota/lib/ota.dart
  42. 5
      local_plugins/ota/lib/ota_method_channel.dart
  43. 4
      local_plugins/ota/lib/ota_platform_interface.dart
  44. 2
      pubspec.yaml

12
.metadata

@ -4,7 +4,7 @@
# This file should be version controlled and should not be manually edited. # This file should be version controlled and should not be manually edited.
version: version:
revision: "ea121f8859e4b13e47a8f845e4586164519588bc" revision: "5c1433509f997e9059d230942bdf83f1b88a179e"
channel: "stable" channel: "stable"
project_type: app project_type: app
@ -13,11 +13,11 @@ project_type: app
migration: migration:
platforms: platforms:
- platform: root - platform: root
create_revision: ea121f8859e4b13e47a8f845e4586164519588bc create_revision: 5c1433509f997e9059d230942bdf83f1b88a179e
base_revision: ea121f8859e4b13e47a8f845e4586164519588bc base_revision: 5c1433509f997e9059d230942bdf83f1b88a179e
- platform: ios - platform: android
create_revision: ea121f8859e4b13e47a8f845e4586164519588bc create_revision: 5c1433509f997e9059d230942bdf83f1b88a179e
base_revision: ea121f8859e4b13e47a8f845e4586164519588bc base_revision: 5c1433509f997e9059d230942bdf83f1b88a179e
# User provided section # User provided section

7
android/app/src/debug/AndroidManifest.xml

@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

5
android/app/src/main/kotlin/com/yunqiinnovation/deep_voice/MainActivity.kt

@ -0,0 +1,5 @@
package com.yunqiinnovation.deep_voice
import io.flutter.embedding.android.FlutterActivity
class MainActivity : FlutterActivity()

7
android/app/src/profile/AndroidManifest.xml

@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

53
lib/data/services/location_manager.dart

@ -0,0 +1,53 @@
import 'package:deep_voice/core/utils/logger.dart';
import 'package:get/get.dart';
import 'package:location_service/location_service.dart';
import 'package:music_service/music_service.dart';
import 'package:permission_handler/permission_handler.dart';
import '../../core/utils/permission_util.dart';
import 'package:flutter/material.dart';
//音乐播放管理
class LocationManager extends GetxService {
static LocationManager get to => Get.find<LocationManager>();
// 音乐服务实例
final LocationService _musicService = LocationService();
/// 初始化
@override
void onInit() {
super.onInit();
requestNotificationPermission();
Logger.info('初始化权限请求');
}
Future<void> requestNotificationPermission() async {
final granted = await PermissionUtil.instance.requestPermission(
// context: context,
permissionType: Permission.notification,
permissionName: '通知',
explanationText: '需要通知权限来显示音乐播放状态。',
permanentDenialText: '您已永久拒绝通知权限,请前往设置开启权限。',
settingsButtonText: '去设置',
cancelButtonText: '取消',
);
if (!granted) {
Logger.warning('通知权限未被授予');
return;
}
Logger.info('通知权限已授予');
}
/// 释放资源
@override
void onClose() {
super.onClose();
}
//定位
Future<Map<String, double>> location() async {
return await _musicService.location();
}
}

25
lib/modules/ota_upgrade/controllers/ota_upgrade_controller.dart

@ -1,3 +1,5 @@
import 'package:ble_service/ble_service.dart';
import 'package:classic_bluetooth/classic_bluetooth.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:path/path.dart'; import 'package:path/path.dart';
@ -30,6 +32,8 @@ class OtaUpgradeController extends GetxController {
// 当前设备固件版本号 // 当前设备固件版本号
final RxString currentVersion = ''.obs; final RxString currentVersion = ''.obs;
String _address = '';
// 更新包地址,用于下载更新文件 // 更新包地址,用于下载更新文件
String updatepackageaddress = ''; String updatepackageaddress = '';
@ -50,6 +54,7 @@ class OtaUpgradeController extends GetxController {
// 蓝牙管理服务,用于获取设备信息和通信 // 蓝牙管理服务,用于获取设备信息和通信
final _bleManager = Get.find<BleManager>(); final _bleManager = Get.find<BleManager>();
final BleService _bleService = BleService();
final maxRetryAttempts = 3; // 最大重试次数 final maxRetryAttempts = 3; // 最大重试次数
int currentRetryCount = 0; int currentRetryCount = 0;
@ -58,6 +63,7 @@ class OtaUpgradeController extends GetxController {
final RxDouble progress = 0.0.obs; // 控制器自己的可观察属性 final RxDouble progress = 0.0.obs; // 控制器自己的可观察属性
final RxInt type = OtaEvent.NOT_STARTED.obs; final RxInt type = OtaEvent.NOT_STARTED.obs;
final RxBool isUpgrade = false.obs; final RxBool isUpgrade = false.obs;
@override @override
void onInit() { void onInit() {
super.onInit(); super.onInit();
@ -67,6 +73,7 @@ class OtaUpgradeController extends GetxController {
@override @override
void onClose() { void onClose() {
jlota.onOtaClose(); jlota.onOtaClose();
_bleService.setEnterOta(false);
isUpgrade.value = false; isUpgrade.value = false;
super.onClose(); super.onClose();
} }
@ -156,7 +163,7 @@ class OtaUpgradeController extends GetxController {
final firstPart = firmwareParts.sublist(0, 3).join('.'); final firstPart = firmwareParts.sublist(0, 3).join('.');
final secondPart = firmwareParts.sublist(3).join('.'); final secondPart = firmwareParts.sublist(3).join('.');
print("firstPart${firstPart},secondPart=${secondPart}"); print("firstPart${firstPart},secondPart=${secondPart}");
// 判断两部分是否一致 // 判断两部分是否一致
if (firstPart != secondPart) { if (firstPart != secondPart) {
throw "固件版本格式错误:${deviceInfo.firmwareVersion}"; throw "固件版本格式错误:${deviceInfo.firmwareVersion}";
} }
@ -164,6 +171,19 @@ class OtaUpgradeController extends GetxController {
// 正常流程 // 正常流程
currentVersion.value = firstPart; currentVersion.value = firstPart;
print('getequipmentinfo,id: ${deviceInfo.firmwareProductId}'); print('getequipmentinfo,id: ${deviceInfo.firmwareProductId}');
final connectedDevices = await ClassicBluetooth.getConnectedDevices();
if (connectedDevices.isNotEmpty) {
_address = connectedDevices.first.address;
}
await _bleService.setEnterOta(true);
await _bleService.disconnect();
await Future.delayed(const Duration(seconds: 2));
jlota.init();
// API请求 // API请求
final response = await Api.getequipmentinfo({ final response = await Api.getequipmentinfo({
"id": deviceInfo.firmwareProductId, "id": deviceInfo.firmwareProductId,
@ -368,7 +388,7 @@ class OtaUpgradeController extends GetxController {
// 开始升级 // 开始升级
void startUpgrading(String path) async { void startUpgrading(String path) async {
jlota.startUpgrading(address: 'E9:09:05:E0:18:7A', filePath: path); jlota.startUpgrading(address: _address, filePath: path);
return; return;
// isUpgrade.value = false; // isUpgrade.value = false;
} }
@ -402,6 +422,7 @@ class OtaUpgradeController extends GetxController {
if (event.event == 'onError') { if (event.event == 'onError') {
type.value = OtaEvent.UPGRADE_FAILED; type.value = OtaEvent.UPGRADE_FAILED;
isUpgrade.value = false; isUpgrade.value = false;
Get.snackbar('升级失败', '请检查设备连接或升级包');
} }
if (event.event == 'NOCONNECTION') { if (event.event == 'NOCONNECTION') {
type.value = OtaEvent.CONNECTION_DISCONNECTION; type.value = OtaEvent.CONNECTION_DISCONNECTION;

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

@ -49,4 +49,5 @@ dependencies {
add("compileOnly", project(":amap_service")) add("compileOnly", project(":amap_service"))
add("compileOnly", project(":speech")) add("compileOnly", project(":speech"))
add("compileOnly", project(":chat_api")) add("compileOnly", project(":chat_api"))
add("compileOnly", project(":location_service"))
} }

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

@ -9,6 +9,7 @@ import com.yunqiinnovation.chat_api.StreamCallback
import com.yunqiinnovation.chat_storage.ChatStorageHelper import com.yunqiinnovation.chat_storage.ChatStorageHelper
import com.yunqiinnovation.music_service.MusicServiceStarter import com.yunqiinnovation.music_service.MusicServiceStarter
import com.yunqiinnovation.amap_service.NavigationServiceHelper import com.yunqiinnovation.amap_service.NavigationServiceHelper
import com.yunqiinnovation.location_service.LocationService
import android.util.Log import android.util.Log
import kotlinx.coroutines.* import kotlinx.coroutines.*
import java.util.Collections import java.util.Collections
@ -125,8 +126,8 @@ object AgentService : CoroutineScope {
this.chatApiService = ChatApiService(context) this.chatApiService = ChatApiService(context)
this.chatStorageHelper = ChatStorageHelper(context) this.chatStorageHelper = ChatStorageHelper(context)
this.audioPlayer = AudioPlayer(context) this.audioPlayer = AudioPlayer(context)
//初始化定位服务
LocationService.initialize(appContext)
// 设置系统提示词 // 设置系统提示词
val customSystemPrompt = config["systemPrompt"] as? String val customSystemPrompt = config["systemPrompt"] as? String
if (!customSystemPrompt.isNullOrBlank()) { if (!customSystemPrompt.isNullOrBlank()) {
@ -621,8 +622,14 @@ object AgentService : CoroutineScope {
// 先添加系统提示词 // 先添加系统提示词
if (systemPrompt.isNotEmpty()) { if (systemPrompt.isNotEmpty()) {
messagesWithSystemPrompt.put(createSystemMessage(systemPrompt)) val location = LocationService.getCachedLocation()?: mapOf(
// Log.d(TAG, "系统提示词: ${systemPrompt}") "user_location" to "定位未开启",
"user_lat" to "定位未开启",
"user_lng" to "定位未开启",
)
val _systemPrompt = Utils.fillTemplate(systemPrompt,location)
messagesWithSystemPrompt.put(createSystemMessage(_systemPrompt))
Log.d(TAG, "liwei--------- 系统提示词: ${_systemPrompt}")
} }
// 再添加历史消息 // 再添加历史消息

13
local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/utils.kt

@ -141,4 +141,15 @@ object Utils {
false false
} }
} }
}
/**
* 替换字符串 变量
* */
fun fillTemplate(tpl: String, data: Map<String, String>): String {
var result = tpl
data.forEach { (k, v) ->
result = result.replace("{$k}", v)
}
return result
}
}

2
local_plugins/amap_service/android/build.gradle

@ -48,7 +48,7 @@ android {
} }
dependencies { dependencies {
implementation 'com.amap.api:navi-3dmap:latest.integration' implementation('com.amap.api:navi-3dmap:latest.integration')
testImplementation("org.jetbrains.kotlin:kotlin-test") testImplementation("org.jetbrains.kotlin:kotlin-test")
testImplementation("org.mockito:mockito-core:5.0.0") testImplementation("org.mockito:mockito-core:5.0.0")
} }

3
local_plugins/amap_service/android/src/main/kotlin/com/yunqiinnovation/amap_service/NavigationServiceHelper.kt

@ -105,4 +105,7 @@ object NavigationServiceHelper {
} }
} }
} }

39
local_plugins/ble_service/ios/ble_service/Sources/ble_service/BleService.swift

@ -43,6 +43,10 @@ public class BleService: NSObject {
private var audioCharacteristic: CBCharacteristic? private var audioCharacteristic: CBCharacteristic?
private var isScanning = false private var isScanning = false
// 是否是ota模式
var isEnterOta = false
private var callbacks = [Callback]() private var callbacks = [Callback]()
private var connectionState = BleConst.STATE_DISCONNECTED private var connectionState = BleConst.STATE_DISCONNECTED
@ -212,6 +216,9 @@ public class BleService: NSObject {
/// 统一的连接方法,支持恢复和重连 /// 统一的连接方法,支持恢复和重连
private func connectPeripheral(_ peripheral: CBPeripheral) { private func connectPeripheral(_ peripheral: CBPeripheral) {
if isEnterOta {
return
}
guard let manager = centralManager else { guard let manager = centralManager else {
return return
} }
@ -592,19 +599,14 @@ public class BleService: NSObject {
] ]
if !responseData.isEmpty { if !responseData.isEmpty {
if let versionStr = String(data: responseData, encoding: .utf8) { // 如果无法解析为字符串,以点分隔的数字表示版本
resultMap["version"] = versionStr let versionNums = responseData
os_log("接收到目标版本号: %@", log: logger, type: .info, versionStr) .map { $0 }
} else { .filter { $0 != 0xFF } // 过滤掉无效字节
// 如果无法解析为字符串,以点分隔的数字表示版本 .map { String($0) }
let versionNums = responseData .joined(separator: ".")
.map { $0 } resultMap["version"] = versionNums
.filter { $0 != 0xFF } // 过滤掉无效字节 os_log("接收到目标版本号(数字格式): %@", log: logger, type: .info, versionNums)
.map { String($0) }
.joined(separator: ".")
resultMap["version"] = versionNums
os_log("接收到目标版本号(数字格式): %@", log: logger, type: .info, versionNums)
}
} }
notifyDeviceInfoReceived(infoType: Int(cmd), infoData: resultMap) notifyDeviceInfoReceived(infoType: Int(cmd), infoData: resultMap)
@ -616,13 +618,8 @@ public class BleService: NSObject {
"rawData": responseData "rawData": responseData
] ]
if responseData.count >= 2 { if !responseData.isEmpty {
let productId = UInt16(responseData[0]) << 8 | UInt16(responseData[1]) let productId = responseData.map { String($0 & 0xFF) }.joined(separator: ".")
resultMap["productId"] = productId
os_log("接收到产品ID: %d", log: logger, type: .info, productId)
} else if !responseData.isEmpty {
// 单字节产品ID
let productId = UInt16(responseData[0])
resultMap["productId"] = productId resultMap["productId"] = productId
os_log("接收到产品ID: %d", log: logger, type: .info, productId) os_log("接收到产品ID: %d", log: logger, type: .info, productId)
} }
@ -1263,4 +1260,4 @@ extension BleService: CBPeripheralDelegate {
os_log("✅ 描述符写入成功: %{public}@", log: logger, type: .debug, descriptor.uuid.uuidString) os_log("✅ 描述符写入成功: %{public}@", log: logger, type: .debug, descriptor.uuid.uuidString)
} }
} }
} }

11
local_plugins/ble_service/ios/ble_service/Sources/ble_service/SwiftBleServicePlugin.swift

@ -150,6 +150,15 @@ public class SwiftBleServicePlugin: NSObject, FlutterPlugin {
case "isBleConnected": case "isBleConnected":
result(BleService.shared.isConnected()) result(BleService.shared.isConnected())
case "setEnterOta":
let arguments = call.arguments as? [String: Any]
BleService.shared.isEnterOta = (arguments?["enterOta"] as? Bool) ?? false
result("")
case "disconnect":
BleService.shared.disconnect()
result("")
case "registerBackgroundScan", "unregisterBackgroundScan": case "registerBackgroundScan", "unregisterBackgroundScan":
// iOS上禁用,直接返回成功以避免Flutter端报错 // iOS上禁用,直接返回成功以避免Flutter端报错
@ -380,4 +389,4 @@ class BleStatusStreamHandler: NSObject, FlutterStreamHandler {
plugin?.statusEventSink = nil plugin?.statusEventSink = nil
return nil return nil
} }
} }

56
local_plugins/ble_service/lib/ble_service.dart

@ -183,34 +183,35 @@ class BleService {
return false; return false;
} }
} }
//======================================================================== //========================================================================
// 以下是Companion Device相关功能 // 以下是Companion Device相关功能
//======================================================================== //========================================================================
/// 检查设备是否支持Companion Device Manager /// 检查设备是否支持Companion Device Manager
/// ///
/// 返回设备是否支持Companion Device Manager API /// 返回设备是否支持Companion Device Manager API
/// 仅在Android 8.0及以上版本可用 /// 仅在Android 8.0及以上版本可用
Future<bool> isCompanionSupported() async { Future<bool> isCompanionSupported() async {
try { try {
final result = await _methodChannel.invokeMethod<bool>('isCompanionSupported'); final result =
await _methodChannel.invokeMethod<bool>('isCompanionSupported');
return result ?? false; return result ?? false;
} catch (e) { } catch (e) {
print('检查Companion设备支持失败: $e'); print('检查Companion设备支持失败: $e');
return false; return false;
} }
} }
/// 启动设备关联流程 /// 启动设备关联流程
/// ///
/// 弹出系统设备选择对话框,用户选择后将建立持久化关联 /// 弹出系统设备选择对话框,用户选择后将建立持久化关联
/// 关联成功后,应用将获得后台扫描和启动前台服务的特权 /// 关联成功后,应用将获得后台扫描和启动前台服务的特权
/// ///
/// [deviceNamePattern] 设备名称正则表达式,例如"MyHeadset.*" /// [deviceNamePattern] 设备名称正则表达式,例如"MyHeadset.*"
/// [serviceUuid] 可选的服务UUID /// [serviceUuid] 可选的服务UUID
/// [macAddress] 可选的MAC地址 /// [macAddress] 可选的MAC地址
/// ///
/// 返回是否成功启动关联流程 /// 返回是否成功启动关联流程
Future<bool> startDeviceAssociation({ Future<bool> startDeviceAssociation({
String? deviceNamePattern, String? deviceNamePattern,
@ -232,13 +233,14 @@ class BleService {
return false; return false;
} }
} }
/// 获取已关联的设备MAC地址 /// 获取已关联的设备MAC地址
/// ///
/// 返回已关联的设备MAC地址,如果没有则返回null /// 返回已关联的设备MAC地址,如果没有则返回null
Future<String?> getPairedMacAddress() async { Future<String?> getPairedMacAddress() async {
try { try {
final result = await _methodChannel.invokeMethod<String>('getPairedMacAddress'); final result =
await _methodChannel.invokeMethod<String>('getPairedMacAddress');
return result; return result;
} catch (e) { } catch (e) {
print('获取配对MAC地址失败: $e'); print('获取配对MAC地址失败: $e');
@ -246,9 +248,8 @@ class BleService {
} }
} }
/// 检查BLE连接状态 /// 检查BLE连接状态
/// ///
/// 返回BLE是否已连接 /// 返回BLE是否已连接
Future<bool> isBleConnected() async { Future<bool> isBleConnected() async {
try { try {
@ -260,11 +261,31 @@ class BleService {
} }
} }
/// 设置是否是ota模式
Future<void> setEnterOta(bool enterOta) async {
try {
await _methodChannel.invokeMethod<String>('setEnterOta', {
'enterOta': enterOta,
});
} catch (e) {
print('设置是否是ota模式失败: $e');
}
}
/// 断开连接
Future<void> disconnect() async {
try {
await _methodChannel.invokeMethod<String>('disconnect');
} catch (e) {
print('断开连接失败: $e');
}
}
/// 注册BLE后台扫描 /// 注册BLE后台扫描
/// ///
/// 注册后,即使应用被杀死,系统也会在检测到设备广播时唤醒应用 /// 注册后,即使应用被杀死,系统也会在检测到设备广播时唤醒应用
/// 这是确保设备可以在应用后台时被自动连接的关键功能 /// 这是确保设备可以在应用后台时被自动连接的关键功能
/// ///
/// [macAddress] 可选的设备MAC地址,如果不提供则使用已关联的设备 /// [macAddress] 可选的设备MAC地址,如果不提供则使用已关联的设备
Future<bool> registerBackgroundScan({String? macAddress}) async { Future<bool> registerBackgroundScan({String? macAddress}) async {
try { try {
@ -290,13 +311,14 @@ class BleService {
return false; return false;
} }
} }
/// 清除所有设备关联 /// 清除所有设备关联
/// ///
/// 用于重置设备关联状态,通常在用户主动解除关联或调试时使用 /// 用于重置设备关联状态,通常在用户主动解除关联或调试时使用
Future<bool> clearAssociations() async { Future<bool> clearAssociations() async {
try { try {
final result = await _methodChannel.invokeMethod<bool>('clearAssociations'); final result =
await _methodChannel.invokeMethod<bool>('clearAssociations');
return result ?? false; return result ?? false;
} catch (e) { } catch (e) {
print('清除设备关联失败: $e'); print('清除设备关联失败: $e');

33
local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/ChatApiService.kt

@ -308,7 +308,8 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor
Log.e("ChatApiService", "消息列表为空") Log.e("ChatApiService", "消息列表为空")
throw IllegalArgumentException("消息列表不能为空") throw IllegalArgumentException("消息列表不能为空")
} }
// Log.e("ChatApiService", "AI解析工具列表: ${tools}")
val chatsFlow = try { val chatsFlow = try {
val chatCompletionRequest = ChatCompletionRequest( val chatCompletionRequest = ChatCompletionRequest(
model = ModelId(currentModel), model = ModelId(currentModel),
@ -333,7 +334,7 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor
chatsFlow.collect { result -> chatsFlow.collect { result ->
if (isCanceled) return@collect if (isCanceled) return@collect
val choice = result.choices?.firstOrNull() val choice = result.choices.firstOrNull()
if (choice == null) { if (choice == null) {
Log.e("ChatApiService", "choice为null") Log.e("ChatApiService", "choice为null")
return@collect return@collect
@ -353,37 +354,34 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor
// 收集工具调用信息 // 收集工具调用信息
delta.toolCalls?.forEach { toolCall -> delta.toolCalls?.forEach { toolCall ->
val index = toolCall.index val index = toolCall.index
// 创建或获取现有的工具调用信息 // 创建或获取现有的工具调用信息
val toolCallInfo = toolCalls.getOrPut(index) { ToolCallInfo() } val toolCallInfo = toolCalls.getOrPut(index) { ToolCallInfo() }
// 安全处理工具调用ID // 安全处理工具调用ID
try { try {
toolCall.id?.let { id -> toolCall.id?.let { i ->
toolCallInfo.id = id.toString() toolCallInfo.id = i.toString()
} }
} catch (e: Exception) { } catch (e: Exception) {
Log.e("ChatApiService", "处理工具调用ID异常: ${e.message}") Log.e("ChatApiService", "处理工具调用ID异常: ${e.message}")
} }
// 安全处理函数信息 // 安全处理函数信息
try { try {
toolCall.function?.let { function -> toolCall.function?.let { function ->
try { try {
function.name?.let { name -> function.name.let { name ->
toolCallInfo.name = name toolCallInfo.name = name
} }
} catch (e: Exception) { } catch (e: Exception) {
Log.e("ChatApiService", "处理工具调用函数名称异常: ${e.message}") Log.e("ChatApiService", "处理工具调用函数名称异常! function:${toolCall.function} err:${e.message}")
} }
try { try {
function.arguments?.let { args -> function.arguments.let { args ->
toolCallInfo.arguments += args toolCallInfo.arguments += args
} }
} catch (e: Exception) { } catch (e: Exception) {
Log.e("ChatApiService", "处理工具调用参数异常: ${e.message}") Log.e("ChatApiService", "处理工具调用参数异常: ${e.message}")
} }
Log.e("ChatApiService", "AI发起工具调用:${toolCallInfo.name} ${toolCallInfo.arguments}")
} }
} catch (e: Exception) { } catch (e: Exception) {
Log.e("ChatApiService", "处理工具调用函数信息异常: ${e.message}") Log.e("ChatApiService", "处理工具调用函数信息异常: ${e.message}")
@ -417,11 +415,9 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor
Log.e("ChatApiService", "查找有效工具调用异常: ${e.message}") Log.e("ChatApiService", "查找有效工具调用异常: ${e.message}")
null null
} }
if (firstToolCall == null) { if (firstToolCall == null) {
return false return false
} }
// 创建函数调用字典 // 创建函数调用字典
val functionCall = try { val functionCall = try {
mapOf( mapOf(
@ -433,10 +429,8 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor
Log.e("ChatApiService", "创建函数调用字典异常: ${e.message}") Log.e("ChatApiService", "创建函数调用字典异常: ${e.message}")
return false return false
} }
// 通知上层工具调用事件 // 通知上层工具调用事件
streamCallback?.onFunctionCall(convertMapToJsonObject(functionCall)) streamCallback?.onFunctionCall(convertMapToJsonObject(functionCall))
// 在后台队列处理工具调用 // 在后台队列处理工具调用
launch { launch {
try { try {
@ -444,14 +438,13 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor
// 通过MCP客户端处理工具调用 // 通过MCP客户端处理工具调用
val functionName = firstToolCall.name val functionName = firstToolCall.name
val argumentsJson = firstToolCall.arguments val argumentsJson = firstToolCall.arguments
val result = if (_mcpClient?.hasToolWithName(functionName) == true) { val result = if (_mcpClient?.hasToolWithName(functionName) == true) {
// 解析参数 // 解析参数
val arguments = _mcpClient?.parseJsonArguments(argumentsJson) ?: emptyMap() val arguments = _mcpClient?.parseJsonArguments(argumentsJson) ?: emptyMap()
// 调用MCP工具 // 调用MCP工具
val toolResult = _mcpClient?.callTool(functionName, arguments) val toolResult = _mcpClient?.callTool(functionName, arguments)
Log.e("ChatApiService", "AI调用工具结果: $functionName}:$arguments} toolResult:${toolResult}")
// 处理结果 // 处理结果
if (toolResult != null) { if (toolResult != null) {
if (toolResult["isError"] == true) { if (toolResult["isError"] == true) {
@ -478,14 +471,12 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor
// 工具不存在 // 工具不存在
mapOf("context" to "Tool not found: $functionName") mapOf("context" to "Tool not found: $functionName")
} }
if (!isCanceled) { if (!isCanceled) {
// 处理结果 // 处理结果
streamCallback?.onFunctionCallResult( streamCallback?.onFunctionCallResult(
convertMapToJsonObject(functionCall), convertMapToJsonObject(functionCall),
convertMapToJsonObject(result) convertMapToJsonObject(result)
) )
// 将结果发送回OpenAI继续对话 // 将结果发送回OpenAI继续对话
sendFunctionCallResultInternal( sendFunctionCallResultInternal(
messages = currentMessages, messages = currentMessages,
@ -697,6 +688,7 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor
description = description, description = description,
parameters = parameters parameters = parameters
) )
Log.e("ChatApiService", "AI携带工具:${name}:${parameters}")
tools.add(tool) tools.add(tool)
} catch (e: Exception) { } catch (e: Exception) {
@ -855,12 +847,11 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor
nameOrNull = name, nameOrNull = name,
argumentsOrNull = arguments argumentsOrNull = arguments
) )
val toolCall = ToolCall.Function( val toolCall = ToolCall.Function(
id = ToolId(id), id = ToolId(id),
function = functionCall function = functionCall
) )
toolCallsList.add(toolCall) toolCallsList.add(toolCall)
} }
} }

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

@ -107,7 +107,9 @@ class CustomSseClientTransport(
"open" -> { "open" -> {
// SSE连接已打开 // SSE连接已打开
} }
"ping" -> {
// 心跳
}
"endpoint" -> { "endpoint" -> {
try { try {
val eventData = event.data ?: "" val eventData = event.data ?: ""

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

@ -77,11 +77,11 @@ class MCPSubClient(
} }
// 连接到服务器 // 连接到服务器
client?.connect(transport) client.connect(transport)
// 获取可用工具列表 // 获取可用工具列表
try { try {
val toolsResult = client?.listTools() val toolsResult = client.listTools()
if (toolsResult != null) { if (toolsResult != null) {
availableTools.clear() availableTools.clear()
val filtered = toolsResult.tools.filter { tool -> val filtered = toolsResult.tools.filter { tool ->
@ -129,7 +129,7 @@ class MCPSubClient(
"properties" to emptyMap<String, Any>(), "properties" to emptyMap<String, Any>(),
"required" to emptyList<String>() "required" to emptyList<String>()
) )
Log.w(TAG, "解析工具数据: ${tool.name} ${parametersMap}")
val toolMap = mapOf( val toolMap = mapOf(
"type" to "function", "type" to "function",
"function" to mapOf( "function" to mapOf(
@ -229,7 +229,7 @@ class MCPSubClient(
val required = mutableListOf<String>() val required = mutableListOf<String>()
// 处理properties // 处理properties
inputSchema.properties?.let { propsJsonObject -> inputSchema.properties.let { propsJsonObject ->
for ((key, value) in propsJsonObject) { for ((key, value) in propsJsonObject) {
when (value) { when (value) {
is JsonPrimitive -> { is JsonPrimitive -> {
@ -260,7 +260,6 @@ class MCPSubClient(
"properties" to properties, "properties" to properties,
"required" to required "required" to required
) )
return result return result
} }

20
local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/SystemFunctionHandler.kt

@ -127,16 +127,16 @@ class SystemFunctionHandler(private val context: Context? = null) {
) )
// 注册获取当前位置函数 // 注册获取当前位置函数
client.registerLocalFunction( // client.registerLocalFunction(
name = "get_current_location", // name = "get_current_location",
description = "获取当前地理位置", // description = "获取当前地理位置",
parameters = mapOf( // parameters = mapOf(
"type" to "object", // "type" to "object",
"properties" to emptyMap<String, Any>(), // "properties" to emptyMap<String, Any>(),
"required" to emptyList<String>() // "required" to emptyList<String>()
), // ),
handler = GetCurrentLocationHandler(context) // handler = GetCurrentLocationHandler(context)
) // )
// 注册媒体播放功能 // 注册媒体播放功能
client.registerLocalFunction( client.registerLocalFunction(

33
local_plugins/location_service/.gitignore

@ -0,0 +1,33 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.build/
.buildlog/
.history
.svn/
.swiftpm/
migrate_working_dir/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
/pubspec.lock
**/doc/api/
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
build/

33
local_plugins/location_service/.metadata

@ -0,0 +1,33 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: "5c1433509f997e9059d230942bdf83f1b88a179e"
channel: "stable"
project_type: plugin
# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: 5c1433509f997e9059d230942bdf83f1b88a179e
base_revision: 5c1433509f997e9059d230942bdf83f1b88a179e
- platform: android
create_revision: 5c1433509f997e9059d230942bdf83f1b88a179e
base_revision: 5c1433509f997e9059d230942bdf83f1b88a179e
- platform: ios
create_revision: 5c1433509f997e9059d230942bdf83f1b88a179e
base_revision: 5c1433509f997e9059d230942bdf83f1b88a179e
# User provided section
# List of Local paths (relative to this file) that should be
# ignored by the migrate tool.
#
# Files that are not part of the templates will be ignored by default.
unmanaged_files:
- 'lib/main.dart'
- 'ios/Runner.xcodeproj/project.pbxproj'

3
local_plugins/location_service/CHANGELOG.md

@ -0,0 +1,3 @@
## 0.0.1
* TODO: Describe initial release.

1
local_plugins/location_service/LICENSE

@ -0,0 +1 @@
TODO: Add your license here.

15
local_plugins/location_service/README.md

@ -0,0 +1,15 @@
# location_service
A new Flutter project.
## Getting Started
This project is a starting point for a Flutter
[plug-in package](https://flutter.dev/to/develop-plugins),
a specialized package that includes platform-specific implementation code for
Android and/or iOS.
For help getting started with Flutter development, view the
[online documentation](https://docs.flutter.dev), which offers tutorials,
samples, guidance on mobile development, and a full API reference.

4
local_plugins/location_service/analysis_options.yaml

@ -0,0 +1,4 @@
include: package:flutter_lints/flutter.yaml
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options

9
local_plugins/location_service/android/.gitignore

@ -0,0 +1,9 @@
*.iml
.gradle
/local.properties
/.idea/workspace.xml
/.idea/libraries
.DS_Store
/build
/captures
.cxx

70
local_plugins/location_service/android/build.gradle

@ -0,0 +1,70 @@
group = "com.yunqiinnovation.location_service"
version = "1.0-SNAPSHOT"
buildscript {
ext.kotlin_version = "2.1.0"
repositories {
google()
mavenCentral()
}
dependencies {
classpath("com.android.tools.build:gradle:8.7.3")
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version")
}
}
allprojects {
repositories {
google()
mavenCentral()
}
}
apply plugin: "com.android.library"
apply plugin: "kotlin-android"
android {
namespace = "com.yunqiinnovation.location_service"
compileSdk = 35
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_11
}
sourceSets {
main.java.srcDirs += "src/main/kotlin"
test.java.srcDirs += "src/test/kotlin"
}
defaultConfig {
minSdk = 21
}
// ✅ dependencies 应该写在这里(android {} 外面)
dependencies {
implementation('com.amap.api:navi-3dmap:latest.integration')
testImplementation("org.jetbrains.kotlin:kotlin-test")
testImplementation("org.mockito:mockito-core:5.0.0")
}
testOptions {
unitTests.all {
useJUnitPlatform()
testLogging {
events "passed", "skipped", "failed", "standardOut", "standardError"
outputs.upToDateWhen {false}
showStandardStreams = true
}
}
}
}

1
local_plugins/location_service/android/settings.gradle

@ -0,0 +1 @@
rootProject.name = 'location_service'

7
local_plugins/location_service/android/src/main/AndroidManifest.xml

@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.yunqiinnovation.location_service">
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
</manifest>

87
local_plugins/location_service/android/src/main/kotlin/com/yunqiinnovation/location_service/LocationService.kt

@ -0,0 +1,87 @@
package com.yunqiinnovation.location_service
import android.content.Context
import android.location.Location
import android.util.Log
import com.amap.api.location.*
import io.flutter.plugin.common.MethodChannel
object LocationService {
private const val TAG = "LocationService"
private var locationClient: AMapLocationClient? = null
private var currentLocation: AMapLocation? = null
private var initialized = false
fun initialize(context: Context) {
if (initialized) return
Log.e(TAG, "liwei--------- 初始化高德定位服务 1")
try {
AMapLocationClient.updatePrivacyShow(context, true, true)
AMapLocationClient.updatePrivacyAgree(context, true)
Log.e(TAG, "liwei--------- 初始化高德定位服务 2")
locationClient = AMapLocationClient(context.applicationContext)
val option = AMapLocationClientOption().apply {
locationMode = AMapLocationClientOption.AMapLocationMode.Hight_Accuracy
isOnceLocation = false
interval = 5000L
}
Log.e(TAG, "liwei--------- 初始化高德定位服务 3")
locationClient?.setLocationOption(option)
Log.e(TAG, "liwei--------- 初始化高德定位服务 4")
locationClient?.setLocationListener { location ->
if (location != null && location.errorCode == 0) {
currentLocation = location
Log.d(TAG, "liwei---------定位更新: ${location.latitude}, ${location.longitude}")
} else {
Log.w(TAG, "liwei---------定位失败: ${location?.errorCode} - ${location?.errorInfo}")
}
}
Log.e(TAG, "liwei--------- 初始化高德定位服务 5")
locationClient?.startLocation()
Log.e(TAG, "liwei--------- 初始化高德定位服务 6")
initialized = true
} catch (e: Exception) {
Log.e(TAG, "高德定位初始化失败: ${e.message}", e)
}
}
fun getCachedLocation(): Map<String, String>? {
// // 定位成功,获取经纬度
// val latitude = location.latitude
// val longitude = location.longitude
//
// // 获取结构化地址
// val province = location.province // 省
// val city = location.city // 市
// val district = location.district // 区县
// val street = location.street // 街道
// val streetNum = location.streetNum // 门牌号
// val adCode = location.adCode // 行政区划代码
// val address = location.address // 详细地址拼接字符串
return currentLocation?.let {
mapOf(
"user_location" to it.address,
"user_lat" to it.latitude.toString(),
"user_lng" to it.longitude.toString(),
)
}
}
fun getCurrentLocation(result: MethodChannel.Result) {
val cached = getCachedLocation()
if (cached != null) {
result.success(cached)
} else {
result.error("NO_LOCATION", "当前位置还未获取", null)
}
}
fun stop() {
locationClient?.stopLocation()
locationClient?.onDestroy()
locationClient = null
currentLocation = null
initialized = false
}
}

32
local_plugins/location_service/android/src/main/kotlin/com/yunqiinnovation/location_service/LocationServicePlugin.kt

@ -0,0 +1,32 @@
package com.yunqiinnovation.location_service
import android.content.Context
import android.util.Log
import io.flutter.embedding.engine.plugins.FlutterPlugin
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
/** LocationServicePlugin */
class LocationServicePlugin: FlutterPlugin, MethodCallHandler {
private val TAG = "LocationServicePlugin"
private lateinit var context: Context
private lateinit var channel : MethodChannel
override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
context = flutterPluginBinding.applicationContext
channel = MethodChannel(flutterPluginBinding.binaryMessenger, "com.yunqiinnovation.location_service")
channel.setMethodCallHandler(this)
}
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
Log.d(TAG, "Method call: ${call.method}")
when (call.method) {
"location" -> LocationService.getCurrentLocation(result)
else -> result.notImplemented()
}
}
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {}
}

38
local_plugins/location_service/ios/.gitignore

@ -0,0 +1,38 @@
.idea/
.vagrant/
.sconsign.dblite
.svn/
.DS_Store
*.swp
profile
DerivedData/
build/
GeneratedPluginRegistrant.h
GeneratedPluginRegistrant.m
.generated/
*.pbxuser
*.mode1v3
*.mode2v3
*.perspectivev3
!default.pbxuser
!default.mode1v3
!default.mode2v3
!default.perspectivev3
xcuserdata
*.moved-aside
*.pyc
*sync/
Icon?
.tags*
/Flutter/Generated.xcconfig
/Flutter/ephemeral/
/Flutter/flutter_export_environment.sh

29
local_plugins/location_service/ios/location_service.podspec

@ -0,0 +1,29 @@
#
# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html.
# Run `pod lib lint location_service.podspec` to validate before publishing.
#
Pod::Spec.new do |s|
s.name = 'location_service'
s.version = '0.0.1'
s.summary = 'A new Flutter project.'
s.description = <<-DESC
A new Flutter project.
DESC
s.homepage = 'http://example.com'
s.license = { :file => '../LICENSE' }
s.author = { 'Your Company' => 'email@example.com' }
s.source = { :path => '.' }
s.source_files = 'location_service/Sources/location_service/**/*'
s.dependency 'Flutter'
s.platform = :ios, '12.0'
# Flutter.framework does not contain a i386 slice.
s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' }
s.swift_version = '5.0'
# If your plugin requires a privacy manifest, for example if it uses any
# required reason APIs, update the PrivacyInfo.xcprivacy file to describe your
# plugin's privacy impact, and then uncomment this line. For more information,
# see https://developer.apple.com/documentation/bundleresources/privacy_manifest_files
# s.resource_bundles = {'location_service_privacy' => ['location_service/Sources/location_service/PrivacyInfo.xcprivacy']}
end

32
local_plugins/location_service/ios/location_service/Package.swift

@ -0,0 +1,32 @@
// swift-tools-version: 5.9
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "location_service",
platforms: [
.iOS("12.0")
],
products: [
.library(name: "location-service", targets: ["location_service"])
],
dependencies: [],
targets: [
.target(
name: "location_service",
dependencies: [],
resources: [
// If your plugin requires a privacy manifest, for example if it uses any required
// reason APIs, update the PrivacyInfo.xcprivacy file to describe your plugin's
// privacy impact, and then uncomment these lines. For more information, see
// https://developer.apple.com/documentation/bundleresources/privacy_manifest_files
// .process("PrivacyInfo.xcprivacy"),
// If you have other resources that need to be bundled with your plugin, refer to
// the following instructions to add them:
// https://developer.apple.com/documentation/xcode/bundling-resources-with-a-swift-package
]
)
]
)

19
local_plugins/location_service/ios/location_service/Sources/location_service/LocationServicePlugin.swift

@ -0,0 +1,19 @@
import Flutter
import UIKit
public class LocationServicePlugin: NSObject, FlutterPlugin {
public static func register(with registrar: FlutterPluginRegistrar) {
let channel = FlutterMethodChannel(name: "location_service", binaryMessenger: registrar.messenger())
let instance = LocationServicePlugin()
registrar.addMethodCallDelegate(instance, channel: channel)
}
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
switch call.method {
case "getPlatformVersion":
result("iOS " + UIDevice.current.systemVersion)
default:
result(FlutterMethodNotImplemented)
}
}
}

14
local_plugins/location_service/ios/location_service/Sources/location_service/PrivacyInfo.xcprivacy

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyTrackingDomains</key>
<array/>
<key>NSPrivacyAccessedAPITypes</key>
<array/>
<key>NSPrivacyCollectedDataTypes</key>
<array/>
<key>NSPrivacyTracking</key>
<false/>
</dict>
</plist>

49
local_plugins/location_service/lib/location_service.dart

@ -0,0 +1,49 @@
import 'package:flutter/services.dart';
class LocationService {
static final LocationService _instance = LocationService._internal();
/// 获取单例
factory LocationService() => _instance;
// 方法通道
final MethodChannel _methodChannel = MethodChannel(
'com.yunqiinnovation.location_service',
);
// 事件通道 - 简化为只使用数据和状态通道
late final EventChannel _dataEventChannel;
late final EventChannel _statusEventChannel;
// 事件流
late final Stream<dynamic> _dataStream;
late final Stream<dynamic> _statusStream;
// 获取事件流
Stream<dynamic> get onDataReceived => _dataStream;
// 状态流直接传递原始数据
Stream<dynamic> get onStatusUpdated => _statusStream;
// 内部构造函数
LocationService._internal() {
_dataEventChannel = const EventChannel(
'com.yunqiinnovation.music_service/data_events',
);
_statusEventChannel = const EventChannel(
'com.yunqiinnovation.music_service/status_events',
);
// 初始化事件流
_dataStream = _dataEventChannel.receiveBroadcastStream();
_statusStream = _statusEventChannel.receiveBroadcastStream();
}
Future<Map<String, double>> location() async {
final result = await _methodChannel.invokeMethod<Map>('location');
return {
'latitude': result?['longitude'] ?? 0.0,
'longitude': result?['longitude'] ?? 0.0,
};
}
}

72
local_plugins/location_service/pubspec.yaml

@ -0,0 +1,72 @@
name: location_service
description: "A new Flutter project."
version: 0.0.1
homepage:
environment:
sdk: ^3.8.1
flutter: '>=3.3.0'
dependencies:
flutter:
sdk: flutter
plugin_platform_interface: ^2.0.2
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^5.0.0
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec
# The following section is specific to Flutter packages.
flutter:
# This section identifies this Flutter project as a plugin project.
# The 'pluginClass' specifies the class (in Java, Kotlin, Swift, Objective-C, etc.)
# which should be registered in the plugin registry. This is required for
# using method channels.
# The Android 'package' specifies package in which the registered class is.
# This is required for using method channels on Android.
# The 'ffiPlugin' specifies that native code should be built and bundled.
# This is required for using `dart:ffi`.
# All these are used by the tooling to maintain consistency when
# adding or updating assets for this project.
plugin:
platforms:
android:
package: com.yunqiinnovation.location_service
pluginClass: LocationServicePlugin
ios:
pluginClass: LocationServicePlugin
# To add assets to your plugin package, add an assets section, like this:
# assets:
# - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg
#
# For details regarding assets in packages, see
# https://flutter.dev/to/asset-from-package
#
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/to/resolution-aware-images
# To add custom fonts to your plugin package, add a fonts section here,
# in this "flutter" section. Each entry in this list should have a
# "family" key with the font family name, and a "fonts" key with a
# list giving the asset and other descriptors for the font. For
# example:
# fonts:
# - family: Schyler
# fonts:
# - asset: fonts/Schyler-Regular.ttf
# - asset: fonts/Schyler-Italic.ttf
# style: italic
# - family: Trajan Pro
# fonts:
# - asset: fonts/TrajanPro.ttf
# - asset: fonts/TrajanPro_Bold.ttf
# weight: 700
#
# For details regarding fonts in packages, see
# https://flutter.dev/to/font-from-package

3
local_plugins/ota/ios/Classes/BleManager/JLBleManager.h

@ -15,6 +15,7 @@
#import <JL_OTALib/JL_OTALib.h> #import <JL_OTALib/JL_OTALib.h>
#import <JL_HashPair/JL_HashPair.h> #import <JL_HashPair/JL_HashPair.h>
#import <JL_AdvParse/JL_AdvParse.h> #import <JL_AdvParse/JL_AdvParse.h>
#import <Flutter/Flutter.h>
NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_BEGIN
@ -63,6 +64,8 @@ extern NSString *kFLT_BLE_DISCONNECTED; //断开连接
@property (strong, nonatomic) JLBleEntity *__nullable currentEntity; @property (strong, nonatomic) JLBleEntity *__nullable currentEntity;
@property (nonatomic, copy, nullable) FlutterEventSink flutterEventSink;
/** /**
* 单例 * 单例
*/ */

89
local_plugins/ota/ios/Classes/BleManager/JLBleManager.m

@ -206,8 +206,17 @@ NSString *FLT_BLE_RCSP_R = @"AE02"; //命令“读”通道
if (ble_name.length == 0) return; if (ble_name.length == 0) return;
kJLLog(JLLOG_DEBUG, @"发现 ----> NAME:%@ RSSI:%@ AD:%@", ble_name,RSSI,ble_AD); kJLLog(JLLOG_DEBUG, @"发现 ----> NAME:%@ RSSI:%@ AD:%@", ble_name,RSSI,ble_AD);
[self addPeripheral:peripheral RSSI:RSSI Name:ble_name Info:ble_AD]; if (ble_AD.length >= 29) {
const uint8_t *bytes = ble_AD.bytes;
NSMutableString *macStr = [NSMutableString string];
for (int i = 7; i <= 12; i++) {
uint8_t macByte = bytes[i] ^ 0x5A;
[macStr appendFormat:@"%02X", macByte];
if (i < 12) [macStr appendString:@":"];
}
[self addPeripheral:peripheral Info:macStr];
}
[[NSNotificationCenter defaultCenter] postNotificationName:kFLT_BLE_FOUND object:_blePeripheralArr userInfo:nil]; [[NSNotificationCenter defaultCenter] postNotificationName:kFLT_BLE_FOUND object:_blePeripheralArr userInfo:nil];
@ -217,11 +226,11 @@ NSString *FLT_BLE_RCSP_R = @"AE02"; //命令“读”通道
} }
} }
- (void)addPeripheral:(CBPeripheral*)peripheral RSSI:(NSNumber *)rssi Name:(NSString*)name Info:(NSData*)blead{ - (void)addPeripheral:(CBPeripheral*)peripheral Info:(NSString*)macStr {
// if ([name isEqualToString:@"DeapSound"]) { if (self.lastAddress && [macStr isEqualToString:self.lastAddress]) {
// [self stopScanBLE]; [self stopScanBLE];
// [self connectBLE:peripheral]; [self connectBLE:peripheral];
// } }
} }
#pragma mark 设备连接回调 #pragma mark 设备连接回调
@ -349,13 +358,7 @@ NSString *FLT_BLE_RCSP_R = @"AE02"; //命令“读”通道
if (needForcedUpgrade) { if (needForcedUpgrade) {
kJLLog(JLLOG_DEBUG, @"设备需要强制升级,请到升级界面选择ota升级文件进行升级!"); kJLLog(JLLOG_DEBUG, @"设备需要强制升级,请到升级界面选择ota升级文件进行升级!");
} }
NSString *documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject]; [self otaFuncWithFilePath:self.lastFilePath];
NSString *otaFilePath = [documentsPath stringByAppendingPathComponent:@"update030.ufw"];
if ([[NSFileManager defaultManager] fileExistsAtPath:otaFilePath]) {
[self otaFuncWithFilePath:otaFilePath];
} else {
kJLLog(JLLOG_DEBUG, @"OTA文件不存在: %@", otaFilePath);
}
}]; }];
}); });
} }
@ -396,6 +399,15 @@ NSString *FLT_BLE_RCSP_R = @"AE02"; //命令“读”通道
*/ */
- (void)otaFuncWithFilePath:(NSString *)otaFilePath { - (void)otaFuncWithFilePath:(NSString *)otaFilePath {
kJLLog(JLLOG_DEBUG, @"current otaFilePath ---> %@", otaFilePath); kJLLog(JLLOG_DEBUG, @"current otaFilePath ---> %@", otaFilePath);
if (self.flutterEventSink) {
self.flutterEventSink(@{
@"event": @"onStartOTA",
@"type": @0,
@"progress": @0.0,
@"code": @8,
@"message": @"开始升级"
});
}
self.selectedOtaFilePath = otaFilePath; self.selectedOtaFilePath = otaFilePath;
NSData *otaData = [[NSData alloc] initWithContentsOfFile:otaFilePath]; NSData *otaData = [[NSData alloc] initWithContentsOfFile:otaFilePath];
@ -425,6 +437,55 @@ NSString *FLT_BLE_RCSP_R = @"AE02"; //命令“读”通道
-(void)otaUpgradeResult:(JL_OTAResult)result Progress:(float)progress{ -(void)otaUpgradeResult:(JL_OTAResult)result Progress:(float)progress{
//TODO: 设备升级过程回调,包括进度状态 //TODO: 设备升级过程回调,包括进度状态
kJLLog(JLLOG_DEBUG, @"otaUpgradeResult ---> %u", result); kJLLog(JLLOG_DEBUG, @"otaUpgradeResult ---> %u", result);
if (result == JL_OTAResultPreparing) {
if (self.flutterEventSink) {
self.flutterEventSink(@{
@"event": @"onProgress",
@"type": @0,
@"progress": @(progress * 100),
@"code": @(result),
@"message": @"开始升级"
});
}
} else if (result == JL_OTAResultUpgrading) {
if (self.flutterEventSink) {
self.flutterEventSink(@{
@"event": @"onProgress",
@"type": @1,
@"progress": @(progress * 100),
@"code": @(result),
@"message": @"开始升级"
});
}
} else if (result == JL_OTAResultPrepared) {
} else if (result == JL_OTAResultReconnect) {
} else if (result == JL_OTAResultReconnectWithMacAddr) {
} else if (result == JL_OTAResultSuccess) {
if (self.flutterEventSink) {
self.flutterEventSink(@{
@"event": @"onStopOTA",
@"type": @0,
@"progress": @0.0,
@"code": @(result),
@"message": @"升级成功"
});
}
} else if (result == JL_OTAResultReboot) {
} else {
if (self.flutterEventSink) {
self.flutterEventSink(@{
@"event": @"onError",
@"type": @0,
@"progress": @0.0,
@"code": @(result),
@"message": @"升级失败"
});
}
}
} }
-(void)otaDataSend:(NSData *)data{ -(void)otaDataSend:(NSData *)data{

35
local_plugins/ota/ios/Classes/OtaPlugin.swift

@ -1,18 +1,24 @@
import Flutter import Flutter
public class OtaPlugin: NSObject, FlutterPlugin { public class OtaPlugin: NSObject, FlutterPlugin, FlutterStreamHandler {
let userManager = JLBleManager.sharedInstance() var userManager: JLBleManager?
var eventSink: FlutterEventSink?
public static func register(with registrar: FlutterPluginRegistrar) { public static func register(with registrar: FlutterPluginRegistrar) {
let channel = FlutterMethodChannel(name: "ota", binaryMessenger: registrar.messenger()) let channel = FlutterMethodChannel(name: "ota", binaryMessenger: registrar.messenger())
let eventChannel = FlutterEventChannel(name: "com.example.ota/events", binaryMessenger: registrar.messenger())
let instance = OtaPlugin() let instance = OtaPlugin()
registrar.addMethodCallDelegate(instance, channel: channel) registrar.addMethodCallDelegate(instance, channel: channel)
eventChannel.setStreamHandler(instance)
} }
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
let arguments = call.arguments as? [String: Any] let arguments = call.arguments as? [String: Any]
switch call.method { switch call.method {
case "init":
userManager = JLBleManager.sharedInstance()
result("")
case "startUpgrading": case "startUpgrading":
let address = arguments?["address"] as? String let address = arguments?["address"] as? String
let filePath = arguments?["filePath"] as? String let filePath = arguments?["filePath"] as? String
@ -26,14 +32,31 @@ public class OtaPlugin: NSObject, FlutterPlugin {
} }
} }
public func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? {
eventSink = events
return nil
}
public func onCancel(withArguments arguments: Any?) -> FlutterError? {
eventSink = nil
userManager?.flutterEventSink = nil
return nil
}
private func startUpgrading(address: String?, filePath: String?) { private func startUpgrading(address: String?, filePath: String?) {
if userManager.mBleManagerState == CBManagerState.poweredOn { if userManager?.mBleManagerState == CBManagerState.poweredOn {
userManager.startScanBLE(address, filePath: filePath) userManager?.flutterEventSink = eventSink
userManager?.startScanBLE(address, filePath: filePath)
DispatchQueue.main.asyncAfter(deadline: .now() + 10.0) { DispatchQueue.main.asyncAfter(deadline: .now() + 10.0) {
self.userManager.stopScanBLE() self.userManager?.stopScanBLE()
} }
} }
} }
private func onOtaClose() {} private func onOtaClose() {
userManager?.stopScanBLE()
userManager?.otaFuncCancel{code in}
userManager?.disconnectBLE()
userManager = nil
}
} }

4
local_plugins/ota/lib/ota.dart

@ -132,6 +132,10 @@ class Ota {
// _isEventListenerSet = true; // _isEventListenerSet = true;
// } // }
Future<void> init() {
return OtaPlatform.instance.init();
}
Future<void> startUpgrading({String? address, String? filePath}) { Future<void> startUpgrading({String? address, String? filePath}) {
return OtaPlatform.instance.startUpgrading( return OtaPlatform.instance.startUpgrading(
address: address, address: address,

5
local_plugins/ota/lib/ota_method_channel.dart

@ -9,6 +9,11 @@ class MethodChannelOta extends OtaPlatform {
@visibleForTesting @visibleForTesting
final methodChannel = const MethodChannel('ota'); final methodChannel = const MethodChannel('ota');
@override
Future<void> init() async {
await methodChannel.invokeMethod<String>('init');
}
@override @override
Future<void> startUpgrading({String? address, String? filePath}) async { Future<void> startUpgrading({String? address, String? filePath}) async {
await methodChannel.invokeMethod<String>('startUpgrading', { await methodChannel.invokeMethod<String>('startUpgrading', {

4
local_plugins/ota/lib/ota_platform_interface.dart

@ -23,6 +23,10 @@ abstract class OtaPlatform extends PlatformInterface {
_instance = instance; _instance = instance;
} }
Future<void> init() {
throw UnimplementedError('init() has not been implemented.');
}
Future<void> startUpgrading({String? address, String? filePath}) { Future<void> startUpgrading({String? address, String? filePath}) {
throw UnimplementedError('startUpgrading() has not been implemented.'); throw UnimplementedError('startUpgrading() has not been implemented.');
} }

2
pubspec.yaml

@ -91,6 +91,8 @@ dependencies:
path: local_plugins/music_service path: local_plugins/music_service
amap_service: amap_service:
path: local_plugins/amap_service path: local_plugins/amap_service
location_service:
path: local_plugins/location_service
realtime: realtime:
path: local_plugins/realtime path: local_plugins/realtime

Loading…
Cancel
Save