VoiceNotes is an Arduino sketch for the Waveshare ESP32-C6-LCD-0.85 that records meetings and voice memos to microSD and turns them into notes: - Records 16 kHz mono WAV (up to 4 h) from the ES7210 dual mics, with a boosted preamp and soft-limited digital gain - Transcribes via an OpenAI-compatible speech-to-text API in ~10 min parts cut at quiet points, with retries and resume after failures - Summarizes the transcript with Claude into meeting notes (decisions, action items, topics with timestamps), streamed to and from the card - Copies the notes to Nextcloud over WebDAV - Shows recording/processing state on the 0.85" LCD and the LED ring - Works around the board's SD card failing multi-sector reads Credentials live in VoiceNotes/secrets.h, which is git-ignored; see secrets.example.h. Also adds the Microsoft CDM customer service SQL implementation (DDL, sample data, reporting queries and docs). Its README is renamed to CDM_README.md so the repository README stays at the top level. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
VoiceNotes — voice recorder + AI notes for the Waveshare ESP32-C6-LCD-0.85
Record meetings (up to 4 hours) or voice memos to the microSD card. Then, with one button press, the device transcribes the newest recording and has Claude turn it into meeting notes: decisions, action items and topics. The notes are saved on the card.
Buttons
The board has two usable buttons (BOOT and PLUS). The PWR key is wired to the power-switch chip, so the firmware can't read it. "Button 3" is a long-press:
| Action | Press | What happens |
|---|---|---|
| Button 1 – record | PLUS click | Starts a new recording |
| Button 2 – stop | BOOT click | Stops and saves the WAV file |
| Button 3 – AI notes | PLUS hold 1 s | Transcribes the latest recording and writes a summary |
If you'd rather have a real third button, wire a push-button from a free GPIO to
GND and set PIN_BTN_EXTRA in config.h.
Files on the microSD card
/recordings/REC_2026-10-02_14-30-05.wav 16 kHz mono 16-bit WAV
/recordings/last.txt remembers the newest recording across reboots
/notes/REC_2026-10-02_14-30-05_transcript.txt transcript, with [hh:mm:ss] markers every ~10 min
/notes/REC_2026-10-02_14-30-05_transcript.progress only while a transcription is unfinished (used to resume)
/notes/REC_2026-10-02_14-30-05_summary.md Claude's notes: summary, decisions, action items, topics
The timestamp comes from NTP over Wi-Fi (the ESP32-C6 has no battery-backed
clock). If Wi-Fi isn't up when you record, the file is named REC_nodate_xxxxxxxx.wav.
Nextcloud
When the notes are ready, the summary and transcript are also copied to your Nextcloud over
WebDAV, into NEXTCLOUD_FOLDER (the folder is created if it doesn't exist). Set it up in secrets.h:
#define NEXTCLOUD_HOST "nc.example.com"
#define NEXTCLOUD_USER "you@example.com"
#define NEXTCLOUD_PASSWORD "xxxxx-xxxxx-xxxxx-xxxxx-xxxxx" // an app password
#define NEXTCLOUD_FOLDER "secondbrain"
Use an app password, not your login password. Create one in Nextcloud under Settings → Security → Devices & sessions → Create new app password. It works even with two-factor authentication on, and you can revoke it without changing your main password.
If the copy fails, the notes are still on the SD card. Holding PLUS again retries only the
Nextcloud step; it doesn't redo the transcription or the summary. To regenerate a summary, delete
its _summary.md from the card first.
What the screen and LED ring show
| State | Screen | LED ring |
|---|---|---|
| Starting | Mic logo, "Connecting Wi-Fi…" | Amber spinner |
| Ready | "Ready", time of the last recording | Slow blue breathing (amber if Wi-Fi is down) |
| Recording | Blinking ● REC, timer (h:mm:ss after an hour), scrolling level meter | Red VU meter that moves with your voice |
| Saved | ✓, date/time, length and size | Green flash |
| Uploading | Progress bar, "Part 3 of 12" and % | Blue ring fills up with overall progress |
| Transcribing | Cyan spinner, part number | Cyan spinning comet |
| Summarizing | Purple spinner, characters received | Two purple comets |
| Saving to Nextcloud | Green spinner | Green spinning comet |
| Done | ✓ "Notes saved", file name or "Also saved to Nextcloud" | Green pulse |
| Error | ✗ and the reason | Red blinking |
| No microSD | Red card icon | Slow red blink |
The status bar shows the clock, the SD card, and Wi-Fi bars.
Setup
- Arduino IDE 2.x with the esp32 core 3.x by Espressif (Boards Manager).
- Install these libraries from Library Manager: GFX Library for Arduino (moononournation), FastLED, OneButton, U8g2, ArduinoJson (v7).
- Copy
secrets.example.htosecrets.hand fill in:- your Wi-Fi SSID and password, and your timezone,
- a speech-to-text API key (
STT_*). Any OpenAI-compatible/audio/transcriptionsendpoint works: OpenAI (whisper-1,gpt-4o-transcribe) or Groq. - an Anthropic API key for the summary (
ANTHROPIC_API_KEY).
- Board settings (Tools menu):
- Board: ESP32C6 Dev Module
- USB CDC On Boot: Enabled (so Serial Monitor works over USB-C)
- Flash Size: 8MB
- Partition Scheme: 8M with spiffs (3MB APP/1.5MB SPIFFS)
- Put a FAT32 microSD card in the slot, then upload.
From the command line:
arduino-cli compile --upload -p /dev/cu.usbmodem* -b esp32:esp32:esp32c6:CDCOnBoot=cdc,FlashSize=8M,PartitionScheme=default_8MB .
Open the Serial Monitor at 115200 baud to see state changes and any API errors. You can also send:
p (same as holding PLUS), n (print the latest transcript and summary), d (list recordings),
s (SD read test), and u<file name> (choose which recording p and n use).
How it works
| File | Role |
|---|---|
VoiceNotes.ino |
Setup, buttons, state machine |
audio.cpp |
ES7210 mic codec setup over I2C, I2S capture task, WAV writer |
cloud.cpp |
HTTPS: streams the WAV to speech-to-text, then streams Claude's reply into summary.md |
ui.cpp |
128×128 screens (drawn off-screen, then pushed to the LCD) and LED ring animations |
config.h |
Pin map and limits |
- Audio: the two onboard mics go through the ES7210 ADC at 16 kHz and are mixed to mono. A high-priority task fills a 2-second RAM buffer, and the main loop writes that buffer to the card. This way a slow SD write can't make the recording skip.
- Shared SPI bus: the LCD and the microSD use the same SPI pins, so every access takes
spiMutex. - Long recordings: the WAV header is updated every 10 s, so a power cut mid-meeting still leaves a
playable file. Recording stops at 4 hours (
MAX_RECORD_SECONDS), or earlier if the card is nearly full. - Transcription in parts: speech-to-text APIs accept at most 25 MB per file, so the recording is sent
in ~10-minute parts (
STT_SEGMENT_SECONDS). Each part is cut at the quietest moment in the 15 s before the target, so no word is split. The end of the previous part's text is sent as a hint, so names and spellings stay consistent. Each part is streamed straight from the card. - Resume: each part is retried up to 3 times. If it still fails (for example, Wi-Fi drops), the finished parts are kept, and holding PLUS again continues from where it stopped.
- SD reads use 512-byte (single-sector) reads. On this board the microSD fails multi-sector reads.
- Summary:
POST /v1/messagestoclaude-opus-5-5with streaming. A 2-hour transcript (~100 KB) is too big for the board's RAM, so the request body is streamed from the card, and the reply is written to the card as it arrives. The request setsfallbacks: "default"(betaserver-side-fallback-2026-07-01). If the model declines, the API retries on a fallback model instead of failing. - If the transcript was saved but the summary step failed, a long-press again reuses the saved transcript and doesn't upload the audio a second time.
Limits and notes
- Recordings use about 115 MB per hour (16 kHz mono WAV). A 2-hour meeting is ~230 MB.
- Processing a 2-hour meeting takes roughly 15–25 minutes: 12 parts, each about a minute to upload at ~300 KB/s plus 20–40 s to transcribe, then a minute or two for Claude. The device can't record while it's processing.
- For multi-hour meetings, run the device from USB power. A small battery won't last.
- Privacy: your audio goes to the speech-to-text provider, and the transcript goes to Anthropic.
- TLS certificates are verified with the ESP32 core's built-in CA bundle.
Troubleshooting
| Symptom | Fix |
|---|---|
| LED colours wrong (red shows green) | Set LED_COLOR_ORDER to GRB in config.h |
| Recordings are quiet or noisy | Change MIC_GAIN_STEP in config.h (10 = 30 dB) |
| "Microphone init failed" | The ES7210 didn't answer at I2C 0x40. Power-cycle the board |
STT HTTP 401 / Claude HTTP 401 |
Check the API keys in secrets.h |
File names say nodate |
Wi-Fi/NTP wasn't ready yet. Wait for the clock in the status bar |
| Serial warns that audio was dropped | Use a faster (Class 10 / U1) microSD card |