Files
AI_Printer/README.md

265 lines
7.1 KiB
Markdown

# ESP32-S3 TQ Printer Controller (Wi-Fi REST API)
ESP32-S3 works as a Wi-Fi REST controller and replaces Android App logic:
- REST client -> ESP32-S3 (`esp_http_server`)
- ESP32-S3 -> onboard direct thermal printer driver
## Features
- Wi-Fi STA-only mode (configured SSID/password; no SoftAP fallback)
- Built-in minimal web UI at `/` for voice tap2talk
- Direct thermal printer control with sensor precheck
- Async print queue with jobs (`queued/running/success/failed/canceled`)
- Printer precheck (paper / battery / temperature)
- Built-in UTF-8 text rendering with Chinese support (GB2312 character set, 16x16 bitmap)
- Tap2talk multimodal voice interaction over DashScope WebSocket:
- ES8311 microphone/speaker via `esp_codec_dev`
- raw-opus uplink/downlink via `esp_audio_codec`
- WebSocket transport via `esp_websocket_client`
- REST APIs:
- Root:
- `GET /`
- Health/connection:
- `GET /v1/health`
- `POST /v1/printer/connect`
- `POST /v1/printer/disconnect`
- `GET /v1/printer/status`
- Print:
- `POST /v1/print/raster`
- `POST /v1/print/image` (JSON prompt -> generate image -> print, also compatible with direct PNG binary upload)
- `POST /v1/print/text`
- Jobs:
- `GET /v1/jobs`
- `GET /v1/jobs/{id}`
- Voice:
- `GET /v1/voice/status`
- `POST /v1/voice/session/start`
- `POST /v1/voice/session/stop`
- `POST /v1/voice/tap/start`
- `POST /v1/voice/tap/cancel`
## Build
Prerequisites:
- ESP-IDF v5.x
- ESP32-S3 board
```bash
cd ai_printer
idf.py set-target esp32s3
idf.py menuconfig
idf.py build
```
## Config
In `menuconfig -> TQ Controller Config`:
- `TQ_WIFI_PROV_SOFTAP_SSID`
- `TQ_WIFI_PROV_SOFTAP_PASSWORD`
- `TQ_WIFI_PROV_SOFTAP_CHANNEL`
- `TQ_WIFI_PROV_SOFTAP_MAX_CONN`
- `TQ_HTTP_PORT`
- `TQ_API_KEY` (optional)
- Z-Image HTTP auth and model fields:
- `TQ_Z_IMAGE_API_KEY` (optional, empty means fallback to `TQ_VOICE_API_KEY`)
- `TQ_Z_IMAGE_API_ENDPOINT`
- `TQ_Z_IMAGE_MODEL`
- `TQ_Z_IMAGE_DEFAULT_SIZE`
- `TQ_Z_IMAGE_TIMEOUT_MS`
- `TQ_Z_IMAGE_DOWNLOAD_TIMEOUT_MS`
- Voice WebSocket auth and app fields:
- `TQ_VOICE_API_KEY`
- `TQ_VOICE_WORKSPACE_ID`
- `TQ_VOICE_APP_ID`
- Voice audio and codec fields:
- `TQ_VOICE_SAMPLE_RATE`
- `TQ_VOICE_OPUS_FRAME_MS`
- `TQ_VOICE_OPUS_BITRATE_KBPS`
- `TQ_VOICE_I2C_*` / `TQ_VOICE_I2S_*`
- `TQ_VOICE_CODEC_*`
- Board GPIO map fields:
- `TQ_POWER_*`, `TQ_LED_*`, `TQ_SCREEN_*`, `TQ_PRINT_*`, `TQ_SPI_*`
- `TQ_KEY_PRINT_BOOST_ENABLE_ON_BOOT` / `TQ_KEY_PRINT_BOOST_ACTIVE_HIGH`
- ST7789 screen fields:
- `TQ_SCREEN_ENABLE`
- `TQ_SCREEN_TEST_PATTERN_ON_BOOT`
- `TQ_SCREEN_PIXEL_CLOCK_HZ`
- `TQ_SCREEN_SPI_MODE`
- `TQ_SCREEN_H_RES` / `TQ_SCREEN_V_RES`
- `TQ_SCREEN_DRAW_LINES`
- `TQ_SCREEN_COLOR_ORDER_BGR`
- `TQ_SCREEN_BACKLIGHT_ACTIVE_HIGH`
- `TQ_SCREEN_MIRROR_*` / `TQ_SCREEN_SWAP_XY`
- `TQ_SCREEN_X_GAP` / `TQ_SCREEN_Y_GAP`
- `TQ_SCREEN_RESET_PIN`
Board pin assignment reference:
- [`docs/gpio-map.md`](docs/gpio-map.md)
When `TQ_API_KEY` is not empty, each request must include:
```text
X-API-Key: <your-key>
```
Wi-Fi provisioning behavior:
- Device first tries saved STA credentials from NVS (`wifi_cfg` namespace).
- If no valid credentials exist or STA connect fails, device starts a SoftAP portal.
- Connect to the SoftAP and open `http://192.168.4.1`; the static page auto-scans nearby SSIDs for selection.
- Provisioning page supports Chinese/English switching and remembers the selected language in browser local storage.
- Credentials are written to NVS only after STA connect succeeds.
## Flash
```bash
cd ai_printer
idf.py -p /dev/tty.usbmodemXXXX flash monitor
```
## Web UI
Open the controller IP in a browser:
```text
http://<esp-ip>/
```
The page provides a minimal voice console:
- single-round tap2talk button
- voice status display
## REST Examples
### Health
```bash
curl http://<esp-ip>/v1/health
```
### Connect printer
```bash
curl -X POST http://<esp-ip>/v1/printer/connect \
-H 'Content-Type: application/json' \
-d '{"timeout_ms":15000}'
# Optional explicit backend field:
# -d '{"backend":"direct","timeout_ms":15000}'
```
### Raster print
```bash
curl -X POST http://<esp-ip>/v1/print/raster \
-H 'Content-Type: application/json' \
-d '{
"width":384,
"height":200,
"density":"中等",
"encoding":"base64_msb_1bpp",
"data":"<BASE64_BYTES>"
}'
```
### Image print (generate with prompt, then print)
```bash
curl -X POST http://<esp-ip>/v1/print/image \
-H 'Content-Type: application/json' \
-d '{
"prompt":"一只坐在窗边的橘猫,午后阳光,胶片质感,写实风格。",
"size":"1120*1440",
"prompt_extend":false,
"threshold":160,
"max_height":2200,
"scale_to_width":true,
"invert":false,
"density":"中等",
"timeout_ms":45000,
"fetch_timeout_ms":15000
}'
```
### Image print (direct PNG upload, backward-compatible)
```bash
curl -X POST 'http://<esp-ip>/v1/print/image?scale_to_width=1&threshold=160&invert=0&max_height=2200&density=medium' \
-H 'Content-Type: image/png' \
--data-binary @./sample.png
```
Request constraints:
- Request body limit: 4 MB
- Suggested image size: <= 3 MB when using direct PNG upload compatibility mode
### Text print
```bash
curl -X POST http://<esp-ip>/v1/print/text \
-H 'Content-Type: application/json' \
-d '{
"text":"欢迎使用TQ打印机\\n订单号: A1024\\n谢谢惠顾",
"density":"中等",
"scale":2,
"line_spacing":2,
"max_height":1800
}'
```
### Jobs
```bash
curl http://<esp-ip>/v1/jobs
curl http://<esp-ip>/v1/jobs/1
```
### Voice tap2talk
Start session:
```bash
curl -X POST http://<esp-ip>/v1/voice/session/start
```
Start one tap2talk round:
```bash
curl -X POST http://<esp-ip>/v1/voice/tap/start
```
Cancel current tap2talk round:
```bash
curl -X POST http://<esp-ip>/v1/voice/tap/cancel
```
Get voice status:
```bash
curl http://<esp-ip>/v1/voice/status
```
Stop session:
```bash
curl -X POST http://<esp-ip>/v1/voice/session/stop
```
## Notes
- `base64_msb_1bpp` uses Android-compatible bit order (MSB first).
- `/v1/print/image` supports two modes: JSON prompt generation (DashScope Z-Image) and raw `image/png` upload (no base64 wrapper).
- `/v1/print/text` supports UTF-8 Chinese via embedded 16x16 GB2312 glyphs.
- Characters outside embedded glyph set are rendered as square fallback boxes.
- Large buffers for image upload/decode prefer PSRAM first, then fallback to internal RAM.
- Partition table uses `partitions.csv` with a 4MB `factory` app partition on 16MB flash modules.
- Voice flow is now single-round: audio uplink stops after `SpeechEnded`, then `LocalRespondingEnded` and `Stop` are sent after local playback drain.
- Voice flow blocks starting the next round while voice is Thinking/Responding, marker image generation is running, or printer queue is still busy.
## Font Assets
Embedded files:
- `components/domain/assets/fonts/cn16_index.bin`
- `components/domain/assets/fonts/cn16_glyphs.bin`
Regenerate from your own CJK font:
```bash
cd ai_printer
python3 -m pip install --user pillow
python3 tools/gen_cn16_font.py \
--font app/src/main/assets/fonts/msyh.ttc \
--font-index 0
```