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` 升级指令不在本协议范围。",