public async Task Check_WriteItemsInOrderAsync_Fails() { // Arrange var items = new IPlcItem[] { new BytePlcItem(0, 0), new BytePlcItem(0, 0), new BytePlcItem(0, 0), }; var plc = Mock.Of <IPlc>(); Mock.Get(plc) .SetupSequence(p => p.WriteItemsAsync(It.IsAny <ICollection <IPlcItem> >(), CancellationToken.None)) .ReturnsAsync(true) .ReturnsAsync(false) .ReturnsAsync(true) ; // Act var result = await plc.WriteItemsInOrderAsync(items); // Assert Assert.False(result); Mock.Get(plc).Verify(p => p.WriteItemsAsync(It.IsAny <ICollection <IPlcItem> >(), CancellationToken.None), Times.Exactly(2)); }
public async Task Check_Logging_When_Reading() { // Arrange var logger = Mock.Of <ILogger>(); LogManager.LoggerFactory = () => logger; LogManager.LogAllReadAndWriteOperations = true; var byteItem = new BytePlcItem(dataBlock: 0, position: 0, initialValue: Byte.MaxValue); ICollection <IPlcItem> items = new IPlcItem[] { byteItem, byteItem.Clone(), byteItem.Clone() }; var plcMock = new Mock <Plc>(Guid.NewGuid().ToString()); plcMock .Setup(p => p.ReadItemsAsync(It.IsAny <IList <IPlcItem> >(), CancellationToken.None)) #if NET45 .Returns(CompletedTask) #else .Returns(Task.CompletedTask) #endif ; var plc = plcMock.Object; // Act await plc.ReadItemsAsync(items); // Assert Mock.Get(logger).Verify(l => l.Trace(It.IsAny <string>(), It.IsAny <object[]>()), Times.Exactly(items.Count)); }
public void RemovePlcItem(IPlcItem plcItem) { lock (_lock) { _plcItems.Remove(plcItem); } }
/// <inheritdoc /> public void UnMonitorItem(IPlcItem plcItem) { lock (_lock) { var hashValue = plcItem.GetHashCode(); if (_plcItemHandlers.TryGetValue(hashValue, out var handler)) { handler.RemovePlcItem(plcItem); } } }
/// <summary> /// Gets the polling frequency for the <paramref name="plcItem"/>. /// </summary> /// <param name="plcItem"> The <see cref="IPlcItem"/> whose polling frequency to get. </param> /// <returns> The polling frequency. </returns> public TimeSpan GetPollingFrequencyForPlcItem(IPlcItem plcItem) { // Use the defined value for the item if available. if (plcItem.Identifier != null && base.Contains(plcItem.Identifier)) { return(PlcItemMonitorConfigurations.ValidatePollingFrequency(TimeSpan.FromMilliseconds(base[plcItem.Identifier].PollingFrequency))); } // Otherwise the default polling frequency is used. return(PlcItemMonitorConfigurations.DefaultPollingFrequency); }
private static void TransferValue(IPlcItem plcItem, byte[] data) { if (!plcItem.Value.HandlesFullBytes && plcItem.Value.Length > 1) { var booleans = DataConverter.ToBooleans(data).Skip((byte)plcItem.BitPosition).Take((int)plcItem.Value.Length).ToArray(); plcItem.Value.TransferValuesFrom(booleans); } else { plcItem.Value.TransferValuesFrom(data); } }
public void CheckClone() { // Arrange var data = Enumerable.Range(byte.MinValue, byte.MaxValue).Select(value => (byte)value).ToArray(); IPlcItem plcItem = new BytesPlcItem(dataBlock: 1234, position: 10, initialValue: data); //! Explicitly cast this to 'IPlcItem' so that its 'Value' is a 'BitCollection'. // Act IPlcItem clonedPlcItem = plcItem.Clone(); // Assert Assert.True(plcItem.Equals(clonedPlcItem)); Assert.False(Object.ReferenceEquals(plcItem, clonedPlcItem)); Assert.True(plcItem.Value.Equals(clonedPlcItem.Value)); }
public void Check_WriteItemsInOrderAsync_Throws() { // Arrange var items = new IPlcItem[] { new BytePlcItem(0, 0), new BytePlcItem(0, 0), new BytePlcItem(0, 0), }; var plc = Mock.Of <IPlc>(); Mock.Get(plc) .SetupSequence(p => p.WriteItemsAsync(It.IsAny <ICollection <IPlcItem> >(), CancellationToken.None)) .ReturnsAsync(true) .Returns(() => throw new WritePlcException(new IPlcItem[0], new (IPlcItem FailedItem, string ErrorMessage)[0]))
/// <summary> /// Adds the <paramref name="plcItem"/> to the internal collection of monitored <see cref="IPlcItem"/>s. /// </summary> /// <param name="plcItem"> The additional plc item to monitor. </param> public void AddPlcItem(IPlcItem plcItem) { lock (_lock) { // Don't allow the same item twice. //! The comparison must be done via referential equals, because the items themselves implement IEquatable and therefore may be equal, but not the same. foreach (var existingPlcItem in _plcItems) { if (Object.ReferenceEquals(existingPlcItem, plcItem)) { return; } } _plcItems.Add(plcItem); } }
/// <summary> /// Gets the <see cref="DataRegion"/> for the <paramref name="plcItem"/>. /// </summary> /// <param name="plcItem"> The <see cref="IPlcItem"/> for which to get the <see cref="DataRegion"/>. </param> /// <returns> The matching <see cref="DataRegion"/>. </returns> /// <remarks> The <see cref="DataRegion"/> will automatically be extended to satisfy the range of the <paramref name="plcItem"/>. </remarks> private DataRegion GetDataRegion(IPlcItem plcItem) { DataRegion dataRegion; switch (plcItem.Type) { case PlcItemType.Input: { dataRegion = this.InputDataHolder.Value; break; } case PlcItemType.Output: { dataRegion = this.OutputDataHolder.Value; break; } case PlcItemType.Flags: { dataRegion = this.FlagsDataHolder.Value; break; } default: case PlcItemType.Data: { lock (_dataBlockLock) { if (!this.DataBlockDataHolders.TryGetValue(plcItem.DataBlock, out dataRegion)) { dataRegion = new DataRegion(); this.DataBlockDataHolders.Add(plcItem.DataBlock, dataRegion); } } break; } } dataRegion.TryExtend((uint)(plcItem.Position * 8 + (byte)plcItem.BitPosition + plcItem.Value.Length)); return(dataRegion); }
/// <summary> /// Constructor /// </summary> /// <param name="plc"> <see cref="IPlc"/> instance used to poll for changes in the monitored <see cref="IPlcItem"/>s. </param> /// <param name="plcItem"> The first <see cref="IPlcItem"/> to monitor. </param> /// <param name="pollingFrequency"> The frequency at which to poll for changes. </param> public PlcItemMonitorHandler(IPlc plc, IPlcItem plcItem, TimeSpan pollingFrequency) { // Save parameters. _plc = plc; _pollingFrequency = pollingFrequency; // Initialize fields. _lock = new object(); //_cancellationLock = new object(); // Create a placeholder plc item that is used to update the value. _placeholderItem = plcItem.Clone($"PLACEHOLDER_{plcItem.GetHashCode()}"); // Add the passed plc item as first one to the internal collection. _plcItems = new List <IPlcItem>() { plcItem }; // Start monitoring. this.Start(); }
/// <inheritdoc /> public void MonitorItem(IPlcItem plcItem) { // The hash code must consist of the hash from the plc item and the polling frequency. var plcItemHash = plcItem.GetHashCode(); var pollingFrequency = _plcItemMonitorConfigurations.GetPollingFrequencyForPlcItem(plcItem); var hashValue = new { plcItemHash, pollingFrequency }.GetHashCode(); lock (_lock) { // Check if such a handler exist. if (_plcItemHandlers.TryGetValue(hashValue, out var handler)) { // YES: Just add the item. handler.AddPlcItem(plcItem); } else { // NO: Create and save the handler. handler = new PlcItemMonitorHandler(_plc, plcItem, pollingFrequency); _plcItemHandlers.Add(hashValue, handler); } } }
/// <summary> /// Constructor /// </summary> /// <param name="plcItem"> The <see cref="IPlcItem"/> that changed. </param> /// <param name="oldValue"> The old value of the plc item. </param> /// <param name="newValue"> The new value of the plc item. </param> public PlcItemChangedEventArgs(IPlcItem plcItem, TValue oldValue, TValue newValue) { this.PlcItem = plcItem; this.OldValue = oldValue; this.NewValue = newValue; }
/// <summary> /// Reads the value of the <paramref name="plcItem"/> from the plc. /// </summary> /// <param name="plc"> The extended <see cref="IPlcItem"/> instance. </param> /// <typeparam name="TValue"> The type of the <see cref="IPlcItem{TValue}.Value"/>. </typeparam> /// <param name="plcItem"> The <see cref="IPlcItem{TValue}"/> to read. </param> /// <param name="cancellationToken"> An optional <see cref="CancellationToken"/> for cancelling the read operation. </param> /// <returns> An awaitable task containing the result as <typeparamref name="TValue"/>. </returns> /// <exception cref="ReadPlcException"> Thrown if an exception occurred while reading. </exception> public static async Task <TValue> ReadItemAsync <TValue>(this IPlc plc, IPlcItem <TValue> plcItem, CancellationToken cancellationToken = default) { await plc.ReadItemAsync(plcItem as IPlcItem, cancellationToken); return(plcItem.Value); }
/// <summary> /// Reads the value of the <paramref name="plcItem"/> from the plc. /// </summary> /// <param name="plc"> The extended <see cref="IPlcItem"/> instance. </param> /// <param name="plcItem"> The <see cref="IPlcItem"/> to read. </param> /// <param name="cancellationToken"> An optional <see cref="CancellationToken"/> for cancelling the read operation. </param> /// <returns> An awaitable task containing the result as <see cref="Byte"/> array. </returns> /// <exception cref="ReadPlcException"> Thrown if an exception occurred while reading. </exception> public static async Task <BitCollection> ReadItemAsync(this IPlc plc, IPlcItem plcItem, CancellationToken cancellationToken = default) { await plc.ReadItemsAsync(new[] { plcItem }, cancellationToken); return(plcItem.Value); }
/// <summary> /// Writes the <paramref name="plcItem"/> to the plc and afterwards reads and compares the written data for validation. /// </summary> /// <param name="plc"> The extended <see cref="IPlcItem"/> instance. </param> /// <param name="plcItem"> The <see cref="IPlcItem"/> to write. </param> /// <param name="cancellationToken"> An optional <see cref="CancellationToken"/> for cancelling the write operation. </param> /// <returns> An awaitable task yielding <c>True</c> on success, otherwise <c>False</c>. </returns> /// <exception cref="WritePlcException"> Thrown if an exception occurred while writing. </exception> public static async Task <bool> WriteItemWithValidationAsync(this IPlc plc, IPlcItem plcItem, CancellationToken cancellationToken = default) => await plc.WriteItemsWithValidationAsync(new[] { plcItem }, cancellationToken);
public async Task MonitorChanges() { var changes = 100; var target = (changes * (changes + 1)) / 2; // Gaußsche Summenformel var monitoredChanges = 0; var collectedValue = 0; var monitorItemIdentifier = "MonitoredItem"; uint monitorItemInterval = 50; var monitoredPlc = new MockPlc().MakeMonitorable ( new Dictionary <string, uint> { { monitorItemIdentifier, monitorItemInterval } } ); // Create the item that must be monitored. var monitorItem = new BytePlcItem(dataBlock: Data.Datablock, position: Data.StartOfModifiableBytes, identifier: monitorItemIdentifier); // Create the item that is used to manipulate the value. IPlcItem writeItem = monitorItem.Clone("ChangeItem"); // Set a callback for changes to the items. monitorItem.ValueChanged += (sender, args) => { monitoredChanges++; collectedValue += args.NewValue; }; try { // Connect to the plc. monitoredPlc.Connect(); // Start monitoring those items. monitoredPlc.MonitorItem(monitorItem); monitoredPlc.Start(); // Manipulate the monitored value. for (byte i = 1; i <= changes; i++) { writeItem.Value.TransferValuesFrom(new[] { i }); await monitoredPlc.WriteItemAsync(writeItem); await Task.Delay((int)monitorItemInterval * 2); // This must be at least the double amount of the polling interval. } // Stop monitoring. monitoredPlc.Stop(); // Further manipulate the value to check if this is not monitored. writeItem.Value.TransferValuesFrom(new[] { byte.MinValue }); writeItem.Value.TransferValuesFrom(new[] { byte.MaxValue }); // Check if all changes where registered. Assert.AreEqual(changes, monitoredChanges); Assert.AreEqual(target, collectedValue); Assert.AreEqual(changes, monitorItem.Value); } finally { monitoredPlc.Dispose(); } }
public async Task MonitorDifferentIntervals() { var monitoredChangesOfFirstItem = 0; var monitoredChangesOfSecondItem = 0; var firstMonitorItemIdentifier = "Monitored item #01"; uint firstMonitorItemInterval = 50; var secondMonitorItemIdentifier = "Monitored item #02"; uint secondMonitorItemInterval = 200; var changes = 100; var secondChanges = changes / ((secondMonitorItemInterval / firstMonitorItemInterval) / 2); var monitoredPlc = new MockPlc().MakeMonitorable ( new Dictionary <string, uint> { { firstMonitorItemIdentifier, firstMonitorItemInterval } , { secondMonitorItemIdentifier, secondMonitorItemInterval } } ); // Create items that must be monitored. var firstMonitoredItem = new BytePlcItem(dataBlock: Data.Datablock, position: Data.StartOfModifiableBytes, identifier: firstMonitorItemIdentifier); var secondMonitoredItem = firstMonitoredItem.Clone(secondMonitorItemIdentifier); // Create the item that is used to manipulate the value. IPlcItem writeItem = firstMonitoredItem.Clone("ChangeItem"); // Set a callback for changes to the items. firstMonitoredItem.ValueChanged += (sender, args) => { monitoredChangesOfFirstItem++; }; secondMonitoredItem.ValueChanged += (sender, args) => { monitoredChangesOfSecondItem++; }; try { // Connect to the plc. monitoredPlc.Connect(); // Start monitoring those items. monitoredPlc.MonitorItem(firstMonitoredItem); monitoredPlc.MonitorItem(secondMonitoredItem); monitoredPlc.Start(); // Manipulate the monitored value. for (byte i = 1; i <= changes; i++) { writeItem.Value.TransferValuesFrom(new[] { i }); await monitoredPlc.WriteItemAsync(writeItem); await Task.Delay((int)firstMonitorItemInterval * 2); // This must be at least the double amount of the polling interval. } // Stop monitoring. monitoredPlc.Stop(); // Check if all changes where registered. Assert.AreEqual(changes, monitoredChangesOfFirstItem); Assert.True(monitoredChangesOfSecondItem >= secondChanges); } finally { monitoredPlc.Dispose(); } }
private static IEnumerable <AGL4.DATA_RW40> CreateAgLinkItems(IPlcItem plcItem, PlcItemUsageType usageType) { var agLinkItem = new AGL4.DATA_RW40 { Offset = plcItem.Position }; switch (plcItem.Type) { case PlcItemType.Input: agLinkItem.DBNr = 0; agLinkItem.OpArea = AGL4.AREA_IN; break; case PlcItemType.Output: agLinkItem.DBNr = 0; agLinkItem.OpArea = AGL4.AREA_OUT; break; case PlcItemType.Flags: agLinkItem.DBNr = 0; agLinkItem.OpArea = AGL4.AREA_FLAG; break; default: case PlcItemType.Data: agLinkItem.DBNr = plcItem.DataBlock; agLinkItem.OpArea = AGL4.AREA_DATA; break; } if (plcItem.Value.HandlesFullBytes || (usageType == PlcItemUsageType.Read && plcItem.Value.Length > 1)) { agLinkItem.OpType = AGL4.TYP_BYTE; agLinkItem.BitNr = 0; agLinkItem.OpAnz = (ushort)DataHelper.GetByteAmountForBits(plcItem.Value.Length); if (usageType == PlcItemUsageType.Write) { agLinkItem.B = plcItem.Value; } else { agLinkItem.B = new byte[agLinkItem.OpAnz]; } yield return(agLinkItem); } else { agLinkItem.OpType = AGL4.TYP_BIT; agLinkItem.OpAnz = 1; for (byte bitPosition = 0; bitPosition < plcItem.Value.Length; bitPosition++) { var bitAgLinkItem = agLinkItem; // Value type will be copied on assignment. bitAgLinkItem.BitNr = (ushort)(bitPosition + plcItem.BitPosition); if (usageType == PlcItemUsageType.Write) { // Get the relevant bit of this item and set the AGLink byte accordingly. bitAgLinkItem.B = new byte[] { plcItem.Value[bitPosition] ? (byte)1 : (byte)0 }; } else { bitAgLinkItem.B = new byte[1]; } yield return(bitAgLinkItem); } } }
public ReadPlcItemWrapper(IPlcItem plcItem) { this.PlcItem = plcItem; this.Start = -1; this.Amount = -1; }
/// <summary> /// Reads the <paramref name="plcItem"/> from the underlying <see cref="DataRegion"/>s. /// </summary> /// <param name="plcItem"> The <see cref="IPlcItem"/> to read. </param> /// <returns> The value as <see cref="bool"/> array. </returns> internal bool[] Read(IPlcItem plcItem) { var dataHolder = this.GetDataRegion(plcItem); return(dataHolder.Read(plcItem.Position * 8 + (byte)plcItem.BitPosition, (int)plcItem.Value.Length)); }
public void Deconstruct(out IPlcItem plcItem, out int start, out int amount) { plcItem = this.PlcItem; start = this.Start; amount = this.Amount; }
/// <summary> /// Writes the <paramref name="plcItem"/> to the underlying <see cref="DataRegion"/>s. /// </summary> /// <param name="plcItem"> The <see cref="IPlcItem"/> to write. </param> /// <returns> <c>True</c> on success, otherwise <c>False</c>. </returns> internal bool Write(IPlcItem plcItem) { var dataHolder = this.GetDataRegion(plcItem); return(dataHolder.Write(plcItem.Value, (uint)(plcItem.Position * 8 + (byte)plcItem.BitPosition))); }