|
|
@@ -0,0 +1,98 @@
|
|
|
+using System;
|
|
|
+using System.Net.Http;
|
|
|
+using System.Text;
|
|
|
+using System.Threading;
|
|
|
+using System.Threading.Tasks;
|
|
|
+using Newtonsoft.Json;
|
|
|
+
|
|
|
+namespace ivf_tl_Operate.Debug
|
|
|
+{
|
|
|
+ /// <summary>
|
|
|
+ /// operate 端调试会话客户端:封 acquire/command/release/heartbeat 的本地 HTTP + 心跳定时器 + 会话失效回调。
|
|
|
+ /// 一次会话:AcquireAsync 成功起心跳,ReleaseAsync 停心跳。
|
|
|
+ /// 任一请求返回 code=SESSION_EXPIRED(control 端 TTL 已回收该会话)→ 触发 OnSessionExpired(UI 据此提示"请重新进入调试")并停心跳。
|
|
|
+ /// 心跳是必需的:control 端 10s TTL 看门狗收不到心跳会自动回收会话、把借用的舱还回去恢复采集。
|
|
|
+ /// </summary>
|
|
|
+ public sealed class DebugSessionClient : IDisposable
|
|
|
+ {
|
|
|
+ private readonly string _baseUrl;
|
|
|
+ private readonly HttpClient _http;
|
|
|
+ private string _sessionId;
|
|
|
+ private Timer _heartbeatTimer;
|
|
|
+ private volatile bool _expiredFired;
|
|
|
+
|
|
|
+ /// <summary>心跳间隔(ms),默认 2.5s(远小于 control 端 10s TTL,容忍偶发卡顿)。</summary>
|
|
|
+ public int HeartbeatIntervalMs { get; set; } = 2500;
|
|
|
+
|
|
|
+ /// <summary>会话失效回调(收到 SESSION_EXPIRED 时触发一次)。</summary>
|
|
|
+ public Action OnSessionExpired { get; set; }
|
|
|
+
|
|
|
+ public string SessionId => _sessionId;
|
|
|
+
|
|
|
+ public DebugSessionClient(string baseUrl, HttpClient http = null)
|
|
|
+ {
|
|
|
+ _baseUrl = baseUrl.TrimEnd('/');
|
|
|
+ _http = http ?? new HttpClient();
|
|
|
+ }
|
|
|
+
|
|
|
+ private async Task<AcquireResult> PostAsync(string path, object body)
|
|
|
+ {
|
|
|
+ var content = new StringContent(body == null ? "{}" : JsonConvert.SerializeObject(body), Encoding.UTF8, "application/json");
|
|
|
+ var resp = await _http.PostAsync($"{_baseUrl}{path}", content);
|
|
|
+ string s = await resp.Content.ReadAsStringAsync();
|
|
|
+ var r = JsonConvert.DeserializeObject<AcquireResult>(s) ?? new AcquireResult { Ok = false, Error = "空响应" };
|
|
|
+ if (r.Code == "SESSION_EXPIRED" && !_expiredFired)
|
|
|
+ {
|
|
|
+ _expiredFired = true;
|
|
|
+ StopHeartbeat();
|
|
|
+ try { OnSessionExpired?.Invoke(); } catch { }
|
|
|
+ }
|
|
|
+ return r;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>借用某舱。成功则记录 sessionId 并起心跳。返回体含 cultivating/embryoCount(供��认框)。</summary>
|
|
|
+ public async Task<AcquireResult> AcquireAsync(int houseSn)
|
|
|
+ {
|
|
|
+ var r = await PostAsync("/debug/acquire", new { houseSn });
|
|
|
+ if (r.Ok && !string.IsNullOrEmpty(r.SessionId))
|
|
|
+ {
|
|
|
+ _sessionId = r.SessionId;
|
|
|
+ _expiredFired = false;
|
|
|
+ StartHeartbeat();
|
|
|
+ }
|
|
|
+ return r;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>在当前会话上执行一个操作(电机/读数/阀门等)。</summary>
|
|
|
+ public Task<AcquireResult> CommandAsync(string op, object args)
|
|
|
+ => PostAsync("/debug/command", new { sessionId = _sessionId, op, args });
|
|
|
+
|
|
|
+ /// <summary>归还会话(幂等):停心跳、清 sessionId、通知 control。多次调用不抛。</summary>
|
|
|
+ public async Task ReleaseAsync()
|
|
|
+ {
|
|
|
+ StopHeartbeat();
|
|
|
+ var sid = _sessionId;
|
|
|
+ _sessionId = null;
|
|
|
+ if (sid != null) { try { await PostAsync("/debug/release", new { sessionId = sid }); } catch { } }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void StartHeartbeat()
|
|
|
+ {
|
|
|
+ StopHeartbeat();
|
|
|
+ _heartbeatTimer = new Timer(async _ =>
|
|
|
+ {
|
|
|
+ var sid = _sessionId;
|
|
|
+ if (sid == null) return;
|
|
|
+ try { await PostAsync("/debug/heartbeat", new { sessionId = sid }); } catch { }
|
|
|
+ }, null, HeartbeatIntervalMs, HeartbeatIntervalMs);
|
|
|
+ }
|
|
|
+
|
|
|
+ private void StopHeartbeat()
|
|
|
+ {
|
|
|
+ try { _heartbeatTimer?.Dispose(); } catch { }
|
|
|
+ _heartbeatTimer = null;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void Dispose() { StopHeartbeat(); }
|
|
|
+ }
|
|
|
+}
|