using S7DataItem = global::S7.Net.Types.DataItem; using S7NetDataType = global::S7.Net.DataType; using VarType = global::S7.Net.VarType; namespace ZB.MOM.WW.OtOpcUa.Driver.S7.Tests; /// /// A scriptable : every connection it creates delegates its wire /// operations to the factory's handlers, so a test scripts the device once and observes every /// connection (including reopened ones). Counts wire operations so a test can prove what did /// NOT go to the wire without timing anything. /// internal sealed class ScriptedS7PlcFactory : IS7PlcFactory { private int _wireOps; /// Every connection created, in order. public List Created { get; } = []; /// Wire operations issued (reads, multi-var reads, writes; not opens or status probes). public int WireOps => Volatile.Read(ref _wireOps); /// Hook run inside every ; the default opens immediately. public Func Open { get; set; } = (_, _) => Task.CompletedTask; /// Per-address string read; the default returns (ushort)42. public Func> Read { get; set; } = (_, _) => Task.FromResult((ushort)42); /// Block read; the default returns count zero bytes. public Func> ReadBytes { get; set; } = (_, _, _, count, _) => Task.FromResult(new byte[count]); /// Multi-var read; the default answers every item with a zero of its var type. public Func, CancellationToken, Task>> ReadMulti { get; set; } = (items, _) => { foreach (var i in items) i.Value = i.VarType switch { VarType.Bit => false, VarType.Byte => (byte)0, VarType.Word => (ushort)0, _ => (uint)0, }; return Task.FromResult>(items); }; /// Per-address string write; the default succeeds. public Func Write { get; set; } = (_, _, _) => Task.CompletedTask; /// Block write; the default succeeds. public Func WriteBytes { get; set; } = (_, _, _, _, _) => Task.CompletedTask; /// CPU-status probe; the default answers. public Func ReadStatus { get; set; } = _ => Task.CompletedTask; /// public IS7Plc Create(S7CpuType cpuType, string host, int port, short rack, short slot, TimeSpan timeout) { lock (Created) { var plc = new ScriptedS7Plc(this, Created.Count); Created.Add(plc); return plc; } } /// Counts one wire operation. internal void CountWireOp() => Interlocked.Increment(ref _wireOps); } /// One connection created by . internal sealed class ScriptedS7Plc(ScriptedS7PlcFactory factory, int index) : IS7Plc { /// Zero-based creation index. public int Index { get; } = index; /// public bool IsConnected { get; private set; } /// True once disposed. public bool Disposed { get; private set; } /// public int MaxPduSize => 240; /// public async Task OpenAsync(CancellationToken cancellationToken) { await factory.Open(Index, cancellationToken).ConfigureAwait(false); IsConnected = true; } /// public Task ReadAsync(string address, CancellationToken cancellationToken) { factory.CountWireOp(); return factory.Read(address, cancellationToken); } /// public Task ReadBytesAsync(S7NetDataType area, int db, int startByteAdr, int count, CancellationToken cancellationToken) { factory.CountWireOp(); return factory.ReadBytes(area, db, startByteAdr, count, cancellationToken); } /// public Task> ReadMultipleVarsAsync(List items, CancellationToken cancellationToken) { factory.CountWireOp(); return factory.ReadMulti(items, cancellationToken); } /// public Task WriteAsync(string address, object value, CancellationToken cancellationToken) { factory.CountWireOp(); return factory.Write(address, value, cancellationToken); } /// public Task WriteBytesAsync(S7NetDataType area, int db, int startByteAdr, byte[] value, CancellationToken cancellationToken) { factory.CountWireOp(); return factory.WriteBytes(area, db, startByteAdr, value, cancellationToken); } /// public Task ReadStatusAsync(CancellationToken cancellationToken) => factory.ReadStatus(cancellationToken); /// public void Close() => IsConnected = false; /// public void Dispose() { Disposed = true; IsConnected = false; } }