/** * Copyright - See the COPYRIGHT that is included with this distribution. * pvAccessCPP is distributed subject to a Software License Agreement found * in file LICENSE that is included with this distribution. */ /* A counter which only ticks if at least one client is connected to it. * * Also, the type changes (toggles between int and real) each time that * the first client connects. */ #include #include #include #include #if !defined(_WIN32) #include #define USE_SIGNAL #endif #include #include #include #include #include #include #include #include #include namespace pvd = epics::pvData; namespace pva = epics::pvAccess; typedef epicsGuard Guard; typedef epicsGuardRelease UnGuard; namespace { epicsEvent done; #ifdef USE_SIGNAL void alldone(int num) { (void)num; done.signal(); } #endif // for demonstration purposes, we will switch between two different types. static pvd::StructureConstPtr int_type(pvd::getFieldCreate()->createFieldBuilder() ->add("value", pvd::pvULong) ->createStructure()); static pvd::StructureConstPtr flt_type(pvd::getFieldCreate()->createFieldBuilder() ->add("value", pvd::pvDouble) ->createStructure()); struct Counter : public pvas::SharedPV::Handler, public pvd::TimerCallback, public pva::Destroyable { POINTER_DEFINITIONS(Counter); // our name, internally only for logging. // The searchable channel name is given to the StaticProvider const std::string name; const pvas::SharedPV::weak_pointer pv; pvd::Timer& timer_queue; // const after build() weak_pointer internal_self; mutable epicsMutex mutex; bool queued; // are we in the Timer queue? pvd::uint64 count; bool typesel; pvd::PVStructurePtr scratch; pvd::PVScalarPtr scratch_value; static Counter::shared_pointer build(const pvas::SharedPV::shared_pointer& pv, const std::string& name, pvd::Timer& timer_queue) { Counter::shared_pointer internal(new Counter(pv, name, timer_queue)), external(internal.get(), pva::Destroyable::cleaner(internal)); // we give out internal ref (to Timer) internal->internal_self = internal; // SharedPV keeps us alive. // destroy() is called when SharedPV is destroyed (or Handler is replace) pv->setHandler(external); return external; } Counter(const pvas::SharedPV::shared_pointer& pv, const std::string& name, pvd::Timer& timer_queue) :name(name) ,pv(pv) ,timer_queue(timer_queue) ,queued(false) ,count(0u) ,typesel(false) {} virtual ~Counter() { printf("%s: destroy\n", name.c_str()); } virtual void destroy() OVERRIDE FINAL { { Guard G(mutex); if(!queued) return; queued = false; } printf("%s: shutdown\n", name.c_str()); timer_queue.cancel(shared_pointer(internal_self)); } // when we go from zero clients connected to more than one client connected. virtual void onFirstConnect(const pvas::SharedPV::shared_pointer& pv) OVERRIDE FINAL { { Guard G(mutex); assert(!queued); queued = true; } printf("%s: starting\n", name.c_str()); // timer first expires after 1 second, then again every second. // so any operation (including pvinfo) will take 1 second. timer_queue.schedulePeriodic(shared_pointer(internal_self), 1.0, 1.0); } // timer expires virtual void callback() OVERRIDE FINAL { bool open; pvd::uint64 next; pvd::PVStructurePtr top; pvd::BitSet vmask; { Guard G(mutex); if(!queued) return; open = !scratch; if(open) { // first expiration after onFirstConnect() // select type. pvd::StructureConstPtr type = typesel ? int_type : flt_type; typesel = !typesel; scratch = pvd::getPVDataCreate()->createPVStructure(type); scratch_value = scratch->getSubFieldT("value"); } // store counter value next = count++; scratch_value->putFrom(next); vmask.set(scratch_value->getFieldOffset()); // We will use the PVStructure when the lock is not held. // This is safe as it is only modified from this (Timer) // thread. top = scratch; } pvas::SharedPV::shared_pointer pv(this->pv); if(open) { // go from closed -> open. // provide initial value (and new type) printf("%s: open %llu\n", name.c_str(), (unsigned long long)next); pv->open(*top, vmask); } else { // post update printf("%s: tick %llu\n", name.c_str(), (unsigned long long)next); pv->post(*top, vmask); } } virtual void timerStopped() OVERRIDE FINAL {} // when we go from 1 client connected to zero clients connected. virtual void onLastDisconnect(const pvas::SharedPV::shared_pointer& pv) OVERRIDE FINAL { bool close; bool cancel; { Guard G(mutex); cancel = queued; queued = false; close = !!scratch; scratch.reset(); scratch_value.reset(); } // !close implies only all clients disconnect before timer expires the first time if(close) { printf("%s: close\n", name.c_str()); pv->close(); } if(cancel) { timer_queue.cancel(shared_pointer(internal_self)); } printf("%s: stopping\n", name.c_str()); } }; } //namespace int main(int argc, char *argv[]) { try { if(argc<=1) { fprintf(stderr, "Usage: %s ...\n", argv[0]); return 1; } pvd::Timer timer_queue("counters", (pvd::ThreadPriority)epicsThreadPriorityMedium); pvas::StaticProvider provider("counters"); // provider name "counters" is arbitrary for(int i=1; iprintInfo(); printf("Running with counters\n"); done.wait(); timer_queue.close(); // joins timer worker } catch(std::exception& e){ std::cerr<<"Error: "<