pilz_modbus_client.cpp
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2018 Pilz GmbH & Co. KG
3  *
4  * This program is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU Lesser General Public License as published by
6  * the Free Software Foundation, either version 3 of the License, or
7  * (at your option) any later version.
8 
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12  * GNU Lesser General Public License for more details.
13 
14  * You should have received a copy of the GNU Lesser General Public License
15  * along with this program. If not, see <http://www.gnu.org/licenses/>.
16  */
17 
19 
20 #include <prbt_hardware_support/ModbusMsgInStamped.h>
24 
25 namespace prbt_hardware_support
26 {
27 
29  const std::vector<unsigned short>& registers_to_read,
30  ModbusClientUniquePtr modbus_client,
31  unsigned int response_timeout_ms,
32  const std::string& modbus_read_topic_name,
33  const std::string& modbus_write_service_name,
34  double read_frequency_hz)
35  : registers_to_read(registers_to_read)
36  , RESPONSE_TIMEOUT_MS(response_timeout_ms)
37  , READ_FREQUENCY_HZ(read_frequency_hz)
38  , modbus_client_(std::move(modbus_client))
39  , modbus_read_pub_(nh.advertise<ModbusMsgInStamped>(modbus_read_topic_name, DEFAULT_QUEUE_SIZE_MODBUS))
40  , modbus_write_service_( nh.advertiseService(modbus_write_service_name,
41  &PilzModbusClient::modbus_write_service_cb,
42  this) )
43 {
44 }
45 
46 
47 bool PilzModbusClient::init(const char* ip, unsigned int port,
48  unsigned int retries, ros::Duration timeout)
49 {
50  for(size_t retry_n = 0; retry_n < retries; ++retry_n)
51  {
52  if(init(ip, port))
53  {
54  return true;
55  }
56 
57  ROS_ERROR_STREAM("Connection to " << ip << ":" << port << " failed. Try(" << retry_n+1 << "/" << retries << ")");
58 
59  if(!ros::ok())
60  {
61  break; // LCOV_EXCL_LINE Simple functionality but hard to test
62  }
63  timeout.sleep();
64  }
65 
66  return false;
67 }
68 
69 
70 bool PilzModbusClient::init(const char* ip, unsigned int port)
71 {
72  State expectedState {State::not_initialized};
73  if (!state_.compare_exchange_strong(expectedState, State::initializing))
74  {
75  ROS_ERROR_STREAM("Modbus-client not in correct state: " << state_ << "expected:" << State::initializing);
76  state_ = State::not_initialized;
77  return false;
78  }
79 
80  if (!modbus_client_->init(ip, port))
81  {
82  ROS_ERROR("Init failed");
83  state_ = State::not_initialized;
84  return false;
85  }
86 
87  modbus_client_->setResponseTimeoutInMs(RESPONSE_TIMEOUT_MS);
88 
89  state_ = State::initialized;
90  ROS_DEBUG_STREAM("Connection to " << ip << ":" << port << " established");
91  return true;
92 }
93 
95 {
96  ModbusMsgInStamped msg;
97  msg.disconnect.data = true;
98  msg.header.stamp = ros::Time::now();
100  ros::spinOnce();
101 }
102 
104 {
105  State expectedState {State::initialized};
106  if (!state_.compare_exchange_strong(expectedState, State::running))
107  {
108  ROS_ERROR_STREAM("Modbus-client not in correct state: " << state_ << "expected:" << State::running);
109  throw PilzModbusClientException("Modbus-client not in correct state.");
110  }
111 
112  RegCont holding_register;
113  RegCont last_holding_register;
114  ros::Time last_update {ros::Time::now()};
115  state_ = State::running;
117  while ( ros::ok() && !stop_run_.load() )
118  {
119  // Work with local copy of buffer to ensure that the service callback
120  // function does not become blocked
121  boost::optional<ModbusRegisterBlock> write_reg_bock {boost::none};
122  {
123  std::lock_guard<std::mutex> lock(write_reg_blocks_mutex_);
124  if (!write_reg_blocks_.empty())
125  {
126  write_reg_bock = write_reg_blocks_.front();
127  // Mark data as send/processed, by "deleting" them from memory
128  write_reg_blocks_.pop();
129  }
130  }
131 
132  std::vector<std::vector<unsigned short>> blocks = split_into_blocks(registers_to_read);
133 
134  unsigned short index_of_first_register = *std::min_element(registers_to_read.begin(), registers_to_read.end());
135  int num_registers = *std::max_element(registers_to_read.begin(), registers_to_read.end()) - index_of_first_register + 1;
136  holding_register = RegCont(num_registers, 0);
137 
138  ROS_DEBUG("blocks.size() %d", blocks.size());
139  try
140  {
141  for(auto &block : blocks){
142  ROS_DEBUG("block.size() %d", block.size());
143  unsigned short index_of_first_register_block = *(block.begin());
144  unsigned long num_registers_block = block.size();
145  RegCont block_holding_register;
146  if (write_reg_bock)
147  {
148  block_holding_register = modbus_client_->writeReadHoldingRegister(static_cast<int>(write_reg_bock->start_idx),
149  write_reg_bock->values,
150  static_cast<int>(index_of_first_register_block),
151  static_cast<int>(num_registers_block));
152  // write only once:
153  write_reg_bock = boost::none;
154  }
155  else
156  {
157  block_holding_register = modbus_client_->readHoldingRegister(static_cast<int>(index_of_first_register_block), static_cast<int>(num_registers_block));
158  }
159  for(uint i = 0; i < num_registers_block; i++)
160  holding_register[i+index_of_first_register_block-index_of_first_register] = block_holding_register[i];
161  }
162  }
163  catch(ModbusExceptionDisconnect &e)
164  {
165  ROS_ERROR_STREAM("Modbus disconnect: " << e.what());
167  break;
168  }
169 
170  ModbusMsgInStampedPtr msg {
171  ModbusMsgInBuilder::createDefaultModbusMsgIn(index_of_first_register, holding_register)
172  };
173 
174  // Publish the received data into ROS
175  if(holding_register != last_holding_register)
176  {
177  ROS_DEBUG_STREAM("Sending new ROS-message.");
178  msg->header.stamp = ros::Time::now();
179  last_update = msg->header.stamp;
180  last_holding_register = holding_register;
181  }
182  else
183  {
184  msg->header.stamp = last_update;
185  }
187 
188  ros::spinOnce();
189  rate.sleep();
190  }
191 
192  stop_run_ = false;
193  state_ = State::not_initialized;
194 }
195 
196 std::vector<std::vector<unsigned short>> PilzModbusClient::split_into_blocks(std::vector<unsigned short> &in){
197  std::vector<std::vector<unsigned short>> out;
198  std::sort(in.begin(), in.end()); // sort just in case to be more user-friendly
199  unsigned short prev{0};
200  std::vector<unsigned short> current_block;
201  for (auto & reg : in){
202  if(reg == prev){
203  throw PilzModbusClientException("List elemts must be unique.");
204  }
205  else if(reg == prev + 1) {
206  current_block.push_back(reg);
207  }
208  else { // *it >= prev + 1
209  std::vector<unsigned short> to_out(current_block);
210  if(to_out.size() > 0) {
211  out.push_back(to_out);
212  }
213  current_block.clear();
214  current_block.push_back(reg);
215  }
216  prev = reg;
217  }
218  std::vector<unsigned short> to_out(current_block);
219  if(to_out.size() > 0) {
220  out.push_back(to_out);
221  }
222  return out;
223 }
224 
225 } // namespace prbt_hardware_support
msg
void run()
Publishes the register values as messages.
void publish(const boost::shared_ptr< M > &message) const
std::vector< uint16_t > RegCont
Convenience data type defining the data type for a collection of registers.
Expection thrown by prbt_hardware_support::PilzModbusClient.
bool init(const char *ip, unsigned int port)
Tries to connect to a modbus server.
bool sleep() const
ServiceServer advertiseService(ros::NodeHandle n, std::string name, boost::function< bool(const typename ActionSpec::_action_goal_type::_goal_type &, typename ActionSpec::_action_result_type::_result_type &result)> service_cb)
const unsigned int RESPONSE_TIMEOUT_MS
Defines how long we wait for a response from the Modbus-server.
PilzModbusClient(ros::NodeHandle &nh, const std::vector< unsigned short > &registers_to_read, ModbusClientUniquePtr modbus_client, unsigned int response_timeout_ms, const std::string &modbus_read_topic_name, const std::string &modbus_write_service_name, double read_frequency_hz=DEFAULT_MODBUS_READ_FREQUENCY_HZ)
Sets up publisher. To open the modbus connection call PilzModbusClient::init.
static ModbusMsgInStampedPtr createDefaultModbusMsgIn(const std_msgs::MultiArrayLayout::_data_offset_type &offset, const RegCont &holding_register)
Creates a standard ModbusMsgIn which contains default values for all essential elements of the messag...
Connects to a modbus server and publishes the received data into ROS.
ROSCPP_DECL bool ok()
#define ROS_DEBUG_STREAM(args)
bool sleep()
std::vector< unsigned short > registers_to_read
Registers which have to be read.
static std::vector< std::vector< unsigned short > > split_into_blocks(std::vector< unsigned short > &in)
Splits a vector of integers into a vector of vectors with consecutive groups.
static Time now()
std::unique_ptr< ModbusClient > ModbusClientUniquePtr
std::queue< ModbusRegisterBlock > write_reg_blocks_
#define ROS_ERROR_STREAM(args)
ROSCPP_DECL void spinOnce()
#define ROS_ERROR(...)
Expection thrown by prbt_hardware_support::LibModbusClient::readHoldingRegister if a disconnect from ...
const double READ_FREQUENCY_HZ
Defines how often the Modbus registers are read in.
#define ROS_DEBUG(...)


prbt_hardware_support
Author(s):
autogenerated on Tue Feb 2 2021 03:50:17