refactor(battery): Abstract value readers

Refs #263, #245
This commit is contained in:
Michael Carlberg 2016-12-31 04:20:46 +01:00
parent 14958bdd23
commit ad0af86a7b
9 changed files with 190 additions and 189 deletions

View File

@ -282,7 +282,7 @@ class config {
string filename{move(var)};
if (file_util::exists(filename)) {
return convert<T>(string_util::trim(file_util::get_contents(filename), '\n'));
return convert<T>(string_util::trim(file_util::contents(filename), '\n'));
}
return fallback;

View File

@ -1,7 +1,6 @@
#pragma once
#include "common.hpp"
#include "config.hpp"
#include "modules/meta/inotify_module.hpp"
POLYBAR_NS
@ -25,6 +24,25 @@ namespace modules {
RATE,
};
template <typename ReturnType>
struct value_reader {
using return_type = ReturnType;
explicit value_reader() = default;
explicit value_reader(function<ReturnType()>&& fn) : m_fn(forward<decltype(fn)>(fn)) {}
ReturnType read() const {
return m_fn();
}
private:
const function<ReturnType()> m_fn;
};
using state_reader = mutex_wrapper<value_reader<bool /* is_charging */>>;
using capacity_reader = mutex_wrapper<value_reader<int /* percentage */>>;
using rate_reader = mutex_wrapper<value_reader<unsigned long /* seconds */>>;
public:
explicit battery_module(const bar_settings&, string);
@ -36,40 +54,51 @@ namespace modules {
bool build(builder* builder, const string& tag) const;
protected:
int current_percentage();
battery_module::state current_state();
state current_state();
int current_percentage(state state);
string current_time();
void subthread();
private:
static constexpr auto FORMAT_CHARGING = "format-charging";
static constexpr auto FORMAT_DISCHARGING = "format-discharging";
static constexpr auto FORMAT_FULL = "format-full";
static constexpr const char* FORMAT_CHARGING{"format-charging"};
static constexpr const char* FORMAT_DISCHARGING{"format-discharging"};
static constexpr const char* FORMAT_FULL{"format-full"};
static constexpr auto TAG_ANIMATION_CHARGING = "<animation-charging>";
static constexpr auto TAG_BAR_CAPACITY = "<bar-capacity>";
static constexpr auto TAG_RAMP_CAPACITY = "<ramp-capacity>";
static constexpr auto TAG_LABEL_CHARGING = "<label-charging>";
static constexpr auto TAG_LABEL_DISCHARGING = "<label-discharging>";
static constexpr auto TAG_LABEL_FULL = "<label-full>";
static constexpr const char* TAG_ANIMATION_CHARGING{"<animation-charging>"};
static constexpr const char* TAG_BAR_CAPACITY{"<bar-capacity>"};
static constexpr const char* TAG_RAMP_CAPACITY{"<ramp-capacity>"};
static constexpr const char* TAG_LABEL_CHARGING{"<label-charging>"};
static constexpr const char* TAG_LABEL_DISCHARGING{"<label-discharging>"};
static constexpr const char* TAG_LABEL_FULL{"<label-full>"};
static const int SKIP_N_UNCHANGED{3};
static const size_t SKIP_N_UNCHANGED{3_z};
unique_ptr<state_reader> m_state_reader;
unique_ptr<capacity_reader> m_capacity_reader;
unique_ptr<rate_reader> m_rate_reader;
animation_t m_animation_charging;
ramp_t m_ramp_capacity;
progressbar_t m_bar_capacity;
label_t m_label_charging;
label_t m_label_discharging;
label_t m_label_full;
animation_t m_animation_charging;
progressbar_t m_bar_capacity;
ramp_t m_ramp_capacity;
string m_fstate;
string m_fcapnow;
string m_fcapfull;
string m_frate;
string m_fvoltage;
state m_state{state::DISCHARGING};
int m_percentage{0};
battery_module::state m_state{battery_module::state::DISCHARGING};
map<value, string> m_valuepath;
std::atomic<int> m_percentage{0};
int m_fullat{100};
string m_timeformat;
size_t m_unchanged{SKIP_N_UNCHANGED};
chrono::duration<double> m_interval{};
chrono::system_clock::time_point m_lastpoll;
string m_timeformat;
int m_unchanged{SKIP_N_UNCHANGED};
thread m_subthread;
};
}

View File

@ -23,6 +23,7 @@ namespace modules {
CAST_MOD(Impl)->broadcast();
while (this->running()) {
std::lock_guard<std::mutex> guard(this->m_updatelock);
CAST_MOD(Impl)->poll_events();
}
} catch (const module_error& err) {
@ -57,8 +58,6 @@ namespace modules {
}
while (this->running()) {
std::unique_lock<std::mutex> guard(this->m_updatelock);
for (auto&& w : watches) {
this->m_log.trace_x("%s: Poll inotify watch %s", this->name(), w->path());
@ -66,11 +65,7 @@ namespace modules {
auto event = w->get_event();
for (auto&& w : watches) {
try {
w->remove();
} catch (const system_error&) {
// ignore
}
w->remove(true);
}
if (CAST_MOD(Impl)->on_event(event.get())) {
@ -83,8 +78,6 @@ namespace modules {
if (!this->running())
break;
}
guard.unlock();
CAST_MOD(Impl)->idle();
}
}

View File

@ -93,7 +93,8 @@ class fd_stream : public StreamType {
namespace file_util {
bool exists(const string& filename);
string get_contents(const string& filename);
string pick(const vector<string>& filenames);
string contents(const string& filename);
bool is_fifo(string filename);
template <typename... Args>

View File

@ -182,8 +182,7 @@ namespace net {
* Test if the network interface is in a valid state
*/
bool network::test_interface() const {
auto operstate = file_util::get_contents("/sys/class/net/" + m_interface + "/operstate");
return operstate.compare(0, 2, "up") == 0;
return file_util::contents("/sys/class/net/" + m_interface + "/operstate").compare(0, 2, "up") == 0;
}
/**

View File

@ -20,7 +20,7 @@ namespace modules {
}
float backlight_module::brightness_handle::read() const {
return std::strtof(file_util::get_contents(m_path).c_str(), nullptr);
return std::strtof(file_util::contents(m_path).c_str(), nullptr);
}
backlight_module::backlight_module(const bar_settings& bar, string name_)

View File

@ -1,10 +1,8 @@
#include "modules/battery.hpp"
#include "drawtypes/animation.hpp"
#include "drawtypes/label.hpp"
#include "drawtypes/progressbar.hpp"
#include "drawtypes/ramp.hpp"
#include "utils/file.hpp"
#include "utils/math.hpp"
@ -15,60 +13,71 @@ POLYBAR_NS
namespace modules {
template class module<battery_module>;
template <typename ValueReader>
typename ValueReader::return_type read(ValueReader& reader) {
std::lock_guard<ValueReader> guard(reader);
return reader.read();
}
/**
* Bootstrap module by setting up required components
*/
battery_module::battery_module(const bar_settings& bar, string name_)
: inotify_module<battery_module>(bar, move(name_)) {
auto battery = m_conf.get(name(), "battery", "BAT0"s);
auto adapter = m_conf.get(name(), "adapter", "ADP1"s);
auto path_adapter = string_util::replace(PATH_ADAPTER, "%adapter%", adapter) + "/";
auto path_battery = string_util::replace(PATH_BATTERY, "%battery%", battery) + "/";
if (!file_util::exists(path_adapter + "online")) {
throw module_error("The file '" + path_adapter + "online' does not exist");
}
m_valuepath[battery_module::value::ADAPTER] = path_adapter + "online";
if (!file_util::exists(path_battery + "voltage_now")) {
throw module_error("The file '" + path_battery + "voltage_now' does not exist");
}
m_valuepath[battery_module::value::VOLTAGE] = path_battery + "voltage_now";
for (auto&& file : vector<string>{"charge", "energy"}) {
if (file_util::exists(path_battery + file + "_now")) {
m_valuepath[battery_module::value::CAPACITY] = path_battery + file + "_now";
}
if (file_util::exists(path_battery + file + "_full")) {
m_valuepath[battery_module::value::CAPACITY_MAX] = path_battery + file + "_full";
}
}
if (m_valuepath[battery_module::value::CAPACITY].empty()) {
throw module_error("The file '" + path_battery + "[charge|energy]_now' does not exist");
}
if (m_valuepath[battery_module::value::CAPACITY_MAX].empty()) {
throw module_error("The file '" + path_battery + "[charge|energy]_full' does not exist");
}
for (auto&& file : vector<string>{"current", "power"}) {
if (file_util::exists(path_battery + file + "_now")) {
m_valuepath[battery_module::value::RATE] = path_battery + file + "_now";
}
}
if (m_valuepath[battery_module::value::RATE].empty()) {
throw module_error("The file '" + path_battery + "[current|power]_now' does not exist");
}
m_fullat = m_conf.get(name(), "full-at", 100);
m_interval = m_conf.get(name(), "poll-interval", 5s);
// Load configuration values
m_fullat = m_conf.get(name(), "full-at", m_fullat);
m_interval = m_conf.get<decltype(m_interval)>(name(), "poll-interval", 5s);
m_lastpoll = chrono::system_clock::now();
auto path_adapter = string_util::replace(PATH_ADAPTER, "%adapter%", m_conf.get(name(), "adapter", "ADP1"s)) + "/";
auto path_battery = string_util::replace(PATH_BATTERY, "%battery%", m_conf.get(name(), "battery", "BAT0"s)) + "/";
// Make state reader
if (file_util::exists((m_fstate = path_adapter + "online"))) {
m_state_reader = make_unique<state_reader>([=] { return file_util::contents(m_fstate).erase(1) == "1"; });
} else if (file_util::exists((m_fstate = path_battery + "status"))) {
m_state_reader = make_unique<state_reader>([=] { return file_util::contents(m_fstate).erase(8) == "Charging"; });
} else {
throw module_error("No suitable way to get current charge state");
}
// Make capacity reader
if ((m_fcapnow = file_util::pick({path_battery + "charge_now", path_battery + "energy_now"})).empty()) {
throw module_error("No suitable way to get current capacity value");
} else if ((m_fcapfull = file_util::pick({path_battery + "charge_full", path_battery + "energy_full"})).empty()) {
throw module_error("No suitable way to get max capacity value");
}
m_capacity_reader = make_unique<capacity_reader>([=] {
auto cap_now = std::strtoul(file_util::contents(m_fcapnow).c_str(), nullptr, 10);
auto cap_max = std::strtoul(file_util::contents(m_fcapfull).c_str(), nullptr, 10);
return math_util::percentage(cap_now, 0UL, cap_max);
});
// Make rate reader
if ((m_fvoltage = file_util::pick({path_battery + "voltage_now"})).empty()) {
throw module_error("No suitable way to get current voltage value");
} else if ((m_frate = file_util::pick({path_battery + "current_now", path_battery + "power_now"})).empty()) {
throw module_error("No suitable way to get current charge rate value");
}
m_rate_reader = make_unique<rate_reader>([this] {
unsigned long rate{std::strtoul(file_util::contents(m_frate).c_str(), nullptr, 10) / 1000UL};
unsigned long volt{std::strtoul(file_util::contents(m_fvoltage).c_str(), nullptr, 10) / 1000UL};
unsigned long now{std::strtoul(file_util::contents(m_fcapnow).c_str(), nullptr, 10) / 1000UL};
unsigned long max{std::strtoul(file_util::contents(m_fcapfull).c_str(), nullptr, 10) / 1000UL};
unsigned long cap{read(*m_state_reader) ? max - now : now};
if (rate && volt && cap) {
return 3600UL * (cap * 1000UL / volt) / (rate * 1000UL / volt);
} else {
return 0UL;
}
});
// Load state and capacity level
m_percentage = current_percentage();
m_state = current_state();
m_percentage = current_percentage(m_state);
// Add formats and elements
m_formatter->add(FORMAT_CHARGING, TAG_LABEL_CHARGING,
@ -97,11 +106,12 @@ namespace modules {
}
// Create inotify watches
watch(m_valuepath[battery_module::value::CAPACITY], IN_ACCESS);
watch(m_valuepath[battery_module::value::ADAPTER], IN_ACCESS);
watch(m_fcapnow, IN_ACCESS);
watch(m_fstate, IN_ACCESS);
// Setup time if token is used
if ((m_label_charging && m_label_charging->has_token("%time%")) || (m_label_discharging && m_label_discharging->has_token("%time%"))) {
if ((m_label_charging && m_label_charging->has_token("%time%")) ||
(m_label_discharging && m_label_discharging->has_token("%time%"))) {
if (!m_bar.locale.empty()) {
setlocale(LC_TIME, m_bar.locale.c_str());
}
@ -115,14 +125,16 @@ namespace modules {
*/
void battery_module::start() {
this->inotify_module::start();
m_threads.emplace_back(thread(&battery_module::subthread, this));
m_subthread = thread(&battery_module::subthread, this);
}
/**
* Release wake lock when stopping the module
*/
void battery_module::teardown() {
wakeup();
if (m_subthread.joinable()) {
m_subthread.join();
}
}
/**
@ -137,11 +149,10 @@ namespace modules {
void battery_module::idle() {
if (m_interval.count() > 0) {
auto now = chrono::system_clock::now();
if (chrono::duration_cast<decltype(m_interval)>(now - m_lastpoll) > m_interval) {
m_lastpoll = now;
m_log.info("%s: Polling values (inotify fallback)", name());
file_util::get_contents(m_valuepath[battery_module::value::CAPACITY]);
read(*m_capacity_reader);
}
}
@ -152,47 +163,40 @@ namespace modules {
* Update values when tracked files have changed
*/
bool battery_module::on_event(inotify_event* event) {
if (event != nullptr) {
m_log.trace("%s: Inotify event reported for %s", name(), event->filename);
}
auto state = current_state();
auto percentage = current_percentage(state);
// Reset timer to avoid unnecessary polling
m_lastpoll = chrono::system_clock::now();
auto state = current_state();
int percentage = m_percentage;
if (event != nullptr) {
m_log.trace("%s: Inotify event reported for %s", name(), event->filename);
if (state != battery_module::state::FULL) {
percentage = current_percentage();
if (state == m_state && percentage == m_percentage && m_unchanged--) {
return false;
}
m_unchanged = SKIP_N_UNCHANGED;
}
if (event != nullptr && state == m_state && percentage == m_percentage && m_unchanged--) {
return false;
}
m_percentage = percentage;
m_state = state;
m_unchanged = SKIP_N_UNCHANGED;
m_percentage = percentage;
string time_remaining;
const auto label = [this] {
if (m_state == state::FULL) {
return m_label_full;
} else if (m_state == state::DISCHARGING) {
return m_label_discharging;
} else {
return m_label_charging;
}
}();
if (m_state == battery_module::state::CHARGING && m_label_charging) {
if (!m_timeformat.empty()) {
time_remaining = current_time();
}
m_label_charging->reset_tokens();
m_label_charging->replace_token("%percentage%", to_string(m_percentage) + "%");
m_label_charging->replace_token("%time%", time_remaining);
} else if (m_state == battery_module::state::DISCHARGING && m_label_discharging) {
if (!m_timeformat.empty()) {
time_remaining = current_time();
}
m_label_discharging->reset_tokens();
m_label_discharging->replace_token("%percentage%", to_string(m_percentage) + "%");
m_label_discharging->replace_token("%time%", time_remaining);
} else if (m_state == battery_module::state::FULL && m_label_full) {
m_label_full->reset_tokens();
m_label_full->replace_token("%percentage%", to_string(m_percentage) + "%");
label->reset_tokens();
label->replace_token("%percentage%", to_string(m_percentage) + "%");
if (m_state != state::FULL && !m_timeformat.empty()) {
label->replace_token("%time%", current_time());
}
return true;
@ -202,17 +206,17 @@ namespace modules {
* Get the output format based on state
*/
string battery_module::get_format() const {
if (m_state == battery_module::state::FULL) {
return FORMAT_FULL;
} else if (m_state == battery_module::state::CHARGING) {
if (m_state == state::CHARGING) {
return FORMAT_CHARGING;
} else {
} else if (m_state == state::DISCHARGING) {
return FORMAT_DISCHARGING;
} else {
return FORMAT_FULL;
}
}
/**
* Generate the module output using defined drawtypes
* Generate module output using defined drawtypes
*/
bool battery_module::build(builder* builder, const string& tag) const {
if (tag == TAG_ANIMATION_CHARGING) {
@ -230,6 +234,7 @@ namespace modules {
} else {
return false;
}
return true;
}
@ -237,83 +242,45 @@ namespace modules {
* Get the current battery state
*/
battery_module::state battery_module::current_state() {
auto adapter_status = file_util::get_contents(m_valuepath[battery_module::value::ADAPTER]);
if (adapter_status.empty()) {
return battery_module::state::DISCHARGING;
} else if (adapter_status[0] == '0') {
return battery_module::state::DISCHARGING;
} else if (adapter_status[0] != '1') {
return battery_module::state::DISCHARGING;
} else if (m_percentage < m_fullat) {
return battery_module::state::CHARGING;
if (!read(*m_state_reader)) {
return state::DISCHARGING;
} else if (read(*m_capacity_reader) < m_fullat) {
return state::CHARGING;
} else {
return battery_module::state::FULL;
return state::FULL;
}
}
/**
* Get the current capacity level
*/
int battery_module::current_percentage() {
auto capacity_now =
std::strtoul(file_util::get_contents(m_valuepath[battery_module::value::CAPACITY]).c_str(), nullptr, 10);
auto capacity_max =
std::strtoul(file_util::get_contents(m_valuepath[battery_module::value::CAPACITY_MAX]).c_str(), nullptr, 10);
auto percentage = math_util::percentage(capacity_now, 0UL, capacity_max);
return percentage < m_fullat ? percentage : 100;
int battery_module::current_percentage(state state) {
int percentage{read(*m_capacity_reader)};
if (state == state::FULL && percentage >= m_fullat) {
percentage = 100;
}
return percentage;
}
/**
* Get estimate of remaining time until fully dis-/charged
*/
string battery_module::current_time() {
if (m_state == battery_module::state::FULL) {
return "";
}
int rate{atoi(file_util::get_contents(m_valuepath[battery_module::value::RATE]).c_str()) / 1000};
int volt{atoi(file_util::get_contents(m_valuepath[battery_module::value::VOLTAGE]).c_str()) / 1000};
int now{atoi(file_util::get_contents(m_valuepath[battery_module::value::CAPACITY]).c_str()) / 1000};
int max{atoi(file_util::get_contents(m_valuepath[battery_module::value::CAPACITY_MAX]).c_str()) / 1000};
int cap{0};
if (m_state == battery_module::state::CHARGING) {
cap = max - now;
} else if (m_state == battery_module::state::DISCHARGING) {
cap = now;
}
struct tm t {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, nullptr
};
if (rate && volt && cap) {
cap = cap * 1000 / volt;
rate = rate * 1000 / volt;
if (!rate) {
rate = -1;
}
chrono::seconds sec{3600 * cap / rate};
m_log.trace("%s: sec=%d %d%% cap=%lu rate=%lu volt=%lu", name(), sec.count(), static_cast<int>(m_percentage), cap,
rate, volt);
if (sec.count() > 0) {
t.tm_hour = chrono::duration_cast<chrono::hours>(sec).count();
sec -= chrono::seconds{3600 * t.tm_hour};
t.tm_min = chrono::duration_cast<chrono::minutes>(sec).count();
sec -= chrono::seconds{60 * t.tm_min};
t.tm_sec = chrono::duration_cast<chrono::seconds>(sec).count();
}
chrono::seconds sec{read(*m_rate_reader)};
if (sec.count() > 0) {
t.tm_hour = chrono::duration_cast<chrono::hours>(sec).count();
sec -= chrono::seconds{3600 * t.tm_hour};
t.tm_min = chrono::duration_cast<chrono::minutes>(sec).count();
sec -= chrono::seconds{60 * t.tm_min};
t.tm_sec = chrono::duration_cast<chrono::seconds>(sec).count();
}
char buffer[256]{0};
strftime(buffer, sizeof(buffer), m_timeformat.c_str(), &t);
return {buffer};
}
@ -322,18 +289,19 @@ namespace modules {
* to refresh <animation-charging> in case it is used.
*/
void battery_module::subthread() {
chrono::duration<double> dur = 1s;
chrono::duration<double> dur{0.0};
if (m_animation_charging) {
dur = chrono::duration<double>(float(m_animation_charging->framerate()) / 1000.0f);
dur += chrono::milliseconds{m_animation_charging->framerate()};
} else {
dur += 1s;
}
while (running()) {
for (int i = 0; running() && i < dur.count(); ++i) {
if (m_state == battery_module::state::CHARGING) {
if (m_state == state::CHARGING) {
broadcast();
}
sleep(dur);
}
}

View File

@ -39,7 +39,7 @@ namespace modules {
}
bool temperature_module::update() {
m_temp = std::atoi(file_util::get_contents(m_path).c_str()) / 1000.0f + 0.5f;
m_temp = std::atoi(file_util::contents(m_path).c_str()) / 1000.0f + 0.5f;
m_perc = math_util::cap(math_util::percentage(m_temp, 0, m_tempwarn), 0, 100);
const auto replace_tokens = [&](label_t& label) {

View File

@ -169,14 +169,25 @@ namespace file_util {
return stat(filename.c_str(), &buffer) == 0;
}
/**
* Picks the first existing file out of given entries
*/
string pick(const vector<string>& filenames) {
for (auto&& f : filenames) {
if (exists(f)) {
return f;
}
}
return "";
}
/**
* Gets the contents of the given file
*/
string get_contents(const string& filename) {
string contents(const string& filename) {
try {
std::ifstream ifs(filename);
string contents((std::istreambuf_iterator<char>(ifs)), (std::istreambuf_iterator<char>()));
return contents;
return string((std::istreambuf_iterator<char>(ifs)), (std::istreambuf_iterator<char>()));
} catch (std::ios_base::failure& e) {
return "";
}