blob: d10afab5706702313f5ffc40dd5c3fafc6515b99 [file] [log] [blame]
// Copyright 2019 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 "diagnostics/cros_healthd/routines/battery_health/battery_health.h"
#include <cstdint>
#include <string>
#include <utility>
#include <base/logging.h>
#include <base/values.h>
#include <power_manager/proto_bindings/power_supply_properties.pb.h>
#include "diagnostics/cros_healthd/routines/simple_routine.h"
#include "mojo/cros_healthd_diagnostics.mojom.h"
namespace diagnostics {
namespace {
namespace mojo_ipc = ::chromeos::cros_healthd::mojom;
bool TestWearPercentage(
const power_manager::PowerSupplyProperties& power_supply_proto,
uint8_t percent_battery_wear_allowed,
mojo_ipc::DiagnosticRoutineStatusEnum* status,
std::string* status_message,
base::Value* result_dict) {
DCHECK(status);
DCHECK(status_message);
DCHECK(result_dict);
DCHECK(result_dict->is_dict());
double capacity = power_supply_proto.battery_charge_full();
double design_capacity = power_supply_proto.battery_charge_full_design();
if (percent_battery_wear_allowed > 100) {
*status_message = kBatteryHealthInvalidParametersMessage;
*status = mojo_ipc::DiagnosticRoutineStatusEnum::kError;
return false;
}
if (!power_supply_proto.has_battery_charge_full() ||
!power_supply_proto.has_battery_charge_full_design()) {
*status_message = kBatteryHealthFailedCalculatingWearPercentageMessage;
*status = mojo_ipc::DiagnosticRoutineStatusEnum::kError;
return false;
}
// Cap the wear percentage at 0. There are cases where the capacity can be
// higher than the design capacity, due to variance in batteries or vendors
// setting conservative design capacities.
uint32_t wear_percentage =
capacity > design_capacity ? 0 : 100 - capacity * 100 / design_capacity;
result_dict->SetIntKey("wearPercentage", static_cast<int>(wear_percentage));
if (wear_percentage > percent_battery_wear_allowed) {
*status_message = kBatteryHealthExcessiveWearMessage;
*status = mojo_ipc::DiagnosticRoutineStatusEnum::kFailed;
return false;
}
return true;
}
bool TestCycleCount(
const power_manager::PowerSupplyProperties& power_supply_proto,
uint32_t maximum_cycle_count,
mojo_ipc::DiagnosticRoutineStatusEnum* status,
std::string* status_message,
base::Value* result_dict) {
DCHECK(status);
DCHECK(status_message);
DCHECK(result_dict);
DCHECK(result_dict->is_dict());
google::protobuf::int64 cycle_count =
power_supply_proto.battery_cycle_count();
if (!power_supply_proto.has_battery_cycle_count()) {
*status_message = kBatteryHealthFailedReadingCycleCountMessage;
*status = mojo_ipc::DiagnosticRoutineStatusEnum::kError;
return false;
}
result_dict->SetIntKey("cycleCount", static_cast<int>(cycle_count));
if (cycle_count > maximum_cycle_count) {
*status_message = kBatteryHealthExcessiveCycleCountMessage;
*status = mojo_ipc::DiagnosticRoutineStatusEnum::kFailed;
return false;
}
return true;
}
void RunBatteryHealthRoutine(Context* const context,
uint32_t maximum_cycle_count,
uint8_t percent_battery_wear_allowed,
mojo_ipc::DiagnosticRoutineStatusEnum* status,
std::string* status_message,
base::Value* output_dict) {
DCHECK(context);
DCHECK(status);
DCHECK(status_message);
DCHECK(output_dict);
DCHECK(output_dict->is_dict());
base::Value result_dict(base::Value::Type::DICTIONARY);
base::Optional<power_manager::PowerSupplyProperties> response =
context->powerd_adapter()->GetPowerSupplyProperties();
if (!response.has_value()) {
*status_message = kPowerdPowerSupplyPropertiesFailedMessage;
*status = mojo_ipc::DiagnosticRoutineStatusEnum::kError;
LOG(ERROR) << kPowerdPowerSupplyPropertiesFailedMessage;
return;
}
auto power_supply_proto = response.value();
auto present =
power_supply_proto.battery_state() ==
power_manager::PowerSupplyProperties_BatteryState_NOT_PRESENT
? 0
: 1;
result_dict.SetIntKey("present", present);
result_dict.SetStringKey("manufacturer", power_supply_proto.battery_vendor());
result_dict.SetIntKey("currentNowA", power_supply_proto.battery_current());
result_dict.SetStringKey("status", power_supply_proto.battery_status());
result_dict.SetIntKey("voltageNowV", power_supply_proto.battery_voltage());
result_dict.SetIntKey("chargeFullAh",
power_supply_proto.battery_charge_full());
result_dict.SetIntKey("chargeFullDesignAh",
power_supply_proto.battery_charge_full_design());
result_dict.SetIntKey("chargeNowAh", power_supply_proto.battery_charge());
if (TestWearPercentage(power_supply_proto, percent_battery_wear_allowed,
status, status_message, &result_dict) &&
TestCycleCount(power_supply_proto, maximum_cycle_count, status,
status_message, &result_dict)) {
*status_message = kBatteryHealthRoutinePassedMessage;
*status = mojo_ipc::DiagnosticRoutineStatusEnum::kPassed;
}
if (result_dict.DictEmpty())
return;
output_dict->SetKey("resultDetails", std::move(result_dict));
}
} // namespace
const char kBatteryHealthInvalidParametersMessage[] =
"Invalid battery health routine parameters.";
const char kBatteryHealthFailedCalculatingWearPercentageMessage[] =
"Could not get wear percentage.";
const char kBatteryHealthExcessiveWearMessage[] = "Battery is over-worn.";
const char kBatteryHealthFailedReadingCycleCountMessage[] =
"Could not get cycle count.";
const char kBatteryHealthExcessiveCycleCountMessage[] =
"Battery cycle count is too high.";
const char kBatteryHealthRoutinePassedMessage[] = "Routine passed.";
const uint32_t kBatteryHealthDefaultMaximumCycleCount = 1000;
const uint8_t kBatteryHealthDefaultPercentBatteryWearAllowed = 50;
std::unique_ptr<DiagnosticRoutine> CreateBatteryHealthRoutine(
Context* const context,
const base::Optional<uint32_t>& maximum_cycle_count,
const base::Optional<uint8_t>& percent_battery_wear_allowed) {
return std::make_unique<SimpleRoutine>(base::BindOnce(
&RunBatteryHealthRoutine, context,
maximum_cycle_count.value_or(kBatteryHealthDefaultMaximumCycleCount),
percent_battery_wear_allowed.value_or(
kBatteryHealthDefaultPercentBatteryWearAllowed)));
}
} // namespace diagnostics