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.robotemi.sdk.Robot
|
||||
import com.robotemi.sdk.TtsRequest
|
||||
import com.robotemi.sdk.BatteryData
|
||||
import com.robotemi.sdk.Robot.TtsListener
|
||||
import com.robotemi.sdk.listeners.OnBatteryStatusChangedListener
|
||||
import com.robotemi.sdk.listeners.OnDetectionStateChangedListener
|
||||
import com.robotemi.sdk.listeners.OnGoToLocationStatusChangedListener
|
||||
import com.robotemi.sdk.listeners.OnMovementStatusChangedListener
|
||||
import com.robotemi.sdk.listeners.OnRobotReadyListener
|
||||
import com.robotemi.sdk.listeners.OnDetectionStateChangedListener.Companion.DETECTED
|
||||
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.model.Position
|
||||
import com.robotemi.sdk.permission.*
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -43,7 +48,8 @@ import javax.crypto.spec.SecretKeySpec
|
||||
|
||||
class MainActivity : AppCompatActivity(), OnRobotReadyListener, TtsListener, OnGoToLocationStatusChangedListener,
|
||||
OnDetectionStateChangedListener, OnReposeStatusChangedListener, SharedPreferences.OnSharedPreferenceChangeListener,
|
||||
OnRequestPermissionResultListener {
|
||||
OnRequestPermissionResultListener, OnBatteryStatusChangedListener, OnMovementStatusChangedListener,
|
||||
OnCurrentPositionChangedListener {
|
||||
|
||||
private lateinit var robot: Robot
|
||||
private lateinit var binding: ActivityMainBinding
|
||||
@@ -73,6 +79,7 @@ class MainActivity : AppCompatActivity(), OnRobotReadyListener, TtsListener, OnG
|
||||
|
||||
private var closeDoorJob: Job? = null
|
||||
private var blinkJob: Job? = null
|
||||
private var telemetryJob: Job? = null
|
||||
|
||||
private var receptionLocation: String = ""
|
||||
private var receptionText: String = ""
|
||||
@@ -84,6 +91,15 @@ class MainActivity : AppCompatActivity(), OnRobotReadyListener, TtsListener, OnG
|
||||
private var patrolNonStop: Boolean = false
|
||||
private var patrolMoveJob: Job? = null
|
||||
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")
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
@@ -149,6 +165,9 @@ class MainActivity : AppCompatActivity(), OnRobotReadyListener, TtsListener, OnG
|
||||
robot.addOnGoToLocationStatusChangedListener(this)
|
||||
robot.addOnDetectionStateChangedListener(this)
|
||||
robot.addOnReposeStatusChangedListener(this)
|
||||
robot.addOnBatteryStatusChangedListener(this)
|
||||
robot.addOnMovementStatusChangedListener(this)
|
||||
robot.addOnCurrentPositionChangedListener(this)
|
||||
robot.addOnRequestPermissionResultListener(this)
|
||||
robot.constraintBeWith()
|
||||
if (mqttManager == null) {
|
||||
@@ -158,6 +177,7 @@ class MainActivity : AppCompatActivity(), OnRobotReadyListener, TtsListener, OnG
|
||||
}
|
||||
updateLiveKitConnection()
|
||||
startBlinking()
|
||||
startTelemetry()
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
@@ -167,10 +187,14 @@ class MainActivity : AppCompatActivity(), OnRobotReadyListener, TtsListener, OnG
|
||||
robot.removeOnGoToLocationStatusChangedListener(this)
|
||||
robot.removeOnDetectionStateChangedListener(this)
|
||||
robot.removeOnReposeStatusChangedListener(this)
|
||||
robot.removeOnBatteryStatusChangedListener(this)
|
||||
robot.removeOnMovementStatusChangedListener(this)
|
||||
robot.removeOnCurrentPositionChangedListener(this)
|
||||
robot.removeOnRequestPermissionResultListener(this)
|
||||
// mqttManager?.disconnect() // Keep MQTT alive in background/settings
|
||||
liveKitManager?.disconnect()
|
||||
stopBlinking()
|
||||
stopTelemetry()
|
||||
}
|
||||
|
||||
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) {
|
||||
val normalized = status.lowercase()
|
||||
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) {
|
||||
isMqttConnected = connected
|
||||
updateConnectionIndicator()
|
||||
if (connected) {
|
||||
publishStatusSnapshot("mqtt_connected", true)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateConnectionIndicator() {
|
||||
@@ -711,6 +774,86 @@ class MainActivity : AppCompatActivity(), OnRobotReadyListener, TtsListener, OnG
|
||||
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) {
|
||||
super.onSaveInstanceState(outState)
|
||||
outState.putString("currentTask", currentTask)
|
||||
|
||||
@@ -308,6 +308,11 @@ class MqttManager(
|
||||
"continue" -> {
|
||||
resumeTts()
|
||||
}
|
||||
"status" -> {
|
||||
scope.launch(Dispatchers.Main) {
|
||||
(context as? MainActivity)?.publishStatusSnapshot("command", true)
|
||||
}
|
||||
}
|
||||
"patrol" -> {
|
||||
speak("接到巡逻任务", "zh")
|
||||
val flag = obj.optBoolean("flag", true)
|
||||
|
||||
Reference in New Issue
Block a user