I've built a C# application to interact with a Control System at work. It calculates some values and/or pulls them from a spreadsheet and then writes them to an OPC server using EasyOPCClient. When I run this application, it will run for 3-5 minutes the first time, close, and reopen, then run 30 seconds to 2 minutes from that point on. Each time it closes and restarts with no errors printed to the console (at least that I can see before it closes), and I don't see anything in the event logs that would make it close. Does anyone see anything I'm doing in the program that would cause that? This is my first attempt at multi-threading, so it wouldn't surprise me if it were something with that.
using System;
using System.IO;
using System.Threading;
using OpcLabs.EasyOpc.DataAccess;
using Excel = Microsoft.Office.Interop.Excel;
/// <summary>
/// OPC Simulator:
/// Used to assist in training operators on how to use Honeywell Experion System
/// Simulates PID and DEVCTL inputs and outputs; uses historical data from ParcView to simulate DACA blocks
///
/// Date: 07/14/2022
/// Authours:
/// </summary>
namespace OPCTest
{
public class Program
{
public static double[] genFunction(int number_of_samples, float amplitude, float frequency_in_hz)
{
/*
* Function: Generates an array of points sampled from a noisy sine wave function.
*
* IN: number_of_samples - The number of samples to generate
* amplitude - Multiplier to scale the function
* frequency_in_hz - Frequency of the generated signal
*
* OUT: An array of integers sampled from the generated sine wave.
*/
Random rand = new Random();
// Initialize any needed variables
double[] points = new double[number_of_samples];
float time;
int samples;
// Generates a noisy signal
for(samples = 0; samples < number_of_samples; samples )
{
time = samples / (frequency_in_hz);
points[samples] = amplitude * (Math.Sin(2.0f * Math.PI * frequency_in_hz * time / 5.0f rand.Next(0, 100))
1/5 * Math.Sin(2.0f * Math.PI * frequency_in_hz * time rand.Next(0, 100))
3 * (Math.Sin(2.0f * Math.PI * frequency_in_hz * time / 100.0f rand.Next(0, 100))));
}
return points;
}
public static void DACA(EasyDAClient client)
{
/*
* Function: Simulates DACA block I/Os using a function generator
*
* IN: None
*
* OUT: None
*
*/
//var client = new EasyDAClient();
string textFile = @"C:\Temp\DACAs.txt"; // Path to file with CMs containing DACAs in it. CM name only
float pveulo, pveuhi, pvloalm, pvhialm, pvllalm, pvhhalm; // Initialize all of the useful PV alarms and variables
float pv, pvlo, pvhi; // PVs that will be used in calculations
float shift; // Initialize the variable that will vertically shift the signal to the correct magnitude
float amplitude; // Initialize the variable that will determine the amplitude of each PV signal
double[] signal; // Initialize an array of points that will be used to store a generated signal
// Read a text file line by line.
string[] lines = File.ReadAllLines(textFile);
while (true)
{
if (File.Exists(textFile))
{
// Iterate through DACAs in text file
foreach (string line in lines)
{
try
{
// Store the necessary variables
pvhi = pveuhi = float.Parse(client.ReadItemValue("", "HWHsc.OPCServer", line ".DACA.PVEUHI").ToString());
pvlo = pveulo = float.Parse(client.ReadItemValue("", "HWHsc.OPCServer", line ".DACA.PVEULO").ToString());
if (float.TryParse(client.ReadItem("", "HWHsc.OPCServer", line ".DACA.PVHHALM.TP").ToString().Split(' ')[0], out pvhhalm))
{
// Console.WriteLine("PVHIHI: " pvhhalm);
pvhi = pvhhalm;
}
if (float.TryParse(client.ReadItem("", "HWHsc.OPCServer", line ".DACA.PVHIALM.TP").ToString().Split(' ')[0], out pvhialm))
{
// Console.WriteLine("PVHI: " pvhialm);
pvhi = pvhialm;
}
if (float.TryParse(client.ReadItem("", "HWHsc.OPCServer", line ".DACA.PVLLALM.TP").ToString().Split(' ')[0], out pvllalm))
{
// Console.WriteLine("PVLOLO: " pvllalm);
pvlo = pvllalm;
}
if (float.TryParse(client.ReadItem("", "HWHsc.OPCServer", line ".DACA.PVLOALM.TP").ToString().Split(' ')[0], out pvloalm))
{
// Console.WriteLine("PVLO: " pvloalm);
pvlo = pvloalm;
}
// Console.WriteLine("PVHI: " pvhi " PVLO: " pvlo);
amplitude = ((pvhi - pvlo) / 2.0f) * 0.0375f;
shift = ((pvhi - pvlo) * 0.8f) pvlo; // Shift the signal up to oscillate between the bounds of the PV
// Generate the signal
signal = genFunction(10, amplitude, 0.003f);
foreach (float sig in signal)
{
pv = sig shift;
client.WriteItemValue("", "HWHsc.OPCServer", line ".DACA.PV", pv);
// Console.WriteLine(line ": " pv);
}
}
catch(Exception e)
{
// Note the failures
Console.WriteLine("DACA Failed at: " line "\n\t" e "\n");
}
}
System.Threading.Thread.Sleep(1000);
}
}
}
public static void DEVCTLA(EasyDAClient client)
{
/*
* Function: Simulates DEVCTLA block I/Os
*
* IN: client - OPC Client that will be used to read and write to the Experion server
*
* OUT: None
*/
string textFile = @"C:\Temp\DEVCTLAs.txt";
string gop, gpv;
// Read a text file line by line.
string[] tags = File.ReadAllLines(textFile);
while (true)
{
foreach (string tagName in tags)
{
try
{
gop = client.ReadItemValue("", "HWHsc.OPCServer2", tagName ".DEVCTLA.GOP").ToString();
gpv = client.ReadItemValue("", "HWHsc.OPCServer2", tagName ".DEVCTLA.GPV").ToString();
if (gpv != gop) client.WriteItemValue("", "HWHsc.OPCServer2", tagName ".DEVCTLA.GPV", gop);
}
catch(Exception e)
{
Console.WriteLine("DEVCTLA Failed at: " tagName "\n\t" e "\n");
}
}
System.Threading.Thread.Sleep(1000);
}
}
public static void PID(EasyDAClient client)
{
/*
* Function: Simulates PID block I/Os
*
* IN: client - OPC Client that will be used to read and write to the Experion server
*
* OUT: None
*/
string textFile = @"C:\Temp\PIDs.txt"; // Path to file with CMs containing PIDs in it. CM name only - I plan to make this browsable by the user
double OP = 0; // Place to store OP of PID
double pveulo = 0; // Place to store PVEULO from DACA
double pveuhi = 0; // Place to store PVEUHI from DACA
double PV = 0; // Place to store calculated PV to write to DACA
double ctlactn = 1; // Place to store Control Action - 0 = Direct, 1 = Reverse
// Read a text file line by line.
string[] lines = File.ReadAllLines(textFile);
foreach (string line in lines)
{
try
{
client.WriteItemValue("", "HWHsc.OPCServer", line "PIDA.MODE", "AUTO"); // Put PID Loops in Automatic
}
catch { }
}
while (true)
{
if (File.Exists(textFile))
{
// Iterate through PIDs in text file
foreach (string line in lines)
{
try
{
// Store the PVEULO
pveulo = float.Parse(client.ReadItemValue("", "HWHsc.OPCServer", line ".DACA.PVEULO").ToString());
// Store the PVEUHI
pveuhi = float.Parse(client.ReadItemValue("", "HWHsc.OPCServer", line ".DACA.PVEUHI").ToString());
// Store the OP
OP = float.Parse(client.ReadItemValue("", "HWHsc.OPCServer", line ".PIDA.OP").ToString());
// Store the PV
try
{
PV = float.Parse(client.ReadItemValue("", "HWHsc.OPCServer", line ".PIDA.PV").ToString());
}
catch
{
PV = 0;
}
// Store the Control Action
try
{
ctlactn = float.Parse(client.ReadItemValue("", "HWHsc.OPCServer", line ".PIDA.CTLACTN").ToString());
}
catch (Exception e)
{
//ctlactn2 = client.ReadItemValue("", "HWHsc.OPCServer", line "PIDA.CTLACTN").ToString();
//ctlactn2 = e.ToString();
}
// If PV is 0, write the halfway point to it so the PID doesn't start in a wound up position
if (PV == 0)
{
// Write the halfway point of the pv range to the PV
client.WriteItemValue("", "HWHsc.OPCServer", line ".DACA.PV", (pveuhi - pveulo) / 2);
}
if (ctlactn == 1)
{
// Calculate the PV based off the OP * PV Range some noise
PV = (OP * 0.01 * (pveuhi - pveulo) pveulo) 0.008 * OP;
}
else
{
// Calculate the PV based off the OP * PV Range some noise
PV = ((106.9 - OP) * 0.01 * (pveuhi - pveulo) pveulo) - 0.008 * (106.9 - OP);
}
// Write the calculated PV to the DACA
client.WriteItemValue("", "HWHsc.OPCServer", line ".DACA.PV", PV);
}
catch(Exception e)
{
// Note the failures
Console.WriteLine("PID Failed at: " line "\n\t" e "\n");
Thread.Sleep(300000);
}
}
System.Threading.Thread.Sleep(1000);
}
}
}
public static void PView(EasyDAClient client)
{
/*
* Function: Simulates DACA block I/Os using historical ParcView data
*
* IN: client - OPC Client that will be used to read and write to the Experion server
* xLWB - The excel workbook containing the ParcView data
* OUT: None
*/
// Connect to the Excel spreadsheet
Excel.Application xLApp = new Excel.Application();
Excel.Workbook xLWB = xLApp.Workbooks.Open(@"C:\Temp\ParcView_Data_Copy_2.xlsx");
Excel.Worksheet rawData = xLWB.Sheets[1];
Excel.Worksheet locData = xLWB.Sheets[2];
Excel.Range rawDataRange = rawData.UsedRange;
Excel.Range locDataRange = locData.UsedRange;
// Determine the size of the spreadshees
int rawDataRow = rawDataRange.Rows.Count;
int rawDataCol = rawDataRange.Columns.Count;
int locDataRow = locDataRange.Rows.Count;
string tagName;
float[] tagValues = new float[locDataRow];
do
{
// Loop through each row of data
for (int i = 1; i <= rawDataRow; i )
{
// Console.WriteLine((rawDataRow - i) " Remaining");
// Get data for tag
for (int j = 1; j <= locDataRow; j )
{
tagName = locDataRange.Cells[j, 1].Value2.ToString();
try
{
if (rawDataRange.Cells[i, j] != null && rawDataRange.Cells[i, j].Value2 != null)
tagValues[j - 1] = float.Parse(rawDataRange.Cells[i, j].Value2.ToString());
client.WriteItemValue("", "HWHsc.OPCServer", tagName ".DACA.PV", tagValues[j - 1]);
}
catch (Exception e)
{
Console.WriteLine("PVIEW Failed at: " tagName "\n\t" e "\n");
Thread.Sleep(300000);
}
}
Thread.Sleep(2000);
}
} while (true);
}
static void Main(string[] args)
{
try
{
// Initialize OPC Client
EasyDAClient client = new EasyDAClient();
EasyDAClient client2 = new EasyDAClient();
// Initialize threads using Threads class
Thread PViewThread = new Thread(() => PView(client));
Thread PIDThread = new Thread(() => PID(client2));
//Thread DACAThread = new Thread(() => DACA(client));
//Thread DEVCTLAThread = new Thread(() => DEVCTLA(client));
// Start the threads
PViewThread.Start();
PIDThread.Start();
//DACAThread.Start();
//DEVCTLAThread.Start();
}
catch (Exception e)
{
Console.WriteLine("MAIN: \n\t" e);
Thread.Sleep(300000);
}
}
}
}
CodePudding user response:
A C# program can launch another instance of itself with Process.Start
. This code does not do that. It can be restarted by a task scheduler or if it is installed as a Windows service or by another application.
But I see that you have several Thread.Sleep
in there. Those are probably responsible for the observed behavior. So, the program is not closing and restarting automatically. It is simply sleeping from time to time.
Especially when an exception occurs Thread.Sleep(300000);
is called, making the program sleep for 5 minutes. If the exceptions occur randomly, this would explain it happening at random intervals.
E.g. float.Parse
throws an exception when the string is not a valid float. Consider using TryParse
instead. Example
string s = client.ReadItemValue("", "HWHsc.OPCServer", line ".DACA.PVEUHI").ToString();
if (float.TryParse(s, out float pvhi)) {
pveuhi = pvhi;
...
} else {
Console.WriteLine($"\"{s}\" is not a valid float in line {line}");
}