mirror of
https://github.com/Ircama/epson_print_conf.git
synced 2025-05-17 08:34:49 -04:00
Refinements
This commit is contained in:
parent
6c5a631d97
commit
ee875e3d41
4 changed files with 553 additions and 190 deletions
|
@ -7,15 +7,15 @@ Epson Printer Configuration tool via SNMP (TCP/IP)
|
||||||
- Access the Epson printer via SNMP (TCP/IP; printer connected over Wi-Fi)
|
- Access the Epson printer via SNMP (TCP/IP; printer connected over Wi-Fi)
|
||||||
- Print the advanced status of the printer, with the possibility to restrict the query to specific information
|
- Print the advanced status of the printer, with the possibility to restrict the query to specific information
|
||||||
- Other inspection features:
|
- Other inspection features:
|
||||||
- Read and write EEPROM addresses
|
|
||||||
- Dump a set of EEPROM addresses
|
|
||||||
- Reset ink waste
|
- Reset ink waste
|
||||||
- Change power off timer
|
- Change power off timer
|
||||||
- Other admin stuffs and debug options
|
- Other admin stuffs and debug options
|
||||||
|
- Read and write EEPROM addresses
|
||||||
|
- Dump a set of EEPROM addresses
|
||||||
- both a GUI and a command line tool
|
- both a GUI and a command line tool
|
||||||
- Python API interface
|
- Python API interface
|
||||||
|
|
||||||
The GUI has an autodiscovery function which finds the printer IP addresses and their model names. By manually filling the printer model and the IP addresses in the related input boxes, the GUI can be used to get the printer status and to reset the ink waste counter.
|
The GUI has an autodiscovery function which finds the printer IP addresses and their model names. The GUI can be used to get the printer status, to set the "Power-off timer", to set the "TI Received Time" and to reset the ink waste counter.
|
||||||
|
|
||||||
The software provides a configurable printer dictionary, which can be easily extended. There is also a tool to import an extensive Epson printer configuration DB.
|
The software provides a configurable printer dictionary, which can be easily extended. There is also a tool to import an extensive Epson printer configuration DB.
|
||||||
|
|
||||||
|
|
|
@ -676,7 +676,7 @@ class EpsonPrinter:
|
||||||
for printer_name, printer_data in self.PRINTER_CONFIG.copy().items():
|
for printer_name, printer_data in self.PRINTER_CONFIG.copy().items():
|
||||||
if "same-as" in printer_data:
|
if "same-as" in printer_data:
|
||||||
sameas = printer_data["same-as"]
|
sameas = printer_data["same-as"]
|
||||||
del printer_data["same-as"]
|
#del printer_data["same-as"]
|
||||||
if sameas in self.PRINTER_CONFIG:
|
if sameas in self.PRINTER_CONFIG:
|
||||||
self.PRINTER_CONFIG[printer_name] = {
|
self.PRINTER_CONFIG[printer_name] = {
|
||||||
**self.PRINTER_CONFIG[sameas],
|
**self.PRINTER_CONFIG[sameas],
|
||||||
|
@ -727,8 +727,10 @@ class EpsonPrinter:
|
||||||
def caesar(self, key, hex=False):
|
def caesar(self, key, hex=False):
|
||||||
"""Convert the string write key to a sequence of numbers"""
|
"""Convert the string write key to a sequence of numbers"""
|
||||||
if hex:
|
if hex:
|
||||||
return " ".join('{0:02x}'.format(b + 1) for b in key)
|
return " ".join(
|
||||||
return ".".join(str(b + 1) for b in key)
|
'00' if b == 0 else '{0:02x}'.format(b + 1) for b in key
|
||||||
|
)
|
||||||
|
return ".".join("0" if b == 0 else str(b + 1) for b in key)
|
||||||
|
|
||||||
|
|
||||||
def reverse_caesar(self, eight_bytes):
|
def reverse_caesar(self, eight_bytes):
|
||||||
|
|
|
@ -5,6 +5,8 @@ import xml.etree.ElementTree as ET
|
||||||
import itertools
|
import itertools
|
||||||
import textwrap
|
import textwrap
|
||||||
|
|
||||||
|
from ui import get_printer_models
|
||||||
|
|
||||||
def to_ranges(iterable):
|
def to_ranges(iterable):
|
||||||
iterable = sorted(set(iterable))
|
iterable = sorted(set(iterable))
|
||||||
for key, group in itertools.groupby(enumerate(iterable),
|
for key, group in itertools.groupby(enumerate(iterable),
|
||||||
|
@ -45,7 +47,8 @@ def traverse_data(element, depth=0):
|
||||||
|
|
||||||
def generate_config(config, traverse, add_fatal_errors, full, printer_model):
|
def generate_config(config, traverse, add_fatal_errors, full, printer_model):
|
||||||
waste_string = [
|
waste_string = [
|
||||||
"main_waste", "borderless_waste", "third_waste", "fourth_waste"
|
"main_waste", "borderless_waste", "third_waste", "fourth_waste",
|
||||||
|
"fifth_waste", "sixth_waste"
|
||||||
]
|
]
|
||||||
irc_pattern = [
|
irc_pattern = [
|
||||||
r'Ink replacement counter %-% (\w+) % \((\w+)\)'
|
r'Ink replacement counter %-% (\w+) % \((\w+)\)'
|
||||||
|
@ -55,7 +58,7 @@ def generate_config(config, traverse, add_fatal_errors, full, printer_model):
|
||||||
printer_config = {}
|
printer_config = {}
|
||||||
for printer in root.iterfind(".//printer"):
|
for printer in root.iterfind(".//printer"):
|
||||||
title = printer.attrib.get("title", "")
|
title = printer.attrib.get("title", "")
|
||||||
if printer_model not in title:
|
if printer_model and printer_model not in title:
|
||||||
continue
|
continue
|
||||||
specs = printer.attrib["specs"].split(",")
|
specs = printer.attrib["specs"].split(",")
|
||||||
logging.info(
|
logging.info(
|
||||||
|
@ -201,7 +204,7 @@ def generate_config(config, traverse, add_fatal_errors, full, printer_model):
|
||||||
chars["write_key"] = (
|
chars["write_key"] = (
|
||||||
"".join(
|
"".join(
|
||||||
[
|
[
|
||||||
chr(b - 1)
|
chr(0 if b == 0 else b - 1)
|
||||||
for b in text_to_bytes(s.text)
|
for b in text_to_bytes(s.text)
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
@ -216,6 +219,78 @@ def generate_config(config, traverse, add_fatal_errors, full, printer_model):
|
||||||
printer_config[printer_short_name] = chars
|
printer_config[printer_short_name] = chars
|
||||||
return printer_config
|
return printer_config
|
||||||
|
|
||||||
|
def normalize_config(config):
|
||||||
|
# Remove printers without write_key or without read_key
|
||||||
|
for base_key, base_items in config.copy().items():
|
||||||
|
if 'write_key' not in base_items:
|
||||||
|
del config[base_key]
|
||||||
|
continue
|
||||||
|
if 'read_key' not in base_items:
|
||||||
|
del config[base_key]
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Replace original names with converted names and add printers for all optional names
|
||||||
|
for key, items in config.copy().items():
|
||||||
|
printer_list = get_printer_models(key)
|
||||||
|
del config[key]
|
||||||
|
for i in printer_list:
|
||||||
|
if i in config and config[i] != items:
|
||||||
|
print("ERROR key", key)
|
||||||
|
quit()
|
||||||
|
config[i] = items
|
||||||
|
|
||||||
|
# Add aliases for same printer with different names and remove aliased printers
|
||||||
|
for base_key, base_items in config.copy().items():
|
||||||
|
found = False
|
||||||
|
for key, items in config.copy().items():
|
||||||
|
if not found:
|
||||||
|
if base_key == key and base_key in config:
|
||||||
|
found = True
|
||||||
|
continue
|
||||||
|
if base_key != key and items == base_items: # different name, same printer
|
||||||
|
if "alias" not in config[base_key]:
|
||||||
|
config[base_key]["alias"] = []
|
||||||
|
for i in get_printer_models(key):
|
||||||
|
if i not in config[base_key]["alias"]:
|
||||||
|
config[base_key]["alias"].append(i)
|
||||||
|
del config[key]
|
||||||
|
|
||||||
|
# Add "same-as" for almost same printer (IGNORED_KEYS) with different names
|
||||||
|
IGNORED_KEYS = ['write_key', 'read_key', 'alias', 'main_waste', 'borderless_waste']
|
||||||
|
for base_key, base_items in config.copy().items():
|
||||||
|
found = False
|
||||||
|
for key, items in config.copy().items():
|
||||||
|
if not found:
|
||||||
|
if base_key == key and base_key in config:
|
||||||
|
found = True
|
||||||
|
continue
|
||||||
|
if base_key != key:
|
||||||
|
if equal_dicts(base_items, items, IGNORED_KEYS): # everything but the IGNORED_KEYS is the same
|
||||||
|
# Get the IGNORED_KEYS from the printer
|
||||||
|
write_key = base_items['write_key']
|
||||||
|
read_key = base_items['read_key']
|
||||||
|
alias = base_items['alias'] if 'alias' in base_items else []
|
||||||
|
main_waste = base_items['main_waste'] if 'main_waste' in base_items else []
|
||||||
|
borderless_waste = base_items['borderless_waste'] if 'borderless_waste' in base_items else []
|
||||||
|
# Rebuild the printer with only the IGNORED_KEYS, then add the 'same-as'
|
||||||
|
del config[base_key]
|
||||||
|
config[base_key] = {}
|
||||||
|
config[base_key]['write_key'] = write_key
|
||||||
|
config[base_key]['read_key'] = read_key
|
||||||
|
if alias:
|
||||||
|
config[base_key]['alias'] = alias
|
||||||
|
if main_waste:
|
||||||
|
config[base_key]['main_waste'] = main_waste
|
||||||
|
if borderless_waste:
|
||||||
|
config[base_key]['borderless_waste'] = borderless_waste
|
||||||
|
config[base_key]['same-as'] = key
|
||||||
|
|
||||||
|
return config
|
||||||
|
|
||||||
|
def equal_dicts(a, b, ignore_keys):
|
||||||
|
ka = set(a).difference(ignore_keys)
|
||||||
|
kb = set(b).difference(ignore_keys)
|
||||||
|
return ka == kb and all(a[k] == b[k] for k in ka)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
import argparse
|
import argparse
|
||||||
|
@ -228,9 +303,9 @@ if __name__ == "__main__":
|
||||||
'-m',
|
'-m',
|
||||||
'--model',
|
'--model',
|
||||||
dest='printer_model',
|
dest='printer_model',
|
||||||
|
default=False,
|
||||||
action="store",
|
action="store",
|
||||||
help='Printer model. Example: -m XP-205',
|
help='Printer model. Example: -m XP-205')
|
||||||
required=True)
|
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-l',
|
'-l',
|
||||||
|
@ -315,14 +390,15 @@ if __name__ == "__main__":
|
||||||
full=args.full,
|
full=args.full,
|
||||||
printer_model=args.printer_model
|
printer_model=args.printer_model
|
||||||
)
|
)
|
||||||
|
normalized_config = normalize_config(printer_config)
|
||||||
try:
|
try:
|
||||||
import black
|
import black
|
||||||
printer_config = "PRINTER_CONFIG = " + repr(printer_config)
|
config_str = "PRINTER_CONFIG = " + repr(normalized_config)
|
||||||
mode = black.Mode(line_length=args.line_length, magic_trailing_comma=False)
|
mode = black.Mode(line_length=args.line_length, magic_trailing_comma=False)
|
||||||
dict_str = black.format_str(printer_config, mode=mode)
|
dict_str = black.format_str(config_str, mode=mode)
|
||||||
except Exception:
|
except Exception:
|
||||||
import pprint
|
import pprint
|
||||||
dict_str = pprint.pformat(printer_config)
|
dict_str = pprint.pformat(config_str)
|
||||||
if args.indent:
|
if args.indent:
|
||||||
dict_str = textwrap.indent(dict_str, ' ')
|
dict_str = textwrap.indent(dict_str, ' ')
|
||||||
print(dict_str)
|
print(dict_str)
|
||||||
|
|
601
ui.py
601
ui.py
|
@ -1,6 +1,7 @@
|
||||||
import re
|
import re
|
||||||
import threading
|
import threading
|
||||||
import ipaddress
|
import ipaddress
|
||||||
|
import inspect
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
|
@ -15,16 +16,57 @@ from epson_print_conf import EpsonPrinter
|
||||||
from find_printers import PrinterScanner
|
from find_printers import PrinterScanner
|
||||||
|
|
||||||
|
|
||||||
VERSION = "2.0"
|
VERSION = "2.1"
|
||||||
|
|
||||||
|
NO_CONF_ERROR = (
|
||||||
|
"[ERROR] Please select a printer model and a valid IP address, or press 'Detect Printers'.\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_printer_models(input_string):
|
||||||
|
# Tokenize the string
|
||||||
|
tokens = re.split(" |/", input_string)
|
||||||
|
if not len(tokens):
|
||||||
|
return []
|
||||||
|
|
||||||
|
# Define the words to remove (uppercase, then case insensitive)
|
||||||
|
remove_tokens = {"EPSON", "SERIES"}
|
||||||
|
|
||||||
|
# Process tokens
|
||||||
|
processed_tokens = []
|
||||||
|
non_numeric_part = ""
|
||||||
|
pre_model = ""
|
||||||
|
for token in tokens:
|
||||||
|
upper_token = token.upper()
|
||||||
|
|
||||||
|
# Remove tokens that match remove_tokens
|
||||||
|
if any(word == upper_token for word in remove_tokens):
|
||||||
|
continue
|
||||||
|
if not any(char.isdigit() for char in token): # no alphanum inside
|
||||||
|
pre_model = pre_model + token + " "
|
||||||
|
continue
|
||||||
|
# Identify the non-numeric part of the first token
|
||||||
|
if not token.isnumeric() and not non_numeric_part:
|
||||||
|
non_numeric_part = "".join(c for c in token if not c.isdigit())
|
||||||
|
# if token is numeric, prepend the non-numeric part
|
||||||
|
if token.isnumeric():
|
||||||
|
processed_tokens.append(f"{pre_model}{non_numeric_part}{token}")
|
||||||
|
else:
|
||||||
|
processed_tokens.append(f"{pre_model}{token}")
|
||||||
|
if not processed_tokens and pre_model:
|
||||||
|
processed_tokens.append(pre_model.strip())
|
||||||
|
return processed_tokens
|
||||||
|
|
||||||
|
|
||||||
class ToolTip:
|
class ToolTip:
|
||||||
def __init__(self, widget, text='widget info', wrap_length=10):
|
def __init__(self, widget, text="widget info", wrap_length=10):
|
||||||
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
|
||||||
widget.bind("<Enter>", self.enter)
|
widget.bind("<Enter>", self.enter, "+") # Show the tooltip on hover
|
||||||
widget.bind("<Leave>", self.leave)
|
widget.bind("<Leave>", self.leave, "+") # Hide the tooltip on leave
|
||||||
|
widget.bind("<Button-1>", self.leave, "+") # Hide tooltip on mouse click
|
||||||
|
|
||||||
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:
|
||||||
|
@ -41,8 +83,14 @@ class ToolTip:
|
||||||
|
|
||||||
tw.geometry(f"+{x}+{y + height + 2}") # Default position below the widget
|
tw.geometry(f"+{x}+{y + height + 2}") # Default position below the widget
|
||||||
|
|
||||||
label = tk.Label(tw, text=self.wrap_text(self.text), justify='left',
|
label = tk.Label(
|
||||||
background='yellow', relief='solid', borderwidth=1)
|
tw,
|
||||||
|
text=self.wrap_text(self.text),
|
||||||
|
justify="left",
|
||||||
|
background="LightYellow",
|
||||||
|
relief="solid",
|
||||||
|
borderwidth=1,
|
||||||
|
)
|
||||||
label.pack(ipadx=1)
|
label.pack(ipadx=1)
|
||||||
|
|
||||||
# Check if the tooltip goes off the screen
|
# Check if the tooltip goes off the screen
|
||||||
|
@ -52,16 +100,14 @@ class ToolTip:
|
||||||
|
|
||||||
if x + tw_width > screen_width: # If tooltip goes beyond screen width
|
if x + tw_width > screen_width: # If tooltip goes beyond screen width
|
||||||
x = screen_width - tw_width - 5
|
x = screen_width - tw_width - 5
|
||||||
if y + height + tw_height > screen_height: # If tooltip goes below screen height
|
if (y + height + tw_height > screen_height): # If tooltip goes below screen height
|
||||||
y = y - tw_height - height - 2 # Position above the widget
|
y = y - tw_height - height - 2 # Position above the widget
|
||||||
|
|
||||||
tw.geometry(f"+{x}+{y}")
|
tw.geometry(f"+{x}+{y}")
|
||||||
|
|
||||||
def leave(self, event=None):
|
def leave(self, event=None):
|
||||||
tw = self.tooltip_window
|
if self.tooltip_window:
|
||||||
self.tooltip_window = None
|
self.tooltip_window.destroy()
|
||||||
if tw:
|
self.tooltip_window = None
|
||||||
tw.destroy()
|
|
||||||
|
|
||||||
def wrap_text(self, text):
|
def wrap_text(self, text):
|
||||||
words = text.split()
|
words = text.split()
|
||||||
|
@ -71,11 +117,11 @@ class ToolTip:
|
||||||
if len(current_line) + len(word.split()) <= self.wrap_length:
|
if len(current_line) + len(word.split()) <= self.wrap_length:
|
||||||
current_line.append(word)
|
current_line.append(word)
|
||||||
else:
|
else:
|
||||||
lines.append(' '.join(current_line))
|
lines.append(" ".join(current_line))
|
||||||
current_line = [word]
|
current_line = [word]
|
||||||
if current_line:
|
if current_line:
|
||||||
lines.append(' '.join(current_line))
|
lines.append(" ".join(current_line))
|
||||||
return '\n'.join(lines)
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
|
||||||
class EpsonPrinterUI(tk.Tk):
|
class EpsonPrinterUI(tk.Tk):
|
||||||
|
@ -84,7 +130,7 @@ class EpsonPrinterUI(tk.Tk):
|
||||||
self.title("Epson Printer Configuration - v" + VERSION)
|
self.title("Epson Printer Configuration - v" + VERSION)
|
||||||
self.geometry("450x500")
|
self.geometry("450x500")
|
||||||
self.minsize(450, 500)
|
self.minsize(450, 500)
|
||||||
self.printer_scanner=PrinterScanner()
|
self.printer_scanner = PrinterScanner()
|
||||||
self.ip_list = []
|
self.ip_list = []
|
||||||
self.ip_list_cycle = None
|
self.ip_list_cycle = None
|
||||||
|
|
||||||
|
@ -93,8 +139,8 @@ class EpsonPrinterUI(tk.Tk):
|
||||||
self.rowconfigure(0, weight=1)
|
self.rowconfigure(0, weight=1)
|
||||||
|
|
||||||
FRAME_PAD = 10
|
FRAME_PAD = 10
|
||||||
PAD = 5
|
PAD = (3, 0)
|
||||||
PADX = 5
|
PADX = 4
|
||||||
PADY = 5
|
PADY = 5
|
||||||
|
|
||||||
# main Frame
|
# main Frame
|
||||||
|
@ -111,41 +157,71 @@ class EpsonPrinterUI(tk.Tk):
|
||||||
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
|
# printer model selection
|
||||||
model_frame = ttk.LabelFrame(model_ip_frame, text="Printer Model", padding=PAD)
|
model_frame = ttk.LabelFrame(
|
||||||
model_frame.grid(row=0, column=0, pady=PADY, padx=(0, PADX), sticky=(tk.W, tk.E))
|
model_ip_frame, text="Printer Model", padding=PAD
|
||||||
|
)
|
||||||
|
model_frame.grid(
|
||||||
|
row=0, column=0, pady=PADY, padx=(0, PADX), sticky=(tk.W, tk.E)
|
||||||
|
)
|
||||||
model_frame.columnconfigure(0, weight=0)
|
model_frame.columnconfigure(0, weight=0)
|
||||||
model_frame.columnconfigure(1, weight=1)
|
model_frame.columnconfigure(1, weight=1)
|
||||||
|
|
||||||
self.model_var = tk.StringVar()
|
self.model_var = tk.StringVar()
|
||||||
ttk.Label(model_frame, text="Model:").grid(row=0, column=0, sticky=tk.W, padx=PADX)
|
ttk.Label(model_frame, text="Model:").grid(
|
||||||
self.model_dropdown = ttk.Combobox(model_frame, textvariable=self.model_var)
|
row=0, column=0, sticky=tk.W, padx=PADX
|
||||||
self.model_dropdown['values'] = sorted(EpsonPrinter().valid_printers)
|
)
|
||||||
self.model_dropdown.grid(row=0, column=1, pady=PADY, padx=PADX, sticky=(tk.W, tk.E))
|
self.model_dropdown = ttk.Combobox(
|
||||||
ToolTip(self.model_dropdown, "Select the model of the printer, or press 'Detect printers'.")
|
model_frame, textvariable=self.model_var, state="readonly"
|
||||||
|
)
|
||||||
|
self.model_dropdown["values"] = sorted(EpsonPrinter().valid_printers)
|
||||||
|
self.model_dropdown.grid(
|
||||||
|
row=0, column=1, pady=PADY, padx=PADX, sticky=(tk.W, tk.E)
|
||||||
|
)
|
||||||
|
ToolTip(
|
||||||
|
self.model_dropdown,
|
||||||
|
"Select the model of the printer, or press 'Detect Printers'.",
|
||||||
|
)
|
||||||
|
|
||||||
# IP address entry
|
# IP address entry
|
||||||
ip_frame = ttk.LabelFrame(model_ip_frame, text="Printer IP Address", padding=PAD)
|
ip_frame = ttk.LabelFrame(
|
||||||
ip_frame.grid(row=0, column=1, pady=PADY, padx=(PADX, 0), sticky=(tk.W, tk.E))
|
model_ip_frame, text="Printer IP Address", padding=PAD
|
||||||
|
)
|
||||||
|
ip_frame.grid(
|
||||||
|
row=0, column=1, pady=PADY, padx=(PADX, 0), sticky=(tk.W, tk.E)
|
||||||
|
)
|
||||||
ip_frame.columnconfigure(0, weight=0)
|
ip_frame.columnconfigure(0, weight=0)
|
||||||
ip_frame.columnconfigure(1, weight=1)
|
ip_frame.columnconfigure(1, weight=1)
|
||||||
|
|
||||||
self.ip_var = tk.StringVar()
|
self.ip_var = tk.StringVar()
|
||||||
ttk.Label(ip_frame, text="IP Address:").grid(row=0, column=0, sticky=tk.W, padx=PADX)
|
ttk.Label(ip_frame, text="IP Address:").grid(
|
||||||
|
row=0, column=0, sticky=tk.W, padx=PADX
|
||||||
|
)
|
||||||
self.ip_entry = ttk.Entry(ip_frame, textvariable=self.ip_var)
|
self.ip_entry = ttk.Entry(ip_frame, textvariable=self.ip_var)
|
||||||
self.ip_entry.grid(row=0, column=1, pady=PADY, padx=PADX, sticky=(tk.W, tk.E))
|
self.ip_entry.grid(
|
||||||
|
row=0, column=1, pady=PADY, padx=PADX, sticky=(tk.W, tk.E)
|
||||||
|
)
|
||||||
self.ip_entry.bind("<F2>", self.next_ip)
|
self.ip_entry.bind("<F2>", self.next_ip)
|
||||||
ToolTip(self.ip_entry, "Enter the IP address, or press 'Detect printers' (enter part of it to speed up the detection), or press F2 to get the next local IP address, which can then be edited.")
|
ToolTip(
|
||||||
|
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').",
|
||||||
|
)
|
||||||
|
|
||||||
# [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)
|
||||||
container_frame.grid(row=row_n, column=0, pady=PADY, sticky=(tk.W, tk.E))
|
container_frame.grid(
|
||||||
|
row=row_n, column=0, pady=PADY, sticky=(tk.W, tk.E)
|
||||||
|
)
|
||||||
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
|
# Power-off timer
|
||||||
po_timer_frame = ttk.LabelFrame(container_frame, text="Power-off timer (minutes)", padding=PAD)
|
po_timer_frame = ttk.LabelFrame(
|
||||||
po_timer_frame.grid(row=0, column=0, pady=PADY, padx=(0, PADX), sticky=(tk.W, tk.E))
|
container_frame, text="Power-off timer (minutes)", padding=PAD
|
||||||
|
)
|
||||||
|
po_timer_frame.grid(
|
||||||
|
row=0, column=0, pady=PADY, padx=(0, PADX), sticky=(tk.W, tk.E)
|
||||||
|
)
|
||||||
po_timer_frame.columnconfigure(0, weight=0) # Button column on the left
|
po_timer_frame.columnconfigure(0, weight=0) # Button column on the left
|
||||||
po_timer_frame.columnconfigure(1, weight=1) # Entry column
|
po_timer_frame.columnconfigure(1, weight=1) # Entry column
|
||||||
po_timer_frame.columnconfigure(2, weight=0) # Button column on the right
|
po_timer_frame.columnconfigure(2, weight=0) # Button column on the right
|
||||||
|
@ -154,35 +230,72 @@ class EpsonPrinterUI(tk.Tk):
|
||||||
validate_cmd = self.register(self.validate_number_input)
|
validate_cmd = self.register(self.validate_number_input)
|
||||||
|
|
||||||
self.po_timer_var = tk.StringVar()
|
self.po_timer_var = tk.StringVar()
|
||||||
self.po_timer_entry = ttk.Entry(po_timer_frame, textvariable=self.po_timer_var, validate='all', validatecommand=(validate_cmd, "%P"), width=6, justify='center')
|
self.po_timer_entry = ttk.Entry(
|
||||||
self.po_timer_entry.grid(row=0, column=1, pady=PADY, padx=PADX, sticky=(tk.W, tk.E))
|
po_timer_frame,
|
||||||
|
textvariable=self.po_timer_var,
|
||||||
|
validate="all",
|
||||||
|
validatecommand=(validate_cmd, "%P"),
|
||||||
|
width=6,
|
||||||
|
justify="center",
|
||||||
|
)
|
||||||
|
self.po_timer_entry.grid(
|
||||||
|
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, "Enter a number of minutes.")
|
||||||
|
|
||||||
button_width = 7
|
button_width = 7
|
||||||
get_po_minutes = ttk.Button(po_timer_frame, text="Get", width=button_width, command=self.get_po_mins)
|
get_po_minutes = ttk.Button(
|
||||||
|
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)
|
get_po_minutes.grid(row=0, column=0, padx=PADX, pady=PADY, sticky=tk.W)
|
||||||
|
|
||||||
set_po_minutes = ttk.Button(po_timer_frame, text="Set", width=button_width, command=self.set_po_mins)
|
set_po_minutes = ttk.Button(
|
||||||
|
po_timer_frame,
|
||||||
|
text="Set",
|
||||||
|
width=button_width,
|
||||||
|
command=self.set_po_mins,
|
||||||
|
)
|
||||||
set_po_minutes.grid(row=0, column=2, padx=PADX, pady=PADY, sticky=tk.E)
|
set_po_minutes.grid(row=0, column=2, padx=PADX, pady=PADY, sticky=tk.E)
|
||||||
|
|
||||||
# TI Received Time
|
# TI Received Time
|
||||||
ti_received_frame = ttk.LabelFrame(container_frame, text="TI Received Time (date)", padding=PAD)
|
ti_received_frame = ttk.LabelFrame(
|
||||||
ti_received_frame.grid(row=0, column=1, pady=PADY, padx=(PADX, 0), sticky=(tk.W, tk.E))
|
container_frame, text="TI Received Time (date)", padding=PAD
|
||||||
|
)
|
||||||
|
ti_received_frame.grid(
|
||||||
|
row=0, column=1, pady=PADY, padx=(PADX, 0), sticky=(tk.W, tk.E)
|
||||||
|
)
|
||||||
ti_received_frame.columnconfigure(0, weight=0) # Button column on the left
|
ti_received_frame.columnconfigure(0, weight=0) # Button column on the left
|
||||||
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 Calendar Widget
|
||||||
self.date_entry = DateEntry(ti_received_frame, date_pattern="yy-mm-dd", width=10, borderwidth=2)
|
self.date_entry = DateEntry(
|
||||||
self.date_entry.grid(row=0, column=1, padx=PADX, pady=PADY, sticky=(tk.W, tk.E))
|
ti_received_frame, date_pattern="yyyy-mm-dd"
|
||||||
self.date_entry.delete(0,"end")
|
)
|
||||||
ToolTip(self.date_entry, "Enter a valid date with format YY-MM-DD.")
|
self.date_entry.grid(
|
||||||
|
row=0, column=1, padx=PADX, pady=PADY, sticky=(tk.W, tk.E)
|
||||||
|
)
|
||||||
|
self.date_entry.delete(0, "end")
|
||||||
|
ToolTip(self.date_entry, "Enter a valid date with format YYYY-MM-DD.")
|
||||||
|
|
||||||
# TI Received Time Buttons
|
# TI Received Time Buttons
|
||||||
get_ti_received = ttk.Button(ti_received_frame, text="Get", width=button_width, command=self.get_ti_date)
|
get_ti_received = ttk.Button(
|
||||||
|
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)
|
get_ti_received.grid(row=0, column=0, padx=PADX, pady=PADY, sticky=tk.W)
|
||||||
|
|
||||||
set_ti_received = ttk.Button(ti_received_frame, text="Set", width=button_width, command=self.set_ti_date)
|
set_ti_received = ttk.Button(
|
||||||
|
ti_received_frame,
|
||||||
|
text="Set",
|
||||||
|
width=button_width,
|
||||||
|
command=self.set_ti_date,
|
||||||
|
)
|
||||||
set_ti_received.grid(row=0, column=2, padx=PADX, pady=PADY, sticky=tk.E)
|
set_ti_received.grid(row=0, column=2, padx=PADX, pady=PADY, sticky=tk.E)
|
||||||
|
|
||||||
# [row 2] Buttons
|
# [row 2] Buttons
|
||||||
|
@ -191,27 +304,56 @@ 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)
|
||||||
|
|
||||||
self.detect_button = ttk.Button(button_frame, text="Detect Printers", command=self.start_detect_printers)
|
self.detect_button = ttk.Button(
|
||||||
self.detect_button.grid(row=0, column=0, padx=PADX, pady=PADX, sticky=(tk.W, tk.E))
|
button_frame,
|
||||||
|
text="Detect Printers",
|
||||||
|
command=self.start_detect_printers,
|
||||||
|
)
|
||||||
|
self.detect_button.grid(
|
||||||
|
row=0, column=0, padx=PADX, pady=PADX, sticky=(tk.W, tk.E)
|
||||||
|
)
|
||||||
|
|
||||||
self.status_button = ttk.Button(button_frame, text="Print Status", command=self.print_status)
|
self.status_button = ttk.Button(
|
||||||
self.status_button.grid(row=0, column=1, padx=PADX, pady=PADY, sticky=(tk.W, tk.E))
|
button_frame, text="Printer Status", command=self.printer_status
|
||||||
|
)
|
||||||
|
self.status_button.grid(
|
||||||
|
row=0, column=1, padx=PADX, pady=PADY, sticky=(tk.W, tk.E)
|
||||||
|
)
|
||||||
|
|
||||||
self.reset_button = ttk.Button(button_frame, text="Reset Waste Ink Levels", command=self.reset_waste_ink)
|
self.reset_button = ttk.Button(
|
||||||
self.reset_button.grid(row=0, column=2, padx=PADX, pady=PADX, sticky=(tk.W, tk.E))
|
button_frame,
|
||||||
|
text="Reset Waste Ink Levels",
|
||||||
|
command=self.reset_waste_ink,
|
||||||
|
)
|
||||||
|
self.reset_button.grid(
|
||||||
|
row=0, column=2, padx=PADX, pady=PADX, sticky=(tk.W, tk.E)
|
||||||
|
)
|
||||||
|
|
||||||
# [row 3] Status display
|
# [row 3] Status display
|
||||||
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(row=row_n, column=0, pady=PADY, sticky=(tk.W, tk.E, tk.N, tk.S))
|
status_frame.grid(
|
||||||
|
row=row_n, column=0, pady=PADY, sticky=(tk.W, tk.E, tk.N, tk.S)
|
||||||
|
)
|
||||||
status_frame.columnconfigure(0, weight=1)
|
status_frame.columnconfigure(0, weight=1)
|
||||||
status_frame.rowconfigure(0, weight=1)
|
status_frame.rowconfigure(0, weight=1)
|
||||||
|
|
||||||
# ScrolledText widget
|
# ScrolledText widget
|
||||||
self.status_text = ScrolledText(status_frame, wrap=tk.WORD, font=("TkDefaultFont"))
|
self.status_text = ScrolledText(
|
||||||
self.status_text.grid(row=0, column=0, pady=PADY, padx=PADY, sticky=(tk.W, tk.E, tk.N, tk.S))
|
status_frame, wrap=tk.WORD, font=("TkDefaultFont")
|
||||||
|
)
|
||||||
|
self.status_text.grid(
|
||||||
|
row=0,
|
||||||
|
column=0,
|
||||||
|
pady=PADY,
|
||||||
|
padx=PADY,
|
||||||
|
sticky=(tk.W, tk.E, tk.N, tk.S),
|
||||||
|
)
|
||||||
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("<Control-c>", lambda event: self.copy_to_clipboard(self.status_text))
|
self.status_text.bind(
|
||||||
|
"<Control-c>",
|
||||||
|
lambda event: self.copy_to_clipboard(self.status_text),
|
||||||
|
)
|
||||||
# self.status_text.bind("<Button-1>", lambda e: "break") # also disable the mouse
|
# self.status_text.bind("<Button-1>", lambda e: "break") # also disable the mouse
|
||||||
|
|
||||||
# Create a frame to contain the Treeview and its scrollbar
|
# Create a frame to contain the Treeview and its scrollbar
|
||||||
|
@ -226,23 +368,31 @@ class EpsonPrinterUI(tk.Tk):
|
||||||
|
|
||||||
# For the treeview, if the treeview_font is a tuple, split into components
|
# For the treeview, if the treeview_font is a tuple, split into components
|
||||||
if isinstance(treeview_font, tuple):
|
if isinstance(treeview_font, tuple):
|
||||||
treeview_font_name, treeview_font_size = treeview_font[0], treeview_font[1]
|
treeview_font_name, treeview_font_size = (
|
||||||
|
treeview_font[0],
|
||||||
|
treeview_font[1],
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
# If font is not a tuple, it might be a font string or other format.
|
# If font is not a tuple, it might be a font string or other format.
|
||||||
treeview_font_name, treeview_font_size = tkfont.Font().actual('family'), tkfont.Font().actual('size')
|
treeview_font_name, treeview_font_size = tkfont.Font().actual(
|
||||||
|
"family"
|
||||||
style.configure("Treeview.Heading",
|
), tkfont.Font().actual("size")
|
||||||
font=(treeview_font_name, treeview_font_size - 2, "bold"),
|
style.configure(
|
||||||
background="lightblue",
|
"Treeview.Heading",
|
||||||
foreground="darkblue")
|
font=(treeview_font_name, treeview_font_size - 4, "bold"),
|
||||||
|
background="lightblue",
|
||||||
|
foreground="darkblue",
|
||||||
|
)
|
||||||
|
|
||||||
# Create and configure the Treeview widget
|
# Create and configure the Treeview widget
|
||||||
self.tree = ttk.Treeview(self.tree_frame, style="Treeview")
|
self.tree = ttk.Treeview(self.tree_frame, style="Treeview")
|
||||||
self.tree.heading("#0", text="Status Information", anchor='w')
|
self.tree.heading("#0", text="Status Information", anchor="w")
|
||||||
self.tree.grid(column=0, row=0, sticky=(tk.W, tk.E, tk.N, tk.S))
|
self.tree.grid(column=0, row=0, sticky=(tk.W, tk.E, tk.N, tk.S))
|
||||||
|
|
||||||
# Create a vertical scrollbar for the Treeview
|
# Create a vertical scrollbar for the Treeview
|
||||||
tree_scrollbar = ttk.Scrollbar(self.tree_frame, orient="vertical", command=self.tree.yview)
|
tree_scrollbar = ttk.Scrollbar(
|
||||||
|
self.tree_frame, orient="vertical", command=self.tree.yview
|
||||||
|
)
|
||||||
tree_scrollbar.grid(column=1, row=0, sticky=(tk.N, tk.S))
|
tree_scrollbar.grid(column=1, row=0, sticky=(tk.N, tk.S))
|
||||||
|
|
||||||
# Configure the Treeview to use the scrollbar
|
# Configure the Treeview to use the scrollbar
|
||||||
|
@ -250,7 +400,9 @@ 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(label="Copy", command=self.copy_selected_item)
|
self.context_menu.add_command(
|
||||||
|
label="Copy", command=self.copy_selected_item
|
||||||
|
)
|
||||||
|
|
||||||
# Bind the right-click event to the Treeview
|
# Bind the right-click event to the Treeview
|
||||||
self.tree.bind("<Button-3>", self.show_context_menu)
|
self.tree.bind("<Button-3>", self.show_context_menu)
|
||||||
|
@ -278,78 +430,166 @@ class EpsonPrinterUI(tk.Tk):
|
||||||
pass
|
pass
|
||||||
return "break"
|
return "break"
|
||||||
|
|
||||||
def get_po_mins(self):
|
def get_po_mins(self, cursor=True):
|
||||||
|
if cursor:
|
||||||
|
self.config(cursor="watch")
|
||||||
|
self.update()
|
||||||
|
current_function_name = inspect.stack()[0][3]
|
||||||
|
method_to_call = getattr(self, current_function_name)
|
||||||
|
self.after(100, lambda: method_to_call(cursor=False))
|
||||||
|
return
|
||||||
self.show_status_text_view()
|
self.show_status_text_view()
|
||||||
model = self.model_var.get()
|
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 model or not self._is_valid_ip(ip_address):
|
||||||
self.status_text.insert(tk.END, "[ERROR] Please select a printer model and enter a valid IP address.\n")
|
self.status_text.insert(tk.END, NO_CONF_ERROR)
|
||||||
|
self.config(cursor="")
|
||||||
|
self.update()
|
||||||
return
|
return
|
||||||
printer = EpsonPrinter(model=model, hostname=ip_address)
|
printer = EpsonPrinter(model=model, hostname=ip_address)
|
||||||
try:
|
try:
|
||||||
po_timer = printer.stats()['stats']['Power off timer']
|
po_timer = printer.stats()["stats"]["Power off timer"]
|
||||||
self.status_text.insert(tk.END, f"[INFO] Power off timer: {po_timer} minutes.\n")
|
self.status_text.insert(
|
||||||
|
tk.END, f"[INFO] Power off timer: {po_timer} minutes.\n"
|
||||||
|
)
|
||||||
self.po_timer_var.set(po_timer)
|
self.po_timer_var.set(po_timer)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.status_text.insert(tk.END, f"[ERROR] {e}: Missing 'Power off timer' in configuration\n")
|
self.status_text.insert(
|
||||||
|
tk.END,
|
||||||
|
f"[ERROR] {e}: Missing 'Power off timer' in configuration\n",
|
||||||
|
)
|
||||||
|
finally:
|
||||||
|
self.config(cursor="")
|
||||||
|
self.update_idletasks()
|
||||||
|
|
||||||
def set_po_mins(self):
|
def set_po_mins(self, cursor=True):
|
||||||
|
if cursor:
|
||||||
|
self.config(cursor="watch")
|
||||||
|
self.update()
|
||||||
|
current_function_name = inspect.stack()[0][3]
|
||||||
|
method_to_call = getattr(self, current_function_name)
|
||||||
|
self.after(100, lambda: method_to_call(cursor=False))
|
||||||
|
return
|
||||||
self.show_status_text_view()
|
self.show_status_text_view()
|
||||||
model = self.model_var.get()
|
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 model or not self._is_valid_ip(ip_address):
|
||||||
self.status_text.insert(tk.END, "[ERROR] Please select a printer model and enter a valid IP address.\n")
|
self.status_text.insert(tk.END, NO_CONF_ERROR)
|
||||||
|
self.config(cursor="")
|
||||||
|
self.update_idletasks()
|
||||||
return
|
return
|
||||||
printer = EpsonPrinter(model=model, hostname=ip_address)
|
printer = EpsonPrinter(model=model, hostname=ip_address)
|
||||||
try:
|
try:
|
||||||
po_timer = printer.stats()['stats']['Power off timer']
|
po_timer = printer.stats()["stats"]["Power off timer"]
|
||||||
po_timer = self.po_timer_var.get()
|
po_timer = self.po_timer_var.get()
|
||||||
|
self.config(cursor="")
|
||||||
|
self.update_idletasks()
|
||||||
if not po_timer.isnumeric():
|
if not po_timer.isnumeric():
|
||||||
self.status_text.insert(tk.END, "[ERROR] Please Use a valid value for minutes.\n")
|
self.status_text.insert(
|
||||||
|
tk.END, "[ERROR] Please Use a valid value for minutes.\n"
|
||||||
|
)
|
||||||
return
|
return
|
||||||
self.status_text.insert(tk.END, f"[INFO] Set Power off timer: {po_timer} minutes.\n")
|
self.status_text.insert(
|
||||||
response = messagebox.askyesno("Confirm Action", "Are you sure you want to proceed?")
|
tk.END, f"[INFO] Set Power off timer: {po_timer} minutes.\n"
|
||||||
|
)
|
||||||
|
response = messagebox.askyesno(
|
||||||
|
"Confirm Action", "Are you sure you want to proceed?"
|
||||||
|
)
|
||||||
if response:
|
if response:
|
||||||
printer.write_poweroff_timer(int(po_timer))
|
printer.write_poweroff_timer(int(po_timer))
|
||||||
else:
|
else:
|
||||||
self.status_text.insert(tk.END, f"[WARNING] Set Power off timer aborted.\n")
|
self.status_text.insert(
|
||||||
|
tk.END, f"[WARNING] Set Power off timer aborted.\n"
|
||||||
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.status_text.insert(tk.END, f"[ERROR] {e}: Cannot set 'Power off timer'; missing configuration\n")
|
self.config(cursor="")
|
||||||
|
self.update_idletasks()
|
||||||
|
self.status_text.insert(
|
||||||
|
tk.END,
|
||||||
|
f"[ERROR] {e}: Cannot set 'Power off timer'; missing configuration\n",
|
||||||
|
)
|
||||||
|
|
||||||
def get_ti_date(self):
|
def get_ti_date(self, cursor=True):
|
||||||
|
if cursor:
|
||||||
|
self.config(cursor="watch")
|
||||||
|
self.update()
|
||||||
|
current_function_name = inspect.stack()[0][3]
|
||||||
|
method_to_call = getattr(self, current_function_name)
|
||||||
|
self.after(100, lambda: method_to_call(cursor=False))
|
||||||
|
return
|
||||||
self.show_status_text_view()
|
self.show_status_text_view()
|
||||||
model = self.model_var.get()
|
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 model or not self._is_valid_ip(ip_address):
|
||||||
self.status_text.insert(tk.END, "[ERROR] Please select a printer model and enter a valid IP address.\n")
|
self.status_text.insert(tk.END, NO_CONF_ERROR)
|
||||||
|
self.config(cursor="")
|
||||||
|
self.update_idletasks()
|
||||||
return
|
return
|
||||||
printer = EpsonPrinter(model=model, hostname=ip_address)
|
printer = EpsonPrinter(model=model, hostname=ip_address)
|
||||||
try:
|
try:
|
||||||
date_string = datetime.strptime(printer.stats()['stats']['First TI received time'], '%d %b %Y').strftime('%y-%m-%d')
|
date_string = datetime.strptime(
|
||||||
self.status_text.insert(tk.END, f"[INFO] First TI received time (YY-MM-DD): {date_string}.\n")
|
printer.stats()["stats"]["First TI received time"], "%d %b %Y"
|
||||||
|
).strftime("%Y-%m-%d")
|
||||||
|
self.status_text.insert(
|
||||||
|
tk.END,
|
||||||
|
f"[INFO] First TI received time (YYYY-MM-DD): {date_string}.\n",
|
||||||
|
)
|
||||||
self.date_entry.set_date(date_string)
|
self.date_entry.set_date(date_string)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.status_text.insert(tk.END, f"[ERROR] {e}: Missing 'First TI received time' in configuration\n")
|
self.status_text.insert(
|
||||||
|
tk.END,
|
||||||
|
f"[ERROR] {e}: Missing 'First TI received time' in configuration\n",
|
||||||
|
)
|
||||||
|
finally:
|
||||||
|
self.config(cursor="")
|
||||||
|
self.update_idletasks()
|
||||||
|
|
||||||
def set_ti_date(self):
|
def set_ti_date(self, cursor=True):
|
||||||
|
if cursor:
|
||||||
|
self.config(cursor="watch")
|
||||||
|
self.update()
|
||||||
|
current_function_name = inspect.stack()[0][3]
|
||||||
|
method_to_call = getattr(self, current_function_name)
|
||||||
|
self.after(100, lambda: method_to_call(cursor=False))
|
||||||
|
return
|
||||||
self.show_status_text_view()
|
self.show_status_text_view()
|
||||||
model = self.model_var.get()
|
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 model or not self._is_valid_ip(ip_address):
|
||||||
self.status_text.insert(tk.END, "[ERROR] Please select a printer model and enter a valid IP address.\n")
|
self.status_text.insert(tk.END, NO_CONF_ERROR)
|
||||||
|
self.config(cursor="")
|
||||||
|
self.update_idletasks()
|
||||||
return
|
return
|
||||||
printer = EpsonPrinter(model=model, hostname=ip_address)
|
printer = EpsonPrinter(model=model, hostname=ip_address)
|
||||||
try:
|
try:
|
||||||
date_string = datetime.strptime(printer.stats()['stats']['First TI received time'], '%d %b %Y').strftime('%y-%m-%d')
|
date_string = datetime.strptime(
|
||||||
|
printer.stats()["stats"]["First TI received time"], "%d %b %Y"
|
||||||
|
).strftime("%y-%m-%d")
|
||||||
date_string = self.date_entry.get_date()
|
date_string = self.date_entry.get_date()
|
||||||
self.status_text.insert(tk.END, f"[INFO] Set 'First TI received time' (YY-MM-DD) to: {date_string.strftime('%Y-%m-%d')}.\n")
|
self.status_text.insert(
|
||||||
response = messagebox.askyesno("Confirm Action", "Are you sure you want to proceed?")
|
tk.END,
|
||||||
|
f"[INFO] Set 'First TI received time' (YYYY-MM-DD) to: {date_string.strftime('%Y-%m-%d')}.\n",
|
||||||
|
)
|
||||||
|
response = messagebox.askyesno(
|
||||||
|
"Confirm Action", "Are you sure you want to proceed?"
|
||||||
|
)
|
||||||
if response:
|
if response:
|
||||||
printer.write_first_ti_received_time(date_string.year, date_string.month, date_string.day)
|
printer.write_first_ti_received_time(
|
||||||
|
date_string.year, date_string.month, date_string.day
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
self.status_text.insert(tk.END, f"[WARNING] Change of 'First TI received time' aborted.\n")
|
self.status_text.insert(
|
||||||
|
tk.END,
|
||||||
|
f"[WARNING] Change of 'First TI received time' aborted.\n",
|
||||||
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.status_text.insert(tk.END, f"[ERROR] {e}: Cannot set 'First TI received time'; missing configuration\n")
|
self.status_text.insert(
|
||||||
|
tk.END,
|
||||||
|
f"[ERROR] {e}: Cannot set 'First TI received time'; missing configuration\n",
|
||||||
|
)
|
||||||
|
finally:
|
||||||
|
self.config(cursor="")
|
||||||
|
self.update_idletasks()
|
||||||
|
|
||||||
def validate_number_input(self, new_value):
|
def validate_number_input(self, new_value):
|
||||||
# This function will be called with the new input value
|
# This function will be called with the new input value
|
||||||
|
@ -368,75 +608,134 @@ class EpsonPrinterUI(tk.Tk):
|
||||||
self.status_text.grid_remove()
|
self.status_text.grid_remove()
|
||||||
self.tree_frame.grid()
|
self.tree_frame.grid()
|
||||||
|
|
||||||
def print_status(self):
|
def printer_status(self, cursor=True):
|
||||||
|
if cursor:
|
||||||
|
self.config(cursor="watch")
|
||||||
|
self.update()
|
||||||
|
current_function_name = inspect.stack()[0][3]
|
||||||
|
method_to_call = getattr(self, current_function_name)
|
||||||
|
self.after(100, lambda: method_to_call(cursor=False))
|
||||||
|
return
|
||||||
self.show_status_text_view()
|
self.show_status_text_view()
|
||||||
model = self.model_var.get()
|
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 model or not self._is_valid_ip(ip_address):
|
||||||
self.status_text.insert(tk.END, "[ERROR] Please select a printer model and enter a valid IP address.\n")
|
self.status_text.insert(tk.END, NO_CONF_ERROR)
|
||||||
|
self.config(cursor="")
|
||||||
|
self.update_idletasks()
|
||||||
return
|
return
|
||||||
printer = EpsonPrinter(model=model, hostname=ip_address)
|
printer = EpsonPrinter(model=model, hostname=ip_address)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.show_treeview()
|
self.show_treeview()
|
||||||
|
|
||||||
|
# Configure tags
|
||||||
|
self.tree.tag_configure("key", foreground="black")
|
||||||
|
self.tree.tag_configure("key_value", foreground="dark blue")
|
||||||
|
self.tree.tag_configure("value", foreground="blue")
|
||||||
|
|
||||||
# Populate the Treeview
|
# Populate the Treeview
|
||||||
self.populate_treeview('', self.tree, printer.stats())
|
self.populate_treeview("", self.tree, printer.stats())
|
||||||
|
|
||||||
# Expand all nodes
|
# Expand all nodes
|
||||||
self.expand_all(self.tree)
|
self.expand_all(self.tree)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.show_status_text_view()
|
self.show_status_text_view()
|
||||||
self.status_text.insert(tk.END, f"[ERROR] {e}\n")
|
self.status_text.insert(tk.END, f"[ERROR] {e}\n")
|
||||||
|
finally:
|
||||||
|
self.config(cursor="")
|
||||||
|
self.update_idletasks()
|
||||||
|
|
||||||
def reset_waste_ink(self):
|
def reset_waste_ink(self, cursor=True):
|
||||||
|
if cursor:
|
||||||
|
self.config(cursor="watch")
|
||||||
|
self.update()
|
||||||
|
current_function_name = inspect.stack()[0][3]
|
||||||
|
method_to_call = getattr(self, current_function_name)
|
||||||
|
self.after(100, lambda: method_to_call(cursor=False))
|
||||||
|
return
|
||||||
self.show_status_text_view()
|
self.show_status_text_view()
|
||||||
model = self.model_var.get()
|
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 model or not self._is_valid_ip(ip_address):
|
||||||
self.status_text.insert(tk.END, "[ERROR] Please select a printer model and enter a valid IP address.\n")
|
self.status_text.insert(tk.END, NO_CONF_ERROR)
|
||||||
|
self.config(cursor="")
|
||||||
|
self.update_idletasks()
|
||||||
return
|
return
|
||||||
printer = EpsonPrinter(model=model, hostname=ip_address)
|
printer = EpsonPrinter(model=model, hostname=ip_address)
|
||||||
try:
|
try:
|
||||||
response = messagebox.askyesno("Confirm Action", "Are you sure you want to proceed?")
|
printer.stats() # query the printer first
|
||||||
|
response = messagebox.askyesno(
|
||||||
|
"Confirm Action", "Are you sure you want to proceed?"
|
||||||
|
)
|
||||||
if response:
|
if response:
|
||||||
printer.reset_waste_ink_levels()
|
printer.reset_waste_ink_levels()
|
||||||
self.status_text.insert(tk.END, "[INFO] Waste ink levels have been reset.\n")
|
self.status_text.insert(
|
||||||
|
tk.END, "[INFO] Waste ink levels have been reset.\n"
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
self.status_text.insert(tk.END, f"[WARNING] Waste ink levels reset aborted.\n")
|
self.status_text.insert(
|
||||||
|
tk.END, f"[WARNING] Waste ink levels reset aborted.\n"
|
||||||
|
)
|
||||||
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")
|
||||||
|
finally:
|
||||||
|
self.config(cursor="")
|
||||||
|
self.update_idletasks()
|
||||||
|
|
||||||
def start_detect_printers(self):
|
def start_detect_printers(self):
|
||||||
self.show_status_text_view()
|
self.show_status_text_view()
|
||||||
self.status_text.insert(tk.END, "[INFO] Detecting printers... (this might take a while)\n")
|
self.status_text.insert(
|
||||||
self.detect_button.config(state=tk.DISABLED) # disable button while processing
|
tk.END, "[INFO] Detecting printers... (this might take a while)\n"
|
||||||
|
)
|
||||||
|
|
||||||
# run printer detection in new thread, as it can take a while
|
# run printer detection in new thread, as it can take a while
|
||||||
threading.Thread(target=self.detect_printers).start()
|
threading.Thread(target=self.detect_printers_thread).start()
|
||||||
|
|
||||||
def detect_printers(self):
|
def detect_printers_thread(self, cursor=True):
|
||||||
|
if cursor:
|
||||||
|
self.config(cursor="watch")
|
||||||
|
self.update()
|
||||||
|
current_function_name = inspect.stack()[0][3]
|
||||||
|
method_to_call = getattr(self, current_function_name)
|
||||||
|
self.after(100, lambda: method_to_call(cursor=False))
|
||||||
|
return
|
||||||
|
self.detect_button.config(state=tk.DISABLED) # disable button while processing
|
||||||
self.show_status_text_view()
|
self.show_status_text_view()
|
||||||
try:
|
try:
|
||||||
printers = self.printer_scanner.get_all_printers(self.ip_var.get().strip())
|
printers = self.printer_scanner.get_all_printers(
|
||||||
|
self.ip_var.get().strip()
|
||||||
|
)
|
||||||
if len(printers) > 0:
|
if len(printers) > 0:
|
||||||
if len(printers) == 1:
|
if len(printers) == 1:
|
||||||
self.status_text.insert(tk.END, f"[INFO] Found printer '{printers[0]['name']}' at {printers[0]['ip']} (hostname: {printers[0]['hostname']})\n")
|
self.status_text.insert(
|
||||||
self.ip_var.set(printers[0]['ip'])
|
tk.END,
|
||||||
for model in self.get_printer_models(printers[0]['name']):
|
f"[INFO] Found printer '{printers[0]['name']}' at {printers[0]['ip']} (hostname: {printers[0]['hostname']})\n",
|
||||||
|
)
|
||||||
|
self.ip_var.set(printers[0]["ip"])
|
||||||
|
for model in get_printer_models(printers[0]["name"]):
|
||||||
if model in EpsonPrinter().valid_printers:
|
if model in EpsonPrinter().valid_printers:
|
||||||
self.model_var.set(model)
|
self.model_var.set(model)
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
self.status_text.insert(tk.END, f"[INFO] Found {len(printers)} printers:\n")
|
self.status_text.insert(
|
||||||
|
tk.END, f"[INFO] Found {len(printers)} printers:\n"
|
||||||
|
)
|
||||||
for printer in printers:
|
for printer in printers:
|
||||||
self.status_text.insert(tk.END, f"[INFO] {printer['name']} found at {printer['ip']} (hostname: {printer['hostname']})\n")
|
self.status_text.insert(
|
||||||
|
tk.END,
|
||||||
|
f"[INFO] {printer['name']} found at {printer['ip']} (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")
|
||||||
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")
|
||||||
finally:
|
finally:
|
||||||
self.detect_button.config(state=tk.NORMAL) # enable button after processing
|
self.detect_button.config(state=tk.NORMAL) # enable button after processing
|
||||||
|
self.config(cursor="")
|
||||||
|
self.update_idletasks()
|
||||||
|
|
||||||
def _is_valid_ip(self,ip):
|
def _is_valid_ip(self, ip):
|
||||||
try:
|
try:
|
||||||
ip = ipaddress.ip_address(ip)
|
ip = ipaddress.ip_address(ip)
|
||||||
return True
|
return True
|
||||||
|
@ -452,7 +751,7 @@ class EpsonPrinterUI(tk.Tk):
|
||||||
for item in data:
|
for item in data:
|
||||||
if isinstance(item, (tuple, list, set)):
|
if isinstance(item, (tuple, list, set)):
|
||||||
return True
|
return True
|
||||||
if isinstance(item, str) and ('(' in item or ')' in item):
|
if isinstance(item, str) and ("(" in item or ")" in item):
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@ -460,29 +759,52 @@ class EpsonPrinterUI(tk.Tk):
|
||||||
if isinstance(data, dict):
|
if isinstance(data, dict):
|
||||||
for key, value in data.items():
|
for key, value in data.items():
|
||||||
if isinstance(value, (dict, list, set, tuple)):
|
if isinstance(value, (dict, list, set, tuple)):
|
||||||
node = treeview.insert(parent, 'end', text=key)
|
node = treeview.insert(
|
||||||
|
parent, "end", text=key, tags=("key",)
|
||||||
|
)
|
||||||
self.populate_treeview(node, treeview, value)
|
self.populate_treeview(node, treeview, value)
|
||||||
else:
|
else:
|
||||||
treeview.insert(parent, 'end', text=f"{key}: {value}")
|
treeview.insert(
|
||||||
|
parent,
|
||||||
|
"end",
|
||||||
|
text=f"{key}: {value}",
|
||||||
|
tags=("key_value"),
|
||||||
|
)
|
||||||
elif isinstance(data, list):
|
elif isinstance(data, list):
|
||||||
if all(self.is_simple_type(item) for item in data) and not self.contains_parentheses(data):
|
if all(
|
||||||
treeview.insert(parent, 'end', text=', '.join(map(str, data)))
|
self.is_simple_type(item) for item in data
|
||||||
|
) and not self.contains_parentheses(data):
|
||||||
|
treeview.insert(
|
||||||
|
parent,
|
||||||
|
"end",
|
||||||
|
text=", ".join(map(str, data)),
|
||||||
|
tags=("value",),
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
for item in data:
|
for item in data:
|
||||||
if isinstance(item, (dict, list, set, tuple)):
|
if isinstance(item, (dict, list, set, tuple)):
|
||||||
self.populate_treeview(parent, treeview, item)
|
self.populate_treeview(parent, treeview, item)
|
||||||
else:
|
else:
|
||||||
treeview.insert(parent, 'end', text=str(item))
|
treeview.insert(
|
||||||
|
parent, "end", text=str(item), tags=("value",)
|
||||||
|
)
|
||||||
elif isinstance(data, set):
|
elif isinstance(data, set):
|
||||||
if not self.contains_parentheses(data):
|
if not self.contains_parentheses(data):
|
||||||
treeview.insert(parent, 'end', text=', '.join(map(str, data)))
|
treeview.insert(
|
||||||
|
parent,
|
||||||
|
"end",
|
||||||
|
text=", ".join(map(str, data)),
|
||||||
|
tags=("value",),
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
for item in data:
|
for item in data:
|
||||||
treeview.insert(parent, 'end', text=str(item))
|
treeview.insert(
|
||||||
|
parent, "end", text=str(item), tags=("value",)
|
||||||
|
)
|
||||||
elif isinstance(data, tuple):
|
elif isinstance(data, tuple):
|
||||||
treeview.insert(parent, 'end', text=str(data))
|
treeview.insert(parent, "end", text=str(data), tags=("value",))
|
||||||
else:
|
else:
|
||||||
treeview.insert(parent, 'end', text=str(data))
|
treeview.insert(parent, "end", text=str(data), tags=("value",))
|
||||||
|
|
||||||
def expand_all(self, treeview):
|
def expand_all(self, treeview):
|
||||||
def recursive_expand(item):
|
def recursive_expand(item):
|
||||||
|
@ -511,43 +833,6 @@ class EpsonPrinterUI(tk.Tk):
|
||||||
self.clipboard_clear()
|
self.clipboard_clear()
|
||||||
self.clipboard_append(item_text)
|
self.clipboard_append(item_text)
|
||||||
|
|
||||||
def get_printer_models(self, input_string):
|
|
||||||
# Tokenize the string
|
|
||||||
tokens = re.split(' |/', input_string)
|
|
||||||
if not len(tokens):
|
|
||||||
return []
|
|
||||||
|
|
||||||
# Define the words to remove (uppercase, then case insensitive)
|
|
||||||
remove_tokens = {"EPSON", "SERIES"}
|
|
||||||
|
|
||||||
# Process tokens
|
|
||||||
processed_tokens = []
|
|
||||||
non_numeric_part = ""
|
|
||||||
pre_model = ""
|
|
||||||
for token in tokens:
|
|
||||||
upper_token = token.upper()
|
|
||||||
|
|
||||||
# Remove tokens that match remove_tokens
|
|
||||||
if any(word == upper_token for word in remove_tokens):
|
|
||||||
continue
|
|
||||||
|
|
||||||
if not any(char.isdigit() for char in token): # no alphanum inside
|
|
||||||
pre_model = pre_model + token + " "
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Identify the non-numeric part of the first token
|
|
||||||
if not token.isnumeric() and not non_numeric_part:
|
|
||||||
non_numeric_part = ''.join(c for c in token if not c.isdigit())
|
|
||||||
|
|
||||||
# if token is numeric, prepend the non-numeric part
|
|
||||||
if token.isnumeric():
|
|
||||||
processed_tokens.append(f"{pre_model}{non_numeric_part}{token}")
|
|
||||||
else:
|
|
||||||
processed_tokens.append(f"{pre_model}{token}")
|
|
||||||
if not processed_tokens and pre_model:
|
|
||||||
processed_tokens.append(pre_model.strip())
|
|
||||||
return processed_tokens
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
app = EpsonPrinterUI()
|
app = EpsonPrinterUI()
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue