feat: add McpScreen phase 2 display tools
Main / Build (Brainfuck) (push) Has been cancelled
Main / Build (Breakout) (push) Has been cancelled
Main / Build (Calculator) (push) Has been cancelled
Main / Build (Diceware) (push) Has been cancelled
Main / Build (EpubReader) (push) Has been cancelled
Main / Build (GPIO) (push) Has been cancelled
Main / Build (GraphicsDemo) (push) Has been cancelled
Main / Build (HelloWorld) (push) Has been cancelled
Main / Build (M5UnitTest) (push) Has been cancelled
Main / Build (Magic8Ball) (push) Has been cancelled
Main / Build (MediaKeys) (push) Has been cancelled
Main / Build (MystifyDemo) (push) Has been cancelled
Main / Build (SerialConsole) (push) Has been cancelled
Main / Build (Snake) (push) Has been cancelled
Main / Build (TamaTac) (push) Has been cancelled
Main / Build (TodoList) (push) Has been cancelled
Main / Build (TwoEleven) (push) Has been cancelled
Main / Bundle (push) Has been cancelled

This commit is contained in:
Adolfo Reyna
2026-06-25 12:43:08 -04:00
parent ac9d6dc0d3
commit 0fb3c22f79
7 changed files with 928 additions and 83 deletions
+116 -3
View File
@@ -1,9 +1,11 @@
#!/usr/bin/env python3
"""Host smoke test for the Tactility MCP Screen Phase 1 API."""
"""Host smoke test for the Tactility MCP Screen Phase 2 API."""
import argparse
import base64
import json
import socket
import struct
import urllib.request
@@ -49,6 +51,53 @@ def require_result(response: dict, label: str) -> None:
raise RuntimeError(f"{label} returned no result: {response}")
def make_bmp() -> bytes:
width = 4
height = 4
row_size = width * 3
pixels = bytearray()
colors = [
(0, 0, 255),
(0, 255, 0),
(255, 0, 0),
(255, 255, 255),
]
for y in range(height):
for x in range(width):
blue, green, red = colors[(x + y) % len(colors)]
pixels.extend((blue, green, red))
pixels.extend(b"\0" * ((4 - row_size % 4) % 4))
offset = 54
file_size = offset + len(pixels)
header = (
b"BM"
+ struct.pack("<IHHI", file_size, 0, 0, offset)
+ struct.pack("<IIIHHIIIIII", 40, width, height, 1, 24, 0, len(pixels), 0, 0, 0, 0)
)
return header + pixels
def raw_endpoint(ip: str) -> None:
pixels = bytes(
[
0xF8, 0x00,
0x07, 0xE0,
0x00, 0x1F,
0xFF, 0xFF,
]
)
request = urllib.request.Request(
f"http://{ip}/api/screen/raw?x=2&y=2&w=2&h=2",
data=pixels,
headers={"Content-Type": "application/octet-stream"},
method="POST",
)
with urllib.request.urlopen(request, timeout=5) as response:
if response.read() != b"OK":
raise RuntimeError("Raw endpoint returned an unexpected response")
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("--ip", help="Device IP; omit to use UDP discovery")
@@ -106,9 +155,73 @@ def main() -> None:
},
)
require_result(drawn, "draw_text")
print("Visible draw test sent.")
print("Phase 1 smoke test passed.")
raw_rpc = rpc(
ip,
5,
"tools/call",
{
"name": "draw_raw_rgb565",
"arguments": {
"rgb565_base64": base64.b64encode(
bytes([0xFF, 0xE0, 0xF8, 0x1F, 0x07, 0xFF, 0x00, 0x00])
).decode(),
"x": 20,
"y": 70,
"w": 4,
"h": 1,
},
},
)
require_result(raw_rpc, "draw_raw_rgb565")
bmp = rpc(
ip,
6,
"tools/call",
{
"name": "draw_color_bmp",
"arguments": {
"bmp_base64": base64.b64encode(make_bmp()).decode(),
"x": 30,
"y": 90,
},
},
)
require_result(bmp, "draw_color_bmp")
pbm_bytes = b"P4\n8 2\n" + bytes([0b10101010, 0b01010101])
pbm = rpc(
ip,
7,
"tools/call",
{
"name": "draw_image",
"arguments": {
"pbm_base64": base64.b64encode(pbm_bytes).decode(),
"x": 50,
"y": 110,
},
},
)
require_result(pbm, "draw_image")
raw_endpoint(ip)
screenshot = rpc(
ip,
8,
"tools/call",
{"name": "get_screenshot", "arguments": {}},
)
require_result(screenshot, "get_screenshot")
screenshot_text = screenshot["result"]["content"][0]["text"]
if not screenshot_text.startswith("__PBM_BASE64__:"):
raise RuntimeError("Screenshot did not return PBM data")
base64.b64decode(screenshot_text.split(":", 1)[1], validate=True)
print("Visible Phase 2 draw and screenshot tests sent.")
print("Phase 2 smoke test passed.")
if __name__ == "__main__":