1. A Pocket Library based on an ESP32
  2. The Hardware
  3. The Software
  4. The Books
  5. The Results

A Pocket Library based on an ESP32

Some time ago, I had the idea of creating a kind of "internet without internet" using an ESP32. A 128MB (yes, megabyte) NAND flash module would store a library of ebooks, as well as a heavily slimmed-down version of Wikipedia, and even software. All of this would be offered via the ESP32's own WiFi access point, allowing any nearby phone or PC to connect and access the content via a web browser.

I made a prototype with an ESP32-S3 WROOM module and a QSPI NAND flash chip, but ended up getting bogged down in writing a NAND flash handler - the addressing and sector clearing gave me trouble. Eventually I solved the problem, but shelved the project.

Fast forward several years, and I was thinking about a widget to hand out at FurEh 2026 - I had about a month and a half before the con, so it had to be something simple. The pocket library idea came back - but what if I slimmed it down, just ebooks, and squeezed into the ESP32 module's own memory?

The front and back of the finished pocket library.

The Hardware

Since I was giving these away for free, I wanted the hardware to be as cheap as possible. This meant that the hardware had to support basic assembly from JLCPCB which restricts what ESP32 WROOM modules can be used. I ended up using an ESP32-C6-WROOM-1-N8, which was a compromise. It has 8MB of flash memory, which is double what you get from most ESP32-C3-WROOM modules. ESP32-32D modules were available with even more memory, but they're older and have no built-in USB programming, and I didn't want to deal with an external programmer or the expense of a USB-UART bridge IC.

The ESP32 runs the whole show, so the rest of the PCB is very simple. A USB-C connector provides power (and programming), and a USBLC6 diode array provides some ESD and overvoltage protection for the USB power and data lines. A schottky diode (1N5819) drops the 5V voltage a bit, which takes a bit of thermal stress off the LDO regulator. The LDO is a TLV75733PDBVR from Texas Instruments with plenty of input and output decoupling, which provides a solid 3.3V@1A for the ESP32.

Since I had some extra board space, I decided to include some RGB LEDs - because why not? I mostly wanted to experiment with single-wire (WS2812B) style addressable LEDs, which I haven't used before. I included four XL-0807RGBC-2812B LEDs in a chain, powered from the schottky diode supply. This supply is slightly lower than 5V which makes driving them with the ESP32's 3V3 logic more reliable.

The EDA view of the front and back of the PCB showing components, traces, and pours.

The entire PCB is about 32mm x 27mm, although one edge is actually blank to prevent shielding the ESP32 antenna. It's a two-layer, standard 1.6mm FR4 PCB with lead-free HASL soldering. The ESP32's GPIO9 pin has a pad on the back of the board to allow it to be shorted to ground, forcing the ESP32 into USB/JTAG download mode for programming.

After programming the boards, I used clear heat-shrink wrap meant for 18650 batteries to wrap the PCBs. It was a tight fit but worked great and gave the small 0402 components some protection. The board pictured above actually went through a full wash-dry cycle in a pants pocket and survived.

A full schematic of the board is available at this link: PocketLibrary Schematic

The Software

The software on the ESP32 needs to handle a couple major tasks:

I wrote the code in the Arduino IDE. The DNSServer library handles the captive portal behaviour of the network - any DNS request returns a response that points to the local IP address of the ESP32. Captive portal is tricky and it works better on some phones than others - on Android and PC it seems to detect it and prompt you to go to the log-in page reliably, although you might be stuck in a webview window rather than a real browser.

Once the user arrives at local IP, the ESPAsyncWebServer handles serving the actual web content. This is mostly static content that is pre-compressed using GZIP to minimize file space. All HTML, CSS, JS, and images are pre-compressed before being written to the ESP32's memory - ESPAsyncWebServer supports serving these GZ files directly from a LittleFS filesystem. The web server also has a couple special routes for handling sending some statistics and saving config info from the about page.

The homepage shown when connecting to the pocket library with a random selection of books.

The LittleFS filesystem stores all of the static content. It uses a custom partition scheme that allocates 0x69FFFF (nice) bytes to a SPIFFS partition that is used by LittleFS (the rest of the 8MB is used for the application + NVS settings storage). I used Earl Philhower's excellent tool available here: Arduino LittleFS Upload to build the LittleFS filesystem and upload it to the ESP32.

Each book consists of a minimal HTML markup file containing all of the book text, as well as a matching cover image. The cover images are small 120x180 GIFs to minimize the filespace required. A file in the static directory called manifest.json contains the matching filenames for the covers and books, author, year, some tags and a short description. This is used by the client (via a JS request) to generate things like the random rotating book display on the front page, or the list of books on the books page.

The e-Reader application is pretty bare-bones but it includes some settings for font size, font type, and light/dark mode. It's not the best implementation - I should have used media queries for light/dark mode, like this blog uses. It does save the settings in your local storage though, along with your scroll position (for each individual book!), so you don't lose your place if you leave the page.

The basic e-reader application used to display the books.

A complete ZIP archive including the .ino file, the data folder containing all of the GZipped files, and the partitions.csv can be downloaded here: Pocket Library Complete Source. Only download this if you're in Canada - all of the included books are public domain in Canada, but they may still be considered copyright in some countries (i.e. the USA). If you want to build this yourself, you'll need to use Philhower's tool or some other LittleFS uploader to upload the contents of the data folder into the SPIFFS partition.

The Books

The actual books themselves turned out to be the most time consuming part of this project. All books are public domain in Canada (and often in the US/UK as well), and I got all of the raw texts from Project Gutenberg, Project Gutenberg Canada, and Faded Page. These are great resources, although the UTF-8 formatting between books isn't particularly standardized. Some of the problems I ran into along the way:

I wrote a Python script to handle most of these problems automatically via regex patterns, although it couldn't identify beginning and ends of chapters. This meant copying each chapter individually, pasting it into a file, running the parsing script, and then pasting it into the book HTML file (and writing a chapter header manually). This was rather agonizing especially for long books with lots of chapters like A Farewell to Arms. Then I had to try to hunt down a cover for the book. Some books have nice covers in the public domain (The Great Gatsby for example), but a lot of these older books have very boring hardcovers or the artwork is still under copyright. For a substantial number of books, I ended up making the covers myself using public domain art, which was a fun exercise.

The HTML markdown of a book after being formatted.

Selecting the books was also a little difficult. Since I was handing these out, I had to try to include something for everyone. This meant restricting it to one book per author, and trying to select for a wide variety of genres and topics. A lot of the books are old, but still good, and I found myself spending a lot of time reading them while processing the texts. In the end, I managed to get 44 books with low-res covers into the pocket library, with the last book being added the night before leaving for the con. There's still a bit of space in the LittleFS, so you could probably squeeze a few more books in there if you wanted.

The Results

For a project with such a short deadline, I'm pretty happy with how it turned out. I handed out a bunch to people I met and dropped others in conspicuous areas to be picked up. They disappeared fast - I was almost out by the end of the second day. Two people reached out to me to let me know they had found one of the Pocket Libraries, which was nice. I hope wherever they went that they get used (or maybe reflashed and used as a dev board).

Maybe one day I'll return to the bigger, NAND-powered version of this project, but for the time being I think I don't want to see another UTF-8 formatted eBook for a while.