Files
eink-push/CLAUDE.md
YANG JIANKUAN 3e0e6c73a1 refactor: 配置迁移至 .env,推送直连设备 MAC
- config.py 顶部新增零依赖 _load_dotenv(),凭证/参数统一从同目录 .env 读取,
  真实环境变量(cron/命令行注入)优先;移除所有明文 fallback
- 新增 .env.example 模板,.env 加入 .gitignore
- pusher.py 删除 get_devices(),resolve_device_id() 直接取 ZECTRIX_DEVICE_ID,
  不再调 GET /devices 列表
- jm_api.py 改为设备密钥授权(client_secret,免验证码),token 进程内缓存 + 401 重试
- 同步 CLAUDE.md / main.py 文档

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29 15:25:36 +08:00

7.2 KiB
Raw Blame History

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 开发约束。

作用把橘喵今日经营、近30天趋势、日期与天气合成为一张 400×300 纯黑白1bit PNG推送到 Zectrix 极趣云墨水屏设备(zectrix-s3-epaper-4.2)。纯 Python + Pillow无浏览器依赖,适合 cron 定时运行。

命令

pip install -r requirements.txt        # 仅 Pillow>=10.0urllib 用标准库
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-onlyoutput/dashboard.png

配置统一走 .env:所有凭证/参数放同目录 .env(已在 .gitignore,本机专用;模板见已提交的 .env.example)。config.py 顶部用零依赖加载器 _load_dotenv().env 写入 os.environ,再由各 os.environ.get(...) 读取;采用 setdefault,故真实环境变量cron/命令行注入)优先级高于 .envconfig.py 内不再保留任何明文凭证fallback 为空串)。可配置项:ZECTRIX_API_KEY/ZECTRIX_DEVICE_IDJM_BASE_API/JM_CLIENT_ID/JM_CLIENT_SECRET/JM_GRANT_TYPE/JM_TENANT_IDQWEATHER_HOST/QWEATHER_KEY/WEATHER_LOCATION/WEATHER_TTL

定时部署run.sh 是 cron 入口(已在 .gitignore本机专用。cron 不继承登录环境,故脚本内 python 写死 vfox 绝对路径、并 cd 到项目目录后追加日志到 output/cron.logcdconfig.py 才能就近找到 .env)。设计为每分钟触发:业务数据每次都刷新,天气/趋势靠缓存避免高频打接口(见 data.py)。

架构(数据 → 渲染 → 推送 三层)

main.py 串联三层,各层职责单一、低耦合:

  • data.py — 唯一数据入口 get_dashboard_data(),返回固定结构的 dict。日期/时间用真实系统时间;橘喵今日经营+趋势由 _mao()jm_api、天气由 _weather()weather_api(和风,定位南京·秦淮)拉真实数据,任何接口异常都回退占位 "--",不回退 mock。改数据保持返回结构不变即可renderer 依赖其 key尤其 mao.trend.series 每条须带 styleweather.icon 为和风图标代码;环比/同比 (direction, text)direction 为 None 时不画涨跌三角)。
    • 两套文件缓存(为 cron 每分钟独立进程而设,必须落盘):天气缓存 .weather_cache.jsonTTL=WEATHER_TTL,默认 15min未过期不发请求趋势缓存 .trend_cache.json(按日期 key当天命中即不再调 recent-days,每天只拉一次)。两者拉取失败均沿用旧缓存避免整片 "--",仍无缓存才回退占位。两个缓存文件已在 .gitignore
  • jm_api.py — jm-devops 后端统计接口客户端,设备密钥授权grant_type=client_secret,免验证码)。先用 CLIENT_ID + CLIENT_SECRETPOST /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 HostQWEATHER_HOST+ X-QW-Api-Key 头鉴权;响应 gzip 压缩(按 magic number 手动解压)、返回 code字符串get_now() 取实时(now.text 中文天气 / now.temp / now.icon 图标代码),get_today()/3ddaily[0] 今日温区。QWEATHER_HOST/QWEATHER_KEY 未配置时直接抛错→天气区显示 "--"
  • renderer.pyDashboardRenderer.render(data) 返回 PIL 'L' 模式纯 0/255 图;render_to_file() 落盘。布局硬编码为四区render 内 A/B/C/DA 大时钟+日期 / B 天气 / C 橘喵今日三列 / D 近30天趋势折线图。趋势线用 _styled_polyline() 按 solid/dashed/dotted 区分(设备无颜色),各指标按自身极值独立归一化(故只看趋势形状、不可横向比绝对值)。
    • 天气图标全矢量手绘(无图片资源):_weather_category() 把和风 icon 代码归并为 8 类sunny/clear_night/cloudy/overcast/thunder/rain/snow/fog识别不了回退 cloudy再由 _weather_icon()_sun/_cloud 等基元按 1bit 描边画出;改天气展示从这里动。
  • pusher.pypush_image() 走 Zectrix APIPOST /devices/{id}/display/image,手写 multipart。设备 ID 直接取 config.DEVICE_ID(即 .envZECTRIX_DEVICE_ID 硬件 MACresolve_device_id() 仅校验非空、未配置即报错——已废弃获取设备列表再取第一个的逻辑(原 get_devices() 已删除,不再调 GET /devices)。
  • config.py — 顶部 _load_dotenv() 先加载同目录 .env,再定义 API、设备、画布尺寸、字体路径、推送参数凭证 fallback 为空串,真实值来自 .env/环境变量。

墨水屏渲染约束(改 renderer.py 必读)

设备是 1bit、无真灰阶——灰阶只能抖动成网点(难看),所以全程纯黑白,靠构图/字号/留白建立层级,不要引入灰色填充或抖动

  • 字体固定用 Fusion Pixel 12px 点阵字体fonts/OFL 协议),只按整数倍尺寸 12/24/48 渲染(F12/F24/F48),并设 d.fontmode = "1" 关闭抗锯齿,否则像素会糊。
  • 加粗用「伪粗体」:_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 入库)。改缓存逻辑后想强制重拉,删掉对应缓存文件即可。