Skip to content

Commit 9f54883

Browse files
committed
Finish p_servo and tests
1 parent 19ff60f commit 9f54883

File tree

3 files changed

+82
-97
lines changed

3 files changed

+82
-97
lines changed

roboticstoolbox/tools/__init__.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from roboticstoolbox.tools.null import null
2-
from roboticstoolbox.tools.p_servo import p_servo, p_servo_old
2+
from roboticstoolbox.tools.p_servo import p_servo
33
from roboticstoolbox.tools.Ticker import Ticker
44
from roboticstoolbox.tools.urdf import * # noqa
55
from roboticstoolbox.tools.trajectory import (
@@ -21,7 +21,6 @@
2121
__all__ = [
2222
"null",
2323
"p_servo",
24-
"p_servo_old",
2524
"Ticker",
2625
"tpoly",
2726
"tpoly_func",

roboticstoolbox/tools/p_servo.py

Lines changed: 49 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,50 @@
55
import math
66

77

8-
def p_servo_old(wTe, wTep, gain=2, threshold=0.1):
8+
def _angle_axis(T, Td):
9+
e = np.empty(6)
10+
e[:3] = Td[:3, -1] - T[:3, -1]
11+
R = Td[:3, :3] @ T[:3, :3].T
12+
li = np.array([R[2, 1] - R[1, 2], R[0, 2] - R[2, 0], R[1, 0] - R[0, 1]])
13+
14+
if base.iszerovec(li):
15+
# diagonal matrix case
16+
if np.trace(R) > 0:
17+
# (1,1,1) case
18+
a = np.zeros((3,))
19+
else:
20+
a = np.pi / 2 * (np.diag(R) + 1)
21+
else:
22+
# non-diagonal matrix case
23+
ln = base.norm(li)
24+
a = math.atan2(ln, np.trace(R) - 1) * li / ln
25+
26+
e[3:] = a
27+
28+
return e
29+
30+
31+
def p_servo(wTe, wTep, gain=1.0, threshold=0.1, method="rpy"):
932
"""
1033
Position-based servoing.
1134
1235
Returns the end-effector velocity which will cause the robot to approach
1336
the desired pose.
1437
1538
:param wTe: The current pose of the end-effecor in the base frame.
16-
:type wTe: SE3
39+
:type wTe: SE3 or ndarray
1740
:param wTep: The desired pose of the end-effecor in the base frame.
18-
:type wTep: SE3
19-
:param gain: The gain for the controller
20-
:type gain: float
41+
:type wTep: SE3 or ndarray
42+
:param gain: The gain for the controller. Can be vector corresponding to each
43+
axis, or scalar corresponding to all axes.
44+
:type gain: float, or array-like
2145
:param threshold: The threshold or tolerance of the final error between
2246
the robot's pose and desired pose
2347
:type threshold: float
48+
:param method: The method used to calculate the error. Default is 'rpy' -
49+
error in the end-effector frame. 'angle-axis' - error in the base frame
50+
using angle-axis method.
51+
:type method: string: 'rpy' or 'angle-axis'
2452
2553
:returns v: The velocity of the end-effecotr which will casue the robot
2654
to approach wTep
@@ -31,71 +59,31 @@ def p_servo_old(wTe, wTep, gain=2, threshold=0.1):
3159
3260
"""
3361

34-
if not isinstance(wTe, SE3):
35-
wTe = SE3(wTe)
36-
37-
if not isinstance(wTep, SE3):
38-
wTep = SE3(wTep)
39-
40-
# Pose difference
41-
eTep = wTe.inv() * wTep
62+
if isinstance(wTe, SE3):
63+
wTe = wTe.A
4264

43-
# Translational error
44-
ev = eTep.t
65+
if isinstance(wTep, SE3):
66+
wTep = wTep.A
4567

46-
# Angular error
47-
ew = eTep.rpy("rad")
68+
if method == "rpy":
69+
# Pose difference
70+
eTep = np.linalg.inv(wTe) @ wTep
71+
e = np.empty(6)
4872

49-
# Form error vector
50-
e = np.r_[ev, ew]
73+
# Translational error
74+
e[:3] = eTep[:3, -1]
5175

52-
# Desired end-effector velocity
53-
v = gain * e
54-
55-
if np.sum(np.abs(e)) < threshold:
56-
arrived = True
76+
# Angular error
77+
e[3:] = base.tr2rpy(eTep, unit="rad", order="zyx", check=False)
5778
else:
58-
arrived = False
59-
60-
return v, arrived
79+
e = _angle_axis(wTe, wTep)
6180

62-
63-
def _angle_axis(T, Td):
64-
e = np.zeros(6)
65-
e[:3] = Td.t - T.t
66-
R = Td.R @ T.R.T
67-
li = np.array([R[2, 1] - R[1, 2], R[0, 2] - R[2, 0], R[1, 0] - R[0, 1]])
68-
69-
if base.iszerovec(li):
70-
# diagonal matrix case
71-
if np.trace(R) > 0:
72-
# (1,1,1) case
73-
a = np.zeros((3,))
74-
else:
75-
a = np.pi / 2 * (np.diag(R) + 1)
81+
if base.isscalar(gain):
82+
k = gain * np.eye(6)
7683
else:
77-
# non-diagonal matrix case
78-
ln = base.norm(li)
79-
a = math.atan2(ln, np.trace(R) - 1) * li / ln
80-
81-
e[3:] = a
82-
83-
return e
84-
85-
86-
def p_servo(wTe, wTep, gain=[1.0, 1.0, 1.0, 1.0, 1.0, 1.0], threshold=0.1):
87-
if not isinstance(wTe, SE3):
88-
wTe = SE3(wTe)
89-
90-
if not isinstance(wTep, SE3):
91-
wTep = SE3(wTep)
92-
93-
k = np.diag(gain)
94-
95-
e = _angle_axis(wTe, wTep)
84+
k = np.diag(gain)
9685

9786
v = k @ e
98-
9987
arrived = True if np.sum(np.abs(e)) < threshold else False
10088

10189
return v, arrived

tests/test_tools.py

Lines changed: 32 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -11,39 +11,19 @@
1111

1212

1313
class Testtools(unittest.TestCase):
14-
1514
def test_null(self):
1615

1716
a0 = np.array([1, 2, 3])
1817

19-
a1 = np.array([
20-
[1, 2, 3],
21-
[4, 5, 6]
22-
])
23-
24-
a2 = np.array([
25-
[1, 2, 3],
26-
[4, 5, 6],
27-
[7, 8, 9]
28-
])
29-
30-
ans0 = np.array([
31-
[-0.5345, -0.8018],
32-
[0.7745, -0.3382],
33-
[-0.3382, 0.4927]
34-
])
35-
36-
ans1 = np.array([
37-
[0.4082],
38-
[-0.8165],
39-
[0.4082]
40-
])
41-
42-
ans2 = np.array([
43-
[-0.4082],
44-
[0.8165],
45-
[-0.4082]
46-
])
18+
a1 = np.array([[1, 2, 3], [4, 5, 6]])
19+
20+
a2 = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
21+
22+
ans0 = np.array([[-0.5345, -0.8018], [0.7745, -0.3382], [-0.3382, 0.4927]])
23+
24+
ans1 = np.array([[0.4082], [-0.8165], [0.4082]])
25+
26+
ans2 = np.array([[-0.4082], [0.8165], [-0.4082]])
4727

4828
r0 = rp.null(a0)
4929
r1 = rp.null(a1)
@@ -53,16 +33,33 @@ def test_null(self):
5333
nt.assert_array_almost_equal(np.abs(r1), np.abs(ans1), decimal=4)
5434
nt.assert_array_almost_equal(np.abs(r2), np.abs(ans2), decimal=4)
5535

56-
def test_p_servo(self):
36+
def test_p_servo_rpy(self):
37+
a = sm.SE3()
38+
b = sm.SE3.Rx(0.7) * sm.SE3.Tx(1)
39+
c = sm.SE3.Tz(0.59)
40+
41+
v0, arrived0 = rp.p_servo(a, b, method="rpy")
42+
v1, _ = rp.p_servo(a.A, b.A, method="rpy")
43+
_, arrived1 = rp.p_servo(a, c, threshold=0.6, method="rpy")
44+
45+
ans = np.array([1, 0, 0, 0.7, -0, 0])
46+
47+
nt.assert_array_almost_equal(v0, ans, decimal=4)
48+
nt.assert_array_almost_equal(v1, ans, decimal=4)
49+
50+
self.assertFalse(arrived0)
51+
self.assertTrue(arrived1)
52+
53+
def test_p_servo_angle_axis(self):
5754
a = sm.SE3()
5855
b = sm.SE3.Rx(0.7) * sm.SE3.Tx(1)
5956
c = sm.SE3.Tz(0.59)
6057

61-
v0, arrived0 = rp.p_servo(a, b)
58+
v0, arrived0 = rp.p_servo(a, b, method="angle-axis")
6259
v1, _ = rp.p_servo(a.A, b.A)
6360
_, arrived1 = rp.p_servo(a, c, threshold=0.6)
6461

65-
ans = np.array([2, 0, 0, 1.4, -0, 0])
62+
ans = np.array([1, 0, 0, 0.7, -0, 0])
6663

6764
nt.assert_array_almost_equal(v0, ans, decimal=4)
6865
nt.assert_array_almost_equal(v1, ans, decimal=4)
@@ -76,6 +73,7 @@ def test_jsingu(self):
7673

7774
rp.jsingu(J)
7875

79-
if __name__ == '__main__': # pragma nocover
76+
77+
if __name__ == "__main__": # pragma nocover
8078
unittest.main()
81-
# pytest.main(['tests/test_SerialLink.py'])
79+
# pytest.main(['tests/test_SerialLink.py'])

0 commit comments

Comments
 (0)