ChimeraTK-DeviceAccess-DoocsBackend 01.12.01
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/MappedImage.h>
13#include <ChimeraTK/NDRegisterAccessor.h>
14#include <ChimeraTK/RegisterPath.h>
15
16#include <doocs/EqCall.h>
17
18#include <eq_errors.h>
19
20namespace ChimeraTK {
21
24 public:
26 std::string _path;
27
29 doocs::EqAdr ea;
30
32 doocs::EqCall eq;
33
35 doocs::EqData src, dst;
36
38 bool isArray{false};
39
41 size_t nElements{0};
42
44 size_t elementOffset{0};
45
47 bool isPartial{false};
48
50 bool useZMQ{false};
51
54 bool isActiveZMQ{false};
55
57 cppext::future_queue<doocs::EqData> notifications;
58
60 bool shutdownCalled{false};
61
63 boost::shared_ptr<DoocsBackend> _backend;
64
65 protected:
67 doocs::EventId _lastEventId;
68 };
69
70 /********************************************************************************************************************/
71
72 template<typename UserType>
73 class DoocsBackendRegisterAccessor : public DoocsBackendRegisterAccessorBase, public NDRegisterAccessor<UserType> {
74 public:
76
88
90
91 bool doWriteTransfer(VersionNumber) override {
93 return false;
94 }
95
96 void doPreRead(TransferType) override {
97 if(!_backend->isOpen()) throw ChimeraTK::logic_error("Read operation not allowed while device is closed.");
98 if(!isReadable()) throw ChimeraTK::logic_error("Try to read from write-only register \"" + _path + "\".");
99 }
100
101 void doPreWrite(TransferType, VersionNumber) override {
102 if(!_backend->isOpen()) throw ChimeraTK::logic_error("Write operation not allowed while device is closed.");
103 if(!isWriteable()) throw ChimeraTK::logic_error("Try to write read-only register \"" + _path + "\".");
104 }
105
106 void doPostRead(TransferType, bool hasNewData) override {
107 if(!hasNewData) return;
108
109 // Note: the original idea was to extract the time stamp from the received data. This idea has been dropped since
110 // the time stamp attached to the data seems to be unreliably, at least for the x2timer macro pulse number. If the
111 // unreliable time stamp is attached to the trigger, all data will get this time stamp. This leads to error
112 // messages of the DOOCS history archiver, which rejects data due to wrong time stamps. Hence we better generate
113 // our own time stamp here.
114
115 // See spec. B.1.3.4.2
116 TransferElement::setDataValidity(dst.error() == 0 ? DataValidity::ok : DataValidity::faulty);
117
118 // If the eventid is valid (!= 0) but older than the last one, we have backward running eventids but VersionNumber
119 // must not run backwards. Keep VersionNumber unchanged and return. See spec B.1.3.2. (Note: after a re-connection
120 // to a slow variable the version number might be the same)
121 if(dst.get_event_id() != doocs::EventId(0) && dst.get_event_id() < _lastEventId) {
122 return;
123 }
124
125 if(dst.get_event_id() == doocs::EventId()) {
126 // See spec. B.1.3.4.1
127 TransferElement::_versionNumber = {};
128 _lastEventId = dst.get_event_id();
129 return;
130 }
131
132 // Only check for a new version number if the event ids no not match up.
133 // Otherwise it might be possible that the EventIDMapper allready removed the old event id from
134 // its cache and will generate a new versionnumber for the same event id.
135 // This prevents not updating slow- if ever changing variables that are polled periodically
136 //
137 // During startup, we can receive multiple receives with event_id == 0. The first check ensures that
138 // we do not hand out the VersionNumber{nullptr} then
139 // if(_lastEventId == doocs::EventId() || _lastEventId != dst.get_event_id()) {
140 // Get VersionNumber from the EventIdMapper. See spec B.1.3.3.
141 auto newVersionNumber =
142 _backend->_dataConsistencyRealm->getVersion(async::DataConsistencyKey(dst.get_event_id().to_int()));
143
144 // Minimum version is _backend->_startVersion. See spec. B.1.3.3.1.
145 auto startVersion = _backend->getStartVersion();
146 if(newVersionNumber < startVersion) {
147 newVersionNumber = startVersion;
148 }
149
150 // Version still must not go backwards. See spec B.1.3.3.2.
151 // If the sender is sending a valid eventid, afterwards sends eventid 0, and after that sends
152 // same eventid again, then the last sent version in this transfer element has been increased
153 // by eventid 0 and now newVersionNumber which is coming from the EventIDMapper is older.
154 if(newVersionNumber < TransferElement::_versionNumber) {
155 return;
156 }
157
158 // See spec. B.1.3.4.1
159 TransferElement::_versionNumber = newVersionNumber;
160 _lastEventId = dst.get_event_id();
161 }
162
163 bool isReadOnly() const override { return isReadable() && not isWriteable(); }
164
165 bool isReadable() const override { return _isReadable; }
166
167 bool isWriteable() const override { return _isWriteable; }
168
169 using TransferElement::_readQueue;
170
171 bool mayReplaceOther(const boost::shared_ptr<TransferElement const>& other) const override {
172 auto rhsCasted = boost::dynamic_pointer_cast<const DoocsBackendRegisterAccessor<UserType>>(other);
173 if(!rhsCasted) return false;
174 if(rhsCasted.get() == this) 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 {
190 if(this->getAccessModeFlags().has(AccessMode::wait_for_new_data)) {
191 this->interrupt_impl(this->notifications);
192 }
193 }
194
195 protected:
196 DoocsBackendRegisterAccessor(boost::shared_ptr<DoocsBackend> backend, const std::string& path,
197 const std::string& registerPathName, size_t numberOfWords, size_t wordOffsetInRegister, AccessModeFlags flags);
198
201
209
212 };
213
214 /********************************************************************************************************************/
215 /********************************************************************************************************************/
217 /********************************************************************************************************************/
218 /********************************************************************************************************************/
219
220 template<typename UserType>
222 size_t actualLength = 0;
223 int typeId = 0;
224
225 // Try to read data (if the device is opened), to obtain type and size of the register. Otherwise take information
226 // from catalogue (-> cache)
227 int rc = 1;
228 if(_backend->isOpen()) {
229 doocs::EqData tmp;
230 rc = eq.get(&ea, &tmp, &dst);
231 }
232 if(rc) {
233 if(rc == eq_errors::ill_property || rc == eq_errors::ill_location || rc == eq_errors::ill_address) {
234 // no property by that name
235 throw ChimeraTK::logic_error("Property does not exist: " + _path);
236 }
237
238 // we cannot reach the server, so try to obtain information from the catalogue
239 actualLength = info.getNumberOfElements();
240 typeId = info.doocsTypeId;
241 }
242 else {
243 // obtain number of elements from server reply
244 actualLength = dst.array_length();
245 if(actualLength == 0 && dst.length() == 1) {
246 actualLength = 1;
247 }
248 else {
249 if(actualLength == 0) actualLength = dst.length();
250 }
251 typeId = dst.type();
252 if(typeId == DATA_IMAGE) {
253 // RegisterAccessor byte array needs to store header and body
254 actualLength += sizeof(ChimeraTK::ImgHeader);
255 }
256 }
257
258 // strings report number of characters, not number of strings..
259 if(typeId == DATA_TEXT || typeId == DATA_STRING) {
260 actualLength = 1;
261 }
262
263 if(actualLength > 1) {
264 isArray = true;
265 }
266 else {
267 isArray = false;
268 }
269
270 if(nElements == 0) {
271 nElements = actualLength;
272 }
273 if(nElements + elementOffset > actualLength) {
274 // for image type, we allow having a byte array larger than required
275 if(typeId != DATA_IMAGE) {
276 throw ChimeraTK::logic_error("Requested number of words exceeds the length of the DOOCS property!");
277 }
278 }
279 if(nElements == actualLength && elementOffset == 0) {
280 isPartial = false;
281 }
282 else {
283 isPartial = true;
284 }
285
286 // allocate buffers
287 NDRegisterAccessor<UserType>::buffer_2D.resize(1);
288 NDRegisterAccessor<UserType>::buffer_2D[0].resize(nElements);
289
290 // set proper type information in the source doocs::EqData
291 src.set_type(typeId);
292 if(typeId == DATA_IMAGE) {
293 // DOOCS data structure has its own image header format and length() is only used for body length
294 src.length(actualLength - sizeof(ChimeraTK::ImgHeader));
295 }
296 else if(typeId != DATA_IIII) {
297 src.length(actualLength);
298 }
299
300 // use ZeroMQ with AccessMode::wait_for_new_data
301 if(useZMQ) {
302 // subscribe via subscription manager
304 }
305 }
306
307 /********************************************************************************************************************/
308
309 template<typename UserType>
311 const std::string& path, const std::string& registerPathName, size_t numberOfWords, size_t wordOffsetInRegister,
312 AccessModeFlags flags)
313 : NDRegisterAccessor<UserType>(path, flags), _isReadable(true), _isWriteable(true) {
314 try {
315 _backend = backend;
316 _path = path;
317 elementOffset = wordOffsetInRegister;
318 nElements = numberOfWords;
319 useZMQ = false;
320
321 // check for unknown access mode flags
322 flags.checkForUnknownFlags({AccessMode::wait_for_new_data});
323
324 // set address
325 ea.adr(path);
326
327 // obtain catalogue entry
328 auto info = backend->getBackendRegisterCatalogue().getBackendRegister(registerPathName);
329
330 // use zero mq subscriptiopn?
331 if(flags.has(AccessMode::wait_for_new_data)) {
332 if(!info.getSupportedAccessModes().has(AccessMode::wait_for_new_data)) {
333 throw ChimeraTK::logic_error("invalid access mode for this register");
334 }
335 useZMQ = true;
336
337 // Create notification queue.
338 notifications = cppext::future_queue<doocs::EqData>(3);
339 _readQueue = notifications.then<void>([this](doocs::EqData& data) { this->dst = data; }, std::launch::deferred);
340 }
341
342 initialise(info);
343 }
344 catch(...) {
345 this->shutdown();
346 throw;
347 }
348 }
349
350 /********************************************************************************************************************/
351
352 template<typename UserType>
356
357 /********************************************************************************************************************/
358
359 template<typename UserType>
361 if(!_backend->isFunctional()) {
362 throw ChimeraTK::runtime_error(std::string("Exception reported by another accessor."));
363 }
364
365 assert(!useZMQ);
366
367 boost::this_thread::interruption_point();
368
369 // read data
370 doocs::EqData tmp;
371 int rc = eq.get(&ea, &tmp, &dst);
372
373 // check error
374 if(rc == doocs::TransactionResult::transaction_error || rc == doocs::TransactionResult::transport_error) {
375 _backend->informRuntimeError(_path);
376 throw ChimeraTK::runtime_error(std::string("Cannot read from DOOCS property: ") + dst.get_string());
377 }
378 }
379
380 /********************************************************************************************************************/
381
382 template<typename UserType>
384 if(!_backend->isFunctional()) {
385 throw ChimeraTK::runtime_error(std::string("Exception reported by another accessor."));
386 }
387
388 // write data
389 int rc = eq.set(&ea, &src, &dst);
390 // check error
391 if(rc == doocs::TransactionResult::transaction_error || rc == doocs::TransactionResult::transport_error ||
392 (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
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