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