88 lines
3 KiB
Bash
Executable file
88 lines
3 KiB
Bash
Executable file
#!/bin/sh
|
|
# power-cycle <host> — off, wait POWER_CYCLE_OFF_SECS (default 5), on
|
|
# power-cycle off|on|status <host> — explicit single action
|
|
# power-cycle list — show configured host -> plug mappings
|
|
#
|
|
# Targets Shelly Gen2 plugs (Plus Plug US/S, etc.) via their local HTTP RPC.
|
|
# No cloud, no account. The host must be reachable on the same network/VPN.
|
|
#
|
|
# Config: ~/.config/power-cycle/plugs.conf
|
|
# one entry per line: <host> <plug-base-url>
|
|
# blank lines and lines starting with # are ignored.
|
|
# Example:
|
|
# apricot http://10.0.0.117
|
|
# plum http://10.0.0.119
|
|
#
|
|
# Env:
|
|
# POWER_CYCLE_OFF_SECS seconds to stay off during a cycle (default 5)
|
|
# POWER_CYCLE_TIMEOUT per-request curl timeout in seconds (default 5)
|
|
|
|
set -eu
|
|
|
|
config="${XDG_CONFIG_HOME:-$HOME/.config}/power-cycle/plugs.conf"
|
|
off_secs=${POWER_CYCLE_OFF_SECS:-5}
|
|
http_timeout=${POWER_CYCLE_TIMEOUT:-5}
|
|
|
|
die() { echo "power-cycle: $*" >&2; exit 1; }
|
|
|
|
usage() {
|
|
sed -n '2,/^$/p' "$0" | sed 's/^# \{0,1\}//'
|
|
exit 2
|
|
}
|
|
|
|
lookup_plug() {
|
|
# echoes the plug base URL for $1, or exits non-zero with a hint.
|
|
host=$1
|
|
[ -f "$config" ] || die "no config at $config — create it (see 'power-cycle' help)"
|
|
url=$(awk -v h="$host" '
|
|
/^[[:space:]]*(#|$)/ { next }
|
|
$1 == h { print $2; found=1; exit }
|
|
END { exit !found }
|
|
' "$config") || die "no plug configured for '$host' in $config"
|
|
[ -n "$url" ] || die "empty plug URL for '$host' in $config"
|
|
printf %s "$url"
|
|
}
|
|
|
|
shelly_set() {
|
|
# shelly_set <base-url> <true|false>
|
|
base=$1; state=$2
|
|
curl --fail --silent --show-error --max-time "$http_timeout" \
|
|
"$base/rpc/Switch.Set?id=0&on=$state" >/dev/null \
|
|
|| die "plug $base unreachable — for modem outages, fall back to BLE (SwitchBot app)"
|
|
}
|
|
|
|
shelly_status() {
|
|
# echoes "on" or "off"
|
|
base=$1
|
|
body=$(curl --fail --silent --show-error --max-time "$http_timeout" \
|
|
"$base/rpc/Switch.GetStatus?id=0") \
|
|
|| die "plug $base unreachable"
|
|
case "$body" in
|
|
*'"output":true'*) echo on ;;
|
|
*'"output":false'*) echo off ;;
|
|
*) die "unrecognized Shelly response: $body" ;;
|
|
esac
|
|
}
|
|
|
|
cmd_list() {
|
|
[ -f "$config" ] || die "no config at $config"
|
|
awk '/^[[:space:]]*(#|$)/ { next } { printf " %-15s %s\n", $1, $2 }' "$config"
|
|
}
|
|
|
|
case "${1:-}" in
|
|
''|-h|--help|help) usage ;;
|
|
list) cmd_list ;;
|
|
off) [ $# -eq 2 ] || usage; shelly_set "$(lookup_plug "$2")" false ;;
|
|
on) [ $# -eq 2 ] || usage; shelly_set "$(lookup_plug "$2")" true ;;
|
|
status) [ $# -eq 2 ] || usage; shelly_status "$(lookup_plug "$2")" ;;
|
|
*)
|
|
[ $# -eq 1 ] || usage
|
|
host=$1
|
|
base=$(lookup_plug "$host")
|
|
echo "cycling $host: off -> ${off_secs}s -> on"
|
|
shelly_set "$base" false
|
|
sleep "$off_secs"
|
|
shelly_set "$base" true
|
|
echo "done. give the host ~30-90s to finish booting."
|
|
;;
|
|
esac
|