diagnostics: Add prober for battery metrics

Implement the prober for battery metrics. Use powerd as the retrieval
source. In the future, hook this up to Chromium's DeviceStatusCollector.
Run 'cros_healthd --probe_battery_metrics' to exercise this behavior.

BUG=chromium:978128
TEST=unit tests and manual testing on DUT (running cros_healthd
--probe_battery_metrics)

Change-Id: I6dab2574a9b325927583e5a6be572003e4382d51
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform2/+/1682268
Tested-by: Kartik Hegde <khegde@chromium.org>
Tested-by: Justin TerAvest <teravest@chromium.org>
Reviewed-by: Justin TerAvest <teravest@chromium.org>
Reviewed-by: Will Bradley <wbbradley@chromium.org>
Legacy-Commit-Queue: Commit Bot <commit-bot@chromium.org>
diff --git a/diagnostics/BUILD.gn b/diagnostics/BUILD.gn
index 5e2cad1..8af5b67 100644
--- a/diagnostics/BUILD.gn
+++ b/diagnostics/BUILD.gn
@@ -72,15 +72,18 @@
     "libchrome-${libbase_ver}",
     "libmojo-${libbase_ver}",
     "libudev",
+    "system_api",
   ]
 }
 
 static_library("libcros_healthd") {
   deps = [
     ":diagnostics_mojo_bindings",
+    "//diagnostics/grpc:diagnostics_grpc_protos",
   ]
   all_dependent_configs = [ ":libcros_healthd_pkg_deps" ]
   sources = [
+    "common/battery_utils.cc",
     "common/disk_utils.cc",
     "cros_healthd/cros_healthd_mojo_service.cc",
     "cros_healthd/cros_healthd_routine_service_impl.cc",
@@ -181,6 +184,7 @@
     ":libcros_healthd",
   ]
   sources = [
+    "common/battery_utils.cc",
     "common/disk_utils.cc",
     "cros_healthd/main.cc",
   ]
@@ -289,6 +293,7 @@
       "../common-mk/testrunner:testrunner",
     ]
     sources = [
+      "common/battery_utils_test.cc",
       "cros_healthd/cros_healthd_mojo_service_test.cc",
     ]
   }
diff --git a/diagnostics/common/battery_utils.cc b/diagnostics/common/battery_utils.cc
new file mode 100644
index 0000000..c6d8822
--- /dev/null
+++ b/diagnostics/common/battery_utils.cc
@@ -0,0 +1,105 @@
+// 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/common/battery_utils.h"
+
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include <chromeos/dbus/service_constants.h>
+#include <dbus/bus.h>
+#include <dbus/message.h>
+#include <dbus/object_proxy.h>
+
+#include "power_manager/proto_bindings/power_supply_properties.pb.h"
+
+namespace diagnostics {
+
+using BatteryInfoPtr = ::chromeos::cros_healthd::mojom::BatteryInfoPtr;
+using BatteryInfo = ::chromeos::cros_healthd::mojom::BatteryInfo;
+
+// Extract the battery metrics from the PowerSupplyProperties protobuf.
+// Return true if the metrics could be successfully extracted from |response|
+// and put into |output_info|.
+bool ExtractBatteryMetrics(dbus::Response* response,
+                           BatteryInfoPtr* output_info) {
+  DCHECK(response);
+  DCHECK(output_info);
+
+  BatteryInfo info;
+  power_manager::PowerSupplyProperties power_supply_proto;
+  dbus::MessageReader reader_response(response);
+  if (!reader_response.PopArrayOfBytesAsProto(&power_supply_proto)) {
+    LOG(ERROR) << "Could not successfully read power supply protobuf";
+    return false;
+  }
+
+  if (power_supply_proto.has_battery_cycle_count()) {
+    info.cycle_count = power_supply_proto.battery_cycle_count();
+  }
+  if (power_supply_proto.has_battery_vendor()) {
+    info.vendor = power_supply_proto.battery_vendor();
+  }
+  if (power_supply_proto.has_battery_voltage()) {
+    info.voltage_now = power_supply_proto.battery_voltage();
+  }
+  if (power_supply_proto.has_battery_charge_full()) {
+    info.charge_full = power_supply_proto.battery_charge_full();
+  }
+  if (power_supply_proto.has_battery_charge_full_design()) {
+    info.charge_full_design = power_supply_proto.battery_charge_full_design();
+  }
+  if (power_supply_proto.has_battery_serial_number()) {
+    info.serial_number = power_supply_proto.battery_serial_number();
+  }
+  if (power_supply_proto.has_battery_voltage_min_design()) {
+    info.voltage_min_design = power_supply_proto.battery_voltage_min_design();
+  }
+
+  *output_info = info.Clone();
+
+  return true;
+}
+
+namespace {
+
+// Make a D-bus call to get the PowerSupplyProperties proto, which contains the
+// battery metrics.
+bool FetchBatteryMetrics(BatteryInfoPtr* output_info) {
+  dbus::Bus::Options options;
+  scoped_refptr<dbus::Bus> bus(new dbus::Bus(options));
+  if (!bus->Connect()) {
+    LOG(ERROR) << "Failed to connect to the system bus";
+    return false;
+  }
+  dbus::ObjectProxy* bus_proxy = bus->GetObjectProxy(
+      power_manager::kPowerManagerServiceName,
+      dbus::ObjectPath(power_manager::kPowerManagerServicePath));
+  constexpr base::TimeDelta kPowerManagerDBusTimeout =
+      base::TimeDelta::FromSeconds(3);
+  dbus::MethodCall method_call(power_manager::kPowerManagerInterface,
+                               power_manager::kGetPowerSupplyPropertiesMethod);
+  auto response = bus_proxy->CallMethodAndBlock(
+      &method_call, kPowerManagerDBusTimeout.InMilliseconds());
+  return ExtractBatteryMetrics(response.get(), output_info);
+}
+
+}  // namespace
+
+std::vector<BatteryInfoPtr> FetchBatteryInfo() {
+  // Since Chromebooks currently only support a single battery (main battery),
+  // the vector should have a size of one. In the future, if Chromebooks
+  // contain more batteries, they can easily be supported by the vector.
+  std::vector<BatteryInfoPtr> batteries{};
+
+  BatteryInfoPtr info;
+  if (FetchBatteryMetrics(&info)) {
+    batteries.push_back(std::move(info));
+  }
+
+  return batteries;
+}
+
+}  // namespace diagnostics
diff --git a/diagnostics/common/battery_utils.h b/diagnostics/common/battery_utils.h
new file mode 100644
index 0000000..185e19b
--- /dev/null
+++ b/diagnostics/common/battery_utils.h
@@ -0,0 +1,19 @@
+// 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.
+
+#ifndef DIAGNOSTICS_COMMON_BATTERY_UTILS_H_
+#define DIAGNOSTICS_COMMON_BATTERY_UTILS_H_
+
+#include <vector>
+
+#include "mojo/cros_healthd_probe.mojom.h"
+
+namespace diagnostics {
+
+// Retrieves the main battery metrics from powerd over D-bus.
+std::vector<chromeos::cros_healthd::mojom::BatteryInfoPtr> FetchBatteryInfo();
+
+}  // namespace diagnostics
+
+#endif  // DIAGNOSTICS_COMMON_BATTERY_UTILS_H_
diff --git a/diagnostics/common/battery_utils_test.cc b/diagnostics/common/battery_utils_test.cc
new file mode 100644
index 0000000..70eb168
--- /dev/null
+++ b/diagnostics/common/battery_utils_test.cc
@@ -0,0 +1,83 @@
+// 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/common/battery_utils.h"
+
+#include <map>
+#include <memory>
+#include <utility>
+
+#include <base/message_loop/message_loop.h>
+#include <base/time/time.h>
+#include <dbus/message.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "power_manager/proto_bindings/power_supply_properties.pb.h"
+
+namespace diagnostics {
+
+using ::chromeos::cros_healthd::mojom::BatteryInfoPtr;
+using ::testing::_;
+
+bool ExtractBatteryMetrics(dbus::Response* response,
+                           BatteryInfoPtr* output_info);
+
+namespace {
+
+const char kBatteryVendor[] = "TEST_MFR";
+const double kBatteryVoltage = 127.45;
+const int kBatteryCycleCount = 2;
+const char kBatterySerialNumber[] = "1000";
+const double kBatteryVoltageMinDesign = 114.00;
+const double kBatteryChargeFull = 4.3;
+const double kBatteryChargeFullDesign = 3.92;
+
+// Test the expected path of extracting battery metrics from a D-bus response.
+TEST(BatteryUtils, TestExtractingBatteryMetrics) {
+  // Create PowerSupplyProperties response protobuf.
+  std::unique_ptr<dbus::Response> response = dbus::Response::CreateEmpty();
+  dbus::MessageWriter writer(response.get());
+  power_manager::PowerSupplyProperties power_supply_proto;
+  power_supply_proto.set_battery_vendor(kBatteryVendor);
+  power_supply_proto.set_battery_voltage(kBatteryVoltage);
+  power_supply_proto.set_battery_cycle_count(kBatteryCycleCount);
+  power_supply_proto.set_battery_charge_full(kBatteryChargeFull);
+  power_supply_proto.set_battery_charge_full_design(kBatteryChargeFullDesign);
+  power_supply_proto.set_battery_serial_number(kBatterySerialNumber);
+  power_supply_proto.set_battery_voltage_min_design(kBatteryVoltageMinDesign);
+  writer.AppendProtoAsArrayOfBytes(power_supply_proto);
+
+  BatteryInfoPtr info;
+
+  ExtractBatteryMetrics(response.get(), &info);
+  ASSERT_EQ(kBatteryCycleCount, info->cycle_count);
+  ASSERT_EQ(kBatteryVendor, info->vendor);
+  ASSERT_EQ(kBatteryVoltage, info->voltage_now);
+  ASSERT_EQ(kBatteryChargeFull, info->charge_full);
+  ASSERT_EQ(kBatteryChargeFullDesign, info->charge_full_design);
+  ASSERT_EQ(kBatterySerialNumber, info->serial_number);
+  ASSERT_EQ(kBatteryVoltageMinDesign, info->voltage_min_design);
+}
+
+// Test that ExtractBatteryMetrics exits safely (returning false) when it
+// receves a bad D-bus response.
+TEST(BatteryUtils, TestBadDbusResponse) {
+  std::unique_ptr<dbus::Response> response = dbus::Response::CreateEmpty();
+  dbus::MessageWriter writer(response.get());
+  writer.AppendVariantOfBool(false);
+
+  dbus::MessageReader reader_response(response.get());
+  power_manager::PowerSupplyProperties power_supply_properties_proto;
+  ASSERT_FALSE(
+      reader_response.PopArrayOfBytesAsProto(&power_supply_properties_proto));
+
+  BatteryInfoPtr info;
+
+  ASSERT_FALSE(ExtractBatteryMetrics(response.get(), &info));
+}
+
+}  // namespace
+
+}  // namespace diagnostics
diff --git a/diagnostics/cros_healthd/main.cc b/diagnostics/cros_healthd/main.cc
index 186fcff..9ad170d 100644
--- a/diagnostics/cros_healthd/main.cc
+++ b/diagnostics/cros_healthd/main.cc
@@ -2,18 +2,22 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include <cstdio>
 #include <cstdlib>
 #include <iostream>
+#include <string>
 
 #include <brillo/flag_helper.h>
 #include <brillo/syslog_logging.h>
 
+#include "diagnostics/common/battery_utils.h"
 #include "diagnostics/common/disk_utils.h"
 
 int main(int argc, char** argv) {
   DEFINE_bool(probe_block_devices, false,
               "Exercise the ProbeNonRemovableBlockDeviceInfo routine");
-  DEFINE_bool(probe_batteries, false, "Exercise the ProbeBatteryInfo routine");
+  DEFINE_bool(probe_battery_metrics, false,
+              "Exercise the ProbeBatteryInfo routine");
 
   brillo::FlagHelper::Init(
       argc, argv, "cros_healthd - Device telemetry and diagnostics daemon.");
@@ -26,16 +30,27 @@
         diagnostics::disk_utils::FetchNonRemovableBlockDevicesInfo(root_dir);
     VLOG(1) << "Found " << devices.size() << " non-removable block device(s)."
             << std::endl;
-    std::cout << "path,size,type,manfid,name,serial" << std::endl;
-    for (auto& device : devices) {
-      std::cout << device->path << "," << std::dec << device->size << ","
-                << device->type << ",0x" << std::hex
-                << static_cast<int>(device->manufacturer_id) << ","
-                << device->name << ",0x" << std::hex << device->serial
-                << std::endl;
+    printf("path,size,type,manfid,name,serial\n");
+    for (const auto& device : devices) {
+      printf("%s,%ld,%s,0x%x,%s,0x%x\n", device->path.c_str(), device->size,
+             device->type.c_str(), static_cast<int>(device->manufacturer_id),
+             device->name.c_str(), device->serial);
     }
-  } else if (FLAGS_probe_batteries) {
-    NOTIMPLEMENTED();
+  } else if (FLAGS_probe_battery_metrics) {
+    auto batteries = diagnostics::FetchBatteryInfo();
+    if (batteries.size() != 1) {
+      LOG(ERROR) << "Did not properly fetch information for main battery.";
+      return EXIT_FAILURE;
+    }
+    VLOG(1) << "Found information for main battery.";
+    printf(
+        "charge_full,charge_full_design,cycle_count,serial_number,"
+        "vendor(manufacturer),voltage_now,voltage_min_design\n");
+    const auto& battery = batteries[0];
+    printf("%f,%f,%ld,%s,%s,%f,%f\n", battery->charge_full,
+           battery->charge_full_design, battery->cycle_count,
+           battery->serial_number.c_str(), battery->vendor.c_str(),
+           battery->voltage_now, battery->voltage_min_design);
   } else {
     // TODO(pmoy): implement daemon
   }
diff --git a/diagnostics/mojo/cros_healthd_probe.mojom b/diagnostics/mojo/cros_healthd_probe.mojom
index a39e53f..408138d 100644
--- a/diagnostics/mojo/cros_healthd_probe.mojom
+++ b/diagnostics/mojo/cros_healthd_probe.mojom
@@ -11,42 +11,20 @@
 module chromeos.cros_healthd.mojom;
 
 struct BatteryInfo {
-  // (1) Most fields are following the naming of exposed ACPI interface.
-  // (2) Most units are in µ because of this unexplained kernel patch:
-  // https://chromium.git.corp.google.com/chromiumos/third_party/kernel/+/d7380965752505951668e85de59c128d1d6fd21f%5E%21/#F1
-
-  // Index number of this battery, starts from 1.
-  int32 index@0;
-  // Manufacturer for length not exceeding EC_COMM_TEXT_MAX.
-  string manufacturer@1;
-  // Model name for length not exceeding EC_COMM_TEXT_MAX.
-  string model_name@2;
-  // Serial number for length not exceeding EC_COMM_TEXT_MAX.
-  string serial_number@3;
-  // Design Capacity (µAh).
-  int32 charge_full_design@4;
-  // Full Capacity (µAh, might change occasionally).
-  int32 charge_full@5;
-  // Remaining capacity (µAh)
-  int32 charge_now@6;
-  // Current Battery voltage (µV)
-  int32 voltage_now@7;
-  // Designed minimum output voltage (µV)
-  int32 voltage_min_design@8;
-  // Smart Battery Cycle Count in http://sbs-forum.org/specs/sbdat110.pdf
-  int32 cycle_count_smart@9;
-  // Smart Battery Status defined in http://sbs-forum.org/specs/sbdat110.pdf
-  int32 status_smart@10;
-  // Temperature in 0.1°K as Smart Battery Temperature defined in
-  // http://sbs-forum.org/specs/sbdat110.pdf
-  int32 temperature_smart@11;
-  // The path of this battery in system. It is useful if caller needs to
-  // correlate with other information
-  string path@12;
-  // Smart Manufacture Date is defined in
-  // http://sbs-forum.org/specs/sbdat110.pdf.
-  // The value is calculated by ((year-1980) * 512 + month * 32 + day).
-  int32 manufacture_date_smart@13;
+  // TODO(https://crbug.com/979245): Update "smart" cycle count.
+  int64 cycle_count;
+  // Current battery voltage (V)
+  double voltage_now;
+  // Manufacturer of the battery
+  string vendor;
+  // Serial number of the battery
+  string serial_number;
+  // Design capacity (Ah)
+  double charge_full_design;
+  // Full capacity (Ah)
+  double charge_full;
+  // Desired minimum output voltage (V)
+  double voltage_min_design;
 };
 
 struct NonRemovableBlockDeviceInfo {