mirror of
https://github.com/koreader/koreader
synced 2024-11-02 15:40:16 +00:00
322 lines
9.3 KiB
Lua
322 lines
9.3 KiB
Lua
local ConfirmBox = require("ui/widget/confirmbox")
|
|
local DataStorage = require("datastorage")
|
|
local Dispatcher = require("dispatcher")
|
|
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 datetime = require("datetime")
|
|
local dbg = require("dbg")
|
|
local time = require("ui/time")
|
|
local _ = require("gettext")
|
|
local T = require("ffi/util").template
|
|
|
|
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:getCapacityHW()
|
|
o.timestamp = time.boottime_or_realtime_coarse()
|
|
end
|
|
return o
|
|
end
|
|
|
|
function State:toString()
|
|
return string.format("{%d @ %s}", self.percentage, os.date("%c", time.to_s(self.timestamp)))
|
|
end
|
|
|
|
local Usage = {}
|
|
local INDENTATION = " " -- Three spaces.
|
|
|
|
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 + math.abs(state.percentage - curr.percentage)
|
|
self.time = self.time + curr.timestamp - state.timestamp
|
|
end
|
|
|
|
function Usage:percentageRate()
|
|
if self.time == 0 then
|
|
return 0
|
|
else
|
|
return self.percentage / time.to_s(self.time)
|
|
end
|
|
end
|
|
|
|
function Usage:percentageRatePerHour()
|
|
return self:percentageRate() * 3600
|
|
end
|
|
|
|
function Usage:remainingTime()
|
|
if self:percentageRate() == 0 then return "N/A" end
|
|
local curr = State:new()
|
|
return curr.percentage / self:percentageRate()
|
|
end
|
|
|
|
function Usage:chargingTime()
|
|
if self:percentageRate() == 0 then return "N/A" end
|
|
local curr = State:new()
|
|
return math.abs(curr.percentage - 100) / self:percentageRate()
|
|
end
|
|
|
|
local function shorten(number)
|
|
if number == "N/A" then return _("N/A") end
|
|
return string.format("%.2f%%", number)
|
|
end
|
|
|
|
local function duration(number)
|
|
local duration_fmt = G_reader_settings:readSetting("duration_format", "classic")
|
|
return type(number) ~= "number" and number or
|
|
datetime.secondsToClockDuration(duration_fmt, number, true, true)
|
|
end
|
|
|
|
function Usage:dump(kv_pairs, id)
|
|
local name = id or _("Consumed:")
|
|
table.insert(kv_pairs, {INDENTATION .. _("Total time:"), duration(time.to_s(self.time)) })
|
|
table.insert(kv_pairs, {INDENTATION .. name, shorten(self.percentage), "%"})
|
|
table.insert(kv_pairs, {INDENTATION .. _("Change per hour:"), shorten(self:percentageRatePerHour())})
|
|
end
|
|
|
|
function Usage:dumpRemaining(kv_pairs)
|
|
table.insert(kv_pairs, {INDENTATION .. _("Estimated remaining time:"), duration(self:remainingTime())})
|
|
end
|
|
|
|
function Usage:dumpCharging(kv_pairs)
|
|
table.insert(kv_pairs, {INDENTATION .. _("Estimated time for charging:"), duration(self:chargingTime())})
|
|
end
|
|
|
|
local BatteryStat = {
|
|
settings = LuaSettings:open(DataStorage:getSettingsDir() .. "/battery_stats.lua"),
|
|
kv_page = nil,
|
|
}
|
|
|
|
function BatteryStat:init()
|
|
self.awake = Usage:new(self.settings:readSetting("awake"))
|
|
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
|
|
-- the unaccumulated values.
|
|
self.awake_state = State:new()
|
|
self.charging_state = State:new()
|
|
|
|
-- 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()
|
|
if self.was_charging then
|
|
self:reset(true, false)
|
|
end
|
|
-- Check if the battery was charging when KO was turned off.
|
|
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
|
|
|
|
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 and not self.was_charging then
|
|
-- Suspending to awake.
|
|
self.sleeping:append(self.awake_state)
|
|
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.
|
|
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:onSuspend()
|
|
if not self.was_suspending then
|
|
self:accumulate()
|
|
end
|
|
self.was_suspending = true
|
|
end
|
|
|
|
function BatteryStat:onResume()
|
|
if self.was_suspending then
|
|
self:accumulate()
|
|
end
|
|
self.was_suspending = false
|
|
end
|
|
|
|
function BatteryStat:onCharging()
|
|
if not self.was_charging then
|
|
self:reset(true, true)
|
|
self:accumulate()
|
|
end
|
|
self.was_charging = true
|
|
end
|
|
|
|
function BatteryStat:onNotCharging()
|
|
if self.was_charging then
|
|
self:reset(false, true)
|
|
self:accumulate()
|
|
end
|
|
self.was_charging = false
|
|
end
|
|
|
|
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()
|
|
local kv_pairs = self:dump()
|
|
kv_pairs[#kv_pairs].separator = true
|
|
table.insert(kv_pairs, {_("Tap to reset the data."), "",
|
|
callback = function()
|
|
UIManager:setDirty(self.kv_page, "fast")
|
|
UIManager:scheduleIn(0.1, function()
|
|
askResetData()
|
|
end)
|
|
end})
|
|
self.kv_page = KeyValuePage:new{
|
|
title = T(_("Battery statistics (now %1%)"), self.awake_state.percentage),
|
|
kv_pairs = kv_pairs,
|
|
single_page = true,
|
|
}
|
|
UIManager:show(self.kv_page)
|
|
end
|
|
|
|
function BatteryStat:reset(withCharging, withDischarging)
|
|
self.awake = Usage:new()
|
|
self.sleeping = Usage:new()
|
|
|
|
if withCharging then
|
|
self.charging = Usage:new()
|
|
end
|
|
if withDischarging then
|
|
self.discharging = Usage:new()
|
|
end
|
|
self.awake_state = State:new()
|
|
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: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, _("Charged:"))
|
|
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:extend{
|
|
name = "batterystat",
|
|
}
|
|
|
|
function BatteryStatWidget:onDispatcherRegisterActions()
|
|
Dispatcher:registerAction("battery_statistics", {category="none", event="ShowBatteryStatistics", title=_("Battery statistics"), device=true, separator=true})
|
|
end
|
|
|
|
function BatteryStatWidget:init()
|
|
-- self.ui is nil in test cases.
|
|
if not self.ui or not self.ui.menu then return end
|
|
self:onDispatcherRegisterActions()
|
|
self.ui.menu:registerToMainMenu(self)
|
|
end
|
|
|
|
function BatteryStatWidget:addToMainMenu(menu_items)
|
|
menu_items.battery_statistics = {
|
|
text = _("Battery statistics"),
|
|
keep_menu_open = true,
|
|
callback = function()
|
|
BatteryStat:showStatistics()
|
|
end,
|
|
}
|
|
end
|
|
|
|
function BatteryStatWidget:onShowBatteryStatistics()
|
|
BatteryStat:showStatistics()
|
|
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
|
|
|
|
-- Test only
|
|
function BatteryStatWidget:stat()
|
|
return BatteryStat
|
|
end
|
|
|
|
return BatteryStatWidget
|