using System.Collections.Generic; using TMPro; using UnityEngine; using UnityEngine.UI; using LightGlue.Unity.Config; using LightGlue.Unity.Python; using System.Collections; using LightGlue.Unity.Roma.Networking; namespace LightGlue.Unity.Roma.UI { /// /// Roma 场景专用网络配置 UI。 /// 目标: /// - 复用 NetworkConfig 的 JSON 存取(NetworkConfigManager) /// - 复用本机 IP 获取(NetworkConfig.GetLocalBindIp) /// - 只管理 Roma 相关端口/参数,降低与旧 LightGlue_Deployment UI 的耦合 /// public sealed class RomaNetworkConfigUIController : MonoBehaviour { [Header("Config Panel (optional)")] [Tooltip("Roma 配置面板根节点(用于显示/隐藏整块 UI)。")] public GameObject configPanelRoot; [Header("Roma UI Fields")] [Tooltip("设备IP(从广播发现得到,仅显示/可手动改)")] public TMP_InputField deviceIpField; [Tooltip("ESP32 模式下的设备端口(供后续 ImageTransmissionUIController 下发硬件信息)。")] public TMP_InputField devicePortField; [Tooltip("本机IP(用于下发给 OrangePi 的 target_ip),不要填 127.0.0.1")] public TMP_InputField localTargetIpField; [Tooltip("OrangePi -> Python WiFi 图传端口({wifiPort},建议默认9000)")] public TMP_InputField romaWifiPortField; [Tooltip("Python -> Unity 图像转发端口({forwardPort},默认12366)")] public TMP_InputField romaForwardPortField; [Tooltip("Unity 结果接收绑定IP({resultIp},默认127.0.0.1)")] public TMP_InputField resultBindIpField; [Tooltip("Unity 结果接收端口({resultPort},默认12348)")] public TMP_InputField resultPortField; [Tooltip("Unity -> Python 控制端口({controlPort},默认12349)")] public TMP_InputField controlPortField; [Tooltip("Roma WiFi 启动参数模板(NetworkConfig.romaWifiPythonScriptArgs,支持占位符)")] public TMP_InputField romaArgsTemplateField; [Header("Hardware Mode (optional)")] [Tooltip("Roma 硬件模式选择(OrangePi/ESP32)。不绑定也可通过配置文件生效。")] public TMP_Dropdown hardwareModeDropdown; [Tooltip("按硬件模式切换可编辑性(TMP_InputField.interactable),而不是隐藏 UI。")] public bool useInteractableToggleByHardwareMode = true; [Header("Hardware Mode Visibility (optional lists)")] [Tooltip("ESP32 模式下需要显示的节点列表(会 SetActive(true))。")] public List showInEsp32Mode = new List(); [Tooltip("OrangePi 模式下需要显示的节点列表(会 SetActive(true))。")] public List showInOrangePiMode = new List(); [Tooltip("是否在切换模式时,自动把“非本模式列表”的节点隐藏(SetActive(false))。")] public bool hideOtherModeNodes = true; [Header("Buttons")] public Button startListenButton; public Button stopListenButton; public Button sendConfigButton; public Button streamOnButton; public Button streamOffButton; public Button fillLocalIpButton; public Button loadButton; public Button saveButton; public Button applyButton; [Header("Toggles")] public Toggle autoStartToggle; public Toggle noDisplayToggle; [Header("Status")] public Text statusText; [Header("Discovery Behavior")] [Tooltip("首次发现设备IP后是否自动停止监听广播,避免重复触发/刷屏。")] public bool stopListeningAfterFirstDiscover = true; [Header("After Config Behavior")] [Tooltip("发送图传 config 后是否自动启动 Roma Python(调用 RomaManager.StartPython)。")] public bool autoStartPythonAfterSendConfig = true; [Tooltip("发送图传 config 后是否自动启动 Unity 侧转发图像接收(12366)。")] public bool autoStartForwardViewerAfterSendConfig = true; [Tooltip("发送图传 config 后是否自动启动 Unity 侧结果接收(12348)。")] public bool autoStartResultReceiverAfterSendConfig = true; [Header("ESP32 Mode")] [Tooltip("当 romaHardwareMode=ESP32 时,进入场景是否自动启动 Python/显示/结果接收(无需下发图传配置)。")] public bool autoStartEsp32PipelineOnStart = true; [Tooltip("ESP32 模式下:若检测到 Python 已在运行,是否自动确保结果接收器(12348)已启动。")] public bool esp32AutoEnsureResultReceiverWhenPythonRunning = true; [Tooltip("ESP32 模式下自动检查间隔(秒)。")] public float esp32EnsureIntervalSeconds = 0.5f; [Header("Apply Targets (optional)")] [Tooltip("RomaManager(跨场景常驻)。为空则自动找 RomaManager.Instance")] public RomaManager romaManager; [Tooltip("Roma 用 PythonProcessController(建议挂在 RomaSystem 上)。为空则从 RomaManager 上找。")] public PythonProcessController romaPythonController; private NetworkConfig _cfg; private string _lastDiscoveredDeviceIp; private string _lastConfigSentKey; private bool _didInitialSyncDeviceIp; private bool _ignoreNextWifiConfigSent; // ESP32 模式下避免重复自动启动 private bool _didAutoStartEsp32; private float _lastEsp32EnsureTime; [Header("Apply Behavior")] [Tooltip("点击 Apply 后,如果 Roma Python 正在运行,是否自动重启以应用新参数/端口。")] public bool restartPythonAfterApply = true; [Tooltip("重启 Python 的延迟(秒),给端口释放一点时间。")] public float restartPythonDelaySeconds = 0.2f; private void Start() { if (romaManager == null) romaManager = RomaManager.Instance; if (romaPythonController == null && romaManager != null) romaPythonController = romaManager.romaPythonController; LoadFromFile(); SetupUi(); HookEvents(); BindRuntimeEvents(); SyncLastDeviceIpToUi(force: true); TryAutoStartEsp32Pipeline(); } private void OnEnable() { // 场景切换/对象重新启用时也要保证订阅存在 BindRuntimeEvents(); SyncLastDeviceIpToUi(force: false); TryAutoStartEsp32Pipeline(); } private void Update() { // 关键修复:你现在遇到的是 Python 已启动,但结果接收器未启动,导致“不点配置就没坐标” if (!esp32AutoEnsureResultReceiverWhenPythonRunning) return; if (_cfg == null) return; if (_cfg.romaHardwareMode != NetworkConfig.RomaHardwareMode.Esp32) return; if (Time.time - _lastEsp32EnsureTime < esp32EnsureIntervalSeconds) return; _lastEsp32EnsureTime = Time.time; if (romaManager == null) romaManager = RomaManager.Instance; if (romaManager == null) return; if (romaPythonController == null) romaPythonController = romaManager.romaPythonController; if (romaPythonController == null || !romaPythonController.IsRunning) return; // 确保 12348 有监听者 var bridge = FindObjectOfType(); if (bridge != null) { bridge.EnsureResultReceiverStarted(); } else { romaManager.StartResultReceiver(); } } private void OnDisable() { // UI 组件禁用时解除订阅,避免重复绑定 if (romaManager != null && romaManager.discovery != null) romaManager.discovery.OnDeviceIpDiscovered -= OnDeviceIpDiscovered; if (romaManager != null && romaManager.wifiControl != null) romaManager.wifiControl.OnConfigSent -= OnWifiConfigSent; } private void BindRuntimeEvents() { if (romaManager == null) romaManager = RomaManager.Instance; if (romaManager == null) return; // 绑定广播发现:发现到设备IP后写入 UI(但不自动下发图传) if (romaManager.discovery != null) { romaManager.discovery.OnDeviceIpDiscovered -= OnDeviceIpDiscovered; romaManager.discovery.OnDeviceIpDiscovered += OnDeviceIpDiscovered; } // 绑定 WiFi 控制:无论是 UI 点击下发还是“自动下发”,都能触发后续启动流程 if (romaManager.wifiControl != null) { romaManager.wifiControl.OnConfigSent -= OnWifiConfigSent; romaManager.wifiControl.OnConfigSent += OnWifiConfigSent; } // 绑定设备信息上报(Python -> Unity, JSON) if (romaManager.deviceInfoReceiver != null) { romaManager.deviceInfoReceiver.OnDeviceInfoUpdated -= OnDeviceInfoUpdatedFromPython; romaManager.deviceInfoReceiver.OnDeviceInfoUpdated += OnDeviceInfoUpdatedFromPython; } } private void OnDeviceInfoUpdatedFromPython(string ip, int srcPort) { if (_cfg == null) _cfg = NetworkConfig.CreateDefault(); if (_cfg.romaHardwareMode != NetworkConfig.RomaHardwareMode.Esp32) return; // ESP32 模式下:自动回填 deviceIp(端口保留用户配置的“控制端口”,不强制覆盖) if (deviceIpField != null && (string.IsNullOrWhiteSpace(deviceIpField.text) || deviceIpField.text.Trim() != ip)) deviceIpField.text = ip; // devicePortField:仅当为空/无效时填默认 8888(避免覆盖用户手动设置) if (devicePortField != null) { string s = devicePortField.text; if (string.IsNullOrWhiteSpace(s) || !int.TryParse(s.Trim(), out int p) || p <= 0 || p > 65535) { devicePortField.text = "8888"; } } if (string.IsNullOrWhiteSpace(_cfg.romaEsp32DeviceIp) || _cfg.romaEsp32DeviceIp != ip) _cfg.romaEsp32DeviceIp = ip; if (_cfg.romaEsp32DevicePort <= 0 || _cfg.romaEsp32DevicePort > 65535) _cfg.romaEsp32DevicePort = 8888; } private void SyncLastDeviceIpToUi(bool force) { if (!force && _didInitialSyncDeviceIp) return; if (romaManager == null) romaManager = RomaManager.Instance; if (romaManager == null || romaManager.discovery == null) return; string ip = romaManager.discovery.LastDeviceIp; if (string.IsNullOrWhiteSpace(ip)) return; if (deviceIpField != null && (force || string.IsNullOrWhiteSpace(deviceIpField.text))) deviceIpField.text = ip; // 走同一条逻辑,确保状态文本与“自动停止监听”等行为一致 OnDeviceIpDiscovered(ip); _didInitialSyncDeviceIp = true; } private void SetupUi() { if (romaWifiPortField != null) romaWifiPortField.contentType = TMP_InputField.ContentType.IntegerNumber; if (romaForwardPortField != null) romaForwardPortField.contentType = TMP_InputField.ContentType.IntegerNumber; if (resultPortField != null) resultPortField.contentType = TMP_InputField.ContentType.IntegerNumber; if (controlPortField != null) controlPortField.contentType = TMP_InputField.ContentType.IntegerNumber; if (devicePortField != null) devicePortField.contentType = TMP_InputField.ContentType.IntegerNumber; if (romaArgsTemplateField != null) { romaArgsTemplateField.lineType = TMP_InputField.LineType.MultiLineNewline; romaArgsTemplateField.textComponent.enableWordWrapping = true; } if (hardwareModeDropdown != null) { hardwareModeDropdown.ClearOptions(); hardwareModeDropdown.AddOptions(new System.Collections.Generic.List { "OrangePi", "ESP32" }); hardwareModeDropdown.onValueChanged.RemoveListener(OnHardwareModeChanged); hardwareModeDropdown.onValueChanged.AddListener(OnHardwareModeChanged); } UpdateUiFromConfig(force: true); } private void OnHardwareModeChanged(int _) { if (_cfg == null) _cfg = NetworkConfig.CreateDefault(); ReadUiIntoConfig(); UpdateHardwareModeInteractable(); UpdateHardwareModeVisibility(); } private void HookEvents() { if (startListenButton != null) startListenButton.onClick.AddListener(StartListening); if (stopListenButton != null) stopListenButton.onClick.AddListener(StopListening); if (sendConfigButton != null) sendConfigButton.onClick.AddListener(OnSendConfigClicked); if (streamOnButton != null) streamOnButton.onClick.AddListener(StreamOn); if (streamOffButton != null) streamOffButton.onClick.AddListener(StreamOff); if (fillLocalIpButton != null) fillLocalIpButton.onClick.AddListener(FillLocalIp); if (loadButton != null) loadButton.onClick.AddListener(LoadFromFile); if (saveButton != null) saveButton.onClick.AddListener(SaveToFile); if (applyButton != null) applyButton.onClick.AddListener(ApplyToRuntime); if (autoStartToggle != null) autoStartToggle.onValueChanged.AddListener(OnAutoStartChanged); if (noDisplayToggle != null) noDisplayToggle.onValueChanged.AddListener(OnNoDisplayChanged); } private void OnSendConfigClicked() { if (_cfg == null) _cfg = NetworkConfig.CreateDefault(); ReadUiIntoConfig(); // ESP32:无需下发图传配置,直接启动/重启 Python 流程 if (_cfg.romaHardwareMode == NetworkConfig.RomaHardwareMode.Esp32) { StartEsp32Pipeline(); return; } SendConfigToDevice(fromAuto: false); } private void OnDeviceIpDiscovered(string ip) { if (string.IsNullOrWhiteSpace(ip)) return; // 去重:同一个 IP 连续广播不重复刷 UI/日志 if (!string.IsNullOrWhiteSpace(_lastDiscoveredDeviceIp) && _lastDiscoveredDeviceIp == ip) return; _lastDiscoveredDeviceIp = ip; if (deviceIpField != null) deviceIpField.text = ip; bool autoCfg = false; bool autoStream = false; if (romaManager != null && romaManager.wifiControl != null) { autoCfg = romaManager.wifiControl.autoConfigAndStartOnDeviceDiscovered; autoStream = autoCfg; // 该开关语义为 config + stream_on } if (autoCfg) UpdateStatus($"发现设备IP: {ip}(已启用自动下发图传配置{(autoStream ? "+开流" : "")},请查看 [RomaWiFiCtrl] 日志确认发送结果)"); else UpdateStatus($"发现设备IP: {ip}(未自动下发图传,请点击配置/开启按钮)"); // 自动模式:OrangePi 才需要下发图传配置;ESP32 不走这条路径 if (autoCfg) { if (_cfg == null) _cfg = NetworkConfig.CreateDefault(); // 仅 OrangePi 模式:统一走 SendConfigToDevice(确保使用 localTargetIpField 的“记录值”) if (_cfg.romaHardwareMode == NetworkConfig.RomaHardwareMode.OrangePi) { // 防止 wifiControl 也在 discovery 回调里自动下发造成重复:已在 wifiControl.autoConfigHandledByUi=true 时阻止 SendConfigToDevice(fromAuto: true); } } if (stopListeningAfterFirstDiscover) { try { romaManager?.discovery?.StopListening(); } catch { // ignore } UpdateStatus($"发现设备IP: {ip},已自动停止监听广播"); } } public void StartListening() { if (romaManager == null) romaManager = RomaManager.Instance; if (romaManager == null || romaManager.discovery == null) { UpdateStatus("未找到 RomaManager 或 discovery,无法开始监听广播"); return; } romaManager.discovery.StartListening(); UpdateStatus("已开始监听广播(12345),等待设备IP..."); } public void StopListening() { if (romaManager == null) romaManager = RomaManager.Instance; romaManager?.discovery?.StopListening(); UpdateStatus("已停止监听广播"); } public void SendConfigToDevice() { SendConfigToDevice(fromAuto: false); } private void SendConfigToDevice(bool fromAuto) { if (romaManager == null) romaManager = RomaManager.Instance; if (romaManager == null || romaManager.wifiControl == null) { UpdateStatus("未找到 RomaManager 或 wifiControl,无法发送配置"); return; } string deviceIp = deviceIpField != null ? deviceIpField.text.Trim() : romaManager.wifiControl.deviceIp; if (!string.IsNullOrWhiteSpace(deviceIp)) romaManager.wifiControl.deviceIp = deviceIp; // 无论自动/手动,都以 UI 里“记录的” localTargetIpField 为准 string targetIp = localTargetIpField != null ? localTargetIpField.text.Trim() : string.Empty; if (string.IsNullOrWhiteSpace(targetIp)) { // 若用户尚未手动填写,给一个合理默认,避免自动下发时 Validate 直接失败 targetIp = NetworkConfig.GetLocalBindIp(); if (localTargetIpField != null) localTargetIpField.text = targetIp; } if (!string.IsNullOrWhiteSpace(targetIp)) romaManager.wifiControl.targetIp = targetIp; if (_cfg == null) _cfg = NetworkConfig.CreateDefault(); ReadUiIntoConfig(); if (_cfg.romaHardwareMode == NetworkConfig.RomaHardwareMode.Esp32) { UpdateStatus("当前为 ESP32 模式:无需下发 OrangePi 图传配置。"); StartEsp32Pipeline(); return; } romaManager.wifiControl.targetPort = _cfg.romaWifiListenPort; // 该次下发由 UI 主动触发,避免 OnConfigSent 回调里重复 StartAfterConfig _ignoreNextWifiConfigSent = true; romaManager.wifiControl.ConfigStream(romaManager.wifiControl.targetIp, romaManager.wifiControl.targetPort); UpdateStatus($"已发送图传 config -> {romaManager.wifiControl.deviceIp}:8008, target={romaManager.wifiControl.targetIp}:{romaManager.wifiControl.targetPort}"); StartAfterConfig( deviceIp: romaManager.wifiControl.deviceIp, targetIp: romaManager.wifiControl.targetIp, targetPort: romaManager.wifiControl.targetPort, fromAutoConfig: fromAuto); // 若勾选了“自动下发 config+开流”,这里也按设备端期望发 stream_on if (fromAuto && romaManager.wifiControl.autoConfigAndStartOnDeviceDiscovered) { int ms = romaManager.wifiControl.delayMsBetweenConfigAndOn; if (ms > 0) StartCoroutine(StreamOnAfterDelay(ms / 1000f)); else StreamOn(); } // 发送配置后:自动启动 Python 接收(手动流程中仅在此处自动启动,避免一进入场景就起进程) if (romaPythonController == null && romaManager != null) romaPythonController = romaManager.romaPythonController; if (romaPythonController != null) { romaPythonController.useRomaWifiArgsFromNetworkConfig = true; romaPythonController.addNoDisplayWhenLaunching = _cfg.pythonNoDisplay; // 默认切到 Unity bridge 入口脚本(低耦合,不再使用 demo_roma_camera_position_async_wifi.py) // 仅当当前 scriptPath 为空或仍指向旧 wifi 脚本时才覆盖,避免用户手动指定被强行改掉。 string p = (romaPythonController.scriptPath ?? string.Empty).Replace("\\", "/").ToLowerInvariant(); if (string.IsNullOrWhiteSpace(p) || p.EndsWith("demo_roma_camera_position_async_wifi.py") || p.EndsWith("/demo_roma_camera_position_async_wifi.py")) { romaPythonController.scriptPath = "demo/demo_roma_camera_position_async_unity_bridge.py"; } } // 启动动作已统一收敛到 StartAfterConfig() } private void OnWifiConfigSent(string targetIp, int targetPort) { if (romaManager == null) romaManager = RomaManager.Instance; if (romaManager == null || romaManager.wifiControl == null) return; if (_ignoreNextWifiConfigSent) { _ignoreNextWifiConfigSent = false; return; } string deviceIp = romaManager.wifiControl.deviceIp; string key = $"{deviceIp}|{targetIp}|{targetPort}"; if (!string.IsNullOrWhiteSpace(_lastConfigSentKey) && _lastConfigSentKey == key) return; _lastConfigSentKey = key; // 如果是“自动下发”,这里补齐 UI 侧的启动流程与状态提示 if (romaManager.wifiControl.autoConfigAndStartOnDeviceDiscovered) { UpdateStatus($"已自动发送图传 config -> {deviceIp}:8008, target={targetIp}:{targetPort}"); StartAfterConfig(deviceIp, targetIp, targetPort, fromAutoConfig: true); } } private void StartAfterConfig(string deviceIp, string targetIp, int targetPort, bool fromAutoConfig) { if (romaManager == null) romaManager = RomaManager.Instance; if (romaManager == null) return; // 这里负责“真正开启”:Python / 图像显示 / 结果接收 if (autoStartPythonAfterSendConfig) romaManager.StartPython(); if (autoStartForwardViewerAfterSendConfig) romaManager.StartForwardViewer(); if (autoStartResultReceiverAfterSendConfig) { // 优先用 RomaHardwareToPythonBridge 的结果接收(避免和 RomaManager 抢占 12348) var bridge = FindObjectOfType(); if (bridge != null) bridge.EnsureResultReceiverStarted(); else romaManager.StartResultReceiver(); } if (fromAutoConfig) Debug.Log($"[RomaNetUI] Auto-start after config: python={(autoStartPythonAfterSendConfig ? 1 : 0)} viewer={(autoStartForwardViewerAfterSendConfig ? 1 : 0)} result={(autoStartResultReceiverAfterSendConfig ? 1 : 0)}"); } public void StreamOn() { if (romaManager == null) romaManager = RomaManager.Instance; romaManager?.wifiControl?.StartStream(); UpdateStatus("已发送 stream_on"); } public void StreamOff() { if (romaManager == null) romaManager = RomaManager.Instance; romaManager?.wifiControl?.StopStream(); UpdateStatus("已发送 stream_off"); } public void FillLocalIp() { string ip = NetworkConfig.GetLocalBindIp(); if (localTargetIpField != null) localTargetIpField.text = ip; UpdateStatus($"本机IP已填充: {ip}"); } public void LoadFromFile() { _cfg = NetworkConfigManager.LoadConfig(); UpdateUiFromConfig(force: true); UpdateStatus($"Roma 配置已加载: {NetworkConfigManager.GetConfigPath()}"); } public void SaveToFile() { if (_cfg == null) _cfg = NetworkConfig.CreateDefault(); ReadUiIntoConfig(); NetworkConfigManager.SaveConfig(_cfg); UpdateStatus($"Roma 配置已保存: {NetworkConfigManager.GetConfigPath()}"); } public void ApplyToRuntime() { if (_cfg == null) _cfg = NetworkConfig.CreateDefault(); ReadUiIntoConfig(); // 1) Roma WiFi 控制:下发目标IP/端口 if (romaManager == null) romaManager = RomaManager.Instance; if (romaManager != null && romaManager.wifiControl != null) { string targetIp = localTargetIpField != null ? localTargetIpField.text.Trim() : string.Empty; if (!string.IsNullOrWhiteSpace(targetIp)) romaManager.wifiControl.targetIp = targetIp; romaManager.wifiControl.targetPort = _cfg.romaWifiListenPort; } // 2) Unity 显示端口(Python->Unity) if (romaManager != null && romaManager.forwardedViewer != null) { romaManager.forwardedViewer.forwardPort = _cfg.romaForwardPort; } // 3) RomaManager 结果接收端口/IP if (romaManager != null) { romaManager.resultBindIp = _cfg.pythonResultBindIp; romaManager.resultPort = _cfg.pythonResultPort; romaManager.maxResultQueueSize = 10; } // 4) Python 启动参数:Roma 模板 if (romaPythonController == null && romaManager != null) romaPythonController = romaManager.romaPythonController; if (romaPythonController != null) { romaPythonController.useRomaWifiArgsFromNetworkConfig = true; romaPythonController.addNoDisplayWhenLaunching = _cfg.pythonNoDisplay; if (restartPythonAfterApply && romaPythonController.IsRunning) { StartCoroutine(RestartPythonCoroutine()); } } // Apply 同时保存到配置文件,确保下次启动保持一致 NetworkConfigManager.SaveConfig(_cfg); UpdateStatus("Roma 配置已应用到运行时组件(如需持久化请点保存)"); HideConfigPanel(); } private IEnumerator StreamOnAfterDelay(float seconds) { if (seconds > 0f) yield return new WaitForSeconds(seconds); StreamOn(); } private IEnumerator RestartPythonCoroutine() { if (romaPythonController == null) yield break; UpdateStatus("检测到 Python 正在运行:将自动重启以应用新配置..."); romaPythonController.StopPython(); if (restartPythonDelaySeconds > 0f) yield return new WaitForSeconds(restartPythonDelaySeconds); romaPythonController.StartPython(); } private void TryAutoStartEsp32Pipeline() { if (_didAutoStartEsp32) return; if (!autoStartEsp32PipelineOnStart) return; if (_cfg == null) return; if (_cfg.romaHardwareMode != NetworkConfig.RomaHardwareMode.Esp32) return; _didAutoStartEsp32 = true; StartEsp32Pipeline(); } private void StartEsp32Pipeline() { if (romaManager == null) romaManager = RomaManager.Instance; if (romaManager == null) { UpdateStatus("未找到 RomaManager,无法启动 ESP32 流程"); return; } if (romaPythonController == null) romaPythonController = romaManager.romaPythonController; // ESP32:无下发配置,直接启动 Python / 显示 / 结果接收 if (autoStartPythonAfterSendConfig) romaManager.StartPython(); if (autoStartForwardViewerAfterSendConfig) romaManager.StartForwardViewer(); if (autoStartResultReceiverAfterSendConfig) { var bridge = FindObjectOfType(); if (bridge != null) bridge.EnsureResultReceiverStarted(); else romaManager.StartResultReceiver(); } UpdateStatus("ESP32 模式:已启动 Python/显示/结果接收(无需下发图传配置)"); } private void OnAutoStartChanged(bool v) { if (_cfg == null) _cfg = NetworkConfig.CreateDefault(); _cfg.autoStartOnPlay = v; } private void OnNoDisplayChanged(bool v) { if (_cfg == null) _cfg = NetworkConfig.CreateDefault(); _cfg.pythonNoDisplay = v; if (romaPythonController != null) romaPythonController.addNoDisplayWhenLaunching = v; } private void UpdateUiFromConfig(bool force) { if (_cfg == null) _cfg = NetworkConfig.CreateDefault(); // localTargetIp:优先使用上次保存的配置;仅当为空时才退回自动获取本机IP if (localTargetIpField != null && (force || string.IsNullOrWhiteSpace(localTargetIpField.text))) { string ip = !string.IsNullOrWhiteSpace(_cfg.romaLocalTargetIp) ? _cfg.romaLocalTargetIp.Trim() : NetworkConfig.GetLocalBindIp(); localTargetIpField.text = ip; } if (romaWifiPortField != null) romaWifiPortField.text = _cfg.romaWifiListenPort.ToString(); if (romaForwardPortField != null) romaForwardPortField.text = _cfg.romaForwardPort.ToString(); if (resultBindIpField != null) resultBindIpField.text = _cfg.pythonResultBindIp; if (resultPortField != null) resultPortField.text = _cfg.pythonResultPort.ToString(); if (controlPortField != null) controlPortField.text = _cfg.romaControlPort.ToString(); if (romaArgsTemplateField != null) romaArgsTemplateField.text = _cfg.romaWifiPythonScriptArgs; if (autoStartToggle != null) autoStartToggle.isOn = _cfg.autoStartOnPlay; if (noDisplayToggle != null) noDisplayToggle.isOn = _cfg.pythonNoDisplay; if (hardwareModeDropdown != null) hardwareModeDropdown.value = (int)_cfg.romaHardwareMode; // ESP32 设备信息:仅 ESP32 模式需要填写/持久化(字段开放可编辑) if (deviceIpField != null && (force || string.IsNullOrWhiteSpace(deviceIpField.text))) deviceIpField.text = _cfg.romaEsp32DeviceIp; if (devicePortField != null) devicePortField.text = _cfg.romaEsp32DevicePort.ToString(); UpdateHardwareModeInteractable(); UpdateHardwareModeVisibility(); } private void ReadUiIntoConfig() { if (_cfg == null) _cfg = NetworkConfig.CreateDefault(); if (localTargetIpField != null && !string.IsNullOrWhiteSpace(localTargetIpField.text)) _cfg.romaLocalTargetIp = localTargetIpField.text.Trim(); if (hardwareModeDropdown != null) { int v = hardwareModeDropdown.value; _cfg.romaHardwareMode = (v == 1) ? NetworkConfig.RomaHardwareMode.Esp32 : NetworkConfig.RomaHardwareMode.OrangePi; } if (_cfg.romaHardwareMode == NetworkConfig.RomaHardwareMode.Esp32) { if (deviceIpField != null && !string.IsNullOrWhiteSpace(deviceIpField.text)) _cfg.romaEsp32DeviceIp = deviceIpField.text.Trim(); _cfg.romaEsp32DevicePort = ParsePortOrKeep(devicePortField, _cfg.romaEsp32DevicePort); } _cfg.romaWifiListenPort = ParsePortOrKeep(romaWifiPortField, _cfg.romaWifiListenPort); _cfg.romaForwardPort = ParsePortOrKeep(romaForwardPortField, _cfg.romaForwardPort); if (resultBindIpField != null && !string.IsNullOrWhiteSpace(resultBindIpField.text)) _cfg.pythonResultBindIp = resultBindIpField.text.Trim(); _cfg.pythonResultPort = ParsePortOrKeep(resultPortField, _cfg.pythonResultPort); _cfg.romaControlPort = ParsePortOrKeep(controlPortField, _cfg.romaControlPort); if (romaArgsTemplateField != null && !string.IsNullOrWhiteSpace(romaArgsTemplateField.text)) _cfg.romaWifiPythonScriptArgs = romaArgsTemplateField.text.Trim(); if (autoStartToggle != null) _cfg.autoStartOnPlay = autoStartToggle.isOn; if (noDisplayToggle != null) _cfg.pythonNoDisplay = noDisplayToggle.isOn; } private void UpdateHardwareModeInteractable() { if (_cfg == null) return; bool isEsp32 = _cfg.romaHardwareMode == NetworkConfig.RomaHardwareMode.Esp32; if (!useInteractableToggleByHardwareMode) return; // ESP32:设备IP/端口可编辑;OrangePi:只读(通常由广播发现/旧流程决定) if (deviceIpField != null) deviceIpField.interactable = isEsp32; if (devicePortField != null) devicePortField.interactable = isEsp32; // localTargetIpField 始终可编辑(不随硬件模式置灰) } private void UpdateHardwareModeVisibility() { if (_cfg == null) return; bool isEsp32 = _cfg.romaHardwareMode == NetworkConfig.RomaHardwareMode.Esp32; // 显示本模式列表 List show = isEsp32 ? showInEsp32Mode : showInOrangePiMode; if (show != null) { for (int i = 0; i < show.Count; i++) { var go = show[i]; if (go != null) go.SetActive(true); } } // 可选:隐藏另一个模式列表 if (!hideOtherModeNodes) return; List hide = isEsp32 ? showInOrangePiMode : showInEsp32Mode; if (hide != null) { for (int i = 0; i < hide.Count; i++) { var go = hide[i]; if (go != null) go.SetActive(false); } } } private static int ParsePortOrKeep(TMP_InputField field, int keep) { if (field == null) return keep; string s = field.text; if (string.IsNullOrWhiteSpace(s)) return keep; if (!int.TryParse(s.Trim(), out int p)) return keep; if (p <= 0 || p > 65535) return keep; return p; } private void UpdateStatus(string msg) { if (statusText != null) statusText.text = msg; Debug.Log($"[RomaNetUI] {msg}"); } /// /// 显示配置面板 /// public void ShowConfigPanel() { if (configPanelRoot != null) { configPanelRoot.SetActive(true); Debug.Log("[RomaNetUI] 配置面板已显示"); } } /// /// 隐藏配置面板 /// public void HideConfigPanel() { if (configPanelRoot != null) { configPanelRoot.SetActive(false); Debug.Log("[RomaNetUI] 配置面板已隐藏"); } } /// /// 切换配置面板显示/隐藏状态 /// public void ToggleConfigPanel() { if (configPanelRoot != null) { bool newState = !configPanelRoot.activeSelf; configPanelRoot.SetActive(newState); Debug.Log($"[RomaNetUI] 配置面板已{(newState ? "显示" : "隐藏")}"); } } private void OnDestroy() { if (romaManager != null && romaManager.discovery != null) { romaManager.discovery.OnDeviceIpDiscovered -= OnDeviceIpDiscovered; } if (romaManager != null && romaManager.wifiControl != null) { romaManager.wifiControl.OnConfigSent -= OnWifiConfigSent; } if (romaManager != null && romaManager.deviceInfoReceiver != null) { romaManager.deviceInfoReceiver.OnDeviceInfoUpdated -= OnDeviceInfoUpdatedFromPython; } } } }