From 0aedb45e6e4fe9d732f69fde18b3b895dca0bf2e Mon Sep 17 00:00:00 2001 From: yuquanjun Date: Tue, 8 Sep 2026 22:02:06 +0800 Subject: [PATCH] Make BLE SysEx UID and fw-code MIDI-safe for BLE-MIDI. Encode 01 07 UID as hex ASCII and use 01 0F (keep 01 FF alias) so high-bit bytes are not dropped as timestamps. Co-authored-by: Cursor --- protocol/bl_uart_parse.c | 32 +++++++++++++++++++++++++------- tools/test_protocol_app_sim.py | 17 +++++++++++++---- tools/test_protocol_ble.py | 31 ++++++++++++++++++++++++------- 3 files changed, 62 insertions(+), 18 deletions(-) diff --git a/protocol/bl_uart_parse.c b/protocol/bl_uart_parse.c index af0eeda..a7776a5 100644 --- a/protocol/bl_uart_parse.c +++ b/protocol/bl_uart_parse.c @@ -112,7 +112,8 @@ static const BLE_SysExCmdItem bleSysExCmdTable[] = {{0x01, 0x0A}, handleAutoPowerOff}, /* auto power-off read (minutes) */ {{0x01, 0x0C}, handleUserFwVer}, /* user fw version */ {{0x01, 0x11}, handleAutoPowerOffSet}, /* auto power-off set (minutes) */ - {{0x01, 0xFF}, handleFwCode}, /* fw code */ + {{0x01, 0x0F}, handleFwCode}, /* fw code (MIDI/BLE-safe; replaces 01 FF) */ + {{0x01, 0xFF}, handleFwCode}, /* fw code alias (raw UART only; 0xFF illegal in BLE-MIDI SysEx) */ /*======== 2. chord map 0x02 ========*/ {{0x02, 0x01}, handleReadRhythmMap}, /* read chord/pitch map */ @@ -646,10 +647,22 @@ static void handleOtherInfo(uint8_t *data) static void handleDevCode(uint8_t *data) { - uint8_t resp[17] = {0xF0, 0x60, 0x01, 0x07}; + /* 96-bit UID as 24 hex ASCII — SysEx data must stay 7-bit for BLE-MIDI */ + static const char hex[] = "0123456789ABCDEF"; + uint8_t resp[29]; + const uint8_t *uid = (const uint8_t *)AT32_UID_BASE; + uint8_t i; (void)data; - memcpy(&resp[4], (const void *)AT32_UID_BASE, 12); - resp[16] = 0xF7; + resp[0] = 0xF0; + resp[1] = 0x60; + resp[2] = 0x01; + resp[3] = 0x07; + for (i = 0; i < 12u; i++) + { + resp[4u + 2u * i] = (uint8_t)hex[uid[i] >> 4]; + resp[4u + 2u * i + 1u] = (uint8_t)hex[uid[i] & 0x0Fu]; + } + resp[28] = 0xF7; USART4_SendData(resp, sizeof(resp)); } @@ -686,14 +699,19 @@ static void handleAutoPowerOffSet(uint8_t *data) static void handleFwCode(uint8_t *data) { - /* 01 FF 固件编码: product string TBD; interim reply uses NVM fwname ("BRS08L") */ + /* 固件编码: 请求 01 0F(推荐)或 01 FF(仅裸串口);应答统一 01 0F + ASCII,避免 BLE-MIDI 吃掉 0xFF */ uint8_t resp[16]; uint8_t len = (uint8_t)strlen(fwname); (void)data; - resp[0] = 0xF0; resp[1] = 0x60; resp[2] = 0x01; resp[3] = 0xFF; + if (len > 10u) + len = 10u; + resp[0] = 0xF0; + resp[1] = 0x60; + resp[2] = 0x01; + resp[3] = 0x0F; memcpy(&resp[4], fwname, len); resp[4 + len] = 0xF7; - USART4_SendData(resp, (uint16_t)(4 + len + 1)); + USART4_SendData(resp, (uint16_t)(4u + len + 1u)); } /*-------- 2. chord map 0x02 --------*/ diff --git a/tools/test_protocol_app_sim.py b/tools/test_protocol_app_sim.py index cc85683..45d3247 100644 --- a/tools/test_protocol_app_sim.py +++ b/tools/test_protocol_app_sim.py @@ -163,12 +163,21 @@ def run_tests(sim, allow_poweroff=False): R.add("01 06 其他信息", "PASS" if ok else "FAIL", d) r = sim.query(build(0x01, 0x07)) - ok, d = expect(r, (0x60, 0x01, 0x07), 17) + ok, d = expect(r, (0x60, 0x01, 0x07), 29) # 12B UID → 24 hex ASCII + if ok: + hx = bytes(r[4:-1]) + ok = len(hx) == 24 and all(c in b"0123456789ABCDEF" for c in hx) + d += f" uid_hex={hx.decode('ascii', 'replace')}" R.add("01 07 设备编码(UID)", "PASS" if ok else "FAIL", d) - r = sim.query(build(0x01, 0xFF)) - ok, d = expect(r, (0x60, 0x01, 0xFF)) - R.add("01 FF 固件编码", "PASS" if ok else "FAIL", d) + # 01 0F:BLE-MIDI 安全子命令(原 01 FF 的 0xFF 非法出现在 SysEx 数据中) + r = sim.query(build(0x01, 0x0F)) + ok, d = expect(r, (0x60, 0x01, 0x0F)) + if ok: + code = bytes(r[4:-1]) + ok = len(code) > 0 and all(0x20 <= c < 0x7F for c in code) + d += f" code={code!r}" + R.add("01 0F 固件编码", "PASS" if ok else "FAIL", d) # 自动关机: 读 -> 设15 -> 读验证 -> 恢复原值 r0 = sim.query(build(0x01, 0x0A)) diff --git a/tools/test_protocol_ble.py b/tools/test_protocol_ble.py index 53c602a..dc3c6a5 100644 --- a/tools/test_protocol_ble.py +++ b/tools/test_protocol_ble.py @@ -61,12 +61,28 @@ def ble_midi_encode_sysex(frame: bytes, max_payload: int): def ble_midi_decode_packet(payload: bytes) -> bytes: - if len(payload) < 2: + """Strip BLE-MIDI header/timestamp bytes; keep App SysEx (F0…F7). + + - Framed: [header ts][optional ts][F0 … ts … F7] + - Raw (some ATS2853 notifies): [F0 … F7] — must not treat F0 as header + (F0/F7 also have bit7=1). + """ + if not payload: return b"" - i = 1 - if payload[i] & 0x80: + i = 0 + if payload[0] not in (0xF0, 0xF7) and (payload[0] & 0x80): + i = 1 # BLE-MIDI header + out = bytearray() + while i < len(payload): + b = payload[i] i += 1 - return bytes(payload[i:]) + if b in (0xF0, 0xF7): + out.append(b) + elif b & 0x80: + continue # timestamp inside/around SysEx + else: + out.append(b) + return bytes(out) class BleMidiSim: @@ -292,7 +308,8 @@ def write_report(R: Results, info: dict, path: str, notes: list[str] | None = No for note in notes or []: lines.append(f"- {note}") lines += [ - "- `01 03` 为 MCU 实际版本编码;`01 FF` 暂 `BRS08L`(文档待定)。", + "- `01 03` 为 MCU 实际版本;`01 0F` 固件编码暂 `BRS08L`(原 `01 FF`,BLE-MIDI 下 0xFF 非法)。", + "- `01 07` UID 为 24 字符 hex ASCII(96-bit),保证 SysEx 7-bit 安全。", "- `04 xx`/`06 xx` 无应答,SENT 表示已发送。", "- `FD 01` 升级指令不在本协议范围。", ] @@ -369,8 +386,8 @@ def main(): ) notes = [ f"transport={sim.info.get('transport')}", - "若 FAIL 且 timeout: 检查手机 App 是否占用、Windows 是否残留配对(--unpair)、吉他是否需完全断电重启。", - "实测: 标准 BLE-MIDI 可稳定连接但 SysEx 无应答; 自定义 uart 写 raw SysEx 会断连。", + "若 FAIL 且 timeout: 检查手机 App 是否占用、Windows 是否残留配对(--unpair)。", + "BLE-MIDI Notify 中的时间戳字节已在解码时剥离。", ] try: R = smoke(sim) if args.smoke else run_tests(sim, allow_poweroff=args.allow_poweroff)