#!/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 E1002(800x480,E 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 秒 # 真机验证结论: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")) 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、近 72 小时(3 天); # 每 5 分钟刷新(与 launchd 节律一致,最新一根随实时价推进)。拉取失败沿用旧缓存。缓存文件已在 .gitignore。 BTC_BAR = os.environ.get("BTC_BAR", "1H") # 周期:"1H" 时K / "1D" 日K BTC_LIMIT = int(os.environ.get("BTC_LIMIT", "72")) # 展示最近 N 根(时 K 默认 72 = 近 3 天) BTC_TTL = int(os.environ.get("BTC_TTL", "300")) # 秒,默认 5 分钟(本机文件缓存与 server.py 内存缓存共用) BTC_CACHE_PATH = os.path.join(BASE_DIR, ".btc_cache.json") # ---- Claude Code 用量(Claude Usage 模块)数据源 ---- # 只读本地、**不发任何网络请求**(详见 usage_local.py)——生产者/消费者模式: # 生产者 = 打过补丁的 claude-statusline,把每次从 Claude Code stdin 拿到的**权威实时**额度 # (five_hour/seven_day 的 utilization + resets_at)落盘到 ~/.claude/usage-snapshot.json;本项目读它。 # 活跃使用时该快照几乎持续刷新、准确;空闲时停在最后一次。文件缺失时用量区显示 "--"。 USAGE_LOCAL_PATH = os.environ.get("USAGE_LOCAL_PATH", os.path.expanduser("~/.claude/usage-snapshot.json")) USAGE_WARN = int(os.environ.get("USAGE_WARN", "80")) # 用量 ≥ 此百分比 → 进度条与百分比转红预警(两个版面统一) # 以下两项仅六色宽版(E1002)使用,三色小屏只有 黑/红 两档: USAGE_OK = int(os.environ.get("USAGE_OK", "30")) # 用量 ≤ 此百分比 → 进度条(含边框)绿色「健康」;介于 OK~WARN 之间为黑 PACE_TOL = int(os.environ.get("PACE_TOL", "10")) # pace=用量%−时间%:≥ +TOL 红(超前过多)/ ≤ −TOL 绿(节余充足)/ 其间黑