fix: 推送网络请求加重试,避免瞬时失败漏推

mDNS(.local)域名解析基于组播UDP,偶发丢包会导致整轮推送直接抛异常跳过。
_request() 对连接类异常加重试(2次/间隔2s);画廊清理列表拉取失败也不再
阻塞后续 /upload,清理是锦上添花而非推送必要条件。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-07-07 10:55:15 +08:00
parent 2121b1eff4
commit b32007483a
2 changed files with 24 additions and 8 deletions

View File

@@ -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`/标签仍黑)。**配色铁律**红与黑同属深色红叠黑对比极低故红只压白底或作大块实心白字压红**绝不红字压黑底**。加红先问是否承载语义」。 - **红色点缀系统**改配色务必克制模块顶部 `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 前挂回 - **`_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 描边画出改天气展示从这里动 - **天气图标全矢量手绘**无图片资源`_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 等价于始终静态展示最新一张历史上曾走 Zectrix 公网 API`POST /devices/{id}/display/image` + 设备 MAC + `ZECTRIX_API_KEY`现统一为局域网直连 - **`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`现统一为局域网直连
- **`config.py`** 顶部 `_load_dotenv()` 先加载同目录 `.env`再定义 API设备画布尺寸字体路径推送参数凭证 fallback 为空串真实值来自 `.env`/环境变量 - **`config.py`** 顶部 `_load_dotenv()` 先加载同目录 `.env`再定义 API设备画布尺寸字体路径推送参数凭证 fallback 为空串真实值来自 `.env`/环境变量
## 墨水屏渲染约束(改 renderer.py 必读) ## 墨水屏渲染约束(改 renderer.py 必读)

View File

@@ -6,21 +6,31 @@
http://<EPD_HOST>/ 调试同一套接口)。 http://<EPD_HOST>/ 调试同一套接口)。
""" """
import json import json
import time
import urllib.parse import urllib.parse
import urllib.request import urllib.request
import urllib.error import urllib.error
import config import config
# 局域网 mDNS.local域名解析基于组播 UDP偶发瞬时失败正常重试即可恢复。
RETRY_TIMES = 2
RETRY_DELAY = 2
def _request(method, path, headers=None, body=None): def _request(method, path, headers=None, body=None):
url = f"http://{config.EPD_HOST}{path}" url = f"http://{config.EPD_HOST}{path}"
req = urllib.request.Request(url, data=body, headers=headers or {}, method=method) req = urllib.request.Request(url, data=body, headers=headers or {}, method=method)
for attempt in range(RETRY_TIMES + 1):
try: try:
with urllib.request.urlopen(req, timeout=30) as resp: with urllib.request.urlopen(req, timeout=30) as resp:
return resp.read().decode("utf-8", "ignore") return resp.read().decode("utf-8", "ignore")
except urllib.error.HTTPError as e: except urllib.error.HTTPError as e:
raise RuntimeError(f"HTTP {e.code}: {e.read().decode('utf-8', 'ignore')}") from 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(): def _list_gallery_names():
@@ -29,8 +39,14 @@ def _list_gallery_names():
def _clear_gallery(): 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: try:
_request("POST", f"/delete_image?name={urllib.parse.quote(name)}") _request("POST", f"/delete_image?name={urllib.parse.quote(name)}")
except Exception as e: except Exception as e: