RobotLabLTH / skiros2

A skill-based platform for ROS v.2
Other
163 stars 20 forks source link

Execute skill by calling ROS service #42

Open robberthofmanfm opened 3 years ago

robberthofmanfm commented 3 years ago

I am working on an integration where I want an external python script to initiate the execution of a skill available in SkiROS2. On the wiki home page, it says

The skills can be parametrized and executed through the SkiROS GUI, or directly through a ROS service for integrated solutions.

While everything works fine using the SkiROS GUI, I haven't got the ROS service working yet.

I just type in rosservice call, and then use tab completion until I see the name of the robot that lives in SkiROS. This give me four options:

of which I assume only the first two to be relevant for this problem.

Calling rosservice call /robot_name/get_skills prints a quite verbose list of skills with their parameters.

Then, further using tab completion on rosservice call /robot_name/command results in a multi-line completion in my terminal, with some parameters to be filled in:

To keep things simple, I made a dummy_primitive, which takes no parameters, and on execution just prints dummy_primitive called and then returns success.

For type, I set skiros:DummyPrimitive, just like I named the SkillDescription class, and just like how it appears in the world model. For name, I set dummy_primitive, like I named it in the primitives file, and also how it appears in the world model. The params section, I either leave unchanged, or remove entirely.

Executing this command results in ok: True and yields the following output in the skill_manager_node terminal:

[start] Starting task 0. [BtTicker] Execution starts. [BtTicker] Execution stops.

However, when executing this very same primitive through the use of the GUI, we can already observe a difference:

[SkillManager] Add task skiros:DummyPrimitive:dummy_primitive [start] Starting task 23. [BtTicker] Execution starts. MatchWm skiros:DummyPrimitive:[Robot=cora:Robot-1-robot_type:robot_name] dummy_primitive called [BtTicker] Execution stops.

It seems that the GUI also makes sure that the SkillManager adds a task, which is then executed.

So to summarize, my question is: how can I execute a skill from an external python script or command line?

robberthofmanfm commented 3 years ago

Something else that I tried and that didn't work, are variations on the code in skiros2/skiros2_skill/nodes/utils/cmd.

Instantiating a SkillLayerInterface and getting all of its agents always results in an empty dictionary for me.

robberthofmanfm commented 3 years ago

I'm getting closer with the rosservice call method. execution_id should be -1, otherwise skill_manager.py L428, _command_cb(self, msg) does not add a task to the skill manager. Then, in skiros2_ws/src/skiros2/skiros2_common/src/skiros2_common/ros/utils.py L171, in the decodeParam(p) method, I added a line if v is not None:, and am now able to call parameterless skills.

I will now try to find out what's going on with decodeParam(p) to make complex skills with parameters working too.

One a side note, I suspect error handling could be better when you pass a command to rosservice call: if you do that with the correct skill description class name (e.g. DummyPrimitive), but wrong skill implementation class name (e.g. dummy_primitive_spelled_wrong), the skill_manager_node terminal shows that it's trying to execute this wrongly spelled skill over and over, even if you use the Qt GUI to execute a different skill. I suspect this is because of skiros2_ws/src/skiros2/skiros2_skill/src/skiros2_skill/core/skill_instanciator.py, L68, method assign_instance(self, skill, ignore_list). It calls skill.setInstance(self.add_instance(skill.label)) there, which I would split into two lines (add_instance can fail too), and maybe also surround with try/except blocks.

frvd commented 3 years ago

Hi @robberthofmanfm , I would always suggest to use the SkillLayerInterface to interface with the skill manager. If the agents results in an empty dictionary, it could be a problem of namespace. It uses a simple discovery system ( see skiros2_skill/src/skiros2_skill/ros/discovery_interface.py ). You have to initialize the SkillLayerInterface with the right namespace, "skill_managers". If you make it work, you will not have to dig into the serialization/deserialization of params yourself.

robberthofmanfm commented 3 years ago

Hi @frvd , thanks a lot for your reply, it made me look in the right direction. The namespace was not the problem, since that's hard coded in the SkillLayerInterface itself, L13: self.init_discovery("skill_managers", self._on_active, self._on_inactive)

However, I understand now that all this information is communicated over ROS messages, so the solution was just to wait until those messages arrive. Before, my code would execute in a few milliseconds and end without success. Now, it waits for the ROS message, picks up the agent and asks the agent to execute a (parameterless) skill. Now I just need to get the parameters working.

The relevant piece of code is the following:

    _sli = sli.SkillLayerInterface('/external_interface')
    while(_sli.agents == dict()):
        pass

    manager = _sli.agent
    type_in = "skiros:DummyPrimitive"
    name = "dummy_primitive"
    sk = SkillHolder(manager, type_in, name, params_in=None, children=None)
    print(_sli.agent.execute(skill_list=[sk]))

I'm not sure if that while loop is considered good practice (I don't want to rospy_spin because this code will be called from within an endless loop), but this seems to work.

Maybe we can document this as a tutorial in the wiki, together with some rosservice call commands?

Thanks again for your help, it's much appreciated.

frvd commented 3 years ago

Hi again, I am glad you found the solution :)

Indeed, waiting with a while loop is the most straightforward solution I can think about at the moment. In the future we could implement an Event to wait.

To execute skills with parameters, you can use first <agent>.get_skill(<name>). It returns a skill holder with all parameters initialized to defaults. You can then pass it to the execute, like in your example.

I agree that a tutorial on this would be helpful. Do you think it would be better to describe the usage of SkillLayerInterface, or the use of raw services?

robberthofmanfm commented 3 years ago

Awesome! That seems to work.

I suppose both SkillLayerInterface and ROS services have their advantages, where the first can be used to easily integrate services based on python (and is imo cleaner and less environment dependent), and the latter can be used for interoperability with any language that can call the equivalent of python's os.system().

Personally, I'm moving forward with the SkillLayerInterface approach since our 'external' service is also written in python :)

dhled commented 3 years ago

Hello @robberthofmanfm do you consider the problem solved ? Should we close the issue ?

Thanks :)

robberthofmanfm commented 3 years ago

Hi @dhled , yes the issue itself is resolved, but I think it might be useful to create a tutorial out of this in the wiki. I don't know if there's some standard process for this. If another wiki tutorial is not preferred then I guess users can always find this issue :)