Skip to content

Latest commit

History

30 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

esp-reader

A streaming e-book reader for the Heltec Vision Master E213 and E290, in CircuitPython, with the firmware work needed to make it sip power.

One code.py runs on both boards. Of ~1150 lines, eleven differ between them — six e-paper pins, the panel rotation, the driver class, and two battery calibration constants — so the differences live in a table at the top of the file and the board is detected at boot from board.board_id.

device/ copy the CONTENTS of this to the CIRCUITPY drive
code.py the reader
boot.py
lib/ both panel drivers, bookmarks, Adafruit deps
fonts/ subset Literata, PCF and BDF
firmware/
install.sh symlink both boards into a CircuitPython checkout
boards/ the two board definitions
patches/ the real-light-sleep patch
tools/ font subsetting and BDF -> PCF conversion

Quick start

Flash the image for your board from the Releases page at 0x0, copy the contents of device/ to CIRCUITPY, drop in a .txt book, reset. See firmware/README.md and device/README.md.

What it does

Reads plain .txt, and converts .epub on the device to the same. Streams pages straight out of the file rather than loading it, so book size costs nothing. Reading positions live in NVM, per book, and survive a flat battery. One button drives everything by escalating hold — tap turns the page, double-tap goes back, hold opens the library, hold longer opens jump-to-percent, hold longer still sleeps. Neighbouring pages are pre-rendered during the idle after a turn, so a page turn is one partial refresh and nothing else.

Power

Measured on the E213 with a PPK2, USB disconnected, 4 V into the battery input:

statecurrent
running, 240 MHz68.1 mA
light sleep, stock CircuitPython43.2 mA
light sleep, patched1.1 mA
deep sleep16.4 µA

Stock CircuitPython does not power-gate in light sleep — it spins in a WFI loop, and upstream says so in a comment. The patch in firmware/patches/ makes alarm.light_sleep_until_alarms() call esp_light_sleep_start() instead, which is a 39x reduction and the reason the reader sleeps between every page turn rather than staying awake.

The cost is that every non-RTC peripheral is torn down across a sleep, so the display bus and the keypad are rebuilt on each wake — and USB does not come back until a reset-class event. The reader refuses to sleep at all while a cable is attached, which is what keeps a bad edit recoverable.

Things that cost real time to find

  • The E213 and E290 panels share no commands. 0x12 is refresh on one and soft reset on the other. Do not port display code between them.
  • The E290 has no differential waveform in OTP: asking for a partial update by mode byte alone silently performs a full flashing refresh. The 153-byte LUT must be uploaded first — and a full refresh wipes it again.
  • 0x22 = 0xF7 reloads the LUT from OTP. Re-upload before the next partial or the first page turn after every full refresh flashes.
  • The E290 panel's BUSY is asserted high; the E213's is asserted low. Getting it backwards fails silently as torn refreshes, not as an error.
  • Vext is GPIO18 on both. It is GPIO21 on the classic Heltec WiFi LoRa 32 boards, so that number is all over the forums — and on these boards GPIO21 is a button and a wake pin.
  • CIRCUITPY_ESP_REAL_LIGHT_SLEEP must be defined in mpconfigboard.h, not mpconfigboard.mk. The espressif Makefile has no CFLAGS_BOARD, so a define there is dropped without a word and you get a firmware that looks right and still spins on WFI.

Licence

GPL-3.0-or-later. Full text in LICENSE; every source file carries an SPDX header.

The deciding factor is device/lib/ssd1680e290.py. The file is original except for its 153-byte partial-update LUT, which was transcribed from GxEPD2 (GPL-3.0-or-later) — and that table is not incidental, it is what makes partial refresh work on this panel at all. Whether a waveform table carries copyright is arguable, but GPL settles it either way rather than resting the whole repository on the argument. If you want that driver under a permissive licence, re-derive the waveform from the SSD1680 datasheet; nothing else in the file is encumbered.

Everything the GPL is layered over stays under its own terms, and all of it is GPL-compatible:

  • device/lib/lcmen2r13efc1.py — MIT, a port of todd-herbert/heltec-eink-modules. It contains no GPL material and keeps its MIT header.
  • Adafruit libraries — MIT. firmware/patches/ modifies CircuitPython — MIT.
  • Literata and Open Sans — SIL Open Font License 1.1. DejaVu Sans — Bitstream Vera derivative. The .pf files are rasterised subsets, and both licences permit that.
  • The hyphenation patterns are Knuth-Liang plus ushyphmax — public domain.

Per-file table in device/README.md.

Do not commit books..gitignore excludes device/*.txt for that reason.

About

e-reader app for the Heltec E213 and E290

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
GitHub - stanelie/esp-reader: e-reader app for the Heltec E213 and E290 · GitHub
Skip to content

Latest commit

History

30 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

esp-reader

A streaming e-book reader for the Heltec Vision Master E213 and E290, in CircuitPython, with the firmware work needed to make it sip power.

One code.py runs on both boards. Of ~1150 lines, eleven differ between them — six e-paper pins, the panel rotation, the driver class, and two battery calibration constants — so the differences live in a table at the top of the file and the board is detected at boot from board.board_id.

device/ copy the CONTENTS of this to the CIRCUITPY drive
code.py the reader
boot.py
lib/ both panel drivers, bookmarks, Adafruit deps
fonts/ subset Literata, PCF and BDF
firmware/
install.sh symlink both boards into a CircuitPython checkout
boards/ the two board definitions
patches/ the real-light-sleep patch
tools/ font subsetting and BDF -> PCF conversion

Quick start

Flash the image for your board from the Releases page at 0x0, copy the contents of device/ to CIRCUITPY, drop in a .txt book, reset. See firmware/README.md and device/README.md.

What it does

Reads plain .txt, and converts .epub on the device to the same. Streams pages straight out of the file rather than loading it, so book size costs nothing. Reading positions live in NVM, per book, and survive a flat battery. One button drives everything by escalating hold — tap turns the page, double-tap goes back, hold opens the library, hold longer opens jump-to-percent, hold longer still sleeps. Neighbouring pages are pre-rendered during the idle after a turn, so a page turn is one partial refresh and nothing else.

Power

Measured on the E213 with a PPK2, USB disconnected, 4 V into the battery input:

statecurrent
running, 240 MHz68.1 mA
light sleep, stock CircuitPython43.2 mA
light sleep, patched1.1 mA
deep sleep16.4 µA

Stock CircuitPython does not power-gate in light sleep — it spins in a WFI loop, and upstream says so in a comment. The patch in firmware/patches/ makes alarm.light_sleep_until_alarms() call esp_light_sleep_start() instead, which is a 39x reduction and the reason the reader sleeps between every page turn rather than staying awake.

The cost is that every non-RTC peripheral is torn down across a sleep, so the display bus and the keypad are rebuilt on each wake — and USB does not come back until a reset-class event. The reader refuses to sleep at all while a cable is attached, which is what keeps a bad edit recoverable.

Things that cost real time to find

  • The E213 and E290 panels share no commands. 0x12 is refresh on one and soft reset on the other. Do not port display code between them.
  • The E290 has no differential waveform in OTP: asking for a partial update by mode byte alone silently performs a full flashing refresh. The 153-byte LUT must be uploaded first — and a full refresh wipes it again.
  • 0x22 = 0xF7 reloads the LUT from OTP. Re-upload before the next partial or the first page turn after every full refresh flashes.
  • The E290 panel's BUSY is asserted high; the E213's is asserted low. Getting it backwards fails silently as torn refreshes, not as an error.
  • Vext is GPIO18 on both. It is GPIO21 on the classic Heltec WiFi LoRa 32 boards, so that number is all over the forums — and on these boards GPIO21 is a button and a wake pin.
  • CIRCUITPY_ESP_REAL_LIGHT_SLEEP must be defined in mpconfigboard.h, not mpconfigboard.mk. The espressif Makefile has no CFLAGS_BOARD, so a define there is dropped without a word and you get a firmware that looks right and still spins on WFI.

Licence

GPL-3.0-or-later. Full text in LICENSE; every source file carries an SPDX header.

The deciding factor is device/lib/ssd1680e290.py. The file is original except for its 153-byte partial-update LUT, which was transcribed from GxEPD2 (GPL-3.0-or-later) — and that table is not incidental, it is what makes partial refresh work on this panel at all. Whether a waveform table carries copyright is arguable, but GPL settles it either way rather than resting the whole repository on the argument. If you want that driver under a permissive licence, re-derive the waveform from the SSD1680 datasheet; nothing else in the file is encumbered.

Everything the GPL is layered over stays under its own terms, and all of it is GPL-compatible:

  • device/lib/lcmen2r13efc1.py — MIT, a port of todd-herbert/heltec-eink-modules. It contains no GPL material and keeps its MIT header.
  • Adafruit libraries — MIT. firmware/patches/ modifies CircuitPython — MIT.
  • Literata and Open Sans — SIL Open Font License 1.1. DejaVu Sans — Bitstream Vera derivative. The .pf files are rasterised subsets, and both licences permit that.
  • The hyphenation patterns are Knuth-Liang plus ushyphmax — public domain.

Per-file table in device/README.md.

Do not commit books..gitignore excludes device/*.txt for that reason.

About

e-reader app for the Heltec E213 and E290

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - stanelie/esp-reader: e-reader app for the Heltec E213 and E290 · GitHub
Skip to content

Latest commit

History

30 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

esp-reader

A streaming e-book reader for the Heltec Vision Master E213 and E290, in CircuitPython, with the firmware work needed to make it sip power.

One code.py runs on both boards. Of ~1150 lines, eleven differ between them — six e-paper pins, the panel rotation, the driver class, and two battery calibration constants — so the differences live in a table at the top of the file and the board is detected at boot from board.board_id.

device/ copy the CONTENTS of this to the CIRCUITPY drive
code.py the reader
boot.py
lib/ both panel drivers, bookmarks, Adafruit deps
fonts/ subset Literata, PCF and BDF
firmware/
install.sh symlink both boards into a CircuitPython checkout
boards/ the two board definitions
patches/ the real-light-sleep patch
tools/ font subsetting and BDF -> PCF conversion

Quick start

Flash the image for your board from the Releases page at 0x0, copy the contents of device/ to CIRCUITPY, drop in a .txt book, reset. See firmware/README.md and device/README.md.

What it does

Reads plain .txt, and converts .epub on the device to the same. Streams pages straight out of the file rather than loading it, so book size costs nothing. Reading positions live in NVM, per book, and survive a flat battery. One button drives everything by escalating hold — tap turns the page, double-tap goes back, hold opens the library, hold longer opens jump-to-percent, hold longer still sleeps. Neighbouring pages are pre-rendered during the idle after a turn, so a page turn is one partial refresh and nothing else.

Power

Measured on the E213 with a PPK2, USB disconnected, 4 V into the battery input:

statecurrent
running, 240 MHz68.1 mA
light sleep, stock CircuitPython43.2 mA
light sleep, patched1.1 mA
deep sleep16.4 µA

Stock CircuitPython does not power-gate in light sleep — it spins in a WFI loop, and upstream says so in a comment. The patch in firmware/patches/ makes alarm.light_sleep_until_alarms() call esp_light_sleep_start() instead, which is a 39x reduction and the reason the reader sleeps between every page turn rather than staying awake.

The cost is that every non-RTC peripheral is torn down across a sleep, so the display bus and the keypad are rebuilt on each wake — and USB does not come back until a reset-class event. The reader refuses to sleep at all while a cable is attached, which is what keeps a bad edit recoverable.

Things that cost real time to find

  • The E213 and E290 panels share no commands. 0x12 is refresh on one and soft reset on the other. Do not port display code between them.
  • The E290 has no differential waveform in OTP: asking for a partial update by mode byte alone silently performs a full flashing refresh. The 153-byte LUT must be uploaded first — and a full refresh wipes it again.
  • 0x22 = 0xF7 reloads the LUT from OTP. Re-upload before the next partial or the first page turn after every full refresh flashes.
  • The E290 panel's BUSY is asserted high; the E213's is asserted low. Getting it backwards fails silently as torn refreshes, not as an error.
  • Vext is GPIO18 on both. It is GPIO21 on the classic Heltec WiFi LoRa 32 boards, so that number is all over the forums — and on these boards GPIO21 is a button and a wake pin.
  • CIRCUITPY_ESP_REAL_LIGHT_SLEEP must be defined in mpconfigboard.h, not mpconfigboard.mk. The espressif Makefile has no CFLAGS_BOARD, so a define there is dropped without a word and you get a firmware that looks right and still spins on WFI.

Licence

GPL-3.0-or-later. Full text in LICENSE; every source file carries an SPDX header.

The deciding factor is device/lib/ssd1680e290.py. The file is original except for its 153-byte partial-update LUT, which was transcribed from GxEPD2 (GPL-3.0-or-later) — and that table is not incidental, it is what makes partial refresh work on this panel at all. Whether a waveform table carries copyright is arguable, but GPL settles it either way rather than resting the whole repository on the argument. If you want that driver under a permissive licence, re-derive the waveform from the SSD1680 datasheet; nothing else in the file is encumbered.

Everything the GPL is layered over stays under its own terms, and all of it is GPL-compatible:

  • device/lib/lcmen2r13efc1.py — MIT, a port of todd-herbert/heltec-eink-modules. It contains no GPL material and keeps its MIT header.
  • Adafruit libraries — MIT. firmware/patches/ modifies CircuitPython — MIT.
  • Literata and Open Sans — SIL Open Font License 1.1. DejaVu Sans — Bitstream Vera derivative. The .pf files are rasterised subsets, and both licences permit that.
  • The hyphenation patterns are Knuth-Liang plus ushyphmax — public domain.

Per-file table in device/README.md.

Do not commit books..gitignore excludes device/*.txt for that reason.

About

e-reader app for the Heltec E213 and E290

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - stanelie/esp-reader: e-reader app for the Heltec E213 and E290 · GitHub
Skip to content

Latest commit

History

30 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

esp-reader

A streaming e-book reader for the Heltec Vision Master E213 and E290, in CircuitPython, with the firmware work needed to make it sip power.

One code.py runs on both boards. Of ~1150 lines, eleven differ between them — six e-paper pins, the panel rotation, the driver class, and two battery calibration constants — so the differences live in a table at the top of the file and the board is detected at boot from board.board_id.

device/ copy the CONTENTS of this to the CIRCUITPY drive
code.py the reader
boot.py
lib/ both panel drivers, bookmarks, Adafruit deps
fonts/ subset Literata, PCF and BDF
firmware/
install.sh symlink both boards into a CircuitPython checkout
boards/ the two board definitions
patches/ the real-light-sleep patch
tools/ font subsetting and BDF -> PCF conversion

Quick start

Flash the image for your board from the Releases page at 0x0, copy the contents of device/ to CIRCUITPY, drop in a .txt book, reset. See firmware/README.md and device/README.md.

What it does

Reads plain .txt, and converts .epub on the device to the same. Streams pages straight out of the file rather than loading it, so book size costs nothing. Reading positions live in NVM, per book, and survive a flat battery. One button drives everything by escalating hold — tap turns the page, double-tap goes back, hold opens the library, hold longer opens jump-to-percent, hold longer still sleeps. Neighbouring pages are pre-rendered during the idle after a turn, so a page turn is one partial refresh and nothing else.

Power

Measured on the E213 with a PPK2, USB disconnected, 4 V into the battery input:

statecurrent
running, 240 MHz68.1 mA
light sleep, stock CircuitPython43.2 mA
light sleep, patched1.1 mA
deep sleep16.4 µA

Stock CircuitPython does not power-gate in light sleep — it spins in a WFI loop, and upstream says so in a comment. The patch in firmware/patches/ makes alarm.light_sleep_until_alarms() call esp_light_sleep_start() instead, which is a 39x reduction and the reason the reader sleeps between every page turn rather than staying awake.

The cost is that every non-RTC peripheral is torn down across a sleep, so the display bus and the keypad are rebuilt on each wake — and USB does not come back until a reset-class event. The reader refuses to sleep at all while a cable is attached, which is what keeps a bad edit recoverable.

Things that cost real time to find

  • The E213 and E290 panels share no commands. 0x12 is refresh on one and soft reset on the other. Do not port display code between them.
  • The E290 has no differential waveform in OTP: asking for a partial update by mode byte alone silently performs a full flashing refresh. The 153-byte LUT must be uploaded first — and a full refresh wipes it again.
  • 0x22 = 0xF7 reloads the LUT from OTP. Re-upload before the next partial or the first page turn after every full refresh flashes.
  • The E290 panel's BUSY is asserted high; the E213's is asserted low. Getting it backwards fails silently as torn refreshes, not as an error.
  • Vext is GPIO18 on both. It is GPIO21 on the classic Heltec WiFi LoRa 32 boards, so that number is all over the forums — and on these boards GPIO21 is a button and a wake pin.
  • CIRCUITPY_ESP_REAL_LIGHT_SLEEP must be defined in mpconfigboard.h, not mpconfigboard.mk. The espressif Makefile has no CFLAGS_BOARD, so a define there is dropped without a word and you get a firmware that looks right and still spins on WFI.

Licence

GPL-3.0-or-later. Full text in LICENSE; every source file carries an SPDX header.

The deciding factor is device/lib/ssd1680e290.py. The file is original except for its 153-byte partial-update LUT, which was transcribed from GxEPD2 (GPL-3.0-or-later) — and that table is not incidental, it is what makes partial refresh work on this panel at all. Whether a waveform table carries copyright is arguable, but GPL settles it either way rather than resting the whole repository on the argument. If you want that driver under a permissive licence, re-derive the waveform from the SSD1680 datasheet; nothing else in the file is encumbered.

Everything the GPL is layered over stays under its own terms, and all of it is GPL-compatible:

  • device/lib/lcmen2r13efc1.py — MIT, a port of todd-herbert/heltec-eink-modules. It contains no GPL material and keeps its MIT header.
  • Adafruit libraries — MIT. firmware/patches/ modifies CircuitPython — MIT.
  • Literata and Open Sans — SIL Open Font License 1.1. DejaVu Sans — Bitstream Vera derivative. The .pf files are rasterised subsets, and both licences permit that.
  • The hyphenation patterns are Knuth-Liang plus ushyphmax — public domain.

Per-file table in device/README.md.

Do not commit books..gitignore excludes device/*.txt for that reason.

About

e-reader app for the Heltec E213 and E290

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' GitHub - stanelie/esp-reader: e-reader app for the Heltec E213 and E290 · GitHub
Skip to content

Latest commit

History

30 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

esp-reader

A streaming e-book reader for the Heltec Vision Master E213 and E290, in CircuitPython, with the firmware work needed to make it sip power.

One code.py runs on both boards. Of ~1150 lines, eleven differ between them — six e-paper pins, the panel rotation, the driver class, and two battery calibration constants — so the differences live in a table at the top of the file and the board is detected at boot from board.board_id.

device/ copy the CONTENTS of this to the CIRCUITPY drive
code.py the reader
boot.py
lib/ both panel drivers, bookmarks, Adafruit deps
fonts/ subset Literata, PCF and BDF
firmware/
install.sh symlink both boards into a CircuitPython checkout
boards/ the two board definitions
patches/ the real-light-sleep patch
tools/ font subsetting and BDF -> PCF conversion

Quick start

Flash the image for your board from the Releases page at 0x0, copy the contents of device/ to CIRCUITPY, drop in a .txt book, reset. See firmware/README.md and device/README.md.

What it does

Reads plain .txt, and converts .epub on the device to the same. Streams pages straight out of the file rather than loading it, so book size costs nothing. Reading positions live in NVM, per book, and survive a flat battery. One button drives everything by escalating hold — tap turns the page, double-tap goes back, hold opens the library, hold longer opens jump-to-percent, hold longer still sleeps. Neighbouring pages are pre-rendered during the idle after a turn, so a page turn is one partial refresh and nothing else.

Power

Measured on the E213 with a PPK2, USB disconnected, 4 V into the battery input:

statecurrent
running, 240 MHz68.1 mA
light sleep, stock CircuitPython43.2 mA
light sleep, patched1.1 mA
deep sleep16.4 µA

Stock CircuitPython does not power-gate in light sleep — it spins in a WFI loop, and upstream says so in a comment. The patch in firmware/patches/ makes alarm.light_sleep_until_alarms() call esp_light_sleep_start() instead, which is a 39x reduction and the reason the reader sleeps between every page turn rather than staying awake.

The cost is that every non-RTC peripheral is torn down across a sleep, so the display bus and the keypad are rebuilt on each wake — and USB does not come back until a reset-class event. The reader refuses to sleep at all while a cable is attached, which is what keeps a bad edit recoverable.

Things that cost real time to find

  • The E213 and E290 panels share no commands. 0x12 is refresh on one and soft reset on the other. Do not port display code between them.
  • The E290 has no differential waveform in OTP: asking for a partial update by mode byte alone silently performs a full flashing refresh. The 153-byte LUT must be uploaded first — and a full refresh wipes it again.
  • 0x22 = 0xF7 reloads the LUT from OTP. Re-upload before the next partial or the first page turn after every full refresh flashes.
  • The E290 panel's BUSY is asserted high; the E213's is asserted low. Getting it backwards fails silently as torn refreshes, not as an error.
  • Vext is GPIO18 on both. It is GPIO21 on the classic Heltec WiFi LoRa 32 boards, so that number is all over the forums — and on these boards GPIO21 is a button and a wake pin.
  • CIRCUITPY_ESP_REAL_LIGHT_SLEEP must be defined in mpconfigboard.h, not mpconfigboard.mk. The espressif Makefile has no CFLAGS_BOARD, so a define there is dropped without a word and you get a firmware that looks right and still spins on WFI.

Licence

GPL-3.0-or-later. Full text in LICENSE; every source file carries an SPDX header.

The deciding factor is device/lib/ssd1680e290.py. The file is original except for its 153-byte partial-update LUT, which was transcribed from GxEPD2 (GPL-3.0-or-later) — and that table is not incidental, it is what makes partial refresh work on this panel at all. Whether a waveform table carries copyright is arguable, but GPL settles it either way rather than resting the whole repository on the argument. If you want that driver under a permissive licence, re-derive the waveform from the SSD1680 datasheet; nothing else in the file is encumbered.

Everything the GPL is layered over stays under its own terms, and all of it is GPL-compatible:

  • device/lib/lcmen2r13efc1.py — MIT, a port of todd-herbert/heltec-eink-modules. It contains no GPL material and keeps its MIT header.
  • Adafruit libraries — MIT. firmware/patches/ modifies CircuitPython — MIT.
  • Literata and Open Sans — SIL Open Font License 1.1. DejaVu Sans — Bitstream Vera derivative. The .pf files are rasterised subsets, and both licences permit that.
  • The hyphenation patterns are Knuth-Liang plus ushyphmax — public domain.

Per-file table in device/README.md.

Do not commit books..gitignore excludes device/*.txt for that reason.

About

e-reader app for the Heltec E213 and E290

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - stanelie/esp-reader: e-reader app for the Heltec E213 and E290 · GitHub
Skip to content

Latest commit

History

30 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

esp-reader

A streaming e-book reader for the Heltec Vision Master E213 and E290, in CircuitPython, with the firmware work needed to make it sip power.

One code.py runs on both boards. Of ~1150 lines, eleven differ between them — six e-paper pins, the panel rotation, the driver class, and two battery calibration constants — so the differences live in a table at the top of the file and the board is detected at boot from board.board_id.

device/ copy the CONTENTS of this to the CIRCUITPY drive
code.py the reader
boot.py
lib/ both panel drivers, bookmarks, Adafruit deps
fonts/ subset Literata, PCF and BDF
firmware/
install.sh symlink both boards into a CircuitPython checkout
boards/ the two board definitions
patches/ the real-light-sleep patch
tools/ font subsetting and BDF -> PCF conversion

Quick start

Flash the image for your board from the Releases page at 0x0, copy the contents of device/ to CIRCUITPY, drop in a .txt book, reset. See firmware/README.md and device/README.md.

What it does

Reads plain .txt, and converts .epub on the device to the same. Streams pages straight out of the file rather than loading it, so book size costs nothing. Reading positions live in NVM, per book, and survive a flat battery. One button drives everything by escalating hold — tap turns the page, double-tap goes back, hold opens the library, hold longer opens jump-to-percent, hold longer still sleeps. Neighbouring pages are pre-rendered during the idle after a turn, so a page turn is one partial refresh and nothing else.

Power

Measured on the E213 with a PPK2, USB disconnected, 4 V into the battery input:

statecurrent
running, 240 MHz68.1 mA
light sleep, stock CircuitPython43.2 mA
light sleep, patched1.1 mA
deep sleep16.4 µA

Stock CircuitPython does not power-gate in light sleep — it spins in a WFI loop, and upstream says so in a comment. The patch in firmware/patches/ makes alarm.light_sleep_until_alarms() call esp_light_sleep_start() instead, which is a 39x reduction and the reason the reader sleeps between every page turn rather than staying awake.

The cost is that every non-RTC peripheral is torn down across a sleep, so the display bus and the keypad are rebuilt on each wake — and USB does not come back until a reset-class event. The reader refuses to sleep at all while a cable is attached, which is what keeps a bad edit recoverable.

Things that cost real time to find

  • The E213 and E290 panels share no commands. 0x12 is refresh on one and soft reset on the other. Do not port display code between them.
  • The E290 has no differential waveform in OTP: asking for a partial update by mode byte alone silently performs a full flashing refresh. The 153-byte LUT must be uploaded first — and a full refresh wipes it again.
  • 0x22 = 0xF7 reloads the LUT from OTP. Re-upload before the next partial or the first page turn after every full refresh flashes.
  • The E290 panel's BUSY is asserted high; the E213's is asserted low. Getting it backwards fails silently as torn refreshes, not as an error.
  • Vext is GPIO18 on both. It is GPIO21 on the classic Heltec WiFi LoRa 32 boards, so that number is all over the forums — and on these boards GPIO21 is a button and a wake pin.
  • CIRCUITPY_ESP_REAL_LIGHT_SLEEP must be defined in mpconfigboard.h, not mpconfigboard.mk. The espressif Makefile has no CFLAGS_BOARD, so a define there is dropped without a word and you get a firmware that looks right and still spins on WFI.

Licence

GPL-3.0-or-later. Full text in LICENSE; every source file carries an SPDX header.

The deciding factor is device/lib/ssd1680e290.py. The file is original except for its 153-byte partial-update LUT, which was transcribed from GxEPD2 (GPL-3.0-or-later) — and that table is not incidental, it is what makes partial refresh work on this panel at all. Whether a waveform table carries copyright is arguable, but GPL settles it either way rather than resting the whole repository on the argument. If you want that driver under a permissive licence, re-derive the waveform from the SSD1680 datasheet; nothing else in the file is encumbered.

Everything the GPL is layered over stays under its own terms, and all of it is GPL-compatible:

  • device/lib/lcmen2r13efc1.py — MIT, a port of todd-herbert/heltec-eink-modules. It contains no GPL material and keeps its MIT header.
  • Adafruit libraries — MIT. firmware/patches/ modifies CircuitPython — MIT.
  • Literata and Open Sans — SIL Open Font License 1.1. DejaVu Sans — Bitstream Vera derivative. The .pf files are rasterised subsets, and both licences permit that.
  • The hyphenation patterns are Knuth-Liang plus ushyphmax — public domain.

Per-file table in device/README.md.

Do not commit books..gitignore excludes device/*.txt for that reason.

About

e-reader app for the Heltec E213 and E290

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); })(); GitHub - stanelie/esp-reader: e-reader app for the Heltec E213 and E290 · GitHub
Skip to content

Latest commit

History

30 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

esp-reader

A streaming e-book reader for the Heltec Vision Master E213 and E290, in CircuitPython, with the firmware work needed to make it sip power.

One code.py runs on both boards. Of ~1150 lines, eleven differ between them — six e-paper pins, the panel rotation, the driver class, and two battery calibration constants — so the differences live in a table at the top of the file and the board is detected at boot from board.board_id.

device/ copy the CONTENTS of this to the CIRCUITPY drive
code.py the reader
boot.py
lib/ both panel drivers, bookmarks, Adafruit deps
fonts/ subset Literata, PCF and BDF
firmware/
install.sh symlink both boards into a CircuitPython checkout
boards/ the two board definitions
patches/ the real-light-sleep patch
tools/ font subsetting and BDF -> PCF conversion

Quick start

Flash the image for your board from the Releases page at 0x0, copy the contents of device/ to CIRCUITPY, drop in a .txt book, reset. See firmware/README.md and device/README.md.

What it does

Reads plain .txt, and converts .epub on the device to the same. Streams pages straight out of the file rather than loading it, so book size costs nothing. Reading positions live in NVM, per book, and survive a flat battery. One button drives everything by escalating hold — tap turns the page, double-tap goes back, hold opens the library, hold longer opens jump-to-percent, hold longer still sleeps. Neighbouring pages are pre-rendered during the idle after a turn, so a page turn is one partial refresh and nothing else.

Power

Measured on the E213 with a PPK2, USB disconnected, 4 V into the battery input:

statecurrent
running, 240 MHz68.1 mA
light sleep, stock CircuitPython43.2 mA
light sleep, patched1.1 mA
deep sleep16.4 µA

Stock CircuitPython does not power-gate in light sleep — it spins in a WFI loop, and upstream says so in a comment. The patch in firmware/patches/ makes alarm.light_sleep_until_alarms() call esp_light_sleep_start() instead, which is a 39x reduction and the reason the reader sleeps between every page turn rather than staying awake.

The cost is that every non-RTC peripheral is torn down across a sleep, so the display bus and the keypad are rebuilt on each wake — and USB does not come back until a reset-class event. The reader refuses to sleep at all while a cable is attached, which is what keeps a bad edit recoverable.

Things that cost real time to find

  • The E213 and E290 panels share no commands. 0x12 is refresh on one and soft reset on the other. Do not port display code between them.
  • The E290 has no differential waveform in OTP: asking for a partial update by mode byte alone silently performs a full flashing refresh. The 153-byte LUT must be uploaded first — and a full refresh wipes it again.
  • 0x22 = 0xF7 reloads the LUT from OTP. Re-upload before the next partial or the first page turn after every full refresh flashes.
  • The E290 panel's BUSY is asserted high; the E213's is asserted low. Getting it backwards fails silently as torn refreshes, not as an error.
  • Vext is GPIO18 on both. It is GPIO21 on the classic Heltec WiFi LoRa 32 boards, so that number is all over the forums — and on these boards GPIO21 is a button and a wake pin.
  • CIRCUITPY_ESP_REAL_LIGHT_SLEEP must be defined in mpconfigboard.h, not mpconfigboard.mk. The espressif Makefile has no CFLAGS_BOARD, so a define there is dropped without a word and you get a firmware that looks right and still spins on WFI.

Licence

GPL-3.0-or-later. Full text in LICENSE; every source file carries an SPDX header.

The deciding factor is device/lib/ssd1680e290.py. The file is original except for its 153-byte partial-update LUT, which was transcribed from GxEPD2 (GPL-3.0-or-later) — and that table is not incidental, it is what makes partial refresh work on this panel at all. Whether a waveform table carries copyright is arguable, but GPL settles it either way rather than resting the whole repository on the argument. If you want that driver under a permissive licence, re-derive the waveform from the SSD1680 datasheet; nothing else in the file is encumbered.

Everything the GPL is layered over stays under its own terms, and all of it is GPL-compatible:

  • device/lib/lcmen2r13efc1.py — MIT, a port of todd-herbert/heltec-eink-modules. It contains no GPL material and keeps its MIT header.
  • Adafruit libraries — MIT. firmware/patches/ modifies CircuitPython — MIT.
  • Literata and Open Sans — SIL Open Font License 1.1. DejaVu Sans — Bitstream Vera derivative. The .pf files are rasterised subsets, and both licences permit that.
  • The hyphenation patterns are Knuth-Liang plus ushyphmax — public domain.

Per-file table in device/README.md.

Do not commit books..gitignore excludes device/*.txt for that reason.

About

e-reader app for the Heltec E213 and E290

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages