dynamixel_interface_controller.cpp
Go to the documentation of this file.
1 /* CSIRO Open Source Software License Agreement (variation of the BSD / MIT License)
2  * Copyright (c) 2020, Commonwealth Scientific and Industrial Research Organisation (CSIRO) ABN 41 687 119 230.
3  * All rights reserved. CSIRO is willing to grant you a license to the dynamixel_actuator ROS packages on the following
4  * terms, except where otherwise indicated for third party material. Redistribution and use of this software in source
5  * and binary forms, with or without modification, are permitted provided that the following conditions are met:
6  * - Redistributions of source code must retain the above copyright notice, this list of conditions and the following
7  * disclaimer.
8  * - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
9  * disclaimer in the documentation and/or other materials provided with the distribution.
10  * - Neither the name of CSIRO nor the names of its contributors may be used to endorse or promote products derived from
11  * this software without specific prior written permission of CSIRO.
12  *
13  * EXCEPT AS EXPRESSLY STATED IN THIS AGREEMENT AND TO THE FULL EXTENT PERMITTED BY APPLICABLE LAW, THE SOFTWARE IS
14  * PROVIDED "AS-IS". CSIRO MAKES NO REPRESENTATIONS, WARRANTIES OR CONDITIONS OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
15  * BUT NOT LIMITED TO ANY REPRESENTATIONS, WARRANTIES OR CONDITIONS REGARDING THE CONTENTS OR ACCURACY OF THE SOFTWARE,
16  * OR OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, THE ABSENCE OF LATENT OR OTHER
17  * DEFECTS, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT DISCOVERABLE.
18  * TO THE FULL EXTENT PERMITTED BY APPLICABLE LAW, IN NO EVENT SHALL CSIRO BE LIABLE ON ANY LEGAL THEORY (INCLUDING,
19  * WITHOUT LIMITATION, IN AN ACTION FOR BREACH OF CONTRACT, NEGLIGENCE OR OTHERWISE) FOR ANY CLAIM, LOSS, DAMAGES OR
20  * OTHER LIABILITY HOWSOEVER INCURRED. WITHOUT LIMITING THE SCOPE OF THE PREVIOUS SENTENCE THE EXCLUSION OF LIABILITY
21  * SHALL INCLUDE: LOSS OF PRODUCTION OR OPERATION TIME, LOSS, DAMAGE OR CORRUPTION OF DATA OR RECORDS; OR LOSS OF
22  * ANTICIPATED SAVINGS, OPPORTUNITY, REVENUE, PROFIT OR GOODWILL, OR OTHER ECONOMIC LOSS; OR ANY SPECIAL, INCIDENTAL,
23  * INDIRECT, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES, ARISING OUT OF OR IN CONNECTION WITH THIS AGREEMENT, ACCESS
24  * OF THE SOFTWARE OR ANY OTHER DEALINGS WITH THE SOFTWARE, EVEN IF CSIRO HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
25  * CLAIM, LOSS, DAMAGES OR OTHER LIABILITY. APPLICABLE LEGISLATION SUCH AS THE AUSTRALIAN CONSUMER LAW MAY APPLY
26  * REPRESENTATIONS, WARRANTIES, OR CONDITIONS, OR IMPOSES OBLIGATIONS OR LIABILITY ON CSIRO THAT CANNOT BE EXCLUDED,
27  * RESTRICTED OR MODIFIED TO THE FULL EXTENT SET OUT IN THE EXPRESS TERMS OF THIS CLAUSE ABOVE "CONSUMER GUARANTEES".
28  * TO THE EXTENT THAT SUCH CONSUMER GUARANTEES CONTINUE TO APPLY, THEN TO THE FULL EXTENT PERMITTED BY THE APPLICABLE
29  * LEGISLATION, THE LIABILITY OF CSIRO UNDER THE RELEVANT CONSUMER GUARANTEE IS LIMITED (WHERE PERMITTED AT CSIRO'S
30  * OPTION) TO ONE OF FOLLOWING REMEDIES OR SUBSTANTIALLY EQUIVALENT REMEDIES:
31  * (a) THE REPLACEMENT OF THE SOFTWARE, THE SUPPLY OF EQUIVALENT SOFTWARE, OR SUPPLYING RELEVANT SERVICES AGAIN;
32  * (b) THE REPAIR OF THE SOFTWARE;
33  * (c) THE PAYMENT OF THE COST OF REPLACING THE SOFTWARE, OF ACQUIRING EQUIVALENT SOFTWARE, HAVING THE RELEVANT
34  * SERVICES SUPPLIED AGAIN, OR HAVING THE SOFTWARE REPAIRED.
35  * IN THIS CLAUSE, CSIRO INCLUDES ANY THIRD PARTY AUTHOR OR OWNER OF ANY PART OF THE SOFTWARE OR MATERIAL DISTRIBUTED
36  * WITH IT. CSIRO MAY ENFORCE ANY RIGHTS ON BEHALF OF THE RELEVANT THIRD PARTY.
37  *
38  * Third Party Components:
39  *
40  * The following third party components are distributed with the Software. You agree to comply with the license terms
41  * for these components as part of accessing the Software. Other third party software may also be identified in
42  * separate files distributed with the Software.
43  * ___________________________________________________________________
44  *
45  * dynamixel_interface is forked from projects authored by Brian
46  * Axelrod (on behalf of Willow Garage):
47  *
48  * https://github.com/baxelrod/dynamixel_pro_controller
49  * https://github.com/baxelrod/dynamixel_pro_driver
50  *
51  * Thus they retain the following notice:
52  *
53  * Software License Agreement (BSD License)
54  * Copyright (c) 2013, Willow Garage
55  * All rights reserved.
56  * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
57  * following conditions are met:
58  * - Redistributions of source code must retain the above copyright notice, this list of conditions and the following
59  * disclaimer.
60  * - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
61  * following disclaimer in the documentation and/or other materials provided with the distribution.
62  * - Neither the name of Willow Garage nor the names of its contributors may be used to endorse or promote products
63  * derived from this software without specific prior written permission.
64  *
65  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
66  * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
67  * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
68  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
69  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
70  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
71  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
72  * ___________________________________________________________________
73  */
74 
75 
83 #include <stdlib.h>
84 #include <chrono>
85 #include <fstream>
86 #include <mutex>
87 #include <string>
88 #include <thread>
89 
90 #define _USE_MATH_DEFINES
91 #include <math.h>
92 
94 #include <ros/callback_queue.h>
95 #include <ros/package.h>
96 #include <ros/spinner.h>
97 #include <ros/transport_hints.h>
98 
99 
100 namespace dynamixel_interface
101 {
105 {
106  nh_ = std::make_unique<ros::NodeHandle>();
107 }
108 
109 
112 {
113  ROS_INFO("shutting_down dynamixel_interface_controller");
114 
116  {
117  for (int i = 0; i < dynamixel_ports_.size(); i++)
118  {
119  // Turn off all the motors
120  for (auto &it : dynamixel_ports_[i].joints)
121  {
122  dynamixel_ports_[i].driver->setTorqueEnabled(it.second.id, it.second.model_spec->type, 0);
123  ROS_INFO("Torque disabled on %s joint\n", it.first.c_str());
124  }
125  }
126  }
127 }
128 
132 {
134  if (parameters_parsed_)
135  {
136  ROS_ERROR("Parameters already parsed");
137  return false;
138  }
139 
140  // Stores config variables only used in init function
141  std::string mode;
142  std::string node_name = ros::this_node::getName();
143 
144  // load all the info from the param server, with defaults
145  ros::param::param<double>("~loop_rate", loop_rate_, 50.0);
146  ros::param::param<bool>("~disable_torque_on_shutdown", stop_motors_on_shutdown_, false);
147 
148  ros::param::param<bool>("~ignore_input_velocity", ignore_input_velocity_, false);
149 
150  ros::param::param<double>("~dataport_rate", dataport_rate_, 0.0);
151  ros::param::param<double>("~diagnostics_rate", diagnostics_rate_, 0.0);
152  ros::param::param<int>("~recv_queue_size", recv_queue_size_, 1);
153 
154  ros::param::param<std::string>("~control_mode", mode, "Position");
155 
156  ros::param::param<double>("~global_max_vel", global_max_vel_, 5.0);
157  ros::param::param<double>("~global_torque_limit", global_torque_limit_, 1.0);
158 
159 
160  // clamp read rates to sensible values
161  if (loop_rate_ <= 0)
162  {
163  ROS_ERROR("Loop rate must be > 0!");
164  return false;
165  }
166 
167  if (recv_queue_size_ <= 0)
168  {
169  ROS_ERROR("recv queue size must be >= 1");
170  return false;
171  }
172 
173  if (diagnostics_rate_ < 0)
174  {
175  diagnostics_rate_ = 0;
176  }
177  else if (diagnostics_rate_ > loop_rate_)
178  {
179  ROS_WARN("Clamping diagnostics rate to loop rate.");
180  diagnostics_rate_ = loop_rate_;
181  }
182  else
183  {
184  diagnostics_iters_ = std::round(loop_rate_ / diagnostics_rate_);
186  }
187 
188  // num iters to read from
189  if (dataport_rate_ < 0)
190  {
191  dataport_rate_ = 0;
192  }
193  else if (dataport_rate_ > loop_rate_)
194  {
195  ROS_WARN("Clamping dataport rate to loop rate.");
196  dataport_rate_ = loop_rate_;
197  }
198  else
199  {
200  dataport_iters_ = std::round(loop_rate_ / dataport_rate_);
201  dataport_counter_ = 0;
202  }
203 
204 
205  // Set control mode for this run
206  if (mode == "position")
207  {
209  }
210  else if (mode == "velocity")
211  {
213  }
214  else if (mode == "effort")
215  {
217  }
218  else
219  {
220  ROS_ERROR("Control Mode Not Supported!");
221  return false;
222  }
223 
224  // Attempt to parse information for each device (port)
225  if (ros::param::has("~ports"))
226  {
227  // PARSE ARRAY OF PORT INFORMATION
228  XmlRpc::XmlRpcValue ports;
229  ros::param::get("~ports", ports);
230 
231  if (!parsePortInformation(ports))
232  {
233  ROS_ERROR("Unable to parse ports from config!");
234  return false;
235  }
236 
237  // shutdown if no valid ports
238  if (dynamixel_ports_.size() == 0)
239  {
240  ROS_ERROR("No valid ports found, shutting_down...");
241  return false;
242  }
243  }
244  else
245  {
246  ROS_ERROR("No port details loaded to param server");
247  return false;
248  }
249 
250  if (diagnostics_rate_ > 0)
251  {
252  diagnostics_publisher_ = nh_->advertise<dynamixel_interface::ServoDiags>("servo_diagnostics", 1);
253  }
254 
255  if (dataport_rate_ > 0)
256  {
257  dataport_publisher_ = nh_->advertise<dynamixel_interface::DataPorts>("external_dataports", 1);
258  }
259 
260  parameters_parsed_ = true;
261  return true;
262 }
263 
264 
268 {
270  if (parameters_parsed_)
271  {
272  ROS_ERROR("Parameters already parsed");
273  return false;
274  }
275 
276  // If there is no servos array in the param server, return
278  {
279  ROS_ERROR("Invalid/missing device information on the param server");
280  return false;
281  }
282 
283  // number of ports defined
284  int num_ports = ports.size();
285 
286  // For every port, load and verify its information
287  for (int i = 0; i < ports.size(); i++)
288  {
289  PortInfo port;
290 
291  bool use_group_read = false;
292  bool use_group_write = false;
293 
294  /************************* PORT ARGUMENTS *********************/
295 
296 
297  // check if info exists for specified port
298  if (ports[i].getType() != XmlRpc::XmlRpcValue::TypeStruct)
299  {
300  ROS_ERROR("Invalid/Missing info-struct for servo index %d", i);
301  return false;
302  }
303 
304 
305  // get port name
306  if (ports[i]["name"].getType() != XmlRpc::XmlRpcValue::TypeString)
307  {
308  ROS_ERROR("Invalid/Missing port name for port %d", i);
309  return false;
310  }
311  else
312  {
313  port.port_name = static_cast<std::string>(ports[i]["name"]);
314  }
315 
316 
317  // get device name
318  if (ports[i]["device"].getType() != XmlRpc::XmlRpcValue::TypeString)
319  {
320  ROS_ERROR("Invalid/Missing device name for port %d", i);
321  return false;
322  }
323  else
324  {
325  port.device = static_cast<std::string>(ports[i]["device"]);
326  }
327 
328  // get baud rate
329  if (ports[i]["baudrate"].getType() != XmlRpc::XmlRpcValue::TypeInt)
330  {
331  ROS_ERROR("Invalid/Missing baudrate for port %d", i);
332  return false;
333  }
334  else
335  {
336  port.baudrate = static_cast<int>(ports[i]["baudrate"]);
337  }
338 
339  // get protocol
340  if (ports[i]["use_legacy_protocol"].getType() != XmlRpc::XmlRpcValue::TypeBoolean)
341  {
342  ROS_ERROR("Invalid/Missing use_legacy_protocol option for port %d", i);
343  return false;
344  }
345  else
346  {
347  port.use_legacy_protocol = static_cast<bool>(ports[i]["use_legacy_protocol"]);
348  }
349 
350  // get group read enabled
351  if (ports[i]["group_read_enabled"].getType() != XmlRpc::XmlRpcValue::TypeBoolean)
352  {
353  ROS_ERROR("Invalid/Missing group_read_enabled option for port %d", i);
354  return false;
355  }
356  else
357  {
358  use_group_read = static_cast<bool>(ports[i]["group_read_enabled"]);
359  }
360 
361  // get group write enabled
362  if (ports[i]["group_write_enabled"].getType() != XmlRpc::XmlRpcValue::TypeBoolean)
363  {
364  ROS_ERROR("Invalid/Missing group_write_enabled option for port %d", i);
365  return false;
366  }
367  else
368  {
369  use_group_write = static_cast<bool>(ports[i]["group_write_enabled"]);
370  }
371 
372 
373  /************************* Driver initialisation *********************/
374 
375  // Create driver
376  port.driver = std::unique_ptr<DynamixelInterfaceDriver>(new DynamixelInterfaceDriver(
377  port.device, port.baudrate, port.use_legacy_protocol, use_group_read, use_group_write));
378 
379  /************************* Dynamixel initialisation *********************/
380 
381  XmlRpc::XmlRpcValue servos;
382 
383  // If there is no servos array in the param server, return
384  if (ports[i]["servos"].getType() != XmlRpc::XmlRpcValue::TypeArray)
385  {
386  ROS_ERROR("Invalid/missing servo information on the param server");
387  return false;
388  }
389  else
390  {
391  servos = ports[i]["servos"];
392  }
393 
394  if (!parseServoInformation(port, servos))
395  {
396  ROS_ERROR("Unable to parse servo information for port %s", port.port_name.c_str());
397  return false;
398  }
399 
400  // add port only if dynamixels were found
401  if (port.joints.size() > 0)
402  {
403  ROS_INFO("Adding port %s to loop", port.port_name.c_str());
404  // add port information to server
405  dynamixel_ports_.emplace_back(std::move(port));
406  }
407  else
408  {
409  ROS_ERROR("No dynamixels found on %s (%s)!", port.device.c_str(), port.port_name.c_str());
410  return false;
411  }
412  }
413 
414  return true;
415 }
416 
421 {
423  if (parameters_parsed_)
424  {
425  ROS_ERROR("Parameters already parsed");
426  return false;
427  }
428 
429  // number of servos defined
430  int num_servos = servos.size();
431 
432  // For every servo, load and verify its information
433  for (int i = 0; i < servos.size(); i++)
434  {
435  // store the loaded information in this struct
436  DynamixelInfo info;
437 
438  // check if info exists for specified joint
439  if (servos[i].getType() != XmlRpc::XmlRpcValue::TypeStruct)
440  {
441  ROS_ERROR("Invalid/Missing info-struct for servo index %d", i);
442  return false;
443  }
444 
445  // get joint id
446  if (servos[i]["id"].getType() != XmlRpc::XmlRpcValue::TypeInt)
447  {
448  ROS_ERROR("Invalid/Missing id for servo index %d", i);
449  return false;
450  }
451  else
452  {
453  // store the servo's ID
454  info.id = static_cast<int>(servos[i]["id"]);
455  }
456 
457  // get joint name
458  if (servos[i]["joint_name"].getType() != XmlRpc::XmlRpcValue::TypeString)
459  {
460  ROS_ERROR("Invalid/Missing joint name for servo index %d, id: %d", i, info.id);
461  return false;
462  }
463  else
464  {
465  // store the servo's corresponding joint name
466  info.joint_name = static_cast<std::string>(servos[i]["joint_name"]);
467 
468  // check this port and all previous ports for duplicate joint names (not allowed as joints are
469  // referred to by name)
470  if (port.joints.find(info.joint_name) != port.joints.end())
471  {
472  ROS_ERROR("Cannot have multiple joints with the same name [%s]", info.joint_name.c_str());
473  return false;
474  }
475  else
476  {
477  for (int j = 0; j < dynamixel_ports_.size(); j++)
478  {
479  if (dynamixel_ports_[j].joints.find(info.joint_name) != dynamixel_ports_[j].joints.end())
480  {
481  ROS_ERROR("Cannot have multiple joints with the same name [%s]", info.joint_name.c_str());
482  return false;
483  }
484  }
485  }
486  }
487 
488  // get joint zero position
489  if (servos[i]["zero_pos"].getType() != XmlRpc::XmlRpcValue::TypeInt)
490  {
491  ROS_ERROR("Invalid/Missing zero position for servo index %d, id: %d", i, info.id);
492  return false;
493  }
494  else
495  {
496  // store the servo's corresponding joint
497  info.zero_pos = static_cast<int>(servos[i]["zero_pos"]);
498  }
499 
500  // get joint default min position
501  if (servos[i]["min_pos"].getType() != XmlRpc::XmlRpcValue::TypeInt)
502  {
503  ROS_ERROR("Invalid/Missing min position for servo index %d, id: %d", i, info.id);
504  return false;
505  }
506  else
507  {
508  info.min_pos = static_cast<int>(servos[i]["min_pos"]);
509  }
510 
511  // get joint default max position
512  if (servos[i]["max_pos"].getType() != XmlRpc::XmlRpcValue::TypeInt)
513  {
514  ROS_ERROR("Invalid/Missing max position for servo index %d, id: %d", i, info.id);
515  return false;
516  }
517  else
518  {
519  info.max_pos = static_cast<int>(servos[i]["max_pos"]);
520  }
521 
522  // get joint default joint speed (or set to global if none specified)
523  if (servos[i]["max_vel"].getType() != XmlRpc::XmlRpcValue::TypeDouble)
524  {
525  info.max_vel = global_max_vel_;
526  }
527  else
528  {
529  info.max_vel = static_cast<double>(servos[i]["max_vel"]);
530  if (info.max_vel < 0.0)
531  {
532  info.max_vel = global_max_vel_;
533  }
534  }
535 
536 
537  // get joint torque limit (or set to global if none specified)
538  if (servos[i]["torque_limit"].getType() != XmlRpc::XmlRpcValue::TypeDouble)
539  {
541  }
542  else
543  {
544  info.torque_limit = static_cast<double>(servos[i]["torque_limit"]);
545  if ((info.torque_limit > 1.0) || (info.torque_limit < 0.0))
546  {
548  }
549  }
550 
551  // store current control mode
553 
554  // add joint to port
555  port.joints[info.joint_name] = info;
556  ROS_INFO("Added joint %s (%d) to port %s", info.joint_name.c_str(), info.id, port.port_name.c_str());
557  }
558 
559  return true;
560 }
561 
562 
567 // - check response on bus
572 {
573  // don't initialise if we have no parameters or are already running
574  if (!parameters_parsed_)
575  {
576  ROS_ERROR("Parameters not parsed yet, must be parsed before init");
577  return false;
578  }
579  else if (initialised_)
580  {
581  ROS_ERROR("Already initialised");
582  return false;
583  }
584 
585  for (auto &port : dynamixel_ports_)
586  {
587  if (!initialisePort(port))
588  {
589  ROS_ERROR("Unable to initialised port %s", port.port_name.c_str());
590  return false;
591  }
592  }
593 
594  // advertise the joint state input and output topics
595  joint_state_publisher_ = nh_->advertise<sensor_msgs::JointState>("joint_states", 1);
596  joint_state_subscriber_ = nh_->subscribe<sensor_msgs::JointState>("desired_joint_states", recv_queue_size_,
598  this, ros::TransportHints().tcpNoDelay());
599 
600  initialised_ = true;
601  return true;
602 }
603 
604 
608 {
609  // don't initialise if we have no parameters or are already running
610  if (!parameters_parsed_)
611  {
612  ROS_ERROR("Parameters not parsed yet, must be parsed before init");
613  return false;
614  }
615  else if (initialised_)
616  {
617  ROS_ERROR("Already initialised");
618  return false;
619  }
620 
621  DynamixelSeriesType type_check;
622  bool first_dynamixel = true;
623 
624  // Attempt driver initialisation
625  if (!port.driver->initialise())
626  {
627  ROS_ERROR("Unable to initialise driver");
628  return false;
629  }
630 
631  // first, initialise dynamixels
632  for (auto &it : port.joints)
633  {
634  if (!initialiseDynamixel(port, it.second))
635  {
636  ROS_ERROR("Unable to initialised dynamixel %s", it.second.joint_name.c_str());
637  return false;
638  }
639  }
640 
641  // then, check type safety of bus
642  for (auto &it : port.joints)
643  {
644  if (first_dynamixel)
645  {
646  type_check = it.second.model_spec->type;
647  first_dynamixel = false;
648  }
649  else
650  {
651  switch (type_check)
652  {
653  case kSeriesAX:
654  case kSeriesRX:
655  case kSeriesDX:
656  case kSeriesEX:
657  case kSeriesLegacyMX:
658  if (it.second.model_spec->type > kSeriesLegacyMX)
659  {
660  ROS_ERROR("Type mismatch on bus, only dynamixel who share a common register table may share a bus! "
661  "Have both %s and %s.",
662  port.driver->getSeriesName(type_check).c_str(),
663  port.driver->getSeriesName(it.second.model_spec->type).c_str());
664  return false;
665  }
666  break;
667 
668  case kSeriesX:
669  case kSeriesMX:
670  if ((it.second.model_spec->type != kSeriesX) && (it.second.model_spec->type != kSeriesMX))
671  {
672  ROS_ERROR("Type mismatch on bus, only dynamixel who share a common register table may share a bus! "
673  "Have both %s and %s.",
674  port.driver->getSeriesName(type_check).c_str(),
675  port.driver->getSeriesName(it.second.model_spec->type).c_str());
676  return false;
677  }
678  break;
679 
680  case kSeriesP:
681  if (it.second.model_spec->type != kSeriesP)
682  {
683  ROS_ERROR("Type mismatch on bus, only dynamixel who share a common register table may share a bus! "
684  "Have both %s and %s.",
685  port.driver->getSeriesName(type_check).c_str(),
686  port.driver->getSeriesName(it.second.model_spec->type).c_str());
687  return false;
688  }
689  break;
690 
691  case kSeriesLegacyPro:
692  if (it.second.model_spec->type != kSeriesLegacyPro)
693  {
694  ROS_ERROR("Type mismatch on bus, only dynamixel who share a common register table may share a bus! "
695  "Have both %s and %s.",
696  port.driver->getSeriesName(type_check).c_str(),
697  port.driver->getSeriesName(it.second.model_spec->type).c_str());
698  return false;
699  }
700  break;
701  }
702  }
703  }
704 
705  return true;
706 }
707 
712 {
713  // don't initialise if we have no parameters or are already running
714  if (!parameters_parsed_)
715  {
716  ROS_ERROR("Parameters not parsed yet, must be parsed before init");
717  return false;
718  }
719  else if (initialised_)
720  {
721  ROS_ERROR("Already initialised");
722  return false;
723  }
724 
725  // sleep to make sure the bus is clear of comms
726  ros::Duration(0.2).sleep();
727 
728  // Ping the servo to make sure that we can actually connect to it
729  // and that it is alive and well on our bus, if not, sleep and try again
730  // if all retry's fail, throw an error
731  bool ping_success = true;
732  int ping_count = 0;
733  while (!port.driver->ping(dynamixel.id))
734  {
735  // increment ping count
736  ping_count++;
737 
738  ROS_WARN("Failed to ping id: %d, attempt %d, retrying...", dynamixel.id, ping_count);
739  // max number of retry's
740  if (ping_count > 5)
741  {
742  // unable to detect motor
743  ROS_ERROR("Cannot ping dynamixel id: %d", dynamixel.id);
744  return false;
745  }
746 
747  // sleep and try again
748  ros::Duration(0.5).sleep();
749  }
750 
751  // only add if ping was successful
752  bool success = true;
753  uint16_t model_number = 0;
754 
755  if (!port.driver->getModelNumber(dynamixel.id, &model_number))
756  {
757  ROS_ERROR("Unable to retrieve model number for dynamixel id %d", dynamixel.id);
758  return false;
759  }
760  else if (model_number == 0)
761  {
762  ROS_ERROR("Invalid model number retrieved for dynamixel id %d", dynamixel.id);
763  return false;
764  }
765 
766  // If valid motor, setup in operating mode
767  dynamixel.model_spec = port.driver->getModelSpec(model_number);
768  if (dynamixel.model_spec == nullptr)
769  {
770  ROS_ERROR("Failed to load model information for dynamixel id %d", dynamixel.id);
771  ROS_ERROR("Model Number: %d", model_number);
772  ROS_ERROR("Info is not in database");
773  return false;
774  ;
775  }
776 
777  // check error status of dynamixel
778  uint8_t error_status = 0;
779  if (!port.driver->getErrorStatus(dynamixel.id, dynamixel.model_spec->type, &error_status))
780  {
781  ROS_WARN("Failed to check error status of dynamixel id %d", dynamixel.id);
782  }
783  else if (error_status)
784  {
785  ROS_WARN("Dynamixel %d returned error code %d", dynamixel.id, error_status);
786  }
787 
788  dynamixel.torque_enabled = false;
789 
790  // Display joint info
791  ROS_INFO("Joint Name: %s, ID: %d, Series: %s, Model: %s", dynamixel.joint_name.c_str(), dynamixel.id,
792  port.driver->getSeriesName(dynamixel.model_spec->type).c_str(), dynamixel.model_spec->name.c_str());
793 
794  // Only support effort control on newer spec dynamixels
796  {
797  switch (dynamixel.model_spec->type)
798  {
799  case kSeriesAX:
800  case kSeriesRX:
801  case kSeriesDX:
802  case kSeriesEX:
803  case kSeriesLegacyMX:
804  case kSeriesLegacyPro:
805  ROS_ERROR("Effort Control not supported for legacy series dynamixels!");
806  return false;
807  }
808  }
809 
810  // maintain torque state in motor
811  if (!port.driver->getTorqueEnabled(dynamixel.id, dynamixel.model_spec->type, &dynamixel.torque_enabled))
812  {
813  ROS_ERROR("Unable to get torque_enable status for dynamixel id %d", dynamixel.id);
814  return false;
815  }
816  if (!port.driver->setTorqueEnabled(dynamixel.id, dynamixel.model_spec->type, 0))
817  {
818  ROS_ERROR("Unable to set torque_enable status for dynamixel id %d", dynamixel.id);
819  return false;
820  }
821 
822  // set operating mode for the motor
823  if (!port.driver->setOperatingMode(dynamixel.id, dynamixel.model_spec->type, control_type_))
824  {
825  ROS_ERROR("Failed to set operating mode for dynamixel id %d", dynamixel.id);
826  return false;
827  }
828 
829  // set torque limit for the motor
830  if (!port.driver->setMaxTorque(dynamixel.id, dynamixel.model_spec->type,
831  (int)(dynamixel.torque_limit * dynamixel.model_spec->effort_reg_max)))
832  {
833  ROS_ERROR("Failed to set torque limit for dynamixel id %d", dynamixel.id);
834  return false;
835  }
836 
837  // set velocity limits for the motor
838  if (dynamixel.model_spec->type > kSeriesLegacyMX)
839  {
840  if (!port.driver->setMaxVelocity(dynamixel.id, dynamixel.model_spec->type,
841  (int)(dynamixel.max_vel * dynamixel.model_spec->velocity_radps_to_reg)))
842  {
843  ROS_ERROR("Failed to set velocity limit for dynamixel id %d", dynamixel.id);
844  return false;
845  }
846  }
847 
848  // angle limits are only relevant in position control mode
850  {
851  // set angle limits & direction
852  if (dynamixel.min_pos > dynamixel.max_pos)
853  {
854  if (!port.driver->setAngleLimits(dynamixel.id, dynamixel.model_spec->type, dynamixel.max_pos, dynamixel.min_pos))
855  {
856  ROS_ERROR("Failed to set angle limits for dynamixel id %d", dynamixel.id);
857  return false;
858  }
859  }
860  else
861  {
862  if (!port.driver->setAngleLimits(dynamixel.id, dynamixel.model_spec->type, dynamixel.min_pos, dynamixel.max_pos))
863  {
864  ROS_ERROR("Failed to set angle limits for dynamixel id %d", dynamixel.id);
865  return false;
866  }
867  }
868  }
869 
870  // preserve torque enable state
871  if (!port.driver->setTorqueEnabled(dynamixel.id, dynamixel.model_spec->type, dynamixel.torque_enabled))
872  {
873  ROS_ERROR("Unable to reset torque_enable status for dynamixel id %d", dynamixel.id);
874  return false;
875  }
876 
877  return true;
878 }
879 
883 void DynamixelInterfaceController::jointStateCallback(const sensor_msgs::JointState::ConstPtr &joint_commands)
884 {
885  // thread safe modification
886  std::unique_lock<std::mutex> lock(write_mutex_);
887 
888  // In order to prevent information being lost if multiple nodes are publishing commands, the joint command is checked
889  // against the existing message, either adding new dynamixel to the message or updating the commands for the joints
890  // already inside. This way joint command messages with subsets of dynamixels are combined rather than overwritten.
891  if (write_msg_.name.empty())
892  {
893  write_msg_ = *joint_commands;
894  }
895  // avoid array length issues
896  else if ((write_msg_.position.empty() == joint_commands->position.empty()) &&
897  (write_msg_.velocity.empty() == joint_commands->velocity.empty()) &&
898  (write_msg_.effort.empty() == joint_commands->effort.empty()))
899  {
900  // only add joint if not already in list (THIS TAKES N*M TIME, NEED TO OPTIMISE)
901  for (int i = 0; i < joint_commands->name.size(); i++)
902  {
903  bool in_msg = false;
904 
905  // if joint already in message, update values
906  for (int j = 0; j < write_msg_.name.size(); j++)
907  {
908  if (joint_commands->name[i] == write_msg_.name[j])
909  {
910  if (!write_msg_.position.empty() && !joint_commands->position.empty())
911  {
912  write_msg_.position[j] = joint_commands->position[i];
913  }
914 
915  if (!write_msg_.velocity.empty() && !joint_commands->velocity.empty())
916  {
917  write_msg_.velocity[j] = joint_commands->velocity[i];
918  }
919 
920  if (!write_msg_.effort.empty() && !joint_commands->effort.empty())
921  {
922  write_msg_.effort[j] = joint_commands->effort[i];
923  }
924 
925  in_msg = true;
926  break;
927  }
928  }
929 
930  // not already in message, push back
931  if (!in_msg)
932  {
933  write_msg_.name.push_back(joint_commands->name[i]);
934 
935  if (!write_msg_.position.empty() && !joint_commands->position.empty())
936  {
937  write_msg_.position.push_back(joint_commands->position[i]);
938  }
939 
940  if (!write_msg_.velocity.empty() && !joint_commands->velocity.empty())
941  {
942  write_msg_.velocity.push_back(joint_commands->velocity[i]);
943  }
944 
945  if (!write_msg_.effort.empty() && !joint_commands->effort.empty())
946  {
947  write_msg_.effort.push_back(joint_commands->effort[i]);
948  }
949  }
950  }
951  }
952 
953  write_ready_ = true;
954 }
955 
959 {
960  // wait until init
961  if (!initialised_)
962  {
963  return;
964  }
965 
966  // don't access the driver after its been cleaned up
967  int num_servos = 0;
968  std::vector<std::thread> threads;
969 
970  sensor_msgs::JointState read_msg;
971  sensor_msgs::JointState reads[dynamixel_ports_.size()];
972 
973  dynamixel_interface::DataPorts dataports_msg;
974  dynamixel_interface::DataPorts dataports_reads[dynamixel_ports_.size()];
975 
976  dynamixel_interface::ServoDiags diags_msg;
977  dynamixel_interface::ServoDiags diags_reads[dynamixel_ports_.size()];
978 
979  std::unique_lock<std::mutex> lock(write_mutex_);
980 
981  // Control loop rate of dataport reads
982  if (dataport_rate_ > 0)
983  {
986  {
987  read_dataport_ = true;
988  dataport_counter_ = 0;
989  }
990  }
991 
992  // Control loop rate of diagnostic reads
993  if (diagnostics_rate_ > 0)
994  {
997  {
998  read_diagnostics_ = true;
1000  }
1001  }
1002 
1003  // spawn an IO thread for each additional port
1004  for (int i = 1; i < dynamixel_ports_.size(); i++)
1005  {
1006  num_servos = num_servos + dynamixel_ports_[i].joints.size();
1007  reads[i] = sensor_msgs::JointState();
1008 
1009  threads.emplace_back(&DynamixelInterfaceController::multiThreadedIO, this, std::ref(dynamixel_ports_[i]),
1010  std::ref(reads[i]), std::ref(dataports_reads[i]), std::ref(diags_reads[i]), write_ready_);
1011  }
1012 
1013  // get messages for the first port
1014  reads[0] = sensor_msgs::JointState();
1015 
1016  num_servos = num_servos + dynamixel_ports_[0].joints.size();
1017 
1018  // keep the write message thread safe
1019  sensor_msgs::JointState temp_msg = write_msg_;
1020 
1021  // Perform the IO for the first port here (don't bother spinning out another thread)
1022  multiThreadedIO(dynamixel_ports_[0], reads[0], dataports_reads[0], diags_reads[0], write_ready_);
1023 
1024  // loop and get port information (wait for threads in order if any were created)
1025  for (int i = 0; i < dynamixel_ports_.size(); i++)
1026  {
1027  if (i > 0)
1028  {
1029  threads[i - 1].join();
1030  }
1031 
1032  if (reads[i].name.size() == 0)
1033  {
1034  // ROS_ERROR("No values passed back from %s thread", dynamixel_ports_[i].device.c_str());
1035  continue;
1036  }
1037 
1038  // append read values to published message
1039  read_msg.name.insert(read_msg.name.end(), reads[i].name.begin(), reads[i].name.end());
1040  read_msg.position.insert(read_msg.position.end(), reads[i].position.begin(), reads[i].position.end());
1041  read_msg.velocity.insert(read_msg.velocity.end(), reads[i].velocity.begin(), reads[i].velocity.end());
1042  read_msg.effort.insert(read_msg.effort.end(), reads[i].effort.begin(), reads[i].effort.end());
1043 
1044  // get dataport read info if available
1045  if (read_dataport_)
1046  {
1047  if (dataports_reads[i].states.size() > 0)
1048  {
1049  // append read values to published message
1050  dataports_msg.states.insert(dataports_msg.states.end(), dataports_reads[i].states.begin(),
1051  dataports_reads[i].states.end());
1052  }
1053  }
1054 
1055  // get diagnostics info if available
1056  if (read_diagnostics_)
1057  {
1058  if (diags_reads[i].diagnostics.size() > 0)
1059  {
1060  diags_msg.diagnostics.insert(diags_msg.diagnostics.end(), diags_reads[i].diagnostics.begin(),
1061  diags_reads[i].diagnostics.end());
1062  }
1063  }
1064  }
1065 
1066  // reset write flag
1067  if (write_ready_)
1068  {
1069  write_ready_ = false;
1070  write_msg_.name.clear();
1071  write_msg_.position.clear();
1072  write_msg_.velocity.clear();
1073  write_msg_.effort.clear();
1074  }
1075 
1076  lock.unlock();
1077 
1078  // publish joint states
1079  if (read_msg.name.size() > 0)
1080  {
1081  read_msg.header.stamp = ros::Time::now();
1082  joint_state_publisher_.publish(read_msg);
1083  }
1084 
1085  // publish external dataport message
1086  if ((read_dataport_) && (dataports_msg.states.size() > 0) && (dataport_rate_ > 0))
1087  {
1088  dataport_publisher_.publish(dataports_msg);
1089  read_dataport_ = false;
1090  }
1091 
1092  if (read_diagnostics_ && (diags_msg.diagnostics.size() > 0) && (diagnostics_rate_ > 0))
1093  {
1094  diagnostics_publisher_.publish(diags_msg);
1095  read_diagnostics_ = false;
1096  }
1097 }
1098 
1105 void DynamixelInterfaceController::multiThreadedIO(PortInfo &port, sensor_msgs::JointState &read_msg,
1106  dynamixel_interface::DataPorts &dataport_msg,
1107  dynamixel_interface::ServoDiags &diags_msg, bool perform_write) const
1108 {
1109  // perform write
1110  if (perform_write)
1111  {
1113  }
1114 
1115  // perform read
1116  multiThreadedRead(port, read_msg, dataport_msg, diags_msg);
1117 }
1118 
1119 
1123 void DynamixelInterfaceController::multiThreadedWrite(PortInfo &port, sensor_msgs::JointState joint_commands) const
1124 {
1125  // ignore empty messages
1126  if (joint_commands.name.size() < 1)
1127  {
1128  return;
1129  }
1130 
1131  // booleans used to setup which parameters to write
1132  bool has_pos = false;
1133  bool has_vel = false;
1134  bool has_eff = false;
1135 
1136  // figure out which values have been specified
1137  if ((joint_commands.position.size() == joint_commands.name.size()) && (control_type_ == kModePositionControl))
1138  {
1139  has_pos = true;
1140  }
1141  if ((joint_commands.velocity.size() == joint_commands.name.size()) &&
1143  {
1144  has_vel = true;
1145  }
1146  if ((joint_commands.effort.size() == joint_commands.name.size()) && (control_type_ == kModeTorqueControl))
1147  {
1148  has_eff = true;
1149  }
1150 
1151  if ((!has_pos) && (!has_vel) && (!has_eff))
1152  {
1153  // no valid array sizes for current control mode, ignore
1154  return;
1155  }
1156 
1157  // write objects
1158  std::unordered_map<int, SyncData> velocities, positions, efforts;
1159 
1160  // push motor encoder value onto list
1161  SyncData write_data;
1162 
1163  // loop and calculate the values for each specified joint
1164  for (int i = 0; i < joint_commands.name.size(); i++)
1165  {
1166  // lookup the information for that particular joint to be able to control it
1167  if (port.joints.find(joint_commands.name[i]) == port.joints.end())
1168  {
1169  // Joint not on this port, ignore
1170  continue;
1171  }
1172 
1173  // Retrieve dynamixel information
1174  DynamixelInfo *info = &port.joints[joint_commands.name[i]];
1175 
1176  // enable torque if not already
1177  if (!info->torque_enabled)
1178  {
1179  if (port.driver->setTorqueEnabled(info->id, info->model_spec->type, true))
1180  {
1181  // if in position control mode we enable the default join movement speed (profile velocity)
1183  {
1184  int regVal = static_cast<int>((static_cast<double>(info->max_vel) * info->model_spec->velocity_radps_to_reg));
1185 
1186  if (!port.driver->setProfileVelocity(info->id, info->model_spec->type, regVal))
1187  {
1188  ROS_WARN("Unable to set profile velocity on %s joint", info->joint_name.c_str());
1189  }
1190  }
1191  ROS_INFO("Torque enabled on %s joint", info->joint_name.c_str());
1192  info->torque_enabled = true;
1193  }
1194  else
1195  {
1196  ROS_WARN("Unable to enable torque on %s joint", info->joint_name.c_str());
1197  }
1198  }
1199 
1200  // calculate the position register value for the motor
1201  if ((has_pos) && (control_type_ == kModePositionControl))
1202  {
1203  // used to bound positions to limits
1204  int up_lim, dn_lim;
1205 
1206  // get radian position value
1207  double rad_pos = joint_commands.position[i];
1208 
1209  // define our clamping limits to avoid exceeding safe joint angles
1210  if (info->min_pos > info->max_pos)
1211  {
1212  rad_pos = -rad_pos;
1213  up_lim = info->min_pos;
1214  dn_lim = info->max_pos;
1215  }
1216  else
1217  {
1218  up_lim = info->max_pos;
1219  dn_lim = info->min_pos;
1220  }
1221 
1222  // convert from radians to motor encoder value
1223  double pos =
1224  rad_pos * ((info->model_spec->encoder_cpr * 360.0) / (2 * M_PI * info->model_spec->encoder_range_deg)) +
1225  info->zero_pos;
1226 
1227  // clamp joint angle to be within safe limit
1228  if ((pos <= up_lim) && (pos >= dn_lim))
1229  {
1230  // push motor encoder value onto list
1231  write_data.id = info->id;
1232  write_data.type = info->model_spec->type;
1233  if (write_data.type <= kSeriesLegacyMX)
1234  {
1235  write_data.data.resize(2);
1236  *(reinterpret_cast<uint16_t*>(write_data.data.data())) = static_cast<uint16_t>(pos);
1237  }
1238  else
1239  {
1240  write_data.data.resize(4);
1241  *(reinterpret_cast<uint32_t*>(write_data.data.data())) = static_cast<uint32_t>(pos);
1242  }
1243 
1244  positions[info->id] = write_data;
1245  }
1246  }
1247 
1248  // calculate the velocity register value for the motor
1249  if (has_vel && (control_type_ != kModeTorqueControl))
1250  {
1251  // get rad/s value from message
1252  double rad_s_vel = joint_commands.velocity[i];
1253 
1254  // clamp to joint speed limit
1255  rad_s_vel = std::min(std::max(rad_s_vel, -info->max_vel), info->max_vel);
1256 
1257  // convert to motor encoder value
1258  int vel = (int)((rad_s_vel * info->model_spec->velocity_radps_to_reg));
1259 
1260  // Velocity values serve 2 different functions, in velocity control mode their sign
1261  // defines direction, however in position control mode their absolute value is used
1262  // to set the profile velocity (how fast the motor moves to the specified position)
1263  // we also need to take an absolute value as each motor series handles negative inputs
1264  // differently
1265  if ((control_type_ == kModeVelocityControl) && (info->min_pos > info->max_pos))
1266  {
1267  // The old dynamixel series use a different method of representing velocity values. They define the value in the
1268  // register as a signed 10-bit value, with the first 9 bits representing magnitude and the 10th bit representing
1269  // the sign.
1270  if (info->model_spec->type <= kSeriesLegacyMX)
1271  {
1272  vel = std::abs(vel) + 1024;
1273  }
1274  else
1275  {
1276  vel = -vel;
1277  }
1278  }
1279  else if (control_type_ == kModePositionControl)
1280  {
1281  vel = std::abs(vel);
1282  }
1283 
1284  // push motor encoder value onto list
1285  write_data.id = info->id;
1286  write_data.type = info->model_spec->type;
1287  if (write_data.type <= kSeriesLegacyMX)
1288  {
1289  write_data.data.resize(2);
1290  *(reinterpret_cast<uint16_t*>(write_data.data.data())) = static_cast<uint16_t>(vel);
1291  }
1292  else
1293  {
1294  write_data.data.resize(4);
1295  *(reinterpret_cast<uint32_t*>(write_data.data.data())) = static_cast<uint32_t>(vel);
1296  }
1297 
1298  velocities[info->id] = write_data;
1299  }
1300 
1301  if (has_eff && (control_type_ == kModeTorqueControl))
1302  {
1303  double input_effort = joint_commands.effort[i];
1304 
1305  int16_t effort = 0;
1306 
1307  // input and outputs are current values (in A)
1308  if (info->model_spec->effort_reg_to_mA != 0)
1309  {
1310  effort = (input_effort * 1000 / info->model_spec->effort_reg_to_mA);
1311  effort = abs(effort);
1312 
1313  if ((input_effort < 0) != (info->min_pos > info->max_pos))
1314  {
1315  effort = -effort;
1316  }
1317  }
1318 
1319  // push motor encoder value onto list
1320  write_data.id = info->id;
1321  write_data.type = info->model_spec->type;
1322  write_data.data.resize(2);
1323  *(reinterpret_cast<int16_t*>(write_data.data.data())) = effort;
1324 
1325  efforts[info->id] = write_data;
1326  }
1327  }
1328 
1329  // use the multi-motor write functions to reduce the bandwidth required to command
1330  // all the motors
1332  {
1333  // set the profile velocities if they have been defined
1334  if ((has_vel) && (!ignore_input_velocity_))
1335  {
1336  if (!port.driver->setMultiProfileVelocity(velocities))
1337  {
1338  ROS_ERROR("Failed to set profile velocities on port %s!", port.port_name.c_str());
1339  }
1340  }
1341  // send the positions to the motors
1342  if (has_pos)
1343  {
1344  if (!port.driver->setMultiPosition(positions))
1345  {
1346  ROS_ERROR("Failed to set positions on port %s!", port.port_name.c_str());
1347  }
1348  }
1349  }
1350  else if ((control_type_ == kModeVelocityControl) && has_vel)
1351  {
1352  // set the velocity values for each motor
1353  if (!port.driver->setMultiVelocity(velocities))
1354  {
1355  ROS_ERROR("Failed to set velocities on port %s!", port.port_name.c_str());
1356  }
1357  }
1358  else if ((control_type_ == kModeTorqueControl) && has_eff)
1359  {
1360  // set the effort values for each motor
1361  if (!port.driver->setMultiTorque(efforts))
1362  {
1363  ROS_ERROR("Failed to set efforts on port %s!", port.port_name.c_str());
1364  }
1365  }
1366 }
1367 
1368 
1374 void DynamixelInterfaceController::multiThreadedRead(PortInfo &port, sensor_msgs::JointState &read_msg,
1375  dynamixel_interface::DataPorts &dataport_msg,
1376  dynamixel_interface::ServoDiags &diags_msg) const
1377 {
1378  bool comm_success;
1379  std::unordered_map<int, DynamixelState> state_map;
1380  std::unordered_map<int, DynamixelDiagnostic> diag_map;
1381  std::unordered_map<int, DynamixelDataport> data_map;
1382 
1383  // Iterate over all connected servos and add to list
1384  for (auto &it : port.joints) //(map<string, DynamixelInfo>::iterator iter = port.joints.begin(); iter !=
1385  // port.joints.end(); iter++)
1386  {
1387  state_map[it.second.id] = DynamixelState();
1388  state_map[it.second.id].id = it.second.id;
1389  state_map[it.second.id].type = it.second.model_spec->type;
1390  diag_map[it.second.id] = DynamixelDiagnostic();
1391  diag_map[it.second.id].id = it.second.id;
1392  diag_map[it.second.id].type = it.second.model_spec->type;
1393  if (it.second.model_spec->external_ports)
1394  {
1395  data_map[it.second.id] = DynamixelDataport();
1396  data_map[it.second.id].id = it.second.id;
1397  data_map[it.second.id].type = it.second.model_spec->type;
1398  }
1399  }
1400 
1401  // get state info back from all dynamixels
1402  if (port.driver->getBulkState(state_map))
1403  {
1404  // Iterate over all connected servos and add to list
1405  for (auto &it : port.joints)
1406  {
1407  // //joint name
1408  std::string joint_name = it.first;
1409 
1410  // ignore joints that failed to read
1411  if (!state_map[it.second.id].success)
1412  {
1413  ROS_WARN("FAILED TO READ DYNAMIXEL %s (id %d)!", joint_name.c_str(), it.second.id);
1414  continue;
1415  }
1416 
1417  // put joint name in message
1418  read_msg.name.push_back(joint_name);
1419 
1420  // POSITION VALUE
1421  // get position and convert to radians
1422  // due to the fact that some series of dynamixel do not have a full 360 deg output range, we must scale the,
1423  // encoder counts by this fractional value to get the output in radians, thus:
1424  // rad_pos = (count-count_initial_position) * (range/360) * (2*PI/encoder_cpr)
1425  double rad_pos = (state_map[it.second.id].position - it.second.zero_pos) *
1426  (2.0 * M_PI * it.second.model_spec->encoder_range_deg) /
1427  (360.0 * it.second.model_spec->encoder_cpr);
1428  if (it.second.min_pos > it.second.max_pos)
1429  {
1430  rad_pos = -rad_pos;
1431  }
1432 
1433  // Put position in message
1434  read_msg.position.push_back(rad_pos);
1435 
1436  // VELOCITY VALUE
1437  int raw_vel = state_map[it.second.id].velocity;
1438 
1439  // handle the sign of the value based on the motor series
1440  if (it.second.model_spec->type <= kSeriesLegacyMX)
1441  {
1442  // The old dynamixel series use a different method of representing effort values. They define the value in the
1443  // register as a signed 10-bit value, with the first 9 bits representing magnitude and the 10th bit representing
1444  // the sign.
1445  raw_vel = (raw_vel & 0x3FF);
1446 
1447  if ((raw_vel > 1023) && (it.second.max_pos > it.second.min_pos))
1448  {
1449  raw_vel = -raw_vel;
1450  }
1451  else if ((raw_vel < 1023) && (it.second.max_pos < it.second.min_pos))
1452  {
1453  raw_vel = -raw_vel;
1454  }
1455  }
1456  else if (it.second.min_pos > it.second.max_pos)
1457  {
1458  raw_vel = -raw_vel;
1459  }
1460 
1461  // convert to rad/s
1462  double rad_s_vel = (static_cast<double>(raw_vel)) / it.second.model_spec->velocity_radps_to_reg;
1463 
1464  // put velocity in message
1465  read_msg.velocity.push_back(rad_s_vel);
1466 
1467  // TORQUE EFFORT VALUE
1468  double effort = 0;
1469 
1470  if (it.second.model_spec->type <= kSeriesLegacyMX)
1471  {
1472  // The old dynamixel series use a different method of representing effort values. They define the value in the
1473  // register as a signed 10-bit value, with the first 9 bits representing magnitude and the 10th bit representing
1474  // the sign.
1475  effort = static_cast<double>(state_map[it.second.id].effort & 0x3FF) * it.second.model_spec->effort_reg_to_mA;
1476  // check sign
1477  if (state_map[it.second.id].effort < 1023)
1478  {
1479  effort = 0.0 - effort;
1480  }
1481  }
1482  else
1483  {
1484  effort = static_cast<double>(state_map[it.second.id].effort) * it.second.model_spec->effort_reg_to_mA;
1485  }
1486 
1487  if (it.second.min_pos > it.second.max_pos)
1488  {
1489  effort = -effort;
1490  }
1491 
1492  // put effort in message
1493  read_msg.effort.push_back(effort);
1494  }
1495  }
1496  else
1497  {
1498  ROS_WARN("Failed to get bulk state on port %s", port.port_name.c_str());
1499  }
1500 
1501 
1502  if (read_dataport_ && (data_map.size() > 0))
1503  {
1504  if (port.driver->getBulkDataportInfo(data_map))
1505  {
1506  // dynamixel_interface::ServoState diag_msg;
1507  dataport_msg.header.frame_id = port.port_name.c_str();
1508 
1509  // Iterate over all connected servos and add to list
1510  for (auto &it : port.joints)
1511  {
1512  dynamixel_interface::DataPort msg;
1513 
1514  // ignore joints that failed to read
1515  if (!data_map[it.second.id].success)
1516  {
1517  continue;
1518  }
1519 
1520  // get data
1521  msg.name = it.first;
1522  msg.port_values.push_back(data_map[it.second.id].port_values[0]);
1523  msg.port_values.push_back(data_map[it.second.id].port_values[1]);
1524  msg.port_values.push_back(data_map[it.second.id].port_values[2]);
1525  msg.port_values.push_back(data_map[it.second.id].port_values[3]);
1526 
1527  // push msg into array
1528  dataport_msg.states.push_back(msg);
1529  }
1530  }
1531  else
1532  {
1533  ROS_WARN("Failed to get dataports on port %s", port.port_name.c_str());
1534  }
1535  }
1536 
1537  if (read_diagnostics_)
1538  {
1539  if (port.driver->getBulkDiagnosticInfo(diag_map))
1540  {
1541  // dynamixel_interface::ServoState diag_msg;
1542  diags_msg.header.frame_id = port.port_name.c_str();
1543 
1544  // Iterate over all connected servos and add to list
1545  for (auto &it : port.joints)
1546  {
1547  dynamixel_interface::ServoDiag msg;
1548 
1549  // ignore joints that failed to read
1550  if (!diag_map[it.second.id].success)
1551  {
1552  continue;
1553  }
1554 
1555  // get name
1556  msg.name = it.first;
1557  msg.id = it.second.id;
1558  msg.model_name = it.second.model_spec->name;
1559 
1560  // get error code
1561  msg.error_code = diag_map[it.second.id].error;
1562 
1563  // get voltage (div 10 as units are 0.1V)
1564  msg.voltage = diag_map[it.second.id].voltage / 10.0;
1565 
1566  // get temperatures
1567  msg.temperature = static_cast<double>(diag_map[it.second.id].temperature);
1568 
1569  // push msg into array
1570  diags_msg.diagnostics.push_back(msg);
1571  }
1572  }
1573  else
1574  {
1575  ROS_WARN("Failed to get diagnostics on port %s", port.port_name.c_str());
1576  }
1577  }
1578 }
1579 
1580 } // namespace dynamixel_interface
std::string port_name
User defined port name.
std::unique_ptr< DynamixelInterfaceDriver > driver
The driver object.
double max_vel
Motor maximum joint velocity (rad/s)
int zero_pos
Motor initial position (in raw encoder values). This value defines the 0 radian position.
int min_pos
Motor minimum encoder value. Note that if min > max, the motor direction is reversed.
std::string device
Serial device name.
data struct used with getBulkDataportInfo() to retrieve dataport values
int id
The unique (per port) ID of the motor.
uint diagnostics_iters_
publish when diagnostic_counter_ == this
ROSCPP_DECL const std::string & getName()
data struct used with getBulkDiagnosticInfo() to retrieve diagnostics
uint diagnostics_counter_
Counter for tracking diagnostics rate.
Struct that describes each servo&#39;s place in the system including which joint it corresponds to...
Defines the dynamixel controller class and the types used therein.
ros::Publisher diagnostics_publisher_
Publishes joint states from reads.
#define ROS_WARN(...)
std::mutex write_mutex_
Mutex for write_msg, as there are potentially multiple threads.
ros::Publisher dataport_publisher_
Publishes the data from the external ports on dynamixel_pros.
void jointStateCallback(const sensor_msgs::JointState::ConstPtr &joint_commands)
TransportHints & tcpNoDelay(bool nodelay=true)
double diagnostics_rate_
Desired rate at which servo diagnostic information is published.
std::unordered_map< std::string, DynamixelInfo > joints
map of joint information by name
Type const & getType() const
void publish(const boost::shared_ptr< M > &message) const
bool parseServoInformation(PortInfo &port, XmlRpc::XmlRpcValue servos)
DynamixelSeriesType
Dynamixel types.
double velocity_radps_to_reg
Conversion factor from velocity in radians/sec to register counts.
void multiThreadedWrite(PortInfo &port, sensor_msgs::JointState joint_commands) const
int recv_queue_size_
Receive queue size for desired_joint_states topic.
bool read_diagnostics_
Bool for telling threads to read diagnostics data.
ROSCPP_DECL bool get(const std::string &key, std::string &s)
#define ROS_INFO(...)
sensor_msgs::JointState write_msg_
Stores the last message received from the write command topic.
std::vector< PortInfo > dynamixel_ports_
method of control (position/velocity/torque)
Struct which stores information about each port in use and which joints use that port.
bool initialised_
Bool indicating if we are ready for operation.
ROSCPP_DECL bool has(const std::string &key)
bool read_dataport_
Bool for telling threads to read dataport data.
DynamixelControlMode current_mode
control mode (position, velocity, torque)
~DynamixelInterfaceController()
Destructor, deletes the objects holding the serial ports and disables the motors if required...
bool use_legacy_protocol
boolean indicating if legacy protocol (for older series dynamixels) is in use
bool parameters_parsed_
method of control (position/velocity/torque)
ros::Subscriber joint_state_subscriber_
Gets joint states for writes.
bool stop_motors_on_shutdown_
Indicates if the motors should be turned off when the controller stops.
DynamixelSeriesType type
Model type (e.g MX, AX, Pro)
double dataport_rate_
Rate at which the pro external dataport is read.
Basic struct for representing dynamixel data exchange.
double encoder_range_deg
Motor encoder range in degrees.
std::string joint_name
The unique (globally) name of the joint.
bool ignore_input_velocity_
can set driver to ignore profile velocity commands in position mode
double torque_limit
Motor maximum torque limit (rated max)
int encoder_cpr
Motor encoder counts per revolution.
const DynamixelSpec * model_spec
Motor model specification including encoder counts and unit conversion factors.
static Time now()
int max_pos
Motor maximum encoder value. Note that if min > max, the motor direction is reversed.
bool write_ready_
Booleans indicating if we have received commands.
double effort_reg_max
Max possible value for effort register.
bool sleep() const
double effort_reg_to_mA
Conversion factor from register values to current in mA.
data struct used with getBulkState() to retrieve physical state
DynamixelSeriesType type
type of dynamixel
#define ROS_ERROR(...)
void multiThreadedRead(PortInfo &port, sensor_msgs::JointState &read_msg, dynamixel_interface::DataPorts &dataports_msg, dynamixel_interface::ServoDiags &diags_msg) const
std::vector< uint8_t > data
IO data array.
void multiThreadedIO(PortInfo &port, sensor_msgs::JointState &read_msg, dynamixel_interface::DataPorts &dataport_msg, dynamixel_interface::ServoDiags &status_msg, bool perform_write) const
std::unique_ptr< ros::NodeHandle > nh_
Handler for the ROS Node.
uint dataport_iters_
publish when dataport_counter_ == this
ros::Publisher joint_state_publisher_
Publishes joint states from reads.
double loop_rate_
Desired loop rate (joint states are published at this rate)
bool initialiseDynamixel(PortInfo &port, DynamixelInfo &dynamixel)


dynamixel_interface
Author(s): Tom Molnar
autogenerated on Mon Feb 28 2022 22:15:51