Browse Source

上传代码

newdev_shunjiawei
liwei1dao 5 months ago
parent
commit
e78e409a0a
  1. 2
      lib/data/models/appconfig.dart
  2. 37
      lib/data/services/ble_manager.dart
  3. 5
      lib/modules/mobile_elf/controllers/mobile_elf_controller.dart
  4. 11
      local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt
  5. 15
      local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentServicePlugin.kt
  6. 6
      local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/BleMediaService.kt
  7. 38
      local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/StsAgent.kt
  8. 21
      local_plugins/agent_service/lib/agent_service.dart

2
lib/data/models/appconfig.dart

@ -7,7 +7,7 @@ class AppConfig {
// true => 首页/发现页切换为精简布局,新手引导跳过 AI 引导步骤 // true => 首页/发现页切换为精简布局,新手引导跳过 AI 引导步骤
// false => 保持原有布局 // false => 保持原有布局
// TODO: 后续接入 BLE PID 判断逻辑,B017 设备连接时自动置 true // TODO: 后续接入 BLE PID 判断逻辑,B017 设备连接时自动置 true
static bool hideAiAssistant = true; static bool hideAiAssistant = false;
static late UserGetAppConfigResp config; static late UserGetAppConfigResp config;

37
lib/data/services/ble_manager.dart

@ -539,11 +539,45 @@ class BleManager extends GetxService {
); );
Logger.info('设备版本信息已更新: 版本=$version'); Logger.info('设备版本信息已更新: 版本=$version');
// 主动透传给原生 STS 心跳所需字段,避免上报数据不完整
pushStsDeviceInfoToNative();
} catch (e) { } catch (e) {
Logger.error('更新版本信息失败: ${e.toString()}'); Logger.error('更新版本信息失败: ${e.toString()}');
} }
} }
/// 主动透传当前 BLE 设备 MAC / 固件版本(左3段) / CMEI 给原生 STS Agent
///
/// 任一字段被首次填上都会在原生侧触发一次心跳补报。非 STS 模式下原生侧自然 no-op。
void pushStsDeviceInfoToNative() {
try {
final mac = currentDeviceAddress?.toUpperCase() ?? '';
// 固件版本原始为 "L1.L2.L3.R1.R2.R3",STS 平台只取左 3 段
final rawFw = deviceInfo.value?.firmwareVersion ?? '';
final fwParts = rawFw.split('.');
final stsFirmwareVersion =
fwParts.length >= 3 ? fwParts.take(3).join('.') : rawFw;
String stsCmei = '';
if (mac.isNotEmpty) {
try {
final matchedDevice = userInfo.User.instance.devices.firstWhere(
(d) => d.devicemac.toUpperCase() == mac,
);
stsCmei = matchedDevice.cmei;
} catch (_) {}
}
if (mac.isEmpty && stsFirmwareVersion.isEmpty && stsCmei.isEmpty) return;
AgentService.updateStsDeviceInfo(
stsMac: mac,
stsFirmwareVersion: stsFirmwareVersion,
stsCmei: stsCmei,
);
} catch (e) {
Logger.error('透传STS设备信息失败: $e');
}
}
/// 更新设备id,并检测是否为移动精灵设备 /// 更新设备id,并检测是否为移动精灵设备
void _updateProductIdFromData(Map<dynamic, dynamic> data) { void _updateProductIdFromData(Map<dynamic, dynamic> data) {
try { try {
@ -1376,6 +1410,9 @@ class BleManager extends GetxService {
Logger.info( Logger.info(
'保存设备信息: address=$currentDeviceAddress, name=$_currentDeviceName'); '保存设备信息: address=$currentDeviceAddress, name=$_currentDeviceName');
// 主动透传给原生 STS 心跳所需字段(mac/cmei 此时已知,固件版本待后续回调补齐)
pushStsDeviceInfoToNative();
} }
return await _bleService.registerBackgroundScan( return await _bleService.registerBackgroundScan(

5
lib/modules/mobile_elf/controllers/mobile_elf_controller.dart

@ -8,6 +8,7 @@ import 'package:permission_handler/permission_handler.dart';
import '../../../core/utils/logger.dart'; import '../../../core/utils/logger.dart';
import '../../../core/utils/permission_util.dart'; import '../../../core/utils/permission_util.dart';
import '../../../data/services/ble_manager.dart';
enum MobileElfStatus { idle, connecting, recording, thinking, speaking, error } enum MobileElfStatus { idle, connecting, recording, thinking, speaking, error }
@ -80,6 +81,10 @@ class MobileElfController extends GetxController {
}); });
// 主动触发认证检查(页面打开时,确保 credential_failed 事件不被错过) // 主动触发认证检查(页面打开时,确保 credential_failed 事件不被错过)
AgentService.fetchCredentials(); AgentService.fetchCredentials();
// 页面打开时再透传一次 STS 心跳所需的设备信息,兜底覆盖 BLE 早于 StsAgent 完成连接的场景
try {
BleManager.to.pushStsDeviceInfoToNative();
} catch (_) {}
} }
Future<void> _loadChatHistory() async { Future<void> _loadChatHistory() async {

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

@ -1292,6 +1292,17 @@ object AgentService : CoroutineScope {
stsAgent?.fetchCredentials() stsAgent?.fetchCredentials()
} }
/**
* Flutter 层在拿到 BLE 设备 MAC / 固件版本 / CMEI 之后主动透传。
* 凡是非空字段都会写入 StsAgent,若某字段从空首次填上则会触发一次心跳上报;
* 心跳本身在原生层做完整性校验,缺字段会自动跳过。
*/
fun updateStsDeviceInfo(mac: String?, firmwareVersion: String?, cmei: String?) {
if (!mac.isNullOrEmpty()) stsAgent?.updateMac(mac)
if (!firmwareVersion.isNullOrEmpty()) stsAgent?.updateFirmwareVersion(firmwareVersion)
if (!cmei.isNullOrEmpty()) stsAgent?.updateCmei(cmei)
}
fun interruptCurrentResponse() { fun interruptCurrentResponse() {
if (agentMode == "sts") { if (agentMode == "sts") {
stsAgent?.interruptSpeaking() stsAgent?.interruptSpeaking()

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

@ -106,6 +106,8 @@ class AgentServicePlugin : FlutterPlugin, MethodCallHandler, EventChannel.Stream
config["stsDeviceId"]?.toString()?.let { intent.putExtra("stsDeviceId", it) } config["stsDeviceId"]?.toString()?.let { intent.putExtra("stsDeviceId", it) }
config["stsDeviceSecret"]?.toString()?.let { intent.putExtra("stsDeviceSecret", it) } config["stsDeviceSecret"]?.toString()?.let { intent.putExtra("stsDeviceSecret", it) }
config["stsCmei"]?.toString()?.let { intent.putExtra("stsCmei", it) } config["stsCmei"]?.toString()?.let { intent.putExtra("stsCmei", it) }
config["stsMac"]?.toString()?.let { intent.putExtra("stsMac", it) }
config["stsFirmwareVersion"]?.toString()?.let { intent.putExtra("stsFirmwareVersion", it) }
config["vipexptime"]?.toString()?.let { config["vipexptime"]?.toString()?.let {
intent.putExtra("vipexptime", it) intent.putExtra("vipexptime", it)
//打印日志 //打印日志
@ -384,6 +386,19 @@ class AgentServicePlugin : FlutterPlugin, MethodCallHandler, EventChannel.Stream
} }
} }
"updateStsDeviceInfo" -> {
try {
val mac = call.argument<String>("stsMac")
val firmwareVersion = call.argument<String>("stsFirmwareVersion")
val cmei = call.argument<String>("stsCmei")
AgentService.updateStsDeviceInfo(mac, firmwareVersion, cmei)
result.success(true)
} catch (e: Exception) {
Log.e(TAG, "更新STS设备信息失败", e)
result.error("UPDATE_STS_DEVICE_INFO_ERROR", "更新STS设备信息失败: ${e.message}", null)
}
}
"setCanWakeUp" -> { "setCanWakeUp" -> {
try { try {
val canWakeUp = call.argument<Boolean>("canWakeUp") val canWakeUp = call.argument<Boolean>("canWakeUp")

6
local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/BleMediaService.kt

@ -86,6 +86,8 @@ class BleMediaService : Service(), CoroutineScope {
private const val KEY_STS_DEVICE_ID = "stsDeviceId" private const val KEY_STS_DEVICE_ID = "stsDeviceId"
private const val KEY_STS_DEVICE_SECRET = "stsDeviceSecret" private const val KEY_STS_DEVICE_SECRET = "stsDeviceSecret"
private const val KEY_STS_CMEI = "stsCmei" private const val KEY_STS_CMEI = "stsCmei"
private const val KEY_STS_MAC = "stsMac"
private const val KEY_STS_FIRMWARE_VERSION = "stsFirmwareVersion"
// 用于跟踪服务是否正在运行 // 用于跟踪服务是否正在运行
private val isRunning = AtomicBoolean(false) private val isRunning = AtomicBoolean(false)
@ -623,6 +625,8 @@ private var companionManager: BleCompanionManager? = null
it.getStringExtra(KEY_STS_DEVICE_ID)?.let { value -> config[KEY_STS_DEVICE_ID] = value } it.getStringExtra(KEY_STS_DEVICE_ID)?.let { value -> config[KEY_STS_DEVICE_ID] = value }
it.getStringExtra(KEY_STS_DEVICE_SECRET)?.let { value -> config[KEY_STS_DEVICE_SECRET] = value } it.getStringExtra(KEY_STS_DEVICE_SECRET)?.let { value -> config[KEY_STS_DEVICE_SECRET] = value }
it.getStringExtra(KEY_STS_CMEI)?.let { value -> config[KEY_STS_CMEI] = value } it.getStringExtra(KEY_STS_CMEI)?.let { value -> config[KEY_STS_CMEI] = value }
it.getStringExtra(KEY_STS_MAC)?.let { value -> config[KEY_STS_MAC] = value }
it.getStringExtra(KEY_STS_FIRMWARE_VERSION)?.let { value -> config[KEY_STS_FIRMWARE_VERSION] = value }
Log.d(TAG, "liwei-------vipexptime 3-1 ${ config[KEY_VIP_EXPTIME]}") Log.d(TAG, "liwei-------vipexptime 3-1 ${ config[KEY_VIP_EXPTIME]}")
// 提取 supportedLanguages 参数 // 提取 supportedLanguages 参数
it.getStringArrayListExtra(KEY_SUPPORTED_LANGUAGES)?.let { languages -> it.getStringArrayListExtra(KEY_SUPPORTED_LANGUAGES)?.let { languages ->
@ -698,6 +702,8 @@ private var companionManager: BleCompanionManager? = null
config[KEY_STS_DEVICE_ID] = getConfigString(prefs, KEY_STS_DEVICE_ID, "") config[KEY_STS_DEVICE_ID] = getConfigString(prefs, KEY_STS_DEVICE_ID, "")
config[KEY_STS_DEVICE_SECRET] = getConfigString(prefs, KEY_STS_DEVICE_SECRET, "") config[KEY_STS_DEVICE_SECRET] = getConfigString(prefs, KEY_STS_DEVICE_SECRET, "")
config[KEY_STS_CMEI] = getConfigString(prefs, KEY_STS_CMEI, "") config[KEY_STS_CMEI] = getConfigString(prefs, KEY_STS_CMEI, "")
config[KEY_STS_MAC] = getConfigString(prefs, KEY_STS_MAC, "")
config[KEY_STS_FIRMWARE_VERSION] = getConfigString(prefs, KEY_STS_FIRMWARE_VERSION, "")
Log.d(TAG, "liwei-------vipexptime 3-0 ${ config[KEY_VIP_EXPTIME]}") Log.d(TAG, "liwei-------vipexptime 3-0 ${ config[KEY_VIP_EXPTIME]}")
// 添加 supportedLanguages 参数处理 // 添加 supportedLanguages 参数处理
val supportedLanguagesStr = getConfigString(prefs, KEY_SUPPORTED_LANGUAGES, "") val supportedLanguagesStr = getConfigString(prefs, KEY_SUPPORTED_LANGUAGES, "")

38
local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/StsAgent.kt

@ -70,24 +70,30 @@ class StsAgent(
/** 动态更新 CMEI(首次初始化时可能尚未获取到) */ /** 动态更新 CMEI(首次初始化时可能尚未获取到) */
fun updateCmei(cmei: String) { fun updateCmei(cmei: String) {
if (cmei.isNotEmpty() && cmei != config.cmei) { if (cmei.isNotEmpty() && cmei != config.cmei) {
val wasEmpty = config.cmei.isEmpty()
config = config.copy(cmei = cmei) config = config.copy(cmei = cmei)
Log.i(TAG, "[YIDONG] updateCmei: cmei已更新为 $cmei") Log.i(TAG, "[YIDONG] updateCmei: cmei已更新为 $cmei")
if (wasEmpty) reportHeartbeat()
} }
} }
/** 动态更新 MAC(首次初始化时可能尚未获取到) */ /** 动态更新 MAC(首次初始化时可能尚未获取到) */
fun updateMac(mac: String) { fun updateMac(mac: String) {
if (mac.isNotEmpty() && mac != config.mac) { if (mac.isNotEmpty() && mac != config.mac) {
val wasEmpty = config.mac.isEmpty()
config = config.copy(mac = mac) config = config.copy(mac = mac)
Log.i(TAG, "[YIDONG] updateMac: mac已更新为 $mac") Log.i(TAG, "[YIDONG] updateMac: mac已更新为 $mac")
if (wasEmpty) reportHeartbeat()
} }
} }
/** 动态更新固件版本(设备连接后由 BLE 上报,首次初始化时通常尚未获取到) */ /** 动态更新固件版本(设备连接后由 BLE 上报,首次初始化时通常尚未获取到) */
fun updateFirmwareVersion(firmwareVersion: String) { fun updateFirmwareVersion(firmwareVersion: String) {
if (firmwareVersion.isNotEmpty() && firmwareVersion != config.firmwareVersion) { if (firmwareVersion.isNotEmpty() && firmwareVersion != config.firmwareVersion) {
val wasEmpty = config.firmwareVersion.isEmpty()
config = config.copy(firmwareVersion = firmwareVersion) config = config.copy(firmwareVersion = firmwareVersion)
Log.i(TAG, "[YIDONG] updateFirmwareVersion: firmwareVersion已更新为 $firmwareVersion") Log.i(TAG, "[YIDONG] updateFirmwareVersion: firmwareVersion已更新为 $firmwareVersion")
if (wasEmpty) reportHeartbeat()
} }
} }
@ -181,7 +187,12 @@ class StsAgent(
private set private set
// ── 心跳冷却 ────────────────────────────────────────────────────────────── // ── 心跳冷却 ──────────────────────────────────────────────────────────────
// 同一进程内 StsAgent 会被反复重建(切页面/BLE 重连等),需避免重复上报;
// 但进程冷启动必须能再报一次,所以用进程级静态变量而非 SharedPreferences。
private val HEARTBEAT_COOLDOWN_MS = 3_600_000L // 1 小时 private val HEARTBEAT_COOLDOWN_MS = 3_600_000L // 1 小时
companion object {
@Volatile private var lastHeartbeatTs: Long = 0L
}
// ═════════════════════════════════════════════════════════════════════════ // ═════════════════════════════════════════════════════════════════════════
// 凭证获取 // 凭证获取
@ -246,16 +257,29 @@ class StsAgent(
Log.w(TAG, "[YIDONG] reportHeartbeat: 凭证未就绪,跳过上报") Log.w(TAG, "[YIDONG] reportHeartbeat: 凭证未就绪,跳过上报")
return return
} }
// 1 小时冷却:StsAgent 会被反复重建,init/fetchCredentials 都会调到这里, // 数据完整性校验:deviceId / cmei / mac / firmwareVersion 任一为空都不上报。
// 必须在原生层拦,否则每次重建都会打一发心跳 // 这些字段在 BLE 配对/连接成功后才会透传过来,首次初始化时通常缺失;
val prefs = context.getSharedPreferences("sts_agent", Context.MODE_PRIVATE) // 空数据上报既无意义,又会把 1 小时冷却时间戳写死,阻塞 update*() 触发的补报。
val last = prefs.getLong("last_heartbeat_ts", 0L) val missing = buildList {
if (config.deviceId.isEmpty()) add("deviceId")
if (config.cmei.isEmpty()) add("cmei")
if (config.mac.isEmpty()) add("mac")
if (config.firmwareVersion.isEmpty()) add("firmwareVersion")
}
if (missing.isNotEmpty()) {
Log.i(TAG, "[YIDONG] reportHeartbeat: 数据不完整,跳过上报 missing=${missing.joinToString(",")} deviceId=${config.deviceId} cmei=${config.cmei} mac=${config.mac} firmwareVersion=${config.firmwareVersion}")
return
}
// 进程级 1 小时冷却:同一进程内 StsAgent 会被反复重建(切页面/BLE 重连等),
// init/fetchCredentials 都会调到这里,必须在原生层拦;
// 但冷却仅在进程生命周期内有效,进程冷启动后归零,保证每次启动都能上报一次。
val last = lastHeartbeatTs
val now = System.currentTimeMillis() val now = System.currentTimeMillis()
if (last > 0L && now - last < HEARTBEAT_COOLDOWN_MS) { if (last > 0L && now - last < HEARTBEAT_COOLDOWN_MS) {
Log.i(TAG, "[YIDONG] reportHeartbeat: 距上次 ${(now - last) / 1000}s,冷却中跳过") Log.i(TAG, "[YIDONG] reportHeartbeat: 距上次 ${(now - last) / 1000}s,冷却中跳过")
return return
} }
prefs.edit().putLong("last_heartbeat_ts", now).apply() lastHeartbeatTs = now
Log.i(TAG, "[YIDONG] reportHeartbeat: 开始上报 deviceId=${config.deviceId} deviceNo=${config.deviceNo} cmei=${config.cmei} mac=${config.mac} firmwareVersion=${config.firmwareVersion}") Log.i(TAG, "[YIDONG] reportHeartbeat: 开始上报 deviceId=${config.deviceId} deviceNo=${config.deviceNo} cmei=${config.cmei} mac=${config.mac} firmwareVersion=${config.firmwareVersion}")
launch { launch {
try { try {
@ -303,13 +327,13 @@ class StsAgent(
} else { } else {
Log.e(TAG, "[YIDONG] reportHeartbeat: 上报失败 code=$code msg=$msg") Log.e(TAG, "[YIDONG] reportHeartbeat: 上报失败 code=$code msg=$msg")
// 上报失败回滚时间戳,避免卡 1 小时无法重试 // 上报失败回滚时间戳,避免卡 1 小时无法重试
prefs.edit().putLong("last_heartbeat_ts", last).apply() lastHeartbeatTs = last
} }
} }
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "[YIDONG] reportHeartbeat: 请求异常 ${e.message}") Log.e(TAG, "[YIDONG] reportHeartbeat: 请求异常 ${e.message}")
// 异常同样回滚时间戳 // 异常同样回滚时间戳
prefs.edit().putLong("last_heartbeat_ts", last).apply() lastHeartbeatTs = last
} }
} }
} }

21
local_plugins/agent_service/lib/agent_service.dart

@ -514,6 +514,27 @@ class AgentService {
} }
} }
/// 主动透传 BLE 设备 MAC / 固件版本 / CMEI 到原生层(移动精灵)
///
/// 这些字段在 startAgentService 时可能尚未拿到,需要 Flutter 在后续
/// 拿到后主动调用此方法补齐;原生侧任一空字段被首次填上都会触发一次心跳上报。
/// 传 null 或空字符串的字段会被忽略,不会覆盖已有值。
static Future<void> updateStsDeviceInfo({
String? stsMac,
String? stsFirmwareVersion,
String? stsCmei,
}) async {
try {
await _channel.invokeMethod('updateStsDeviceInfo', {
'stsMac': stsMac,
'stsFirmwareVersion': stsFirmwareVersion,
'stsCmei': stsCmei,
});
} on PlatformException catch (_) {
// 透传失败不影响主流程,原生侧会在下次调用时再补
}
}
/// 中断当前响应 /// 中断当前响应
/// ///
/// 停止语音合成和AI流输出 /// 停止语音合成和AI流输出

Loading…
Cancel
Save