ChimeraTK-DeviceAccess-DoocsBackend  01.09.02
DoocsBackendRegisterAccessor.h
Go to the documentation of this file.
1 #ifndef CHIMERATK_DOOCS_BACKEND_REGISTER_ACCESSOR_H
2 #define CHIMERATK_DOOCS_BACKEND_REGISTER_ACCESSOR_H
3 #include "DoocsBackend.h"
4 #include "EventIdMapper.h"
5 #include "RegisterInfo.h"
7 #include <doocs/EqCall.h>
8 #include <eq_errors.h>
9 #include <eq_fct.h>
10 #include <type_traits>
11 
12 #include <ChimeraTK/AccessMode.h>
13 #include <ChimeraTK/Exception.h>
14 #include <ChimeraTK/FixedPointConverter.h>
15 #include <ChimeraTK/MappedImage.h>
16 #include <ChimeraTK/NDRegisterAccessor.h>
17 #include <ChimeraTK/RegisterPath.h>
18 
19 #include <mutex>
20 #include <queue>
21 
22 namespace ChimeraTK {
23 
26  public:
28  std::string _path;
29 
31  doocs::EqAdr ea;
32 
34  doocs::EqCall eq;
35 
37  doocs::EqData src, dst;
38 
40  bool isArray{false};
41 
43  size_t nElements{0};
44 
46  size_t elementOffset{0};
47 
49  bool isPartial{false};
50 
52  bool useZMQ{false};
53 
56  bool isActiveZMQ{false};
57 
59  cppext::future_queue<doocs::EqData> notifications;
60 
62  bool shutdownCalled{false};
63 
65  boost::shared_ptr<DoocsBackend> _backend;
66 
67  protected:
69  doocs::EventId _lastEventId;
70  };
71 
72  /********************************************************************************************************************/
73 
74  template<typename UserType>
75  class DoocsBackendRegisterAccessor : public DoocsBackendRegisterAccessorBase, public NDRegisterAccessor<UserType> {
76  public:
78 
84  void shutdown() {
85  if(useZMQ) {
87  }
88  shutdownCalled = true;
89  }
90 
91  void doReadTransferSynchronously() override;
92 
93  bool doWriteTransfer(VersionNumber) override {
95  return false;
96  }
97 
98  void doPreRead(TransferType) override {
99  if(!_backend->isOpen()) throw ChimeraTK::logic_error("Read operation not allowed while device is closed.");
100  if(!isReadable()) throw ChimeraTK::logic_error("Try to read from write-only register \"" + _path + "\".");
101  }
102 
103  void doPreWrite(TransferType, VersionNumber) override {
104  if(!_backend->isOpen()) throw ChimeraTK::logic_error("Write operation not allowed while device is closed.");
105  if(!isWriteable()) throw ChimeraTK::logic_error("Try to write read-only register \"" + _path + "\".");
106  }
107 
108  void doPostRead(TransferType, bool hasNewData) override {
109  if(!hasNewData) return;
110 
111  // Note: the original idea was to extract the time stamp from the received data. This idea has been dropped since
112  // the time stamp attached to the data seems to be unreliably, at least for the x2timer macro pulse number. If the
113  // unreliable time stamp is attached to the trigger, all data will get this time stamp. This leads to error
114  // messages of the DOOCS history archiver, which rejects data due to wrong time stamps. Hence we better generate
115  // our own time stamp here.
116 
117  // See spec. B.1.3.4.2
118  TransferElement::setDataValidity(dst.error() == 0 ? DataValidity::ok : DataValidity::faulty);
119 
120  // If the eventid is valid (!= 0) but older than the last one, we have backward running eventids but VersionNumber
121  // must not run backwards. Keep VersionNumber unchanged and return. See spec B.1.3.2. (Note: after a re-connection
122  // to a slow variable the version number might be the same)
123  if(dst.get_event_id() != doocs::EventId(0) && dst.get_event_id() < _lastEventId) {
124  return;
125  }
126 
127  if(dst.get_event_id() == doocs::EventId()) {
128  // See spec. B.1.3.4.1
129  TransferElement::_versionNumber = {};
130  _lastEventId = dst.get_event_id();
131  return;
132  }
133 
134  // Only check for a new version number if the event ids no not match up.
135  // Otherwise it might be possible that the EventIDMapper allready removed the old event id from
136  // its cache and will generate a new versionnumber for the same event id.
137  // This prevents not updating slow- if ever changing variables that are polled periodically
138  //
139  // During startup, we can receive multiple receives with event_id == 0. The first check ensures that
140  // we do not hand out the VersionNumber{nullptr} then
141  // if(_lastEventId == doocs::EventId() || _lastEventId != dst.get_event_id()) {
142  // Get VersionNumber from the EventIdMapper. See spec B.1.3.3.
143  auto newVersionNumber = EventIdMapper::getInstance().getVersionForEventId(dst.get_event_id());
144 
145  // Minimum version is _backend->_startVersion. See spec. B.1.3.3.1.
146  auto startVersion = _backend->getStartVersion();
147  if(newVersionNumber < startVersion) {
148  newVersionNumber = startVersion;
149  }
150 
151  // Version still must not go backwards. See spec B.1.3.3.2.
152  // If the sender is sending a valid eventid, afterwards sends eventid 0, and after that sends
153  // same eventid again, then the last sent version in this transfer element has been increased
154  // by eventid 0 and now newVersionNumber which is coming from the EventIDMapper is older.
155  if(newVersionNumber < TransferElement::_versionNumber) {
156  return;
157  }
158 
159  // See spec. B.1.3.4.1
160  TransferElement::_versionNumber = newVersionNumber;
161  _lastEventId = dst.get_event_id();
162  }
163 
164  bool isReadOnly() const override { return isReadable() && not isWriteable(); }
165 
166  bool isReadable() const override { return _isReadable; }
167 
168  bool isWriteable() const override { return _isWriteable; }
169 
170  using TransferElement::_readQueue;
171 
172  bool mayReplaceOther(const boost::shared_ptr<TransferElement const>& other) const override {
173  auto rhsCasted = boost::dynamic_pointer_cast<const DoocsBackendRegisterAccessor<UserType>>(other);
174  if(!rhsCasted) return false;
175  if(_path != rhsCasted->_path) return false;
176  if(nElements != rhsCasted->nElements) return false;
177  if(elementOffset != rhsCasted->elementOffset) return false;
178  return true;
179  }
180 
181  std::vector<boost::shared_ptr<TransferElement>> getHardwareAccessingElements() override {
182  return {boost::enable_shared_from_this<TransferElement>::shared_from_this()};
183  }
184 
185  std::list<boost::shared_ptr<ChimeraTK::TransferElement>> getInternalElements() override { return {}; }
186 
187  void replaceTransferElement(boost::shared_ptr<TransferElement> /*newElement*/) override {} // LCOV_EXCL_LINE
188 
189  void interrupt() override { this->interrupt_impl(this->notifications); }
190 
191  protected:
192  DoocsBackendRegisterAccessor(boost::shared_ptr<DoocsBackend> backend, const std::string& path,
193  const std::string& registerPathName, size_t numberOfWords, size_t wordOffsetInRegister, AccessModeFlags flags);
194 
196  void write_internal();
197 
204  void initialise(const DoocsBackendRegisterInfo& info);
205 
208  };
209 
210  /********************************************************************************************************************/
211  /********************************************************************************************************************/
213  /********************************************************************************************************************/
214  /********************************************************************************************************************/
215 
216  template<typename UserType>
218  size_t actualLength = 0;
219  int typeId = 0;
220 
221  // Try to read data (if the device is opened), to obtain type and size of the register. Otherwise take information
222  // from catalogue (-> cache)
223  int rc = 1;
224  if(_backend->isOpen()) {
225  doocs::EqData tmp;
226  rc = eq.get(&ea, &tmp, &dst);
227  }
228  if(rc) {
229  if(rc == eq_errors::ill_property || rc == eq_errors::ill_location || rc == eq_errors::ill_address) {
230  // no property by that name
231  throw ChimeraTK::logic_error("Property does not exist: " + _path);
232  }
233 
234  // we cannot reach the server, so try to obtain information from the catalogue
235  actualLength = info.getNumberOfElements();
236  typeId = info.doocsTypeId;
237  }
238  else {
239  // obtain number of elements from server reply
240  actualLength = dst.array_length();
241  if(actualLength == 0 && dst.length() == 1) {
242  actualLength = 1;
243  }
244  else {
245  if(actualLength == 0) actualLength = dst.length();
246  }
247  typeId = dst.type();
248  if(typeId == DATA_IMAGE) {
249  // RegisterAccessor byte array needs to store header and body
250  actualLength += sizeof(ChimeraTK::ImgHeader);
251  }
252  }
253 
254  // strings report number of characters, not number of strings..
255  if(typeId == DATA_TEXT || typeId == DATA_STRING) {
256  actualLength = 1;
257  }
258 
259  if(actualLength > 1) {
260  isArray = true;
261  }
262  else {
263  isArray = false;
264  }
265 
266  if(nElements == 0) {
267  nElements = actualLength;
268  }
269  if(nElements + elementOffset > actualLength) {
270  // for image type, we allow having a byte array larger than required
271  if(typeId != DATA_IMAGE) {
272  throw ChimeraTK::logic_error("Requested number of words exceeds the length of the DOOCS property!");
273  }
274  }
275  if(nElements == actualLength && elementOffset == 0) {
276  isPartial = false;
277  }
278  else {
279  isPartial = true;
280  }
281 
282  // allocate buffers
283  NDRegisterAccessor<UserType>::buffer_2D.resize(1);
284  NDRegisterAccessor<UserType>::buffer_2D[0].resize(nElements);
285 
286  // set proper type information in the source doocs::EqData
287  src.set_type(typeId);
288  if(typeId == DATA_IMAGE) {
289  // DOOCS data structure has its own image header format and length() is only used for body length
290  src.length(actualLength - sizeof(ChimeraTK::ImgHeader));
291  }
292  else if(typeId != DATA_IIII) {
293  src.length(actualLength);
294  }
295 
296  // use ZeroMQ with AccessMode::wait_for_new_data
297  if(useZMQ) {
298  // subscribe via subscription manager
300  }
301  }
302 
303  /********************************************************************************************************************/
304 
305  template<typename UserType>
307  const std::string& path, const std::string& registerPathName, size_t numberOfWords, size_t wordOffsetInRegister,
308  AccessModeFlags flags)
309  : NDRegisterAccessor<UserType>(path, flags), _isReadable(true), _isWriteable(true) {
310  try {
311  _backend = backend;
312  _path = path;
313  elementOffset = wordOffsetInRegister;
314  nElements = numberOfWords;
315  useZMQ = false;
316 
317  // check for unknown access mode flags
318  flags.checkForUnknownFlags({AccessMode::wait_for_new_data});
319 
320  // set address
321  ea.adr(path);
322 
323  // obtain catalogue entry
324  auto info = backend->getBackendRegisterCatalogue().getBackendRegister(registerPathName);
325 
326  // use zero mq subscriptiopn?
327  if(flags.has(AccessMode::wait_for_new_data)) {
328  if(!info.getSupportedAccessModes().has(AccessMode::wait_for_new_data)) {
329  throw ChimeraTK::logic_error("invalid access mode for this register");
330  }
331  useZMQ = true;
332 
333  // Create notification queue.
334  notifications = cppext::future_queue<doocs::EqData>(3);
335  _readQueue = notifications.then<void>([this](doocs::EqData& data) { this->dst = data; }, std::launch::deferred);
336  }
337 
338  initialise(info);
339  }
340  catch(...) {
341  this->shutdown();
342  throw;
343  }
344  }
345 
346  /********************************************************************************************************************/
347 
348  template<typename UserType>
350  assert(shutdownCalled);
351  }
352 
353  /********************************************************************************************************************/
354 
355  template<typename UserType>
357  if(!_backend->isFunctional()) {
358  throw ChimeraTK::runtime_error(std::string("Exception reported by another accessor."));
359  }
360 
361  assert(!useZMQ);
362 
363  boost::this_thread::interruption_point();
364 
365  // read data
366  doocs::EqData tmp;
367  int rc = eq.get(&ea, &tmp, &dst);
368 
369  // check error
370  if(rc && DoocsBackend::isCommunicationError(dst.error())) {
371  _backend->informRuntimeError(_path);
372  throw ChimeraTK::runtime_error(std::string("Cannot read from DOOCS property: ") + dst.get_string());
373  }
374  }
375 
376  /********************************************************************************************************************/
377 
378  template<typename UserType>
380  if(!_backend->isFunctional()) {
381  throw ChimeraTK::runtime_error(std::string("Exception reported by another accessor."));
382  }
383 
384  // write data
385  int rc = eq.set(&ea, &src, &dst);
386  // check error
387  if(rc && (DoocsBackend::isCommunicationError(dst.error()) || (dst.error() == eq_errors::read_only))) {
388  _backend->informRuntimeError(_path);
389  if(dst.error() == eq_errors::read_only) {
390  this->_isWriteable = false;
391  }
392  throw ChimeraTK::runtime_error(std::string("Cannot write to DOOCS property: ") + dst.get_string());
393  }
394  }
395 
396  /********************************************************************************************************************/
397 
398 } // namespace ChimeraTK
399 
400 #endif /* CHIMERATK_DOOCS_BACKEND_REGISTER_ACCESSOR_H */
DoocsBackendRegisterInfo::getNumberOfElements
unsigned int getNumberOfElements() const override
Definition: RegisterInfo.h:25
ChimeraTK::DoocsBackendRegisterAccessor
Definition: DoocsBackendRegisterAccessor.h:75
ChimeraTK::DoocsBackendRegisterAccessorBase::isPartial
bool isPartial
flag if the accessor should affect only a part of the property (in case of an array)
Definition: DoocsBackendRegisterAccessor.h:49
ZMQSubscriptionManager.h
ChimeraTK::DoocsBackendRegisterAccessorBase::dst
doocs::EqData dst
Definition: DoocsBackendRegisterAccessor.h:37
ChimeraTK::DoocsBackendRegisterAccessorBase::elementOffset
size_t elementOffset
element offset specified by the user
Definition: DoocsBackendRegisterAccessor.h:46
ChimeraTK::DoocsBackendRegisterAccessorBase::eq
doocs::EqCall eq
DOOCS rpc call object.
Definition: DoocsBackendRegisterAccessor.h:34
ChimeraTK::DoocsBackendRegisterAccessorBase::isArray
bool isArray
flag if the DOOCS data type is an array or not
Definition: DoocsBackendRegisterAccessor.h:40
ChimeraTK::DoocsBackendNamespace::ZMQSubscriptionManager::unsubscribe
void unsubscribe(const std::string &path, DoocsBackendRegisterAccessorBase *accessor)
Unregister accessor subscription.
Definition: ZMQSubscriptionManager.cc:96
ChimeraTK::DoocsBackendRegisterAccessorBase::shutdownCalled
bool shutdownCalled
Flag whether shutdown() has been called or not.
Definition: DoocsBackendRegisterAccessor.h:62
ChimeraTK::DoocsBackendRegisterAccessorBase
This is the untemplated base class which unifies all data members not depending on the UserType.
Definition: DoocsBackendRegisterAccessor.h:25
ChimeraTK::DoocsBackendRegisterAccessor::interrupt
void interrupt() override
Definition: DoocsBackendRegisterAccessor.h:189
ChimeraTK::DoocsBackendRegisterAccessor::getHardwareAccessingElements
std::vector< boost::shared_ptr< TransferElement > > getHardwareAccessingElements() override
Definition: DoocsBackendRegisterAccessor.h:181
ChimeraTK::DoocsBackendRegisterAccessorBase::_path
std::string _path
register path
Definition: DoocsBackendRegisterAccessor.h:28
ChimeraTK::DoocsBackendRegisterAccessor::DoocsBackendRegisterAccessor
DoocsBackendRegisterAccessor(boost::shared_ptr< DoocsBackend > backend, const std::string &path, const std::string &registerPathName, size_t numberOfWords, size_t wordOffsetInRegister, AccessModeFlags flags)
Definition: DoocsBackendRegisterAccessor.h:306
ChimeraTK::DoocsBackendRegisterAccessor::_isReadable
bool _isReadable
Definition: DoocsBackendRegisterAccessor.h:206
DoocsBackendRegisterInfo::doocsTypeId
int doocsTypeId
Definition: RegisterInfo.h:43
ChimeraTK::DoocsBackendRegisterAccessorBase::nElements
size_t nElements
number of elements
Definition: DoocsBackendRegisterAccessor.h:43
ChimeraTK::DoocsBackendRegisterAccessorBase::useZMQ
bool useZMQ
flag if a ZeroMQ subscribtion is used for reading data (c.f. AccessMode::wait_for_new_data)
Definition: DoocsBackendRegisterAccessor.h:52
ChimeraTK::DoocsBackendRegisterAccessor::mayReplaceOther
bool mayReplaceOther(const boost::shared_ptr< TransferElement const > &other) const override
Definition: DoocsBackendRegisterAccessor.h:172
ChimeraTK::DoocsBackendRegisterAccessor::doWriteTransfer
bool doWriteTransfer(VersionNumber) override
Definition: DoocsBackendRegisterAccessor.h:93
EventIdMapper::getVersionForEventId
ChimeraTK::VersionNumber getVersionForEventId(const doocs::EventId &eventId)
Definition: EventIdMapper.cc:7
ChimeraTK::DoocsBackendRegisterAccessorBase::isActiveZMQ
bool isActiveZMQ
flag whether it should receive updates from the ZeroMQ subscription.
Definition: DoocsBackendRegisterAccessor.h:56
ChimeraTK::DoocsBackendRegisterAccessor::doPostRead
void doPostRead(TransferType, bool hasNewData) override
Definition: DoocsBackendRegisterAccessor.h:108
ChimeraTK::DoocsBackendRegisterAccessor::isWriteable
bool isWriteable() const override
Definition: DoocsBackendRegisterAccessor.h:168
ChimeraTK::DoocsBackendRegisterAccessor::isReadable
bool isReadable() const override
Definition: DoocsBackendRegisterAccessor.h:166
ChimeraTK::DoocsBackendRegisterAccessorBase::notifications
cppext::future_queue< doocs::EqData > notifications
future_queue used to notify the TransferFuture about completed transfers
Definition: DoocsBackendRegisterAccessor.h:59
ChimeraTK::DoocsBackendNamespace::ZMQSubscriptionManager::getInstance
static ZMQSubscriptionManager & getInstance()
Definition: ZMQSubscriptionManager.h:25
ChimeraTK::DoocsBackendRegisterAccessorBase::ea
doocs::EqAdr ea
DOOCS address structure.
Definition: DoocsBackendRegisterAccessor.h:31
RegisterInfo.h
ChimeraTK::DoocsBackendRegisterAccessorBase::src
doocs::EqData src
DOOCS data structures.
Definition: DoocsBackendRegisterAccessor.h:37
ChimeraTK::DoocsBackendRegisterAccessor::write_internal
void write_internal()
internal write from doocs::EqData src
Definition: DoocsBackendRegisterAccessor.h:379
ChimeraTK::DoocsBackendRegisterAccessor::doPreWrite
void doPreWrite(TransferType, VersionNumber) override
Definition: DoocsBackendRegisterAccessor.h:103
DoocsBackend.h
ChimeraTK::DoocsBackendRegisterAccessor::shutdown
void shutdown()
All implementations must call this function in their destructor.
Definition: DoocsBackendRegisterAccessor.h:84
EventIdMapper.h
ChimeraTK::DoocsBackendRegisterAccessor::isReadOnly
bool isReadOnly() const override
Definition: DoocsBackendRegisterAccessor.h:164
EventIdMapper::getInstance
static EventIdMapper & getInstance()
Definition: EventIdMapper.h:12
ChimeraTK::DoocsBackendRegisterAccessor::replaceTransferElement
void replaceTransferElement(boost::shared_ptr< TransferElement >) override
Definition: DoocsBackendRegisterAccessor.h:187
ChimeraTK::DoocsBackendRegisterAccessor::_isWriteable
bool _isWriteable
Definition: DoocsBackendRegisterAccessor.h:207
ChimeraTK::DoocsBackendRegisterAccessor::doReadTransferSynchronously
void doReadTransferSynchronously() override
Definition: DoocsBackendRegisterAccessor.h:356
ChimeraTK::DoocsBackendRegisterAccessorBase::_lastEventId
doocs::EventId _lastEventId
first valid eventId
Definition: DoocsBackendRegisterAccessor.h:69
DoocsBackendRegisterInfo
Definition: RegisterInfo.h:21
ChimeraTK::DoocsBackendRegisterAccessor::getInternalElements
std::list< boost::shared_ptr< ChimeraTK::TransferElement > > getInternalElements() override
Definition: DoocsBackendRegisterAccessor.h:185
ChimeraTK
Definition: spec_DoocsBackend.dox:2
ChimeraTK::DoocsBackendRegisterAccessor::doPreRead
void doPreRead(TransferType) override
Definition: DoocsBackendRegisterAccessor.h:98
ChimeraTK::DoocsBackendRegisterAccessor::initialise
void initialise(const DoocsBackendRegisterInfo &info)
Perform initialisation (i.e.
Definition: DoocsBackendRegisterAccessor.h:217
ChimeraTK::DoocsBackendRegisterAccessor::~DoocsBackendRegisterAccessor
~DoocsBackendRegisterAccessor() override
Definition: DoocsBackendRegisterAccessor.h:349
ChimeraTK::DoocsBackendNamespace::ZMQSubscriptionManager::subscribe
void subscribe(const std::string &path, DoocsBackendRegisterAccessorBase *accessor)
Register accessor subscription.
Definition: ZMQSubscriptionManager.cc:36
ChimeraTK::DoocsBackendRegisterAccessorBase::_backend
boost::shared_ptr< DoocsBackend > _backend
Pointer to the backend.
Definition: DoocsBackendRegisterAccessor.h:65