Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Force restart device on serial error. #274

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

61 changes: 40 additions & 21 deletions library/lcd/lcd_comm.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import os
import queue
import signal
import sys
import threading
from abc import ABC, abstractmethod
Expand All @@ -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):
Expand All @@ -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)
Expand Down Expand Up @@ -73,22 +80,24 @@ 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:
sys.exit(0)
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:
Expand Down Expand Up @@ -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()
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's a shame to always reset USB and stop the program, when in some cases just the closeSerial/openSerial/write is enough to keep the program running (people have confirmed this in #269)
Maybe the closeSerial/openSerial/write can be kept, and then only on exception the USB is reset?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a good question... In case of desktop suspend/resume, only close and reopen the serial, don't work in 5 pol version.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By the way, the official app on windows, send reset USB in case of errors in serial. It's try to open serial 2 times, if don't work, it's send the reset. I checked this behavior with Wireshark in USB.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The 5 pol version its very strict to send images, updates, if the order is not following, it's doesn't work.
For me, the correct is to stop all threads, reset USB and start again, but it's a massive change to the code, stop the program has less work to do, because the systemd cares about restart it.


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
Expand All @@ -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
Expand Down
5 changes: 2 additions & 3 deletions library/lcd/lcd_comm_rev_a.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
5 changes: 2 additions & 3 deletions library/lcd/lcd_comm_rev_b.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion library/lcd/lcd_comm_rev_c.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
14 changes: 14 additions & 0 deletions tools/sleep@turing-smart-screen-python.service
Original file line number Diff line number Diff line change
@@ -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