rack:界面改版(仪器前面板风格 + 暗色模式)+ 底板温度曲线
- 总览:每块板一块面板、五个刀片槽(状态灯 + DIN 读数 + 最近两分钟电流小曲线,平线 = 疑似卡死);
只有异常 / 故障 / 执行中上色;顶上一句话概况,有问题直接列出是哪几路
- 单板页:新增「底板温度」(两路 NTC + 风扇占空比,上下两块共用时间轴,10 分钟 ~ 7 天);
每路电流 / 电压去掉双 Y 轴,改上下两块,电压固定 3.5~4.1 V;名称 / 位置就地编辑
- 主题:theme.css 亮 / 暗两套变量(跟随系统,Element Plus 暗色变量同挂 html.dark),图表切换时重画;
读数用系统自带 DIN 系字体(不加载网络字体);温度两色按本页亮暗底色跑过 dataviz validate_palette.js 全通过
- 后端:board_tele_min 分钟聚合(温度带 min/max,留 90 天)+ GET /api/boards/{id}/history(Db.board_history)
- 修一个偶发失败的测试(test_ota_events_are_named_by_arg:只推一次遥测,赶上 Hub 正在同步时会漏触发);
后端 42 例本机 + 服务器全绿,已部署;线上已取到板 B 温度 24.4 / 26.0 °C
Co-Authored-By: Claude Opus 5.5 <[email protected]>
这个提交包含在:
@@ -129,3 +129,27 @@ def test_a_real_websocket_library_is_installed():
|
||||
deploy.sh 在服务器上跑这套测试,所以缺库会在换上去之前被拦住。"""
|
||||
from prrack.main import ws_library_ok
|
||||
assert ws_library_ok()
|
||||
|
||||
|
||||
def test_board_history_raw_and_minute(env):
|
||||
c, hub, sim, db = env
|
||||
base = now_ms() // 60000 * 60000 - 3 * 60000
|
||||
rows = [(base + i * 1000, B, 12000, 1500000, 35000 + i, 40000 - i, 300) for i in range(120)]
|
||||
db.many("INSERT INTO board_tele VALUES (?,?,?,?,?,?,?)", rows)
|
||||
h = c.get("/api/boards/%s/history" % B, params={"start": base, "end": base + 120000}).json()
|
||||
assert h["resolution"] == "raw" and len(h["points"]) >= 100
|
||||
p = h["points"][0]
|
||||
assert p[1] == 35000 and p[4] == 40000 and p[7] == 300 and p[8] == 12000
|
||||
# 分钟聚合:两个整分钟,每分钟 min/max 就是那一分钟里的首尾
|
||||
assert db.downsample(base + 180000) > 0
|
||||
m = db.board_history(B, base, base + 120000 + 1) # 跨度小也能强制走聚合:t0 早于原始保留期时
|
||||
rows_min = db.q("SELECT * FROM board_tele_min WHERE board_id=? ORDER BY ts", (B,))
|
||||
assert [r["t0_min"] for r in rows_min][:2] == [35000, 35060]
|
||||
assert [r["t0_max"] for r in rows_min][:2] == [35059, 35119]
|
||||
assert rows_min[0]["t1_min"] == 40000 - 59 and rows_min[0]["fan_avg"] == 300
|
||||
assert m["resolution"] == "raw"
|
||||
old = db.board_history(B, base - 10 * 86400000, base + 120000)
|
||||
# 10 天跨度 ⇒ 桶约 9 分钟,两分钟落进同一桶:min 取两分钟的最小、max 取最大
|
||||
assert old["resolution"] == "minute" and old["points"][0][2] == 35000
|
||||
assert max(p[3] for p in old["points"]) == 35119
|
||||
assert c.get("/api/boards/nope/history", params={"start": 0}).status_code == 404
|
||||
|
||||
@@ -199,8 +199,13 @@ def test_ota_events_are_named_by_arg(env):
|
||||
c, hub, sim = env
|
||||
sim._ev(0, 18, 3, 3, 996064) # 失败:哈希不对
|
||||
sim._ev(0, 18, 5) # 回滚
|
||||
sim.tick(now_ms())
|
||||
assert wait_for(lambda: len([e for e in c.get("/api/events").json() if e["type"] == 18]) == 2)
|
||||
|
||||
# 每次检查前都推一条遥测:Hub 正在同步上一批事件(启动时的 BOOT)时会跳过这一条遥测带来的
|
||||
# 「有新事件」,只推一次的话偶尔再也没人触发同步(服务器上 3 次里红过 1 次)。真板 1 s 一条,没有这个问题。
|
||||
def synced():
|
||||
sim.tick(now_ms())
|
||||
return len([e for e in c.get("/api/events").json() if e["type"] == 18]) == 2
|
||||
assert wait_for(synced)
|
||||
ev = {e["arg"]: e for e in c.get("/api/events").json() if e["type"] == 18}
|
||||
assert ev[3]["name"] == "固件升级(失败):哈希不对" and ev[3]["severe"] is True
|
||||
assert ev[5]["name"] == "固件升级(回滚)" and ev[5]["severe"] is True
|
||||
|
||||
在新工单中引用
屏蔽一个用户