RobotWebTools / roslibjs

The Standard ROS JavaScript Library
https://robotwebtools.github.io/roslibjs
Other
660 stars 373 forks source link

Extending the documentation of ServiceRequest #579

Closed bhomaidan1990 closed 1 year ago

bhomaidan1990 commented 1 year ago

The documentation of ServiceRequest.js states that: "@param values - object matching the fields defined in the .srv definition file".

I have created this .srv file:

geometry_msgs/Point pick
geometry_msgs/Point place
uint16 speed
bool arm
---
bool success

with a simple print service server:

#!/usr/bin/python3

import rospy
from print_service_server.srv import PickPlace, PickPlaceResponse

def ppp(req):
    pick = req.pick
    place = req.place
    speed = req.speed
    arm = req.arm
    print(pick, place, speed, arm)
    return PickPlaceResponse(True)

def pick_place_rapid_server():
    rospy.init_node('pick_place_server')
    s = rospy.Service('pick_place', PickPlace, ppp)
    rospy.spin()

if __name__ == "__main__":
    pick_place_rapid_server()

Calling that service from the terminal it works:

$ rosservice call /pick_place "pick:
  x: 0.0
  y: 0.0
  z: 0.0
place:
  x: 0.0
  y: 0.0
  z: 0.0
speed: 0
arm: False" 
success: True

But when I try to call the same service using ServiceRequest it doesn't work!

import { Ros, Param, Message, Topic, Service, ServiceRequest } from "roslib";

export default class ROSInterface {
  constructor() {
    this.ros = new Ros({
      url: "ws://localhost:9090",
    });
  }

  createMessage=(msg_data) =>{
    const msg = new Message(msg_data);
    return msg;
  }

  createService=(service_name, service_type) =>{
    const service = new Service({
      ros: this.ros,
      name: service_name,
      serviceType: service_type,
    });

    return service;
  }

  callService=(service_name, service_type, params) =>{
    const service = this.createService(service_name, service_type);
    const request = new ServiceRequest(params);
    service.callService(request, function (result) {
      return result;
    });
  }
}

I have tried calling the service using different approaches:

import ROSInterface from "./ROSInterface.js"

export default class PlanScheduler {
    constructor() {
        this.iface = new ROSInterface();
        this.old_status = false;
        this.execute_action();
    }

    init() {
      this.execute_action();
    }

    execute_action = () => {

        const service_name = "/pick_place";
        const service_type = "print_service_server/PickPlace";

        /*******************************************
        // let params = "{pick: \n  x: 0.0 \n  y: 0.0\n  z: 0.0\nplace:\n  x: 0.0\n  y: 0.0\n  z: 0.0\nspeed: 0\narm: false}";
        ********************************************/
        /*******************************************
        // let params = {
        //     pick : {
        //       x : 0.1,
        //       y : 0.2,
        //       z : 0.3
        //     },
        //     place : {
        //       x : -0.1,
        //       y : -0.2,
        //       z : -0.3
        //     },
        //     speed:  200 ,
        //     arm: false
        //   }
        ********************************************/
        let pick = this.iface.createMessage({
            x: 0.1,
            y: 0.2,
            z: 0.3
        });
        let place = this.iface.createMessage({
            x: 0.1,
            y: 0.2,
            z: 0.3
        });
        let speed = this.iface.createMessage(100);
        let arm = this.iface.createMessage(true);
        let params = {pick, place, speed, arm}

        let result = this.iface.callService(service_name, service_type, params);
        console.log(result);
    }
}

I'm using roslaunch rosbridge_server rosbridge_websocket.launch and I can subscribe and publish, the only problem is that the documentation is not clear to me.

"@param values - object" what should be inside this object { }? Messages created using ROSLib.js, objects, strings....??

MatthijsBurgh commented 1 year ago

Check the simple example. It is very clear.

bhomaidan1990 commented 1 year ago

Check the simple example. It is very clear. @MatthijsBurgh

I have seen the example {a: 1, b: 2} and the Twist one, and therefore I created my service params: let params = { pick : { x : 0.1, y : 0.2, z : 0.3 }, place : { x : -0.1, y : -0.2, z : -0.3 }, speed: 200 , arm: false } but that didn't work! the documentaion doesn't explain the complex service calls composed of multiple message types!

MatthijsBurgh commented 1 year ago

It works the same as an composed message. Which is also in the simple example.

You are creating multiple message instances which you combine in one object which you send to the service. This is not according the example. You should create one object with just other objects as children.

bhomaidan1990 commented 1 year ago

@MatthijsBurgh "It works the same as an composed message" As you can see in the question, I have tried three approaches to send the params to the ServiceCall, one of them is "one object with just other objects as children", and that also didn't work!

MatthijsBurgh commented 1 year ago

Can you confirm the child objects are plain objects? Not Message objects?

Also try to implement your message/service in the simple example as the bug could also be in your wrapper functions.

Also provide some logs. Just providing the code and saying it doesn't work isn't very clear. I have no clue what is not working.

bhomaidan1990 commented 1 year ago

@MatthijsBurgh

Can you confirm the child objects are plain objects? Not Message objects?

I can confirm this point as you can see here

Also try to implement your message/service in the simple example as the bug could also be in your wrapper functions.

I have changed my implementation here to comply with this request.

Also provide some logs. Just providing the code and saying it doesn't work isn't very clear. I have no clue what is not working.

I have created a reproducible example here but I don't understand well which type of logs should I provide as I don't get any error or warning, and the service is not called!

MatthijsBurgh commented 1 year ago

Thanks for the example.

I have run your example on my machine(Ubuntu 20.04, ROS1 Noetic). (I don't think there is any need to run npm run build) When running the npm run serve and opening the provided url in Google Chrome (109.0.5414.10 dev), I checked the dev tools console. It showed the service was called and it was successful. Also the server printed the correct values. So your example is valid and the problem is somewhere else on your machine. I provided my package-lock.json, so you know which npm versions of the dependencies should work.

package-lock.zip

bhomaidan1990 commented 1 year ago

@MatthijsBurgh Thanks for your time and support.

bhomaidan1990 commented 1 year ago

@MatthijsBurgh
Adding the error print function to the service call (which is not mentioned in the documentation), and changing the name of the function that calls the service from callService to a different name has solved the problem. thank you again for your valuable feedback.

import { Ros, Param, Message, Topic, Service, ServiceRequest } from "roslib";

export default class ROSInterface {
  constructor() {
    this.ros = new Ros({
      url: "ws://localhost:9090",
    });
  }

  createMessage=(msg_data) =>{
    const msg = new Message(msg_data);
    return msg;
  }

  createService=(service_name, service_type) =>{
    const service = new Service({
      ros: this.ros,
      name: service_name,
      serviceType: service_type,
    });

    return service;
  }

  callService_=(service_name, service_type, params) =>{
    const service = this.createService(service_name, service_type);
    const request = new ServiceRequest(params);
    service.callService(
      req,
      (result) => {
        console.log(result);
        serv_status = result;
      },
      (error) => {
        console.log(error);
      }
    );
  }
}