fix: 推送网络请求加重试,避免瞬时失败漏推
mDNS(.local)域名解析基于组播UDP,偶发丢包会导致整轮推送直接抛异常跳过。 _request() 对连接类异常加重试(2次/间隔2s);画廊清理列表拉取失败也不再 阻塞后续 /upload,清理是锦上添花而非推送必要条件。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -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://<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`/环境变量。
|
||||
|
||||
## 墨水屏渲染约束(改 renderer.py 必读)
|
||||
|
||||
30
pusher.py
30
pusher.py
@@ -6,21 +6,31 @@
|
||||
http://<EPD_HOST>/ 调试同一套接口)。
|
||||
"""
|
||||
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:
|
||||
|
||||
Reference in New Issue
Block a user