Please ensure Javascript is enabled for purposes of website accessibility
Powered by Zoomin Software. For more details please contactZoomin

AVEVA Pipeline Operations for Liquids

Example implementation

  • Last UpdatedApr 24, 2025
  • 7 minute read

The following code shows an example plugin implementation that manipulates a pipeline-scope value in the initialization function and then writes to an extended profile field for each milepost defined in the profile and if_profile arrays.

extern "C" OAS_EXPORT void initializeHydraulicProfile(pipelineslot plslot)

{

/*

* If anything needs to be initialized before the hydraulic profile is

calculated

* it can be done during this function.

*

* In this example there is a custom field on the pipeline record that increments

* each time initialization is called.

*

* The function is passed a slot number which refers to a write locked pipeline record, so

* there is no need to further lock any tables or records. Instead the table and record

* objects are attached to the underlying database items.

*/

RealTimeTable rtTable;

RealTimeRecord rtRecord;

rtTable.Bind("pipeline");

rtRecord.BindTable(rtTable);

if (!rtTable.AttachInsteadOfLock(REQ_SAFE_READ))

{

cpu_debug(CPU_DEBUG_ERROR, "initializeHydraulicProfile: Unable to attach

to pipeline table");

return;

}

if (!rtRecord.BindRecordNumber(plslot))

{

cpu_debug(CPU_DEBUG_ERROR, "initializeHydraulicProfile: Unable to bind to pipeline record %d", (int)plslot);

rtTable.DetachInsteadOfUnLock();

return;

}

if (!rtRecord.AttachInsteadOfLock(REQ_SAFE_WRITE))

{

cpu_debug(CPU_DEBUG_ERROR, "initializeHydraulicProfile: Unable to attach to pipeline record %d", (int)plslot);

rtTable.DetachInsteadOfUnLock();

return;

}

TFlexString name = rtRecord.ReadString("name");

char* customFieldName = "ext_customValue";

double customValue = rtRecord.ReadDouble(customFieldName);

cpu_debug(CPU_DEBUG_ALWAYS, "initializeHydraulicProfile: pipeline %s has %s %f", name.Get(), customFieldName, customValue);

// if the custom value reaches 100, reset

if (customValue == 100)

{

customValue = 0;

}

else if (customValue > 100)

{

// An error is about to be thrown so detach from the record and table first

rtRecord.DetachInsteadOfUnLock();

rtTable.DetachInsteadOfUnLock();

/*

* The code that calls this plug in function will handle any exceptions thrown.

*

* To provoke this exception the ext_customValue field could be set to

* over 100 via an external application e.g. using iSQL.

*

* The exception should be caught and an error logged.

*/

TFlexString errorMessage;

errorMessage << "initializeHydraulicProfile: pipeline: " << name << " - custom value can't be above 100. Current value " << customValue;

throw std::exception(errorMessage.Get());

}

else

{

// increment the value

customValue++;

}

rtRecord.WriteDouble(customFieldName, customValue);

cpu_debug(CPU_DEBUG_ALWAYS, "initializeHydraulicProfile: wrote new value of %s for pipeline %s: %f", customFieldName, name.Get(), customValue);

rtRecord.DetachInsteadOfUnLock();

rtTable.DetachInsteadOfUnLock();

}

extern "C" OAS_EXPORT void extendHydraulicProfile(pipelineslot plslot, pipe_segmentslt segmentslot)

{

/*

* This function receives a slot number for a write locked pipeline

* record and a slot number for a write locked pipeline segment

*

* In a particular cycle this function will be called once for

* each segment in a particular pipeline.

*

* This example works out the array element order of the mileposts

* and then writes a value to each custom profile point in milepost

* order. The value written out starts with the value of 'ext_customValue'

* that was set in the initialize function and then increments the value

* for each milepost in this segment.

*

* The example also demonstrates the two different arrays of profile points

* that exist. There are static profile points in the profile array but

* also dynamically created batch interface profile points in the if_profile

* array. The profile point extensions therefore also have to exist in two

* arrays: profile_extensions and if_profile_extensions. See the developer

* guide for more information on how to extend the schema.

*/

RealTimeTable rtTable;

RealTimeRecord rtRecord;

rtTable.Bind("pipeline");

rtRecord.BindTable(rtTable);

if (!rtTable.AttachInsteadOfLock(REQ_SAFE_READ))

{

cpu_debug(CPU_DEBUG_ERROR, "extendHydraulicProfile: Unable to attach pipeline table");

return;

}

if (!rtRecord.BindRecordNumber(plslot))

{

cpu_debug(CPU_DEBUG_ERROR, "extendHydraulicProfile: Unable to bind to pipeline record %d", (int)plslot);

rtTable.DetachInsteadOfUnLock();

return;

}

if (!rtRecord.AttachInsteadOfLock(REQ_SAFE_WRITE))

{

cpu_debug(CPU_DEBUG_ERROR, "extendHydraulicProfile: Unable to attach to pipeline record %d", (int)plslot);

rtTable.DetachInsteadOfUnLock();

return;

}

TFlexString pipeName = rtRecord.ReadString("name");

char* customFieldName = "ext_customValue";

double customValue = rtRecord.ReadDouble(customFieldName);

cpu_debug(CPU_DEBUG_ALWAYS, "extendHydraulicProfile: pipeline %s has %s %f", pipeName.Get(), customFieldName, customValue);

rtRecord.DetachInsteadOfUnLock();

rtTable.DetachInsteadOfUnLock();

if (customValue > 100)

{

/*

* The code that calls this plug-in function will handle any exceptions thrown.

*

* To provoke this exception the ext_customValue field could be set to over 100

* via an external application e.g. using iSQL.

*

* The exception should be caught and an error logged.

*/

TFlexString errorMessage;

errorMessage << "extendHydraulicProfile: pipeline: " << pipeName << " - custom value can't be above 100. Current value " << customValue;

throw std::exception(errorMessage.Get());

}

/*

* Attach to the pipe segment record so we can read and write from it.

*/

RealTimeTable pipeSegmentTable;

RealTimeRecord pipeSegmentRecord;

pipeSegmentTable.Bind("pipe_segment");

pipeSegmentRecord.BindTable(pipeSegmentTable);

if (!pipeSegmentTable.AttachInsteadOfLock(REQ_SAFE_READ))

{

cpu_debug(CPU_DEBUG_ERROR, "extendHydraulicProfile: Unable to attach to the pipe_segment table");

return;

}

if (!pipeSegmentRecord.BindRecordNumber(segmentslot))

{

cpu_debug(CPU_DEBUG_ERROR, "extendHydraulicProfile: Unable to bind pipe_segment record %d", (int)segmentslot);

pipeSegmentTable.DetachInsteadOfUnLock();

return;

}

if (!pipeSegmentRecord.AttachInsteadOfLock(REQ_SAFE_WRITE))

{

cpu_debug(CPU_DEBUG_ERROR, "extendHydraulicProfile: Unable to attach pipe_segment record %d", (int)segmentslot);

pipeSegmentTable.DetachInsteadOfUnLock();

return;

}

// Get the segment name for logging purposes

TFlexString segmentName = pipeSegmentRecord.ReadString("name");

/*

* Existing profile points will be in a 40 element array called profile and

* in another 40 element array called if_profile and can be accessed using

* field names like the following:

*

* profile[0].temp

* profile[12].pres

* if_profile[2].ppnt.temp

* if_profile[7].ppnt.pres

*

* Extended profile points are expected to be in two different 40 element

* arrays called profile_extensions and if_profile_extensions and can be

* accessed using field names like the following:

*

* profile_extensions[0].ext_fld

* profile_extensions[12].my_custom_field

* if_profile_extensions[1].ext_fld

* if_profile_extensions[3].my_custom_field

*

* It is expected that the values of the custom fields at a given element

* in profile_extensions will be the values for the milepost at the same

* element in profile. Similarly it is expected that the values of custom

* fields at a given element in if_profile_extensions will be the values

* for the milepost at the same element in if_profile

*

* So if milepost 1 is element 0 in the profile array, then the extended

* field relating to milepost 1 should also be at element 0 in

* profile_extensions.

*

* The example will write a value to an extended field which is one greater

* than the previous milepost.

*

* In order to do this it will need to find out the element for each milepost

* and then write the elements in the correct order.

*

* As batch interface profile points can be at mileposts interspersed between

* static profile points and the get_dns_prof_pnt function returns profile points

* of either type in milepost order, the example code has to detect which type of

* profile point is the current one and write to the correct extension array.

*/

const char* profile_prefix = "profile[";

const char* if_profile_prefix = "if_profile[";

const char* array_postfix = "].";

const char* if_array_postfix = "].ppnt.";

const char* extended_profile_prefix = "profile_extensions[";

const char* extended_if_profile_prefix = "if_profile_extensions[";

// store in a map the array element of each milepost so it can be looked up by milepost

std::map<double, size_t> mapOfMilepostToArrayElement;

for (int i = 0; i < 40; i++)

{

TFlexString milepostFieldName = profile_prefix;

milepostFieldName << i << array_postfix << "milepost";

double milepost = -1;

if (!pipeSegmentRecord.ReadDouble(milepostFieldName, milepost))

{

cpu_debug(CPU_DEBUG_ERROR, "extendHydraulicProfile: Unable to read field %s on pipe_segment %s in pipe %s", milepostFieldName.Get(),segmentName.Get(), pipeName.Get());

}

if (milepost < 0)

{

// no profile point defined at this element

continue;

}

cpu_debug(CPU_DEBUG_ALWAYS, "extendHydraulicProfile: For segment %s mapping milepost %f to index %d", segmentName.Get(), milepost, i);

mapOfMilepostToArrayElement.insert(std::make_pair(milepost, i));

}

// work out the mapping between active mileposts in the if_profile array and array elements

std::map<double, size_t> mapOfBatchInterfaceMilepostToArrayElement;

for (int i = 0; i < 40; i++)

{

// Read the 'used' field to see whether the if_profile array element is active

TFlexString usedFieldName = if_profile_prefix;

usedFieldName << i << if_array_postfix << "used";

oas_boolean_t used = OAS_FALSE;

if (!pipeSegmentRecord.ReadBool(usedFieldName, used))

{

cpu_debug(CPU_DEBUG_ERROR, "extendHydraulicProfile: Unable to read field %s on pipe_segment %s in pipe %s", usedFieldName.Get(), segmentName.Get(), pipeName.Get());

}

if (!used)

{

// this entry in the array is no longer relevant and it can be ignored

continue;

}

TFlexString ifMilepostFieldName = if_profile_prefix;

ifMilepostFieldName << i << if_array_postfix << "milepost";

double if_milepost = -1;

if (!pipeSegmentRecord.ReadDouble(ifMilepostFieldName, if_milepost))

{

cpu_debug(CPU_DEBUG_ERROR, "extendHydraulicProfile: Unable to read field %s on pipe_segment %s in pipe %s", ifMilepostFieldName.Get(), segmentName.Get(), pipeName.Get());

}

cpu_debug(CPU_DEBUG_ALWAYS, "extendHydraulicProfile: For segment %s mapping batch interface milepost %f to index %d", segmentName.Get(), if_milepost, i);

mapOfBatchInterfaceMilepostToArrayElement.insert(std::make_pair(if_milepost, i));

}

/*

* This is the value that will be written out for each milepost.

* Initialize to the custom value that was read from the pipeline record.

*/

double ext_fld_value = customValue;

/*

* Iterate through the profile points using the iterator that accounts for

* flow direction i.e. get_dns_prof_pnt

*

* To do this, a different form of the segment record is required which

* can be obtained using bt_get_rec

*/

pipe_segment* pPipeSegment = static_cast<pipe_segment*>(bt_get_rec(DATABASE_PIPE_SEGMENT, segmentslot));

profile_point* pProfPnt = get_upmost_prof_pnt(pPipeSegment);

while (pProfPnt != NULL)

{

// use the map from above to get the array index to write to the extended field

size_t index = MAXULONGLONG; // Initialize to max value to indicate not found

TFlexString extFieldName;

auto iter = mapOfMilepostToArrayElement.find(pProfPnt->milepost);

if (iter == mapOfMilepostToArrayElement.end())

{

auto if_iter = mapOfBatchInterfaceMilepostToArrayElement.find(pProfPnt->milepost);

if (if_iter != mapOfBatchInterfaceMilepostToArrayElement.end())

{

index = if_iter->second;

cpu_debug(CPU_DEBUG_ALWAYS, "extendHydraulicProfile: For segment %s getting index %d for batch interface milepost %f", segmentName.Get(), index, pProfPnt->milepost);

// now construct the field name to write to

extFieldName = extended_if_profile_prefix;

}

else

{

cpu_debug(CPU_DEBUG_ERROR, "extendHydraulicProfile: For segment %s milepost %f is not a profile point or batch interface profile point", segmentName.Get(), pProfPnt->milepost);

}

}

else

{

index = iter->second;

cpu_debug(CPU_DEBUG_ALWAYS, "extendHydraulicProfile: For segment %s getting index %d for milepost %f", segmentName.Get(), index, pProfPnt->milepost);

// now construct the field name to write to

extFieldName = extended_profile_prefix;

}

if (index < 40)

{

extFieldName << index << array_postfix << "ext_fld";

// write the value

if (pipeSegmentRecord.WriteDouble(extFieldName, ext_fld_value))

{

cpu_debug(CPU_DEBUG_ALWAYS, "extendHydraulicProfile: Successfully wrote %f as new value for %s on pipe segment %s", ext_fld_value, extFieldName.Get(), segmentName.Get());

}

else

{

cpu_debug(CPU_DEBUG_ERROR, "extendHydraulicProfile: Failed to write %f as new value for %s on pipe segment %s", ext_fld_value, extFieldName.Get(), segmentName.Get());

}

// increment the value so the next milepost has a different value

ext_fld_value++;

}

pProfPnt = get_dns_prof_pnt(pPipeSegment, pProfPnt->milepost, OAS_TRUE);

}

pipeSegmentRecord.DetachInsteadOfUnLock();

pipeSegmentTable.DetachInsteadOfUnLock();

}