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
{
///
/// 图像传输配置UI控制器
/// - 提供分辨率、质量、上报时间间隔、传输开关等控件
/// - 将配置同步到 Bridge:Python 链路(开关/间隔)+ 硬件 0x40 参数下发(由 Bridge 统一管理)
///
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 onConfigChanged;
private void Start()
{
if (bridge == null && LightGlueManager.Instance != null)
bridge = LightGlueManager.Instance.bridge;
if (romaBridge == null && RomaManager.Instance != null)
romaBridge = FindObjectOfType();
// 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);
}
}
///
/// 初始化UI组件,从配置加载当前值
///
private void InitializeUI()
{
// 初始化分辨率下拉菜单(与硬件分辨率枚举对应)
if (resolutionDropdown != null)
{
resolutionDropdown.ClearOptions();
resolutionDropdown.AddOptions(new System.Collections.Generic.List
{
"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;
}
}
///
/// 设置UI事件监听器
///
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);
}
}
///
/// 分辨率下拉菜单变更处理
///
private void OnResolutionChanged(int value)
{
if (value >= 0 && value <= 7)
{
config.resolution = (ImageResolution)value;
NotifyConfigChanged();
SaveToLocal();
Debug.Log($"[UI] 分辨率已更改为: {config.GetResolutionString()}");
}
}
///
/// 图片质量输入框变更处理
///
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();
}
}
}
///
/// 上报时间间隔输入框变更处理
///
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();
}
}
}
///
/// 传输开关变更处理
///
private void OnTransmissionToggleChanged(bool value)
{
config.enableImageTransmission = value;
NotifyConfigChanged();
SaveToLocal();
Debug.Log($"[UI] 图片传输已{(value ? "开启" : "关闭")}");
}
///
/// 通知配置已变更
///
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) 硬件配置不在每次修改时立刻下发,
// 而是等用户点击“应用配置”按钮时一次性下发。
}
///
/// “应用配置”按钮点击:通过 Bridge 将当前配置以 0x40 帧下发给硬件,并同步到 Python 链路
///
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");
}
}
///
/// 从本地(PlayerPrefs)加载配置;若没有记录则保持当前 Inspector 默认值。
///
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;
}
}
///
/// 保存当前配置到本地(PlayerPrefs)。
///
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();
}
///
/// 重置为默认配置:保存到本地,并立刻同步到 Bridge + 下发硬件 0x40(供 Button 调用)。
///
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");
}
}
///
/// 获取当前配置(供外部访问)
///
public ImageTransmissionConfig GetConfig()
{
return config;
}
///
/// 设置配置(供外部设置)
///
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);
}
}
}
}