diff --git a/lib/data/services/ble_manager.dart b/lib/data/services/ble_manager.dart index 29fe982f0..c333607fd 100644 --- a/lib/data/services/ble_manager.dart +++ b/lib/data/services/ble_manager.dart @@ -989,6 +989,7 @@ class BleManager extends GetxService { 'stsDeviceId': stsDeviceId, 'stsDeviceSecret': stsDeviceSecret, 'stsCmei': stsCmei, + 'stsMac': mac, // 语音代理配置参数 'azureSpeechKey': azureSpeechKey, 'azureSpeechRegion': azureSpeechRegion, diff --git a/lib/modules/devices/views/devices_view.dart b/lib/modules/devices/views/devices_view.dart index 30b5570ee..4e710fd13 100644 --- a/lib/modules/devices/views/devices_view.dart +++ b/lib/modules/devices/views/devices_view.dart @@ -496,37 +496,66 @@ class DevicesView extends GetView { ), ], ), - // 下部:授权码 / CMEI - if (device.license.isNotEmpty || device.cmei.isNotEmpty) ...[ - SizedBox(height: 16.h), - Container( - padding: EdgeInsets.all(12.w), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(12.r), - color: isDark - ? Colors.white.withOpacity(0.03) - : const Color(0xFFF7F7FA), - ), - child: Column( - children: [ - if (device.license.isNotEmpty) - _buildFieldRow( - label: "licenseCode".tr, - value: device.license, - isDark: isDark, - ), - if (device.license.isNotEmpty && device.cmei.isNotEmpty) - SizedBox(height: 8.h), - if (device.cmei.isNotEmpty) - _buildFieldRow( - label: "CMEI", - value: device.cmei, - isDark: isDark, - ), - ], + // 下部:固件版本 / 授权码 / CMEI + Obx(() { + final bleManager = Get.find(); + // 必须无条件读取 deviceInfo.value,否则未连接设备的 Obx 内没有任何 + // observable,会触发 "improper use of a GetX" 错误 + final info = bleManager.deviceInfo.value; + final fwVersion = + connected ? (info?.firmwareVersion ?? '') : ''; + // 固件版本格式:左 3 段 + 右 3 段,拼成 "0.0.1.0.0.1";按 6 段拆左右 + final parts = fwVersion.split('.'); + final hasLeftRight = parts.length >= 6; + final fwDisplay = hasLeftRight + ? 'L ${parts.take(3).join('.')} · R ${parts.skip(3).take(3).join('.')}' + : fwVersion; + final hasFirmware = fwVersion.isNotEmpty; + final hasLicense = device.license.isNotEmpty; + final hasCmei = device.cmei.isNotEmpty; + + if (!hasFirmware && !hasLicense && !hasCmei) { + return const SizedBox.shrink(); + } + + return Padding( + padding: EdgeInsets.only(top: 16.h), + child: Container( + padding: EdgeInsets.all(12.w), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12.r), + color: isDark + ? Colors.white.withOpacity(0.03) + : const Color(0xFFF7F7FA), + ), + child: Column( + children: [ + if (hasFirmware) + _buildFieldRow( + label: "firmwareVersion".tr, + value: fwDisplay, + isDark: isDark, + ), + if (hasFirmware && (hasLicense || hasCmei)) + SizedBox(height: 8.h), + if (hasLicense) + _buildFieldRow( + label: "licenseCode".tr, + value: device.license, + isDark: isDark, + ), + if (hasLicense && hasCmei) SizedBox(height: 8.h), + if (hasCmei) + _buildFieldRow( + label: "CMEI", + value: device.cmei, + isDark: isDark, + ), + ], + ), ), - ), - ], + ); + }), ], ), ), @@ -541,7 +570,7 @@ class DevicesView extends GetView { return Row( children: [ SizedBox( - width: 44.w, + width: 72.w, child: Text( label, style: TextStyle( diff --git a/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt b/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt index 56158a387..c335e5aaa 100644 --- a/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt +++ b/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt @@ -496,6 +496,7 @@ object AgentService : CoroutineScope { deviceSecret = config["stsDeviceSecret"]?.toString() ?: "", agentId = agentId, cmei = config["stsCmei"]?.toString() ?: "", + mac = config["stsMac"]?.toString() ?: "", ) stsAgent = StsAgent( context = context, @@ -631,11 +632,15 @@ object AgentService : CoroutineScope { config["mcpServer"]?.toString() ?: "", config["musicServiceType"]?.toString() ?: "" ) - // 更新 STS 模式参数(cmei 等可能在首次初始化时尚未获取到) + // 更新 STS 模式参数(cmei / mac 等可能在首次初始化时尚未获取到) val newCmei = config["stsCmei"]?.toString() ?: "" if (newCmei.isNotEmpty()) { stsAgent?.updateCmei(newCmei) } + val newMac = config["stsMac"]?.toString() ?: "" + if (newMac.isNotEmpty()) { + stsAgent?.updateMac(newMac) + } } catch (e: Exception) { _log("ERROR", "[Agent] 应用配置更新失败: ${e.message}") } diff --git a/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/StsAgent.kt b/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/StsAgent.kt index 5601dfb5c..6ff8eaea7 100644 --- a/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/StsAgent.kt +++ b/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/StsAgent.kt @@ -41,6 +41,7 @@ data class StsConfig( var deviceSecret: String, val agentId: String, val cmei: String = "", + val mac: String = "", ) /** @@ -61,7 +62,7 @@ class StsAgent( override val coroutineContext = Dispatchers.IO + job init { - Log.i(TAG, "[YIDONG] StsAgent初始化: productId=${config.productId} deviceNo=${config.deviceNo} deviceId=${config.deviceId} agentId=${config.agentId} cmei=${config.cmei} hasSecret=${config.deviceSecret.isNotEmpty()}") + Log.i(TAG, "[YIDONG] StsAgent初始化: productId=${config.productId} deviceNo=${config.deviceNo} deviceId=${config.deviceId} agentId=${config.agentId} cmei=${config.cmei} mac=${config.mac} hasSecret=${config.deviceSecret.isNotEmpty()}") fetchCredentials() } @@ -73,6 +74,14 @@ class StsAgent( } } + /** 动态更新 MAC(首次初始化时可能尚未获取到) */ + fun updateMac(mac: String) { + if (mac.isNotEmpty() && mac != config.mac) { + config = config.copy(mac = mac) + Log.i(TAG, "[YIDONG] updateMac: mac已更新为 $mac") + } + } + // ── WebSocket ───────────────────────────────────────────────────────────── private val okHttpClient: OkHttpClient = buildTrustAllClient() private var webSocket: WebSocket? = null @@ -238,7 +247,7 @@ class StsAgent( return } prefs.edit().putLong("last_heartbeat_ts", now).apply() - Log.i(TAG, "[YIDONG] reportHeartbeat: 开始上报 deviceId=${config.deviceId} deviceNo=${config.deviceNo} cmei=${config.cmei}") + Log.i(TAG, "[YIDONG] reportHeartbeat: 开始上报 deviceId=${config.deviceId} deviceNo=${config.deviceNo} cmei=${config.cmei} mac=${config.mac}") launch { try { val netType = try { @@ -261,12 +270,12 @@ class StsAgent( put("innerIp", JSONArray().put("127.0.0.1")) put("netSpeed", "0Mbps") put("netType", netType) - put("platform", "Android${android.os.Build.VERSION.RELEASE}") + put("platform", "") put("sdkVersion", "ai_http_1.0") put("firmwareVersion", "1.0.0") put("imei", "") put("cmei", config.cmei) // 优先使用 UserDevice 的 CMEI - put("mac", "") + put("mac", config.mac) // 当前连接的 BLE 设备 MAC(由 Flutter 透传) }) }.toString().toRequestBody("application/json".toMediaType()) diff --git a/local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServiceImpl.swift b/local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServiceImpl.swift index 0db3caec3..80af5c5b7 100644 --- a/local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServiceImpl.swift +++ b/local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServiceImpl.swift @@ -271,7 +271,8 @@ class AgentServiceImpl: NSObject { deviceId: config["stsDeviceId"] as? String ?? "", deviceSecret: config["stsDeviceSecret"] as? String ?? "", agentId: agentId, - cmei: config["stsCmei"] as? String ?? "" + cmei: config["stsCmei"] as? String ?? "", + mac: config["stsMac"] as? String ?? "" ) stsAgent = StsAgent( config: stsConfig, diff --git a/local_plugins/agent_service/ios/agent_service/Sources/agent_service/StsAgent.swift b/local_plugins/agent_service/ios/agent_service/Sources/agent_service/StsAgent.swift index 477eb78cb..d5218631b 100644 --- a/local_plugins/agent_service/ios/agent_service/Sources/agent_service/StsAgent.swift +++ b/local_plugins/agent_service/ios/agent_service/Sources/agent_service/StsAgent.swift @@ -15,6 +15,7 @@ struct StsConfig { var deviceSecret: String let agentId: String var cmei: String = "" + var mac: String = "" } // MARK: - StsAgent @@ -87,9 +88,9 @@ class StsAgent: NSObject { super.init() let sessionConfig = URLSessionConfiguration.default urlSession = URLSession(configuration: sessionConfig, delegate: nil, delegateQueue: OperationQueue()) - os_log("StsAgent 初始化: productId=%{public}@ deviceNo=%{public}@ deviceId=%{public}@ cmei=%{public}@ hasSecret=%{public}@", + os_log("StsAgent 初始化: productId=%{public}@ deviceNo=%{public}@ deviceId=%{public}@ cmei=%{public}@ mac=%{public}@ hasSecret=%{public}@", log: logger, type: .info, - config.productId, config.deviceNo, config.deviceId, config.cmei, + config.productId, config.deviceNo, config.deviceId, config.cmei, config.mac, config.deviceSecret.isEmpty ? "false" : "true") // 与 Android 行为对齐:凭证已有时立即尝试上报一次心跳(受 1 小时冷却保护) if !config.deviceSecret.isEmpty && !config.deviceId.isEmpty { @@ -105,6 +106,13 @@ class StsAgent: NSObject { os_log("updateCmei: cmei 已更新为 %{public}@", log: logger, type: .info, cmei) } + /// 动态更新 MAC(首次初始化时可能尚未获取到) + func updateMac(_ mac: String) { + guard !mac.isEmpty, mac != config.mac else { return } + config.mac = mac + os_log("updateMac: mac 已更新为 %{public}@", log: logger, type: .info, mac) + } + // MARK: - 公共接口 func startCall() { @@ -233,8 +241,8 @@ class StsAgent: NSObject { return } defaults.set(now, forKey: HEARTBEAT_PREFS_KEY) - os_log("reportHeartbeat: 开始上报 deviceId=%{public}@ deviceNo=%{public}@ cmei=%{public}@", - log: logger, type: .info, config.deviceId, config.deviceNo, config.cmei) + os_log("reportHeartbeat: 开始上报 deviceId=%{public}@ deviceNo=%{public}@ cmei=%{public}@ mac=%{public}@", + log: logger, type: .info, config.deviceId, config.deviceNo, config.cmei, config.mac) let urlStr = "https://ivs.chinamobiledevice.com:11443/v2/customer/device/report" guard let url = URL(string: urlStr) else { return } @@ -243,17 +251,16 @@ class StsAgent: NSObject { req.setValue("application/json", forHTTPHeaderField: "Content-Type") req.timeoutInterval = 10 - let systemVersion = UIDevice.current.systemVersion let params: [String: Any] = [ "innerIp": ["127.0.0.1"], "netSpeed": "0Mbps", "netType": "WiFi", - "platform": "iOS\(systemVersion)", + "platform": "", "sdkVersion": "ai_http_1.0", "firmwareVersion": "1.0.0", "imei": "", "cmei": config.cmei, - "mac": "", + "mac": config.mac, ] let bodyDict: [String: Any] = [ "deviceId": config.deviceId,