laatzen/Common/modbus_master_csharp/Program.cs

786 lines
36 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO.Ports;
using XYLEM.Communication;
using System.Xml.Linq;
using XYLEM.Base;
using XYLEM.Communication.ValueEncoders;
using System.Linq.Expressions;
using System.Threading;
using MagFlux6200_metrology_reg_list;
using XYLEM.Device;
using static modbus_master_csharp.Program;
using System.Diagnostics.Contracts;
using System.Security.Policy;
using System.IO;
using System.Diagnostics;
using System.Xml.Schema;
using System.Net.NetworkInformation;
namespace modbus_master_csharp
{
internal class Program
{
static bool LIVE_RUN = true;
#if (DEBUG)
//static bool LIVE_RUN = false; // overwrite default
static bool LIVE_RUN_DIRECT_MET = false; // Run using RS485 9600,8,E setup as default
//static bool LIVE_RUN_DIRECT_MET = LIVE_RUN & true; // overwrite default to Run using 115200,8,N setup as default
#endif
/*For testing directly to metrology */
static string[] argsDefault1 = new string[] { "TEST", "COM11", "115200", "N" };
/*For metrology RS485 when transparent to metrology*/
static string[] argsDefault2 = new string[] { "SAVE_CAL", "COM2" };
// Having more ports
static string[] argsDefault3 = new string[] { "TEST", "COM2", "COM6" };
/*For metrology RS485 when transparent to metrology*/
static string[] argsDefault4 = new string[] { "LOAD_CAL=\"0_sensor_cal_for_testing.xml\"", "COM2" };
/*For metrology RS485 when transparent to metrology*/
static string[] argsDefault5 = new string[] { "TEST", "COM2" };
#region Reused functions for testing
private static bool GetDeviceHealthy(IMagFluxRequestProtocol dut)
{
var name = nameof(dut.GetDeviceHealthy);
var isOkay = dut.GetDeviceHealthy();
var message = $"{name}: {(isOkay ? "Is okay" : "Has errors")}";
if (!isOkay)
{
if (dut.GetDeviceErrorMessages(out var errorList))
{
Console.WriteLine(message + "\nError List:\n" + errorList.ToSingleString(false));
}
else
{
Console.WriteLine(message);
}
}
return isOkay;
}
private static bool GetCalibrationPoints(IMagFluxRequestProtocol dut, out CalibrationPoints value_out, bool printoutPoints)
{
var name = nameof(dut.GetCalibrationPoints);
var isOkay = dut.GetCalibrationPoints(out value_out);
if (printoutPoints)
{
Console.WriteLine($"{name}: Read points-> {(isOkay ? "\n"+value_out.ToString() : "!Read failed!")}");
}
else
{
Console.WriteLine($"{name}: Reading calibration points {(isOkay ? "Okay" : "!Read failed!")}");
}
return isOkay;
}
private static bool SetCalibrationPoints(IMagFluxRequestProtocol dut, CalibrationPoints new_calibration_points)
{
var isOkay = dut.SetCalibrationPoints(new_calibration_points);
Console.WriteLine($"{nameof(dut.SetCalibrationPoints)}: {(isOkay ? "Okay" : "Failed")}\n{new_calibration_points}");
return isOkay;
}
private static void RunTestCalibrationPoints(IMagFluxRequestProtocol dut, CalibrationPoints new_calibration_points)
{
{ // Write calibrations
var isOkay = SetCalibrationPoints(dut, new_calibration_points);
if (!isOkay)
{
GetDeviceHealthy(dut);
throw new Exception("Failed writing the calibration to device");
}
}
{ // Get calibrations and check it
if (!GetCalibrationPoints(dut, out var value_out, false/*Don't printout*/) || (new_calibration_points != value_out))
{
GetDeviceHealthy(dut);
var msg = $"Failed! Calibration is not the same read as written to device\nWritten:\n{new_calibration_points}\nRead:\n{value_out}";
Console.WriteLine(msg);
throw new Exception(msg);
}
}
}
/// <summary>
/// save and load what is read from device
/// </summary>
/// <param name="dut"></param>
/// <param name="value_out"></param>
private static void SaveCalPointsToTempFolder(IMagFluxRequestProtocol dut, CalibrationPoints value_out)
{
var tempSaveFolder = Environment.GetEnvironmentVariable("USERPROFILE")+@"\Downloads\";
if (dut.GetSensorSerialNo(out var sensorSerial))
{
dut.GetUniqueId(out var id);
var sensor_cal_file = tempSaveFolder+$"{sensorSerial}_sensor_cal_id_{id}_{DateTime.Now.ToFileTimeTxt()}.xml";
Console.WriteLine($"Save to:\n{sensor_cal_file}");
value_out.SaveToFile(sensor_cal_file);
// Check saved data
var to_saved_points = value_out.calibrations.ToArray();
value_out.LoadFromFile(sensor_cal_file);
var loaded_points = value_out.calibrations.ToArray();
if (!to_saved_points.SequenceEqual(loaded_points))
{
var msg = $"Failed save of calibrations\nSaved:\n{to_saved_points}\nLoaded:\n{loaded_points}";
Console.WriteLine(msg);
throw new Exception(msg);
}
}
}
private static void SAVE_CAL(List<IMagFluxRequestProtocol> duts)
{
{ // Check Save all calibrations one DUT at a time
for (var dutIdx = 0; dutIdx < duts.Count(); dutIdx++)
{
var dut = duts[dutIdx];
Console.WriteLine($"------------------- DUT{dutIdx+1} --------------------");
try
{
if (!GetCalibrationPoints(dut, out var value_out, true/*printout points*/))
{
GetDeviceHealthy(dut);
throw new Exception("Failed saving device calibration");
}
else
{
// save and load what is read from device
SaveCalPointsToTempFolder(dut, value_out);
}
}
catch (Exception ex)
{
dut.OpenLogFile();
throw ex;
}
}
}
}
/// <summary>
/// Load Calibrations
/// </summary>
/// <param name="duts"></param>
/// <param name="loadedCalibrations"></param>
private static void LOAD_CAL(List<IMagFluxRequestProtocol> duts, List<CalibrationPoints> loadedCalibrations)
{
{ // Check all Values one by one a DUT at a time
if (duts.Count() != loadedCalibrations.Count())
{
throw new Exception("The number of calibrations is not the same as DUT's to load it in");
}
Console.WriteLine("\nSave Calibrations before starting writing new calibrations\n");
SAVE_CAL(duts);
Console.WriteLine("\nStarting writing new calibrations\n");
for (var dutIdx = 0; dutIdx < duts.Count(); dutIdx++)
{
var dut = duts[dutIdx];
Console.WriteLine($"------------------- DUT{dutIdx+1} --------------------");
try
{
var new_calibration_points = loadedCalibrations[dutIdx];
var isOkay = SetCalibrationPoints(dut, new_calibration_points);
if (!isOkay)
{
throw new Exception($"Writing calibration Fail!\n{new_calibration_points}");
}
}
catch (Exception ex)
{
dut.OpenLogFile();
throw ex;
}
}
}
}
static void TEST(List<IMagFluxRequestProtocol> duts)
{
{ // Check all Values one by one a DUT at a time
for (var dutIdx = 0; dutIdx < duts.Count(); dutIdx++)
{
Console.WriteLine($"------------------- DUT{dutIdx+1} --------------------");
var dut = duts[dutIdx];
try
{
//if (false) // Don't do basic reads
if (true) // Do basic read test
{
{
var name = nameof(dut.GetFwGitHash);
var isOkay = dut.GetFwGitHash(out var value_out);
var message = $"{name}: {(isOkay ? value_out : "!Read failed!")}";
Console.WriteLine(message);
if (!isOkay)
{
throw new Exception(message);
}
}
{
var name = nameof(dut.GetSensorSerialNo);
var isOkay = dut.GetSensorSerialNo(out var value_out);
var message = $"{name}: {(isOkay ? value_out : "!Read failed!")}";
Console.WriteLine(message);
if (!isOkay)
{
throw new Exception(message);
}
}
{
var name = nameof(dut.GetUniqueId);
var isOkay = dut.GetUniqueId(out var value_out);
var message = $"{name}: {(isOkay ? value_out : "!Read failed!")}";
Console.WriteLine(message);
if (!isOkay)
{
throw new Exception(message);
}
}
{
var name = nameof(dut.GetFirmwareVersion);
var isOkay = dut.GetFirmwareVersion(out var value_out);
var message = $"{name}: {(isOkay ? value_out : "!Read failed!")}";
Console.WriteLine(message);
if (!isOkay)
{
throw new Exception(message);
}
}
{
var name = nameof(dut.GetFwBuildDate);
var isOkay = dut.GetFwBuildDate(out var value_out);
var message = $"{name}: {(isOkay ? value_out : "!Read failed!")}";
Console.WriteLine(message);
if (!isOkay)
{
throw new Exception(message);
}
}
{
var name = nameof(dut.GetFlowRate_lps);
var isOkay = dut.GetFlowRate_lps(out var value_out);
var message = $"{name}: {(isOkay ? $"{value_out} l/s or {value_out*3.6} m3/h" : "!Read failed!")}";
Console.WriteLine(message);
if (!isOkay)
{
throw new Exception(message);
}
}
{
var name = nameof(dut.GetDn_mm);
var isOkay = dut.GetDn_mm(out var value_out);
var message = $"{name}: {(isOkay ? $"{value_out}mm" : "!Read failed!")}";
Console.WriteLine(message);
if (!isOkay)
{
throw new Exception(message);
}
}
{
GetDeviceHealthy(dut);
}
{
var name = nameof(dut.GetDeviceErrorMessages);
var isOkay = dut.GetDeviceErrorMessages(out var value_out);
var message = $"{name}: \n{(isOkay ? String.Join("\n", value_out) : "!Read failed!")}";
Console.WriteLine(message);
if (!isOkay)
{
throw new Exception(message);
}
}
{
var name = nameof(dut.AbortDeviceForCalibration);
var isOkay = dut.AbortDeviceForCalibration();
var message = $"{name}: {(isOkay ? "Done" : "!Abort failed!")}";
Console.WriteLine(message);
if (!isOkay)
{
throw new Exception(message);
}
}
{
if (!GetCalibrationPoints(dut, out var value_out, true/*printout points*/))
{
GetDeviceHealthy(dut);
}
else
{
// save and load what is read from device
SaveCalPointsToTempFolder(dut, value_out);
}
}
}
//if (false) // Do not execute calibration test
if (LIVE_RUN && true) // execute calibration test
{ // A single calibration Test
{ // --- Test setting the device ready for the first measurement used for calibrations ---
{
var isOkay = dut.PrepareDeviceForCalibration();
Console.WriteLine($"{nameof(dut.PrepareDeviceForCalibration)}: {(isOkay ? "Okay" : "Failed")}");
if (!isOkay || !GetCalibrationPoints(dut, out var value_out, true/*printout points*/))
{
GetDeviceHealthy(dut);
throw new Exception("Failed Setting device up for calibration");
}
}
}
{ // --- Do a simple 2 point calibration with zero ---
var new_calibration_points = new CalibrationPoints(
new List<CalibrationPoint> {
new CalibrationPoint(0,0.001f), // 0
new CalibrationPoint(1,1.002f), // 1
new CalibrationPoint(2,2.003f), // 2
},
dut.CalibrationPointsMax // Extend to
);
RunTestCalibrationPoints(dut, new_calibration_points);
}
{ // --- Do a 6 point calibration with negative zero ---
var new_calibration_points = new CalibrationPoints(
new List<CalibrationPoint> {
new CalibrationPoint(0,-0.001f), // 0
new CalibrationPoint(1,1.002f), // 1
new CalibrationPoint(2,2.003f), // 2
new CalibrationPoint(3,3.004f), // 3
new CalibrationPoint(4,4.005f), // 4
new CalibrationPoint(5,5.006f), // 5
},
dut.CalibrationPointsMax // Extend to
);
RunTestCalibrationPoints(dut, new_calibration_points);
// save and load what is read from device
SaveCalPointsToTempFolder(dut, new_calibration_points);
}
{ // --- Do a check of write of calibrations points that is not ordered correct ---
Console.WriteLine($"Test writing calibration in wrong order is failing and Background check service is running");
{
var new_calibration_points = new CalibrationPoints(
new List<CalibrationPoint> {
new CalibrationPoint(0,-0.001f), // 0
new CalibrationPoint(3,3.002f), // 1 Error this an error in order and need to brake calibrations
new CalibrationPoint(2,2.003f), // 2
},
dut.CalibrationPointsMax // Extend to
);
var isOkay = SetCalibrationPoints(dut, new_calibration_points);
if (isOkay)
{
throw new Exception($"Writing calibration in wrong order needs to Fail!\n{new_calibration_points}");
}
int maxWait_s = 5;
while (maxWait_s-- > 0)
{
Console.Write($"\rWait timer out in {maxWait_s}s for getting Device Healthy is returning showing error: ");
if (!dut.GetFlowRate_lps(out var value_out))
{
throw new Exception($"read of flow failed during polling Device Healthy!");
}
if (!GetDeviceHealthy(dut))
{
Console.WriteLine($"\nGot correct replay that is Device healthy is \"false\"");
break; // Exit
}
Task.Delay(1000).GetAwaiter().GetResult();
}
if (maxWait_s <= 0)
{
throw new Exception($"Device Healthy was returning \"false\" when calibration was failing!\n{new_calibration_points}");
}
}
{// Cleanup by returning calibration to default
var isOkay = dut.PrepareDeviceForCalibration();
if (!isOkay)
{
throw new Exception("Failing returning calibration to default!");
}
}
}
}
}
catch (Exception ex)
{
dut.OpenLogFile();
throw ex;
}
}
}
}
#endregion
enum RUN_ACTION
{
UNKNOWN,
TEST,
SAVE_CAL,
LOAD_CAL,
}
/// <summary>
/// For unit testing setup
/// </summary>
/// <param name="args"></param>
static void Main(string[] args)
{
int ret_error = 0; // Is okay
List<IMagFluxRequestProtocol> duts = new List<IMagFluxRequestProtocol>();
try
{
var runMode = RUN_TYPE.Mock;
if (LIVE_RUN)
{
runMode = RUN_TYPE.Normal;
}
// Just for internal testing
#if (DEBUG)
if (args.Length <= 0)
{
if(LIVE_RUN)
{
args = argsDefault5; //metrology RS485 when transparent to metrology
}
else
{
args = argsDefault5; // Run the functional test as mock
}
if (LIVE_RUN_DIRECT_MET)
{
args = argsDefault1; //testing directly to metrology
}
}
#endif
#region Handle if arguments
bool gotArgs = (args.Length >= 1);
List<string> comports = new List<string>();
int? baudrate = null;
Parity? parity = null;
var runtask = RUN_ACTION.UNKNOWN;
var loadedCalibrations = new List<CalibrationPoints>();
var argHelpNeeded = gotArgs ? false : true;
// Use Default connection if nothing is provided by arguments
var arg_offset = 0;
if (args.Length > arg_offset)
{
// What task to do
if (args.Length > arg_offset)
{
if ("-H" == args[arg_offset].ToUpper())
{
argHelpNeeded = true;
}
else if ("TEST" == args[arg_offset].ToUpper())
{
runtask = RUN_ACTION.TEST;
}
else if ("SAVE_CAL" == args[arg_offset].ToUpper())
{
runtask = RUN_ACTION.SAVE_CAL;
}
else if (args[arg_offset].ToUpper().StartsWith("LOAD_CAL="))
{
runtask = RUN_ACTION.LOAD_CAL;
var calFilePath = args[arg_offset].Split('=').Last();
var cal = new CalibrationPoints();
if (!File.Exists(calFilePath) || !cal.LoadFromFile(calFilePath))
{
throw new InvalidDataException($"Error load calibrations from\n {calFilePath}");
}
loadedCalibrations.Add(cal);
}
else
{
throw new NotImplementedException($"Missing support for parameter {args[arg_offset]}");
}
}
arg_offset++;
{ // Parse com port / ports
var exit = false;
while (!exit)
{
if (args.Length > arg_offset)
{
var arg = args[arg_offset].ToUpper();
if ("COM" == arg.Remove(3))
{
comports.Add(arg);
}
else if (comports.Count <= 0)
{
Console.WriteLine($"Com port {arg} not understood.");
argHelpNeeded = true;
exit = true;
}
else
{
exit = true; // No more com to connect to
}
arg_offset++;
}
else
{
exit = true; // No more com to connect to
}
}
}
if (args.Length > arg_offset)
{
var arg = args[arg_offset].ToUpper();
var allowed_baudrate = new string[] { "9600", "19200", "38400", "57600", "115200", "230400" };
if (allowed_baudrate.Contains(arg))
{
baudrate = int.Parse(arg);
}
else
{
Console.WriteLine($"Baud rate {arg} not known.");
baudrate = -1;
argHelpNeeded = true;
}
}
arg_offset++;
if (args.Length > arg_offset)
{
var arg = args[arg_offset].ToUpper();
if (arg == "E")
{
parity = Parity.Even;
}
else if (arg == "N")
{
parity = Parity.None;
}
else if (arg == "O")
{
parity = Parity.Odd;
}
else
{
Console.WriteLine($"Parity {arg} not known.");
parity = Parity.None;
argHelpNeeded = true;
}
}
}
if (argHelpNeeded)
{
var name_exe = System.AppDomain.CurrentDomain.FriendlyName;
var message = $@"
Arglist indicates help needed. See stdout.
Example of from command line:
Save calibrations only include COMx to use standard baudrate and parity setup:
""{name_exe} {argsDefault2.ToSingleString(false)}""
or run functional test for 2 MagFlux
""{name_exe} {argsDefault3.ToSingleString(false)}""
To run functional test and configure both COMx, baudrate, parity. (Used for com directly to Metrology)
""{name_exe} {argsDefault1.ToSingleString(false)}""
To Load calibrations only include COMx to use standard baudrate and parity setup:
""{name_exe} {argsDefault4.ToSingleString(false)}""
";
Console.WriteLine(message);
return; // exit main
}
#endregion
// Set window to max size to better fit content
// Not needed for now! Console.SetWindowSize(Console.LargestWindowWidth, Console.LargestWindowHeight);
#region Setup Communication to dut's
foreach (var comport in comports)
{
if (runMode != RUN_TYPE.Normal)
{
Console.WriteLine($"{new string('!', 50)}\n Warning is in running in {runMode} mode\n{new string('!', 50)}");
}
MagFlux6200 dut = null;
try
{ // Set up on DUT
dut = new MagFlux6200(runMode);
var log_path = Environment.GetEnvironmentVariable("USERPROFILE")+@"\Downloads\";
var isOkay = dut.SetLogLocation(log_path, "logs_"+comport);
Console.WriteLine($"Using program log path:\n{log_path}");
isOkay &=gotArgs;
if (isOkay)
{ // Use standard and just setup as port
if (baudrate == null)
{
isOkay &=dut.Connect(comport);
}
else if ((baudrate != null) && (parity != null))
{
isOkay &=dut.Connect(comport, baudrate.Value, parity.Value);
}
else
{
isOkay = false; // Error in settings
}
}
if (isOkay)
{
duts.Add(dut);
}
else
{
var port = comport;
if (String.IsNullOrEmpty(port))
{
port = "COM ?";
}
throw new Exception($"Fail to setup device for {port}");
}
}
catch (Exception ex)
{
if (dut!=null)
{
dut.OpenLogFile();
}
throw ex;
}
}
#endregion //#region Setup Communication to dut's
switch (runtask)
{
case RUN_ACTION.TEST:
TEST(duts);
break;
case RUN_ACTION.SAVE_CAL:
{
SAVE_CAL(duts);
}
break;
case RUN_ACTION.LOAD_CAL:
{
LOAD_CAL(duts, loadedCalibrations);
}
break;
default:
{
throw new NotImplementedException($"Missing support run mode {runtask}");
}
}
Console.WriteLine("finished functional testing");
// Is set to true when the monitoring values also needs to stop
bool exitMonitoringLoop = false, pauseMonitoringLoop = false, _LastPauseMonitoringLoop = false;
// Start a task for monitoring a key for exit
Console.WriteLine("Press key 'x' exit, 'p' for pause read, 'c' continue read");
Task.Run(() =>
{
ConsoleKeyInfo keyinfo;
do
{
keyinfo = Console.ReadKey();
if (keyinfo.Key == ConsoleKey.P)
{
pauseMonitoringLoop = true;
}
else if (keyinfo.Key == ConsoleKey.C)
{
pauseMonitoringLoop = false;
}
else
{
Console.WriteLine($"\n{keyinfo.Key} was pressed. Press key 'x' exit");
}
}
while (keyinfo.Key != ConsoleKey.X);
exitMonitoringLoop = true;
});
// Just read flow and other values for testing general measurement
int count = 0;
// "Structure of an interpolated string"
// https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/tokens/interpolated
while (!exitMonitoringLoop)
{
if (!pauseMonitoringLoop)
{
var message = "\r";
var dutNum = 1;
foreach (var dut in duts)
{
{ // Read Flow
var isOkay = dut.GetFlowRate_lps(out var value_out);
if (dutNum > 1)
{
message += " | ";
}
message += $"DUT{dutNum++}: ";
if (!dut.GetDeviceHealthy())
{
message += $"Error:";
if (dut.GetDeviceErrorMessages(out var errorList))
{
message += $"{errorList.ToSingleString(false)} ";
}
else
{
message += $"No Text to display!";
}
}
else
{
const int w = 9;
message += $"{(isOkay ? $"{value_out*3.6,w:F3} m3/h" : $"!Read failed!")} {dut.GetComCounters()}\t";
if (!isOkay)
{
throw new Exception(message);
}
}
}
}
Console.Write(message + $" msg={++count}\t");
}
{ // Print out if paused read
if (_LastPauseMonitoringLoop != pauseMonitoringLoop)
{
_LastPauseMonitoringLoop = pauseMonitoringLoop;
if (pauseMonitoringLoop)
{
Console.Write("\rRead is Paused!\t\t\t\t\t\t\t\t");
}
}
}
}
}
catch (Exception ex)
{
var name_exe = System.AppDomain.CurrentDomain.FriendlyName;
AppConst.Logger.AddFuncLog(true, name_exe, "main()", ex);
AppConst.Logger.OpenProgramLogFile();
ret_error = -1; //https://learn.microsoft.com/en-us/windows/win32/debug/system-error-codes
}
finally
{
// Close connection before exit
try
{
if (duts.Count != 0)
{
foreach (var dut in duts)
{
dut.CloseConnection();
}
}
}
catch { }
Environment.Exit(ret_error); //https://learn.microsoft.com/en-us/windows/win32/debug/system-error-codes
}
}
}
}