v2.2 - bug fixing and UI improvements

This commit is contained in:
Ircama 2024-09-19 04:49:14 +02:00
parent f2e4955725
commit 249ee446ab
3 changed files with 344 additions and 96 deletions

View file

@ -99,10 +99,14 @@ This GUI runs on any Operating Systems supported by Python (not just Windows), b
GUI usage: GUI usage:
``` ```
python ui.py [-h] [-P PICKLE_FILE] [-O] python ui.py [-h] [-m MODEL] [-a HOSTNAME] [-P PICKLE_FILE] [-O]
optional arguments: optional arguments:
-h, --help show this help message and exit -h, --help show this help message and exit
-m MODEL, --model MODEL
Printer model. Example: -m XP-205
-a HOSTNAME, --address HOSTNAME
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
@ -484,16 +488,22 @@ printer.reset_waste_ink_levels()
printer.brute_force_read_key() printer.brute_force_read_key()
printer.write_first_ti_received_time(2000, 1, 2) printer.write_first_ti_received_time(2000, 1, 2)
# Dump all printer parameters # Dump all printer configuration parameters
from pprint import pprint from pprint import pprint
pprint(printer.parm) pprint(printer.parm)
```
# "black" way to dump all printer parameters "black" way to dump all printer parameters:
```python
import textwrap, black import textwrap, black
from epson_print_conf import EpsonPrinter from epson_print_conf import EpsonPrinter
printer = EpsonPrinter(model="TX730WD", hostname="192.168.178.29") printer = EpsonPrinter(model="TX730WD", hostname="192.168.178.29")
mode = black.Mode(line_length=200, magic_trailing_comma=False) mode = black.Mode(line_length=200, magic_trailing_comma=False)
print(textwrap.indent(black.format_str(f"{printer.model}: " + repr(printer.parm), mode=mode), 8*' ')) print(textwrap.indent(black.format_str(f'"{printer.model}": ' + repr(printer.parm), mode=mode), 8*' '))
# Print status:
print(black.format_str(f'"{printer.model}": ' + repr(printer.stats()), mode=mode))
``` ```
## Output example ## Output example

View file

@ -955,6 +955,26 @@ class EpsonPrinter:
"in '%s' configuration.", "in '%s' configuration.",
sameas, printer_name sameas, printer_name
) )
# process itertools classes
def expand_itertools_in_dict(d):
for key, value in d.items():
if isinstance(value, dict): # If the value is another dictionary, recurse into it
expand_itertools_in_dict(value)
elif isinstance(
value, (
itertools.chain, itertools.cycle, itertools.islice,
itertools.permutations, itertools.combinations,
itertools.product, itertools.zip_longest,
itertools.starmap, itertools.groupby
)
):
d[key] = list(value) # Convert itertools object to a list
elif isinstance(value, list): # Check inside lists for dictionaries
for i, item in enumerate(value):
if isinstance(item, dict):
expand_itertools_in_dict(item)
for printer_name, printer_data in conf.copy().items():
expand_itertools_in_dict(printer_data)
def stats(self): def stats(self):
"""Return all available information about a printer.""" """Return all available information about a printer."""
@ -1954,7 +1974,7 @@ class EpsonPrinter:
) )
return d return d
def reset_waste_ink_levels(self) -> bool: def reset_waste_ink_levels(self, dry_run=False) -> bool:
""" """
Set waste ink levels to 0. Set waste ink levels to 0.
""" """
@ -1962,12 +1982,16 @@ class EpsonPrinter:
logging.error("EpsonPrinter - invalid API usage") logging.error("EpsonPrinter - invalid API usage")
return None return None
if "raw_waste_reset" in self.parm: if "raw_waste_reset" in self.parm:
if dry_run:
return True
for oid, value in self.parm["raw_waste_reset"].items(): for oid, value in self.parm["raw_waste_reset"].items():
if not self.write_eeprom(oid, value, label="raw_waste_reset"): if not self.write_eeprom(oid, value, label="raw_waste_reset"):
return False return False
return True return True
if "main_waste" not in self.parm: if "main_waste" not in self.parm:
return None return None
if dry_run:
return True
for oid in self.parm["main_waste"]["oids"]: for oid in self.parm["main_waste"]["oids"]:
if not self.write_eeprom(oid, 0, label="main_waste"): if not self.write_eeprom(oid, 0, label="main_waste"):
return False return False

396
ui.py
View file

@ -1,10 +1,19 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Epson Printer Configuration via SNMP (TCP/IP) - GUI
"""
import sys import sys
import re import re
import threading import threading
import ipaddress import ipaddress
import inspect import inspect
from datetime import datetime from datetime import datetime
import socket
import black
import tkinter as tk import tkinter as tk
from tkinter import ttk, Menu from tkinter import ttk, Menu
from tkinter.scrolledtext import ScrolledText from tkinter.scrolledtext import ScrolledText
@ -17,10 +26,11 @@ from epson_print_conf import EpsonPrinter
from find_printers import PrinterScanner from find_printers import PrinterScanner
VERSION = "2.1" VERSION = "2.2"
NO_CONF_ERROR = ( NO_CONF_ERROR = (
"[ERROR] Please select a printer model and a valid IP address, or press 'Detect Printers'.\n" "[ERROR] Please select a printer model and a valid IP address,"
" or press 'Detect Printers'.\n"
) )
@ -60,15 +70,34 @@ def get_printer_models(input_string):
class ToolTip: class ToolTip:
def __init__(self, widget, text="widget info", wrap_length=10): def __init__(
self,
widget,
text="widget info",
wrap_length=10,
destroy=True
):
self.widget = widget self.widget = widget
self.text = text self.text = text
self.wrap_length = wrap_length self.wrap_length = wrap_length
self.tooltip_window = None self.tooltip_window = None
# Check and remove existing bindings if they exist
if destroy:
self.remove_existing_binding("<Enter>")
self.remove_existing_binding("<Leave>")
self.remove_existing_binding("<Button-1>")
# Set new bindings
widget.bind("<Enter>", self.enter, "+") # Show the tooltip on hover widget.bind("<Enter>", self.enter, "+") # Show the tooltip on hover
widget.bind("<Leave>", self.leave, "+") # Hide the tooltip on leave widget.bind("<Leave>", self.leave, "+") # Hide the tooltip on leave
widget.bind("<Button-1>", self.leave, "+") # Hide tooltip on mouse click widget.bind("<Button-1>", self.leave, "+") # Hide tooltip on mouse click
def remove_existing_binding(self, event):
# Check if there's already a binding for the event
if self.widget.bind(event):
self.widget.unbind(event) # Remove the existing binding
def enter(self, event=None): def enter(self, event=None):
if self.tooltip_window or not self.text: if self.tooltip_window or not self.text:
return return
@ -128,18 +157,26 @@ class ToolTip:
class BugFixedDateEntry(DateEntry): class BugFixedDateEntry(DateEntry):
""" """
Fixes a bug on the calendar that does not accept mouse selection with Linux Fixes a bug on the calendar that does not accept mouse selection with Linux
Fixes a drop down bug when the DateEntry widget is not focused
""" """
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
def drop_down(self): def drop_down(self):
self.focus_set() # Set focus to the DateEntry widget
super().drop_down() super().drop_down()
if self._top_cal is not None and not self._calendar.winfo_ismapped(): if self._top_cal is not None and not self._calendar.winfo_ismapped():
self._top_cal.lift() self._top_cal.lift()
class EpsonPrinterUI(tk.Tk): class EpsonPrinterUI(tk.Tk):
def __init__(self, conf_dict={}, replace_conf=False): def __init__(
self,
model: str = None,
hostname: str = None,
conf_dict={},
replace_conf=False
):
super().__init__() super().__init__()
self.title("Epson Printer Configuration - v" + VERSION) self.title("Epson Printer Configuration - v" + VERSION)
self.geometry("500x500") self.geometry("500x500")
@ -149,6 +186,9 @@ class EpsonPrinterUI(tk.Tk):
self.ip_list_cycle = None self.ip_list_cycle = None
self.conf_dict = conf_dict self.conf_dict = conf_dict
self.replace_conf = replace_conf self.replace_conf = replace_conf
self.text_dump = ""
self.mode = black.Mode(line_length=200, magic_trailing_comma=False)
self.printer = None
# configure the main window to be resizable # configure the main window to be resizable
self.columnconfigure(0, weight=1) self.columnconfigure(0, weight=1)
@ -172,7 +212,7 @@ class EpsonPrinterUI(tk.Tk):
model_ip_frame.columnconfigure(0, weight=1) # Allow column to expand model_ip_frame.columnconfigure(0, weight=1) # Allow column to expand
model_ip_frame.columnconfigure(1, weight=1) # Allow column to expand model_ip_frame.columnconfigure(1, weight=1) # Allow column to expand
# printer model selection # BOX printer model selection
model_frame = ttk.LabelFrame( model_frame = ttk.LabelFrame(
model_ip_frame, text="Printer Model", padding=PAD model_ip_frame, text="Printer Model", padding=PAD
) )
@ -182,12 +222,15 @@ class EpsonPrinterUI(tk.Tk):
model_frame.columnconfigure(0, weight=0) model_frame.columnconfigure(0, weight=0)
model_frame.columnconfigure(1, weight=1) model_frame.columnconfigure(1, weight=1)
# Model combobox
self.model_var = tk.StringVar() self.model_var = tk.StringVar()
if ( if (
"internal_data" in conf_dict "internal_data" in conf_dict
and "default_model" in conf_dict["internal_data"] and "default_model" in conf_dict["internal_data"]
): ):
self.model_var.set(conf_dict["internal_data"]["default_model"]) self.model_var.set(conf_dict["internal_data"]["default_model"])
if model:
self.model_var.set(model)
ttk.Label(model_frame, text="Model:").grid( ttk.Label(model_frame, text="Model:").grid(
row=0, column=0, sticky=tk.W, padx=PADX row=0, column=0, sticky=tk.W, padx=PADX
) )
@ -203,11 +246,12 @@ class EpsonPrinterUI(tk.Tk):
) )
ToolTip( ToolTip(
self.model_dropdown, self.model_dropdown,
"Select the model of the printer, or press 'Detect Printers'.\nPress F2 to dump the parameters associated to the printer model.", "Select the model of the printer, or press 'Detect Printers'.\n"
"Press F2 to dump the parameters associated to the printer model.",
) )
self.model_dropdown.bind("<F2>", self.printer_config) self.model_dropdown.bind("<F2>", self.printer_config)
# IP address entry # BOX IP address
ip_frame = ttk.LabelFrame( ip_frame = ttk.LabelFrame(
model_ip_frame, text="Printer IP Address", padding=PAD model_ip_frame, text="Printer IP Address", padding=PAD
) )
@ -217,12 +261,15 @@ class EpsonPrinterUI(tk.Tk):
ip_frame.columnconfigure(0, weight=0) ip_frame.columnconfigure(0, weight=0)
ip_frame.columnconfigure(1, weight=1) ip_frame.columnconfigure(1, weight=1)
# IP address entry
self.ip_var = tk.StringVar() self.ip_var = tk.StringVar()
if ( if (
"internal_data" in conf_dict "internal_data" in conf_dict
and "hostname" in conf_dict["internal_data"] and "hostname" in conf_dict["internal_data"]
): ):
self.ip_var.set(conf_dict["internal_data"]["hostname"]) self.ip_var.set(conf_dict["internal_data"]["hostname"])
if hostname:
self.ip_var.set(hostname)
ttk.Label(ip_frame, text="IP Address:").grid( ttk.Label(ip_frame, text="IP Address:").grid(
row=0, column=0, sticky=tk.W, padx=PADX row=0, column=0, sticky=tk.W, padx=PADX
) )
@ -233,7 +280,12 @@ class EpsonPrinterUI(tk.Tk):
self.ip_entry.bind("<F2>", self.next_ip) self.ip_entry.bind("<F2>", self.next_ip)
ToolTip( ToolTip(
self.ip_entry, self.ip_entry,
"Enter the IP address, or press 'Detect Printers' (you can also enter part of the IP address to speed up the detection), or press F2 more times to get the next local IP address, which can then be edited (by removing the last part before pressing 'Detect Printers').", "Enter the IP address, or press 'Detect Printers'"
" (you can also enter part of the IP address"
" to speed up the detection),"
" or press F2 more times to get the next local IP address,"
" which can then be edited"
" (by removing the last part before pressing 'Detect Printers').",
) )
# [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
@ -245,7 +297,7 @@ class EpsonPrinterUI(tk.Tk):
container_frame.columnconfigure(0, weight=1) # Allow column to expand container_frame.columnconfigure(0, weight=1) # Allow column to expand
container_frame.columnconfigure(1, weight=1) # Allow column to expand container_frame.columnconfigure(1, weight=1) # Allow column to expand
# Power-off timer # BOX Power-off timer (minutes)
po_timer_frame = ttk.LabelFrame( po_timer_frame = ttk.LabelFrame(
container_frame, text="Power-off timer (minutes)", padding=PAD container_frame, text="Power-off timer (minutes)", padding=PAD
) )
@ -259,6 +311,19 @@ class EpsonPrinterUI(tk.Tk):
# Configure validation command for numeric entry # Configure validation command for numeric entry
validate_cmd = self.register(self.validate_number_input) validate_cmd = self.register(self.validate_number_input)
# Power-off timer (minutes) - Get Button
button_width = 7
self.get_po_minutes = ttk.Button(
po_timer_frame,
text="Get",
width=button_width,
command=self.get_po_mins,
)
self.get_po_minutes.grid(
row=0, column=0, padx=PADX, pady=PADY, sticky=tk.W
)
# Power-off timer (minutes) - minutes Entry
self.po_timer_var = tk.StringVar() self.po_timer_var = tk.StringVar()
self.po_timer_entry = ttk.Entry( self.po_timer_entry = ttk.Entry(
po_timer_frame, po_timer_frame,
@ -271,26 +336,24 @@ class EpsonPrinterUI(tk.Tk):
self.po_timer_entry.grid( self.po_timer_entry.grid(
row=0, column=1, pady=PADY, padx=PADX, sticky=(tk.W, tk.E) row=0, column=1, pady=PADY, padx=PADX, sticky=(tk.W, tk.E)
) )
ToolTip(self.po_timer_entry, "Enter a number of minutes.") ToolTip(
self.po_timer_entry,
button_width = 7 "Enter a number of minutes.",
get_po_minutes = ttk.Button( destroy=False
po_timer_frame,
text="Get",
width=button_width,
command=self.get_po_mins,
) )
get_po_minutes.grid(row=0, column=0, padx=PADX, pady=PADY, sticky=tk.W)
set_po_minutes = ttk.Button( # Power-off timer (minutes) - Set Button
self.set_po_minutes = ttk.Button(
po_timer_frame, po_timer_frame,
text="Set", text="Set",
width=button_width, width=button_width,
command=self.set_po_mins, command=self.set_po_mins,
) )
set_po_minutes.grid(row=0, column=2, padx=PADX, pady=PADY, sticky=tk.E) self.set_po_minutes.grid(
row=0, column=2, padx=PADX, pady=PADY, sticky=tk.E
)
# TI Received Time # BOX TI Received Time (date)
ti_received_frame = ttk.LabelFrame( ti_received_frame = ttk.LabelFrame(
container_frame, text="TI Received Time (date)", padding=PAD container_frame, text="TI Received Time (date)", padding=PAD
) )
@ -301,7 +364,18 @@ class EpsonPrinterUI(tk.Tk):
ti_received_frame.columnconfigure(1, weight=1) # Calendar column ti_received_frame.columnconfigure(1, weight=1) # Calendar column
ti_received_frame.columnconfigure(2, weight=0) # Button column on the right ti_received_frame.columnconfigure(2, weight=0) # Button column on the right
# TI Received Time Calendar Widget # TI Received Time - Get Button
self.get_ti_received = ttk.Button(
ti_received_frame,
text="Get",
width=button_width,
command=self.get_ti_date,
)
self.get_ti_received.grid(
row=0, column=0, padx=PADX, pady=PADY, sticky=tk.W
)
# TI Received Time - Calendar Widget
self.date_entry = BugFixedDateEntry( self.date_entry = BugFixedDateEntry(
ti_received_frame, date_pattern="yyyy-mm-dd" ti_received_frame, date_pattern="yyyy-mm-dd"
) )
@ -309,24 +383,22 @@ class EpsonPrinterUI(tk.Tk):
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)
) )
self.date_entry.delete(0, "end") # blank the field removing the current date self.date_entry.delete(0, "end") # blank the field removing the current date
ToolTip(self.date_entry, "Enter a valid date with format YYYY-MM-DD.") ToolTip(
self.date_entry,
# TI Received Time Buttons "Enter a valid date with format YYYY-MM-DD.",
get_ti_received = ttk.Button( destroy=False
ti_received_frame,
text="Get",
width=button_width,
command=self.get_ti_date,
) )
get_ti_received.grid(row=0, column=0, padx=PADX, pady=PADY, sticky=tk.W)
set_ti_received = ttk.Button( # TI Received Time - Set Button
self.set_ti_received = ttk.Button(
ti_received_frame, ti_received_frame,
text="Set", text="Set",
width=button_width, width=button_width,
command=self.set_ti_date, command=self.set_ti_date,
) )
set_ti_received.grid(row=0, column=2, padx=PADX, pady=PADY, sticky=tk.E) self.set_ti_received.grid(
row=0, column=2, padx=PADX, pady=PADY, sticky=tk.E
)
# [row 2] Buttons # [row 2] Buttons
row_n += 1 row_n += 1
@ -334,6 +406,7 @@ class EpsonPrinterUI(tk.Tk):
button_frame.grid(row=row_n, column=0, pady=PADY, sticky=(tk.W, tk.E)) button_frame.grid(row=row_n, column=0, pady=PADY, sticky=(tk.W, tk.E))
button_frame.columnconfigure((0, 1, 2), weight=1) button_frame.columnconfigure((0, 1, 2), weight=1)
# Detect Printers
self.detect_button = ttk.Button( self.detect_button = ttk.Button(
button_frame, button_frame,
text="Detect Printers", text="Detect Printers",
@ -343,6 +416,7 @@ class EpsonPrinterUI(tk.Tk):
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)
) )
# 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 Status", command=self.printer_status
) )
@ -350,6 +424,7 @@ class EpsonPrinterUI(tk.Tk):
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)
) )
# 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 Ink Levels",
@ -359,7 +434,7 @@ class EpsonPrinterUI(tk.Tk):
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)
) )
# [row 3] Status display # [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)
status_frame.grid( status_frame.grid(
@ -379,6 +454,7 @@ class EpsonPrinterUI(tk.Tk):
padx=PADY, padx=PADY,
sticky=(tk.W, tk.E, tk.N, tk.S), sticky=(tk.W, tk.E, tk.N, tk.S),
) )
self.status_text.bind("<Tab>", self.focus_next)
self.status_text.bind("<Key>", lambda e: "break") # disable editing text self.status_text.bind("<Key>", lambda e: "break") # disable editing text
self.status_text.bind( self.status_text.bind(
"<Control-c>", "<Control-c>",
@ -430,7 +506,13 @@ class EpsonPrinterUI(tk.Tk):
# Create a context menu # Create a context menu
self.context_menu = Menu(self, tearoff=0) self.context_menu = Menu(self, tearoff=0)
self.context_menu.add_command( self.context_menu.add_command(
label="Copy", command=self.copy_selected_item label="Copy this item", command=self.copy_selected_item
)
self.context_menu.add_command(
label="Copy all items", command=self.copy_all_items
)
self.context_menu.add_command(
label="Print all items", command=self.print_items
) )
# Bind the right-click event to the Treeview # Bind the right-click event to the Treeview
@ -439,6 +521,82 @@ class EpsonPrinterUI(tk.Tk):
# Hide the Treeview initially # Hide the Treeview initially
self.tree_frame.grid_remove() self.tree_frame.grid_remove()
self.model_var.trace('w', self.change_widget_states)
self.ip_var.trace('w', self.change_widget_states)
self.change_widget_states()
def focus_next(self, event):
event.widget.tk_focusNext().focus()
return("break")
def change_widget_states(self, index=None, value=None, op=None):
if self.ip_var.get():
self.status_button.state(["!disabled"])
self.printer = None
else:
self.status_button.state(["disabled"])
if self.ip_var.get() and self.model_var.get():
self.printer = EpsonPrinter(
conf_dict=self.conf_dict,
replace_conf=self.replace_conf,
model=self.model_var.get(),
hostname=self.ip_var.get()
)
if not self.printer:
return
if not self.printer.parm:
self.reset_printer_model()
return
if self.printer.parm.get("stats", {}).get("Power off timer"):
self.po_timer_entry.state(["!disabled"])
self.get_po_minutes.state(["!disabled"])
self.set_po_minutes.state(["!disabled"])
ToolTip(self.get_po_minutes, "")
else:
self.po_timer_entry.state(["disabled"])
self.get_po_minutes.state(["disabled"])
self.set_po_minutes.state(["disabled"])
ToolTip(
self.get_po_minutes,
"Feature not defined in the printer configuration."
)
if self.printer.parm.get("stats", {}).get("First TI received time"):
self.date_entry.state(["!disabled"])
self.get_ti_received.state(["!disabled"])
self.set_ti_received.state(["!disabled"])
ToolTip(self.get_ti_received, "")
else:
self.date_entry.state(["disabled"])
self.get_ti_received.state(["disabled"])
self.set_ti_received.state(["disabled"])
ToolTip(
self.get_ti_received,
"Feature not defined in the printer configuration."
)
if self.printer.reset_waste_ink_levels(dry_run=True):
self.reset_button.state(["!disabled"])
ToolTip(
self.reset_button,
"Ensure you really want this before pressing this key."
)
else:
self.reset_button.state(["disabled"])
ToolTip(
self.reset_button,
"Feature not defined in the printer configuration."
)
else:
self.po_timer_entry.state(["disabled"])
self.get_po_minutes.state(["disabled"])
self.set_po_minutes.state(["disabled"])
self.date_entry.state(["disabled"])
self.get_ti_received.state(["disabled"])
self.set_ti_received.state(["disabled"])
self.reset_button.state(["disabled"])
self.update_idletasks()
def next_ip(self, event): def next_ip(self, event):
ip = self.ip_var.get() ip = self.ip_var.get()
if self.ip_list_cycle == None: if self.ip_list_cycle == None:
@ -468,20 +626,15 @@ class EpsonPrinterUI(tk.Tk):
self.after(100, lambda: method_to_call(cursor=False)) self.after(100, lambda: method_to_call(cursor=False))
return return
self.show_status_text_view() self.show_status_text_view()
model = self.model_var.get()
ip_address = self.ip_var.get() ip_address = self.ip_var.get()
if not model or not self._is_valid_ip(ip_address): if not self._is_valid_ip(ip_address):
self.status_text.insert(tk.END, NO_CONF_ERROR) self.status_text.insert(tk.END, NO_CONF_ERROR)
self.config(cursor="") self.config(cursor="")
self.update() self.update()
return return
printer = EpsonPrinter( if not self.printer:
conf_dict=self.conf_dict, return
replace_conf=self.replace_conf, if not self.printer.parm.get("stats", {}).get("Power off timer"):
model=model,
hostname=ip_address
)
if not printer.parm.get("stats", {}).get("Power off timer"):
self.status_text.insert( self.status_text.insert(
tk.END, tk.END,
f"[ERROR]: Missing 'Power off timer' in configuration\n", f"[ERROR]: Missing 'Power off timer' in configuration\n",
@ -490,7 +643,7 @@ class EpsonPrinterUI(tk.Tk):
self.update_idletasks() self.update_idletasks()
return return
try: try:
po_timer = printer.stats()["stats"]["Power off timer"] po_timer = self.printer.stats()["stats"]["Power off timer"]
self.status_text.insert( self.status_text.insert(
tk.END, f"[INFO] Power off timer: {po_timer} minutes.\n" tk.END, f"[INFO] Power off timer: {po_timer} minutes.\n"
) )
@ -510,20 +663,15 @@ class EpsonPrinterUI(tk.Tk):
self.after(100, lambda: method_to_call(cursor=False)) self.after(100, lambda: method_to_call(cursor=False))
return return
self.show_status_text_view() self.show_status_text_view()
model = self.model_var.get()
ip_address = self.ip_var.get() ip_address = self.ip_var.get()
if not model or not self._is_valid_ip(ip_address): if not self._is_valid_ip(ip_address):
self.status_text.insert(tk.END, NO_CONF_ERROR) self.status_text.insert(tk.END, NO_CONF_ERROR)
self.config(cursor="") self.config(cursor="")
self.update_idletasks() self.update_idletasks()
return return
printer = EpsonPrinter( if not self.printer:
conf_dict=self.conf_dict, return
replace_conf=self.replace_conf, if not self.printer.parm.get("stats", {}).get("Power off timer"):
model=model,
hostname=ip_address
)
if not printer.parm.get("stats", {}).get("Power off timer"):
self.status_text.insert( self.status_text.insert(
tk.END, tk.END,
f"[ERROR]: Missing 'Power off timer' in configuration\n", f"[ERROR]: Missing 'Power off timer' in configuration\n",
@ -547,7 +695,7 @@ class EpsonPrinterUI(tk.Tk):
) )
if response: if response:
try: try:
printer.write_poweroff_timer(int(po_timer)) self.printer.write_poweroff_timer(int(po_timer))
except Exception as e: except Exception as e:
self.status_text.insert(tk.END, f"[ERROR] {e}\n") self.status_text.insert(tk.END, f"[ERROR] {e}\n")
else: else:
@ -566,20 +714,15 @@ class EpsonPrinterUI(tk.Tk):
self.after(100, lambda: method_to_call(cursor=False)) self.after(100, lambda: method_to_call(cursor=False))
return return
self.show_status_text_view() self.show_status_text_view()
model = self.model_var.get()
ip_address = self.ip_var.get() ip_address = self.ip_var.get()
if not model or not self._is_valid_ip(ip_address): if not self._is_valid_ip(ip_address):
self.status_text.insert(tk.END, NO_CONF_ERROR) self.status_text.insert(tk.END, NO_CONF_ERROR)
self.config(cursor="") self.config(cursor="")
self.update_idletasks() self.update_idletasks()
return return
printer = EpsonPrinter( if not self.printer:
conf_dict=self.conf_dict, return
replace_conf=self.replace_conf, if not self.printer.parm.get("stats", {}).get("First TI received time"):
model=model,
hostname=ip_address
)
if not printer.parm.get("stats", {}).get("First TI received time"):
self.status_text.insert( self.status_text.insert(
tk.END, tk.END,
f"[ERROR]: Missing 'First TI received time' in configuration\n", f"[ERROR]: Missing 'First TI received time' in configuration\n",
@ -589,7 +732,8 @@ class EpsonPrinterUI(tk.Tk):
return return
try: try:
date_string = datetime.strptime( date_string = datetime.strptime(
printer.stats()["stats"]["First TI received time"], "%d %b %Y" self.printer.stats(
)["stats"]["First TI received time"], "%d %b %Y"
).strftime("%Y-%m-%d") ).strftime("%Y-%m-%d")
self.status_text.insert( self.status_text.insert(
tk.END, tk.END,
@ -611,20 +755,15 @@ class EpsonPrinterUI(tk.Tk):
self.after(100, lambda: method_to_call(cursor=False)) self.after(100, lambda: method_to_call(cursor=False))
return return
self.show_status_text_view() self.show_status_text_view()
model = self.model_var.get()
ip_address = self.ip_var.get() ip_address = self.ip_var.get()
if not model or not self._is_valid_ip(ip_address): if not self._is_valid_ip(ip_address):
self.status_text.insert(tk.END, NO_CONF_ERROR) self.status_text.insert(tk.END, NO_CONF_ERROR)
self.config(cursor="") self.config(cursor="")
self.update_idletasks() self.update_idletasks()
return return
printer = EpsonPrinter( if not self.printer:
conf_dict=self.conf_dict, return
replace_conf=self.replace_conf, if not self.printer.parm.get("stats", {}).get("First TI received time"):
model=model,
hostname=ip_address
)
if not printer.parm.get("stats", {}).get("First TI received time"):
self.status_text.insert( self.status_text.insert(
tk.END, tk.END,
f"[ERROR]: Missing 'First TI received time' in configuration\n", f"[ERROR]: Missing 'First TI received time' in configuration\n",
@ -635,14 +774,15 @@ class EpsonPrinterUI(tk.Tk):
date_string = self.date_entry.get_date() date_string = self.date_entry.get_date()
self.status_text.insert( self.status_text.insert(
tk.END, tk.END,
f"[INFO] Set 'First TI received time' (YYYY-MM-DD) to: {date_string.strftime('%Y-%m-%d')}.\n", f"[INFO] Set 'First TI received time' (YYYY-MM-DD) to: "
f"{date_string.strftime('%Y-%m-%d')}.\n",
) )
response = messagebox.askyesno( response = messagebox.askyesno(
"Confirm Action", "Are you sure you want to proceed?" "Confirm Action", "Are you sure you want to proceed?"
) )
if response: if response:
try: try:
printer.write_first_ti_received_time( self.printer.write_first_ti_received_time(
date_string.year, date_string.month, date_string.day date_string.year, date_string.month, date_string.day
) )
except Exception as e: except Exception as e:
@ -686,7 +826,8 @@ class EpsonPrinterUI(tk.Tk):
if not self._is_valid_ip(ip_address): if not self._is_valid_ip(ip_address):
self.status_text.insert( self.status_text.insert(
tk.END, tk.END,
"[ERROR] Please enter a valid IP address, or press 'Detect Printers'.\n" "[ERROR] Please enter a valid IP address, or "
"press 'Detect Printers'.\n"
) )
self.config(cursor="") self.config(cursor="")
self.update_idletasks() self.update_idletasks()
@ -697,8 +838,12 @@ class EpsonPrinterUI(tk.Tk):
model=model, model=model,
hostname=ip_address hostname=ip_address
) )
if not printer:
return
try: try:
self.text_dump = black.format_str(
f'"{printer.model}": ' + repr(printer.stats()), mode=self.mode
)
self.show_treeview() self.show_treeview()
# Configure tags # Configure tags
@ -720,14 +865,43 @@ class EpsonPrinterUI(tk.Tk):
self.config(cursor="") self.config(cursor="")
self.update_idletasks() self.update_idletasks()
def reset_printer_model(self):
self.show_status_text_view()
if self.model_var.get():
self.status_text.insert(
tk.END,
'[ERROR]: Unknown printer model '
f'"{self.model_var.get()}"\n',
)
else:
self.status_text.insert(
tk.END,
'[ERROR]: Select a valid printer model.\n'
)
self.config(cursor="")
self.update()
self.model_var.set("")
def printer_config(self, cursor=True): def printer_config(self, cursor=True):
"""
Pressing F2 dumps the printer configuration
"""
model = self.model_var.get() model = self.model_var.get()
printer = EpsonPrinter( printer = EpsonPrinter(
conf_dict=self.conf_dict, conf_dict=self.conf_dict,
replace_conf=self.replace_conf, replace_conf=self.replace_conf,
model=model model=model
) )
if not printer:
return
if not printer.parm:
self.reset_printer_model()
return
try: try:
self.text_dump = black.format_str(
f'"{printer.model}": ' + repr(printer.parm),
mode=self.mode
)
self.show_treeview() self.show_treeview()
# Configure tags # Configure tags
@ -757,25 +931,20 @@ class EpsonPrinterUI(tk.Tk):
self.after(100, lambda: method_to_call(cursor=False)) self.after(100, lambda: method_to_call(cursor=False))
return return
self.show_status_text_view() self.show_status_text_view()
model = self.model_var.get()
ip_address = self.ip_var.get() ip_address = self.ip_var.get()
if not model or not self._is_valid_ip(ip_address): if not self._is_valid_ip(ip_address):
self.status_text.insert(tk.END, NO_CONF_ERROR) self.status_text.insert(tk.END, NO_CONF_ERROR)
self.config(cursor="") self.config(cursor="")
self.update_idletasks() self.update_idletasks()
return return
printer = EpsonPrinter(
conf_dict=self.conf_dict,
replace_conf=self.replace_conf,
model=model,
hostname=ip_address
)
response = messagebox.askyesno( response = messagebox.askyesno(
"Confirm Action", "Are you sure you want to proceed?" "Confirm Action", "Are you sure you want to proceed?"
) )
if not self.printer:
return
if response: if response:
try: try:
printer.reset_waste_ink_levels() self.printer.reset_waste_ink_levels()
self.status_text.insert( self.status_text.insert(
tk.END, tk.END,
"[INFO] Waste ink levels have been reset." "[INFO] Waste ink levels have been reset."
@ -817,7 +986,9 @@ class EpsonPrinterUI(tk.Tk):
if len(printers) == 1: if len(printers) == 1:
self.status_text.insert( self.status_text.insert(
tk.END, tk.END,
f"[INFO] Found printer '{printers[0]['name']}' at {printers[0]['ip']} (hostname: {printers[0]['hostname']})\n", f"[INFO] Found printer '{printers[0]['name']}' "
f"at {printers[0]['ip']} "
f"(hostname: {printers[0]['hostname']})\n",
) )
self.ip_var.set(printers[0]["ip"]) self.ip_var.set(printers[0]["ip"])
for model in get_printer_models(printers[0]["name"]): for model in get_printer_models(printers[0]["name"]):
@ -834,7 +1005,8 @@ class EpsonPrinterUI(tk.Tk):
for printer in printers: for printer in printers:
self.status_text.insert( self.status_text.insert(
tk.END, tk.END,
f"[INFO] {printer['name']} found at {printer['ip']} (hostname: {printer['hostname']})\n", f"[INFO] {printer['name']} found at {printer['ip']}"
f" (hostname: {printer['hostname']})\n",
) )
else: else:
self.status_text.insert(tk.END, "[WARN] No printers found.\n") self.status_text.insert(tk.END, "[WARN] No printers found.\n")
@ -943,6 +1115,27 @@ class EpsonPrinterUI(tk.Tk):
self.clipboard_clear() self.clipboard_clear()
self.clipboard_append(item_text) self.clipboard_append(item_text)
def copy_all_items(self):
"""Copy all items to the clipboard."""
self.clipboard_clear()
self.clipboard_append(self.text_dump)
def print_items(self):
"""Print items."""
self.clipboard_append(self.text_dump)
message = (
"\x1B\x40" # Initialize printer
"Printer configuration\n"
+ self.text_dump
)
ip_address = self.ip_var.get()
if not self._is_valid_ip(ip_address):
return
# Send the message to the printer
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
sock.connect((ip_address, 9100))
sock.sendall(message.encode('utf-8') + b"\f")
def main(): def main():
import argparse import argparse
@ -951,6 +1144,22 @@ def main():
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
epilog='epson_print_conf GUI' epilog='epson_print_conf GUI'
) )
parser.add_argument(
'-m',
'--model',
dest='model',
action="store",
help='Printer model. Example: -m XP-205',
default=None
)
parser.add_argument(
'-a',
'--address',
dest='hostname',
action="store",
help='Printer host name or IP address. (Example: -a 192.168.1.87)',
default=None
)
parser.add_argument( parser.add_argument(
'-P', '-P',
"--pickle", "--pickle",
@ -974,12 +1183,17 @@ def main():
if args.pickle: if args.pickle:
conf_dict = pickle.load(args.pickle[0]) conf_dict = pickle.load(args.pickle[0])
return EpsonPrinterUI(conf_dict=conf_dict, replace_conf=args.override) return EpsonPrinterUI(
model=args.model,
hostname=args.hostname,
conf_dict=conf_dict,
replace_conf=args.override
)
if __name__ == "__main__": if __name__ == "__main__":
try: try:
main().mainloop() main().mainloop()
except: except KeyboardInterrupt:
print("\nInterrupted.") print("\nInterrupted.")
sys.exit(0) sys.exit(0)