feat: E1002 顶部时钟改 F84+用量行 12px 三档配色、标签右对齐

- 顶部时钟 F48→F84 占满 A 段,星期/日期移到时钟右侧并与基线对齐
- 用量行:标签常规字重右对齐贴进度条,百分比与 pace 数字 12px 粗体
- 配色:进度条/边框/百分比同色,用量与 pace 各自三档绿/黑/红、独立取色
  (曾按要求加过黄档,同日被否并移除)
- 用量块标题右侧「快照」改为「上次更新」,与今日实时统一
- 今日实时标题与指标名间距加大 4px
- 基类抽出 PCT_TRACK / LABEL_BOLD / LABEL_ALIGN / FPACE 供宽版覆写,
  400×300 小屏输出逐像素不变

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-09-14 11:39:55 +08:00
parent b58e1decf0
commit 2015947812
7 changed files with 79 additions and 51 deletions

View File

@@ -5,11 +5,14 @@ reTerminal E1002 渲染层 —— 复用 renderer.DashboardRenderer 的组件(
利用 800x480 的四倍面积重新排版为「AI 用量看板」,输出 **E Ink Spectra 6 原生六色**(黑/白/黄/红/绿/蓝)纯色图,
并生成一张固定 800x480 的 HTML 页面供 SenseCraft HMI 的 HTML 控件云端截图拉取。
版式k=1点阵字体且**只用整数倍字号 12/24/36/48**,不放大;四段,三道横规):
A 顶部090 :大时钟+日期(左)|天气(右,贴 790——与三色版同一套组件与尺寸。
版式k=1点阵字体且**只用整数倍字号 12/24/36/48/84**,不放大;四段,三道横规):
A 顶部090 **大时钟 F84**(墨迹 1679占满上下边距内的整段高度 星期/日期 F12 紧随其右、与时钟数字**基线对齐**(左)
|天气(右,贴 790——天气组件与三色版同尺寸。
B 用量96206 :左半 **Claude Usage**all 5h / all 7d / fable 7d竖规 x=400右半 **ChatGPT Usage**mockall 5h / all 7d / codex 7d
行组件沿用三色版的紧凑单行 `_usage_row`(行距 31,标签用 bar["label"]statusline 同款写法),列宽按最长标签自适应。
C 今日218318**橘喵·今日实时** 五列全宽(探店单量/探店流水/团购单量/团购流水/毛利):列内四行逐行居中——
行组件沿用三色版的紧凑单行 `_usage_row`(行距 31**三处文字统一 12px**(基类 F13/F16/FPACE 都映射到 12px
标签**常规字重、在标签列内右对齐**(贴进度条),百分比与 pace 数字**粗体**(曾试过 24px用户嫌大
标签用 bar["label"]statusline 同款写法),列宽按最长标签自适应;标题右侧「上次更新 HH:MM」与今日实时同款。
C 今日218322**橘喵·今日实时** 五列全宽(探店单量/探店流水/团购单量/团购流水/毛利):列内四行逐行居中——
名 F12 / 值 F24 粗 / 环比 / 同比(五列每列 ~157px原「值 F36 右侧叠放环比/同比」并排组放不下)。
D 行情(~336470**BTC/USDT 时K近 48 小时)** 蜡烛图(`_btc_chart`,数据来自 btc_apiOKX 主源、Coinbase/Huobi 备源5 分钟缓存;
周期/根数由 BTC_BAR/BTC_LIMIT 配置)——标题右侧最新价 + 24h 涨跌幅;**涨(收≥开)绿、跌红**,实心蜡烛 + 1px 影线;
@@ -17,9 +20,12 @@ reTerminal E1002 渲染层 —— 复用 renderer.DashboardRenderer 的组件(
可选470479六色测试色条config.E1002_COLOR_STRIP默认关真机校准色彩量化时打开
六色的语义分工(每种颜色只有一个含义,不为「有」而用):
- 黑/白:结构与正文。红:危险(用量 ≥ warn 的进度条+边框+百分比、pace ≥ +tol、涨、定位、太阳/闪电。
- 绿:健康(用量 ≤ ok 的进度条+边框、pace ≤ tol、K 线上涨;红另用于 K 线下跌。用量与 pace 各自三档:绿 / 黑 / 红(阈值 USAGE_OK / USAGE_WARN / PACE_TOL)。
- 蓝:天气雨滴/雪花。黄:不用(用户明确不要黄色进度条;曾试过 60%80% 黄「注意」档,已移除)。
- 黑/白:结构与正文。红:危险、涨、定位、太阳/闪电绿健康、K 线上涨(红另用于 K 线下跌);蓝:天气雨滴/雪花
黄:**不用**2026-09-14 曾按要求加过四档含黄,随即被否)。
- 用量与 pace **各自三档、颜色顺序固定 绿 → 黑 → 红**,两者独立计算(`_usage_colors` / `_pace_color`
用量 pct≤ USAGE_OK(30) 绿「健康」|(30, 80) 黑「正常」|≥ USAGE_WARN(80) 红「危险」——进度条填充、边框与**百分比数字三者同色**
pace = 用量% 时间%:≤ PACE_TOL(10) 绿「节余」|(10, +10) 黑「持平」|≥ +10 红「偏快」。
绿色数字在白底上比黑字淡一些24px 字号下可读;这是「百分比随条色」的已知取舍。
- 整图只允许六种纯色;渲染后可用 `img.getcolors()` 自查。
为什么是「固定 800x480 + 一张 PNG」而不是自适应 HTML
@@ -33,6 +39,7 @@ import time
import config
from renderer import DashboardRenderer, BLACK, WHITE, RED
import config as _cfg
from PIL import ImageFont
# E Ink Spectra 6 原生六色。取纯饱和值作为给平台量化器的明确信号(与 ESPHome/GxEPD2 驱动的 BWYRGB 映射一致)。
YELLOW, GREEN, BLUE = (255, 255, 0), (0, 255, 0), (0, 0, 255)
@@ -42,30 +49,32 @@ SPECTRA6 = [BLACK, WHITE, YELLOW, RED, GREEN, BLUE]
class E1002Renderer(DashboardRenderer):
"""800x480 六色版。组件继承k=1字号不变只重写版式与配色钩子。"""
PCT_TRACK = 0 # 百分比是整数倍字号(自带侧边距),不再手动加字符间距
LABEL_BOLD = False # 标签常规字重(数值才加粗,层级:数值 > 标签)
LABEL_ALIGN = "r" # 标签右对齐贴进度条长短标签all 5h / fable 7d右缘齐平
def __init__(self, font_path=config.FONT_PATH):
super().__init__(font_path, size=(config.E1002_WIDTH, config.E1002_HEIGHT), k=1)
# 宽版只用点阵字体的**整数倍字号**12 / 24 / 36 / 48基类给用量行用的 F13标签pace F16百分比
# 是非整数倍缩放(笔画不均),这里分别替换成 12px24px;行几何靠 _mid 按墨迹中心对齐,不受字号变化影响。
from PIL import ImageFont
# 宽版只用点阵字体的**整数倍字号**12 / 24 / 36 / 48 / 84):基类给用量行用的 F13标签/ FPACEpace/ F16百分比
# 是非整数倍缩放(笔画不均),这里统一映射到 12px24px 试过、用户嫌大);标签常规字重、百分比与 pace 数字粗体;
# 行几何靠 _mid 按墨迹中心对齐不受字号变化影响。F84 给顶部大时钟7 倍整数缩放,墨迹 63px 恰好占满 A 段)。
self.F13 = ImageFont.truetype(font_path, 12)
self.F16 = ImageFont.truetype(font_path, 24)
self.F16 = self.F13
self.FPACE = self.F13
self.F84 = ImageFont.truetype(font_path, 84)
# ---------- 配色钩子(三档:绿 健康 / 黑 正常 / 红 危险----------
# ---------- 配色钩子(三档,顺序固定 绿 → 黑 → 红;用量与 pace 各自独立计算----------
_ok, _pace_tol = _cfg.USAGE_OK, _cfg.PACE_TOL # 由 _usage_block 按数据覆盖
def _usage_colors(self, ok, pct, warn, bar):
"""进度条填充边框同色pct ≥ warn 红pct ≤ ok 绿|其间黑。
百分比文字只在红档转红,绿档仍黑——绿在白底上偏淡,做大块填充清楚、做小字发虚。"""
"""进度条填充边框、百分比数字三者同色pct ≤ ok 绿「健康」其间黑「正常」pct ≥ warn 红「危险」。"""
if not ok:
return BLACK, BLACK, BLACK
if pct >= warn:
return RED, RED, RED
if pct <= self._ok:
return GREEN, GREEN, BLACK
return BLACK, BLACK, BLACK
c = RED if pct >= warn else GREEN if pct <= self._ok else BLACK
return c, c, c
def _pace_color(self, diff):
"""pace = 用量% 时间%:≥ +tol 红(烧得比时间快,超前过多)|tol 绿节余充足)|其间黑"""
"""pace = 用量% 时间%(与用量档位独立):tol 绿节余」|(tol, +tol) 黑「持平」|≥ +tol 红「偏快」"""
if diff >= self._pace_tol:
return RED
if diff <= -self._pace_tol:
@@ -86,14 +95,14 @@ class E1002Renderer(DashboardRenderer):
self.d.rectangle([bx0, y0, bx1, y1], outline=BLACK, width=1)
def _usage_block(self, x0, x1, y0, usage):
"""一个用量区块标题_label右侧「快照 HH:MM」mock 「示例数据」)+ 逐行紧凑 `_usage_row`(行距 31
标签列宽按本块最长标签墨迹 + 8px 算,保证 "Fable 7d" 这类长标签不压进度条。返回末行底部 y。"""
right = "示例数据" if usage.get("mock") else (f'快照 {usage["updated"]}' if usage.get("updated") else None)
"""一个用量区块标题_label右侧「上次更新 HH:MM」——与今日实时同款文案;mock 数据则标「示例数据」)
+ 逐行紧凑 `_usage_row`(行距 31。标签列宽按本块最长标签墨迹 + LABEL_GAP 算(标签右对齐贴进度条,短标签左侧留白)。返回末行底部 y。"""
right = "示例数据" if usage.get("mock") else (f'上次更新 {usage["updated"]}' if usage.get("updated") else None)
self._label(y0, usage.get("title", "Usage"), right=right, x0=x0, x1=x1)
warn = usage.get("warn", 80)
self._ok, self._pace_tol = usage.get("ok", _cfg.USAGE_OK), usage.get("pace_tol", _cfg.PACE_TOL)
bars = [dict(b, k=b.get("label") or b["k"]) for b in usage.get("bars", [])] # 宽版用长标签all 5h / fable 7d
label_w = max([self._tw(b["k"], self.F13) + 1 for b in bars] + [26]) + 8
label_w = max([self._tw(b["k"], self.F13) for b in bars] + [26]) + self.LABEL_GAP
y = y0 + 27
for b in bars:
self._usage_row(x0, x1, y, b, warn, label_w=label_w)
@@ -102,7 +111,8 @@ class E1002Renderer(DashboardRenderer):
def _realtime(self, y0, mao, x0=8, x1=None):
"""橘喵·今日实时五列(宽版):探店单量 / 探店流水 / 团购单量 / 团购流水 / 毛利,列内四行逐行水平居中:
指标名 F12y0+17)→ 值 F24 粗y0+34)→ 环比y0+68)→ 同比y0+84,行距 16按墨迹框算↔值 ~9px、值↔环比 ~6px、环比↔同比 5px。
指标名 F12y0+21→ 值 F24 粗y0+38)→ 环比y0+72)→ 同比y0+88,行距 16按墨迹框算标题↔名 ~9px(原 ~5px 太挤,整列下移 4px
名↔值 ~9px、值↔环比 ~6px、环比↔同比 5px。
为什么不再是「值 F36 右侧叠放环比/同比」的并排组:五列每列仅 ~157pxF36 并排组如「2.12万」102间隙环比 77
近 194px 放不下;降到 F24 后并排组仍有 156px贴着两侧分隔线太挤。竖排最宽元素「1234.56%」一行 89px余量充足
与 400×300 小屏同构。(更早曾试过名与值同行、名贴左上角角标,均已否。)返回区块底部 y。"""
@@ -110,16 +120,16 @@ class E1002Renderer(DashboardRenderer):
self._label(y0, "橘喵·今日实时", right=f'上次更新 {mao["upd"]}', x0=x0 + 2, x1=x1 - 2)
cols = mao["cols"]; n = len(cols)
cw = (x1 - x0) / n
TAG_Y, VAL_Y, HB_Y, TB_Y = y0 + 17, y0 + 34, y0 + 68, y0 + 84
TAG_Y, VAL_Y, HB_Y, TB_Y = y0 + 21, y0 + 38, y0 + 72, y0 + 88
for i in range(1, n):
self.vdash(x0 + cw * i, y0 + 22, y0 + 98)
self.vdash(x0 + cw * i, y0 + 26, y0 + 102)
for i, c in enumerate(cols):
cx = x0 + cw * (i + 0.5)
self.ctext(cx, TAG_Y, c["k"], self.F12)
self.ctext(cx, VAL_Y, c["v"], self.F24, bold=1)
self._cmp_line(cx, HB_Y, "环比", c["hb"])
self._cmp_line(cx, TB_Y, "同比", c["tb"])
return y0 + 100
return y0 + 104
@staticmethod
def _fmt_price(v):
@@ -195,10 +205,16 @@ class E1002Renderer(DashboardRenderer):
dt, wt, mao = data["date"], data["weather"], data["mao"]
usage, gpt = data["usage"], data.get("usage_gpt") or {}
# ---- A. 顶部:大时钟+日期(左)/ 天气(右)——与三色版同尺寸 ----
# ---- A. 顶部:大时钟 F84 占满整段(左)+ 星期/日期紧随其右、与时钟数字基线对齐 / 天气(右)----
DIV_Y = 90
self.text(10, 0, dt["time"], self.F48, bold=2)
self.text(12, 64, f'{dt["week"]} {dt["greg"]}', self.F12)
TIME_BOLD, INK_TOP = 3, 16 # 伪粗体 3px墨迹顶沿 16 与旧版 F48 相同,保住上边距
_, tt, _, tb = self.F84.getbbox(dt["time"]) # F84 数字墨迹 2891 → 高 63摆到 1679距横规 11px
time_y = INK_TOP - tt
base_y = time_y + tb # 时钟数字基线(墨迹底沿)
self.text(10, time_y, dt["time"], self.F84, bold=TIME_BOLD)
date_txt = f'{dt["week"]} {dt["greg"]}'
_, _, _, db = self.F12.getbbox(dt["greg"]) # 用日期数字的底沿对齐基线(汉字底沿比数字低 1px忽略
self.text(10 + self._tw(dt["time"], self.F84) + TIME_BOLD + 14, base_y - db, date_txt, self.F12)
loc, temp, cr = wt["loc"], wt["temp"], f'{wt["cond"]} {wt["range"]}'
self.rtext(R, 8, loc, self.F12)
self.rtext(R, 30, temp, self.F24, bold=1)