controller.cpp
Go to the documentation of this file.
00001 
00026 #include "roboteq_driver/controller.h"
00027 #include "roboteq_driver/channel.h"
00028 
00029 #include "roboteq_msgs/Status.h"
00030 #include "serial/serial.h"
00031 
00032 #include <boost/bind.hpp>
00033 #include <boost/algorithm/string/replace.hpp>
00034 #include <boost/algorithm/string/trim.hpp>
00035 #include <boost/algorithm/string/split.hpp>
00036 #include <boost/algorithm/string/classification.hpp>
00037 #include <unistd.h>
00038 #include <iostream>
00039 #include <sstream>
00040 
00041 // Link to generated source from Microbasic script file. 
00042 extern const char* script_lines[];
00043 extern const int script_ver = 8;
00044 
00045 namespace roboteq {
00046 
00047 const std::string eol("\r");
00048 const size_t max_line_length(128);
00049 
00050 Controller::Controller(const char *port, int baud)
00051   : nh_("~"), port_(port), baud_(baud), connected_(false), version_(""),
00052     start_script_attempts_(0), serial_(NULL),
00053     command("!", this), query("?", this), param("^", this)
00054 {
00055   pub_status_ = nh_.advertise<roboteq_msgs::Status>("status", 1);
00056 }
00057 
00058 Controller::~Controller() {
00059 }
00060 
00061 void Controller::addChannel(Channel* channel) {
00062   channels_.push_back(channel);
00063 }
00064 
00065 void Controller::connect() {
00066   if (!serial_) serial_ = new serial::Serial();
00067   serial::Timeout to(serial::Timeout::simpleTimeout(500));
00068   serial_->setTimeout(to);
00069   serial_->setPort(port_);
00070   serial_->setBaudrate(baud_);
00071 
00072   for (int tries = 0; tries < 5; tries++) {
00073     try {
00074       serial_->open();
00075       query << "FID" << send;
00076       setSerialEcho(false);
00077       flush();
00078     } catch (serial::IOException) {
00079     }
00080 
00081     if (serial_->isOpen()) {
00082       connected_ = true;
00083       return;
00084     } else {
00085       connected_ = false;
00086       ROS_INFO("Bad Connection with serial port Error %s",port_);
00087     }
00088   }
00089 
00090   ROS_INFO("Motor controller not responding.");
00091 }
00092 
00093 void Controller::read() {
00094   ROS_DEBUG_STREAM_NAMED("serial", "Bytes waiting: " << serial_->available());
00095   std::string msg = serial_->readline(max_line_length, eol);
00096   static bool receiving_script_messages = false;
00097   if (!msg.empty()) {
00098     ROS_DEBUG_STREAM_NAMED("serial", "RX: " << msg);
00099     if (msg[0] == '+' || msg[0] == '-') {
00100       // Notify the ROS thread that a message response (ack/nack) has arrived.
00101       boost::lock_guard<boost::mutex> lock(last_response_mutex_);
00102       last_response_ = msg;
00103       last_response_available_.notify_one();
00104     } else if (msg[0] == '&') {
00105       // Message generated by the Microbasic script.
00106       boost::trim(msg);
00107       if (msg[1] == 's') {
00108         processStatus(msg);
00109       } else if (msg[1] == 'f') {
00110         processFeedback(msg);
00111       } 
00112       receiving_script_messages = true;
00113     } else {
00114       // Unknown other message.
00115       ROS_WARN_STREAM("Unknown serial message received: " << msg);
00116     }
00117   } else {
00118     ROS_WARN_NAMED("serial", "Serial::readline() returned no data.");
00119     if (!receiving_script_messages) {
00120       if (start_script_attempts_ < 5) {
00121         start_script_attempts_++;
00122         ROS_DEBUG("Attempt #%d to start MBS program.", start_script_attempts_);
00123         startScript();
00124         flush();
00125       } else {
00126         ROS_DEBUG("Attempting to download MBS program.");
00127         if (downloadScript()) {
00128           start_script_attempts_ = 0;
00129         }
00130         ros::Duration(1.0).sleep(); 
00131       }
00132     } else {
00133       ROS_DEBUG("Script is believed to be in-place and running, so taking no action.");
00134     }
00135   }
00136 }
00137 
00138 void Controller::write(std::string msg) {
00139   tx_buffer_ << msg << eol;
00140 }
00141 
00142 void Controller::flush() {
00143   ROS_DEBUG_STREAM_NAMED("serial", "TX: " << boost::algorithm::replace_all_copy(tx_buffer_.str(), "\r", "\\r"));
00144   ssize_t bytes_written = serial_->write(tx_buffer_.str());
00145   if (bytes_written < tx_buffer_.tellp()) {
00146     ROS_WARN_STREAM("Serial write timeout, " << bytes_written << " bytes written of " << tx_buffer_.tellp() << ".");
00147   }
00148   tx_buffer_.str("");
00149 }
00150 
00151 void Controller::processStatus(std::string str) {
00152   roboteq_msgs::Status msg;
00153   msg.header.stamp = ros::Time::now();
00154 
00155   std::vector<std::string> fields;
00156   boost::split(fields, str, boost::algorithm::is_any_of(":"));
00157   try {
00158     int reported_script_ver = boost::lexical_cast<int>(fields[1]);
00159     static int wrong_script_version_count = 0;
00160     if (reported_script_ver == script_ver) {
00161       wrong_script_version_count = 0;
00162     } else {
00163       if (++wrong_script_version_count > 5) {
00164         ROS_WARN_STREAM("Script version mismatch. Expecting " << script_ver <<
00165             " but controller consistently reports " << reported_script_ver << ". " <<
00166             ". Now attempting download.");
00167         downloadScript();
00168       }
00169       return;
00170     }
00171     
00172     if (fields.size() != 7) {
00173       ROS_WARN("Wrong number of status fields. Dropping message.");
00174       return;
00175     }
00176 
00177     msg.fault = boost::lexical_cast<int>(fields[2]);
00178     msg.status = boost::lexical_cast<int>(fields[3]);
00179     msg.ic_temperature = boost::lexical_cast<int>(fields[6]);
00180   } catch (std::bad_cast& e) {
00181     ROS_WARN("Failure parsing status data. Dropping message.");
00182     return;
00183   }
00184 
00185   pub_status_.publish(msg); 
00186 }
00187 
00188 void Controller::processFeedback(std::string msg) {
00189   std::vector<std::string> fields;
00190   boost::split(fields, msg, boost::algorithm::is_any_of(":"));
00191   if (fields.size() != 11) {
00192     ROS_WARN("Wrong number of feedback fields. Dropping message.");
00193     return;
00194   }
00195   int channel_num;
00196   try {
00197     channel_num = boost::lexical_cast<int>(fields[1]);
00198   } catch (std::bad_cast& e) {
00199     ROS_WARN("Failure parsing feedback channel number. Dropping message.");
00200     return;
00201   }
00202   if (channel_num >= 1 && channel_num <= channels_.size()) {
00203     channels_[channel_num - 1]->feedbackCallback(fields);
00204   } else {
00205     ROS_WARN("Bad channel number. Dropping message.");
00206     return;
00207   }
00208 }
00209 
00210 bool Controller::downloadScript() {
00211   ROS_DEBUG("Commanding driver to stop executing script.");
00212   stopScript(); flush();
00213   ros::Duration(0.5).sleep();
00214   serial_->read();  // Clear the buffer.
00215   
00216   // Send SLD.
00217   ROS_DEBUG("Commanding driver to enter download mode.");
00218   write("%SLD 321654987"); flush();
00219 
00220   // Look for special ack from SLD.
00221   for (int find_ack = 0; find_ack < 7; find_ack++) {
00222     std::string msg = serial_->readline(max_line_length, eol);
00223     ROS_DEBUG_STREAM_NAMED("serial", "HLD-RX: " << msg); 
00224     if (msg == "HLD\r") goto found_ack;
00225   }
00226   ROS_DEBUG("Could not enter download mode.");
00227   return false;
00228   found_ack:
00229   
00230   // Send hex program, line by line, checking for an ack from each line.
00231   int line_num = 0;
00232   while(script_lines[line_num]) {
00233     std::string line(script_lines[line_num]);
00234     write(line);
00235     flush();
00236     std::string ack = serial_->readline(max_line_length, eol);
00237     ROS_DEBUG_STREAM_NAMED("serial", "ACK-RX: " << ack);
00238     if (ack != "+\r") return false;
00239     line_num++;
00240   }
00241   return true;
00242 }
00243 
00244 }  // namespace roboteq


roboteq_driver
Author(s):
autogenerated on Fri Feb 12 2016 00:10:33