This repository contains an Android application to turn your Nook e-reader into a TRMNL display client.
- TRMNL team for the API
- Nook development community
- Renate from XDA forums and MobileReads for figuring out the developer options password
- downeaster59 from MobileReads for the instructions on sideloading apps onto Nook
- Exameden and Renate from XDA forums for discovering the power_enhance_enable setting to put the device into deep sleep
- Nook Glowlight 4
- ADB (Android Debug Bridge)
- TRMNL API key (physical device, BYOD license, or BYOS)
Download and install Android Platform Tools depending on your OS: https://developer.android.com/tools/releases/platform-tools
- Tap upper right corner for Quick Settings
- Tap "See All Settings"
- Go to page 2 (lower right arrow)
- Tap "About"
- Tap Nook icon 5 times rapidly
- Enter the password:
NOOK-BNRV1100 - Tap "Android Development Settings"
- Enable USB Debugging
- Connect Nook to computer via USB
- Open Terminal/Command Prompt/PowerShell
- Run:
adb devices- Verify device is listed
F-Droid is an open-source app store and is recommended to install as there is no other app store on Nook and it is very difficult/impossible to get Google Play Store on it. We will use F-Droid to download a launcher.
- Download the latest F-Droid APK from F-Droid.org
- Navigate to APK directory (replace with your path to the APK directory):
cd c:\Users\User\Downloads\- Install via ADB:
adb install F-Droid.apk- Open F-Droid. If it didn't open when installing, use ADB to open it:
adb shell monkey -p org.fdroid.fdroid -c android.intent.category.LAUNCHER 1- Search for and install a launcher (KISS Launcher is recommended for its simplicity and low performance impact)
- Reboot your Nook
- When prompted to select a launcher:
- Choose your newly installed launcher
- Select "Always" to make it default
- Note: You can still access the stock Nook launcher through your new launcher. Sometimes the original device homescreen will not launch using this icon, but you can reboot the device or use a shortcut maker app to create a shortcut for EpdHomeActivity (original device homescreen).
Why a custom launcher is necessary:
- Stock Nook launcher has no app drawer
- Essential for accessing TRMNL client app
- Download and install the trmnl-nook APK (under the releases section)
- Make sure to grant write system settings permission to the trmnl-nook app. This is necessary to put the device into deep sleep and to modify the default maximum screen timeout of 1 hour.
- Navigate to the system settings application
- Apps and Notifications > Advanced > Special app access > Modify system settings > Enable for trmnl-nook
Edit the application settings by clicking on the "Battery Level/Last Updated" text to configure:
{
"BASE_URL": "https://trmnl.app",
"MAC_ADDRESS": "your-mac-address",
"API_KEY": "your-api-key",
"SHOW_DEBUG_INFO": false,
"SCREEN_TIMEOUT": "30 days"
}Important: Set the screen timeout to a long duration for unattended operation.
How to configure: In the trmnl-nook app settings, use the "Screen Timeout" dropdown to select your preferred duration. This setting prevents the display from turning off during long-term operation.
If using your own server, make sure Permit Auto-Join is on. The device should show up and it will generate a new API key. (For some reason, it does not use the API key originally generated) Enter this API key in trmnl-nook settings. Also, on the server modify your device resolution to 1448 x 1072 (Nook screen resolution).
To put the device into deep sleep after updating the display, we use the power_enhance_enable setting. Waking the device and reliably keeping the device awake during updates was tricky due to the aggressive nature of the power_enhance_enable setting. The device does not appear to have any method to wake without user interaction, even setting alarms does not work to wake the device from deep sleep. When checking the logs during the deep sleep states, it did appear there was some activity that started around the time when alarms should have gone off. Mainly, battery level changes, screen timeout countdown, and there appeared to be some phantom headphone connection events as well. Since the battery level changes were far between in deep sleep and the screen timeout is customizable, we utilize the phantom headphone events to help enhance the waking of the device after the alarm puts the device into the half-way wake state. To reliably wake and keep the device awake during updates we:
- Set an alarm before sleep - Provides consistent timing for updates
- Capture phantom headphone events - Use them to immediately set a wakelock
- Use file logging - Maintains storage subsystem activity to keep the device awake during updates
┌─────────────────────────────────────────────────────────────────┐
│ INITIAL STARTUP │
├─────────────────────────────────────────────────────────────────┤
│ 1. MainActivity launches → Check permissions │
│ 2. Request WRITE_SETTINGS permission → Enable deep sleep │
│ 3. Load configuration → BASE_URL, API_KEY, MAC_ADDRESS │
│ 4. Register HeadphoneReceiver → Listen for phantom events │
│ 5. Trigger initial update → DisplayUpdateWorker starts │
│ 6. Schedule first alarm → Refresh rate grabbed from server │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ SCHEDULED UPDATE CYCLE │
├─────────────────────────────────────────────────────────────────┤
│ 1. AlarmManager fires → Device enters partial wake state │
│ 2. HeadphoneReceiver triggered → Call PowerManagerNook.wake() │
│ 3. Enqueue DisplayUpdateWorker → Start update process │
│ 4. WorkManager launches worker → Begin system activity │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ DISPLAYUPDATEWORKER PROCESS │
├─────────────────────────────────────────────────────────────────┤
│ 1. Start activity logging → Keep system components awake │
│ 2. Enable WiFi → PowerManagerNook.ensureWifiOn() │
│ ├─ Attempt 1 → Wait 2 seconds if failed │
│ ├─ Attempt 2 → Wait 4 seconds if failed │
│ └─ Attempt 3 → Return failure if still failed │
│ 3. Register broadcast receivers → Listen for completion │
│ 4. Trigger MainActivity update → Send ACTION_DISPLAY_UPDATE │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ MAINACTIVITY UPDATE │
├─────────────────────────────────────────────────────────────────┤
│ 1. Receive broadcast → TrmnlDisplay recomposition triggered │
│ 2. Setup device (if needed) → POST to /api/setup │
│ 3. Fetch display data → GET /api/display with headers: │
│ ├─ ID: MAC_ADDRESS │
│ ├─ Access-Token: API_KEY │
│ ├─ battery-level: current battery % │
│ ├─ png-width: 1448, png-height: 1072 │
│ └─ User-Agent: trmnl-display/1.5.11 │
│ 4. Parse JSON response → Extract image_url, refresh_rate │
│ 5. Download image → HTTP GET image_url │
│ 6. Update UI → Image composable with new bitmap │
│ 7. Send network completion → ACTION_NETWORK_COMPLETE │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ DISPLAYUPDATEWORKER COMPLETION │
├─────────────────────────────────────────────────────────────────┤
│ 1. Wait for network completion → Timeout after 30 seconds │
│ 2. Wait for screen completion → Timeout after 15 seconds │
│ 3. Schedule next alarm → Single alarm in {refresh_rate} secs │
│ 4. Disable WiFi → PowerManagerNook.goToSleep() │
│ 5. Stop activity logging → Allow system to enter deep sleep │
│ 6. Return SUCCESS → WorkManager marks job complete │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ DEEP SLEEP │
├─────────────────────────────────────────────────────────────────┤
│ Device enters custom Nook deep sleep mode: │
│ • Screen off, WiFi disabled, minimal power usage │
│ • AlarmManager maintains next wake schedule │
│ • Wait {refresh_rate} seconds (typically 900s = 15 minutes) │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ ERROR SCENARIOS │
├─────────────────────────────────────────────────────────────────┤
│ WiFi Enable Failure: │
│ └─ Retry 3 times → Return RETRY → WorkManager reschedules │
│ │
│ Network Request Timeout: │
│ └─ 30 second timeout → Use fallback refresh rate → Continue │
│ │
│ Image Download Failure: │
│ └─ Retry 3 times → Log error → Send completion anyway │
│ │
│ Screen Refresh Timeout: │
│ └─ 15 second timeout → Log warning → Proceed to sleep │
└─────────────────────────────────────────────────────────────────┘
UpdateReceiver: Responds to scheduled alarms, provides wake amplification
DisplayUpdateWorker: Main update logic, maintains system activity, handles completion
HeadphoneReceiver: Processes phantom headphone events to ensure device immediately acquires wakelock
PowerManagerNook: Nook-specific wake/sleep management and WiFi control
MainActivity/TrmnlDisplay: UI updates, API communication, image display
-
Display update issues:
- Currently, waking the device from deep sleep is very finnicky. Sometimes it will go a few days without issue, but other times it stops updating several times throughout the day. Looking for a more reliable way to wake the device, but in the meantime, if you notice it stopped updating, pressing any of the page turn buttons should trigger an update and fix the issue.
-
Power management:
- Verify WRITE_SETTINGS permission
-
Display issues:
- When switching to another app and then back to trmnl-nook, the status bar will display. Enter the settings screen and save to fix this.
Force quitting and restarting the app usually fixes most issues.
Enable debug mode in settings to see:
- Network requests
- Update cycle timing
- Error messages
- 1.0.0: Initial release