using ZB.MOM.WW.OtOpcUa.Driver.FOCAS; namespace ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Tests; internal class FakeFocasClient : IFocasClient { /// Gets a value indicating whether the client is connected. public bool IsConnected { get; private set; } /// Gets the count of connection attempts. public int ConnectCount { get; private set; } /// Gets the count of dispose operations. public int DisposeCount { get; private set; } /// Gets or sets a value indicating whether to throw on connect. public bool ThrowOnConnect { get; set; } /// Gets or sets a value indicating whether to throw on read. public bool ThrowOnRead { get; set; } /// Gets or sets a value indicating whether to throw on write. public bool ThrowOnWrite { get; set; } /// Gets or sets the result of probe operations. public bool ProbeResult { get; set; } = true; /// Gets or sets the exception to throw. public Exception? Exception { get; set; } /// Gets the dictionary of read values keyed by address. public Dictionary Values { get; } = new(StringComparer.OrdinalIgnoreCase); /// Gets the dictionary of read statuses keyed by address. public Dictionary ReadStatuses { get; } = new(StringComparer.OrdinalIgnoreCase); /// Gets the dictionary of write statuses keyed by address. public Dictionary WriteStatuses { get; } = new(StringComparer.OrdinalIgnoreCase); /// Gets the log of write operations. public List<(FocasAddress addr, FocasDataType type, object? value)> WriteLog { get; } = new(); /// Connects to a FOCAS host asynchronously. /// The FOCAS host address. /// The connection timeout duration. /// The cancellation token. public virtual Task ConnectAsync(FocasHostAddress address, TimeSpan timeout, CancellationToken ct) { ConnectCount++; if (ThrowOnConnect) throw Exception ?? new InvalidOperationException(); IsConnected = true; return Task.CompletedTask; } /// Reads a value from a FOCAS address asynchronously. /// The FOCAS address to read from. /// The data type of the value. /// The cancellation token. public virtual Task<(object? value, uint status)> ReadAsync( FocasAddress address, FocasDataType type, CancellationToken ct) { if (ThrowOnRead) throw Exception ?? new InvalidOperationException(); var key = address.Canonical; var status = ReadStatuses.TryGetValue(key, out var s) ? s : FocasStatusMapper.Good; var value = Values.TryGetValue(key, out var v) ? v : null; return Task.FromResult((value, status)); } /// Writes a value to a FOCAS address asynchronously. /// The FOCAS address to write to. /// The data type of the value. /// The value to write. /// The cancellation token. public virtual Task WriteAsync( FocasAddress address, FocasDataType type, object? value, CancellationToken ct) { if (ThrowOnWrite) throw Exception ?? new InvalidOperationException(); WriteLog.Add((address, type, value)); Values[address.Canonical] = value; var status = WriteStatuses.TryGetValue(address.Canonical, out var s) ? s : FocasStatusMapper.Good; return Task.FromResult(status); } /// Probes the FOCAS connection asynchronously. /// The cancellation token. public virtual Task ProbeAsync(CancellationToken ct) => Task.FromResult(ProbeResult); /// Gets the list of active alarms. public List Alarms { get; } = []; /// Reads active alarms asynchronously. /// The cancellation token. public virtual Task> ReadAlarmsAsync(CancellationToken ct) => Task.FromResult>([.. Alarms]); // ---- Fixed-tree T1 ---- /// Gets or sets the system information. public FocasSysInfo SysInfo { get; set; } = new(0, 3, "M", "M", "30i", "A1.0", 3); /// Gets the list of axis names. public List AxisNames { get; } = [new("X", ""), new("Y", ""), new("Z", "")]; /// Gets the list of spindle names. public List SpindleNames { get; } = [new("S", "1", "", "")]; /// Gets the dictionary of dynamic snapshots keyed by axis index. public Dictionary DynamicByAxis { get; } = []; /// Gets system information asynchronously. /// The cancellation token. public virtual Task GetSysInfoAsync(CancellationToken ct) => Task.FromResult(SysInfo); /// Gets axis names asynchronously. /// The cancellation token. public virtual Task> GetAxisNamesAsync(CancellationToken ct) => Task.FromResult>([.. AxisNames]); /// Gets spindle names asynchronously. /// The cancellation token. public virtual Task> GetSpindleNamesAsync(CancellationToken ct) => Task.FromResult>([.. SpindleNames]); /// Reads dynamic data for an axis asynchronously. /// The zero-based axis index. /// The cancellation token. public virtual Task ReadDynamicAsync(int axisIndex, CancellationToken ct) { if (!DynamicByAxis.TryGetValue(axisIndex, out var snap)) snap = new FocasDynamicSnapshot(axisIndex, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); return Task.FromResult(snap); } /// Gets or sets the program information. public FocasProgramInfo ProgramInfo { get; set; } = new("O0001", 1, 0, 1); /// Gets program information asynchronously. /// The cancellation token. public virtual Task GetProgramInfoAsync(CancellationToken ct) => Task.FromResult(ProgramInfo); /// Gets the dictionary of timers keyed by timer kind. public Dictionary Timers { get; } = []; /// Gets timer data asynchronously. /// The timer kind to retrieve. /// The cancellation token. public virtual Task GetTimerAsync(FocasTimerKind kind, CancellationToken ct) { if (!Timers.TryGetValue(kind, out var t)) t = new FocasTimer(kind, 0, 0); return Task.FromResult(t); } /// Gets the list of servo loads. public List ServoLoads { get; } = []; /// Gets servo loads asynchronously. /// The cancellation token. public virtual Task> GetServoLoadsAsync(CancellationToken ct) => Task.FromResult>([.. ServoLoads]); /// Gets the list of spindle loads. public List SpindleLoads { get; } = []; /// Gets the list of spindle maximum RPMs. public List SpindleMaxRpms { get; } = []; /// Gets spindle loads asynchronously. /// The cancellation token. public virtual Task> GetSpindleLoadsAsync(CancellationToken ct) => Task.FromResult>([.. SpindleLoads]); /// Gets spindle maximum RPMs asynchronously. /// The cancellation token. public virtual Task> GetSpindleMaxRpmsAsync(CancellationToken ct) => Task.FromResult>([.. SpindleMaxRpms]); /// Disposes the client. public virtual void Dispose() { DisposeCount++; IsConnected = false; } } /// A factory for creating fake FOCAS clients. internal sealed class FakeFocasClientFactory : IFocasClientFactory { /// Gets the list of created clients. public List Clients { get; } = new(); /// Gets or sets a customization function for creating clients. public Func? Customise { get; set; } /// Creates a fake FOCAS client. public IFocasClient Create() { var c = Customise?.Invoke() ?? new FakeFocasClient(); Clients.Add(c); return c; } }