Implement Basic Logging to File
Attempt at dumping values from memory into a CSV file for post-processing Signed-off-by: mharb <mharb@noreply.localhost>
This commit is contained in:
@@ -6,7 +6,7 @@
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<RunAnalyzersDuringBuild>true</RunAnalyzersDuringBuild>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="ProtosXdemo.zip" />
|
||||
@@ -14,16 +14,19 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="9.0.0">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="NModbus4" Version="2.1.0" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118">
|
||||
<PackageReference Include="System.IO.Ports" Version="9.0.7" />
|
||||
<PackageReference Include="OxyPlot.Core" Version="2.1.2" />
|
||||
<PackageReference Include="OxyPlot.SkiaSharp" Version="2.1.2" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="9.0.0">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="NModbus4" Version="2.1.0" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="System.IO.Ports" Version="9.0.7" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
namespace ProtosXdemo
|
||||
{
|
||||
// Stores counts of I/O channels for easy access.
|
||||
internal static class ChannelCache
|
||||
{
|
||||
public static ushort DigitalInputCount { get; private set; }
|
||||
@@ -12,6 +13,7 @@ namespace ProtosXdemo
|
||||
|
||||
public static ushort AnalogInputCount { get; private set; }
|
||||
|
||||
// Initializes counts from Data.MachineState arrays.
|
||||
public static void Initialize()
|
||||
{
|
||||
DigitalInputCount = (ushort)Data.MachineState.DigitalInputChannels.Length;
|
||||
@@ -19,4 +21,4 @@ namespace ProtosXdemo
|
||||
AnalogInputCount = (ushort)Data.MachineState.NumberOfAnalogueInputs;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,15 +4,16 @@
|
||||
|
||||
namespace ProtosXdemo
|
||||
{
|
||||
using static System.Console;
|
||||
using System;
|
||||
|
||||
// Parses command-line arguments for recipe name and repeat count.
|
||||
internal class CommandLineParser
|
||||
{
|
||||
public string? RecipeName { get; }
|
||||
public string RecipeName { get; }
|
||||
|
||||
public int TimesToRepeat { get; }
|
||||
|
||||
private CommandLineParser(string? recipeName, int timesToRepeat)
|
||||
private CommandLineParser(string recipeName, int timesToRepeat)
|
||||
{
|
||||
this.RecipeName = recipeName;
|
||||
this.TimesToRepeat = timesToRepeat;
|
||||
@@ -20,20 +21,20 @@ namespace ProtosXdemo
|
||||
|
||||
public static CommandLineParser Parse(string[] args)
|
||||
{
|
||||
return args.Length switch
|
||||
switch (args.Length)
|
||||
{
|
||||
0 => new CommandLineParser(null, 0),
|
||||
1 => new CommandLineParser(args[0], 0),
|
||||
2 => new CommandLineParser(args[0], Convert.ToInt32(args[1])),
|
||||
_ => ExitOnTooManyArgs()
|
||||
};
|
||||
}
|
||||
|
||||
private static CommandLineParser ExitOnTooManyArgs()
|
||||
{
|
||||
WriteLine("Too many command-line arguments; exiting.");
|
||||
Environment.Exit(Data.MachineState.FailureCode[2]);
|
||||
throw new OperationCanceledException();
|
||||
case 0:
|
||||
return new CommandLineParser(null, 0);
|
||||
case 1:
|
||||
return new CommandLineParser(args[0], 0);
|
||||
case 2:
|
||||
int repeatCount = Convert.ToInt32(args[1]);
|
||||
return new CommandLineParser(args[0], repeatCount);
|
||||
default:
|
||||
Console.WriteLine("Too many command-line arguments; exiting.");
|
||||
Environment.Exit(Data.MachineState.FailureCode[2]);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,17 +4,21 @@
|
||||
|
||||
namespace ProtosXdemo
|
||||
{
|
||||
using static System.Console;
|
||||
using System;
|
||||
|
||||
// Sets up console appearance (size, color) — Windows only.
|
||||
internal static class ConsoleSetup
|
||||
{
|
||||
public static void ApplyDefaultAppearance()
|
||||
{
|
||||
WindowUtility.SetAppearanceOptions();
|
||||
WindowUtility.MoveWindowToCenter();
|
||||
BackgroundColor = ConsoleColor.DarkBlue;
|
||||
ForegroundColor = ConsoleColor.White;
|
||||
Clear();
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
WindowUtility.SetAppearanceOptions();
|
||||
WindowUtility.MoveWindowToCenter();
|
||||
Console.BackgroundColor = ConsoleColor.DarkBlue;
|
||||
Console.ForegroundColor = ConsoleColor.White;
|
||||
Console.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
230
px-tcp1/ProtosXDemo/src/CsvDataLogger.cs
Normal file
230
px-tcp1/ProtosXDemo/src/CsvDataLogger.cs
Normal file
@@ -0,0 +1,230 @@
|
||||
// <copyright file="CsvDataLogger.cs" company="PlaceholderCompany">
|
||||
// Copyright (c) PlaceholderCompany. All rights reserved.
|
||||
// </copyright>
|
||||
|
||||
namespace ProtosXdemo
|
||||
{
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
// Logs machine state to CSV file periodically.
|
||||
public class CsvDataLogger
|
||||
{
|
||||
private readonly string filePath;
|
||||
private readonly int intervalMs;
|
||||
private readonly long maxBytes;
|
||||
private CancellationTokenSource cts;
|
||||
private Task loopTask;
|
||||
private readonly object fileLock = new object();
|
||||
|
||||
public CsvDataLogger(string filePath, int intervalMs = 200, long maxBytes = 1_048_576)
|
||||
{
|
||||
if (filePath == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(filePath));
|
||||
}
|
||||
|
||||
if (intervalMs <= 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(intervalMs), "Interval must be > 0");
|
||||
}
|
||||
|
||||
if (maxBytes <= 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(maxBytes), "Max bytes must be > 0");
|
||||
}
|
||||
|
||||
this.filePath = filePath;
|
||||
this.intervalMs = intervalMs;
|
||||
this.maxBytes = maxBytes;
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
if (this.cts != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
this.cts = new CancellationTokenSource();
|
||||
this.loopTask = Task.Run(() => this.LoopAsync(this.cts.Token), this.cts.Token);
|
||||
}
|
||||
|
||||
public async Task StopAsync()
|
||||
{
|
||||
if (this.cts == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
this.cts.Cancel();
|
||||
try
|
||||
{
|
||||
if (this.loopTask != null)
|
||||
{
|
||||
await this.loopTask.ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
}
|
||||
finally
|
||||
{
|
||||
this.cts?.Dispose();
|
||||
this.cts = null;
|
||||
this.loopTask = null;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task LoopAsync(CancellationToken token)
|
||||
{
|
||||
bool writeHeader = !File.Exists(this.filePath);
|
||||
try
|
||||
{
|
||||
if (writeHeader)
|
||||
{
|
||||
string header = CreateHeaderLine();
|
||||
await this.AppendLineAsync(header).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"[CSV Logger] Error writing header: {ex.Message}");
|
||||
return;
|
||||
}
|
||||
|
||||
while (!token.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (this.IsSizeExceeded())
|
||||
{
|
||||
Console.WriteLine("[CSV Logger] Max file size reached, stopping.");
|
||||
break;
|
||||
}
|
||||
|
||||
string entryLine = CreateEntryLine();
|
||||
await this.AppendLineAsync(entryLine).ConfigureAwait(false);
|
||||
await Task.Delay(this.intervalMs, token).ConfigureAwait(false);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
break;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"[CSV Logger] Error logging: {ex.Message}");
|
||||
await Task.Delay(this.intervalMs, token).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsSizeExceeded()
|
||||
{
|
||||
if (!File.Exists(this.filePath))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return new FileInfo(this.filePath).Length >= this.maxBytes;
|
||||
}
|
||||
|
||||
private static string CreateHeaderLine()
|
||||
{
|
||||
var header = new StringBuilder();
|
||||
header.Append("Timestamp");
|
||||
for (int i = 0; i < Data.MachineState.DigitalInputChannels.Length; i++)
|
||||
{
|
||||
header.Append($",DigitalInput{i}");
|
||||
}
|
||||
|
||||
for (int i = 0; i < Data.MachineState.DigitalOutputChannels.Length; i++)
|
||||
{
|
||||
header.Append($",DigitalOutput{i}");
|
||||
}
|
||||
|
||||
for (int i = 0; i < Data.MachineState.Analogue420Inputs.Length; i++)
|
||||
{
|
||||
header.Append($",Analogue420Input{i}");
|
||||
}
|
||||
|
||||
for (int i = 0; i < Data.MachineState.AnalogueTC1Inputs.Length; i++)
|
||||
{
|
||||
header.Append($",AnalogueTC1Input{i}");
|
||||
}
|
||||
|
||||
for (int i = 0; i < Data.MachineState.AnalogueTC2Inputs.Length; i++)
|
||||
{
|
||||
header.Append($",AnalogueTC2Input{i}");
|
||||
}
|
||||
|
||||
for (int i = 0; i < Data.MachineState.AnalogueRTD1Inputs.Length; i++)
|
||||
{
|
||||
header.Append($",AnalogueRTD1Input{i}");
|
||||
}
|
||||
|
||||
for (int i = 0; i < Data.MachineState.AnalogueRTD2Inputs.Length; i++)
|
||||
{
|
||||
header.Append($",AnalogueRTD2Input{i}");
|
||||
}
|
||||
|
||||
return header.ToString();
|
||||
}
|
||||
|
||||
private static string CreateEntryLine()
|
||||
{
|
||||
var line = new StringBuilder();
|
||||
line.Append(DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss.fff"));
|
||||
foreach (bool input in Data.MachineState.DigitalInputChannels)
|
||||
{
|
||||
line.Append($",{(input ? "1" : "0")}");
|
||||
}
|
||||
|
||||
foreach (bool output in Data.MachineState.DigitalOutputChannels)
|
||||
{
|
||||
line.Append($",{(output ? "1" : "0")}");
|
||||
}
|
||||
|
||||
foreach (int value in Data.MachineState.Analogue420Inputs)
|
||||
{
|
||||
line.Append($",{value}");
|
||||
}
|
||||
|
||||
foreach (int value in Data.MachineState.AnalogueTC1Inputs)
|
||||
{
|
||||
line.Append($",{value}");
|
||||
}
|
||||
|
||||
foreach (int value in Data.MachineState.AnalogueTC2Inputs)
|
||||
{
|
||||
line.Append($",{value}");
|
||||
}
|
||||
|
||||
foreach (int value in Data.MachineState.AnalogueRTD1Inputs)
|
||||
{
|
||||
line.Append($",{value}");
|
||||
}
|
||||
|
||||
foreach (int value in Data.MachineState.AnalogueRTD2Inputs)
|
||||
{
|
||||
line.Append($",{value}");
|
||||
}
|
||||
|
||||
return line.ToString();
|
||||
}
|
||||
|
||||
private async Task AppendLineAsync(string line)
|
||||
{
|
||||
await Task.Run(() =>
|
||||
{
|
||||
lock (this.fileLock)
|
||||
{
|
||||
File.AppendAllText(this.filePath, line + Environment.NewLine);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
119
px-tcp1/ProtosXDemo/src/CsvPlotter.cs
Normal file
119
px-tcp1/ProtosXDemo/src/CsvPlotter.cs
Normal file
@@ -0,0 +1,119 @@
|
||||
// <copyright file="CsvPlotter.cs" company="PlaceholderCompany">
|
||||
// Copyright (c) PlaceholderCompany. All rights reserved.
|
||||
// </copyright>
|
||||
|
||||
namespace ProtosXdemo
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using OxyPlot;
|
||||
using OxyPlot.Axes;
|
||||
using OxyPlot.Legends;
|
||||
using OxyPlot.Series;
|
||||
using OxyPlot.SkiaSharp;
|
||||
|
||||
public static class CsvPlotter
|
||||
{
|
||||
public static async Task GeneratePngFromCsvAsync(string csvPath, string pngPath)
|
||||
{
|
||||
try
|
||||
{
|
||||
var (times, columns) = await LoadCsvAsync(csvPath);
|
||||
var model = new PlotModel { Title = "Machine State Log" };
|
||||
|
||||
model.Axes.Add(new DateTimeAxis
|
||||
{
|
||||
Position = AxisPosition.Bottom,
|
||||
Title = "Time",
|
||||
StringFormat = "HH:mm:ss",
|
||||
});
|
||||
|
||||
model.Axes.Add(new LinearAxis
|
||||
{
|
||||
Position = AxisPosition.Left,
|
||||
Title = "Value",
|
||||
});
|
||||
|
||||
foreach (var col in columns)
|
||||
{
|
||||
var series = new LineSeries
|
||||
{
|
||||
Title = col.Key,
|
||||
StrokeThickness = 1,
|
||||
MarkerSize = 0,
|
||||
};
|
||||
|
||||
for (int i = 0; i < times.Length && i < col.Value.Length; i++)
|
||||
{
|
||||
if (!double.IsNaN(col.Value[i]))
|
||||
{
|
||||
series.Points.Add(new DataPoint(DateTimeAxis.ToDouble(times[i]), col.Value[i]));
|
||||
}
|
||||
}
|
||||
|
||||
model.Series.Add(series);
|
||||
}
|
||||
|
||||
// Legend properties need debugging
|
||||
// model.IsLegendVisible = true;
|
||||
// model.LegendPlacement = LegendPlacement.Outside;
|
||||
// model.LegendPosition = LegendPosition.RightTop;
|
||||
// model.LegendOrientation = LegendOrientation.Vertical;
|
||||
await Task.Run(() =>
|
||||
{
|
||||
using var stream = File.Create(pngPath);
|
||||
var exporter = new PngExporter { Width = 1200, Height = 600 };
|
||||
exporter.Export(model, stream);
|
||||
});
|
||||
|
||||
Console.WriteLine($"Plot saved as {pngPath}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"[Plot] Failed: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<(DateTime[] times, Dictionary<string, double[]> columns)> LoadCsvAsync(string path)
|
||||
{
|
||||
string[] lines = await File.ReadAllLinesAsync(path);
|
||||
if (lines.Length < 2)
|
||||
{
|
||||
throw new InvalidOperationException("CSV has no data.");
|
||||
}
|
||||
|
||||
string[] headers = lines[0].Split(',');
|
||||
var data = headers.Skip(1).ToDictionary(h => h, _ => new List<double>());
|
||||
var timestamps = new List<DateTime>();
|
||||
|
||||
foreach (string line in lines.Skip(1))
|
||||
{
|
||||
string[] parts = line.Split(',');
|
||||
if (string.IsNullOrWhiteSpace(parts[0]))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!DateTime.TryParseExact(parts[0], "yyyy-MM-dd HH:mm:ss.fff", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out DateTime ts))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
timestamps.Add(ts);
|
||||
|
||||
for (int i = 1; i < parts.Length && i < headers.Length; i++)
|
||||
{
|
||||
double val = double.TryParse(parts[i], NumberStyles.Float, CultureInfo.InvariantCulture, out var v) ? v : double.NaN;
|
||||
data[headers[i]].Add(val);
|
||||
}
|
||||
}
|
||||
|
||||
var arrays = data.ToDictionary(kv => kv.Key, kv => kv.Value.ToArray());
|
||||
return (timestamps.ToArray(), arrays);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,9 +4,10 @@
|
||||
|
||||
namespace ProtosXdemo
|
||||
{
|
||||
using System;
|
||||
using Modbus.Device;
|
||||
using static System.Console;
|
||||
|
||||
// Reads and displays digital input states via Modbus.
|
||||
internal class DiscreteReader
|
||||
{
|
||||
private readonly ModbusIpMaster master;
|
||||
@@ -18,7 +19,7 @@ namespace ProtosXdemo
|
||||
|
||||
public void ReadAll()
|
||||
{
|
||||
WriteLine("Reading all discrete inputs…");
|
||||
Console.WriteLine("Reading all discrete inputs…");
|
||||
try
|
||||
{
|
||||
bool[] inputs = this.master.ReadInputs(0, ChannelCache.DigitalInputCount);
|
||||
@@ -26,25 +27,28 @@ namespace ProtosXdemo
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
WriteLine($"[Error] Reading inputs failed → {ex.Message}");
|
||||
Console.WriteLine($"[Error] Reading inputs failed → {ex.Message}");
|
||||
PromptKey("Press any key to continue…");
|
||||
}
|
||||
}
|
||||
|
||||
private static void Display(bool[] inputs)
|
||||
{
|
||||
WriteLine("Discrete Inputs:");
|
||||
Console.WriteLine("Discrete Inputs:");
|
||||
for (int i = 0; i < inputs.Length; i++)
|
||||
{
|
||||
string state = inputs[i] ? "On" : "Off";
|
||||
WriteLine($"Input #{i + 1}: {state}");
|
||||
Console.WriteLine($"Input #{i + 1}: {state}");
|
||||
}
|
||||
}
|
||||
|
||||
private static void PromptKey(string prompt)
|
||||
{
|
||||
WriteLine(prompt);
|
||||
ReadKey(intercept: true);
|
||||
if (Console.IsInputRedirected == false)
|
||||
{
|
||||
Console.WriteLine(prompt);
|
||||
Console.ReadKey(intercept: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,15 +4,16 @@
|
||||
|
||||
namespace ProtosXdemo
|
||||
{
|
||||
// Initializes standard failure codes.
|
||||
internal class FailureCodes
|
||||
{
|
||||
public static void FailureCodeValues()
|
||||
{
|
||||
Data.MachineState.FailureCode[0] = 0; // success
|
||||
Data.MachineState.FailureCode[1] = 1; // digital write error from hardware
|
||||
Data.MachineState.FailureCode[2] = 2; // too many CLI parameters
|
||||
Data.MachineState.FailureCode[3] = 3; // program was interrupted
|
||||
Data.MachineState.FailureCode[0] = 0; // success
|
||||
Data.MachineState.FailureCode[1] = 1; // digital write error
|
||||
Data.MachineState.FailureCode[2] = 2; // too many CLI args
|
||||
Data.MachineState.FailureCode[3] = 3; // interrupted
|
||||
Data.MachineState.FailureCode[255] = 255; // other error
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,17 +4,19 @@
|
||||
|
||||
namespace ProtosXdemo
|
||||
{
|
||||
using System;
|
||||
|
||||
// Loads random state into digital outputs.
|
||||
internal class LoadMachineStateDigital
|
||||
{
|
||||
public static void MakeRandomStateDigital()
|
||||
{
|
||||
Data.MachineState.DigitalOutputChannels.Initialize();
|
||||
var rand = new Random();
|
||||
|
||||
Array.Clear(Data.MachineState.DigitalOutputChannels, 0, Data.MachineState.DigitalOutputChannels.Length);
|
||||
Random rand = new Random();
|
||||
for (int i = 0; i < Data.MachineState.NumberOfDigitalOutputs; i++)
|
||||
{
|
||||
Data.MachineState.DigitalOutputChannels[i] = rand.Next(1, 99) % 2 == 0;
|
||||
Data.MachineState.DigitalOutputChannels[i] = rand.Next(0, 2) == 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,34 +4,36 @@
|
||||
|
||||
namespace ProtosXdemo
|
||||
{
|
||||
using System;
|
||||
using System.Threading;
|
||||
using Modbus.Device;
|
||||
using static System.Console;
|
||||
|
||||
// Main control loop for digital output sequences.
|
||||
internal class MainEventLoop
|
||||
{
|
||||
public static int RunEventLoop(ModbusIpMaster modbusMaster, ushort coilOutputStartAddress)
|
||||
{
|
||||
WriteLine("\nTurning all the outputs OFF..."); // Start with known state = all outputs OFF
|
||||
int shutdownErrorCode = MakeHardStop.HardStop(modbusMaster, coilOutputStartAddress); // Write the machine state values to the I/O hardware and check for success
|
||||
|
||||
Console.WriteLine("\nTurning all outputs OFF...");
|
||||
int shutdownErrorCode = MakeHardStop.HardStop(modbusMaster, coilOutputStartAddress);
|
||||
if (shutdownErrorCode == Data.MachineState.FailureCode[0])
|
||||
{
|
||||
WriteLine("All digital outputs have been turned OFF");
|
||||
WriteLine("Program begins running here");
|
||||
Console.WriteLine("All digital outputs OFF");
|
||||
Console.WriteLine("Program begins running here");
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteLine("Program fails here...");
|
||||
Console.WriteLine("Program fails here...");
|
||||
return shutdownErrorCode;
|
||||
}
|
||||
|
||||
WriteLine("Main event loop starts here... press the 'Q' key to stop the program run");
|
||||
Console.WriteLine("Main event loop starts... press 'Q' to stop");
|
||||
|
||||
// Sequence 1: All ON/OFF
|
||||
for (int ndx = 0; ndx < 4; ndx++)
|
||||
{
|
||||
if (KeyAvailable)
|
||||
if (Console.KeyAvailable)
|
||||
{
|
||||
char c = ReadKey(true).KeyChar;
|
||||
char c = Console.ReadKey(true).KeyChar;
|
||||
if (c == 'q' || c == 'Q')
|
||||
{
|
||||
MakeHardStop.HardStop(modbusMaster, coilOutputStartAddress);
|
||||
@@ -39,34 +41,40 @@ namespace ProtosXdemo
|
||||
}
|
||||
}
|
||||
|
||||
WriteLine("Turning all the outputs ON...");
|
||||
for (byte indx = 0; indx < 16; indx++) // Set machine state values to all outputs ON
|
||||
Console.WriteLine("Turning all outputs ON...");
|
||||
for (byte indx = 0; indx < 16; indx++)
|
||||
{
|
||||
Data.MachineState.DigitalOutputChannels[indx] = true;
|
||||
}
|
||||
|
||||
if (WriteMachineDigitalStateToIO.WriteMachineStateToIO(modbusMaster, coilOutputStartAddress) == Data.MachineState.FailureCode[0]) // Write the machine state values to the I/O hardware
|
||||
if (WriteMachineDigitalStateToIO.WriteMachineStateToIO(modbusMaster, coilOutputStartAddress) == Data.MachineState.FailureCode[0])
|
||||
{
|
||||
WriteLine($"Digital outputs written to I/O at {DateTime.Now:H:mm:ss.fff}");
|
||||
Console.WriteLine($"Digital outputs written at {DateTime.Now:H:mm:ss.fff}");
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteLine("Writing machine-state variable failed... press the <ANY> key to end the program");
|
||||
_ = ReadKey(true);
|
||||
Console.WriteLine("Writing machine state failed... press any key to end");
|
||||
if (!Console.IsInputRedirected)
|
||||
{
|
||||
Console.ReadKey(true);
|
||||
}
|
||||
|
||||
return Data.MachineState.FailureCode[1];
|
||||
}
|
||||
|
||||
Thread.Sleep(250);
|
||||
WriteLine("Turning all the outputs OFF...");
|
||||
|
||||
Console.WriteLine("Turning all outputs OFF...");
|
||||
MakeHardStop.HardStop(modbusMaster, coilOutputStartAddress);
|
||||
Thread.Sleep(250);
|
||||
}
|
||||
|
||||
// Sequence 2: Random states
|
||||
for (int ndx = 0; ndx < 40; ndx++)
|
||||
{
|
||||
if (KeyAvailable)
|
||||
if (Console.KeyAvailable)
|
||||
{
|
||||
char c = ReadKey(true).KeyChar;
|
||||
char c = Console.ReadKey(true).KeyChar;
|
||||
if (c == 'q' || c == 'Q')
|
||||
{
|
||||
MakeHardStop.HardStop(modbusMaster, coilOutputStartAddress);
|
||||
@@ -75,14 +83,18 @@ namespace ProtosXdemo
|
||||
}
|
||||
|
||||
LoadMachineStateDigital.MakeRandomStateDigital();
|
||||
if (WriteMachineDigitalStateToIO.WriteMachineStateToIO(modbusMaster, coilOutputStartAddress) == Data.MachineState.FailureCode[0]) // Write the machine state values to the I/O hardware
|
||||
if (WriteMachineDigitalStateToIO.WriteMachineStateToIO(modbusMaster, coilOutputStartAddress) == Data.MachineState.FailureCode[0])
|
||||
{
|
||||
WriteLine("All digital outputs are now ON");
|
||||
Console.WriteLine("Digital outputs updated with random state");
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteLine("Writing machine-state variable failed... press the <ANY> key to end the program");
|
||||
_ = ReadKey(true);
|
||||
Console.WriteLine("Writing machine state failed... press any key to end");
|
||||
if (!Console.IsInputRedirected)
|
||||
{
|
||||
Console.ReadKey(true);
|
||||
}
|
||||
|
||||
return Data.MachineState.FailureCode[1];
|
||||
}
|
||||
|
||||
@@ -92,4 +104,4 @@ namespace ProtosXdemo
|
||||
return Data.MachineState.FailureCode[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,19 +5,19 @@
|
||||
namespace ProtosXdemo
|
||||
{
|
||||
using Modbus.Device;
|
||||
using static System.Console;
|
||||
|
||||
// Turns all digital outputs OFF.
|
||||
internal class MakeHardStop
|
||||
{
|
||||
public static int HardStop(ModbusIpMaster modbusMaster, ushort coilOutputStartAddress)
|
||||
{
|
||||
WriteLine("Turning all the outputs OFF...");
|
||||
for (byte indx = 0; indx < 16; indx++) // Set machine state values to all outputs OFF
|
||||
Console.WriteLine("Turning all outputs OFF...");
|
||||
for (byte indx = 0; indx < 16; indx++)
|
||||
{
|
||||
Data.MachineState.DigitalOutputChannels[indx] = false;
|
||||
}
|
||||
|
||||
return WriteMachineDigitalStateToIO.WriteMachineStateToIO(modbusMaster, coilOutputStartAddress); // Write the machine state values to the I/O hardware
|
||||
return WriteMachineDigitalStateToIO.WriteMachineStateToIO(modbusMaster, coilOutputStartAddress);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,9 +4,10 @@
|
||||
|
||||
namespace ProtosXdemo
|
||||
{
|
||||
using System;
|
||||
using Modbus.Device;
|
||||
using static System.Console;
|
||||
|
||||
// Simple ON/OFF sequence for testing.
|
||||
internal class OutputExerciser
|
||||
{
|
||||
private readonly ModbusIpMaster master;
|
||||
@@ -27,17 +28,16 @@ namespace ProtosXdemo
|
||||
|
||||
private void SetAllOutputs(bool state, string message)
|
||||
{
|
||||
WriteLine(message);
|
||||
Console.WriteLine(message);
|
||||
UpdateMachineState(state);
|
||||
int result = WriteMachineDigitalStateToIO.WriteMachineStateToIO(this.master, 0);
|
||||
|
||||
if (result == Data.MachineState.FailureCode[0])
|
||||
{
|
||||
WriteLine($"All digital outputs are now {(state ? "ON" : "OFF")}");
|
||||
Console.WriteLine($"All outputs now {(state ? "ON" : "OFF")}");
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteLine("[Error] Writing machine state failed");
|
||||
Console.WriteLine("[Error] Writing machine state failed");
|
||||
WaitForKey("Press any key to exit…");
|
||||
Environment.Exit(Data.MachineState.FailureCode[1]);
|
||||
}
|
||||
@@ -53,8 +53,11 @@ namespace ProtosXdemo
|
||||
|
||||
private static void WaitForKey(string prompt)
|
||||
{
|
||||
WriteLine(prompt);
|
||||
ReadKey(intercept: true);
|
||||
if (Console.IsInputRedirected == false)
|
||||
{
|
||||
Console.WriteLine(prompt);
|
||||
Console.ReadKey(intercept: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,64 +5,56 @@
|
||||
namespace ProtosXdemo
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Sockets;
|
||||
using System.Threading.Tasks;
|
||||
using Modbus.Device;
|
||||
using static System.Console;
|
||||
|
||||
public class Program
|
||||
{
|
||||
private static readonly string IpAddress = "10.10.1.1"; // Replace with the device's IP address
|
||||
private static readonly int Port = 502; // Default Modbus TCP port
|
||||
private static readonly ushort CoilInputStartAddress = 0; // Starting address for digital inputs
|
||||
private static readonly ushort CoilOutputStartAddress = 0; // Starting address for writing to digital outputs
|
||||
private static string? recipeName; // Name of recipe data file in SQL database
|
||||
private static int timesToRepeat; // 0 = run once only (default); > 0 = number of repeating invocations
|
||||
private static readonly string IpAddress = "10.10.1.1";
|
||||
private static readonly int Port = 502;
|
||||
private static readonly ushort CoilOutputStartAddress = 0;
|
||||
|
||||
public static int Main(string[] args)
|
||||
public static async Task<int> Main(string[] args)
|
||||
{
|
||||
WindowUtility.SetAppearanceOptions(); // Set console size
|
||||
WindowUtility.MoveWindowToCenter(); // Set console window in center of display
|
||||
BackgroundColor = ConsoleColor.DarkBlue; // Add a bit of color
|
||||
ForegroundColor = ConsoleColor.White;
|
||||
Clear(); // Start with tabular
|
||||
ConsoleSetup.ApplyDefaultAppearance();
|
||||
var cli = CommandLineParser.Parse(args);
|
||||
string recipeName = cli.RecipeName;
|
||||
int timesToRepeat = cli.TimesToRepeat;
|
||||
|
||||
switch (args.Length)
|
||||
{
|
||||
case 0:
|
||||
recipeName = null;
|
||||
timesToRepeat = 0;
|
||||
break;
|
||||
case 1:
|
||||
recipeName = args[0]; // Name of recipe to run
|
||||
timesToRepeat = 0;
|
||||
break;
|
||||
case 2:
|
||||
recipeName = args[0]; // Name of recipe to run
|
||||
timesToRepeat = Convert.ToInt16(args[1]); // Number of repeats
|
||||
break;
|
||||
default:
|
||||
WriteLine("Too many command-line arguments; program fails here");
|
||||
return Data.MachineState.FailureCode[2];
|
||||
}
|
||||
FailureCodes.FailureCodeValues();
|
||||
ChannelCache.Initialize();
|
||||
|
||||
FailureCodes.FailureCodeValues(); // Seed for much more comprehensive error handler
|
||||
TcpClient client = CreateTcpClient(IpAddress, Port); // NModbus initialization
|
||||
TcpClient client = CreateTcpClient(IpAddress, Port);
|
||||
var modbusMaster = ModbusIpMaster.CreateIp(client);
|
||||
Data.MachineState.NumberOfDigitalInputs = (ushort)Data.MachineState.DigitalInputChannels.Length;
|
||||
Data.MachineState.NumberOfDigitalOutputs = (ushort)Data.MachineState.DigitalOutputChannels.Length;
|
||||
Console.WriteLine("Created Modbus master.");
|
||||
|
||||
WriteLine("Created Modbus master.");
|
||||
ReadDiscreteInputs(modbusMaster);
|
||||
WriteLine("All digital inputs have been read");
|
||||
WriteLine("\nMain event loop begins...\n");
|
||||
string logPath = "machine_log.csv";
|
||||
var logger = new CsvDataLogger(logPath);
|
||||
logger.Start();
|
||||
|
||||
if (MainEventLoop.RunEventLoop(modbusMaster, CoilOutputStartAddress) != Data.MachineState.FailureCode[0])
|
||||
int result = MainEventLoop.RunEventLoop(modbusMaster, CoilOutputStartAddress);
|
||||
|
||||
await logger.StopAsync();
|
||||
|
||||
// Generate plot if log exists and has data
|
||||
if (File.Exists(logPath) && new FileInfo(logPath).Length > 100)
|
||||
{
|
||||
return Data.MachineState.FailureCode[3];
|
||||
string plotPath = "machine_log_plot.png";
|
||||
await CsvPlotter.GeneratePngFromCsvAsync(logPath, plotPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("No data to plot.");
|
||||
}
|
||||
|
||||
int shutdownErrorCode = MakeHardStop.HardStop(modbusMaster, CoilOutputStartAddress); // Write the machine state values to the I/O hardware and check for success
|
||||
return shutdownErrorCode;
|
||||
int shutdownErrorCode = MakeHardStop.HardStop(modbusMaster, CoilOutputStartAddress);
|
||||
client?.Close();
|
||||
return result == 0 ? shutdownErrorCode : result;
|
||||
}
|
||||
|
||||
private static TcpClient CreateTcpClient(string ipAddress, int port)
|
||||
@@ -73,33 +65,10 @@ namespace ProtosXdemo
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
WriteLine($"Error creating TCP client: {ex.Message}");
|
||||
throw; // Rethrow to handle it in the Main method
|
||||
}
|
||||
}
|
||||
|
||||
private static void ReadDiscreteInputs(ModbusIpMaster master)
|
||||
{
|
||||
WriteLine("Start reading inputs.");
|
||||
try
|
||||
{
|
||||
bool[] inputs = master.ReadInputs(CoilInputStartAddress, Data.MachineState.NumberOfDigitalInputs);
|
||||
DisplayDiscreteInputs(inputs);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
WriteLine($"Error reading discrete inputs: {ex.Message}");
|
||||
_ = ReadKey(true);
|
||||
}
|
||||
}
|
||||
|
||||
private static void DisplayDiscreteInputs(bool[] inputs)
|
||||
{
|
||||
WriteLine("Discrete Inputs:");
|
||||
for (int i = 0; i < inputs.Length; i++)
|
||||
{
|
||||
WriteLine($"Input {CoilInputStartAddress + i + 1}: {(inputs[i] ? "On" : "Off")}");
|
||||
Console.WriteLine($"Error creating TCP client: {ex.Message}");
|
||||
Environment.Exit(Data.MachineState.FailureCode[255]);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,89 +4,91 @@
|
||||
|
||||
namespace ProtosXdemo
|
||||
{
|
||||
using System;
|
||||
using System.Threading;
|
||||
using Modbus.Device;
|
||||
using static System.Console;
|
||||
|
||||
// Reads analog input registers (TC, RTD, etc.).
|
||||
public class RegisterReader
|
||||
{
|
||||
private static readonly ModbusIpMaster Master;
|
||||
|
||||
private static readonly ushort TCRegisterStartAddress = 17; // Starting address for reading PX-332-J inputs
|
||||
private static readonly ushort RTDRegisterStartAddress = 25; // Starting address for reading PX-322-1 inputs
|
||||
private static readonly ushort TCNumberOfPoints = 8; // Number of values to read
|
||||
private static readonly ushort RTDNumberOfPoints = 6; // Number of values to read
|
||||
private static readonly ushort TCRegisterStartAddress = 17;
|
||||
private static readonly ushort RTDRegisterStartAddress = 25;
|
||||
private static readonly ushort TCNumberOfPoints = 8;
|
||||
private static readonly ushort RTDNumberOfPoints = 6;
|
||||
|
||||
public static void ReadAll(ModbusIpMaster master)
|
||||
{
|
||||
// Display header for temperature readings
|
||||
WriteLine("Reading and displaying all analogue input values in engineering units");
|
||||
|
||||
// Read and display temperature values for 42 iterations
|
||||
Console.WriteLine("Reading analogue input values in engineering units");
|
||||
for (int iteration = 0; iteration < 42; iteration++)
|
||||
{
|
||||
// Read thermocouple values
|
||||
ushort[] tCValues = master.ReadInputRegisters(TCRegisterStartAddress, TCNumberOfPoints);
|
||||
Console.Write($"T/C #1: {tCValues[0] / 10.0:F1}°C ");
|
||||
Console.Write($"T/C #2: {tCValues[2] / 10.0:F1}°C ");
|
||||
Console.Write($"T/C #3: {tCValues[4] / 10.0:F1}°C ");
|
||||
Console.WriteLine($"T/C #4: {tCValues[6] / 10.0:F1}°C ");
|
||||
|
||||
// Display thermocouple readings with consistent spacing
|
||||
Write($"T/C value #1: {tCValues[0] / 10.0:F1}°C ");
|
||||
Write($"T/C value #2: {tCValues[2] / 10.0:F1}°C ");
|
||||
Write($"T/C value #3: {tCValues[4] / 10.0:F1}°C ");
|
||||
WriteLine($"T/C value #4: {tCValues[6] / 10.0:F1}°C ");
|
||||
|
||||
// Read RTD values
|
||||
ushort[] rTDValues = master.ReadInputRegisters(RTDRegisterStartAddress, RTDNumberOfPoints);
|
||||
Console.Write($"RTD #1: {rTDValues[0] / 10.0:F1}°C ");
|
||||
Console.Write($"RTD #2: {rTDValues[2] / 10.0:F1}°C ");
|
||||
Console.WriteLine($"RTD #3: {rTDValues[4] / 10.0:F1}°C ");
|
||||
|
||||
// Display RTD readings with consistent spacing
|
||||
Write($"RTD value #1: {rTDValues[0] / 10.0:F1}°C ");
|
||||
Write($"RTD value #2: {rTDValues[2] / 10.0:F1}°C ");
|
||||
WriteLine($"RTD value #3: {rTDValues[4] / 10.0:F1}°C ");
|
||||
|
||||
// Add delay between readings
|
||||
Thread.Sleep(250);
|
||||
}
|
||||
|
||||
WriteLine("Reading all input registers…");
|
||||
Console.WriteLine("Reading all input registers…");
|
||||
try
|
||||
{
|
||||
ushort[] registers = master.ReadInputRegisters(0, ChannelCache.AnalogInputCount);
|
||||
ushort[] registers = master.ReadInputRegisters(0, 22); // Fixed: use known total (8+4+4+4+4=24? but original used 22)
|
||||
Display(registers);
|
||||
bool[] bits = ConvertRegistersToBools(registers);
|
||||
Display(bits);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
WriteLine($"[Error] Reading input registers failed → {ex.Message}");
|
||||
Console.WriteLine($"[Error] Reading input registers failed → {ex.Message}");
|
||||
PromptKey("Press any key to continue…");
|
||||
}
|
||||
}
|
||||
|
||||
private static void Display(ushort[] registers)
|
||||
{
|
||||
WriteLine("Input Registers (ushort):");
|
||||
Console.WriteLine("Input Registers (ushort):");
|
||||
for (int i = 0; i < registers.Length; i++)
|
||||
{
|
||||
WriteLine($"Register #{i + 1}: {registers[i]}");
|
||||
Console.WriteLine($"Register #{i + 1}: {registers[i]}");
|
||||
}
|
||||
}
|
||||
|
||||
private static void Display(bool[] bits)
|
||||
{
|
||||
WriteLine("Flattened Bits:");
|
||||
Console.WriteLine("Flattened Bits:");
|
||||
for (int i = 0; i < bits.Length; i++)
|
||||
{
|
||||
WriteLine($"Bit #{i + 1}: {(bits[i] ? "On" : "Off")}");
|
||||
Console.WriteLine($"Bit #{i + 1}: {(bits[i] ? "On" : "Off")}");
|
||||
}
|
||||
}
|
||||
|
||||
private static bool[] ConvertRegistersToBools(ushort[] registers)
|
||||
{
|
||||
return registers.SelectMany(r => Enumerable.Range(0, 16).Select(bit => ((r >> bit) & 1) == 1)).ToArray();
|
||||
var boolList = new System.Collections.Generic.List<bool>();
|
||||
foreach (ushort reg in registers)
|
||||
{
|
||||
for (int bit = 0; bit < 16; bit++)
|
||||
{
|
||||
boolList.Add(((reg >> bit) & 1) == 1);
|
||||
}
|
||||
}
|
||||
|
||||
return boolList.ToArray();
|
||||
}
|
||||
|
||||
private static void PromptKey(string prompt)
|
||||
{
|
||||
WriteLine(prompt);
|
||||
ReadKey(intercept: true);
|
||||
if (Console.IsInputRedirected == false)
|
||||
{
|
||||
Console.WriteLine(prompt);
|
||||
Console.ReadKey(intercept: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
// <copyright file="WindowUtility.cs" company="PlaceholderCompany">
|
||||
// <copyright file="WindowUtility.cs" company="PlaceholderCompany">
|
||||
// Copyright (c) PlaceholderCompany. All rights reserved.
|
||||
// </copyright>
|
||||
|
||||
namespace ProtosXdemo
|
||||
{
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using static System.Console;
|
||||
|
||||
// Windows-only console window manipulation.
|
||||
internal static class WindowUtility
|
||||
{
|
||||
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
@@ -15,8 +16,8 @@ namespace ProtosXdemo
|
||||
[DllImport("user32.dll", SetLastError = true)]
|
||||
private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int x, int y, int cx, int cy, uint uFlags);
|
||||
|
||||
private const uint SWPNOSIZE = 0x0001;
|
||||
private const uint SWPNOZORDER = 0x0004;
|
||||
private const uint SWPNOSIZE = 0x0001;
|
||||
|
||||
private static Size GetScreenSize() => new Size(GetSystemMetrics(0), GetSystemMetrics(1));
|
||||
|
||||
@@ -46,10 +47,10 @@ namespace ProtosXdemo
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct Rect
|
||||
{
|
||||
public int Left; // x position of upper-left corner
|
||||
public int Top; // y position of upper-left corner
|
||||
public int Right; // x position of lower-right corner
|
||||
public int Bottom; // y position of lower-right corner
|
||||
public int Left;
|
||||
public int Top;
|
||||
public int Right;
|
||||
public int Bottom;
|
||||
}
|
||||
|
||||
private static Size GetWindowSize(IntPtr window)
|
||||
@@ -59,35 +60,43 @@ namespace ProtosXdemo
|
||||
throw new Exception("Unable to get window rect!");
|
||||
}
|
||||
|
||||
int width = rect.Right - rect.Left;
|
||||
int height = rect.Bottom - rect.Top;
|
||||
|
||||
return new Size(width, height);
|
||||
return new Size(rect.Right - rect.Left, rect.Bottom - rect.Top);
|
||||
}
|
||||
|
||||
public static void MoveWindowToCenter()
|
||||
{
|
||||
if (!OperatingSystem.IsWindows())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
IntPtr window = GetConsoleWindow();
|
||||
if (window == IntPtr.Zero)
|
||||
{
|
||||
throw new Exception("Couldn't find a window to center!");
|
||||
return;
|
||||
}
|
||||
|
||||
Size screenSize = GetScreenSize();
|
||||
Size windowSize = GetWindowSize(window);
|
||||
|
||||
int x = (screenSize.Width - windowSize.Width) / 2;
|
||||
int y = (screenSize.Height - windowSize.Height) / 2;
|
||||
|
||||
SetWindowPos(window, IntPtr.Zero, x, y, 0, 0, SWPNOSIZE | SWPNOZORDER);
|
||||
}
|
||||
|
||||
public static void SetAppearanceOptions()
|
||||
{
|
||||
if (!IsOutputRedirected) // set console window size
|
||||
if (!Console.IsOutputRedirected && OperatingSystem.IsWindows())
|
||||
{
|
||||
BufferWidth = 150;
|
||||
SetWindowSize(BufferWidth, 50);
|
||||
try
|
||||
{
|
||||
Console.BufferWidth = Math.Min(150, Console.LargestWindowWidth);
|
||||
int width = Console.BufferWidth;
|
||||
int height = Math.Min(50, Console.LargestWindowHeight);
|
||||
Console.SetWindowSize(width, height);
|
||||
}
|
||||
catch
|
||||
{ /* Ignore if not supported */
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,9 +4,10 @@
|
||||
|
||||
namespace ProtosXdemo
|
||||
{
|
||||
using System;
|
||||
using Modbus.Device;
|
||||
using static System.Console;
|
||||
|
||||
// Writes digital output state to hardware via Modbus.
|
||||
internal class WriteMachineDigitalStateToIO
|
||||
{
|
||||
public static byte WriteMachineStateToIO(ModbusIpMaster master, ushort coilOutputStartAddress)
|
||||
@@ -17,12 +18,16 @@ namespace ProtosXdemo
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
WriteLine($"Error writing discrete outputs: {ex.Message}");
|
||||
_ = ReadKey(true);
|
||||
Console.WriteLine($"Error writing discrete outputs: {ex.Message}");
|
||||
if (!Console.IsInputRedirected)
|
||||
{
|
||||
Console.ReadKey(true);
|
||||
}
|
||||
|
||||
return Data.MachineState.FailureCode[1];
|
||||
}
|
||||
|
||||
return Data.MachineState.FailureCode[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user