| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546 |
- using UnityEngine;
- using UnityEngine.UI;
- using TMPro;
- using LightGlue.Unity.Config;
- using LightGlue.Unity.Bridge;
- using LightGlue.Unity.Game;
- using LightGlue.Unity.Roma;
- using LightGlue.Unity.Roma.Bridge;
- using LightGlue.Unity.Sdk.Unity;
- using System.Collections;
- namespace LightGlue.Unity.UI
- {
- /// <summary>
- /// 图像传输配置UI控制器
- /// - 提供分辨率、质量、上报时间间隔、传输开关等控件
- /// - 将配置同步到 Bridge:Python 链路(开关/间隔)+ 硬件 0x40 参数下发(由 Bridge 统一管理)
- /// </summary>
- public class ImageTransmissionUIController : MonoBehaviour
- {
- private const string KeyPrefix = "LightGlue_ImageTx_";
- private const string KeyResolution = KeyPrefix + "Resolution";
- private const string KeyQuality = KeyPrefix + "Quality";
- private const string KeyInterval = KeyPrefix + "IntervalMs";
- private const string KeyEnabled = KeyPrefix + "Enabled";
- [Header("UI组件引用")]
- [Tooltip("图片分辨率下拉菜单")]
- public TMP_Dropdown resolutionDropdown;
- [Tooltip("图片质量输入框")]
- public TMP_InputField qualityInputField;
- [Tooltip("上报时间间隔输入框 (毫秒)")]
- public TMP_InputField intervalInputField;
- [Tooltip("开启图片传输开关(主要控制 Python 链路与硬件采图开关状态)")]
- public Toggle transmissionToggle;
- [Tooltip("应用配置按钮(点击时将当前配置一次性下发给硬件)")]
- public Button applyConfigButton;
- [Tooltip("重置为默认图像配置并立即下发硬件的按钮")]
- public Button resetConfigButton;
- [Header("配置")]
- [Tooltip("图像传输配置")]
- public ImageTransmissionConfig config = new ImageTransmissionConfig();
- [Header("硬件下发开关")]
- [Tooltip("是否启用硬件下发相关功能(0x40)。关闭后会隐藏下发按钮,且不会在启动/配置变更时触发任何硬件下发动作。")]
- public bool enableHardwareApply = true;
- [Tooltip("关闭硬件下发时,是否隐藏“应用配置/重置并下发”按钮。")]
- public bool hideHardwareApplyButtonsWhenDisabled = true;
- [Header("Roma: 下发后自动恢复链路")]
- [Tooltip("在 Roma(ESP32) 下发 0x40 后,是否自动重启 Roma Python(避免硬件重配置导致画面/结果暂时中断)。")]
- public bool restartRomaPythonAfterHardwareApply = true;
- [Tooltip("重启 Roma Python 的延迟(秒),给端口/硬件一点恢复时间。")]
- public float restartRomaPythonDelaySeconds = 0.25f;
- // 启动时记录的“场景默认配置”(来自 Inspector),仅用于 ResetToDefaultAndSave,不受本地记录覆盖影响。
- private ImageTransmissionConfig _defaultConfigSnapshot;
- [Header("本地记录")]
- [Tooltip("启动时是否从本地(PlayerPrefs)加载上次保存的图像传输配置")]
- public bool loadFromLocalOnStart = true;
- [Header("Bridge连接")]
- [Tooltip("Bridge(同步传输配置并下发 0x40 到硬件;可选,未设置时从 LightGlueManager 获取)")]
- public HardwareToPythonUdpBridge bridge;
- [Tooltip("Roma Bridge(用于 ESP32 模式下发 0x40 到硬件;可选,未设置时从 RomaManager 查找)")]
- public RomaHardwareToPythonBridge romaBridge;
- [Tooltip("LightGlue SDK 新桥接组件(可选,用于对比旧 Bridge 行为)")]
- public LightGlueBridgeBehaviour sdkBridge;
- [Header("事件")]
- [Tooltip("配置变更时触发的事件")]
- public UnityEngine.Events.UnityEvent<ImageTransmissionConfig> onConfigChanged;
- private void Start()
- {
- if (bridge == null && LightGlueManager.Instance != null)
- bridge = LightGlueManager.Instance.bridge;
- if (romaBridge == null && RomaManager.Instance != null)
- romaBridge = FindObjectOfType<RomaHardwareToPythonBridge>();
- // sdkBridge 一般通过 Inspector 指定;不自动从 Manager 获取,避免与旧 Bridge 冲突。
- // 1) 先记录当前 Inspector 中的 config 作为“默认值快照”(仅 Reset 使用)
- _defaultConfigSnapshot = new ImageTransmissionConfig
- {
- resolution = config.resolution,
- quality = config.quality,
- reportIntervalMs = config.reportIntervalMs,
- enableImageTransmission = config.enableImageTransmission
- };
- // 2) 再按需从本地记录覆盖运行时 config(不影响默认值快照)
- if (loadFromLocalOnStart)
- {
- LoadFromLocal();
- }
- InitializeUI();
- SetupEventListeners();
- // 关闭硬件下发时:隐藏按钮并跳过“启动时自动同步到 Bridge”(避免触发 0x40)
- if (!enableHardwareApply)
- {
- if (hideHardwareApplyButtonsWhenDisabled)
- {
- if (applyConfigButton != null) applyConfigButton.gameObject.SetActive(false);
- if (resetConfigButton != null) resetConfigButton.gameObject.SetActive(false);
- }
- Debug.Log("[UI] 硬件下发已禁用:将不会在启动/配置变更时触发 0x40 下发。");
- return;
- }
-
- // 初始化时同步配置到 Bridge(SetTransmissionConfig 内部会立即下发 0x40,保证硬件从第一帧起就用正确参数)
- if (bridge != null)
- {
- bridge.SetTransmissionConfig(config);
- bridge.MarkHardwareAutoApplyReady();
- }
- // 同步到新 SDK Bridge,便于对比测试
- if (sdkBridge != null)
- {
- sdkBridge.SetTransmissionConfig(config);
- }
- }
- /// <summary>
- /// 初始化UI组件,从配置加载当前值
- /// </summary>
- private void InitializeUI()
- {
- // 初始化分辨率下拉菜单(与硬件分辨率枚举对应)
- if (resolutionDropdown != null)
- {
- resolutionDropdown.ClearOptions();
- resolutionDropdown.AddOptions(new System.Collections.Generic.List<string>
- {
- "QQVGA 160x120",
- "QVGA 320x240",
- "VGA 640x480"
- //"SVGA 800x600",
- //"XGA 1024x768",
- //"HD 1280x720",
- //"SXGA 1280x1024",
- //"UXGA 1600x1200"
- });
- resolutionDropdown.value = (int)config.resolution;
- }
- // 初始化图片质量输入框(0-63)
- if (qualityInputField != null)
- {
- qualityInputField.text = config.quality.ToString();
- qualityInputField.contentType = TMP_InputField.ContentType.IntegerNumber;
- qualityInputField.characterLimit = 2; // 0-63,最多2位
- }
- // 初始化上报时间间隔输入框
- if (intervalInputField != null)
- {
- intervalInputField.text = config.reportIntervalMs.ToString();
- intervalInputField.contentType = TMP_InputField.ContentType.IntegerNumber;
- }
- // 初始化传输开关
- if (transmissionToggle != null)
- {
- transmissionToggle.isOn = config.enableImageTransmission;
- }
- }
- /// <summary>
- /// 设置UI事件监听器
- /// </summary>
- private void SetupEventListeners()
- {
- // 应用配置按钮
- if (applyConfigButton != null)
- {
- applyConfigButton.onClick.AddListener(OnApplyConfigButtonClicked);
- }
- if (resetConfigButton != null)
- {
- resetConfigButton.onClick.AddListener(ResetToDefaultAndSave);
- }
- // 分辨率下拉菜单变更事件
- if (resolutionDropdown != null)
- {
- resolutionDropdown.onValueChanged.AddListener(OnResolutionChanged);
- }
- // 图片质量输入框变更事件
- if (qualityInputField != null)
- {
- qualityInputField.onEndEdit.AddListener(OnQualityChanged);
- }
- // 上报时间间隔输入框变更事件
- if (intervalInputField != null)
- {
- intervalInputField.onEndEdit.AddListener(OnIntervalChanged);
- }
- // 传输开关变更事件
- if (transmissionToggle != null)
- {
- transmissionToggle.onValueChanged.AddListener(OnTransmissionToggleChanged);
- }
- }
- /// <summary>
- /// 分辨率下拉菜单变更处理
- /// </summary>
- private void OnResolutionChanged(int value)
- {
- if (value >= 0 && value <= 7)
- {
- config.resolution = (ImageResolution)value;
- NotifyConfigChanged();
- SaveToLocal();
- Debug.Log($"[UI] 分辨率已更改为: {config.GetResolutionString()}");
- }
- }
- /// <summary>
- /// 图片质量输入框变更处理
- /// </summary>
- private void OnQualityChanged(string value)
- {
- if (int.TryParse(value, out int quality))
- {
- // 限制在0-63范围内(与硬件一致)
- quality = Mathf.Clamp(quality, 0, 63);
- config.quality = quality;
-
- // 如果输入值被修正,更新输入框显示
- if (qualityInputField != null && qualityInputField.text != quality.ToString())
- {
- qualityInputField.text = quality.ToString();
- }
-
- NotifyConfigChanged();
- SaveToLocal();
- Debug.Log($"[UI] 图片质量已更改为: {quality}");
- }
- else if (!string.IsNullOrEmpty(value))
- {
- // 输入无效,恢复原值
- if (qualityInputField != null)
- {
- qualityInputField.text = config.quality.ToString();
- }
- }
- }
- /// <summary>
- /// 上报时间间隔输入框变更处理
- /// </summary>
- private void OnIntervalChanged(string value)
- {
- if (int.TryParse(value, out int interval))
- {
- // 限制最小值为0
- interval = Mathf.Max(0, interval);
- config.reportIntervalMs = interval;
-
- // 如果输入值被修正,更新输入框显示
- if (intervalInputField != null && intervalInputField.text != interval.ToString())
- {
- intervalInputField.text = interval.ToString();
- }
-
- NotifyConfigChanged();
- SaveToLocal();
- Debug.Log($"[UI] 上报时间间隔已更改为: {interval}ms");
- }
- else if (!string.IsNullOrEmpty(value))
- {
- // 输入无效,恢复原值
- if (intervalInputField != null)
- {
- intervalInputField.text = config.reportIntervalMs.ToString();
- }
- }
- }
- /// <summary>
- /// 传输开关变更处理
- /// </summary>
- private void OnTransmissionToggleChanged(bool value)
- {
- config.enableImageTransmission = value;
- NotifyConfigChanged();
- SaveToLocal();
- Debug.Log($"[UI] 图片传输已{(value ? "开启" : "关闭")}");
- }
- /// <summary>
- /// 通知配置已变更
- /// </summary>
- private void NotifyConfigChanged()
- {
- onConfigChanged?.Invoke(config);
- // 1) 同步到 Python Bridge:用于控制 Unity->Python 转发(toggle + interval等)
- if (enableHardwareApply && bridge != null)
- {
- bridge.SetTransmissionConfig(config);
- }
- // 1b) 同步到新 SDK Bridge
- if (sdkBridge != null)
- {
- sdkBridge.SetTransmissionConfig(config);
- }
- // 2) 硬件配置不在每次修改时立刻下发,
- // 而是等用户点击“应用配置”按钮时一次性下发。
- }
- /// <summary>
- /// “应用配置”按钮点击:通过 Bridge 将当前配置以 0x40 帧下发给硬件,并同步到 Python 链路
- /// </summary>
- private void OnApplyConfigButtonClicked()
- {
- if (!enableHardwareApply)
- {
- Debug.Log("[UI] 硬件下发已禁用:忽略“应用配置”点击。");
- return;
- }
- if (bridge != null)
- {
- bridge.SetTransmissionConfig(config);
- bridge.ApplyConfig(config);
- Debug.Log("[UI] 已将当前图像配置下发给硬件并同步到 Bridge");
- }
- else if (romaBridge != null)
- {
- romaBridge.ApplyEsp32HardwareConfig(config);
- Debug.Log("[UI][Roma] 已将当前图像配置以 0x40 下发给 ESP32(RomaBridge)");
- if (restartRomaPythonAfterHardwareApply)
- StartCoroutine(RestartRomaPythonCoroutine());
- }
- if (sdkBridge != null)
- {
- sdkBridge.SetTransmissionConfig(config);
- sdkBridge.ApplyHardwareConfig(config);
- Debug.Log("[UI] 已将当前图像配置下发给硬件并同步到 SDK Bridge");
- }
- }
- /// <summary>
- /// 从本地(PlayerPrefs)加载配置;若没有记录则保持当前 Inspector 默认值。
- /// </summary>
- public void LoadFromLocal()
- {
- if (PlayerPrefs.HasKey(KeyResolution))
- {
- int r = PlayerPrefs.GetInt(KeyResolution);
- if (r >= 0 && r <= 7)
- config.resolution = (ImageResolution)r;
- }
- if (PlayerPrefs.HasKey(KeyQuality))
- {
- config.quality = Mathf.Clamp(PlayerPrefs.GetInt(KeyQuality), 0, 63);
- }
- if (PlayerPrefs.HasKey(KeyInterval))
- {
- config.reportIntervalMs = Mathf.Max(0, PlayerPrefs.GetInt(KeyInterval));
- }
- if (PlayerPrefs.HasKey(KeyEnabled))
- {
- config.enableImageTransmission = PlayerPrefs.GetInt(KeyEnabled) != 0;
- }
- }
- /// <summary>
- /// 保存当前配置到本地(PlayerPrefs)。
- /// </summary>
- public void SaveToLocal()
- {
- PlayerPrefs.SetInt(KeyResolution, (int)config.resolution);
- PlayerPrefs.SetInt(KeyQuality, Mathf.Clamp(config.quality, 0, 63));
- PlayerPrefs.SetInt(KeyInterval, Mathf.Max(0, config.reportIntervalMs));
- PlayerPrefs.SetInt(KeyEnabled, config.enableImageTransmission ? 1 : 0);
- PlayerPrefs.Save();
- }
- /// <summary>
- /// 重置为默认配置:保存到本地,并立刻同步到 Bridge + 下发硬件 0x40(供 Button 调用)。
- /// </summary>
- public void ResetToDefaultAndSave()
- {
- // 1) 使用启动时记录的默认值快照重置配置(若快照为空则退回到 ImageTransmissionConfig 默认)
- ImageTransmissionConfig resetConfig;
- if (_defaultConfigSnapshot != null)
- {
- resetConfig = new ImageTransmissionConfig
- {
- resolution = _defaultConfigSnapshot.resolution,
- quality = _defaultConfigSnapshot.quality,
- reportIntervalMs = _defaultConfigSnapshot.reportIntervalMs,
- enableImageTransmission = _defaultConfigSnapshot.enableImageTransmission
- };
- }
- else
- {
- resetConfig = new ImageTransmissionConfig();
- }
- // 重置并刷新 UI + 同步到 Bridge(Unity->Python 链路)
- SetConfig(resetConfig);
- // 2) 保存到本地
- SaveToLocal();
- // 3) 立刻下发硬件 0x40(只做一次的自动下发逻辑是 Bridge 自己的,这里是“用户点重置”的显式下发)
- if (!enableHardwareApply)
- {
- Debug.Log("[UI] 硬件下发已禁用:已重置并保存本地,但不下发 0x40。");
- }
- else if (bridge != null)
- {
- bridge.SetTransmissionConfig(config);
- bridge.ApplyConfig(config);
- Debug.Log("[UI] 已重置图像配置:已保存本地,并立刻下发 0x40 到硬件 + 同步到 Bridge");
- }
- else if (romaBridge != null)
- {
- romaBridge.ApplyEsp32HardwareConfig(config);
- Debug.Log("[UI][Roma] 已重置图像配置:已保存本地,并立刻下发 0x40 到 ESP32(RomaBridge)");
- if (restartRomaPythonAfterHardwareApply)
- StartCoroutine(RestartRomaPythonCoroutine());
- }
- if (sdkBridge != null)
- {
- sdkBridge.SetTransmissionConfig(config);
- sdkBridge.ApplyHardwareConfig(config);
- Debug.Log("[UI] 已重置图像配置:已保存本地,并立刻下发 0x40 到硬件 + 同步到 SDK Bridge");
- }
- }
- /// <summary>
- /// 获取当前配置(供外部访问)
- /// </summary>
- public ImageTransmissionConfig GetConfig()
- {
- return config;
- }
- /// <summary>
- /// 设置配置(供外部设置)
- /// </summary>
- public void SetConfig(ImageTransmissionConfig newConfig)
- {
- if (newConfig == null) return;
- config = newConfig;
-
- // 更新UI显示
- if (resolutionDropdown != null)
- {
- resolutionDropdown.value = (int)config.resolution;
- }
- if (qualityInputField != null)
- {
- qualityInputField.text = config.quality.ToString();
- }
- if (intervalInputField != null)
- {
- intervalInputField.text = config.reportIntervalMs.ToString();
- }
- if (transmissionToggle != null)
- {
- transmissionToggle.isOn = config.enableImageTransmission;
- }
- NotifyConfigChanged();
- }
- private IEnumerator RestartRomaPythonCoroutine()
- {
- var romaMgr = RomaManager.Instance;
- if (romaMgr == null || romaMgr.romaPythonController == null) yield break;
- // 仅在运行中才重启,避免无意义启动
- if (!romaMgr.romaPythonController.IsRunning) yield break;
- Debug.Log("[UI][Roma] Hardware config applied, restarting Roma Python to recover streaming...");
- romaMgr.romaPythonController.StopPython();
- if (restartRomaPythonDelaySeconds > 0f)
- yield return new WaitForSeconds(restartRomaPythonDelaySeconds);
- romaMgr.romaPythonController.StartPython();
- }
- private void OnDestroy()
- {
- // 清理事件监听器
- if (applyConfigButton != null)
- {
- applyConfigButton.onClick.RemoveListener(OnApplyConfigButtonClicked);
- }
- if (resetConfigButton != null)
- {
- resetConfigButton.onClick.RemoveListener(ResetToDefaultAndSave);
- }
- if (resolutionDropdown != null)
- {
- resolutionDropdown.onValueChanged.RemoveListener(OnResolutionChanged);
- }
- if (qualityInputField != null)
- {
- qualityInputField.onEndEdit.RemoveListener(OnQualityChanged);
- }
- if (intervalInputField != null)
- {
- intervalInputField.onEndEdit.RemoveListener(OnIntervalChanged);
- }
- if (transmissionToggle != null)
- {
- transmissionToggle.onValueChanged.RemoveListener(OnTransmissionToggleChanged);
- }
- }
- }
- }
|