Files
MeowOS-For-R3P2/patch_v2/meow_powermgr/meow_powermgr.c
T
MeowOS 250c9ae8b1 feat(powermgr): meow_powermgr 期0调研+期1骨架——功耗管理专项服务(POWER_MANAGER_DESIGN)
期0: POWER_HW_FACTS.md 真机只读取证——battery/无current_now(实时功率=derived+estimated
双轨)、无cpufreq(省电档无降频点)、背光量程0-101(非255)、rfkill可用、axp_battery为
幻影设备、无thermal zones、audiod STATE字段完备。

期1: patch_v2/meow_powermgr/ 单二进制双形态(meow_powermgr守护+meow_power CLI):
- svcd托管 trigger=boot/restart=on-fail;HibyOS只读降级(/tmp/meow_boot_mode!=meow
  只采样电池,不碰wlan0/bt/声卡,热切换)
- 权威充电链0-diff照抄svc_power(mp2731→fusb302→battery兜底)
- 电池深加工 state/cap/mV/health/续航估算(容量差分速率环 30s×240)
- 经验功耗模型(/usr/data/meow/power_model.conf 可调,内置默认全部标注待标定)
- IPC /usr/data/meow_pm.sock MSG_PM_QUERY(0x00D0)→MSG_PM_RESULT(0x00D1) JSON,
  subset位;meow_ipc.h 0x00D0-0x00D7 号段回填
- CLI status/estimate/model 落地(IPC优先+直读降级),history/top/events/policy 期2/3桩
- 宿主桩测试36+1例全过(MEOW_PS_DIR等假节点)+qemu-mipsel全过+真机对照svc_power
  battery口径一致(93%/4270mV);mipsel静态117KB(<200KB);-Wall -Wextra 0 warning
2026-09-01 16:21:12 +08:00

1072 lines
44 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,期 1 骨架)。
*
* 定位(三服务职责划分,§1):svc_power=一次性系统动作;svc_perf=性能只读聚合;
* **powermgr=功耗**——电池深加工(state/cap/mV/续航估算)、经验功耗模型估算与分解。
* 期 1 范围(§9):svcd 托管骨架 + IPC(MSG_PM_QUERY/RESULT) + CLI status/estimate/model +
* 权威充电链复用 + 经验模型;history/top/events/policy 为期 2/3 桩。
*
* 单一二进制双形态(同 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 "meow_ipc.h" /* 帧格式/消息号 + MSG_PM 系列 + PM_SUBSET_* + m_pm_*(号段 0x00D0-0x00D7) */
/* ---------------- 路径(全部可经 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/power_model.conf"; /* MEOW_PM_MODEL */
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 240 /* 采样环容量(30s×240=2h) */
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) */
} 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;
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);
}
/* ---------------- 经验功耗模型(§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) ---------------- */
typedef struct { long mono; int pct; int charging; } sample_t;
static 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)
{
if (b->pct < 0) return;
sample_t s;
s.mono = mono_now();
s.pct = b->pct;
s.charging = b->charging;
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 sample_t *nw = &g_ring[g_rcount - 1];
const 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;
}
/* WiFi:0=off(接口不存在/operstate 非 up)1=idle 2=tx(netdev 增量>阈值)。
* 关联/未关联档 v1 无判据(要 wpa_cli,期 2 再细化)——up 即按 idle 起算,诚实标注。 */
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;
FILE *f = fopen(g_netdev, "r");
if (!f) return 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 *tk = c + 1;
for (int i = 0; i < 8; i++) { tk = strchr(tk, ' '); if (!tk) break; while (*tk == ' ') tk++; }
/* 第 9 列 = tx bytes */
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;
return 1;
}
/* 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;
return 1; /* up 空闲档(连接判定期 2) */
}
/* 音频链: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 wifi_state; /* 0/1/2 */
int bt_state; /* 0/1 */
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;
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;
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 */
long rx = -1, tx = -1;
e->wifi_state = wifi_read_state(&rx, &tx);
if (e->wifi_state > 0) {
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 : m->wifi_idle_mw;
}
if (prev_rx) { *prev_rx = rx; *prev_tx = tx; }
/* BT */
e->bt_state = bt_read_state();
e->bt_mw = e->bt_state ? 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\",");
}
/* 聚合 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_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}");
} else {
jadd(&w, "\"est_mw\":%d,\"confidence\":\"estimated\",\"calibrated\":false}", e->total_mw);
}
}
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 == 1 ? "idle" : "off"));
jadd(&w, "\"bt_mw\":%d,\"bt_state\":\"%s\",", e->bt_mw, e->bt_state ? "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, 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, MSG_PM_RESULT, 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;
static pm_model_t g_model;
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;
}
/* 采样拍:模式热切换 + 电池入环 + CPU 差分基准推进。(WiFi 计数基准由 est_compute 回存。) */
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);
ring_push(&b);
long t = -1, i = 0;
cpu_util_pct(g_cpu_prev_total, g_cpu_prev_idle, &t, &i);
g_cpu_prev_total = t; g_cpu_prev_idle = i;
}
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, req_id, q.subset, json);
continue;
}
send_err(c, req_id, MEOW_ERR_INVAL, "unknown type (期1仅 PM_QUERY)");
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;
}
}
/* ---------------- 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");
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", def ? "内置默认(conf 缺失)" : g_model_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;
}
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[小时]|top|events|policy(后四者期2/3桩)";
}
static int cli_run(int n, char **v)
{
const char *cmd = v[0];
(void)n; /* 子命令参数期 2/3 才用(history[小时] 等) */
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_stub("history", "2");
if (!strcmp(cmd, "top")) return cli_stub("top", "2");
if (!strcmp(cmd, "events")) return cli_stub("events", "2");
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);
}