feat: printer beep support for kitchen tickets
Adds configurable buzzer control so the printer alerts kitchen staff
on each new ticket print.
Backend:
- printer_service.py: ESC 0x07 beep command fired before paper cut,
reads print.beep_on_ticket / print.beep_pattern settings; supports
single/double/triple/long presets and a custom:n1:n2:n3 format
- settings.py: registers the two new print.beep_* settings with
defaults (beep_on_ticket=true, beep_pattern=double)
- system.py: POST /api/system/printers/test-beep endpoint for
live testing from the dashboard
Frontend (PrintFontsTab):
- BeepSection component with preset grid, custom n1/n2/n3 inputs,
and a live test-beep button targeting any configured printer
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -37,6 +37,9 @@ VALID_SETTINGS = {
|
||||
"print.font_ingredient": "Font for removed ingredient lines (- marker): SIZE:BOLD:CAPS",
|
||||
"print.font_item_note": "Font for per-item note lines: SIZE:BOLD:CAPS",
|
||||
"print.font_order_note": "Font for order-level notes: SIZE:BOLD:CAPS",
|
||||
# Beep settings
|
||||
"print.beep_on_ticket": "Play beep when a kitchen ticket prints: 'true' | 'false'",
|
||||
"print.beep_pattern": "Beep pattern: 'single' | 'double' | 'triple' | 'long' | 'custom:n1:n2:n3'",
|
||||
}
|
||||
|
||||
DEFAULTS = {
|
||||
@@ -63,6 +66,8 @@ DEFAULTS = {
|
||||
"print.font_ingredient": "0:0:0",
|
||||
"print.font_item_note": "0:0:0",
|
||||
"print.font_order_note": "0:1:0",
|
||||
"print.beep_on_ticket": "true",
|
||||
"print.beep_pattern": "double",
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -133,6 +133,15 @@ def test_order_print(printer_id: int, db: Session = Depends(get_db), user: User
|
||||
return {"success": success, "error": error}
|
||||
|
||||
|
||||
@router.post("/printers/test-beep")
|
||||
def test_beep(printer_id: int, n1: int = 2, n2: int = 2, n3: int = 1, db: Session = Depends(get_db), user: User = Depends(require_manager)):
|
||||
printer = db.query(Printer).filter(Printer.id == printer_id).first()
|
||||
if not printer:
|
||||
raise HTTPException(status_code=404, detail="Printer not found")
|
||||
success, error = printer_service.send_test_beep(printer.ip_address, printer.port, n1, n2, n3)
|
||||
return {"success": success, "error": error}
|
||||
|
||||
|
||||
@router.put("/printers/{printer_id}", response_model=PrinterOut)
|
||||
def update_printer(printer_id: int, body: PrinterUpdate, db: Session = Depends(get_db), user: User = Depends(require_manager)):
|
||||
printer = db.query(Printer).filter(Printer.id == printer_id).first()
|
||||
|
||||
@@ -66,6 +66,8 @@ _PRINT_SETTING_KEYS = [
|
||||
"print.font_ingredient",
|
||||
"print.font_item_note",
|
||||
"print.font_order_note",
|
||||
"print.beep_on_ticket",
|
||||
"print.beep_pattern",
|
||||
]
|
||||
|
||||
_PRINT_SETTING_DEFAULTS = {
|
||||
@@ -80,6 +82,17 @@ _PRINT_SETTING_DEFAULTS = {
|
||||
"print.font_ingredient": "0:0:0",
|
||||
"print.font_item_note": "0:0:0",
|
||||
"print.font_order_note": "0:1:0",
|
||||
"print.beep_on_ticket": "true",
|
||||
"print.beep_pattern": "double",
|
||||
}
|
||||
|
||||
# Beep patterns: (n1=on×100ms, n2=off×100ms, n3=count)
|
||||
# Using ESC BEL n1 n2 n3 (0x1B 0x07 n1 n2 n3)
|
||||
_BEEP_PATTERNS = {
|
||||
"single": (2, 2, 1), # one 200ms beep
|
||||
"double": (1, 1, 2), # two short beeps
|
||||
"triple": (1, 1, 3), # three short beeps
|
||||
"long": (5, 2, 1), # one 500ms beep
|
||||
}
|
||||
|
||||
# SIZE byte values (ESC ! base, no bold bit):
|
||||
@@ -179,6 +192,21 @@ def is_spoof_mode() -> bool:
|
||||
db.close()
|
||||
|
||||
|
||||
def send_test_beep(ip: str, port: int, n1: int, n2: int, n3: int) -> Tuple[bool, str]:
|
||||
"""Send a standalone beep-only job. n1=on-time×100ms, n2=off-time×100ms, n3=count."""
|
||||
if is_spoof_mode():
|
||||
logger.info("Spoof printing ON — dropping test beep")
|
||||
return True, ""
|
||||
try:
|
||||
p = _get_printer(ip, port)
|
||||
p._raw(bytes([0x1b, 0x07, n1, n2, n3]))
|
||||
p.close()
|
||||
return True, ""
|
||||
except Exception as e:
|
||||
logger.error("Test beep failed for %s:%s — %s", ip, port, e)
|
||||
return False, str(e)
|
||||
|
||||
|
||||
def send_test_print(ip: str, port: int, name: str) -> Tuple[bool, str]:
|
||||
if is_spoof_mode():
|
||||
logger.info("Spoof printing ON — dropping test print for %s", name)
|
||||
@@ -563,6 +591,22 @@ def _print_kitchen_ticket(p: Network, order: Order, items: List[OrderItem], db:
|
||||
p._raw(b'\x1b\x21\x00')
|
||||
|
||||
p._raw(b'\n\n\n')
|
||||
|
||||
# Beep before cut so the buzzer fires as the paper advances
|
||||
beep_enabled = cfg.get("print.beep_on_ticket", "true") == "true"
|
||||
if beep_enabled:
|
||||
pattern = cfg.get("print.beep_pattern", "double")
|
||||
if pattern.startswith("custom:"):
|
||||
# custom:n1:n2:n3
|
||||
try:
|
||||
_, n1s, n2s, n3s = pattern.split(":")
|
||||
beep_bytes = (int(n1s), int(n2s), int(n3s))
|
||||
except (ValueError, TypeError):
|
||||
beep_bytes = _BEEP_PATTERNS["double"]
|
||||
else:
|
||||
beep_bytes = _BEEP_PATTERNS.get(pattern, _BEEP_PATTERNS["double"])
|
||||
p._raw(bytes([0x1b, 0x07, beep_bytes[0], beep_bytes[1], beep_bytes[2]]))
|
||||
|
||||
p.cut()
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user