Migrate MCP and utilities to CircuitPython, including color GIF and volume fixes

This commit is contained in:
Adolfo Reyna
2026-06-22 10:13:10 -04:00
parent 2863f21459
commit 8f871e499e
53 changed files with 4239 additions and 108 deletions
+25
View File
@@ -0,0 +1,25 @@
import urllib.request
import json
def call_mcp(method, params={}):
url = "http://192.168.68.118/api/mcp"
payload = {
"jsonrpc": "2.0",
"id": 1,
"method": method,
"params": params
}
req = urllib.request.Request(
url,
data=json.dumps(payload).encode("utf-8"),
headers={"Content-Type": "application/json"},
method="POST"
)
try:
with urllib.request.urlopen(req, timeout=5.0) as resp:
return json.loads(resp.read().decode("utf-8"))
except Exception as e:
return {"error": str(e)}
res = call_mcp("tools/call", {"name": "get_stream_stats", "arguments": {}})
print(json.dumps(res, indent=2))
+30
View File
@@ -0,0 +1,30 @@
import serial
import time
import sys
def main():
port = "/dev/cu.usbmodem411CF91D65091"
print(f"Connecting to {port} at 115200...")
try:
ser = serial.Serial(port, 115200, timeout=1.0)
except Exception as e:
print(f"Error opening serial port: {e}")
return
print("Reading serial output for 3 seconds...")
start_time = time.time()
try:
while time.time() - start_time < 3.0:
if ser.in_waiting > 0:
data = ser.read(ser.in_waiting)
sys.stdout.write(data.decode("utf-8", "ignore"))
sys.stdout.flush()
time.sleep(0.1)
except KeyboardInterrupt:
pass
finally:
ser.close()
print("\nSerial connection closed.")
if __name__ == "__main__":
main()
+39
View File
@@ -0,0 +1,39 @@
import serial
import time
import sys
def main():
port = "/dev/cu.usbmodem411CF91D65091"
print(f"Connecting to {port} at 115200...")
try:
ser = serial.Serial(port, 115200, timeout=1.0)
except Exception as e:
print(f"Error: {e}")
return
print("Sending Ctrl-C to interrupt...")
ser.write(b'\x03')
time.sleep(0.5)
print("--- Current buffer contents ---")
print(ser.read_all().decode("utf-8", "ignore"))
print("Sending Ctrl-D to trigger soft reboot...")
ser.write(b'\x04')
print("--- Capturing boot output (10 seconds) ---")
start_time = time.time()
try:
while time.time() - start_time < 10.0:
if ser.in_waiting > 0:
data = ser.read(ser.in_waiting)
sys.stdout.write(data.decode("utf-8", "ignore"))
sys.stdout.flush()
time.sleep(0.05)
except KeyboardInterrupt:
pass
finally:
ser.close()
print("\nConnection closed.")
if __name__ == "__main__":
main()
+68
View File
@@ -0,0 +1,68 @@
import serial
import time
import sys
def run_diagnostic(port):
print(f"Opening serial port {port}...")
try:
ser = serial.Serial(port, 115200, timeout=1)
except Exception as e:
print("Failed to open serial port:", e)
return False
print("Interrupting board execution (Ctrl+C)...")
for _ in range(30):
ser.write(b'\x03')
time.sleep(0.02)
time.sleep(0.5)
ser.read_all()
print("Entering raw REPL...")
ser.write(b'\x01')
time.sleep(0.2)
resp = ser.read_all()
if b'raw REPL' not in resp and b'OK' not in resp and b'raw' not in resp:
print("Could not enter raw REPL:", resp)
ser.close()
return False
# Python code to test playback
test_code = """
import audio_util
try:
print("Starting play_tone test...")
audio_util.play_tone(440, 1000, 50)
print("play_tone test finished!")
except Exception as e:
import sys
sys.print_exception(e)
"""
print("Running diagnostic code on the board...")
# Send code
ser.write(test_code.encode('utf-8'))
ser.write(b'\x04') # Ctrl+D to execute
time.sleep(0.2)
# Read output
print("\n--- Board Output ---")
start = time.time()
while time.time() - start < 5.0:
line = ser.readline()
if line:
print(line.decode('utf-8', 'ignore').strip())
# Reset the board to return to normal
print("Resetting board to normal...")
ser.write(b'\x02') # Ctrl+B to exit raw REPL
time.sleep(0.1)
ser.write(b'\x03')
time.sleep(0.1)
ser.write(b"import machine\nmachine.reset()\n")
ser.close()
return True
if __name__ == "__main__":
run_diagnostic('/dev/cu.usbmodem101')
+64
View File
@@ -0,0 +1,64 @@
import urllib.request
import urllib.parse
import time
from PIL import Image, ImageDraw
IP = "192.168.68.128"
def send_raw_frame(payload, x=0, y=0, w=320, h=240, fmt=None):
params = {"x": x, "y": y, "w": w, "h": h}
if fmt:
params["format"] = fmt
q = urllib.parse.urlencode(params)
url = f"http://{IP}/api/screen/raw?{q}"
req = urllib.request.Request(
url,
data=payload,
headers={"Content-Type": "application/octet-stream"},
method="POST"
)
try:
with urllib.request.urlopen(req, timeout=5.0) as resp:
return resp.read().decode("utf-8")
except Exception as e:
return f"Error: {e}"
# 1. Test Color Frame (RGB565)
print("Generating raw color frame...")
img_color = Image.new("RGB", (320, 240))
draw = ImageDraw.Draw(img_color)
for x in range(320):
for y in range(240):
r = (x * 255 // 319)
g = (y * 255 // 239)
b = 128
img_color.putpixel((x, y), (r, g, b))
draw.text((20, 20), "RAW COLOR STREAM (RGB565)", fill=(255, 255, 255))
# Convert to RGB565 BE bytes
color_payload = bytearray(320 * 240 * 2)
for idx, (r, g, b) in enumerate(img_color.getdata()):
val = ((r >> 3) << 11) | ((g >> 2) << 5) | (b >> 3)
color_payload[idx * 2] = (val >> 8) & 0xFF
color_payload[idx * 2 + 1] = val & 0xFF
print("Streaming raw color frame...")
res = send_raw_frame(bytes(color_payload), fmt="rgb565")
print("Response:", res)
time.sleep(3.0)
# 2. Test Monochrome Frame (1-bit)
print("\nGenerating raw monochrome frame...")
img_mono = Image.new("1", (320, 240), 0)
draw_mono = ImageDraw.Draw(img_mono)
draw_mono.rectangle([10, 10, 310, 230], outline=1, width=3)
draw_mono.text((30, 100), "RAW MONOCHROME STREAM (1-BIT)", fill=1)
# Convert PIL 1-bit to MHMSB bytearray
mono_payload = img_mono.tobytes()
print(f"Monochrome payload size: {len(mono_payload)} bytes")
print("Streaming raw monochrome frame...")
res_mono = send_raw_frame(mono_payload, fmt="mono")
print("Response:", res_mono)
+48
View File
@@ -0,0 +1,48 @@
import urllib.request
import json
import time
def call_mcp(method, params={}):
url = "http://192.168.68.128/api/mcp"
payload = {
"jsonrpc": "2.0",
"id": 1,
"method": method,
"params": params
}
req = urllib.request.Request(
url,
data=json.dumps(payload).encode("utf-8"),
headers={"Content-Type": "application/json"},
method="POST"
)
try:
with urllib.request.urlopen(req, timeout=5.0) as resp:
return json.loads(resp.read().decode("utf-8"))
except Exception as e:
return {"error": str(e)}
# 1. Fetch start stats
print("Fetching start stats...")
res_start = call_mcp("tools/call", {"name": "get_stream_stats", "arguments": {}})
print(json.dumps(res_start, indent=2))
# 2. Wait 3 seconds
print("Waiting 3 seconds...")
time.sleep(3.0)
# 3. Fetch end stats
print("Fetching end stats...")
res_end = call_mcp("tools/call", {"name": "get_stream_stats", "arguments": {}})
print(json.dumps(res_end, indent=2))
# Calculate FPS
try:
start_stats = json.loads(res_start["result"]["content"][0]["text"])
end_stats = json.loads(res_end["result"]["content"][0]["text"])
frames_diff = end_stats["frames_drawn"] - start_stats["frames_drawn"]
packets_diff = end_stats["udp_packets_received"] - start_stats["udp_packets_received"]
print(f"\nResult: Drew {frames_diff} frames in 3.0 seconds ({frames_diff / 3.0:.1f} FPS)")
print(f"Packets received: {packets_diff} in 3.0 seconds ({packets_diff / 3.0:.1f} pps)")
except Exception as e:
print("Failed to calculate FPS:", e)
+88
View File
@@ -0,0 +1,88 @@
import serial
import time
import sys
def upload_via_serial(port, local_path, remote_path):
print(f"Opening serial port {port}...")
try:
ser = serial.Serial(port, 115200, timeout=0.5)
except Exception as e:
print("Failed to open serial port:", e)
return False
print("Interrupting board (Ctrl+C)...")
for _ in range(50):
ser.write(b'\x03')
time.sleep(0.02)
time.sleep(0.5)
ser.read_all()
print("Entering raw REPL...")
ser.write(b'\x01')
time.sleep(0.2)
resp = ser.read_all()
if b'raw REPL' not in resp and b'OK' not in resp and b'raw' not in resp:
print("Could not enter raw REPL:", resp)
ser.close()
return False
with open(local_path, 'rb') as f:
content = f.read()
print(f"Uploading {local_path} to remote {remote_path} ({len(content)} bytes)...")
# Open file
cmd = f"f = open('{remote_path}', 'wb')\n".encode('utf-8')
ser.write(cmd)
ser.write(b'\x04')
time.sleep(0.1)
ser.read_all()
# Write chunks
chunk_size = 256
for i in range(0, len(content), chunk_size):
chunk = content[i:i+chunk_size]
cmd = b"f.write(" + repr(chunk).encode('utf-8') + b")\n"
ser.write(cmd)
ser.write(b'\x04')
time.sleep(0.05)
sys.stdout.write('.')
sys.stdout.flush()
cmd = b"f.close()\n"
ser.write(cmd)
ser.write(b'\x04')
time.sleep(0.1)
ser.read_all()
print(f"\nUploaded {remote_path} successfully!")
ser.write(b'\x02')
ser.close()
return True
def main():
port = '/dev/cu.usbmodem101'
files_to_upload = [
("main.py", "main.py"),
("demo_websocket_voice.py", "demo_websocket_voice.py")
]
for local, remote in files_to_upload:
if not upload_via_serial(port, local, remote):
print(f"Failed to upload {local}. Exiting.")
sys.exit(1)
print("Rebooting board...")
try:
ser = serial.Serial(port, 115200, timeout=0.5)
ser.write(b'\x03')
time.sleep(0.1)
ser.write(b'import machine\nmachine.reset()\n')
ser.close()
print("Reset triggered successfully!")
except Exception as e:
print("Could not trigger reset:", e)
if __name__ == "__main__":
main()