Skip to content

Commit

Permalink
action server model
Browse files Browse the repository at this point in the history
  • Loading branch information
maker-ATOM committed Sep 16, 2023
1 parent 9dfbd9c commit 4aab328
Show file tree
Hide file tree
Showing 15 changed files with 398 additions and 4 deletions.
28 changes: 24 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,19 @@ Basic utilization of server client model.
<br>
Implementation of node parameters.

**custom_interface**
<br>
Package for custom interfaces.

**actionsrvcli**
<br>
Basic utilization of Action server client model.

## Repository Structure
```python
├── pubsub
│   ├── package.xml
│   ├── pubsub
│   │   ├── __init__.py
│   │   ├── publisher.py
│   │   └── subscriber.py
│   ├── README.md
Expand All @@ -49,21 +56,35 @@ Implementation of node parameters.
│ ├── setup.py
│ └── srvcli
│    ├── client.py
│    ├── __init__.py
│    └── server.py
├── node_parameters
│   ├── launch
│   │   └── parametric_node_launch.launch.py
│   ├── node_parameters
│   │   ├── __init__.py
│   │   └── parametric_node.py
│   ├── package.xml
│   ├── README.md
│   ├── setup.cfg
│   └── setup.py
├── actionsrvcli
│   ├── actionsrvcli
│   │   ├── actionclient.py
│   │   ├── actionserver.py
│   ├── package.xml
│   ├── README.md
│   ├── setup.cfg
│   └── setup.py
├── custom_interfaces
│   ├── action
│   │   └── Fibonacci.action
│   ├── CMakeLists.txt
│   ├── package.xml
│   └── README.md
├── Python for ROS
│   ├── joint.py
│   └── README.md
Expand All @@ -77,5 +98,4 @@ Implementation of node parameters.
│ └── README.md
└── README.md

```
132 changes: 132 additions & 0 deletions actionsrvcli/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
# Action Server Model

## Server

```python
def __init__(self):
super().__init__('fibonacci_action_server')
self._action_server = ActionServer(
self,
Fibonacci,
'fibonacci',
self.execute_callback)
```
The init method setups a node name `fibonacci_action_server`. Then the we instantiate a new action server with the associated to the action client, Fibonacci as the action type the server will handle, 'fibonacci' as the action name and execute_callback method to be executed when action goal is received.

```python
def execute_callback(self, goal_handle):
self.get_logger().info('Executing goal...')
result = Fibonacci.Result()
return result
```

If the callback had only this content the server will work, but we will gwt a warning stating goal not set.

```python
def execute_callback(self, goal_handle):
self.get_logger().info('Executing goal...')

goal_handle.succeed()

result = Fibonacci.Result()
return result
```

We need to use succeed method() on goal handle to indicate that goal was successful. We will receive goal finished with the status SUCCEEDED. But in this case nothing is being computed specifically the fibonacci series.

```python
def execute_callback(self, goal_handle):
self.get_logger().info('Executing goal...')

sequence = [0, 1]
for i in range(1, goal_handle.request.order):
sequence.append(sequence[i] + sequence[i-1])

goal_handle.succeed()
result = Fibonacci.Result()
result.sequence = sequence
return result
```
Now the series is being calculated and stored within the sequence element of result.

```python
def execute_callback(self, goal_handle):
self.get_logger().info('Executing goal...')

feedback_msg = Fibonacci.Feedback()
feedback_msg.partial_sequence = [0, 1]

for i in range(1, goal_handle.request.order):
feedback_msg.partial_sequence.append(
feedback_msg.partial_sequence[i] + feedback_msg.partial_sequence[i-1])
self.get_logger().info('Feedback: {0}'.format(feedback_msg.partial_sequence))
goal_handle.publish_feedback(feedback_msg)
time.sleep(1)

goal_handle.succeed()

result = Fibonacci.Result()
result.sequence = feedback_msg.partial_sequence
return result
```
We can also provide feedback to an action client during goal execution. We can make our action server publish feedback for action clients by calling the goal handle’s publish_feedback() method.

We’ll replace the sequence variable, and use a feedback message to store the sequence instead. After every update of the feedback message in the for-loop.

## Client

```python
def __init__(self):
super().__init__('fibonacci_action_client')
self._action_client = ActionClient(self, Fibonacci, 'fibonacci')
```

In the init method we setup the node and instantiate a action client, arguments begin the ROS2 node utilizing the action service, type of action and action name.

```python
def send_goal(self, order):
goal_msg = Fibonacci.Goal()
goal_msg.order = order

self._action_client.wait_for_server()

return self._action_client.send_goal_async(goal_msg)
```
This method waits for the action server to be available, then sends a goal to the server. It returns a future that we can later wait on. This method only sends the goal to receive and does not takes result into account.

```python
def send_goal(self, order):
goal_msg = Fibonacci.Goal()
goal_msg.order = order

self._action_client.wait_for_server()

self._send_goal_future = self._action_client.send_goal_async(goal_msg)

self._send_goal_future.add_done_callback(self.goal_response_callback)
```

The modified send goal method sends the request and waits for the service to be available. The ActionClient.send_goal_async() method returns a future to a goal handle. First we register a callback for when the future is complete. Note that the future is completed when an action server accepts or rejects the goal request.

```python
def goal_response_callback(self, future):
goal_handle = future.result()
if not goal_handle.accepted:
self.get_logger().info('Goal rejected :(')
return

self.get_logger().info('Goal accepted :)')

self._get_result_future = goal_handle.get_result_async()
self._get_result_future.add_done_callback(self.get_result_callback)
```

After the action server rejects or accepts the goal request, goal_response_callback is executed. Within it is checked if rejected it returns early. If accepted a message is logged. Now that we’ve got a goal handle, we can use it to request the result with the method get_result_async(). Similar to sending the goal, we will get a future that will complete when the result is ready.In the callback, we log the result sequence and shutdown ROS 2 for a clean exit.

We can also receive feedbacks from teh server.

## ROS Graph

<p align="center">
<img src="images/rosgraph.png"/>
</p>
Empty file.
Binary file not shown.
Binary file not shown.
56 changes: 56 additions & 0 deletions actionsrvcli/actionsrvcli/actionclient.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import rclpy
from rclpy.action import ActionClient
from rclpy.node import Node

from action_tutorials_interfaces.action import Fibonacci


class FibonacciActionClient(Node):

def __init__(self):
super().__init__('fibonacci_action_client')
self._action_client = ActionClient(self, Fibonacci, 'fibonacci')

def send_goal(self, order):
goal_msg = Fibonacci.Goal()
goal_msg.order = order

self._action_client.wait_for_server()

self._send_goal_future = self._action_client.send_goal_async(goal_msg, feedback_callback=self.feedback_callback)

self._send_goal_future.add_done_callback(self.goal_response_callback)

def goal_response_callback(self, future):
goal_handle = future.result()
if not goal_handle.accepted:
self.get_logger().info('Goal rejected :(')
return

self.get_logger().info('Goal accepted :)')

self._get_result_future = goal_handle.get_result_async()
self._get_result_future.add_done_callback(self.get_result_callback)

def get_result_callback(self, future):
result = future.result().result
self.get_logger().info('Result: {0}'.format(result.sequence))
rclpy.shutdown()

def feedback_callback(self, feedback_msg):
feedback = feedback_msg.feedback
self.get_logger().info('Received feedback: {0}'.format(feedback.partial_sequence))


def main(args=None):
rclpy.init(args=args)

action_client = FibonacciActionClient()

action_client.send_goal(10)

rclpy.spin(action_client)


if __name__ == '__main__':
main()
61 changes: 61 additions & 0 deletions actionsrvcli/actionsrvcli/actionserver.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import time


import rclpy
from rclpy.action import ActionServer
from rclpy.node import Node

from action_tutorials_interfaces.action import Fibonacci


class FibonacciActionServer(Node):

def __init__(self):
super().__init__('fibonacci_action_server')
self._action_server = ActionServer(
self,
Fibonacci,
'fibonacci',
self.execute_callback)

def execute_callback(self, goal_handle):
self.get_logger().info('Executing goal...')


feedback_msg = Fibonacci.Feedback()

feedback_msg.partial_sequence = [0, 1]


for i in range(1, goal_handle.request.order):

feedback_msg.partial_sequence.append(

feedback_msg.partial_sequence[i] + feedback_msg.partial_sequence[i-1])

self.get_logger().info('Feedback: {0}'.format(feedback_msg.partial_sequence))

goal_handle.publish_feedback(feedback_msg)

time.sleep(1)


goal_handle.succeed()

result = Fibonacci.Result()

result.sequence = feedback_msg.partial_sequence

return result


def main(args=None):
rclpy.init(args=args)

fibonacci_action_server = FibonacciActionServer()

rclpy.spin(fibonacci_action_server)


if __name__ == '__main__':
main()
Binary file added actionsrvcli/images/rosgraph.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
21 changes: 21 additions & 0 deletions actionsrvcli/package.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?xml version="1.0"?>
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="3">
<name>actionsrvcli</name>
<version>0.0.0</version>
<description>TODO: Package description</description>
<maintainer email="[email protected]">aditya</maintainer>
<license>TODO: License declaration</license>

<test_depend>ament_copyright</test_depend>
<test_depend>ament_flake8</test_depend>
<test_depend>ament_pep257</test_depend>
<test_depend>python3-pytest</test_depend>

<depend>rclpy</depend>
<exec_depend>custom_interfaces</exec_depend>

<export>
<build_type>ament_python</build_type>
</export>
</package>
Empty file.
4 changes: 4 additions & 0 deletions actionsrvcli/setup.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[develop]
script_dir=$base/lib/actionsrvcli
[install]
install_scripts=$base/lib/actionsrvcli
27 changes: 27 additions & 0 deletions actionsrvcli/setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from setuptools import find_packages, setup

package_name = 'actionsrvcli'

setup(
name=package_name,
version='0.0.0',
packages=find_packages(exclude=['test']),
data_files=[
('share/ament_index/resource_index/packages',
['resource/' + package_name]),
('share/' + package_name, ['package.xml']),
],
install_requires=['setuptools'],
zip_safe=True,
maintainer='aditya',
maintainer_email='[email protected]',
description='TODO: Package description',
license='TODO: License declaration',
tests_require=['pytest'],
entry_points={
'console_scripts': [
'server = actionsrvcli.actionserver:main',
'client = actionsrvcli.actionclient:main',
],
},
)
Loading

0 comments on commit 4aab328

Please sign in to comment.