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 9318a31..ec8c7d2 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 @@ -96,6 +96,9 @@ class MainActivity : AppCompatActivity(), OnRobotReadyListener, TtsListener, OnG } override fun onTtsStatusChanged(ttsRequest: TtsRequest) { + // Forward TTS status to MqttManager for stream queue handling + mqttManager?.handleTtsStatusChange(ttsRequest) + when (ttsRequest.status) { TtsRequest.Status.STARTED -> { Log.i("MainActivity", "TTS started: ${ttsRequest.speech}") 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 032a43a..b7fa85b 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 @@ -8,6 +8,8 @@ import kotlinx.coroutines.* import org.eclipse.paho.client.mqttv3.* import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence import org.json.JSONObject +import java.util.LinkedList +import java.util.Queue class MqttManager( private val context: Context, @@ -24,6 +26,13 @@ class MqttManager( private val scope = CoroutineScope(Dispatchers.IO + job) private var reconnectJob: Job? = null + // Streaming text buffer + private val speechBuffer = StringBuilder() + + // TTS Queue + private val ttsQueue: Queue = LinkedList() + private var isTtsBusy = false + init { try { mqttClient = MqttClient(brokerUri, clientId, MemoryPersistence()) @@ -88,6 +97,7 @@ class MqttManager( } fun disconnect() { + robot.removeTtsListener(this) scope.launch { try { reconnectJob?.cancel() @@ -161,8 +171,20 @@ class MqttManager( val lang = obj.optString("lang", "") speak(text, lang) } + "stream" -> { + val text = obj.optString("text", obj.optString("content", "")) + val lang = obj.optString("lang", "") + processStreamText(text, lang) + } + "repose" -> { + val ok = navController.repose() + speak("正在进行重新定位") + Log.i(TAG, "Repose command sent: $ok") + monitorReposeStatus() + } "stop" -> { navController.stop() + stopTts() } "patrol" -> { navController.randomPatrol() @@ -171,27 +193,137 @@ class MqttManager( } } - private fun goTo(location: String) { + private fun processStreamText(text: String, langCode: String?) { + speechBuffer.append(text) + + while (true) { + val content = speechBuffer.toString() + var minIndex = -1 + + // Sentence delimiters: Chinese and English punctuation + newline + val delimiters = listOf("。", "!", "?", "!", "?", "\n") + + for (d in delimiters) { + val index = content.indexOf(d) + if (index != -1) { + if (minIndex == -1 || index < minIndex) { + minIndex = index + } + } + } + + if (minIndex != -1) { + // Extract sentence including the delimiter + val sentence = content.substring(0, minIndex + 1) + speak(sentence, langCode) + + // Remove processed sentence from buffer + speechBuffer.delete(0, minIndex + 1) + } else { + // No more complete sentences found + break + } + } + } + + private fun stopTts() { + // Clear buffer + speechBuffer.setLength(0) + + scope.launch(Dispatchers.Main) { + ttsQueue.clear() + isTtsBusy = false + robot.cancelAllTtsRequests() + Log.i(TAG, "TTS stopped and queue cleared.") + } + } + + private fun monitorReposeStatus() { + scope.launch { + Log.i(TAG, "Starting repose monitoring...") + val timeout = 30000L + val startTime = System.currentTimeMillis() + var lastStatus: String? = null + + while (System.currentTimeMillis() - startTime < timeout) { + delay(1000L) + val status = robot.reposeStatus.toString().uppercase() + + if (status != lastStatus) { + Log.i(TAG, "Repose status: $status") + lastStatus = status + } + + if (status == "COMPLETE") { + speak("重新定位成功", null) + return@launch + } else if (status == "FAILURE" || status == "ABORTED") { + speak("重新定位失败", null) + return@launch + } + } + Log.w(TAG, "Repose monitoring timed out.") + speak("重新定位超时", null) + } + } + + private fun goTo(location: String, backwards: Boolean = false) { 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") + val ok = navController.goTo(target, backwards) + Log.i(TAG, "GoTo command sent: $target, backwards=$backwards, result=$ok") } private fun speak(text: String, langCode: String?) { val content = text.trim() if (content.isEmpty()) { - Log.w(TAG, "Speak ignored: empty text") + // Log.w(TAG, "Speak ignored: empty text") // Too noisy for stream? 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") + + if (!isTtsBusy) { + isTtsBusy = true + robot.speak(ttsRequest) + Log.i(TAG, "Speak immediate: $content") + } else { + ttsQueue.offer(ttsRequest) + Log.i(TAG, "Speak queued: $content. Queue size: ${ttsQueue.size}") + } + } + } + + override fun handleTtsStatusChange(ttsRequest: TtsRequest) { + scope.launch(Dispatchers.Main) { + when (ttsRequest.status) { + TtsRequest.Status.STARTED -> { + isTtsBusy = true + } + TtsRequest.Status.COMPLETED, + TtsRequest.Status.CANCELED, + TtsRequest.Status.ERROR, + TtsRequest.Status.ABORTED -> { + isTtsBusy = false + processNextTts() + } + else -> {} + } + } + } + + private fun processNextTts() { + if (!isTtsBusy && ttsQueue.isNotEmpty()) { + val next = ttsQueue.poll() + if (next != null) { + isTtsBusy = true + robot.speak(next) + Log.i(TAG, "Speak from queue: ${next.speech}") + } } } 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 index 7815f36..0436020 100644 --- a/app/src/main/java/com/example/lzwcai_terminal_temi/NavController.kt +++ b/app/src/main/java/com/example/lzwcai_terminal_temi/NavController.kt @@ -6,8 +6,8 @@ import com.robotemi.sdk.Robot class NavController(private val robot: Robot) { private val TAG = "NavController" - fun goTo(location: String): Boolean { - robot.goTo(location) + fun goTo(location: String, backwards: Boolean = false): Boolean { + robot.goTo(location, backwards) return true } @@ -24,6 +24,11 @@ class NavController(private val robot: Robot) { robot.patrol(locations, nonStop, times, waiting) } + fun repose(): Boolean { + robot.repose() + return true + } + fun randomPatrol() { val allLocations = getAllLocations() if (allLocations.size < 3) {