2017-03-24 06:36:15 +00:00
|
|
|
|
|
|
|
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
|
2017-05-16 09:11:11 +00:00
|
|
|
local dbg = require("dbg")
|
2017-03-24 06:36:15 +00:00
|
|
|
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
|
2017-06-24 14:53:20 +00:00
|
|
|
o.percentage = PowerD:getCapacityHW()
|
2017-03-24 06:36:15 +00:00
|
|
|
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,
|
2017-05-16 09:11:11 +00:00
|
|
|
kv_page = nil,
|
2017-03-24 06:36:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function BatteryStat:init()
|
|
|
|
self.charging = Usage:new(self.settings:readSetting("charging"))
|
2017-05-16 09:11:11 +00:00
|
|
|
self.discharging = Usage:new(self.settings:readSetting("discharging"))
|
2017-03-24 06:36:15 +00:00
|
|
|
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"))
|
2017-05-31 18:31:27 +00:00
|
|
|
-- 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()
|
2017-03-24 06:36:15 +00:00
|
|
|
|
|
|
|
if self.debugging then
|
|
|
|
self.debugOutput = self._debugOutput
|
|
|
|
else
|
|
|
|
self.debugOutput = function() end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
function BatteryStat:onFlushSettings()
|
|
|
|
self.settings:reset({
|
|
|
|
charging = self.charging,
|
2017-05-16 09:11:11 +00:00
|
|
|
discharging = self.discharging,
|
2017-03-24 06:36:15 +00:00
|
|
|
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
|
2017-05-16 09:11:11 +00:00
|
|
|
self.discharging:append(self.charging_state)
|
2017-03-24 06:36:15 +00:00
|
|
|
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")
|
2017-08-08 06:29:57 +00:00
|
|
|
if not self.was_suspending then
|
|
|
|
self:accumulate()
|
|
|
|
end
|
|
|
|
self.was_suspending = true
|
2017-03-24 06:36:15 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
function BatteryStat:onResume()
|
|
|
|
self:debugOutput("onResume")
|
2017-08-08 06:29:57 +00:00
|
|
|
if self.was_suspending then
|
|
|
|
self:accumulate()
|
|
|
|
end
|
|
|
|
self.was_suspending = false
|
2017-03-24 06:36:15 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
function BatteryStat:onCharging()
|
|
|
|
self:debugOutput("onCharging")
|
2017-08-08 06:29:57 +00:00
|
|
|
if not self.was_charging then
|
|
|
|
self:reset(true, false)
|
|
|
|
self:accumulate()
|
|
|
|
end
|
|
|
|
self.was_charging = true
|
2017-03-24 06:36:15 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
function BatteryStat:onNotCharging()
|
|
|
|
self:debugOutput("onNotCharging")
|
2017-08-08 06:29:57 +00:00
|
|
|
if self.was_charging then
|
|
|
|
self:reset(false, true)
|
|
|
|
self:accumulate()
|
|
|
|
end
|
|
|
|
self.was_charging = false
|
2017-03-24 06:36:15 +00:00
|
|
|
end
|
|
|
|
|
2017-04-02 21:46:19 +00:00
|
|
|
function BatteryStat:showStatistics()
|
2017-03-24 06:36:15 +00:00
|
|
|
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, ""})
|
2017-05-16 09:11:11 +00:00
|
|
|
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{
|
2017-03-24 06:36:15 +00:00
|
|
|
title = _("Battery statistics"),
|
|
|
|
kv_pairs = kv_pairs,
|
2017-05-16 09:11:11 +00:00
|
|
|
}
|
|
|
|
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()
|
2017-03-24 06:36:15 +00:00
|
|
|
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"), ""})
|
2017-05-16 09:11:11 +00:00
|
|
|
self.discharging:dump(kv_pairs)
|
|
|
|
self.discharging:dumpRemaining(kv_pairs)
|
2017-03-24 06:36:15 +00:00
|
|
|
return kv_pairs
|
|
|
|
end
|
|
|
|
|
|
|
|
BatteryStat:init()
|
|
|
|
|
2017-04-02 21:46:19 +00:00
|
|
|
local BatteryStatWidget = WidgetContainer:new{
|
|
|
|
name = "batterystat",
|
|
|
|
}
|
2017-03-24 06:36:15 +00:00
|
|
|
|
|
|
|
function BatteryStatWidget:init()
|
2017-08-08 06:29:57 +00:00
|
|
|
-- self.ui is nil in test cases.
|
|
|
|
if not self.ui or not self.ui.menu then return end
|
2017-03-24 06:36:15 +00:00
|
|
|
self.ui.menu:registerToMainMenu(self)
|
|
|
|
end
|
|
|
|
|
2017-03-26 09:27:43 +00:00
|
|
|
function BatteryStatWidget:addToMainMenu(menu_items)
|
|
|
|
menu_items.battery_statistics = {
|
2017-03-24 06:36:15 +00:00
|
|
|
text = _("Battery statistics"),
|
|
|
|
callback = function()
|
2017-04-02 21:46:19 +00:00
|
|
|
BatteryStat:showStatistics()
|
2017-03-24 06:36:15 +00:00
|
|
|
end,
|
2017-03-26 10:57:47 +00:00
|
|
|
}
|
2017-03-24 06:36:15 +00:00
|
|
|
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
|
|
|
|
|
2017-08-08 06:29:57 +00:00
|
|
|
-- Test only
|
|
|
|
function BatteryStatWidget:stat()
|
|
|
|
return BatteryStat
|
|
|
|
end
|
|
|
|
|
2017-03-24 06:36:15 +00:00
|
|
|
return BatteryStatWidget
|