| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168 |
- using System;
- using System.Net;
- using System.Net.Sockets;
- using System.Text;
- using System.Threading;
- using UnityEngine;
- namespace LightGlue.Unity.Roma.Networking
- {
- /// <summary>
- /// 接收 Python 上报的设备信息(JSON over UDP)。
- /// 典型用途:ESP32 无广播时,从 Python 收图线程“学到”的 source_ip/source_port 回填到 Unity UI。
- /// JSON 示例:{"device_ip":"192.168.0.16","device_port":12346,"ts":1710000000.0}
- /// </summary>
- public sealed class RomaDeviceInfoReceiver : MonoBehaviour
- {
- [Header("Listen")]
- public string bindIp = "0.0.0.0";
- public int port = 12350;
- public bool autoStartOnEnable = true;
- [Header("State (readonly)")]
- [SerializeField] private string latestDeviceIp;
- [SerializeField] private int latestDevicePort;
- [SerializeField] private bool hasLatest;
- public string LatestDeviceIp => latestDeviceIp;
- public int LatestDevicePort => latestDevicePort;
- public bool HasLatest => hasLatest;
- public event Action<string, int> OnDeviceInfoUpdated;
- private UdpClient _client;
- private Thread _thread;
- private volatile bool _running;
- private readonly object _lock = new object();
- private string _pendingIp;
- private int _pendingPort;
- private bool _hasPending;
- [Serializable]
- private class DeviceInfoMsg
- {
- public string device_ip;
- public int device_port;
- public double ts;
- }
- private void OnEnable()
- {
- if (autoStartOnEnable)
- StartListening();
- }
- private void OnDisable()
- {
- StopListening();
- }
- private void Update()
- {
- string ip = null;
- int p = 0;
- bool has = false;
- lock (_lock)
- {
- if (_hasPending)
- {
- ip = _pendingIp;
- p = _pendingPort;
- has = true;
- _hasPending = false;
- }
- }
- if (!has || string.IsNullOrWhiteSpace(ip)) return;
- latestDeviceIp = ip;
- latestDevicePort = p;
- hasLatest = true;
- OnDeviceInfoUpdated?.Invoke(ip, p);
- }
- public void StartListening()
- {
- if (_running) return;
- try
- {
- var ip = IPAddress.Parse(string.IsNullOrWhiteSpace(bindIp) ? "0.0.0.0" : bindIp);
- _client = new UdpClient(new IPEndPoint(ip, port));
- _client.Client.ReceiveTimeout = 200;
- _running = true;
- _thread = new Thread(ReceiveLoop) { IsBackground = true, Name = "RomaDeviceInfoReceiver" };
- _thread.Start();
- Debug.Log($"[RomaDevInfo] Listening on {bindIp}:{port}");
- }
- catch (Exception ex)
- {
- Debug.LogWarning($"[RomaDevInfo] Start failed: {ex.Message}");
- Cleanup();
- }
- }
- public void StopListening()
- {
- _running = false;
- try { _client?.Close(); } catch { /* ignore */ }
- try { _client?.Dispose(); } catch { /* ignore */ }
- _client = null;
- if (_thread != null && _thread.IsAlive)
- {
- if (!_thread.Join(500))
- {
- try { _thread.Interrupt(); } catch { /* ignore */ }
- }
- }
- _thread = null;
- }
- private void ReceiveLoop()
- {
- var remote = new IPEndPoint(IPAddress.Any, 0);
- while (_running)
- {
- try
- {
- byte[] data = _client.Receive(ref remote);
- if (data == null || data.Length == 0) continue;
- string json = Encoding.UTF8.GetString(data);
- DeviceInfoMsg msg = JsonUtility.FromJson<DeviceInfoMsg>(json);
- if (msg == null || string.IsNullOrWhiteSpace(msg.device_ip)) continue;
- int p = msg.device_port;
- lock (_lock)
- {
- _pendingIp = msg.device_ip;
- _pendingPort = p;
- _hasPending = true;
- }
- }
- catch (SocketException)
- {
- continue;
- }
- catch (ObjectDisposedException)
- {
- break;
- }
- catch (ThreadInterruptedException)
- {
- break;
- }
- catch (Exception ex)
- {
- Debug.LogWarning($"[RomaDevInfo] Receive error: {ex.Message}");
- }
- }
- }
- private void Cleanup()
- {
- _running = false;
- try { _client?.Close(); } catch { /* ignore */ }
- try { _client?.Dispose(); } catch { /* ignore */ }
- _client = null;
- }
- }
- }
|