| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879 |
- 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
- {
- /// <summary>
- /// Roma 场景专用网络配置 UI。
- /// 目标:
- /// - 复用 NetworkConfig 的 JSON 存取(NetworkConfigManager)
- /// - 复用本机 IP 获取(NetworkConfig.GetLocalBindIp)
- /// - 只管理 Roma 相关端口/参数,降低与旧 LightGlue_Deployment UI 的耦合
- /// </summary>
- 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<GameObject> showInEsp32Mode = new List<GameObject>();
- [Tooltip("OrangePi 模式下需要显示的节点列表(会 SetActive(true))。")]
- public List<GameObject> showInOrangePiMode = new List<GameObject>();
- [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<LightGlue.Unity.Roma.Bridge.RomaHardwareToPythonBridge>();
- 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<string> { "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<LightGlue.Unity.Roma.Bridge.RomaHardwareToPythonBridge>();
- 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<LightGlue.Unity.Roma.Bridge.RomaHardwareToPythonBridge>();
- 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<GameObject> 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<GameObject> 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}");
- }
- /// <summary>
- /// 显示配置面板
- /// </summary>
- public void ShowConfigPanel()
- {
- if (configPanelRoot != null)
- {
- configPanelRoot.SetActive(true);
- Debug.Log("[RomaNetUI] 配置面板已显示");
- }
- }
- /// <summary>
- /// 隐藏配置面板
- /// </summary>
- public void HideConfigPanel()
- {
- if (configPanelRoot != null)
- {
- configPanelRoot.SetActive(false);
- Debug.Log("[RomaNetUI] 配置面板已隐藏");
- }
- }
- /// <summary>
- /// 切换配置面板显示/隐藏状态
- /// </summary>
- 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;
- }
- }
- }
- }
|