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 minimal web UI at / for voice tap2talk
  • 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:
    • 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
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:

When TQ_API_KEY is not empty, each request must include:

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

cd ai_printer
idf.py -p /dev/tty.usbmodemXXXX flash monitor

Web UI

Open the controller IP in a browser:

http://<esp-ip>/

The page provides a minimal voice console:

  • single-round tap2talk button
  • voice status display

REST Examples

Health

curl http://<esp-ip>/v1/health

Connect printer

curl -X POST http://<esp-ip>/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):

curl -X POST http://<esp-ip>/v1/printer/connect \
  -H 'Content-Type: application/json' \
  -d '{"name":"*","timeout_ms":20000}'

Raster print

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)

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)

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

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

curl http://<esp-ip>/v1/jobs
curl http://<esp-ip>/v1/jobs/1

Voice tap2talk

Start session:

curl -X POST http://<esp-ip>/v1/voice/session/start

Start one tap2talk round:

curl -X POST http://<esp-ip>/v1/voice/tap/start

Cancel current tap2talk round:

curl -X POST http://<esp-ip>/v1/voice/tap/cancel

Get voice status:

curl http://<esp-ip>/v1/voice/status

Stop session:

curl -X POST http://<esp-ip>/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).
  • /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:

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
Description
No description provided
Readme 165 MiB
Languages
C 67.4%
Python 29.2%
HTML 2.9%
CMake 0.5%