From 22a7233c4904db283014f1ec4563fc4cde20cd88 Mon Sep 17 00:00:00 2001 From: Marvin Bruns Date: Sat, 16 Nov 2024 01:32:12 +0100 Subject: [PATCH 1/5] Feature/pinctrl (#1) * shutdown mechanism using systemd and pinctrl * (un)install scripts and docs * install script check prequisites --- README.md | 27 +++++++-------- install.sh | 45 +++++++++++++++++++++++++ src/x730button.service | 11 ++++++ src/x730button.sh | 53 +++++++++++++++++++++++++++++ src/x730shutdown.service | 12 +++++++ src/x730shutdown.sh | 19 +++++++++++ uninstall.sh | 28 ++++++++++++++++ x730.sh | 72 ---------------------------------------- 8 files changed, 180 insertions(+), 87 deletions(-) create mode 100644 install.sh create mode 100644 src/x730button.service create mode 100644 src/x730button.sh create mode 100644 src/x730shutdown.service create mode 100644 src/x730shutdown.sh create mode 100644 uninstall.sh delete mode 100644 x730.sh diff --git a/README.md b/README.md index 7dab7a4..dbe820b 100644 --- a/README.md +++ b/README.md @@ -1,26 +1,23 @@ # x730-script -This is the safe shutdown script for x730; -NOTE: +This is the safe shutdown script for x730 -We test this shell script base official Raspbian '2018-11-13-raspbian-stretch.img' version; -We test this shell script base official Raspbian '2019-09-26 Raspbian Buster' version also; +> **NOTE** +> This version targets newer Raspberry Pi OS versions supporting `pinctrl` instead of `/sys/class/gpio` or `raspi-gpio` -How to use? -* step 1: -> wget https://raw.githubusercontent.com/geekworm-com/x730-script/master/x730.sh +## Prequisites -> sudo chmod +x x730.sh +1. OS supporting systemd +2. `/bin/bash` binary +3. `pinctrl` binary on path -> sudo bash x730.sh -* step 2: +## Installation -> printf "%s\\n" "alias x730off='sudo x730shutdown.sh'" >> ~/.bashrc +Run `sudo install.sh` -* step 3: -> sudo reboot -* how to safe shut down, run the following comamdn: -> x730off +## Usage + +Use default GUI or terminal possibilities for poweroff/reboot or press the button of you PI's case (short reboot, long shutdown). diff --git a/install.sh b/install.sh new file mode 100644 index 0000000..73cfc72 --- /dev/null +++ b/install.sh @@ -0,0 +1,45 @@ +#!/bin/bash + +SCRIPT_PATH="${BASH_SOURCE}" +while [ -L "${SCRIPT_PATH}" ]; do + SCRIPT_DIR="$(cd -P "$(dirname "${SCRIPT_PATH}")" >/dev/null 2>&1 && pwd)" + SCRIPT_PATH="$(readlink "${SCRIPT_PATH}")" + [[ ${SCRIPT_PATH} != /* ]] && SCRIPT_PATH="${SCRIPT_DIR}/${SCRIPT_PATH}" +done +SCRIPT_PATH="$(readlink -f "${SCRIPT_PATH}")" +SCRIPT_DIR="$(cd -P "$(dirname -- "${SCRIPT_PATH}")" >/dev/null 2>&1 && pwd)" + +# Test is root +if [ "$EUID" -ne 0 ] +then + echo "Please run as root" + exit +fi + + +# Check prequisites +echo "Checking prequisites ..." + +for bin in "systemctl" "pinctrl" +do + if ! type "$bin" &> /dev/null + then + echo "$bin ... missing" + exit 1 + else + echo "$bin ... passed" + fi +done + + +# Install shell scripts +chmod 755 "$SCRIPT_DIR"/src/*.sh +cp "$SCRIPT_DIR"/src/*.sh /usr/local/bin/ + + +# Install systemd units +chmod 644 "$SCRIPT_DIR"/src/*.service +cp "$SCRIPT_DIR"/src/*.service /etc/systemd/system/ + +systemctl enable x730shutdown +systemctl enable --now x730button diff --git a/src/x730button.service b/src/x730button.service new file mode 100644 index 0000000..2bf0fa8 --- /dev/null +++ b/src/x730button.service @@ -0,0 +1,11 @@ +[Unit] +Description=X730 Case Button +After=local-fs.target + +[Service] +Type=exec +RemainAfterExit=no +ExecStart=/usr/local/bin/x730button.sh + +[Install] +WantedBy=multi-user.target diff --git a/src/x730button.sh b/src/x730button.sh new file mode 100644 index 0000000..c287690 --- /dev/null +++ b/src/x730button.sh @@ -0,0 +1,53 @@ +#!/bin/bash + +SHUTDOWN=4 +REBOOTPULSEMINIMUM=200 +REBOOTPULSEMAXIMUM=600 +SLEEPPULSESECONDS=$(echo "scale=${#REBOOTPULSEMINIMUM}; $REBOOTPULSEMINIMUM / 1000" | bc) +pinctrl set "$SHUTDOWN" ip pd + +BOOT=17 +pinctrl set "$BOOT" op pn dl +pinctrl set "$BOOT" dh + +getShutdownSignal() { + echo "$(pinctrl lev "$SHUTDOWN")" +} + +getPulseTimestamp() { + echo "$(date +%s%N | cut -b1-13)" +} + +sleepPulse() { + sleep "$SLEEPPULSESECONDS" +} + +echo "X730 Shutting down..." + +while : +do + shutdownSignal=$(getShutdownSignal) + if [ $shutdownSignal = 0 ] + then + sleepPulse + else + pulseStart=$(getPulseTimestamp) + while [ $shutdownSignal = 1 ] + do + sleepPulse + if [ $(( $(getPulseTimestamp) - $pulseStart )) -gt $REBOOTPULSEMAXIMUM ] + then + echo "X730 Shutting down, halting Rpi ..." + sudo poweroff + exit + fi + shutdownSignal=$(getShutdownSignal) + done + if [ $(( $(getPulseTimestamp) - $pulseStart )) -gt $REBOOTPULSEMINIMUM ] + then + echo "X730 Rebooting, recycling Rpi ..." + sudo reboot + exit + fi + fi +done diff --git a/src/x730shutdown.service b/src/x730shutdown.service new file mode 100644 index 0000000..a8e0a2c --- /dev/null +++ b/src/x730shutdown.service @@ -0,0 +1,12 @@ +[Unit] +Description=X730 Case Shutdown +DefaultDependencies=no +Before=shutdown.target + +[Service] +Type=oneshot +RemainAfterExit=no +ExecStart=/usr/local/bin/x730shutdown.sh + +[Install] +WantedBy=shutdown.target diff --git a/src/x730shutdown.sh b/src/x730shutdown.sh new file mode 100644 index 0000000..430705d --- /dev/null +++ b/src/x730shutdown.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +BUTTON=18 + +pinctrl set "$BUTTON" op pn dl +pinctrl set "$BUTTON" dh + +SLEEP=${1:-4} + +re='^[0-9\.]+$' +if ! [[ $SLEEP =~ $re ]] ; then + echo "error: sleep time not a number" >&2; exit 1 +fi + +echo "X730 Shutting down..." +sleep $SLEEP + +#restore GPIO 18 +pinctrl set "$BUTTON" dl diff --git a/uninstall.sh b/uninstall.sh new file mode 100644 index 0000000..6ef66e0 --- /dev/null +++ b/uninstall.sh @@ -0,0 +1,28 @@ +#!/bin/bash + +SCRIPT_PATH="${BASH_SOURCE}" +while [ -L "${SCRIPT_PATH}" ]; do + SCRIPT_DIR="$(cd -P "$(dirname "${SCRIPT_PATH}")" >/dev/null 2>&1 && pwd)" + SCRIPT_PATH="$(readlink "${SCRIPT_PATH}")" + [[ ${SCRIPT_PATH} != /* ]] && SCRIPT_PATH="${SCRIPT_DIR}/${SCRIPT_PATH}" +done +SCRIPT_PATH="$(readlink -f "${SCRIPT_PATH}")" +SCRIPT_DIR="$(cd -P "$(dirname -- "${SCRIPT_PATH}")" >/dev/null 2>&1 && pwd)" + +# Test is root +if [ "$EUID" -ne 0 ] +then + echo "Please run as root" + exit +fi + + +# Uninstall systemd units +systemctl disable x730shutdown +systemctl disable --now x730button +rm /etc/systemd/system/x730shutdown.service +rm /etc/systemd/system/x730button.service + +# Unnstall shell scripts +rm /usr/local/bin/x730shutdown.sh +rm /usr/local/bin/x730button.sh diff --git a/x730.sh b/x730.sh deleted file mode 100644 index 1fbe25a..0000000 --- a/x730.sh +++ /dev/null @@ -1,72 +0,0 @@ -#x730 Powering on /reboot /shutdown from hardware -#!/bin/bash - - sudo sed -e '/shutdown/ s/^#*/#/' -i /etc/rc.local - - echo '#!/bin/bash - -SHUTDOWN=4 -REBOOTPULSEMINIMUM=200 -REBOOTPULSEMAXIMUM=600 -echo "$SHUTDOWN" > /sys/class/gpio/export -echo "in" > /sys/class/gpio/gpio$SHUTDOWN/direction -BOOT=17 -echo "$BOOT" > /sys/class/gpio/export -echo "out" > /sys/class/gpio/gpio$BOOT/direction -echo "1" > /sys/class/gpio/gpio$BOOT/value - -echo "X730 Shutting down..." - -while [ 1 ]; do - shutdownSignal=$(cat /sys/class/gpio/gpio$SHUTDOWN/value) - if [ $shutdownSignal = 0 ]; then - /bin/sleep 0.2 - else - pulseStart=$(date +%s%N | cut -b1-13) - while [ $shutdownSignal = 1 ]; do - /bin/sleep 0.02 - if [ $(($(date +%s%N | cut -b1-13)-$pulseStart)) -gt $REBOOTPULSEMAXIMUM ]; then - echo "X730 Shutting down", SHUTDOWN, ", halting Rpi ..." - sudo poweroff - exit - fi - shutdownSignal=$(cat /sys/class/gpio/gpio$SHUTDOWN/value) - done - if [ $(($(date +%s%N | cut -b1-13)-$pulseStart)) -gt $REBOOTPULSEMINIMUM ]; then - echo "X730 Rebooting", SHUTDOWN, ", recycling Rpi ..." - sudo reboot - exit - fi - fi -done' > /etc/x730pwr.sh -sudo chmod +x /etc/x730pwr.sh -sudo sed -i '$ i /etc/x730pwr.sh &' /etc/rc.local - - -#X730 full shutdown through Software -#!/bin/bash - - sudo sed -e '/button/ s/^#*/#/' -i /etc/rc.local - - echo '#!/bin/bash - -BUTTON=18 - -echo "$BUTTON" > /sys/class/gpio/export; -echo "out" > /sys/class/gpio/gpio$BUTTON/direction -echo "1" > /sys/class/gpio/gpio$BUTTON/value - -SLEEP=${1:-4} - -re='^[0-9\.]+$' -if ! [[ $SLEEP =~ $re ]] ; then - echo "error: sleep time not a number" >&2; exit 1 -fi - -echo "X730 Shutting down..." -/bin/sleep $SLEEP - -#restore GPIO 18 -echo "0" > /sys/class/gpio/gpio$BUTTON/value -' > /usr/local/bin/x730shutdown.sh -sudo chmod +x /usr/local/bin/x730shutdown.sh From cae3dd58cefecbc2f7d87c6f3d06fe2a608f3bd1 Mon Sep 17 00:00:00 2001 From: Marvin Bruns Date: Sat, 16 Nov 2024 02:27:53 +0100 Subject: [PATCH 2/5] Feature/reboot service (#2) * add reboot service * refactor poweroff and reboot services --- install.sh | 3 ++- src/x730poweroff.service | 13 +++++++++++++ src/x730reboot.service | 13 +++++++++++++ src/x730shutdown.service | 12 ------------ src/x730shutdown.sh | 1 + uninstall.sh | 6 ++++-- 6 files changed, 33 insertions(+), 15 deletions(-) create mode 100644 src/x730poweroff.service create mode 100644 src/x730reboot.service delete mode 100644 src/x730shutdown.service diff --git a/install.sh b/install.sh index 73cfc72..9b4a6a1 100644 --- a/install.sh +++ b/install.sh @@ -41,5 +41,6 @@ cp "$SCRIPT_DIR"/src/*.sh /usr/local/bin/ chmod 644 "$SCRIPT_DIR"/src/*.service cp "$SCRIPT_DIR"/src/*.service /etc/systemd/system/ -systemctl enable x730shutdown +systemctl enable x730poweroff +systemctl enable x730reboot systemctl enable --now x730button diff --git a/src/x730poweroff.service b/src/x730poweroff.service new file mode 100644 index 0000000..49aced8 --- /dev/null +++ b/src/x730poweroff.service @@ -0,0 +1,13 @@ +[Unit] +Description=X730 Case Poweroff +DefaultDependencies=no +Conflicts=reboot.target +Before=poweroff.target halt.target shutdown.target + +[Service] +Type=oneshot +RemainAfterExit=no +ExecStart=/usr/local/bin/x730shutdown.sh "4" + +[Install] +WantedBy=poweroff.target halt.target shutdown.target diff --git a/src/x730reboot.service b/src/x730reboot.service new file mode 100644 index 0000000..cb6696f --- /dev/null +++ b/src/x730reboot.service @@ -0,0 +1,13 @@ +[Unit] +Description=X730 Case Reboot +DefaultDependencies=no +Conflicts=poweroff.target halt.target +Before=reboot.target + +[Service] +Type=oneshot +RemainAfterExit=no +ExecStart=/usr/local/bin/x730shutdown.sh "1" + +[Install] +WantedBy=reboot.target diff --git a/src/x730shutdown.service b/src/x730shutdown.service deleted file mode 100644 index a8e0a2c..0000000 --- a/src/x730shutdown.service +++ /dev/null @@ -1,12 +0,0 @@ -[Unit] -Description=X730 Case Shutdown -DefaultDependencies=no -Before=shutdown.target - -[Service] -Type=oneshot -RemainAfterExit=no -ExecStart=/usr/local/bin/x730shutdown.sh - -[Install] -WantedBy=shutdown.target diff --git a/src/x730shutdown.sh b/src/x730shutdown.sh index 430705d..1b59678 100644 --- a/src/x730shutdown.sh +++ b/src/x730shutdown.sh @@ -5,6 +5,7 @@ BUTTON=18 pinctrl set "$BUTTON" op pn dl pinctrl set "$BUTTON" dh +# 1-2 sec for reboot, 3-7 for poweroff (default) 8+ crash (pull the plug) SLEEP=${1:-4} re='^[0-9\.]+$' diff --git a/uninstall.sh b/uninstall.sh index 6ef66e0..79049e9 100644 --- a/uninstall.sh +++ b/uninstall.sh @@ -18,9 +18,11 @@ fi # Uninstall systemd units -systemctl disable x730shutdown +systemctl disable x730poweroff +systemctl disable x730reboot systemctl disable --now x730button -rm /etc/systemd/system/x730shutdown.service +rm /etc/systemd/system/x730poweroff.service +rm /etc/systemd/system/x730reboot.service rm /etc/systemd/system/x730button.service # Unnstall shell scripts From ae8728b6788b40083d22cdc6dffb4fe49b8fb35a Mon Sep 17 00:00:00 2001 From: Marvin Bruns Date: Thu, 20 Mar 2025 12:05:15 +0100 Subject: [PATCH 3/5] More robust millisecond timestamp --- src/x730button.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/x730button.sh b/src/x730button.sh index c287690..7a92b95 100644 --- a/src/x730button.sh +++ b/src/x730button.sh @@ -15,7 +15,7 @@ getShutdownSignal() { } getPulseTimestamp() { - echo "$(date +%s%N | cut -b1-13)" + echo "$(date +%s%3N)" } sleepPulse() { From ca4a453ba34e2fb42dc8fc33b642da090f903f05 Mon Sep 17 00:00:00 2001 From: Marvin Bruns Date: Sun, 13 Jul 2025 12:43:31 +0200 Subject: [PATCH 4/5] alternatives for timing (#4) added fallbacks for sleeping and timestamps --- README.md | 8 +++++--- install.sh | 9 ++++++--- src/x730button.sh | 34 +++++++++++++++++++++++----------- src/x730shutdown.sh | 10 ++++++++-- uninstall.sh | 2 +- 5 files changed, 43 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index dbe820b..98e1e90 100644 --- a/README.md +++ b/README.md @@ -2,15 +2,17 @@ This is the safe shutdown script for x730 -> **NOTE** +> [!NOTE] +> > This version targets newer Raspberry Pi OS versions supporting `pinctrl` instead of `/sys/class/gpio` or `raspi-gpio` ## Prequisites 1. OS supporting systemd -2. `/bin/bash` binary -3. `pinctrl` binary on path +2. `bash` shell (v5+ or GNU coreutils) +3. Either sleep supporting fractions of seconds or perl or python +4. `pinctrl` binary on path ## Installation diff --git a/install.sh b/install.sh index 9b4a6a1..6f6ccee 100644 --- a/install.sh +++ b/install.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash SCRIPT_PATH="${BASH_SOURCE}" while [ -L "${SCRIPT_PATH}" ]; do @@ -20,9 +20,10 @@ fi # Check prequisites echo "Checking prequisites ..." -for bin in "systemctl" "pinctrl" +echo "Test required binaries" +for bin in "systemctl" "pinctrl" "bash" do - if ! type "$bin" &> /dev/null + if ! command -v "$bin" &> /dev/null then echo "$bin ... missing" exit 1 @@ -31,6 +32,8 @@ do fi done +echo "Test " + # Install shell scripts chmod 755 "$SCRIPT_DIR"/src/*.sh diff --git a/src/x730button.sh b/src/x730button.sh index 7a92b95..09e65c1 100644 --- a/src/x730button.sh +++ b/src/x730button.sh @@ -1,25 +1,37 @@ -#!/bin/bash +#!/usr/bin/env bash SHUTDOWN=4 -REBOOTPULSEMINIMUM=200 -REBOOTPULSEMAXIMUM=600 -SLEEPPULSESECONDS=$(echo "scale=${#REBOOTPULSEMINIMUM}; $REBOOTPULSEMINIMUM / 1000" | bc) +REBOOT_PULSE_MINIMUM_MILLISECONDS=200 +REBOOT_PULSE_MAXIMUM_MILLISECONDS=600 +SLEEP_PULSE_SECONDS="0.200" pinctrl set "$SHUTDOWN" ip pd BOOT=17 pinctrl set "$BOOT" op pn dl pinctrl set "$BOOT" dh +exSleep() { + sleep "$1" \ + || perl -e "select(undef, undef, undef, $1)" \ + || "$(command -v python3 || command -v python || command -v python2)" -c "import time; time.sleep($1)" +} + getShutdownSignal() { - echo "$(pinctrl lev "$SHUTDOWN")" + pinctrl lev "$SHUTDOWN" } getPulseTimestamp() { - echo "$(date +%s%3N)" + if [ -n "$EPOCHREALTIME" ] + then + local us="${EPOCHREALTIME/[^0-9]/}" + echo "${us:0:-3}" + else + date +%s%3N + fi } sleepPulse() { - sleep "$SLEEPPULSESECONDS" + exSleep "$SLEEP_PULSE_SECONDS" } echo "X730 Shutting down..." @@ -27,15 +39,15 @@ echo "X730 Shutting down..." while : do shutdownSignal=$(getShutdownSignal) - if [ $shutdownSignal = 0 ] + if [ "$shutdownSignal" = 0 ] then sleepPulse else pulseStart=$(getPulseTimestamp) - while [ $shutdownSignal = 1 ] + while [ "$shutdownSignal" = 1 ] do sleepPulse - if [ $(( $(getPulseTimestamp) - $pulseStart )) -gt $REBOOTPULSEMAXIMUM ] + if [ $(( $(getPulseTimestamp) - $pulseStart )) -gt $REBOOT_PULSE_MAXIMUM_MILLISECONDS ] then echo "X730 Shutting down, halting Rpi ..." sudo poweroff @@ -43,7 +55,7 @@ do fi shutdownSignal=$(getShutdownSignal) done - if [ $(( $(getPulseTimestamp) - $pulseStart )) -gt $REBOOTPULSEMINIMUM ] + if [ $(( $(getPulseTimestamp) - $pulseStart )) -gt $REBOOT_PULSE_MINIMUM_MILLISECONDS ] then echo "X730 Rebooting, recycling Rpi ..." sudo reboot diff --git a/src/x730shutdown.sh b/src/x730shutdown.sh index 1b59678..a2354eb 100644 --- a/src/x730shutdown.sh +++ b/src/x730shutdown.sh @@ -1,10 +1,16 @@ -#!/bin/bash +#!/usr/bin/env bash BUTTON=18 pinctrl set "$BUTTON" op pn dl pinctrl set "$BUTTON" dh +exSleep() { + sleep "$1" \ + || perl -e "select(undef, undef, undef, $1)" \ + || python3 -c "import time; time.sleep($1)" +} + # 1-2 sec for reboot, 3-7 for poweroff (default) 8+ crash (pull the plug) SLEEP=${1:-4} @@ -14,7 +20,7 @@ if ! [[ $SLEEP =~ $re ]] ; then fi echo "X730 Shutting down..." -sleep $SLEEP +exSleep "$SLEEP" #restore GPIO 18 pinctrl set "$BUTTON" dl diff --git a/uninstall.sh b/uninstall.sh index 79049e9..8eb50c2 100644 --- a/uninstall.sh +++ b/uninstall.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash SCRIPT_PATH="${BASH_SOURCE}" while [ -L "${SCRIPT_PATH}" ]; do From 22980977002b7a9b4bb812c6df7d48fed3ed4315 Mon Sep 17 00:00:00 2001 From: Marvin Bruns Date: Tue, 15 Jul 2025 19:14:08 +0200 Subject: [PATCH 5/5] add python alternative for getPulseTimestamp (#5) --- src/x730button.sh | 9 +++++++-- src/x730shutdown.sh | 3 +-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/x730button.sh b/src/x730button.sh index 09e65c1..d2a9631 100644 --- a/src/x730button.sh +++ b/src/x730button.sh @@ -10,10 +10,14 @@ BOOT=17 pinctrl set "$BOOT" op pn dl pinctrl set "$BOOT" dh +python() { + "$(command -v python3 || command -v python || command -v python2)" "$@" +} + exSleep() { sleep "$1" \ || perl -e "select(undef, undef, undef, $1)" \ - || "$(command -v python3 || command -v python || command -v python2)" -c "import time; time.sleep($1)" + || python -c "import time; time.sleep($1)" } getShutdownSignal() { @@ -26,7 +30,8 @@ getPulseTimestamp() { local us="${EPOCHREALTIME/[^0-9]/}" echo "${us:0:-3}" else - date +%s%3N + python -c "import time; print(int(time.time() * 1000))" \ + || date +%s%3N fi } diff --git a/src/x730shutdown.sh b/src/x730shutdown.sh index a2354eb..b255520 100644 --- a/src/x730shutdown.sh +++ b/src/x730shutdown.sh @@ -14,8 +14,7 @@ exSleep() { # 1-2 sec for reboot, 3-7 for poweroff (default) 8+ crash (pull the plug) SLEEP=${1:-4} -re='^[0-9\.]+$' -if ! [[ $SLEEP =~ $re ]] ; then +if ! [[ "$SLEEP" =~ ^[0-9\.]+$ ]] ; then echo "error: sleep time not a number" >&2; exit 1 fi