diff --git a/adafruit_thermal_printer/thermal_printer.py b/adafruit_thermal_printer/thermal_printer.py index c31634b..23d3b31 100644 --- a/adafruit_thermal_printer/thermal_printer.py +++ b/adafruit_thermal_printer/thermal_printer.py @@ -35,6 +35,13 @@ from micropython import const +try: + from typing import Optional, Type + from typing_extensions import Literal + from circuitpython_typing import ReadableBuffer + from busio import UART +except ImportError: + pass __version__ = "0.0.0+auto.0" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Thermal_Printer.git" @@ -117,13 +124,17 @@ class _PrintModeBit: # which by design only implements get, set, init. As a result workaround # this pylint issue by disabling the warning. # pylint: disable=too-few-public-methods - def __init__(self, mask): + def __init__(self, mask: int) -> None: self._mask = mask - def __get__(self, obj, objtype): + def __get__( + self, + obj: Optional["ThermalPrinter"], + objtype: Type["ThermalPrinter"], + ) -> bool: return obj._print_mode & self._mask > 0 - def __set__(self, obj, val): + def __set__(self, obj: "ThermalPrinter", val: int) -> None: if val: obj._set_print_mode(self._mask) else: @@ -134,13 +145,13 @@ def __set__(self, obj, val): def __init__( self, - uart, + uart: UART, *, - byte_delay_s=0.00057346, - dot_feed_s=0.0021, - dot_print_s=0.03, - auto_warm_up=True - ): + byte_delay_s: float = 0.00057346, + dot_feed_s: float = 0.0021, + dot_print_s: float = 0.03, + auto_warm_up: bool = True, + ) -> None: """Thermal printer class. Requires a serial UART connection with at least the TX pin connected. Take care connecting RX as the printer will output a 5V signal which can damage boards! If RX is unconnected @@ -176,16 +187,16 @@ def __init__( if auto_warm_up: self.warm_up() - def _set_timeout(self, period_s): + def _set_timeout(self, period_s: float) -> None: # Set a timeout before future commands can be sent. self._resume = time.monotonic() + period_s - def _wait_timeout(self): + def _wait_timeout(self) -> None: # Ensure the timeout that was previously set has passed (will busy wait). while time.monotonic() < self._resume: pass - def _write_char(self, char): + def _write_char(self, char: str) -> None: # Write a single character to the printer. if char == "\r": return # Strip carriage returns by skipping them. @@ -207,33 +218,33 @@ def _write_char(self, char): self._column += 1 self._set_timeout(delay) - def _write_print_mode(self): + def _write_print_mode(self) -> None: # Write the printer mode to the printer. self.send_command( - "\x1B!{0}".format(chr(self._print_mode)) + f"\x1B!{chr(self._print_mode)}" ) # ESC + '!' + print mode byte # Adjust character height and column count based on print mode. self._char_height = 48 if self._print_mode & _DOUBLE_HEIGHT_MASK else 24 self._max_column = 16 if self._print_mode & _DOUBLE_WIDTH_MASK else 32 - def _set_print_mode(self, mask): + def _set_print_mode(self, mask: int) -> None: # Enable the specified bits of the print mode. self._print_mode |= mask & 0xFF self._write_print_mode() - def _unset_print_mode(self, mask): + def _unset_print_mode(self, mask: int) -> None: # Disable the specified bits of the print mode. self._print_mode &= ~(mask & 0xFF) self._write_print_mode() - def send_command(self, command): + def send_command(self, command: str) -> None: """Send a command string to the printer.""" self._uart.write(bytes(command, "ascii")) # Do initialization in warm_up instead of the initializer because this # initialization takes a long time (5 seconds) and shouldn't happen during # object creation (users need explicit control of when to start it). - def warm_up(self, heat_time=120): + def warm_up(self, heat_time: int = 120) -> None: """Initialize the printer. Can specify an optional heat_time keyword to override the default heating timing of 1.2 ms. See the datasheet for details on the heating time value (duration in 10uS increments). @@ -261,7 +272,7 @@ def warm_up(self, heat_time=120): # possibly paper 'stiction'. More heating interval = clearer print, # but slower printing speed. # Send ESC + '7' (print settings) + heating dots, heat time, heat interval. - self.send_command("\x1B7\x0B{0}\x28".format(chr(heat_time))) + self.send_command(f"\x1B7\x0B{chr(heat_time)}\x28") # Print density description from manual: # DC2 # n Set printing density # D4..D0 of n is used to set the printing density. Density is @@ -271,9 +282,9 @@ def warm_up(self, heat_time=120): print_density = 10 # 100% (? can go higher, text is darker but fuzzy) print_break_time = 2 # 500 uS dc2_value = (print_break_time << 5) | print_density - self.send_command("\x12#{0}".format(chr(dc2_value))) # DC2 + '#' + value + self.send_command(f"\x12#{chr(dc2_value)}") # DC2 + '#' + value - def reset(self): + def reset(self) -> None: """Reset the printer.""" # Issue a reset command to the printer. (ESC + @) self.send_command("\x1B@") @@ -287,7 +298,7 @@ def reset(self): # ESC + 'D' + tab stop value list ending with null to terminate. self.send_command("\x1BD\x04\x08\x10\x14\x18\x1C\x00") - def print(self, text, end="\n"): + def print(self, text: str, end: Optional[str] = "\n") -> None: """Print a line of text. Optionally specify the end keyword to override the new line printed after the text (set to None to disable the new line entirely). @@ -297,7 +308,7 @@ def print(self, text, end="\n"): if end is not None: self._write_char(end) - def print_barcode(self, text, barcode_type): + def print_barcode(self, text: str, barcode_type: int) -> None: """Print a barcode with the specified text/number (the meaning varies based on the type of barcode) and type. Type is a value from the datasheet or class-level variables like UPC_A, etc. for @@ -309,14 +320,14 @@ def print_barcode(self, text, barcode_type): self.feed(1) # Recent firmware can't print barcode w/o feed first??? self.send_command("\x1DH\x02") # Print label below barcode self.send_command("\x1Dw\x03") # Barcode width 3 (0.375/1.0mm thin/thick) - self.send_command("\x1Dk{0}".format(chr(barcode_type))) # Barcode type + self.send_command(f"\x1Dk{chr(barcode_type)}") # Barcode type # Write length and then string (note this only works with 2.64+). self.send_command(chr(len(text))) self.send_command(text) self._set_timeout((self._barcode_height + 40) * self._dot_print_s) self._column = 0 - def _print_bitmap(self, width, height, data): + def _print_bitmap(self, width: int, height: int, data: ReadableBuffer) -> None: """Print a bitmap image of the specified width, height and data bytes. Data bytes must be in 1-bit per pixel format, i.e. each byte represents 8 pixels of image data along a row of the image. You will want to @@ -338,9 +349,7 @@ def _print_bitmap(self, width, height, data): for row_start in range(0, height, chunk_height_limit): # Issue up to chunkHeightLimit rows at a time. chunk_height = min(height - row_start, chunk_height_limit) - self.send_command( - "\x12*{0}{1}".format(chr(chunk_height), chr(row_bytes_clipped)) - ) + self.send_command(f"\x12*{chr(chunk_height)}{chr(row_bytes_clipped)}") for _ in range(chunk_height): for _ in range(row_bytes_clipped): # Drop down to low level UART access to avoid newline and @@ -352,7 +361,7 @@ def _print_bitmap(self, width, height, data): self._set_timeout(chunk_height * self._dot_print_s) self._column = 0 - def test_page(self): + def test_page(self) -> None: """Print a test page.""" self.send_command("\x12T") # DC2 + 'T' for test page # Delay for 26 lines w/text (ea. 24 dots high) + @@ -361,7 +370,7 @@ def test_page(self): self._dot_print_s * 24 * 26 + self._dot_feed_s * (6 * 26 + 30) ) - def set_defaults(self): + def set_defaults(self) -> None: """Set default printing and text options. This is useful to reset back to a good state after printing different size, weight, etc. text. """ @@ -383,7 +392,7 @@ def set_defaults(self): self._set_charset() self._set_code_page() - def _set_justify(self, val): + def _set_justify(self, val: Literal[0, 1, 2]) -> None: assert 0 <= val <= 2 if val == JUSTIFY_LEFT: self.send_command("\x1Ba\x00") # ESC + 'a' + 0 @@ -404,7 +413,7 @@ def _set_justify(self, val): ) # pylint: enable=line-too-long - def _set_size(self, val): + def _set_size(self, val: Literal[0, 1, 2]) -> None: assert 0 <= val <= 2 if val == SIZE_SMALL: self._char_height = 24 @@ -432,7 +441,7 @@ def _set_size(self, val): ) # pylint: enable=line-too-long - def _set_underline(self, val): + def _set_underline(self, val: Optional[Literal[0, 1]]) -> None: assert val is None or (0 <= val <= 1) if val is None: # Turn off underline. @@ -454,7 +463,7 @@ def _set_underline(self, val): ) # pylint: enable=line-too-long - def _set_inverse(self, inverse): + def _set_inverse(self, inverse: bool) -> None: # Set the inverse printing state to enabled disabled with the specified # boolean value. This requires printer firmare 2.68+ if inverse: @@ -474,7 +483,7 @@ def _set_inverse(self, inverse): ) # pylint: enable=line-too-long - def _set_up_down_mode(self, up_down_mode): + def _set_up_down_mode(self, up_down_mode: bool) -> None: if up_down_mode: self.send_command("\x1B{\x01") @@ -496,35 +505,35 @@ def _set_up_down_mode(self, up_down_mode): bold = _PrintModeBit(_BOLD_MASK) - def feed(self, lines): + def feed(self, lines: int) -> None: """Advance paper by specified number of blank lines.""" assert 0 <= lines <= 255 - self.send_command("\x1Bd{0}".format(chr(lines))) + self.send_command(f"\x1Bd{chr(lines)}") self._set_timeout(self._dot_feed_s * self._char_height) self._column = 0 - def feed_rows(self, rows): + def feed_rows(self, rows: int) -> None: """Advance paper by specified number of pixel rows.""" assert 0 <= rows <= 255 - self.send_command("\x1BJ{0}".format(chr(rows))) + self.send_command(f"\x1BJ{chr(rows)}") self._set_timeout(rows * self._dot_feed_s) self._column = 0 - def flush(self): + def flush(self) -> None: """Flush data pending in the printer.""" self.send_command("\f") - def offline(self): + def offline(self) -> None: """Put the printer into an offline state. No other commands can be sent until an online call is made. """ self.send_command("\x1B=\x00") # ESC + '=' + 0 - def online(self): + def online(self) -> None: """Put the printer into an online state after previously put offline.""" self.send_command("\x1B=\x01") # ESC + '=' + 1 - def has_paper(self): + def has_paper(self) -> bool: """Return a boolean indicating if the printer has paper. You MUST have the serial RX line hooked up for this to work. NOTE: be VERY CAREFUL to ensure your board can handle a 5V serial input before hooking up @@ -537,38 +546,38 @@ def has_paper(self): return False return not status[0] & 0b00000100 - def _set_line_height(self, height): + def _set_line_height(self, height: int) -> None: """Set the line height in pixels. This is the total amount of space between lines, including the height of text. The smallest value is 24 and the largest is 255. """ assert 24 <= height <= 255 self._line_spacing = height - 24 - self.send_command("\x1B3{0}".format(chr(height))) # ESC + '3' + height + self.send_command(f"\x1B3{chr(height)}") # ESC + '3' + height - def _set_barcode_height(self, height): + def _set_barcode_height(self, height: int) -> None: """Set the barcode height in pixels. Must be a value 1 - 255.""" assert 1 <= height <= 255 self._barcode_height = height - self.send_command("\x1Dh{0}".format(chr(height))) # ASCII GS + 'h' + height + self.send_command(f"\x1Dh{chr(height)}") # ASCII GS + 'h' + height - def _set_charset(self, charset=0): + def _set_charset(self, charset: int = 0) -> None: """Alters the character set for ASCII characters 0x23-0x7E. See datasheet for details on character set values (0-15). Note this is only supported on more recent firmware printers! """ assert 0 <= charset <= 15 - self.send_command("\x1BR{0}".format(chr(charset))) # ESC + 'R' + charset + self.send_command(f"\x1BR{chr(charset)}") # ESC + 'R' + charset - def _set_code_page(self, code_page=0): + def _set_code_page(self, code_page: int = 0) -> None: """Select alternate code page for upper ASCII symbols 0x80-0xFF. See datasheet for code page values (0 - 47). Note this is only supported on more recent firmware printers! """ assert 0 <= code_page <= 47 - self.send_command("\x1Bt{0}".format(chr(code_page))) # ESC + 't' + code page + self.send_command(f"\x1Bt{chr(code_page)}") # ESC + 't' + code page - def tab(self): + def tab(self) -> None: """Print a tab (i.e. move to next 4 character block). Note this is only supported on more recent firmware printers!""" self.send_command("\t") diff --git a/adafruit_thermal_printer/thermal_printer_2168.py b/adafruit_thermal_printer/thermal_printer_2168.py index 6597ad2..bc9acf9 100644 --- a/adafruit_thermal_printer/thermal_printer_2168.py +++ b/adafruit_thermal_printer/thermal_printer_2168.py @@ -23,6 +23,11 @@ from adafruit_thermal_printer import thermal_printer +try: + import typing # pylint: disable=unused-import + from busio import UART +except ImportError: + pass # pylint: disable=too-many-arguments class ThermalPrinter(thermal_printer.ThermalPrinter): @@ -44,12 +49,12 @@ class ThermalPrinter(thermal_printer.ThermalPrinter): def __init__( self, - uart, - byte_delay_s=0.00057346, - dot_feed_s=0.0021, - dot_print_s=0.03, - auto_warm_up=True, - ): + uart: UART, + byte_delay_s: float = 0.00057346, + dot_feed_s: float = 0.0021, + dot_print_s: float = 0.03, + auto_warm_up: bool = True, + ) -> None: """Thermal printer class. Requires a serial UART connection with at least the TX pin connected. Take care connecting RX as the printer will output a 5V signal which can damage boards! If RX is unconnected @@ -68,7 +73,7 @@ def __init__( auto_warm_up=auto_warm_up, ) - def warm_up(self, heat_time=120): + def warm_up(self, heat_time: int = 120) -> None: """Apparently there are no parameters for setting darkness in 2.168 (at least commands from 2.68 dont work), So it is little compatibility method to reuse older code. diff --git a/adafruit_thermal_printer/thermal_printer_264.py b/adafruit_thermal_printer/thermal_printer_264.py index a6db5f3..edf3c0b 100644 --- a/adafruit_thermal_printer/thermal_printer_264.py +++ b/adafruit_thermal_printer/thermal_printer_264.py @@ -22,6 +22,11 @@ from adafruit_thermal_printer import thermal_printer +try: + import typing # pylint: disable=unused-import + from busio import UART +except ImportError: + pass __version__ = "0.0.0+auto.0" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Thermal_Printer.git" @@ -54,8 +59,12 @@ class ThermalPrinter(thermal_printer.ThermalPrinter): CODE128 = 73 def __init__( - self, uart, byte_delay_s=0.00057346, dot_feed_s=0.0021, dot_print_s=0.03 - ): + self, + uart: UART, + byte_delay_s: float = 0.00057346, + dot_feed_s: float = 0.0021, + dot_print_s: float = 0.03, + ) -> None: """Thermal printer class. Requires a serial UART connection with at least the TX pin connected. Take care connecting RX as the printer will output a 5V signal which can damage boards! If RX is unconnected diff --git a/adafruit_thermal_printer/thermal_printer_legacy.py b/adafruit_thermal_printer/thermal_printer_legacy.py index 5a61be3..4299112 100644 --- a/adafruit_thermal_printer/thermal_printer_legacy.py +++ b/adafruit_thermal_printer/thermal_printer_legacy.py @@ -22,6 +22,11 @@ from adafruit_thermal_printer import thermal_printer +try: + import typing # pylint: disable=unused-import + from busio import UART +except ImportError: + pass __version__ = "0.0.0+auto.0" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Thermal_Printer.git" @@ -54,8 +59,12 @@ class ThermalPrinter(thermal_printer.ThermalPrinter): MSI = 10 def __init__( - self, uart, byte_delay_s=0.00057346, dot_feed_s=0.0021, dot_print_s=0.03 - ): + self, + uart: UART, + byte_delay_s: float = 0.00057346, + dot_feed_s: float = 0.0021, + dot_print_s: float = 0.03, + ) -> None: """Thermal printer class. Requires a serial UART connection with at least the TX pin connected. Take care connecting RX as the printer will output a 5V signal which can damage boards! If RX is unconnected @@ -73,7 +82,7 @@ def __init__( dot_print_s=dot_print_s, ) - def print_barcode(self, text, barcode_type): + def print_barcode(self, text: str, barcode_type: int) -> None: """Print a barcode with the specified text/number (the meaning varies based on the type of barcode) and type. Type is a value from the datasheet or class-level variables like UPC_A, etc. for @@ -85,7 +94,7 @@ def print_barcode(self, text, barcode_type): self.feed(1) # Recent firmware can't print barcode w/o feed first??? self.send_command("\x1DH\x02") # Print label below barcode self.send_command("\x1Dw\x03") # Barcode width 3 (0.375/1.0mm thin/thick) - self.send_command("\x1Dk{0}".format(chr(barcode_type))) # Barcode type + self.send_command(f"\x1Dk{chr(barcode_type)}") # Barcode type # Pre-2.64 firmware prints the text and then a null character to end. # Instead of the length of text as a prefix. self.send_command(text) @@ -93,7 +102,7 @@ def print_barcode(self, text, barcode_type): self._set_timeout((self._barcode_height + 40) * self._dot_print_s) self._column = 0 - def reset(self): + def reset(self) -> None: """Reset the printer.""" # Issue a reset command to the printer. (ESC + @) self.send_command("\x1B@") @@ -105,13 +114,13 @@ def reset(self): self._barcode_height = 50 # Skip tab configuration on older printers. - def feed(self, lines): + def feed(self, lines: int) -> None: """Advance paper by specified number of blank lines.""" # Just send line feeds for older printers. for _ in range(lines): self._write_char("\n") - def has_paper(self): + def has_paper(self) -> bool: """Return a boolean indicating if the printer has paper. You MUST have the serial RX line hooked up for this to work. diff --git a/requirements.txt b/requirements.txt index 4021987..91c8fc7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,3 +4,5 @@ Adafruit-Blinka pyserial +adafruit-circuitpython-typing +typing-extensions~=4.0