Fixed bool support and duplicate register refs

This commit is contained in:
Felix Weiß
2023-08-09 18:08:47 +02:00
parent a06a69be3f
commit 111eacb785
31 changed files with 599 additions and 277 deletions

View File

@@ -9,7 +9,7 @@
MinWidth="500"
MinHeight="400"
Height="850"
Width="800"
Width="1200"
Title="MewtocolNet WPF Demo">
<Grid>
<Grid.RowDefinitions>

View File

@@ -0,0 +1,31 @@
using MewtocolNet;
using MewtocolNet.RegisterAttributes;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Examples.WPF.RegisterCollections;
public class TestRegisterCollection : RegisterCollection {
[Register("R11A")]
public bool? TestR11A { get; set; }
[Register("R11A")]
public bool TestR11A_Duplicate_NonNullable { get; set; }
[Register("R16B")]
public bool TestR16B { get; set; }
[BitRegister("DT1000", 0), PollLevel(3)]
public bool? TestDT100_Word_Duplicate_SingleBit { get; set; }
[Register("DT1000")]
public Word TestDT100_Word_Duplicate { get; set; }
[BitRegister("DDT1010", 1)]
public bool? TestDDT1010_DWord_Duplicate_SingleBit { get; set; }
}

View File

@@ -1,4 +1,5 @@
using MewtocolNet;
using Examples.WPF.RegisterCollections;
using MewtocolNet;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -10,6 +11,7 @@ namespace Examples.WPF.ViewModels {
public class AppViewModel : ViewModelBase {
private IPlc? plc;
private TestRegisterCollection testRegCollection = null!;
public bool PlcIsNull => plc == null;
@@ -25,6 +27,14 @@ namespace Examples.WPF.ViewModels {
}
}
public TestRegisterCollection TestRegCollection {
get => testRegCollection;
set {
testRegCollection = value;
OnPropChange();
}
}
}
}

View File

@@ -1,4 +1,5 @@
using MewtocolNet;
using Examples.WPF.RegisterCollections;
using MewtocolNet;
using MewtocolNet.Events;
using System;
using System.Collections.Generic;
@@ -14,6 +15,8 @@ public class PlcDataViewViewModel : ViewModelBase {
public IPlc Plc => App.ViewModel.Plc!;
public TestRegisterCollection RegCollection => App.ViewModel.TestRegCollection;
public ReconnectArgs PlcCurrentReconnectArgs {
get => plcCurrentReconnectArgs;
set {

View File

@@ -1,4 +1,5 @@
using Examples.WPF.ViewModels;
using Examples.WPF.RegisterCollections;
using Examples.WPF.ViewModels;
using MewtocolNet;
using MewtocolNet.ComCassette;
using MewtocolNet.Registers;
@@ -55,31 +56,46 @@ public partial class ConnectView : UserControl {
var parsedInt = int.Parse(viewModel.SelectedPort);
IRegister<short> heartbeatSetter = null!;
IRegister<bool> outputContactReference = null!;
IRegister<bool> testBoolReference = null!;
IRegister<Word> wordRefTest = null!;
//build a new interface
App.ViewModel.Plc = Mewtocol.Ethernet(viewModel.SelectedIP, parsedInt)
.WithPoller()
.WithInterfaceSettings(setting => {
setting.TryReconnectAttempts = 0;
setting.TryReconnectAttempts = 10;
setting.TryReconnectDelayMs = 2000;
setting.SendReceiveTimeoutMs = 1000;
setting.HeartbeatIntervalMs = 3000;
setting.MaxDataBlocksPerWrite = 12;
setting.MaxDataBlocksPerWrite = 20;
setting.MaxOptimizationDistance = 10;
})
.WithCustomPollLevels(lvl => {
lvl.SetLevel(2, 3);
lvl.SetLevel(3, TimeSpan.FromSeconds(5));
lvl.SetLevel(4, TimeSpan.FromSeconds(10));
})
.WithRegisterCollections(collector => {
App.ViewModel.TestRegCollection = collector.AddCollection<TestRegisterCollection>();
})
.WithRegisters(b => {
//b.Struct<short>("DT0").Build();
//b.Struct<short>("DT0").AsArray(30).Build();
b.Bool("R10A").Build();
b.Bool("X4").Build();
b.Bool("Y4").Build(out outputContactReference);
b.Bool("R10A").PollLevel(PollLevel.FirstIteration).Build(out testBoolReference);
b.Struct<short>("DT1000").Build(out heartbeatSetter);
b.Struct<Word>("DT1000").Build();
//these will be merged into one
b.Struct<Word>("DT1000").Build(out wordRefTest);
b.Struct<Word>("DT1000").Build(out wordRefTest);
b.Struct<ushort>("DT1001").PollLevel(2).Build();
b.Struct<Word>("DT1002").PollLevel(2).Build();
@@ -98,9 +114,16 @@ public partial class ConnectView : UserControl {
await heartbeatSetter.WriteAsync((short)new Random().Next(short.MinValue, short.MaxValue));
if (outputContactReference.Value != null)
await outputContactReference.WriteAsync(!outputContactReference.Value.Value);
if(testBoolReference.Value != null)
await testBoolReference.WriteAsync(!testBoolReference.Value.Value);
})
.Build();
//connect to it
await App.ViewModel.Plc.ConnectAsync();
if (App.ViewModel.Plc.IsConnected) {

View File

@@ -145,17 +145,78 @@
</StackPanel>
<DataGrid Grid.Row="2"
<Grid Grid.Row="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1.5*"/>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<GridSplitter Grid.Column="1"
Grid.Row="1"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
Background="Gray"
ShowsPreview="true"
Width="10">
<GridSplitter.Template>
<ControlTemplate>
<Rectangle Fill="Gray"
Width="2"
Margin="2"/>
</ControlTemplate>
</GridSplitter.Template>
</GridSplitter>
<TextBlock Text="Underlying Registers"
FontSize="18"
Margin="10"/>
<DataGrid Grid.Row="1"
AutoGenerateColumns="False"
IsReadOnly="True"
ItemsSource="{Binding Plc.Registers, Mode=OneWay}">
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Style.Triggers>
<DataTrigger Binding="{Binding IsAutoGenerated}" Value="True">
<Setter Property="IsEnabled" Value="False"/>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.RowStyle>
<DataGrid.Columns>
<DataGridTextColumn Header="FP Address" Binding="{Binding PLCAddressName}"/>
<DataGridTextColumn Header="Type" Binding="{Binding UnderlyingSystemType.Name}"/>
<DataGridTextColumn Header="Value" Binding="{Binding ValueStr}"/>
<DataGridTemplateColumn Header="Value" Width="*">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Border HorizontalAlignment="Left"
Padding="2,0">
<TextBlock Text="{Binding ValueStr}"/>
<Border.Style>
<Style TargetType="Border">
<Style.Triggers>
<DataTrigger Binding="{Binding ValueStr}" Value="True">
<Setter Property="Background" Value="Blue"/>
<Setter Property="TextElement.Foreground" Value="White"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
</Border>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Header="Poll Level" Binding="{Binding PollLevel, Mode=OneWay}"/>
<DataGridTextColumn Header="Update Frequency" Binding="{Binding UpdateFreqHz, StringFormat='{}{0} Hz',Mode=OneWay}"/>
<DataGridTemplateColumn>
<DataGridTemplateColumn Width="15">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Border Background="{Binding MemoryAreaHash, Mode=OneWay, Converter={StaticResource hashColor}}"/>
@@ -165,5 +226,67 @@
</DataGrid.Columns>
</DataGrid>
<TextBlock Text="Property Bindings"
Grid.Column="2"
FontSize="18"
Margin="10"/>
<Border Grid.Column="2"
Grid.Row="1"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
BorderBrush="LightBlue"
BorderThickness="1.5">
<ScrollViewer>
<StackPanel>
<TextBlock Text="Boolean internal relays"/>
<TextBlock>
<Run Text="R11A Nullable property: "/>
<Run Text="{Binding RegCollection.TestR11A}"
FontWeight="Bold"/>
</TextBlock>
<TextBlock>
<Run Text="R11A Duplicate non nullable property: "/>
<Run Text="{Binding RegCollection.TestR11A_Duplicate_NonNullable}"
FontWeight="Bold"/>
</TextBlock>
<TextBlock>
<Run Text="R16B"/>
<Run Text="{Binding RegCollection.TestR16B}"
FontWeight="Bold"/>
</TextBlock>
<TextBlock>
<Run Text="DT1000 Word duplicate: "/>
<Run Text="{Binding RegCollection.TestDT100_Word_Duplicate, Mode=OneWay}"
FontWeight="Bold"/>
</TextBlock>
<TextBlock>
<Run Text="DT1000 Word direct bit read (Idx 0): "/>
<Run Text="{Binding RegCollection.TestDT100_Word_Duplicate_SingleBit, Mode=OneWay}"
FontWeight="Bold"/>
</TextBlock>
<TextBlock>
<Run Text="DDT1010 DWord direct bit read (Idx 1): "/>
<Run Text="{Binding RegCollection.TestDDT1010_DWord_Duplicate_SingleBit, Mode=OneWay}"
FontWeight="Bold"/>
</TextBlock>
</StackPanel>
</ScrollViewer>
</Border>
</Grid>
</Grid>
</UserControl>

View File

@@ -53,10 +53,14 @@ namespace MewtocolNet {
/// </summary>
public bool this[int bitIndex] {
get {
if (bitIndex > bitLength - 1)
throw new IndexOutOfRangeException($"The DWord bit index was out of range ({bitIndex}/{bitLength - 1})");
if (bitIndex > bitLength - 1 && bitLength != 0)
throw new IndexOutOfRangeException($"The word bit index was out of range ({bitIndex}/{bitLength - 1})");
if (bitLength == 0) return false;
return (value & (1 << bitIndex)) != 0;
}
}

View File

@@ -53,10 +53,14 @@ namespace MewtocolNet {
/// </summary>
public bool this[int bitIndex] {
get {
if (bitIndex > bitLength - 1)
if (bitIndex > bitLength - 1 && bitLength != 0)
throw new IndexOutOfRangeException($"The word bit index was out of range ({bitIndex}/{bitLength - 1})");
if (bitLength == 0) return false;
return (value & (1 << bitIndex)) != 0;
}
}

View File

@@ -139,11 +139,11 @@ namespace MewtocolNet {
/// </summary>
/// <param name="_onString"></param>
/// <returns>A <see cref="T:byte[]"/> or null of failed</returns>
internal static byte[] ParseDTRawStringAsBytes(this string _onString) {
internal static byte[] ParseResponseStringAsBytes(this string _onString) {
_onString = _onString.Replace("\r", "");
var res = new Regex(@"\%([0-9a-fA-F]{2})\$(?:RD|RP)(?<data>.*)(?<csum>..)").Match(_onString);
var res = new Regex(@"\%([0-9a-fA-F]{2})\$(?:RD|RP|RC)(?<data>.*)(?<csum>..)").Match(_onString);
if (res.Success) {
string val = res.Groups["data"].Value;

View File

@@ -10,6 +10,7 @@ using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Threading.Tasks;
using static MewtocolNet.Mewtocol;
namespace MewtocolNet
{
@@ -210,21 +211,21 @@ namespace MewtocolNet
internal List<RegisterCollection> collections = new List<RegisterCollection>();
public RegCollector AddCollection(RegisterCollection collection) {
public T AddCollection<T>(T collection) where T : RegisterCollection {
collections.Add(collection);
return this;
return collection;
}
public RegCollector AddCollection<T>() where T : RegisterCollection {
public T AddCollection<T>() where T : RegisterCollection {
var instance = (RegisterCollection)Activator.CreateInstance(typeof(T));
collections.Add(instance);
return this;
return (T)instance;
}
@@ -354,7 +355,7 @@ namespace MewtocolNet
/// <summary>
/// A builder for attaching register collections
/// </summary>
public PostRegisterSetup<T> WithRegisterCollections(Action<RegCollector> collector) {
public PostInit<T> WithRegisterCollections(Action<RegCollector> collector) {
try {
@@ -365,9 +366,7 @@ namespace MewtocolNet
imew.WithRegisterCollections(res.collections);
}
return new PostRegisterSetup<T> {
postInit = this
};
return this;
} catch {
@@ -380,7 +379,7 @@ namespace MewtocolNet
/// <summary>
/// A builder for attaching register collections
/// </summary>
public PostRegisterSetup<T> WithRegisters(Action<RBuild> builder) {
public PostInit<T> WithRegisters(Action<RBuild> builder) {
try {
@@ -391,7 +390,31 @@ namespace MewtocolNet
plc.AddRegisters(regBuilder.assembler.assembled.ToArray());
return new PostRegisterSetup<T> {
return this;
} catch {
throw;
}
}
/// <summary>
/// Repeats the passed method each time the hearbeat is triggered,
/// use
/// </summary>
/// <param name="heartBeatAsync"></param>
/// <returns></returns>
public EndInitSetup<T> WithHeartbeatTask(Func<Task> heartBeatAsync, bool executeInProg = false) {
try {
var plc = (MewtocolInterface)(object)this.intf;
plc.heartbeatCallbackTask = heartBeatAsync;
plc.execHeartBeatCallbackTaskInProg = executeInProg;
return new EndInitSetup<T> {
postInit = this,
};
@@ -406,63 +429,22 @@ namespace MewtocolNet
/// <summary>
/// Builds and returns the final plc interface
/// </summary>
public T Build() => intf;
public T Build() => (T)(object)((MewtocolInterface)(object)intf).Build();
}
#endregion
public class PostRegisterSetup<T> {
internal PostInit<T> postInit;
/// <summary>
/// Repeats the passed method each time the hearbeat is triggered,
/// use
/// </summary>
/// <param name="heartBeatAsync"></param>
/// <returns></returns>
public EndInitSetup<T> WithHeartbeatTask(Func<Task> heartBeatAsync, bool executeInProg = false) {
try {
var plc = (MewtocolInterface)(object)postInit.intf;
plc.heartbeatCallbackTask = heartBeatAsync;
plc.execHeartBeatCallbackTaskInProg = executeInProg;
return new EndInitSetup<T> {
postInit = this.postInit,
postRegSetupInit = this
};
} catch {
throw;
}
}
/// <summary>
/// Builds and returns the final plc interface
/// </summary>
public T Build() => postInit.intf;
}
#region Interface building step 4
public class EndInitSetup<T> {
internal PostInit<T> postInit;
internal PostRegisterSetup<T> postRegSetupInit;
/// <summary>
/// Builds and returns the final plc interface
/// </summary>
public T Build() => postInit.intf;
public T Build() => (T)(object)((MewtocolInterface)(object)postInit.intf).Build();
}

View File

@@ -193,6 +193,14 @@ namespace MewtocolNet {
}
internal MewtocolInterface Build () {
memoryManager.LinkAndMergeRegisters();
return this;
}
private void MewtocolInterface_Connected(object sender, PlcConnectionArgs args) {
IsConnected = true;
@@ -226,8 +234,6 @@ namespace MewtocolNet {
}
OnRegisterChangedUpdateProps(asInternal);
}
/// <inheritdoc/>
@@ -245,7 +251,7 @@ namespace MewtocolNet {
Logger.Log($"DIAG ERR: {PlcInfo.SelfDiagnosticError}", LogLevel.Verbose, this);
Logger.Log($"CPU VER: {PlcInfo.CpuVersion}", LogLevel.Verbose, this);
if(alwaysGetMetadata) {
if(alwaysGetMetadata && PlcInfo.Metadata != null) {
Logger.LogVerbose($"METADATA: {PlcInfo.Metadata.MetaDataVersion}", this);
Logger.LogVerbose($"FP-WIN VERSION: {PlcInfo.Metadata.FPWinVersion}", this);

View File

@@ -242,57 +242,6 @@ namespace MewtocolNet {
#endregion
#region Smart register polling methods
[Obsolete]
private async Task UpdateRCPRegisters() {
//build booleans
//var rcpList = RegistersUnderlying.Where(x => x.GetType() == typeof(BoolRegister))
// .Select(x => (BoolRegister)x)
// .ToArray();
////one frame can only read 8 registers at a time
//int rcpFrameCount = (int)Math.Ceiling((double)rcpList.Length / 8);
//int rcpLastFrameRemainder = rcpList.Length <= 8 ? rcpList.Length : rcpList.Length % 8;
//for (int i = 0; i < rcpFrameCount; i++) {
// int toReadRegistersCount = 8;
// if (i == rcpFrameCount - 1) toReadRegistersCount = rcpLastFrameRemainder;
// var rcpString = new StringBuilder($"%{GetStationNumber()}#RCP{toReadRegistersCount}");
// for (int j = 0; j < toReadRegistersCount; j++) {
// BoolRegister register = rcpList[i + j];
// rcpString.Append(register.BuildMewtocolQuery());
// }
// string rcpRequest = rcpString.ToString();
// var result = await SendCommandAsync(rcpRequest);
// if (!result.Success) return;
// var resultBitArray = result.Response.ParseRCMultiBit();
// for (int k = 0; k < resultBitArray.Length; k++) {
// var register = rcpList[i + k];
// if ((bool)register.Value != resultBitArray[k]) {
// register.SetValueFromPLC(resultBitArray[k]);
// }
// }
//}
}
#endregion
#region Register Collection adding
/// <summary>
@@ -331,9 +280,10 @@ namespace MewtocolNet {
//add builder item
regBuild
.AddressFromAttribute(cAttribute.MewAddress, cAttribute.TypeDef, collection, prop, byteHint)
.AddressFromAttribute(cAttribute.MewAddress, cAttribute.TypeDef, collection, prop, cAttribute, byteHint)
.AsType(dotnetType.IsEnum ? dotnetType.UnderlyingSystemType : dotnetType)
.PollLevel(pollLevel);
.PollLevel(pollLevel)
.Finalize();
}
@@ -353,27 +303,7 @@ namespace MewtocolNet {
}
var assembler = new RegisterAssembler(this);
AddRegisters(assembler.assembled.ToArray());
}
/// <summary>
/// Writes back the values changes of the underlying registers to the corrosponding property
/// </summary>
private void OnRegisterChangedUpdateProps(Register reg) {
var collection = reg.ContainedCollection;
if (collection == null) return;
var props = collection.GetType().GetProperties();
//set the specific bit array if needed
//prop.SetValue(collection, bitAr);
//collection.TriggerPropertyChanged(prop.Name);
AddRegisters(regBuild.assembler.assembled.ToArray());
}
@@ -383,16 +313,7 @@ namespace MewtocolNet {
internal void AddRegisters(params Register[] registers) {
InsertRegistersToMemoryStack(registers.ToList());
}
internal void InsertRegistersToMemoryStack(List<Register> registers) {
memoryManager.LinkAndMergeRegisters(registers);
//run a second iteration
//memoryManager.LinkAndMergeRegisters();
memoryManager.LinkAndMergeRegisters(registers.ToList());
}

View File

@@ -9,6 +9,7 @@ using System.Threading;
using System.Threading.Tasks;
using MewtocolNet.Helpers;
using System.Reflection;
using System.Runtime.InteropServices.ComTypes;
namespace MewtocolNet {
@@ -98,7 +99,7 @@ namespace MewtocolNet {
var metaMarker = new byte[] { 0x4D, 0x65, 0x74, 0x41 };
var data = await ReadByteRangeNonBlocking(endAddress - 2 - (readBytes / 2), readBytes);
var data = await ReadAreaByteRangeAsync(endAddress - 2 - (readBytes / 2), readBytes);
if (data != null && data.SearchBytePattern(metaMarker) == readBytes - 4) {
@@ -204,7 +205,7 @@ namespace MewtocolNet {
if (res.Success) {
var bytes = res.Response.ParseDTRawStringAsBytes();
var bytes = res.Response.ParseResponseStringAsBytes();
var foundEndPattern = bytes.SearchBytePattern(new byte[] { 0xF8, 0xFF, 0xFF });
for (int j = 0; j < bytes.Length; j += 2) {
@@ -243,7 +244,7 @@ namespace MewtocolNet {
/// /// <param name="start">start address of the array</param>
/// <param name="byteArr"></param>
/// <returns></returns>
public async Task<bool> WriteByteRange(int start, byte[] byteArr) {
public async Task<bool> WriteAreaByteRange(int start, byte[] byteArr) {
if (byteArr == null)
throw new ArgumentNullException(nameof(byteArr));
@@ -271,7 +272,7 @@ namespace MewtocolNet {
/// <param name="byteCount">Number of bytes to get</param>
/// <param name="onProgress">Gets invoked when the progress changes, contains the progress as a double from 0 - 1.0</param>
/// <returns>A byte array of the requested DT area</returns>
public async Task<byte[]> ReadByteRangeNonBlocking(int start, int byteCount, Action<double> onProgress = null) {
public async Task<byte[]> ReadAreaByteRangeAsync(int start, int byteCount, RegisterPrefix areaPrefix = RegisterPrefix.DT, Action<double> onProgress = null) {
//on odd bytes add one word
var wordLength = byteCount / 2;
@@ -287,19 +288,41 @@ namespace MewtocolNet {
List<byte> readBytes = new List<byte>();
int padLeftLen = 0;
string areaCodeStr = null;
switch (areaPrefix) {
case RegisterPrefix.X:
areaCodeStr = $"RCCX";
padLeftLen = 4;
break;
case RegisterPrefix.Y:
areaCodeStr = $"RCCY";
padLeftLen = 4;
break;
case RegisterPrefix.R:
areaCodeStr = $"RCCR";
padLeftLen = 4;
break;
case RegisterPrefix.DT:
case RegisterPrefix.DDT:
areaCodeStr = $"RDD";
padLeftLen = 5;
break;
}
async Task ReadBlock(int wordStart, int wordEnd, Action<double> readProg) {
int blockSize = wordEnd - wordStart + 1;
string startStr = wordStart.ToString().PadLeft(5, '0');
string endStr = wordEnd.ToString().PadLeft(5, '0');
string requeststring = $"%{GetStationNumber()}#RDD{startStr}{endStr}";
string startStr = wordStart.ToString().PadLeft(padLeftLen, '0');
string endStr = wordEnd.ToString().PadLeft(padLeftLen, '0');
string requeststring = $"%{GetStationNumber()}#{areaCodeStr}{startStr}{endStr}";
var result = await SendCommandInternalAsync(requeststring, onReceiveProgress: readProg);
if (result.Success && !string.IsNullOrEmpty(result.Response)) {
var bytes = result.Response.ParseDTRawStringAsBytes();
var bytes = result.Response.ParseResponseStringAsBytes();
if (bytes != null)
readBytes.AddRange(bytes);

View File

@@ -0,0 +1,23 @@
using System;
namespace MewtocolNet.RegisterAttributes {
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class BitRegisterAttribute : RegisterAttribute {
internal int bitIndex;
/// <summary>
/// Builds automatic data transfer between the property below this and
/// the plc register
/// </summary>
/// <param name="mewAddress">The FP-Address (DT, DDT, R, X, Y..)</param>
/// <param name="plcTypeDef">The type definition from the PLC (STRING[n], ARRAY [0..2] OF ...)</param>
public BitRegisterAttribute(string mewAddress, byte bitIndex) : base(mewAddress, null) {
this.bitIndex = bitIndex;
}
}
}

View File

@@ -8,15 +8,14 @@ namespace MewtocolNet.RegisterAttributes {
//propinfo of the bound property
internal PropertyInfo BoundProperty;
//general number of bits or bytes to read back to the prop
internal int? LinkLength;
internal RegisterAttribute PropertyAttribute;
internal RegisterCollection ContainedCollection;
public override string ToString() {
var sb = new StringBuilder();
sb.Append($"{BoundProperty}");
if (LinkLength != null) sb.Append($" -Len: {LinkLength}");
return sb.ToString();
}

View File

@@ -24,7 +24,7 @@ namespace MewtocolNet.RegisterBuilding.BuilderPatterns {
}
//internal use only, adds a type definition (for use when building from attibute)
internal DynamicStp AddressFromAttribute(string dtAddr, string typeDef, RegisterCollection regCol, PropertyInfo prop, uint? bytesizeHint = null) {
internal DynamicStp AddressFromAttribute(string dtAddr, string typeDef, RegisterCollection regCol, PropertyInfo prop, RegisterAttribute propAttr, uint? bytesizeHint = null) {
var stpData = AddressTools.ParseAddress(dtAddr);
@@ -32,6 +32,7 @@ namespace MewtocolNet.RegisterBuilding.BuilderPatterns {
stpData.buildSource = RegisterBuildSource.Attribute;
stpData.regCollection = regCol;
stpData.boundProperty = prop;
stpData.boundPropertyAttribute = propAttr;
stpData.byteSizeHint = bytesizeHint;
return new DynamicStp {
@@ -58,9 +59,16 @@ namespace MewtocolNet.RegisterBuilding.BuilderPatterns {
internal class DynamicRegister : SBaseRBDyn {
public void PollLevel (int level) {
public DynamicRegister PollLevel (int level) {
Data.pollLevel = level;
return this;
}
public void Finalize () {
builder.assembler.Assemble(Data);
}

View File

@@ -3,6 +3,7 @@ using MewtocolNet.RegisterBuilding.BuilderPatterns;
using MewtocolNet.Registers;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
@@ -142,20 +143,46 @@ namespace MewtocolNet.RegisterBuilding {
if (generatedInstance == null)
throw new ArgumentException("Failed to build register");
if (collectionTarget != null)
generatedInstance.WithRegisterCollection(collectionTarget);
if (data.boundProperty != null)
generatedInstance.WithBoundProperty(new RegisterPropTarget {
BoundProperty = data.boundProperty,
PropertyAttribute = data.boundPropertyAttribute,
ContainedCollection = data.regCollection
});
generatedInstance.attachedInterface = onInterface;
generatedInstance.underlyingSystemType = data.dotnetVarType;
generatedInstance.pollLevel = data.pollLevel;
if (data.regCollection != null)
generatedInstance.autoGenerated = true;
//set auto generated
generatedInstance.autoGenerated = data.buildSource == PublicEnums.RegisterBuildSource.Attribute;
//Check for a dupe, first in all registers, then in the local generation group
var foundDupe = onInterface.GetAllRegistersInternal().FirstOrDefault(x => x.IsSameAddressAndType(generatedInstance)) ??
assembled.FirstOrDefault(x => x.IsSameAddressAndType(generatedInstance));
if (foundDupe != null) {
if(data.boundProperty != null)
foundDupe.WithBoundProperty(new RegisterPropTarget {
BoundProperty = data.boundProperty,
ContainedCollection = data.regCollection,
PropertyAttribute = data.boundPropertyAttribute,
});
if(onInterface.memoryManager.pollLevelOrMode == PollLevelOverwriteMode.Highest) {
foundDupe.pollLevel = Math.Max(foundDupe.pollLevel, generatedInstance.pollLevel);
} else {
foundDupe.pollLevel = Math.Min(foundDupe.pollLevel, generatedInstance.pollLevel);
}
return foundDupe;
} else {
assembled.Add(generatedInstance);
return generatedInstance;
@@ -165,3 +192,5 @@ namespace MewtocolNet.RegisterBuilding {
}
}
}

View File

@@ -1,4 +1,5 @@
using MewtocolNet.PublicEnums;
using MewtocolNet.RegisterAttributes;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -38,6 +39,11 @@ namespace MewtocolNet.RegisterBuilding {
/// </param>
internal static StepBase AsType(this StepBase b, Type type) {
//check for nullable props
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>)) {
type = Nullable.GetUnderlyingType(type);
}
//for internal only, relay to AsType from string
if (b.Data.buildSource == RegisterBuildSource.Attribute) {
@@ -63,7 +69,29 @@ namespace MewtocolNet.RegisterBuilding {
}
bool isDDTDT = b.Data.regType == RegisterPrefix.DT || b.Data.regType == RegisterPrefix.DDT;
bool isDeclaredNormalRegisterAttribite = b.Data.boundPropertyAttribute != null &&
b.Data.boundPropertyAttribute.GetType().DeclaringType == typeof(RegisterAttribute);
if (b.Data.boundPropertyAttribute is BitRegisterAttribute && type != typeof(bool)) {
throw new NotSupportedException($"Only booleans are allowed as the target type for BitRegister attributes");
} else if (isDeclaredNormalRegisterAttribite && isDDTDT && type == typeof(bool)) {
throw new NotSupportedException($"Single bit DT registers are only supported with the BitRegister attribute");
}
//special case type defintions for register that use an other underlying type
if (b.Data.regType == RegisterPrefix.DT && type == typeof(bool)) {
b.Data.dotnetVarType = typeof(Word);
} else if (b.Data.regType == RegisterPrefix.DDT && type == typeof(bool)) {
b.Data.dotnetVarType = typeof(DWord);
} else {
b.Data.dotnetVarType = type;
}
return b;

View File

@@ -36,6 +36,7 @@ namespace MewtocolNet.RegisterBuilding {
//only for building from attributes
internal RegisterCollection regCollection;
internal PropertyInfo boundProperty;
internal RegisterAttribute boundPropertyAttribute;
internal string typeDef;

View File

@@ -14,6 +14,12 @@ namespace MewtocolNet.Registers {
/// </summary>
event RegisterChangedEventHandler ValueChanged;
/// <summary>
/// Defines if the register was auto generated from a property.<br/>
/// If so it is not allowed to remove the register from the interface stack
/// </summary>
bool IsAutoGenerated { get; }
/// <summary>
/// Type of the underlying register
/// </summary>

View File

@@ -21,7 +21,6 @@ namespace MewtocolNet.Registers {
public event PropertyChangedEventHandler PropertyChanged;
//links to
internal RegisterCollection containedCollection;
internal MewtocolInterface attachedInterface;
internal List<RegisterPropTarget> boundProperties = new List<RegisterPropTarget>();
@@ -50,7 +49,7 @@ namespace MewtocolNet.Registers {
private float[] updateFreqAvgList;
/// <inheritdoc/>
internal RegisterCollection ContainedCollection => containedCollection;
public bool IsAutoGenerated => autoGenerated;
/// <inheritdoc/>
internal MewtocolInterface AttachedInterface => attachedInterface;
@@ -94,7 +93,7 @@ namespace MewtocolNet.Registers {
}
}
public string MemoryAreaInfo => underlyingMemory.GetName();
public string MemoryAreaInfo => underlyingMemory.ToString();
public string MemoryAreaHash => underlyingMemory.GetHashCode().ToString();
@@ -131,6 +130,61 @@ namespace MewtocolNet.Registers {
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ValueObj)));
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ValueStr)));
UpdateBoundProperties();
}
private void UpdateBoundProperties () {
//set the bound property values of there is one
foreach (var prop in boundProperties) {
//nullable
var boundPropType = prop.BoundProperty.PropertyType;
if (boundPropType.IsGenericType && boundPropType.GetGenericTypeDefinition() == typeof(Nullable<>)) {
boundPropType = Nullable.GetUnderlyingType(boundPropType);
}
bool isBitRegisterAttribute = prop.PropertyAttribute is BitRegisterAttribute;
if (boundPropType == underlyingSystemType) {
//the bound prop is the same type as the one of the underlying register
prop.BoundProperty.SetValue(prop.ContainedCollection, ValueObj);
prop.ContainedCollection.TriggerPropertyChanged(prop.BoundProperty.Name);
} else if(boundPropType == typeof(bool) && isBitRegisterAttribute && underlyingSystemType == typeof(Word)) {
var bitRegAttr = ((BitRegisterAttribute)prop.PropertyAttribute).bitIndex;
if(ValueObj != null) {
prop.BoundProperty.SetValue(prop.ContainedCollection, ((Word)ValueObj)[bitRegAttr]);
} else {
prop.BoundProperty.SetValue(prop.ContainedCollection, null);
}
prop.ContainedCollection.TriggerPropertyChanged(prop.BoundProperty.Name);
} else if (boundPropType == typeof(bool) && isBitRegisterAttribute && underlyingSystemType == typeof(DWord)) {
var bitRegAttr = ((BitRegisterAttribute)prop.PropertyAttribute).bitIndex;
if (ValueObj != null) {
prop.BoundProperty.SetValue(prop.ContainedCollection, ((DWord)ValueObj)[bitRegAttr]);
} else {
prop.BoundProperty.SetValue(prop.ContainedCollection, null);
}
prop.ContainedCollection.TriggerPropertyChanged(prop.BoundProperty.Name);
}
}
}
private int updateFreqAvgListIteration = 0;
@@ -206,13 +260,11 @@ namespace MewtocolNet.Registers {
internal virtual object SetValueFromBytes(byte[] bytes) => throw new NotImplementedException();
internal void WithRegisterCollection(RegisterCollection collection) => containedCollection = collection;
internal void WithBoundProperty(RegisterPropTarget propInfo) => boundProperties.Add(propInfo);
internal void WithBoundProperties(IEnumerable<RegisterPropTarget> propInfos) {
foreach (var item in propInfos)
foreach (var item in propInfos.ToArray())
boundProperties.Add(item);
}
@@ -227,10 +279,6 @@ namespace MewtocolNet.Registers {
public virtual string GetRegisterString() => RegisterType.ToString();
public virtual string GetCombinedName() => $"{GetContainerName()}{(GetContainerName() != null ? "." : "")}{Name ?? "Unnamed"}";
public virtual string GetContainerName() => $"{(containedCollection != null ? $"{containedCollection.GetType().Name}" : null)}";
public virtual string GetMewName() => $"{GetRegisterString()}{MemoryAddress}";
public virtual uint GetRegisterAddressLen() => throw new NotImplementedException();
@@ -271,6 +319,12 @@ namespace MewtocolNet.Registers {
}
internal virtual bool IsSameAddressTypeAndPollLevel(Register toCompare) {
return IsSameAddressAndType(toCompare) && PollLevel == toCompare.PollLevel;
}
internal int AveragePollLevel(List<Register> testAgainst, PollLevelOverwriteMode mode) {
var whereAddressFitsInto = this.CanFitAddressRange(testAgainst)
@@ -353,9 +407,6 @@ namespace MewtocolNet.Registers {
if (GetSpecialAddress() != null)
sb.Append($"SPAddress: {GetSpecialAddress():X1}\n");
if (containedCollection != null)
sb.Append($"In collection: {containedCollection.GetType()}\n");
if (boundProperties != null && boundProperties.Count > 0)
sb.Append($"Bound props: \n\t{string.Join(",\n\t", boundProperties)}");
else

View File

@@ -87,7 +87,7 @@ namespace MewtocolNet.Registers {
var encoded = PlcValueParser.EncodeArray(this, value);
var res = await attachedInterface.WriteByteRange((int)MemoryAddress, encoded);
var res = await attachedInterface.WriteAreaByteRange((int)MemoryAddress, encoded);
if (res) {
@@ -116,7 +116,7 @@ namespace MewtocolNet.Registers {
var encoded = PlcValueParser.EncodeArray(this, value);
var res = await attachedInterface.WriteByteRange((int)MemoryAddress, encoded);
var res = await attachedInterface.WriteAreaByteRange((int)MemoryAddress, encoded);
if (res) {
@@ -144,7 +144,7 @@ namespace MewtocolNet.Registers {
/// <inheritdoc/>
private async Task<object> ReadAsync() {
var res = await attachedInterface.ReadByteRangeNonBlocking((int)MemoryAddress, (int)GetRegisterAddressLen() * 2);
var res = await attachedInterface.ReadAreaByteRangeAsync((int)MemoryAddress, (int)GetRegisterAddressLen() * 2);
if (res == null) throw new Exception();
var matchingReg = attachedInterface.memoryManager.GetAllRegisters()

View File

@@ -1,19 +1,25 @@
using System;
using System.Collections;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MewtocolNet.Registers {
/// <summary>
/// Defines a register containing a boolean
/// </summary>
public class BoolRegister : Register {
public class BoolRegister : Register, IRegister<bool> {
internal byte specialAddress;
/// <summary>
/// The registers memory adress if not a special register
/// </summary>
public byte SpecialAddress => specialAddress;
public bool? Value => (bool?)ValueObj;
[Obsolete("Creating registers directly is not supported use IPlc.Register instead")]
public BoolRegister() =>
throw new NotSupportedException("Direct register instancing is not supported, use the builder pattern");
@@ -74,14 +80,67 @@ namespace MewtocolNet.Registers {
/// <inheritdoc/>
public override uint GetRegisterAddressLen() => 1;
/// <inheritdoc/>
public async Task WriteAsync(bool value) {
var res = await WriteSingleBitAsync(value);
if (res) {
//find the underlying memory
var matchingReg = attachedInterface.memoryManager.GetAllRegisters()
.FirstOrDefault(x => x.IsSameAddressAndType(this));
if (matchingReg != null)
matchingReg.underlyingMemory.SetUnderlyingBits(matchingReg, specialAddress, value);
AddSuccessWrite();
UpdateHoldingValue(value);
}
}
private async Task<bool> WriteSingleBitAsync(bool val) {
var rawAddr = $"{MemoryAddress}{SpecialAddress.ToString("X1")}".PadLeft(4, '0');
string addStr = $"{GetRegisterString()}{rawAddr}";
string cmd = $"%{attachedInterface.GetStationNumber()}#WCS{addStr}{(val ? "1" : "0")}";
var res = await attachedInterface.SendCommandInternalAsync(cmd);
return res.Success;
}
/// <inheritdoc/>
public async Task<bool> ReadAsync() {
var res = await attachedInterface.ReadAreaByteRangeAsync((int)MemoryAddress, (int)GetRegisterAddressLen() * 2);
if (res == null) throw new Exception($"Failed to read the register {this}");
var matchingReg = attachedInterface.memoryManager.GetAllRegisters()
.FirstOrDefault(x => x.IsSameAddressAndType(this));
if (matchingReg != null) {
matchingReg.underlyingMemory.SetUnderlyingBytes(matchingReg, res);
}
return (bool)SetValueFromBytes(res);
}
internal override object SetValueFromBytes(byte[] bytes) {
AddSuccessRead();
var parsed = PlcValueParser.Parse<bool>(this, bytes);
var bitArrVal = new BitArray(bytes)[SpecialAddress];
UpdateHoldingValue(parsed);
return parsed;
UpdateHoldingValue(bitArrVal);
return bitArrVal;
}

View File

@@ -75,7 +75,7 @@ namespace MewtocolNet.Registers {
}
var encoded = PlcValueParser.Encode(this, value);
var res = await attachedInterface.WriteByteRange((int)MemoryAddress, encoded);
var res = await attachedInterface.WriteAreaByteRange((int)MemoryAddress, encoded);
if (res) {
@@ -96,7 +96,7 @@ namespace MewtocolNet.Registers {
/// <inheritdoc/>
public async Task<string> ReadAsync() {
var res = await attachedInterface.ReadByteRangeNonBlocking((int)MemoryAddress, (int)GetRegisterAddressLen() * 2);
var res = await attachedInterface.ReadAreaByteRangeAsync((int)MemoryAddress, (int)GetRegisterAddressLen() * 2);
if (res == null) return null;
var matchingReg = attachedInterface.memoryManager.GetAllRegisters()

View File

@@ -125,7 +125,7 @@ namespace MewtocolNet.Registers {
public async Task WriteAsync(T value) {
var encoded = PlcValueParser.Encode(this, (T)value);
var res = await attachedInterface.WriteByteRange((int)MemoryAddress, encoded);
var res = await attachedInterface.WriteAreaByteRange((int)MemoryAddress, encoded);
if (res) {
@@ -146,7 +146,7 @@ namespace MewtocolNet.Registers {
/// <inheritdoc/>
public async Task<T> ReadAsync() {
var res = await attachedInterface.ReadByteRangeNonBlocking((int)MemoryAddress, (int)GetRegisterAddressLen() * 2);
var res = await attachedInterface.ReadAreaByteRangeAsync((int)MemoryAddress, (int)GetRegisterAddressLen() * 2);
if (res == null) throw new Exception($"Failed to read the register {this}");
var matchingReg = attachedInterface.memoryManager.GetAllRegisters()

View File

@@ -34,7 +34,7 @@ namespace MewtocolNet.SetupClasses {
/// Defines how many WORD blocks the interface will send on a DT area write request before splitting up messages <br/>
/// Higher numbers will result in a longer send and receive thread blocking time
/// </summary>
public int MaxDataBlocksPerWrite { get; set; } = 8;
public int MaxDataBlocksPerWrite { get; set; } = 20;
/// <summary>
/// The send and receive timout for messages in milliseconds

View File

@@ -1,4 +1,6 @@
using MewtocolNet.Registers;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
@@ -6,7 +8,7 @@ using System.Threading.Tasks;
namespace MewtocolNet.UnderlyingRegisters {
public abstract class AreaBase {
public class AreaBase {
private MewtocolInterface mewInterface;
@@ -66,7 +68,7 @@ namespace MewtocolNet.UnderlyingRegisters {
internal async Task<bool> RequestByteReadAsync(ulong addStart, ulong addEnd) {
var byteCount = (addEnd - addStart + 1) * 2;
var result = await mewInterface.ReadByteRangeNonBlocking((int)addStart, (int)byteCount);
var result = await mewInterface.ReadAreaByteRangeAsync((int)addStart, (int)byteCount, registerType);
if (result != null) {
@@ -104,6 +106,18 @@ namespace MewtocolNet.UnderlyingRegisters {
}
public void SetUnderlyingBits(Register reg, int bitIndex, bool value) {
var underlyingBefore = GetUnderlyingBytes(reg);
var bitArr = new BitArray(underlyingBefore);
bitArr.CopyTo(underlyingBefore, 0);
SetUnderlyingBytes(underlyingBefore, reg.MemoryAddress);
}
private void SetUnderlyingBytes(byte[] bytes, ulong addStart) {
int copyOffset = (int)((addStart - addressStart) * 2);
@@ -117,11 +131,21 @@ namespace MewtocolNet.UnderlyingRegisters {
}
public override string ToString() => $"DT{AddressStart}-{AddressEnd}";
public override string ToString() {
public virtual string GetName() => $"{ToString()} ({managedRegisters.Count} Registers)";
switch (registerType) {
case RegisterPrefix.X:
case RegisterPrefix.Y:
case RegisterPrefix.R:
return $"W{registerType}{AddressStart}-{AddressEnd} ({managedRegisters.Count} Registers)";
case RegisterPrefix.DT:
case RegisterPrefix.DDT:
return $"DT{AddressStart}-{AddressEnd} ({managedRegisters.Count} Registers)";
}
public string GetHash() => GetHashCode().ToString();
return "";
}
}

View File

@@ -1,19 +0,0 @@
using MewtocolNet.Registers;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MewtocolNet.UnderlyingRegisters {
public class DTArea : AreaBase, IMemoryArea {
internal DTArea(MewtocolInterface mewIf) : base(mewIf) { }
public override string ToString() => $"DT{AddressStart}-{AddressEnd}";
public override string GetName() => $"{ToString()} ({managedRegisters.Count} Registers)";
}
}

View File

@@ -1,19 +0,0 @@
using MewtocolNet.Registers;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MewtocolNet.UnderlyingRegisters {
public class WRArea : AreaBase, IMemoryArea {
internal WRArea(MewtocolInterface mewIf) : base(mewIf) { }
public override string ToString() => $"DT{AddressStart}-{AddressEnd}";
public override string GetName() => $"{ToString()} ({managedRegisters.Count} Registers)";
}
}

View File

@@ -94,6 +94,9 @@ namespace MewtocolNet.UnderlyingRegisters {
AddToArea(reg, reg.RegisterType);
//reset overlap fitted for all
reg.wasOverlapFitted = false;
}
//order
@@ -102,23 +105,20 @@ namespace MewtocolNet.UnderlyingRegisters {
PollLevel lvl = pollLevels[i];
//poll level has no areas
if(lvl.dataAreas.Count == 0 &&
lvl.externalRelayInAreas.Count == 0 &&
lvl.externalRelayOutAreas.Count == 0 &&
lvl.internalRelayAreas.Count == 0) {
if(lvl.GetAllAreas().Count() == 0) {
pollLevels.Remove(lvl);
continue;
}
foreach (var area in lvl.dataAreas) {
foreach (var area in lvl.GetAllAreas()) {
area.managedRegisters = area.managedRegisters.OrderBy(x => x.AddressStart).ToList();
}
lvl.dataAreas = lvl.dataAreas.OrderBy(x => x.AddressStart).ToList();
//lvl.dataAreas = lvl.dataAreas.OrderBy(x => x.AddressStart).ToList();
}
@@ -221,7 +221,7 @@ namespace MewtocolNet.UnderlyingRegisters {
//create a new area
if (targetArea == null) {
targetArea = new DTArea(mewInterface) {
targetArea = new AreaBase(mewInterface) {
addressStart = regInsAddStart,
addressEnd = regInsAddEnd,
registerType = insertReg.RegisterType,
@@ -253,10 +253,12 @@ namespace MewtocolNet.UnderlyingRegisters {
//check if the linked group has duplicate type registers
var dupedTypeReg = existinglinkedGroup.Linked.FirstOrDefault(x => x.IsSameAddressAndType(insertReg));
var dupedTypeReg = existinglinkedGroup.Linked
.FirstOrDefault(x => x.IsSameAddressAndType(insertReg) && x.PollLevel == insertReg.PollLevel);
if (dupedTypeReg != null && insertReg.autoGenerated) {
if (dupedTypeReg != null) {
dupedTypeReg.WithBoundProperties(insertReg.boundProperties);
dupedTypeReg.autoGenerated = insertReg.autoGenerated;
} else {
existinglinkedGroup.Linked.Add(insertReg);
existinglinkedGroup.Linked = existinglinkedGroup.Linked.OrderBy(x => x.MemoryAddress).ToList();

View File

@@ -66,7 +66,7 @@ namespace MewtocolTests {
}
[Fact(DisplayName = nameof(MewtocolHelpers.ParseDTRawStringAsBytes))]
[Fact(DisplayName = nameof(MewtocolHelpers.ParseResponseStringAsBytes))]
public void ParseDTByteStringGeneration() {
var testList = new List<byte[]>() {
@@ -77,7 +77,7 @@ namespace MewtocolTests {
foreach (var item in testList) {
Assert.Equal(item, $"%01$RD{item.ToHexString()}".BCC_Mew().ParseDTRawStringAsBytes());
Assert.Equal(item, $"%01$RD{item.ToHexString()}".BCC_Mew().ParseResponseStringAsBytes());
}