Autosuspend: add autostandby (#8815)

Allows the device to go into standby (if available in `/sys/power/state`) to save power.
Adds an entry in the device menu to tune the timeout for standby.
(Shows total standby- and suspend-time in system statistics.)
reviewable/pr8958/r1
zwim 2 years ago committed by GitHub
parent 8c2c68d9c6
commit 158f4be724
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -147,6 +147,11 @@ function ReaderCoptListener:onResume()
self:headerRefresh()
end
function ReaderCoptListener:onLeaveStandby()
self:onResume()
self:onOutOfScreenSaver()
end
function ReaderCoptListener:onOutOfScreenSaver()
if not self._delayed_screensaver then
return
@ -159,6 +164,7 @@ end
-- Unschedule on these events
ReaderCoptListener.onCloseDocument = ReaderCoptListener.unscheduleHeaderRefresh
ReaderCoptListener.onSuspend = ReaderCoptListener.unscheduleHeaderRefresh
ReaderCoptListener.onEnterStandby = ReaderCoptListener.unscheduleHeaderRefresh
function ReaderCoptListener:setAndSave(setting, property, value)
self.ui.document._document:setIntProperty(property, value)

@ -2432,10 +2432,17 @@ function ReaderFooter:onOutOfScreenSaver()
self:rescheduleFooterAutoRefreshIfNeeded()
end
function ReaderFooter:onLeaveStandby()
self:onResume()
self:onOutOfScreenSaver()
end
function ReaderFooter:onSuspend()
self:unscheduleFooterAutoRefresh()
end
ReaderFooter.onEnterStandby = ReaderFooter.onSuspend
function ReaderFooter:onCloseDocument()
self:unscheduleFooterAutoRefresh()
end

@ -6,6 +6,7 @@ This module defines stubs for common methods.
local DataStorage = require("datastorage")
local Geom = require("ui/geometry")
local TimeVal = require("ui/timeval")
local logger = require("logger")
local util = require("util")
local _ = require("gettext")
@ -67,6 +68,12 @@ local Device = {
canUseWAL = yes, -- requires mmap'ed I/O on the target FS
canRestart = yes,
canSuspend = yes,
canStandby = no,
canPowerSaveWhileCharging = no,
total_standby_tv = TimeVal.zero, -- total time spent in standby
last_standby_tv = TimeVal.zero,
total_suspend_tv = TimeVal.zero, -- total time spent in suspend
last_suspend_tv = TimeVal.zero,
canReboot = no,
canPowerOff = no,
canAssociateFileExtensions = no,
@ -238,7 +245,7 @@ end
function Device:rescheduleSuspend()
local UIManager = require("ui/uimanager")
UIManager:unschedule(self.suspend)
UIManager:scheduleIn(self.suspend_wait_timeout, self.suspend)
UIManager:scheduleIn(self.suspend_wait_timeout, self.suspend, self)
end
-- Only used on platforms where we handle suspend ourselves.
@ -430,6 +437,9 @@ function Device:saveSettings() end
function Device:simulateSuspend() end
function Device:simulateResume() end
-- Put device into standby, input devices (buttons, touchscreen ...) stay enabled
function Device:standby(max_duration) end
--[[--
Device specific method for performing haptic feedback.

@ -24,6 +24,36 @@ local function koboEnableWifi(toggle)
end
end
-- checks if standby is available on the device
local function checkStandby()
logger.dbg("Kobo: checking if standby is possible ...")
local f = io.open("/sys/power/state")
if not f then
return no
end
local mode = f:read()
logger.dbg("Kobo: available power states", mode)
if mode:find("standby") then
logger.dbg("Kobo: standby state allowed")
return yes
end
logger.dbg("Kobo: standby state not allowed")
return no
end
local function writeToSys(val, file)
local f = io.open(file, "we")
if not f then
logger.err("Cannot open:", file)
return
end
local re, err_msg, err_code = f:write(val, "\n")
if not re then
logger.err("Error writing value to file:", val, file, err_msg, err_code)
end
f:close()
return re
end
local Kobo = Generic:new{
model = "Kobo",
@ -32,9 +62,9 @@ local Kobo = Generic:new{
hasOTAUpdates = yes,
hasFastWifiStatusQuery = yes,
hasWifiManager = yes,
canStandby = no, -- will get updated by checkStandby()
canReboot = yes,
canPowerOff = yes,
-- most Kobos have X/Y switched for the touch screen
touch_switch_xy = true,
-- most Kobos have also mirrored X coordinates
@ -75,6 +105,8 @@ local Kobo = Generic:new{
isSMP = no,
-- Device supports "eclipse" waveform modes (i.e., optimized for nightmode).
hasEclipseWfm = no,
unexpected_wakeup_count = 0
}
-- Kobo Touch:
@ -517,6 +549,11 @@ function Kobo:init()
-- Only enable a single core on startup
self:enableCPUCores(1)
self.canStandby = checkStandby()
if self.canStandby() and (self:isMk7() or self:isSunxi()) then
self.canPowerSaveWhileCharging = yes
end
end
function Kobo:setDateTime(year, month, day, hour, min, sec)
@ -684,8 +721,7 @@ local function getProductId()
return product_id
end
local unexpected_wakeup_count = 0
local function check_unexpected_wakeup()
function Kobo:checkUnexpectedWakeup()
local UIManager = require("ui/uimanager")
-- just in case other events like SleepCoverClosed also scheduled a suspend
UIManager:unschedule(Kobo.suspend)
@ -700,11 +736,11 @@ local function check_unexpected_wakeup()
logger.info("Kobo suspend: putting device back to sleep.")
-- Most wakeup actions are linear, but we need some leeway for the
-- poweroff action to send out close events to all requisite widgets.
UIManager:scheduleIn(30, Kobo.suspend)
UIManager:scheduleIn(30, Kobo.suspend, self)
else
logger.dbg("Kobo suspend: checking unexpected wakeup:",
unexpected_wakeup_count)
if unexpected_wakeup_count == 0 or unexpected_wakeup_count > 20 then
self.unexpected_wakeup_count)
if self.unexpected_wakeup_count == 0 or self.unexpected_wakeup_count > 20 then
-- Don't put device back to sleep under the following two cases:
-- 1. a resume event triggered Kobo:resume() function
-- 2. trying to put device back to sleep more than 20 times after unexpected wakeup
@ -715,17 +751,42 @@ local function check_unexpected_wakeup()
end
logger.err("Kobo suspend: putting device back to sleep. Unexpected wakeups:",
unexpected_wakeup_count)
Kobo.suspend()
self.unexpected_wakeup_count)
Kobo:suspend()
end
end
function Kobo:getUnexpectedWakeup() return unexpected_wakeup_count end
function Kobo:getUnexpectedWakeup() return self.unexpected_wakeup_count end
--- The function to put the device into standby, with enabled touchscreen.
-- max_duration ... maximum time for the next standby, can wake earlier (e.g. Tap, Button ...)
function Kobo:standby(max_duration)
-- just for wake up, dummy function
local function dummy() end
if max_duration then
self.wakeup_mgr:addTask(max_duration, dummy)
end
local TimeVal = require("ui/timeval")
local standby_time_tv = TimeVal:boottime_or_realtime_coarse()
local ret = writeToSys("standby", "/sys/power/state")
self.last_standby_tv = TimeVal:boottime_or_realtime_coarse() - standby_time_tv
self.total_standby_tv = self.total_standby_tv + self.last_standby_tv
logger.info("Kobo suspend: asked the kernel to put subsystems to standby, ret:", ret)
if max_duration then
self.wakeup_mgr:removeTask(nil, nil, dummy)
end
end
function Kobo:suspend()
logger.info("Kobo suspend: going to sleep . . .")
local UIManager = require("ui/uimanager")
UIManager:unschedule(check_unexpected_wakeup)
UIManager:unschedule(self.checkUnexpectedWakeup)
local f, re, err_msg, err_code
-- NOTE: Sleep as little as possible here, sleeping has a tendency to make
-- everything mysteriously hang...
@ -762,17 +823,8 @@ function Kobo:suspend()
-- NOTE: Sets gSleep_Mode_Suspend to 1. Used as a flag throughout the
-- kernel to suspend/resume various subsystems
-- cf. kernel/power/main.c @ L#207
f = io.open("/sys/power/state-extended", "we")
if not f then
logger.err("Cannot open /sys/power/state-extended for writing!")
return false
end
re, err_msg, err_code = f:write("1\n")
f:close()
logger.info("Kobo suspend: asked the kernel to put subsystems to sleep, ret:", re)
if not re then
logger.err('write error: ', err_msg, err_code)
end
local ret = writeToSys("1", "/sys/power/state-extended")
logger.info("Kobo suspend: asked the kernel to put subsystems to sleep, ret:", ret)
util.sleep(2)
logger.info("Kobo suspend: waited for 2s because of reasons...")
@ -813,15 +865,23 @@ function Kobo:suspend()
end
return false
end
local TimeVal = require("ui/timeval")
local suspend_time_tv = TimeVal:boottime_or_realtime_coarse()
re, err_msg, err_code = f:write("mem\n")
if not re then
logger.err("write error: ", err_msg, err_code)
end
f:close()
-- NOTE: At this point, we *should* be in suspend to RAM, as such,
-- execution should only resume on wakeup...
self.last_suspend_tv = TimeVal:boottime_or_realtime_coarse() - suspend_time_tv
self.total_suspend_tv = self.total_suspend_tv + self.last_suspend_tv
logger.info("Kobo suspend: ZzZ ZzZ ZzZ? Write syscall returned: ", re)
if not re then
logger.err('write error: ', err_msg, err_code)
end
f:close()
-- NOTE: Ideally, we'd need a way to warn the user that suspending
-- gloriously failed at this point...
-- We can safely assume that just from a non-zero return code, without
@ -849,44 +909,32 @@ function Kobo:suspend()
-- things tidy and easier to follow
-- Kobo:resume() will reset unexpected_wakeup_count = 0 to signal an
-- expected wakeup, which gets checked in check_unexpected_wakeup().
unexpected_wakeup_count = unexpected_wakeup_count + 1
-- expected wakeup, which gets checked in checkUnexpectedWakeup().
self.unexpected_wakeup_count = self.unexpected_wakeup_count + 1
-- assuming Kobo:resume() will be called in 15 seconds
logger.dbg("Kobo suspend: scheduling unexpected wakeup guard")
UIManager:scheduleIn(15, check_unexpected_wakeup)
UIManager:scheduleIn(15, self.checkUnexpectedWakeup, self)
end
function Kobo:resume()
logger.info("Kobo resume: clean up after wakeup")
-- reset unexpected_wakeup_count ASAP
unexpected_wakeup_count = 0
require("ui/uimanager"):unschedule(check_unexpected_wakeup)
self.unexpected_wakeup_count = 0
require("ui/uimanager"):unschedule(self.checkUnexpectedWakeup)
-- Now that we're up, unflag subsystems for suspend...
-- NOTE: Sets gSleep_Mode_Suspend to 0. Used as a flag throughout the
-- kernel to suspend/resume various subsystems
-- cf. kernel/power/main.c @ L#207
local f = io.open("/sys/power/state-extended", "we")
if not f then
logger.err("cannot open /sys/power/state-extended for writing!")
return false
end
local re, err_msg, err_code = f:write("0\n")
f:close()
logger.info("Kobo resume: unflagged kernel subsystems for resume, ret:", re)
if not re then
logger.err('write error: ', err_msg, err_code)
end
local ret = writeToSys("0", "/sys/power/state-extended")
logger.info("Kobo resume: unflagged kernel subsystems for resume, ret:", ret)
-- HACK: wait a bit (0.1 sec) for the kernel to catch up
util.usleep(100000)
-- cf. #1862, I can reliably break IR touch input on resume...
-- cf. also #1943 for the rationale behind applying this workaorund in every case...
f = io.open("/sys/devices/virtual/input/input1/neocmd", "we")
if f ~= nil then
f:write("a\n")
f:close()
end
writeToSys("a", "/sys/devices/virtual/input/input1/neocmd")
-- A full suspend may have toggled the LED off.
self:setupChargingLED()

@ -66,6 +66,7 @@ local Device = Generic:new{
hasEinkScreen = no,
hasSystemFonts = yes,
canSuspend = no,
canStandby = yes,
startTextInput = SDL.startTextInput,
stopTextInput = SDL.stopTextInput,
canOpenLink = getLinkOpener,

@ -1179,6 +1179,15 @@ function UIManager:broadcastEvent(event)
end
end
function UIManager:getNextTaskTimes(count)
count = count or 1
local times = {}
for i = 1, math.min(count, #self._task_queue) do
times[i] = UIManager._task_queue[i].time - TimeVal:now()
end
return times
end
function UIManager:_checkTasks()
self._now = TimeVal:now()
local wait_until = nil

@ -1,6 +1,6 @@
local _ = require("gettext")
return {
name = "autosuspend",
fullname = _("Auto suspend"),
description = _([[Suspends the device after a period of inactivity.]]),
fullname = _("Auto power save"),
description = _([["Puts the device into standby, suspend or power off after specified periods of inactivity."]]),
}

@ -10,6 +10,8 @@ if not Device:isCervantes() and
return { disabled = true, }
end
local Event = require("ui/event")
local NetworkMgr = require("ui/network/manager")
local PluginShare = require("pluginshare")
local TimeVal = require("ui/timeval")
local UIManager = require("ui/uimanager")
@ -22,17 +24,23 @@ local T = require("ffi/util").template
local default_autoshutdown_timeout_seconds = 3*24*60*60 -- three days
local default_auto_suspend_timeout_seconds = 15*60 -- 15 minutes
local default_auto_standby_timeout_seconds = 4 -- 4 seconds; should be safe on Kobo/Sage
local AutoSuspend = WidgetContainer:new{
name = "autosuspend",
is_doc_only = false,
autoshutdown_timeout_seconds = default_autoshutdown_timeout_seconds,
auto_suspend_timeout_seconds = default_auto_suspend_timeout_seconds,
auto_standby_timeout_seconds = default_auto_standby_timeout_seconds,
last_action_tv = TimeVal.zero,
standby_prevented = false,
is_standby_scheduled = nil,
task = nil,
}
function AutoSuspend:_enabledStandby()
return Device:canStandby() and self.auto_standby_timeout_seconds > 0
end
function AutoSuspend:_enabled()
return self.auto_suspend_timeout_seconds > 0
end
@ -49,15 +57,13 @@ function AutoSuspend:_schedule(shutdown_only)
local delay_suspend, delay_shutdown
if PluginShare.pause_auto_suspend or Device.standby_prevented or Device.powerd:isCharging() then
if PluginShare.pause_auto_suspend or Device.powerd:isCharging() then
delay_suspend = self.auto_suspend_timeout_seconds
delay_shutdown = self.autoshutdown_timeout_seconds
else
local now_tv = UIManager:getTime()
delay_suspend = self.last_action_tv + TimeVal:new{ sec = self.auto_suspend_timeout_seconds, usec = 0 } - now_tv
delay_suspend = delay_suspend:tonumber()
delay_shutdown = self.last_action_tv + TimeVal:new{ sec = self.autoshutdown_timeout_seconds, usec = 0 } - now_tv
delay_shutdown = delay_shutdown:tonumber()
local now_tv = UIManager:getTime() + Device.total_standby_tv
delay_suspend = (self.last_action_tv - now_tv):tonumber() + self.auto_suspend_timeout_seconds
delay_shutdown = (self.last_action_tv - now_tv):tonumber() + self.autoshutdown_timeout_seconds
end
-- Try to shutdown first, as we may have been woken up from suspend just for the sole purpose of doing that.
@ -88,9 +94,8 @@ end
function AutoSuspend:_start()
if self:_enabled() or self:_enabledShutdown() then
local now_tv = UIManager:getTime()
logger.dbg("AutoSuspend: start at", now_tv:tonumber())
self.last_action_tv = now_tv
self.last_action_tv = UIManager:getTime() + Device.total_standby_tv
logger.dbg("AutoSuspend: start at", self.last_action_tv:tonumber())
self:_schedule()
end
end
@ -98,9 +103,8 @@ end
-- Variant that only re-engages the shutdown timer for onUnexpectedWakeupLimit
function AutoSuspend:_restart()
if self:_enabledShutdown() then
local now_tv = UIManager:getTime()
logger.dbg("AutoSuspend: restart at", now_tv:tonumber())
self.last_action_tv = now_tv
self.last_action_tv = UIManager:getTime() + Device.total_standby_tv
logger.dbg("AutoSuspend: restart at", self.last_action_tv:tonumber())
self:_schedule(true)
end
end
@ -113,6 +117,9 @@ function AutoSuspend:init()
self.auto_suspend_timeout_seconds = G_reader_settings:readSetting("auto_suspend_timeout_seconds",
default_auto_suspend_timeout_seconds)
-- Disabled, until the user opts in.
self.auto_standby_timeout_seconds = G_reader_settings:readSetting("auto_standby_timeout_seconds", -1)
UIManager.event_hook:registerWidget("InputEvent", self)
-- We need an instance-specific function reference to schedule, because in some rare cases,
-- we may instantiate a new plugin instance *before* tearing down the old one.
@ -120,6 +127,8 @@ function AutoSuspend:init()
self:_schedule(shutdown_only)
end
self:_start()
self:_reschedule_standby()
-- self.ui is nil in the testsuite
if not self.ui or not self.ui.menu then return end
self.ui.menu:registerToMainMenu(self)
@ -131,11 +140,55 @@ function AutoSuspend:onCloseWidget()
if Device:isPocketBook() and not Device:canSuspend() then return end
self:_unschedule()
self.task = nil
self:_unschedule_standby()
-- allowStandby is necessary, as we do a preventStandby on plugin start
UIManager:allowStandby()
end
function AutoSuspend:onInputEvent()
logger.dbg("AutoSuspend: onInputEvent")
self.last_action_tv = UIManager:getTime()
self.last_action_tv = UIManager:getTime() + Device.total_standby_tv
self:_reschedule_standby()
end
function AutoSuspend:_unschedule_standby()
UIManager:unschedule(AutoSuspend.allowStandby)
end
function AutoSuspend:_reschedule_standby(standby_timeout)
if not Device:canStandby() then return end
standby_timeout = standby_timeout or self.auto_standby_timeout_seconds
self:_unschedule_standby()
if standby_timeout < 1 then
return
end
self:preventStandby()
logger.dbg("AutoSuspend: schedule autoStandby in", standby_timeout) -- xxx may be deleted later
UIManager:scheduleIn(standby_timeout, self.allowStandby, self)
end
function AutoSuspend:preventStandby()
if self.is_standby_scheduled ~= false then
self.is_standby_scheduled = false
UIManager:preventStandby()
end
end
function AutoSuspend:allowStandby()
if not self.is_standby_scheduled then
self.is_standby_scheduled = true
UIManager:allowStandby()
-- This is necessary for wakeup from standby, as the deadline for receiving input events
-- is calculated from the time to the next scheduled function.
-- Make sure this function comes soon, as the time for going to standby after a scheduled wakeup
-- is prolonged by the given time. Any time between 0.500 and 0.001 seconds would go.
-- Let's call it deadline_guard.
UIManager:scheduleIn(0.100, function() end)
end
end
function AutoSuspend:onSuspend()
@ -143,6 +196,7 @@ function AutoSuspend:onSuspend()
-- We do not want auto suspend procedure to waste battery during suspend. So let's unschedule it
-- when suspending and restart it after resume.
self:_unschedule()
self:_unschedule_standby()
if self:_enabledShutdown() and Device.wakeup_mgr then
Device.wakeup_mgr:addTask(self.autoshutdown_timeout_seconds, UIManager.poweroff_action)
end
@ -156,6 +210,11 @@ function AutoSuspend:onResume()
-- Unschedule in case we tripped onUnexpectedWakeupLimit first...
self:_unschedule()
self:_start()
self:_reschedule_standby()
end
function AutoSuspend:onLeaveStandby()
self:_reschedule_standby()
end
function AutoSuspend:onUnexpectedWakeupLimit()
@ -164,16 +223,12 @@ function AutoSuspend:onUnexpectedWakeupLimit()
self:_restart()
end
function AutoSuspend:onAllowStandby()
self.standby_prevented = false
end
function AutoSuspend:onPreventStandby()
self.standby_prevented = true
end
-- time_scale:
-- 2 ... display day:hour
-- 1 ... display hour:min
-- else ... display min:sec
function AutoSuspend:setSuspendShutdownTimes(touchmenu_instance, title, info, setting,
default_value, range, is_day_hour)
default_value, range, time_scale)
-- Attention if is_day_hour then time.hour stands for days and time.min for hours
local InfoMessage = require("ui/widget/infomessage")
@ -181,10 +236,23 @@ function AutoSuspend:setSuspendShutdownTimes(touchmenu_instance, title, info, se
local setting_val = self[setting] > 0 and self[setting] or default_value
local left_val = is_day_hour and math.floor(setting_val / (24*3600))
or math.floor(setting_val / 3600)
local right_val = is_day_hour and math.floor(setting_val / 3600) % 24
or math.floor((setting_val / 60) % 60)
local left_val
if time_scale == 2 then
left_val = math.floor(setting_val / (24*3600))
elseif time_scale == 1 then
left_val = math.floor(setting_val / 3600)
else
left_val = math.floor(setting_val / 60)
end
local right_val
if time_scale == 2 then
right_val = math.floor(setting_val / 3600) % 24
elseif time_scale == 1 then
right_val = math.floor(setting_val / 60) % 60
else
right_val = math.floor(setting_val) % 60
end
local time_spinner
time_spinner = DateTimeWidget:new {
is_date = false,
@ -192,36 +260,57 @@ function AutoSuspend:setSuspendShutdownTimes(touchmenu_instance, title, info, se
min = right_val,
hour_hold_step = 5,
min_hold_step = 10,
hour_max = is_day_hour and math.floor(range[2] / (24*3600)) or math.floor(range[2] / 3600),
min_max = is_day_hour and 23 or 59,
hour_max = (time_scale == 2 and math.floor(range[2] / (24*3600)))
or (time_scale == 1 and math.floor(range[2] / 3600))
or math.floor(range[2] / 60),
min_max = (time_scale == 2 and 23) or 59,
ok_text = _("Set timeout"),
title_text = title,
info_text = info,
callback = function(time)
self[setting] = is_day_hour and (time.hour * 24 * 3600 + time.min * 3600)
or (time.hour * 3600 + time.min * 60)
if time_scale == 2 then
self[setting] = (time.hour * 24 + time.min) * 3600
elseif time_scale == 1 then
self[setting] = time.hour * 3600 + time.min * 60
else
self[setting] = time.hour * 60 + time.min
end
self[setting] = Math.clamp(self[setting], range[1], range[2])
G_reader_settings:saveSetting(setting, self[setting])
self:_unschedule()
self:_start()
if touchmenu_instance then touchmenu_instance:updateItems() end
local time_string = util.secondsToClockDuration("modern", self[setting], true, true, true)
time_string = time_string:gsub("00m","")
local time_string = util.secondsToClockDuration("modern", self[setting],
time_scale == 2 or time_scale == 1, true, true)
time_string = time_string:gsub("00m$", ""):gsub("^0+m", ""):gsub("^0", "")
UIManager:show(InfoMessage:new{
text = T(_("%1: %2"), title, time_string),
timeout = 3,
})
end,
default_value = util.secondsToClockDuration("modern", default_value, true, true, true):gsub("00m$",""),
default_value = util.secondsToClockDuration("modern", default_value,
time_scale == 2 or time_scale == 1, true, true):gsub("00m$", ""):gsub("^00m:", ""),
default_callback = function()
local hour = is_day_hour and math.floor(default_value / (24*3600))
or math.floor(default_value / 3600)
local min = is_day_hour and math.floor(default_value / 3600) % 24
or math.floor(default_value / 60) % 60
local hour
if time_scale == 2 then
hour = math.floor(default_value / (24*3600))
elseif time_scale == 1 then
hour = math.floor(default_value / 3600)
else
hour = math.floor(default_value / 60)
end
local min
if time_scale == 2 then
min = math.floor(default_value / 3600) % 24
elseif time_scale == 1 then
min = math.floor(default_value / 60) % 60
else
min = math.floor(default_value % 60)
end
time_spinner:update(nil, hour, min)
end,
extra_text = _("Disable"),
extra_callback = function(_self)
extra_callback = function(this)
self[setting] = -1 -- disable with a negative time/number
G_reader_settings:saveSetting(setting, -1)
self:_unschedule()
@ -230,7 +319,7 @@ function AutoSuspend:setSuspendShutdownTimes(touchmenu_instance, title, info, se
text = T(_("%1: disabled"), title),
timeout = 3,
})
_self:onClose()
this:onClose()
end,
keep_shown_on_apply = true,
}
@ -246,7 +335,7 @@ function AutoSuspend:addToMainMenu(menu_items)
text_func = function()
if self.auto_suspend_timeout_seconds and self.auto_suspend_timeout_seconds > 0 then
local time_string = util.secondsToClockDuration("modern",
self.auto_suspend_timeout_seconds, true, true, true):gsub("00m$","")
self.auto_suspend_timeout_seconds, true, true, true):gsub("00m$", ""):gsub("^00m:", "")
return T(_("Autosuspend timeout: %1"), time_string)
else
return _("Autosuspend timeout")
@ -260,36 +349,122 @@ function AutoSuspend:addToMainMenu(menu_items)
self:setSuspendShutdownTimes(touchmenu_instance,
_("Timeout for autosuspend"), _("Enter time in hours and minutes."),
"auto_suspend_timeout_seconds", default_auto_suspend_timeout_seconds,
{60, 24*3600}, false)
end,
}
if not (Device:canPowerOff() or Device:isEmulator()) then return end
menu_items.autoshutdown = {
sorting_hint = "device",
checked_func = function()
return self:_enabledShutdown()
end,
text_func = function()
if self.autoshutdown_timeout_seconds and self.autoshutdown_timeout_seconds > 0 then
local time_string = util.secondsToClockDuration("modern",
self.autoshutdown_timeout_seconds, true, true, true):gsub("00m$","")
return T(_("Autoshutdown timeout: %1"), time_string)
else
return _("Autoshutdown timeout")
end
end,
keep_menu_open = true,
callback = function(touchmenu_instance)
-- 5*60 sec (5') is the minimum and 28*24*3600 (28days) is the maximum shutdown time.
-- Minimum time has to be big enough, to avoid start-stop death scenarious.
-- Maximum more than four weeks seems a bit excessive if you want to enable authoshutdown,
-- even if the battery can last up to three months.
self:setSuspendShutdownTimes(touchmenu_instance,
_("Timeout for autoshutdown"), _("Enter time in days and hours."),
"autoshutdown_timeout_seconds", default_autoshutdown_timeout_seconds,
{5*60, 28*24*3600}, true)
{60, 24*3600}, 1)
end,
}
if Device:canPowerOff() or Device:isEmulator() then
menu_items.autoshutdown = {
sorting_hint = "device",
checked_func = function()
return self:_enabledShutdown()
end,
text_func = function()
if self.autoshutdown_timeout_seconds and self.autoshutdown_timeout_seconds > 0 then
local time_string = util.secondsToClockDuration("modern", self.autoshutdown_timeout_seconds,
true, true, true):gsub("00m$", ""):gsub("^00m:", "")
return T(_("Autoshutdown timeout: %1"), time_string)
else
return _("Autoshutdown timeout")
end
end,
keep_menu_open = true,
callback = function(touchmenu_instance)
-- 5*60 sec (5') is the minimum and 28*24*3600 (28days) is the maximum shutdown time.
-- Minimum time has to be big enough, to avoid start-stop death scenarious.
-- Maximum more than four weeks seems a bit excessive if you want to enable authoshutdown,
-- even if the battery can last up to three months.
self:setSuspendShutdownTimes(touchmenu_instance,
_("Timeout for autoshutdown"), _("Enter time in days and hours."),
"autoshutdown_timeout_seconds", default_autoshutdown_timeout_seconds,
{5*60, 28*24*3600}, 2)
end,
}
end
if Device:canStandby() then
local standby_help = _([[Standby puts the device into a power-saving state in which the screen is on and user input can be performed.
Standby can not be entered if Wi-Fi is on.
Upon user input, the device needs a certain amount of time to wake up. With some devices this period of time is not noticeable, with other devices it can be annoying.]])
menu_items.autostandby = {
sorting_hint = "device",
checked_func = function()
return self:_enabledStandby()
end,
text_func = function()
if self.auto_standby_timeout_seconds and self.auto_standby_timeout_seconds > 0 then
local time_string = util.secondsToClockDuration("modern", self.auto_standby_timeout_seconds,
false, true, true):gsub("00m$", ""):gsub("^0+m", ""):gsub("^0", "")
return T(_("Autostandby timeout: %1"), time_string)
else
return _("Autostandby timeout")
end
end,
help_text = standby_help,
keep_menu_open = true,
callback = function(touchmenu_instance)
-- 5 sec is the minimum and 60*60 sec (15min) is the maximum standby time.
-- We need a minimum time, so that scheduled function have a chance to execute.
-- A standby time of 15 min seem excessive.
-- But or battery testing it might give some sense.
self:setSuspendShutdownTimes(touchmenu_instance,
_("Timeout for autostandby"), _("Enter time in minutes and seconds."),
"auto_standby_timeout_seconds", default_auto_standby_timeout_seconds,
{3, 15*60}, 0)
end,
}
end
end
-- KOReader is merely waiting for user input right now.
-- UI signals us that standby is allowed at this very moment because nothing else goes on in the background.
function AutoSuspend:onAllowStandby()
logger.dbg("AutoSuspend: onAllowStandby")
-- In case the OS frontend itself doesn't manage power state, we can do it on our own here.
-- One should also configure wake-up pins and perhaps wake alarm,
-- if we want to enter deeper sleep states later on from within standby.
-- Don't enter standby if wifi is on, as this my break reconnecting (at least on Kobo-Sage)
if NetworkMgr:isWifiOn() then
logger.dbg("AutoSuspend: WiFi is on, no standby")
return
end
-- Don't enter standby if device is charging and it is a non sunxi kobo
if Device.powerd:isCharging() and not Device:canPowerSaveWhileCharging() then
logger.dbg("AutoSuspend: charging, no standby")
return
end
if Device:canStandby() then
local wake_in = math.huge
-- The next scheduled function should be the deadline_guard
-- Wake before the second next scheduled function executes (e.g. footer update, suspend ...)
local scheduler_times = UIManager:getNextTaskTimes(2)
if #scheduler_times == 2 then
-- Wake up slightly after the formerly scheduled event, to avoid resheduling the same function
-- after a fraction of a second again (e.g. don't draw footer twice)
wake_in = math.floor(scheduler_times[2]:tonumber()) + 1
end
if wake_in > 3 then -- don't go into standby, if scheduled wake is in less than 3 secs
UIManager:broadcastEvent(Event:new("EnterStandby"))
logger.dbg("AutoSuspend: going to standby and wake in " .. wake_in .. "s zZzzZzZzzzzZZZzZZZz")
-- This is for the Kobo Sage/Elipsa for now, as these are the only with useStandby.
-- Other devices may be added
Device:standby(wake_in)
logger.dbg("AutoSuspend: leaving standby after " .. Device.last_standby_tv:tonumber() .. " s")
UIManager:broadcastEvent(Event:new("LeaveStandby"))
self:_unschedule() -- unschedule suspend and shutdown as the realtime clock has ticked
self:_schedule() -- reschedule suspend and shutdown with the new time
end
-- Don't do a `self:_reschedule_standby()` here, as this will interfere with suspend.
-- Better to to it in onLeaveStandby.
end
end
return AutoSuspend

@ -1,3 +1,4 @@
local Device = require("device")
local Event = require("ui/event")
local PluginShare = require("pluginshare")
local TimeVal = require("ui/timeval")
@ -34,20 +35,24 @@ function AutoTurn:_schedule()
if UIManager:getTopWidget() == "ReaderUI" then
logger.dbg("AutoTurn: go to next page")
self.ui:handleEvent(Event:new("GotoViewRel", self.autoturn_distance))
self.last_action_tv = UIManager:getTime()
end
logger.dbg("AutoTurn: schedule in", self.autoturn_sec)
UIManager:scheduleIn(self.autoturn_sec, self.task)
self.scheduled = true
else
logger.dbg("AutoTurn: schedule in", delay)
UIManager:scheduleIn(delay, self.task)
self.scheduled = true
end
end
function AutoTurn:_unschedule()
PluginShare.pause_auto_suspend = false
if self.task then
if self.scheduled then
logger.dbg("AutoTurn: unschedule")
UIManager:unschedule(self.task)
self.scheduled = false
end
end
@ -79,8 +84,8 @@ end
function AutoTurn:init()
UIManager.event_hook:registerWidget("InputEvent", self)
self.autoturn_sec = G_reader_settings:readSetting("autoturn_timeout_seconds") or 0
self.autoturn_distance = G_reader_settings:readSetting("autoturn_distance") or 1
self.autoturn_sec = G_reader_settings:readSetting("autoturn_timeout_seconds", 0)
self.autoturn_distance = G_reader_settings:readSetting("autoturn_distance", 1)
self.enabled = G_reader_settings:isTrue("autoturn_enabled")
self.ui.menu:registerToMainMenu(self)
self.task = function()
@ -105,6 +110,10 @@ function AutoTurn:onInputEvent()
self.last_action_tv = UIManager:getTime()
end
function AutoTurn:onEnterStandby()
self:_unschedule()
end
-- We do not want autoturn to turn pages during the suspend process.
-- Unschedule it and restart after resume.
function AutoTurn:onSuspend()
@ -112,7 +121,17 @@ function AutoTurn:onSuspend()
self:_unschedule()
end
function AutoTurn:onResume()
function AutoTurn:_onLeaveStandby()
self.last_action_tv = self.last_action_tv - Device.last_standby_tv
-- We messed with last_action_tv, so a complete reschedule has to be done.
if self:_enabled() then
self:_unschedule()
self:_schedule()
end
end
function AutoTurn:_onResume()
logger.dbg("AutoTurn: onResume")
self:_start()
end
@ -139,7 +158,10 @@ function AutoTurn:addToMainMenu(menu_items)
G_reader_settings:makeFalse("autoturn_enabled")
self:_unschedule()
menu:updateItems()
self.onResume = nil
self.onLeaveStandby = nil
end,
ok_always_enabled = true,
callback = function(autoturn_spin)
self.autoturn_sec = autoturn_spin.value
G_reader_settings:saveSetting("autoturn_timeout_seconds", autoturn_spin.value)
@ -148,6 +170,8 @@ function AutoTurn:addToMainMenu(menu_items)
self:_unschedule()
self:_start()
menu:updateItems()
self.onResume = self._onResume
self.onLeaveStandby = self._onLeaveStandby
end,
}
UIManager:show(autoturn_spin)

@ -5,7 +5,6 @@ Plugin for setting screen warmth based on the sun position and/or a time schedul
--]]--
local Device = require("device")
local ConfirmBox = require("ui/widget/confirmbox")
local DateTimeWidget = require("ui/widget/datetimewidget")
local DoubleSpinWidget = require("/ui/widget/doublespinwidget")
@ -21,6 +20,7 @@ local SunTime = require("suntime")
local TextWidget = require("ui/widget/textwidget")
local UIManager = require("ui/uimanager")
local WidgetContainer = require("ui/widget/container/widgetcontainer")
local logger = require("logger")
local _ = require("gettext")
local T = FFIUtil.template
local Screen = require("device").screen
@ -119,6 +119,7 @@ end
function AutoWarmth:onResume()
if self.activate == 0 then return end
logger.dbg("AutoWarmth: onResume/onLeaveStandby")
local resume_date = os.date("*t")
-- check if resume and suspend are done on the same day
@ -131,6 +132,8 @@ function AutoWarmth:onResume()
end
end
AutoWarmth.onLeaveStandby = AutoWarmth.onResume
-- wrapper for unscheduling, so that only our setWarmth gets unscheduled
function AutoWarmth.setWarmth(val)
if val then
@ -147,6 +150,7 @@ function AutoWarmth.setWarmth(val)
end
function AutoWarmth:scheduleMidnightUpdate()
logger.dbg("AutoWarmth: scheduleMidnightUpdate")
-- first unschedule all old functions
UIManager:unschedule(self.scheduleMidnightUpdate) -- when called from menu or resume
@ -249,7 +253,11 @@ function AutoWarmth:scheduleMidnightUpdate()
self:scheduleWarmthChanges(now)
end
--- @todo: As we have standby now, don't do the scheduling of the whole schedule,
-- but only the next warmth value plus an additional scheduleWarmthChanges
-- This would safe a bit of energy, but not really much.
function AutoWarmth:scheduleWarmthChanges(time)
logger.dbg("AutoWarmth: scheduleWarmthChanges")
for i = 1, #self.sched_funcs do -- loop not essential, as unschedule unschedules all functions at once
if not UIManager:unschedule(self.sched_funcs[i][1]) then
break

@ -44,8 +44,16 @@ function SystemStat:appendCounters()
if self.resume_sec then
self:put({_(" Last resume time"), os.date("%c", self.resume_sec)})
end
self:put({_(" Up hours"),
string.format("%.2f", os.difftime(os.time(), self.start_sec) / 60 / 60)})
self:put({_(" Up time"),
util.secondsToClockDuration("", os.difftime(os.time(), self.start_sec), false, true, true)})
if Device:canSuspend() then
self:put({_(" Time in suspend"),
util.secondsToClockDuration("", Device.total_suspend_tv:tonumber(), false, true, true)})
end
if Device:canStandby() then
self:put({_(" Time in standby"),
util.secondsToClockDuration("", Device.total_standby_tv:tonumber(), false, true, true)})
end
self:put({_("Counters"), ""})
self:put({_(" wake-ups"), self.wakeup_count})
-- @translators The number of "sleeps", that is the number of times the device has entered standby. This could also be translated as a rendition of a phrase like "entered sleep".

@ -15,10 +15,12 @@ local MockTime = {
original_tv_monotonic = nil,
original_tv_monotonic_coarse = nil,
original_tv_boottime = nil,
original_tv_boottime_or_realtime_coarse = nil,
original_tv_now = nil,
monotonic = 0,
realtime = 0,
boottime = 0,
boottime_or_realtime_coarse = 0,
}
function MockTime:install()
@ -47,6 +49,10 @@ function MockTime:install()
self.original_tv_boottime = TimeVal.boottime
assert(self.original_tv_boottime ~= nil)
end
if self.original_tv_boottime_or_realtime_coarse == nil then
self.original_tv_boottime_or_realtime_coarse = TimeVal.boottime_or_realtime_coarse
assert(self.original_tv_boottime_or_realtime_coarse ~= nil)
end
if self.original_tv_now == nil then
self.original_tv_now = TimeVal.now
assert(self.original_tv_now ~= nil)
@ -86,6 +92,10 @@ function MockTime:install()
logger.dbg("MockTime:TimeVal.boottime: ", self.boottime)
return TimeVal:new{ sec = self.boottime }
end
TimeVal.boottime_or_realtime_coarse = function()
logger.dbg("MockTime:TimeVal.boottime: ", self.boottime_or_realtime_coarse)
return TimeVal:new{ sec = self.boottime_or_realtime_coarse }
end
TimeVal.now = function()
logger.dbg("MockTime:TimeVal.now: ", self.monotonic)
return TimeVal:new{ sec = self.monotonic }
@ -113,6 +123,9 @@ function MockTime:uninstall()
if self.original_tv_boottime ~= nil then
TimeVal.boottime = self.original_tv_boottime
end
if self.original_tv_boottime_or_realtime_coarse ~= nil then
TimeVal.boottime_or_realtime_coarse = self.original_tv_boottime_or_realtime_coarse
end
if self.original_tv_now ~= nil then
TimeVal.now = self.original_tv_now
end
@ -178,6 +191,26 @@ function MockTime:increase_boottime(value)
return true
end
function MockTime:set_boottime_or_realtime_coarse(value)
assert(self ~= nil)
if type(value) ~= "number" then
return false
end
self.boottime_or_realtime_coarse = math.floor(value)
logger.dbg("MockTime:set_boottime ", self.boottime_or_realtime_coarse)
return true
end
function MockTime:increase_boottime_or_realtime_coarse(value)
assert(self ~= nil)
if type(value) ~= "number" then
return false
end
self.boottime_or_realtime_coarse = math.floor(self.boottime_or_realtime_coarse + value)
logger.dbg("MockTime:increase_boottime ", self.boottime_or_realtime_coarse)
return true
end
function MockTime:set(value)
assert(self ~= nil)
if type(value) ~= "number" then
@ -189,6 +222,8 @@ function MockTime:set(value)
logger.dbg("MockTime:set (monotonic) ", self.monotonic)
self.boottime = math.floor(value)
logger.dbg("MockTime:set (boottime) ", self.boottime)
self.boottime_or_realtime_coarse = math.floor(value)
logger.dbg("MockTime:set (boottime) ", self.boottime_or_realtime_coarse)
return true
end
@ -203,6 +238,8 @@ function MockTime:increase(value)
logger.dbg("MockTime:increase (monotonic) ", self.monotonic)
self.boottime = math.floor(self.boottime + value)
logger.dbg("MockTime:increase (boottime) ", self.boottime)
self.boottime_or_realtime_coarse = math.floor(self.boottime_or_realtime_coarse + value)
logger.dbg("MockTime:increase (boottime) ", self.boottime_or_realtime_coarse)
return true
end

Loading…
Cancel
Save