#!/bin/sh # power-cycle — off, wait POWER_CYCLE_OFF_SECS (default 5), on # power-cycle off|on|status — 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: # 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=$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