From 250c9ae8b15fee5ea2d2d4ced6fe069dcba70d0a Mon Sep 17 00:00:00 2001 From: MeowOS Date: Tue, 1 Sep 2026 16:21:12 +0800 Subject: [PATCH] =?UTF-8?q?feat(powermgr):=20meow=5Fpowermgr=20=E6=9C=9F0?= =?UTF-8?q?=E8=B0=83=E7=A0=94+=E6=9C=9F1=E9=AA=A8=E6=9E=B6=E2=80=94?= =?UTF-8?q?=E2=80=94=E5=8A=9F=E8=80=97=E7=AE=A1=E7=90=86=E4=B8=93=E9=A1=B9?= =?UTF-8?q?=E6=9C=8D=E5=8A=A1=EF=BC=88POWER=5FMANAGER=5FDESIGN=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 期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 --- devdocs/plan/POWER_HW_FACTS.md | 101 ++ patch_v2/meow_launcherd/meow_ipc.h | 31 + patch_v2/meow_powermgr/build_powermgr.sh | 31 + patch_v2/meow_powermgr/meow_powermgr | Bin 0 -> 117628 bytes patch_v2/meow_powermgr/meow_powermgr.c | 1071 +++++++++++++++++ patch_v2/meow_powermgr/meow_powermgr_host | Bin 0 -> 76176 bytes .../services.d/meow_powermgr.conf | 15 + patch_v2/meow_powermgr/test_powermgr.sh | 162 +++ scripts/build/fw_content.conf | 2 + 9 files changed, 1413 insertions(+) create mode 100644 devdocs/plan/POWER_HW_FACTS.md create mode 100755 patch_v2/meow_powermgr/build_powermgr.sh create mode 100755 patch_v2/meow_powermgr/meow_powermgr create mode 100644 patch_v2/meow_powermgr/meow_powermgr.c create mode 100755 patch_v2/meow_powermgr/meow_powermgr_host create mode 100644 patch_v2/meow_powermgr/services.d/meow_powermgr.conf create mode 100755 patch_v2/meow_powermgr/test_powermgr.sh diff --git a/devdocs/plan/POWER_HW_FACTS.md b/devdocs/plan/POWER_HW_FACTS.md new file mode 100644 index 0000000..829ecfd --- /dev/null +++ b/devdocs/plan/POWER_HW_FACTS.md @@ -0,0 +1,101 @@ +# POWER_HW_FACTS — meow_powermgr 期 0 真机只读调研结论 + +> 调研时间:2026-09-01 15:43(设备 10.78.193.50,uptime 1:48,`/tmp/meow_boot_mode`=meow,固件 0.7.89+)。 +> 方式:全部只读(ls/cat/sysfs 读),未写任何节点、未碰进程、未重启。 +> 对照基准:`meow_svc_power battery` = `battery=94%(Discharging) 4275mV`。 + +## 0. 两个决定形态的关键结论(必读) + +| 问题 | 结论 | 影响 | +|---|---|---| +| `battery/current_now` 存在吗? | **不存在**。battery/ 全集无 current_now/charge_now/charge_full/charge_full_design/cycle_count/temp | 实时功率**不可能 measured**;走「容量差分 derived」+「经验模型 estimated」双轨(设计 §3.3 第三档)。健康度(charge_full/design)**不可算**,只能透传 `health` 字符串 | +| `cpufreq` 存在吗? | **不存在**(`/sys/devices/system/cpu/cpu0/cpufreq/` No such file;单核 cpu0) | 省电档**无降频控制点**;CPU 功耗项只能按 util×满载系数估算,无可控项(设计 §5.1 CPU 行降级为「仅观测」) | + +## 1. §7 清单逐项结论 + +### 项 1:battery/ 全集 +``` +capacity=94 health=Good present=1 status=Discharging(会撒谎,勿用) +technology=Li-ion voltage_now=4275000(µV) time_to_empty_now=8191 +``` +- `time_to_empty_now=8191`(=0x1FFF)为**恒定哨兵值**,不随负载变化 → 不可用,勿报。 +- 无 temp 节点 → 电池温度报 null(充电器温度见项 2 可替代参考)。 + +### 项 2:mp2731-charger/(权威充电态 + 充电功率可算) +``` +status=Discharging(权威源,设计 §3.1 已实测) online=0 temp=300(=30.0°C) +current_now=0(µA,放电时恒 0;充电时=充电电流) constant_charge_current=0/_max=4520000 +constant_charge_voltage=4200000/_max=4670000 charge_term_current=120000 +input_current_now=100000 input_current_limited=2000000 input_suspend=0 +input_voltage_settled=2280000 charge_control_limit=3000000 charge_full=0(无效) +real_type=USB typec_power_role=sink step_charging_enabled=1 +``` +- **充电功率可估**:`constant_charge_voltage × current_now / 1e6`(充电中 current_now 非 0,待插电实测确认非 0 时刻的值域)。 +- `charge_type` 节点不存在(清单原问的 charge_type 无)。 + +### 项 3:fusb302 / ac / usb / axp_battery +- `fusb302-typec-source/`:online✓(插线检测兜底)、voltage_now、current_max(PD 档位相关,本次=0)。 +- `ac/`:online=0;voltage_now=4600/current_now=1200 为**静态默认值**(online=0 时不变),仅 online 位可用。 +- `usb/`:online=0;current_now=780 静态默认。仅 online/present 位可用。 +- **`axp_battery/`:幻影设备(不可用)**——capacity=0、voltage_now=0、current_now=200000 **三次采样(间隔 5s)恒定不变** → 非实时电量计,是 AXP 遗留驱动的死数据。唯一有用信息:`energy_full_design=1600` + `voltage_max_design=4350000/min=3400000`——可作**标称容量参考**(1600mAh 级,与 R3 Pro II 官方 1600mAh 电池吻合;诚实标注:单位/口径未经证实,仅作续航估算的默认标称容量,可经 power_model.conf `nominal_mah` 覆盖)。 + +### 项 4:cpufreq —— **不存在**(见 §0) + +### 项 5:rfkill —— **存在且可用** +``` +rfkill0: type=bluetooth state=1 soft=0 hard=0 +rfkill1: type=wlan state=1 soft=0 hard=0 +rfkill2: type=wlan state=1 soft=0 hard=0 +rfkill3: type=bluetooth state=1 soft=0 hard=0 +``` +- 4 个 rfkill(2×wlan + 2×bluetooth,两个同类型并存——WiFi/BT combo 芯片双接口特征)。 +- **软关断控制点存在**(写 soft=1,本次未写只读 state)→ 期 3 策略层可用「rfkill soft block」替代 `ip link down`/`hciconfig down`(更省电)。**红线不变:策略层不直接 poke sysfs,需经 svc_power 类动作通道或新增授权写点,期 3 定案。** + +### 项 6:背光节点全集 +``` +max_brightness=101 brightness=0 actual_brightness=0 bl_power=0 type=raw +``` +- **⚠️ 量程是 0-101,不是设计 §3.2 假定的 0-255** → 功耗表背光分档按 0-101 归一(亮度%=brightness/101×100 再分档)。POWER_MANAGER_DESIGN §3.2 表「0-255」需修正为「0-101(R3 Pro II 实测)」。 +- 调查时刻 brightness=0(设备息屏中)→ **息屏态可用 actual_brightness==0 判定**(fb0/blank 读取为空串——驱动未实现 read,不可用)。 + +### 项 7/8:CPU 满载/背光/WiFi 标定实验 +- 本期 0 未执行:容量差分法需分钟级窗口 + 逐项对照(亮屏定档/stress 满载/WiFi 传输),且设备无 current_now 真值可借 → **留待用户在场配合的标定窗口**(每次实验 ≥10min)。 +- 方法已备:capacity 差分(94%→x%)÷ Δt × nominal_mah × 平均电压 = 平均功率;基线=息屏 WiFi 关联空闲。 +- 在标定完成前,一切 mW 数字均为**占位经验值**(设计 §10 诚实标注不变)。 + +### 项 9:WiFi 状态/计数器 +- `wpa_cli status` ✓:`wpa_state=COMPLETED`、bssid/freq/ssid/ip_address 全有(关联态可判)。 +- `/proc/net/dev` wlan0 收发计数器 ✓(bytes/packets 递增)→ 传输态由增量判,可行。 +- wpa_cli 无直接 scan 计数;扫网风暴可用 `wpa_cli scan_results` 时间戳或 wpa 日志——期 2 再定,标记**待验**。 + +### 项 10:蓝牙 +- `hciconfig hci0` ✓(Type: Primary Bus: UART,当前 DOWN,RX/TX 计数器有)。 +- `/sys/class/bluetooth/hci0/` ✓ → up/down + 连接数可判(连接数走 `hciconfig -a` 或 /sys 下 device 枚举,期 2 细化)。 + +### 项 11:audiod MSG_AUDIO_STATE +- `meow_audio status` 实测:`active=0 app=- sid=0 rate=44100 ch=2 fmt=- written=... played=... underrun=1 dropped=0 stall=0 txdrop=0 paused=0 route_mode=auto port=4 vol=44`。 +- **字段完备**:active(流活跃)/route/port/vol 全有 → 音频链功耗项输入够用(无流≈静态 / 播放≈DAC+FPGA 档)。 + +### 项 12:thermal zones —— **不存在** +- `/sys/class/thermal/` 目录本身不存在;`/sys/class/hwmon/` 也不存在。 +- 唯一温度源:`mp2731-charger/temp`=300(30.0°C,充电器芯片温度,非电池/SoC)。 +- → 「异常发热」辅助信号**无热源可用**,期 2 异常检测规则不含温度维度(或仅用充电器温度粗参考)。 + +## 2. 对设计文档的回填修正 + +1. **§3.2 背光量程 0-255 → 0-101**(max_brightness 实测)。 +2. **§3.3 实时功率**:current_now 路径(measured)**整层不可达**——v1 只有 derived(容量差分)+ estimated(经验模型)。UI 必须带 confidence 标注(设计已要求,此处确认强制执行)。 +3. **§3.1 健康度**:charge_full/design 不存在 → health 输出透传 `health` 字符串(Good),不输出百分比。 +4. **§3.1 续航估算**:以容量差分平均速率(%/min)为准;nominal_mah 默认 1600(axp energy_full_design 旁证,待标定)。 +5. **§5.1 CPU 行**:无 cpufreq → 「降频」行为项删除,CPU 仅观测。 +6. **§5.1 WiFi/BT 行**:rfkill soft block 可用作控制点候选(期 3 定案,v1 不动)。 +7. **息屏判定**:fb0/blank 不可读 → 用 `backlight_pwm0/actual_brightness==0` 代理(期 1 采纳)。 +8. **充电功率**:mp2731 `current_now`(µA) + `constant_charge_voltage`(µV) 可估充电功率(插电态待实测值域)。 + +## 3. 遗留 / 待验 + +- [ ] 插电态复测 mp2731 current_now/constant_charge_current 非零值域(充电功率估算校准)。 +- [ ] 标定实验(项 7/8):需用户在场,≥10min/项 × 约 5 项。 +- [ ] axp energy_full_design=1600 的单位口径(mAh? mWh?)——与官方 1600mAh 规格吻合但未经驱动源码证实。 +- [ ] wpa 扫网频率计数法(期 2)。 +- [ ] 时间校准对照:无 current_now,derived 功率的时间分辨率受 capacity 1% 粒度限制(1600mAh×1%=16mAh,~5-15min 才跳 1%)→ 续航估算 v1 精度受限,UI 需标注。 diff --git a/patch_v2/meow_launcherd/meow_ipc.h b/patch_v2/meow_launcherd/meow_ipc.h index 817a0b8..b4963ed 100644 --- a/patch_v2/meow_launcherd/meow_ipc.h +++ b/patch_v2/meow_launcherd/meow_ipc.h @@ -92,6 +92,15 @@ enum { MSG_AUDIO_STATE = 0x00C8, /* ↓ 状态快照文本(NUL 结尾;当前流/参数/帧数/underrun/路由/音量) */ MSG_AUDIO_ROUTE = 0x00C9, /* ↑ 手动路由(u32 port:0=auto 2=3.5 3=4.4;调试/面板用) */ MSG_AUDIO_VOLUME = 0x00CA, /* ↑ 音量(i32 pct:-1=查询 0-100=设置;v1 透传写 DAC numid=1/2) */ + /* ---- meow_powermgr 功耗管理服务(devdocs/plan/POWER_MANAGER_DESIGN.md §6.1):0x00D0-0x00D7 ---- + * 语义:功耗维度的查看/分析/管理(svc_power 管动作、svc_perf 管性能,powermgr 管功耗)。 + * 独立 socket /usr/data/meow_pm.sock(--sock/MEOW_PM_SOCK 覆盖)。鉴权分级同 svc_power: + * QUERY/HISTORY/EVENTS=读级任意调用者;POLICY_SET=动作级(期 3 落地时接 SO_PEERCRED 同源代码)。 */ + MSG_PM_QUERY = 0x00D0, /* ↑ 功耗聚合查询(m_pm_query_t{subset}:PM_SUBSET_* 位;0=全部) */ + MSG_PM_RESULT = 0x00D1, /* ↓ 聚合 JSON(电池深加工/功耗估算/分解/策略,形状对齐 §3/§4.1) */ + /* 0x00D2 MSG_PM_POLICY_GET / 0x00D3 MSG_PM_POLICY_SET —— 期 3(profile/低电阈值读改,SET 动作级鉴权) + * 0x00D4 MSG_PM_HISTORY(u32 hours)/ 0x00D5 MSG_PM_HISTORY_RESULT(统计 JSON)—— 期 2 + * 0x00D6 MSG_PM_EVENTS / 0x00D7 MSG_PM_EVENTS_RESULT(异常耗电事件清单)—— 期 2 */ MSG_SYS = 0x00F0 /* ↓ 系统事件(守护关闭/服务死亡/播放器状态) */ }; @@ -341,6 +350,28 @@ typedef struct m_power_status { char msg[96]; /* 结果/状态文本 */ } m_power_status_t; +/* ---- meow_powermgr 功耗管理(POWER_MANAGER_DESIGN §6.1;服务端 meow_powermgr)---- */ + +/* MSG_PM_QUERY 子集位(0=全部) */ +#define PM_SUBSET_BATTERY (1u << 0) /* 电池深加工:state/pct/mv/health/续航估算 */ +#define PM_SUBSET_ESTIMATE (1u << 1) /* 当前功耗估算:est_mw + confidence(estimated/derived) */ +#define PM_SUBSET_BREAKDOWN (1u << 2) /* 功耗分解:各子系统分量(§3.2 经验模型,系数待标定) */ +#define PM_SUBSET_POLICY (1u << 3) /* 策略档位快照(期 3;期 1 恒 null) */ + +/* MSG_PM_QUERY ↑:功耗聚合查询 */ +typedef struct m_pm_query { + uint32_t subset; /* 0=全部;PM_SUBSET_* 组合 */ +} m_pm_query_t; /* 4 B */ + +/* MSG_PM_RESULT ↓:聚合 JSON(payload 为 NUL 结尾 JSON 文本,同 m_perf_result_t 口径) */ +typedef struct m_pm_result { + uint32_t code; /* MEOW_OK / MEOW_ERR_* */ + uint32_t subset; /* 回显请求 subset */ + uint16_t json_len; /* json 有效字节(不含 NUL) */ + uint16_t pad; + char json[MEOW_IPC_MAX - 12]; /* 聚合 JSON(§3/§4.1 形状) */ +} m_pm_result_t; + /* ---- amsd 控制(AMSD_DESIGN §4.2)---- */ /* MSG_AMS_START →:请求 launcher 拉起某 App。app_id=包名(权威);activity 空=裸启动 diff --git a/patch_v2/meow_powermgr/build_powermgr.sh b/patch_v2/meow_powermgr/build_powermgr.sh new file mode 100755 index 0000000..ef6d46e --- /dev/null +++ b/patch_v2/meow_powermgr/build_powermgr.sh @@ -0,0 +1,31 @@ +#!/bin/bash +# build_powermgr.sh — meow_powermgr 交叉编译(mipsel 静态)+ 宿主编译可选。 +# +# 目标:R3 Pro II / MeowOS(mipsel-linux-muslsf,静态)。powermgr 仅 POSIX + Linux 基接口 +# (复用 meow_ipc.h 帧格式,无 launcher 依赖),单文件满足「固件内、不入卡」(<200KB 阈值)。 +# 单一二进制双形态:/usr/bin/meow_powermgr(svcd 空参=守护)+ /usr/bin/meow_power(CLI,同一份 +# 文件两次安装,argv[0] basename 分形态;fw_content.conf 两行同 src)。 +# +# 用法: +# ./build_powermgr.sh # 交叉编译 → patch_v2/meow_powermgr/meow_powermgr(mipsel 静态) +# ./build_powermgr.sh host # 宿主编译 → patch_v2/meow_powermgr/meow_powermgr_host +set -e +cd "$(dirname "$0")" +ROOT="$HOME/Project/R3ProII" +CC="$ROOT/.tools/zig-mipsel-cc" +I_HDR="$ROOT/patch_v2/meow_launcherd" # meow_ipc.h(帧/MSG_PM_*/PM_SUBSET_*/m_pm_*) + +if [ "$1" = "host" ]; then + HOST_CC="${CC_HOST:-cc}" + "$HOST_CC" -Os -Wall -Wextra -I"$I_HDR" meow_powermgr.c -o meow_powermgr_host + echo "== host build ==" + ls -la meow_powermgr_host + file meow_powermgr_host + exit 0 +fi + +"$CC" -Os -Wall -Wextra -static -I"$I_HDR" meow_powermgr.c -o meow_powermgr +echo "== meow_powermgr (mipsel static) ==" +ls -la meow_powermgr +md5sum meow_powermgr +file meow_powermgr diff --git a/patch_v2/meow_powermgr/meow_powermgr b/patch_v2/meow_powermgr/meow_powermgr new file mode 100755 index 0000000000000000000000000000000000000000..696459b427c969a749046e8178daee49b799c2ab GIT binary patch literal 117628 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

9_1owg`6tdM@bhUj8(v}C;x)C+ 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 Pr{Pb}GU|tz9N+#6^QeEl literal 0 HcmV?d00001 diff --git a/patch_v2/meow_powermgr/meow_powermgr.c b/patch_v2/meow_powermgr/meow_powermgr.c new file mode 100644 index 0000000..663e598 --- /dev/null +++ b/patch_v2/meow_powermgr/meow_powermgr.c @@ -0,0 +1,1071 @@ +#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 `(argv[0] 双形态)或 `meow_powermgr ` → 一次性 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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); +} diff --git a/patch_v2/meow_powermgr/meow_powermgr_host b/patch_v2/meow_powermgr/meow_powermgr_host new file mode 100755 index 0000000000000000000000000000000000000000..c7e923ae16166335ff61e9d290d4f0cfd1b736c8 GIT binary patch literal 76176 zcmeHv3wV@8mUeY_Zd?U|5CUVm12_;Ng!@I1&V`FB2qy6|>P)B8=_DWC{@+tyRp;v_J&ynL z{Ik#hJm06C`p#RYPF0;cb?Q`A)0-A8TWmBKn0!p^6~?KnRKzMWR%Z9|SlJxbpIO=W z*cg_A{3LuNUwoQpL?#}Yrl}+|A*FKLMSgrbTJXtK)=p`%$m=UvX;V>|GPO7SFSYf^ zc{TsYc~zoJL;Y0TI!aAtIcgr2J1NSY6y;>fMEhkb`$v7FPp!z0PsbsT9+?s^J(SWX zPxK=`H6xE6Gykjd((B5uKn0a+zg5kMPbUcl%2d{O6UtHl{?=!RU@R8(CDP{vQGrZF z^}KJxA%n($$&EL&(&m@%9{%SSe*F94?H$XE_m4UB{_xwTxLxH_+!d4EE>GRM$#%QX zQ9Wx$uHTzGRUH+QCFP0c{7s1oR=aZ8<*X()0-zj7HUCLe&<879kLrY1fZ|>I1}Jmke?kd1oS^9sJA=;o|i!WQ1B1!L;m0d{GJ5z?@fT0 zCcwW*Ape~N{P!i`|0DrEHvv8}0ZuyZYh3P6fNxHK|2jeYS0v!SBLV;N1o$r!$hj^7 z|D6f=XC~mkE&>0|3Ha|yz`r2(F^wv5(DC|O|)27?}euv$IMxZKZ zfU0&Q*XOiXusXln>8vFP|D(W~N=<#eM}SeUsW{Xq6wlj9V*Gw8&&LFH*3^OrReHQO=-(c2 zc|B~c&lPYod%4%gRTS{9vhkLARPNq^sj9R4D$t>F2d(|HSl-0IjuQQnK`qJe#=*C$^s_~UBFLZl7&Qg21o9v*f#_JJkw5hCpGQtY!F^d08JX%PK z(h~nJDaElWe0zvqO0tVOuimhxtd30TI>bBA@63JU@vIM?RH# zk<%uYC1|z0FGC0T8# zxHi5vYjE<}@|mE)~ew`hW%(rp9v{ zG&sd#^4Y1uGbD(#QG-XfdcgN<@JtPViw2i_QOZ7`!N+L$+cfz1H27f+E~XIAZP(yp z%5nUN1~+TugfzHCgP+vk*&6(m1|O@zyEXVY4bH^8Ap0M$!P7MO)f#+=2LHYW&(Po$ zJIKeZ!L{>rf(E}vG9%5?;FC1?91T8MgIhItt_EMK!KY~Ol^UG(H1fGkgJatneabZW zv?$70wFaNA!D}`63=O_cgU{69K@C1jgFm3bXKU~V4L(PM@6_P5cacw{2ESHidhMom_pN5_X`k;os4s=*U7l1a- z>#ct==xh!B51-tx?V%C0liN{-wpbphQ0@MSVPx?HqP&@|31*!8u~%d1seJ%pvyJ%BcSUw z^y8rSY3N;`4{GQ=pu-w^A86x(-unL;bhd_W23??`UjSXMq5lnZy@q}n^ga#!D(Hh6 z`VG)w4Sf)_@w(pn-vOPiq5l(ffrkEX(B&Gs19ZKH{s{Cw4gEXN2Q~C(pu;LH=kec~ zMz$pX=HiASnD0r-**k_coi(wX5au^Kv!v&A$- zFZW)C@*m$?ZdkNjrYv_MLwW7n$^{2S%MFy}@gEwoGLi$~9s;L$+dPY(^*Q zFds4YaGN-jsT3i<4f!vi4C1NPHH?*Vp8d(KoX0v^xmNJ}G@hp+xfN?}!xP!;Hy=Gd zTA2&!aBOhO+L{ zuwTRPjXhsQA|nx_H4wk0Wwde@^_3;F8By%@(F%=sTN0b`xDoRrIanNm{C9zwli5r3 zPP~>o$0)~vZ8BcDsxgVp{MV~kX^7_7AD8qXrfVQwJBEx<3RaF(@>#H$VUB%7GVqSR zw_7k5k8ZN|WCM3F;}=Hs%lqFNVmB`1Yxo?1<^mUlZjWsGE)9{>wgM!6rpo5V6DpHbLjWi6oB!|mBJVyE2#I_T^ z3wi&JI^LS4j>AmQgd5QQKVPKr?w++@$hie+w0#4=r*uKaUr_cm?8R>!sg(KVE9Y>% z_1^}z{VXuD#g_`0Li!vD`rMkyN?GO=t>*3&<-o2*t>=uvU7NW+u57*ovWXvYdBfJ0 zk;+k1Mot(uv~|d1yJWjDkP2J&J!Ne+Q{F+{jrB$)1Nsl8vYs-dwH3BGgZHoDD&-B) zzp1=GBbqlMeVFTV6m$?Q{x$k`{t9bPmTB}C15;S((`a)y#-l8AMQfIct%7eZUDr>w zTe3IHm<+|SG(!oeTGe@1bd~Z`)Ugiz2|dGbL-`EXCUT z4eIJHv9@NtZd$!8m6aNj*ig))R>Q`PkA^zT+rGKW(mkfkFwu^6pRwR0p-qOSqmUCW zWJ=>Z1wEgjeFt{k+Zq~R?a45W-q!ezu_pwXW%s4_$oh7nKEe$D7%VPG!JII$ciBe6 z=&ZwRHEXQT+y|W(fF?UUlWLlXv?GfdR~g&uGefBB4V0%bc>ZjpG_=sVzU*x3EV2cf zI|h8-A)P@-jb|dW|GgjC&*+@r0(%;IHDG+Xo&u8+Sv7a2Z2?m((`S z^x^kV&sF%qhA}U!J>juTIc`FGsZHHWdQ8Ym!e=7d(Si@hPc7*|+g^P3Vq_%wibQpS z_Y@0_P~eA7S&%Md?29(lC%0RUE$OjC#wo-I4Eq3*6VH$N@^1EpP4DLq+`P?lA^*|0 zz#m4t3N9?^Dd%O4(X#5^;|;#|I6Z*%&|EAF-!d0#(`?9XXD65R1W|_9k!0;jMqiGQ zABPQ;g-6Ui4STw{;b3&XLVY6r{9YX&@^8h4w&Yf7XJkw2Z_p?1TQ6=H)pW5iMLAuP z312!IzI04e&limQ+wiYg54V}a!H&0vwS84EC)iP6+xC?qjWrqiLtc_`qG1$l>V~$j z>QjRq#!<+7z|fSR670y&YWvDK4gBK?Gt!azDNEi2rE?3)(t8=+uRts{gejfr6jK3v z0N6;pn}LlMSPQT$yypQME3i+1eIM^wn>(-JSi5E5M5S{I-d6&fF0f_5X5+mK*tG%+ z0$YgpT403&dk)wVya$2ZAh32|EAZX`tVCcJfZdAsMqqacY(|dKX~TO9Fo(e0z^d@x z20zyhe3ig=1K%>JiN=%q%dGIJr2~~$FTgHS*^6_7!4CNI=5E;VC_~#A+>m&F|JHfL(g(*R!b#7LWtxn3g{_bETQkwM;TU`ws zHh;fu7rU4`8-Ahr>C|8`*%RX5=CTWs7vVeR?lrP6_JPlE$~I>q%8x^N9FLZ!V=k55 zv+;dH^}22<`xn#`L@d!rb>ZFr=Z)_}uZpGG-)&~B_1Cb!Gw8!-NV6_v&7rl|58h4l z*q3izjFkQgG}-WfA?^8sZ1@LQ*Kda{-T_;@6YF^@U(YXY_=P$*N(?+Mhkq$PdxsG* zyD{e;)N4-ao(d z!9OB@7xK#Qu|B#bh5cslUDD z3X;?^r_p|l$*^vu5tHFJMkCf?j1TQ4E^aV3UEIQy)7+*JE0NtpkH~K)8-QIaEQnYj z!kXYux$piH)~yh5J};JKD37V_UmY5`H#0i!SR=G?Q4sBOfRAkB9NKYuH0iUT3GLxJ zj1(HM-!U~^d>d^kNg_SwNLeH&jJZeio!Vne3Kl;j+A=8G7VBJT3$(5#p)QJjWIw|r ztug+e+Ov?_gIMcJwCCc6!X{dWPL~ulp)GxktFf8vi^i44@+^F7y)pgWr;X`+9II6w zd^s6)V+^YLUZgmgVljgm#E66DEUl{3uhp zFn-6L!5q4WJ%4lyQ^Jt5=^n%LohI&E+e3cH3gsvP$jKTAJ3b#N?KBlO(K={hY#Z9v zQGRz>H^aDWWyWn;X7=S~=<%45^U%k73@C4#tKaEI!)Q2PU+oNOM$DF)} z##7A6vt!jcdAg)fGY4LyH9_?iCYqaurs3e{^8mK-A!6@y4pY-h^{GnFs-&jK+o`|d zc6v_Osp-n*WYj@+ISTxUf1BrSxdt&?Mso@9G85TlMsq3Ne+gMQ9&i6?u%EJZ%p}Ex zHN60JbwY0x|E7Wm-)Ac(|4MWfXpG5f*+23>IMe8V^jxe>)0$HR-t=YgXNW<`whuzr zS-{%PM`j)e-Gz2i-CK8Gb6LIQ`yvrT(}{0r4+NV>-Q*dm`N$g>>2M!5-c zSx=nYQ8$F zpqxgmKyzM>Q}~LZO~*zKYdW5l-W0k&>G|-($BNb z8iDxy3-}AzPqK0hKBo$M=I}OSQ@Aim2_wDsy#_m$n?7-(_M- zWXs$0QSUayvX9VMT`~n1L{ftbE^XO5KeFZC`7fZ4G)8}dzb(Jd+GK7sZ_5{Tn$5vo z*yHy^+dVthM_WlU>$K)T1c(|BH_I-tKG#jv0kfF>UvZCBRi?P>wf#SKMRGt!$+mn?!$ zk!yyClQBZuPo?$OZamo+yUS?#iG!JRKDORTS+YI^w z_g}s71<5>%n3UFpHz3>Ehin(*p)WIn6kk1neHmgS_=&k^)2%o=B>8H5Pxt|j+Y3W)WP3+pgZX54u(wN_*z;wOLv}X@Iv|-=oH2ZkG9lr^!hP5n zjW^lX>5`@Jd#a65OgCtFeFxpQkk2EXM;w#ijhL_}7P4SZ@I_dYSETWNSU}Uc%MT&1 z&D1@62KolO$~g*~f?v)_Lflo4&l&iWQ)rVG-$yYS{5RIM9cQM)-=n`4iotPSN9Qu3 zRhVldjdO2@>`n*mdDHnh%skL^4ny=6c!v%3uI($9#>8Oc2aVt{haN;6g|nQdrmtu{ zF|)R>LK&g2Lhq-MJ?G3*ebLl1#K7vAPkIjVy^c7X)|AEzh)uKd*JB^IIv?+6c34~Y zrdU^>#9DsVVN}k*_AowJi>@SJoAZJw7y5=`+Vq?*^l|T{g{wo6&Bfav31%A5SFBmh zmYtK7H}9G;XB65ys3mM~ys+ITFQA zPEuY1wvTnsielhvQTcHGBgzh+tl&I!pM(~%VsQi}38N9CXz+Yw=ynfPw|szAt*z7UW>U~HZV90?Q5}okf#)E zWTOiPew<0Xso>%GvT4fOmwCwn#d2(h(v3Ms``7y3aci+8 z%~HMsUf(~e`xeWxS<0UX%hq7QS;`FPzWyiq6QgZ@ah7tF_^#IQ&6%y_fNyh-wY7eP zwRK?{;+G`ss}RS)Mq5neC!fVx-2r@t1b0Pc1!gPlkhM7%;|iYoOr(b=(;3!kDntC# zM+a=%a%MKy@gd@$hI+0-JtQA%8u7=}b0)5yia83MkJ32(1iIj|cTVQ}eg6KzWQBA` zdq3LuoxNib_6Lg*!^*Y%HS9}R%hqvo6-VBPJexhSma8b7^;MEGc8mKZ%(3`2jfJfJ}-7)NBR>nieQV~@yEh}|G$*kaNhZl#i#%!*of8ki`<387ZDcqL z=jTZc{5&Zxwr_+TELZDP+A)@>8~RcE(TEu4qZGC}AM4U5$dhehs0%U8(8#&Sj!%J; zevVRE)N>r^UyHl3{;g1B@4?u&n-CjZp~gS(<T<~TF>v?quK6$M5&5i7NJI<=8uZ=TUtCSV?#>Sp)Ls;oh^q2e^?$BB? z3bFq(B{e6ThSA=sW$X623j9|q+1=N$GqTx`L$$eA(BDo|W)9KM!p`WN9b>V2Am%Lk zKCJ9&inD@`bV6nbXQ5fZE$EfXm&xdwC zsA=-H2kZ;k7#iACfv2)e4x1OeZ(4cH!<+{nRfKGu{$y&I1KF? z##$F{Wvd@T>>Gr?EE~*P5nGR3m}Xe*CJg?P&TE2$t*t$j!e3EeUhav!GzVuZWx1@? zO7S*)Xjvv}T@9a8Hci#p)38tSbM}u+6W>f>Q`<-${MbwPu+iktUmDz&S=XPnmOW_2 z*S3rU91rFrH4RYT3!?AP>3}lq5$;C)Wr%sm=YD~A%$wrRk)D5&_DDCvkF9_oTM0jQ z6Ktz6(>ax9mN8=W|l{Iy#51 zL(D*D@gra>+_(KojpH}f2j`Q`kl(U=Fi&~&5_|s5ONQsyV4T+!BzLd*5o^U+D{OE? z4)K{VKGdhEpUb#b`ACz0o?=_pJ)7>D*ub|k$*wnLv$QQOL%iwKG>z++EeiYyD;c}myr;Hu4 zWtS0g0>!5MdlE^(j?cbE`@ut)7q-^cpUy9?RNLa7uiOQU{2RrtWcSg&?Vr!TuYfqZ z*iLa1`tL)%QGFeo&toN{3HcO5A^$FIe%=BFcZ-MzupdI+i^waxMI*mrfpWhl|2fLP zU6cRf0%emX|5?h%ctrgS&gp3^qxWO*3Fpw)sLt}{D_)c*{YqUIK!()CB=p;QF7guZ zFU9iE#rJ_VejR~+#8?IAD?fq^q4(k~D9`;0?gFKpVJNd7_H2e-w;;Xj%)L;axxd;p zXx!G`{TbF>TAS$%>OAgU^9&*tQp~vH?`W5tgF-DZ?h4*CXKD{2;-ih(=iTb)9E=ks(PN9k2Rg>dr?jpk!6DhTu z^wkA@kxV+vq_)pM3=4f0ABPQ4UKsUuq769nll?1x2|jc>)&V1{Fa8b2{3yy(JGY`7 zkDoGw#ogDiRqgXGthHnQ@@F4--aD;>OYa{h-?}#u|L)-6ZZD} z-V+%7T)V}LyHMca)8wx?)?&duY6N=eMt^a(An|q!&YpXKpDqNR9>rTM*lV2z9v&OT z+AY;gp*bI(uEBOQ<#WOcqgaat`>$h!l|-?2OB+-EK$t^=g_!a?!f^K@+SzXDX3B?z z(R~5hzQsU(3w;`6P-Vi0$KXp1%4eKEBNlfXlq0}HZx_s^JyrNxYpWyaI)&m&lILKL z@3QPRC{~Q&66n5biM92YDc!UAxe0vO*3wj^6aEl3)l74JF!A6!EMIRiD&za)8^QUK zz}LAYe=Z;QN!Q{2X+-lv)Yr#vdrq4Q=3dDj+jSQAD?}edJ&f(#6~Z}7 z$YyP&HijQwf^lZ8U8Ap`Xz7MezQfwe=gh50O%&S&i!)H)t$&Kl;I@VDhmn1v?sit2 zq`Y({vcmztMSXz07CPfC1OI<>jIVtSgxvw`ePCq&H2<|{`xiGH;&cAuhSy@x9p$=) zyCOM9+UWcTXGmWkH7GssmEEu%K3~K+q?zW6!^qD+$VYQ~hki_mUwHd)&I5li@hs*5 z=H*1JNBAU7455sZk1_U;csF5uz~4Y;J2v2|{bT%*$8ntR(AhPu8&M4BMGFa=uEB6# zMCX*@M>QDEfo2ls*I+mY$|G!x2E#efHG~yLu@=kx6y<7Q--R4$+Ja^ucjTPW}Q z)>i9)?%9QW4-B3UQWfLCw=)ZarqRTAH}PcQe8iyPnUNO5vl=|%v2pEtAWc~X{9J#; zz3JB0t=MZcrghJ9;H(M##TjI>VnRKz?;8whhcd?nlSW7T){>^&iL#v|t*zMK%?@ob zD6c|Slpn^sC9NOVO9}CewzgUX&(F0y>r|e901xl`b1JzwpWK7(}TFsEQ2 z^4Koi@$xermXyNJjG}D{_E%=3>>2E{xc&Fe!4`I?zw*Ip#NuLJgmLao^FhY4W}Gt= ziFWX@OG{J6fo{fJGz~nI$@Os|S*d^y-UJq{bACT16MS@z7R8GCD^~)0S74T7{gnU2 zyF8=r!5wPsyjJh)E^a`)I_AE<<5hohlbqXhA1wPZ7k9zQqv|_)a39R)w>Ja*qW%2% zZTL=353S$Wd+@XQ4{6^)_q~YQL+`F^{rdfttyyf;s?D^vKWSf2adrsryszU1!slWS zhO#HHSEWnS(;u&F-IQWo{ZEqT&VdSjPvgv9`+7fO%)K~AF+h(;-=;hL5jjb)*L>J# z1m7DWIg4;6L1!#Uqbc^J7?onrD|3D(+GW7^S!BP$-rHivhh4!+Kf(CzEx>)kVBA#= z!#N=2)I)x!W9+u_NATT2%xTosdL#Hk&mb1UoVyuuqaEJ~H7DUakLcfjVXw`S*i9C! z%h~PbZG{7^trYK%L%NrlR#}iw{3d)Kl6Z=cn!%Udp1o~swWHfoUD0i4hIfxbwxi+R z)`iUUMGo!)MneYOc@Ke}3h8&@sE{Ro{03{S%x#s~Mds~FBm*q`f*gZG&6 zU4*xGk!{mC%NgtkH=^!uAp17>GKLtN;#~4|o9|!J6WU~KV!tZr*#LcY;+}3Z>wd-X zD`OAlV9)0JQ+w#UuZGcEI|@}f<|N_A z@ZHIyh%t|wGMgzs482j@{aeKBy)myzagt8K)BS7Kdg&|J(R&NOv|rlXk>0R*7kz&N zXGYDT5}b8CV_5%Z3*z&v>gjXtfi6f_?;z!NwtJvbiFdO5`1OwN zvd{Y$;Vx2*b?Hv`EzDm!80rTI(ivt|1r#YJUthVnz$K7IEmns;E3Qcig|XN6x>KE--X@is$Q);>rn zK^}eIg6J^Dk&U5$&!C=n6wq1qCxf|P90WdIX3`bPV!VeD z+lXRYhbdvgu)ajG!^4zM3B!64#gfvMBZOhCiehupmG=q5caNi3AYJ)y!lr1j*VC2% zAZ)q@OB${m0`>@WKOBAAzIZtN5q)nZz1avo;7)rctqYEd;e1V{7}eq)&UO1T%9)|Z zt+MRFE2CvkBM z-8THwdAzT6Rl00-9=jjEdl02v6>evg2&|*m@_?W7)qD>cHA#IXe(^fK zViFr)Q9V9yT15?Bpi2HT3to?VJqxU3)JNv6tYmeyjC$zuRN*HjR(ZT@J#0fZeyYNm zJ&#Mv4xkC+-H?`DZV%uW3)W}P+rVnp;0G=2zAC5B7I4Ee4D^MQY9-DEVN5Dr6;6)>EfHPpqXOz?AMydmpc?6Y zv0vUywE&lXS!=TK^9|5{1*uwNq&AN?Kw8s4=3@t5J-+|xu07jM{QGOiA8VQL`D;5* zzVPhSS$TPry56|=c;o$DFYKC_{f~^d)>Q)ql6KXmua)Z1Y68+d&g@BOLY2>6fi}zw z`0AYh*jt2p9#a6y=YrAPYzKaSqn!GKa+P+s9|b36m*XcvR#jl&dDR-z#<-Jq$?$8? z7$WGsF5q(0Fq1(zR=F_+F}iAgwmK0dce#%;J@^$4m7h;d$;t`*yav~qYPXn@G8eD0 zdFo`8*(#;sKtJ#kC@u%AlLnJv2Ca45J$X~`;~@Bv6V$>u&CLBMug1R4*4sKlFsJI^ zY+}J!YP_EnlF`##DRTMw1fX{@FQz!`wRVRqus#Z`@wx-{DkltMEvu=WHhcQi$zs+_ zp(fS&S!JESe0tur$>a(gll|U0pTju?a}>TMDn+m^rf%6C@wwGbyBp3!wjtnjRC~N` zZ&hzAXoAL9&oNoXvEBji=0zFr&fBhg_kX@+|B2Tg{=E62t_Pm#3cik>VFI7nz4!R; zSH(2$k3mqE1unR@4#@c5J<>Bco9_s^$w-YexC!$ zcOMke`wOk4_g7x%df>j#gL_Z>hPdL7N@6=ynq%S%voLc$W7WkAB37=rby2ZxdH#+0OOO^VD!G2ijpl>@GCg-1NgQid z=gIM>etP`XhIwYv%vf`HSEE_X9KQNtG5qUOC|b|bTWyQ;3rkB-!&LL+*{7RJ9x1LSI?^wwo5bB+0#-JlL zpKSO=*X|bbM!$Hp>z{s1dU;{biJt&TG-Pwlusf9IW)|?)GUpnn2ftmWE}v{VpSyMb zjhHLkN;ZimoqK-wT%YzTdd0w3jGee8ct2G?@jOJTMk5xpUQuW(Da|hx@>Z-U zwJl#!v`FMG=fpCV%D=hj`W3d46@@oMb7P78f+JUOHB|nhPD8 zy&ftyK{&KYX1~3r)(ziQ;h$vYLk|Bok-G@<3Phe+|K7h0o{~-GS(hueRhV;yDBat&m({O+{13$LahA(LZke)>P0Mdp|@F5KxgM9qx*3zy>WCGIalV~?m z>mMVL-AK#+5{ZP6(u1=y{uu5qW=ZR=GK?OW#jy;a#}1($%FR!`iufksllRL=B#$sW zgjw-f22B2D&N$Ge_ymBhM9T6f=MOeNm9)oH@Tl>|&pv&BFc84l`?HPsG=fk2dtW4T zJ3bA->75>G!+v}o0`?;G%<>0^OodPN-_vi=qiNeyA4_>Wc}LPt(-TJHGr#@#$cG=j z|G}r94F(&vH-t~xVdxPl%H=(kvM0IV(WLFB!pDq{8*KeQ`;_?6D14L043;4n_pRXH zsq$NkMALutzABUYM_*Fy1`o9pGFRpcp6!M*Sv9ZwPViOWANZ81d~tQJh^d?U_z?KF zgP&{?avM$gPo?ciEqF9#dveiZNspTfb{KaWj5lk$$m=--{!d_|e@i_RAo>jW2f~IA z;azRNRLY{qj8z{-`#@uAhQ6N#?@qN%D@p4`r1b()`{KcENvi618H^=^S1$VO)8GD1 zZ65}}jPf*JE?}Nqz&q+{Gv+^)xo1?tqZxOr3MGAR1rN=k-6~Jn;;24<7;OvjJqNy} zm|LYPpR}s&22!OMMC#uM;0u7SLFMb!d%O@2M<3WXo<~W?wn*nSoM=-yTZU=07uthRug0a-v1*@9{YUA<;N<8&Cj-5*3E!_jvdQ$9Or zX!sPNaiZ~h!iaJ5#E%n|PfKF#J@^^&XRLsa)4)9g3Lfphz2Bn8(jHGO+>x>~`H7@m zrh+Gp#y!86qs%7-^?8i8J$%194*SM zC{~NIySt)wioUg#U*5Of2IGOjWsA|fPo%anr^Z3Xi;%GnKD|xor!YESj7_qCE5Y{( z_{v_v8iX|v?F_-TX-r9lMO0r=uOd;||Mc&zh=w(Pfbol11X5_(;&w~ZfPTN69x_v^ zLVt7(=o-*9pld+afUW^u1G)xu4d@!sHK1!i*MP18T?4uXbPebl&^4fIK-Yk-0bK*S z26PSR8qhVMYe3h4t^r*Gx(0L&=o-*9pld+afUW^u1G)xu4d@!sHK1!i*MP18T?4uX zbPebl&^4fIK-Yk-0bK*S26PSR8qhVMYe3h4t^r*Gx(0L&=o-*9pld+afUW^u1G)xu z4d@!sHK1!i*MP18T?4uXbPebl&^4fIK-Yk-0bK*S26PSR8qhVMYe3h4t^r*Gx(0L& z=o-*9pld+afUW^u1G)xu4d@!sHK1!i*MP18T?4uXbPebl&^4fIK-Yk-0bK*S26PSR z8qhVMYe3h4t^r*Gx(0L&=o-*9pld+afUW^u1G)xu4d@!sHK1!i*MP18T?4uXbPebl z&^4fIK-Yk-0bK*S26PSR8u)*s0dtlr_r0ss)F7()T)as7w0IdP@>dR1Uj_;K)b;Ai zC_x`NuND|B=roaLiZmqh$He8Iy%g~h)M8XKl12UjAs|K2?IORwpuK_~Am|K{UMW(U zn8eG;i)#BsF=iDN4Hx;TA{{BxA)=!aPZNre{Yn%1=_mMQ{X(rw@|};U{R2CPE(vvR$VHJwfF62wLhzRQLZpsTJd`r>d&|f78QBG2i}HL$QwNn=_56mN}))@0(I# z57?*FIK6A9)Oy!CeYP5Jh0~qu@OmoyF8p^yrvz$hdDCp=UT=W6?C)6nw^uc#*5`Fh z@dxaIZ|D8r;F#iH@1Npu+x`A2zRFcDxBKth2u=Ov?mA}xgIN9dY(lJk+w64}E^n^i z>sa-7YJ{f$9_Rmpji~mCs;5*q*ZiIO{9UTi=%?K7SmkzARR^X--)yyOYx2H}xbMuX zsbBSd{<_*)_xkTF=l{0AI#CeuLd+xMjaP0}8I}|l&NENAxxCI3s54KUnL8~vZ}QYx zb)23$Q6ON=AB#VBr@h=|t904}bv~!f?y0bqy8?bjp^ZWKeN}4Te@)=3m4SGER==AI#GJL5ryGCUymD7vreSHL)R#{XmOI{-G?-))zjE$#`6w zYhvk4#^JH}aMoA6U}9G?88^rBQ?PLP0GrrIb|ASgTt>%QDGcK-cMrWd*uF4V%m)}9%0?IC$aHjqhN?l08# zUv5__ez~1Y{;T!&XJsNj2OEE+oD0`WL``QP`D^W;Aohc@R1sdN{St@e@#hAC&k_7D ziebJ*;D5BJe9x%~s}T6kY!%oka9UePejfQBJloZTJ%E((Ljo@oIOP*QZk!4P$w2Tt zjSu0^j^`vRQxo=rz>}_4fkOgs75HI+?-X)=CGfHdD&qyg|DM3_oTvi31>Pa>Zh@By z{9}PH%Ta*_fqx?KPXvBU;FMDP+pbZ8XGMF?3A};~0M8^fVYJ^Q{sw`I=CV-&KOpd> zf`78WS*{8kGO1~izz

EU7=%LIO8x{4nVxLe>2vs7HxOXEQ8IUw-;g8xB*m(5lA zj|rUmNBl+eRr~`rVQm7>SfB!C(asM9KF6x!M+AOc;6sXH`M(l)-VzmW6ZNX|;&T72 z`iBwltb$Q3#ta}0tQT(-_+Wvz2!4rY3VbCM#3S*m1%684LCVB4UEsAwl|RT)#^y`@ zIQ)9aZ;ItFk^BO06msqqctcVwzf<5tgnyRxdL+NVlZ2dm1in+?X##&(;Ei$k9)a(V zlcVZ+Bx@765X1hJ^Y`Mi-hUJLp*T6O3H-2>LyjEJL5T}PmhJ2iczYb)Dexl#m-YTh z;GYP5jw&7&OV0?_BI_0SS;8J-Pt?zvhR_;?|K=*gMUac)Xv$8{+t7 zJLP^|G?R@H{PMg{;?sZ|H0w2{4}VlSm&Xa$asJD3r;snt6{UPt&gF5)YA)w;Tg--XIldPj}qXICBRQ4z|SSX`@;_V z(tmaWd`1F%aRR(70qzGrq!0TKCg9(m0N z-EIQCCX0{(wWfImsN@$!A{(+T+hJps;29KOKRTrV59j*Y8eHXEH(1?*J} zunVxm?snVOI_;}$l^()8!02Ye#!u&Js(iT)=JnY8t}4cFDQs@9y@L6i_6l32%k8uU z)&*GfroiU%+o-T6-{lF|Dx8&UolPxMhZAu}ZJn(y;Bwn)9RXyl^|=C0TP5ZAoj5zI z^x12itUREWprd{#yUSit!S1SbdGHq^*zuyzHoGI>TBGvY zz+?0JY#x$K0&LV62~^hLcEjbNTNi%-mkPDHd91w7<*u;Z<@d(IYh9HtO=Dt;IQ;>e z!&_5Zhep==ZMF75bxeM_J)m}k)z*k6UM8RS+UbL;d)t!h^84&)lEdw?p$=CKw4lXE zEp>j0RO8GMgMsoGM3nH=d1O|Vdb>nb`LmctZRMFHMW2^V0Q!H%H)V|9Z0{u zrWP&tI~_y>ytPPt4lj;f>BZ%#LK2nXLMK7@iU^PhvK;lIO7s;NZ!*l;AE>LWgb7zT zeaYug{O`igk=e)9nm6E1>;8Ibir6m(A|; z+1J~go`7#X;}&bHsH>@2k0Kfh7Uv4Yu-a^ki}RN+vMsu?$Yx`Hn*#skG>D{!0zhho<4)QNmIQDDzRN(x*YRZDzS8VA*ry` zUXB5=*~%)B}eWz#uwS*{YlYOpchsei)}OrmW3LH?GX@sf9%adfP;8purSfAm?LaaCK;q zwkDfRmb5vlS7~!9(FlJ`<@SKrrRBg-1iY(kG0mx@?rE89wl#@r@H+!!%ih|6wt%A= z&R7f7dfhQiMztPCO|6#2hIvT6kM9HQ2bE|!?GA@CwmvklvhUL5)#FAAlLp#W4FcW> z3W{Su<1j$p7@a`J0o+GX<7}07SA1uDj_Nq}sv26BqDI$Sx~k6Zi_r~VU$p&01N~l` z>NA}|QieZn>PQQxvF&XsEF6=+Z#ALI@xuXY1HWo>GPwBF@z0yKBwDG1;kseJ3ta$ z_#1E{*2(4mBG>2T9wpaVE!=%|1iiT?N$g@*UOKYg`Vr!3)W% zMEFvMK%k}uOAE`*FJ7LD2*T$oM}Xmv4JrSxDEeL*h17DlBJUk#IxbGC+@DC8j=>F@ z^4ou(V+G94qBWxWV%V;(Cg`bw89-!5KcdJ_9UdMLqB zkNRaXr1Iu$sla7i~|luh2B%Jd#yiq68+N7f_L9|ETDu1Q+n zv&ytgl%w_&PkjH~z(}?%FYjZcsc5jQUzV5sUn9yFAf-p%+sf1`^hf3CJAm=!gTSZ? zY!CS(?{{S?$4_KO^&sgDc+r-Z_rNl3q(Rd>^4?5B_u)lb-bzPmcx2il?p;(AxO@@m zLrAsd<-M{@rJiIlsYgjaBFabgFYce^J@h++B&V9jm){jvUfxs7bP_dA^Te0mk33?L z^+)fs#d&X@92g-(j-M3yZ+Owpe|hgMQ?q0gX?*={zz5?ahe_U#A33iUh_6r5GJOwh zbmbt+%X{?D-&p<#%2F3)d3mmUQk0j95oR2/dev/null; rm -rf "$T"' EXIT +PASS=0; FAIL=0 +ck() { # ck <名> <期望子串> <实际> + if echo "$3" | grep -qF "$2"; then PASS=$((PASS+1)); echo "PASS $1"; + else FAIL=$((FAIL+1)); echo "FAIL $1: 期望含 [$2] 实际=[$3]"; fi +} + +# ---- 搭假节点 ---- +PS="$T/ps"; BL="$T/bl"; NET="$T/net"; RF="$T/rfkill"; BT="$T/bt" +mkdir -p "$PS/battery" "$PS/mp2731-charger" "$PS/fusb302-typec-source" "$PS/ac" \ + "$BL" "$NET/wlan0" "$RF/rfkill0" "$BT" +echo 94 > "$PS/battery/capacity" +echo Discharging > "$PS/battery/status" +echo 4275000 > "$PS/battery/voltage_now" +echo Good > "$PS/battery/health" +echo Li-ion > "$PS/battery/technology" +echo Discharging > "$PS/mp2731-charger/status" # 权威链 +echo 300 > "$PS/mp2731-charger/temp" +echo 0 > "$PS/fusb302-typec-source/online" +echo 0 > "$PS/ac/online" +echo 101 > "$BL/max_brightness" # R3 Pro II 实测量程 0-101 +echo 50 > "$BL/actual_brightness" # 中档(49%) +echo 50 > "$BL/brightness" +echo up > "$NET/wlan0/operstate" +echo bluetooth > "$RF/rfkill0/type" +echo 1 > "$RF/rfkill0/soft" # BT 被 soft 块 → off +cat > "$T/netdev" <<'EOF' +Inter-| Receive | Transmit + face |bytes packets errs drop fifo frame compressed multicast|bytes packets errs drop fifo colls carrier compressed + wlan0: 1000000 1000 0 0 0 0 0 0 2000000 1000 0 0 0 0 0 0 +EOF +echo "cpu 100 0 100 800 0 0 0 0 0 0" > "$T/proc_stat" +echo meow > "$T/boot_mode" + +ENVS="MEOW_PS_DIR=$PS MEOW_BL_DIR=$BL MEOW_NET_DIR=$NET MEOW_RFKILL_DIR=$RF MEOW_BT_DIR=$BT + MEOW_NETDEV=$T/netdev MEOW_PROC_STAT=$T/proc_stat MEOW_BOOT_MODE=$T/boot_mode + MEOW_PM_MODEL=$T/model.conf MEOW_AUDIO_SOCK=$T/no_audio.sock MEOW_PM_SOCK=$T/pm.sock" +ln -sf "$(readlink -f "$BIN")" "$T/meow_power" # argv[0] 双形态 + +# ---- T1: 守护直读(无守护 → 降级直读) ---- +OUT=$(env $ENVS $Q "$T/meow_power" status 2>/dev/null) +ck T1a "state=Discharging cap=94%" "$OUT" +echo "$OUT" | grep -q "mv=4275" && { PASS=$((PASS+1)); echo "PASS T1b mv"; } || { FAIL=$((FAIL+1)); echo "FAIL T1b: $OUT"; } +ck T1c "health=Good" "$OUT" +ck T1d "charger_temp=30.0C" "$OUT" +ck T1e "rate=null" "$OUT" +ck T1f "src=direct" "$OUT" + +# ---- T2: 起守护,IPC 路径 ---- +env $ENVS MEOW_PM_SAMPLE_SEC=1 MEOW_PM_MIN_SPAN_SEC=2 $Q "$BIN" --sock "$T/pm.sock" >"$T/daemon.log" 2>&1 & +DPID=$! +for i in $(seq 1 30); do [ -S "$T/pm.sock" ] && break; sleep 0.2; done +[ -S "$T/pm.sock" ] || { echo "FAIL T0: daemon socket 未出现"; cat "$T/daemon.log"; exit 1; } +PASS=$((PASS+1)); echo "PASS T0 daemon up" + +OUT=$(env $ENVS $Q "$T/meow_power" status 2>/dev/null) +ck T2a "src=daemon" "$OUT" +ck T2b "cap=94%" "$OUT" +ck T2c "mode=meow" "$OUT" + +# ---- T3: 容量差分速率(94→93,span≥2s)→ rate/est_left=derived ---- +echo 93 > "$PS/battery/capacity" +sleep 4 +OUT=$(env $ENVS $Q "$T/meow_power" status 2>/dev/null) +ck T3a "conf=derived" "$OUT" +ck T3b "est_left=" "$OUT" +echo "$OUT" | grep -q "rate=-" && { PASS=$((PASS+1)); echo "PASS T3c 放电速率为负"; } || { FAIL=$((FAIL+1)); echo "FAIL T3c: $OUT"; } + +# ---- T4: estimate(模型合成;中档背光+面板+wifi idle+bt off+audio unknown) ---- +OUT=$(env $ENVS $Q "$T/meow_power" estimate 2>/dev/null) +ck T4a "confidence=estimated" "$OUT" +ck T4b "待真机标定" "$OUT" +ck T4c "backlight=450" "$OUT" # 49% → 中档 450 +ck T4d "panel=80" "$OUT" +ck T4e "bt=0(off)" "$OUT" # rfkill soft=1 → off +ck T4f "audio=0(unknown)" "$OUT" # 无 audiod → unknown 0mW +# est_mw = 300(base)+450+80+200(wifi idle)+0+0+cpu(0,静态proc_stat)=1030 +ck T4g "est_mw=1030" "$OUT" + +# ---- T5: wifi 传输档(netdev 增量 >4096/拍 → tx=600) ---- +sed -i 's/wlan0: 1000000/wlan0: 1999999/; s/ 2000000 / 2999999 /' "$T/netdev" +sleep 3 +OUT=$(env $ENVS $Q "$T/meow_power" estimate 2>/dev/null) +ck T5a "wifi=600(tx)" "$OUT" + +# ---- T6: 息屏(actual_brightness=0)→ 背光/面板归零 ---- +echo 0 > "$BL/actual_brightness" +OUT=$(env $ENVS $Q "$T/meow_power" estimate 2>/dev/null) +ck T6a "backlight=0" "$OUT" +ck T6b "panel=0" "$OUT" +echo 50 > "$BL/actual_brightness" + +# ---- T7: 模型 conf 覆盖 ---- +printf '# test\nbaseline_mw=999\nwifi_idle_mw=111\n' > "$T/model.conf" +OUT=$(env $ENVS $Q "$T/meow_power" model 2>/dev/null) +ck T7a "baseline_mw=999" "$OUT" +ck T7b "wifi_idle_mw=111" "$OUT" +ck T7c "bl_high_mw=900" "$OUT" # 未覆盖键留默认 +rm -f "$T/model.conf" + +# ---- T8: HibyOS 只读降级(不施估算;电池仍可读) ---- +echo hiby > "$T/boot_mode" +sleep 2 # 等守护下一拍热切换 +OUT=$(env $ENVS $Q "$T/meow_power" estimate 2>/dev/null) +ck T8a "HibyOS 只读模式" "$OUT" +OUT=$(env $ENVS $Q "$T/meow_power" status 2>/dev/null) +ck T8b "cap=93%" "$OUT" # 只读模式电池采样不受影响 +ck T8c "mode=readonly" "$OUT" +echo meow > "$T/boot_mode" +sleep 2 +OUT=$(env $ENVS $Q "$T/meow_power" estimate 2>/dev/null) +ck T8d "est_mw=" "$OUT" # 热切换回全功能 + +# ---- T9: CLI 桩(期 2/3) ---- +OUT=$(env $ENVS $Q "$T/meow_power" history 2>&1); RC=$? +ck T9a "期 2" "$OUT" +[ $RC -eq 1 ] && { PASS=$((PASS+1)); echo "PASS T9b rc=1"; } || { FAIL=$((FAIL+1)); echo "FAIL T9b rc=$RC"; } +OUT=$(env $ENVS $Q "$T/meow_power" policy 2>&1) +ck T9c "期 3" "$OUT" + +# ---- T10: meow_powermgr 名直调子命令也走 CLI ---- +OUT=$(env $ENVS $Q "$BIN" model 2>/dev/null) +ck T10a "nominal_mah=1600" "$OUT" + +# ---- T11: 守护停 → CLI 自动降级直读不崩 ---- +kill $DPID 2>/dev/null; wait $DPID 2>/dev/null; DPID="" +sleep 1 +OUT=$(env $ENVS $Q "$T/meow_power" status 2>/dev/null) +ck T11a "src=direct" "$OUT" +ck T11b "cap=93%" "$OUT" + +# ---- T12: audiod 在场时音频链分量(真 audiod 二进制当桩:无 HELLO 直接 QUERY 协议) ---- +AUDIOD_BIN=../meow_audiod/meow_audiod_host +[ -n "$Q" ] && AUDIOD_BIN=../meow_audiod/meow_audiod +if [ -x "$AUDIOD_BIN" ]; then + env MEOW_AUDIO_FORCE=1 MEOW_AUDIO_SOCK="$T/audio.sock" MEOW_AUDIO_OUT="$T/a.wav" \ + MEOW_AUDIO_SINK=null $Q "$AUDIOD_BIN" --sock "$T/audio.sock" >"$T/audiod.log" 2>&1 & + APID=$! + for i in $(seq 1 30); do [ -S "$T/audio.sock" ] && break; sleep 0.2; done + env $ENVS MEOW_AUDIO_SOCK="$T/audio.sock" MEOW_PM_SAMPLE_SEC=1 MEOW_PM_MIN_SPAN_SEC=2 \ + MEOW_PM_SOCK="$T/pm2.sock" $Q "$BIN" --sock "$T/pm2.sock" >"$T/daemon2.log" 2>&1 & + DPID=$! + for i in $(seq 1 30); do [ -S "$T/pm2.sock" ] && break; sleep 0.2; done + OUT=$(env $ENVS MEOW_AUDIO_SOCK="$T/audio.sock" MEOW_PM_SOCK="$T/pm2.sock" $Q "$T/meow_power" estimate 2>/dev/null) + ck T12a "audio=50(idle)" "$OUT" # audiod 在场无流 → 静态档 50 + kill $DPID $APID 2>/dev/null; wait 2>/dev/null; DPID="" +else + echo "SKIP T12 (无 audiod 二进制 $AUDIOD_BIN)" +fi + +echo "==============================" +echo "PASS=$PASS FAIL=$FAIL" +[ $FAIL -eq 0 ] diff --git a/scripts/build/fw_content.conf b/scripts/build/fw_content.conf index 520a483..f0d4716 100644 --- a/scripts/build/fw_content.conf +++ b/scripts/build/fw_content.conf @@ -40,6 +40,8 @@ patch_v2/meow_crash/meow_crash|/usr/bin/meow_crash|62781084445e82351bcadbdadbe4c patch_v2/meow_svc_perf/meow_svc_perf|/usr/bin/meow_svc_perf|7ea196de9d59ab8cbd1e26edb7762772|性能专项服务 M2.75(net_json 补 network 闭合括号,修 /api/system JSON 解析失败) patch_v2/meow_audiod/meow_audiod|/usr/bin/meow_audiod|176c840d80c3e1161df0cb769185520d|独占音频守护 M2.97(+DAC 数字域电平表 rms/peak + 1s 监控日志 audio_monitor_.log) patch_v2/meow_audiod/meow_audio|/usr/bin/meow_audio|891ab26475ef9e9931bf9147d265dce4|audiod 用户侧 CLI M2.97(+monitor 实时电平表/文本电平条;status 补 rms/peak) +patch_v2/meow_powermgr/meow_powermgr|/usr/bin/meow_powermgr|469dddb74de424f7b9bc9c1ef597d69d|功耗管理专项服务期1(POWER_MANAGER_DESIGN:trigger=boot+HibyOS只读降级;权威充电链复用;经验模型待标定;IPC 0x00D0/D1) +patch_v2/meow_powermgr/meow_powermgr|/usr/bin/meow_power|469dddb74de424f7b9bc9c1ef597d69d|meow_powermgr 的 CLI 形态(同二进制双形态,argv[0]=meow_power 即 CLI:status/estimate/model,history/top/events/policy 期2/3桩) # ---- 开机 init 脚本(etc/init.d,数字决定启动顺序,按文件名自然序)---- patch_v2/meow_sysui/S70meow_sysui|/etc/init.d/S70meow_sysui|e435affcb56499e5229728c6ae2524b9|sysui 自启 M2.85(launcher.conf enabled=1+迁移)