I developed an ADO adapter that integrates with my custom TCP server built using Node.js. It runs correctly on my personal machine. However, after copying the ADO adapter to a university machine, it no longer functions as expected.
On the university setup, the log displays the message:
“Initiating {0} {1} based connection”,
but the connection doesn’t seem to proceed beyond this point. It’s also can’t connect to PMU Connection tester. ‘Attempting to connect’.
Additionally, when I try to export measurements using the OpenPDC web interface at the university, it doesn’t work either—I see no data in the “Trend Data Chart.”
Interestingly, OpenHistorian does show that data is being received.
Could this be a configuration or environment issue? What might be causing the adapter to hang at that connection message, and why isn’t the data visible in OpenPDC but is present in OpenHistorian?
My code:
using System;
using System.IO;
using System.Text;
using GSF;
using GSF.PhasorProtocols;
using MySql.Data.MySqlClient;
using Newtonsoft.Json;
using WebSocketSharp;
using WebSocketSharp.Server;
namespace DeviceToData
{
public class DotEnv
{
public static void Load(string filePath = ".env")
{
if (!File.Exists(filePath))
{
Console.WriteLine(string.Format("Warning: {0} file not found. Using system environment variables.", filePath));
return;
}
foreach (var line in File.ReadAllLines(filePath))
{
var trimmedLine = line.Trim();
// Skip empty lines and comments
if (string.IsNullOrEmpty(trimmedLine) || trimmedLine.StartsWith("#"))
continue;
var equalIndex = trimmedLine.IndexOf('=');
if (equalIndex == -1)
continue;
var key = trimmedLine.Substring(0, equalIndex).Trim();
var value = trimmedLine.Substring(equalIndex + 1).Trim();
// Remove quotes if present
if (value.StartsWith("\"") && value.EndsWith("\""))
value = value.Substring(1, value.Length - 2);
else if (value.StartsWith("'") && value.EndsWith("'"))
value = value.Substring(1, value.Length - 2);
// Only set if not already set in system environment
if (Environment.GetEnvironmentVariable(key) == null)
{
Environment.SetEnvironmentVariable(key, value);
}
}
}
}
public class PhasorDataBehavior : WebSocketBehavior
{
public void SendPhasorData(object data)
{
if (Context.WebSocket.ReadyState == WebSocketState.Open)
{
string json = JsonConvert.SerializeObject(data);
Send(json);
}
}
}
class Program
{
static MultiProtocolFrameParser parser;
static long frameCount;
static WebSocketServer wsServer;
static void Main(string[] args)
{
// Load environment variables from .env file
DotEnv.Load();
string wsHost = Environment.GetEnvironmentVariable("WS_HOST");
string wsPort = Environment.GetEnvironmentVariable("WS_PORT");
wsServer = new WebSocketServer(string.Format("ws://{0}:{1}", wsHost, wsPort));
wsServer.AddWebSocketService<PhasorDataBehavior>("/phasors");
wsServer.Start();
Console.WriteLine("WebSocket server started on ws://{0}:{1}/phasors", wsHost, wsPort);
parser = new MultiProtocolFrameParser();
parser.ConnectionAttempt += Parser_ConnectionAttempt;
parser.ConnectionEstablished += Parser_ConnectionEstablished;
parser.ConnectionException += Parser_ConnectionException;
parser.ParsingException += Parser_ParsingException;
parser.ReceivedConfigurationFrame += Parser_ReceivedConfigurationFrame;
parser.ReceivedDataFrame += Parser_ReceivedDataFrame;
string pmuCommProtocol = Environment.GetEnvironmentVariable("PMU_COMM_PROTOCOL");
string pmuID = Environment.GetEnvironmentVariable("PMU_ID");
string pmuTransProtocol = Environment.GetEnvironmentVariable("PMU_TRANS_PROTOCOL");
string pmuServer = Environment.GetEnvironmentVariable("PMU_SERVER");
string pmuHost = Environment.GetEnvironmentVariable("PMU_PORT");
parser.ConnectionString = string.Format("phasorProtocol={0}; accessID={1}; transportprotocol={2}; server={3}; port={4}; islistener=false;", pmuCommProtocol, pmuID, pmuTransProtocol, pmuServer, pmuHost);
parser.AutoRepeatCapturedPlayback = true;
parser.AutoStartDataParsingSequence = true;
parser.Start();
Console.WriteLine("Press Enter to exit...");
Console.ReadLine();
wsServer.Stop();
}
private static string GetConnectionString()
{
// Get db credentials from env variables
string server = Environment.GetEnvironmentVariable("MYSQL_HOST");
string user = Environment.GetEnvironmentVariable("MYSQL_USER");
string password = Environment.GetEnvironmentVariable("MYSQL_PASSWORD");
string database = Environment.GetEnvironmentVariable("MYSQL_DATABASE");
// Validate required env variables
if (string.IsNullOrEmpty(password))
{
throw new InvalidOperationException("MYSQL_PASSWORD environment variable is required");
}
return string.Format("server={0};user={1};password={2};database={3}", server, user, password, database);
}
private static void Parser_ReceivedDataFrame(object sender, EventArgs<IDataFrame> e)
{
frameCount++;
if (e.Argument.Cells.Count > 0)
{
Console.WriteLine(string.Format("Received Data Frame #{0} with {1} devices", frameCount, e.Argument.Cells.Count));
string connStr = GetConnectionString();
using (MySqlConnection conn = new MySqlConnection(connStr))
{
try
{
conn.Open();
string sql = "INSERT INTO timeseriesmeasurement (timestamp, frequency, phasor_angle, phasor_magnitude, id_code, definition, phasor_type) " +
"VALUES (@timestamp, @frequency, @angle, @magnitude, @idcode, @definition, @type)";
foreach (IDataCell device in e.Argument.Cells)
{
Console.WriteLine(string.Format("Anton Phasors (Total Devices): {0}", e.Argument.Cells.Count));
foreach (var phasor in device.PhasorValues)
{
var dataToSend = new
{
Timestamp = e.Argument.Timestamp.ToString("o"),
Frequency = device.FrequencyValue.Frequency,
PhasorAngle = phasor.Angle,
PhasorMagnitude = phasor.Magnitude,
DeviceID = device.IDCode,
PhasorLabel = phasor.Definition.Label,
PhasorType = phasor.Type,
};
wsServer.WebSocketServices["/phasors"].Sessions.Broadcast(JsonConvert.SerializeObject(dataToSend));
Console.WriteLine("data sent to WebSocket clients");
using (MySqlCommand cmd = new MySqlCommand(sql, conn))
{
string formattedTimestamp = ((DateTime)e.Argument.Timestamp).ToString("yyyy-MM-dd HH:mm:ss.fff");
cmd.Parameters.AddWithValue("@timestamp", formattedTimestamp);
cmd.Parameters.AddWithValue("@frequency", Convert.ToDouble(device.FrequencyValue.Frequency));
cmd.Parameters.AddWithValue("@angle", Convert.ToDouble(phasor.Angle));
cmd.Parameters.AddWithValue("@magnitude", Convert.ToDouble(phasor.Magnitude));
cmd.Parameters.AddWithValue("@idcode", Convert.ToInt32(device.IDCode));
cmd.Parameters.AddWithValue("@definition", phasor.Definition);
cmd.Parameters.AddWithValue("@type", phasor.Type.ToString());
Console.WriteLine(string.Format("Phasor TYPE: {0}", phasor.Type));
cmd.ExecuteNonQuery();
}
}
}
}
catch (Exception ex)
{
Console.WriteLine(string.Format("MySQL Error: {0}", ex.Message));
}
}
}
}
private static void Parser_ConnectionAttempt(object sender, EventArgs e)
{
Console.WriteLine("Attempting connection...");
}
private static void Parser_ConnectionEstablished(object sender, EventArgs e)
{
Console.WriteLine(string.Format("Initiating {0} {1} based connection...", parser.PhasorProtocol.GetFormattedProtocolName(), parser.TransportProtocol.ToString().ToUpper()));
}
private static void Parser_ConnectionException(object sender, EventArgs<Exception, int> e)
{
Console.WriteLine(string.Format("Connection attempt {0} failed due to exception: {1}", e.Argument2, e.Argument1));
}
private static void Parser_ParsingException(object sender, EventArgs<Exception> e)
{
Console.WriteLine(string.Format("Parsing exception: {0}", e.Argument));
}
private static void Parser_ReceivedConfigurationFrame(object sender, EventArgs<IConfigurationFrame> e)
{
Console.WriteLine(string.Format("Received configuration frame with {0} device(s)", e.Argument.Cells.Count));
}
}
}