Shutdown splash for the OLED script; restore non-Latin transliteration; other refactoring (#991)

- Bring back the shutdown splash, with the tweak that is blanks out after 700ms
- Restore the non-Latin transliteration originally from https://github.com/akuker/RASCSI/pull/449 which was lost when the `common` package was introduced
- Bump to the latest libraries, while removing implicit dependencies from requirements.txt
- Shorter duration of the startup splash
- Reintroduce shell shutdown/reboot methods for use with the Web UI. This addresses https://github.com/akuker/RASCSI/issues/538 (the ctrlboard client will continue to use the built-in rascsi system calls which are slightly faster)
- Remove overt references to RPi. This addresses https://github.com/akuker/RASCSI/issues/990
- Other refactorings
This commit is contained in:
Daniel Markstedt 2022-11-17 16:21:18 -08:00 committed by GitHub
parent 8c5dcd2f49
commit 4afb11d3dd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 93 additions and 53 deletions

View File

@ -445,10 +445,12 @@ class RaCtlCmds:
result.ParseFromString(data)
return {"status": result.status, "msg": result.msg}
def shutdown_pi(self, mode):
def shutdown(self, mode):
"""
Sends a SHUT_DOWN command to the server.
Takes (str) mode as an argument.
The backend will use system calls to reboot or shut down the system.
It can also shut down the backend process itself.
Returns (bool) status and (str) msg.
"""
command = proto.PbCommand()

View File

@ -223,3 +223,31 @@ class SysCmds:
return process.returncode, process.stdout.decode("utf-8")
return process.returncode, process.stderr.decode("utf-8")
@staticmethod
def reboot_system():
"""
Sends a reboot command to the system
"""
process = run(
["sudo", "reboot"],
capture_output=True,
)
if process.returncode == 0:
return process.returncode, process.stdout.decode("utf-8")
return process.returncode, process.stderr.decode("utf-8")
@staticmethod
def shutdown_system():
"""
Sends a shutdown command to the system
"""
process = run(
["sudo", "shutdown", "-h", "now"],
capture_output=True,
)
if process.returncode == 0:
return process.returncode, process.stdout.decode("utf-8")
return process.returncode, process.stderr.decode("utf-8")

View File

@ -193,7 +193,7 @@ class CtrlBoardMenuUpdateEventHandler(Observer):
# noinspection PyUnusedLocal
def handle_action_menu_shutdown(self, info_object):
"""Method handles the rotary button press on 'Shutdown' in the action menu."""
self.ractl_cmd.shutdown_pi("system")
self.ractl_cmd.shutdown("system")
self._menu_controller.show_message("Shutting down!", 150)
self._menu_controller.segue(CtrlBoardMenuBuilder.SCSI_ID_MENU,
transition_attributes=self._menu_renderer_config.

View File

@ -19,7 +19,7 @@ class RascsiShutdownCycler(Cycler):
if self.executed_once is False:
self.executed_once = True
self._menu_controller.show_timed_message("Shutting down...")
self.ractl_cmd.shutdown_pi("system")
self.ractl_cmd.shutdown("system")
return "shutdown"
return None

View File

@ -1,16 +1,5 @@
Adafruit-Blinka==6.15.0
adafruit-circuitpython-busdevice==5.1.0
adafruit-circuitpython-framebuf==1.4.7
adafruit-circuitpython-ssd1306==2.12.2
Adafruit-PlatformDetect==3.17.2
Adafruit-PureIO==1.1.9
Pillow==9.0.1
pkg-resources==0.0.0
pyftdi==0.53.3
pyserial==3.5
pyusb==1.2.1
rpi-ws281x==4.3.0
RPi.GPIO==0.7.0
sysv-ipc==1.1.0
protobuf==3.19.5
unidecode==1.3.2
adafruit-circuitpython-ssd1306==2.12.11
Pillow==9.3.0
protobuf==3.20.2
unidecode==1.3.6

Binary file not shown.

After

Width:  |  Height:  |  Size: 574 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -35,6 +35,7 @@ import argparse
import sys
from time import sleep
from collections import deque
from unidecode import unidecode
from board import I2C
from adafruit_ssd1306 import SSD1306_I2C
from PIL import Image, ImageDraw, ImageFont
@ -125,19 +126,11 @@ print("Will update the OLED display every " + str(DELAY_TIME_MS) + "ms (approxim
# Show a startup splash bitmap image before starting the main loop
# Convert the image to mode '1' for 1-bit color (monochrome)
# Make sure the splash bitmap image is in the same dir as this script
IMAGE_STOP = Image.open(f"resources/splash_stop_{HEIGHT}.bmp").convert("1")
IMAGE = Image.open(f"resources/splash_start_{HEIGHT}.bmp").convert("1")
OLED.image(IMAGE)
OLED.show()
# Keep the pretty splash on screen for a number of seconds
sleep(4)
# Get drawing object to draw on image.
DRAW = ImageDraw.Draw(IMAGE)
# Draw a black filled box to clear the image.
DRAW.rectangle((0, 0, WIDTH, HEIGHT), outline=0, fill=0)
# Draw some shapes.
# First define some constants to allow easy resizing of shapes.
# Depending on the font used, you may want to change the value of PADDING
@ -164,6 +157,15 @@ IP_ADDR, HOSTNAME = sys_cmd.get_ip_and_host()
REMOVABLE_DEVICE_TYPES = ractl_cmd.get_removable_device_types()
PERIPHERAL_DEVICE_TYPES = ractl_cmd.get_peripheral_device_types()
# Keep the pretty splash up on screen for a number of seconds
sleep(2)
# Get drawing object to draw on image.
DRAW = ImageDraw.Draw(IMAGE)
# Draw a black filled box to clear the image.
DRAW.rectangle((0, 0, WIDTH, HEIGHT), outline=0, fill=0)
def formatted_output():
"""
Formats the strings to be displayed on the Screen
@ -176,6 +178,9 @@ def formatted_output():
output.append("Permission denied!")
elif rascsi_list:
for line in rascsi_list:
# Transliterate non-Latin characters
if line["file"]:
line["file"] = unidecode(line["file"])
if line["device_type"] in REMOVABLE_DEVICE_TYPES:
# Print image file name only when there is an image attached
if line["file"]:
@ -207,10 +212,24 @@ def formatted_output():
output.append("Check network connection")
return output
def shutdown():
"""
Display the shutdown splash, then blank the screen after a sleep
Finally shuts down the script
"""
OLED.image(IMAGE_STOP)
OLED.show()
OLED.fill(0)
sleep(700/1000)
OLED.show()
sys.exit("Shutting down the OLED display...")
with GracefulInterruptHandler() as handler:
"""
The main display loop inside an interrupt handler
"""
while True:
# The reference snapshot of attached devices that will be compared against each cycle
# to identify changes in RaSCSI backend
ref_snapshot = formatted_output()
@ -239,8 +258,4 @@ with GracefulInterruptHandler() as handler:
snapshot = formatted_output()
if handler.interrupted:
# Catch interrupt signals and blank out the screen
DRAW.rectangle((0, 0, WIDTH, HEIGHT), outline=0, fill=0)
OLED.image(IMAGE)
OLED.show()
sys.exit("Shutting down the OLED display...")
shutdown()

View File

@ -348,7 +348,7 @@
{% endfor %}
</tbody>
</table>
<p><small>{{ _("%(disk_space)s MiB disk space remaining on the Pi", disk_space=env["free_disk_space"]) }}</small></p>
<p><small>{{ _("%(disk_space)s MiB disk space remaining on the system", disk_space=env["free_disk_space"]) }}</small></p>
</section>
<hr/>
@ -723,18 +723,18 @@
<section id="system">
<details>
<summary class="heading">
{{ _("Raspberry Pi Operations") }}
{{ _("System Operations") }}
</summary>
<ul>
<li>{{ _("IMPORTANT: Always shut down the Pi before turning off the power. Failing to do so may lead to data loss.") }}</li>
<li>{{ _("IMPORTANT: Always shut down the system before turning off the power. Failing to do so may lead to data loss.") }}</li>
</ul>
</details>
<form action="/pi/reboot" method="post" onclick="if (confirm('{{ _("Reboot the Raspberry Pi?") }}')) shutdownNotify('{{ _("Rebooting the Raspberry Pi...") }}'); else event.preventDefault();">
<input type="submit" value="{{ _("Reboot Raspberry Pi") }}">
<form action="/sys/reboot" method="post" onclick="if (confirm('{{ _("Reboot the System?") }}')) shutdownNotify('{{ _("Rebooting the system...") }}'); else event.preventDefault();">
<input type="submit" value="{{ _("Reboot System") }}">
</form>
<form action="/pi/shutdown" method="post" onclick="if (confirm('{{ _("Shut down the Raspberry Pi?") }}')) shutdownNotify('{{ _("Shutting down the Raspberry Pi...") }}'); else event.preventDefault();">
<input type="submit" value="{{ _("Shut Down Raspberry Pi") }}">
<form action="/sys/shutdown" method="post" onclick="if (confirm('{{ _("Shut down the System?") }}')) shutdownNotify('{{ _("Shutting down the system...") }}'); else event.preventDefault();">
<input type="submit" value="{{ _("Shut Down System") }}">
</form>
</section>

View File

@ -797,24 +797,30 @@ def release_id():
return response(error=True, message=process["msg"])
@APP.route("/pi/reboot", methods=["POST"])
@APP.route("/sys/reboot", methods=["POST"])
@login_required
def restart():
"""
Restarts the Pi
Restarts the system
"""
ractl_cmd.shutdown_pi("reboot")
return response()
returncode, message = sys_cmd.reboot_system()
if not returncode:
return response()
return response(error=True, message=message)
@APP.route("/pi/shutdown", methods=["POST"])
@APP.route("/sys/shutdown", methods=["POST"])
@login_required
def shutdown():
"""
Shuts down the Pi
Shuts down the system
"""
ractl_cmd.shutdown_pi("system")
return response()
returncode, message = sys_cmd.shutdown_system()
if not returncode:
return response()
return response(error=True, message=message)
@APP.route("/files/download_to_iso", methods=["POST"])
@ -889,7 +895,7 @@ def download_to_iso():
@login_required
def download_file():
"""
Downloads a remote file onto the images dir on the Pi
Downloads a remote file onto the images dir on the system
"""
destination = request.form.get("destination")
url = request.form.get("url")
@ -915,7 +921,7 @@ def download_file():
@APP.route("/files/upload", methods=["POST"])
def upload_file():
"""
Uploads a file from the local computer to the images dir on the Pi
Uploads a file from the local computer to the images dir on the system
Depending on the Dropzone.js JavaScript library
"""
# Due to the embedded javascript library, we cannot use the @login_required decorator
@ -1052,7 +1058,7 @@ def create_file():
@login_required
def download():
"""
Downloads a file from the Pi to the local computer
Downloads a file from the system to the local computer
"""
file_name = Path(request.form.get("file"))
safe_path = is_safe_path(file_name)

View File

@ -164,7 +164,7 @@ def test_set_theme(http_client, theme):
response = http_client.post(
"/theme",
data={
"theme": theme,
"name": theme,
},
)
@ -187,7 +187,7 @@ def test_set_theme_via_query_string(http_client, theme):
response = http_client.get(
"/theme",
params={
"v": theme,
"name": theme,
},
)