using System.Net; using System.Net.Sockets; using UnityEngine; using UnityEngine.Windows; namespace LightGlue.Unity.Config { /// /// 网络配置参数(统一管理所有IP和端口配置) /// 用于部署到不同设备时的配置管理 /// [System.Serializable] public class NetworkConfig { public enum RomaHardwareMode { OrangePi = 0, Esp32 = 1, } [Header("硬件图像接收 (Hardware -> Unity)")] [Tooltip("Unity本地绑定IP(用于接收硬件发送的JPEG图像流)。默认由 GetLocalBindIp() 自动获取本机地址一次;也可调用 ApplyLocalBindIp() 或 NetworkConfig.GetLocalBindIp() 手动刷新。")] public string hardwareBindIp = "192.168.0.105"; [Tooltip("硬件发送JPEG图像的UDP端口")] public int hardwarePort = 12346; [Tooltip("JPEG接收超时时间(秒),超过此时间未收到完整帧则重置缓冲区")] public float hardwareTimeoutSeconds = 2.0f; [Header("硬件控制 (Unity -> Hardware)")] [Tooltip("硬件设备IP地址(用于发送控制指令,例如ESP32S3的IP,如192.168.0.106)")] public string hardwareControlIp = "192.168.0.106"; [Tooltip("硬件监听控制命令的UDP端口")] public int hardwareControlPort = 8888; [Header("Python通信 (Unity -> Python)")] [Tooltip("Python进程IP(UDP模式使用,通常为localhost)")] public string pythonIp = "127.0.0.1"; [Tooltip("Python UDP端口(UDP模式使用,用于接收Unity发送的JPEG)")] public int pythonPort = 12347; [Header("Python结果接收 (Python -> Unity)")] [Tooltip("Unity本地绑定IP(用于接收Python发送的算法结果),例如 127.0.0.1 或 0.0.0.0(监听所有接口)")] public string pythonResultBindIp = "127.0.0.1"; [Tooltip("Python结果接收端口(用于接收Python发送的算法结果,默认12348)")] public int pythonResultPort = 12348; [Header("Python启动参数")] [Tooltip("Python脚本启动参数(完整命令行参数,不包括脚本路径)。" + "可以使用以下占位符,会自动替换为实际值:" + "{pythonPort} - Python UDP端口(接收图像)" + "{resultIp} - Unity结果接收IP(默认127.0.0.1)" + "{resultPort} - Unity结果接收端口(默认12348)" + "比较全的指令:--input \"udp://0.0.0.0:{pythonPort}\" --result_ip {resultIp} --result_port {resultPort} --control_port 12349 --max_keypoints 64 --use_fp16 --use_tensorrt --tensorrt_precision fp16 --depth_confidence 0.90 --width_confidence 0.95 --keypoint_threshold 0.015 --nms_radius 5 --show_fps")] [TextArea(3, 8)] public string pythonScriptArgs = "--input \"udp://0.0.0.0:{pythonPort}\" --result_ip {resultIp} --result_port {resultPort} --control_port 12349 --use_tensorrt"; [Header("Python启动参数(Roma)")] [Tooltip("Roma 脚本启动参数(完整命令行参数,不包括脚本路径)。" + "可用占位符:" + "{pythonPort} - 传统 udp:// 输入端口(作为 --input udp://0.0.0.0:{pythonPort} 的端口,兼容旧风格)" + "{wifiPort} - OrangePi WiFi 图传接收端口(FrameHeader 分片输入)" + "{forwardPort} - Python->Unity 图像转发端口(用于显示)" + "{resultIp} - Unity结果接收IP" + "{resultPort} - Unity结果接收端口" + "{controlPort} - Unity->Python 控制端口(n/s/r)" + "示例:--input udp://0.0.0.0:{pythonPort} --reference_image ... --model tiny ... --result_ip {resultIp} --result_port {resultPort} --control_port {controlPort}")] [TextArea(3, 8)] public string romaPythonScriptArgs = "--input udp://0.0.0.0:{pythonPort} --reference_image assets/sacre_coeur_A.jpg --model tiny --resize 320 240 " + "--sample_num 500 --sample_thresh 0.05 --ransac_reproj_threshold 4.0 --min_matches 120 --min_inlier_ratio 0.08 " + "--roma_interval 10 --smooth_alpha 0.9 --trail_len 150 --show_fps --max_fps 90 --max_display_fps 60 --idle_sleep_ms 2 --timer_print_interval 0.5 " + "--result_ip {resultIp} --result_port {resultPort} --control_port {controlPort} --log_send_result"; [Header("Python启动参数(Roma WiFi/FrameHeader)")] [Tooltip("Roma WiFi 图传版桥接脚本(demo_roma_camera_position_async_unity_bridge.py)启动参数模板。" + "可用占位符:" + "{wifiPort} - OrangePi WiFi 图传接收端口(--wifi_listen_port)" + "{forwardPort} - Python->Unity 图像转发端口(--forward_port)" + "{deviceInfoPort} - Python->Unity 设备信息上报端口(JSON)" + "{resultIp} - Unity结果接收IP" + "{resultPort} - Unity结果接收端口" + "{controlPort} - Unity->Python 控制端口(n/s/r)")] [TextArea(3, 8)] public string romaWifiPythonScriptArgs = "--input udp://0.0.0.0:{wifiPort} " + "--reference_image assets/sacre_coeur_A.jpg --model tiny --resize 320 240 " + "--sample_num 500 --sample_thresh 0.05 --ransac_reproj_threshold 4.0 --min_matches 120 --min_inlier_ratio 0.08 " + "--roma_interval 10 --smooth_alpha 0.9 --trail_len 150 " + "--forward_ip {resultIp} --forward_port {forwardPort} --forward_fps 30 " + "--device_info_ip {resultIp} --device_info_port {deviceInfoPort} " + "--result_ip {resultIp} --result_port {resultPort} --control_port {controlPort} " + "--show_fps --max_fps 90 --max_display_fps 60 --idle_sleep_ms 2 --timer_print_interval 0.5"; [Header("Roma 控制端口(Unity -> Python)")] [Tooltip("Roma 控制端口占位符 {controlPort} 的默认值(与 Unity 侧 Roma 控制端口保持一致)。")] public int romaControlPort = 12349; [Header("Roma WiFi 图传端口(OrangePi -> Python)")] [Tooltip("Roma WiFi 图传接收端口占位符 {wifiPort} 的默认值(避免与广播 12345 冲突)。\n" + "注意:若同一台机器上同时运行旧 LightGlue_Deployment 场景(其 hardwarePort 也可能是 12346),会发生端口占用冲突。")] public int romaWifiListenPort = 12346; [Header("Roma 图像转发端口(Python -> Unity)")] [Tooltip("Roma Python->Unity 图像转发端口占位符 {forwardPort} 的默认值(用于 Unity 显示 RawImage)。")] public int romaForwardPort = 12366; [Header("Roma 设备信息上报端口(Python -> Unity, JSON)")] [Tooltip("Python 将硬件 source_ip/source_port 通过 JSON 上报给 Unity 的端口占位符 {deviceInfoPort}。")] public int romaDeviceInfoPort = 12350; [Header("Roma 硬件模式")] [Tooltip("OrangePi:广播发现+下发图传配置;ESP32:无需下发配置,直接有图像回传则启动算法流程与结果接收。")] public RomaHardwareMode romaHardwareMode = RomaHardwareMode.Esp32; [Header("Roma 本机目标IP(PC -> OrangePi config target_ip)")] [Tooltip("下发给 OrangePi 的 target_ip(也就是本机接收 WiFi 图传的 IP)。用于持久化 RomaNetworkConfigUIController.localTargetIpField。")] public string romaLocalTargetIp = ""; [Header("Roma ESP32 设备信息(后续用于下发硬件参数)")] [Tooltip("ESP32 设备 IP(仅在 romaHardwareMode=ESP32 时使用;供后续 ImageTransmissionUIController 下发硬件信息)。")] public string romaEsp32DeviceIp = ""; [Tooltip("ESP32 设备端口(仅在 romaHardwareMode=ESP32 时使用;供后续 ImageTransmissionUIController 下发硬件信息)。")] public int romaEsp32DevicePort = 8888; [Header("启动控制")] [Tooltip("是否在Play时自动启动所有相关组件(如果为false,需要手动启动)")] public bool autoStartOnPlay = false; [Tooltip("是否以无窗口方式启动 Python(--no_display),避免绘制窗口在游戏背后时卡顿;取消勾选可保留 Python 窗口便于调试")] public bool pythonNoDisplay = true; /// /// 验证IP地址格式 /// public bool ValidateIpAddress(string ip) { if (string.IsNullOrWhiteSpace(ip)) return false; if (ip == "0.0.0.0") return true; // 允许监听所有接口 string[] parts = ip.Split('.'); if (parts.Length != 4) return false; foreach (string part in parts) { if (!int.TryParse(part, out int num) || num < 0 || num > 255) return false; } return true; } /// /// 验证配置有效性 /// public bool Validate() { if (!ValidateIpAddress(hardwareBindIp)) { Debug.LogError($"[NetworkConfig] 无效的硬件绑定IP: {hardwareBindIp}"); return false; } if (!ValidateIpAddress(hardwareControlIp)) { Debug.LogError($"[NetworkConfig] 无效的硬件控制IP: {hardwareControlIp}"); return false; } if (!ValidateIpAddress(pythonIp)) { Debug.LogError($"[NetworkConfig] 无效的Python IP: {pythonIp}"); return false; } if (!ValidateIpAddress(pythonResultBindIp)) { Debug.LogError($"[NetworkConfig] 无效的Python结果绑定IP: {pythonResultBindIp}"); return false; } if (hardwarePort <= 0 || hardwarePort > 65535) { Debug.LogError($"[NetworkConfig] 无效的硬件端口: {hardwarePort}"); return false; } if (hardwareControlPort <= 0 || hardwareControlPort > 65535) { Debug.LogError($"[NetworkConfig] 无效的硬件控制端口: {hardwareControlPort}"); return false; } if (pythonPort <= 0 || pythonPort > 65535) { Debug.LogError($"[NetworkConfig] 无效的Python端口: {pythonPort}"); return false; } if (pythonResultPort <= 0 || pythonResultPort > 65535) { Debug.LogError($"[NetworkConfig] 无效的Python结果端口: {pythonResultPort}"); return false; } return true; } /// /// 获取处理后的Python启动参数(替换占位符) /// public string GetPythonScriptArgs() { if (string.IsNullOrWhiteSpace(pythonScriptArgs)) return string.Empty; // 替换所有占位符 string args = pythonScriptArgs; args = args.Replace("{pythonPort}", pythonPort.ToString()); args = args.Replace("{resultIp}", pythonResultBindIp); args = args.Replace("{resultPort}", pythonResultPort.ToString()); return args; } /// /// 获取处理后的 Roma 启动参数(替换占位符)。 /// public string GetRomaPythonScriptArgs() { if (string.IsNullOrWhiteSpace(romaPythonScriptArgs)) return string.Empty; string args = romaPythonScriptArgs; args = args.Replace("{pythonPort}", pythonPort.ToString()); args = args.Replace("{wifiPort}", romaWifiListenPort.ToString()); args = args.Replace("{forwardPort}", romaForwardPort.ToString()); args = args.Replace("{resultIp}", pythonResultBindIp); args = args.Replace("{resultPort}", pythonResultPort.ToString()); args = args.Replace("{controlPort}", romaControlPort.ToString()); return args; } /// /// 获取处理后的 Roma WiFi 图传版启动参数(替换占位符)。 /// public string GetRomaWifiPythonScriptArgs() { if (string.IsNullOrWhiteSpace(romaWifiPythonScriptArgs)) return string.Empty; string args = romaWifiPythonScriptArgs; args = args.Replace("{wifiPort}", romaWifiListenPort.ToString()); args = args.Replace("{forwardPort}", romaForwardPort.ToString()); args = args.Replace("{deviceInfoPort}", romaDeviceInfoPort.ToString()); args = args.Replace("{resultIp}", pythonResultBindIp); args = args.Replace("{resultPort}", pythonResultPort.ToString()); args = args.Replace("{controlPort}", romaControlPort.ToString()); return args; } /// /// 获取本机用于绑定的 IPv4 地址(非回环、首选网卡)。 /// 可供外部或 UI 调用,用于填充 hardwareBindIp。 /// 失败时返回 "0.0.0.0"(监听所有接口)。 /// public static string GetLocalBindIp() { try { var host = Dns.GetHostEntry(Dns.GetHostName()); foreach (var ip in host.AddressList) { if (ip.AddressFamily == AddressFamily.InterNetwork && !IPAddress.IsLoopback(ip)) return ip.ToString(); } } catch (System.Exception ex) { Debug.LogWarning($"[NetworkConfig] 获取本机IP失败,使用 0.0.0.0: {ex.Message}"); } return "0.0.0.0"; } /// /// 将当前配置的 hardwareBindIp 设为本次获取的本机地址(可多次调用刷新)。 /// public void ApplyLocalBindIp() { hardwareBindIp = GetLocalBindIp(); } /// /// 创建默认配置(hardwareBindIp 自动设为本次获取的本机地址)。 /// public static NetworkConfig CreateDefault() { var config = new NetworkConfig(); config.hardwareBindIp = GetLocalBindIp(); config.romaLocalTargetIp = GetLocalBindIp(); config.romaHardwareMode = RomaHardwareMode.Esp32; return config; } } }