general fixes and ordering display overhaul

This commit is contained in:
2026-04-30 16:58:13 +03:00
parent 1fd7d16ec9
commit 8e27b7666e
19 changed files with 1470 additions and 335 deletions

View File

@@ -1,305 +1,343 @@
"""
Printer font & symbol test script.
Usage (inside Docker): python print_test.py [IP] [PORT]
Defaults to 10.98.20.25:9100
Printer comprehensive test script — Jolimark TP850UE
Usage: python print_test.py [IP] [PORT]
Default: 10.98.20.25:9100
Prints 6 pages:
Page 1 — ESC ! modes, Font A, English
Page 2 — ESC ! modes, Font B, English
Page 3 — ESC ! modes, Font A, Greek
Page 4 — ESC ! modes, Font B, Greek
Page 5 — GS ! character size multipliers (both fonts)
Page 6 — Beep tests + misc (underline, invert, symbols)
ESC ! (0x1B 0x21 n) correct bit map for TP850UE:
Bit 0 (0x01) — Font B instead of Font A
Bit 3 (0x08) — Emphasize / Bold
Bit 4 (0x10) — Double-height
Bit 5 (0x20) — Double-width
Bit 7 (0x80) — Underline
GS ! (0x1D 0x21 n) character size multiplier:
Low nibble (bits 0-3): height multiplier (0=1x, 1=2x, 2=3x … 7=8x)
High nibble (bits 4-7): width multiplier (0=1x, 1=2x, 2=3x … 7=8x)
e.g. n=0x00 → 1×1, n=0x11 → 2×2, n=0x22 → 3×3, n=0x77 → 8×8
"""
import sys
import time
PRINTER_IP = sys.argv[1] if len(sys.argv) > 1 else "10.98.20.25"
PRINTER_PORT = int(sys.argv[2]) if len(sys.argv) > 2 else 9100
from escpos.printer import Network
# ── Low-level helpers ──────────────────────────────────────────────────────────
def _gr(text: str) -> bytes:
return text.encode('cp737', errors='replace')
def _open():
p = Network(PRINTER_IP, PRINTER_PORT, timeout=10)
p._raw(b'\x1b\x40') # ESC @ reset
p._raw(b'\x1b\x74\x1d') # CP737 Greek code page
p._raw(b'\x1b\x40') # ESC @ — full reset
p._raw(b'\x1b\x74\x1d') # ESC t 29 — CP737 Greek code page
return p
def _t(p, text: str):
p._raw(_gr(text))
def _reset(p):
"""Reset to: Font A, normal size, no bold, left-align."""
p._raw(b'\x1b\x4d\x00') # ESC M 0 — Font A
p._raw(b'\x1b\x21\x00') # ESC ! 0 — normal
p._raw(b'\x1d\x21\x00') # GS ! 0 — 1×1 size
p._raw(b'\x1b\x45\x00') # ESC E 0 — bold off
p._raw(b'\x1b\x61\x00') # ESC a 0 — left align
def _center(p): p._raw(b'\x1b\x61\x01')
def _left(p): p._raw(b'\x1b\x61\x00')
def _divider(p, char="-", width=48):
p._raw(b'\x1b\x61\x00')
_left(p)
_t(p, char * width + "\n")
def _center(p):
p._raw(b'\x1b\x61\x01')
def _page_header(p, title: str):
_center(p)
p._raw(b'\x1b\x21\x28') # double-width + bold (bits 3+5 = 0x28)
_t(p, title + "\n")
_reset(p)
_divider(p, "=")
def _left(p):
p._raw(b'\x1b\x61\x00')
# ── ESC ! n byte reference ──────────────────────────────────────────────────
# Bit 0 → underline (not tested, minor)
# Bit 1 → double-strike (bold)
# Bit 3 → double-height
# Bit 4 → double-width
# Bit 5 → delete-line
# Bit 7 → bold (ESC E alias in some models)
# Common combos used here:
# 0x00 = normal
# 0x08 = double-height only (48 chars wide)
# 0x10 = double-height only (alt bit) (48 chars wide)
# 0x18 = double-height + bold
# 0x20 = double-width only (24 chars wide)
# 0x30 = double-width + double-height (24 chars wide)
# 0x38 = double-width + double-height + bold
# 0x48 = double-height (bit 6 combo — some printers)
# ── ESC ! mode table ───────────────────────────────────────────────────────────
#
# Each entry: (esc_bang_byte, esc_e_bold, label)
# esc_bang_byte sets the mode via ESC ! n
# esc_e_bold adds ESC E on top (independent bold layer)
# We test every useful combination so you can see the exact visual result.
MODES = [
(0x00, "Normal (0x00)"),
(0x08, "Double-height bit3 (0x08)"),
(0x10, "Double-height bit4 (0x10)"),
(0x18, "Double-height + Bold (0x18)"),
(0x20, "Double-width (0x20)"),
(0x30, "Double-width + Double-height (0x30)"),
(0x38, "Double-width + Double-height + Bold (0x38)"),
ESC_BANG_MODES = [
# (byte, extra_bold, label)
(0x00, False, "0x00 Normal"),
(0x00, True, "0x00 +ESC E Normal + Bold (ESC E)"),
(0x08, False, "0x08 Bold only (bit3)"),
(0x10, False, "0x10 Double-height (bit4)"),
(0x10, True, "0x10 +ESC E Double-height + Bold"),
(0x18, False, "0x18 Double-height + Bold (bits 3+4)"),
(0x20, False, "0x20 Double-width (bit5)"),
(0x20, True, "0x20 +ESC E Double-width + Bold"),
(0x28, False, "0x28 Double-width + Bold (bits 3+5)"),
(0x30, False, "0x30 Double-width + Double-height (bits 4+5)"),
(0x38, False, "0x38 Double-width + Double-height + Bold (bits 3+4+5)"),
]
# ── Section 1 — Font sizes & styles, English ───────────────────────────────
def section_english(p):
_center(p)
p._raw(b'\x1b\x21\x38')
_t(p, "=== FONT SIZES (EN) ===\n")
p._raw(b'\x1b\x21\x00')
_divider(p, "=")
def _esc_bang_section(p, english: bool):
lang = "EN" if english else "GR"
sample_normal = "TEST PRINT Hello 123" if english else "ΔΟΚΙΜΗ ΕΚΤΥΠΩΣΗΣ"
sample_lower = "test print hello 123" if english else "δοκιμη εκτυπωσης"
for code, label in MODES:
for (byte_val, extra_bold, label) in ESC_BANG_MODES:
_left(p)
_t(p, f"[{label}]\n")
p._raw(bytes([0x1b, 0x21, code]))
_t(p, "TEST PRINT Hello World Abc123\n")
# Print the label in small normal text first
p._raw(b'\x1b\x21\x00')
# bold on/off via ESC E
_t(p, " -> bold ON: ")
p._raw(b'\x1b\x45\x01')
p._raw(bytes([0x1b, 0x21, code]))
_t(p, "Bold Sample\n")
p._raw(b'\x1b\x45\x00')
p._raw(b'\x1b\x21\x00')
_t(p, f"[{label}]\n")
# Apply mode
p._raw(bytes([0x1b, 0x21, byte_val]))
if extra_bold:
p._raw(b'\x1b\x45\x01')
_t(p, sample_normal + "\n")
_t(p, sample_lower + "\n")
# Reset
_reset(p)
_t(p, "\n")
_divider(p)
p._raw(b'\n')
# ── Section 2 — Font sizes & styles, Greek ─────────────────────────────────
def section_greek(p):
_center(p)
p._raw(b'\x1b\x21\x38')
_t(p, "=== FONT SIZES (GR) ===\n")
p._raw(b'\x1b\x21\x00')
_divider(p, "=")
# ── Pages 14: ESC ! modes ────────────────────────────────────────────────────
for code, label in MODES:
def page_esc_bang(font_b: bool, english: bool):
font_label = "Font B (8x16 small)" if font_b else "Font A (12x24 standard)"
lang_label = "GREEK" if not english else "ENGLISH"
p = _open()
# Select font
p._raw(b'\x1b\x4d\x01' if font_b else b'\x1b\x4d\x00')
_page_header(p, f"ESC! MODES — {lang_label}{font_label[:6]}")
_t(p, f"Font: {font_label}\n")
_divider(p)
_esc_bang_section(p, english)
p._raw(b'\n\n\n')
p.cut()
p.close()
# ── Page 5: GS ! size multipliers ─────────────────────────────────────────────
# Combinations worth seeing: square multipliers + some asymmetric
GS_SIZES = [
(0x00, "1x1 normal"),
(0x01, "1w x 2h"),
(0x10, "2w x 1h"),
(0x11, "2x2"),
(0x22, "3x3"),
(0x33, "4x4"),
(0x44, "5x5"),
(0x55, "6x6"),
(0x02, "1w x 3h"),
(0x20, "3w x 1h"),
(0x21, "3w x 2h"),
(0x12, "2w x 3h"),
]
def page_gs_sizes():
p = _open()
_page_header(p, "GS! SIZE MULTIPLIERS")
_t(p, "GS ! n (0x1D 0x21 n)\n")
_t(p, "Low nibble=height, High nibble=width\n")
_divider(p)
for (byte_val, label) in GS_SIZES:
_left(p)
_t(p, f"[{label}]\n")
p._raw(bytes([0x1b, 0x21, code]))
_t(p, "ΔΟΚΙΜΑΣΤΙΚΗ ΕΚΤΥΠΩΣΗ\n")
_t(p, "δοκιμαστικη εκτυπωση\n") # lowercase
p._raw(b'\x1b\x21\x00')
p._raw(b'\x1b\x45\x01')
p._raw(bytes([0x1b, 0x21, code]))
_t(p, "Bold: Καλημερα Κοσμε\n")
p._raw(b'\x1b\x45\x00')
# Label in tiny normal text
p._raw(b'\x1b\x21\x00')
p._raw(b'\x1d\x21\x00')
_t(p, f"[n=0x{byte_val:02X} {label}]\n")
# Font A sample
p._raw(b'\x1b\x4d\x00')
p._raw(bytes([0x1d, 0x21, byte_val]))
_t(p, "Aa SAMPLE\n")
p._raw(b'\x1d\x21\x00')
# Font B sample on same size
p._raw(b'\x1b\x4d\x01')
p._raw(bytes([0x1d, 0x21, byte_val]))
_t(p, "Bb SMALL\n")
p._raw(b'\x1d\x21\x00')
p._raw(b'\x1b\x4d\x00') # back to Font A
_t(p, "\n")
_divider(p)
p._raw(b'\n')
# ── Section 3 — All printable ASCII symbols ────────────────────────────────
def section_ascii_symbols(p):
_center(p)
p._raw(b'\x1b\x21\x18') # double-height bold for header
_t(p, "=== ASCII SYMBOLS ===\n")
p._raw(b'\x1b\x21\x00')
_divider(p, "=")
_left(p)
# Printable ASCII 0x20 0x7E, 16 per line
chars = [chr(c) for c in range(0x20, 0x7F)]
line = ""
for i, ch in enumerate(chars):
line += ch + " "
if (i + 1) % 24 == 0:
_t(p, line + "\n")
line = ""
if line:
_t(p, line + "\n")
# Also show GS ! combined with ESC ! bold
_t(p, "\n")
_t(p, "Notable:\n")
_t(p, " Bullets : * + - # @ ! ? > < | / \\ ^ ~ _\n")
_t(p, " Framing : [ ] { } ( ) = : ; , . \"\n")
_t(p, " Currency : $ %\n")
_divider(p)
p._raw(b'\n')
# ── Section 4 — CP737 extended chars (0x800xFF) ───────────────────────────
def section_extended(p):
_center(p)
p._raw(b'\x1b\x21\x18')
_t(p, "=== CP737 EXTENDED (0x80-FF) ===\n")
p._raw(b'\x1b\x21\x00')
_divider(p, "=")
_left(p)
_t(p, "Hex offset rows x16 columns\n\n")
for row in range(8): # 0x80, 0x90 ... 0xF0
base = 0x80 + row * 16
row_bytes = bytes(range(base, base + 16))
label = f"0x{base:02X}: "
p._raw(_gr(label))
p._raw(row_bytes)
p._raw(b'\n')
_t(p, "\n")
_t(p, "Key CP737 specials:\n")
specials = [
(0xB3, "─ thin horiz line"),
(0xC4, "─ double line"),
(0xBA, "│ vert line"),
(0xBB, "┐ corner"),
(0xBC, "┘ corner"),
(0xC9, "╔ corner"),
(0xCA, "╩ junction"),
(0xCB, "╦ junction"),
(0xCC, "╠ junction"),
(0xCD, "═ double horiz"),
(0xCE, "╬ cross"),
(0xC8, "╚ corner"),
(0xBB, "╗ corner"),
(0xBC, "╝ corner"),
(0xDB, "█ full block"),
(0xDC, "▄ lower block"),
(0xDF, "▀ upper block"),
(0xB0, "░ light shade"),
(0xB1, "▒ medium shade"),
(0xB2, "▓ dark shade"),
(0xF8, "° degree"),
(0xF9, "· middle dot"),
(0xFA, "· bullet dot"),
(0xFB, "√ check / tick"),
(0xFE, "■ filled square"),
]
for code, desc in specials:
row_bytes = bytes([code, 0x20]) # char + space
p._raw(row_bytes)
_t(p, f" {desc}\n")
_divider(p)
p._raw(b'\n')
# ── Section 5 — Underline ──────────────────────────────────────────────────
def section_underline(p):
_center(p)
p._raw(b'\x1b\x21\x18')
_t(p, "=== UNDERLINE TEST ===\n")
p._raw(b'\x1b\x21\x00')
_t(p, "GS! + ESC E bold combined:\n")
_divider(p, "=")
_left(p)
for (byte_val, label) in [(0x11,"2x2"), (0x22,"3x3"), (0x33,"4x4")]:
p._raw(b'\x1b\x21\x00')
p._raw(b'\x1d\x21\x00')
_t(p, f"[{label} + bold]\n")
p._raw(b'\x1b\x45\x01')
p._raw(bytes([0x1d, 0x21, byte_val]))
_t(p, "BOLD LARGE\n")
p._raw(b'\x1b\x45\x00')
p._raw(b'\x1d\x21\x00')
_t(p, "\n")
# ESC - n : underline 0=off, 1=thin, 2=thick
p._raw(b'\n\n\n')
p.cut()
p.close()
# ── Page 6: Beep + misc ────────────────────────────────────────────────────────
def page_beep_misc():
p = _open()
_page_header(p, "BEEP + MISC TESTS")
# ── Beep section ──
_t(p, "BEEP TESTS\n")
_divider(p, "-")
_t(p, "Sending beeps now...\n\n")
# BEL — single beep (0x07)
_t(p, "[1] BEL single beep (0x07)\n")
p._raw(b'\x07')
time.sleep(0.5)
# ESC BEL n1 n2 n3 — beep for appointment
# n1=beep length (100ms units), n2=intermission (100ms), n3=count
_t(p, "[2] ESC BEL: 1 beep, 200ms long\n")
p._raw(bytes([0x1b, 0x07, 2, 2, 1])) # 200ms on, 200ms off, 1 beep
time.sleep(0.8)
_t(p, "[3] ESC BEL: 3 short beeps\n")
p._raw(bytes([0x1b, 0x07, 1, 1, 3])) # 100ms on, 100ms off, 3 beeps
time.sleep(1.5)
_t(p, "[4] ESC BEL: 1 long beep (500ms)\n")
p._raw(bytes([0x1b, 0x07, 5, 2, 1])) # 500ms on, 200ms off, 1 beep
time.sleep(1.2)
_t(p, "[5] GS BEL: 2 beeps\n")
p._raw(bytes([0x1d, 0x07, 2, 3, 2])) # 2 beeps, 300ms long, 200ms off
time.sleep(1.5)
_t(p, "Beep tests done.\n")
_divider(p)
# ── Underline ──
_t(p, "\nUNDERLINE\n")
_divider(p, "-")
for ul in [1, 2]:
p._raw(bytes([0x1b, 0x2d, ul]))
_t(p, f"Underline mode {ul}: TEST PRINT Abc123\n")
p._raw(b'\x1b\x2d\x00') # off
_t(p, f"Underline mode {ul}: Hello World 123\n")
p._raw(b'\x1b\x2d\x00')
_t(p, "\n")
_divider(p)
p._raw(b'\n')
# ── Section 6 — Inverted / white-on-black ─────────────────────────────────
def section_invert(p):
_center(p)
p._raw(b'\x1b\x21\x18')
_t(p, "=== INVERT (white-on-black) ===\n")
p._raw(b'\x1b\x21\x00')
_divider(p, "=")
_left(p)
# GS B n — invert
# ── White-on-black invert ──
_t(p, "\nWHITE-ON-BLACK (GS B)\n")
_divider(p, "-")
p._raw(b'\x1d\x42\x01')
_t(p, " INVERTED TEXT SAMPLE \n")
_t(p, " INVERTED NORMAL \n")
p._raw(b'\x1d\x21\x11') # 2x2 inverted
_t(p, " INVERTED 2x2 \n")
p._raw(b'\x1d\x21\x00')
p._raw(b'\x1d\x42\x00')
_t(p, "Normal text after invert\n")
_t(p, "\n")
_t(p, "Normal after invert\n")
_divider(p)
p._raw(b'\n')
# ── Section 7 — QR Code sample ────────────────────────────────────────────
def section_qr(p):
_center(p)
p._raw(b'\x1b\x21\x18')
_t(p, "=== QR CODE SAMPLE ===\n")
p._raw(b'\x1b\x21\x00')
_divider(p, "=")
data = b"https://pos.test"
# GS ( k — QR store data
store_len = len(data) + 3
p._raw(b'\x1d\x28\x6b' + bytes([store_len & 0xFF, (store_len >> 8) & 0xFF, 0x31, 0x50, 0x30]) + data)
# GS ( k — set size (module=6)
p._raw(b'\x1d\x28\x6b\x03\x00\x31\x43\x06')
# GS ( k — error correction level M
p._raw(b'\x1d\x28\x6b\x03\x00\x31\x45\x31')
# GS ( k — print
p._raw(b'\x1d\x28\x6b\x03\x00\x31\x51\x30')
_t(p, "\nhttps://pos.test\n\n")
# ── 90-degree rotation ──
_t(p, "\n90-DEGREE ROTATION (ESC V)\n")
_divider(p, "-")
p._raw(b'\x1b\x56\x01')
_t(p, "ROTATED TEXT\n")
p._raw(b'\x1b\x56\x00')
_t(p, "Normal again\n")
_divider(p)
p._raw(b'\n')
# ── Main ───────────────────────────────────────────────────────────────────
# ── CP737 useful symbols at normal size ──
_t(p, "\nUSEFUL CP737 SYMBOLS\n")
_divider(p, "-")
symbols = [
(0xFB, "tick / checkmark"),
(0xFE, "filled square"),
(0xF9, "middle dot"),
(0xFA, "small bullet"),
(0xF8, "degree"),
(0xDB, "full block"),
(0xDC, "lower half block"),
(0xDF, "upper half block"),
(0xB0, "light shade"),
(0xB1, "medium shade"),
(0xB2, "dark shade"),
(0xC4, "thin horiz line"),
(0xCD, "double horiz line"),
(0xBA, "vertical bar"),
(0xC9, "top-left corner dbl"),
(0xBB, "top-right corner dbl"),
(0xC8, "bot-left corner dbl"),
(0xBC, "bot-right corner dbl"),
]
for code, desc in symbols:
p._raw(bytes([code, 0x20, code, 0x20, code, 0x20]))
_t(p, f" {desc}\n")
_divider(p)
p._raw(b'\n\n\n')
p.cut()
p.close()
# ── Main ───────────────────────────────────────────────────────────────────────
def main():
print(f"Connecting to {PRINTER_IP}:{PRINTER_PORT} ...")
print(f"Connecting to {PRINTER_IP}:{PRINTER_PORT}")
print("Printing 6 pages...\n")
# ---- PAGE 1: English fonts ----
p = _open()
section_english(p)
p._raw(b'\n\n\n')
p.cut()
p.close()
print("Page 1 sent (English fonts)")
page_esc_bang(font_b=False, english=True)
print("Page 1 done — ESC! modes, Font A, English")
# ---- PAGE 2: Greek fonts ----
p = _open()
section_greek(p)
p._raw(b'\n\n\n')
p.cut()
p.close()
print("Page 2 sent (Greek fonts)")
page_esc_bang(font_b=True, english=True)
print("Page 2 done — ESC! modes, Font B, English")
# ---- PAGE 3: Symbols & special chars ----
p = _open()
section_ascii_symbols(p)
section_extended(p)
p._raw(b'\n\n\n')
p.cut()
p.close()
print("Page 3 sent (ASCII + CP737 extended)")
page_esc_bang(font_b=False, english=False)
print("Page 3 done — ESC! modes, Font A, Greek")
# ---- PAGE 4: Underline + Invert + QR ----
p = _open()
section_underline(p)
section_invert(p)
section_qr(p)
p._raw(b'\n\n\n')
p.cut()
p.close()
print("Page 4 sent (underline / invert / QR)")
page_esc_bang(font_b=True, english=False)
print("Page 4 done — ESC! modes, Font B, Greek")
print("Done — 4 pages printed.")
page_gs_sizes()
print("Page 5 done — GS! size multipliers")
page_beep_misc()
print("Page 6 done — Beep tests + misc")
print("\nAll done.")
if __name__ == "__main__":
main()