Files
pvAccess/pvtoolsSrc/eget.cpp
Michael Davidsaver 7a523dd948 drop unused NOMINMAX
2018-05-18 15:20:26 -07:00

1927 lines
58 KiB
C++

#include <iostream>
#include <pv/pvAccess.h>
#include <pv/caProvider.h>
#include <stdio.h>
#include <epicsStdlib.h>
#include <epicsGetopt.h>
#include <epicsThread.h>
#include <pv/logger.h>
#include <pv/lock.h>
#include <vector>
#include <string>
#include <istream>
#include <ostream>
#include <fstream>
#include <sstream>
#include <iomanip>
#include <map>
#include <pv/event.h>
#include <epicsExit.h>
#include "pvutils.cpp"
using namespace std;
namespace TR1 = std::tr1;
using namespace epics::pvData;
using namespace epics::pvAccess;
enum PrintMode { ValueOnlyMode, StructureMode, TerseMode };
PrintMode mode = ValueOnlyMode;
bool columnMajor = false;
bool transpose = false;
bool dumpStructure = false;
#if defined(_WIN32) && !defined(_MINGW)
FILE *popen(const char *command, const char *mode) {
return _popen(command, mode);
}
int pclose(FILE *stream) {
return _pclose(stream);
}
#endif
void formatNTAny(std::ostream& o, PVStructurePtr const & pvStruct)
{
PVFieldPtr value = pvStruct->getSubField("value");
if (value.get() == 0)
{
std::cerr << "no 'value' field in NTAny" << std::endl;
return;
}
o << *value;
}
void formatNTScalar(std::ostream& o, PVStructurePtr const & pvStruct)
{
PVScalarPtr value = TR1::dynamic_pointer_cast<PVScalar>(pvStruct->getSubField("value"));
if (value.get() == 0)
{
std::cerr << "no scalar_t 'value' field in NTScalar" << std::endl;
return;
}
o << *value;
}
std::ostream& formatVector(std::ostream& o,
string label,
PVScalarArrayPtr const & pvScalarArray,
bool transpose)
{
size_t len = pvScalarArray->getLength();
if (!transpose)
{
if (!label.empty())
o << label << std::endl;
for (size_t i = 0; i < len; i++)
pvScalarArray->dumpValue(o, i) << std::endl;
}
else
{
bool first = true;
if (!label.empty())
{
o << label;
first = false;
}
for (size_t i = 0; i < len; i++) {
if (first)
first = false;
else
o << fieldSeparator;
pvScalarArray->dumpValue(o, i);
}
}
return o;
}
/*
std::ostream& formatScalarArray(std::ostream& o, PVScalarArrayPtr const & pvScalarArray)
{
size_t len = pvScalarArray->getLength();
if (len == 0)
{
// TODO do we really want this
o << "(empty)" << std::endl;
}
else
{
for (size_t i = 0; i < len; i++)
pvScalarArray->dumpValue(o, i) << std::endl;
}
return o;
}
*/
void formatNTScalarArray(std::ostream& o, PVStructurePtr const & pvStruct)
{
PVScalarArrayPtr value = TR1::dynamic_pointer_cast<PVScalarArray>(pvStruct->getSubField("value"));
if (value.get() == 0)
{
std::cerr << "no scalar_t[] 'value' field in NTScalarArray" << std::endl;
return;
}
//o << *value;
//formatScalarArray(o, value);
formatVector(o, "", value, mode == TerseMode || transpose);
}
void formatNTEnum(std::ostream& o, PVStructurePtr const & pvStruct)
{
PVStructurePtr enumt = TR1::dynamic_pointer_cast<PVStructure>(pvStruct->getSubField("value"));
if (enumt.get() == 0)
{
std::cerr << "no enum_t 'value' field in NTEnum" << std::endl;
return;
}
printEnumT(o, enumt);
}
size_t getLongestString(shared_vector<const string> const & array)
{
size_t max = 0;
size_t len = array.size();
for (size_t i = 0; i < len; i++)
{
size_t l = array[i].size();
if (l > max) max = l;
}
return max;
}
size_t getLongestString(PVScalarArrayPtr const & array)
{
size_t max = 0;
string empty;
ostringstream oss;
size_t len = array->getLength();
for (size_t i = 0; i < len; i++)
{
oss.str(empty);
array->dumpValue(oss, i);
size_t l = oss.tellp();
if (l > max) max = l;
}
return max;
}
// labels are optional
// if provided labels.size() must equals columnData.size()
void formatTable(std::ostream& o,
shared_vector<const string> const & labels,
vector<PVScalarArrayPtr> const & columnData,
bool showHeader, bool transpose)
{
// array with maximum number of elements
size_t maxValues = 0;
// value with longest string form
size_t maxLabelColumnLength = (showHeader && labels.size()) ? getLongestString(labels) : 0;
size_t maxColumnLength = 0;
//
// get maxValue and maxColumnLength
//
size_t numColumns = columnData.size();
for (size_t i = 0; i < numColumns; i++)
{
PVScalarArrayPtr array = columnData[i];
if (array.get())
{
size_t arrayLength = array->getLength();
if (maxValues < arrayLength) maxValues = arrayLength;
size_t colLen = getLongestString(array);
if (colLen > maxColumnLength) maxColumnLength = colLen;
}
}
// add some space
size_t padding = 2;
maxColumnLength += padding;
if (!transpose)
{
/* non-compact
maxLabelColumnLength += padding;
// increase maxColumnLength to maxLabelColumnLength
if (maxLabelColumnLength > maxColumnLength)
maxColumnLength = maxLabelColumnLength;
*/
//
// <column0>, <column1>, ...
// values values ...
//
// first print labels
if (showHeader && labels.size())
{
for (size_t i = 0; i < numColumns; i++)
{
if (fieldSeparator == ' ')
{
int width = std::max(labels[i].size()+padding, maxColumnLength);
o << std::setw(width) << std::right;
// non-compact o << std::setw(maxColumnLength) << std::right;
}
else if (i > 0)
{
o << fieldSeparator;
}
o << labels[i];
}
o << std::endl;
}
// then values
for (size_t r = 0; r < maxValues; r++)
{
for (size_t i = 0; i < numColumns; i++)
{
if (fieldSeparator == ' ' && (showHeader || numColumns > 1))
{
int width = std::max(labels[i].size()+padding, maxColumnLength);
o << setw(width) << std::right;
// non-compact o << std::setw(maxColumnLength) << std::right;
}
else if (i > 0)
{
o << fieldSeparator;
}
PVScalarArrayPtr array = columnData[i];
if (array.get() && r < array->getLength())
array->dumpValue(o, r);
else
o << "";
}
o << std::endl;
}
}
else
{
//
// <column0> values...
// <column1> values...
// ...
//
for (size_t i = 0; i < numColumns; i++)
{
if (showHeader && labels.size())
{
if (fieldSeparator == ' ')
{
o << std::setw(maxLabelColumnLength) << std::left;
}
o << labels[i];
}
for (size_t r = 0; r < maxValues; r++)
{
if (fieldSeparator == ' ' && (showHeader || numColumns > 1))
{
o << std::setw(maxColumnLength) << std::right;
}
else if (showHeader || r > 0)
o << fieldSeparator;
PVScalarArrayPtr array = columnData[i];
if (array.get() && r < array->getLength())
array->dumpValue(o, r);
else
o << "";
}
o << std::endl;
}
}
}
void formatNTTable(std::ostream& o, PVStructurePtr const & pvStruct)
{
PVStringArrayPtr labels = pvStruct->getSubField<PVStringArray>("labels");
if (labels.get() == 0)
{
std::cerr << "no string[] 'labels' field in NTTable" << std::endl;
return;
}
PVStructurePtr value = pvStruct->getSubField<PVStructure>("value");
if (value.get() == 0)
{
std::cerr << "no 'value' structure in NTTable" << std::endl;
return;
}
vector<PVScalarArrayPtr> columnData;
PVFieldPtrArray fields = value->getPVFields();
size_t numColumns = fields.size();
if (labels->getLength() != numColumns)
{
std::cerr << "malformed NTTable, length of 'labels' array does not equal to a number of 'value' structure subfields" << std::endl;
return;
}
for (size_t i = 0; i < numColumns; i++)
{
PVScalarArrayPtr array = TR1::dynamic_pointer_cast<PVScalarArray>(fields[i]);
if (array.get() == 0)
{
std::cerr << "malformed NTTable, " << (i+1) << ". field is not scalar_t[]" << std::endl;
return;
}
columnData.push_back(array);
}
bool showHeader = (mode != TerseMode);
formatTable(o, labels->view(), columnData, showHeader, transpose);
}
void formatNTMatrix(std::ostream& o, PVStructurePtr const & pvStruct)
{
PVDoubleArrayPtr value = pvStruct->getSubField<PVDoubleArray>("value");
if (value.get() == 0)
{
std::cerr << "no double[] 'value' field in NTMatrix" << std::endl;
return;
}
int32 rows, cols;
PVIntArrayPtr dim = pvStruct->getSubField<PVIntArray>("dim");
if (dim.get() != 0)
{
// dim[] = { rows, columns }
size_t dims = dim->getLength();
if (dims != 1 && dims != 2)
{
std::cerr << "malformed NTMatrix, dim[] must contain 1 or 2 elements instead of " << dims << std::endl;
return;
}
PVIntArray::const_svector data = dim->view();
rows = data[0];
cols = (dims == 2) ? data[1] : 1;
if (rows <= 0 || cols <= 0)
{
std::cerr << "malformed NTMatrix, dim[] must contain elements > 0" << std::endl;
return;
}
}
else
{
// column vector
rows = value->getLength();
cols = 1;
}
o << std::left;
size_t len = static_cast<size_t>(rows*cols);
if (len != value->getLength())
{
std::cerr << "malformed NTMatrix, values[] must contain " << len << " elements instead of " << value->getLength() << std::endl;
return;
}
// add some space
size_t padding = 2;
size_t maxColumnLength = getLongestString(value) + padding;
if (!transpose)
{
//
// el1 el2 el3
// el4 el5 el6
//
size_t ix = 0;
for (int32 r = 0; r < rows; r++)
{
for (int32 c = 0; c < cols; c++)
{
if (fieldSeparator == ' ' && cols > 1)
o << std::setw(maxColumnLength) << std::right;
else if (c > 0)
o << fieldSeparator;
if (columnMajor)
value->dumpValue(o, r + c * rows);
else
value->dumpValue(o, ix++);
}
o << std::endl;
}
}
else
{
//
// el1 el4
// el2 el5
// el3 el6
//
size_t ix = 0;
for (int32 c = 0; c < cols; c++)
{
for (int32 r = 0; r < rows; r++)
{
if (fieldSeparator == ' ' && rows > 1)
o << std::setw(maxColumnLength) << std::right;
else if (r > 0)
o << fieldSeparator;
if (columnMajor)
value->dumpValue(o, ix++);
else
value->dumpValue(o, r * cols + c);
}
o << std::endl;
}
}
}
// TODO use formatNTTable
void formatNTNameValue(std::ostream& o, PVStructurePtr const & pvStruct)
{
PVStringArrayPtr name = pvStruct->getSubField<PVStringArray>("name");
if (name.get() == 0)
{
std::cerr << "no string[] 'name' field in NTNameValue" << std::endl;
return;
}
PVFieldPtr value = pvStruct->getSubField("value");
if (value.get() == 0)
{
std::cerr << "no 'value' field in NTNameValue" << std::endl;
return;
}
PVScalarArrayPtr array = TR1::dynamic_pointer_cast<PVScalarArray>(value);
if (array.get() == 0)
{
std::cerr << "malformed NTNameValue, 'value' field is not scalar_t[]" << std::endl;
return;
}
if (name->getLength() != array->getLength())
{
std::cerr << "malformed NTNameValue, length of 'name' and 'value' array does not equal" << std::endl;
return;
}
size_t numColumns = name->getLength();
// get names
PVStringArray::const_svector nameData = name->view();
// get max column name size
bool showHeader = (mode != TerseMode);
size_t maxLabelColumnLength = showHeader ? getLongestString(nameData) : 0;
size_t maxColumnLength = showHeader ? getLongestString(array) : 0;
// add some space
size_t padding = 2;
maxColumnLength += padding;
if (transpose)
{
/* non-compact
maxLabelColumnLength += padding;
// increase maxColumnLength to maxLabelColumnLength
if (maxLabelColumnLength > maxColumnLength)
maxColumnLength = maxLabelColumnLength;
*/
//
// <name0>, <name1>, ...
// value values ...
//
// first print names
if (showHeader)
{
for (size_t i = 0; i < numColumns; i++)
{
if (fieldSeparator == ' ')
{
int width = std::max(nameData[i].size()+padding, maxColumnLength);
o << std::setw(width) << std::right;
// non-compact o << std::setw(maxColumnLength) << std::right;
}
else if (i > 0)
{
o << fieldSeparator;
}
o << nameData[i];
}
o << std::endl;
}
// then values
for (size_t i = 0; i < numColumns; i++)
{
if (fieldSeparator == ' ' && showHeader)
{
int width = std::max(nameData[i].size()+padding, maxColumnLength);
o << std::setw(width) << std::right;
// non-compact o << std::setw(maxColumnLength) << std::right;
}
else if (i > 0)
{
o << fieldSeparator;
}
array->dumpValue(o, i);
}
o << std::endl;
}
else
{
//
// <name0> values...
// <name1> values...
// ...
//
for (size_t i = 0; i < numColumns; i++)
{
if (showHeader)
{
if (fieldSeparator == ' ')
{
o << std::setw(maxLabelColumnLength) << std::left;
}
o << nameData[i];
}
if (fieldSeparator == ' ' && showHeader)
{
o << std::setw(maxColumnLength) << std::right;
}
else if (showHeader)
{
o << fieldSeparator;
}
array->dumpValue(o, i);
o << std::endl;
}
}
}
void formatNTURI(std::ostream& o, PVStructurePtr const & pvStruct)
{
PVStringPtr scheme = TR1::dynamic_pointer_cast<PVString>(pvStruct->getSubField<PVString>("scheme"));
if (scheme.get() == 0)
{
std::cerr << "no string 'scheme' field in NTURI" << std::endl;
}
PVStringPtr authority = TR1::dynamic_pointer_cast<PVString>(pvStruct->getSubField("authority"));
PVStringPtr path = TR1::dynamic_pointer_cast<PVString>(pvStruct->getSubField<PVString>("path"));
if (path.get() == 0)
{
std::cerr << "no string 'path' field in NTURI" << std::endl;
return;
}
PVStructurePtr query = TR1::dynamic_pointer_cast<PVStructure>(pvStruct->getSubField("query"));
o << scheme->get() << "://";
if (authority.get()) o << authority->get();
o << '/' << path->get();
// query
if (query.get())
{
PVFieldPtrArray fields = query->getPVFields();
size_t numColumns = fields.size();
if (numColumns > 0)
{
o << '?';
for (size_t i = 0; i < numColumns; i++)
{
if (i)
o << '&';
// TODO encode value!!!
o << fields[i]->getFieldName() << '=' << *(fields[i].get());
}
}
}
o << std::endl;
}
void formatNTNDArray(std::ostream& /*o*/, PVStructurePtr const & pvStruct)
{
PVUnion::shared_pointer pvUnion = pvStruct->getSubField<PVUnion>("value");
if (pvUnion.get() == 0)
{
std::cerr << "no 'value' union field in NTNDArray" << std::endl;
return;
}
PVScalarArray::shared_pointer value = pvUnion->get<PVScalarArray>();
if (value.get() == 0)
{
std::cerr << "'value' union field in given NTNDArray does not hold scalar array value" << std::endl;
return;
}
// undefined
int32 cm = -1;
PVStructureArray::shared_pointer pvAttributes = pvStruct->getSubField<PVStructureArray>("attribute");
PVStructureArray::const_svector attributes(pvAttributes->view());
for (PVStructureArray::const_svector::const_iterator iter = attributes.begin();
iter != attributes.end();
iter++)
{
PVStructure::shared_pointer attribute = *iter;
PVString::shared_pointer pvName = attribute->getSubField<PVString>("name");
if (pvName && pvName->get() == "ColorMode")
{
PVInt::shared_pointer pvCM = attribute->getSubField<PVUnion>("value")->get<PVInt>();
if (!pvCM)
break;
cm = pvCM->get();
break;
}
}
if (cm == -1)
{
std::cerr << "no PVInt 'ColorMode' attribute in NTNDArray" << std::endl;
return;
}
if (cm != 0 && cm != 1 && cm != 2)
{
std::cerr << "unsupported 'ColorMode', only {0,1,2} modes are supported" << std::endl;
return;
}
int32 rows, cols;
PVStructureArray::shared_pointer dim = pvStruct->getSubField<PVStructureArray>("dimension");
if (dim.get() == 0)
{
std::cerr << "no 'dimension' structure array field in NTNDArray" << std::endl;
return;
}
// dim[] = { rows, columns } or
// dim[] = { 3, rows, columns }
PVStructureArray::const_svector data = dim->view();
size_t dims = dim->getLength();
size_t imageSize;
if ((cm == 0 || cm == 1) && dims == 2)
{
cols = data[0]->getSubField<PVInt>("size")->get();
rows = data[1]->getSubField<PVInt>("size")->get();
imageSize = cols * rows;
}
else if (cm == 2 && dims == 3)
{
cols = data[1]->getSubField<PVInt>("size")->get();
rows = data[2]->getSubField<PVInt>("size")->get();
imageSize = cols * rows * 3;
}
else
{
std::cerr << "malformed NTNDArray, dimension[] is invalid for specified color mode" << std::endl;
return;
}
if (rows <= 0 || cols <= 0)
{
std::cerr << "malformed NTNDArray, dimension[] gives negative size" << std::endl;
return;
}
PVByteArrayPtr array = TR1::dynamic_pointer_cast<PVByteArray>(value);
if (array.get() == 0)
{
std::cerr << "currently only byte[] value is supported" << std::endl;
return;
}
if (array->getLength() != imageSize)
{
std::cerr << "value array length does not match expected image size (" <<
array->getLength() << " != " << imageSize << ")" << std::endl;
return;
}
PVByteArray::const_svector img = array->view();
/*
size_t len = static_cast<size_t>(rows*cols);
for (size_t i = 0; i < len; i++)
o << img[i];
*/
//eget -s testImage | gnuplot -e "set size ratio -1; set palette grey; set cbrange [0:255]; plot '-' binary array=(512,384) flipy format='%uchar' with image"
FILE* gnuplotPipe = popen ("gnuplot -persist", "w"); // use -persist for backward compatibility (to support older gnuplot versions)
const char *prologue = getenv("EGET_GNUPLOT_PROLOGUE");
if (prologue)
fprintf(gnuplotPipe, "%s\n", prologue);
fprintf(gnuplotPipe, "set format \"\"\n");
fprintf(gnuplotPipe, "unset key\n");
fprintf(gnuplotPipe, "unset border\n");
fprintf(gnuplotPipe, "unset colorbox\n");
fprintf(gnuplotPipe, "unset xtics\n");
fprintf(gnuplotPipe, "unset ytics\n");
fprintf(gnuplotPipe, "set size ratio 1\n");
fprintf(gnuplotPipe, "set xrange [0:%u]\n", cols-1);
fprintf(gnuplotPipe, "set yrange [0:%u]\n", rows-1);
if (cm == 2)
{
// RGB
fprintf(gnuplotPipe, "plot '-' binary array=(%u,%u) flipy format='%%uchar' with rgbimage\n", cols, rows);
for (size_t i = 0; i < imageSize; i++)
fprintf(gnuplotPipe, "%c", img[i]);
}
else
{
// monochrome
fprintf(gnuplotPipe, "set palette grey\n");
fprintf(gnuplotPipe, "set cbrange [0:255]\n");
fprintf(gnuplotPipe, "plot '-' binary array=(%u,%u) flipy format='%%uchar' with image\n", cols, rows);
for (size_t i = 0; i < imageSize; i++)
fprintf(gnuplotPipe, "%c", img[i]);
}
fflush(gnuplotPipe);
pclose(gnuplotPipe);
}
typedef void(*NTFormatterFunc)(std::ostream& o, PVStructurePtr const & pvStruct);
typedef map<string, NTFormatterFunc> NTFormatterLUTMap;
NTFormatterLUTMap ntFormatterLUT;
void initializeNTFormatterLUT()
{
ntFormatterLUT["epics:nt/NTScalar:1"] = formatNTScalar;
ntFormatterLUT["epics:nt/NTScalarArray:1"] = formatNTScalarArray;
ntFormatterLUT["epics:nt/NTEnum:1"] = formatNTEnum;
ntFormatterLUT["epics:nt/NTTable:1"] = formatNTTable;
ntFormatterLUT["epics:nt/NTMatrix:1"] = formatNTMatrix;
ntFormatterLUT["epics:nt/NTAny:1"] = formatNTAny;
ntFormatterLUT["epics:nt/NTNameValue:1"] = formatNTNameValue;
ntFormatterLUT["epics:nt/NTURI:1"] = formatNTURI;
ntFormatterLUT["epics:nt/NTNDArray:1"] = formatNTNDArray;
}
void formatNT(std::ostream& o, PVFieldPtr const & pv)
{
static bool lutInitialized = false;
if (!lutInitialized)
{
initializeNTFormatterLUT();
lutInitialized = true;
}
if (pv.get() == 0)
{
o << "(null)" << std::endl;
return;
}
Type type = pv->getField()->getType();
if (type==structure)
{
PVStructurePtr pvStruct = TR1::static_pointer_cast<PVStructure>(pv);
{
string id = pvStruct->getField()->getID();
// remove minor
size_t pos = id.find_last_of('.');
if (pos != string::npos)
id = id.substr(0, pos);
NTFormatterLUTMap::const_iterator formatter = ntFormatterLUT.find(id);
if (formatter != ntFormatterLUT.end())
{
(formatter->second)(o, pvStruct);
}
else
{
std::cerr << "non-normative type" << std::endl;
//o << *(pv.get()) << std::endl;
pvutil_ostream myos(std::cout.rdbuf());
myos << *(pv.get()) << std::endl;
}
return;
}
}
// no ID, just dump
pvutil_ostream myos(std::cout.rdbuf());
myos << *(pv.get()) << std::endl;
}
void dumpValue(std::string const & channelName, PVField::shared_pointer const & pv)
{
if (!channelName.empty())
std::cout << channelName << std::endl;
//std::cout << *(pv.get()) << std::endl << std::endl;
pvutil_ostream myos(std::cout.rdbuf());
if (pv->getField()->getType() == structure)
myos << *(TR1::static_pointer_cast<PVStructure>(pv).get()) << std::endl << std::endl;
else
myos << *(pv.get()) << std::endl << std::endl;
}
void printValue(std::string const & channelName, PVStructure::shared_pointer const & pv, bool forceTerseWithName = false)
{
if (forceTerseWithName)
{
if (!channelName.empty())
std::cout << channelName << fieldSeparator;
terseStructure(std::cout, pv) << std::endl;
}
else if (mode == ValueOnlyMode)
{
PVField::shared_pointer value = pv->getSubField("value");
if (value.get() == 0)
{
std::cerr << "no 'value' field" << std::endl;
dumpValue(channelName, pv);
}
else
{
Type valueType = value->getField()->getType();
if (valueType == scalar)
std::cout << *(value.get()) << std::endl;
else if (valueType == scalarArray)
{
//formatScalarArray(std::cout, TR1::dynamic_pointer_cast<PVScalarArray>(value));
formatVector(std::cout, "", TR1::dynamic_pointer_cast<PVScalarArray>(value), false);
}
else
{
// switch to structure mode, unless it's T-type
if (valueType == structure && isTType(TR1::static_pointer_cast<PVStructure>(value)))
{
formatTType(std::cout, TR1::static_pointer_cast<PVStructure>(value));
std::cout << std::endl;
}
else
dumpValue(channelName, pv);
}
}
}
else if (mode == TerseMode)
terseStructure(std::cout, pv) << std::endl;
else
dumpValue(channelName, pv);
}
// only in ValueOnlyMode
// NOTE: names might be empty
void printValues(shared_vector<const string> const & names, vector<PVStructure::shared_pointer> const & values)
{
size_t len = values.size();
vector<PVScalar::shared_pointer> scalars;
vector<PVScalarArray::shared_pointer> scalarArrays;
for (size_t i = 0; i < len; i++)
{
PVField::shared_pointer value = values[i]->getSubField("value");
if (value.get() != 0)
{
Type type = value->getField()->getType();
if (type == scalarArray)
scalarArrays.push_back(TR1::dynamic_pointer_cast<PVScalarArray>(value));
else if (type == scalar)
{
PVScalar::shared_pointer scalar = TR1::dynamic_pointer_cast<PVScalar>(value);
scalars.push_back(scalar);
// make an array, i.e. PVStringArray, out of a scalar (since scalar is an array w/ element count == 1)
PVStringArray::shared_pointer StringArray =
TR1::dynamic_pointer_cast<PVStringArray>(getPVDataCreate()->createPVScalarArray(pvString));
PVStringArray::svector values;
values.push_back(scalar->getAs<std::string>());
StringArray->replace(freeze(values));
scalarArrays.push_back(StringArray);
}
}
else
{
// a structure detected, break
break;
}
}
if (scalars.size() == len)
{
if (!transpose)
{
bool first = true;
for (size_t i = 0; i < len; i++)
{
if (first)
first = false;
else
std::cout << fieldSeparator;
std::cout << *(scalars[i].get());
}
std::cout << std::endl;
}
else
{
for (size_t i = 0; i < len; i++)
std::cout << *(scalars[i].get()) << std::endl;
}
}
else if (scalarArrays.size() == len)
{
// format as a table
bool showHeader = false; //(mode != TerseMode);
formatTable(std::cout, names, scalarArrays, showHeader, transpose);
}
else
{
// force terse mode with names
for (size_t i = 0; i < len; i++)
printValue(names[i], values[i], true);
}
}
#define DEFAULT_TIMEOUT 3.0
#define DEFAULT_REQUEST "field(value)"
#define DEFAULT_RPC_REQUEST ""
#define DEFAULT_PROVIDER "pva"
double timeOut = DEFAULT_TIMEOUT;
string request(DEFAULT_REQUEST);
void usage (void)
{
fprintf (stderr, "\nUsage: eget [options] [<PV name>... | -s <service name>]\n\n"
"\noptions:\n"
" -h: Help: Print this message\n"
" -V: Print version and exit\n"
" -s <service name>: Service API compliant based RPC service name (accepts NTURI request argument)\n"
" -a <service arg>: Service argument in 'name[=value]' or 'name value' form\n"
" -r <pv request>: Get request string, specifies what fields to return and options, default is '%s'\n"
" -w <sec>: Wait time, specifies timeout, default is %f second(s)\n"
" -z: Pure pvAccess RPC based service (send NTURI.query as request argument)\n"
" -N: Do not format NT types, dump structure instead\n"
" -i: Do not format standard types (enum_t, time_t, ...)\n"
" -t: Terse mode\n"
" -T: Transpose vector, table, matrix\n"
" -x: Use column-major order to decode matrix\n"
" -p <provider>: Set default provider name, default is '%s'\n"
" -q: Quiet mode, print only error messages\n"
" -d: Enable debug output\n"
" -F <ofs>: Use <ofs> as an alternate output field fieldSeparator\n"
" -f <input file>: Use <input file> as an input that provides a list PV name(s) to be read, use '-' for stdin\n"
" -c: Wait for clean shutdown and report used instance count (for expert users)\n"
" enum format:\n"
" -n: Force enum interpretation of values as numbers (default is enum string)\n"
"\n\nexamples:\n\n"
"#! Get the value of the PV corr:li32:53:bdes\n"
"> eget corr:li32:53:bdes\n"
"\n"
"#! Get the table of all correctors from the rdb service\n"
"> eget -s rdbService -a entity=swissfel:devicenames\n"
"\n"
"#! Get the archive history of quad45:bdes;history between 2 times, from the archive service\n"
"> eget -s archiveService -a entity=quad45:bdes;history -a starttime=2012-02-12T10:04:56 -a endtime=2012-02-01T10:04:56\n"
"\n"
"#! Get polynomials for bunch of quads using a stdin to give a list of PV names\n"
"> eget -s names -a pattern=QUAD:LTU1:8%%:POLYCOEF | eget -f -\n"
"\n"
, DEFAULT_REQUEST, DEFAULT_TIMEOUT, DEFAULT_PROVIDER);
}
class ChannelGetRequesterImpl : public ChannelGetRequester
{
private:
string m_channelName;
bool m_printValue;
PVStructure::shared_pointer m_pvStructure;
BitSet::shared_pointer m_bitSet;
Mutex m_pointerMutex;
Event m_event;
bool m_done;
public:
ChannelGetRequesterImpl(std::string channelName, bool printValue) :
m_channelName(channelName),
m_printValue(printValue),
m_done(false)
{
}
virtual string getRequesterName()
{
return "ChannelGetRequesterImpl";
}
virtual void channelGetConnect(const epics::pvData::Status& status,
ChannelGet::shared_pointer const & channelGet,
epics::pvData::Structure::const_shared_pointer const & /*structure*/)
{
if (status.isSuccess())
{
// show warning
if (!status.isOK())
{
std::cerr << "[" << m_channelName << "] channel get create: " << dump_stack_only_on_debug(status) << std::endl;
}
channelGet->lastRequest();
channelGet->get();
}
else
{
std::cerr << "[" << m_channelName << "] failed to create channel get: " << dump_stack_only_on_debug(status) << std::endl;
m_event.signal();
}
}
virtual void getDone(const epics::pvData::Status& status,
ChannelGet::shared_pointer const & /*channelGet*/,
epics::pvData::PVStructure::shared_pointer const & pvStructure,
epics::pvData::BitSet::shared_pointer const & bitSet)
{
if (status.isSuccess())
{
// show warning
if (!status.isOK())
{
std::cerr << "[" << m_channelName << "] channel get: " << dump_stack_only_on_debug(status) << std::endl;
}
// access smart pointers
{
Lock lock(m_pointerMutex);
m_pvStructure = pvStructure;
m_bitSet = bitSet;
m_done = true;
if (m_printValue)
{
printValue(m_channelName, m_pvStructure);
}
}
}
else
{
std::cerr << "[" << m_channelName << "] failed to get: " << dump_stack_only_on_debug(status) << std::endl;
}
m_event.signal();
}
PVStructure::shared_pointer getPVStructure()
{
Lock lock(m_pointerMutex);
return m_pvStructure;
}
bool waitUntilGet(double timeOut)
{
bool signaled = m_event.wait(timeOut);
if (!signaled)
{
std::cerr << "[" << m_channelName << "] get timeout" << std::endl;
return false;
}
Lock lock(m_pointerMutex);
return m_done;
}
};
class ChannelRPCRequesterImpl : public ChannelRPCRequester
{
private:
Mutex m_pointerMutex;
Event m_event;
Event m_connectionEvent;
bool m_successfullyConnected;
string m_channelName;
PVStructure::shared_pointer m_lastResponse;
bool m_done;
public:
ChannelRPCRequesterImpl(std::string channelName) :
m_successfullyConnected(false),
m_channelName(channelName),
m_done(false)
{
}
virtual string getRequesterName()
{
return "ChannelRPCRequesterImpl";
}
virtual void channelRPCConnect(const epics::pvData::Status& status, ChannelRPC::shared_pointer const & /*channelRPC*/)
{
if (status.isSuccess())
{
// show warning
if (!status.isOK())
{
std::cerr << "[" << m_channelName << "] channel RPC create: " << dump_stack_only_on_debug(status) << std::endl;
}
{
Lock lock(m_pointerMutex);
m_successfullyConnected = status.isSuccess();
}
m_connectionEvent.signal();
}
else
{
std::cerr << "[" << m_channelName << "] failed to create channel RPC: " << dump_stack_only_on_debug(status) << std::endl;
m_connectionEvent.signal();
}
}
virtual void requestDone (const epics::pvData::Status &status,
ChannelRPC::shared_pointer const & /*channelRPC*/,
epics::pvData::PVStructure::shared_pointer const &pvResponse)
{
if (status.isSuccess())
{
// show warning
if (!status.isOK())
{
std::cerr << "[" << m_channelName << "] channel RPC: " << dump_stack_only_on_debug(status) << std::endl;
}
// access smart pointers
{
Lock lock(m_pointerMutex);
m_done = true;
m_lastResponse = pvResponse;
/*
formatNT(std::cout, pvResponse);
std::cout << std::endl;
*/
}
}
else
{
std::cerr << "[" << m_channelName << "] failed to RPC: " << dump_stack_only_on_debug(status) << std::endl;
}
m_event.signal();
}
/*
void request(epics::pvData::PVStructure::shared_pointer const &pvRequest)
{
Lock lock(m_pointerMutex);
m_channelRPC->request(pvRequest, false);
}
*/
PVStructure::shared_pointer getLastResponse()
{
Lock lock(m_pointerMutex);
return m_lastResponse;
}
bool waitUntilRPC(double timeOut)
{
bool signaled = m_event.wait(timeOut);
if (!signaled)
{
std::cerr << "[" << m_channelName << "] RPC timeout" << std::endl;
return false;
}
Lock lock(m_pointerMutex);
return m_done;
}
bool waitUntilConnected(double timeOut)
{
bool signaled = m_connectionEvent.wait(timeOut);
if (!signaled)
{
std::cerr << "[" << m_channelName << "] RPC create timeout" << std::endl;
return false;
}
Lock lock(m_pointerMutex);
return m_successfullyConnected;
}
};
/*+**************************************************************************
*
* Function: main
*
* Description: eget main()
* Evaluate command line options, set up PVA, connect the
* channels, print the data as requested
*
* Arg(s) In: [options] <pv-name>...
*
* Arg(s) Out: none
*
* Return(s): Standard return code (0=success, 1=error)
*
**************************************************************************-*/
int main (int argc, char *argv[])
{
int opt; /* getopt() current option */
bool debug = false;
bool cleanupAndReport = false;
bool serviceRequest = false;
bool pvRequestProvidedByUser = false;
bool onlyQuery = false;
istream* inputStream = 0;
ifstream ifs;
bool fromStream = false;
string service;
//string urlEncodedRequest;
vector< pair<string,string> > parameters;
string defaultProvider = DEFAULT_PROVIDER;
setvbuf(stdout,NULL,_IOLBF,BUFSIZ); /* Set stdout to line buffering */
while ((opt = getopt(argc, argv, ":hVr:s:a:w:zNtTmxp:qdcF:f:ni")) != -1) {
switch (opt) {
case 'h': /* Print usage */
usage();
return 0;
case 'V': /* Print version */
{
Version version("eget", "cpp",
EPICS_PVA_MAJOR_VERSION,
EPICS_PVA_MINOR_VERSION,
EPICS_PVA_MAINTENANCE_VERSION,
EPICS_PVA_DEVELOPMENT_FLAG);
fprintf(stdout, "%s\n", version.getVersionString().c_str());
return 0;
}
case 'w': /* Set PVA timeout value */
if((epicsScanDouble(optarg, &timeOut)) != 1 || timeOut <= 0.0)
{
fprintf(stderr, "'%s' is not a valid timeout value "
"- ignored. ('eget -h' for help.)\n", optarg);
timeOut = DEFAULT_TIMEOUT;
}
break;
case 'r': /* Set pvRequest value */
request = optarg;
pvRequestProvidedByUser = true;
// do not override terse mode
if (mode == ValueOnlyMode) mode = StructureMode;
break;
case 'a': /* Service parameters */
{
string param = optarg;
size_t eqPos = param.find('=');
if (eqPos==0)
{
// no name
fprintf(stderr, "Parameter not specified in '-a name=value' or '-a name value' form. ('eget -h' for help.)\n");
return 1;
}
else if (eqPos==string::npos)
{
// is next argument actually a value, i.e. "-a name value" form
if (optind < argc && *argv[optind] != '-')
{
parameters.push_back(pair<string,string>(param, argv[optind]));
optind++;
}
else
{
// no value
//fprintf(stderr, "Parameter not specified in '-a name=value' or '-a name value' form. ('eget -h' for help.)\n");
//return 1;
parameters.push_back(pair<string,string>(param, ""));
}
}
else
{
parameters.push_back(pair<string,string>(param.substr(0, eqPos), param.substr(eqPos+1, string::npos)));
}
/*
if (urlEncodedRequest.size())
urlEncodedRequest += '&';
char* encoded = url_encode(optarg);
urlEncodedRequest += encoded;
free(encoded);
*/
break;
}
case 's': /* Service name */
service = optarg;
serviceRequest = true;
break;
case 'z': /* pvAccess RPC mode */
onlyQuery = true;
break;
case 'N': /* Do not format NT types */
dumpStructure = true;
break;
case 'i': /* T-types format mode */
formatTTypesFlag = false;
break;
case 't': /* Terse mode */
mode = TerseMode;
break;
case 'T': /* Terse mode */
transpose = true;
break;
case 'm': /* Monitor mode */
std::cerr<<"Monitor mode is broken and has been disabled until fixed\n";
exit(1);
break;
case 'x': /* Column-major order mode */
columnMajor = true;
break;
case 'p': /* Provider name */
defaultProvider = optarg;
// for now no only pva/ca schema is supported
// TODO
if (defaultProvider != "pva" && defaultProvider != "ca")
{
std::cerr << "invalid default provider '" << defaultProvider << "', only 'pva' and 'ca' are supported" << std::endl;
// TODO
return 1;
}
break;
case 'q': /* Quiet mode */
break;
case 'd': /* Debug log level */
debug = true;
break;
case 'c': /* Clean-up and report used instance count */
cleanupAndReport = true;
break;
case 'F': /* Store this for output formatting */
fieldSeparator = (char) *optarg;
break;
case 'f': /* Use input stream as input */
{
string fileName = optarg;
if (fileName == "-")
inputStream = &cin;
else
{
ifs.open(fileName.c_str(), ifstream::in);
if (!ifs)
{
fprintf(stderr,
"Failed to open file '%s'.\n",
fileName.c_str());
return 1;
}
else
inputStream = &ifs;
}
fromStream = true;
break;
}
case 'n':
enumMode = NumberEnum;
break;
case '?':
fprintf(stderr,
"Unrecognized option: '-%c'. ('eget -h' for help.)\n",
optopt);
return 1;
case ':':
fprintf(stderr,
"Option '-%c' requires an argument. ('eget -h' for help.)\n",
optopt);
return 1;
default :
usage();
return 1;
}
}
int nPvs = argc - optind; /* Remaining arg list are PV names */
if (nPvs > 0)
{
// do not allow reading file and command line specified pvs
fromStream = false;
}
else if (nPvs < 1 && !serviceRequest && !fromStream)
{
fprintf(stderr, "No PV name(s) specified. ('eget -h' for help.)\n");
return 1;
}
// only one pv, arguments provided without serviceRequest switch
if (nPvs == 1 && parameters.size() > 0)
{
// switch to serviceRequest
service = argv[optind];
serviceRequest = true;
nPvs = 0;
}
else if (nPvs > 0 && serviceRequest)
{
fprintf(stderr, "PV name(s) specified and service query requested. ('eget -h' for help.)\n");
return 1;
}
SET_LOG_LEVEL(debug ? logLevelDebug : logLevelError);
std::cout << std::boolalpha;
arrayCountFlag = false;
bool allOK = true;
// parse URI
// try to parse as URI if only one nPvs
URI uri;
bool validURI =
(serviceRequest || nPvs == 1) ?
URI::parse(serviceRequest ? service : argv[optind], uri) :
false;
// if there is only one nPvs and it's a valid URI that has ? character,
// then it's an service (RPC) request
if (!serviceRequest && validURI && uri.query_indicated)
{
service = argv[optind];
serviceRequest = true;
}
// register "ca" provider
epics::pvAccess::ca::CAClientFactory::start();
// PVs mode
if (!serviceRequest)
{
vector<string> pvs;
vector<string> pvsAddress;
vector<ChannelProvider::shared_pointer> providers;
vector<epics::pvAccess::Destroyable::shared_pointer> operations;
if (validURI)
{
// standard get request
// for now no only pva/ca schema is supported, without authority
// TODO
if (uri.protocol != "pva" && uri.protocol != "ca")
{
std::cerr << "invalid URI scheme '" << uri.protocol << "', only 'pva' and 'ca' are supported" << std::endl;
// TODO
return 1;
}
if (uri.path.length() <= 1)
{
std::cerr << "invalid URI, empty path" << std::endl;
// TODO
return 1;
}
// skip trailing '/'
pvs.push_back(uri.path.substr(1));
pvsAddress.push_back(uri.host);
providers.push_back(ChannelProviderRegistry::clients()->getProvider(uri.protocol));
if(!providers.back()) {
std::cerr<<"Unknown provider \""<<uri.protocol<<"\" for \""<<pvs.back()<<"\n";
allOK = false;
}
}
else
{
std::vector<std::string> uris;
if(!fromStream) {
for (int n = 0; optind < argc; n++, optind++)
{
uris.push_back(argv[optind]);
}
} else {
string cn;
while (true)
{
*inputStream >> cn;
if (!(*inputStream))
break;
uris.push_back(cn);
}
}
for (size_t n = 0; n<uris.size(); n++)
{
URI uri;
bool validURI = URI::parse(uris[n], uri);
if (validURI)
{
if (uri.path.length() <= 1)
{
std::cerr << "invalid URI, empty path" << std::endl;
// TODO
return 1;
}
// skip trailing '/'
pvs.push_back(uri.path.substr(1));
pvsAddress.push_back(uri.host);
providers.push_back(ChannelProviderRegistry::clients()->getProvider(uri.protocol));
}
else
{
uri.protocol = defaultProvider;
pvs.push_back(uris[n]);
pvsAddress.push_back(std::string());
providers.push_back(ChannelProviderRegistry::clients()->getProvider(defaultProvider));
}
if(!providers.back()) {
std::cerr<<"Unknown provider \""<<uri.protocol<<"\" for \""<<pvs.back()<<"\"\n";
allOK = false;
}
}
}
nPvs = pvs.size();
PVStructure::shared_pointer pvRequest =
CreateRequest::create()->createRequest(request);
if(pvRequest.get()==0) {
fprintf(stderr, "failed to parse request string\n");
return 1;
}
// first connect to all, this allows resource (e.g. TCP connection) sharing
vector<Channel::shared_pointer> channels(nPvs);
for (int n = 0; n < nPvs; n++)
{
if(!providers[n]) continue;
channels[n] = providers[n]->createChannel(pvs[n], DefaultChannelRequester::build(),
ChannelProvider::PRIORITY_DEFAULT, pvsAddress[n]);
if(!channels[n]) {
std::cerr<<"No such channel '"<<pvs[n]<<"'\n";
}
}
// TODO maybe unify for nPvs == 1?!
// we cannot collect when fromStream is true, since we want to print value immediately
bool collectValues = (mode == ValueOnlyMode) && nPvs > 1 && !fromStream;
vector<PVStructure::shared_pointer> collectedValues;
shared_vector<string> collectedNames;
if (collectValues)
{
collectedValues.reserve(nPvs);
collectedNames.reserve(nPvs);
}
// for now a simple iterating sync implementation, guarantees order
for (int n = 0; n < nPvs; n++)
{
Channel::shared_pointer channel(channels[n]);
if(!channel) {
allOK = false;
continue;
}
{
TR1::shared_ptr<GetFieldRequesterImpl> getFieldRequesterImpl;
// probe for value field
// but only if there is only one PV request (otherwise mode change makes a mess)
if (mode == ValueOnlyMode && nPvs == 1)
{
getFieldRequesterImpl.reset(new GetFieldRequesterImpl(channel));
// get all to be immune to bad clients not supporting selective getField request
channel->getField(getFieldRequesterImpl, "");
}
if (getFieldRequesterImpl.get() == 0 ||
getFieldRequesterImpl->waitUntilFieldGet(timeOut))
{
// check probe
if (getFieldRequesterImpl.get())
{
Structure::const_shared_pointer structure =
TR1::dynamic_pointer_cast<const Structure>(getFieldRequesterImpl->getField());
if (structure.get() == 0 || structure->getField("value").get() == 0)
{
// fallback to structure
mode = StructureMode;
pvRequest = CreateRequest::create()->createRequest("field()");
}
}
TR1::shared_ptr<ChannelGetRequesterImpl> getRequesterImpl(
new ChannelGetRequesterImpl(channel->getChannelName(), false)
);
ChannelGet::shared_pointer channelGet = channel->createChannelGet(getRequesterImpl, pvRequest);
bool ok = getRequesterImpl->waitUntilGet(timeOut);
allOK &= ok;
if (ok)
{
if (collectValues)
{
collectedValues.push_back(getRequesterImpl->getPVStructure());
collectedNames.push_back(channel->getChannelName());
}
else
{
// print immediately
printValue(channel->getChannelName(), getRequesterImpl->getPVStructure(), fromStream);
}
}
}
else
{
allOK = false;
channel->destroy();
std::cerr << "[" << channel->getChannelName() << "] failed to get channel introspection data" << std::endl;
}
}
}
if (collectValues)
printValues(freeze(collectedNames), collectedValues);
}
// service RPC mode
else
{
string authority;
if (validURI)
{
if (uri.protocol != "pva")
{
std::cerr << "invalid URI scheme '" << uri.protocol << "', only 'pva' is supported" << std::endl;
// TODO
return 1;
}
authority = uri.host;
if (uri.path.length() <= 1)
{
std::cerr << "invalid URI, empty path" << std::endl;
// TODO
return 1;
}
// skip trailing '/'
service = uri.path.substr(1);
string::const_iterator end_i = uri.query.end();
string::const_iterator begin_i = uri.query.begin();
while (begin_i != end_i)
{
string::const_iterator pair_end_i = find(begin_i, end_i, '&');
string::const_iterator name_end_i = find(begin_i, pair_end_i, '=');
if (name_end_i != pair_end_i)
{
string name(begin_i, name_end_i);
string value(name_end_i+1, pair_end_i);
parameters.push_back(pair<string,string>(name, value));
}
else
{
//fprintf(stderr, "Parameter not specified in name=value form. ('eget -h' for help.)\n");
//return 1;
string name(begin_i, pair_end_i);
parameters.push_back(pair<string,string>(name, ""));
}
begin_i = pair_end_i;
if (begin_i != end_i)
begin_i++; // skip '&'
}
}
/*
std::cerr << "service : " << service << std::endl;
std::cerr << "parameters : " << std::endl;
vector< pair<string, string> >::iterator iter = parameters.begin();
for (; iter != parameters.end(); iter++)
std::cerr << " " << iter->first << " = " << iter->second << std::endl;
//std::cerr << "encoded URL request: '" << urlEncodedRequest << "'" << std::endl;
*/
// simply empty
PVStructure::shared_pointer pvRequest =
CreateRequest::create()->createRequest(
!pvRequestProvidedByUser ? DEFAULT_RPC_REQUEST : request
);
if(pvRequest.get()==NULL) {
fprintf(stderr, "failed to parse request string\n");
return 1;
}
StringArray queryFieldNames;
FieldConstPtrArray queryFields;
for (vector< pair<string, string> >::iterator iter = parameters.begin();
iter != parameters.end();
iter++)
{
queryFieldNames.push_back(iter->first);
queryFields.push_back(getFieldCreate()->createScalar(pvString));
}
Structure::const_shared_pointer queryStructure(
getFieldCreate()->createStructure(
queryFieldNames,
queryFields
)
);
StringArray uriFieldNames;
uriFieldNames.push_back("scheme");
if (!authority.empty()) uriFieldNames.push_back("authority");
uriFieldNames.push_back("path");
uriFieldNames.push_back("query");
FieldConstPtrArray uriFields;
uriFields.push_back(getFieldCreate()->createScalar(pvString));
if (!authority.empty()) uriFields.push_back(getFieldCreate()->createScalar(pvString));
uriFields.push_back(getFieldCreate()->createScalar(pvString));
uriFields.push_back(queryStructure);
Structure::const_shared_pointer uriStructure(
getFieldCreate()->createStructure(
"epics:nt/NTURI:1.0",
uriFieldNames,
uriFields
)
);
PVStructure::shared_pointer request(
getPVDataCreate()->createPVStructure(uriStructure)
);
request->getSubField<PVString>("scheme")->put("pva");
if (!authority.empty()) request->getSubField<PVString>("authority")->put(authority);
request->getSubField<PVString>("path")->put(service);
PVStructure::shared_pointer query = request->getSubField<PVStructure>("query");
for (vector< pair<string, string> >::iterator iter = parameters.begin();
iter != parameters.end();
iter++)
{
query->getSubField<PVString>(iter->first)->put(iter->second);
}
PVStructure::shared_pointer arg = onlyQuery ? query : request;
if (debug)
{
std::cout << "Request structure: " << std::endl << *(arg.get()) << std::endl;
}
ChannelProvider::shared_pointer provider = ChannelProviderRegistry::clients()->getProvider("pva");
assert(provider);
Channel::shared_pointer channel =
provider->createChannel(service, DefaultChannelRequester::build(),
ChannelProvider::PRIORITY_DEFAULT, authority);
TR1::shared_ptr<ChannelRPCRequesterImpl> rpcRequesterImpl(new ChannelRPCRequesterImpl(channel->getChannelName()));
ChannelRPC::shared_pointer channelRPC = channel->createChannelRPC(rpcRequesterImpl, pvRequest);
if (rpcRequesterImpl->waitUntilConnected(timeOut))
{
channelRPC->lastRequest();
channelRPC->request(arg);
allOK &= rpcRequesterImpl->waitUntilRPC(timeOut);
if (allOK)
{
if (dumpStructure)
{
if (rpcRequesterImpl->getLastResponse().get() == 0)
std::cout << "(null)" << std::endl;
else
{
//std::cout << *(rpcRequesterImpl->getLastResponse().get()) << std::endl;
pvutil_ostream myos(std::cout.rdbuf());
myos << *(rpcRequesterImpl->getLastResponse().get()) << std::endl;
}
}
else
formatNT(std::cout, rpcRequesterImpl->getLastResponse());
std::cout << std::endl;
}
}
else
{
allOK = false;
}
channel->destroy();
}
if (cleanupAndReport)
{
// TODO implement wait on context
epicsThreadSleep ( 3.0 );
//std::cerr << "-----------------------------------------------------------------------" << std::endl;
//epicsExitCallAtExits();
}
return allOK ? 0 : 1;
}