rack:PD 供电信息、每日耗电量与平均功率

PD 供电(板卡页新增一格):
- 优先用遥测里的实时值 status.pd(固件 ≥0.1.17);老固件退回最近一条 PD_LIMIT 事件(快照的 pd)。
  显示 20 V / 档位电流 / 整板功率上限 / 输入功率占上限的比例。
- PD_LIMIT 事件写成「PD 20 V / 3.00 A,上限 60 W」,不再把那两列当电压电流显示;arg 2 = 运行中换档。

耗电量(板卡页新增卡片):
- 新表 energy_day(永久保留):从分钟聚合滚出每日能量与有数据秒数,rail 0 = 底板 12 V 输入,1~5 = 各路主板。
  每分钟重算今天 / 昨天,更早的只补缺不覆盖(分钟数据 90 天后会被清)。按服务器本地零点切日。
- GET /api/boards/{id}/energy;前端 EnergyCard:今日 / 昨日 / 近 7 天 / 近 30 天 / 累计(各带平均功率)、
  每日柱状图、今日各路主板。量的是 12 V 输入,不含 PD 20→12 V 降压损耗。

后端测试 57 项全过;已部署。

Co-Authored-By: Claude Opus 5.5 <[email protected]>
这个提交包含在:
zhou yang
2026-10-06 06:31:04 +08:00
共同撰写人 Claude Opus 5.5
父节点 489cd6a6f3
当前提交 580468e48e
共修改 11 个文件,包含 385 行新增和 8 行删除
+16
查看文件
@@ -114,6 +114,15 @@ def create_app(hub: Hub, rec: RecoveryManager, static_dir: Optional[str] = None,
_board_or_404(hub, board)
return hub.db.board_history(board, start, end or now_ms())
@app.get("/api/boards/{board}/energy")
def energy(board: str, days: int = 90):
"""每日耗电量(Db.energy_rollup)。单位 mW·s 与秒,换算留给前端;`today` 是服务器本地日期。
今天这一行最多滞后两分钟(分钟聚合 + 60 s 一次的滚动)。"""
_board_or_404(hub, board)
today = hub.db.q("SELECT date(?/1000,'unixepoch','localtime') d", (now_ms(),))[0]["d"]
return {"today": today, "days": hub.db.energy(board, max(1, min(days, 3660))),
"total": hub.db.energy_total(board)}
@app.get("/api/boards/{board}/rails/{rail}/history")
def history(board: str, rail: int, start: int, end: Optional[int] = None):
_board_or_404(hub, board)
@@ -188,6 +197,13 @@ def create_app(hub: Hub, rec: RecoveryManager, static_dir: Optional[str] = None,
if d["arg"] == 3:
d["name"] += ":" + protocol.OTA_ERR_NAMES.get(d["mv"], str(d["mv"]))
d["severe"] = d["arg"] in (3, 5)
elif d["type"] == protocol.EV_PD_LIMIT:
pd = protocol.pd_from_event(d["arg"], d["mv"], d["ua"])
d["name"] += "(%s,上限 %g W)" % (
"CH224A 读不到,按保守值" if pd["ma"] is None else
"%sPD %g V / %.2f A" % ("运行中换档:" if d["arg"] == protocol.PD_EV_CHANGED else "",
pd["mv"] / 1000, pd["ma"] / 1000), pd["limit_mw"] / 1000)
d["mv"] = d["ua"] = None # 两列装的是上限和档位电流,不是当时的电压电流
elif d["type"] == 21:
a = d["arg"]
d["name"] += "(%s)" % ("手动关" if a == protocol.CHG_EV_OFF else