Skip to content

Commit cc82f1c

Browse files
committed
Better error management: send all requests to display before exit
1 parent 3f0b334 commit cc82f1c

File tree

6 files changed

+108
-62
lines changed

6 files changed

+108
-62
lines changed

library/display.py

+3-8
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
from library import config
2-
from library.lcd_comm import Orientation
32
from library.lcd_comm_rev_a import LcdCommRevA
43
from library.lcd_comm_rev_b import LcdCommRevB
54

@@ -25,15 +24,11 @@ def __init__(self):
2524
print("Unknown display revision '", CONFIG_DATA["display"]["REVISION"], "'")
2625

2726
def initialize_display(self):
28-
# Send initialization commands
29-
self.lcd.InitializeComm()
30-
31-
# Reset screen in case it was in an unstable state
27+
# Reset screen in case it was in an unstable state (screen is also cleared)
3228
self.lcd.Reset()
3329

34-
# Clear screen (blank)
35-
self.lcd.SetOrientation(Orientation.PORTRAIT) # Bug: orientation needs to be PORTRAIT before clearing
36-
self.lcd.Clear()
30+
# Send initialization commands
31+
self.lcd.InitializeComm()
3732

3833
# Set brightness
3934
self.lcd.SetBrightness()

library/lcd_comm.py

+17
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
from abc import ABC, abstractmethod
22
from enum import IntEnum
3+
4+
import serial
35
from PIL import Image, ImageDraw, ImageFont
46

57
from library import config
@@ -44,6 +46,21 @@ def get_height() -> int:
4446

4547

4648
class LcdComm(ABC):
49+
def openSerial(self):
50+
if CONFIG_DATA['config']['COM_PORT'] == 'AUTO':
51+
lcd_com_port = self.auto_detect_com_port()
52+
self.lcd_serial = serial.Serial(lcd_com_port, 115200, timeout=1, rtscts=1)
53+
print(f"Auto detected comm port: {lcd_com_port}")
54+
else:
55+
lcd_com_port = CONFIG_DATA["config"]["COM_PORT"]
56+
print(f"Static comm port: {lcd_com_port}")
57+
self.lcd_serial = serial.Serial(lcd_com_port, 115200, timeout=1, rtscts=1)
58+
59+
@staticmethod
60+
@abstractmethod
61+
def auto_detect_com_port():
62+
pass
63+
4764
@abstractmethod
4865
def InitializeComm(self):
4966
pass

library/lcd_comm_rev_a.py

+24-15
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import struct
2+
import time
23

3-
import serial
44
from serial.tools.list_ports import comports
55

66
from library.lcd_comm import *
@@ -19,15 +19,14 @@ class Command(IntEnum):
1919

2020
class LcdCommRevA(LcdComm):
2121
def __init__(self):
22-
self.lcd_serial = None
23-
if CONFIG_DATA['config']['COM_PORT'] == 'AUTO':
24-
lcd_com_port = self.auto_detect_com_port()
25-
self.lcd_serial = serial.Serial(lcd_com_port, 115200, timeout=1, rtscts=1)
26-
print(f"Auto detected comm port: {lcd_com_port}")
27-
else:
28-
lcd_com_port = CONFIG_DATA["config"]["COM_PORT"]
29-
print(f"Static comm port: {lcd_com_port}")
30-
self.lcd_serial = serial.Serial(lcd_com_port, 115200, timeout=1, rtscts=1)
22+
self.openSerial()
23+
24+
def __del__(self):
25+
try:
26+
print("close serial")
27+
self.lcd_serial.close()
28+
except:
29+
pass
3130

3231
@staticmethod
3332
def auto_detect_com_port():
@@ -40,7 +39,7 @@ def auto_detect_com_port():
4039

4140
return auto_com_port
4241

43-
def SendCommand(self, cmd: Command, x: int, y: int, ex: int, ey: int):
42+
def SendCommand(self, cmd: Command, x: int, y: int, ex: int, ey: int, bypass_queue: bool = False):
4443
byteBuffer = bytearray(6)
4544
byteBuffer[0] = (x >> 2)
4645
byteBuffer[1] = (((x & 3) << 6) + (y >> 4))
@@ -49,9 +48,12 @@ def SendCommand(self, cmd: Command, x: int, y: int, ex: int, ey: int):
4948
byteBuffer[4] = (ey & 255)
5049
byteBuffer[5] = cmd
5150

52-
# Lock queue mutex then queue the request
53-
with config.update_queue_mutex:
54-
config.update_queue.put((self.WriteData, [byteBuffer]))
51+
if bypass_queue:
52+
self.WriteData(byteBuffer)
53+
else:
54+
# Lock queue mutex then queue the request
55+
with config.update_queue_mutex:
56+
config.update_queue.put((self.WriteData, [byteBuffer]))
5557

5658
def WriteData(self, byteBuffer: bytearray):
5759
try:
@@ -75,10 +77,17 @@ def InitializeComm(self):
7577
pass
7678

7779
def Reset(self):
78-
self.SendCommand(Command.RESET, 0, 0, 0, 0)
80+
print("Display reset...")
81+
# Reset command bypasses queue because it is run when queue threads are not yet started
82+
self.SendCommand(Command.RESET, 0, 0, 0, 0, bypass_queue=True)
83+
# Wait for display reset then reconnect
84+
time.sleep(1)
85+
self.openSerial()
7986

8087
def Clear(self):
88+
self.SetOrientation(Orientation.PORTRAIT) # Bug: orientation needs to be PORTRAIT before clearing
8189
self.SendCommand(Command.CLEAR, 0, 0, 0, 0)
90+
self.SetOrientation() # Restore default orientation
8291

8392
def ScreenOff(self):
8493
self.SendCommand(Command.SCREEN_OFF, 0, 0, 0, 0)

library/lcd_comm_rev_b.py

+24-33
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import struct
22

3-
import serial
43
from serial.tools.list_ports import comports
54

65
from library.lcd_comm import *
@@ -28,15 +27,13 @@ def get_rev_b_orientation(orientation: Orientation) -> OrientationValueRevB:
2827

2928
class LcdCommRevB(LcdComm):
3029
def __init__(self):
31-
self.lcd_serial = None
32-
if CONFIG_DATA['config']['COM_PORT'] == 'AUTO':
33-
lcd_com_port = self.auto_detect_com_port()
34-
self.lcd_serial = serial.Serial(lcd_com_port, 115200, timeout=1, rtscts=1)
35-
print(f"Auto detected comm port: {lcd_com_port}")
36-
else:
37-
lcd_com_port = CONFIG_DATA["config"]["COM_PORT"]
38-
print(f"Static comm port: {lcd_com_port}")
39-
self.lcd_serial = serial.Serial(lcd_com_port, 115200, timeout=1, rtscts=1)
30+
self.openSerial()
31+
32+
def __del__(self):
33+
try:
34+
self.lcd_serial.close()
35+
except:
36+
pass
4037

4138
@staticmethod
4239
def auto_detect_com_port():
@@ -49,7 +46,7 @@ def auto_detect_com_port():
4946

5047
return auto_com_port
5148

52-
def SendCommand(self, cmd: Command, payload=None):
49+
def SendCommand(self, cmd: Command, payload=None, bypass_queue: bool = False):
5350
# New protocol (10 byte packets, framed with the command, 8 data bytes inside)
5451
if payload is None:
5552
payload = [0] * 8
@@ -68,9 +65,12 @@ def SendCommand(self, cmd: Command, payload=None):
6865
byteBuffer[8] = payload[7]
6966
byteBuffer[9] = cmd
7067

71-
# Lock queue mutex then queue the request
72-
with config.update_queue_mutex:
73-
config.update_queue.put((self.WriteData, [byteBuffer]))
68+
if bypass_queue:
69+
self.WriteData(byteBuffer)
70+
else:
71+
# Lock queue mutex then queue the request
72+
with config.update_queue_mutex:
73+
config.update_queue.put((self.WriteData, [byteBuffer]))
7474

7575
def WriteData(self, byteBuffer: bytearray):
7676
try:
@@ -90,28 +90,17 @@ def WriteLine(self, line: bytes):
9090
print("(Write line) Too fast! Slow down!")
9191

9292
def Hello(self):
93-
# This command reads LCD answer on serial link, so it bypasses the queue
94-
byteBuffer = bytearray(10)
95-
byteBuffer[0] = Command.HELLO
96-
byteBuffer[1] = ord('H')
97-
byteBuffer[2] = ord('E')
98-
byteBuffer[3] = ord('L')
99-
byteBuffer[4] = ord('L')
100-
byteBuffer[5] = ord('O')
101-
byteBuffer[6] = 0
102-
byteBuffer[7] = 0
103-
byteBuffer[8] = 0
104-
byteBuffer[9] = Command.HELLO
93+
hello = [ord('H'), ord('E'), ord('L'), ord('L'), ord('O')]
10594

106-
with config.update_queue_mutex:
107-
self.WriteData(byteBuffer)
108-
response = self.lcd_serial.read(10)
95+
# This command reads LCD answer on serial link, so it bypasses the queue
96+
self.SendCommand(Command.HELLO, payload=hello, bypass_queue=True)
97+
response = self.lcd_serial.read(10)
10998

11099
if len(response) != 10:
111100
print("Device not recognised (short response to HELLO)")
112101
if response[0] != Command.HELLO or response[-1] != Command.HELLO:
113102
print("Device not recognised (bad framing)")
114-
if [x for x in response[1:6]] != byteBuffer[1:6]:
103+
if [x for x in response[1:6]] != hello:
115104
print("Device not recognised (No HELLO; got %r)" % (response[1:6],))
116105
# The HELLO response here is followed by:
117106
# 0x0A, 0x12, 0x00
@@ -125,11 +114,13 @@ def InitializeComm(self):
125114
self.Hello()
126115

127116
def Reset(self):
128-
# HW revision B does not implement a command to reset it
129-
pass
117+
# HW revision B does not implement a command to reset it: clear the screen instead
118+
self.Clear()
130119

131120
def Clear(self):
132-
pass
121+
# HW revision B does not implement a Clear command: display a blank image on the whole screen
122+
blank = Image.new("RGB", (get_width(), get_height()), (255, 255, 255))
123+
self.DisplayPILImage(blank)
133124

134125
def ScreenOff(self):
135126
# HW revision B does not implement a "ScreenOff" native command: using SetBrightness(0) instead

library/scheduler.py

+23-5
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99

1010
THEME_DATA = config.THEME_DATA
1111

12+
STOPPING = False
13+
1214

1315
def async_job(threadname=None):
1416
""" wrapper to handle asynchronous threads """
@@ -36,8 +38,11 @@ def decorator(func):
3638

3739
def periodic(scheduler, periodic_interval, action, actionargs=()):
3840
""" Wrap the scheduler with our periodic interval """
39-
scheduler.enter(periodic_interval, 1, periodic,
40-
(scheduler, periodic_interval, action, actionargs))
41+
global STOPPING
42+
if not STOPPING:
43+
# If the program is not stopping: re-schedule the task for future execution
44+
scheduler.enter(periodic_interval, 1, periodic,
45+
(scheduler, periodic_interval, action, actionargs))
4146
action(*actionargs)
4247

4348
@wraps(func)
@@ -120,6 +125,19 @@ def DiskStats():
120125
@async_job("Queue_Handler")
121126
@schedule(timedelta(milliseconds=1).total_seconds())
122127
def QueueHandler():
123-
f, args = config.update_queue.get()
124-
if f:
125-
f(*args)
128+
global STOPPING
129+
130+
if STOPPING:
131+
# Empty the message queue to allow program to exit cleanly
132+
while not config.update_queue.empty():
133+
f, args = config.update_queue.get()
134+
f(*args)
135+
else:
136+
# Execute first action in the queue
137+
f, args = config.update_queue.get()
138+
if f:
139+
f(*args)
140+
141+
142+
def is_queue_empty() -> bool:
143+
return config.update_queue.empty()

main.py

+17-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import os
66
import signal
77
import sys
8+
import time
89

910
MIN_PYTHON = (3, 7)
1011
if sys.version_info < MIN_PYTHON:
@@ -20,12 +21,27 @@
2021
if __name__ == "__main__":
2122

2223
def sighandler(signum, frame):
23-
print(" Caught signal ", str(signum), ", exiting...")
24+
print(" Caught signal ", str(signum), ", exiting")
25+
26+
# Do not stop the program now in case data transmission was in progress
27+
# Instead, ask the scheduler to finish its current task before stopping
28+
scheduler.STOPPING = True
29+
30+
print("Waiting for all pending request to be sent to display...")
31+
32+
# Allow 2 seconds max. delay in case scheduler is not responding
33+
wait_time = 2
34+
while not scheduler.is_queue_empty() and wait_time > 0:
35+
time.sleep(0.1)
36+
wait_time = wait_time - 0.1
37+
38+
# We force the exit to avoid waiting for other scheduled tasks: they may have a long delay!
2439
try:
2540
sys.exit(0)
2641
except:
2742
os._exit(0)
2843

44+
2945
# Set the signal handlers, to send a complete frame to the LCD before exit
3046
signal.signal(signal.SIGINT, sighandler)
3147
signal.signal(signal.SIGTERM, sighandler)

0 commit comments

Comments
 (0)