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 8e9d63e..10e610d 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 @@ -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) 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 bd369b9..68cd468 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 @@ -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)