using System;
using System.Collections.Concurrent;
using IvfTl.Hardware;
using Newtonsoft.Json.Linq;
namespace IvfTl.ControlHost.Debug
{
///
/// control 端调试会话后端:会话表 + 借用/归还 + 心跳续约 + 超时自动回收 + 命令分发。
/// 安全地基(spec §5):绝不指望 operate 主动还,SweepExpired 超时回收兜底。
///
public sealed class DebugSessionManager
{
private readonly Func _gateOf;
private readonly Func _clock;
private readonly int _ttlMs;
private readonly Action _log;
private readonly ConcurrentDictionary _sessions = new ConcurrentDictionary();
public DebugSessionManager(Func gateOf, Func clock, int ttlMs, Action 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", "会话不存在或已过期");
}
/// 只读取会话(供推流端点校验 sid)。不刷新 LastSeen、不改状态。
public bool TryGet(string sid, out DebugSession session)
{
if (sid != null) return _sessions.TryGetValue(sid, out session);
session = null; return false;
}
public DebugCommandResult Release(string sid)
{
if (sid != null && _sessions.TryRemove(sid, out var s))
{
try { s.Lease.Dispose(); } catch (Exception ex) { _log($"[debug] dispose 异常 sid={sid} 舱{s.HouseSn}: {ex.Message}"); }
_log($"[debug] release sid={sid} 舱{s.HouseSn}");
}
return DebugCommandResult.Okay();
}
/// 超时回收:LastSeen + ttl < now 的会话自动归还(spec §5.1)。
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 (Exception ex) { _log($"[debug] dispose 异常 sid={kv.Key} 舱{s.HouseSn}: {ex.Message}"); }
_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)
{
int Arg(string k, int def = 0) => args?[k] != null ? args[k].Value() : def;
int delay = Arg("motorDelay", -1);
switch (op)
{
case "VerticalReset": { bool ok = ser.VerticalResetWait(delay); if (ok) s.CurrentVer = 0; return DebugCommandResult.Okay(ok); }
case "VerticalMoveTo":
{
int pos = Arg("pos");
if (!MotorClamp.IsVerticalInRange(pos)) return DebugCommandResult.Fail("OUT_OF_RANGE", $"垂直目标{pos}越界[0,{MotorClamp.VerMax}]");
bool ok = ser.VerticalMoveToWait(pos, delay); if (ok) s.CurrentVer = pos; return DebugCommandResult.Okay(ok);
}
case "VerticalForward":
case "VerticalBackward":
{
// 红线钳位必须基于回读的真实物理位,不信任会话跟踪位(防真机已在高位时相对运动越红线)。
int basePos = ser.ReadVerticalPositionWait();
int delta = Arg("value") * (op == "VerticalBackward" ? -1 : 1);
int target = MotorClamp.RelativeTarget(basePos, delta);
if (!MotorClamp.IsVerticalInRange(target)) return DebugCommandResult.Fail("OUT_OF_RANGE", $"垂直目标{target}越界");
bool ok = op == "VerticalBackward" ? ser.VerticalBackwardWait(Arg("value"), delay) : ser.VerticalForwardWait(Arg("value"), delay);
if (ok) s.CurrentVer = ser.ReadVerticalPositionWait(); return DebugCommandResult.Okay(ok);
}
case "HorizontalReset": { bool ok = ser.HorizontalResetWait(delay); if (ok) s.CurrentHor = 0; return DebugCommandResult.Okay(ok); }
case "HorizontalMoveTo":
{
int pos = Arg("pos");
if (!MotorClamp.IsHorizontalInRange(pos)) return DebugCommandResult.Fail("OUT_OF_RANGE", $"水平目标{pos}越界[0,{MotorClamp.HorMax}]");
bool ok = ser.HorizontalMoveToWait(pos, delay); if (ok) s.CurrentHor = pos; return DebugCommandResult.Okay(ok);
}
case "HorizontalForward":
case "HorizontalBackward":
{
// 红线钳位必须基于回读的真实物理位,不信任会话跟踪位。
int basePos = ser.ReadHorizontalPositionWait();
int delta = Arg("value") * (op == "HorizontalBackward" ? -1 : 1);
int target = MotorClamp.RelativeTarget(basePos, delta);
if (!MotorClamp.IsHorizontalInRange(target)) return DebugCommandResult.Fail("OUT_OF_RANGE", $"水平目标{target}越界");
bool ok = op == "HorizontalBackward" ? ser.HorizontalBackwardWait(Arg("value"), delay) : ser.HorizontalForwardWait(Arg("value"), delay);
if (ok) s.CurrentHor = ser.ReadHorizontalPositionWait(); return DebugCommandResult.Okay(ok);
}
case "WriteScanStep": return DebugCommandResult.Okay(ser.WriteScanStepWait(Arg("value")));
case "WriteOpenIntakeTime": return DebugCommandResult.Okay(ser.WriteOpenIntakeTimeWait(Arg("value")));
case "WriteOpenVentTime": return DebugCommandResult.Okay(ser.WriteOpenVentTimeWait(Arg("value")));
case "WriteWellHorizontalPos": return DebugCommandResult.Okay(ser.WriteWellHorizontalPosWait(Arg("well"), Arg("hor")));
default: return DebugCommandResult.Fail("BAD_OP", $"未知 op: {op}");
}
}
}
}