KOReader patch for the Nook Glowlight 4 Plus (model bnrv1300) that enables AllWinner hardware deep sleep between page turns, giving weeks of standby battery life instead of hours.
Based on nopowen v20231105 by NiLuJe.
The deep sleep mechanism, JNI Settings.System interface, and page-turn wrapping
are from nopowen. This patch adds wake-key detection via /dev/input fd
monitoring, a poll timer to handle the first-press problem, and rotation-aware
button direction.
E-ink displays are bistable — they hold an image indefinitely with zero power.
The only idle drain is the SoC. The AllWinner chip in the Nook GL4+ supports a
vendor-specific setting (power_enhance_enable) that fully suspends the CPU
after each page turn, leaving only the RTC and wake-interrupt logic alive.
Current draw in this state is in the µA range, giving weeks of standby rather
than the hours typical of Android idle.
The patch hooks KOReader's page-turn events: before sleep it sets
power_enhance_enable=1; on wake it sets it back to 0, turns the page, then
re-arms sleep.
When the device is in deep sleep, the first button press wakes the CPU but is
consumed by Android's PhoneWindowManager for the wakeup action — it is never
delivered to KOReader's input queue. On a standard Android device, onResume
would fire within ~500ms and the app could respond. On this vendor's firmware
the Activity lifecycle is bypassed entirely: the device goes back to sleep at
~500ms regardless. Without a fix, two button presses are required: the first
wakes the CPU, the second turns the page.
This patch solves it by keeping /dev/input file descriptors open at all
times. Each open() call gets an independent kernel ring buffer, so the wake
key event is still readable from our fd even after Android "consumed" it for
wakeup. A 100ms poll timer reads the buffer and fires the page turn within
the ~500ms wake window, before the device goes back to sleep.
The device has four physical buttons — two on the left edge, two on the right edge — paired top and bottom. This lets either hand activate them one-handed.
Left edge Right edge
───────── ──────────
[scan 191] [scan 192] ← top pair → page back
[scan 193] [scan 194] ← bottom pair → page forward
The patch maps both top buttons to the same function and both bottom buttons to the same function, so left-handed and right-handed use work identically.
- Nook Glowlight 4 Plus (model bnrv1300, Android 8.1, AllWinner SoC) — may work on closely related models but has only been tested on this one
- Root access — needed to modify
/system/usr/keylayout/Generic.kl - ADB — needed for file transfer during installation
- KOReader installed on the device
- The
Modify system settingspermission granted to KOReader (Settings → Apps → KOReader → Permissions)
This is required for the patch to write power_enhance_enable to
Settings.System. Without it the patch logs a failure and no sleep occurs.
On the device: Settings → Apps → KOReader → Modify system settings → Allow
The factory button mapping uses F9–F12, which are not Android wake keys.
We remap the top pair to BACK and the bottom pair to MENU — both are
hardcoded wake keys in Android's PhoneWindowManager.
This step requires root. The /system partition is read-only by default;
the script remounts it read-write briefly, writes the file, then remounts
read-only again.
Use the included script:
# Apply the patch (creates a backup on the device first)
./install_generic_kl.sh
# To restore the original at any time:
./install_generic_kl.sh --restoreThe backup is stored on the device at
/system/usr/keylayout/Generic.kl.nook-deepsleep-backup.
If you prefer to patch manually, see keylayout/Generic.kl.vendor-section
for the exact section to change.
Reboot the device for the key layout change to take effect:
adb rebootKOReader has a built-in mechanism to load a device-specific key remapping file
at startup (frontend/device/input.lua). This file remaps the Android
keycodes BACK and MENU into KOReader's LPgBack/LPgFwd family so that:
- Screen rotation (180°) automatically swaps which button pages forward vs. back
- KOReader's "invert page turn buttons" reader menu setting works correctly
adb push settings/event_map.lua /sdcard/koreader/settings/event_map.luaNote on the BACK remapping: mapping
BACK→LPgBackmeans the top buttons no longer act as Android's Back key inside KOReader (directory-up, dialog-dismiss, etc.). On this device the physical buttons are dedicated page-turn keys; touch and swipe gestures handle all other navigation.
KOReader loads all .lua files from /sdcard/koreader/patches/ at startup,
sorted alphanumerically. The 2111- prefix is a load-order key that ensures
this patch loads after KOReader's core modules (UIManager, Device, etc.) are
fully initialised — see About the 2111 prefix below.
adb push patch/2111-nook-gl4plus-deepsleep.lua /sdcard/koreader/patches/2111-nook-gl4plus-deepsleep.luaForce-stop KOReader and reopen it. Open any book. Disconnect USB — the device should now enter deep sleep within 1–4 seconds of each page turn and wake with a single button press.
With ADB connected, start a logcat filtered to KOReader messages:
adb shell "logcat -v time KOReader:I *:S"Open a book and disconnect USB. Press a button. You should see:
KRP: delayed_deepsleep: power_enhance_enable=1
KRP: wake monitor: input buffers drained
... [press button] ...
KRP: wake monitor: forward button (code=193)
KRP: poll: forward → diff=1
KRP: i_am_paging! diff=1
The time between the drain log and i_am_paging should be under 400ms.
The test below was run on 2026-06-03 with the device fully unplugged. A custom
KOReader plugin turned one page per minute and logged battery capacity and
voltage from the fuel gauge after each turn. The device woke from AllWinner
deep sleep via RTC alarm (/sys/class/rtc/rtc0/wakealarm) for each page turn,
then returned to deep sleep within ~1 second.
| Metric | Value |
|---|---|
| Duration (unplugged) | 11.1 hours |
| Page turns completed | 640 |
| Battery drain | 100% → 89% (−11%) |
| Drain rate | ~1% / hour |
| Voltage drop | 4381 mV → 4240 mV (−141 mV) |
| Average discharge current | ~163 mA |
The two pink bands on the left are brief USB reconnections during the test (~19 min and ~6 min) where the plugin automatically paused. The main 10-hour unplugged segment ran uninterrupted.
At ~1%/hour, a full charge provides approximately 100 hours of reading at 1 page/minute. The backlight was off during this test; enabling it will increase drain. Deep sleep between pages means the device is essentially off at the hardware level between turns.
Two presses still required:
- Check that
Generic.klwas modified and the device was rebooted - Look for
KRP: wake monitor: no /dev/input/event* openedin logcat — this means/dev/inputis not readable. KOReader may need additional permissions (device-dependent) - Check
KRP: rolling found!orKRP: paging found!— if absent, the ReaderUI hook did not fire
Buttons do nothing at all:
- Check
KRP: widget showing: ReaderUIin logcat — if missing, UIManager.show hook is not firing. Verify the patch file is in the correct directory. - Check that
event_map.luais in/sdcard/koreader/settings/not/sdcard/koreader/
power_enhance_enable write fails:
- Grant KOReader the "Modify system settings" permission (Step 1)
- Check for
KRP: failed to set power_enhance_enablein logcat
Buttons don't track 180° screen rotation:
- Check that
settings/event_map.luawas installed (Step 3) - In logcat,
KRP: poll: forward → diff=-1when rotated confirms the rotation fix is working
KOReader's patch loader reads all .lua files from patches/ in
alphanumeric filename order. The numeric prefix controls load order — a patch
numbered 2111 loads before one numbered 3000 and after one numbered 1000.
The original nopowen patch used 2111, placing it after KOReader's core
modules are ready (UIManager, Device, the reader framework) but before any
user interaction. This patch keeps the same number so it serves as a
drop-in replacement for nopowen — install one or the other, not both.
If you have other patches that conflict at this position, rename the file with
a different prefix (e.g., 2200-nook-gl4plus-deepsleep.lua). The only
requirement is that core KOReader modules exist when this patch runs.
To undo everything:
# Restore Generic.kl
adb shell "su -c 'mount -o remount,rw /system'"
adb shell "su -c 'cp /system/usr/keylayout/Generic.bak /system/usr/keylayout/Generic.kl'"
adb shell "su -c 'mount -o remount,ro /system'"
# Remove KOReader files
adb shell "rm /sdcard/koreader/patches/2111-nook-gl4plus-deepsleep.lua"
adb shell "rm /sdcard/koreader/settings/event_map.lua"Reboot the device. Button behaviour and battery life return to factory defaults.
-
Generic.kl maps scan codes 191/192 →
BACKand 193/194 →MENU. Both are hardcoded wake keys in Android'sPhoneWindowManager; pressing either frommWakefulness=Asleepwakes the CPU. -
settings/event_map.lua remaps Android keycode
BACK(4) →LPgBackandMENU(82) →LPgFwdwithin KOReader. This puts both buttons into theLPgFwd/LPgBackfamily used by KOReader's rotation and invert-buttons logic. -
The patch hooks
UIManager.showto detect when a book is opened, then wrapspageHandler.onGotoViewRelto setpower_enhance_enable=0before each page turn andpower_enhance_enable=1after. -
The poll timer runs every 100ms. When the device wakes from deep sleep,
ALooper_pollOncereturnsEINTR(the wake key was pre-dispatched byPhoneWindowManager, not delivered to KOReader). UIManager then runs scheduled tasks — the timer fires, reads the pre-opened/dev/inputfd buffer to identify the wake button, and fires the page turn viascheduleIn(0.15). -
Rotation awareness:
get_rotation_aware_diff()readsevent_map[82](whichinvertButtons()toggles for the user setting) and appliesrotation_map(which swapsLPgFwd↔LPgBackat 180°) to compute the correct+1/-1direction for both the poll timer and the Resume hook.
For a deeper technical explanation see the nopowen.md and keymapping.md
files in the nook-gl4plus-clean KOReader branch
where this patch was developed.
patch/
2111-nook-gl4plus-deepsleep.lua → install to /sdcard/koreader/patches/
settings/
event_map.lua → install to /sdcard/koreader/settings/
keylayout/
Generic.kl.vendor-section → reference showing the required kl change
analysis/
battery_drain_test_2026-06-03.png → battery drain chart (2026-06-03 test run)
plot_bdt.py → script to regenerate the chart from log data
install_generic_kl.sh → script to patch/restore Generic.kl via ADB
README.md → this file
-
nopowen by NiLuJe — the foundational deep sleep mechanism, JNI Settings.System interface, and core page-turn hooking pattern. https://www.mobileread.com/forums/showthread.php?t=347529
-
KOReader — open-source document reader. https://github.com/koreader/koreader
