You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
koreader/plugins/batterystat.koplugin/main.lua

318 lines
8.7 KiB
Lua

local DataStorage = require("datastorage")
local KeyValuePage = require("ui/widget/keyvaluepage")
local LuaSettings = require("luasettings")
local PowerD = require("device"):getPowerDevice()
local UIManager = require("ui/uimanager")
local WidgetContainer = require("ui/widget/container/widgetcontainer")
local T = require("ffi/util").template
local dbg = require("dbg")
local logger = require("logger")
local util = require("ffi/util")
local _ = require("gettext")
local State = {}
function State:new(o)
o = o or {}
setmetatable(o, self)
self.__index = self
if o.percentage == nil or o.timestamp == nil then
o.percentage = PowerD:getCapacity()
o.timestamp = os.time()
end
return o
end
function State:toString()
return string.format("{%d @ %s}", self.percentage, os.date("%c", self.timestamp))
end
local Usage = {}
function Usage:new(o)
o = o or {}
setmetatable(o, self)
self.__index = self
if o.percentage == nil or o.time == nil then
o.percentage = 0
o.time = 0
end
return o
end
function Usage:append(state)
local curr = State:new()
self.percentage = self.percentage + (state.percentage - curr.percentage)
self.time = self.time + os.difftime(curr.timestamp - state.timestamp)
end
function Usage:minutes()
return self.time / 60
end
function Usage:hours()
return self:minutes() / 60
end
function Usage:percentagePerHour()
if self.time == 0 then
return 0
else
return self.percentage / self:hours()
end
end
function Usage:remainingHours()
local curr = State:new()
return curr.percentage / self:percentagePerHour()
end
function Usage:chargingHours()
local curr = State:new()
return (curr.percentage - 100) / self:percentagePerHour()
end
local function shorten(number)
return string.format("%.2f", number);
end
function Usage:dump(kv_pairs)
table.insert(kv_pairs, {_(" Consumed %"), shorten(self.percentage)})
table.insert(kv_pairs, {_(" Total minutes"), shorten(self:minutes())})
table.insert(kv_pairs, {_(" % per hour"), shorten(self:percentagePerHour())})
end
function Usage:dumpRemaining(kv_pairs)
table.insert(kv_pairs, {_(" Estimated remaining hours"), shorten(self:remainingHours())})
end
function Usage:dumpCharging(kv_pairs)
table.insert(kv_pairs, {_(" Estimated hours for charging"), shorten(self:chargingHours())})
end
local BatteryStat = {
settings = LuaSettings:open(DataStorage:getSettingsDir() .. "/batterstat.lua"),
dump_file = util.realpath(DataStorage:getDataDir()) .. "/batterystat.log",
debugging = false,
kv_page = nil,
}
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.sleeping = Usage:new(self.settings:readSetting("sleeping"))
-- Note: these fields are not the "real" timestamp and battery usage, but
-- the unaccumulated values.
self.charging_state = State:new(self.settings:readSetting("charging_state"))
self.awake_state = State:new(self.settings:readSetting("awake_state"))
self:initCurrentState()
if self.debugging then
self.debugOutput = self._debugOutput
else
self.debugOutput = function() end
end
end
function BatteryStat:initCurrentState()
-- Whether the device was suspending before current timestamp.
self.was_suspending = false
-- Whether the device was charging before current timestamp.
self.was_charging = PowerD:isCharging()
end
function BatteryStat:onFlushSettings()
self.settings:reset({
charging = self.charging,
discharging = self.discharging,
awake = self.awake,
sleeping = self.sleeping,
charging_state = self.charging_state,
awake_state = self.awake_state,
})
self.settings:flush()
end
function BatteryStat:accumulate()
if self.was_suspending then
-- Suspending to awake.
self.sleeping:append(self.awake_state)
else
-- Awake to suspending, time between self.awake_state and now should belong to awake.
self.awake:append(self.awake_state)
end
if self.was_charging then
-- Decharging to charging.
self.charging:append(self.charging_state)
else
self.discharging:append(self.charging_state)
end
self.awake_state = State:new()
self.charging_state = State:new()
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()
self:debugOutput("onSuspend")
self.was_suspending = false
self:accumulate()
end
function BatteryStat:onResume()
self:debugOutput("onResume")
self.was_suspending = true
self:accumulate()
end
function BatteryStat:onCharging()
self:debugOutput("onCharging")
self.was_charging = false
self:reset(true, false)
self:accumulate()
end
function BatteryStat:onNotCharging()
self:debugOutput("onNotCharging")
self.was_charging = true
self:rest(false, true)
self:accumulate()
end
function BatteryStat:showStatistics()
self:initCurrentState()
self:accumulate()
local kv_pairs = self:dump()
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,"), "",
callback = function()
self:resetAll()
self:restart()
end})
table.insert(kv_pairs, {_("please tap here."), "",
callback = function()
self:resetAll()
self:restart()
end})
self.kv_page = KeyValuePage:new{
title = _("Battery statistics"),
kv_pairs = kv_pairs,
}
UIManager:show(self.kv_page)
end
function BatteryStat:reset(withCharging, withDischarging)
self:dumpToText()
self.awake = Usage:new()
self.sleeping = Usage:new()
if withCharging then
self.charging = Usage:new()
end
if withDischarging then
self.discharging = Usage:new()
end
end
function BatteryStat:resetAll()
self:reset(true, true)
self.charging_state = State:new()
self.awake_state = State:new()
end
function BatteryStat:restart()
dbg.dassert(self.kv_page ~= nil)
UIManager:close(self.kv_page)
self:showStatistics()
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()
local kv_pairs = {}
table.insert(kv_pairs, {_("Awake since last charge"), ""})
self.awake:dump(kv_pairs)
self.awake:dumpRemaining(kv_pairs)
table.insert(kv_pairs, {_("Sleeping since last charge"), ""})
self.sleeping:dump(kv_pairs)
self.sleeping:dumpRemaining(kv_pairs)
table.insert(kv_pairs, {_("During last charge"), ""})
self.charging:dump(kv_pairs)
self.charging:dumpCharging(kv_pairs)
table.insert(kv_pairs, {_("Since last charge"), ""})
self.discharging:dump(kv_pairs)
self.discharging:dumpRemaining(kv_pairs)
return kv_pairs
end
BatteryStat:init()
local BatteryStatWidget = WidgetContainer:new{
name = "batterystat",
}
function BatteryStatWidget:init()
self.ui.menu:registerToMainMenu(self)
end
function BatteryStatWidget:addToMainMenu(menu_items)
menu_items.battery_statistics = {
text = _("Battery statistics"),
callback = function()
BatteryStat:showStatistics()
end,
}
end
function BatteryStatWidget:onFlushSettings()
BatteryStat:onFlushSettings()
end
function BatteryStatWidget:onSuspend()
BatteryStat:onSuspend()
end
function BatteryStatWidget:onResume()
BatteryStat:onResume()
end
function BatteryStatWidget:onCharging()
BatteryStat:onCharging()
end
function BatteryStatWidget:onNotCharging()
BatteryStat:onNotCharging()
end
return BatteryStatWidget