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

313 lines
8.9 KiB
Lua

local ConfirmBox = require("ui/widget/confirmbox")
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 dbg = require("dbg")
local util = require("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:getCapacityHW()
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 = {}
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 + 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()
if self:percentagePerHour() == 0 then return "n/a" end
local curr = State:new()
return curr.percentage / self:percentagePerHour()
end
function Usage:chargingHours()
if self:percentagePerHour() == 0 then return "n/a" end
local curr = State:new()
return math.abs(curr.percentage - 100) / self:percentagePerHour()
end
local function shorten(number)
if number == "n/a" then return _("n/a") end
return string.format("%.2f", number);
end
function Usage:dump(kv_pairs)
table.insert(kv_pairs, {INDENTATION .. _("Consumed %"), shorten(self.percentage)})
table.insert(kv_pairs, {INDENTATION .. _("Total time"), util.secondsToHClock(self.time, true, true)})
table.insert(kv_pairs, {INDENTATION .. _("% per hour"), shorten(self:percentagePerHour())})
end
function Usage:dumpRemaining(kv_pairs)
table.insert(kv_pairs, {INDENTATION .. _("Estimated remaining hours"), shorten(self:remainingHours())})
end
function Usage:dumpCharging(kv_pairs)
table.insert(kv_pairs, {INDENTATION .. _("Estimated hours for charging"), shorten(self:chargingHours())})
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()
table.insert(kv_pairs, "----------")
table.insert(kv_pairs, {_("If you would like to reset the data,"), "",
callback = function()
UIManager:setDirty(self.kv_page, "fast")
UIManager:scheduleIn(0.1, function()
askResetData()
end)
end})
table.insert(kv_pairs, {_("please tap here."), "",
callback = function()
UIManager:setDirty(self.kv_page, "fast")
UIManager:scheduleIn(0.1, function()
askResetData()
end)
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.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)
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 is nil in test cases.
if not self.ui or not self.ui.menu then return end
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: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