Skip to the content
PCBKitNew board
2 layers100 × 60 mmPrawnd, the printed circuit board in 3D

An ESP32-based, Plaud-style voice recorder. Press a button to start/stop recording; the device captures 16 kHz stereo audio to an SD card and POSTs each finished WAV to a webserver you configure on-device.

Size
100 × 60 mm
Copper layers
2
Thickness
1.6 mm
Parts
19
Nets
15
Pads
50
Tracks, vias
102, 0
Pours
1
Routed
Yes
KiCad
KiCad 10.0 (file 20260206)
Created
6 Jun 2026
Updated
27 Sep 2026

Overview

Prawnd is an open-source printed circuit board by hashtagcyber, from the hashtagcyber/prawnd repository on GitHub. It is a 2-layer board of 100 × 60 mm with 19 parts on 15 nets, routed with 102 tracks and 0 vias.

Its parts include 8 connectors, 3 capacitors, 1 resistor and 1 diode. It belongs with the ESP32 boards of this library.

README

README.md

Prawnd

An ESP32-based, Plaud-style voice recorder. Press a button to start/stop recording; the device captures 16 kHz stereo audio to an SD card and POSTs each finished WAV to a webserver you configure on-device.

  • Hardware — Seeed XIAO ESP32C6, 2× INMP441 I²S mics (stereo), SPI microSD adapter, push-button. Wiring diagrams live in hardware/wiring.md.
  • Firmware — Arduino-ESP32 via PlatformIO. Source in firmware/.
  • Webservice — Node.js + Fastify, SQLite-indexed, Dockerized. Source in server/.

[!CAUTION]

🔓 HTTPS uploads use setInsecure() — TLS certificate validation is OFF

When the configured Upload URL is https://…, the firmware uploads with WiFiClientSecure::setInsecure() (firmware/src/uploader.cpp), which does the TLS handshake but does NOT validate the server's certificate. The connection is encrypted, but there is no protection against a man-in-the-middle: anyone who can intercept the device's traffic can present a forged certificate, decrypt the upload, and steal the API key (which the device sends as Authorization: Bearer …).

This is a beta convenience only. Before any non-hobby/production use, pin a CA bundle (e.g. ISRG Root X1) via setCACert() and remove setInsecure(), and treat each device's API key as compromised if its network is ever untrusted. Rotate keys in the web UI if in doubt.

Quick start

bash
# 1. Bring the server up locally
cd server
cp .env.example .env            # then edit PRAWND_API_KEY
docker compose up -d --build
curl -s http://localhost:8080/healthz   # {"ok":true}

# 2. Build the firmware
cd ../firmware
pio run                                  # first run downloads toolchains (slow)

# 3. Flash it (with the XIAO plugged in via USB-C)
pio run -t upload
pio device monitor                       # 115200 baud

On first boot the device has no WiFi configured, so it brings up an AP named Prawnd-XXXX (password prawnd1234). Connect, browse to http://192.168.4.1, fill in WiFi creds + upload URL + API key, hit save. The device reboots into STA mode and is ready to record.

Server

Prereqs

  • Docker / Docker Compose (Rancher Desktop, OrbStack, or Docker Desktop all fine)

Build & run

bash
cd server
cp .env.example .env       # set PRAWND_API_KEY
docker compose up -d --build
docker compose logs -f prawnd

The container exposes port 8080, persists data under ./data/ on the host (SQLite at data/prawnd.sqlite, WAVs under data/audio/<device_id>/<yyyy>/<mm>/).

Browser UI

Visit http://localhost:8080/ for a built-in single-page UI: enter your API key once (cached in localStorage), filter by device id, and play back any recording inline. Audio elements stream from /recordings/:id/file?api_key=... directly. Caveat: the API key ends up in <audio src> attributes, so treat the URL as secret on the LAN — don't share screenshots of the DOM.

Endpoints

MethodPathAuthPurpose
GET/noBuilt-in HTML UI.
POST/uploadyesUpload a WAV body. Headers: Authorization: Bearer <key> (or X-API-Key: <key>), Content-Type: audio/wav, X-Device-Id: <id>, X-Client-Timestamp: <ms> (optional).
GET/recordings?limit=&cursor=&device_id=yesCursor-paginated list, newest first.
GET/recordings/:idyesJSON metadata.
GET/recordings/:id/fileyesRaw audio/wav download. Auth accepts ?api_key= query for <audio> playback.
GET/healthznoLiveness.

API key auth accepts any of: Authorization: Bearer <key> header, X-API-Key: <key> header, or ?api_key=<key> query param.

Manual upload (smoke test without firmware)

bash
curl -fsS -X POST \
  -H "Authorization: Bearer $PRAWND_API_KEY" \
  -H "Content-Type: audio/wav" \
  -H "X-Device-Id: bench" \
  --data-binary @some.wav \
  http://localhost:8080/upload
curl -fsS -H "Authorization: Bearer $PRAWND_API_KEY" http://localhost:8080/recordings | jq

Stop / clean

bash
docker compose down            # stop
rm -rf data && mkdir data      # wipe all recordings + db

Firmware

Prereqs

  • PlatformIO Core (brew install platformio on macOS)
  • A USB-C cable that carries data
  • A microSD card formatted FAT32

Build

bash
cd firmware
pio run

First run pulls the espressif32 platform, the Arduino-ESP32 framework, the toolchain, and the listed libs — expect several minutes on a fresh machine. Subsequent builds take seconds. The flashable binary lands at .pio/build/xiao_esp32c6/firmware.bin.

Flash

Plug the XIAO in via USB-C. The C6 enters bootloader automatically on most ports; if not, hold the BOOT button, tap RESET, then release BOOT.

The compiled artifact is .pio/build/xiao_esp32c6/firmware.bin (~1.1 MB).

bash
pio run -t upload                # build + flash
pio run -t upload -t monitor     # flash + open serial monitor

Set the port explicitly if PlatformIO can't auto-detect:

bash
pio run -t upload --upload-port /dev/cu.usbmodem<TAB>

Serial monitor

bash
pio device monitor               # 115200 baud (configured in platformio.ini)

Optional: battery-monitoring addon

A second PlatformIO env, xiao_esp32c6_batt, compiles in an optional MAX17048 LiPo fuel-gauge driver. The base xiao_esp32c6 build is unchanged — the addon is purely a compile-time flag (-DENABLE_BATTERY), so the standard USB-powered firmware carries no battery code.

bash
pio run -e xiao_esp32c6_batt              # build the battery variant
pio run -e xiao_esp32c6_batt -t upload    # build + flash it

Wire a MAX17048 breakout to the XIAO's I²C pins (D4=SDA, D5=SCL) and the BAT pads — see hardware/wiring.md. When a gauge is detected, GET /status gains batt_pct and batt_mv fields and the config page shows the charge level; with no gauge fitted it reports battery: n/a and behaves identically to the base firmware. The artifact lands at .pio/build/xiao_esp32c6_batt/firmware.bin.

First-time WiFi + upload setup

  1. After flash, the device has no NVS config → it boots into AP mode.
  2. From a phone or laptop, join the WiFi network Prawnd-XXXX (password prawnd1234, where XXXX is the last 4 hex of the MAC).
  3. Open http://192.168.4.1.
  4. Enter:
    • WiFi SSID + password (your home/office WiFi)
    • Upload URL — either:
      • Local server: http://<your-mac-IP>:8080/upload (plain HTTP), or
      • Hosted beta: https://prawnd.dev.cobl.io/upload (HTTPS — see the certificate-validation caution at the top of this README).
    • API key:
      • Local server: must match the server's PRAWND_API_KEY.
      • Hosted beta: log in at https://prawnd.dev.cobl.io with cobl.io and generate an upload key under API keys.
    • Device id (optional; auto-derived from MAC)
  5. Hit Save and reboot. The device reconnects in STA mode.

To find the device's IP after reboot, watch the serial monitor or check your router's DHCP list. GET http://<device-ip>/status returns its current state.

Troubleshooting the first build

The firmware/platformio.ini deliberately uses the pioarduino platform fork (https://github.com/pioarduino/platform-espressif32/releases/...) because the official platform-espressif32 still bundles Arduino-ESP32 v2.x, which has no ESP32-C6 support. Two snags you may hit on a fresh machine:

  • Could not find a version that satisfies the requirement cryptography from pip — your global pip is pointing at a local mirror that isn't serving. Override for the one command: PIP_INDEX_URL=https://pypi.org/simple/ pio run.
  • FileNotFoundError: 'package-postinstall.py' from the pioarduino esptool zip — the v4.8.6 release omits the post-install script that PlatformIO tries to run. Stub it once: touch ~/.platformio/packages/tool-esptoolpy/package-postinstall.py, then re-run pio run.

Recording flow

  • Short-press button — start recording (LED on board lights, serial logs path)
  • Short-press again — stop, patch WAV header, queue for upload, POST to server
  • Long-press ≥ 5 s — factory reset (wipes NVS, reboots into AP mode)

Pending uploads (WiFi down, server unreachable) land in /pending/ on the SD card and retry once a minute while the device is idle and connected. Successfully uploaded files move to /uploaded/.

End-to-end verification

  1. cd server && docker compose up -d — confirm /healthz returns 200.
  2. cd ../firmware && pio run -t upload && pio device monitor — flash and watch boot logs.
  3. Join the AP, configure with the upload URL pointing at your machine's LAN IP (NOT localhost — that's the device's own loopback).
  4. Press the button, speak for a few seconds, press again.
  5. Watch the serial log for Upload .../ -> 200.
  6. curl -fsS -H "Authorization: Bearer $PRAWND_API_KEY" http://localhost:8080/recordings | jq — your recording shows up at the top.
  7. Pull the file: curl -fsS -H "Authorization: Bearer $PRAWND_API_KEY" http://localhost:8080/recordings/<id>/file -o out.wav and play in VLC/Audacity — both L and R channels should be populated.

Project layout

text
prawnd/
├── README.md
├── hardware.md                # high-level spec
├── hardware/
│   └── wiring.md              # per-device wiring diagrams + pin map
├── firmware/
│   ├── platformio.ini
│   ├── include/pins.h
│   └── src/                   # main, audio, wav, sd, wifi, portal, uploader, button, config,
│                              #   battery (optional MAX17048 addon, -DENABLE_BATTERY)
└── server/
    ├── Dockerfile
    ├── docker-compose.yml
    ├── package.json
    ├── .env.example
    └── src/                   # server.js, db.js, auth.js, wav.js,
                               #   routes/ (upload, recordings, health, ui)

Hardware Links

I purchased the following parts on Amazon:

Parts

19
QtyValueFootprintReferences
2100nC_Disc_D5.0mm_W2.5mm_P5.00mmC1, C2
2LanyardMountingHole_4mmH1, H2
1+3V3TestPoint_Pad_2.0x2.0mmTP1
110uCP_Radial_D5.0mm_P2.50mmC3
1120RR_Axial_DIN0207_L6.3mm_D2.5mm_P2.54mm_VerticalR1
1BTNPinHeader_1x02_P2.54mm_VerticalJ8
1ESP32 LPinHeader_1x07_P2.54mm_VerticalJ1
1ESP32 RPinHeader_1x07_P2.54mm_VerticalJ2
1GNDTestPoint_Pad_2.0x2.0mmTP2
1LED_D10.0mmLED_THT:LED_D10.0mmD1
1Mic1 LPinHeader_1x03_P2.54mm_VerticalJ4
1Mic1 RPinHeader_1x03_P2.54mm_VerticalJ3
1Mic2 LPinHeader_1x03_P2.54mm_VerticalJ6
1Mic2 RPinHeader_1x03_P2.54mm_VerticalJ5
1SCLTestPoint_Pad_2.0x2.0mmTP4
1SD CardPinHeader_1x06_P2.54mm_VerticalJ7
1SDATestPoint_Pad_2.0x2.0mmTP3

Questions

How big is the Prawnd PCB?

It measures 100 × 60 mm and has 2 copper layers, 1.6 mm thick.

What parts does the Prawnd use?

19 parts. The full list of its parts is on this page, with links to each part's symbol and footprint where PCBKit's library has them.

Where can I download the Prawnd KiCad files?

From the hashtagcyber/prawnd repository on GitHub, at the commit this page was made from: the board is hardware/carrier/Prawnd.kicad_pcb and its schematic hardware/carrier/Prawnd.kicad_sch. PCBKit opens the board in the browser, with nothing to install.

Can I use the Prawnd design in my own project?

Its repository states no licence, so its author keeps all rights: ask hashtagcyber before reusing the design.

Similar boards

  • 2 layers43.9 × 27.7 mmTcd1304-4v, the printed circuit board in 3D

    Tcd1304-4v

    OpnTec/open-spectrometer-hardware

    Open source VIS spectrometer

    19 parts1.5k
  • 2 layers50 × 60 mmDebate Device, the printed circuit board in 3D

    Debate Device

    hackclub/OnBoard

    💡 Join 1,000 teenagers and make your first PCB with a $100 grant!

    19 parts915
  • 2 layers102.9 × 96.7 mmISA adapter, the printed circuit board in 3D

    ISA adapter

    polpo/picogus

    Emulation of ISA sound cards and CD-ROM on Raspberry Pi Pico (GUS, SB/Adlib, MPU-401, Tandy, CMS, Panasonic CD-ROM) with USB mouse/joystick support

    19 parts805
  • 2 layers60 × 39 mmPs2usb, the printed circuit board in 3D

    Ps2usb

    rasteri/HIDman

    Adapting USB devices to work on old computers

    19 parts432
  • 2 layers70.1 × 43.2 mmIaq device, the printed circuit board in 3D

    Iaq device

    nkitanov/iaq_board

    IAQ Board is a DIY (Do-It-Yourself) device for measuring internal air quality

    19 parts396
  • 2 layers80 × 80 mmGB BRK CPU G1, the printed circuit board in 3D

    GB BRK CPU G1

    Gekkio/gb-hardware

    Game Boy -related custom hardware by gekkio

    19 parts379CC-BY-4.0
  • 2 layers25.4 × 25.4 mmIna169, the printed circuit board in 3D

    Ina169

    Ardumower/ardumower

    Develop an open source robotic lawn mower (HW+SW reference platform)

    19 parts282
  • 2 layers58.4 × 28.6 mmNew rele, the printed circuit board in 3D

    New rele

    zmaker/arduino_cookbook

    gli sketch del nuovo progetto Arduino Cookbook e dei video di YouTube

    19 parts269