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:
2026-05-31 23:53:58 +03:00
parent b9d6055c48
commit 607e78ea82
4 changed files with 254 additions and 2 deletions

View File

@@ -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()