OLED screen: Clean exit on error; proper handling of removable devices; signal handling; splash screen (#364)

* Clean exit on errors

* Avoid unnecessary scrolling

* Fix bug where removable media wasn't updated

* Add signal handler, solution from https://stackoverflow.com/questions/1112343/how-do-i-capture-sigint-in-python

* Display a splash screen on startup

* Introduce stop splash

* Add parameters to enable the SIGTERM handling to take effect

* Change to a less busy stop splash

* Less busy start splash

* Iterate on splash bitmaps

* Iterate on splash bitmap

* Add check for libopenjp2-7-dev in the start script

* Iterate on splash bitmap

* Iterate on splash bitmap
This commit is contained in:
Daniel Markstedt 2021-10-24 19:13:32 -07:00 committed by GitHub
parent 960cc91168
commit e3794619b4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 92 additions and 28 deletions

View File

@ -6,6 +6,9 @@ After=network.target rascsi.service
Type=simple
Restart=always
ExecStart=/home/pi/RASCSI/src/oled_monitor/start.sh
ExecStop=/bin/echo "Shutting down the OLED Monitor gracefully..."
ExecStop=/bin/pkill --signal 2 -f "python3 rascsi_oled_monitor.py"
ExecStop=/bin/sleep 2
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=RASCSIMON

View File

@ -30,16 +30,49 @@
# THE SOFTWARE.
from time import sleep
from sys import argv, exit
import logging
from board import I2C
from adafruit_ssd1306 import SSD1306_I2C
from PIL import Image, ImageDraw, ImageFont
from os import path
from os import path, getcwd
from collections import deque
from struct import pack, unpack
import signal
import socket
import rascsi_interface_pb2 as proto
class GracefulInterruptHandler(object):
def __init__(self, signals=(signal.SIGINT, signal.SIGTERM)):
self.signals = signals
self.original_handlers = {}
def __enter__(self):
self.interrupted = False
self.released = False
for sig in self.signals:
self.original_handlers[sig] = signal.getsignal(sig)
signal.signal(sig, self.handler)
return self
def handler(self, signum, frame):
self.release()
self.interrupted = True
def __exit__(self, type, value, tb):
self.release()
def release(self):
if self.released:
return False
for sig in self.signals:
signal.signal(sig, self.original_handlers[sig])
self.released = True
return True
WIDTH = 128
HEIGHT = 32 # Change to 64 if needed
BORDER = 5
@ -186,11 +219,11 @@ def send_pb_command(payload):
return send_over_socket(s, payload)
except socket.error as error:
counter += 1
logging.warning("The RaSCSI service is not responding - attempt " + \
print("The RaSCSI service is not responding - attempt " + \
str(counter) + "/" + str(tries))
error_msg = str(error)
logging.error(error_msg)
exit(error_msg)
def send_over_socket(s, payload):
@ -216,7 +249,7 @@ def send_over_socket(s, payload):
while bytes_recvd < response_length:
chunk = s.recv(min(response_length - bytes_recvd, 2048))
if chunk == b'':
logging.error("Read an empty chunk from the socket. \
exit("Read an empty chunk from the socket. \
Socket connection has dropped unexpectedly. \
RaSCSI may have has crashed.")
chunks.append(chunk)
@ -224,7 +257,7 @@ def send_over_socket(s, payload):
response_message = b''.join(chunks)
return response_message
else:
logging.error("The response from RaSCSI did not contain a protobuf header. \
exit("The response from RaSCSI did not contain a protobuf header. \
RaSCSI may have crashed.")
@ -255,29 +288,52 @@ def formatted_output():
output.append(f"~~RaSCSI v{version}~~")
return output
def start_splash():
splash = Image.open(f"{cwd}/splash_start.bmp").convert("1")
draw.bitmap((0, 0), splash)
oled.image(splash)
oled.show()
sleep(6)
def stop_splash():
draw.rectangle((0,0,WIDTH,HEIGHT), outline=0, fill=0)
splash = Image.open(f"{cwd}/splash_stop.bmp").convert("1")
draw.bitmap((0, 0), splash)
oled.image(splash)
oled.show()
cwd = getcwd()
start_splash()
version = rascsi_version()
while True:
with GracefulInterruptHandler() as h:
while True:
snapshot = deque(formatted_output())
output = snapshot
ref_snapshot = formatted_output()
snapshot = ref_snapshot
output = deque(snapshot)
while len(snapshot) == len(output):
# Draw a black filled box to clear the image.
draw.rectangle((0,0,WIDTH,HEIGHT), outline=0, fill=0)
y_pos = top
for line in output:
draw.text((x, y_pos), line, font=font, fill=255)
y_pos += 8
while snapshot == ref_snapshot:
# Draw a black filled box to clear the image.
draw.rectangle((0,0,WIDTH,HEIGHT), outline=0, fill=0)
y_pos = top
for line in output:
draw.text((x, y_pos), line, font=font, fill=255)
y_pos += 8
#if len(snapshot) > 4:
output.rotate(-1)
# Shift the index of the array by one to get a scrolling effect
if len(output) > 5:
output.rotate(-1)
# Display image.
oled.image(image)
oled.show()
sleep(1000/delay_time_ms)
# Display image.
oled.image(image)
oled.show()
sleep(1000/delay_time_ms)
snapshot = deque(formatted_output())
if len(snapshot) < 5:
break
snapshot = formatted_output()
if h.interrupted:
stop_splash()
exit("Shutting down the OLED display...")

Binary file not shown.

After

Width:  |  Height:  |  Size: 574 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 658 B

View File

@ -15,6 +15,11 @@ if ! command -v python3 &> /dev/null ; then
echo "Run 'sudo apt install python3' to fix."
ERROR=1
fi
if ! python3 -m venv --help &> /dev/null ; then
echo "venv could not be found"
echo "Run 'sudo apt install python3-venv' to fix."
ERROR=1
fi
# Dep to build Pillow
if ! dpkg -l python3-dev &> /dev/null; then
echo "python3-dev could not be found"
@ -31,9 +36,9 @@ if ! dpkg -l libpng-dev &> /dev/null; then
echo "Run 'sudo apt install libpng-dev' to fix."
ERROR=1
fi
if ! python3 -m venv --help &> /dev/null ; then
echo "venv could not be found"
echo "Run 'sudo apt install python3-venv' to fix."
if ! dpkg -l libopenjp2-7-dev &> /dev/null; then
echo "libopenjp2-7-dev could not be found"
echo "Run 'sudo apt install libopenjp2-7-dev' to fix."
ERROR=1
fi
if [ $ERROR = 1 ] ; then