将仪表盘从「四区(时钟/天气/今日实时/30天趋势)」重构为上中下三段: - A 顶部:大时钟+日期 / 天气(定位改南京·江宁) - B 中部主角 Claude Usage:5h/7d 用量进度条 + 大号百分比 + 时间维度对比 pace (pace=用量%−时间%,超前↑红/节余↓黑,参考 claude-statusline);用量≥阈值该条转红预警 - C 底部 橘喵今日实时:三列版式对齐主分支(名F12/值F24粗/环比同比偏移 +18/+34/+64/+82) - 近30天趋势图隐藏(_trend_chart 及依赖保留、未调用,可挂回) 用量只读本地、不发网络请求:新增 usage_local.py 读取 claude-statusline 落盘的 /tmp/claude/statusline-usage-cache.json(utilization + resets_at)。utilization 可能滞后, 但 resets_at 为绝对时间,故 pace 每次按当前时间现算、始终准确;文件缺失回退 "--"。 其他: - 天气定位 秦淮→江宁(config 默认 118.840,31.953) - 进度条边框随条色(红条即红框);行内元素按墨迹竖直中心对齐(_mid) - 新增 F13/F16 字号(非整数倍,fontmode=1 保持纯色) - 全图仍严格三色(黑/白/红)、无灰阶不抖动;底部留 ~11px 下边距 - 同步更新 CLAUDE.md / README.md Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
11 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
项目定位
eink-push 是 JM monorepo 中一个独立的 Python 工具,与其余 Java/Vue 子项目技术栈无关,不受父级 CLAUDE.md 的 jm-cloud/uniapp/admin 开发约束。
作用:把日期天气、Claude Code 用量、橘喵今日经营合成为一张 400×300 三色(黑/白/红,BWR) PNG,推送到 Zectrix 极趣云墨水屏设备。纯 Python + Pillow,无浏览器依赖,适合 cron 定时运行。
分支
feature/colorful:新设备在黑白外支持红色,本分支把渲染升级为黑/白/红三色。当前版式为上中下三段:A 大时钟+日期 / 天气(定位南京·江宁);B Claude Usage 主角(5h/7d 用量进度条 + 时间维度对比 pace,用量吃紧转红预警);C 橘喵今日实时三列。红作语义强调(用量预警、涨、定位、天气太阳/闪电),仍无真灰阶、不抖动,全图只三种纯色。近30天趋势图当前隐藏(renderer._trend_chart及依赖保留、未调用,随时可挂回)。
命令
pip install -r requirements.txt # 仅 Pillow>=10.0;urllib 用标准库
cp .env.example .env # 首次:复制模板并填入真实凭证 + 设备 MAC
python3 main.py # 取数据 → 渲染 → 推送(设备 ID 取自 .env)
python3 main.py --render-only # 只渲染到 output/dashboard.png,不推送(本地调试渲染时用这个)
sh run.sh # cron 包装脚本:cd 项目目录 + vfox python 绝对路径 + 追加日志到 output/cron.log
# 临时覆盖:真实环境变量优先级高于 .env
ZECTRIX_DEVICE_ID=AA:BB:CC:DD:EE:FF python3 main.py
无测试、无 lint。调试渲染效果就跑 --render-only 看 output/dashboard.png。
配置统一走 .env:所有凭证/参数放同目录 .env(已在 .gitignore,本机专用;模板见已提交的 .env.example)。config.py 顶部用零依赖加载器 _load_dotenv() 把 .env 写入 os.environ,再由各 os.environ.get(...) 读取;采用 setdefault,故真实环境变量(cron/命令行注入)优先级高于 .env。config.py 内不再保留任何明文凭证(fallback 为空串)。可配置项:ZECTRIX_API_KEY/ZECTRIX_DEVICE_ID、JM_BASE_API/JM_CLIENT_ID/JM_CLIENT_SECRET/JM_GRANT_TYPE/JM_TENANT_ID、QWEATHER_HOST/QWEATHER_KEY/WEATHER_LOCATION(默认南京·江宁)/WEATHER_TTL、USAGE_LOCAL_PATH/USAGE_WARN。
定时部署:run.sh 是 cron 入口(已在 .gitignore,本机专用)。cron 不继承登录环境,故脚本内 python 写死 vfox 绝对路径、并 cd 到项目目录后追加日志到 output/cron.log(cd 后 config.py 才能就近找到 .env)。设计为每分钟触发:业务数据每次都刷新,天气靠缓存、用量读本地文件,避免高频打接口(见 data.py)。
架构(数据 → 渲染 → 推送 三层)
main.py 串联三层,各层职责单一、低耦合:
data.py— 唯一数据入口get_dashboard_data(),返回固定结构的 dict:date/weather/mao/usage。日期时间用真实系统时间;橘喵今日经营由_mao()经jm_api、天气由_weather()经weather_api(和风,定位南京·江宁)、Claude 用量由_usage()经usage_local,任何异常都回退占位"--",不回退 mock。改数据保持返回结构不变即可(renderer 依赖其 key:weather.icon为和风图标代码;mao.cols[].hb/tb为环比/同比(direction, text),direction 为None时不画三角;usage.bars[]为{k, pct(或 None), time_pct?}、usage.warn为转红阈值)。注:_mao仍会拉mao.trend,但趋势图当前隐藏、renderer 不使用它。- 缓存(为 cron 每分钟独立进程而设,必须落盘):天气
.weather_cache.json(TTL=WEATHER_TTL,默认 15min)、趋势.trend_cache.json(按日期 key,每天只拉一次);两者拉取失败沿用旧缓存。用量不自建缓存——直接读 statusline 落盘的本地文件(见usage_local),失败即"--"。两个缓存文件已在.gitignore。 - 用量的 pace(时间维度对比):
_usage()每次按当前时间现算time_pct=(窗口已流逝/窗口长)、pace=pct−time_pct(>0 超前↑ / <0 节余↓);故即使utilization来自可能滞后的本地缓存,pace 仍随时钟准确推进。5h 窗口 18000s、7d 窗口 604800s。
- 缓存(为 cron 每分钟独立进程而设,必须落盘):天气
jm_api.py— jm-devops 后端统计接口客户端,设备密钥授权(grant_type=client_secret,免验证码)。先用CLIENT_ID + CLIENT_SECRET调POST /auth/login换 token(进程内缓存、按expire_in提前刷新),再带Authorization: Bearer <token>+clientid头调/system/statistics/{realtime,recent-days};遇 401 清 token 重登一次重试。token 字段为蛇形access_token/expire_in。注:历史上曾免鉴权直调、更早曾用账号密码+验证码 OCR 登录(ddddocr 不稳定),现统一为 client_secret 授权——密钥即长期凭证,务必走 HTTPS、优先用JM_CLIENT_SECRET环境变量、可在后端sys_client轮换。weather_api.py— 和风天气(QWeather)客户端。用用户专属 API Host(QWEATHER_HOST)+X-QW-Api-Key头鉴权;响应 gzip 压缩(按 magic number 手动解压)、返回code为字符串。get_now()取实时(now.text中文天气 /now.temp/now.icon图标代码),get_today()取/3d的daily[0]今日温区。QWEATHER_HOST/QWEATHER_KEY未配置时直接抛错→天气区显示"--"。usage_local.py— Claude Code 用量本地读取,不发任何网络请求:只读config.USAGE_LOCAL_PATH(默认/tmp/claude/statusline-usage-cache.json,由已安装的 claude-statusline 落盘),取five_hour/seven_day的utilization(已用%) 与resets_at(重置时间)。注:新版 Claude Code 经 stdin 喂 statusline,该磁盘缓存刷新不频繁、utilization 可能滞后;但resets_at是绝对时间,故data._usage现算的 pace 始终准。文件缺失/损坏→用量区"--"。(曾评估直调 Anthropic OAuth 用量接口/api/oauth/usage,按需求改为只读本地。)renderer.py—DashboardRenderer.render(data)返回 PIL'RGB'模式、仅黑/白/红三色图;render_to_file()落盘。布局硬编码为上中下三段,两道hdash横规分隔(y=90 / 200):- A 顶部(0–90):大时钟(F48)+日期(左)/ 天气(右:红定位水滴 + 温度 + 天气·温区 + 全矢量图标)。
- B 中部主角 Claude Usage(
_label标题 + 两条_usage_row):每条单行 = 左标签5h/7d(F13) + 粗进度条 + 百分比(F16) + 时间维度对比 pace(F13,_pace);pct≥warn→该条进度条(含边框)与百分比转红预警;pace 用上/下三角替代 +/−(超前↑ / 节余↓)。整行「标签·进度条·百分比·三角」用_mid()按墨迹竖直中心对齐到同一中线。 - C 底部 橘喵今日实时(
_label+ 三列,版式对齐主分支):■ 橘喵·今日实时+上次更新,列内 名(F12)/值(F24 粗)/环比·同比(_cmp_line),相对标题偏移 +18/+34/+64/+82,底部留 ~11px 下边距。 - 红色点缀系统(改配色务必克制):模块顶部
BLACK/WHITE/RED,RED=(255,0,0)是给设备红通道的明确信号。红只用在:① 定位水滴(_loc_pin(fg=RED))② 天气太阳/闪电(_weather_icon(accent=RED),云/雨/雪/雾/月仍黑)③ 用量进度条与百分比(pct≥warn) + pace 超前(↑) ④ 环比/同比「涨」的三角与数值(_cmp_line,跌/标签仍黑)。配色铁律:红与黑同属深色、红叠黑对比极低,故红只压白底或作大块实心(白字压红),绝不红字压黑底。加红先问「是否承载语义」。 _trend_chart(当前隐藏):近30天三指标折线,用_styled_polyline()按 solid/dashed/dotted 区分、主指标(流水)红线,各指标按自身极值独立归一化。已保留但render()未调用,想恢复即在 C 前挂回。- 天气图标全矢量手绘(无图片资源):
_weather_category()把和风 icon 代码归并为 8 类(sunny/clear_night/cloudy/overcast/thunder/rain/snow/fog,识别不了回退 cloudy),再由_weather_icon()用_sun/_cloud等基元按 1bit 描边画出;改天气展示从这里动。
pusher.py—push_image()走 Zectrix API(POST /devices/{id}/display/image,手写 multipart)。设备 ID 直接取config.DEVICE_ID(即.env的ZECTRIX_DEVICE_ID硬件 MAC),resolve_device_id()仅校验非空、未配置即报错——已废弃获取设备列表再取第一个的逻辑(原get_devices()已删除,不再调GET /devices)。config.py— 顶部_load_dotenv()先加载同目录.env,再定义 API、设备、画布尺寸、字体路径、推送参数;凭证 fallback 为空串,真实值来自.env/环境变量。
墨水屏渲染约束(改 renderer.py 必读)
设备是 三色(黑/白/红)、无真灰阶——灰阶只能抖动成网点(难看),所以底色只用黑白 + 红作稀缺强调,靠构图/字号/留白建立层级,不要引入灰色填充或抖动:
-
红只作语义强调:方向(涨)、警示(闪电)、强调(定位/主折线);红占墨保持很小(<10% 墨迹),滥用会稀释注意力、破坏层级。整图只允许三种纯色
(0,0,0)/(255,255,255)/(255,0,0)——渲染后可用img.getcolors()自查,出现第四种色(多为抗锯齿灰边)即为 bug。矢量图元(line/ellipse/polygon/arc)默认不抗锯齿故为纯色;文字靠fontmode="1"保持纯色。 -
字体固定用 Fusion Pixel 12px 点阵字体(
fonts/,OFL 协议),首选整数倍尺寸 12/24/48(F12/F24/F48)最锐利;用量区为拿到合适的字号层级另用了 F13/F16(1.08x/1.33x 非整数倍,笔画略不均,属有意取舍),靠d.fontmode = "1"关抗锯齿仍保持纯色不糊。跨字号竖直居中统一用_mid()(按getbbox墨迹中心对齐)。 -
加粗用「伪粗体」:
_draw()按 1~bold px 水平偏移叠绘笔画(点阵字体只有单一字重)。 -
推送时
DITHER = False(硬阈值),纯黑白图最锐利,勿改成true。 -
坐标、字号均为像素网格上的硬编码常量;调布局时注意各区分隔线
hdash的 y 值与下方组件位置联动。
注意
- 所有凭证均在
.env(不提交);config.py不再内置明文 fallback。分享代码时给出.env.example即可,真实.env切勿入库。 .gitignore已忽略:output/、__pycache__/、*.pyc、两个缓存文件.weather_cache.json/.trend_cache.json、本机专用的run.sh、含凭证的.env(模板.env.example入库)。改缓存逻辑后想强制重拉,删掉对应缓存文件即可。