feat(powermgr): 期3 policy 生效 + 标定工具 + 模型单键 setter + events 说明
- policy:IPC 0xD2/0xD3 GET/SET(SET 动作级鉴权=root+系统身份,SO_PEERCRED→pid→exe→classify,同 svc_power) + CLI `meow_power policy show|set`。策略项 profile 四档(performance/balanced/powersave/ultra) + 低电阈值(power.low_pct/critical_pct 读 settings.conf) + 亮度上限(power.brightness_cap) + 息屏上限(power.screen_timeout_cap)。**默认全关**(profile=balanced 无操作=现状, cap=0 不生效, 只能用户手动开)。生效方式: powermgr 不直接写系统——只写 settings.conf power.* 键(原子), launcher/sysui 期4 消费; 本服务绝不 poke 背光/息屏/wlan0/bt。低电/极低阈值改从 settings.conf 读, 事件检测即时生效。 - calibrate:`meow_power calibrate start <场景>|stop|list`——守护内 2s 节拍采样 电流(mp2731 current_now)+亮度+CPU+wifi/bt 状态+电量%变化,写 TF MeowOS/logs/power_calib_<epoch>.csv (无卡降级 /tmp,逐行 fflush),用户实验中 start/stop 两命令即可。 - model:`meow_power model set <key> <mw>` 读改写单键+原子写(tmp+rename),守护每拍重载 conf。 - events:App 前后台切换——launcher MSG_ACT_FOCUS 为单向 app→launcher 上报,powermgr 侧无导出点 可接,诚实标注「期4 接 launcher」扩展,本期不编造该事件(延续期2 口径)。 - 验证:宿主+qemu 全量 76/76 PASS(期1/2 全过+新增 policy/calibrate/model-set 用例),mipsel 静态交叉 0 warning,二进制 151516B(<200KB 阈值)。md5 1ef2f8afc07acc7aa09dee9f7149aba8。
This commit is contained in:
1 parent
8a43f17653
commit
5663acfbb9
4 files changed
+875
-34
No files matched your search
Binary file not shown.
@@ -12,6 +12,22 @@
|
||||
* 模型系数路径迁移到 /data/meow/powermgr/power_model.conf(=/usr/data symlink,组件持久化目录定案)。
|
||||
* IPC 新增 MSG_PM_HISTORY/D5 + MSG_PM_EVENTS/D7(号段 0x00D0-0x00D7 剩余位;数字本文件局部定,
|
||||
* meow_ipc.h 仅注释保留——只动本目录,不碰 meow_launcherd/meow_ipc.h)。
|
||||
* 期 3 新增(本文件,POWER_MANAGER_DESIGN §9 期 3):
|
||||
* ① policy 生效(期 2 桩落地):IPC MSG_PM_POLICY_GET/D2 + MSG_PM_POLICY_SET/D3 + CLI
|
||||
* `meow_power policy show|set`。策略项:profile 四档(performance/balanced/powersave/ultra)
|
||||
* + 低电阈值(power.low_pct/critical_pct 读 settings.conf)+ 亮度上限(power.brightness_cap)
|
||||
* + 息屏上限(power.screen_timeout_cap)。**默认全关**(profile=balanced 无操作=现状,cap=0 不生效),
|
||||
* 用户手动开(§附 3 只能用户手动开)。**生效方式:powermgr 不直接写系统**——policy 只写 settings.conf
|
||||
* `power.*` 键(launcher/sysui 期 4 消费;本服务只报告 effective 值,绝不 poke sysfs/背光/息屏)。
|
||||
* ② 标定工具(为用户的标定实验备好,§7 项 7/8):`meow_power calibrate start <场景>|stop|list`——
|
||||
* 守护进程内 2s 节拍采样 电流(mp2731 current_now)+亮度+CPU+wifi/bt 状态+电量%变化,写 TF
|
||||
* MeowOS/logs/power_calib_<epoch>.csv(无卡降级 /tmp);用户实验只需在真机跑 start/stop。
|
||||
* ③ 模型 conf 单键 setter:`meow_power model set <key> <mw>`(读改写单键 + 原子写 tmp+rename)。
|
||||
* ④ events 扩展:App 前后台切换——launcher MSG_ACT_FOCUS 为 app→launcher 单向上报,powermgr 侧无
|
||||
* 导出点可接,**诚实标注**:记录「期 4 接 launcher」扩展,本期不编造该事件(延续期 2 口径)。
|
||||
* ⑤ 动作级鉴权(POLICY_SET/CALIB_*):SO_PEERCRED→pid→exe→classify(同 svc_power),
|
||||
* 动作级须 root + 系统身份(os.meow 前缀或受信系统二进制);读级(POLICY_GET)任意调用者。
|
||||
* MEOW_PM_AUTH_TEST=1 仅宿主测试关闭动作级鉴权(生产绝不设)。
|
||||
*
|
||||
* 单一二进制双形态(同 svc_power):
|
||||
* - svcd 空参拉起(板载 services.d/meow_powermgr.conf,trigger=boot/restart=on-fail)→
|
||||
@@ -61,12 +77,22 @@
|
||||
/* 期 2 消息号:meow_ipc.h 仅以注释保留(0x00D4-0x00D7),本文件局部定义,避免动 meow_launcherd/。
|
||||
* 响应帧复用 m_pm_result_t(code+subset+json_len+json),type 区分 HISTORY_RESULT/EVENTS_RESULT。 */
|
||||
#ifndef MSG_PM_HISTORY
|
||||
#define MSG_PM_HISTORY 0x00D4 /* ↑ 历史采样查询(req: u32 count=N,0=默认) */
|
||||
#define MSG_PM_POLICY_GET 0x00D2 /* ↑ 策略读(base 空负载 → 回 MSG_PM_RESULT policy JSON;读级任意调用者) */
|
||||
#define MSG_PM_POLICY_SET 0x00D3 /* ↑ 策略写(req: m_pm_policy_set_t;动作级=root+系统身份,期 3) */
|
||||
#define MSG_PM_HISTORY 0x00D4 /* ↑ 历史采样查询(req: u32 count=N,0=默认) */
|
||||
#define MSG_PM_HISTORY_RESULT 0x00D5 /* ↓ 历史 JSON(采样点清单 + 窗口汇总) */
|
||||
#define MSG_PM_EVENTS 0x00D6 /* ↑ 事件流查询(req: u32 count=N,0=默认) */
|
||||
#define MSG_PM_EVENTS_RESULT 0x00D7 /* ↓ 事件 JSON 清单 */
|
||||
#define MSG_PM_EVENTS 0x00D6 /* ↑ 事件流查询(req: u32 count=N,0=默认) */
|
||||
#define MSG_PM_EVENTS_RESULT 0x00D7 /* ↓ 事件 JSON 清单 */
|
||||
#define MSG_PM_CALIB_START 0x00D8 /* ↑ 标定启动(req: 场景名 NUL 串;动作级;0x00D8 空闲位,期 3) */
|
||||
#define MSG_PM_CALIB_STOP 0x00D9 /* ↑ 标定停止(req 空;动作级)→ 回 MSG_PM_RESULT calib JSON */
|
||||
#endif
|
||||
|
||||
/* 期 3 · 策略写负载(item="profile" / "low-pct" / "crit-pct" / "bri-cap" / "scr-cap")。 */
|
||||
typedef struct m_pm_policy_set {
|
||||
char item[16]; /* 策略项名 */
|
||||
char val[32]; /* 值(profile 枚举字符串或数字) */
|
||||
} m_pm_policy_set_t; /* 48 B */
|
||||
|
||||
/* ---------------- 路径(全部可经 env 覆盖,宿主/qemu 桩测试用) ---------------- */
|
||||
static char g_ps_dir[128] = "/sys/class/power_supply"; /* MEOW_PS_DIR */
|
||||
static char g_bl_dir[128] = "/sys/class/backlight/backlight_pwm0"; /* MEOW_BL_DIR */
|
||||
@@ -80,6 +106,10 @@ static char g_model_conf[160] = "/usr/data/meow/powermgr/power_model.conf"; /* M
|
||||
(= /data/meow/powermgr/power_model.conf,/data=/usr/data symlink,
|
||||
组件持久化目录定案 SPECIAL_REF §1.1;存量平铺件不动) */
|
||||
static char g_audio_sock[128] = "/usr/data/meow_audio.sock"; /* MEOW_AUDIO_SOCK */
|
||||
static char g_settings[160] = "/usr/data/meow/settings.conf"; /* MEOW_SETTINGS_FILE
|
||||
(policy power.* 键落点;与 svc_power/launcher 同单点事实源) */
|
||||
static char g_log_dir[192] = "/usr/data/mnt/sd_0/MeowOS/logs"; /* MEOW_LOG_DIR(标定 CSV 落点,
|
||||
无卡降级 /tmp;同 netmgrd/audiod 日志根) */
|
||||
static const char *g_sock = "/usr/data/meow_pm.sock"; /* MEOW_PM_SOCK/--sock */
|
||||
|
||||
#define SAMPLE_SEC_DEF 30 /* 电池采样/状态刷新节拍(秒;MEOW_PM_SAMPLE_SEC 覆盖,测试用) */
|
||||
@@ -134,6 +164,166 @@ static void ps_path(char *buf, size_t cap, const char *node)
|
||||
snprintf(buf, cap, "%s/%s", g_ps_dir, node);
|
||||
}
|
||||
|
||||
/* ---------------- settings.conf 读写(policy power.* 键;单点事实源与 svc_power/launcher 共用) ----------------
|
||||
* 键格式:key=value('=' 切分,key 去首尾空白精确匹配,避免 power.low_pct 误配 power.low_pct_xx
|
||||
* 或 audio.volume 误配 audio.volume_max——同 svc_power settings_get_int_trim 解析口径)。
|
||||
* 写:读原文件→逐字保留非目标行→命中替换/末尾追加→**原子写**(tmp+rename,避免半截文件),
|
||||
* 比 svc_power 的 O_TRUNC 更稳(settings 与 launcher 同写,防写一半被读到)。 */
|
||||
static int settings_get_str(const char *key, char *out, int cap, const char *def)
|
||||
{
|
||||
FILE *f = fopen(g_settings, "r");
|
||||
if (!f) { snprintf(out, cap, "%s", def ? def : ""); return 1; }
|
||||
size_t kl = strlen(key);
|
||||
char line[256];
|
||||
int found = 0;
|
||||
while (fgets(line, sizeof line, f)) {
|
||||
char *eq = strchr(line, '=');
|
||||
if (!eq) continue;
|
||||
char *k = line;
|
||||
while (*k == ' ' || *k == '\t') k++;
|
||||
long n = (long)(eq - k);
|
||||
while (n > 0 && (k[n - 1] == ' ' || k[n - 1] == '\t')) n--;
|
||||
if ((size_t)n != kl || strncmp(k, key, kl) != 0) continue;
|
||||
char *v = eq + 1;
|
||||
while (*v == ' ' || *v == '\t') v++;
|
||||
int i = 0;
|
||||
while (v[i] && v[i] != '\n' && v[i] != '\r' && v[i] != ' ' && v[i] != '\t' && i < cap - 1) { out[i] = v[i]; i++; }
|
||||
out[i] = 0;
|
||||
found = 1;
|
||||
break;
|
||||
}
|
||||
fclose(f);
|
||||
if (!found) { snprintf(out, cap, "%s", def ? def : ""); return 1; }
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 读 settings.conf 某键(找到整数写入 *out)。返回 0=命中。 */
|
||||
static int settings_get_int_x(const char *key, int *out)
|
||||
{
|
||||
char buf[32];
|
||||
if (settings_get_str(key, buf, sizeof buf, "") != 0 || !buf[0]) return 1;
|
||||
*out = atoi(buf);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 原子写 settings.conf(tmp+rename)。text 为完整新内容。成功 0。 */
|
||||
static int settings_write_atomic(const char *text)
|
||||
{
|
||||
char tmp[192];
|
||||
snprintf(tmp, sizeof tmp, "%s.tmp", g_settings);
|
||||
int fd = open(tmp, O_WRONLY | O_CREAT | O_TRUNC, 0644);
|
||||
if (fd < 0) return -1;
|
||||
size_t tl = strlen(text);
|
||||
ssize_t w = write(fd, text, tl);
|
||||
close(fd);
|
||||
if (w < 0 || (size_t)w != tl) { unlink(tmp); return -1; }
|
||||
if (rename(tmp, g_settings) < 0) { unlink(tmp); return -1; }
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 读 settings.conf 全文(≤2KB,settings 为 KB 级小文件)。返回字节数;无文件返回 0。 */
|
||||
static size_t settings_read_text(char *buf, size_t cap)
|
||||
{
|
||||
FILE *f = fopen(g_settings, "r");
|
||||
if (!f) { buf[0] = 0; return 0; }
|
||||
size_t n = 0;
|
||||
int c;
|
||||
while (n < cap - 1 && (c = fgetc(f)) != EOF) buf[n++] = (char)c;
|
||||
fclose(f);
|
||||
buf[n] = 0;
|
||||
return n;
|
||||
}
|
||||
|
||||
/* 更新/插入 settings.conf 某键(值字符串;保留头部注释与其余键逐字,原子写)。 */
|
||||
static int settings_set_str(const char *key, const char *val)
|
||||
{
|
||||
char txt[2048], out[2048];
|
||||
size_t n = settings_read_text(txt, sizeof txt);
|
||||
size_t kl = strlen(key), o = 0;
|
||||
int found = 0;
|
||||
size_t start = 0;
|
||||
while (start < n) {
|
||||
size_t i = 0;
|
||||
while (start + i < n && txt[start + i] != '\n') i++;
|
||||
char line[256];
|
||||
if (i >= sizeof line - 1) i = sizeof line - 2;
|
||||
memcpy(line, txt + start, i); line[i] = 0;
|
||||
start += i;
|
||||
if (start < n && txt[start] == '\n') start++;
|
||||
char *q = line;
|
||||
while (*q == ' ' || *q == '\t') q++;
|
||||
int is_target = (strncmp(q, key, kl) == 0 && (q[kl] == '=' ));
|
||||
if (is_target) {
|
||||
int l = snprintf(out + o, sizeof out - o, "%s=%s\n", key, val);
|
||||
if (l > 0 && o + (size_t)l < sizeof out) o += (size_t)l;
|
||||
found = 1;
|
||||
continue; /* 跳过旧行 */
|
||||
}
|
||||
int l = snprintf(out + o, sizeof out - o, "%s\n", line);
|
||||
if (l > 0 && o + (size_t)l < sizeof out) o += (size_t)l;
|
||||
}
|
||||
if (o > 0 && out[o - 1] == '\n') o--; /* 去掉尾多余换行(避免双空行) */
|
||||
if (!found) {
|
||||
int l = snprintf(out + o, sizeof out - o, "%s%s=%s\n", o ? "\n" : "", key, val);
|
||||
if (l > 0 && o + (size_t)l < sizeof out) o += (size_t)l;
|
||||
}
|
||||
out[o] = 0;
|
||||
return settings_write_atomic(out);
|
||||
}
|
||||
|
||||
static int settings_set_int(const char *key, int val)
|
||||
{
|
||||
char v[32];
|
||||
snprintf(v, sizeof v, "%d", val);
|
||||
return settings_set_str(key, v);
|
||||
}
|
||||
|
||||
/* ---------------- 策略(policy)模型:profile 四档 + 低电阈值 + 亮度/息屏上限 ----------------
|
||||
* 默认全关(POWER_MANAGER_DESIGN §9 期 3):profile=balanced(无操作=现状),cap=0(不生效)。
|
||||
* 用户手动开(§附 3)。**powermgr 不直接写系统**:policy set 只写 settings.conf `power.*` 键,
|
||||
* launcher/sysui 期 4 消费;本服务只算 effective 值用于报告,绝不 poke 背光/息屏/wlan0/bt。 */
|
||||
typedef struct {
|
||||
char profile[16]; /* performance/balanced/powersave/ultra */
|
||||
int low_pct; /* power.low_pct(默认 15) */
|
||||
int crit_pct; /* power.critical_pct(默认 5) */
|
||||
int bri_cap; /* power.brightness_cap(0=off;量程 0-101 实测) */
|
||||
int scr_cap; /* power.screen_timeout_cap(0=off;秒) */
|
||||
} pm_policy_t;
|
||||
|
||||
static void policy_defaults(pm_policy_t *p)
|
||||
{
|
||||
snprintf(p->profile, sizeof p->profile, "balanced");
|
||||
p->low_pct = 15; p->crit_pct = 5; p->bri_cap = 0; p->scr_cap = 0;
|
||||
}
|
||||
|
||||
static const char *const k_policy_profiles[] = { "performance", "balanced", "powersave", "ultra", NULL };
|
||||
|
||||
/* 读 settings.conf 的 power.* 键(键缺失/无文件 → 默认=全关)。 */
|
||||
static void policy_load(pm_policy_t *p)
|
||||
{
|
||||
policy_defaults(p);
|
||||
settings_get_str("power.profile", p->profile, sizeof p->profile, "balanced");
|
||||
if (settings_get_int_x("power.low_pct", &p->low_pct)) p->low_pct = 15;
|
||||
if (settings_get_int_x("power.critical_pct", &p->crit_pct)) p->crit_pct = 5;
|
||||
if (settings_get_int_x("power.brightness_cap", &p->bri_cap)) p->bri_cap = 0;
|
||||
if (settings_get_int_x("power.screen_timeout_cap", &p->scr_cap)) p->scr_cap = 0;
|
||||
/* 背光量程 0-101(POWER_HW_FACTS 项 6);cap 越界 clamp,防手误写 255 造成无效上限。
|
||||
* profile 单独存(供期 4 launcher 依据 §5.1 表计算 min(用户值,档位值));本服务不在此推算
|
||||
* 档位隐含数值——设计 §5.1 的 180/120 是按 0-255 假定写的旧值,0-101 实测量程下推算会失真,
|
||||
* 诚实处理:只透传用户显式 cap,档位映射留给 launcher 期 4 定案后落地。 */
|
||||
if (p->bri_cap < 0) p->bri_cap = 0;
|
||||
if (p->bri_cap > 101) p->bri_cap = 101;
|
||||
if (p->scr_cap < 0) p->scr_cap = 0;
|
||||
if (p->low_pct < 1) p->low_pct = 1;
|
||||
if (p->crit_pct < 1) p->crit_pct = 1;
|
||||
}
|
||||
|
||||
/* 是否已有「被手动开启」的策略(非默认全关态):profile!=balanced 或任一 cap>0。 */
|
||||
static int policy_enacted(const pm_policy_t *p)
|
||||
{
|
||||
return (strcmp(p->profile, "balanced") != 0) || p->bri_cap > 0 || p->scr_cap > 0;
|
||||
}
|
||||
|
||||
/* ---------------- 权威充电链(0-diff 照抄 svc_power charging_status_str 口径) ----------------
|
||||
* mp2731-charger/status(Charging/Full=充电中)为权威;不可读回退 fusb302-typec-source/online==1
|
||||
* (USB-C PD 控制器插线检测);再回退旧逻辑 battery/status==Charging || ac/online==1。 */
|
||||
@@ -277,6 +467,90 @@ static int model_load(pm_model_t *m)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 模型键是否在已知集合(CLI `model set` 校验用,拒绝未知键防写坏 conf)。 */
|
||||
static int model_key_is_known(const char *key)
|
||||
{
|
||||
static const char *const keys[] = {
|
||||
"nominal_mah", "bl_low_mw", "bl_mid_mw", "bl_high_mw", "panel_on_mw",
|
||||
"wifi_unassoc_mw", "wifi_idle_mw", "wifi_tx_mw", "bt_up_mw", "bt_conn_mw",
|
||||
"audio_idle_mw", "audio_play_mw", "cpu_full_mw", "baseline_mw", NULL
|
||||
};
|
||||
for (int i = 0; keys[i]; i++) if (!strcmp(key, keys[i])) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int atomic_write_path(const char *path, const char *text); /* fwd:model_conf_set_key 用它 */
|
||||
|
||||
/* 模型单键 setter:读改写单键 + **原子写**(tmp+rename,同 netmgrd 口径)。成功 0。
|
||||
* 保留其它键与注释行逐字;conf 缺失 → 生成 `key=value`(model_load 缺失键仍走内置默认)。 */
|
||||
static int model_conf_set_key(const char *key, int val)
|
||||
{
|
||||
char txt[2048] = "", out[2048];
|
||||
size_t n = 0;
|
||||
FILE *f = fopen(g_model_conf, "r");
|
||||
if (f) {
|
||||
size_t r = 0;
|
||||
int c;
|
||||
while (r < sizeof txt - 1 && (c = fgetc(f)) != EOF) txt[r++] = (char)c;
|
||||
txt[r] = 0;
|
||||
n = r;
|
||||
fclose(f);
|
||||
}
|
||||
if (!model_key_is_known(key)) return -1;
|
||||
size_t kl = strlen(key), o = 0;
|
||||
if (n == 0) {
|
||||
/* conf 缺失 → 生成注释头 + 键(其余键 model_load 走内置默认) */
|
||||
int l = snprintf(out, sizeof out, "# meow_powermgr 经验功耗模型(POWER_MANAGER_DESIGN §3.2)\n"
|
||||
"# 系数待真机标定(POWER_HW_FACTS §1 项 7/8)\n%s=%d\n", key, val);
|
||||
if (l > 0) o = (size_t)l;
|
||||
} else {
|
||||
int found = 0;
|
||||
size_t start = 0;
|
||||
while (start < n) {
|
||||
size_t i = 0;
|
||||
while (start + i < n && txt[start + i] != '\n') i++;
|
||||
char line[256];
|
||||
if (i >= sizeof line - 1) i = sizeof line - 2;
|
||||
memcpy(line, txt + start, i); line[i] = 0;
|
||||
start += i;
|
||||
if (start < n && txt[start] == '\n') start++;
|
||||
char *q = line;
|
||||
while (*q == ' ' || *q == '\t') q++;
|
||||
int is_target = (strncmp(q, key, kl) == 0 && q[kl] == '=');
|
||||
if (is_target) {
|
||||
int l = snprintf(out + o, sizeof out - o, "%s=%d\n", key, val);
|
||||
if (l > 0 && o + (size_t)l < sizeof out) o += (size_t)l;
|
||||
found = 1;
|
||||
continue;
|
||||
}
|
||||
int l = snprintf(out + o, sizeof out - o, "%s\n", line);
|
||||
if (l > 0 && o + (size_t)l < sizeof out) o += (size_t)l;
|
||||
}
|
||||
if (o > 0 && out[o - 1] == '\n') o--;
|
||||
if (!found) {
|
||||
int l = snprintf(out + o, sizeof out - o, "\n%s=%d\n", key, val);
|
||||
if (l > 0 && o + (size_t)l < sizeof out) o += (size_t)l;
|
||||
}
|
||||
}
|
||||
out[o] = 0;
|
||||
return atomic_write_path(g_model_conf, out);
|
||||
}
|
||||
|
||||
/* 通用原子写(写 path.tmp → rename(path);同 netmgrd 口径,防半截配置)。成功 0。 */
|
||||
static int atomic_write_path(const char *path, const char *text)
|
||||
{
|
||||
char tmp[256];
|
||||
snprintf(tmp, sizeof tmp, "%s.tmp", path);
|
||||
int fd = open(tmp, O_WRONLY | O_CREAT | O_TRUNC, 0644);
|
||||
if (fd < 0) return -1;
|
||||
size_t tl = strlen(text);
|
||||
ssize_t w = write(fd, text, tl);
|
||||
close(fd);
|
||||
if (w < 0 || (size_t)w != tl) { unlink(tmp); return -1; }
|
||||
if (rename(tmp, path) < 0) { unlink(tmp); return -1; }
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ---------------- 采样环(守护进程;容量差分速率 → 续航估算 §3.1/§3.3 derived;
|
||||
* 期 2 同时服务 history 采样点:pct/est_mw/充电态/屏幕态/亮度,内存环形不落盘,重启清零) ---------------- */
|
||||
typedef struct {
|
||||
@@ -707,10 +981,68 @@ static void build_json(uint32_t subset, int full, const batt_t *b,
|
||||
jadd(&w, "}");
|
||||
}
|
||||
}
|
||||
if (subset & PM_SUBSET_POLICY) jadd(&w, ",\"policy\":null"); /* 期 3 桩 */
|
||||
if (subset & PM_SUBSET_POLICY) {
|
||||
/* 期 3:策略快照(读 settings.conf power.* 键)。powermgr 只读+报告 effective,绝不写系统。 */
|
||||
pm_policy_t pol;
|
||||
policy_load(&pol);
|
||||
jadd(&w, ",\"policy\":{");
|
||||
jadd(&w, "\"profile\":\"%s\",", pol.profile);
|
||||
jadd(&w, "\"low_pct\":%d,\"critical_pct\":%d,", pol.low_pct, pol.crit_pct);
|
||||
jadd(&w, "\"brightness_cap\":%d,\"screen_timeout_cap\":%d,", pol.bri_cap, pol.scr_cap);
|
||||
jadd(&w, "\"enacted\":%d,", policy_enacted(&pol) ? 1 : 0);
|
||||
jadd(&w, "\"note\":\"powermgr 只写 power.* 键,launcher/sysui 期4消费;本服务不直接写系统/绝不poke背光息屏\"}");
|
||||
}
|
||||
jadd(&w, "}");
|
||||
}
|
||||
|
||||
/* 策略 JSON(IPC GET 响应 / CLI policy show 共用;纯读 settings,无副作用)。 */
|
||||
static void build_policy_json(const pm_policy_t *p, char *out, size_t cap)
|
||||
{
|
||||
jw_t w = { out, cap, 0 };
|
||||
out[0] = 0;
|
||||
jadd(&w, "{\"kind\":\"policy\",\"profile\":\"%s\",\"low_pct\":%d,\"critical_pct\":%d,",
|
||||
p->profile, p->low_pct, p->crit_pct);
|
||||
jadd(&w, "\"brightness_cap\":%d,\"screen_timeout_cap\":%d,\"enacted\":%d,",
|
||||
p->bri_cap, p->scr_cap, policy_enacted(p) ? 1 : 0);
|
||||
jadd(&w, "\"note\":\"powermgr 只写 power.* 键;launcher/sysui 期4消费生效;本服务不直接写系统\"}");
|
||||
}
|
||||
|
||||
/* 应用一条 policy set(item+val → 写 settings.conf power.* 键,原子)。成功 0;失败写 why。 */
|
||||
static int policy_set_item(const char *item, const char *val, char *why, int cap)
|
||||
{
|
||||
if (!item || !val) { snprintf(why, cap, "缺少 item/val"); return -1; }
|
||||
if (!strcmp(item, "profile")) {
|
||||
for (int i = 0; k_policy_profiles[i]; i++) {
|
||||
if (!strcmp(k_policy_profiles[i], val)) {
|
||||
if (settings_set_str("power.profile", val) != 0) { snprintf(why, cap, "写 settings 失败"); return -1; }
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
snprintf(why, cap, "profile 须为 %s/%s/%s/%s", k_policy_profiles[0], k_policy_profiles[1],
|
||||
k_policy_profiles[2], k_policy_profiles[3]);
|
||||
return -1;
|
||||
}
|
||||
int v = atoi(val);
|
||||
if (!strcmp(item, "low-pct")) {
|
||||
if (v < 1 || v > 99) { snprintf(why, cap, "low-pct 须 1-99"); return -1; }
|
||||
return settings_set_int("power.low_pct", v);
|
||||
}
|
||||
if (!strcmp(item, "crit-pct")) {
|
||||
if (v < 1 || v > 99) { snprintf(why, cap, "crit-pct 须 1-99"); return -1; }
|
||||
return settings_set_int("power.critical_pct", v);
|
||||
}
|
||||
if (!strcmp(item, "bri-cap")) {
|
||||
if (v < 0 || v > 101) { snprintf(why, cap, "bri-cap 须 0-101(背光实测量程,0=不生效)"); return -1; }
|
||||
return settings_set_int("power.brightness_cap", v);
|
||||
}
|
||||
if (!strcmp(item, "scr-cap")) {
|
||||
if (v < 0) { snprintf(why, cap, "scr-cap 须 ≥0 秒(0=不生效)"); return -1; }
|
||||
return settings_set_int("power.screen_timeout_cap", v);
|
||||
}
|
||||
snprintf(why, cap, "未知 item(profile|low-pct|crit-pct|bri-cap|scr-cap)");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* ---------------- IPC 帧读写(同 svc_power 约定,全小端) ---------------- */
|
||||
static int x_read_full(int fd, uint8_t *buf, size_t n)
|
||||
{
|
||||
@@ -793,6 +1125,22 @@ static pm_model_t g_model;
|
||||
/* 上一拍状态(事件检测用;-1=首拍未初始化,跳过跃迁判定) */
|
||||
static int g_prev_charging = -1, g_prev_pct = -1, g_prev_screen = -1, g_prev_bri_pct = -1;
|
||||
|
||||
/* ---------------- 标定(calibrate)会话态(期 3;守护进程内 2s 节拍,用户跑 start/stop) ----------------
|
||||
* 为用户标定实验(§7 项 7/8:纯亮屏/息屏待机/放音乐/放视频四类场景)采集样本流:
|
||||
* ts,cap_pct,vol_mv,current_ua,charge_mw,brightness_pct,cpu_util_pct,wifi_state,bt_state,scene
|
||||
* 写 TF MeowOS/logs/power_calib_<epoch>.csv(无卡降级 /tmp)。2 行一节拍逐行 fflush(突发掉电不丢)。 */
|
||||
#define CALIB_SEC 2 /* 标定采样节拍(秒) */
|
||||
#define CALIB_SCENE_MAX 48 /* 场景名上限(UTF-8 字节) */
|
||||
static int g_calib_active = 0;
|
||||
static char g_calib_scene[CALIB_SCENE_MAX];
|
||||
static FILE *g_calib_fp = NULL;
|
||||
static char g_calib_csv[384];
|
||||
static long g_calib_rows = 0;
|
||||
static long g_calib_first_pct = -1, g_calib_last_pct = -1;
|
||||
static long g_calib_start_mono = 0;
|
||||
static long g_calib_cpu_pt = -1, g_calib_cpu_pi = 0; /* 标定专用 CPU 差分基线(与 history 无关) */
|
||||
static long g_calib_prx = -1, g_calib_ptx = 0; /* 标定专用 netdev 增量基线(判 wifi tx 档) */
|
||||
|
||||
static int boot_mode_is_meow(void)
|
||||
{
|
||||
char buf[16] = "";
|
||||
@@ -802,12 +1150,12 @@ static int boot_mode_is_meow(void)
|
||||
|
||||
/* ---------------- 电源事件流(守护进程;内存环形,重启清零,不落盘) ----------------
|
||||
* 可检测:① 充电器插拔(charging 位翻转)② 低电阈值穿越(低 15%/极低 5%/回到 20% 恢复,§5.2 默认)
|
||||
* ③ 屏幕亮灭 ④ 背光跳变(≥30% 点)。⚠ App 前后台切换:期 2 无可轮询的 fg/bg 导出点(launcher 对外
|
||||
* 无 App 前台态;/tmp/meow_*_active 是 watchdog 让位的行为标记而非 App 前后台),**诚实标注不支持**,
|
||||
* 不编造事件——留期 3 接 launcher MSG_ACT_FOCUS 后再扩。 */
|
||||
#define EV_LOW_PCT 15 /* 低电阈值(§5.2 power.low_pct 默认 15,期 3 接 settings) */
|
||||
#define EV_CRIT_PCT 5 /* 极低电阈值(§5.2 power.critical_pct 默认 5) */
|
||||
#define EV_LOW_REC 20 /* 恢复阈值(low+5;电量回到 ≥20% 触发恢复事件) */
|
||||
* ③ 屏幕亮灭 ④ 背光跳变(≥30% 点)。⚠ App 前后台切换:launcher MSG_ACT_FOCUS 为 app→launcher 单向上报,
|
||||
* powermgr 侧**无导出点可接**(launcher 未对外提供当前顶层 activity 查询;/tmp/meow_*_active 是 watchdog
|
||||
* 让位的行为标记而非 App 前后台),**诚实标注**:记录「期 4 接 launcher」扩展,本期不编造该事件(延续期 2 口径)。 */
|
||||
#define EV_LOW_PCT 15 /* 低电阈值默认(期 3 改由 settings power.low_pct 覆盖,见 detect_events) */
|
||||
#define EV_CRIT_PCT 5 /* 极低电阈值默认(期 3 改由 settings power.critical_pct 覆盖) */
|
||||
#define EV_LOW_REC 20 /* 恢复阈值默认(=low+5) */
|
||||
#define EV_BRI_JUMP 30 /* 背光跳变阈值(百分点) */
|
||||
|
||||
typedef struct {
|
||||
@@ -836,6 +1184,13 @@ static void ev_push(int type, int arg, int pct, const char *fmt, ...)
|
||||
|
||||
static void detect_events(const batt_t *b, const est_t *e)
|
||||
{
|
||||
/* 期 3:低电阈值读 settings power.low_pct/critical_pct(§5.2;键缺失 → 默认 15/5,恢复=low+5)。 */
|
||||
pm_policy_t pol;
|
||||
policy_load(&pol);
|
||||
int low = pol.low_pct, crit = pol.crit_pct, rec = pol.low_pct + 5;
|
||||
if (low < 1) low = EV_LOW_PCT;
|
||||
if (crit < 1) crit = EV_CRIT_PCT;
|
||||
if (rec <= crit) rec = crit + 1;
|
||||
/* ① 充电器插拔 */
|
||||
if (g_prev_charging >= 0 && g_prev_charging != b->charging) {
|
||||
if (b->charging) ev_push(0, 1, b->pct, "插上充电器,开始充电 (state=%s)", b->state);
|
||||
@@ -844,13 +1199,13 @@ static void detect_events(const batt_t *b, const est_t *e)
|
||||
/* ② 低电阈值(放电下降沿触发;充电回阈值恢复) */
|
||||
if (b->pct >= 0 && g_prev_pct >= 0) {
|
||||
if (!b->charging) {
|
||||
if (g_prev_pct >= EV_LOW_PCT && b->pct < EV_LOW_PCT)
|
||||
ev_push(1, 0, b->pct, "电量低 %d%%(阈值 %d%%)", b->pct, EV_LOW_PCT);
|
||||
if (g_prev_pct >= EV_CRIT_PCT && b->pct < EV_CRIT_PCT)
|
||||
ev_push(1, 1, b->pct, "电量极低 %d%%(阈值 %d%%)", b->pct, EV_CRIT_PCT);
|
||||
if (g_prev_pct >= low && b->pct < low)
|
||||
ev_push(1, 0, b->pct, "电量低 %d%%(阈值 %d%%)", b->pct, low);
|
||||
if (g_prev_pct >= crit && b->pct < crit)
|
||||
ev_push(1, 1, b->pct, "电量极低 %d%%(阈值 %d%%)", b->pct, crit);
|
||||
}
|
||||
if (g_prev_pct < EV_LOW_REC && b->pct >= EV_LOW_REC)
|
||||
ev_push(1, 2, b->pct, "电量回到 %d%%(≥%d%% 恢复)", b->pct, EV_LOW_REC);
|
||||
if (g_prev_pct < rec && b->pct >= rec)
|
||||
ev_push(1, 2, b->pct, "电量回到 %d%%(≥%d%% 恢复)", b->pct, rec);
|
||||
}
|
||||
/* ③ 屏幕亮灭 / ④ 背光跳变(只读模式 screen=-1 → 跳过;息屏时不算背光跳变) */
|
||||
if (e->screen_on >= 0) {
|
||||
@@ -878,6 +1233,8 @@ static void daemon_tick(void)
|
||||
g_boot_mode, fm ? "=meow" : "!=meow/missing");
|
||||
g_full_mode = fm;
|
||||
}
|
||||
/* 期 3:每拍重载模型 conf(`model set`/手工改系数即时生效;conf 缺失走内置默认,读侧无害)。 */
|
||||
if (g_full_mode) model_load(&g_model);
|
||||
batt_t b;
|
||||
batt_read(&b);
|
||||
long t = -1, i = 0;
|
||||
@@ -891,6 +1248,146 @@ static void daemon_tick(void)
|
||||
detect_events(&b, &e);
|
||||
}
|
||||
|
||||
/* ---------------- 标定(calibrate)实现(守护进程内;用户 CLI start/stop 驱动) ---------------- */
|
||||
static void calib_csv_path(void)
|
||||
{
|
||||
time_t t = time(NULL);
|
||||
char ts[32];
|
||||
snprintf(ts, sizeof ts, "%ld", (long)t);
|
||||
/* TF 可写 → MeowOS/logs;否则 /tmp(同 netmgrd 无卡降级)。路径去重(同秒 → _N)。 */
|
||||
if (access(g_log_dir, W_OK) != 0) {
|
||||
char d[200];
|
||||
snprintf(d, sizeof d, "%s", g_log_dir);
|
||||
char *s = strrchr(d, '/');
|
||||
if (s) *s = 0;
|
||||
mkdir(d, 0755); /* 建父层(如 /usr/data/mnt/sd_0/MeowOS);外层已挂载则瞬时成功 */
|
||||
mkdir(g_log_dir, 0755);
|
||||
}
|
||||
const char *base = (access(g_log_dir, W_OK) == 0) ? g_log_dir : "/tmp";
|
||||
char p[384];
|
||||
for (int n = 0; n < 100; n++) {
|
||||
if (n == 0) snprintf(p, sizeof p, "%s/power_calib_%s.csv", base, ts);
|
||||
else snprintf(p, sizeof p, "%s/power_calib_%s_%d.csv", base, ts, n);
|
||||
if (access(p, F_OK) != 0) break;
|
||||
}
|
||||
snprintf(g_calib_csv, sizeof g_calib_csv, "%s", p);
|
||||
}
|
||||
|
||||
static void calib_write_header(const char *scene)
|
||||
{
|
||||
if (!g_calib_fp) return;
|
||||
fprintf(g_calib_fp, "# meow_powermgr calibrate scene=%s ts=%ld\n", scene, (long)time(NULL));
|
||||
fprintf(g_calib_fp, "ts,cap_pct,vol_mv,current_ua,charge_mw,brightness_pct,cpu_util_pct,wifi_state,bt_state\n");
|
||||
fflush(g_calib_fp);
|
||||
}
|
||||
|
||||
/* 启动标定会话(CLI calibrate start <scene> 经 IPC;已启动 → 报错)。返回 0=启动成功。 */
|
||||
static int calib_start(const char *scene)
|
||||
{
|
||||
if (g_calib_active) return -1;
|
||||
if (!scene || !scene[0]) return -1;
|
||||
if (!g_full_mode) { svc_log("calib: readonly 模式不采样(HibyOS 降级)"); return -1; }
|
||||
calib_csv_path();
|
||||
g_calib_fp = fopen(g_calib_csv, "w");
|
||||
if (!g_calib_fp) { svc_log("calib: open %s fail: %s", g_calib_csv, strerror(errno)); return -1; }
|
||||
snprintf(g_calib_scene, sizeof g_calib_scene, "%s", scene);
|
||||
calib_write_header(scene);
|
||||
g_calib_active = 1;
|
||||
g_calib_rows = 0;
|
||||
g_calib_first_pct = -1; g_calib_last_pct = -1;
|
||||
g_calib_start_mono = mono_now();
|
||||
g_calib_cpu_pt = -1; g_calib_cpu_pi = 0;
|
||||
g_calib_prx = -1; g_calib_ptx = 0;
|
||||
svc_log("calib: start scene=%s -> %s", scene, g_calib_csv);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 单次标定采样(CALIB_SEC 节拍):电流(μA)/充电功率/亮度/CPU 差分/wifi/bt/电量%。 */
|
||||
static void calib_tick(void)
|
||||
{
|
||||
if (!g_calib_active || !g_calib_fp) return;
|
||||
batt_t b;
|
||||
batt_read(&b);
|
||||
/* CPU 差分(独立基线,勿动 history 的 g_cpu_prev_*) */
|
||||
long t = -1, i = 0;
|
||||
int cu = cpu_util_pct(g_calib_cpu_pt, g_calib_cpu_pi, &t, &i);
|
||||
if (t >= 0) { g_calib_cpu_pt = t; g_calib_cpu_pi = i; }
|
||||
if (cu < 0) cu = -1;
|
||||
/* 亮度/面板、wifi 状态、bt 状态 */
|
||||
int on = -1;
|
||||
int bp = bl_read_pct(&on);
|
||||
long rx = -1, tx = -1;
|
||||
int ws = wifi_read_state(&rx, &tx);
|
||||
if (ws == 1 && g_calib_prx >= 0 && rx >= 0 && (rx - g_calib_prx) + (tx - g_calib_ptx) > 4096) ws = 2;
|
||||
g_calib_prx = rx; g_calib_ptx = tx;
|
||||
int bs = bt_read_state();
|
||||
/* 充电功率(mp2731 实测;放电恒 0 无实测轨) */
|
||||
int cmw = pm_charge_mw(&b);
|
||||
if (g_calib_rows == 0) g_calib_first_pct = b.pct;
|
||||
g_calib_last_pct = b.pct;
|
||||
long now = mono_now() - g_calib_start_mono;
|
||||
fprintf(g_calib_fp, "%ld,%d,%d,%d,%d,%d,%d,%d,%d\n",
|
||||
now, b.pct, b.mv, b.chg_cur_ua, cmw, bp, cu, ws, bs);
|
||||
fflush(g_calib_fp);
|
||||
g_calib_rows++;
|
||||
}
|
||||
|
||||
/* 停止标定会话:写尾注 + 关文件;返回样本数。 */
|
||||
static long calib_stop(void)
|
||||
{
|
||||
if (!g_calib_active) return -1;
|
||||
if (g_calib_fp) {
|
||||
fprintf(g_calib_fp, "# end rows=%ld duration_s=%ld cap=%ld->%ld\n",
|
||||
g_calib_rows, mono_now() - g_calib_start_mono,
|
||||
g_calib_first_pct, g_calib_last_pct);
|
||||
fclose(g_calib_fp);
|
||||
g_calib_fp = NULL;
|
||||
}
|
||||
long rows = g_calib_rows;
|
||||
g_calib_active = 0;
|
||||
char scene[CALIB_SCENE_MAX];
|
||||
snprintf(scene, sizeof scene, "%s", g_calib_scene);
|
||||
svc_log("calib: stop scene=%s rows=%ld csv=%s", scene, rows, g_calib_csv);
|
||||
return rows;
|
||||
}
|
||||
|
||||
/* 守护退出时收尾(SIGTERM/INT → g_stop):若标定未停则落地收尾,防半截/丢缓冲。 */
|
||||
static void calib_finalize_on_exit(void)
|
||||
{
|
||||
if (g_calib_active && g_calib_fp) calib_stop();
|
||||
}
|
||||
|
||||
/* 标定 JSON(IPC 响应/CLI 用):列出指定目录下的 power_calib_*.csv + 当前会话态。 */
|
||||
static void build_calib_json(char *out, size_t cap)
|
||||
{
|
||||
jw_t w = { out, cap, 0 };
|
||||
out[0] = 0;
|
||||
jadd(&w, "{\"kind\":\"calib\",\"active\":%d,", g_calib_active ? 1 : 0);
|
||||
if (g_calib_active) {
|
||||
jadd(&w, "\"scene\":\"%s\",\"csv\":\"%s\",\"rows\":%ld,", g_calib_scene, g_calib_csv, g_calib_rows);
|
||||
} else {
|
||||
jadd(&w, "\"scene\":null,\"csv\":null,\"rows\":0,");
|
||||
}
|
||||
jadd(&w, "\"files\":[");
|
||||
DIR *d = opendir(g_log_dir);
|
||||
int first = 1;
|
||||
if (d) {
|
||||
struct dirent *de;
|
||||
while ((de = readdir(d))) {
|
||||
if (strncmp(de->d_name, "power_calib_", 12) != 0 || !strstr(de->d_name, ".csv")) continue;
|
||||
char p[512];
|
||||
snprintf(p, sizeof p, "%s/%s", g_log_dir, de->d_name);
|
||||
struct stat st;
|
||||
if (stat(p, &st) == 0) {
|
||||
jadd(&w, "%s\"%s\"", first ? "" : ",", de->d_name);
|
||||
first = 0;
|
||||
}
|
||||
}
|
||||
closedir(d);
|
||||
}
|
||||
jadd(&w, "]}");
|
||||
}
|
||||
|
||||
/* 期 2 · history:从采样环出最近最多 count 条 + 全环窗口汇总(内存环形,重启清零)。
|
||||
* JSON: {kind:history,count,n_samples,window_sec,start_pct,end_pct,min_pct,max_pct,avg_mw,samples:[[ts,pct,est_mw,chg,scr,bri],...]} */
|
||||
static void build_history_json(uint32_t count, char *out, size_t cap)
|
||||
@@ -956,8 +1453,82 @@ static void build_events_json(uint32_t count, char *out, size_t cap)
|
||||
jadd(&w, "]}");
|
||||
}
|
||||
|
||||
/* ---------------- 动作级鉴权(期 3 POLICY_SET/CALIB_*;同 svc_power 模型,0-diff 可移植) ----------------
|
||||
* 读级(POLICY_GET/QUERY/HISTORY/EVENTS)任意调用者;动作级(POLICY_SET/CALIB_START/CALIB_STOP)
|
||||
* 须 root(uid==0) + 系统身份(CALL_SYS 或 os.meow.* 包)。
|
||||
* MEOW_PM_AUTH_TEST=1(仅宿主测试)关闭动作级鉴权——生产绝不设置。 */
|
||||
typedef enum { CALL_OTHER, CALL_PKG, CALL_SYS } caller_kind_t;
|
||||
typedef struct {
|
||||
caller_kind_t kind;
|
||||
uid_t uid;
|
||||
char pkg[MEOW_APP_ID_MAX];
|
||||
} caller_t;
|
||||
|
||||
static void classify_exe(const char *exe, caller_t *out)
|
||||
{
|
||||
static const char *mp = "/usr/data/mnt/sd_0/MeowAPPs/";
|
||||
if (strncmp(exe, mp, strlen(mp)) == 0) {
|
||||
const char *p = exe + strlen(mp);
|
||||
if (*p == '/') p++;
|
||||
const char *end = strchr(p, '/');
|
||||
int plen = end ? (int)(end - p) : (int)strlen(p);
|
||||
if (plen > 0 && plen < MEOW_APP_ID_MAX) {
|
||||
memcpy(out->pkg, p, (size_t)plen); out->pkg[plen] = 0;
|
||||
out->kind = CALL_PKG;
|
||||
return;
|
||||
}
|
||||
}
|
||||
char tmp[MEOW_APP_ID_MAX];
|
||||
if (strcmp(exe, "/usr/bin/meow_launcherd") == 0) {
|
||||
snprintf(tmp, sizeof tmp, "%s", "os.meow.launcher");
|
||||
} else if (strcmp(exe, "/usr/data/mnt/sd_0/MeowOS/opt/MeowPanel/meowpanel") == 0) {
|
||||
snprintf(tmp, sizeof tmp, "%s", "os.meow.panel");
|
||||
} else if (strncmp(exe, "/usr/bin/meow_", 14) == 0) {
|
||||
size_t l = strlen(exe + 11);
|
||||
if (l >= sizeof tmp) l = sizeof tmp - 1;
|
||||
memcpy(tmp, exe + 11, l); tmp[l] = 0;
|
||||
} else if (strncmp(exe, "/usr/data/meow/", 15) == 0) {
|
||||
snprintf(tmp, sizeof tmp, "%s", "os.meow.core");
|
||||
} else {
|
||||
return; /* CALL_OTHER */
|
||||
}
|
||||
snprintf(out->pkg, sizeof out->pkg, "%s", tmp);
|
||||
out->kind = CALL_SYS;
|
||||
}
|
||||
|
||||
static void classify_caller(int fd, caller_t *out)
|
||||
{
|
||||
memset(out, 0, sizeof *out);
|
||||
out->uid = (uid_t)-1;
|
||||
struct ucred cred;
|
||||
socklen_t cl = sizeof cred;
|
||||
if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &cred, &cl) != 0) return;
|
||||
out->uid = cred.uid;
|
||||
char procpath[64], exe[256];
|
||||
snprintf(procpath, sizeof procpath, "/proc/%d/exe", (int)cred.pid);
|
||||
ssize_t n = readlink(procpath, exe, sizeof exe - 1);
|
||||
if (n <= 0) return;
|
||||
exe[n] = 0;
|
||||
classify_exe(exe, out);
|
||||
}
|
||||
|
||||
static int is_sys_identity(const caller_t *c)
|
||||
{
|
||||
if (c->kind == CALL_SYS) return 1;
|
||||
if (c->kind == CALL_PKG && !strncmp(c->pkg, "os.meow.", 8)) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int caller_is_action_allowed(const caller_t *c)
|
||||
{
|
||||
if (getenv("MEOW_PM_AUTH_TEST")) return 1; /* 宿主测试专用:关闭动作级鉴权 */
|
||||
return (c->uid == 0) && is_sys_identity(c);
|
||||
}
|
||||
|
||||
static void handle_conn(int c)
|
||||
{
|
||||
caller_t caller;
|
||||
classify_caller(c, &caller); /* 连接级身份(pid→exe→classify);期 3 动作级鉴权用 */
|
||||
for (;;) {
|
||||
uint8_t hdr[MEOW_IPC_HDR_LEN];
|
||||
if (x_read_full(c, hdr, sizeof hdr) < 0) break;
|
||||
@@ -1023,7 +1594,82 @@ static void handle_conn(int c)
|
||||
continue;
|
||||
}
|
||||
|
||||
send_err(c, req_id, MEOW_ERR_INVAL, "unknown type (期1 PM_QUERY / 期2 HISTORY/EVENTS)");
|
||||
if (type == MSG_PM_POLICY_GET) {
|
||||
/* 期 3:策略读(读级任意调用者)。回 policy JSON(MSG_PM_RESULT)。 */
|
||||
pm_policy_t p;
|
||||
policy_load(&p);
|
||||
char json[MEOW_IPC_MAX];
|
||||
build_policy_json(&p, json, sizeof json);
|
||||
send_pm_result(c, MSG_PM_RESULT, req_id, PM_SUBSET_POLICY, json);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (type == MSG_PM_POLICY_SET) {
|
||||
/* 期 3:策略写(动作级=root+系统身份)。req: m_pm_policy_set_t{item,val}。
|
||||
* 生效方式:只写 settings.conf power.* 键(原子),powermgr 不直接写系统。 */
|
||||
if (!caller_is_action_allowed(&caller)) {
|
||||
send_err(c, req_id, MEOW_ERR_PERM, "policy set 需 root+系统身份");
|
||||
continue;
|
||||
}
|
||||
m_pm_policy_set_t s;
|
||||
memset(&s, 0, sizeof s);
|
||||
if (rlen > sizeof s) rlen = sizeof s;
|
||||
memcpy(&s, payload, rlen);
|
||||
s.item[sizeof s.item - 1] = 0; s.val[sizeof s.val - 1] = 0;
|
||||
char why[128] = "";
|
||||
int rc = policy_set_item(s.item, s.val, why, sizeof why);
|
||||
pm_policy_t p;
|
||||
policy_load(&p);
|
||||
char json[MEOW_IPC_MAX];
|
||||
if (rc != 0) {
|
||||
snprintf(json, sizeof json, "{\"kind\":\"policy\",\"ok\":false,\"why\":\"%s\"}", why);
|
||||
} else {
|
||||
build_policy_json(&p, json, sizeof json);
|
||||
}
|
||||
send_pm_result(c, MSG_PM_RESULT, req_id, rc == 0 ? PM_SUBSET_POLICY : 0, json);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (type == MSG_PM_CALIB_START) {
|
||||
/* 期 3:标定启动(动作级)。req: 场景名 NUL 串。 */
|
||||
if (!caller_is_action_allowed(&caller)) {
|
||||
send_err(c, req_id, MEOW_ERR_PERM, "calib start 需 root+系统身份");
|
||||
continue;
|
||||
}
|
||||
char scene[CALIB_SCENE_MAX];
|
||||
memset(scene, 0, sizeof scene);
|
||||
if (rlen > sizeof scene - 1) rlen = sizeof scene - 1;
|
||||
memcpy(scene, payload, rlen);
|
||||
if (calib_start(scene) != 0) {
|
||||
send_err(c, req_id, MEOW_ERR_BUSY, g_calib_active ? "calib 已启动" : "calib 启动失败(只读或打不开csv)");
|
||||
continue;
|
||||
}
|
||||
char json[MEOW_IPC_MAX];
|
||||
build_calib_json(json, sizeof json);
|
||||
send_pm_result(c, MSG_PM_RESULT, req_id, 0, json);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (type == MSG_PM_CALIB_STOP) {
|
||||
/* 期 3:标定停止(动作级)。req 空。 */
|
||||
if (!caller_is_action_allowed(&caller)) {
|
||||
send_err(c, req_id, MEOW_ERR_PERM, "calib stop 需 root+系统身份");
|
||||
continue;
|
||||
}
|
||||
if (!g_calib_active) {
|
||||
send_err(c, req_id, MEOW_ERR_BUSY, "calib 未启动");
|
||||
continue;
|
||||
}
|
||||
long rows = calib_stop();
|
||||
char json[MEOW_IPC_MAX];
|
||||
snprintf(json, sizeof json,
|
||||
"{\"kind\":\"calib\",\"stopped\":true,\"rows\":%ld,\"csv\":\"%s\",\"scene\":\"%s\"}",
|
||||
rows, g_calib_csv, g_calib_scene);
|
||||
send_pm_result(c, MSG_PM_RESULT, req_id, 0, json);
|
||||
continue;
|
||||
}
|
||||
|
||||
send_err(c, req_id, MEOW_ERR_INVAL, "unknown type (期1 QUERY / 期2 HISTORY/EVENTS / 期3 POLICY/CALIB)");
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1057,14 +1703,23 @@ static int run_daemon(const char *sock)
|
||||
sock, g_full_mode ? "full" : "readonly",
|
||||
g_sample_sec, access(g_model_conf, R_OK) == 0 ? g_model_conf : "builtin-default(待标定)");
|
||||
|
||||
long next_tick = 0; /* 立即首拍 */
|
||||
long next_tick = 0; /* 立即首拍 */
|
||||
long next_calib = 0; /* 标定节拍(仅 g_calib_active 时推进) */
|
||||
while (!g_stop) {
|
||||
long now = mono_now();
|
||||
if (now >= next_tick) {
|
||||
daemon_tick();
|
||||
next_tick = now + g_sample_sec;
|
||||
}
|
||||
int wait_ms = (int)((next_tick - mono_now()) * 1000);
|
||||
if (g_calib_active && now >= next_calib) {
|
||||
calib_tick();
|
||||
next_calib = now + CALIB_SEC;
|
||||
}
|
||||
/* poll 休眠取「下一事件」最近者:采样拍、标定拍(活动时)。上界 g_sample_sec 冗余保护。 */
|
||||
long d_tick = next_tick - mono_now();
|
||||
long d_calib = g_calib_active ? (next_calib - mono_now()) : d_tick;
|
||||
long dmin = d_tick < d_calib ? d_tick : d_calib;
|
||||
int wait_ms = (int)(dmin * 1000);
|
||||
if (wait_ms < 50) wait_ms = 50;
|
||||
if (wait_ms > g_sample_sec * 1000) wait_ms = g_sample_sec * 1000;
|
||||
struct pollfd pfd = { lfd, POLLIN, 0 };
|
||||
@@ -1074,6 +1729,7 @@ static int run_daemon(const char *sock)
|
||||
if (c >= 0) { handle_conn(c); close(c); }
|
||||
}
|
||||
}
|
||||
calib_finalize_on_exit(); /* 期 3:守护退出前收尾标定(SIGTERM 等) */
|
||||
svc_log("meow_powermgr exit");
|
||||
close(lfd);
|
||||
unlink(sock);
|
||||
@@ -1329,8 +1985,30 @@ static int cli_estimate(void)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cli_model(void)
|
||||
static int cli_model(int n, char **v)
|
||||
{
|
||||
/* 期 3:`model set <key> <mw>`——读改写单键 + 原子写(标定后即时改系数)。 */
|
||||
if (n >= 1 && !strcmp(v[0], "set")) {
|
||||
if (n >= 3) {
|
||||
const char *key = v[1];
|
||||
int mw = atoi(v[2]);
|
||||
if (!model_key_is_known(key)) {
|
||||
fprintf(stderr, "model set: 未知键 %s(可用键见 model 输出)\n", key);
|
||||
return 1;
|
||||
}
|
||||
if (mw < 0 || mw > 1000000) { fprintf(stderr, "model set: mw 须 0-1000000\n"); return 1; }
|
||||
if (model_conf_set_key(key, mw) != 0) {
|
||||
fprintf(stderr, "model set: 写 %s 失败(原子写)\n", g_model_conf);
|
||||
return 1;
|
||||
}
|
||||
pm_model_t m;
|
||||
model_load(&m);
|
||||
printf("model set %s=%d(%s)✓\n", key, mw, g_model_conf);
|
||||
return 0;
|
||||
}
|
||||
fprintf(stderr, "usage: meow_power model set <key> <mw>\n");
|
||||
return 1;
|
||||
}
|
||||
pm_model_t m;
|
||||
int def = model_load(&m);
|
||||
printf("# meow_powermgr 经验功耗模型(POWER_MANAGER_DESIGN §3.2)\n");
|
||||
@@ -1354,6 +2032,109 @@ static int cli_model(void)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 期 3 · policy:show|set。策略数据源 = settings.conf power.* 键;powermgr 只写键不写系统。 */
|
||||
static int cli_policy(int n, char **v)
|
||||
{
|
||||
if (n >= 1 && !strcmp(v[0], "show")) {
|
||||
/* 优先 IPC(守护 view),不可达降级直读 settings.conf(读侧无副作用、口径一致)。 */
|
||||
char js[MEOW_IPC_MAX];
|
||||
if (pm_ipc_json(MSG_PM_POLICY_GET, NULL, 0, MSG_PM_RESULT, js, sizeof js) != 0) {
|
||||
pm_policy_t p;
|
||||
policy_load(&p);
|
||||
build_policy_json(&p, js, sizeof js);
|
||||
fprintf(stderr, "note: powermgr 守护不可达,直读 settings.conf(无 daemon 侧差)\n");
|
||||
}
|
||||
char prof[16];
|
||||
int low = -1, crit = -1, bcap = -1, scap = -1, enacted = -1;
|
||||
jstr(js, "profile", prof, sizeof prof);
|
||||
jint(js, "low_pct", &low);
|
||||
jint(js, "critical_pct", &crit);
|
||||
jint(js, "brightness_cap", &bcap);
|
||||
jint(js, "screen_timeout_cap", &scap);
|
||||
jint(js, "enacted", &enacted);
|
||||
printf("policy: profile=%s low_pct=%d critical_pct=%d\n", prof[0] ? prof : "?", low, crit);
|
||||
printf(" brightness_cap=%d screen_timeout_cap=%d enact=%s\n", bcap, scap, enacted == 1 ? "on" : "off");
|
||||
printf("生效方式: powermgr 只写 settings.conf power.* 键,launcher/sysui 期4消费(本服务不直接写系统,"
|
||||
"绝不poke背光/息屏/wlan0/bt)\n");
|
||||
printf("默认全关: profile=balanced 无操作=现状; cap=0 不生效; 用户手动开(只能用户手动开)\n");
|
||||
if (enacted == 1)
|
||||
printf("注意: 已开启策略档——但 launcher 未接入前仅持久化,不实际生效(期4接 launcher)\n");
|
||||
return 0;
|
||||
}
|
||||
if (n >= 2 && !strcmp(v[0], "set")) {
|
||||
const char *item = v[1], *val = n >= 3 ? v[2] : "";
|
||||
char why[128] = "";
|
||||
int rc = policy_set_item(item, val, why, sizeof why);
|
||||
if (rc != 0) { fprintf(stderr, "policy set %s %s: %s\n", item, val, why[0] ? why : "写 settings 失败"); return 1; }
|
||||
printf("policy set %s=%s ✓(settings.conf power.* 键;launcher 期4消费)\n", item, val);
|
||||
return 0;
|
||||
}
|
||||
fprintf(stderr, "usage: meow_power policy show| set profile <档>|set low-pct <n>|set crit-pct <n>|"
|
||||
"set bri-cap <0-101>|set scr-cap <秒>\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* 期 3 · calibrate:start|stop|list(用户标定实验;start/stop 经 IPC 驱动守护 2s 采样写 CSV)。 */
|
||||
static int cli_calibrate(int n, char **v)
|
||||
{
|
||||
if (n >= 1 && !strcmp(v[0], "start")) {
|
||||
if (n < 2) { fprintf(stderr, "usage: meow_power calibrate start <场景名>\n"); return 1; }
|
||||
char js[MEOW_IPC_MAX];
|
||||
if (pm_ipc_json(MSG_PM_CALIB_START, v[1], (uint32_t)strlen(v[1]), MSG_PM_RESULT, js, sizeof js) != 0) {
|
||||
fprintf(stderr, "powermgr 守护不可达:calibrate 需 meow_powermgr 常驻(trigger=boot)\n");
|
||||
return 1;
|
||||
}
|
||||
if (strstr(js, "\"active\":1") || strstr(js, "\"active\": true")) {
|
||||
char csv[260] = "", sc[64] = "";
|
||||
jstr(js, "csv", csv, sizeof csv); jstr(js, "scene", sc, sizeof sc);
|
||||
printf("calibrate 已启动: scene=%s csv=%s(2s 节拍采样,实验后 `meow_power calibrate stop` 停止)\n",
|
||||
sc[0] ? sc : v[1], csv[0] ? csv : "?");
|
||||
return 0;
|
||||
}
|
||||
fprintf(stderr, "calibrate start 失败: %s\n", strstr(js, "why") ? js : "未知");
|
||||
return 1;
|
||||
}
|
||||
if (n >= 1 && !strcmp(v[0], "stop")) {
|
||||
char js[MEOW_IPC_MAX];
|
||||
if (pm_ipc_json(MSG_PM_CALIB_STOP, NULL, 0, MSG_PM_RESULT, js, sizeof js) != 0) {
|
||||
fprintf(stderr, "powermgr 守护不可达:calibrate stop 需 meow_powermgr 常驻\n");
|
||||
return 1;
|
||||
}
|
||||
long rows = -1; char csv[260] = "", sc[64] = "";
|
||||
int rr = 0;
|
||||
if (jint(js, "rows", &rr) == 0) rows = rr;
|
||||
jstr(js, "csv", csv, sizeof csv);
|
||||
jstr(js, "scene", sc, sizeof sc);
|
||||
if (rows >= 0)
|
||||
printf("calibrate 已停止: scene=%s rows=%ld csv=%s\n", sc, rows, csv);
|
||||
else
|
||||
printf("calibrate 停止: %s\n", strstr(js, "未启动") ? "calib 未启动" : js);
|
||||
return 0;
|
||||
}
|
||||
if (n >= 1 && !strcmp(v[0], "list")) {
|
||||
DIR *d = opendir(g_log_dir);
|
||||
int cnt = 0;
|
||||
printf("calibrate 列表(%s):\n", g_log_dir);
|
||||
if (!d) { printf(" (日志目录不可读,无卡降级?)\n"); return 0; }
|
||||
struct dirent *de;
|
||||
while ((de = readdir(d))) {
|
||||
if (strncmp(de->d_name, "power_calib_", 12) != 0 || !strstr(de->d_name, ".csv")) continue;
|
||||
char p[512];
|
||||
snprintf(p, sizeof p, "%s/%s", g_log_dir, de->d_name);
|
||||
struct stat st;
|
||||
long kb = 0;
|
||||
if (stat(p, &st) == 0) kb = (long)st.st_size;
|
||||
printf(" %s (%ldB)\n", de->d_name, kb);
|
||||
cnt++;
|
||||
}
|
||||
closedir(d);
|
||||
if (cnt == 0) printf(" (无标定记录)\n");
|
||||
return 0;
|
||||
}
|
||||
fprintf(stderr, "usage: meow_power calibrate start <场景名>| stop| list\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* 期 2 · history:最近 N 个采样点 + 全环窗口汇总(不落盘,重启清零) */
|
||||
static int cli_history(int n, char **v)
|
||||
{
|
||||
@@ -1419,7 +2200,8 @@ static int cli_events(int n, char **v)
|
||||
return 1;
|
||||
}
|
||||
int cnt = 0; jint(js, "count", &cnt);
|
||||
printf("events: %d 条(内存环形,重启清零;App 前后台切换无可轮询导出点——期2诚实标注不支持)\n", cnt);
|
||||
printf("events: %d 条(内存环形,重启清零;App 前后台切换——launcher MSG_ACT_FOCUS 为单向 app→launcher 上报,"
|
||||
"powermgr 侧无导出点可接,期 4 接 launcher 扩展)\n", cnt);
|
||||
if (cnt == 0) { printf(" (无事件)\n"); return 0; }
|
||||
long ts[64]; char tn[64][16]; int pct[64]; char txt[64][128];
|
||||
int m = 0;
|
||||
@@ -1499,15 +2281,9 @@ static int cli_top(void)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cli_stub(const char *what, const char *phase)
|
||||
{
|
||||
printf("meow_power %s: 期 %s 落地,当前未实现(POWER_MANAGER_DESIGN §9 分期)\n", what, phase);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static const char *cli_usage(void)
|
||||
{
|
||||
return "status|estimate|model|history[N]|top|events[N]|policy(history/top/events 期2已落地;policy 期3桩)";
|
||||
return "status|estimate|model[set <key> <mw>]|history[N]|top|events[N]|policy show|set ...|calibrate start|stop|list";
|
||||
}
|
||||
|
||||
static int cli_run(int n, char **v)
|
||||
@@ -1515,11 +2291,12 @@ static int cli_run(int n, char **v)
|
||||
const char *cmd = v[0];
|
||||
if (!strcmp(cmd, "status")) return cli_status();
|
||||
if (!strcmp(cmd, "estimate")) return cli_estimate();
|
||||
if (!strcmp(cmd, "model")) return cli_model();
|
||||
if (!strcmp(cmd, "model")) return cli_model(n - 1, v + 1);
|
||||
if (!strcmp(cmd, "history")) return cli_history(n - 1, v + 1);
|
||||
if (!strcmp(cmd, "top")) return cli_top();
|
||||
if (!strcmp(cmd, "events")) return cli_events(n - 1, v + 1);
|
||||
if (!strcmp(cmd, "policy")) return cli_stub("policy", "3");
|
||||
if (!strcmp(cmd, "policy")) return cli_policy(n - 1, v + 1);
|
||||
if (!strcmp(cmd, "calibrate")) return cli_calibrate(n - 1, v + 1);
|
||||
fprintf(stderr, "usage: meow_power <%s>\n", cli_usage());
|
||||
return 1;
|
||||
}
|
||||
@@ -1537,6 +2314,8 @@ int main(int argc, char **argv)
|
||||
if ((e = getenv("MEOW_PROC_STAT")) && e[0]) snprintf(g_proc_stat, sizeof g_proc_stat, "%s", e);
|
||||
if ((e = getenv("MEOW_BOOT_MODE")) && e[0]) snprintf(g_boot_mode, sizeof g_boot_mode, "%s", e);
|
||||
if ((e = getenv("MEOW_PM_MODEL")) && e[0]) snprintf(g_model_conf, sizeof g_model_conf, "%s", e);
|
||||
if ((e = getenv("MEOW_SETTINGS_FILE")) && e[0]) snprintf(g_settings, sizeof g_settings, "%s", e);
|
||||
if ((e = getenv("MEOW_LOG_DIR")) && e[0]) snprintf(g_log_dir, sizeof g_log_dir, "%s", e);
|
||||
if ((e = getenv("MEOW_AUDIO_SOCK")) && e[0]) snprintf(g_audio_sock, sizeof g_audio_sock, "%s", e);
|
||||
if ((e = getenv("MEOW_PM_SOCK")) && e[0]) g_sock = e;
|
||||
if ((e = getenv("MEOW_PM_SAMPLE_SEC")) && atoi(e) > 0) g_sample_sec = atoi(e);
|
||||
|
||||
Binary file not shown.
@@ -42,10 +42,15 @@ EOF
|
||||
echo "cpu 100 0 100 800 0 0 0 0 0 0" > "$T/proc_stat"
|
||||
echo meow > "$T/boot_mode"
|
||||
|
||||
mkdir -p "$T/logs" # 标定 CSV 落点(MEOW_LOG_DIR)
|
||||
cat > "$T/settings.conf" <<'SEOF'
|
||||
# meow-settings 1 (测试桩:policy power.* 键落点)
|
||||
SEOF
|
||||
ENVS="MEOW_PS_DIR=$PS MEOW_BL_DIR=$BL MEOW_NET_DIR=$NET MEOW_RFKILL_DIR=$RF MEOW_BT_DIR=$BT
|
||||
MEOW_NETDEV=$T/netdev MEOW_PROC_STAT=$T/proc_stat MEOW_BOOT_MODE=$T/boot_mode
|
||||
MEOW_PM_MODEL=$T/model.conf MEOW_AUDIO_SOCK=$T/no_audio.sock MEOW_PM_SOCK=$T/pm.sock
|
||||
MEOW_PM_NO_HCICONFIG=1"
|
||||
MEOW_PM_MODEL=$T/model.conf MEOW_SETTINGS_FILE=$T/settings.conf MEOW_LOG_DIR=$T/logs
|
||||
MEOW_AUDIO_SOCK=$T/no_audio.sock MEOW_PM_SOCK=$T/pm.sock
|
||||
MEOW_PM_NO_HCICONFIG=1 MEOW_PM_AUTH_TEST=1"
|
||||
ln -sf "$(readlink -f "$BIN")" "$T/meow_power" # argv[0] 双形态
|
||||
|
||||
# ---- T1: 守护直读(无守护 → 降级直读) ----
|
||||
@@ -143,8 +148,65 @@ ck T9a "history:" "$OUT"
|
||||
ck T9b "采样点" "$OUT"
|
||||
ck T9c "est_mW" "$OUT"
|
||||
echo "$OUT" | grep -q "守护不可达" && { FAIL=$((FAIL+1)); echo "FAIL T9d 应走守护"; } || { PASS=$((PASS+1)); echo "PASS T9d via-daemon"; }
|
||||
OUT=$(env $ENVS $Q "$T/meow_power" policy 2>&1)
|
||||
ck T9e "期 3" "$OUT"
|
||||
# ---- T9e: 期 3 policy show(默认全关:profile=balanced / cap=0 / enact=off)----
|
||||
OUT=$(env $ENVS $Q "$T/meow_power" policy show 2>&1)
|
||||
ck T9e "profile=balanced low_pct=15 critical_pct=5" "$OUT"
|
||||
ck T9e2 "brightness_cap=0 screen_timeout_cap=0 enact=off" "$OUT"
|
||||
ck T9e3 "默认全关" "$OUT"
|
||||
|
||||
# ---- T9f: 期 3 policy set profile powersave(写入 settings.conf;enact=on)----
|
||||
OUT=$(env $ENVS $Q "$T/meow_power" policy set profile powersave 2>&1)
|
||||
ck T9f "policy set profile=powersave" "$OUT"
|
||||
OUT=$(env $ENVS $Q "$T/meow_power" policy show 2>&1)
|
||||
ck T9f2 "profile=powersave" "$OUT"
|
||||
ck T9f3 "enact=on" "$OUT"
|
||||
grep -q "power.profile=powersave" "$T/settings.conf" && { PASS=$((PASS+1)); echo "PASS T9f4 settings 键"; } || { FAIL=$((FAIL+1)); echo "FAIL T9f4: $(cat "$T/settings.conf")"; }
|
||||
|
||||
# ---- T9g: 期 3 低电阈值读 settings.conf(power.low_pct=20 生效于事件判定)----
|
||||
env $ENVS $Q "$T/meow_power" policy set low-pct 20 >/dev/null 2>&1
|
||||
echo 19 > "$PS/battery/capacity" # 从 93 跳到 19 < 20 → 低电事件(阈值 20%)
|
||||
sleep 4 # 等守护节拍采样
|
||||
OUT=$(env $ENVS $Q "$T/meow_power" policy show 2>&1)
|
||||
ck T9g "low_pct=20" "$OUT"
|
||||
OUT=$(env $ENVS $Q "$T/meow_power" events 2>&1)
|
||||
ck T9g2 "阈值 20%" "$OUT"
|
||||
echo 93 > "$PS/battery/capacity" # 恢复
|
||||
env $ENVS $Q "$T/meow_power" policy set low-pct 15 >/dev/null 2>&1
|
||||
env $ENVS $Q "$T/meow_power" policy set profile balanced >/dev/null 2>&1
|
||||
|
||||
# ---- T9h: 期 3 policy set bri-cap(0-101 越界拒绝)----
|
||||
OUT=$(env $ENVS $Q "$T/meow_power" policy set bri-cap 88 2>&1)
|
||||
ck T9h "policy set bri-cap=88" "$OUT"
|
||||
OUT=$(env $ENVS $Q "$T/meow_power" policy set bri-cap 255 2>&1)
|
||||
ck T9h2 "0-101" "$OUT"
|
||||
env $ENVS $Q "$T/meow_power" policy set bri-cap 0 >/dev/null 2>&1
|
||||
|
||||
# ---- T9i: 期 3 标定工具 calibrate start/stop/list 出 CSV ----
|
||||
OUT=$(env $ENVS $Q "$T/meow_power" calibrate start 纯亮屏 2>&1)
|
||||
ck T9i "calibrate 已启动" "$OUT"
|
||||
ck T9i2 "power_calib_" "$OUT"
|
||||
sleep 5 # 等 2s×2 拍
|
||||
OUT=$(env $ENVS $Q "$T/meow_power" calibrate stop 2>&1)
|
||||
ck T9i3 "calibrate 已停止" "$OUT"
|
||||
ck T9i4 "rows=" "$OUT"
|
||||
CSV=$(ls "$T/logs"/power_calib_*.csv 2>/dev/null | head -1)
|
||||
if [ -n "$CSV" ]; then PASS=$((PASS+1)); echo "PASS T9i5 csv exists ($CSV)"; else FAIL=$((FAIL+1)); echo "FAIL T9i5 CSV 未生成"; fi
|
||||
[ -n "$CSV" ] && grep -q "ts,cap_pct,vol_mv,current_ua,charge_mw,brightness_pct,cpu_util_pct,wifi_state,bt_state" "$CSV" && { PASS=$((PASS+1)); echo "PASS T9i6 csv header"; } || { FAIL=$((FAIL+1)); echo "FAIL T9i6 csv header"; }
|
||||
[ -n "$CSV" ] && grep -q "scene=纯亮屏" "$CSV" && { PASS=$((PASS+1)); echo "PASS T9i7 csv scene"; } || { FAIL=$((FAIL+1)); echo "FAIL T9i7 csv scene"; }
|
||||
OUT=$(env $ENVS $Q "$T/meow_power" calibrate list 2>&1)
|
||||
ck T9i8 "power_calib_" "$OUT"
|
||||
|
||||
# ---- T9j: 期 3 模型单键 setter model set <key> <mw>(守护每拍重载生效)----
|
||||
OUT=$(env $ENVS $Q "$T/meow_power" model set baseline_mw 999 2>&1)
|
||||
ck T9j "model set baseline_mw=999" "$OUT"
|
||||
OUT=$(env $ENVS $Q "$T/meow_power" model 2>/dev/null)
|
||||
ck T9j2 "baseline_mw=999" "$OUT"
|
||||
sleep 2 # 等守护重载 model
|
||||
OUT=$(env $ENVS $Q "$T/meow_power" estimate 2>/dev/null)
|
||||
ck T9j3 "baseline=999" "$OUT" # 守护侧 estimate 用新系数
|
||||
OUT=$(env $ENVS $Q "$T/meow_power" model set bad_key 5 2>&1)
|
||||
ck T9j4 "未知键" "$OUT"
|
||||
env $ENVS $Q "$T/meow_power" model set baseline_mw 300 >/dev/null 2>&1
|
||||
|
||||
# ---- T10: meow_powermgr 名直调子命令也走 CLI ----
|
||||
OUT=$(env $ENVS $Q "$BIN" model 2>/dev/null)
|
||||
|
||||
Reference in new issue
Block a user