A pocket voice-note device that records your voice, transcribes it, and uses AI to turn rambling speech into clean, coherent notes β synced straight to a GitHub repo and ready for Obsidian.
Hold a button, talk, let go. A few seconds later a tidy Markdown note β with an AI-written title, a one-line summary, and a cleaned-up body β appears in your notes vault. The raw transcript is always preserved, too.
- One-button voice capture on a tiny e-ink device β no screen-tapping, no phone.
- On-device transcription via OpenAI Whisper.
- AI note cleanup (the headline upgrade): a language model rewrites your messy, filler-filled speech into coherent, succinct prose, generates a title, a one-sentence summary, and topic links β automatically, every time you sync.
- Verbatim safety net: the original raw transcript is tucked into a foldable callout, so nothing you said is ever lost.
- Syncs to GitHub as clean Markdown with YAML frontmatter and tags β drop it into Obsidian and your notes organise themselves.
- No app, no account lock-in, no cloud middleman β it talks directly to OpenAI and to your GitHub repo.
Forrest Note is built on the original Pala Note firmware β full credit to its original author for the hardware bring-up, the e-ink/audio/codec drivers, the recording engine, and the device UI. This project stands entirely on that foundation.
Original project: Pala Note β https://ko-fi.com/s/674a1a82e0 Huge thanks to the Pala Note author for the original device and firmware that made this possible.
Credit where it's due β the original firmware already provided: voice recording, Whisper transcription, a local note-transfer web server, on-device tags, sleep/power management, and all the low-level e-ink, audio (ES8311/ES7210) and codec drivers β plus the physical device and 3D-printable case design.
Everything below is new or changed on top of that foundation:
| Upgrade | Type | What changed |
|---|---|---|
| π€ AI note cleanup | β New | A gpt-4o-mini pass rewrites each transcript into coherent, succinct prose β removing "um", false starts, and repetition while preserving every fact, name, and number. |
| π§ AI metadata | β New | Auto-generated note title, one-line summary callout, and topic backlinks ([[wikilinks]]) plus auto-built tag index ("MOC") pages. |
| ποΈ Original transcript preserved | β New | The verbatim transcript is kept in a foldable > [!quote]- Original transcript callout under the clean version. |
| βοΈ GitHub β Obsidian sync | β New | Notes are pushed to your own GitHub repo as Markdown via the GitHub Contents API, ready for Obsidian. |
| πΆ Runtime provisioning | β New | A ForrestNote-Setup Wi-Fi hotspot + captive portal stores Wi-Fi and keys in on-device NVS β replacing the original's hardcoded secrets.h. |
| π Real TLS validation | π Changed | HTTPS now validates against the Mozilla CA bundle β replacing the original's insecure setInsecure(). |
| π OTA updates | β New | Firmware can be updated over the air from the portal (/ota), backed by a dual-OTA custom partition table (partitions.csv). |
| π Chunked-HTTP fix | β New | The HTTPS client now decodes Transfer-Encoding: chunked responses β which is what makes the OpenAI chat (enrichment) calls actually work. Without it, enrichment silently fails. |
| π Snappier level meter | π Changed | The live recording VU meter refresh rate was increased (~2 Hz β ~10 Hz), plus async/coalesced e-paper refresh for responsive UI. |
| π Zero secrets in code | π Changed | Wi-Fi and API keys live on-device, not in the repo. |
| π·οΈ Title-named files | π Changed | Each note is saved under its one-word topic (Soho.md) instead of note_001.md, so Obsidian shows a real title. Names are checked against the vault and auto-suffixed (Soho 2.md) on collision, so same-named notes never merge or re-link. |
| βοΈ One-word topic titles | π Changed | The AI title is a single topic word (a clean filename + display name); the full context still lives in the summary and body. |
| π Calendar events | β New | When a note describes a dated plan ("going to Soho House tomorrow", "dentist next Friday at 3"), the AI extracts it into event_* frontmatter (resolving "tomorrow" against the note's date). A small bridge can turn that into a real calendar event β see Calendar sync. |
| π§Ή Two-way delete | β New | Deleting a note on the device also removes its file from the GitHub vault and updates the tag index β via an offline-safe queue that drains on the next sync. |
| ποΈ Erase All | β New | Settings β Erase All wipes every note from the SD card, with a choice of Device only (keep the vault) or Device + GitHub (also clear the vault). |
This firmware is built for the Waveshare ESP32-S3 1.54β³ e-Paper AIoT Development Board β a single board that integrates the MCU, display, audio, storage, sensors, and battery charging:
π Reference board: Waveshare ESP32-S3 1.54inch e-Paper AIoT Dev Board (the B/W, non-"G" variant). Buy this exact board, print the original Pala Note case, flash this firmware β done.
- MCU: ESP32-S3 (Xtensa LX7 dual-core @ 240 MHz) β N8R8 stacked package: 8 MB flash + 8 MB OCTAL (OPI) PSRAM
- Display: 1.54β³ 200Γ200 e-paper (black/white)
- Audio: onboard low-power audio codec + microphone & speaker (ES8311 / ES7210)
- Storage: microSD / TF card (SD_MMC, 1-bit)
- Extras: RTC chip, SHTC3 temp/humidity sensor, LiPo battery + onboard charge management
- Buttons: Record (GPIO0 / BOOT) and Power (GPIO18)
- Wireless: 2.4 GHz Wi-Fi + BLE 5
Note: there's also a four-colour "1.54G" variant β this firmware targets the black/white board.
The printable enclosure (front & back housing, button, and an assembled STEP reference) is the original creator's hardware design and is not redistributed here. Download the case files from the original Pala Note project: https://ko-fi.com/s/674a1a82e0.
- The device (assembled Pala Note hardware) and a USB-C cable.
- A computer (macOS/Linux/Windows).
- An OpenAI API key β from https://platform.openai.com/api-keys. (Used for Whisper transcription + note cleanup. Billing must be enabled.)
- A GitHub repo to store your notes (can be private) and a fine-grained Personal Access Token with Contents: Read and write on that repo β from https://github.com/settings/tokens.
- Your 2.4 GHz Wi-Fi name + password (the ESP32-S3 Wi-Fi is 2.4 GHz only).
You do not put any of these into the code. You enter them on the device's setup hotspot (see Setup, below).
There are two paths: the easy way (let Claude Code do it for you) and the manual way. Both end the same place: firmware flashed onto your device.
If you have Claude Code installed, cd into this repo and paste these prompts one at a time. Each one is self-contained.
1. Install the toolchain & build the firmware:
Set up my environment to build this Forrest Note ESP32-S3 firmware. Install arduino-cli if missing,
install the esp32 board core version 3.2.0, and install the "Adafruit GFX Library" and "ArduinoJson"
libraries. Then compile the sketch in ./forrest_note for an ESP32-S3 N8R8 board using these options:
PSRAM=opi, PartitionScheme=custom, CDCOnBoot=cdc, FlashSize=8M. Report any errors.
2. Flash it to the device:
Flash the Forrest Note firmware in ./forrest_note to my connected ESP32-S3 device. The board only
stays on its USB port if it's in the ROM bootloader, so walk me through this: tell me to HOLD the
record button (BOOT/GPIO0), plug in USB while holding, and keep holding until the write finishes.
Detect the serial port, then run the upload with the board options PSRAM=opi, PartitionScheme=custom,
CDCOnBoot=cdc, FlashSize=8M. Confirm when the hash is verified.
3. (Optional) Set up an Obsidian vault that auto-pulls your notes:
I want my Forrest Note notes (which the device pushes to my GitHub repo OWNER/REPO under the
VoiceNotes/ folder) to show up in Obsidian. Clone that repo into a local folder, open it as an
Obsidian vault, and configure the Obsidian Git community plugin to auto-pull every minute and on
launch so new notes appear automatically. Don't push my Obsidian config back to the repo.
Replace
OWNER/REPOwith your actual repo. After flashing, continue to Setup below to provision the device.
1. Install arduino-cli (or use the Arduino IDE β board/lib names are identical).
# macOS (Homebrew)
brew install arduino-cli
# or: curl -fsSL https://raw.githubusercontent.com/arduino/arduino-cli/master/install.sh | sh2. Install the ESP32 board core (version 3.2.0) and libraries:
arduino-cli config init
arduino-cli config add board_manager.additional_urls \
https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json
arduino-cli core update-index
arduino-cli core install esp32:esp32@3.2.0
arduino-cli lib install "Adafruit GFX Library" "ArduinoJson"3. Compile (run from the repo root):
arduino-cli compile \
-b "esp32:esp32:esp32s3:PSRAM=opi,PartitionScheme=custom,CDCOnBoot=cdc,FlashSize=8M" \
./forrest_noteYou should see something like Sketch uses ~1.43 MB (8%) of program storage space.
4. Flash it.
- Press and hold the record button (BOOT / GPIO0).
- While holding, plug in the USB-C cable.
- Keep holding through the entire write.
Find the port and upload:
# find the port (macOS shows /dev/cu.usbmodemXXXX ; Linux /dev/ttyACM0 ; Windows COMx)
arduino-cli board list
arduino-cli compile --upload -p /dev/cu.usbmodemXXXX \
-b "esp32:esp32:esp32s3:PSRAM=opi,PartitionScheme=custom,CDCOnBoot=cdc,FlashSize=8M" \
./forrest_noteWhen you see Hash of data verified. and Hard resetting..., release the button. Done.
Arduino IDE alternative: open
forrest_note/forrest_note.ino, select board ESP32S3 Dev Module, then set: PSRAM β OPI PSRAM, Partition Scheme β custom (uses the includedpartitions.csv), USB CDC On Boot β Enabled, Flash Size β 8MB. Hold BOOT, plug in, click Upload.
The firmware ships with no Wi-Fi or keys baked in. On first boot (or any time it has no Wi-Fi credentials) it hosts a setup hotspot.
- Power on the device. With no Wi-Fi stored, it broadcasts an open Wi-Fi network called
ForrestNote-Setup. - Connect your phone or laptop to
ForrestNote-Setup. - Open a browser to
http://192.168.4.1(most phones pop up the captive portal automatically). You'll land on the forrest setup page. - Fill in the form:
Field What to enter Wi-Fi network (SSID) Your 2.4 GHz Wi-Fi name Wi-Fi password Your Wi-Fi password OpenAI API key (sk-...) Your OpenAI key GitHub repo (owner/name) e.g. yourname/NotesBranch main(default)Vault folder VoiceNotes(default)GitHub token (github_pat_...) Your fine-grained PAT with Contents R/W β Enable GitHub sync tick this on β AI titles + topic links tick this on (enables the AI cleanup) - Tap Save. The device stores everything in on-device NVS and reboots onto your Wi-Fi.
Tip: you can re-open this page any time the device is on your network at
http://<device-ip>/provision(or just reconnect to the setup hotspot). Leave any text field blank to keep its current value.
That's it β the device is now a personal AI note-taker.
| Action | Control |
|---|---|
| Record a note | Hold the record button while idle, speak, release |
| Stop recording | Release the record button |
| Scroll / next | Tap power |
| Select / open | Tap record |
| Back | Hold record |
| Delete a note | Hold power (while viewing a note) β also removes it from the vault on next sync |
| Erase all notes | Settings β Erase All β choose Device only or Device + GitHub (pwr switches, record selects, hold record cancels) |
After recording, the device transcribes and (when online) syncs to GitHub automatically. Each note becomes a Markdown file named after its topic, like VoiceNotes/Soho.md.
On sync, for each new note the firmware:
- Transcribes the audio with Whisper (
whisper-1). - Enriches it with
gpt-4o-mini, which returns: a one-word topic title, a one-sentence summary, a cleaned coherent rewrite of the body, up to 6 topics, and β when the note describes a dated plan β a calendar event ({title, start, end, allDay}, with relative dates like "tomorrow" resolved against the note's own date). - Writes Markdown (named after the topic, e.g.
Soho.md) and pushes it to your GitHub repo via the GitHub Contents API, plus updates a tag index page per tag.
A generated note looks like:
---
title: "Soho"
aliases: ["Soho"]
date: 2026-06-22T18:10:00Z
id: 11
uid: Soho
source: forrest-note
tags: ["Note"]
event_title: "Soho House"
event_start: 2026-06-23T19:00
event_end: 2026-06-23T21:00
event_allday: false
---
> [!summary] Planning to meet friends at Soho House tomorrow evening.
I'm heading to Soho House tomorrow at seven to meet a couple of friends...
> [!quote]- Original transcript
> So I'm going to Soho House tomorrow, like seven-ish, meeting up with...The event_* fields only appear when the note actually describes a plan with a date/time.
Because notes are plain Markdown with YAML frontmatter and tags, they work in Obsidian out of the box. To sync them into an Obsidian vault:
- Clone your notes repo locally.
- Open the folder as an Obsidian vault.
- Install the Obsidian Git community plugin and enable auto-pull on launch + an interval pull (1 min) so new device notes appear automatically.
The [[topic]] backlinks and per-tag index pages give you an auto-built map of content. (The Claude Code prompt in Installation β Option A, step 3 sets all of this up for you.)
Notes that mention a dated plan carry machine-readable event frontmatter (event_title, event_start, event_end, event_allday) β so "I'm going to Soho House tomorrow at 7" becomes:
event_title: "Soho House"
event_start: 2026-06-23T19:00
event_end: 2026-06-23T21:00
event_allday: falseAny automation that watches your vault can turn those into real calendar events. The firmware does the hard part (understanding the speech and resolving "tomorrow"); the bridge is just a few lines that read the frontmatter and create the event.
- Apple / iCloud Calendar (macOS): a small Python + AppleScript script, triggered by a
launchdagent that watches the notes folder, reads theevent_*fields and creates the event in a chosen calendar (idempotent via a state file). iCloud has no cloud API, so this runs on your Mac. - Google Calendar: a GitHub Action on your notes repo can create events via the Google Calendar API on each push β fully cloud-side, no computer needed.
Using Claude Code? Ask it: "Build me a bridge that watches my notes vault and creates an Apple Calendar event in my 'Event' calendar from each note's
event_*frontmatter, installed as a launchd agent."
All runtime config lives in the device's NVS (namespace forrest) and is set via the portal β never in code:
WIFI_SSID/WIFI_PASSβ Wi-Fi credentialsOPENAI_KEYβ OpenAI API key (Whisper + enrichment)- GitHub:
repo(owner/name),branch(defaultmain),dir(defaultVoiceNotes),token,enabled,ai-enrich
secrets.h exists only as an optional one-time seed and ships with "...." placeholders β you normally never touch it.
- Device won't stay on the USB port for flashing β that's expected; hold the record/BOOT button while plugging in and keep holding through the write.
- Build error about a duplicate
.cppβ delete any* 2.cpp/* copy.cppfiles that cloud-sync may have created undersrc/. - Boot loop / PSRAM errors β make sure you selected PSRAM = OPI and Flash Size = 8MB (this is an N8R8 module).
- Notes sync but have no AI summary β make sure "AI titles + topic links" is ticked in the portal and your OpenAI key has billing/chat access. (The chunked-HTTP fix in this fork is required for enrichment to work β make sure you flashed this firmware.)
- Wi-Fi won't connect β the ESP32-S3 is 2.4 GHz only; a 5 GHz-only network won't appear.
- No secrets in this repo. Keys and Wi-Fi are entered on-device and stored in NVS.
- Your notes go only to OpenAI (for transcription/cleanup) and to your own GitHub repo. There is no third-party server.
- If you ever paste a real key into
secrets.h, do not commit it (uncomment thesecrets.hline in.gitignorefirst).
Forrest Note's additions (the AI enrichment pipeline, the chunked-HTTP fix, the level-meter change, and this documentation) are released under the MIT License β see LICENSE.
The firmware is built on the original Pala Note project; please also honour the original author's license terms for their portions of the code.