用户原话:「开关都存在显示问题 我都不能判断它是不是真的开启还是关闭(包括 WiFi 页也是)」。
根因不是「没读到硬件态」,而是「读到/没读到都被拿去当用户意图的装饰」:
- bt_page.c:103 开关初值来自 settings 布尔;:112-114 下发后立刻 g_sys.bt_on = on(显示意图);
:129 c->bt_on = nm_bt_powered() 赋值后全文无人引用(M45 也独立发现)。
- wifi_page.c:40 读不到 settings 时默认 1(显示已开)。
- netmgrd 的 bt/wifi 查询本身把「问不到」塌缩成 0(nm_bt_powered/nm_wifi_connected)。
本单只解「状态可信」,不碰扫描/配对/音频(另有 M45/M46 单)。
1) 真实态三态(-1 未知 / 0 关 / 1 开)
- net.c 新增 net_bt_power_probe()(hciconfig hci0 UP/DOWN;无输出=未知)与
net_wifi_power_probe()(rfkill wlan soft/hard 汇总,回退 wlan0 IFF_UP;读不到=未知)。
- netmgr_ipc.c 新增 nm_bt_power_state()(daemon bt status 的 available/powered 权威,不可达
回退直连探测)与 nm_wifi_power_state()(netmgrd 的 wifi status 只有 wpa_state、不报射频态,
故取本地 rfkill——daemon 的 wifi on/off 改的就是同一份内核状态)。
- 两个子页的 top_sw/rows 只读这两个函数;settings 布尔降级为「意图持久化」,不再决定外观。
- 「读不到默认 1」删除:未知就是未知。
2) 点击 → 待确认 → 真实态定稿
- 纯函数 meow_toggle_{display,pending,rollback,error}(bt_page.c,无 I/O/UI):
只有 probe 确认才算收敛;probe 未知继续等,绝不因 last_real 恰好等于意图就宣布成功。
- 页面:落盘意图 → 下发 → 250ms 轮询(BT 上限 4s、WiFi 上限 5s;hciconfig 0.3~2s、
wifi_up.sh 起 supplicant/DHCP 更慢,故不用 20ms 级忙轮询)→ 真实态定稿;失败/超时把
settings 回滚到已确认真实态 + s_dd_modal 可见报错,不静默、不乐观。
- subpage.c:切换前先画「正在开启/关闭…(等待设备确认)」条(待确认态与稳定态可区分);
切换后回读 top_sw 定稿(取消原「点了就翻面」),live tick 也回读,外部改动驻留期间可见。
3) 第三态呈现(未知 ≠ 已关)
- SDK meow_switch 只有两态:未知时开关停「关」并在左侧画灰色「?」;行内另有
「蓝牙状态未知 / WiFi 开关状态未知」文字说明。选「?」是因为它不占用开关控件本身、
且「关+?」不会被读成「已关」。
4) 顺带修 net.c 三条开关失真(塔追加的 M45 盘点,同一病根)
- s_net_toggle_bt() 补 rfkill soft(与 netmgrd 的 bt on/off 同一语义;开:先 unblock 再 up;
关:先 down 再 block)。此前只发 hciconfig → 「BT 页关过再从头开」会被 rfkill 拦下。
- s_net_sync_from_sys() WiFi 判据从 wlan_connected()(已关联)改为射频态;BT 用三态,
读不到不写(旧写法把「网卡 up 未关联」写成 0,把未知写成已关)。
- rfkill 同类多节点(本机 2 wlan + 2 bluetooth)按「任一 block 即 block」读、全部写入。
测试(`bash patch_v2/meow_launcherd/build_launcher.sh test`,新增 test 入口)
- test_bt_page.c:P4 三态与未知占位、P5 收敛四例(达成/下发失败/超时/未知)、P6 收敛矩阵
12 组(真实开/关 × 下发成功/失败/超时 × probe 未知),全 PASS(64 条断言)。
- test_netmgr_ipc.c:新增 nm_bt_power_state 解析(powered=1/0、available=0、缺字段→-1)、
nm_wifi_power_state 透传、不可达回退=未知、wifi_rows 未知态占位;并修掉两处既有测试缺陷
(行内 UI 桩缺失导致本文件此前根本链不过;直连连接桩统一返回 "0\n" 导致等满 10s 必失败)。
- 交叉编译 0 warning(-Wall -Wextra,逐文件单独编 + 整链);launcherd 899388 B
md5 1c88661a9410ac1a4139304362281cdd,同 commit 同步 scripts/build/fw_content.conf 钉值。
真机(10.58.215.50,仅只读自检,未替换 /usr/bin/meow_launcherd、未刷机)
- 二进制放 /usr/data/mnt/sd_0/MeowOS/tmp/ 跑 --sysdump:exit 0,输出 wifi_on=1 / bt_seen=1
bt_on=0,与设备现状一致(rfkill 全 0 + wlan0 up + hci0 DOWN + netmgrd powered=0)。
- 卡上 du -s /usr/data/mnt/sd_0/Music 前后恒 199164544;临时文件已删;settings 未改。
- launcherd 在 squashfs 里 → 本改动需刷机生效。
已知不在本单:输出目标只写 settings(audiod 仅启动读一次)→ M45 finding / M46;
netmgrd 侧四处失真(未知当关、up 与 unblock 顺序、rfkill 只写首节点、wifi status 无射频字段)
已报 finding;设置主页/通知中心的开关显示仍是 settings(另立单)。
358 lines
18 KiB
C
358 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 真实态未知 → 等满上限后仍是未知 + 弹窗(绝不把未知显示成成功) */
|
||
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 == 0, "未知 → settings 不留意图值 1(回滚为 0)");
|
||
|
||
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, 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, "真开·下发成功·探测未知(超时)" },
|
||
{-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;
|
||
}
|