ChimeraTK-DeviceAccess 03.25.00
Loading...
Searching...
No Matches
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"
10
11#include <boost/bind/bind.hpp>
12
13#include <sstream>
14#include <utility>
15
16namespace 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
void setOpenedAndClearException() noexcept
Backends should call this function at the end of a (successful) open() call.
void setException(const std::string &message) noexcept final
Set the backend into an exception state.
void checkActiveException() final
Function to be called by backends when needing to check for an active exception.
bool isOpen() override
Return whether a device has been opened or not.
std::atomic< bool > _opened
flag if backend is opened
Base class for address-based device backends (e.g.
Handles the communication over TCP protocol with RebotDevice-based devices.
Definition Connection.h:19
void read(uint8_t bar, uint32_t addressInBytes, int32_t *data, size_t sizeInBytes) override
Deprecated read function using 32bit address for backwards compatibility.
RebotBackend(std::string boardAddr, std::string port, const std::string &mapFileName="", uint32_t connectionTimeout_sec=DEFAULT_CONNECTION_TIMEOUT_sec)
boost::chrono::steady_clock::time_point _lastSendTime
The time when the last command (read/write/heartbeat) was send.
void open() override
The function opens the connection to the device.
boost::thread _heartbeatThread
boost::shared_ptr< ThreadInformerMutex > _threadInformerMutex
unsigned int _connectionTimeout
void heartbeatLoop(const boost::shared_ptr< ThreadInformerMutex > &threadInformerMutex)
std::unique_ptr< RebotProtocolImplementor > _protocolImplementor
void write(uint8_t bar, uint32_t addressInBytes, int32_t const *data, size_t sizeInBytes) override
Deprecated write function using 32bit address for backwards compatibility.
static const uint32_t DEFAULT_CONNECTION_TIMEOUT_sec
boost::shared_ptr< Rebot::Connection > _connection
void closeImpl() override
All backends derrived from NumericAddressedBackend must implement closeImpl() instead of close.
static boost::shared_ptr< DeviceBackend > createInstance(std::string address, std::map< std::string, std::string > parameters)
Exception thrown when a logic error has occured.
Definition Exception.h:51
Exception thrown when a runtime error has occured.
Definition Exception.h:18
const char * what() const noexcept override
Return the message describing what exactly went wrong.
Definition Exception.cpp:14
boost::chrono::steady_clock::time_point now()
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...
std::unique_ptr< RebotProtocolImplementor > getProtocolImplementor(boost::shared_ptr< Rebot::Connection > &c)
uint32_t parseRxServerHello(const std::vector< uint32_t > &serverHello)
uint32_t getProtocolVersion(boost::shared_ptr< Rebot::Connection > &c)
STL namespace.