"""HTTP REST API for the Fichero D11s thermal label printer. Start with: fichero-server [--host HOST] [--port PORT] or: python -m fichero.api Endpoints: GET /status – Printer status GET /info – Printer info (model, firmware, battery, …) POST /print/text – Print a text label POST /print/image – Print an uploaded image file """ from __future__ import annotations import argparse import asyncio import io import re import os from contextlib import asynccontextmanager from pathlib import Path from typing import Annotated from fastapi import FastAPI, File, Form, HTTPException, UploadFile from fastapi.middleware.cors import CORSMiddleware from fastapi.openapi.docs import get_swagger_ui_html from fastapi.responses import HTMLResponse from fastapi.staticfiles import StaticFiles from PIL import Image from bleak import BleakScanner from fichero.cli import DOTS_PER_MM, do_print from fichero.imaging import text_to_image from fichero.printer import ( PAPER_GAP, PrinterError, PrinterNotFound, PrinterTimeout, connect, ) # --------------------------------------------------------------------------- # Global connection settings (env vars or CLI flags at startup) # --------------------------------------------------------------------------- _DEFAULT_ADDRESS: str | None = os.environ.get("FICHERO_ADDR") _DEFAULT_CLASSIC: bool = os.environ.get("FICHERO_TRANSPORT", "").lower() == "classic" _DEFAULT_CHANNEL: int = int(os.environ.get("FICHERO_CHANNEL", "1")) _PAPER_MAP = {"gap": 0, "black": 1, "continuous": 2} def _parse_paper(value: str) -> int: if value in _PAPER_MAP: return _PAPER_MAP[value] try: val = int(value) if 0 <= val <= 2: return val except ValueError: pass raise HTTPException(status_code=422, detail=f"Invalid paper type '{value}'. Use gap, black, continuous or 0-2.") # --------------------------------------------------------------------------- # FastAPI app # --------------------------------------------------------------------------- @asynccontextmanager async def lifespan(app: FastAPI): # noqa: ARG001 yield app = FastAPI( title="Fichero Printer API", description="REST API for the Fichero D11s (AiYin) thermal label printer.", version = "0.1.34", lifespan=lifespan, docs_url=None, redoc_url=None, ) app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"], ) # Serve static files for the modern web UI (if built and present in 'web' dir) _WEB_ROOT = Path(__file__).parent / "web" if _WEB_ROOT.exists(): # Typical SPA assets folder if (_WEB_ROOT / "assets").exists(): app.mount("/assets", StaticFiles(directory=_WEB_ROOT / "assets"), name="assets") def _address(address: str | None) -> str | None: """Return the effective BLE address (request value overrides env default).""" return address or _DEFAULT_ADDRESS def _ui_html() -> str: default_address = _DEFAULT_ADDRESS or "" default_transport = "classic" if _DEFAULT_CLASSIC else "ble" try: template_path = Path(__file__).parent / "index.html" template = template_path.read_text(encoding="utf-8") except FileNotFoundError: return "
Scans for all nearby BLE devices to help with debugging connection issues.
📱 Click "Scan for BLE Devices" to search for nearby Bluetooth devices...