feat(telemetry): 添加机器人状态监控与定期上报功能

- 新增电池状态、移动状态和位置变化的监听器
- 实现定期心跳上报和状态快照发布机制
- 添加低电量预警和事件发布功能
- 在MQTT连接时主动发布状态信息
- 支持通过"status"命令手动触发状态上报
This commit is contained in:
2026-03-16 14:09:23 +08:00
parent 0c7d4d9402
commit 66fc204cff
2 changed files with 149 additions and 1 deletions

View File

@@ -18,13 +18,18 @@ import androidx.core.content.ContextCompat
import com.example.lzwcai_terminal_temi.databinding.ActivityMainBinding import com.example.lzwcai_terminal_temi.databinding.ActivityMainBinding
import com.robotemi.sdk.Robot import com.robotemi.sdk.Robot
import com.robotemi.sdk.TtsRequest import com.robotemi.sdk.TtsRequest
import com.robotemi.sdk.BatteryData
import com.robotemi.sdk.Robot.TtsListener import com.robotemi.sdk.Robot.TtsListener
import com.robotemi.sdk.listeners.OnBatteryStatusChangedListener
import com.robotemi.sdk.listeners.OnDetectionStateChangedListener import com.robotemi.sdk.listeners.OnDetectionStateChangedListener
import com.robotemi.sdk.listeners.OnGoToLocationStatusChangedListener import com.robotemi.sdk.listeners.OnGoToLocationStatusChangedListener
import com.robotemi.sdk.listeners.OnMovementStatusChangedListener
import com.robotemi.sdk.listeners.OnRobotReadyListener import com.robotemi.sdk.listeners.OnRobotReadyListener
import com.robotemi.sdk.listeners.OnDetectionStateChangedListener.Companion.DETECTED import com.robotemi.sdk.listeners.OnDetectionStateChangedListener.Companion.DETECTED
import com.robotemi.sdk.listeners.OnDetectionStateChangedListener.Companion.IDLE import com.robotemi.sdk.listeners.OnDetectionStateChangedListener.Companion.IDLE
import com.robotemi.sdk.navigation.listener.OnCurrentPositionChangedListener
import com.robotemi.sdk.navigation.listener.OnReposeStatusChangedListener import com.robotemi.sdk.navigation.listener.OnReposeStatusChangedListener
import com.robotemi.sdk.navigation.model.Position
import com.robotemi.sdk.permission.* import com.robotemi.sdk.permission.*
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@@ -43,7 +48,8 @@ import javax.crypto.spec.SecretKeySpec
class MainActivity : AppCompatActivity(), OnRobotReadyListener, TtsListener, OnGoToLocationStatusChangedListener, class MainActivity : AppCompatActivity(), OnRobotReadyListener, TtsListener, OnGoToLocationStatusChangedListener,
OnDetectionStateChangedListener, OnReposeStatusChangedListener, SharedPreferences.OnSharedPreferenceChangeListener, OnDetectionStateChangedListener, OnReposeStatusChangedListener, SharedPreferences.OnSharedPreferenceChangeListener,
OnRequestPermissionResultListener { OnRequestPermissionResultListener, OnBatteryStatusChangedListener, OnMovementStatusChangedListener,
OnCurrentPositionChangedListener {
private lateinit var robot: Robot private lateinit var robot: Robot
private lateinit var binding: ActivityMainBinding private lateinit var binding: ActivityMainBinding
@@ -73,6 +79,7 @@ class MainActivity : AppCompatActivity(), OnRobotReadyListener, TtsListener, OnG
private var closeDoorJob: Job? = null private var closeDoorJob: Job? = null
private var blinkJob: Job? = null private var blinkJob: Job? = null
private var telemetryJob: Job? = null
private var receptionLocation: String = "" private var receptionLocation: String = ""
private var receptionText: String = "" private var receptionText: String = ""
@@ -84,6 +91,15 @@ class MainActivity : AppCompatActivity(), OnRobotReadyListener, TtsListener, OnG
private var patrolNonStop: Boolean = false private var patrolNonStop: Boolean = false
private var patrolMoveJob: Job? = null private var patrolMoveJob: Job? = null
private var isLeavingHomeBase: Boolean = false private var isLeavingHomeBase: Boolean = false
private var latestBatteryLevel: Int? = null
private var latestBatteryCharging: Boolean? = null
private var latestBattery2Level: Int? = null
private var latestPosition: Position? = null
private var latestMovementType: String? = null
private var latestMovementStatus: String? = null
private var lastTelemetrySentAt: Long = 0L
private var lastBatteryLowWarningAt: Long = 0L
private val batteryLowThreshold = 20
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@@ -149,6 +165,9 @@ class MainActivity : AppCompatActivity(), OnRobotReadyListener, TtsListener, OnG
robot.addOnGoToLocationStatusChangedListener(this) robot.addOnGoToLocationStatusChangedListener(this)
robot.addOnDetectionStateChangedListener(this) robot.addOnDetectionStateChangedListener(this)
robot.addOnReposeStatusChangedListener(this) robot.addOnReposeStatusChangedListener(this)
robot.addOnBatteryStatusChangedListener(this)
robot.addOnMovementStatusChangedListener(this)
robot.addOnCurrentPositionChangedListener(this)
robot.addOnRequestPermissionResultListener(this) robot.addOnRequestPermissionResultListener(this)
robot.constraintBeWith() robot.constraintBeWith()
if (mqttManager == null) { if (mqttManager == null) {
@@ -158,6 +177,7 @@ class MainActivity : AppCompatActivity(), OnRobotReadyListener, TtsListener, OnG
} }
updateLiveKitConnection() updateLiveKitConnection()
startBlinking() startBlinking()
startTelemetry()
} }
override fun onStop() { override fun onStop() {
@@ -167,10 +187,14 @@ class MainActivity : AppCompatActivity(), OnRobotReadyListener, TtsListener, OnG
robot.removeOnGoToLocationStatusChangedListener(this) robot.removeOnGoToLocationStatusChangedListener(this)
robot.removeOnDetectionStateChangedListener(this) robot.removeOnDetectionStateChangedListener(this)
robot.removeOnReposeStatusChangedListener(this) robot.removeOnReposeStatusChangedListener(this)
robot.removeOnBatteryStatusChangedListener(this)
robot.removeOnMovementStatusChangedListener(this)
robot.removeOnCurrentPositionChangedListener(this)
robot.removeOnRequestPermissionResultListener(this) robot.removeOnRequestPermissionResultListener(this)
// mqttManager?.disconnect() // Keep MQTT alive in background/settings // mqttManager?.disconnect() // Keep MQTT alive in background/settings
liveKitManager?.disconnect() liveKitManager?.disconnect()
stopBlinking() stopBlinking()
stopTelemetry()
} }
override fun onDestroy() { override fun onDestroy() {
@@ -228,6 +252,42 @@ class MainActivity : AppCompatActivity(), OnRobotReadyListener, TtsListener, OnG
} }
} }
override fun onBatteryStatusChanged(batteryData: BatteryData?) {
if (batteryData == null) {
return
}
latestBatteryLevel = batteryData.level
latestBatteryCharging = batteryData.isCharging
latestBattery2Level = batteryData.battery2Level
val now = System.currentTimeMillis()
if (batteryData.level <= batteryLowThreshold && batteryData.isCharging.not()) {
if (now - lastBatteryLowWarningAt > 10 * 60 * 1000L) {
lastBatteryLowWarningAt = now
val ttsRequest = TtsRequest.create("电量低,请及时充电。", false, language = TtsRequest.Language.ZH_CN)
robot.speak(ttsRequest)
publishEvent(
"battery_low",
JSONObject()
.put("level", batteryData.level)
.put("charging", batteryData.isCharging)
.put("battery2Level", batteryData.battery2Level)
)
}
}
publishStatusSnapshot("battery")
}
override fun onMovementStatusChanged(type: String, status: String) {
latestMovementType = type
latestMovementStatus = status
publishStatusSnapshot("movement")
}
override fun onCurrentPositionChanged(position: Position) {
latestPosition = position
publishStatusSnapshot("position")
}
override fun onGoToLocationStatusChanged(location: String, status: String, descriptionId: Int, description: String) { override fun onGoToLocationStatusChanged(location: String, status: String, descriptionId: Int, description: String) {
val normalized = status.lowercase() val normalized = status.lowercase()
val isAbort = normalized in setOf("abort", "aborted", "canceled", "cancelled", "stopped", "stop", "failed", "error") val isAbort = normalized in setOf("abort", "aborted", "canceled", "cancelled", "stopped", "stop", "failed", "error")
@@ -664,6 +724,9 @@ class MainActivity : AppCompatActivity(), OnRobotReadyListener, TtsListener, OnG
private fun setMqttConnectionStatus(connected: Boolean) { private fun setMqttConnectionStatus(connected: Boolean) {
isMqttConnected = connected isMqttConnected = connected
updateConnectionIndicator() updateConnectionIndicator()
if (connected) {
publishStatusSnapshot("mqtt_connected", true)
}
} }
private fun updateConnectionIndicator() { private fun updateConnectionIndicator() {
@@ -711,6 +774,86 @@ class MainActivity : AppCompatActivity(), OnRobotReadyListener, TtsListener, OnG
blinkJob?.cancel() blinkJob?.cancel()
} }
private fun startTelemetry() {
stopTelemetry()
telemetryJob = mainScope.launch {
while (isActive) {
publishStatusSnapshot("heartbeat", true)
delay(15000L)
}
}
}
private fun stopTelemetry() {
telemetryJob?.cancel()
telemetryJob = null
}
fun publishStatusSnapshot(reason: String, force: Boolean = false) {
val now = System.currentTimeMillis()
if (!force && now - lastTelemetrySentAt < 5000L) {
return
}
lastTelemetrySentAt = now
val payload = JSONObject()
.put("type", "status")
.put("reason", reason)
.put("ts", now)
.put("task", currentTask)
.put("location", lastArrivalLocation ?: JSONObject.NULL)
.put("mqttConnected", isMqttConnected)
.put("liveKitConnected", isLiveKitConnected)
val batteryJson = JSONObject()
if (latestBatteryLevel != null) {
batteryJson.put("level", latestBatteryLevel)
}
if (latestBatteryCharging != null) {
batteryJson.put("charging", latestBatteryCharging)
}
if (latestBattery2Level != null) {
batteryJson.put("battery2Level", latestBattery2Level)
}
if (batteryJson.length() > 0) {
payload.put("battery", batteryJson)
}
val movementJson = JSONObject()
if (!latestMovementType.isNullOrBlank()) {
movementJson.put("type", latestMovementType)
}
if (!latestMovementStatus.isNullOrBlank()) {
movementJson.put("status", latestMovementStatus)
}
if (movementJson.length() > 0) {
payload.put("movement", movementJson)
}
val position = latestPosition
if (position != null) {
payload.put(
"position",
JSONObject()
.put("x", position.x)
.put("y", position.y)
.put("yaw", position.yaw)
.put("tiltAngle", position.tiltAngle)
.put("inMapArea", position.isInMapArea)
)
}
mqttManager?.publish("robot/status", payload.toString())
}
private fun publishEvent(event: String, data: JSONObject) {
val payload = JSONObject()
.put("type", "event")
.put("event", event)
.put("ts", System.currentTimeMillis())
.put("data", data)
mqttManager?.publish("robot/event", payload.toString())
}
override fun onSaveInstanceState(outState: Bundle) { override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState) super.onSaveInstanceState(outState)
outState.putString("currentTask", currentTask) outState.putString("currentTask", currentTask)

View File

@@ -308,6 +308,11 @@ class MqttManager(
"continue" -> { "continue" -> {
resumeTts() resumeTts()
} }
"status" -> {
scope.launch(Dispatchers.Main) {
(context as? MainActivity)?.publishStatusSnapshot("command", true)
}
}
"patrol" -> { "patrol" -> {
speak("接到巡逻任务", "zh") speak("接到巡逻任务", "zh")
val flag = obj.optBoolean("flag", true) val flag = obj.optBoolean("flag", true)