Battery stats plugin: fix and improvements (#5626)

- fix incorrectly shown awake, sleeping, charging and discharging,
- remove unneeded debug mode and logging to external file,
- prevent showing values like inf, -inf, nan in estimated times
  (we now show "n/a"), and values below zero,
- show extra confirm box when we want to reset data,
- show time in format xxhxxm instead of only pure minutes,
- check at initialization that the device was charging when it was
  turned off (battery level larger than at the time of exit).
pull/5647/head
Robert 4 years ago committed by poire-z
parent 2161a76ea8
commit 830fc790f1

@ -1,14 +1,12 @@
local ConfirmBox = require("ui/widget/confirmbox")
local DataStorage = require("datastorage") local DataStorage = require("datastorage")
local KeyValuePage = require("ui/widget/keyvaluepage") local KeyValuePage = require("ui/widget/keyvaluepage")
local LuaSettings = require("luasettings") local LuaSettings = require("luasettings")
local PowerD = require("device"):getPowerDevice() local PowerD = require("device"):getPowerDevice()
local UIManager = require("ui/uimanager") local UIManager = require("ui/uimanager")
local WidgetContainer = require("ui/widget/container/widgetcontainer") local WidgetContainer = require("ui/widget/container/widgetcontainer")
local T = require("ffi/util").template
local dbg = require("dbg") local dbg = require("dbg")
local logger = require("logger") local util = require("util")
local util = require("ffi/util")
local _ = require("gettext") local _ = require("gettext")
local State = {} local State = {}
@ -29,6 +27,7 @@ function State:toString()
end end
local Usage = {} local Usage = {}
local INDENTATION = " " -- Three spaces.
function Usage:new(o) function Usage:new(o)
o = o or {} o = o or {}
@ -43,7 +42,7 @@ end
function Usage:append(state) function Usage:append(state)
local curr = State:new() local curr = State:new()
self.percentage = self.percentage + (state.percentage - curr.percentage) self.percentage = self.percentage + math.abs(state.percentage - curr.percentage)
self.time = self.time + os.difftime(curr.timestamp - state.timestamp) self.time = self.time + os.difftime(curr.timestamp - state.timestamp)
end end
@ -64,59 +63,64 @@ function Usage:percentagePerHour()
end end
function Usage:remainingHours() function Usage:remainingHours()
if self:percentagePerHour() == 0 then return "n/a" end
local curr = State:new() local curr = State:new()
return curr.percentage / self:percentagePerHour() return curr.percentage / self:percentagePerHour()
end end
function Usage:chargingHours() function Usage:chargingHours()
if self:percentagePerHour() == 0 then return "n/a" end
local curr = State:new() local curr = State:new()
return (curr.percentage - 100) / self:percentagePerHour() return math.abs(curr.percentage - 100) / self:percentagePerHour()
end end
local function shorten(number) local function shorten(number)
if number == "n/a" then return _("n/a") end
return string.format("%.2f", number); return string.format("%.2f", number);
end end
function Usage:dump(kv_pairs) function Usage:dump(kv_pairs)
table.insert(kv_pairs, {_(" Consumed %"), shorten(self.percentage)}) table.insert(kv_pairs, {INDENTATION .. _("Consumed %"), shorten(self.percentage)})
table.insert(kv_pairs, {_(" Total minutes"), shorten(self:minutes())}) table.insert(kv_pairs, {INDENTATION .. _("Total time"), util.secondsToHClock(self.time, true, true)})
table.insert(kv_pairs, {_(" % per hour"), shorten(self:percentagePerHour())}) table.insert(kv_pairs, {INDENTATION .. _("% per hour"), shorten(self:percentagePerHour())})
end end
function Usage:dumpRemaining(kv_pairs) function Usage:dumpRemaining(kv_pairs)
table.insert(kv_pairs, {_(" Estimated remaining hours"), shorten(self:remainingHours())}) table.insert(kv_pairs, {INDENTATION .. _("Estimated remaining hours"), shorten(self:remainingHours())})
end end
function Usage:dumpCharging(kv_pairs) function Usage:dumpCharging(kv_pairs)
table.insert(kv_pairs, {_(" Estimated hours for charging"), shorten(self:chargingHours())}) table.insert(kv_pairs, {INDENTATION .. _("Estimated hours for charging"), shorten(self:chargingHours())})
end end
local BatteryStat = { local BatteryStat = {
settings = LuaSettings:open(DataStorage:getSettingsDir() .. "/batterstat.lua"), settings = LuaSettings:open(DataStorage:getSettingsDir() .. "/battery_stats.lua"),
dump_file = util.realpath(DataStorage:getDataDir()) .. "/batterystat.log",
debugging = false,
kv_page = nil, kv_page = nil,
} }
function BatteryStat:init() function BatteryStat:init()
self.charging = Usage:new(self.settings:readSetting("charging"))
self.discharging = Usage:new(self.settings:readSetting("discharging"))
self.awake = Usage:new(self.settings:readSetting("awake")) self.awake = Usage:new(self.settings:readSetting("awake"))
self.sleeping = Usage:new(self.settings:readSetting("sleeping")) self.sleeping = Usage:new(self.settings:readSetting("sleeping"))
self.charging = Usage:new(self.settings:readSetting("charging"))
self.discharging = Usage:new(self.settings:readSetting("discharging"))
-- Note: these fields are not the "real" timestamp and battery usage, but -- Note: these fields are not the "real" timestamp and battery usage, but
-- the unaccumulated values. -- the unaccumulated values.
self.charging_state = State:new(self.settings:readSetting("charging_state")) self.awake_state = State:new()
self.awake_state = State:new(self.settings:readSetting("awake_state")) self.charging_state = State:new()
-- Whether the device was suspending before current timestamp. -- Whether the device was suspending before current timestamp.
self.was_suspending = false self.was_suspending = false
-- Whether the device was charging before current timestamp. -- Whether the device was charging before current timestamp.
self.was_charging = PowerD:isCharging() self.was_charging = PowerD:isCharging()
if self.was_charging then
if self.debugging then self:reset(true, false)
self.debugOutput = self._debugOutput end
else -- Check if the battery was charging when KO was turned off.
self.debugOutput = function() end local battery_before_off = self.settings:readSetting("awake_state")
if battery_before_off and battery_before_off.percentage
and self.awake_state.percentage > battery_before_off.percentage then
self:reset(false, true)
end end
end end
@ -133,10 +137,10 @@ function BatteryStat:onFlushSettings()
end end
function BatteryStat:accumulate() function BatteryStat:accumulate()
if self.was_suspending then if self.was_suspending and not self.was_charging then
-- Suspending to awake. -- Suspending to awake.
self.sleeping:append(self.awake_state) self.sleeping:append(self.awake_state)
else elseif not self.was_suspending and not self.was_charging then
-- Awake to suspending, time between self.awake_state and now should belong to awake. -- Awake to suspending, time between self.awake_state and now should belong to awake.
self.awake:append(self.awake_state) self.awake:append(self.awake_state)
end end
@ -150,24 +154,7 @@ function BatteryStat:accumulate()
self.charging_state = State:new() self.charging_state = State:new()
end end
function BatteryStat:dumpOrLog(content)
local file = io.open(self.dump_file, "a")
if file then
file:write(content .. "\n")
file:close()
else
logger.warn("Failed to dump output ", content, " into ", self.dump_file )
end
end
function BatteryStat:_debugOutput(event)
self:dumpOrLog(event .. " @ " .. State:new():toString() ..
", awake_state " .. self.awake_state:toString() ..
", charging_state " .. self.charging_state:toString())
end
function BatteryStat:onSuspend() function BatteryStat:onSuspend()
self:debugOutput("onSuspend")
if not self.was_suspending then if not self.was_suspending then
self:accumulate() self:accumulate()
end end
@ -175,7 +162,6 @@ function BatteryStat:onSuspend()
end end
function BatteryStat:onResume() function BatteryStat:onResume()
self:debugOutput("onResume")
if self.was_suspending then if self.was_suspending then
self:accumulate() self:accumulate()
end end
@ -183,16 +169,14 @@ function BatteryStat:onResume()
end end
function BatteryStat:onCharging() function BatteryStat:onCharging()
self:debugOutput("onCharging")
if not self.was_charging then if not self.was_charging then
self:reset(true, false) self:reset(true, true)
self:accumulate() self:accumulate()
end end
self.was_charging = true self.was_charging = true
end end
function BatteryStat:onNotCharging() function BatteryStat:onNotCharging()
self:debugOutput("onNotCharging")
if self.was_charging then if self.was_charging then
self:reset(false, true) self:reset(false, true)
self:accumulate() self:accumulate()
@ -201,21 +185,33 @@ function BatteryStat:onNotCharging()
end end
function BatteryStat:showStatistics() function BatteryStat:showStatistics()
local function askResetData()
UIManager:show(ConfirmBox:new{
text = _("Are you sure that you want to clear battery statistics?"),
ok_text = _("Clear"),
ok_callback = function()
self:resetAll()
self:restart()
end,
})
end
self:accumulate() self:accumulate()
local kv_pairs = self:dump() local kv_pairs = self:dump()
table.insert(kv_pairs, "----------") table.insert(kv_pairs, "----------")
table.insert(kv_pairs, {_("Historical records are dumped to"), ""})
table.insert(kv_pairs, {self.dump_file, ""})
table.insert(kv_pairs, "----------")
table.insert(kv_pairs, {_("If you would like to reset the data,"), "", table.insert(kv_pairs, {_("If you would like to reset the data,"), "",
callback = function() callback = function()
self:resetAll() UIManager:setDirty(self.kv_page, "fast")
self:restart() UIManager:scheduleIn(0.1, function()
askResetData()
end)
end}) end})
table.insert(kv_pairs, {_("please tap here."), "", table.insert(kv_pairs, {_("please tap here."), "",
callback = function() callback = function()
self:resetAll() UIManager:setDirty(self.kv_page, "fast")
self:restart() UIManager:scheduleIn(0.1, function()
askResetData()
end)
end}) end})
self.kv_page = KeyValuePage:new{ self.kv_page = KeyValuePage:new{
title = _("Battery statistics"), title = _("Battery statistics"),
@ -225,7 +221,6 @@ function BatteryStat:showStatistics()
end end
function BatteryStat:reset(withCharging, withDischarging) function BatteryStat:reset(withCharging, withDischarging)
self:dumpToText()
self.awake = Usage:new() self.awake = Usage:new()
self.sleeping = Usage:new() self.sleeping = Usage:new()
@ -235,6 +230,7 @@ function BatteryStat:reset(withCharging, withDischarging)
if withDischarging then if withDischarging then
self.discharging = Usage:new() self.discharging = Usage:new()
end end
self.awake_state = State:new()
end end
function BatteryStat:resetAll() function BatteryStat:resetAll()
@ -249,18 +245,6 @@ function BatteryStat:restart()
self:showStatistics() self:showStatistics()
end end
function BatteryStat:dumpToText()
local kv_pairs = self:dump()
local content = T(_("Dump at %1"), os.date("%c"))
for _, pair in ipairs(kv_pairs) do
content = content .. "\n" .. pair[1]
if pair[2] ~= nil and pair[2] ~= "" then
content = content .. "\t" .. pair[2]
end
end
self:dumpOrLog(content .. "\n-=-=-=-=-=-\n")
end
function BatteryStat:dump() function BatteryStat:dump()
local kv_pairs = {} local kv_pairs = {}
table.insert(kv_pairs, {_("Awake since last charge"), ""}) table.insert(kv_pairs, {_("Awake since last charge"), ""})

@ -40,10 +40,10 @@ describe("BatteryState plugin tests #nocov", function()
assert.is_false(widget.was_suspending) assert.is_false(widget.was_suspending)
MockTime:increase(1) MockTime:increase(1)
widget:accumulate() widget:accumulate()
-- awake & charging time should be reset. -- Awake charging & discharging time should be reset.
assert.are.equal(1, widget.awake.time) assert.are.equal(0, widget.awake.time)
assert.are.equal(0, widget.sleeping.time) assert.are.equal(0, widget.sleeping.time)
assert.are.equal(1, widget.discharging.time) assert.are.equal(0, widget.discharging.time)
assert.are.equal(1, widget.charging.time) assert.are.equal(1, widget.charging.time)
widget:onNotCharging() widget:onNotCharging()
@ -62,10 +62,10 @@ describe("BatteryState plugin tests #nocov", function()
assert.is_false(widget.was_suspending) assert.is_false(widget.was_suspending)
MockTime:increase(1) MockTime:increase(1)
widget:accumulate() widget:accumulate()
-- awake & charging time should be reset. -- Awake charging & discharging time should be reset.
assert.are.equal(1, widget.awake.time) assert.are.equal(0, widget.awake.time)
assert.are.equal(0, widget.sleeping.time) assert.are.equal(0, widget.sleeping.time)
assert.are.equal(1, widget.discharging.time) assert.are.equal(0, widget.discharging.time)
assert.are.equal(1, widget.charging.time) assert.are.equal(1, widget.charging.time)
end) end)
@ -129,10 +129,10 @@ describe("BatteryState plugin tests #nocov", function()
assert.is_false(widget.was_suspending) assert.is_false(widget.was_suspending)
MockTime:increase(1) MockTime:increase(1)
widget:accumulate() widget:accumulate()
-- awake & charging time should be reset. -- Awake charging & discharging time should be reset.
assert.are.equal(1, widget.awake.time) assert.are.equal(0, widget.awake.time)
assert.are.equal(0, widget.sleeping.time) assert.are.equal(0, widget.sleeping.time)
assert.are.equal(1, widget.discharging.time) assert.are.equal(0, widget.discharging.time)
assert.are.equal(1, widget.charging.time) assert.are.equal(1, widget.charging.time)
widget:onCharging() widget:onCharging()
@ -140,9 +140,9 @@ describe("BatteryState plugin tests #nocov", function()
assert.is_false(widget.was_suspending) assert.is_false(widget.was_suspending)
MockTime:increase(1) MockTime:increase(1)
widget:accumulate() widget:accumulate()
assert.are.equal(2, widget.awake.time) assert.are.equal(0, widget.awake.time)
assert.are.equal(0, widget.sleeping.time) assert.are.equal(0, widget.sleeping.time)
assert.are.equal(1, widget.discharging.time) assert.are.equal(0, widget.discharging.time)
assert.are.equal(2, widget.charging.time) assert.are.equal(2, widget.charging.time)
end) end)

Loading…
Cancel
Save