Merging Beta 2.0.0 Release Candidate

This commit is contained in:
Russel Yasol
2025-03-11 15:21:04 +08:00
parent 0ce398b6e5
commit 36874b12f0
169 changed files with 8794 additions and 3057 deletions

View File

@@ -0,0 +1,266 @@
#!/bin/sh
# AT Queue Client for OpenWRT
# Located in /www/cgi-bin/services/at_queue_client
QUEUE_DIR="/tmp/at_queue"
RESULTS_DIR="$QUEUE_DIR/results"
QUEUE_MANAGER="/www/cgi-bin/services/at_queue_manager.sh"
POLL_INTERVAL=0.01
usage() {
echo "Usage: $0 [options] <AT command>"
echo "Options:"
echo " -w Wait for command completion"
echo " -t Timeout in seconds (default: 240)"
echo " -h Show this help message"
exit 1
}
# Output JSON response
output_json() {
local content="$1"
local headers="${2:-1}" # Default to showing headers
echo "$content"
}
# URL decode function
urldecode() {
local encoded="$1"
logger -t at_queue -p daemon.debug "urldecode: input='$encoded'"
# Handle %2B -> + and %22 -> " conversions
local decoded="${encoded//%2B/+}"
decoded="${decoded//%22/\"}"
# Then handle other encoded characters
decoded=$(printf '%b' "${decoded//%/\\x}")
logger -t at_queue -p daemon.debug "urldecode: output='$decoded'"
echo "$decoded"
}
# Extract command ID from response with improved error handling
get_command_id() {
local response="$1"
echo "DEBUG: Raw response: '$response'" >&2
# Strip any headers from response
local json_response=$(echo "$response" | sed -n '/^{/,$p')
echo "DEBUG: JSON portion: '$json_response'" >&2
# Try to extract command_id using grep and sed instead of jsonfilter
local cmd_id=$(echo "$json_response" | grep -o '"command_id":"[^"]*"' | sed 's/"command_id":"//;s/"$//')
if [ -n "$cmd_id" ]; then
echo "$cmd_id"
return 0
fi
# Fallback to jsonfilter if available
echo "DEBUG: Trying jsonfilter as fallback" >&2
local cmd_id_jsonfilter=$(echo "$json_response" | jsonfilter -e '@.command_id' 2>/dev/null)
if [ -n "$cmd_id_jsonfilter" ]; then
echo "$cmd_id_jsonfilter"
return 0
fi
echo "ERROR: Failed to extract command ID from response" >&2
return 1
}
# Normalize AT command
normalize_at_command() {
local cmd="$1"
logger -t at_queue -p daemon.debug "normalize: input='$cmd'"
# URL decode the command
cmd=$(urldecode "$cmd")
logger -t at_queue -p daemon.debug "normalize: after urldecode='$cmd'"
# Remove any carriage returns or newlines
cmd=$(echo "$cmd" | tr -d '\r\n')
logger -t at_queue -p daemon.debug "normalize: after cleanup='$cmd'"
# Trim leading/trailing whitespace while preserving quotes
cmd=$(echo "$cmd" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
logger -t at_queue -p daemon.debug "normalize: final output='$cmd'"
echo "$cmd"
}
# Submit command with priority handling
submit_command() {
local cmd="$1"
local priority=10
# Set high priority for QSCAN commands for faster processing
if echo "$cmd" | grep -qi "AT+QSCAN"; then
priority=1
fi
# Submit using appropriate method
if [ "${SCRIPT_NAME}" != "" ]; then
# CGI mode - direct execution
local escaped_cmd=$(echo "$cmd" | sed 's/"/\\"/g')
QUERY_STRING="action=enqueue&command=${escaped_cmd}&priority=$priority" "$QUEUE_MANAGER"
else
# CLI mode
"$QUEUE_MANAGER" enqueue "$cmd" "$priority"
fi
}
# Check if result exists with proper error handling
check_result() {
local cmd_id="$1"
local show_headers="${2:-1}" # Add parameter for header control
if [ -f "$RESULTS_DIR/$cmd_id.json" ]; then
local result_content=$(cat "$RESULTS_DIR/$cmd_id.json")
if [ -z "$result_content" ]; then
logger -t at_queue -p daemon.error "Empty result file for command ID: $cmd_id"
local error_json="{\"error\":\"Empty result file\",\"command_id\":\"$cmd_id\"}"
output_json "$error_json" "$show_headers"
return 1
fi
output_json "$result_content" "$show_headers"
return 0
fi
local error_json="{\"error\":\"Result not found\",\"command_id\":\"$cmd_id\"}"
output_json "$error_json" "$show_headers"
return 1
}
# Wait for command completion with optimized polling and better error handling
wait_for_completion() {
local cmd_id="$1"
local timeout="$2"
local show_headers="${3:-1}"
local result_file="$RESULTS_DIR/$cmd_id.json"
if [ -z "$cmd_id" ]; then
local error_json="{\"error\":\"Invalid command ID\"}"
output_json "$error_json" "$show_headers"
return 1
fi
# First quick check
if [ -f "$result_file" ]; then
output_json "$(cat "$result_file")" "$show_headers"
return 0
fi
# Wait with shorter polling interval
local start_time=$(date +%s)
local current_time
while true; do
if [ -f "$result_file" ]; then
output_json "$(cat "$result_file")" "$show_headers"
return 0
fi
current_time=$(date +%s)
if [ $((current_time - start_time)) -ge "$timeout" ]; then
break
fi
sleep $POLL_INTERVAL
done
local error_json=$(cat << EOF
{
"error": "Timeout waiting for completion",
"command_id": "$cmd_id",
"timeout": $timeout
}
EOF
)
output_json "$error_json" "$show_headers"
return 1
}
# CGI request handling
if [ "${SCRIPT_NAME}" != "" ]; then
# Output headers only once at the beginning
echo "Content-Type: application/json"
echo ""
# Parse query string
eval $(echo "$QUERY_STRING" | sed 's/&/;/g')
# Handle different actions
if [ -n "$command_id" ]; then
# Get result for specific command ID
check_result "$command_id" "0" # Don't show headers
elif [ -n "$command" ]; then
# URL decode and normalize the command
command=$(urldecode "$command")
command=$(normalize_at_command "$command")
# Check if it's a valid AT command
if echo "$command" | grep -qi "^AT"; then
# Submit command and get response
response=$(submit_command "$command")
cmd_id=$(get_command_id "$response")
if [ "$wait" = "1" ]; then
if [ -n "$cmd_id" ]; then
wait_for_completion "$cmd_id" "${timeout:-180}" "0" # Don't show headers
else
output_json "{\"error\":\"Failed to get command ID from response\",\"response\":\"$response\"}" "0"
fi
else
output_json "$response" "0" # Don't show headers
fi
else
output_json "{\"error\":\"Invalid AT command format\"}" "0"
fi
else
output_json "{\"error\":\"No command or command_id specified\"}" "0"
fi
exit 0
fi
# CLI processing
wait_mode=0
timeout=180
while getopts "wt:h" opt; do
case $opt in
w) wait_mode=1 ;;
t) timeout="$OPTARG" ;;
h) usage ;;
?) usage ;;
esac
done
shift $((OPTIND-1))
if [ $# -eq 0 ]; then
usage
fi
# Combine remaining arguments into AT command
command="$*"
# Validate AT command format
if ! echo "$command" | grep -qi "^AT"; then
echo "Error: Command must start with 'AT'"
exit 1
fi
# Submit command and get response
response=$(submit_command "$command")
cmd_id=$(get_command_id "$response")
if [ -z "$cmd_id" ]; then
echo "Error: Failed to get command ID"
echo "Response: $response"
exit 1
fi
if [ $wait_mode -eq 1 ]; then
wait_for_completion "$cmd_id" "$timeout"
else
echo "$response"
fi

View File

@@ -0,0 +1,195 @@
#!/bin/sh
# Set content-type for JSON response
echo "Content-type: application/json"
echo ""
# Define paths and constants to match queue system
QUEUE_DIR="/tmp/at_queue"
QUEUE_MANAGER="/www/cgi-bin/services/at_queue_manager"
LOCK_ID="FETCH_DATA_$(date +%s)_$$"
TOKEN_FILE="$QUEUE_DIR/token"
# Logging function (minimized)
log_message() {
# Only log errors and critical info
if [ "$1" = "error" ] || [ "$1" = "crit" ]; then
logger -t at_queue -p "daemon.$1" "$2"
fi
}
# Enhanced JSON string escaping function
escape_json() {
printf '%s' "$1" | awk '
BEGIN { RS="\n"; ORS="\\n" }
{
gsub(/\\/, "\\\\")
gsub(/"/, "\\\"")
gsub(/\r/, "")
gsub(/\t/, "\\t")
gsub(/\f/, "\\f")
gsub(/\b/, "\\b")
print
}
' | sed 's/\\n$//'
}
# Acquire token directly (avoid CGI overhead)
acquire_token() {
local priority="${1:-10}"
local max_attempts=10
local attempt=0
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
rm -f "$TOKEN_FILE" 2>/dev/null
elif [ $priority -lt $current_priority ]; then
# Preempt lower priority token
rm -f "$TOKEN_FILE" 2>/dev/null
else
# Try again
sleep 0.1
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
return 0
fi
sleep 0.1
attempt=$((attempt + 1))
done
return 1
}
# Release token directly
release_token() {
# Only remove if it's our token
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
fi
fi
}
# Direct AT command execution with minimal overhead
execute_at_command() {
local CMD="$1"
sms_tool at "$CMD" -t 3 2>/dev/null
}
# Batch process all commands with a single token
process_all_commands() {
local commands="$1"
local priority="${2:-10}"
local first=1
# Acquire a single token for all commands
if ! acquire_token "$priority"; then
log_message "error" "Failed to acquire token for batch processing"
# Return all failed responses
printf '['
first=1
for cmd in $commands; do
[ $first -eq 0 ] && printf ','
first=0
ESCAPED_CMD=$(escape_json "$cmd")
printf '{"command":"%s","response":"Failed to acquire token","status":"error"}' "${ESCAPED_CMD}"
done
printf ']\n'
return 1
fi
# Process all commands with the single token
printf '['
for cmd in $commands; do
[ $first -eq 0 ] && printf ','
first=0
OUTPUT=$(execute_at_command "$cmd")
local CMD_STATUS=$?
ESCAPED_CMD=$(escape_json "$cmd")
ESCAPED_OUTPUT=$(escape_json "$OUTPUT")
if [ $CMD_STATUS -eq 0 ] && [ -n "$OUTPUT" ]; then
printf '{"command":"%s","response":"%s","status":"success"}' \
"${ESCAPED_CMD}" \
"${ESCAPED_OUTPUT}"
else
printf '{"command":"%s","response":"Command failed","status":"error"}' \
"${ESCAPED_CMD}"
fi
done
printf ']\n'
# Release token after all commands are done
release_token
return 0
}
# Main execution with timeout and proper cleanup
trap 'release_token; exit 1' INT TERM
# Command sets
COMMAND_SET_1='AT+QUIMSLOT? AT+CNUM AT+COPS? AT+CIMI AT+ICCID AT+CGSN AT+CPIN? AT+CGDCONT? AT+CREG? AT+CFUN? AT+QENG="servingcell" AT+QTEMP AT+CGCONTRDP AT+QCAINFO AT+QRSRP AT+QMAP="WWAN" AT+C5GREG=2;+C5GREG? AT+CGREG=2;+CGREG? AT+QRSRQ AT+QSINR'
COMMAND_SET_2='AT+CGDCONT? AT+CGCONTRDP AT+QNWPREFCFG="mode_pref" AT+QNWPREFCFG="nr5g_disable_mode" AT+QUIMSLOT? AT+CFUN=?'
COMMAND_SET_3='AT+CGMI AT+CGMM AT+QGMR AT+CNUM AT+CIMI AT+ICCID AT+CGSN AT+QMAP="LANIP" AT+QMAP="WWAN" AT+QGETCAPABILITY'
COMMAND_SET_4='AT+QMAP="MPDN_RULE" AT+QMAP="DHCPV4DNS" AT+QCFG="usbnet"'
COMMAND_SET_5='AT+QRSRP AT+QRSRQ AT+QSINR AT+QCAINFO AT+QSPN'
COMMAND_SET_6='AT+CEREG=2;+CEREG? AT+C5GREG=2;+C5GREG? AT+CPIN? AT+CGDCONT? AT+CGCONTRDP AT+QMAP="WWAN" AT+QRSRP AT+QTEMP AT+QNETRC?'
COMMAND_SET_7='AT+QNWPREFCFG="policy_band" AT+QNWPREFCFG="lte_band";+QNWPREFCFG="nsa_nr5g_band";+QNWPREFCFG="nr5g_band"'
COMMAND_SET_8='AT+QNWLOCK="common/4g" AT+QNWLOCK="common/5g" AT+QNWLOCK="save_ctrl"'
# Get command set from query string with validation
COMMAND_SET=$(echo "$QUERY_STRING" | grep -o 'set=[1-8]' | cut -d'=' -f2 | tr -cd '0-9')
if [ -z "$COMMAND_SET" ] || [ "$COMMAND_SET" -lt 1 ] || [ "$COMMAND_SET" -gt 8 ]; then
COMMAND_SET=1
fi
# Select the appropriate command set
case "$COMMAND_SET" in
1) COMMANDS="$COMMAND_SET_1";;
2) COMMANDS="$COMMAND_SET_2";;
3) COMMANDS="$COMMAND_SET_3";;
4) COMMANDS="$COMMAND_SET_4";;
5) COMMANDS="$COMMAND_SET_5";;
6) COMMANDS="$COMMAND_SET_6";;
7) COMMANDS="$COMMAND_SET_7";;
8) COMMANDS="$COMMAND_SET_8";;
esac
# Set priority based on content
PRIORITY=10
if echo "$COMMANDS" | grep -qi "AT+QSCAN"; then
PRIORITY=1
fi
# Process commands with timeout protection
( sleep 60; kill -TERM $$ 2>/dev/null ) &
TIMEOUT_PID=$!
process_all_commands "$COMMANDS" "$PRIORITY"
# Clean up
kill $TIMEOUT_PID 2>/dev/null
release_token