diff --git a/chicken-dashboard.service b/chicken-dashboard.service index 37ead2b..4003a35 100644 --- a/chicken-dashboard.service +++ b/chicken-dashboard.service @@ -1,16 +1,16 @@ [Unit] -Description=Chicken Counter Dashboard +Description=Chicken Counter Live Dashboard After=network.target [Service] Type=simple -User=asus -WorkingDirectory=/home/asus/.Codes/chicken-counting-sukawarna-det -ExecStart=/home/asus/.Codes/chicken-counting-sukawarna-det/start_dashboard.sh +# Uses dynamic %h (user home directory) so it works on any user account (asus, jetson, ubuntu, etc.) +WorkingDirectory=%h/.Codes/chicken-counting-sukawarna-det +ExecStart=%h/.Codes/chicken-counting-sukawarna-det/start_dashboard.sh Restart=always RestartSec=5 Environment=PYTHONUNBUFFERED=1 Environment=PORT=8080 [Install] -WantedBy=multi-user.target +WantedBy=default.target diff --git a/install_service.sh b/install_service.sh new file mode 100755 index 0000000..0c0c6e9 --- /dev/null +++ b/install_service.sh @@ -0,0 +1,45 @@ +#!/bin/bash +# Portable Systemd Service Installer for Chicken Counter Dashboard +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +CURRENT_USER="$(id -un)" +SERVICE_NAME="chicken-dashboard.service" + +echo "=== Chicken Dashboard Service Installer ===" +echo "Target User: $CURRENT_USER" +echo "Project Dir: $SCRIPT_DIR" + +# Check if user wants user-level systemd (no root required) or system-level systemd +USER_SERVICE_DIR="$HOME/.config/systemd/user" +mkdir -p "$USER_SERVICE_DIR" + +cat < "$USER_SERVICE_DIR/$SERVICE_NAME" +[Unit] +Description=Chicken Counter Live Dashboard +After=network.target + +[Service] +Type=simple +WorkingDirectory=$SCRIPT_DIR +ExecStart=$SCRIPT_DIR/start_dashboard.sh +Restart=always +RestartSec=5 +Environment=PYTHONUNBUFFERED=1 +Environment=PORT=8080 + +[Install] +WantedBy=default.target +EOF + +echo "" +echo "✅ Installed user service to: $USER_SERVICE_DIR/$SERVICE_NAME" +echo "" +echo "To enable and start the dashboard service (runs on boot without sudo):" +echo " systemctl --user daemon-reload" +echo " systemctl --user enable --now $SERVICE_NAME" +echo " loginctl enable-linger $CURRENT_USER # Ensures service runs even if logged out" +echo "" +echo "To check status:" +echo " systemctl --user status $SERVICE_NAME" +echo "" diff --git a/run_all_coops.sh b/run_all_coops.sh index bdfc041..372d111 100755 --- a/run_all_coops.sh +++ b/run_all_coops.sh @@ -23,7 +23,12 @@ for cfg in $(ls -1 "$SCRIPT_DIR/configs/floor_config/"K*-L*.yaml 2>/dev/null | s continue fi CONFIGS+=("configs/floor_config/$(basename "$cfg")") -done +# Startup check: Ensure sibling VIDEOS directory exists +VIDEOS_BASE="$SCRIPT_DIR/../VIDEOS" +if [ ! -d "$VIDEOS_BASE" ]; then + echo "[setup] 📁 Sibling directory '$VIDEOS_BASE' not found; creating directory skeleton..." + mkdir -p "$VIDEOS_BASE/cycle7" +fi echo "=================================================================" echo " Starting Multi-Floor Batch Processing for Date: $DATE" diff --git a/src/chicken_counter/batch_discovery.py b/src/chicken_counter/batch_discovery.py index 595d2d1..c630f75 100644 --- a/src/chicken_counter/batch_discovery.py +++ b/src/chicken_counter/batch_discovery.py @@ -16,7 +16,15 @@ class CameraDiscoveryResult: def discover_camera_videos(day_dir: Path, settings: BatchSettings) -> CameraDiscoveryResult: if not day_dir.is_dir(): - raise FileNotFoundError(f"Daily input folder does not exist: {day_dir}") + # Startup check: Auto-create skeleton root_dir if missing + root_p = Path(settings.batch.root_dir) + if not root_p.exists(): + root_p.mkdir(parents=True, exist_ok=True) + print(f"[batch] 📁 Created missing video root directory: {root_p}") + raise FileNotFoundError( + f"Daily input folder does not exist: {day_dir}\n" + f"👉 Place camera video files (e.g. {settings.batch.location}_cam1_*.mp4 .. cam4_*.mp4) inside: {day_dir}" + ) result = CameraDiscoveryResult() total_configured = len(settings.cameras) diff --git a/start_dashboard.sh b/start_dashboard.sh index 902412a..914e815 100755 --- a/start_dashboard.sh +++ b/start_dashboard.sh @@ -19,18 +19,30 @@ DATE_ARG="${DATE_ARG:-}" CMD=("$PYTHON" "$SCRIPT_DIR/dashboard.py" --port "$PORT" --db "$DB_PATH") -# Auto-discover mortality directories (any folder containing mortality_report.json) -# Override with MORTALITY_DIRS="dir1,dir2" env variable or let it auto-discover +# Startup check: Ensure sibling VIDEOS directory structure exists +VIDEOS_BASE="$SCRIPT_DIR/../VIDEOS" +if [ ! -d "$VIDEOS_BASE" ]; then + echo "[dashboard] 📁 Sibling directory '$VIDEOS_BASE' not found; creating skeleton directory..." + mkdir -p "$VIDEOS_BASE/cycle7" +fi + +# Auto-discover mortality directories (any folder containing mortality_report.json or mortality folders) if [ -n "${MORTALITY_DIRS:-}" ]; then IFS=',' read -ra MDIRS <<< "$MORTALITY_DIRS" for mdir in "${MDIRS[@]}"; do CMD+=(--mortality-dir "$mdir") done else - # Default: check adjacent VIDEOS folder for any mortality subdirectory - DEFAULT_MORTALITY="$SCRIPT_DIR/../VIDEOS/cycle7/kandang-atas/mortality" - if [ -d "$DEFAULT_MORTALITY" ]; then - CMD+=(--mortality-dir "$(realpath "$DEFAULT_MORTALITY")") + # Recursively discover all mortality subdirectories under ../VIDEOS + FOUND_MDIRS=() + while IFS= read -r -d '' mdir; do + FOUND_MDIRS+=("$mdir") + done < <(find "$VIDEOS_BASE" -type d -name "mortality" -print0 2>/dev/null) + + if [ ${#FOUND_MDIRS[@]} -gt 0 ]; then + for mdir in "${FOUND_MDIRS[@]}"; do + CMD+=(--mortality-dir "$mdir") + done fi fi