feat(ppclock): batch JSONL 批量执行(单连接),104 测试全绿

This commit is contained in:
agent committed 2026-07-24 11:03:36 +00:00
1 parent f291c61676
commit 5ff399673a
6 files changed
+906 -71

No files matched your search

+102 -71
View File
@@ -161,83 +161,114 @@ def build_parser() -> argparse.ArgumentParser:
sp.add_argument("hex")
sp.add_argument("--channel", default="rxtx", choices=["rxtx", "epd"])
sp.add_argument("--no-response", action="store_true")
sp = sub.add_parser("batch", help="JSONL 批量执行(单次连接,agent 编排)")
sp.add_argument("file", help="JSONL 文件,每行 {\"argv\": [...]};'-' 读 stdin")
sp.add_argument("--stop-on-error", action="store_true")
return p
async def _dispatch(args) -> dict | None:
async def _run_command(args, t):
"""在已有连接上执行单条命令(body 供 batch 复用)。"""
c = args.cmd
if c == "time":
dt = (datetime.datetime.fromisoformat(args.datetime)
if args.datetime else None)
await C.set_time(t, dt)
return {"set": (dt or datetime.datetime.now()).isoformat()}
if c == "mode":
await C.set_mode(t, args.name)
return {"mode": args.name}
if c == "toggle":
await C.toggle(t, args.name)
return {"toggle": args.name}
if c == "clear":
await C.clear(t)
return None
if c == "image":
return await _cmd_image(args, t)
if c == "template":
return await _cmd_template(args, t)
if c == "countdown":
if args.off:
await C.countdown_off(t)
return {"countdown": "off"}
if args.show_calendar:
if not args.prefix:
raise ValueError("--show-calendar 需要 --prefix 提供日历文字")
await C.countdown_set(t, None, args.mode, prefix=args.prefix,
show_calendar=True)
return {"calendar_text": args.prefix}
if not args.date:
raise ValueError("缺少日期(YYYY-MM-DD)或 --off")
d = datetime.date.fromisoformat(args.date)
await C.countdown_set(t, d, args.mode, prefix=args.prefix)
return {"date": d.isoformat(), "mode": args.mode,
"prefix": args.prefix}
if c == "sleep":
on = args.on or not args.off
await C.sleep(t, on, args.start, args.end)
return {"on": on, "start": args.start, "end": args.end}
if c == "parking":
await C.parking(t, args.number)
return {"number": args.number}
if c == "rotation":
await C.rotation(t, count=args.count, interval=args.interval, unit=args.unit)
return {"count": args.count, "interval": args.interval, "unit": args.unit}
if c == "lut":
await C.lut(t, args.value)
return {"lut": hex(args.value)}
if c == "activate":
if args.show_id:
return {"device_id": await C.get_device_id(t)}
if not args.code:
raise ValueError("缺少激活码或 --show-id")
await C.activate(t, args.code)
return None
if c == "wifi":
await C.wifi(t, args.ssid, args.password, args.city)
return None
if c == "raw":
data = bytes.fromhex(args.hex)
if args.channel == "epd":
await t.write_epd(data, response=not args.no_response)
else:
await t.write_rxtx(data, response=not args.no_response)
return {"sent": len(data)}
raise ValueError(f"未知命令 {c}")
async def _run_batch(args) -> list:
lines = (sys.stdin.read() if args.file == "-"
else open(args.file, encoding="utf-8").read())
results = []
parser = build_parser()
async with _make_transport(args) as t:
for lineno, line in enumerate(lines.splitlines(), 1):
line = line.strip()
if not line:
continue
try:
item = json.loads(line)
argv = item["argv"] if isinstance(item, dict) else item
sub_args = parser.parse_args(argv)
data = await _run_command(sub_args, t)
results.append({"line": lineno, "ok": True, "cmd": sub_args.cmd,
"data": data})
except Exception as e:
results.append({"line": lineno, "ok": False, "error": str(e)})
if args.stop_on_error:
break
return results
async def _dispatch(args) -> dict | list | None:
if args.cmd == "scan":
from .transport import scan
return {"devices": await scan(timeout=args.timeout)}
async def body(t):
c = args.cmd
if c == "time":
dt = (datetime.datetime.fromisoformat(args.datetime)
if args.datetime else None)
await C.set_time(t, dt)
return {"set": (dt or datetime.datetime.now()).isoformat()}
if c == "mode":
await C.set_mode(t, args.name)
return {"mode": args.name}
if c == "toggle":
await C.toggle(t, args.name)
return {"toggle": args.name}
if c == "clear":
await C.clear(t)
return None
if c == "image":
return await _cmd_image(args, t)
if c == "template":
return await _cmd_template(args, t)
if c == "countdown":
if args.off:
await C.countdown_off(t)
return {"countdown": "off"}
if args.show_calendar:
if not args.prefix:
raise ValueError("--show-calendar 需要 --prefix 提供日历文字")
await C.countdown_set(t, None, args.mode, prefix=args.prefix,
show_calendar=True)
return {"calendar_text": args.prefix}
if not args.date:
raise ValueError("缺少日期(YYYY-MM-DD)或 --off")
d = datetime.date.fromisoformat(args.date)
await C.countdown_set(t, d, args.mode, prefix=args.prefix)
return {"date": d.isoformat(), "mode": args.mode,
"prefix": args.prefix}
if c == "sleep":
on = args.on or not args.off
await C.sleep(t, on, args.start, args.end)
return {"on": on, "start": args.start, "end": args.end}
if c == "parking":
await C.parking(t, args.number)
return {"number": args.number}
if c == "rotation":
await C.rotation(t, count=args.count, interval=args.interval, unit=args.unit)
return {"count": args.count, "interval": args.interval, "unit": args.unit}
if c == "lut":
await C.lut(t, args.value)
return {"lut": hex(args.value)}
if c == "activate":
if args.show_id:
return {"device_id": await C.get_device_id(t)}
if not args.code:
raise ValueError("缺少激活码或 --show-id")
await C.activate(t, args.code)
return None
if c == "wifi":
await C.wifi(t, args.ssid, args.password, args.city)
return None
if c == "raw":
data = bytes.fromhex(args.hex)
if args.channel == "epd":
await t.write_epd(data, response=not args.no_response)
else:
await t.write_rxtx(data, response=not args.no_response)
return {"sent": len(data)}
raise ValueError(f"未知命令 {c}")
return await _run_with_transport(args, body)
if args.cmd == "batch":
return {"results": await _run_batch(args)}
return await _run_with_transport(args, lambda t: _run_command(args, t))
def main(argv=None) -> int: