Generized memory areas

This commit is contained in:
Felix Weiß
2023-08-08 17:41:18 +02:00
parent 5ed2580bb6
commit a06a69be3f
25 changed files with 531 additions and 304 deletions

View File

@@ -6,5 +6,6 @@
StartupUri="MainWindow.xaml"> StartupUri="MainWindow.xaml">
<Application.Resources> <Application.Resources>
<conv:NegationConverter x:Key="bInv"/> <conv:NegationConverter x:Key="bInv"/>
<conv:ColorHashConverter x:Key="hashColor"/>
</Application.Resources> </Application.Resources>
</Application> </Application>

View File

@@ -0,0 +1,62 @@
using System;
using System.Drawing;
using System.Globalization;
using System.Windows.Data;
namespace Examples.WPF.Converters;
[ValueConversion(typeof(bool), typeof(bool))]
public class ColorHashConverter : IValueConverter {
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
var hashCode = value.GetHashCode();
var randColor = GenerateRandomVibrantColor(new Random(hashCode));
System.Windows.Media.Brush outBrush = new System.Windows.Media.SolidColorBrush(new System.Windows.Media.Color {
R = randColor.R,
G = randColor.G,
B = randColor.B,
A = 255,
});
return outBrush;
}
private Color GenerateRandomVibrantColor(Random random) {
byte red = (byte)random.Next(256);
byte green = (byte)random.Next(256);
byte blue = (byte)random.Next(256);
Color color = Color.FromArgb(255, red, green, blue);
// Ensure the color is vibrant and colorful
while (!IsVibrantColor(color)) {
red = (byte)random.Next(256);
green = (byte)random.Next(256);
blue = (byte)random.Next(256);
color = Color.FromArgb(255,red, green, blue);
}
return color;
}
private bool IsVibrantColor(Color color) {
int minBrightness = 100;
int maxBrightness = 200;
int minSaturation = 150;
int brightness = (int)(color.GetBrightness() * 255);
int saturation = (int)(color.GetSaturation() * 255);
return brightness >= minBrightness && brightness <= maxBrightness && saturation >= minSaturation;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) => throw new NotImplementedException();
}

View File

@@ -1,4 +1,5 @@
using MewtocolNet; using MewtocolNet;
using MewtocolNet.Events;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@@ -9,6 +10,24 @@ namespace Examples.WPF.ViewModels;
public class PlcDataViewViewModel : ViewModelBase { public class PlcDataViewViewModel : ViewModelBase {
private ReconnectArgs plcCurrentReconnectArgs = null!;
public IPlc Plc => App.ViewModel.Plc!; public IPlc Plc => App.ViewModel.Plc!;
public ReconnectArgs PlcCurrentReconnectArgs {
get => plcCurrentReconnectArgs;
set {
plcCurrentReconnectArgs = value;
OnPropChange();
}
}
public PlcDataViewViewModel () {
Plc.ReconnectTryStarted += (s, e) => PlcCurrentReconnectArgs = e;
Plc.Reconnected += (s, e) => PlcCurrentReconnectArgs = null!;
Plc.Disconnected += (s, e) => PlcCurrentReconnectArgs = null!;
}
} }

View File

@@ -59,9 +59,12 @@ public partial class ConnectView : UserControl {
App.ViewModel.Plc = Mewtocol.Ethernet(viewModel.SelectedIP, parsedInt) App.ViewModel.Plc = Mewtocol.Ethernet(viewModel.SelectedIP, parsedInt)
.WithPoller() .WithPoller()
.WithInterfaceSettings(setting => { .WithInterfaceSettings(setting => {
setting.TryReconnectAttempts = 30; setting.TryReconnectAttempts = 0;
setting.TryReconnectDelayMs = 2000; setting.TryReconnectDelayMs = 2000;
setting.SendReceiveTimeoutMs = 1000;
setting.HeartbeatIntervalMs = 3000; setting.HeartbeatIntervalMs = 3000;
setting.MaxDataBlocksPerWrite = 12;
setting.MaxOptimizationDistance = 10;
}) })
.WithCustomPollLevels(lvl => { .WithCustomPollLevels(lvl => {
lvl.SetLevel(2, 3); lvl.SetLevel(2, 3);
@@ -73,6 +76,8 @@ public partial class ConnectView : UserControl {
//b.Struct<short>("DT0").Build(); //b.Struct<short>("DT0").Build();
//b.Struct<short>("DT0").AsArray(30).Build(); //b.Struct<short>("DT0").AsArray(30).Build();
b.Bool("R10A").Build();
b.Struct<short>("DT1000").Build(out heartbeatSetter); b.Struct<short>("DT1000").Build(out heartbeatSetter);
b.Struct<Word>("DT1000").Build(); b.Struct<Word>("DT1000").Build();

View File

@@ -33,16 +33,56 @@
<StackPanel Margin="10" <StackPanel Margin="10"
Grid.Row="1"> Grid.Row="1">
<TextBlock> <TextBlock IsEnabled="{Binding Plc.IsConnected}">
<Run Text="{Binding Plc.PlcInfo.TypeName, Mode=OneWay}" <Run Text="{Binding Plc.PlcInfo.TypeName, Mode=OneWay}"
FontSize="24" FontSize="24"
BaselineAlignment="Center"
FontWeight="SemiBold"/> FontWeight="SemiBold"/>
<Run Text="{Binding Plc.PlcInfo.CpuVersion, StringFormat='v{0}', Mode=OneWay}" <Run Text="{Binding Plc.PlcInfo.CpuVersion, StringFormat='v{0}', Mode=OneWay}"
FontSize="24" FontSize="24"
FontWeight="Light"/> FontWeight="Light"/>
<Ellipse Width="10"
Height="10"
Fill="Lime"
IsEnabled="{Binding Plc.IsConnected}"/>
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Visibility" Value="Collapsed"/>
</Trigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
<TextBlock IsEnabled="{Binding Plc.IsConnected, Converter={StaticResource bInv}}">
<Run Text="Disconnected"
FontSize="24"
BaselineAlignment="Center"
FontWeight="SemiBold"/>
<Ellipse Width="10"
Height="10"
Fill="Red"
IsEnabled="{Binding Plc.IsConnected}"/>
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Visibility" Value="Collapsed"/>
</Trigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock> </TextBlock>
<TextBlock Text="{Binding Plc.PlcInfo.TypeCode, StringFormat='#{0:X}', Mode=OneWay}" <TextBlock Text="{Binding Plc.PlcInfo.TypeCode, StringFormat='#{0:X}', Mode=OneWay}"
@@ -84,6 +124,25 @@
</ItemsControl.ItemTemplate> </ItemsControl.ItemTemplate>
</ItemsControl> </ItemsControl>
<TextBlock>
<Run Text="Reconnecting..."/>
<Run Text="{Binding PlcCurrentReconnectArgs.ReconnectTry, Mode=OneWay, StringFormat='{}{0}/'}"/>
<Run Text="{Binding PlcCurrentReconnectArgs.MaxAttempts, Mode=OneWay}"/> in
<Run Text="{Binding PlcCurrentReconnectArgs.RetryCountDownRemaining, Mode=OneWay}"/>
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding PlcCurrentReconnectArgs, Mode=OneWay}" Value="{x:Null}">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</StackPanel> </StackPanel>
<DataGrid Grid.Row="2" <DataGrid Grid.Row="2"
@@ -96,6 +155,13 @@
<DataGridTextColumn Header="Value" Binding="{Binding ValueStr}"/> <DataGridTextColumn Header="Value" Binding="{Binding ValueStr}"/>
<DataGridTextColumn Header="Poll Level" Binding="{Binding PollLevel, Mode=OneWay}"/> <DataGridTextColumn Header="Poll Level" Binding="{Binding PollLevel, Mode=OneWay}"/>
<DataGridTextColumn Header="Update Frequency" Binding="{Binding UpdateFreqHz, StringFormat='{}{0} Hz',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.Columns>
</DataGrid> </DataGrid>

View File

@@ -0,0 +1,84 @@
using MewtocolNet.Registers;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Text;
namespace MewtocolNet.Events {
public delegate void PlcReconnectEventHandler(object sender, ReconnectArgs e);
public class ReconnectArgs : EventArgs, INotifyPropertyChanged {
private TimeSpan retryCountDownRemaining;
public event PropertyChangedEventHandler PropertyChanged;
public int ReconnectTry { get; internal set; }
public int MaxAttempts { get; internal set; }
public TimeSpan RetryCountDownTime { get; internal set; }
public TimeSpan RetryCountDownRemaining {
get => retryCountDownRemaining;
private set {
retryCountDownRemaining = value;
OnPropChange();
}
}
private System.Timers.Timer countDownTimer;
internal ReconnectArgs(int currentAttempt, int totalAttempts, TimeSpan delayBetween) {
ReconnectTry = currentAttempt;
MaxAttempts = totalAttempts;
RetryCountDownTime = delayBetween;
//start countdown timer
RetryCountDownRemaining = RetryCountDownTime;
var interval = 100;
var intervalTS = TimeSpan.FromMilliseconds(interval);
countDownTimer = new System.Timers.Timer(100);
countDownTimer.Elapsed += (s, e) => {
if (RetryCountDownRemaining <= TimeSpan.Zero) {
StopTimer();
return;
}
RetryCountDownRemaining -= intervalTS;
};
countDownTimer.Start();
}
internal void ConnectionSuccess () {
StopTimer();
}
private void StopTimer () {
countDownTimer?.Stop();
RetryCountDownRemaining = TimeSpan.Zero;
}
private void OnPropChange([CallerMemberName] string propertyName = null) {
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}

View File

@@ -20,6 +20,23 @@ namespace MewtocolNet {
#region Byte and string operation helpers #region Byte and string operation helpers
public static T SetFlag<T>(this Enum value, T flag, bool set) {
Type underlyingType = Enum.GetUnderlyingType(value.GetType());
dynamic valueAsInt = Convert.ChangeType(value, underlyingType);
dynamic flagAsInt = Convert.ChangeType(flag, underlyingType);
if (set) {
valueAsInt |= flagAsInt;
} else {
valueAsInt &= ~flagAsInt;
}
return (T)valueAsInt;
}
public static int DetermineTypeByteIntialSize(this Type type) { public static int DetermineTypeByteIntialSize(this Type type) {
//enums can only be of numeric types //enums can only be of numeric types

View File

@@ -1,4 +1,5 @@
using MewtocolNet.ProgramParsing; using MewtocolNet.Events;
using MewtocolNet.ProgramParsing;
using MewtocolNet.RegisterBuilding; using MewtocolNet.RegisterBuilding;
using MewtocolNet.Registers; using MewtocolNet.Registers;
using System; using System;
@@ -13,6 +14,31 @@ namespace MewtocolNet {
/// </summary> /// </summary>
public interface IPlc : IDisposable, INotifyPropertyChanged { public interface IPlc : IDisposable, INotifyPropertyChanged {
/// <summary>
/// Fires when the interface is fully connected to a PLC
/// </summary>
event PlcConnectionEventHandler Connected;
/// <summary>
/// Fires when a reconnect attempt was successfull
/// </summary>
event PlcConnectionEventHandler Reconnected;
/// <summary>
/// Fires when the interfaces makes a reconnect try to the PLC
/// </summary>
event PlcReconnectEventHandler ReconnectTryStarted;
/// <summary>
/// Fires when the plc/interface connection was fully closed
/// </summary>
event PlcConnectionEventHandler Disconnected;
/// <summary>
/// Fires when the value of a register changes
/// </summary>
event RegisterChangedEventHandler RegisterChanged;
/// <summary> /// <summary>
/// The current connection state of the interface /// The current connection state of the interface
/// </summary> /// </summary>

View File

@@ -29,6 +29,9 @@ namespace MewtocolNet {
/// <inheritdoc/> /// <inheritdoc/>
public event PlcConnectionEventHandler Reconnected; public event PlcConnectionEventHandler Reconnected;
/// <inheritdoc/>
public event PlcReconnectEventHandler ReconnectTryStarted;
/// <inheritdoc/> /// <inheritdoc/>
public event PlcConnectionEventHandler Disconnected; public event PlcConnectionEventHandler Disconnected;
@@ -139,13 +142,7 @@ namespace MewtocolNet {
} }
/// <inheritdoc/> /// <inheritdoc/>
public int BytesPerSecondUpstream { public int BytesPerSecondUpstream => bytesPerSecondUpstream;
get { return bytesPerSecondUpstream; }
private protected set {
bytesPerSecondUpstream = value;
OnPropChange();
}
}
/// <inheritdoc/> /// <inheritdoc/>
public bool IsReceiving { public bool IsReceiving {
@@ -157,13 +154,7 @@ namespace MewtocolNet {
} }
/// <inheritdoc/> /// <inheritdoc/>
public int BytesPerSecondDownstream { public int BytesPerSecondDownstream => bytesPerSecondDownstream;
get { return bytesPerSecondDownstream; }
private protected set {
bytesPerSecondDownstream = value;
OnPropChange();
}
}
/// <inheritdoc/> /// <inheritdoc/>
public MewtocolVersion MewtocolVersion { public MewtocolVersion MewtocolVersion {
@@ -302,7 +293,15 @@ namespace MewtocolNet {
if (pollCycleTask != null && !pollCycleTask.IsCompleted) pollCycleTask.Wait(); if (pollCycleTask != null && !pollCycleTask.IsCompleted) pollCycleTask.Wait();
OnMajorSocketExceptionWhileConnected(); if (IsConnected) {
tSource.Cancel();
isMessageLocked = false;
Logger.Log("The PLC connection was closed manually", LogLevel.Error, this);
OnDisconnect();
}
} }
@@ -348,8 +347,16 @@ namespace MewtocolNet {
//stop the heartbeat timer for the time of retries //stop the heartbeat timer for the time of retries
StopHeartBeat(); StopHeartBeat();
var eArgs = new ReconnectArgs(retryCount, tryReconnectAttempts, TimeSpan.FromMilliseconds(tryReconnectDelayMs));
ReconnectTryStarted?.Invoke(this, eArgs);
Reconnected += (s, e) => eArgs.ConnectionSuccess();
await ReconnectAsync(tryReconnectDelayMs); await ReconnectAsync(tryReconnectDelayMs);
await Task.Delay(2000);
if (IsConnected) return;
await Task.Delay(tryReconnectDelayMs);
retryCount++; retryCount++;
@@ -378,17 +385,7 @@ namespace MewtocolNet {
if (regularSendTask != null && !regularSendTask.IsCompleted) { if (regularSendTask != null && !regularSendTask.IsCompleted) {
//queue self //queue self
return await EnqueueMessage(_msg, onReceiveProgress);
var t = new Task<MewtocolFrameResponse>(() => SendCommandInternalAsync(_msg, onReceiveProgress).Result);
userInputSendTasks.Enqueue(t);
OnPropChange(nameof(QueuedMessages));
await t;
OnPropChange(nameof(QueuedMessages));
return t.Result;
} }
@@ -399,17 +396,12 @@ namespace MewtocolNet {
isMessageLocked = true; isMessageLocked = true;
//wait for the last send task to complete
//if (regularSendTask != null && !regularSendTask.IsCompleted) await regularSendTask;
//send request //send request
regularSendTask = SendTwoDirectionalFrameAsync(_msg, onReceiveProgress); regularSendTask = SendTwoDirectionalFrameAsync(_msg, onReceiveProgress);
//if (regularSendTask == null) return MewtocolFrameResponse.Canceled;
try { try {
var timeoutAwaiter = await Task.WhenAny(regularSendTask, Task.Delay(2000, tSource.Token)); var timeoutAwaiter = await Task.WhenAny(regularSendTask, Task.Delay(sendReceiveTimeoutMs, tSource.Token));
if (timeoutAwaiter != regularSendTask) { if (timeoutAwaiter != regularSendTask) {
@@ -444,10 +436,52 @@ namespace MewtocolNet {
isMessageLocked = false; isMessageLocked = false;
regularSendTask = null; regularSendTask = null;
//run the remaining tasks if no poller is used
if(!PollerActive && !tSource.Token.IsCancellationRequested) {
while(userInputSendTasks.Count > 1) {
if (PollerActive || tSource.Token.IsCancellationRequested) break;
await RunOneOpenQueuedTask();
}
}
return responseData; return responseData;
} }
protected async Task RunOneOpenQueuedTask() {
if (userInputSendTasks != null && userInputSendTasks.Count > 0) {
var t = userInputSendTasks.Dequeue();
t.Start();
await t;
}
}
protected async Task<MewtocolFrameResponse> EnqueueMessage(string _msg, Action<double> onReceiveProgress = null) {
var t = new Task<MewtocolFrameResponse>(() => SendCommandInternalAsync(_msg, onReceiveProgress).Result);
userInputSendTasks.Enqueue(t);
OnPropChange(nameof(QueuedMessages));
await t;
OnPropChange(nameof(QueuedMessages));
return t.Result;
}
private protected async Task<MewtocolFrameResponse> SendOneDirectionalFrameAsync (string frame) { private protected async Task<MewtocolFrameResponse> SendOneDirectionalFrameAsync (string frame) {
try { try {
@@ -763,17 +797,17 @@ namespace MewtocolNet {
} }
private protected void OnMajorSocketExceptionWhileConnected() { private protected void OnSocketExceptionWhileConnected() {
if (IsConnected) {
tSource.Cancel(); tSource.Cancel();
bytesPerSecondDownstream = 0;
bytesPerSecondUpstream = 0;
isMessageLocked = false; isMessageLocked = false;
IsConnected = false;
Logger.Log("The PLC connection was closed", LogLevel.Error, this); if (reconnectTask == null) StartReconnectTask();
OnDisconnect();
}
} }
@@ -785,16 +819,6 @@ namespace MewtocolNet {
} }
private protected void OnSocketExceptionWhileConnected () {
tSource.Cancel();
isMessageLocked = false;
IsConnected = false;
if (reconnectTask == null) StartReconnectTask();
}
private protected virtual void OnConnected(PLCInfo plcinf) { private protected virtual void OnConnected(PLCInfo plcinf) {
Logger.Log("Connected to PLC", LogLevel.Info, this); Logger.Log("Connected to PLC", LogLevel.Info, this);
@@ -807,18 +831,23 @@ namespace MewtocolNet {
//notify the registers //notify the registers
GetAllRegisters().Cast<Register>().ToList().ForEach(x => x.OnPlcConnected()); GetAllRegisters().Cast<Register>().ToList().ForEach(x => x.OnPlcConnected());
reconnectTask = null;
IsConnected = true; IsConnected = true;
isReconnectingStage = false;
Connected?.Invoke(this, new PlcConnectionArgs()); isConnectingStage = false;
if (!usePoller) { if (!usePoller) {
firstPollTask.RunSynchronously(); firstPollTask.RunSynchronously();
} }
Connected?.Invoke(this, new PlcConnectionArgs());
PolledCycle += OnPollCycleDone; PolledCycle += OnPollCycleDone;
void OnPollCycleDone() { void OnPollCycleDone() {
if(firstPollTask != null && !firstPollTask.IsCompleted)
firstPollTask.RunSynchronously(); firstPollTask.RunSynchronously();
PolledCycle -= OnPollCycleDone; PolledCycle -= OnPollCycleDone;
} }
@@ -829,8 +858,8 @@ namespace MewtocolNet {
IsReceiving = false; IsReceiving = false;
IsSending = false; IsSending = false;
BytesPerSecondDownstream = 0; bytesPerSecondDownstream = 0;
BytesPerSecondUpstream = 0; bytesPerSecondUpstream = 0;
PollerCycleDurationMs = 0; PollerCycleDurationMs = 0;
isMessageLocked = false; isMessageLocked = false;
@@ -856,8 +885,8 @@ namespace MewtocolNet {
IsReceiving = false; IsReceiving = false;
IsSending = false; IsSending = false;
BytesPerSecondDownstream = 0; bytesPerSecondDownstream = 0;
BytesPerSecondUpstream = 0; bytesPerSecondUpstream = 0;
PollerCycleDurationMs = 0; PollerCycleDurationMs = 0;
PlcInfo = null; PlcInfo = null;
@@ -887,6 +916,9 @@ namespace MewtocolNet {
GetAllRegisters().Cast<Register>() GetAllRegisters().Cast<Register>()
.ToList().ForEach(x => x.OnInterfaceCyclicTimerUpdate((int)cyclicGenericUpdateCounter.Interval)); .ToList().ForEach(x => x.OnInterfaceCyclicTimerUpdate((int)cyclicGenericUpdateCounter.Interval));
OnPropChange(nameof(BytesPerSecondUpstream));
OnPropChange(nameof(BytesPerSecondDownstream));
} }
private void SetUpstreamStopWatchStart() { private void SetUpstreamStopWatchStart() {
@@ -921,7 +953,7 @@ namespace MewtocolNet {
var perSecUpstream = (double)((bytesTotalCountedUpstream / speedStopwatchUpstr.Elapsed.TotalMilliseconds) * 1000); var perSecUpstream = (double)((bytesTotalCountedUpstream / speedStopwatchUpstr.Elapsed.TotalMilliseconds) * 1000);
if (perSecUpstream <= 10000) if (perSecUpstream <= 10000)
BytesPerSecondUpstream = (int)Math.Round(perSecUpstream, MidpointRounding.AwayFromZero); bytesPerSecondUpstream = (int)Math.Round(perSecUpstream, MidpointRounding.AwayFromZero);
} }
@@ -932,7 +964,7 @@ namespace MewtocolNet {
var perSecDownstream = (double)((bytesTotalCountedDownstream / speedStopwatchDownstr.Elapsed.TotalMilliseconds) * 1000); var perSecDownstream = (double)((bytesTotalCountedDownstream / speedStopwatchDownstr.Elapsed.TotalMilliseconds) * 1000);
if (perSecDownstream <= 10000) if (perSecDownstream <= 10000)
BytesPerSecondDownstream = (int)Math.Round(perSecDownstream, MidpointRounding.AwayFromZero); bytesPerSecondDownstream = (int)Math.Round(perSecDownstream, MidpointRounding.AwayFromZero);
} }

View File

@@ -17,6 +17,8 @@ namespace MewtocolNet {
/// </summary> /// </summary>
public abstract partial class MewtocolInterface { public abstract partial class MewtocolInterface {
private bool heartbeatNeedsRun = false;
internal Task heartbeatTask = Task.CompletedTask; internal Task heartbeatTask = Task.CompletedTask;
internal Func<Task> heartbeatCallbackTask; internal Func<Task> heartbeatCallbackTask;
@@ -127,9 +129,13 @@ namespace MewtocolNet {
heartbeatNeedsRun = true; heartbeatNeedsRun = true;
if(!PollerActive) {
Task.Run(HeartbeatTickTask);
} }
private bool heartbeatNeedsRun = false; }
private async Task HeartbeatTickTask () { private async Task HeartbeatTickTask () {
@@ -141,8 +147,7 @@ namespace MewtocolNet {
Logger.LogError("Heartbeat timed out", this); Logger.LogError("Heartbeat timed out", this);
//OnSocketExceptionWhileConnected(); OnSocketExceptionWhileConnected();
//StartReconnectTask();
return; return;
@@ -224,15 +229,7 @@ namespace MewtocolNet {
await memoryManager.PollAllAreasAsync(async () => { await memoryManager.PollAllAreasAsync(async () => {
if (userInputSendTasks != null && userInputSendTasks.Count > 0) { await RunOneOpenQueuedTask();
var t = userInputSendTasks.Dequeue();
t.Start();
await t;
}
}); });

View File

@@ -138,7 +138,12 @@ namespace MewtocolNet {
var result = await SendCommandInternalAsync(requeststring); var result = await SendCommandInternalAsync(requeststring);
if (result.Success) { if (result.Success) {
Logger.Log($"Operation mode was changed to {(setRun ? "Run" : "Prog")}", LogLevel.Info, this); Logger.Log($"Operation mode was changed to {(setRun ? "Run" : "Prog")}", LogLevel.Info, this);
//directily update the op mode
PlcInfo.OperationMode = PlcInfo.OperationMode.SetFlag(OPMode.RunMode, setRun);
} else { } else {
Logger.Log("Operation mode change failed", LogLevel.Error, this); Logger.Log("Operation mode change failed", LogLevel.Error, this);
} }

View File

@@ -211,30 +211,12 @@ namespace MewtocolNet {
regularSendTask = null; regularSendTask = null;
reconnectTask = Task.CompletedTask; reconnectTask = Task.CompletedTask;
//try to abort any non read message if (await SendCommandInternalAsync($"%{GetStationNumber()}#RT") != null) {
//await SendNoResponseCommandAsync($"%{GetStationNumber()}#AB");
//get plc info 2 times to clear old stuff from the buffer
Logger.Log("Reconnect successfull");
OnReconnected(); OnReconnected();
//var plcinf = await SendCommandAsync($"%{GetStationNumber()}#RT"); }
//if (plcinf != null) {
// Logger.Log("Reconnect successfull");
// OnReconnected();
// //await base.ConnectAsync();
// //OnConnected(plcinf);
//} else {
// Logger.Log("Initial connection failed", LogLevel.Error, this);
// OnDisconnect();
//}
await Task.CompletedTask; await Task.CompletedTask;

View File

@@ -41,6 +41,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
<PackageReference Include="System.IO.Ports" Version="7.0.0" /> <PackageReference Include="System.IO.Ports" Version="7.0.0" />
</ItemGroup> </ItemGroup>

View File

@@ -32,6 +32,20 @@ namespace MewtocolNet.RegisterBuilding.BuilderPatterns {
internal Register Assemble(StepBase stp) => assembler.Assemble(stp.Data); internal Register Assemble(StepBase stp) => assembler.Assemble(stp.Data);
//bool constructor
public StructStp<bool> Bool(string fpAddr, string name = null) {
var data = AddressTools.ParseAddress(fpAddr, name);
data.dotnetVarType = typeof(bool);
return new StructStp<bool>(data) {
builder = this,
};
}
//struct constructor //struct constructor
public StructStp<T> Struct<T>(string fpAddr, string name = null) where T : struct { public StructStp<T> Struct<T>(string fpAddr, string name = null) where T : struct {

View File

@@ -29,6 +29,11 @@ namespace MewtocolNet.Registers {
/// </summary> /// </summary>
int PollLevel { get; } int PollLevel { get; }
/// <summary>
/// Info string about the memory area
/// </summary>
string MemoryAreaInfo { get; }
/// <summary> /// <summary>
/// The update frequency of the register in Hz /// The update frequency of the register in Hz
/// </summary> /// </summary>
@@ -59,6 +64,8 @@ namespace MewtocolNet.Registers {
/// </summary> /// </summary>
uint MemoryAddress { get; } uint MemoryAddress { get; }
string MemoryAreaHash { get; }
/// <summary> /// <summary>
/// Gets the value of the register as the plc representation string /// Gets the value of the register as the plc representation string
/// </summary> /// </summary>

View File

@@ -27,7 +27,7 @@ namespace MewtocolNet.Registers {
internal List<RegisterPropTarget> boundProperties = new List<RegisterPropTarget>(); internal List<RegisterPropTarget> boundProperties = new List<RegisterPropTarget>();
internal Type underlyingSystemType; internal Type underlyingSystemType;
internal IMemoryArea underlyingMemory; internal AreaBase underlyingMemory;
internal bool autoGenerated; internal bool autoGenerated;
internal bool isAnonymous; internal bool isAnonymous;
@@ -94,6 +94,9 @@ namespace MewtocolNet.Registers {
} }
} }
public string MemoryAreaInfo => underlyingMemory.GetName();
public string MemoryAreaHash => underlyingMemory.GetHashCode().ToString();
internal Register() { } internal Register() { }

View File

@@ -74,6 +74,17 @@ namespace MewtocolNet.Registers {
/// <inheritdoc/> /// <inheritdoc/>
public override uint GetRegisterAddressLen() => 1; public override uint GetRegisterAddressLen() => 1;
internal override object SetValueFromBytes(byte[] bytes) {
AddSuccessRead();
var parsed = PlcValueParser.Parse<bool>(this, bytes);
UpdateHoldingValue(parsed);
return parsed;
}
} }
} }

View File

@@ -123,9 +123,7 @@ namespace MewtocolNet.Registers {
//if string correct the sizing of the byte hint was wrong //if string correct the sizing of the byte hint was wrong
var reservedSize = BitConverter.ToInt16(bytes, 0); var reservedSize = BitConverter.ToInt16(bytes, 0);
if (reservedStringLength != reservedSize && if (reservedStringLength != reservedSize && attachedInterface.PlcInfo.IsRunMode)
attachedInterface.PlcInfo.IsRunMode &&
(attachedInterface.pollerTaskStopped || attachedInterface.pollerFirstCycleCompleted))
throw new NotSupportedException( throw new NotSupportedException(
$"The STRING register at {GetMewName()} is not correctly sized, " + $"The STRING register at {GetMewName()} is not correctly sized, " +
$"the size should be STRING[{reservedSize}] instead of STRING[{reservedStringLength}]" $"the size should be STRING[{reservedSize}] instead of STRING[{reservedStringLength}]"

View File

@@ -5,7 +5,8 @@ using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace MewtocolNet.UnderlyingRegisters { namespace MewtocolNet.UnderlyingRegisters {
public class DTArea : IMemoryArea {
public abstract class AreaBase {
private MewtocolInterface mewInterface; private MewtocolInterface mewInterface;
@@ -23,7 +24,7 @@ namespace MewtocolNet.UnderlyingRegisters {
public ulong AddressStart => addressStart; public ulong AddressStart => addressStart;
public ulong AddressEnd => addressEnd; public ulong AddressEnd => addressEnd;
internal DTArea(MewtocolInterface mewIf) { internal AreaBase(MewtocolInterface mewIf) {
mewInterface = mewIf; mewInterface = mewIf;
@@ -116,26 +117,12 @@ namespace MewtocolNet.UnderlyingRegisters {
} }
private string GetMewtocolIdent() {
StringBuilder asciistring = new StringBuilder("D");
asciistring.Append(AddressStart.ToString().PadLeft(5, '0'));
asciistring.Append(AddressEnd.ToString().PadLeft(5, '0'));
return asciistring.ToString();
}
private string GetMewtocolIdent(ulong addStart, ulong addEnd) {
StringBuilder asciistring = new StringBuilder("D");
asciistring.Append(addStart.ToString().PadLeft(5, '0'));
asciistring.Append(addEnd.ToString().PadLeft(5, '0'));
return asciistring.ToString();
}
public override string ToString() => $"DT{AddressStart}-{AddressEnd}"; public override string ToString() => $"DT{AddressStart}-{AddressEnd}";
public virtual string GetName() => $"{ToString()} ({managedRegisters.Count} Registers)";
public string GetHash() => GetHashCode().ToString();
} }
} }

View File

@@ -0,0 +1,19 @@
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

@@ -4,6 +4,8 @@ namespace MewtocolNet.UnderlyingRegisters {
internal interface IMemoryArea { internal interface IMemoryArea {
string GetName();
byte[] GetUnderlyingBytes(Register reg); byte[] GetUnderlyingBytes(Register reg);
void SetUnderlyingBytes(Register reg, byte[] bytes); void SetUnderlyingBytes(Register reg, byte[] bytes);

View File

@@ -0,0 +1,19 @@
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

@@ -92,17 +92,7 @@ namespace MewtocolNet.UnderlyingRegisters {
TestPollLevelExistence(reg); TestPollLevelExistence(reg);
switch (reg.RegisterType) { AddToArea(reg, reg.RegisterType);
case RegisterPrefix.X:
case RegisterPrefix.Y:
case RegisterPrefix.R:
AddToWRArea(reg);
break;
case RegisterPrefix.DT:
case RegisterPrefix.DDT:
AddToDTArea(reg);
break;
}
} }
@@ -155,73 +145,33 @@ namespace MewtocolNet.UnderlyingRegisters {
} }
private bool AddToWRArea(Register insertReg) { private void AddToArea(Register insertReg, RegisterPrefix prefix) {
var pollLevelFound = pollLevels.FirstOrDefault(x => x.level == insertReg.pollLevel);
List<WRArea> collection = null;
switch (insertReg.RegisterType) {
case RegisterPrefix.X:
collection = pollLevelFound.externalRelayInAreas;
break;
case RegisterPrefix.Y:
collection = pollLevelFound.externalRelayOutAreas;
break;
case RegisterPrefix.R:
collection = pollLevelFound.internalRelayAreas;
break;
}
WRArea area = collection.FirstOrDefault(x => x.AddressStart == insertReg.MemoryAddress);
if (area != null) {
var existingLinkedRegister = area.linkedRegisters
.FirstOrDefault(x => x.CompareIsDuplicate(insertReg));
if (existingLinkedRegister != null) {
return false;
} else {
insertReg.underlyingMemory = area;
area.linkedRegisters.Add(insertReg);
return true;
}
} else {
area = new WRArea(mewInterface) {
registerType = insertReg.RegisterType,
addressStart = insertReg.MemoryAddress,
};
insertReg.underlyingMemory = area;
area.linkedRegisters.Add(insertReg);
collection.Add(area);
collection = collection.OrderBy(x => x.AddressStart).ToList();
return true;
}
}
private void AddToDTArea(Register insertReg) {
uint regInsAddStart = insertReg.MemoryAddress; uint regInsAddStart = insertReg.MemoryAddress;
uint regInsAddEnd = insertReg.MemoryAddress + insertReg.GetRegisterAddressLen() - 1; uint regInsAddEnd = insertReg.MemoryAddress + insertReg.GetRegisterAddressLen() - 1;
DTArea targetArea = null; AreaBase targetArea = null;
var pollLevelFound = pollLevels.FirstOrDefault(x => x.level == insertReg.pollLevel); var pollLevelFound = pollLevels.FirstOrDefault(x => x.level == insertReg.pollLevel);
var dataAreas = pollLevelFound.dataAreas; List<AreaBase> pollLevelAreas = null;
foreach (var dtArea in dataAreas) { switch (prefix) {
case RegisterPrefix.X:
pollLevelAreas = pollLevelFound.externalRelayInAreas;
break;
case RegisterPrefix.Y:
pollLevelAreas = pollLevelFound.externalRelayOutAreas;
break;
case RegisterPrefix.R:
pollLevelAreas = pollLevelFound.internalRelayAreas;
break;
case RegisterPrefix.DT:
case RegisterPrefix.DDT:
pollLevelAreas = pollLevelFound.dataAreas;
break;
}
foreach (var dtArea in pollLevelAreas) {
bool addressInsideArea = regInsAddStart >= dtArea.AddressStart && bool addressInsideArea = regInsAddStart >= dtArea.AddressStart &&
regInsAddEnd <= dtArea.AddressEnd; regInsAddEnd <= dtArea.AddressEnd;
@@ -278,7 +228,7 @@ namespace MewtocolNet.UnderlyingRegisters {
}; };
targetArea.BoundaryUdpdate(); targetArea.BoundaryUdpdate();
dataAreas.Add(targetArea); pollLevelAreas.Add(targetArea);
} }
@@ -358,7 +308,7 @@ namespace MewtocolNet.UnderlyingRegisters {
} }
//update registers in poll level //update registers in poll level
foreach (var dtArea in pollLevel.dataAreas.ToArray()) { foreach (var dtArea in pollLevel.GetAllAreas().ToArray()) {
//set the whole memory area at once //set the whole memory area at once
await dtArea.RequestByteReadAsync(dtArea.AddressStart, dtArea.AddressEnd); await dtArea.RequestByteReadAsync(dtArea.AddressStart, dtArea.AddressEnd);
@@ -493,10 +443,9 @@ namespace MewtocolNet.UnderlyingRegisters {
foreach (var lvl in pollLevels) { foreach (var lvl in pollLevels) {
registers.AddRange(lvl.dataAreas.SelectMany(x => x.managedRegisters).SelectMany(x => x.Linked)); registers.AddRange(lvl.dataAreas.SelectMany(x => x.managedRegisters).SelectMany(x => x.Linked));
registers.AddRange(lvl.internalRelayAreas.SelectMany(x => x.managedRegisters).SelectMany(x => x.Linked));
registers.AddRange(lvl.internalRelayAreas.SelectMany(x => x.linkedRegisters)); registers.AddRange(lvl.externalRelayInAreas.SelectMany(x => x.managedRegisters).SelectMany(x => x.Linked));
registers.AddRange(lvl.externalRelayInAreas.SelectMany(x => x.linkedRegisters)); registers.AddRange(lvl.externalRelayOutAreas.SelectMany(x => x.managedRegisters).SelectMany(x => x.Linked));
registers.AddRange(lvl.externalRelayOutAreas.SelectMany(x => x.linkedRegisters));
} }

View File

@@ -6,30 +6,43 @@ namespace MewtocolNet.UnderlyingRegisters {
internal int lastReadTimeMs = 0; internal int lastReadTimeMs = 0;
internal PollLevel(int wrSize, int dtSize) {
externalRelayInAreas = new List<WRArea>(wrSize * 16);
externalRelayOutAreas = new List<WRArea>(wrSize * 16);
internalRelayAreas = new List<WRArea>(wrSize * 16);
dataAreas = new List<DTArea>(dtSize);
}
internal int level; internal int level;
// WR areas are n of words, each word has 2 bytes representing the "special address component" // WR areas are n of words, each word has 2 bytes representing the "special address component"
//X WR //X WR
internal List<WRArea> externalRelayInAreas; internal List<AreaBase> externalRelayInAreas;
//Y WR //Y WR
internal List<WRArea> externalRelayOutAreas; internal List<AreaBase> externalRelayOutAreas;
//R WR //R WR
internal List<WRArea> internalRelayAreas; internal List<AreaBase> internalRelayAreas;
//DT //DT
internal List<DTArea> dataAreas; internal List<AreaBase> dataAreas;
internal PollLevel(int wrSize, int dtSize) {
externalRelayInAreas = new List<AreaBase>(wrSize * 16);
externalRelayOutAreas = new List<AreaBase>(wrSize * 16);
internalRelayAreas = new List<AreaBase>(wrSize * 16);
dataAreas = new List<AreaBase>(dtSize);
}
internal IEnumerable<AreaBase> GetAllAreas () {
List<AreaBase> combined = new List<AreaBase>();
combined.AddRange(internalRelayAreas);
combined.AddRange(externalRelayInAreas);
combined.AddRange(externalRelayOutAreas);
combined.AddRange(dataAreas);
return combined;
}
} }

View File

@@ -1,92 +0,0 @@
using MewtocolNet.Registers;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
namespace MewtocolNet.UnderlyingRegisters {
public class WRArea : IMemoryArea {
private MewtocolInterface mewInterface;
internal RegisterPrefix registerType;
internal ulong addressStart;
internal byte[] wordData = new byte[2];
internal List<Register> linkedRegisters = new List<Register>();
public ulong AddressStart => addressStart;
internal WRArea(MewtocolInterface mewIf) {
mewInterface = mewIf;
}
public void UpdateAreaRegisterValues() {
}
public void SetUnderlyingBytes(Register reg, byte[] bytes) {
}
public byte[] GetUnderlyingBytes(Register reg) {
return null;
}
public async Task<bool> ReadRegisterAsync(Register reg) {
return true;
}
public async Task<bool> WriteRegisterAsync(Register reg, byte[] bytes) {
return true;
}
public string GetMewtocolIdent() => GetMewtocolIdentsAllBits();
public string GetMewtocolIdentsAllBits() {
StringBuilder asciistring = new StringBuilder();
for (byte i = 0; i < 16; i++) {
asciistring.Append(GetMewtocolIdentSingleBit(i));
}
return asciistring.ToString();
}
public string GetMewtocolIdentSingleBit(byte specialAddress) {
//(R|X|Y)(area add [3] + special add [1])
StringBuilder asciistring = new StringBuilder();
string prefix = registerType.ToString();
string mem = AddressStart.ToString();
string sp = specialAddress.ToString("X1");
asciistring.Append(prefix);
asciistring.Append(mem.PadLeft(3, '0'));
asciistring.Append(sp);
return asciistring.ToString();
}
public override string ToString() => $"{registerType}{AddressStart} 0-F";
}
}