How to Access Your Measurement Data Using DNP3

by Paul Smart | Updated: 09/02/2016 | Comments: 1

Subscribe to the Blog

Set up your preferences for receiving email notifications when new blog articles are posted that match your areas of interest.

Suggest an Article

Leave this field empty

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.

Getting Started

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)

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
```

DNP3 Programming

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:

• Program our data logger to listen to DNP3 polls on the appropriate communications port.
• Assign a DNP3 group and variation to the variables.
• With each scan, update our DNP3 data points and scale the values if needed.

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)

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)

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
```

A Closer Look

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.

DNP() Instruction

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.)

DNPVariable() Instruction

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.

• The DNPObject parameter (like a field in a table) defines the group number of the DNP3 data.
• The DNPVariation parameter (like a data type for a field) defines the variation of the data type within the group number.
• The DNPClass parameter (like selecting a data table to put the field into) defines the class of the data. Static data is always 0, and event data is classified as 1, 2, or 3.

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.

• If we use a Boolean variable for this parameter, an event is created every time the DNPUpdate() instruction (more on this instruction below) is executed while the Boolean variable is “True.”
• If the event parameter is set to a constant 0, as it is in our example, an event is created every time the DNPUpdate() instruction is executed, and there is a change to the source variable.

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.

DNPUpdate() Instruction

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.

Conclusion

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.

Related Content

Paul Smart is the Vice President of Sales and Marketing at Campbell Scientific, Inc. His first experience with Campbell Scientific equipment came soon after graduating from college while working on a series of plant-growing experiments conducted on the International Space Station. Paul enjoys leveraging unique Campbell Scientific technology to solve challenging measurement problems. He has a bachelor’s degree in electrical engineering and an MBA. Paul also enjoys the outdoors, fly fishing, and spending time with his family.

View all articles by this author.