cob_bms_driver_node.cpp
Go to the documentation of this file.
1 /*
2  * Copyright 2017 Fraunhofer Institute for Manufacturing Engineering and Automation (IPA)
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9 
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 
18 #include <std_msgs/Float64.h>
19 #include <std_msgs/Int64.h>
20 #include <std_msgs/UInt64.h>
21 #include <std_msgs/Bool.h>
22 #include <XmlRpcException.h>
23 
24 #include <stdint.h>
25 #include <endian.h>
26 #include <boost/format.hpp>
27 
29 
30 using boost::make_shared;
31 
32 //template to be used to read CAN frames received from the BMS
33 template<int N> void big_endian_to_host(const void* in, void* out);
34 template<> void big_endian_to_host<1>(const void* in, void* out){ *(uint8_t*)out = *(uint8_t*)in;}
35 template<> void big_endian_to_host<2>(const void* in, void* out){ *(uint16_t*)out = be16toh(*(uint16_t*)in);}
36 template<> void big_endian_to_host<4>(const void* in, void* out){ *(uint32_t*)out = be32toh(*(uint32_t*)in);}
37 template<> void big_endian_to_host<8>(const void* in, void* out){ *(uint64_t*)out = be64toh(*(uint64_t*)in);}
38 
39 template<typename T> T read_value(const can::Frame &f, uint8_t offset){
40  T res;
41  big_endian_to_host<sizeof(T)>(&f.data[offset], &res);
42  return res;
43 }
44 template<typename T> bool readTypedValue(const can::Frame &f, const BmsParameter &param, T &data){
45  switch (param.length)
46  {
47  case 1:
48  data = param.is_signed ? read_value<int8_t> (f, param.offset) : read_value<uint8_t> (f, param.offset);
49  break;
50 
51  case 2:
52  data = param.is_signed ? read_value<int16_t> (f, param.offset) : read_value<uint16_t> (f, param.offset);
53  break;
54 
55  case 4:
56  data = param.is_signed ? read_value<int32_t> (f, param.offset) : read_value<uint32_t> (f, param.offset);
57  break;
58 
59  default:
60  ROS_WARN_STREAM("Unknown length of BmsParameter: " << param.kv.key << ". Cannot read data!");
61  return false;
62  }
63  return true;
64 }
65 
66 template<typename T> struct TypedBmsParameter : BmsParameter {
67  T msg_;
68 
69  void publish(){
70  //if the BmsParameter is a topic, publish data to the topic
71  if (static_cast<void*>(publisher))
72  {
73  publisher.publish(msg_);
74  }
75  }
76  virtual void advertise(ros::NodeHandle &nh, const std::string &topic){
77  publisher = nh.advertise<T> (topic, 1, true);
78  }
79 };
80 
81 struct FloatBmsParameter : TypedBmsParameter<std_msgs::Float64> {
82  double factor;
83  FloatBmsParameter(double factor) : factor(factor) {}
84  void update(const can::Frame &f){
85  readTypedValue(f, *this, msg_.data);
86  msg_.data *= factor;
87 
88  //save data for diagnostics updater (and round to two digits for readability)
89  std::stringstream sstream;
90  sstream << std::setprecision(2) << msg_.data;
91  kv.value = sstream.str();
92  publish();
93  }
94 };
95 
96 struct IntBmsParameter : TypedBmsParameter<std_msgs::Int64> {
98  void update(const can::Frame &f){
99  readTypedValue(f, *this, msg_.data);
100 
101  kv.value = (boost::format("%lld") % msg_.data).str();
102  publish();
103  }
104 };
105 
106 struct UIntBmsParameter : TypedBmsParameter<std_msgs::UInt64> {
108  void update(const can::Frame &f){
109  readTypedValue(f, *this, msg_.data);
110 
111  kv.value = (boost::format("%llu") % msg_.data).str();
112  publish();
113  }
114 };
115 
116 struct BooleanBmsParameter : TypedBmsParameter<std_msgs::Bool> {
117  int bit_mask;
118  BooleanBmsParameter(int bit_mask) : bit_mask(bit_mask) { is_signed = true; }
119  void update(const can::Frame &f){
120  int value;
121  readTypedValue(f, *this, value);
122  msg_.data = (value & bit_mask) == bit_mask;
123 
124  kv.value = msg_.data ? "True" : "False";
125  publish();
126  }
127 };
128 
130 : nh_priv_("~")
131 {}
132 
134 {
136 }
137 
138 //initlializes SocketCAN interface, saves data from ROS parameter server, loads polling lists and sets up diagnostic updater
140 {
141  //reads parameters from ROS parameter server and saves them in respective member variable: config_map_, poll_period_for_two_ids_in_ms_, can_device_, bms_id_to_poll_
142  if (!getParams())
143  {
144  ROS_ERROR("Could not prepare driver for start up");
145  return false;
146  }
147 
149 
150  //initalize polling lists iterators
153 
154  updater_.setHardwareID("bms");
155  updater_.add("cob_bms_dagnostics_updater", this, &CobBmsDriverNode::produceDiagnostics);
156 
158 
159  //initialize the socketcan interface
160  if(!socketcan_interface_.init(can_device_, false)) {
161  ROS_ERROR("cob_bms_driver initialization failed");
162  return false;
163  }
164 
165  //create listener for CAN frames
167 
168  return true;
169 }
170 
171 //function to get ROS parameters from parameter server
173 {
174  //local declarations
175  XmlRpc::XmlRpcValue diagnostics;
176  std::vector <std::string> topics;
177  int poll_frequency_hz;
178 
179  if (!nh_priv_.getParam("topics", topics))
180  {
181  ROS_INFO_STREAM("Did not find \"topics\" on parameter server");
182  }
183  if (topics.empty())
184  {
185  ROS_INFO("Topic list is empty. No publisher created");
186  }
187 
188  if (!nh_priv_.getParam("diagnostics", diagnostics))
189  {
190  ROS_ERROR_STREAM("Did not find \"diagnostics\" on parameter server");
191  return false;
192  }
193  try {
194  if(!loadConfigMap(diagnostics, topics)) return false;
195  }
196  catch(XmlRpc::XmlRpcException &e){
197  ROS_ERROR_STREAM("Could not parse 'diagnostics': "<< e.getMessage());
198  return false;
199  }
200 
201  if (!topics.empty())
202  {
203  for(std::vector<std::string>::iterator it = topics.begin(); it != topics.end(); ++it){
204  ROS_ERROR_STREAM("Could not find entry for topic '" << *it << "'.");
205  }
206  return false;
207  }
208 
209  if (!nh_priv_.getParam("can_device", can_device_))
210  {
211  ROS_INFO_STREAM("Did not find \"can_device\" on parameter server. Using default value: can0");
212  can_device_ = "can0";
213  }
214 
215  if (!nh_priv_.getParam("bms_id_to_poll", bms_id_to_poll_))
216  {
217  ROS_INFO_STREAM("Did not find \"bms_id_to_poll\" on parameter server. Using default value: 0x200");
218  bms_id_to_poll_ = 0x200;
219  }
220 
221  if (!nh_priv_.getParam("poll_frequency_hz", poll_frequency_hz))
222  {
223  ROS_INFO_STREAM("Did not find \"poll_frequency\" on parameter server. Using default value: 20 Hz");
224  poll_frequency_hz = 20;
225  }
226  evaluatePollPeriodFrom(poll_frequency_hz);
227  return true;
228 }
229 
230 //function to interpret the diagnostics XmlRpcValue and save data in config_map_
231 bool CobBmsDriverNode::loadConfigMap(XmlRpc::XmlRpcValue &diagnostics, std::vector<std::string> &topics)
232 {
233  //for each id in list of ids
234  for (size_t i = 0; i < diagnostics.size(); ++i)
235  {
236  uint8_t id;
237  XmlRpc::XmlRpcValue config = diagnostics[i];
238 
239  if(!config.hasMember("id")) {
240  ROS_ERROR_STREAM("diagnostics[" << i << "]: id is missing.");
241  return false;
242  }
243  if(!config.hasMember("fields")) {
244  ROS_ERROR_STREAM("diagnostics[" << i << "]: fields is missing.");
245  return false;
246  }
247  id = static_cast<uint8_t>(static_cast<int>(config["id"]));
248 
249  XmlRpc::XmlRpcValue fields = config["fields"];
250  bool publishes = false;
251 
252  for(int32_t j=0; j<fields.size(); ++j)
253  {
254  XmlRpc::XmlRpcValue field = fields[j];
255  if(!field.hasMember("name")){
256  ROS_ERROR_STREAM("diagnostics[" << i << "]: fields[" << j << "]: name is missing.");
257  return false;
258  }
259  std::string name = static_cast<std::string>(field["name"]);
260 
261  if(!field.hasMember("len")){
262  ROS_ERROR_STREAM("diagnostics[" << i << "]: fields[" << j << "]: len is missing.");
263  return false;
264  }
265  int len = static_cast<int>(field["len"]);
266 
267  BmsParameter::Ptr entry;
268  if(field.hasMember("bit_mask")){
269  int bit_mask = static_cast<int>(field["bit_mask"]);
270  if(bit_mask & ~((1<<(len*8))-1)){
271  ROS_ERROR_STREAM("diagnostics[" << i << "]: fields[" << j << "]: bit_mask does fit not into type of length " << len);
272  return false;
273  }
274  entry = make_shared<BooleanBmsParameter>(bit_mask);
275  entry->kv.key = name;
276  }else{
277  if(!field.hasMember("is_signed")){
278  ROS_ERROR_STREAM("diagnostics[" << i << "]: fields[" << j << "]: is_signed is missing.");
279  return false;
280  }
281  if(field.hasMember("factor")){
282  double factor = 1.0;
283  if(field.hasMember("factor")){
284  factor = static_cast<double>(field["factor"]);
285  }
286 
287  entry = make_shared<FloatBmsParameter>(factor);
288 
289  entry->is_signed = static_cast<bool>(field["is_signed"]);
290 
291  if(field.hasMember("unit")){
292  entry->kv.key = name + "[" + static_cast<std::string>(field["unit"]) + "]";
293  }else{
294  entry->kv.key = name;
295  }
296  }else{
297  if(static_cast<bool>(field["is_signed"])){
298  entry = make_shared<IntBmsParameter>();
299  }else{
300  entry = make_shared<UIntBmsParameter>();
301  }
302  entry->is_signed = static_cast<bool>(field["is_signed"]);
303  entry->kv.key = name;
304  }
305  }
306 
307  if(!field.hasMember("offset")){
308  ROS_ERROR_STREAM("diagnostics[" << i << "]: fields[" << j << "]: offset is missing.");
309  return false;
310  }
311  entry->offset = static_cast<int>(field["offset"]);
312 
313  entry->length = len;
314 
315  std::vector<std::string>::iterator topic_it = find(topics.begin(), topics.end(), name);
316  if(topic_it != topics.end()){
317  entry->advertise(nh_priv_, name);
318  topics.erase(topic_it);
319  publishes = true;
320  ROS_INFO_STREAM("Created publisher for: " << name);
321  }
322 
323  config_map_.insert(std::make_pair(id, entry));
324 
325  }
326  if(publishes) polling_list1_.push_back(id);
327  else polling_list2_.push_back(id);
328 
329  ROS_INFO_STREAM("Got "<< fields.size() << " BmsParameter(s) with CAN-ID: 0x" << std::hex << (unsigned int) id << std::dec);
330  }
331  return true;
332 }
333 
334 //helper function to evaluate poll period from given poll frequency
335 void CobBmsDriverNode::evaluatePollPeriodFrom(int poll_frequency_hz)
336 {
337  //check the validity of given poll_frequency_hz
338  if ((poll_frequency_hz < 0) && (poll_frequency_hz > 20))
339  {
340  ROS_WARN_STREAM("Invalid ROS parameter value: poll_frequency_hz = "<< poll_frequency_hz << ". Setting poll_frequency_hz to 20 Hz");
341  poll_frequency_hz = 20;
342  }
343  //now evaluate and save poll period
344  poll_period_for_two_ids_in_ms_ = (1/double(poll_frequency_hz))*1000;
345  ROS_INFO_STREAM("Evaluated polling period: "<< poll_period_for_two_ids_in_ms_ << " ms");
346 }
347 
348 //function that goes through config_map_ and fills polling_list1_ and polling_list2_. If topics are found on ROS Parameter Server, they are kept in list1 otherwise, all parameter id are divided between both lists.
350 {
351  if(polling_list1_.size() == 0){ // no topics, so just distribute topics
352  while(polling_list1_.size() < polling_list2_.size()){
353  polling_list1_.push_back(polling_list2_.back());
354  polling_list2_.pop_back();
355  }
356  }else{
357  while(polling_list1_.size() > polling_list2_.size()){
358  polling_list2_.push_back(polling_list1_.back());
359  polling_list1_.pop_back();
360  }
361  }
362  ROS_INFO_STREAM("Loaded \'"<< polling_list1_.size() << "\' CAN-ID(s) in polling_list1_ and \'"<< polling_list2_.size() <<"\' CAN-ID(s) in polling_list2_");
363 }
364 
365 //function that polls BMS for given ids
366 void CobBmsDriverNode::pollBmsForIds(const uint16_t first_id, const uint16_t second_id)
367 {
368  can::Frame f(can::Header(bms_id_to_poll_,false,false,false),4);
369  f.data[0] = first_id >> 8; //high_byte
370  f.data[1] = first_id & 0xff; //low_byte
371  f.data[2] = second_id >> 8;
372  f.data[3] = second_id & 0xff;
373 
374  socketcan_interface_.send(f);
375 
376  boost::this_thread::sleep_for(boost::chrono::milliseconds(poll_period_for_two_ids_in_ms_));
377 }
378 
379 //cycles through polling lists and sends 2 ids at a time (one from each list) to the BMS
381 {
382  //restart if reached the end of polling lists
385  //clear stat_, so that it can be refilled with new data
386  stat_.clear();
387 
388  uint16_t first_id = (polling_list1_it_ == polling_list1_.end()) ? 0 : (*polling_list1_it_ | 0x0100);
389  uint16_t second_id = (polling_list2_it_ == polling_list2_.end()) ? 0 : (*polling_list2_it_ | 0x0100);
390 
391  ROS_DEBUG_STREAM("polling BMS for CAN-IDs: 0x" << std::hex << (int)first_id << " and 0x" << (int) second_id << std::dec);
392 
393  pollBmsForIds(first_id,second_id);
394 
395  //increment iterators for next poll
396  if (!polling_list1_.empty()) ++polling_list1_it_;
397  if (!polling_list2_.empty()) ++polling_list2_it_;
398 }
399 
400 //callback function to handle all types of frames received from BMS
402 {
403  boost::mutex::scoped_lock lock(data_mutex_);
404 
405  //id to find in config map
406  std::pair<ConfigMap::iterator, ConfigMap::iterator> range = config_map_.equal_range(f.id);
407 
408  for (; range.first != range.second; ++range.first)
409  {
410  range.first->second->update(f);
411  }
412 }
413 
414 //updates the diagnostics data with the new data received from BMS
416 {
417  boost::mutex::scoped_lock lock(data_mutex_);
418 
419  can::State state = socketcan_interface_.getState();
420  stat.add("error_code", state.error_code);
421  stat.add("can_error_code", state.internal_error);
422  switch (state.driver_state)
423  {
424  case can::State::closed:
425  stat.summary(diagnostic_msgs::DiagnosticStatus::ERROR, "Driver State: Closed");
426  break;
427 
428  case can::State::open:
429  stat.summary(diagnostic_msgs::DiagnosticStatus::ERROR, "Driver State: Opened");
430  break;
431 
432  case can::State::ready:
433  stat.summary(diagnostic_msgs::DiagnosticStatus::OK, "Driver State: Ready");
434  break;
435 
436  default:
437  stat.summary(diagnostic_msgs::DiagnosticStatus::ERROR, "Driver State: Unknown");
438  break;
439  }
440 
441  for (ConfigMap::iterator cm_it = config_map_.begin(); cm_it != config_map_.end(); ++cm_it)
442  {
443  stat.values.push_back(cm_it->second->kv);
444  }
445 }
446 
448 {
449  //update diagnostics
450  updater_.update();
451  if(!socketcan_interface_.getState().isReady()){
452  ROS_ERROR("Restarting BMS socketcan");
454  socketcan_interface_.recover();
455  }
456 }
457 
458 int main(int argc, char **argv)
459 {
460  ros::init(argc, argv, "bms_driver_node");
461 
462  CobBmsDriverNode cob_bms_driver_node;
463 
464  if (!cob_bms_driver_node.prepare()) return 1;
465 
466  ROS_INFO("Started polling BMS...");
467  while (ros::ok())
468  {
469  cob_bms_driver_node.pollNextInLists();
470  ros::spinOnce();
471  }
472  return 0;
473 }
const std::string & getMessage() const
ros::Publisher publisher
void update(const can::Frame &f)
void update(const can::Frame &f)
void diagnosticsTimerCallback(const ros::TimerEvent &)
void publish(const boost::shared_ptr< M > &message) const
void big_endian_to_host< 8 >(const void *in, void *out)
virtual bool init(const std::string &device, bool loopback)
f
void summary(unsigned char lvl, const std::string s)
diagnostic_updater::DiagnosticStatusWrapper stat_
int size() const
void setHardwareID(const std::string &hwid)
boost::mutex data_mutex_
std::vector< uint8_t > polling_list2_
unsigned int offset
ROSCPP_DECL void init(int &argc, char **argv, const std::string &name, uint32_t options=0)
std::vector< uint8_t >::iterator polling_list2_it_
void add(const std::string &name, TaskFunction f)
std::vector< uint8_t >::iterator polling_list1_it_
std::vector< uint8_t > polling_list1_
void big_endian_to_host< 4 >(const void *in, void *out)
boost::array< value_type, 8 > data
void pollBmsForIds(const uint16_t first_id, const uint16_t second_id)
void evaluatePollPeriodFrom(int poll_frequency)
T read_value(const can::Frame &f, uint8_t offset)
ros::NodeHandle nh_
void big_endian_to_host< 1 >(const void *in, void *out)
FloatBmsParameter(double factor)
#define ROS_INFO(...)
can::CommInterface::FrameListenerConstSharedPtr frame_listener_
diagnostic_msgs::KeyValue kv
int main(int argc, char **argv)
ROSCPP_DECL bool ok()
Timer createTimer(Rate r, Handler h, Obj o, bool oneshot=false, bool autostart=true) const
ros::NodeHandle nh_priv_
void produceDiagnostics(diagnostic_updater::DiagnosticStatusWrapper &stat)
Publisher advertise(const std::string &topic, uint32_t queue_size, bool latch=false)
bool readTypedValue(const can::Frame &f, const BmsParameter &param, T &data)
#define ROS_WARN_STREAM(args)
#define ROS_DEBUG_STREAM(args)
enum can::State::DriverState driver_state
void big_endian_to_host< 2 >(const void *in, void *out)
can::ThreadedSocketCANInterface socketcan_interface_
void handleFrames(const can::Frame &f)
bool hasMember(const std::string &name) const
void update(const can::Frame &f)
#define ROS_INFO_STREAM(args)
unsigned int length
bool getParam(const std::string &key, std::string &s) const
unsigned int internal_error
bool loadConfigMap(XmlRpc::XmlRpcValue &diagnostics, std::vector< std::string > &topics)
void add(const std::string &key, const T &val)
#define ROS_ERROR_STREAM(args)
BooleanBmsParameter(int bit_mask)
ROSCPP_DECL void spinOnce()
unsigned int id
#define ROS_ERROR(...)
void update(const can::Frame &f)
diagnostic_updater::Updater updater_
virtual void shutdown()
void big_endian_to_host(const void *in, void *out)
virtual void advertise(ros::NodeHandle &nh, const std::string &topic)
boost::system::error_code error_code


cob_bms_driver
Author(s): mig-mc , Mathias Lüdtke
autogenerated on Wed Apr 7 2021 02:11:37