Handle printer model error when reading EEPROM values; add read/write EEPROM
Some checks are pending
Python syntax checker / build (3.10) (push) Waiting to run
Python syntax checker / build (3.7) (push) Waiting to run
Python syntax checker / build (3.8) (push) Waiting to run
Python syntax checker / build (3.9) (push) Waiting to run
Python syntax checker / build (3.x) (push) Waiting to run

ref #26
This commit is contained in:
Ircama 2024-10-05 11:48:30 +02:00
parent 4f3027f972
commit ea059cbde9
2 changed files with 310 additions and 25 deletions

View file

@ -39,6 +39,7 @@ The GUI can automatically find and display printer IP addresses and model names,
- Set the power-off timer. - Set the power-off timer.
- Reset the ink waste counter. - Reset the ink waste counter.
- Configure the _First TI Received Time_. - Configure the _First TI Received Time_.
- read and write the EEPROM
The *First TI Received Time* in Epson printers typically refers to the timestamp of the first transmission instruction to the printer when it was first set up. This feature tracks when the printer first operated. The *First TI Received Time* in Epson printers typically refers to the timestamp of the first transmission instruction to the printer when it was first set up. This feature tracks when the printer first operated.
@ -99,7 +100,7 @@ This GUI runs on any Operating Systems supported by Python (not just Windows), b
GUI usage: GUI usage:
``` ```
python ui.py [-h] [-m MODEL] [-a HOSTNAME] [-P PICKLE_FILE] [-O] usage: ui.py [-h] [-m MODEL] [-a HOSTNAME] [-P PICKLE_FILE] [-O] [-d]
optional arguments: optional arguments:
-h, --help show this help message and exit -h, --help show this help message and exit
@ -109,8 +110,8 @@ optional arguments:
Printer host name or IP address. (Example: -a 192.168.1.87) Printer host name or IP address. (Example: -a 192.168.1.87)
-P PICKLE_FILE, --pickle PICKLE_FILE -P PICKLE_FILE, --pickle PICKLE_FILE
Load a pickle configuration archive saved by parse_devices.py Load a pickle configuration archive saved by parse_devices.py
-O, --override Replace the default configuration with the one in the pickle file instead of merging (default -O, --override Replace the default configuration with the one in the pickle file instead of merging (default is to merge)
is to merge) -d, --debug Print debug information
epson_print_conf GUI epson_print_conf GUI
``` ```

314
ui.py
View file

@ -13,6 +13,7 @@ import inspect
from datetime import datetime from datetime import datetime
import socket import socket
import traceback import traceback
import logging
import black import black
import tkinter as tk import tkinter as tk
@ -20,7 +21,7 @@ from tkinter import ttk, Menu
from tkinter.scrolledtext import ScrolledText from tkinter.scrolledtext import ScrolledText
import tkinter.font as tkfont import tkinter.font as tkfont
from tkcalendar import DateEntry # Ensure you have: pip install tkcalendar from tkcalendar import DateEntry # Ensure you have: pip install tkcalendar
from tkinter import messagebox from tkinter import simpledialog, messagebox
import pyperclip import pyperclip
from epson_print_conf import EpsonPrinter from epson_print_conf import EpsonPrinter
@ -77,6 +78,27 @@ def get_printer_models(input_string):
return processed_tokens return processed_tokens
class MultiLineInputDialog(simpledialog.Dialog):
def __init__(self, parent, title=None, text=""):
self.text=text
super().__init__(parent, title)
def body(self, frame):
# Add a label with instructions
self.label = tk.Label(frame, text=self.text)
self.label.pack(pady=5)
# Create a Text widget for multiline input
self.textbox = tk.Text(frame, height=5, width=50)
self.textbox.configure(font=("TkDefaultFont"))
self.textbox.pack()
return self.textbox
def apply(self):
# Get the input from the Text widget
self.result = self.textbox.get("1.0", tk.END).strip()
class ToolTip: class ToolTip:
def __init__( def __init__(
self, self,
@ -296,6 +318,10 @@ class EpsonPrinterUI(tk.Tk):
" (by removing the last part before pressing 'Detect Printers').", " (by removing the last part before pressing 'Detect Printers').",
) )
# Create a custom style for the button to center the text
style = ttk.Style()
style.configure("Centered.TButton", justify='center', anchor="center")
# [row 1] Container frame for the two LabelFrames Power-off timer and TI Received Time # [row 1] Container frame for the two LabelFrames Power-off timer and TI Received Time
row_n += 1 row_n += 1
container_frame = ttk.Frame(main_frame, padding=PAD) container_frame = ttk.Frame(main_frame, padding=PAD)
@ -417,8 +443,9 @@ class EpsonPrinterUI(tk.Tk):
# Detect Printers # Detect Printers
self.detect_button = ttk.Button( self.detect_button = ttk.Button(
button_frame, button_frame,
text="Detect Printers", text="Detect\nPrinters",
command=self.start_detect_printers, command=self.start_detect_printers,
style="Centered.TButton"
) )
self.detect_button.grid( self.detect_button.grid(
row=0, column=0, padx=PADX, pady=PADX, sticky=(tk.W, tk.E) row=0, column=0, padx=PADX, pady=PADX, sticky=(tk.W, tk.E)
@ -426,7 +453,9 @@ class EpsonPrinterUI(tk.Tk):
# Printer Status # Printer Status
self.status_button = ttk.Button( self.status_button = ttk.Button(
button_frame, text="Printer Status", command=self.printer_status button_frame, text="Printer\nStatus",
command=self.printer_status,
style="Centered.TButton"
) )
self.status_button.grid( self.status_button.grid(
row=0, column=1, padx=PADX, pady=PADY, sticky=(tk.W, tk.E) row=0, column=1, padx=PADX, pady=PADY, sticky=(tk.W, tk.E)
@ -435,13 +464,40 @@ class EpsonPrinterUI(tk.Tk):
# Reset Waste Ink Levels # Reset Waste Ink Levels
self.reset_button = ttk.Button( self.reset_button = ttk.Button(
button_frame, button_frame,
text="Reset Waste Ink Levels", text="Reset Waste\nInk Levels",
command=self.reset_waste_ink, command=self.reset_waste_ink,
style="Centered.TButton"
) )
self.reset_button.grid( self.reset_button.grid(
row=0, column=2, padx=PADX, pady=PADX, sticky=(tk.W, tk.E) row=0, column=2, padx=PADX, pady=PADX, sticky=(tk.W, tk.E)
) )
# Read EEPROM
self.read_eeprom_button = ttk.Button(
button_frame,
text="Read\nEEPROM",
command=self.read_eeprom,
style="Centered.TButton"
)
self.read_eeprom_button.grid(
row=0, column=3, padx=PADX, pady=PADX, sticky=(tk.W, tk.E)
)
# Read EEPROM
self.write_eeprom_button = ttk.Button(
button_frame,
text="Write\nEEPROM",
command=self.write_eeprom,
style="Centered.TButton"
)
ToolTip(
self.write_eeprom_button,
"Ensure you really want this before pressing this key."
)
self.write_eeprom_button.grid(
row=0, column=4, padx=PADX, pady=PADX, sticky=(tk.W, tk.E)
)
# [row 3] Status display (including ScrolledText and Treeview) # [row 3] Status display (including ScrolledText and Treeview)
row_n += 1 row_n += 1
status_frame = ttk.LabelFrame(main_frame, text="Status", padding=PAD) status_frame = ttk.LabelFrame(main_frame, text="Status", padding=PAD)
@ -676,14 +732,26 @@ class EpsonPrinterUI(tk.Tk):
def get_current_eeprom_values(self, values, label): def get_current_eeprom_values(self, values, label):
try: try:
org_values = ', '.join( org_values = ', '.join(
f"{k}: {int(v, 16)}" for k, v in zip( "" if v is None else f"{k}: {int(v, 16)}" for k, v in zip(
values, self.printer.read_eeprom_many(values, label=label) values, self.printer.read_eeprom_many(values, label=label)
) )
) )
if org_values:
self.status_text.insert( self.status_text.insert(
tk.END, tk.END,
f"[NOTE] Current EEPROM values for {label}: {org_values}.\n" f"[NOTE] Current EEPROM values for {label}: {org_values}.\n"
) )
else:
self.status_text.insert(
tk.END,
f'[ERROR] Cannot read EEPROM values for "{label}"'
': invalid printer model selected.\n'
)
self.config(cursor="")
self.update_idletasks()
return False
self.config(cursor="")
self.update_idletasks()
return True return True
except Exception as e: except Exception as e:
self.handle_printer_error(e) self.handle_printer_error(e)
@ -723,12 +791,16 @@ class EpsonPrinterUI(tk.Tk):
self.status_text.insert( self.status_text.insert(
tk.END, "[ERROR] Please Use a valid value for minutes.\n" tk.END, "[ERROR] Please Use a valid value for minutes.\n"
) )
self.config(cursor="")
self.update_idletasks()
return return
try: try:
if not self.get_current_eeprom_values( if not self.get_current_eeprom_values(
self.printer.parm["stats"]["Power off timer"], self.printer.parm["stats"]["Power off timer"],
"Power off timer" "Power off timer"
): ):
self.config(cursor="")
self.update_idletasks()
return return
except Exception as e: except Exception as e:
self.handle_printer_error(e) self.handle_printer_error(e)
@ -823,6 +895,8 @@ class EpsonPrinterUI(tk.Tk):
self.printer.parm["stats"]["First TI received time"], self.printer.parm["stats"]["First TI received time"],
"First TI received time" "First TI received time"
): ):
self.config(cursor="")
self.update_idletasks()
return return
except Exception as e: except Exception as e:
self.handle_printer_error(e) self.handle_printer_error(e)
@ -975,6 +1049,208 @@ class EpsonPrinterUI(tk.Tk):
finally: finally:
self.update_idletasks() self.update_idletasks()
def read_eeprom(self):
def parse_list_input(input_str):
try:
# Remove any character before ":" if not including digits
colon_index = input_str.find(':')
if colon_index != -1 and not any(
char.isdigit() for char in input_str[:colon_index]
):
input_str = input_str[colon_index + 1:]
# Remove any unwanted characters like brackets, if present
input_str = input_str.strip('{}[]()')
# Remove trailing ".", if present
input_str = input_str.rstrip('.')
parts = input_str.split(',')
addresses = []
for part in parts:
part = part.strip()
if '-' in part:
# Handle range like '2-10'
start, end = map(int, part.split('-'))
addresses.extend(range(start, end + 1)) # Generate sequence between start and end
else:
# Handle individual addresses
addresses.append(int(part))
return addresses
except ValueError:
# Show an error if the input is not valid
messagebox.showerror(
"Invalid Input for Read EEPROM",
"Please enter a valid list of integers."
)
return None
def get_input():
# Create a popup to accept the list input
dialog = MultiLineInputDialog(
self,
"Read EEPROM values",
"Enter a comma-separated list of addresses to\n"
"be read (e.g. 22, 23, 59 or [171, 170, 169, 168]).\n"
"Use hyphen to represent a range (e.g., 1, 3-10, 13):"
)
if dialog.result:
addresses = parse_list_input(dialog.result)
if addresses:
return addresses
return None
def get_values(addresses):
try:
values = ', '.join(
"" if v is None else f"{k}: {int(v, 16)}" for k, v in zip(
addresses,
self.printer.read_eeprom_many(
addresses,
label="read_EEPROM"
)
)
)
except Exception as e:
self.handle_printer_error(e)
self.config(cursor="")
self.update_idletasks()
return
if values:
self.status_text.insert(
tk.END,
f"[INFO] EEPROM values: {values}.\n"
)
else:
self.status_text.insert(
tk.END,
f'[ERROR] Cannot read EEPROM values'
': invalid printer model selected.\n'
)
self.config(cursor="")
self.update_idletasks()
self.show_status_text_view()
ip_address = self.ip_var.get()
if not self._is_valid_ip(ip_address):
self.status_text.insert(tk.END, NO_CONF_ERROR)
self.config(cursor="")
self.update_idletasks()
return
addresses = get_input()
if addresses is not None:
self.config(cursor="watch")
self.update()
self.after(100, lambda: get_values(addresses))
def write_eeprom(self):
def parse_dict_input(input_str):
try:
# Remove any character before ":" if not including digits
colon_index = input_str.find(':')
if colon_index != -1 and not any(
char.isdigit() for char in input_str[:colon_index]
):
input_str = input_str[colon_index + 1:]
# Remove any unwanted characters like brackets, if present
input_str = input_str.strip('{}[]()')
# Remove trailing ".", if present
input_str = input_str.rstrip('.')
parts = input_str.split(',')
result_dict = {}
for part in parts:
part = part.strip()
if ':' not in part:
raise ValueError()
# Handle key: value pairs
key, value = map(str.strip, part.split(':', 1))
result_dict[int(key)] = int(value)
return result_dict
except ValueError:
messagebox.showerror(
"Invalid input for Write EEPROM",
"Please enter a valid comma-separated sequence "
"of 'address: value', like 24: 0, 25: 0, 30: 0 "
"using decimal numbers."
)
return {}
def get_input():
# Create a popup to accept the dictionary input
dialog = MultiLineInputDialog(
self,
"Write EEPROM values",
"Warning: this is a dangerous operation.\nContinue only "
"if you are very sure of what you do.\n\n"
"Enter a comma-separated sequence of 'address: value'\n"
"(like 24: 0, 25: 0, 30: 0) using decimal numbers:"
)
if dialog.result:
dict_addr_val = parse_dict_input(dialog.result)
if dict_addr_val:
return dict_addr_val
return None
def dialog_write_values(dict_addr_val):
try:
if not self.get_current_eeprom_values(
dict_addr_val.keys(),
"the entered addresses"
):
self.config(cursor="")
self.update_idletasks()
return
except Exception as e:
self.handle_printer_error(e)
self.config(cursor="")
self.update_idletasks()
return
self.config(cursor="")
self.update_idletasks()
response = messagebox.askyesno(*CONFIRM_MESSAGE)
if response:
self.config(cursor="watch")
self.update()
self.after(200, lambda: write_eeprom_values(dict_addr_val))
else:
self.status_text.insert(
tk.END, f"[WARNING] Write EEPROM aborted.\n"
)
self.config(cursor="")
self.update_idletasks()
def write_eeprom_values(dict_addr_val):
try:
for oid, value in dict_addr_val.items():
if not self.printer.write_eeprom(
oid, value, label="write_eeprom"
):
return False
except Exception as e:
self.handle_printer_error(e)
self.status_text.insert(
tk.END, f"[WARNING] Write EEPROM completed.\n"
)
self.config(cursor="")
self.update_idletasks()
self.show_status_text_view()
ip_address = self.ip_var.get()
if not self._is_valid_ip(ip_address):
self.status_text.insert(tk.END, NO_CONF_ERROR)
return
dict_addr_val = get_input()
if dict_addr_val is not None:
self.config(cursor="watch")
self.update()
self.status_text.insert(
tk.END, f"[INFO] Going to write EEPROM: {dict_addr_val}.\n"
)
self.after(200, lambda: dialog_write_values(dict_addr_val))
def reset_waste_ink(self, cursor=True): def reset_waste_ink(self, cursor=True):
if cursor: if cursor:
self.config(cursor="watch") self.config(cursor="watch")
@ -993,21 +1269,20 @@ class EpsonPrinterUI(tk.Tk):
try: try:
if "raw_waste_reset" in self.printer.parm: if "raw_waste_reset" in self.printer.parm:
if not self.get_current_eeprom_values( if not self.get_current_eeprom_values(
self.printer.parm["raw_waste_reset"], self.printer.parm["raw_waste_reset"].keys(),
"Raw waste reset" "Raw waste reset"
): ):
self.config(cursor="")
self.update_idletasks()
return return
if "main_waste" in self.printer.parm: for i in self.printer.parm:
if i.endswith("_waste") and "oids" in self.printer.parm[i]:
if not self.get_current_eeprom_values( if not self.get_current_eeprom_values(
self.printer.parm["main_waste"]["oids"], self.printer.parm[i]["oids"],
"Main waste reset" i.replace("_", " ").capitalize()
):
return
if "borderless_waste" in self.printer.parm:
if not self.get_current_eeprom_values(
self.printer.parm["borderless_waste"]["oids"],
"Borderless waste reset"
): ):
self.config(cursor="")
self.update_idletasks()
return return
except Exception as e: except Exception as e:
self.handle_printer_error(e) self.handle_printer_error(e)
@ -1266,7 +1541,16 @@ def main():
help="Replace the default configuration with the one in the pickle " help="Replace the default configuration with the one in the pickle "
"file instead of merging (default is to merge)", "file instead of merging (default is to merge)",
) )
parser.add_argument(
'-d',
'--debug',
dest='debug',
action='store_true',
help='Print debug information'
)
args = parser.parse_args() args = parser.parse_args()
if args.debug:
logging.getLogger().setLevel(logging.DEBUG)
conf_dict = {} conf_dict = {}
if args.pickle: if args.pickle:
conf_dict = pickle.load(args.pickle[0]) conf_dict = pickle.load(args.pickle[0])