# 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 -> BLE printer (`TQPrinter` / `lyfPrinter`, `FFF2` write / `FFF1` notify) - Command compatibility: - Print path: `0x00~0x07` - OTA path: `0xA0~0xA4` ## Features - Wi-Fi STA-only mode (configured SSID/password; no SoftAP fallback) - Built-in web UI at `/` for connect/status, text print, and Z-Image prompt print - BLE central client auto-scan/connect to printer name - 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` - Android-compatible print flow: - `0x00` power on - `0x03` set print param - `0x04` chunked raster send + ACK - `0x00` power off - `0x02` feed paper - REST APIs: - 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/qr` - `POST /v1/print/text` - `POST /v1/print/receipt` - `POST /v1/print/label` - Jobs: - `GET /v1/jobs` - `DELETE /v1/jobs` - `GET /v1/jobs/{id}` - `DELETE /v1/jobs/{id}` - Label control: - `POST /v1/label/gap_move` - `GET /v1/label/offset` - `POST /v1/label/offset` - OTA: - `GET /v1/ota/version` - `POST /v1/ota/jump_boot` - `POST /v1/ota/jump_app` - `POST /v1/ota/erase_page` - `POST /v1/ota/write_frame` - `POST /v1/ota/upgrade` - 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_SSID` - `TQ_WIFI_PASSWORD` - `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: ``` STA-only behavior: - If SSID is empty, startup fails. - If STA connect fails, startup fails (no AP fallback). ## Flash ```bash cd ai_printer idf.py -p /dev/tty.usbmodemXXXX flash monitor ``` ## Web UI Open the controller IP in a browser: ```text http:/// ``` The page provides: - health/status check - connect/disconnect printer - submit a simple text print job - input an image prompt, then let ESP32-S3 call DashScope Z-Image, download PNG, decode/threshold/scale/raster conversion, and print - view jobs list If API key is enabled, input it in the page before invoking actions. ## REST Examples ### Health ```bash curl http:///v1/health ``` ### Connect printer ```bash curl -X POST http:///v1/printer/connect \ -H 'Content-Type: application/json' \ -d '{"name":"TQPrinter","timeout_ms":15000}' # Legacy device name is also supported: # -d '{"name":"lyfPrinter","timeout_ms":15000}' ``` Auto-match a compatible printer (by BLE service `0xFFF0`): ```bash curl -X POST http:///v1/printer/connect \ -H 'Content-Type: application/json' \ -d '{"name":"*","timeout_ms":20000}' ``` ### Raster print ```bash curl -X POST http:///v1/print/raster \ -H 'Content-Type: application/json' \ -d '{ "width":384, "height":200, "density":"中等", "encoding":"base64_msb_1bpp", "data":"" }' ``` ### Image print (generate with prompt, then print) ```bash curl -X POST http:///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:///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 ### QR print ```bash curl -X POST http:///v1/print/qr \ -H 'Content-Type: application/json' \ -d '{ "text":"https://example.com/pay/123", "ecc":"H", "module_scale":0, "margin_modules":2, "density":"中等" }' ``` ### Text print ```bash curl -X POST http:///v1/print/text \ -H 'Content-Type: application/json' \ -d '{ "text":"欢迎使用TQ打印机\\n订单号: A1024\\n谢谢惠顾", "density":"中等", "scale":2, "line_spacing":2, "max_height":1800 }' ``` ### Receipt print ```bash curl -X POST http:///v1/print/receipt \ -H 'Content-Type: application/json' \ -d '{ "title":"TQ FOOD", "density":"中等", "footer":"Thanks!", "items":[ {"name":"DishA","qty":2,"price":12.5}, {"name":"DishB","qty":1,"price":8.0} ] }' ``` ### Label print (with optional gap/offset) ```bash curl -X POST http:///v1/print/label \ -H 'Content-Type: application/json' \ -d '{ "width":384, "height":260, "encoding":"base64_msb_1bpp", "data":"", "gap_move_before":true, "offset_tenths_mm":128, "density":"中等" }' ``` ### Jobs ```bash curl http:///v1/jobs curl http:///v1/jobs/1 curl -X DELETE http:///v1/jobs/1 curl -X DELETE http:///v1/jobs -d '{"include_success":true,"include_failed":true,"include_canceled":true}' ``` ### Label control ```bash curl -X POST http:///v1/label/gap_move curl http:///v1/label/offset curl -X POST http:///v1/label/offset \ -H 'Content-Type: application/json' \ -d '{"offset_tenths_mm":128}' ``` ### OTA primitives ```bash curl http:///v1/ota/version curl -X POST http:///v1/ota/jump_boot curl -X POST http:///v1/ota/erase_page -H 'Content-Type: application/json' -d '{"page_num":0}' curl -X POST http:///v1/ota/write_frame -H 'Content-Type: application/json' -d '{"packet_num":0,"is_last_frame":false,"data":""}' curl -X POST http:///v1/ota/jump_app ``` ### OTA full upgrade ```bash curl -X POST http:///v1/ota/upgrade \ -H 'Content-Type: application/json' \ -d '{ "firmware":"", "jump_boot":true, "jump_app":true, "page_size":1024, "packet_size":236, "timeout_ms_per_step":5000 }' ``` ### Voice tap2talk Start session: ```bash curl -X POST http:///v1/voice/session/start ``` Start one tap2talk round: ```bash curl -X POST http:///v1/voice/tap/start ``` Cancel current tap2talk round: ```bash curl -X POST http:///v1/voice/tap/cancel ``` Get voice status: ```bash curl http:///v1/voice/status ``` Stop session: ```bash curl -X POST http:///v1/voice/session/stop ``` ## Notes - BLE side is central/client role, not printer peripheral role. - `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). - 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. - `/v1/print/text` and `/v1/print/receipt` now support UTF-8 Chinese via embedded 16x16 GB2312 glyphs. - Characters outside embedded glyph set are rendered as square fallback boxes. - QR encoding uses embedded `qrcodegen` (Project Nayuki C implementation). - Voice flow is now single-round: audio uplink stops after `SpeechEnded`, then `LocalRespondingEnded` and `Stop` are sent after local playback drain. - Web UI blocks starting the next voice 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 ```