diff --git a/README.md b/README.md index 556b8f3..91ee1ac 100644 --- a/README.md +++ b/README.md @@ -12,33 +12,33 @@ cd epson_print_conf ## Usage ``` -usage: epson_print_conf.py [-h] -m MODEL -a HOSTNAME [-i] [-q QUERY] - [--reset_waste_ink] [--brute-force-read-key] [-d] - [-e DUMP_EEPROM DUMP_EEPROM DUMP_EEPROM] [--dry-run] - [--write-first-ti-received-time FTRT FTRT FTRT] +usage: epson_print_conf.py [-h] -m MODEL -a HOSTNAME [-i] [-q QUERY] [--reset_waste_ink] [--detect-key] [-d] + [-e DUMP_EEPROM DUMP_EEPROM] [--dry-run] [--write-first-ti-received-time FTRT FTRT FTRT] + [-R READ_EEPROM] [-W WRITE_EEPROM] [-S WS_TO_STRING] optional arguments: -h, --help show this help message and exit -m MODEL, --model MODEL - Printer model. Example: -m XP-205 (use ? to print all - supported models) + Printer model. Example: -m XP-205 (use ? to print all supported models) -a HOSTNAME, --address HOSTNAME Printer host name or IP address. (Example: -m 192.168.1.87) - -i, --info Print all available information and statistics (default - option) + -i, --info Print all available information and statistics (default option) -q QUERY, --query QUERY - Print specific information. (Use ? to list all available - queries) + Print specific information. (Use ? to list all available queries) --reset_waste_ink Reset all waste ink levels to 0 - --brute-force-read-key - Detect the read_key via brute force + --detect-key Detect the read_key via brute force -d, --debug Print debug information - -e DUMP_EEPROM DUMP_EEPROM DUMP_EEPROM, --eeprom-dump DUMP_EEPROM DUMP_EEPROM DUMP_EEPROM - Dump EEPROM (arguments: extension, start, stop) + -e DUMP_EEPROM DUMP_EEPROM, --eeprom-dump DUMP_EEPROM DUMP_EEPROM + Dump EEPROM (arguments: start, stop) --dry-run Dry-run change operations --write-first-ti-received-time FTRT FTRT FTRT - Change the first TI received time (arguments: year, month, - day) + Change the first TI received time (arguments: year, month, day) + -R READ_EEPROM, --read-eeprom READ_EEPROM + Read the values of a list of printer EEPROM addreses. Format is: address [, ...] + -W WRITE_EEPROM, --write-eeprom WRITE_EEPROM + Write related values to a list of printer EEPROM addresses. Format is: address: value [, ...] + -S WS_TO_STRING, --write-sequence-to-string WS_TO_STRING + Convert write sequence of numbers to string. Epson Printer Configuration accessed via SNMP (TCP/IP) ``` @@ -46,26 +46,32 @@ Epson Printer Configuration accessed via SNMP (TCP/IP) Examples: ``` -# Print informations +# Print informations (-i is not needed): python3 epson_print_conf.py -m XP-205 -a 192.168.1.87 -i -# Reset all waste ink levels to 0 +# Reset all waste ink levels to 0: python3 epson_print_conf.py -m XP-205 -a 192.168.1.87 --reset_waste_ink -# Change the first TI received time to 31 December 2016 +# Change the first TI received time to 31 December 2016: python3 epson_print_conf.py -m XP-205 -a 192.168.1.87 --write-first-ti-received-time 2016 12 31 -# Detect the read_key via brute force -python3 epson_print_conf.py -m XP-205 -a 192.168.1.87 --brute-force-read-key +# Detect the read_key via brute force: +python3 epson_print_conf.py -m XP-205 -a 192.168.1.87 --detect-key -# Only print status information +# Only print status information: python3 epson_print_conf.py -m XP-205 -a 192.168.1.87 -q printer_status -# Only print SNMP 'MAC Address' name +# Only print SNMP 'MAC Address' name: python3 epson_print_conf.py -m XP-205 -a 192.168.1.87 -q 'MAC Address' -# Only print SNMP 'Lang 5' name +# Only print SNMP 'Lang 5' name: python3 epson_print_conf.py -m XP-205 -a 192.168.1.87 -q 'Lang 5' + +# Write value 1 to the EEPROM address 173 and value 0xDE to the EEPROM address 172: +python3 epson_print_conf.py -m XP-205 -a 192.168.1.87 -W 173:1,172:0xde + +# Read EEPROM address 173 and EEPROM address 172: +python3 epson_print_conf.py -m XP-205 -a 192.168.1.87 -R 173,172 ``` ## API Interface @@ -103,7 +109,7 @@ ret = printer.session.get_stats() print("get_stats:", ret) printer.session.reset_waste_ink_levels() -printer.session.brute_force_read_key() +printer.session.detect_key() printer.session.write_first_ti_received_time(2000, 1, 2) ``` @@ -132,7 +138,7 @@ ValueError ('Yellow', '1L', 10), ('Yellow', '1S', 1)}, 'last_printer_fatal_errors': ['08', 'F1', 'F1', 'F1', 'F1', '10'], - 'printer_head_id': '...', + 'printer_head_id': '00AB73648E - 6464640E', 'printer_status': {'cancel_code': 'No request', 'ink_level': [(1, 89, 'Black'), (5, 77, 'Yellow'), @@ -147,7 +153,7 @@ ValueError 'replace_cartridge': '00000001', 'status': (4, 'Idle'), 'unknown': [('0x24', b'\x0f\x0f')]}, - 'serial_number': '...', + 'serial_number': 'QJFK135617', 'snmp_info': {'Descr': 'EPSON Built-in 11b/g/n Print Server', 'EEPS2 firmware version': 'EEPS2 Hard Ver.1.00 Firm Ver.0.50', 'Emulation 1': 'unknown', @@ -160,21 +166,24 @@ ValueError 'Lang 3': 'BDC', 'Lang 4': 'D4', 'Lang 5': 'ESCPR1', - 'MAC Address': '...', + 'MAC Address': 'A4-EE-57-DE-FD-03', 'Model': 'EPSON XP-205 207 Series', 'Model short': 'XP-205 207 Series', - 'Name': '...', + 'Name': 'EPSONDEFD03', + 'Print counter': '0', 'Print input': 'Auto sheet feeder', - 'UpTime': '00:32:38'}, - 'stats': {'First TI received time': '...', + 'UpTime': '00:00:30'}, + 'stats': {'First TI received time': '25 Dec 2012', 'Ink replacement cleaning counter': 78, + 'Maintenance required level of 1st waste ink counter': 94, + 'Maintenance required level of 2nd waste ink counter': 94, 'Manual cleaning counter': 129, 'Timer cleaning counter': 4, 'Total print page counter': 11504, 'Total print pass counter': 510136, 'Total scan counter': 4967}, 'waste_ink_levels': [90.45, 4.63]} -``` + ``` ## Resources @@ -204,7 +213,7 @@ 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. ### Development resources -epson-printer-snmp: https://github.com/Zedeldi/epson-printer-snmp +epson-printer-snmp: https://github.com/Zedeldi/epson-printer-snmp (and https://github.com/Zedeldi/epson-printer-snmp/issues/1) ReInkPy: https://codeberg.org/atufi/reinkpy/ diff --git a/epson_print_conf.py b/epson_print_conf.py index ebd7d0f..170b31b 100644 --- a/epson_print_conf.py +++ b/epson_print_conf.py @@ -12,6 +12,7 @@ import datetime import easysnmp # pip3 install easysnmp import time import textwrap +import ast class EpsonPrinter: @@ -22,8 +23,8 @@ class EpsonPrinter: "alias": ["XP-200", "XP-207"], "read_key": [25, 7], "write_key": b'Wakatobi', - "main_waste": {"oids": [24, 25], "divider": 73.5}, - "borderless_waste": {"oids": [26, 27], "divider": 34.34}, + "main_waste": {"oids": [24, 25, 30], "divider": 73.5}, + "borderless_waste": {"oids": [26, 27, 34], "divider": 34.34}, "serial_number": range(192, 202), "printer_head_id_h": range(122, 127), "printer_head_id_f": [136, 137, 138, 129], @@ -34,7 +35,17 @@ class EpsonPrinter: "Total print pass counter": [171, 170, 169, 168], "Total print page counter": [167, 166, 165, 164], "Total scan counter": [0x01d7, 0x01d6, 0x01d5, 0x01d4], - "First TI received time": [173, 172] + "First TI received time": [173, 172], + "Maintenance required level of 1st waste ink counter": [46], + "Maintenance required level of 2nd waste ink counter": [47], + }, + "raw_waste_reset": { + 24: 0, 25: 0, 30: 0, # Data of 1st counter + 28: 0, 29: 0, # another store of 1st counter + 46: 94, # Maintenance required level of 1st counter + 26: 0, 27: 0, 34: 0, # Data of 2nd counter + 47: 94, # Maintenance required level of 2st counter + 49: 0 # ? }, "ink_replacement_counters": { "Black": {"1B": 242, "1S": 208, "1L": 209}, @@ -78,7 +89,14 @@ class EpsonPrinter: "main_waste": {"oids": [20, 21], "divider": 196.5}, "borderless_waste": {"oids": [22, 23], "divider": 52.05}, "serial_number": range(192, 202), - # untested + "stats": { + "Maintenance required level of 1st waste ink counter": [60], + "Maintenance required level of 2nd waste ink counter": [61], + }, + "raw_waste_reset": { + 20: 0, 21: 0, 22: 0, 23: 0, 24: 0, 25: 0, 59: 0, 60: 94, 61: 94 + } + # to be completed }, "L355": { "read_key": [65, 9], @@ -87,6 +105,10 @@ class EpsonPrinter: "L3160": { "read_key": [151, 7], "write_key": b'Maribaya', + "stats": { + "Maintenance required level of 1st waste ink counter": [54], + "Maintenance required level of 2nd waste ink counter": [55], + }, "raw_waste_reset": { 48: 0, 49: 0, 47: 0, 52: 0, 53: 0, 54: 94, 50: 0, 51: 0, 55: 94, 28: 0 @@ -96,6 +118,10 @@ class EpsonPrinter: "L4160": { "read_key": [73, 8], "write_key": b'Arantifo', + "stats": { + "Maintenance required level of 1st waste ink counter": [54], + "Maintenance required level of 2nd waste ink counter": [55], + }, "raw_waste_reset": { 48: 0, 49: 0, 47: 0, 52: 0, 53: 0, 54: 94, 50: 0, 51: 0, 55: 94, 28: 0 @@ -105,28 +131,38 @@ class EpsonPrinter: "XP-315": { "read_key": [129, 8], "write_key": b'Wakatobi', + "main_waste": {"oids": [24, 25, 30], "divider": 196.5}, + "borderless_waste": {"oids": [26, 27, 34], "divider": 52.05}, + "stats": { + "Maintenance required level of 1st waste ink counter": [46], + "Maintenance required level of 2nd waste ink counter": [47], + }, + "raw_waste_reset": { + 24: 0, 25: 0, 30: 0, 28: 0, 29: 0, + 46: 94, 26: 0, 27: 0, 34: 0, 47: 94, 49: 0 + } # to be completed }, "XP-422": { "read_key": [85, 5], "write_key": b'Muscari.', # to be completed + "main_waste": {"oids": [24, 25, 30], "divider": 196.5}, + "borderless_waste": {"oids": [26, 27, 34], "divider": 52.05}, + "stats": { + "Maintenance required level of 1st waste ink counter": [46], + "Maintenance required level of 2nd waste ink counter": [47], + }, + "raw_waste_reset": { + 24: 0, 25: 0, 26: 0, 27: 0, 28: 0, + 29: 0, 30: 0, 34: 0, 46: 94, 47: 94, 49: 0 + } }, "XP-435": { "read_key": [133, 5], "write_key": b'Polyxena', # to be completed }, - "XP-510": { - "read_key": [121, 4], - "write_key": b'Gossypiu', - # to be completed - }, - "XP-530": { - "read_key": [40, 9], - "write_key": b'Irisgarm', - # to be completed - }, "XP-540": { "read_key": [20, 4], "write_key": b'Firmiana', @@ -137,6 +173,9 @@ class EpsonPrinter: "XP-610": { "read_key": [121, 4], "write_key": b'Gossypiu', + "alias": ["XP-611", "XP-615", "XP-510"], + "main_waste": {"oids": [16, 17], "divider": 84.5}, # divider to be changed + "borderless_waste": {"oids": [18, 19], "divider": 33.7}, # divider to be changed # to be completed }, "XP-620": { @@ -144,11 +183,6 @@ class EpsonPrinter: "write_key": b'Althaea.', # to be completed }, - "XP-630": { - "read_key": [40, 9], - "write_key": b'Irisgarm', # (Iris graminea with typo?) - # to be completed - }, "XP-700": { "read_key": [40, 0], # to be completed @@ -160,6 +194,7 @@ class EpsonPrinter: "XP-830": { "read_key": [40, 9], "write_key": b'Irisgarm', # (Iris graminea with typo?) + "alias": ["XP-530", "XP-630", "XP-635"], "main_waste": {"oids": [0x10, 0x11], "divider": 84.5}, # To be changed "borderless_waste": {"oids": [0x12, 0x13], "divider": 33.7}, # To be changed "idProduct": 0x110b, @@ -168,6 +203,8 @@ class EpsonPrinter: "XP-850": { "read_key": [40, 0], "write_key": b'Hibiscus', + "main_waste": {"oids": [16, 17], "divider": 84.5}, # divider to be changed + "borderless_waste": {"oids": [18, 19], "divider": 33.7}, # divider to be changed # to be completed }, "XP-7100": { @@ -177,6 +214,15 @@ class EpsonPrinter: "borderless_waste": {"oids": [0x12, 0x13], "divider": 33.7}, # To be changed # to be completed }, + "ET-2500": { + "read_key": [68, 1], + "write_key": b'Gerbera*', # (Iris graminea with typo?) + "stats": { + "Maintenance required level of waste ink counter": [46], + }, + "raw_waste_reset": {24: 0, 25: 0, 30: 0, 28: 0, 29: 0, 46: 94} + # to be completed + }, } snmp_info = { @@ -198,6 +244,7 @@ class EpsonPrinter: "Emulation 3": "1.3.6.1.2.1.43.15.1.1.5.1.3", "Emulation 4": "1.3.6.1.2.1.43.15.1.1.5.1.4", "Emulation 5": "1.3.6.1.2.1.43.15.1.1.5.1.5", + "Print counter": "1.3.6.1.2.1.43.10.2.1.4.1.1", } SNMP_OID_ENTERPRISE = "1.3.6.1.4.1" @@ -246,7 +293,6 @@ class EpsonPrinter: printer_name for printer_name in self.PRINTER_CONFIG.keys() if "read_key" in self.PRINTER_CONFIG[printer_name] - and "write_key" in self.PRINTER_CONFIG[printer_name] } @property @@ -299,6 +345,8 @@ class EpsonSession(easysnmp.Session): if oid > 255: msb = oid // 256 oid = oid % 256 + if 'read_key' not in self.printer.parm: + return None return ( f"{self.printer.eeprom_link}" ".124.124" # || (0x7C 0x7C) @@ -319,6 +367,9 @@ class EpsonSession(easysnmp.Session): if oid > 255: msb = oid // 256 oid = oid % 256 + if ('write_key' not in self.printer.parm + or 'read_key' not in self.printer.parm): + return None write_op = ( f"{self.printer.eeprom_link}" ".124.124" # || (0x7C 0x7C) @@ -350,17 +401,23 @@ class EpsonSession(easysnmp.Session): oid: int, label: str = "unknown method") -> str: """Read EEPROM data.""" - response = self.read_value( - self.eeprom_oid_read_address(oid, label=label)) if self.debug: print( f"EEPROM_DUMP {label}:\n" f" ADDRESS: " f"{self.eeprom_oid_read_address(oid, label=label)}\n" - f" OID: {oid}={hex(oid)}\n" - f" RESPONSE: {repr(response)}" + f" OID: {oid}={hex(oid)}" ) - response = re.findall(r"EE:[0-9A-F]{6}", response)[0][3:] + response = self.read_value( + self.eeprom_oid_read_address(oid, label=label)) + if self.debug: + print(f" RESPONSE: {repr(response)}") + try: + response = re.findall(r"EE:[0-9A-F]{6}", response)[0][3:] + except IndexError: + if self.debug: + print(f"Invalid read key.") + return None chk_addr = response[0:4] value = response[4:6] if int(chk_addr, 16) != oid: @@ -383,21 +440,33 @@ class EpsonSession(easysnmp.Session): value: int, label: str = "unknown method") -> None: """Write value to OID with specified type to EEPROM.""" + if "write_key" not in self.printer.parm: + if self.debug: + print(f"Missing 'write_key' parameter in configuration.") + return False + if not self.dry_run and self.debug: + response = self.read_eeprom(oid, label=label) + print(f"Previous value for {label}: {response}") oid_string = self.eeprom_oid_write_address(oid, value, label=label) + response = None try: response = self.get(oid_string) - if self.debug: - print( - f"EEPROM_WRITE {label}:\n" - f" ADDRESS: {oid_string}\n" - f" OID: {oid}={hex(oid)}\n" - f" RESPONSE: {repr(response.value)}" - ) except easysnmp.exceptions.EasySNMPTimeoutError as e: if not self.dry_run: raise TimeoutError(str(e)) except Exception as e: raise ValueError(str(e)) + if self.debug: + print( + f"EEPROM_WRITE {label}:\n" + f" ADDRESS: {oid_string}\n" + f" OID: {oid}={hex(oid)}" + ) + if self.debug and response: + print(f" RESPONSE: {repr(response.value)}") + if response and not ":OK;" in repr(response.value): # ":NA;" is an error + return False + return True def status_parser(self, data): """Parse an ST2 status response and decode as much as possible.""" @@ -717,40 +786,59 @@ class EpsonSession(easysnmp.Session): d[oid] = int(self.read_eeprom(oid, label="dump_eeprom"), 16) return d - def reset_waste_ink_levels(self) -> None: + def reset_waste_ink_levels(self) -> bool: """ Set waste ink levels to 0. """ if "raw_waste_reset" in self.printer.parm: for oid, value in self.printer.parm["raw_waste_reset"].items(): - self.write_eeprom(oid, value, label="raw_waste_reset") - return + if not self.write_eeprom(oid, value, label="raw_waste_reset"): + return False + return True + if "main_waste" not in self.printer.parm: + return None for oid in self.printer.parm["main_waste"]["oids"]: - self.write_eeprom(oid, 0, label="main_waste") + if not self.write_eeprom(oid, 0, label="main_waste"): + return False + if "borderless_waste" not in self.printer.parm: + return True for oid in self.printer.parm["borderless_waste"]["oids"]: - self.write_eeprom(oid, 0, label="borderless_waste") + if not self.write_eeprom(oid, 0, label="borderless_waste"): + return False + return True def write_first_ti_received_time( - self, year: int, month: int, day: int) -> None: + self, year: int, month: int, day: int) -> bool: """Update first TI received time""" - msb = self.printer.parm["stats"]["First TI received time"][0] - lsb = self.printer.parm["stats"]["First TI received time"][1] + try: + msb = self.printer.parm["stats"]["First TI received time"][0] + lsb = self.printer.parm["stats"]["First TI received time"][1] + except KeyError: + return False n = (year - 2000) * 16 * 32 + 32 * month + day if self.debug: print("FTRT:", hex(n // 256), hex(n % 256), "=", n // 256, n % 256) - self.write_eeprom(msb, n // 256, label="First TI received time") - self.write_eeprom(lsb, n % 256, label="First TI received time") + if not self.write_eeprom(msb, n // 256, label="First TI received time"): + return False + if not self.write_eeprom(lsb, n % 256, label="First TI received time"): + return False + return True + + def detect_write_key(self, debug=False): + for model, chars in self.printer.PRINTER_CONFIG.items(): + if 'write_key' in chars: + print(model, chars['write_key']) def brute_force_read_key( - self, minimum: int = 0x00, maximum: int = 0xFF + self, minimum: int = 0x00, maximum: int = 0xFF, debug=False ): """Brute force read_key for printer.""" for x, y in itertools.permutations(range(minimum, maximum), r=2): self.printer.parm['read_key'] = [x, y] - print(f"Trying {self.printer.parm['read_key']}...") + if debug: + print(f"Trying {self.printer.parm['read_key']}...") try: self.read_eeprom(0x00, label="brute_force_read_key") - print(f"read_key found: {self.printer.parm['read_key']}") return self.printer.parm['read_key'] except IndexError: continue @@ -758,6 +846,10 @@ class EpsonSession(easysnmp.Session): return None return None + def write_sequence_to_string(self, write_sequence): + int_sequence = [int(b) for b in write_sequence[0].split(".")] + return "".join([chr(b-1) for b in int_sequence]) + if __name__ == "__main__": import argparse @@ -802,8 +894,8 @@ if __name__ == "__main__": action='store_true', help='Reset all waste ink levels to 0') parser.add_argument( - "--brute-force-read-key", - dest='brute_force', + "--detect-key", + dest='detect_key', action='store_true', help="Detect the read_key via brute force") parser.add_argument( @@ -832,6 +924,33 @@ if __name__ == "__main__": help='Change the first TI received time (arguments: year, month, day)', nargs=3, ) + parser.add_argument( + '-R', + '--read-eeprom', + dest='read_eeprom', + action='store', + type=str, + nargs=1, + help='Read the values of a list of printer EEPROM addreses.' + ' Format is: address [, ...]') + parser.add_argument( + '-W', + '--write-eeprom', + dest='write_eeprom', + action='store', + type=str, + nargs=1, + help='Write related values to a list of printer EEPROM addresses.' + ' Format is: address: value [, ...]') + parser.add_argument( + '-S', + '--write-sequence-to-string', + dest='ws_to_string', + action='store', + type=str, + nargs=1, + help='Convert write sequence of numbers to string.' + ) args = parser.parse_args() printer = EpsonPrinter( @@ -844,16 +963,32 @@ if __name__ == "__main__": quit(1) print_opt = False try: + if args.ws_to_string: + print_opt = True + print(printer.session.write_sequence_to_string(args.ws_to_string)) if args.reset_waste_ink: print_opt = True - printer.session.reset_waste_ink_levels() - if args.brute_force: + if printer.session.reset_waste_ink_levels(): + print("Reset waste ink levels done.") + else: + print("Failed to reset waste ink levels. Check configuration.") + if args.detect_key: print_opt = True - printer.session.brute_force_read_key() + read_key = printer.session.brute_force_read_key(debug=True) + if read_key: + print(f"read_key found: {read_key}") + else: + print(f"Cannot found read_key") if args.ftrt: print_opt = True - printer.session.write_first_ti_received_time( - int(args.ftrt[0]), int(args.ftrt[1]), int(args.ftrt[2])) + if printer.session.write_first_ti_received_time( + int(args.ftrt[0]), int(args.ftrt[1]), int(args.ftrt[2])): + print("Write first TI received time done.") + else: + print( + "Failed to write first TI received time." + " Check configuration." + ) if args.dump_eeprom: print_opt = True for addr, val in printer.session.dump_eeprom( @@ -913,6 +1048,36 @@ if __name__ == "__main__": initial_indent='', subsequent_indent=' ' ) ) + if args.read_eeprom: + print_opt = True + read_list = re.split(',\s*', args.read_eeprom[0]) + for value in read_list: + try: + val = printer.session.read_eeprom( + ast.literal_eval(value), label='read_eeprom') + if val is None: + print("EEPROM read error.") + else: + print(f"0x{val}={int(val, 16)}") + except (ValueError, SyntaxError): + print("invalid argument for read_eeprom") + quit(1) + if args.write_eeprom: + print_opt = True + read_list = re.split(',\s*|;\s*|\|\s*', args.write_eeprom[0]) + for key_val in read_list: + key, val = re.split(':|=', key_val) + try: + val_int = ast.literal_eval(val) + if not printer.session.write_eeprom( + ast.literal_eval(key), + str(val_int), label='write_eeprom' + ): + print("invalid write operation") + quit(1) + except (ValueError, SyntaxError): + print("invalid argument for write_eeprom") + quit(1) if args.info or not print_opt: ret = printer.stats() if ret: