power_state_estimator.cpp
Go to the documentation of this file.
1 /*********************************************************************
2  * Software License Agreement (BSD License)
3  *
4  * Copyright (c) 2009, Willow Garage, Inc.
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  *
11  * * Redistributions of source code must retain the above copyright
12  * notice, this list of conditions and the following disclaimer.
13  * * Redistributions in binary form must reproduce the above
14  * copyright notice, this list of conditions and the following
15  * disclaimer in the documentation and/or other materials provided
16  * with the distribution.
17  * * Neither the name of the Willow Garage nor the names of its
18  * contributors may be used to endorse or promote products derived
19  * from this software without specific prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
24  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
25  * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
27  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
29  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
31  * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32  * POSSIBILITY OF SUCH DAMAGE.
33  *********************************************************************/
34 
35 #include "power_state_estimator.h"
36 
37 #include <errno.h>
38 #include <stdlib.h>
39 #include <fstream>
40 #include <iostream>
41 #include <sys/file.h>
42 #include <sys/stat.h>
43 #include <sys/types.h>
44 
45 #include "ros/ros.h"
46 
47 using namespace std;
48 using namespace power_monitor;
49 
50 // PowerStateEstimator
51 
52 PowerStateEstimator::PowerStateEstimator() { }
53 
54 void PowerStateEstimator::recordObservation(const PowerObservation& obs)
55 {
56  obs_ = obs;
57 }
58 
59 bool PowerStateEstimator::canEstimate(const ros::Time& t) const
60 {
61  return t <= ros::Time::now() && obs_.getBatteries().size() > 0;
62 }
63 
64 // FuelGaugePowerStateEstimator
65 
66 string FuelGaugePowerStateEstimator::getMethodName() const { return "fuel gauge"; }
67 PowerStateEstimator::Type FuelGaugePowerStateEstimator::getMethodType() const { return FuelGauge; }
68 
69 PowerStateEstimate FuelGaugePowerStateEstimator::estimate(const ros::Time& t)
70 {
72  ps.time_remaining = obs_.getAcCount() > 0 ? obs_.getMaxTimeToFull(t) : obs_.getMinTimeToEmpty(t);
73  ps.relative_capacity = obs_.getMinRelativeStateOfCharge();
74 
75  return ps;
76 }
77 
78 // AdvancedPowerStateEstimator
79 
80 const std::string AdvancedPowerStateEstimator::DEFAULT_LOG_FILE = "/var/ros/power_monitor/power.log";
81 
82 AdvancedPowerStateEstimator::AdvancedPowerStateEstimator()
83 {
84  ros::NodeHandle node("~");
85 
86  log_filename_ = DEFAULT_LOG_FILE;
87  node.getParam("advanced_log_file", log_filename_);
88  ROS_INFO("Using log file: %s", log_filename_.c_str());
89 
90  readObservations(log_);
91  ROS_INFO("Read %d observations", (int) log_.size());
92 }
93 
94 string AdvancedPowerStateEstimator::getMethodName() const { return "advanced"; }
95 PowerStateEstimator::Type AdvancedPowerStateEstimator::getMethodType() const { return Advanced; }
96 
97 void AdvancedPowerStateEstimator::recordObservation(const PowerObservation& obs)
98 {
99  PowerStateEstimator::recordObservation(obs);
100 
101  // Ignore any observation with less than 16 batteries
102  if (obs.getBatteries().size() != 16)
103  return;
104 
105  LogRecord record;
106  record.sec = obs.getStamp().sec;
107  record.master_state = obs.getMasterState();
108  record.charging = obs.getAcCount();
109  record.total_power = obs.getTotalPower();
110  record.min_voltage = obs.getMinVoltage();
113  log_.push_back(record);
114 
115  saveObservation(obs);
116 }
117 
122 bool AdvancedPowerStateEstimator::hasEverDischarged() const
123 {
124  for (vector<LogRecord>::const_iterator i = log_.begin(); i != log_.end(); i++)
125  if ((*i).master_state == pr2_msgs::PowerBoardState::MASTER_SHUTDOWN)
126  return true;
127 
128  return false;
129 }
130 
131 PowerStateEstimate AdvancedPowerStateEstimator::estimate(const ros::Time& t)
132 {
134 
135  // If we have history of the batteries being completely drained, then offset our estimate by the minimum reported capacity
136  if (log_.size() > 0 && obs_.getAcCount() == 0 && hasEverDischarged())
137  {
138  // Get the minimum remaining capacity reported ever
139  unsigned int min_rsc = 100;
140  float min_rem_cap = 999999.9;
141  for (vector<LogRecord>::const_iterator i = log_.begin(); i != log_.end(); i++)
142  {
143  if ((*i).master_state == pr2_msgs::PowerBoardState::MASTER_SHUTDOWN)
144  {
145  min_rsc = min(min_rsc, (*i).min_relative_state_of_charge);
146  min_rem_cap = min(min_rem_cap, (*i).total_remaining_capacity);
147  }
148  }
149 
150  // @todo: should filter the noisy current
151  float current = obs_.getTotalPower() / obs_.getMinVoltage();
152 
153  float actual_rem_cap = obs_.getTotalRemainingCapacity() - min_rem_cap;
154  float rem_hours = actual_rem_cap / -current;
155 
156  ROS_DEBUG("current reported relative state of charge: %d", obs_.getMinRelativeStateOfCharge());
157  ROS_DEBUG("minimum reported remaining capacity: %d", (int) min_rem_cap);
158  ROS_DEBUG("minimum reported relative state of charge: %d", min_rsc);
159  ROS_DEBUG("current: %.2f", current);
160  ROS_DEBUG("report remaining capacity: %f", obs_.getTotalRemainingCapacity());
161  ROS_DEBUG("time remaining: %.2f mins", rem_hours * 60);
162 
163  ps.time_remaining = ros::Duration(rem_hours * 60 * 60);
164  ps.relative_capacity = int(100 * (obs_.getMinRelativeStateOfCharge() - min_rsc) / (100.0 - min_rsc));
165  }
166  else
167  {
168  // No history. Resort to fuel gauge method
169  ROS_DEBUG("No history (resorting to fuel gauge)");
170  ROS_DEBUG("AC count: %d", obs_.getAcCount());
171  ROS_DEBUG("current reported relative state of charge: %d", obs_.getMinRelativeStateOfCharge());
172  ROS_DEBUG("maximum reported time-to-full: %d", obs_.getMaxTimeToFull(t).sec);
173  ROS_DEBUG("minimum reported time-to-empty: %d", obs_.getMinTimeToEmpty(t).sec);
174 
175  ps.time_remaining = obs_.getAcCount() > 0 ? obs_.getMaxTimeToFull(t) : obs_.getMinTimeToEmpty(t);
176  ps.relative_capacity = obs_.getMinRelativeStateOfCharge();
177  }
178 
179  return ps;
180 }
181 
182 void AdvancedPowerStateEstimator::tokenize(const string& str, vector<string>& tokens, const string& delimiters)
183 {
184  string::size_type last_pos = str.find_first_not_of(delimiters, 0);
185  string::size_type pos = str.find_first_of(delimiters, last_pos);
186 
187  while (string::npos != pos || string::npos != last_pos)
188  {
189  tokens.push_back(str.substr(last_pos, pos - last_pos));
190 
191  last_pos = str.find_first_not_of(delimiters, pos);
192  pos = str.find_first_of(delimiters, last_pos);
193  }
194 }
195 
196 bool AdvancedPowerStateEstimator::readObservations(vector<LogRecord>& log)
197 {
198  ifstream f(log_filename_.c_str(), ios::in);
199  if (!f.is_open())
200  return false;
201 
202  // Consume header line
203  string line;
204  getline(f, line);
205  if (!f.good())
206  {
207  ROS_ERROR("Error reading header from log file: %s", log_filename_.c_str());
208  return false;
209  }
210 
211  int line_num = 0;
212  while (true)
213  {
214  line_num++;
215 
216  getline(f, line);
217  if (!f.good())
218  break;
219 
220  vector<string> tokens;
221  tokenize(line, tokens, ",");
222 
223  try
224  {
225  if (tokens.size() == 7)
226  {
227  LogRecord record;
228  record.sec = boost::lexical_cast<uint32_t>(tokens[0]);
229  record.master_state = boost::lexical_cast<int>(tokens[1]);
230  record.charging = boost::lexical_cast<int>(tokens[2]);
231  record.total_power = boost::lexical_cast<float>(tokens[3]);
232  record.min_voltage = boost::lexical_cast<float>(tokens[4]);
233  record.min_relative_state_of_charge = boost::lexical_cast<unsigned int>(tokens[5]);
234  record.total_remaining_capacity = boost::lexical_cast<float>(tokens[6]);
235  log.push_back(record);
236  continue;
237  }
238  }
239  catch (const boost::bad_lexical_cast& ex) { }
240 
241  ROS_DEBUG("Invalid line %d in log file: %s.", line_num, log_filename_.c_str());
242  }
243 
244  f.close();
245 
246  return true;
247 }
248 
249 bool AdvancedPowerStateEstimator::saveObservation(const PowerObservation& obs) const
250 {
251  // Check if the log file exists
252  bool exists = false;
253  FILE* f = fopen(log_filename_.c_str(), "r");
254  if (f) {
255  fclose(f);
256  exists = true;
257  }
258 
259  if (!exists)
260  {
261  // Check if the directory exists
262  size_t last_dir_sep = log_filename_.rfind("/");
263  if (last_dir_sep == string::npos)
264  {
265  ROS_ERROR("Invalid log file: %s", log_filename_.c_str());
266  return false;
267  }
268 
269  string log_dir = log_filename_.substr(0, last_dir_sep);
270 
271  struct stat s;
272  if (stat(log_dir.c_str(), &s) != 0)
273  {
274  // Directory doesn't exist - create
275  ROS_INFO("Creating log file directory: %s", log_dir.c_str());
276 
277  mode_t old_umask = umask(0);
278  int res = mkdir(log_dir.c_str(), S_IRWXU | S_IRWXG | S_IRWXO);
279  umask(old_umask);
280  if (res != 0) // create directory with permissions: rwxrwxrwx
281  {
282  ROS_ERROR("Error creating power monitor log file directory");
283  return false;
284  }
285  }
286  }
287 
288  // Open the log file for appending
289  mode_t old_umask = umask(0);
290  int fd = open(log_filename_.c_str(), O_WRONLY | O_APPEND | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
291  umask(old_umask);
292  if (fd == -1)
293  {
294  ROS_ERROR("Error opening log file %s: %s", log_filename_.c_str(), strerror(errno));
295  return false;
296  }
297  f = fdopen(fd, "a");
298  if (f == NULL)
299  {
300  ROS_ERROR("Error opening log file for appending: %s", log_filename_.c_str());
301  return false;
302  }
303 
304  flock(fd, LOCK_SH);
305  if (!exists)
306  fprintf(f, "secs,master_state,charging,total_power,min_voltage,min_relative_state_of_charge,total_remaining_capacity\n");
307  fprintf(f, "%d,%d,%d,%.3f,%.3f,%d,%.3f\n", obs.getStamp().sec, (int) obs.getMasterState(), obs.getAcCount(), obs.getTotalPower(), obs.getMinVoltage(), obs.getMinRelativeStateOfCharge(), obs.getTotalRemainingCapacity());
308  fclose(f);
309  close(fd);
310 
311  return true;
312 }
f
#define ROS_INFO(...)
unsigned int getAcCount() const
Definition: observation.cpp:56
int min(int a, int b)
unsigned int getMinRelativeStateOfCharge() const
Definition: observation.cpp:90
bool getParam(const std::string &key, std::string &s) const
static Time now()
ROSCPP_DECL bool exists(const std::string &service_name, bool print_failure_reason)
const ros::Time & getStamp() const
Definition: observation.cpp:52
float getTotalRemainingCapacity() const
Definition: observation.cpp:99
const std::vector< BatteryObservation > & getBatteries() const
Definition: observation.cpp:54
#define ROS_ERROR(...)
#define ROS_DEBUG(...)


power_monitor
Author(s): Tim Field, Curt Meyers
autogenerated on Wed Jun 5 2019 20:40:38