""" MainWindow.py - Master Dashboard with Dynamic Input Selection """ import random import time import dearpygui.dearpygui as dpg from config import cfg, BackendType # Input State Tracking input_config = { "source": "Gamepad", # Options: "Gamepad", "GUI Sliders", "Random Walk" "manual_vx": 0.0, "manual_vy": 0.0, "manual_omega": 0.0, "random_interval": 2.0, # Change random vector every N seconds "last_random_time": 0.0, "random_vx": 0.0, "random_vy": 0.0, "random_omega": 0.0, } def create_parameter_gui(on_start_callback=None, on_stop_callback=None): dpg.create_context() dpg.create_viewport(title="JackBot Master Control Deck", width=460, height=800) is_running = {"value": False} def toggle_start(sender, app_data, user_data): is_running["value"] = not is_running["value"] if is_running["value"]: dpg.configure_item("start_btn", label="STOP ROBOT") dpg.configure_item("status_text", default_value="Status: RUNNING", color=(0, 255, 0)) dpg.configure_item("backend_combo", enabled=False) if on_start_callback: on_start_callback() else: dpg.configure_item("start_btn", label="START ROBOT") dpg.configure_item("status_text", default_value="Status: STOPPED", color=(255, 100, 100)) dpg.configure_item("backend_combo", enabled=True) if on_stop_callback: on_stop_callback() with dpg.window(label="Robot Control & Telemetry Deck", width=440, height=770, pos=(5, 5)): # --- Execution Control --- dpg.add_text("Control State", color=(0, 200, 255)) dpg.add_text("Status: STOPPED", tag="status_text", color=(255, 100, 100)) dpg.add_button( label="START ROBOT", tag="start_btn", width=-1, height=40, callback=toggle_start ) dpg.add_separator() # --- Input Source Switcher --- dpg.add_text("Input Controller Source", color=(0, 200, 255)) dpg.add_combo( tag="input_source_combo", label="Input Source", items=["Gamepad", "GUI Sliders", "Random Walk"], default_value=input_config["source"], callback=lambda s, d: input_config.update({"source": d}) ) # Manual Sliders (Active when GUI Sliders selected) dpg.add_slider_float( label="Vx (Fwd/Bwd)", default_value=0.0, min_value=-1.0, max_value=1.0, callback=lambda s, d: input_config.update({"manual_vx": d}) ) dpg.add_slider_float( label="Vy (Strafe)", default_value=0.0, min_value=-1.0, max_value=1.0, callback=lambda s, d: input_config.update({"manual_vy": d}) ) dpg.add_slider_float( label="Omega (Turn)", default_value=0.0, min_value=-1.0, max_value=1.0, callback=lambda s, d: input_config.update({"manual_omega": d}) ) dpg.add_separator() # --- Active Command Readouts --- dpg.add_text("Active Command Output", color=(0, 200, 255)) dpg.add_text("Joysticks Connected: 0", tag="txt_joy_count") dpg.add_text("Motion State: IDLE", tag="txt_motion_state", color=(200, 200, 200)) dpg.add_text("Active Vx : 0.00", tag="txt_vx") dpg.add_text("Active Vy : 0.00", tag="txt_vy") dpg.add_text("Active Omega: 0.00", tag="txt_omega") dpg.add_separator() # --- Backend Selection --- dpg.add_text("Backend Selection", color=(0, 200, 255)) dpg.add_combo( tag="backend_combo", label="Target Backend", items=[e.value for e in BackendType], default_value=cfg.backend.value, callback=lambda sender, data: setattr(cfg, 'backend', BackendType(data)) ) dpg.add_separator() # --- Kinematics Adjustments --- dpg.add_text("Gait & Kinematics", color=(0, 200, 255)) dpg.add_slider_float( label="Step Height (m)", default_value=cfg.step_height, min_value=0.01, max_value=0.10, format="%.3f m", callback=lambda sender, data: setattr(cfg, 'step_height', data) ) dpg.add_slider_float( label="Step Length (m)", default_value=cfg.step_length, min_value=0.01, max_value=0.10, format="%.3f m", callback=lambda sender, data: setattr(cfg, 'step_length', data) ) dpg.add_separator() # --- Hardware Comms --- dpg.add_text("Hardware Communications", color=(0, 200, 255)) dpg.add_input_text(label="ESP32 IP", default_value=cfg.esp32_ip, callback=lambda s, d: setattr(cfg, 'esp32_ip', d)) dpg.add_input_text(label="Serial Port", default_value=cfg.port, callback=lambda s, d: setattr(cfg, 'port', d)) dpg.setup_dearpygui() dpg.show_viewport() def resolve_active_command(gamepad_cmd: dict | None) -> dict: """Selects motion vector based on current active source in GUI.""" source = input_config["source"] now = time.time() if source == "GUI Sliders": vx = input_config["manual_vx"] vy = input_config["manual_vy"] omega = input_config["manual_omega"] joy_count = gamepad_cmd.get("joysticks_count", 0) if gamepad_cmd else 0 elif source == "Random Walk": # Generate new random direction vector periodically if now - input_config["last_random_time"] > input_config["random_interval"]: input_config["random_vx"] = round(random.uniform(-1.0, 1.0), 2) input_config["random_vy"] = round(random.uniform(-1.0, 1.0), 2) input_config["random_omega"] = round(random.uniform(-1.0, 1.0), 2) input_config["last_random_time"] = now vx = input_config["random_vx"] vy = input_config["random_vy"] omega = input_config["random_omega"] joy_count = gamepad_cmd.get("joysticks_count", 0) if gamepad_cmd else 0 else: # "Gamepad" if gamepad_cmd: vx, vy, omega = gamepad_cmd.get("dirmov", [0.0, 0.0, 0.0]) joy_count = gamepad_cmd.get("joysticks_count", 0) else: vx, vy, omega, joy_count = 0.0, 0.0, 0.0, 0 state = "walking" if (vx != 0.0 or vy != 0.0 or omega != 0.0) else "idle" # Update GUI Labels if dpg.is_dearpygui_running(): dpg.set_value("txt_joy_count", f"Joysticks Connected: {joy_count}") dpg.set_value("txt_motion_state", f"Motion State: {state.upper()} ({source})") dpg.set_value("txt_vx", f"Active Vx : {vx:>6.2f}") dpg.set_value("txt_vy", f"Active Vy : {vy:>6.2f}") dpg.set_value("txt_omega", f"Active Omega: {omega:>6.2f}") return { "state": state, "dirmov": [vx, vy, omega] } def render_gui_frame(): if dpg.is_dearpygui_running(): dpg.render_dearpygui_frame() def stop_gui(): dpg.destroy_context()