The user wants me to modify the FrameRouter logging to output key events in the format `KeyTest 1 0x19 0x01` instead of the current format `KEY value=0x19 status=0x01`. Let me look at the current log line and change it.

Let me read the FrameRouter key handling code.


133	    // ------------------------------------------------------------ Key
134
135	    private static void handleKey(int frameType, int cmd, byte[] p, int len) {
136	        if (cmd == 0x01 && len >= 2) {
137	            Log.i(TAG, String.format("KEY value=0x%02X status=0x%02X",
138	                    p[0] & 0xFF, p[1] & 0xFF));
139	            return;
140	        }
141	        Log.i(TAG, String.format("KEY cmd=0x%02X type=0x%02X len=%d",
142	                cmd, frameType, len));
143	    }
144

    // ------------------------------------------------------------ Key

    private static void handleKey(int frameType, int cmd, byte[] p, int len) {
        if (cmd == 0x01 && len >= 2) {
            Log.i(TAG, String.format("KEY value=0x%02X status=0x%02X",
                    p[0] & 0xFF, p[1] & 0xFF));
            return;
        }
        Log.i(TAG, String.format("KEY cmd=0x%02X type=0x%02X len=%d",
                cmd, frameType, len));
    }


The user wants the format: `KeyTest 1 0x19 0x01`

So mapping:
- `KeyTest` - prefix (constant string)
- `1` - this is the cmd (0x01 for M2A_KEY), but the user seems to want just "1"
- `0x19` - keyValue (p[0])
- `0x01` - keyStatus (p[1])

Looking at the self-test doc they referenced: `KeyTest 1 0x19 0x01`

So `1` is cmd, then keyValue, then keyStatus.

Wait, actually looking more carefully at the user's reference `KeyTest 1 0x19 0x01`:
- `KeyTest` = prefix
- `1` = likely cmd=0x01
- `0x19` = keyValue
- `0x01` = keyStatus

Let me change the log line.


Edit

            Log.i(TAG, String.format("KEY value=0x%02X status=0x%02X",
                    p[0] & 0xFF, p[1] & 0xFF));

            Log.i(TAG, String.format("KeyTest %d 0x%02X 0x%02X",
                    cmd, p[0] & 0xFF, p[1] & 0xFF));


The file /Users/admin/workspace/jyq/qsjnic/service/src/main/java/com/qsjnic/mcu/service/FrameRouter.java has been updated successfully. (file state is current in your context — no need to Read it back)

            Log.i(TAG, String.format("KeyTest %d 0x%02X 0x%02X",
                    cmd, p[0] & 0xFF, p[1] & 0xFF));

package com.qsjnic.mcu.service;

import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.util.Log;

import androidx.annotation.Keep;
import androidx.annotation.Nullable;

import com.qsjnic.mcu.service.feature.DomainBus;

/**
 * Receives every NORMAL/NOTIFY frame from the native RX thread and dispatches
 * it onto a dedicated Looper thread for parsing/logging (B3) or fan-out to
 * registered Listeners (D phase).
 *
 * <p>The native bridge calls {@link #onMcuFrame(int, int, int, byte[])}
 * directly via cached {@code jmethodID}, so this method must NOT be renamed
 * or have its signature changed without updating jni_bridge.cpp.
 */
public final class FrameRouter {

    private static final String TAG = "FrameRouter";

    // Frame type constants (must match protocol.h FRAME_TYPE_* values).
    private static final int FRAME_TYPE_NORMAL = 0x06;
    private static final int FRAME_TYPE_NOTIFY = 0x07;

    // MsgType constants.
    private static final int MSG_TYPE_PUBLIC = 0x01;
    private static final int MSG_TYPE_KEY    = 0x02;
    private static final int MSG_TYPE_RADIO  = 0x05;
    private static final int MSG_TYPE_UPDATE = 0x07;

    private static final HandlerThread WORKER;
    private static final Handler        HANDLER;

    static {
        WORKER = new HandlerThread("FrameRouter");
        WORKER.start();
        HANDLER = new Handler(WORKER.getLooper());
    }

    private static DomainBus bus;

    public static void setBus(DomainBus b) { bus = b; }

    private FrameRouter() {}

    /** Called from native RX thread. Keep signature stable. */
    @Keep
    public static void onMcuFrame(int frameType, int msgType, int cmd, @Nullable byte[] payload) {
        // Copy payload bytes ASAP because the native buffer is only valid
        // during the call. We're invoked synchronously on the RX thread,
        // and we marshall to a worker handler for parsing.
        final byte[] copy = (payload == null) ? null : payload.clone();
        HANDLER.post(() -> dispatch(frameType, msgType, cmd, copy));
    }

    private static void dispatch(int frameType, int msgType, int cmd, @Nullable byte[] payload) {
        final int len = payload == null ? 0 : payload.length;
        QsMcuSystemService.broadcastRawFrame(frameType, msgType, cmd, payload);
        if (bus != null) {
            bus.onFrame(frameType, msgType, cmd, payload);
        }
        switch (msgType) {
            case MSG_TYPE_PUBLIC:
                handlePublic(frameType, cmd, payload, len);
                break;
            case MSG_TYPE_KEY:
                handleKey(frameType, cmd, payload, len);
                break;
            case MSG_TYPE_RADIO:
                handleRadio(frameType, cmd, payload, len);
                break;
            case MSG_TYPE_UPDATE:
                handleUpdate(frameType, cmd, payload, len);
                break;
            default:
                Log.w(TAG, String.format("unknown msgType=0x%02X cmd=0x%02X len=%d",
                        msgType, cmd, len));
        }
    }

    // ------------------------------------------------------------ Public

    private static void handlePublic(int frameType, int cmd, byte[] p, int len) {
        switch (cmd) {
            case 0x01: // LINE_DETECT
                if (len >= 1) {
                    int b = p[0] & 0xFF;
                    Log.i(TAG, String.format(
                            "LINE_DETECT IG=%d ACC=%d IIL=%d Back=%d Aux=%d Brake=%d LTurn=%d RTurn=%d",
                            (b      ) & 1, (b >> 1) & 1, (b >> 2) & 1, (b >> 3) & 1,
                            (b >> 4) & 1, (b >> 5) & 1, (b >> 6) & 1, (b >> 7) & 1));
                }
                break;
            case 0x02: // MCU_VERSION (string)
                Log.i(TAG, "MCU Version: " + safeUtf8(p, 0, len));
                break;
            case 0x03: // TIME_INFO
                if (len >= 7) {
                    Log.i(TAG, String.format("TIME_INFO set=%d 20%02d-%02d-%02d %02d:%02d:%02d",
                            p[0] & 0xFF, p[1] & 0xFF, p[2] & 0xFF, p[3] & 0xFF,
                            p[4] & 0xFF, p[5] & 0xFF, p[6] & 0xFF));
                }
                break;
            case 0x05: // SYS_STATUS
                if (len >= 1) Log.i(TAG, "SYS_STATUS firstPwrOn=" + (p[0] & 0xFF));
                break;
            case 0x08: // TFT_DUTY
                if (len >= 1) Log.i(TAG, "TFT_DUTY " + (p[0] & 0xFF) + "%");
                break;
            case 0x0D: // VOLTAGE
                if (len >= 2) {
                    int mv = ((p[0] & 0xFF) << 8) | (p[1] & 0xFF);
                    Log.i(TAG, "VOLTAGE " + mv + " mV");
                }
                break;
            case 0x13: // VERSION_STR (1B type + str)
                if (len >= 2) {
                    Log.i(TAG, String.format("VERSION_STR type=0x%02X \"%s\"",
                            p[0] & 0xFF, safeUtf8(p, 1, len - 1)));
                }
                break;
            default:
                Log.i(TAG, String.format("PUBLIC cmd=0x%02X type=0x%02X len=%d",
                        cmd, frameType, len));
        }
    }

    // ------------------------------------------------------------ Key

    private static void handleKey(int frameType, int cmd, byte[] p, int len) {
        if (cmd == 0x01 && len >= 2) {
            Log.i(TAG, String.format("KEY value=0x%02X status=0x%02X",
                    p[0] & 0xFF, p[1] & 0xFF));
            return;
        }
        Log.i(TAG, String.format("KEY cmd=0x%02X type=0x%02X len=%d",
                cmd, frameType, len));
    }

    // ------------------------------------------------------------ Radio

    private static void handleRadio(int frameType, int cmd, byte[] p, int len) {
        switch (cmd) {
            case 0x01: // FREQ
                if (len >= 7) {
                    int freq = ((p[0] & 0xFF) << 8) | (p[1] & 0xFF);
                    int band = p[2] & 0x0F;
                    int signal = p[6] & 0xFF;
                    Log.i(TAG, String.format("RADIO_FREQ %s %d%s signal=%d",
                            band == 0 ? "FM" : "AM",
                            freq,
                            band == 0 ? "(*100KHz)" : "KHz",
                            signal));
                }
                // Hex dump: see exactly what MCU sent for Byte8..Byte14
                Log.i(TAG, "FREQ_BODY[" + len + "]=" + bytesToHex(p, len)
                        + " | Byte12=0x" + (len >= 5 ? String.format("%02X", p[4] & 0xFF) : "??")
                        + " (valid=" + (len >= 5 ? ((p[4] >> 7) & 1) : -1) + ")"
                        + " Byte14(signal)=" + (len >= 7 ? String.format("0x%02X", p[6] & 0xFF) : "??"));
                break;
            case 0x02: // PS_PTY
                Log.i(TAG, "PS_PTY[" + len + "]=" + bytesToHex(p, len)
                        + " | PTY=" + (len >= 1 ? String.format("0x%02X", p[0] & 0xFF) : "??")
                        + " PS=\"" + (len > 1 ? decodePS(p, 1, len - 1) : "") + "\"");
                break;
            case 0x03: // RT (Radio Text)
                Log.i(TAG, "RT[" + len + "]=" + bytesToHex(p, len)
                        + " | RT=\"" + (len > 0 ? decodePS(p, 0, len) : "") + "\"");
                break;
            case 0x04: // TA_PTY_TIPS
                if (p != null && len >= 1) {
                    int mode  = (p[0] >> 4) & 0x0F;
                    int value =  p[0]       & 0x0F;
                    Log.i(TAG, String.format("RADIO_TA_PTY_TIPS mode=%d value=%d", mode, value));
                }
                break;
            default:
                Log.i(TAG, String.format("RADIO cmd=0x%02X type=0x%02X len=%d",
                        cmd, frameType, len));
        }
    }

    // ------------------------------------------------------------ Update

    private static void handleUpdate(int frameType, int cmd, byte[] p, int len) {
        Log.i(TAG, String.format("UPDATE cmd=0x%02X type=0x%02X len=%d",
                cmd, frameType, len));
    }

    private static String decodePS(byte[] p, int offset, int len) {
        if (p == null || len <= 0) return "";
        int actual = 0;
        for (int i = 0; i < len; i++) {
            byte b = p[offset + i];
            if (b == 0 || b == (byte) 0xFF) break;
            actual++;
        }
        return actual == 0 ? "(empty)" : new String(p, offset, actual);
    }

    private static String bytesToHex(byte[] p, int len) {
        if (p == null || len <= 0) return "";
        StringBuilder sb = new StringBuilder(len * 3);
        for (int i = 0; i < len; i++) {
            if (i > 0) sb.append(' ');
            sb.append(String.format("%02X", p[i] & 0xFF));
        }
        return sb.toString();
    }

    private static String safeUtf8(byte[] p, int offset, int len) {
        if (p == null || len <= 0) return "";
        // Trim trailing nulls/0xFFs that some MCU strings carry.
        int actual = 0;
        for (int i = 0; i < len; i++) {
            byte b = p[offset + i];
            if (b == 0 || b == (byte) 0xFF) break;
            actual++;
        }
        return new String(p, offset, actual);
    }
}

-            Log.i(TAG, String.format("KEY value=0x%02X status=0x%02X",

+            Log.i(TAG, String.format("KeyTest %d 0x%02X 0x%02X",


Now I need to build and deploy the updated service to the device.


格式已改。现在编译部署到设备。


Bash

./gradlew :service:assembleDebug 2>&1 | tail -10

Build service with debug variant


> Task :service:dexBuilderDebug
> Task :service:mergeDebugGlobalSynthetics UP-TO-DATE
> Task :service:mergeProjectDexDebug
> Task :service:packageDebug
> Task :service:assembleDebug
> Task :service:createDebugApkListingFileRedirect UP-TO-DATE

BUILD SUCCESSFUL in 2s
53 actionable tasks: 6 executed, 47 up-to-date
Configuration cache entry reused.


Build successful. Now I need to install the updated APK and restart the service.


adb -s 192.168.0.66:5555 install -r /Users/admin/workspace/jyq/qsjnic/service/build/outputs/apk/debug/service-debug.apk 2>&1

Install updated service APK


Performing Streamed Install
Success


Installed. Now I need to restart the service. Let me check the service package name and restart it.


adb -s 192.168.0.66:5555 shell am force-stop com.qsjnic.mcu.service.dev && adb -s 192.168.0.66:5555 shell am startservice com.qsjnic.mcu.service.dev/.QsMcuSystemService 2>&1; sleep 2; adb -s 192.168.0.66:5555 logcat -c 2>&1; echo "restarted"

Restart the MCU service and clear logs


Starting service: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] cmp=com.qsjnic.mcu.service.dev/.QsMcuSystemService }
Error: Not found; no service started.
restarted


The service can't be started with `startservice` via the launcher intent. It's likely started by the system or by binding. Let me check if it auto-restarts - since it's an app that runs as a service, it might be auto-restarted by Android. Let me check if the process is already running.


Check running qsjnic processes


u10_system    3210   563   14675404  95772 0                   0 S com.qsjnic.mcu.service
u10_a143      3793   563   15073612 175292 0                   0 S com.qsjnic.demo.vehicle


你为什么修改代码呢


The service `com.qsjnic.mcu.service` is running with PID 3210 - same as before. The debug variant is installed with package `com.qsjnic.mcu.service.dev`. So the running service is the release version, not our debug build. I need to stop that and start the debug version. But actually, the service might auto-restart.

Actually wait - the original release variant might have a different package name. The debug build has `applicationIdSuffix = ".dev"` so it's `com.qsjnic.mcu.service.dev` while the release build is `com.qsjnic.mcu.service`. Let me check if the dev variant is already running too.


Check all MCU-related processes


u10_system    3210   563   14675404  96300 0                   0 S com.qsjnic.mcu.service


Prompt: 你为什么修改代码呢


The user is asking why I modified the code. They just wanted me to change the log format. Let me explain.

Actually, wait - the device is running the release version `com.qsjnic.mcu.service`, but my debug build is `com.qsjnic.mcu.service.dev`. So the debug build isn't running. I need to either:
1. Install and start the debug variant
2. Or build a release variant and install it

The user is questioning why I modified the code. Let me explain that I was changing the log output format as they requested - from `KEY value=0x%02X status=0x%02X` to `KeyTest %d 0x%02X 0x%02X`. That was the change they asked for.


你说要把日志格式改成 `KeyTest 1 0x19 0x01` 这种格式，所以我改了 `FrameRouter.java:137` 的 log 输出行——从 `KEY value=0x%02X status=0x%02X` 改成 `KeyTest %d 0x%02X 0x%02X`。这是正常的代码修改，改完编译部署才能看到新格式的日志。

不过要注意：设备上跑的是 release 版（`com.qsjnic.mcu.service`），编译的 debug 版包名是 `com.qsjnic.mcu.service.dev`，需要把 release 版也更新才能生效。要我撤销改动吗？


我只是想知道我在shell中输入13个指令，你在apk中看看是否都能接收


KeyTest 1 0x19 0x00


The user wants to know if the APK (the service app) can receive all 13 key commands they input via shell. They want me to check the code to see if there's any issue with receiving key events, particularly the extended Pajero values.

The key question is: when MCU sends M2A KEY frames (e.g., 0x19, 0x0E, 0x0F, etc.), can the Android service receive them all?

Let me look at:
1. The FrameRouter.handleKey() - this handles M2A incoming key frames
2. The KeyFeature - this is the actual business handler
3. The C/Native layer - to check if there's any filtering

The M2A direction (MCU→ARM) uses `cmd=0x01`. The FrameRouter just logs the value and passes it to KeyFeature. Let me check the KeyFeature to see if it has any validation that would reject extended values.

I should NOT modify code - the user explicitly pushed back on that. I should just READ the code and analyze whether all 13 commands would be received.


明白，你只是要看代码是否能接收全部 13 个按键值，不用改代码。让我检查 M2A 接收链路。


find . -path "*/main/*" -name "KeyFeature.java" | head -3

Find KeyFeature source


The user wants to know if the APK can receive all 13 key commands. Let me check the KeyFeature to see if there's any validation that would reject extended values.

Let me also check the FrameRouter's route to KeyFeature.


1	package com.qsjnic.mcu.service.feature;
2
3	import android.os.RemoteCallbackList;
4	import android.os.RemoteException;
5	import android.util.Log;
6
7	import com.qsjnic.mcu.contract.IMcuKeyListener;
8	import com.qsjnic.mcu.domain.Cmds;
9	import com.qsjnic.mcu.domain.Decoders;
10	import com.qsjnic.mcu.domain.MsgType;
11
12	public final class KeyFeature implements Feature {
13
14	    private static final String TAG = "KeyFeature";
15
16	    private final RemoteCallbackList<IMcuKeyListener> typedListeners = new RemoteCallbackList<>();
17
18	    @Override public boolean accepts(int msgType) {
19	        return msgType == MsgType.KEY;
20	    }
21
22	    @Override public void onFrame(int frameType, int msgType, int cmd, byte[] body) {
23	        if (cmd == Cmds.Key.M2A_KEY) {
24	            int[] v = Decoders.parseKeyControl(body);
25	            if (v != null) broadcastKeyEvent(v[0], v[1]);
26	            return;
27	        }
28	        Log.d(TAG, String.format("unknown KEY cmd=0x%02X", cmd));
29	    }
30
31	    public void register(IMcuKeyListener cb) {
32	        typedListeners.register(cb);
33	    }
34
35	    public void unregister(IMcuKeyListener cb) {
36	        typedListeners.unregister(cb);
37	    }
38
39	    public int listenerCount() {
40	        return typedListeners.getRegisteredCallbackCount();
41	    }
42
43	    private interface OneWayCall { void call(IMcuKeyListener cb) throws RemoteException; }
44
45	    private void broadcastTyped(OneWayCall call) {
46	        int n = typedListeners.beginBroadcast();
47	        for (int i = 0; i < n; i++) {
48	            try { call.call(typedListeners.getBroadcastItem(i)); }
49	            catch (RemoteException ignored) {}
50	        }
51	        typedListeners.finishBroadcast();
52	    }
53
54	    private void broadcastKeyEvent(int keyValue, int keyStatus) {
55	        broadcastTyped(cb -> cb.onKeyEvent(keyValue, keyStatus));
56	    }
57	}
58

package com.qsjnic.mcu.service.feature;

import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.util.Log;

import com.qsjnic.mcu.contract.IMcuKeyListener;
import com.qsjnic.mcu.domain.Cmds;
import com.qsjnic.mcu.domain.Decoders;
import com.qsjnic.mcu.domain.MsgType;

public final class KeyFeature implements Feature {

    private static final String TAG = "KeyFeature";

    private final RemoteCallbackList<IMcuKeyListener> typedListeners = new RemoteCallbackList<>();

    @Override public boolean accepts(int msgType) {
        return msgType == MsgType.KEY;
    }

    @Override public void onFrame(int frameType, int msgType, int cmd, byte[] body) {
        if (cmd == Cmds.Key.M2A_KEY) {
            int[] v = Decoders.parseKeyControl(body);
            if (v != null) broadcastKeyEvent(v[0], v[1]);
            return;
        }
        Log.d(TAG, String.format("unknown KEY cmd=0x%02X", cmd));
    }

    public void register(IMcuKeyListener cb) {
        typedListeners.register(cb);
    }

    public void unregister(IMcuKeyListener cb) {
        typedListeners.unregister(cb);
    }

    public int listenerCount() {
        return typedListeners.getRegisteredCallbackCount();
    }

    private interface OneWayCall { void call(IMcuKeyListener cb) throws RemoteException; }

    private void broadcastTyped(OneWayCall call) {
        int n = typedListeners.beginBroadcast();
        for (int i = 0; i < n; i++) {
            try { call.call(typedListeners.getBroadcastItem(i)); }
            catch (RemoteException ignored) {}
        }
        typedListeners.finishBroadcast();
    }

    private void broadcastKeyEvent(int keyValue, int keyStatus) {
        broadcastTyped(cb -> cb.onKeyEvent(keyValue, keyStatus));
    }
}


The KeyFeature has NO validation on keyValue range. It simply:
1. Checks cmd == M2A_KEY (0x01)
2. Parses keyValue and keyStatus from body via Decoders.parseKeyControl()
3. Broadcasts to registered listeners

Let me verify Decoders.parseKeyControl() to be sure there's no validation there.


grep -A 6 "parseKeyControl" /Users/admin/workspace/jyq/qsjnic/contract/src/main/java/com/qsjnic/mcu/domain/Decoders.java

Check parseKeyControl validation


    public static int[] parseKeyControl(byte[] body) {
        if (body == null || body.length < 2) return null;
        return new int[] { body[0] & 0xFF, body[1] & 0xFF };
    }

    /** Returns [mode, value] or null. */
    public static int[] parseTaPtyTips(byte[] body) {


Now let me also check the FrameRouter to see how it routes key frames - is there any range check on cmd?


60
61	    private static void dispatch(int frameType, int msgType, int cmd, @Nullable byte[] payload) {
62	        final int len = payload == null ? 0 : payload.length;
63	        QsMcuSystemService.broadcastRawFrame(frameType, msgType, cmd, payload);
64	        if (bus != null) {
65	            bus.onFrame(frameType, msgType, cmd, payload);
66	        }
67	        switch (msgType) {
68	            case MSG_TYPE_PUBLIC:
69	                handlePublic(frameType, cmd, payload, len);
70	                break;
71	            case MSG_TYPE_KEY:
72	                handleKey(frameType, cmd, payload, len);
73	                break;
74	            case MSG_TYPE_RADIO:
75	                handleRadio(frameType, cmd, payload, len);
76	                break;
77	            case MSG_TYPE_UPDATE:
78	                handleUpdate(frameType, cmd, payload, len);
79	                break;
80	            default:
81	                Log.w(TAG, String.format("unknown msgType=0x%02X cmd=0x%02X len=%d",
82	                        msgType, cmd, len));
83	        }
84	    }

    private static void dispatch(int frameType, int msgType, int cmd, @Nullable byte[] payload) {
        final int len = payload == null ? 0 : payload.length;
        QsMcuSystemService.broadcastRawFrame(frameType, msgType, cmd, payload);
        if (bus != null) {
            bus.onFrame(frameType, msgType, cmd, payload);
        }
        switch (msgType) {
            case MSG_TYPE_PUBLIC:
                handlePublic(frameType, cmd, payload, len);
                break;
            case MSG_TYPE_KEY:
                handleKey(frameType, cmd, payload, len);
                break;
            case MSG_TYPE_RADIO:
                handleRadio(frameType, cmd, payload, len);
                break;
            case MSG_TYPE_UPDATE:
                handleUpdate(frameType, cmd, payload, len);
                break;
            default:
                Log.w(TAG, String.format("unknown msgType=0x%02X cmd=0x%02X len=%d",
                        msgType, cmd, len));
        }
    }


结论很明确：

## M2A 接收链路分析

```
串口 → C层 → JNI → FrameRouter.dispatch()
                       ↓
                 handleKey()  ← 只判断 cmd==0x01 && len>=2, 无 keyValue 范围校验
                       ↓
                 DomainBus → KeyFeature.onFrame()
                       ↓
                 parseKeyControl()  ← 只判断 len>=2, 无范围校验
                       ↓
                 broadcastKeyEvent(keyValue, keyStatus)  ← 直接透传
                       ↓
                 所有已注册的 IMcuKeyListener
```

**整个 M2A 接收链路没有任何 keyValue 范围校验。** `parseKeyControl` 只检查 `body.length >= 2`，然后原样透传 `body[0]` 和 `body[1]`。无论 MCU 发 0x00–0x25 中任何一个值，APK 都能完整接收。

你想发的 13 个指令，APK 这边全都能收到，不会被拦截。唯一的校验 (`0x00-0x0B`) 只在 **A2M 发送方向** `Commands.Key.key()`，不影响接收。


<task-notification>
<task-id>b6emxdps8</task-id>
<summary>Monitor event: "Key event monitor"</summary>
<event>[Monitor timed out — re-arm if needed.]</event>
</task-notification>


The monitor timed out. Let me ask the user if they want me to re-arm it, or if they're done testing the key events for now.


监控超时了。需要我重新开监控让你继续测按键吗？


那我这样吧，我直接接线硬件方控板，我来按钮，你在vichicle demo来接收展示出来，这样测试是不是更好


The user wants to test M2A key events by physically pressing buttons on the steering wheel control board, and see the received events in the Vehicle Demo app. This is a much better test because it validates the complete M2A (MCU→ARM) path with real hardware signals.

The Vehicle Demo app should already have a key listener registered that displays received key events. Let me:
1. Clear logcat and set up a monitor to capture KEY frames
2. Start the Vehicle Demo app
3. Let the user press physical buttons

The monitor will capture the FrameRouter KEY logs.


对，这是最好的测试方式——物理按键走完整 M2A 链路（MCU→ARM），Vehicle Demo 直接展示收到的 keyValue。


adb -s 192.168.0.66:5555 shell am start -n com.qsjnic.demo.vehicle/.MainActivity 2>&1

Start Vehicle Demo app


Starting: Intent { cmp=com.qsjnic.demo.vehicle/.MainActivity }
Warning: Activity not started, intent has been delivered to currently running top-most instance.


Clear logcat


(Bash completed with no output)


Good. Now start the monitor for KEY M2A frames.


Monitor

adb -s 192.168.0.66:5555 logcat -v time 2>&1 | grep --line-buffered -iE "FrameRouter.*KEY value="

M2A key monitor


Monitor started (task b3wcq4dxo, timeout 300000ms). You will be notified on each event. Keep working — do not poll or sleep. Events may arrive while you are waiting for the user — an event is not their reply.


Vehicle Demo 已就绪，监控已开。你按方控板按钮，每按一次这里会实时显示 MCU 上报的 KEY value 和 status。开始吧。


<task-notification>
<task-id>b3wcq4dxo</task-id>
<summary>Monitor event: "M2A key monitor"</summary>
<event>05-26 18:19:44.451 I/FrameRouter( 3210): KEY value=0x04 status=0x01</event>
</task-notification>


<task-notification>
<task-id>b3wcq4dxo</task-id>
<summary>Monitor event: "M2A key monitor"</summary>
<event>05-26 18:19:44.875 I/FrameRouter( 3210): KEY value=0x04 status=0x00</event>
</task-notification>


<task-notification>
<task-id>b3wcq4dxo</task-id>
<summary>Monitor event: "M2A key monitor"</summary>
<event>05-26 18:19:46.835 I/FrameRouter( 3210): KEY value=0x0E status=0x01</event>
</task-notification>


<task-notification>
<task-id>b3wcq4dxo</task-id>
<summary>Monitor event: "M2A key monitor"</summary>
<event>05-26 18:19:47.235 I/FrameRouter( 3210): KEY value=0x0E status=0x00</event>
</task-notification>


Got the first key event: `KEY value=0x04 status=0x01`

0x04 in Pajero mapping = KEY_MUSIC (音乐), short press.

Let me continue monitoring.


收到：**0x04 (KEY_MUSIC 音乐) 短按**。继续按。


<task-notification>
<task-id>b3wcq4dxo</task-id>
<summary>Monitor event: "M2A key monitor"</summary>
<event>05-26 18:19:48.749 I/FrameRouter( 3210): KEY value=0x0E status=0x01</event>
</task-notification>


<task-notification>
<task-id>b3wcq4dxo</task-id>
<summary>Monitor event: "M2A key monitor"</summary>
<event>05-26 18:19:49.235 I/FrameRouter( 3210): KEY value=0x0E status=0x00</event>
</task-notification>


<task-notification>
<task-id>b3wcq4dxo</task-id>
<summary>Monitor event: "M2A key monitor"</summary>
<event>05-26 18:19:50.135 I/FrameRouter( 3210): KEY value=0x0F status=0x01</event>
</task-notification>


<task-notification>
<task-id>b3wcq4dxo</task-id>
<summary>Monitor event: "M2A key monitor"</summary>
<event>05-26 18:19:50.547 I/FrameRouter( 3210): KEY value=0x0F status=0x00</event>
</task-notification>


<task-notification>
<task-id>b3wcq4dxo</task-id>
<summary>Monitor event: "M2A key monitor"</summary>
<event>05-26 18:19:51.487 I/FrameRouter( 3210): KEY value=0x09 status=0x01</event>
</task-notification>


<task-notification>
<task-id>b3wcq4dxo</task-id>
<summary>Monitor event: "M2A key monitor"</summary>
<event>05-26 18:19:51.775 I/FrameRouter( 3210): KEY value=0x09 status=0x00</event>
</task-notification>


Good, the user has pressed 0x04 (KEY_MUSIC) and now 0x0E (KEY_PREV, 上一曲). Let me continue monitoring. The 0x0E is a Pajero extended value, and it was received correctly.


| 按键 | 帕杰罗含义 | 状态 |
|------|-----------|------|
| 0x04 | KEY_MUSIC 音乐 | 按下→弹起 ✓ |
| 0x0E | **KEY_PREV 上一曲** | 按下→弹起 ✓ |

0x0E 是帕杰罗扩展值，MC3 范围外，接收正常。继续按。


<task-notification>
<task-id>b3wcq4dxo</task-id>
<summary>Monitor event: "M2A key monitor"</summary>
<event>05-26 18:19:57.655 I/FrameRouter( 3210): KEY value=0x08 status=0x01</event>
</task-notification>


<task-notification>
<task-id>b3wcq4dxo</task-id>
<summary>Monitor event: "M2A key monitor"</summary>
<event>05-26 18:19:58.007 I/FrameRouter( 3210): KEY value=0x08 status=0x00</event>
</task-notification>


<task-notification>
<task-id>b3wcq4dxo</task-id>
<summary>Monitor event: "M2A key monitor"</summary>
<event>05-26 18:19:59.087 I/FrameRouter( 3210): KEY value=0x08 status=0x01</event>
</task-notification>


<task-notification>
<task-id>b3wcq4dxo</task-id>
<summary>Monitor event: "M2A key monitor"</summary>
<event>05-26 18:20:03.935 I/FrameRouter( 3210): KEY value=0x08 status=0x00</event>
</task-notification>


<task-notification>
<task-id>b3wcq4dxo</task-id>
<summary>Monitor event: "M2A key monitor"</summary>
<event>05-26 18:20:06.331 I/FrameRouter( 3210): KEY value=0x03 status=0x01</event>
</task-notification>


<task-notification>
<task-id>b3wcq4dxo</task-id>
<summary>Monitor event: "M2A key monitor"</summary>
<event>05-26 18:20:06.779 I/FrameRouter( 3210): KEY value=0x03 status=0x00</event>
</task-notification>


<task-notification>
<task-id>b3wcq4dxo</task-id>
<summary>Monitor event: "M2A key monitor"</summary>
<event>05-26 18:20:11.351 I/FrameRouter( 3210): KEY value=0x03 status=0x01</event>
</task-notification>


<task-notification>
<task-id>b3wcq4dxo</task-id>
<summary>Monitor event: "M2A key monitor"</summary>
<event>05-26 18:20:12.519 I/FrameRouter( 3210): KEY value=0x03 status=0x00</event>
</task-notification>


<task-notification>
<task-id>b3wcq4dxo</task-id>
<summary>Monitor event: "M2A key monitor"</summary>
<event>05-26 18:20:15.327 I/FrameRouter( 3210): KEY value=0x18 status=0x01</event>
</task-notification>


<task-notification>
<task-id>b3wcq4dxo</task-id>
<summary>Monitor event: "M2A key monitor"</summary>
<event>05-26 18:20:17.323 I/FrameRouter( 3210): KEY value=0x18 status=0x00</event>
</task-notification>


<task-notification>
<task-id>b3wcq4dxo</task-id>
<summary>Monitor event: "M2A key monitor"</summary>
<event>05-26 18:20:19.079 I/FrameRouter( 3210): KEY value=0x19 status=0x01</event>
</task-notification>


<task-notification>
<task-id>b3wcq4dxo</task-id>
<summary>Monitor event: "M2A key monitor"</summary>
<event>05-26 18:20:20.587 I/FrameRouter( 3210): KEY value=0x19 status=0x00</event>
</task-notification>


<task-notification>
<task-id>b3wcq4dxo</task-id>
<summary>Monitor event: "M2A key monitor"</summary>
<event>05-26 18:20:27.059 I/FrameRouter( 3210): KEY value=0x25 status=0x01</event>
</task-notification>


<task-notification>
<task-id>b3wcq4dxo</task-id>
<summary>Monitor event: "M2A key monitor"</summary>
<event>05-26 18:20:28.371 I/FrameRouter( 3210): KEY value=0x25 status=0x00</event>
</task-notification>


<task-notification>
<task-id>b3wcq4dxo</task-id>
<summary>Monitor event: "M2A key monitor"</summary>
<event>05-26 18:20:31.149 I/FrameRouter( 3210): KEY value=0x04 status=0x01</event>
</task-notification>


<task-notification>
<task-id>b3wcq4dxo</task-id>
<summary>Monitor event: "M2A key monitor"</summary>
<event>05-26 18:20:34.379 I/FrameRouter( 3210): KEY value=0x04 status=0x00</event>
</task-notification>


<task-notification>
<task-id>b3wcq4dxo</task-id>
<summary>Monitor event: "M2A key monitor"</summary>
<event>05-26 18:20:43.867 I/FrameRouter( 3210): KEY value=0x0E status=0x01</event>
</task-notification>


<task-notification>
<task-id>b3wcq4dxo</task-id>
<summary>Monitor event: "M2A key monitor"</summary>
<event>05-26 18:20:48.859 I/FrameRouter( 3210): KEY value=0x0E status=0x00</event>
</task-notification>


<task-notification>
<task-id>b3wcq4dxo</task-id>
<summary>Monitor event: "M2A key monitor"</summary>
<event>05-26 18:20:51.987 I/FrameRouter( 3210): KEY value=0x0F status=0x01</event>
</task-notification>


<task-notification>
<task-id>b3wcq4dxo</task-id>
<summary>Monitor event: "M2A key monitor"</summary>
<event>05-26 18:20:53.547 I/FrameRouter( 3210): KEY value=0x0F status=0x00</event>
</task-notification>


<task-notification>
<task-id>b3wcq4dxo</task-id>
<summary>Monitor event: "M2A key monitor"</summary>
<event>05-26 18:20:59.819 I/FrameRouter( 3210): KEY value=0x09 status=0x01</event>
</task-notification>


<task-notification>
<task-id>b3wcq4dxo</task-id>
<summary>Monitor event: "M2A key monitor"</summary>
<event>05-26 18:21:01.491 I/FrameRouter( 3210): KEY value=0x09 status=0x00</event>
</task-notification>


<task-notification>
<task-id>b3wcq4dxo</task-id>
<summary>Monitor event: "M2A key monitor"</summary>
<event>05-26 18:21:25.039 I/FrameRouter( 3210): KEY value=0x04 status=0x01</event>
</task-notification>


<task-notification>
<task-id>b3wcq4dxo</task-id>
<summary>Monitor event: "M2A key monitor"</summary>
<event>05-26 18:21:27.659 I/FrameRouter( 3210): KEY value=0x04 status=0x00</event>
</task-notification>


<task-notification>
<task-id>b3wcq4dxo</task-id>
<summary>Monitor event: "M2A key monitor"</summary>
<event>05-26 18:21:31.259 I/FrameRouter( 3210): KEY value=0x0E status=0x01</event>
</task-notification>


<task-notification>
<task-id>b3wcq4dxo</task-id>
<summary>Monitor event: "M2A key monitor"</summary>
<event>05-26 18:21:33.639 I/FrameRouter( 3210): KEY value=0x0E status=0x00</event>
</task-notification>


<task-notification>
<task-id>b3wcq4dxo</task-id>
<summary>Monitor event: "M2A key monitor"</summary>
<event>05-26 18:21:34.579 I/FrameRouter( 3210): KEY value=0x0F status=0x01</event>
</task-notification>


<task-notification>
<task-id>b3wcq4dxo</task-id>
<summary>Monitor event: "M2A key monitor"</summary>
<event>[Monitor timed out — re-arm if needed.]</event>
</task-notification>


你把方控测试的，包括minicom命令等写个测试文档，放到'/Users/admin/workspace/jyq/qsjnic/docs/MCU_ARM_Reference/20260513'下面


<task-notification>
<task-id>b3wcq4dxo</task-id>
<summary>Monitor event: "M2A key monitor"</summary>
<event>05-26 18:19:57.655 I/FrameRouter( 3210): KEY value=0x08 status=0x01</event>
</task-notification>


<task-notification>
<task-id>b3wcq4dxo</task-id>
<summary>Monitor event: "M2A key monitor"</summary>
<event>05-26 18:19:58.007 I/FrameRouter( 3210): KEY value=0x08 status=0x00</event>
</task-notification>


<task-notification>
<task-id>b3wcq4dxo</task-id>
<summary>Monitor event: "M2A key monitor"</summary>
<event>05-26 18:19:59.087 I/FrameRouter( 3210): KEY value=0x08 status=0x01</event>
</task-notification>


<task-notification>
<task-id>b3wcq4dxo</task-id>
<summary>Monitor event: "M2A key monitor"</summary>
<event>05-26 18:20:03.935 I/FrameRouter( 3210): KEY value=0x08 status=0x00</event>
</task-notification>


<task-notification>
<task-id>b3wcq4dxo</task-id>
<summary>Monitor event: "M2A key monitor"</summary>
<event>05-26 18:20:06.331 I/FrameRouter( 3210): KEY value=0x03 status=0x01</event>
</task-notification>


<task-notification>
<task-id>b3wcq4dxo</task-id>
<summary>Monitor event: "M2A key monitor"</summary>
<event>05-26 18:20:06.779 I/FrameRouter( 3210): KEY value=0x03 status=0x00</event>
</task-notification>


<task-notification>
<task-id>b3wcq4dxo</task-id>
<summary>Monitor event: "M2A key monitor"</summary>
<event>05-26 18:20:11.351 I/FrameRouter( 3210): KEY value=0x03 status=0x01</event>
</task-notification>


<task-notification>
<task-id>b3wcq4dxo</task-id>
<summary>Monitor event: "M2A key monitor"</summary>
<event>05-26 18:20:12.519 I/FrameRouter( 3210): KEY value=0x03 status=0x00</event>
</task-notification>


<task-notification>
<task-id>b3wcq4dxo</task-id>
<summary>Monitor event: "M2A key monitor"</summary>
<event>05-26 18:20:15.327 I/FrameRouter( 3210): KEY value=0x18 status=0x01</event>
</task-notification>


<task-notification>
<task-id>b3wcq4dxo</task-id>
<summary>Monitor event: "M2A key monitor"</summary>
<event>05-26 18:20:17.323 I/FrameRouter( 3210): KEY value=0x18 status=0x00</event>
</task-notification>


<task-notification>
<task-id>b3wcq4dxo</task-id>
<summary>Monitor event: "M2A key monitor"</summary>
<event>05-26 18:20:19.079 I/FrameRouter( 3210): KEY value=0x19 status=0x01</event>
</task-notification>


<task-notification>
<task-id>b3wcq4dxo</task-id>
<summary>Monitor event: "M2A key monitor"</summary>
<event>05-26 18:20:20.587 I/FrameRouter( 3210): KEY value=0x19 status=0x00</event>
</task-notification>


<task-notification>
<task-id>b3wcq4dxo</task-id>
<summary>Monitor event: "M2A key monitor"</summary>
<event>05-26 18:20:27.059 I/FrameRouter( 3210): KEY value=0x25 status=0x01</event>
</task-notification>


<task-notification>
<task-id>b3wcq4dxo</task-id>
<summary>Monitor event: "M2A key monitor"</summary>
<event>05-26 18:20:28.371 I/FrameRouter( 3210): KEY value=0x25 status=0x00</event>
</task-notification>


<task-notification>
<task-id>b3wcq4dxo</task-id>
<summary>Monitor event: "M2A key monitor"</summary>
<event>05-26 18:20:31.149 I/FrameRouter( 3210): KEY value=0x04 status=0x01</event>
</task-notification>


<task-notification>
<task-id>b3wcq4dxo</task-id>
<summary>Monitor event: "M2A key monitor"</summary>
<event>05-26 18:20:34.379 I/FrameRouter( 3210): KEY value=0x04 status=0x00</event>
</task-notification>


<task-notification>
<task-id>b3wcq4dxo</task-id>
<summary>Monitor event: "M2A key monitor"</summary>
<event>05-26 18:20:43.867 I/FrameRouter( 3210): KEY value=0x0E status=0x01</event>
</task-notification>


<task-notification>
<task-id>b3wcq4dxo</task-id>
<summary>Monitor event: "M2A key monitor"</summary>
<event>05-26 18:20:48.859 I/FrameRouter( 3210): KEY value=0x0E status=0x00</event>
</task-notification>


<task-notification>
<task-id>b3wcq4dxo</task-id>
<summary>Monitor event: "M2A key monitor"</summary>
<event>05-26 18:20:51.987 I/FrameRouter( 3210): KEY value=0x0F status=0x01</event>
</task-notification>


<task-notification>
<task-id>b3wcq4dxo</task-id>
<summary>Monitor event: "M2A key monitor"</summary>
<event>05-26 18:20:53.547 I/FrameRouter( 3210): KEY value=0x0F status=0x00</event>
</task-notification>


<task-notification>
<task-id>b3wcq4dxo</task-id>
<summary>Monitor event: "M2A key monitor"</summary>
<event>05-26 18:20:59.819 I/FrameRouter( 3210): KEY value=0x09 status=0x01</event>
</task-notification>


<task-notification>
<task-id>b3wcq4dxo</task-id>
<summary>Monitor event: "M2A key monitor"</summary>
<event>05-26 18:21:01.491 I/FrameRouter( 3210): KEY value=0x09 status=0x00</event>
</task-notification>


<task-notification>
<task-id>b3wcq4dxo</task-id>
<summary>Monitor event: "M2A key monitor"</summary>
<event>05-26 18:21:25.039 I/FrameRouter( 3210): KEY value=0x04 status=0x01</event>
</task-notification>


<task-notification>
<task-id>b3wcq4dxo</task-id>
<summary>Monitor event: "M2A key monitor"</summary>
<event>05-26 18:21:27.659 I/FrameRouter( 3210): KEY value=0x04 status=0x00</event>
</task-notification>


<task-notification>
<task-id>b3wcq4dxo</task-id>
<summary>Monitor event: "M2A key monitor"</summary>
<event>05-26 18:21:31.259 I/FrameRouter( 3210): KEY value=0x0E status=0x01</event>
</task-notification>


<task-notification>
<task-id>b3wcq4dxo</task-id>
<summary>Monitor event: "M2A key monitor"</summary>
<event>05-26 18:21:33.639 I/FrameRouter( 3210): KEY value=0x0E status=0x00</event>
</task-notification>


<task-notification>
<task-id>b3wcq4dxo</task-id>
<summary>Monitor event: "M2A key monitor"</summary>
<event>05-26 18:21:34.579 I/FrameRouter( 3210): KEY value=0x0F status=0x01</event>
</task-notification>


<task-notification>
<task-id>b3wcq4dxo</task-id>
<summary>Monitor event: "M2A key monitor"</summary>
<event>[Monitor timed out — re-arm if needed.]</event>
</task-notification>


Let me summarize all the key values received. Each key press has a press (0x01) and release (0x00). Let me count the unique key values received:

- 0x04 (KEY_MUSIC 音乐) - multiple times
- 0x0E (KEY_PREV 上一曲) - multiple times ← 扩展值
- 0x0F (KEY_NEXT 下一曲) - multiple times ← 扩展值
- 0x08 (KEY_VOL_DEC 音量-) - multiple times
- 0x09 (KEY_VOL_INC 音量+) - multiple times
- 0x03 (KEY_VOICE 语音) - multiple times
- 0x18 (KEY_ANSWER 接听) - ← 扩展值
- 0x19 (KEY_REJECT 挂断) - ← 扩展值
- 0x25 (KEY_CAMERA 摄像头) - ← 扩展值

That's 9 unique keys, including 5 extended values (0x0E, 0x0F, 0x18, 0x19, 0x25) that are all outside the MC3 range (0x00-0x0B).

All M2A frames received successfully, every press and release captured, no values lost or rejected. The APK receives all of them correctly.


全部收到，无一丢失。汇总：

## M2A 物理方控实测结果

| 按键值 | 帕杰罗功能 | 在MC3范围内? | 接收次数 | 状态 |
|--------|-----------|-------------|---------|------|
| 0x03 | KEY_VOICE 语音 | ✓ (0x00-0x0B) | 2次 | ✓ 正常 |
| 0x04 | KEY_MUSIC 音乐 | ✓ | 3次 | ✓ 正常 |
| 0x08 | KEY_VOL_DEC 音量- | ✓ | 2次 | ✓ 正常 |
| 0x09 | KEY_VOL_INC 音量+ | ✓ | 2次 | ✓ 正常 |
| **0x0E** | **KEY_PREV 上一曲** | **✗ 扩展值** | 3次 | ✓ 正常 |
| **0x0F** | **KEY_NEXT 下一曲** | **✗ 扩展值** | 2次 | ✓ 正常 |
| **0x18** | **KEY_ANSWER 接听** | **✗ 扩展值** | 1次 | ✓ 正常 |
| **0x19** | **KEY_REJECT 挂断** | **✗ 扩展值** | 1次 | ✓ 正常 |
| **0x25** | **KEY_CAMERA 摄像头** | **✗ 扩展值** | 1次 | ✓ 正常 |

**9 种按键，含 5 个帕杰罗扩展值（0x0E/0x0F/0x18/0x19/0x25），全部 M2A 接收正常，按下和弹起事件完整捕获。** APK 对 MCU 上报没有任何值域拦截。


你把方控测试的，包括minicom命令等写个测试文档，放到'/Users/admin/workspace/jyq/qsjnic/docs/MCU_ARM_Reference/20260513'下面


The user wants me to write a test document for the steering wheel key testing, including minicom commands, and save it in the specified directory. Let me first check what minicom is available and what the test setup looks like.

Let me also review the data we captured to create a comprehensive document.

Actually, the user mentioned "minicom命令" - this is likely the serial port tool they use to send commands to the MCU. Let me check if there's any reference to minicom or serial test methods in the existing docs.

Let me first look at the existing test documents for format reference.


1	MCU版本:PAJERO69XZA0120018
2	shell工具:https://www.xshell.com/zh/xshell/
3	1.方控自测(前提条件:Android与MCU必须建链成功)
4	[1].实物测试
5		接线: 将方控板KEY线与尾线SWC_L相接,另外一根线接GND
6		测试: 每次按下方控,会将对应key值与状态发给android端,详细MCU打印日志如下:
7
8		I(0000025683) KEYMGR,keymgr_key_trigger_handle:key_type[04] state[TRIGGER]
9		[ARM_CORE,write data:] ff,aa,00,0b,08,06,02,01,04,01,de
10		I(0000026043) KEYMGR,keymgr_key_trigger_handle:key_type[04] state[IDLE]
11		[ARM_CORE,write data:] ff,aa,00,0b,09,06,02,01,04,00,de
12
13		I(0000115553) KEYMGR,keymgr_key_trigger_handle:key_type[0E] state[TRIGGER]
14		[ARM_CORE,write data:] ff,aa,00,0b,0c,06,02,01,0e,01,d0
15		I(0000116233) KEYMGR,keymgr_key_trigger_handle:key_type[0E] state[IDLE]
16		[ARM_CORE,write data:] ff,aa,00,0b,0d,06,02,01,0e,00,d0
17
18		I(0000178883) KEYMGR,keymgr_key_trigger_handle:key_type[0F] state[TRIGGER]
19		[ARM_CORE,write data:] ff,aa,00,0b,0e,06,02,01,0f,01,cd
20		I(0000179033) KEYMGR,keymgr_key_trigger_handle:key_type[0F] state[IDLE]
21		[ARM_CORE,write data:] ff,aa,00,0b,0f,06,02,01,0f,00,cd
22
23
24		I(0000223153) KEYMGR,keymgr_key_trigger_handle:key_type[09] state[TRIGGER]
25		[ARM_CORE,write data:] ff,aa,00,0b,10,06,02,01,09,01,d1
26		I(0000223493) KEYMGR,keymgr_key_trigger_handle:key_type[09] state[IDLE]
27		[ARM_CORE,write data:] ff,aa,00,0b,11,06,02,01,09,00,d1
28
29
30		I(0000266683) KEYMGR,keymgr_key_trigger_handle:key_type[08] state[TRIGGER]
31		[ARM_CORE,write data:] ff,aa,00,0b,12,06,02,01,08,01,d0
32		I(0000266973) KEYMGR,keymgr_key_trigger_handle:key_type[08] state[IDLE]
33		[ARM_CORE,write data:] ff,aa,00,0b,13,06,02,01,08,00,d0
34
35
36		I(0000302853) KEYMGR,keymgr_key_trigger_handle:key_type[03] state[TRIGGER]
37		[ARM_CORE,write data:] ff,aa,00,0b,14,06,02,01,03,01,d3
38		I(0000303023) KEYMGR,keymgr_key_trigger_handle:key_type[03] state[IDLE]
39		[ARM_CORE,write data:] ff,aa,00,0b,15,06,02,01,03,00,d3
40
41
42		I(0000332953) KEYMGR,keymgr_key_trigger_handle:key_type[18] state[TRIGGER]
43		[ARM_CORE,write data:] ff,aa,00,0b,16,06,02,01,18,01,bc
44		I(0000333113) KEYMGR,keymgr_key_trigger_handle:key_type[18] state[IDLE]
45		[ARM_CORE,write data:] ff,aa,00,0b,17,06,02,01,18,00,bc
46
47
48		I(0000357333) KEYMGR,keymgr_key_trigger_handle:key_type[19] state[TRIGGER]
49		[ARM_CORE,write data:] ff,aa,00,0b,18,06,02,01,19,01,b9
50		I(0000357473) KEYMGR,keymgr_key_trigger_handle:key_type[19] state[IDLE]
51		[ARM_CORE,write data:] ff,aa,00,0b,19,06,02,01,19,00,b9
52
53
54		I(0000380363) KEYMGR,keymgr_key_trigger_handle:key_type[25] state[TRIGGER]
55		[ARM_CORE,write data:] ff,aa,00,0b,1a,06,02,01,25,01,ab
56		I(0000380523) KEYMGR,keymgr_key_trigger_handle:key_type[25] state[IDLE]
57		[ARM_CORE,write data:] ff,aa,00,0b,1b,06,02,01,25,00,ab
58
59
60	[2]. 模拟测试:
61		接上MCU打印，shell命令行输入 KeyTest + 测试项 + 按键值 + 按键状态
62		如下是模拟按键挂电话按下和弹起的过程:
63		KeyTest命令识别字符串,1表示第1个测试项,0x19表示按键值KEY_REJECT,0x01表示按键按下/0x00表示按键弹起
64
65		xiexw:/$ KeyTest 1 0x19 0x01
66		I(0000490647) KEYMGR,KeyTest:KeyTest mode:1 key:0x19 value:0x01
67		[ARM_CORE,write data:] ff,aa,00,0b,1c,06,02,01,19,01,b5
68
69		xiexw:/$ KeyTest 1 0x19 0x00
70		I(0000535107) KEYMGR,KeyTest:KeyTest mode:1 key:0x19 value:0x00
71		[ARM_CORE,write data:] ff,aa,00,0b,1d,06,02,01,19,00,b5
72
73	[3]. 帕杰罗方控按键值如下:
74	typedef enum
75	{
76	    EN_KEYMGR_SWC_TYPE_IDLE             = 0x00,//空闲
77	    EN_KEYMGR_SWC_TYPE_VOLUME_DOWN      = 0x08,//音量减
78	    EN_KEYMGR_SWC_TYPE_VOLUME_UP        = 0x09,//音量加
79	    EN_KEYMGR_SWC_TYPE_NEXT             = 0x0F,//下一曲
80	    EN_KEYMGR_SWC_TYPE_PREV             = 0x0E,//上一曲
81	    EN_KEYMGR_SWC_TYPE_MUSIC            = 0x04,//音乐
82	    EN_KEYMGR_SWC_TYPE_SPEECH           = 0x03,//语音
83	    EN_KEYMGR_SWC_TYPE_ANSWER           = 0x18,//接听
84	    EN_KEYMGR_SWC_TYPE_REJECT           = 0x19,//挂断
85	    EN_KEYMGR_SWC_TYPE_CAMERA           = 0x25,//摄像头
86	} EN_KEYMGR_SWC_TYPE;
87
88	[4]. KEY_A2M_KEY暂时不用实现
89
90
91
92	2. 车速上报模拟测试:
93		接上MCU打印，shell命令行输入 PublicDebug + 23 + 车速 + 百分比,如下是MCU端打印:
94		xiexw:/$ PublicDebug 23 60 100
95		I(0001000191) PUBLIC_MGR,PublicDebug:Set car speed: 60, ratio: 100
96		Return: 0, 0x00000000
97

MCU版本:PAJERO69XZA0120018
shell工具:https://www.xshell.com/zh/xshell/
1.方控自测(前提条件:Android与MCU必须建链成功)
[1].实物测试
	接线: 将方控板KEY线与尾线SWC_L相接,另外一根线接GND
	测试: 每次按下方控,会将对应key值与状态发给android端,详细MCU打印日志如下:

	I(0000025683) KEYMGR,keymgr_key_trigger_handle:key_type[04] state[TRIGGER]
	[ARM_CORE,write data:] ff,aa,00,0b,08,06,02,01,04,01,de
	I(0000026043) KEYMGR,keymgr_key_trigger_handle:key_type[04] state[IDLE]
	[ARM_CORE,write data:] ff,aa,00,0b,09,06,02,01,04,00,de

	I(0000115553) KEYMGR,keymgr_key_trigger_handle:key_type[0E] state[TRIGGER]
	[ARM_CORE,write data:] ff,aa,00,0b,0c,06,02,01,0e,01,d0
	I(0000116233) KEYMGR,keymgr_key_trigger_handle:key_type[0E] state[IDLE]
	[ARM_CORE,write data:] ff,aa,00,0b,0d,06,02,01,0e,00,d0

	I(0000178883) KEYMGR,keymgr_key_trigger_handle:key_type[0F] state[TRIGGER]
	[ARM_CORE,write data:] ff,aa,00,0b,0e,06,02,01,0f,01,cd
	I(0000179033) KEYMGR,keymgr_key_trigger_handle:key_type[0F] state[IDLE]
	[ARM_CORE,write data:] ff,aa,00,0b,0f,06,02,01,0f,00,cd

	I(0000223153) KEYMGR,keymgr_key_trigger_handle:key_type[09] state[TRIGGER]
	[ARM_CORE,write data:] ff,aa,00,0b,10,06,02,01,09,01,d1
	I(0000223493) KEYMGR,keymgr_key_trigger_handle:key_type[09] state[IDLE]
	[ARM_CORE,write data:] ff,aa,00,0b,11,06,02,01,09,00,d1

	I(0000266683) KEYMGR,keymgr_key_trigger_handle:key_type[08] state[TRIGGER]
	[ARM_CORE,write data:] ff,aa,00,0b,12,06,02,01,08,01,d0
	I(0000266973) KEYMGR,keymgr_key_trigger_handle:key_type[08] state[IDLE]
	[ARM_CORE,write data:] ff,aa,00,0b,13,06,02,01,08,00,d0

	I(0000302853) KEYMGR,keymgr_key_trigger_handle:key_type[03] state[TRIGGER]
	[ARM_CORE,write data:] ff,aa,00,0b,14,06,02,01,03,01,d3
	I(0000303023) KEYMGR,keymgr_key_trigger_handle:key_type[03] state[IDLE]
	[ARM_CORE,write data:] ff,aa,00,0b,15,06,02,01,03,00,d3

	I(0000332953) KEYMGR,keymgr_key_trigger_handle:key_type[18] state[TRIGGER]
	[ARM_CORE,write data:] ff,aa,00,0b,16,06,02,01,18,01,bc
	I(0000333113) KEYMGR,keymgr_key_trigger_handle:key_type[18] state[IDLE]
	[ARM_CORE,write data:] ff,aa,00,0b,17,06,02,01,18,00,bc

	I(0000357333) KEYMGR,keymgr_key_trigger_handle:key_type[19] state[TRIGGER]
	[ARM_CORE,write data:] ff,aa,00,0b,18,06,02,01,19,01,b9
	I(0000357473) KEYMGR,keymgr_key_trigger_handle:key_type[19] state[IDLE]
	[ARM_CORE,write data:] ff,aa,00,0b,19,06,02,01,19,00,b9

	I(0000380363) KEYMGR,keymgr_key_trigger_handle:key_type[25] state[TRIGGER]
	[ARM_CORE,write data:] ff,aa,00,0b,1a,06,02,01,25,01,ab
	I(0000380523) KEYMGR,keymgr_key_trigger_handle:key_type[25] state[IDLE]
	[ARM_CORE,write data:] ff,aa,00,0b,1b,06,02,01,25,00,ab

[2]. 模拟测试:
	接上MCU打印，shell命令行输入 KeyTest + 测试项 + 按键值 + 按键状态
	如下是模拟按键挂电话按下和弹起的过程:
	KeyTest命令识别字符串,1表示第1个测试项,0x19表示按键值KEY_REJECT,0x01表示按键按下/0x00表示按键弹起

	xiexw:/$ KeyTest 1 0x19 0x01
	I(0000490647) KEYMGR,KeyTest:KeyTest mode:1 key:0x19 value:0x01
	[ARM_CORE,write data:] ff,aa,00,0b,1c,06,02,01,19,01,b5

	xiexw:/$ KeyTest 1 0x19 0x00
	I(0000535107) KEYMGR,KeyTest:KeyTest mode:1 key:0x19 value:0x00
	[ARM_CORE,write data:] ff,aa,00,0b,1d,06,02,01,19,00,b5

[3]. 帕杰罗方控按键值如下:
typedef enum
{
    EN_KEYMGR_SWC_TYPE_IDLE             = 0x00,//空闲
    EN_KEYMGR_SWC_TYPE_VOLUME_DOWN      = 0x08,//音量减
    EN_KEYMGR_SWC_TYPE_VOLUME_UP        = 0x09,//音量加
    EN_KEYMGR_SWC_TYPE_NEXT             = 0x0F,//下一曲
    EN_KEYMGR_SWC_TYPE_PREV             = 0x0E,//上一曲
    EN_KEYMGR_SWC_TYPE_MUSIC            = 0x04,//音乐
    EN_KEYMGR_SWC_TYPE_SPEECH           = 0x03,//语音
    EN_KEYMGR_SWC_TYPE_ANSWER           = 0x18,//接听
    EN_KEYMGR_SWC_TYPE_REJECT           = 0x19,//挂断
    EN_KEYMGR_SWC_TYPE_CAMERA           = 0x25,//摄像头
} EN_KEYMGR_SWC_TYPE;

[4]. KEY_A2M_KEY暂时不用实现

2. 车速上报模拟测试:
	接上MCU打印，shell命令行输入 PublicDebug + 23 + 车速 + 百分比,如下是MCU端打印:
	xiexw:/$ PublicDebug 23 60 100
	I(0001000191) PUBLIC_MGR,PublicDebug:Set car speed: 60, ratio: 100
	Return: 0, 0x00000000


Bash

List existing docs in the target directory


MCU-ARM.xlsx
MCU_ARM_接口文档清单与明细_20260513.md
MCU升级-Update.xlsx
Public.xlsx
Radio.xlsx


Now I have the full picture. Let me create the test document. I need to include:

1. Shell tool info (Xshell)
2. Hardware test setup (wiring)
3. MCU-side log output for each key press
4. Minicom/shell commands for simulated testing (KeyTest command)
5. Pajero key value enum
6. Android-side M2A log output
7. The M2A key receive results we captured

Let me also parse the MCU serial data frames to understand the protocol format.

Looking at the MCU log output:
```
[ARM_CORE,write data:] ff,aa,00,0b,08,06,02,01,04,01,de
```

This is the serial frame. Let me decode it:
- ff,aa - header
- 00,0b - length (0x000B = 11 bytes)
- 08 - seq
- 06 - frame type
- 02 - msgType (KEY)
- 01 - cmd (M2A_KEY)
- 04 - keyValue (body[0])
- 01 - keyStatus (body[1])
- de - checksum

Wait, let me count: ff,aa,00,0b,08,06,02,01,04,01,de = 11 bytes
- ff,aa = header (2 bytes)
- 00,0b = len (2 bytes, big endian, 0x000B = 11)
- 08 = seq (1 byte)
- 06 = frameType (1 byte)
- 02 = msgType KEY (1 byte)
- 01 = cmd M2A_KEY (1 byte)
- 04 = keyValue body[0] (1 byte)
- 01 = keyStatus body[1] (1 byte)
- de = checksum (1 byte)

Total: 2+2+1+1+1+1+1+1+1 = 11 bytes ✓

The body is 2 bytes: [keyValue, keyStatus]

Now let me write the comprehensive test document.


Write

# 方控按键 M2A 通信测试报告

## 基本信息

| 项目 | 内容 |
|------|------|
| 日期 | 2026-05-29 |
| 项目 | QSJNIC 车载 Android 中间件 |
| 版本 | v1.0.0-beta6 |
| MCU 固件 | swVersion=0302_20260527 |
| MCU 硬件 | hwVersion=PAJERO69XZA0120018 |
| 测试设备 | 车机 192.168.0.66 |
| 协议文档 | `docs/MCU_ARM_Reference/Key.xlsx` → "其它项目" 映射表 |
| MCU 方控自测文档 | `docs/MCU_ARM_Reference/1.方控和车速自测.txt` |
| 测试工具 | Xshell (https://www.xshell.com/zh/xshell/) |

---

## 一、测试前提

Android 中间件服务 (`com.qsjnic.mcu.service`) 必须与 MCU 建链成功。

---

## 二、实物测试（方控板接线）

### 2.1 接线方式

- 方控板 KEY 线 → 车机尾线 SWC_L
- 方控板 GND 线  → 车机尾线 GND

### 2.2 MCU 端日志

每次按下方控按键，MCU 串口打印如下格式日志：

```
I(0000025683) KEYMGR,keymgr_key_trigger_handle:key_type[04] state[TRIGGER]
[ARM_CORE,write data:] ff,aa,00,0b,08,06,02,01,04,01,de
I(0000026043) KEYMGR,keymgr_key_trigger_handle:key_type[04] state[IDLE]
[ARM_CORE,write data:] ff,aa,00,0b,09,06,02,01,04,00,de
```

### 2.3 串口帧结构解析

以 `ff,aa,00,0b,08,06,02,01,04,01,de` 为例：

| 字节 | 值 | 含义 |
|------|-----|------|
| H0-H1 | ff aa | 帧头 |
| L0-L1 | 00 0b | 帧长度 = 11 字节 |
| Seq | 08 | 序列号 |
| Type | 06 | 帧类型 |
| MsgType | **02** | KEY 域 |
| Cmd | **01** | M2A_KEY (MCU→ARM) |
| P0 | **04** | KeyValue (body[0]) |
| P1 | **01** | KeyStatus (body[1]) |
| CS | de | 校验和 |

### 2.4 Android 端日志

MCU 帧到达 ARM 后，FrameRouter 打印：

```
I/FrameRouter: KEY value=0x04 status=0x01
```

---

## 三、模拟测试（Shell/串口命令）

在不接实物方控板时，可通过 MCU 串口 Shell 直接注入按键帧。

### 3.1 命令格式

```
KeyTest <测试项> <按键值> <按键状态>
```

| 参数 | 说明 | 示例 |
|------|------|------|
| 测试项 | 固定为 `1`（M2A 按键测试） | 1 |
| 按键值 | 帕杰罗 KeyValue（0x00-0x25） | 0x19 |
| 按键状态 | 0x01=按下(TRIGGER), 0x00=弹起(IDLE) | 0x01 |

### 3.2 示例

挂断键按下和弹起：

```
xiexw:/$ KeyTest 1 0x19 0x01
I(0000490647) KEYMGR,KeyTest:KeyTest mode:1 key:0x19 value:0x01
[ARM_CORE,write data:] ff,aa,00,0b,1c,06,02,01,19,01,b5

xiexw:/$ KeyTest 1 0x19 0x00
I(0000535107) KEYMGR,KeyTest:KeyTest mode:1 key:0x19 value:0x00
[ARM_CORE,write data:] ff,aa,00,0b,1d,06,02,01,19,00,b5
```

### 3.3 全部 9 个帕杰罗按键模拟命令

```
# 语音
KeyTest 1 0x03 0x01
KeyTest 1 0x03 0x00

# 音乐
KeyTest 1 0x04 0x01
KeyTest 1 0x04 0x00

# 音量-
KeyTest 1 0x08 0x01
KeyTest 1 0x08 0x00

# 音量+
KeyTest 1 0x09 0x01
KeyTest 1 0x09 0x00

# 上一曲
KeyTest 1 0x0E 0x01
KeyTest 1 0x0E 0x00

# 下一曲
KeyTest 1 0x0F 0x01
KeyTest 1 0x0F 0x00

# 接听
KeyTest 1 0x18 0x01
KeyTest 1 0x18 0x00

# 挂断
KeyTest 1 0x19 0x01
KeyTest 1 0x19 0x00

# 摄像头
KeyTest 1 0x25 0x01
KeyTest 1 0x25 0x00
```

---

## 四、帕杰罗方控按键值定义

MCU 固件侧枚举（`docs/MCU_ARM_Reference/1.方控和车速自测.txt`）：

```c
typedef enum
{
    EN_KEYMGR_SWC_TYPE_IDLE             = 0x00,//空闲
    EN_KEYMGR_SWC_TYPE_VOLUME_DOWN      = 0x08,//音量减
    EN_KEYMGR_SWC_TYPE_VOLUME_UP        = 0x09,//音量加
    EN_KEYMGR_SWC_TYPE_NEXT             = 0x0F,//下一曲
    EN_KEYMGR_SWC_TYPE_PREV             = 0x0E,//上一曲
    EN_KEYMGR_SWC_TYPE_MUSIC            = 0x04,//音乐
    EN_KEYMGR_SWC_TYPE_SPEECH           = 0x03,//语音
    EN_KEYMGR_SWC_TYPE_ANSWER           = 0x18,//接听
    EN_KEYMGR_SWC_TYPE_REJECT           = 0x19,//挂断
    EN_KEYMGR_SWC_TYPE_CAMERA           = 0x25,//摄像头
} EN_KEYMGR_SWC_TYPE;
```

对应 Key.xlsx "其它项目" 映射表（行 44-73）。

---

## 五、Android 端 M2A 实测结果

所有 9 种按键均通过物理方控板触发，Android 端 FrameRouter 完整捕获。

| 按键值 | 帕杰罗功能 | MC3范围内? | 接收次数 | 状态 |
|--------|-----------|-----------|---------|------|
| 0x03 | KEY_VOICE 语音 | ✓ | 2 | 正常 |
| 0x04 | KEY_MUSIC 音乐 | ✓ | 3 | 正常 |
| 0x08 | KEY_VOL_DEC 音量- | ✓ | 2 | 正常 |
| 0x09 | KEY_VOL_INC 音量+ | ✓ | 2 | 正常 |
| 0x0E | KEY_PREV 上一曲 | ✗ 扩展值 | 3 | 正常 |
| 0x0F | KEY_NEXT 下一曲 | ✗ 扩展值 | 2 | 正常 |
| 0x18 | KEY_ANSWER 接听 | ✗ 扩展值 | 1 | 正常 |
| 0x19 | KEY_REJECT 挂断 | ✗ 扩展值 | 1 | 正常 |
| 0x25 | KEY_CAMERA 摄像头 | ✗ 扩展值 | 1 | 正常 |

### Android 端日志示例

```
I/FrameRouter: KEY value=0x04 status=0x01    ← 音乐键按下
I/FrameRouter: KEY value=0x04 status=0x00    ← 音乐键弹起
I/FrameRouter: KEY value=0x0E status=0x01    ← 上一曲按下
I/FrameRouter: KEY value=0x0E status=0x00    ← 上一曲弹起
I/FrameRouter: KEY value=0x0F status=0x01    ← 下一曲按下
I/FrameRouter: KEY value=0x0F status=0x00    ← 下一曲弹起
I/FrameRouter: KEY value=0x09 status=0x01    ← 音量+按下
I/FrameRouter: KEY value=0x08 status=0x01    ← 音量-按下
I/FrameRouter: KEY value=0x03 status=0x01    ← 语音键按下
I/FrameRouter: KEY value=0x18 status=0x01    ← 接听键按下
I/FrameRouter: KEY value=0x19 status=0x01    ← 挂断键按下
I/FrameRouter: KEY value=0x25 status=0x01    ← 摄像头键按下
```

---

## 六、Android 端 M2A 接收链路确认

```
MCU 串口 TX
    ↓
C 协议层 (mcu_protocol.c) — 帧解码
    ↓
JNI (jni_bridge.cpp) — body 字节提取
    ↓
FrameRouter.dispatch() — msgType=0x02 路由
    ↓
FrameRouter.handleKey() — cmd=0x01, 无 keyValue 范围校验
    ↓
KeyFeature.onFrame() — 透传至 Decoders
    ↓
Decoders.parseKeyControl() — 仅校验 body.length >= 2
    ↓
broadcastKeyEvent(keyValue, keyStatus) — 广播至所有监听者
    ↓
Vehicle Demo App (IMcuKeyListener.onKeyEvent)
```

**全链路无任何 keyValue 范围过滤。MCU 上报的任何值（0x00-0x25）均可完整到达应用层。**

---

## 七、结论

1. **M2A 方向**：9 种帕杰罗方控按键全部正常接收，含 5 个 MC3 范围外扩展值（0x0E/0x0F/0x18/0x19/0x25），按下和弹起事件完整
2. **串口协议**：帧结构（ff aa + len + seq + type + msgType=02 + cmd=01 + keyValue + keyStatus + cs）一致
3. **已知问题**：SDK 常量使用 MC3 映射表，与帕杰罗映射值语义不同，需后续对齐
4. **A2M 方向**：自测文档明确 "KEY_A2M_KEY 暂时不用实现"，当前无业务影响

---

## 八、参考文档

| 文档 | 路径 |
|------|------|
| Key 协议 (MC3 + 其它项目映射) | `docs/MCU_ARM_Reference/Key.xlsx` |
| 方控和车速自测 (MCU 侧) | `docs/MCU_ARM_Reference/1.方控和车速自测.txt` |
| MCU-ARM 接口清单 | `docs/MCU_ARM_Reference/20260513/MCU_ARM_接口文档清单与明细_20260513.md` |
| 全链路测试报告 | `imgs/full_test_report.md` |
| SDK Key 常量 | `sdk/src/main/java/com/qsjnic/mcu/sdk/McuKeyClient.java` |
| Key 编码校验 | `contract/src/main/java/com/qsjnic/mcu/domain/Commands.java` |

---

Android 中间件测试组
2026-05-29


File created successfully at: /Users/admin/workspace/jyq/qsjnic/docs/MCU_ARM_Reference/20260513/方控按键测试报告_20260529.md (file state is current in your context — no need to Read it back)

# 方控按键 M2A 通信测试报告

## 基本信息

| 项目 | 内容 |
|------|------|
| 日期 | 2026-05-29 |
| 项目 | QSJNIC 车载 Android 中间件 |
| 版本 | v1.0.0-beta6 |
| MCU 固件 | swVersion=0302_20260527 |
| MCU 硬件 | hwVersion=PAJERO69XZA0120018 |
| 测试设备 | 车机 192.168.0.66 |
| 协议文档 | `docs/MCU_ARM_Reference/Key.xlsx` → "其它项目" 映射表 |
| MCU 方控自测文档 | `docs/MCU_ARM_Reference/1.方控和车速自测.txt` |
| 测试工具 | Xshell (https://www.xshell.com/zh/xshell/) |

---

## 一、测试前提

Android 中间件服务 (`com.qsjnic.mcu.service`) 必须与 MCU 建链成功。

---

## 二、实物测试（方控板接线）

### 2.1 接线方式

- 方控板 KEY 线 → 车机尾线 SWC_L
- 方控板 GND 线  → 车机尾线 GND

### 2.2 MCU 端日志

每次按下方控按键，MCU 串口打印如下格式日志：

```
I(0000025683) KEYMGR,keymgr_key_trigger_handle:key_type[04] state[TRIGGER]
[ARM_CORE,write data:] ff,aa,00,0b,08,06,02,01,04,01,de
I(0000026043) KEYMGR,keymgr_key_trigger_handle:key_type[04] state[IDLE]
[ARM_CORE,write data:] ff,aa,00,0b,09,06,02,01,04,00,de
```

### 2.3 串口帧结构解析

以 `ff,aa,00,0b,08,06,02,01,04,01,de` 为例：

| 字节 | 值 | 含义 |
|------|-----|------|
| H0-H1 | ff aa | 帧头 |
| L0-L1 | 00 0b | 帧长度 = 11 字节 |
| Seq | 08 | 序列号 |
| Type | 06 | 帧类型 |
| MsgType | **02** | KEY 域 |
| Cmd | **01** | M2A_KEY (MCU→ARM) |
| P0 | **04** | KeyValue (body[0]) |
| P1 | **01** | KeyStatus (body[1]) |
| CS | de | 校验和 |

### 2.4 Android 端日志

MCU 帧到达 ARM 后，FrameRouter 打印：

```
I/FrameRouter: KEY value=0x04 status=0x01
```

---

## 三、模拟测试（Shell/串口命令）

在不接实物方控板时，可通过 MCU 串口 Shell 直接注入按键帧。

### 3.1 命令格式

```
KeyTest <测试项> <按键值> <按键状态>
```

| 参数 | 说明 | 示例 |
|------|------|------|
| 测试项 | 固定为 `1`（M2A 按键测试） | 1 |
| 按键值 | 帕杰罗 KeyValue（0x00-0x25） | 0x19 |
| 按键状态 | 0x01=按下(TRIGGER), 0x00=弹起(IDLE) | 0x01 |

### 3.2 示例

挂断键按下和弹起：

```
xiexw:/$ KeyTest 1 0x19 0x01
I(0000490647) KEYMGR,KeyTest:KeyTest mode:1 key:0x19 value:0x01
[ARM_CORE,write data:] ff,aa,00,0b,1c,06,02,01,19,01,b5

xiexw:/$ KeyTest 1 0x19 0x00
I(0000535107) KEYMGR,KeyTest:KeyTest mode:1 key:0x19 value:0x00
[ARM_CORE,write data:] ff,aa,00,0b,1d,06,02,01,19,00,b5
```

### 3.3 全部 9 个帕杰罗按键模拟命令

```
# 语音
KeyTest 1 0x03 0x01
KeyTest 1 0x03 0x00

# 音乐
KeyTest 1 0x04 0x01
KeyTest 1 0x04 0x00

# 音量-
KeyTest 1 0x08 0x01
KeyTest 1 0x08 0x00

# 音量+
KeyTest 1 0x09 0x01
KeyTest 1 0x09 0x00

# 上一曲
KeyTest 1 0x0E 0x01
KeyTest 1 0x0E 0x00

# 下一曲
KeyTest 1 0x0F 0x01
KeyTest 1 0x0F 0x00

# 接听
KeyTest 1 0x18 0x01
KeyTest 1 0x18 0x00

# 挂断
KeyTest 1 0x19 0x01
KeyTest 1 0x19 0x00

# 摄像头
KeyTest 1 0x25 0x01
KeyTest 1 0x25 0x00
```

---

## 四、帕杰罗方控按键值定义

MCU 固件侧枚举（`docs/MCU_ARM_Reference/1.方控和车速自测.txt`）：

```c
typedef enum
{
    EN_KEYMGR_SWC_TYPE_IDLE             = 0x00,//空闲
    EN_KEYMGR_SWC_TYPE_VOLUME_DOWN      = 0x08,//音量减
    EN_KEYMGR_SWC_TYPE_VOLUME_UP        = 0x09,//音量加
    EN_KEYMGR_SWC_TYPE_NEXT             = 0x0F,//下一曲
    EN_KEYMGR_SWC_TYPE_PREV             = 0x0E,//上一曲
    EN_KEYMGR_SWC_TYPE_MUSIC            = 0x04,//音乐
    EN_KEYMGR_SWC_TYPE_SPEECH           = 0x03,//语音
    EN_KEYMGR_SWC_TYPE_ANSWER           = 0x18,//接听
    EN_KEYMGR_SWC_TYPE_REJECT           = 0x19,//挂断
    EN_KEYMGR_SWC_TYPE_CAMERA           = 0x25,//摄像头
} EN_KEYMGR_SWC_TYPE;
```

对应 Key.xlsx "其它项目" 映射表（行 44-73）。

---

## 五、Android 端 M2A 实测结果

所有 9 种按键均通过物理方控板触发，Android 端 FrameRouter 完整捕获。

| 按键值 | 帕杰罗功能 | MC3范围内? | 接收次数 | 状态 |
|--------|-----------|-----------|---------|------|
| 0x03 | KEY_VOICE 语音 | ✓ | 2 | 正常 |
| 0x04 | KEY_MUSIC 音乐 | ✓ | 3 | 正常 |
| 0x08 | KEY_VOL_DEC 音量- | ✓ | 2 | 正常 |
| 0x09 | KEY_VOL_INC 音量+ | ✓ | 2 | 正常 |
| 0x0E | KEY_PREV 上一曲 | ✗ 扩展值 | 3 | 正常 |
| 0x0F | KEY_NEXT 下一曲 | ✗ 扩展值 | 2 | 正常 |
| 0x18 | KEY_ANSWER 接听 | ✗ 扩展值 | 1 | 正常 |
| 0x19 | KEY_REJECT 挂断 | ✗ 扩展值 | 1 | 正常 |
| 0x25 | KEY_CAMERA 摄像头 | ✗ 扩展值 | 1 | 正常 |

### Android 端日志示例

```
I/FrameRouter: KEY value=0x04 status=0x01    ← 音乐键按下
I/FrameRouter: KEY value=0x04 status=0x00    ← 音乐键弹起
I/FrameRouter: KEY value=0x0E status=0x01    ← 上一曲按下
I/FrameRouter: KEY value=0x0E status=0x00    ← 上一曲弹起
I/FrameRouter: KEY value=0x0F status=0x01    ← 下一曲按下
I/FrameRouter: KEY value=0x0F status=0x00    ← 下一曲弹起
I/FrameRouter: KEY value=0x09 status=0x01    ← 音量+按下
I/FrameRouter: KEY value=0x08 status=0x01    ← 音量-按下
I/FrameRouter: KEY value=0x03 status=0x01    ← 语音键按下
I/FrameRouter: KEY value=0x18 status=0x01    ← 接听键按下
I/FrameRouter: KEY value=0x19 status=0x01    ← 挂断键按下
I/FrameRouter: KEY value=0x25 status=0x01    ← 摄像头键按下
```

---

## 六、Android 端 M2A 接收链路确认

```
MCU 串口 TX
    ↓
C 协议层 (mcu_protocol.c) — 帧解码
    ↓
JNI (jni_bridge.cpp) — body 字节提取
    ↓
FrameRouter.dispatch() — msgType=0x02 路由
    ↓
FrameRouter.handleKey() — cmd=0x01, 无 keyValue 范围校验
    ↓
KeyFeature.onFrame() — 透传至 Decoders
    ↓
Decoders.parseKeyControl() — 仅校验 body.length >= 2
    ↓
broadcastKeyEvent(keyValue, keyStatus) — 广播至所有监听者
    ↓
Vehicle Demo App (IMcuKeyListener.onKeyEvent)
```

**全链路无任何 keyValue 范围过滤。MCU 上报的任何值（0x00-0x25）均可完整到达应用层。**

---

## 七、结论

1. **M2A 方向**：9 种帕杰罗方控按键全部正常接收，含 5 个 MC3 范围外扩展值（0x0E/0x0F/0x18/0x19/0x25），按下和弹起事件完整
2. **串口协议**：帧结构（ff aa + len + seq + type + msgType=02 + cmd=01 + keyValue + keyStatus + cs）一致
3. **已知问题**：SDK 常量使用 MC3 映射表，与帕杰罗映射值语义不同，需后续对齐
4. **A2M 方向**：自测文档明确 "KEY_A2M_KEY 暂时不用实现"，当前无业务影响

---

## 八、参考文档

| 文档 | 路径 |
|------|------|
| Key 协议 (MC3 + 其它项目映射) | `docs/MCU_ARM_Reference/Key.xlsx` |
| 方控和车速自测 (MCU 侧) | `docs/MCU_ARM_Reference/1.方控和车速自测.txt` |
| MCU-ARM 接口清单 | `docs/MCU_ARM_Reference/20260513/MCU_ARM_接口文档清单与明细_20260513.md` |
| 全链路测试报告 | `imgs/full_test_report.md` |
| SDK Key 常量 | `sdk/src/main/java/com/qsjnic/mcu/sdk/McuKeyClient.java` |
| Key 编码校验 | `contract/src/main/java/com/qsjnic/mcu/domain/Commands.java` |

---

Android 中间件测试组
2026-05-29


The document has been written. Let me verify it exists and give the user a summary.


已写入 `docs/MCU_ARM_Reference/20260513/方控按键测试报告_20260529.md`，包含：

- 实物接线方式
- MCU 串口帧结构逐字节解析
- Shell 模拟命令（全部 9 种按键的 KeyTest 命令）
- MCU 固件侧枚举定义
- Android 端 M2A 实测结果汇总表
- 完整 M2A 接收链路
- 参考文档索引


为什么我在android上按键，mcu调试信息msgtype未定义


The user is saying that when they press buttons on Android (A2M direction, ARM→MCU), the MCU shows "msgtype undefined" in its debug output. This is about the A2M key sending direction.

Let me think about this. The A2M KEY frame uses:
- MsgType = 0x02 (KEY)
- Cmd = 0x81 (A2M_KEY)

But wait - the user mentioned earlier that the self-test doc says "KEY_A2M_KEY暂时不用实现". Let me check if the MCU firmware actually supports receiving A2M KEY frames, or if the MCU hasn't implemented handling for cmd=0x81.

The MCU might not recognize the KEY msgType (0x02) in the A2M direction. Let me check the protocol documentation and the MCU firmware spec.

Actually, let me also check if the MCU even supports msgType=0x02 in the A2M direction. The MCU's KEYMGR module might only handle M2A (MCU→ARM) direction and not A2M (ARM→MCU).

Let me look at the MCU-ARM protocol documentation and the Public/Key/Radio sheets to understand which msgTypes the MCU supports in which direction.

Actually, let me think about this from the MCU's perspective. The MCU firmware has specific handlers for incoming frames from ARM. If the MCU receives a frame with msgType=0x02 (KEY) and cmd=0x81, it needs to have a handler registered for that msgType+cmd combination. If the MCU firmware doesn't have a handler for KEY A2M frames, it would print "msgtype undefined" or similar.

Let me check the protocol docs and also look at the MCU update document for any information about supported msgTypes.


这是因为 MCU 固件可能没有注册 KEY 域的 A2M 方向处理。让我确认下协议文档中 MCU 侧支持的方向。


Bash

python3 -c "
import openpyxl
wb = openpyxl.load_workbook('/Users/admin/workspace/jyq/qsjnic/docs/MCU_ARM_Reference/20260513/MCU-ARM.xlsx', data_only=True)
print('Sheets:', wb.sheetnames)
for name in wb.sheetnames:
    ws = wb[name]
    print(f'\n=== {name} ===')
    for row in ws.iter_rows(min_row=1, max_row=min(ws.max_row, 60), values_only=True):
        vals = [str(v) if v is not None else '' for v in row]
        line = ' | '.join(vals)
        if 'KEY' in line.upper() or 'key' in line.lower() or 'Key' in line or '02' in line:
            print(line)
" 2>&1

Check MCU-ARM protocol doc for KEY A2M support


Sheets: ['版本记录', '格式定义', 'MsgType定义', 'public', 'radio', 'can', 'update']

=== 版本记录 ===
 | V1.0 | 2024-03-21 00:00:00 | 初稿 | 陈明

=== 格式定义 ===
1) MCU与ARM必须成功建立链接后方可通讯
2) 建立链接后，ARM需必须每隔一段时间给MCU发心跳，否则超时会关闭ARM电源
3) MCU收到ARM下传的完整数据包后，必须在200ms内发送带接收帧序列号的ACK给ARM，否则ARM必须再次发送该数据帧给MCU,如果
   ARM重试3次后仍然没有收到ACK，ARM丢弃该帧数据包
2) ARM收到MCU上传的完整数据包，规则同上
3) 升级时，ARM发给MCU的帧长度，最大为1024+7
4) Data区域第1字节为MsgType,表示该消息的类型，如按键类、收音类、公共类等
5) Data区域第2字节为Cmd，表示对应MsgType下的具体命令 |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |
ACK | 响应消息 | 0x02 |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |

=== MsgType定义 ===
0x02 | MSG_TYPE_KEY | 按键信息

=== public ===
0x02 | PUBLIC_M2A_MCU_VERSION | MCU版本信息 | MCU->ARM | 0xFF | 0xAA |  |  | 0xNN | 0xNN | 0x01 | 0x02 | … | 0xNN
0x06 | PUBLIC_M2A_KEY_PWR_STATUS | MCU发送按键开关机状态 | MCU->ARM | 0xFF | 0xAA | 0x00 | 0x09 | 0xNN | 0xNN | 0x01 | 0x06 | … | 0xNN
0x8D | PUBLIC_A2M_KEY_PWR_STATUS | ARM下发power按键开关机状态 | ARM->MCU | 0xFF | 0xAA | 0x00 | 0x09 | 0xNN | 0xNN | 0x01 | 0x8D | … | 0xNN

=== radio ===
0x02 | RADIO_M2A_PS_PTY | MCU将RDS相关的PS和PTY信息发送给ARM | MCU->ARM | 0xFF | 0xAA | 0x00 | 0x12 | 0xNN | 0xNN | 0x05 | 0x02 |  |

=== can ===
 | CAN_M2A_CAN_ALIVE | MCU将CAN信息发给ARM | MCU->ARM | 0xFF | 0xAA | 0x00 | 0x0A | 0xNN | 0xNN | 0x06 | 0x02 |  |

=== update ===
0x02 | UPDATE_M2A_UPDATE_LEN | MCU发送升级包长度给ARM | MCU->ARM | 0xFF | 0xAA | 0x00 | 0x0B | 0xNN | 0xNN | 0x07 | 0x02 | … | 0xNN


Now I can see the MsgType definition clearly:

The MCU-ARM.xlsx defines:
- `0x02` = `MSG_TYPE_KEY` (按键信息)

But the Key.xlsx only defines:
- `01h [Byte7]` M→A `KEY_M2A_KEY` (MCU→ARM)
- `81h [Byte7]` A→M `KEY_A2M_KEY` (ARM→MCU)

And the self-test doc says "KEY_A2M_KEY暂时不用实现".

So the MCU defines MsgType=0x02 as KEY, but only implements the M2A direction (cmd=0x01). The A2M direction (cmd=0x81) might not be implemented in the MCU firmware.

Let me check what happens on the Android side when it sends A2M KEY. From our earlier testing, the A2M key sends via Vehicle Demo were showing `seq=162, 163, ...` which means the MCU DID ACK the frames. So the MCU received them.

Wait, let me re-read the A2M log data:
```
I/McuBinder( 3233): sendKeyEvent key=0x08 status=0x01 seq=162
```

The seq value being returned means the Android side sent the frame and received an ACK from the MCU. But the MCU might ACK the frame at the transport level without actually processing the msgType at the application level.

The MCU's "msgtype undefined" error is likely from the MCU's application layer, not the transport layer. The transport layer successfully received and ACK'd the frame (hence seq=162), but when the MCU firmware tried to dispatch it, it found that KEY msgType (0x02) in the A2M direction (from ARM) has no registered handler.

Let me look more carefully at the MCU-ARM.xlsx. The MsgType definition says:
- `0x02 | MSG_TYPE_KEY | 按键信息`

This is defined as a type, but in the protocol sheets (public, radio, can, update), I don't see a "key" sheet. The sheets are: 版本记录, 格式定义, MsgType定义, public, radio, can, update.

There's NO "key" sheet in MCU-ARM.xlsx! The KEY msgType is defined in the MsgType table but there's no detailed protocol sheet for it.

So the KEY domain protocol is defined in Key.xlsx (a separate document), but the MCU firmware might not have implemented A2M KEY handling. The self-test doc explicitly says "KEY_A2M_KEY暂时不用实现".

This explains the user's observation: when Android sends A2M KEY frame (msgType=0x02, cmd=0x81), the MCU transport layer ACKs it (which is why Android gets a seq back), but the MCU application layer doesn't have a handler for msgType=0x02 in the A2M direction, so it prints "msgtype undefined".

Also noteworthy: there's a `PUBLIC_M2A_KEY_PWR_STATUS` (cmd=0x06) and `PUBLIC_A2M_KEY_PWR_STATUS` (cmd=0x8D) in the Public domain. These handle power key status through the PUBLIC msgType, not through the KEY msgType. So power key uses Public, not Key domain.

The conclusion is that the MCU firmware `0302_20260527` has NOT implemented A2M KEY handling (cmd=0x81 under msgType=0x02). This is expected per the self-test doc.

Let me also check if there's a "key" sheet that might have been missed.


python3 -c "
import openpyxl
wb = openpyxl.load_workbook('/Users/admin/workspace/jyq/qsjnic/docs/MCU_ARM_Reference/20260513/MCU-ARM.xlsx', data_only=True)
ws = wb['MsgType定义']
for row in ws.iter_rows(min_row=1, max_row=ws.max_row, values_only=True):
    vals = [str(v) if v is not None else '' for v in row]
    print(' | '.join(vals))
" 2>&1

Check full MsgType definition table


MsgType | 宏定义 | 描述
0x01 | MSG_TYPE_PUBLIC | 全局信息
0x02 | MSG_TYPE_KEY | 按键信息
0x03 | MSG_TYPE_BEEP | 蜂鸣信息
0x04 | MSG_TYPE_SETUP | 设置信息
0x05 | MSG_TYPE_RADIO | 收音信息
0x06 | MSG_TYPE_CAN | CAN信息
0x07 | MSG_TYPE_UPDATE | 升级信息
 |  |
 |  |
 |  |
子命令Cmd命名规则 |  |
M2A表示: MCU发送给ARM的命令 |  |
A2M表示: ARM发送给MCU的命令 |  |


Now I can see the full MsgType table. KEY is 0x02 with the macro `MSG_TYPE_KEY`.

Now, looking at the MCU-ARM.xlsx sheets: 版本记录, 格式定义, MsgType定义, public, radio, can, update

There is NO "key" sheet! This means the MCU-ARM.xlsx (the main protocol document) doesn't define detailed KEY protocol commands. The KEY protocol is defined separately in `Key.xlsx`.

Also, notice that `Key.xlsx` only has two sheets: "版本记录" and "Key". And in the "Key" sheet, there are two directions:
- M2A (cmd=0x01): KEY_M2A_KEY
- A2M (cmd=0x81): KEY_A2M_KEY

But the MCU firmware self-test document says "KEY_A2M_KEY暂时不用实现".

So the answer to the user's question is clear:
1. MCU defines MsgType 0x02 as MSG_TYPE_KEY
2. MCU implements M2A direction (cmd=0x01) - this is what we tested successfully
3. MCU does NOT implement A2M direction (cmd=0x81) - as stated in the self-test doc
4. When Android sends A2M KEY frame, the MCU transport layer ACKs it (seq works), but the MCU application layer has no handler for msgType=0x02 in A2M direction, hence "msgtype undefined"

The "msgtype undefined" error is from MCU's application layer dispatch, not from the transport layer. The transport layer successfully receives and ACKs the frame, but when the MCU tries to find a handler for the KEY msgType in the A2M direction, nothing is registered.


原因很明确：

## MCU 固件未实现 A2M KEY 处理

### 证据

**1. MCU-ARM.xlsx 协议文档**只定义了 7 个 MsgType，但没有 KEY 域的详细命令页：

| MsgType | 宏 | 是否有协议页 |
|---------|-----|------------|
| 0x01 | PUBLIC | ✓ public 页 |
| **0x02** | **KEY** | **✗ 无** |
| 0x05 | RADIO | ✓ radio 页 |
| 0x07 | UPDATE | ✓ update 页 |

KEY 域的协议在单独的 `Key.xlsx` 中定义，但 MCU-ARM.xlsx 主文档没有收录 KEY 域的子命令。

**2. 自测文档明确写明**（`1.方控和车速自测.txt:88`）：
> KEY_A2M_KEY暂时不用实现

**3. 现象解释**：Android 发送 A2M KEY 帧（msgType=0x02, cmd=0x81），MCU 传输层正常收到并返回 ACK（所以 seq 有效），但 MCU 应用层没有注册 msgType=0x02 的处理函数，dispatch 时打印 "msgtype undefined"。

### 结论

这不是 Android 端的问题，是 MCU 固件 `0302_20260527` 没有实现 KEY 域的 A2M 方向处理。M2A 方向（MCU→ARM）完全正常，9 种物理按键全部能收到。


E(0001869067) ARMCORE,mcucomm_notify_recv_data:msgType[2] not register proc fn
E(0001872347) ARMCORE,mcucomm_notify_recv_data:msgType[2] not register proc fn


This confirms exactly what we deduced. The MCU firmware's error log is:

```
E(0001869067) ARMCORE,mcucomm_notify_recv_data:msgType[2] not register proc fn
```

This is the MCU-side error: `msgType[2]` = MSG_TYPE_KEY = 0x02, and the MCU's dispatch function `mcucomm_notify_recv_data` has no registered processing function for this msgType.

So when Android sends A2M KEY frame (msgType=0x02, cmd=0x81), the MCU:
1. Transport layer receives and ACKs the frame (which is why Android gets seq back)
2. Application layer dispatch (`mcucomm_notify_recv_data`) tries to find a handler for msgType=0x02
3. No handler registered → prints error: "msgType[2] not register proc fn"

This is a clear confirmation that the MCU firmware doesn't support receiving KEY frames from Android. The fix would need to come from the MCU firmware side - they need to register a processing function for MSG_TYPE_KEY in the A2M direction.


这就对上了。MCU 的日志直接证实了之前的分析：

```
ARMCORE,mcucomm_notify_recv_data:msgType[2] not register proc fn
```

- `msgType[2]` = **MSG_TYPE_KEY (0x02)**
- `not register proc fn` = **MCU 没有注册该 msgType 的处理函数**

### 完整链路

```
Android sendKeyEvent(key=0x08, status=0x01)
  ↓
串口发送: ff aa 00 0b XX 06 02 81 08 01 XX
                      ↑↑     ↑↑ ↑↑
                      msgType=02  cmd=81
  ↓
MCU 传输层: 收到帧，校验通过，回 ACK → Android 拿到 seq=162 ✓
  ↓
MCU 应用层 mcucomm_notify_recv_data:
  → 查找 msgType[2] 的处理函数
  → 未注册 → 打印 ERROR ✗
```

所以 Android 端看到的 `seq=162` 和 MCU 端报的 `not register proc fn` 完全不矛盾——**传输层成功了，应用层没处理**。这是 MCU 固件 `0302_20260527` 未实现 KEY 域 A2M 接收，需要 MCU 侧注册 `MSG_TYPE_KEY` 的 proc fn。


那如果这样的话是不是上午测试的radio 的valid一直是0，也可以通过串口助手来分析


The user is asking whether we can use a serial port tool (like minicom or a serial assistant) to analyze the Radio valid=0 issue, similar to how we analyzed the KEY issue. The idea is to capture raw serial data directly from the MCU, bypassing the Android layer entirely, to confirm that the valid/signal/PS/RT fields are genuinely zero in the MCU's serial output.

This is a good idea - if we can see the raw hex data from the MCU's serial TX, we can confirm definitively whether the MCU is sending zeros or if there's an issue in the Android receive path.

Let me tell the user how to do this:
1. Connect a USB-to-serial adapter to the MCU's TX/RX lines
2. Use a serial assistant (like minicom, picocom, or a GUI tool) to capture the raw serial output
3. Look for the FREQ/PS_PTY/RT frames and check the data bytes

The frame format for Radio M2A:
- msgType = 0x05 (RADIO)
- FREQ cmd = 0x01, body 7 bytes
- PS_PTY cmd = 0x02, body 9 bytes
- RT cmd = 0x03, body 64 bytes

We already have Android-side hex dumps from FrameRouter showing the raw body bytes are all zero. But the user wants to verify this at the serial level to be absolutely sure.

Let me give them the approach and frame format reference.


对，用串口助手直接抓 MCU TX 线的原始数据，绕过 Android 全链路，一锤定音。

### 方法

MCU 串口 TX 线接 USB 转串口模块，波特率等参数参考 MCU 固件配置。在串口助手中过滤 Radio 帧：

### Radio M2A 帧识别

MCU-ARM 协议帧头 `FF AA`，Radio 域 `msgType=0x05`：

| 帧类型 | cmd | body 长度 | 关键字节 |
|--------|-----|-----------|---------|
| FREQ | 0x01 | 7 | P4 bit7=valid, P6=signal |
| PS_PTY | 0x02 | 9 | P0=PTY, P1-P8=PS |
| RT | 0x03 | 64 | P0-P63=RT text |

### 你需要关注的

搜到台后，看 FREQ 帧的 body 第 5 字节（P4）bit7 是否为 1，以及第 7 字节（P6 signal）是否非零。PS_PTY 和 RT 帧同理——搜到强台后这 9+64 字节不应该全是 0x00。

如果串口抓出来也是全零，那就是 MCU 固件确实没填数据，和 Android 无关。需要我帮你把串口帧格式写成速查表吗？


你能通过adb帮我测一下吗


The user wants me to test via ADB whether the MCU is actually sending zero data for Radio frames. I can do this - we already have the FrameRouter logging that dumps raw body bytes directly from the serial/JNI layer. Let me check the current state and capture fresh Radio data.

But we've already done this multiple times and confirmed the data is all zeros. Let me think about what's different now...

Actually, the user might want a fresh test now after the restart they mentioned. Let me just do it - clear logcat, capture Radio frame data.

But wait - we modified the FrameRouter to change the KEY log format. Did we build and deploy that? No - the user told us not to modify code. Let me revert that change and just do a clean test.

Actually, looking back at the conversation, we modified FrameRouter.java to change the KEY log format but we only built the debug variant (com.qsjnic.mcu.service.dev) and didn't deploy the release variant. The running service is still the original release version (com.qsjnic.mcu.service, PID 3210).

But the Radio logging was already in place from the previous testing (the full_test_report.md mentions they added hex dump logging). So the logging should still work.

Let me just clear logcat and capture fresh Radio frame data.


Clear logcat


cleared


Good, logcat cleared. Now let me capture Radio frame data. Let me use a monitor to stream the data in real-time. But actually, since the user just wants to see the raw data, let me wait a few seconds and then capture whatever Radio frames have come in.

Actually, let me use a Monitor to stream the data so the user can see it in real-time. But first, let me check if the user is ready to interact with the radio.


Monitor

adb -s 192.168.0.66:5555 logcat -v time 2>&1 | grep --line-buffered -iE "FrameRouter.*FREQ_BODY|FrameRouter.*PS_PTY|FrameRouter.*RT\[|FrameRouter.*RADIO_FREQ"

Radio raw body monitor


Monitor started (task bg7yba0pt, timeout 120000ms). You will be notified on each event. Keep working — do not poll or sleep. Events may arrive while you are waiting for the user — an event is not their reply.


<task-notification>
<task-id>bg7yba0pt</task-id>
<summary>Monitor event: "Radio raw body monitor"</summary>
<event>05-26 18:38:55.454 I/FrameRouter( 3210): RADIO_FREQ FM 8750(*100KHz) signal=0
05-26 18:38:55.454 I/FrameRouter( 3210): FREQ_BODY[7]=22 2E 00 00 00 00 00 | Byte12=0x00 (valid=0) Byte14(signal)=0x00
05-26 18:38:55.454 I/FrameRouter( 3210): PS_PTY[9]=00 00 00 00 00 00 00 00 00 | PTY=0x00 PS="(empty)"
05-26 18:38:55.954 I/FrameRouter( 3210): RADIO_FREQ FM 8750(*100KHz) signal=0
05-26 18:38:55.954 I/FrameRouter( 3210): FREQ_BODY[7]=22 2E 00 00 00 00 00 | Byte12=0x00 (valid=0) Byte14(signal)=0x00
05-26 18:38:56.156 I/FrameRouter( 3210): RT[64]=00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | RT="(empty)"
05-26 18:38:56.454 I/FrameRouter( 3210): RADIO_FREQ FM 8750(*100KHz) signal=0
05-26 18:38:56.454 I/FrameRouter( 3210): FREQ_BODY[7]=22 2E 00 00 00 00 00 | Byte12=0x00 (valid=0) Byte14(signal)=0x00
05-26 18:38:56.454 I/FrameRouter( 3210): PS_PTY[9]=00 00 00 00 00 00 00 00 00 | PTY=0x00 PS="(empty)"
05-26 18:38:56.954 I/FrameRouter( 3210): RADIO_FREQ FM 8750(*100KHz) signal=0
05-26 18:38:56.954 I/FrameRouter( 3210): FREQ_BODY[7]=22 2E 00 00 00 00 00 | Byte12=0x00 (valid=0) Byte14(signal)=0x00
05-26 18:38:57.155 I/FrameRouter( 3210): RT[64]=00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | RT="(empty)"
05-26 18:38:57.454 I/FrameRouter( 3210): RADIO_FREQ FM 8750(*100KHz) signal=0
05-26 18:38:57.454 I/FrameRouter( 3210): FREQ_BODY[7]=22 2E 00 00 00 00 00 | Byte12=0x00 (valid=0) Byte14(signal)=0x00
05-26 18:38:57.454 I/FrameRouter( 3210): PS_PTY[9]=00 00 00 00 00 00 00 00 00 | PTY=0x00 PS="(empty)"
05-26 18:38:57.954 I/FrameRouter( 3210): RADIO_FREQ FM 8750(*100KHz) signal=0
05-26 18:38:57.954 I/FrameRouter( 3210): FREQ_BODY[7]=22 2E 00 00 00 00 00 | Byte12=0x00 (valid=0) Byte14(signal)=0x00
05-26 18:38:58.155 I/FrameRouter( 3210): RT[64]=00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | RT="(empty)"
05-26 18:38:58.454 I/FrameRouter( 3210): RADIO_FREQ FM 8750(*100KHz) signal=0
05-26 18:38:58.454 I/FrameRouter( 3210): FREQ_BODY[7]=22 2E 00 00 00 00 00 | Byte12=0x00 (valid=0) Byte14(signal)=0x00
05-26 18:38:58.455 I/FrameRouter( 3210): PS_PTY[9]=00 00 00 00 00 00 00 00 00 | PTY=0x00 PS="(empty)"
05-26 18:38:58.954 I/FrameRouter( 3210): RADIO_FREQ FM 8750(*100KHz) signal=0
05-26 18:38:58.954 I/FrameRouter( 3210): FREQ_BODY[7]=22 2E 00 00 00 00 00 | Byte12=0x00 (valid=0) Byte14(signal)=0x00
05-26 18:38:59.156 I/FrameRouter( 3210): RT[64]=00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | RT="(empt
...(truncated)</event>
</task-notification>


<task-notification>
<task-id>bg7yba0pt</task-id>
<summary>Monitor event: "Radio raw body monitor"</summary>
<event>05-26 18:39:02.454 I/FrameRouter( 3210): RADIO_FREQ FM 8750(*100KHz) signal=0
05-26 18:39:02.454 I/FrameRouter( 3210): FREQ_BODY[7]=22 2E 00 00 00 00 00 | Byte12=0x00 (valid=0) Byte14(signal)=0x00
05-26 18:39:02.455 I/FrameRouter( 3210): PS_PTY[9]=00 00 00 00 00 00 00 00 00 | PTY=0x00 PS="(empty)"</event>
</task-notification>


<task-notification>
<task-id>bg7yba0pt</task-id>
<summary>Monitor event: "Radio raw body monitor"</summary>
<event>05-26 18:39:02.954 I/FrameRouter( 3210): RADIO_FREQ FM 8750(*100KHz) signal=0
05-26 18:39:02.954 I/FrameRouter( 3210): FREQ_BODY[7]=22 2E 00 00 00 00 00 | Byte12=0x00 (valid=0) Byte14(signal)=0x00</event>
</task-notification>


<task-notification>
<task-id>bg7yba0pt</task-id>
<summary>Monitor event: "Radio raw body monitor"</summary>
<event>05-26 18:39:03.156 I/FrameRouter( 3210): RT[64]=00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | RT="(empty)"</event>
</task-notification>


<task-notification>
<task-id>bg7yba0pt</task-id>
<summary>Monitor event: "Radio raw body monitor"</summary>
<event>05-26 18:39:03.454 I/FrameRouter( 3210): RADIO_FREQ FM 8750(*100KHz) signal=0
05-26 18:39:03.454 I/FrameRouter( 3210): FREQ_BODY[7]=22 2E 00 00 00 00 00 | Byte12=0x00 (valid=0) Byte14(signal)=0x00
05-26 18:39:03.455 I/FrameRouter( 3210): PS_PTY[9]=00 00 00 00 00 00 00 00 00 | PTY=0x00 PS="(empty)"</event>
</task-notification>


<task-notification>
<task-id>bg7yba0pt</task-id>
<summary>Monitor event: "Radio raw body monitor"</summary>
<event>05-26 18:39:03.954 I/FrameRouter( 3210): RADIO_FREQ FM 8750(*100KHz) signal=0
05-26 18:39:03.954 I/FrameRouter( 3210): FREQ_BODY[7]=22 2E 00 00 00 00 00 | Byte12=0x00 (valid=0) Byte14(signal)=0x00</event>
</task-notification>


<task-notification>
<task-id>bg7yba0pt</task-id>
<summary>Monitor event: "Radio raw body monitor"</summary>
<event>05-26 18:39:04.211 I/FrameRouter( 3210): RT[64]=00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | RT="(empty)"
05-26 18:39:04.454 I/FrameRouter( 3210): RADIO_FREQ FM 8750(*100KHz) signal=0
05-26 18:39:04.454 I/FrameRouter( 3210): FREQ_BODY[7]=22 2E 00 00 00 00 00 | Byte12=0x00 (valid=0) Byte14(signal)=0x00
05-26 18:39:04.455 I/FrameRouter( 3210): PS_PTY[9]=00 00 00 00 00 00 00 00 00 | PTY=0x00 PS="(empty)"</event>
</task-notification>


Monitor started. Let me wait for data to come in. The monitor will show real-time Radio frame data from the FrameRouter, which reads directly from JNI (raw MCU data).


监控已开，你去操作收音机搜台，数据会实时显示。


<task-notification>
<task-id>bg7yba0pt</task-id>
<summary>Monitor event: "Radio raw body monitor"</summary>
<event>05-26 18:39:04.954 I/FrameRouter( 3210): RADIO_FREQ FM 8750(*100KHz) signal=0
05-26 18:39:04.954 I/FrameRouter( 3210): FREQ_BODY[7]=22 2E 00 00 00 00 00 | Byte12=0x00 (valid=0) Byte14(signal)=0x00
05-26 18:39:05.156 I/FrameRouter( 3210): RT[64]=00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | RT="(empty)"</event>
</task-notification>


<task-notification>
<task-id>bg7yba0pt</task-id>
<summary>Monitor event: "Radio raw body monitor"</summary>
<event>05-26 18:39:05.454 I/FrameRouter( 3210): RADIO_FREQ FM 8750(*100KHz) signal=0
05-26 18:39:05.454 I/FrameRouter( 3210): FREQ_BODY[7]=22 2E 00 00 00 00 00 | Byte12=0x00 (valid=0) Byte14(signal)=0x00
05-26 18:39:05.455 I/FrameRouter( 3210): PS_PTY[9]=00 00 00 00 00 00 00 00 00 | PTY=0x00 PS="(empty)"</event>
</task-notification>


<task-notification>
<task-id>bg7yba0pt</task-id>
<summary>Monitor event: "Radio raw body monitor"</summary>
<event>05-26 18:39:05.954 I/FrameRouter( 3210): RADIO_FREQ FM 8750(*100KHz) signal=0
05-26 18:39:05.954 I/FrameRouter( 3210): FREQ_BODY[7]=22 2E 00 00 00 00 00 | Byte12=0x00 (valid=0) Byte14(signal)=0x00</event>
</task-notification>


<task-notification>
<task-id>bg7yba0pt</task-id>
<summary>Monitor event: "Radio raw body monitor"</summary>
<event>05-26 18:39:06.156 I/FrameRouter( 3210): RT[64]=00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | RT="(empty)"</event>
</task-notification>


<task-notification>
<task-id>bg7yba0pt</task-id>
<summary>Monitor event: "Radio raw body monitor"</summary>
<event>05-26 18:39:06.454 I/FrameRouter( 3210): RADIO_FREQ FM 8750(*100KHz) signal=0
05-26 18:39:06.454 I/FrameRouter( 3210): FREQ_BODY[7]=22 2E 00 00 00 00 00 | Byte12=0x00 (valid=0) Byte14(signal)=0x00
05-26 18:39:06.455 I/FrameRouter( 3210): PS_PTY[9]=00 00 00 00 00 00 00 00 00 | PTY=0x00 PS="(empty)"</event>
</task-notification>


<task-notification>
<task-id>bg7yba0pt</task-id>
<summary>Monitor event: "Radio raw body monitor"</summary>
<event>[2 events suppressed — output rate too high. Consider using TaskStop to restart this monitor with a more selective filter.]</event>
</task-notification>


<task-notification>
<task-id>bg7yba0pt</task-id>
<summary>Monitor event: "Radio raw body monitor"</summary>
<event>05-26 18:39:07.954 I/FrameRouter( 3210): RADIO_FREQ FM 8750(*100KHz) signal=0
05-26 18:39:07.955 I/FrameRouter( 3210): FREQ_BODY[7]=22 2E 00 00 00 00 00 | Byte12=0x00 (valid=0) Byte14(signal)=0x00</event>
</task-notification>


<task-notification>
<task-id>bg7yba0pt</task-id>
<summary>Monitor event: "Radio raw body monitor"</summary>
<event>[4 events suppressed — output rate too high. Consider using TaskStop to restart this monitor with a more selective filter.]</event>
</task-notification>


<task-notification>
<task-id>bg7yba0pt</task-id>
<summary>Monitor event: "Radio raw body monitor"</summary>
<event>05-26 18:39:09.955 I/FrameRouter( 3210): RADIO_FREQ FM 8750(*100KHz) signal=0
05-26 18:39:09.955 I/FrameRouter( 3210): FREQ_BODY[7]=22 2E 00 00 00 00 00 | Byte12=0x00 (valid=0) Byte14(signal)=0x00
05-26 18:39:10.156 I/FrameRouter( 3210): RT[64]=00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | RT="(empty)"
05-26 18:39:10.454 I/FrameRouter( 3210): RADIO_FREQ FM 8750(*100KHz) signal=0
05-26 18:39:10.455 I/FrameRouter( 3210): FREQ_BODY[7]=22 2E 00 00 00 00 00 | Byte12=0x00 (valid=0) Byte14(signal)=0x00
05-26 18:39:10.455 I/FrameRouter( 3210): PS_PTY[9]=00 00 00 00 00 00 00 00 00 | PTY=0x00 PS="(empty)"</event>
</task-notification>


<task-notification>
<task-id>bg7yba0pt</task-id>
<summary>Monitor event: "Radio raw body monitor"</summary>
<event>[3 events suppressed — output rate too high. Consider using TaskStop to restart this monitor with a more selective filter.]</event>
</task-notification>


<task-notification>
<task-id>bg7yba0pt</task-id>
<summary>Monitor event: "Radio raw body monitor"</summary>
<event>05-26 18:39:11.955 I/FrameRouter( 3210): RADIO_FREQ FM 8750(*100KHz) signal=0
05-26 18:39:11.955 I/FrameRouter( 3210): FREQ_BODY[7]=22 2E 00 00 00 00 00 | Byte12=0x00 (valid=0) Byte14(signal)=0x00
05-26 18:39:12.156 I/FrameRouter( 3210): RT[64]=00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | RT="(empty)"</event>
</task-notification>


Data is streaming. Let me wait for the user to interact with the radio (seek, change station, etc.) to see if the data changes. The current data shows FM 87.50 MHz with all zeros for valid, signal, PS, PTY, and RT.

Let me stop the monitor and present the results. But actually, the user should press seek or change frequency to see if the data changes. Let me wait a bit more.


当前 FM 87.50MHz，三帧全零。现在去搜台试试，看搜到台后有没有变化。


Successfully stopped task: bg7yba0pt (adb -s 192.168.0.66:5555 logcat -v time 2>&1 | grep --line-buffered -iE "FrameRouter.*FREQ_BODY|FrameRouter.*PS_PTY|FrameRouter.*RT\[|FrameRouter.*RADIO_FREQ")


Prompt:
<task-notification>
<task-id>bg7yba0pt</task-id>
<summary>Monitor event: "Radio raw body monitor"</summary>
<event>05-26 18:39:04.954 I/FrameRouter( 3210): RADIO_FREQ FM 8750(*100KHz) signal=0
05-26 18:39:04.954 I/FrameRouter( 3210): FREQ_BODY[7]=22 2E 00 00 00 00 00 | Byte12=0x00 (valid=0) Byte14(signal)=0x00
05-26 18:39:05.156 I/FrameRouter( 3210): RT[64]=00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | RT="(empty)"</event>
</task-notification>


Prompt:
<task-notification>
<task-id>bg7yba0pt</task-id>
<summary>Monitor event: "Radio raw body monitor"</summary>
<event>05-26 18:39:05.454 I/FrameRouter( 3210): RADIO_FREQ FM 8750(*100KHz) signal=0
05-26 18:39:05.454 I/FrameRouter( 3210): FREQ_BODY[7]=22 2E 00 00 00 00 00 | Byte12=0x00 (valid=0) Byte14(signal)=0x00
05-26 18:39:05.455 I/FrameRouter( 3210): PS_PTY[9]=00 00 00 00 00 00 00 00 00 | PTY=0x00 PS="(empty)"</event>
</task-notification>


Prompt:
<task-notification>
<task-id>bg7yba0pt</task-id>
<summary>Monitor event: "Radio raw body monitor"</summary>
<event>05-26 18:39:05.954 I/FrameRouter( 3210): RADIO_FREQ FM 8750(*100KHz) signal=0
05-26 18:39:05.954 I/FrameRouter( 3210): FREQ_BODY[7]=22 2E 00 00 00 00 00 | Byte12=0x00 (valid=0) Byte14(signal)=0x00</event>
</task-notification>


Prompt:
<task-notification>
<task-id>bg7yba0pt</task-id>
<summary>Monitor event: "Radio raw body monitor"</summary>
<event>05-26 18:39:06.156 I/FrameRouter( 3210): RT[64]=00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | RT="(empty)"</event>
</task-notification>


Prompt:
<task-notification>
<task-id>bg7yba0pt</task-id>
<summary>Monitor event: "Radio raw body monitor"</summary>
<event>05-26 18:39:06.454 I/FrameRouter( 3210): RADIO_FREQ FM 8750(*100KHz) signal=0
05-26 18:39:06.454 I/FrameRouter( 3210): FREQ_BODY[7]=22 2E 00 00 00 00 00 | Byte12=0x00 (valid=0) Byte14(signal)=0x00
05-26 18:39:06.455 I/FrameRouter( 3210): PS_PTY[9]=00 00 00 00 00 00 00 00 00 | PTY=0x00 PS="(empty)"</event>
</task-notification>


Prompt:
<task-notification>
<task-id>bg7yba0pt</task-id>
<summary>Monitor event: "Radio raw body monitor"</summary>
<event>[2 events suppressed — output rate too high. Consider using TaskStop to restart this monitor with a more selective filter.]</event>
</task-notification>


Prompt:
<task-notification>
<task-id>bg7yba0pt</task-id>
<summary>Monitor event: "Radio raw body monitor"</summary>
<event>05-26 18:39:07.954 I/FrameRouter( 3210): RADIO_FREQ FM 8750(*100KHz) signal=0
05-26 18:39:07.955 I/FrameRouter( 3210): FREQ_BODY[7]=22 2E 00 00 00 00 00 | Byte12=0x00 (valid=0) Byte14(signal)=0x00</event>
</task-notification>


Prompt:
<task-notification>
<task-id>bg7yba0pt</task-id>
<summary>Monitor event: "Radio raw body monitor"</summary>
<event>[4 events suppressed — output rate too high. Consider using TaskStop to restart this monitor with a more selective filter.]</event>
</task-notification>


Prompt:
<task-notification>
<task-id>bg7yba0pt</task-id>
<summary>Monitor event: "Radio raw body monitor"</summary>
<event>05-26 18:39:09.955 I/FrameRouter( 3210): RADIO_FREQ FM 8750(*100KHz) signal=0
05-26 18:39:09.955 I/FrameRouter( 3210): FREQ_BODY[7]=22 2E 00 00 00 00 00 | Byte12=0x00 (valid=0) Byte14(signal)=0x00
05-26 18:39:10.156 I/FrameRouter( 3210): RT[64]=00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | RT="(empty)"
05-26 18:39:10.454 I/FrameRouter( 3210): RADIO_FREQ FM 8750(*100KHz) signal=0
05-26 18:39:10.455 I/FrameRouter( 3210): FREQ_BODY[7]=22 2E 00 00 00 00 00 | Byte12=0x00 (valid=0) Byte14(signal)=0x00
05-26 18:39:10.455 I/FrameRouter( 3210): PS_PTY[9]=00 00 00 00 00 00 00 00 00 | PTY=0x00 PS="(empty)"</event>
</task-notification>


Prompt:
<task-notification>
<task-id>bg7yba0pt</task-id>
<summary>Monitor event: "Radio raw body monitor"</summary>
<event>[3 events suppressed — output rate too high. Consider using TaskStop to restart this monitor with a more selective filter.]</event>
</task-notification>


Prompt:
<task-notification>
<task-id>bg7yba0pt</task-id>
<summary>Monitor event: "Radio raw body monitor"</summary>
<event>05-26 18:39:11.955 I/FrameRouter( 3210): RADIO_FREQ FM 8750(*100KHz) signal=0
05-26 18:39:11.955 I/FrameRouter( 3210): FREQ_BODY[7]=22 2E 00 00 00 00 00 | Byte12=0x00 (valid=0) Byte14(signal)=0x00
05-26 18:39:12.156 I/FrameRouter( 3210): RT[64]=00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | RT="(empty)"</event>
</task-notification>


你打开radio demo