hand_node.cpp
Go to the documentation of this file.
00001 /* Driver node that communicates with an Otto Bock SensorHand Speed 
00002  * prosthetics device.
00003  * 
00004  * Institute for Robotics (ROBIN), JKU Linz
00005  * Alexander Reiter
00006  * January 2014
00007  * 
00008  * This node establishes a serial connection with an Otto Bock 
00009  * SensorHand Speed prosthetics device and allows to issue gripping 
00010  * commands and receive feedback.
00011  * 
00012  * The device offers multiple modes of operation:
00013  *  0: gripping with soft grip fastening
00014  *  1: hand stops after first grip
00015  *  2: maximum velocity, maximum force will be applied
00016  *  3,4: undocumented
00017  * 
00018  * PARAMETERS:
00019  *  port (str): name of serial device (default /dev/ttyUSB0)
00020  *  baud_rate (int): baud rate of serial interface (needs to be 19200 = default)
00021  * 
00022  * PUBLISHED TOPICS:
00023  *  hand_dump (sensorhand_speed::hand_dump): topic on which feedback is published
00024  * 
00025  * SUBSCRIBED TOPICS:
00026  *  none
00027  * 
00028  * OFFERED SERVICES:
00029  *  hand_srv (sensorhand_speed::hand_srv): offers access to device functionality
00030 */
00031 
00032 #include <stdio.h>
00033 #include <vector>
00034 #include <math.h> 
00035 #include <sstream>
00036 
00037 #include <ros/ros.h>
00038 #include <cereal_port/CerealPort.h>
00039 
00040 #include "sensorhand_speed/hand_cmd.h"
00041 #include "sensorhand_speed/hand_dump.h"
00042 
00043 #define TIMEOUT 100
00044 
00045 // structs and enums
00046 enum Command_id {START_DUMP = 0, STOP_DUMP = 1, OPEN = 2, CLOSE = 3, STOP = 4, GET_SN = 5, RESET = 6, PRGM = 7};
00047 typedef struct {
00048     Command_id id;
00049     double param;
00050     char* response; // data vector of expected response
00051 } Command;
00052 
00053 enum State {IDLE, READ_FIRST, READ_LEN, READ_DATA, READ_CS} state;
00054 
00055 // function declarations
00056 bool readByte(cereal::CerealPort* device, char* data);
00057 bool sendCommand(cereal::CerealPort* device, Command* cmd);
00058 bool srv_callback(sensorhand_speed::hand_cmd::Request& request, sensorhand_speed::hand_cmd::Response& response);
00059 void publish_dump(ros::Publisher& dump_pub, char* data);
00060 
00061 
00062 // vector of pending commands
00063 std::vector<Command> pending_cmd;
00064 bool dump_mode = false;
00065 
00066 /* NOTE:
00067  * If a message that is being sent by the hand is interrupted by a 
00068  * command, the message will be re-sent completely before the response 
00069  * to the command will be sent.
00070  */
00071 
00072 int main(int argc, char** argv) {
00073   ros::init(argc, argv, "hand_node");
00074   ros::NodeHandle n("~"); // local node handle for accessing local parameters
00075 
00076   cereal::CerealPort device;  // instance of CerealPort
00077 
00078   // init service for controlling the hand device
00079   ros::ServiceServer service = n.advertiseService("hand_srv", srv_callback);
00080   
00081   // init publisher for dump values
00082   ros::Publisher dump_pub = n.advertise<sensorhand_speed::hand_dump>("hand_dump", 1000);
00083   
00084   std::string port;
00085   if (!n.hasParam("port")) {
00086     ROS_WARN("Parameter port not received, using default value /dev/ttyUSB0");
00087   }
00088   n.param<std::string>("port", port, "/dev/ttyUSB0");
00089 
00090   int baud_rate;
00091   if (!n.hasParam("baud_rate")) {
00092     ROS_WARN("Parameter baud_rate not received, using default value 19200");
00093   }
00094   n.param<int>("baud_rate", baud_rate, 19200);
00095 
00096   try { 
00097     device.open(port.c_str(), baud_rate); 
00098   }
00099   catch(cereal::Exception& e) {
00100       ROS_FATAL("Unable to open the serial port %s.", port.c_str());
00101       ROS_BREAK();
00102   }
00103   ROS_INFO("The serial port %s is opened at %d baud", port.c_str(), baud_rate);
00104 
00105   // prepare variables for reading data from the serial connection
00106   char reply; // received byte
00107   char* data; // data vector
00108   size_t data_pos = 0;  // position in data vector
00109   uint8_t data_len = 0; // length of expected data stream
00110   char cs = 0x00; // check sum
00111   
00112   // give ROS some time to sync
00113   ros::Duration(0.5).sleep();
00114   ros::spinOnce();
00115   
00116   state = IDLE;
00117   ROS_INFO("state IDLE");
00118   
00119   
00120   Command cmd;  // declare a Command instance for various purposes
00121   
00122   // flush serial connection buffer
00123   device.flush();
00124   // reset hand electronics
00125   cmd.param = 0.;
00126   cmd.id = RESET;
00127   //pending_cmd.push_back(cmd);
00128   sendCommand(&device, &cmd);
00129   device.flush();
00130   
00131   bool dump_msg = false;
00132   bool busy = false;
00133   bool waiting_for_dump;
00134   
00135   ros::Rate r(100);
00136   while(ros::ok()) {
00137     if(!pending_cmd.empty() && !busy && !waiting_for_dump) {  // commands pending
00138       device.flush();
00139       sendCommand(&device, &(pending_cmd[0]));
00140       state = READ_FIRST;
00141       busy = true;
00142     }
00143     switch(state) {
00144       case READ_FIRST: // check for dump symbol
00145         if(readByte(&device, &reply)) {
00146           if((uint8_t) reply == 0xff) { // dump symbol
00147             state = READ_LEN;
00148             ROS_INFO("state READ_LEN");
00149             dump_msg = true;
00150           } else {
00151             data_len = (uint8_t) reply;
00152             ROS_INFO("data_len is %u", data_len);
00153             if(!data) free(data);
00154             data = (char*) calloc(data_len, sizeof(char));
00155             data_pos = 0;
00156             cs = reply; // message length is counted in check sum
00157             state = READ_DATA;
00158             ROS_INFO("state READ_DATA (from READ_FIRST)");
00159             dump_msg = false;
00160           }
00161         }
00162         break;
00163       case READ_LEN:  // read size of message length
00164         if(readByte(&device, &reply)) {
00165           data_len = (uint8_t) reply;
00166           ROS_INFO("data_len is %u", data_len);
00167           if(!data) free(data);
00168           data = (char*) calloc(data_len, sizeof(char));
00169           data_pos = 0;
00170           cs = reply; // message length is counted in check sum
00171           state = READ_DATA;
00172           ROS_INFO("state READ_DATA (from READ_LEN)");
00173         }
00174         break;
00175       case READ_DATA: // read data
00176         if(readByte(&device, &reply)) { 
00177           if(data_pos < data_len - 1) { // check sum takes up last position in received frame and is counted in data_len
00178             data[data_pos] = reply;
00179             data_pos++;
00180             cs = cs + reply;
00181             if(data_pos == data_len - 1) {
00182               state = READ_CS;
00183               ROS_INFO("state READ_CS");
00184             }
00185           } 
00186         }
00187         break;
00188       case READ_CS: // read and check check sum
00189         if(readByte(&device, &reply)) { 
00190           if(reply != cs) {
00191             ROS_WARN("Check sum incorrect, received data will be discharded");
00192             data_len = 0;
00193             free(data);
00194             break;
00195           }
00196           if(data_len == 2 && data[0] == 0x73) {  // dump start command confirmed
00197             dump_mode = true;
00198             waiting_for_dump = true;
00199             state = READ_FIRST;
00200             ROS_INFO("state READ_FIRST");
00201           }
00202           if(data_len == 2 && data[0] == 0x7a) {  // dump stop command confirmed
00203             dump_mode = false;
00204             ROS_INFO("DUMP MODE OFF");
00205           }
00206           if(dump_msg && dump_mode) {
00207             publish_dump(dump_pub, data);
00208             cmd.id = STOP_DUMP;
00209             sendCommand(&device, &cmd);
00210             ros::Duration(0.05).sleep();
00211             device.flush();
00212             cmd.id = START_DUMP;
00213             pending_cmd.push_back(cmd);
00214             waiting_for_dump = false;
00215           } else {
00216             if(data_pos+1 != (uint8_t) pending_cmd[0].response[0]) {
00217               ROS_WARN("Response length of %u incorrect, expected %u", (uint8_t) pending_cmd[0].response[0], data_pos+1 );
00218             } else {
00219               for(size_t i = 0; i < data_pos; i++) {
00220                 if(data[i] != pending_cmd[0].response[i+1]) {
00221                   ROS_WARN("Response data %02x incorrect, expected %02x", (uint8_t) data[i], (uint8_t) pending_cmd[0].response[i] );
00222                 }
00223               }
00224             }
00225             pending_cmd.erase(pending_cmd.begin());
00226           }
00227           if(!dump_mode) {
00228             state = IDLE;
00229             ROS_INFO("state IDLE");
00230             // remove all START_DUMPs
00231             for(size_t i = 0; i < pending_cmd.size(); i++) {
00232               if(pending_cmd[i].id == START_DUMP) {
00233                 pending_cmd.erase(pending_cmd.begin() + i);
00234                 i--;
00235               }
00236             }
00237             
00238           }
00239           ROS_INFO("Received data is:");
00240           for(size_t i = 0; i < data_pos; i++) {
00241             ROS_INFO("%02x", (uint8_t) data[i]);
00242           }
00243           busy = false;
00244           
00245           free(data);
00246         }
00247         break;
00248       default:
00249         break;
00250     }
00251     
00252     ros::spinOnce();
00253     r.sleep();
00254   }
00255   free(data);
00256   return 0;
00257 }
00258 
00259 /* Function that reads a byte from the serial port device and stores it
00260  * in data if received correctly.
00261  * 
00262  * INPUT:   cereal::CerealPort &device: pointer to serial port device instance
00263  *          char* data: pointer to data
00264  * OUTPUT:  bool: true if transmission was successful, false otherwise
00265  */
00266 bool readByte(cereal::CerealPort* device, char* data) {
00267   try {
00268     device->readBytes(data, 1, TIMEOUT); // timeout in ms
00269   } catch(cereal::TimeoutException& e) {
00270       ROS_ERROR("Timeout while reading from serial port.");
00271       return false;
00272   }
00273   ROS_INFO("Received %02x", (uint8_t) *data);
00274   return true;
00275 }
00276 
00277 /* Function that sends a pre-defined command over the serial  
00278  * interface device. The command may incorporate additional information 
00279  * which has to be provided in additional arguments (e.g. speed for 
00280  * opening or closing the hand).
00281  * 
00282  * INPUT:   cereal::CerealPort &device: pointer to serial port device instance
00283  *          Command* cmd: pointer to command
00284  * OUTPUT:  bool:   true if transmission successful, otherwise false
00285  */
00286 bool sendCommand(cereal::CerealPort* device, Command* cmd) {
00287   uint8_t cmd_size = 0;  // length of command excluding length symbol, including check sum
00288   
00289   char cmd_buffer[5]; // longest command is size 6 (including length symbol and check sum)
00290   
00291   // longest reply is of length 8 (serial number; including length, excluding check sum)
00292   // first element will hold length of expected reply (excluding length symbol), comparable to first symbol of actual reply
00293   cmd->response = (char*) calloc(8, sizeof(char));
00294   
00295   switch(cmd->id) {
00296     case START_DUMP:
00297       cmd_size = 2;
00298       cmd_buffer[1] = 0x73;
00299       //state = READ_FIRST;
00300       //ROS_INFO("state READ_FIRST");
00301       
00302       cmd->response[0] = 2;
00303       cmd->response[1] = cmd_buffer[1];
00304       
00305       break;
00306     case STOP_DUMP:
00307       cmd_size = 2;
00308       cmd_buffer[1] = 0x7a;
00309       //state = READ_LEN;
00310       //ROS_INFO("state READ_LEN");
00311       
00312       cmd->response[0] = 2;
00313       cmd->response[1] = cmd_buffer[1];
00314       
00315       // dump mode will be turned off after stop command response was received
00316       break;
00317     case OPEN:
00318       cmd_size = 4;
00319       cmd_buffer[1] = 0x75;
00320       { // calculate speed
00321       cmd->param = std::min(std::max(fabs(cmd->param), 0.), 1.0);  // saturate v with 0..1
00322       cmd_buffer[2] = (char) (0x31 + cmd->param*(0x85 - 0x31));
00323       }
00324       cmd_buffer[3] = 0x00;
00325       //state = READ_LEN;
00326       //ROS_INFO("state READ_LEN");
00327       
00328       cmd->response[0] = 3;
00329       cmd->response[1] = cmd_buffer[2];
00330       cmd->response[2] = cmd_buffer[3];
00331       break;
00332     case CLOSE:
00333       cmd_size = 4;
00334       cmd_buffer[1] = 0x75;
00335       cmd_buffer[2] = 0x00;
00336       { // calculate speed
00337       cmd->param = std::min(std::max(fabs(cmd->param), 0.), 1.0);  // saturate v with 0..1
00338       cmd_buffer[3] = (char) (0x31 + cmd->param*(0x85 - 0x31));
00339       }
00340       //state = READ_LEN;
00341       //ROS_INFO("state READ_LEN");
00342       
00343       cmd->response[0] = 3;
00344       cmd->response[1] = cmd_buffer[2];
00345       cmd->response[2] = cmd_buffer[3];
00346       break;
00347     case STOP:
00348       cmd_size = 4;
00349       cmd_buffer[1] = 0x75;
00350       cmd_buffer[2] = 0x00;
00351       cmd_buffer[3] = 0x00;
00352       //state = READ_LEN;
00353       //ROS_INFO("state READ_LEN");
00354       
00355       cmd->response[0] = 3;
00356       cmd->response[1] = cmd_buffer[2];
00357       cmd->response[2] = cmd_buffer[3];
00358       break;
00359     case GET_SN:
00360       cmd_size = 2;
00361       cmd_buffer[1] = 0x76;
00362       //state = READ_LEN;
00363       //ROS_INFO("state READ_LEN");
00364       
00365       // this will be different on each hand
00366       cmd->response[0] = 8;
00367       cmd->response[1] = 0x31;
00368       cmd->response[2] = 0x31;
00369       cmd->response[3] = 0x30;
00370       cmd->response[4] = 0x2c;
00371       cmd->response[5] = 0x30;
00372       cmd->response[6] = 0x2e;
00373       cmd->response[7] = 0x35;
00374       break;
00375     case RESET:
00376       cmd_size = 2;
00377       cmd_buffer[1] = 0x6d;
00378       //state = READ_LEN;
00379       //ROS_INFO("state READ_LEN");
00380       
00381       cmd->response[0] = 2;
00382       cmd->response[1] = 0x6d;
00383       break;
00384     case PRGM:
00385       cmd_size = 3;
00386       cmd_buffer[1] = 0x61;
00387       cmd_buffer[2] = (char) (cmd->param);
00388       //state = READ_LEN;
00389       //ROS_INFO("state READ_LEN");
00390       
00391       cmd->response[0] = 3;
00392       cmd->response[1] = 0x61;
00393       cmd->response[2] = cmd_buffer[2];
00394       break;
00395     default:
00396       ;
00397   }
00398   cmd_buffer[0] = cmd_size;
00399   cmd_buffer[cmd_size] = 0;
00400   
00401   // calculate check sum
00402   for(uint8_t i = 0; i < cmd_size; i++) {
00403     cmd_buffer[cmd_size] = cmd_buffer[cmd_size] + cmd_buffer[i];
00404   }
00405   
00406   std::stringstream ss;
00407   ss << "Command";
00408   char substr[4];
00409   for(uint8_t i = 0; i <= cmd_size; i++) {
00410     snprintf(substr, 4, " %02x", cmd_buffer[i]);
00411     ss << substr;
00412   }
00413   if(device->write(cmd_buffer, cmd_size + 1) == cmd_size + 1) {
00414     ss << " sent successfully";
00415     ROS_INFO_STREAM(ss.str());
00416     return true;
00417   } else {
00418     ss << " NOT successful";
00419     ROS_INFO_STREAM(ss.str());
00420     return false;
00421   }
00422   
00423 }
00424 
00425 /* Service callback function that is used to receive operation requests 
00426  * for the hand device. If the communication with the device succeeded,
00427  * true is returned as the function return value as well as in the 
00428  * service response data structure.
00429  * 
00430  * INPUT: sensorhand_speed::hand_cmd::Request&: pointer to request structure
00431  *        sensorhand_speed::hand_cmd::Response&: pointer to response structure
00432  * OUTPUT: bool if communiction with the hand device was successful
00433  */
00434 bool srv_callback(sensorhand_speed::hand_cmd::Request& request, sensorhand_speed::hand_cmd::Response& response) {
00435   ROS_INFO("Hand service called");
00436   
00437   Command cmd;
00438   cmd.param = request.param;
00439   cmd.id = (Command_id) request.cmd;
00440   pending_cmd.push_back(cmd);
00441   
00442   response.success = true;
00443   return true;
00444 }
00445 
00446 /* Function that reads data from the data received from the hand device
00447  * and converts it from binary to numeric values, stores it in a 
00448  * sensorhand_speed::hand_dump structure and published the message by means of 
00449  * the provided publisher.
00450  * 
00451  * Conversion information in [brackets]:
00452  * float32 V_open, V_close # electrode voltages in V [0 .. 85 -> 0 .. 1.5 V]
00453  * float32 V_bat       # supply voltage in V [140 -> 4.9 V, 238 -> 8.4 V ]
00454  * uint8 state         # state number of the hand's internal state machine [no conversion]
00455  * float32 F_bracket   # bracket force in N [0 .. 110 -> 0 .. 120 N ]
00456  * float32 F_pad{1,2,3}# pad force in N [ no sure, maybe like brackets]
00457  * float32 I           # supply current in A [ 0 .. 141 -> 0 .. 1.5 A]
00458  * 
00459  * INPUT: ros::Publisher& dump_pub: publisher for sending dump data
00460  *        char* data: data array of size 9
00461  * OUTPUT:  none
00462  */
00463 void publish_dump(ros::Publisher& dump_pub, char* data) {
00464   sensorhand_speed::hand_dump msg;
00465   msg.V_open = 1.5/85 * (uint8_t) data[0];
00466   msg.V_close = 1.5/85 * (uint8_t) data[1];
00467   msg.V_bat = 8.4/238 * (uint8_t) data[2];
00468   msg.state = (uint8_t) data[3];
00469   msg.F_bracket = 120./110 * (uint8_t) data[4];
00470   msg.F_pad1 = 120./110 * (uint8_t) data[5];
00471   msg.F_pad2 = 120./110 * (uint8_t) data[6];
00472   msg.F_pad3 = 120./110 * (uint8_t) data[7];
00473   msg.I = 1.5/141 * (uint8_t) data[8];
00474   
00475   dump_pub.publish(msg);
00476 }


sensorhand_speed
Author(s): Alexander Reiter
autogenerated on Fri Aug 28 2015 13:21:01