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;
}
}
}
}