From 88e22a423bc06e04e4d83b91fe4e3d6e758f7838 Mon Sep 17 00:00:00 2001 From: kassam Date: Wed, 22 Apr 2026 11:19:59 +0400 Subject: [PATCH] Update 2026-04-22 11:19:55 --- Client/marcus_cli.py | 4 ++-- Client/marcus_client.py | 2 +- Server/marcus_server.py | 50 ++++++++++++++++++++++++++++++++++++++++- 3 files changed, 52 insertions(+), 4 deletions(-) diff --git a/Client/marcus_cli.py b/Client/marcus_cli.py index 82cc68c..7ab1d0c 100644 --- a/Client/marcus_cli.py +++ b/Client/marcus_cli.py @@ -242,7 +242,7 @@ def _print_help(): # ── MAIN ───────────────────────────────────────────────────────────────────── def main(): - parser = argparse.ArgumentParser(description="Marcus CLI Client") + parser = argparse.ArgumentParser(description="Sanad CLI Client (talks to the Marcus brain over WebSocket)") parser.add_argument("--ip", default=None, help="Server IP address") parser.add_argument("--port", type=int, default=None, help="Server port") args = parser.parse_args() @@ -250,7 +250,7 @@ def main(): eth_ip = _net.get("jetson_ip", "192.168.123.164") wlan_ip = _net.get("jetson_wlan_ip", "10.255.254.86") - print(f"\n{C.BOLD}{C.ORANGE} MARCUS — CLI Client{C.RESET}") + print(f"\n{C.BOLD}{C.ORANGE} SANAD — CLI Client{C.RESET}") print(f" {'═' * 40}") print(f" {C.GRAY}Connection options:{C.RESET}") print(f" 1) eth0 — {eth_ip}:{DEFAULT_PORT}") diff --git a/Client/marcus_client.py b/Client/marcus_client.py index 64aabdc..c99ab66 100644 --- a/Client/marcus_client.py +++ b/Client/marcus_client.py @@ -140,7 +140,7 @@ def btn(parent, text, bg_color=BG3, fg_color=WHITE, font=FONT_SM, command=None, class MarcusGUI: def __init__(self, root): self.root = root - self.root.title("Marcus — Navigation Control") + self.root.title("Sanad — Navigation Control") self.root.configure(bg=BG) self.root.geometry("1360x860") self.root.minsize(1000, 650) diff --git a/Server/marcus_server.py b/Server/marcus_server.py index 9b32854..0164f44 100644 --- a/Server/marcus_server.py +++ b/Server/marcus_server.py @@ -29,10 +29,25 @@ from Brain.marcus_brain import init_brain, process_command, get_brain_status, sh from API.camera_api import get_frame _net = load_config("Network") +_cam = load_config("Camera") HOST = "0.0.0.0" PORT = _net.get("websocket_port", 8765) + +def _camera_config_payload() -> dict: + """Current camera parameters for the `camera_config` client event.""" + return { + "type": "camera_config", + "profile": _cam.get("profile", "default"), + "width": _cam.get("width", 424), + "height": _cam.get("height", 240), + "fps": _cam.get("fps", 15), + "jpeg_quality": _cam.get("jpeg_quality", 70), + "pipeline_active": True, # camera thread is always on in this build + "timestamp": time.strftime("%H:%M:%S"), + } + connected_clients = set() @@ -76,9 +91,13 @@ async def handler(websocket): "memory": status["memory"], "camera": status["camera"], "lidar": lidar_ok, - "message": "Marcus server ready (full brain)", + "message": "Sanad server ready (full brain)", })) + # Also push current camera config so the GUI's Camera tab populates + # immediately on connect without needing an explicit get_camera round-trip. + await websocket.send(json.dumps(_camera_config_payload())) + try: async for message in websocket: data = json.loads(message) @@ -139,6 +158,35 @@ async def handler(websocket): "timestamp": time.strftime("%H:%M:%S"), })) + elif msg_type == "get_camera": + await websocket.send(json.dumps(_camera_config_payload())) + + elif msg_type in ("set_camera", "set_resolution"): + # The RealSense pipeline is started once at boot with values + # from config_Camera.json; hot-switching resolution would + # require tearing down and rebuilding the camera thread. + # Not implemented today — reply with a clear error so the + # GUI surfaces it rather than hanging forever waiting for + # a camera_config event that never comes. + await websocket.send(json.dumps({ + "type": "error", + "message": (f"`{msg_type}` is not supported in this build — " + "camera parameters are pinned from config_Camera.json " + "at startup."), + "echo": msg_type, + "timestamp": time.strftime("%H:%M:%S"), + })) + # Still send the current config so the GUI can refresh its UI. + await websocket.send(json.dumps(_camera_config_payload())) + + else: + await websocket.send(json.dumps({ + "type": "error", + "message": f"Unknown message type: {msg_type!r}", + "echo": msg_type, + "timestamp": time.strftime("%H:%M:%S"), + })) + except websockets.exceptions.ConnectionClosed: print(f"[Server] Client disconnected: {client}") finally: