Skip to content

Commit 111d347

Browse files
feat: add framebuffer command helpers for reading and saving PNGs (#4)
1 parent 5f9249a commit 111d347

File tree

2 files changed

+83
-0
lines changed

2 files changed

+83
-0
lines changed

src/wokwi_client/client.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@
55
from pathlib import Path
66
from typing import Any, Optional, Union
77

8+
from wokwi_client.framebuffer import (
9+
read_framebuffer_png_bytes,
10+
save_framebuffer_png,
11+
)
12+
813
from .__version__ import get_version
914
from .constants import DEFAULT_WS_URL
1015
from .control import set_control
@@ -233,3 +238,11 @@ async def set_control(
233238
value: Control value to set (float).
234239
"""
235240
return await set_control(self._transport, part=part, control=control, value=value)
241+
242+
async def read_framebuffer_png_bytes(self, id: str) -> bytes:
243+
"""Return the current framebuffer as PNG bytes."""
244+
return await read_framebuffer_png_bytes(self._transport, id=id)
245+
246+
async def save_framebuffer_png(self, id: str, path: Path, overwrite: bool = True) -> Path:
247+
"""Save the current framebuffer as a PNG file."""
248+
return await save_framebuffer_png(self._transport, id=id, path=path, overwrite=overwrite)

src/wokwi_client/framebuffer.py

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
"""Framebuffer command helpers.
2+
3+
Provides utilities to interact with devices exposing a framebuffer (e.g. LCD
4+
modules) via the `framebuffer:read` command.
5+
6+
Exposed helpers:
7+
* framebuffer_read -> raw response (contains base64 PNG at result.png)
8+
* framebuffer_png_bytes -> decoded PNG bytes
9+
* save_framebuffer_png -> save PNG to disk
10+
* compare_framebuffer_png -> compare current framebuffer against reference
11+
"""
12+
13+
# SPDX-FileCopyrightText: 2025-present CodeMagic LTD
14+
#
15+
# SPDX-License-Identifier: MIT
16+
17+
from __future__ import annotations
18+
19+
import base64
20+
from pathlib import Path
21+
22+
from .exceptions import WokwiError
23+
from .protocol_types import ResponseMessage
24+
from .transport import Transport
25+
26+
__all__ = [
27+
"read_framebuffer",
28+
"read_framebuffer_png_bytes",
29+
"save_framebuffer_png",
30+
]
31+
32+
33+
async def read_framebuffer(transport: Transport, *, id: str) -> ResponseMessage:
34+
"""Issue `framebuffer:read` for the given device id and return raw response."""
35+
return await transport.request("framebuffer:read", {"id": id})
36+
37+
38+
def _extract_png_b64(resp: ResponseMessage) -> str:
39+
result = resp.get("result", {})
40+
png_b64 = result.get("png")
41+
if not isinstance(png_b64, str): # pragma: no cover - defensive
42+
raise WokwiError("Malformed framebuffer:read response: missing 'png' base64 string")
43+
return png_b64
44+
45+
46+
async def read_framebuffer_png_bytes(transport: Transport, *, id: str) -> bytes:
47+
"""Return decoded PNG bytes for the framebuffer of device `id`."""
48+
resp = await read_framebuffer(transport, id=id)
49+
return base64.b64decode(_extract_png_b64(resp))
50+
51+
52+
async def save_framebuffer_png(
53+
transport: Transport, *, id: str, path: Path, overwrite: bool = True
54+
) -> Path:
55+
"""Save the framebuffer PNG to `path` and return the path.
56+
57+
Args:
58+
transport: Active transport.
59+
id: Device id (e.g. "lcd1").
60+
path: Destination file path.
61+
overwrite: Overwrite existing file (default True). If False and file
62+
exists, raises WokwiError.
63+
"""
64+
if path.exists() and not overwrite:
65+
raise WokwiError(f"File already exists and overwrite=False: {path}")
66+
data = await read_framebuffer_png_bytes(transport, id=id)
67+
path.parent.mkdir(parents=True, exist_ok=True)
68+
with open(path, "wb") as f:
69+
f.write(data)
70+
return path

0 commit comments

Comments
 (0)