Skip to content

Commit 30c3d16

Browse files
Reymerclaude
andcommitted
[新增] TCPListener 模式模擬情境(FakeArduino + FakeUnity)
包含 EdgeLink Server 設定步驟、執行順序說明, 可在沒有真實 ESP32 的情況下完整驗證 TCPListener 架構。 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 30726c7 commit 30c3d16

5 files changed

Lines changed: 272 additions & 0 deletions

File tree

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<TargetFramework>net6.0</TargetFramework>
6+
<Nullable>enable</Nullable>
7+
<ImplicitUsings>enable</ImplicitUsings>
8+
<AssemblyName>FakeArduino</AssemblyName>
9+
</PropertyGroup>
10+
11+
</Project>
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/**
2+
* FakeArduino — 模擬 ESP32 感測器
3+
*
4+
* 模擬情境:TCPListener 模式
5+
* FakeArduino ──TCP──▶ EdgeLink Port 8888 (TCP Server)
6+
* ↓ 路由轉發
7+
* EdgeLink Port 9001 (TCP Client) ──TCP──▶ FakeUnity
8+
*
9+
* 執行前請先在 EdgeLink Web UI 設定:
10+
* 1. 新增 TCP Server Port 8888(Arduino 連入)
11+
* 2. 新增 TCP Client Port 9001,Host = 執行 FakeUnity 的機器 IP
12+
* SourceProtocolId = Port 8888 的 ID(路由設定)
13+
*/
14+
15+
using System.Net.Sockets;
16+
using System.Text;
17+
18+
const string HOST = "127.0.0.1"; // EdgeLink Server IP
19+
const int PORT = 8888; // EdgeLink TCP Server Port
20+
const int SEND_MS = 2000; // 每 2 秒送一筆
21+
22+
int seq = 0;
23+
float baseTemp = 25.0f;
24+
25+
Console.WriteLine($"[FakeArduino] 連線到 EdgeLink {HOST}:{PORT}");
26+
27+
while (true)
28+
{
29+
try
30+
{
31+
using var client = new TcpClient();
32+
client.NoDelay = true;
33+
await client.ConnectAsync(HOST, PORT);
34+
Console.WriteLine("[FakeArduino] 已連線,開始傳送資料...");
35+
36+
using var stream = client.GetStream();
37+
var lineBuf = new StringBuilder();
38+
39+
// 讀取 PING 並回應 PONG(背景執行)
40+
_ = Task.Run(async () =>
41+
{
42+
var buf = new byte[256];
43+
while (client.Connected)
44+
{
45+
try
46+
{
47+
int n = await stream.ReadAsync(buf);
48+
if (n == 0) break;
49+
lineBuf.Append(Encoding.UTF8.GetString(buf, 0, n));
50+
int idx;
51+
while ((idx = lineBuf.ToString().IndexOf('\n')) >= 0)
52+
{
53+
string line = lineBuf.ToString(0, idx).Trim();
54+
lineBuf.Remove(0, idx + 1);
55+
if (line.StartsWith("EDGELINK_PING:"))
56+
{
57+
string pong = $"EDGELINK_PONG:{line[14..]}\n";
58+
await stream.WriteAsync(Encoding.UTF8.GetBytes(pong));
59+
Console.WriteLine($"[FakeArduino] ← PING → PONG");
60+
}
61+
}
62+
}
63+
catch { break; }
64+
}
65+
});
66+
67+
// 定期送感測資料
68+
while (client.Connected)
69+
{
70+
seq++;
71+
float temp = baseTemp + MathF.Sin(seq * 0.3f) * 3f;
72+
float humid = 60f + MathF.Cos(seq * 0.2f) * 5f;
73+
74+
string msg = $"id:ESP32_SIM;seq:{seq};temp:{temp:F1};humid:{humid:F1}";
75+
byte[] bytes = Encoding.UTF8.GetBytes(msg + "\n");
76+
await stream.WriteAsync(bytes);
77+
Console.WriteLine($"[FakeArduino] 送出 → {msg}");
78+
79+
await Task.Delay(SEND_MS);
80+
}
81+
}
82+
catch (Exception ex)
83+
{
84+
Console.WriteLine($"[FakeArduino] 連線失敗: {ex.Message},5 秒後重試...");
85+
}
86+
87+
await Task.Delay(5000);
88+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<TargetFramework>net6.0</TargetFramework>
6+
<Nullable>enable</Nullable>
7+
<ImplicitUsings>enable</ImplicitUsings>
8+
<AssemblyName>FakeUnity</AssemblyName>
9+
</PropertyGroup>
10+
11+
<ItemGroup>
12+
<ProjectReference Include="..\..\..\EdgeLink.csproj" />
13+
</ItemGroup>
14+
15+
</Project>
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
/**
2+
* FakeUnity — 模擬 Unity TCPListener 模式
3+
*
4+
* 模擬情境:TCPListener 模式
5+
* FakeArduino ──TCP──▶ EdgeLink Port 8888 (TCP Server)
6+
* ↓ 路由轉發
7+
* EdgeLink Port 9001 (TCP Client) ──TCP──▶ FakeUnity(此程式)
8+
*
9+
* FakeUnity 開啟本地 Port 等待 EdgeLink 主動連入,
10+
* 收到訊息後解析 id / temp / humid 欄位,並模擬設備斷線偵測。
11+
*/
12+
13+
using EdgeLink;
14+
15+
const int LOCAL_PORT = 9001; // EdgeLink TCP Client 會連到這個 Port
16+
const float TIMEOUT_SECONDS = 20f; // 超過幾秒沒資料視為設備斷線
17+
18+
var listener = new EdgeLinkTcpListener(LOCAL_PORT);
19+
20+
// ── 連線狀態 ─────────────────────────────────────────────────────────────────
21+
listener.OnConnected += () => Console.WriteLine("[FakeUnity] EdgeLink 已連入");
22+
listener.OnDisconnected += () => Console.WriteLine("[FakeUnity] EdgeLink 已斷線");
23+
listener.OnError += ex => Console.WriteLine($"[FakeUnity] 錯誤: {ex.Message}");
24+
25+
// ── 設備上線 / 斷線通知(TCP,拔電後 ~15 秒)──────────────────────────────
26+
listener.OnDeviceStatus += (connected, endpoint) =>
27+
{
28+
string ip = endpoint.Contains("@") ? endpoint.Split('@')[1] : endpoint;
29+
Console.ForegroundColor = connected ? ConsoleColor.Green : ConsoleColor.Red;
30+
Console.WriteLine(connected
31+
? $"[FakeUnity] ▲ 設備上線 IP: {ip}"
32+
: $"[FakeUnity] ▼ 設備斷線 IP: {ip}");
33+
Console.ResetColor();
34+
};
35+
36+
// ── 訊息處理 ──────────────────────────────────────────────────────────────────
37+
var lastSeen = new Dictionary<string, DateTime>();
38+
var timedOut = new HashSet<string>();
39+
40+
listener.OnMessage += msg =>
41+
{
42+
// 解析 key:value;key:value 格式
43+
var fields = new Dictionary<string, string>();
44+
foreach (var part in msg.Split(';'))
45+
{
46+
int i = part.IndexOf(':');
47+
if (i < 0) continue;
48+
fields[part[..i].Trim()] = part[(i + 1)..].Trim();
49+
}
50+
51+
fields.TryGetValue("id", out string? id);
52+
fields.TryGetValue("temp", out string? temp);
53+
fields.TryGetValue("humid", out string? humid);
54+
fields.TryGetValue("seq", out string? seq);
55+
56+
Console.WriteLine($"[FakeUnity] [{id ?? "?"}] seq={seq} temp={temp}°C humid={humid}%");
57+
58+
// 更新 lastSeen,處理重新上線
59+
if (id != null)
60+
{
61+
if (timedOut.Remove(id))
62+
{
63+
Console.ForegroundColor = ConsoleColor.Cyan;
64+
Console.WriteLine($"[FakeUnity] ↑ {id} 重新上線");
65+
Console.ResetColor();
66+
}
67+
lastSeen[id] = DateTime.UtcNow;
68+
}
69+
};
70+
71+
listener.Start();
72+
Console.WriteLine($"[FakeUnity] 監聽 Port {LOCAL_PORT},等待 EdgeLink 連入...");
73+
Console.WriteLine("[FakeUnity] 按 Ctrl+C 結束\n");
74+
75+
// ── Timeout 偵測迴圈(模擬 Unity Update())────────────────────────────────────
76+
using var cts = new CancellationTokenSource();
77+
Console.CancelKeyPress += (_, e) => { e.Cancel = true; cts.Cancel(); };
78+
79+
try
80+
{
81+
while (!cts.Token.IsCancellationRequested)
82+
{
83+
await Task.Delay(1000, cts.Token);
84+
85+
foreach (var kv in lastSeen)
86+
{
87+
double elapsed = (DateTime.UtcNow - kv.Value).TotalSeconds;
88+
if (elapsed > TIMEOUT_SECONDS && !timedOut.Contains(kv.Key))
89+
{
90+
timedOut.Add(kv.Key);
91+
Console.ForegroundColor = ConsoleColor.Yellow;
92+
Console.WriteLine($"[FakeUnity] ⚠ {kv.Key} 超時 {elapsed:F0}s,可能已離線");
93+
Console.ResetColor();
94+
}
95+
}
96+
}
97+
}
98+
catch (OperationCanceledException) { }
99+
100+
listener.Stop();
101+
Console.WriteLine("[FakeUnity] 已停止");
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# TCPListener 模式模擬情境
2+
3+
## 架構
4+
5+
```
6+
FakeArduino ──TCP──▶ EdgeLink Port 8888 (TCP Server)
7+
↓ 路由轉發
8+
EdgeLink Port 9001 (TCP Client) ──TCP──▶ FakeUnity
9+
```
10+
11+
## EdgeLink Server 設定步驟
12+
13+
1. 開啟 EdgeLink Web UI(https://localhost:8443)
14+
2. 新增 **TCP Server Port 8888**(等待 Arduino 連入)
15+
3. 新增 **TCP Client Port 9001**
16+
- Host:執行 FakeUnity 的機器 IP(本機測試填 `127.0.0.1`
17+
- Port:`9001`
18+
- SourceProtocolId:選擇 Port 8888 的 ID(路由設定)
19+
20+
## 執行順序
21+
22+
```bash
23+
# 終端機 1 — 先啟動 FakeUnity(開 Port 等待)
24+
cd FakeUnity
25+
dotnet run
26+
27+
# 終端機 2 — 再啟動 EdgeLink Server(publish 目錄)
28+
EdgeLinkServer.exe
29+
30+
# 終端機 3 — 最後啟動 FakeArduino(模擬感測器)
31+
cd FakeArduino
32+
dotnet run
33+
```
34+
35+
## 預期輸出
36+
37+
**FakeUnity**
38+
```
39+
[FakeUnity] 監聽 Port 9001,等待 EdgeLink 連入...
40+
[FakeUnity] EdgeLink 已連入
41+
[FakeUnity] ▲ 設備上線 IP: 127.0.0.1
42+
[FakeUnity] [ESP32_SIM] seq=1 temp=25.0°C humid=60.0%
43+
[FakeUnity] [ESP32_SIM] seq=2 temp=25.9°C humid=59.0%
44+
...
45+
# 關閉 FakeArduino 後約 15 秒
46+
[FakeUnity] ▼ 設備斷線 IP: 127.0.0.1
47+
[FakeUnity] ⚠ ESP32_SIM 超時 20s,可能已離線
48+
```
49+
50+
**FakeArduino**
51+
```
52+
[FakeArduino] 已連線,開始傳送資料...
53+
[FakeArduino] 送出 → id:ESP32_SIM;seq:1;temp:25.0;humid:60.0
54+
[FakeArduino] ← PING → PONG
55+
[FakeArduino] 送出 → id:ESP32_SIM;seq:2;temp:25.9;humid:59.0
56+
...
57+
```

0 commit comments

Comments
 (0)