00001
00002 import rospy
00003 import roslib
00004 import sys
00005 import rospy
00006 import rosbag
00007 import numpy
00008 import scipy.optimize
00009 import rospkg
00010 import socket
00011
00012 from matplotlib import pyplot
00013 from scipy.optimize import curve_fit
00014 from diagnostic_msgs.msg import DiagnosticArray, DiagnosticStatus, KeyValue
00015 from std_msgs.msg import Header
00016 from rospy.numpy_msg import numpy_msg
00017
00018 """
00019 This program is a diagnostic tool for interpreting offline voltage data from the Segbot fleet.
00020 In particular, the program reads in a .bag file, and extracts voltage data published on the /diagnostics topic.
00021
00022 Currently, this
00023 plots the voltage data as well as the fitted model.
00024 writes the model to file for time-remaining estimation (handled elsewhere)
00025
00026 Possible improvements:
00027 Have ways of merging multple runs into one model, or an existing model and an additional run
00028 Change from half hours to hours, without breaking fitting function.
00029 (note this would also requires changes to the battery diagnostics node)
00030
00031 Author: Maxwell J Svetlik
00032 """
00033
00034 sliding_window = 1
00035 half_hour_conversion = 1800
00036 eol_voltage = 10.75
00037
00038
00039 def model_func(t, A, K, C):
00040 return -A * numpy.exp(K * t) + C
00041
00042
00043 def fit_exp_nonlinear(t, y):
00044 opt_parms, parm_cov = scipy.optimize.curve_fit(model_func, t, y, maxfev=1000)
00045 A, K, C = opt_parms
00046 return A, K, C
00047
00048
00049 class battery_profiler:
00050
00051 def __init__(self):
00052 print 'STARTING BATTERY PROFILER'
00053 self.voltage_values = []
00054 self.time_values = []
00055 self.time_base = -1
00056 self.rospack = rospkg.RosPack()
00057 self.model_filename = self.rospack.get_path('segbot_sensors') + "/config/battery_profile"
00058
00059 def mySmooth(self, data, window):
00060 smoothed = numpy.zeros((len(data), 1))
00061 for d in range(len(data)):
00062 smoothed[d] = numpy.mean(data[max(0, d-window):d])
00063 return smoothed
00064
00065 """
00066 returns the time remaining based on the current voltage value, and the estimated time to reach
00067 the end of life voltage (eol_voltage). Note that this voltage may need to be changed based on the specific
00068 battery type. For instance, on a segbot_v3, this value should be around 71.5
00069 """
00070 def get_time_estimate(self, A, K, C, voltage):
00071 max_life = numpy.log((eol_voltage - C) / -A) / K
00072 if voltage > C:
00073 A = -A
00074 return (numpy.log((voltage - C) / -A) / K + max_life)
00075 cur_life = numpy.log((voltage - C) / -A) / K
00076 return max_life - cur_life
00077
00078 def writeModelToFile(self, A, K, C):
00079 print "Opening the file at {}...".format(self.model_filename)
00080 target = open(self.model_filename, 'w')
00081 target.write(str(A)+'\n')
00082 target.write(str(K)+'\n')
00083 target.write(str(C)+'\n')
00084 target.close()
00085 print "Model written to configuration file successfully."
00086
00087 def listener(self):
00088 try:
00089 bag = rosbag.Bag(sys.argv[1])
00090 except IOError:
00091 print 'Could not open bag file at ' + sys.argv[1]
00092 sys.exit(-1)
00093 topic, msg_arr, t = bag.read_messages(topics=['/diagnostics']).next()
00094 self.time_base = msg_arr.header.stamp.to_sec()
00095 print 'Base time: {}\n'.format(self.time_base)
00096
00097 for topic, msg_arr, t in bag.read_messages(topics=['/diagnostics']):
00098 for msg in msg_arr.status:
00099 if msg.name == 'voltage':
00100 self.voltage_values.append(float(msg.values[0].value))
00101 """
00102 Collect relative times, and convert into half-hours
00103 Note that this is necessary for the exp-fit function; minutes are too large, hours are too small
00104 in terms of values that the fit function can handle.
00105 """
00106 self.time_values.append(float((msg_arr.header.stamp.to_sec() -
00107 self.time_base) / half_hour_conversion))
00108 bag.close()
00109
00110 pyplot.plot(self.time_values, self.mySmooth(self.voltage_values, sliding_window), 'b', label="Voltage Data")
00111 title = 'Segbot Voltage over Time on Active Use: {}'.format(socket.gethostname())
00112 pyplot.legend(loc=4)
00113 pyplot.title(title)
00114 pyplot.xlabel('Time ( 30-min Increments)')
00115 pyplot.ylabel('Voltage')
00116 pyplot.legend(loc='upper right')
00117
00118 total_time = self.time_values[len(self.time_values) - 1] - self.time_values[0]
00119 print 'EXPERIMENT DATA'
00120 print 'Total run time: {}'.format(total_time)
00121 print 'Starting voltage: {}'.format(self.voltage_values[0])
00122 print 'Ending voltage: {}'.format(self.voltage_values[len(self.voltage_values) - 1])
00123
00124 A, K, C = fit_exp_nonlinear(numpy.asarray(self.time_values), numpy.asarray(self.voltage_values))
00125 fit_y = model_func(numpy.asarray(self.time_values), A, K, C)
00126 print 'Model: -{} * exp({}x) + {}\n'.format(A, K, C)
00127 self.writeModelToFile(A, K, C)
00128
00129 pyplot.plot(numpy.asarray(self.time_values), fit_y, "r--", label="Exponential Model")
00130 pyplot.show()
00131
00132 if __name__ == '__main__':
00133 if len(sys.argv) < 2:
00134 print 'Missing argument: path to bagfile. Exiting.'
00135 sys.exit(-1)
00136 battery_profiler = battery_profiler()
00137 rospy.init_node('battery_profiler', anonymous=True)
00138 battery_profiler.listener()