| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100 |
- using System;
- using System.Collections.Concurrent;
- using IvfTl.Hardware;
- using Newtonsoft.Json.Linq;
- namespace IvfTl.ControlHost.Debug
- {
- /// <summary>
- /// control 端调试会话后端:会话表 + 借用/归还 + 心跳续约 + 超时自动回收 + 命令分发。
- /// 安全地基(spec §5):绝不指望 operate 主动还,SweepExpired 超时回收兜底。
- /// </summary>
- public sealed class DebugSessionManager
- {
- private readonly Func<int, IHouseGate> _gateOf;
- private readonly Func<DateTime> _clock;
- private readonly int _ttlMs;
- private readonly Action<string> _log;
- private readonly ConcurrentDictionary<string, DebugSession> _sessions = new ConcurrentDictionary<string, DebugSession>();
- public DebugSessionManager(Func<int, IHouseGate> gateOf, Func<DateTime> clock, int ttlMs, Action<string> log)
- {
- _gateOf = gateOf; _clock = clock; _ttlMs = ttlMs; _log = log ?? (_ => { });
- }
- public DebugCommandResult Acquire(int houseSn)
- {
- var gate = _gateOf(houseSn);
- if (gate == null) return DebugCommandResult.Fail("NO_HANDLE", $"舱{houseSn}无闸门");
- var lease = gate.Acquire(HardwareUser.OperateDebug);
- if (lease == null) return DebugCommandResult.Fail("BUSY", $"舱{houseSn}被占用,借用超时");
- string sid = Guid.NewGuid().ToString("N");
- _sessions[sid] = new DebugSession(sid, houseSn, lease, _clock());
- _log($"[debug] acquire 舱{houseSn} sid={sid}");
- return DebugCommandResult.Okay(sid);
- }
- public DebugCommandResult Heartbeat(string sid)
- {
- if (sid != null && _sessions.TryGetValue(sid, out var s)) { s.LastSeen = _clock(); return DebugCommandResult.Okay(); }
- return DebugCommandResult.Fail("SESSION_EXPIRED", "会话不存在或已过期");
- }
- public DebugCommandResult Release(string sid)
- {
- if (sid != null && _sessions.TryRemove(sid, out var s))
- {
- try { s.Lease.Dispose(); } catch { }
- _log($"[debug] release sid={sid} 舱{s.HouseSn}");
- }
- return DebugCommandResult.Okay();
- }
- /// <summary>超时回收:LastSeen + ttl < now 的会话自动归还(spec §5.1)。</summary>
- public int SweepExpired()
- {
- int n = 0; var now = _clock();
- foreach (var kv in _sessions)
- {
- if ((now - kv.Value.LastSeen).TotalMilliseconds > _ttlMs)
- {
- if (_sessions.TryRemove(kv.Key, out var s))
- {
- try { s.Lease.Dispose(); } catch { }
- _log($"[debug] 会话超时自动回收 sid={kv.Key} 舱{s.HouseSn}");
- n++;
- }
- }
- }
- return n;
- }
- public DebugCommandResult Execute(string sid, string op, JObject args)
- {
- if (sid == null || !_sessions.TryGetValue(sid, out var s))
- return DebugCommandResult.Fail("SESSION_EXPIRED", "会话不存在或已过期");
- s.LastSeen = _clock();
- var ser = s.Lease.Serial;
- if (ser == null) return DebugCommandResult.Fail("NO_HANDLE", "借用串口句柄为空");
- try
- {
- switch (op)
- {
- case "ReadTemp": return DebugCommandResult.Okay(ser.TemperatureWait());
- case "ReadPressure": return DebugCommandResult.Okay(ser.PressureWait());
- case "ReadDoor": return DebugCommandResult.Okay(ser.DoorStatusWait().ToString());
- case "ReadVentTime": return DebugCommandResult.Okay(ser.ReadOpenVentTimeWait());
- case "ShakeHands": return DebugCommandResult.Okay(ser.ShakeHandsWait());
- case "OpenLed": return DebugCommandResult.Okay(ser.OpenLedWait());
- case "CloseLed": return DebugCommandResult.Okay(ser.CloseLedWait());
- case "OpenIntake": return DebugCommandResult.Okay(ser.OpenIntakeValveWait());
- case "CloseIntake": return DebugCommandResult.Okay(ser.CloseIntakeValveWait());
- case "OpenExhaust": return DebugCommandResult.Okay(ser.OpenExhaustValveWait());
- case "CloseExhaust": return DebugCommandResult.Okay(ser.CloseExhaustValveWait());
- case "HouseAeration": return DebugCommandResult.Okay(ser.HouseAerationWait());
- case "HouseVent": return DebugCommandResult.Okay(ser.HouseVentWait());
- default:
- return ExecuteMotorOrEeprom(s, ser, op, args);
- }
- }
- catch (Exception ex) { return DebugCommandResult.Fail("HARDWARE_ERROR", ex.Message); }
- }
- // Task7 先占位:返回 BAD_OP,让"未知 op"测试通过;Task7 替换为真实电机/EEPROM 分发。
- private DebugCommandResult ExecuteMotorOrEeprom(DebugSession s, IvfTl.Hardware.ISerialChannel ser, string op, JObject args)
- => DebugCommandResult.Fail("BAD_OP", $"未知 op: {op}");
- }
- }
|