3 from __future__
import print_function
9 from fnmatch
import fnmatch
10 from math
import pi, pow, sqrt
13 from nav_msgs.msg
import Odometry
14 from tf_conversions
import transformations
16 DEFAULT_PATH =
"~/odometer"
17 DEFAULT_FILENAME =
'odometer'
24 Odometer logs odometry. The odometry is measured in a callback function and sampled to a data storage in sample().
25 The data is written to a csv file, which is appended with a date. This is done is write(). In shutdown(), sample() and
26 write() are called to prevent data lost. This function is called on rospy shutdown. You can activate periodic
27 writing by calling active_write() in a loop, which the maximum length of your data storage as argument.
29 def __init__(self, path=DEFAULT_PATH, filename=DEFAULT_FILENAME):
32 In the constructor old data is retrieved, if possible. Otherwise it starts from zero.
34 :param path: Path to store the data. Path is expanded by hostname(lowercase). Can be relative or in home folder
36 :param filename: Filename of data file. Filenames are appended with date and extension.
39 if fnmatch(path,
"~*"):
40 path = os.path.expanduser(path)
41 path = os.path.abspath(path)
43 hostname = socket.gethostname()
44 date = time.strftime(
"%Y_%m_%d")
46 hostfolderpath = os.path.join(os.path.expanduser(path), hostname.lower())
47 self.
newfilepath = os.path.join(hostfolderpath, filename +
"_" + date + EXT)
61 if os.path.exists(hostfolderpath):
64 rospy.logdebug(
"Last data file is today's file")
66 files = [item
for item
in sorted(os.listdir(hostfolderpath), reverse=
True)
if
67 os.path.isfile(os.path.join(hostfolderpath, item))]
70 if fnmatch(file, filename+
"_*"+EXT):
71 filepath = os.path.join(hostfolderpath, file)
72 lastfilepath = filepath
73 rospy.logdebug(
"Found last file: {}".format(lastfilepath))
77 rospy.logdebug(
"Not found a correct data file in the folder: {}".format(hostfolderpath))
79 rospy.logdebug(
"No data files there yet in: {}".format(hostfolderpath))
81 os.makedirs(hostfolderpath)
82 rospy.logdebug(
"No folder for hostname: '{}' found".format(hostname))
86 rospy.logdebug(
"No previous data file found. Starting from zero")
88 rospy.logdebug(
"Reading from last data file: {}".format(lastfilepath))
89 with open(lastfilepath,
"r")
as f:
90 reader = csv.reader(f)
94 if header == [
'timestamp',
'distance',
'rotation',
'time']:
103 last_row = next(reader)
106 except StopIteration:
109 last_row = dict(zip(header, last_row))
113 self.
total_time = int(float(last_row[
'time']))
116 rospy.logdebug(
"Loaded data from file: {}".format(lastfilepath))
117 except Exception
as e:
119 rospy.signal_shutdown(
"Unable to read last data. Last data is corrupt")
121 rospy.logerr(
"No header found in file: {}".format(lastfilepath))
122 rospy.signal_shutdown(
"Shutdown, because last data file has no header")
124 rospy.loginfo(
"Logging odometry to file: {}".format(self.
newfilepath))
126 rospy.Subscriber(
"odom", Odometry, self.
callback)
127 rospy.on_shutdown(
lambda: self.
shutdown())
131 Current measurements are stored in self.data
135 new_time = rospy.Time.now().secs
140 timestamp = time.strftime(
"%Y_%m_%d_%H_%M_%S")
144 self.
data.append({
'timestamp': timestamp,
'distance': dist,
'rotation': rot,
'time': t})
148 Writing all data in self.data to the data file and closing it again. This should prevent file corruption.
154 rospy.logdebug(
"Today's file already exists")
156 rospy.logdebug(
"First time writing in today's file")
160 writer = csv.DictWriter(new_file, fieldnames=[
'timestamp',
'distance',
'rotation',
'time'])
162 rospy.logdebug(
"Printing header of csv file")
166 rospy.logdebug(
"Writing data to csv file")
167 writer.writerows(self.
data)
170 except Exception
as e:
175 Measuring the displacement based on the new position
177 :param msg: Odometry msg with position and rotation information
178 :type msg: nag_msgs.msg.Odometry
182 new_pose = msg.pose.pose
183 pos = new_pose.position
185 distance_delta = sqrt(pow(pos.x-pos_old.x, 2) + pow(pos.y-pos_old.y, 2))
186 if distance_delta < 0.5:
189 new_orientation = new_pose.orientation
190 old_orientation = self.
last_pose.orientation
191 new_rotation = transformations.euler_from_quaternion([new_orientation.x,
195 old_rotation = transformations.euler_from_quaternion([old_orientation.x,
199 rotation_delta = new_rotation[2] - old_rotation[2]
200 if rotation_delta >= pi:
201 rotation_delta -= 2*pi
202 elif rotation_delta <= -pi:
203 rotation_delta += 2*pi
206 rospy.logerr(
"Distance delta too big (%f m), ignoring this step" % distance_delta)
212 If self.data contains more than X samples, all the data is written to the file
218 if len(self.
data) >= length:
223 To prevent data loss between last sample and shutdown, this function is called to sample and write all the data.