# 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 image upload 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` (direct JPEG binary upload, no base64) - `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) - 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_*` 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 - upload a JPG image (binary payload) and let ESP32-S3 decode/threshold/scale/raster conversion - 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 (direct JPG upload, no base64) ```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/jpeg' \ --data-binary @./sample.jpg ``` Request constraints: - Request body limit: 4 MB - Suggested image size: <= 3 MB when using built-in Web UI uploader ### 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` accepts raw `image/jpeg` bytes directly (no base64 wrapper). - Large buffers for image upload/decode prefer PSRAM first, then fallback to internal RAM. - `/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). - Tap2talk uses server-side VAD and keeps uploading 100ms raw-opus frames in `Listening` state (silence when user is not tapping), matching `docs/websocket.md` requirements. ## Font Assets Embedded files: - `main/domain/assets/fonts/cn16_index.bin` - `main/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 ```