feat(telemetry): 添加机器人状态监控与定期上报功能
- 新增电池状态、移动状态和位置变化的监听器 - 实现定期心跳上报和状态快照发布机制 - 添加低电量预警和事件发布功能 - 在MQTT连接时主动发布状态信息 - 支持通过"status"命令手动触发状态上报
This commit is contained in:
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user