battery_diagnostics.cpp
Go to the documentation of this file.
00001 /* This node to incoming voltage level from the central battery in the segbot platform. 
00002  * The default values are that of the segbot_v2, where the marine battery data is published through the arduino.
00003  * You must change the launch file parameters to adjust to other segbot models, if needed.
00004  *
00005  * This node sends emails and requires that the local smtp client is setup properly.
00006  * Please follow the instructions on the BWI Wiki.
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  * Given the voltage and the parameters of the model, return the estimated time remaining in hours
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   //Catch the case where the starting voltage is greater than the model accounts for.
00050   //This can happen because the exponentially weighted voltage is initialized higher than the realized voltage.
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; //Model is in 30-minute units. Divide by 2 to get intervals of 1 hour.
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   //parameters
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;            //initialize with a value that won't trigger mail service
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);                //1hz
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";       //must match what the aggregator config file expects
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   //Does there exist a battery profile? If so, acquire the parameters it defines
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;                   // 0 = OK
00149       voltages.level = 0;
00150     } else if (voltage > threshold && voltage < (range + threshold)) {
00151       status.message = "Battery low, return to lab.";
00152       status.level = 1;                   //1 = WARN
00153       voltages.level = 1;
00154       if (!sentMail) {
00155         //To maintain steady diag readings, run sendmail on thread
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;                    //2 = CRITICAL
00176       voltages.level = 2;
00177       voltages.message = "ERROR: Ensure accurate volt readings!";
00178     }
00179     //Clearing messages reduces memory overhead, but limits the perception of changes in state in the diagnostics viewer.
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 }


segbot_sensors
Author(s): Piyush Khandelwal
autogenerated on Thu Jun 6 2019 21:37:13