using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using SSPCTester.Devices.Config; using SSPCTester.Devices.Drivers.Real; using SSPCTester.Devices.Interfaces; namespace SSPCTester.Devices.Drivers.Mock; /// /// 模拟数据采集卡。 /// 生成逼真的电压上升 / 下降波形:V(t) = Vmax * (1 - exp(-t/τ)) + 噪声。 /// public sealed class MockDaq : MockDeviceBase, IDaq { private readonly Random _rng = new(1024); private readonly DaqOptions _opts; private double? _nextTriggerSec; /// 满量程电压(V)。 public double NominalVoltage { get; set; } = 28.0; /// 满量程电流(A)。 public double NominalCurrent { get; set; } = 1.0; /// 开启延时(秒),用于模拟"控制信号 → 开始上升"的延时。 public double OnDelaySec { get; set; } = 0.0003; /// 关断延时(秒)。 public double OffDelaySec { get; set; } = 0.0003; /// RC 时间常数(秒)。 public double TauSec { get; set; } = 0.0005; /// 下一次 CaptureAsync 的模式:true=上升曲线,false=下降曲线。 public bool NextCaptureIsRising { get; set; } = true; public MockDaq(IOptions options, ILogger logger) : base(logger, "PCIe8586 高速采集卡 (Mock)") { _opts = options.Value.Daq; } public event EventHandler? SampleReceived; public Task> EnumerateDevicesAsync(CancellationToken ct = default) => Task.FromResult>( new[] { new DaqDeviceDescriptor(0, 0, "模拟 PCIe8586", true) }); public async Task CaptureAsync( int[] channels, int sampleRateHz, double durationSec, CancellationToken ct = default) { if (sampleRateHz <= 0) throw new ArgumentOutOfRangeException(nameof(sampleRateHz)); if (durationSec <= 0) throw new ArgumentOutOfRangeException(nameof(durationSec)); // 模拟采集耗时(不全等比例,避免阻塞太久) int simulatedDelayMs = (int)Math.Min(durationSec * 1000.0 * 0.05, 400); await Task.Delay(simulatedDelayMs, ct).ConfigureAwait(false); int n = (int)Math.Round(sampleRateHz * durationSec); var v = new double[n]; var i = new double[n]; double dt = 1.0 / sampleRateHz; bool rising = NextCaptureIsRising; double delay = rising ? OnDelaySec : OffDelaySec; // 触发时刻设在 1/4 处,便于前后查看 double triggerSec = _nextTriggerSec ?? durationSec * 0.25; _nextTriggerSec = null; for (int k = 0; k < n; k++) { double t = k * dt; double tRel = t - triggerSec - delay; // 相对于"开始上升/下降"的时间 double voltage; if (rising) { voltage = tRel <= 0 ? 0 : NominalVoltage * (1 - Math.Exp(-tRel / TauSec)); } else { voltage = tRel <= 0 ? NominalVoltage : NominalVoltage * Math.Exp(-tRel / TauSec); } voltage += NextGaussian() * NominalVoltage * 0.005; v[k] = voltage; double current; if (rising) current = tRel <= 0 ? 0 : NominalCurrent * (1 - Math.Exp(-tRel / TauSec)); else current = tRel <= 0 ? NominalCurrent : NominalCurrent * Math.Exp(-tRel / TauSec); current += NextGaussian() * NominalCurrent * 0.01; i[k] = current; } return new CurveData { SampleRateHz = sampleRateHz, TriggerTimeSec = triggerSec, Voltage = v, Current = i }; } public Task ReadInstantAsync(int[] channels, CancellationToken ct = default) { var result = new double[channels.Length]; for (int k = 0; k < channels.Length; k++) { int physical = channels[k]; var cfg = _opts.Channels.FirstOrDefault(x => x.PhysicalChannel == physical); var role = cfg is null ? DaqChannelRole.Unconfigured : Pcie8586Daq.ParseRole(cfg.Role); result[k] = role switch { DaqChannelRole.OutputVoltage or DaqChannelRole.InputVoltage => NominalVoltage * (1 + (_rng.NextDouble() - 0.5) * 0.004), DaqChannelRole.OutputCurrent or DaqChannelRole.InputCurrent => NominalCurrent * (1 + (_rng.NextDouble() - 0.5) * 0.02), _ => 0 }; } return Task.FromResult(result); } public async Task CaptureAroundActionAsync( DaqActionCaptureRequest request, Func action, CancellationToken ct = default) { // Mock 下也遵循“先采前置窗口,再执行动作”的时序,避免与真卡行为不一致。 await Task.Delay((int)Math.Max(1, request.PreTriggerMs * 0.2), ct).ConfigureAwait(false); double durationSec = (request.PreTriggerMs + request.PostTriggerMs) / 1_000.0; int sampleRate = _opts.SampleRateHz; NextCaptureIsRising = request.IsRising; await action(ct).ConfigureAwait(false); _nextTriggerSec = request.PreTriggerMs / 1_000.0; var curve = await CaptureAsync(new[] { 0 }, sampleRate, durationSec, ct).ConfigureAwait(false); int triggerIndex = (int)Math.Round(sampleRate * request.PreTriggerMs / 1_000.0); return new CurveData { SampleRateHz = curve.SampleRateHz, TriggerSampleIndex = triggerIndex, TriggerTimeSec = triggerIndex / (double)curve.SampleRateHz, Voltage = curve.Voltage, Current = curve.Current, EngineeringChannels = new[] { new CurveChannelData(0, DaqChannelRole.OutputVoltage, "模拟输出电压", "V", 1, 0, curve.Voltage), new CurveChannelData(1, DaqChannelRole.OutputCurrent, "模拟输出电流", "A", 1, 0, curve.Current!) } }; } /// 由 ReadInstantAsync 衍生的"电流读数"接口(基础测试页用)。 public (double volts, double amps) ReadVoltageCurrent(int channel, bool channelOn) { if (!channelOn) return (0.0, 0.0); double v = NominalVoltage * (1 + (_rng.NextDouble() - 0.5) * 0.004); double a = NominalCurrent * (1 + (_rng.NextDouble() - 0.5) * 0.02); return (v, a); } /// Box-Muller 生成高斯噪声。 private double NextGaussian() { double u1 = 1.0 - _rng.NextDouble(); double u2 = 1.0 - _rng.NextDouble(); return Math.Sqrt(-2.0 * Math.Log(u1)) * Math.Cos(2.0 * Math.PI * u2); } /// 仅用于编译期消除"事件未使用"警告。 private void RaiseSample(CurveSample s) => SampleReceived?.Invoke(this, s); }