I created a PMU simulator but I can't configure with PMU Connection Tester

I’m trying to create a PMU simulator for my uni project. I connected to the PMU Connection tester and trying to send Config Frame 2 in PMU Connction tester, but it says: ‘Command “SendConfigurationFrame2” requested at 21.04.2025 17:16:52’ and nothing happens. Still ‘awaiting for configuration frame…’. This is my code:

const net = require('net');
const crc = require('crc');

const HOST = '127.0.0.1';
const PORT = 4712;
const DEVICE_ID = 2;
const FRAMES_PER_SECOND = 60;

// Helper func for big-endian values
function writeUInt16BE(val) {
  const buf = Buffer.alloc(2);
  buf.writeUInt16BE(val);
  return buf;
}

function writeUInt32BE(val) {
  const buf = Buffer.alloc(4);
  buf.writeUInt32BE(val);
  return buf;
}

// CRC-16-CCITT calculation
function calcCRC(buf) {
  const crcVal = crc.crc16ccitt(buf, 0xFFFF);
  const crcBuf = Buffer.alloc(2);
  crcBuf.writeUInt16BE(crcVal);
  return crcBuf;
}

// Build some config frame for tests
function buildConfigFrame() {
  const now = new Date();
  const soc = Math.floor(now.getTime() / 1000); // Current Unix timestamp
  const fracsec = Math.floor((now.getMilliseconds() * 1000000) / 1000); // Microseconds

  const payloadParts = [
    writeUInt16BE(DEVICE_ID),          // IDCODE
    writeUInt32BE(soc),                // SOC
    writeUInt32BE(fracsec),            // FRACSEC
    writeUInt16BE(0x0004),             // Frame type = CONFIG-2
    writeUInt32BE(1000000),            // TIME_BASE (1,000,000 µs)
    writeUInt16BE(1),                  // NUM_PMU
    Buffer.from('PMU1'.padEnd(16, ' ')), // Station name
    writeUInt16BE(DEVICE_ID),          // PMU ID
    writeUInt16BE(0x0001),             // FMT: floating-point, rectangular
    writeUInt16BE(1),                  // PHNMR: 1 phasor
    writeUInt16BE(0),                  // ANNMR: 0 analogs
    writeUInt16BE(0),                  // DGNMR: 0 digitals
    writeUInt16BE(FRAMES_PER_SECOND),  // DATA_RATE (60 Hz)
    Buffer.from('VA'.padEnd(16, ' ')), // Phasor name
    writeUInt32BE(0x00010000),         // Phasor conversion factor
    writeUInt16BE(60),                 // Nominal frequency
    writeUInt16BE(1),                  // CFGCNT
  ];

  const payload = Buffer.concat(payloadParts);
  const sync = Buffer.from([ 0xAA, 0x01 ]); // SYNC for CONFIG
  const frameSize = 2 + 2 + payload.length + 2; // SYNC + FRAMESIZE + payload + CRC

  const frameWithoutCRC = Buffer.concat([ sync, writeUInt16BE(frameSize), payload ]);
  const crcBuf = calcCRC(frameWithoutCRC);
  return Buffer.concat([ frameWithoutCRC, crcBuf ]);
}

// Server
const server = net.createServer(socket => {
  console.log('✅ Tester connected');

  // Send initial CONFIG-2 frame
  const configFrame = buildConfigFrame();
  socket.write(configFrame);
  console.log(`📡 Sent initial CONFIG-2 frame: ${configFrame.toString('hex')}`);

  socket.on('data', data => {
    console.log(`📥 Received from tester: ${data.toString('hex')}`);

    if ( data.length >= 16 ) {
      const commandCode = data.readUInt16BE(14);
      if ( commandCode === 0x0001 ) {
        console.log('Received START command - configuration accepted!');
      } else if ( commandCode === 0x0005 ) {
        console.log('Received request for CONFIG-2 frame');
        const newConfigFrame = buildConfigFrame();
        socket.write(newConfigFrame);
        console.log(`📡 Sent CONFIG-2 frame in response: ${newConfigFrame.toString('hex')}`);
      } else {
        console.log(`Unexpected command code: 0x${commandCode.toString(16)}`);
      }
    }
  });

  socket.on('end', () => console.log('🔌 Connection closed'));
});

server.listen(PORT, () => console.log(`📡 PMU Simulator running on port ${PORT}`));

Hello Tagan,

These are the issues I identified.

  1. Frame type for config frame is 3.
  2. Frame type doesn’t belong between FRACSEC and TIME_BASE. It is included in the SYNC bytes.
  3. DATA_RATE belongs at the end of the frame, just before the checksum.

After fixing these three issues, I was able to get PMU Connection Tester to recognize your config frame.

Thanks,
Stephen

Hi Stephen,

It worked, thanks! I have also one more question: I wanna save the data from OpenPDC in MySQL database, for it I created a table:

CREATE TABLE TimeSeriesMeasurement
(
    SignalID NCHAR(36) NOT NULL,
    Timestamp VARCHAR(24) NOT  NULL,
    Value DOUBLE NOT NULL
)

In the openPDC Manager, under Outputs > Manage Custom Outputs

Name: ADO
Type: ADO: Archives measurements to an ADO data source
Connection String: BulkInsertLimit=500; DataProviderString={AssemblyName={Npgsql, Version=4.0.11.0, Culture=neutral, PublicKeyToken=5d8b90d52f46fda7}; ConnectionType=Npgsql.NpgsqlConnection; AdapterType=Npgsql.NpgsqlDataAdapter}; DbConnectionString={Server=hostname; Port=localhost; Database=test; User Id=anton; Password=somePassword}; TableName=TimeSeriesMeasurement; IDFieldName=SignalID; TimestampFieldName=Timestamp; ValueFieldName=Value; SourceIDs=PPA
Enabled: Checked

I did the following steps, but it’s not working - database is empty. Maybe u can help with that also? I also see the graph only with that value or line(screenshot).

Thanks in advance!

Your DataProviderString parameter is incorrect for connecting to MySQL. Npgsql is the name of the ADO.NET provider for PostgreSQL. You need to download MySQL Connector/NET and then locate the MySql.Data.dll assembly. You can get the AssemblyName using the System.Reflection.AssemblyName.GetAssemblyName() method. ConnectionType and AdapterType are just the fully-qualified names of types in that assembly. My DataProviderString ends up looking like this…

DataProviderString={AssemblyName={MySql.Data, Version=6.7.8.0, Culture=neutral, PublicKeyToken=c5687fc88969c44d}; ConnectionType=MySql.Data.MySqlClient.MySqlConnection; AdapterType=MySql.Data.MySqlClient.MySqlDataAdapter}

Probably I did something wrong?

BulkInsertLimit=500; DataProviderString={AssemblyName={MySql.Data, Version=9.3.0.0, Culture=neutral, PublicKeyToken=testtoken}; ConnectionType=MySql.Data.MySqlClient.MySqlConnection; AdapterType=MySql.Data.MySqlClient.MySqlDataAdapter}; DbConnectionString={Server=localhost; Port=3306; Database=predicted_outputs; User Id=anton; Password=testpassw}; TableName=TimeSeriesMeasurement; IDFieldName=SignalID; TimestampFieldName=Timestamp; ValueFieldName=Value; SourceIDs=PPA
Enabled: Checked

I clicked save but nothing happened. MySQL db is still empty

Did you switch your adapter type? Earlier, you said you were using the ADO adapter. You won’t be able to get the MySQL adapter to work without code updates. It was written many years ago as a fairly basic example of how to create an input adapter. You can tell how far behind it is by looking at the hardcoded version numbers it supports.

Apologies if this is a basic question — I’m a student and just started working with OpenPDC about a week ago.
Is it possible to connect directly to MySQL without using the ADO adapter?
In my project, I’ve written a WebSocket that simulates a PMU, and now I’d like to receive data from OpenPDC or openHistorian. Ideally, I want to store this data directly in a MySQL database without ADO adapter if it’s possible.
After that, I plan to access and observe the data through my WebSocket, and later use it in the frontend via API calls.

Of course, you’re free to integrate your PMU simulator with MySQL however you like. Here are some potentially useful documents. Keep in mind, they may also be out of date so some of the details may be inaccurate.

Device to Data in 5 Easy Steps describes how to receive PMU data directly into your own custom application, written from scratch using GSF libraries.
https://github.com/GridProtectionAlliance/openPDC/blob/master/Source/Documentation/wiki/Developers_Device_to_Data_in_5_Easy_Steps.md

How to Create a Custom Adapter explains the basics for creating your own adapters that can be used to integrate openPDC with other systems like MySQL.
https://github.com/GridProtectionAlliance/openPDC/blob/master/Source/Documentation/wiki/Developers_Custom_Adapters.md

Two Custom Adapter Examples provides a couple example projects to help guide you to a point where you have a working adapter that can be integrated into openPDC.
https://github.com/GridProtectionAlliance/openPDC/blob/master/Source/Documentation/wiki/Developers_Two_Custom_Adapter_Examples.md

Hi Stephen,

I followed the first example you sent me and was able to receive the configuration frame in the console. However, I haven’t received any data frames. Do you have any idea what might be causing this?

If it helps, I can share my code and some screenshots for context.

Hi Tagan,

Your PMU simulator doesn’t generate any data frames. Did you add the code for that? Does the PMU Connection Tester display any information about your data frames?

Thanks,
Stephen

I updadet my code to save data frame and now it’s working with pmu connection tester and openPDC. It’s not working with my custom adapter.
Code to send dataFrame:

function buildDataFrame(startTime) {
  const now = new Date();
  const soc = Math.floor(now.getTime() / 1000);
  const fracsec = Math.floor(((now.getTime() % 1000) / 1000) * TIME_BASE); //

  const deltaT = (Date.now() - startTime) / 1000; // time delta t

  const Vrms = 230; // Voltage 230V (root-mean-square)
  const Vpeak = Vrms * Math.sqrt(2); // peak Voltage
  const Vscale = 1 / Math.sqrt(2); // from peak to rms based on ieee

  const baseFreq = NOMINAL_FREQ; // 60 hz
  const freq = baseFreq + (Math.random() - 0.5) * 0.02; // added random noise
  const omega = 2 * Math.PI * freq;

  const phaseAngles = [ 0, -2 * Math.PI / 3, 2 * Math.PI / 3 ]; // 0°, -120°, +120°

  const phasors = phaseAngles.map(angle => {
    const real = Vpeak * Vscale * Math.cos(omega * deltaT + angle); // Real part
    const imag = Vpeak * Vscale * Math.sin(omega * deltaT + angle); // Imagine part
    return [ writeFloatBE(real), writeFloatBE(imag) ];
  }).flat();

  const SYNC = 0xAA01; // SYNC word: 0xAA01 = data frame, not last frame
  const STAT = 0b0000000000000000; // PMU time is synchronized

  const payload = Buffer.concat([
    writeUInt16BE(DEVICE_ID),       // PMU ID
    writeUInt32BE(soc),             // SOC
    writeUInt32BE(fracsec),         // FRACSEC
    writeUInt16BE(STAT),            // STAT
    ...phasors,                     // 3 phasors → 6 floats → 24 bytes
    writeFloatBE(freq),             // Frequency (float)
    writeFloatBE(0.0),              // ROCOF = 0
  ]);

  const syncBuf = writeUInt16BE(SYNC);
  const frameSize = 2 + 2 + payload.length + 2; // SYNC + FRAMESIZE + payload + CRC
  const frameSizeBuf = writeUInt16BE(frameSize);

  const frameWithoutCRC = Buffer.concat([ syncBuf, frameSizeBuf, payload ]);
  const crc = calcCRC(frameWithoutCRC);
  return Buffer.concat([ frameWithoutCRC, crc ]);
}

Adapter:

using System;
using GSF;
using GSF.PhasorProtocols;
namespace DeviceToData
{
    class Program
    {
        static MultiProtocolFrameParser parser;
        static long frameCount;
        static void Main(string[] args)
        {
            // Create a new protocol parser
            parser = new MultiProtocolFrameParser();
            // Attach to desired events
            parser.ConnectionAttempt += parser_ConnectionAttempt;
            parser.ConnectionEstablished += parser_ConnectionEstablished;
            parser.ConnectionException += parser_ConnectionException;
            parser.ParsingException += parser_ParsingException;
            parser.ReceivedConfigurationFrame += parser_ReceivedConfigurationFrame;
            parser.ReceivedDataFrame += parser_ReceivedDataFrame;
            // Define the connection string
            parser.ConnectionString = 
                   "phasorProtocol=IeeeC37_118V2; accessID=2; transportprotocol=tcp; server=127.0.0.1; port=4713; islistener=false;";



                
            
            // When connecting to a file based resource you may want to loop the data
            parser.AutoRepeatCapturedPlayback = true;
            // Start frame parser
            parser.AutoStartDataParsingSequence = true;
            parser.Start();
            // Keep the console open while receiving live data; application will be terminated when the user presses the Enter key:
            Console.ReadLine();
        }
        
        static void parser_ReceivedDataFrame(object sender, EventArgs<IDataFrame> e)
        {
                                Console.WriteLine("Data Frame");

            // Increase the frame count each time a frame is received
            frameCount++;
            // Print information each time we receive 60 frames (every 2 seconds for 30 frames per second)
            // Also check to assure the DataFrame has at least one Cell
            if ((frameCount % 60 == 0) && (e.Argument.Cells.Count > 0))
            {
                IDataCell device = e.Argument.Cells[0];
                Console.WriteLine("Received {0} data frames so far...", frameCount);
                Console.WriteLine("    Last frequency: {0}Hz", device.FrequencyValue.Frequency);
                for (int x = 0; x < device.PhasorValues.Count; x++)
                {
                    Console.WriteLine("PMU {0} Phasor {1} Angle = {2}", device.IDCode, x, device.PhasorValues[x].Angle);
                    Console.WriteLine("PMU {0} Phasor {1} Magnitude = {2}", device.IDCode, x, device.PhasorValues[x].Magnitude);
                }
                Console.WriteLine("    Last Timestamp: {0}", ((DateTime)e.Argument.Timestamp).ToString("yyyy-MM-dd HH:mm:ss.fff"));
            }
        }
        
        static void parser_ReceivedConfigurationFrame(object sender, EventArgs<IConfigurationFrame> e)
        {
            // Notify the user when a configuration frame is received
                                            Console.WriteLine("Config Frame");

 Console.WriteLine(sender);
            Console.WriteLine("Received configuration frame with {0} device(s)", e.Argument.Cells.Count);
        }
       
        static void parser_ParsingException(object sender, EventArgs<Exception> e)
        {
            // Output the exception to the user
            Console.WriteLine("Parsing exception: {0}", e.Argument);
        }
        
        static void parser_ConnectionException(object sender, EventArgs<Exception, int> e)
        {
            // Display which connection attempt failed and the exception that occurred
            Console.WriteLine("Connection attempt {0} failed due to exception: {1}",
                e.Argument2, e.Argument1);
        }
        
        static void parser_ConnectionEstablished(object sender, EventArgs e)
        {
            // Notify the user when the connection is established
            Console.WriteLine("Initiating {0} {1} based connection...",
                parser.PhasorProtocol.GetFormattedProtocolName(),
                parser.TransportProtocol.ToString().ToUpper());
        }
        
        static void parser_ConnectionAttempt(object sender, EventArgs e)
        {
            // Let the user know we are attempting to connect
            Console.WriteLine("Attempting connection...");
        }
    }
}
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFrameworkVersion>v4.6</TargetFrameworkVersion>
    <PlatformTarget>AnyCPU</PlatformTarget>
    <LangVersion>7.3</LangVersion>
    <RootNamespace>DeviceToData</RootNamespace>
    <AssemblyName>DeviceToData</AssemblyName>
  </PropertyGroup>

  <ItemGroup>
    <Reference Include="System" />
    <Reference Include="GSF.Communication">
      <HintPath>libs\GSF.Communication.dll</HintPath>
    </Reference>
    <Reference Include="GSF.Core">
      <HintPath>libs\GSF.Core.dll</HintPath>
    </Reference>
    <Reference Include="GSF.PhasorProtocols">
      <HintPath>libs\GSF.PhasorProtocols.dll</HintPath>
    </Reference>
    <Reference Include="GSF.TimeSeries">
      <HintPath>libs\GSF.TimeSeries.dll</HintPath>
    </Reference>
  </ItemGroup>

  <ItemGroup>
    <Compile Include="Program.cs" />
  </ItemGroup>

  <Target Name="Build">
    <Csc Sources="@(Compile)" References="@(Reference->'%(HintPath)')" OutputAssembly="DeviceToData.exe" />
  </Target>
</Project>

I receive in logs only:

Attempting connection…
Initiating IEEE C37.118.2-2011 TCP based connection…
Config Frame
GSF.PhasorProtocols.MultiProtocolFrameParser
Received configuration frame with 1 device(s)

It sends the data frame to the openPDC and PMU Connection Tester, but not to adapter.

The code you sent works for me. Here some of the issues I ran into getting things to work smoothly. Keep in mind, I’m still using the code you provided initially in your first post so if you’ve already changed things, some of this advice may be irrelevant.

  • Your PMU simulator only supports config frame v2, so you might be better off using phasorProtocol=IeeeC37_118V1 in your MultiProtocolFrameParser connection string. Otherwise, your application will ask for config frame v3 instead.
  • I removed the code that sends an unsolicited config frame immediately after the connection is established, since you are already handling commandCode === 0x0005. This isn’t really necessary, but it does avoid sending the config frame twice.
  • The MultiProtocolFrameParser should automatically send command code 2 (EnableRealTimeData) to indicate you can start sending data frames. I invoked your buildDataFrame() function in response to that command code.

Here is the output I received from your DeviceToData application.

Attempting connection...
Initiating IEEE C37.118-2005 TCP based connection...
Config Frame
GSF.PhasorProtocols.MultiProtocolFrameParser
Received configuration frame with 1 device(s)
Data Frame

Thanks,
Stephen