Add clean nozzles and revise documentation
Ref. https://github.com/Ircama/epson_print_conf/discussions/57#discussioncomment-13418667
This commit is contained in:
74
README.md
74
README.md
@@ -23,6 +23,14 @@ The software also includes a configurable printer dictionary, which can be easil
|
||||
|
||||
- Open the Web interface of the printer (via the default browser).
|
||||
|
||||
- Clean Nozzles.
|
||||
|
||||
Performs a standard cleaning cycle on the selected nozzle group.
|
||||
|
||||
- Power Clean of the nozzles.
|
||||
|
||||
Uses a higher quantity of ink to perform a deeper cleaning cycle. Power cleaning also consumes more ink and fills the waste ink tank more quickly. It should only be used when normal cleaning is insufficient.
|
||||
|
||||
- Temporary reset of the ink waste counter.
|
||||
|
||||
The ink waste counters track the amount of ink discarded during maintenance tasks to prevent overflow in the waste ink pads. Once the counters indicate that one of the printer pads is full, the printer will stop working to avoid potential damage or ink spills. The "Printer status" button includes information showing the levels of the waste ink tanks; specifically, two sections are relevant: "Maintenance box information" ("maintenance_box_...") and "Waste Ink Levels" ("waste_ink_levels"). The former has a counter associated for each tank, which indicates the number of temporary resets performed by the user to temporarily restore a disabled printer.
|
||||
@@ -510,13 +518,23 @@ printer = EpsonPrinter(hostname="192.168.1.87")
|
||||
pprint.pprint(printer.status_parser(printer.fetch_snmp_values("1.3.6.1.4.1.1248.1.2.2.1.1.1.4.1")[1]))
|
||||
```
|
||||
|
||||
## END4 EPSON-CTRL commands over SNMP
|
||||
## EPSON-CTRL commands over SNMP
|
||||
|
||||
END4 commands (totally undocumented) might be a limited set of bidirectional remote commands that can be sent without establishing a D4 connection.
|
||||
[Communication between PC and Printer can be done by several transport protocols](https://github.com/lion-simba/reink/blob/master/reink.c#L79C5-L85): ESCP/2, EJL, D4. And in addition SNMP, END4. “D4” (or “Dot 4”) is an abbreviated form of the IEEE-1284.4 specification: it provides a bi-directional, packetized link with multiple logical “sockets”. The two primary Epson-defined channels are:
|
||||
|
||||
END4 EPSON-CTRL commands can be converted into OIDs and sent via SNMP.
|
||||
- EPSON-CTRL
|
||||
– Carries printer-control commands, status queries, configuration
|
||||
- Structure: 2 lowercase letters + length + payload
|
||||
– Also tunneled via END4
|
||||
- undocumented commands.
|
||||
|
||||
Ref. excellent analysis from [ciprian](https://codeberg.org/atufi/reinkpy/issues/12#issuecomment-1660026) and [dger](https://codeberg.org/atufi/reinkpy/issues/12#issuecomment-1661250).
|
||||
- EPSON-DATA
|
||||
– Carries the actual print-job content: raster image streams, font/download data, macros, etc.
|
||||
- Allow "Remote Mode" commands, entered and terminated via a special sequence (`ESC (R BC=8 00 R E M O T E 1`, `ESC 00 00 00`); [remote mode commands](https://gimp-print.sourceforge.io/reference-html/x952.html) are partially documented and have a similar structure as EPSON-CTRL (2 letters + length + payload), but the letters are uppercase and cannot be mapped to SNMP.
|
||||
|
||||
EPSON-CTRL can be transported over D4, or encapsulated in SNMP OIDs. Some EPSON-CTRL instructions implement a subset of Epson’s Remote Mode protocol, while others are proprietary.
|
||||
|
||||
END4 is a proprietary protocol to transport EPSON-CTRL commands [over the standard print channel](https://codeberg.org/atufi/reinkpy/issues/12#issuecomment-1660026), without using the EPSON-CTRL channel.
|
||||
|
||||
OID Header:
|
||||
|
||||
@@ -532,13 +550,11 @@ Full OID header sequence: `1.3.6.1.4.1.1248.1.2.2.44.1.1.2.1.`
|
||||
|
||||
Subsequent digits:
|
||||
|
||||
- Two ASCII characters that identify the command (e.g., "st", "ex"). These are command identifiers of the END4 EPSON-CTRL messages (Remote Mode)
|
||||
- Two ASCII characters that identify the command (e.g., "st", "ex"). These are command identifiers of the EPSON-CTRL messages (Remote Mode)
|
||||
- 2-byte little-endian length field (gives the number of bytes in the parameter section that follows)
|
||||
- payload (a block of bytes that are specific to the command).
|
||||
|
||||
END4 commands partially overlap with Epson’s Remote Mode bi-directional printer-control language, though they are not strictly equivalent. 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)). Some END4 instructions implement subsets of Epson’s Remote Mode protocol, while others are proprietary extensions and lie outside Epson’s published command set.
|
||||
|
||||
The following is the list of END4 commands supported by the XP-205.
|
||||
The following is the list of EPSON-CTRL commands supported by the XP-205.
|
||||
|
||||
Two-bytes|Description | Notes | Parameters
|
||||
:--:| ---------------------------------------------- | ----------------| -------------
|
||||
@@ -560,8 +576,8 @@ pm | Select control language ("PM" 02H 00H 00H m1m1=0(ESC/P), 2(IBM 238x Plus em
|
||||
rj | Resume jobs (?) | |
|
||||
rp | (serial number ? ) | | (0)
|
||||
rs | Initialize | | (1)
|
||||
rw | Reset Waste | Implemented in this program | (1, 0) + Serial SHA1 hash (20 bytes)
|
||||
st | Get printer status ("st" 01H 00H 01H) | Implemented in this program | (1)
|
||||
rw | Reset Waste | Implemented in this program | (1, 0) + [Serial SHA1 hash](https://codeberg.org/atufi/reinkpy/issues/12#issuecomment-1661250) (20 bytes)
|
||||
st | Get printer status ("st" 01H 00H 01H) | Implemented in this program; se below "ST2 Status Reply Codes" | (1)
|
||||
ti | Set printer time | (" TI" 08H 00H 00H YYYY MM DD hh mm ss) |
|
||||
vi | Version Information | Implemented in this program | (0)
|
||||
xi | | | (1)
|
||||
@@ -574,8 +590,8 @@ xi | | | (1)
|
||||
- 7.0: Two-byte payload length = 7 bytes
|
||||
- two bytes for the read key
|
||||
- 65: 'A' = read
|
||||
- 190: Take the bitwise NOT of the ASCII value of 'A' = read, then mask to the lowest 8 bits. The result is 190.
|
||||
- 160: Shift the ASCII value of 'A' (read) right by 1 and mask to 7 bits, then OR it with the highest bit of the value shifted left by 7. The result is 160.
|
||||
- 190: [Take the bitwise NOT of the ASCII value of 'A' = read, then mask to the lowest 8 bits](https://github.com/lion-simba/reink/blob/master/reink.c#L1414). The result is 190.
|
||||
- 160: [Shift the ASCII value of 'A' (read) right by 1 and mask to 7 bits, then OR it with the highest bit of the value shifted left by 7](https://github.com/lion-simba/reink/blob/master/reink.c#L1415). The result is 160.
|
||||
- two bytes for the EEPROM address
|
||||
|
||||
```
|
||||
@@ -625,7 +641,7 @@ AC = Value
|
||||
|
||||
#### epctrl_snmp_oid()
|
||||
|
||||
`self.epctrl_snmp_oid(two-char-command, payload)` converts an END4 EPSON-CTRL Remote command into a SNMP OID format suitable for use in SNMP operations.
|
||||
`self.epctrl_snmp_oid(two-char-command, payload)` converts an EPSON-CTRL Remote command into a SNMP OID format suitable for use in SNMP operations.
|
||||
|
||||
**Parameters**
|
||||
|
||||
@@ -651,7 +667,7 @@ It returns a SNMP OID string to be used by `self.printer.fetch_oid_values()`.
|
||||
|
||||
To return the value of the OID query: `self.fetch_oid_values(oid)[0][1]`.
|
||||
|
||||
### Testing END4 remote commands
|
||||
### Testing EPSON-CTRL commands
|
||||
|
||||
Open the *epson_print_conf* application, set printer model and IP address, test printer connection. Then: Settings > Debug Shell.
|
||||
|
||||
@@ -712,29 +728,11 @@ for i in ec_sequences:
|
||||
print(r)
|
||||
```
|
||||
|
||||
Examples of commands ("CH" = Clean Heads and "NC" = Nozzle Check) that are not included in the END4 set, so they have to be delivered via TCP (port 9100 or port 515) and cannot be mapped to SNMP.
|
||||
## Remote Mode commands
|
||||
|
||||
- CH BC=2 00 xx Perform a head cleaning cycle. "00" cleans all heads
|
||||
|
||||
| Name | Bytes (hex) | Purpose |
|
||||
| ----------------------- | ---------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- |
|
||||
| EXIT\_PACKET\_MODE | `00 00 00 1B 01 40 45 4A 4C 20 31 32 38 34 2E 34 0A 40 45 4A 4C` | Exit packet mode. |
|
||||
| ENTER\_REMOTE\_MODE | `1B 40 1B 40 1B 28 52 08 00 00 52 45 4D 4F 54 45 31` | Put the device into "remote" control mode. |
|
||||
| SET\_CLOCK | `54 49 08 00 00 07 E9 06 08 17 0E 22` | "TI": Write the internal real-time clock to 2025-06-08 17:14:34. |
|
||||
| CLEAN\_HEADS | `43 48 02 00 00 00` | "CH": trigger print-head cleaning cycle. |
|
||||
| EXIT\_REMOTE\_MODE | `1B 00 00 00` | Exit remote control mode. |
|
||||
| JOB\_END | `4A 45 01 00 00` | Finalize the maintenance job. |
|
||||
|
||||
- NC BC=2 00 00 Print a nozzle check pattern
|
||||
|
||||
| Name | Bytes (hex) | Purpose |
|
||||
| ------------------------ | ---------------------------------------- | -------------------------------------------------------------- |
|
||||
| EXIT\_PACKET\_MODE | `00 00 00 1B 01 40 45 4A 4C 20 31 32 38 34 2E 34 0A 40 45 4A 4C` | Exit packet mode. |
|
||||
| ENTER\_REMOTE\_MODE | `1B 40 1B 40 1B 28 52 08 00 00 52 45 4D 4F 54 45 31` | Put the device into "remote" control mode. |
|
||||
| PRINT\_NOZZLE\_CHECK | `4E 43 02 00 00 00` | "NC": issue nozzle-check print pattern. |
|
||||
| EXIT\_REMOTE\_MODE | `1B 00 00 00` | Exit remote control mode. |
|
||||
| JOB\_END | `4A 45 01 00 00` | Finalize the maintenance job. |
|
||||
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.
|
||||
|
||||
## ST2 Status Reply Codes
|
||||
|
||||
@@ -983,12 +981,12 @@ snmpget -v1 -d -c public 192.168.1.87 1.3.6.1.4.1.1248.1.2.2.44.1.1.2.1.124.124.
|
||||
|
||||
### References
|
||||
|
||||
ReInk: <https://github.com/lion-simba/reink> (especially <https://github.com/lion-simba/reink/issues/1>)
|
||||
|
||||
epson-printer-snmp: <https://github.com/Zedeldi/epson-printer-snmp> (and <https://github.com/Zedeldi/epson-printer-snmp/issues/1>)
|
||||
|
||||
ReInkPy: <https://codeberg.org/atufi/reinkpy/>
|
||||
|
||||
ReInk: <https://github.com/lion-simba/reink> (especially <https://github.com/lion-simba/reink/issues/1>)
|
||||
|
||||
reink-net: <https://github.com/gentu/reink-net>
|
||||
|
||||
epson-l4160-ink-waste-resetter: <https://github.com/nicootto/epson-l4160-ink-waste-resetter>
|
||||
@@ -997,6 +995,8 @@ epson-l3160-ink-waste-resetter: <https://github.com/k3dt/epson-l3160-ink-waste-r
|
||||
|
||||
emanage x900: <https://github.com/abrasive/x900-otsakupuhastajat/>
|
||||
|
||||
Reversing Epson printers: <https://github.com/abrasive/epson-reversing/>
|
||||
|
||||
### Other programs
|
||||
|
||||
- Epson One-Time Maintenance Ink Pad Reset Utility: <https://epson.com/Support/wa00369>
|
||||
|
||||
@@ -21,6 +21,7 @@ import pickle
|
||||
import abc
|
||||
import hashlib
|
||||
import struct
|
||||
import socket
|
||||
|
||||
from pysnmp.hlapi.v1arch.asyncio import *
|
||||
from pyasn1.type.univ import OctetString as OctetStringType
|
||||
@@ -33,6 +34,72 @@ from pysnmp_sync_adapter import (
|
||||
from pysnmp.proto.errind import RequestTimedOut
|
||||
|
||||
|
||||
class EpsonLpr:
|
||||
"""
|
||||
Interface for sending Epson LPR commands over RAW (port 9100)
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
hostname: str,
|
||||
port: int = 9100,
|
||||
timeout: float = 5.0,
|
||||
recv_buffer: int = 4096):
|
||||
self.hostname = hostname
|
||||
self.port = port
|
||||
self.timeout = timeout
|
||||
self.recv_buffer = recv_buffer
|
||||
self.sock: Optional[socket.socket] = None
|
||||
|
||||
# Define Epson sequences
|
||||
self.LF = b'\x0a'
|
||||
self.FF = b'\x0c' # flush buffer
|
||||
self.EXIT_PACKET_MODE = (
|
||||
b'\x00\x00\x00\x1b\x01@EJL 1284.4\n@EJL ' +
|
||||
self.LF
|
||||
)
|
||||
self.INITIALIZE_PRINTER = b'\x1b@'
|
||||
self.REMOTE_MODE = b'\x1b' + self.remote_cmd("(R", b'\x00REMOTE1')
|
||||
self.ENTER_REMOTE_MODE = (
|
||||
self.INITIALIZE_PRINTER +
|
||||
self.INITIALIZE_PRINTER +
|
||||
self.REMOTE_MODE
|
||||
)
|
||||
self.EXIT_REMOTE_MODE = b'\x1b\x00\x00\x00'
|
||||
self.JOB_START = self.remote_cmd("JS", b'\x00\x00\x00\x00')
|
||||
self.JOB_END = self.remote_cmd("JE", b'\x00')
|
||||
self.PRINT_NOZZLE_CHECK = self.remote_cmd("NC", b'\x00\x00')
|
||||
self.VERSION_INFORMATION = self.remote_cmd("VI", b'\x00\x00')
|
||||
self.LD = self.remote_cmd("LD", b'')
|
||||
|
||||
def connect(self) -> None:
|
||||
"""Establish a TCP connection to the printer."""
|
||||
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
self.sock.settimeout(self.timeout)
|
||||
self.sock.connect((self.hostname, self.port))
|
||||
|
||||
def disconnect(self) -> None:
|
||||
"""Shutdown and close the socket."""
|
||||
if self.sock:
|
||||
try:
|
||||
self.sock.shutdown(socket.SHUT_RDWR)
|
||||
except Exception:
|
||||
pass
|
||||
self.sock.close()
|
||||
self.sock = None
|
||||
|
||||
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)
|
||||
|
||||
def remote_cmd(self, cmd, args):
|
||||
"Generate a Remote Mode command."
|
||||
if len(cmd) != 2:
|
||||
raise ValueError("command should be 2 bytes")
|
||||
return cmd.encode() + struct.pack('<H', len(args)) + args
|
||||
|
||||
|
||||
class EpsonPrinter:
|
||||
"""SNMP Epson Printer Configuration."""
|
||||
|
||||
@@ -2510,6 +2577,71 @@ class EpsonPrinter:
|
||||
return False
|
||||
return True
|
||||
|
||||
def check_nozzles(self):
|
||||
"""
|
||||
Print nozzle-check pattern.
|
||||
"""
|
||||
if not self.hostname:
|
||||
return None
|
||||
status = True
|
||||
lpr = EpsonLpr(self.hostname)
|
||||
|
||||
# Sequence list
|
||||
commands = [
|
||||
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
|
||||
]
|
||||
try:
|
||||
lpr.connect()
|
||||
resp = lpr.send(b"".join(commands))
|
||||
except Exception as e:
|
||||
status = False
|
||||
finally:
|
||||
lpr.disconnect()
|
||||
return status
|
||||
|
||||
def clean_nozzles(self, group_index, power_clean=False):
|
||||
"""
|
||||
Initiates nozzles cleaning routine with optional power clean.
|
||||
"""
|
||||
if not self.hostname:
|
||||
return None
|
||||
if group_index > 5 or group_index < 0:
|
||||
return None
|
||||
status = True
|
||||
lpr = EpsonLpr(self.hostname)
|
||||
|
||||
now = datetime.datetime.now()
|
||||
t_data = bytearray()
|
||||
t_data = b'\x00'
|
||||
t_data += now.year.to_bytes(2, 'big') # Year
|
||||
t_data += bytes([now.month, now.day, now.hour, now.minute, now.second])
|
||||
|
||||
group = group_index # https://github.com/abrasive/x900-otsakupuhastajat/blob/master/emanage.py#L148-L154
|
||||
if power_clean:
|
||||
group |= 0x10 # https://github.com/abrasive/x900-otsakupuhastajat/blob/master/emanage.py#L220
|
||||
|
||||
# Sequence list
|
||||
commands = [
|
||||
lpr.EXIT_PACKET_MODE, # Exit packet mode
|
||||
lpr.ENTER_REMOTE_MODE, # Engage remote mode commands
|
||||
lpr.remote_cmd("TI", t_data), # Synchronize RTC
|
||||
lpr.remote_cmd("CH", b'\x00' + bytes([group])), # Run print-head cleaning
|
||||
lpr.EXIT_REMOTE_MODE, # Disengage remote control
|
||||
lpr.JOB_END # Mark maintenance job complete
|
||||
]
|
||||
try:
|
||||
lpr.connect()
|
||||
resp = lpr.send(b"".join(commands))
|
||||
except Exception as e:
|
||||
status = False
|
||||
finally:
|
||||
lpr.disconnect()
|
||||
return status
|
||||
|
||||
def write_first_ti_received_time(
|
||||
self, year: int, month: int, day: int) -> bool:
|
||||
"""Update first TI received time"""
|
||||
|
||||
311
ui.py
311
ui.py
@@ -37,7 +37,7 @@ from find_printers import PrinterScanner
|
||||
from text_console import TextConsole
|
||||
|
||||
|
||||
VERSION = "6.0.2"
|
||||
VERSION = "6.1.0"
|
||||
|
||||
NO_CONF_ERROR = (
|
||||
" Please select a printer model and a valid IP address,"
|
||||
@@ -45,7 +45,7 @@ NO_CONF_ERROR = (
|
||||
)
|
||||
|
||||
CONFIRM_MESSAGE = (
|
||||
"Confirm Action",
|
||||
"EEPROM update - Confirm Action",
|
||||
"Please copy and save the codes in the [NOTE] shown on the screen."
|
||||
" They can be used to restore the initial configuration"
|
||||
" in case of problems.\n\n"
|
||||
@@ -723,7 +723,7 @@ class EpsonPrinterUI(tk.Tk):
|
||||
row_n += 1
|
||||
button_frame = ttk.Frame(main_frame, padding=PAD)
|
||||
button_frame.grid(row=row_n, column=0, pady=PADY, sticky=(tk.W, tk.E))
|
||||
button_frame.columnconfigure((0, 1, 2, 3), weight=1) # expand columns
|
||||
button_frame.columnconfigure((0, 1, 2, 3, 4), weight=1) # expand columns
|
||||
|
||||
# Query Printer Status
|
||||
self.status_button = ttk.Button(
|
||||
@@ -746,6 +746,17 @@ class EpsonPrinterUI(tk.Tk):
|
||||
row=0, column=1, padx=PADX, pady=PADX, sticky=(tk.W, tk.E)
|
||||
)
|
||||
|
||||
# Clean nozzles
|
||||
self.clean_nozzles_button = ttk.Button(
|
||||
button_frame,
|
||||
text="Clean\nNozzles",
|
||||
command=self.clean_nozzles,
|
||||
style="Centered.TButton"
|
||||
)
|
||||
self.clean_nozzles_button.grid(
|
||||
row=0, column=2, padx=PADX, pady=PADX, sticky=(tk.W, tk.E)
|
||||
)
|
||||
|
||||
# Detect configuration values
|
||||
self.detect_configuration_button = ttk.Button(
|
||||
button_frame,
|
||||
@@ -754,18 +765,18 @@ class EpsonPrinterUI(tk.Tk):
|
||||
style="Centered.TButton"
|
||||
)
|
||||
self.detect_configuration_button.grid(
|
||||
row=0, column=2, padx=PADX, pady=PADX, sticky=(tk.W, tk.E)
|
||||
row=0, column=3, padx=PADX, pady=PADX, sticky=(tk.W, tk.E)
|
||||
)
|
||||
|
||||
# Temporary Reset Waste Ink Levels
|
||||
self.detect_configuration_button = ttk.Button(
|
||||
self.temp_reset_ink_waste_button = ttk.Button(
|
||||
button_frame,
|
||||
text="Temporary Reset\nWaste Ink Levels",
|
||||
command=self.temp_reset_waste_ink,
|
||||
style="Centered.TButton"
|
||||
)
|
||||
self.detect_configuration_button.grid(
|
||||
row=0, column=3, padx=PADX, pady=PADX, sticky=(tk.W, tk.E)
|
||||
self.temp_reset_ink_waste_button.grid(
|
||||
row=0, column=4, padx=PADX, pady=PADX, sticky=(tk.W, tk.E)
|
||||
)
|
||||
|
||||
# [row 4] Tweak Buttons
|
||||
@@ -984,9 +995,10 @@ class EpsonPrinterUI(tk.Tk):
|
||||
)
|
||||
if not file_path:
|
||||
self.show_status_text_view()
|
||||
self.status_text.insert(tk.END, '[WARNING]', "warn")
|
||||
self.status_text.insert(
|
||||
tk.END,
|
||||
f"[WARNING] File save operation aborted.\n"
|
||||
f" File save operation aborted.\n"
|
||||
)
|
||||
return
|
||||
# Ensure the file has the desired extension
|
||||
@@ -1025,9 +1037,10 @@ class EpsonPrinterUI(tk.Tk):
|
||||
self.config(cursor="")
|
||||
self.update_idletasks()
|
||||
self.show_status_text_view()
|
||||
self.status_text.insert(tk.END, '[WARNING]', "warn")
|
||||
self.status_text.insert(
|
||||
tk.END,
|
||||
f"[WARNING] File load operation aborted.\n"
|
||||
f" File load operation aborted.\n"
|
||||
)
|
||||
return
|
||||
if type == 0:
|
||||
@@ -1225,6 +1238,8 @@ Web site: https://github.com/Ircama/epson_print_conf
|
||||
ToolTip(self.set_mac_addr, "")
|
||||
ToolTip(self.read_eeprom_button, "")
|
||||
ToolTip(self.detect_configuration_button, "")
|
||||
ToolTip(self.clean_nozzles_button, "")
|
||||
ToolTip(self.temp_reset_ink_waste_button, "")
|
||||
ToolTip(self.write_eeprom_button, "")
|
||||
ToolTip(self.reset_button, "")
|
||||
if self.ip_var.get():
|
||||
@@ -1238,6 +1253,8 @@ Web site: https://github.com/Ircama/epson_print_conf
|
||||
self.reset_button.state(["disabled"])
|
||||
self.status_button.state(["disabled"])
|
||||
self.read_eeprom_button.state(["disabled"])
|
||||
self.clean_nozzles_button.state(["disabled"])
|
||||
self.temp_reset_ink_waste_button.state(["disabled"])
|
||||
self.detect_configuration_button.state(["disabled"])
|
||||
self.write_eeprom_button.state(["disabled"])
|
||||
self.web_interface_button.state(["disabled"])
|
||||
@@ -1260,10 +1277,20 @@ Web site: https://github.com/Ircama/epson_print_conf
|
||||
self.read_eeprom_button,
|
||||
"Feature not defined in the printer configuration."
|
||||
)
|
||||
self.temp_reset_ink_waste_button.state(["disabled"])
|
||||
ToolTip(
|
||||
self.temp_reset_ink_waste_button,
|
||||
"Select the printer first."
|
||||
)
|
||||
self.clean_nozzles_button.state(["disabled"])
|
||||
ToolTip(
|
||||
self.clean_nozzles_button,
|
||||
"Select the printer first."
|
||||
)
|
||||
self.detect_configuration_button.state(["disabled"])
|
||||
ToolTip(
|
||||
self.detect_configuration_button,
|
||||
"Feature not defined in the printer configuration."
|
||||
"Select the printer first."
|
||||
)
|
||||
self.write_eeprom_button.state(["disabled"])
|
||||
ToolTip(
|
||||
@@ -1274,8 +1301,12 @@ Web site: https://github.com/Ircama/epson_print_conf
|
||||
if "read_key" in self.printer.parm:
|
||||
self.read_eeprom_button.state(["!disabled"])
|
||||
ToolTip(self.read_eeprom_button, "")
|
||||
self.clean_nozzles_button.state(["!disabled"])
|
||||
self.temp_reset_ink_waste_button.state(["!disabled"])
|
||||
self.detect_configuration_button.state(["!disabled"])
|
||||
ToolTip(self.detect_configuration_button, "")
|
||||
ToolTip(self.clean_nozzles_button, "")
|
||||
ToolTip(self.temp_reset_ink_waste_button, "")
|
||||
if "write_key" in self.printer.parm:
|
||||
self.write_eeprom_button.state(["!disabled"])
|
||||
ToolTip(
|
||||
@@ -1355,6 +1386,8 @@ Web site: https://github.com/Ircama/epson_print_conf
|
||||
self.status_button.state(["disabled"])
|
||||
self.read_eeprom_button.state(["disabled"])
|
||||
self.detect_configuration_button.state(["disabled"])
|
||||
self.clean_nozzles_button.state(["disabled"])
|
||||
self.temp_reset_ink_waste_button.state(["disabled"])
|
||||
self.write_eeprom_button.state(["disabled"])
|
||||
|
||||
self.po_timer_entry.state(["disabled"])
|
||||
@@ -1642,8 +1675,9 @@ Web site: https://github.com/Ircama/epson_print_conf
|
||||
except Exception as e:
|
||||
self.handle_printer_error(e)
|
||||
else:
|
||||
self.status_text.insert(tk.END, '[WARNING]', "warn")
|
||||
self.status_text.insert(
|
||||
tk.END, f"[WARNING] Set Power off timer aborted.\n"
|
||||
tk.END, f" Set Power off timer aborted.\n"
|
||||
)
|
||||
self.config(cursor="")
|
||||
self.update_idletasks()
|
||||
@@ -1684,8 +1718,9 @@ Web site: https://github.com/Ircama/epson_print_conf
|
||||
"if you are very sure of what you do.\n\n",
|
||||
default='no')
|
||||
if not response:
|
||||
self.status_text.insert(tk.END, '[WARNING]', "warn")
|
||||
self.status_text.insert(
|
||||
tk.END, "[WARNING] Operation aborted.\n"
|
||||
tk.END, " Operation aborted.\n"
|
||||
)
|
||||
self.config(cursor="")
|
||||
self.update_idletasks()
|
||||
@@ -1710,8 +1745,9 @@ Web site: https://github.com/Ircama/epson_print_conf
|
||||
)
|
||||
response = messagebox.askyesno(*CONFIRM_MESSAGE, default='no')
|
||||
if not response:
|
||||
self.status_text.insert(tk.END, '[WARNING]', "warn")
|
||||
self.status_text.insert(
|
||||
tk.END, "[WARNING] Operation aborted.\n"
|
||||
tk.END, " Operation aborted.\n"
|
||||
)
|
||||
self.config(cursor="")
|
||||
self.update_idletasks()
|
||||
@@ -1797,8 +1833,9 @@ Web site: https://github.com/Ircama/epson_print_conf
|
||||
)
|
||||
response = messagebox.askyesno(*CONFIRM_MESSAGE, default='no')
|
||||
if not response:
|
||||
self.status_text.insert(tk.END, '[WARNING]', "warn")
|
||||
self.status_text.insert(
|
||||
tk.END, "[WARNING] Operation aborted.\n"
|
||||
tk.END, " Operation aborted.\n"
|
||||
)
|
||||
self.config(cursor="")
|
||||
self.update_idletasks()
|
||||
@@ -1943,9 +1980,10 @@ Web site: https://github.com/Ircama/epson_print_conf
|
||||
except Exception as e:
|
||||
self.handle_printer_error(e)
|
||||
else:
|
||||
self.status_text.insert(tk.END, '[WARNING]', "warn")
|
||||
self.status_text.insert(
|
||||
tk.END,
|
||||
f"[WARNING] Change of 'First TI received time' aborted.\n",
|
||||
f" Change of 'First TI received time' aborted.\n",
|
||||
)
|
||||
self.config(cursor="")
|
||||
self.update_idletasks()
|
||||
@@ -2571,6 +2609,8 @@ Web site: https://github.com/Ircama/epson_print_conf
|
||||
f'{self.printer.PRINTER_CONFIG[DETECTED]}.\n'
|
||||
)
|
||||
self.detect_configuration_button.state(["!disabled"])
|
||||
self.clean_nozzles_button.state(["!disabled"])
|
||||
self.temp_reset_ink_waste_button.state(["!disabled"])
|
||||
self.status_text.insert(tk.END, '[INFO]', "info")
|
||||
self.status_text.insert(
|
||||
tk.END, " Detect operation completed.\n"
|
||||
@@ -2595,7 +2635,7 @@ Web site: https://github.com/Ircama/epson_print_conf
|
||||
self.status_text.insert(tk.END, NO_CONF_ERROR)
|
||||
return
|
||||
response = messagebox.askyesno(
|
||||
"Confirm Action",
|
||||
"Detect Access Keys - Confirm Action",
|
||||
"Warning: this is a brute force operation, which takes several\n"
|
||||
"minutes to complete.\n\n"
|
||||
"Results will be shown in the status box.\n\n"
|
||||
@@ -2614,12 +2654,18 @@ Web site: https://github.com/Ircama/epson_print_conf
|
||||
self.update()
|
||||
self.after(100, lambda: run_detection())
|
||||
else:
|
||||
self.status_text.insert(tk.END, '[WARNING]', "warn")
|
||||
self.status_text.insert(
|
||||
tk.END, f"[WARNING] Detect access key aborted.\n"
|
||||
tk.END, f" Detect access key aborted.\n"
|
||||
)
|
||||
self.config(cursor="")
|
||||
self.update_idletasks()
|
||||
|
||||
def set_cursor(self, widget, cursor_type):
|
||||
widget.config(cursor=cursor_type)
|
||||
for child in widget.winfo_children():
|
||||
self.set_cursor(child, cursor_type)
|
||||
|
||||
def web_interface(self, cursor=True):
|
||||
if cursor:
|
||||
self.config(cursor="watch")
|
||||
@@ -2659,6 +2705,195 @@ Web site: https://github.com/Ircama/epson_print_conf
|
||||
self.config(cursor="")
|
||||
self.update_idletasks()
|
||||
|
||||
def clean_nozzles(self):
|
||||
"""
|
||||
Initiates nozzles cleaning routine with optional power clean.
|
||||
Displays a dialog to select a nozzle group and power clean option.
|
||||
"""
|
||||
|
||||
def show_clean_dialog():
|
||||
# Define groups
|
||||
groups = [
|
||||
"Clean all nozzles",
|
||||
"Cyan + Vivid Magenta",
|
||||
"Photo Black + Matte Black + Light Black",
|
||||
"Orange + Green",
|
||||
"Light Light Black + Yellow",
|
||||
"Vivid Light Magenta + Light Cyan"
|
||||
]
|
||||
|
||||
# Create modal dialog
|
||||
dialog = tk.Toplevel(self)
|
||||
dialog.title("Clean Nozzles Options")
|
||||
dialog.transient(self)
|
||||
dialog.grab_set()
|
||||
dialog.focus_force()
|
||||
|
||||
introduction = (
|
||||
'The printer performs nozzles cleaning by flushing excess ink '
|
||||
'through the nozzles.'
|
||||
)
|
||||
tk.Label(
|
||||
dialog,
|
||||
text=introduction,
|
||||
wraplength=400,
|
||||
justify='left',
|
||||
foreground='gray30'
|
||||
).pack(padx=10)
|
||||
|
||||
# Label
|
||||
tk.Label(dialog, text="Select Nozzle Group:").pack(
|
||||
padx=10, pady=(10, 0)
|
||||
)
|
||||
|
||||
# Compute width in characters for combobox
|
||||
max_len = max(len(item) for item in groups)
|
||||
combo_var = tk.StringVar()
|
||||
combo = ttk.Combobox(
|
||||
dialog,
|
||||
textvariable=combo_var,
|
||||
values=groups,
|
||||
state="readonly",
|
||||
width=max_len
|
||||
)
|
||||
combo.current(0)
|
||||
combo.configure(justify='center') # Center the displayed text in the combobox
|
||||
combo.pack(padx=10, pady=5)
|
||||
combo.pack(padx=10, pady=5)
|
||||
combo.focus_set()
|
||||
|
||||
note = (
|
||||
'The default action is to clean all nozzles.\n'
|
||||
'Other actions might not be supported on your printer.'
|
||||
)
|
||||
tk.Label(
|
||||
dialog,
|
||||
text=note,
|
||||
wraplength=400,
|
||||
justify='left',
|
||||
foreground='gray30'
|
||||
).pack(padx=10, pady=(10, 5))
|
||||
|
||||
# Checkbutton for power clean
|
||||
power_var = tk.BooleanVar(value=False)
|
||||
chk = ttk.Checkbutton(
|
||||
dialog, text="Power Clean", variable=power_var
|
||||
)
|
||||
chk.pack(padx=10, pady=5)
|
||||
|
||||
# Warning message for power clean ink usage
|
||||
warning_text = (
|
||||
"Power Clean uses a significant amount of ink "
|
||||
"to flush the nozzles, "
|
||||
"and more rapidly fills the internal waste ink tank, "
|
||||
"which collects "
|
||||
"the excess ink used during the cleaning process."
|
||||
)
|
||||
msg = tk.Message(
|
||||
dialog,
|
||||
text=warning_text, width=(max_len+2)*8,
|
||||
foreground='gray30'
|
||||
)
|
||||
msg.pack(padx=10, pady=(2, 5))
|
||||
|
||||
# Container for buttons
|
||||
btn_frame = ttk.Frame(dialog)
|
||||
btn_frame.pack(padx=10, pady=(5, 10), fill=tk.X)
|
||||
btn_frame.columnconfigure((0, 1), weight=1)
|
||||
|
||||
result = {'value': None}
|
||||
|
||||
def on_confirm(event=None):
|
||||
sel = combo.current()
|
||||
if sel < 0:
|
||||
return
|
||||
result['value'] = (sel, power_var.get())
|
||||
dialog.destroy()
|
||||
|
||||
def on_cancel(event=None):
|
||||
dialog.destroy()
|
||||
|
||||
# Confirm and Cancel buttons
|
||||
confirm_btn = ttk.Button(
|
||||
btn_frame, text="Confirm", command=on_confirm
|
||||
)
|
||||
cancel_btn = ttk.Button(
|
||||
btn_frame, text="Cancel", command=on_cancel
|
||||
)
|
||||
confirm_btn.grid(row=0, column=0, sticky=tk.EW, padx=(0, 5))
|
||||
cancel_btn.grid(row=0, column=1, sticky=tk.EW)
|
||||
|
||||
# Highlight Cancel as default and bind keys
|
||||
cancel_btn.focus_set()
|
||||
dialog.bind('<Return>', on_confirm)
|
||||
dialog.bind('<Escape>', on_cancel)
|
||||
|
||||
# Center dialog
|
||||
dialog.update_idletasks()
|
||||
w = dialog.winfo_reqwidth()
|
||||
h = dialog.winfo_reqheight()
|
||||
x = self.winfo_x() + (self.winfo_width() - w) // 2
|
||||
y = self.winfo_y() + (self.winfo_height() - h) // 2
|
||||
dialog.geometry(f"{w}x{h}+{x}+{y}")
|
||||
|
||||
dialog.wait_window()
|
||||
return result['value']
|
||||
|
||||
def run_cleaning(group_index, power_clean):
|
||||
try:
|
||||
ret = self.printer.clean_nozzles(group_index, power_clean)
|
||||
except Exception as e:
|
||||
self.status_text.insert(tk.END, '[ERROR]', "error")
|
||||
self.status_text.insert(
|
||||
tk.END, f" Clean nozzless failure: {e}\n"
|
||||
)
|
||||
if ret is None:
|
||||
self.status_text.insert(tk.END, '[ERROR]', "error")
|
||||
self.status_text.insert(
|
||||
tk.END, f" clean_nozzles internal error.\n"
|
||||
)
|
||||
elif ret is False:
|
||||
self.status_text.insert(tk.END, '[ERROR]', "error")
|
||||
self.status_text.insert(
|
||||
tk.END, f" Printer is unreachable or offline.\n"
|
||||
)
|
||||
else:
|
||||
self.status_text.insert(tk.END, '[INFO]', "info")
|
||||
self.status_text.insert(tk.END,
|
||||
f" Initiated cleaning of nozzles.\n"
|
||||
)
|
||||
self.set_cursor(self, "")
|
||||
self.update_idletasks()
|
||||
|
||||
ip_address = self.ip_var.get()
|
||||
if not self._is_valid_ip(ip_address):
|
||||
self.status_text.insert(tk.END, '[ERROR]', "error")
|
||||
self.status_text.insert(tk.END, NO_CONF_ERROR)
|
||||
self.set_cursor(self, "")
|
||||
self.update_idletasks()
|
||||
return
|
||||
|
||||
if not self.printer:
|
||||
return
|
||||
|
||||
# Call the dialog
|
||||
selection = show_clean_dialog()
|
||||
if selection is None:
|
||||
self.status_text.insert(tk.END, '[WARNING]', "warn")
|
||||
self.status_text.insert(
|
||||
tk.END,
|
||||
f" Nozzles cleaning operation aborted.\n"
|
||||
)
|
||||
self.set_cursor(self, "")
|
||||
self.update_idletasks()
|
||||
return # User cancelled
|
||||
|
||||
group_index, power_clean = selection
|
||||
|
||||
self.set_cursor(self, "watch")
|
||||
self.update_idletasks()
|
||||
self.after(100, lambda: run_cleaning(group_index, power_clean))
|
||||
|
||||
def detect_configuration(self, cursor=True):
|
||||
def detect_sequence(eeprom, sequence):
|
||||
seq_len = len(sequence)
|
||||
@@ -2928,8 +3163,9 @@ Web site: https://github.com/Ircama/epson_print_conf
|
||||
self.update()
|
||||
self.after(200, lambda: write_eeprom_values(dict_addr_val))
|
||||
else:
|
||||
self.status_text.insert(tk.END, '[WARNING]', "warn")
|
||||
self.status_text.insert(
|
||||
tk.END, f"[WARNING] Write EEPROM aborted.\n"
|
||||
tk.END, f" Write EEPROM aborted.\n"
|
||||
)
|
||||
self.config(cursor="")
|
||||
self.update_idletasks()
|
||||
@@ -2974,6 +3210,24 @@ Web site: https://github.com/Ircama/epson_print_conf
|
||||
method_to_call = getattr(self, current_function_name)
|
||||
self.after(100, lambda: method_to_call(cursor=False))
|
||||
return
|
||||
msg = (
|
||||
"Reset Waste Ink Levels - Confirm Action",
|
||||
"This feature permanently resets the ink waste tank full counters."
|
||||
"\n\nAlways replace the waste ink pads before "
|
||||
"continuing. Carefully monitor the ink flow and "
|
||||
"consider risks of ink overflow into printer internals and "
|
||||
"also environmental contamination that possibly cannot be cleaned."
|
||||
"\n\nAre you sure you want to proceed?"
|
||||
)
|
||||
response = messagebox.askyesno(*msg, default='no')
|
||||
if not response:
|
||||
self.status_text.insert(tk.END, '[WARNING]', "warn")
|
||||
self.status_text.insert(
|
||||
tk.END, f" Waste ink levels reset aborted.\n"
|
||||
)
|
||||
self.config(cursor="")
|
||||
self.update_idletasks()
|
||||
return
|
||||
self.show_status_text_view()
|
||||
ip_address = self.ip_var.get()
|
||||
if (
|
||||
@@ -3026,8 +3280,9 @@ Web site: https://github.com/Ircama/epson_print_conf
|
||||
except Exception as e:
|
||||
self.handle_printer_error(e)
|
||||
else:
|
||||
self.status_text.insert(tk.END, '[WARNING]', "warn")
|
||||
self.status_text.insert(
|
||||
tk.END, f"[WARNING] Waste ink levels reset aborted.\n"
|
||||
tk.END, f" Waste ink levels reset aborted.\n"
|
||||
)
|
||||
self.config(cursor="")
|
||||
self.update_idletasks()
|
||||
@@ -3057,10 +3312,14 @@ Web site: https://github.com/Ircama/epson_print_conf
|
||||
if not self.printer:
|
||||
return
|
||||
msg = (
|
||||
"Confirm Action",
|
||||
"This feature temporarily bypasses the ink waste tank full warning,"
|
||||
" which would otherwise disable printing. "
|
||||
"Temporary Bypass of the Waste Ink Lock - Confirm Action",
|
||||
"This feature temporarily bypasses the ink waste tank full"
|
||||
" message, which would otherwise disable printing. "
|
||||
"\n\nThis setting does not persist a reboot. "
|
||||
"\n\nAlways replace the waste ink pads before "
|
||||
"continuing. Carefully monitor the ink flow and "
|
||||
"consider risks of ink overflow into printer internals and "
|
||||
"also environmental contamination that possibly cannot be cleaned."
|
||||
"\n\nAre you sure you want to proceed?"
|
||||
)
|
||||
response = messagebox.askyesno(*msg, default='no')
|
||||
@@ -3070,21 +3329,23 @@ Web site: https://github.com/Ircama/epson_print_conf
|
||||
self.status_text.insert(tk.END, '[INFO]', "info")
|
||||
self.status_text.insert(
|
||||
tk.END,
|
||||
" Waste ink levels have been temporarily reset."
|
||||
" Waste ink levels have been temporarily bypassed."
|
||||
" You can now print.\n"
|
||||
)
|
||||
else:
|
||||
self.status_text.insert(tk.END, '[ERROR]', "error")
|
||||
self.status_text.insert(
|
||||
tk.END,
|
||||
" Failed to perform the temporary reset of the "
|
||||
" Failed to perform the temporary bypass of the "
|
||||
"waste ink levels."
|
||||
)
|
||||
except Exception as e:
|
||||
self.handle_printer_error(e)
|
||||
else:
|
||||
self.status_text.insert(tk.END, '[WARNING]', "warn")
|
||||
self.status_text.insert(
|
||||
tk.END, f"[WARNING] Waste ink levels reset aborted.\n"
|
||||
tk.END,
|
||||
" Temporary bypass of the waste ink levels aborted.\n"
|
||||
)
|
||||
self.config(cursor="")
|
||||
self.update_idletasks()
|
||||
|
||||
Reference in New Issue
Block a user