ChimeraTK-DeviceAccess-DoocsBackend  01.09.02
DoocsBackend.cc
Go to the documentation of this file.
1 /*
2  * DoocsBackend.cc
3  *
4  * Created on: Apr 26, 2016
5  * Author: Martin Hierholzer
6  */
7 
8 #include "DoocsBackend.h"
9 
10 #include "CatalogueCache.h"
11 #include "CatalogueFetcher.h"
19 #include "RegisterInfo.h"
20 #include "StringUtility.h"
21 #include "ZMQSubscriptionManager.h"
22 
23 #include <ChimeraTK/BackendFactory.h>
24 #include <ChimeraTK/DeviceAccessVersion.h>
25 #include <ChimeraTK/TypeChangingDecorator.h>
26 
27 #include <boost/algorithm/string.hpp>
28 
29 #include <algorithm>
30 #include <fstream>
31 
32 // this is required since we link against the DOOCS libEqServer.so
33 const char* object_name = "DoocsBackend";
34 
35 namespace ctk = ChimeraTK;
36 
37 extern "C" {
38 boost::shared_ptr<ChimeraTK::DeviceBackend> ChimeraTK_DeviceAccess_createBackend(
39  std::string address, std::map<std::string, std::string> parameters) {
40  return ChimeraTK::DoocsBackend::createInstance(address, parameters);
41 }
42 
43 static std::vector<std::string> ChimeraTK_DeviceAccess_sdmParameterNames{"facility", "device", "location"};
44 
45 static std::string ChimeraTK_DeviceAccess_version{CHIMERATK_DEVICEACCESS_VERSION};
46 
47 static std::string backend_name = "doocs";
48 }
49 
50 static DoocsBackendRegisterCatalogue fetchCatalogue(
51  std::string serverAddress, std::string cacheFile, std::future<void> cancelFlag);
52 
53 /********************************************************************************************************************/
54 
55 static DoocsBackendRegisterCatalogue fetchCatalogue(
56  std::string serverAddress, std::string cacheFile, std::future<void> cancelFlag) {
57  auto result = CatalogueFetcher(serverAddress, std::move(cancelFlag)).fetch();
58  auto catalogue = std::move(result.first);
59  auto isCatalogueComplete = result.second;
60  bool isCacheFileNameSpecified = not cacheFile.empty();
61 
62  if(isCatalogueComplete && isCacheFileNameSpecified) {
63  Cache::saveCatalogue(catalogue, cacheFile);
64  }
65  return catalogue;
66 }
67 
68 namespace ChimeraTK {
69 
70  /********************************************************************************************************************/
71 
72  DoocsBackend::BackendRegisterer DoocsBackend::backendRegisterer;
73 
75  std::cout << "DoocsBackend::BackendRegisterer: registering backend type doocs" << std::endl;
76  ChimeraTK::BackendFactory::getInstance().registerBackendType(
77  "doocs", &DoocsBackend::createInstance, {"facility", "device", "location"});
78  }
79 
80  /********************************************************************************************************************/
81 
83  const std::string& serverAddress, const std::string& cacheFile, const std::string& updateCache)
84  : _serverAddress(serverAddress), _cacheFile(cacheFile) {
85  if(cacheFileExists() && isCachingEnabled()) {
86  // provide catalogue immediately from cache
87  catalogue = Cache::readCatalogue(_cacheFile);
88 
89  // update cache file in the background
90  if(updateCache == "1") {
91  std::thread(fetchCatalogue, serverAddress, cacheFile, _cancelFlag.get_future()).detach();
92  }
93  }
94  else {
95  // fill catalogue in the background (and save to cache if enabled)
96  _catalogueFuture =
97  std::async(std::launch::async, fetchCatalogue, serverAddress, cacheFile, _cancelFlag.get_future());
98  }
99 
100  // Reduce ZeroMQ timeout so inconsistencies get corrected more quickly. The downside is that DOOCS will do more
101  // frequent RPC polls on rarely changing ZeroMQ variables (every 10 seconds instead of every 4 minutes), but this
102  // is acceptable as it is still slow enough.
103  doocs::zmq_set_subscription_timeout(10);
104 
105  FILL_VIRTUAL_FUNCTION_TEMPLATE_VTABLE(getRegisterAccessor_impl);
106  }
107 
108  /********************************************************************************************************************/
109 
111  if(_catalogueFuture.valid()) {
112  try {
113  _cancelFlag.set_value(); // cancel fill catalogue async task
114  _catalogueFuture.get();
115  }
116  catch(...) {
117  // prevent throwing in destructor (ub if it does);
118  }
119  }
120  }
121 
122  /********************************************************************************************************************/
123 
124  bool DoocsBackend::cacheFileExists() {
125  if(_cacheFile.empty()) {
126  return false;
127  }
128  std::ifstream f(_cacheFile.c_str());
129  return f.good();
130  }
131 
132  /********************************************************************************************************************/
133 
134  bool DoocsBackend::isCachingEnabled() const {
135  return !_cacheFile.empty();
136  }
137 
138  /********************************************************************************************************************/
139 
140  boost::shared_ptr<DeviceBackend> DoocsBackend::createInstance(
141  std::string address, std::map<std::string, std::string> parameters) {
142  // if address is empty, build it from parameters (for compatibility with SDM)
143  if(address.empty()) {
144  RegisterPath serverAddress;
145  serverAddress /= parameters["facility"];
146  serverAddress /= parameters["device"];
147  serverAddress /= parameters["location"];
148  address = std::string(serverAddress).substr(1);
149  }
150  std::string cacheFile{};
151  std::string updateCache{"0"};
152  try {
153  cacheFile = parameters.at("cacheFile");
154  updateCache = parameters.at("updateCache");
155  }
156  catch(std::out_of_range&) {
157  // empty cacheFile string => no caching
158  // empty updateCache string => no cache update
159  }
160 
161  // create and return the backend
162  return boost::shared_ptr<DeviceBackend>(new DoocsBackend(address, cacheFile, updateCache));
163  }
164 
165  /********************************************************************************************************************/
166 
168  std::unique_lock<std::mutex> lk(_mxRecovery);
169  if(lastFailedAddress != "") {
170  // open() is called after a runtime_error: check if device is recovered.
171  doocs::EqAdr ea;
172  ea.adr(lastFailedAddress);
173  EqCall eq;
174  doocs::EqData src, dst;
175  int rc = eq.get(&ea, &src, &dst);
176  // if again error received, throw exception
177  if(rc && isCommunicationError(dst.error())) {
178  lk.unlock();
179  auto message = std::string("Cannot read from DOOCS property: ") + dst.get_string();
180  setException(message);
181  throw ChimeraTK::runtime_error(message);
182  }
183  lastFailedAddress = "";
184  }
185  _startVersion = {};
186  setOpenedAndClearException();
187  }
188 
189  /********************************************************************************************************************/
190 
191  RegisterCatalogue DoocsBackend::getRegisterCatalogue() const {
192  return RegisterCatalogue(getBackendRegisterCatalogue().clone());
193  }
194 
195  /********************************************************************************************************************/
196 
198  if(_catalogueFuture.valid()) {
199  catalogue = _catalogueFuture.get();
200  }
201  return catalogue;
202  }
203 
204  /********************************************************************************************************************/
205 
208  _opened = false;
209  _asyncReadActivated = false;
210  {
211  std::unique_lock<std::mutex> lk(_mxRecovery);
212 
213  lastFailedAddress = "";
214  }
215  }
216 
217  /********************************************************************************************************************/
218 
219  void DoocsBackend::informRuntimeError(const std::string& address) {
220  std::lock_guard<std::mutex> lk(_mxRecovery);
221  if(lastFailedAddress == "") {
222  lastFailedAddress = address;
223  }
224  }
225 
226  /********************************************************************************************************************/
227 
229  _asyncReadActivated = false;
231  }
232 
233  /********************************************************************************************************************/
234 
236  if(!isFunctional()) { // Spec TransferElement 8.5.4
237  return;
238  }
239  _asyncReadActivated = true;
241  }
242 
243  /********************************************************************************************************************/
244 
245  template<typename UserType>
246  boost::shared_ptr<NDRegisterAccessor<UserType>> DoocsBackend::getRegisterAccessor_impl(
247  const RegisterPath& registerPathName, size_t numberOfWords, size_t wordOffsetInRegister, AccessModeFlags flags) {
248  boost::shared_ptr<NDRegisterAccessor<UserType>> p;
249  std::string path = _serverAddress + registerPathName;
250 
251  // check for additional hierarchy level, which indicates an access to a field of a complex property data type
252  bool hasExtraLevel = false;
253  if(!boost::starts_with(path, "doocs://") && !boost::starts_with(path, "epics://")) {
254  size_t nSlashes = std::count(path.begin(), path.end(), '/');
255  if(nSlashes == 4) {
256  hasExtraLevel = true;
257  }
258  else if(nSlashes < 3 || nSlashes > 4) {
259  throw ChimeraTK::logic_error(std::string("DOOCS address has an illegal format: ") + path);
260  }
261  }
262  else if(boost::starts_with(path, "doocs://")) {
263  size_t nSlashes = std::count(path.begin(), path.end(), '/');
264  // we have 3 extra slashes compared to the standard syntax without "doocs:://"
265  if(nSlashes == 4 + 3) {
266  hasExtraLevel = true;
267  }
268  else if(nSlashes < 3 + 3 || nSlashes > 4 + 3) {
269  throw ChimeraTK::logic_error(std::string("DOOCS address has an illegal format: ") + path);
270  }
271  }
272 
273  // split the path into property name and field name
274  std::string field;
275  if(hasExtraLevel) {
276  field = path.substr(path.find_last_of('/') + 1);
277  path = path.substr(0, path.find_last_of('/'));
278  }
279 
280  // if backend is open, read property once to obtain type
281  int doocsTypeId = DATA_NULL;
282  if(isOpen()) {
283  doocs::EqAdr ea;
284  EqCall eq;
285  doocs::EqData src, dst;
286  ea.adr(path);
287  int rc = eq.get(&ea, &src, &dst);
288  if(rc) {
289  if(rc == eq_errors::ill_property || rc == eq_errors::ill_location ||
290  rc == eq_errors::ill_address) { // no property by that name
291  throw ChimeraTK::logic_error("Property does not exist: " + path + "': " + dst.get_string());
292  }
293  // runtime errors are thrown later
294  }
295  else {
296  doocsTypeId = dst.type();
297  }
298  }
299 
300  // if backend is closed, or if property could not be read, use the (potentially cached) catalogue
301  if(doocsTypeId == DATA_NULL) {
302  auto reg = getBackendRegisterCatalogue().getBackendRegister(registerPathName);
303  doocsTypeId = reg.doocsTypeId;
304  }
305 
306  // check type and create matching accessor
307  bool extraLevelUsed = false;
308  auto sharedThis = boost::static_pointer_cast<DoocsBackend>(shared_from_this());
309 
310  if(field == "eventId") {
311  extraLevelUsed = true;
312  p.reset(new DoocsBackendEventIdRegisterAccessor<UserType>(sharedThis, path, registerPathName, flags));
313  }
314  else if(field == "timeStamp") {
315  extraLevelUsed = true;
316  p.reset(new DoocsBackendTimeStampRegisterAccessor<UserType>(sharedThis, path, registerPathName, flags));
317  }
318  else {
319  switch(doocsTypeId) {
320  case DATA_BOOL:
321  case DATA_A_BOOL:
322  case DATA_SHORT:
323  case DATA_A_SHORT:
324  case DATA_USHORT:
325  case DATA_A_USHORT:
326  case DATA_INT:
327  case DATA_A_INT:
328  case DATA_UINT:
329  case DATA_A_UINT:
330  case DATA_LONG:
331  case DATA_A_LONG:
332  case DATA_ULONG:
333  case DATA_A_ULONG:
334  case DATA_FLOAT:
335  case DATA_SPECTRUM:
336  case DATA_GSPECTRUM:
337  case DATA_A_FLOAT:
338  case DATA_DOUBLE:
339  case DATA_A_DOUBLE:
341  sharedThis, path, registerPathName, numberOfWords, wordOffsetInRegister, flags));
342  break;
343 
344  case DATA_IIII:
346  sharedThis, path, registerPathName, numberOfWords, wordOffsetInRegister, flags));
347  break;
348 
349  case DATA_IFFF:
350  if(!hasExtraLevel) {
351  throw ChimeraTK::logic_error("DOOCS property of IFFF type '" + _serverAddress + registerPathName +
352  "' cannot be accessed as a whole.");
353  }
354  extraLevelUsed = true;
356  sharedThis, path, field, registerPathName, numberOfWords, wordOffsetInRegister, flags));
357  break;
358 
359  case DATA_TEXT:
360  case DATA_STRING:
362  sharedThis, path, registerPathName, numberOfWords, wordOffsetInRegister, flags));
363  break;
364 
365  case DATA_IMAGE: {
366  auto accImpl = new DoocsBackendImageRegisterAccessor(
367  sharedThis, path, registerPathName, numberOfWords, wordOffsetInRegister, flags);
368  if constexpr(std::is_same_v<UserType, std::uint8_t>) {
369  p.reset(accImpl);
370  }
371  else {
372  boost::shared_ptr<NDRegisterAccessor<std::uint8_t>> pImpl(accImpl);
373  // any UserType can hold uint8
374  boost::shared_ptr<NDRegisterAccessor<UserType>> accDecorated(
375  new TypeChangingRangeCheckingDecorator<UserType, std::uint8_t>(
376  boost::dynamic_pointer_cast<ChimeraTK::NDRegisterAccessor<std::uint8_t>>(pImpl)));
377  p = accDecorated;
378  }
379  break;
380  }
381 
382  default:
383  throw ChimeraTK::logic_error("Unsupported DOOCS data type " +
384  std::string(doocs::EqData().type_string(doocsTypeId)) + " of property '" + _serverAddress +
385  registerPathName + "'");
386  }
387  }
388 
389  // if the field name has been specified but the data type does not use it, throw an exception
390  if(hasExtraLevel && !extraLevelUsed) {
391  throw ChimeraTK::logic_error("Specifiaction of field name is not supported for the DOOCS data type " +
392  std::string(doocs::EqData().type_string(doocsTypeId)) + ": " + _serverAddress + registerPathName);
393  }
394 
395  p->setExceptionBackend(shared_from_this());
396  return p;
397  }
398  /********************************************************************************************************************/
399 
400  bool DoocsBackend::isCommunicationError(int doocs_error) {
401  // logic_error-like errors are caught at a different place. If such error appears later, a runtime_error need to
402  // be generated (device does not behave as expected, as it is not expected to change)
403  switch(doocs_error) {
404  case eq_errors::unsup_domain:
405  case eq_errors::no_ens_entry:
406  case eq_errors::ill_monitor:
407  case eq_errors::faulty_chans:
408  case eq_errors::unavail_serv:
409  case eq_errors::ill_serv:
410  case eq_errors::rpc_prot_error:
411  case eq_errors::ens_fault:
412  case eq_errors::ill_protocol:
413  case eq_errors::ill_location:
414  case eq_errors::ill_property:
415  case eq_errors::no_connection:
416  case eq_errors::conn_timeout:
417  case eq_errors::alias_error:
418  case eq_errors::no_permission:
419  return true;
420  default:
421  return false;
422  }
423  }
424 
425 } /* namespace ChimeraTK */
DoocsBackendImageRegisterAccessor.h
ChimeraTK::DoocsBackendNamespace::ZMQSubscriptionManager::activateAllListeners
void activateAllListeners(DoocsBackend *backend)
Activate all listeners for the given backend. Should be called from DoocsBackend::activateAsyncRead()...
Definition: ZMQSubscriptionManager.cc:188
ChimeraTK::DoocsBackend::getRegisterAccessor_impl
boost::shared_ptr< NDRegisterAccessor< UserType > > getRegisterAccessor_impl(const RegisterPath &registerPathName, size_t numberOfWords, size_t wordOffsetInRegister, AccessModeFlags flags)
Definition: DoocsBackend.cc:246
ChimeraTK::DoocsBackendImageRegisterAccessor
Definition: DoocsBackendImageRegisterAccessor.h:15
ZMQSubscriptionManager.h
ChimeraTK::DoocsBackend::_asyncReadActivated
std::atomic< bool > _asyncReadActivated
Definition: DoocsBackend.h:105
Cache::saveCatalogue
void saveCatalogue(const DoocsBackendRegisterCatalogue &c, const std::string &xmlfile)
Definition: CatalogueCache.cc:59
CatalogueFetcher.h
CatalogueCache.h
ChimeraTK::DoocsBackendEventIdRegisterAccessor
Definition: DoocsBackendEventIdAccessor.h:12
ChimeraTK::DoocsBackend::close
void close() override
Definition: DoocsBackend.cc:206
DoocsBackendStringRegisterAccessor.h
Cache::readCatalogue
DoocsBackendRegisterCatalogue readCatalogue(const std::string &xmlfile)
Definition: CatalogueCache.cc:33
ChimeraTK::DoocsBackend::BackendRegisterer::BackendRegisterer
BackendRegisterer()
Definition: DoocsBackend.cc:74
ChimeraTK::DoocsBackend::informRuntimeError
void informRuntimeError(const std::string &address)
Called by accessors to inform about addess causing a runtime_error.
Definition: DoocsBackend.cc:219
ChimeraTK::DoocsBackend::createInstance
static boost::shared_ptr< DeviceBackend > createInstance(std::string address, std::map< std::string, std::string > parameters)
Definition: DoocsBackend.cc:140
ChimeraTK::DoocsBackend::setExceptionImpl
void setExceptionImpl() noexcept override
Definition: DoocsBackend.cc:228
ChimeraTK::DoocsBackendTimeStampRegisterAccessor
Definition: DoocsBackendTimeStampAccessor.h:12
DoocsBackendRegisterCatalogue
Definition: RegisterInfo.h:12
ChimeraTK::DoocsBackendNumericRegisterAccessor
Definition: DoocsBackendNumericRegisterAccessor.h:18
CatalogueFetcher
Definition: CatalogueFetcher.h:10
ChimeraTK::DoocsBackend::activateAsyncRead
void activateAsyncRead() noexcept override
Definition: DoocsBackend.cc:235
ChimeraTK::DoocsBackend::open
void open() override
Definition: DoocsBackend.cc:167
DoocsBackendTimeStampAccessor.h
ChimeraTK::DoocsBackendIIIIRegisterAccessor
Definition: DoocsBackendIIIIRegisterAccessor.h:15
ChimeraTK::DoocsBackendNamespace::ZMQSubscriptionManager::getInstance
static ZMQSubscriptionManager & getInstance()
Definition: ZMQSubscriptionManager.h:25
ChimeraTK::DoocsBackend::~DoocsBackend
~DoocsBackend() override
Definition: DoocsBackend.cc:110
ChimeraTK::DoocsBackend::backendRegisterer
static BackendRegisterer backendRegisterer
Definition: DoocsBackend.h:91
RegisterInfo.h
ChimeraTK::DoocsBackendNamespace::ZMQSubscriptionManager::deactivateAllListenersAndPushException
void deactivateAllListenersAndPushException(DoocsBackend *backend)
Deactivate all listeners for the given backend and push exceptions into the queues.
Definition: ZMQSubscriptionManager.cc:252
DoocsBackendIFFFRegisterAccessor.h
ChimeraTK::DoocsBackendNamespace::ZMQSubscriptionManager::deactivateAllListeners
void deactivateAllListeners(DoocsBackend *backend)
Deactivate all listeners the given backend. Should be called from DoocsBackend::close().
Definition: ZMQSubscriptionManager.cc:217
ChimeraTK::DoocsBackend::_serverAddress
std::string _serverAddress
DOOCS address component for the server (FACILITY/DEVICE)
Definition: DoocsBackend.h:84
DoocsBackend.h
ChimeraTK::DoocsBackendStringRegisterAccessor
Definition: DoocsBackendStringRegisterAccessor.h:18
ChimeraTK::DoocsBackend::DoocsBackend
DoocsBackend(const std::string &serverAddress, const std::string &cacheFile={}, const std::string &updateCache={})
Definition: DoocsBackend.cc:82
DoocsBackendIIIIRegisterAccessor.h
ChimeraTK::DoocsBackend::isCommunicationError
static bool isCommunicationError(int doocs_error)
Definition: DoocsBackend.cc:400
DoocsBackendNumericRegisterAccessor.h
DoocsBackendEventIdAccessor.h
ChimeraTK::DoocsBackend::getBackendRegisterCatalogue
const DoocsBackendRegisterCatalogue & getBackendRegisterCatalogue() const
Definition: DoocsBackend.cc:197
ChimeraTK_DeviceAccess_createBackend
boost::shared_ptr< ChimeraTK::DeviceBackend > ChimeraTK_DeviceAccess_createBackend(std::string address, std::map< std::string, std::string > parameters)
Definition: DoocsBackend.cc:38
ChimeraTK::DoocsBackend::getRegisterCatalogue
RegisterCatalogue getRegisterCatalogue() const override
Definition: DoocsBackend.cc:191
ChimeraTK
Definition: spec_DoocsBackend.dox:2
StringUtility.h
object_name
const char * object_name
Definition: DoocsBackend.cc:33
ChimeraTK::DoocsBackendIFFFRegisterAccessor
Definition: DoocsBackendIFFFRegisterAccessor.h:18
CatalogueFetcher::fetch
std::pair< DoocsBackendRegisterCatalogue, bool > fetch()
Definition: CatalogueFetcher.cc:14