Skip to content

Instantly share code, notes, and snippets.

@JayGoldberg
Last active May 31, 2025 20:11
Show Gist options
  • Save JayGoldberg/d483ce1a056871dc068fc1315977a09c to your computer and use it in GitHub Desktop.
Save JayGoldberg/d483ce1a056871dc068fc1315977a09c to your computer and use it in GitHub Desktop.
Camhi patrol automation for PTZ cameras
# ~/.config/ptz/cam1.conf
# Configuration for camhi camera patrol (CGI API)
# This file contains sensitive information and configuration.
# DO NOT COMMIT THIS FILE TO GIT!
# Camera Credentials
CAMERA_HOST="cam1.lan"
CAMERA_USER="admin"
CAMERA_PASS="mypass" # <--- CHANGE THIS TO YOUR REAL PASSWORD
# Schedule for this specific camera
# Format: "START_HOUR END_HOUR SLEEP_SECONDS ZONE1 ZONE2 ..."
declare -a SCHEDULE=(
"05 06 30 1 3"
"06 07 60 1 3"
"07 08 60 2 4 5"
"08 16 120 2 3 4 5 4 3"
"16 20 60 2 3"
"20 22 30 1 3"
"22 24 30 1"
"00 05 30 1"
"00 24 90 1" # Fallback/Default
)
#!/bin/bash
# ==============================================================================
# SCRIPT METADATA AND DEFAULTS (Minimal)
# ==============================================================================
# Default Lock file location if not specified in individual camera config
LOCKFILE_DEFAULT="/tmp/ptz-patrol.lock"
# ==============================================================================
# EXTERNAL CONFIGURATION LOADING AND CAMERA SELECTION
# ==============================================================================
# Base directory for camera-specific configuration files
CONFIG_BASE_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/ptz"
# Default camera name if none is provided on the command line
# This *must* match the base name of a config file (e.g., 'chickens' for 'chickens.conf')
DEFAULT_CAMERA_SLUG="cam1" # Set a sensible default here
# --- Camera Selection ---
CAMERA_SLUG="" # This will hold the name like "cam-chickens" or "front-door"
if [[ -n "$1" ]]; then
CAMERA_SLUG="$1"
shift # Remove the first argument
else
if [[ -n "$DEFAULT_CAMERA_SLUG" ]]; then
CAMERA_SLUG="$DEFAULT_CAMERA_SLUG"
echo "No camera specified. Using default camera: $DEFAULT_CAMERA_SLUG"
else
echo "Error: No camera specified and DEFAULT_CAMERA_SLUG is not set in the script." >&2
echo "Usage: $0 <camera_name>" >&2
exit 1
fi
fi
# Construct the path to the specific camera's configuration file
CAMERA_CONFIG_FILE="${CONFIG_BASE_DIR}/${CAMERA_SLUG}.conf"
# Load camera-specific configuration
if [[ -f "$CAMERA_CONFIG_FILE" ]]; then
echo "Loading configuration for '$CAMERA_SLUG' from $CAMERA_CONFIG_FILE"
source "$CAMERA_CONFIG_FILE"
else
echo "Error: Camera configuration file not found at $CAMERA_CONFIG_FILE" >&2
echo "Please ensure the file exists and is readable, and that '$CAMERA_SLUG' is the correct camera name." >&2
exit 1
fi
# Ensure mandatory variables are set after sourcing the config
if [[ -z "${CAMERA_HOST}" || -z "${CAMERA_USER}" || -z "${CAMERA_PASS}" ]]; then
echo "Error: Mandatory camera details (CAMERA_HOST, CAMERA_USER, CAMERA_PASS) not found or incomplete in $CAMERA_CONFIG_FILE." >&2
exit 1
fi
# Ensure LOCKFILE is set (either from camera config or default)
# Append camera slug to default lockfile to prevent conflicts between cameras
: "${LOCKFILE:=${LOCKFILE_DEFAULT}-${CAMERA_SLUG}.lock}"
# Validate that SCHEDULE array is set after sourcing config
if [[ ! -v SCHEDULE ]]; then # Check if variable is set at all (Bash 4.2+)
echo "Error: 'SCHEDULE' array not found in $CAMERA_CONFIG_FILE." >&2
echo "Please define the SCHEDULE array in your camera's configuration file." >&2
exit 1
elif [[ ${#SCHEDULE[@]} -eq 0 ]]; then # Check if array is empty
echo "Warning: 'SCHEDULE' array is empty in $CAMERA_CONFIG_FILE. No zones will be called." >&2
fi
BASE_URL="http://${CAMERA_USER}:${CAMERA_PASS}@${CAMERA_HOST}/cgi-bin/hi3510/param.cgi?cmd=preset&-act=goto&-number="
echo "Configured for camera: $CAMERA_SLUG (${CAMERA_HOST})"
# ==============================================================================
# SCRIPT LOGIC (DO NOT MODIFY BELOW THIS LINE UNLESS YOU KNOW WHAT YOU'RE DOING)
# ==============================================================================
# --- Lock File Management ---
if [ -f "$LOCKFILE" ]; then
pid=$(cat "$LOCKFILE")
if ps -p "$pid" >/dev/null 2>&1; then
echo "Script is already running (PID: $pid). Exiting." >&2
exit 1
else
echo "Stale lock file found. Removing it."
rm "$LOCKFILE"
fi
fi
echo "$$" >"$LOCKFILE"
trap "rm -f '$LOCKFILE'; exit" INT TERM EXIT
# --- Helper Function: call_zone ---
call_zone() {
local input_zone_id="$1"
local sleeptime="$2"
if ! [[ "$input_zone_id" =~ ^[1-9][0-9]*$ ]]; then
echo "Error in call_zone: Invalid zone ID '$input_zone_id'. Must be a positive integer." >&2
return 1
fi
local actual_zone_id=$((input_zone_id - 1))
echo " Calling Zone ID: ${input_zone_id})"
curl -s -f "${BASE_URL}${actual_zone_id}"
local curl_status=$?
if [[ "$curl_status" -ne 0 ]]; then
echo " Error: curl failed ${BASE_URL}${actual_zone_id} with status $curl_status." >&2
fi
echo " Sleeping for ${sleeptime} seconds..."
sleep "$sleeptime"
}
# --- Main Loop ---
while true; do
current_hour=$(date +%H)
current_hour_int=${current_hour#0}
MATCHED_SCHEDULE=false
for entry in "${SCHEDULE[@]}"; do
read -r start_hour_str end_hour_str sleep_s zones_list <<<"$entry"
start_hour=${start_hour_str#0}
end_hour=${end_hour_str#0}
if [[ "$start_hour" -le "$end_hour" ]]; then
if [[ "$current_hour_int" -ge "$start_hour" && "$current_hour_int" -lt "$end_hour" ]]; then
MATCHED_SCHEDULE=true
fi
else
if [[ "$current_hour_int" -ge "$start_hour" || "$current_hour_int" -lt "$end_hour" ]]; then
MATCHED_SCHEDULE=true
fi
fi
if "$MATCHED_SCHEDULE"; then
echo "--- $(date '+%Y-%m-%d %H:%M:%S') ---"
echo "PROGRAM: Camera '$CAMERA_SLUG', Hour ${current_hour_int} matches schedule [${start_hour_str}-${end_hour_str})."
current_sleeptime="$sleep_s"
IFS=' ' read -r -a current_zones <<<"$zones_list"
for zone_to_call in "${current_zones[@]}"; do
call_zone "$zone_to_call" "$current_sleeptime"
done
break
fi
done
if ! "$MATCHED_SCHEDULE"; then
echo "--- $(date '+%Y-%m-%d %H:%M:%S') ---"
echo "PROGRAM: No specific schedule found for hour ${current_hour_int} for camera '$CAMERA_SLUG'."
echo " (Ensure your camera's config file includes a '00 24' entry in SCHEDULE for default behavior.)"
sleep 60
fi
sleep 1
done
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment