From 8b0d9bcc11e84ced9796fea78b6b539136577a91 Mon Sep 17 00:00:00 2001 From: Ircama Date: Sun, 15 Jun 2025 16:35:28 +0200 Subject: [PATCH] Update VERSION to v6.2.10 New features Revision of the documentation Fix bug --- README.md | 50 +++++++++++++++++++++++++++++++++++++++++++-- epson_print_conf.py | 43 ++++++++++++++++++++++++++++++++++++++ requirements.txt | 4 ++-- ui.py | 24 ++++++++++++++-------- 4 files changed, 108 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index acd0de8..912aef5 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,10 @@ The software also includes a configurable printer dictionary, which can be easil - Import and export printer configuration datasets in various formats: epson_print_conf pickle, Reinkpy XML, Reinkpy TOML. + - Interactive Console (API Playground). + + The application includes an integrated Interactive Console. It allows Python developers to interact with the application's runtime environment, evaluate expressions, test APIs, and inspect variables. This console acts as a live API Playground, ideal for debugging, printer configuration testing and rapid prototyping. + - Access various administrative and debugging options. - __Available Interfaces__: @@ -80,6 +84,8 @@ cd epson_print_conf pip install -r requirements.txt ``` +On Linux, you might also install the tkinter module: `sudo apt install python3-tk`. + This program exploits [pysnmp v7+](https://github.com/lextudio/pysnmp) and [pysnmp-sync-adapter](https://github.com/Ircama/pysnmp-sync-adapter). It is tested with Ubuntu / Windows Subsystem for Linux, Windows. @@ -673,7 +679,7 @@ To return the value of the OID query: `self.fetch_oid_values(oid)[0][1]`. Open the *epson_print_conf* application, set printer model and IP address, test printer connection. Then: Settings > Debug Shell. -The following are examples of instructions to test the END4 commands: +The following are examples of instructions to test the EPSON-CTRL commands: ```python # cs @@ -734,7 +740,47 @@ for i in ec_sequences: Comprehensive, unified documentation for Epson’s Remote Mode commands does not exist: support varies by model, and command references are scattered across service manuals, programming guides and third-party sources (for example, the [Developer's Guide to Gutenprint](https://gimp-print.sourceforge.io/reference-html/x952.html) or [GIMP-Print - ESC/P2 Remote Mode Commands](http://osr507doc.xinuos.com/en/OSAdminG/OSAdminG_gimp/manual-html/gimpprint_37.html)). -Check `self.printer.check_nozzles()` and `self.printer.clean_nozzles(0)` for examples of usage of remote commands. +The `EpsonLpr` class is used for sending Epson LPR commands over a RAW, unidirectional TCP connection on port 9100. This channel does not support receiving responses from the printer. + +| **Method** | **Description** | +| ----------------------- | -------------------------------------------------------------------------------------------------------------------------------- | +| `connect` | Opens a TCP socket connection to the printer at the specified host and port, with timeout. | +| `disconnect` | Gracefully shuts down and closes the socket connection if open. | +| `send(data)` | Sends raw `bytes` directly to the printer over the socket connection. | +| `remote_cmd(cmd, args)` | Constructs a Remote Mode command: 2-byte ASCII command + 2-byte little-endian length + arguments. | + +Predefined remote mode commands in this class: + +| **Command** | **Description** | +| --------------------- | ------------------------------------------------------- | +| `LF` | Line Feed (new line). | +| `FF` | Form Feed; flushes the buffer / ejects the page. | +| `EXIT_PACKET_MODE` | Exits IEEE 1284.4 (D4) packet mode. | +| `INITIALIZE_PRINTER` | Resets printer to default state (ESC @). | +| `REMOTE_MODE` | Enter Epson Remote Command mode. | +| `ENTER_REMOTE_MODE` | Initialize printer and enter Epson Remote Command mode. | +| `EXIT_REMOTE_MODE` | Exits Remote Mode. | +| `JOB_START` | Begins a print job (JS). | +| `JOB_END` | Ends a print job (JE). | +| `PRINT_NOZZLE_CHECK` | Triggers a nozzle check print pattern (NC). | +| `VERSION_INFORMATION` | Requests firmware or printer version info (VI). | +| `LD` | (unknown). | + +Check `self.printer.check_nozzles()` and `self.printer.clean_nozzles(0)` for examples of usage. The following code prints the nozzle-check print pattern (copy and paste the code to the Interactive Console after selecting a printer and related host address): + +```python +from epson_print_conf import EpsonLpr +lpr = EpsonLpr(self.printer.hostname) +data = ( + lpr.EXIT_PACKET_MODE + # Exit packet mode + lpr.ENTER_REMOTE_MODE + # Engage remote mode commands + lpr.PRINT_NOZZLE_CHECK + # Issue nozzle-check print pattern + lpr.EXIT_REMOTE_MODE + # Disengage remote control + lpr.JOB_END # Mark maintenance job complete +) +print(f"\nDump of data:\n{self.printer.hexdump(data)}\n") +lpr.connect().send(data).disconnect() +``` ## ST2 Status Reply Codes diff --git a/epson_print_conf.py b/epson_print_conf.py index 421d7e9..0105454 100644 --- a/epson_print_conf.py +++ b/epson_print_conf.py @@ -76,6 +76,7 @@ class EpsonLpr: self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.sock.settimeout(self.timeout) self.sock.connect((self.hostname, self.port)) + return self def disconnect(self) -> None: """Shutdown and close the socket.""" @@ -86,12 +87,14 @@ class EpsonLpr: pass self.sock.close() self.sock = None + return self def send(self, data: bytes) -> None: """Send raw bytes to the printer.""" if not self.sock: raise RuntimeError("Not connected to printer") self.sock.sendall(data) + return self def remote_cmd(self, cmd, args): "Generate a Remote Mode command." @@ -1137,6 +1140,46 @@ class EpsonPrinter: """ return(filter(lambda x: x.startswith("get_"), dir(self))) + def hexdump(self, data: Union[bytes, bytearray], width: int = 16) -> str: + """ + Produce a hex + ASCII dump of the given data. + + Each line shows: + - 8-digit hex offset + - hex bytes (grouped by width, with extra space every 8 bytes) + - printable ASCII (non-printables as '.') + + :param data: Bytes to dump. + :param width: Number of bytes per line (default: 16). + :return: The formatted hexdump. + """ + lines = [] + for offset in range(0, len(data), width): + chunk = data[offset : offset + width] + + # Hex part, with a space every byte and extra gap at half‑width + hex_bytes = ' '.join(f"{b:02X}" for b in chunk) + half = width // 2 + if len(chunk) > half: + # insert extra space between halves + parts = hex_bytes.split(' ') + hex_bytes = ( + ' '.join(parts[:half]) + ' ' + ' '.join(parts[half:]) + ) + + # Pad hex part so ASCII column aligns + expected_len = width * 2 + (width - 1) + 2 # bytes*2 hex + spaces + extra half‑split + hex_part = hex_bytes.ljust(expected_len) + + # ASCII part: printable or '.' + ascii_part = ( + ''.join(chr(b) if 32 <= b < 127 else '.' for b in chunk) + ) + + lines.append(f"{offset:08X} {hex_part} {ascii_part}") + + return "\n".join(lines) + def expand_printer_conf(self, conf): """ Expand "alias" and "same-as" of a printer database for all printers diff --git a/requirements.txt b/requirements.txt index f372439..2e35571 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,8 +1,8 @@ pyyaml pysnmp>=7.1.20 -pysnmp_sync_adapter>=1.0.6 +pysnmp_sync_adapter>=1.0.8 tkcalendar pyperclip black tomli -text-console>=2.0.0 +text-console>=2.0.2 diff --git a/ui.py b/ui.py index e425404..ff20ee2 100644 --- a/ui.py +++ b/ui.py @@ -37,7 +37,7 @@ from find_printers import PrinterScanner from text_console import TextConsole -VERSION = "6.2.0" +VERSION = "6.2.10" NO_CONF_ERROR = ( " Please select a printer model and a valid IP address," @@ -55,7 +55,9 @@ CONFIRM_MESSAGE = ( class EpcTextConsole(TextConsole): - show_about_message = "epson_print_conf Debug Console." + show_about_message = ( + "Epson Printer Configuration Interactive Console (API Playground)." + ) def show_help(self): """Open a separate window with help text.""" @@ -67,13 +69,15 @@ class EpcTextConsole(TextConsole): scrollbar = tk.Scrollbar(help_window) scrollbar.pack(side="right", fill="y") - help_text = tk.Text(help_window, wrap="word", yscrollcommand=scrollbar.set) + help_text = tk.Text( + help_window, wrap="word", yscrollcommand=scrollbar.set + ) help_text.tag_configure("title", foreground="purple") help_text.tag_configure("section", foreground="blue") help_text.insert( tk.END, - 'Welcome to the epson_print_conf Debug Console\n\n', + f'Welcome to the {self.show_about_message}\n\n', "title" ) help_text.insert( @@ -98,7 +102,7 @@ class EpcTextConsole(TextConsole): help_text.insert( tk.END, ( - "- F7: Open the debug console.\n\n" + "- F7: Open the Interactive Console (API Playground).\n\n" ) ) help_text.insert( @@ -370,8 +374,8 @@ class EpsonPrinterUI(tk.Tk): help_menu.add_command(label="Clear printer list", command=self.clear_printer_list) help_menu.entryconfig("Clear printer list", accelerator="F6") - help_menu.add_command(label="Debug shell", command=self.tk_console) - help_menu.entryconfig("Debug shell", accelerator="F7") + help_menu.add_command(label="Interactive Console (API Playground)", command=self.tk_console) + help_menu.entryconfig("Interactive Console (API Playground)", accelerator="F7") help_menu.add_command(label="Remove selected printer configuration", command=self.remove_printer_conf) help_menu.entryconfig("Remove selected printer configuration", accelerator="F8") @@ -1186,7 +1190,9 @@ class EpsonPrinterUI(tk.Tk): return self._console_window = tk.Toplevel(self) - self._console_window.title("Debug Console") + self._console_window.title( + "Epson Printer Configuration Interactive Console (API Playground)" + ) self._console_window.geometry("800x400") console = EpcTextConsole(self, self._console_window) @@ -3596,7 +3602,7 @@ def main(): import pickle parser = argparse.ArgumentParser( - epilog='epson_print_conf GUI' + epilog='Epson Printer Configuration GUI' ) parser.add_argument( '-m',