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:
@@ -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 顶部(0~90) :大时钟+日期(左)|天气(右,贴 790)——与三色版同一套组件与尺寸。
|
||||
版式(k=1,点阵字体且**只用整数倍字号 12/24/36/48/84**,不放大;四段,三道横规):
|
||||
A 顶部(0~90) :**大时钟 F84**(墨迹 16~79,占满上下边距内的整段高度)+ 星期/日期 F12 紧随其右、与时钟数字**基线对齐**(左)
|
||||
|天气(右,贴 790)——天气组件与三色版同尺寸。
|
||||
B 用量(96~206) :左半 **Claude Usage**(all 5h / all 7d / fable 7d)|竖规 x=400|右半 **ChatGPT Usage**(mock:all 5h / all 7d / codex 7d)。
|
||||
行组件沿用三色版的紧凑单行 `_usage_row`(行距 31),标签用 bar["label"](statusline 同款写法),列宽按最长标签自适应。
|
||||
C 今日(218~318):**橘喵·今日实时** 五列全宽(探店单量/探店流水/团购单量/团购流水/毛利):列内四行逐行居中——
|
||||
行组件沿用三色版的紧凑单行 `_usage_row`(行距 31);**三处文字统一 12px**(基类 F13/F16/FPACE 都映射到 12px):
|
||||
标签**常规字重、在标签列内右对齐**(贴进度条),百分比与 pace 数字**粗体**(曾试过 24px,用户嫌大),
|
||||
标签用 bar["label"](statusline 同款写法),列宽按最长标签自适应;标题右侧「上次更新 HH:MM」与今日实时同款。
|
||||
C 今日(218~322):**橘喵·今日实时** 五列全宽(探店单量/探店流水/团购单量/团购流水/毛利):列内四行逐行居中——
|
||||
名 F12 / 值 F24 粗 / 环比 / 同比(五列每列 ~157px,原「值 F36 + 右侧叠放环比/同比」并排组放不下)。
|
||||
D 行情(~336~470):**BTC/USDT 时K(近 48 小时)** 蜡烛图(`_btc_chart`,数据来自 btc_api:OKX 主源、Coinbase/Huobi 备源,5 分钟缓存;
|
||||
周期/根数由 BTC_BAR/BTC_LIMIT 配置)——标题右侧最新价 + 24h 涨跌幅;**涨(收≥开)绿、跌红**,实心蜡烛 + 1px 影线;
|
||||
@@ -17,9 +20,12 @@ reTerminal E1002 渲染层 —— 复用 renderer.DashboardRenderer 的组件(
|
||||
可选(470~479):六色测试色条(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(百分比)
|
||||
# 是非整数倍缩放(笔画不均),这里分别替换成 12px 与 24px;行几何靠 _mid 按墨迹中心对齐,不受字号变化影响。
|
||||
from PIL import ImageFont
|
||||
# 宽版只用点阵字体的**整数倍字号**(12 / 24 / 36 / 48 / 84):基类给用量行用的 F13(标签)/ FPACE(pace)/ F16(百分比)
|
||||
# 是非整数倍缩放(笔画不均),这里统一映射到 12px(24px 试过、用户嫌大);标签常规字重、百分比与 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):
|
||||
"""橘喵·今日实时五列(宽版):探店单量 / 探店流水 / 团购单量 / 团购流水 / 毛利,列内四行逐行水平居中:
|
||||
指标名 F12(y0+17)→ 值 F24 粗(y0+34)→ 环比(y0+68)→ 同比(y0+84,行距 16);按墨迹框算,名↔值 ~9px、值↔环比 ~6px、环比↔同比 5px。
|
||||
指标名 F12(y0+21)→ 值 F24 粗(y0+38)→ 环比(y0+72)→ 同比(y0+88,行距 16);按墨迹框算,标题↔名 ~9px(原 ~5px 太挤,整列下移 4px)、
|
||||
名↔值 ~9px、值↔环比 ~6px、环比↔同比 5px。
|
||||
为什么不再是「值 F36 + 右侧叠放环比/同比」的并排组:五列每列仅 ~157px,F36 并排组(如「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 数字墨迹 28~91 → 高 63,摆到 16~79,距横规 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)
|
||||
|
||||
Reference in New Issue
Block a user