ChimeraTK-DeviceAccess  03.18.00
RebotBackend.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 
4 #include "RebotBackend.h"
5 
6 #include "Connection.h"
7 #include "RebotProtocol1.h"
9 #include "testableRebotSleep.h"
10 
11 #include <boost/bind/bind.hpp>
12 
13 #include <sstream>
14 #include <utility>
15 
16 namespace ChimeraTK {
17 
18  std::unique_ptr<RebotProtocolImplementor> getProtocolImplementor(boost::shared_ptr<Rebot::Connection>& c);
19  uint32_t getProtocolVersion(boost::shared_ptr<Rebot::Connection>& c);
20  uint32_t parseRxServerHello(const std::vector<uint32_t>& serverHello);
21 
22  std::unique_ptr<RebotProtocolImplementor> getProtocolImplementor(boost::shared_ptr<Rebot::Connection>& c) {
23  auto serverVersion = getProtocolVersion(c);
24  if(serverVersion == 0) {
25  return std::make_unique<RebotProtocol0>(c);
26  }
27  if(serverVersion == 1) {
28  return std::make_unique<RebotProtocol1>(c);
29  }
30  c->close();
31  std::stringstream errorMessage;
32  errorMessage << "Server protocol version " << serverVersion << " not supported!";
33  throw ChimeraTK::runtime_error(errorMessage.str());
34  }
35 
36  uint32_t getProtocolVersion(boost::shared_ptr<Rebot::Connection>& c) {
37  // send a negotiation to the server:
38  // sendClientProtocolVersion
39  std::vector<uint32_t> clientHelloMessage;
40  clientHelloMessage.push_back(Rebot::HELLO_TOKEN);
41  clientHelloMessage.push_back(Rebot::MAGIC_WORD);
42  clientHelloMessage.push_back(Rebot::CLIENT_PROTOCOL_VERSION);
43 
44  c->write(clientHelloMessage);
45 
46  // Kludge is needed to work around server bug.
47  // We have a bug with the old version were only one word is returned for
48  // multiple unrecognized command. Fetching one word for the 3 words send is a
49  // workaround.
50  auto serverHello = c->read(1);
51 
52  if(serverHello.at(0) == static_cast<uint32_t>(Rebot::UNKNOWN_INSTRUCTION)) {
53  return 0; // initial protocol version 0.0
54  }
55 
56  auto remainingBytesOfAValidServerHello = c->read(Rebot::LENGTH_OF_HELLO_TOKEN_MESSAGE - 1);
57 
58  serverHello.insert(
59  serverHello.end(), remainingBytesOfAValidServerHello.begin(), remainingBytesOfAValidServerHello.end());
60  return parseRxServerHello(serverHello);
61  }
62 
63  uint32_t parseRxServerHello(const std::vector<uint32_t>& serverHello) {
64  // 3 rd element/word is the version word
65  return serverHello.at(2);
66  }
67 
69  std::string boardAddr, std::string port, const std::string& mapFileName, uint32_t connectionTimeout_sec)
70  : NumericAddressedBackend(mapFileName), _boardAddr(std::move(boardAddr)), _port(std::move(port)),
71  _threadInformerMutex(boost::make_shared<ThreadInformerMutex>()),
72  _connection(boost::make_shared<Rebot::Connection>(_boardAddr, _port, connectionTimeout_sec)),
73  _lastSendTime(testable_rebot_sleep::now()), _connectionTimeout(Rebot::DEFAULT_CONNECTION_TIMEOUT),
74  _heartbeatThread([&]() { heartbeatLoop(_threadInformerMutex); }) {}
75 
77  try {
78  { // extra scope for the lock guard
79  std::lock_guard<std::mutex> lock(_threadInformerMutex->mutex);
80  // make sure the thread does not access any hardware when it gets the lock
81  _threadInformerMutex->quitThread = true;
82 
83  } // end of the lock guard scope. We have to release the lock before waiting
84 
85  // for the thread to join
86  _heartbeatThread.interrupt();
87  _heartbeatThread.join();
88  }
89  catch(boost::system::system_error&) {
90  std::terminate();
91  }
92  }
93 
95  std::lock_guard<std::mutex> lock(_threadInformerMutex->mutex);
96 
97  _connection->open();
98 
101 
103  }
104 
105  void RebotBackend::read(uint8_t /*bar*/, uint32_t addressInBytes, int32_t* data, size_t sizeInBytes) {
106  std::lock_guard<std::mutex> lock(_threadInformerMutex->mutex);
107 
108  if(!isOpen()) {
109  throw ChimeraTK::logic_error("Device is closed");
110  }
112 
114  _protocolImplementor->read(addressInBytes, data, sizeInBytes);
115  }
116 
117  void RebotBackend::write(uint8_t /*bar*/, uint32_t addressInBytes, int32_t const* data, size_t sizeInBytes) {
118  std::lock_guard<std::mutex> lock(_threadInformerMutex->mutex);
119 
120  if(!isOpen()) {
121  throw ChimeraTK::logic_error("Device is closed");
122  }
124 
126  _protocolImplementor->write(addressInBytes, data, sizeInBytes);
127  }
128 
130  std::lock_guard<std::mutex> lock(_threadInformerMutex->mutex);
131 
132  _opened = false;
133  _connection->close();
134  _protocolImplementor.reset(nullptr);
135  }
136 
137  // FIXME #11279 Implement API breaking changes from linter warnings
138  // NOLINTBEGIN(performance-unnecessary-value-param)
139  boost::shared_ptr<DeviceBackend> RebotBackend::createInstance(
140  std::string /*address*/, std::map<std::string, std::string> parameters) {
141  // NOLINTEND(performance-unnecessary-value-param)
142  if(parameters["ip"].empty()) {
143  throw ChimeraTK::logic_error("TMCB IP address not found in the parameter list");
144  }
145  if(parameters["port"].empty()) {
146  throw ChimeraTK::logic_error("TMCB port number not found in the parameter list");
147  }
148 
149  std::string tmcbIP = parameters["ip"];
150  std::string portNumber = parameters["port"];
151  std::string mapFileName = parameters["map"];
153 
154  auto it = parameters.find("timeout");
155  if(it != parameters.end()) {
156  timeout = static_cast<uint32_t>(std::stoul(it->second));
157  }
158  return boost::shared_ptr<RebotBackend>(new RebotBackend(tmcbIP, portNumber, mapFileName, timeout));
159  }
160 
161  void RebotBackend::heartbeatLoop(const boost::shared_ptr<ThreadInformerMutex>& threadInformerMutex) {
162  while(true) {
163  try {
164  // only send a heartbeat if the connection was inactive for half of the
165  // timeout period
166 
167  // We can only calculate this while holding the lock (because we need _lastSendTime), but we have to use
168  // it when not holding the lock because we are calling sleep. Hence we have to store the next wakekup time in a
169  // variable.
170  boost::chrono::steady_clock::time_point wakeupTime;
171 
172  { // scope of the lock guard. We must hold it to safely access _lastSendTime, which we need in the if statement
173  std::lock_guard<std::mutex> lock(_threadInformerMutex->mutex);
174  if((testable_rebot_sleep::now() - _lastSendTime) > boost::chrono::milliseconds(_connectionTimeout / 2)) {
175  // To handle the race condition that the thread woke up while the
176  // desructor was holding the lock and closes the socket: Check the flag
177  // and quit if set
178  if(threadInformerMutex->quitThread) {
179  break;
180  }
181  // always update the last send time. Otherwise the sleep will be
182  // ineffective for a closed connection and go to 100 & CPU load
185  _protocolImplementor->sendHeartbeat();
186  }
187  }
188  // Sleep for half of the connection timeout (plus 1 ms)
189  wakeupTime = _lastSendTime + boost::chrono::milliseconds(_connectionTimeout / 2 + 1);
190  } // scope of the lock guard
191 
192  // sleep without holding the lock.
194  }
195  catch(ChimeraTK::runtime_error& e) {
196  setException(std::string("RebotBackend: Sending heartbeat failed. Caught exception: ") + e.what());
197  }
198  } // while true
199  }
200 
201 } // namespace ChimeraTK
ChimeraTK::RebotBackend::open
void open() override
The function opens the connection to the device.
Definition: RebotBackend.cc:94
ChimeraTK::runtime_error::what
const char * what() const noexcept override
Return the message describing what exactly went wrong.
Definition: Exception.cpp:14
ChimeraTK::RebotBackend::write
void write(uint8_t bar, uint32_t addressInBytes, int32_t const *data, size_t sizeInBytes) override
Definition: RebotBackend.cc:117
ChimeraTK::RebotBackend::read
void read(uint8_t bar, uint32_t addressInBytes, int32_t *data, size_t sizeInBytes) override
Definition: RebotBackend.cc:105
ChimeraTK::DeviceBackendImpl::setOpenedAndClearException
void setOpenedAndClearException() noexcept
Backends should call this function at the end of a (successful) open() call.
Definition: DeviceBackendImpl.cc:12
ChimeraTK::testable_rebot_sleep::now
boost::chrono::steady_clock::time_point now()
Definition: testableRebotSleep.cc:7
ChimeraTK::RebotBackend::_threadInformerMutex
boost::shared_ptr< ThreadInformerMutex > _threadInformerMutex
Definition: RebotBackend.h:49
ChimeraTK::Rebot::Connection
Handles the communication over TCP protocol with RebotDevice-based devices.
Definition: Connection.h:19
ChimeraTK::testable_rebot_sleep::sleep_until
void sleep_until(boost::chrono::steady_clock::time_point t)
There are two implementations with the same signature: One that calls boost::thread::this_thread::sle...
Definition: testableRebotSleep.cc:16
ChimeraTK::getProtocolVersion
uint32_t getProtocolVersion(boost::shared_ptr< Rebot::Connection > &c)
Definition: RebotBackend.cc:36
ChimeraTK::DeviceBackendImpl::_opened
std::atomic< bool > _opened
flag if backend is opened
Definition: DeviceBackendImpl.h:60
ChimeraTK::DeviceBackendImpl::checkActiveException
void checkActiveException() final
Function to be called by backends when needing to check for an active exception.
Definition: DeviceBackendImpl.h:94
ChimeraTK::runtime_error
Exception thrown when a runtime error has occured.
Definition: Exception.h:18
RebotProtocol1.h
testableRebotSleep.h
ChimeraTK::getProtocolImplementor
std::unique_ptr< RebotProtocolImplementor > getProtocolImplementor(boost::shared_ptr< Rebot::Connection > &c)
Definition: RebotBackend.cc:22
ChimeraTK::parseRxServerHello
uint32_t parseRxServerHello(const std::vector< uint32_t > &serverHello)
Definition: RebotBackend.cc:63
ChimeraTK::NumericAddressedBackend
Base class for address-based device backends (e.g.
Definition: NumericAddressedBackend.h:20
ChimeraTK::DeviceBackendImpl::setException
void setException(const std::string &message) noexcept final
Set the backend into an exception state.
Definition: DeviceBackendImpl.cc:26
ChimeraTK::RebotBackend::_heartbeatThread
boost::thread _heartbeatThread
Definition: RebotBackend.h:76
ChimeraTK::RebotBackend::RebotBackend
RebotBackend(std::string boardAddr, std::string port, const std::string &mapFileName="", uint32_t connectionTimeout_sec=DEFAULT_CONNECTION_TIMEOUT_sec)
Definition: RebotBackend.cc:68
ChimeraTK::RebotBackend::heartbeatLoop
void heartbeatLoop(const boost::shared_ptr< ThreadInformerMutex > &threadInformerMutex)
Definition: RebotBackend.cc:161
ChimeraTK::RebotBackend::closeImpl
void closeImpl() override
All backends derrived from NumericAddressedBackend must implement closeImpl() instead of close.
Definition: RebotBackend.cc:129
ChimeraTK::ThreadInformerMutex
Definition: RebotBackend.h:39
RebotBackend.h
Connection.h
ChimeraTK::RebotBackend::_lastSendTime
boost::chrono::steady_clock::time_point _lastSendTime
The time when the last command (read/write/heartbeat) was send.
Definition: RebotBackend.h:55
ChimeraTK::RebotBackend::_connection
boost::shared_ptr< Rebot::Connection > _connection
Definition: RebotBackend.h:52
ChimeraTK::DeviceBackendImpl::isOpen
bool isOpen() override
Return whether a device has been opened or not.
Definition: DeviceBackendImpl.h:27
ChimeraTK::RebotBackend::createInstance
static boost::shared_ptr< DeviceBackend > createInstance(std::string address, std::map< std::string, std::string > parameters)
Definition: RebotBackend.cc:139
ChimeraTK::RebotBackend::DEFAULT_CONNECTION_TIMEOUT_sec
const static uint32_t DEFAULT_CONNECTION_TIMEOUT_sec
Definition: RebotBackend.h:78
ChimeraTK::RebotBackend::~RebotBackend
~RebotBackend() override
Definition: RebotBackend.cc:76
ChimeraTK
Definition: DummyBackend.h:16
ChimeraTK::RebotBackend::_connectionTimeout
unsigned int _connectionTimeout
Definition: RebotBackend.h:56
ChimeraTK::logic_error
Exception thrown when a logic error has occured.
Definition: Exception.h:51
RebotProtocolDefinitions.h
ChimeraTK::RebotBackend::_protocolImplementor
std::unique_ptr< RebotProtocolImplementor > _protocolImplementor
Definition: RebotBackend.h:53