by Paul Smart | Updated: 09/02/2016 | Comments: 1
Turning your Campbell Scientific data logger into a DNP3 outstation is a great way to allow systems that use the DNP3 protocol to have access to your live measurement data, as well as historical data. When you use your data logger as a DNP3 outstation, the data logger is configured to implement the DNP3 communications protocol and listen for DNP3 polls from a DNP3 client, such as a SCADA (supervisory control and data acquisition) system. To better understand how this works, we’ll go through an example exercise to show the basic concepts of how to implement the DNP3 protocol on a data logger.
Recommended for You: If you are new to DNP3, you may find it helpful to review our “Getting to Know DNP3” blog article and reference “A DNP3 Protocol Primer” provided by the DNP Users Group. |
For our example, let’s consider a meteorological station, or “MET station,” installed on a 5 MW operational solar farm that needs to report its data to the local SCADA system using a CR1000 datalogger.
In our solar farm example, our CR1000 datalogger is programmed to measure plane-of-array irradiance, global horizontal irradiance, back-of-module temperature, wind speed, wind direction, ambient air temperature, relative humidity, and barometric pressure as shown in the program below:
'CR1000-based Solar Farm Meteorological Station PreserveVariables 'Declare Constants '110PV cable Resistance Const Cable_R = 1.6 'Sensor Calibration Coefficients Const GHI_1_CF = 7.05 Const POA_1_CF = 7.05 'Declare Constants to be used in Steinhart-Hart equation for the 110PV-L Back of Module Temperature Measurements Const A=1.129241*10^-3 Const B=2.341077*10^-4 Const C=8.775468*10^-8 'Declare variables for measurements Public PTemp, BattV Public GHI Public POA Public CS215_Data(2) Public Wind_Speed Public Wind_Dir Public BOM_Temp Public BP_mb 'Declare Aliases Alias CS215_Data(1) = Ambient_Temp Alias CS215_Data(2) = RH 'Declare Units Units GHI = W/m^2 Units POA = W/m^2 Units Wind_Speed = m/s Units Wind_Dir = degrees Units Ambient_Temp = DegC Units RH = % Units BOM_Temp = DegC Units BP_mb = mb 'Define Data Tables DataTable (min_1,1,-1) DataInterval (0,1,Min,10) Minimum (1,BattV,FP2,0,False) Sample (1,PTemp,FP2) Average (1,GHI,IEEE4,False) Average (1,POA,IEEE4,False) Average (1,BOM_Temp,IEEE4,False) Average (1,Ambient_Temp,IEEE4,False) Average (1,RH,IEEE4,False) Average (1,BP_mb,IEEE4, False) WindVector (1,Wind_Speed,Wind_Dir,IEEE4,False,0,0,0) FieldNames ("mean_wind_speed,mean_wind_direction,stddev_wind_dir") EndTable DataTable (min_10,1,-1) DataInterval (0,10,Min,10) Minimum (1,BattV,FP2,0,False) Sample (1,PTemp,FP2) Average (1,GHI,IEEE4,False) Average (1,POA,IEEE4,False) Average (1,BOM_Temp,IEEE4,False) Average (1,Ambient_Temp,IEEE4,False) Average (1,RH,IEEE4,False) Average (1,BP_mb,IEEE4, False) WindVector (1,Wind_Speed,Wind_Dir,IEEE4,False,0,0,0) FieldNames ("mean_wind_speed,mean_wind_direction,stddev_wind_dir") EndTable 'Main Program BeginProg Scan (1,Sec,0,0) PanelTemp (PTemp,250) 'Irradiance Measurements VoltDiff (GHI,1,mV25,1,True,0,_60Hz,1000/GHI_1_CF,0) VoltDiff (POA,1,mV25,2,True,0,_60Hz,1000/POA_1_CF,0) 'Wind Speed and Direction 034B measurements PulseCount(Wind_Speed,1,1,2,1,0.7989,0.28) If Wind_Speed=0.28 Then Wind_Speed=0 BrHalf(Wind_Dir,1,mV2500,6,VX2,1,2500,True,0,_60Hz,720.0,0) If Wind_Dir>=360 OR Wind_Dir < 0 Then Wind_Dir=0 '110PV BOM Temperature Measurements 'Measure BOM Sensor Resistance Dim BOM_mv, BOM_Res, LogTOhms BrHalf (BOM_mv,1,mV2500,5,Vx1,1,2500,True ,0,_60Hz,1.0,0) 'Convert mV to ohms, equation specific to 110PV BOM_Res = 4990*(1-BOM_mv)/BOM_mv 'subtract cable resistance BOM_Res = BOM_Res - Cable_R 'use Steinhart-Hart equation to convert resistance to temp 'also convert from kelvin to celsius LogTOhms = LOG(BOM_Res) BOM_Temp = (1/(A+B*LogTOhms+C*LogTOhms^3))-273.15 'CS215 Ambient Temperature and RH Measurement SDI12Recorder(CS215_Data(),1,"0","M!",1,0) 'CS100 Barometric Pressure Measurement If TimeIntoInterval(29,30,Min) Then PortSet(5,1) If TimeIntoInterval(0,30,Min) Then VoltSe(BP_mb,1,mV2500,15,1,0,_60Hz,0.2,600) PortSet(5,0) EndIf 'CR1000 Monitoring 'Datalogger Panel Temperature PanelTemp (PTemp,_60Hz) 'Battery Voltage Battery (BattV) 'Call Data Table CallTable min_1 CallTable min_10 NextScan EndProg
The CR1000 datalogger used in our MET station is equipped with an NL121 Ethernet Interface and is configured with a static IP address on our solar farm’s network. Therefore, we need to do the following to establish communication:
To do all of this, we need to use the DNP(), DNPVariable(), and DNPUpdate() instructions, as well as add the necessary variables to our program. This is accomplished using the code below:
'CR1000-based Solar Farm Meteorological Station PreserveVariables 'Declare Constants Const DNP3_Client = 10 Const DNP3_Outstation = 1 '110PV cable Resistance Const Cable_R = 1.6 'Sensor Calibration Coefficients Const GHI_1_CF = 7.05 Const POA_1_CF = 7.05 'Declare Constants to be used in Steinhart-Hart equation for the 110PV-L Back of Module Temperature Measurements Const A=1.129241*10^-3 Const B=2.341077*10^-4 Const C=8.775468*10^-8 'Declare variables for measurements Public PTemp, BattV Public GHI Public POA Public CS215_Data(2) Public Wind_Speed Public Wind_Dir Public BOM_Temp Public BP_mb 'Variables for DNP3 Data Public G30V1(10) As Long 'Declare Aliases Alias CS215_Data(1) = Ambient_Temp Alias CS215_Data(2) = RH 'Declare Units Units GHI = W/m^2 Units POA = W/m^2 Units Wind_Speed = m/s Units Wind_Dir = degrees Units Ambient_Temp = DegC Units RH = % Units BOM_Temp = DegC Units BP_mb = mb 'Define Data Tables DataTable (min_1,1,-1) DataInterval (0,1,Min,10) Minimum (1,BattV,FP2,0,False) Sample (1,PTemp,FP2) Average (1,GHI,IEEE4,False) Average (1,POA,IEEE4,False) Average (1,BOM_Temp,IEEE4,False) Average (1,Ambient_Temp,IEEE4,False) Average (1,RH,IEEE4,False) Average (1,BP_mb,IEEE4, False) WindVector (1,Wind_Speed,Wind_Dir,IEEE4,False,0,0,0) FieldNames ("mean_wind_speed,mean_wind_direction,stddev_wind_dir") EndTable DataTable (min_10,1,-1) DataInterval (0,10,Min,10) Minimum (1,BattV,FP2,0,False) Sample (1,PTemp,FP2) Average (1,GHI,IEEE4,False) Average (1,POA,IEEE4,False) Average (1,BOM_Temp,IEEE4,False) Average (1,Ambient_Temp,IEEE4,False) Average (1,RH,IEEE4,False) Average (1,BP_mb,IEEE4, False) WindVector (1,Wind_Speed,Wind_Dir,IEEE4,False,0,0,0) FieldNames ("mean_wind_speed,mean_wind_direction,stddev_wind_dir") EndTable 'Main Program BeginProg 'Set up DNP3 comms and input map DNP (20000,0,1000) DNPVariable (G30V1(1),10,30,1,0,0,0,0) DNPVariable (G30V1(1),10,32,1,1,0,0,100) Scan (1,Sec,0,0) PanelTemp (PTemp,250) 'Irradiance Measurements VoltDiff (GHI,1,mV25,1,True,0,_60Hz,1000/GHI_1_CF,0) VoltDiff (POA,1,mV25,2,True,0,_60Hz,1000/POA_1_CF,0) 'Wind Speed and Direction 034B measurements PulseCount(Wind_Speed,1,1,2,1,0.7989,0.28) If Wind_Speed=0.28 Then Wind_Speed=0 BrHalf(Wind_Dir,1,mV2500,6,VX2,1,2500,True,0,_60Hz,720.0,0) If Wind_Dir>=360 OR Wind_Dir < 0 Then Wind_Dir=0 '110PV BOM Temperature Measurements 'Measure BOM Sensor Resistance Dim BOM_mv, BOM_Res, LogTOhms BrHalf (BOM_mv,1,mV2500,5,Vx1,1,2500,True ,0,_60Hz,1.0,0) 'Convert mV to ohms, equation specific to 110PV BOM_Res = 4990*(1-BOM_mv)/BOM_mv 'subtract cable resistance BOM_Res = BOM_Res - Cable_R 'use Steinhart-Hart equation to convert resistance to temp 'also convert from kelvin to celsius LogTOhms = LOG(BOM_Res) BOM_Temp = (1/(A+B*LogTOhms+C*LogTOhms^3))-273.15 'CS215 Ambient Temperature and RH Measurement SDI12Recorder(CS215_Data(),1,"0","M!",1,0) 'CS100 Barometric Pressure Measurement If TimeIntoInterval(29,30,Min) Then PortSet(5,1) If TimeIntoInterval(0,30,Min) Then VoltSe(BP_mb,1,mV2500,15,1,0,_60Hz,0.2,600) PortSet(5,0) EndIf 'CR1000 Monitoring 'Datalogger Panel Temperature PanelTemp (PTemp,_60Hz) 'Battery Voltage Battery (BattV) 'Load DNP3 Analog Inputs G30V1(1) = GHI * 100 G30V1(2) = POA * 100 G30V1(3) = BOM_Temp * 100 G30V1(4) = Ambient_Temp * 100 G30V1(5) = RH * 100 G30V1(6) = Wind_Speed * 100 G30V1(7) = Wind_Dir * 100 G30V1(8) = BP_mb * 100 G30V1(9) = PTemp *100 G30V1(10) = BattV * 100 'Call Data Table CallTable min_1 CallTable min_10 'Update DNP3 data DNPUpdate (DNP3_Outstation,DNP3_Client) NextScan EndProg
Let’s take a closer look at the DNP(), DNPVariable(), and DNPUpdate() instructions we added to the program code. By the way, we add the DNP() and DNPVariable() instructions between the BeginProg and Scan statements because they only need to execute once at compile time rather than being executed during each scan.
The DNP() instruction contains three required parameters and two optional parameters. The three required parameters are shown below:
DNP (COMPort, BaudRate, Confirmation)
From our example:
DNP (20000,0,1000)
The COMPort parameter defines the communications port on the data logger that is set to listen for DNP3 polls. In our example we have set the COMPort parameter to 20000—the industry standard TCP/IP port for DNP3.
The BaudRate parameter specifies the baud rate of the communications port. This is set to zero and is not applicable when communications are over TCP/IP.
The Confirmation parameter is a parameter that is specific to the DNP3 protocol. It is used to determine which levels of confirmation are used between the data logger and client. In our example we have used the number 1000, which disables link verification. For most applications, a value of 1000 for the Confirmation parameter is appropriate. (More information on this parameter is available in the CRBasic Editor Help file.)
Two DNPVariable() instructions follow the DNP() instruction in our example. These instructions define the structure of the DNP3 data in the data logger.
DNPVariable (Source, Swath, DNPObject, DNPVariation, DNPClass, DNPFlag, DNPEvent, DNPNumEvents)
The first of the two DNPVariable() instructions defines the “static” or real-time data as it is measured in the data logger.
From our example:
DNPVariable (G30V1(1),10,30,1,0,0,0,0)
The Source parameter specifies the array in which the DNP3 data is stored. In our example we are using the variable named G30V1.
The Swath parameter specifies the number of elements in the array that are included in the DNP3 data structure. In our program example we have 10 data points.
The DNPObject, DNPVariation, and DNPClass parameters are used to define the type of DNP3 data that is stored in the data logger and made available to a DNP3 client.
We are using Group 30 Variation 1 in our example, which is a static 32-bit analog input with flag.
The DNPFlag parameter defines a DNP3 flag associated with each data point in our source array. We can use a variable array of type Long to control the value of each flag. Alternatively, if we set the DNPFlag parameter to a constant 0 in the DNPVariable() instruction, each flag’s status is set to “online.” In our program, we are setting our flags to always be displayed as “online.”
The DNPEvent parameter is the parameter that defines when a DNP3 event is created.
The DNPNumEvents parameter defines the number of events that are saved in the data logger memory. For example, if we enter 100 for this parameter, 100 events are stored for each data point in the source array.
The second DNPVariable() instruction defines the “event” data that contains a history.
From our example:
DNPVariable (G30V1(1),10,32,1,1,0,0,100)
We can customize the creation of an event in the CRBasic program. Our example program creates an event every time our scan is executed and there is a change to the source variable. Up to 100 historical events are stored until the oldest events are overwritten by more current events. Event data is also cleared after it is successfully sent to the DNP3 client. (Look for more information on implementing DNP3 events using CRBasic in future blog articles.)
Let’s go back to our example program for a moment. Later on in our Scan statement we find this code:
'Load DNP3 Analog Inputs
G30V1(1) = GHI * 100
G30V1(2) = POA * 100
G30V1(3) = BOM_Temp * 100
G30V1(4) = Ambient_Temp * 100
G30V1(5) = RH * 100
G30V1(6) = Wind_Speed * 100
G30V1(7) = Wind_Dir * 100
G30V1(8) = BP_mb * 100
G30V1(9) = PTemp *100
G30V1(10) = BattV * 100
Here we are loading our measurements into our source array. This array is defined as the Source parameter in the DNPVariable() instruction. We are using the DNP3 data type of Group 30, Variation 1. This data type is an analog input formatted as a 32-bit integer. To preserve some of our measurement resolution, the values are scaled by a factor of 100 as they are transferred to our DNP3 source array.
The last bit of DNP3 code in our instruction is the DNPUpdate() instruction.
From our example:
'Update DNP3 data
DNPUpdate (DNP3_Outstation,DNP3_Client)
This instruction is used in a similar fashion as the CRBasic CallTable() instruction, which is used to update our data tables. The DNPUpdate() instruction updates our DNP3 data structure in the memory of our data logger. Upon execution, it stores any newly created events and the real-time static data. This instruction also defines the DNP3 address of the data logger, as well as the DNP3 address of the DNP3 client that the data logger listens to. In our example, our data logger outstation address is 1, and the DNP3 client address (normally the SCADA system) is 10, as we declared in our Constants section of the program.
This article shows an example of how to use a CRBasic program to turn your Campbell Scientific data logger into a DNP3 outstation for accessing both your live measurement data and historical data. For more details about DNP3 programming, read the “DNP3 with Campbell Scientific Dataloggers” application note. We plan to publish future blog articles that more thoroughly discuss some of the different aspects of DNP3 and its implementation in Campbell Scientific data loggers. Until then, if you have any DNP3 comments or questions, feel free to post them below.
Comments
PPeters | 09/10/2018 at 09:53 PM
Just a quick question in regards to the DNP3 application.. can you set the logger to act as a Master? I have an application near a pump station that would be benifically to record some of the pump information and I know the current PLC would support DNP3.. just a thought..thanks Paul
Please log in or register to comment.