- btc_api:新增 gateio/huobi_aws/binance_vision 三源,config 增 BTC_SOURCES 控制尝试顺序(境内 okx/coinbase/huobi 主域名不通) - server:IMAGE_TOKEN 非空时三个渲染端点须带 ?token=…,否则与未知路径同样 404(防爬虫扫描);推送令牌改常量时间比较 - 文档:CLAUDE.md/README/.env.example 记录 NGCQ 部署(systemd eink-server、1Panel API 建站坑)、本机 server/cloudflared 已卸载 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
49 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,推送到局域网墨水屏设备(喵喵固件,开源:https://gitee.com/gxp666111/miaomiao)。纯 Python + Pillow,无浏览器依赖,适合 cron 定时运行。
分支
feature/colorful:新设备在黑白外支持红色,本分支把渲染升级为黑/白/红三色。当前版式为上中下三段:A 大时钟+日期 / 天气(定位南京·江宁);B Claude Usage 主角(5h/7d 用量进度条 + 时间维度对比 pace,用量吃紧转红预警);C 橘喵今日实时三列。红作语义强调(用量预警、涨、定位、天气太阳/闪电),仍无真灰阶、不抖动,全图只三种纯色。近30天趋势图当前隐藏(renderer._trend_chart及依赖保留、未调用,随时可挂回)。
分支
feature/reterminal-e1002(基于feature/colorful):新增第二个目标设备 Seeed reTerminal E1002(7.3 寸 800×480,E Ink Spectra 6 原生六色:黑/白/黄/红/绿/蓝,真机已验证六色量化干净)。该设备不接受局域网直推,而是由 SenseCraft HMI 云端的 HTML 控件按设备刷新间隔(最小 5 分钟)每周期重新抓取一个公网 HTTPS URL、无头浏览器截图后量化下发;不支持 ETag/304,内容不变也会整屏重绘(全刷约 17 秒)。真机验证(2026-09):Image 控件只在填写 URL 时抓一次快照、此后不再更新;HTML 控件填同一 URL 则每周期重抓——所以对外只用 HTML 控件。当前主形态是server.py动态服务(部署公网 HTTPS;局域网地址/明文 HTTP 云端抓不到):三条恒定 URL 并行供对比——方案一IMAGE_PATH(默认/e1002.png)每次请求内存中现渲染 PNG(Pillow 点阵版);方案二HTML_PATH(默认/e1002.html)每次请求生成 800×480 现代网页(html_renderer.py,系统/网络字体 + 内联 SVG K 线,无 JS)由平台截图——真机实拍:文字抗锯齿灰边被平台抖动成毛边、小字与复杂汉字发糊;方案三HTML_PNG_PATH(默认/e1002-web.png)同一网页由服务端无头 Chrome 截图后按最近色(不抖动)固化为六色 PNG(html_shot.py),平台拿到纯色图无从抖动,需服务器装 Chrome。结论(2026-09-10):方案一为主力——方案二/三的矢量字体在 125 PPI 下小字必然毛糙(抗锯齿灰边无论由平台还是我们二值化都只能变成锯齿/断笔,见output/e1002/font_compare.png:矢量字体单色渲染 ≈ 抗锯齿后二值化,都不如点阵),Canvas 逐像素画也改变不了这点(Pillow 本身就是逐像素画)。方案二/三端点保留供对比,不再投入。宽版只用点阵字体的整数倍字号 12/24/36/48/84(E1002Renderer.__init__把基类的 F13/F16/FPACE 都映射到 12px——用量行 标签常规字重、右对齐贴进度条(基类类属性LABEL_BOLD/LABEL_ALIGN/LABEL_GAP),百分比与 pace 数字粗体;24px 试过被嫌大;另加 F84 给顶部大时钟),可用all(v % 12 == 0 for v in 字体尺寸)自查。天气/橘喵/BTC/设备遥测 走服务端内存缓存 + 有效期(WEATHER_TTL/MAO_TTL/BTC_TTL/DEVICE_TTL,默认 900/60/300/300 秒,0=每次实拉;请求时过期才实拉并更新时间戳,失败沿用旧值、不更新时间戳),服务不读本机任何文件;Claude/Codex 用量为账户级、形态 A:本机main.py --target push采集(Claude 复用 Claude Code 钥匙串登录态调/api/oauth/usage;Codex 经codex app-serverRPC)后推送原始快照(X-Push-Token鉴权),服务端内存暂存、不持任何凭证、渲染时现算 pace。静态形态main.py --target e1002(output/e1002/index.html+dashboard.png)仍保留。渲染由renderer_e1002.py完成:继承DashboardRenderer组件、k=1 字号与三色版完全相同(不放大,放大后字太大很傻),四段版式:A 大时钟 F84(墨迹 16~79)+ 其右一列三行、左缘对齐(F12,行距 22):① 电池图标+电量(充电中绿、≤20% 红)② 温度计+温度 | 蓝色水滴+湿度(①② 数字 F12 伪粗体、字号不变,占位 -- 不加粗,2026-09-14)③ 星期/日期(与时钟基线对齐);右侧天气(2026-09-15 定稿)上下两层:上层 天气图标 ×1.25 | 实时温度 F48 粗 3(墨迹顶沿 16 与时钟齐平、底沿 52,右贴页边 790;「°」用 F24 粗 1 作上标、墨迹顶沿对齐数字顶沿、补回字形右侧空白使墨迹右缘贴页边;图标按类别真实墨迹范围(E1002Renderer._ICON_EXTENT,多云太阳光芒探出名义框顶部 4 格、雷阵雨闪电垂到云下至 46)竖直居中于温度墨迹中心);下层 一行「红定位水滴+城市 天气 温区」F12 右对齐贴 790,与日期行同基线(底沿 79,与时钟基线对齐)。历史(均已否):同日上午「三行信息(天气/温区/城市)左缘对齐|图标 ×1.75|温度 F84 与时钟同高」;2026-09-14「两行(城市/天气·温区)|图标 ×1.5|温度 F72 小一号」;更早「城市 / F24 温度 / 天气·温区」三行右对齐+图标在左)——设备数据来自 SenseCraft 设备遥测sensecraft_api.py(2026-09-14 新增;先做过「状态栏横排在时钟上方+时钟缩 F60」被否)/天气;B 左 Claude Usage 两行(5h / 合并行 7d——整行高度不变、一个大边框,边框内上下精确对半两条 7px 实心填充(紧挨、无空隙,2026-09-14 用户要求去掉白隔):上=总 7d、下=fable 7d,各按自身档位取色;右侧百分比与 pace 只显示两者中较大者,边框与文字随其档位取色;E1002Renderer._usage_row_split以较大者合成一条交给基类_usage_row(已返回条几何)画边框/文字后在内部重绘两条半高实心。2026-09-14 为与右块对称而合并,先做过「两条各带边框的细条+两行数字」版被否;标签去掉 all 前缀)、右 Codex Usage 两行(5h / 7d——ChatGPT 订阅的 Codex 额度池;服务端未收到推送时 mock 标「示例数据」)。宽版用量行的空值不显示 "--":pct 为 None 按 0 画("00%"、pace "00"、空条、全黑中性色),且 pace 列在 diff=0 时三角位画一个等宽等号「=」(2026-09-14 用户要求,原为留空、后短暂用过「-」;已下沉到基类_pace,小屏同样)——各行进度条右端齐平、等宽(2026-09-14;Codex Plus 接口secondary_window就是 null,原始wham/usage已核对,5h 行为空是数据如此)。真 0 的窗口也必须有 pace 列:Claude 5h 空闲时接口回utilization: 0.0, resets_at: null,算不出 time_pct,早先渲染器遇「有 pct 无 time_pct」整列跳过,该行条就比别行长——现data.usage_from_raw对 pct=0 且无重置时刻的窗口记 time_pct=0(窗口未开始),基类_usage_row只要有 pct 就画 pace(time_pct 缺失按 0 差值画 "00"),紧凑单行样式(曾试过撑满高度的大卡片,被否);C 橘喵·今日实时五列全宽(探店单量/探店流水/团购单量/团购流水/毛利,2026-09-11 接口新增团购字段后由三列改五列;列内四行逐行居中:名 F12 / 值 F24 粗 / 环比 / 同比,与小屏同构——五列每列仅 ~157px,此前「值 F36 粗 + 右侧叠放的环比/同比」并排组约 194px 放不下、降 F24 后 156px 仍贴分隔线,故改竖排;更早试过名与值同行、左上角角标,均已否);D BTC/USDT 时 K 蜡烛图(近 48 小时 = 2 天,2026-09-14 由 72 根改 48 根)(免费公开行情:OKX 主源、Coinbase/Huobi 备源,btc_api.py,5 分钟缓存;周期/根数BTC_BAR/BTC_LIMIT可配,日 K 也支持;涨绿跌红)。此前放过 30 天折线图(用户明确不要)和「近 7 天」数据表(已被 K 线替换)。六色语义:用量进度条(含边框、百分比数字三者同色——2026-09-14 用户要求百分比随条色)与 pace 各自三档、颜色顺序固定 绿→黑→红、两者独立取色:用量 ≤USAGE_OK30 绿「健康」|30~80 黑「正常」|≥USAGE_WARN80 红「危险」;pace ≤ −PACE_TOL10 绿「节余」|±10 内黑「持平」|≥ +10 红「偏快」。黄不用:2026-09-14 曾按要求加过 60%~80% 黄档(含USAGE_NOTICE/PACE_WARN两个阈值),同日被否并整体移除,别再加回。今日实时的环比/同比「三角 + 数值」涨绿跌红(2026-09-14 改,与 K 线一致;基类钩子_cmp_color,三色小屏仍涨红);红另用于定位/太阳闪电,蓝=雨雪。
命令
pip install -r requirements.txt # 仅 Pillow>=10.0;urllib 用标准库
cp .env.example .env # 首次:复制模板并填入真实凭证 + 设备 host
python3 main.py # 取数据 → 渲染 → 推送(设备 host 取自 .env 的 EPD_HOST)
python3 main.py --render-only # 只渲染到 output/dashboard.png,不推送(本地调试渲染时用这个)
python3 main.py --target e1002 # reTerminal E1002 静态形态:渲染 800x480 六色图 + 固定尺寸 index.html 到 output/e1002/,不推送
python3 server.py # reTerminal E1002 动态服务(部署公网):GET /e1002.png(内存现渲染 PNG)/ GET /e1002.html(现代网页)/ GET /e1002-web.png(网页截图固化六色)/ /health
python3 main.py --target push # 本机:采集 Claude(钥匙串登录态→/api/oauth/usage)与 Codex(codex app-server RPC)账户级用量并推送到 server.py(需 .env 的 PUSH_URL + PUSH_TOKEN)
python3 claude_usage.py [--force] # 单独调试 Claude 采集(--force 跳过 TTL 缓存)
python3 codex_usage.py [--force] # 单独调试 Codex 采集;需 Codex 已用 ChatGPT 账号 `codex login`(默认 ~/.codex)
python3 probe_server.py # 验证工具:同一 URL 是否被平台周期重抓(带访问日志)
sh run.sh # cron 包装脚本:cd 项目目录 + vfox python 绝对路径 + 追加日志到 output/cron.log
# 临时覆盖:真实环境变量优先级高于 .env
EPD_HOST=epd400a44.local 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 为空串)。可配置项:EPD_HOST(局域网设备 mDNS host,默认 epd400a44.local)、JM_BASE_API/JM_CLIENT_ID/JM_CLIENT_SECRET/JM_GRANT_TYPE/JM_TENANT_ID、QWEATHER_HOST/QWEATHER_KEY/WEATHER_LOCATION(默认南京·江宁)/WEATHER_TTL、设备遥测 SENSECRAFT_API_KEY/SENSECRAFT_DEVICE_EUI/DEVICE_TTL、服务端 FETCH_BUDGET/IMAGE_TOKEN(页面访问令牌)/PUSH_TOKEN、USAGE_WARN、Claude 采集 CLAUDE_USAGE_TTL(快照有效期,语义同 WEATHER_TTL,0=每次实拉)/CLAUDE_USAGE_BACKOFF/CLAUDE_USAGE_SCOPE_MODEL、Codex 采集 CODEX_BIN/CODEX_USAGE_HOME/CODEX_USAGE_TTL/CODEX_USAGE_BACKOFF、仅宽版的 USAGE_OK/PACE_TOL/BTC_BAR/BTC_LIMIT/BTC_TTL/BTC_SOURCES(行情源顺序,境内服务器须排 gateio/huobi_aws/binance_vision 在前)/MAO_TTL、E1002_COLOR_STRIP。
定时部署:由 macOS launchd LaunchAgent 触发 run.sh(不再用 cron——cron 在现代 macOS 上易被 TCC/权限静默拦截,launchd 是系统原生调度器)。LaunchAgent plist 装在 ~/Library/LaunchAgents/com.jm.eink-push.plist(机器专用,未入库,等价于 run.sh/.env 的本机产物),StartInterval=300 + RunAtLoad=true:登录/重启后立即跑一次,此后每 5 分钟一次。用 LaunchAgent(非 LaunchDaemon)是因为脚本依赖当前用户的登录钥匙串(Claude Code 凭证)、~/.codex(Codex 登录态)、.env,必须在用户会话下运行——机器需已登录(或开了自动登录)才能保证开机即触发。run.sh 内部逻辑不变:cron 不继承登录环境,故脚本内 python 写死 vfox 绝对路径、并 cd 到项目目录后追加日志到 output/cron.log(cd 后 config.py 才能就近找到 .env);launchd 自身的 stdout/stderr 另落 output/launchd.log(正常应为空,非空说明进程本身起不来,而非业务报错)。常用排查:launchctl list | grep com.jm.eink-push 看是否常驻及最近退出码,launchctl kickstart -k gui/$(id -u)/com.jm.eink-push 手动立即触发一次;改 plist 后需 launchctl bootout + bootstrap 重新加载才生效(热改文件不会自动生效)。每 5 分钟触发:业务数据每次都刷新,天气靠缓存、两路用量各有 CLAUDE_USAGE_TTL/CODEX_USAGE_TTL 有效期缓存(默认 300s,同一分钟内小屏渲染与上报两个进程只打一次接口),避免高频打接口(见 data.py)。
架构(数据 → 渲染 → 推送 三层)
main.py 串联三层,各层职责单一、低耦合:
data.py— 两种形态:本机get_dashboard_data()(带文件缓存,两路用量经本机采集器);服务端用纯拉取函数weather_fetch()/mao_fetch()/btc_fetch()+usage_from_raw(now, raw, windows, title)(_usage(now, raw=)/_usage_gpt(now, raw=, allow_local=)都基于它,raw=None 时前者经claude_usage本机采集、后者经codex_usage本机采集,Codex 采集失败或服务端allow_local=False时用gpt_mock_raw()并标 mock)。所有「现在」用now_tz()、时间戳格式化用_local()(TZ=ZoneInfo(config.TZ_NAME)),本机与境外服务端结果一致。返回固定结构的 dict:date/weather/mao/usage/usage_gpt/btc/device(device={battery, charging, temp, humidity},由_device(now)经sensecraft_api拉取、文件缓存.device_cache.jsonTTL=DEVICE_TTL,服务端用纯拉取device_fetch()/占位device_empty())。日期时间用真实系统时间;橘喵今日经营由_mao()经jm_api、天气由_weather()经weather_api(和风,定位南京·江宁)、Claude 用量由_usage()经claude_usage、Codex 用量由_usage_gpt()经codex_usage,Claude 任何异常都回退占位"--",不回退 mock(Codex 无数据时回退 mock 并显式标「示例数据」)。改数据保持返回结构不变即可(renderer 依赖其 key:weather.icon为和风图标代码;mao.cols[]为{k, v, vp, hb, tb, wide}(vp为数值三段(前缀, 主数字, 后缀),金额为("¥", "2.14", "万")/不足万("¥", "2590", "")、单量前后缀为空,v是三段拼接的整串供网页版用;2026-09-14 用户要求 ¥ 与 万 字号小一点),五列由REALTIME_COLS(标签、接口字段前缀、格式化、是否仅宽版)驱动,_col_from()用.get()取today{Base}/{base}WowRate/{base}YoyRate,字段缺失(旧后端未上团购)该列占位--;wide=True(团购两列)小屏render()跳过、宽版全显;hb/tb为环比/同比(direction, text),direction ∈up/down/flat/None:flat为增长率恰等于 0,三角位画等号「=」(2026-09-14 用户要求,与用量行 pace 持平一致,此前 0 也画上三角)、None不画三角;接口文档见../docs/首页看板接口文档-20260911.md(毛利口径已含团购、金额与增长率为字符串、增长率可null);usage.bars[]为{k(短标签,小屏用), label(长标签 5h/7d/fable 7d,宽版用), pct(或 None), time_pct?, reset?, scoped}(宽版把 scoped 行并入紧邻其前的非 scoped 行作同一边框内的下半实心)、usage.warn/usage.ok/usage.pace_tol为用量与 pace 的三档阈值(小屏只用 warn)、usage.updated为快照采集时刻;usage_gpt与usage同构、标题「Codex Usage」、只有 5h/7d 两行(快照各窗口可带window真实秒数,pace 优先按它算),服务端未收到推送时为 mock(mock: True,88/22 覆盖红/绿两端)。注:_mao仍会拉mao.trend,但两个版面当前都不用它。宽版另有btc:_btc()经btc_api.get_candles(BTC_BAR, BTC_LIMIT)取 K 线(默认 1H×48),结构{symbol, bar, src, candles:[{t, o, h, l, c}], last, chg, chg_label},chg 时 K 取相对 24 根前收盘(24h)、日 K 取相对上一根;缓存.btc_cache.json(TTL=BTC_TTL默认 5 分钟,周期变更即失效,失败沿用旧缓存,仍无则 candles 为空 → 面板--)。- 缓存(为 launchd 每 5 分钟独立进程而设,必须落盘):天气
.weather_cache.json(TTL=WEATHER_TTL,默认 15min)、趋势.trend_cache.json(按日期 key,每天只拉一次)、BTC K 线.btc_cache.json(TTL=BTC_TTL,默认 5min);三者拉取失败沿用旧缓存。两路用量各有采集缓存.claude_usage_cache.json/.codex_usage_cache.json(usage_common.cached_snapshot:距上次成功 <TTL(.env的CLAUDE_USAGE_TTL/CODEX_USAGE_TTL)直接用缓存;失败沿用旧快照并退避 BACKOFF 秒;只存快照与时间戳、不存凭证)。以上缓存文件均已在.gitignore。 - 用量的 pace(时间维度对比):
usage_from_raw()每次按当前时间现算time_pct=(窗口已流逝/窗口长)、pace=pct−time_pct(>0 超前↑ / <0 节余↓);窗口空闲(pct=0 且resets_at为 null)记 time_pct=0;故即使utilization来自缓存/推送暂存,pace 仍随时钟准确推进。5h 窗口 18000s、7d 窗口 604800s(Codex 快照带window时用真实值)。
- 缓存(为 launchd 每 5 分钟独立进程而设,必须落盘):天气
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未配置时直接抛错→天气区显示"--"。sensecraft_api.py— SenseCraft HMI 设备遥测客户端:GET {SENSECRAFT_API_BASE}/api/v1/user/device/iot_data/{SENSECRAFT_DEVICE_EUI},头api-key(用户在 SenseCraft 后台生成,放.env,2026-09-14 该 key 曾出现在对话里,建议轮换),取result.battery.level/charging、result.sensor.temp/humidity;设备每dataaccess.interval=300s 上报一次,故DEVICE_TTL默认 300。直连不走代理、超时 10s、code!=200抛错 → 状态栏--。btc_api.py— 比特币 K 线客户端,免费公开接口、无需密钥,get_candles(bar, limit),周期代号BARS = {"1H", "1D"}映射到各源写法,逐源兜底SOURCES = (okx, coinbase, huobi, gateio, huobi_aws, binance_vision):OKX/api/v5/market/candles?instId=BTC-USDT&bar=(主源,本地直连可达且最新)、Coinbase/products/BTC-USD/candles?granularity=、Huobi/market/history/kline?period=(huobi_aws同接口换api-aws.huobi.pro)、Gate.io/api/v4/spot/candlesticks(升序 [ts, 成交额, close, high, low, open, …])、Binance 公开镜像data-api.binance.vision/api/v3/klines(升序 [openTime ms, o, h, l, c, …]);各源字段顺序不同(OKX/Huobi 最新在前,Coinbase 为 [time, low, high, open, close]),统一归一为升序{ts, o, h, l, c}。尝试顺序由config.BTC_SOURCES(_ordered_sources(),未列出的按默认顺序追加)决定:本机默认 okx 在前;境内 NGCQ 服务器 okx/coinbase/huobi 主域名不通,.env设gateio,huobi_aws,binance_vision。api.binance.com在本地区返回 restricted location,不列入。claude_usage.py— Claude 账户级用量采集(形态 A,跑在本机):复用 Claude Code 自身的 OAuth 登录态,直接 GEThttps://api.anthropic.com/api/oauth/usage(头Authorization: Bearer <accessToken>+anthropic-beta: oauth-2025-04-20)——与 Claude Code/usage、claude.ai 用量页同源,CLI/桌面 App/网页消耗都算,不依赖 Claude Code 进程在跑、不依赖 statusline 或任何插件(2026-09-14 起替代旧的「statusline 补丁落盘~/.claude/usage-snapshot.json、本项目读文件」方案,usage_local.py已删)。token 来源按序:环境变量CLAUDE_CODE_OAUTH_TOKEN→ macOS 钥匙串 generic password(serviceClaude Code-credentials,JSONclaudeAiOauth.accessToken/expiresAt(ms))→~/.claude/.credentials.json(尊重CLAUDE_CONFIG_DIR)。铁律:① 只读、绝不刷新 token(refresh token 每次使用即轮换,第三方刷新不写回会让 Claude Code 强制重登,多个官方 issue 证实);access token 约 8 小时有效,过期抛TokenUnavailable→ 沿用旧快照,等 Claude Code 下次启动自动刷新;② 有效期CLAUDE_USAGE_TTL内不再调、建议 ≥300 秒(突发调用 429 会封约 24 小时并连带 Claude Code/usage、claude.ai 一起看不到);③ 直连不走代理。normalize()优先解析limits[](kind=session/weekly_all/weekly_scoped,模型限定周额度按scope.model.display_name == CLAUDE_USAGE_SCOPE_MODEL「Fable」识别——2026 年中起顶层seven_day_opus/sonnet已置 null,只能这样取),顶层five_hour/seven_day兜底;输出与旧快照同构{five_hour, seven_day, seven_day_fable|None, updated_at, source}。get_usage(force=)经usage_common.cached_snapshot带缓存/最小间隔/退避,status∈ fetched/cache/stale/backoff。官方合规页禁止第三方「收集、存储、中介」claude.ai 凭证,未专门点名此端点,个人自用只读监控属灰色地带、无封号报告——所以凭证只留本机、服务端不碰。codex_usage.py— Codex 账户级用量采集(形态 A,跑在本机):经官方codex app-serverstdio JSON-RPC 读 ChatGPT 订阅的 Codex 额度池(CLI/IDE 扩展/Codex Web/ChatGPT 桌面 App 内 Codex 共用)。调研结论(2026-09-14):ChatGPT 订阅的普通聊天额度没有任何可编程的账户级读取途径(私有端点在 Sentinel/Cloudflare 门后、且只在被限流时才有值;官方明确聊天与 Codex 分开计量),故右侧块如实命名「Codex Usage」。协议(本机 codex-cli 0.154 验证):initialize→ 通知initialized→account/rateLimits/read(params{excludeResetCreditDetails:true}),响应rateLimits.primary/secondary{usedPercent, windowDurationMins, resetsAt(epoch 秒)}+planType+ 多桶rateLimitsByLimitId;未登录 ChatGPT 账号时 error「codex account authentication required to read rate limits」。窗口归类按windowDurationMins(≤12h → five_hour,≥5 天 → seven_day),Pro 计划常只回一个窗口、另一个 null,不能按 primary/secondary 位置判断;快照各窗口带window真实秒数供 pace 用。为什么不直调chatgpt.com/backend-api/wham/usage:token 刷新由 Codex CLI 代管(refresh token 轮换,外部自刷会踢掉 CLI 登录,官方 CI 文档也明示「不要自己调刷新接口」)。登录:2026-09-14 用户直接在默认~/.codex用 ChatGPT 账号codex login(auth_mode=chatgpt,原 API Key 仍保留在 auth.json;config.toml的第三方中转 base_url 只影响推理、不影响额度读取),故CODEX_USAGE_HOME默认留空跟随默认目录;若要隔离可设独立CODEX_HOME(目录不存在会自动建)并在其下codex login。CODEX_BIN由config._which_codex()解析为绝对路径(launchd 的 PATH 不含/opt/homebrew/bin,曾报「找不到 codex 可执行文件」进入退避;.env已写死/opt/homebrew/bin/codex)。子进程剥掉代理环境变量直连(chatgpt.com 本地直连可达)。真实数据(Plus 计划):rateLimits.primary只有周窗口(windowDurationMins=10080)、secondary=null,rateLimitsByLimitId仅一个桶codex——故 5h 行为--、7d 行有值;buckets原样附带,若将来出现第二个桶再决定是否加第三行。usage_common.py— 两个采集器共用的骨架cached_snapshot(path, ttl, backoff, fetch, force):距上次成功 <ttl 直接返回缓存(ttl 来自.env,与WEATHER_TTL/MAO_TTL/BTC_TTL同语义,0=每次实拉)(launchd 每 5 分钟先后起「小屏渲染」「上报」两个进程,只打一次接口);失败沿用旧快照并退避 backoff 秒(期内不再尝试);从未成功且失败则抛出。缓存文件.claude_usage_cache.json/.codex_usage_cache.json只存快照与时间戳,不存凭证。renderer.py—DashboardRenderer.render(data)返回 PIL'RGB'模式、仅黑/白/红三色图;组件层提供可覆写钩子/参数供宽版复用:_usage_colors()(用量行 填充/边框/文字 三色元组)、_pace_color()(pace 数值与三角颜色)、_usage_row(label_w=)、_weather_icon(wet=)(降水元素颜色)、_label(x0,x1)、_cmp_at()(环比/同比左对齐版,_cmp_line居中版基于它)、_cmp_color(direction, fg)(三角与数值颜色:三色版涨红,宽版覆写涨绿跌红,flat/None两版都黑;数值 F12 伪粗体、字号不变,占位--不加粗——加粗会粘成一条长横,2026-09-14)、_eq(cx, cy, w)(等号「=」,pace 持平与环比/同比flat共用)、_value(cx, y, col)(今日实时数值:主数字 F24 粗 + 前缀 ¥/后缀 万 F12 小号伪粗体,按数字墨迹底沿基线对齐、整体居中,两版共用);字体另有F36(3 倍整数缩放)。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+ 三列——过滤掉wide列,只显示探店单量/探店流水/毛利,版式对齐主分支):■ 橘喵·今日实时+上次更新,列内 名(F12)/值(_value: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— 直连局域网墨水屏设备(喵喵固件,开源:https://gitee.com/gxp666111/miaomiao,web/即设备自带管理页,可浏览器打开http://<EPD_HOST>/调试同一套接口)。push_image()先调GET /images拿画廊已存图片名单,逐个POST /delete_image?name=清空(单张删除失败不阻塞后续),再POST /upload(Content-Type: image/png,body 为原始 PNG 字节,非 multipart)触发设备转换+刷屏。清空画廊是刻意设计:设备把每次/upload都追加存一份到画廊(GET /images可见),不清理会越推越多、且设备当前current_mode为 slideshow 会轮播到旧图;先清后传使画廊恒为 1 张,等价于始终静态展示最新一张。清理非核心:拉取画廊列表本身失败也不阻塞后续/upload(跳过清理,直接推)。_request()对连接类异常(urllib.error.URLError,含 mDNS.local域名解析瞬时失败)内置重试(RETRY_TIMES=2,间隔RETRY_DELAY=2s)——mDNS 基于组播 UDP,偶发丢包很正常,单次失败不该让整轮推送直接跳过。注:历史上曾走 Zectrix 公网 API(POST /devices/{id}/display/image+ 设备 MAC +ZECTRIX_API_KEY),现统一为局域网直连。renderer_e1002.py— reTerminal E1002 目标:E1002Renderer(DashboardRenderer)以 k=1 复用组件(字号 12/24/48 与三色版一致),只重写版式与配色钩子(基类抽出类属性PCT_TRACK(小屏 F16 百分比的手动字距),宽版置 0——其 F16 映射到 12px、加粗后由_track()自动加 1px 字距;基类另抽FPACE让 pace 字体与标签 F13 解耦)。四段、三道横规,各段 y 由上一段实际底部推算:A 顶部 0–90 大时钟 F84 粗 3(按getbbox把墨迹顶沿放到 16、底沿 79,占满整段且保住原上/左/下边距),其右 14px 起一列三行左缘对齐:电池行、温度湿度行(_status_battery/_status_env)、星期/日期 F12(按日期数字底沿与时钟基线对齐;三行中心约 30.5/52.5/74.5,行距 22);天气右(上层 图标 ×1.25 | 温度 F48 顶沿齐时钟+「°」F24 上标;下层 一行 定位+城市·天气·温区 右对齐,均贴 x=790,见分支说明);B 用量_usage_block×2(左usage10–386|竖规 x=400|右usage_gpt414–790):_label标题 + 右侧「上次更新 HH:MM」(与今日实时同款文案)或 mock 的「示例数据」,行用基类紧凑_usage_row(行距 31,宽版把bar["label"]长标签代入k,label_w按本块最长标签自适应);C_realtime今日实时五列全宽(mao.cols全部,含wide列):列内四行ctext/_cmp_line逐行水平居中——名 F12 y0+21 / 值_valueF24 粗+小号 ¥/万 y0+38 / 环比 y0+72 / 同比 y0+88(按墨迹框:标题↔名 ~9px——2026-09-14 整列下移 4px,原 ~5px 用户觉得挤;名↔值 ~9px、值↔环比 ~6px),竖规 y0+26~102,区块底 y0+104;D_btc_chartBTC K 线(data.btc,标题按bar显示「时K/日K」:右侧最新价 F12 粗 + 口径chg_label(24h/1d)+ 涨跌三角 + 涨跌幅(F12 粗,2026-09-14 加粗),涨绿跌红;每根蜡烛 1px 影线 + 实心实体(宽≈槽位 60% 取奇数)、收≥开 绿 否则 红;右侧 66px 标签列放最高/最低/最新价,最新价横贯一道点规、标签避让顶底;底部基线 + 首/中/末时间标签t;无数据居中--)。用量块_usage_block返回末条进度条底部,B/C 分割线取其 +16px(用户反馈太挤)。配色钩子覆写:_usage_colors三档(≥USAGE_WARN红|≤USAGE_OK绿|其间黑;填充、边框、百分比文字三者同色)、_pace_color三档(≥+PACE_TOL红|≤−PACE_TOL绿|其间黑),阈值由usage.ok/usage.pace_tol(来自 config)在_usage_block内注入。天气图标wet=BLUE让雨滴/雪花着蓝。可选六色测试色条_color_strip(E1002_COLOR_STRIP=1打开,默认关)。write_page()写出固定 800×480 的index.html(零边距、image-rendering: pixelated、PNG 带时间戳参数防缓存)。为何固定尺寸而非自适应:云端截图视口/DPR 不可控,自适应会重排+抗锯齿产生中间色再被平台抖动;固定页里的 1:1 PNG 每个像素本就是六色之一,截图零损失——已用无头 Chrome--window-size=800,480截图验证与 PNG 逐像素一致。设备端 HTML 控件须铺满整个画布。历史(均已弃用):k=2 整体放大版;「左用量/右今日实时/下 30 天折线」版;「用量大卡片撑满」版。server.py— E1002 动态服务(stdlibThreadingHTTPServer,无第三方依赖)。build_data()→(data, 各路状态):日期按config.TZ_NAME时区现算;四路上游(天气/橘喵/BTC/设备遥测)经_cached(key, ttl, fetch)——STATE.last_good[key]未过期(time - STATE.fetched_at[key] < ttl)直接用;过期的并行提交到线程池_POOL(_refresh跑data.weather_fetch()/data.mao_fetch(now, with_trend=False)/data.btc_fetch(),成功更新数据与时间戳),build_data用concurrent.futures.wait整体最多等FETCH_BUDGET秒(默认 5,.env可调),再由_settle结算:完成→fetched(0.27s)(带耗时)/失败沿用旧值stale/从未成功占位none;未完成→slow→stale(用上一次成功值)或slow→none(占位),拉取线程继续跑完写入缓存供下次请求(stale-while-revalidate),STATE.inflight[key]保证同一路进行中不重复实拉。响应上限 ≈ 预算 + 渲染,任一路上游卡死(socket 超时和风/橘喵 30s、BTC 8s)不再拖垮整图。2026-09-14 前为串行实拉(三路全拉约 1.1~2.6s,单路卡住即整体超时);改并行后三路全拉约 0.75s(取最慢一路)。日志每行末尾[weather:cache(12s) mao:fetched(0.06s) btc:slow→stale]可直接看命中/耗时/超预算情况,/health有cache_age_s/ttl_s/fetch_budget_s/inflight;用量取STATE.raw(推送暂存)→data._usage(now, raw=…)/data._usage_gpt(now, raw=…),未推送时 Claude 全--、ChatGPT 用 mock。端点:GET IMAGE_PATH→render_png_bytes()(_RENDER_LOCK串行化,Pillow 渲染器非线程安全);三个渲染端点在IMAGE_TOKEN非空时必须带?token=<IMAGE_TOKEN>(_page_authed,hmac.compare_digest),否则返回与未知路径完全相同的 404、日志标bad page token(2026-09-18 用户要求,防爬虫扫到固定路径);/health不需要令牌;GET HTML_PATH→html_renderer.render_html();GET HTML_PNG_PATH→html_shot.screenshot_html()+quantize6()(?raw=1跳过量化);GET /healthJSON;POST /push/usage|/push/usage_gpt(body=原始快照 JSON,≤64KB,头X-Push-Token==PUSH_TOKEN,为空拒绝);GET /push/*查看暂存(同令牌)。所有响应Cache-Control: no-store;每请求打一行日志(含CF-Connecting-IP)。PUSH_STATE_PATH非空时推送数据落盘(原子替换),重启恢复;图片永不落盘。实测:三路全实拉约 1.1~1.3s(天气 0.28s、橘喵 0.06s、OKX 0.7s 串行),全部命中缓存约 0.02s(渲染 20ms + 编码 14ms)。html_shot.py— 方案三:screenshot_html(html)用无头 Chrome(find_chrome()自动探测或CHROME_BIN;Linux root 需CHROME_EXTRA_ARGS=--no-sandbox)把 HTML 截成 800×480,临时目录用后即删,_SHOT_LOCK串行化;切勿传--user-data-dir指向全新目录——新版--headless=new对空 profile 首启会卡死到超时(实测 A/B/D/E 全超时、只有不带它的 C 2 秒完成)。quantize6(img):每通道二值化(阈值HTML_PNG_THRESHOLD默认 160,偏向保墨迹,细笔画浅灰边归黑而非丢白)→ Pillow 调色板quantize(dither=NONE)映射到SPECTRA6,输出保证只含六色。服务端?raw=1返回未量化截图供对照。实测单次约 3.3s(Chrome 启动为主)。html_renderer.py— 方案二网页版:纯 HTML+CSS+内联 SVG、无 JS,数据服务端写死;字体栈 PingFang/微软雅黑/Noto Sans SC + Google Fonts 兜底(平台截图机若无中文字体会掉字,能否加载取决于其出网能力);颜色只用六色纯值;配色规则与 PNG 版一致(用量/pace 三档、涨红跌黑、K 线涨绿跌红),版式同构。K 线标题右侧价格用<strong>(h3 b::before的方块装饰只给标题)。push_client.py— 本机推送端:SOURCES两路——claude_usage.get_usage()→POST {PUSH_URL}/push/usage、codex_usage.get_usage()→POST {PUSH_URL}/push/usage_gpt(推前去掉status键);两路彼此独立,一路失败只打日志、两路都失败才非零退出;直连不走代理、连接类异常重试 2 次、4xx/5xx 直接报错。probe_server.py— 验证工具(不参与正常流程):极简 HTTP 服务,/probe.png每--every秒重绘一张带大号时刻+序号的 800×480 探针图,响应Cache-Control: no-store,每次请求打印时间/UA/来源 IP(含CF-Connecting-IP)。用途:配合cloudflared tunnel --url http://localhost:8787暴露成公网 HTTPS,填入 SenseCraft 的 Image 控件,凭访问日志判断平台对同一 URL 是否每个刷新周期重抓(成立则可直接覆盖同一 URL 的图片,省掉 HTML 页那一层)。结论(已验证):Image 控件不重抓、HTML 控件重抓,见分支说明。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"保持纯色。 -
DashboardRenderer(k=…)缩放系数:所有组件(_label/_cmp_line/_usage_row/_pace/tri等)的像素常量与伪粗体偏移都乘k,字体按12*k/24*k/48*k创建;k=1为 400×300 原版(改组件后务必用固定夹具比对k=1输出逐像素不变),E1002 版当前用k=1(k=2 整体放大在 800×480 上字过大,已否决),k 参数保留供更高分辩率设备使用。字体属性名(F12/F24/F48)表达层级而非绝对像素。 -
字体固定用 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 水平偏移叠绘笔画(点阵字体只有单一字重)。12px 加粗自动加 1px 字距(_track(),2026-09-14):12px 字形之间只有 1 列空隙,伪粗体右偏 1px 正好填满、相邻字粘连(13/24px 空隙 2 列不受影响);text/rtext/ctext/_mid内部都走它,宽版映射到 12px 的 F13/F16/FPACE 同样命中。手动推进 x 或居中时用_tw_b(s, ft, bold)取含粗体偏移与字距的实际占宽,别再手写_tw + bold。 -
推送时
DITHER = False(硬阈值),纯黑白图最锐利,勿改成true。 -
坐标、字号均为像素网格上的硬编码常量;调布局时注意各区分隔线
hdash的 y 值与下方组件位置联动。
注意
- 所有凭证均在
.env(不提交);config.py不再内置明文 fallback。分享代码时给出.env.example即可,真实.env切勿入库。 .gitignore已忽略:output/、__pycache__/、*.pyc、缓存文件.weather_cache.json/.trend_cache.json/.btc_cache.json/.device_cache.json/.claude_usage_cache.json/.codex_usage_cache.json、本机专用的run.sh、含凭证的.env(模板.env.example入库)。改缓存逻辑后想强制重拉,删掉对应缓存文件即可。
验证与运维手法(会话沉淀)
- 像素回归:改
renderer.py组件后,用固定夹具比对 HEAD 版与工作区版DashboardRenderer的 k=1 输出逐像素相同 (git show HEAD:renderer.py > /tmp/orig/renderer.py,切换sys.path分别 import 渲染,ImageChops.difference(a,b).getbbox() is None); 六色版渲染后set(img.getcolors())必须 ⊆SPECTRA6。夹具需覆盖:pct=None、≥warn 红档、≤ok 绿档、direction=None、空 candles。 - HTML→截图逐像素核验:
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" --headless=new --disable-gpu --hide-scrollbars --force-device-scale-factor=1 --window-size=800,480 --screenshot=/tmp/s.png file://…;勿加指向空目录的--user-data-dir(卡死到超时)。 - server.py 生产部署:NGCQ 服务器(2026-09-18 起),SSH 别名
njcq(Ubuntu 22.04,腾讯云境内,用户ubuntu免密 sudo)。公网地址https://eink.njcqit.com(SenseCraft HTML 控件填https://eink.njcqit.com/e1002.png?token=<IMAGE_TOKEN>,令牌见两端.env的IMAGE_TOKEN(本机与服务器一致);本机.env的PUSH_URL=https://eink.njcqit.com)。组成:- 代码在
/home/ubuntu/eink-push(rsyncgit ls-files的 24 个文件 +.env0600,不含 output/缓存;.env由本机.env派生:SERVER_HOST=127.0.0.1、PUSH_STATE_PATH=/home/ubuntu/eink-push/output/push_state.json、PUSH_URL留空、EPD_HOST/CODEX_*留空、另加BTC_SOURCES=gateio,huobi_aws,binance_vision)。系统/usr/bin/python33.10 + 用户级 Pillow 12.2(~/.local,无 python3-venv 包故不建 venv)。 - 进程:systemd
eink-server.service(/etc/systemd/system/,User=ubuntu、Restart=always、日志追加output/server.log)。改代码后:本机 rsync 同上 →ssh njcq sudo systemctl restart eink-server;查看ssh njcq 'systemctl status eink-server; tail output/server.log; curl -s 127.0.0.1:8790/health'。 - 反代与证书走 1Panel(v2.2.4,OpenResty 容器 host 网络):站点
eink.njcqit.com(id 10,类型 proxy →http://127.0.0.1:8790,默认分组)+ Let's Encrypt 证书(ssl id 11,EC256,dnsAccount腾讯云 DNS 账号 id 1 + acme 账号 id 1,自动续期,与其它 njcqit.com 站点同一套);HTTP 301 到 HTTPS、HSTS。生成的配置:/opt/1panel/www/conf.d/eink.njcqit.com.conf+/opt/1panel/www/sites/eink.njcqit.com/proxy/root.conf。建站时用的是 1Panel API(面板已开 API、白名单 127.0.0.1,浏览器扩展当时未连接):服务器上留有辅助脚本~/.1p_api.sh METHOD /api/v2/path [json](运行时从/opt/1panel/db/core.db取 ApiKey 算1Panel-Token=md5("1panel"+key+ts),不落盘不回显)。坑:v2.2.4 的建站请求体与 dev 分支 swagger 不同——用domains:[{domain,port,ssl}]而非primaryDomain(发primaryDomain直接 500 空响应),可enableSSL:true, websiteSSLID一步挂证书;结构体以raw.githubusercontent.com/1Panel-dev/1Panel/v2.2.4/agent/app/dto/request/website.go为准。建站后配置未即时生效(拿到面板默认证书、HTTP 404),docker exec 1Panel-openresty-wYew nginx -t通过后几秒内自行生效。 - 境内上游可达性:JM 后端、和风、SenseCraft 都通;OKX/Coinbase/Huobi 主域名全部不通且各超时 4~8s(合计超
FETCH_BUDGET,K 线永远拿不到),故btc_api.py新增gateio/huobi_aws(api-aws.huobi.pro)/binance_vision(data-api.binance.vision)三源并加BTC_SOURCES顺序配置(config.py);本机默认顺序不变。 - 该服务器原有 2026-06 部署的 旧 main 分支 Zectrix 版(每分钟 cron 推 Zectrix 设备 44:1B:F6:89:C7:78),已备份为
~/eink-push.legacy-zectrix、cron 行注释保留(crontab -l可见),本分支无 Zectrix 推送。 - 本机不再跑 server/cloudflared:
com.jm.eink-server/com.jm.eink-cloudflared两个 LaunchAgent 已 bootout 并删除 plist;只保留com.jm.eink-push(小屏推送 + 用量上报)。历史坑(cloudflared 快速隧道会被 Cloudflare 静默回收、SIGTERM 退出慢、需--protocol http2)已不再相关。
- 代码在
- shell 坑:本机是 fish——
env -u …不能存进变量再当命令执行;含中文的 Python heredoc 用python3 -X utf8 - <<'EOF',否则 stdin 解码报错;gh未登录且 GitHub API 匿名限流,取源码走curl raw.githubusercontent.com。 - 用量采集验证:
python3 claude_usage.py --force应打出status: fetched且seven_day_fable非空;紧接着不带--force再跑应为cache;缓存文件里不得出现 token。python3 codex_usage.py --force未登录时报「authentication required…请先执行 CODEX_HOME=… codex login」,登录后应有five_hour/seven_day。历史:statusline 补丁(~/.claude/statusline.sh)仍在本机产出~/.claude/usage-snapshot.json,但本项目已不读它。 - 定时任务:
run.sh末尾已追加main.py --target push(vfox python 绝对路径,|| true),Claude/Codex 两路账户级用量随喵喵推送每 5 分钟采集并同步到 server.py;推送暂存落盘output/push_state.json。 - 上游配额:server 三路上游已有 TTL 内存缓存(天气默认 15 分钟 → 和风约 192 次/天);改有效期只动
.env,不动代码。本机.env当前MAO_TTL=0/BTC_TTL=0(每次请求实拉)。 - 「平台抓取失败」排查顺序:①
ssh njcq 'systemctl is-active eink-server; curl -s -o /dev/null -w "%{http_code}" https://eink.njcqit.com/health'服务与反代是否活着(1Panel 站点/证书状态在面板「网站」里看);② 服务器output/server.log该时刻有无该请求(平台 IP 13.91.x.x,每 ~5m45s 一次)——没有记录说明请求根本没到我们这里,问题在平台/Nginx;③ 有记录看末尾状态串与耗时:slow→表示某路超FETCH_BUDGET、本次用了旧值(境内 BTC 源见上)。2026-09-14 一次怀疑「多路 TTL 同时到期导致超时」,核查日志:所有平台请求均 200、最慢 4.23s 且该次天气还是缓存,三路同时实拉的两次仅 1.2~1.5s——猜想不成立(MAO/BTC TTL=0 本就每次同时实拉);真正的隐患是当时串行拉取 + 30s socket 超时,已改并行 + 预算。 - SenseCraft 未公开后端(从网页前端 bundle 提取,
https://sensecraft-hmi-api.seeed.cc,头Authorization: <token>,token 在 localStorageuser-storage):POST /api/v1/oss/file/upload(multipartfile+type=image→file_url)→POST /api/v2/user/page({pages:[{name,type:img|url|layout,data,dither,thumbnail,resolution:"800x480"}]}) →POST /api/v2/user/playlist/upsert_pages→POST /api/v2/user/device/down_link({mac_address,playlist_id,type,refresh_interval?,deep_sleep_enabled?}); 另有POST /api/v2/user/device/config、POST /render/preview({url,img_format,resolution,dither})。未采用(未公开、可随时变更),仅作备选记录。