scripts/positioning_plus.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 """
3 Use this node to perform indoor positioning using the metraTec IPS+ tracking system. Prerequisites for using this node
4 is a running receiver-node that handles communication with the receiver and thus with the beacons in the vicinity.
5 Also, make sure that tf is broadcasting transformations between all the different coordinate frames used in the config
6 file and the coordinate frame of the receiver (which is specified via rosparam, see below).
7 
8 Subscribed topics:
9  - ips/receiver/raw (ros_ips/StringStamped):
10  Raw messages received by the UWB receiver
11 
12 Published topics:
13  - ips/receiver/send (std_msgs/String):
14  Message to be sent to the receiver, e.g. 'SRG <EID> \r' ranging request
15  - ips/receiver/position (geometry_msgs/PointStamped):
16  Estimated position of the receiver after UWB ranging and trilateration
17 
18 Parameters:
19  - ~config_file (string, default='PKG_DIR/config/zones.yml'):
20  Path to the configuration file of zones and beacons relative to the package directory
21  - ~frame_id (string, default='map'):
22  Coordinate frame the receiver position should be estimated in
23  - ~rate (double, default=0.1):
24  The publishing rate in messages per second
25  - ~bcn_len (int, default=2*number_of_beacons):
26  Buffer length for BCN messages
27  - ~srg_len (int, default=number_of_beacons):
28  Buffer length for SRG messages
29  - ~min_beacons (int, default=4):
30  Minimum number of beacons to be used for UWB ranging. Should be 3 (two possible points) or 4
31  - ~max_z (double, default=None):
32  Maximum z-coordinate the receiver should have after ranging. Used as bounds for trilateration.
33 """
34 
35 import os
36 import rospy
37 import rospkg
38 import tf
39 from std_msgs.msg import String
40 from geometry_msgs.msg import PointStamped
41 from ros_ips.msg import StringStamped
42 from ros_ips.positioning_plus import PositioningPlus
43 
44 
45 class IPSplus:
46  """Configure ROS node for metraTec IPS+ indoor positioning system with UWB ranging functionality."""
47  # parameters specifying wait for SRG responses and timeout to avoid infinite wait for SRG response
48  srg_sleep = 0.1 # interval in which to check for responses in seconds
49  srg_timeout = 20 # number of times to check for responses. After that, continue with next beacon
50 
51  def __init__(self):
52  """
53  Initialize instance variables with values from ROS parameter server (or default values) and zone/beacon
54  configuration from YAML file.
55  """
56  # get directory of config file
57  config_dir = rospy.get_param('~config_file') if rospy.has_param('~config_file') else 'config/zones.yml'
58  abs_dir = os.path.join(rospkg.RosPack().get_path('ros_ips'), config_dir)
59  # get minimum number of beacons to use for ranging
60  min_beacons = rospy.get_param('~min_beacons') if rospy.has_param('~min_beacons') else 4
61  # get maximum z position the receiver can have
62  max_z = rospy.get_param('~max_z') if rospy.has_param('~max_z') else None
63  # initialize positioning class
64  self.positioning = PositioningPlus(abs_dir, min_beacons=min_beacons, max_z=max_z)
65  # get number of beacons specified in zones.yml file for default buffer values
66  n_beacons = self.positioning.n_beacons
67 
68  # publisher for estimated position of UWB trilateration
69  self.position_pub = rospy.Publisher('ips/receiver/position', PointStamped, queue_size=1)
70  # publisher for serial messages to be sent to the receiver
71  self.receiver_send_pub = rospy.Publisher('ips/receiver/send', String, queue_size=n_beacons)
72  # subscribe to raw messages of receiver
73  self.receiver_sub = rospy.Subscriber('ips/receiver/raw', StringStamped, self.callback)
74 
75  # number of messages to keep
76  self.bcn_buffer_length = rospy.get_param('~bcn_len') if rospy.has_param('~bcn_len') else 2*n_beacons
77  # list of incoming messages
78  self.bcn_buffer = []
79  # timestamp from last received message
80  self.bcn_last_time = None
81 
82  # number of messages to keep
83  self.srg_buffer_length = rospy.get_param('~srg_len') if rospy.has_param('srg_len') else n_beacons
84  # list of incoming messages
85  self.srg_buffer = []
86  # timestamp from last received message
87  self.srg_last_time = None
88  # flag that is set when a SRG message is received
89  self.srg_wait = True
90 
91  # initialize tf transform listener
92  self.tf = tf.TransformListener()
93  # get map frame to do positioning in from parameter server. Defaults to "map" if not specified
94  self.frame_id = rospy.get_param('~frame_id') if rospy.has_param('~frame_id') else 'map'
95 
96  # set publishing rate
97  self.rate = rospy.Rate(rospy.get_param('~rate')) if rospy.has_param('~rate') else rospy.Rate(0.1)
98 
99  def callback(self, msg):
100  """
101  Append incoming messages to list of previous message. Differentiate between BCN messages (regular beacon pings)
102  and SRG messages (responses of UWB ranging responses)
103  :param msg: String, message of subscribed topic
104  """
105  # sort message and add to buffer depending on prefix of the message
106  if msg.data.split(' ')[0] == 'BCN':
107  buff_len = self.bcn_buffer_length
108  buff = self.bcn_buffer
109  self.bcn_last_time = msg.header.stamp
110  elif msg.data.split(' ')[0] == 'SRG':
111  buff_len = self.srg_buffer_length
112  buff = self.srg_buffer
113  self.srg_last_time = msg.header.stamp
114  self.srg_wait = False
115  else:
116  return
117  # append message to buffer
118  buff.append(msg.data)
119  # delete oldest message if buffer is full
120  if len(buff) > buff_len:
121  del(buff[0])
122 
123  def publish(self):
124  """Publish the estimated position of the receiver"""
125  while not rospy.is_shutdown():
126  # estimate current position
127  position = self.estimate_position()
128  # publish estimated position
129  if position is not None and len(position) == 3:
130  point = PointStamped()
131  point.header.stamp = rospy.Time.now()
132  point.header.frame_id = self.frame_id
133  point.point.x, point.point.y, point.point.z = position[0], position[1], position[2]
134  self.position_pub.publish(point)
135  self.rate.sleep()
136 
137  def estimate_position(self):
138  """
139  Estimate the position of the receiver using UWB ranging responses.
140  :return: [Float, Float, Float]: estimated position of the UWB receiver [x, y, z]
141  """
142  while not rospy.is_shutdown():
143  # get all beacons in range
144  beacons = self.positioning.in_range(self.bcn_buffer)
145  print('Using the following beacons for ranging: {}'.format(beacons))
146  # send ranging request to all beacons
147  iters = 0
148  for b in beacons:
149  request = 'SRG ' + b + '\r'
150  self.srg_wait = True
151  self.receiver_send_pub.publish(request)
152  # wait until SRG response arrives
153  while self.srg_wait:
154  # stop waiting after a specific amount of iterations to avoid infinite loop
155  if iters >= self.srg_timeout:
156  print('Abort ranging with {} due to timeout.'.format(b))
157  break
158  # wait for SRG response
159  rospy.sleep(self.srg_sleep)
160  # increment number of iterations
161  iters += 1
162  # reset number of iterations for next beacon
163  iters = 0
164  # get ranges for all SRG responses
165  ranges = self.positioning.parse_srg(self.srg_buffer)
166  # transform beacon positions into frame specified by self.frame_id (~frame_id)
167  transformed_ranges = []
168  for i, r in enumerate(ranges):
169  # skip points that are already in the correct frame
170  if r[0].frame_id != self.frame_id:
171  # transform points into correct frame if tf knows the respective transform
172  try:
173  # look for transform from beacon frame to receiver frame
174  (trans, rot) = self.tf.lookupTransform('/' + self.frame_id, '/' + r[0].frame_id, rospy.Time(0))
175  # transform point
176  tp = r[0].position[:]
177  tp.append(0)
178  tp = tf.transformations.quaternion_multiply(
179  tf.transformations.quaternion_multiply(rot, tp),
180  tf.transformations.quaternion_conjugate(rot)
181  )
182  tp = [tp[0]+trans[0], tp[1]+trans[1], tp[2]+trans[2]]
183  print(tp)
184  transformed_ranges.append((tp, r[1]))
185  except (tf.LookupException, tf.ConnectivityException, tf.ExtrapolationException):
186  print('Cannot transform {} into {}. Check your tf tree!'.format(r[0].frame_id, self.frame_id))
187  continue
188  else:
189  transformed_ranges.append((r[0].position, r[1]))
190  # estimate position
191  position = self.positioning.trilaterate(transformed_ranges)
192  # clear SRG message buffer
193  self.srg_buffer = []
194  # return estimated position
195  return position
196 
197 
198 if __name__ == '__main__':
199  # start node
200  rospy.init_node('positioning_plus', anonymous=False)
201  # initialize IPSReceiver class
202  ips = IPSplus()
203  try:
204  # publish receiver messages
205  ips.publish()
206  except rospy.ROSInterruptException:
207  pass


ros_ips
Author(s): Christian Arndt
autogenerated on Sat May 12 2018 02:24:12