From 096e9487e4493e6855f4c0873eb166b8c258b0ae Mon Sep 17 00:00:00 2001 From: Ircama Date: Tue, 6 Aug 2024 07:13:49 +0200 Subject: [PATCH] Improvements --- README.md | 15 +++++--- epson_print_conf.py | 90 ++++++++++++++++++++++++++++++++++++--------- parse_devices.py | 44 +++++++++++++++++++++- 3 files changed, 125 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index 5031442..e483666 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Epson Printer Configuration tool via SNMP (TCP/IP) ## Features -- Access the Epson printer via SNMP (TCP/IP; printer connected over Wi-Fi) +- Interface Epson printers via SNMP (TCP/IP, with printers connected over Wi-Fi) - Print the advanced status of the printer, with the possibility to restrict the query to specific information - Other inspection features: - Reset ink waste @@ -12,12 +12,12 @@ Epson Printer Configuration tool via SNMP (TCP/IP) - 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 -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 GUI has an autodiscovery function which finds the printer IP addresses and their model names; it 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 and convert an extensive Epson printer configuration DB. ## Installation @@ -218,8 +218,8 @@ The `-m` option is optional and is used to filter the printer model in scope. If Program usage: ``` -parse_devices.py [-h] [-m PRINTER_MODEL] [-l LINE_LENGTH] [-i] [-d] [-t] [-v] [-f] [-e] [-c CONFIG_FILE] - [-s DEFAULT_MODEL] [-a HOSTNAME] [-p PICKLE_FILE] [-I] [-N] [-A] [-S] +parse_devices.py [-h] [-m PRINTER_MODEL] [-l LINE_LENGTH] [-i] [-d] [-t] [-v] [-f] [-e] [-c CONFIG_FILE] [-s DEFAULT_MODEL] [-a HOSTNAME] [-p PICKLE_FILE] [-I] + [-N] [-A] [-G] [-S] [-M] optional arguments: -h, --help show this help message and exit @@ -244,7 +244,10 @@ optional arguments: -I, --keep_invalid Do not remove printers without write_key or without read_key -N, --keep_names Do not replace original names with converted names and add printers for all optional names -A, --no_alias Do not add aliases for same printer with different names and remove aliased printers + -G, --no_aggregate_alias + Do not aggregate aliases of printers with same configuration -S, --no_same_as Do not add "same-as" for similar printers with different names + -M, --no_maint_level Do not add "Maintenance required levelas" in "stats" Generate printer configuration from devices.xml ``` diff --git a/epson_print_conf.py b/epson_print_conf.py index e28393b..e08ffac 100644 --- a/epson_print_conf.py +++ b/epson_print_conf.py @@ -300,6 +300,52 @@ class EpsonPrinter: "Maintenance required level of 2nd waste ink counter": [55], }, }, + "ET-2810": { + "read_key": [74, 54], + "write_key": b"Maribaya", + "main_waste": {"oids": [48, 49, 47], "divider": 63.46}, + "borderless_waste": {"oids": [50, 51, 47], "divider": 34.16}, + "third_waste": {"oids": [252, 253, 254], "divider": 13.0}, + "raw_waste_reset": { + 48: 0, 49: 0, 47: 0, 52: 0, 53: 0, 54: 94, 50: 0, 51: 0, + 55: 94, 28: 0, 252: 0, 253: 0, 254: 0, 255: 94 + }, + "stats": { + "Maintenance required level of 1st waste ink counter": [54], + "Maintenance required level of 2nd waste ink counter": [55], + "Manual cleaning counter": [90], + "Timer cleaning counter": [89], + "Power cleaning counter": [91], + "Total print pass counter": [133, 132, 131, 130], + "Total print page counter": [776, 775, 774, 773], + "Total scan counter": [1843, 1842, 1841, 1840], + }, + "serial_number": range(1604, 1614), + "alias": ["ET-2811", "ET-2813", "ET-2815"], + }, + "ET-2812": { + "read_key": [74, 54], + "write_key": b"Maribaya", + "main_waste": {"oids": [48, 49, 47], "divider": 63.46}, + "borderless_waste": {"oids": [50, 51, 47], "divider": 34.16}, + "third_waste": {"oids": [252, 253, 254], "divider": 13.0}, + "raw_waste_reset": { + 48: 0, 49: 0, 47: 0, 52: 0, 53: 0, 54: 94, 50: 0, 51: 0, + 55: 94, 28: 0, 252: 0, 253: 0, 254: 0, 255: 94 + }, + "stats": { + "Maintenance required level of 1st waste ink counter": [54], + "Maintenance required level of 2nd waste ink counter": [55], + "Manual cleaning counter": [90], + "Timer cleaning counter": [89], + "Power cleaning counter": [91], + "Total print pass counter": [133, 132, 131, 130], + "Total print page counter": [776, 775, 774, 773], + "Total scan counter": [1843, 1842, 1841, 1840], + }, + "serial_number": range(1604, 1614), + "alias": ["ET-2814", "ET-2816", "ET-2818"], + }, "L3160": { "read_key": [151, 7], "write_key": b'Maribaya', @@ -325,6 +371,7 @@ class EpsonPrinter: "main_waste": {"oids": [24, 25, 30], "divider": 62.07}, "raw_waste_reset": {24: 0, 25: 0, 30: 0, 28: 0, 29: 0, 46: 94}, "stats": { + "Maintenance required level of waste ink counter": [46], "Manual cleaning counter": [147], "Timer cleaning counter": [149], "Power cleaning counter": [148], @@ -548,7 +595,7 @@ class EpsonPrinter: "ET-2550": { # Epson EcoTank ET-2550 "read_key": [0x44, 0x01], "write_key": b'Gazania*', - "main_waste": {"oids": [24, 25], "divider": 62.06}, + "main_waste": {"oids": [24, 25, 30], "divider": 62.06}, "serial_number": range(192, 202), "stats": { "Maintenance required level of waste ink counter": [46] @@ -561,17 +608,18 @@ class EpsonPrinter: # uncompleted }, "ET-2700": { # Epson EcoTank ET-2700 Series + "alias": ["ET-2701", "ET-2703", "ET-2705"], "read_key": [73, 8], "write_key": b'Arantifo', "serial_number": range(1604, 1614), - "main_waste": {"oids": [48, 49], "divider": 109.125}, - "second_waste": {"oids": [50, 51], "divider": 16.31}, + "main_waste": {"oids": [48, 49, 47], "divider": 109.13}, + "borderless_waste": {"oids": [50, 51, 47], "divider": 16.31}, "stats": { "Maintenance required level of 1st waste ink counter": [54], "Maintenance required level of 2nd waste ink counter": [55], "First TI received time": [9, 8], "Total print pass counter": [133, 132, 131, 130], - "Total print page counter": [776, 775, 774, 773], + "Total print page counter - rear feed": [755, 754, 753, 752], "Total scan counter": [1843, 1842, 1841, 1840], "Ink replacement counter - Black": [554], "Ink replacement counter - Cyan": [555], @@ -1685,26 +1733,34 @@ class EpsonPrinter: j.rjust(4), i[j].rjust(4), value.rjust(4) ) try: + missing = "Not available" return [ { k: v for k, v in { - # items which must exist - "ink_color": self.ink_color(int(i['IC1'], 16)), - "ink_quantity": int(i['IQT'], 16), + "ink_color": self.ink_color(int(i['IC1'], 16)) + if 'IC1' in i else missing, + "ink_quantity": int(i['IQT'], 16) + if 'IQT' in i else missing, "production_year": int(i['PDY'], 16) + ( - 1900 if int(i['PDY'], 16) > 80 else 2000), - "production_month": int(i['PDM'], 16), - # items which can be excluded - "data": i.get('SID').strip(), - "manufacturer": i.get('LOG').strip(), + 1900 if int(i['PDY'], 16) > 80 else 2000) + if 'PDY' in i else missing, + "production_month": int(i['PDM'], 16) + if 'PDM' in i else missing, + "data": i.get('SID').strip() + if 'SID' in i else missing, + "manufacturer": i.get('LOG').strip() + if 'LOG' in i else missing, }.items() if v # exclude items without value + } if 'II' in i and i['II'] == '03' else { + "Ink Information": f"Unknown {i['II']}" + if 'II' in i and i['II'] != '00' else missing } for i in cartridges ] except Exception as e: - logging.error("Cartridge value error: %s", e) + logging.error("Cartridge value error: %s.\n%s", e, cartridges) return None def dump_eeprom(self, start: int = 0, end: int = 0xFF): @@ -2329,13 +2385,13 @@ if __name__ == "__main__": args.query[0] in printer.parm["stats"]): ret = printer.get_stats(args.query[0]) if ret: - pprint(ret) + pprint(ret, width=100, compact=True) else: print("No information returned. Check printer definition.") elif args.query[0] in printer.MIB_INFO.keys(): ret = printer.get_snmp_info(args.query[0]) if ret: - pprint(ret) + pprint(ret, width=100, compact=True) else: print("No information returned. Check printer definition.") else: @@ -2346,7 +2402,7 @@ if __name__ == "__main__": if method in printer.list_methods: ret = printer.__getattribute__(method)() if ret: - pprint(ret) + pprint(ret, width=100, compact=True) else: print( "No information returned." @@ -2412,7 +2468,7 @@ if __name__ == "__main__": if args.info or not print_opt: ret = printer.stats() if ret: - pprint(ret) + pprint(ret, width=100, compact=True) else: print("No information returned. Check printer definition.") except TimeoutError as e: diff --git a/parse_devices.py b/parse_devices.py index 218bb7a..4013b7d 100644 --- a/parse_devices.py +++ b/parse_devices.py @@ -1,4 +1,5 @@ import sys +import os import logging import re import xml.etree.ElementTree as ET @@ -225,6 +226,7 @@ def normalize_config( expand_names, add_alias, aggregate_alias, + maint_level, add_same_as, ): logging.info("Number of configuration entries before removing invalid ones: %s", len(config)) @@ -330,15 +332,37 @@ def normalize_config( ))) del config[key] + if maint_level: + for key, items in config.copy().items(): + if "raw_waste_reset" in items: + n = 1 + for k, v in items["raw_waste_reset"].items(): + if v == 94: + if "stats" not in items: + items["stats"] = {} + m_key = f"Maintenance required level of {ordinal(n)} waste ink counter" + if m_key in items["stats"]: + print("ERROR key", key, m_key) + quit() + items["stats"][m_key] = [k] + n += 1 + logging.info("Number of obtained configuration entries: %s", len(config)) return config +def ordinal(n: int): + if 11 <= (n % 100) <= 13: + suffix = 'th' + else: + suffix = ['th', 'st', 'nd', 'rd', 'th'][min(n % 10, 4)] + return str(n) + suffix + 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__": +def main(): import argparse import pickle @@ -472,6 +496,13 @@ if __name__ == "__main__": action='store_true', help='Do not add "same-as" for similar printers with different names' ) + parser.add_argument( + '-M', + '--no_maint_level', + dest='no_maint_level', + action='store_true', + help='Do not add "Maintenance required levelas" in "stats"' + ) args = parser.parse_args() if args.debug: @@ -501,6 +532,7 @@ if __name__ == "__main__": expand_names=not args.keep_names, add_alias=not args.no_alias, aggregate_alias=not args.no_aggregate_alias, + maint_level=not args.no_maint_level, add_same_as=not args.no_same_as, ) @@ -532,3 +564,13 @@ if __name__ == "__main__": if args.indent: dict_str = textwrap.indent(dict_str, ' ') print(dict_str) + +if __name__ == "__main__": + try: + main() + except KeyboardInterrupt: + print('\nInterrupted.') + try: + sys.exit(130) + except SystemExit: + os._exit(130)