Skip to content

Commit 1de65d1

Browse files
authored
Merge pull request #9 from collaborative-robotics/rc-1.2
1.2.0
2 parents a289d94 + 9eb7742 commit 1de65d1

17 files changed

+1053
-665
lines changed

CHANGELOG.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
Change log
2+
==========
3+
4+
1.2.0 (2023-11-21)
5+
==================
6+
7+
* :warning: Use `PoseStamped` instead of `TransformStamped` for CRTK `_cp` commands (see collaborative-robotics/documentation#1)
8+
* Documentation ported to ReadTheDocs: https://crtk-robotics.readthedocs.io
9+
* Added `crtk.ral` (ROS Abstraction Layer) so Python scripts can be written for either ROS 1 or ROS 2
10+
* `ral.check_connections` allows you to check if there are subscribers/publishers connected to your publishers/subscriber
11+
* `ral.spin` starts the thread for ROS spin (no op on ROS 1)
12+
* `ral.parse_argv` parses and removed ROS specific arguments
13+
* ...
14+
* `utils.py`:
15+
* Fixed bug in `measured_cp`, was returning `setpoint_cp`
16+
* Fixed `wait` on move handle
17+
* Add `hold`, `free`, `forward_kinematics`, `inverse_kinematics`, `servo_cv`
18+
* Added `joystick_button` to wrap foot pedals and other buttons

CMakeLists.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
cmake_minimum_required (VERSION 2.8.3)
2-
project (crtk_python_client)
1+
cmake_minimum_required (VERSION 3.1)
2+
project (crtk_python_client VERSION 1.2.0)
33

44
## Find catkin macros and libraries
55
## if COMPONENTS list like find_package(catkin REQUIRED COMPONENTS xyz)

README.md

Lines changed: 1 addition & 156 deletions
Original file line numberDiff line numberDiff line change
@@ -1,158 +1,3 @@
11
# CRTK Python client library
22

3-
This Python package provides some tools to facilitate the development of a CRTK compatible client over ROS, i.e. create a "proxy" class to communicate with an existing CRTK compatible ROS device.
4-
5-
CRTK specifications can be found on the [CRTK github page](https://github.com/collaborative-robotics/documentation/wiki/Robot-API).
6-
7-
Examples of CRTK devices with a CRTK/ROS interface:
8-
* [Raven II](https://github.com/uw-biorobotics/raven2/tree/crtk)
9-
* [dVRK](https://github.com/jhu-dvrk/sawIntuitiveResearchKit/wiki)
10-
11-
# Build
12-
13-
To build this package, we recommend to use the catkin build tools, i.e. `catkin build`.
14-
You will need to clone this repository as well as the repository with CRTK specific ROS messages:
15-
```bash
16-
cd ~/catkin_ws/src # or wherever your catkin workspace is
17-
git clone https://github.com/collaborative-robotics/crtk_msgs
18-
git clone https://github.com/collaborative-robotics/crtk_python_client
19-
catkin build
20-
```
21-
22-
Once these packages are built, you should source your `setup.bash`: `source ~/catkin_ws/devel/setup.bash`.
23-
At that point, you should be able to import the crtk python package in Python using `import crtk`.
24-
25-
# Usage
26-
27-
The main class in the CRTK Python client library is `crtk.utils`. It can be used to quickly populate an existing class by adding CRTK like methods.
28-
These methods will handle the following for you:
29-
* declare all required ROS publishers and wrap publisher calls in methods to send data to the device.
30-
* declare all required ROS subscribers and provide callbacks to receive the data from the device.
31-
* convert ROS messages to more convenient Python data types, i.e. numpy arrays for joint values and PyKDL types for cartesian data.
32-
* some events to manage asynchronous communication between the device and the "proxy" class.
33-
34-
The class `crtk.utils` is designed to add CRTK features "a la carte", i.e. it doesn't assume that all CRTK features are available. This allows to:
35-
* match only the features that are available on the CRTK devices one wants to use (server side)
36-
* reduce the number of features to those strictly needed for the application (client side). Reducing the number of ROS topics used helps in terms of performance.
37-
38-
## Base class and `crtk.utils`
39-
40-
You can find some examples in the `scripts` directory. Overall, the approach is always the same, i.e. create a class and populate it with `crtk.utils`. You can then use an instance of this class. For example:
41-
42-
```python
43-
class crtk_move_cp_example:
44-
# constructor
45-
def __init__(self, device_namespace):
46-
# ROS initialization
47-
if not rospy.get_node_uri():
48-
rospy.init_node('crtk_move_cp_example', anonymous = True, log_level = rospy.WARN)
49-
# populate this class with all the ROS topics we need
50-
self.crtk_utils = crtk.utils(self, device_namespace)
51-
self.crtk_utils.add_measured_cp()
52-
self.crtk_utils.add_move_cp()
53-
```
54-
55-
What is happening behind the scene:
56-
* `device_namespace` is the ROS namespace used by the device. E.g. if the namespace is `left`, we assume the device will have its CRTK ROS topics under `/left`.
57-
* `get_node_uri()` and `init_node()` are not strictly needed but helps if the user did not properly initialize the ROS node
58-
* Add an instance of `crtk.utils` in your class. The first parameter indicates which Python object should be "populated", i.e. which object will have the CRTK methods added to its dictionary.
59-
* `add_measured_cp()`:
60-
* Creates a subscriber for the topic, e.g. : `/left/measured_cp`
61-
* Registers a built-in callback for the topic. The callback will store the latest `measured_cp` ROS message in `crtk_utils`
62-
* Provides a method to read the latest `measured_cp` message as a PyKDL frame.
63-
* Adds the method `measured_cp()` to the user class (`crtk_move_cp_example`)
64-
* `add_move_cp()`:
65-
* Creates a publisher for the topic, e.g. : `/left/move_cp`
66-
* Provides a method to send a PyKDL frame (goal), internally converts to a ROS message.
67-
* Adds the method `move_cp()` to the user class (`crtk_move_cp_example`)
68-
69-
Once the class is defined, the user can use it:
70-
```python
71-
example = crtk_move_cp_example('left')
72-
position = example.measured_cp()
73-
position.p[2] += 0.05 # move by 5 cm
74-
example.move_cp(position)
75-
```
76-
77-
## Motion Commands
78-
79-
`crtk.utils` supports the following CRTK features:
80-
* subscribers:
81-
* `add_setpoint_js`, `add_setpoint_cp`
82-
* `add_measured_js`, `add_measured_cp`, `add_measured_cv`, `add_measured_cf`
83-
* ...
84-
* publishers
85-
* `add_servo_jp`, `add_servo_jf`, `add_servo_cp`, `add_servo_cf`
86-
* `add_move_jp`, `add_move_cp`
87-
* ...
88-
89-
All methods relying on subscribers to get data have the following two _optional_ parameters: `age` and `wait`:
90-
```python
91-
setpoint_cp(age = None, wait = None)
92-
```
93-
The parameter `age` specifies how old the data can be to be considered valid and `wait` specifies how long to wait for the next message if the data currently cached is too old. By default, both are based on the expected interval provided when creating an instance of `crtk.utils`. The expected interval should match the publishing rate from the CRTK device. Setting the `age` to zero means that any cached data should be used and the method shouldn't wait for new messages.
94-
95-
All move commands (`move_jp` and `move_cp`) return a ROS time object. This is the time just before sending (i.e., publishing) the move command to the device. This timestamp can be used to wait for motion completion using:
96-
```python
97-
# wait until robot is not busy, i.e. move has ended
98-
h = example.move_cp(goal) # record time move was sent
99-
h.wait()
100-
# compact syntax
101-
example.move_cp(goal).wait()
102-
# other example, wait until move has started
103-
example.move_cp(goal).wait(is_busy = True)
104-
```
105-
106-
The method `wait_for_busy` used by `handle.wait()` depends on the CRTK device operating state and can be added to the example class using `crtk.utils.add_operating_state`. See section below.
107-
108-
## Operating States
109-
110-
`crtk.utils.add_operating_state` adds:
111-
* State status `operating_state()` and helper queries: `is_enabled()`,`is_homed()`, `is_busy()`
112-
* State command `operating_state_command()` and helper commands: `enable()`, `disable()`, `home()`, `unhome()`
113-
* Timer/event utilities:
114-
* For subscribers: `wait_for_valid_data`
115-
* For publishers (used by move commands): , `wait_for_busy()`
116-
* For state changes (used by `enable()`, `home()`...): `wait_for_operating_state()`
117-
118-
# Examples
119-
120-
## dVRK
121-
122-
For the dVRK, one can use the classes `dvrk.arm`, `dvrk.psm`, `dvrk.mtm`... that use the `crtk.utils` to provide as many features as possible. This is convenient for general purpose testing, for example in combination with iPython to test snippets of code. In general, it is recommended to use your own class and only add the features you need to reduce the number of ROS messages and callbacks.
123-
124-
The dVRK arm class implementation can be found in the [dvrk_python](https://github.com/jhu-dvrk/dvrk-ros/blob/devel/dvrk_python/src/dvrk/arm.py) package.
125-
126-
Example of use:
127-
```python
128-
import dvrk
129-
p = dvrk.arm('PSM1')
130-
p.enable()
131-
p.home()
132-
133-
# get measured joint state
134-
[position, velocity, effort, time] = p.measured_js()
135-
# get only position
136-
position = p.measured_jp()
137-
# get position and time
138-
[position, time] = p.measured_jp(extra = True)
139-
140-
# move in joint space
141-
import numpy
142-
p.move_jp(numpy.array([0.0, 0.0, 0.10, 0.0, 0.0, 0.0]))
143-
144-
# move in cartesian space
145-
import PyKDL
146-
# start position
147-
goal = p.setpoint_cp()
148-
# move 5cm in z direction
149-
goal.p[2] += 0.05
150-
p.move_cp(goal).wait()
151-
152-
import math
153-
# start position
154-
goal = p.setpoint_cp()
155-
# rotate tool tip frame by 25 degrees
156-
goal.M.DoRotX(math.pi * 0.25)
157-
p.move_cp(goal).wait()
158-
```
3+
See https://crtk-robotics.readthedocs.io

package.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<?xml version="1.0"?>
22
<package>
33
<name>crtk_python_client</name>
4-
<version>1.0.0</version>
4+
<version>1.2.0</version>
55
<description>crtk Python client</description>
66

77
<maintainer email="[email protected]">Anton Deguet</maintainer>

scripts/crtk_haptic_example.py

Lines changed: 74 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -14,47 +14,79 @@
1414

1515
# To communicate with the device using ROS topics, see the python based example:
1616
# > rosrun crtk_python_client crtk_haptic_example <device-namespace>
17+
# For dVRK, add -b/--body flag when running the crtk_haptic_example
1718

19+
import argparse
1820
import crtk
19-
import math
20-
import sys
21-
import rospy
22-
import numpy
2321
import PyKDL
22+
import std_msgs.msg
23+
import sys
24+
2425

2526
if sys.version_info.major < 3:
2627
input = raw_input
2728

28-
# example of application using device.py
29+
2930
class crtk_haptic_example:
31+
class Subframe:
32+
def __init__(self, ral):
33+
self.ral = ral
34+
self.crtk_utils = crtk.utils(self, ral)
35+
self.crtk_utils.add_servo_cf()
36+
37+
self.set_cf_orientation_absolute_pub = self.ral.publisher(
38+
'set_cf_orientation_absolute', std_msgs.msg.Bool)
39+
40+
def set_cf_orientation_absolute(self, absolute = True):
41+
m = std_msgs.msg.Bool()
42+
m.data = absolute
43+
self.set_cf_orientation_absolute_pub.publish(m)
3044

31-
# configuration
32-
def configure(self, device_namespace):
33-
# ROS initialization
34-
if not rospy.get_node_uri():
35-
rospy.init_node('crtk_haptic_example', anonymous = True, log_level = rospy.WARN)
45+
def __init__(self, ral, has_body_orientation):
46+
self.ral = ral
3647

37-
print(rospy.get_caller_id() + ' -> configuring crtk_device_test for: ' + device_namespace)
3848
# populate this class with all the ROS topics we need
39-
self.crtk_utils = crtk.utils(self, device_namespace)
49+
self.crtk_utils = crtk.utils(self, ral)
4050
self.crtk_utils.add_operating_state()
4151
self.crtk_utils.add_measured_cp()
4252
self.crtk_utils.add_measured_cv()
43-
self.crtk_utils.add_servo_cf()
53+
54+
if has_body_orientation:
55+
self.body = crtk_haptic_example.Subframe(self.ral.create_child('/body'))
56+
self.servo_cf = lambda sp: self.body.servo_cf(sp)
57+
else:
58+
self.body = None
59+
self.crtk_utils.add_servo_cf()
60+
4461
# for all examples
45-
self.duration = 10 # 10 seconds
46-
self.rate = 500 # aiming for 500 Hz
62+
self.duration = 20 # seconds
63+
self.rate = 200 # aiming for 200 Hz
4764
self.samples = self.duration * self.rate
4865

4966
# main loop
5067
def run(self):
51-
if not self.enable(60):
52-
print("Unable to enable the device, make sure it is connected.")
68+
self.ral.check_connections()
69+
70+
if not self.enable(30):
71+
print('Unable to enable the device, make sure it is connected.')
72+
return
73+
74+
if not self.home(30):
75+
print('Unable to home the device, make sure it is connected.')
5376
return
5477

78+
if self.body is not None:
79+
self.body.set_cf_orientation_absolute()
80+
5581
self.running = True
5682
while (self.running):
57-
print ('\n- q: quit\n- p: print position, velocity\n- b: virtual box around current position with linear forces (10s)\n- v: viscosity (10s)')
83+
msg = ('\n'
84+
'- q: quit\n'
85+
'- p: print position, velocity\n'
86+
'- b: virtual box around current position with linear forces ({}s)\n'
87+
'- v: viscosity ({}s)'
88+
)
89+
print(msg.format(self.duration, self.duration))
5890
answer = input('Enter your choice and [enter] to continue\n')
5991
if answer == 'q':
6092
self.running = False
@@ -75,11 +107,14 @@ def run_print(self):
75107

76108
# virtual box
77109
def run_box(self):
78-
# save current position
79-
dim = 0.01 # 2 cm cube
110+
dim = 0.01 # +/- 1 cm per dimension creates a 2 cm cube
80111
p_gain = -500.0
112+
113+
# save current position
81114
center = PyKDL.Frame()
82115
center.p = self.measured_cp().p
116+
117+
sleep_rate = self.ral.create_rate(self.rate)
83118
for i in range(self.samples):
84119
wrench = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
85120
# foreach d dimension x, y, z
@@ -90,32 +125,38 @@ def run_box(self):
90125
elif (distance < -dim):
91126
wrench[d] = p_gain * (distance + dim)
92127
self.servo_cf(wrench)
93-
rospy.sleep(1.0 / self.rate)
128+
sleep_rate.sleep()
129+
94130
wrench = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
95131
self.servo_cf(wrench)
96132

97133
# viscosity
98134
def run_viscosity(self):
99135
d_gain = -10.0
136+
sleep_rate = self.ral.create_rate(self.rate)
100137
for i in range(self.samples):
101138
wrench = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
102139
# foreach d dimension x, y, z
103140
for d in range(3):
104141
wrench[d] = d_gain * self.measured_cv()[d]
105142
self.servo_cf(wrench)
106-
rospy.sleep(1.0 / self.rate)
143+
sleep_rate.sleep()
144+
107145
wrench = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
108146
self.servo_cf(wrench)
109147

110-
# use the class now, i.e. main program
111-
if __name__ == '__main__':
112-
try:
113-
if (len(sys.argv) != 2):
114-
print(sys.argv[0], ' requires one argument, i.e. crtk device namespace')
115-
else:
116-
example = crtk_haptic_example()
117-
example.configure(sys.argv[1])
118-
example.run()
119148

120-
except rospy.ROSInterruptException:
121-
pass
149+
def main():
150+
parser = argparse.ArgumentParser()
151+
parser.add_argument('namespace', type = str, help = 'ROS namespace for CRTK device')
152+
parser.add_argument('-b', '--body', action = 'store_true', help = 'Indicates CRTK device body orientation needs to be set to absolute')
153+
app_args = crtk.ral.parse_argv(sys.argv[1:]) # process and remove ROS args
154+
args = parser.parse_args(app_args)
155+
156+
example_name = type(crtk_haptic_example).__name__
157+
ral = crtk.ral(example_name, args.namespace)
158+
example = crtk_haptic_example(ral, args.body)
159+
ral.spin_and_execute(example.run)
160+
161+
if __name__ == '__main__':
162+
main()

0 commit comments

Comments
 (0)