diff --git a/README.md b/README.md index d20aacd8..4d8fbac0 100644 --- a/README.md +++ b/README.md @@ -94,4 +94,7 @@ Check `simple-program.py` as an example. ## Troubleshooting If you have trouble running the program as described in the wiki, please check [open/closed issues](https://github.com/mathoudebine/turing-smart-screen-python/issues) & [the wiki Troubleshooting page](https://github.com/mathoudebine/turing-smart-screen-python/wiki/Troubleshooting) +## Star History + +[![Star History Chart](https://api.star-history.com/svg?repos=mathoudebine/turing-smart-screen-python&type=Date)](https://star-history.com/#mathoudebine/turing-smart-screen-python&Date) diff --git a/library/lcd/lcd_comm.py b/library/lcd/lcd_comm.py index b15f42d7..b4638670 100644 --- a/library/lcd/lcd_comm.py +++ b/library/lcd/lcd_comm.py @@ -18,6 +18,7 @@ import os import queue +import signal import sys import threading from abc import ABC, abstractmethod @@ -28,6 +29,8 @@ from PIL import Image, ImageDraw, ImageFont from library.log import logger +from usb.core import find as finddev +from usb.core import USBError class Orientation(IntEnum): @@ -45,6 +48,10 @@ def __init__(self, com_port: str = "AUTO", display_width: int = 320, display_hei # String containing absolute path to serial port e.g. "COM3", "/dev/ttyACM1" or "AUTO" for auto-discovery self.com_port = com_port + # This is used to reset the device if the computer sleeps and freeze the display. + self.idVendor = None + self.idProduct = None + # Display always start in portrait orientation by default self.orientation = Orientation.PORTRAIT # Display width in default orientation (portrait) @@ -73,9 +80,10 @@ def get_height(self) -> int: return self.display_width def openSerial(self): + com_port, self.idVendor, self.idProduct = self.auto_detect_com_port() + if self.com_port == 'AUTO': - self.com_port = self.auto_detect_com_port() - if not self.com_port: + if not com_port: logger.error( "Cannot find COM port automatically, please run Configuration again and select COM port manually") try: @@ -83,12 +91,13 @@ def openSerial(self): except: os._exit(0) else: + self.com_port = com_port logger.debug(f"Auto detected COM port: {self.com_port}") else: 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=5, rtscts=1, write_timeout=5) except Exception as e: logger.error(f"Cannot open COM port {self.com_port}: {e}") try: @@ -116,32 +125,22 @@ def SendLine(self, line: bytes): def WriteLine(self, line: bytes): try: self.lcd_serial.write(line) - except serial.serialutil.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: - # 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() - self.openSerial() - self.lcd_serial.write(line) + logger.warning("(WriteLine) error, reseting device.") + self.reset_by_usb() def ReadData(self, readSize: int): try: response = self.lcd_serial.read(readSize) # logger.debug("Received: [{}]".format(str(response, 'utf-8'))) + r = str(response) + if r.startswith("needReSend:1", 0, 24): + logger.warning("(ReadData) received needReSend, call hello..") + self.InitializeComm() return response - except serial.serialutil.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: - # 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() - self.openSerial() - return self.lcd_serial.read(readSize) + logger.warning("(ReadData) error, reseting device.") + self.reset_by_usb() @staticmethod @abstractmethod @@ -156,6 +155,26 @@ def InitializeComm(self): def Reset(self): pass + def reset_by_usb(self): + if self.idProduct is not None and not self.idVendor is not None: + logger.info(f"Reseting device via USB...cleaning all {self.update_queue.qsize()} queue entries") + with self.update_queue_mutex: + while not self.update_queue.empty(): + try: + self.update_queue.get() + except queue.Empty: + continue + self.update_queue.task_done() + logger.info(f"Reseting device via USB queue cleaned: {self.update_queue.empty()}") + try: + dev = finddev(idVendor=self.idVendor, idProduct=self.idProduct) + if dev is not None: + dev.reset() + os.kill(os.getpid(), signal.SIGTERM) + except USBError or OSError: + logger.info("Error reseting device via USB...") + pass + @abstractmethod def Clear(self): pass diff --git a/library/lcd/lcd_comm_rev_a.py b/library/lcd/lcd_comm_rev_a.py index 2685eeea..c922e307 100644 --- a/library/lcd/lcd_comm_rev_a.py +++ b/library/lcd/lcd_comm_rev_a.py @@ -48,13 +48,12 @@ def __del__(self): @staticmethod def auto_detect_com_port(): com_ports = comports() - auto_com_port = None for com_port in com_ports: if com_port.serial_number == "USB35INCHIPSV2": - auto_com_port = com_port.device + return com_port.device, com_port.vid, com_port.pid - return auto_com_port + return None def SendCommand(self, cmd: Command, x: int, y: int, ex: int, ey: int, bypass_queue: bool = False): byteBuffer = bytearray(6) diff --git a/library/lcd/lcd_comm_rev_b.py b/library/lcd/lcd_comm_rev_b.py index 7a022da3..2984bc32 100644 --- a/library/lcd/lcd_comm_rev_b.py +++ b/library/lcd/lcd_comm_rev_b.py @@ -67,13 +67,12 @@ def is_brightness_range(self): @staticmethod def auto_detect_com_port(): com_ports = comports() - auto_com_port = None for com_port in com_ports: if com_port.serial_number == "2017-2-25": - auto_com_port = com_port.device + return com_port.device, com_port.vid, com_port.pid - return auto_com_port + return None def SendCommand(self, cmd: Command, payload=None, bypass_queue: bool = False): # New protocol (10 byte packets, framed with the command, 8 data bytes inside) diff --git a/library/lcd/lcd_comm_rev_c.py b/library/lcd/lcd_comm_rev_c.py index 65f1788c..61698803 100644 --- a/library/lcd/lcd_comm_rev_c.py +++ b/library/lcd/lcd_comm_rev_c.py @@ -147,7 +147,7 @@ def auto_detect_com_port(): LcdCommRevC._connect_to_reset_device_name(com_port) return LcdCommRevC.auto_detect_com_port() if com_port.serial_number == '20080411': - return com_port.device + return com_port.device, com_port.vid, com_port.pid return None diff --git a/tools/sleep@turing-smart-screen-python.service b/tools/sleep@turing-smart-screen-python.service new file mode 100644 index 00000000..5393b8be --- /dev/null +++ b/tools/sleep@turing-smart-screen-python.service @@ -0,0 +1,14 @@ +# Just eneble it. Dont need to "start/stop" it. +[Unit] +Description=%I sleep hook +Before=sleep.target +StopWhenUnneeded=yes + +[Service] +Type=oneshot +RemainAfterExit=yes +ExecStart=-/usr/bin/systemctl stop %i +ExecStop=-/usr/bin/systemctl start %i + +[Install] +WantedBy=sleep.target \ No newline at end of file