评审 r1 status=p2-4items / merge=fix-then-merge(reviewed e4eda3a)。逐条: P2-1 未知态被写成 settings=0,把失真推到别的入口 - bt_page.c / wifi_page.c:`meow_settings_set_bool(..., disp == 1)` 加 `disp >= 0` 门。 disp == -1(daemon 与直连都读不到)时**保留用户原意图不动**。该键被 notif_center / settings_page 当显示态读、被 app_launcher 用来决定自动拉起 WiFi,写 0 会把「不知道」 污染成「确定已关」。 - test_bt_page.c 5d 期望同步改:未知 → settings 保持原意图 1(不再期望「回滚为 0」)。 P2-2 收尾后「探测未知」仍显示成确定态,第三态丢失 - mtog_calc:显示态**只认 probe**,probe < 0 直接返回 -1;last_real 不再顶替确定态 (`(void)last_real`,仅由调用方用于弹窗文案)。超时/下发失败且探测未知 → 顶栏保留 「关+?」、行内保留「状态未知」,与文件头契约一致。 - 弹窗文案按结论区分:「无法确认当前状态(上次已知:开启/关闭/未知)」vs 「切换未生效:当前仍为开启/关闭」——不再出现「当前仍为开启」自相矛盾(该文案只在 probe 已知且 != 意图时出现)。日志加 elapsed。 P2-3 deadline 起点错,注释上限是假的 - bt_page/wifi_page:`deadline` 从**点击时刻**起算(含下发耗时)。 - 注释改成如实上限:快路径 1~2s / ≈16 次探测;慢路径(on-off 读超时 1.5s + hciconfig 0.3~2s,之后一次探测 status 1.5s + 直连 1.8s ≈3.3s)总阻塞最坏 ≈7s、预算内只够 1 次 探测,此时只能确认「探测超时」而非「设备无响应」——文案已按此区分。异步化留给后续单。 P2-4 开关路径弹窗后缺 after_modal / 按键基线复位 - subpage.c:顶开关命中分支补上与行命中分支相同的后置清理(排空触摸 + 40ms 二次排空、 触摸基线/cooldown、key_router_rebase、kd_*/kp_poll 基线、m_prev_*、after_modal=1)。 s_dd_modal 是全屏压暗层,否则关闭后状态栏(0..27)残留压暗到下一次时钟重绘,电源/播放键 也可能带进弹窗里的陈旧边沿。 顺带(评审观察,未改全局超时策略) - netmgr_ipc.c:拆出 `nm_cmd_t(..., int *read_timeout)`;`nm_bt_power_state()` 走它, **读超时 = 状态未知且不把可达性缓存打成 down**(netmgrd 单线程忙时 `bt status` 自己 跑 hciconfig 可能超 1.5s;旧行为会标 down 1s,令紧随的 bt_rows 闪出「服务未运行(直连 模式)」并把后续探测切到 1.8s 直连)。WiFi/DNS 等其它调用保持旧语义(传 NULL)。 验证 - `bash patch_v2/meow_launcherd/build_launcher.sh test` → exit 0,**65 PASS / 0 FAIL / 0 warning** (矩阵 13 组,新增「下发失败·探测未知 → 未知不顶替」)。 - 交叉编译 0 warning;产物(临时副本,不入库)901020 B / md5 e21450ff6621a67fd854165413c58be1。 - `test_time32_abi.sh` → 4263=3 / 4403=0 ALL PASS;`pkg_install` 与 7 条切换中/未知态文案 均在 strings 中(旧的两条「无法确认蓝牙/WiFi 状态」已合并为「无法确认当前状态(上次已知:%s)」)。 - 真机 --sysdump(只读,临时文件已删):exit 0,bt_on=1/wifi_on=1 与设备现状一致; Music du 前后 199164544;未替换 /usr/bin/meow_launcherd、未重启 launcherd。 - 交付形态不变:本 commit 只动 5 个源码文件;二进制与 scripts/build/fw_content.conf 对 main 零 diff。
361 lines
18 KiB
C
361 lines
18 KiB
C
/* test_bt_page.c — MeowOS launcher 蓝牙子页(bt_page.c)sub_page_t 逻辑宿主自测(非生产代码)。
|
||
*
|
||
* 通过 #include bt_page.c 把其 FILE-STATIC 的 bt_top_sw/bt_rows/bt_act/bt_detail 平移进本 TU,
|
||
* 桩掉 netmgr IPC 客户端(nm_bt_*)+ 设置后端(meow_settings_*)+ UI(s_dd_modal/logmsg 等),
|
||
* 直接验证:
|
||
* P1 bt_rows 行骨架(plan §六 8 分组:②A2DP 摘要 ③输出目标 ④已连接 ⑤已配对 ⑥附近 ⑦音质 ⑧功耗)
|
||
* 与「已连接设备」数据(权威 netmgrd 状态 → mac/name/codec 渲染);
|
||
* P2 bt_act 命中 → 正确下发 netmgrd 命令(已连接→disconnect、已配对未连→connect、输出目标
|
||
* 切换、编解码器循环);
|
||
* P3 附近设备仅列未配对(已配对者归「已配对设备」)。
|
||
* P4 M47 开关三态:真实态未知(-1) → top_sw 回 -1 + 行内「蓝牙状态未知」(不当已关);
|
||
* P5 M47 点击收敛:下发成功且真实态达成 → 定稿、不弹窗;下发失败/超时/未知 → 回滚 + 弹窗;
|
||
* P6 M47 收敛矩阵(纯函数 meow_toggle_*,真实态×下发结果×探测 全组合)。
|
||
*
|
||
* 编译(在 patch_v2/meow_launcherd 下):
|
||
* gcc -I../sdk -I. -Wall -Wextra -fsanitize=undefined test_bt_page.c -o test_bt_page
|
||
* 运行:./test_bt_page(或 `bash build_launcher.sh test`)
|
||
*/
|
||
#include <stdio.h>
|
||
#include <stdlib.h>
|
||
#include <string.h>
|
||
#include <stdarg.h>
|
||
#include <strings.h>
|
||
#include <unistd.h>
|
||
#include <time.h>
|
||
|
||
/* M47:把收敛等待改成宿主可接受的尺度(生产 4000ms/250ms,见 bt_page.c)。 */
|
||
#define BT_TOGGLE_SETTLE_MS 80
|
||
#define BT_TOGGLE_POLL_MS 10
|
||
|
||
#include "meow_launcher_internal.h"
|
||
#include "bt_page.c" /* bt_rows/bt_act/bt_top_sw/bt_detail 平移到本 TU */
|
||
|
||
/* ---------- 测试态:netmgrd BT 状态桩(launcher 客户端视角) ---------- */
|
||
static meow_net_t g_conn[8], g_paired[8], g_near[8];
|
||
static int g_conn_n, g_paired_n, g_near_n;
|
||
static bt_ep_t g_eps[8]; static int g_eps_n;
|
||
static char g_last_cmd[128]; /* 记录 act 下发命令,如 "disconnect:<mac>" */
|
||
static int g_output_bt; /* audio.output_bt 桩值 */
|
||
static char g_codec[16] = "auto"; /* bt.codec 桩值 */
|
||
static int g_bt_state = 1; /* M47:真实态三态桩(-1 未知 / 0 / 1) */
|
||
static int g_onoff_rc = MEOW_OK; /* M47:nm_bt_onoff 下发结果桩 */
|
||
static int g_bt_en_setting = 1; /* M47:network.bluetooth_enabled 桩值(意图/回滚观察点) */
|
||
|
||
int nm_bt_power_state(void) { return g_bt_state; }
|
||
int nm_bt_onoff(int on) { (void)on; return g_onoff_rc; }
|
||
int nm_bt_connected(meow_net_t *out, int max) {
|
||
if (max > g_conn_n) max = g_conn_n;
|
||
memcpy(out, g_conn, (size_t)max * sizeof *out); return max;
|
||
}
|
||
int nm_bt_paired(meow_net_t *out, int max) {
|
||
if (max > g_paired_n) max = g_paired_n;
|
||
memcpy(out, g_paired, (size_t)max * sizeof *out); return max;
|
||
}
|
||
int nm_bt_devices(meow_net_t *out, int max) {
|
||
if (max > g_near_n) max = g_near_n;
|
||
memcpy(out, g_near, (size_t)max * sizeof *out); return max;
|
||
}
|
||
int nm_bt_a2dp(bt_ep_t *eps, int max) {
|
||
if (max > g_eps_n) max = g_eps_n;
|
||
memcpy(eps, g_eps, (size_t)max * sizeof *eps); return max;
|
||
}
|
||
static void set_cmd(const char *cmd, const char *mac)
|
||
{
|
||
snprintf(g_last_cmd, sizeof g_last_cmd, "%s:%s", cmd, mac ? mac : "");
|
||
}
|
||
int nm_bt_connect(const char *mac) { set_cmd("connect", mac); return MEOW_OK; }
|
||
int nm_bt_disconnect(const char *mac) { set_cmd("disconnect", mac); return MEOW_OK; }
|
||
int nm_bt_pair(const char *mac) { set_cmd("pair", mac); return MEOW_OK; }
|
||
int nm_bt_unpair(const char *mac) { set_cmd("unpair", mac); return MEOW_OK; }
|
||
void nm_bt_scan_on(void) {}
|
||
void nm_bt_scan_off(void) {}
|
||
int netmgr_ok(void) { return 1; }
|
||
void net_bt_mode_str(char *buf, int len) { snprintf(buf, (size_t)len, "主模式·已连 Pudding"); }
|
||
|
||
/* ---------- 设置/UI 桩 ---------- */
|
||
int meow_settings_get_bool(const char *k, int *v)
|
||
{
|
||
if (v) *v = 0;
|
||
if (!strcmp(k, "network.bluetooth_enabled")) { if (v) *v = g_bt_en_setting; return 0; }
|
||
if (!strcmp(k, "audio.output_bt")) { if (v) *v = g_output_bt; return 0; }
|
||
return -1;
|
||
}
|
||
int meow_settings_set_bool(const char *k, int v)
|
||
{
|
||
if (!strcmp(k, "audio.output_bt")) { g_output_bt = v; return 0; }
|
||
if (!strcmp(k, "network.bluetooth_enabled")) { g_bt_en_setting = v; return 0; }
|
||
return -1;
|
||
}
|
||
int meow_settings_get_str(const char *k, char *b, int n)
|
||
{
|
||
if (!strcmp(k, "bt.codec")) { snprintf(b, (size_t)n, "%s", g_codec); return 0; }
|
||
if (b && n > 0) b[0] = 0;
|
||
return -1;
|
||
}
|
||
int meow_settings_set_str(const char *k, const char *v)
|
||
{
|
||
if (!strcmp(k, "bt.codec")) { snprintf(g_codec, sizeof g_codec, "%s", v); return 0; }
|
||
return -1;
|
||
}
|
||
int meow_settings_save(void) { return 0; }
|
||
void paint_sb_right(void) {}
|
||
void logmsg(const char *fmt, ...) { (void)fmt; }
|
||
void bt_role_refresh(int force) { (void)force; }
|
||
meow_sys_state g_sys;
|
||
static int g_modal_n = 0; /* M47:可见错误提示(弹窗)计数 */
|
||
static char g_modal_line[128] = "";
|
||
int s_dd_modal(const char *title, const char *item[S_NET_MAX], int n, int cur)
|
||
{
|
||
(void)title; (void)cur;
|
||
g_modal_n++;
|
||
g_modal_line[0] = 0;
|
||
if (item && n > 0 && item[0]) snprintf(g_modal_line, sizeof g_modal_line, "%s", item[0]);
|
||
return -1;
|
||
}
|
||
int meow_subpage_run(sub_page_t *p) { (void)p; return 0; }
|
||
|
||
/* M47:真实时钟(收敛等待用),毫秒单调。 */
|
||
unsigned long long now_ms(void)
|
||
{
|
||
struct timespec ts;
|
||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||
return (unsigned long long)ts.tv_sec * 1000ULL + (unsigned long long)(ts.tv_nsec / 1000000);
|
||
}
|
||
|
||
static int g_fail = 0;
|
||
#define CHECK(cond, ...) do { if (cond) { printf(" PASS: " __VA_ARGS__); printf("\n"); } \
|
||
else { printf(" FAIL: " __VA_ARGS__); printf("\n"); g_fail++; } } while (0)
|
||
|
||
/* 在 rows 里找下标:kind==HDR 且 label==lbl;返回该 HDR 的下一行下标起查找 */
|
||
static int find_hdr(const sub_row_t *r, int n, const char *lbl)
|
||
{
|
||
for (int i = 0; i < n; i++) if (r[i].kind == SUB_HDR && r[i].label && !strcmp(r[i].label, lbl)) return i;
|
||
return -1;
|
||
}
|
||
/* 在 [from,n) 找首个 SUB_ACT 且 label 前缀 == pref,返回下标 */
|
||
static int find_act(const sub_row_t *r, int n, int from, const char *pref)
|
||
{
|
||
for (int i = from; i < n; i++)
|
||
if (r[i].kind == SUB_ACT && r[i].label && !strncmp(r[i].label, pref, strlen(pref))) return i;
|
||
return -1;
|
||
}
|
||
/* 按 label 精确找任意行(M47 未知态占位行用) */
|
||
static int row_label(const sub_row_t *r, int n, const char *lbl)
|
||
{
|
||
for (int i = 0; i < n; i++)
|
||
if (r[i].label && !strcmp(r[i].label, lbl)) return i;
|
||
return -1;
|
||
}
|
||
|
||
static void fill_state(void)
|
||
{
|
||
/* 已连接:Pudding(a2dp 播放中)+ Speaker */
|
||
g_conn_n = 2;
|
||
snprintf(g_conn[0].mac, sizeof g_conn[0].mac, "33:DE:2E:21:AD:30");
|
||
snprintf(g_conn[0].name, sizeof g_conn[0].name, "Pudding");
|
||
snprintf(g_conn[1].mac, sizeof g_conn[1].mac, "11:22:33:44:55:66");
|
||
snprintf(g_conn[1].name, sizeof g_conn[1].name, "Speaker");
|
||
/* 已配对:+ 17OP15(未连) */
|
||
g_paired_n = 3;
|
||
memcpy(&g_paired[0], &g_conn[0], sizeof g_conn[0]);
|
||
memcpy(&g_paired[1], &g_conn[1], sizeof g_conn[1]);
|
||
snprintf(g_paired[2].mac, sizeof g_paired[2].mac, "E0:42:6D:79:C5:98");
|
||
snprintf(g_paired[2].name, sizeof g_paired[2].name, "17OP15");
|
||
/* 附近:Earbuds(未配对 → 附近列出) */
|
||
g_near_n = 2;
|
||
snprintf(g_near[0].mac, sizeof g_near[0].mac, "AA:BB:CC:DD:EE:FF");
|
||
snprintf(g_near[0].name, sizeof g_near[0].name, "Earbuds");
|
||
snprintf(g_near[1].mac, sizeof g_near[1].mac, "33:DE:2E:21:AD:30"); /* 已配对 → 附近应过滤 */
|
||
snprintf(g_near[1].name, sizeof g_near[1].name, "Pudding");
|
||
/* A2DP:Pudding SBC/44100 acquired */
|
||
g_eps_n = 1;
|
||
snprintf(g_eps[0].mac, sizeof g_eps[0].mac, "33:DE:2E:21:AD:30");
|
||
snprintf(g_eps[0].codec, sizeof g_eps[0].codec, "SBC");
|
||
snprintf(g_eps[0].rate, sizeof g_eps[0].rate, "44100");
|
||
snprintf(g_eps[0].chn, sizeof g_eps[0].chn, "2");
|
||
snprintf(g_eps[0].bits, sizeof g_eps[0].bits, "16");
|
||
g_eps[0].acquired = 1;
|
||
snprintf(g_eps[0].role, sizeof g_eps[0].role, "src");
|
||
g_output_bt = 0; /* DAC */
|
||
snprintf(g_codec, sizeof g_codec, "auto");
|
||
}
|
||
|
||
int main(void)
|
||
{
|
||
setbuf(stdout, NULL);
|
||
fill_state();
|
||
bt_ctx_t c;
|
||
memset(&c, 0, sizeof c);
|
||
c.full = 1;
|
||
c.output_bt = 0;
|
||
sub_row_t rows[SUB_ROWS_MAX];
|
||
int n = bt_rows(&c, rows, SUB_ROWS_MAX);
|
||
|
||
printf("== P1 bt_rows 分组骨架 ==\n");
|
||
CHECK(find_hdr(rows, n, "输出目标") >= 0, "分组③输出目标存在");
|
||
CHECK(find_hdr(rows, n, "已连接设备") > 0, "分组④已连接设备存在");
|
||
CHECK(find_hdr(rows, n, "已配对设备") > 0, "分组⑤已配对设备存在");
|
||
CHECK(find_hdr(rows, n, "附近设备") > 0, "分组⑥附近设备存在");
|
||
CHECK(find_hdr(rows, n, "音质 / 连接管理") > 0, "分组⑦音质/连接管理存在");
|
||
CHECK(find_hdr(rows, n, "功耗") > 0, "分组⑧功耗存在");
|
||
/* A2DP 摘要(首行 SUB_ROW,主=Pudding,副含 codec/采样率/输出=DAC) */
|
||
CHECK(rows[0].kind == SUB_ROW, "行0=A2DP摘要(SUB_ROW)");
|
||
CHECK(rows[0].label && !strcmp(rows[0].label, "Pudding"), "摘要主=Pudding (实际=%s)",
|
||
rows[0].label ? rows[0].label : "(null)");
|
||
CHECK(rows[0].sub && strstr(rows[0].sub, "SBC") && strstr(rows[0].sub, "44100") &&
|
||
strstr(rows[0].sub, "输出=DAC"), "摘要副含 codec/采样率/输出=DAC (%s)",
|
||
rows[0].sub ? rows[0].sub : "(null)");
|
||
|
||
printf("== P1 已连接设备(权威状态,含 codec join)==\n");
|
||
int hdr = find_hdr(rows, n, "已连接设备");
|
||
CHECK(hdr >= 0, "定位已连接 HDR");
|
||
int ci = find_act(rows, n, hdr + 1, "Pudding");
|
||
CHECK(ci >= 0, "已连接列出 Pudding");
|
||
CHECK(ci >= 0 && rows[ci].sub && strstr(rows[ci].sub, "SBC"), "已连接 Pudding sub 含 codec SBC");
|
||
CHECK(find_act(rows, n, hdr + 1, "Speaker") >= 0, "已连接列出 Speaker");
|
||
|
||
printf("== P1 已配对设备(已连者标「已连接」on=1)==\n");
|
||
hdr = find_hdr(rows, n, "已配对设备");
|
||
CHECK(hdr >= 0, "定位已配对 HDR");
|
||
ci = find_act(rows, n, hdr + 1, "Pudding");
|
||
CHECK(ci >= 0 && rows[ci].on == 1 && rows[ci].sub && strstr(rows[ci].sub, "已连接"),
|
||
"已配对 Pudding 标已连接(on=1)");
|
||
ci = find_act(rows, n, hdr + 1, "17OP15");
|
||
CHECK(ci >= 0 && rows[ci].on == 0, "已配对 17OP15 未连(on=0)");
|
||
|
||
printf("== P3 附近设备仅列未配对(已配对者过滤)==\n");
|
||
hdr = find_hdr(rows, n, "附近设备");
|
||
CHECK(hdr >= 0, "定位附近 HDR");
|
||
ci = find_act(rows, n, hdr + 1, "Earbuds");
|
||
CHECK(ci >= 0, "附近列出 Earbuds");
|
||
ci = find_act(rows, n, hdr + 1, "Pudding");
|
||
CHECK(ci < 0, "已配对 Pudding 不在附近列表");
|
||
|
||
printf("== P2 bt_act 命中 → netmgrd 命令 ==\n");
|
||
/* 已连接 Pudding tap → disconnect */
|
||
memset(&c, 0, sizeof c); c.full = 1; fill_state();
|
||
n = bt_rows(&c, rows, SUB_ROWS_MAX);
|
||
hdr = find_hdr(rows, n, "已连接设备");
|
||
ci = find_act(rows, n, hdr + 1, "Pudding");
|
||
g_last_cmd[0] = 0;
|
||
bt_act(&c, ci, rows[ci].act);
|
||
CHECK(strcmp(g_last_cmd, "disconnect:33:DE:2E:21:AD:30") == 0, "已连接 tap → disconnect (实际=%s)", g_last_cmd);
|
||
/* 已配对 17OP15(未连)tap → connect */
|
||
memset(&c, 0, sizeof c); c.full = 1; fill_state();
|
||
n = bt_rows(&c, rows, SUB_ROWS_MAX);
|
||
hdr = find_hdr(rows, n, "已配对设备");
|
||
ci = find_act(rows, n, hdr + 1, "17OP15");
|
||
g_last_cmd[0] = 0;
|
||
bt_act(&c, ci, rows[ci].act);
|
||
CHECK(strcmp(g_last_cmd, "connect:E0:42:6D:79:C5:98") == 0, "已配对未连 tap → connect (实际=%s)", g_last_cmd);
|
||
/* 输出目标 toggle → audio.output_bt 翻转为 1;bt_rows 摘要改输出=蓝牙 */
|
||
memset(&c, 0, sizeof c); c.full = 1; fill_state();
|
||
n = bt_rows(&c, rows, SUB_ROWS_MAX);
|
||
hdr = find_hdr(rows, n, "输出目标");
|
||
ci = find_act(rows, n, hdr + 1, "输出目标");
|
||
bt_act(&c, ci, rows[ci].act);
|
||
CHECK(g_output_bt == 1, "输出目标 tap → output_bt=1 (蓝牙)");
|
||
n = bt_rows(&c, rows, SUB_ROWS_MAX);
|
||
CHECK(rows[0].sub && strstr(rows[0].sub, "输出=蓝牙"), "切蓝牙后 A2DP 摘要=输出=蓝牙");
|
||
/* 编解码器循环:auto → SBC */
|
||
memset(&c, 0, sizeof c); c.full = 1; fill_state();
|
||
n = bt_rows(&c, rows, SUB_ROWS_MAX);
|
||
hdr = find_hdr(rows, n, "音质 / 连接管理");
|
||
ci = find_act(rows, n, hdr + 1, "编解码器");
|
||
bt_act(&c, ci, rows[ci].act);
|
||
CHECK(strcmp(g_codec, "SBC") == 0, "编解码器 auto → SBC (实际=%s)", g_codec);
|
||
|
||
printf("== P4 M47 开关三态(未知 ≠ 已关)==\n");
|
||
int sw = 0;
|
||
g_bt_state = -1; /* 真实态未知 */
|
||
memset(&c, 0, sizeof c); c.full = 1; fill_state();
|
||
CHECK(bt_top_sw(&c, &sw) == 1 && sw == -1, "真实态未知 → top_sw *on=-1 (实际=%d)", sw);
|
||
n = bt_rows(&c, rows, SUB_ROWS_MAX);
|
||
CHECK(row_label(rows, n, "蓝牙状态未知") >= 0, "未知态 → 行内含「蓝牙状态未知」");
|
||
g_bt_state = 0; /* 真实态为关 */
|
||
memset(&c, 0, sizeof c); c.full = 1;
|
||
sw = -2;
|
||
bt_top_sw(&c, &sw);
|
||
CHECK(sw == 0, "真实态关 → top_sw *on=0 (实际=%d)", sw);
|
||
n = bt_rows(&c, rows, SUB_ROWS_MAX);
|
||
CHECK(row_label(rows, n, "蓝牙状态未知") < 0, "真实态已知 → 无未知占位行");
|
||
|
||
printf("== P5 M47 点击收敛:定稿 / 回滚 + 可见错误 ==\n");
|
||
/* 5a 下发成功且真实态已达成 → 立即定稿,无弹窗,settings/状态栏=真实态 */
|
||
g_bt_state = 1; g_onoff_rc = MEOW_OK; g_modal_n = 0; g_bt_en_setting = 0;
|
||
memset(&c, 0, sizeof c);
|
||
unsigned long long t0 = now_ms();
|
||
bt_top_sw_set(&c, 1);
|
||
unsigned long long dt = now_ms() - t0;
|
||
CHECK(g_modal_n == 0, "已达成 → 无错误弹窗 (弹窗数=%d)", g_modal_n);
|
||
CHECK(g_bt_en_setting == 1, "已达成 → settings 定稿为 1");
|
||
CHECK(g_sys.bt_on == 1, "已达成 → 状态栏 g_sys.bt_on=1");
|
||
CHECK(dt < BT_TOGGLE_SETTLE_MS, "已达成 → 不必等满超时 (%llums)", dt);
|
||
|
||
/* 5b 下发失败、真实态仍为开(要求关)→ 回滚到开 + 可见弹窗 */
|
||
g_bt_state = 1; g_onoff_rc = MEOW_ERR_NODEV; g_modal_n = 0; g_bt_en_setting = 1;
|
||
memset(&c, 0, sizeof c);
|
||
bt_top_sw_set(&c, 0);
|
||
CHECK(g_modal_n == 1, "下发失败 → 有可见错误弹窗");
|
||
CHECK(g_bt_en_setting == 1, "下发失败 → settings 回滚为真实态 1 (实际=%d)", g_bt_en_setting);
|
||
CHECK(g_sys.bt_on == 1, "下发失败 → 状态栏仍为真实态 1");
|
||
|
||
/* 5c 下发成功但真实态不收敛(要求开、真实一直关)→ 等满上限后回滚 + 弹窗 */
|
||
g_bt_state = 0; g_onoff_rc = MEOW_OK; g_modal_n = 0; g_bt_en_setting = 1;
|
||
memset(&c, 0, sizeof c);
|
||
t0 = now_ms();
|
||
bt_top_sw_set(&c, 1);
|
||
dt = now_ms() - t0;
|
||
CHECK(g_modal_n == 1, "超时未收敛 → 有可见错误弹窗(%s)", g_modal_line);
|
||
CHECK(g_bt_en_setting == 0, "超时未收敛 → settings 回滚为真实态 0");
|
||
CHECK(g_sys.bt_on == 0, "超时未收敛 → 状态栏=真实态 0");
|
||
CHECK(dt >= BT_TOGGLE_SETTLE_MS, "确实等满了上限 (%llums)", dt);
|
||
|
||
/* 5d 真实态未知 → 等满上限后仍是未知 + 弹窗(绝不把未知显示成成功);
|
||
* settings 保留原意图不动(评审 r1 P2-1:写 0 会把「不知道」污染成「确定已关」,
|
||
* notif_center/settings_page/app_launcher 都把这个键当显示态/自动拉起依据读)。 */
|
||
g_bt_state = -1; g_onoff_rc = MEOW_OK; g_modal_n = 0; g_bt_en_setting = 1;
|
||
memset(&c, 0, sizeof c);
|
||
bt_top_sw_set(&c, 1);
|
||
CHECK(g_modal_n == 1 && strstr(g_modal_line, "无法确认") != NULL,
|
||
"未知 → 弹窗明说无法确认 (%s)", g_modal_line);
|
||
CHECK(c.bt_on == -1, "未知 → 定稿态仍为 -1 (实际=%d)", c.bt_on);
|
||
CHECK(g_bt_en_setting == 1, "未知 → settings 保留原意图 1,不被改写 (实际=%d)", g_bt_en_setting);
|
||
|
||
printf("== P6 M47 收敛矩阵(真实态 × 下发结果 × 探测 × 超时)==\n");
|
||
{
|
||
static const struct {
|
||
int last, want, ok, probe, to; /* 输入 */
|
||
int disp, pend, rb, err; /* 期望输出 */
|
||
const char *what;
|
||
} m[] = {
|
||
{ 1, 1, 1, 1, 0, 1, 0, 0, 0, "真开·下发成功·探测开" },
|
||
{ 1, 0, 1, 0, 0, 0, 0, 0, 0, "真开·下发成功·探测关" },
|
||
{ 1, 0, 1, 1, 1, 1, 0, 1, 1, "真开·下发成功·未收敛(超时)" },
|
||
{ 1, 0, 0, 1, 0, 1, 0, 1, 1, "真开·下发失败·探测仍开" },
|
||
{ 1, 0, 0, -1, 0, -1, 0, 1, 1, "真开·下发失败·探测未知 → 未知(不顶替)" },
|
||
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, "真开·下发失败但探测已关(不误报)" },
|
||
{ 0, 1, 1, 1, 0, 1, 0, 0, 0, "真关·下发成功·探测开" },
|
||
{ 0, 1, 1, 0, 1, 0, 0, 1, 1, "真关·下发成功·未收敛(超时)" },
|
||
{ 0, 1, 0, 0, 0, 0, 0, 1, 1, "真关·下发失败·仍关" },
|
||
{ 1, 0, 1, -1, 0, -1, 1, 1, 0, "真开·下发成功·探测未知(继续等)" },
|
||
{ 1, 0, 1, -1, 1, -1, 0, 1, 1, "真开·下发成功·探测未知(超时)→未知,不用 last_real 顶替" },
|
||
{-1, 1, 1, -1, 1, -1, 0, 1, 1, "未知·下发成功·探测仍未知(超时)" },
|
||
{-1, 1, 1, -1, 0, -1, 1, 1, 0, "未知·下发成功·探测仍未知(继续等)" },
|
||
};
|
||
for (size_t i = 0; i < sizeof m / sizeof m[0]; i++) {
|
||
int d = meow_toggle_display(m[i].want, m[i].last, m[i].ok, m[i].probe, m[i].to);
|
||
int p = meow_toggle_pending(m[i].want, m[i].last, m[i].ok, m[i].probe, m[i].to);
|
||
int r = meow_toggle_rollback(m[i].want, m[i].last, m[i].ok, m[i].probe, m[i].to);
|
||
int e = meow_toggle_error(m[i].want, m[i].last, m[i].ok, m[i].probe, m[i].to);
|
||
CHECK(d == m[i].disp && p == m[i].pend && r == m[i].rb && e == m[i].err,
|
||
"%s → disp=%d pend=%d rb=%d err=%d (期望 %d/%d/%d/%d)",
|
||
m[i].what, d, p, r, e, m[i].disp, m[i].pend, m[i].rb, m[i].err);
|
||
}
|
||
}
|
||
|
||
printf(g_fail ? "\nRESULT: %d FAIL(S)\n" : "\nRESULT: ALL PASS\n", g_fail);
|
||
return g_fail;
|
||
}
|