diff --git a/library/lcd/color.py b/library/lcd/color.py new file mode 100644 index 00000000..3b38cedd --- /dev/null +++ b/library/lcd/color.py @@ -0,0 +1,47 @@ +from typing import Union, Tuple + +from PIL import ImageColor + +RGBColor = Tuple[int, int, int] + +# Color can be an RGB tuple (RGBColor), or a string in any of these formats: +# - "r, g, b" (e.g. "255, 0, 0"), as is found in the themes' yaml settings +# - any of the formats supported by PIL: https://pillow.readthedocs.io/en/stable/reference/ImageColor.html +# +# For example, here are multiple ways to write the pure red color: +# - (255, 0, 0) +# - "255, 0, 0" +# - "#ff0000" +# - "red" +# - "hsl(0, 100%, 50%)" +Color = Union[str, RGBColor] + +def parse_color(color: Color) -> RGBColor: + # even if undocumented, let's be nice and accept a list in lieu of a tuple + if isinstance(color, tuple) or isinstance(color, list): + if len(color) != 3: + raise ValueError("RGB color must have 3 values") + return (int(color[0]), int(color[1]), int(color[2])) + + if not isinstance(color, str): + raise ValueError("Color must be either an RGB tuple or a string") + + # Try to parse it as our custom "r, g, b" format + rgb = color.split(',') + if len(rgb) == 3: + r, g, b = rgb + try: + rgbcolor = (int(r.strip()), int(g.strip()), int(b.strip())) + except ValueError: + # at least one element can't be converted to int, we continue to + # try parsing as a PIL color + pass + else: + return rgbcolor + + # fallback as a PIL color + rgbcolor = ImageColor.getrgb(color) + if len(rgbcolor) == 4: + return (rgbcolor[0], rgbcolor[1], rgbcolor[2]) + return rgbcolor + diff --git a/library/lcd/lcd_comm.py b/library/lcd/lcd_comm.py index 6ef3d5f9..489f23e5 100644 --- a/library/lcd/lcd_comm.py +++ b/library/lcd/lcd_comm.py @@ -25,12 +25,13 @@ import time from abc import ABC, abstractmethod from enum import IntEnum -from typing import Tuple, List +from typing import Tuple, List, Optional, Dict import serial from PIL import Image, ImageDraw, ImageFont from library.log import logger +from library.lcd.color import Color, parse_color class Orientation(IntEnum): @@ -42,7 +43,7 @@ class Orientation(IntEnum): class LcdComm(ABC): def __init__(self, com_port: str = "AUTO", display_width: int = 320, display_height: int = 480, - update_queue: queue.Queue = None): + update_queue: Optional[queue.Queue] = None): self.lcd_serial = None # String containing absolute path to serial port e.g. "COM3", "/dev/ttyACM1" or "AUTO" for auto-discovery @@ -67,7 +68,10 @@ def __init__(self, com_port: str = "AUTO", display_width: int = 320, display_hei self.image_cache = {} # { key=path, value=PIL.Image } # Create a cache to store opened fonts, to avoid opening and loading from the filesystem every time - self.font_cache = {} # { key=(font, size), value=PIL.ImageFont } + self.font_cache: Dict[ + Tuple[str, int], # key=(font, size) + ImageFont.FreeTypeFont # value= a loaded freetype font + ] = {} def get_width(self) -> int: if self.orientation == Orientation.PORTRAIT or self.orientation == Orientation.REVERSE_PORTRAIT: @@ -97,7 +101,7 @@ def openSerial(self): logger.debug(f"Static COM port: {self.com_port}") try: - self.lcd_serial = serial.Serial(self.com_port, 115200, timeout=1, rtscts=1) + self.lcd_serial = serial.Serial(self.com_port, 115200, timeout=1, rtscts=True) except Exception as e: logger.error(f"Cannot open COM port {self.com_port}: {e}") try: @@ -106,10 +110,20 @@ def openSerial(self): os._exit(0) def closeSerial(self): - try: + if self.lcd_serial is not None: self.lcd_serial.close() - except: - pass + + def serial_write(self, data: bytes): + assert self.lcd_serial is not None + self.lcd_serial.write(data) + + def serial_read(self, size: int) -> bytes: + assert self.lcd_serial is not None + return self.lcd_serial.read(size) + + def serial_flush_input(self): + if self.lcd_serial is not None: + self.lcd_serial.reset_input_buffer() def WriteData(self, byteBuffer: bytearray): self.WriteLine(bytes(byteBuffer)) @@ -124,39 +138,39 @@ def SendLine(self, line: bytes): def WriteLine(self, line: bytes): try: - self.lcd_serial.write(line) - except serial.serialutil.SerialTimeoutException: + self.serial_write(line) + except serial.SerialTimeoutException: # We timed-out trying to write to our device, slow things down. logger.warning("(Write line) Too fast! Slow down!") - except serial.serialutil.SerialException: + except serial.SerialException: # Error writing data to device: close and reopen serial port, try to write again logger.error( "SerialException: Failed to send serial data to device. Closing and reopening COM port before retrying once.") self.closeSerial() time.sleep(1) self.openSerial() - self.lcd_serial.write(line) + self.serial_write(line) def ReadData(self, readSize: int): try: - response = self.lcd_serial.read(readSize) + response = self.serial_read(readSize) # logger.debug("Received: [{}]".format(str(response, 'utf-8'))) return response - except serial.serialutil.SerialTimeoutException: + except serial.SerialTimeoutException: # We timed-out trying to read from our device, slow things down. logger.warning("(Read data) Too fast! Slow down!") - except serial.serialutil.SerialException: + except serial.SerialException: # Error writing data to device: close and reopen serial port, try to read again logger.error( "SerialException: Failed to read serial data from device. Closing and reopening COM port before retrying once.") self.closeSerial() time.sleep(1) self.openSerial() - return self.lcd_serial.read(readSize) + return self.serial_read(readSize) @staticmethod @abstractmethod - def auto_detect_com_port(): + def auto_detect_com_port() -> Optional[str]: pass @abstractmethod @@ -193,7 +207,7 @@ def SetOrientation(self, orientation: Orientation): @abstractmethod def DisplayPILImage( self, - image: Image, + image: Image.Image, x: int = 0, y: int = 0, image_width: int = 0, image_height: int = 0 @@ -213,20 +227,17 @@ def DisplayText( height: int = 0, font: str = "./res/fonts/roboto-mono/RobotoMono-Regular.ttf", font_size: int = 20, - font_color: Tuple[int, int, int] = (0, 0, 0), - background_color: Tuple[int, int, int] = (255, 255, 255), - background_image: str = None, + font_color: Color = (0, 0, 0), + background_color: Color = (255, 255, 255), + background_image: Optional[str] = None, align: str = 'left', - anchor: str = None, + anchor: str = 'la', ): # Convert text to bitmap using PIL and display it # Provide the background image path to display text with transparent background - if isinstance(font_color, str): - font_color = tuple(map(int, font_color.split(', '))) - - if isinstance(background_color, str): - background_color = tuple(map(int, background_color.split(', '))) + font_color = parse_color(font_color) + background_color = parse_color(background_color) assert x <= self.get_width(), 'Text X coordinate ' + str(x) + ' must be <= display width ' + str( self.get_width()) @@ -251,13 +262,11 @@ def DisplayText( text_image = self.open_image(background_image) # Get text bounding box - if (font, font_size) not in self.font_cache: - self.font_cache[(font, font_size)] = ImageFont.truetype(font, font_size) - font = self.font_cache[(font, font_size)] + ttfont = self.open_font(font, font_size) d = ImageDraw.Draw(text_image) if width == 0 or height == 0: - left, top, right, bottom = d.textbbox((x, y), text, font=font, align=align, anchor=anchor) + left, top, right, bottom = d.textbbox((x, y), text, font=ttfont, align=align, anchor=anchor) # textbbox may return float values, which is not good for the bitmap operations below. # Let's extend the bounding box to the next whole pixel in all directions @@ -267,21 +276,21 @@ def DisplayText( left, top, right, bottom = x, y, x + width, y + height if anchor.startswith("m"): - x = (right + left) / 2 + x = int((right + left) / 2) elif anchor.startswith("r"): x = right else: x = left if anchor.endswith("m"): - y = (bottom + top) / 2 + y = int((bottom + top) / 2) elif anchor.endswith("b"): y = bottom else: y = top # Draw text onto the background image with specified color & font - d.text((x, y), text, font=font, fill=font_color, align=align, anchor=anchor) + d.text((x, y), text, font=ttfont, fill=font_color, align=align, anchor=anchor) # Restrict the dimensions if they overflow the display size left = max(left, 0) @@ -296,18 +305,15 @@ def DisplayText( def DisplayProgressBar(self, x: int, y: int, width: int, height: int, min_value: int = 0, max_value: int = 100, value: int = 50, - bar_color: Tuple[int, int, int] = (0, 0, 0), + bar_color: Color = (0, 0, 0), bar_outline: bool = True, - background_color: Tuple[int, int, int] = (255, 255, 255), - background_image: str = None): + background_color: Color = (255, 255, 255), + background_image: Optional[str] = None): # Generate a progress bar and display it # Provide the background image path to display progress bar with transparent background - if isinstance(bar_color, str): - bar_color = tuple(map(int, bar_color.split(', '))) - - if isinstance(background_color, str): - background_color = tuple(map(int, background_color.split(', '))) + bar_color = parse_color(bar_color) + background_color = parse_color(background_color) assert x <= self.get_width(), 'Progress bar X coordinate must be <= display width' assert y <= self.get_height(), 'Progress bar Y coordinate must be <= display height' @@ -347,28 +353,23 @@ def DisplayProgressBar(self, x: int, y: int, width: int, height: int, min_value: def DisplayLineGraph(self, x: int, y: int, width: int, height: int, values: List[float], - min_value: int = 0, - max_value: int = 100, + min_value: float = 0, + max_value: float = 100, autoscale: bool = False, - line_color: Tuple[int, int, int] = (0, 0, 0), + line_color: Color = (0, 0, 0), line_width: int = 2, graph_axis: bool = True, - axis_color: Tuple[int, int, int] = (0, 0, 0), + axis_color: Color = (0, 0, 0), axis_font: str = "./res/fonts/roboto/Roboto-Black.ttf", axis_font_size: int = 10, - background_color: Tuple[int, int, int] = (255, 255, 255), - background_image: str = None): + background_color: Color = (255, 255, 255), + background_image: Optional[str] = None): # Generate a plot graph and display it # Provide the background image path to display plot graph with transparent background - if isinstance(line_color, str): - line_color = tuple(map(int, line_color.split(', '))) - - if isinstance(axis_color, str): - axis_color = tuple(map(int, axis_color.split(', '))) - - if isinstance(background_color, str): - background_color = tuple(map(int, background_color.split(', '))) + line_color = parse_color(line_color) + axis_color = parse_color(axis_color) + background_color = parse_color(background_color) assert x <= self.get_width(), 'Progress bar X coordinate must be <= display width' assert y <= self.get_height(), 'Progress bar Y coordinate must be <= display height' @@ -434,20 +435,19 @@ def DisplayLineGraph(self, x: int, y: int, width: int, height: int, # Draw Legend draw.line([0, 0, 1, 0], fill=axis_color) text = f"{int(max_value)}" - ttfont = ImageFont.truetype(axis_font, axis_font_size) - left, top, right, bottom = ttfont.getbbox(text) + ttfont = self.open_font(axis_font, axis_font_size) + _, top, right, bottom = ttfont.getbbox(text) draw.text((2, 0 - top), text, font=ttfont, fill=axis_color) text = f"{int(min_value)}" - ttfont = ImageFont.truetype(axis_font, axis_font_size) - left, top, right, bottom = ttfont.getbbox(text) + _, top, right, bottom = ttfont.getbbox(text) draw.text((width - 1 - right, height - 2 - bottom), text, font=ttfont, fill=axis_color) self.DisplayPILImage(graph_image, x, y) - def DrawRadialDecoration(self, draw: ImageDraw, angle: float, radius: float, width: float, color: Tuple[int, int, int] = (0, 0, 0)): + def DrawRadialDecoration(self, draw: ImageDraw.ImageDraw, angle: float, radius: float, width: float, color: Tuple[int, int, int] = (0, 0, 0)): i_cos = math.cos(angle*math.pi/180) i_sin = math.sin(angle*math.pi/180) x_f = (i_cos * (radius - width/2)) + radius @@ -473,39 +473,32 @@ def DrawRadialDecoration(self, draw: ImageDraw, angle: float, radius: float, wid def DisplayRadialProgressBar(self, xc: int, yc: int, radius: int, bar_width: int, min_value: int = 0, max_value: int = 100, - angle_start: int = 0, - angle_end: int = 360, + angle_start: float = 0, + angle_end: float = 360, angle_sep: int = 5, angle_steps: int = 10, clockwise: bool = True, value: int = 50, - text: str = None, + text: Optional[str] = None, with_text: bool = True, font: str = "./res/fonts/roboto/Roboto-Black.ttf", font_size: int = 20, - font_color: Tuple[int, int, int] = (0, 0, 0), - bar_color: Tuple[int, int, int] = (0, 0, 0), - background_color: Tuple[int, int, int] = (255, 255, 255), - background_image: str = None, + font_color: Color = (0, 0, 0), + bar_color: Color = (0, 0, 0), + background_color: Color = (255, 255, 255), + background_image: Optional[str] = None, custom_bbox: Tuple[int, int, int, int] = (0, 0, 0, 0), text_offset: Tuple[int, int] = (0,0), - bar_background_color: Tuple[int, int, int] = (0, 0, 0), + bar_background_color: Color = (0, 0, 0), draw_bar_background: bool = False, bar_decoration: str = ""): # Generate a radial progress bar and display it # Provide the background image path to display progress bar with transparent background - if isinstance(bar_color, str): - bar_color = tuple(map(int, bar_color.split(', '))) - - if isinstance(background_color, str): - background_color = tuple(map(int, background_color.split(', '))) - - if isinstance(font_color, str): - font_color = tuple(map(int, font_color.split(', '))) - - if isinstance(bar_background_color, str): - bar_background_color = tuple(map(int, bar_background_color.split(', '))) + bar_color = parse_color(bar_color) + background_color = parse_color(background_color) + font_color = parse_color(font_color) + bar_background_color = parse_color(bar_background_color) if angle_start % 361 == angle_end % 361: if clockwise: @@ -659,11 +652,11 @@ def DisplayRadialProgressBar(self, xc: int, yc: int, radius: int, bar_width: int if with_text: if text is None: text = f"{int(pct * 100 + .5)}%" - font = ImageFont.truetype(font, font_size) - left, top, right, bottom = font.getbbox(text) + ttfont = self.open_font(font, font_size) + left, top, right, bottom = ttfont.getbbox(text) w, h = right - left, bottom - top draw.text((radius - w / 2 + text_offset[0], radius - top - h / 2 + text_offset[1]), text, - font=font, fill=font_color) + font=ttfont, fill=font_color) if custom_bbox[0] != 0 or custom_bbox[1] != 0 or custom_bbox[2] != 0 or custom_bbox[3] != 0: bar_image = bar_image.crop(box=custom_bbox) @@ -672,8 +665,13 @@ def DisplayRadialProgressBar(self, xc: int, yc: int, radius: int, bar_width: int # self.DisplayPILImage(bar_image, xc - radius, yc - radius) # Load image from the filesystem, or get from the cache if it has already been loaded previously - def open_image(self, bitmap_path: str) -> Image: + def open_image(self, bitmap_path: str) -> Image.Image: if bitmap_path not in self.image_cache: logger.debug("Bitmap " + bitmap_path + " is now loaded in the cache") self.image_cache[bitmap_path] = Image.open(bitmap_path) return copy.copy(self.image_cache[bitmap_path]) + + def open_font(self, name: str, size: int) -> ImageFont.FreeTypeFont: + if (name, size) not in self.font_cache: + self.font_cache[(name, size)] = ImageFont.truetype(name, size) + return self.font_cache[(name, size)] diff --git a/library/lcd/lcd_comm_rev_a.py b/library/lcd/lcd_comm_rev_a.py index 33d9b45a..9b2b9c6f 100644 --- a/library/lcd/lcd_comm_rev_a.py +++ b/library/lcd/lcd_comm_rev_a.py @@ -18,6 +18,7 @@ import time from enum import Enum +from typing import Optional from serial.tools.list_ports import comports import numpy as np @@ -53,7 +54,7 @@ class SubRevision(Enum): # This class is for Turing Smart Screen (rev. A) 3.5" and UsbMonitor screens (all sizes) class LcdCommRevA(LcdComm): def __init__(self, com_port: str = "AUTO", display_width: int = 320, display_height: int = 480, - update_queue: queue.Queue = None): + update_queue: Optional[queue.Queue] = None): logger.debug("HW revision: A") LcdComm.__init__(self, com_port, display_width, display_height, update_queue) self.openSerial() @@ -62,7 +63,7 @@ def __del__(self): self.closeSerial() @staticmethod - def auto_detect_com_port(): + def auto_detect_com_port() -> Optional[str]: com_ports = comports() auto_com_port = None @@ -95,8 +96,8 @@ def _hello(self): # This command reads LCD answer on serial link, so it bypasses the queue self.WriteData(hello) - response = self.lcd_serial.read(6) - self.lcd_serial.flushInput() + response = self.serial_read(6) + self.serial_flush_input() if response == SubRevision.USBMONITOR_3_5.value: self.sub_revision = SubRevision.USBMONITOR_3_5 @@ -170,10 +171,10 @@ def SetOrientation(self, orientation: Orientation = Orientation.PORTRAIT): byteBuffer[8] = (width & 255) byteBuffer[9] = (height >> 8) byteBuffer[10] = (height & 255) - self.lcd_serial.write(bytes(byteBuffer)) + self.serial_write(bytes(byteBuffer)) @staticmethod - def imageToRGB565LE(image: Image): + def imageToRGB565LE(image: Image.Image): if image.mode not in ["RGB", "RGBA"]: # we need the first 3 channels to be R, G and B image = image.convert("RGB") @@ -200,7 +201,7 @@ def imageToRGB565LE(image: Image): def DisplayPILImage( self, - image: Image, + image: Image.Image, x: int = 0, y: int = 0, image_width: int = 0, image_height: int = 0 diff --git a/library/lcd/lcd_comm_rev_b.py b/library/lcd/lcd_comm_rev_b.py index a64ba872..4128c25b 100644 --- a/library/lcd/lcd_comm_rev_b.py +++ b/library/lcd/lcd_comm_rev_b.py @@ -51,7 +51,7 @@ class SubRevision(IntEnum): # This class is for XuanFang (rev. B & flagship) 3.5" screens class LcdCommRevB(LcdComm): def __init__(self, com_port: str = "AUTO", display_width: int = 320, display_height: int = 480, - update_queue: queue.Queue = None): + update_queue: Optional[queue.Queue] = None): logger.debug("HW revision: B") LcdComm.__init__(self, com_port, display_width, display_height, update_queue) self.openSerial() @@ -67,7 +67,7 @@ def is_brightness_range(self): return self.sub_revision == SubRevision.A11 or self.sub_revision == SubRevision.A12 @staticmethod - def auto_detect_com_port(): + def auto_detect_com_port() -> Optional[str]: com_ports = comports() auto_com_port = None @@ -110,8 +110,8 @@ def _hello(self): # This command reads LCD answer on serial link, so it bypasses the queue self.SendCommand(Command.HELLO, payload=hello, bypass_queue=True) - response = self.lcd_serial.read(10) - self.lcd_serial.flushInput() + response = self.serial_read(10) + self.serial_flush_input() if len(response) != 10: logger.warning("Device not recognised (short response to HELLO)") @@ -178,9 +178,8 @@ def SetBrightness(self, level: int = 25): self.SendCommand(Command.SET_BRIGHTNESS, payload=[converted_level]) - def SetBackplateLedColor(self, led_color: Tuple[int, int, int] = (255, 255, 255)): - if isinstance(led_color, str): - led_color = tuple(map(int, led_color.split(', '))) + def SetBackplateLedColor(self, led_color: Color = (255, 255, 255)): + led_color = parse_color(led_color) if self.is_flagship(): self.SendCommand(Command.SET_LIGHTING, payload=list(led_color)) else: @@ -197,7 +196,7 @@ def SetOrientation(self, orientation: Orientation = Orientation.PORTRAIT): def DisplayPILImage( self, - image: Image, + image: Image.Image, x: int = 0, y: int = 0, image_width: int = 0, image_height: int = 0 diff --git a/library/lcd/lcd_comm_rev_c.py b/library/lcd/lcd_comm_rev_c.py index 2d2f5492..6d4dd2ba 100644 --- a/library/lcd/lcd_comm_rev_c.py +++ b/library/lcd/lcd_comm_rev_c.py @@ -22,6 +22,7 @@ import time from enum import Enum from math import ceil +from typing import Optional import serial from PIL import Image @@ -129,7 +130,7 @@ def __init__(self, command): # This class is for Turing Smart Screen 5" screens class LcdCommRevC(LcdComm): def __init__(self, com_port: str = "AUTO", display_width: int = 480, display_height: int = 800, - update_queue: queue.Queue = None): + update_queue: Optional[queue.Queue] = None): logger.debug("HW revision: C") LcdComm.__init__(self, com_port, display_width, display_height, update_queue) self.openSerial() @@ -138,7 +139,7 @@ def __del__(self): self.closeSerial() @staticmethod - def auto_detect_com_port(): + def auto_detect_com_port() -> Optional[str]: com_ports = comports() for com_port in com_ports: @@ -155,13 +156,13 @@ def _connect_to_reset_device_name(com_port): # this device enumerates differently when off, we need to connect once to reset it to correct COM device try: logger.debug(f"Waiting for device {com_port} to be turned ON...") - serial.Serial(com_port.device, 115200, timeout=1, rtscts=1) - except serial.serialutil.SerialException: + serial.Serial(com_port.device, 115200, timeout=1, rtscts=True) + except serial.SerialException: pass time.sleep(10) - def _send_command(self, cmd: Command, payload: bytearray = None, padding: Padding = None, - bypass_queue: bool = False, readsize: int = None): + def _send_command(self, cmd: Command, payload: Optional[bytearray] = None, padding: Optional[Padding] = None, + bypass_queue: bool = False, readsize: Optional[int] = None): message = bytearray() if cmd != Command.SEND_PAYLOAD: @@ -196,8 +197,8 @@ def _hello(self): # This command reads LCD answer on serial link, so it bypasses the queue self.sub_revision = SubRevision.UNKNOWN self._send_command(Command.HELLO, bypass_queue=True) - response = str(self.lcd_serial.read(22).decode()) - self.lcd_serial.flushInput() + response = str(self.serial_read(22).decode()) + self.serial_flush_input() if response.startswith(SubRevision.FIVEINCH.value): self.sub_revision = SubRevision.FIVEINCH else: @@ -264,7 +265,7 @@ def SetOrientation(self, orientation: Orientation = Orientation.PORTRAIT): def DisplayPILImage( self, - image: Image, + image: Image.Image, x: int = 0, y: int = 0, image_width: int = 0, image_height: int = 0 @@ -305,7 +306,7 @@ def DisplayPILImage( Count.Start += 1 @staticmethod - def _generate_full_image(image: Image, orientation: Orientation = Orientation.PORTRAIT): + def _generate_full_image(image: Image.Image, orientation: Orientation = Orientation.PORTRAIT): if orientation == Orientation.PORTRAIT: image = image.rotate(90, expand=True) elif orientation == Orientation.REVERSE_PORTRAIT: @@ -323,7 +324,7 @@ def _generate_full_image(image: Image, orientation: Orientation = Orientation.PO hex_data = bytearray.fromhex(image_ret) return b'\x00'.join(hex_data[i:i + 249] for i in range(0, len(hex_data), 249)) - def _generate_update_image(self, image, x, y, count, cmd: Command = None, + def _generate_update_image(self, image, x, y, count, cmd: Optional[Command] = None, orientation: Orientation = Orientation.PORTRAIT): x0, y0 = x, y diff --git a/library/lcd/lcd_comm_rev_d.py b/library/lcd/lcd_comm_rev_d.py index 1a400dcf..fcc7e100 100644 --- a/library/lcd/lcd_comm_rev_d.py +++ b/library/lcd/lcd_comm_rev_d.py @@ -41,7 +41,7 @@ class Command(Enum): # This class is for Kipye Qiye Smart Display 3.5" class LcdCommRevD(LcdComm): def __init__(self, com_port: str = "AUTO", display_width: int = 320, display_height: int = 480, - update_queue: queue.Queue = None): + update_queue: Optional[queue.Queue] = None): logger.debug("HW revision: D") LcdComm.__init__(self, com_port, display_width, display_height, update_queue) self.openSerial() @@ -50,7 +50,7 @@ def __del__(self): self.closeSerial() @staticmethod - def auto_detect_com_port(): + def auto_detect_com_port() -> Optional[str]: com_ports = comports() auto_com_port = None @@ -65,9 +65,9 @@ def WriteData(self, byteBuffer: bytearray): LcdComm.WriteData(self, byteBuffer) # Empty the input buffer after each write: we don't process acknowledgements the screen sends back - self.lcd_serial.reset_input_buffer() + self.serial_flush_input() - def SendCommand(self, cmd: Command, payload: bytearray = None, bypass_queue: bool = False): + def SendCommand(self, cmd: Command, payload: Optional[bytearray] = None, bypass_queue: bool = False): message = bytearray(cmd.value) if payload: @@ -127,7 +127,7 @@ def SetOrientation(self, orientation: Orientation = Orientation.PORTRAIT): def DisplayPILImage( self, - image: Image, + image: Image.Image, x: int = 0, y: int = 0, image_width: int = 0, image_height: int = 0 diff --git a/library/lcd/lcd_simulated.py b/library/lcd/lcd_simulated.py index c0f1f518..3d0c4d40 100644 --- a/library/lcd/lcd_simulated.py +++ b/library/lcd/lcd_simulated.py @@ -47,7 +47,8 @@ def do_GET(self): imgfile = open(SCREENSHOT_FILE, 'rb').read() mimetype = mimetypes.MimeTypes().guess_type(SCREENSHOT_FILE)[0] self.send_response(200) - self.send_header('Content-type', mimetype) + if mimetype is not None: + self.send_header('Content-type', mimetype) self.end_headers() self.wfile.write(imgfile) @@ -55,7 +56,7 @@ def do_GET(self): # Simulated display: write on a file instead of serial port class LcdSimulated(LcdComm): def __init__(self, com_port: str = "AUTO", display_width: int = 320, display_height: int = 480, - update_queue: queue.Queue = None): + update_queue: Optional[queue.Queue] = None): LcdComm.__init__(self, com_port, display_width, display_height, update_queue) self.screen_image = Image.new("RGB", (self.get_width(), self.get_height()), (255, 255, 255)) self.screen_image.save("tmp", "PNG") @@ -73,7 +74,7 @@ def __del__(self): self.closeSerial() @staticmethod - def auto_detect_com_port(): + def auto_detect_com_port() -> Optional[str]: return None def closeSerial(self): @@ -111,7 +112,7 @@ def SetOrientation(self, orientation: Orientation = Orientation.PORTRAIT): def DisplayPILImage( self, - image: Image, + image: Image.Image, x: int = 0, y: int = 0, image_width: int = 0, image_height: int = 0