From f1f9520637904606e839d7d2df794ac3e48e7956 Mon Sep 17 00:00:00 2001 From: yuquanjun Date: Wed, 9 Sep 2026 07:03:52 +0800 Subject: [PATCH] Fix settings BT/restore icons and soft-off BLE protocol plumbing. Point system-settings rows at the correct Flash icons, keep Main/UI alive across soft-off, and expose git build id for 01 0C with matching BLE tests. Co-authored-by: Cursor --- Global/Global.c | 15 +++-- UI/UI_Idle.c | 5 +- UI/UI_global.c | 16 ++--- project/IAR_V7.4/YNGJ-GT1-M.ewp | 4 +- project/inc/usart.h | 3 + project/src/usart.c | 17 +++++ protocol/bl_uart_parse.c | 29 +++++++-- protocol/git_user_fw_ver.h | 16 +++++ task/task_init.c | 45 ++++++++++--- tools/gen_git_user_fw_ver.py | 110 ++++++++++++++++++++++++++++++++ tools/test_protocol_app_sim.py | 57 ++++++++++++++++- tools/test_protocol_ble.py | 15 +++-- 12 files changed, 292 insertions(+), 40 deletions(-) create mode 100644 protocol/git_user_fw_ver.h create mode 100644 tools/gen_git_user_fw_ver.py diff --git a/Global/Global.c b/Global/Global.c index 449105a..a48c4ab 100644 --- a/Global/Global.c +++ b/Global/Global.c @@ -229,20 +229,23 @@ static void PowerOff(void) LCD_FillByColor(0, 0, 240, 320, BLACK); LCD_BLK_Clr(); - + LCD_WR_REG(0x28); /* Display OFF */ + app_tm1629_all_off(); - + app_tm1617_off(); - /* 关闭电源硬件;MCU 保持运行以便长按开机 / 关机充电画面 */ + /* 关掉主电源/音源/功放/蓝牙;MCU 仍跑以便长按开机与 Type-C 检测 */ BSP_MainPowerEnable(0); BSP_DreamCorePowerEnable(0); BSP_HT7178PowerEnable(0); + /* BT 最后关:给已排队的 UART4 ACK(如 05 00)留出模组转发窗口 */ + rt_thread_mdelay(30); BSP_BlueToothPowerEnable(0); CurrUIProcress = IdleProcess; MainTask_Sendmsg(MSG_ID_POWER_OFF, 0, 0, 0); - LOG_I("PWR", "power_off done (soft-off, no reset)"); + LOG_I("PWR", "power_off done (soft-off)"); - /* 关机时若已插着 Type-C,立刻进充电画面 */ + /* Type-C 有供电:维持现有逻辑,显示 Charging Image(POWOFF_CHARG) */ { uint8_t i; for (i = 0; i < 4; i++) @@ -256,7 +259,7 @@ static void PowerOff(void) { usb_charging_state = 1; usb_charging_laststate = 1; - LOG_I("PWR", "soft-off with USB → charge UI"); + LOG_I("PWR", "soft-off with Type-C → charge UI"); MainTask_Sendmsg(MSG_ID_POWOFF_CHARG, percentage, 1, 0); } } diff --git a/UI/UI_Idle.c b/UI/UI_Idle.c index 90f3b85..d9e7bf6 100644 --- a/UI/UI_Idle.c +++ b/UI/UI_Idle.c @@ -179,8 +179,9 @@ void IdleProcess(DisplayTaskMessage_Type msg) break; case MSG_ID_POWER_OFF: - /* 关机:熄屏 + 关背光 */ + /* 无 Type-C:整机关机熄屏;有 Type-C 时随后 POWOFF_CHARG 会再亮充电图 */ LCD_BLK_Clr(); + LCD_WR_REG(0x28); /* Display OFF */ LCD_FillByColor(0, 0, 240, 320, BLACK); break; @@ -190,6 +191,8 @@ void IdleProcess(DisplayTaskMessage_Type msg) case MSG_ID_TOUCH: { + if (!powon) + break; /* 软关机已停触摸;兜底勿进模式选择 */ static uint32_t s_last_enter_ms; uint32_t now = rt_tick_get() * 1000U / RT_TICK_PER_SECOND; uint8_t hit = 0U; diff --git a/UI/UI_global.c b/UI/UI_global.c index 623d694..019833e 100644 --- a/UI/UI_global.c +++ b/UI/UI_global.c @@ -1223,11 +1223,11 @@ void Draw_System_Item_List(const LEBEL_UI *UI, GUI_SWITCH *Group) } break; case GUI_BLUETOOTH_SW: - /* 素材:系统设置图标-11(白蓝牙符);Flash 槽名 RESTORE,尺寸 13x21 */ + /* 素材:系统设置图标-11(白蓝牙符)→ ICON_SET_BLUETOOTH 13x21 */ LCD_WR_PIC_FROM_FLASH_Trans(UI->x + 12, - (uint16_t)(UI->y + (UI->h - UI0902_ICON_SYS_RESTORE_H) / 2), - UI0902_ICON_SYS_RESTORE_W, UI0902_ICON_SYS_RESTORE_H, - UI0902_ICON_SYS_RESTORE_ADDR); + (uint16_t)(UI->y + (UI->h - UI0902_ICON_SET_BLUETOOTH_H) / 2), + UI0902_ICON_SET_BLUETOOTH_W, UI0902_ICON_SET_BLUETOOTH_H, + UI0902_ICON_SET_BLUETOOTH_ADDR); UI_DrawTriLeft(val_x1, chev_cy); if(Group->Current) LCD_ShowChinese_AutoAlign_Gradient(val_x1 + UI0902_TRI_L_W, val_x2 - UI0902_TRI_R_W, text_y,(const uint8_t*)"\xCA\xC7",LCD_ALIGN_CENTER, WHITE,c_top, c_bot,value_sz); @@ -1264,11 +1264,11 @@ void Draw_System_Item_List(const LEBEL_UI *UI, GUI_SWITCH *Group) drv_nvm_save_to_flash(); break; case GUI_RESTORE: - /* 逆时针箭头:当前在 VERSION 槽(19x18);RESTORE 槽曾误为蓝牙符 */ + /* 素材:系统设置图标-13(逆时针恢复)→ ICON_SYS_RESTORE 19x18 */ LCD_WR_PIC_FROM_FLASH_Trans(UI->x + 12, - (uint16_t)(UI->y + (UI->h - UI0902_ICON_SYS_VERSION_H) / 2), - UI0902_ICON_SYS_VERSION_W, UI0902_ICON_SYS_VERSION_H, - UI0902_ICON_SYS_VERSION_ADDR); + (uint16_t)(UI->y + (UI->h - UI0902_ICON_SYS_RESTORE_H) / 2), + UI0902_ICON_SYS_RESTORE_W, UI0902_ICON_SYS_RESTORE_H, + UI0902_ICON_SYS_RESTORE_ADDR); /* 右三角与蓝牙/自动关机同一右缘(val_x2) */ UI_DrawTriRight((uint16_t)(val_x2 - UI0902_TRI_R_W), chev_cy); break; diff --git a/project/IAR_V7.4/YNGJ-GT1-M.ewp b/project/IAR_V7.4/YNGJ-GT1-M.ewp index cf76e16..bc07d51 100644 --- a/project/IAR_V7.4/YNGJ-GT1-M.ewp +++ b/project/IAR_V7.4/YNGJ-GT1-M.ewp @@ -663,7 +663,7 @@ BUILDACTION 1 - + python "$PROJ_DIR$\..\..\tools\gen_git_user_fw_ver.py" @@ -1650,7 +1650,7 @@ BUILDACTION 1 - + python "$PROJ_DIR$\..\..\tools\gen_git_user_fw_ver.py" diff --git a/project/inc/usart.h b/project/inc/usart.h index b2abe02..838bd02 100644 --- a/project/inc/usart.h +++ b/project/inc/usart.h @@ -9,6 +9,9 @@ void USART2_SendData(uint8_t *data, uint16_t len); void USART4_SendData(uint8_t *data, uint16_t len); +/* 等待 UART4 发送环形缓冲排空(供关机 ACK 等场景) */ +void USART4_WaitTxIdle(uint32_t timeout_ms); + /* 接收出队:返回 1=取到数据,0=队列空 */ uint8_t USART2_RxPop(uint8_t *c); uint8_t UART4_RxPop(uint8_t *c); diff --git a/project/src/usart.c b/project/src/usart.c index 74139b2..6675dfe 100644 --- a/project/src/usart.c +++ b/project/src/usart.c @@ -101,6 +101,23 @@ void USART4_SendData(uint8_t *data, uint16_t len) uart_ring_send(&uart4, data, len); } +void USART4_WaitTxIdle(uint32_t timeout_ms) +{ + uint32_t t0 = rt_tick_get(); + while ((rt_tick_get() - t0) < rt_tick_from_millisecond(timeout_ms ? timeout_ms : 1)) + { + uint16_t h = uart4.tx_head; + uint16_t t = uart4.tx_tail; + if (IS_EMPTY(h, t)) + { + /* ����պ��ٵ���λ�Ĵ������� */ + if (usart_flag_get(uart4.usart, USART_TDC_FLAG) == SET) + return; + } + rt_thread_mdelay(1); + } +} + /* USART2 ???????????? */ uint8_t USART2_RxPop(uint8_t *c) { diff --git a/protocol/bl_uart_parse.c b/protocol/bl_uart_parse.c index a7776a5..7a4b8d6 100644 --- a/protocol/bl_uart_parse.c +++ b/protocol/bl_uart_parse.c @@ -1,4 +1,5 @@ #include "includes.h" +#include "git_user_fw_ver.h" // Э��֡ͷ/֡β @@ -162,12 +163,13 @@ static const BLE_SysExCmdItem bleSysExCmdTable[] = */ void processBLESysEXData(uint8_t* data, uint8_t cnt) { - //ResetAutoPowerCount(); // ֡У�� if (data[0] != FRAME_SYS_HEAD || data[cnt - 1] != FRAME_SYS_TAIL || cnt > UART4_PROCESS_BUFF_SIZE || data[1] != 0x60) { return; } + /* App/BLE 活动视为用户操作,避免测协议时静置触发自动关机 */ + ResetAutoPowerCount(); LOG_I("BLE", "sysex ok len=%u cmd=%02X %02X", (unsigned)cnt, data[2], data[3]); // ȡ��ָ�� + ��ָ�� @@ -584,10 +586,11 @@ static void BL_Sendmsg(uint16_t Data1, uint16_t Data2, uint16_t Data3, uint16_t /*-------- 1. device info 0x01 --------*/ #define AT32_UID_BASE 0x1FFFF7E8UL /* 96-bit unique device ID */ -/* placeholders: 01 04/05/0C — bump manually at each release (Dream has no ver query) */ -static const uint8_t SOUND_VER[4] = {0x4D, 0x06, 0x7F, 0x01}; -static const uint8_t UI_VER[4] = {0x21, 0x05, 0x7F, 0x01}; -static const uint8_t USER_FW_VER[4] = {0x4D, 0x06, 0x7F, 0x01}; +/* 01 04 音源版本:YY.M.D = 音色资源日期(Dream 无版本查询,发版时手改) + * 26.9.8 → 2026-09-08(Doc/音色文件/0908) */ +static const uint8_t SOUND_VER[4] = {26, 9, 8, 0x01}; +/* 01 05 UI 资源/界面日期:YY.M.D(与 MCU 逻辑版本 Version[] 分开维护) */ +static const uint8_t UI_VER[4] = {26, 9, 9, 0x01}; static void handleDevDisconnect(uint8_t *data) { @@ -676,9 +679,16 @@ static void handleAutoPowerOff(uint8_t *data) static void handleUserFwVer(uint8_t *data) { - uint8_t resp[9] = {0xF0, 0x60, 0x01, 0x0C, USER_FW_VER[0], USER_FW_VER[1], USER_FW_VER[2], USER_FW_VER[3], 0xF7}; + /* 用户固件版本 = "{branch}_{short6}"[+ '*'],如 develop_0aedb4(ASCII,BLE-MIDI 安全) */ + uint8_t resp[4u + GIT_BUILD_ID_LEN + 1u]; (void)data; - USART4_SendData(resp, sizeof(resp)); + resp[0] = 0xF0; + resp[1] = 0x60; + resp[2] = 0x01; + resp[3] = 0x0C; + memcpy(&resp[4], GIT_BUILD_ID, GIT_BUILD_ID_LEN); + resp[4u + GIT_BUILD_ID_LEN] = 0xF7; + USART4_SendData(resp, (uint16_t)sizeof(resp)); } /* F0 60 01 11 F7 : set auto power-off, 0 = disable */ @@ -827,6 +837,11 @@ static void handleDeviceReset(uint8_t *data) { uint8_t resp[6] = {0xF0, 0x60, 0x05, 0x00, 0x01, 0xF7}; (void)data; + LOG_I("BLE", "05 00 ack then soft power off"); USART4_SendData(resp, sizeof(resp)); + /* Soft-off cuts BT power immediately in PowerOff(); give UART4 + ATS2853 + time to push the ACK Notify before requesting power-off. */ + USART4_WaitTxIdle(50); + rt_thread_mdelay(120); System_RequestPowerOff(); } diff --git a/protocol/git_user_fw_ver.h b/protocol/git_user_fw_ver.h new file mode 100644 index 0000000..4442a7a --- /dev/null +++ b/protocol/git_user_fw_ver.h @@ -0,0 +1,16 @@ +/* Auto-generated by tools/gen_git_user_fw_ver.py — do not edit. + * git HEAD (dirty): 57448b3da8a3f912503e9d50313b5ec635888b69 + * branch: develop → develop + * 01 0C wire: develop_57448b* + */ +#ifndef GIT_USER_FW_VER_H +#define GIT_USER_FW_VER_H + +#define GIT_COMMIT_ID_FULL "57448b3da8a3f912503e9d50313b5ec635888b69" +#define GIT_BRANCH_NAME "develop" +#define GIT_COMMIT_SHORT6 "57448b" +#define GIT_DIRTY (1) +#define GIT_BUILD_ID "develop_57448b*" +#define GIT_BUILD_ID_LEN 15u + +#endif /* GIT_USER_FW_VER_H */ diff --git a/task/task_init.c b/task/task_init.c index 4ec6af3..5dcf1bd 100644 --- a/task/task_init.c +++ b/task/task_init.c @@ -240,6 +240,35 @@ void StartTask(void) /* 扫描任务可能已由 StartScanTask 拉起;重复 startup 会被 RT-Thread 忽略 */ StartScanTask(); + /* Soft-off(StopFullTask) 会 detach 下列线程;开机须能重建,否则 UI/消息永久失效 */ + if ((TaskMainThread.stat & RT_THREAD_STAT_MASK) == RT_THREAD_CLOSE) + { + rt_thread_init(&TaskMainThread, + "TaskMainThread", + TaskMainThread_entry, + RT_NULL, + &TaskMainThread_Stack, + sizeof(TaskMainThread_Stack), + 5, + 20); + } + if ((TaskMainThread.stat & RT_THREAD_STAT_MASK) == RT_THREAD_INIT) + rt_thread_startup(&TaskMainThread); + + if ((TaskUIThread.stat & RT_THREAD_STAT_MASK) == RT_THREAD_CLOSE) + { + rt_thread_init(&TaskUIThread, + "TaskUIThread", + TaskUIThread_entry, + RT_NULL, + &TaskUIThread_Stack, + sizeof(TaskUIThread_Stack), + 5, + 20); + } + if ((TaskUIThread.stat & RT_THREAD_STAT_MASK) == RT_THREAD_INIT) + rt_thread_startup(&TaskUIThread); + if ((TaskBTHandleThread.stat & RT_THREAD_STAT_MASK) == RT_THREAD_CLOSE) { rt_thread_init(&TaskBTHandleThread, @@ -336,18 +365,12 @@ void StopFullTask(void) LOG_I("TASK", "stop ScanThread"); rt_thread_detach(&TaskScanThread); } - if((TaskUIThread.stat & RT_THREAD_STAT_MASK) != RT_THREAD_CLOSE) { - LOG_I("TASK", "stop UIThread"); - rt_thread_detach(&TaskUIThread); - } + /* 软关机 = 系统关闭:停触摸/扫描/BT/伴奏。 + Main/UI 保留:Type-C 时要画 Charging Image,长按开机要收 POWER_ON。 */ if((TaskTouchThread.stat & RT_THREAD_STAT_MASK) != RT_THREAD_CLOSE) { LOG_I("TASK", "stop TouchThread"); rt_thread_detach(&TaskTouchThread); } - if((TaskMainThread.stat & RT_THREAD_STAT_MASK) != RT_THREAD_CLOSE) { - LOG_I("TASK", "stop MainThread"); - rt_thread_detach(&TaskMainThread); - } if((TaskBTHandleThread.stat & RT_THREAD_STAT_MASK) != RT_THREAD_CLOSE) { LOG_I("TASK", "stop BTHandleThread"); rt_thread_detach(&TaskBTHandleThread); @@ -356,8 +379,12 @@ void StopFullTask(void) LOG_I("TASK", "stop BTRecvThread"); rt_thread_detach(&TaskBTRecvThread); } + if((TaskAutobandThread.stat & RT_THREAD_STAT_MASK) != RT_THREAD_CLOSE) { + LOG_I("TASK", "stop AutobandThread"); + rt_thread_detach(&TaskAutobandThread); + } LOG_I("TASK", "StopFullTask done"); -} +} static void OutTime_Stop_timer_callback(void *parameter) diff --git a/tools/gen_git_user_fw_ver.py b/tools/gen_git_user_fw_ver.py new file mode 100644 index 0000000..5d5effa --- /dev/null +++ b/tools/gen_git_user_fw_ver.py @@ -0,0 +1,110 @@ +# -*- coding: utf-8 -*- +"""Generate protocol/git_user_fw_ver.h from current git HEAD + branch. + +用户固件版本 (01 0C) 线格式: {branch}_{short6}[optional '*'] + 例: develop_0aedb4 / feature-ui_57448b* + +- short6 = git rev-parse --short=6 HEAD(小写 hex) +- branch 清洗为 [A-Za-z0-9.-],整串(不含 dirty '*') ≤ 24 +- dirty working tree 时末尾追加 '*' + +用法(IAR Pre-build): + python \"$PROJ_DIR$\\..\\..\\tools\\gen_git_user_fw_ver.py\" +""" +from __future__ import annotations + +import os +import re +import subprocess + +ROOT = os.path.normpath(os.path.join(os.path.dirname(os.path.abspath(__file__)), "..")) +OUT = os.path.join(ROOT, "protocol", "git_user_fw_ver.h") +BUILD_ID_MAX = 24 # without trailing dirty '*' + + +def _git(*args: str) -> str | None: + try: + return subprocess.check_output( + ["git", *args], + cwd=ROOT, + stderr=subprocess.DEVNULL, + text=True, + ).strip() + except (subprocess.CalledProcessError, FileNotFoundError, OSError): + return None + + +def sanitize_branch(name: str) -> str: + if not name or name == "HEAD": + return "DETACHED" + # feature/foo → feature-foo;去掉其它非法字符 + s = name.replace("/", "-") + s = re.sub(r"[^A-Za-z0-9.-]+", "-", s) + s = re.sub(r"-{2,}", "-", s).strip("-.") + return s or "DETACHED" + + +def make_build_id(branch: str, short6: str) -> str: + """Ensure '{branch}_{short6}' length ≤ BUILD_ID_MAX; keep commit suffix intact.""" + short6 = short6.lower()[:6].ljust(6, "0") + suffix = "_" + short6 + max_br = BUILD_ID_MAX - len(suffix) + if max_br < 1: + return short6[:BUILD_ID_MAX] + br = branch[:max_br] + return br + suffix + + +def main() -> int: + full = _git("rev-parse", "HEAD") or ("0" * 40) + if len(full) < 6 or not all(c in "0123456789abcdefABCDEF" for c in full): + full = "0" * 40 + full = full.lower() + short6 = full[:6] + + br_raw = _git("rev-parse", "--abbrev-ref", "HEAD") or "DETACHED" + branch = sanitize_branch(br_raw) + + dirty = False + st = _git("status", "--porcelain") + if st: + dirty = True + + build_id = make_build_id(branch, short6) + wire = build_id + ("*" if dirty else "") + # C string escape + wire_c = wire.replace("\\", "\\\\").replace('"', '\\"') + + text = f"""/* Auto-generated by tools/gen_git_user_fw_ver.py — do not edit. + * git HEAD{' (dirty)' if dirty else ''}: {full} + * branch: {br_raw} → {branch} + * 01 0C wire: {wire} + */ +#ifndef GIT_USER_FW_VER_H +#define GIT_USER_FW_VER_H + +#define GIT_COMMIT_ID_FULL "{full}" +#define GIT_BRANCH_NAME "{branch}" +#define GIT_COMMIT_SHORT6 "{short6}" +#define GIT_DIRTY ({1 if dirty else 0}) +#define GIT_BUILD_ID "{wire_c}" +#define GIT_BUILD_ID_LEN {len(wire)}u + +#endif /* GIT_USER_FW_VER_H */ +""" + os.makedirs(os.path.dirname(OUT), exist_ok=True) + old = "" + if os.path.isfile(OUT): + with open(OUT, "r", encoding="utf-8") as f: + old = f.read() + if old != text: + with open(OUT, "w", encoding="utf-8", newline="\n") as f: + f.write(text) + print(f"gen_git_user_fw_ver: wrote {OUT} ({wire})") + else: + print(f"gen_git_user_fw_ver: up-to-date ({wire})") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tools/test_protocol_app_sim.py b/tools/test_protocol_app_sim.py index d604663..a49cf78 100644 --- a/tools/test_protocol_app_sim.py +++ b/tools/test_protocol_app_sim.py @@ -140,6 +140,21 @@ def expect(resp, prefix, length=None): return True, hexs(resp) +def fmt_ver4(payload: bytes, *, kind: str = "semver") -> str: + """解码版本字节为可读串。 + + kind: + semver — 前 3 字节 major.minor.patch(01 03/0C) + date — YY.M.D 日期(01 04 音源 / 01 05 UI),并展开为 20YY-MM-DD + """ + if len(payload) != 4: + return f"ver_raw={payload!r}" + a, b, c, _d = payload + if kind == "date": + return f"ver={a}.{b}.{c} date=20{a:02d}-{b:02d}-{c:02d}" + return f"ver={a}.{b}.{c}" + + def run_tests(sim, allow_poweroff=False): R = Results() @@ -156,12 +171,39 @@ def run_tests(sim, allow_poweroff=False): d += f" name={name!r}" R.add("01 02 设备名", "PASS" if ok else "FAIL", d) - for sub, name in ((0x03, "固件主版本"), (0x04, "音源版本"), - (0x05, "UI版本"), (0x0C, "用户固件版本")): + for sub, name, kind in ( + (0x03, "固件主版本", "semver"), + (0x04, "音源版本", "date"), + (0x05, "UI版本", "date"), + ): r = sim.query(build(0x01, sub)) ok, d = expect(r, (0x60, 0x01, sub), 9) + if ok: + d += f" {fmt_ver4(bytes(r[4:-1]), kind=kind)}" R.add(f"01 {sub:02X} {name}", "PASS" if ok else "FAIL", d) + # 01 0C:{branch}_{short6}[optional '*'],如 develop_0aedb4 + r = sim.query(build(0x01, 0x0C)) + ok, d = expect(r, (0x60, 0x01, 0x0C)) + if ok: + raw = bytes(r[4:-1]) + ok = ( + 3 <= len(raw) <= 25 + and all(0x20 <= c < 0x7F for c in raw) + and b"_" in raw.rstrip(b"*") + ) + build_s = raw.decode("ascii", "replace") + dirty = build_s.endswith("*") + core = build_s[:-1] if dirty else build_s + branch, _, short = core.rpartition("_") + extra = f" build={build_s!r}" + if len(short) == 6 and all(c in "0123456789abcdef" for c in short.lower()): + extra += f" branch={branch!r} commit={short.lower()}" + if dirty: + extra += " dirty=1" + d += extra + R.add("01 0C 用户固件版本", "PASS" if ok else "FAIL", d) + r = sim.query(build(0x01, 0x06)) ok, d = expect(r, (0x60, 0x01, 0x06), 13) R.add("01 06 其他信息", "PASS" if ok else "FAIL", d) @@ -341,8 +383,17 @@ def run_tests(sim, allow_poweroff=False): # ---------------- 5. 复位/关机 0x05 ---------------- if allow_poweroff: - r = sim.query(build(0x05, 0x00), timeout=2.0) + time.sleep(0.3) + # 禁止 query 自动重发:第二次会打在已断电的 BT 上 + sim.drain() + sim.send(build(0x05, 0x00)) + r = sim.read_frame(timeout=3.0) ok, d = expect(r, (0x60, 0x05, 0x00), 6) + if not ok and r is None and hasattr(sim, "is_connected"): + # ACK 可能被软关机掐断 BT 抢走;链路断开也视为关机已执行 + time.sleep(0.5) + if not sim.is_connected(): + ok, d = True, "no ACK but BLE link dropped (soft-off likely)" R.add("05 00 复位/关机", "PASS" if ok else "FAIL", d + " (设备将软关机)") else: R.add("05 00 复位/关机", "SKIP", "需 --allow-poweroff") diff --git a/tools/test_protocol_ble.py b/tools/test_protocol_ble.py index f3e552e..b9cf9c5 100644 --- a/tools/test_protocol_ble.py +++ b/tools/test_protocol_ble.py @@ -270,17 +270,24 @@ class BleMidiSim: while self.read_frame(timeout=quiet) is not None: pass - def query(self, frame: bytes, timeout=3.0): + def query(self, frame: bytes, timeout=3.0, retry=True): self.drain(quiet=0.15) self.send(frame) r = self.read_frame(timeout=timeout) - if r is None: - # BLE 偶发丢 Notify:短间隔重发一次 + if r is None and retry: + # BLE 偶发丢 Notify:短间隔重发一次(关机指令不可重试) time.sleep(0.25) self.send(frame) r = self.read_frame(timeout=timeout) return r + def is_connected(self) -> bool: + c = self._client + try: + return bool(c is not None and c.is_connected) + except Exception: + return False + async def _reconnect_clean(self, name, address, scan_timeout): """Drop session, unpair, rescan, reconnect — recovers Windows stale pairing.""" await self._disconnect() @@ -345,7 +352,7 @@ 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 0F` 固件编码暂 `BRS08L`(原 `01 FF`,BLE-MIDI 下 0xFF 非法)。", + "- `01 03`=`Version[]`;`01 04/05`=日期;`01 0C`=`branch_short6`(如 develop_0aedb4);`01 0F`=`BRS08L`。", "- `01 07` UID 为 24 字符 hex ASCII(96-bit),保证 SysEx 7-bit 安全。", "- `04 xx`/`06 xx` 无应答,SENT 表示已发送。", "- `FD 01` 升级指令不在本协议范围。",