blob: fc67e4c589d06da695bfaf10adfde2f807ba08e5 [file] [log] [blame]
// Copyright 2018 The Fuchsia 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 <dirent.h>
#include <fidl/fuchsia.hardware.powersource/cpp/wire.h>
#include <lib/component/incoming/cpp/protocol.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <zircon/syscalls.h>
#include <zircon/types.h>
#include <algorithm>
#include <filesystem>
#include <iterator>
#include <fbl/algorithm.h>
#include <fbl/string_buffer.h>
#include <fbl/vector.h>
using pwrdev_t = struct {
fuchsia_hardware_powersource::wire::PowerType type;
std::string name;
uint8_t state;
zx::event events;
fidl::WireSyncClient<fuchsia_hardware_powersource::Source> fidl_channel;
};
struct arg_data {
bool debug;
bool poll_events;
};
constexpr const char* type_to_string[] = {"AC", "battery"};
zx::result<fuchsia_hardware_powersource::wire::SourceInfo> get_source_info(
const fidl::WireSyncClient<fuchsia_hardware_powersource::Source>& client) {
const fidl::WireResult result = client->GetPowerInfo();
if (!result.ok()) {
fprintf(stderr, "GetPowerInfo failed (transport: %s)\n", result.status_string());
return zx::error(result.status());
}
const fidl::WireResponse response = result.value();
if (zx_status_t status = response.status; status != ZX_OK) {
fprintf(stderr, "GetPowerInfo failed (operation: %s)\n", zx_status_get_string(status));
return zx::error(status);
}
return zx::ok(response.info);
}
constexpr const char* state_to_string[] = {"online", "discharging", "charging", "critical"};
constexpr const char* state_offline = "offline/not present";
const char* get_state_string(uint32_t state, fbl::StringBuffer<256>* buf) {
buf->Clear();
for (size_t i = 0; i < std::size(state_to_string); i++) {
if (state & (1 << i)) {
if (buf->length()) {
buf->Append(", ");
}
buf->Append(state_to_string[i]);
}
}
return (buf->length() > 0) ? buf->c_str() : state_offline;
}
zx_status_t get_battery_info(
const fidl::WireSyncClient<fuchsia_hardware_powersource::Source>& client) {
const fidl::WireResult result = client->GetBatteryInfo();
if (!result.ok()) {
fprintf(stderr, "GetBatteryInfo failed (transport: %s)\n", result.status_string());
return result.status();
}
const fidl::WireResponse response = result.value();
if (zx_status_t status = response.status; status != ZX_OK) {
fprintf(stderr, "GetBatteryInfo failed (operation: %s)\n", zx_status_get_string(status));
return status;
}
fuchsia_hardware_powersource::wire::BatteryInfo binfo = response.info;
const char* unit;
switch (binfo.unit) {
case fuchsia_hardware_powersource::wire::BatteryUnit::kMw:
unit = "mW";
break;
case fuchsia_hardware_powersource::wire::BatteryUnit::kMa:
unit = "mA";
break;
}
printf(" design capacity: %d %s\n", binfo.design_capacity, unit);
printf(" last full capacity: %d %s\n", binfo.last_full_capacity, unit);
printf(" design voltage: %d mV\n", binfo.design_voltage);
printf(" warning capacity: %d %s\n", binfo.capacity_warning, unit);
printf(" low capacity: %d %s\n", binfo.capacity_low, unit);
printf(" low/warning granularity: %d %s\n", binfo.capacity_granularity_low_warning, unit);
printf(" warning/full granularity: %d %s\n", binfo.capacity_granularity_warning_full, unit);
printf(" present rate: %d %s\n", binfo.present_rate, unit);
printf(" remaining capacity: %d %s\n", binfo.remaining_capacity, unit);
printf(" present voltage: %d mV\n", binfo.present_voltage);
printf("==========================================\n");
printf("remaining battery percentage: %d %%\n",
binfo.remaining_capacity * 100 / binfo.last_full_capacity);
if (binfo.present_rate < 0) {
printf(
" remaining battery life: %.2f h\n",
static_cast<float>(binfo.remaining_capacity) / static_cast<float>(binfo.present_rate) * -1);
}
putchar('\n');
return ZX_OK;
}
void parse_arguments(int argc, char** argv, struct arg_data* args) {
int opt;
while ((opt = getopt(argc, argv, "p")) != -1) {
switch (opt) {
case 'p':
args->poll_events = true;
break;
default:
fprintf(stderr, "Invalid arg: %c\nUsage: %s [-p]\n", opt, argv[0]);
exit(EXIT_FAILURE);
}
}
}
void handle_event(pwrdev_t& interface) {
zx::result result = get_source_info(interface.fidl_channel);
if (result.is_error()) {
exit(EXIT_FAILURE);
}
fuchsia_hardware_powersource::wire::SourceInfo info = result.value();
fbl::StringBuffer<256> old_buf;
fbl::StringBuffer<256> new_buf;
printf("%s (%s): state change %s (%#x) -> %s (%#x)\n", interface.name.c_str(),
type_to_string[fidl::ToUnderlying(interface.type)],
get_state_string(interface.state, &old_buf), interface.state,
get_state_string(info.state, &new_buf), info.state);
if (interface.type == fuchsia_hardware_powersource::wire::PowerType::kBattery &&
(info.state & fuchsia_hardware_powersource::wire::kPowerStateOnline)) {
if (get_battery_info(interface.fidl_channel) != ZX_OK) {
exit(EXIT_FAILURE);
}
}
interface.state = info.state;
}
void poll_events(fbl::Vector<pwrdev_t>& interfaces) {
zx_wait_item_t* items = new zx_wait_item_t[interfaces.size()];
for (size_t i = 0; i < interfaces.size(); i++) {
items[i].handle = interfaces[i].events.get();
items[i].waitfor = ZX_USER_SIGNAL_0;
items[i].pending = 0;
}
zx_status_t status;
printf("waiting for events...\n\n");
for (;;) {
status = zx_object_wait_many(items, interfaces.size(), ZX_TIME_INFINITE);
if (status != ZX_OK) {
printf("zx_object_wait_many() returned %d\n", status);
exit(EXIT_FAILURE);
}
for (size_t i = 0; i < interfaces.size(); i++) {
if (items[i].pending & ZX_USER_SIGNAL_0) {
handle_event(interfaces[i]);
}
}
}
}
int main(int argc, char** argv) {
struct arg_data args = {};
parse_arguments(argc, argv, &args);
fbl::StringBuffer<256> state_str;
fbl::Vector<pwrdev_t> interfaces;
for (auto const& dir_entry : std::filesystem::directory_iterator{"/dev/class/power"}) {
zx::result client_end =
component::Connect<fuchsia_hardware_powersource::Source>(dir_entry.path().c_str());
if (client_end.is_error()) {
printf("failed to connect to %s: %s; skipping\n", dir_entry.path().c_str(),
client_end.status_string());
continue;
}
fidl::WireSyncClient client{std::move(client_end.value())};
zx::result result = get_source_info(client);
if (result.is_error()) {
printf("failed to read source info from %s: %s; skipping\n", dir_entry.path().c_str(),
result.status_string());
continue;
}
fuchsia_hardware_powersource::wire::SourceInfo pinfo = result.value();
printf("[%s] type: %s, state: %s (%#x)\n", dir_entry.path().c_str(),
type_to_string[fidl::ToUnderlying(pinfo.type)],
get_state_string(pinfo.state, &state_str), pinfo.state);
if (pinfo.type == fuchsia_hardware_powersource::wire::PowerType::kBattery &&
(pinfo.state & fuchsia_hardware_powersource::wire::kPowerStateOnline)) {
if (zx_status_t status = get_battery_info(client); status != ZX_OK) {
printf("failed to read battery info from %s: %s; skipping\n", dir_entry.path().c_str(),
zx_status_get_string(status));
continue;
}
}
if (args.poll_events) {
fidl::WireResult result = client->GetStateChangeEvent();
if (!result.ok()) {
printf("failed to get event from %s: %s; skipping\n", dir_entry.path().c_str(),
result.status_string());
continue;
}
auto& response = result.value();
if (zx_status_t status = response.status; status != ZX_OK) {
printf("failed to get event from %s: %s; skipping\n", dir_entry.path().c_str(),
zx_status_get_string(status));
continue;
}
interfaces.push_back({
.type = pinfo.type,
.name = dir_entry.path().string(),
.state = pinfo.state,
.events = std::move(response.handle),
.fidl_channel = std::move(client),
});
}
}
if (args.poll_events) {
poll_events(interfaces);
}
return 0;
}