@@ -23,19 +23,32 @@ public enum Protocol { TCP, TCPListener, UDP }
2323 [ HideInInspector ] public string kvSeparator = ":" ;
2424 [ HideInInspector ] public string outputTemplate = "{raw}" ;
2525
26+ [ Tooltip ( "訊息中代表設備 ID 的欄位名稱,留空則不追蹤 timeout" ) ]
27+ public string deviceIdKey = "id" ;
28+ [ Tooltip ( "超過幾秒沒收到訊息視為設備離線(0 = 停用)" ) ]
29+ public float deviceTimeoutSeconds = 20f ;
30+
2631 private EdgeLinkClient tcp ;
2732 private EdgeLinkTcpListener tcpListener ;
2833 private EdgeLinkUdpClient udp ;
2934
30- private readonly Dictionary < string , string > latest = new ( ) ;
35+ private readonly Dictionary < string , string > latest = new ( ) ;
36+ private readonly Dictionary < string , float > lastSeenTime = new ( ) ;
37+ private readonly HashSet < string > timedOut = new ( ) ;
3138 private readonly System . Collections . Concurrent . ConcurrentQueue < ( bool , string ) > deviceStatusQueue = new ( ) ;
3239
3340 public string Raw { get ; private set ; }
3441
35- /// <summary>Fired on Unity main thread when an upstream device connects/disconnects.
36- /// bool = isConnected, string = endpoint (e.g. "TCPServer@192.168.1.50:9001 ")</summary>
42+ /// <summary>Fired on Unity main thread when an upstream device connects/disconnects (TCP only) .
43+ /// bool = isConnected, string = endpoint (e.g. "TCPServer@192.168.1.50")</summary>
3744 public event Action < bool , string > OnDeviceStatus ;
3845
46+ /// <summary>Fired on Unity main thread when a device ID stops sending data beyond deviceTimeoutSeconds.</summary>
47+ public event Action < string > OnDeviceTimeout ;
48+
49+ /// <summary>Fired on Unity main thread when a previously timed-out device sends data again.</summary>
50+ public event Action < string > OnDeviceReconnected ;
51+
3952 public string Get ( string key ) =>
4053 latest . TryGetValue ( key , out string val ) ? val : null ;
4154
@@ -130,13 +143,37 @@ private void Update()
130143
131144 while ( deviceStatusQueue . TryDequeue ( out var ds ) )
132145 OnDeviceStatus ? . Invoke ( ds . Item1 , ds . Item2 ) ;
146+
147+ CheckDeviceTimeouts ( ) ;
148+ }
149+
150+ private void CheckDeviceTimeouts ( )
151+ {
152+ if ( deviceTimeoutSeconds <= 0 || string . IsNullOrEmpty ( deviceIdKey ) ) return ;
153+
154+ foreach ( var kv in lastSeenTime )
155+ {
156+ bool isTimedOut = Time . time - kv . Value > deviceTimeoutSeconds ;
157+ if ( isTimedOut && ! timedOut . Contains ( kv . Key ) )
158+ {
159+ timedOut . Add ( kv . Key ) ;
160+ OnDeviceTimeout ? . Invoke ( kv . Key ) ;
161+ }
162+ }
133163 }
134164
135165 private void Handle ( string msg )
136166 {
137167 Raw = msg ;
138168 var parsed = Parse ( msg ) ;
139169 foreach ( var kv in parsed ) latest [ kv . Key ] = kv . Value ;
170+
171+ if ( ! string . IsNullOrEmpty ( deviceIdKey ) && parsed . TryGetValue ( deviceIdKey , out string deviceId ) )
172+ {
173+ lastSeenTime [ deviceId ] = Time . time ;
174+ if ( timedOut . Remove ( deviceId ) )
175+ OnDeviceReconnected ? . Invoke ( deviceId ) ;
176+ }
140177 }
141178
142179 private void OnDestroy ( )
0 commit comments