0.1.30
This commit is contained in:
@@ -27,8 +27,10 @@ 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, RedirectResponse
|
||||
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
|
||||
@@ -75,7 +77,7 @@ async def lifespan(app: FastAPI): # noqa: ARG001
|
||||
app = FastAPI(
|
||||
title="Fichero Printer API",
|
||||
description="REST API for the Fichero D11s (AiYin) thermal label printer.",
|
||||
version = "0.1.29",
|
||||
version = "0.1.30",
|
||||
lifespan=lifespan,
|
||||
docs_url=None,
|
||||
redoc_url=None,
|
||||
@@ -88,6 +90,13 @@ app.add_middleware(
|
||||
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)."""
|
||||
@@ -105,21 +114,67 @@ def _ui_html() -> str:
|
||||
return "<h1>Error: index.html not found</h1>"
|
||||
|
||||
# Simple substitution for initial values
|
||||
return (
|
||||
template = (
|
||||
template.replace("{default_address}", default_address)
|
||||
.replace("{ble_selected}", " selected" if default_transport == "ble" else "")
|
||||
.replace("{classic_selected}", " selected" if default_transport == "classic" else "")
|
||||
.replace("{default_channel}", str(_DEFAULT_CHANNEL))
|
||||
)
|
||||
|
||||
# Inject debug scan section and script
|
||||
scan_html = """
|
||||
<div class="section">
|
||||
<h2>Debug Scan</h2>
|
||||
<p>Scans for all nearby BLE devices to help with debugging connection issues.</p>
|
||||
<button type="button" onclick="scanForDevices()">Scan for BLE Devices (10s)</button>
|
||||
<pre id="scan-results" style="background-color: #f0f0f0; border: 1px solid #ccc; padding: 10px; margin-top: 10px; white-space: pre-wrap; word-wrap: break-word;"></pre>
|
||||
</div>
|
||||
"""
|
||||
scan_script = r'''
|
||||
<script>
|
||||
async function scanForDevices() {
|
||||
const resultsEl = document.getElementById('scan-results');
|
||||
resultsEl.textContent = 'Scanning for 10 seconds...';
|
||||
try {
|
||||
const response = await fetch('/scan');
|
||||
if (!response.ok) {
|
||||
const error = await response.json();
|
||||
throw new Error(error.detail || `HTTP error! status: ${response.status}`);
|
||||
}
|
||||
const devices = await response.json();
|
||||
if (devices.length === 0) {
|
||||
resultsEl.textContent = 'No BLE devices found.';
|
||||
} else {
|
||||
resultsEl.textContent = 'Found devices:\n\n' +
|
||||
devices.map(d => ` ${d.address} | RSSI: ${d.rssi} | ${d.name}`).join('\n');
|
||||
}
|
||||
} catch (e) {
|
||||
resultsEl.textContent = 'Error during scan: ' + e.message;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
'''
|
||||
# Inject before the closing </body> tag
|
||||
if "</body>" in template:
|
||||
parts = template.split("</body>", 1)
|
||||
template = parts[0] + scan_html + scan_script + "</body>" + parts[1]
|
||||
else:
|
||||
# Fallback if no body tag
|
||||
template += scan_html + scan_script
|
||||
|
||||
return template
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Endpoints
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@app.get("/", include_in_schema=False, response_class=HTMLResponse)
|
||||
async def root():
|
||||
async def root(legacy: bool = False):
|
||||
"""Serve a compact printer UI for Home Assistant."""
|
||||
# Prefer the modern SPA if available, unless ?legacy=true is used
|
||||
if not legacy and (_WEB_ROOT / "index.html").exists():
|
||||
return HTMLResponse((_WEB_ROOT / "index.html").read_text(encoding="utf-8"))
|
||||
return HTMLResponse(_ui_html())
|
||||
|
||||
|
||||
@@ -190,6 +245,26 @@ async def get_info(
|
||||
return info
|
||||
|
||||
|
||||
@app.get(
|
||||
"/scan",
|
||||
summary="Scan for BLE devices",
|
||||
response_description="List of discovered BLE devices",
|
||||
)
|
||||
async def scan_devices():
|
||||
"""Scan for nearby BLE devices for 10 seconds for debugging."""
|
||||
try:
|
||||
devices = await BleakScanner.discover(timeout=10.0)
|
||||
return [
|
||||
{"address": d.address, "name": d.name or "N/A", "rssi": d.rssi}
|
||||
for d in devices
|
||||
]
|
||||
except Exception as exc:
|
||||
# This provides more debug info to the user if scanning fails
|
||||
raise HTTPException(
|
||||
status_code=500, detail=f"An error occurred during BLE scanning: {exc}"
|
||||
)
|
||||
|
||||
|
||||
@app.post(
|
||||
"/pair",
|
||||
summary="Pair and trust a Bluetooth device",
|
||||
|
||||
Reference in New Issue
Block a user