Files
MeowOS-For-R3P2/patch_v2/meow_powermgr/meow_powermgr.c
T
MeowOS 0cae95c9cc powermgr 期2: 历史采样环/功耗分解top/电源事件流 + mp2731电流接入 + BT·WiFi分档细化
- history: 30s 节拍内存环形(RING_N=480≈4h)存 pct/est_mw/充电态/屏幕态/亮度,
  不落盘重启清零(板载写磨损不考虑); IPC MSG_PM_HISTORY(0xD4)/D5, CLI history[N]
- top: estimate breakdown 分量实时占比排序视图(2s 刷新, Ctrl-C 退,
  MEOW_PM_TOP_ONCE=1 一次输出供测试); CLI top
- events: 插拔充电器/低电阈值穿越(15%/5%/回20%)/亮灭屏/背光跳变(≥30%);
  App 前后台无可轮询导出点→诚实标注不支持(语言, 期3接 launcher)
  IPC MSG_PM_EVENTS(0xD6)/D7, CLI events[N]
- 模型: 系数路径迁 /data/meow/powermgr/power_model.conf(=/usr/data symlink,
  组件持久化目录定案); WiFi 增未关联(carrier=0→unassoc)分档; BT 细化
  rfkill+hciconfig ACL(up/connected, 有界子进程 900ms 超时, 不可用回退 up)
- mp2731 current_now 接入: 充电态实测 charge_mw=cc_voltage×current/1e9,
  charge_confidence=measured(消费侧无实测节点仍 estimated, 条件写清)
- IPC 0xD4-D7 号段本文件局部定(不动 meow_launcherd/meow_ipc.h, 严守只动本目录)

验证: 宿主 aarch64 54 PASS + qemu-mipsel 54 PASS; 交叉 -Wall -Wextra -Werror 0 warning;
固件阈值 <200KB 达标(133KB); BSS 37.6KB 起见 RSS<1MB 达标
2026-09-01 22:13:42 +08:00

1565 lines
69 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#define _GNU_SOURCE /* struct ucred / SO_PEERCRED、asprintf 等 GNU/musl 扩展 */
/* meow_powermgr.c — MeowOS 功耗管理专项服务(devdocs/plan/POWER_MANAGER_DESIGN.md,期 2)。
*
* 定位(三服务职责划分,§1):svc_power=一次性系统动作;svc_perf=性能只读聚合;
* **powermgr=功耗**——电池深加工(state/cap/mV/续航估算)、经验功耗模型估算与分解。
* 期 1 范围(§9):svcd 托管骨架 + IPC(MSG_PM_QUERY/RESULT) + CLI status/estimate/model +
* 权威充电链复用 + 经验模型。
* 期 2 新增(本文件):历史采样环(30s 节拍,pct/est_mw/充电态/屏幕态/亮度,内存环形不落盘)、
* 功耗分解 top 排序视图、电源事件流(插拔/低电阈值/背光变化;App 前后台无口径可采→诚实标注不支持)、
* mp2731 current_now 充电功率接入(充电态下 charge_mw 为实测,consume est_mw 仍为模型 estimated)、
* 模型系数路径迁移到 /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)。
*
* 单一二进制双形态(同 svc_power):
* - svcd 空参拉起(板载 services.d/meow_powermgr.conf,trigger=boot/restart=on-fail)→
* IPC 服务常驻:bind /usr/data/meow_pm.sock(--sock/MEOW_PM_SOCK 覆盖),
* MSG_HELLO→MSG_ACK、MSG_PM_QUERY(0x00D0)→MSG_PM_RESULT(0x00D1, JSON)。
* - `meow_power <cmd>`(argv[0] 双形态)或 `meow_powermgr <cmd>` → 一次性 CLI(root 免鉴权)。
* CLI 优先走 IPC(拿守护进程的容量差分速率),守护不可达降级为直读 sysfs(rate=null)。
*
* HibyOS 只读降级(用户定案 2026-09-01 §附 1):trigger=boot + 进程内读 /tmp/meow_boot_mode:
* "meow"=全功能;否则(hiby/缺失)=只采样电池,绝不施策略、不碰 wlan0/bt/声卡(§2 门控语义,
* 与 audiod 的 trigger=manual+exit(0) 不同:本服务读侧无害故常驻只读)。每采样拍重判,可热切换。
*
* 权威充电链(0-diff 照抄 svc_power charging_status_str,真机实测 2026-09-01):
* mp2731-charger/status(权威)→ fusb302-typec-source/online → battery/status+ac/online 兜底。
*
* 硬件事实(devdocs/plan/POWER_HW_FACTS.md,期 0 实测):
* - battery/ 无 current_now/charge_full/cycle_count/temp → 实时功率不可 measured,
* 续航=容量差分 derived + 功耗=经验模型 estimated(confidence 如实标注,不得呈现为实测);
* - 背光量程 0-101(max_brightness 实测,非 255);fb0/blank 不可读 → 息屏=actual_brightness==0;
* - 无 cpufreq(省电档无降频控制点);无 thermal zones;rfkill 存在(期 3 候选控制点)。
*
* 经验模型系数:板载 /usr/data/meow/power_model.conf(key=value,缺失用内置默认);
* **全部系数为经验初值,待真机标定**(§7 项 7/8 标定实验未做)。
*
* 编译(mipsel 静态):build_powermgr.sh;宿主:build_powermgr.sh host。
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <stdint.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <signal.h>
#include <poll.h>
#include <time.h>
#include <dirent.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include "meow_ipc.h" /* 帧格式/消息号 + MSG_PM 系列 + PM_SUBSET_* + m_pm_*(号段 0x00D0-0x00D7) */
/* 期 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_HISTORY_RESULT 0x00D5 /* ↓ 历史 JSON(采样点清单 + 窗口汇总) */
#define MSG_PM_EVENTS 0x00D6 /* ↑ 事件流查询(req: u32 count=N,0=默认) */
#define MSG_PM_EVENTS_RESULT 0x00D7 /* ↓ 事件 JSON 清单 */
#endif
/* ---------------- 路径(全部可经 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 */
static char g_net_dir[128] = "/sys/class/net"; /* MEOW_NET_DIR */
static char g_rfkill_dir[128] = "/sys/class/rfkill"; /* MEOW_RFKILL_DIR */
static char g_bt_dir[128] = "/sys/class/bluetooth"; /* MEOW_BT_DIR */
static char g_netdev[160] = "/proc/net/dev"; /* MEOW_NETDEV */
static char g_proc_stat[160] = "/proc/stat"; /* MEOW_PROC_STAT */
static char g_boot_mode[160] = "/tmp/meow_boot_mode"; /* MEOW_BOOT_MODE */
static char g_model_conf[160] = "/usr/data/meow/powermgr/power_model.conf"; /* MEOW_PM_MODEL
(= /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 const char *g_sock = "/usr/data/meow_pm.sock"; /* MEOW_PM_SOCK/--sock */
#define SAMPLE_SEC_DEF 30 /* 电池采样/状态刷新节拍(秒;MEOW_PM_SAMPLE_SEC 覆盖,测试用) */
#define RATE_MIN_SPAN_DEF 120 /* 容量差分速率的最小时间窗(秒;MEOW_PM_MIN_SPAN_SEC 覆盖) */
#define RING_N 480 /* 采样环容量(30s×480=4h;内存环形不落盘,重启清零——板载 flash 写磨损不考虑) */
#define HIST_MAX_REPLY 80 /* history IPC 单帧最多回采样点(MEOW_IPC_MAX=4096 下单帧可承载上限) */
#define EVENT_RING_N 64 /* 事件环容量(内存环形,重启清零) */
static int g_sample_sec = SAMPLE_SEC_DEF;
static int g_rate_min_span = RATE_MIN_SPAN_DEF;
/* ---------------- 极简日志(stderr;svcd supervisor 捕获) ---------------- */
static void svc_log(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
fprintf(stderr, "[meow_powermgr] ");
vfprintf(stderr, fmt, ap);
fprintf(stderr, "\n");
va_end(ap);
}
/* ---------------- 文件小工具(同 svc_power) ---------------- */
static int read_file_txt(const char *path, char *buf, int cap)
{
int fd = open(path, O_RDONLY);
if (fd < 0) return -1;
int n = 0;
while (n < cap - 1) {
ssize_t r = read(fd, buf + n, (size_t)(cap - 1 - n));
if (r <= 0) break;
n += (int)r;
}
close(fd);
buf[n] = 0;
while (n > 0 && (buf[n - 1] == '\n' || buf[n - 1] == '\r')) buf[--n] = 0;
return n;
}
static int read_file_int_def(const char *path, int def)
{
char buf[32];
if (read_file_txt(path, buf, sizeof buf) <= 0) return def;
char *p = buf;
while (*p == ' ' || *p == '\t') p++;
return atoi(p);
}
/* 拼 power_supply 节点路径:ps_path(b, cap, "battery/capacity") */
static void ps_path(char *buf, size_t cap, const char *node)
{
snprintf(buf, cap, "%s/%s", g_ps_dir, node);
}
/* ---------------- 权威充电链(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。 */
static void charging_status_str(char *st, int cap)
{
char p[192];
st[0] = 0;
ps_path(p, sizeof p, "mp2731-charger/status");
if (read_file_txt(p, st, cap) > 0 && st[0]) return; /* 权威源直用 */
ps_path(p, sizeof p, "fusb302-typec-source/online");
int on = read_file_int_def(p, -1);
if (on >= 0) { snprintf(st, cap, "%s", on == 1 ? "Charging" : "Discharging"); return; }
ps_path(p, sizeof p, "battery/status");
read_file_txt(p, st, cap);
if (!strstr(st, "Charging") && !strstr(st, "Full")) {
ps_path(p, sizeof p, "ac/online");
if (read_file_int_def(p, 0) == 1) snprintf(st, cap, "Charging");
}
}
/* ---------------- 电池深加工(POWER_MANAGER_DESIGN §3.1 + POWER_HW_FACTS 项 1) ---------------- */
typedef struct {
int pct; /* -1=未知 */
int mv; /* -1=未知(voltage_now µV→mV) */
char state[32]; /* Charging/Discharging/Full(权威链) */
int charging; /* state 含 Charging/Full */
char health[16]; /* battery/health 字符串(如 Good);""=无 → null */
char tech[24]; /* battery/technology;""=无 → null */
int temp_c10; /* battery/temp(本机无 → -1 → null) */
int charger_temp_c10; /* mp2731-charger/temp(-1=无);÷10=℃ */
int cycle; /* battery/cycle_count(本机无 → -1 → null) */
int chg_cur_ua; /* mp2731-charger/current_now(µA;放电恒 0,充电=充电电流;-1=无) */
int chg_vol_uv; /* mp2731-charger/constant_charge_voltage(µV;-1=无)→ 充电功率实测 */
} batt_t;
static void batt_read(batt_t *b)
{
char p[192];
memset(b, 0, sizeof *b);
b->pct = -1; b->mv = -1; b->temp_c10 = -1; b->charger_temp_c10 = -1; b->cycle = -1;
b->chg_cur_ua = -1; b->chg_vol_uv = -1;
ps_path(p, sizeof p, "battery/capacity");
b->pct = read_file_int_def(p, -1);
ps_path(p, sizeof p, "battery/voltage_now");
int uv = read_file_int_def(p, -1);
if (uv >= 0) b->mv = uv / 1000;
charging_status_str(b->state, sizeof b->state);
b->charging = (strstr(b->state, "Charging") || strstr(b->state, "Full")) ? 1 : 0;
ps_path(p, sizeof p, "battery/health");
read_file_txt(p, b->health, sizeof b->health);
ps_path(p, sizeof p, "battery/technology");
read_file_txt(p, b->tech, sizeof b->tech);
ps_path(p, sizeof p, "battery/temp");
b->temp_c10 = read_file_int_def(p, -1);
ps_path(p, sizeof p, "mp2731-charger/temp");
b->charger_temp_c10 = read_file_int_def(p, -1);
ps_path(p, sizeof p, "battery/cycle_count");
b->cycle = read_file_int_def(p, -1);
ps_path(p, sizeof p, "mp2731-charger/current_now");
b->chg_cur_ua = read_file_int_def(p, -1);
ps_path(p, sizeof p, "mp2731-charger/constant_charge_voltage");
b->chg_vol_uv = read_file_int_def(p, -1);
}
/* ---------------- 经验功耗模型(§3.2;系数全部经验初值,待真机标定) ---------------- */
typedef struct {
int nominal_mah; /* 标称容量 mAh(默认 1600:axp energy_full_design 旁证,待标定) */
int bl_low_mw; /* 背光低档(亮度 1-33%) */
int bl_mid_mw; /* 背光中档(34-66%) */
int bl_high_mw; /* 背光高档(67-100%) */
int panel_on_mw; /* 面板亮(背光>0 时附加) */
int wifi_unassoc_mw; /* WiFi up 未关联(v1 状态机无此档判据,预留) */
int wifi_idle_mw; /* WiFi 关联空闲 */
int wifi_tx_mw; /* WiFi 传输中(netdev 增量判) */
int bt_up_mw; /* BT up 空闲 */
int bt_conn_mw; /* BT 有连接(连接数判据期 2,预留) */
int audio_idle_mw; /* 音频链无流(FPGA 静态) */
int audio_play_mw; /* 音频链播放(DAC+FPGA) */
int cpu_full_mw; /* CPU 满载功耗(util%×此项) */
int baseline_mw; /* 基线(SoC/DDR/PMIC 静态) */
} pm_model_t;
static void model_defaults(pm_model_t *m)
{
/* 经验初值,全部待真机标定(POWER_HW_FACTS §1 项 7/8:标定实验未做)。 */
m->nominal_mah = 1600;
m->bl_low_mw = 150;
m->bl_mid_mw = 450;
m->bl_high_mw = 900;
m->panel_on_mw = 80;
m->wifi_unassoc_mw = 80;
m->wifi_idle_mw = 200;
m->wifi_tx_mw = 600;
m->bt_up_mw = 60;
m->bt_conn_mw = 200;
m->audio_idle_mw = 50;
m->audio_play_mw = 250;
m->cpu_full_mw = 400;
m->baseline_mw = 300;
}
typedef struct { const char *key; int *ptr; } model_ent_t;
/* 读模型 conf(key=value,# 注释;缺失键留默认)。返回 0=用了 conf,1=conf 缺失(纯内置默认)。 */
static int model_load(pm_model_t *m)
{
model_defaults(m);
model_ent_t ents[] = {
{ "nominal_mah", &m->nominal_mah },
{ "bl_low_mw", &m->bl_low_mw },
{ "bl_mid_mw", &m->bl_mid_mw },
{ "bl_high_mw", &m->bl_high_mw },
{ "panel_on_mw", &m->panel_on_mw },
{ "wifi_unassoc_mw", &m->wifi_unassoc_mw },
{ "wifi_idle_mw", &m->wifi_idle_mw },
{ "wifi_tx_mw", &m->wifi_tx_mw },
{ "bt_up_mw", &m->bt_up_mw },
{ "bt_conn_mw", &m->bt_conn_mw },
{ "audio_idle_mw", &m->audio_idle_mw },
{ "audio_play_mw", &m->audio_play_mw },
{ "cpu_full_mw", &m->cpu_full_mw },
{ "baseline_mw", &m->baseline_mw },
};
FILE *f = fopen(g_model_conf, "r");
if (!f) return 1;
char line[160];
while (fgets(line, sizeof line, f)) {
char *p = line;
while (*p == ' ' || *p == '\t') p++;
if (*p == '#' || *p == '\n' || !*p) continue;
char *eq = strchr(p, '=');
if (!eq) continue;
*eq = 0;
char *kend = eq;
while (kend > p && (kend[-1] == ' ' || kend[-1] == '\t')) *--kend = 0;
int v = atoi(eq + 1);
for (size_t i = 0; i < sizeof ents / sizeof ents[0]; i++)
if (!strcmp(p, ents[i].key)) { *ents[i].ptr = v; break; }
}
fclose(f);
return 0;
}
/* ---------------- 采样环(守护进程;容量差分速率 → 续航估算 §3.1/§3.3 derived;
* 期 2 同时服务 history 采样点:pct/est_mw/充电态/屏幕态/亮度,内存环形不落盘,重启清零) ---------------- */
typedef struct {
long mono; /* 采样时刻(monotonic 秒) */
int pct; /* 电量 % */
int est_mw; /* est_mw(-1=只读/未知) */
int charging; /* 0/1 */
int screen; /* 0=off 1=on(-1=未知) */
int brightness_pct; /* -1=未知 */
} hist_sample_t;
static hist_sample_t g_ring[RING_N];
static int g_rcount = 0; /* 已入环条数(≤RING_N;覆盖写最旧) */
static long mono_now(void)
{
struct timespec tp;
clock_gettime(CLOCK_MONOTONIC, &tp);
return tp.tv_sec;
}
static void ring_push(const batt_t *b, int est_mw, int screen, int bri)
{
if (b->pct < 0) return;
hist_sample_t s;
s.mono = mono_now();
s.pct = b->pct;
s.charging = b->charging;
s.est_mw = est_mw; s.screen = screen; s.brightness_pct = bri;
if (g_rcount < RING_N) g_ring[g_rcount++] = s;
else { memmove(g_ring, g_ring + 1, sizeof g_ring - sizeof g_ring[0]); g_ring[RING_N - 1] = s; }
}
/* 容量差分平均速率:rate_x100 = (Δpct×100)/min,放电为负、充电为正。
* 用环内最旧↔最新同向(同 charging 态)样本对;跨度 < g_rate_min_span 或 |Δpct|<1 → 不可得。
* 返回 1=有速率。 */
static int ring_rate(int *rate_x100)
{
if (g_rcount < 2) return 0;
const hist_sample_t *nw = &g_ring[g_rcount - 1];
const hist_sample_t *ol = NULL;
for (int i = 0; i < g_rcount - 1; i++)
if (g_ring[i].charging == nw->charging) { ol = &g_ring[i]; break; }
if (!ol) return 0;
long span = nw->mono - ol->mono;
int dpct = nw->pct - ol->pct;
if (span < g_rate_min_span || dpct == 0) return 0;
*rate_x100 = (int)((long)dpct * 100 * 60 / span);
if (*rate_x100 == 0) return 0;
return 1;
}
/* ---------------- 子系统状态读取(经验模型输入;全部只读) ---------------- */
/* 背光/面板:actual_brightness 归一 0-100%(量程读 max_brightness,本机 0-101,HW_FACTS 项 6)。
* 返回亮度%(-1=未知);*on = 面板亮(亮度>0;fb0/blank 不可读故以此代理息屏判定)。 */
static int bl_read_pct(int *on)
{
char p[192];
snprintf(p, sizeof p, "%s/max_brightness", g_bl_dir);
int mx = read_file_int_def(p, -1);
if (mx <= 0) { *on = -1; return -1; }
snprintf(p, sizeof p, "%s/actual_brightness", g_bl_dir);
int v = read_file_int_def(p, -1);
if (v < 0) {
snprintf(p, sizeof p, "%s/brightness", g_bl_dir);
v = read_file_int_def(p, -1);
}
if (v < 0) { *on = -1; return -1; }
if (v > mx) v = mx;
*on = v > 0 ? 1 : 0;
return v * 100 / mx;
}
/* 有界子进程命令(fork+pipe+poll+超时 SIGKILL,绝不阻塞采样拍;netgr 同源性)。
* 返回读到字节数(含尾 NUL 截断);-1=失败/超时。out 在 cap 内 NUL 结尾。
* ⚠ 仅 meow 全功能模式、每拍至多一两次(wpa_cli/hciconfig);只读模式绝不调用(门控红线)。 */
static int run_cmd_bounded(const char *cmd, char *out, size_t cap, int tmo_ms)
{
if (cap < 2) return -1;
int pfd[2];
if (pipe(pfd) != 0) return -1;
pid_t pid = fork();
if (pid < 0) { close(pfd[0]); close(pfd[1]); return -1; }
if (pid == 0) {
close(pfd[0]);
dup2(pfd[1], STDOUT_FILENO);
close(pfd[1]);
int nul = open("/dev/null", O_WRONLY);
if (nul >= 0) { dup2(nul, STDERR_FILENO); if (nul > 2) close(nul); }
execl("/bin/sh", "sh", "-c", cmd, (char *)NULL);
_exit(127);
}
close(pfd[1]);
size_t off = 0;
struct pollfd pf = { pfd[0], POLLIN, 0 };
for (;;) {
int pr = poll(&pf, 1, tmo_ms);
if (pr < 0) { if (errno == EINTR) continue; break; }
if (pr == 0) break; /* 超时 */
ssize_t r = read(pfd[0], out + off, cap - 1 - off);
if (r > 0) { off += (size_t)r; if (off >= cap - 1) break; }
else if (r == 0) break; /* EOF */
else if (errno == EINTR) continue;
else break;
}
close(pfd[0]);
kill(pid, SIGKILL); /* 已退出则 0,未退出强杀 */
waitpid(pid, NULL, 0); /* 收尸彻底 */
out[off] = 0;
return off > 0 ? (int)off : -1;
}
/* WiFi:0=off(接口不存在/operstate 非 up)1=idle 2=tx(netdev 增量>阈值)3=unassoc(up 但未关联)。
* 关联判定:/sys/class/net/wlan0/carrier(0=未关联,1=已关联,不可读则默认已关联——同 wpa 常态,
* 保留期 1 语义:up 即 idle 起算)。tx 由 netdev 计数器增量判(期 1 惯例)。 */
static int wifi_read_state(long *rx_out, long *tx_out)
{
char p[192];
snprintf(p, sizeof p, "%s/wlan0/operstate", g_net_dir);
char os[32] = "";
if (read_file_txt(p, os, sizeof os) <= 0 || strcmp(os, "up") != 0) return 0;
/* carrier 关联判定(0=未关联→unassoc;1 或不可读→已关联向下走 idle/tx 档) */
snprintf(p, sizeof p, "%s/wlan0/carrier", g_net_dir);
int carrier = read_file_int_def(p, 1); /* 缺省 1=已关联(期 1 语义) */
FILE *f = fopen(g_netdev, "r");
if (!f) { if (rx_out) *rx_out = -1; if (tx_out) *tx_out = -1; return carrier == 0 ? 3 : 1; }
char line[256];
long rx = -1, tx = -1;
while (fgets(line, sizeof line, f)) {
char *c = strchr(line, ':');
if (!c) continue;
*c = 0;
char *n = line;
while (*n == ' ' || *n == '\t') n++;
if (strcmp(n, "wlan0") != 0) continue;
rx = strtol(c + 1, NULL, 10);
char *save = NULL;
char *w = strtok_r(c + 1, " \t", &save);
for (int i = 0; w && i < 8; i++) w = strtok_r(NULL, " \t", &save);
if (w) tx = strtol(w, NULL, 10);
break;
}
fclose(f);
if (rx_out) *rx_out = rx;
if (tx_out) *tx_out = tx;
if (carrier == 0) return 3;
return 1; /* 传送档由调用方按增量判 2 */
}
/* BT:rfkill soft 块→off;hci0 存在且未被块→up;连接数档(期 2 细化见下)。 */
static int bt_read_state(void)
{
char p[448]; /* rfkill_dir + "/" + d_name(≤255) + "/type|/soft" 充裕 */
DIR *d = opendir(g_rfkill_dir);
int blocked = 0, seen = 0;
if (d) {
struct dirent *de;
while ((de = readdir(d))) {
if (strncmp(de->d_name, "rfkill", 6) != 0) continue;
char t[32] = "";
snprintf(p, sizeof p, "%s/%s/type", g_rfkill_dir, de->d_name);
if (read_file_txt(p, t, sizeof t) <= 0 || strcmp(t, "bluetooth") != 0) continue;
seen = 1;
snprintf(p, sizeof p, "%s/%s/soft", g_rfkill_dir, de->d_name);
if (read_file_int_def(p, 0) == 1) blocked = 1;
}
closedir(d);
}
snprintf(p, sizeof p, "%s/hci0", g_bt_dir);
int hci0 = access(p, F_OK) == 0;
if (blocked) return 0;
if (!hci0 && !seen) return 0; /* 无任何 BT 踪迹 → off */
if (!hci0) return 0;
/* hciconfig 实测 UP/RUNNING + ACL 连接数(期 1 只据 sysfs 存在→up 偏粗,期 2 细化)。
* hciconfig 不可用(宿主/qemu 桩无该命令)或 MEOW_PM_NO_HCICONFIG=1(宿主测试桩,防宿主机
* 自带 hciconfig/蓝牙控制器干扰断言)→ 回退 up(1),连接数无法判(诚实标注,期 3 接 hcitool)。 */
char out[320];
int r = -1;
if (!getenv("MEOW_PM_NO_HCICONFIG"))
r = run_cmd_bounded("PATH=/bin:/sbin:/usr/bin:/usr/sbin hciconfig -a hci0 2>/dev/null",
out, sizeof out, 900);
if (r > 0) {
if (strstr(out, "DOWN") && !strstr(out, "UP RUNNING")) return 0; /* 电源开但接口未 up */
const char *ah = strcasestr(out, "acl:"); /* 连接数 "ACL: N" / "acl: N" */
if (!ah) ah = strcasestr(out, "acl ");
if (ah) { int n = atoi(ah + 4); if (n > 0) return 2; } /* 有 ACL 连接 → connected */
return 1; /* up 空闲 */
}
return 1; /* hciconfig 不可用 → sysfs 存在=up(1) */
}
/* 音频链:meow 模式下经 audiod IPC(MSG_AUDIO_QUERY→MSG_AUDIO_STATE 文本)判 active。
* -1=audiod 不可达(unknown→0mW) 0=无流 1=播放中。Hiby 只读模式绝不调用(门控红线)。
* 注意:audiod dispatch 无 MSG_HELLO 分支(查询无需握手),直接发 QUERY。 */
static int x_write_full(int fd, const uint8_t *buf, size_t n);
static int x_read_full(int fd, uint8_t *buf, size_t n);
static int audio_query_active(void)
{
int fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (fd < 0) return -1;
struct timeval tv = { 1, 0 };
setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof tv);
setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof tv);
struct sockaddr_un a;
memset(&a, 0, sizeof a);
a.sun_family = AF_UNIX;
strncpy(a.sun_path, g_audio_sock, sizeof a.sun_path - 1);
if (connect(fd, (struct sockaddr *)&a, sizeof a) < 0) { close(fd); return -1; }
/* 直接 QUERY → STATE(无 HELLO 握手,见上注) */
uint8_t hdr[MEOW_IPC_HDR_LEN];
uint32_t magic = MEOW_IPC_MAGIC, zero = 0, rid = 1;
uint16_t ver = MEOW_IPC_VERSION, t = MSG_AUDIO_QUERY;
memcpy(hdr + 0, &magic, 4); memcpy(hdr + 4, &ver, 2); memcpy(hdr + 6, &t, 2);
memcpy(hdr + 8, &rid, 4); memcpy(hdr + 12, &zero, 4);
if (x_write_full(fd, hdr, sizeof hdr) < 0) { close(fd); return -1; }
if (x_read_full(fd, hdr, sizeof hdr) < 0) { close(fd); return -1; }
uint32_t rlen;
uint16_t rt;
memcpy(&rt, hdr + 6, 2);
memcpy(&rlen, hdr + 12, 4);
if (rt != MSG_AUDIO_STATE || rlen > MEOW_IPC_MAX) { close(fd); return -1; }
uint8_t pl[MEOW_IPC_MAX];
if (rlen && x_read_full(fd, pl, rlen) < 0) { close(fd); return -1; }
close(fd);
pl[rlen < MEOW_IPC_MAX ? rlen : MEOW_IPC_MAX - 1] = 0;
return strstr((const char *)pl, "active=1") ? 1 : 0;
}
/* CPU util%(/proc/stat 差分;首次/失败 → -1) */
static int cpu_util_pct(long prev_total, long prev_idle, long *out_total, long *out_idle)
{
FILE *f = fopen(g_proc_stat, "r");
if (!f) return -1;
char line[256];
if (!fgets(line, sizeof line, f)) { fclose(f); return -1; }
fclose(f);
if (strncmp(line, "cpu ", 4) != 0) return -1;
long long v[8] = {0};
int n = sscanf(line + 4, "%lld %lld %lld %lld %lld %lld %lld %lld",
&v[0], &v[1], &v[2], &v[3], &v[4], &v[5], &v[6], &v[7]);
if (n < 4) return -1;
long idle = (long)(v[3] + v[4]); /* idle + iowait */
long total = 0;
for (int i = 0; i < 8; i++) total += (long)v[i];
*out_total = total; *out_idle = idle;
if (prev_total < 0 || total <= prev_total) return -1;
long dt = total - prev_total, di = idle - prev_idle;
if (dt <= 0) return -1;
int u = (int)((dt - di) * 100 / dt);
return u < 0 ? 0 : (u > 100 ? 100 : u);
}
/* ---------------- 估算结果 ---------------- */
typedef struct {
int total_mw;
int baseline_mw, cpu_mw, bl_mw, panel_mw, wifi_mw, bt_mw, audio_mw;
int cpu_util; /* -1=未知 */
int brightness_pct; /* -1=未知 */
int screen_on; /* 0=off 1=on -1=未知(actual_brightness>0 代理,期 2 历史用) */
int wifi_state; /* 0=off 1=idle 2=tx 3=unassoc */
int bt_state; /* 0=off 1=up 2=connected */
int audio_state; /* -1/0/1 */
int degraded; /* 1=Hiby 只读模式:不碰 wlan0/bt/声卡,分量全 0 */
} est_t;
/* 经验模型合成。full=0(Hiby 只读模式)→ 分量全 0 + degraded 标记(不碰 wlan0/bt/声卡)。
* prev_rx/prev_tx 非 NULL 时:作 netdev 增量基准(>4096B/拍→tx 档)并回存新计数(守护用);
* NULL(CLI 降级直读)→ 无基准,up 即 idle。 */
static void est_compute(const pm_model_t *m, int full, int cpu_util,
long *prev_rx, long *prev_tx, est_t *e)
{
memset(e, 0, sizeof *e);
e->cpu_util = -1; e->brightness_pct = -1; e->audio_state = -1; e->screen_on = -1;
if (!full) { e->degraded = 1; return; }
e->baseline_mw = m->baseline_mw;
/* 屏幕 */
int on = -1;
int bp = bl_read_pct(&on);
e->brightness_pct = bp;
e->screen_on = on;
if (on == 1) {
e->panel_mw = m->panel_on_mw;
e->bl_mw = bp <= 33 ? m->bl_low_mw : (bp <= 66 ? m->bl_mid_mw : m->bl_high_mw);
}
/* WiFi(0=off 1=idle 2=tx 3=unassoc;tx 仅对已关联档判) */
long rx = -1, tx = -1;
e->wifi_state = wifi_read_state(&rx, &tx);
if (e->wifi_state == 1) {
if (prev_rx && *prev_rx >= 0 && rx >= 0 &&
(rx - *prev_rx) + (tx - *prev_tx) > 4096)
e->wifi_state = 2;
}
e->wifi_mw = e->wifi_state == 2 ? m->wifi_tx_mw
: (e->wifi_state == 3 ? m->wifi_unassoc_mw
: (e->wifi_state == 1 ? m->wifi_idle_mw : 0));
if (prev_rx) { *prev_rx = rx; *prev_tx = tx; }
/* BT(0=off 1=up 2=connected) */
e->bt_state = bt_read_state();
e->bt_mw = e->bt_state == 2 ? m->bt_conn_mw
: (e->bt_state == 1 ? m->bt_up_mw : 0);
/* 音频链 */
e->audio_state = audio_query_active();
e->audio_mw = e->audio_state == 1 ? m->audio_play_mw
: (e->audio_state == 0 ? m->audio_idle_mw : 0);
/* CPU */
e->cpu_util = cpu_util;
if (cpu_util >= 0) e->cpu_mw = cpu_util * m->cpu_full_mw / 100;
e->total_mw = e->baseline_mw + e->cpu_mw + e->bl_mw + e->panel_mw
+ e->wifi_mw + e->bt_mw + e->audio_mw;
}
/* ---------------- JSON 构建(vsnprintf 助手法,截断分析免疫) ---------------- */
typedef struct { char *buf; size_t cap, off; } jw_t;
static void jadd(jw_t *w, const char *fmt, ...)
{
if (w->off >= w->cap - 1) return;
va_list ap;
va_start(ap, fmt);
int n = vsnprintf(w->buf + w->off, w->cap - w->off, fmt, ap);
va_end(ap);
if (n > 0) {
w->off += (size_t)n;
if (w->off >= w->cap) w->off = w->cap - 1;
}
}
static void jadd_int_or_null(jw_t *w, const char *key, int v)
{
if (v < 0) jadd(w, "\"%s\":null,", key);
else jadd(w, "\"%s\":%d,", key, v);
}
static void jadd_str_or_null(jw_t *w, const char *key, const char *s)
{
if (!s || !s[0]) jadd(w, "\"%s\":null,", key);
else jadd(w, "\"%s\":\"%s\",", key, s);
}
/* 续航/速率段(battery 子集内)。rate_x100 不可得 → 三字段全 null。 */
static void jadd_rate(jw_t *w, const batt_t *b, int have_rate, int rate_x100)
{
if (!have_rate || b->pct < 0) {
jadd(w, "\"rate_pct_min_x100\":null,\"est_minutes_left\":null,"
"\"est_minutes_full\":null,\"rate_confidence\":null,");
return;
}
jadd(w, "\"rate_pct_min_x100\":%d,", rate_x100);
if (rate_x100 < 0) { /* 放电 → 剩余分钟 */
jadd(w, "\"est_minutes_left\":%d,\"est_minutes_full\":null,",
b->pct * 100 / -rate_x100);
} else { /* 充电 → 充满分钟 */
jadd(w, "\"est_minutes_left\":null,\"est_minutes_full\":%d,",
(100 - b->pct) * 100 / rate_x100);
}
jadd(w, "\"rate_confidence\":\"derived\",");
}
/* 充电功率(mp2731 current_now 实测轨;POWER_HW_FACTS §0/项 2,期 2 接入 §3.3):
* charge_mw = constant_charge_voltage(µV) × current_now(µA) / 1e9。
* 仅当「权威链判定 charging && current_now>0 && constant_charge_voltage>0」时返回实测 mW,
* 否则返回 -1(≠ 容量差分/经验模型,诚实标注——充电功率可测,消费功耗不可测)。 */
static int pm_charge_mw(const batt_t *b)
{
if (!b->charging) return -1;
if (b->chg_cur_ua <= 0 || b->chg_vol_uv <= 0) return -1;
long long v = (long long)b->chg_vol_uv * b->chg_cur_ua;
return (int)(v / 1000000000LL);
}
/* 聚合 JSON(subset 位选段;0=全部)。守护/CLI 降级直读共用。
* have_rate/cpu_util/prev_rx/prev_tx 由调用方提供(守护=环/差分态;直读=瞬测)。 */
static void build_json(uint32_t subset, int full, const batt_t *b,
int have_rate, int rate_x100, const est_t *e,
char *out, size_t cap)
{
if (subset == 0) subset = PM_SUBSET_BATTERY | PM_SUBSET_ESTIMATE
| PM_SUBSET_BREAKDOWN | PM_SUBSET_POLICY;
jw_t w = { out, cap, 0 };
out[0] = 0;
jadd(&w, "{\"mode\":\"%s\",\"ts\":%ld,", full ? "meow" : "readonly", (long)time(NULL));
if (subset & PM_SUBSET_BATTERY) {
jadd(&w, "\"battery\":{");
jadd_str_or_null(&w, "state", b->state);
jadd_int_or_null(&w, "pct", b->pct);
jadd_int_or_null(&w, "mv", b->mv);
jadd_str_or_null(&w, "health", b->health);
jadd_str_or_null(&w, "technology", b->tech);
jadd_int_or_null(&w, "temp_c10", b->temp_c10);
jadd_int_or_null(&w, "charger_temp_c10", b->charger_temp_c10);
jadd_int_or_null(&w, "cycle_count", b->cycle);
jadd_int_or_null(&w, "chg_current_ua", b->chg_cur_ua);
jadd_int_or_null(&w, "chg_voltage_uv", b->chg_vol_uv);
jadd_rate(&w, b, have_rate, rate_x100);
jadd(&w, "\"nominal_mah_note\":\"nominal_mah 为模型系数(默认1600,待标定)\"}");
}
if (subset & PM_SUBSET_ESTIMATE) {
jadd(&w, ",\"estimate\":{");
if (e->degraded) {
jadd(&w, "\"est_mw\":null,\"confidence\":\"none\",\"degraded\":true,"
"\"charge_mw\":null,\"charge_confidence\":null}");
} else {
int cmw = pm_charge_mw(b);
jadd(&w, "\"est_mw\":%d,\"confidence\":\"estimated\",\"calibrated\":false,", e->total_mw);
if (cmw >= 0)
jadd(&w, "\"charge_mw\":%d,\"charge_confidence\":\"measured\"}", cmw);
else
jadd(&w, "\"charge_mw\":null,\"charge_confidence\":null}");
}
}
if (subset & PM_SUBSET_BREAKDOWN) {
jadd(&w, ",\"breakdown\":{");
if (e->degraded) {
jadd(&w, "\"degraded\":true}");
} else {
jadd(&w, "\"baseline_mw\":%d,", e->baseline_mw);
jadd(&w, "\"cpu_mw\":%d,", e->cpu_mw);
jadd_int_or_null(&w, "cpu_util_pct", e->cpu_util);
jadd(&w, "\"backlight_mw\":%d,\"panel_mw\":%d,", e->bl_mw, e->panel_mw);
jadd_int_or_null(&w, "brightness_pct", e->brightness_pct);
jadd(&w, "\"wifi_mw\":%d,\"wifi_state\":\"%s\",", e->wifi_mw,
e->wifi_state == 2 ? "tx"
: (e->wifi_state == 3 ? "unassoc"
: (e->wifi_state == 1 ? "idle" : "off")));
jadd(&w, "\"bt_mw\":%d,\"bt_state\":\"%s\",", e->bt_mw,
e->bt_state == 2 ? "connected" : (e->bt_state == 1 ? "up" : "off"));
jadd(&w, "\"audio_mw\":%d,\"audio_state\":\"%s\"", e->audio_mw,
e->audio_state == 1 ? "playing" : (e->audio_state == 0 ? "idle" : "unknown"));
jadd(&w, "}");
}
}
if (subset & PM_SUBSET_POLICY) jadd(&w, ",\"policy\":null"); /* 期 3 桩 */
jadd(&w, "}");
}
/* ---------------- IPC 帧读写(同 svc_power 约定,全小端) ---------------- */
static int x_read_full(int fd, uint8_t *buf, size_t n)
{
size_t off = 0;
while (off < n) {
ssize_t r = read(fd, buf + off, n - off);
if (r <= 0) return -1;
off += (size_t)r;
}
return 0;
}
static int x_write_full(int fd, const uint8_t *buf, size_t n)
{
size_t off = 0;
while (off < n) {
ssize_t w = write(fd, buf + off, n - off);
if (w <= 0) return -1;
off += (size_t)w;
}
return 0;
}
static int send_frame(int fd, uint16_t type, uint32_t req_id, const void *payload, uint32_t len)
{
uint8_t hdr[MEOW_IPC_HDR_LEN];
uint32_t magic = MEOW_IPC_MAGIC, rlen = len;
uint16_t ver = MEOW_IPC_VERSION, t = type;
memcpy(hdr + 0, &magic, 4);
memcpy(hdr + 4, &ver, 2);
memcpy(hdr + 6, &t, 2);
memcpy(hdr + 8, &req_id, 4);
memcpy(hdr + 12, &rlen, 4);
if (x_write_full(fd, hdr, MEOW_IPC_HDR_LEN) < 0) return -1;
if (len && payload && x_write_full(fd, payload, len) < 0) return -1;
return 0;
}
static int send_ack(int fd, uint32_t req_id, uint32_t code, uint32_t status)
{
m_ack_t a;
memset(&a, 0, sizeof a);
a.code = code;
a.status = status;
return send_frame(fd, MSG_ACK, req_id, &a, sizeof a);
}
static int send_err(int fd, uint32_t req_id, uint32_t code, const char *msg)
{
m_err_t e;
memset(&e, 0, sizeof e);
e.code = code;
uint16_t ml = msg ? (uint16_t)strlen(msg) : 0;
if (ml > 63) ml = 63;
e.msg_len = ml;
if (ml) memcpy(e.msg, msg, ml);
return send_frame(fd, MSG_ERR, req_id, &e, sizeof e);
}
static int send_pm_result(int fd, uint16_t rtype, uint32_t req_id, uint32_t subset, const char *json)
{
m_pm_result_t r;
memset(&r, 0, sizeof r);
r.code = MEOW_OK;
r.subset = subset;
size_t jl = strlen(json);
if (jl > sizeof r.json - 1) jl = sizeof r.json - 1;
r.json_len = (uint16_t)jl;
memcpy(r.json, json, jl);
r.json[jl] = 0;
return send_frame(fd, rtype, req_id, &r, (uint32_t)(12 + jl + 1));
}
/* ---------------- 守护进程态 ---------------- */
static int g_full_mode = 0; /* meow=1 全功能;0=只读降级(hiby/缺失) */
static long g_cpu_prev_total = -1, g_cpu_prev_idle = 0;
static long g_wifi_prev_rx = -1, g_wifi_prev_tx = 0; /* PM_QUERY 实时估计算的 WiFi 基线(查询驱动) */
static long g_hist_wifi_prev_rx = -1, g_hist_wifi_prev_tx = 0; /* 守护节拍估计算的 WiFi 基线(history 采样) */
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;
static int boot_mode_is_meow(void)
{
char buf[16] = "";
if (read_file_txt(g_boot_mode, buf, sizeof buf) <= 0) return 0; /* 缺失=只读等落地 */
return strcmp(buf, "meow") == 0;
}
/* ---------------- 电源事件流(守护进程;内存环形,重启清零,不落盘) ----------------
* 可检测:① 充电器插拔(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% 触发恢复事件) */
#define EV_BRI_JUMP 30 /* 背光跳变阈值(百分点) */
typedef struct {
long mono; /* 事件时刻(monotonic 秒) */
int type; /* 0=charger 1=lowbat 2=screen 3=brightness */
int arg; /* charger: new charging(0/1);lowbat: 0低 1极低 2恢复;screen: on(0/1);bri: new pct */
int pct; /* 事件时电量 % */
char txt[112]; /* 可读描述 */
} pm_event_t;
static pm_event_t g_events[EVENT_RING_N];
static int g_ev_count = 0; /* 累计事件数含滚出;展示取 min(count, EVENT_RING_N) */
static void ev_push(int type, int arg, int pct, const char *fmt, ...)
{
pm_event_t *dst;
if (g_ev_count < EVENT_RING_N) dst = &g_events[g_ev_count];
else { memmove(g_events, g_events + 1, sizeof g_events - sizeof g_events[0]); dst = &g_events[EVENT_RING_N - 1]; }
g_ev_count++;
memset(dst, 0, sizeof *dst);
dst->mono = mono_now(); dst->type = type; dst->arg = arg; dst->pct = pct;
va_list ap;
va_start(ap, fmt);
vsnprintf(dst->txt, sizeof dst->txt, fmt, ap);
va_end(ap);
}
static void detect_events(const batt_t *b, const est_t *e)
{
/* ① 充电器插拔 */
if (g_prev_charging >= 0 && g_prev_charging != b->charging) {
if (b->charging) ev_push(0, 1, b->pct, "插上充电器,开始充电 (state=%s)", b->state);
else ev_push(0, 0, b->pct, "拔出充电器,停止充电 (state=%s)", b->state);
}
/* ② 低电阈值(放电下降沿触发;充电回阈值恢复) */
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 < EV_LOW_REC && b->pct >= EV_LOW_REC)
ev_push(1, 2, b->pct, "电量回到 %d%%(≥%d%% 恢复)", b->pct, EV_LOW_REC);
}
/* ③ 屏幕亮灭 / ④ 背光跳变(只读模式 screen=-1 → 跳过;息屏时不算背光跳变) */
if (e->screen_on >= 0) {
if (g_prev_screen >= 0 && g_prev_screen != e->screen_on)
ev_push(2, e->screen_on, b->pct, e->screen_on ? "亮屏" : "息屏");
if (e->screen_on == 1 && e->brightness_pct >= 0 && g_prev_bri_pct >= 0 &&
((e->brightness_pct > g_prev_bri_pct ? e->brightness_pct - g_prev_bri_pct
: g_prev_bri_pct - e->brightness_pct)) >= EV_BRI_JUMP)
ev_push(3, e->brightness_pct, b->pct, "背光跳变 %d%%→%d%%(≥%d%%)",
g_prev_bri_pct, e->brightness_pct, EV_BRI_JUMP);
}
g_prev_charging = b->charging;
if (b->pct >= 0) g_prev_pct = b->pct;
g_prev_screen = e->screen_on;
g_prev_bri_pct = e->brightness_pct;
}
/* 采样拍:模式热切换 + 电池入环 + CPU 差分基准推进 + 守护节拍估算(history/events 用)。
* (PM_QUERY 的实时估算在 handle_conn 独立重算,基线 g_wifi_prev_* 查询驱动——保持期 1 已测语义。) */
static void daemon_tick(void)
{
int fm = boot_mode_is_meow();
if (fm != g_full_mode) {
svc_log("mode -> %s (%s %s)", fm ? "full(meow)" : "readonly",
g_boot_mode, fm ? "=meow" : "!=meow/missing");
g_full_mode = fm;
}
batt_t b;
batt_read(&b);
long t = -1, i = 0;
int cu = cpu_util_pct(g_cpu_prev_total, g_cpu_prev_idle, &t, &i);
g_cpu_prev_total = t; g_cpu_prev_idle = i;
est_t e;
est_compute(&g_model, g_full_mode, cu, &g_hist_wifi_prev_rx, &g_hist_wifi_prev_tx, &e);
ring_push(&b, e.degraded ? -1 : e.total_mw,
e.degraded ? -1 : e.screen_on,
e.degraded ? -1 : e.brightness_pct);
detect_events(&b, &e);
}
/* 期 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)
{
if (count == 0 || count > HIST_MAX_REPLY) count = HIST_MAX_REPLY;
int n = g_rcount < (int)count ? g_rcount : (int)count;
if (n < 0) n = 0;
int start_pct = -1, end_pct = -1, min_pct = 101, max_pct = -1, sum_mw = 0, mwden = 0;
long first_mono = 0, last_mono = 0;
if (g_rcount > 0) {
start_pct = g_ring[0].pct;
end_pct = g_ring[g_rcount - 1].pct;
first_mono = g_ring[0].mono;
last_mono = g_ring[g_rcount - 1].mono;
for (int i = 0; i < g_rcount; i++) {
if (g_ring[i].pct >= 0) {
if (g_ring[i].pct < min_pct) min_pct = g_ring[i].pct;
if (g_ring[i].pct > max_pct) max_pct = g_ring[i].pct;
}
if (g_ring[i].est_mw >= 0) { sum_mw += g_ring[i].est_mw; mwden++; }
}
if (min_pct == 101) min_pct = -1;
}
jw_t w = { out, cap, 0 };
out[0] = 0;
jadd(&w, "{\"kind\":\"history\",\"count\":%d,\"n_samples\":%d,\"window_sec\":%ld,"
"\"start_pct\":%d,\"end_pct\":%d,\"min_pct\":%d,\"max_pct\":%d,\"avg_mw\":%d,\"samples\":[",
n, g_rcount, g_rcount > 0 ? last_mono - first_mono : 0,
start_pct, end_pct, min_pct, max_pct, mwden ? sum_mw / mwden : -1);
int start = g_rcount - n;
for (int i = start; i < g_rcount; i++) {
const hist_sample_t *s = &g_ring[i];
jadd(&w, "%s[%ld,%d,%d,%d,%d,%d]", i > start ? "," : "",
s->mono, s->pct, s->est_mw, s->charging, s->screen, s->brightness_pct);
}
jadd(&w, "]}");
}
/* 期 2 · events:从事件环出最近最多 count 条(内存环形,重启清零)。
* JSON: {kind:events,count,events:[[ts,type,pct,txt],...]}
* 单事件最坏 ~160B(txt 长 ≤112)→ 回帧上限收紧到 EV_REPLY_MAX,防 JSON 撑爆 MEOW_IPC_MAX。 */
#define EV_REPLY_MAX ((int)((MEOW_IPC_MAX - 12 - 64) / 160))
static void build_events_json(uint32_t count, char *out, size_t cap)
{
if (count == 0) count = 20;
if ((int)count > EVENT_RING_N) count = EVENT_RING_N;
if ((int)count > EV_REPLY_MAX) count = (uint32_t)EV_REPLY_MAX;
int tot = g_ev_count < EVENT_RING_N ? g_ev_count : EVENT_RING_N;
int n = tot < (int)count ? tot : (int)count;
if (n < 0) n = 0;
int start = tot - n;
jw_t w = { out, cap, 0 };
out[0] = 0;
jadd(&w, "{\"kind\":\"events\",\"count\":%d,\"events\":[", n);
for (int i = start; i < tot; i++) {
const pm_event_t *ev = &g_events[i];
const char *tn = ev->type == 0 ? "charger"
: (ev->type == 1 ? "lowbat"
: (ev->type == 2 ? "screen" : "brightness"));
jadd(&w, "%s[%ld,\"%s\",%d,\"%s\"]", i > start ? "," : "",
ev->mono, tn, ev->pct, ev->txt);
}
jadd(&w, "]}");
}
static void handle_conn(int c)
{
for (;;) {
uint8_t hdr[MEOW_IPC_HDR_LEN];
if (x_read_full(c, hdr, sizeof hdr) < 0) break;
uint32_t magic, rlen, req_id;
uint16_t ver, type;
memcpy(&magic, hdr, 4);
memcpy(&ver, hdr + 4, 2);
memcpy(&type, hdr + 6, 2);
memcpy(&req_id, hdr + 8, 4);
memcpy(&rlen, hdr + 12, 4);
if (magic != MEOW_IPC_MAGIC || ver != MEOW_IPC_VERSION) {
send_err(c, req_id, MEOW_ERR_INVAL, "bad magic/ver"); break;
}
if (rlen > MEOW_IPC_MAX) {
send_err(c, req_id, MEOW_ERR_INVAL, "payload too big"); break;
}
uint8_t payload[MEOW_IPC_MAX];
if (rlen && x_read_full(c, payload, rlen) < 0) break;
if (type == MSG_HELLO) { send_ack(c, req_id, MEOW_OK, 0); continue; }
if (type == MSG_PM_QUERY) {
/* 读级:任意调用者放行(§6.1 鉴权分级:QUERY=读级;POLICY_SET 期 3 才接动作级)。
* 查询不推进采样环(速率由节拍样本积累,防查询刷屏污染时间窗);电池现读保证新鲜。 */
m_pm_query_t q;
memset(&q, 0, sizeof q);
if (rlen > sizeof q) rlen = sizeof q;
memcpy(&q, payload, rlen);
batt_t b;
batt_read(&b);
int rate_x100 = 0;
int have_rate = ring_rate(&rate_x100);
long t = -1, i = 0;
int cpu_util = cpu_util_pct(g_cpu_prev_total, g_cpu_prev_idle, &t, &i);
if (t >= 0) { g_cpu_prev_total = t; g_cpu_prev_idle = i; }
est_t e;
est_compute(&g_model, g_full_mode, cpu_util,
&g_wifi_prev_rx, &g_wifi_prev_tx, &e);
char json[MEOW_IPC_MAX];
build_json(q.subset, g_full_mode, &b, have_rate, rate_x100, &e,
json, sizeof json);
send_pm_result(c, MSG_PM_RESULT, req_id, q.subset, json);
continue;
}
if (type == MSG_PM_HISTORY) {
/* 期 2:历史采样查询(读级任意调用者)。req: u32 count */
uint32_t count = 0;
if (rlen >= 4) memcpy(&count, payload, 4);
char json[MEOW_IPC_MAX];
build_history_json(count, json, sizeof json);
send_pm_result(c, MSG_PM_HISTORY_RESULT, req_id, count, json);
continue;
}
if (type == MSG_PM_EVENTS) {
/* 期 2:电源事件查询(读级任意调用者)。req: u32 count */
uint32_t count = 0;
if (rlen >= 4) memcpy(&count, payload, 4);
char json[MEOW_IPC_MAX];
build_events_json(count, json, sizeof json);
send_pm_result(c, MSG_PM_EVENTS_RESULT, req_id, count, json);
continue;
}
send_err(c, req_id, MEOW_ERR_INVAL, "unknown type (期1 PM_QUERY / 期2 HISTORY/EVENTS)");
break;
}
}
static volatile sig_atomic_t g_stop = 0;
static void on_sig(int sig) { (void)sig; g_stop = 1; }
static int run_daemon(const char *sock)
{
signal(SIGPIPE, SIG_IGN);
signal(SIGTERM, on_sig);
signal(SIGINT, on_sig);
int lfd = socket(AF_UNIX, SOCK_STREAM, 0);
if (lfd < 0) { svc_log("socket fail: %s", strerror(errno)); return 1; }
struct sockaddr_un a;
memset(&a, 0, sizeof a);
a.sun_family = AF_UNIX;
strncpy(a.sun_path, sock, sizeof a.sun_path - 1);
unlink(sock);
if (bind(lfd, (struct sockaddr *)&a, sizeof a) < 0) {
svc_log("bind %s fail: %s", sock, strerror(errno));
close(lfd);
return 1;
}
chmod(sock, 0666);
if (listen(lfd, 8) < 0) { svc_log("listen fail: %s", strerror(errno)); close(lfd); return 1; }
model_load(&g_model);
g_full_mode = boot_mode_is_meow();
svc_log("meow_powermgr listening on %s (mode=%s, sample=%ds, model=%s)",
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; /* 立即首拍 */
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 (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 };
int pr = poll(&pfd, 1, wait_ms);
if (pr > 0 && (pfd.revents & POLLIN)) {
int c = accept(lfd, NULL, NULL);
if (c >= 0) { handle_conn(c); close(c); }
}
}
svc_log("meow_powermgr exit");
close(lfd);
unlink(sock);
return 0;
}
/* ---------------- CLI:极简 JSON 字段提取(自产 JSON 固定形状,不做通用解析) ---------------- */
static const char *jfind(const char *js, const char *key)
{
char pat[48];
snprintf(pat, sizeof pat, "\"%s\":", key);
const char *p = strstr(js, pat);
return p ? p + strlen(pat) : NULL;
}
/* 0=取到整数;1=字段为 null/缺失(*out 不动) */
static int jint(const char *js, const char *key, int *out)
{
const char *p = jfind(js, key);
if (!p || strncmp(p, "null", 4) == 0) return 1;
*out = atoi(p);
return 0;
}
static void jstr(const char *js, const char *key, char *out, size_t cap)
{
const char *p = jfind(js, key);
if (!p || strncmp(p, "null", 4) == 0 || *p != '"') { out[0] = 0; return; }
p++;
size_t n = 0;
while (p[n] && p[n] != '"' && n < cap - 1) { out[n] = p[n]; n++; }
out[n] = 0;
}
/* CLI 查询:优先 IPC(守护有容量差分速率);守护不可达 → 直读 sysfs 降级(rate=null,
* cpu_util 用 200ms 双采样瞬测,wifi 无增量基准则 idle)。返回 0=来自守护,1=降级直读。 */
static int pm_query_json(uint32_t subset, char *out, size_t cap)
{
int fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (fd >= 0) {
struct timeval tv = { 1, 500000 };
setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof tv);
setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof tv);
struct sockaddr_un a;
memset(&a, 0, sizeof a);
a.sun_family = AF_UNIX;
strncpy(a.sun_path, g_sock, sizeof a.sun_path - 1);
if (connect(fd, (struct sockaddr *)&a, sizeof a) == 0) {
if (send_frame(fd, MSG_HELLO, 1, NULL, 0) == 0) {
uint8_t hdr[MEOW_IPC_HDR_LEN];
uint8_t pl[sizeof(m_pm_result_t)];
if (x_read_full(fd, hdr, sizeof hdr) == 0) {
uint32_t rl;
memcpy(&rl, hdr + 12, 4);
if (rl > sizeof pl) rl = sizeof pl;
if (rl && x_read_full(fd, pl, rl) < 0) { close(fd); goto direct; }
m_pm_query_t q;
memset(&q, 0, sizeof q);
q.subset = subset;
if (send_frame(fd, MSG_PM_QUERY, 2, &q, sizeof q) == 0 &&
x_read_full(fd, hdr, sizeof hdr) == 0) {
uint16_t rt;
memcpy(&rt, hdr + 6, 2);
memcpy(&rl, hdr + 12, 4);
if (rt == MSG_PM_RESULT && rl >= 12 && rl <= sizeof pl &&
x_read_full(fd, pl, rl) == 0) {
m_pm_result_t r;
memset(&r, 0, sizeof r);
memcpy(&r, pl, rl);
size_t jl = r.json_len;
if (jl >= cap) jl = cap - 1;
memcpy(out, r.json, jl);
out[jl] = 0;
close(fd);
return 0;
}
}
}
}
}
close(fd);
}
direct:
{
batt_t b;
batt_read(&b);
pm_model_t m;
model_load(&m);
long t1 = -1, i1 = 0, t2, i2;
cpu_util_pct(-1, 0, &t1, &i1);
usleep(200 * 1000);
int cu = cpu_util_pct(t1, i1, &t2, &i2);
int full = boot_mode_is_meow();
est_t e;
est_compute(&m, full, cu, NULL, NULL, &e);
build_json(subset, full, &b, 0, 0, &e, out, cap);
return 1;
}
}
/* 期 2:history/events 通用 IPC 客户端(HELLO→ACK→req→RESULT 提取 JSON)。
* 返回 0=守护返回;-1=守护不可达/协议错。history/events 无直读降级——采样环/事件环在守护内存。 */
static int pm_ipc_json(uint16_t reqtype, const void *req, uint32_t reqlen,
uint16_t wanttype, char *out, size_t cap)
{
int fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (fd < 0) return -1;
struct timeval tv = { 1, 500000 };
setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof tv);
setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof tv);
struct sockaddr_un a;
memset(&a, 0, sizeof a);
a.sun_family = AF_UNIX;
strncpy(a.sun_path, g_sock, sizeof a.sun_path - 1);
int rc = -1;
if (connect(fd, (struct sockaddr *)&a, sizeof a) != 0) { close(fd); return -1; }
uint8_t hdr[MEOW_IPC_HDR_LEN];
uint8_t pl[sizeof(m_pm_result_t)];
if (send_frame(fd, MSG_HELLO, 1, NULL, 0) != 0) goto out;
if (x_read_full(fd, hdr, sizeof hdr) != 0) goto out; /* MSG_ACK 头 */
{ uint32_t al; memcpy(&al, hdr + 12, 4); if (al <= sizeof pl && al && x_read_full(fd, pl, al) != 0) goto out; } /* 丢 ACK 载荷 */
if (send_frame(fd, reqtype, 2, req, reqlen) != 0) goto out;
if (x_read_full(fd, hdr, sizeof hdr) != 0) goto out;
uint16_t rt; uint32_t rl;
memcpy(&rt, hdr + 6, 2);
memcpy(&rl, hdr + 12, 4);
if (rt != wanttype || rl < 12 || rl > sizeof pl) goto out;
if (x_read_full(fd, pl, rl) != 0) goto out;
{
m_pm_result_t r;
memset(&r, 0, sizeof r);
memcpy(&r, pl, rl);
size_t jl = r.json_len;
if (jl >= cap) jl = cap - 1;
memcpy(out, r.json, jl);
out[jl] = 0;
rc = 0;
}
out:
close(fd);
return rc;
}
/* 极简数组解析:跳跃分隔符后取一个整数,推进指针(自产 JSON 固定形状 [a,b,c])。 */
static long next_int2(const char **pp)
{
const char *p = *pp;
while (*p == ',' || *p == ' ' || *p == ']') p++;
long v = strtol(p, (char **)&p, 10);
*pp = p;
return v;
}
/* ---------------- CLI 子命令 ---------------- */
static void fmt_min(char *out, size_t cap, int min)
{
if (min >= 120) snprintf(out, cap, "%dh%02dm", min / 60, min % 60);
else snprintf(out, cap, "%dmin", min);
}
static int cli_status(void)
{
char js[MEOW_IPC_MAX];
int via = pm_query_json(PM_SUBSET_BATTERY, js, sizeof js);
char state[32], health[16], tech[24];
int pct = -1, mv = -1, rate = 0, el = -1, ef = -1, ctemp = -1, cycle = -1, temp = -1;
jstr(js, "state", state, sizeof state);
jstr(js, "health", health, sizeof health);
jstr(js, "technology", tech, sizeof tech);
jint(js, "pct", &pct);
jint(js, "mv", &mv);
jint(js, "temp_c10", &temp);
jint(js, "charger_temp_c10", &ctemp);
jint(js, "cycle_count", &cycle);
int have_rate = jint(js, "rate_pct_min_x100", &rate) == 0;
jint(js, "est_minutes_left", &el);
jint(js, "est_minutes_full", &ef);
char mode[16];
jstr(js, "mode", mode, sizeof mode);
printf("state=%s cap=%d%%", state[0] ? state : "?", pct);
if (mv >= 0) printf(" mv=%d", mv);
printf(" health=%s", health[0] ? health : "null");
printf(" tech=%s", tech[0] ? tech : "null");
if (temp >= 0) printf(" temp=%d.%dC", temp / 10, temp % 10);
else printf(" temp=null");
if (ctemp >= 0) printf(" charger_temp=%d.%dC", ctemp / 10, ctemp % 10);
if (cycle >= 0) printf(" cycle=%d", cycle);
if (have_rate) {
printf(" rate=%s%d.%02d%%/min", rate < 0 ? "-" : "+",
rate < 0 ? -rate / 100 : rate / 100,
rate < 0 ? -rate % 100 : rate % 100);
char tb[24];
if (el >= 0) { fmt_min(tb, sizeof tb, el); printf(" est_left=%s", tb); }
else printf(" est_left=null");
if (ef >= 0) { fmt_min(tb, sizeof tb, ef); printf(" est_full=%s", tb); }
else printf(" est_full=null");
printf(" conf=derived");
} else {
printf(" rate=null est_left=null est_full=null");
}
printf(" mode=%s src=%s\n", mode[0] ? mode : "?", via == 0 ? "daemon" : "direct");
if (via != 0)
fprintf(stderr, "note: powermgr 守护不可达,降级直读(无容量差分速率 → rate=null)\n");
return 0;
}
static int cli_estimate(void)
{
char js[MEOW_IPC_MAX];
int via = pm_query_json(PM_SUBSET_ESTIMATE | PM_SUBSET_BREAKDOWN, js, sizeof js);
if (strstr(js, "\"degraded\":true")) {
printf("degraded: HibyOS 只读模式——只采样电池,不施功耗估算(不碰 wlan0/bt/声卡)\n");
return 0;
}
int total = -1, base = 0, cpu = 0, cu = -1, bl = 0, panel = 0, bp = -1, wifi = 0, bt = 0, audio = 0;
char conf[24], ws[12], bs[12], as[12];
jint(js, "est_mw", &total);
jstr(js, "confidence", conf, sizeof conf);
jint(js, "baseline_mw", &base);
jint(js, "cpu_mw", &cpu);
jint(js, "cpu_util_pct", &cu);
jint(js, "backlight_mw", &bl);
jint(js, "panel_mw", &panel);
jint(js, "brightness_pct", &bp);
jint(js, "wifi_mw", &wifi);
jint(js, "bt_mw", &bt);
jint(js, "audio_mw", &audio);
jstr(js, "wifi_state", ws, sizeof ws);
jstr(js, "bt_state", bs, sizeof bs);
jstr(js, "audio_state", as, sizeof as);
printf("est_mw=%d confidence=%s calibrated=false (系数全部经验初值,待真机标定)\n",
total, conf[0] ? conf : "estimated");
/* 期 2:mp2731 current_now 实测充电功率(仅充电态且 current_now>0 → measured;否则 null) */
{
int cmw = -1; char cc[16];
jint(js, "charge_mw", &cmw);
jstr(js, "charge_confidence", cc, sizeof cc);
if (cmw >= 0)
printf("charge=+%dmW confidence=%s (mp2731 current_now×constant_charge_voltage 实测;系统消费侧无实测节点仍为 estimated)\n",
cmw, cc[0] ? cc : "measured");
else
printf("charge=null confidence=null (非充电态或无 current_now——充电功率无实测轨)\n");
}
char cu_s[16], bp_s[16];
if (cu >= 0) snprintf(cu_s, sizeof cu_s, "%d%%", cu); else snprintf(cu_s, sizeof cu_s, "null");
if (bp >= 0) snprintf(bp_s, sizeof bp_s, "%d%%", bp); else snprintf(bp_s, sizeof bp_s, "null");
printf("baseline=%d cpu=%d(util=%s) backlight=%d panel=%d(bri=%s) wifi=%d(%s) bt=%d(%s) audio=%d(%s)\n",
base, cpu, cu_s, bl, panel, bp_s,
wifi, ws[0] ? ws : "?", bt, bs[0] ? bs : "?", audio, as[0] ? as : "?");
if (via != 0)
fprintf(stderr, "note: powermgr 守护不可达,降级直读(cpu_util=200ms 瞬测,wifi 无增量判 tx)\n");
return 0;
}
static int cli_model(void)
{
pm_model_t m;
int def = model_load(&m);
printf("# meow_powermgr 经验功耗模型(POWER_MANAGER_DESIGN §3.2)\n");
printf("# 系数来源: %s\n", g_model_conf);
printf("# %s\n", def ? "⚠ conf 缺失 → 以上为内置默认(待标定)" : "conf 读取成功");
printf("# ⚠ 全部系数为经验初值,待真机标定(POWER_HW_FACTS §1 项 7/8 标定实验未做)\n");
printf("nominal_mah=%d\n", m.nominal_mah);
printf("baseline_mw=%d\n", m.baseline_mw);
printf("bl_low_mw=%d\n", m.bl_low_mw);
printf("bl_mid_mw=%d\n", m.bl_mid_mw);
printf("bl_high_mw=%d\n", m.bl_high_mw);
printf("panel_on_mw=%d\n", m.panel_on_mw);
printf("wifi_unassoc_mw=%d\n", m.wifi_unassoc_mw);
printf("wifi_idle_mw=%d\n", m.wifi_idle_mw);
printf("wifi_tx_mw=%d\n", m.wifi_tx_mw);
printf("bt_up_mw=%d\n", m.bt_up_mw);
printf("bt_conn_mw=%d\n", m.bt_conn_mw);
printf("audio_idle_mw=%d\n", m.audio_idle_mw);
printf("audio_play_mw=%d\n", m.audio_play_mw);
printf("cpu_full_mw=%d\n", m.cpu_full_mw);
return 0;
}
/* 期 2 · history:最近 N 个采样点 + 全环窗口汇总(不落盘,重启清零) */
static int cli_history(int n, char **v)
{
uint32_t count = 0;
if (n >= 1) { int c = atoi(v[0]); if (c > 0) count = (uint32_t)c; }
char js[MEOW_IPC_MAX];
if (pm_ipc_json(MSG_PM_HISTORY, &count, sizeof count, MSG_PM_HISTORY_RESULT, js, sizeof js) != 0) {
fprintf(stderr, "powermgr 守护不可达:history 采样环在守护内存,需 meow_powermgr 常驻\n");
return 1;
}
int cnt = 0, ns = 0, sp = -1, ep = -1, mnp = -1, mxp = -1, avg = -1;
long win = 0;
jint(js, "count", &cnt); jint(js, "n_samples", &ns);
jint(js, "start_pct", &sp); jint(js, "end_pct", &ep);
jint(js, "min_pct", &mnp); jint(js, "max_pct", &mxp); jint(js, "avg_mw", &avg);
{ int wi = 0; jint(js, "window_sec", &wi); win = wi; }
printf("history: %d 条采样点(环共 %d,窗口 %lds=%.2fh)\n", cnt, ns, win, win / 3600.0);
if (sp >= 0)
printf(" 电量: 起 %d%% 止 %d%% 低 %d%% 高 %d%% 平均功耗 %dmW(estimated/derived,系数未标定)\n",
sp, ep, mnp, mxp, avg);
long ts[80]; int pc[80], mw[80], cg[80], sc[80], br[80];
int m = 0;
const char *p = strstr(js, "\"samples\":[");
if (p) {
p += 11; /* "\"samples\":[" 长 11 */
while (m < 80 && *p == '[') {
p++; /* '[' */
ts[m] = next_int2(&p);
pc[m] = (int)next_int2(&p);
mw[m] = (int)next_int2(&p);
cg[m] = (int)next_int2(&p);
sc[m] = (int)next_int2(&p);
br[m] = (int)next_int2(&p);
m++;
if (*p == ']') { p++; if (*p == ',') p++; }
}
}
printf("\n%-10s %5s %6s %5s %5s %6s\n", "time", "cap%", "est_mW", "chg", "scr", "bri");
long newest = m > 0 ? ts[m - 1] : 0;
for (int i = 0; i < m; i++) {
char tb[24], bs[16], bri_s[16];
long off = newest - ts[i]; /* 距最新采样(秒,≥0) */
if (off >= 7200) snprintf(tb, sizeof tb, "-%ldh%02ldm", off / 3600, (off % 3600) / 60);
else snprintf(tb, sizeof tb, "-%ldmin", off / 60);
snprintf(bs, sizeof bs, "%s", cg[i] ? "Chg" : "Dis");
if (br[i] < 0) snprintf(bri_s, sizeof bri_s, "?"); else snprintf(bri_s, sizeof bri_s, "%d%%", br[i]);
printf("%-10s %5d %6d %5s %5s %6s\n", tb, pc[i], mw[i], bs,
sc[i] == 1 ? "On" : (sc[i] == 0 ? "Off" : "?"), bri_s);
}
fprintf(stderr, "note: est_mW/scr/bri 为守护 %ds 节拍估算;只读模式下为 -1/off/未知(只采样电池)\n",
g_sample_sec);
return 0;
}
/* 期 2 · events:电源事件流(内存环形,重启清零) */
static int cli_events(int n, char **v)
{
uint32_t count = 0;
if (n >= 1) { int c = atoi(v[0]); if (c > 0) count = (uint32_t)c; }
char js[MEOW_IPC_MAX];
if (pm_ipc_json(MSG_PM_EVENTS, &count, sizeof count, MSG_PM_EVENTS_RESULT, js, sizeof js) != 0) {
fprintf(stderr, "powermgr 守护不可达:events 事件环在守护内存,需 meow_powermgr 常驻\n");
return 1;
}
int cnt = 0; jint(js, "count", &cnt);
printf("events: %d 条(内存环形,重启清零;App 前后台切换无可轮询导出点——期2诚实标注不支持)\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;
const char *p = strstr(js, "\"events\":[");
if (p) {
p += 10; /* "\"events\":[" 长 10 */
while (m < 64 && *p == '[') {
p++; /* '[' */
ts[m] = next_int2(&p);
while (*p && *p != '"') p++;
if (*p == '"') p++;
int k = 0;
while (*p && *p != '"' && k < 15) tn[m][k++] = *p++;
tn[m][k] = 0; if (*p == '"') p++;
if (*p == ',') p++;
pct[m] = (int)next_int2(&p);
while (*p && *p != '"') p++;
if (*p == '"') p++;
k = 0;
while (*p && *p != '"' && k < 127) txt[m][k++] = *p++;
txt[m][k] = 0; if (*p == '"') p++;
m++;
if (*p == ']') { p++; if (*p == ',') p++; }
}
}
long newest = m > 0 ? ts[m - 1] : 0;
for (int i = m - 1; i >= 0; i--) { /* 最新在前 */
char tb[24];
long off = newest - ts[i];
if (off >= 7200) snprintf(tb, sizeof tb, "-%ldh%02ldm", off / 3600, (off % 3600) / 60);
else snprintf(tb, sizeof tb, "-%ldmin", off / 60);
printf("%-8s %-10s %3d%% %s\n", tb, tn[i], pct[i], txt[i]);
}
return 0;
}
/* 期 2 · top:功耗分解实时排序视图(2s 刷新,Ctrl-C 退;MEOW_PM_TOP_ONCE=1 一次输出用于测试)。 */
static int cli_top(void)
{
static const char *name[6] = { "baseline", "screen", "wifi", "bt", "audio", "cpu" };
signal(SIGINT, on_sig);
signal(SIGTERM, on_sig);
int once = getenv("MEOW_PM_TOP_ONCE") != NULL;
do {
char js[MEOW_IPC_MAX];
pm_query_json(PM_SUBSET_BREAKDOWN, js, sizeof js);
if (strstr(js, "\"degraded\":true")) {
printf("degraded: HibyOS 只读模式——不施功耗分解(只采样电池)\n");
return 0;
}
int b = 0, cpu = 0, w = 0, bt = 0, au = 0, bl = 0, panel = 0;
jint(js, "baseline_mw", &b); jint(js, "cpu_mw", &cpu);
jint(js, "wifi_mw", &w); jint(js, "bt_mw", &bt);
jint(js, "audio_mw", &au); jint(js, "backlight_mw", &bl); jint(js, "panel_mw", &panel);
int mw[6] = { b, bl + panel, w, bt, au, cpu, };
int total = 0;
for (int i = 0; i < 6; i++) total += mw[i];
int idx[6] = { 0, 1, 2, 3, 4, 5 };
for (int i = 0; i < 5; i++)
for (int j = i + 1; j < 6; j++)
if (mw[idx[j]] > mw[idx[i]]) { int t = idx[i]; idx[i] = idx[j]; idx[j] = t; }
if (!once) printf("\033[2J\033[H"); /* 清屏(仅终端) */
printf("=== meow_power top 功耗分解 总 %dmW (confidence=estimated, 系数待真机标定) ===\n", total);
for (int i = 0; i < 6; i++) {
int k = idx[i];
int pctv = total > 0 ? mw[k] * 100 / total : 0;
int nb = pctv * 40 / 100;
char bar[41];
for (int x = 0; x < nb; x++) bar[x] = '#';
bar[nb] = 0;
printf("%-9s %6dmW %3d%% |%-40s|\n", name[k], mw[k], pctv, bar);
}
fflush(stdout);
if (once) break;
for (int sl = 0; sl < 20 && !g_stop; sl++) usleep(100 * 1000); /* 2s,可中断 */
} while (!g_stop);
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桩)";
}
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, "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");
fprintf(stderr, "usage: meow_power <%s>\n", cli_usage());
return 1;
}
/* ---------------- main:单一二进制双形态 ---------------- */
int main(int argc, char **argv)
{
const char *e;
if ((e = getenv("MEOW_PS_DIR")) && e[0]) snprintf(g_ps_dir, sizeof g_ps_dir, "%s", e);
if ((e = getenv("MEOW_BL_DIR")) && e[0]) snprintf(g_bl_dir, sizeof g_bl_dir, "%s", e);
if ((e = getenv("MEOW_NET_DIR")) && e[0]) snprintf(g_net_dir, sizeof g_net_dir, "%s", e);
if ((e = getenv("MEOW_RFKILL_DIR")) && e[0]) snprintf(g_rfkill_dir, sizeof g_rfkill_dir, "%s", e);
if ((e = getenv("MEOW_BT_DIR")) && e[0]) snprintf(g_bt_dir, sizeof g_bt_dir, "%s", e);
if ((e = getenv("MEOW_NETDEV")) && e[0]) snprintf(g_netdev, sizeof g_netdev, "%s", e);
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_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);
if ((e = getenv("MEOW_PM_MIN_SPAN_SEC")) && atoi(e) > 0) g_rate_min_span = atoi(e);
int i = 1;
for (; i < argc; i++) {
if (!strcmp(argv[i], "--sock") && i + 1 < argc) g_sock = argv[++i];
else if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help")) {
printf("meow_powermgr / meow_power (%s)\n", cli_usage());
return 0;
}
else break;
}
/* argv[0] basename=meow_power → 强制 CLI(双形态);meow_powermgr 空参 → 守护常驻。 */
const char *base = strrchr(argv[0], '/');
base = base ? base + 1 : argv[0];
if (!strcmp(base, "meow_power")) {
if (i >= argc) { fprintf(stderr, "usage: meow_power <%s>\n", cli_usage()); return 1; }
return cli_run(argc - i, argv + i);
}
if (i >= argc) return run_daemon(g_sock); /* svcd 空参 → IPC 常驻 */
return cli_run(argc - i, argv + i);
}