Firmware:
- fix 90° rotation (was transpose)
- fix Adafruit_Thermal constructor (spurious DTR pin arg)
- wire up uploadImage; heatInterval now applied
- PSRAM-free strip dithering: 2-row buffers (~1KB) replace 153KB PSRAM alloc
- consolidate all pins into config.h; BUTTON_PIN → Config::PIN_BUTTON
- constrain contrast/brightness/heat in settings save handler
- uploadImage size param int → size_t
Server:
- canonicalize upload_dir at startup (fixes path traversal guard)
- path traversal guard in serve_image
- replace unwrap in gallery_data with error handling
- IMAGE_WIDTH/IMAGE_HEIGHT named constants
Gallery:
- innerHTML → createElement (XSS-safe)
- encodeURIComponent on image URLs
- replace("_"," ") → regex /_/g
Docs: rewrite README, clarify GUEST_MANUAL placeholders
132 lines
3.7 KiB
Markdown
132 lines
3.7 KiB
Markdown
# Party Cam: Instant Thermal Camera & Digital Gallery
|
||
|
||
A DIY point-and-shoot that prints dithered lo-fi photos on thermal receipt paper and simultaneously uploads captures to a local Rust web server.
|
||
|
||
---
|
||
|
||
## Hardware BOM
|
||
|
||
| Part | Notes |
|
||
| :--- | :--- |
|
||
| ESP32-CAM (AI-Thinker) | Camera + WiFi module |
|
||
| Thermal Printer | TTL serial, 5V–9V |
|
||
| 2× 18650 Li-Ion | In series = 7.4V; powers printer directly + 5V buck converter for ESP32 |
|
||
| Tactile button | Shutter trigger |
|
||
| FTDI adapter or Arduino Uno | For flashing only |
|
||
|
||
---
|
||
|
||
## Wiring
|
||
|
||
| ESP32-CAM Pin | Component | Function |
|
||
| :--- | :--- | :--- |
|
||
| **5V** | PSU 5V | Power in |
|
||
| **GND** | PSU GND | Common ground |
|
||
| **GPIO 12** | Button | Shutter (other leg → GND) |
|
||
| **GPIO 14** (U1T) | Printer RX | Data to printer |
|
||
| **GPIO 15** (U1R) | Printer TX | Data from printer |
|
||
| **GPIO 4** | Flash LED | Optional built-in flash |
|
||
|
||
All pin assignments are in `include/config.h`. Change them there if your wiring differs.
|
||
|
||
---
|
||
|
||
## Server Setup
|
||
|
||
Run the server on any machine on the same local network as the camera. It receives uploads and hosts the gallery.
|
||
|
||
### Option A: Docker Compose (recommended)
|
||
|
||
```bash
|
||
cd server
|
||
docker compose up -d
|
||
```
|
||
|
||
Images are saved to `server/party_images/` on the host. To change the gallery password, edit `GALLERY_PASSWORD` in `compose.yml`.
|
||
|
||
### Option B: Cargo (dev)
|
||
|
||
```bash
|
||
cd server
|
||
cargo run --release
|
||
```
|
||
|
||
Requires a `.env` file in the `server/` directory:
|
||
|
||
```ini
|
||
SERVER_HOST=0.0.0.0
|
||
SERVER_PORT=3000
|
||
GALLERY_PASSWORD=partytime
|
||
UPLOAD_DIR=./uploads
|
||
```
|
||
|
||
`GALLERY_PASSWORD` is required — the server panics at startup without it.
|
||
|
||
---
|
||
|
||
## Firmware Setup
|
||
|
||
### 1. Configure
|
||
|
||
Check `platformio.ini` and make sure `board` matches your actual hardware. The default is `4d_systems_esp32s3_gen4_r8n16` — change it to `ai_thinker_esp32-cam` if using a standard ESP32-CAM. Camera pin definitions in `include/config.h` are pre-set for AI-Thinker.
|
||
|
||
### 2. Flash (Arduino Uno as bridge)
|
||
|
||
Wire the Uno as a passthrough:
|
||
- Arduino RESET → Arduino GND (bypasses the Uno chip)
|
||
- Arduino Pin 0 (RX) → ESP32 U0R
|
||
- Arduino Pin 1 (TX) → ESP32 U0T
|
||
- **ESP32 IO0 → ESP32 GND** (enables download mode)
|
||
|
||
Click **Upload** in PlatformIO, then press RST on the ESP32 when "Connecting…" appears.
|
||
|
||
### 3. Boot
|
||
|
||
Disconnect IO0 from GND, press RST. The camera boots.
|
||
|
||
---
|
||
|
||
## First Boot Configuration
|
||
|
||
1. Power on. Camera broadcasts WiFi AP: **`PartyCam-Setup`**.
|
||
2. Connect your phone — a captive portal opens automatically (or go to `192.168.4.1`).
|
||
3. Select the venue WiFi and enter the password.
|
||
4. Set the **Upload URL** to `http://<server-machine-ip>:3000/upload`.
|
||
5. Adjust contrast/heat to taste. **Save & Reboot.**
|
||
|
||
The portal times out after 3 minutes. If it closes before you save, the camera boots in offline mode (printing still works, upload disabled).
|
||
|
||
---
|
||
|
||
## Settings Page
|
||
|
||
After first boot the settings page is always reachable at:
|
||
|
||
```
|
||
http://partycam.local
|
||
```
|
||
|
||
(or the camera's IP address on port 80). Use it to adjust contrast, brightness, flip/mirror, heat density, and the upload URL.
|
||
|
||
---
|
||
|
||
## Gallery
|
||
|
||
```
|
||
http://<server-ip>:3000/gallery
|
||
```
|
||
|
||
Open from any device on the same network. The browser will prompt for the gallery password. New photos appear automatically — the page polls every 5 seconds.
|
||
|
||
---
|
||
|
||
## Customisation
|
||
|
||
| What | Where |
|
||
| :--- | :--- |
|
||
| Pin assignments | `include/config.h` |
|
||
| Printer baud rate | `include/config.h` → `BAUD_RATE` |
|
||
| Default camera settings | `include/settings_service.h` → `AppSettings` struct defaults |
|
||
| Gallery password | `compose.yml` or `.env` |
|
||
| Capture resolution | `src/camera_service.cpp` → `config.frame_size` (update `IMAGE_WIDTH`/`IMAGE_HEIGHT` in `server/src/main.rs` to match) |
|