From b32007483ae796295ddf8c7094768d04840768ab Mon Sep 17 00:00:00 2001 From: YANG JIANKUAN Date: Tue, 7 Jul 2026 10:55:15 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E6=8E=A8=E9=80=81=E7=BD=91=E7=BB=9C?= =?UTF-8?q?=E8=AF=B7=E6=B1=82=E5=8A=A0=E9=87=8D=E8=AF=95=EF=BC=8C=E9=81=BF?= =?UTF-8?q?=E5=85=8D=E7=9E=AC=E6=97=B6=E5=A4=B1=E8=B4=A5=E6=BC=8F=E6=8E=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit mDNS(.local)域名解析基于组播UDP,偶发丢包会导致整轮推送直接抛异常跳过。 _request() 对连接类异常加重试(2次/间隔2s);画廊清理列表拉取失败也不再 阻塞后续 /upload,清理是锦上添花而非推送必要条件。 Co-Authored-By: Claude Opus 4.6 --- CLAUDE.md | 2 +- pusher.py | 30 +++++++++++++++++++++++------- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 1412b1e..b377540 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -47,7 +47,7 @@ EPD_HOST=epd400a44.local python3 main.py - **红色点缀系统**(改配色务必克制):模块顶部 `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:///` 调试同一套接口)。`push_image()` 先调 `GET /images` 拿画廊已存图片名单,逐个 `POST /delete_image?name=`清空(单张删除失败不阻塞后续),再 `POST /upload`(`Content-Type: image/png`,body 为原始 PNG 字节,非 multipart)触发设备转换+刷屏。**清空画廊是刻意设计**:设备把每次 `/upload` 都追加存一份到画廊(`GET /images` 可见),不清理会越推越多、且设备当前 `current_mode` 为 slideshow 会轮播到旧图;先清后传使画廊恒为 1 张,等价于始终静态展示最新一张。注:历史上曾走 Zectrix 公网 API(`POST /devices/{id}/display/image` + 设备 MAC + `ZECTRIX_API_KEY`),现统一为局域网直连。 +- **`pusher.py`** — 直连局域网墨水屏设备(喵喵固件,开源:https://gitee.com/gxp666111/miaomiao,`web/` 即设备自带管理页,可浏览器打开 `http:///` 调试同一套接口)。`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`),现统一为局域网直连。 - **`config.py`** — 顶部 `_load_dotenv()` 先加载同目录 `.env`,再定义 API、设备、画布尺寸、字体路径、推送参数;凭证 fallback 为空串,真实值来自 `.env`/环境变量。 ## 墨水屏渲染约束(改 renderer.py 必读) diff --git a/pusher.py b/pusher.py index 3c5b5f7..8f7e60e 100644 --- a/pusher.py +++ b/pusher.py @@ -6,21 +6,31 @@ http:/// 调试同一套接口)。 """ import json +import time import urllib.parse import urllib.request import urllib.error import config +# 局域网 mDNS(.local)域名解析基于组播 UDP,偶发瞬时失败正常,重试即可恢复。 +RETRY_TIMES = 2 +RETRY_DELAY = 2 + def _request(method, path, headers=None, body=None): url = f"http://{config.EPD_HOST}{path}" req = urllib.request.Request(url, data=body, headers=headers or {}, method=method) - try: - with urllib.request.urlopen(req, timeout=30) as resp: - return resp.read().decode("utf-8", "ignore") - except urllib.error.HTTPError as e: - raise RuntimeError(f"HTTP {e.code}: {e.read().decode('utf-8', 'ignore')}") from e + for attempt in range(RETRY_TIMES + 1): + try: + with urllib.request.urlopen(req, timeout=30) as resp: + return resp.read().decode("utf-8", "ignore") + except urllib.error.HTTPError as e: + raise RuntimeError(f"HTTP {e.code}: {e.read().decode('utf-8', 'ignore')}") from e + except urllib.error.URLError: + if attempt == RETRY_TIMES: + raise + time.sleep(RETRY_DELAY) def _list_gallery_names(): @@ -29,8 +39,14 @@ def _list_gallery_names(): def _clear_gallery(): - """设备画廊按追加保存,用完即删,使其始终只留最新一张。单张删除失败不影响后续上传。""" - for name in _list_gallery_names(): + """设备画廊按追加保存,用完即删,使其始终只留最新一张。 + 清理是锦上添花,不是推送的必要条件——拉列表或单张删除失败都不能阻塞后续上传。""" + try: + names = _list_gallery_names() + except Exception as e: + print(f"[推送] 拉取画廊列表失败,跳过本次清理:{e}") + return + for name in names: try: _request("POST", f"/delete_image?name={urllib.parse.quote(name)}") except Exception as e: