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