BatteryStat plugin and instruction of KoboLight plugin (#2643)

* Start battery stat plugin

* BatteryStat & kobolight

* Several minor improvements

* Remove a useless function

* flush settings

* Some review feedbacks

* Resolve review comments

* Remaining Minutes -> Remaining Hours

* Add dump_file

* typo

* realpath

* realpath on folder

* Remove useless os.time()

* Resolve review comments

* warning

* Add BatteryStat.debugging flag

* treat log as txt

* Minor improvement

* Charging hour should be positive

* Use warn instead of info

* onSuspend in Kobo

* Charging events for kobo and kindle

* More events

* dumpOrLog

* Warnings

* Typo

* More space

* Singleton

* slightly format change

* BatteryStat singleton

* Init

* Remove debugging flag

* sleeping percentage is still negative

* Read settings

* Do not need to change was_suspending and was_charging

* Typo

* Remove debugging flag

* Not charging should happen before suspend

* Resolve review comments

* was_suspend and was_charging should be updated each time in onCallback()
pull/2671/head
Hzj_jie 7 years ago committed by GitHub
parent 0edf1cca29
commit 0b29e73e2e

@ -4,10 +4,6 @@ local _, android = pcall(require, "android")
local AndroidPowerD = BasePowerD:new{
fl_min = 0, fl_max = 25,
fl_intensity = 10,
batt_capacity_file = "/sys/class/power_supply/battery/capacity",
is_charging_file = "/sys/class/power_supply/battery/charging_enabled",
battCapacity = nil,
is_charging = nil,
}
function AndroidPowerD:init()
@ -18,13 +14,11 @@ function AndroidPowerD:setIntensityHW()
end
function AndroidPowerD:getCapacityHW()
self.battCapacity = android.getBatteryLevel()
return self.battCapacity
return android.getBatteryLevel()
end
function AndroidPowerD:isChargingHW()
self.is_charging = android.isCharging()
return self.is_charging
return android.isCharging()
end
return AndroidPowerD

@ -1,14 +1,13 @@
local logger = require("logger")
local BasePowerD = {
fl_min = 0, -- min frontlight intensity
fl_max = 10, -- max frontlight intensity
fl_intensity = nil, -- frontlight intensity
battCapacity = nil, -- battery capacity
device = nil, -- device object
fl_min = 0, -- min frontlight intensity
fl_max = 10, -- max frontlight intensity
fl_intensity = nil, -- frontlight intensity
battCapacity = 0, -- battery capacity
device = nil, -- device object
capacity_pulled_count = 0,
capacity_cached_count = 10,
last_capacity_pull_time = 0, -- timestamp of last pull
}
function BasePowerD:new(o)
@ -22,8 +21,8 @@ end
function BasePowerD:init() end
function BasePowerD:toggleFrontlight() end
function BasePowerD:setIntensityHW() end
function BasePowerD:getCapacityHW() return "0" end
function BasePowerD:isChargingHW() end
function BasePowerD:getCapacityHW() return 0 end
function BasePowerD:isChargingHW() return false end
-- Anything needs to be done before do a real hardware suspend. Such as turn off
-- front light.
function BasePowerD:beforeSuspend() end
@ -66,19 +65,17 @@ function BasePowerD:setIntensity(intensity)
end
function BasePowerD:getCapacity()
if self.capacity_pulled_count == self.capacity_cached_count then
self.capacity_pulled_count = 0
return self:getCapacityHW()
else
self.capacity_pulled_count = self.capacity_pulled_count + 1
return self.battCapacity or self:getCapacityHW()
if os.time() - self.last_capacity_pull_time >= 60 then
self.battCapacity = self:getCapacityHW()
self.last_capacity_pull_time = os.time()
end
return self.battCapacity
end
function BasePowerD:refreshCapacity()
-- We want our next getCapacity call to actually pull up to date info
-- instead of a cached value ;)
self.capacity_pulled_count = self.capacity_cached_count
self.last_capacity_pull_time = 0
end
function BasePowerD:isCharging()

@ -5,8 +5,6 @@ local KindlePowerD = BasePowerD:new{
fl_min = 0, fl_max = 24,
fl_intensity = nil,
battCapacity = nil,
is_charging = nil,
lipc_handle = nil,
is_fl_on = false,
@ -55,20 +53,20 @@ end
function KindlePowerD:getCapacityHW()
if self.lipc_handle ~= nil then
self.battCapacity = self.lipc_handle:get_int_property("com.lab126.powerd", "battLevel")
return self.lipc_handle:get_int_property("com.lab126.powerd", "battLevel")
else
self.battCapacity = self:read_int_file(self.batt_capacity_file)
return self:read_int_file(self.batt_capacity_file)
end
return self.battCapacity
end
function KindlePowerD:isChargingHW()
local is_charging
if self.lipc_handle ~= nil then
self.is_charging = self.lipc_handle:get_int_property("com.lab126.powerd", "isCharging")
is_charging = self.lipc_handle:get_int_property("com.lab126.powerd", "isCharging")
else
self.is_charging = self:read_int_file(self.is_charging_file)
is_charging = self:read_int_file(self.is_charging_file)
end
return self.is_charging == 1
return is_charging == 1
end
function KindlePowerD:__gc()

@ -19,8 +19,6 @@ local KoboPowerD = BasePowerD:new{
batt_capacity_file = batt_state_folder .. "capacity",
is_charging_file = batt_state_folder .. "status",
battCapacity = nil,
is_charging = nil,
}
function KoboPowerD:init()
@ -77,13 +75,11 @@ function KoboPowerD:setIntensityHW()
end
function KoboPowerD:getCapacityHW()
self.battCapacity = self:read_int_file(self.batt_capacity_file)
return self.battCapacity
return self:read_int_file(self.batt_capacity_file)
end
function KoboPowerD:isChargingHW()
self.is_charging = self:read_str_file(self.is_charging_file) == "Charging\n"
return self.is_charging
return self:read_str_file(self.is_charging_file) == "Charging\n"
end
-- Turn off front light before suspend.

@ -7,7 +7,6 @@ int IsCharging();
]]
local PocketBookPowerD = BasePowerD:new{
battCapacity = nil,
is_charging = nil,
batt_capacity_file = "/sys/devices/platform/sun5i-i2c.0/i2c-0/0-0034/axp20-supplyer.28/power_supply/battery/capacity",
is_charging_file = "/sys/devices/platform/sun5i-i2c.0/i2c-0/0-0034/axp20-supplyer.28/power_supply/battery/status",
@ -17,8 +16,7 @@ function PocketBookPowerD:init()
end
function PocketBookPowerD:getCapacityHW()
self.battCapacity = self:read_int_file(self.batt_capacity_file)
return self.battCapacity
return self:read_int_file(self.batt_capacity_file)
end
function PocketBookPowerD:isChargingHW()

@ -2,7 +2,6 @@ local lfs = require("libs/libkoreader-lfs")
local DataStorage = require("datastorage")
local dump = require("dump")
local purgeDir = require("ffi/util").purgeDir
local logger = require("logger")
local DocSettings = {}
@ -23,9 +22,6 @@ function DocSettings:getSidecarDir(doc_path)
if file_without_suffix then
return file_without_suffix..".sdr"
end
-- We shouldn't be called with anything but files with registered
-- extensions, but in case we are, return something useful
logger.err("getSidecarFile called with unexpected path:", doc_path)
return doc_path..".sdr"
end

@ -459,7 +459,9 @@ end
function CreDocument:register(registry)
registry:addProvider("txt", "application/txt", self)
registry:addProvider("log", "application/txt", self)
registry:addProvider("txt.zip", "application/zip", self)
registry:addProvider("log.zip", "application/zip", self)
registry:addProvider("epub", "application/epub", self)
registry:addProvider("fb2", "application/fb2", self)
registry:addProvider("fb2.zip", "application/zip", self)

@ -60,6 +60,10 @@ function LuaSettings:flipTrue(key)
end
end
function LuaSettings:reset(table)
self.data = table
end
function LuaSettings:flush()
local f_out = io.open(self.file, "w")
if f_out ~= nil then

@ -22,13 +22,14 @@ function PluginLoader:loadPlugins()
local ok, plugin_module = pcall(dofile, mainfile)
if not ok or not plugin_module then
logger.warn("Error when loading", mainfile, plugin_module)
end
if ok and plugin_module and not plugin_module.disabled then
elseif type(plugin_module.disabled) ~= "boolean" or not plugin_module.disabled then
package.path = package_path
package.cpath = package_cpath
plugin_module.path = path
plugin_module.name = plugin_module.name or path:match("/(.-)%.koplugin")
table.insert(self.plugins, plugin_module)
else
logger.info("Plugin ", mainfile, " has been disabled.")
end
end
end

@ -60,11 +60,12 @@ function UIManager:init()
self:_initAutoSuspend()
self.event_handlers["Suspend"] = function()
self:_stopAutoSuspend()
self:broadcastEvent(Event:new("Suspend"))
Device:onPowerEvent("Suspend")
end
self.event_handlers["Resume"] = function()
Device:onPowerEvent("Resume")
self:sendEvent(Event:new("Resume"))
self:broadcastEvent(Event:new("Resume"))
self:_startAutoSuspend()
end
self.event_handlers["PowerPress"] = function()
@ -99,11 +100,17 @@ function UIManager:init()
Device:getPowerDevice():toggleFrontlight()
end
self.event_handlers["Charging"] = function()
self:broadcastEvent(Event:new("Charging"))
if Device.screen_saver_mode then
self.event_handlers["Suspend"]()
end
end
self.event_handlers["NotCharging"] = function()
self:broadcastEvent(Event:new("NotCharging"))
if Device.screen_saver_mode then
self.event_handlers["Suspend"]()
end
end
self.event_handlers["NotCharging"] = self.event_handlers["Charging"]
self.event_handlers["__default__"] = function(input_event)
if Device.screen_saver_mode then
-- Suspension in Kobo can be interrupted by screen updates. We
@ -116,18 +123,20 @@ function UIManager:init()
end
elseif Device:isKindle() then
self.event_handlers["IntoSS"] = function()
self:broadcastEvent(Event:new("Suspend"))
Device:intoScreenSaver()
end
self.event_handlers["OutOfSS"] = function()
Device:outofScreenSaver()
self:sendEvent(Event:new("Resume"))
self:broadcastEvent(Event:new("Resume"))
end
self.event_handlers["Charging"] = function()
self:broadcastEvent(Event:new("Charging"))
Device:usbPlugIn()
end
self.event_handlers["NotCharging"] = function()
Device:usbPlugOut()
self:sendEvent(Event:new("NotCharging"))
self:broadcastEvent(Event:new("NotCharging"))
end
end
end
@ -728,7 +737,7 @@ function UIManager:_initAutoSuspend()
local now = util.gettime()
-- Do not repeat auto suspend procedure after suspend.
if self.last_action_sec + self.auto_suspend_sec <= now then
Device:onPowerEvent("Suspend")
self.event_handlers["Suspend"]()
else
self:scheduleIn(
self.last_action_sec + self.auto_suspend_sec - now,

@ -0,0 +1,283 @@
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 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 = {
name = "batterstat",
settings = LuaSettings:open(DataStorage:getSettingsDir() .. "/batterstat.lua"),
dump_file = util.realpath(DataStorage:getDataDir()) .. "/batterystat.log",
debugging = false,
}
function BatteryStat:init()
self.charging = Usage:new(self.settings:readSetting("charging"))
self.decharging = Usage:new(self.settings:readSetting("decharging"))
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,
decharging = self.decharging,
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.decharging: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:dumpToText()
self.charging = Usage:new()
self.awake = Usage:new()
self.sleeping = Usage:new()
self:accumulate()
end
function BatteryStat:onNotCharging()
self:debugOutput("onNotCharging")
self.was_charging = true
self:dumpToText()
self.decharging = Usage:new()
self.awake = Usage:new()
self.sleeping = Usage:new()
self:accumulate()
end
function BatteryStat:onCallback()
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, ""})
UIManager:show(KeyValuePage:new{
title = _("Battery statistics"),
kv_pairs = kv_pairs,
})
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.decharging:dump(kv_pairs)
self.decharging:dumpRemaining(kv_pairs)
return kv_pairs
end
BatteryStat:init()
local BatteryStatWidget = WidgetContainer:new()
function BatteryStatWidget:init()
self.ui.menu:registerToMainMenu(self)
end
function BatteryStatWidget:addToMainMenu(tab_item_table)
table.insert(tab_item_table.plugins, {
text = _("Battery statistics"),
callback = function()
BatteryStat:onCallback()
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

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

@ -1,20 +1,24 @@
local Device = require("device")
if not ((Device:isKindle() or Device:isKobo()) and Device:hasFrontlight()) then
local with_frontlight = (Device:isKindle() or Device:isKobo()) and Device:hasFrontlight()
if not (with_frontlight or Device:isSDL()) then
return { disabled = true, }
end
local WidgetContainer = require("ui/widget/container/widgetcontainer")
local Screen = Device.screen
local UIManager = require("ui/uimanager")
local ConfirmBox = require("ui/widget/confirmbox")
local ImageWidget = require("ui/widget/imagewidget")
local InfoMessage = require("ui/widget/infomessage")
local Notification = require("ui/widget/notification")
local PluginLoader = require("pluginloader")
local Screen = require("device").screen
local UIManager = require("ui/uimanager")
local WidgetContainer = require("ui/widget/container/widgetcontainer")
local T = require("ffi/util").template
local _ = require("gettext")
local tap_touch_zone_ratio = { x = 0, y = 15/16, w = 1/10, h = 1/16, }
local swipe_touch_zone_ratio = { x = 0, y = 1/8, w = 1/10, h = 7/8, }
local KoboLight = WidgetContainer:new{
name = 'kobolight',
gestureScale = nil, -- initialized in self:resetLayout()
@ -28,6 +32,8 @@ function KoboLight:init()
do
self.steps[i] = math.ceil(self.steps[i] * scale)
end
self.ui.menu:registerToMainMenu(self)
end
function KoboLight:onReaderReady()
@ -35,8 +41,13 @@ function KoboLight:onReaderReady()
self:resetLayout()
end
function KoboLight:disabled()
return G_reader_settings:isTrue("disable_kobolight")
end
function KoboLight:setupTouchZones()
if not Device:isTouchDevice() then return end
if self:disabled() then return end
local swipe_zone = {
ratio_x = swipe_touch_zone_ratio.x, ratio_y = swipe_touch_zone_ratio.y,
ratio_w = swipe_touch_zone_ratio.w, ratio_h = swipe_touch_zone_ratio.h,
@ -148,4 +159,39 @@ function KoboLight:onSwipe(_, ges)
return true
end
function KoboLight:addToMainMenu(tab_item_table)
table.insert(tab_item_table.plugins, {
text = _("Frontlight gesture controller"),
callback = function()
local image = ImageWidget:new{
file = PluginLoader.plugin_path .. "/kobolight.koplugin/demo.png",
height = Screen:getHeight(),
width = Screen:getWidth(),
scale_factor = 0,
}
UIManager:show(image)
UIManager:show(ConfirmBox:new{
text = T(_("Frontlight gesture controller can:\n- Turn on or off frontlight by tapping bottom left of the screen.\n- Change frontlight intensity by swiping up or down on the left of the screen.\n\nDo you want to %1 it?"),
self:disabled() and _("enable") or _("disable")),
ok_text = self:disabled() and _("Enable") or _("Disable"),
ok_callback = function()
UIManager:close(image)
UIManager:setDirty("all", "full")
UIManager:show(InfoMessage:new{
text = T(_("You have %1 the frontlight gesture controller. It will take effect on next restart."),
self:disabled() and _("enabled") or _("disabled"))
})
G_reader_settings:flipTrue("disable_kobolight")
end,
cancel_text = _("Close"),
cancel_callback = function()
UIManager:close(image)
UIManager:setDirty("all", "full")
end,
})
UIManager:setDirty("all", "full")
end,
})
end
return KoboLight

Loading…
Cancel
Save