Last active
May 31, 2025 20:11
-
-
Save JayGoldberg/d483ce1a056871dc068fc1315977a09c to your computer and use it in GitHub Desktop.
Camhi patrol automation for PTZ cameras
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# ~/.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 | |
) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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