Skip to content

Latest commit

 

History

History
327 lines (239 loc) · 12.1 KB

File metadata and controls

327 lines (239 loc) · 12.1 KB

nook-gl4plus-deepsleep

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.


What it does

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.

The first-press problem (and why it matters)

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.


Physical button layout

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.


Requirements

  • 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 settings permission granted to KOReader (Settings → Apps → KOReader → Permissions)

Installation

Step 1 — Grant KOReader the "Modify system settings" permission

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

Step 2 — Patch Generic.kl

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 --restore

The 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 reboot

Step 3 — Install the event map

KOReader 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.lua

Note on the BACK remapping: mapping BACKLPgBack means 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.

Step 4 — Install the patch

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.lua

Step 5 — Restart KOReader

Force-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.


Verifying it works

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.


Measured battery drain

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.

Battery drain chart

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.


Troubleshooting

Two presses still required:

  • Check that Generic.kl was modified and the device was rebooted
  • Look for KRP: wake monitor: no /dev/input/event* opened in logcat — this means /dev/input is not readable. KOReader may need additional permissions (device-dependent)
  • Check KRP: rolling found! or KRP: paging found! — if absent, the ReaderUI hook did not fire

Buttons do nothing at all:

  • Check KRP: widget showing: ReaderUI in logcat — if missing, UIManager.show hook is not firing. Verify the patch file is in the correct directory.
  • Check that event_map.lua is 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_enable in logcat

Buttons don't track 180° screen rotation:

  • Check that settings/event_map.lua was installed (Step 3)
  • In logcat, KRP: poll: forward → diff=-1 when rotated confirms the rotation fix is working

About the 2111 prefix

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.


Restoring factory state

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.


How it works (technical summary)

  1. Generic.kl maps scan codes 191/192 → BACK and 193/194 → MENU. Both are hardcoded wake keys in Android's PhoneWindowManager; pressing either from mWakefulness=Asleep wakes the CPU.

  2. settings/event_map.lua remaps Android keycode BACK (4) → LPgBack and MENU (82) → LPgFwd within KOReader. This puts both buttons into the LPgFwd/LPgBack family used by KOReader's rotation and invert-buttons logic.

  3. The patch hooks UIManager.show to detect when a book is opened, then wraps pageHandler.onGotoViewRel to set power_enhance_enable=0 before each page turn and power_enhance_enable=1 after.

  4. The poll timer runs every 100ms. When the device wakes from deep sleep, ALooper_pollOnce returns EINTR (the wake key was pre-dispatched by PhoneWindowManager, not delivered to KOReader). UIManager then runs scheduled tasks — the timer fires, reads the pre-opened /dev/input fd buffer to identify the wake button, and fires the page turn via scheduleIn(0.15).

  5. Rotation awareness: get_rotation_aware_diff() reads event_map[82] (which invertButtons() toggles for the user setting) and applies rotation_map (which swaps LPgFwdLPgBack at 180°) to compute the correct +1/-1 direction 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.


Files in this repo

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

Credits