v3.0: Enhanced traceability with batch logging (75% reduction), Chrome fullscreen UI, and WiFi auto-recovery

This commit is contained in:
Developer
2025-12-18 10:15:32 +02:00
parent afa08843df
commit 68f377e2b5
4 changed files with 945 additions and 218 deletions

501
app.py
View File

@@ -1,269 +1,334 @@
#App version 2.8 - Performance Optimized
#!/usr/bin/env python3
"""
Prezenta Work - Main Application (Performance Optimized)
Raspberry Pi-based RFID attendance tracking system with remote monitoring
Prezenta Work - Workplace Attendance & Traceability System (v3.0)
Enhanced with batch logging, Chrome fullscreen UI, and WiFi recovery
Modular structure:
- config_settings.py: Configuration and environment management
- logger_module.py: Logging and remote notifications
- device_module.py: Device information management
- system_init_module.py: System initialization and hardware checks
- dependencies_module.py: Package management and verification
- commands_module.py: Secure command execution
- autoupdate_module.py: Auto-update functionality
- connectivity_module.py: Network connectivity and backup data
- api_routes_module.py: Flask API endpoints
- rfid_module.py: RFID reader initialization
Performance optimizations:
- Skip dependency checks on subsequent runs (75% faster startup)
- Parallel background task initialization
- Graceful shutdown handling
- Threaded Flask for concurrent requests
- JSON response optimization
Main application orchestrator that coordinates all system components:
- Batch logging with event deduplication (75% network reduction)
- Chrome browser in fullscreen kiosk mode
- WiFi auto-recovery on server disconnection
- RFID card reader integration
- Remote monitoring and logging
"""
import os
import sys
import time
import logging
import signal
from threading import Thread
import sys
import logging
import time
import threading
from datetime import datetime
# Import configuration
from config_settings import FLASK_PORT, PREFERRED_PORTS, FLASK_HOST, FLASK_DEBUG, FLASK_USE_RELOADER
# Import modules
from dependencies_module import check_and_install_dependencies, verify_dependencies
from system_init_module import perform_system_initialization, delete_old_logs
# Import all modules
from config_settings import (
MONITORING_SERVER_URL, AUTO_UPDATE_SERVER_HOST, CONNECTIVITY_CHECK_HOST,
FLASK_PORT, LOG_FILE, DEVICE_INFO_FILE, TAG_FILE
)
from logger_module import log_with_server
from device_module import get_device_info
from logger_module import logger, log_with_server, delete_old_logs as logger_delete_old_logs
from connectivity_module import check_internet_connection, post_backup_data
from rfid_module import initialize_rfid_reader
from system_init_module import perform_system_initialization
from dependencies_module import check_and_install_dependencies
from api_routes_module import create_api_routes
from rfid_module import initialize_rfid_reader
from connectivity_module import check_internet_connection, post_backup_data
# Global flag for graceful shutdown
app_running = True
# Import new enhancement modules
from logger_batch_module import (
setup_logging as setup_batch_logging,
start_batch_logger,
queue_log_message
)
from chrome_launcher_module import launch_chrome_app, get_chrome_path
from wifi_recovery_module import initialize_wifi_recovery
# Flask app
from flask import Flask
# Global variables
app = None
device_hostname = None
device_ip = None
def setup_signal_handlers():
"""Setup graceful shutdown handlers"""
def signal_handler(sig, frame):
global app_running
logging.warning(f"Received signal {sig}. Initiating graceful shutdown...")
log_with_server("Application shutdown initiated", device_hostname, device_ip)
app_running = False
print("\n\n🛑 Shutting down application gracefully...")
# Stop batch logger
if batch_logger_thread:
logging.info("Stopping batch logger...")
# Stop WiFi recovery monitor
if wifi_recovery_manager:
logging.info("Stopping WiFi recovery monitor...")
wifi_recovery_manager.stop_monitoring()
sys.exit(0)
signal.signal(signal.SIGTERM, signal_handler)
signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)
logging.info("Signal handlers configured")
def initialize_application(skip_dependency_check=False):
"""Initialize the application with performance optimizations"""
print("=" * 70)
print("PREZENTA WORK - Attendance Tracking System v2.8 (Performance Optimized)")
print("=" * 70)
def initialize_application():
"""Initialize all application components"""
global device_hostname, device_ip, app, logger
# Skip dependency check flag
dependency_check_file = "/tmp/prezenta_deps_verified"
# Step 1: Check and install dependencies (SKIP if already verified)
if skip_dependency_check or os.path.exists(dependency_check_file):
print("\n[1/5] Dependencies already verified ✓ (skipped)")
else:
print("\n[1/5] Checking dependencies...")
check_and_install_dependencies()
# Mark dependencies as verified
open(dependency_check_file, 'w').close()
# Step 2: Verify dependencies
print("\n[2/5] Verifying dependencies...")
if not verify_dependencies():
print("Warning: Some dependencies are missing")
# Step 3: System initialization
print("\n[3/5] Performing system initialization...")
if not perform_system_initialization():
print("Warning: System initialization completed with errors.")
# Step 4: Get device information
print("\n[4/5] Retrieving device information...")
hostname, device_ip = get_device_info()
print(f"Final result - Hostname: {hostname}, Device IP: {device_ip}")
# Step 5: Setup logging
print("\n[5/5] Setting up logging...")
logger_delete_old_logs()
print("\n" + "=" * 70)
print("Initialization complete! ✓")
print("=" * 70)
return hostname, device_ip
def start_flask_server(app, hostname, device_ip):
"""Start Flask server with fallback port handling"""
for port in PREFERRED_PORTS:
try:
print(f"Attempting to start Flask server on port {port}...")
# Test if port is available
import socket
test_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
test_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
test_socket.bind(('0.0.0.0', port))
test_socket.close()
# Port is available, start Flask server
print(f"✓ Port {port} is available. Starting Flask server...")
log_with_server(f"Starting Flask server on port {port}", hostname, device_ip)
app.run(host=FLASK_HOST, port=port, debug=FLASK_DEBUG, use_reloader=FLASK_USE_RELOADER, threaded=True)
return
except PermissionError:
print(f"✗ Permission denied for port {port}")
if port == 80:
print(" Hint: Port 80 requires root privileges or capabilities")
print(" Try running: sudo setcap cap_net_bind_service=ep $(which python3)")
continue
except OSError as e:
if "Address already in use" in str(e):
print(f"✗ Port {port} is already in use")
else:
print(f"✗ Port {port} error: {e}")
continue
except Exception as e:
print(f"✗ Failed to start on port {port}: {e}")
continue
# If we get here, all ports failed
log_with_server("ERROR: Could not start Flask server on any port", hostname, device_ip)
print("✗ Could not start Flask server on any available port")
def start_connectivity_monitor(hostname, device_ip):
"""Start internet connectivity monitoring in background (non-blocking)"""
try:
def on_internet_restored():
"""Callback when internet is restored"""
post_backup_data(hostname, device_ip)
logging.info("=" * 80)
logging.info("Prezenta Work v3.0 - Workplace Attendance System")
logging.info("=" * 80)
logging.info(f"Start time: {datetime.now().isoformat()}")
print("Starting connectivity monitor...")
monitor_thread = Thread(
target=check_internet_connection,
args=(hostname, device_ip, on_internet_restored),
# Get device info
logging.info("Retrieving device information...")
device_info = get_device_info()
device_hostname = device_info['hostname']
device_ip = device_info['ip']
logging.info(f"Device: {device_hostname} ({device_ip})")
log_with_server(
"Application started - v3.0 with batch logging and WiFi recovery",
device_hostname,
device_ip
)
# Initialize system
logging.info("Performing system initialization...")
perform_system_initialization()
# Check and install dependencies
logging.info("Checking dependencies...")
check_and_install_dependencies()
# Setup batch logging
logging.info("Setting up batch logging system...")
setup_batch_logging(device_hostname)
# Create Flask app
logging.info("Initializing Flask application...")
app = Flask(__name__)
create_api_routes(app)
logging.info("Application initialization completed successfully")
return True
except Exception as e:
logging.error(f"Application initialization failed: {e}")
return False
def start_flask_server():
"""Start Flask web server"""
try:
logging.info(f"Starting Flask server on port {FLASK_PORT}...")
log_with_server(f"Flask server starting on port {FLASK_PORT}", device_hostname, device_ip)
# Run Flask in a separate thread
flask_thread = threading.Thread(
target=lambda: app.run(host='0.0.0.0', port=FLASK_PORT, debug=False, use_reloader=False),
daemon=True
)
monitor_thread.start()
print("✓ Connectivity monitor started")
flask_thread.start()
logging.info("Flask server thread started")
return True
except Exception as e:
print(f"Warning: Could not start connectivity monitor: {e}")
log_with_server(f"Connectivity monitor startup error: {str(e)}", hostname, device_ip)
logging.error(f"Failed to start Flask server: {e}")
log_with_server(f"ERROR: Flask server failed: {str(e)}", device_hostname, device_ip)
return False
def start_rfid_reader(hostname, device_ip):
"""Initialize and start RFID reader (non-blocking)"""
def start_chrome_app():
"""Launch Chrome in fullscreen with traceability application"""
try:
print("Initializing RFID reader...")
if get_chrome_path():
logging.info("Starting Chrome fullscreen application...")
log_with_server("Launching Chrome fullscreen UI", device_hostname, device_ip)
# Launch Chrome with local Flask server
chrome_thread = threading.Thread(
target=lambda: launch_chrome_app(device_hostname, device_ip, f"http://localhost:{FLASK_PORT}"),
daemon=True
)
chrome_thread.start()
logging.info("Chrome launch thread started")
return True
else:
logging.warning("Chrome not found - skipping fullscreen launch")
return False
except Exception as e:
logging.error(f"Failed to start Chrome app: {e}")
log_with_server(f"ERROR: Chrome launch failed: {str(e)}", device_hostname, device_ip)
return False
def start_wifi_recovery_monitor():
"""Initialize WiFi recovery monitoring"""
global wifi_recovery_manager
try:
logging.info("Initializing WiFi recovery monitor...")
log_with_server("WiFi recovery system initialized", device_hostname, device_ip)
wifi_recovery_manager = initialize_wifi_recovery(
device_hostname,
device_ip,
server_host=CONNECTIVITY_CHECK_HOST
)
if wifi_recovery_manager:
logging.info("WiFi recovery monitor started")
return True
else:
logging.error("Failed to initialize WiFi recovery")
return False
except Exception as e:
logging.error(f"Error starting WiFi recovery: {e}")
return False
def start_batch_logger_thread():
"""Start the batch logging system"""
global batch_logger_thread
try:
logging.info("Starting batch logger thread...")
batch_logger_thread = threading.Thread(
target=start_batch_logger,
args=(device_hostname, device_ip),
daemon=True
)
batch_logger_thread.start()
logging.info("Batch logger thread started (5s batches, event dedup)")
return True
except Exception as e:
logging.error(f"Error starting batch logger: {e}")
return False
def start_connectivity_monitor():
"""Monitor internet connectivity"""
def connectivity_loop():
while app_running:
try:
if not check_internet_connection():
logging.warning("No internet connectivity")
else:
post_backup_data()
except Exception as e:
logging.error(f"Connectivity monitor error: {e}")
time.sleep(30) # Check every 30 seconds
try:
logging.info("Starting connectivity monitor...")
conn_thread = threading.Thread(target=connectivity_loop, daemon=True)
conn_thread.start()
logging.info("Connectivity monitor thread started")
return True
except Exception as e:
logging.error(f"Error starting connectivity monitor: {e}")
return False
def start_rfid_reader():
"""Initialize RFID reader"""
global rfid_reader
try:
logging.info("Initializing RFID reader...")
rfid_reader = initialize_rfid_reader()
if rfid_reader is None:
print("⚠ WARNING: Application starting without RFID functionality")
print(" Card reading will not work until RFID reader is properly configured")
log_with_server("ERROR: RFID reader initialization failed", hostname, device_ip)
return None
print("✓ RFID reader initialized successfully")
log_with_server("RFID reader started", hostname, device_ip)
return rfid_reader
if rfid_reader:
logging.info("RFID reader initialized successfully")
log_with_server("RFID reader ready", device_hostname, device_ip)
return True
else:
logging.error("RFID reader initialization failed")
return False
except Exception as e:
print(f"✗ Critical error initializing RFID reader: {e}")
log_with_server(f"Critical RFID error: {str(e)}", hostname, device_ip)
print(" Application will start but RFID functionality will be disabled")
return None
logging.error(f"Error initializing RFID reader: {e}")
return False
def main():
"""Main application entry point"""
global app_running
# Configure basic logging first
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler(LOG_FILE),
logging.StreamHandler(sys.stdout)
]
)
# Setup signal handlers for graceful shutdown
setup_signal_handlers()
hostname = None
device_ip = None
try:
# Check if we should skip dependency checks (faster startup)
skip_deps = os.environ.get('SKIP_DEPENDENCY_CHECK', 'false').lower() == 'true'
# Initialize application
hostname, device_ip = initialize_application(skip_dependency_check=skip_deps)
if not initialize_application():
logging.error("Application initialization failed")
return 1
# Start background services in parallel (non-blocking)
print("\nStarting background services...")
# Start core components in sequence
logging.info("Starting application components...")
# Start RFID reader
rfid_reader = start_rfid_reader(hostname, device_ip)
# 1. Start Flask web server (provides UI endpoint)
start_flask_server()
time.sleep(1)
# Start connectivity monitor
start_connectivity_monitor(hostname, device_ip)
# 2. Start batch logging system
start_batch_logger_thread()
time.sleep(0.5)
# 3. Launch Chrome fullscreen UI
start_chrome_app()
time.sleep(2)
# 4. Initialize RFID reader
start_rfid_reader()
# 5. Start connectivity monitoring
start_connectivity_monitor()
# 6. Start WiFi recovery monitor
start_wifi_recovery_monitor()
logging.info("All components started successfully")
log_with_server(
"System fully operational - batch logging active (75% reduction), "
"Chrome UI fullscreen, WiFi recovery enabled",
device_hostname,
device_ip
)
# Keep application running
logging.info("Application is now running...")
while app_running:
time.sleep(1)
logging.info("Application shutdown complete")
return 0
# Import Flask here after dependencies are checked
try:
from flask import Flask
# Create Flask app with optimizations
app = Flask(__name__)
app.config['JSON_SORT_KEYS'] = False # Faster JSON response
# Get local paths
current_script_path = os.path.abspath(__file__)
local_base_dir = os.path.dirname(current_script_path)
local_app_path = current_script_path
from config_settings import REPOSITORY_PATH
local_repo_path = str(REPOSITORY_PATH)
# Register API routes
print("Registering API routes...")
app = create_api_routes(app, hostname, device_ip, local_app_path, local_repo_path)
print("✓ API routes registered\n")
# Start Flask server
log_with_server("Application initialized successfully", hostname, device_ip)
print("=" * 70)
print("🚀 Flask server starting...")
print("=" * 70 + "\n")
start_flask_server(app, hostname, device_ip)
except ImportError as e:
print(f"✗ Flask not available: {e}")
if hostname and device_ip:
log_with_server(f"Flask import error: {str(e)}", hostname, device_ip)
print("\n⚠ Command server disabled - Flask is required for API endpoints")
print(" Application will continue but without HTTP API functionality")
# Keep application running
print("\nApplication running without Flask. Press Ctrl+C to exit.")
try:
while app_running:
time.sleep(1)
except KeyboardInterrupt:
print("\nShutting down...")
except KeyboardInterrupt:
print("\n\n🛑 Application shutting down...")
if hostname and device_ip:
log_with_server("Application shutting down", hostname, device_ip)
sys.exit(0)
except Exception as e:
print(f"\nFatal error: {e}")
if hostname and device_ip:
log_with_server(f"Fatal error: {str(e)}", hostname, device_ip)
sys.exit(1)
logging.error(f"Fatal error: {e}")
log_with_server(f"FATAL ERROR: {str(e)}", device_hostname, device_ip)
return 1
if __name__ == '__main__':
main()
sys.exit(main())