From 02e9ea1aaa865551d7ad1788579bc1fa8a3c03f5 Mon Sep 17 00:00:00 2001 From: ske087 Date: Wed, 10 Dec 2025 15:53:30 +0200 Subject: [PATCH] updated to monitor the netowrk and reset wifi if is not working --- setup_wifi_control.sh | 53 ++++++++ src/main.py | 35 ++++++ src/network_monitor.py | 235 ++++++++++++++++++++++++++++++++++++ src/test_network_monitor.py | 61 ++++++++++ 4 files changed, 384 insertions(+) create mode 100644 setup_wifi_control.sh create mode 100644 src/network_monitor.py create mode 100644 src/test_network_monitor.py diff --git a/setup_wifi_control.sh b/setup_wifi_control.sh new file mode 100644 index 0000000..1e550f9 --- /dev/null +++ b/setup_wifi_control.sh @@ -0,0 +1,53 @@ +#!/bin/bash +# Setup script to allow passwordless sudo for WiFi control commands + +echo "Setting up passwordless sudo for WiFi control..." +echo "" + +# Create sudoers file for WiFi commands +SUDOERS_FILE="/etc/sudoers.d/kiwy-signage-wifi" + +# Check if running as root +if [ "$EUID" -ne 0 ]; then + echo "This script must be run as root (use sudo)" + echo "Usage: sudo bash setup_wifi_control.sh" + exit 1 +fi + +# Get the username who invoked sudo +ACTUAL_USER="${SUDO_USER:-$USER}" + +echo "Configuring passwordless sudo for user: $ACTUAL_USER" +echo "" + +# Create sudoers entry +cat > "$SUDOERS_FILE" << EOF +# Allow $ACTUAL_USER to control WiFi without password for Kiwy Signage Player +$ACTUAL_USER ALL=(ALL) NOPASSWD: /usr/sbin/rfkill block wifi +$ACTUAL_USER ALL=(ALL) NOPASSWD: /usr/sbin/rfkill unblock wifi +$ACTUAL_USER ALL=(ALL) NOPASSWD: /sbin/ifconfig wlan0 down +$ACTUAL_USER ALL=(ALL) NOPASSWD: /sbin/ifconfig wlan0 up +$ACTUAL_USER ALL=(ALL) NOPASSWD: /sbin/dhclient wlan0 +EOF + +# Set correct permissions +chmod 0440 "$SUDOERS_FILE" + +echo "✓ Created sudoers file: $SUDOERS_FILE" +echo "" + +# Validate the sudoers file +if visudo -c -f "$SUDOERS_FILE"; then + echo "✓ Sudoers file validated successfully" + echo "" + echo "Setup complete! User '$ACTUAL_USER' can now control WiFi without password." + echo "" + echo "Test with:" + echo " sudo rfkill block wifi" + echo " sudo rfkill unblock wifi" +else + echo "✗ Error: Sudoers file validation failed" + echo "Removing invalid file..." + rm -f "$SUDOERS_FILE" + exit 1 +fi diff --git a/src/main.py b/src/main.py index fc9ae31..7a0a384 100644 --- a/src/main.py +++ b/src/main.py @@ -56,6 +56,7 @@ from get_playlists_v2 import ( send_player_error_feedback ) from keyboard_widget import KeyboardWidget +from network_monitor import NetworkMonitor from kivy.graphics import Color, Line, Ellipse from kivy.uix.floatlayout import FloatLayout from kivy.uix.slider import Slider @@ -1496,6 +1497,8 @@ class SignagePlayer(Widget): # Card reader for authentication self.card_reader = None self._pending_edit_image = None + # Network monitor + self.network_monitor = None # Paths self.base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) self.config_dir = os.path.join(self.base_dir, 'config') @@ -1542,6 +1545,9 @@ class SignagePlayer(Widget): # Load configuration self.load_config() + # Initialize network monitor + self.start_network_monitoring() + # Play intro video first self.play_intro_video() @@ -1582,6 +1588,30 @@ class SignagePlayer(Widget): Logger.info("SignagePlayer: Configuration saved") except Exception as e: Logger.error(f"SignagePlayer: Error saving config: {e}") + + def start_network_monitoring(self): + """Initialize and start network monitoring""" + try: + if self.config and 'server_ip' in self.config: + server_url = self.config.get('server_ip', '') + + # Initialize network monitor with: + # - Check every 30-45 minutes + # - Restart WiFi for 20 minutes on connection failure + self.network_monitor = NetworkMonitor( + server_url=server_url, + check_interval_min=30, + check_interval_max=45, + wifi_restart_duration=20 + ) + + # Start monitoring + self.network_monitor.start_monitoring() + Logger.info("SignagePlayer: Network monitoring started") + else: + Logger.warning("SignagePlayer: Cannot start network monitoring - no server configured") + except Exception as e: + Logger.error(f"SignagePlayer: Error starting network monitoring: {e}") async def async_playlist_update_loop(self): """Async coroutine to check for playlist updates without blocking UI""" @@ -2363,6 +2393,11 @@ class SignagePlayerApp(App): def on_stop(self): Logger.info("SignagePlayerApp: Application stopped") + # Stop network monitoring + if hasattr(self.root, 'network_monitor') and self.root.network_monitor: + self.root.network_monitor.stop_monitoring() + Logger.info("SignagePlayerApp: Network monitoring stopped") + # Cancel all async tasks try: pending = asyncio.all_tasks() diff --git a/src/network_monitor.py b/src/network_monitor.py new file mode 100644 index 0000000..321919f --- /dev/null +++ b/src/network_monitor.py @@ -0,0 +1,235 @@ +""" +Network Monitoring Module +Checks server connectivity and manages WiFi restart on connection failure +""" + +import subprocess +import time +import random +import requests +from datetime import datetime +from kivy.logger import Logger +from kivy.clock import Clock + + +class NetworkMonitor: + """Monitor network connectivity and manage WiFi restart""" + + def __init__(self, server_url, check_interval_min=30, check_interval_max=45, wifi_restart_duration=20): + """ + Initialize network monitor + + Args: + server_url (str): Server URL to check connectivity (e.g., 'https://digi-signage.moto-adv.com') + check_interval_min (int): Minimum minutes between checks (default: 30) + check_interval_max (int): Maximum minutes between checks (default: 45) + wifi_restart_duration (int): Minutes to keep WiFi off during restart (default: 20) + """ + self.server_url = server_url.rstrip('/') + self.check_interval_min = check_interval_min * 60 # Convert to seconds + self.check_interval_max = check_interval_max * 60 # Convert to seconds + self.wifi_restart_duration = wifi_restart_duration * 60 # Convert to seconds + self.is_monitoring = False + self.scheduled_event = None + self.consecutive_failures = 0 + self.max_failures_before_restart = 3 # Restart WiFi after 3 consecutive failures + + def start_monitoring(self): + """Start the network monitoring loop""" + if not self.is_monitoring: + self.is_monitoring = True + Logger.info("NetworkMonitor: Starting network monitoring") + self._schedule_next_check() + + def stop_monitoring(self): + """Stop the network monitoring""" + self.is_monitoring = False + if self.scheduled_event: + self.scheduled_event.cancel() + self.scheduled_event = None + Logger.info("NetworkMonitor: Stopped network monitoring") + + def _schedule_next_check(self): + """Schedule the next connectivity check at a random interval""" + if not self.is_monitoring: + return + + # Random interval between min and max + next_check_seconds = random.randint(self.check_interval_min, self.check_interval_max) + next_check_minutes = next_check_seconds / 60 + + Logger.info(f"NetworkMonitor: Next check scheduled in {next_check_minutes:.1f} minutes") + + # Schedule using Kivy Clock + self.scheduled_event = Clock.schedule_once( + lambda dt: self._check_connectivity(), + next_check_seconds + ) + + def _check_connectivity(self): + """Check network connectivity to server""" + Logger.info("NetworkMonitor: Checking server connectivity...") + + if self._test_server_connection(): + Logger.info("NetworkMonitor: ✓ Server connection successful") + self.consecutive_failures = 0 + else: + self.consecutive_failures += 1 + Logger.warning(f"NetworkMonitor: ✗ Server connection failed (attempt {self.consecutive_failures}/{self.max_failures_before_restart})") + + if self.consecutive_failures >= self.max_failures_before_restart: + Logger.error("NetworkMonitor: Multiple connection failures detected - initiating WiFi restart") + self._restart_wifi() + self.consecutive_failures = 0 # Reset counter after restart + + # Schedule next check + self._schedule_next_check() + + def _test_server_connection(self): + """ + Test connection to the server using ping only + This works in closed networks where the server is local + + Returns: + bool: True if server is reachable, False otherwise + """ + try: + # Extract hostname from server URL (remove http:// or https://) + hostname = self.server_url.replace('https://', '').replace('http://', '').split('/')[0] + + Logger.info(f"NetworkMonitor: Pinging server: {hostname}") + + # Ping the server hostname with 3 attempts + result = subprocess.run( + ['ping', '-c', '3', '-W', '3', hostname], + capture_output=True, + text=True, + timeout=10 + ) + + if result.returncode == 0: + Logger.info(f"NetworkMonitor: ✓ Server {hostname} is reachable") + return True + else: + Logger.warning(f"NetworkMonitor: ✗ Cannot reach server {hostname}") + return False + + except subprocess.TimeoutExpired: + Logger.warning(f"NetworkMonitor: ✗ Ping timeout to server") + return False + except Exception as e: + Logger.error(f"NetworkMonitor: Error pinging server: {e}") + return False + + def _restart_wifi(self): + """ + Restart WiFi by turning it off for a specified duration then back on + This runs in a separate thread to not block the main application + """ + def wifi_restart_thread(): + try: + Logger.info("NetworkMonitor: ====================================") + Logger.info("NetworkMonitor: INITIATING WIFI RESTART SEQUENCE") + Logger.info("NetworkMonitor: ====================================") + + # Turn off WiFi using rfkill (more reliable on Raspberry Pi) + Logger.info("NetworkMonitor: Turning WiFi OFF using rfkill...") + result = subprocess.run( + ['sudo', 'rfkill', 'block', 'wifi'], + capture_output=True, + text=True, + timeout=10 + ) + + if result.returncode == 0: + Logger.info("NetworkMonitor: ✓ WiFi turned OFF successfully (rfkill)") + Logger.info("NetworkMonitor: WiFi is now DISABLED and will remain OFF") + else: + Logger.error(f"NetworkMonitor: rfkill failed, trying ifconfig...") + Logger.error(f"NetworkMonitor: rfkill error: {result.stderr}") + + # Fallback to ifconfig + result2 = subprocess.run( + ['sudo', 'ifconfig', 'wlan0', 'down'], + capture_output=True, + text=True, + timeout=10 + ) + + if result2.returncode == 0: + Logger.info("NetworkMonitor: ✓ WiFi turned OFF successfully (ifconfig)") + else: + Logger.error(f"NetworkMonitor: Failed to turn WiFi off: {result2.stderr}") + Logger.error(f"NetworkMonitor: Return code: {result2.returncode}") + Logger.error(f"NetworkMonitor: STDOUT: {result2.stdout}") + return + + # Wait for the specified duration with WiFi OFF + wait_minutes = self.wifi_restart_duration / 60 + Logger.info(f"NetworkMonitor: ====================================") + Logger.info(f"NetworkMonitor: WiFi will remain OFF for {wait_minutes:.0f} minutes") + Logger.info(f"NetworkMonitor: Waiting period started at: {datetime.now().strftime('%H:%M:%S')}") + Logger.info(f"NetworkMonitor: ====================================") + + # Sleep while WiFi is OFF + time.sleep(self.wifi_restart_duration) + + Logger.info(f"NetworkMonitor: Wait period completed at: {datetime.now().strftime('%H:%M:%S')}") + + # Turn WiFi back on after the wait period + Logger.info("NetworkMonitor: ====================================") + Logger.info("NetworkMonitor: Now turning WiFi back ON...") + Logger.info("NetworkMonitor: ====================================") + + # Unblock WiFi using rfkill + result = subprocess.run( + ['sudo', 'rfkill', 'unblock', 'wifi'], + capture_output=True, + text=True, + timeout=10 + ) + + if result.returncode == 0: + Logger.info("NetworkMonitor: ✓ WiFi unblocked successfully (rfkill)") + else: + Logger.error(f"NetworkMonitor: rfkill unblock failed: {result.stderr}") + + # Also bring interface up + result2 = subprocess.run( + ['sudo', 'ifconfig', 'wlan0', 'up'], + capture_output=True, + text=True, + timeout=10 + ) + + if result2.returncode == 0: + Logger.info("NetworkMonitor: ✓ WiFi interface brought UP successfully") + + # Wait a bit for connection to establish + Logger.info("NetworkMonitor: Waiting 10 seconds for WiFi to initialize...") + time.sleep(10) + + # Try to restart DHCP + Logger.info("NetworkMonitor: Requesting IP address...") + subprocess.run( + ['sudo', 'dhclient', 'wlan0'], + capture_output=True, + text=True, + timeout=15 + ) + + Logger.info("NetworkMonitor: ====================================") + Logger.info("NetworkMonitor: WIFI RESTART SEQUENCE COMPLETED") + Logger.info("NetworkMonitor: ====================================") + else: + Logger.error(f"NetworkMonitor: Failed to turn WiFi on: {result.stderr}") + + except subprocess.TimeoutExpired: + Logger.error("NetworkMonitor: WiFi restart command timeout") + except Exception as e: + Logger.error(f"NetworkMonitor: Error during WiFi restart: {e}") + + # Run in separate thread to not block the application + import threading + thread = threading.Thread(target=wifi_restart_thread, daemon=True) + thread.start() diff --git a/src/test_network_monitor.py b/src/test_network_monitor.py new file mode 100644 index 0000000..ca62623 --- /dev/null +++ b/src/test_network_monitor.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 +""" +Test script for network monitor functionality +""" + +import sys +import time +from kivy.app import App +from kivy.clock import Clock +from network_monitor import NetworkMonitor + +class TestMonitorApp(App): + """Minimal Kivy app to test network monitor""" + + def build(self): + """Build the app""" + from kivy.uix.label import Label + return Label(text='Network Monitor Test Running\nCheck terminal for output') + + def on_start(self): + """Start monitoring when app starts""" + server_url = "https://digi-signage.moto-adv.com" + + print("=" * 60) + print("Network Monitor Test") + print("=" * 60) + print() + print(f"Server URL: {server_url}") + print("Check interval: 0.5 minutes (30 seconds for testing)") + print("WiFi restart duration: 1 minute (for testing)") + print() + + # Create monitor with short intervals for testing + self.monitor = NetworkMonitor( + server_url=server_url, + check_interval_min=0.5, # 30 seconds + check_interval_max=0.5, # 30 seconds + wifi_restart_duration=1 # 1 minute + ) + + # Perform immediate test + print("Performing immediate connectivity test...") + self.monitor._check_connectivity() + + # Start monitoring for future checks + print("\nStarting periodic network monitoring...") + self.monitor.start_monitoring() + + print("\nMonitoring is active. Press Ctrl+C to stop.") + print("Next check will occur in ~30 seconds.") + print() + + def on_stop(self): + """Stop monitoring when app stops""" + if hasattr(self, 'monitor'): + self.monitor.stop_monitoring() + print("\nNetwork monitoring stopped") + print("Test completed!") + +if __name__ == '__main__': + TestMonitorApp().run()