| // 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/powerd/system/power_supply.h" |
| |
| #include <algorithm> |
| #include <cmath> |
| #include <cstdlib> |
| |
| #include <base/bind.h> |
| #include <base/files/file_enumerator.h> |
| #include <base/files/file_path.h> |
| #include <base/files/file_util.h> |
| #include <base/logging.h> |
| #include <base/message_loop/message_loop.h> |
| #include <base/strings/string_number_conversions.h> |
| #include <base/strings/string_split.h> |
| #include <base/strings/string_util.h> |
| #include <base/strings/stringprintf.h> |
| |
| #include "power_manager/common/clock.h" |
| #include "power_manager/common/power_constants.h" |
| #include "power_manager/common/prefs.h" |
| #include "power_manager/common/util.h" |
| #include "power_manager/powerd/system/udev.h" |
| #include "power_manager/proto_bindings/power_supply_properties.pb.h" |
| |
| namespace power_manager { |
| namespace system { |
| |
| namespace { |
| |
| // 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; |
| |
| // Default time interval between polls, in milliseconds. |
| const int kDefaultPollMs = 30000; |
| |
| // Default values for |battery_stabilized_after_*_delay_|, in milliseconds. |
| const int kDefaultBatteryStabilizedAfterStartupDelayMs = 5000; |
| const int kDefaultBatteryStabilizedAfterLinePowerConnectedDelayMs = 5000; |
| const int kDefaultBatteryStabilizedAfterLinePowerDisconnectedDelayMs = 5000; |
| const int kDefaultBatteryStabilizedAfterResumeDelayMs = 5000; |
| |
| // Different power supply types reported by the kernel. |
| const char kBatteryType[] = "Battery"; |
| const char kMainsType[] = "Mains"; |
| const char kUnknownType[] = "Unknown"; |
| |
| // Battery states reported by the kernel. This is not the full set of |
| // possible states; see drivers/power/power_supply_sysfs.c. |
| const char kBatteryStatusCharging[] = "Charging"; |
| const char kBatteryStatusFull[] = "Full"; |
| |
| // Line power status reported by the kernel for a bidirectional port through |
| // which the system is being charged. |
| const char kLinePowerStatusCharging[] = "Charging"; |
| |
| // Reads the contents of |filename| within |directory| into |out|, trimming |
| // trailing whitespace. Returns true on success. |
| bool ReadAndTrimString(const base::FilePath& directory, |
| const std::string& filename, |
| std::string* out) { |
| if (!base::ReadFileToString(directory.Append(filename), out)) |
| return false; |
| |
| base::TrimWhitespaceASCII(*out, base::TRIM_TRAILING, out); |
| return true; |
| } |
| |
| // Reads a 64-bit integer value from a file and returns true on success. |
| bool ReadInt64(const base::FilePath& directory, |
| const std::string& filename, |
| int64_t* out) { |
| std::string buffer; |
| if (!ReadAndTrimString(directory, filename, &buffer)) |
| return false; |
| return base::StringToInt64(buffer, out); |
| } |
| |
| // Reads an integer value and scales it to a double (see |kDoubleScaleFactor|. |
| // Returns 0.0 on failure. |
| double ReadScaledDouble(const base::FilePath& directory, |
| const std::string& filename) { |
| int64_t value = 0; |
| return ReadInt64(directory, filename, &value) ? |
| kDoubleScaleFactor * value : 0.0; |
| } |
| |
| // Returns true if |type|, a power supply type read from a "type" file in |
| // sysfs, indicates USB. |
| bool IsUsbChargerType(const std::string& type) { |
| // These are defined in drivers/power/power_supply_sysfs.c in the kernel. |
| return type == "USB" || type == "USB_DCP" || type == "USB_CDP" || |
| type == "USB_ACA"; |
| } |
| |
| // Returns true if |path|, a sysfs directory, corresponds to an external |
| // peripheral (e.g. a wireless mouse or keyboard). |
| bool IsExternalPeripheral(const base::FilePath& path) { |
| std::string scope; |
| return ReadAndTrimString(path, "scope", &scope) && scope == "Device"; |
| } |
| |
| // Returns true if |path|, a sysfs directory, corresponds to a battery. |
| bool IsBatteryPresent(const base::FilePath& path) { |
| int64_t present = 0; |
| return ReadInt64(path, "present", &present) && present != 0; |
| } |
| |
| // Returns a string describing |type|. |
| const char* ExternalPowerToString(PowerSupplyProperties::ExternalPower type) { |
| switch (type) { |
| case PowerSupplyProperties_ExternalPower_AC: |
| return "AC"; |
| case PowerSupplyProperties_ExternalPower_USB: |
| return "USB"; |
| case PowerSupplyProperties_ExternalPower_DISCONNECTED: |
| return "none"; |
| } |
| return "unknown"; |
| } |
| |
| // Less-than comparator for PowerStatus::Source structs. |
| struct SourceComparator { |
| bool operator()(const PowerStatus::Source& a, const PowerStatus::Source& b) { |
| return a.id < b.id; |
| } |
| }; |
| |
| // Maps names from PowerSupplyProperties::PowerSource::Port to the corresponding |
| // values. |
| PowerSupplyProperties::PowerSource::Port GetPortFromString( |
| const std::string& name) { |
| if (name == "LEFT") |
| return PowerSupplyProperties_PowerSource_Port_LEFT; |
| else if (name == "RIGHT") |
| return PowerSupplyProperties_PowerSource_Port_RIGHT; |
| else if (name == "BACK") |
| return PowerSupplyProperties_PowerSource_Port_BACK; |
| else if (name == "FRONT") |
| return PowerSupplyProperties_PowerSource_Port_FRONT; |
| else if (name == "LEFT_FRONT") |
| return PowerSupplyProperties_PowerSource_Port_LEFT_FRONT; |
| else if (name == "LEFT_BACK") |
| return PowerSupplyProperties_PowerSource_Port_LEFT_BACK; |
| else if (name == "RIGHT_FRONT") |
| return PowerSupplyProperties_PowerSource_Port_RIGHT_FRONT; |
| else if (name == "RIGHT_BACK") |
| return PowerSupplyProperties_PowerSource_Port_RIGHT_BACK; |
| else if (name == "BACK_LEFT") |
| return PowerSupplyProperties_PowerSource_Port_BACK_LEFT; |
| else if (name == "BACK_RIGHT") |
| return PowerSupplyProperties_PowerSource_Port_BACK_RIGHT; |
| else |
| return PowerSupplyProperties_PowerSource_Port_UNKNOWN; |
| } |
| |
| } // namespace |
| |
| void CopyPowerStatusToProtocolBuffer(const PowerStatus& status, |
| PowerSupplyProperties* proto) { |
| DCHECK(proto); |
| proto->Clear(); |
| proto->set_external_power(status.external_power); |
| proto->set_battery_state(status.battery_state); |
| proto->set_battery_percent(status.display_battery_percentage); |
| proto->set_supports_dual_role_devices(status.supports_dual_role_devices); |
| |
| // Show the user the time until powerd will shut down the system automatically |
| // rather than the time until the battery is completely empty. |
| proto->set_battery_time_to_empty_sec( |
| status.battery_time_to_shutdown.InSeconds()); |
| proto->set_battery_time_to_full_sec( |
| status.battery_time_to_full.InSeconds()); |
| proto->set_is_calculating_battery_time(status.is_calculating_battery_time); |
| |
| if (status.battery_state == PowerSupplyProperties_BatteryState_FULL || |
| status.battery_state == PowerSupplyProperties_BatteryState_CHARGING) { |
| proto->set_battery_discharge_rate(-status.battery_energy_rate); |
| } else { |
| proto->set_battery_discharge_rate(status.battery_energy_rate); |
| } |
| |
| for (auto source : status.available_external_power_sources) { |
| PowerSupplyProperties::PowerSource* proto_source = |
| proto->add_available_external_power_source(); |
| proto_source->set_id(source.id); |
| proto_source->set_port(source.port); |
| proto_source->set_manufacturer_id(source.manufacturer_id); |
| proto_source->set_model_id(source.model_id); |
| proto_source->set_max_power(source.max_power); |
| proto_source->set_active_by_default(source.active_by_default); |
| } |
| if (!status.external_power_source_id.empty()) |
| proto->set_external_power_source_id(status.external_power_source_id); |
| } |
| |
| std::string GetPowerStatusBatteryDebugString(const PowerStatus& status) { |
| if (!status.battery_is_present) |
| return std::string(); |
| |
| std::string output; |
| switch (status.external_power) { |
| case PowerSupplyProperties_ExternalPower_AC: |
| case PowerSupplyProperties_ExternalPower_USB: |
| output = base::StringPrintf("On %s (%s", |
| ExternalPowerToString(status.external_power), |
| status.line_power_type.c_str()); |
| if (status.line_power_current || status.line_power_voltage) { |
| output += base::StringPrintf(", %.3fA at %.1fV", |
| status.line_power_current, status.line_power_voltage); |
| if (status.line_power_max_current && status.line_power_max_voltage) { |
| output += base::StringPrintf(", max %.1fA at %.1fV", |
| status.line_power_max_current, status.line_power_max_voltage); |
| } |
| } |
| output += ") with battery at "; |
| break; |
| case PowerSupplyProperties_ExternalPower_DISCONNECTED: |
| output = "On battery at "; |
| break; |
| } |
| |
| int rounded_actual = lround(status.battery_percentage); |
| int rounded_display = lround(status.display_battery_percentage); |
| output += base::StringPrintf("%d%%", rounded_actual); |
| if (rounded_actual != rounded_display) |
| output += base::StringPrintf(" (displayed as %d%%)", rounded_display); |
| output += base::StringPrintf(", %.3f/%.3fAh at %.3fA", status.battery_charge, |
| status.battery_charge_full, status.battery_current); |
| |
| switch (status.battery_state) { |
| case PowerSupplyProperties_BatteryState_FULL: |
| output += ", full"; |
| break; |
| case PowerSupplyProperties_BatteryState_CHARGING: |
| if (status.battery_time_to_full >= base::TimeDelta()) { |
| output += ", " + util::TimeDeltaToString(status.battery_time_to_full) + |
| " until full"; |
| if (status.is_calculating_battery_time) |
| output += " (calculating)"; |
| } |
| break; |
| case PowerSupplyProperties_BatteryState_DISCHARGING: |
| if (status.battery_time_to_empty >= base::TimeDelta()) { |
| output += ", " + util::TimeDeltaToString(status.battery_time_to_empty) + |
| " until empty"; |
| if (status.is_calculating_battery_time) { |
| output += " (calculating)"; |
| } else if (status.battery_time_to_shutdown != |
| status.battery_time_to_empty) { |
| output += base::StringPrintf(" (%s until shutdown)", |
| util::TimeDeltaToString(status.battery_time_to_shutdown).c_str()); |
| } |
| } |
| break; |
| case PowerSupplyProperties_BatteryState_NOT_PRESENT: |
| break; |
| } |
| |
| return output; |
| } |
| |
| PowerStatus::Source::Source(const std::string& id, |
| PowerSupplyProperties::PowerSource::Port port, |
| const std::string& manufacturer_id, |
| const std::string& model_id, |
| double max_power, |
| bool active_by_default) |
| : id(id), |
| port(port), |
| manufacturer_id(manufacturer_id), |
| model_id(model_id), |
| max_power(max_power), |
| active_by_default(active_by_default) {} |
| |
| PowerStatus::Source::~Source() {} |
| |
| PowerStatus::PowerStatus() |
| : line_power_on(false), |
| line_power_voltage(0.0), |
| line_power_max_voltage(0.0), |
| line_power_current(0.0), |
| line_power_max_current(0.0), |
| battery_energy(0.0), |
| battery_energy_rate(0.0), |
| battery_voltage(0.0), |
| battery_current(0.0), |
| battery_charge(0.0), |
| battery_charge_full(0.0), |
| battery_charge_full_design(0.0), |
| observed_battery_charge_rate(0.0), |
| nominal_voltage(0.0), |
| is_calculating_battery_time(false), |
| battery_percentage(-1.0), |
| display_battery_percentage(-1.0), |
| battery_is_present(false), |
| battery_below_shutdown_threshold(false), |
| external_power(PowerSupplyProperties_ExternalPower_DISCONNECTED), |
| battery_state(PowerSupplyProperties_BatteryState_NOT_PRESENT), |
| supports_dual_role_devices(false) {} |
| |
| PowerStatus::~PowerStatus() {} |
| |
| base::TimeTicks PowerSupply::TestApi::GetCurrentTime() const { |
| return power_supply_->clock_->GetCurrentTime(); |
| } |
| |
| void PowerSupply::TestApi::SetCurrentTime(base::TimeTicks now) { |
| power_supply_->clock_->set_current_time_for_testing(now); |
| } |
| |
| void PowerSupply::TestApi::AdvanceTime(base::TimeDelta interval) { |
| power_supply_->clock_->set_current_time_for_testing( |
| GetCurrentTime() + interval); |
| } |
| |
| bool PowerSupply::TestApi::TriggerPollTimeout() { |
| if (!power_supply_->poll_timer_.IsRunning()) |
| return false; |
| |
| power_supply_->poll_timer_.Stop(); |
| power_supply_->HandlePollTimeout(); |
| return true; |
| } |
| |
| const char PowerSupply::kUdevSubsystem[] = "power_supply"; |
| const char PowerSupply::kChargeControlLimitMaxFile[] = |
| "charge_control_limit_max"; |
| const int PowerSupply::kObservedBatteryChargeRateMinMs = kDefaultPollMs; |
| const int PowerSupply::kBatteryStabilizedSlackMs = 50; |
| const double PowerSupply::kLowBatteryShutdownSafetyPercent = 5.0; |
| |
| PowerSupply::PowerSupply() |
| : prefs_(NULL), |
| udev_(NULL), |
| clock_(new Clock), |
| power_status_initialized_(false), |
| low_battery_shutdown_percent_(0.0), |
| usb_min_ac_watts_(0.0), |
| is_suspended_(false), |
| full_factor_(1.0) { |
| } |
| |
| PowerSupply::~PowerSupply() { |
| if (udev_) |
| udev_->RemoveSubsystemObserver(kUdevSubsystem, this); |
| } |
| |
| void PowerSupply::Init(const base::FilePath& power_supply_path, |
| PrefsInterface* prefs, |
| UdevInterface* udev, |
| bool log_shutdown_thresholds) { |
| udev_ = udev; |
| udev_->AddSubsystemObserver(kUdevSubsystem, this); |
| |
| prefs_ = prefs; |
| power_supply_path_ = power_supply_path; |
| |
| poll_delay_ = GetMsPref(kBatteryPollIntervalPref, kDefaultPollMs); |
| battery_stabilized_after_startup_delay_ = GetMsPref( |
| kBatteryStabilizedAfterStartupMsPref, |
| kDefaultBatteryStabilizedAfterStartupDelayMs); |
| battery_stabilized_after_line_power_connected_delay_ = GetMsPref( |
| kBatteryStabilizedAfterLinePowerConnectedMsPref, |
| kDefaultBatteryStabilizedAfterLinePowerConnectedDelayMs); |
| battery_stabilized_after_line_power_disconnected_delay_ = GetMsPref( |
| kBatteryStabilizedAfterLinePowerDisconnectedMsPref, |
| kDefaultBatteryStabilizedAfterLinePowerDisconnectedDelayMs); |
| battery_stabilized_after_resume_delay_ = GetMsPref( |
| kBatteryStabilizedAfterResumeMsPref, |
| kDefaultBatteryStabilizedAfterResumeDelayMs); |
| |
| prefs_->GetDouble(kPowerSupplyFullFactorPref, &full_factor_); |
| full_factor_ = std::min(std::max(kEpsilon, full_factor_), 1.0); |
| |
| prefs_->GetDouble(kUsbMinAcWattsPref, &usb_min_ac_watts_); |
| |
| int64_t shutdown_time_sec = 0; |
| if (prefs_->GetInt64(kLowBatteryShutdownTimePref, &shutdown_time_sec)) { |
| low_battery_shutdown_time_ = |
| base::TimeDelta::FromSeconds(shutdown_time_sec); |
| } |
| |
| // The percentage-based threshold takes precedence over the time-based |
| // threshold. |
| if (prefs_->GetDouble(kLowBatteryShutdownPercentPref, |
| &low_battery_shutdown_percent_)) { |
| low_battery_shutdown_time_ = base::TimeDelta(); |
| } |
| |
| int64_t samples = 0; |
| CHECK(prefs_->GetInt64(kMaxCurrentSamplesPref, &samples)); |
| current_samples_on_line_power_.reset(new RollingAverage(samples)); |
| current_samples_on_battery_power_.reset(new RollingAverage(samples)); |
| |
| CHECK(prefs_->GetInt64(kMaxChargeSamplesPref, &samples)); |
| charge_samples_.reset(new RollingAverage(samples)); |
| |
| // This log message is needed by the power_LoadTest autotest, but we don't |
| // want to spam the logs when this method is called by power_supply_info. |
| if (log_shutdown_thresholds) { |
| LOG(INFO) << "Using low battery time threshold of " |
| << low_battery_shutdown_time_.InSeconds() |
| << " secs and using low battery percent threshold of " |
| << low_battery_shutdown_percent_; |
| } |
| |
| std::string ports_string; |
| if (prefs_->GetString(kChargingPortsPref, &ports_string)) { |
| base::TrimWhitespaceASCII(ports_string, base::TRIM_TRAILING, &ports_string); |
| base::StringPairs pairs; |
| if (!base::SplitStringIntoKeyValuePairs(ports_string, ' ', '\n', &pairs)) |
| LOG(FATAL) << "Failed parsing " << kChargingPortsPref << " pref"; |
| for (const auto& pair : pairs) { |
| const PowerSupplyProperties::PowerSource::Port port = |
| GetPortFromString(pair.second); |
| if (port == PowerSupplyProperties_PowerSource_Port_UNKNOWN) { |
| LOG(FATAL) << "Unrecognized port \"" << pair.second << "\" for \"" |
| << pair.first << "\" in " << kChargingPortsPref << " pref"; |
| } |
| if (!port_names_.insert(std::make_pair(pair.first, port)).second) { |
| LOG(FATAL) << "Duplicate entry for \"" << pair.first << "\" in " |
| << kChargingPortsPref << " pref"; |
| } |
| } |
| } |
| |
| DeferBatterySampling(battery_stabilized_after_startup_delay_); |
| SchedulePoll(); |
| } |
| |
| void PowerSupply::AddObserver(PowerSupplyObserver* observer) { |
| DCHECK(observer); |
| observers_.AddObserver(observer); |
| } |
| |
| void PowerSupply::RemoveObserver(PowerSupplyObserver* observer) { |
| DCHECK(observer); |
| observers_.RemoveObserver(observer); |
| } |
| |
| PowerStatus PowerSupply::GetPowerStatus() const { |
| return power_status_; |
| } |
| |
| bool PowerSupply::RefreshImmediately() { |
| const bool success = UpdatePowerStatus(); |
| if (!is_suspended_) |
| SchedulePoll(); |
| if (success) { |
| notify_observers_task_.Reset( |
| base::Bind(&PowerSupply::NotifyObservers, base::Unretained(this))); |
| base::MessageLoop::current()->PostTask( |
| FROM_HERE, notify_observers_task_.callback()); |
| } |
| return success; |
| } |
| |
| void PowerSupply::SetSuspended(bool suspended) { |
| if (is_suspended_ == suspended) |
| return; |
| |
| is_suspended_ = suspended; |
| if (is_suspended_) { |
| VLOG(1) << "Stopping polling due to suspend"; |
| poll_timer_.Stop(); |
| current_poll_delay_for_testing_ = base::TimeDelta(); |
| } else { |
| DeferBatterySampling(battery_stabilized_after_resume_delay_); |
| charge_samples_->Clear(); |
| current_samples_on_line_power_->Clear(); |
| RefreshImmediately(); |
| } |
| } |
| |
| bool PowerSupply::SetPowerSource(const std::string& id) { |
| // An empty ID means we should write -1 to any power source (we'll use the |
| // active one) to ask the kernel to use the battery as the power source. |
| // Otherwise, write 0 to the requested power source to activate it. |
| const base::FilePath device_path = GetPathForId( |
| id.empty() ? power_status_.external_power_source_id : id); |
| if (device_path.empty()) |
| return false; |
| |
| const base::FilePath limit_path = |
| device_path.Append(kChargeControlLimitMaxFile); |
| const std::string value = id.empty() ? "-1" : "0"; |
| if (!util::WriteFileFully(limit_path, value.c_str(), value.size())) { |
| LOG(ERROR) << "Failed to write " << value << " to " << limit_path.value(); |
| return false; |
| } |
| return true; |
| } |
| |
| void PowerSupply::OnUdevEvent(const std::string& subsystem, |
| const std::string& sysname, |
| UdevAction action) { |
| VLOG(1) << "Heard about udev event"; |
| if (!is_suspended_) |
| RefreshImmediately(); |
| } |
| |
| std::string PowerSupply::GetIdForPath(const base::FilePath& path) const { |
| DCHECK(power_supply_path_.IsParent(path)) |
| << path.value() << " isn't a child of " << power_supply_path_.value(); |
| return path.BaseName().value(); |
| } |
| |
| base::FilePath PowerSupply::GetPathForId(const std::string& id) const { |
| // Double-check that nobody's playing games with bogus IDs. |
| if (id.empty() || id == "." || id == ".." || |
| id.find('/') != std::string::npos) { |
| LOG(WARNING) << "Got invalid ID \"" << id << "\""; |
| return base::FilePath(); |
| } |
| base::FilePath path = power_supply_path_.Append(id); |
| if (!base::DirectoryExists(path)) { |
| LOG(WARNING) << "Got invalid ID \"" << id << "\""; |
| return base::FilePath(); |
| } |
| return path; |
| } |
| |
| base::TimeDelta PowerSupply::GetMsPref(const std::string& pref_name, |
| int64_t default_duration_ms) const { |
| prefs_->GetInt64(pref_name, &default_duration_ms); |
| return base::TimeDelta::FromMilliseconds(default_duration_ms); |
| } |
| |
| void PowerSupply::DeferBatterySampling(base::TimeDelta stabilized_delay) { |
| const base::TimeTicks now = clock_->GetCurrentTime(); |
| battery_stabilized_timestamp_ = |
| std::max(battery_stabilized_timestamp_, now + stabilized_delay); |
| VLOG(1) << "Waiting " |
| << (battery_stabilized_timestamp_ - now).InMilliseconds() |
| << " ms for battery current and charge to stabilize"; |
| } |
| |
| bool PowerSupply::UpdatePowerStatus() { |
| CHECK(prefs_) << "PowerSupply::Init() wasn't called"; |
| |
| VLOG(1) << "Updating power status"; |
| PowerStatus status; |
| |
| // Track whether we found at least one (possibly offline) power source. |
| bool saw_power_source = false; |
| |
| // The battery state is dependent on the line power state, so defer reading it |
| // until all other directories have been examined. |
| base::FilePath battery_path; |
| |
| // Iterate through sysfs's power supply information. |
| base::FileEnumerator file_enum( |
| power_supply_path_, false, base::FileEnumerator::DIRECTORIES); |
| for (base::FilePath path = file_enum.Next(); !path.empty(); |
| path = file_enum.Next()) { |
| if (IsExternalPeripheral(path)) |
| continue; |
| |
| std::string type; |
| if (!base::ReadFileToString(path.Append("type"), &type)) |
| continue; |
| base::TrimWhitespaceASCII(type, base::TRIM_TRAILING, &type); |
| |
| saw_power_source = true; |
| |
| if (type == kBatteryType) { |
| if (battery_path.empty()) |
| battery_path = path; |
| else |
| LOG(WARNING) << "Multiple batteries; skipping " << path.value(); |
| } else { |
| ReadLinePowerDirectory(path, &status); |
| } |
| } |
| |
| // If no battery was found, assume that the system is actually on AC power. |
| if (!status.line_power_on && |
| (battery_path.empty() || !IsBatteryPresent(battery_path))) { |
| if (saw_power_source) { |
| // Batteryless Chromeboxes sometimes don't report any power sources. If we |
| // saw at least one source but it wasn't online, the battery status might |
| // be getting misreported, though; log a warning. |
| LOG(WARNING) << "Found neither line power nor a battery; assuming that " |
| << "line power is connected"; |
| } |
| status.line_power_on = true; |
| status.line_power_type = kMainsType; |
| status.external_power = PowerSupplyProperties_ExternalPower_AC; |
| } |
| |
| // Sort the power source list to make life easier for tests. |
| std::sort(status.available_external_power_sources.begin(), |
| status.available_external_power_sources.end(), |
| SourceComparator()); |
| |
| // Even though we haven't successfully finished initializing the status yet, |
| // save what we have so far so that if we bail out early due to a messed-up |
| // battery we'll at least start out knowing whether line power is connected. |
| if (!power_status_initialized_) |
| power_status_ = status; |
| |
| // Finally, read the battery status. |
| if (!battery_path.empty() && !ReadBatteryDirectory(battery_path, &status)) |
| return false; |
| |
| // Update running averages and use them to compute battery estimates. |
| if (status.battery_is_present) { |
| if (power_status_initialized_ && |
| status.line_power_on != power_status_.line_power_on) { |
| DeferBatterySampling(status.line_power_on ? |
| battery_stabilized_after_line_power_connected_delay_ : |
| battery_stabilized_after_line_power_disconnected_delay_); |
| charge_samples_->Clear(); |
| |
| // Chargers can deliver highly-variable currents depending on various |
| // factors (e.g. negotiated current for USB chargers, charge level, etc.). |
| // If one was just connected, throw away the previous average. |
| if (status.line_power_on) |
| current_samples_on_line_power_->Clear(); |
| } |
| |
| base::TimeTicks now = clock_->GetCurrentTime(); |
| if (now >= battery_stabilized_timestamp_) { |
| charge_samples_->AddSample(status.battery_charge, now); |
| |
| if (status.battery_current > 0.0) { |
| const double signed_current = |
| (status.battery_state == |
| PowerSupplyProperties_BatteryState_DISCHARGING) ? |
| -status.battery_current : status.battery_current; |
| if (status.line_power_on) |
| current_samples_on_line_power_->AddSample(signed_current, now); |
| else |
| current_samples_on_battery_power_->AddSample(signed_current, now); |
| } |
| } |
| |
| UpdateObservedBatteryChargeRate(&status); |
| status.is_calculating_battery_time = !UpdateBatteryTimeEstimates(&status); |
| status.battery_below_shutdown_threshold = |
| IsBatteryBelowShutdownThreshold(status); |
| } |
| |
| power_status_ = status; |
| power_status_initialized_ = true; |
| return true; |
| } |
| |
| void PowerSupply::ReadLinePowerDirectory(const base::FilePath& path, |
| PowerStatus* status) { |
| // Bidirectional/dual-role ports export a "status" field. |
| std::string line_status; |
| ReadAndTrimString(path, "status", &line_status); |
| const bool is_dual_role_port = !line_status.empty(); |
| if (is_dual_role_port) |
| status->supports_dual_role_devices = true; |
| |
| // If "online" is false, nothing is connected. |
| int64_t online_value = 0; |
| if (!ReadInt64(path, "online", &online_value) || !online_value) |
| return; |
| |
| // An "Unknown" type indicates a sink-only device that can't supply power. |
| std::string type; |
| ReadAndTrimString(path, "type", &type); |
| if (type == kUnknownType) |
| return; |
| |
| // Okay, this is a potential power source. Add it to the list. |
| const std::string id = GetIdForPath(path); |
| |
| // Non-USB PD devices are always active by default. The USB PD kernel driver |
| // uses "Mains" for source-only devices (i.e. dedicated chargers) and "USB" |
| // for dual-role devices (which aren't active by default). See |
| // http://crbug.com/459412 for additional discussion. |
| const bool is_usb_type = IsUsbChargerType(type); |
| const bool active_by_default = !is_dual_role_port || !is_usb_type; |
| |
| const auto port_it = port_names_.find(path.BaseName().value()); |
| const PowerSupplyProperties::PowerSource::Port port = |
| port_it != port_names_.end() |
| ? port_it->second |
| : PowerSupplyProperties_PowerSource_Port_UNKNOWN; |
| |
| std::string manufacturer_id, model_id; |
| ReadAndTrimString(path, "manufacturer", &manufacturer_id); |
| ReadAndTrimString(path, "model_name", &model_id); |
| |
| const double max_voltage = ReadScaledDouble(path, "voltage_max_design"); |
| const double max_current = ReadScaledDouble(path, "current_max"); |
| const double max_power = max_voltage * max_current; // watts |
| |
| status->available_external_power_sources.push_back(PowerStatus::Source( |
| id, port, manufacturer_id, model_id, max_power, active_by_default)); |
| VLOG(1) << "Added power source " << id << ": port=" << port |
| << " manufacturer=" << manufacturer_id << " model=" << model_id |
| << " max_power=" << max_power << " active_by_default=" |
| << active_by_default; |
| |
| // If this is a dual-role device, make sure that we're actually getting |
| // charged by it. |
| if (is_dual_role_port && line_status != kLinePowerStatusCharging) |
| return; |
| |
| if (!status->line_power_path.empty()) { |
| LOG(WARNING) << "Skipping additional line power source at " << path.value() |
| << " (previously saw " << status->line_power_path << ")"; |
| return; |
| } |
| |
| status->line_power_on = true; |
| status->line_power_path = path.value(); |
| status->line_power_type = type; |
| status->line_power_voltage = ReadScaledDouble(path, "voltage_now"); |
| status->line_power_max_voltage = max_voltage; |
| status->line_power_current = ReadScaledDouble(path, "current_now"); |
| status->line_power_max_current = max_current; |
| |
| // The USB PD driver reports the maximum power as being 0 watts while it's |
| // being determined; avoid reporting a low-power charger in that case. |
| const bool max_power_is_less_than_ac_min = max_power > 0.0 && |
| max_power < usb_min_ac_watts_; |
| |
| if (!is_dual_role_port && is_usb_type) { |
| // On spring, report all non-official chargers (which are reported as type |
| // USB rather than Mains) as being low-power. |
| status->external_power = PowerSupplyProperties_ExternalPower_USB; |
| } else if (is_dual_role_port && max_power_is_less_than_ac_min) { |
| // For dual-role USB PD devices, check whether the maximum supported power |
| // is below the configured threshold. |
| status->external_power = PowerSupplyProperties_ExternalPower_USB; |
| } else { |
| // Otherwise, report a high-power source. |
| status->external_power = PowerSupplyProperties_ExternalPower_AC; |
| } |
| status->external_power_source_id = id; |
| |
| VLOG(1) << "Found line power of type \"" << status->line_power_type |
| << "\" at " << path.value(); |
| } |
| |
| bool PowerSupply::ReadBatteryDirectory(const base::FilePath& path, |
| PowerStatus* status) { |
| VLOG(1) << "Reading battery status from " << path.value(); |
| status->battery_path = path.value(); |
| status->battery_is_present = IsBatteryPresent(path); |
| if (!status->battery_is_present) |
| return true; |
| |
| std::string status_value; |
| ReadAndTrimString(path, "status", &status_value); |
| |
| // POWER_SUPPLY_PROP_VENDOR does not seem to be a valid property |
| // defined in <linux/power_supply.h>. |
| ReadAndTrimString(path, |
| base::PathExists(path.Append("manufacturer")) ? "manufacturer" : "vendor", |
| &status->battery_vendor); |
| ReadAndTrimString(path, "model_name", &status->battery_model_name); |
| ReadAndTrimString(path, "serial_number", &status->battery_serial); |
| ReadAndTrimString(path, "technology", &status->battery_technology); |
| |
| double voltage = ReadScaledDouble(path, "voltage_now"); |
| status->battery_voltage = voltage; |
| |
| // Attempt to determine nominal voltage for time-remaining calculations. 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. |
| // Some batteries don't have a voltage_min/max_design attribute, so just use |
| // the current voltage in that case. |
| double nominal_voltage = voltage; |
| if (base::PathExists(path.Append("voltage_min_design"))) |
| nominal_voltage = ReadScaledDouble(path, "voltage_min_design"); |
| else if (base::PathExists(path.Append("voltage_max_design"))) |
| nominal_voltage = ReadScaledDouble(path, "voltage_max_design"); |
| |
| // Nominal voltage is not required to obtain the charge level; if it's |
| // missing, just use |battery_voltage|. Save the fact that it was zero so it |
| // can be used later to detect cases where battery info has been zeroed out |
| // during an in-progress firmware update, though. |
| const bool read_zero_nominal_voltage = nominal_voltage == 0.0; |
| if (nominal_voltage <= 0) { |
| LOG(WARNING) << "Got nominal voltage " << nominal_voltage << "; using " |
| << "instantaneous voltage " << voltage << " instead"; |
| nominal_voltage = 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. |
| // |
| // Some other batteries have more than that. If it has the charge attributes, |
| // just read those and the energy_now attribute. |
| double charge_full = 0; |
| double charge_full_design = 0; |
| double charge = 0; |
| double energy = 0; |
| |
| if (base::PathExists(path.Append("energy_now"))) |
| energy = ReadScaledDouble(path, "energy_now"); |
| |
| if (base::PathExists(path.Append("charge_full"))) { |
| charge_full = ReadScaledDouble(path, "charge_full"); |
| charge_full_design = ReadScaledDouble(path, "charge_full_design"); |
| charge = ReadScaledDouble(path, "charge_now"); |
| if (energy <= 0.0) |
| energy = charge * nominal_voltage; |
| } else if (base::PathExists(path.Append("energy_full"))) { |
| // Valid voltage is required to determine the charge so return early if it |
| // is not present. In this case, we know nothing about battery state or |
| // remaining percentage, so set proper status. |
| if (nominal_voltage <= 0) { |
| LOG(WARNING) << "Invalid nominal voltage reading for energy-to-charge" |
| << " conversion: " << nominal_voltage; |
| return false; |
| } |
| charge_full = ReadScaledDouble(path, "energy_full") / nominal_voltage; |
| charge_full_design = ReadScaledDouble(path, "energy_full_design") / |
| nominal_voltage; |
| charge = energy / nominal_voltage; |
| } else { |
| LOG(WARNING) << "No charge/energy readings for battery"; |
| return false; |
| } |
| |
| if (charge == 0.0 && read_zero_nominal_voltage) { |
| LOG(WARNING) << "Ignoring reading with zero battery charge and nominal " |
| << "voltage (firmware update in progress?)"; |
| return false; |
| } |
| if (charge_full <= 0.0) { |
| LOG(WARNING) << "Ignoring reading with battery charge of " << charge |
| << " and battery-full charge of " << charge_full; |
| return false; |
| } |
| |
| status->battery_charge_full = charge_full; |
| status->battery_charge_full_design = charge_full_design; |
| status->battery_charge = charge; |
| status->battery_energy = energy; |
| |
| // The current can be reported as negative on some systems but not on others, |
| // so it can't be used to determine whether the battery is charging or |
| // discharging. |
| double current = base::PathExists(path.Append("power_now")) ? |
| fabs(ReadScaledDouble(path, "power_now")) / voltage : |
| fabs(ReadScaledDouble(path, "current_now")); |
| status->battery_current = current; |
| status->battery_energy_rate = current * voltage; |
| |
| status->battery_percentage = util::ClampPercent(100.0 * charge / charge_full); |
| status->display_battery_percentage = util::ClampPercent( |
| 100.0 * (status->battery_percentage - low_battery_shutdown_percent_) / |
| (100.0 * full_factor_ - low_battery_shutdown_percent_)); |
| |
| const bool is_full = charge >= charge_full * full_factor_; |
| |
| if (status->line_power_on) { |
| if (is_full) { |
| status->battery_state = PowerSupplyProperties_BatteryState_FULL; |
| } else if (current > 0.0 && |
| (status_value == kBatteryStatusCharging || |
| status_value == kBatteryStatusFull)) { |
| status->battery_state = PowerSupplyProperties_BatteryState_CHARGING; |
| } else { |
| status->battery_state = PowerSupplyProperties_BatteryState_DISCHARGING; |
| } |
| } else { |
| status->battery_state = PowerSupplyProperties_BatteryState_DISCHARGING; |
| } |
| |
| return true; |
| } |
| |
| bool PowerSupply::UpdateBatteryTimeEstimates(PowerStatus* status) { |
| DCHECK(status); |
| status->battery_time_to_full = base::TimeDelta(); |
| status->battery_time_to_empty = base::TimeDelta(); |
| status->battery_time_to_shutdown = base::TimeDelta(); |
| |
| if (clock_->GetCurrentTime() < battery_stabilized_timestamp_) |
| return false; |
| |
| // Positive if the battery is charging and negative if it's discharging. |
| const double signed_current = status->line_power_on ? |
| current_samples_on_line_power_->GetAverage() : |
| current_samples_on_battery_power_->GetAverage(); |
| |
| switch (status->battery_state) { |
| case PowerSupplyProperties_BatteryState_CHARGING: |
| if (signed_current <= kEpsilon) { |
| status->battery_time_to_full = base::TimeDelta::FromSeconds(-1); |
| } else { |
| const double charge_to_full = std::max(0.0, |
| status->battery_charge_full * full_factor_ - |
| status->battery_charge); |
| status->battery_time_to_full = base::TimeDelta::FromSeconds( |
| roundl(3600 * charge_to_full / signed_current)); |
| } |
| break; |
| case PowerSupplyProperties_BatteryState_DISCHARGING: |
| if (signed_current >= -kEpsilon) { |
| status->battery_time_to_empty = base::TimeDelta::FromSeconds(-1); |
| status->battery_time_to_shutdown = base::TimeDelta::FromSeconds(-1); |
| } else { |
| status->battery_time_to_empty = base::TimeDelta::FromSeconds( |
| roundl(3600 * (status->battery_charge * status->nominal_voltage) / |
| (-signed_current * status->battery_voltage))); |
| |
| const double shutdown_charge = |
| status->battery_charge_full * low_battery_shutdown_percent_ / 100.0; |
| const double available_charge = std::max(0.0, |
| status->battery_charge - shutdown_charge); |
| status->battery_time_to_shutdown = base::TimeDelta::FromSeconds( |
| roundl(3600 * (available_charge * status->nominal_voltage) / |
| (-signed_current * status->battery_voltage))) - |
| low_battery_shutdown_time_; |
| status->battery_time_to_shutdown = |
| std::max(base::TimeDelta(), status->battery_time_to_shutdown); |
| } |
| break; |
| case PowerSupplyProperties_BatteryState_FULL: |
| break; |
| default: |
| NOTREACHED() << "Unhandled battery state " << status->battery_state; |
| } |
| |
| return true; |
| } |
| |
| void PowerSupply::UpdateObservedBatteryChargeRate(PowerStatus* status) const { |
| DCHECK(status); |
| const base::TimeDelta time_delta = charge_samples_->GetTimeDelta(); |
| status->observed_battery_charge_rate = |
| (time_delta.InMilliseconds() < kObservedBatteryChargeRateMinMs) ? 0.0 : |
| charge_samples_->GetValueDelta() / (time_delta.InSecondsF() / 3600); |
| } |
| |
| bool PowerSupply::IsBatteryBelowShutdownThreshold( |
| const PowerStatus& status) const { |
| if (low_battery_shutdown_time_ == base::TimeDelta() && |
| low_battery_shutdown_percent_ <= kEpsilon) |
| return false; |
| |
| // TODO(derat): Figure out what's causing http://crosbug.com/38912. |
| if (status.battery_percentage <= kEpsilon) { |
| LOG(WARNING) << "Ignoring probably-bogus zero battery percentage"; |
| return false; |
| } |
| |
| const bool below_threshold = |
| (status.battery_time_to_empty > base::TimeDelta() && |
| status.battery_time_to_empty <= low_battery_shutdown_time_ && |
| status.battery_percentage <= kLowBatteryShutdownSafetyPercent) || |
| status.battery_percentage <= low_battery_shutdown_percent_; |
| |
| // Most AC chargers can deliver enough current to prevent the battery from |
| // discharging while the device is in use; other chargers (e.g. USB) may not |
| // be able to, though. The observed charge rate is checked to verify whether |
| // the battery's charge is increasing or decreasing. |
| if (status.line_power_on) |
| return below_threshold && status.observed_battery_charge_rate < 0.0; |
| |
| return below_threshold; |
| } |
| |
| void PowerSupply::SchedulePoll() { |
| base::TimeDelta delay = poll_delay_; |
| base::TimeTicks now = clock_->GetCurrentTime(); |
| if (battery_stabilized_timestamp_ > now) { |
| delay = std::min(delay, |
| battery_stabilized_timestamp_ - now + |
| base::TimeDelta::FromMilliseconds(kBatteryStabilizedSlackMs)); |
| } |
| |
| VLOG(1) << "Scheduling update in " << delay.InMilliseconds() << " ms"; |
| poll_timer_.Start(FROM_HERE, delay, this, &PowerSupply::HandlePollTimeout); |
| current_poll_delay_for_testing_ = delay; |
| } |
| |
| void PowerSupply::HandlePollTimeout() { |
| current_poll_delay_for_testing_ = base::TimeDelta(); |
| const bool success = UpdatePowerStatus(); |
| SchedulePoll(); |
| if (success) |
| NotifyObservers(); |
| } |
| |
| void PowerSupply::NotifyObservers() { |
| FOR_EACH_OBSERVER(PowerSupplyObserver, observers_, OnPowerStatusUpdate()); |
| } |
| |
| } // namespace system |
| } // namespace power_manager |