mDNS(.local)域名解析基于组播UDP,偶发丢包会导致整轮推送直接抛异常跳过。 _request() 对连接类异常加重试(2次/间隔2s);画廊清理列表拉取失败也不再 阻塞后续 /upload,清理是锦上添花而非推送必要条件。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
63 lines
2.3 KiB
Python
63 lines
2.3 KiB
Python
#!/usr/bin/env python3
|
||
# -*- coding: utf-8 -*-
|
||
"""
|
||
推送层 —— 直连局域网墨水屏设备(喵喵固件,仅用标准库 urllib,无第三方依赖)。
|
||
固件开源:https://gitee.com/gxp666111/miaomiao(web/ 为设备自带管理页,可在浏览器打开
|
||
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)
|
||
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():
|
||
text = _request("GET", "/images")
|
||
return [item["name"] for item in json.loads(text).get("items", [])]
|
||
|
||
|
||
def _clear_gallery():
|
||
"""设备画廊按追加保存,用完即删,使其始终只留最新一张。
|
||
清理是锦上添花,不是推送的必要条件——拉列表或单张删除失败都不能阻塞后续上传。"""
|
||
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:
|
||
print(f"[推送] 清理画廊旧图 {name} 失败:{e}")
|
||
|
||
|
||
def push_image(image_path):
|
||
"""推送图片到局域网墨水屏设备:先清空画廊旧图,再上传新图触发显示刷新。"""
|
||
_clear_gallery()
|
||
with open(image_path, "rb") as fp:
|
||
content = fp.read()
|
||
resp = _request("POST", "/upload", headers={"Content-Type": "image/png"}, body=content)
|
||
return config.EPD_HOST, resp
|