update player with wachdog and intro
This commit is contained in:
1
.player_heartbeat
Normal file
1
.player_heartbeat
Normal file
@@ -0,0 +1 @@
|
|||||||
|
1763799978.6257727
|
||||||
76
check_player_status.sh
Executable file
76
check_player_status.sh
Executable file
@@ -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 ""
|
||||||
40
src/main.py
40
src/main.py
@@ -91,6 +91,19 @@ class ExitPasswordPopup(Popup):
|
|||||||
if entered_password == correct_password:
|
if entered_password == correct_password:
|
||||||
# Password correct, exit app
|
# Password correct, exit app
|
||||||
Logger.info("ExitPasswordPopup: Correct password, exiting 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()
|
self.dismiss()
|
||||||
App.get_running_app().stop()
|
App.get_running_app().stop()
|
||||||
else:
|
else:
|
||||||
@@ -285,6 +298,7 @@ class SignagePlayer(Widget):
|
|||||||
self.playlists_dir = os.path.join(self.base_dir, 'playlists')
|
self.playlists_dir = os.path.join(self.base_dir, 'playlists')
|
||||||
self.config_file = os.path.join(self.config_dir, 'app_config.json')
|
self.config_file = os.path.join(self.config_dir, 'app_config.json')
|
||||||
self.resources_path = os.path.join(self.config_dir, 'resources')
|
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
|
# Create directories if they don't exist
|
||||||
for directory in [self.config_dir, self.media_dir, self.playlists_dir]:
|
for directory in [self.config_dir, self.media_dir, self.playlists_dir]:
|
||||||
os.makedirs(directory, exist_ok=True)
|
os.makedirs(directory, exist_ok=True)
|
||||||
@@ -299,11 +313,22 @@ class SignagePlayer(Widget):
|
|||||||
self.controls_timer = None
|
self.controls_timer = None
|
||||||
# Auto-hide controls
|
# Auto-hide controls
|
||||||
self.schedule_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):
|
def _update_size(self, instance, value):
|
||||||
self.size = value
|
self.size = value
|
||||||
if hasattr(self, 'ids') and 'content_area' in self.ids:
|
if hasattr(self, 'ids') and 'content_area' in self.ids:
|
||||||
self.ids.content_area.size = value
|
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):
|
def initialize_player(self, dt):
|
||||||
"""Initialize the player - load config and start playlist checking"""
|
"""Initialize the player - load config and start playlist checking"""
|
||||||
@@ -983,4 +1008,17 @@ class SignagePlayerApp(App):
|
|||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
SignagePlayerApp().run()
|
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")
|
||||||
158
start.sh
158
start.sh
@@ -1,58 +1,152 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
# Kivy Signage Player Startup Script
|
# Kivy Signage Player Startup Script with Watchdog
|
||||||
# This script activates the virtual environment and starts the player
|
# This script monitors and auto-restarts the player if it crashes
|
||||||
|
|
||||||
# Get the directory where this script is located
|
# Get the directory where this script is located
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
|
||||||
echo "=========================================="
|
# Configuration
|
||||||
echo "Starting Kivy Signage Player"
|
MAX_RETRIES=999999 # Effectively unlimited retries
|
||||||
echo "=========================================="
|
RESTART_DELAY=5 # Seconds to wait before restart
|
||||||
echo ""
|
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
|
# Change to the project directory
|
||||||
cd "$SCRIPT_DIR"
|
cd "$SCRIPT_DIR"
|
||||||
echo "Project directory: $SCRIPT_DIR"
|
|
||||||
|
|
||||||
# Check if virtual environment exists
|
# Check if virtual environment exists
|
||||||
if [ -d ".venv" ]; then
|
if [ -d ".venv" ]; then
|
||||||
echo "Activating virtual environment..."
|
log_message "✓ Virtual environment found"
|
||||||
source .venv/bin/activate
|
source .venv/bin/activate
|
||||||
echo "✓ Virtual environment activated"
|
|
||||||
else
|
else
|
||||||
echo "Warning: Virtual environment not found at .venv/"
|
log_message "⚠️ Creating virtual environment..."
|
||||||
echo "Creating virtual environment..."
|
|
||||||
python3 -m venv .venv
|
python3 -m venv .venv
|
||||||
source .venv/bin/activate
|
source .venv/bin/activate
|
||||||
echo "Installing dependencies..."
|
log_message "📦 Installing dependencies..."
|
||||||
pip3 install -r requirements.txt
|
pip3 install -r requirements.txt
|
||||||
echo "✓ Virtual environment created and dependencies installed"
|
log_message "✓ Virtual environment ready"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# Check if configuration exists
|
# Check if configuration exists
|
||||||
if [ ! -f "config/app_config.json" ]; then
|
if [ ! -f "config/app_config.json" ]; then
|
||||||
echo "=========================================="
|
log_message "⚠️ WARNING: Configuration file not found!"
|
||||||
echo "⚠ WARNING: Configuration file not found!"
|
log_message "Player may not function correctly without configuration"
|
||||||
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 ""
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Change to src directory and start the application
|
# Main watchdog loop
|
||||||
echo "Starting application..."
|
retry_count=0
|
||||||
echo "=========================================="
|
while true; do
|
||||||
echo ""
|
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
|
log_message ""
|
||||||
python3 main.py
|
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
|
deactivate
|
||||||
|
|||||||
34
stop_player.sh
Executable file
34
stop_player.sh
Executable file
@@ -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 ""
|
||||||
Reference in New Issue
Block a user