mirror of
https://github.com/OpenLogics/MewtocolNet.git
synced 2025-12-06 03:01:24 +00:00
Fixed bool support and duplicate register refs
This commit is contained in:
@@ -9,7 +9,7 @@
|
||||
MinWidth="500"
|
||||
MinHeight="400"
|
||||
Height="850"
|
||||
Width="800"
|
||||
Width="1200"
|
||||
Title="MewtocolNet WPF Demo">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
|
||||
31
Examples.WPF/RegisterCollections/TestRegisterCollection.cs
Normal file
31
Examples.WPF/RegisterCollections/TestRegisterCollection.cs
Normal 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; }
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -145,25 +145,148 @@
|
||||
|
||||
</StackPanel>
|
||||
|
||||
<DataGrid Grid.Row="2"
|
||||
AutoGenerateColumns="False"
|
||||
IsReadOnly="True"
|
||||
ItemsSource="{Binding Plc.Registers, Mode=OneWay}">
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn Header="FP Address" Binding="{Binding PLCAddressName}"/>
|
||||
<DataGridTextColumn Header="Type" Binding="{Binding UnderlyingSystemType.Name}"/>
|
||||
<DataGridTextColumn Header="Value" Binding="{Binding ValueStr}"/>
|
||||
<DataGridTextColumn Header="Poll Level" Binding="{Binding PollLevel, Mode=OneWay}"/>
|
||||
<DataGridTextColumn Header="Update Frequency" Binding="{Binding UpdateFreqHz, StringFormat='{}{0} Hz',Mode=OneWay}"/>
|
||||
<DataGridTemplateColumn>
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<Border Background="{Binding MemoryAreaHash, Mode=OneWay, Converter={StaticResource hashColor}}"/>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
<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}"/>
|
||||
<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 Width="15">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<Border Background="{Binding MemoryAreaHash, Mode=OneWay, Converter={StaticResource hashColor}}"/>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
</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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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());
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
23
MewtocolNet/RegisterAttributes/BitRegisterAttribute.cs
Normal file
23
MewtocolNet/RegisterAttributes/BitRegisterAttribute.cs
Normal 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;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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,23 +143,51 @@ 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;
|
||||
|
||||
assembled.Add(generatedInstance);
|
||||
return generatedInstance;
|
||||
//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;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
|
||||
b.Data.dotnetVarType = type;
|
||||
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;
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ namespace MewtocolNet.RegisterBuilding {
|
||||
//only for building from attributes
|
||||
internal RegisterCollection regCollection;
|
||||
internal PropertyInfo boundProperty;
|
||||
internal RegisterAttribute boundPropertyAttribute;
|
||||
|
||||
internal string typeDef;
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 "";
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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)";
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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)";
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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());
|
||||
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user