diff --git a/README.md b/README.md index f58de439..3747d6f4 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Please check out the full story with much more detail on the [wiki](https://gith # How do I contribute? PiSCSI is using the Gitflow Workflow. A quick overview: -- The *master* branch should always reflect the contents of the last stable release +- The *main* branch should always reflect the contents of the last stable release - The *develop* branch should contain the latest tested & approved updates. Pull requests should be used to merge changes into develop. - The rest of the feature branches are for developing new features - A tag will be created for each "release". The releases will be named .. where the release number is incremented for each subsequent release tagged in the same calendar month. The first release of the month of January 2021 is called "21.01.01", the second one in the same month "21.01.02" and so on. diff --git a/python/common/requirements.txt b/python/common/requirements.txt index 45ccd776..90a99b04 100644 --- a/python/common/requirements.txt +++ b/python/common/requirements.txt @@ -1,2 +1,3 @@ protobuf==3.19.5 requests==2.31.0 +vcgencmd==0.1.1 diff --git a/python/common/src/piscsi/return_codes.py b/python/common/src/piscsi/return_codes.py index cfd8265c..25cd2392 100644 --- a/python/common/src/piscsi/return_codes.py +++ b/python/common/src/piscsi/return_codes.py @@ -27,3 +27,11 @@ class ReturnCodes: EXTRACTIMAGE_NO_FILES_SPECIFIED = 91 EXTRACTIMAGE_NO_FILES_EXTRACTED = 92 EXTRACTIMAGE_COMMAND_ERROR = 93 + UNDER_VOLTAGE_DETECTED = 100 + ARM_FREQUENCY_CAPPED = 101 + CURRENTLY_THROTTLED = 102 + SOFT_TEMPERATURE_LIMIT_ACTIVE = 103 + UNDER_VOLTAGE_HAS_OCCURRED = 116 + ARM_FREQUENCY_CAPPING_HAS_OCCURRED = 117 + THROTTLING_HAS_OCCURRED = 118 + SOFT_TEMPERATURE_LIMIT_HAS_OCCURRED = 119 diff --git a/python/common/src/piscsi/sys_cmds.py b/python/common/src/piscsi/sys_cmds.py index c21cc8f4..cb43360a 100644 --- a/python/common/src/piscsi/sys_cmds.py +++ b/python/common/src/piscsi/sys_cmds.py @@ -9,7 +9,9 @@ from re import findall, match from socket import socket, gethostname, AF_INET, SOCK_DGRAM from pathlib import Path from platform import uname +from vcgencmd import Vcgencmd +from piscsi.return_codes import ReturnCodes from piscsi.common_settings import SHELL_ERROR @@ -263,3 +265,37 @@ class SysCmds: return process.returncode, process.stdout.decode("utf-8") return process.returncode, process.stderr.decode("utf-8") + + @staticmethod + def get_throttled(enabled_modes, test_modes): + """ + Takes (list) enabled_modes & (list) test_modes parameters & returns a + tuple of (str) category & (str) message. + + enabled_modes is a list of modes that will be enabled for display if + they're triggered. test_modes works similarly to enabled_mode but will + ALWAYS display the modes listed for troubleshooting styling. + """ + vcgcmd = Vcgencmd() + t_states = vcgcmd.get_throttled()['breakdown'] + matched_states = [] + + state_msgs = { + "0": ("error", ReturnCodes.UNDER_VOLTAGE_DETECTED), + "1": ("warning", ReturnCodes.ARM_FREQUENCY_CAPPED), + "2": ("error", ReturnCodes.CURRENTLY_THROTTLED), + "3": ("warning", ReturnCodes.SOFT_TEMPERATURE_LIMIT_ACTIVE), + "16": ("warning", ReturnCodes.UNDER_VOLTAGE_HAS_OCCURRED), + "17": ("warning", ReturnCodes.ARM_FREQUENCY_CAPPING_HAS_OCCURRED), + "18": ("warning", ReturnCodes.THROTTLING_HAS_OCCURRED), + "19": ("warning", ReturnCodes.SOFT_TEMPERATURE_LIMIT_HAS_OCCURRED), + } + + for k in t_states: + if t_states[k] and k in enabled_modes: + matched_states.append(state_msgs[k]) + + for t in test_modes: + matched_states.append(state_msgs[t]) + + return matched_states diff --git a/python/web/mock/bin/vcgencmd b/python/web/mock/bin/vcgencmd new file mode 100644 index 00000000..ce21e8e2 --- /dev/null +++ b/python/web/mock/bin/vcgencmd @@ -0,0 +1,23 @@ +#!/usr/bin/env bash + +# Info: https://www.raspberrypi.com/documentation/computers/os.html#vcgencmd +# +# Bit Hex value Meaning +# ----- ----------- ------------------------ +# 0 0x1 Under-voltage detected +# 1 0x2 Arm frequency capped +# 2 0x4 Currently throttled +# 3 0x8 Soft temperature limit active +# 16 0x10000 Under-voltage has occurred +# 17 0x20000 Arm frequency capping has occurred +# 18 0x40000 Throttling has occurred +# 19 0x80000 Soft temperature limit has occurred + +if [[ "$1" == "get_throttled" ]] +then + # Return 'Under-voltage detected' & 'Under-voltage has occurred' + echo "throttled=0x10001" +fi + +echo "Mock does not recognize: $0 $@" +exit 1 \ No newline at end of file diff --git a/python/web/requirements.txt b/python/web/requirements.txt index d5e01521..60758b1c 100644 --- a/python/web/requirements.txt +++ b/python/web/requirements.txt @@ -5,4 +5,5 @@ protobuf==3.20.2 requests==2.31.0 simplepam==0.1.5 flask_babel==2.0.0 -ua-parser==0.16.1 \ No newline at end of file +ua-parser==0.16.1 +vcgencmd==0.1.1 \ No newline at end of file diff --git a/python/web/src/return_code_mapper.py b/python/web/src/return_code_mapper.py index f6c6e273..9695a1dd 100644 --- a/python/web/src/return_code_mapper.py +++ b/python/web/src/return_code_mapper.py @@ -50,6 +50,22 @@ class ReturnCodeMapper: _("No files were extracted (existing files are skipped)"), ReturnCodes.EXTRACTIMAGE_COMMAND_ERROR: _("Unable to extract archive: %(error)s"), + ReturnCodes.UNDER_VOLTAGE_DETECTED: + _("Under voltage detected - Make sure to use a proper power source (2.5+ amps)."), + ReturnCodes.ARM_FREQUENCY_CAPPED: + _("ARM frequency capped - Ensure proper airflow/cooling."), + ReturnCodes.CURRENTLY_THROTTLED: + _("Currently throttled - Make sure to use a proper power source (2.5+ amps)."), + ReturnCodes.SOFT_TEMPERATURE_LIMIT_ACTIVE: + _("Soft-temperature limit active - Ensure proper airflow/cooling."), + ReturnCodes.UNDER_VOLTAGE_HAS_OCCURRED: + _("Under voltage has occurred since last reboot. Make sure to use a proper power source (2.5+ amps)."), + ReturnCodes.ARM_FREQUENCY_CAPPING_HAS_OCCURRED: + _("ARM frequency capping has occurred since last reboot. Ensure proper airflow/cooling."), + ReturnCodes.THROTTLING_HAS_OCCURRED: + _("Throttling has occurred since the last reboot. Make sure to use a proper power source (2.5+ amps)."), + ReturnCodes.SOFT_TEMPERATURE_LIMIT_HAS_OCCURRED: + _("Soft temperature limit has occurred since last reboot. Ensure proper airflow/cooling."), } # fmt: on diff --git a/python/web/src/settings.py b/python/web/src/settings.py index 3a2c4d73..7bcc99c3 100644 --- a/python/web/src/settings.py +++ b/python/web/src/settings.py @@ -31,3 +31,19 @@ TEMPLATE_THEME_DEFAULT = "modern" # Fallback theme for older browsers TEMPLATE_THEME_LEGACY = "classic" + +# Enable throttle notifications +# +# Available modes: +# "0": "Under-voltage detected" +# "1": "Arm frequency capped" +# "2": "Currently throttled" +# "3": "Soft temperature limit active" +# "16": "Under-voltage has occurred" +# "17": "Arm frequency capping has occurred" +# "18": "Throttling has occurred" +# "19": "Soft temperature limit has occurred" +THROTTLE_NOTIFY_MODES = ["0", "16"] +# Include a list of modes to be shown ALL THE TIME to be used for styling +# and formatting. +THROTTLE_TEST_MODES = [] diff --git a/python/web/src/static/themes/classic/style.css b/python/web/src/static/themes/classic/style.css index b8ed990d..c5da7eed 100644 --- a/python/web/src/static/themes/classic/style.css +++ b/python/web/src/static/themes/classic/style.css @@ -172,3 +172,36 @@ summary.filename { left: 50%; margin-left: -27px; } + +div.throttle_notice > div { + display: grid; + align-items: center; + background-color: #efefef; + background-repeat: no-repeat; + background-position: 1rem center; + background-size: 1rem; + font-size: x-small; + font-weight: bold; + justify-content: center; +} + +div.throttle_notice > div.error { + background-color: #dc3545; + align-items: center; +} + +div.throttle_notice > div.warning { + background-color: #ffc107; + align-items: center; +} + +div.throttle_notice > div a { + color: black; + text-decoration: none; +} + +div.throttle_notice > div a:hover { + text-decoration: underline; +} + + diff --git a/python/web/src/static/themes/modern/style.css b/python/web/src/static/themes/modern/style.css index 361f5bed..f6c55871 100644 --- a/python/web/src/static/themes/modern/style.css +++ b/python/web/src/static/themes/modern/style.css @@ -470,6 +470,49 @@ div.footer div.theme-change-hint a { color: yellow; } +/* + ------------------------------------------------------------------------------ + Throttle messages + ------------------------------------------------------------------------------ + */ + +div.throttle_notice > div { + display: grid; + align-items: center; + background-color: #efefef; + background-repeat: no-repeat; + background-position: 1rem center; + background-size: 1rem; + font-size: x-small; + font-weight: bold; +} + +div.throttle_notice > div.error { + background-color: var(--danger); + background-image: url("icons/error.svg"); + color: #fff; + align-items: center; +} + +div.throttle_notice > div.warning { + background-color: var(--warning); + background-image: url("icons/warning.svg"); + align-items: center; +} + +div.throttle_notice > div > span.message { + padding-left: 3rem; +} + +div.throttle_notice > div a { + color: black; + text-decoration: none; +} + +div.throttle_notice > div a:hover { + text-decoration: underline; +} + /* ------------------------------------------------------------------------------ Flash messages diff --git a/python/web/src/templates/base.html b/python/web/src/templates/base.html index 25331690..5bca87b2 100644 --- a/python/web/src/templates/base.html +++ b/python/web/src/templates/base.html @@ -73,6 +73,18 @@ +
+ {% if (env["throttle_status"]|length > 0) %} + {% for category, response in env["throttle_status"] %} + + {% endfor %} + {% endif %} +
+
{% if get_flashed_messages(): %} {% for category, message in get_flashed_messages(with_categories=true) %} diff --git a/python/web/src/web.py b/python/web/src/web.py index cc0a1398..732ef0fe 100644 --- a/python/web/src/web.py +++ b/python/web/src/web.py @@ -69,6 +69,8 @@ from settings import ( TEMPLATE_THEMES, TEMPLATE_THEME_DEFAULT, TEMPLATE_THEME_LEGACY, + THROTTLE_NOTIFY_MODES, + THROTTLE_TEST_MODES, ) @@ -88,6 +90,9 @@ def get_env_info(): else: username = None + throttled_statuses = sys_cmd.get_throttled( + THROTTLE_NOTIFY_MODES, THROTTLE_TEST_MODES) + return { "running_env": sys_cmd.running_env(), "username": username, @@ -106,6 +111,8 @@ def get_env_info(): "cd_suffixes": tuple(server_info["sccd"]), "rm_suffixes": tuple(server_info["scrm"]), "mo_suffixes": tuple(server_info["scmo"]), + "throttle_status": + [(s[0], ReturnCodeMapper.add_msg({"return_code":s[1]})) for s in throttled_statuses], } diff --git a/python/web/tests/api/test_settings.py b/python/web/tests/api/test_settings.py index 3875fd9e..1c51205a 100644 --- a/python/web/tests/api/test_settings.py +++ b/python/web/tests/api/test_settings.py @@ -286,3 +286,12 @@ def test_rename_system(env, http_client): response_data = response.json() assert response_data["data"]["system_name"] == old_name + + +def test_throttle_notification(http_client): + response = http_client.get("/") + response_data = response.json() + + assert response.status_code == 200 + assert response_data["status"] == STATUS_SUCCESS + assert "Under voltage detected" in response_data["data"]