Skip to content

Commit 3003f85

Browse files
changlin.caiclaude
andcommitted
[新增] UDP 端對端 deviceId 連線/斷線事件(Server emit + 5 SDK 解析)
延伸前一 commit 的 server 端 UDP 設備追蹤,補上對下游 SDK 的事件通知, 跟 TCP 那條鏈完全對齊。 Server 端: - UdpConnector.TrackDevice 第一次看到某 deviceId 時,emit EDGELINK_STATUS:CONNECTED:protocol@deviceIp:deviceId\n 到設定的 forward target(TargetIP:LocalPort) - UdpConnector.SweepStaleDevices 移除超時 deviceId 時 emit EDGELINK_STATUS:DISCONNECTED:protocol@deviceIp:deviceId\n - 沒設 forward target 的 port 直接跳過 (no-op) SDK 端(5 個全部對齊): - Unity: EdgeLinkUdpClient 加 OnDeviceStatus(bool, string, string) event, 解析 EDGELINK_STATUS、過濾 EDGELINK_*; EdgeLinkManager UDP case 訂閱並丟進 _deviceStatusQ(與 TCP/TCPListener 共用) - C#: EdgeLinkUdpClient 加 OnDeviceStatus event,解析 + 過濾 - JS: EdgeLinkUdpClient 加 "deviceStatus" 事件,解析 + 過濾 - Python: EdgeLinkUdpClient 加 on_device_status callback,解析 + 過濾 - Arduino EdgeLinkUDP / EdgeLinkAsyncUDP:加 onDeviceStatus callback,過濾 EDGELINK_*(避免 callback 收到 EDGELINK_STATUS 噪訊); AsyncUDP 的 lambda 內也加同樣處理 - Arduino EdgeLink.zip 重新打包 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent e34757b commit 3003f85

11 files changed

Lines changed: 200 additions & 31 deletions

File tree

SDK/Arduino/EdgeLink.zip

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
version https://git-lfs.github.com/spec/v1
2-
oid sha256:a2d009f1a900cdc4df229d106bde20c5a7151ba2633705db95390f6f540254d7
3-
size 9015
2+
oid sha256:724467f47af27f9820e95db52c6cefeb706e2b772251e32a99383ed30281fa9b
3+
size 10007

SDK/Arduino/EdgeLink/src/EdgeLinkAsyncUDP.cpp

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,19 @@ EdgeLinkAsyncUDP::EdgeLinkAsyncUDP(AsyncUDP& udp) : _udp(udp) {}
66

77
bool EdgeLinkAsyncUDP::begin(uint16_t localPort) {
88
_udp.onPacket([this](AsyncUDPPacket packet) {
9-
if (!_onMsg) return;
109
size_t len = packet.length();
1110
if (len == 0) return;
1211
String msg;
1312
msg.reserve(len);
1413
const uint8_t* data = packet.data();
1514
for (size_t i = 0; i < len; ++i) msg += (char)data[i];
1615
msg.trim();
17-
if (msg.length() > 0) _onMsg(msg, packet.remoteIP(), packet.remotePort());
16+
if (msg.length() == 0) return;
17+
18+
if (msg.startsWith("EDGELINK_STATUS:")) { _dispatchStatus(msg); return; }
19+
if (msg.startsWith("EDGELINK_")) return; // other control prefixes — drop
20+
21+
if (_onMsg) _onMsg(msg, packet.remoteIP(), packet.remotePort());
1822
});
1923

2024
if (localPort == 0) return true; // send-only mode
@@ -31,7 +35,22 @@ size_t EdgeLinkAsyncUDP::send(IPAddress ip, uint16_t port, const String& message
3135
message.length(), ip, port);
3236
}
3337

34-
void EdgeLinkAsyncUDP::onMessage(MessageCallback cb) { _onMsg = cb; }
38+
void EdgeLinkAsyncUDP::onMessage(MessageCallback cb) { _onMsg = cb; }
39+
void EdgeLinkAsyncUDP::onDeviceStatus(DeviceStatusCallback cb) { _onStatus = cb; }
40+
41+
void EdgeLinkAsyncUDP::_dispatchStatus(const String& line) {
42+
if (!_onStatus) return;
43+
String body = line.substring(16);
44+
int sep = body.indexOf(':');
45+
if (sep < 0) return;
46+
String stat = body.substring(0, sep);
47+
String rest = body.substring(sep + 1);
48+
bool conn = stat.equalsIgnoreCase("CONNECTED");
49+
int dsep = rest.lastIndexOf(':');
50+
String ep = dsep >= 0 ? rest.substring(0, dsep) : rest;
51+
String devId = dsep >= 0 ? rest.substring(dsep + 1) : "";
52+
_onStatus(conn, ep, devId);
53+
}
3554

3655
void EdgeLinkAsyncUDP::close() { _udp.close(); }
3756

SDK/Arduino/EdgeLink/src/EdgeLinkAsyncUDP.h

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,10 @@
1515

1616
class EdgeLinkAsyncUDP {
1717
public:
18-
using MessageCallback = void (*)(const String& message, IPAddress remoteIP, uint16_t remotePort);
18+
using MessageCallback = void (*)(const String& message, IPAddress remoteIP, uint16_t remotePort);
19+
// EDGELINK_STATUS event from EdgeLink Server (timeout-based for UDP).
20+
// Parameters: isConnected, endpoint (e.g. "UDPPort@192.168.1.50"), deviceId.
21+
using DeviceStatusCallback = void (*)(bool connected, const String& endpoint, const String& deviceId);
1922

2023
explicit EdgeLinkAsyncUDP(AsyncUDP& udp);
2124

@@ -27,14 +30,19 @@ class EdgeLinkAsyncUDP {
2730
size_t send(const char* host, uint16_t port, const String& message);
2831
size_t send(IPAddress ip, uint16_t port, const String& message);
2932

30-
// Callback runs in AsyncUDP's task (not loop()) — keep handler short, no Serial.print spam.
33+
// Callbacks run in AsyncUDP's task (not loop()) — keep handlers short, no Serial.print spam.
34+
// EDGELINK_* control messages are filtered out of onMessage.
3135
void onMessage(MessageCallback cb);
36+
void onDeviceStatus(DeviceStatusCallback cb);
3237

3338
void close();
3439

3540
private:
36-
AsyncUDP& _udp;
37-
MessageCallback _onMsg = nullptr;
41+
AsyncUDP& _udp;
42+
MessageCallback _onMsg = nullptr;
43+
DeviceStatusCallback _onStatus = nullptr;
44+
45+
void _dispatchStatus(const String& line);
3846
};
3947

4048
#endif // AsyncUDP available

SDK/Arduino/EdgeLink/src/EdgeLinkUDP.cpp

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,12 @@ void EdgeLinkUDP::loop() {
1717
msg += (char)_udp.read();
1818
}
1919
msg.trim();
20+
if (msg.length() == 0) return;
2021

21-
if (msg.length() > 0 && _onMsg) {
22-
_onMsg(msg, _udp.remoteIP(), _udp.remotePort());
23-
}
22+
if (msg.startsWith("EDGELINK_STATUS:")) { _dispatchStatus(msg); return; }
23+
if (msg.startsWith("EDGELINK_")) return; // other control prefixes — drop
24+
25+
if (_onMsg) _onMsg(msg, _udp.remoteIP(), _udp.remotePort());
2426
}
2527

2628
bool EdgeLinkUDP::send(const char* host, uint16_t port, const String& message) {
@@ -38,3 +40,22 @@ bool EdgeLinkUDP::send(IPAddress ip, uint16_t port, const String& message) {
3840
void EdgeLinkUDP::onMessage(MessageCallback cb) {
3941
_onMsg = cb;
4042
}
43+
44+
void EdgeLinkUDP::onDeviceStatus(DeviceStatusCallback cb) {
45+
_onStatus = cb;
46+
}
47+
48+
void EdgeLinkUDP::_dispatchStatus(const String& line) {
49+
if (!_onStatus) return;
50+
// line: "EDGELINK_STATUS:STATUS:protocol@ip" or "...:protocol@ip:deviceId"
51+
String body = line.substring(16);
52+
int sep = body.indexOf(':');
53+
if (sep < 0) return;
54+
String stat = body.substring(0, sep);
55+
String rest = body.substring(sep + 1);
56+
bool conn = stat.equalsIgnoreCase("CONNECTED");
57+
int dsep = rest.lastIndexOf(':');
58+
String ep = dsep >= 0 ? rest.substring(0, dsep) : rest;
59+
String devId = dsep >= 0 ? rest.substring(dsep + 1) : "";
60+
_onStatus(conn, ep, devId);
61+
}

SDK/Arduino/EdgeLink/src/EdgeLinkUDP.h

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@
44

55
class EdgeLinkUDP {
66
public:
7-
using MessageCallback = void (*)(const String& message, IPAddress remoteIP, uint16_t remotePort);
7+
using MessageCallback = void (*)(const String& message, IPAddress remoteIP, uint16_t remotePort);
8+
// EDGELINK_STATUS event from EdgeLink Server (timeout-based for UDP).
9+
// Parameters: isConnected, endpoint (e.g. "UDPPort@192.168.1.50"), deviceId.
10+
using DeviceStatusCallback = void (*)(bool connected, const String& endpoint, const String& deviceId);
811

912
explicit EdgeLinkUDP(UDP& udp);
1013

@@ -18,10 +21,14 @@ class EdgeLinkUDP {
1821
bool send(const char* host, uint16_t port, const String& message);
1922
bool send(IPAddress ip, uint16_t port, const String& message);
2023

21-
// Callback for incoming messages
24+
// Callbacks (EDGELINK_* control messages are filtered out of onMessage)
2225
void onMessage(MessageCallback cb);
26+
void onDeviceStatus(DeviceStatusCallback cb);
2327

2428
private:
25-
UDP& _udp;
26-
MessageCallback _onMsg = nullptr;
29+
UDP& _udp;
30+
MessageCallback _onMsg = nullptr;
31+
DeviceStatusCallback _onStatus = nullptr;
32+
33+
void _dispatchStatus(const String& line);
2734
};

SDK/CSharp/EdgeLinkUdpClient.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ public class EdgeLinkUdpClient : IDisposable
1313
{
1414
public event Action<string>? OnMessage;
1515
public event Action<Exception>? OnError;
16+
/// <summary>Fired when an upstream device starts/stops sending packets to EdgeLink Server (timeout-based).
17+
/// Parameters: isConnected, endpoint (e.g. "UDPPort@192.168.1.50"), deviceId (parsed from message id field).</summary>
18+
public event Action<bool, string, string>? OnDeviceStatus;
1619

1720
public int LocalPort { get; }
1821
public bool IsRunning => !disposed && !cts.IsCancellationRequested;
@@ -45,6 +48,22 @@ private async Task ReceiveLoopAsync(CancellationToken ct)
4548
string msg = Encoding.UTF8.GetString(result.Buffer).Trim();
4649
if (string.IsNullOrEmpty(msg)) continue;
4750

51+
if (msg.StartsWith("EDGELINK_STATUS:", StringComparison.Ordinal))
52+
{
53+
// body: "STATUS:protocol@ip" or "STATUS:protocol@ip:deviceId"
54+
string body = msg[16..];
55+
int sep = body.IndexOf(':');
56+
string statusStr = sep >= 0 ? body[..sep] : body;
57+
string rest = sep >= 0 ? body[(sep + 1)..] : "";
58+
bool connected = statusStr.Equals("CONNECTED", StringComparison.OrdinalIgnoreCase);
59+
int devSep = rest.LastIndexOf(':');
60+
string endpoint = devSep >= 0 ? rest[..devSep] : rest;
61+
string deviceId = devSep >= 0 ? rest[(devSep + 1)..] : "";
62+
OnDeviceStatus?.Invoke(connected, endpoint, deviceId);
63+
continue;
64+
}
65+
if (msg.StartsWith("EDGELINK_", StringComparison.Ordinal)) continue;
66+
4867
queue.Enqueue(msg);
4968
OnMessage?.Invoke(msg);
5069
}

SDK/JavaScript/src/EdgeLinkUdpClient.js

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@ const EventEmitter = require("events");
77
* UDP receiver — binds to a local port and emits "message" for each packet.
88
*
99
* Events:
10-
* "message" (string)
11-
* "error" (Error)
10+
* "message" (string)
11+
* "deviceStatus" (connected: boolean, endpoint: string, deviceId: string)
12+
* Fired when an upstream device starts/stops sending packets to EdgeLink Server (timeout-based).
13+
* "error" (Error)
1214
*/
1315
class EdgeLinkUdpClient extends EventEmitter {
1416
/**
@@ -26,7 +28,24 @@ class EdgeLinkUdpClient extends EventEmitter {
2628

2729
this._socket.on("message", (buf) => {
2830
const msg = buf.toString("utf8").trim();
29-
if (msg) this.emit("message", msg);
31+
if (!msg) return;
32+
33+
if (msg.startsWith("EDGELINK_STATUS:")) {
34+
// body: "STATUS:protocol@ip" or "STATUS:protocol@ip:deviceId"
35+
const body = msg.slice(16);
36+
const sep = body.indexOf(":");
37+
const status = sep >= 0 ? body.slice(0, sep) : body;
38+
const rest = sep >= 0 ? body.slice(sep + 1) : "";
39+
const connected = status.toUpperCase() === "CONNECTED";
40+
const devSep = rest.lastIndexOf(":");
41+
const endpoint = devSep >= 0 ? rest.slice(0, devSep) : rest;
42+
const deviceId = devSep >= 0 ? rest.slice(devSep + 1) : "";
43+
this.emit("deviceStatus", connected, endpoint, deviceId);
44+
return;
45+
}
46+
if (msg.startsWith("EDGELINK_")) return;
47+
48+
this.emit("message", msg);
3049
});
3150

3251
this._socket.on("error", (err) => this.emit("error", err));

SDK/Python/edgelink/udp.py

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,9 @@ class EdgeLinkUdpClient:
2020

2121
def __init__(self, local_port: int) -> None:
2222
self.local_port = local_port
23-
self._on_message: list[Callable[[str], None]] = []
24-
self._on_error: list[Callable[[Exception], None]] = []
23+
self._on_message: list[Callable[[str], None]] = []
24+
self._on_error: list[Callable[[Exception], None]] = []
25+
self._on_device_status: list[Callable[[bool, str, str], None]] = []
2526
self._queue: deque[str] = deque()
2627
self._transport: asyncio.BaseTransport | None = None
2728
self.is_running = False
@@ -32,6 +33,10 @@ def on_message(self, cb: Callable[[str], None]) -> None:
3233
def on_error(self, cb: Callable[[Exception], None]) -> None:
3334
self._on_error.append(cb)
3435

36+
def on_device_status(self, cb: Callable[[bool, str, str], None]) -> None:
37+
"""cb(is_connected: bool, endpoint: str, device_id: str) — fired when an upstream device starts/stops sending packets (timeout-based)."""
38+
self._on_device_status.append(cb)
39+
3540
async def start(self) -> None:
3641
loop = asyncio.get_running_loop()
3742
self._transport, _ = await loop.create_datagram_endpoint(
@@ -52,6 +57,23 @@ def _receive(self, data: bytes, _addr: tuple) -> None:
5257
msg = data.decode(errors="replace").strip()
5358
if not msg:
5459
return
60+
61+
if msg.startswith("EDGELINK_STATUS:"):
62+
# body: "STATUS:protocol@ip" or "STATUS:protocol@ip:deviceId"
63+
body = msg[16:]
64+
sep = body.find(":")
65+
status = body[:sep] if sep >= 0 else body
66+
rest = body[sep + 1:] if sep >= 0 else ""
67+
connected = status.upper() == "CONNECTED"
68+
dev_sep = rest.rfind(":")
69+
endpoint = rest[:dev_sep] if dev_sep >= 0 else rest
70+
device_id = rest[dev_sep + 1:] if dev_sep >= 0 else ""
71+
for cb in self._on_device_status:
72+
cb(connected, endpoint, device_id)
73+
return
74+
if msg.startswith("EDGELINK_"):
75+
return
76+
5577
self._queue.append(msg)
5678
for cb in self._on_message:
5779
cb(msg)

SDK/Unity/Package/Runtime/EdgeLinkManager.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,8 @@ private async void Connect()
156156

157157
case Protocol.UDP:
158158
_udp = new EdgeLinkUdpClient(udpLocalPort);
159-
_udp.OnError += ex => Debug.LogWarning($"[EdgeLink UDP] {ex.Message}");
159+
_udp.OnError += ex => Debug.LogWarning($"[EdgeLink UDP] {ex.Message}");
160+
_udp.OnDeviceStatus += (c, ep, id) => _deviceStatusQ.Enqueue((c, ep, id));
160161
_udp.Start();
161162
Debug.Log($"[EdgeLink UDP] Listening on port {udpLocalPort}");
162163
break;

SDK/Unity/Package/Runtime/EdgeLinkUdpClient.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ public class EdgeLinkUdpClient : IDisposable
1111
{
1212
public event Action<string>? OnMessage;
1313
public event Action<Exception>? OnError;
14+
/// <summary>Fired when an upstream device starts/stops sending packets to EdgeLink Server (timeout-based).
15+
/// Parameters: isConnected, endpoint (e.g. "UDPPort@192.168.1.50"), deviceId (parsed from message id field).</summary>
16+
public event Action<bool, string, string>? OnDeviceStatus;
1417

1518
public int LocalPort { get; }
1619
public bool IsRunning => !disposed && cts != null && !cts.IsCancellationRequested;
@@ -43,6 +46,22 @@ private async Task ReceiveLoopAsync(CancellationToken ct)
4346
string msg = Encoding.UTF8.GetString(result.Buffer).Trim();
4447
if (string.IsNullOrEmpty(msg)) continue;
4548

49+
if (msg.StartsWith("EDGELINK_STATUS:", StringComparison.Ordinal))
50+
{
51+
// body: "STATUS:protocol@ip" or "STATUS:protocol@ip:deviceId"
52+
string body = msg.Substring(16);
53+
int sep = body.IndexOf(':');
54+
string statusStr = sep >= 0 ? body.Substring(0, sep) : body;
55+
string rest = sep >= 0 ? body.Substring(sep + 1) : "";
56+
bool connected = statusStr.Equals("CONNECTED", StringComparison.OrdinalIgnoreCase);
57+
int devSep = rest.LastIndexOf(':');
58+
string endpoint = devSep >= 0 ? rest.Substring(0, devSep) : rest;
59+
string deviceId = devSep >= 0 ? rest.Substring(devSep + 1) : "";
60+
OnDeviceStatus?.Invoke(connected, endpoint, deviceId);
61+
continue;
62+
}
63+
if (msg.StartsWith("EDGELINK_", StringComparison.Ordinal)) continue;
64+
4665
queue.Enqueue(msg);
4766
}
4867
catch (OperationCanceledException) { return; }

0 commit comments

Comments
 (0)