diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 860b825..472707c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,6 +11,24 @@ jobs: steps: - name: Git Checkout uses: actions/checkout@v4 + with: + ref: main + fetch-depth: 0 # Fetch all history for accurate commits and tags + + - name: Update VERSION in python file ui.py; commit and push updates + run: | + $filePath = "ui.py" + $VERSION = "${{ github.ref_name }}" + if ($VERSION.StartsWith('v')) { + $VERSION = $VERSION.Substring(1) + } + (Get-Content $filePath) -replace '^VERSION = ".*"$', "VERSION = `"$VERSION`"" | Set-Content $filePath + git config --local user.name "github-actions[bot]" + git config --local user.email "github-actions[bot]@users.noreply.github.com" + git add $filePath + git commit -m "Update VERSION to ${{ github.ref_name }}" + git remote set-url origin https://${GITHUB_USER}:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }} + git push origin main - name: Install Python uses: actions/setup-python@v5 @@ -27,9 +45,7 @@ jobs: pip install -r requirements.txt - name: Run PyInstaller to create epson_print_conf.exe - run: > - python -m PyInstaller epson_print_conf.spec -- --default - --version ${{ github.ref_name }} + run: python -m PyInstaller epson_print_conf.spec -- --default - name: Zip the epson_print_conf.exe asset to epson_print_conf.zip run: | diff --git a/.github/workflows/jekyll-gh-pages.yml b/.github/workflows/jekyll-gh-pages.yml index 91218dd..f159950 100644 --- a/.github/workflows/jekyll-gh-pages.yml +++ b/.github/workflows/jekyll-gh-pages.yml @@ -28,6 +28,10 @@ jobs: steps: - name: Checkout uses: actions/checkout@v4 + - name: Convert URLs to Autolink Format + run: | + # Find all markdown files and convert URLs to format + find . -name "*.md" -exec sed -i 's#\([^<]\)\(http[s]\?://[^\s)\]]\+\)#[\1](<\2>)#g' {} + - name: Setup Pages uses: actions/configure-pages@v5 - name: Build with Jekyll diff --git a/.gitignore b/.gitignore index 7c02179..0a8a145 100644 --- a/.gitignore +++ b/.gitignore @@ -162,3 +162,5 @@ devices.xml *.toml *.srs *.pickle + +.console_history diff --git a/README.md b/README.md index 08b6096..ab16cd3 100644 --- a/README.md +++ b/README.md @@ -4,46 +4,44 @@ Epson Printer Configuration tool via SNMP (TCP/IP) ## Product Overview -The *Epson Printer Configuration Tool* simplifies the management of Epson printers connected via Wi-Fi over the SNMP protocol. +The Epson Printer Configuration Tool provides an interface for the configuration and monitoring of Epson printers connected via Wi-Fi using the SNMP protocol. A range of features are offered for both end-users and developers. -A range of features are offered for both end-users and developers, making it easier to administer and maintain Epson printers. - -The software provides a configurable printer dictionary, which can be easily extended. In addition, it is possible to import and convert external Epson printer configuration databases. +The software also includes a configurable printer dictionary, which can be easily extended. In addition, it is possible to import and convert external Epson printer configuration databases. ## Key Features -- __SNMP Interface__: Seamlessly connect and manage Epson printers using SNMP over TCP/IP, supporting Wi-Fi connections (not USB). +- __SNMP Interface__: Connect and manage Epson printers using SNMP over TCP/IP, supporting Wi-Fi connections (not USB). - Printers are queried via Simple Network Management Protocol (SNMP) with a set of Object Identifiers (OIDs) used by Epson printers. Some of them are also valid with other printer brands. SNMP is also used to manage the EEPROM and read/set specific Epson configuration. + Printers are queried via Simple Network Management Protocol (SNMP) with a set of Object Identifiers (OIDs) used by Epson printers. Some of them are also valid with other printer brands. SNMP is used to manage the EEPROM and read/set specific Epson configuration. - __Detailed Status Reporting__: Produce a comprehensive printer status report (with options to focus on specific details). Epson printers produce a status response in a proprietary "new binary format" named @BDC ST2, including a data structure which is partially undocumented (such messages - start with `@BDC [SP] ST2 [CR] [LF]` ...). It is used to convey various aspects of the status of the printer, such as errors, paper status, ink and more. The element fields of this format may vary depending on the printer model. The *Epson Printer Configuration Tool* can decode all element fields found in publicly available Epson Programming Manuals of various printer models (a relevant subset of fields used by the Epson printers). + start with `@BDC [SP] ST2 [CR] [LF]` ...). @BDC ST2 is used to convey various aspects of the status of the printer, such as errors, paper status, ink and more. The element fields of this format may vary depending on the printer model. The *Epson Printer Configuration Tool* can decode all element fields found in publicly available Epson Programming Manuals of various printer models (a relevant subset of fields used by the Epson printers). - __Advanced Maintenance Functions__: - Open the Web interface of the printer (via the default browser). - Reset the ink waste counter. - The ink waste counters track the amount of ink discarded during maintenance tasks to prevent overflow in the waste ink pads. Once the counters indicate that one of the printer pads is full, the printer will stop working to avoid potential damage or ink spills. Resetting the ink waste counter extends the printer operation while a pad maintenance or tank replacement is programmed. - - Adjust the power-off timer (for more accurate energy efficiency). + The ink waste counters track the amount of ink discarded during maintenance tasks to prevent overflow in the waste ink pads. Once the counters indicate that one of the printer pads is full, the printer will stop working to avoid potential damage or ink spills. Resetting the ink waste counter extends the printer operation while a pad maintenance or tank replacement is programmed (operation that shall necessarily be pefromed). + - Adjust the power-off timer (for energy efficiency). - Change the _First TI Received Time_, - 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. This feature tracks when the printer first operated. - Change the printer WiFi MAC address and the printer serial number (typically used in specialized scenarios where specific device identifiers are required). - - Read and write to EEPROM addresses for advanced configurations. + - Read and write to EEPROM addresses. - Dump and analyze sets of EEPROM addresses. - Detect the access key (*read_key* and *write_key*) and some attributes of the printer configuration. - The GUI includes some features that attempt to detect the attributes of an Epson printer whose model is not included in the configuration, which can also be used with known printers, to detect additional parameters. + The GUI includes some features that attempt to detect the attributes of an Epson printer whose model is not included in the configuration; such features can also be used with known printers, to detect additional parameters. - Import and export printer configuration datasets in various formats: epson_print_conf pickle, Reinkpy XML, Reinkpy TOML. - Access various administrative and debugging options. -- __User-Friendly Interfaces__: - - __Graphical User Interface (GUI)__: Intuitive interface with an autodiscovery function that detects printer IP addresses and model names. +- __Available Interfaces__: + - __Graphical User Interface__: [Tcl/Tk](https://en.wikipedia.org/wiki/Tk_(software)) platform-independent GUI with an autodiscovery function that detects printer IP addresses and model names. - __Command Line Tool__: For users who prefer command-line interactions, providing the full set of features. - __Python API Interface__: For developers to integrate and automate printer management tasks. @@ -59,29 +57,12 @@ cd epson_print_conf pip install -r requirements.txt ``` -Alternatively, install requirements via command line: - -``` -git clone https://github.com/Ircama/epson_print_conf -pip3 install pyyaml -pip3 install pyasn1==0.4.8 -pip3 install git+https://github.com/etingof/pysnmp.git -pip3 install tkcalendar -pip3 install pyperclip -pip3 install black -pip3 install tomli - -cd epson_print_conf -``` - -With Python 12, also: `pip3 install pyasyncore`. - Notes (at the time of writing): - [before pysnmp, install pyasn1 with version 0.4.8 and not 0.5](https://github.com/etingof/pysnmp/issues/440#issuecomment-1544341598) - [pull pysnmp from the GitHub master branch, not from PyPI](https://stackoverflow.com/questions/54868134/snmp-reading-from-an-oid-with-three-libraries-gives-different-execution-times#comment96532761_54869361) -This program exploits [pysnmp](https://github.com/etingof/pysnmp), with related [documentation](https://pysnmp.readthedocs.io/). +This program exploits [pysnmp](https://github.com/etingof/pysnmp), basing on the related [documentation](https://pysnmp.readthedocs.io/). It is tested with Ubuntu / Windows Subsystem for Linux, Windows. @@ -130,17 +111,17 @@ With the GUI, the following operations are possible (from the file menu): - Import an XML configuration file or web URL - This option allows to import the XML configuration file downloaded from https://codeberg.org/attachments/147f41a3-a6ea-45f6-8c2a-25bac4495a1d. Alternatively, this option directly accepts the [source Web URL](https://codeberg.org/attachments/147f41a3-a6ea-45f6-8c2a-25bac4495a1d) of this file, incorporating the download operation into the GUI. + This option allows to import the XML configuration file downloaded from . Alternatively, this option directly accepts the [source Web URL](https://codeberg.org/attachments/147f41a3-a6ea-45f6-8c2a-25bac4495a1d) of this file, incorporating the download operation into the GUI. - Import a TOML configuration file or web URL - Similar to the XML import, this option allows to load the TOML configuration file downloaded from https://codeberg.org/atufi/reinkpy/raw/branch/main/reinkpy/epson.toml and also accepts the [source Web URL](https://codeberg.org/atufi/reinkpy/raw/branch/main/reinkpy/epson.toml) of this file, incorporating the download operation into the GUI. + Similar to the XML import, this option allows to load the TOML configuration file downloaded from and also accepts the [source Web URL](https://codeberg.org/atufi/reinkpy/raw/branch/main/reinkpy/epson.toml) of this file, incorporating the download operation into the GUI. Other menu options allow to filter or clean up the configuration list, as well as select a specific printer model and then save data to a PICKLE file. ### How to detect parameters of an unknown printer -First press "Detect Printers". If the printer is not in the configuration, press "Detect Access Keys". If the output does not show errors, press "Detect Configuration". These commands produce a tree view and a text view, which are useful to analyze whether there is a configured model that might be close or possibly same to target one. Notice that these operations take many minutes to complete and the printer shall be kept switched on for the whole period. Temporarily disabling the auto power-off timer is suggested. +First press "Detect Printers". If the printer is not in the configuration, press "Detect Access Keys". If the output does not show errors, press "Detect Configuration". These commands produce a tree view and a text view, which are useful to analyze whether there is a configured model that might be close or possibly same to target one. Use the right key of the mouse to switch between the two views. Notice that these operations take many minutes to complete and the printer shall be kept switched on for the whole period. Temporarily disabling the auto power-off timer is suggested. ### How to revert a change performed through the GUI @@ -277,7 +258,7 @@ This repository includes a Windows *epson_print_conf.exe* executable file which ### parse_devices.py -Within a [report](https://codeberg.org/atufi/reinkpy/issues/12#issue-716809) in repo https://codeberg.org/atufi/reinkpy there is an interesting [attachment](https://codeberg.org/attachments/147f41a3-a6ea-45f6-8c2a-25bac4495a1d) which includes an extensive XML database of Epson model features. +Within a [report](https://codeberg.org/atufi/reinkpy/issues/12#issue-716809) in repo there is an interesting [attachment](https://codeberg.org/attachments/147f41a3-a6ea-45f6-8c2a-25bac4495a1d) which includes an extensive XML database of Epson model features. The program *parse_devices.py* transforms this XML DB into the dictionary that *epson_print_conf.py* can use. It is also able to accept the [TOML](https://toml.io/) input format used by [reinkpy](https://codeberg.org/atufi/reinkpy) in [epson.toml](https://codeberg.org/atufi/reinkpy/src/branch/main/reinkpy/epson.toml), if the `-T` option is used. @@ -694,30 +675,30 @@ snmpget -v1 -d -c public 192.168.1.87 1.3.6.1.4.1.1248.1.2.2.44.1.1.2.1.124.124. ### References -epson-printer-snmp: https://github.com/Zedeldi/epson-printer-snmp (and https://github.com/Zedeldi/epson-printer-snmp/issues/1) +epson-printer-snmp: (and ) -ReInkPy: https://codeberg.org/atufi/reinkpy/ +ReInkPy: -ReInk: https://github.com/lion-simba/reink (especially https://github.com/lion-simba/reink/issues/1) +ReInk: (especially ) -reink-net: https://github.com/gentu/reink-net +reink-net: -epson-l4160-ink-waste-resetter: https://github.com/nicootto/epson-l4160-ink-waste-resetter +epson-l4160-ink-waste-resetter: -epson-l3160-ink-waste-resetter: https://github.com/k3dt/epson-l3160-ink-waste-resetter +epson-l3160-ink-waste-resetter: -emanage x900: https://github.com/abrasive/x900-otsakupuhastajat/ +emanage x900: ### Other programs -- Epson One-Time Maintenance Ink Pad Reset Utility: https://epson.com/Support/wa00369 - - Epson Maintenance Reset Utility: https://epson.com/epsonstorefront/orbeon/fr/us_regular_s03/us_ServiceInk_Pad_Reset/new - - Epson Ink Pads Reset Utility Terms and Conditions: https://epson.com/Support/wa00370 +- Epson One-Time Maintenance Ink Pad Reset Utility: + - Epson Maintenance Reset Utility: + - Epson Ink Pads Reset Utility Terms and Conditions: - Epson Adjustment Program (developed by EPSON) -- WIC-Reset: https://www.wic.support/download/ / https://www.2manuals.com / (Use at your risk) -- PrintHelp: https://printhelp.info/ (Use at your risk) +- WIC-Reset: / (Use at your risk) +- PrintHelp: (Use at your risk) ### Other resources -- https://codeberg.org/attachments/147f41a3-a6ea-45f6-8c2a-25bac4495a1d -- https://codeberg.org/atufi/reinkpy/src/branch/main/reinkpy/epson.toml +- +- diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..a12b7df --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,14 @@ +# ELM327-emulator Security Policy + +*ELM327-emulator* is a testing software which is not expected to run in production environments. Considering that the dictionary allows executing user-defined commands, +it is important to revise any third-party customization to avoid that security flaws are introduced. + +Security bugs will be taken seriously and, +if confirmed upon investigation, a new patch will be released within a reasonable amount of time, including a security bulletin and the credit to the discoverer. + +Warning: when using the TCP/IP networking, the INET socket is bound to all interfaces. + +## Reporting a Security Bug + +The way to report a security bug is to open an [issue](https://github.com/Ircama/ELM327-emulator/issues) including related information +(e.g., reproduction steps, version). \ No newline at end of file diff --git a/_config.yml b/_config.yml index e073309..a1a8fd8 100644 --- a/_config.yml +++ b/_config.yml @@ -1,12 +1,11 @@ -remote_theme: bmndc/just-the-docs +remote_theme: ircama/just-the-docs + +# Enable or disable the site search +# Supports true (default) or false search_enabled: false + +# For copy button on code enable_copy_code_button: true -heading_anchors: true -aux_links: - "epson_print_conf on GitHub": - - "https://github.com/Ircama/epson_print_conf/" -aux_links_new_tab: true -nav_enabled: false # Table of Contents # Enable or disable the Table of Contents globally @@ -26,6 +25,54 @@ toc: # Supports true or false (default) flat_toc: false +# By default, consuming the theme as a gem leaves mermaid disabled; it is opt-in +mermaid: + # Version of mermaid library + # Pick an available version from https://cdn.jsdelivr.net/npm/mermaid/ + version: "9.1.6" + # Put any additional configuration, such as setting the theme, in _includes/mermaid_config.js + # See also docs/ui-components/code + # To load mermaid from a local library, also use the `path` key to specify the location of the library; e.g. + # for (v10+): + # path: "/assets/js/mermaid.esm.min.mjs" + # for (", self.printer_config) - self.model_dropdown.bind("", self.key_values) - self.model_dropdown.bind("", lambda event: self.remove_printer_conf()) - self.model_dropdown.bind("", lambda event: self.keep_printer_conf()) - self.model_dropdown.bind("", lambda event: self.clear_printer_list()) + self.bind_all("", self.printer_config) + self.bind_all("", self.key_values) + self.bind_all("", lambda event: self.remove_printer_conf()) + self.bind_all("", lambda event: self.keep_printer_conf()) + self.bind_all("", lambda event: self.clear_printer_list()) + self.bind_all("", lambda event: self.tk_console()) # BOX IP address ip_frame = ttk.LabelFrame( @@ -726,6 +821,10 @@ def __init__( self.status_text = ScrolledText( status_frame, wrap=tk.WORD, font=("TkDefaultFont") ) + self.status_text.tag_configure("error", foreground="red") + self.status_text.tag_configure("warn", foreground="blue") + self.status_text.tag_configure("note", foreground="purple") + self.status_text.tag_configure("info", foreground="green") self.status_text.grid( row=0, column=0, @@ -836,9 +935,10 @@ def __init__( def save_to_file(self): if not self.model_var.get(): self.show_status_text_view() + self.status_text.insert(tk.END, '[ERROR]', "error") self.status_text.insert( tk.END, - '[ERROR]: Unknown printer model.' + ': Unknown printer model.' ) return if not self.printer: @@ -848,9 +948,10 @@ def save_to_file(self): ) if not self.printer or not self.printer.parm: self.show_status_text_view() + self.status_text.insert(tk.END, '[ERROR]', "error") self.status_text.insert( tk.END, - '[ERROR]: No printer configuration defined.' + ': No printer configuration defined.' ) return # Open file dialog to enter the file @@ -880,14 +981,16 @@ def save_to_file(self): pickle.dump(normalized_config, file) # serialize the list except Exception: self.show_status_text_view() + self.status_text.insert(tk.END, '[ERROR]', "error") self.status_text.insert( tk.END, - f"[ERROR] File save operation failed.\n" + f" File save operation failed.\n" ) return + self.status_text.insert(tk.END, '[INFO]', "info") self.status_text.insert( tk.END, - f'[INFO] "{os.path.basename(file_path)}" file save operation completed.\n' + f' "{os.path.basename(file_path)}" file save operation completed.\n' ) def load_from_file(self, file_type, type): @@ -915,14 +1018,16 @@ def load_from_file(self, file_type, type): self.update_idletasks() self.show_status_text_view() if not file_path.tell(): + self.status_text.insert(tk.END, '[ERROR]', "error") self.status_text.insert( tk.END, - f"[ERROR] Empty PICKLE FILE {file_path}.\n" + f" Empty PICKLE FILE {file_path}.\n" ) else: + self.status_text.insert(tk.END, '[ERROR]', "error") self.status_text.insert( tk.END, - f"[ERROR] Cannot load PICKLE file {file_path}. {e}\n" + f" Cannot load PICKLE file {file_path}. {e}\n" ) return if ( @@ -937,9 +1042,10 @@ def load_from_file(self, file_type, type): self.model_var.set(self.conf_dict["internal_data"]["default_model"]) else: self.config(cursor="watch") + self.status_text.insert(tk.END, '[INFO]', "info") self.status_text.insert( tk.END, - f"[INFO] Converting file, please wait...\n" + f" Converting file, please wait...\n" ) self.update_idletasks() if type == 1: @@ -949,9 +1055,10 @@ def load_from_file(self, file_type, type): if not printer_config: self.config(cursor="") self.show_status_text_view() + self.status_text.insert(tk.END, '[ERROR]', "error") self.status_text.insert( tk.END, - f"[ERROR] Cannot load file {file_path}\n" + f" Cannot load file {file_path}\n" ) return self.conf_dict = normalize_config(config=printer_config) @@ -963,17 +1070,19 @@ def load_from_file(self, file_type, type): self.update_idletasks() if file_path: self.show_status_text_view() + self.status_text.insert(tk.END, '[INFO]', "info") self.status_text.insert( tk.END, - f"[INFO] Loaded file {os.path.basename(file_path)}.\n" + f" Loaded file {os.path.basename(file_path)}.\n" ) def keep_printer_conf(self): self.show_status_text_view() if not self.model_var.get(): + self.status_text.insert(tk.END, '[ERROR]', "error") self.status_text.insert( tk.END, - '[ERROR]: Select a valid printer model.\n' + ': Select a valid printer model.\n' ) return keep_model = self.model_var.get() @@ -983,17 +1092,19 @@ def keep_printer_conf(self): self.replace_conf = True self.show_status_text_view() self.update_idletasks() + self.status_text.insert(tk.END, '[INFO]', "info") self.status_text.insert( tk.END, - f"[INFO] Printer {keep_model} is the only one in the list.\n" + f" Printer {keep_model} is the only one in the list.\n" ) def remove_printer_conf(self): self.show_status_text_view() if not self.model_var.get(): + self.status_text.insert(tk.END, '[ERROR]', "error") self.status_text.insert( tk.END, - '[ERROR]: Select a valid printer model.\n' + ': Select a valid printer model.\n' ) return remove_model = self.model_var.get() @@ -1004,9 +1115,10 @@ def remove_printer_conf(self): self.replace_conf = True self.show_status_text_view() self.update_idletasks() + self.status_text.insert(tk.END, '[INFO]', "info") self.status_text.insert( tk.END, - f"[INFO] Configuation of printer {remove_model} removed.\n" + f" Configuation of printer {remove_model} removed.\n" ) def clear_printer_list(self): @@ -1016,11 +1128,24 @@ def clear_printer_list(self): self.replace_conf = True self.show_status_text_view() self.update_idletasks() + self.status_text.insert(tk.END, '[INFO]', "info") self.status_text.insert( tk.END, - f"[INFO] Printer list cleared.\n" + f" Printer list cleared.\n" ) + def tk_console(self): + console_window = tk.Toplevel(self) + console_window.title("Debug Console") + console_window.geometry("800x400") + + console = EpcTextConsole(self, console_window) + console.pack(fill='both', expand=True) # Use pack within the frame + + # Configure grid resizing for the frame + self.grid_rowconfigure(0, weight=1) + self.grid_columnconfigure(0, weight=1) + def open_help_browser(self): # Opens a web browser to a help URL url = "https://ircama.github.io/epson_print_conf" @@ -1028,16 +1153,19 @@ def open_help_browser(self): try: ret = webbrowser.open(url) if ret: + self.status_text.insert(tk.END, '[INFO]', "info") self.status_text.insert( - tk.END, f"[INFO] The browser is being opened.\n" + tk.END, f" The browser is being opened.\n" ) else: + self.status_text.insert(tk.END, '[ERROR]', "error") self.status_text.insert( - tk.END, f"[ERROR] Cannot open browser.\n" + tk.END, f" Cannot open browser.\n" ) except Exception as e: + self.status_text.insert(tk.END, '[ERROR]', "error") self.status_text.insert( - tk.END, f"[ERROR] Cannot open web browser: {e}\n" + tk.END, f" Cannot open web browser: {e}\n" ) finally: self.config(cursor="") @@ -1249,12 +1377,14 @@ def copy_to_clipboard(self, text_widget): def handle_printer_error(self, e): self.show_status_text_view() if isinstance(e, TimeoutError): + self.status_text.insert(tk.END, '[ERROR]', "error") self.status_text.insert( - tk.END, f"[ERROR] Printer is unreachable or offline.\n" + tk.END, f" Printer is unreachable or offline.\n" ) else: + self.status_text.insert(tk.END, '[ERROR]', "error") self.status_text.insert( - tk.END, f"[ERROR] {e}\n{traceback.format_exc()}\n" + tk.END, f" {e}\n{traceback.format_exc()}\n" ) def get_po_mins(self, cursor=True): @@ -1268,6 +1398,7 @@ def get_po_mins(self, cursor=True): 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, '[ERROR]', "error") self.status_text.insert(tk.END, NO_CONF_ERROR) self.config(cursor="") self.update() @@ -1275,17 +1406,19 @@ def get_po_mins(self, cursor=True): if not self.printer: return if not self.printer.parm.get("stats", {}).get("Power off timer"): + self.status_text.insert(tk.END, '[ERROR]', "error") self.status_text.insert( tk.END, - f"[ERROR]: Missing 'Power off timer' in configuration\n", + f": Missing 'Power off timer' in configuration\n", ) self.config(cursor="") self.update_idletasks() return try: po_timer = self.printer.stats()["stats"]["Power off timer"] + self.status_text.insert(tk.END, '[INFO]', "info") self.status_text.insert( - tk.END, f"[INFO] Power off timer: {po_timer} minutes.\n" + tk.END, f" Power off timer: {po_timer} minutes.\n" ) self.po_timer_var.set(po_timer) except Exception as e: @@ -1305,6 +1438,7 @@ def get_ser_number(self, cursor=True): 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, '[ERROR]', "error") self.status_text.insert(tk.END, NO_CONF_ERROR) self.config(cursor="") self.update() @@ -1319,23 +1453,26 @@ def get_ser_number(self, cursor=True): self.update_idletasks() return if ser_num is False: + self.status_text.insert(tk.END, '[ERROR]', "error") self.status_text.insert( tk.END, - f"[ERROR]: Improper values in printer serial number.\n", + f": Improper values in printer serial number.\n", ) self.config(cursor="") self.update_idletasks() return if not ser_num or "?" in ser_num: + self.status_text.insert(tk.END, '[ERROR]', "error") self.status_text.insert( tk.END, - f"[ERROR]: Cannot retrieve the printer serial number.\n", + f": Cannot retrieve the printer serial number.\n", ) self.config(cursor="") self.update_idletasks() return + self.status_text.insert(tk.END, '[INFO]', "info") self.status_text.insert( - tk.END, f"[INFO] Printer serial number: {ser_num}.\n" + tk.END, f" Printer serial number: {ser_num}.\n" ) self.ser_num_var.set(ser_num) self.config(cursor="") @@ -1352,6 +1489,7 @@ def get_mac_address(self, cursor=True): 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, '[ERROR]', "error") self.status_text.insert(tk.END, NO_CONF_ERROR) self.config(cursor="") self.update() @@ -1366,15 +1504,17 @@ def get_mac_address(self, cursor=True): self.update_idletasks() return if not mac_addr: + self.status_text.insert(tk.END, '[ERROR]', "error") self.status_text.insert( tk.END, - f"[ERROR]: Cannot retrieve the printer WiFi MAC address.\n", + f": Cannot retrieve the printer WiFi MAC address.\n", ) self.config(cursor="") self.update_idletasks() return + self.status_text.insert(tk.END, '[INFO]', "info") self.status_text.insert( - tk.END, f"[INFO] Printer WiFi MAC address: {mac_addr}.\n" + tk.END, f" Printer WiFi MAC address: {mac_addr}.\n" ) self.mac_addr_var.set(mac_addr) self.config(cursor="") @@ -1388,15 +1528,17 @@ def get_current_eeprom_values(self, values, label): ) ) if org_values: + self.status_text.insert(tk.END, '[NOTE]', "note") self.status_text.insert( tk.END, - f"[NOTE] Current EEPROM values for {label}: {org_values}.\n" + f" Current EEPROM values for {label}: {org_values}.\n" ) else: + self.status_text.insert(tk.END, '[ERROR]', "error") self.status_text.insert( tk.END, - f'[ERROR] Cannot read EEPROM values for "{label}"' - ': invalid printer model selected.\n' + f' Cannot read EEPROM values for "{label}"' + f': invalid printer model selected: {self.printer.model}.\n' ) self.config(cursor="") self.update_idletasks() @@ -1421,6 +1563,7 @@ def set_po_mins(self, cursor=True): 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, '[ERROR]', "error") self.status_text.insert(tk.END, NO_CONF_ERROR) self.config(cursor="") self.update_idletasks() @@ -1428,9 +1571,10 @@ def set_po_mins(self, cursor=True): if not self.printer: return if not self.printer.parm.get("stats", {}).get("Power off timer"): + self.status_text.insert(tk.END, '[ERROR]', "error") self.status_text.insert( tk.END, - f"[ERROR]: Missing 'Power off timer' in configuration\n", + f": Missing 'Power off timer' in configuration\n", ) self.config(cursor="") self.update_idletasks() @@ -1439,8 +1583,9 @@ def set_po_mins(self, cursor=True): self.config(cursor="") self.update_idletasks() if not po_timer.isnumeric(): + self.status_text.insert(tk.END, '[ERROR]', "error") self.status_text.insert( - tk.END, "[ERROR] Please Use a valid value for minutes.\n" + tk.END, " Please Use a valid value for minutes.\n" ) self.config(cursor="") self.update_idletasks() @@ -1458,17 +1603,19 @@ def set_po_mins(self, cursor=True): self.config(cursor="") self.update_idletasks() return + self.status_text.insert(tk.END, '[INFO]', "info") self.status_text.insert( tk.END, - f"[INFO] Set Power off timer: {po_timer} minutes. Restarting" + f" Set Power off timer: {po_timer} minutes. Restarting" " the printer is required for this change to take effect.\n" ) response = messagebox.askyesno(*CONFIRM_MESSAGE, default='no') if response: try: self.printer.write_poweroff_timer(int(po_timer)) + self.status_text.insert(tk.END, '[INFO]', "info") self.status_text.insert( - tk.END, "[INFO] Update operation completed.\n" + tk.END, " Update operation completed.\n" ) except Exception as e: self.handle_printer_error(e) @@ -1500,8 +1647,9 @@ def set_mac_address(self, cursor=True): if not mac or not self.validate_mac_address( self.mac_addr_var.get() ): + self.status_text.insert(tk.END, '[ERROR]', "error") self.status_text.insert( - tk.END, "[ERROR] Please Use a valid MAC address.\n" + tk.END, " Please Use a valid MAC address.\n" ) self.config(cursor="") self.update_idletasks() @@ -1533,9 +1681,10 @@ def set_mac_address(self, cursor=True): self.config(cursor="") self.update_idletasks() return + self.status_text.insert(tk.END, '[INFO]', "info") self.status_text.insert( tk.END, - f"[INFO] Set WiFi MAC Address: {self.mac_addr_var.get()}.\n" + f" Set WiFi MAC Address: {self.mac_addr_var.get()}.\n" ) response = messagebox.askyesno(*CONFIRM_MESSAGE, default='no') if not response: @@ -1545,9 +1694,10 @@ def set_mac_address(self, cursor=True): self.config(cursor="") self.update_idletasks() return + self.status_text.insert(tk.END, '[INFO]', "info") self.status_text.insert( tk.END, - "[INFO] Changing the WiFi MAC address of the printer. Restarting" + " Changing the WiFi MAC address of the printer. Restarting" " the printer is required for this change to take effect.\n" ) ret = None @@ -1560,12 +1710,14 @@ def set_mac_address(self, cursor=True): except Exception as e: self.handle_printer_error(e) if ret: + self.status_text.insert(tk.END, '[INFO]', "info") self.status_text.insert( - tk.END, "[INFO] Update operation completed.\n" + tk.END, " Update operation completed.\n" ) else: + self.status_text.insert(tk.END, '[ERROR]', "error") self.status_text.insert( - tk.END, f"[ERROR] Write operation failed.\n" + tk.END, f" Write operation failed.\n" ) self.config(cursor="") self.update_idletasks() @@ -1581,6 +1733,7 @@ def set_ser_number(self, cursor=True): 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, '[ERROR]', "error") self.status_text.insert(tk.END, NO_CONF_ERROR) self.config(cursor="") self.update_idletasks() @@ -1590,8 +1743,9 @@ def set_ser_number(self, cursor=True): if not self.ser_num_var.get() or not self.validate_ser_number( self.ser_num_var.get() ): + self.status_text.insert(tk.END, '[ERROR]', "error") self.status_text.insert( - tk.END, "[ERROR] Please Use a valid serial number.\n" + tk.END, " Please Use a valid serial number.\n" ) self.config(cursor="") self.update_idletasks() @@ -1612,9 +1766,10 @@ def set_ser_number(self, cursor=True): self.config(cursor="") self.update_idletasks() return + self.status_text.insert(tk.END, '[INFO]', "info") self.status_text.insert( tk.END, - f"[INFO] Set Printer Serial Number: {self.ser_num_var.get()}.\n" + f" Set Printer Serial Number: {self.ser_num_var.get()}.\n" ) response = messagebox.askyesno(*CONFIRM_MESSAGE, default='no') if not response: @@ -1624,9 +1779,10 @@ def set_ser_number(self, cursor=True): self.config(cursor="") self.update_idletasks() return + self.status_text.insert(tk.END, '[INFO]', "info") self.status_text.insert( tk.END, - "[INFO] Changing the serial number of the printer. Restarting" + " Changing the serial number of the printer. Restarting" " the printer is required for this change to take effect.\n" ) ret = None @@ -1639,12 +1795,14 @@ def set_ser_number(self, cursor=True): except Exception as e: self.handle_printer_error(e) if ret: + self.status_text.insert(tk.END, '[INFO]', "info") self.status_text.insert( - tk.END, "[INFO] Update operation completed.\n" + tk.END, " Update operation completed.\n" ) else: + self.status_text.insert(tk.END, '[ERROR]', "error") self.status_text.insert( - tk.END, f"[ERROR] Write operation failed.\n" + tk.END, f" Write operation failed.\n" ) self.config(cursor="") self.update_idletasks() @@ -1660,6 +1818,7 @@ def get_ti_date(self, cursor=True): 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, '[ERROR]', "error") self.status_text.insert(tk.END, NO_CONF_ERROR) self.config(cursor="") self.update_idletasks() @@ -1667,21 +1826,31 @@ def get_ti_date(self, cursor=True): if not self.printer: return if not self.printer.parm.get("stats", {}).get("First TI received time"): + self.status_text.insert(tk.END, '[ERROR]', "error") self.status_text.insert( tk.END, - f"[ERROR]: Missing 'First TI received time' in configuration\n", + f": Missing 'First TI received time' in configuration\n", ) self.config(cursor="") self.update_idletasks() return try: - date_string = datetime.strptime( - self.printer.stats( - )["stats"]["First TI received time"], "%d %b %Y" - ).strftime("%Y-%m-%d") + d = self.printer.stats()["stats"]["First TI received time"] + if d == "?": + self.status_text.insert(tk.END, '[ERROR]', "error") + self.status_text.insert( + tk.END, + ": No data from 'First TI received time'." + " Check printer configuration.\n", + ) + self.config(cursor="") + self.update_idletasks() + return + date_string = datetime.strptime(d, "%d %b %Y").strftime("%Y-%m-%d") + self.status_text.insert(tk.END, '[INFO]', "info") self.status_text.insert( tk.END, - f"[INFO] First TI received time (YYYY-MM-DD): {date_string}.\n", + f" First TI received time (YYYY-MM-DD): {date_string}.\n", ) self.date_entry.set_date(date_string) except Exception as e: @@ -1701,6 +1870,7 @@ def set_ti_date(self, cursor=True): 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, '[ERROR]', "error") self.status_text.insert(tk.END, NO_CONF_ERROR) self.config(cursor="") self.update_idletasks() @@ -1708,9 +1878,10 @@ def set_ti_date(self, cursor=True): if not self.printer: return if not self.printer.parm.get("stats", {}).get("First TI received time"): + self.status_text.insert(tk.END, '[ERROR]', "error") self.status_text.insert( tk.END, - f"[ERROR]: Missing 'First TI received time' in configuration\n", + f": Missing 'First TI received time' in configuration\n", ) self.config(cursor="") self.update_idletasks() @@ -1729,9 +1900,10 @@ def set_ti_date(self, cursor=True): self.config(cursor="") self.update_idletasks() return + self.status_text.insert(tk.END, '[INFO]', "info") self.status_text.insert( tk.END, - f"[INFO] Set 'First TI received time' (YYYY-MM-DD) to: " + f" Set 'First TI received time' (YYYY-MM-DD) to: " f"{date_string.strftime('%Y-%m-%d')}.\n", ) response = messagebox.askyesno(*CONFIRM_MESSAGE, default='no') @@ -1740,8 +1912,9 @@ def set_ti_date(self, cursor=True): self.printer.write_first_ti_received_time( date_string.year, date_string.month, date_string.day ) + self.status_text.insert(tk.END, '[INFO]', "info") self.status_text.insert( - tk.END, "[INFO] Update operation completed.\n" + tk.END, " Update operation completed.\n" ) except Exception as e: self.handle_printer_error(e) @@ -1809,9 +1982,10 @@ def printer_status(self, cursor=True): model = self.model_var.get() ip_address = self.ip_var.get() if not self._is_valid_ip(ip_address): + self.status_text.insert(tk.END, '[ERROR]', "error") self.status_text.insert( tk.END, - "[ERROR] Please enter a valid IP address, or " + " Please enter a valid IP address, or " "press 'Detect Printers'.\n" ) self.config(cursor="") @@ -1852,15 +2026,17 @@ def printer_status(self, cursor=True): def reset_printer_model(self): self.show_status_text_view() if self.model_var.get(): + self.status_text.insert(tk.END, '[ERROR]', "error") self.status_text.insert( tk.END, - '[ERROR]: Unknown printer model ' + ': Unknown printer model ' f'"{self.model_var.get()}"\n', ) else: + self.status_text.insert(tk.END, '[ERROR]', "error") self.status_text.insert( tk.END, - '[ERROR]: Select a valid printer model.\n' + ': Select a valid printer model.\n' ) self.config(cursor="") self.update() @@ -1883,7 +2059,7 @@ def printer_config(self, cursor=True): return try: self.text_dump = black.format_str( # used by Copy All - f'"{printer.model}" + " configuration": ' + repr(printer.parm), + f'"{printer.model}": ' + repr(printer.parm), mode=self.mode ) self.show_treeview() @@ -1941,7 +2117,11 @@ def key_values(self, cursor=True): "Hex write sequence": printer.caesar( printer.parm.get("write_key", b''), hex=True - ).upper() + ).upper(), + "OID - Read address 0": + printer.eeprom_oid_read_address(0), + "OID - Write value 0 to address 0": + printer.eeprom_oid_write_address(0, 0), } self.text_dump = black.format_str( # used by Copy All @@ -2039,14 +2219,16 @@ def get_values(addresses): self.update_idletasks() return if values: + self.status_text.insert(tk.END, '[INFO]', "info") self.status_text.insert( tk.END, - f"[INFO] EEPROM values: {values}.\n" + f" EEPROM values: {values}.\n" ) else: + self.status_text.insert(tk.END, '[ERROR]', "error") self.status_text.insert( tk.END, - f'[ERROR] Cannot read EEPROM values' + f' Cannot read EEPROM values for addresses "{addresses}"' ': invalid printer model selected.\n' ) self.config(cursor="") @@ -2055,6 +2237,7 @@ def get_values(addresses): 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, '[ERROR]', "error") self.status_text.insert(tk.END, NO_CONF_ERROR) self.config(cursor="") self.update_idletasks() @@ -2078,6 +2261,7 @@ def run_detection(): current_log_level = logging.getLogger().getEffectiveLevel() logging.getLogger().setLevel(logging.ERROR) if not self._is_valid_ip(ip_address): + self.status_text.insert(tk.END, '[ERROR]', "error") self.status_text.insert(tk.END, NO_CONF_ERROR) logging.getLogger().setLevel(current_log_level) self.config(cursor="") @@ -2091,9 +2275,10 @@ def run_detection(): self.printer.parm = {'read_key': None} # Detect the read_key + self.status_text.insert(tk.END, '[INFO]', "info") self.status_text.insert( tk.END, - f"[INFO] Detecting the read_key...\n" + f" Detecting the read_key...\n" ) self.update_idletasks() read_key = None @@ -2106,12 +2291,14 @@ def run_detection(): self.update_idletasks() return if read_key: + self.status_text.insert(tk.END, '[INFO]', "info") self.status_text.insert( - tk.END, f"[INFO] Detected read_key: {read_key}.\n" + tk.END, f" Detected read_key: {read_key}.\n" ) else: + self.status_text.insert(tk.END, '[ERROR]', "error") self.status_text.insert( - tk.END, f"[ERROR] Could not detect read_key.\n" + tk.END, f" Could not detect read_key.\n" ) logging.getLogger().setLevel(current_log_level) self.config(cursor="") @@ -2130,17 +2317,19 @@ def run_detection(): and self.printer.parm['read_key'] != read_key ): if self.printer.parm['read_key']: + self.status_text.insert(tk.END, '[ERROR]', "error") self.status_text.insert( tk.END, - f"[ERROR] You selected a model with the wrong read_key " + f" You selected a model with the wrong read_key " f"{self.printer.parm['read_key']} instead of " f"{read_key}. Using the detected one to go on.\n" ) self.printer.PRINTER_CONFIG[DETECTED] = {'read_key': read_key} self.printer.parm = self.printer.PRINTER_CONFIG[DETECTED] + self.status_text.insert(tk.END, '[INFO]', "info") self.status_text.insert( tk.END, - f"[INFO] Detecting the serial number...\n" + f" Detecting the serial number...\n" ) try: hex_bytes, matches = self.printer.find_serial_number( @@ -2153,23 +2342,26 @@ def run_detection(): self.update_idletasks() return if not matches: + self.status_text.insert(tk.END, '[ERROR]', "error") self.status_text.insert( tk.END, - f"[ERROR] Cannot detect the serial number.\n" + f" Cannot detect the serial number.\n" ) left_ser_num = None for match in matches: tmp_ser_num = match.group() if left_ser_num is not None and tmp_ser_num != left_ser_num: + self.status_text.insert(tk.END, '[ERROR]', "error") self.status_text.insert( tk.END, - "[ERROR] More than one pattern appears to be" + " More than one pattern appears to be" " a serial number with different values:\n" ) for match in matches: + self.status_text.insert(tk.END, '[ERROR]', "error") self.status_text.insert( tk.END, - f'[ERROR] - found pattern "{match.group()}"' + f' - found pattern "{match.group()}"' f" at address {match.start()}\n" ) left_ser_num = None @@ -2183,24 +2375,27 @@ def run_detection(): serial_number_address, serial_number_address + len_ser_num ) + self.status_text.insert(tk.END, '[INFO]', "info") self.status_text.insert( tk.END, - f'[INFO] Detected serial number "{serial_number}"' + f' Detected serial number "{serial_number}"' f" at address {serial_number_address}.\n" ) last_ser_num_addr = serial_number_address + len_ser_num - 1 last_ser_num_value = int(hex_bytes[last_ser_num_addr], 16) + self.status_text.insert(tk.END, '[NOTE]', "note") self.status_text.insert( tk.END, - f"[NOTE] Current EEPROM value for the last byte of the" + f" Current EEPROM value for the last byte of the" f" serial number:" f" {last_ser_num_addr}: {last_ser_num_value}.\n" ) if last_ser_num_addr is None: + self.status_text.insert(tk.END, '[ERROR]', "error") self.status_text.insert( tk.END, - "[ERROR] Could not detect serial number.\n" + " Could not detect serial number.\n" ) logging.getLogger().setLevel(current_log_level) self.config(cursor="") @@ -2211,9 +2406,10 @@ def run_detection(): or self.printer.parm['serial_number'] != serial_number_range ): if 'serial_number' in self.printer.parm: + self.status_text.insert(tk.END, '[ERROR]', "error") self.status_text.insert( tk.END, - f"[ERROR] The serial number addresses" + f" The serial number addresses" f" {self.printer.parm['serial_number']} of the" f" selected printer is different from the detected" f" one {serial_number_range}," @@ -2229,9 +2425,10 @@ def run_detection(): write_key_list = self.printer.write_key_list(read_key) # Validate the write_key against any of the known values + self.status_text.insert(tk.END, '[INFO]', "info") self.status_text.insert( tk.END, - "[INFO] Detecting the write_key," + " Detecting the write_key," " do not power off the printer now...\n" ) old_write_key = self.printer.parm.get('write_key') @@ -2247,9 +2444,10 @@ def run_detection(): ) assert valid is not None except AssertionError: + self.status_text.insert(tk.END, '[ERROR]', "error") self.status_text.insert( tk.END, - "[ERROR] Write operation failed. Check whether the" + " Write operation failed. Check whether the" " serial number is changed and restore it manually.\n" ) self.printer.parm['write_key'] = old_write_key @@ -2265,8 +2463,9 @@ def run_detection(): self.update_idletasks() return if valid is None: + self.status_text.insert(tk.END, '[ERROR]', "error") self.status_text.insert( - tk.END, "[ERROR] Operation interrupted with errors.\n" + tk.END, " Operation interrupted with errors.\n" ) self.printer.parm['write_key'] = old_write_key logging.getLogger().setLevel(current_log_level) @@ -2278,14 +2477,16 @@ def run_detection(): found_write_key = write_key self.printer.parm['write_key'] = old_write_key + self.status_text.insert(tk.END, '[INFO]', "info") self.status_text.insert( - tk.END, f"[INFO] Detected write_key: {found_write_key}\n" + tk.END, f" Detected write_key: {found_write_key}\n" ) if not old_write_key or old_write_key != found_write_key: if old_write_key and old_write_key != found_write_key: + self.status_text.insert(tk.END, '[ERROR]', "error") self.status_text.insert( tk.END, - f"[ERROR] The selected write key {old_write_key}" + f" The selected write key {old_write_key}" f" is different from the detected one, which will" f" be used to go on.\n" ) @@ -2317,39 +2518,45 @@ def run_detection(): ): rwk_kist.append(p) if rk_kist: + self.status_text.insert(tk.END, '[INFO]', "info") self.status_text.insert( tk.END, - f"[INFO] Models with same read_key: {rk_kist}\n" + f" Models with same read_key: {rk_kist}\n" ) if wk_kist: + self.status_text.insert(tk.END, '[INFO]', "info") self.status_text.insert( tk.END, - f"[INFO] Models with same write_key: {wk_kist}\n" + f" Models with same write_key: {wk_kist}\n" ) if rwk_kist: + self.status_text.insert(tk.END, '[INFO]', "info") self.status_text.insert( tk.END, - f"[INFO] Models with same access keys: {rwk_kist}\n" + f" Models with same access keys: {rwk_kist}\n" ) if ( DETECTED in self.printer.PRINTER_CONFIG and self.printer.PRINTER_CONFIG[DETECTED] ): + self.status_text.insert(tk.END, '[INFO]', "info") self.status_text.insert( tk.END, - f'[INFO] Found data: ' + f' Found data: ' f'{self.printer.PRINTER_CONFIG[DETECTED]}.\n' ) self.detect_configuration_button.state(["!disabled"]) + self.status_text.insert(tk.END, '[INFO]', "info") self.status_text.insert( - tk.END, "[INFO] Detect operation completed.\n" + tk.END, " Detect operation completed.\n" ) break if not found_write_key: + self.status_text.insert(tk.END, '[ERROR]', "error") self.status_text.insert( tk.END, - "[ERROR] Unable to detect the write key by validating" + " Unable to detect the write key by validating" " against any of the known ones.\n" ) logging.getLogger().setLevel(current_log_level) @@ -2360,6 +2567,7 @@ def run_detection(): 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, '[ERROR]', "error") self.status_text.insert(tk.END, NO_CONF_ERROR) return response = messagebox.askyesno( @@ -2373,9 +2581,10 @@ def run_detection(): default='no' ) if response: + self.status_text.insert(tk.END, '[INFO]', "info") self.status_text.insert( tk.END, - f"[INFO] Starting the access key detection, please wait for many minutes...\n" + f" Starting the access key detection, please wait for many minutes...\n" ) self.config(cursor="watch") self.update() @@ -2398,6 +2607,7 @@ def web_interface(self, cursor=True): 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, '[ERROR]', "error") self.status_text.insert(tk.END, NO_CONF_ERROR) self.config(cursor="") self.update() @@ -2407,16 +2617,19 @@ def web_interface(self, cursor=True): try: ret = webbrowser.open(ip_address) if ret: + self.status_text.insert(tk.END, '[INFO]', "info") self.status_text.insert( - tk.END, f"[INFO] The browser is being opened.\n" + tk.END, f" The browser is being opened.\n" ) else: + self.status_text.insert(tk.END, '[ERROR]', "error") self.status_text.insert( - tk.END, f"[ERROR] Cannot open browser.\n" + tk.END, f" Cannot open browser.\n" ) except Exception as e: + self.status_text.insert(tk.END, '[ERROR]', "error") self.status_text.insert( - tk.END, f"[ERROR] Cannot open web browser: {e}\n" + tk.END, f" Cannot open web browser: {e}\n" ) finally: self.config(cursor="") @@ -2445,15 +2658,17 @@ def detect_sequence(eeprom, sequence): 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, '[ERROR]', "error") self.status_text.insert(tk.END, NO_CONF_ERROR) self.config(cursor="") self.update() return if not self.printer: return + self.status_text.insert(tk.END, '[INFO]', "info") self.status_text.insert( tk.END, - f"[INFO] Reading Printer SNMP values...\n" + f" Reading Printer SNMP values...\n" ) try: stats = self.printer.stats() @@ -2463,17 +2678,19 @@ def detect_sequence(eeprom, sequence): self.update_idletasks() return False if not "snmp_info" in stats: + self.status_text.insert(tk.END, '[ERROR]', "error") self.status_text.insert( tk.END, - '[ERROR] No SNMP values could be found.\n' + ' No SNMP values could be found.\n' ) self.update() self.config(cursor="") self.update_idletasks() return False + self.status_text.insert(tk.END, '[INFO]', "info") self.status_text.insert( tk.END, - f"[INFO] Reading EEPROM values, please wait for some minutes...\n" + f" Reading EEPROM values, please wait for some minutes...\n" ) self.update() try: @@ -2487,10 +2704,10 @@ def detect_sequence(eeprom, sequence): ) } if not eeprom or eeprom == {0: None}: + self.status_text.insert(tk.END, '[ERROR]', "error") self.status_text.insert( tk.END, - '[ERROR] Cannot read EEPROM values' - ': invalid printer model selected.\n' + ' Cannot read EEPROM values: invalid printer model selected.\n' ) self.update() self.config(cursor="") @@ -2501,9 +2718,10 @@ def detect_sequence(eeprom, sequence): self.config(cursor="") self.update_idletasks() return False + self.status_text.insert(tk.END, '[INFO]', "info") self.status_text.insert( tk.END, - f"[INFO] Analyzing EEPROM values...\n" + f" Analyzing EEPROM values...\n" ) self.update() @@ -2604,9 +2822,10 @@ def detect_sequence(eeprom, sequence): except Exception as e: self.handle_printer_error(e) finally: + self.status_text.insert(tk.END, '[INFO]', "info") self.status_text.insert( tk.END, - f"[INFO] Operation completed.\n" + f" Operation completed.\n" ) self.update_idletasks() self.config(cursor="") @@ -2700,8 +2919,9 @@ def write_eeprom_values(dict_addr_val): return False except Exception as e: self.handle_printer_error(e) + self.status_text.insert(tk.END, '[INFO]', "info") self.status_text.insert( - tk.END, f"[INFO] Write EEPROM completed.\n" + tk.END, f" Write EEPROM completed.\n" ) self.config(cursor="") self.update_idletasks() @@ -2709,14 +2929,16 @@ def write_eeprom_values(dict_addr_val): 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, '[ERROR]', "error") 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, '[INFO]', "info") self.status_text.insert( - tk.END, f"[INFO] Going to write EEPROM: {dict_addr_val}.\n" + tk.END, f" Going to write EEPROM: {dict_addr_val}.\n" ) self.after(200, lambda: dialog_write_values(dict_addr_val)) @@ -2737,6 +2959,7 @@ def reset_waste_ink(self, cursor=True): or "read_key" not in self.printer.parm or "write_key" not in self.printer.parm ): + self.status_text.insert(tk.END, '[ERROR]', "error") self.status_text.insert(tk.END, NO_CONF_ERROR) self.config(cursor="") self.update_idletasks() @@ -2770,9 +2993,10 @@ def reset_waste_ink(self, cursor=True): if response: try: self.printer.reset_waste_ink_levels() + self.status_text.insert(tk.END, '[INFO]', "info") self.status_text.insert( tk.END, - "[INFO] Waste ink levels have been reset." + " Waste ink levels have been reset." " Perform a power cycle of the printer now.\n" ) except Exception as e: @@ -2786,8 +3010,9 @@ def reset_waste_ink(self, cursor=True): def start_detect_printers(self): self.show_status_text_view() + self.status_text.insert(tk.END, '[INFO]', "info") self.status_text.insert( - tk.END, "[INFO] Detecting printers... (this might take a while)\n" + tk.END, " Detecting printers... (this might take a while)\n" ) # run printer detection in new thread, as it can take a while @@ -2809,9 +3034,10 @@ def detect_printers_thread(self, cursor=True): ) if len(printers) > 0: if len(printers) == 1: + self.status_text.insert(tk.END, '[INFO]', "info") self.status_text.insert( tk.END, - f"[INFO] Found printer '{printers[0]['name']}' " + f" Found printer '{printers[0]['name']}' " f"at {printers[0]['ip']} " f"(hostname: {printers[0]['hostname']})\n", ) @@ -2824,23 +3050,27 @@ def detect_printers_thread(self, cursor=True): self.model_var.set(model) break if self.model_var.get() == "": + self.status_text.insert(tk.END, '[ERROR]', "error") self.status_text.insert( tk.END, - f'[ERROR] Printer model unknown.\n' + f' Printer model unknown.\n' ) self.model_var.set("") else: + self.status_text.insert(tk.END, '[INFO]', "info") self.status_text.insert( - tk.END, f"[INFO] Found {len(printers)} printers:\n" + tk.END, f" Found {len(printers)} printers:\n" ) for printer in printers: + self.status_text.insert(tk.END, '[INFO]', "info") self.status_text.insert( tk.END, - f"[INFO] {printer['name']} found at {printer['ip']}" + f" {printer['name']} found at {printer['ip']}" f" (hostname: {printer['hostname']})\n", ) else: - self.status_text.insert(tk.END, "[WARN] No printers found.\n") + self.status_text.insert(tk.END, '[WARN]', "warn") + self.status_text.insert(tk.END, " No printers found.\n") except Exception as e: self.handle_printer_error(e) finally: @@ -2980,8 +3210,9 @@ def print_items(self, text): ip_address = self.ip_var.get() if not self._is_valid_ip(ip_address): self.show_status_text_view() + self.status_text.insert(tk.END, '[ERROR]', "error") self.status_text.insert( - tk.END, f"[ERROR] Missing IP address or printer host name.\n" + tk.END, f" Missing IP address or printer host name.\n" ) return try: @@ -2996,8 +3227,9 @@ def print_items(self, text): ) except Exception as e: self.show_status_text_view() + self.status_text.insert(tk.END, '[ERROR]', "error") self.status_text.insert( - tk.END, f"[ERROR] Printer is unreachable or offline.\n" + tk.END, f" Printer is unreachable or offline.\n" )