diff --git a/README.md b/README.md index f7c2c28..5031442 100644 --- a/README.md +++ b/README.md @@ -185,7 +185,7 @@ An alternative way to create the executable file named *epson_print_conf.exe* fr pyinstaller --onefile ui.py --name epson_print_conf --hidden-import babel.numbers --windowed ``` -A file named *gui.py* is also included (similar to *ui.py*), which automatically loads a previously created configuration file that has to be named *printer_conf.pickle*, merging it with the program configuration. To build the executable program with this file instead of the default *ui.py*, run the following command: +A file named *gui.py* is also included (similar to *ui.py*), which automatically loads a previously created configuration file that has to be named *printer_conf.pickle*, merging it with the program configuration. (See below the *parse_devices.py* utility.) To build the executable program with this file instead of the default *ui.py*, run the following command: ```bash pip install pyinstaller # if not yet installed @@ -200,7 +200,7 @@ When the build operation is completed, you can run the *epson_print_conf.exe* fi ### parse_devices.py -Within an [issue](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 reports an extensive XML database of Epson model features. +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. The program *parse_devices.py* transforms this XML DB into the dictionary that *epson_print_conf.py* can use. @@ -211,7 +211,7 @@ curl -o devices.xml https://codeberg.org/attachments/147f41a3-a6ea-45f6-8c2a-25b python3 parse_devices.py -i -m XP-205 ``` -After generating the related printer configuration, *epson_print_conf.py* shall be manually edited to copy/paste the output of *parse_devices.py* within its PRINTER_CONFIG dictionary. Alternatively, the program is able to create a *pickle* configuration file, which the other programs can load. +After generating the related printer configuration, *epson_print_conf.py* shall be manually edited to copy/paste the output of *parse_devices.py* within its PRINTER_CONFIG dictionary. Alternatively, the program is able to create a *pickle* configuration file (check the `-p` lowercase option), which the other programs can load (with the `-P` uppercase option and in addition with the optional `-O` flag). The `-m` option is optional and is used to filter the printer model in scope. If the produced output is not referred to the target model, use part of the model name as a filter (e.g., only the digits, like `parse_devices.py -i -m 315`) and select the appropriate model from the output. @@ -264,9 +264,9 @@ Output example: ### Other utilities ``` -import epson_print_conf +from epson_print_conf import EpsonPrinter import pprint -printer = epson_print_conf.EpsonPrinter() +printer = EpsonPrinter() # Decode write_key: printer.reverse_caesar(bytes.fromhex("48 62 7B 62 6F 6A 62 2B")) # last 8 bytes @@ -288,12 +288,12 @@ ink_level = int("".join(reversed(byte_sequence.split())), 16) waste_percent = round(ink_level / divider, 2) # Print the read key sequence in byte and hex formats: -printer = epson_print_conf.EpsonPrinter(model="ET-2700") +printer = EpsonPrinter(model="ET-2700") '.'.join(str(x) for x in printer.parm['read_key']) " ".join('{0:02x}'.format(x) for x in printer.parm['read_key']) # Print the write key sequence in byte and hex formats: -printer = epson_print_conf.EpsonPrinter(model="ET-2700") +printer = EpsonPrinter(model="ET-2700") printer.caesar(printer.parm['write_key']) printer.caesar(printer.parm['write_key'], hex=True).upper() @@ -311,9 +311,9 @@ for key, value in printer.parm["raw_waste_reset"].items(): Generic query of the status of the printer (regardless of the model): ``` -import epson_print_conf +from epson_print_conf import EpsonPrinter import pprint -printer = epson_print_conf.EpsonPrinter(hostname="192.168.1.87") +printer = EpsonPrinter(hostname="192.168.1.87") pprint.pprint(printer.status_parser(printer.snmp_mib("1.3.6.1.4.1.1248.1.2.2.1.1.1.4.1")[1])) ``` @@ -393,12 +393,12 @@ ValueError ### Sample ```python -import epson_print_conf +from epson_print_conf import EpsonPrinter import logging logging.basicConfig(level=logging.DEBUG, format="%(message)s") # if logging is needed -printer = epson_print_conf.EpsonPrinter( +printer = EpsonPrinter( model="XP-205", hostname="192.168.1.87") if not printer.parm: diff --git a/parse_devices.py b/parse_devices.py index 7b724e3..218bb7a 100644 --- a/parse_devices.py +++ b/parse_devices.py @@ -7,6 +7,11 @@ import textwrap from ui import get_printer_models +WASTE_LABELS = [ + "main_waste", "borderless_waste", "third_waste", "fourth_waste", + "fifth_waste", "sixth_waste" +] + def to_ranges(iterable): iterable = sorted(set(iterable)) for key, group in itertools.groupby(enumerate(iterable), @@ -46,10 +51,6 @@ def traverse_data(element, depth=0): def generate_config(config, traverse, add_fatal_errors, full, printer_model): - waste_string = [ - "main_waste", "borderless_waste", "third_waste", "fourth_waste", - "fifth_waste", "sixth_waste" - ] irc_pattern = [ r'Ink replacement counter %-% (\w+) % \((\w+)\)' ] @@ -183,7 +184,7 @@ def generate_config(config, traverse, add_fatal_errors, full, printer_model): waste["oids"] += text_to_bytes(counter.text) else: waste["oids"] = text_to_bytes(counter.text) - chars[waste_string[count]] = waste + chars[WASTE_LABELS[count]] = waste count += 1 if item.tag == "serial": chars["serial_number"] = text_to_bytes(item.text) @@ -223,8 +224,10 @@ def normalize_config( remove_invalid, expand_names, add_alias, + aggregate_alias, add_same_as, ): + logging.info("Number of configuration entries before removing invalid ones: %s", len(config)) # Remove printers without write_key or without read_key if remove_invalid: for base_key, base_items in config.copy().items(): @@ -236,6 +239,7 @@ def normalize_config( continue # Replace original names with converted names and add printers for all optional names + logging.info("Number of configuration entries before adding optional names: %s", len(config)) if expand_names: for key, items in config.copy().items(): printer_list = get_printer_models(key) @@ -247,6 +251,7 @@ def normalize_config( config[i] = items # Add aliases for same printer with different names and remove aliased printers + logging.info("Number of configuration entries before removing aliases: %s", len(config)) if add_alias: for base_key, base_items in config.copy().items(): found = False @@ -263,9 +268,9 @@ def normalize_config( config[base_key]["alias"].append(i) del config[key] - # Add "same-as" for almost same printer (IGNORED_KEYS) with different names - if add_same_as: - IGNORED_KEYS = ['write_key', 'read_key', 'alias', 'main_waste', 'borderless_waste'] + # Aggregate aliases + logging.info("Number of configuration entries before aggregating aliases: %s", len(config)) + if aggregate_alias: for base_key, base_items in config.copy().items(): found = False for key, items in config.copy().items(): @@ -273,27 +278,59 @@ def normalize_config( 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 + if base_key != key and equal_dicts(base_items, items, ["alias"]): # everything but the alias is the same + base_items["alias"] = sorted(list(set( + (base_items["alias"] if "alias" in base_items else []) + + (items["alias"] if "alias" in items else []) + + [key] + ))) + del config[key] + # Add "same-as" for almost same printer (IGNORED_KEYS) with different names + if add_same_as: + IGNORED_KEYS = [ # 'alias' must always be present + ['write_key', 'read_key', 'alias'], + ['write_key', 'read_key', 'alias'] + WASTE_LABELS, + ] + for ignored_keys in IGNORED_KEYS: + same_as_counter = 0 + 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 'same-as' not in base_key: + if equal_dicts(base_items, items, ignored_keys): # everything but the ignored keys is the same + # Rebuild the printer with only the ignored keys, then add the 'same-as' + config[base_key] = {} + for i in ignored_keys: + if i in base_items: + config[base_key][i] = base_items[i] + config[base_key]['same-as'] = key + same_as_counter += 1 + logging.info("Number of added 'same-as' entries with %s: %s", ignored_keys, same_as_counter) + + # Aggregate aliases + logging.info("Number of configuration entries before aggregating aliases: %s", len(config)) + if aggregate_alias: + 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 equal_dicts(base_items, items, ["alias"]): # everything but the alias is the same + base_items["alias"] = sorted(list(set( + (base_items["alias"] if "alias" in base_items else []) + + (items["alias"] if "alias" in items else []) + + [key] + ))) + del config[key] + + logging.info("Number of obtained configuration entries: %s", len(config)) return config def equal_dicts(a, b, ignore_keys): @@ -421,6 +458,13 @@ if __name__ == "__main__": action='store_true', help='Do not add aliases for same printer with different names and remove aliased printers' ) + parser.add_argument( + '-G', + '--no_aggregate_alias', + dest='no_aggregate_alias', + action='store_true', + help='Do not aggregate aliases of printers with same configuration' + ) parser.add_argument( '-S', '--no_same_as', @@ -456,8 +500,10 @@ if __name__ == "__main__": remove_invalid=not args.keep_invalid, expand_names=not args.keep_names, add_alias=not args.no_alias, + aggregate_alias=not args.no_aggregate_alias, add_same_as=not args.no_same_as, ) + if args.default_model: if "internal_data" not in normalized_config: normalized_config["internal_data"] = {} @@ -469,6 +515,10 @@ if __name__ == "__main__": if args.pickle: pickle.dump(normalized_config, args.pickle[0]) # serialize the list args.pickle[0].close() + + from epson_print_conf import EpsonPrinter + ep = EpsonPrinter(conf_dict=normalized_config, replace_conf=True) + logging.info("Number of expanded configuration entries: %s", len(ep.PRINTER_CONFIG)) quit() try: