手机主板温度:代理 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 行删除
+19 -2
查看文件
@@ -76,6 +76,10 @@
<span class="readout pw">{{ W(x.r.mv, x.r.ua) }}<small>W</small></span></div>
<div class="ps" :style="{ color: loud(x.h.level) ? LEVEL_COLOR[x.h.level] : 'var(--muted)' }">
{{ x.h.text }}<span class="muted readout"> {{ V(x.r.mv) }} V</span></div>
<div class="pt" v-if="x.temp" :title="x.temp.tip">
<span :style="{ color: x.temp.color }">主板 <b class="readout">{{ x.temp.board }}</b> °C</span>
<span class="muted">CPU <span class="readout">{{ x.temp.cpu }}</span> °C</span>
</div>
</template>
<div class="ps muted" v-else>空槽</div>
</div>
@@ -128,11 +132,14 @@ import { computed, reactive, watch } from 'vue'
import { ElMessage } from 'element-plus'
import RailOps from './RailOps.vue'
import { api } from '../api'
import { mA, V, W, railHealth, LEVEL_COLOR } from '../util'
import { mA, V, W, C, railHealth, LEVEL_COLOR } from '../util'
const props = defineProps({ board: Object, sel: Number, recStates: Object })
defineEmits(['select'])
// 手机主板 / CPU 温度的提示线(只改颜色,不参与任何动作):50 °C 橙、60 °C 红
const TEMP_WARN_MC = 50000
const TEMP_BAD_MC = 60000
const LIVE_UA = 5000
const s = computed(() => props.board?.status)
const online = computed(() => !!props.board?.online && !props.board?.stale && !!s.value)
@@ -160,11 +167,19 @@ const rails = computed(() => [1, 2, 3, 4, 5].map(i => {
const live = online.value && pr && r.en && Math.abs(r.ua ?? 0) > LIVE_UA
const fault = pr && r.flt && !(r.chg > 0)
const phone = props.board?.phones?.find(x => x.rail === i)
// 手机主板温度:机架代理(≥0.8.0)每次轮询报的 quiet_therm 和最热的 CPU 核;手机不在线 / 老代理 ⇒ 这一行不出现
const lv = phone?.live
const tmax = Math.max(lv?.temp_board_mc ?? -1e9, lv?.temp_cpu_mc ?? -1e9)
const temp = lv?.online && tmax > -1e9 ? {
board: C(lv.temp_board_mc), cpu: C(lv.temp_cpu_mc),
color: tmax >= TEMP_BAD_MC ? LEVEL_COLOR.bad : tmax >= TEMP_WARN_MC ? LEVEL_COLOR.warn : 'var(--ink-2)',
tip: `手机自己报的(机架代理 ${lv.agent},机架位 ${lv.fleet_rail}):主板热敏 quiet_therm 和最热的 CPU 核,约 25 秒更新一次`,
} : null
const w1 = !online.value || !pr ? 'off' : live ? 'live' : 'idle' // 母线 → 降压(12 V 侧)
const w2 = !online.value || !pr ? 'off' : live ? 'live' : 'idle' // 降压 → eFuse
const w3 = !online.value || !pr || !r.en ? 'off' : fault ? 'bad' : live ? 'live' : 'idle' // eFuse → 手机
return {
i, r, h, pr, fault, w1, w2, w3, label: phone?.label || '',
i, r, h, pr, fault, w1, w2, w3, temp, label: phone?.label || '',
ctl: online.value && pr, // 图上元件可点(调压 / 上下电 / USB);序列类动作另看执行器空闲,在 RailOps 里
rev: live && (r.ua ?? 0) < -LIVE_UA,
f: flow(r?.ua, p),
@@ -282,6 +297,8 @@ const vbN = computed(() => rails.value.filter(x => x.pr && x.r.vb).length)
.pv .pw { margin-left: auto; font-size: 14px; color: var(--ink-2); }
.ps { font-size: 12px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ps .muted { font-weight: 400; font-size: 11px; }
.pt { display: flex; gap: 10px; font-size: 11px; white-space: nowrap; overflow: hidden; margin-top: 1px; }
.pt b { font-weight: 600; }
.phone.bad { border-color: var(--st-bad); }
.phone.busy { border-color: var(--st-busy); }
.phone.warn { border-color: var(--st-warn); }
+6
查看文件
@@ -51,6 +51,12 @@ function handle(m) {
case 'board':
state.boards[m.board.id] = m.board
break
case 'phones_live': {
// 手机自己报的那一半(在线 / 温度,后端 fleet.py);live 里没有的路 = 读不到,清掉
const b = state.boards[m.board]
if (b) for (const p of b.phones || []) p.live = m.live[p.rail] ?? null
break
}
case 'tele': {
const b = state.boards[m.board]
if (b) { b.status = m.status; b.last_tele = m.ts; b.stale = false; b.online = true }