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">
-
+
-
+
-
-
-
+ android:layout_alignParentBottom="true"
+ android:layout_centerHorizontal="true"
+ android:layout_marginBottom="32dp"
+ android:orientation="horizontal">
-
-
+
-
\ No newline at end of file
+
+
+
+
diff --git a/app/src/main/res/layout/activity_settings.xml b/app/src/main/res/layout/activity_settings.xml
index dd82c8c..e960264 100644
--- a/app/src/main/res/layout/activity_settings.xml
+++ b/app/src/main/res/layout/activity_settings.xml
@@ -91,38 +91,5 @@
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 4866c89..82391e8 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -8,13 +8,9 @@
请输入 IP 地址 (例如 192.168.1.100)
保存
返回主界面
- 显示日志
- 隐藏日志
IP 已保存: %1$s
请输入有效的 IP 地址
- 应用启动完成。目标 IP: %1$s
- 日志将显示在这里...
随机表情
让机器人说话
长按重启应用
-
\ No newline at end of file
+