00001
00002
00003
00004
00005
00006
00007
00008
00009 #include "ros/ros.h"
00010 #include "ros/time.h"
00011 #include "std_msgs/Header.h"
00012 #include "std_msgs/Float32.h"
00013 #include "diagnostic_msgs/DiagnosticStatus.h"
00014 #include "diagnostic_msgs/DiagnosticArray.h"
00015 #include "diagnostic_msgs/KeyValue.h"
00016 #include "sensor_msgs/BatteryState.h"
00017 #include <ros/package.h>
00018 #include <vector>
00019 #include <cmath>
00020 #include <stdio.h>
00021 #include <stdlib.h>
00022 #include <fstream>
00023 #include <unistd.h>
00024 #include <boost/thread.hpp>
00025 #include <boost/lexical_cast.hpp>
00026
00027 float voltage;
00028 double alpha;
00029
00030 void voltageCallback(const sensor_msgs::BatteryState::ConstPtr& msg) {
00031 voltage = alpha * msg->voltage + (1.0 - alpha) * voltage;
00032 }
00033
00034 std::string getLocalHostname() {
00035 char szHostName[1048];
00036 std::string error = "error";
00037 if ( gethostname(szHostName, 1048) == 0 ) {
00038 return std::string(szHostName);
00039 }
00040 return error;
00041 }
00042
00043
00044
00045
00046 float getBatteryEstimate(double A, double K, double C, float voltage, double cutoffVoltage) {
00047 double max_life = std::log((cutoffVoltage - C) / -A) / K;
00048 int negation = 1;
00049
00050
00051 if( voltage > C) {
00052 A *= -1;
00053 negation = -1;
00054 }
00055 double cur_life = std::log((voltage - C) / -A) / K;
00056 return (max_life - negation * cur_life) / 2;
00057 }
00058
00059 int sendMail(std::string outboundList, std::string sender) {
00060 std::string lhn = getLocalHostname();
00061 if(lhn.compare("error") == 0)
00062 lhn = "a robot";
00063 std::string msg = "Greetings,\nJust so you know, " + lhn +"'s battery is low. Please seek this robot and bring it to the lab for charging.\nBest,\nBuilding Wide Intelligence\n\nNOTE: This is an autogenerated message. Please don't respond";
00064 int retval = -1;
00065 FILE *mailpipe = popen("/usr/lib/sendmail -t", "w");
00066 if (mailpipe != NULL) {
00067 std::string to = "To: " + outboundList + "\n";
00068 std::string from = "From: " + sender + "\n";
00069 std::string subject = "Subject: Segbot Alert: " + lhn + " Has a Low Battery\n";
00070 fputs(to.c_str(), mailpipe);
00071 fputs(from.c_str(), mailpipe);
00072 fputs(subject.c_str(), mailpipe);
00073 fwrite(msg.c_str(), 1, strlen(msg.c_str()), mailpipe);
00074 fwrite(".\n", 1, 2, mailpipe);
00075 pclose(mailpipe);
00076 retval = 0;
00077 } else {
00078 perror("Failed to invoke sendmail");
00079 }
00080 return retval;
00081 }
00082
00083 int main(int argc, char **argv) {
00084 ros::init(argc, argv, "battery_estimator");
00085 ros::NodeHandle n;
00086
00087 double range, threshold;
00088 bool sentMail = false;
00089 std::string volt_topic;
00090
00091
00092 n.param("weighted_average_const", alpha, .05);
00093 n.param("voltage_threshold", threshold, 10.9);
00094 n.param("range_percent", range, 0.5);
00095 n.param("voltage_topic", volt_topic, (std::string) "/battery0");
00096 voltage = threshold * 1.25;
00097
00098 ros::Publisher battery_life_pub = n.advertise<diagnostic_msgs::DiagnosticArray>("diagnostics", 10);
00099 ros::Subscriber voltage_sub = n.subscribe(volt_topic, 10, voltageCallback);
00100 ros::Rate loop_rate(1);
00101 diagnostic_msgs::DiagnosticStatus voltages;
00102 diagnostic_msgs::DiagnosticStatus status;
00103 diagnostic_msgs::DiagnosticArray diagAr;
00104 diagnostic_msgs::KeyValue status_val;
00105 diagnostic_msgs::KeyValue voltages_val;
00106 voltages.hardware_id = "005";
00107 voltages.name = "voltage";
00108 voltages_val.value = voltage;
00109 voltages_val.key = "segbot voltage: ";
00110 status.hardware_id = "005";
00111 status.name = "Remaining Battery Estimator";
00112 status_val.key = "Battery life remaining (hours)";
00113 status_val.value = "Unknown. Battery profile not set.";
00114 std::ostringstream ss;
00115 bool profileExists = false;
00116
00117
00118 std::string path = ros::package::getPath("segbot_sensors");
00119 path += "/config/battery_profile";
00120 std::ifstream infile(path.c_str());
00121 profileExists = infile.good();
00122 double A, K, C, value = 0.0;
00123 int count = 0;
00124
00125 while (infile >> value) {
00126 switch (count) {
00127 case (0) : A = value; break;
00128 case (1) : K = value; break;
00129 case (2) : C = value; break;
00130 default: ROS_ERROR("ERROR. Profile has too many arguments. Abandoning battery estimation.\n");
00131 profileExists = false;
00132 break;
00133 }
00134 count += 1;
00135 }
00136
00137 while (ros::ok()) {
00138 voltages_val.value = boost::lexical_cast<std::string>(voltage).substr(0,4);
00139 voltages.message = voltages_val.value;
00140 diagAr.header.stamp = ros::Time::now();
00141 diagAr.header.frame_id = 1;
00142
00143 if (profileExists)
00144 status_val.value = boost::lexical_cast<std::string>(getBatteryEstimate(A, K, C, voltage, threshold+range)).substr(0,4);
00145
00146 if (voltage > range + threshold) {
00147 status.message = status_val.value + " hours ";
00148 status.level = 0;
00149 voltages.level = 0;
00150 } else if (voltage > threshold && voltage < (range + threshold)) {
00151 status.message = "Battery low, return to lab.";
00152 status.level = 1;
00153 voltages.level = 1;
00154 if (!sentMail) {
00155
00156 sentMail = true;
00157 bool paramSuccess;
00158 std::string outbound;
00159 paramSuccess = n.getParam("email_alert_outbound", outbound);
00160 std::string send;
00161 paramSuccess &= n.getParam("email_alert_sender", send);
00162 if (!paramSuccess) {
00163 std::cout << "Error reading send mail parameters. Check launchfile. Emails not sent." << std::endl;
00164 } else {
00165 boost::thread mailThread(sendMail, outbound, send);
00166 }
00167 }
00168 } else if (!voltage) {
00169 voltages.message = "No voltage data";
00170 voltages.level = 2;
00171 status.message = "Cannot extrapolate battery charge. No voltage data.";
00172 status.level = 2;
00173 } else {
00174 status.message = "Battery CRITICALLY low, or voltmeter data is inaccurate (or missing). Ensure the volt sensor is connected properly and its publisher relaying data.";
00175 status.level = 2;
00176 voltages.level = 2;
00177 voltages.message = "ERROR: Ensure accurate volt readings!";
00178 }
00179
00180 status.values.clear();
00181 status.values.push_back(status_val);
00182 voltages.values.clear();
00183 voltages.values.push_back(voltages_val);
00184 diagAr.status.clear();
00185 diagAr.status.push_back(status);
00186 diagAr.status.push_back(voltages);
00187 battery_life_pub.publish(diagAr);
00188 ros::spinOnce();
00189 loop_rate.sleep();
00190 }
00191 return 0;
00192 }