From 8c687aa76e7c49fb04f628f675132a505a5865d6 Mon Sep 17 00:00:00 2001 From: Sucan <632190820@qq.com> Date: Wed, 11 Mar 2026 15:30:23 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E5=AF=BC=E8=88=AA?= =?UTF-8?q?=E6=8E=A7=E5=88=B6=E4=B8=8EMQTT=E5=91=BD=E4=BB=A4=E5=A4=84?= =?UTF-8?q?=E7=90=86=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增NavController类,封装机器人导航相关操作(前往、停止、巡逻等) - 扩展MqttManager以支持JSON命令解析,处理导航与语音指令 - 在AndroidManifest中添加temimetadata声明,使应用作为技能运行 - 移除设置界面中的日志显示功能,简化UI - 优化主界面布局结构,修复缩进问题 - 添加到达目的地自动语音播报功能 - 固定表情视图尺寸,确保显示一致性 --- app/src/main/AndroidManifest.xml | 5 +- .../lzwcai_terminal_temi/MainActivity.kt | 42 ++++++++- .../lzwcai_terminal_temi/MqttManager.kt | 88 ++++++++++++++++++- .../lzwcai_terminal_temi/NavController.kt | 37 ++++++++ app/src/main/res/layout/activity_main.xml | 66 +++++++------- app/src/main/res/layout/activity_settings.xml | 35 +------- app/src/main/res/values/strings.xml | 6 +- 7 files changed, 199 insertions(+), 80 deletions(-) create mode 100644 app/src/main/java/com/example/lzwcai_terminal_temi/NavController.kt diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index f69a4f1..3f065ee 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -16,6 +16,9 @@ android:supportsRtl="true" android:theme="@style/Theme.Lzwcaiterminaltemi" tools:targetApi="31"> + @@ -31,4 +34,4 @@ - \ No newline at end of file + diff --git a/app/src/main/java/com/example/lzwcai_terminal_temi/MainActivity.kt b/app/src/main/java/com/example/lzwcai_terminal_temi/MainActivity.kt index 356c5f7..9318a31 100644 --- a/app/src/main/java/com/example/lzwcai_terminal_temi/MainActivity.kt +++ b/app/src/main/java/com/example/lzwcai_terminal_temi/MainActivity.kt @@ -12,14 +12,20 @@ import com.example.lzwcai_terminal_temi.databinding.ActivityMainBinding import com.robotemi.sdk.Robot import com.robotemi.sdk.TtsRequest import com.robotemi.sdk.Robot.TtsListener +import com.robotemi.sdk.listeners.OnGoToLocationStatusChangedListener import com.robotemi.sdk.listeners.OnRobotReadyListener -class MainActivity : AppCompatActivity(), OnRobotReadyListener, TtsListener, SharedPreferences.OnSharedPreferenceChangeListener { +class MainActivity : AppCompatActivity(), OnRobotReadyListener, TtsListener, OnGoToLocationStatusChangedListener, SharedPreferences.OnSharedPreferenceChangeListener { private lateinit var robot: Robot private lateinit var binding: ActivityMainBinding private var mqttManager: MqttManager? = null private lateinit var prefs: SharedPreferences + private lateinit var navCon: NavController + private var lastArrivalLocation: String? = null + private var lastArrivalAt: Long = 0L + private val fixedFaceScale = 1.0f + private val baseFaceSizeDp = 1000f @SuppressLint("SetTextI18n") override fun onCreate(savedInstanceState: Bundle?) { @@ -30,6 +36,7 @@ class MainActivity : AppCompatActivity(), OnRobotReadyListener, TtsListener, Sha LogManager.startLogcatListener() window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN) robot = Robot.getInstance() + navCon = NavController(robot) prefs = getSharedPreferences("app_prefs", Context.MODE_PRIVATE) @@ -46,6 +53,7 @@ class MainActivity : AppCompatActivity(), OnRobotReadyListener, TtsListener, Sha } binding.animatedEmojiView.currentExpression = AnimatedEmojiView.Expression.SMILE + applyFaceScale(fixedFaceScale) updateMqttConnection() } @@ -54,6 +62,7 @@ class MainActivity : AppCompatActivity(), OnRobotReadyListener, TtsListener, Sha super.onStart() robot.addOnRobotReadyListener(this) robot.addTtsListener(this) + robot.addOnGoToLocationStatusChangedListener(this) prefs.registerOnSharedPreferenceChangeListener(this) mqttManager?.connect() } @@ -62,13 +71,13 @@ class MainActivity : AppCompatActivity(), OnRobotReadyListener, TtsListener, Sha super.onStop() robot.removeOnRobotReadyListener(this) robot.removeTtsListener(this) + robot.removeOnGoToLocationStatusChangedListener(this) prefs.unregisterOnSharedPreferenceChangeListener(this) mqttManager?.disconnect() } override fun onDestroy() { super.onDestroy() - // 确保在应用销毁时彻底释放资源 mqttManager?.disconnect() LogManager.stopLogcatListener() Log.i("MainActivity", "All resources released on destroy.") @@ -108,6 +117,23 @@ class MainActivity : AppCompatActivity(), OnRobotReadyListener, TtsListener, Sha } } + override fun onGoToLocationStatusChanged(location: String, status: String, descriptionId: Int, description: String) { + val normalized = status.lowercase() + if (normalized != "complete") { + return + } + val now = System.currentTimeMillis() + if (lastArrivalLocation == location && now - lastArrivalAt < 5000L) { + return + } + lastArrivalLocation = location + lastArrivalAt = now + val text = "已到达$location" + val ttsRequest = TtsRequest.create(text, false, language = TtsRequest.Language.ZH_CN) + robot.speak(ttsRequest) + Log.i("MainActivity", "Arrived at $location, announcement sent.") + } + override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) { if (key == "network_ip") { Log.i("MainActivity", "IP address changed, re-initializing MQTT connection.") @@ -119,7 +145,7 @@ class MainActivity : AppCompatActivity(), OnRobotReadyListener, TtsListener, Sha mqttManager?.disconnect() val ip = prefs.getString("network_ip", null) if (!ip.isNullOrEmpty()) { - mqttManager = MqttManager(this, ip) + mqttManager = MqttManager(this, ip, robot, navCon) mqttManager?.connect() Log.i("MainActivity", "MQTT Manager updated with new IP: $ip") } else { @@ -127,4 +153,14 @@ class MainActivity : AppCompatActivity(), OnRobotReadyListener, TtsListener, Sha Log.w("MainActivity", "MQTT Manager disabled: IP address is not set.") } } + + private fun applyFaceScale(scale: Float) { + val density = resources.displayMetrics.density + val sizePx = (baseFaceSizeDp * density * scale).toInt() + val params = binding.animatedEmojiView.layoutParams + params.width = sizePx + params.height = sizePx + binding.animatedEmojiView.layoutParams = params + binding.animatedEmojiView.requestLayout() + } } diff --git a/app/src/main/java/com/example/lzwcai_terminal_temi/MqttManager.kt b/app/src/main/java/com/example/lzwcai_terminal_temi/MqttManager.kt index ec8e50c..032a43a 100644 --- a/app/src/main/java/com/example/lzwcai_terminal_temi/MqttManager.kt +++ b/app/src/main/java/com/example/lzwcai_terminal_temi/MqttManager.kt @@ -2,11 +2,19 @@ package com.example.lzwcai_terminal_temi import android.content.Context import android.util.Log +import com.robotemi.sdk.Robot +import com.robotemi.sdk.TtsRequest import kotlinx.coroutines.* import org.eclipse.paho.client.mqttv3.* import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence +import org.json.JSONObject -class MqttManager(private val context: Context, private val serverIp: String) { +class MqttManager( + private val context: Context, + private val serverIp: String, + private val robot: Robot, + private val navController: NavController +) { private var mqttClient: MqttClient? = null private val TAG = "MqttManager" @@ -33,10 +41,10 @@ class MqttManager(private val context: Context, private val serverIp: String) { override fun messageArrived(topic: String?, message: MqttMessage?) { val payload = String(message?.payload ?: ByteArray(0)) Log.i(TAG, "Message arrived: Topic=$topic, Payload=$payload") + handleIncomingMessage(topic, payload) } override fun deliveryComplete(token: IMqttDeliveryToken?) { - // This is not critical for our use case } }) } catch (e: MqttException) { @@ -70,7 +78,7 @@ class MqttManager(private val context: Context, private val serverIp: String) { private fun scheduleReconnect() { if (reconnectJob?.isActive == true) { - return // 如果已经在计划重连,则不再创建新的 + return } reconnectJob = scope.launch { Log.d(TAG, "Scheduling reconnect in 5 seconds.") @@ -82,7 +90,7 @@ class MqttManager(private val context: Context, private val serverIp: String) { fun disconnect() { scope.launch { try { - reconnectJob?.cancel() // 取消任何待处理的重连任务 + reconnectJob?.cancel() if (mqttClient?.isConnected == true) { mqttClient?.disconnect() Log.i(TAG, "Disconnected from MQTT broker.") @@ -123,4 +131,76 @@ class MqttManager(private val context: Context, private val serverIp: String) { } } } + + private fun handleIncomingMessage(topic: String?, payload: String) { + val trimmed = payload.trim() + if (trimmed.isEmpty()) { + return + } + if (!trimmed.startsWith("{")) { + Log.w(TAG, "Ignored non-JSON payload on ${topic ?: "unknown"}: $payload") + return + } + try { + val obj = JSONObject(trimmed) + handleJsonCommand(obj) + } catch (e: Exception) { + Log.w(TAG, "Invalid JSON payload: $payload") + } + } + + private fun handleJsonCommand(obj: JSONObject) { + val action = obj.optString("action", obj.optString("cmd", obj.optString("type", ""))).lowercase() + when (action) { + "goto" -> { + val location = obj.optString("location", obj.optString("target", "")) + goTo(location) + } + "speak" -> { + val text = obj.optString("text", obj.optString("speech", "")) + val lang = obj.optString("lang", "") + speak(text, lang) + } + "stop" -> { + navController.stop() + } + "patrol" -> { + navController.randomPatrol() + } + else -> Log.w(TAG, "Unknown command action: $action") + } + } + + private fun goTo(location: String) { + val target = location.trim() + if (target.isEmpty()) { + Log.w(TAG, "GoTo ignored: empty location") + return + } + val ok = navController.goTo(target) + Log.i(TAG, "GoTo command sent: $target, result=$ok") + } + + private fun speak(text: String, langCode: String?) { + val content = text.trim() + if (content.isEmpty()) { + Log.w(TAG, "Speak ignored: empty text") + return + } + val language = resolveLanguage(langCode) + scope.launch(Dispatchers.Main) { + val ttsRequest = TtsRequest.create(content, false, language = language) + robot.speak(ttsRequest) + Log.i(TAG, "Speak command sent: $content, lang=$language") + } + } + + private fun resolveLanguage(langCode: String?): TtsRequest.Language { + val code = langCode?.trim()?.lowercase().orEmpty() + return when (code) { + "zh", "zh_cn", "zh-cn" -> TtsRequest.Language.ZH_CN + "en", "en_us", "en-us" -> TtsRequest.Language.EN_US + else -> TtsRequest.Language.ZH_CN + } + } } diff --git a/app/src/main/java/com/example/lzwcai_terminal_temi/NavController.kt b/app/src/main/java/com/example/lzwcai_terminal_temi/NavController.kt new file mode 100644 index 0000000..7815f36 --- /dev/null +++ b/app/src/main/java/com/example/lzwcai_terminal_temi/NavController.kt @@ -0,0 +1,37 @@ +package com.example.lzwcai_terminal_temi + +import android.util.Log +import com.robotemi.sdk.Robot + +class NavController(private val robot: Robot) { + private val TAG = "NavController" + + fun goTo(location: String): Boolean { + robot.goTo(location) + return true + } + + fun stop(): Boolean { + robot.stopMovement() + return true + } + + fun getAllLocations(): List { + return robot.locations + } + + fun patrol(locations: List, nonStop: Boolean = false, times: Int = 1, waiting: Int = 3) { + robot.patrol(locations, nonStop, times, waiting) + } + + fun randomPatrol() { + val allLocations = getAllLocations() + if (allLocations.size < 3) { + return + } + val patrolCount = (3..minOf(6, allLocations.size)).random() + val patrolLocations = allLocations.shuffled().take(patrolCount) + + patrol(patrolLocations, false, 1, 3) + } +} diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 4d17619..74761e0 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -6,42 +6,42 @@ android:padding="16dp" tools:context=".MainActivity"> - + - + - - -