From 744681bb2085b2dd2005fb87045a41bcf4baaabb Mon Sep 17 00:00:00 2001 From: ske087 Date: Sat, 22 Nov 2025 10:26:20 +0200 Subject: [PATCH] update player with wachdog and intro --- .player_heartbeat | 1 + check_player_status.sh | 76 ++++++++++++++++++++ src/main.py | 40 ++++++++++- start.sh | 158 ++++++++++++++++++++++++++++++++--------- stop_player.sh | 34 +++++++++ 5 files changed, 276 insertions(+), 33 deletions(-) create mode 100644 .player_heartbeat create mode 100755 check_player_status.sh create mode 100755 stop_player.sh diff --git a/.player_heartbeat b/.player_heartbeat new file mode 100644 index 0000000..4476cd4 --- /dev/null +++ b/.player_heartbeat @@ -0,0 +1 @@ +1763799978.6257727 \ No newline at end of file diff --git a/check_player_status.sh b/check_player_status.sh new file mode 100755 index 0000000..3ef6b69 --- /dev/null +++ b/check_player_status.sh @@ -0,0 +1,76 @@ +#!/bin/bash + +# Player Status Checker +# Check if the player is running and healthy + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +HEARTBEAT_FILE="$SCRIPT_DIR/.player_heartbeat" +STOP_FLAG_FILE="$SCRIPT_DIR/.player_stop_requested" +LOG_FILE="$SCRIPT_DIR/player_watchdog.log" + +echo "==========================================" +echo "Kivy Signage Player - Status Check" +echo "==========================================" +echo "" + +# Check for stop flag +if [ -f "$STOP_FLAG_FILE" ]; then + echo "🛑 Stop Flag: PRESENT (user requested exit)" + echo " Watchdog will not restart player" + echo " To restart: ./start.sh" + echo "" +fi + +# Check if player process is running +PLAYER_PID=$(pgrep -f "python3 main.py" | head -1) + +if [ -z "$PLAYER_PID" ]; then + echo "Status: ❌ NOT RUNNING" + echo "" + echo "Player process is not running" +else + echo "Status: ✓ RUNNING" + echo "PID: $PLAYER_PID" + echo "" + + # Check heartbeat + if [ -f "$HEARTBEAT_FILE" ]; then + last_update=$(stat -c %Y "$HEARTBEAT_FILE" 2>/dev/null || echo 0) + current_time=$(date +%s) + diff=$((current_time - last_update)) + + echo "Heartbeat: $(date -d @${last_update} '+%Y-%m-%d %H:%M:%S')" + echo "Last update: ${diff}s ago" + + if [ $diff -lt 60 ]; then + echo "Health: ✓ HEALTHY" + else + echo "Health: ⚠️ STALE (may be frozen)" + fi + else + echo "Heartbeat: Not found (player may be starting)" + fi +fi + +echo "" + +# Show watchdog status +if pgrep -f "start.sh" > /dev/null; then + echo "Watchdog: ✓ ACTIVE" +else + echo "Watchdog: ❌ NOT RUNNING" + echo "" + echo "To start with watchdog: ./start.sh" +fi + +echo "" + +# Show last few log entries +if [ -f "$LOG_FILE" ]; then + echo "==========================================" + echo "Recent Log Entries (last 10):" + echo "==========================================" + tail -10 "$LOG_FILE" +fi + +echo "" diff --git a/src/main.py b/src/main.py index 58b3856..7c5c2d2 100644 --- a/src/main.py +++ b/src/main.py @@ -91,6 +91,19 @@ class ExitPasswordPopup(Popup): if entered_password == correct_password: # Password correct, exit app Logger.info("ExitPasswordPopup: Correct password, exiting app") + + # Create stop flag to prevent watchdog restart + stop_flag = os.path.join( + os.path.dirname(os.path.dirname(os.path.abspath(__file__))), + '.player_stop_requested' + ) + try: + with open(stop_flag, 'w') as f: + f.write('User requested exit via password') + Logger.info("ExitPasswordPopup: Stop flag created - watchdog will not restart") + except Exception as e: + Logger.warning(f"ExitPasswordPopup: Could not create stop flag: {e}") + self.dismiss() App.get_running_app().stop() else: @@ -285,6 +298,7 @@ class SignagePlayer(Widget): self.playlists_dir = os.path.join(self.base_dir, 'playlists') self.config_file = os.path.join(self.config_dir, 'app_config.json') self.resources_path = os.path.join(self.config_dir, 'resources') + self.heartbeat_file = os.path.join(self.base_dir, '.player_heartbeat') # Create directories if they don't exist for directory in [self.config_dir, self.media_dir, self.playlists_dir]: os.makedirs(directory, exist_ok=True) @@ -299,11 +313,22 @@ class SignagePlayer(Widget): self.controls_timer = None # Auto-hide controls self.schedule_hide_controls() + # Start heartbeat monitoring + Clock.schedule_interval(self.update_heartbeat, 10) # Update every 10 seconds def _update_size(self, instance, value): self.size = value if hasattr(self, 'ids') and 'content_area' in self.ids: self.ids.content_area.size = value + + def update_heartbeat(self, dt): + """Update heartbeat file to indicate player is alive""" + try: + # Touch the heartbeat file to update its modification time + with open(self.heartbeat_file, 'w') as f: + f.write(str(time.time())) + except Exception as e: + Logger.warning(f"SignagePlayer: Failed to update heartbeat: {e}") def initialize_player(self, dt): """Initialize the player - load config and start playlist checking""" @@ -983,4 +1008,17 @@ class SignagePlayerApp(App): if __name__ == '__main__': - SignagePlayerApp().run() \ No newline at end of file + try: + Logger.info("=" * 80) + Logger.info("Starting Kivy Signage Player Application") + Logger.info("=" * 80) + SignagePlayerApp().run() + except KeyboardInterrupt: + Logger.info("Application stopped by user (Ctrl+C)") + except Exception as e: + Logger.critical(f"Fatal error in application: {e}") + Logger.exception("Full traceback:") + import sys + sys.exit(1) + finally: + Logger.info("Application shutdown complete") \ No newline at end of file diff --git a/start.sh b/start.sh index c484236..f2b54a9 100755 --- a/start.sh +++ b/start.sh @@ -1,58 +1,152 @@ #!/bin/bash -# Kivy Signage Player Startup Script -# This script activates the virtual environment and starts the player +# Kivy Signage Player Startup Script with Watchdog +# This script monitors and auto-restarts the player if it crashes # Get the directory where this script is located SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -echo "==========================================" -echo "Starting Kivy Signage Player" -echo "==========================================" -echo "" +# Configuration +MAX_RETRIES=999999 # Effectively unlimited retries +RESTART_DELAY=5 # Seconds to wait before restart +HEALTH_CHECK_INTERVAL=30 # Seconds between health checks +HEARTBEAT_FILE="$SCRIPT_DIR/.player_heartbeat" +STOP_FLAG_FILE="$SCRIPT_DIR/.player_stop_requested" +LOG_FILE="$SCRIPT_DIR/player_watchdog.log" + +# Function to log messages +log_message() { + echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE" +} + +# Function to check if player is healthy +check_health() { + # Check if heartbeat file exists and is recent (within last 60 seconds) + if [ -f "$HEARTBEAT_FILE" ]; then + local last_update=$(stat -c %Y "$HEARTBEAT_FILE" 2>/dev/null || echo 0) + local current_time=$(date +%s) + local diff=$((current_time - last_update)) + + if [ $diff -lt 60 ]; then + return 0 # Healthy + else + log_message "⚠️ Player heartbeat stale (${diff}s old)" + return 1 # Unhealthy + fi + else + # If heartbeat file doesn't exist yet, assume player is starting + return 0 + fi +} + +# Cleanup function +cleanup() { + log_message "🛑 Watchdog received stop signal" + rm -f "$HEARTBEAT_FILE" + rm -f "$STOP_FLAG_FILE" + exit 0 +} + +# Trap signals for graceful shutdown +trap cleanup SIGINT SIGTERM + +log_message "==========================================" +log_message "🚀 Kivy Signage Player Watchdog Started" +log_message "==========================================" +log_message "Project directory: $SCRIPT_DIR" +log_message "Max retries: Unlimited" +log_message "Restart delay: ${RESTART_DELAY}s" +log_message "" + +# Remove old stop flag if exists (fresh start) +rm -f "$STOP_FLAG_FILE" # Change to the project directory cd "$SCRIPT_DIR" -echo "Project directory: $SCRIPT_DIR" # Check if virtual environment exists if [ -d ".venv" ]; then - echo "Activating virtual environment..." + log_message "✓ Virtual environment found" source .venv/bin/activate - echo "✓ Virtual environment activated" else - echo "Warning: Virtual environment not found at .venv/" - echo "Creating virtual environment..." + log_message "⚠️ Creating virtual environment..." python3 -m venv .venv source .venv/bin/activate - echo "Installing dependencies..." + log_message "📦 Installing dependencies..." pip3 install -r requirements.txt - echo "✓ Virtual environment created and dependencies installed" + log_message "✓ Virtual environment ready" fi -echo "" - # Check if configuration exists if [ ! -f "config/app_config.json" ]; then - echo "==========================================" - echo "⚠ WARNING: Configuration file not found!" - echo "==========================================" - echo "" - echo "Please configure the player before running:" - echo " 1. Copy config/app_config.json.example to config/app_config.json" - echo " 2. Edit the configuration file with your server details" - echo "" - read -p "Press Enter to continue anyway, or Ctrl+C to exit..." - echo "" + log_message "⚠️ WARNING: Configuration file not found!" + log_message "Player may not function correctly without configuration" fi -# Change to src directory and start the application -echo "Starting application..." -echo "==========================================" -echo "" +# Main watchdog loop +retry_count=0 +while true; do + retry_count=$((retry_count + 1)) + + log_message "" + log_message "==========================================" + log_message "▶️ Starting player (attempt #${retry_count})" + log_message "==========================================" + + # Clean old heartbeat + rm -f "$HEARTBEAT_FILE" + + # Start the player + cd "$SCRIPT_DIR/src" + python3 main.py & + PLAYER_PID=$! + + log_message "Player PID: $PLAYER_PID" + + # Monitor the player + while true; do + sleep $HEALTH_CHECK_INTERVAL + + # Check if process is still running + if ! kill -0 $PLAYER_PID 2>/dev/null; then + log_message "❌ Player process crashed or stopped (PID: $PLAYER_PID)" + break + fi + + # Check health via heartbeat + if ! check_health; then + log_message "❌ Player health check failed - may be frozen" + kill $PLAYER_PID 2>/dev/null + sleep 2 + kill -9 $PLAYER_PID 2>/dev/null + break + fi + + # Player is healthy, continue monitoring + done + + # Player stopped or crashed + # Check if user requested intentional exit + if [ -f "$STOP_FLAG_FILE" ]; then + log_message "✋ Stop flag detected - user requested exit via password" + log_message "Watchdog will NOT restart the player" + log_message "To restart, run ./start.sh again" + rm -f "$HEARTBEAT_FILE" + break + fi + + log_message "⏳ Waiting ${RESTART_DELAY}s before restart..." + sleep $RESTART_DELAY + + # Cleanup any zombie processes + pkill -9 -f "python3 main.py" 2>/dev/null + +done -cd src -python3 main.py +log_message "" +log_message "==========================================" +log_message "Watchdog stopped" +log_message "==========================================" -# Deactivate virtual environment when app exits +# Deactivate virtual environment (this line is never reached in watchdog mode) deactivate diff --git a/stop_player.sh b/stop_player.sh new file mode 100755 index 0000000..4e94414 --- /dev/null +++ b/stop_player.sh @@ -0,0 +1,34 @@ +#!/bin/bash + +# Stop the player and watchdog +# Use this to gracefully shutdown the player + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +echo "==========================================" +echo "Stopping Kivy Signage Player" +echo "==========================================" +echo "" + +# Kill watchdog (start.sh) +echo "Stopping watchdog..." +pkill -f "bash.*start.sh" + +# Kill player +echo "Stopping player..." +pkill -f "python3 main.py" + +# Give processes time to exit gracefully +sleep 2 + +# Force kill if still running +pkill -9 -f "bash.*start.sh" 2>/dev/null +pkill -9 -f "python3 main.py" 2>/dev/null + +# Clean up heartbeat and stop flag files +rm -f "$SCRIPT_DIR/.player_heartbeat" +rm -f "$SCRIPT_DIR/.player_stop_requested" + +echo "" +echo "✓ Player and watchdog stopped" +echo ""