手机主板温度:代理 0.8.0 上报,rack 拓扑图显示

- 机架代理 0.8.0(Thermal.java):读 /sys/class/thermal 的 quiet_therm(主板热敏)和最热的 CPU 核,
  每次长轮询拼在 URL 后面(&tb=&tc=,m°C),不为它多醒一次。电池那一路不报(电池拆了)。
- rack 后端 prrack/fleet.py:每 5 秒读 AppTask 后端的 /api/fleet/phones,按机架位 B-n 对到第 n 路,
  放进快照 phones[].live(在线 / 在跑活 / 代理版本 / 温度);变了才推 phones_live。只显示,不参与自动恢复。
  读不到就清空,不留旧值。服务单元加 --apptask-env。
- 前端拓扑图每台手机多一行「主板 / CPU」温度(50 °C 橙、60 °C 红,只改颜色)。
- agent_upgrade_safe.sh:升级后 4 分钟没开机就 power_cycle 救一次并补收尾。

五台已升到 0.8.0(两路并行推);第 2、1、5 路 staged 重启后卡死 / 僵住,断电重启后版本都已是新的。
后端测试 58 项全过;已部署。

Co-Authored-By: Claude Opus 5.5 <[email protected]>
这个提交包含在:
zhou yang
2026-10-06 07:06:15 +08:00
共同撰写人 Claude Opus 5.5
父节点 580468e48e
当前提交 3bf4548c62
共修改 7 个文件,包含 175 行新增和 4 行删除
+39
查看文件
@@ -399,3 +399,42 @@ def test_energy_is_rolled_up_per_local_day_and_old_days_are_not_shrunk(tmp_path)
db.energy_rollup(days_back=1, now=d2)
assert db.energy(B)[0]["in_mws"] == 360000
assert len(db.energy(B, days=1)) == 1 and db.energy(B, days=1)[0]["day"] == "2026-10-05"
# ---- 手机自己报的那一半(fleet.py)---------------------------------------------
def test_fleet_phones_are_mapped_to_board_rails(tmp_path):
from prrack.fleet import map_phones
ph = lambda rail, **kw: dict({"rail": rail, "enabled": True, "online": True, "busy": False, "version": "0.8.0",
"temp_board_mc": 28000, "temp_cpu_mc": 31000, "temp_at": 1700000000}, **kw)
phones = [ph("B-1"), ph("B-3", busy=True), ph(""), ph("B-9"), ph("B-2", enabled=False),
ph("B-5", temp_board_mc=None, temp_cpu_mc=None, temp_at=None)]
one = map_phones(phones, [(B, "")])
assert sorted(r for _, r in one) == [1, 3, 5] # 没填机架位 / 路号越界 / 停用的不摆
assert one[(B, 1)]["temp_board_mc"] == 28000 and one[(B, 1)]["temp_at"] == 1700000000000
assert one[(B, 3)]["busy"] is True and one[(B, 5)]["temp_board_mc"] is None
# 多块板:只认名字就是那个字母的;对不上不猜
two = map_phones(phones + [ph("A-1")], [(B, "板 B"), ("aaaaaaaaaaaa", "")])
assert sorted(two) == [(B, 1), (B, 3), (B, 5)]
hub, sim, db = rig(tmp_path)
got = []
async def go():
await hub.start()
sim.start()
sim.tick(1000)
await settle()
q = hub.subscribe_client()
hub.set_phone_live(one)
hub.set_phone_live(one) # 没变:不再推
while not q.empty():
got.append(q.get_nowait())
snap = hub.snapshot(B)
assert [p["live"] and p["live"]["temp_cpu_mc"] for p in snap["phones"]] == [31000, None, 31000, None, None]
hub.set_phone_live({}) # AppTask 读不到:清空,界面那一行消失
assert all(p["live"] is None for p in hub.snapshot(B)["phones"])
run(go())
msgs = [m for m in got if m["t"] == "phones_live"]
assert len(msgs) == 1 and set(msgs[0]["live"]) == {1, 3, 5}