Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 0 additions & 11 deletions Printer/reliance_printer.py

This file was deleted.

Empty file added py_esc_pos/__init__.py
Empty file.
20 changes: 8 additions & 12 deletions commands.py → py_esc_pos/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,15 @@ class Commands:
CENTER = b'\x01'
RIGHT = b'\x02'

RT_PRINTER = b'\x01'
RT_OFFLINE = b'\x02'
RT_ERROR = b'\x03'
RT_PAPER = b'\x04'

class PhoenixCommands(Commands):
SELECT_FONT_A = b'\x1b\x50'
SELECT_FONT_B = b'\x1b\x54'
SELECT_FONT_C = b'\x1b\x55'
SELECT_FONT_C = b'\x1b\x54'
SELECT_FONT_D = b'\x1b\x55'
PAPER_STATUS = b'\x1b\x76'
PRINT_N_FEED_PAPER_N_LINES = b'\x1b\x64'
PRINT_N_FEED_PAPER = b'\x1b\x4a'
Expand All @@ -37,15 +42,6 @@ class PhoenixCommands(Commands):
TOGGLE_AUTO_CUT = b'\x1c\x7d\x60'
DYNAMIC_2D_BARCODE = b'\x1d\x28\x6b'
PRINT_REAL_TIME_CLOCK = b'\x1c\x7d\x70'
RT_PRINTER = b'\x01'
RT_OFFLINE = b'\x02'
RT_ERROR = b'\x03'
RT_PAPER = b'\x04'

# Actions
TEST_COIN_IN = b'\x60'
TEST_NOTE_IN = b'\x61'
PRINT_RTC = b'\x1c\x7d\x70'

class RelianceCommands(Commands):
HORIZONTAL_TAB = b'\x09'
Expand All @@ -72,7 +68,7 @@ class RelianceCommands(Commands):
BARCODE_GENERATOR_2D = b'\x1c\x7d\x25'
SET_2D_BARCODE_SIZE = b'\x1c\x7d\x74'
BARCODE_GENERATOR = b'\x1d\x6b'
SET_1d_BARCODE_WIDTH_MULT = b'\x1d\x77'
SET_1D_BARCODE_WIDTH_MULT = b'\x1d\x77'
SET_1D_BARCODE_HEIGHT = b'\x1d\x68'
SET_HRI_PRINTING_POSITION = b'\x1d\x48'
SET_HRI_FONT = b'\x1d\x66'
Expand Down
2 changes: 1 addition & 1 deletion main.py → py_esc_pos/main.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from Menu.main_menu import MainMenu
from py_esc_pos.menu.main_menu import MainMenu

if __name__ == "__main__":
app = MainMenu()
Expand Down
47 changes: 30 additions & 17 deletions Menu/main_menu.py → py_esc_pos/menu/main_menu.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@
from rich.prompt import Prompt
from rich.panel import Panel
from rich.table import Table
from Menu.print_menu import PrintMenu
from Menu.util import get_raster_blob, find_port, verify_printer_connection
from commands import PhoenixCommands, RelianceCommands, Commands
from Printer import phoenix_printer, reliance_printer

from py_esc_pos.commands import PhoenixCommands, RelianceCommands, Commands
from py_esc_pos.printer import phoenix_printer, reliance_printer

from print_menu import PrintMenu
from util import get_raster_blob, find_port, verify_printer_connection

console = Console()

Expand Down Expand Up @@ -34,7 +36,7 @@ def connect(self):
if not port: return False

printer_type = Prompt.ask(
"\nSelect Printer Type",
"\nSelect printer Type",
choices=["phoenix", "reliance"]
).lower()

Expand All @@ -53,16 +55,19 @@ def run(self):
console.print("[bold red]Failed to connect to printer. Exiting.[/bold red]")
return

self.menu()

def menu(self):
while True:
menu_text = (
"1. [bold cyan]Print Menu[/bold cyan] (Text, Font, Style)\n"
"1. [bold cyan]Print menu[/bold cyan] (Text, Font, Style)\n"
"2. [bold cyan]Print image[/bold cyan] \n"
"3. [bold magenta]Send Raw Input[/bold magenta]\n"
"4. [bold green]View Command List[/bold green]\n"
"0. Exit"
)

console.print(Panel(menu_text, title="Main Menu", expand=False))
console.print(Panel(menu_text, title="Main menu", expand=False))
choice = Prompt.ask("Action", choices=["0", "1", "2", "3", "4"], default="0")

if choice == "0":
Expand All @@ -85,9 +90,17 @@ def handle_print_image(self):
path = Prompt.ask("Enter image path")
try:
raster_blob = get_raster_blob(path)
self.printer.send_command(PhoenixCommands.INIT)
self.printer.send_command(raster_blob)
self.printer.send_command(PhoenixCommands.FULL_CUT)
if self.printer.get_type() == "PhoenixPrinter":
self.printer.send_command(PhoenixCommands.INIT)
self.printer.send_command(raster_blob)
self.printer.send_command(PhoenixCommands.FULL_CUT)
elif self.printer.get_type() == "ReliancePrinter":
self.printer.send_command(RelianceCommands.INIT)
self.printer.send_command(raster_blob)
self.printer.send_command(RelianceCommands.EJECTOR + b'\x05')
else:
console.print("[bold red]Unsupported printer type for image printing.[/bold red]")
return
console.print("[bold green]Image sent successfully![/bold green]")
except Exception as e:
console.print(f"[bold red]Error:[/bold red] {e}")
Expand All @@ -100,23 +113,23 @@ def handle_raw_input(self):
response = self.printer.read_response(timeout=2.0)
hex_response = " ".join(f"{b:02X}" for b in response)
if hex_response:
console.print(f"Printer response (hex): [green]{hex_response}[/green]")
console.print(f"Printer response: [green]00[/green]")
console.print(f"printer response (hex): [green]{hex_response}[/green]")
console.print(f"printer response: [green]00[/green]")

def display_commands(self):
is_phoenix = "Phoenix" in str(type(self.printer))
phoenix_cmds = PhoenixCommands if is_phoenix else RelianceCommands
is_phoenix = self.printer.get_type() == "PhoenixPrinter"
cmds = PhoenixCommands if is_phoenix else RelianceCommands

table = Table(title=f"{phoenix_cmds.__name__} List", show_header=True, header_style="bold magenta")
table = Table(title=f"{cmds.__name__} List", show_header=True, header_style="bold magenta")
table.add_column("Command Name", style="cyan")
table.add_column("Hex Value", style="green", justify="right")

commands_to_show = {cmds_name: cmds_bytes for cmds_name, cmds_bytes in phoenix_cmds.__dict__.items() if cmds_name.isupper()}
commands_to_show = {cmds_name: cmds_bytes for cmds_name, cmds_bytes in cmds.__dict__.items() if cmds_name.isupper()}
commands_to_show.update({cmds_name: cmds_bytes for cmds_name, cmds_bytes in Commands.__dict__.items() if cmds_name.isupper()})

for name, value in sorted(commands_to_show.items()):
hex_val = " ".join(f"{b:02X}" for b in value) if isinstance(value, bytes) else str(value)
table.add_row(name, hex_val)

console.print(table)
Prompt.ask("\nPress [bold]Enter[/bold] to return to Main Menu")
Prompt.ask("\nPress [bold]Enter[/bold] to return to Main menu")
6 changes: 3 additions & 3 deletions Menu/print_menu.py → py_esc_pos/menu/print_menu.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@
from rich.panel import Panel
from rich.prompt import Prompt
from rich.table import Table
from commands import PhoenixCommands, RelianceCommands, Commands
from py_esc_pos.commands import PhoenixCommands

console = Console()

def add_font_type(print_buffer, font_type):
if not print_buffer:
console.print("[bold red]No lines to set font for![/bold red]")
return
type_map = {"A": PhoenixCommands.SELECT_FONT_A, "B": PhoenixCommands.SELECT_FONT_B, "C": PhoenixCommands.SELECT_FONT_C}
type_map = {"A": PhoenixCommands.SELECT_FONT_A, "C": PhoenixCommands.SELECT_FONT_C, "D": PhoenixCommands.SELECT_FONT_D}
print_buffer[-1]["type"] = type_map[font_type]

def add_font_style(print_buffer, style):
Expand Down Expand Up @@ -66,7 +66,7 @@ def menu_print_settings(self):
"3. [bold cyan]Set Font Style[/bold cyan] (Last Line)\n"
"4. [red]Remove Last Line[/red]\n"
"5. [bold magenta]PROCESS PRINT JOB[/bold magenta]\n"
"6. [yellow]Back to Main Menu[/yellow]\n"
"6. [yellow]Back to Main menu[/yellow]\n"
"0. Exit"
)
menu_panel = Panel(menu_text, title="Print Settings", expand=False)
Expand Down
2 changes: 1 addition & 1 deletion Menu/util.py → py_esc_pos/menu/util.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from PIL import Image, ImageOps
import serial.tools.list_ports as port_list

from commands import PhoenixCommands
from py_esc_pos.commands import PhoenixCommands

def find_port():
return list(port_list.comports())
Comment on lines 6 to 7
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method itself is fine, however when you call it you're just grabbing the first COM port listed. Since these could be any serial device (and the order depends on what OS and what devices are plugged in).

For the samples, I would just use the list to let the user select which COM port to use.

You could auto-detect the printer by sending a status command to each port, but that ends up sending junk data to every other device connected.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ def __init__(self, port, baudrate):
parity=serial.PARITY_NONE,
stopbits=serial.STOPBITS_ONE,
)

self.printer_type = "BasePrinter"
time.sleep(1)
self.ser.reset_input_buffer()
self.ser.reset_output_buffer()
Expand All @@ -31,4 +31,7 @@ def close(self):
def read_response(self, timeout=1.0):
self.ser.timeout = timeout
response = self.ser.read_all()
return response
return response

def get_type(self):
return self.printer_type
Original file line number Diff line number Diff line change
@@ -1,22 +1,23 @@
from Printer import base_printer
from py_esc_pos.printer import base_printer
from py_esc_pos.commands import PhoenixCommands
import time
from commands import PhoenixCommands

## @class PhoenixPrinter
# @brief Implementation for the Pyramid Phoenix Thermal Printer.
# @brief Implementation for the Pyramid phoenix Thermal printer.
# @details This class handles hardware-specific handshaking and status parsing.
# @see [Phoenix Status Documentation](https://escpos.readthedocs.io/en/latest/phoenix_status.html)
# @see [phoenix Status Documentation](https://escpos.readthedocs.io/en/latest/phoenix_status.html)
Comment thread
ltn-PTI marked this conversation as resolved.
class PhoenixPrinter(base_printer.BasePrinter):
def __init__(self, port):
super().__init__(port, 9600)
self.printer_type = "PhoenixPrinter"

time.sleep(1)
self.ser.reset_input_buffer()

## @brief Verifies the logic link between the printer and host.
# @details Sends a DLE EOT (Real Time Status) command to check if the printer is responsive and online.
# @return String description of connection status.
# @see [Phoenix Real Time Status Documentation](https://escpos.readthedocs.io/en/latest/phoenix_status.html#p1004)
# @see [phoenix Real Time Status Documentation](https://escpos.readthedocs.io/en/latest/phoenix_status.html#p1004)
# @note A response of 0xAC typically indicates a hardware communication error such as incorrect parity or a wiring fault.
def verify_logic_link(self):
self.ser.reset_input_buffer()
Expand Down
25 changes: 25 additions & 0 deletions py_esc_pos/printer/reliance_printer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from py_esc_pos.printer import base_printer
from py_esc_pos.commands import RelianceCommands
import time

class ReliancePrinter(base_printer.BasePrinter):
def __init__(self, port):
super().__init__(port, 9600)
self.printer_type = "ReliancePrinter"

time.sleep(1)
self.ser.reset_input_buffer()

def verify_logic_link(self):
self.ser.reset_input_buffer()
self.ser.write(RelianceCommands.REAL_TIME_STATUS + RelianceCommands.RT_OFFLINE)
time.sleep(0.25)

if self.ser.in_waiting > 0:
res = self.ser.read(1)[0]
if res == 0xAC:
return "CONNECTED_BUT_MANGLED (Check Parity/Stop Bits)"

is_online = (res & 0x08) == 0x08
return "ONLINE" if is_online else "OFFLINE"
return "NO_RESPONSE"
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# @brief This sample demonstrates how to establish a link with the Phoenix printer.
from Printer.phoenix_printer import PhoenixPrinter
from Menu.util import find_port

from py_esc_pos.printer.phoenix_printer import PhoenixPrinter
from py_esc_pos.menu.util import find_port

## Connects to the first available printer and checks status.
# @returns A PhoenixPrinter instance if successful.
Expand All @@ -13,4 +14,7 @@ def simple_connect():
# verify_logic_link is the 'handshake'
status = printer.verify_logic_link()
print(f"Printer Status: {status}")
return printer
return printer

if __name__ == "__main__":
simple_connect()
Original file line number Diff line number Diff line change
@@ -1,33 +1,31 @@
# @brief Basic Command Execution for Phoenix Printers.
# @details This sample demonstrates how to initialize the printer, print text, and trigger a full cut using ESC/POS constants.
from Printer.phoenix_printer import PhoenixPrinter
from commands import PhoenixCommands
from Menu.util import find_port

from py_esc_pos.printer.phoenix_printer import PhoenixPrinter
from py_esc_pos.commands import PhoenixCommands
from py_esc_pos.menu.util import find_port
## Illustrates the standard "Initialize -> Action -> Cut" workflow.
# @see [Phoenix Paper Movement Commands](https://escpos.readthedocs.io/en/latest/paper_movement.html)
def run_basic_print():
# 1. Setup Connection
ports = find_port()
if not ports:
print("No printer found.")
return

printer = PhoenixPrinter(ports[1].device)
# Use the first detected printer port by default.
printer = PhoenixPrinter(ports[0].device)

try:
# 2. Initialize (ESC @)
# Always good practice to clear the buffer and reset settings
print("Initializing printer...")
# Initialize
printer.send_command(PhoenixCommands.INIT)

# 3. Send Text Data
# Send Text Data
print("Sending text...")
printer.send_command(b"Phoenix Sample Print\n")
printer.send_command(b"--------------------\n")
printer.send_command(b"SWT-189: Command Sample\n\n")
printer.send_command(b"Command Sample\n\n")

# 4. Feed and Cut (GS V)
# Feed and Cut (GS V)
# We feed a bit of paper so the text clears the cutter blade
print("Cutting paper...")
printer.send_command(PhoenixCommands.FULL_CUT)
Expand Down
Loading