diff --git a/python/common/src/rascsi/ractl_cmds.py b/python/common/src/rascsi/ractl_cmds.py index a9407d67..22d8136f 100644 --- a/python/common/src/rascsi/ractl_cmds.py +++ b/python/common/src/rascsi/ractl_cmds.py @@ -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() diff --git a/python/common/src/rascsi/sys_cmds.py b/python/common/src/rascsi/sys_cmds.py index c6beddf5..0eb5ee64 100644 --- a/python/common/src/rascsi/sys_cmds.py +++ b/python/common/src/rascsi/sys_cmds.py @@ -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") diff --git a/python/ctrlboard/src/ctrlboard_event_handler/ctrlboard_menu_update_event_handler.py b/python/ctrlboard/src/ctrlboard_event_handler/ctrlboard_menu_update_event_handler.py index 21909815..cdb9715c 100644 --- a/python/ctrlboard/src/ctrlboard_event_handler/ctrlboard_menu_update_event_handler.py +++ b/python/ctrlboard/src/ctrlboard_event_handler/ctrlboard_menu_update_event_handler.py @@ -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. diff --git a/python/ctrlboard/src/ctrlboard_event_handler/rascsi_shutdown_cycler.py b/python/ctrlboard/src/ctrlboard_event_handler/rascsi_shutdown_cycler.py index 3cd90b27..5c772195 100644 --- a/python/ctrlboard/src/ctrlboard_event_handler/rascsi_shutdown_cycler.py +++ b/python/ctrlboard/src/ctrlboard_event_handler/rascsi_shutdown_cycler.py @@ -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 diff --git a/python/oled/requirements.txt b/python/oled/requirements.txt index 31e1bb28..139b09f5 100644 --- a/python/oled/requirements.txt +++ b/python/oled/requirements.txt @@ -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 diff --git a/python/oled/resources/splash_stop_32.bmp b/python/oled/resources/splash_stop_32.bmp new file mode 100644 index 00000000..010f4df1 Binary files /dev/null and b/python/oled/resources/splash_stop_32.bmp differ diff --git a/python/oled/resources/splash_stop_64.bmp b/python/oled/resources/splash_stop_64.bmp new file mode 100644 index 00000000..05884b9e Binary files /dev/null and b/python/oled/resources/splash_stop_64.bmp differ diff --git a/python/oled/src/rascsi_oled_monitor.py b/python/oled/src/rascsi_oled_monitor.py index 3201e768..f59bd04d 100755 --- a/python/oled/src/rascsi_oled_monitor.py +++ b/python/oled/src/rascsi_oled_monitor.py @@ -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() diff --git a/python/web/src/templates/index.html b/python/web/src/templates/index.html index d0e1b878..f749000e 100644 --- a/python/web/src/templates/index.html +++ b/python/web/src/templates/index.html @@ -348,7 +348,7 @@ {% endfor %} -

{{ _("%(disk_space)s MiB disk space remaining on the Pi", disk_space=env["free_disk_space"]) }}

+

{{ _("%(disk_space)s MiB disk space remaining on the system", disk_space=env["free_disk_space"]) }}


@@ -723,18 +723,18 @@
- {{ _("Raspberry Pi Operations") }} + {{ _("System Operations") }}
-
- + +
-
- + +
diff --git a/python/web/src/web.py b/python/web/src/web.py index cc9dbf5c..3fbe9f08 100644 --- a/python/web/src/web.py +++ b/python/web/src/web.py @@ -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) diff --git a/python/web/tests/api/test_settings.py b/python/web/tests/api/test_settings.py index 91ca214f..95e87451 100644 --- a/python/web/tests/api/test_settings.py +++ b/python/web/tests/api/test_settings.py @@ -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, }, )