Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 883c040c30 |
@@ -38,7 +38,7 @@ android {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(files("libs/unity-bridge.aar"))
|
||||
implementation(files("libs/unity-bridge-aar-debug-v1.3.0.aar"))
|
||||
implementation("androidx.core:core-ktx:1.12.0")
|
||||
implementation("androidx.appcompat:appcompat:1.6.1")
|
||||
implementation("androidx.fragment:fragment-ktx:1.6.2")
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -36,23 +36,21 @@ class BridgeRepository {
|
||||
fun getLatestPoseFrameJson(): String = KiwiiRuntimeClientBridge.getLatestPoseFrameJson()
|
||||
fun getLatestBodyPoseFrameJson(): String = KiwiiRuntimeClientBridge.getLatestBodyPoseFrameJson()
|
||||
|
||||
fun getLeftHandleIMULatest(): FloatArray = KiwiiRuntimeClientBridge.getLatestLeftHandleIMU()
|
||||
fun getLeftHandleIMULatest(): FloatArray = KiwiiRuntimeClientBridge.getLeftHandleIMULatest()
|
||||
fun getLeftHandleIMUQueue(): Array<FloatArray> {
|
||||
val latest = KiwiiRuntimeClientBridge.getLatestLeftHandleIMU()
|
||||
val latest = KiwiiRuntimeClientBridge.getLeftHandleIMULatest()
|
||||
return if (latest.isEmpty()) emptyArray() else arrayOf(latest)
|
||||
}
|
||||
fun submitLeftStaticEffect(effectId: Int): String = KiwiiRuntimeClientBridge.submitLeftStaticEffect(effectId)
|
||||
fun submitLeftHeStream(streamId: Int, payload: String): String =
|
||||
KiwiiRuntimeClientBridge.submitLeftHeStream(buildHeStreamPatternJson(streamId, payload))
|
||||
KiwiiRuntimeClientBridge.submitLeftHeStream(streamId, payload)
|
||||
|
||||
fun getRightHandleIMULatest(): FloatArray = KiwiiRuntimeClientBridge.getLatestRightHandleIMU()
|
||||
fun getRightHandleIMULatest(): FloatArray = KiwiiRuntimeClientBridge.getRightHandleIMULatest()
|
||||
fun getRightHandleIMUQueue(): Array<FloatArray> {
|
||||
val latest = KiwiiRuntimeClientBridge.getLatestRightHandleIMU()
|
||||
val latest = KiwiiRuntimeClientBridge.getRightHandleIMULatest()
|
||||
return if (latest.isEmpty()) emptyArray() else arrayOf(latest)
|
||||
}
|
||||
fun submitRightStaticEffect(effectId: Int): String = KiwiiRuntimeClientBridge.submitRightStaticEffect(effectId)
|
||||
fun submitRightHeStream(streamId: Int, payload: String): String =
|
||||
KiwiiRuntimeClientBridge.submitRightHeStream(buildHeStreamPatternJson(streamId, payload))
|
||||
KiwiiRuntimeClientBridge.submitRightHeStream(streamId, payload)
|
||||
|
||||
fun getHandleStateJson(): String = KiwiiRuntimeClientBridge.getHandleStateJson()
|
||||
|
||||
@@ -68,13 +66,6 @@ class BridgeRepository {
|
||||
fun submitDeviceCommandDryRun(command: String): String = KiwiiRuntimeClientBridge.submitDeviceCommandDryRun(command)
|
||||
fun getDeviceCommandStateJson(): String = KiwiiRuntimeClientBridge.getDeviceCommandStateJson()
|
||||
|
||||
private fun buildHeStreamPatternJson(streamId: Int, payload: String): String {
|
||||
val trimmed = payload.trim()
|
||||
if (trimmed.startsWith("{")) return trimmed
|
||||
val escaped = payload.replace("\\", "\\\\").replace("\"", "\\\"")
|
||||
return "{\"streamId\":$streamId,\"payload\":\"$escaped\"}"
|
||||
}
|
||||
|
||||
suspend fun <T> callAsync(block: () -> T): OperationResult<T> = withContext(Dispatchers.IO) {
|
||||
try {
|
||||
val result = block()
|
||||
|
||||
@@ -30,16 +30,6 @@ class LeftHandleOps(
|
||||
}
|
||||
})
|
||||
|
||||
layout.addView(createSection("Haptics"))
|
||||
val effectInput = createParameterInput("Effect ID (int)", android.text.InputType.TYPE_CLASS_NUMBER)
|
||||
val staticEffectAction = createButton("submitLeftStaticEffect") {
|
||||
val id = effectInput.text.toString().toIntOrNull() ?: 0
|
||||
viewModel.executeOperation(ComponentType.LEFT_HANDLE, "submitLeftStaticEffect", "effectId=$id") {
|
||||
viewModel.bridgeRepo.submitLeftStaticEffect(id)
|
||||
}
|
||||
}
|
||||
layout.addView(createParameterActionRow(staticEffectAction, effectInput))
|
||||
|
||||
val streamIdInput = createParameterInput("Stream ID (int)", android.text.InputType.TYPE_CLASS_NUMBER)
|
||||
val payloadInput = createParameterInput("Payload (String)", android.text.InputType.TYPE_CLASS_TEXT)
|
||||
val heStreamAction = createButton("submitLeftHeStream") {
|
||||
|
||||
@@ -30,16 +30,6 @@ class RightHandleOps(
|
||||
}
|
||||
})
|
||||
|
||||
layout.addView(createSection("Haptics"))
|
||||
val effectInput = createParameterInput("Effect ID (int)", android.text.InputType.TYPE_CLASS_NUMBER)
|
||||
val staticEffectAction = createButton("submitRightStaticEffect") {
|
||||
val id = effectInput.text.toString().toIntOrNull() ?: 0
|
||||
viewModel.executeOperation(ComponentType.RIGHT_HANDLE, "submitRightStaticEffect", "effectId=$id") {
|
||||
viewModel.bridgeRepo.submitRightStaticEffect(id)
|
||||
}
|
||||
}
|
||||
layout.addView(createParameterActionRow(staticEffectAction, effectInput))
|
||||
|
||||
val streamIdInput = createParameterInput("Stream ID (int)", android.text.InputType.TYPE_CLASS_NUMBER)
|
||||
val payloadInput = createParameterInput("Payload (String)", android.text.InputType.TYPE_CLASS_TEXT)
|
||||
val heStreamAction = createButton("submitRightHeStream") {
|
||||
|
||||
@@ -143,7 +143,7 @@ class ComponentDetailFragment : Fragment() {
|
||||
viewModel.stateSnapshot.collect { snapshot ->
|
||||
if (snapshot != null) {
|
||||
latestStateText = JsonFormatter.format(snapshot.data)
|
||||
val items = RuntimeHostStatusParser.parse(snapshot.data, viewModel.bridgeRepo.isBound())
|
||||
val items = parseStateItems(snapshot.data)
|
||||
.ifEmpty { listOf(StatItem("State", "Updated")) }
|
||||
stateAdapter.submitList(items)
|
||||
val sdf = SimpleDateFormat("HH:mm:ss", Locale.getDefault())
|
||||
@@ -179,6 +179,16 @@ class ComponentDetailFragment : Fragment() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseStateItems(json: String): List<StatItem> {
|
||||
return if (componentType == ComponentType.MOTOR) {
|
||||
MotorRuntimeStateParser.parse(json).ifEmpty {
|
||||
RuntimeHostStatusParser.parse(json, viewModel.bridgeRepo.isBound())
|
||||
}
|
||||
} else {
|
||||
RuntimeHostStatusParser.parse(json, viewModel.bridgeRepo.isBound())
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun observeLogs() {
|
||||
viewModel.logEntries.collect { entries ->
|
||||
val visibleEntries = entries.takeLast(80).ifEmpty {
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.kiwii.controlpanel.ui.detail
|
||||
|
||||
import com.kiwii.bridge.MotorRuntimeState
|
||||
import java.util.Locale
|
||||
|
||||
/** 将 AAR 的 MotorRuntimeState 映射成详情页状态卡片。 */
|
||||
object MotorRuntimeStateParser {
|
||||
|
||||
fun parse(json: String): List<StatItem> {
|
||||
val state = try {
|
||||
MotorRuntimeState.fromJson(json)
|
||||
} catch (_: Throwable) {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
return listOf(
|
||||
StatItem("Available", state.available.toString()),
|
||||
StatItem("Last Key", formatKey(state.lastKey), "motor uplink key"),
|
||||
StatItem("Runtime", formatFloat(state.runtimeSec), "s"),
|
||||
StatItem("Current", formatFloat(state.currentA), "A"),
|
||||
StatItem("Torque", formatFloat(state.torqueNm), "N.m"),
|
||||
StatItem("Velocity Rad", formatFloat(state.velocityRadps), "rad/s"),
|
||||
StatItem("Velocity", formatFloat(state.velocityMps), "m/s"),
|
||||
StatItem("Position", formatFloat(state.positionRad), "rad"),
|
||||
StatItem("Extension", formatFloat(state.extensionM), "m"),
|
||||
StatItem("Voltage", formatFloat(state.voltageV), "V"),
|
||||
StatItem("Temperature", formatFloat(state.tempC), "C"),
|
||||
StatItem("Sys", state.sys.toString()),
|
||||
StatItem("Merr", state.merr.toString()),
|
||||
StatItem("OBD", state.obd.toString()),
|
||||
StatItem("Seq ID", state.seqId.toString()),
|
||||
StatItem("T Data", state.tDataUnixMs.toString(), "Unix ms"),
|
||||
StatItem("Fault", state.faultSummary.ifBlank { "none" }),
|
||||
StatItem("Host Arrival", state.hostArrivalNs.toString(), "ns"),
|
||||
StatItem("Last Upload", state.lastUploadNs.toString(), "ns"),
|
||||
StatItem("Data Age", state.dataAgeMs.toString(), "ms"),
|
||||
StatItem("Raw Line", state.rawLine.compact(), "raw debug")
|
||||
)
|
||||
}
|
||||
|
||||
private fun formatKey(key: Int): String = String.format(Locale.US, "0x%02X (%d)", key, key)
|
||||
|
||||
private fun formatFloat(value: Float): String = String.format(Locale.US, "%.4f", value)
|
||||
|
||||
private fun String.compact(): String {
|
||||
return if (length > 48) "${take(45)}..." else this
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package com.kiwii.controlpanel
|
||||
|
||||
import com.kiwii.controlpanel.ui.detail.MotorRuntimeStateParser
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
|
||||
class MotorRuntimeStateParserTest {
|
||||
|
||||
@Test
|
||||
fun `parse maps new motor runtime fields`() {
|
||||
val json = """
|
||||
{
|
||||
"contractVersion": "kiwii.motor-runtime-state.v1",
|
||||
"available": true,
|
||||
"lastKey": 175,
|
||||
"runtimeSec": 12.5,
|
||||
"currentA": 1.25,
|
||||
"torqueNm": 2.5,
|
||||
"velocityRadps": 3.5,
|
||||
"velocityMps": 4.5,
|
||||
"positionRad": 5.5,
|
||||
"extensionM": 0.25,
|
||||
"voltageV": 24.0,
|
||||
"tempC": 36.5,
|
||||
"sys": 7,
|
||||
"merr": 8,
|
||||
"obd": 9,
|
||||
"seqId": 10,
|
||||
"tDataUnixMs": 1710000000000,
|
||||
"faultSummary": "ok",
|
||||
"hostArrivalNs": 111,
|
||||
"lastUploadNs": 222,
|
||||
"dataAgeMs": 333,
|
||||
"rawLine": "motor raw debug line",
|
||||
"rawJson": "{\"available\":true}"
|
||||
}
|
||||
""".trimIndent()
|
||||
|
||||
val items = MotorRuntimeStateParser.parse(json)
|
||||
|
||||
assertEquals("true", items.first { it.label == "Available" }.value)
|
||||
assertEquals("0xAF (175)", items.first { it.label == "Last Key" }.value)
|
||||
assertEquals("12.5000", items.first { it.label == "Runtime" }.value)
|
||||
assertEquals("ms", items.first { it.label == "Data Age" }.detail)
|
||||
assertEquals("motor raw debug line", items.first { it.label == "Raw Line" }.value)
|
||||
assertTrue(items.none { it.label == "Raw Json" })
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user