ChimeraTK-DeviceAccess 03.25.00
Loading...
Searching...
No Matches
LogicalNameMappingBackend.cc
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
5
10#include "SupportedUserTypes.h"
11
12namespace ChimeraTK {
13
15 : hasParsed(false), _lmapFileName(std::move(lmapFileName)) {
17 }
18
19 /********************************************************************************************************************/
20
22 // don't run, if already parsed
23 if(hasParsed) {
24 return;
25 }
26 hasParsed = true;
27
28 // parse the map file
31
32 // create all devices referenced in the map
33 for(const auto& devName : getTargetDevices()) {
35 }
36 // iterate over plugins and call postParsingHook
37 for(auto& reg : _catalogue_mutable) {
38 for(auto& p : reg.plugins) {
39 auto thisp = boost::static_pointer_cast<const LogicalNameMappingBackend>(shared_from_this());
40 p->postParsingHook(thisp);
41 }
42 }
43 }
44
45 /********************************************************************************************************************/
46
48 if(isFunctional()) {
49 return;
50 }
51 parse();
52
53 // open all referenced devices (unconditionally, open() is also used for recovery)
54 for(const auto& device : _devices) {
55 device.second->open();
56 }
57
58 // flag as opened
59 _versionOnOpen = ChimeraTK::VersionNumber{};
60 _asyncReadActive = false;
62
63 // make sure to update the catalogue from target devices in case they change their catalogue upon open
64 catalogueCompleted = false;
65
66 // call the openHook for all plugins
67 for(auto& reg : _catalogue_mutable) {
68 for(auto& plug : dynamic_cast<LNMBackendRegisterInfo&>(reg).plugins) {
69 plug->openHook(boost::dynamic_pointer_cast<LogicalNameMappingBackend>(shared_from_this()));
70 }
71 }
72
73 // update versions of constants
74 auto versionForConstants = ChimeraTK::VersionNumber{}; // needs to be bigger than _versionOnOpen
75 for(auto& nameAndVar : _variables) {
76 auto& variable = nameAndVar.second;
77 if(variable.isConstant) {
78 std::lock_guard<std::mutex> lk(variable.valueTable_mutex);
79 ChimeraTK::callForType(variable.valueType, [&](auto v) {
80 boost::fusion::at_key<decltype(v)>(variable.valueTable.table).latestVersion = versionForConstants;
81 });
82 }
83 }
84 }
85
86 /********************************************************************************************************************/
87
89 if(!_opened) {
90 return;
91 }
92
93 // call the closeHook for all plugins
94 for(auto& reg : _catalogue_mutable) {
95 for(auto& plug : dynamic_cast<LNMBackendRegisterInfo&>(reg).plugins) {
96 plug->closeHook();
97 }
98 }
99
100 // close all referenced devices
101 for(const auto& device : _devices) {
102 if(device.second->isOpen()) {
103 device.second->close();
104 }
105 }
106 // flag as closed
107 _opened = false;
108 _asyncReadActive = false;
109 }
110
111 /********************************************************************************************************************/
112
113 // FIXME #11279 Implement API breaking changes from linter warnings
114 // NOLINTBEGIN(performance-unnecessary-value-param)
115 boost::shared_ptr<DeviceBackend> LogicalNameMappingBackend::createInstance(
116 std::string /*address*/, std::map<std::string, std::string> parameters) {
117 // NOLINTEND(performance-unnecessary-value-param)
118 if(parameters["map"].empty()) {
119 throw ChimeraTK::logic_error("Map file name not specified.");
120 }
121 auto ptr = boost::make_shared<LogicalNameMappingBackend>(parameters["map"]);
122 parameters.erase(parameters.find("map"));
123 ptr->_parameters = parameters;
124 return boost::static_pointer_cast<DeviceBackend>(ptr);
125 }
126
127 /********************************************************************************************************************/
128
129 template<typename UserType>
130 boost::shared_ptr<NDRegisterAccessor<UserType>> LogicalNameMappingBackend::getRegisterAccessor_impl(
131 const RegisterPath& registerPathName, size_t numberOfWords, size_t wordOffsetInRegister, AccessModeFlags flags,
132 size_t omitPlugins) {
133 parse();
134 // check if accessor plugin present
135 boost::shared_ptr<NDRegisterAccessor<UserType>> returnValue;
136 auto info = _catalogue_mutable.getBackendRegister(registerPathName);
137 if(info.plugins.size() <= omitPlugins) {
138 // no plugin: directly return the accessor
139 returnValue =
140 getRegisterAccessor_internal<UserType>(registerPathName, numberOfWords, wordOffsetInRegister, flags);
141 }
142 else {
143 returnValue = info.plugins[omitPlugins]->getAccessor<UserType>(
144 boost::static_pointer_cast<LogicalNameMappingBackend>(shared_from_this()), numberOfWords,
145 wordOffsetInRegister, flags, omitPlugins);
146 }
147
148 returnValue->setExceptionBackend(shared_from_this());
149
150 return returnValue;
151 }
152
153 /********************************************************************************************************************/
154
155 template<typename UserType>
156 boost::shared_ptr<NDRegisterAccessor<UserType>> LogicalNameMappingBackend::getRegisterAccessor_internal(
157 const RegisterPath& registerPathName, size_t numberOfWords, size_t wordOffsetInRegister, AccessModeFlags flags) {
158 // obtain register info
159 auto info = _catalogue_mutable.getBackendRegister(registerPathName);
160
161 // Check that the requested requested accessor fits into the register as described by the info. It is not enough to
162 // let the target do the check. It might be a sub-register of a much larger one and for the target it is fine.
163 if(info.length != 0) {
164 // If info->length is 0 we let the target device do the checking. Nothing we can decide here.
165 if(numberOfWords == 0) {
166 numberOfWords = info.length;
167 }
168 if((numberOfWords + wordOffsetInRegister) > info.length) {
170 std::string(
171 "LogicalNameMappingBackend: Error creating accessor. Number of words plus offset too large in ") +
172 registerPathName);
173 }
174 }
175
176 // determine the offset and length
177 size_t actualOffset = size_t(info.firstIndex) + wordOffsetInRegister;
178 size_t actualLength = (numberOfWords > 0 ? numberOfWords : size_t(info.length));
179
180 // implementation for each type
181 boost::shared_ptr<NDRegisterAccessor<UserType>> ptr;
182 if(info.targetType == LNMBackendRegisterInfo::TargetType::REGISTER) {
183 DeviceBackend* _targetDevice;
184 std::string devName = info.deviceName;
185 if(devName != "this") {
186 _targetDevice = _devices[info.deviceName].get();
187 }
188 else {
189 _targetDevice = this;
190 }
191 // make sure the target device exists
192 if(_targetDevice == nullptr) {
193 throw ChimeraTK::logic_error("Target device for this logical register is not opened. See "
194 "exception thrown in open()!");
195 }
196 // obtain underlying register accessor
197 ptr = _targetDevice->getRegisterAccessor<UserType>(
198 RegisterPath(info.registerName), actualLength, actualOffset, flags);
199 }
200 else if(info.targetType == LNMBackendRegisterInfo::TargetType::CHANNEL) {
201 ptr = boost::shared_ptr<NDRegisterAccessor<UserType>>(new LNMBackendChannelAccessor<UserType>(
202 shared_from_this(), registerPathName, actualLength, actualOffset, flags));
203 }
204 else if(info.targetType == LNMBackendRegisterInfo::TargetType::BIT) {
205 ptr = boost::shared_ptr<NDRegisterAccessor<UserType>>(
206 new LNMBackendBitAccessor<UserType>(shared_from_this(), registerPathName, actualLength, actualOffset, flags));
207 }
208 else if(info.targetType == LNMBackendRegisterInfo::TargetType::CONSTANT ||
210 ptr = boost::shared_ptr<NDRegisterAccessor<UserType>>(new LNMBackendVariableAccessor<UserType>(
211 shared_from_this(), registerPathName, actualLength, actualOffset, flags));
212 }
213 else {
215 "For this register type, a RegisterAccessor cannot be obtained (name of logical register: " +
216 registerPathName + ").");
217 }
218
219 return ptr;
220 }
221
222 /********************************************************************************************************************/
223
227 }
228 parse();
229
230 // map of register catalogues for all target devices
231 std::map<std::string, RegisterCatalogue> catalogueMap;
232 for(const auto& [name, device] : _devices) {
233 catalogueMap.emplace(name, device->getRegisterCatalogue());
234 }
235
236 // fill in information to the catalogue from the target devices
237 for(auto& lnmInfo : _catalogue_mutable) {
238 auto targetType = lnmInfo.targetType;
242 continue;
243 }
244
245 std::string devName = lnmInfo.deviceName;
246
247 RegisterInfo target_info(lnmInfo.clone()); // Start with a clone of this info as there is not default constructor
248 // In case the device is not "this" replace it with the real target register info
249 if(devName != "this") {
250 const auto& cat = catalogueMap.at(devName);
251 if(!cat.hasRegister(lnmInfo.registerName)) {
252 continue;
253 }
254 target_info = cat.getRegister(lnmInfo.registerName);
255 }
256 else {
257 target_info = _catalogue_mutable.getRegister(lnmInfo.registerName);
258 // target_info might also be affected by plugins. e.g. forceReadOnly plugin
259 // we need to process plugin list of target register before taking over anything
260 auto& i = dynamic_cast<LNMBackendRegisterInfo&>(target_info.getImpl());
261 for(auto& plugin : i.plugins) {
262 plugin->updateRegisterInfo(_catalogue_mutable);
263 }
264 target_info = _catalogue_mutable.getRegister(lnmInfo.registerName);
265 }
266
267 lnmInfo.supportedFlags = target_info.getSupportedAccessModes();
269 lnmInfo._dataDescriptor = target_info.getDataDescriptor();
270 }
271 else {
272 lnmInfo._dataDescriptor = DataDescriptor(DataDescriptor::FundamentalType::boolean, true, false, 1, 0);
273 lnmInfo.supportedFlags.remove(AccessMode::raw);
274 }
275 lnmInfo.readable = target_info.isReadable();
276 lnmInfo.writeable = target_info.isWriteable();
277
279 lnmInfo.writeable = false;
280 }
281
283 lnmInfo.nChannels = target_info.getNumberOfChannels();
284 }
285 if(lnmInfo.length == 0) {
286 lnmInfo.length = target_info.getNumberOfElements();
287 }
288
289 lnmInfo.tags = target_info.getTags();
290
291 _catalogue_mutable.modifyRegister(lnmInfo);
292 }
293
294 // update catalogue info by plugins
295 for(auto& lnmInfo : _catalogue_mutable) {
296 for(auto& plugin : lnmInfo.plugins) {
297 plugin->updateRegisterInfo(_catalogue_mutable);
298 }
299 }
300
301 catalogueCompleted = true;
303 }
304
305 /********************************************************************************************************************/
306 // Instantiate templated members - this is required for some gcc versions like the one on Ubuntu 18.04
307
308 template<typename UserType>
310 LogicalNameMappingBackend* _p{nullptr};
311
312 // NOLINTNEXTLINE(readability-identifier-naming)
313 void getRegisterAccessor_impl(const RegisterPath& registerPathName, size_t numberOfWords,
314 size_t wordOffsetInRegister, AccessModeFlags flags, size_t omitPlugins) {
315 _p->getRegisterAccessor_impl<UserType>(
316 registerPathName, numberOfWords, wordOffsetInRegister, std::move(flags), omitPlugins);
317 }
318
319 // NOLINTNEXTLINE(readability-identifier-naming)
320 void getRegisterAccessor_internal(const RegisterPath& registerPathName, size_t numberOfWords,
321 size_t wordOffsetInRegister, AccessModeFlags flags) {
322 _p->getRegisterAccessor_internal<UserType>(
323 registerPathName, numberOfWords, wordOffsetInRegister, std::move(flags));
324 }
325 };
327
328 /********************************************************************************************************************/
329
331 auto message = getActiveExceptionMessage();
332 for(auto& d : _devices) {
333 d.second->setException(message);
334 }
335
336 // iterate all push subscriptions of variables and place exception into queue
337 if(_asyncReadActive) {
338 VersionNumber v{};
339 for(auto& r : _catalogue_mutable) {
340 auto& info = dynamic_cast<LNMBackendRegisterInfo&>(r);
341 if(info.targetType == LNMBackendRegisterInfo::TargetType::VARIABLE) {
342 callForType(info.valueType, [&](auto arg) {
343 auto& lnmVariable = _variables[info.name];
344 auto& vtEntry = boost::fusion::at_key<decltype(arg)>(lnmVariable.valueTable.table);
345 for(auto& sub : vtEntry.subscriptions) {
346 try {
347 throw ChimeraTK::runtime_error(message);
348 }
349 catch(...) {
350 sub.second.push_overwrite_exception(std::current_exception());
351 }
352 }
353 });
354 }
355 }
356 }
357
358 // deactivate async read
359 _asyncReadActive = false;
360
361 // call the exceptionHook for all plugins
362 for(auto& reg : _catalogue_mutable) {
363 for(auto& plug : dynamic_cast<LNMBackendRegisterInfo&>(reg).plugins) {
364 plug->exceptionHook();
365 }
366 }
367 }
368
369 /********************************************************************************************************************/
370
371 void LogicalNameMappingBackend::activateAsyncRead() noexcept {
372 if(!isFunctional()) {
373 return;
374 }
375
376 // Store information locally, as variable accessors have async read.
377 // Atomically exchange with the existing value (for thread safety against concurrent activateAsyncRead calls), and
378 // check the old state. If async read was already active, don't send the "initial" values again.
379 auto asyncWasActive = _asyncReadActive.exchange(true);
380 if(asyncWasActive) {
381 return;
382 }
383
384 // delegate to target devices
385 for(auto& d : _devices) {
386 d.second->activateAsyncRead();
387 }
388
389 // iterate all push subscriptions of variables and place initial value into queue
390 VersionNumber v = getVersionOnOpen();
391 for(auto& r : _catalogue_mutable) {
392 auto& info = dynamic_cast<LNMBackendRegisterInfo&>(r);
393 if(info.targetType == LNMBackendRegisterInfo::TargetType::VARIABLE) {
394 auto& lnmVariable = _variables[info.name];
395 try {
396 callForType(info.valueType, [&](auto arg) {
397 auto& vtEntry = boost::fusion::at_key<decltype(arg)>(lnmVariable.valueTable.table);
398 // override version number if last write to variable was before reopening the device
399 if(vtEntry.latestVersion < v) {
400 vtEntry.latestVersion = v; // store in case an accessor is created after calling activateAsyncRead
401 }
402 for(auto& sub : vtEntry.subscriptions) {
403 try {
404 sub.second.push_overwrite({vtEntry.latestValue, vtEntry.latestValidity, vtEntry.latestVersion});
405 }
406 catch(std::system_error& e) {
407 std::cerr << "Caught system error in activateAsyncRead(): " << e.what() << std::endl;
408 std::terminate();
409 }
410 }
411 });
412 }
413 catch(std::bad_cast& e) {
414 // bad_cast is thrown by callForType if the type is not known. This should not happen at this point any more
415 // because we are iterating a list that has already been processed before.
416 std::ignore = e;
417 assert(false);
418 }
419 }
420 }
421 }
422
423 /********************************************************************************************************************/
424
425 std::unordered_set<std::string> LogicalNameMappingBackend::getTargetDevices() const {
426 std::unordered_set<std::string> ret;
427 for(const auto& info : _catalogue_mutable) {
428 if(info.deviceName != "this" && !info.deviceName.empty()) {
429 ret.insert(info.deviceName);
430 }
431 }
432 return ret;
433 }
434
435 /********************************************************************************************************************/
436
437 ChimeraTK::VersionNumber LogicalNameMappingBackend::getVersionOnOpen() const {
438 return _versionOnOpen;
439 }
440
441 /********************************************************************************************************************/
442
443 std::set<DeviceBackend::BackendID> LogicalNameMappingBackend::getInvolvedBackendIDs() {
444 parse();
445 std::set<DeviceBackend::BackendID> retVal{getBackendID()};
446 for(auto& devIter : _devices) {
447 auto& dev = devIter.second;
448 retVal.merge(dev->getInvolvedBackendIDs());
449 }
450 return retVal;
451 }
452
453 /********************************************************************************************************************/
454
455} // namespace ChimeraTK
#define INSTANTIATE_TEMPLATE_FOR_CHIMERATK_USER_TYPES(TemplateClass)
#define FILL_VIRTUAL_FUNCTION_TEMPLATE_VTABLE(functionName)
Fill the vtable of a virtual function template defined with DEFINE_VIRTUAL_FUNCTION_TEMPLATE.
Set of AccessMode flags with additional functionality for an easier handling.
Definition AccessMode.h:48
static BackendFactory & getInstance()
Static function to get an instance of factory.
boost::shared_ptr< DeviceBackend > createBackend(const std::string &aliasOrUri)
Create a new backend and return the instance as a shared pointer.
Class describing the actual payload data format of a register in an abstract manner.
The base class for backends providing IO functionality for the Device class.
boost::shared_ptr< NDRegisterAccessor< UserType > > getRegisterAccessor(const RegisterPath &registerPathName, size_t numberOfWords, size_t wordOffsetInRegister, AccessModeFlags flags)
Get a NDRegisterAccessor object from the register name.
bool isFunctional() const noexcept final
Return whether a device is working as intended, usually this means it is opened and does not have any...
void setOpenedAndClearException() noexcept
Backends should call this function at the end of a (successful) open() call.
std::string getActiveExceptionMessage() noexcept
std::atomic< bool > _opened
flag if backend is opened
void close()
Close the device.
Definition Device.cc:66
RegisterCatalogue getRegisterCatalogue() const
Return the register catalogue with detailed information on all registers.
Definition Device.cc:22
void open(std::string const &aliasName)
Open a device by the given alias name from the DMAP file.
Definition Device.cc:58
RegisterInfo structure for the LogicalNameMappingBackend.
Logical name map: store information from xlmap file and provide it to the LogicalNameMappingBackend a...
BackendRegisterCatalogue< LNMBackendRegisterInfo > parseFile(const std::string &fileName)
parse the given XML file
Backend to map logical register names onto real hardware registers.
BackendRegisterCatalogue< LNMBackendRegisterInfo > _catalogue_mutable
We need to make the catalogue mutable, since we fill it within getRegisterCatalogue()
std::unordered_set< std::string > getTargetDevices() const
Obtain list of all target devices referenced in the catalogue.
static boost::shared_ptr< DeviceBackend > createInstance(std::string address, std::map< std::string, std::string > parameters)
void parse() const
parse the logical map file, if not yet done
RegisterCatalogue getRegisterCatalogue() const override
Return the register catalogue with detailed information on all registers.
std::map< std::string, boost::shared_ptr< DeviceBackend > > _devices
map of target devices
LogicalNameMappingBackend(std::string lmapFileName="")
std::atomic< bool > _asyncReadActive
Flag storing whether asynchronous read has been activated.
boost::shared_ptr< NDRegisterAccessor< UserType > > getRegisterAccessor_internal(const RegisterPath &registerPathName, size_t numberOfWords, size_t wordOffsetInRegister, AccessModeFlags flags)
boost::shared_ptr< NDRegisterAccessor< UserType > > getRegisterAccessor_impl(const RegisterPath &registerPathName, size_t numberOfWords, size_t wordOffsetInRegister, AccessModeFlags flags, size_t omitPlugins=0)
void setExceptionImpl() noexcept override
Function to be (optionally) implemented by backends if additional actions are needed when switching t...
std::map< std::string, LNMVariable > _variables
Map of variables and constants.
bool catalogueCompleted
Flag whether the catalogue has already been filled with extra information from the target backends.
std::map< std::string, std::string > _parameters
map of parameters passed through the CDD
std::string _lmapFileName
name of the logical map file
Catalogue of register information.
bool isWriteable() const
Return whether the register is writeable.
BackendRegisterInfoBase & getImpl()
Return a reference to the implementation object.
unsigned int getNumberOfChannels() const
Return number of channels in register.
const DataDescriptor & getDataDescriptor() const
Return desciption of the actual payload data for this register.
std::set< std::string > getTags() const
Get the list of tags that are associated with this register.
AccessModeFlags getSupportedAccessModes() const
Return all supported AccessModes for this register.
unsigned int getNumberOfElements() const
Return number of elements per channel.
bool isReadable() const
Return whether the register is readable.
Class to store a register path name.
Class for generating and holding version numbers without exposing a numeric representation.
Exception thrown when a logic error has occured.
Definition Exception.h:51
void callForType(const std::type_info &type, LAMBDATYPE lambda)
Helper function for running code which uses some compile-time type that is specified at runtime as a ...
@ raw
Raw access: disable any possible conversion from the original hardware data type into the given UserT...
STL namespace.
ctk::Device device