ROS Resources: Documentation | Support | Discussion Forum | Index | Service Status | ros @ Robotics Stack Exchange
Ask Your Question
0

Can I determine the topic which executed a callback in rosserial?

asked 2016-10-04 20:06:12 -0500

kylerlaird gravatar image

updated 2016-10-09 21:11:58 -0500

I have a few PWM input/output pairs connected to my Arduino Nano and defined in an array.

typedef struct {
        char *name;
        byte pin_in;
        byte pin_out;
        unsigned long rise_time;
        unsigned long fall_time;
        byte value_in;
        byte value_out;
        byte state;
} control_t;

control_t controls[]={
     { .name="throttle", .pin_in=3, .pin_out=11, },
     { .name="steering", .pin_in=4, .pin_out=10, },
     { .name="transmission", .pin_in=5, .pin_out=9, },
     { .name=NULL}
};

I want to create a subscriber and publisher for each one so that the input value (from the physical control) is published and the output value (sent to the robot) can be subscribed. I tried using classes but discovered that rosserial doesn't support using class methods for callbacks. So I thought I could have one subscriber callback for all of the controls. It just needs to know the topic used so that it can do a lookup on the control name and set the PWM output of the corresponding pin. For example, publishing 123 to /myrobot/steering/set(?) should put the value 123 on pin 10. (I also welcome help with topic names.)

Is it possible to get the topic name (or ID?) inside the callback? Or is there a better way to handle this?

Thank you.

--kyler

[updates to keep them all together...]

  1. There is good info here on how classes are implemented in C++: http://stackoverflow.com/questions/40...

  2. boost::bind and boost::function (from #1) appear to do what I want.

  3. There is a (partial) Boost library for Arduino: https://github.com/vancegroup/arduino...

  4. C++11 can do lambda/anonymous functions. C++11 still seems to be experimental for Arduino.

  5. I wonder if I should subclass the rosserial code to make it do what I need.

I'm going to try #4 first. I welcome alternatives.

[...]

I punted and made another option:

  1. Use the preprocessor.

I don't like that I have to both define and initialize the controls, but it's otherwise workable and should be easy to extend.

#define CONTROL(n, pi, po, ptmin, ptmax) \
        static control_t control_ ## n = { name:(char *)#n, pin_in:pi, pin_out:po, pt_min:ptmin, pt_max:ptmax, }; \
        void put_ ## n( const std_msgs::UInt8& msg ) { \
                sprintf(buf, "put %s: %u", control_ ## n.name, msg.data); \
                nh.logwarn(buf); \
                control_ ## n.value_out = msg.data; \
                analogWrite(control_ ## n.pin_out, control_ ## n.value_out); \
        } \


#define INIT_CONTROL(n) \
        control_ ## n.next=controls; \
        controls=&control_ ## n; \
        static ros::Subscriber<std_msgs::UInt8> n ## _sub("/" #n "/put", &put_ ## n ); \
        nh.subscribe(n ## _sub); \


control_t *controls = NULL;

CONTROL(throttle, 3, 11, 0, 0)
CONTROL(transmission, 4, 10, 0, 0)
CONTROL(steering, 5, 9, 110, 145)

void setup()
{
        nh.initNode();

        INIT_CONTROL(throttle);
        INIT_CONTROL(transmission);
        INIT_CONTROL(steering);
[...]

(I added support for a min/max range so that I can revert to passthrough mode if a control moves out of the range. IOW, if the steering wheel is moved, full control is passed to the local operator.

I've been a bit concerned about processing several 500 Hz PWM signals but it's working fairly well so far. I need to add at ... (more)

edit retag flag offensive close merge delete

Comments

Can you not get around this by using some global class, that way it can be accessed in any function? Or if it is possible, pass the class as an argument to the callback function?

JoshMarino gravatar image JoshMarino  ( 2016-10-04 22:51:54 -0500 )edit

No, I can't use a class function because ros::Subscriber (in rosserial) requires a plain function with no arguments (including "this"). It looks like the Boost libraries make it possible to create new (argument-less) functions like this. Doing this on Arduino will take some effort.

kylerlaird gravatar image kylerlaird  ( 2016-10-05 08:24:30 -0500 )edit

It might make more sense to define a custom message type that includes all three control signals, and publish and subscribe to that.

ahendrix gravatar image ahendrix  ( 2016-10-10 01:22:59 -0500 )edit

Thank, ahendrix, it probably would make sense for the current situation. But then I'll add some more more controls (perhaps splitting them over multiple Arduinos), and I'll have different nodes publishing to different controls. It would be a pain to keep refactoring and compiling new message types

kylerlaird gravatar image kylerlaird  ( 2016-10-10 09:07:08 -0500 )edit

3 Answers

Sort by ยป oldest newest most voted
0

answered 2016-10-09 21:16:47 -0500

kylerlaird gravatar image

I believe the answer to my question is "no." You can only extract the data (and checksum, etc.) from the message passed to the callback. There does not seem to be a clean way to use a single callback function for multiple subscribers in rosserial.

The real solution is to update Subscriber in rosserial so that it handles arguments (like full ROS does).

edit flag offensive delete link more
0

answered 2017-05-20 02:55:37 -0500

rreignier gravatar image

I am a bit late. But actually, it is possible to use a subscriber within a class with rosserial.

The writing is a bit different than the usual ROS, it uses a template parameter.

See the following example:

#include <ros.h>
#include <std_msgs/UInt8.h>

ros::NodeHandle nh;

struct Control {
  Control(byte out, char* topic)
    : pin_out(out),
      sub(topic, &Control::callback, this)
  {}
  void init() {
    nh.subscribe(sub);
  }
  void callback(const std_msgs::UInt8& msg) {
    analogWrite(pin_out, msg.data);
  }

  byte pin_out;
  ros::Subscriber<std_msgs::UInt8, Control> sub;
};

Control throttle(1, "throttle");
Control transmission(2, "transmission");
Control steering(3, "steering");

void setup() {
  nh.initNode();
  throttle.init();
  transmission.init();
  steering.init();
}

void loop()
{}
edit flag offensive delete link more
0

answered 2016-10-05 08:37:00 -0500

zhibo gravatar image
ros::NodeHandle  nh;

Servo servo;

void servo_cb( const std_msgs::UInt16& cmd_msg){
  servo.write(cmd_msg.data); //set servo angle, should be from 0-180  
  digitalWrite(13, HIGH-digitalRead(13));  //toggle led  
}


ros::Subscriber<std_msgs::UInt16> sub("servo", servo_cb);

void setup(){
  pinMode(13, OUTPUT);

  nh.initNode();
  nh.subscribe(sub);

  servo.attach(8); //attach it to pin 8
}

void loop(){
  nh.spinOnce();
  delay(1);
}

this is a typical rosserial subscriber function, based on this model, you can publish your physical control to the same topic with the adequate message type.

edit flag offensive delete link more

Question Tools

1 follower

Stats

Asked: 2016-10-04 20:06:12 -0500

Seen: 587 times

Last updated: May 20 '17