Files
eink-push/config.py
YANG JIANKUAN 04fa31cb94 feat: server.py 迁移部署到 NGCQ(1Panel Nginx 反代 eink.njcqit.com)+境内可达 BTC 行情源+页面访问令牌
- 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>
2026-09-18 11:42:23 +08:00

160 lines
13 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""全局配置。敏感项从同目录 .env 读取,真实环境变量(如 cron 注入)优先。"""
import os
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
def _load_dotenv(path):
"""极简零依赖 .env 加载器:把 KEY=VALUE 写入 os.environ。
已存在的真实环境变量优先(不覆盖),故 cron/命令行注入仍可压过 .env。
支持 # 注释、空行、行内 KEY=VALUE值两侧成对的单/双引号会被去掉。"""
try:
with open(path, "r", encoding="utf-8") as fp:
for raw in fp:
line = raw.strip()
if not line or line.startswith("#") or "=" not in line:
continue
key, _, val = line.partition("=")
key, val = key.strip(), val.strip()
if len(val) >= 2 and val[0] == val[-1] and val[0] in "\"'":
val = val[1:-1]
if key:
os.environ.setdefault(key, val)
except FileNotFoundError:
pass
_load_dotenv(os.path.join(BASE_DIR, ".env"))
# ---- 局域网墨水屏设备喵喵固件开源https://gitee.com/gxp666111/miaomiao----
# 不再走 Zectrix 公网 API + 设备 MAC改为直连局域网设备 mDNS host 的 HTTP 接口。
EPD_HOST = os.environ.get("EPD_HOST", "epd400a44.local")
# ---- 时区 ----
# 服务端可能部署在境外 VPS所有「现在时刻」与时间戳格式化统一按此时区data.TZ本机与服务端结果一致。
TZ_NAME = os.environ.get("TZ_NAME", "Asia/Shanghai")
# ---- 画布(喵喵固件 400x300 三色屏)----
WIDTH, HEIGHT = 400, 300
# ---- reTerminal E1002800x480E Ink Spectra 6 六色;经 SenseCraft HMI 的 HTML 控件云端抓取)----
# 产物:固定 800x480 的 index.html + 同目录 dashboard.png需部署到公网 HTTPS 地址供平台每周期重新拉取。
E1002_WIDTH, E1002_HEIGHT = 800, 480
E1002_COLOR_STRIP = os.environ.get("E1002_COLOR_STRIP", "0") == "1" # 底部六色测试色条(真机校准用,默认关)
# ---- 图片服务server.py恒定 URL每次请求内存中现渲染 800x480 PNG供 SenseCraft HTML 控件周期抓取)----
# 服务端三路上游的内存缓存有效期(秒):收到请求先查缓存是否过期,过期才实拉并更新时间戳,否则直接用缓存渲染;
# 拉取失败沿用旧值且不更新时间戳(下次请求再试)。设 0 = 每次请求都实拉。天气/BTC 复用上面的 WEATHER_TTL/BTC_TTL。
MAO_TTL = int(os.environ.get("MAO_TTL", "60")) # 橘喵今日实时,默认 60 秒
# server.py 一次请求里等待过期上游并行实拉的总预算(秒):超预算的那路本次用上一次成功值,后台继续拉完更新缓存。
# 上游 socket 超时各 30s和风/橘喵、8sBTC若不设预算、任一路卡住会拖到平台抓取超时预算 ≥ 正常三路并行耗时(~1s即可。
FETCH_BUDGET = float(os.environ.get("FETCH_BUDGET", "5"))
# 真机验证结论Image 控件只在填写 URL 时抓一次快照、此后不再更新HTML 控件填同一图片 URL 则每周期重抓。
SERVER_HOST = os.environ.get("SERVER_HOST", "0.0.0.0")
SERVER_PORT = int(os.environ.get("SERVER_PORT", "8790"))
IMAGE_PATH = os.environ.get("IMAGE_PATH", "/e1002.png") # 方案一:恒定图片路径(每次请求内存渲染 PNG可改成难猜的路径防扫描
HTML_PATH = os.environ.get("HTML_PATH", "/e1002.html") # 方案二:恒定网页路径(每次请求生成 800x480 现代网页,平台截图)
# 方案三:服务端用无头 Chrome 把方案二的网页截成 800x480再按「最近色、不抖动」固化为六色 PNG需服务器装 Chrome/Chromium
HTML_PNG_PATH = os.environ.get("HTML_PNG_PATH", "/e1002-web.png")
CHROME_BIN = os.environ.get("CHROME_BIN", "") # 为空则按常见路径自动探测macOS Google Chrome / google-chrome / chromium
CHROME_EXTRA_ARGS = os.environ.get("CHROME_EXTRA_ARGS", "") # 例如 Linux 以 root 运行需 "--no-sandbox"
# 每通道二值化阈值0~255低于阈值归 0、否则归 255取 160而非 128偏向保留墨迹细笔画的浅灰边不至于被丢成白
HTML_PNG_THRESHOLD = int(os.environ.get("HTML_PNG_THRESHOLD", "160"))
# 页面访问令牌:非空时 IMAGE_PATH/HTML_PATH/HTML_PNG_PATH 必须带 ?token=<IMAGE_TOKEN>,否则一律 404对扫描器等同不存在
# 服务在公网且路径固定SenseCraft 里填 https://<域名>/e1002.png?token=<IMAGE_TOKEN>2026-09-18 用户要求,防爬虫扫到)
IMAGE_TOKEN = os.environ.get("IMAGE_TOKEN", "")
PUSH_TOKEN = os.environ.get("PUSH_TOKEN", "") # 推送鉴权令牌X-Push-Token 头);为空则拒绝一切推送
PUSH_STATE_PATH = os.environ.get("PUSH_STATE_PATH", "") # 推送数据落盘路径(重启后恢复);为空=仅内存
# 本机推送端main.py --target push服务地址含协议与端口不含路径令牌同 PUSH_TOKEN
PUSH_URL = os.environ.get("PUSH_URL", "")
# ---- 资源路径 ----
FONT_PATH = os.path.join(BASE_DIR, "fonts", "fusion-pixel-12px-proportional-zh_hans.otf")
OUTPUT_DIR = os.path.join(BASE_DIR, "output")
OUTPUT_PATH = os.path.join(OUTPUT_DIR, "dashboard.png")
E1002_OUTPUT_DIR = os.path.join(OUTPUT_DIR, "e1002")
E1002_PNG_PATH = os.path.join(E1002_OUTPUT_DIR, "dashboard.png")
E1002_HTML_PATH = os.path.join(E1002_OUTPUT_DIR, "index.html")
# ---- 橘喵 jm-devops 后端(今日经营 / 近30天趋势 数据源)----
# 鉴权方式设备密钥授权grant_type=client_secret免验证码
# 用 CLIENT_ID + CLIENT_SECRET 调 /auth/login 换 token再带 Bearer 调统计接口(详见 jm_api.py
# CLIENT_SECRET 等价于长期凭证,务必走 HTTPS、优先用环境变量覆盖、可在后端 sys_client 轮换。
BASE_API = os.environ.get("JM_BASE_API", "https://admin.jumiaotandian.com/prod-api")
CLIENT_ID = os.environ.get("JM_CLIENT_ID", "")
CLIENT_SECRET = os.environ.get("JM_CLIENT_SECRET", "")
GRANT_TYPE = os.environ.get("JM_GRANT_TYPE", "client_secret")
TENANT_ID = os.environ.get("JM_TENANT_ID", "000000")
# ---- 和风天气QWeather数据源 ----
# 专属 API Host 与 Key 在 https://console.qweather.com/setting 查看,须用环境变量配置;
# 未配置时天气区显示占位 "--",不影响其余渲染与推送。
QWEATHER_HOST = os.environ.get("QWEATHER_HOST", "") # 用户专属 API Host
QWEATHER_KEY = os.environ.get("QWEATHER_KEY", "")
# 定位固定为南京·江宁区。和风经纬度格式为「经度,纬度」
WEATHER_LOCATION = os.environ.get("WEATHER_LOCATION", "118.840,31.953")
WEATHER_LOC_NAME = "南京·江宁"
# 天气缓存:业务数据每分钟刷新,但天气 15 分钟才重新拉一次cron 每次独立进程,缓存须落盘)。
WEATHER_TTL = int(os.environ.get("WEATHER_TTL", "900")) # 秒,默认 900=15 分钟(本机文件缓存与 server.py 内存缓存共用)
WEATHER_CACHE_PATH = os.path.join(BASE_DIR, ".weather_cache.json")
# 近30天趋势缓存recent-days 每天只更新一次,按日期为 key 缓存,当天命中即不再请求。
TREND_CACHE_PATH = os.path.join(BASE_DIR, ".trend_cache.json")
# ---- 比特币 K 线(仅 800x480 宽版底部面板)----
# 免费公开接口无需密钥逐源兜底OKX → Coinbase → Huobi见 btc_api.py。当前为时 K、近 48 小时2 天);
# 每 5 分钟刷新(与 launchd 节律一致,最新一根随实时价推进)。拉取失败沿用旧缓存。缓存文件已在 .gitignore。
BTC_BAR = os.environ.get("BTC_BAR", "1H") # 周期:"1H" 时K / "1D" 日K
BTC_LIMIT = int(os.environ.get("BTC_LIMIT", "48")) # 展示最近 N 根(时 K 默认 48 = 近 2 天2026-09-14 由 72 改 48
BTC_TTL = int(os.environ.get("BTC_TTL", "300")) # 秒,默认 5 分钟(本机文件缓存与 server.py 内存缓存共用)
# 行情源尝试顺序(逗号分隔,见 btc_api.SOURCES未列出的按默认顺序追加。境内服务器 okx/coinbase/huobi 主域名不通且各超时数秒,
# 部署 NGCQ 时设 BTC_SOURCES=gateio,huobi_aws,binance_vision否则每次请求都超 FETCH_BUDGET 拿不到 K 线。
BTC_SOURCES = os.environ.get("BTC_SOURCES", "okx,coinbase,huobi,gateio,huobi_aws,binance_vision")
BTC_CACHE_PATH = os.path.join(BASE_DIR, ".btc_cache.json")
# ---- reTerminal E1002 设备遥测SenseCraft HMI电量/温度/湿度,宽版顶部状态栏)----
# api-key 由用户在 SenseCraft 后台生成,放 .env设备每 300s 上报一次DEVICE_TTL 与之同步0=每次实拉)。
SENSECRAFT_API_BASE = os.environ.get("SENSECRAFT_API_BASE", "https://sensecraft-hmi-api.seeed.cc")
SENSECRAFT_API_KEY = os.environ.get("SENSECRAFT_API_KEY", "")
SENSECRAFT_DEVICE_EUI = os.environ.get("SENSECRAFT_DEVICE_EUI", "")
DEVICE_TTL = int(os.environ.get("DEVICE_TTL", "300"))
DEVICE_CACHE_PATH = os.path.join(BASE_DIR, ".device_cache.json")
# ---- AI 用量数据源Claude Usage / Codex Usage 两个模块)----
# 形态 A两路都在**本机**采集(需本机的登录态),由 main.py --target push 上报给 server.py服务端不持任何凭证、不读本机文件。
# Claudeclaude_usage.py复用 Claude Code 自身的 OAuth 登录态macOS 钥匙串「Claude Code-credentials」只读、绝不刷新
# 直接调 https://api.anthropic.com/api/oauth/usage——与 Claude Code `/usage` 同源、**账户级**CLI/桌面/网页消耗都算),
# 不依赖 Claude Code 进程在跑、不依赖 statusline 插件。
CLAUDE_USAGE_TTL = int(os.environ.get("CLAUDE_USAGE_TTL", "300")) # 秒,快照有效期(同 WEATHER_TTL 语义0=每次实拉);社区一致建议 ≥300突发调用 429 会封约 24 小时)
CLAUDE_USAGE_BACKOFF = int(os.environ.get("CLAUDE_USAGE_BACKOFF", "1800")) # 秒;拉取失败后的退避期,期内沿用旧快照
CLAUDE_USAGE_SCOPE_MODEL = os.environ.get("CLAUDE_USAGE_SCOPE_MODEL", "Fable") # limits[] 里模型限定周额度按 display_name 识别
CLAUDE_USAGE_SCOPE_FALLBACK_KEY = os.environ.get("CLAUDE_USAGE_SCOPE_FALLBACK_KEY", "seven_day_opus") # 旧版顶层键兜底
CLAUDE_USAGE_UA = os.environ.get("CLAUDE_USAGE_UA", "claude-code/2.1.34")
CLAUDE_USAGE_CACHE_PATH = os.path.join(BASE_DIR, ".claude_usage_cache.json") # 只存快照与时间戳,不存凭证
# Codexcodex_usage.py经官方 `codex app-server` JSON-RPC 读 ChatGPT 订阅的 Codex 额度池CLI/IDE/Web/桌面 App 内 Codex 共用);
# token 刷新由 Codex CLI 代管。需 Codex 以 ChatGPT 账号登录(`codex login`CODEX_USAGE_HOME 留空用默认 ~/.codex
# 也可指向独立 CODEX_HOME 与日常配置隔离。
def _which_codex():
"""launchd 不继承登录 shell 的 PATH无 /opt/homebrew/bin故按 PATH → Homebrew/常见位置解析出绝对路径。"""
import shutil
env = os.environ.get("CODEX_BIN", "")
if env:
return os.path.expanduser(env)
for cand in (shutil.which("codex"), "/opt/homebrew/bin/codex", "/usr/local/bin/codex", os.path.expanduser("~/.npm-global/bin/codex")):
if cand and os.path.exists(cand):
return cand
return "codex"
CODEX_BIN = _which_codex()
# 留空 = 用 Codex 默认目录 ~/.codex2026-09-14 用户直接在默认目录用 ChatGPT 账号登录,故默认跟随之);
# 若日常 Codex 需保持 API Key/中转登录而另开一份 ChatGPT 登录,可设为独立目录(如 ~/.codex-usage并在其下 `CODEX_HOME=… codex login`。
CODEX_USAGE_HOME = os.environ.get("CODEX_USAGE_HOME", "")
CODEX_USAGE_TTL = int(os.environ.get("CODEX_USAGE_TTL", "300")) # 秒快照有效期0=每次实拉)
CODEX_USAGE_BACKOFF = int(os.environ.get("CODEX_USAGE_BACKOFF", "900"))
CODEX_USAGE_CACHE_PATH = os.path.join(BASE_DIR, ".codex_usage_cache.json")
USAGE_WARN = int(os.environ.get("USAGE_WARN", "80")) # 用量 ≥ 此百分比 → 进度条与百分比转红预警(两个版面统一)
# 以下两项仅六色宽版E1002使用三色小屏只有 黑/红 两档。用量与 pace 各自三档、独立取色,颜色顺序固定为 绿 → 黑 → 红
# 黄不用2026-09-14 曾按要求加过 60%80% 黄档,随即被否——黄小字压白底发虚、且四档在三行里过于花):
USAGE_OK = int(os.environ.get("USAGE_OK", "30")) # 用量 ≤ OK 绿「健康」OK~WARN 黑「正常」|≥ WARN 红「危险」;进度条、边框与百分比数字同色
PACE_TOL = int(os.environ.get("PACE_TOL", "10")) # pace=用量%−时间%:≤ TOL 绿「节余」±TOL 内黑「持平」|≥ +TOL 红「偏快」