Skip to content

Commit 248422c

Browse files
feat: add synchronizer to applevision_motion
1 parent e4c0047 commit 248422c

File tree

1 file changed

+102
-51
lines changed

1 file changed

+102
-51
lines changed

bin/applevision_motion.py

+102-51
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,84 @@
11
#!/usr/bin/env python
2-
import math
2+
33
import sys
4-
import copy
5-
from tokenize import group
4+
65
import rospy
7-
import time
86
import moveit_commander
97
import moveit_msgs.msg
10-
import geometry_msgs.msg
11-
from random import random
12-
import os
13-
import subprocess, shlex, psutil
14-
15-
from math import pi
16-
17-
import std_msgs.msg
18-
from std_msgs.msg import String
19-
from moveit_commander.conversions import pose_to_list
20-
from tf.transformations import quaternion_from_euler
21-
8+
import tf
229
from std_msgs.msg import String
2310
from sensor_msgs.msg import Range
24-
import csv
25-
2611
import numpy as np
12+
from message_filters import ApproximateTimeSynchronizer, Subscriber
2713

2814
from applevision_rospkg.msg import PointWithCovarianceStamped, RegionOfInterestWithCovarianceStamped
2915

30-
import tf
31-
32-
33-
class approachPlanner(object):
34-
def __init__(self):
35-
super(approachPlanner, self).__init__()
36-
16+
class SynchronizerMinTick(ApproximateTimeSynchronizer):
17+
"""
18+
Calls a callback if one of two conditions occur:
19+
1. A group of messages have timestamps within self.slop
20+
2. An above group has not occured within self.min_tick
21+
In other words, this class seeks to synchronize incoming messages with the constraint that
22+
we would also like the callback to be invoked at a minimum frequency. This behavior allows
23+
for graceful failure when a sensor stops publishing unexpectedly.
24+
25+
If the min_tick timer elapses and not all messages are ready, the callback will be invoked
26+
with the earliest message from the highest priority subscriber (in order passed to the constructor),
27+
all topics that do not have a message recent enough will be filled with None.
28+
"""
29+
30+
def __init__(self, fs, queue_size, slop, min_tick):
31+
super(SynchronizerMinTick, self).__init__(fs, queue_size, slop)
32+
self.min_tick = rospy.Duration.from_sec(min_tick)
33+
self.last_tick = rospy.Time()
34+
self.tick_timer = rospy.Timer(self.min_tick, self._timerCallback, reset=True, oneshot=True)
35+
36+
def signalMessage(self, *msg):
37+
if self.tick_timer:
38+
self.tick_timer.shutdown()
39+
40+
self.last_tick = rospy.Time()
41+
self.tick_timer = rospy.Timer(self.min_tick, self._timerCallback, reset=True, oneshot=True)
42+
return super(SynchronizerMinTick, self).signalMessage(*msg)
43+
44+
def _timerCallback(self, _):
45+
# signalMessage with whatever the latest data is
46+
with self.lock:
47+
found_msg = None
48+
for q in self.queues:
49+
if not q:
50+
continue
51+
highest_stamp = max(q)
52+
if rospy.Time.now() - highest_stamp <= self.min_tick:
53+
found_msg = q[highest_stamp]
54+
55+
if not found_msg:
56+
# nothing has pinged within the tick rate
57+
rospy.logwarn('SynchronizerMinTick: no packets within min tick rate')
58+
ret = [None for _ in range(len(self.queues))]
59+
else:
60+
target_stamp = found_msg.header.stamp
61+
# collect all things that have pinged within the slop
62+
ret = []
63+
for q in self.queues:
64+
if not q:
65+
ret.append(None)
66+
continue
67+
best_stamp_diff, best_stamp = min((abs(target_stamp - s), s) for s in q)
68+
if best_stamp_diff <= self.slop:
69+
ret.append(q[best_stamp])
70+
else:
71+
ret.append(None)
72+
73+
# empty queue and return
74+
self.signalMessage(*ret)
75+
for q in self.queues:
76+
q.clear()
77+
78+
79+
class ApproachPlanner():
80+
def __init__(self, acSub, adSub):
3781
# Moveit Setup
38-
3982
moveit_commander.roscpp_initialize(sys.argv)
4083

4184
self.robot = moveit_commander.RobotCommander()
@@ -50,6 +93,7 @@ def __init__(self):
5093

5194
self.tf_listener_ = tf.TransformListener()
5295

96+
5397
# CV Details
5498

5599
self.CAMERA_RES = np.array((640, 360))
@@ -62,7 +106,9 @@ def __init__(self):
62106
'pixel' : 75, # center vec
63107
'dist' : 0.2, # distance sensor
64108
'dead' : 0.4, # dead recking zone
65-
'miss' : 20 # count dropped
109+
'miss' : 20, # count dropped
110+
'slop' : 0.017,# sec timestamp off
111+
'tick' : 1 # sec no data without action
66112
}
67113

68114
self.distMissCount = 0
@@ -73,30 +119,36 @@ def __init__(self):
73119
self.A = None
74120
self.B = None
75121

76-
def toCenterCoord(self, q):
77-
return np.array((q.x + (q.w // 2), q.y + (q.h // 2)))
78-
79-
def aCCallback(self, res):
80-
if self.aCLast == None:
81-
self.aCLast = res
82-
self.tryPlan()
83-
else:
84-
if self.aDLast == None and self.distDisabled is False: self.distMissCount += 1
85-
86-
if self.distMissCount > self.tolerances['miss']: self.distDisabled = True
122+
# synchronizer
87123

88-
def aDCallback(self, res):
89-
if self.aDLast == None:
90-
self.aDLast = res
124+
# registers tryPlan as the message callback.
125+
self.sync = SynchronizerMinTick([acSub, adSub], 10, self.tolerances['slop'], self.tolerances['min_tick'])
126+
self.sync.registerCallback(self.tryPlan)
91127

92-
if self.distDisabled == True:
93-
self.distDisabled == False
94-
self.distMissCount = 0
128+
def toCenterCoord(self, q):
129+
return np.array((q.x + (q.w // 2), q.y + (q.h // 2)))
95130

96-
self.tryPlan()
97-
pass
131+
# def aCCallback(self, res):
132+
# if self.aCLast == None:
133+
# self.aCLast = res
134+
# self.tryPlan()
135+
# else:
136+
# if self.aDLast == None and self.distDisabled is False: self.distMissCount += 1
137+
# if self.distMissCount > self.tolerances['miss']: self.distDisabled = True
138+
139+
# def aDCallback(self, res):
140+
# if self.aDLast == None:
141+
# self.aDLast = res
142+
# if self.distDisabled == True:
143+
# self.distDisabled == False
144+
# self.distMissCount = 0
145+
# self.tryPlan()
146+
# pass
147+
148+
def tryPlan(self, cam_msg, dist_msg):
149+
# TODO: do something with cam_msg and dist_msg. Note that cam_msg and dist_msg can be None.
150+
# TODO: refactor now that messages aren't in self.aCLast and self.aDLast
98151

99-
def tryPlan(self):
100152
# Pre-Exit on Missing Sub
101153
if self.aCLast == None and (self.aDLast == None or self.distDisabled == True):
102154
rospy.logwarn("stuck doing nothing")
@@ -249,10 +301,9 @@ def main():
249301
rospy.init_node('applevision_motion')
250302
rospy.wait_for_service('Tf2Transform')
251303

252-
aP = approachPlanner()
253-
254-
aCpub = rospy.Subscriber('applevision/apple_camera', RegionOfInterestWithCovarianceStamped, aP.aCCallback, queue_size=1)
255-
aDPub = rospy.Subscriber('applevision/apple_dist', Range, aP.aDCallback, queue_size=10)
304+
aCsub = Subscriber('applevision/apple_camera', RegionOfInterestWithCovarianceStamped)
305+
aDsub = Subscriber('applevision/apple_dist', Range)
306+
aP = ApproachPlanner(aCsub, aDsub)
256307

257308
rospy.spin()
258309

0 commit comments

Comments
 (0)