NetworkConfig.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348
  1. using System.Net;
  2. using System.Net.Sockets;
  3. using System.Text.RegularExpressions;
  4. using UnityEngine;
  5. using UnityEngine.Windows;
  6. namespace LightGlue.Unity.Config
  7. {
  8. /// <summary>
  9. /// 网络配置参数(统一管理所有IP和端口配置)
  10. /// 用于部署到不同设备时的配置管理
  11. /// </summary>
  12. [System.Serializable]
  13. public class NetworkConfig
  14. {
  15. public enum RomaHardwareMode
  16. {
  17. OrangePi = 0,
  18. Esp32 = 1,
  19. }
  20. [Header("硬件图像接收 (Hardware -> Unity)")]
  21. [Tooltip("Unity本地绑定IP(用于接收硬件发送的JPEG图像流)。默认由 GetLocalBindIp() 自动获取本机地址一次;也可调用 ApplyLocalBindIp() 或 NetworkConfig.GetLocalBindIp() 手动刷新。")]
  22. public string hardwareBindIp = "192.168.0.105";
  23. [Tooltip("硬件发送JPEG图像的UDP端口")]
  24. public int hardwarePort = 12346;
  25. [Tooltip("JPEG接收超时时间(秒),超过此时间未收到完整帧则重置缓冲区")]
  26. public float hardwareTimeoutSeconds = 2.0f;
  27. [Header("硬件控制 (Unity -> Hardware)")]
  28. [Tooltip("硬件设备IP地址(用于发送控制指令,例如ESP32S3的IP,如192.168.0.106)")]
  29. public string hardwareControlIp = "192.168.0.106";
  30. [Tooltip("硬件监听控制命令的UDP端口")]
  31. public int hardwareControlPort = 8888;
  32. [Header("Python通信 (Unity -> Python)")]
  33. [Tooltip("Python进程IP(UDP模式使用,通常为localhost)")]
  34. public string pythonIp = "127.0.0.1";
  35. [Tooltip("Python UDP端口(UDP模式使用,用于接收Unity发送的JPEG)")]
  36. public int pythonPort = 12347;
  37. [Header("Python结果接收 (Python -> Unity)")]
  38. [Tooltip("Unity本地绑定IP(用于接收Python发送的算法结果),例如 127.0.0.1 或 0.0.0.0(监听所有接口)")]
  39. public string pythonResultBindIp = "127.0.0.1";
  40. [Tooltip("Python结果接收端口(用于接收Python发送的算法结果,默认12348)")]
  41. public int pythonResultPort = 12348;
  42. [Header("Python启动参数")]
  43. [Tooltip("Python脚本启动参数(完整命令行参数,不包括脚本路径)。" +
  44. "可以使用以下占位符,会自动替换为实际值:" +
  45. "{pythonPort} - Python UDP端口(接收图像)" +
  46. "{resultIp} - Unity结果接收IP(默认127.0.0.1)" +
  47. "{resultPort} - Unity结果接收端口(默认12348)" +
  48. "比较全的指令:--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")]
  49. [TextArea(3, 8)]
  50. public string pythonScriptArgs = "--input \"udp://0.0.0.0:{pythonPort}\" --result_ip {resultIp} --result_port {resultPort} --control_port 12349 --use_tensorrt";
  51. [Header("参考图像尺寸(与 Python --resize 一致)")]
  52. [Tooltip("参考图像宽度(像素)。用于 C# 坐标映射与 Python --resize 参数的占位符替换。")]
  53. public int referenceResizeWidth = 320;
  54. [Tooltip("参考图像高度(像素)。用于 C# 坐标映射与 Python --resize 参数的占位符替换。")]
  55. public int referenceResizeHeight = 240;
  56. [Header("Python启动参数(Roma)")]
  57. [Tooltip("Roma 脚本启动参数(完整命令行参数,不包括脚本路径)。" +
  58. "可用占位符:" +
  59. "{pythonPort} - 传统 udp:// 输入端口(作为 --input udp://0.0.0.0:{pythonPort} 的端口,兼容旧风格)" +
  60. "{wifiPort} - OrangePi WiFi 图传接收端口(FrameHeader 分片输入)" +
  61. "{forwardPort} - Python->Unity 图像转发端口(用于显示)" +
  62. "{resultIp} - Unity结果接收IP" +
  63. "{resultPort} - Unity结果接收端口" +
  64. "{controlPort} - Unity->Python 控制端口(n/s/r)" +
  65. "示例:--input udp://0.0.0.0:{pythonPort} --reference_image ... --model tiny ... --result_ip {resultIp} --result_port {resultPort} --control_port {controlPort}")]
  66. [TextArea(3, 8)]
  67. public string romaPythonScriptArgs =
  68. "--input udp://0.0.0.0:{pythonPort} --reference_image assets/sacre_coeur_A.jpg --model tiny --resize {resizeW} {resizeH} " +
  69. "--sample_num 500 --sample_thresh 0.05 --ransac_reproj_threshold 4.0 --min_matches 120 --min_inlier_ratio 0.08 " +
  70. "--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 " +
  71. "--result_ip {resultIp} --result_port {resultPort} --control_port {controlPort} --log_send_result";
  72. [Header("Python启动参数(Roma WiFi/FrameHeader)")]
  73. [Tooltip("Roma WiFi 图传版桥接脚本(demo_roma_camera_position_async_unity_bridge.py)启动参数模板。" +
  74. "可用占位符:" +
  75. "{wifiPort} - OrangePi WiFi 图传接收端口(--wifi_listen_port)" +
  76. "{forwardPort} - Python->Unity 图像转发端口(--forward_port)" +
  77. "{deviceInfoPort} - Python->Unity 设备信息上报端口(JSON)" +
  78. "{resultIp} - Unity结果接收IP" +
  79. "{resultPort} - Unity结果接收端口" +
  80. "{controlPort} - Unity->Python 控制端口(n/s/r)")]
  81. [TextArea(3, 8)]
  82. public string romaWifiPythonScriptArgs =
  83. "--input udp://0.0.0.0:{wifiPort} " +
  84. "--reference_image assets/sacre_coeur_A.jpg --model tiny --resize {resizeW} {resizeH} " +
  85. "--sample_num 500 --sample_thresh 0.05 --ransac_reproj_threshold 4.0 --min_matches 120 --min_inlier_ratio 0.08 " +
  86. "--roma_interval 10 --smooth_alpha 0.9 --trail_len 150 " +
  87. "--forward_ip {resultIp} --forward_port {forwardPort} --forward_fps 30 " +
  88. "--device_info_ip {resultIp} --device_info_port {deviceInfoPort} " +
  89. "--result_ip {resultIp} --result_port {resultPort} --control_port {controlPort} " +
  90. "--show_fps --max_fps 90 --max_display_fps 60 --idle_sleep_ms 2 --timer_print_interval 0.5";
  91. [Header("Roma 控制端口(Unity -> Python)")]
  92. [Tooltip("Roma 控制端口占位符 {controlPort} 的默认值(与 Unity 侧 Roma 控制端口保持一致)。")]
  93. public int romaControlPort = 12349;
  94. [Header("Roma WiFi 图传端口(OrangePi -> Python)")]
  95. [Tooltip("Roma WiFi 图传接收端口占位符 {wifiPort} 的默认值(避免与广播 12345 冲突)。\n" +
  96. "注意:若同一台机器上同时运行旧 LightGlue_Deployment 场景(其 hardwarePort 也可能是 12346),会发生端口占用冲突。")]
  97. public int romaWifiListenPort = 12346;
  98. [Header("Roma 图像转发端口(Python -> Unity)")]
  99. [Tooltip("Roma Python->Unity 图像转发端口占位符 {forwardPort} 的默认值(用于 Unity 显示 RawImage)。")]
  100. public int romaForwardPort = 12366;
  101. [Header("Roma 设备信息上报端口(Python -> Unity, JSON)")]
  102. [Tooltip("Python 将硬件 source_ip/source_port 通过 JSON 上报给 Unity 的端口占位符 {deviceInfoPort}。")]
  103. public int romaDeviceInfoPort = 12350;
  104. [Header("Roma 硬件模式")]
  105. [Tooltip("OrangePi:广播发现+下发图传配置;ESP32:无需下发配置,直接有图像回传则启动算法流程与结果接收。")]
  106. public RomaHardwareMode romaHardwareMode = RomaHardwareMode.Esp32;
  107. [Header("Roma 本机目标IP(PC -> OrangePi config target_ip)")]
  108. [Tooltip("下发给 OrangePi 的 target_ip(也就是本机接收 WiFi 图传的 IP)。用于持久化 RomaNetworkConfigUIController.localTargetIpField。")]
  109. public string romaLocalTargetIp = "";
  110. [Header("Roma ESP32 设备信息(后续用于下发硬件参数)")]
  111. [Tooltip("ESP32 设备 IP(仅在 romaHardwareMode=ESP32 时使用;供后续 ImageTransmissionUIController 下发硬件信息)。")]
  112. public string romaEsp32DeviceIp = "";
  113. [Tooltip("ESP32 设备端口(仅在 romaHardwareMode=ESP32 时使用;供后续 ImageTransmissionUIController 下发硬件信息)。")]
  114. public int romaEsp32DevicePort = 8888;
  115. [Header("启动控制")]
  116. [Tooltip("是否在Play时自动启动所有相关组件(如果为false,需要手动启动)")]
  117. public bool autoStartOnPlay = false;
  118. [Tooltip("是否以无窗口方式启动 Python(--no_display),避免绘制窗口在游戏背后时卡顿;取消勾选可保留 Python 窗口便于调试")]
  119. public bool pythonNoDisplay = true;
  120. /// <summary>
  121. /// 验证IP地址格式
  122. /// </summary>
  123. public bool ValidateIpAddress(string ip)
  124. {
  125. if (string.IsNullOrWhiteSpace(ip))
  126. return false;
  127. if (ip == "0.0.0.0")
  128. return true; // 允许监听所有接口
  129. string[] parts = ip.Split('.');
  130. if (parts.Length != 4)
  131. return false;
  132. foreach (string part in parts)
  133. {
  134. if (!int.TryParse(part, out int num) || num < 0 || num > 255)
  135. return false;
  136. }
  137. return true;
  138. }
  139. /// <summary>
  140. /// 验证配置有效性
  141. /// </summary>
  142. public bool Validate()
  143. {
  144. if (!ValidateIpAddress(hardwareBindIp))
  145. {
  146. Debug.LogError($"[NetworkConfig] 无效的硬件绑定IP: {hardwareBindIp}");
  147. return false;
  148. }
  149. if (!ValidateIpAddress(hardwareControlIp))
  150. {
  151. Debug.LogError($"[NetworkConfig] 无效的硬件控制IP: {hardwareControlIp}");
  152. return false;
  153. }
  154. if (!ValidateIpAddress(pythonIp))
  155. {
  156. Debug.LogError($"[NetworkConfig] 无效的Python IP: {pythonIp}");
  157. return false;
  158. }
  159. if (!ValidateIpAddress(pythonResultBindIp))
  160. {
  161. Debug.LogError($"[NetworkConfig] 无效的Python结果绑定IP: {pythonResultBindIp}");
  162. return false;
  163. }
  164. if (hardwarePort <= 0 || hardwarePort > 65535)
  165. {
  166. Debug.LogError($"[NetworkConfig] 无效的硬件端口: {hardwarePort}");
  167. return false;
  168. }
  169. if (hardwareControlPort <= 0 || hardwareControlPort > 65535)
  170. {
  171. Debug.LogError($"[NetworkConfig] 无效的硬件控制端口: {hardwareControlPort}");
  172. return false;
  173. }
  174. if (pythonPort <= 0 || pythonPort > 65535)
  175. {
  176. Debug.LogError($"[NetworkConfig] 无效的Python端口: {pythonPort}");
  177. return false;
  178. }
  179. if (pythonResultPort <= 0 || pythonResultPort > 65535)
  180. {
  181. Debug.LogError($"[NetworkConfig] 无效的Python结果端口: {pythonResultPort}");
  182. return false;
  183. }
  184. return true;
  185. }
  186. /// <summary>
  187. /// 获取处理后的Python启动参数(替换占位符)
  188. /// </summary>
  189. public string GetPythonScriptArgs()
  190. {
  191. if (string.IsNullOrWhiteSpace(pythonScriptArgs))
  192. return string.Empty;
  193. // 替换所有占位符
  194. string args = pythonScriptArgs;
  195. args = args.Replace("{pythonPort}", pythonPort.ToString());
  196. args = args.Replace("{resultIp}", pythonResultBindIp);
  197. args = args.Replace("{resultPort}", pythonResultPort.ToString());
  198. return args;
  199. }
  200. /// <summary>
  201. /// 获取处理后的 Roma 启动参数(替换占位符)。
  202. /// </summary>
  203. public string GetRomaPythonScriptArgs()
  204. {
  205. if (string.IsNullOrWhiteSpace(romaPythonScriptArgs))
  206. return string.Empty;
  207. string args = romaPythonScriptArgs;
  208. args = args.Replace("{pythonPort}", pythonPort.ToString());
  209. args = args.Replace("{wifiPort}", romaWifiListenPort.ToString());
  210. args = args.Replace("{forwardPort}", romaForwardPort.ToString());
  211. args = args.Replace("{resultIp}", pythonResultBindIp);
  212. args = args.Replace("{resultPort}", pythonResultPort.ToString());
  213. args = args.Replace("{controlPort}", romaControlPort.ToString());
  214. args = args.Replace("{resizeW}", referenceResizeWidth.ToString());
  215. args = args.Replace("{resizeH}", referenceResizeHeight.ToString());
  216. args = InjectResizeIfHardcoded(args, referenceResizeWidth, referenceResizeHeight);
  217. return args;
  218. }
  219. /// <summary>
  220. /// 获取处理后的 Roma WiFi 图传版启动参数(替换占位符)。
  221. /// </summary>
  222. public string GetRomaWifiPythonScriptArgs()
  223. {
  224. if (string.IsNullOrWhiteSpace(romaWifiPythonScriptArgs))
  225. return string.Empty;
  226. string args = romaWifiPythonScriptArgs;
  227. args = args.Replace("{wifiPort}", romaWifiListenPort.ToString());
  228. args = args.Replace("{forwardPort}", romaForwardPort.ToString());
  229. args = args.Replace("{deviceInfoPort}", romaDeviceInfoPort.ToString());
  230. args = args.Replace("{resultIp}", pythonResultBindIp);
  231. args = args.Replace("{resultPort}", pythonResultPort.ToString());
  232. args = args.Replace("{controlPort}", romaControlPort.ToString());
  233. args = args.Replace("{resizeW}", referenceResizeWidth.ToString());
  234. args = args.Replace("{resizeH}", referenceResizeHeight.ToString());
  235. args = InjectResizeIfHardcoded(args, referenceResizeWidth, referenceResizeHeight);
  236. return args;
  237. }
  238. private static string InjectResizeIfHardcoded(string args, int w, int h)
  239. {
  240. if (string.IsNullOrWhiteSpace(args)) return args;
  241. if (w <= 0 || h <= 0) return args;
  242. // 兼容历史配置:JSON 里可能还是 "--resize 640 480" 这种硬编码模板
  243. // 这里统一强制注入最新值,避免 UI 下拉框改了但启动参数不变。
  244. try
  245. {
  246. return Regex.Replace(
  247. args,
  248. @"--resize\s+\d+\s+\d+",
  249. $"--resize {w} {h}",
  250. RegexOptions.CultureInvariant);
  251. }
  252. catch
  253. {
  254. return args;
  255. }
  256. }
  257. /// <summary>
  258. /// 获取本机用于绑定的 IPv4 地址(非回环、首选网卡)。
  259. /// 可供外部或 UI 调用,用于填充 hardwareBindIp。
  260. /// 失败时返回 "0.0.0.0"(监听所有接口)。
  261. /// </summary>
  262. public static string GetLocalBindIp()
  263. {
  264. try
  265. {
  266. var host = Dns.GetHostEntry(Dns.GetHostName());
  267. foreach (var ip in host.AddressList)
  268. {
  269. if (ip.AddressFamily == AddressFamily.InterNetwork && !IPAddress.IsLoopback(ip))
  270. return ip.ToString();
  271. }
  272. }
  273. catch (System.Exception ex)
  274. {
  275. Debug.LogWarning($"[NetworkConfig] 获取本机IP失败,使用 0.0.0.0: {ex.Message}");
  276. }
  277. return "0.0.0.0";
  278. }
  279. /// <summary>
  280. /// 将当前配置的 hardwareBindIp 设为本次获取的本机地址(可多次调用刷新)。
  281. /// </summary>
  282. public void ApplyLocalBindIp()
  283. {
  284. hardwareBindIp = GetLocalBindIp();
  285. }
  286. /// <summary>
  287. /// 创建默认配置(hardwareBindIp 自动设为本次获取的本机地址)。
  288. /// </summary>
  289. public static NetworkConfig CreateDefault()
  290. {
  291. var config = new NetworkConfig();
  292. config.hardwareBindIp = GetLocalBindIp();
  293. config.romaLocalTargetIp = GetLocalBindIp();
  294. config.romaHardwareMode = RomaHardwareMode.Esp32;
  295. return config;
  296. }
  297. }
  298. }