From 3fd76d8d143e17897b1a138d5c73fbd380d0cceb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20W=20Baul=C3=A9?= Date: Thu, 29 Jun 2023 22:09:51 -0300 Subject: [PATCH 1/6] Force restart device on serial error. With this commit, when the devices stops read/write will clean the queue, reset the device with USB Command, and send "SIGTERM" signal to himself, to stop the daemon. In linux, with "Restart=always" in service file, will restart the service, fixing some read/write problem. --- library/lcd/lcd_comm.py | 49 +++++++++++++++++++++-------------- library/lcd/lcd_comm_rev_a.py | 2 ++ library/lcd/lcd_comm_rev_b.py | 2 ++ library/lcd/lcd_comm_rev_c.py | 2 ++ 4 files changed, 36 insertions(+), 19 deletions(-) diff --git a/library/lcd/lcd_comm.py b/library/lcd/lcd_comm.py index b15f42d7..a6663f85 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) @@ -88,7 +95,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=5, rtscts=1, write_timeout=5) except Exception as e: logger.error(f"Cannot open COM port {self.com_port}: {e}") try: @@ -116,32 +123,18 @@ 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'))) 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 +149,24 @@ def InitializeComm(self): def Reset(self): pass + def reset_by_usb(self): + 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) + 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..6bf4799e 100644 --- a/library/lcd/lcd_comm_rev_a.py +++ b/library/lcd/lcd_comm_rev_a.py @@ -41,6 +41,8 @@ def __init__(self, com_port: str = "AUTO", display_width: int = 320, display_hei update_queue: queue.Queue = None): LcdComm.__init__(self, com_port, display_width, display_height, update_queue) self.openSerial() + self.idVendor = 0x1a86 + self.idProduct = 0x5722 def __del__(self): self.closeSerial() diff --git a/library/lcd/lcd_comm_rev_b.py b/library/lcd/lcd_comm_rev_b.py index 7a022da3..4b77ac0d 100644 --- a/library/lcd/lcd_comm_rev_b.py +++ b/library/lcd/lcd_comm_rev_b.py @@ -53,6 +53,8 @@ def __init__(self, com_port: str = "AUTO", display_width: int = 320, display_hei update_queue: queue.Queue = None): LcdComm.__init__(self, com_port, display_width, display_height, update_queue) self.openSerial() + self.idVendor = 0x1a86 + self.idProduct = 0x5722 self.sub_revision = SubRevision.A01 # Run a Hello command to detect correct sub-rev. def __del__(self): diff --git a/library/lcd/lcd_comm_rev_c.py b/library/lcd/lcd_comm_rev_c.py index 65f1788c..30a494c9 100644 --- a/library/lcd/lcd_comm_rev_c.py +++ b/library/lcd/lcd_comm_rev_c.py @@ -134,6 +134,8 @@ def __init__(self, com_port: str = "AUTO", display_width: int = 480, display_hei update_queue: queue.Queue = None): LcdComm.__init__(self, com_port, display_width, display_height, update_queue) self.openSerial() + self.idVendor = 0x1d6b + self.idProduct = 0x0106 def __del__(self): self.closeSerial() From 3af3dca63255c4eed7ee2e0ebf4c54db83c1ceb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20W=20Baul=C3=A9?= Date: Thu, 29 Jun 2023 22:31:14 -0300 Subject: [PATCH 2/6] Check if is None (device does not exist) --- library/lcd/lcd_comm.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/library/lcd/lcd_comm.py b/library/lcd/lcd_comm.py index a6663f85..79c2304b 100644 --- a/library/lcd/lcd_comm.py +++ b/library/lcd/lcd_comm.py @@ -161,7 +161,8 @@ def reset_by_usb(self): logger.info(f"Reseting device via USB queue cleaned: {self.update_queue.empty()}") try: dev = finddev(idVendor=self.idVendor, idProduct=self.idProduct) - dev.reset() + if dev is not None: + dev.reset() os.kill(os.getpid(), signal.SIGTERM) except USBError or OSError: logger.info("Error reseting device via USB...") From 2f8553fb0a8306ef81233392c08e26c5609b82b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20W=20Baul=C3=A9?= Date: Tue, 4 Jul 2023 22:23:31 -0300 Subject: [PATCH 3/6] Update detection Pid/Vid. Add sleep service to hook suspend/resume and restart the service. --- config.yaml | 6 +++--- library/lcd/lcd_comm.py | 10 ++++++++-- library/lcd/lcd_comm_rev_a.py | 7 ++----- library/lcd/lcd_comm_rev_b.py | 7 ++----- library/lcd/lcd_comm_rev_c.py | 4 +--- tools/sleep@turing-smart-screen-python.service | 14 ++++++++++++++ 6 files changed, 30 insertions(+), 18 deletions(-) create mode 100644 tools/sleep@turing-smart-screen-python.service diff --git a/config.yaml b/config.yaml index fe57e45d..29045a15 100644 --- a/config.yaml +++ b/config.yaml @@ -9,7 +9,7 @@ config: # Theme to use (located in res/themes) # Use the name of the folder as value - THEME: 3.5inchTheme2 + THEME: DragonBall5inch # Hardware sensors reading # Choose the appropriate method for reading your hardware sensors: @@ -23,14 +23,14 @@ config: # Linux/MacOS interfaces are named "eth0", "wlan0", "wlp1s0", "enp2s0"... # For Windows use the interfaces pretty name: "Ethernet 2", "Wi-Fi", ... # Leave the fields empty if the card does not exist on your setup - ETH: "" # Ethernet Card + ETH: "enp4s0" # Ethernet Card WLO: "" # Wi-Fi Card display: # Display revision: A for Turing 3.5", B for Xuanfang 3.5" (inc. flagship), C for Turing 5" # Use SIMU for 3.5" simulated LCD (image written in screencap.png) or SIMU5 for 5" simulated LCD # To identify your revision: https://github.com/mathoudebine/turing-smart-screen-python/wiki/Hardware-revisions - REVISION: A + REVISION: C # Display Brightness # Set this as the desired %, 0 being completely dark and 100 being max brightness diff --git a/library/lcd/lcd_comm.py b/library/lcd/lcd_comm.py index 79c2304b..90293633 100644 --- a/library/lcd/lcd_comm.py +++ b/library/lcd/lcd_comm.py @@ -80,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: @@ -90,6 +91,7 @@ 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}") @@ -131,6 +133,10 @@ 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.SerialException: logger.warning("(ReadData) error, reseting device.") diff --git a/library/lcd/lcd_comm_rev_a.py b/library/lcd/lcd_comm_rev_a.py index 6bf4799e..c922e307 100644 --- a/library/lcd/lcd_comm_rev_a.py +++ b/library/lcd/lcd_comm_rev_a.py @@ -41,8 +41,6 @@ def __init__(self, com_port: str = "AUTO", display_width: int = 320, display_hei update_queue: queue.Queue = None): LcdComm.__init__(self, com_port, display_width, display_height, update_queue) self.openSerial() - self.idVendor = 0x1a86 - self.idProduct = 0x5722 def __del__(self): self.closeSerial() @@ -50,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 4b77ac0d..2984bc32 100644 --- a/library/lcd/lcd_comm_rev_b.py +++ b/library/lcd/lcd_comm_rev_b.py @@ -53,8 +53,6 @@ def __init__(self, com_port: str = "AUTO", display_width: int = 320, display_hei update_queue: queue.Queue = None): LcdComm.__init__(self, com_port, display_width, display_height, update_queue) self.openSerial() - self.idVendor = 0x1a86 - self.idProduct = 0x5722 self.sub_revision = SubRevision.A01 # Run a Hello command to detect correct sub-rev. def __del__(self): @@ -69,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 30a494c9..61698803 100644 --- a/library/lcd/lcd_comm_rev_c.py +++ b/library/lcd/lcd_comm_rev_c.py @@ -134,8 +134,6 @@ def __init__(self, com_port: str = "AUTO", display_width: int = 480, display_hei update_queue: queue.Queue = None): LcdComm.__init__(self, com_port, display_width, display_height, update_queue) self.openSerial() - self.idVendor = 0x1d6b - self.idProduct = 0x0106 def __del__(self): self.closeSerial() @@ -149,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 From a2445040d00471ba059c207847d871b20df18c3e Mon Sep 17 00:00:00 2001 From: Matthieu Houdebine Date: Tue, 4 Jul 2023 09:37:37 +0200 Subject: [PATCH 4/6] Update README.md --- README.md | 3 +++ 1 file changed, 3 insertions(+) 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) From 5a21541384439d84f7920843aff6aa05404f117d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20W=20Baul=C3=A9?= Date: Tue, 4 Jul 2023 22:37:37 -0300 Subject: [PATCH 5/6] default config --- config.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/config.yaml b/config.yaml index 29045a15..fe57e45d 100644 --- a/config.yaml +++ b/config.yaml @@ -9,7 +9,7 @@ config: # Theme to use (located in res/themes) # Use the name of the folder as value - THEME: DragonBall5inch + THEME: 3.5inchTheme2 # Hardware sensors reading # Choose the appropriate method for reading your hardware sensors: @@ -23,14 +23,14 @@ config: # Linux/MacOS interfaces are named "eth0", "wlan0", "wlp1s0", "enp2s0"... # For Windows use the interfaces pretty name: "Ethernet 2", "Wi-Fi", ... # Leave the fields empty if the card does not exist on your setup - ETH: "enp4s0" # Ethernet Card + ETH: "" # Ethernet Card WLO: "" # Wi-Fi Card display: # Display revision: A for Turing 3.5", B for Xuanfang 3.5" (inc. flagship), C for Turing 5" # Use SIMU for 3.5" simulated LCD (image written in screencap.png) or SIMU5 for 5" simulated LCD # To identify your revision: https://github.com/mathoudebine/turing-smart-screen-python/wiki/Hardware-revisions - REVISION: C + REVISION: A # Display Brightness # Set this as the desired %, 0 being completely dark and 100 being max brightness From 7110730185bc7f476f74bff31a5613961b8445a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20W=20Baul=C3=A9?= Date: Mon, 10 Jul 2023 10:27:12 -0300 Subject: [PATCH 6/6] If has idProduct and idVendor --- library/lcd/lcd_comm.py | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/library/lcd/lcd_comm.py b/library/lcd/lcd_comm.py index 90293633..b4638670 100644 --- a/library/lcd/lcd_comm.py +++ b/library/lcd/lcd_comm.py @@ -156,22 +156,23 @@ def Reset(self): pass def reset_by_usb(self): - 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(): + 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: - 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...") + 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