Skip to content

Commit 3618d9e

Browse files
Reymerclaude
andcommitted
[新增] 所有 SDK 加入 OnDeviceStatus — 感測器斷線通知
Server 已有 EDGELINK_STATUS 機制,各 SDK 之前將其過濾丟棄。 現在攔截並暴露為事件/回呼,讓應用程式得知上游設備連線狀態變化。 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent e1ae246 commit 3618d9e

8 files changed

Lines changed: 116 additions & 8 deletions

File tree

SDK/CSharp/EdgeLinkClient.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ public class EdgeLinkClient : IDisposable
1313
public event Action? OnConnected;
1414
public event Action? OnDisconnected;
1515
public event Action<Exception>? OnError;
16+
/// <summary>Fired when an upstream device connects or disconnects from EdgeLink Server.
17+
/// Parameters: isConnected, endpoint (e.g. "TCPServer@192.168.1.50:9001")</summary>
18+
public event Action<bool, string>? OnDeviceStatus;
1619

1720
public bool IsConnected => tcpClient?.Connected == true && !disposed;
1821
public string Host { get; }
@@ -122,6 +125,16 @@ private void HandleLine(string line)
122125
_ = SendRawAsync($"EDGELINK_PONG:{hex}\n");
123126
return;
124127
}
128+
if (line.StartsWith("EDGELINK_STATUS:", StringComparison.Ordinal))
129+
{
130+
string body = line[16..];
131+
int sep = body.IndexOf(':');
132+
string statusStr = sep >= 0 ? body[..sep] : body;
133+
string endpoint = sep >= 0 ? body[(sep + 1)..] : "";
134+
bool connected = statusStr.Equals("CONNECTED", StringComparison.OrdinalIgnoreCase);
135+
OnDeviceStatus?.Invoke(connected, endpoint);
136+
return;
137+
}
125138
if (line.StartsWith("EDGELINK_", StringComparison.Ordinal)) return;
126139

127140
queue.Enqueue(line);

SDK/CSharp/EdgeLinkTcpListener.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ public class EdgeLinkTcpListener : IDisposable
1414
public event Action? OnConnected;
1515
public event Action? OnDisconnected;
1616
public event Action<Exception>? OnError;
17+
/// <summary>Fired when an upstream device connects or disconnects from EdgeLink Server.
18+
/// Parameters: isConnected, endpoint (e.g. "TCPServer@192.168.1.50:9001")</summary>
19+
public event Action<bool, string>? OnDeviceStatus;
1720

1821
public int LocalPort { get; }
1922
public bool IsRunning { get; private set; }
@@ -100,6 +103,16 @@ private async Task HandleLineAsync(NetworkStream networkStream, string line)
100103
catch { }
101104
return;
102105
}
106+
if (line.StartsWith("EDGELINK_STATUS:", StringComparison.Ordinal))
107+
{
108+
string body = line[16..];
109+
int sep = body.IndexOf(':');
110+
string statusStr = sep >= 0 ? body[..sep] : body;
111+
string endpoint = sep >= 0 ? body[(sep + 1)..] : "";
112+
bool connected = statusStr.Equals("CONNECTED", StringComparison.OrdinalIgnoreCase);
113+
OnDeviceStatus?.Invoke(connected, endpoint);
114+
return;
115+
}
103116
if (line.StartsWith("EDGELINK_", StringComparison.Ordinal)) return;
104117

105118
queue.Enqueue(line);

SDK/JavaScript/src/EdgeLinkClient.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,15 @@ class EdgeLinkClient extends EventEmitter {
104104
}
105105
return;
106106
}
107+
if (line.startsWith("EDGELINK_STATUS:")) {
108+
const body = line.slice(16);
109+
const sep = body.indexOf(":");
110+
const status = sep >= 0 ? body.slice(0, sep) : body;
111+
const endpoint = sep >= 0 ? body.slice(sep + 1) : "";
112+
const connected = status.toUpperCase() === "CONNECTED";
113+
this.emit("deviceStatus", connected, endpoint);
114+
return;
115+
}
107116
if (line.startsWith("EDGELINK_")) return;
108117

109118
this.emit("message", line);

SDK/JavaScript/src/EdgeLinkTcpListener.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,15 @@ class EdgeLinkTcpListener extends EventEmitter {
6464
}
6565
return;
6666
}
67+
if (line.startsWith("EDGELINK_STATUS:")) {
68+
const body = line.slice(16);
69+
const sep = body.indexOf(":");
70+
const status = sep >= 0 ? body.slice(0, sep) : body;
71+
const endpoint = sep >= 0 ? body.slice(sep + 1) : "";
72+
const connected = status.toUpperCase() === "CONNECTED";
73+
this.emit("deviceStatus", connected, endpoint);
74+
return;
75+
}
6776
if (line.startsWith("EDGELINK_")) return;
6877

6978
this.emit("message", line);

SDK/Python/edgelink/tcp.py

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,11 @@ def __init__(self, host: str, port: int) -> None:
1111
self.port = port
1212
self._auto_reconnect = True
1313
self._reconnect_delay = 5.0
14-
self._on_message: list[Callable[[str], None]] = []
15-
self._on_connected: list[Callable[[], None]] = []
16-
self._on_disconnected: list[Callable[[], None]] = []
17-
self._on_error: list[Callable[[Exception], None]] = []
14+
self._on_message: list[Callable[[str], None]] = []
15+
self._on_connected: list[Callable[[], None]] = []
16+
self._on_disconnected: list[Callable[[], None]] = []
17+
self._on_error: list[Callable[[Exception], None]] = []
18+
self._on_device_status: list[Callable[[bool, str], None]] = []
1819
self._queue: deque[str] = deque()
1920
self._writer: asyncio.StreamWriter | None = None
2021
self._task: asyncio.Task | None = None
@@ -38,6 +39,10 @@ def on_disconnected(self, cb: Callable[[], None]) -> None:
3839
def on_error(self, cb: Callable[[Exception], None]) -> None:
3940
self._on_error.append(cb)
4041

42+
def on_device_status(self, cb: Callable[[bool, str], None]) -> None:
43+
"""cb(is_connected: bool, endpoint: str) — fired when an upstream device connects/disconnects."""
44+
self._on_device_status.append(cb)
45+
4146
# ── public API ─────────────────────────────────────────────────────────────
4247

4348
@property
@@ -125,6 +130,15 @@ def _handle_line(self, line: str) -> None:
125130
if self._writer and not self._writer.is_closing():
126131
self._writer.write(f"EDGELINK_PONG:{hex_val}\n".encode())
127132
return
133+
if line.startswith("EDGELINK_STATUS:"):
134+
body = line[16:]
135+
sep = body.find(":")
136+
status = body[:sep] if sep >= 0 else body
137+
endpoint = body[sep + 1:] if sep >= 0 else ""
138+
connected = status.upper() == "CONNECTED"
139+
for cb in self._on_device_status:
140+
cb(connected, endpoint)
141+
return
128142
if line.startswith("EDGELINK_"):
129143
return
130144

@@ -138,10 +152,11 @@ class EdgeLinkTcpListener:
138152

139153
def __init__(self, local_port: int) -> None:
140154
self.local_port = local_port
141-
self._on_message: list[Callable[[str], None]] = []
142-
self._on_connected: list[Callable[[], None]] = []
143-
self._on_disconnected: list[Callable[[], None]] = []
144-
self._on_error: list[Callable[[Exception], None]] = []
155+
self._on_message: list[Callable[[str], None]] = []
156+
self._on_connected: list[Callable[[], None]] = []
157+
self._on_disconnected: list[Callable[[], None]] = []
158+
self._on_error: list[Callable[[Exception], None]] = []
159+
self._on_device_status: list[Callable[[bool, str], None]] = []
145160
self._queue: deque[str] = deque()
146161
self._server: asyncio.Server | None = None
147162
self.is_running = False
@@ -158,6 +173,10 @@ def on_disconnected(self, cb: Callable[[], None]) -> None:
158173
def on_error(self, cb: Callable[[Exception], None]) -> None:
159174
self._on_error.append(cb)
160175

176+
def on_device_status(self, cb: Callable[[bool, str], None]) -> None:
177+
"""cb(is_connected: bool, endpoint: str) — fired when an upstream device connects/disconnects."""
178+
self._on_device_status.append(cb)
179+
161180
async def start(self) -> None:
162181
self._server = await asyncio.start_server(self._handle_client, "0.0.0.0", self.local_port)
163182
self.is_running = True
@@ -204,6 +223,15 @@ async def _handle_line(self, line: str, writer: asyncio.StreamWriter) -> None:
204223
except Exception:
205224
pass
206225
return
226+
if line.startswith("EDGELINK_STATUS:"):
227+
body = line[16:]
228+
sep = body.find(":")
229+
status = body[:sep] if sep >= 0 else body
230+
endpoint = body[sep + 1:] if sep >= 0 else ""
231+
connected = status.upper() == "CONNECTED"
232+
for cb in self._on_device_status:
233+
cb(connected, endpoint)
234+
return
207235
if line.startswith("EDGELINK_"):
208236
return
209237

SDK/Unity/Package/Runtime/EdgeLinkClient.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ public class EdgeLinkClient : IDisposable
1313
public event Action? OnConnected;
1414
public event Action? OnDisconnected;
1515
public event Action<Exception>? OnError;
16+
/// <summary>Fired when an upstream device connects or disconnects from EdgeLink Server.
17+
/// Parameters: isConnected, protocol, endpoint (e.g. "TCPServer@192.168.1.50:9001")</summary>
18+
public event Action<bool, string>? OnDeviceStatus;
1619

1720
public bool IsConnected => tcpClient?.Connected == true && !disposed;
1821
public string Host { get; }
@@ -122,6 +125,16 @@ private void HandleLine(string line)
122125
_ = SendRawAsync($"EDGELINK_PONG:{hex}\n");
123126
return;
124127
}
128+
if (line.StartsWith("EDGELINK_STATUS:", StringComparison.Ordinal))
129+
{
130+
string body = line.Substring(16);
131+
int sep = body.IndexOf(':');
132+
string statusStr = sep >= 0 ? body.Substring(0, sep) : body;
133+
string endpoint = sep >= 0 ? body.Substring(sep + 1) : "";
134+
bool connected = statusStr.Equals("CONNECTED", StringComparison.OrdinalIgnoreCase);
135+
OnDeviceStatus?.Invoke(connected, endpoint);
136+
return;
137+
}
125138
if (line.StartsWith("EDGELINK_", StringComparison.Ordinal)) return;
126139

127140
queue.Enqueue(line);

SDK/Unity/Package/Runtime/EdgeLinkManager.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,14 @@ public enum Protocol { TCP, TCPListener, UDP }
2828
private EdgeLinkUdpClient udp;
2929

3030
private readonly Dictionary<string, string> latest = new();
31+
private readonly System.Collections.Concurrent.ConcurrentQueue<(bool, string)> deviceStatusQueue = new();
3132

3233
public string Raw { get; private set; }
3334

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>
37+
public event Action<bool, string> OnDeviceStatus;
38+
3439
public string Get(string key) =>
3540
latest.TryGetValue(key, out string val) ? val : null;
3641

@@ -93,6 +98,7 @@ private async void StartConnection()
9398
tcp.OnConnected += () => Debug.Log("[EdgeLink TCP] Connected");
9499
tcp.OnDisconnected += () => Debug.Log("[EdgeLink TCP] Disconnected");
95100
tcp.OnError += ex => Debug.LogWarning($"[EdgeLink TCP] {ex.Message}");
101+
tcp.OnDeviceStatus += (connected, ep) => deviceStatusQueue.Enqueue((connected, ep));
96102
tcp.SetAutoReconnect(true, 5000);
97103
try { await tcp.ConnectAsync(); }
98104
catch { Debug.LogWarning("[EdgeLink TCP] Initial connect failed, will retry..."); }
@@ -103,6 +109,7 @@ private async void StartConnection()
103109
tcpListener.OnConnected += () => Debug.Log("[EdgeLink TCPListener] EdgeLink connected");
104110
tcpListener.OnDisconnected += () => Debug.Log("[EdgeLink TCPListener] EdgeLink disconnected");
105111
tcpListener.OnError += ex => Debug.LogWarning($"[EdgeLink TCPListener] {ex.Message}");
112+
tcpListener.OnDeviceStatus += (connected, ep) => deviceStatusQueue.Enqueue((connected, ep));
106113
tcpListener.Start();
107114
Debug.Log($"[EdgeLink TCPListener] Listening on port {tcpListenPort}");
108115
}
@@ -120,6 +127,9 @@ private void Update()
120127
if (tcp != null) while (tcp.TryDequeue(out string msg)) Handle(msg);
121128
if (tcpListener != null) while (tcpListener.TryDequeue(out string msg)) Handle(msg);
122129
if (udp != null) while (udp.TryDequeue(out string msg)) Handle(msg);
130+
131+
while (deviceStatusQueue.TryDequeue(out var ds))
132+
OnDeviceStatus?.Invoke(ds.Item1, ds.Item2);
123133
}
124134

125135
private void Handle(string msg)

SDK/Unity/Package/Runtime/EdgeLinkTcpListener.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ public class EdgeLinkTcpListener : IDisposable
1414
public event Action? OnConnected;
1515
public event Action? OnDisconnected;
1616
public event Action<Exception>? OnError;
17+
/// <summary>Fired when an upstream device connects or disconnects from EdgeLink Server.
18+
/// Parameters: isConnected, endpoint (e.g. "TCPServer@192.168.1.50:9001")</summary>
19+
public event Action<bool, string>? OnDeviceStatus;
1720

1821
public int LocalPort { get; }
1922
public bool IsRunning { get; private set; }
@@ -100,6 +103,16 @@ private async Task HandleLineAsync(NetworkStream networkStream, string line)
100103
catch { }
101104
return;
102105
}
106+
if (line.StartsWith("EDGELINK_STATUS:", StringComparison.Ordinal))
107+
{
108+
string body = line.Substring(16);
109+
int sep = body.IndexOf(':');
110+
string statusStr = sep >= 0 ? body.Substring(0, sep) : body;
111+
string endpoint = sep >= 0 ? body.Substring(sep + 1) : "";
112+
bool connected = statusStr.Equals("CONNECTED", StringComparison.OrdinalIgnoreCase);
113+
OnDeviceStatus?.Invoke(connected, endpoint);
114+
return;
115+
}
103116
if (line.StartsWith("EDGELINK_", StringComparison.Ordinal)) return;
104117

105118
queue.Enqueue(line);

0 commit comments

Comments
 (0)