#!/bin/sh # Updated QuecProfiles daemon with enhanced SA/NSA NR5G band management and TTL support # Including profile application functions and fixed comparison logic # Configuration QUEUE_DIR="/tmp/at_queue" TOKEN_FILE="$QUEUE_DIR/token" TRACK_FILE="/tmp/quecprofiles_active" CHECK_TRIGGER="/tmp/quecprofiles_check" STATUS_FILE="/tmp/quecprofiles_status.json" APPLIED_FLAG="/tmp/quecprofiles_applied" DEBUG_LOG="/tmp/quecprofiles_debug.log" DETAILED_LOG="/tmp/quecprofiles_detailed.log" DEFAULT_CHECK_INTERVAL=60 # Default check interval in seconds COMMAND_TIMEOUT=10 # Default timeout for AT commands in seconds QUEUE_PRIORITY=3 # Medium-high priority (1 is highest for cell scan) MAX_TOKEN_WAIT=15 # Maximum seconds to wait for token acquisition # Initialize log file echo "$(date) - Starting QuecProfiles daemon with SA/NSA NR5G and TTL support (PID: $$)" >"$DEBUG_LOG" echo "$(date) - Starting QuecProfiles daemon with SA/NSA NR5G and TTL support (PID: $$)" >"$DETAILED_LOG" chmod 644 "$DEBUG_LOG" "$DETAILED_LOG" # Function to log messages log_message() { local message="$1" local level="${2:-info}" local timestamp=$(date "+%Y-%m-%d %H:%M:%S") # Log to system log logger -t quecprofiles_daemon -p "daemon.$level" "$message" # Log to debug file echo "[$timestamp] [$level] $message" >>"$DEBUG_LOG" # For detailed logs or errors if [ "$level" = "error" ] || [ "$level" = "debug" ]; then echo "[$timestamp] [$level] $message" >>"$DETAILED_LOG" fi } # Function to update track file with status - IMPROVED VERSION update_track() { local status="$1" local message="$2" local profile="${3:-unknown}" local progress="${4:-0}" # Create JSON status cat >"$STATUS_FILE" <"$TRACK_FILE" chmod 644 "$TRACK_FILE" fi log_message "Status updated: $status - $message ($progress%)" } # Function to find profile by ICCID in UCI find_profile_by_iccid() { local iccid="$1" local profile_indices log_message "Looking for profile with ICCID: $iccid" "info" # Get all profile indices profile_indices=$(uci show quecprofiles | grep -o '@profile\[[0-9]\+\]' | sort -u) # Exit early if no profiles found if [ -z "$profile_indices" ]; then log_message "No profiles configured in the system" "info" return 1 fi for profile_index in $profile_indices; do local current_iccid=$(uci -q get quecprofiles.$profile_index.iccid) if [ "$current_iccid" = "$iccid" ]; then log_message "Found matching profile: $profile_index" "info" echo "$profile_index" return 0 fi done log_message "No matching profile found for ICCID: $iccid" "info" return 1 } # Function to normalize and compare values - handles format differences compare_values() { local current="$1" local desired="$2" local type="$3" # Skip empty values if [ -z "$desired" ]; then log_message "Desired value for $type is empty, skipping comparison" "debug" return 1 # No change needed fi # Normalize values for comparison local norm_current local norm_desired # Different normalization based on type case "$type" in "apn") # APN comparison is case-insensitive norm_current=$(echo "$current" | tr '[:upper:]' '[:lower:]') norm_desired=$(echo "$desired" | tr '[:upper:]' '[:lower:]') ;; "mode") # Network mode - normalize format and sort parts norm_current=$(echo "$current" | tr '[:upper:]' '[:lower:]' | tr ':' ',' | tr -d ' ' | tr ',' '\n' | sort | tr '\n' ',' | sed 's/,$//') norm_desired=$(echo "$desired" | tr '[:upper:]' '[:lower:]' | tr ':' ',' | tr -d ' ' | tr ',' '\n' | sort | tr '\n' ',' | sed 's/,$//') ;; "bands") # Bands - sort numbers for consistent comparison norm_current=$(echo "$current" | tr ',' '\n' | sort -n | tr '\n' ',' | sed 's/,$//') norm_desired=$(echo "$desired" | tr ',' '\n' | sort -n | tr '\n' ',' | sed 's/,$//') ;; *) # Default comparison norm_current="$current" norm_desired="$desired" ;; esac log_message "Comparing $type - Current: '$norm_current', Desired: '$norm_desired'" "debug" # Check if values are equivalent after normalization if [ "$norm_current" = "$norm_desired" ]; then log_message "$type values match after normalization" "debug" return 1 # No change needed else log_message "$type values differ after normalization - change needed" "debug" return 0 # Change needed fi } # Function to check if profile is already applied is_profile_applied() { local iccid="$1" local profile_name="$2" # Check if applied flag exists and matches current profile if [ -f "$APPLIED_FLAG" ]; then local applied_data=$(cat "$APPLIED_FLAG" 2>/dev/null) local applied_iccid=$(echo "$applied_data" | cut -d':' -f1) local applied_name=$(echo "$applied_data" | cut -d':' -f2) local applied_time=$(echo "$applied_data" | cut -d':' -f3) # Check if the applied profile matches current one if [ "$applied_iccid" = "$iccid" ] && [ "$applied_name" = "$profile_name" ]; then log_message "Profile '$profile_name' already applied at $(date -d @$applied_time)" "info" return 0 # Profile already applied fi fi # No matching applied profile found return 1 } # Function to mark profile as applied mark_profile_applied() { local iccid="$1" local profile_name="$2" # Save profile application data echo "$iccid:$profile_name:$(date +%s)" >"$APPLIED_FLAG" chmod 644 "$APPLIED_FLAG" log_message "Marked profile '$profile_name' as applied for ICCID $iccid" "info" } # Enhanced JSON string escaping function escape_json() { printf '%s' "$1" | sed 's/\\/\\\\/g' | sed 's/"/\\"/g' | tr -d '\n' | sed 's/\r//g' } # Function to acquire token directly with retries acquire_token() { local lock_id="QUECPROFILES_$(date +%s)_$$" local priority="$QUEUE_PRIORITY" local max_attempts=$MAX_TOKEN_WAIT local attempt=0 log_message "Attempting to acquire AT queue token with priority $priority" "debug" while [ $attempt -lt $max_attempts ]; do # Check if token file exists if [ -f "$TOKEN_FILE" ]; then local current_holder=$(cat "$TOKEN_FILE" | jsonfilter -e '@.id' 2>/dev/null) local current_priority=$(cat "$TOKEN_FILE" | jsonfilter -e '@.priority' 2>/dev/null) local timestamp=$(cat "$TOKEN_FILE" | jsonfilter -e '@.timestamp' 2>/dev/null) local current_time=$(date +%s) # Check for expired token (> 30 seconds old) if [ $((current_time - timestamp)) -gt 30 ] || [ -z "$current_holder" ]; then # Remove expired token log_message "Found expired token from $current_holder, removing" "debug" rm -f "$TOKEN_FILE" 2>/dev/null elif [ $priority -lt $current_priority ]; then # Preempt lower priority token log_message "Preempting token from $current_holder (priority: $current_priority)" "debug" rm -f "$TOKEN_FILE" 2>/dev/null else # Try again - higher priority token exists log_message "Token held by $current_holder with priority $current_priority, retrying..." "debug" sleep 0.5 attempt=$((attempt + 1)) continue fi fi # Try to create token file echo "{\"id\":\"$lock_id\",\"priority\":$priority,\"timestamp\":$(date +%s)}" >"$TOKEN_FILE" 2>/dev/null chmod 644 "$TOKEN_FILE" 2>/dev/null # Verify we got the token local holder=$(cat "$TOKEN_FILE" 2>/dev/null | jsonfilter -e '@.id' 2>/dev/null) if [ "$holder" = "$lock_id" ]; then log_message "Successfully acquired token with ID $lock_id" "debug" echo "$lock_id" return 0 fi sleep 0.5 attempt=$((attempt + 1)) done log_message "Failed to acquire token after $max_attempts attempts" "error" return 1 } # Function to release token release_token() { local lock_id="$1" if [ -f "$TOKEN_FILE" ]; then local current_holder=$(cat "$TOKEN_FILE" | jsonfilter -e '@.id' 2>/dev/null) if [ "$current_holder" = "$lock_id" ]; then rm -f "$TOKEN_FILE" 2>/dev/null log_message "Released token $lock_id" "debug" return 0 fi log_message "Token held by $current_holder, not by us ($lock_id)" "warn" else log_message "Token file doesn't exist, nothing to release" "debug" fi return 1 } # Function to execute AT command with proper error handling execute_at_command() { local cmd="$1" local timeout="${2:-$COMMAND_TIMEOUT}" local token_id="$3" if [ -z "$token_id" ]; then log_message "No valid token provided for command: $cmd" "error" return 1 fi log_message "Executing AT command: $cmd (timeout: ${timeout}s)" "debug" # Execute the command with proper timeout local output="" local status=1 # Check if sms_tool exists if which sms_tool >/dev/null 2>&1; then output=$(sms_tool at "$cmd" -t "$timeout" 2>&1) status=$? log_message "AT command executed, status: $status" "debug" else log_message "sms_tool not found, cannot execute AT command" "error" return 1 fi # Log command output for debugging echo "Command: $cmd" >>"$DETAILED_LOG" echo "Output: $output" >>"$DETAILED_LOG" echo "Status: $status" >>"$DETAILED_LOG" if [ $status -ne 0 ]; then log_message "AT command failed: $cmd (exit code: $status)" "error" return 1 fi echo "$output" return 0 } # Function to fetch all modem data at once with a single token fetch_modem_data() { local token_id="" local result=1 local modem_data="" log_message "Fetching all modem data at once" "info" # Define commands to execute local COMMANDS="AT+ICCID AT+CGDCONT? AT+QNWPREFCFG=\"mode_pref\" AT+QNWPREFCFG=\"lte_band\" AT+QNWPREFCFG=\"nsa_nr5g_band\" AT+QNWPREFCFG=\"nr5g_band\" AT+CGSN" # Get token token_id=$(acquire_token) if [ -z "$token_id" ]; then log_message "Failed to acquire token for fetching modem data" "error" return 1 fi # Execute each command and combine outputs for cmd in $COMMANDS; do log_message "Executing command: $cmd" "debug" local output=$(execute_at_command "$cmd" 5 "$token_id") local status=$? if [ $status -eq 0 ]; then # Append to modem_data modem_data="${modem_data}====COMMAND_START:${cmd}====\n${output}\n====COMMAND_END====\n\n" else log_message "Command failed: $cmd" "warn" fi done # Release token release_token "$token_id" if [ -n "$modem_data" ]; then # Save output to DETAILED_LOG for debugging echo -e "MODEM DATA:\n$modem_data" >>"$DETAILED_LOG" echo "$modem_data" return 0 else log_message "No modem data fetched" "error" return 1 fi } # Function to extract ICCID from modem data extract_iccid() { local modem_data="$1" local iccid="" # Extract section containing ICCID command response local iccid_section=$(echo -e "$modem_data" | sed -n '/====COMMAND_START:AT+ICCID====/,/====COMMAND_END====/p') # Try to extract ICCID (look for 10-20 digit number) iccid=$(echo "$iccid_section" | grep -o '[0-9]\{10,20\}' | head -n 1) if [ -z "$iccid" ]; then log_message "Failed to extract ICCID from modem data" "error" return 1 fi log_message "Extracted ICCID: $iccid" "info" echo "$iccid" return 0 } # Function to extract APN from modem data extract_apn() { local modem_data="$1" local apn="" # Extract section containing CGDCONT command response local apn_section=$(echo -e "$modem_data" | sed -n '/====COMMAND_START:AT+CGDCONT?====/,/====COMMAND_END====/p') # Try to extract APN from the response - look for context 1 apn=$(echo "$apn_section" | grep -o '+CGDCONT: 1,[^,]*,"[^"]*"' | cut -d'"' -f2) if [ -z "$apn" ]; then # Try alternative pattern apn=$(echo "$apn_section" | grep -o '+CGDCONT: 1,[^,]*,[^,]*' | cut -d',' -f3 | tr -d '"') if [ -z "$apn" ]; then log_message "Failed to extract APN from modem data" "error" return 1 fi fi log_message "Extracted APN: $apn" "info" echo "$apn" return 0 } # Function to extract network mode from modem data extract_network_mode() { local modem_data="$1" local mode="" # Extract section containing mode_pref command response local mode_section=$(echo -e "$modem_data" | sed -n '/====COMMAND_START:AT+QNWPREFCFG="mode_pref"====/,/====COMMAND_END====/p') # Try to extract mode from the response mode=$(echo "$mode_section" | grep -o '+QNWPREFCFG:.*' | cut -d'"' -f3) if [ -z "$mode" ]; then log_message "Failed to extract network mode from modem data" "error" return 1 fi # Clean up the value - remove leading comma if present mode=$(echo "$mode" | sed 's/^,//') log_message "Extracted network mode: $mode" "info" echo "$mode" return 0 } # Function to extract LTE bands from modem data extract_lte_bands() { local modem_data="$1" local bands="" # Extract section containing lte_band command response local bands_section=$(echo -e "$modem_data" | sed -n '/====COMMAND_START:AT+QNWPREFCFG="lte_band"====/,/====COMMAND_END====/p') # Try to extract bands from the response bands=$(echo "$bands_section" | grep -o '+QNWPREFCFG:.*' | cut -d'"' -f3) if [ -z "$bands" ]; then log_message "Failed to extract LTE bands from modem data" "error" return 1 fi # Convert colon-separated to comma-separated and remove leading comma if present bands=$(echo "$bands" | tr ':' ',' | sed 's/^,//') log_message "Extracted LTE bands: $bands" "info" echo "$bands" return 0 } # Updated: Function to extract both SA and NSA NR5G bands from modem data extract_nr5g_bands() { local modem_data="$1" local bands_type="$2" # "sa" or "nsa" local section_type="" if [ "$bands_type" = "sa" ]; then section_type="nr5g_band" else section_type="nsa_nr5g_band" fi # Extract sections containing NR5G band command responses local bands_section=$(echo -e "$modem_data" | sed -n "/====COMMAND_START:AT+QNWPREFCFG=\"$section_type\"====/,/====COMMAND_END====/p") # Try to extract bands local bands=$(echo "$bands_section" | grep -o '+QNWPREFCFG:.*' | cut -d'"' -f3) if [ -n "$bands" ]; then # Clean up the value - convert colon-separated to comma-separated and remove leading comma bands=$(echo "$bands" | tr ':' ',' | sed 's/^,//') log_message "Extracted $bands_type NR5G bands: $bands" "info" echo "$bands" return 0 fi log_message "Failed to extract $bands_type NR5G bands from modem data" "warn" return 1 } # Function to extract IMEI from modem data extract_imei() { local modem_data="$1" local imei="" # Extract section containing CGSN command response local imei_section=$(echo -e "$modem_data" | sed -n '/====COMMAND_START:AT+CGSN====/,/====COMMAND_END====/p') # Try to extract IMEI (look for 15 digit number) imei=$(echo "$imei_section" | grep -o '[0-9]\{15\}' | head -n 1) if [ -z "$imei" ]; then log_message "Failed to extract IMEI from modem data" "error" return 1 fi log_message "Extracted IMEI: $imei" "info" echo "$imei" return 0 } # Function to setup TTL configuration persistence setup_ttl_persistence() { if [ ! -f "/etc/data/lanUtils.sh" ]; then log_message "lanUtils.sh not found, TTL changes might not persist across reboots" "warn" return 1 fi # Backup the original script if not already done if [ ! -f "/etc/data/lanUtils.sh.bak" ]; then cp "/etc/data/lanUtils.sh" "/etc/data/lanUtils.sh.bak" fi # Add the local ttl_firewall_file line if it's not already present if ! grep -q "local ttl_firewall_file" "/etc/data/lanUtils.sh"; then sed -i '/local tcpmss_firewall_filev6/a \ local ttl_firewall_file=/etc/firewall.user.ttl' "/etc/data/lanUtils.sh" fi # Add the condition to include the ttl_firewall_file if it's not already present if ! grep -q "if \[ -f \"\$ttl_firewall_file\" \]; then" "/etc/data/lanUtils.sh"; then sed -i '/if \[ -f "\$tcpmss_firewall_filev6" \]; then/i \ if [ -f "\$ttl_firewall_file" ]; then\n cat \$ttl_firewall_file >> \$firewall_file\n fi' "/etc/data/lanUtils.sh" fi log_message "TTL persistence setup completed" "info" return 0 } # Function to apply TTL settings apply_ttl_settings() { local ttl="$1" local current_ttl="$2" local token_id="$3" local profile_name="$4" # If TTL is not set, default to 0 (disabled) ttl="${ttl:-0}" current_ttl="${current_ttl:-0}" # Check if change is needed if [ "$ttl" = "$current_ttl" ]; then log_message "TTL already set to $ttl, no change needed" "debug" return 0 fi update_track "applying" "Setting TTL from '$current_ttl' to '$ttl'" "$profile_name" "85" log_message "Changing TTL from '$current_ttl' to '$ttl'" "info" # Create TTL file directory if it doesn't exist mkdir -p /etc if [ "$ttl" = "0" ]; then # Clear existing rules iptables -t mangle -D POSTROUTING -o rmnet+ -j TTL --ttl-set "$current_ttl" 2>/dev/null ip6tables -t mangle -D POSTROUTING -o rmnet+ -j HL --hl-set "$current_ttl" 2>/dev/null >"/etc/firewall.user.ttl" log_message "TTL settings cleared" "info" else # Clear existing rules if [ "$current_ttl" != "0" ]; then iptables -t mangle -D POSTROUTING -o rmnet+ -j TTL --ttl-set "$current_ttl" 2>/dev/null ip6tables -t mangle -D POSTROUTING -o rmnet+ -j HL --hl-set "$current_ttl" 2>/dev/null fi # Set new rules echo "iptables -t mangle -A POSTROUTING -o rmnet+ -j TTL --ttl-set $ttl" >"/etc/firewall.user.ttl" echo "ip6tables -t mangle -A POSTROUTING -o rmnet+ -j HL --hl-set $ttl" >>"/etc/firewall.user.ttl" # Apply the rules iptables -t mangle -A POSTROUTING -o rmnet+ -j TTL --ttl-set "$ttl" ip6tables -t mangle -A POSTROUTING -o rmnet+ -j HL --hl-set "$ttl" log_message "TTL changed successfully to $ttl" "info" fi # Setup persistence setup_ttl_persistence return 0 } # Function to get current TTL value get_current_ttl() { local current_ttl=0 if [ -f "/etc/firewall.user.ttl" ]; then current_ttl=$(grep 'iptables -t mangle -A POSTROUTING' "/etc/firewall.user.ttl" | awk '{for(i=1;i<=NF;i++){if($i=="--ttl-set"){print $(i+1)}}}') if ! [[ "$current_ttl" =~ ^[0-9]+$ ]]; then current_ttl=0 fi fi log_message "Current TTL value: $current_ttl" "debug" echo "$current_ttl" return 0 } # Updated function to apply profile settings with separate SA/NSA NR5G bands and TTL support apply_profile_settings() { local profile_name="$1" local network_type="$2" local lte_bands="$3" local sa_nr5g_bands="$4" local nsa_nr5g_bands="$5" local apn="$6" local pdp_type="$7" local imei="$8" local ttl="$9" local current_apn="${10}" local current_mode="${11}" local current_lte_bands="${12}" local current_sa_nr5g_bands="${13}" local current_nsa_nr5g_bands="${14}" local current_imei="${15}" local iccid="${16}" local mobile_provider="${17}" # Set TTL to 0 (disabled) if not specified ttl="${ttl:-0}" log_message "Applying profile '$profile_name' with settings:" "info" log_message "- Network type: $network_type" "info" log_message "- LTE bands: $lte_bands" "info" log_message "- SA NR5G bands: $sa_nr5g_bands" "info" log_message "- NSA NR5G bands: $nsa_nr5g_bands" "info" log_message "- APN: $apn ($pdp_type)" "info" log_message "- IMEI: $imei" "info" log_message "- TTL: $ttl" "info" log_message "- Mobile Provider: $mobile_provider" "info" # Check if any changes are needed using improved comparison local needs_apn_change=0 local needs_mode_change=0 local needs_lte_bands_change=0 local needs_sa_nr5g_bands_change=0 local needs_nsa_nr5g_bands_change=0 local needs_imei_change=0 local needs_ttl_change=0 local changes_needed=0 local requires_reboot=0 local change_for_reboot="" # Use normalized comparison compare_values "$current_apn" "$apn" "apn" && needs_apn_change=1 && changes_needed=1 compare_values "$current_mode" "$network_type" "mode" && needs_mode_change=1 && changes_needed=1 compare_values "$current_lte_bands" "$lte_bands" "bands" && needs_lte_bands_change=1 && changes_needed=1 compare_values "$current_sa_nr5g_bands" "$sa_nr5g_bands" "bands" && needs_sa_nr5g_bands_change=1 && changes_needed=1 compare_values "$current_nsa_nr5g_bands" "$nsa_nr5g_bands" "bands" && needs_nsa_nr5g_bands_change=1 && changes_needed=1 # Get current TTL value local current_ttl=$(get_current_ttl) # Compare TTL values if [ "$current_ttl" != "$ttl" ]; then needs_ttl_change=1 changes_needed=1 fi # IMEI is a special case - only change if explicitly specified if [ -n "$imei" ]; then compare_values "$current_imei" "$imei" "imei" && needs_imei_change=1 && changes_needed=1 && requires_reboot=1 fi if [ $changes_needed -eq 0 ]; then log_message "No changes needed for profile '$profile_name', settings already correct" "info" mark_profile_applied "$iccid" "$profile_name" update_track "success" "Profile already correctly applied" "$profile_name" "100" return 0 fi # Get token for applying settings local token_id=$(acquire_token) if [ -z "$token_id" ]; then log_message "Failed to acquire token for applying profile settings" "error" update_track "error" "Failed to acquire token" "$profile_name" "0" return 1 fi local apply_success=1 local changes_made=0 # Apply APN change first (most important) if [ $needs_apn_change -eq 1 ]; then update_track "applying" "Setting APN from '$current_apn' to '$apn'" "$profile_name" "20" log_message "Changing APN from '$current_apn' to '$apn' ($pdp_type)" "info" # Set APN using AT command local apn_cmd="AT+CGDCONT=1,\"$pdp_type\",\"$apn\"" local output=$(execute_at_command "$apn_cmd" 10 "$token_id") if [ $? -eq 0 ]; then changes_made=1 log_message "APN changed successfully to $apn ($pdp_type)" "info" # Verify APN setting - fetch APN again to confirm local verify_output=$(execute_at_command "AT+CGDCONT?" 5 "$token_id") if echo "$verify_output" | grep -q "\"$apn\""; then log_message "APN change verified successfully" "info" update_track "applying" "APN set successfully" "$profile_name" "30" else log_message "APN change could not be verified, continuing anyway" "warn" fi else log_message "Failed to change APN to $apn" "error" update_track "error" "Failed to set APN" "$profile_name" "20" apply_success=0 fi fi # Apply network mode change if [ $needs_mode_change -eq 1 ] && [ $apply_success -eq 1 ]; then update_track "applying" "Setting network mode from '$current_mode' to '$network_type'" "$profile_name" "40" log_message "Changing network mode from '$current_mode' to '$network_type'" "info" # Format network mode for AT command (may already be in correct format) local mode_cmd="AT+QNWPREFCFG=\"mode_pref\",$network_type" local output=$(execute_at_command "$mode_cmd" 10 "$token_id") if [ $? -eq 0 ]; then changes_made=1 log_message "Network mode changed successfully to $network_type" "info" update_track "applying" "Network mode set successfully" "$profile_name" "50" # If mode includes NR5G, ensure it's enabled if echo "$network_type" | grep -q "NR5G"; then log_message "Ensuring NR5G is enabled" "debug" local nr5g_cmd="AT+QNWPREFCFG=\"nr5g_disable_mode\",0" execute_at_command "$nr5g_cmd" 5 "$token_id" fi else log_message "Failed to change network mode to $network_type" "error" update_track "applying" "Failed to set network mode, continuing" "$profile_name" "45" fi fi # Apply LTE bands change if [ $needs_lte_bands_change -eq 1 ] && [ $apply_success -eq 1 ] && [ -n "$lte_bands" ]; then update_track "applying" "Setting LTE bands from '$current_lte_bands' to '$lte_bands'" "$profile_name" "60" log_message "Changing LTE bands from '$current_lte_bands' to '$lte_bands'" "info" # Convert comma-separated to colon-separated for AT command local bands_formatted=$(echo "$lte_bands" | tr ',' ':') local bands_cmd="AT+QNWPREFCFG=\"lte_band\",$bands_formatted" local output=$(execute_at_command "$bands_cmd" 10 "$token_id") if [ $? -eq 0 ]; then changes_made=1 log_message "LTE bands changed successfully to $lte_bands" "info" update_track "applying" "LTE bands set successfully" "$profile_name" "70" else log_message "Failed to change LTE bands to $lte_bands" "error" update_track "applying" "Failed to set LTE bands, continuing" "$profile_name" "65" fi fi # Apply NSA NR5G bands change if [ $needs_nsa_nr5g_bands_change -eq 1 ] && [ $apply_success -eq 1 ] && [ -n "$nsa_nr5g_bands" ]; then update_track "applying" "Setting NSA NR5G bands from '$current_nsa_nr5g_bands' to '$nsa_nr5g_bands'" "$profile_name" "75" log_message "Changing NSA NR5G bands from '$current_nsa_nr5g_bands' to '$nsa_nr5g_bands'" "info" # Convert comma-separated to colon-separated for AT command local bands_formatted=$(echo "$nsa_nr5g_bands" | tr ',' ':') local nsa_cmd="AT+QNWPREFCFG=\"nsa_nr5g_band\",$bands_formatted" local output=$(execute_at_command "$nsa_cmd" 10 "$token_id") if [ $? -eq 0 ]; then changes_made=1 log_message "NSA NR5G bands changed successfully to $nsa_nr5g_bands" "info" update_track "applying" "NSA NR5G bands set successfully" "$profile_name" "80" else log_message "Failed to change NSA NR5G bands to $nsa_nr5g_bands" "error" update_track "applying" "Failed to set NSA NR5G bands, continuing" "$profile_name" "75" fi fi # Apply SA NR5G bands change if [ $needs_sa_nr5g_bands_change -eq 1 ] && [ $apply_success -eq 1 ] && [ -n "$sa_nr5g_bands" ]; then update_track "applying" "Setting SA NR5G bands from '$current_sa_nr5g_bands' to '$sa_nr5g_bands'" "$profile_name" "85" log_message "Changing SA NR5G bands from '$current_sa_nr5g_bands' to '$sa_nr5g_bands'" "info" # Convert comma-separated to colon-separated for AT command local bands_formatted=$(echo "$sa_nr5g_bands" | tr ',' ':') local sa_cmd="AT+QNWPREFCFG=\"nr5g_band\",$bands_formatted" local output=$(execute_at_command "$sa_cmd" 10 "$token_id") if [ $? -eq 0 ]; then changes_made=1 log_message "SA NR5G bands changed successfully to $sa_nr5g_bands" "info" update_track "applying" "SA NR5G bands set successfully" "$profile_name" "90" else log_message "Failed to change SA NR5G bands to $sa_nr5g_bands" "error" update_track "applying" "Failed to set SA NR5G bands, continuing" "$profile_name" "85" fi fi # Apply TTL change if needed if [ $needs_ttl_change -eq 1 ] && [ $apply_success -eq 1 ]; then apply_ttl_settings "$ttl" "$current_ttl" "$token_id" "$profile_name" if [ $? -eq 0 ]; then changes_made=1 log_message "TTL settings applied successfully" "info" fi fi # Apply IMEI change (requires reboot) if [ $needs_imei_change -eq 1 ] && [ $apply_success -eq 1 ] && [ -n "$imei" ]; then update_track "applying" "Setting IMEI from '$current_imei' to '$imei'" "$profile_name" "95" log_message "Changing IMEI from '$current_imei' to '$imei'" "info" local imei_cmd="AT+EGMR=1,7,\"$imei\"" local output=$(execute_at_command "$imei_cmd" 10 "$token_id") if [ $? -eq 0 ]; then changes_made=1 requires_reboot=1 change_for_reboot="IMEI" log_message "IMEI changed successfully to $imei (device will reboot)" "info" update_track "rebooting" "IMEI changed, device will reboot" "$profile_name" "95" else log_message "Failed to change IMEI to $imei" "error" update_track "applying" "Failed to set IMEI, continuing" "$profile_name" "90" requires_reboot=0 fi fi # Apply unique rule setup for Verizon, but also handle "Other" Mobile Providers because of MPDN_rule shenanigans # Probably requires reboot output_check=$(execute_at_command "AT+QMAP=\"mpdn_rule\"") sleep 1 # Short delay to ensure command is processed qmap_rule0=$(echo "$output_check" | grep '+QMAP: "MPDN_rule",0,') qmap_ippt_rule0=$(echo "$qmap_rule0" | cut -d',' -f5) if [ $apply_success -eq 1 ] && [ -n "$mobile_provider" ]; then if [ "$mobile_provider" = "Verizon" ]; then # If Verizon, data call should be set to rule 3, AT+QMAP="mpdn_rule",0,3,0,0,1 if echo "$qmap_rule0" | awk -F',' '{exit !($2==0 && $3==3 && $6==1)}'; then log_message "Verizon rule already set correctly, no changes needed" "info" else log_message "Setting Verizon data call mpdn_rule to 3" "info" update_track "applying" "Setting Verizon data call rule to 3" "$profile_name" "100" verizon_cmd="AT+QMAP=\"mpdn_rule\",0,3,0,$qmap_ippt_rule0,1" execute_at_command "$verizon_cmd" 10 "$token_id" >/dev/null sleep 1 # Short delay to ensure command is processed fi elif [ "$mobile_provider" = "Other" ]; then # Check if MPDN_rule 0 is already set to all zeros if echo "$qmap_rule0" | awk -F',' '{exit !($2==0 && $3==0 && $6==0)}'; then log_message "Default rule already set correctly, no changes needed" "info" else log_message "Setting to default mpdn_rule and releasing" "info" update_track "applying" "Setting Default data call mpdn_rule to 0" "$profile_name" "100" def_cmd1="AT+QMAP=\"mpdn_rule\",0" execute_at_command "$def_cmd1" 10 "$token_id" sleep 1 # Short delay to ensure command is processed def_cmd2="AT+QMAP=\"mpdn_rule\",0,1,0,$qmap_ippt_rule0,1" execute_at_command "$def_cmd2" 10 "$token_id" sleep 1 # Short delay to ensure command is processed if [ "$qmap_ippt_rule0" = "0" ]; then log_message "IPPT is disabled for rule, release the MPDN_rule" "info" def_cmd3="AT+QMAP=\"mpdn_rule\",0" execute_at_command "$def_cmd3" 10 "$token_id" sleep 1 # Short delay to ensure command is processed if [ "$(cat /sys/devices/soc0/machine)" = "SDXPINN" ]; then requires_reboot=1 change_for_reboot="MPDN_rule" update_track "rebooting" "MPDN_rule released, device will reboot" "$profile_name" "105" fi else log_message "IPPT is enabled for rule0 not releasing MPDN_rule, no reboot needed: IPPT Value $qmap_ippt_rule0" "info" fi fi fi fi # Release token release_token "$token_id" # Mark profile as applied if changes were made if [ $changes_made -eq 1 ]; then mark_profile_applied "$iccid" "$profile_name" fi # If IMEI was changed, need to reboot if [ $requires_reboot -eq 1 ]; then log_message "IMEI change requires reboot, scheduling reboot..." "info" update_track "rebooting" "Device is rebooting to apply $change_for_reboot change" "$profile_name" "100" sleep 2 reboot & return 0 fi # Force network reset if changes were made but no reboot required if [ $changes_made -eq 1 ] && [ $requires_reboot -eq 0 ]; then log_message "Changes applied, resetting network connection to apply changes" "info" update_track "applying" "Resetting network connection" "$profile_name" "95" # Get a new token for network reset token_id=$(acquire_token) if [ -n "$token_id" ]; then # Force PDP context reconnection - note: errors here are common and non-fatal log_message "Forcing network reconnection" "info" execute_at_command "AT+COPS=2" 5 "$token_id" || true sleep 2 execute_at_command "AT+COPS=0" 5 "$token_id" || true sleep 1 # Release token release_token "$token_id" fi fi # Check if any changes were made if [ $changes_made -eq 0 ]; then log_message "Profile '$profile_name' already applied correctly, no changes needed" "info" update_track "success" "Profile already correctly applied" "$profile_name" "100" else log_message "Successfully applied profile '$profile_name'" "info" update_track "success" "Profile applied successfully" "$profile_name" "100" fi return 0 } # Check profile function with updated SA/NSA bands and TTL support check_profile() { local forced="${1:-0}" log_message "Performing profile check (forced=$forced)" "info" # Get all modem data at once with a single token local modem_data="" modem_data=$(fetch_modem_data) if [ $? -ne 0 ]; then log_message "Failed to fetch modem data, will retry later" "error" update_track "error" "Could not communicate with modem. Will retry later." "unknown" "0" return 1 fi # Extract ICCID from modem data local current_iccid="" current_iccid=$(extract_iccid "$modem_data") if [ $? -ne 0 ]; then log_message "Failed to extract ICCID from modem data, will retry later" "error" update_track "error" "Could not detect SIM card. Please check that a SIM is inserted." "unknown" "0" return 1 fi log_message "Current ICCID: $current_iccid" "info" # Find profile for current ICCID local profile_index="" profile_index=$(find_profile_by_iccid "$current_iccid") local profile_result=$? # CRITICAL FIX: Early return if no profile is found if [ $profile_result -ne 0 ]; then log_message "No profile found for ICCID $current_iccid, nothing to apply" "info" update_track "idle" "No profile exists for current SIM card. Create a profile to configure network settings." "$current_iccid" "0" return 0 fi # Only continue if we found a valid profile log_message "Found valid profile index: $profile_index" "debug" # Get profile details local profile_name=$(uci -q get quecprofiles.$profile_index.name) local network_type=$(uci -q get quecprofiles.$profile_index.network_type) local lte_bands=$(uci -q get quecprofiles.$profile_index.lte_bands) local sa_nr5g_bands=$(uci -q get quecprofiles.$profile_index.sa_nr5g_bands) local nsa_nr5g_bands=$(uci -q get quecprofiles.$profile_index.nsa_nr5g_bands) local apn=$(uci -q get quecprofiles.$profile_index.apn) local pdp_type=$(uci -q get quecprofiles.$profile_index.pdp_type) local imei=$(uci -q get quecprofiles.$profile_index.imei) local ttl=$(uci -q get quecprofiles.$profile_index.ttl) local mobile_provider=$(uci -q get quecprofiles.$profile_index.mobile_provider) # Check if profile is paused local paused=$(uci -q get quecprofiles.$profile_index.paused) paused="${paused:-0}" # Default to not paused if not set # Skip applying paused profiles if [ "$paused" = "1" ]; then log_message "Profile '$profile_name' is paused, skipping application" "info" update_track "paused" "Profile is paused. Resume the profile to apply settings." "$profile_name" "0" return 0 fi # Default pdp_type to "IP" if not specified pdp_type="${pdp_type:-IP}" # Default TTL to 0 (disabled) if not specified ttl="${ttl:-0}" # For backward compatibility - check if old nr5g_bands exists but new fields don't local nr5g_bands=$(uci -q get quecprofiles.$profile_index.nr5g_bands) if [ -n "$nr5g_bands" ] && [ -z "$sa_nr5g_bands" ] && [ -z "$nsa_nr5g_bands" ]; then sa_nr5g_bands=$nr5g_bands nsa_nr5g_bands=$nr5g_bands log_message "Migrating legacy nr5g_bands for profile $profile_name" "info" fi log_message "Found profile: $profile_name for ICCID: $current_iccid" "info" log_message "Profile settings: network_type=$network_type, lte_bands=$lte_bands, sa_nr5g_bands=$sa_nr5g_bands, nsa_nr5g_bands=$nsa_nr5g_bands, apn=$apn, pdp_type=$pdp_type, imei=$imei, ttl=$ttl" "info" # Check if APN is configured - it's the minimum required setting if [ -z "$apn" ]; then log_message "Profile has no APN configured, cannot apply" "error" update_track "error" "Profile \"$profile_name\" is missing the required APN setting. Please edit the profile and add an APN." "$profile_name" "0" return 1 fi # Check if profile is already applied (unless forced) if [ "$forced" != "1" ] && is_profile_applied "$current_iccid" "$profile_name"; then log_message "Profile '$profile_name' is already applied, skipping" "info" update_track "success" "Profile already applied (from flag)" "$profile_name" "100" return 0 fi # Apply profile if forced or if autoswitch is enabled local enable_autoswitch enable_autoswitch=$(uci -q get quecprofiles.settings.enable_autoswitch) enable_autoswitch="${enable_autoswitch:-1}" # Default to enabled if [ "$forced" = "1" ] || [ "$enable_autoswitch" = "1" ]; then log_message "Applying profile settings..." "info" update_track "applying" "Applying profile settings" "$profile_name" "10" # Extract current modem settings for comparison local current_apn="" local current_mode="" local current_lte_bands="" local current_sa_nr5g_bands="" local current_nsa_nr5g_bands="" local current_imei="" current_apn=$(extract_apn "$modem_data") current_mode=$(extract_network_mode "$modem_data") current_lte_bands=$(extract_lte_bands "$modem_data") current_sa_nr5g_bands=$(extract_nr5g_bands "$modem_data" "sa") current_nsa_nr5g_bands=$(extract_nr5g_bands "$modem_data" "nsa") current_imei=$(extract_imei "$modem_data") # Apply profile settings with the new parameters apply_profile_settings "$profile_name" "$network_type" "$lte_bands" "$sa_nr5g_bands" "$nsa_nr5g_bands" \ "$apn" "$pdp_type" "$imei" "$ttl" "$current_apn" "$current_mode" "$current_lte_bands" \ "$current_sa_nr5g_bands" "$current_nsa_nr5g_bands" "$current_imei" "$current_iccid" "$mobile_provider" return $? else log_message "Automatic profile switching is disabled, not applying profile" "info" update_track "idle" "Automatic profile switching is disabled" "$profile_name" "0" return 0 fi } # Main function main() { log_message "QuecProfiles daemon starting with SA/NSA NR5G and TTL support (PID: $$)" "info" # Clear status files at startup rm -f "$TRACK_FILE" "$CHECK_TRIGGER" update_track "idle" "Daemon started" "none" "0" # Get check interval from UCI local check_interval check_interval=$(uci -q get quecprofiles.settings.check_interval) check_interval="${check_interval:-$DEFAULT_CHECK_INTERVAL}" # Check autoswitch setting local enable_autoswitch enable_autoswitch=$(uci -q get quecprofiles.settings.enable_autoswitch) enable_autoswitch="${enable_autoswitch:-1}" # Default to enabled log_message "Daemon configured with check_interval=$check_interval seconds, enable_autoswitch=$enable_autoswitch" "info" # Add a startup delay log_message "Waiting 10 seconds before initial check..." "info" sleep 10 # Main loop while true; do # Check if there's a manual check request if [ -f "$CHECK_TRIGGER" ]; then log_message "Manual check triggered" "info" rm -f "$CHECK_TRIGGER" check_profile 1 # Forced check elif [ "$enable_autoswitch" -eq 1 ]; then # Perform regular check check_profile 0 # Regular check else log_message "Automatic profile switching is disabled" "info" update_track "idle" "Automatic profile switching is disabled" "none" "0" fi # Sleep for the check interval log_message "Sleeping for $check_interval seconds" "info" # Break the sleep into smaller intervals to check for triggers sleep_counter=0 while [ $sleep_counter -lt $check_interval ]; do sleep 5 sleep_counter=$((sleep_counter + 5)) # Check for manual trigger during sleep if [ -f "$CHECK_TRIGGER" ]; then log_message "Manual check triggered during sleep" "info" break fi done done } # Set up trap handlers for clean shutdown trap 'log_message "Received SIGTERM, exiting"; update_track "idle" "Daemon stopped" "none" "0"; exit 0' TERM trap 'log_message "Received SIGINT, exiting"; update_track "idle" "Daemon stopped" "none" "0"; exit 0' INT # Start the main function main