| // Copyright (c) 2012 The Chromium OS Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "power_manager/power_supply.h" |
| |
| #include <cmath> |
| |
| #include "base/file_util.h" |
| #include "base/logging.h" |
| #include "base/string_util.h" |
| |
| namespace { |
| |
| // For passing string pointers when no data is available, and we don't want to |
| // pass a NULL pointer. |
| const char kUnknownString[] = "Unknown"; |
| |
| // sysfs reports only integer values. For non-integral values, it scales them |
| // up by 10^6. This factor scales them back down accordingly. |
| const double kDoubleScaleFactor = 0.000001; |
| |
| // How much the remaining time can vary, as a fraction of the baseline time. |
| const double kAcceptableVariance = 0.02; |
| |
| // Initially, allow 10 seconds before deciding on an acceptable time. |
| const base::TimeDelta kHysteresisTimeFast = base::TimeDelta::FromSeconds(10); |
| |
| // Allow three minutes before deciding on a new acceptable time. |
| const base::TimeDelta kHysteresisTime = base::TimeDelta::FromMinutes(3); |
| |
| // Converts time from hours to seconds. |
| inline double HoursToSecondsDouble(double num_hours) { |
| return num_hours * 3600.; |
| } |
| // Same as above, but rounds to nearest integer. |
| inline int64 HoursToSecondsInt(double num_hours) { |
| return lround(HoursToSecondsDouble(num_hours)); |
| } |
| |
| } // namespace |
| |
| namespace power_manager { |
| |
| PowerSupply::PowerSupply(const FilePath& power_supply_path) |
| : line_power_info_(NULL), |
| battery_info_(NULL), |
| power_supply_path_(power_supply_path), |
| acceptable_variance_(kAcceptableVariance), |
| hysteresis_time_(kHysteresisTimeFast), |
| found_acceptable_time_range_(false), |
| time_now_func(base::Time::Now), |
| is_suspended_(false) {} |
| |
| PowerSupply::~PowerSupply() { |
| // Clean up allocated objects. |
| if (line_power_info_) |
| delete line_power_info_; |
| if (battery_info_) |
| delete battery_info_; |
| } |
| |
| void PowerSupply::Init() { |
| GetPowerSupplyPaths(); |
| } |
| |
| bool PowerSupply::GetPowerStatus(PowerStatus* status, bool is_calculating) { |
| CHECK(status); |
| |
| status->is_calculating_battery_time = is_calculating; |
| |
| // Look for battery path if none has been found yet. |
| if (!battery_info_ || !line_power_info_) |
| GetPowerSupplyPaths(); |
| // The line power path should have been found during initialization, so there |
| // is no need to look for it again. However, check just to make sure the path |
| // is still valid. Better safe than sorry. |
| if ((!line_power_info_ || !file_util::PathExists(line_power_path_)) && |
| (!battery_info_ || !file_util::PathExists(battery_path_))) { |
| #ifndef IS_DESKTOP |
| // A hack for situations like VMs where there is no power supply sysfs. |
| LOG(INFO) << "No power supply sysfs path found, assuming line power on."; |
| #endif |
| status->line_power_on = true; |
| status->battery_is_present = false; |
| return true; |
| } |
| int64 value; |
| bool line_power_status_found = false; |
| if (line_power_info_ && file_util::PathExists(line_power_path_)) { |
| line_power_info_->GetInt64("online", &value); |
| // Return the line power status. |
| status->line_power_on = static_cast<bool>(value); |
| line_power_status_found = true; |
| } |
| |
| // If no battery was found, or if the previously found path doesn't exist |
| // anymore, return true. This is still an acceptable case since the battery |
| // could be physically removed. |
| if (!battery_info_ || !file_util::PathExists(battery_path_)) { |
| status->battery_is_present = false; |
| return true; |
| } |
| |
| battery_info_->GetInt64("present", &value); |
| status->battery_is_present = static_cast<bool>(value); |
| // If there is no battery present, we can skip the rest of the readings. |
| if (!status->battery_is_present) { |
| // No battery but still running means AC power must be present. |
| if (!line_power_status_found) |
| status->line_power_on = true; |
| return true; |
| } |
| |
| // Attempt to determine line power status from nominal battery status. |
| if (!line_power_status_found) { |
| std::string battery_status_string; |
| status->line_power_on = false; |
| if (battery_info_->GetString("status", &battery_status_string) && |
| (battery_status_string == "Charging" || |
| battery_status_string == "Fully charged")) { |
| status->line_power_on = true; |
| } |
| } |
| |
| double battery_voltage = battery_info_->ReadScaledDouble("voltage_now"); |
| status->battery_voltage = battery_voltage; |
| |
| // Attempt to determine nominal voltage for time remaining calculations. |
| // The battery voltage used in calculating time remaining. This may or may |
| // not be the same as the instantaneous voltage |battery_voltage|, as voltage |
| // levels vary over the time the battery is charged or discharged. |
| double nominal_voltage = -1.0; |
| if (file_util::PathExists(battery_path_.Append("voltage_min_design"))) |
| nominal_voltage = battery_info_->ReadScaledDouble("voltage_min_design"); |
| else if (file_util::PathExists(battery_path_.Append("voltage_max_design"))) |
| nominal_voltage = battery_info_->ReadScaledDouble("voltage_max_design"); |
| |
| // Nominal voltage is not required to obtain charge level. If it is missing, |
| // just log a message, set to |battery_voltage| so time remaining |
| // calculations will function, and proceed. |
| if (nominal_voltage <= 0) { |
| LOG(WARNING) << "Invalid voltage_min/max_design reading: " |
| << nominal_voltage << "V." |
| << " Time remaining calculations will not be available."; |
| nominal_voltage = battery_voltage; |
| } |
| status->nominal_voltage = nominal_voltage; |
| |
| // ACPI has two different battery types: charge_battery and energy_battery. |
| // The main difference is that charge_battery type exposes |
| // 1. current_now in A |
| // 2. charge_{now, full, full_design} in Ah |
| // while energy_battery type exposes |
| // 1. power_now W |
| // 2. energy_{now, full, full_design} in Wh |
| // Change all the energy readings to charge format. |
| // If both energy and charge reading are present (some non-ACPI drivers |
| // expose both readings), read only the charge format. |
| double battery_charge_full = 0; |
| double battery_charge_full_design = 0; |
| double battery_charge = 0; |
| |
| if (file_util::PathExists(battery_path_.Append("charge_full"))) { |
| battery_charge_full = battery_info_->ReadScaledDouble("charge_full"); |
| battery_charge_full_design = |
| battery_info_->ReadScaledDouble("charge_full_design"); |
| battery_charge = battery_info_->ReadScaledDouble("charge_now"); |
| } else if (file_util::PathExists(battery_path_.Append("energy_full"))) { |
| // Valid |battery_voltage| is required to determine the charge so return |
| // early if it is not present. |
| if (battery_voltage <= 0) { |
| LOG(WARNING) << "Invalid voltage_now reading for energy-to-charge" |
| << " conversion: " << battery_voltage; |
| return false; |
| } |
| battery_charge_full = |
| battery_info_->ReadScaledDouble("energy_full") / battery_voltage; |
| battery_charge_full_design = |
| battery_info_->ReadScaledDouble("energy_full_design") / battery_voltage; |
| battery_charge = |
| battery_info_->ReadScaledDouble("energy_now") / battery_voltage; |
| } else { |
| LOG(WARNING) << "No charge/energy readings for battery"; |
| return false; |
| } |
| status->battery_charge_full = battery_charge_full; |
| status->battery_charge = battery_charge; |
| |
| // Sometimes current could be negative. Ignore it and use |line_power_on| to |
| // determine whether it's charging or discharging. |
| double battery_current = 0; |
| if (file_util::PathExists(battery_path_.Append("power_now"))) { |
| battery_current = fabs(battery_info_->ReadScaledDouble("power_now")) / |
| battery_voltage; |
| } else { |
| battery_current = fabs(battery_info_->ReadScaledDouble("current_now")); |
| } |
| status->battery_current = battery_current; |
| |
| // Perform calculations / interpretations of the data read from sysfs. |
| status->battery_energy = battery_charge * battery_voltage; |
| status->battery_energy_rate = battery_current * battery_voltage; |
| |
| CalculateRemainingTime(status); |
| |
| if (battery_charge_full > 0 && battery_charge_full_design > 0) |
| status->battery_percentage = |
| std::min(100., 100. * battery_charge / battery_charge_full); |
| else |
| status->battery_percentage = -1; |
| |
| // Determine battery state from above readings. Disregard the "status" field |
| // in sysfs, as that can be inconsistent with the numerical readings. |
| status->battery_state = BATTERY_STATE_UNKNOWN; |
| if (status->line_power_on) { |
| if (battery_charge >= battery_charge_full) { |
| status->battery_state = BATTERY_STATE_FULLY_CHARGED; |
| } else { |
| if (battery_current <= 0) |
| LOG(WARNING) << "Line power is on and battery is not fully charged " |
| << "but battery current is " << battery_current << " A."; |
| status->battery_state = BATTERY_STATE_CHARGING; |
| } |
| } else { |
| status->battery_state = BATTERY_STATE_DISCHARGING; |
| if (battery_charge == 0) |
| status->battery_state = BATTERY_STATE_EMPTY; |
| } |
| return true; |
| } |
| |
| bool PowerSupply::GetPowerInformation(PowerInformation* info) { |
| CHECK(info); |
| GetPowerStatus(&info->power_status, false); |
| if (!info->power_status.battery_is_present) |
| return true; |
| |
| info->battery_vendor.clear(); |
| info->battery_model.clear(); |
| info->battery_serial.clear(); |
| info->battery_technology.clear(); |
| |
| // POWER_SUPPLY_PROP_VENDOR does not seem to be a valid property |
| // defined in <linux/power_supply.y>. |
| if (file_util::PathExists(battery_path_.Append("manufacturer"))) |
| battery_info_->ReadString("manufacturer", &info->battery_vendor); |
| else |
| battery_info_->ReadString("vendor", &info->battery_vendor); |
| battery_info_->ReadString("model_name", &info->battery_model); |
| battery_info_->ReadString("serial_number", &info->battery_serial); |
| battery_info_->ReadString("technology", &info->battery_technology); |
| |
| switch (info->power_status.battery_state) { |
| case BATTERY_STATE_CHARGING: |
| info->battery_state_string = "Charging"; |
| break; |
| case BATTERY_STATE_DISCHARGING: |
| info->battery_state_string = "Discharging"; |
| break; |
| case BATTERY_STATE_EMPTY: |
| info->battery_state_string = "Empty"; |
| break; |
| case BATTERY_STATE_FULLY_CHARGED: |
| info->battery_state_string = "Fully charged"; |
| break; |
| default: |
| info->battery_state_string = "Unknown"; |
| break; |
| } |
| return true; |
| } |
| |
| void PowerSupply::SetSuspendState(bool state) { |
| // Do not take any action if there is no change in suspend state. |
| if (is_suspended_ == state) |
| return; |
| is_suspended_ = state; |
| |
| // Record the suspend time. |
| if (is_suspended_) { |
| suspend_time_ = time_now_func(); |
| return; |
| } |
| |
| // If resuming, deduct the time suspended from the hysteresis state machine |
| // timestamps. |
| base::TimeDelta offset = time_now_func() - suspend_time_; |
| AdjustHysteresisTimes(offset); |
| } |
| |
| void PowerSupply::GetPowerSupplyPaths() { |
| // First check if both line power and battery paths have been found and still |
| // exist. If so, there is no need to do anything else. |
| if (battery_info_ && file_util::PathExists(battery_path_) && |
| line_power_info_ && file_util::PathExists(line_power_path_)) |
| return; |
| // Use a FileEnumerator to browse through all files/subdirectories in the |
| // power supply sysfs directory. |
| file_util::FileEnumerator file_enum(power_supply_path_, false, |
| file_util::FileEnumerator::DIRECTORIES); |
| // Read type info from all power sources, and try to identify battery and line |
| // power sources. Their paths are to be stored locally. |
| for (FilePath path = file_enum.Next(); |
| !path.empty(); |
| path = file_enum.Next()) { |
| std::string buf; |
| if (file_util::ReadFileToString(path.Append("type"), &buf)) { |
| TrimWhitespaceASCII(buf, TRIM_TRAILING, &buf); |
| // Only look for battery / line power paths if they haven't been found |
| // already. This makes the assumption that they don't change (but battery |
| // path can disappear if removed). So this code should only be run once |
| // for each power source. |
| if (buf == "Battery" && !battery_info_) { |
| DLOG(INFO) << "Battery path found: " << path.value(); |
| battery_path_ = path; |
| battery_info_ = new PowerInfoReader(path); |
| } else if (buf == "Mains" && !line_power_info_) { |
| DLOG(INFO) << "Line power path found: " << path.value(); |
| line_power_path_ = path; |
| line_power_info_ = new PowerInfoReader(path); |
| } |
| } |
| } |
| } |
| |
| double PowerSupply::GetLinearTimeToEmpty(const PowerStatus& status) { |
| return HoursToSecondsDouble(status.nominal_voltage * status.battery_charge / |
| (status.battery_current * status.battery_voltage)); |
| } |
| |
| void PowerSupply::CalculateRemainingTime(PowerStatus* status) { |
| CHECK(time_now_func); |
| base::Time time_now = time_now_func(); |
| // This function might be called due to a race condition between the suspend |
| // process and the battery polling. If that's the case, handle it gracefully |
| // by updating the hysteresis times and suspend time. |
| // |
| // Since the time between suspend and now has been taken into account in the |
| // hysteresis times, the recorded suspend time should be updated to the |
| // current time, to compensate. |
| // |
| // Example: |
| // Hysteresis time = 3 |
| // At time t=0, there is a read of the power supply. |
| // At time t=1, the system is suspended. |
| // At time t=4, the system is resumed. There is a power supply read at t=4. |
| // At time t=4.5, SetSuspendState(false) is called (latency in resume process) |
| // |
| // At t=4, the remaining time could be set to something very high, based on |
| // the low suspend current, since the time since last read is greater than the |
| // hysteresis time. |
| // |
| // The solution is to shift the last read time forward by 3, which is the time |
| // elapsed between suspend (t=1) and the next reading (t=4). Thus, the time |
| // of last read becomes t=3, and time since last read becomes 1 instead of 4. |
| // This avoids triggering the time hysteresis adjustment. |
| // |
| // At this point, the suspend time is also reset to the current time. This is |
| // so that when AdjustHysteresisTimes() is called again (e.g. during resume), |
| // the previous period of t=1 to t=4 is not used again in the adjustment. |
| // Continuing the example: |
| // At t=4.5, SetSuspendState(false) is called, and it calls |
| // AdjustHysteresisTimes(). Since suspend time has been adjusted from t=1 |
| // to t=4, the new offset is only 0.5. So time of last read gets shifted |
| // from t=3 to t=3.5. |
| // If suspend time was not reset to t=4, then we'd have an offset of 3.5 |
| // instead of 0.5, and time of last read gets set from t=3 to t=6.5, which is |
| // invalid. |
| if (is_suspended_) { |
| AdjustHysteresisTimes(time_now - suspend_time_); |
| suspend_time_ = time_now; |
| } |
| |
| // Check to make sure there isn't a division by zero. |
| if (status->battery_current > 0) { |
| double time_to_empty = 0; |
| if (status->line_power_on) { |
| status->battery_time_to_full = |
| HoursToSecondsInt((status->battery_charge_full - |
| status->battery_charge) / status->battery_current); |
| // Reset the remaining-time-calculation state machine when AC plugged in. |
| found_acceptable_time_range_ = false; |
| last_poll_time_ = base::Time(); |
| discharge_start_time_ = base::Time(); |
| last_acceptable_range_time_ = base::Time(); |
| // Make sure that when the system switches to battery power, the initial |
| // hysteresis time will be very short, so it can find an acceptable |
| // battery remaining time as quickly as possible. |
| hysteresis_time_ = kHysteresisTimeFast; |
| } else if (!found_acceptable_time_range_) { |
| // No base range found, need to give it some time to stabilize. For now, |
| // use the simple linear calculation for time. |
| if (discharge_start_time_.is_null()) |
| discharge_start_time_ = time_now; |
| time_to_empty = GetLinearTimeToEmpty(*status); |
| status->battery_time_to_empty = lround(time_to_empty); |
| // Select an acceptable remaining time once the system has been |
| // discharging for the necessary amount of time. |
| if (time_now - discharge_start_time_ >= hysteresis_time_) { |
| acceptable_time_ = time_to_empty; |
| found_acceptable_time_range_ = true; |
| last_poll_time_ = last_acceptable_range_time_ = time_now; |
| // Since an acceptable time has been found, start using the normal |
| // hysteresis time going forward. |
| hysteresis_time_ = kHysteresisTime; |
| } |
| } else { |
| double calculated_time = GetLinearTimeToEmpty(*status); |
| double allowed_time_variation = acceptable_time_ * acceptable_variance_; |
| // Reduce the acceptable time range as time goes by. |
| acceptable_time_ -= (time_now - last_poll_time_).InSecondsF(); |
| if (fabs(calculated_time - acceptable_time_) <= allowed_time_variation) { |
| last_acceptable_range_time_ = time_now; |
| time_to_empty = calculated_time; |
| } else if (time_now - last_acceptable_range_time_ >= hysteresis_time_) { |
| // If the calculated time has been outside the acceptable range for a |
| // long enough period of time, make it the basis for a new acceptable |
| // range. |
| acceptable_time_ = calculated_time; |
| time_to_empty = calculated_time; |
| found_acceptable_time_range_ = true; |
| last_acceptable_range_time_ = time_now; |
| } else if (calculated_time < acceptable_time_ - allowed_time_variation) { |
| // Clip remaining time at lower bound if it is too low. |
| time_to_empty = acceptable_time_ - allowed_time_variation; |
| } else { |
| // Clip remaining time at upper bound if it is too high. |
| time_to_empty = acceptable_time_ + allowed_time_variation; |
| } |
| last_poll_time_ = time_now; |
| } |
| status->battery_time_to_empty = lround(time_to_empty); |
| } else { |
| status->battery_time_to_empty = 0; |
| status->battery_time_to_full = 0; |
| } |
| } |
| |
| void PowerSupply::AdjustHysteresisTimes(const base::TimeDelta& offset) { |
| if (!discharge_start_time_.is_null()) |
| discharge_start_time_ += offset; |
| if (!last_acceptable_range_time_.is_null()) |
| last_acceptable_range_time_ += offset; |
| if (!last_poll_time_.is_null()) |
| last_poll_time_ += offset; |
| } |
| |
| double PowerSupply::PowerInfoReader::ReadScaledDouble(const char* name) { |
| int64 value; |
| if (GetInt64(name, &value)) |
| return kDoubleScaleFactor * value; |
| return -1.; |
| } |
| |
| bool PowerSupply::PowerInfoReader::ReadString(const char* name, |
| std::string* str) { |
| bool result = file_util::ReadFileToString(pref_path().Append(name), str); |
| TrimWhitespaceASCII(*str, TRIM_TRAILING, str); |
| return result; |
| } |
| |
| } // namespace power_manager |