ChimeraTK-DeviceAccess-EPICS-Backend 01.00.02
Loading...
Searching...
No Matches
EPICS-Backend.cc
Go to the documentation of this file.
1// SPDX-FileCopyrightText: Helmholtz-Zentrum Dresden-Rossendorf, FWKE, ChimeraTK Project <chimeratk-support@desy.de>
2// SPDX-License-Identifier: LGPL-3.0-or-later
3/*
4 * EPICS-Backend.cc
5 *
6 * Created on: Jan 22, 2021
7 * Author: Klaus Zenker (HZDR)
8 */
9
10#include "EPICS-Backend.h"
11
13#include "EPICSChannelManager.h"
14
15#include <ChimeraTK/BackendFactory.h>
16#include <ChimeraTK/DeviceAccessVersion.h>
17
18#include <boost/tokenizer.hpp>
19
20#include <cadef.h>
21#include <fstream>
22#include <iostream>
23#include <thread>
24#include <vector>
25typedef boost::tokenizer<boost::char_separator<char>> tokenizer;
26
27extern "C" {
28boost::shared_ptr<ChimeraTK::DeviceBackend> ChimeraTK_DeviceAccess_createBackend(
29 std::string address, std::map<std::string, std::string> parameters) {
30 return ChimeraTK::EpicsBackend::createInstance(address, parameters);
31}
32
33std::vector<std::string> ChimeraTK_DeviceAccess_sdmParameterNames{"map"};
34
35std::string ChimeraTK_DeviceAccess_version{CHIMERATK_DEVICEACCESS_VERSION};
36
37std::string backend_name = "epics";
38}
39
40namespace ChimeraTK {
41 EpicsBackend::BackendRegisterer EpicsBackend::backendRegisterer;
42
43 EpicsBackend::EpicsBackend(const std::string& mapfile) : _catalogue_filled(false), _freshCreated(true) {
44 FILL_VIRTUAL_FUNCTION_TEMPLATE_VTABLE(getRegisterAccessor_impl);
45 prepareChannelAccess();
46
47 fillCatalogueFromMapFile(mapfile);
48 _catalogue_filled = true;
49 }
50
56
57 void EpicsBackend::prepareChannelAccess() {
58 // introduced to run the unit tests
59 // \ToDo: Why is that needed - each test finishes with closing the device which should destroy the context already
60 if(ca_current_context()) {
61 ca_context_destroy();
62 }
63 auto result = ca_context_create(ca_enable_preemptive_callback);
64 if(result != ECA_NORMAL) {
65 std::stringstream ss;
66 ss << "CA error " << ca_message(result) << "occurred while trying to start channel access.";
67 throw ChimeraTK::runtime_error(ss.str());
68 }
69 }
70
72 if(!isFunctional()) {
73 // after closing a new ca_context is needed (_opened is set also in constructor to signal prepareChannelAccess was
74 // just called before)
75 if(!_freshCreated) {
76 prepareChannelAccess();
77 {
78 std::lock_guard<std::mutex> lock(ChannelManager::getInstance().mapLock);
80 }
81 }
82 else {
83 _freshCreated = false;
84 }
85 size_t n = default_ca_timeout / 0.1; // sleep 100ms per loop, wait default_ca_timeout until giving up
86 for(size_t i = 0; i < n; i++) {
87 {
88 std::lock_guard<std::mutex> lock(ChannelManager::getInstance().mapLock);
89 if(ChannelManager::getInstance().checkAllConnections(true)) {
90 _channelAccessUp = true;
91 break;
92 }
93 }
94 std::this_thread::sleep_for(std::chrono::milliseconds(100));
95 }
96 if(!_channelAccessUp) {
97 throw ChimeraTK::runtime_error("Failed to establish channel access connection.");
98 }
100 std::lock_guard<std::mutex> lock(ChannelManager::getInstance().mapLock);
102 }
103 _startVersion = {};
104 setOpenedAndClearException();
105 }
106 }
107
109 _opened = false;
110 _asyncReadActivated = false;
111 std::lock_guard<std::mutex> lock(ChannelManager::getInstance().mapLock);
114 ca_context_destroy();
115 }
116
118 if(!isFunctional()) return;
119 // activate async read expects an initial value so deactivate channels first to force initial value
120 {
121 std::lock_guard<std::mutex> lock(ChannelManager::getInstance().mapLock);
124 }
125 size_t n = default_ca_timeout / 0.1; // sleep 100ms per loop, wait default_ca_timeout until giving up
126 bool allGood = false;
127 for(size_t i = 0; i < n; i++) {
128 if(ChannelManager::getInstance().checkInitialValueReceived()) {
129 allGood = true;
130 break;
131 }
132 std::this_thread::sleep_for(std::chrono::milliseconds(100));
133 }
134 if(!allGood) {
135 std::cerr << "Failed to receive initial value for all subscriptions in activateAsyncRead()." << std::endl;
136 }
137 _asyncReadActivated = true;
138 }
139
140 template<typename UserType>
141 boost::shared_ptr<NDRegisterAccessor<UserType>> EpicsBackend::getRegisterAccessor_impl(
142 const RegisterPath& registerPathName, size_t numberOfWords, size_t wordOffsetInRegister, AccessModeFlags flags) {
143 RegisterPath path = "EPICS://" + registerPathName;
144
145 EpicsBackendRegisterInfo info = _catalogue_mutable.getBackendRegister(registerPathName);
146
147 if(numberOfWords + wordOffsetInRegister > info._nElements || (numberOfWords == 0 && wordOffsetInRegister > 0)) {
148 std::stringstream ss;
149 ss << "Requested number of words/elements ( " << numberOfWords << ") with offset " << wordOffsetInRegister
150 << " exceeds the number of available words/elements: " << info._nElements;
151 throw ChimeraTK::logic_error(ss.str());
152 }
153
154 if(numberOfWords == 0) numberOfWords = info._nElements;
155
156 unsigned base_type = info._dbfType % (LAST_TYPE + 1);
157 if(info._dbfType == DBR_STSACK_STRING || info._dbfType == DBR_CLASS_NAME) base_type = DBR_STRING;
158 // switch(info._dpfType){
159 switch(base_type) {
160 case DBR_STRING:
161 return boost::make_shared<EpicsBackendRegisterAccessor<dbr_string_t, dbr_time_string, UserType>>(
162 path, shared_from_this(), info, flags, numberOfWords, wordOffsetInRegister, _asyncReadActivated);
163 break;
164 case DBR_FLOAT:
165 return boost::make_shared<EpicsBackendRegisterAccessor<dbr_float_t, dbr_time_float, UserType>>(
166 path, shared_from_this(), info, flags, numberOfWords, wordOffsetInRegister, _asyncReadActivated);
167 break;
168 case DBR_DOUBLE:
169 return boost::make_shared<EpicsBackendRegisterAccessor<dbr_double_t, dbr_time_double, UserType>>(
170 path, shared_from_this(), info, flags, numberOfWords, wordOffsetInRegister, _asyncReadActivated);
171 break;
172 case DBR_CHAR:
173 return boost::make_shared<EpicsBackendRegisterAccessor<dbr_char_t, dbr_time_char, UserType>>(
174 path, shared_from_this(), info, flags, numberOfWords, wordOffsetInRegister, _asyncReadActivated);
175 break;
176 case DBR_SHORT:
177 return boost::make_shared<EpicsBackendRegisterAccessor<dbr_short_t, dbr_time_short, UserType>>(
178 path, shared_from_this(), info, flags, numberOfWords, wordOffsetInRegister, _asyncReadActivated);
179 break;
180 case DBR_LONG:
181 return boost::make_shared<EpicsBackendRegisterAccessor<dbr_long_t, dbr_time_long, UserType>>(
182 path, shared_from_this(), info, flags, numberOfWords, wordOffsetInRegister, _asyncReadActivated);
183 break;
184 case DBR_ENUM:
185 return boost::make_shared<EpicsBackendRegisterAccessor<dbr_enum_t, dbr_time_enum, UserType>>(
186 path, shared_from_this(), info, flags, numberOfWords, wordOffsetInRegister, _asyncReadActivated);
187 break;
188 // if(dbr_type_is_CTRL(info._dpfType))
189 // return boost::make_shared<EpicsBackendRegisterAccessor<dbr_gr_enum, UserType>>(path,
190 // shared_from_this(), info, flags, numberOfWords, wordOffsetInRegister);
191 // else if (dbr_type_is_GR(info._dpfType))
192 // return boost::make_shared<EpicsBackendRegisterAccessor<dbr_ctrl_enum, UserType>>(path,
193 // shared_from_this(), info, flags, numberOfWords, wordOffsetInRegister);
194 // else
195 // return boost::make_shared<EpicsBackendRegisterAccessor<int, UserType>>(path, shared_from_this(),
196 // info, flags, numberOfWords, wordOffsetInRegister);
197 // break;
198 default:
199 throw ChimeraTK::runtime_error(std::string("Type ") + std::to_string(info._dbfType) + " not implemented.");
200 break;
201 }
202 }
203
205 BackendFactory::getInstance().registerBackendType("epics", &EpicsBackend::createInstance, {"map"});
206 std::cout << "BackendRegisterer: registered backend type epics" << std::endl;
207 }
208
209 boost::shared_ptr<DeviceBackend> EpicsBackend::createInstance(
210 std::string /*address*/, std::map<std::string, std::string> parameters) {
211 if(parameters["map"].empty()) {
212 throw ChimeraTK::logic_error("No map file provided.");
213 }
214 return boost::shared_ptr<DeviceBackend>(new EpicsBackend(parameters["map"]));
215 }
216
217 void EpicsBackend::addCatalogueEntry(RegisterPath path, std::shared_ptr<std::string> pvName) {
218 EpicsBackendRegisterInfo info(path);
219 info._caName = std::string(*pvName.get());
220 try {
221 std::lock_guard<std::mutex> lock(ChannelManager::getInstance().mapLock);
222 ChannelManager::getInstance().addChannel(info._caName, this);
223 }
224 catch(ChimeraTK::runtime_error& e) {
225 std::cerr << e.what() << ". PV is not added to the catalog." << std::endl;
226 return;
227 }
228 _catalogue_mutable.addRegister(info);
229 }
230
231 void EpicsBackend::configureChannel(EpicsBackendRegisterInfo& info) {
232 std::lock_guard<std::mutex> lock(ChannelManager::getInstance().mapLock);
233 auto pv = ChannelManager::getInstance().getPV(info._caName);
234 if(!ChannelManager::getInstance().isChannelConfigured(info._caName)) {
235 throw ChimeraTK::runtime_error("Trying to read an unconfigured channel.");
236 }
237 info._nElements = pv->nElems;
238 info._dbfType = pv->dbfType;
239
240 if(ca_read_access(pv->chid) != 1) info._isReadable = false;
241 if(ca_write_access(pv->chid) != 1) info._isWritable = false;
242 if(pv->dbfType == DBF_STRING) {
243 info._dataDescriptor = DataDescriptor(DataDescriptor::FundamentalType::string, true, true, 320, 300);
244 }
245 else if(pv->dbfType == DBF_DOUBLE || pv->dbfType == DBF_FLOAT) {
246 info._dataDescriptor = DataDescriptor(DataDescriptor::FundamentalType::numeric, false, true, 320, 300);
247 }
248 else if(pv->dbfType == DBF_INT || pv->dbfType == DBF_LONG || pv->dbfType == DBF_SHORT) {
249 info._dataDescriptor = DataDescriptor(DataDescriptor::FundamentalType::numeric, true, true, 320, 300);
250 }
251 else if(pv->dbfType == DBF_ENUM) {
252 info._dataDescriptor = DataDescriptor(DataDescriptor::FundamentalType::boolean, true, true, 320, 300);
253 }
254 else {
255 std::cerr << "Failed to data descriptor for node: " << info._caName << "." << std::endl;
256 }
257 info._accessModes.add(AccessMode::wait_for_new_data);
258 }
259
260 void EpicsBackend::fillCatalogueFromMapFile(const std::string& mapfileName) {
261 boost::char_separator<char> sep{"\t ", "", boost::drop_empty_tokens};
262 std::string line;
263 std::ifstream mapfile(mapfileName);
264 if(mapfile.is_open()) {
265 while(std::getline(mapfile, line)) {
266 if(line.empty()) continue;
267 tokenizer tok{line, sep};
268 size_t nTokens = std::distance(tok.begin(), tok.end());
269 if(!(nTokens == 2)) {
270 std::cerr << "Wrong number of tokens (" << nTokens << ") in mapfile " << mapfileName
271 << " line (-> line is ignored): \n " << line << std::endl;
272 continue;
273 }
274 auto it = tok.begin();
275
276 try {
277 std::shared_ptr<std::string> pathStr = std::make_shared<std::string>(*it);
278 RegisterPath path(*(pathStr.get()));
279 it++;
280 std::shared_ptr<std::string> nodeName = std::make_shared<std::string>(*it);
281 addCatalogueEntry(path, nodeName);
282 }
283 catch(std::out_of_range& e) {
284 std::cerr << "Failed reading the following line from mapping file " << mapfileName << "\n " << line
285 << std::endl;
286 }
287 }
288 mapfile.close();
289 }
290 else {
291 throw ChimeraTK::runtime_error(std::string("Failed reading mapfile: ") + mapfileName);
292 }
293
294 if(_catalogue_mutable.getNumberOfRegisters() == 0) {
295 throw ChimeraTK::runtime_error("No registers found in catalogue!");
296 }
297
298 auto result = ca_flush_io();
299 if(result == ECA_TIMEOUT) {
300 throw ChimeraTK::runtime_error("Channel setup failed.");
301 }
302 size_t n = default_ca_timeout / 0.1; // sleep 100ms per loop, wait default_ca_timeout until giving up
303 for(size_t i = 0; i < n; i++) {
304 {
305 std::lock_guard<std::mutex> lock(ChannelManager::getInstance().mapLock);
306 if(ChannelManager::getInstance().checkAllConnections(true)) {
307 _channelAccessUp = true;
308 break;
309 }
310 }
311 std::this_thread::sleep_for(std::chrono::milliseconds(100));
312 }
313 if(!_channelAccessUp) {
314 throw ChimeraTK::runtime_error("Failed to establish channel access connection.");
315 }
316 for(auto& reg : _catalogue_mutable) {
317 configureChannel(reg);
318 }
319 }
320
322 _asyncReadActivated = false;
323 ChannelManager::getInstance().setException(std::string("Exception reported by another accessor."));
324 }
325} // namespace ChimeraTK
std::vector< std::string > ChimeraTK_DeviceAccess_sdmParameterNames
boost::tokenizer< boost::char_separator< char > > tokenizer
std::string ChimeraTK_DeviceAccess_version
std::string backend_name
boost::shared_ptr< ChimeraTK::DeviceBackend > ChimeraTK_DeviceAccess_createBackend(std::string address, std::map< std::string, std::string > parameters)
void setException(const std::string error)
Push exception to all accessors that are registered.
void cleanup()
Reset the map content.
void addChannel(const std::string name, EpicsBackend *backend)
Add channel to the map and open channel access.
std::shared_ptr< pv > getPV(const std::string &name)
Get pv pointer.
void resetConnectionState()
Reset configuration and connected state for all channels.
static ChannelManager & getInstance()
void activateChannels()
Activate all registered channels.
void addChannelsFromMap(EpicsBackend *backend)
Register all channels in the map and open channel access.
void deactivateChannels()
Deactivate subscription of all registered channels.
void activateAsyncRead() noexcept override
std::atomic< bool > _asyncReadActivated
static BackendRegisterer backendRegisterer
void setExceptionImpl() noexcept override
EpicsBackend(const std::string &mapfile="")
BackendRegisterCatalogue< EpicsBackendRegisterInfo > _catalogue_mutable
We need to make the catalog mutable, since we fill it within getRegisterCatalogue()
static boost::shared_ptr< DeviceBackend > createInstance(std::string address, std::map< std::string, std::string > parameters)
std::atomic< bool > _channelAccessUp
boost::shared_ptr< NDRegisterAccessor< UserType > > getRegisterAccessor_impl(const RegisterPath &registerPathName, size_t numberOfWords, size_t wordOffsetInRegister, AccessModeFlags flags)
unsigned long nElems
True length of data in value.
Definition EPICSTypes.h:23
chanId chid
Definition EPICSTypes.h:20
long dbfType
Definition EPICSTypes.h:21