#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if EPICS_VERSION_INT>=VERSION_INT(3,15,0,1) # include # define USE_JSON #endif #include #include #include #include "pvutils.cpp" using namespace std; namespace TR1 = std::tr1; using namespace epics::pvData; using namespace epics::pvAccess; namespace { #define DEFAULT_TIMEOUT 3.0 #define DEFAULT_REQUEST "field(value)" #define DEFAULT_PROVIDER "pva" double timeOut = DEFAULT_TIMEOUT; string request(DEFAULT_REQUEST); string defaultProvider(DEFAULT_PROVIDER); const string noAddress; enum PrintMode { ValueOnlyMode, StructureMode, TerseMode }; PrintMode mode = ValueOnlyMode; char fieldSeparator = ' '; bool debug = false; void usage (bool details=false) { fprintf (stderr, "Usage: pvput [options] \n\n" " pvput [options] = ...\n" "\n" " -h: Help: Print this message\n" " -v: Print version and exit\n" "\noptions:\n" " -r : Request, specifies what fields to return and options, default is '%s'\n" " -w : Wait time, specifies timeout, default is %f second(s)\n" " -t: Terse mode - print only successfully written value, without names\n" " -p : Set default provider name, default is '%s'\n" " -q: Quiet mode, print only error messages\n" " -d: Enable debug output\n" " -F : Use as an alternate output field separator\n" " -f : Use as an input that provides a list PV name(s) to be read, use '-' for stdin\n" " enum format:\n" " default: Auto - try value as enum string, then as index number\n" " -n: Force enum interpretation of values as numbers\n" " -s: Force enum interpretation of values as strings\n" , DEFAULT_REQUEST, DEFAULT_TIMEOUT, DEFAULT_PROVIDER); if(details) { fprintf (stderr, #ifdef USE_JSON " JSON support is present\n" #else " no JSON support (needs EPICS Base >=3.15.0.1)\n"; #endif ); fprintf (stderr, "\nExamples:\n" "\n" " pvput double01 1.234\n" "equivalent to:\n" " pvput double01 value=1.234\n" ); #ifdef USE_JSON fprintf (stderr, "\n" "Field values may be given with JSON syntax. eg. and array:\n" "\n" " pvput arr:pv \"[1.0, 2.0]\"\n" "equivalent to:\n" " pvput arr:pv value=\"[1.0, 2.0]\"\n"); #endif } } void printValue(std::string const & channelName, PVStructure::const_shared_pointer const & pv) { if (mode == ValueOnlyMode) { PVField::const_shared_pointer value = pv->getSubField("value"); if (value.get() == 0) { std::cerr << "no 'value' field" << std::endl; std::cout << std::endl << *(pv.get()) << std::endl << std::endl; } else { Type valueType = value->getField()->getType(); if (valueType != scalar && valueType != scalarArray) { // special case for enum if (valueType == structure) { PVStructure::const_shared_pointer pvStructure = TR1::static_pointer_cast(value); if (pvStructure->getStructure()->getID() == "enum_t") { if (fieldSeparator == ' ') std::cout << std::setw(30) << std::left << channelName; else std::cout << channelName; std::cout << fieldSeparator; printEnumT(std::cout, pvStructure); std::cout << std::endl; return; } } // switch to structure mode std::cout << channelName << std::endl << *(pv.get()) << std::endl << std::endl; } else { if (fieldSeparator == ' ' && value->getField()->getType() == scalar) std::cout << std::setw(30) << std::left << channelName; else std::cout << channelName; std::cout << fieldSeparator; terse(std::cout, value) << std::endl; } } } else if (mode == TerseMode) terseStructure(std::cout, pv) << std::endl; else std::cout << std::endl << *(pv.get()) << std::endl << std::endl; } void early(const char *inp, unsigned pos) { fprintf(stderr, "Unexpected end of input: %s\n", inp); throw std::runtime_error("Unexpected end of input"); } // rudimentory parser for json array // needed as long as Base < 3.15 is supported. // for consistency, used with all version void jarray(shared_vector& out, const char *inp) { assert(inp[0]=='['); const char * const orig = inp; inp++; while(true) { // starting a new token for(; *inp==' '; inp++) {} // skip leading whitespace if(*inp=='\0') early(inp, inp-orig); if(isalnum(*inp) || *inp=='+' || *inp=='-') { // number const char *start = inp; while(isalnum(*inp) || *inp=='.' || *inp=='+' || *inp=='-') inp++; if(*inp=='\0') early(inp, inp-orig); // inp points to first char after token out.push_back(std::string(start, inp-start)); } else if(*inp=='"') { // quoted string const char *start = ++inp; // skip quote while(*inp!='\0' && *inp!='"') inp++; if(*inp=='\0') early(inp, inp-orig); // inp points to trailing " out.push_back(std::string(start, inp-start)); inp++; // skip trailing " } else if(*inp==']') { // no-op } else { fprintf(stderr, "Unknown token '%c' in \"%s\"", *inp, inp); throw std::runtime_error("Unknown token"); } for(; *inp==' '; inp++) {} // skip trailing whitespace if(*inp==',') inp++; else if(*inp==']') break; else { fprintf(stderr, "Unknown token '%c' in \"%s\"", *inp, inp); throw std::runtime_error("Unknown token"); } } std::cerr<<"jarray "< KV_t; typedef std::vector pairs_t; pairs_t pairs; shared_vector jarr; virtual void putBuild(const epics::pvData::StructureConstPtr& build, Args& args) { PVStructurePtr root(getPVDataCreate()->createPVStructure(build)); if(pairs.empty()) { std::istringstream strm(jblob); parseJSON(strm, root, &args.tosend); } else { for(pairs_t::const_iterator it=pairs.begin(), end=pairs.end(); it!=end; ++it) { PVFieldPtr fld(root->getSubField(it->first)); if(!fld) { fprintf(stderr, "%s : Warning: no such field\n", it->first.c_str()); // ignore } else if(it->second[0]=='[') { shared_vector arr; jarray(arr, it->second.c_str()); PVScalarArray* afld(dynamic_cast(fld.get())); if(!afld) { fprintf(stderr, "%s : Error not a scalar array field\n", it->first.c_str()); throw std::runtime_error("Not a scalar array field"); } afld->putFrom(freeze(arr)); args.tosend.set(afld->getFieldOffset()); } else if(it->second[0]=='{' || it->second[0]=='[') { std::istringstream strm(it->second); parseJSON(strm, fld, &args.tosend); } else { PVScalarPtr sfld(std::tr1::dynamic_pointer_cast(fld)); if(!sfld) { fprintf(stderr, "%s : Error: need a scalar field\n", it->first.c_str()); } else { sfld->putFrom(it->second); args.tosend.set(sfld->getFieldOffset()); } } } } args.root = root; if(debug) std::cout<<"To be sent: "< G(lock); result = evt.event; message = evt.message; done = true; } wait.signal(); } }; } // namespace int main (int argc, char *argv[]) { int opt; /* getopt() current option */ bool quiet = false; istream* inputStream = 0; ifstream ifs; bool fromStream = false; setvbuf(stdout,NULL,_IOLBF,BUFSIZ); /* Set stdout to line buffering */ putenv(const_cast("POSIXLY_CORRECT=")); /* Behave correct on GNU getopt systems; e.g. handle negative numbers */ while ((opt = getopt(argc, argv, ":hvr:w:tp:qdF:f:ns")) != -1) { switch (opt) { case 'h': /* Print usage */ usage(true); return 0; case 'v': /* Print version */ { Version version("pvput", "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. ('pvput -h' for help.)\n", optarg); timeOut = DEFAULT_TIMEOUT; } break; case 'r': /* Set PVA timeout value */ request = optarg; // do not override terse mode if (mode == ValueOnlyMode) mode = StructureMode; break; case 't': /* Terse mode */ mode = TerseMode; break; case 'd': /* Debug log level */ debug = true; break; case 'p': /* Set default provider */ defaultProvider = optarg; break; case 'q': /* Quiet mode */ quiet = 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 's': enumMode = StringEnum; break; case '?': fprintf(stderr, "Unrecognized option: '-%c'. ('pvput -h' for help.)\n", optopt); return 1; case ':': fprintf(stderr, "Option '-%c' requires an argument. ('pvput -h' for help.)\n", optopt); return 1; default : usage(); return 1; } } if (argc <= optind) { fprintf(stderr, "No pv name specified. ('pvput -h' for help.)\n"); return 1; } string pv = argv[optind++]; URI uri; bool validURI = URI::parse(pv, uri); string providerName(defaultProvider); string pvName(pv); string address(noAddress); if (validURI) { if (uri.path.length() <= 1) { std::cerr << "invalid URI '" << pv << "', empty path" << std::endl; return 1; } providerName = uri.protocol; pvName = uri.path.substr(1); address = uri.host; } int nVals = argc - optind; /* Remaining arg list are PV names */ if (nVals > 0) { // do not allow reading file and command line specified pvs fromStream = false; } else if (nVals < 1 && !fromStream) { fprintf(stderr, "No value(s) specified. ('pvput -h' for help.)\n"); return 1; } vector values; if (fromStream) { string cn; while (true) { *inputStream >> cn; if (!(*inputStream)) break; values.push_back(cn); } } else { // copy values from command line for (int n = 0; optind < argc; n++, optind++) values.push_back(argv[optind]); } if(values.empty()) { usage(); fprintf(stderr, "\nNo values provided\n"); return 1; } Putter thework; if(values[0][0]=='{' && values.size()>1) { usage(); fprintf(stderr, "\nMissing quotes around JSON?\n"); return 1; } else if(values[0][0]=='{') { // write entire blob #ifndef USE_JSON fprintf(stderr, "JSON syntax not supported by this build.\n"); return 1; #endif thework.jblob = values[0]; } else { for(size_t i=0, N=values.size(); i G(thework.lock); while(!thework.done) { epicsGuardRelease U(G); if(!thework.wait.wait(timeOut)) { fprintf(stderr, "Put timeout\n"); return 1; } } } if(thework.result==pvac::PutEvent::Fail) { fprintf(stderr, "Error: %s\n", thework.message.c_str()); } if (mode != TerseMode && !quiet) { std::cout << "New : "; } printValue(pvName, chan.get(timeOut, pvRequest)); return thework.result!=pvac::PutEvent::Success; }